sagar007 commited on
Commit
60c475d
ยท
verified ยท
1 Parent(s): c908926

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -454
app.py CHANGED
@@ -1,513 +1,246 @@
1
  import gradio as gr
2
- from transformers import AutoModelForCausalLM, AutoTokenizer
3
- import spaces
4
  from duckduckgo_search import DDGS
5
- import time
6
- import torch
7
  from datetime import datetime
8
- import os
9
- import subprocess
10
- import numpy as np
11
- from typing import List, Dict, Tuple, Any
12
 
13
- # Install required dependencies for Kokoro with better error handling
14
- try:
15
- subprocess.run(['git', 'lfs', 'install'], check=True)
16
- if not os.path.exists('Kokoro-82M'):
17
- subprocess.run(['git', 'clone', 'https://huggingface.co/hexgrad/Kokoro-82M'], check=True)
18
 
19
- # Try installing espeak with proper package manager commands
20
- try:
21
- subprocess.run(['apt-get', 'update'], check=True)
22
- subprocess.run(['apt-get', 'install', '-y', 'espeak'], check=True)
23
- except subprocess.CalledProcessError:
24
- print("Warning: Could not install espeak. Attempting espeak-ng...")
25
- try:
26
- subprocess.run(['apt-get', 'install', '-y', 'espeak-ng'], check=True)
27
- except subprocess.CalledProcessError:
28
- print("Warning: Could not install espeak or espeak-ng. TTS functionality may be limited.")
29
-
30
- except Exception as e:
31
- print(f"Warning: Initial setup error: {str(e)}")
32
- print("Continuing with limited functionality...")
33
-
34
- # --- Initialization (Do this ONCE) ---
35
-
36
- model_name = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
37
- tokenizer = AutoTokenizer.from_pretrained(model_name)
38
- tokenizer.pad_token = tokenizer.eos_token
39
- # Initialize DeepSeek model
40
- model = AutoModelForCausalLM.from_pretrained(
41
- model_name,
42
- device_map="auto",
43
- offload_folder="offload",
44
- low_cpu_mem_usage=True,
45
- torch_dtype=torch.float16
46
- )
47
-
48
- # Initialize Kokoro TTS (with error handling)
49
- VOICE_CHOICES = {
50
- '๐Ÿ‡บ๐Ÿ‡ธ Female (Default)': 'af',
51
- '๐Ÿ‡บ๐Ÿ‡ธ Bella': 'af_bella',
52
- '๐Ÿ‡บ๐Ÿ‡ธ Sarah': 'af_sarah',
53
- '๐Ÿ‡บ๐Ÿ‡ธ Nicole': 'af_nicole'
54
- }
55
- TTS_ENABLED = False
56
- TTS_MODEL = None
57
- VOICEPACK = None
58
-
59
- try:
60
- if os.path.exists('Kokoro-82M'):
61
- import sys
62
- sys.path.append('Kokoro-82M')
63
- from models import build_model # type: ignore
64
- from kokoro import generate # type: ignore
65
-
66
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
67
- TTS_MODEL = build_model('Kokoro-82M/kokoro-v0_19.pth', device)
68
-
69
- # Load default voice
70
- try:
71
- VOICEPACK = torch.load('Kokoro-82M/voices/af.pt', map_location=device, weights_only=True)
72
- except Exception as e:
73
- print(f"Warning: Could not load default voice: {e}")
74
- raise
75
-
76
- TTS_ENABLED = True
77
- else:
78
- print("Warning: Kokoro-82M directory not found. TTS disabled.")
79
- except Exception as e:
80
- print(f"Warning: Could not initialize Kokoro TTS: {str(e)}")
81
- TTS_ENABLED = False
82
-
83
- def get_web_results(query: str, max_results: int = 5) -> List[Dict[str, str]]:
84
- """Get web search results using DuckDuckGo"""
85
  try:
86
  with DDGS() as ddgs:
87
- results = list(ddgs.text(query, max_results=max_results))
88
- return [{
89
- "title": result.get("title", ""),
90
- "snippet": result["body"],
91
- "url": result["href"],
92
- "date": result.get("published", "")
93
- } for result in results]
94
  except Exception as e:
