Upload 2 files
Browse files- app.py +340 -232
- config.json +11 -6
app.py
CHANGED
@@ -13,101 +13,69 @@ import urllib.parse
|
|
13 |
SPACE_NAME = "AI Assistant"
|
14 |
SPACE_DESCRIPTION = "A customizable AI assistant"
|
15 |
|
16 |
-
# Default configuration values
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
'system_prompt': """You are an expert literary analysis instructor specializing in close reading techniques across genres, periods, and theoretical approaches. Your role is to help students develop sophisticated reading practices connecting textual details to interpretive arguments. Follow the close reading framework below:
|
21 |
-
|
22 |
-
1. Reading Process
|
23 |
-
1a. Initial Analysis: ensure literal comprehension; identify literary devices and effects; examine diction, syntax, imagery
|
24 |
-
1b. Pattern Recognition: trace recurring motifs, tensions, contradictions; analyze formal elements by genre; connect style to meaning
|
25 |
-
1c. Interpretive Synthesis: apply theoretical lenses when appropriate; build arguments through textual evidence; acknowledge interpretive complexity
|
26 |
-
|
27 |
-
2. Genre-Specific Techniques:
|
28 |
-
2a. Poetry: prosody, enjambment, sound patterns, visual arrangement
|
29 |
-
2b. Fiction: narration, characterization, setting, temporal structure
|
30 |
-
2c. Drama: dialogue patterns, stage directions, performance considerations
|
31 |
-
|
32 |
-
3. Critical Approaches:
|
33 |
-
3a. **Formal Analysis**: unity, ambiguity, paradox, irony
|
34 |
-
3b. **Contextual Reading**: historical, biographical, cultural frameworks
|
35 |
-
3c. **Theoretical Applications**: psychoanalytic, Marxist, feminist, postcolonial""",
|
36 |
-
'temperature': 0.7,
|
37 |
-
'max_tokens': 1500,
|
38 |
-
'model': "nvidia/llama-3.1-nemotron-70b-instruct",
|
39 |
-
'api_key_var': "API_KEY",
|
40 |
-
'theme': "Origin",
|
41 |
-
'grounding_urls': ["https://owl.purdue.edu/owl/subject_specific_writing/writing_in_literature/writing_about_fiction/index.html", "https://writingcenter.fas.harvard.edu/pages/how-do-close-reading"],
|
42 |
-
'enable_dynamic_urls': True,
|
43 |
-
'examples': ['How do I analyze symbolism in this passage?', 'Help me apply feminist theory to this text', 'What should I look for in poetry analysis?', 'How do I write a literary analysis paper?'],
|
44 |
-
'locked': False
|
45 |
-
}
|
46 |
-
|
47 |
-
# Load configuration from file - this is the single source of truth
|
48 |
-
def load_config():
|
49 |
-
"""Load configuration from config.json with fallback to defaults"""
|
50 |
-
try:
|
51 |
-
with open('config.json', 'r') as f:
|
52 |
-
config = json.load(f)
|
53 |
-
print("✅ Loaded configuration from config.json")
|
54 |
-
return config
|
55 |
-
except FileNotFoundError:
|
56 |
-
print("ℹ️ No config.json found, using default configuration")
|
57 |
-
# Save default config for future use
|
58 |
-
try:
|
59 |
-
with open('config.json', 'w') as f:
|
60 |
-
json.dump(DEFAULT_CONFIG, f, indent=2)
|
61 |
-
print("✅ Created config.json with default values")
|
62 |
-
except:
|
63 |
-
pass
|
64 |
-
return DEFAULT_CONFIG
|
65 |
-
except Exception as e:
|
66 |
-
print(f"⚠️ Error loading config.json: {e}, using defaults")
|
67 |
-
return DEFAULT_CONFIG
|
68 |
-
|
69 |
-
# Load configuration
|
70 |
-
config = load_config()
|
71 |
-
|
72 |
-
# Function to reload configuration values from config
|
73 |
-
def reload_config_values():
|
74 |
-
"""Reload all configuration values from the config dict"""
|
75 |
-
global SPACE_NAME, SPACE_DESCRIPTION, SYSTEM_PROMPT, temperature, max_tokens, MODEL, THEME, GROUNDING_URLS, ENABLE_DYNAMIC_URLS, API_KEY_VAR
|
76 |
-
|
77 |
-
# Reload config from file
|
78 |
-
global config
|
79 |
-
config = load_config()
|
80 |
-
|
81 |
-
# Update all global variables
|
82 |
-
SPACE_NAME = config.get('name', DEFAULT_CONFIG['name'])
|
83 |
-
SPACE_DESCRIPTION = config.get('description', DEFAULT_CONFIG['description'])
|
84 |
-
SYSTEM_PROMPT = config.get('system_prompt', DEFAULT_CONFIG['system_prompt'])
|
85 |
-
temperature = config.get('temperature', DEFAULT_CONFIG['temperature'])
|
86 |
-
max_tokens = config.get('max_tokens', DEFAULT_CONFIG['max_tokens'])
|
87 |
-
MODEL = config.get('model', DEFAULT_CONFIG['model'])
|
88 |
-
THEME = config.get('theme', DEFAULT_CONFIG['theme'])
|
89 |
-
GROUNDING_URLS = config.get('grounding_urls', DEFAULT_CONFIG['grounding_urls'])
|
90 |
-
ENABLE_DYNAMIC_URLS = config.get('enable_dynamic_urls', DEFAULT_CONFIG['enable_dynamic_urls'])
|
91 |
-
API_KEY_VAR = config.get('api_key_var', DEFAULT_CONFIG['api_key_var'])
|
92 |
-
|
93 |
-
# Initial load of configuration values
|
94 |
-
SPACE_NAME = config.get('name', DEFAULT_CONFIG['name'])
|
95 |
-
SPACE_DESCRIPTION = config.get('description', DEFAULT_CONFIG['description'])
|
96 |
-
SYSTEM_PROMPT = config.get('system_prompt', DEFAULT_CONFIG['system_prompt'])
|
97 |
-
temperature = config.get('temperature', DEFAULT_CONFIG['temperature'])
|
98 |
-
max_tokens = config.get('max_tokens', DEFAULT_CONFIG['max_tokens'])
|
99 |
-
MODEL = config.get('model', DEFAULT_CONFIG['model'])
|
100 |
-
THEME = config.get('theme', DEFAULT_CONFIG['theme'])
|
101 |
-
GROUNDING_URLS = config.get('grounding_urls', DEFAULT_CONFIG['grounding_urls'])
|
102 |
-
ENABLE_DYNAMIC_URLS = config.get('enable_dynamic_urls', DEFAULT_CONFIG['enable_dynamic_urls'])
|
103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
104 |
# Get access code from environment variable for security
|
105 |
# If ACCESS_CODE is not set, no access control is applied
|
106 |
ACCESS_CODE = os.environ.get("ACCESS_CODE")
|
|
|
107 |
|
108 |
# Get API key from environment - customizable variable name with validation
|
109 |
-
|
110 |
-
API_KEY = os.environ.get(API_KEY_VAR)
|
111 |
if API_KEY:
|
112 |
API_KEY = API_KEY.strip() # Remove any whitespace
|
113 |
if not API_KEY: # Check if empty after stripping
|
@@ -118,21 +86,21 @@ def validate_api_key():
|
|
118 |
"""Validate API key configuration with detailed logging"""
|
119 |
if not API_KEY:
|
120 |
print(f"⚠️ API KEY CONFIGURATION ERROR:")
|
121 |
-
print(f" Variable name:
|
122 |
print(f" Status: Not set or empty")
|
123 |
-
print(f" Action needed: Set '
|
124 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
125 |
return False
|
126 |
elif not API_KEY.startswith('sk-or-'):
|
127 |
print(f"⚠️ API KEY FORMAT WARNING:")
|
128 |
-
print(f" Variable name:
|
129 |
print(f" Current value: {API_KEY[:10]}..." if len(API_KEY) > 10 else API_KEY)
|
130 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
131 |
print(f" Note: OpenRouter keys should start with 'sk-or-'")
|
132 |
return True # Still try to use it
|
133 |
else:
|
134 |
print(f"✅ API Key configured successfully")
|
135 |
-
print(f" Variable:
|
136 |
print(f" Format: Valid OpenRouter key")
|
137 |
return True
|
138 |
|
@@ -298,10 +266,10 @@ def generate_response(message, history):
|
|
298 |
error_msg += f"Please configure your OpenRouter API key:\n"
|
299 |
error_msg += f"1. Go to Settings (⚙️) in your HuggingFace Space\n"
|
300 |
error_msg += f"2. Click 'Variables and secrets'\n"
|
301 |
-
error_msg += f"3. Add secret: **
|
302 |
error_msg += f"4. Value: Your OpenRouter API key (starts with `sk-or-`)\n\n"
|
303 |
error_msg += f"Get your API key at: https://openrouter.ai/keys"
|
304 |
-
print(f"❌ API request failed: No API key configured for
|
305 |
return error_msg
|
306 |
|
307 |
# Get grounding context
|
@@ -360,7 +328,7 @@ def generate_response(message, history):
|
|
360 |
"model": MODEL,
|
361 |
"messages": messages,
|
362 |
"temperature": 0.7,
|
363 |
-
"max_tokens":
|
364 |
},
|
365 |
timeout=30
|
366 |
)
|
@@ -399,7 +367,7 @@ def generate_response(message, history):
|
|
399 |
error_msg = f"🔐 **Authentication Error**\n\n"
|
400 |
error_msg += f"Your API key appears to be invalid or expired.\n\n"
|
401 |
error_msg += f"**Troubleshooting:**\n"
|
402 |
-
error_msg += f"1. Check that your **
|
403 |
error_msg += f"2. Verify your API key at: https://openrouter.ai/keys\n"
|
404 |
error_msg += f"3. Ensure your key starts with `sk-or-`\n"
|
405 |
error_msg += f"4. Check that you have credits on your OpenRouter account"
|
@@ -474,14 +442,14 @@ def verify_access_code(code):
|
|
474 |
global _access_granted_global
|
475 |
if ACCESS_CODE is None:
|
476 |
_access_granted_global = True
|
477 |
-
return gr.update(visible=False), gr.update(visible=True), gr.update(value=True)
|
478 |
|
479 |
if code == ACCESS_CODE:
|
480 |
_access_granted_global = True
|
481 |
-
return gr.update(visible=False), gr.update(visible=True), gr.update(value=True)
|
482 |
else:
|
483 |
_access_granted_global = False
|
484 |
-
return gr.update(visible=True, value="❌ Incorrect access code. Please try again."), gr.update(visible=False), gr.update(value=False)
|
485 |
|
486 |
def protected_generate_response(message, history):
|
487 |
"""Protected response function that checks access"""
|
@@ -545,47 +513,46 @@ def export_conversation(history):
|
|
545 |
|
546 |
# Configuration status display
|
547 |
def get_configuration_status():
|
548 |
-
"""Generate a
|
549 |
status_parts = []
|
550 |
|
551 |
-
|
552 |
-
status_parts.append("")
|
553 |
-
|
554 |
-
|
555 |
-
status_parts.append(f"**Theme:** {THEME}")
|
556 |
-
status_parts.append(f"**Temperature:** {temperature}")
|
557 |
-
status_parts.append(f"**Max Response Tokens:** {max_tokens}")
|
558 |
-
status_parts.append("")
|
559 |
-
status_parts.append(f"**System Prompt:** {SYSTEM_PROMPT}")
|
560 |
-
|
561 |
-
# Example prompts
|
562 |
-
status_parts.append("")
|
563 |
-
examples_list = config.get('examples', [])
|
564 |
-
if isinstance(examples_list, str):
|
565 |
-
try:
|
566 |
-
import ast
|
567 |
-
examples_list = ast.literal_eval(examples_list)
|
568 |
-
except:
|
569 |
-
examples_list = []
|
570 |
-
|
571 |
-
if examples_list and len(examples_list) > 0:
|
572 |
-
status_parts.append("**Example Prompts:**")
|
573 |
-
for example in examples_list[:5]: # Show up to 5 examples
|
574 |
-
status_parts.append(f"• {example}")
|
575 |
-
if len(examples_list) > 5:
|
576 |
-
status_parts.append(f"• ... and {len(examples_list) - 5} more")
|
577 |
else:
|
578 |
-
status_parts.append("**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
579 |
|
580 |
# URL Context if configured
|
581 |
-
if GROUNDING_URLS
|
582 |
-
status_parts.append("")
|
583 |
-
status_parts.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
584 |
|
585 |
-
#
|
586 |
-
status_parts.append("")
|
587 |
-
|
588 |
-
|
|
|
|
|
589 |
|
590 |
return "\n".join(status_parts)
|
591 |
|
@@ -615,7 +582,7 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
615 |
fn=store_and_generate_response, # Use wrapper function to store history
|
616 |
title="", # Title already shown above
|
617 |
description="", # Description already shown above
|
618 |
-
examples=
|
619 |
type="messages" # Use modern message format for better compatibility
|
620 |
)
|
621 |
|
@@ -630,26 +597,23 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
630 |
outputs=[export_file]
|
631 |
)
|
632 |
|
633 |
-
# Configuration status
|
634 |
-
with gr.Accordion("Configuration", open=False):
|
635 |
-
gr.Markdown(get_configuration_status())
|
636 |
|
637 |
# Connect access verification
|
638 |
if ACCESS_CODE is not None:
|
639 |
access_btn.click(
|
640 |
verify_access_code,
|
641 |
inputs=[access_input],
|
642 |
-
outputs=[access_error, chat_section, access_granted]
|
643 |
)
|
644 |
access_input.submit(
|
645 |
verify_access_code,
|
646 |
inputs=[access_input],
|
647 |
-
outputs=[access_error, chat_section, access_granted]
|
648 |
)
|
649 |
|
650 |
# Faculty Configuration Section - appears at the bottom with password protection
|
651 |
-
with gr.Accordion("Faculty Configuration", open=False, visible=True) as faculty_section:
|
652 |
-
gr.Markdown("Edit assistant configuration. Requires CONFIG_CODE secret.")
|
653 |
|
654 |
# Check if faculty password is configured
|
655 |
FACULTY_PASSWORD = os.environ.get("CONFIG_CODE", "").strip()
|
@@ -680,25 +644,64 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
680 |
current_config = json.load(f)
|
681 |
except:
|
682 |
current_config = {
|
|
|
|
|
|
|
|
|
683 |
'system_prompt': SYSTEM_PROMPT,
|
|
|
|
|
684 |
'temperature': 0.7,
|
685 |
-
'max_tokens':
|
686 |
'locked': False
|
687 |
}
|
688 |
|
689 |
# Editable fields
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
690 |
edit_system_prompt = gr.Textbox(
|
691 |
label="System Prompt",
|
692 |
value=current_config.get('system_prompt', SYSTEM_PROMPT),
|
693 |
lines=5
|
694 |
)
|
695 |
|
696 |
-
#
|
697 |
-
|
698 |
-
if isinstance(
|
699 |
-
examples_text_value = "\n".join(examples_value)
|
700 |
-
else:
|
701 |
-
examples_text_value = ""
|
702 |
|
703 |
edit_examples = gr.Textbox(
|
704 |
label="Example Prompts (one per line)",
|
@@ -719,30 +722,43 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
719 |
label="Max Tokens",
|
720 |
minimum=50,
|
721 |
maximum=4096,
|
722 |
-
value=current_config.get('max_tokens',
|
723 |
step=50
|
724 |
)
|
725 |
|
726 |
# URL Grounding fields
|
727 |
-
gr.Markdown("### URL
|
728 |
-
|
729 |
-
|
|
|
|
|
|
|
730 |
try:
|
731 |
import ast
|
732 |
-
|
733 |
except:
|
734 |
-
|
735 |
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
746 |
|
747 |
config_locked = gr.Checkbox(
|
748 |
label="Lock Configuration (Prevent further edits)",
|
@@ -750,8 +766,8 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
750 |
)
|
751 |
|
752 |
with gr.Row():
|
753 |
-
save_config_btn = gr.Button("Save Configuration", variant="primary")
|
754 |
-
reset_config_btn = gr.Button("Reset to Defaults", variant="secondary")
|
755 |
|
756 |
config_status = gr.Markdown("")
|
757 |
|
@@ -759,77 +775,174 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
759 |
def verify_faculty_password(password):
|
760 |
if password == FACULTY_PASSWORD:
|
761 |
return (
|
762 |
-
gr.update(value="Authentication successful!"),
|
763 |
gr.update(visible=False), # Hide auth row
|
764 |
gr.update(visible=True), # Show config section
|
765 |
True # Update auth state
|
766 |
)
|
767 |
else:
|
768 |
return (
|
769 |
-
gr.update(value="Invalid password"),
|
770 |
gr.update(visible=True), # Keep auth row visible
|
771 |
gr.update(visible=False), # Keep config hidden
|
772 |
False # Auth failed
|
773 |
)
|
774 |
|
775 |
# Save configuration function
|
776 |
-
def save_configuration(new_prompt, new_examples, new_temp, new_tokens,
|
777 |
if not is_authenticated:
|
778 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
779 |
|
780 |
# Check if configuration is already locked
|
781 |
try:
|
782 |
with open('config.json', 'r') as f:
|
783 |
existing_config = json.load(f)
|
784 |
if existing_config.get('locked', False):
|
785 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
786 |
except:
|
787 |
pass
|
788 |
|
789 |
-
#
|
790 |
-
try:
|
791 |
-
with open('config.json', 'r') as f:
|
792 |
-
current_full_config = json.load(f)
|
793 |
-
except:
|
794 |
-
# If config.json doesn't exist, use global config
|
795 |
-
current_full_config = config.copy()
|
796 |
-
|
797 |
-
# Process example prompts
|
798 |
examples_list = [ex.strip() for ex in new_examples.split('\n') if ex.strip()]
|
799 |
|
800 |
-
# Process
|
801 |
-
urls =
|
802 |
-
lock_config = url_values[10] # 11th is lock_config
|
803 |
-
# Filter out empty URLs
|
804 |
grounding_urls = [url.strip() for url in urls if url.strip()]
|
805 |
|
806 |
-
#
|
807 |
-
|
|
|
|
|
|
|
|
|
808 |
'system_prompt': new_prompt,
|
809 |
'examples': examples_list,
|
|
|
810 |
'temperature': new_temp,
|
811 |
'max_tokens': int(new_tokens),
|
812 |
-
'
|
813 |
'locked': lock_config,
|
814 |
'last_modified': datetime.now().isoformat(),
|
815 |
'modified_by': 'faculty'
|
816 |
-
}
|
817 |
|
818 |
try:
|
819 |
with open('config.json', 'w') as f:
|
820 |
-
json.dump(
|
|
|
821 |
|
822 |
-
#
|
823 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
824 |
|
825 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
826 |
except Exception as e:
|
827 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
828 |
|
829 |
# Reset configuration function
|
830 |
def reset_configuration(is_authenticated):
|
831 |
if not is_authenticated:
|
832 |
-
updates = ["Not authenticated"] + [gr.update() for _ in range(
|
833 |
return tuple(updates)
|
834 |
|
835 |
# Check if locked
|
@@ -837,42 +950,33 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
837 |
with open('config.json', 'r') as f:
|
838 |
existing_config = json.load(f)
|
839 |
if existing_config.get('locked', False):
|
840 |
-
updates = ["Configuration is locked"] + [gr.update() for _ in range(
|
841 |
return tuple(updates)
|
842 |
except:
|
843 |
pass
|
844 |
|
845 |
-
#
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
"
|
864 |
-
gr.update(value=
|
865 |
-
gr.update(value=
|
866 |
-
|
867 |
-
gr.update(value=DEFAULT_CONFIG.get('max_tokens', 500))
|
868 |
-
]
|
869 |
-
|
870 |
-
# Add URL updates
|
871 |
-
for i in range(10):
|
872 |
-
url_value = default_urls[i] if i < len(default_urls) else ""
|
873 |
-
updates.append(gr.update(value=url_value))
|
874 |
-
|
875 |
-
return tuple(updates)
|
876 |
|
877 |
# Connect authentication
|
878 |
faculty_auth_btn.click(
|
@@ -890,17 +994,21 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
890 |
# Connect configuration buttons
|
891 |
save_config_btn.click(
|
892 |
save_configuration,
|
893 |
-
inputs=[edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens
|
894 |
-
|
|
|
|
|
|
|
895 |
)
|
896 |
|
897 |
reset_config_btn.click(
|
898 |
reset_configuration,
|
899 |
inputs=[faculty_auth_state],
|
900 |
-
outputs=[config_status, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens
|
|
|
901 |
)
|
902 |
else:
|
903 |
-
gr.Markdown("Faculty configuration is not enabled. Set CONFIG_CODE in Space secrets to enable.")
|
904 |
|
905 |
if __name__ == "__main__":
|
906 |
demo.launch()
|
|
|
13 |
SPACE_NAME = "AI Assistant"
|
14 |
SPACE_DESCRIPTION = "A customizable AI assistant"
|
15 |
|
16 |
+
# Default configuration values
|
17 |
+
DEFAULT_SYSTEM_PROMPT = """You are an AI assistant specialized in mathematics and statistics who guides users through problem-solving rather than providing direct answers. You help users discover solutions by asking strategic questions ('What do we know so far?' 'What method might apply here?' 'Can you identify a pattern?'), prompting them to explain their reasoning, and offering hints that build on their current understanding. Format all mathematical expressions in LaTeX (inline: $x^2 + y^2 = r^2$, display: $$\int_a^b f(x)dx$$). When users are stuck, provide scaffolded support: suggest examining simpler cases, identifying relevant formulas or theorems, or breaking the problem into smaller parts. Use multiple representations to illuminate different aspects of the problem, validate partial progress to build confidence, and help users recognize and correct their own errors through targeted questions rather than corrections. Your goal is to develop problem-solving skills and mathematical reasoning, not just arrive at answers."""
|
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 |
+
SPACE_NAME = saved_config.get('name', "AI Assistant")
|
26 |
+
SPACE_DESCRIPTION = saved_config.get('description', "A customizable AI assistant")
|
27 |
+
MODEL = saved_config.get('model', "google/gemini-2.0-flash-001")
|
28 |
+
THEME = saved_config.get('theme', "Default")
|
29 |
+
SYSTEM_PROMPT = saved_config.get('system_prompt', DEFAULT_SYSTEM_PROMPT)
|
30 |
+
temperature = saved_config.get('temperature', DEFAULT_TEMPERATURE)
|
31 |
+
max_tokens = saved_config.get('max_tokens', DEFAULT_MAX_TOKENS)
|
32 |
+
|
33 |
+
# Load grounding URLs
|
34 |
+
saved_urls = saved_config.get('grounding_urls', [])
|
35 |
+
if isinstance(saved_urls, str):
|
36 |
+
try:
|
37 |
+
import ast
|
38 |
+
GROUNDING_URLS = ast.literal_eval(saved_urls)
|
39 |
+
except:
|
40 |
+
GROUNDING_URLS = []
|
41 |
+
else:
|
42 |
+
GROUNDING_URLS = saved_urls
|
43 |
+
|
44 |
+
# Load examples
|
45 |
+
saved_examples = saved_config.get('examples', ['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?'])
|
46 |
+
if isinstance(saved_examples, str):
|
47 |
+
try:
|
48 |
+
import ast
|
49 |
+
EXAMPLES = ast.literal_eval(saved_examples)
|
50 |
+
except:
|
51 |
+
EXAMPLES = ['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?']
|
52 |
+
elif isinstance(saved_examples, list):
|
53 |
+
EXAMPLES = saved_examples
|
54 |
+
else:
|
55 |
+
EXAMPLES = ['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?']
|
56 |
+
|
57 |
+
print("✅ Loaded configuration from config.json")
|
58 |
+
except:
|
59 |
+
# Use defaults if no config file or error
|
60 |
+
SPACE_NAME = "AI Assistant"
|
61 |
+
SPACE_DESCRIPTION = "A customizable AI assistant"
|
62 |
+
MODEL = "google/gemini-2.0-flash-001"
|
63 |
+
THEME = "Default"
|
64 |
+
SYSTEM_PROMPT = DEFAULT_SYSTEM_PROMPT
|
65 |
+
temperature = DEFAULT_TEMPERATURE
|
66 |
+
max_tokens = DEFAULT_MAX_TOKENS
|
67 |
+
GROUNDING_URLS = []
|
68 |
+
EXAMPLES = ['Hello! How can you help me?', 'Tell me something interesting', 'What can you do?']
|
69 |
+
print("ℹ️ Using default configuration")
|
70 |
+
|
71 |
+
# MODEL, THEME, and GROUNDING_URLS are now set by config loading above
|
72 |
# Get access code from environment variable for security
|
73 |
# If ACCESS_CODE is not set, no access control is applied
|
74 |
ACCESS_CODE = os.environ.get("ACCESS_CODE")
|
75 |
+
ENABLE_DYNAMIC_URLS = True
|
76 |
|
77 |
# Get API key from environment - customizable variable name with validation
|
78 |
+
API_KEY = os.environ.get("API_KEY")
|
|
|
79 |
if API_KEY:
|
80 |
API_KEY = API_KEY.strip() # Remove any whitespace
|
81 |
if not API_KEY: # Check if empty after stripping
|
|
|
86 |
"""Validate API key configuration with detailed logging"""
|
87 |
if not API_KEY:
|
88 |
print(f"⚠️ API KEY CONFIGURATION ERROR:")
|
89 |
+
print(f" Variable name: API_KEY")
|
90 |
print(f" Status: Not set or empty")
|
91 |
+
print(f" Action needed: Set 'API_KEY' in HuggingFace Space secrets")
|
92 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
93 |
return False
|
94 |
elif not API_KEY.startswith('sk-or-'):
|
95 |
print(f"⚠️ API KEY FORMAT WARNING:")
|
96 |
+
print(f" Variable name: API_KEY")
|
97 |
print(f" Current value: {API_KEY[:10]}..." if len(API_KEY) > 10 else API_KEY)
|
98 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
99 |
print(f" Note: OpenRouter keys should start with 'sk-or-'")
|
100 |
return True # Still try to use it
|
101 |
else:
|
102 |
print(f"✅ API Key configured successfully")
|
103 |
+
print(f" Variable: API_KEY")
|
104 |
print(f" Format: Valid OpenRouter key")
|
105 |
return True
|
106 |
|
|
|
266 |
error_msg += f"Please configure your OpenRouter API key:\n"
|
267 |
error_msg += f"1. Go to Settings (⚙️) in your HuggingFace Space\n"
|
268 |
error_msg += f"2. Click 'Variables and secrets'\n"
|
269 |
+
error_msg += f"3. Add secret: **API_KEY**\n"
|
270 |
error_msg += f"4. Value: Your OpenRouter API key (starts with `sk-or-`)\n\n"
|
271 |
error_msg += f"Get your API key at: https://openrouter.ai/keys"
|
272 |
+
print(f"❌ API request failed: No API key configured for API_KEY")
|
273 |
return error_msg
|
274 |
|
275 |
# Get grounding context
|
|
|
328 |
"model": MODEL,
|
329 |
"messages": messages,
|
330 |
"temperature": 0.7,
|
331 |
+
"max_tokens": 750
|
332 |
},
|
333 |
timeout=30
|
334 |
)
|
|
|
367 |
error_msg = f"🔐 **Authentication Error**\n\n"
|
368 |
error_msg += f"Your API key appears to be invalid or expired.\n\n"
|
369 |
error_msg += f"**Troubleshooting:**\n"
|
370 |
+
error_msg += f"1. Check that your **API_KEY** secret is set correctly\n"
|
371 |
error_msg += f"2. Verify your API key at: https://openrouter.ai/keys\n"
|
372 |
error_msg += f"3. Ensure your key starts with `sk-or-`\n"
|
373 |
error_msg += f"4. Check that you have credits on your OpenRouter account"
|
|
|
442 |
global _access_granted_global
|
443 |
if ACCESS_CODE is None:
|
444 |
_access_granted_global = True
|
445 |
+
return gr.update(visible=False), gr.update(visible=True), gr.update(value=True), gr.update(visible=False)
|
446 |
|
447 |
if code == ACCESS_CODE:
|
448 |
_access_granted_global = True
|
449 |
+
return gr.update(visible=False), gr.update(visible=True), gr.update(value=True), gr.update(visible=False)
|
450 |
else:
|
451 |
_access_granted_global = False
|
452 |
+
return gr.update(visible=True, value="❌ Incorrect access code. Please try again."), gr.update(visible=False), gr.update(value=False), gr.update(visible=True)
|
453 |
|
454 |
def protected_generate_response(message, history):
|
455 |
"""Protected response function that checks access"""
|
|
|
513 |
|
514 |
# Configuration status display
|
515 |
def get_configuration_status():
|
516 |
+
"""Generate a configuration status message for display"""
|
517 |
status_parts = []
|
518 |
|
519 |
+
# API Key status
|
520 |
+
status_parts.append("### 🔑 API Configuration")
|
521 |
+
if API_KEY_VALID:
|
522 |
+
status_parts.append("✅ **API Key:** Ready")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
523 |
else:
|
524 |
+
status_parts.append("❌ **API Key:** Not configured")
|
525 |
+
status_parts.append(" Set `API_KEY` in Space secrets")
|
526 |
+
|
527 |
+
# Model and parameters
|
528 |
+
status_parts.append("") # Blank line
|
529 |
+
status_parts.append("### 🤖 Model Settings")
|
530 |
+
status_parts.append(f"**Model:** {MODEL.split('/')[-1]}")
|
531 |
+
status_parts.append(f"**Temperature:** 0.7")
|
532 |
+
status_parts.append(f"**Max Tokens:** 750")
|
533 |
|
534 |
# URL Context if configured
|
535 |
+
if GROUNDING_URLS:
|
536 |
+
status_parts.append("") # Blank line
|
537 |
+
status_parts.append("### 🔗 Context Sources")
|
538 |
+
status_parts.append(f"**URLs Configured:** {len(GROUNDING_URLS)}")
|
539 |
+
for i, url in enumerate(GROUNDING_URLS[:2], 1):
|
540 |
+
status_parts.append(f" {i}. {url[:50]}{'...' if len(url) > 50 else ''}")
|
541 |
+
if len(GROUNDING_URLS) > 2:
|
542 |
+
status_parts.append(f" ... and {len(GROUNDING_URLS) - 2} more")
|
543 |
+
|
544 |
+
# Access control
|
545 |
+
if ACCESS_CODE is not None:
|
546 |
+
status_parts.append("") # Blank line
|
547 |
+
status_parts.append("### 🔐 Access Control")
|
548 |
+
status_parts.append("**Status:** Password protected")
|
549 |
|
550 |
+
# System prompt
|
551 |
+
status_parts.append("") # Blank line
|
552 |
+
status_parts.append("### 📝 System Prompt")
|
553 |
+
# Show first 200 chars of system prompt
|
554 |
+
prompt_preview = SYSTEM_PROMPT[:200] + "..." if len(SYSTEM_PROMPT) > 200 else SYSTEM_PROMPT
|
555 |
+
status_parts.append(f"```\n{prompt_preview}\n```")
|
556 |
|
557 |
return "\n".join(status_parts)
|
558 |
|
|
|
582 |
fn=store_and_generate_response, # Use wrapper function to store history
|
583 |
title="", # Title already shown above
|
584 |
description="", # Description already shown above
|
585 |
+
examples=EXAMPLES if EXAMPLES else None,
|
586 |
type="messages" # Use modern message format for better compatibility
|
587 |
)
|
588 |
|
|
|
597 |
outputs=[export_file]
|
598 |
)
|
599 |
|
|
|
|
|
|
|
600 |
|
601 |
# Connect access verification
|
602 |
if ACCESS_CODE is not None:
|
603 |
access_btn.click(
|
604 |
verify_access_code,
|
605 |
inputs=[access_input],
|
606 |
+
outputs=[access_error, chat_section, access_granted, access_section]
|
607 |
)
|
608 |
access_input.submit(
|
609 |
verify_access_code,
|
610 |
inputs=[access_input],
|
611 |
+
outputs=[access_error, chat_section, access_granted, access_section]
|
612 |
)
|
613 |
|
614 |
# Faculty Configuration Section - appears at the bottom with password protection
|
615 |
+
with gr.Accordion("🔧 Faculty Configuration", open=False, visible=True) as faculty_section:
|
616 |
+
gr.Markdown("**Faculty Only:** Edit assistant configuration. Requires CONFIG_CODE secret.")
|
617 |
|
618 |
# Check if faculty password is configured
|
619 |
FACULTY_PASSWORD = os.environ.get("CONFIG_CODE", "").strip()
|
|
|
644 |
current_config = json.load(f)
|
645 |
except:
|
646 |
current_config = {
|
647 |
+
'name': SPACE_NAME.strip('"'),
|
648 |
+
'description': SPACE_DESCRIPTION.strip('"'),
|
649 |
+
'model': MODEL.strip('"'),
|
650 |
+
'theme': THEME.strip('"'),
|
651 |
'system_prompt': SYSTEM_PROMPT,
|
652 |
+
'examples': [],
|
653 |
+
'grounding_urls': GROUNDING_URLS,
|
654 |
'temperature': 0.7,
|
655 |
+
'max_tokens': 750,
|
656 |
'locked': False
|
657 |
}
|
658 |
|
659 |
# Editable fields
|
660 |
+
edit_name = gr.Textbox(
|
661 |
+
label="Assistant Name",
|
662 |
+
value=current_config.get('name', SPACE_NAME.strip('"')),
|
663 |
+
info="The display name for your assistant"
|
664 |
+
)
|
665 |
+
|
666 |
+
edit_description = gr.Textbox(
|
667 |
+
label="Assistant Description",
|
668 |
+
value=current_config.get('description', SPACE_DESCRIPTION.strip('"')),
|
669 |
+
lines=2,
|
670 |
+
info="Brief description of what your assistant does"
|
671 |
+
)
|
672 |
+
|
673 |
+
edit_model = gr.Dropdown(
|
674 |
+
label="AI Model",
|
675 |
+
choices=[
|
676 |
+
"google/gemini-2.0-flash-001",
|
677 |
+
"google/gemma-3-27b-it",
|
678 |
+
"anthropic/claude-3.5-sonnet",
|
679 |
+
"anthropic/claude-3.5-haiku",
|
680 |
+
"openai/gpt-4o-mini-search-preview",
|
681 |
+
"openai/gpt-4.1-nano",
|
682 |
+
"nvidia/llama-3.1-nemotron-70b-instruct",
|
683 |
+
"mistralai/devstral-small"
|
684 |
+
],
|
685 |
+
value=current_config.get('model', MODEL.strip('"')),
|
686 |
+
info="The AI model to use for responses"
|
687 |
+
)
|
688 |
+
|
689 |
+
edit_theme = gr.Dropdown(
|
690 |
+
label="Interface Theme",
|
691 |
+
choices=["Default", "Origin", "Citrus", "Monochrome", "Soft", "Glass", "Ocean"],
|
692 |
+
value=current_config.get('theme', THEME.strip('"')),
|
693 |
+
info="Visual theme for the chat interface"
|
694 |
+
)
|
695 |
+
|
696 |
edit_system_prompt = gr.Textbox(
|
697 |
label="System Prompt",
|
698 |
value=current_config.get('system_prompt', SYSTEM_PROMPT),
|
699 |
lines=5
|
700 |
)
|
701 |
|
702 |
+
# Get current examples as text
|
703 |
+
current_examples = current_config.get('examples', [])
|
704 |
+
examples_text_value = "\n".join(current_examples) if isinstance(current_examples, list) else ""
|
|
|
|
|
|
|
705 |
|
706 |
edit_examples = gr.Textbox(
|
707 |
label="Example Prompts (one per line)",
|
|
|
722 |
label="Max Tokens",
|
723 |
minimum=50,
|
724 |
maximum=4096,
|
725 |
+
value=current_config.get('max_tokens', 750),
|
726 |
step=50
|
727 |
)
|
728 |
|
729 |
# URL Grounding fields
|
730 |
+
gr.Markdown("### 🔗 URL Context")
|
731 |
+
gr.Markdown("**Primary URLs** (processed first, highest priority)")
|
732 |
+
|
733 |
+
# Get current URLs from config
|
734 |
+
current_urls = current_config.get('grounding_urls', GROUNDING_URLS or [])
|
735 |
+
if isinstance(current_urls, str):
|
736 |
try:
|
737 |
import ast
|
738 |
+
current_urls = ast.literal_eval(current_urls)
|
739 |
except:
|
740 |
+
current_urls = []
|
741 |
|
742 |
+
edit_url1 = gr.Textbox(
|
743 |
+
label="Primary URL 1",
|
744 |
+
value=current_urls[0] if len(current_urls) > 0 else "",
|
745 |
+
placeholder="https://..."
|
746 |
+
)
|
747 |
+
edit_url2 = gr.Textbox(
|
748 |
+
label="Primary URL 2",
|
749 |
+
value=current_urls[1] if len(current_urls) > 1 else "",
|
750 |
+
placeholder="https://..."
|
751 |
+
)
|
752 |
+
|
753 |
+
gr.Markdown("**Secondary URLs** (additional context)")
|
754 |
+
edit_url3 = gr.Textbox(label="Secondary URL 1", value=current_urls[2] if len(current_urls) > 2 else "", placeholder="https://...")
|
755 |
+
edit_url4 = gr.Textbox(label="Secondary URL 2", value=current_urls[3] if len(current_urls) > 3 else "", placeholder="https://...")
|
756 |
+
edit_url5 = gr.Textbox(label="Secondary URL 3", value=current_urls[4] if len(current_urls) > 4 else "", placeholder="https://...")
|
757 |
+
edit_url6 = gr.Textbox(label="Secondary URL 4", value=current_urls[5] if len(current_urls) > 5 else "", placeholder="https://...")
|
758 |
+
edit_url7 = gr.Textbox(label="Secondary URL 5", value=current_urls[6] if len(current_urls) > 6 else "", placeholder="https://...")
|
759 |
+
edit_url8 = gr.Textbox(label="Secondary URL 6", value=current_urls[7] if len(current_urls) > 7 else "", placeholder="https://...")
|
760 |
+
edit_url9 = gr.Textbox(label="Secondary URL 7", value=current_urls[8] if len(current_urls) > 8 else "", placeholder="https://...")
|
761 |
+
edit_url10 = gr.Textbox(label="Secondary URL 8", value=current_urls[9] if len(current_urls) > 9 else "", placeholder="https://...")
|
762 |
|
763 |
config_locked = gr.Checkbox(
|
764 |
label="Lock Configuration (Prevent further edits)",
|
|
|
766 |
)
|
767 |
|
768 |
with gr.Row():
|
769 |
+
save_config_btn = gr.Button("💾 Save Configuration", variant="primary")
|
770 |
+
reset_config_btn = gr.Button("↩️ Reset to Defaults", variant="secondary")
|
771 |
|
772 |
config_status = gr.Markdown("")
|
773 |
|
|
|
775 |
def verify_faculty_password(password):
|
776 |
if password == FACULTY_PASSWORD:
|
777 |
return (
|
778 |
+
gr.update(value="✅ Authentication successful!"),
|
779 |
gr.update(visible=False), # Hide auth row
|
780 |
gr.update(visible=True), # Show config section
|
781 |
True # Update auth state
|
782 |
)
|
783 |
else:
|
784 |
return (
|
785 |
+
gr.update(value="❌ Invalid password"),
|
786 |
gr.update(visible=True), # Keep auth row visible
|
787 |
gr.update(visible=False), # Keep config hidden
|
788 |
False # Auth failed
|
789 |
)
|
790 |
|
791 |
# Save configuration function
|
792 |
+
def save_configuration(new_name, new_description, new_model, new_theme, new_prompt, new_examples, new_temp, new_tokens, url1, url2, url3, url4, url5, url6, url7, url8, url9, url10, lock_config, is_authenticated):
|
793 |
if not is_authenticated:
|
794 |
+
# Return not authenticated message and keep current values
|
795 |
+
return (
|
796 |
+
"❌ Not authenticated",
|
797 |
+
gr.update(), # Keep current name
|
798 |
+
gr.update(), # Keep current description
|
799 |
+
gr.update(), # Keep current model
|
800 |
+
gr.update(), # Keep current theme
|
801 |
+
gr.update(), # Keep current prompt
|
802 |
+
gr.update(), # Keep current examples
|
803 |
+
gr.update(), # Keep current temp
|
804 |
+
gr.update(), # Keep current tokens
|
805 |
+
gr.update(), # Keep current url1
|
806 |
+
gr.update(), # Keep current url2
|
807 |
+
gr.update(), # Keep current url3
|
808 |
+
gr.update(), # Keep current url4
|
809 |
+
gr.update(), # Keep current url5
|
810 |
+
gr.update(), # Keep current url6
|
811 |
+
gr.update(), # Keep current url7
|
812 |
+
gr.update(), # Keep current url8
|
813 |
+
gr.update(), # Keep current url9
|
814 |
+
gr.update(), # Keep current url10
|
815 |
+
gr.update() # Keep current lock
|
816 |
+
)
|
817 |
|
818 |
# Check if configuration is already locked
|
819 |
try:
|
820 |
with open('config.json', 'r') as f:
|
821 |
existing_config = json.load(f)
|
822 |
if existing_config.get('locked', False):
|
823 |
+
# Return locked message and keep current values
|
824 |
+
return (
|
825 |
+
"🔒 Configuration is locked and cannot be modified",
|
826 |
+
gr.update(), # Keep current name
|
827 |
+
gr.update(), # Keep current description
|
828 |
+
gr.update(), # Keep current model
|
829 |
+
gr.update(), # Keep current theme
|
830 |
+
gr.update(), # Keep current prompt
|
831 |
+
gr.update(), # Keep current examples
|
832 |
+
gr.update(), # Keep current temp
|
833 |
+
gr.update(), # Keep current tokens
|
834 |
+
gr.update(), # Keep current url1
|
835 |
+
gr.update(), # Keep current url2
|
836 |
+
gr.update(), # Keep current url3
|
837 |
+
gr.update(), # Keep current url4
|
838 |
+
gr.update(), # Keep current url5
|
839 |
+
gr.update(), # Keep current url6
|
840 |
+
gr.update(), # Keep current url7
|
841 |
+
gr.update(), # Keep current url8
|
842 |
+
gr.update(), # Keep current url9
|
843 |
+
gr.update(), # Keep current url10
|
844 |
+
gr.update() # Keep current lock
|
845 |
+
)
|
846 |
except:
|
847 |
pass
|
848 |
|
849 |
+
# Process examples
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
850 |
examples_list = [ex.strip() for ex in new_examples.split('\n') if ex.strip()]
|
851 |
|
852 |
+
# Process URLs
|
853 |
+
urls = [url1, url2, url3, url4, url5, url6, url7, url8, url9, url10]
|
|
|
|
|
854 |
grounding_urls = [url.strip() for url in urls if url.strip()]
|
855 |
|
856 |
+
# Save new configuration
|
857 |
+
new_config = {
|
858 |
+
'name': new_name,
|
859 |
+
'description': new_description,
|
860 |
+
'model': new_model,
|
861 |
+
'theme': new_theme,
|
862 |
'system_prompt': new_prompt,
|
863 |
'examples': examples_list,
|
864 |
+
'grounding_urls': grounding_urls,
|
865 |
'temperature': new_temp,
|
866 |
'max_tokens': int(new_tokens),
|
867 |
+
'enable_dynamic_urls': True,
|
868 |
'locked': lock_config,
|
869 |
'last_modified': datetime.now().isoformat(),
|
870 |
'modified_by': 'faculty'
|
871 |
+
}
|
872 |
|
873 |
try:
|
874 |
with open('config.json', 'w') as f:
|
875 |
+
json.dump(new_config, f, indent=2)
|
876 |
+
print(f"✅ Faculty config saved to config.json: {new_config}")
|
877 |
|
878 |
+
# Update global variables
|
879 |
+
global SPACE_NAME, SPACE_DESCRIPTION, MODEL, THEME, SYSTEM_PROMPT, temperature, max_tokens, GROUNDING_URLS, EXAMPLES
|
880 |
+
SPACE_NAME = new_name
|
881 |
+
SPACE_DESCRIPTION = new_description
|
882 |
+
MODEL = new_model
|
883 |
+
THEME = new_theme
|
884 |
+
SYSTEM_PROMPT = new_prompt
|
885 |
+
temperature = new_temp
|
886 |
+
max_tokens = int(new_tokens)
|
887 |
+
GROUNDING_URLS = grounding_urls
|
888 |
+
EXAMPLES = examples_list
|
889 |
|
890 |
+
# Return success message AND updated form values
|
891 |
+
success_msg = f"✅ Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n🔄 **Please refresh the page to see all changes take effect.**"
|
892 |
+
|
893 |
+
# Return tuple with status message and all updated field values
|
894 |
+
return (
|
895 |
+
success_msg,
|
896 |
+
gr.update(value=new_name),
|
897 |
+
gr.update(value=new_description),
|
898 |
+
gr.update(value=new_model),
|
899 |
+
gr.update(value=new_theme),
|
900 |
+
gr.update(value=new_prompt),
|
901 |
+
gr.update(value=new_examples),
|
902 |
+
gr.update(value=new_temp),
|
903 |
+
gr.update(value=int(new_tokens)),
|
904 |
+
gr.update(value=url1),
|
905 |
+
gr.update(value=url2),
|
906 |
+
gr.update(value=url3),
|
907 |
+
gr.update(value=url4),
|
908 |
+
gr.update(value=url5),
|
909 |
+
gr.update(value=url6),
|
910 |
+
gr.update(value=url7),
|
911 |
+
gr.update(value=url8),
|
912 |
+
gr.update(value=url9),
|
913 |
+
gr.update(value=url10),
|
914 |
+
gr.update(value=lock_config)
|
915 |
+
)
|
916 |
except Exception as e:
|
917 |
+
error_msg = f"❌ Error saving configuration: {str(e)}"
|
918 |
+
# Return error message and keep current values
|
919 |
+
return (
|
920 |
+
error_msg,
|
921 |
+
gr.update(), # Keep current name
|
922 |
+
gr.update(), # Keep current description
|
923 |
+
gr.update(), # Keep current model
|
924 |
+
gr.update(), # Keep current theme
|
925 |
+
gr.update(), # Keep current prompt
|
926 |
+
gr.update(), # Keep current examples
|
927 |
+
gr.update(), # Keep current temp
|
928 |
+
gr.update(), # Keep current tokens
|
929 |
+
gr.update(), # Keep current url1
|
930 |
+
gr.update(), # Keep current url2
|
931 |
+
gr.update(), # Keep current url3
|
932 |
+
gr.update(), # Keep current url4
|
933 |
+
gr.update(), # Keep current url5
|
934 |
+
gr.update(), # Keep current url6
|
935 |
+
gr.update(), # Keep current url7
|
936 |
+
gr.update(), # Keep current url8
|
937 |
+
gr.update(), # Keep current url9
|
938 |
+
gr.update(), # Keep current url10
|
939 |
+
gr.update() # Keep current lock
|
940 |
+
)
|
941 |
|
942 |
# Reset configuration function
|
943 |
def reset_configuration(is_authenticated):
|
944 |
if not is_authenticated:
|
945 |
+
updates = ["❌ Not authenticated"] + [gr.update() for _ in range(17)] # 1 status + 16 fields
|
946 |
return tuple(updates)
|
947 |
|
948 |
# Check if locked
|
|
|
950 |
with open('config.json', 'r') as f:
|
951 |
existing_config = json.load(f)
|
952 |
if existing_config.get('locked', False):
|
953 |
+
updates = ["🔒 Configuration is locked"] + [gr.update() for _ in range(17)]
|
954 |
return tuple(updates)
|
955 |
except:
|
956 |
pass
|
957 |
|
958 |
+
# Reset to original values
|
959 |
+
return (
|
960 |
+
"↩️ Reset to default values",
|
961 |
+
gr.update(value=SPACE_NAME.strip('"')), # name
|
962 |
+
gr.update(value=SPACE_DESCRIPTION.strip('"')), # description
|
963 |
+
gr.update(value=MODEL.strip('"')), # model
|
964 |
+
gr.update(value=THEME.strip('"')), # theme
|
965 |
+
gr.update(value=SYSTEM_PROMPT), # system prompt
|
966 |
+
gr.update(value=""), # examples
|
967 |
+
gr.update(value=0.7), # temperature
|
968 |
+
gr.update(value=750), # max tokens
|
969 |
+
gr.update(value=""), # url1
|
970 |
+
gr.update(value=""), # url2
|
971 |
+
gr.update(value=""), # url3
|
972 |
+
gr.update(value=""), # url4
|
973 |
+
gr.update(value=""), # url5
|
974 |
+
gr.update(value=""), # url6
|
975 |
+
gr.update(value=""), # url7
|
976 |
+
gr.update(value=""), # url8
|
977 |
+
gr.update(value=""), # url9
|
978 |
+
gr.update(value="") # url10
|
979 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
980 |
|
981 |
# Connect authentication
|
982 |
faculty_auth_btn.click(
|
|
|
994 |
# Connect configuration buttons
|
995 |
save_config_btn.click(
|
996 |
save_configuration,
|
997 |
+
inputs=[edit_name, edit_description, edit_model, edit_theme, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens,
|
998 |
+
edit_url1, edit_url2, edit_url3, edit_url4, edit_url5, edit_url6, edit_url7, edit_url8, edit_url9, edit_url10,
|
999 |
+
config_locked, faculty_auth_state],
|
1000 |
+
outputs=[config_status, edit_name, edit_description, edit_model, edit_theme, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens,
|
1001 |
+
edit_url1, edit_url2, edit_url3, edit_url4, edit_url5, edit_url6, edit_url7, edit_url8, edit_url9, edit_url10, config_locked]
|
1002 |
)
|
1003 |
|
1004 |
reset_config_btn.click(
|
1005 |
reset_configuration,
|
1006 |
inputs=[faculty_auth_state],
|
1007 |
+
outputs=[config_status, edit_name, edit_description, edit_model, edit_theme, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens,
|
1008 |
+
edit_url1, edit_url2, edit_url3, edit_url4, edit_url5, edit_url6, edit_url7, edit_url8, edit_url9, edit_url10]
|
1009 |
)
|
1010 |
else:
|
1011 |
+
gr.Markdown("ℹ️ Faculty configuration is not enabled. Set CONFIG_CODE in Space secrets to enable.")
|
1012 |
|
1013 |
if __name__ == "__main__":
|
1014 |
demo.launch()
|
config.json
CHANGED
@@ -1,13 +1,18 @@
|
|
1 |
{
|
2 |
"name": "AI Assistant",
|
3 |
"description": "A customizable AI assistant",
|
4 |
-
"system_prompt": "You are an
|
5 |
-
"model": "
|
6 |
"api_key_var": "API_KEY",
|
7 |
"temperature": 0.7,
|
8 |
-
"max_tokens":
|
9 |
-
"examples":
|
10 |
-
|
|
|
|
|
|
|
|
|
11 |
"enable_dynamic_urls": true,
|
12 |
-
"theme": "
|
|
|
13 |
}
|
|
|
1 |
{
|
2 |
"name": "AI Assistant",
|
3 |
"description": "A customizable AI assistant",
|
4 |
+
"system_prompt": "You are an AI assistant specialized in mathematics and statistics who guides users through problem-solving rather than providing direct answers. You help users discover solutions by asking strategic questions ('What do we know so far?' 'What method might apply here?' 'Can you identify a pattern?'), prompting them to explain their reasoning, and offering hints that build on their current understanding. Format all mathematical expressions in LaTeX (inline: $x^2 + y^2 = r^2$, display: $$\\int_a^b f(x)dx$$). When users are stuck, provide scaffolded support: suggest examining simpler cases, identifying relevant formulas or theorems, or breaking the problem into smaller parts. Use multiple representations to illuminate different aspects of the problem, validate partial progress to build confidence, and help users recognize and correct their own errors through targeted questions rather than corrections. Your goal is to develop problem-solving skills and mathematical reasoning, not just arrive at answers.",
|
5 |
+
"model": "google/gemini-2.0-flash-001",
|
6 |
"api_key_var": "API_KEY",
|
7 |
"temperature": 0.7,
|
8 |
+
"max_tokens": 750,
|
9 |
+
"examples": [
|
10 |
+
"Hello! How can you help me?",
|
11 |
+
"Tell me something interesting",
|
12 |
+
"What can you do?"
|
13 |
+
],
|
14 |
+
"grounding_urls": [],
|
15 |
"enable_dynamic_urls": true,
|
16 |
+
"theme": "Default",
|
17 |
+
"locked": false
|
18 |
}
|