faisalsns commited on
Commit
d0f78af
Β·
1 Parent(s): 797c475

Initial commit

Browse files
Files changed (4) hide show
  1. README.md +25 -1
  2. app.py +167 -0
  3. requirements.txt +6 -0
  4. styles.css +83 -0
README.md CHANGED
@@ -11,4 +11,28 @@ license: mit
11
  short_description: Try LLM's in a side by side comparison with prompt levers
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  short_description: Try LLM's in a side by side comparison with prompt levers
12
  ---
13
 
14
+ # README.md
15
+ # 🧠 Prompt Canvas Engine
16
+
17
+ Test different prompt phrasings across two LLMs and compare outputs side-by-side.
18
+
19
+
20
+ ## Features
21
+ - Compare any two models supported by OpenRouter
22
+ - Select or customize prompts
23
+ - Tune temperature, top_p, penalties, and max_tokens
24
+ - See outputs side-by-side
25
+ - Rate model outputs
26
+ - Share publicly
27
+
28
+
29
+ ## πŸš€ Run locally
30
+ ```bash
31
+ export OPENROUTER_API_KEY=your_key_here
32
+ python app.py
33
+ ```
34
+
35
+ ## πŸ“€ Deploy on Hugging Face
36
+ - Create a new **Gradio Space**
37
+ - Upload `app.py`, `requirements.txt`, and `README.md`
38
+ - Set `OPENROUTER_API_KEY` in Space secrets
app.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ API_KEY = os.getenv("OPENROUTER_API_KEY")
9
+ API_URL = "https://openrouter.ai/api/v1/chat/completions"
10
+
11
+ MODEL_METADATA = {
12
+ "mistralai/mistral-7b-instruct": "⚑ Free (Very Fast)",
13
+ "meta-llama/llama-3-70b-instruct": "⚑ Free (Very Accurate)",
14
+ "google/gemini-2.0-flash-exp:free": "⚑ Free (Fast, Token Efficient)",
15
+
16
+ "huggingfaceh4/zephyr-7b-beta": "πŸͺ™ Token-efficient (Compact, Responsive)",
17
+ "nousresearch/nous-capybara-7b": "πŸͺ™ Token-efficient (Lightweight)",
18
+ "meta-llama/llama-3-8b-instruct": "πŸͺ™ Token-efficient (Fast, Balanced)",
19
+
20
+ "openai/gpt-3.5-turbo": "πŸ’° Paid (Fast, Cheap)",
21
+ "openai/gpt-4": "πŸ’° Paid (Accurate, Expensive)",
22
+ "anthropic/claude-3-haiku": "πŸ’° Paid (Long Context, Fast)",
23
+ "cohere/command-r-plus": "πŸ’° Paid (Context-Aware)"
24
+ }
25
+
26
+ MODEL_OPTIONS = list(MODEL_METADATA.keys())
27
+ FREE_MODELS = [m for m in MODEL_OPTIONS if "⚑ Free" in MODEL_METADATA[m]]
28
+
29
+ def get_default_model2(model1):
30
+ for m in FREE_MODELS:
31
+ if m != model1:
32
+ return f"{m} {MODEL_METADATA[m]}"
33
+ return f"{FREE_MODELS[0]} {MODEL_METADATA[FREE_MODELS[0]]}"
34
+
35
+ rating_store = []
36
+
37
+ def run_dual_model(prompt, model1, model2, temperature, top_p, max_tokens, freq_penalty, pres_penalty):
38
+ outputs = []
39
+ for model in [model1, model2]:
40
+ data = {
41
+ "model": model,
42
+ "messages": [
43
+ {"role": "system", "content": "You are a helpful assistant."},
44
+ {"role": "user", "content": prompt}
45
+ ],
46
+ "temperature": temperature,
47
+ "top_p": top_p,
48
+ "max_tokens": max_tokens,
49
+ "frequency_penalty": freq_penalty,
50
+ "presence_penalty": pres_penalty
51
+ }
52
+ headers = {
53
+ "Authorization": f"Bearer {API_KEY}",
54
+ "Content-Type": "application/json"
55
+ }
56
+ try:
57
+ resp = requests.post(API_URL, headers=headers, json=data)
58
+ resp_data = resp.json()
59
+ if "choices" not in resp_data:
60
+ outputs.append(f"API Error: {resp_data.get('error', 'No choices')}")
61
+ else:
62
+ outputs.append(resp_data["choices"][0]["message"]["content"].strip())
63
+ except Exception as e:
64
+ outputs.append(f"Error: {e}")
65
+ return tuple(outputs)
66
+
67
+ def record_feedback(prompt_index, rating):
68
+ rating_store.append((prompt_index, rating))
69
+ return f"Feedback saved: Output {prompt_index+1} rated {rating}"
70
+
71
+ def extract_model(raw_label):
72
+ return raw_label.split(" ")[0]
73
+
74
+ def build_combined_prompt(task, user_text):
75
+ return f"{task}\n\n{user_text}"
76
+
77
+ def update_model2_on_model1_change(m1_val):
78
+ model_key = extract_model(m1_val)
79
+ return get_default_model2(model_key)
80
+
81
+ # Load CSS from external file
82
+ def load_css():
83
+ try:
84
+ with open('styles.css', 'r') as f:
85
+ return f.read()
86
+ except FileNotFoundError:
87
+ print("Warning: styles.css not found. Using default styling.")
88
+ return ""
89
+
90
+ css = load_css()
91
+
92
+ with gr.Blocks(css=css, title="Prompt Canvas Engine") as demo:
93
+ gr.Markdown("# 🧠 Prompt Canvas Lab\n## **Compare prompt effectiveness across two language models.**")
94
+
95
+ with gr.Row():
96
+ model1 = gr.Dropdown(label="Model 1", choices=[f"{k} {MODEL_METADATA[k]}" for k in MODEL_OPTIONS], value="mistralai/mistral-7b-instruct ⚑ Free (Very Fast)")
97
+ model2 = gr.Dropdown(label="Model 2", choices=[f"{k} {MODEL_METADATA[k]}" for k in MODEL_OPTIONS], value="meta-llama/llama-3-70b-instruct ⚑ Free (Very Accurate)")
98
+
99
+ with gr.Row():
100
+ prompt_selector = gr.Dropdown(label="Prompt", choices=[
101
+ "Summarize the paragraph in 3 points.",
102
+ "Translate this sentence to French.",
103
+ "Write a one-sentence TL;DR.",
104
+ "Fix grammar and spelling mistakes.",
105
+ "Extract keywords from the text.",
106
+ "Convert this bullet list into a paragraph.",
107
+ "Write a short product description for SEO."
108
+ ], value="Summarize the paragraph in 3 points.", interactive=True, scale=1)
109
+
110
+ user_input = gr.Textbox(label="Input Text", value="A qubitβ€”short for quantum bitβ€”is the fundamental unit of quantum information. Unlike classical bits that exist strictly as 0 or 1, a qubit can exist in a superposition of both states simultaneously, thanks to the principles of quantum mechanics. This allows quantum computers to process complex problems with exponentially greater efficiency.", lines=3, max_lines=3, show_copy_button=True)
111
+
112
+ # Parameter controls section
113
+ with gr.Row():
114
+ gr.Markdown("## **Parameter Guide**")
115
+
116
+ with gr.Row(elem_classes="params-row"):
117
+ with gr.Column(elem_classes="param-sliders"):
118
+ temperature = gr.Slider(0.0, 1.0, value=0.7, step=0.1, label="Temperature: Controls randomness (higher = more creative)", interactive=True)
119
+ top_p = gr.Slider(0.1, 1.0, value=1.0, step=0.1, label="Top-p: Nucleus sampling (higher = more variety)", interactive=True)
120
+ max_tokens = gr.Slider(32, 1024, value=256, step=32, label="Max Tokens: Limits response length", interactive=True)
121
+ freq_penalty = gr.Slider(-2.0, 2.0, value=0.0, step=0.1, label="Frequency Penalty: Reduces repetition", interactive=True)
122
+ pres_penalty = gr.Slider(-2.0, 2.0, value=0.0, step=0.1, label="Presence Penalty: Encourages topic diversity", interactive=True)
123
+
124
+ run_btn = gr.Button("Run", elem_classes="run-btn")
125
+
126
+ with gr.Row():
127
+ out1 = gr.Textbox(label="Model 1 Output", lines=6)
128
+ out2 = gr.Textbox(label="Model 2 Output", lines=6)
129
+
130
+ with gr.Row(elem_classes="rating-row"):
131
+ rating1 = gr.Radio(choices=["πŸ‘", "πŸ‘Ž"], label="Rate Model 1")
132
+ rating2 = gr.Radio(choices=["πŸ‘", "πŸ‘Ž"], label="Rate Model 2")
133
+
134
+ feedback_msg = gr.Textbox(visible=False)
135
+
136
+ run_btn.click(
137
+ fn=lambda task, user_text, m1, m2, t, p, mt, fp, pp: run_dual_model(
138
+ build_combined_prompt(task, user_text),
139
+ extract_model(m1),
140
+ extract_model(m2),
141
+ t, p, mt, fp, pp),
142
+ inputs=[prompt_selector, user_input, model1, model2, temperature, top_p, max_tokens, freq_penalty, pres_penalty],
143
+ outputs=[out1, out2]
144
+ )
145
+
146
+ model1.change(update_model2_on_model1_change, inputs=model1, outputs=model2)
147
+
148
+ rating1.change(fn=record_feedback, inputs=[gr.State(0), rating1], outputs=feedback_msg)
149
+ rating2.change(fn=record_feedback, inputs=[gr.State(1), rating2], outputs=feedback_msg)
150
+
151
+ # Add shareable link section
152
+ with gr.Row():
153
+ gr.Markdown("---")
154
+
155
+ with gr.Row():
156
+ gr.Markdown("## **Share this workspace:** ")
157
+ share_link = gr.Textbox(
158
+ value="https://your-app-url.com",
159
+ label="",
160
+ interactive=False,
161
+ show_copy_button=True,
162
+ scale=4
163
+ )
164
+ gr.Markdown("*Copy this link to collaborate with your team on prompt optimization.*")
165
+
166
+ if __name__ == "__main__":
167
+ demo.launch(share=True) # This will generate a shareable link
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # requirements.txt
2
+ gradio
3
+ requests
4
+ python-dotenv
5
+ openai
6
+
styles.css ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
3
+ background: #f4f7fb;
4
+ }
5
+
6
+ h1, h2, label {
7
+ color: #1f2937;
8
+ font-weight: bold;
9
+ }
10
+
11
+ /* Make all form labels bold */
12
+ .gr-form label,
13
+ .gr-dropdown label,
14
+ .gr-textbox label,
15
+ .gr-slider label,
16
+ .gr-radio label {
17
+ font-weight: bold !important;
18
+ }
19
+
20
+ .gr-button {
21
+ background: linear-gradient(90deg, #4f46e5 0%, #3b82f6 100%) !important;
22
+ color: white !important;
23
+ font-weight: 600;
24
+ border-radius: 8px;
25
+ border: none;
26
+ padding: 10px 24px;
27
+ }
28
+
29
+ .run-btn {
30
+ background: #fa8072 !important;
31
+ color: white !important;
32
+ font-weight: 600;
33
+ border-radius: 8px;
34
+ border: none;
35
+ padding: 12px 32px;
36
+ font-size: 16px;
37
+ }
38
+
39
+ .gr-radio input:checked + span {
40
+ color: #2563eb !important;
41
+ font-weight: 700;
42
+ }
43
+
44
+ .gr-textbox {
45
+ background: #fff;
46
+ border: 1px solid #d1d5db;
47
+ border-radius: 6px;
48
+ max-height: 160px;
49
+ overflow-y: auto;
50
+ resize: none;
51
+ }
52
+
53
+ .gr-dropdown {
54
+ border-radius: 6px;
55
+ }
56
+
57
+ .gr-slider {
58
+ color: #2563eb;
59
+ }
60
+
61
+ .rating-row {
62
+ margin-top: 10px;
63
+ }
64
+
65
+ .params-row {
66
+ display: flex;
67
+ align-items: flex-start;
68
+ gap: 24px;
69
+ }
70
+
71
+ .param-sliders {
72
+ flex: 3;
73
+ }
74
+
75
+ .param-desc {
76
+ flex: 2;
77
+ background: #e0e7ff;
78
+ padding: 12px;
79
+ border-radius: 8px;
80
+ color: #3730a3;
81
+ font-size: 14px;
82
+ line-height: 1.4;
83
+ }