sagar007 commited on
Commit
a6e4f9f
·
verified ·
1 Parent(s): d64ad42

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +452 -138
app.py CHANGED
@@ -1,199 +1,513 @@
1
  import gradio as gr
2
- import spaces # Required for ZeroGPU
3
- from transformers import pipeline
4
  from duckduckgo_search import DDGS
 
 
5
  from datetime import datetime
6
- import re # Added for regular expressions
 
 
 
7
 
8
- # Initialize a lightweight text generation model on CPU
9
- generator = pipeline("text-generation", model="distilgpt2", device=-1) # -1 ensures CPU
 
 
 
10
 
11
- # Web search function (CPU-based)
12
- def get_web_results(query: str, max_results: int = 3) -> list:
13
- """Fetch web results synchronously for Zero GPU compatibility."""
14
  try:
15
- with DDGS() as ddgs:
16
- results = list(ddgs.text(query, max_results=max_results))
17
- return [{"title": r.get("title", "No Title"), "snippet": r["body"], "url": r["href"]} for r in results]
18
- except Exception as e:
19
- return [{"title": "Error", "snippet": f"Failed to fetch results: {str(e)}", "url": "#"}]
 
 
 
20
 
 
 
 
21
 
22
- # Format prompt for the AI model (CPU-based) - IMPROVED
23
- def format_prompt(query: str, web_results: list) -> str:
24
- """Create a concise prompt with web context, explicitly instructing citation."""
25
- current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
26
- context = ""
27
- for i, r in enumerate(web_results, 1): # Start index at 1 for citations
28
- context += f"- [{i}] {r['title']}: {r['snippet']}\n"
29
 
30
- return f"""
31
- Time: {current_time}
32
- Query: {query}
33
- Web Context:
34
- {context}
35
- Provide a concise answer in markdown format. Cite relevant sources using the bracketed numbers provided (e.g., [1], [2]). Focus on direct answers. If the context doesn't contain the answer, say that the information wasn't found in the provided sources.
36
- """.strip()
 
 
 
 
37
 
 
 
 
 
 
 
 
 
 
 
38
 
39
- # GPU-decorated answer generation - IMPROVED
40
- @spaces.GPU(duration=120) # Allow up to 120 seconds of GPU time
41
- def generate_answer(prompt: str, web_results: list) -> str:
42
- """Generate and post-process the research answer."""
43
- response = generator(prompt, max_new_tokens=150, num_return_sequences=1, truncation=True, return_full_text=False)[0]["generated_text"]
 
44
 
45
- # Basic post-processing (can be expanded):
46
- response = response.strip()
47
 
48
- # Replace citation placeholders *if* they exist in the web_results.
49
- for i in range(1, len(web_results) + 1):
50
- response = response.replace(f"[{i}]", f"[^{i}^](#{i})") #Markdown link to source
 
 
 
51
 
52
- return response
 
 
 
 
 
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- # Format sources for display (CPU-based) - IMPROVED
56
- def format_sources(web_results: list) -> str:
57
- """Create an HTML list of sources with anchors."""
58
  if not web_results:
59
- return "<div>No sources available</div>"
60
- sources_html = "<div class='sources-list'>"
 
61
  for i, res in enumerate(web_results, 1):
 
 
62
  sources_html += f"""
63
- <div class='source-item' id='{i}'>
64
- <span class='source-number'>[{i}]</span>
65
- <a href='{res['url']}' target='_blank'>{res['title']}</a>: {res['snippet'][:100]}...
 
 
 
 
66
  </div>
67
  """
68
  sources_html += "</div>"
69
  return sources_html
70
 
