milwright commited on
Commit
dbb4d15
·
1 Parent(s): f51b1f2

Preserve user work when toggling research template and update system prompt

Browse files

- Modified toggle_research_assistant() to preserve user's existing field values when disabling template
- Added current field values as function parameters to prevent data loss
- Updated research template system prompt to emphasize DOI-verified academic sources
- Added LibKey.io reference for DOI cross-reference validation
- Enhanced preview chat with real OpenRouter API integration and URL context testing
- Added conversation export functionality to generated spaces and preview mode
- Improved template preservation ensures users can safely toggle without losing work

Files changed (1) hide show
  1. app.py +200 -26
app.py CHANGED
@@ -10,6 +10,7 @@ from bs4 import BeautifulSoup
10
  import tempfile
11
  from pathlib import Path
12
  from support_docs import create_support_docs, export_conversation_to_markdown
 
13
  # Simple URL content fetching using requests and BeautifulSoup
14
  def get_grounding_context_simple(urls):
15
  """Fetch grounding context using simple HTTP requests"""
@@ -37,12 +38,22 @@ except ImportError:
37
  # Load environment variables from .env file
38
  load_dotenv()
39
 
 
 
 
 
 
 
 
 
40
  # Template for generated space app (based on mvp_simple.py)
41
  SPACE_TEMPLATE = '''import gradio as gr
42
  import os
43
  import requests
44
  import json
45
  from bs4 import BeautifulSoup
 
 
46
 
47
  # Configuration
48
  SPACE_NAME = "{name}"
@@ -116,12 +127,29 @@ def get_grounding_context():
116
  _url_content_cache[cache_key] = result
117
  return result
118
 
119
- import re
 
 
 
 
 
 
120
 
121
- def extract_urls_from_text(text):
122
- """Extract URLs from text using regex"""
123
- url_pattern = r'https?://[^\\s<>"{{}}|\\^`\\[\\]"]+'
124
- return re.findall(url_pattern, text)
 
 
 
 
 
 
 
 
 
 
 
125
 
126
  # Initialize RAG context if enabled
127
  if ENABLE_VECTOR_RAG and RAG_DATA:
@@ -250,6 +278,20 @@ def protected_generate_response(message, history):
250
  return "Please enter the access code to continue."
251
  return generate_response(message, history)
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  # Create interface with access code protection
254
  with gr.Blocks(title=SPACE_NAME) as demo:
255
  gr.Markdown(f"# {{SPACE_NAME}}")
@@ -276,6 +318,18 @@ with gr.Blocks(title=SPACE_NAME) as demo:
276
  description="", # Description already shown above
277
  examples=None
278
  )
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  # Connect access verification
281
  if ACCESS_CODE:
@@ -798,16 +852,87 @@ Use the chat interface below to test your assistant before generating the deploy
798
  gr.update(value=config_display)
799
  )
800
 
801
- def preview_chat_response(message, history, config_data):
802
- """Generate response for preview chat"""
803
  if not config_data or not message:
804
  return "", history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
805
 
806
- # Simple simulated response for preview
807
- response = f"[Preview Mode] I'm {config_data.get('name', 'your assistant')} running on {config_data.get('model', 'unknown model')}. This is a preview of how I would respond. In the actual deployment, I would use the OpenRouter API with your configured system prompt: {config_data.get('system_prompt', '')[:100]}..."
 
 
 
 
 
 
 
808
 
809
- history.append({"role": "user", "content": message})
810
- history.append({"role": "assistant", "content": response})
811
  return "", history
812
 
813
  def clear_preview_chat():
@@ -1073,16 +1198,16 @@ def remove_chat_urls(count):
1073
  else:
1074
  return (gr.update(), gr.update(), gr.update(), gr.update(), count)
1075
 
1076
- def toggle_research_assistant(enable_research, current_custom_enabled):
1077
  """Toggle visibility of research assistant detailed fields"""
1078
  if enable_research:
1079
- combined_prompt = "You are a research assistant that provides link-grounded information through web fetching. Use MLA documentation for parenthetical citations and bibliographic entries. This assistant is designed for students and researchers conducting academic inquiry. Your main responsibilities include: analyzing academic sources, fact-checking claims with evidence, providing properly cited research summaries, and helping users navigate scholarly information. Ground all responses in provided URL contexts and any additional URLs you're instructed to fetch. Never rely on memory for factual claims."
1080
  return (
1081
  gr.update(visible=True), # Show research detailed fields
1082
  gr.update(value=combined_prompt), # Update main system prompt
1083
- gr.update(value="You are a research assistant that provides link-grounded information through web fetching. Use MLA documentation for parenthetical citations and bibliographic entries."),
1084
- gr.update(value="This assistant is designed for students and researchers conducting academic inquiry."),
1085
- gr.update(value="Your main responsibilities include: analyzing academic sources, fact-checking claims with evidence, providing properly cited research summaries, and helping users navigate scholarly information."),
1086
  gr.update(value="Ground all responses in provided URL contexts and any additional URLs you're instructed to fetch. Never rely on memory for factual claims."),
1087
  gr.update(value=True), # Enable dynamic URL fetching for research template
1088
  gr.update(visible=False), # Hide custom categories fields
@@ -1091,13 +1216,13 @@ def toggle_research_assistant(enable_research, current_custom_enabled):
1091
  else:
1092
  return (
1093
  gr.update(visible=False), # Hide research detailed fields
1094
- gr.update(value=""), # Clear main system prompt
1095
- gr.update(value=""), # Clear research fields
1096
- gr.update(value=""),
1097
- gr.update(value=""),
1098
- gr.update(value=""),
1099
  gr.update(value=False), # Disable dynamic URL setting
1100
- gr.update(visible=not current_custom_enabled), # Show custom fields if custom was enabled
1101
  gr.update() # Keep custom categories checkbox as is
1102
  )
1103
 
@@ -1127,7 +1252,7 @@ def toggle_custom_categories(enable_custom, current_research_enabled):
1127
  else:
1128
  return (
1129
  gr.update(visible=False), # Hide custom categories fields
1130
- gr.update(visible=not current_research_enabled), # Show research fields if research was enabled
1131
  gr.update() # Keep research assistant checkbox as is
1132
  )
1133
 
@@ -1382,7 +1507,7 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
1382
  # Connect the research assistant checkbox
1383
  enable_research_assistant.change(
1384
  toggle_research_assistant,
1385
- inputs=[enable_research_assistant, enable_custom_categories],
1386
  outputs=[research_detailed_fields, system_prompt, role_purpose, intended_audience, key_tasks, additional_context, enable_dynamic_urls, custom_categories_fields, enable_custom_categories]
1387
  )
1388
 
@@ -1465,6 +1590,42 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
1465
  lines=2
1466
  )
1467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1468
  with gr.Row():
1469
  preview_send = gr.Button("Send", variant="primary")
1470
  preview_clear = gr.Button("Clear")
@@ -1481,13 +1642,13 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
1481
  # Connect preview chat functionality
1482
  preview_send.click(
1483
  preview_chat_response,
1484
- inputs=[preview_msg, preview_chatbot, preview_config_state],
1485
  outputs=[preview_msg, preview_chatbot]
1486
  )
1487
 
1488
  preview_msg.submit(
1489
  preview_chat_response,
1490
- inputs=[preview_msg, preview_chatbot, preview_config_state],
1491
  outputs=[preview_msg, preview_chatbot]
1492
  )
1493
 
@@ -1501,6 +1662,19 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
1501
  inputs=[preview_chatbot],
1502
  outputs=[export_file]
1503
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
1504
 
1505
  with gr.Tab("Support Docs"):
1506
  create_support_docs()
 
10
  import tempfile
11
  from pathlib import Path
12
  from support_docs import create_support_docs, export_conversation_to_markdown
13
+
14
  # Simple URL content fetching using requests and BeautifulSoup
15
  def get_grounding_context_simple(urls):
16
  """Fetch grounding context using simple HTTP requests"""
 
38
  # Load environment variables from .env file
39
  load_dotenv()
40
 
41
+ # Utility functions
42
+ import re
43
+
44
+ def extract_urls_from_text(text):
45
+ """Extract URLs from text using regex"""
46
+ url_pattern = r'https?://[^\s<>"{}|\\^`\[\]"]+'
47
+ return re.findall(url_pattern, text)
48
+
49
  # Template for generated space app (based on mvp_simple.py)
50
  SPACE_TEMPLATE = '''import gradio as gr
