Leonardo commited on
Commit
c508178
·
verified ·
1 Parent(s): 1ab1fd4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +261 -149
app.py CHANGED
@@ -1,8 +1,16 @@
 
 
 
 
 
 
 
 
1
  import mimetypes
2
  import os
3
  import re
4
  import shutil
5
- from typing import Optional
6
 
7
  from dotenv import load_dotenv
8
  from huggingface_hub import login
@@ -139,13 +147,13 @@ class ModelManager:
139
  if chosen_inference == "hf_api":
140
  return HfApiModel(model_id=model_id)
141
 
142
- elif chosen_inference == "hf_api_provider":
143
  return HfApiModel(provider="together")
144
 
145
- elif chosen_inference == "litellm":
146
  return LiteLLMModel(model_id=model_id)
147
 
148
- elif chosen_inference == "openai":
149
  if not key_manager:
150
  raise ValueError("Key manager required for OpenAI model")
151
 
@@ -153,15 +161,14 @@ class ModelManager:
153
  model_id=model_id, api_key=key_manager.get_key("openai_api_key")
154
  )
155
 
156
- elif chosen_inference == "transformers":
157
  return TransformersModel(
158
  model_id="HuggingFaceTB/SmolLM2-1.7B-Instruct",
159
  device_map="auto",
160
  max_new_tokens=1000,
161
  )
162
 
163
- else:
164
- raise ValueError(f"Invalid inference type: {chosen_inference}")
165
 
166
  except Exception as e:
167
  print(f"✗ Couldn't load model: {e}")
@@ -205,7 +212,9 @@ class ToolRegistry:
205
  return Tool.from_space(
206
  space_id="xkerser/FLUX.1-dev",
207
  name="image_generator",
208
- description="Generates high-quality AgentImage using the FLUX.1-dev model based on text prompts.",
 
 
209
  )
210
  except Exception as e:
211
  print(f"✗ Couldn't initialize image generation tool: {e}")
@@ -235,21 +244,38 @@ def create_agent():
235
  text_limit = 30000
236
  browser = SimpleTextBrowser(**BROWSER_CONFIG)
237
 
238
- # Collect all tools in a single list
239
  web_tools = ToolRegistry.load_web_tools(model, browser, text_limit)
240
- doc_tools = ToolRegistry.load_document_tools() # New document tools
241
- image_generator = ToolRegistry.load_image_generation_tools()
242
 
243
- # Combine all tools into a single list
244
- all_tools = [visualizer] + web_tools + doc_tools + [image_generator]
 
 
 
 
245
 
246
- # Validate tools before creating agent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  for tool in all_tools:
248
- if not isinstance(tool, Tool):
249
- raise ValueError(
250
- f"Invalid tool type: {type(tool)}. "
251
- f"All tools must be instances of Tool class."
252
- )
253
 
254
  return CodeAgent(
255
  model=model,
@@ -259,46 +285,55 @@ def create_agent():
259
  additional_authorized_imports=AUTHORIZED_IMPORTS,
260
  planning_interval=4,
261
  )
262
- except (ValueError, RuntimeError) as e:
263
  print(f"Failed to create agent: {e}")
264
- raise RuntimeError(f"Agent creation failed: {e}")
265
 
266
 
267
- def stream_to_gradio(
268
- agent,
269
- task: str,
270
- reset_agent_memory: bool = False,
271
- additional_args: Optional[dict] = None,
272
- ):
273
  """Runs an agent with the given task and streams messages as Gradio ChatMessages."""
274
- for step_log in agent.run(
275
- task, stream=True, reset=reset_agent_memory, additional_args=additional_args
276
- ):
277
- for message in pull_messages_from_step(step_log):
278
- yield message
279
-
280
- # Process final answer : Use a more comprehensive media output
281
- final_answer = step_log # Last log is the run's final_answer
282
- final_answer = handle_agent_output_types(final_answer)
283
-
284
- if isinstance(final_answer, AgentText):
285
- yield gr.ChatMessage(
286
- role="assistant",
287
- content=f"**Final answer:**\n{final_answer.to_string()}\n",
288
- )
289
- elif isinstance(final_answer, AgentImage):
290
- yield gr.ChatMessage(
291
- role="assistant",
292
- content={"image": final_answer.to_string(), "type": "file"},
293
- ) # Send as Gradio-compatible file object:
294
- elif isinstance(final_answer, AgentAudio):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  yield gr.ChatMessage(
296
  role="assistant",
297
- content={"audio": final_answer.to_string(), "type": "file"},
298
- ) # Send as Gradio-compatible file object
299
- else:
300
- yield gr.ChatMessage(
301
- role="assistant", content=f"**Final answer:** {str(final_answer)}"
302
  )
