milwright commited on
Commit
9791c26
·
verified ·
1 Parent(s): 6bf95cb

Upload 3 files

Browse files
Files changed (2) hide show
  1. app.py +109 -120
  2. config.json +6 -6
app.py CHANGED
@@ -10,8 +10,8 @@ import urllib.parse
10
 
11
 
12
  # Configuration
13
- SPACE_NAME = "CUNY Virgil"
14
- SPACE_DESCRIPTION = "Adaptable AI assistant for research methods support"
15
 
16
  # Default configuration values
17
  DEFAULT_SYSTEM_PROMPT = """You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation."""
@@ -33,9 +33,9 @@ except:
33
  max_tokens = DEFAULT_MAX_TOKENS
34
  print("ℹ️ Using default configuration")
35
 
36
- MODEL = "nvidia/llama-3.1-nemotron-70b-instruct"
37
- THEME = "Glass" # Gradio theme name
38
- GROUNDING_URLS = []
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")
@@ -549,7 +549,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=['Help me narrow a research question'],
553
  type="messages" # Use modern message format for better compatibility
554
  )
555
 
@@ -611,37 +611,40 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
611
  current_config = json.load(f)
612
  except:
613
  current_config = {
614
- 'name': SPACE_NAME.strip('"'),
615
- 'description': SPACE_DESCRIPTION.strip('"'),
616
- 'model': MODEL.strip('"'),
617
- 'theme': THEME.strip('"'),
618
  'system_prompt': SYSTEM_PROMPT,
619
- 'examples': [],
620
- 'grounding_urls': GROUNDING_URLS,
621
  'temperature': 0.7,
622
  'max_tokens': 750,
623
  'locked': False
624
  }
625
 
626
- # Editable fields
 
627
  edit_name = gr.Textbox(
628
  label="Assistant Name",
629
- value=current_config.get('name', SPACE_NAME.strip('"')),
630
- info="The display name for your assistant"
631
  )
632
 
633
  edit_description = gr.Textbox(
634
- label="Assistant Description",
635
- value=current_config.get('description', SPACE_DESCRIPTION.strip('"')),
636
  lines=2,
637
- info="Brief description of what your assistant does"
638
  )
639
 
 
 
 
 
 
 
 
 
640
  edit_model = gr.Dropdown(
641
- label="AI Model",
642
  choices=[
643
  "google/gemini-2.0-flash-001",
644
- "google/gemma-3-27b-it",
645
  "anthropic/claude-3.5-sonnet",
646
  "anthropic/claude-3.5-haiku",
647
  "openai/gpt-4o-mini-search-preview",
@@ -649,26 +652,15 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
649
  "nvidia/llama-3.1-nemotron-70b-instruct",
650
  "mistralai/devstral-small"
651
  ],
652
- value=current_config.get('model', MODEL.strip('"')),
653
- info="The AI model to use for responses"
654
- )
655
-
656
- edit_theme = gr.Dropdown(
657
- label="Interface Theme",
658
- choices=["Default", "Origin", "Citrus", "Monochrome", "Soft", "Glass", "Ocean"],
659
- value=current_config.get('theme', THEME.strip('"')),
660
- info="Visual theme for the chat interface"
661
  )
662
 
663
- edit_system_prompt = gr.Textbox(
664
- label="System Prompt",
665
- value=current_config.get('system_prompt', SYSTEM_PROMPT),
666
- lines=5
667
- )
668
-
669
- # Get current examples as text
670
- current_examples = current_config.get('examples', [])
671
- examples_text_value = "\n".join(current_examples) if isinstance(current_examples, list) else ""
672
 
673
  edit_examples = gr.Textbox(
674
  label="Example Prompts (one per line)",
@@ -677,6 +669,7 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
677
  placeholder="What can you help me with?\nExplain this concept\nHelp me understand..."
678
  )
679
 
 
680
  with gr.Row():
681
  edit_temperature = gr.Slider(
682
  label="Temperature",
@@ -693,39 +686,26 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
693
  step=50
694
  )
695
 
696
- # URL Grounding fields
697
- gr.Markdown("### 🔗 URL Context")
698
- gr.Markdown("**Primary URLs** (processed first, highest priority)")
699
-
700
- # Get current URLs from config
701
- current_urls = current_config.get('grounding_urls', GROUNDING_URLS or [])
702
- if isinstance(current_urls, str):
703
  try:
704
  import ast
705
- current_urls = ast.literal_eval(current_urls)
706
  except:
