Spaces:
Runtime error
Runtime error
Upload app.py
Browse filesImprove chat UI and implement search result caching.
This commit fixes the chat display by switching to the default Gradio Chatbot (tuple-based) with a fixed height and returning chat pairs so conversations are visible. It also introduces a per-session resource cache to store web search results keyed by the query, preventing repeated searches and ensuring all resources shown come from real results. When searching, previously seen URLs are filtered out before adding to sources. State management now includes the resource cache alongside chat history, pairs, sources, and plan.
app.py
CHANGED
|
@@ -14,13 +14,15 @@ SYSTEM_PROMPT = (
|
|
| 14 |
"conversation and gathered resources."
|
| 15 |
)
|
| 16 |
|
| 17 |
-
def chat(user_message, chat_history, chat_pairs, sources, plan):
|
| 18 |
"""Handle a user chat message and return updated chat state."""
|
| 19 |
-
# Ensure lists are initialised
|
| 20 |
if chat_history is None:
|
| 21 |
chat_history = []
|
| 22 |
if chat_pairs is None:
|
| 23 |
chat_pairs = []
|
|
|
|
|
|
|
| 24 |
# Append the user's message to the conversation history (list of dictionaries for Chatbot)
|
| 25 |
chat_history.append({"role": "user", "content": user_message})
|
| 26 |
# Build messages including system prompt for API call
|
|
@@ -131,8 +133,14 @@ def chat(user_message, chat_history, chat_pairs, sources, plan):
|
|
| 131 |
lower_msg = user_message.lower()
|
| 132 |
if any(trig in lower_msg for trig in search_triggers):
|
| 133 |
try:
|
| 134 |
-
# Perform web search using the entire user message as the query
|
| 135 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
# Normalize results:
|
| 137 |
# Tavily may return a dictionary with a "results" key containing
|
| 138 |
# the list of search results. If so, extract that list. If it's a
|
|
@@ -146,10 +154,24 @@ def chat(user_message, chat_history, chat_pairs, sources, plan):
|
|
| 146 |
# Ensure the sources list is initialised
|
| 147 |
if sources is None:
|
| 148 |
sources = []
|
| 149 |
-
sources
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
# Summarise results into a simple string with title and URL
|
| 151 |
summary_lines = []
|
| 152 |
-
for r in
|
| 153 |
# Defensive: ensure r is a dict
|
| 154 |
if isinstance(r, dict):
|
| 155 |
title = r.get("title", "")
|
|
@@ -159,7 +181,10 @@ def chat(user_message, chat_history, chat_pairs, sources, plan):
|
|
| 159 |
if summary_lines:
|
| 160 |
assistant_reply = "Here are some resources I found:\n" + "\n".join(summary_lines)
|
| 161 |
else:
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
| 163 |
except Exception as e:
|
| 164 |
assistant_reply = (
|
| 165 |
"An error occurred during web search. Please ensure your search API key is configured.\n"
|
|
@@ -228,8 +253,8 @@ def chat(user_message, chat_history, chat_pairs, sources, plan):
|
|
| 228 |
chat_history.append({"role": "assistant", "content": assistant_reply})
|
| 229 |
# Append pair to display history for any other uses (kept for compatibility)
|
| 230 |
chat_pairs.append((user_message, assistant_reply))
|
| 231 |
-
# For Chatbot with type
|
| 232 |
-
return
|
| 233 |
|
| 234 |
def run_search(query, chat_history, chat_pairs, sources, plan, num_results=5, domain_filter=""):
|
| 235 |
"""Execute a web search and update sources list."""
|
|
@@ -309,9 +334,10 @@ with gr.Blocks() as demo:
|
|
| 309 |
Chat with the assistant to brainstorm your course idea. You can ask the assistant to search the internet directly in the chat. When you're ready, click **Finalize Outline** to generate a course plan. Then generate the final course package (ZIP)."""
|
| 310 |
)
|
| 311 |
# Chat interface components
|
| 312 |
-
# Use
|
| 313 |
-
#
|
| 314 |
-
|
|
|
|
| 315 |
msg_input = gr.Textbox(label="Your message", placeholder="Type your message and press Enter", lines=1)
|
| 316 |
# Buttons and outputs (search is triggered via chat; no separate search controls)
|
| 317 |
finalize_btn = gr.Button("Finalize Outline")
|
|
@@ -323,12 +349,15 @@ Chat with the assistant to brainstorm your course idea. You can ask the assistan
|
|
| 323 |
state_chat_pairs = gr.State([])
|
| 324 |
state_sources = gr.State([])
|
| 325 |
state_plan = gr.State("")
|
|
|
|
|
|
|
|
|
|
| 326 |
# Handle chat submission: update chatbot display and states
|
| 327 |
msg_input.submit(
|
| 328 |
chat,
|
| 329 |
-
inputs=[msg_input, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
| 330 |
-
# First output goes to the Chatbot;
|
| 331 |
-
outputs=[chatbot, state_chat_history, state_chat_pairs, state_sources, state_plan],
|
| 332 |
)
|
| 333 |
# Finalize outline button: generate course plan and store it
|
| 334 |
finalize_btn.click(
|
|
|
|
| 14 |
"conversation and gathered resources."
|
| 15 |
)
|
| 16 |
|
| 17 |
+
def chat(user_message, chat_history, chat_pairs, sources, plan, resource_cache):
|
| 18 |
"""Handle a user chat message and return updated chat state."""
|
| 19 |
+
# Ensure lists/dicts are initialised
|
| 20 |
if chat_history is None:
|
| 21 |
chat_history = []
|
| 22 |
if chat_pairs is None:
|
| 23 |
chat_pairs = []
|
| 24 |
+
if resource_cache is None:
|
| 25 |
+
resource_cache = {}
|
| 26 |
# Append the user's message to the conversation history (list of dictionaries for Chatbot)
|
| 27 |
chat_history.append({"role": "user", "content": user_message})
|
| 28 |
# Build messages including system prompt for API call
|
|
|
|
| 133 |
lower_msg = user_message.lower()
|
| 134 |
if any(trig in lower_msg for trig in search_triggers):
|
| 135 |
try:
|
| 136 |
+
# Perform web search using the entire user message as the query. Use cached results if available
|
| 137 |
+
query_key = user_message.strip().lower()
|
| 138 |
+
if query_key in resource_cache:
|
| 139 |
+
results = resource_cache[query_key]
|
| 140 |
+
else:
|
| 141 |
+
results = run_web_search(user_message, num_results=5, domain_filter="")
|
| 142 |
+
# store results in cache for future queries
|
| 143 |
+
resource_cache[query_key] = results
|
| 144 |
# Normalize results:
|
| 145 |
# Tavily may return a dictionary with a "results" key containing
|
| 146 |
# the list of search results. If so, extract that list. If it's a
|
|
|
|
| 154 |
# Ensure the sources list is initialised
|
| 155 |
if sources is None:
|
| 156 |
sources = []
|
| 157 |
+
# Filter out duplicate URLs already in sources
|
| 158 |
+
existing_urls = set()
|
| 159 |
+
for src in sources:
|
| 160 |
+
if isinstance(src, dict):
|
| 161 |
+
url = src.get("url")
|
| 162 |
+
if url:
|
| 163 |
+
existing_urls.add(url)
|
| 164 |
+
new_results = []
|
| 165 |
+
for r in normalized_results:
|
| 166 |
+
if isinstance(r, dict):
|
| 167 |
+
url = r.get("url")
|
| 168 |
+
if url and url not in existing_urls:
|
| 169 |
+
new_results.append(r)
|
| 170 |
+
existing_urls.add(url)
|
| 171 |
+
sources.extend(new_results)
|
| 172 |
# Summarise results into a simple string with title and URL
|
| 173 |
summary_lines = []
|
| 174 |
+
for r in new_results:
|
| 175 |
# Defensive: ensure r is a dict
|
| 176 |
if isinstance(r, dict):
|
| 177 |
title = r.get("title", "")
|
|
|
|
| 181 |
if summary_lines:
|
| 182 |
assistant_reply = "Here are some resources I found:\n" + "\n".join(summary_lines)
|
| 183 |
else:
|
| 184 |
+
if normalized_results:
|
| 185 |
+
assistant_reply = "I've already shared the relevant resources from this search."
|
| 186 |
+
else:
|
| 187 |
+
assistant_reply = "I couldn't find any results for that query."
|
| 188 |
except Exception as e:
|
| 189 |
assistant_reply = (
|
| 190 |
"An error occurred during web search. Please ensure your search API key is configured.\n"
|
|
|
|
| 253 |
chat_history.append({"role": "assistant", "content": assistant_reply})
|
| 254 |
# Append pair to display history for any other uses (kept for compatibility)
|
| 255 |
chat_pairs.append((user_message, assistant_reply))
|
| 256 |
+
# For Chatbot with default type (list of (user, assistant) tuples), return chat_pairs as the first output
|
| 257 |
+
return chat_pairs, chat_history, chat_pairs, sources, plan, resource_cache
|
| 258 |
|
| 259 |
def run_search(query, chat_history, chat_pairs, sources, plan, num_results=5, domain_filter=""):
|
| 260 |
"""Execute a web search and update sources list."""
|
|
|
|
| 334 |
Chat with the assistant to brainstorm your course idea. You can ask the assistant to search the internet directly in the chat. When you're ready, click **Finalize Outline** to generate a course plan. Then generate the final course package (ZIP)."""
|
| 335 |
)
|
| 336 |
# Chat interface components
|
| 337 |
+
# Use the default Chatbot type (list of (user, assistant) tuples). This avoids CSS issues with the
|
| 338 |
+
# messages type and makes the conversation visible without custom styling. Specify a height to
|
| 339 |
+
# constrain the display area.
|
| 340 |
+
chatbot = gr.Chatbot(label="Conversation", height=400)
|
| 341 |
msg_input = gr.Textbox(label="Your message", placeholder="Type your message and press Enter", lines=1)
|
| 342 |
# Buttons and outputs (search is triggered via chat; no separate search controls)
|
| 343 |
finalize_btn = gr.Button("Finalize Outline")
|
|
|
|
| 349 |
state_chat_pairs = gr.State([])
|
| 350 |
state_sources = gr.State([])
|
| 351 |
state_plan = gr.State("")
|
| 352 |
+
# Cache for previously fetched search results (keyed by lowercased query). Helps prevent
|
| 353 |
+
# duplicate web searches and ensures all returned resources are real and consistent.
|
| 354 |
+
state_resource_cache = gr.State({})
|
| 355 |
# Handle chat submission: update chatbot display and states
|
| 356 |
msg_input.submit(
|
| 357 |
chat,
|
| 358 |
+
inputs=[msg_input, state_chat_history, state_chat_pairs, state_sources, state_plan, state_resource_cache],
|
| 359 |
+
# First output goes to the Chatbot; return the updated chat pairs for proper display
|
| 360 |
+
outputs=[chatbot, state_chat_history, state_chat_pairs, state_sources, state_plan, state_resource_cache],
|
| 361 |
)
|
| 362 |
# Finalize outline button: generate course plan and store it
|
| 363 |
finalize_btn.click(
|