cstr commited on
Commit
a13c2bb
·
verified ·
1 Parent(s): 25aa6b5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +286 -57
app.py CHANGED
@@ -1,79 +1,308 @@
 
 
1
  import gradio as gr
2
  import requests
 
3
  import json
4
- import os
5
- import base64
6
 
7
- # Set your OpenRouter API key in the Space's secrets as "OPENROUTER_API_KEY"
8
- OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
9
- HEADERS = {
10
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
11
- "HTTP-Referer": "https://huggingface.co/spaces/YOUR_SPACE", # Optional
12
- "X-Title": "CrispChat" # Optional
13
- }
14
 
15
- # List of free OpenRouter models
16
- FREE_MODELS = {
17
- "Google: Gemini Pro 2.5 Experimental (free)": ("google/gemini-2.5-pro-exp-03-25:free", 1000000),
18
- "DeepSeek: DeepSeek V3 (free)": ("deepseek/deepseek-chat:free", 131072),
19
- "Meta: Llama 3.2 11B Vision Instruct (free)": ("meta-llama/llama-3.2-11b-vision-instruct:free", 131072),
20
- "Qwen: Qwen2.5 VL 72B Instruct (free)": ("qwen/qwen2.5-vl-72b-instruct:free", 131072),
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- def query_openrouter_model(model_id, prompt, image=None):
24
- # If there's an image, embed it in the prompt as text
25
- if image is not None:
26
- with open(image, "rb") as f:
27
- b64_img = base64.b64encode(f.read()).decode("utf-8")
28
- prompt += f"\n[User attached an image: data:image/png;base64,{b64_img}]"
 
 
 
 
29
 
30
- messages = [
31
- {"role": "user", "content": prompt}
32
- ]
33
 
34
- payload = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  "model": model_id,
36
  "messages": messages
37
  }
38
-
39
- response = requests.post(
40
- url="https://openrouter.ai/api/v1/chat/completions",
41
- headers=HEADERS,
42
- data=json.dumps(payload)
43
- )
44
-
45
  try:
 
 
46
  response.raise_for_status()
47
- data = response.json()
48
- return data["choices"][0]["message"]["content"]
 
 
 
 
 
 
 
49
  except Exception as e:
50
- return f"Error: {str(e)}\n{response.text}"
51
 