51
  import os
52
  import requests
53
  import json
54
  from bs4 import BeautifulSoup
55
+ from datetime import datetime
56
+ import tempfile
57
 
58
  # Configuration
59
  SPACE_NAME = "{name}"
 
127
  _url_content_cache[cache_key] = result
128
  return result
129
 
130
+ def export_conversation_to_markdown(conversation_history):
131
+ """Export conversation history to markdown format"""
132
+ if not conversation_history:
133
+ return "No conversation to export."
134
+
135
+ markdown_content = f"""# Conversation Export
136
+ Generated on: {{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}}
137
 
138
+ ---
139
+
140
+ """
141
+
142
+ for i, message in enumerate(conversation_history):
143
+ if isinstance(message, dict):
144
+ role = message.get('role', 'unknown')
145
+ content = message.get('content', '')
146
+
147
+ if role == 'user':
148
+ markdown_content += f"## User Message {{(i//2) + 1}}\\n\\n{{content}}\\n\\n"
149
+ elif role == 'assistant':
150
+ markdown_content += f"## Assistant Response {{(i//2) + 1}}\\n\\n{{content}}\\n\\n---\\n\\n"
151
+
152
+ return markdown_content
153
 
154
  # Initialize RAG context if enabled
155
  if ENABLE_VECTOR_RAG and RAG_DATA:
 