303
 
304
 
@@ -317,100 +352,134 @@ class GradioUI:
317
  def interact_with_agent(self, prompt, messages, session_state):
318
  """Main interaction handler with the agent."""
319
 
320
- # Get or create session-specific agent
321
  if "agent" not in session_state:
322
- session_state["agent"] = create_agent()
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- # Adding monitoring
325
- try:
326
- # Log the existence of agent memory
327
- has_memory = hasattr(session_state["agent"], "memory")
328
- print(f"Agent has memory: {has_memory}")
329
- if has_memory:
330
- print(f"Memory type: {type(session_state['agent'].memory)}")
331
 
332
- messages.append(gr.ChatMessage(role="user", content=prompt))
333
- yield messages
 
 
 
 
 
334
 
335
  for msg in stream_to_gradio(
336
- session_state["agent"], task=prompt, reset_agent_memory=False
337
  ):
338
  messages.append(msg)
339
- yield messages # Yield messages after each step
340
- yield messages # Yield messages one last time
 
 
 
341
 
342
  except Exception as e:
343
- print(f"Error in interaction: {str(e)}")
344
- raise
 
 
 
 
 
345
 
346
- def upload_file(
347
- self,
348
- file,
349
- file_uploads_log,
350
- ):
351
- """Handle file uploads with proper validation and security."""
352
  if file is None:
353
  return gr.Textbox("No file uploaded", visible=True), file_uploads_log
354
 
355
  try:
356
- mime_type, _ = mimetypes.guess_type(file.name)
357
- except Exception as e:
358
- return gr.Textbox(f"Error: {e}", visible=True), file_uploads_log
359
-
360
- if mime_type not in ALLOWED_FILE_TYPES:
361
- return gr.Textbox("File type disallowed", visible=True), file_uploads_log
 
 
 
 
 
 
362
 
363
- # Sanitize file name
364
- original_name = os.path.basename(file.name)
365
- sanitized_name = re.sub(
366
- r"[^\w\-.]", "_", original_name
367
- ) # Replace invalid chars with underscores
 
 
 
 
 
 
 
 
368
 
369
- # Ensure the extension correlates to the mime type
370
- type_to_ext = {}
371
- for ext, t in mimetypes.types_map.items():
372
- if t not in type_to_ext:
373
- type_to_ext[t] = ext
374
 
375
- # Build sanitized filename with proper extension
376
- name_parts = sanitized_name.split(".")[:-1]
377
- extension = type_to_ext.get(mime_type, "")
378
- sanitized_name = "".join(name_parts) + extension
379
 
380
- # Limit File Size, and Throw Error
381
- max_file_size_mb = 50 # Define the limit
382
- file_size_mb = os.path.getsize(file.name) / (1024 * 1024) # Size in MB
 
383
 
384
- if file_size_mb > max_file_size_mb:
385
  return (
386
- gr.Textbox(
387
- f"File size exceeds {max_file_size_mb} MB limit.", visible=True
388
- ),
389
  file_uploads_log,
390
  )
391
 
392
- # Save the uploaded file to the specified folder
393
- file_path = os.path.join(self.file_upload_folder, sanitized_name)
394
- shutil.copy(file.name, file_path)
395
-
396
- return gr.Textbox(
397
- f"File uploaded: {file_path}", visible=True
398
- ), file_uploads_log + [file_path]
399
-
400
  def log_user_message(self, text_input, file_uploads_log):
401
- """Process user message and handle file references."""
402
  message = text_input
403
 
404
  if len(file_uploads_log) > 0:
405
- message += f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}" # Added file list
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
  return (
408
  message,
409
- gr.Textbox(
410
- value="",
411
- interactive=False,
412
- placeholder="Processing...", # Changed placeholder.
413
- ),
414
  gr.Button(interactive=False),
415
  )
416
 
@@ -460,68 +529,111 @@ class GradioUI:
460
  ) # Add queue with reasonable size