707
- current_urls = []
708
-
709
- edit_url1 = gr.Textbox(
710
- label="Primary URL 1",
711
- value=current_urls[0] if len(current_urls) > 0 else "",
712
- placeholder="https://..."
713
- )
714
- edit_url2 = gr.Textbox(
715
- label="Primary URL 2",
716
- value=current_urls[1] if len(current_urls) > 1 else "",
717
- placeholder="https://..."
718
- )
719
 
720
- gr.Markdown("**Secondary URLs** (additional context)")
721
- edit_url3 = gr.Textbox(label="Secondary URL 1", value=current_urls[2] if len(current_urls) > 2 else "", placeholder="https://...")
722
- edit_url4 = gr.Textbox(label="Secondary URL 2", value=current_urls[3] if len(current_urls) > 3 else "", placeholder="https://...")
723
- edit_url5 = gr.Textbox(label="Secondary URL 3", value=current_urls[4] if len(current_urls) > 4 else "", placeholder="https://...")
724
- edit_url6 = gr.Textbox(label="Secondary URL 4", value=current_urls[5] if len(current_urls) > 5 else "", placeholder="https://...")
725
- edit_url7 = gr.Textbox(label="Secondary URL 5", value=current_urls[6] if len(current_urls) > 6 else "", placeholder="https://...")
726
- edit_url8 = gr.Textbox(label="Secondary URL 6", value=current_urls[7] if len(current_urls) > 7 else "", placeholder="https://...")
727
- edit_url9 = gr.Textbox(label="Secondary URL 7", value=current_urls[8] if len(current_urls) > 8 else "", placeholder="https://...")
728
- edit_url10 = gr.Textbox(label="Secondary URL 8", value=current_urls[9] if len(current_urls) > 9 else "", placeholder="https://...")
 
729
 
730
  config_locked = gr.Checkbox(
731
  label="Lock Configuration (Prevent further edits)",
@@ -756,7 +736,7 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
756
  )
757
 
758
  # Save configuration function
759
- 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):
760
  if not is_authenticated:
761
  return "❌ Not authenticated"
762
 
@@ -769,53 +749,53 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
769
  except:
770
  pass
771
 
772
- # Process examples
 
 
 
 
 
 
 
 
773
  examples_list = [ex.strip() for ex in new_examples.split('\n') if ex.strip()]
774
 
775
- # Process URLs
776
- urls = [url1, url2, url3, url4, url5, url6, url7, url8, url9, url10]
 
 
777
  grounding_urls = [url.strip() for url in urls if url.strip()]
778
 
779
- # Save new configuration
780
- new_config = {
781
  'name': new_name,
782
  'description': new_description,
783
- 'model': new_model,
784
- 'theme': new_theme,
785
  'system_prompt': new_prompt,
 
786
  'examples': examples_list,
787
- 'grounding_urls': grounding_urls,
788
  'temperature': new_temp,
789
  'max_tokens': int(new_tokens),
790
- 'enable_dynamic_urls': True,
791
- 'locked': lock_config,
792
  'last_modified': datetime.now().isoformat(),
793
- 'modified_by': 'faculty'
794
  }
795
 
796
  try:
797
  with open('config.json', 'w') as f:
798
- json.dump(new_config, f, indent=2)
799
 
800
- # Update global variables
801
- global SPACE_NAME, SPACE_DESCRIPTION, MODEL, THEME, SYSTEM_PROMPT, temperature, max_tokens, GROUNDING_URLS
802
- SPACE_NAME = new_name
803
- SPACE_DESCRIPTION = new_description
804
- MODEL = new_model
805
- THEME = new_theme
806
- SYSTEM_PROMPT = new_prompt
807
- temperature = new_temp
808
- max_tokens = int(new_tokens)
809
- GROUNDING_URLS = grounding_urls
810
 
811
- return f"✅ Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n⚠️ **Note:** Example prompts will update on next page refresh."
812
  except Exception as e:
813
  return f"❌ Error saving configuration: {str(e)}"
814
 
815
  # Reset configuration function
816
  def reset_configuration(is_authenticated):
817
  if not is_authenticated:
818
- updates = ["❌ Not authenticated"] + [gr.update() for _ in range(17)] # 1 status + 16 fields
819
  return tuple(updates)
820
 
821
  # Check if locked
@@ -823,33 +803,45 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
823
  with open('config.json', 'r') as f:
824
  existing_config = json.load(f)
825
  if existing_config.get('locked', False):
826
- updates = ["🔒 Configuration is locked"] + [gr.update() for _ in range(17)]
827
  return tuple(updates)