278
  return "Please enter the access code to continue."
279
  return generate_response(message, history)
280
 
281
+ def export_conversation(history):
282
+ \"\"\"Export conversation to markdown file\"\"\"
283
+ if not history:
284
+ return gr.update(visible=False)
285
+
286
+ markdown_content = export_conversation_to_markdown(history)
287
+
288
+ # Save to temporary file
289
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
290
+ f.write(markdown_content)
291
+ temp_file = f.name
292
+
293
+ return gr.update(value=temp_file, visible=True)
294
+
295
  # Create interface with access code protection
296
  with gr.Blocks(title=SPACE_NAME) as demo:
297
  gr.Markdown(f"# {{SPACE_NAME}}")
 
318
  description="", # Description already shown above
319
  examples=None
320
  )
321
+
322
+ # Export functionality
323
+ with gr.Row():
324
+ export_btn = gr.Button("Export Conversation", variant="secondary", size="sm")
325
+ export_file = gr.File(label="Download Conversation", visible=False)
326
+
327
+ # Connect export functionality
328
+ export_btn.click(
329
+ export_conversation,
330
+ inputs=[chat_interface],
331
+ outputs=[export_file]
332
+ )
333
 
334
  # Connect access verification
335
  if ACCESS_CODE:
 
852
  gr.update(value=config_display)
853
  )
854
 
855
+ def preview_chat_response(message, history, config_data, url1="", url2="", url3="", url4=""):
856
+ """Generate response for preview chat using actual OpenRouter API"""
857
  if not config_data or not message:
858
  return "", history