52
- def chat_interface(prompt, image, model_label):
53
- model_id, _ = FREE_MODELS[model_label]
54
- return query_openrouter_model(model_id, prompt, image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
- with gr.Blocks(title="CrispChat") as demo:
57
- gr.Markdown("""
58
- # 🌟 CrispChat
59
- Multi-modal chat with free OpenRouter models
 
 
60
  """)
61
-
62
  with gr.Row():
63
- prompt = gr.Textbox(label="Enter your message", lines=4, placeholder="Ask me anything...")
64
- image = gr.Image(type="filepath", label="Optional image input")
65
-
66
- model_choice = gr.Dropdown(
67
- choices=list(FREE_MODELS.keys()),
68
- value="Google: Gemini Pro 2.5 Experimental (free)",
69
- label="Select model"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  )
71
 
72
- output = gr.Textbox(label="Response", lines=6)
73
-
74
- submit = gr.Button("Submit")
75
- submit.click(fn=chat_interface, inputs=[prompt, image, model_choice], outputs=output)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
  if __name__ == "__main__":
78
- # Hide the API documentation to avoid schema issues
79
- demo.launch(show_api=False)
 
1
+ import os
2
+ import base64
3
  import gradio as gr
4
  import requests
5
+ from io import BytesIO
6
  import json
7
+ from PIL import Image
 
8
 
9
+ # Get API key from environment variable for security
10
+ OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
 
 
 
 
 
11
 
12
+ # Model information
13
+ free_models = [
14
+ ("Google: Gemini Pro 2.0 Experimental (free)", "google/gemini-2.0-pro-exp-02-05:free", 0, 0, 2000000),
15
+ ("Google: Gemini 2.0 Flash Thinking Experimental 01-21 (free)", "google/gemini-2.0-flash-thinking-exp:free", 0, 0, 1048576),
16
+ ("Google: Gemini Flash 2.0 Experimental (free)", "google/gemini-2.0-flash-exp:free", 0, 0, 1048576),
17
+ ("Google: Gemini Pro 2.5 Experimental (free)", "google/gemini-2.5-pro-exp-03-25:free", 0, 0, 1000000),
18
+ ("Google: Gemini Flash 1.5 8B Experimental", "google/gemini-flash-1.5-8b-exp", 0, 0, 1000000),
19
+ ("DeepSeek: DeepSeek R1 Zero (free)", "deepseek/deepseek-r1-zero:free", 0, 0, 163840),
20
+ ("DeepSeek: R1 (free)", "deepseek/deepseek-r1:free", 0, 0, 163840),
21
+ ("DeepSeek: DeepSeek V3 Base (free)", "deepseek/deepseek-v3-base:free", 0, 0, 131072),
22
+ ("DeepSeek: DeepSeek V3 0324 (free)", "deepseek/deepseek-chat-v3-0324:free", 0, 0, 131072),
23
+ ("Google: Gemma 3 4B (free)", "google/gemma-3-4b-it:free", 0, 0, 131072),
24
+ ("Google: Gemma 3 12B (free)", "google/gemma-3-12b-it:free", 0, 0, 131072),
25
+ ("Nous: DeepHermes 3 Llama 3 8B Preview (free)", "nousresearch/deephermes-3-llama-3-8b-preview:free", 0, 0, 131072),
26
+ ("Qwen: Qwen2.5 VL 72B Instruct (free)", "qwen/qwen2.5-vl-72b-instruct:free", 0, 0, 131072),
27
+ ("DeepSeek: DeepSeek V3 (free)", "deepseek/deepseek-chat:free", 0, 0, 131072),
28
+ ("NVIDIA: Llama 3.1 Nemotron 70B Instruct (free)", "nvidia/llama-3.1-nemotron-70b-instruct:free", 0, 0, 131072),
29
+ ("Meta: Llama 3.2 1B Instruct (free)", "meta-llama/llama-3.2-1b-instruct:free", 0, 0, 131072),
30
+ ("Meta: Llama 3.2 11B Vision Instruct (free)", "meta-llama/llama-3.2-11b-vision-instruct:free", 0, 0, 131072),
31
+ ("Meta: Llama 3.1 8B Instruct (free)", "meta-llama/llama-3.1-8b-instruct:free", 0, 0, 131072),
32
+ ("Mistral: Mistral Nemo (free)", "mistralai/mistral-nemo:free", 0, 0, 128000),
33
+ ("Mistral: Mistral Small 3.1 24B (free)", "mistralai/mistral-small-3.1-24b-instruct:free", 0, 0, 96000),
34
+ ("Google: Gemma 3 27B (free)", "google/gemma-3-27b-it:free", 0, 0, 96000),
35
+ ("Qwen: Qwen2.5 VL 3B Instruct (free)", "qwen/qwen2.5-vl-3b-instruct:free", 0, 0, 64000),
36
+ ("DeepSeek: R1 Distill Qwen 14B (free)", "deepseek/deepseek-r1-distill-qwen-14b:free", 0, 0, 64000),
37
+ ("Qwen: Qwen2.5-VL 7B Instruct (free)", "qwen/qwen-2.5-vl-7b-instruct:free", 0, 0, 64000),
38
+ ("Google: LearnLM 1.5 Pro Experimental (free)", "google/learnlm-1.5-pro-experimental:free", 0, 0, 40960),
39
+ ("Qwen: QwQ 32B (free)", "qwen/qwq-32b:free", 0, 0, 40000),
40
+ ("Google: Gemini 2.0 Flash Thinking Experimental (free)", "google/gemini-2.0-flash-thinking-exp-1219:free", 0, 0, 40000),
41
+ ("Bytedance: UI-TARS 72B (free)", "bytedance-research/ui-tars-72b:free", 0, 0, 32768),
42
+ ("Qwerky 72b (free)", "featherless/qwerky-72b:free", 0, 0, 32768),
43
+ ("OlympicCoder 7B (free)", "open-r1/olympiccoder-7b:free", 0, 0, 32768),
44
+ ("OlympicCoder 32B (free)", "open-r1/olympiccoder-32b:free", 0, 0, 32768),
45
+ ("Google: Gemma 3 1B (free)", "google/gemma-3-1b-it:free", 0, 0, 32768),
46
+ ("Reka: Flash 3 (free)", "rekaai/reka-flash-3:free", 0, 0, 32768),
47
+ ("Dolphin3.0 R1 Mistral 24B (free)", "cognitivecomputations/dolphin3.0-r1-mistral-24b:free", 0, 0, 32768),
48
+ ("Dolphin3.0 Mistral 24B (free)", "cognitivecomputations/dolphin3.0-mistral-24b:free", 0, 0, 32768),
49
+ ("Mistral: Mistral Small 3 (free)", "mistralai/mistral-small-24b-instruct-2501:free", 0, 0, 32768),
50
+ ("Qwen2.5 Coder 32B Instruct (free)", "qwen/qwen-2.5-coder-32b-instruct:free", 0, 0, 32768),
51
+ ("Qwen2.5 72B Instruct (free)", "qwen/qwen-2.5-72b-instruct:free", 0, 0, 32768),
52
+ ("Meta: Llama 3.2 3B Instruct (free)", "meta-llama/llama-3.2-3b-instruct:free", 0, 0, 20000),
53
+ ("Qwen: QwQ 32B Preview (free)", "qwen/qwq-32b-preview:free", 0, 0, 16384),
54
+ ("DeepSeek: R1 Distill Qwen 32B (free)", "deepseek/deepseek-r1-distill-qwen-32b:free", 0, 0, 16000),
55
+ ("Qwen: Qwen2.5 VL 32B Instruct (free)", "qwen/qwen2.5-vl-32b-instruct:free", 0, 0, 8192),
56
+ ("Moonshot AI: Moonlight 16B A3B Instruct (free)", "moonshotai/moonlight-16b-a3b-instruct:free", 0, 0, 8192),
57
+ ("DeepSeek: R1 Distill Llama 70B (free)", "deepseek/deepseek-r1-distill-llama-70b:free", 0, 0, 8192),
58
+ ("Qwen 2 7B Instruct (free)", "qwen/qwen-2-7b-instruct:free", 0, 0, 8192),
59
+ ("Google: Gemma 2 9B (free)", "google/gemma-2-9b-it:free", 0, 0, 8192),
60
+ ("Mistral: Mistral 7B Instruct (free)", "mistralai/mistral-7b-instruct:free", 0, 0, 8192),
61
+ ("Microsoft: Phi-3 Mini 128K Instruct (free)", "microsoft/phi-3-mini-128k-instruct:free", 0, 0, 8192),
62
+ ("Microsoft: Phi-3 Medium 128K Instruct (free)", "microsoft/phi-3-medium-128k-instruct:free", 0, 0, 8192),
63
+ ("Meta: Llama 3 8B Instruct (free)", "meta-llama/llama-3-8b-instruct:free", 0, 0, 8192),
64
+ ("OpenChat 3.5 7B (free)", "openchat/openchat-7b:free", 0, 0, 8192),
65
+ ("Meta: Llama 3.3 70B Instruct (free)", "meta-llama/llama-3.3-70b-instruct:free", 0, 0, 8000),
66
+ ("AllenAI: Molmo 7B D (free)", "allenai/molmo-7b-d:free", 0, 0, 4096),
67
+ ("Rogue Rose 103B v0.2 (free)", "sophosympatheia/rogue-rose-103b-v0.2:free", 0, 0, 4096),
68
+ ("Toppy M 7B (free)", "undi95/toppy-m-7b:free", 0, 0, 4096),
69
+ ("Hugging Face: Zephyr 7B (free)", "huggingfaceh4/zephyr-7b-beta:free", 0, 0, 4096),
70
+ ("MythoMax 13B (free)", "gryphe/mythomax-l2-13b:free", 0, 0, 4096),
71
+ ]
72
 
73
+ # Filter for vision models
74
+ vision_model_ids = [
75
+ "meta-llama/llama-3.2-11b-vision-instruct:free",
76
+ "qwen/qwen2.5-vl-72b-instruct:free",
77
+ "qwen/qwen2.5-vl-3b-instruct:free",
78
+ "qwen/qwen2.5-vl-32b-instruct:free",
79
+ "qwen/qwen-2.5-vl-7b-instruct:free",
80
+ "google/gemini-2.0-pro-exp-02-05:free",
81
+ "google/gemini-2.5-pro-exp-03-25:free"
82
+ ]
83
 
84
+ # Prefilter vision models
85
+ vision_models = [(name, model_id) for name, model_id, _, _, _ in free_models if model_id in vision_model_ids]
86
+ text_models = [(name, model_id) for name, model_id, _, _, _ in free_models]
87
 
88
+ def encode_image(image):
89
+ """Convert PIL Image to base64 string"""
90
+ buffered = BytesIO()
91
+ image.save(buffered, format="PNG")
92
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
93
+
94
+ def process_message(message, chat_history, model_name, uploaded_image=None):
95
+ """Process message and return model response"""
96
+ model_id = next((model_id for name, model_id, _, _, _ in free_models if name == model_name), text_models[0][1])
97
+
98
+ # Check if API key is set
99
+ if not OPENROUTER_API_KEY:
100
+ return "Please set your OpenRouter API key in the environment variables.", chat_history
101
+
102
+ # Setup headers and URL
103
+ headers = {
104
+ "Content-Type": "application/json",
105
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
106
+ "HTTP-Referer": "https://huggingface.co/spaces", # Replace with your actual space URL in production
107
+ }
108
+
109
+ url = "https://openrouter.ai/api/v1/chat/completions"
110
+
111
+ # Build message content
112
+ messages = []
113
+
114
+ # Add chat history
115
+ for human_msg, ai_msg in chat_history:
116
+ messages.append({"role": "user", "content": human_msg})
117
+ messages.append({"role": "assistant", "content": ai_msg})
118
+
119
+ # Add current message
120
+ if uploaded_image:
121
+ # Image processing for vision models
122
+ base64_image = encode_image(uploaded_image)
123
+ content = [
124
+ {"type": "text", "text": message},
125
+ {
126
+ "type": "image_url",
127
+ "image_url": {
128
+ "url": f"data:image/png;base64,{base64_image}"
129
+ }
130
+ }
131
+ ]
132
+ messages.append({"role": "user", "content": content})
133
+ else:
134
+ messages.append({"role": "user", "content": message})
135
+
136
+ # Build request data
137
+ data = {
138
  "model": model_id,
139
  "messages": messages
140
  }
141
+
 
 
 
 
 
 
142
  try:
143
+ # Make API call
144
+ response = requests.post(url, headers=headers, json=data)
145
  response.raise_for_status()
146
+
147
+ # Parse response
148
+ result = response.json()
149
+ reply = result.get("choices", [{}])[0].get("message", {}).get("content", "No response")
150
+
151
+ # Update chat history
152
+ chat_history.append((message, reply))
153
+ return reply, chat_history
154
+
155
  except Exception as e:
156
+ return f"Error: {str(e)}", chat_history
157
 
158
+ # Create a nice CSS theme
159
+ css = """
160
+ .gradio-container {
161
+ font-family: 'Segoe UI', Arial, sans-serif;
162
+ }
163
+ #chat-message {
164
+ min-height: 100px;
165
+ }
166
+ #model-selector {
167
+ max-width: 100%;
168
+ }
169
+ .app-header {
170
+ text-align: center;
171
+ margin-bottom: 10px;
172
+ }
173
+ .app-header h1 {
174
+ font-weight: 700;
175
+ color: #2C3E50;
176
+ }
177
+ .app-header p {
178
+ color: #7F8C8D;
179
+ }
180
+ """
181
 
182
+ with gr.Blocks(css=css) as demo:
183
+ gr.HTML("""
184
+ <div class="app-header">
185
+ <h1>🔆 CrispChat</h1>
186
+ <p>Chat with free OpenRouter AI models - supports text and images</p>
187
+ </div>
188
  """)
189
+
190
  with gr.Row():
191
+ with gr.Column(scale=3):
192
+ chatbot = gr.Chatbot(
193
+ height=500,
194
+ show_copy_button=True,
195
+ show_share_button=False,
196
+ elem_id="chatbot",
197
+ layout="panel",
198
+ )
199
+
200
+ with gr.Row():
201
+ user_message = gr.Textbox(
202
+ placeholder="Type your message here...",
203
+ show_label=False,
204
+ elem_id="chat-message",
205
+ scale=10
206
+ )
207
+ image_upload = gr.Image(
208
+ type="pil",
209
+ label="Image Upload (optional)",
210
+ show_label=False,
211
+ tool="select",
212
+ scale=2
213
+ )
214
+ submit_btn = gr.Button("Send", scale=1, variant="primary")
215
+
216
+ with gr.Column(scale=1):
217
+ with gr.Accordion("Model Selection", open=True):
218
+ using_vision = gr.Checkbox(label="Using image", value=False)
219
+
220
+ model_selector = gr.Dropdown(
221
+ choices=[name for name, _ in text_models],
222
+ value=text_models[0][0],
223
+ label="Select Model",
224
+ elem_id="model-selector"
225
+ )
226
+
227
+ with gr.Accordion("Tips", open=True):
228
+ gr.Markdown("""
229
+ * For best results with images, select a vision-capable model
230
+ * Text models can handle up to 32k tokens
231
+ * Try different models for different tasks
232
+ * API output is in Markdown format for code highlighting
233
+ """)
234
+
235
+ with gr.Accordion("API", open=False):
236
+ api_url = gr.Textbox(
237
+ value="https://[your-space-name].hf.space/api/generate",
238
+ label="API Endpoint",
239
+ interactive=False
240
+ )
241
+ api_docs = gr.Markdown("""
242
+ ```json
243
+ POST /api/generate
244
+ {
245
+ "message": "Your message here",
246
+ "model": "model-id-here",
247
+ "image_data": "optional-base64-encoded-image"
248
+ }
249
+ ```
250
+ """)
251
+
252
+ # Define events
253
+ def update_model_selector(use_vision):
254
+ if use_vision:
255
+ return gr.Dropdown(choices=[name for name, _ in vision_models], value=vision_models[0][0])
256
+ else:
257
+ return gr.Dropdown(choices=[name for name, _ in text_models], value=text_models[0][0])
258
+
259
+ using_vision.change(
260
+ fn=update_model_selector,
261
+ inputs=using_vision,
262
+ outputs=model_selector
263
+ )
264
+
265
+ # Submit function
266
+ def on_submit(message, history, model, image):
267
+ if not message and not image:
268
+ return "", history
269
+ return "", process_message(message, history, model, image)
270
+
271
+ # Set up submission events
272
+ submit_btn.click(
273
+ on_submit,
274
+ inputs=[user_message, chatbot, model_selector, image_upload],
275
+ outputs=[user_message, chatbot]
276
+ )
277
+
278
+ user_message.submit(
279
+ on_submit,
280
+ inputs=[user_message, chatbot, model_selector, image_upload],
281
+ outputs=[user_message, chatbot]
282
  )
283
 
284
+ # API endpoint for external access
285
+ @demo.load(api_name="generate")
286
+ def api_generate(message, model=None, image_data=None):
287
+ """API endpoint for generating responses"""
288
+ model_name = model or text_models[0][0]
289
+
290
+ # Process image if provided
291
+ image = None
292
+ if image_data:
293
+ try:
294
+ # Decode base64 image
295
+ image_bytes = base64.b64decode(image_data)
296
+ image = Image.open(BytesIO(image_bytes))
297
+ except Exception as e:
298
+ return {"error": f"Image processing error: {str(e)}"}
299
+
300
+ # Generate response
301
+ try:
302
+ response, _ = process_message(message, [], model_name, image)
303
+ return {"response": response}
304
+ except Exception as e:
305
+ return {"error": f"Error generating response: {str(e)}"}
306
 
307
  if __name__ == "__main__":
308
+ demo.launch()