461
 
462
  def _create_desktop_layout(self):
463
- """Create the desktop layout with sidebar."""
464
  with gr.Blocks(fill_height=True) as sidebar_demo:
465
  with gr.Sidebar():
466
  gr.Markdown(
467
- """#OpenDeepResearch - 3theSmolagents!
468
- Model_id: google/gemini-2.0-flash-001"""
 
469
  )
470
  with gr.Group():
471
- gr.Markdown("**What's on your mind mate?**", container=True)
472
  text_input = gr.Textbox(
473
- lines=3,
474
  label="Your request",
475
  container=False,
476
- placeholder="Enter your prompt here and press Shift+Enter or press the button",
 
477
  )
478
- launch_research_btn = gr.Button("Run", variant="primary")
479
 
480
- # If an upload folder is provided, enable the upload feature
481
- if self.file_upload_folder is not None:
482
- upload_file = gr.File(label="Upload a file")
483
- upload_status = gr.Textbox(
484
- label="Upload Status", interactive=False, visible=False
485
- )
486
- file_uploads_log = gr.State([])
487
- upload_file.change(
488
- self.upload_file,
489
- [upload_file, file_uploads_log],
490
- [upload_status, file_uploads_log],
491
- )
492
 
493
- gr.HTML("<br><br><h4><center>Powered by:</center></h4>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  with gr.Row():
495
  gr.HTML(
496
  """
497
- <div style="display: flex; align-items: center; gap: 8px; font-family: system-ui, -apple-system, sans-serif;">
498
- <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png"
499
- style="width: 32px; height: 32px; object-fit: contain;" alt="logo">
500
- <a target="_blank" href="https://github.com/huggingface/smolagents">
501
- <b>huggingface/smolagents</b>
502
- </a>
503
- </div>
504
- """
505
  )
506
 
507
- # Add session state to store session-specific data
508
- session_state = gr.State({}) # Initialize empty state for each session
509
  stored_messages = gr.State([])
510
  if "file_uploads_log" not in locals():
511
  file_uploads_log = gr.State([])
512
 
513
  chatbot = gr.Chatbot(
514
- label="open-Deep-Research",
515
  type="messages",
516
  avatar_images=(
517
  None,
518
  "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
519
  ),
520
- resizeable=False,
 
521
  scale=1,
522
  elem_id="my-chatbot",
 
 
 
 
 
 
 
 
523
  )
524
 
 
525
  self._connect_event_handlers(
526
  text_input,
527
  launch_research_btn,
 
1
+ """
2
+ OpenDeepResearch Web Interface Application
3
+
4
+ This module provides a Gradio-based web interface for interacting with AI agents
5
+ using the smolagents framework. It integrates document processing tools,
6
+ web searching, and image generation capabilities.
7
+ """
8
+
9
  import mimetypes
10
  import os
11
  import re
12
  import shutil
13
+ import datetime
14
 
15
  from dotenv import load_dotenv
16
  from huggingface_hub import login
 
147
  if chosen_inference == "hf_api":
148
  return HfApiModel(model_id=model_id)
149
 
150
+ if chosen_inference == "hf_api_provider":
151
  return HfApiModel(provider="together")
152
 
153
+ if chosen_inference == "litellm":
154
  return LiteLLMModel(model_id=model_id)
155
 
156
+ if chosen_inference == "openai":
157
  if not key_manager:
158
  raise ValueError("Key manager required for OpenAI model")
159
 
 
161
  model_id=model_id, api_key=key_manager.get_key("openai_api_key")
162
  )
163
 
164
+ if chosen_inference == "transformers":
165
  return TransformersModel(
166
  model_id="HuggingFaceTB/SmolLM2-1.7B-Instruct",
167
  device_map="auto",
168
  max_new_tokens=1000,
169
  )
170
 
171
+ raise ValueError(f"Invalid inference type: {chosen_inference}")
 
172
 
173
  except Exception as e:
174
  print(f"✗ Couldn't load model: {e}")
 
212
  return Tool.from_space(
213
  space_id="xkerser/FLUX.1-dev",
214
  name="image_generator",
215
+ description=(
216
+ "Generates high-quality AgentImage using the FLUX.1-dev model based on text prompts."
217
+ ),
218
  )
219
  except Exception as e:
220
  print(f"✗ Couldn't initialize image generation tool: {e}")
 
244
  text_limit = 30000
245
  browser = SimpleTextBrowser(**BROWSER_CONFIG)
246
 
247
+ # Create tool instances with proper error handling
248
  web_tools = ToolRegistry.load_web_tools(model, browser, text_limit)
 
 
249
 
250
+ try:
251
+ doc_tools = ToolRegistry.load_document_tools()
252
+ except AssertionError as e:
253
+ print(f"Warning: Error loading document tools: {str(e)}")
254
+ print("Attempting to continue with available tools...")
255
+ doc_tools = []
256
 
257
+ try:
258
+ image_generator = ToolRegistry.load_image_generation_tools()
259
+ except Exception as e:
260
+ print(f"Warning: Image generation tools unavailable: {str(e)}")
261
+ image_generator = None
262
+
263
+ # Combine available tools (filter out None values)
264
+ all_tools = [
265
+ tool
266
+ for tool in (
267
+ [visualizer]
268
+ + web_tools
269
+ + doc_tools
270
+ + ([image_generator] if image_generator else [])
271
+ )
272
+ if tool is not None
273
+ ]
274
+
275
+ # Log available tools
276
+ print(f"Loaded {len(all_tools)} tools successfully")
277
  for tool in all_tools:
278
+ print(f"- {tool.name}: {tool.description[:50]}...")
 
 
 
 
279
 
280
  return CodeAgent(
281
  model=model,
 
285
  additional_authorized_imports=AUTHORIZED_IMPORTS,
286
  planning_interval=4,
287
  )
288
+ except Exception as e:
289
  print(f"Failed to create agent: {e}")
290
+ raise RuntimeError(f"Agent creation failed: {e}") from e
291
 
292
 
293
+ def stream_to_gradio(agent, task, reset_agent_memory=False, additional_args=None):
 
 
 
 
 
294
  """Runs an agent with the given task and streams messages as Gradio ChatMessages."""
295
+ try:
296
+ for step_log in agent.run(
297
+ task, stream=True, reset=reset_agent_memory, additional_args=additional_args
298
+ ):
299
+ yield from pull_messages_from_step(step_log)
300
+
301
+ # Get the last step log from the agent's memory for final answer
302
+ last_step_log = agent.memory.steps[-1] if agent.memory.steps else None
303
+
304
+ if last_step_log:
305
+ # Process final answer with comprehensive media output
306
+ final_answer = handle_agent_output_types(last_step_log)
307
+
308
+ # Output handling based on type
309
+ if isinstance(final_answer, AgentText):
310
+ yield gr.ChatMessage(
311
+ role="assistant",
312
+ content=f"**Final answer:**\n{final_answer.to_string()}\n",
313
+ )
314
+ elif isinstance(final_answer, AgentImage):
315
+ yield gr.ChatMessage(
316
+ role="assistant",
317
+ content={"image": final_answer.to_string(), "type": "file"},
318
+ )
319
+ elif isinstance(final_answer, AgentAudio):
320
+ yield gr.ChatMessage(
321
+ role="assistant",
322
+ content={"audio": final_answer.to_string(), "type": "file"},
323
+ )
324
+ else:
325
+ yield gr.ChatMessage(
326
+ role="assistant", content=f"**Final answer:** {str(final_answer)}"
327
+ )
328
+ else:
329
+ yield gr.ChatMessage(
330
+ role="assistant",
331
+ content="No final answer was generated. Please try again.",
332
+ )
333
+ except Exception as e:
334
  yield gr.ChatMessage(
335
  role="assistant",
336
+ content=f"**Error occurred during processing**: {str(e)}\n\nPlease try again with a different query or check your inputs.",
 
 
 
 
337
  )
338
 
339
 
 
352
  def interact_with_agent(self, prompt, messages, session_state):
353
  """Main interaction handler with the agent."""
354
 
355
+ # Get or create session-specific agent with cache persistence
356
  if "agent" not in session_state:
357
+ try:
358
+ session_state["agent"] = create_agent()
359
+ session_state["creation_time"] = datetime.datetime.now()
360
+ session_state["request_count"] = 0
361
+ except Exception as e:
362
+ messages.append(
363
+ gr.ChatMessage(
364
+ role="assistant",
365
+ content=f"**Error initializing agent**: {str(e)}\n\nPlease refresh the page and try again.",
366
+ )
367
+ )
368
+ yield messages
369
+ return
370
 
371
+ session_state["request_count"] += 1
 
 
 
 
 
 
372
 
373
+ # Add user message
374
+ messages.append(gr.ChatMessage(role="user", content=prompt))
375
+ yield messages
376
+
377
+ try:
378
+ # Check if agent should be reset (e.g., if too many requests)
379
+ reset_needed = session_state["request_count"] > 15
380
 
381
  for msg in stream_to_gradio(
382
+ session_state["agent"], task=prompt, reset_agent_memory=reset_needed
383
  ):
384
  messages.append(msg)
385
+ yield messages
386
+
387
+ # If we reset the agent memory, update the request count
388
+ if reset_needed:
389
+ session_state["request_count"] = 1
390
 
391
  except Exception as e:
392
+ messages.append(
393
+ gr.ChatMessage(
394
+ role="assistant",
395
+ content=f"**Error processing your request**: {str(e)}\n\nPlease try again with a different query.",
396
+ )
397
+ )
398
+ yield messages
399
 
400
+ def upload_file(self, file, file_uploads_log):
401
+ """Handle file uploads with validation, security, and clear feedback."""
 
 
 
 
402
  if file is None:
403
  return gr.Textbox("No file uploaded", visible=True), file_uploads_log
404
 
405
  try:
406
+ # Get file size and check limit before processing
407
+ file_size_mb = os.path.getsize(file.name) / (1024 * 1024) # Size in MB
408
+ max_file_size_mb = 50 # Define the limit
409
+
410
+ if file_size_mb > max_file_size_mb:
411
+ return (
412
+ gr.Textbox(
413
+ f"❌ File size ({file_size_mb:.1f} MB) exceeds {max_file_size_mb} MB limit.",
414
+ visible=True,
415
+ ),
416
+ file_uploads_log,
417
+ )
418
 
419
+ # Check MIME type
420
+ mime_type, _ = mimetypes.guess_type(file.name)
421
+ if mime_type not in ALLOWED_FILE_TYPES:
422
+ allowed_extensions = [
423
+ t.rsplit("/", maxsplit=1)[-1] for t in ALLOWED_FILE_TYPES
424
+ ]
425
+ return (
426
+ gr.Textbox(
427
+ f"❌ File type '{mime_type or 'unknown'}' is not allowed. Supported types: {', '.join(allowed_extensions)}",
428
+ visible=True,
429
+ ),
430
+ file_uploads_log,
431
+ )
432
 
433
+ # Sanitize file name with better pattern
434
+ original_name = os.path.basename(file.name)
435
+ sanitized_name = re.sub(r"[^\w\-.]", "_", original_name)
 
 
436
 
437
+ # Save the uploaded file
438
+ file_path = os.path.join(self.file_upload_folder, sanitized_name)
439
+ shutil.copy(file.name, file_path)
 
440
 
441
+ return gr.Textbox(
442
+ f"✓ File uploaded successfully: {os.path.basename(file_path)} ({file_size_mb:.1f} MB)",
443
+ visible=True,
444
+ ), file_uploads_log + [file_path]
445
 
446
+ except Exception as e:
447
  return (
448
+ gr.Textbox(f"❌ Upload error: {str(e)}", visible=True),
 
 
449
  file_uploads_log,
450
  )
451
 
 
 
 
 
 
 
 
 
452
  def log_user_message(self, text_input, file_uploads_log):
453
+ """Process user message and handle file references with proper agent types."""
454
  message = text_input
455
 
456
  if len(file_uploads_log) > 0:
457
+ # Group files by type for better agent processing
458
+ file_info = {}
459
+ for file_path in file_uploads_log:
460
+ ext = os.path.splitext(file_path)[1].lower()
461
+ if ext in [".jpg", ".jpeg", ".png", ".gif", ".webp"]:
462
+ category = "images"
463
+ elif ext in [".mp3", ".wav", ".ogg"]:
464
+ category = "audio"
465
+ else:
466
+ category = "documents"
467
+
468
+ if category not in file_info:
469
+ file_info[category] = []
470
+ file_info[category].append(os.path.basename(file_path))
471
+
472
+ # Format file information for the agent
473
+ file_message = "\nYou have been provided with these files:\n"
474
+ for category, files in file_info.items():
475
+ file_message += f"- {category.capitalize()}: {', '.join(files)}\n"
476
+
477
+ message += file_message
478
+ message += "\nUse inspect_file_as_text for documents, visualizer for images, and the appropriate tools for audio files."
479
 
480
  return (
481
  message,
482
+ gr.Textbox(value="", interactive=False, placeholder="Processing..."),
 
 
 
 
483
  gr.Button(interactive=False),
484
  )
485
 
 
529
  ) # Add queue with reasonable size
