Spaces:
Running
Running
Add URL caching system to prevent re-crawling in conversations
Browse files- Implement global url_content_cache dictionary
- Add get_cached_grounding_context function with cache key generation
- Add cache status display and clear functionality
- Optimize performance by avoiding duplicate URL fetches
- Cache persists across conversation turns
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <[email protected]>
app.py
CHANGED
@@ -26,6 +26,8 @@ SPACE_DESCRIPTION = "{description}"
|
|
26 |
SYSTEM_PROMPT = """{system_prompt}"""
|
27 |
MODEL = "{model}"
|
28 |
GROUNDING_URLS = {grounding_urls}
|
|
|
|
|
29 |
|
30 |
# Get API key from environment - customizable variable name
|
31 |
API_KEY = os.environ.get("{api_key_var}")
|
@@ -63,11 +65,21 @@ def fetch_url_content(url):
|
|
63 |
except Exception as e:
|
64 |
return f"Error fetching {{url}}: {{str(e)}}"
|
65 |
|
|
|
|
|
|
|
66 |
def get_grounding_context():
|
67 |
-
"""Fetch context from grounding URLs"""
|
68 |
if not GROUNDING_URLS:
|
69 |
return ""
|
70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
context_parts = []
|
72 |
for i, url in enumerate(GROUNDING_URLS, 1):
|
73 |
if url.strip():
|
@@ -75,8 +87,20 @@ def get_grounding_context():
|
|
75 |
context_parts.append(f"Context from URL {{i}} ({{url}}):\\n{{content}}")
|
76 |
|
77 |
if context_parts:
|
78 |
-
|
79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
def generate_response(message, history):
|
82 |
"""Generate response using OpenRouter API"""
|
@@ -87,6 +111,18 @@ def generate_response(message, history):
|
|
87 |
# Get grounding context
|
88 |
grounding_context = get_grounding_context()
|
89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
# Build enhanced system prompt with grounding context
|
91 |
enhanced_system_prompt = SYSTEM_PROMPT + grounding_context
|
92 |
|
@@ -132,13 +168,64 @@ def generate_response(message, history):
|
|
132 |
except Exception as e:
|
133 |
return f"Error: {{str(e)}}"
|
134 |
|
135 |
-
#
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
|
143 |
if __name__ == "__main__":
|
144 |
demo.launch()
|
@@ -237,13 +324,25 @@ pinned: false
|
|
237 |
5. Value: Your OpenRouter API key
|
238 |
6. Click "Add"
|
239 |
|
240 |
-
### Step 4:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
1. Go to https://openrouter.ai/keys
|
242 |
2. Sign up/login if needed
|
243 |
3. Click "Create Key"
|
244 |
4. Copy the key (starts with `sk-or-`)
|
245 |
|
246 |
-
### Step 5: Test Your Space
|
247 |
- Go back to "App" tab
|
248 |
- Your Space should be running!
|
249 |
- Try the example prompts or ask a question
|
@@ -253,7 +352,9 @@ pinned: false
|
|
253 |
- **Model**: {config['model']}
|
254 |
- **Temperature**: {config['temperature']}
|
255 |
- **Max Tokens**: {config['max_tokens']}
|
256 |
-
- **API Key Variable**: {config['api_key_var']}
|
|
|
|
|
257 |
|
258 |
## Customization
|
259 |
|
@@ -284,7 +385,7 @@ def create_requirements():
|
|
284 |
"""Generate requirements.txt"""
|
285 |
return "gradio==4.44.1\nrequests==2.32.3\ncrawl4ai==0.4.245"
|
286 |
|
287 |
-
def generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1="", url2="", url3="", url4=""):
|
288 |
"""Generate deployable zip file"""
|
289 |
|
290 |
# Process examples
|
@@ -314,7 +415,9 @@ def generate_zip(name, description, system_prompt, model, api_key_var, temperatu
|
|
314 |
'temperature': temperature,
|
315 |
'max_tokens': int(max_tokens),
|
316 |
'examples': examples_json,
|
317 |
-
'grounding_urls': json.dumps(grounding_urls)
|
|
|
|
|
318 |
}
|
319 |
|
320 |
# Generate files
|
@@ -341,7 +444,7 @@ def generate_zip(name, description, system_prompt, model, api_key_var, temperatu
|
|
341 |
return filename
|
342 |
|
343 |
# Define callback functions outside the interface
|
344 |
-
def on_generate(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4):
|
345 |
if not name or not name.strip():
|
346 |
return gr.update(value="Error: Please provide a Space Title", visible=True), gr.update(visible=False)
|
347 |
|
@@ -349,7 +452,7 @@ def on_generate(name, description, system_prompt, model, api_key_var, temperatur
|
|
349 |
return gr.update(value="Error: Please provide a System Prompt", visible=True), gr.update(visible=False)
|
350 |
|
351 |
try:
|
352 |
-
filename = generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4)
|
353 |
|
354 |
success_msg = f"""**Deployment package ready!**
|
355 |
|
@@ -373,6 +476,40 @@ def on_generate(name, description, system_prompt, model, api_key_var, temperatur
|
|
373 |
except Exception as e:
|
374 |
return gr.update(value=f"Error: {str(e)}", visible=True), gr.update(visible=False)
|
375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
376 |
def respond(message, chat_history, url1="", url2="", url3="", url4=""):
|
377 |
# Make actual API request to OpenRouter
|
378 |
import os
|
@@ -386,9 +523,9 @@ def respond(message, chat_history, url1="", url2="", url3="", url4=""):
|
|
386 |
chat_history.append([message, response])
|
387 |
return "", chat_history
|
388 |
|
389 |
-
# Get grounding context from URLs using
|
390 |
grounding_urls = [url1, url2, url3, url4]
|
391 |
-
grounding_context =
|
392 |
|
393 |
# Build enhanced system prompt with grounding context
|
394 |
base_system_prompt = """You are an expert assistant specializing in Gradio configurations for HuggingFace Spaces. You have deep knowledge of:
|
@@ -409,9 +546,13 @@ Provide specific, technical guidance focused on Gradio implementation details an
|
|
409 |
"content": enhanced_system_prompt
|
410 |
}]
|
411 |
|
412 |
-
# Add conversation history -
|
413 |
for chat in chat_history:
|
414 |
-
if isinstance(chat,
|
|
|
|
|
|
|
|
|
415 |
user_msg, assistant_msg = chat[0], chat[1]
|
416 |
if user_msg:
|
417 |
messages.append({"role": "user", "content": user_msg})
|
@@ -430,7 +571,7 @@ Provide specific, technical guidance focused on Gradio implementation details an
|
|
430 |
"Content-Type": "application/json"
|
431 |
},
|
432 |
json={
|
433 |
-
"model": "google/
|
434 |
"messages": messages,
|
435 |
"temperature": 0.7,
|
436 |
"max_tokens": 500
|
@@ -445,12 +586,69 @@ Provide specific, technical guidance focused on Gradio implementation details an
|
|
445 |
except Exception as e:
|
446 |
assistant_response = f"Error: {str(e)}"
|
447 |
|
448 |
-
chat_history.append(
|
|
|
449 |
return "", chat_history
|
450 |
|
451 |
def clear_chat():
|
452 |
return "", []
|
453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
# Create Gradio interface with proper tab structure
|
455 |
with gr.Blocks(title="Chat U/I Helper") as demo:
|
456 |
with gr.Tabs():
|
@@ -485,11 +683,24 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
485 |
info="Name for the secret in HuggingFace Space settings"
|
486 |
)
|
487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
488 |
system_prompt = gr.Textbox(
|
489 |
label="System Prompt",
|
490 |
placeholder="You are a research assistant...",
|
491 |
lines=4,
|
492 |
-
value="You are a
|
|
|
|
|
|
|
|
|
|
|
|
|
493 |
)
|
494 |
|
495 |
examples_text = gr.Textbox(
|
@@ -499,32 +710,42 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
499 |
info="These will appear as clickable examples in the chat interface"
|
500 |
)
|
501 |
|
502 |
-
gr.
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
528 |
|
529 |
with gr.Row():
|
530 |
temperature = gr.Slider(
|
@@ -549,10 +770,23 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
549 |
status = gr.Markdown(visible=False)
|
550 |
download_file = gr.File(label="Download your zip package", visible=False)
|
551 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
552 |
# Connect the generate button
|
553 |
generate_btn.click(
|
554 |
on_generate,
|
555 |
-
inputs=[name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, url1, url2, url3, url4],
|
556 |
outputs=[status, download_file]
|
557 |
)
|
558 |
|
@@ -564,8 +798,9 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
564 |
with gr.Column():
|
565 |
chatbot = gr.Chatbot(
|
566 |
value=[],
|
567 |
-
label="Chat Support Assistant",
|
568 |
-
height=400
|
|
|
569 |
)
|
570 |
msg = gr.Textbox(
|
571 |
label="Ask about configuring chat UIs for courses, research, or custom HuggingFace Spaces",
|
@@ -582,19 +817,37 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
582 |
)
|
583 |
chat_url2 = gr.Textbox(
|
584 |
label="URL 2",
|
585 |
-
value="
|
586 |
-
|
|
|
587 |
)
|
|
|
|
|
588 |
chat_url3 = gr.Textbox(
|
589 |
label="URL 3",
|
590 |
-
|
591 |
-
info="
|
|
|
592 |
)
|
|
|
593 |
chat_url4 = gr.Textbox(
|
594 |
label="URL 4",
|
595 |
-
|
596 |
-
info="
|
|
|
597 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
598 |
with gr.Row():
|
599 |
submit = gr.Button("Send", variant="primary")
|
600 |
clear = gr.Button("Clear")
|
@@ -611,9 +864,25 @@ with gr.Blocks(title="Chat U/I Helper") as demo:
|
|
611 |
inputs=msg
|
612 |
)
|
613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
614 |
# Connect the chat functionality
|
615 |
-
submit.click(
|
616 |
-
msg.submit(
|
617 |
clear.click(clear_chat, outputs=[msg, chatbot])
|
618 |
|
619 |
if __name__ == "__main__":
|
|
|
26 |
SYSTEM_PROMPT = """{system_prompt}"""
|
27 |
MODEL = "{model}"
|
28 |
GROUNDING_URLS = {grounding_urls}
|
29 |
+
ACCESS_CODE = "{access_code}"
|
30 |
+
ENABLE_DYNAMIC_URLS = {enable_dynamic_urls}
|
31 |
|
32 |
# Get API key from environment - customizable variable name
|
33 |
API_KEY = os.environ.get("{api_key_var}")
|
|
|
65 |
except Exception as e:
|
66 |
return f"Error fetching {{url}}: {{str(e)}}"
|
67 |
|
68 |
+
# Global cache for URL content to avoid re-crawling in generated spaces
|
69 |
+
_url_content_cache = {{}}
|
70 |
+
|
71 |
def get_grounding_context():
|
72 |
+
"""Fetch context from grounding URLs with caching"""
|
73 |
if not GROUNDING_URLS:
|
74 |
return ""
|
75 |
|
76 |
+
# Create cache key from URLs
|
77 |
+
cache_key = tuple(sorted([url for url in GROUNDING_URLS if url and url.strip()]))
|
78 |
+
|
79 |
+
# Check cache first
|
80 |
+
if cache_key in _url_content_cache:
|
81 |
+
return _url_content_cache[cache_key]
|
82 |
+
|
83 |
context_parts = []
|
84 |
for i, url in enumerate(GROUNDING_URLS, 1):
|
85 |
if url.strip():
|
|
|
87 |
context_parts.append(f"Context from URL {{i}} ({{url}}):\\n{{content}}")
|
88 |
|
89 |
if context_parts:
|
90 |
+
result = "\\n\\n" + "\\n\\n".join(context_parts) + "\\n\\n"
|
91 |
+
else:
|
92 |
+
result = ""
|
93 |
+
|
94 |
+
# Cache the result
|
95 |
+
_url_content_cache[cache_key] = result
|
96 |
+
return result
|
97 |
+
|
98 |
+
import re
|
99 |
+
|
100 |
+
def extract_urls_from_text(text):
|
101 |
+
"""Extract URLs from text using regex"""
|
102 |
+
url_pattern = r'https?://[^\\s<>"{{}}|\\^`\\[\\]"]+'
|
103 |
+
return re.findall(url_pattern, text)
|
104 |
|
105 |
def generate_response(message, history):
|
106 |
"""Generate response using OpenRouter API"""
|
|
|
111 |
# Get grounding context
|
112 |
grounding_context = get_grounding_context()
|
113 |
|
114 |
+
# If dynamic URLs are enabled, check message for URLs to fetch
|
115 |
+
if ENABLE_DYNAMIC_URLS:
|
116 |
+
urls_in_message = extract_urls_from_text(message)
|
117 |
+
if urls_in_message:
|
118 |
+
# Fetch content from URLs mentioned in the message
|
119 |
+
dynamic_context_parts = []
|
120 |
+
for url in urls_in_message[:3]: # Limit to 3 URLs per message
|
121 |
+
content = fetch_url_content(url)
|
122 |
+
dynamic_context_parts.append(f"\\n\\nDynamic context from {{url}}:\\n{{content}}")
|
123 |
+
if dynamic_context_parts:
|
124 |
+
grounding_context += "\\n".join(dynamic_context_parts)
|
125 |
+
|
126 |
# Build enhanced system prompt with grounding context
|
127 |
enhanced_system_prompt = SYSTEM_PROMPT + grounding_context
|
128 |
|
|
|
168 |
except Exception as e:
|
169 |
return f"Error: {{str(e)}}"
|
170 |
|
171 |
+
# Access code verification
|
172 |
+
access_granted = gr.State(False)
|
173 |
+
|
174 |
+
def verify_access_code(code):
|
175 |
+
\"\"\"Verify the access code\"\"\"
|
176 |
+
if not ACCESS_CODE:
|
177 |
+
return gr.update(visible=False), gr.update(visible=True), True
|
178 |
+
|
179 |
+
if code == ACCESS_CODE:
|
180 |
+
return gr.update(visible=False), gr.update(visible=True), True
|
181 |
+
else:
|
182 |
+
return gr.update(visible=True, value="❌ Incorrect access code. Please try again."), gr.update(visible=False), False
|
183 |
+
|
184 |
+
def protected_generate_response(message, history, access_state):
|
185 |
+
\"\"\"Protected response function that checks access\"\"\"
|
186 |
+
if not access_state:
|
187 |
+
return "Please enter the access code to continue."
|
188 |
+
return generate_response(message, history)
|
189 |
+
|
190 |
+
# Create interface with access code protection
|
191 |
+
with gr.Blocks(title=SPACE_NAME) as demo:
|
192 |
+
gr.Markdown(f"# {{SPACE_NAME}}")
|
193 |
+
gr.Markdown(SPACE_DESCRIPTION)
|
194 |
+
|
195 |
+
# Access code section (shown only if ACCESS_CODE is set)
|
196 |
+
with gr.Column(visible=bool(ACCESS_CODE)) as access_section:
|
197 |
+
gr.Markdown("### 🔐 Access Required")
|
198 |
+
gr.Markdown("Please enter the access code provided by your instructor:")
|
199 |
+
|
200 |
+
access_input = gr.Textbox(
|
201 |
+
label="Access Code",
|
202 |
+
placeholder="Enter access code...",
|
203 |
+
type="password"
|
204 |
+
)
|
205 |
+
access_btn = gr.Button("Submit", variant="primary")
|
206 |
+
access_error = gr.Markdown(visible=False)
|
207 |
+
|
208 |
+
# Main chat interface (hidden until access granted)
|
209 |
+
with gr.Column(visible=not bool(ACCESS_CODE)) as chat_section:
|
210 |
+
chat_interface = gr.ChatInterface(
|
211 |
+
fn=lambda msg, hist: protected_generate_response(msg, hist, access_granted.value),
|
212 |
+
title="", # Title already shown above
|
213 |
+
description="", # Description already shown above
|
214 |
+
examples={examples}
|
215 |
+
)
|
216 |
+
|
217 |
+
# Connect access verification
|
218 |
+
if ACCESS_CODE:
|
219 |
+
access_btn.click(
|
220 |
+
verify_access_code,
|
221 |
+
inputs=[access_input],
|
222 |
+
outputs=[access_error, chat_section, access_granted]
|
223 |
+
)
|
224 |
+
access_input.submit(
|
225 |
+
verify_access_code,
|
226 |
+
inputs=[access_input],
|
227 |
+
outputs=[access_error, chat_section, access_granted]
|
228 |
+
)
|
229 |
|
230 |
if __name__ == "__main__":
|
231 |
demo.launch()
|
|
|
324 |
5. Value: Your OpenRouter API key
|
325 |
6. Click "Add"
|
326 |
|
327 |
+
{f'''### Step 4: Configure Access Control (Optional)
|
328 |
+
Your Space is configured with access code protection. Students will need to enter the access code to use the chatbot.
|
329 |
+
|
330 |
+
**Access Code**: `{config['access_code']}`
|
331 |
+
|
332 |
+
To disable access protection:
|
333 |
+
1. Edit `app.py` in your Space
|
334 |
+
2. Change `ACCESS_CODE = "{config['access_code']}"` to `ACCESS_CODE = ""`
|
335 |
+
3. The Space will rebuild automatically
|
336 |
+
|
337 |
+
''' if config['access_code'] else ''}
|
338 |
+
|
339 |
+
### Step {4 if not config['access_code'] else 5}: Get Your API Key
|
340 |
1. Go to https://openrouter.ai/keys
|
341 |
2. Sign up/login if needed
|
342 |
3. Click "Create Key"
|
343 |
4. Copy the key (starts with `sk-or-`)
|
344 |
|
345 |
+
### Step {5 if not config['access_code'] else 6}: Test Your Space
|
346 |
- Go back to "App" tab
|
347 |
- Your Space should be running!
|
348 |
- Try the example prompts or ask a question
|
|
|
352 |
- **Model**: {config['model']}
|
353 |
- **Temperature**: {config['temperature']}
|
354 |
- **Max Tokens**: {config['max_tokens']}
|
355 |
+
- **API Key Variable**: {config['api_key_var']}{f"""
|
356 |
+
- **Access Code**: {config['access_code']} (Students need this to access the chatbot)""" if config['access_code'] else ""}{f"""
|
357 |
+
- **Dynamic URL Fetching**: Enabled (Assistant can fetch URLs mentioned in conversations)""" if config.get('enable_dynamic_urls') else ""}
|
358 |
|
359 |
## Customization
|
360 |
|
|
|
385 |
"""Generate requirements.txt"""
|
386 |
return "gradio==4.44.1\nrequests==2.32.3\ncrawl4ai==0.4.245"
|
387 |
|
388 |
+
def generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, access_code="", enable_dynamic_urls=False, url1="", url2="", url3="", url4=""):
|
389 |
"""Generate deployable zip file"""
|
390 |
|
391 |
# Process examples
|
|
|
415 |
'temperature': temperature,
|
416 |
'max_tokens': int(max_tokens),
|
417 |
'examples': examples_json,
|
418 |
+
'grounding_urls': json.dumps(grounding_urls),
|
419 |
+
'access_code': access_code or "",
|
420 |
+
'enable_dynamic_urls': enable_dynamic_urls
|
421 |
}
|
422 |
|
423 |
# Generate files
|
|
|
444 |
return filename
|
445 |
|
446 |
# Define callback functions outside the interface
|
447 |
+
def on_generate(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, access_code, enable_dynamic_urls, url1, url2, url3, url4):
|
448 |
if not name or not name.strip():
|
449 |
return gr.update(value="Error: Please provide a Space Title", visible=True), gr.update(visible=False)
|
450 |
|
|
|
452 |
return gr.update(value="Error: Please provide a System Prompt", visible=True), gr.update(visible=False)
|
453 |
|
454 |
try:
|
455 |
+
filename = generate_zip(name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, access_code, enable_dynamic_urls, url1, url2, url3, url4)
|
456 |
|
457 |
success_msg = f"""**Deployment package ready!**
|
458 |
|
|
|
476 |
except Exception as e:
|
477 |
return gr.update(value=f"Error: {str(e)}", visible=True), gr.update(visible=False)
|
478 |
|
479 |
+
# Global cache for URL content to avoid re-crawling
|
480 |
+
url_content_cache = {}
|
481 |
+
|
482 |
+
def get_cached_grounding_context(urls):
|
483 |
+
"""Get grounding context with caching to avoid re-crawling same URLs"""
|
484 |
+
if not urls:
|
485 |
+
return ""
|
486 |
+
|
487 |
+
# Filter valid URLs
|
488 |
+
valid_urls = [url for url in urls if url and url.strip()]
|
489 |
+
if not valid_urls:
|
490 |
+
return ""
|
491 |
+
|
492 |
+
# Create cache key from sorted URLs
|
493 |
+
cache_key = tuple(sorted(valid_urls))
|
494 |
+
|
495 |
+
# Check if we already have this content cached
|
496 |
+
if cache_key in url_content_cache:
|
497 |
+
return url_content_cache[cache_key]
|
498 |
+
|
499 |
+
# If not cached, fetch using Crawl4AI
|
500 |
+
grounding_context = get_grounding_context_crawl4ai(valid_urls)
|
501 |
+
|
502 |
+
# Cache the result
|
503 |
+
url_content_cache[cache_key] = grounding_context
|
504 |
+
|
505 |
+
return grounding_context
|
506 |
+
|
507 |
+
def respond_with_cache_update(message, chat_history, url1="", url2="", url3="", url4=""):
|
508 |
+
"""Wrapper that updates cache status after responding"""
|
509 |
+
msg, history = respond(message, chat_history, url1, url2, url3, url4)
|
510 |
+
cache_status = get_cache_status()
|
511 |
+
return msg, history, cache_status
|
512 |
+
|
513 |
def respond(message, chat_history, url1="", url2="", url3="", url4=""):
|
514 |
# Make actual API request to OpenRouter
|
515 |
import os
|
|
|
523 |
chat_history.append([message, response])
|
524 |
return "", chat_history
|
525 |
|
526 |
+
# Get grounding context from URLs using cached approach
|
527 |
grounding_urls = [url1, url2, url3, url4]
|
528 |
+
grounding_context = get_cached_grounding_context(grounding_urls)
|
529 |
|
530 |
# Build enhanced system prompt with grounding context
|
531 |
base_system_prompt = """You are an expert assistant specializing in Gradio configurations for HuggingFace Spaces. You have deep knowledge of:
|
|
|
546 |
"content": enhanced_system_prompt
|
547 |
}]
|
548 |
|
549 |
+
# Add conversation history - Support both new messages format and legacy tuple format
|
550 |
for chat in chat_history:
|
551 |
+
if isinstance(chat, dict):
|
552 |
+
# New format: {"role": "user", "content": "..."}
|
553 |
+
messages.append(chat)
|
554 |
+
elif isinstance(chat, (list, tuple)) and len(chat) >= 2:
|
555 |
+
# Legacy format: ("user msg", "bot msg")
|
556 |
user_msg, assistant_msg = chat[0], chat[1]
|
557 |
if user_msg:
|
558 |
messages.append({"role": "user", "content": user_msg})
|
|
|
571 |
"Content-Type": "application/json"
|
572 |
},
|
573 |
json={
|
574 |
+
"model": "google/gemini-2.0-flash-001",
|
575 |
"messages": messages,
|
576 |
"temperature": 0.7,
|
577 |
"max_tokens": 500
|
|
|
586 |
except Exception as e:
|
587 |
assistant_response = f"Error: {str(e)}"
|
588 |
|
589 |
+
chat_history.append({"role": "user", "content": message})
|
590 |
+
chat_history.append({"role": "assistant", "content": assistant_response})
|
591 |
return "", chat_history
|
592 |
|
593 |
def clear_chat():
|
594 |
return "", []
|
595 |
|
596 |
+
def clear_url_cache():
|
597 |
+
"""Clear the URL content cache"""
|
598 |
+
global url_content_cache
|
599 |
+
url_content_cache.clear()
|
600 |
+
return "✅ URL cache cleared. Next request will re-fetch content."
|
601 |
+
|
602 |
+
def get_cache_status():
|
603 |
+
"""Get current cache status"""
|
604 |
+
if not url_content_cache:
|
605 |
+
return "🔄 No URLs cached"
|
606 |
+
return f"💾 {len(url_content_cache)} URL set(s) cached"
|
607 |
+
|
608 |
+
def add_urls(count):
|
609 |
+
"""Show additional URL fields"""
|
610 |
+
if count == 2:
|
611 |
+
return (gr.update(visible=True), gr.update(visible=False),
|
612 |
+
gr.update(value="+ Add URLs"), gr.update(visible=True), 3)
|
613 |
+
elif count == 3:
|
614 |
+
return (gr.update(visible=True), gr.update(visible=True),
|
615 |
+
gr.update(value="Max URLs", interactive=False), gr.update(visible=True), 4)
|
616 |
+
else:
|
617 |
+
return (gr.update(), gr.update(), gr.update(), gr.update(), count)
|
618 |
+
|
619 |
+
def remove_urls(count):
|
620 |
+
"""Hide URL fields"""
|
621 |
+
if count == 4:
|
622 |
+
return (gr.update(visible=True), gr.update(visible=False, value=""),
|
623 |
+
gr.update(value="+ Add URLs", interactive=True), gr.update(visible=True), 3)
|
624 |
+
elif count == 3:
|
625 |
+
return (gr.update(visible=False, value=""), gr.update(visible=False, value=""),
|
626 |
+
gr.update(value="+ Add URLs", interactive=True), gr.update(visible=False), 2)
|
627 |
+
else:
|
628 |
+
return (gr.update(), gr.update(), gr.update(), gr.update(), count)
|
629 |
+
|
630 |
+
def add_chat_urls(count):
|
631 |
+
"""Show additional chat URL fields"""
|
632 |
+
if count == 2:
|
633 |
+
return (gr.update(visible=True), gr.update(visible=False),
|
634 |
+
gr.update(value="+ Add URLs"), gr.update(visible=True), 3)
|
635 |
+
elif count == 3:
|
636 |
+
return (gr.update(visible=True), gr.update(visible=True),
|
637 |
+
gr.update(value="Max URLs", interactive=False), gr.update(visible=True), 4)
|
638 |
+
else:
|
639 |
+
return (gr.update(), gr.update(), gr.update(), gr.update(), count)
|
640 |
+
|
641 |
+
def remove_chat_urls(count):
|
642 |
+
"""Hide chat URL fields"""
|
643 |
+
if count == 4:
|
644 |
+
return (gr.update(visible=True), gr.update(visible=False, value=""),
|
645 |
+
gr.update(value="+ Add URLs", interactive=True), gr.update(visible=True), 3)
|
646 |
+
elif count == 3:
|
647 |
+
return (gr.update(visible=False, value=""), gr.update(visible=False, value=""),
|
648 |
+
gr.update(value="+ Add URLs", interactive=True), gr.update(visible=False), 2)
|
649 |
+
else:
|
650 |
+
return (gr.update(), gr.update(), gr.update(), gr.update(), count)
|
651 |
+
|
652 |
# Create Gradio interface with proper tab structure
|
653 |
with gr.Blocks(title="Chat U/I Helper") as demo:
|
654 |
with gr.Tabs():
|
|
|
683 |
info="Name for the secret in HuggingFace Space settings"
|
684 |
)
|
685 |
|
686 |
+
access_code = gr.Textbox(
|
687 |
+
label="Access Code (Optional)",
|
688 |
+
placeholder="Leave empty for public access, or enter code for student access",
|
689 |
+
info="If set, students must enter this code to access the chatbot",
|
690 |
+
type="password"
|
691 |
+
)
|
692 |
+
|
693 |
system_prompt = gr.Textbox(
|
694 |
label="System Prompt",
|
695 |
placeholder="You are a research assistant...",
|
696 |
lines=4,
|
697 |
+
value="You are a research assistant that provides link-grounded information through Crawl4AI web fetching. Every claim must be supported by retrieved sources: fetch content from URLs to verify accuracy, and cite with precise links—never rely on memory for facts. Ground your responses in the provided URL contexts and any additional URLs you're instructed to fetch."
|
698 |
+
)
|
699 |
+
|
700 |
+
enable_dynamic_urls = gr.Checkbox(
|
701 |
+
label="Enable Dynamic URL Fetching",
|
702 |
+
value=False,
|
703 |
+
info="Allow the assistant to fetch additional URLs mentioned in conversations (uses Crawl4AI)"
|
704 |
)
|
705 |
|
706 |
examples_text = gr.Textbox(
|
|
|
710 |
info="These will appear as clickable examples in the chat interface"
|
711 |
)
|
712 |
|
713 |
+
with gr.Accordion("URL Grounding (Optional)", open=False):
|
714 |
+
gr.Markdown("Add URLs to provide context. Content will be fetched and added to the system prompt.")
|
715 |
+
|
716 |
+
# Initial URL fields
|
717 |
+
url1 = gr.Textbox(
|
718 |
+
label="URL 1",
|
719 |
+
placeholder="https://example.com/page1",
|
720 |
+
info="First URL for context grounding"
|
721 |
+
)
|
722 |
+
|
723 |
+
url2 = gr.Textbox(
|
724 |
+
label="URL 2",
|
725 |
+
placeholder="https://example.com/page2",
|
726 |
+
info="Second URL for context grounding"
|
727 |
+
)
|
728 |
+
|
729 |
+
# Additional URL fields (initially hidden)
|
730 |
+
url3 = gr.Textbox(
|
731 |
+
label="URL 3",
|
732 |
+
placeholder="https://example.com/page3",
|
733 |
+
info="Third URL for context grounding",
|
734 |
+
visible=False
|
735 |
+
)
|
736 |
+
|
737 |
+
url4 = gr.Textbox(
|
738 |
+
label="URL 4",
|
739 |
+
placeholder="https://example.com/page4",
|
740 |
+
info="Fourth URL for context grounding",
|
741 |
+
visible=False
|
742 |
+
)
|
743 |
+
|
744 |
+
# URL management buttons
|
745 |
+
with gr.Row():
|
746 |
+
add_url_btn = gr.Button("+ Add URLs", size="sm")
|
747 |
+
remove_url_btn = gr.Button("- Remove URLs", size="sm", visible=False)
|
748 |
+
url_count = gr.State(2) # Track number of visible URLs
|
749 |
|
750 |
with gr.Row():
|
751 |
temperature = gr.Slider(
|
|
|
770 |
status = gr.Markdown(visible=False)
|
771 |
download_file = gr.File(label="Download your zip package", visible=False)
|
772 |
|
773 |
+
# Connect the URL management buttons
|
774 |
+
add_url_btn.click(
|
775 |
+
add_urls,
|
776 |
+
inputs=[url_count],
|
777 |
+
outputs=[url3, url4, add_url_btn, remove_url_btn, url_count]
|
778 |
+
)
|
779 |
+
|
780 |
+
remove_url_btn.click(
|
781 |
+
remove_urls,
|
782 |
+
inputs=[url_count],
|
783 |
+
outputs=[url3, url4, add_url_btn, remove_url_btn, url_count]
|
784 |
+
)
|
785 |
+
|
786 |
# Connect the generate button
|
787 |
generate_btn.click(
|
788 |
on_generate,
|
789 |
+
inputs=[name, description, system_prompt, model, api_key_var, temperature, max_tokens, examples_text, access_code, enable_dynamic_urls, url1, url2, url3, url4],
|
790 |
outputs=[status, download_file]
|
791 |
)
|
792 |
|
|
|
798 |
with gr.Column():
|
799 |
chatbot = gr.Chatbot(
|
800 |
value=[],
|
801 |
+
label="Chat Support Assistant",
|
802 |
+
height=400,
|
803 |
+
type="messages"
|
804 |
)
|
805 |
msg = gr.Textbox(
|
806 |
label="Ask about configuring chat UIs for courses, research, or custom HuggingFace Spaces",
|
|
|
817 |
)
|
818 |
chat_url2 = gr.Textbox(
|
819 |
label="URL 2",
|
820 |
+
value="",
|
821 |
+
placeholder="https://example.com/page2",
|
822 |
+
info="Additional context URL"
|
823 |
)
|
824 |
+
|
825 |
+
# Additional URL fields for chat (initially hidden)
|
826 |
chat_url3 = gr.Textbox(
|
827 |
label="URL 3",
|
828 |
+
placeholder="https://example.com/page3",
|
829 |
+
info="Additional context URL",
|
830 |
+
visible=False
|
831 |
)
|
832 |
+
|
833 |
chat_url4 = gr.Textbox(
|
834 |
label="URL 4",
|
835 |
+
placeholder="https://example.com/page4",
|
836 |
+
info="Additional context URL",
|
837 |
+
visible=False
|
838 |
)
|
839 |
+
|
840 |
+
# Chat URL management buttons
|
841 |
+
with gr.Row():
|
842 |
+
add_chat_url_btn = gr.Button("+ Add URLs", size="sm")
|
843 |
+
remove_chat_url_btn = gr.Button("- Remove URLs", size="sm", visible=False)
|
844 |
+
chat_url_count = gr.State(2) # Track number of visible chat URLs
|
845 |
+
|
846 |
+
# Cache controls
|
847 |
+
with gr.Row():
|
848 |
+
cache_status = gr.Markdown("🔄 No URLs cached")
|
849 |
+
clear_cache_btn = gr.Button("Clear URL Cache", size="sm")
|
850 |
+
|
851 |
with gr.Row():
|
852 |
submit = gr.Button("Send", variant="primary")
|
853 |
clear = gr.Button("Clear")
|
|
|
864 |
inputs=msg
|
865 |
)
|
866 |
|
867 |
+
# Connect the chat URL management buttons
|
868 |
+
add_chat_url_btn.click(
|
869 |
+
add_chat_urls,
|
870 |
+
inputs=[chat_url_count],
|
871 |
+
outputs=[chat_url3, chat_url4, add_chat_url_btn, remove_chat_url_btn, chat_url_count]
|
872 |
+
)
|
873 |
+
|
874 |
+
remove_chat_url_btn.click(
|
875 |
+
remove_chat_urls,
|
876 |
+
inputs=[chat_url_count],
|
877 |
+
outputs=[chat_url3, chat_url4, add_chat_url_btn, remove_chat_url_btn, chat_url_count]
|
878 |
+
)
|
879 |
+
|
880 |
+
# Connect cache controls
|
881 |
+
clear_cache_btn.click(clear_url_cache, outputs=[cache_status])
|
882 |
+
|
883 |
# Connect the chat functionality
|
884 |
+
submit.click(respond_with_cache_update, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot, cache_status])
|
885 |
+
msg.submit(respond_with_cache_update, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot, cache_status])
|
886 |
clear.click(clear_chat, outputs=[msg, chatbot])
|
887 |
|
888 |
if __name__ == "__main__":
|