cstr commited on
Commit
3e6631d
·
verified ·
1 Parent(s): 82deaf2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +386 -178
app.py CHANGED
@@ -82,9 +82,21 @@ vision_model_ids = [
82
  "google/gemini-2.5-pro-exp-03-25:free"
83
  ]
84
 
 
 
 
 
 
 
 
 
85
  # Prefilter vision models
86
- vision_models = [(name, model_id) for name, model_id, _, _, _ in free_models if model_id in vision_model_ids]
87
- text_models = [(name, model_id) for name, model_id, _, _, _ in free_models]
 
 
 
 
88
 
89
  def encode_image(image):
90
  """Convert PIL Image to base64 string"""
@@ -92,9 +104,19 @@ def encode_image(image):
92
  image.save(buffered, format="JPEG")
93
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
94
 
95
- def process_message_stream(message, chat_history, model_name, uploaded_image=None):
 
 
 
 
 
 
 
 
 
96
  """Process message and stream the model response"""
97
- model_id = next((model_id for name, model_id, _, _, _ in free_models if name == model_name), text_models[0][1])
 
98
 
99
  # Check if API key is set
100
  if not OPENROUTER_API_KEY:
@@ -105,7 +127,7 @@ def process_message_stream(message, chat_history, model_name, uploaded_image=Non
105
  headers = {
106
  "Content-Type": "application/json",
107
  "Authorization": f"Bearer {OPENROUTER_API_KEY}",
108
- "HTTP-Referer": "https://huggingface.co/spaces/cstr/CrispChat", # Replace with your actual space URL in production
109
  }
110
 
111
  url = "https://openrouter.ai/api/v1/chat/completions"
@@ -114,101 +136,173 @@ def process_message_stream(message, chat_history, model_name, uploaded_image=Non
114
  messages = []
115
 
116
  # Add chat history
117
- for human_msg, ai_msg in chat_history:
118
- messages.append({"role": "user", "content": human_msg})
119
- messages.append({"role": "assistant", "content": ai_msg})
 
 
 
 
 
 
120
 
121
- # Add current message
122
  if uploaded_image:
123
  # Image processing for vision models
124
  base64_image = encode_image(uploaded_image)
125
  content = [
126
- {"type": "text", "text": message},
127
- {
128
- "type": "image_url",
129
- "image_url": {
130
- "url": f"data:image/jpeg;base64,{base64_image}"
131
- }
132
- }
133
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  messages.append({"role": "user", "content": content})
135
  else:
136
- messages.append({"role": "user", "content": message})
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
  # Build request data
139
  data = {
140
  "model": model_id,
141
  "messages": messages,
142
- "stream": True,
143
- "temperature": 0.7
 
 
144
  }
145
 
146
  try:
147
  # Create a new message pair in the chat history
148
- chat_history.append((message, ""))
 
 
 
 
149
  full_response = ""
150
 
151
- # Make streaming API call
152
- with requests.post(url, headers=headers, json=data, stream=True) as response:
153
- response.raise_for_status()
154
- buffer = ""
155
-
156
- for chunk in response.iter_content(chunk_size=1024, decode_unicode=False):
157
- if chunk:
158
- buffer += chunk.decode('utf-8')
159
-
160
- while True:
161
- line_end = buffer.find('\n')
162
- if line_end == -1:
163
- break
164
-
165
- line = buffer[:line_end].strip()
166
- buffer = buffer[line_end + 1:]
167
 
168
- if line.startswith('data: '):
169
- data = line[6:]
170
- if data == '[DONE]':
171
  break
172
 
173
- try:
174
- data_obj = json.loads(data)
175
- delta_content = data_obj["choices"][0]["delta"].get("content", "")
176
- if delta_content:
177
- full_response += delta_content
178
- # Update the last assistant message
179
- chat_history[-1] = (message, full_response)
180
- yield full_response, chat_history
181
- except json.JSONDecodeError:
182
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
- return full_response, chat_history
185
 
186
  except Exception as e:
187
  error_msg = f"Error: {str(e)}"
188
- chat_history[-1] = (message, error_msg)
189
- yield error_msg, chat_history
190
 
191
  # Create a nice CSS theme
192
  css = """
193
  .gradio-container {
194
- font-family: 'Segoe UI', Arial, sans-serif;
 
 
 
 
 
195
  }
196
- #chat-message {
197
- min-height: 100px;
198
  }
199
- #model-selector {
200
- max-width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
  .app-header {
203
  text-align: center;
204
- margin-bottom: 10px;
205
  }
206
  .app-header h1 {
207
  font-weight: 700;
208
  color: #2C3E50;
 
209
  }
210
  .app-header p {
211
  color: #7F8C8D;
 
 
 
 
 
 
 
 
 
 
212
  }
213
  """
214
 
@@ -216,182 +310,296 @@ with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
216
  gr.HTML("""
217
  <div class="app-header">
218
  <h1>🔆 CrispChat</h1>
219
- <p>Chat with AI models - supports text and images</p>
220
  </div>
221
  """)
222
 
223
  with gr.Row():
224
- with gr.Column(scale=3):
225
  chatbot = gr.Chatbot(
226
- height=500,
227
  show_copy_button=True,
228
  show_share_button=False,
229
  elem_id="chatbot",
230
- layout="panel",
 
 
231
  type="messages" # Use new message format
232
  )
233
 
234
  with gr.Row():
235
- user_message = gr.Textbox(
236
- placeholder="Type your message here...",
237
- show_label=False,
238
- elem_id="chat-message",
239
- scale=10
240
- )
241
- image_upload = gr.Image(
242
- type="pil",
243
- label="Image Upload (optional)",
244
- show_label=False,
245
- scale=2
246
- )
247
- submit_btn = gr.Button("Send", scale=1, variant="primary")
 
 
 
 
 
 
 
 
 
 
248
 
249
- with gr.Column(scale=1):
250
  with gr.Accordion("Model Selection", open=True):
251
  using_vision = gr.Checkbox(label="Using image", value=False)
252
 
253
  model_selector = gr.Dropdown(
254
- choices=[name for name, _ in text_models],
255
  value=text_models[0][0],
256
  label="Select Model",
257
  elem_id="model-selector"
258
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
 
260
- with gr.Accordion("Tips", open=True):
261
  gr.Markdown("""
262
- * For best results with images, select a vision-capable model
263
- * Text models can handle up to 32k tokens
264
- * Try different models for different tasks
265
- * API output is in Markdown format for code highlighting
266
- """)
267
-
268
- with gr.Accordion("API", open=False):
269
- api_url = gr.Textbox(
270
- value="https://cstr-crispchat.hf.space/api/generate",
271
- label="API Endpoint",
272
- interactive=False
273
- )
274
- api_docs = gr.Markdown("""
275
- ```json
276
- POST /api/generate
277
- {
278
- "message": "Your message here",
279
- "model": "model-id-here",
280
- "image_data": "optional-base64-encoded-image"
281
- }
282
- ```
283
  """)
284
 
285
  # Define events
286
  def update_model_selector(use_vision):
287
  if use_vision:
288
- return gr.Dropdown(choices=[name for name, _ in vision_models], value=vision_models[0][0])
 
 
 
289
  else:
290
- return gr.Dropdown(choices=[name for name, _ in text_models], value=text_models[0][0])
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
  using_vision.change(
293
  fn=update_model_selector,
294
  inputs=using_vision,
295
- outputs=model_selector
 
 
 
 
 
 
296
  )
297
 
298
  # Submit function
299
- def on_submit(message, history, model, image):
300
- if not message and not image:
301
  return "", history
302
- return "", process_message_stream(message, history, model, image)
 
 
 
 
 
 
 
 
 
 
303
 
304
  # Set up submission events
305
  submit_btn.click(
306
  on_submit,
307
- inputs=[user_message, chatbot, model_selector, image_upload],
 
 
 
 
308
  outputs=[user_message, chatbot]
309
  )
310
 
311
  user_message.submit(
312
  on_submit,
313
- inputs=[user_message, chatbot, model_selector, image_upload],
 
 
 
 
314
  outputs=[user_message, chatbot]
315
  )
316
 
317
- # API endpoint for external access
318
- @demo.queue()
319
- def api_generate(message, model=None, image_data=None):
 
 
 
 
 
 
 
 
 
 
 
 
320
  """API endpoint for generating responses"""
321
- model_name = model or text_models[0][0]
322
-
323
- # Process image if provided
324
- image = None
325
- if image_data:
326
- try:
327
- # Decode base64 image
328
- image_bytes = base64.b64decode(image_data)
329
- image = Image.open(BytesIO(image_bytes))
330
- except Exception as e:
331
- return {"error": f"Image processing error: {str(e)}"}
332
-
333
- # Generate response
334
  try:
335
- # Setup headers and URL
336
- headers = {
337
- "Content-Type": "application/json",
338
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
339
- "HTTP-Referer": "https://huggingface.co/spaces",
340
- }
341
-
342
- url = "https://openrouter.ai/api/v1/chat/completions"
343
-
344
- # Get model_id from model_name
345
- model_id = next((model_id for name, model_id, _, _, _ in free_models if name == model_name), None)
346
- if not model_id and model:
347
- # Check if model parameter is a direct model ID
348
- model_id = model
349
 
350
- if not model_id:
351
- model_id = text_models[0][1]
352
-
353
- # Build messages
354
- messages = []
 
 
 
 
 
 
 
355
 
356
- if image:
357
- # Image processing for vision models
358
- base64_image = encode_image(image)
359
- content = [
360
- {"type": "text", "text": message},
361
- {
362
- "type": "image_url",
363
- "image_url": {
364
- "url": f"data:image/jpeg;base64,{base64_image}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  }
366
- }
367
- ]
368
- messages.append({"role": "user", "content": content})
369
- else:
370
- messages.append({"role": "user", "content": message})
371
-
372
- # Build request data
373
- data = {
374
- "model": model_id,
375
- "messages": messages,
376
- "temperature": 0.7
377
- }
378
-
379
- # Make API call
380
- response = requests.post(url, headers=headers, json=data)
381
- response.raise_for_status()
382
-
383
- # Parse response
384
- result = response.json()
385
- reply = result.get("choices", [{}])[0].get("message", {}).get("content", "No response")
 
386
 
387
- return {"response": reply}
388
-
 
 
 
 
389
  except Exception as e:
390
- return {"error": f"Error generating response: {str(e)}"}
 
 
 
391
 
392
- demo.queue()
393
- demo.launch(share=False)
 
 
 
 
 
 
 
 
 
 
394
 
 
395
  if __name__ == "__main__":
396
- # Remove or comment out demo.launch() here if you added it above
397
- pass
 
 
82
  "google/gemini-2.5-pro-exp-03-25:free"
83
  ]
84
 
85
+ # Format model names to include context size
86
+ def format_model_name(name, context_size):
87
+ if context_size >= 1000000:
88
+ context_str = f"{context_size/1000000:.1f}M tokens"
89
+ else:
90
+ context_str = f"{context_size/1000:.0f}K tokens"
91
+ return f"{name} ({context_str})"
92
+
93
  # Prefilter vision models
94
+ vision_models = [(format_model_name(name, context_size), model_id, context_size)
95
+ for name, model_id, _, _, context_size in free_models
96
+ if model_id in vision_model_ids]
97
+
98
+ text_models = [(format_model_name(name, context_size), model_id, context_size)
99
+ for name, model_id, _, _, context_size in free_models]
100
 
101
  def encode_image(image):
102
  """Convert PIL Image to base64 string"""
 
104
  image.save(buffered, format="JPEG")
105
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
106
 
107
+ def encode_file(file_path):
108
+ """Convert text file to string"""
109
+ try:
110
+ with open(file_path, 'r', encoding='utf-8') as file:
111
+ return file.read()
112
+ except Exception as e:
113
+ return f"Error reading file: {str(e)}"
114
+
115
+ def process_message_stream(message, chat_history, model_name, uploaded_image=None, uploaded_file=None,
116
+ temperature=0.7, top_p=1.0, max_tokens=None, stream=True):
117
  """Process message and stream the model response"""
118
+ # Extract model_id from the display name
119
+ model_id = model_name.split(' ')[1] if len(model_name.split(' ')) > 1 else model_name
120
 
121
  # Check if API key is set
122
  if not OPENROUTER_API_KEY:
 
127
  headers = {
128
  "Content-Type": "application/json",
129
  "Authorization": f"Bearer {OPENROUTER_API_KEY}",
130
+ "HTTP-Referer": "https://huggingface.co/spaces", # Replace with your actual space URL in production
131
  }
132
 
133
  url = "https://openrouter.ai/api/v1/chat/completions"
 
136
  messages = []
137
 
138
  # Add chat history
139
+ for item in chat_history:
140
+ if isinstance(item, tuple):
141
+ # Old format compatibility
142
+ human_msg, ai_msg = item
143
+ messages.append({"role": "user", "content": human_msg})
144
+ messages.append({"role": "assistant", "content": ai_msg})
145
+ else:
146
+ # New message format
147
+ messages.append(item)
148
 
149
+ # Add current message with any attachments
150
  if uploaded_image:
151
  # Image processing for vision models
152
  base64_image = encode_image(uploaded_image)
153
  content = [
154
+ {"type": "text", "text": message}
 
 
 
 
 
 
155
  ]
156
+
157
+ # Add text from file if provided
158
+ if uploaded_file:
159
+ file_content = encode_file(uploaded_file)
160
+ content[0]["text"] = f"{message}\n\nFile content:\n```\n{file_content}\n```"
161
+
162
+ # Add image
163
+ content.append({
164
+ "type": "image_url",
165
+ "image_url": {
166
+ "url": f"data:image/jpeg;base64,{base64_image}"
167
+ }
168
+ })
169
+
170
  messages.append({"role": "user", "content": content})
171
  else:
172
+ if uploaded_file:
173
+ file_content = encode_file(uploaded_file)
174
+ content = f"{message}\n\nFile content:\n```\n{file_content}\n```"
175
+ messages.append({"role": "user", "content": content})
176
+ else:
177
+ messages.append({"role": "user", "content": message})
178
+
179
+ # Get context length for the model
180
+ context_length = next((context for _, model_id, context in text_models if model_id == model_id), 4096)
181
+
182
+ # Calculate default max tokens if not specified
183
+ if not max_tokens:
184
+ # Use 25% of context length as a reasonable default
185
+ max_tokens = min(4000, int(context_length * 0.25))
186
 
187
  # Build request data
188
  data = {
189
  "model": model_id,
190
  "messages": messages,
191
+ "stream": stream,
192
+ "temperature": temperature,
193
+ "top_p": top_p,
194
+ "max_tokens": max_tokens
195
  }
196
 
197
  try:
198
  # Create a new message pair in the chat history
199
+ user_msg = {"role": "user", "content": message}
200
+ ai_msg = {"role": "assistant", "content": ""}
201
+ chat_history.append(user_msg)
202
+ chat_history.append(ai_msg)
203
+
204
  full_response = ""
205
 
206
+ if stream:
207
+ # Make streaming API call
208
+ with requests.post(url, headers=headers, json=data, stream=True) as response:
209
+ response.raise_for_status()
210
+ buffer = ""
211
+
212
+ for chunk in response.iter_content(chunk_size=1024, decode_unicode=False):
213
+ if chunk:
214
+ buffer += chunk.decode('utf-8')
 
 
 
 
 
 
 
215
 
216
+ while True:
217
+ line_end = buffer.find('\n')
218
+ if line_end == -1:
219
  break
220
 
221
+ line = buffer[:line_end].strip()
222
+ buffer = buffer[line_end + 1:]
223
+
224
+ if line.startswith('data: '):
225
+ data = line[6:]
226
+ if data == '[DONE]':
227
+ break
228
+
229
+ try:
230
+ data_obj = json.loads(data)
231
+ delta_content = data_obj["choices"][0]["delta"].get("content", "")
232
+ if delta_content:
233
+ full_response += delta_content
234
+ # Update the last assistant message
235
+ chat_history[-1]["content"] = full_response
236
+ yield chat_history
237
+ except json.JSONDecodeError:
238
+ pass
239
+ else:
240
+ # Non-streaming API call
241
+ response = requests.post(url, headers=headers, json=data)
242
+ response.raise_for_status()
243
+ result = response.json()
244
+ full_response = result.get("choices", [{}])[0].get("message", {}).get("content", "No response")
245
+ chat_history[-1]["content"] = full_response
246
+ yield chat_history
247
 
248
+ return chat_history
249
 
250
  except Exception as e:
251
  error_msg = f"Error: {str(e)}"
252
+ chat_history[-1]["content"] = error_msg
253
+ yield chat_history
254
 
255
  # Create a nice CSS theme
256
  css = """
257
  .gradio-container {
258
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
259
+ }
260
+ .chat-message {
261
+ padding: 15px;
262
+ border-radius: 10px;
263
+ margin-bottom: 10px;
264
  }
265
+ .user-message {
266
+ background-color: #f0f4f8;
267
  }
268
+ .assistant-message {
269
+ background-color: #e9f5ff;
270
+ }
271
+ #chat-container {
272
+ height: 600px;
273
+ overflow-y: auto;
274
+ }
275
+ #chat-input {
276
+ min-height: 120px;
277
+ border-radius: 8px;
278
+ padding: 10px;
279
+ }
280
+ #model-select-container {
281
+ border-radius: 8px;
282
+ padding: 15px;
283
+ background-color: #f8fafc;
284
  }
285
  .app-header {
286
  text-align: center;
287
+ margin-bottom: 20px;
288
  }
289
  .app-header h1 {
290
  font-weight: 700;
291
  color: #2C3E50;
292
+ margin-bottom: 5px;
293
  }
294
  .app-header p {
295
  color: #7F8C8D;
296
+ margin-top: 0;
297
+ }
298
+ .parameter-container {
299
+ background-color: #f8fafc;
300
+ padding: 10px;
301
+ border-radius: 8px;
302
+ margin-top: 10px;
303
+ }
304
+ .file-upload-container {
305
+ margin-top: 10px;
306
  }
307
  """
308
 
 
310
  gr.HTML("""
311
  <div class="app-header">
312
  <h1>🔆 CrispChat</h1>
313
+ <p>Chat with free OpenRouter AI models - supports text, images, and files</p>
314
  </div>
315
  """)
316
 
317
  with gr.Row():
318
+ with gr.Column(scale=4):
319
  chatbot = gr.Chatbot(
320
+ height=600,
321
  show_copy_button=True,
322
  show_share_button=False,
323
  elem_id="chatbot",
324
+ layout="bubble",
325
+ avatar_images=("👤", "🤖"),
326
+ bubble_full_width=False,
327
  type="messages" # Use new message format
328
  )
329
 
330
  with gr.Row():
331
+ with gr.Column(scale=10):
332
+ user_message = gr.Textbox(
333
+ placeholder="Type your message here...",
334
+ show_label=False,
335
+ elem_id="chat-input",
336
+ lines=3
337
+ )
338
+
339
+ with gr.Row():
340
+ image_upload = gr.Image(
341
+ type="pil",
342
+ label="Image (optional)",
343
+ show_label=True,
344
+ scale=1
345
+ )
346
+
347
+ file_upload = gr.File(
348
+ label="Text File (optional)",
349
+ file_types=[".txt", ".md", ".py", ".js", ".html", ".css", ".json"],
350
+ scale=1
351
+ )
352
+
353
+ submit_btn = gr.Button("Send", scale=1, variant="primary")
354
 
355
+ with gr.Column(scale=2):
356
  with gr.Accordion("Model Selection", open=True):
357
  using_vision = gr.Checkbox(label="Using image", value=False)
358
 
359
  model_selector = gr.Dropdown(
360
+ choices=[name for name, _, _ in text_models],
361
  value=text_models[0][0],
362
  label="Select Model",
363
  elem_id="model-selector"
364
  )
365
+
366
+ context_info = gr.Markdown(value=f"Context: {text_models[0][2]:,} tokens")
367
+
368
+ with gr.Accordion("Parameters", open=False):
369
+ with gr.Group():
370
+ temperature = gr.Slider(
371
+ minimum=0.0,
372
+ maximum=2.0,
373
+ value=0.7,
374
+ step=0.1,
375
+ label="Temperature",
376
+ info="Higher = more creative, Lower = more deterministic"
377
+ )
378
+
379
+ top_p = gr.Slider(
380
+ minimum=0.1,
381
+ maximum=1.0,
382
+ value=1.0,
383
+ step=0.1,
384
+ label="Top P",
385
+ info="Controls token diversity"
386
+ )
387
+
388
+ max_tokens = gr.Slider(
389
+ minimum=100,
390
+ maximum=8000,
391
+ value=1000,
392
+ step=100,
393
+ label="Max Tokens",
394
+ info="Maximum length of the response"
395
+ )
396
+
397
+ use_streaming = gr.Checkbox(
398
+ label="Stream Response",
399
+ value=True,
400
+ info="Show response as it's generated"
401
+ )
402
 
403
+ with gr.Accordion("Tips", open=False):
404
  gr.Markdown("""
405
+ * Select a vision-capable model for images
406
+ * Upload text files to include their content
407
+ * Check model context window sizes
408
+ * Adjust temperature for creativity level
409
+ * Top P controls diversity of responses
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  """)
411
 
412
  # Define events
413
  def update_model_selector(use_vision):
414
  if use_vision:
415
+ return (
416
+ gr.Dropdown(choices=[name for name, _, _ in vision_models], value=vision_models[0][0]),
417
+ f"Context: {vision_models[0][2]:,} tokens"
418
+ )
419
  else:
420
+ return (
421
+ gr.Dropdown(choices=[name for name, _, _ in text_models], value=text_models[0][0]),
422
+ f"Context: {text_models[0][2]:,} tokens"
423
+ )
424
+
425
+ def update_context_info(model_name):
426
+ # Extract context size from model name
427
+ for name, _, context_size in text_models:
428
+ if name == model_name:
429
+ return f"Context: {context_size:,} tokens"
430
+ for name, _, context_size in vision_models:
431
+ if name == model_name:
432
+ return f"Context: {context_size:,} tokens"
433
+ return "Context size unknown"
434
 
435
  using_vision.change(
436
  fn=update_model_selector,
437
  inputs=using_vision,
438
+ outputs=[model_selector, context_info]
439
+ )
440
+
441
+ model_selector.change(
442
+ fn=update_context_info,
443
+ inputs=model_selector,
444
+ outputs=context_info
445
  )
446
 
447
  # Submit function
448
+ def on_submit(message, history, model, image, file, temp, top_p_val, max_tok, stream):
449
+ if not message and not image and not file:
450
  return "", history
451
+ return "", process_message_stream(
452
+ message,
453
+ history,
454
+ model,
455
+ image,
456
+ file.name if file else None,
457
+ temperature=temp,
458
+ top_p=top_p_val,
459
+ max_tokens=max_tok,
460
+ stream=stream
461
+ )
462
 
463
  # Set up submission events
464
  submit_btn.click(
465
  on_submit,
466
+ inputs=[
467
+ user_message, chatbot, model_selector,
468
+ image_upload, file_upload,
469
+ temperature, top_p, max_tokens, use_streaming
470
+ ],
471
  outputs=[user_message, chatbot]
472
  )
473
 
474
  user_message.submit(
475
  on_submit,
476
+ inputs=[
477
+ user_message, chatbot, model_selector,
478
+ image_upload, file_upload,
479
+ temperature, top_p, max_tokens, use_streaming
480
+ ],
481
  outputs=[user_message, chatbot]
482
  )
483
 
484
+ # Define FastAPI endpoint
485
+ from fastapi import FastAPI, Request, HTTPException
486
+ from fastapi.responses import JSONResponse
487
+ from pydantic import BaseModel
488
+ from fastapi.middleware.cors import CORSMiddleware
489
+
490
+ app = FastAPI()
491
+
492
+ class GenerateRequest(BaseModel):
493
+ message: str
494
+ model: str = None
495
+ image_data: str = None
496
+
497
+ @app.post("/api/generate")
498
+ async def api_generate(request: GenerateRequest):
499
  """API endpoint for generating responses"""
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  try:
501
+ message = request.message
502
+ model_name = request.model
503
+ image_data = request.image_data
 
 
 
 
 
 
 
 
 
 
 
504
 
505
+ # Process image if provided
506
+ image = None
507
+ if image_data:
508
+ try:
509
+ # Decode base64 image
510
+ image_bytes = base64.b64decode(image_data)
511
+ image = Image.open(BytesIO(image_bytes))
512
+ except Exception as e:
513
+ return JSONResponse(
514
+ status_code=400,
515
+ content={"error": f"Image processing error: {str(e)}"}
516
+ )
517
 
518
+ # Generate response
519
+ try:
520
+ # Setup headers and URL
521
+ headers = {
522
+ "Content-Type": "application/json",
523
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
524
+ "HTTP-Referer": "https://huggingface.co/spaces",
525
+ }
526
+
527
+ url = "https://openrouter.ai/api/v1/chat/completions"
528
+
529
+ # Get model_id from model_name
530
+ model_id = None
531
+ if model_name:
532
+ for _, mid, _ in text_models + vision_models:
533
+ if model_name in mid or model_name == mid:
534
+ model_id = mid
535
+ break
536
+
537
+ if not model_id:
538
+ model_id = text_models[0][1]
539
+
540
+ # Build messages
541
+ messages = []
542
+
543
+ if image:
544
+ # Image processing for vision models
545
+ base64_image = encode_image(image)
546
+ content = [
547
+ {"type": "text", "text": message},
548
+ {
549
+ "type": "image_url",
550
+ "image_url": {
551
+ "url": f"data:image/jpeg;base64,{base64_image}"
552
+ }
553
  }
554
+ ]
555
+ messages.append({"role": "user", "content": content})
556
+ else:
557
+ messages.append({"role": "user", "content": message})
558
+
559
+ # Build request data
560
+ data = {
561
+ "model": model_id,
562
+ "messages": messages,
563
+ "temperature": 0.7
564
+ }
565
+
566
+ # Make API call
567
+ response = requests.post(url, headers=headers, json=data)
568
+ response.raise_for_status()
569
+
570
+ # Parse response
571
+ result = response.json()
572
+ reply = result.get("choices", [{}])[0].get("message", {}).get("content", "No response")
573
+
574
+ return {"response": reply}
575
 
576
+ except Exception as e:
577
+ return JSONResponse(
578
+ status_code=500,
579
+ content={"error": f"Error generating response: {str(e)}"}
580
+ )
581
+
582
  except Exception as e:
583
+ return JSONResponse(
584
+ status_code=500,
585
+ content={"error": f"Server error: {str(e)}"}
586
+ )
587
 
588
+ # Add CORS middleware to allow cross-origin requests
589
+ app.add_middleware(
590
+ CORSMiddleware,
591
+ allow_origins=["*"],
592
+ allow_credentials=True,
593
+ allow_methods=["*"],
594
+ allow_headers=["*"],
595
+ )
596
+
597
+ # Mount Gradio app
598
+ import gradio as gr
599
+ app = gr.mount_gradio_app(app, demo, path="/")
600
 
601
+ # Start the app
602
  if __name__ == "__main__":
603
+ # Use 'uvicorn' directly in HF Spaces
604
+ import uvicorn
605
+ uvicorn.run(app, host="0.0.0.0", port=7860)