95
- print(f"Error in web search: {e}")
96
- return []
97
 
98
- def format_prompt(query: str, context: List[Dict[str, str]]) -> str:
99
- """Format the prompt with web context"""
 
100
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
101
- context_lines = '\n'.join([f'- [{res["title"]}]: {res["snippet"]}' for res in context])
102
- return f"""You are an intelligent search assistant. Answer the user's query using the provided web context.
103
- Current Time: {current_time}
104
- Important: For election-related queries, please distinguish clearly between different election years and types (presidential vs. non-presidential). Only use information from the provided web context.
105
  Query: {query}
106
  Web Context:
107
- {context_lines}
108
- Provide a detailed answer in markdown format. Include relevant information from sources and cite them using [1], [2], etc. If the query is about elections, clearly specify which year and type of election you're discussing.
109
- Answer:"""
110
 
111
- def format_sources(web_results: List[Dict[str, str]]) -> str:
112
- """Format sources with more details"""
 
 
 
 
 
 
 
 
 
113
  if not web_results:
114
- return "<div class='no-sources'>No sources available</div>"
115
-
116
- sources_html = "<div class='sources-container'>"
117
  for i, res in enumerate(web_results, 1):
118
- title = res["title"] or "Source"
119
- date = f"<span class='source-date'>{res['date']}</span>" if res['date'] else ""
120
  sources_html += f"""
121
  <div class='source-item'>
122
- <div class='source-number'>[{i}]</div>
123
- <div class='source-content'>
124
- <a href="{res['url']}" target="_blank" class='source-title'>{title}</a>
125
- {date}
126
- <div class='source-snippet'>{res['snippet'][:150]}...</div>
127
- </div>
128
  </div>
129
  """
130
  sources_html += "</div>"
131
  return sources_html
132
 
