MusaR commited on
Commit
042fd4f
Β·
verified Β·
1 Parent(s): 1518c87

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +524 -108
app.py CHANGED
@@ -3,39 +3,320 @@ import gradio as gr
3
  import google.generativeai as genai
4
  from tavily import TavilyClient
5
  from sentence_transformers import SentenceTransformer, CrossEncoder
 
 
 
 
 
6
 
7
  from research_agent.config import AgentConfig
8
  from research_agent.agent import get_clarifying_questions, research_and_plan, write_report_stream
9
 
10
-
11
  google_key = os.getenv("GOOGLE_API_KEY")
12
  tavily_key = os.getenv("TAVILY_API_KEY")
13
 
14
  if not google_key or not tavily_key:
15
  raise ValueError("API keys not found.")
16
 
 
 
 
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- CSS = """
20
- body, .gradio-container { font-family: 'Inter', sans-serif; background-color: #343541; color: #ECECEC; }
21
- .gradio-container { max-width: 800px !important; margin: auto !important; padding-top: 2rem !important;}
22
- h1 { text-align: center; font-weight: 700; font-size: 2.5em; color: white; }
23
- .sub-header { text-align: center; color: #C5C5D2; margin-bottom: 2rem; font-size: 1.1em; }
24
- #chatbot { box-shadow: none !important; border: none !important; background-color: transparent !important; }
25
- .message-bubble { background: #40414F !important; border: 1px solid #565869 !important; color: #ECECEC !important;}
26
- .message-bubble.user { background: #343541 !important; border: none !important; }
27
- footer { display: none !important; }
28
- .gr-box.gradio-container { padding: 0 !important; }
29
- .gr-form { background-color: transparent !important; border: none !important; box-shadow: none !important; }
30
- .gradio-container .gr-form .gr-button { display: none; } /* Hide the default submit button */
31
- #chat-input-container { position: relative; }
32
- #chat-input-container textarea { background-color: #40414F; color: white; border: 1px solid #565869 !important; }
33
- #submit-button { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: #2563EB; color: white; border-radius: 4px; padding: 4px 8px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  """
35
 
36
- # --- Model Initialization ---
37
  config = AgentConfig()
38
-
39
  writer_model, planner_model, embedding_model, reranker, tavily_client = None, None, None, None, None
40
  IS_PROCESSING = False
41
 
@@ -43,7 +324,6 @@ def initialize_models():
43
  """Initializes all the models and clients using keys from environment variables."""
44
  global writer_model, planner_model, embedding_model, reranker, tavily_client, IS_PROCESSING
45
  try:
46
-
47
  genai.configure(api_key=google_key)
48
  tavily_client = TavilyClient(api_key=tavily_key)
49
  writer_model = genai.GenerativeModel(config.WRITER_MODEL)
@@ -51,119 +331,252 @@ def initialize_models():
51
  embedding_model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
52
  reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2', device='cpu')
53
  except Exception as e:
54
- # This error will be displayed in the Hugging Face logs
55
  print(f"FATAL: Failed to initialize models. Error: {str(e)}")
56
- # Raise an exception to stop the app from running incorrectly
57
  raise gr.Error(f"Failed to initialize models. Please check the logs. Error: {str(e)}")
58
  IS_PROCESSING = False
59
  print("Models and clients initialized successfully.")
60
 
61
- # --- 3. Initialize models on application startup ---
62
  initialize_models()
63
 