530
 
531
  def _create_desktop_layout(self):
532
+ """Create the desktop layout with sidebar and enhanced styling."""
533
  with gr.Blocks(fill_height=True) as sidebar_demo:
534
  with gr.Sidebar():
535
  gr.Markdown(
536
+ """#
537
+ ### Smolagents + Document Tools
538
+ """
539
  )
540
  with gr.Group():
541
+ gr.Markdown("**What can I help you with today?**", container=True)
542
  text_input = gr.Textbox(
543
+ lines=4,
544
  label="Your request",
545
  container=False,
546
+ placeholder="Enter your question or task here...",
547
+ show_label=False,
548
  )
 
549
 
550
+ with gr.Row():
551
+ clear_btn = gr.Button("Clear", variant="secondary")
552
+ launch_research_btn = gr.Button("Run", variant="primary")
 
 
 
 
 
 
 
 
 
553
 
554
+ # File upload section with better labeling
555
+ if self.file_upload_folder is not None:
556
+ with gr.Group():
557
+ gr.Markdown("** Upload Documents**")
558
+ upload_file = gr.File(
559
+ label="Upload files for analysis",
560
+ file_types=[
561
+ "pdf",
562
+ "docx",
563
+ "txt",
564
+ "md",
565
+ "csv",
566
+ "xlsx",
567
+ "jpg",
568
+ "png",
569
+ ],
570
+ file_count="multiple",
571
+ )
572
+ upload_status = gr.Textbox(
573
+ label="Upload Status", interactive=False, visible=False
574
+ )
575
+ file_uploads_log = gr.State([])
576
+
577
+ # Show uploaded files list
578
+ uploaded_files_display = gr.Markdown("No files uploaded yet")
579
+
580
+ upload_file.change(
581
+ self.upload_file,
582
+ [upload_file, file_uploads_log],
583
+ [upload_status, file_uploads_log],
584
+ ).then(
585
+ lambda files: (
586
+ "**Uploaded Files:**\n"
587
+ + "\n".join([f"- {os.path.basename(f)}" for f in files])
588
+ if files
589
+ else "No files uploaded yet"
590
+ ),
591
+ [file_uploads_log],
592
+ [uploaded_files_display],
593
+ )
594
+
595
+ gr.HTML("<br><hr><h4><center>Powered by:</center></h4>")
596
  with gr.Row():