133
- @spaces.GPU(duration=30)
134
- def generate_answer(prompt: str) -> str:
135
- """Generate answer using the DeepSeek model"""
136
- inputs = tokenizer(
137
- prompt,
138
- return_tensors="pt",
139
- padding=True,
140
- truncation=True,
141
- max_length=512,
142
- return_attention_mask=True
143
- ).to(model.device)
144
-
145
- outputs = model.generate(
146
- inputs.input_ids,
147
- attention_mask=inputs.attention_mask,
148
- max_new_tokens=256,
149
- temperature=0.7,
150
- top_p=0.95,
151
- pad_token_id=tokenizer.eos_token_id,
152
- do_sample=True,
153
- early_stopping=True
154
- )
155
- return tokenizer.decode(outputs[0], skip_special_tokens=True)
156
-
157
- @spaces.GPU(duration=30)
158
- def generate_speech_with_gpu(text: str, voice_name: str = 'af', tts_model=TTS_MODEL, voicepack=VOICEPACK) -> Tuple[int, np.ndarray] | None:
159
- """Generate speech from text using Kokoro TTS model."""
160
- if not TTS_ENABLED or tts_model is None:
161
- print("TTS is not enabled or model is not loaded.")
162
- return None
163
-
164
- try:
165
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
166
-
167
- # Handle voicepack loading
168
- voice_file = f'Kokoro-82M/voices/{voice_name}.pt'
169
- if voice_name == 'af' and voicepack is not None:
170
- # Use the pre-loaded default voicepack
171
- pass
172
- elif os.path.exists(voice_file):
173
- # Load the selected voicepack if it exists
174
- voicepack = torch.load(voice_file, map_location=device, weights_only=True)
175
- else:
176
- # Fall back to default 'af' if selected voicepack is missing
177
- print(f"Voicepack {voice_name}.pt not found. Falling back to default 'af'.")
178
- voice_file = 'Kokoro-82M/voices/af.pt'
179
- if os.path.exists(voice_file):
180
- voicepack = torch.load(voice_file, map_location=device, weights_only=True)
181
- else:
182
- print("Default voicepack 'af.pt' not found. Cannot generate audio.")
183
- return None
184
-
185
- # Clean the text
186
- clean_text = ' '.join([line for line in text.split('\n') if not line.startswith('#')])
187
- clean_text = clean_text.replace('[', '').replace(']', '').replace('*', '')
188
-
189
- # Split long text into chunks
190
- max_chars = 1000
191
- chunks = []
192
- if len(clean_text) > max_chars:
193
- sentences = clean_text.split('.')
194
- current_chunk = ""
195
- for sentence in sentences:
196
- if len(current_chunk) + len(sentence) + 1 < max_chars:
197
- current_chunk += sentence + "."
198
- else:
199
- chunks.append(current_chunk.strip())
200
- current_chunk = sentence + "."
201
- if current_chunk:
202
- chunks.append(current_chunk.strip())
203
- else:
204
- chunks = [clean_text]
205
-
206
- # Generate audio for each chunk
207
- audio_chunks = []
208
- for chunk in chunks:
209
- if chunk.strip():
210
- chunk_audio, _ = generate(tts_model, chunk, voicepack, lang='a')
211
- if isinstance(chunk_audio, torch.Tensor):
212
- chunk_audio = chunk_audio.cpu().numpy()
213
- audio_chunks.append(chunk_audio)
214
-
215
- # Concatenate chunks
216
- if audio_chunks:
217
- final_audio = np.concatenate(audio_chunks) if len(audio_chunks) > 1 else audio_chunks[0]
218
- return (24000, final_audio)
219
- else:
220
- return None
221
-
222
- except Exception as e:
223
- print(f"Error generating speech: {str(e)}")
224
- return None
225
-
226
- def process_query(query: str, history: List[List[str]], selected_voice: str = 'af'):
227
- """Process user query with streaming effect"""
228
- try:
229
- if history is None:
230
- history = []
231
-
232
- # Get web results first
233
- web_results = get_web_results(query)
234
- sources_html = format_sources(web_results)
235
-
236
- current_history = history + [[query, "*Searching...*"]]
237
-
238
- # Yield initial searching state
239
- yield (
240
- "*Searching & Thinking...*", # answer_output (Markdown)
241
- sources_html, # sources_output (HTML)
242
- "Searching...", # search_btn (Button)
243
- current_history, # chat_history_display (Chatbot)
244
- None # audio_output (Audio)
245
- )
246
-
247
- # Generate answer
248
- prompt = format_prompt(query, web_results)
249
- answer = generate_answer(prompt)
250
- final_answer = answer.split("Answer:")[-1].strip()
251
-
252
- # Update history before TTS
253
- updated_history = history + [[query, final_answer]]
254
-
255
- # Generate speech from the answer (only if enabled)
256
- if TTS_ENABLED:
257
- yield (
258
- final_answer, # answer_output
259
- sources_html, # sources_output
260
- "Generating audio...", # search_btn
261
- updated_history, # chat_history_display
262
- None # audio_output
263
- )
264
- try:
265
- audio = generate_speech_with_gpu(final_answer, selected_voice)
266
- if audio is None:
267
- final_answer += "\n\n*Audio generation failed. The voicepack may be missing or incompatible.*"
268
- except Exception as e:
269
- final_answer += f"\n\n*Error generating audio: {str(e)}*"
270
- audio = None
271
- else:
272
- final_answer += "\n\n*TTS is disabled. Audio not available.*"
273
- audio = None
274
-
275
- # Yield final result
276
- yield (
277
- final_answer, # answer_output
278
- sources_html, # sources_output
279
- "Search", # search_btn
280
- updated_history, # chat_history_display
281
- audio if audio is not None else None # audio_output
282
- )
283
-
284
- except Exception as e:
285
- error_message = str(e)
286
- if "GPU quota" in error_message:
287
- error_message = "โš ๏ธ GPU quota exceeded. Please try again later when the daily quota resets."
288
- yield (
289
- f"Error: {error_message}", # answer_output
290
- sources_html, # sources_output
291
- "Search", # search_btn
292
- history + [[query, f"*Error: {error_message}*"]], # chat_history_display
293
- None # audio_output
294
- )
295
-
296
- # Update the CSS for better contrast and readability
297
  css = """
 
 
 
 
 
298
  .gradio-container {
299
- max-width: 1200px !important;
300
- background-color: #f7f7f8 !important;
 
301
  }
302
- #header {
303
  text-align: center;
304
- margin-bottom: 2rem;
305
- padding: 2rem 0;
306
- background: #1a1b1e;
307
- border-radius: 12px;
308
- color: white;
309
- }
310
- #header h1 {
311
- color: white;
312
- font-size: 2.5rem;
313
- margin-bottom: 0.5rem;
314
  }
315
- #header h3 {
316
- color: #a8a9ab;
 
 
317
  }
318
- .search-container {
319
- background: #1a1b1e;
320
- border-radius: 12px;
321
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
322
- padding: 1rem;
323
- margin-bottom: 1rem;
324
  }
325
  .search-box {
326
- padding: 1rem;
327
- background: #2c2d30;
328
- border-radius: 8px;
329
- margin-bottom: 1rem;
330
- }
331
- .search-box input[type="text"] {
332
- background: #3a3b3e !important;
333
- border: 1px solid #4a4b4e !important;
334
- color: white !important;
335
- border-radius: 8px !important;
336
- }
337
- .search-box input[type="text"]::placeholder {
338
- color: #a8a9ab !important;
339
  }
340
  .search-box button {
341
- background: #2563eb !important;
342
  border: none !important;
 
 
 
 
 
343
  }
344
  .results-container {
345
- background: #2c2d30;
346
- border-radius: 8px;
347
- padding: 1rem;
348
- margin-top: 1rem;
349
  }
350
  .answer-box {
351
- background: #3a3b3e;
352
- border-radius: 8px;
353
- padding: 1.5rem;
354
- color: white;
355
- margin-bottom: 1rem;
356
- }
357
- .answer-box p {
358
- color: #e5e7eb;
359
  line-height: 1.6;
360
  }
361
- .sources-container {
362
- margin-top: 1rem;
363
- background: #2c2d30;
364
- border-radius: 8px;
365
- padding: 1rem;
 
366
  }
367
  .source-item {
368
- display: flex;
369
- padding: 12px;
370
- margin: 8px 0;
371
- background: #3a3b3e;
372
- border-radius: 8px;
373
- transition: all 0.2s;
374
- }
375
- .source-item:hover {
376
- background: #4a4b4e;
377
  }
378
  .source-number {
 
379
  font-weight: bold;
380
- margin-right: 12px;
381
- color: #60a5fa;
382
- }
383
- .source-content {
384
- flex: 1;
385
  }
386
- .source-title {
387
- color: #60a5fa;
388
- font-weight: 500;
389
  text-decoration: none;
390
- display: block;
391
- margin-bottom: 4px;
392
- }
393
- .source-date {
394
- color: #a8a9ab;
395
- font-size: 0.9em;
396
- margin-left: 8px;
397
  }
398
- .source-snippet {
399
- color: #e5e7eb;
400
- font-size: 0.9em;
401
- line-height: 1.4;
402
  }
403
- .chat-history {
404
- max-height: 400px;
 
 
 
 
405
  overflow-y: auto;
406
- padding: 1rem;
407
- background: #2c2d30;
408
- border-radius: 8px;
409
- margin-top: 1rem;
410
- }
411
- .examples-container {
412
- background: #2c2d30;
413
- border-radius: 8px;
414
- padding: 1rem;
415
- margin-top: 1rem;
416
- }
417
- .examples-container button {
418
- background: #3a3b3e !important;
419
- border: 1px solid #4a4b4e !important;
420
- color: #e5e7eb !important;
421
- }
422
- .markdown-content {
423
- color: #e5e7eb !important;
424
- }
425
- .markdown-content h1, .markdown-content h2, .markdown-content h3 {
426
- color: white !important;
427
- }
428
- .markdown-content a {
429
- color: #60a5fa !important;
430
- }
431
- .accordion {
432
- background: #2c2d30 !important;
433
- border-radius: 8px !important;
434
- margin-top: 1rem !important;
435
- }
436
- .voice-selector {
437
- margin-top: 1rem;
438
- background: #2c2d30;
439
- border-radius: 8px;
440
- padding: 0.5rem;
441
- }
442
- .voice-selector select {
443
- background: #3a3b3e !important;
444
- color: white !important;
445
- border: 1px solid #4a4b4e !important;
446
  }
447
  """