64
- # --- Gradio Application Logic ---
65
- with gr.Blocks(css=CSS, theme=gr.themes.Base()) as app:
66
- gr.Markdown("<h1>Mini DeepSearch Agent</h1>")
67
- gr.Markdown("<p class='sub-header'>Your AI partner for in-depth research and analysis.</p>")
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- agent_state = gr.State("INITIAL")
70
- initial_topic_state = gr.State("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
 
 
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  chatbot = gr.Chatbot(
74
  elem_id="chatbot",
75
  bubble_full_width=False,
76
- height=500,
77
- # --- 5. Make the chatbot visible from the start ---
78
  visible=True,
79
- value=[(None, "Agent is ready. What would you like to research?")]
 
80
  )
81
-
82
- with gr.Row(elem_id="chat-input-container"):
83
- chat_input = gr.Textbox(
84
- placeholder="What would you like to research?",
85
- # --- 6. Make the input box interactive and visible from the start ---
86
- interactive=True,
87
- visible=True,
88
- show_label=False,
89
- scale=8
90
- )
91
- submit_button = gr.Button("Submit", elem_id="submit-button", visible=True, scale=1)
92
-
93
- # The handle_initialization function is no longer needed.
94
-
95
- def chat_step_wrapper(user_input, history, current_agent_state, topic_state):
96
- """A wrapper to manage the processing lock."""
97
- global IS_PROCESSING
98
- if IS_PROCESSING:
99
- print("Ignoring duplicate request while processing.")
100
- # This is a generator, so we must yield something. An empty yield is fine.
101
- if False: yield
102
- return
103
-
104
- IS_PROCESSING = True
105
- try:
106
- # Yield all updates from the actual agent logic
107
- for update in chat_step(user_input, history, current_agent_state, topic_state):
108
- yield update
109
- except Exception as e:
110
- error_message = f"An error occurred: {str(e)}"
111
- history.append((None, error_message))
112
- yield history, "INITIAL", "", gr.update(interactive=True, placeholder="Let's try again. What's the topic?")
113
- finally:
114
- # Release the lock once the generator is exhausted or an error occurs
115
- IS_PROCESSING = False
116
- print("Processing finished. Lock released.")
117
-
118
- def chat_step(user_input, history, current_agent_state, topic_state):
119
- history = history or []
120
- history.append((user_input, None))
121
-
122
- if current_agent_state == "INITIAL":
123
- yield history, "CLARIFYING", user_input, gr.update(interactive=False, placeholder="Thinking...")
124
- questions = get_clarifying_questions(planner_model, user_input)
125
- history[-1] = (user_input, "To give you the best report, could you answer these questions for me?\n\n" + questions)
126
- yield history, "CLARIFYING", user_input, gr.update(interactive=True, placeholder="Provide your answers to the questions above...")
127
-
128
- elif current_agent_state == "CLARIFYING":
129
- thinking_message = "Got it. Generating your full research report. This will take a moment..."
130
- history[-1] = (user_input, thinking_message)
131
- yield history, "GENERATING", topic_state, gr.update(interactive=False, placeholder="Generating...")
132
-
133
- try:
134
- plan = research_and_plan(config, planner_model, tavily_client, topic_state, user_input)
135
- report_generator = write_report_stream(config, writer_model, tavily_client, embedding_model, reranker, plan)
136
-
137
- stream_content = ""
138
- for update in report_generator:
139
- stream_content = update
140
- history[-1] = (user_input, stream_content)
141
- yield history, "GENERATING", topic_state, gr.update(interactive=False)
142
-
143
- yield history, "INITIAL", "", gr.update(interactive=True, placeholder="Research complete. What's the next topic?")
144
-
145
- except Exception as e:
146
- error_message = f"An error occurred: {str(e)}"
147
- history.append((None, error_message))
148
- yield history, "INITIAL", "", gr.update(interactive=True, placeholder="Let's try again. What's the topic?")
149
-
150
- # --- Event Listeners ---
151
-
152
  submit_event = submit_button.click(
153
  fn=chat_step_wrapper,
154
- inputs=[chat_input, chatbot, agent_state, initial_topic_state],
155
- outputs=[chatbot, agent_state, initial_topic_state, chat_input],
156
  ).then(
157
  fn=lambda: gr.update(value=""),
158
  inputs=None,
159
  outputs=[chat_input],
160
  queue=False
161
  )
162
-
163
  chat_input.submit(
164
  fn=chat_step_wrapper,
165
- inputs=[chat_input, chatbot, agent_state, initial_topic_state],
166
- outputs=[chatbot, agent_state, initial_topic_state, chat_input],
167
  ).then(
168
  fn=lambda: gr.update(value=""),
169
  inputs=None,
@@ -171,4 +584,7 @@ with gr.Blocks(css=CSS, theme=gr.themes.Base()) as app:
171
  queue=False
172
  )
173
 
174
- app.launch(debug=True)
 
 
 
 
3
  import google.generativeai as genai
4
  from tavily import TavilyClient
5
  from sentence_transformers import SentenceTransformer, CrossEncoder
6
+ import markdown
7
+ from weasyprint import HTML, CSS as WeasyCSS
8
+ from datetime import datetime
9
+ import tempfile
10
+ import re
11
 
12
  from research_agent.config import AgentConfig
13
  from research_agent.agent import get_clarifying_questions, research_and_plan, write_report_stream
14
 
 
15
  google_key = os.getenv("GOOGLE_API_KEY")
16
  tavily_key = os.getenv("TAVILY_API_KEY")
17
 
18
  if not google_key or not tavily_key:
19
  raise ValueError("API keys not found.")
20
 
21
+ # Enhanced CSS for a professional research interface
22
+ CSS = """
23
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
24
 
25
+ :root {
26
+ --primary-color: #2563eb;
27
+ --primary-hover: #1d4ed8;
28
+ --bg-primary: #0f172a;
29
+ --bg-secondary: #1e293b;
30
+ --bg-tertiary: #334155;
31
+ --text-primary: #f1f5f9;
32
+ --text-secondary: #cbd5e1;
33
+ --text-muted: #94a3b8;
34
+ --border-color: #334155;
35
+ --success-color: #10b981;
36
+ --warning-color: #f59e0b;
37
+ --error-color: #ef4444;
38
+ }
39
 
40
+ body, .gradio-container {
41
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
42
+ background-color: var(--bg-primary) !important;
43
+ color: var(--text-primary) !important;
44
+ }
45
+
46
+ .gradio-container {
47
+ max-width: 1200px !important;
48
+ margin: 0 auto !important;
49
+ padding: 2rem !important;
50
+ }
51
+
52
+ /* Header Styling */
53
+ .header-container {
54
+ text-align: center;
55
+ margin-bottom: 3rem;
56
+ padding: 2rem;
57
+ background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
58
+ border-radius: 16px;
59
+ border: 1px solid var(--border-color);
60
+ }
61
+
62
+ h1 {
63
+ font-size: 3rem;
64
+ font-weight: 700;
65
+ background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 100%);
66
+ -webkit-background-clip: text;
67
+ -webkit-text-fill-color: transparent;
68
+ margin-bottom: 0.5rem;
69
+ }
70
+
71
+ .subtitle {
72
+ color: var(--text-secondary);
73
+ font-size: 1.25rem;
74
+ font-weight: 400;
75
+ }
76
+
77
+ /* Status Bar */
78
+ .status-bar {
79
+ background: var(--bg-secondary);
80
+ border: 1px solid var(--border-color);
81
+ border-radius: 12px;
82
+ padding: 1rem 1.5rem;
83
+ margin-bottom: 2rem;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ }
88
+
89
+ .status-indicator {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 0.5rem;
93
+ }
94
+
95
+ .status-dot {
96
+ width: 8px;
97
+ height: 8px;
98
+ border-radius: 50%;
99
+ background: var(--success-color);
100
+ animation: pulse 2s infinite;
101
+ }
102
+
103
+ @keyframes pulse {
104
+ 0% { opacity: 1; }
105
+ 50% { opacity: 0.5; }
106
+ 100% { opacity: 1; }
107
+ }
108
+
109
+ /* Chat Interface */
110
+ #chatbot {
111
+ background: var(--bg-secondary) !important;
112
+ border: 1px solid var(--border-color) !important;
113
+ border-radius: 16px !important;
114
+ overflow: hidden !important;
115
+ }
116
+
117
+ #chatbot .message {
118
+ border: none !important;
119
+ padding: 1.5rem !important;
120
+ }
121
+
122
+ #chatbot .user {
123
+ background: var(--bg-tertiary) !important;
124
+ border-left: 4px solid var(--primary-color) !important;
125
+ }
126
+
127
+ #chatbot .bot {
128
+ background: var(--bg-secondary) !important;
129
+ }
130
+
131
+ /* Progress Indicators */
132
+ .progress-container {
133
+ background: var(--bg-tertiary);
134
+ border-radius: 8px;
135
+ padding: 1rem;
136
+ margin: 1rem 0;
137
+ }
138
+
139
+ .progress-bar {
140
+ height: 4px;
141
+ background: var(--border-color);
142
+ border-radius: 2px;
143
+ overflow: hidden;
144
+ margin-top: 0.5rem;
145
+ }
146
+
147
+ .progress-fill {
148
+ height: 100%;
149
+ background: linear-gradient(90deg, var(--primary-color) 0%, #60a5fa 100%);
150
+ transition: width 0.3s ease;
151
+ animation: shimmer 2s infinite;
152
+ }
153
+
154
+ @keyframes shimmer {
155
+ 0% { opacity: 0.8; }
156
+ 50% { opacity: 1; }
157
+ 100% { opacity: 0.8; }
158
+ }
159
+
160
+ /* Input Area */
161
+ .input-container {
162
+ background: var(--bg-secondary);
163
+ border: 1px solid var(--border-color);
164
+ border-radius: 12px;
165
+ padding: 1.5rem;
166
+ margin-top: 2rem;
167
+ }
168
+
169
+ #chat-input textarea {
170
+ background: var(--bg-tertiary) !important;
171
+ color: var(--text-primary) !important;
172
+ border: 1px solid var(--border-color) !important;
173
+ border-radius: 8px !important;
174
+ padding: 1rem !important;
175
+ font-size: 1rem !important;
176
+ transition: all 0.2s ease !important;
177
+ }
178
+
179
+ #chat-input textarea:focus {
180
+ border-color: var(--primary-color) !important;
181
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1) !important;
182
+ }
183
+
184
+ /* Buttons */
185
+ .gr-button {
186
+ background: var(--primary-color) !important;
187
+ color: white !important;
188
+ border: none !important;
189
+ border-radius: 8px !important;
190
+ padding: 0.75rem 1.5rem !important;
191
+ font-weight: 600 !important;
192
+ transition: all 0.2s ease !important;
193
+ cursor: pointer !important;
194
+ }
195
+
196
+ .gr-button:hover {
197
+ background: var(--primary-hover) !important;
198
+ transform: translateY(-1px);
199
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
200
+ }
201
+
202
+ .gr-button.secondary {
203
+ background: var(--bg-tertiary) !important;
204
+ color: var(--text-primary) !important;
205
+ }
206
+
207
+ .gr-button.secondary:hover {
208
+ background: #475569 !important;
209
+ }
210
+
211
+ /* Report Display */
212
+ .report-section {
213
+ background: var(--bg-secondary);
214
+ border: 1px solid var(--border-color);
215
+ border-radius: 12px;
216
+ padding: 2rem;
217
+ margin: 1rem 0;
218
+ }
219
+
220
+ .report-section h2 {
221
+ color: var(--text-primary);
222
+ font-size: 1.75rem;
223
+ font-weight: 600;
224
+ margin-bottom: 1rem;
225
+ padding-bottom: 0.75rem;
226
+ border-bottom: 2px solid var(--border-color);
227
+ }
228
+
229
+ .report-section h3 {
230
+ color: var(--text-secondary);
231
+ font-size: 1.25rem;
232
+ font-weight: 500;
233
+ margin: 1.5rem 0 0.75rem 0;
234
+ }
235
+
236
+ .source-list {
237
+ background: var(--bg-tertiary);
238
+ border-radius: 8px;
239
+ padding: 1rem;
240
+ margin-top: 1rem;
241
+ }
242
+
243
+ .source-item {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 0.5rem;
247
+ padding: 0.5rem 0;
248
+ color: var(--text-secondary);
249
+ text-decoration: none;
250
+ transition: color 0.2s ease;
251
+ }
252
+
253
+ .source-item:hover {
254
+ color: var(--primary-color);
255
+ }
256
+
257
+ /* Loading States */
258
+ .thinking-indicator {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 0.75rem;
262
+ color: var(--text-secondary);
263
+ font-style: italic;
264
+ }
265
+
266
+ .thinking-dots {
267
+ display: flex;
268
+ gap: 0.25rem;
269
+ }
270
+
271
+ .thinking-dots span {
272
+ width: 6px;
273
+ height: 6px;
274
+ background: var(--text-muted);
275
+ border-radius: 50%;
276
+ animation: bounce 1.4s infinite ease-in-out both;
277
+ }
278
+
279
+ .thinking-dots span:nth-child(1) { animation-delay: -0.32s; }
280
+ .thinking-dots span:nth-child(2) { animation-delay: -0.16s; }
281
+
282
+ @keyframes bounce {
283
+ 0%, 80%, 100% { transform: scale(0); }
284
+ 40% { transform: scale(1); }
285
+ }
286
+
287
+ /* Export Options */
288
+ .export-container {
289
+ background: var(--bg-secondary);
290
+ border: 1px solid var(--border-color);
291
+ border-radius: 12px;
292
+ padding: 1.5rem;
293
+ margin-top: 2rem;
294
+ }
295
+
296
+ .export-buttons {
297
+ display: flex;
298
+ gap: 1rem;
299
+ margin-top: 1rem;
300
+ }
301
+
302
+ /* Responsive Design */
303
+ @media (max-width: 768px) {
304
+ .gradio-container {
305
+ padding: 1rem !important;
306
+ }
307
+
308
+ h1 {
309
+ font-size: 2rem;
310
+ }
311
+
312
+ .export-buttons {
313
+ flex-direction: column;
314
+ }
315
+ }
316
  """
317
 
318
+ # Initialize models
319
  config = AgentConfig()
 
320
  writer_model, planner_model, embedding_model, reranker, tavily_client = None, None, None, None, None
321
  IS_PROCESSING = False
322
 
 
324
  """Initializes all the models and clients using keys from environment variables."""
325
  global writer_model, planner_model, embedding_model, reranker, tavily_client, IS_PROCESSING
326
  try:
 
327
  genai.configure(api_key=google_key)
328
  tavily_client = TavilyClient(api_key=tavily_key)
329
  writer_model = genai.GenerativeModel(config.WRITER_MODEL)
 
331
  embedding_model = SentenceTransformer('all-MiniLM-L6-v2', device='cpu')
332
  reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2', device='cpu')
333
  except Exception as e:
 
334
  print(f"FATAL: Failed to initialize models. Error: {str(e)}")
 
335
  raise gr.Error(f"Failed to initialize models. Please check the logs. Error: {str(e)}")
336
  IS_PROCESSING = False
337
  print("Models and clients initialized successfully.")
338
 
339
+ # Initialize models on startup
340
  initialize_models()
341
 
342
+ # Helper functions for better UI
343
+ def format_progress_message(message):
344
+ """Formats progress messages with visual indicators"""
345
+ if "Step" in message:
346
+ return f"πŸ”„ **{message}**"
347
+ elif "Searching" in message:
348
+ return f"πŸ” {message}"
349
+ elif "Found" in message:
350
+ return f"βœ… {message}"
351
+ elif "Processing" in message:
352
+ return f"βš™οΈ {message}"
353
+ elif "Writing" in message or "Synthesizing" in message:
354
+ return f"✍️ {message}"
355
+ elif "Fact-checking" in message:
356
+ return f"πŸ”Ž {message}"
357
+ else:
358
+ return message
359
 
360
+ def export_to_pdf(report_content, filename="research_report.pdf"):
361
+ """Exports the report to PDF with proper formatting"""
362
+ try:
363
+ # Convert markdown to HTML
364
+ html_content = markdown.markdown(report_content, extensions=['extra', 'codehilite'])
365
+
366
+ # Add CSS for PDF
367
+ pdf_css = """
368
+ @page { size: A4; margin: 2cm; }
369
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
370
+ h1 { color: #2563eb; border-bottom: 2px solid #2563eb; padding-bottom: 10px; }
371
+ h2 { color: #1e40af; margin-top: 30px; }
372
+ h3 { color: #3730a3; }
373
+ pre { background: #f3f4f6; padding: 10px; border-radius: 4px; }
374
+ code { background: #e5e7eb; padding: 2px 4px; border-radius: 2px; }
375
+ blockquote { border-left: 4px solid #2563eb; padding-left: 16px; color: #6b7280; }
376
+ """
377
+
378
+ # Create PDF
379
+ with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_file:
380
+ HTML(string=f"<html><body>{html_content}</body></html>").write_pdf(
381
+ tmp_file.name,
382
+ stylesheets=[WeasyCSS(string=pdf_css)]
383
+ )
384
+ return tmp_file.name
385
+ except Exception as e:
386
+ print(f"Error creating PDF: {e}")
387
+ return None
388
 
389
+ def chat_step_wrapper(user_input, history, current_agent_state, topic_state, progress_state):
390
+ """Enhanced wrapper with progress tracking"""
391
+ global IS_PROCESSING
392
+ if IS_PROCESSING:
393
+ print("Ignoring duplicate request while processing.")
394
+ if False: yield
395
+ return
396
 
397
+ IS_PROCESSING = True
398
+ try:
399
+ for update in chat_step(user_input, history, current_agent_state, topic_state, progress_state):
400
+ yield update
401
+ except Exception as e:
402
+ error_message = f"❌ **Error**: {str(e)}"
403
+ history.append((None, error_message))
404
+ yield history, "INITIAL", "", {}, gr.update(interactive=True, placeholder="Let's try again. What would you like to research?"), None, gr.update(visible=False)
405
+ finally:
406
+ IS_PROCESSING = False
407
+ print("Processing finished. Lock released.")
408
+
409
+ def chat_step(user_input, history, current_agent_state, topic_state, progress_state):
410
+ """Enhanced chat step with visual progress tracking"""
411
+ history = history or []
412
+ history.append((user_input, None))
413
+
414
+ if current_agent_state == "INITIAL":
415
+ yield history, "CLARIFYING", user_input, progress_state, gr.update(interactive=False, placeholder="Analyzing your topic..."), None, gr.update(visible=False)
416
+
417
+ # Show thinking animation
418
+ thinking_msg = """<div class="thinking-indicator">
419
+ <span>Analyzing your research topic</span>
420
+ <div class="thinking-dots">
421
+ <span></span><span></span><span></span>
422
+ </div>
423
+ </div>"""
424
+ history[-1] = (user_input, thinking_msg)
425
+ yield history, "CLARIFYING", user_input, progress_state, gr.update(interactive=False), None, gr.update(visible=False)
426
+
427
+ questions = get_clarifying_questions(planner_model, user_input)
428
+ formatted_questions = f"""
429
+ ### 🎯 Let's refine your research
430
+
431
+ To create the most comprehensive report on **{user_input}**, I'd like to understand your specific interests:
432
+
433
+ {questions}
434
+
435
+ Please provide your answers below to help me tailor the research to your needs.
436
+ """
437
+ history[-1] = (user_input, formatted_questions)
438
+ yield history, "CLARIFYING", user_input, progress_state, gr.update(interactive=True, placeholder="Type your answers here..."), None, gr.update(visible=False)
439
+
440
+ elif current_agent_state == "CLARIFYING":
441
+ # Show initial processing message
442
+ history[-1] = (user_input, "πŸ“‹ **Perfect! I have all the information I need.**\n\nStarting deep research process...")
443
+ yield history, "GENERATING", topic_state, {"current_step": 1, "total_steps": 5}, gr.update(interactive=False, placeholder="Generating report..."), None, gr.update(visible=False)
444
+
445
+ try:
446
+ # Research and planning phase
447
+ plan = research_and_plan(config, planner_model, tavily_client, topic_state, user_input)
448
+
449
+ # Show research plan
450
+ sections_preview = "\n".join([f" {i+1}. {s.title}" for i, s in enumerate(plan['sections'])])
451
+ planning_update = f"""
452
+ ### πŸ“Š Research Plan Created
453
+
454
+ **Topic**: {plan['detailed_topic']}
455
+
456
+ **Report Structure**:
457
+ {sections_preview}
458
+
459
+ Now conducting deep research and writing each section...
460
+ """
461
+ history[-1] = (user_input, planning_update)
462
+ yield history, "GENERATING", topic_state, {"current_step": 2, "total_steps": 5}, gr.update(interactive=False), None, gr.update(visible=False)
463
+
464
+ # Stream report generation
465
+ report_generator = write_report_stream(config, writer_model, tavily_client, embedding_model, reranker, plan)
466
+
467
+ full_report = ""
468
+ for update in report_generator:
469
+ # Format the update for better display
470
+ if update.startswith("#"):
471
+ full_report = update
472
+ # Add progress indicators to the report display
473
+ display_report = full_report
474
+ else:
475
+ # Show progress updates
476
+ progress_msg = format_progress_message(update)
477
+ display_report = f"{planning_update}\n\n---\n\n**Current Progress**: {progress_msg}\n\n---\n\n### πŸ“„ Report Preview:\n\n{full_report}"
478
+
479
+ history[-1] = (user_input, display_report)
480
+ yield history, "GENERATING", topic_state, progress_state, gr.update(interactive=False), None, gr.update(visible=False)
481
+
482
+ # Final report display
483
+ completion_message = f"""
484
+ ### βœ… Research Complete!
485
+
486
+ Your comprehensive research report is ready. You can:
487
+ - πŸ“₯ Download as PDF using the button below
488
+ - πŸ“‹ Copy the text directly from the report
489
+ - πŸ”„ Start a new research topic
490
+
491
+ ---
492
+
493
+ {full_report}
494
+ """
495
+ history[-1] = (user_input, completion_message)
496
+
497
+ # Enable PDF download
498
+ pdf_path = export_to_pdf(full_report)
499
+
500
+ yield history, "INITIAL", "", {}, gr.update(interactive=True, placeholder="What would you like to research next?"), pdf_path, gr.update(visible=True)
501
+
502
+ except Exception as e:
503
+ error_msg = f"❌ **Error during research**: {str(e)}\n\nPlease try again with a different topic or check your API keys."
504
+ history.append((None, error_msg))
505
+ yield history, "INITIAL", "", {}, gr.update(interactive=True, placeholder="Let's try again. What would you like to research?"), None, gr.update(visible=False)
506
+
507
+ # Build the Gradio interface
508
+ with gr.Blocks(css=CSS, theme=gr.themes.Base()) as app:
509
+ # Header
510
+ gr.HTML("""
511
+ <div class="header-container">
512
+ <h1>DeepSearch Research Agent</h1>
513
+ <p class="subtitle">AI-powered comprehensive research and analysis</p>
514
+ </div>
515
+ """)
516
+
517
+ # Status bar
518
+ gr.HTML("""
519
+ <div class="status-bar">
520
+ <div class="status-indicator">
521
+ <span class="status-dot"></span>
522
+ <span>System Online</span>
523
+ </div>
524
+ <div>
525
+ <span style="color: var(--text-muted);">Powered by Gemini & Tavily</span>
526
+ </div>
527
+ </div>
528
+ """)
529
+
530
+ # State management
531
+ agent_state = gr.State("INITIAL")
532
+ initial_topic_state = gr.State("")
533
+ progress_state = gr.State({})
534
+
535
+ # Chat interface
536
  chatbot = gr.Chatbot(
537
  elem_id="chatbot",
538
  bubble_full_width=False,
539
+ height=600,
 
540
  visible=True,
541
+ value=[(None, "πŸ‘‹ **Welcome to DeepSearch!**\n\nI'm your AI research assistant. I can help you create comprehensive, well-researched reports on any topic.\n\n**How it works:**\n1. Tell me what you'd like to research\n2. I'll ask a few clarifying questions\n3. I'll conduct deep research and write a detailed report\n4. You'll get a downloadable PDF with all sources\n\n**What would you like to research today?**")],
542
+ avatar_images=(None, "πŸ”¬")
543
  )
544
+
545
+ # Input area
546
+ with gr.Group(elem_classes="input-container"):
547
+ with gr.Row():
548
+ chat_input = gr.Textbox(
549
+ placeholder="Enter your research topic (e.g., 'Impact of AI on healthcare', 'Climate change solutions', 'History of quantum computing')",
550
+ interactive=True,
551
+ visible=True,
552
+ show_label=False,
553
+ scale=8,
554
+ elem_id="chat-input"
555
+ )
556
+ submit_button = gr.Button("πŸš€ Start Research", scale=2, variant="primary")
557
+
558
+ # Export section
559
+ with gr.Group(elem_classes="export-container", visible=False) as export_group:
560
+ gr.Markdown("### πŸ“₯ Export Options")
561
+ with gr.Row(elem_classes="export-buttons"):
562
+ pdf_download = gr.File(label="Download PDF Report", visible=False)
563
+
564
+ # Event handlers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  submit_event = submit_button.click(
566
  fn=chat_step_wrapper,
567
+ inputs=[chat_input, chatbot, agent_state, initial_topic_state, progress_state],
568
+ outputs=[chatbot, agent_state, initial_topic_state, progress_state, chat_input, pdf_download, export_group],
569
  ).then(
570
  fn=lambda: gr.update(value=""),
571
  inputs=None,
572
  outputs=[chat_input],
573
  queue=False
574
  )
575
+
576
  chat_input.submit(
577
  fn=chat_step_wrapper,
578
+ inputs=[chat_input, chatbot, agent_state, initial_topic_state, progress_state],
579
+ outputs=[chatbot, agent_state, initial_topic_state, progress_state, chat_input, pdf_download, export_group],
580
  ).then(
581
  fn=lambda: gr.update(value=""),
582
  inputs=None,
 
584
  queue=False
585
  )
586
 
587
+ # Launch the app
588
+ if __name__ == "__main__":
589
+ app.queue()
590
+ app.launch(debug=True, share=False)