597
  gr.HTML(
598
  """
599
+ <div style="display: flex; align-items: center; justify-content: center; gap: 8px; font-family: system-ui, -apple-system, sans-serif;">
600
+ <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png"
601
+ style="width: 32px; height: 32px; object-fit: contain;" alt="logo">
602
+ <a target="_blank" href="https://github.com/huggingface/smolagents">
603
+ <b>huggingface/smolagents</b>
604
+ </a>
605
+ </div>
606
+ """
607
  )
608
 
609
+ # Main chat area with improved styling
610
+ session_state = gr.State({})
611
  stored_messages = gr.State([])
612
  if "file_uploads_log" not in locals():
613
  file_uploads_log = gr.State([])
614
 
615
  chatbot = gr.Chatbot(
616
+ label="OpenDeepResearch Assistant",
617
  type="messages",
618
  avatar_images=(
619
  None,
620
  "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
621
  ),
622
+ resizeable=True,
623
+ show_copy_button=True,
624
  scale=1,
625
  elem_id="my-chatbot",
626
+ height=700,
627
+ )
628
+
629
+ # Connect clear button
630
+ clear_btn.click(
631
+ lambda: ([], [], {"agent": session_state.get("agent")}),
632
+ None,
633
+ [chatbot, stored_messages, session_state],
634
  )
635
 
636
+ # Connect event handlers
637
  self._connect_event_handlers(
638
  text_input,
639
  launch_research_btn,