448
 
449
- # Update the Gradio interface layout
450
- with gr.Blocks(title="AI Search Assistant", css=css, theme="dark") as demo:
451
- chat_history = gr.State([])
452
-
453
- with gr.Column(elem_id="header"):
454
- gr.Markdown("# ๐Ÿ” AI Search Assistant")
455
- gr.Markdown("### Powered by DeepSeek & Real-time Web Results with Voice")
456
-
457
- with gr.Column(elem_classes="search-container"):
458
- with gr.Row(elem_classes="search-box"):
459
- search_input = gr.Textbox(
460
- label="",
461
- placeholder="Ask anything...",
462
- scale=5,
463
- container=False
464
- )
465
- search_btn = gr.Button("Search", variant="primary", scale=1)
466
- voice_select = gr.Dropdown(
467
- choices=list(VOICE_CHOICES.items()),
468
- value='af',
469
- label="Select Voice",
470
- elem_classes="voice-selector"
471
- )
472
-
473
- with gr.Row(elem_classes="results-container"):
474
- with gr.Column(scale=2):
475
- with gr.Column(elem_classes="answer-box"):
476
- answer_output = gr.Markdown(elem_classes="markdown-content")
477
- with gr.Row():
478
- audio_output = gr.Audio(label="Voice Response", elem_classes="audio-player")
479
- with gr.Accordion("Chat History", open=False, elem_classes="accordion"):
480
- chat_history_display = gr.Chatbot(elem_classes="chat-history")
481
- with gr.Column(scale=1):
482
- with gr.Column(elem_classes="sources-box"):
483
- gr.Markdown("### Sources")
484
- sources_output = gr.HTML()
485
-
486
- with gr.Row(elem_classes="examples-container"):
487
- gr.Examples(
488
- examples=[
489
- "musk explores blockchain for doge",
490
- "nvidia to launch new gaming card",
491
- "What are the best practices for sustainable living?",
492
- "tesla mistaken for asteroid"
493
- ],
494
- inputs=search_input,
495
- label="Try these examples"
496
- )
497
 
