milwright commited on
Commit
9114df5
·
verified ·
1 Parent(s): 6af5951

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +250 -107
  2. config.json +8 -8
app.py CHANGED
@@ -10,39 +10,104 @@ import urllib.parse
10
 
11
 
12
  # Configuration
13
- SPACE_NAME = "Math-o-Matic"
14
- SPACE_DESCRIPTION = "A customizable AI assistant for advanced mathematics help and numeracy guidance"
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.6
19
- DEFAULT_MAX_TOKENS = 1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- API_KEY = os.environ.get("API_KEY")
 
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: API_KEY")
57
  print(f" Status: Not set or empty")
58
- print(f" Action needed: Set 'API_KEY' in HuggingFace Space secrets")
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: API_KEY")
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: API_KEY")
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: **API_KEY**\n"
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 API_KEY")
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.6,
298
- "max_tokens": 1000
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 **API_KEY** secret is set correctly\n"
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
- # API Key status
487
- status_parts.append("### 🔑 API Configuration")
488
- if API_KEY_VALID:
489
- status_parts.append("**API Key:** Ready")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  else:
491
- status_parts.append("**API Key:** Not configured")
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("") # Blank line
504
- status_parts.append("### 🔗 Context Sources")
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
- # System prompt
518
- status_parts.append("") # Blank line
519
- status_parts.append("### 📝 System Prompt")
520
- # Show first 200 chars of system prompt
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=['Can you help me with the quadratic formula?', 'What is Bayesian statistics?', 'Help me work with a math problem'],
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("📊 Configuration Status", open=True):
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("🔧 Faculty Configuration", open=False, visible=True) as faculty_section:
586
- gr.Markdown("**Faculty Only:** Edit assistant configuration. Requires CONFIG_CODE secret.")
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.6,
619
- 'max_tokens': 1000,
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.6),
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', 1000),
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("💾 Save Configuration", variant="primary")
653
- reset_config_btn = gr.Button("↩️ Reset to Defaults", variant="secondary")
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="Authentication successful!"),
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="Invalid password"),
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 "Not authenticated"
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 "🔒 Configuration is locked and cannot be modified"
685
  except:
686
  pass
687
 
688
- # Save new configuration
689
- new_config = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(new_config, f, indent=2)
701
 
702
- # Update global variables
703
- global SYSTEM_PROMPT, temperature, max_tokens
704
- SYSTEM_PROMPT = new_prompt
705
- temperature = new_temp
706
- max_tokens = int(new_tokens)
707
 
708
- return f"Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
709
  except Exception as e:
710
- return f"Error saving configuration: {str(e)}"
711
 
712
  # Reset configuration function
713
  def reset_configuration(is_authenticated):
714
  if not is_authenticated:
715
- return "Not authenticated", gr.update(), gr.update(), gr.update()
 
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
- return "🔒 Configuration is locked", gr.update(), gr.update(), gr.update()
 
723
  except:
724
  pass
725
 
726
- # Reset to original values
727
- return (
728
- "↩️ Reset to default values",
729
- gr.update(value=SYSTEM_PROMPT),
730
- gr.update(value=0.6),
731
- gr.update(value=1000)
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, config_locked, faculty_auth_state],
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("ℹ️ Faculty configuration is not enabled. Set CONFIG_CODE in Space secrets to enable.")
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": "Math-o-Matic",
3
- "description": "An AI assistant for help and guidance with college-level statistical and mathematics.",
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": "anthropic/claude-3.5-sonnet",
6
  "api_key_var": "API_KEY",
7
- "temperature": 0.5,
8
- "max_tokens": 1000,
9
- "examples": "['Can you help me with the quadratic formula?', 'What is Bayesian statistics?', 'Help me work with a math problem']",
10
- "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\"]",
11
  "enable_dynamic_urls": true,
12
- "theme": "Glass"
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
  }