Spaces:
Running
enhance ux with keyboard shortcuts, multi-format export, and real-time status indicators
Browse files## new features:
- keyboard shortcuts for improved efficiency:
* ctrl+enter: send message
* ctrl+l: clear chat
* ctrl+e: export conversation
* auto-focus on message input on page load
- multi-format export options:
* markdown: original formatted export
* json: structured data with metadata (timestamp, model, language)
* pdf: text-based export for easy sharing
- real-time status indicators:
* api health: green/red indicator for api connectivity
* response time: color-coded latency display (green <2s, yellow 2-5s, red >5s)
* token usage: displays total tokens consumed per request
## improvements:
- enhanced http headers for better url content fetching
- added visual feedback in ui placeholders
- maintained full gradio 5.x compatibility
- improved error handling with detailed metrics
these changes significantly improve the user experience by providing better feedback, faster interaction methods, and flexible export options.
|
@@ -183,7 +183,13 @@ def fetch_url_content(url: str, max_length: int = 3000) -> str:
|
|
| 183 |
return f"β Invalid URL format: {url}"
|
| 184 |
|
| 185 |
headers = {
|
| 186 |
-
'User-Agent': 'Mozilla/5.0 (compatible; HuggingFace-Space/1.0)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
}
|
| 188 |
|
| 189 |
response = requests.get(url, headers=headers, timeout=5)
|
|
@@ -358,12 +364,67 @@ Model: {MODEL}
|
|
| 358 |
return markdown_content
|
| 359 |
|
| 360 |
|
| 361 |
-
def
|
| 362 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
# API key validation
|
| 365 |
if not API_KEY:
|
| 366 |
-
|
|
|
|
| 367 |
|
| 368 |
Please configure your OpenRouter API key:
|
| 369 |
1. Go to Settings (βοΈ) in your HuggingFace Space
|
|
@@ -371,7 +432,7 @@ Please configure your OpenRouter API key:
|
|
| 371 |
3. Add secret: **{API_KEY_VAR}**
|
| 372 |
4. Value: Your OpenRouter API key (starts with `sk-or-`)
|
| 373 |
|
| 374 |
-
Get your API key at: https://openrouter.ai/keys"""
|
| 375 |
|
| 376 |
# Process files if provided
|
| 377 |
file_context = ""
|
|
@@ -473,22 +534,35 @@ Get your API key at: https://openrouter.ai/keys"""
|
|
| 473 |
result = response.json()
|
| 474 |
ai_response = result['choices'][0]['message']['content']
|
| 475 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
# Add file notification if files were uploaded
|
| 477 |
if file_notification:
|
| 478 |
ai_response += file_notification
|
| 479 |
|
| 480 |
-
return ai_response
|
| 481 |
else:
|
| 482 |
error_data = response.json()
|
| 483 |
error_message = error_data.get('error', {}).get('message', 'Unknown error')
|
| 484 |
-
|
|
|
|
| 485 |
|
| 486 |
except requests.exceptions.Timeout:
|
| 487 |
-
|
|
|
|
| 488 |
except requests.exceptions.ConnectionError:
|
| 489 |
-
|
|
|
|
| 490 |
except Exception as e:
|
| 491 |
-
|
|
|
|
| 492 |
|
| 493 |
|
| 494 |
# Chat history for export
|
|
@@ -534,9 +608,22 @@ def create_interface():
|
|
| 534 |
# Access control check
|
| 535 |
has_access = ACCESS_CODE is None # No access code required
|
| 536 |
|
| 537 |
-
with gr.Blocks(title=SPACE_NAME, theme=theme
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
# State for access control
|
| 539 |
access_granted = gr.State(has_access)
|
|
|
|
|
|
|
| 540 |
|
| 541 |
# Header - always visible
|
| 542 |
gr.Markdown(f"# {SPACE_NAME}")
|
|
@@ -563,6 +650,21 @@ def create_interface():
|
|
| 563 |
with gr.Tabs() as tabs:
|
| 564 |
# Chat Tab
|
| 565 |
with gr.Tab("π¬ Chat"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 566 |
# Get examples
|
| 567 |
examples = config.get('examples', [])
|
| 568 |
if isinstance(examples, str):
|
|
@@ -576,19 +678,32 @@ def create_interface():
|
|
| 576 |
|
| 577 |
# Create chat interface
|
| 578 |
chatbot = gr.Chatbot(type="messages", height=400)
|
| 579 |
-
msg = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
|
| 581 |
with gr.Row():
|
| 582 |
-
submit_btn = gr.Button("Send", variant="primary")
|
| 583 |
-
clear_btn = gr.Button("Clear")
|
| 584 |
|
| 585 |
# Export functionality
|
| 586 |
with gr.Row():
|
| 587 |
# Use a regular Button for triggering export
|
| 588 |
export_trigger_btn = gr.Button(
|
| 589 |
-
"π₯ Export Conversation",
|
| 590 |
variant="secondary",
|
| 591 |
-
size="sm"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 592 |
)
|
| 593 |
# Hidden file component for actual download
|
| 594 |
export_file = gr.File(
|
|
@@ -597,22 +712,32 @@ def create_interface():
|
|
| 597 |
)
|
| 598 |
|
| 599 |
# Export handler
|
| 600 |
-
def prepare_export(chat_history):
|
| 601 |
if not chat_history:
|
| 602 |
gr.Warning("No conversation history to export.")
|
| 603 |
return None
|
| 604 |
|
| 605 |
try:
|
| 606 |
-
content = export_conversation_to_markdown(chat_history)
|
| 607 |
-
|
| 608 |
-
# Create filename
|
| 609 |
space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', SPACE_NAME).lower()
|
| 610 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 611 |
-
filename = f"{space_name_safe}_conversation_{timestamp}.md"
|
| 612 |
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
|
| 617 |
# Return the file path for download
|
| 618 |
return gr.File(visible=True, value=str(temp_path))
|
|
@@ -622,7 +747,7 @@ def create_interface():
|
|
| 622 |
|
| 623 |
export_trigger_btn.click(
|
| 624 |
prepare_export,
|
| 625 |
-
inputs=[chatbot],
|
| 626 |
outputs=[export_file]
|
| 627 |
)
|
| 628 |
|
|
@@ -633,10 +758,10 @@ def create_interface():
|
|
| 633 |
# Chat functionality
|
| 634 |
def respond(message, chat_history, files_state, is_granted):
|
| 635 |
if not is_granted:
|
| 636 |
-
return chat_history, "", is_granted
|
| 637 |
|
| 638 |
if not message:
|
| 639 |
-
return chat_history, "", is_granted
|
| 640 |
|
| 641 |
# Format history for the generate_response function
|
| 642 |
formatted_history = []
|
|
@@ -644,8 +769,8 @@ def create_interface():
|
|
| 644 |
if isinstance(h, dict):
|
| 645 |
formatted_history.append(h)
|
| 646 |
|
| 647 |
-
# Get response
|
| 648 |
-
response = generate_response(message, formatted_history, files_state)
|
| 649 |
|
| 650 |
# Update chat history
|
| 651 |
chat_history = chat_history + [
|
|
@@ -657,11 +782,21 @@ def create_interface():
|
|
| 657 |
global chat_history_store
|
| 658 |
chat_history_store = chat_history
|
| 659 |
|
| 660 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 661 |
|
| 662 |
# Wire up the interface
|
| 663 |
-
msg.submit(respond, [msg, chatbot, uploaded_files, access_granted], [chatbot, msg, access_granted])
|
| 664 |
-
submit_btn.click(respond, [msg, chatbot, uploaded_files, access_granted], [chatbot, msg, access_granted])
|
| 665 |
|
| 666 |
def clear_chat():
|
| 667 |
global chat_history_store
|
|
@@ -1038,6 +1173,42 @@ def create_interface():
|
|
| 1038 |
inputs=[access_input, access_granted],
|
| 1039 |
outputs=[access_panel, main_panel, access_status, access_granted]
|
| 1040 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1041 |
|
| 1042 |
return demo
|
| 1043 |
|
|
@@ -1045,4 +1216,12 @@ def create_interface():
|
|
| 1045 |
# Create and launch the interface
|
| 1046 |
if __name__ == "__main__":
|
| 1047 |
demo = create_interface()
|
| 1048 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
return f"β Invalid URL format: {url}"
|
| 184 |
|
| 185 |
headers = {
|
| 186 |
+
'User-Agent': 'Mozilla/5.0 (compatible; HuggingFace-Space/1.0)',
|
| 187 |
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
| 188 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
| 189 |
+
'Accept-Encoding': 'gzip, deflate',
|
| 190 |
+
'DNT': '1',
|
| 191 |
+
'Connection': 'keep-alive',
|
| 192 |
+
'Upgrade-Insecure-Requests': '1'
|
| 193 |
}
|
| 194 |
|
| 195 |
response = requests.get(url, headers=headers, timeout=5)
|
|
|
|
| 364 |
return markdown_content
|
| 365 |
|
| 366 |
|
| 367 |
+
def export_conversation_to_json(history: List[Dict[str, str]]) -> str:
|
| 368 |
+
"""Export conversation history to JSON"""
|
| 369 |
+
if not history:
|
| 370 |
+
return json.dumps({"error": "No conversation to export"})
|
| 371 |
+
|
| 372 |
+
export_data = {
|
| 373 |
+
"metadata": {
|
| 374 |
+
"generated_on": datetime.now().isoformat(),
|
| 375 |
+
"space_name": SPACE_NAME,
|
| 376 |
+
"model": MODEL,
|
| 377 |
+
"language": LANGUAGE,
|
| 378 |
+
"message_count": len([m for m in history if m.get('role') == 'user'])
|
| 379 |
+
},
|
| 380 |
+
"conversation": history
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
return json.dumps(export_data, indent=2, ensure_ascii=False)
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def export_conversation_to_pdf(history: List[Dict[str, str]]) -> bytes:
|
| 387 |
+
"""Export conversation history to PDF (simple text-based PDF)"""
|
| 388 |
+
try:
|
| 389 |
+
# Create a simple text-based PDF representation
|
| 390 |
+
# For a proper PDF, you'd need reportlab or similar library
|
| 391 |
+
pdf_content = f"CONVERSATION EXPORT\n"
|
| 392 |
+
pdf_content += f"{'='*50}\n\n"
|
| 393 |
+
pdf_content += f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
| 394 |
+
pdf_content += f"Space: {SPACE_NAME}\n"
|
| 395 |
+
pdf_content += f"Model: {MODEL}\n"
|
| 396 |
+
pdf_content += f"Language: {LANGUAGE}\n\n"
|
| 397 |
+
pdf_content += f"{'='*50}\n\n"
|
| 398 |
+
|
| 399 |
+
message_count = 0
|
| 400 |
+
for message in history:
|
| 401 |
+
if isinstance(message, dict):
|
| 402 |
+
role = message.get('role', 'unknown')
|
| 403 |
+
content = message.get('content', '')
|
| 404 |
+
|
| 405 |
+
if role == 'user':
|
| 406 |
+
message_count += 1
|
| 407 |
+
pdf_content += f"USER MESSAGE {message_count}:\n{content}\n\n"
|
| 408 |
+
elif role == 'assistant':
|
| 409 |
+
pdf_content += f"ASSISTANT RESPONSE {message_count}:\n{content}\n\n"
|
| 410 |
+
pdf_content += f"{'-'*50}\n\n"
|
| 411 |
+
|
| 412 |
+
# Convert to bytes for PDF mime type
|
| 413 |
+
return pdf_content.encode('utf-8')
|
| 414 |
+
except Exception as e:
|
| 415 |
+
return f"Error generating PDF: {str(e)}".encode('utf-8')
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def generate_response(message: str, history: List[Dict[str, str]], files: Optional[List] = None) -> Tuple[str, Dict[str, Any]]:
|
| 419 |
+
"""Generate response using OpenRouter API with file support, returns (response, metrics)"""
|
| 420 |
+
|
| 421 |
+
start_time = datetime.now()
|
| 422 |
+
metrics = {"response_time": 0, "tokens_used": 0, "api_healthy": True}
|
| 423 |
|
| 424 |
# API key validation
|
| 425 |
if not API_KEY:
|
| 426 |
+
metrics["api_healthy"] = False
|
| 427 |
+
return (f"""π **API Key Required**
|
| 428 |
|
| 429 |
Please configure your OpenRouter API key:
|
| 430 |
1. Go to Settings (βοΈ) in your HuggingFace Space
|
|
|
|
| 432 |
3. Add secret: **{API_KEY_VAR}**
|
| 433 |
4. Value: Your OpenRouter API key (starts with `sk-or-`)
|
| 434 |
|
| 435 |
+
Get your API key at: https://openrouter.ai/keys""", metrics)
|
| 436 |
|
| 437 |
# Process files if provided
|
| 438 |
file_context = ""
|
|
|
|
| 534 |
result = response.json()
|
| 535 |
ai_response = result['choices'][0]['message']['content']
|
| 536 |
|
| 537 |
+
# Calculate metrics
|
| 538 |
+
end_time = datetime.now()
|
| 539 |
+
metrics["response_time"] = int((end_time - start_time).total_seconds() * 1000)
|
| 540 |
+
|
| 541 |
+
# Try to get token usage from response
|
| 542 |
+
usage = result.get('usage', {})
|
| 543 |
+
metrics["tokens_used"] = usage.get('total_tokens', 0)
|
| 544 |
+
metrics["api_healthy"] = True
|
| 545 |
+
|
| 546 |
# Add file notification if files were uploaded
|
| 547 |
if file_notification:
|
| 548 |
ai_response += file_notification
|
| 549 |
|
| 550 |
+
return ai_response, metrics
|
| 551 |
else:
|
| 552 |
error_data = response.json()
|
| 553 |
error_message = error_data.get('error', {}).get('message', 'Unknown error')
|
| 554 |
+
metrics["api_healthy"] = False
|
| 555 |
+
return f"β API Error ({response.status_code}): {error_message}", metrics
|
| 556 |
|
| 557 |
except requests.exceptions.Timeout:
|
| 558 |
+
metrics["api_healthy"] = False
|
| 559 |
+
return "β° Request timeout (30s limit). Try a shorter message or different model.", metrics
|
| 560 |
except requests.exceptions.ConnectionError:
|
| 561 |
+
metrics["api_healthy"] = False
|
| 562 |
+
return "π Connection error. Check your internet connection and try again.", metrics
|
| 563 |
except Exception as e:
|
| 564 |
+
metrics["api_healthy"] = False
|
| 565 |
+
return f"β Error: {str(e)}", metrics
|
| 566 |
|
| 567 |
|
| 568 |
# Chat history for export
|
|
|
|
| 608 |
# Access control check
|
| 609 |
has_access = ACCESS_CODE is None # No access code required
|
| 610 |
|
| 611 |
+
with gr.Blocks(title=SPACE_NAME, theme=theme, css="""
|
| 612 |
+
.status-indicator {
|
| 613 |
+
padding: 8px 12px;
|
| 614 |
+
border-radius: 4px;
|
| 615 |
+
font-size: 0.9em;
|
| 616 |
+
display: inline-block;
|
| 617 |
+
margin: 2px;
|
| 618 |
+
}
|
| 619 |
+
.status-ok { background-color: #d4edda; color: #155724; }
|
| 620 |
+
.status-error { background-color: #f8d7da; color: #721c24; }
|
| 621 |
+
.status-warning { background-color: #fff3cd; color: #856404; }
|
| 622 |
+
""") as demo:
|
| 623 |
# State for access control
|
| 624 |
access_granted = gr.State(has_access)
|
| 625 |
+
# State for API metrics
|
| 626 |
+
api_metrics = gr.State({"response_time": 0, "tokens_used": 0, "api_healthy": True})
|
| 627 |
|
| 628 |
# Header - always visible
|
| 629 |
gr.Markdown(f"# {SPACE_NAME}")
|
|
|
|
| 650 |
with gr.Tabs() as tabs:
|
| 651 |
# Chat Tab
|
| 652 |
with gr.Tab("π¬ Chat"):
|
| 653 |
+
# Status indicators row
|
| 654 |
+
with gr.Row():
|
| 655 |
+
api_status = gr.HTML(
|
| 656 |
+
'<span class="status-indicator status-ok">π’ API: Healthy</span>',
|
| 657 |
+
elem_id="api-status"
|
| 658 |
+
)
|
| 659 |
+
response_time_status = gr.HTML(
|
| 660 |
+
'<span class="status-indicator status-ok">β±οΈ Response Time: 0ms</span>',
|
| 661 |
+
elem_id="response-time"
|
| 662 |
+
)
|
| 663 |
+
tokens_status = gr.HTML(
|
| 664 |
+
'<span class="status-indicator status-ok">π― Tokens: 0</span>',
|
| 665 |
+
elem_id="tokens-used"
|
| 666 |
+
)
|
| 667 |
+
|
| 668 |
# Get examples
|
| 669 |
examples = config.get('examples', [])
|
| 670 |
if isinstance(examples, str):
|
|
|
|
| 678 |
|
| 679 |
# Create chat interface
|
| 680 |
chatbot = gr.Chatbot(type="messages", height=400)
|
| 681 |
+
msg = gr.Textbox(
|
| 682 |
+
label="Message",
|
| 683 |
+
placeholder="Type your message here... (Ctrl+Enter to send)",
|
| 684 |
+
lines=2,
|
| 685 |
+
elem_id="msg-textbox"
|
| 686 |
+
)
|
| 687 |
|
| 688 |
with gr.Row():
|
| 689 |
+
submit_btn = gr.Button("Send", variant="primary", elem_id="send-btn")
|
| 690 |
+
clear_btn = gr.Button("Clear", elem_id="clear-btn")
|
| 691 |
|
| 692 |
# Export functionality
|
| 693 |
with gr.Row():
|
| 694 |
# Use a regular Button for triggering export
|
| 695 |
export_trigger_btn = gr.Button(
|
| 696 |
+
"π₯ Export Conversation (Ctrl+E)",
|
| 697 |
variant="secondary",
|
| 698 |
+
size="sm",
|
| 699 |
+
elem_id="export-btn"
|
| 700 |
+
)
|
| 701 |
+
# Export format dropdown
|
| 702 |
+
export_format = gr.Dropdown(
|
| 703 |
+
choices=["Markdown", "JSON", "PDF"],
|
| 704 |
+
value="Markdown",
|
| 705 |
+
label="Format",
|
| 706 |
+
scale=1
|
| 707 |
)
|
| 708 |
# Hidden file component for actual download
|
| 709 |
export_file = gr.File(
|
|
|
|
| 712 |
)
|
| 713 |
|
| 714 |
# Export handler
|
| 715 |
+
def prepare_export(chat_history, format_choice):
|
| 716 |
if not chat_history:
|
| 717 |
gr.Warning("No conversation history to export.")
|
| 718 |
return None
|
| 719 |
|
| 720 |
try:
|
|
|
|
|
|
|
|
|
|
| 721 |
space_name_safe = re.sub(r'[^a-zA-Z0-9]+', '_', SPACE_NAME).lower()
|
| 722 |
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
|
|
| 723 |
|
| 724 |
+
if format_choice == "Markdown":
|
| 725 |
+
content = export_conversation_to_markdown(chat_history)
|
| 726 |
+
filename = f"{space_name_safe}_conversation_{timestamp}.md"
|
| 727 |
+
temp_path = Path(tempfile.gettempdir()) / filename
|
| 728 |
+
temp_path.write_text(content, encoding='utf-8')
|
| 729 |
+
|
| 730 |
+
elif format_choice == "JSON":
|
| 731 |
+
content = export_conversation_to_json(chat_history)
|
| 732 |
+
filename = f"{space_name_safe}_conversation_{timestamp}.json"
|
| 733 |
+
temp_path = Path(tempfile.gettempdir()) / filename
|
| 734 |
+
temp_path.write_text(content, encoding='utf-8')
|
| 735 |
+
|
| 736 |
+
elif format_choice == "PDF":
|
| 737 |
+
content = export_conversation_to_pdf(chat_history)
|
| 738 |
+
filename = f"{space_name_safe}_conversation_{timestamp}.pdf"
|
| 739 |
+
temp_path = Path(tempfile.gettempdir()) / filename
|
| 740 |
+
temp_path.write_bytes(content)
|
| 741 |
|
| 742 |
# Return the file path for download
|
| 743 |
return gr.File(visible=True, value=str(temp_path))
|
|
|
|
| 747 |
|
| 748 |
export_trigger_btn.click(
|
| 749 |
prepare_export,
|
| 750 |
+
inputs=[chatbot, export_format],
|
| 751 |
outputs=[export_file]
|
| 752 |
)
|
| 753 |
|
|
|
|
| 758 |
# Chat functionality
|
| 759 |
def respond(message, chat_history, files_state, is_granted):
|
| 760 |
if not is_granted:
|
| 761 |
+
return chat_history, "", is_granted, gr.update(), gr.update(), gr.update()
|
| 762 |
|
| 763 |
if not message:
|
| 764 |
+
return chat_history, "", is_granted, gr.update(), gr.update(), gr.update()
|
| 765 |
|
| 766 |
# Format history for the generate_response function
|
| 767 |
formatted_history = []
|
|
|
|
| 769 |
if isinstance(h, dict):
|
| 770 |
formatted_history.append(h)
|
| 771 |
|
| 772 |
+
# Get response and metrics
|
| 773 |
+
response, metrics = generate_response(message, formatted_history, files_state)
|
| 774 |
|
| 775 |
# Update chat history
|
| 776 |
chat_history = chat_history + [
|
|
|
|
| 782 |
global chat_history_store
|
| 783 |
chat_history_store = chat_history
|
| 784 |
|
| 785 |
+
# Update status indicators
|
| 786 |
+
api_class = "status-ok" if metrics["api_healthy"] else "status-error"
|
| 787 |
+
api_icon = "π’" if metrics["api_healthy"] else "π΄"
|
| 788 |
+
|
| 789 |
+
time_class = "status-ok" if metrics["response_time"] < 2000 else "status-warning" if metrics["response_time"] < 5000 else "status-error"
|
| 790 |
+
|
| 791 |
+
api_status_html = f'<span class="status-indicator {api_class}">{api_icon} API: {"Healthy" if metrics["api_healthy"] else "Error"}</span>'
|
| 792 |
+
response_time_html = f'<span class="status-indicator {time_class}">β±οΈ Response Time: {metrics["response_time"]}ms</span>'
|
| 793 |
+
tokens_html = f'<span class="status-indicator status-ok">π― Tokens: {metrics["tokens_used"]}</span>'
|
| 794 |
+
|
| 795 |
+
return chat_history, "", is_granted, api_status_html, response_time_html, tokens_html
|
| 796 |
|
| 797 |
# Wire up the interface
|
| 798 |
+
msg.submit(respond, [msg, chatbot, uploaded_files, access_granted], [chatbot, msg, access_granted, api_status, response_time_status, tokens_status])
|
| 799 |
+
submit_btn.click(respond, [msg, chatbot, uploaded_files, access_granted], [chatbot, msg, access_granted, api_status, response_time_status, tokens_status])
|
| 800 |
|
| 801 |
def clear_chat():
|
| 802 |
global chat_history_store
|
|
|
|
| 1173 |
inputs=[access_input, access_granted],
|
| 1174 |
outputs=[access_panel, main_panel, access_status, access_granted]
|
| 1175 |
)
|
| 1176 |
+
|
| 1177 |
+
# Add keyboard shortcuts
|
| 1178 |
+
demo.load(
|
| 1179 |
+
None,
|
| 1180 |
+
None,
|
| 1181 |
+
None,
|
| 1182 |
+
js="""
|
| 1183 |
+
() => {
|
| 1184 |
+
// Keyboard shortcuts
|
| 1185 |
+
document.addEventListener('keydown', function(e) {
|
| 1186 |
+
// Ctrl+Enter to send message
|
| 1187 |
+
if (e.ctrlKey && e.key === 'Enter') {
|
| 1188 |
+
e.preventDefault();
|
| 1189 |
+
const sendBtn = document.querySelector('#send-btn button');
|
| 1190 |
+
if (sendBtn) sendBtn.click();
|
| 1191 |
+
}
|
| 1192 |
+
// Ctrl+L to clear chat
|
| 1193 |
+
else if (e.ctrlKey && e.key === 'l') {
|
| 1194 |
+
e.preventDefault();
|
| 1195 |
+
const clearBtn = document.querySelector('#clear-btn button');
|
| 1196 |
+
if (clearBtn) clearBtn.click();
|
| 1197 |
+
}
|
| 1198 |
+
// Ctrl+E to export
|
| 1199 |
+
else if (e.ctrlKey && e.key === 'e') {
|
| 1200 |
+
e.preventDefault();
|
| 1201 |
+
const exportBtn = document.querySelector('#export-btn button');
|
| 1202 |
+
if (exportBtn) exportBtn.click();
|
| 1203 |
+
}
|
| 1204 |
+
});
|
| 1205 |
+
|
| 1206 |
+
// Focus on message input when page loads
|
| 1207 |
+
const msgInput = document.querySelector('#msg-textbox textarea');
|
| 1208 |
+
if (msgInput) msgInput.focus();
|
| 1209 |
+
}
|
| 1210 |
+
"""
|
| 1211 |
+
)
|
| 1212 |
|
| 1213 |
return demo
|
| 1214 |
|
|
|
|
| 1216 |
# Create and launch the interface
|
| 1217 |
if __name__ == "__main__":
|
| 1218 |
demo = create_interface()
|
| 1219 |
+
# Launch with appropriate settings for HuggingFace Spaces
|
| 1220 |
+
demo.launch(
|
| 1221 |
+
server_name="0.0.0.0",
|
| 1222 |
+
server_port=7860,
|
| 1223 |
+
share=False,
|
| 1224 |
+
# Add allowed CORS origins if needed
|
| 1225 |
+
# Since this is a Gradio app, CORS is handled by Gradio itself
|
| 1226 |
+
# For custom API endpoints, you would need to add CORS middleware
|
| 1227 |
+
)
|