498
- # Handle interactions
499
  search_btn.click(
500
- fn=process_query,
501
- inputs=[search_input, chat_history, voice_select],
502
- outputs=[answer_output, sources_output, search_btn, chat_history_display, audio_output]
 
 
 
 
 
503
  )
504
-
505
- # Also trigger search on Enter key
506
  search_input.submit(
507
- fn=process_query,
508
- inputs=[search_input, chat_history, voice_select],
509
- outputs=[answer_output, sources_output, search_btn, chat_history_display, audio_output]
 
 
 
 
510
  )
511
 
 
512
  if __name__ == "__main__":
513
- demo.launch(share=True)
 
1
  import gradio as gr
2
+ from transformers import pipeline
 
3
  from duckduckgo_search import DDGS
 
 
4
  from datetime import datetime
5
+ import asyncio
 
 
 
6
 
7
+ # Initialize a lightweight text generation model (distilgpt2 for speed)
8
+ generator = pipeline("text-generation", model="distilgpt2", device=0 if gr.cuda.is_available() else -1)
 
 
 
9
 
10
+ # Web search function using DuckDuckGo
11
+ async def get_web_results(query: str, max_results: int = 5) -> list:
12
+ """Fetch web results asynchronously for deep research."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  try:
14
  with DDGS() as ddgs:
15
+ results = await asyncio.to_thread(lambda: list(ddgs.text(query, max_results=max_results)))
16
+ return [
17
+ {"title": r.get("title", "No Title"), "snippet": r["body"], "url": r["href"]}
18
+ for r in results
19
+ ]
 
 
20
  except Exception as e:
21
+ return [{"title": "Error", "snippet": f"Failed to fetch results: {str(e)}", "url": "#"}]
 
22
 
23
+ # Format prompt for the AI model
24
+ def format_prompt(query: str, web_results: list) -> str:
25
+ """Create a concise prompt with web context."""
26
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
27
+ context = "\n".join([f"- {r['title']}: {r['snippet']}" for r in web_results])
28
+ return f"""Time: {current_time}
 
 
29
  Query: {query}
