Upload 3 files
Browse files- app.py +250 -107
- config.json +8 -8
app.py
CHANGED
@@ -10,39 +10,104 @@ import urllib.parse
|
|
10 |
|
11 |
|
12 |
# Configuration
|
13 |
-
SPACE_NAME = "
|
14 |
-
SPACE_DESCRIPTION = "A customizable AI assistant
|
15 |
|
16 |
-
# Default configuration values
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = "anthropic/claude-3.5-sonnet"
|
37 |
-
THEME = "Monochrome" # Gradio theme name (Remember to add this to configuration panel)
|
38 |
-
GROUNDING_URLS = ["https://www.maths.tcd.ie/~dwilkins/LaTeXPrimer/MathMode.html", "https://www.maths.tcd.ie/~dwilkins/LaTeXPrimer/TeXEntities.html", "https://www.maths.tcd.ie/~dwilkins/LaTeXPrimer/Matrices.html", "https://www.maths.tcd.ie/~dwilkins/LaTeXPrimer/StdFuncts.html"]
|
39 |
# Get access code from environment variable for security
|
40 |
# If ACCESS_CODE is not set, no access control is applied
|
41 |
ACCESS_CODE = os.environ.get("ACCESS_CODE")
|
42 |
-
ENABLE_DYNAMIC_URLS = True
|
43 |
|
44 |
# Get API key from environment - customizable variable name with validation
|
45 |
-
|
|
|
46 |
if API_KEY:
|
47 |
API_KEY = API_KEY.strip() # Remove any whitespace
|
48 |
if not API_KEY: # Check if empty after stripping
|
@@ -53,21 +118,21 @@ def validate_api_key():
|
|
53 |
"""Validate API key configuration with detailed logging"""
|
54 |
if not API_KEY:
|
55 |
print(f"⚠️ API KEY CONFIGURATION ERROR:")
|
56 |
-
print(f" Variable name:
|
57 |
print(f" Status: Not set or empty")
|
58 |
-
print(f" Action needed: Set '
|
59 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
60 |
return False
|
61 |
elif not API_KEY.startswith('sk-or-'):
|
62 |
print(f"⚠️ API KEY FORMAT WARNING:")
|
63 |
-
print(f" Variable name:
|
64 |
print(f" Current value: {API_KEY[:10]}..." if len(API_KEY) > 10 else API_KEY)
|
65 |
print(f" Expected format: sk-or-xxxxxxxxxx")
|
66 |
print(f" Note: OpenRouter keys should start with 'sk-or-'")
|
67 |
return True # Still try to use it
|
68 |
else:
|
69 |
print(f"✅ API Key configured successfully")
|
70 |
-
print(f" Variable:
|
71 |
print(f" Format: Valid OpenRouter key")
|
72 |
return True
|
73 |
|
@@ -233,10 +298,10 @@ def generate_response(message, history):
|
|
233 |
error_msg += f"Please configure your OpenRouter API key:\n"
|
234 |
error_msg += f"1. Go to Settings (⚙️) in your HuggingFace Space\n"
|
235 |
error_msg += f"2. Click 'Variables and secrets'\n"
|
236 |
-
error_msg += f"3. Add secret: **
|
237 |
error_msg += f"4. Value: Your OpenRouter API key (starts with `sk-or-`)\n\n"
|
238 |
error_msg += f"Get your API key at: https://openrouter.ai/keys"
|
239 |
-
print(f"❌ API request failed: No API key configured for
|
240 |
return error_msg
|
241 |
|
242 |
# Get grounding context
|
@@ -294,8 +359,8 @@ def generate_response(message, history):
|
|
294 |
json={
|
295 |
"model": MODEL,
|
296 |
"messages": messages,
|
297 |
-
"temperature": 0.
|
298 |
-
"max_tokens":
|
299 |
},
|
300 |
timeout=30
|
301 |
)
|
@@ -334,7 +399,7 @@ def generate_response(message, history):
|
|
334 |
error_msg = f"🔐 **Authentication Error**\n\n"
|
335 |
error_msg += f"Your API key appears to be invalid or expired.\n\n"
|
336 |
error_msg += f"**Troubleshooting:**\n"
|
337 |
-
error_msg += f"1. Check that your **
|
338 |
error_msg += f"2. Verify your API key at: https://openrouter.ai/keys\n"
|
339 |
error_msg += f"3. Ensure your key starts with `sk-or-`\n"
|
340 |
error_msg += f"4. Check that you have credits on your OpenRouter account"
|
@@ -480,46 +545,47 @@ def export_conversation(history):
|
|
480 |
|
481 |
# Configuration status display
|
482 |
def get_configuration_status():
|
483 |
-
"""Generate a configuration status message for display"""
|
484 |
status_parts = []
|
485 |
|
486 |
-
|
487 |
-
status_parts.append("
|
488 |
-
|
489 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
490 |
else:
|
491 |
-
status_parts.append("
|
492 |
-
status_parts.append(" Set `API_KEY` in Space secrets")
|
493 |
-
|
494 |
-
# Model and parameters
|
495 |
-
status_parts.append("") # Blank line
|
496 |
-
status_parts.append("### 🤖 Model Settings")
|
497 |
-
status_parts.append(f"**Model:** {MODEL.split('/')[-1]}")
|
498 |
-
status_parts.append(f"**Temperature:** 0.6")
|
499 |
-
status_parts.append(f"**Max Tokens:** 1000")
|
500 |
|
501 |
# URL Context if configured
|
502 |
-
if GROUNDING_URLS:
|
503 |
-
status_parts.append("")
|
504 |
-
status_parts.append("
|
505 |
-
status_parts.append(f"**URLs Configured:** {len(GROUNDING_URLS)}")
|
506 |
-
for i, url in enumerate(GROUNDING_URLS[:2], 1):
|
507 |
-
status_parts.append(f" {i}. {url[:50]}{'...' if len(url) > 50 else ''}")
|
508 |
-
if len(GROUNDING_URLS) > 2:
|
509 |
-
status_parts.append(f" ... and {len(GROUNDING_URLS) - 2} more")
|
510 |
-
|
511 |
-
# Access control
|
512 |
-
if ACCESS_CODE is not None:
|
513 |
-
status_parts.append("") # Blank line
|
514 |
-
status_parts.append("### 🔐 Access Control")
|
515 |
-
status_parts.append("**Status:** Password protected")
|
516 |
|
517 |
-
#
|
518 |
-
status_parts.append("")
|
519 |
-
|
520 |
-
|
521 |
-
prompt_preview = SYSTEM_PROMPT[:200] + "..." if len(SYSTEM_PROMPT) > 200 else SYSTEM_PROMPT
|
522 |
-
status_parts.append(f"```\n{prompt_preview}\n```")
|
523 |
|
524 |
return "\n".join(status_parts)
|
525 |
|
@@ -549,7 +615,7 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
549 |
fn=store_and_generate_response, # Use wrapper function to store history
|
550 |
title="", # Title already shown above
|
551 |
description="", # Description already shown above
|
552 |
-
examples=['
|
553 |
type="messages" # Use modern message format for better compatibility
|
554 |
)
|
555 |
|
@@ -565,7 +631,7 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
565 |
)
|
566 |
|
567 |
# Configuration status
|
568 |
-
with gr.Accordion("
|
569 |
gr.Markdown(get_configuration_status())
|
570 |
|
571 |
# Connect access verification
|
@@ -582,8 +648,8 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
582 |
)
|
583 |
|
584 |
# Faculty Configuration Section - appears at the bottom with password protection
|
585 |
-
with gr.Accordion("
|
586 |
-
gr.Markdown("
|
587 |
|
588 |
# Check if faculty password is configured
|
589 |
FACULTY_PASSWORD = os.environ.get("CONFIG_CODE", "").strip()
|
@@ -615,8 +681,8 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
615 |
except:
|
616 |
current_config = {
|
617 |
'system_prompt': SYSTEM_PROMPT,
|
618 |
-
'temperature': 0.
|
619 |
-
'max_tokens':
|
620 |
'locked': False
|
621 |
}
|
622 |
|
@@ -627,30 +693,65 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
627 |
lines=5
|
628 |
)
|
629 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
630 |
with gr.Row():
|
631 |
edit_temperature = gr.Slider(
|
632 |
label="Temperature",
|
633 |
minimum=0,
|
634 |
maximum=2,
|
635 |
-
value=current_config.get('temperature', 0.
|
636 |
step=0.1
|
637 |
)
|
638 |
edit_max_tokens = gr.Slider(
|
639 |
label="Max Tokens",
|
640 |
minimum=50,
|
641 |
maximum=4096,
|
642 |
-
value=current_config.get('max_tokens',
|
643 |
step=50
|
644 |
)
|
645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
646 |
config_locked = gr.Checkbox(
|
647 |
label="Lock Configuration (Prevent further edits)",
|
648 |
value=current_config.get('locked', False)
|
649 |
)
|
650 |
|
651 |
with gr.Row():
|
652 |
-
save_config_btn = gr.Button("
|
653 |
-
reset_config_btn = gr.Button("
|
654 |
|
655 |
config_status = gr.Markdown("")
|
656 |
|
@@ -658,78 +759,120 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
658 |
def verify_faculty_password(password):
|
659 |
if password == FACULTY_PASSWORD:
|
660 |
return (
|
661 |
-
gr.update(value="
|
662 |
gr.update(visible=False), # Hide auth row
|
663 |
gr.update(visible=True), # Show config section
|
664 |
True # Update auth state
|
665 |
)
|
666 |
else:
|
667 |
return (
|
668 |
-
gr.update(value="
|
669 |
gr.update(visible=True), # Keep auth row visible
|
670 |
gr.update(visible=False), # Keep config hidden
|
671 |
False # Auth failed
|
672 |
)
|
673 |
|
674 |
# Save configuration function
|
675 |
-
def save_configuration(new_prompt, new_temp, new_tokens, lock_config, is_authenticated):
|
676 |
if not is_authenticated:
|
677 |
-
return "
|
678 |
|
679 |
# Check if configuration is already locked
|
680 |
try:
|
681 |
with open('config.json', 'r') as f:
|
682 |
existing_config = json.load(f)
|
683 |
if existing_config.get('locked', False):
|
684 |
-
return "
|
685 |
except:
|
686 |
pass
|
687 |
|
688 |
-
#
|
689 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
690 |
'system_prompt': new_prompt,
|
|
|
691 |
'temperature': new_temp,
|
692 |
'max_tokens': int(new_tokens),
|
|
|
693 |
'locked': lock_config,
|
694 |
'last_modified': datetime.now().isoformat(),
|
695 |
'modified_by': 'faculty'
|
696 |
-
}
|
697 |
|
698 |
try:
|
699 |
with open('config.json', 'w') as f:
|
700 |
-
json.dump(
|
701 |
|
702 |
-
#
|
703 |
-
|
704 |
-
SYSTEM_PROMPT = new_prompt
|
705 |
-
temperature = new_temp
|
706 |
-
max_tokens = int(new_tokens)
|
707 |
|
708 |
-
return f"
|
709 |
except Exception as e:
|
710 |
-
return f"
|
711 |
|
712 |
# Reset configuration function
|
713 |
def reset_configuration(is_authenticated):
|
714 |
if not is_authenticated:
|
715 |
-
|
|
|
716 |
|
717 |
# Check if locked
|
718 |
try:
|
719 |
with open('config.json', 'r') as f:
|
720 |
existing_config = json.load(f)
|
721 |
if existing_config.get('locked', False):
|
722 |
-
|
|
|
723 |
except:
|
724 |
pass
|
725 |
|
726 |
-
#
|
727 |
-
|
728 |
-
|
729 |
-
|
730 |
-
|
731 |
-
|
732 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
733 |
|
734 |
# Connect authentication
|
735 |
faculty_auth_btn.click(
|
@@ -747,17 +890,17 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
|
|
747 |
# Connect configuration buttons
|
748 |
save_config_btn.click(
|
749 |
save_configuration,
|
750 |
-
inputs=[edit_system_prompt, edit_temperature, edit_max_tokens
|
751 |
outputs=[config_status]
|
752 |
)
|
753 |
|
754 |
reset_config_btn.click(
|
755 |
reset_configuration,
|
756 |
inputs=[faculty_auth_state],
|
757 |
-
outputs=[config_status, edit_system_prompt, edit_temperature, edit_max_tokens]
|
758 |
)
|
759 |
else:
|
760 |
-
gr.Markdown("
|
761 |
|
762 |
if __name__ == "__main__":
|
763 |
demo.launch()
|
|
|
10 |
|
11 |
|
12 |
# Configuration
|
13 |
+
SPACE_NAME = "AI Assistant"
|
14 |
+
SPACE_DESCRIPTION = "A customizable AI assistant"
|
15 |
|
16 |
+
# Default configuration values (used only if config.json is missing)
|
17 |
+
DEFAULT_CONFIG = {
|
18 |
+
'name': "AI Assistant",
|
19 |
+
'description': "A customizable AI assistant",
|
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': "anthropic/claude-3.5-sonnet",
|
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 |
+
API_KEY_VAR = config.get('api_key_var', DEFAULT_CONFIG['api_key_var'])
|
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 |
"""Validate API key configuration with detailed logging"""
|
119 |
if not API_KEY:
|
120 |
print(f"⚠️ API KEY CONFIGURATION ERROR:")
|
121 |
+
print(f" Variable name: {API_KEY_VAR}")
|
122 |
print(f" Status: Not set or empty")
|
123 |
+
print(f" Action needed: Set '{API_KEY_VAR}' in HuggingFace Space secrets")
|
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: {API_KEY_VAR}")
|
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: {API_KEY_VAR}")
|
136 |
print(f" Format: Valid OpenRouter key")
|
137 |
return True
|
138 |
|
|
|
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: **{API_KEY_VAR}**\n"
|
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 {API_KEY_VAR}")
|
305 |
return error_msg
|
306 |
|
307 |
# Get grounding context
|
|
|
359 |
json={
|
360 |
"model": MODEL,
|
361 |
"messages": messages,
|
362 |
+
"temperature": 0.7,
|
363 |
+
"max_tokens": 1500
|
364 |
},
|
365 |
timeout=30
|
366 |
)
|
|
|
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 **{API_KEY_VAR}** secret is set correctly\n"
|
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"
|
|
|
545 |
|
546 |
# Configuration status display
|
547 |
def get_configuration_status():
|
548 |
+
"""Generate a clean configuration status message for display"""
|
549 |
status_parts = []
|
550 |
|
551 |
+
status_parts.append("**Configuration:**")
|
552 |
+
status_parts.append("")
|
553 |
+
status_parts.append(f"**Name:** {SPACE_NAME}")
|
554 |
+
status_parts.append(f"**Model:** {MODEL}")
|
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("**Example Prompts:** No example prompts configured")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
579 |
|
580 |
# URL Context if configured
|
581 |
+
if GROUNDING_URLS and len(GROUNDING_URLS) > 0:
|
582 |
+
status_parts.append("")
|
583 |
+
status_parts.append(f"**Grounding URLs:** {len(GROUNDING_URLS)} configured")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
584 |
|
585 |
+
# API Key status (minimal, at the end)
|
586 |
+
status_parts.append("")
|
587 |
+
if not API_KEY_VALID:
|
588 |
+
status_parts.append(f"**Note:** API key ({API_KEY_VAR}) not configured in Space secrets")
|
|
|
|
|
589 |
|
590 |
return "\n".join(status_parts)
|
591 |
|
|
|
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=['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?'],
|
619 |
type="messages" # Use modern message format for better compatibility
|
620 |
)
|
621 |
|
|
|
631 |
)
|
632 |
|
633 |
# Configuration status
|
634 |
+
with gr.Accordion("Configuration", open=False):
|
635 |
gr.Markdown(get_configuration_status())
|
636 |
|
637 |
# Connect access verification
|
|
|
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()
|
|
|
681 |
except:
|
682 |
current_config = {
|
683 |
'system_prompt': SYSTEM_PROMPT,
|
684 |
+
'temperature': 0.7,
|
685 |
+
'max_tokens': 1500,
|
686 |
'locked': False
|
687 |
}
|
688 |
|
|
|
693 |
lines=5
|
694 |
)
|
695 |
|
696 |
+
# Example prompts field
|
697 |
+
examples_value = current_config.get('examples', [])
|
698 |
+
if isinstance(examples_value, list):
|
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)",
|
705 |
+
value=examples_text_value,
|
706 |
+
lines=3,
|
707 |
+
placeholder="What can you help me with?\nExplain this concept\nHelp me understand..."
|
708 |
+
)
|
709 |
+
|
710 |
with gr.Row():
|
711 |
edit_temperature = gr.Slider(
|
712 |
label="Temperature",
|
713 |
minimum=0,
|
714 |
maximum=2,
|
715 |
+
value=current_config.get('temperature', 0.7),
|
716 |
step=0.1
|
717 |
)
|
718 |
edit_max_tokens = gr.Slider(
|
719 |
label="Max Tokens",
|
720 |
minimum=50,
|
721 |
maximum=4096,
|
722 |
+
value=current_config.get('max_tokens', 1500),
|
723 |
step=50
|
724 |
)
|
725 |
|
726 |
+
# URL Grounding fields
|
727 |
+
gr.Markdown("### URL Grounding")
|
728 |
+
grounding_urls_value = current_config.get('grounding_urls', [])
|
729 |
+
if isinstance(grounding_urls_value, str):
|
730 |
+
try:
|
731 |
+
import ast
|
732 |
+
grounding_urls_value = ast.literal_eval(grounding_urls_value)
|
733 |
+
except:
|
734 |
+
grounding_urls_value = []
|
735 |
+
|
736 |
+
# Create 10 URL input fields
|
737 |
+
url_fields = []
|
738 |
+
for i in range(10):
|
739 |
+
url_value = grounding_urls_value[i] if i < len(grounding_urls_value) else ""
|
740 |
+
url_field = gr.Textbox(
|
741 |
+
label=f"URL {i+1}" + (" (Primary)" if i < 2 else " (Secondary)"),
|
742 |
+
value=url_value,
|
743 |
+
placeholder="https://..."
|
744 |
+
)
|
745 |
+
url_fields.append(url_field)
|
746 |
+
|
747 |
config_locked = gr.Checkbox(
|
748 |
label="Lock Configuration (Prevent further edits)",
|
749 |
value=current_config.get('locked', False)
|
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 |
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, *url_values, lock_config, is_authenticated):
|
777 |
if not is_authenticated:
|
778 |
+
return "Not authenticated"
|
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 |
+
return "Configuration is locked and cannot be modified"
|
786 |
except:
|
787 |
pass
|
788 |
|
789 |
+
# Load current config to preserve all values
|
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 URL values - extract the 10 URL values and lock_config from varargs
|
801 |
+
urls = list(url_values[:10]) # First 10 are 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 |
+
# Update only the editable fields while preserving everything else
|
807 |
+
current_full_config.update({
|
808 |
'system_prompt': new_prompt,
|
809 |
+
'examples': examples_list,
|
810 |
'temperature': new_temp,
|
811 |
'max_tokens': int(new_tokens),
|
812 |
+
'grounding_urls': grounding_urls,
|
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(current_full_config, f, indent=2)
|
821 |
|
822 |
+
# Reload all configuration values from the updated config.json
|
823 |
+
reload_config_values()
|
|
|
|
|
|
|
824 |
|
825 |
+
return f"Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
826 |
except Exception as e:
|
827 |
+
return f"Error saving configuration: {str(e)}"
|
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(14)] # 1 status + 14 fields
|
833 |
+
return tuple(updates)
|
834 |
|
835 |
# Check if locked
|
836 |
try:
|
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(14)]
|
841 |
+
return tuple(updates)
|
842 |
except:
|
843 |
pass
|
844 |
|
845 |
+
# Get default examples as text
|
846 |
+
default_examples = DEFAULT_CONFIG.get('examples', [])
|
847 |
+
if isinstance(default_examples, list):
|
848 |
+
examples_text = "\n".join(default_examples)
|
849 |
+
else:
|
850 |
+
examples_text = ""
|
851 |
+
|
852 |
+
# Get default URLs
|
853 |
+
default_urls = DEFAULT_CONFIG.get('grounding_urls', [])
|
854 |
+
if isinstance(default_urls, str):
|
855 |
+
try:
|
856 |
+
import ast
|
857 |
+
default_urls = ast.literal_eval(default_urls)
|
858 |
+
except:
|
859 |
+
default_urls = []
|
860 |
+
|
861 |
+
# Reset to original default values
|
862 |
+
updates = [
|
863 |
+
"Reset to default values",
|
864 |
+
gr.update(value=DEFAULT_CONFIG.get('system_prompt', '')),
|
865 |
+
gr.update(value=examples_text),
|
866 |
+
gr.update(value=DEFAULT_CONFIG.get('temperature', 0.7)),
|
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 |
# Connect configuration buttons
|
891 |
save_config_btn.click(
|
892 |
save_configuration,
|
893 |
+
inputs=[edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens] + url_fields + [config_locked, faculty_auth_state],
|
894 |
outputs=[config_status]
|
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] + url_fields
|
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()
|
config.json
CHANGED
@@ -1,13 +1,13 @@
|
|
1 |
{
|
2 |
-
"name": "
|
3 |
-
"description": "
|
4 |
-
"system_prompt": "You are an
|
5 |
"model": "anthropic/claude-3.5-sonnet",
|
6 |
"api_key_var": "API_KEY",
|
7 |
-
"temperature": 0.
|
8 |
-
"max_tokens":
|
9 |
-
"examples": "['
|
10 |
-
"grounding_urls": "[\"https://
|
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 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:\n\n1. Reading Process\n1a. Initial Analysis: ensure literal comprehension; identify literary devices and effects; examine diction, syntax, imagery\n1b. Pattern Recognition: trace recurring motifs, tensions, contradictions; analyze formal elements by genre; connect style to meaning\n1c. Interpretive Synthesis: apply theoretical lenses when appropriate; build arguments through textual evidence; acknowledge interpretive complexity\n\n2. Genre-Specific Techniques:\n2a. Poetry: prosody, enjambment, sound patterns, visual arrangement\n2b. Fiction: narration, characterization, setting, temporal structure\n2c. Drama: dialogue patterns, stage directions, performance considerations\n\n3. Critical Approaches:\n3a. **Formal Analysis**: unity, ambiguity, paradox, irony\n3b. **Contextual Reading**: historical, biographical, cultural frameworks\n3c. **Theoretical Applications**: psychoanalytic, Marxist, feminist, postcolonial",
|
5 |
"model": "anthropic/claude-3.5-sonnet",
|
6 |
"api_key_var": "API_KEY",
|
7 |
+
"temperature": 0.7,
|
8 |
+
"max_tokens": 1500,
|
9 |
+
"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?']",
|
10 |
+
"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\"]",
|
11 |
"enable_dynamic_urls": true,
|
12 |
+
"theme": "Origin"
|
13 |
}
|