828
  except:
829
  pass
830
 
831
- # Reset to original values
832
- return (
833
- "↩️ Reset to default values",
834
- gr.update(value=SPACE_NAME.strip('"')), # name
835
- gr.update(value=SPACE_DESCRIPTION.strip('"')), # description
836
- gr.update(value=MODEL.strip('"')), # model
837
- gr.update(value=THEME.strip('"')), # theme
838
- gr.update(value=SYSTEM_PROMPT), # system prompt
839
- gr.update(value=""), # examples
840
- gr.update(value=0.7), # temperature
841
- gr.update(value=750), # max tokens
842
- gr.update(value=""), # url1
843
- gr.update(value=""), # url2
844
- gr.update(value=""), # url3
845
- gr.update(value=""), # url4
846
- gr.update(value=""), # url5
847
- gr.update(value=""), # url6
848
- gr.update(value=""), # url7
849
- gr.update(value=""), # url8
850
- gr.update(value=""), # url9
851
- gr.update(value="") # url10
852
- )
 
 
 
 
 
 
 
 
 
 
 
 
853
 
854
  # Connect authentication
855
  faculty_auth_btn.click(
@@ -867,17 +859,14 @@ with gr.Blocks(title=SPACE_NAME, theme=theme_class()) as demo:
867
  # Connect configuration buttons
868
  save_config_btn.click(
869
  save_configuration,
870
- inputs=[edit_name, edit_description, edit_model, edit_theme, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens,
871
- edit_url1, edit_url2, edit_url3, edit_url4, edit_url5, edit_url6, edit_url7, edit_url8, edit_url9, edit_url10,
872
- config_locked, faculty_auth_state],
873
  outputs=[config_status]
874
  )
875
 
876
  reset_config_btn.click(
877
  reset_configuration,
878
  inputs=[faculty_auth_state],
879
- outputs=[config_status, edit_name, edit_description, edit_model, edit_theme, edit_system_prompt, edit_examples, edit_temperature, edit_max_tokens,
880
- edit_url1, edit_url2, edit_url3, edit_url4, edit_url5, edit_url6, edit_url7, edit_url8, edit_url9, edit_url10]
881
  )
882
  else:
883
  gr.Markdown("ℹ️ Faculty configuration is not enabled. Set CONFIG_CODE in Space secrets to enable.")
 
10
 
11
 
12
  # Configuration
13
+ SPACE_NAME = "Virgil Research Guide"
14
+ SPACE_DESCRIPTION = "A customizable AI assistant"
15
 
16
  # Default configuration values
17
  DEFAULT_SYSTEM_PROMPT = """You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation."""
 
33
  max_tokens = DEFAULT_MAX_TOKENS
34
  print("ℹ️ Using default configuration")
35
 
36
+ MODEL = "google/gemini-2.0-flash-001"
37
+ THEME = "Default" # Gradio theme name
38
+ GROUNDING_URLS = ["https://bcc-cuny.libguides.com/", "https://researchguides.journalism.cuny.edu/"]
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")
 
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=['Help me prepare for ethnographic fieldwork?', 'What are some methods of digital ethnography?'],
553
  type="messages" # Use modern message format for better compatibility
554
  )
555
 
 
611
  current_config = json.load(f)
612
  except:
613
  current_config = {
 
 
 
 
614
  'system_prompt': SYSTEM_PROMPT,
 
 
615
  'temperature': 0.7,
616
  'max_tokens': 750,
617
  'locked': False
618
  }
619
 
620
+ # Editable fields - Order matches the Configuration tab
621
+ # 1. Assistant Identity
622
  edit_name = gr.Textbox(
623
  label="Assistant Name",
624
+ value=current_config.get('name', SPACE_NAME),
625
+ placeholder="My AI Assistant"
626
  )
627
 
628
  edit_description = gr.Textbox(
629
+ label="Assistant Description",
630
+ value=current_config.get('description', SPACE_DESCRIPTION),
631
  lines=2,
632
+ placeholder="A helpful AI assistant for..."
633
  )
634
 
635
+ # 2. System Prompt
636
+ edit_system_prompt = gr.Textbox(
637
+ label="System Prompt",
638
+ value=current_config.get('system_prompt', SYSTEM_PROMPT),
639
+ lines=5
640
+ )
641
+
642
+ # 3. Model Selection
643
  edit_model = gr.Dropdown(
644
+ label="Model",
645
  choices=[
646
  "google/gemini-2.0-flash-001",
647
+ "google/gemma-3-27b-it",
648
  "anthropic/claude-3.5-sonnet",
649
  "anthropic/claude-3.5-haiku",
650
  "openai/gpt-4o-mini-search-preview",
 
652
  "nvidia/llama-3.1-nemotron-70b-instruct",
653
  "mistralai/devstral-small"
654
  ],
655
+ value=current_config.get('model', MODEL)
 
 
 
 
 
 
 
 
656
  )