30
  Web Context:
31
+ {context}
32
+ Provide a detailed, well-structured answer in markdown format with citations [1], [2], etc."""
 
33
 
34
+ # Generate answer using the AI model
35
+ def generate_answer(prompt: str) -> str:
36
+ """Generate a detailed research answer."""
37
+ response = generator(prompt, max_length=300, num_return_sequences=1, truncation=True)[0]["generated_text"]
38
+ # Extract the answer after the prompt
39
+ answer_start = response.find("Provide a detailed") + len("Provide a detailed, well-structured answer in markdown format with citations [1], [2], etc.")
40
+ return response[answer_start:].strip()
41
+
42
+ # Format sources for display
43
+ def format_sources(web_results: list) -> str:
44
+ """Create an HTML list of sources."""
45
  if not web_results:
46
+ return "<div>No sources available</div>"
47
+
48
+ sources_html = "<div class='sources-list'>"
49
  for i, res in enumerate(web_results, 1):
 
 
50
  sources_html += f"""
51
  <div class='source-item'>
52
+ <span class='source-number'>[{i}]</span>
53
+ <a href='{res['url']}' target='_blank'>{res['title']}</a>: {res['snippet'][:150]}...
 
 
 
 
54
  </div>
55
  """
56
  sources_html += "</div>"
57
  return sources_html
58
 
59
+ # Main processing function
60
+ async def process_deep_research(query: str, history: list):
61
+ """Handle the deep research process with progressive updates."""
62
+ if not history:
63
+ history = []
64
+
65
+ # Step 1: Initial loading state
66
+ yield {
67
+ "answer": "*Searching the web...*",
68
+ "sources": "<div>Fetching sources...</div>",
69
+ "history": history + [[query, "*Searching...*"]]
70
+ }
71
+
72
+ # Step 2: Fetch web results
73
+ web_results = await get_web_results(query)
74
+ sources_html = format_sources(web_results)
75
+
76
+ # Step 3: Update with web search completed
77
+ yield {
78
+ "answer": "*Analyzing results...*",
79
+ "sources": sources_html,
80
+ "history": history + [[query, "*Analyzing...*"]]
81
+ }
82
+
83
+ # Step 4: Generate detailed answer
84
+ prompt = format_prompt(query, web_results)
85
+ answer = generate_answer(prompt)
86
+ final_history = history + [[query, answer]]
87
+
88
+ # Step 5: Final result
89
+ yield {
90
+ "answer": answer,
91
+ "sources": sources_html,
92
+ "history": final_history
93
+ }
94
+
95
+ # Custom CSS for a cool, modern UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  css = """
97
+ body {
98
+ font-family: 'Arial', sans-serif;
99
+ background: #1a1a1a;
100
+ color: #ffffff;
101
+ }
102
  .gradio-container {
103
+ max-width: 1000px;
104
+ margin: 0 auto;
105
+ padding: 20px;
106
  }
