cstr commited on
Commit
a791178
·
verified ·
1 Parent(s): 74dc9ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +359 -244
app.py CHANGED
@@ -5,6 +5,7 @@ import json
5
  import base64
6
  import logging
7
  import io
 
8
 
9
  # Configure logging
10
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
@@ -32,7 +33,14 @@ except ImportError:
32
  # API key
33
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
34
 
35
- # Complete model list with context sizes - as per requested list
 
 
 
 
 
 
 
36
  MODELS = [
37
  # 1M+ Context Models
38
  {"category": "1M+ Context", "models": [
@@ -146,17 +154,62 @@ for category in MODELS:
146
  if model not in ALL_MODELS: # Avoid duplicates
147
  ALL_MODELS.append(model)
148
 
149
- def format_to_message_dict(history):
150
- """Convert history to proper message format"""
151
- messages = []
152
- for pair in history:
153
- if len(pair) == 2:
154
- human, ai = pair
155
- if human:
156
- messages.append({"role": "user", "content": human})
157
- if ai:
158
- messages.append({"role": "assistant", "content": ai})
159
- return messages
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  def encode_image_to_base64(image_path):
162
  """Encode an image file to base64 string"""
@@ -166,7 +219,7 @@ def encode_image_to_base64(image_path):
166
  encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
167
  file_extension = image_path.split('.')[-1].lower()
168
  mime_type = f"image/{file_extension}"
169
- if file_extension == "jpg" or file_extension == "jpeg":
170
  mime_type = "image/jpeg"
171
  elif file_extension == "png":
172
  mime_type = "image/png"
@@ -204,8 +257,7 @@ def extract_text_from_file(file_path):
204
 
205
  elif file_extension == 'md':
206
  with open(file_path, 'r', encoding='utf-8') as file:
207
- md_text = file.read()
208
- return md_text
209
 
210
  elif file_extension == 'txt':
211
  with open(file_path, 'r', encoding='utf-8') as file:
@@ -252,7 +304,7 @@ def prepare_message_with_media(text, images=None, documents=None):
252
  content = [{"type": "text", "text": text}]
253
 
254
  # Add images if any
255
- if images:
256
  for img in images:
257
  if img is None:
258
  continue
@@ -266,6 +318,18 @@ def prepare_message_with_media(text, images=None, documents=None):
266
 
267
  return content
268
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  def process_uploaded_images(files):
270
  """Process uploaded image files - fixed for Gradio 4.44.1"""
271
  file_paths = []
@@ -274,37 +338,118 @@ def process_uploaded_images(files):
274
  file_paths.append(file.name)
275
  return file_paths
276
 
277
- def ask_ai(message, chatbot, model_choice, temperature, max_tokens, top_p,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  frequency_penalty, presence_penalty, repetition_penalty, top_k,
279
  min_p, seed, top_a, stream_output, response_format,
280
  images, documents, reasoning_effort, system_message, transforms):
281
- """Comprehensive AI query function with all parameters"""
 
282
  if not message.strip() and not images and not documents:
283
- return chatbot, ""
284
 
285
- # Get model ID and context size
286
- model_id = None
287
- context_size = 0
288
- for name, model_id_value, ctx_size in ALL_MODELS:
289
- if name == model_choice:
290
- model_id = model_id_value
291
- context_size = ctx_size
292
- break
293
-
294
- if model_id is None:
295
  logger.error(f"Model not found: {model_choice}")
296
- return chatbot + [[message, "Error: Model not found"]], ""
 
 
 
 
297
 
298
- # Create messages from chatbot history
299
- messages = format_to_message_dict(chatbot)
300
 
301
  # Add system message if provided
302
  if system_message and system_message.strip():
303
- # Insert at the beginning to override any existing system message
304
- for i, msg in enumerate(messages):
305
- if msg.get("role") == "system":
306
- messages.pop(i)
307
- break
308
  messages.insert(0, {"role": "system", "content": system_message.strip()})
309
 
310
  # Prepare message with images and documents if any
@@ -313,237 +458,174 @@ def ask_ai(message, chatbot, model_choice, temperature, max_tokens, top_p,
313
  # Add current message
314
  messages.append({"role": "user", "content": content})
315
 
316
- # Call API
317
- try:
318
- logger.info(f"Sending request to model: {model_id}")
319
-
320
- # Build the comprehensive payload with all parameters
321
- payload = {
322
- "model": model_id,
323
- "messages": messages,
324
- "temperature": temperature,
325
- "max_tokens": max_tokens,
326
- "top_p": top_p,
327
- "frequency_penalty": frequency_penalty,
328
- "presence_penalty": presence_penalty,
329
- "repetition_penalty": repetition_penalty if repetition_penalty != 1.0 else None,
330
- "top_k": top_k,
331
- "min_p": min_p if min_p > 0 else None,
332
- "seed": seed if seed > 0 else None,
333
- "top_a": top_a if top_a > 0 else None,
334
- "stream": stream_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
336
-
337
- # Add response format if not default
338
- if response_format == "json_object":
339
- payload["response_format"] = {"type": "json_object"}
340
-
341
- # Add reasoning if selected
342
- if reasoning_effort != "none":
343
- payload["reasoning"] = {
344
- "effort": reasoning_effort
345
- }
346
-
347
- # Add transforms if selected
348
- if transforms:
349
- payload["transforms"] = transforms
350
-
351
- # Remove None values
352
- payload = {k: v for k, v in payload.items() if v is not None}
353
-
354
- logger.info(f"Request payload: {json.dumps(payload, default=str)}")
355
-
356
- response = requests.post(
357
- "https://openrouter.ai/api/v1/chat/completions",
358
- headers={
359
- "Content-Type": "application/json",
360
- "Authorization": f"Bearer {OPENROUTER_API_KEY}",
361
- "HTTP-Referer": "https://huggingface.co/spaces/cstr/CrispStrobe"
362
- },
363
- json=payload,
364
- timeout=180, # Longer timeout for document processing and streaming
365
- stream=stream_output
366
- )
367
-
368
  logger.info(f"Response status: {response.status_code}")
369
 
 
370
  if stream_output and response.status_code == 200:
371
- # Handle streaming response
372
- chatbot = chatbot + [[message, ""]]
 
 
 
 
 
373
 
374
- for line in response.iter_lines():
375
- if line:
376
- line = line.decode('utf-8')
377
- if line.startswith('data: '):
378
- data = line[6:]
379
- if data.strip() == '[DONE]':
380
- break
381
- try:
382
- chunk = json.loads(data)
383
- if "choices" in chunk and len(chunk["choices"]) > 0:
384
- delta = chunk["choices"][0].get("delta", {})
385
- if "content" in delta and delta["content"]:
386
- chatbot[-1][1] += delta["content"]
387
- yield chatbot, ""
388
- except json.JSONDecodeError:
389
- continue
390
- return chatbot, ""
391
 
 
392
  elif response.status_code == 200:
393
- # Handle normal response
394
- try:
395
- result = response.json()
396
- # Add detailed logging to debug what's in the response
397
- logger.info(f"Response content: {result}")
398
-
399
- ai_response = ""
400
- if "choices" in result and len(result["choices"]) > 0:
401
- if "message" in result["choices"][0]:
402
- ai_response = result["choices"][0]["message"].get("content", "")
403
- elif "delta" in result["choices"][0]:
404
- ai_response = result["choices"][0]["delta"].get("content", "")
405
- else:
406
- logger.error(f"Unexpected response structure: {result}")
407
- ai_response = "Error: Unexpected response structure from API"
408
- else:
409
- logger.error(f"No choices in response: {result}")
410
- ai_response = "Error: No response received from the model"
411
-
412
- chatbot = chatbot + [[message, ai_response]]
413
-
414
- # Log token usage if available
415
- if "usage" in result:
416
- logger.info(f"Token usage: {result['usage']}")
417
- except Exception as e:
418
- logger.error(f"Error processing response: {str(e)}")
419
- logger.error(f"Response raw text: {response.text}")
420
- chatbot = chatbot + [[message, f"Error processing response: {str(e)}"]]
421
  else:
422
- response_text = response.text
423
- logger.info(f"Error response body: {response_text}")
424
- error_message = f"Error: Status code {response.status_code}\n\nResponse: {response_text}"
425
- chatbot = chatbot + [[message, error_message]]
 
 
 
 
 
 
 
426
  except Exception as e:
427
- logger.error(f"Exception during API call: {str(e)}")
428
- chatbot = chatbot + [[message, f"Error: {str(e)}"]]
429
-
430
- return chatbot, ""
431
 
432
  def clear_chat():
433
  """Reset all inputs"""
434
  return [], "", [], [], 0.7, 1000, 0.8, 0.0, 0.0, 1.0, 40, 0.1, 0, 0.0, False, "default", "none", "", []
435
 
436
- # Create requirements.txt content
437
- requirements = """
438
- gradio>=4.44.1
439
- requests>=2.28.1
440
- Pillow>=9.0.0
441
- PyPDF2>=3.0.0
442
- markdown>=3.4.1
443
- """
444
-
445
- # Helper function to filter models
446
- def filter_models(search_term):
447
- if not search_term:
448
- return [model[0] for model in ALL_MODELS], ALL_MODELS[0][0]
449
-
450
- filtered_models = [model[0] for model in ALL_MODELS if search_term.lower() in model[0].lower()]
451
-
452
- if filtered_models:
453
- return filtered_models, filtered_models[0]
454
- else:
455
- return [model[0] for model in ALL_MODELS], ALL_MODELS[0][0]
456
-
457
- # Helper function for context display
458
- def update_context_display(model_name):
459
- for model in ALL_MODELS:
460
- if model[0] == model_name:
461
- _, _, context_size = model
462
- context_formatted = f"{context_size:,}"
463
- return f"{context_formatted} tokens"
464
- return "Unknown"
465
-
466
- # Helper function for model info display
467
- # Update the model info display function to indicate vision capability
468
- def update_model_info(model_name):
469
- for model in ALL_MODELS:
470
- if model[0] == model_name:
471
- name, model_id, context_size = model
472
-
473
- # Check if this is a vision model
474
- is_vision_model = False
475
- for cat in MODELS:
476
- if cat["category"] == "Vision Models":
477
- if any(m[0] == model_name for m in cat["models"]):
478
- is_vision_model = True
479
- break
480
-
481
- vision_badge = '<span style="background-color: #4CAF50; color: white; padding: 3px 6px; border-radius: 3px; font-size: 0.8em; margin-left: 5px;">Vision</span>' if is_vision_model else ''
482
-
483
- return f"""
484
- <div class="model-info">
485
- <h3>{name} {vision_badge}</h3>
486
- <p><strong>Model ID:</strong> {model_id}</p>
487
- <p><strong>Context Size:</strong> {context_size:,} tokens</p>
488
- <p><strong>Provider:</strong> {model_id.split('/')[0]}</p>
489
- {f'<p><strong>Features:</strong> Supports image understanding</p>' if is_vision_model else ''}
490
- </div>
491
- """
492
- return "<p>Model information not available</p>"
493
-
494
- # Helper function to update category models
495
- def update_category_models(category):
496
- for cat in MODELS:
497
- if cat["category"] == category:
498
- model_names = [model[0] for model in cat["models"]]
499
- return model_names, model_names[0]
500
- return [], ""
501
-
502
- # Main application
503
  def create_app():
504
- with gr.Blocks(css="""
505
- .context-size {
506
- font-size: 0.9em;
507
- color: #666;
508
- margin-left: 10px;
509
- }
510
- footer { display: none !important; }
511
- .model-selection-row {
512
- display: flex;
513
- align-items: center;
514
- }
515
- .parameter-grid {
516
- display: grid;
517
- grid-template-columns: 1fr 1fr;
518
- gap: 10px;
519
- }
520
- """) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
521
  gr.Markdown("""
522
- # Enhanced AI Chat
523
 
524
  Chat with various AI models from OpenRouter with support for images and documents.
525
  """)
526
 
527
  with gr.Row():
528
  with gr.Column(scale=2):
 
529
  chatbot = gr.Chatbot(
530
  height=500,
531
  show_copy_button=True,
532
  show_label=False,
533
- # avatar_images=(None, "https://upload.wikimedia.org/wikipedia/commons/0/04/ChatGPT_logo.svg"),
534
- type="messages" # Fixed: Use messages format instead of tuples
 
 
 
 
 
 
 
535
  )
536
 
537
  with gr.Row():
538
  message = gr.Textbox(
539
  placeholder="Type your message here...",
540
  label="Message",
541
- lines=2
 
 
542
  )
543
 
544
  with gr.Row():
545
  with gr.Column(scale=3):
546
- submit_btn = gr.Button("Send", variant="primary")
547
 
548
  with gr.Column(scale=1):
549
  clear_btn = gr.Button("Clear Chat", variant="secondary")
@@ -588,7 +670,8 @@ def create_app():
588
  model_choice = gr.Dropdown(
589
  [model[0] for model in ALL_MODELS],
590
  value=ALL_MODELS[0][0],
591
- label="Model"
 
592
  )
593
  context_display = gr.Textbox(
594
  value=update_context_display(ALL_MODELS[0][0]),
@@ -740,7 +823,7 @@ def create_app():
740
  # Add a model information section
741
  with gr.Accordion("About Selected Model", open=False):
742
  model_info_display = gr.HTML(
743
- value="<p>Select a model to see details</p>"
744
  )
745
 
746
  # Add usage instructions
@@ -770,12 +853,16 @@ def create_app():
770
  # Add a footer with version info
771
  footer_md = gr.Markdown("""
772
  ---
773
- ### CrispChat v1.0
774
- Built with ❤️ using Gradio and OpenRouter API | Context sizes shown next to model names
775
  """)
776
-
777
 
778
-
 
 
 
 
 
779
  # Connect model search to dropdown filter
780
  model_search.change(
781
  fn=filter_models,
@@ -827,8 +914,12 @@ def create_app():
827
  top_k, min_p, seed, top_a, stream_output, response_format,
828
  images, documents, reasoning_effort, system_message, transforms
829
  ],
830
- outputs=[chatbot, message],
831
- api_name=False # ensure direct function calling
 
 
 
 
832
  )
833
 
834
  # Set up events for message submission (pressing Enter)
@@ -840,8 +931,12 @@ def create_app():
840
  top_k, min_p, seed, top_a, stream_output, response_format,
841
  images, documents, reasoning_effort, system_message, transforms
842
  ],
843
- outputs=[chatbot, message],
844
- api_name=False # ensure direct function calling
 
 
 
 
845
  )
846
 
847
  # Set up events for the clear button
@@ -856,10 +951,30 @@ def create_app():
856
  ]
857
  )
858
 
 
 
 
 
 
 
 
 
 
 
 
859
  return demo
860
 
861
-
862
  # Launch the app
863
  if __name__ == "__main__":
 
 
 
 
 
864
  demo = create_app()
865
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
5
  import base64
6
  import logging
7
  import io
8
+ from typing import List, Dict, Any, Union, Tuple, Optional
9
 
10
  # Configure logging
11
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
33
  # API key
34
  OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY", "")
35
 
36
+ # Log API key status (masked for security)
37
+ if OPENROUTER_API_KEY:
38
+ masked_key = OPENROUTER_API_KEY[:4] + "..." + OPENROUTER_API_KEY[-4:] if len(OPENROUTER_API_KEY) > 8 else "***"
39
+ logger.info(f"Using API key: {masked_key}")
40
+ else:
41
+ logger.warning("No API key provided!")
42
+
43
+ # Keep the existing model lists
44
  MODELS = [
45
  # 1M+ Context Models
46
  {"category": "1M+ Context", "models": [
 
154
  if model not in ALL_MODELS: # Avoid duplicates
155
  ALL_MODELS.append(model)
156
 
157
+ # Helper functions moved to the top to avoid undefined references
158
+ def filter_models(search_term):
159
+ """Filter models based on search term"""
160
+ if not search_term:
161
+ return [model[0] for model in ALL_MODELS], ALL_MODELS[0][0]
162
+
163
+ filtered_models = [model[0] for model in ALL_MODELS if search_term.lower() in model[0].lower()]
164
+
165
+ if filtered_models:
166
+ return filtered_models, filtered_models[0]
167
+ else:
168
+ return [model[0] for model in ALL_MODELS], ALL_MODELS[0][0]
169
+
170
+ def update_context_display(model_name):
171
+ """Update context size display for the selected model"""
172
+ for model in ALL_MODELS:
173
+ if model[0] == model_name:
174
+ _, _, context_size = model
175
+ context_formatted = f"{context_size:,}"
176
+ return f"{context_formatted} tokens"
177
+ return "Unknown"
178
+
179
+ def update_model_info(model_name):
180
+ """Generate HTML info display for the selected model"""
181
+ for model in ALL_MODELS:
182
+ if model[0] == model_name:
183
+ name, model_id, context_size = model
184
+
185
+ # Check if this is a vision model
186
+ is_vision_model = False
187
+ for cat in MODELS:
188
+ if cat["category"] == "Vision Models":
189
+ if any(m[0] == model_name for m in cat["models"]):
190
+ is_vision_model = True
191
+ break
192
+
193
+ vision_badge = '<span style="background-color: #4CAF50; color: white; padding: 3px 6px; border-radius: 3px; font-size: 0.8em; margin-left: 5px;">Vision</span>' if is_vision_model else ''
194
+
195
+ return f"""
196
+ <div class="model-info">
197
+ <h3>{name} {vision_badge}</h3>
198
+ <p><strong>Model ID:</strong> {model_id}</p>
199
+ <p><strong>Context Size:</strong> {context_size:,} tokens</p>
200
+ <p><strong>Provider:</strong> {model_id.split('/')[0]}</p>
201
+ {f'<p><strong>Features:</strong> Supports image understanding</p>' if is_vision_model else ''}
202
+ </div>
203
+ """
204
+ return "<p>Model information not available</p>"
205
+
206
+ def update_category_models(category):
207
+ """Update model list based on selected category"""
208
+ for cat in MODELS:
209
+ if cat["category"] == category:
210
+ model_names = [model[0] for model in cat["models"]]
211
+ return model_names, model_names[0]
212
+ return [], ""
213
 
214
  def encode_image_to_base64(image_path):
215
  """Encode an image file to base64 string"""
 
219
  encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
220
  file_extension = image_path.split('.')[-1].lower()
221
  mime_type = f"image/{file_extension}"
222
+ if file_extension in ["jpg", "jpeg"]:
223
  mime_type = "image/jpeg"
224
  elif file_extension == "png":
225
  mime_type = "image/png"
 
257
 
258
  elif file_extension == 'md':
259
  with open(file_path, 'r', encoding='utf-8') as file:
260
+ return file.read()
 
261
 
262
  elif file_extension == 'txt':
263
  with open(file_path, 'r', encoding='utf-8') as file:
 
304
  content = [{"type": "text", "text": text}]
305
 
306
  # Add images if any
307
+ if images and any(img is not None for img in images):
308
  for img in images:
309
  if img is None:
310
  continue
 
318
 
319
  return content
320
 
321
+ def format_to_message_dict(history):
322
+ """Convert history to proper message format"""
323
+ messages = []
324
+ for pair in history:
325
+ if len(pair) == 2:
326
+ human, ai = pair
327
+ if human:
328
+ messages.append({"role": "user", "content": human})
329
+ if ai:
330
+ messages.append({"role": "assistant", "content": ai})
331
+ return messages
332
+
333
  def process_uploaded_images(files):
334
  """Process uploaded image files - fixed for Gradio 4.44.1"""
335
  file_paths = []
 
338
  file_paths.append(file.name)
339
  return file_paths
340
 
341
+ def get_model_info(model_choice):
342
+ """Get model ID and context size from model name"""
343
+ for name, model_id_value, ctx_size in ALL_MODELS:
344
+ if name == model_choice:
345
+ return model_id_value, ctx_size
346
+ return None, 0
347
+
348
+ def call_openrouter_api(payload):
349
+ """Make a call to OpenRouter API with error handling"""
350
+ try:
351
+ response = requests.post(
352
+ "https://openrouter.ai/api/v1/chat/completions",
353
+ headers={
354
+ "Content-Type": "application/json",
355
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
356
+ "HTTP-Referer": "https://huggingface.co/spaces/cstr/CrispChat"
357
+ },
358
+ json=payload,
359
+ timeout=180 # Longer timeout for document processing
360
+ )
361
+ return response
362
+ except requests.RequestException as e:
363
+ logger.error(f"API request error: {str(e)}")
364
+ raise e
365
+
366
+ def extract_ai_response(result):
367
+ """Extract AI response from OpenRouter API result"""
368
+ try:
369
+ if "choices" in result and len(result["choices"]) > 0:
370
+ if "message" in result["choices"][0]:
371
+ # Handle reasoning field if available
372
+ message = result["choices"][0]["message"]
373
+ if message.get("reasoning") and not message.get("content"):
374
+ # Extract response from reasoning if there's no content
375
+ reasoning = message.get("reasoning")
376
+ # If reasoning contains the actual response, find it
377
+ lines = reasoning.strip().split('\n')
378
+ for line in lines:
379
+ if line and not line.startswith('I should') and not line.startswith('Let me'):
380
+ return line.strip()
381
+ # If no clear response found, return the first non-empty line
382
+ for line in lines:
383
+ if line.strip():
384
+ return line.strip()
385
+ return message.get("content", "")
386
+ elif "delta" in result["choices"][0]:
387
+ return result["choices"][0]["delta"].get("content", "")
388
+
389
+ logger.error(f"Unexpected response structure: {result}")
390
+ return "Error: Could not extract response from API result"
391
+ except Exception as e:
392
+ logger.error(f"Error extracting AI response: {str(e)}")
393
+ return f"Error: {str(e)}"
394
+
395
+ def streaming_handler(response, chatbot, message_idx):
396
+ """Handle streaming response from OpenRouter API"""
397
+ try:
398
+ for line in response.iter_lines():
399
+ if not line:
400
+ continue
401
+
402
+ line = line.decode('utf-8')
403
+ if not line.startswith('data: '):
404
+ continue
405
+
406
+ data = line[6:]
407
+ if data.strip() == '[DONE]':
408
+ break
409
+
410
+ try:
411
+ chunk = json.loads(data)
412
+ if "choices" in chunk and len(chunk["choices"]) > 0:
413
+ delta = chunk["choices"][0].get("delta", {})
414
+ if "content" in delta and delta["content"]:
415
+ chatbot[message_idx][1] += delta["content"]
416
+ yield chatbot
417
+ except json.JSONDecodeError:
418
+ logger.error(f"Failed to parse JSON from chunk: {data}")
419
+ except Exception as e:
420
+ logger.error(f"Error in streaming handler: {str(e)}")
421
+ # Add error message to the current response
422
+ if len(chatbot) > message_idx:
423
+ chatbot[message_idx][1] += f"\n\nError during streaming: {str(e)}"
424
+ yield chatbot
425
+
426
+ def ask_ai(message, history, model_choice, temperature, max_tokens, top_p,
427
  frequency_penalty, presence_penalty, repetition_penalty, top_k,
428
  min_p, seed, top_a, stream_output, response_format,
429
  images, documents, reasoning_effort, system_message, transforms):
430
+ """Redesigned AI query function with proper error handling for Gradio 4.44.1"""
431
+ # Validate input
432
  if not message.strip() and not images and not documents:
433
+ return history
434
 
435
+ # Get model information
436
+ model_id, context_size = get_model_info(model_choice)
437
+ if not model_id:
 
 
 
 
 
 
 
438
  logger.error(f"Model not found: {model_choice}")
439
+ history.append((message, f"Error: Model '{model_choice}' not found"))
440
+ return history
441
+
442
+ # Copy history to new list to avoid modifying the original
443
+ chat_history = list(history)
444
 
445
+ # Create messages from chat history
446
+ messages = format_to_message_dict(chat_history)
447
 
448
  # Add system message if provided
449
  if system_message and system_message.strip():
450
+ # Remove any existing system message
451
+ messages = [msg for msg in messages if msg.get("role") != "system"]
452
+ # Add new system message at the beginning
 
 
453
  messages.insert(0, {"role": "system", "content": system_message.strip()})
454
 
455
  # Prepare message with images and documents if any
 
458
  # Add current message
459
  messages.append({"role": "user", "content": content})
460
 
461
+ # Build the payload with all parameters
462
+ payload = {
463
+ "model": model_id,
464
+ "messages": messages,
465
+ "temperature": temperature,
466
+ "max_tokens": max_tokens,
467
+ "top_p": top_p,
468
+ "frequency_penalty": frequency_penalty,
469
+ "presence_penalty": presence_penalty,
470
+ "stream": stream_output
471
+ }
472
+
473
+ # Add optional parameters if set
474
+ if repetition_penalty != 1.0:
475
+ payload["repetition_penalty"] = repetition_penalty
476
+
477
+ if top_k > 0:
478
+ payload["top_k"] = top_k
479
+
480
+ if min_p > 0:
481
+ payload["min_p"] = min_p
482
+
483
+ if seed > 0:
484
+ payload["seed"] = seed
485
+
486
+ if top_a > 0:
487
+ payload["top_a"] = top_a
488
+
489
+ # Add response format if JSON is requested
490
+ if response_format == "json_object":
491
+ payload["response_format"] = {"type": "json_object"}
492
+
493
+ # Add reasoning if selected
494
+ if reasoning_effort != "none":
495
+ payload["reasoning"] = {
496
+ "effort": reasoning_effort
497
  }
498
+
499
+ # Add transforms if selected
500
+ if transforms:
501
+ payload["transforms"] = transforms
502
+
503
+ # Log the request
504
+ logger.info(f"Sending request to model: {model_id}")
505
+ logger.info(f"Request payload: {json.dumps(payload, default=str)}")
506
+
507
+ try:
508
+ # Call OpenRouter API
509
+ response = call_openrouter_api(payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  logger.info(f"Response status: {response.status_code}")
511
 
512
+ # Handle streaming response
513
  if stream_output and response.status_code == 200:
514
+ # Add empty response slot to history
515
+ chat_history.append([message, ""])
516
+
517
+ # Set up generator for streaming updates
518
+ def streaming_generator():
519
+ for updated_history in streaming_handler(response, chat_history, len(chat_history) - 1):
520
+ yield updated_history
521
 
522
+ return streaming_generator()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
 
524
+ # Handle normal response
525
  elif response.status_code == 200:
526
+ result = response.json()
527
+ logger.info(f"Response content: {result}")
528
+
529
+ # Extract AI response
530
+ ai_response = extract_ai_response(result)
531
+
532
+ # Log token usage if available
533
+ if "usage" in result:
534
+ logger.info(f"Token usage: {result['usage']}")
535
+
536
+ # Add response to history
537
+ chat_history.append([message, ai_response])
538
+ return chat_history
539
+
540
+ # Handle error response
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  else:
542
+ error_message = f"Error: Status code {response.status_code}"
543
+ try:
544
+ response_data = response.json()
545
+ error_message += f"\n\nDetails: {json.dumps(response_data, indent=2)}"
546
+ except:
547
+ error_message += f"\n\nResponse: {response.text}"
548
+
549
+ logger.error(error_message)
550
+ chat_history.append([message, error_message])
551
+ return chat_history
552
+
553
  except Exception as e:
554
+ error_message = f"Error: {str(e)}"
555
+ logger.error(f"Exception during API call: {error_message}")
556
+ chat_history.append([message, error_message])
557
+ return chat_history
558
 
559
  def clear_chat():
560
  """Reset all inputs"""
561
  return [], "", [], [], 0.7, 1000, 0.8, 0.0, 0.0, 1.0, 40, 0.1, 0, 0.0, False, "default", "none", "", []
562
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  def create_app():
564
+ """Create the Gradio application with improved UI and response handling"""
565
+ with gr.Blocks(
566
+ title="CrispChat - AI Assistant",
567
+ css="""
568
+ .context-size {
569
+ font-size: 0.9em;
570
+ color: #666;
571
+ margin-left: 10px;
572
+ }
573
+ footer { display: none !important; }
574
+ .model-selection-row {
575
+ display: flex;
576
+ align-items: center;
577
+ }
578
+ .parameter-grid {
579
+ display: grid;
580
+ grid-template-columns: 1fr 1fr;
581
+ gap: 10px;
582
+ }
583
+ .vision-badge {
584
+ background-color: #4CAF50;
585
+ color: white;
586
+ padding: 3px 6px;
587
+ border-radius: 3px;
588
+ font-size: 0.8em;
589
+ margin-left: 5px;
590
+ }
591
+ """
592
+ ) as demo:
593
  gr.Markdown("""
594
+ # CrispChat AI Assistant
595
 
596
  Chat with various AI models from OpenRouter with support for images and documents.
597
  """)
598
 
599
  with gr.Row():
600
  with gr.Column(scale=2):
601
+ # Chatbot interface - properly configured for Gradio 4.44.1
602
  chatbot = gr.Chatbot(
603
  height=500,
604
  show_copy_button=True,
605
  show_label=False,
606
+ avatar_images=(None, "https://upload.wikimedia.org/wikipedia/commons/0/04/ChatGPT_logo.svg"),
607
+ render=True, # Explicitly enable rendering
608
+ elem_id="chat-window" # Add elem_id for debugging
609
+ )
610
+
611
+ # Debug output for development
612
+ debug_output = gr.JSON(
613
+ label="Debug Output (Hidden in Production)",
614
+ visible=False
615
  )
616
 
617
  with gr.Row():
618
  message = gr.Textbox(
619
  placeholder="Type your message here...",
620
  label="Message",
621
+ lines=2,
622
+ elem_id="message-input", # Add elem_id for debugging
623
+ scale=4
624
  )
625
 
626
  with gr.Row():
627
  with gr.Column(scale=3):
628
+ submit_btn = gr.Button("Send", variant="primary", elem_id="send-btn")
629
 
630
  with gr.Column(scale=1):
631
  clear_btn = gr.Button("Clear Chat", variant="secondary")
 
670
  model_choice = gr.Dropdown(
671
  [model[0] for model in ALL_MODELS],
672
  value=ALL_MODELS[0][0],
673
+ label="Model",
674
+ elem_id="model-choice" # Add elem_id for debugging
675
  )
676
  context_display = gr.Textbox(
677
  value=update_context_display(ALL_MODELS[0][0]),
 
823
  # Add a model information section
824
  with gr.Accordion("About Selected Model", open=False):
825
  model_info_display = gr.HTML(
826
+ value=update_model_info(ALL_MODELS[0][0])
827
  )
828
 
829
  # Add usage instructions
 
853
  # Add a footer with version info
854
  footer_md = gr.Markdown("""
855
  ---
856
+ ### CrispChat v1.1
857
+ Built with ❤️ using Gradio 4.44.1 and OpenRouter API | Context sizes shown next to model names
858
  """)
 
859
 
860
+ # Define a test function for debugging
861
+ def test_chatbot(test_message):
862
+ """Simple test function to verify chatbot updates work"""
863
+ logger.info(f"Test function called with: {test_message}")
864
+ return [[test_message, "This is a test response to verify the chatbot is working"]]
865
+
866
  # Connect model search to dropdown filter
867
  model_search.change(
868
  fn=filter_models,
 
914
  top_k, min_p, seed, top_a, stream_output, response_format,
915
  images, documents, reasoning_effort, system_message, transforms
916
  ],
917
+ outputs=chatbot,
918
+ show_progress="minimal",
919
+ ).then(
920
+ fn=lambda: "", # Clear message box after sending
921
+ inputs=None,
922
+ outputs=message
923
  )
924
 
925
  # Set up events for message submission (pressing Enter)
 
931
  top_k, min_p, seed, top_a, stream_output, response_format,
932
  images, documents, reasoning_effort, system_message, transforms
933
  ],
934
+ outputs=chatbot,
935
+ show_progress="minimal",
936
+ ).then(
937
+ fn=lambda: "", # Clear message box after sending
938
+ inputs=None,
939
+ outputs=message
940
  )
941
 
942
  # Set up events for the clear button
 
951
  ]
952
  )
953
 
954
+ # Debug button (hidden in production)
955
+ debug_btn = gr.Button("Debug Chatbot", visible=False)
956
+ debug_btn.click(
957
+ fn=test_chatbot,
958
+ inputs=[message],
959
+ outputs=[chatbot]
960
+ )
961
+
962
+ # Enable debugging for key components
963
+ gr.debug(chatbot)
964
+
965
  return demo
966
 
 
967
  # Launch the app
968
  if __name__ == "__main__":
969
+ # Check API key before starting
970
+ if not OPENROUTER_API_KEY:
971
+ logger.warning("WARNING: OPENROUTER_API_KEY environment variable is not set")
972
+ print("WARNING: OpenRouter API key not found. Set OPENROUTER_API_KEY environment variable.")
973
+
974
  demo = create_app()
975
+ demo.launch(
976
+ server_name="0.0.0.0",
977
+ server_port=7860,
978
+ debug=True,
979
+ show_error=True
980
+ )