71
- # Main processing function - IMPROVED
72
- def process_deep_research(query: str, history: list):
73
- """Handle the deep research process, including history updates."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- # Fetch web results (CPU)
76
- web_results = get_web_results(query)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
- # Generate answer (GPU)
79
- prompt = format_prompt(query, web_results)
80
- answer = generate_answer(prompt, web_results)
 
 
 
 
 
81
 
82
- sources_html = format_sources(web_results)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
- # Update history (using the Gradio Chatbot's expected format)
85
- new_history = history + [[query, answer + "\n\n" + sources_html]]
 
86
 
87
- return answer, sources_html, new_history
88
 
 
 
 
 
 
 
 
 
89
 
 
 
 
 
90
 
91
- # Custom CSS - Slightly adjusted for better spacing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  css = """
93
- body {
94
- font-family: 'Arial', sans-serif;
95
- background: #1a1a1a;
96
- color: #ffffff;
97
- }
98
  .gradio-container {
99
- max-width: 900px;
100
- margin: 0 auto;
101
- padding: 15px;
102
  }
103
- .header {
104
  text-align: center;
105
- padding: 15px;
106
- background: linear-gradient(135deg, #2c3e50, #3498db);
107
- border-radius: 8px;
108
- margin-bottom: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  }
110
- .header h1 { font-size: 2em; margin: 0; color: #ffffff; }
111
- .header p { color: #bdc3c7; font-size: 1em; }
112
  .search-box {
113
- background: #2c2c2c;
114
- padding: 10px;
115
  border-radius: 8px;
116
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
117
  }
118
- .search-box input {
119
- background: #3a3a3a !important;
120
- color: #ffffff !important;
121
- border: none !important;
122
- border-radius: 5px !important;
 
 
 
123
  }
124
  .search-box button {
125
- background: #3498db !important;
126
  border: none !important;
127
- border-radius: 5px !important;
128
  }
129
  .results-container {
130
- margin-top: 15px;
131
- display: flex;
132
- flex-direction: column; /* Stack answer and sources vertically */
133
- gap: 15px;
134
  }
135
  .answer-box {
136
- /* flex: 2; Removed flex property */
137
- background: #2c2c2c;
138
- padding: 15px;
139
  border-radius: 8px;
140
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
 
 
 
 
 
 
141
  }
142
- .answer-box .markdown { color: #ecf0f1; line-height: 1.5; }
143
- .sources-list {
144
- /* flex: 1; Removed flex property */
145
- background: #2c2c2c;
146
- padding: 10px;
147
  border-radius: 8px;
148
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
149
- }
150
- .source-item { margin-bottom: 8px; }
151
- .source-number { color: #3498db; font-weight: bold; margin-right: 5px; }
152
- .source-item a { color: #3498db; text-decoration: none; }
153
- .source-item a:hover { text-decoration: underline; }
154
- .history-box {
155
- margin-top: 15px;
156
- background: #2c2c2c;
157
- padding: 10px;
158
  border-radius: 8px;
159
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  }
161
-
162
  """
163
 
164
- # Gradio app setup with Blocks
165
- with gr.Blocks(title="Deep Research Engine - ZeroGPU", css=css) as demo:
166
- # Header
167
- with gr.Column(elem_classes="header"):
168
- gr.Markdown("# Deep Research Engine")
169
- gr.Markdown("Fast, in-depth answers powered by web insights (ZeroGPU).")
170
-
171
- # Search input and button
172
- with gr.Row(elem_classes="search-box"):
173
- search_input = gr.Textbox(label="", placeholder="Ask anything...", lines=2)
174
- search_btn = gr.Button("Research", variant="primary")
175
-
176
- # Results layout - Now using a single Chatbot component
177
- history = gr.Chatbot(elem_classes="history-box", label="Research Results & History")
178
-
179
-
180
- # Event handling - Simplified
181
- def handle_search(query, history_data):
182
- answer, sources, new_history = process_deep_research(query, history_data)
183
- return new_history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
 
185
  search_btn.click(
186
- fn=handle_search,
187
- inputs=[search_input, history],
188
- outputs=[history]
189
  )
 
 
190
  search_input.submit(
191
- fn=handle_search,
192
- inputs=[search_input, history],
193
- outputs=[history]
194
  )
195
 
196
-
197
- # Launch the app
198
  if __name__ == "__main__":
199
- demo.launch()
 
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)