107
+ .header {
108
  text-align: center;
109
+ padding: 20px;
110
+ background: linear-gradient(135deg, #2c3e50, #3498db);
111
+ border-radius: 10px;
112
+ margin-bottom: 20px;
 
 
 
 
 
 
113
  }
114
+ .header h1 {
115
+ font-size: 2.5em;
116
+ margin: 0;
117
+ color: #ffffff;
118
  }
119
+ .header p {
120
+ color: #bdc3c7;
121
+ font-size: 1.1em;
 
 
 
122
  }
123
  .search-box {
124
+ background: #2c2c2c;
125
+ padding: 15px;
126
+ border-radius: 10px;
127
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
128
+ }
129
+ .search-box input {
130
+ background: #3a3a3a !important;
131
+ color: #ffffff !important;
132
+ border: none !important;
133
+ border-radius: 5px !important;
 
 
 
134
  }
135
  .search-box button {
136
+ background: #3498db !important;
137
  border: none !important;
138
+ border-radius: 5px !important;
139
+ transition: background 0.3s;
140
+ }
141
+ .search-box button:hover {
142
+ background: #2980b9 !important;
143
  }
144
  .results-container {
145
+ margin-top: 20px;
146
+ display: flex;
147
+ gap: 20px;
 
148
  }
149
  .answer-box {
150
+ flex: 2;
151
+ background: #2c2c2c;
152
+ padding: 20px;
153
+ border-radius: 10px;
154
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
155
+ }
156
+ .answer-box .markdown {
157
+ color: #ecf0f1;
158
  line-height: 1.6;
159
  }
160
+ .sources-list {
161
+ flex: 1;
162
+ background: #2c2c2c;
163
+ padding: 15px;
164
+ border-radius: 10px;
165
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
166
  }
167
  .source-item {
168
+ margin-bottom: 10px;
 
 
 
 
 
 
 
 
169
  }
170
  .source-number {
171
+ color: #3498db;
172
  font-weight: bold;
173
+ margin-right: 5px;
 
 
 
 
174
  }
175
+ .source-item a {
176
+ color: #3498db;
 
177
  text-decoration: none;
 
 
 
 
 
 
 
178
  }
179
+ .source-item a:hover {
180
+ text-decoration: underline;
 
 
181
  }
182
+ .history-box {
183
+ margin-top: 20px;
184
+ background: #2c2c2c;
185
+ padding: 15px;
186
+ border-radius: 10px;
187
+ max-height: 300px;
188
  overflow-y: auto;
189
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  }
191
  """
192
 
193
+ # Gradio app setup with Blocks for better control
194
+ with gr.Blocks(title="Deep Research Engine", css=css) as demo:
195
+ history_state = gr.State([])
196
+
197
+ # Header
198
+ with gr.Column(elem_classes="header"):
199
+ gr.Markdown("# Deep Research Engine")
200
+ gr.Markdown("Your gateway to in-depth answers with real-time web insights.")
201
+
202
+ # Search input and button
203
+ with gr.Row(elem_classes="search-box"):
204
+ search_input = gr.Textbox(label="", placeholder="Ask anything...", lines=2)
205
+ search_btn = gr.Button("Research", variant="primary")
206
+
207
+ # Results layout
208
+ with gr.Row(elem_classes="results-container"):
209
+ with gr.Column():
210
+ answer_output = gr.Markdown(label="Research Findings", elem_classes="answer-box")
211
+ with gr.Column():
212
+ sources_output = gr.HTML(label="Sources", elem_classes="sources-list")
213
+
214
+ # Chat history
215
+ with gr.Row():
216
+ history_display = gr.Chatbot(label="History", elem_classes="history-box")
217
+
218
+ # Event handling
219
+ async def handle_search(query, history):
220
+ async for step in process_deep_research(query, history):
221
+ yield step["answer"], step["sources"], step["history"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
 
 
223
  search_btn.click(
224
+ fn=handle_search,
225
+ inputs=[search_input, history_state],
226
+ outputs=[answer_output, sources_output, history_display],
227
+ _js="() => [document.querySelector('.search-box input').value, null]" # Ensure history is managed
228
+ ).then(
229
+ fn=lambda x: x,
230
+ inputs=[history_display],
231
+ outputs=[history_state]
232
  )
233
+
 
234
  search_input.submit(
235
+ fn=handle_search,
236
+ inputs=[search_input, history_state],
237
+ outputs=[answer_output, sources_output, history_display]
238
+ ).then(
239
+ fn=lambda x: x,
240
+ inputs=[history_display],
241
+ outputs=[history_state]
242
  )
243
 
244
+ # Launch the app
245
  if __name__ == "__main__":
246
+ demo.launch()