657
 
658
+ # 4. Example Prompts
659
+ examples_value = current_config.get('examples', [])
660
+ if isinstance(examples_value, list):
661
+ examples_text_value = "\n".join(examples_value)
662
+ else:
663
+ examples_text_value = ""
 
 
 
664
 
665
  edit_examples = gr.Textbox(
666
  label="Example Prompts (one per line)",
 
669
  placeholder="What can you help me with?\nExplain this concept\nHelp me understand..."
670
  )
671
 
672
+ # 5. Model Parameters
673
  with gr.Row():
674
  edit_temperature = gr.Slider(
675
  label="Temperature",
 
686
  step=50
687
  )
688
 
689
+ # 6. URL Grounding
690
+ gr.Markdown("### URL Grounding")
691
+ grounding_urls_value = current_config.get('grounding_urls', [])
692
+ if isinstance(grounding_urls_value, str):
 
 
 
693
  try:
694
  import ast
695
+ grounding_urls_value = ast.literal_eval(grounding_urls_value)
696
  except:
697
+ grounding_urls_value = []
 
 
 
 
 
 
 
 
 
 
 
698
 
699
+ # Create 10 URL input fields
700
+ url_fields = []
701
+ for i in range(10):
702
+ url_value = grounding_urls_value[i] if i < len(grounding_urls_value) else ""
703
+ url_field = gr.Textbox(
704
+ label=f"URL {i+1}" + (" (Primary)" if i < 2 else " (Secondary)"),
705
+ value=url_value,
706
+ placeholder="https://..."
707
+ )
708
+ url_fields.append(url_field)
709
 
710
  config_locked = gr.Checkbox(
711
  label="Lock Configuration (Prevent further edits)",
 
736
  )
737
 
738
  # Save configuration function
739
+ def save_configuration(new_name, new_description, new_prompt, new_model, new_examples, new_temp, new_tokens, *url_values, lock_config, is_authenticated):
740
  if not is_authenticated:
741
  return "❌ Not authenticated"
742
 
 
749
  except:
750
  pass
751
 
752
+ # Load current config to preserve all values
753
+ try:
754
+ with open('config.json', 'r') as f:
755
+ current_full_config = json.load(f)
756
+ except:
757
+ # If config.json doesn't exist, use global config
758
+ current_full_config = config.copy()
759
+
760
+ # Process example prompts
761
  examples_list = [ex.strip() for ex in new_examples.split('\n') if ex.strip()]
762
 
763
+ # Process URL values - lock_config is the last parameter
764
+ urls = list(url_values[:-1]) # All but last are URLs
765
+ lock_config_from_args = url_values[-1] # Last is lock_config
766
+ # Filter out empty URLs
767
  grounding_urls = [url.strip() for url in urls if url.strip()]
768
 
