Spaces:
Running
Running
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
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
807 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
808 |
|
809 |
-
|
810 |
-
history.append(
|
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
|
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
|
1084 |
-
gr.update(value="This
|
1085 |
-
gr.update(value="
|
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
|
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=
|
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=
|
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()
|