859
+
860
+ # Get API key from environment
861
+ api_key = os.environ.get("OPENROUTER_API_KEY")
862
+
863
+ if not api_key:
864
+ response = f"[Preview Mode - No API Key] I'm {config_data.get('name', 'your assistant')} running on {config_data.get('model', 'unknown model')}. To test with real API responses, set your OPENROUTER_API_KEY in the environment. This preview would use your system prompt: {config_data.get('system_prompt', '')[:100]}..."
865
+ history.append([message, response])
866
+ return "", history
867
+
868
+ try:
869
+ # Get grounding context from URLs if configured
870
+ grounding_urls = [url1, url2, url3, url4]
871
+ grounding_context = get_cached_grounding_context([url for url in grounding_urls if url and url.strip()])
872
+
873
+ # Add RAG context if available (simplified for preview)
874
+ rag_context = ""
875
+ if config_data.get('enable_vector_rag'):
876
+ rag_context = "\n\n[RAG context would be retrieved here based on similarity search]\n\n"
877
+
878
+ # If dynamic URLs are enabled, check message for URLs to fetch
879
+ dynamic_context = ""
880
+ if config_data.get('enable_dynamic_urls'):
881
+ urls_in_message = extract_urls_from_text(message)
882
+ if urls_in_message:
883
+ dynamic_context_parts = []
884
+ for url in urls_in_message[:2]: # Limit to 2 URLs in preview
885
+ content = fetch_url_content(url)
886
+ dynamic_context_parts.append(f"\n\nDynamic context from {url}:\n{content}")
887
+ if dynamic_context_parts:
888
+ dynamic_context = "\n".join(dynamic_context_parts)
889
+
890
+ # Build enhanced system prompt with all contexts
891
+ enhanced_system_prompt = config_data.get('system_prompt', '') + grounding_context + rag_context + dynamic_context
892
+
893
+ # Build messages array for the API
894
+ messages = [{"role": "system", "content": enhanced_system_prompt}]
895
+
896
+ # Add conversation history - handle both formats
897
+ for chat in history:
898
+ if isinstance(chat, list) and len(chat) >= 2:
899
+ # Legacy format: [user_msg, assistant_msg]
900
+ user_msg, assistant_msg = chat[0], chat[1]
901
+ if user_msg:
902
+ messages.append({"role": "user", "content": user_msg})
903
+ if assistant_msg:
904
+ messages.append({"role": "assistant", "content": assistant_msg})
905
+
906
+ # Add current message
907
+ messages.append({"role": "user", "content": message})
908
+
909
+ # Make API request to OpenRouter
910
+ response = requests.post(
911
+ url="https://openrouter.ai/api/v1/chat/completions",
912
+ headers={
913
+ "Authorization": f"Bearer {api_key}",
914
+ "Content-Type": "application/json"
915
+ },
916
+ json={
917
+ "model": config_data.get('model', 'google/gemini-2.0-flash-001'),
918
+ "messages": messages,
919
+ "temperature": config_data.get('temperature', 0.7),
920
+ "max_tokens": config_data.get('max_tokens', 500)
921
+ }
922
+ )
923
 
924
+ if response.status_code == 200:
925
+ assistant_response = response.json()['choices'][0]['message']['content']
926
+ # Add preview indicator
927
+ assistant_response = f"[Preview Mode] {assistant_response}"
928
+ else:
929
+ assistant_response = f"[Preview Error] API Error: {response.status_code} - {response.text}"
930
+
931
+ except Exception as e:
932
+ assistant_response = f"[Preview Error] {str(e)}"
933
 
934
+ # Return in the legacy tuple format that Gradio ChatInterface expects
935
+ history.append([message, assistant_response])
936
  return "", history
937
 
938
  def clear_preview_chat():
 
1198
  else:
1199
  return (gr.update(), gr.update(), gr.update(), gr.update(), count)
1200
 
1201
+ def toggle_research_assistant(enable_research, current_custom_enabled, current_system_prompt, current_role, current_audience, current_tasks, current_context):
1202
  """Toggle visibility of research assistant detailed fields"""
1203
  if enable_research:
1204
+ combined_prompt = "You are a search tool that provides link-grounded information through web fetching, limiting source criteria to DOI-verified articles from academic databases and official sources. Use https://libkey.io/ to cross-reference and validate article DOIs for inclusion. This tool is designed for students and researchers conducting academic inquiry. Additional responsibilities include analyzing academic sources, fact-checking claims with evidence, providing properly cited research summaries, and helping users navigate scholarly information. Ground all responses in provided URL contexts and any additional URLs you're instructed to fetch. Never rely on memory for factual claims."
1205
  return (
1206
  gr.update(visible=True), # Show research detailed fields
1207
  gr.update(value=combined_prompt), # Update main system prompt
1208
+ gr.update(value="You are a search tool that provides link-grounded information through web fetching, limiting source criteria to DOI-verified articles from academic databases and official sources. Use https://libkey.io/ to cross-reference and validate article DOIs."),
1209
+ gr.update(value="This tool is designed for students and researchers conducting academic inquiry."),
1210
+ gr.update(value="Analyze academic sources, fact-check claims with evidence, provide properly cited research summaries, and help users navigate scholarly information."),
1211
  gr.update(value="Ground all responses in provided URL contexts and any additional URLs you're instructed to fetch. Never rely on memory for factual claims."),
1212
  gr.update(value=True), # Enable dynamic URL fetching for research template
1213
  gr.update(visible=False), # Hide custom categories fields
 
1216
  else:
1217
  return (
1218
  gr.update(visible=False), # Hide research detailed fields
1219
+ gr.update(value=""), # Clear main system prompt when disabling
1220
+ gr.update(value=""), # Clear role field
1221
+ gr.update(value=""), # Clear audience field
1222
+ gr.update(value=""), # Clear tasks field
1223
+ gr.update(value=""), # Clear context field
1224
  gr.update(value=False), # Disable dynamic URL setting
1225
+ gr.update(visible=False), # Hide custom fields when research is disabled
1226
  gr.update() # Keep custom categories checkbox as is
1227
  )
1228
 
 
1252
  else:
1253
  return (
1254
  gr.update(visible=False), # Hide custom categories fields
1255
+ gr.update(visible=False), # Hide research fields when custom is disabled
1256
  gr.update() # Keep research assistant checkbox as is
1257
  )
1258
 
 
1507
  # Connect the research assistant checkbox
1508
  enable_research_assistant.change(
1509
  toggle_research_assistant,
1510
+ inputs=[enable_research_assistant, enable_custom_categories, system_prompt, role_purpose, intended_audience, key_tasks, additional_context],
1511
  outputs=[research_detailed_fields, system_prompt, role_purpose, intended_audience, key_tasks, additional_context, enable_dynamic_urls, custom_categories_fields, enable_custom_categories]
1512
  )
1513
 
 
1590
  lines=2
1591
  )