769
+ # Update all editable fields while preserving everything else
770
+ current_full_config.update({
771
  'name': new_name,
772
  'description': new_description,
 
 
773
  'system_prompt': new_prompt,
774
+ 'model': new_model,
775
  'examples': examples_list,
 
776
  'temperature': new_temp,
777
  'max_tokens': int(new_tokens),
778
+ 'grounding_urls': grounding_urls,
779
+ 'locked': lock_config_from_args,
780
  'last_modified': datetime.now().isoformat(),
781
+ 'last_modified_by': 'faculty'
782
  }
783
 
784
  try:
785
  with open('config.json', 'w') as f:
786
+ json.dump(current_full_config, f, indent=2)
787
 
788
+ # Reload all configuration values
789
+ reload_config_values()
 
 
 
 
 
 
 
 
790
 
791
+ return f"✅ Configuration saved successfully at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
792
  except Exception as e:
793
  return f"❌ Error saving configuration: {str(e)}"
794
 
795
  # Reset configuration function
796
  def reset_configuration(is_authenticated):
797
  if not is_authenticated:
798
+ updates = ["❌ Not authenticated"] + [gr.update() for _ in range(16)] # 1 status + 16 fields
799
  return tuple(updates)
800
 
801
  # Check if locked
 
803
  with open('config.json', 'r') as f:
804
  existing_config = json.load(f)
805
  if existing_config.get('locked', False):
806
+ updates = ["🔒 Configuration is locked"] + [gr.update() for _ in range(16)]
807
  return tuple(updates)
808
  except:
809
  pass
810
 
811
+ # Get default examples as text
812
+ default_examples = DEFAULT_CONFIG.get('examples', [])
813
+ if isinstance(default_examples, list):
814
+ examples_text = "\n".join(default_examples)
815
+ else:
816
+ examples_text = ""
817
+
818
+ # Get default URLs
819
+ default_urls = DEFAULT_CONFIG.get('grounding_urls', [])
820
+ if isinstance(default_urls, str):
821
+ try:
822
+ import ast
823
+ default_urls = ast.literal_eval(default_urls)
824
+ except:
825
+ default_urls = []
826
+
827
+ # Reset to original default values
828
+ updates = [
829
+ "✅ Reset to default values",
830
+ gr.update(value=DEFAULT_CONFIG.get('name', SPACE_NAME)),
831
+ gr.update(value=DEFAULT_CONFIG.get('description', SPACE_DESCRIPTION)),
832
+ gr.update(value=DEFAULT_CONFIG.get('system_prompt', SYSTEM_PROMPT)),
833
+ gr.update(value=DEFAULT_CONFIG.get('model', MODEL)),
834
+ gr.update(value=examples_text),
835
+ gr.update(value=DEFAULT_CONFIG.get('temperature', 0.7)),
836
+ gr.update(value=DEFAULT_CONFIG.get('max_tokens', 750))
837
+ ]
838
+
839
+ # Add URL updates
840
+ for i in range(10):
841
+ url_value = default_urls[i] if i < len(default_urls) else ""
842
+ updates.append(gr.update(value=url_value))
843
+
844
+ return tuple(updates)
845
 
846
  # Connect authentication
847
  faculty_auth_btn.click(
 
859
  # Connect configuration buttons
860
  save_config_btn.click(
861
  save_configuration,
862
+ inputs=[edit_name, edit_description, edit_system_prompt, edit_model, edit_examples, edit_temperature, edit_max_tokens] + url_fields + [config_locked, faculty_auth_state],
 
 
863
  outputs=[config_status]
864
  )
865
 
866
  reset_config_btn.click(
867
  reset_configuration,
868
  inputs=[faculty_auth_state],
869
+ outputs=[config_status, edit_name, edit_description, edit_system_prompt, edit_model, edit_examples, edit_temperature, edit_max_tokens] + url_fields
 
870
  )
871
  else:
872
  gr.Markdown("ℹ️ Faculty configuration is not enabled. Set CONFIG_CODE in Space secrets to enable.")
config.json CHANGED
@@ -1,13 +1,13 @@
1
  {
2
- "name": "CUNY Virgil",
3
- "description": "Adaptable AI assistant for research methods support",
4
  "system_prompt": "You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation.",
5
- "model": "nvidia/llama-3.1-nemotron-70b-instruct",
6
  "api_key_var": "API_KEY",
7
  "temperature": 0.7,
8
  "max_tokens": 750,
9
- "examples": "['Help me narrow a research question']",
10
- "grounding_urls": "[]",
11
  "enable_dynamic_urls": true,
12
- "theme": "Glass"
13
  }
 
1
  {
2
+ "name": "Virgil Research Guide",
3
+ "description": "A customizable AI assistant",
4
  "system_prompt": "You are a research aid specializing in academic literature search and analysis. Your expertise spans discovering peer-reviewed sources, assessing research methodologies, synthesizing findings across studies, and delivering properly formatted citations. When responding, anchor claims in specific sources from provided URL contexts, differentiate between direct evidence and interpretive analysis, and note any limitations or contradictory results. Employ clear, accessible language that demystifies complex research, and propose connected research directions when appropriate. Your purpose is to serve as an informed research tool supporting users through initial concept development, exploratory investigation, information collection, and source compilation.",
5
+ "model": "google/gemini-2.0-flash-001",
6
  "api_key_var": "API_KEY",
7
  "temperature": 0.7,
8
  "max_tokens": 750,
9
+ "examples": "['Help me prepare for ethnographic fieldwork?', 'What are some methods of digital ethnography?']",
10
+ "grounding_urls": "[\"https://bcc-cuny.libguides.com/\", \"https://researchguides.journalism.cuny.edu/\"]",
11
  "enable_dynamic_urls": true,
12
+ "theme": "Default"
13
  }