Upload 3 files
Browse files- app.py +219 -39
- config.json +6 -6
app.py
CHANGED
@@ -12,14 +12,34 @@ import urllib.parse
|
|
12 |
# Configuration
|
13 |
SPACE_NAME = "AI Assistant"
|
14 |
SPACE_DESCRIPTION = "A customizable AI assistant"
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
# Get access code from environment variable for security
|
20 |
# If SPACE_ACCESS_CODE is not set, no access control is applied
|
21 |
ACCESS_CODE = os.environ.get("SPACE_ACCESS_CODE")
|
22 |
-
ENABLE_DYNAMIC_URLS =
|
23 |
|
24 |
# Get API key from environment - customizable variable name with validation
|
25 |
API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
@@ -463,41 +483,22 @@ def get_configuration_status():
|
|
463 |
"""Generate a configuration status message for display"""
|
464 |
status_parts = []
|
465 |
|
|
|
466 |
if API_KEY_VALID:
|
467 |
-
status_parts.append("✅ **API Key:**
|
468 |
else:
|
469 |
-
status_parts.append("❌ **API Key:** Not configured
|
470 |
|
471 |
-
|
472 |
-
status_parts.append(f"
|
473 |
-
status_parts.append(f"📝 **Max Tokens:** 750")
|
474 |
|
475 |
-
#
|
476 |
if GROUNDING_URLS:
|
477 |
-
status_parts.append(f"
|
478 |
-
# Show first few URLs as examples
|
479 |
-
for i, url in enumerate(GROUNDING_URLS[:3], 1):
|
480 |
-
priority_label = "Primary" if i <= 2 else "Secondary"
|
481 |
-
status_parts.append(f" - [{priority_label}] {url}")
|
482 |
-
if len(GROUNDING_URLS) > 3:
|
483 |
-
status_parts.append(f" - ... and {len(GROUNDING_URLS) - 3} more URLs")
|
484 |
-
else:
|
485 |
-
status_parts.append("🔗 **URL Grounding:** No URLs configured")
|
486 |
-
|
487 |
-
if ENABLE_DYNAMIC_URLS:
|
488 |
-
status_parts.append("🔄 **Dynamic URLs:** Enabled")
|
489 |
-
else:
|
490 |
-
status_parts.append("🔄 **Dynamic URLs:** Disabled")
|
491 |
|
|
|
492 |
if ACCESS_CODE is not None:
|
493 |
-
status_parts.append("🔐 **Access
|
494 |
-
else:
|
495 |
-
status_parts.append("🌐 **Access:** Public Chatbot")
|
496 |
-
|
497 |
-
# System Prompt (add at the end)
|
498 |
-
status_parts.append("") # Empty line for spacing
|
499 |
-
status_parts.append("**System Prompt:**")
|
500 |
-
status_parts.append(f"{SYSTEM_PROMPT}")
|
501 |
|
502 |
return "\n".join(status_parts)
|
503 |
|
@@ -527,14 +528,17 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
527 |
fn=store_and_generate_response, # Use wrapper function to store history
|
528 |
title="", # Title already shown above
|
529 |
description="", # Description already shown above
|
530 |
-
examples=[
|
531 |
type="messages" # Use modern message format for better compatibility
|
532 |
)
|
533 |
|
534 |
-
# Export functionality
|
535 |
with gr.Row():
|
536 |
-
|
537 |
-
|
|
|
|
|
|
|
538 |
|
539 |
# Connect export functionality
|
540 |
export_btn.click(
|
@@ -542,8 +546,8 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
542 |
outputs=[export_file]
|
543 |
)
|
544 |
|
545 |
-
# Configuration status
|
546 |
-
with gr.Accordion("
|
547 |
gr.Markdown(get_configuration_status())
|
548 |
|
549 |
# Connect access verification
|
@@ -559,5 +563,181 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
559 |
outputs=[access_error, chat_section, access_granted]
|
560 |
)
|
561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
562 |
if __name__ == "__main__":
|
563 |
demo.launch()
|
|
|
12 |
# Configuration
|
13 |
SPACE_NAME = "AI Assistant"
|
14 |
SPACE_DESCRIPTION = "A customizable AI assistant"
|
15 |
+
|
16 |
+
# Default configuration values
|
17 |
+
DEFAULT_SYSTEM_PROMPT = """You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation."""
|
18 |
+
DEFAULT_TEMPERATURE = 0.7
|
19 |
+
DEFAULT_MAX_TOKENS = 750
|
20 |
+
|
21 |
+
# Try to load configuration from file (if modified by faculty)
|
22 |
+
try:
|
23 |
+
with open('config.json', 'r') as f:
|
24 |
+
saved_config = json.load(f)
|
25 |
+
SYSTEM_PROMPT = saved_config.get('system_prompt', DEFAULT_SYSTEM_PROMPT)
|
26 |
+
temperature = saved_config.get('temperature', DEFAULT_TEMPERATURE)
|
27 |
+
max_tokens = saved_config.get('max_tokens', DEFAULT_MAX_TOKENS)
|
28 |
+
print("✅ Loaded configuration from config.json")
|
29 |
+
except:
|
30 |
+
# Use defaults if no config file or error
|
31 |
+
SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT
|
32 |
+
temperature = DEFAULT_TEMPERATURE
|
33 |
+
max_tokens = DEFAULT_MAX_TOKENS
|
34 |
+
print("ℹ️ Using default configuration")
|
35 |
+
|
36 |
+
MODEL = "google/gemini-2.0-flash-001"
|
37 |
+
THEME = "Default" # Gradio theme name
|
38 |
+
GROUNDING_URLS = []
|
39 |
# Get access code from environment variable for security
|
40 |
# If SPACE_ACCESS_CODE is not set, no access control is applied
|
41 |
ACCESS_CODE = os.environ.get("SPACE_ACCESS_CODE")
|
42 |
+
ENABLE_DYNAMIC_URLS = True
|
43 |
|
44 |
# Get API key from environment - customizable variable name with validation
|
45 |
API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
|
|
483 |
"""Generate a configuration status message for display"""
|
484 |
status_parts = []
|
485 |
|
486 |
+
# API Key status - simplified
|
487 |
if API_KEY_VALID:
|
488 |
+
status_parts.append("✅ **API Key:** Ready")
|
489 |
else:
|
490 |
+
status_parts.append("❌ **API Key:** Not configured ([set OPENROUTER_API_KEY in settings](https://huggingface.co/docs/hub/spaces-overview#managing-secrets))")
|
491 |
|
492 |
+
# Basic info in a more compact format
|
493 |
+
status_parts.append(f"**Model:** {MODEL.split('/')[-1]}") # Show only model name, not provider
|
|
|
494 |
|
495 |
+
# Only show URLs if configured
|
496 |
if GROUNDING_URLS:
|
497 |
+
status_parts.append(f"**Context URLs:** {len(GROUNDING_URLS)} configured")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
|
499 |
+
# Access info only if restricted
|
500 |
if ACCESS_CODE is not None:
|
501 |
+
status_parts.append("🔐 **Access:** Password protected")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
502 |
|
503 |
return "\n".join(status_parts)
|
504 |
|
|
|
528 |
fn=store_and_generate_response, # Use wrapper function to store history
|
529 |
title="", # Title already shown above
|
530 |
description="", # Description already shown above
|
531 |
+
examples=['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?'],
|
532 |
type="messages" # Use modern message format for better compatibility
|
533 |
)
|
534 |
|
535 |
+
# Export functionality - subtle placement
|
536 |
with gr.Row():
|
537 |
+
with gr.Column(scale=10):
|
538 |
+
pass # Empty column for spacing
|
539 |
+
with gr.Column(scale=2):
|
540 |
+
export_btn = gr.Button("Export", variant="secondary", size="sm")
|
541 |
+
export_file = gr.File(label="Download", visible=False)
|
542 |
|
543 |
# Connect export functionality
|
544 |
export_btn.click(
|
|
|
546 |
outputs=[export_file]
|
547 |
)
|
548 |
|
549 |
+
# Configuration status - minimized when everything is working
|
550 |
+
with gr.Accordion("ℹ️ Status", open=not API_KEY_VALID):
|
551 |
gr.Markdown(get_configuration_status())
|
552 |
|
553 |
# Connect access verification
|
|
|
563 |
outputs=[access_error, chat_section, access_granted]
|
564 |
)
|
565 |
|
566 |
+
# Faculty Configuration Section - appears at the bottom with password protection
|
567 |
+
with gr.Accordion("🔧 Faculty Configuration", open=False, visible=True) as faculty_section:
|
568 |
+
gr.Markdown("**Faculty Only:** Edit assistant configuration. Requires FACULTY_CONFIG_PASSWORD secret.")
|
569 |
+
|
570 |
+
# Check if faculty password is configured
|
571 |
+
FACULTY_PASSWORD = os.environ.get("FACULTY_CONFIG_PASSWORD", "").strip()
|
572 |
+
|
573 |
+
if FACULTY_PASSWORD:
|
574 |
+
faculty_auth_state = gr.State(False)
|
575 |
+
|
576 |
+
# Authentication row
|
577 |
+
with gr.Row() as faculty_auth_row:
|
578 |
+
faculty_password_input = gr.Textbox(
|
579 |
+
label="Faculty Password",
|
580 |
+
type="password",
|
581 |
+
placeholder="Enter faculty configuration password"
|
582 |
+
)
|
583 |
+
faculty_auth_btn = gr.Button("Unlock Configuration", variant="primary")
|
584 |
+
faculty_auth_status = gr.Markdown("")
|
585 |
+
|
586 |
+
# Configuration editor (hidden until authenticated)
|
587 |
+
with gr.Column(visible=False) as faculty_config_section:
|
588 |
+
gr.Markdown("### Edit Assistant Configuration")
|
589 |
+
gr.Markdown("⚠️ **Warning:** Changes will affect all users immediately.")
|
590 |
+
|
591 |
+
# Load current configuration
|
592 |
+
try:
|
593 |
+
with open('config.json', 'r') as f:
|
594 |
+
current_config = json.load(f)
|
595 |
+
except:
|
596 |
+
current_config = {
|
597 |
+
'system_prompt': SYSTEM_PROMPT,
|
598 |
+
'temperature': 0.7,
|
599 |
+
'max_tokens': 750,
|
600 |
+
'locked': False
|
601 |
+
}
|
602 |
+
|
603 |
+
# Editable fields
|
604 |
+
edit_system_prompt = gr.Textbox(
|
605 |
+
label="System Prompt",
|
606 |
+
value=current_config.get('system_prompt', SYSTEM_PROMPT),
|
607 |
+
lines=5
|
608 |
+
)
|
609 |
+
|
610 |
+
with gr.Row():
|
611 |
+
edit_temperature = gr.Slider(
|
612 |
+
label="Temperature",
|
613 |
+
minimum=0,
|
614 |
+
maximum=2,
|
615 |
+
value=current_config.get('temperature', 0.7),
|
616 |
+
step=0.1
|
617 |
+
)
|
618 |
+
edit_max_tokens = gr.Slider(
|
619 |
+
label="Max Tokens",
|
620 |
+
minimum=50,
|
621 |
+
maximum=4096,
|
622 |
+
value=current_config.get('max_tokens', 750),
|
623 |
+
step=50
|
624 |
+
)
|
625 |
+
|
626 |
+
config_locked = gr.Checkbox(
|
627 |
+
label="Lock Configuration (Prevent further edits)",
|
628 |
+
value=current_config.get('locked', False)
|
629 |
+
)
|
630 |
+
|
631 |
+
with gr.Row():
|
632 |
+
save_config_btn = gr.Button("💾 Save Configuration", variant="primary")
|
633 |
+
reset_config_btn = gr.Button("↩️ Reset to Defaults", variant="secondary")
|
634 |
+
|
635 |
+
config_status = gr.Markdown("")
|
636 |
+
|
637 |
+
# Faculty authentication function
|
638 |
+
def verify_faculty_password(password):
|
639 |
+
if password == FACULTY_PASSWORD:
|
640 |
+
return (
|
641 |
+
gr.update(value="✅ Authentication successful!"),
|
642 |
+
gr.update(visible=False), # Hide auth row
|
643 |
+
gr.update(visible=True), # Show config section
|
644 |
+
True # Update auth state
|
645 |
+
)
|
646 |
+
else:
|
647 |
+
return (
|
648 |
+
gr.update(value="❌ Invalid password"),
|
649 |
+
gr.update(visible=True), # Keep auth row visible
|
650 |
+
gr.update(visible=False), # Keep config hidden
|
651 |
+
False # Auth failed
|
652 |
+
)
|
653 |
+
|
654 |
+
# Save configuration function
|
655 |
+
def save_configuration(new_prompt, new_temp, new_tokens, lock_config, is_authenticated):
|
656 |
+
if not is_authenticated:
|
657 |
+
return "❌ Not authenticated"
|
658 |
+
|
659 |
+
# Check if configuration is already locked
|
660 |
+
try:
|
661 |
+
with open('config.json', 'r') as f:
|
662 |
+
existing_config = json.load(f)
|
663 |
+
if existing_config.get('locked', False):
|
664 |
+
return "🔒 Configuration is locked and cannot be modified"
|
665 |
+
except:
|
666 |
+
pass
|
667 |
+
|
668 |
+
# Save new configuration
|
669 |
+
new_config = {
|
670 |
+
'system_prompt': new_prompt,
|
671 |
+
'temperature': new_temp,
|
672 |
+
'max_tokens': int(new_tokens),
|
673 |
+
'locked': lock_config,
|
674 |
+
'last_modified': datetime.now().isoformat(),
|
675 |
+
'modified_by': 'faculty'
|
676 |
+
}
|
677 |
+
|
678 |
+
try:
|
679 |
+
with open('config.json', 'w') as f:
|
680 |
+
json.dump(new_config, f, indent=2)
|
681 |
+
|
682 |
+
# Update global variables
|
683 |
+
global SYSTEM_PROMPT, temperature, max_tokens
|
684 |
+
SYSTEM_PROMPT = new_prompt
|
685 |
+
temperature = new_temp
|
686 |
+
max_tokens = int(new_tokens)
|
687 |
+
|
688 |
+
return f"✅ Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
689 |
+
except Exception as e:
|
690 |
+
return f"❌ Error saving configuration: {str(e)}"
|
691 |
+
|
692 |
+
# Reset configuration function
|
693 |
+
def reset_configuration(is_authenticated):
|
694 |
+
if not is_authenticated:
|
695 |
+
return "❌ Not authenticated", gr.update(), gr.update(), gr.update()
|
696 |
+
|
697 |
+
# Check if locked
|
698 |
+
try:
|
699 |
+
with open('config.json', 'r') as f:
|
700 |
+
existing_config = json.load(f)
|
701 |
+
if existing_config.get('locked', False):
|
702 |
+
return "🔒 Configuration is locked", gr.update(), gr.update(), gr.update()
|
703 |
+
except:
|
704 |
+
pass
|
705 |
+
|
706 |
+
# Reset to original values
|
707 |
+
return (
|
708 |
+
"↩️ Reset to default values",
|
709 |
+
gr.update(value=SYSTEM_PROMPT),
|
710 |
+
gr.update(value=0.7),
|
711 |
+
gr.update(value=750)
|
712 |
+
)
|
713 |
+
|
714 |
+
# Connect authentication
|
715 |
+
faculty_auth_btn.click(
|
716 |
+
verify_faculty_password,
|
717 |
+
inputs=[faculty_password_input],
|
718 |
+
outputs=[faculty_auth_status, faculty_auth_row, faculty_config_section, faculty_auth_state]
|
719 |
+
)
|
720 |
+
|
721 |
+
faculty_password_input.submit(
|
722 |
+
verify_faculty_password,
|
723 |
+
inputs=[faculty_password_input],
|
724 |
+
outputs=[faculty_auth_status, faculty_auth_row, faculty_config_section, faculty_auth_state]
|
725 |
+
)
|
726 |
+
|
727 |
+
# Connect configuration buttons
|
728 |
+
save_config_btn.click(
|
729 |
+
save_configuration,
|
730 |
+
inputs=[edit_system_prompt, edit_temperature, edit_max_tokens, config_locked, faculty_auth_state],
|
731 |
+
outputs=[config_status]
|
732 |
+
)
|
733 |
+
|
734 |
+
reset_config_btn.click(
|
735 |
+
reset_configuration,
|
736 |
+
inputs=[faculty_auth_state],
|
737 |
+
outputs=[config_status, edit_system_prompt, edit_temperature, edit_max_tokens]
|
738 |
+
)
|
739 |
+
else:
|
740 |
+
gr.Markdown("ℹ️ Faculty configuration is not enabled. Set FACULTY_CONFIG_PASSWORD in Space secrets to enable.")
|
741 |
+
|
742 |
if __name__ == "__main__":
|
743 |
demo.launch()
|
config.json
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
{
|
2 |
"name": "AI Assistant",
|
3 |
"description": "A customizable AI assistant",
|
4 |
-
"system_prompt": "You are
|
5 |
-
"model": "
|
6 |
"api_key_var": "OPENROUTER_API_KEY",
|
7 |
"temperature": 0.7,
|
8 |
"max_tokens": 750,
|
9 |
-
"examples": "[
|
10 |
-
"grounding_urls": "[
|
11 |
-
"enable_dynamic_urls":
|
12 |
-
"theme": "
|
13 |
}
|
|
|
1 |
{
|
2 |
"name": "AI Assistant",
|
3 |
"description": "A customizable AI assistant",
|
4 |
+
"system_prompt": "You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation.",
|
5 |
+
"model": "google/gemini-2.0-flash-001",
|
6 |
"api_key_var": "OPENROUTER_API_KEY",
|
7 |
"temperature": 0.7,
|
8 |
"max_tokens": 750,
|
9 |
+
"examples": "['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?']",
|
10 |
+
"grounding_urls": "[]",
|
11 |
+
"enable_dynamic_urls": true,
|
12 |
+
"theme": "Default"
|
13 |
}
|