1592
 
1593
+ # URL context fields for preview testing
1594
+ with gr.Accordion("Test URL Context (Optional)", open=False):
1595
+ gr.Markdown("Add URLs to test context grounding in the preview")
1596
+
1597
+ with gr.Row():
1598
+ preview_url1 = gr.Textbox(
1599
+ label="URL 1",
1600
+ placeholder="https://example.com/page1",
1601
+ scale=1
1602
+ )
1603
+ preview_url2 = gr.Textbox(
1604
+ label="URL 2",
1605
+ placeholder="https://example.com/page2",
1606
+ scale=1
1607
+ )
1608
+
1609
+ with gr.Row():
1610
+ preview_url3 = gr.Textbox(
1611
+ label="URL 3",
1612
+ placeholder="https://example.com/page3",
1613
+ scale=1,
1614
+ visible=False
1615
+ )
1616
+ preview_url4 = gr.Textbox(
1617
+ label="URL 4",
1618
+ placeholder="https://example.com/page4",
1619
+ scale=1,
1620
+ visible=False
1621
+ )
1622
+
1623
+ # URL management for preview
1624
+ with gr.Row():
1625
+ preview_add_url_btn = gr.Button("+ Add URLs", size="sm")
1626
+ preview_remove_url_btn = gr.Button("- Remove URLs", size="sm", visible=False)
1627
+ preview_url_count = gr.State(2)
1628
+
1629
  with gr.Row():
1630
  preview_send = gr.Button("Send", variant="primary")
1631
  preview_clear = gr.Button("Clear")
 
1642
  # Connect preview chat functionality
1643
  preview_send.click(
1644
  preview_chat_response,
1645
+ inputs=[preview_msg, preview_chatbot, preview_config_state, preview_url1, preview_url2, preview_url3, preview_url4],
1646
  outputs=[preview_msg, preview_chatbot]
1647
  )
1648
 
1649
  preview_msg.submit(
1650
  preview_chat_response,
1651
+ inputs=[preview_msg, preview_chatbot, preview_config_state, preview_url1, preview_url2, preview_url3, preview_url4],
1652
  outputs=[preview_msg, preview_chatbot]
1653
  )
1654
 
 
1662
  inputs=[preview_chatbot],
1663
  outputs=[export_file]
1664
  )
1665
+
1666
+ # Connect preview URL management buttons
1667
+ preview_add_url_btn.click(
1668
+ add_chat_urls,
1669
+ inputs=[preview_url_count],
1670
+ outputs=[preview_url3, preview_url4, preview_add_url_btn, preview_remove_url_btn, preview_url_count]
1671
+ )
1672
+
1673
+ preview_remove_url_btn.click(
1674
+ remove_chat_urls,
1675
+ inputs=[preview_url_count],
1676
+ outputs=[preview_url3, preview_url4, preview_add_url_btn, preview_remove_url_btn, preview_url_count]
1677
+ )
1678
 
1679
  with gr.Tab("Support Docs"):
1680
  create_support_docs()