milwright Claude commited on
Commit
ffc2e6f
·
1 Parent(s): f3a35a2

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]>

Files changed (1) hide show
  1. app.py +330 -61
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
- return "\\n\\n" + "\\n\\n".join(context_parts) + "\\n\\n"
79
- return ""
 
 
 
 
 
 
 
 
 
 
 
 
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
- # Create simple Gradio interface using ChatInterface
136
- demo = gr.ChatInterface(
137
- fn=generate_response,
138
- title=SPACE_NAME,
139
- description=SPACE_DESCRIPTION,
140
- examples={examples}
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: Get Your API Key
 
 
 
 
 
 
 
 
 
 
 
 
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 Crawl4AI
390
  grounding_urls = [url1, url2, url3, url4]
391
- grounding_context = get_grounding_context_crawl4ai(grounding_urls)
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 - Gradio 4.x uses list/tuple format
413
  for chat in chat_history:
414
- if isinstance(chat, (list, tuple)) and len(chat) >= 2:
 
 
 
 
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/gemma-3-27b-it",
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([message, assistant_response])
 
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 knowledgeable academic research assistant. Provide thoughtful, evidence-based guidance for scholarly work, literature reviews, and academic writing. Support students and researchers with clear explanations and critical thinking."
 
 
 
 
 
 
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.Markdown("### URL Grounding (Optional)")
503
- gr.Markdown("Add up to 4 URLs to provide context. Content will be fetched and added to the system prompt.")
504
-
505
- url1 = gr.Textbox(
506
- label="URL 1",
507
- placeholder="https://example.com/page1",
508
- info="First URL for context grounding"
509
- )
510
-
511
- url2 = gr.Textbox(
512
- label="URL 2",
513
- placeholder="https://example.com/page2",
514
- info="Second URL for context grounding"
515
- )
516
-
517
- url3 = gr.Textbox(
518
- label="URL 3",
519
- placeholder="https://example.com/page3",
520
- info="Third URL for context grounding"
521
- )
522
-
523
- url4 = gr.Textbox(
524
- label="URL 4",
525
- placeholder="https://example.com/page4",
526
- info="Fourth URL for context grounding"
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="https://huggingface.co/docs/hub/en/spaces-config-reference",
586
- info="Spaces Configuration Reference"
 
587
  )
 
 
588
  chat_url3 = gr.Textbox(
589
  label="URL 3",
590
- value="https://huggingface.co/docs/hub/en/spaces-settings",
591
- info="Spaces Settings Documentation"
 
592
  )
 
593
  chat_url4 = gr.Textbox(
594
  label="URL 4",
595
- value="https://huggingface.co/docs/hub/en/spaces-sdks-gradio",
596
- info="Gradio SDK for Spaces"
 
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(respond, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot])
616
- msg.submit(respond, [msg, chatbot, chat_url1, chat_url2, chat_url3, chat_url4], [msg, chatbot])
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__":