ginipick commited on
Commit
ad59ac8
·
verified ·
1 Parent(s): 8366798

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -137
app.py CHANGED
@@ -1,43 +1,51 @@
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
 
12
- # Install required dependencies for Kokoro with better error handling
13
  try:
14
  subprocess.run(['git', 'lfs', 'install'], check=True)
15
  if not os.path.exists('Kokoro-82M'):
16
  subprocess.run(['git', 'clone', 'https://huggingface.co/hexgrad/Kokoro-82M'], check=True)
17
 
18
- # Try installing espeak with proper package manager commands
19
  try:
20
- # Update package list first
21
  subprocess.run(['apt-get', 'update'], check=True)
22
- # Try installing espeak first (more widely available)
23
  subprocess.run(['apt-get', 'install', '-y', 'espeak'], check=True)
24
  except subprocess.CalledProcessError:
25
- print("Warning: Could not install espeak. Attempting espeak-ng...")
26
  try:
27
  subprocess.run(['apt-get', 'install', '-y', 'espeak-ng'], check=True)
28
  except subprocess.CalledProcessError:
29
  print("Warning: Could not install espeak or espeak-ng. TTS functionality may be limited.")
30
-
31
  except Exception as e:
32
  print(f"Warning: Initial setup error: {str(e)}")
33
  print("Continuing with limited functionality...")
34
 
35
- # Initialize models and tokenizers
 
 
 
 
 
 
 
 
 
 
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
 
40
- # Move model initialization inside a function to prevent CUDA initialization in main process
41
  def init_models():
42
  model = AutoModelForCausalLM.from_pretrained(
43
  model_name,
@@ -48,27 +56,20 @@ def init_models():
48
  )
49
  return model
50
 
51
- # Initialize Kokoro TTS with better error handling
52
  try:
53
  import sys
54
  sys.path.append('Kokoro-82M')
55
  from models import build_model
56
  from kokoro import generate
57
 
58
- # Don't initialize models/voices in main process for ZeroGPU compatibility
59
- VOICE_CHOICES = {
60
- '🇺🇸 Female (Default)': 'af',
61
- '🇺🇸 Bella': 'af_bella',
62
- '🇺🇸 Sarah': 'af_sarah',
63
- '🇺🇸 Nicole': 'af_nicole'
64
- }
65
  TTS_ENABLED = True
66
  except Exception as e:
67
  print(f"Warning: Could not initialize Kokoro TTS: {str(e)}")
68
  TTS_ENABLED = False
69
 
70
- def get_web_results(query, max_results=5): # Increased to 5 for better context
71
- """Get web search results using DuckDuckGo"""
72
  try:
73
  with DDGS() as ddgs:
74
  results = list(ddgs.text(query, max_results=max_results))
@@ -82,7 +83,6 @@ def get_web_results(query, max_results=5): # Increased to 5 for better context
82
  return []
83
 
84
  def format_prompt(query, context):
85
- """Format the prompt with web context"""
86
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
87
  context_lines = '\n'.join([f'- [{res["title"]}]: {res["snippet"]}' for res in context])
88
  return f"""You are an intelligent search assistant. Answer the user's query using the provided web context.
@@ -99,7 +99,6 @@ Provide a detailed answer in markdown format. Include relevant information from
99
  Answer:"""
100
 
101
  def format_sources(web_results):
102
- """Format sources with more details"""
103
  if not web_results:
104
  return "<div class='no-sources'>No sources available</div>"
105
 
@@ -120,11 +119,9 @@ def format_sources(web_results):
120
  sources_html += "</div>"
121
  return sources_html
122
 
123
- # Wrap the answer generation with spaces.GPU decorator
124
  @spaces.GPU(duration=30)
125
  def generate_answer(prompt):
126
- """Generate answer using the DeepSeek model"""
127
- # Initialize model inside the GPU-decorated function
128
  model = init_models()
129
 
130
  inputs = tokenizer(
@@ -148,28 +145,21 @@ def generate_answer(prompt):
148
  )
149
  return tokenizer.decode(outputs[0], skip_special_tokens=True)
150
 
151
- # Similarly wrap TTS generation with spaces.GPU
152
  @spaces.GPU(duration=60)
153
  def generate_speech_with_gpu(text, voice_name='af'):
154
- """Generate speech from text using Kokoro TTS model with GPU handling"""
155
  try:
156
- # Initialize TTS model and voice inside GPU function
157
  device = 'cuda'
158
  TTS_MODEL = build_model('Kokoro-82M/kokoro-v0_19.pth', device)
159
  VOICEPACK = torch.load(f'Kokoro-82M/voices/{voice_name}.pt', weights_only=True).to(device)
160
 
161
- # Clean the text
162
  clean_text = ' '.join([line for line in text.split('\n') if not line.startswith('#')])
163
  clean_text = clean_text.replace('[', '').replace(']', '').replace('*', '')
164
 
165
- # Split long text into chunks
166
  max_chars = 1000
167
- chunks = []
168
-
169
  if len(clean_text) > max_chars:
170
  sentences = clean_text.split('.')
 
171
  current_chunk = ""
172
-
173
  for sentence in sentences:
174
  if len(current_chunk) + len(sentence) < max_chars:
175
  current_chunk += sentence + "."
@@ -182,21 +172,16 @@ def generate_speech_with_gpu(text, voice_name='af'):
182
  else:
183
  chunks = [clean_text]
184
 
185
- # Generate audio for each chunk
186
  audio_chunks = []
187
  for chunk in chunks:
188
- if chunk.strip(): # Only process non-empty chunks
189
  chunk_audio, _ = generate(TTS_MODEL, chunk.strip(), VOICEPACK, lang='a')
190
  if isinstance(chunk_audio, torch.Tensor):
191
  chunk_audio = chunk_audio.cpu().numpy()
192
  audio_chunks.append(chunk_audio)
193
 
194
- # Concatenate chunks if we have any
195
  if audio_chunks:
196
- if len(audio_chunks) > 1:
197
- final_audio = np.concatenate(audio_chunks)
198
- else:
199
- final_audio = audio_chunks[0]
200
  return (24000, final_audio)
201
  return None
202
 
@@ -207,12 +192,10 @@ def generate_speech_with_gpu(text, voice_name='af'):
207
  return None
208
 
209
  def process_query(query, history, selected_voice='af'):
210
- """Process user query with streaming effect"""
211
  try:
212
  if history is None:
213
  history = []
214
 
215
- # Get web results first
216
  web_results = get_web_results(query)
217
  sources_html = format_sources(web_results)
218
 
@@ -225,12 +208,10 @@ def process_query(query, history, selected_voice='af'):
225
  audio_output: None
226
  }
227
 
228
- # Generate answer
229
- prompt = format_prompt(query, web_results)
230
- answer = generate_answer(prompt)
231
  final_answer = answer.split("Answer:")[-1].strip()
232
 
233
- # Generate speech from the answer
234
  if TTS_ENABLED:
235
  try:
236
  yield {
@@ -240,10 +221,7 @@ def process_query(query, history, selected_voice='af'):
240
  chat_history_display: history + [[query, final_answer]],
241
  audio_output: None
242
  }
243
-
244
  audio = generate_speech_with_gpu(final_answer, selected_voice)
245
- if audio is None:
246
- print("Failed to generate audio")
247
  except Exception as e:
248
  print(f"Error in speech generation: {str(e)}")
249
  audio = None
@@ -271,89 +249,82 @@ def process_query(query, history, selected_voice='af'):
271
  audio_output: None
272
  }
273
 
274
- # Update the CSS for better contrast and readability
275
  css = """
276
  .gradio-container {
277
  max-width: 1200px !important;
278
- background-color: #f7f7f8 !important;
 
 
279
  }
280
 
281
  #header {
282
  text-align: center;
283
- margin-bottom: 2rem;
284
  padding: 2rem 0;
285
- background: #1a1b1e;
286
  border-radius: 12px;
287
- color: white;
 
288
  }
289
 
290
  #header h1 {
291
- color: white;
292
  font-size: 2.5rem;
293
  margin-bottom: 0.5rem;
294
  }
295
 
296
- #header h3 {
297
- color: #a8a9ab;
298
- }
299
-
300
  .search-container {
301
- background: #1a1b1e;
302
  border-radius: 12px;
303
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
304
- padding: 1rem;
305
  margin-bottom: 1rem;
306
  }
307
 
308
  .search-box {
309
  padding: 1rem;
310
- background: #2c2d30;
311
  border-radius: 8px;
312
  margin-bottom: 1rem;
313
  }
314
 
315
- /* Style the input textbox */
316
  .search-box input[type="text"] {
317
- background: #3a3b3e !important;
318
- border: 1px solid #4a4b4e !important;
319
- color: white !important;
320
  border-radius: 8px !important;
321
  }
322
 
323
  .search-box input[type="text"]::placeholder {
324
- color: #a8a9ab !important;
325
  }
326
 
327
- /* Style the search button */
328
  .search-box button {
329
  background: #2563eb !important;
330
  border: none !important;
331
  }
332
 
333
- /* Results area styling */
334
  .results-container {
335
- background: #2c2d30;
336
  border-radius: 8px;
337
- padding: 1rem;
338
  margin-top: 1rem;
339
  }
340
 
341
  .answer-box {
342
- background: #3a3b3e;
343
  border-radius: 8px;
344
  padding: 1.5rem;
345
- color: white;
346
  margin-bottom: 1rem;
347
  }
348
 
349
  .answer-box p {
350
- color: #e5e7eb;
351
  line-height: 1.6;
352
  }
353
 
354
  .sources-container {
355
  margin-top: 1rem;
356
- background: #2c2d30;
357
  border-radius: 8px;
358
  padding: 1rem;
359
  }
@@ -362,13 +333,13 @@ css = """
362
  display: flex;
363
  padding: 12px;
364
  margin: 8px 0;
365
- background: #3a3b3e;
366
  border-radius: 8px;
367
  transition: all 0.2s;
368
  }
369
 
370
  .source-item:hover {
371
- background: #4a4b4e;
372
  }
373
 
374
  .source-number {
@@ -390,13 +361,13 @@ css = """
390
  }
391
 
392
  .source-date {
393
- color: #a8a9ab;
394
  font-size: 0.9em;
395
  margin-left: 8px;
396
  }
397
 
398
  .source-snippet {
399
- color: #e5e7eb;
400
  font-size: 0.9em;
401
  line-height: 1.4;
402
  }
@@ -405,63 +376,36 @@ css = """
405
  max-height: 400px;
406
  overflow-y: auto;
407
  padding: 1rem;
408
- background: #2c2d30;
409
- border-radius: 8px;
410
- margin-top: 1rem;
411
- }
412
-
413
- .examples-container {
414
- background: #2c2d30;
415
  border-radius: 8px;
416
- padding: 1rem;
417
  margin-top: 1rem;
418
  }
419
 
420
- .examples-container button {
421
- background: #3a3b3e !important;
422
- border: 1px solid #4a4b4e !important;
423
- color: #e5e7eb !important;
424
- }
425
-
426
- /* Markdown content styling */
427
- .markdown-content {
428
- color: #e5e7eb !important;
429
- }
430
-
431
- .markdown-content h1, .markdown-content h2, .markdown-content h3 {
432
- color: white !important;
433
- }
434
-
435
- .markdown-content a {
436
- color: #60a5fa !important;
437
- }
438
-
439
- /* Accordion styling */
440
- .accordion {
441
- background: #2c2d30 !important;
442
- border-radius: 8px !important;
443
- margin-top: 1rem !important;
444
- }
445
-
446
  .voice-selector {
447
  margin-top: 1rem;
448
- background: #2c2d30;
449
  border-radius: 8px;
450
  padding: 0.5rem;
451
  }
452
 
453
  .voice-selector select {
454
- background: #3a3b3e !important;
455
- color: white !important;
456
- border: 1px solid #4a4b4e !important;
 
 
 
 
 
 
457
  }
458
  """
459
 
460
- # Update the Gradio interface layout
461
- with gr.Blocks(title="AI Search Assistant", css=css, theme="dark") as demo:
462
  chat_history = gr.State([])
463
 
464
- with gr.Column(elem_id="header"):
465
  gr.Markdown("# 🔍 AI Search Assistant")
466
  gr.Markdown("### Powered by DeepSeek & Real-time Web Results with Voice")
467
 
@@ -484,17 +428,16 @@ with gr.Blocks(title="AI Search Assistant", css=css, theme="dark") as demo:
484
  with gr.Row(elem_classes="results-container"):
485
  with gr.Column(scale=2):
486
  with gr.Column(elem_classes="answer-box"):
487
- answer_output = gr.Markdown(elem_classes="markdown-content")
488
- with gr.Row():
489
- audio_output = gr.Audio(label="Voice Response", elem_classes="audio-player")
490
- with gr.Accordion("Chat History", open=False, elem_classes="accordion"):
491
  chat_history_display = gr.Chatbot(elem_classes="chat-history")
492
  with gr.Column(scale=1):
493
- with gr.Column(elem_classes="sources-box"):
494
  gr.Markdown("### Sources")
495
  sources_output = gr.HTML()
496
 
497
- with gr.Row(elem_classes="examples-container"):
498
  gr.Examples(
499
  examples=[
500
  "musk explores blockchain for doge",
@@ -505,15 +448,12 @@ with gr.Blocks(title="AI Search Assistant", css=css, theme="dark") as demo:
505
  inputs=search_input,
506
  label="Try these examples"
507
  )
508
-
509
- # Handle interactions
510
  search_btn.click(
511
  fn=process_query,
512
  inputs=[search_input, chat_history, voice_select],
513
  outputs=[answer_output, sources_output, search_btn, chat_history_display, audio_output]
514
  )
515
-
516
- # Also trigger search on Enter key
517
  search_input.submit(
518
  fn=process_query,
519
  inputs=[search_input, chat_history, voice_select],
@@ -521,4 +461,4 @@ with gr.Blocks(title="AI Search Assistant", css=css, theme="dark") as demo:
521
  )
522
 
523
  if __name__ == "__main__":
524
- demo.launch(share=True)
 
1
+ import subprocess # 🥲
2
+ import os
 
 
3
  import time
4
  import torch
 
 
 
5
  import numpy as np
6
+ import gradio as gr
7
+ import spaces
8
+ import re
9
+ import json
10
+ from datetime import datetime
11
+ from transformers import AutoModelForCausalLM, AutoTokenizer
12
+ from duckduckgo_search import DDGS
13
+ from pydantic import BaseModel
14
 
15
+ # ----------------------- Setup & Dependency Installation ----------------------- #
16
  try:
17
  subprocess.run(['git', 'lfs', 'install'], check=True)
18
  if not os.path.exists('Kokoro-82M'):
19
  subprocess.run(['git', 'clone', 'https://huggingface.co/hexgrad/Kokoro-82M'], check=True)
20
 
 
21
  try:
 
22
  subprocess.run(['apt-get', 'update'], check=True)
 
23
  subprocess.run(['apt-get', 'install', '-y', 'espeak'], check=True)
24
  except subprocess.CalledProcessError:
25
+ print("Warning: Could not install espeak. Trying espeak-ng...")
26
  try:
27
  subprocess.run(['apt-get', 'install', '-y', 'espeak-ng'], check=True)
28
  except subprocess.CalledProcessError:
29
  print("Warning: Could not install espeak or espeak-ng. TTS functionality may be limited.")
 
30
  except Exception as e:
31
  print(f"Warning: Initial setup error: {str(e)}")
32
  print("Continuing with limited functionality...")
33
 
34
+ # ----------------------- Global Variables ----------------------- #
35
+ # VOICE_CHOICES 정의 (TTS가 초기화되지 않더라도 기본값 제공)
36
+ VOICE_CHOICES = {
37
+ '🇺🇸 Female (Default)': 'af',
38
+ '🇺🇸 Bella': 'af_bella',
39
+ '🇺🇸 Sarah': 'af_sarah',
40
+ '🇺🇸 Nicole': 'af_nicole'
41
+ }
42
+ TTS_ENABLED = False # 초기 TTS 모듈 불러오기 실패 시 기본적으로 비활성화
43
+
44
+ # ----------------------- Model and Tokenizer Initialization ----------------------- #
45
  model_name = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
46
  tokenizer = AutoTokenizer.from_pretrained(model_name)
47
  tokenizer.pad_token = tokenizer.eos_token
48
 
 
49
  def init_models():
50
  model = AutoModelForCausalLM.from_pretrained(
51
  model_name,
 
56
  )
57
  return model
58
 
59
+ # ----------------------- Kokoro TTS Initialization ----------------------- #
60
  try:
61
  import sys
62
  sys.path.append('Kokoro-82M')
63
  from models import build_model
64
  from kokoro import generate
65
 
 
 
 
 
 
 
 
66
  TTS_ENABLED = True
67
  except Exception as e:
68
  print(f"Warning: Could not initialize Kokoro TTS: {str(e)}")
69
  TTS_ENABLED = False
70
 
71
+ # ----------------------- Web Search Functions ----------------------- #
72
+ def get_web_results(query, max_results=5):
73
  try:
74
  with DDGS() as ddgs:
75
  results = list(ddgs.text(query, max_results=max_results))
 
83
  return []
84
 
85
  def format_prompt(query, context):
 
86
  current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
87
  context_lines = '\n'.join([f'- [{res["title"]}]: {res["snippet"]}' for res in context])
88
  return f"""You are an intelligent search assistant. Answer the user's query using the provided web context.
 
99
  Answer:"""
100
 
101
  def format_sources(web_results):
 
102
  if not web_results:
103
  return "<div class='no-sources'>No sources available</div>"
104
 
 
119
  sources_html += "</div>"
120
  return sources_html
121
 
122
+ # ----------------------- Answer Generation ----------------------- #
123
  @spaces.GPU(duration=30)
124
  def generate_answer(prompt):
 
 
125
  model = init_models()
126
 
127
  inputs = tokenizer(
 
145
  )
146
  return tokenizer.decode(outputs[0], skip_special_tokens=True)
147
 
 
148
  @spaces.GPU(duration=60)
149
  def generate_speech_with_gpu(text, voice_name='af'):
 
150
  try:
 
151
  device = 'cuda'
152
  TTS_MODEL = build_model('Kokoro-82M/kokoro-v0_19.pth', device)
153
  VOICEPACK = torch.load(f'Kokoro-82M/voices/{voice_name}.pt', weights_only=True).to(device)
154
 
 
155
  clean_text = ' '.join([line for line in text.split('\n') if not line.startswith('#')])
156
  clean_text = clean_text.replace('[', '').replace(']', '').replace('*', '')
157
 
 
158
  max_chars = 1000
 
 
159
  if len(clean_text) > max_chars:
160
  sentences = clean_text.split('.')
161
+ chunks = []
162
  current_chunk = ""
 
163
  for sentence in sentences:
164
  if len(current_chunk) + len(sentence) < max_chars:
165
  current_chunk += sentence + "."
 
172
  else:
173
  chunks = [clean_text]
174
 
 
175
  audio_chunks = []
176
  for chunk in chunks:
177
+ if chunk.strip():
178
  chunk_audio, _ = generate(TTS_MODEL, chunk.strip(), VOICEPACK, lang='a')
179
  if isinstance(chunk_audio, torch.Tensor):
180
  chunk_audio = chunk_audio.cpu().numpy()
181
  audio_chunks.append(chunk_audio)
182
 
 
183
  if audio_chunks:
184
+ final_audio = np.concatenate(audio_chunks) if len(audio_chunks) > 1 else audio_chunks[0]
 
 
 
185
  return (24000, final_audio)
186
  return None
187
 
 
192
  return None
193
 
194
  def process_query(query, history, selected_voice='af'):
 
195
  try:
196
  if history is None:
197
  history = []
198
 
 
199
  web_results = get_web_results(query)
200
  sources_html = format_sources(web_results)
201
 
 
208
  audio_output: None
209
  }
210
 
211
+ prompt_text = format_prompt(query, web_results)
212
+ answer = generate_answer(prompt_text)
 
213
  final_answer = answer.split("Answer:")[-1].strip()
214
 
 
215
  if TTS_ENABLED:
216
  try:
217
  yield {
 
221
  chat_history_display: history + [[query, final_answer]],
222
  audio_output: None
223
  }
 
224
  audio = generate_speech_with_gpu(final_answer, selected_voice)
 
 
225
  except Exception as e:
226
  print(f"Error in speech generation: {str(e)}")
227
  audio = None
 
249
  audio_output: None
250
  }
251
 
252
+ # ----------------------- Custom CSS for Improved UI ----------------------- #
253
  css = """
254
  .gradio-container {
255
  max-width: 1200px !important;
256
+ background-color: #1e1e1e !important;
257
+ padding: 20px;
258
+ border-radius: 12px;
259
  }
260
 
261
  #header {
262
  text-align: center;
 
263
  padding: 2rem 0;
264
+ background: #272727;
265
  border-radius: 12px;
266
+ color: #ffffff;
267
+ margin-bottom: 2rem;
268
  }
269
 
270
  #header h1 {
 
271
  font-size: 2.5rem;
272
  margin-bottom: 0.5rem;
273
  }
274
 
 
 
 
 
275
  .search-container {
276
+ background: #272727;
277
  border-radius: 12px;
278
+ padding: 1.5rem;
 
279
  margin-bottom: 1rem;
280
  }
281
 
282
  .search-box {
283
  padding: 1rem;
284
+ background: #333333;
285
  border-radius: 8px;
286
  margin-bottom: 1rem;
287
  }
288
 
 
289
  .search-box input[type="text"] {
290
+ background: #444444 !important;
291
+ border: 1px solid #555555 !important;
292
+ color: #ffffff !important;
293
  border-radius: 8px !important;
294
  }
295
 
296
  .search-box input[type="text"]::placeholder {
297
+ color: #bbbbbb !important;
298
  }
299
 
 
300
  .search-box button {
301
  background: #2563eb !important;
302
  border: none !important;
303
  }
304
 
 
305
  .results-container {
306
+ background: #2c2c2c;
307
  border-radius: 8px;
308
+ padding: 1.5rem;
309
  margin-top: 1rem;
310
  }
311
 
312
  .answer-box {
313
+ background: #3a3a3a;
314
  border-radius: 8px;
315
  padding: 1.5rem;
316
+ color: #ffffff;
317
  margin-bottom: 1rem;
318
  }
319
 
320
  .answer-box p {
321
+ color: #e0e0e0;
322
  line-height: 1.6;
323
  }
324
 
325
  .sources-container {
326
  margin-top: 1rem;
327
+ background: #2c2c2c;
328
  border-radius: 8px;
329
  padding: 1rem;
330
  }
 
333
  display: flex;
334
  padding: 12px;
335
  margin: 8px 0;
336
+ background: #3a3a3a;
337
  border-radius: 8px;
338
  transition: all 0.2s;
339
  }
340
 
341
  .source-item:hover {
342
+ background: #4a4a4a;
343
  }
344
 
345
  .source-number {
 
361
  }
362
 
363
  .source-date {
364
+ color: #bbbbbb;
365
  font-size: 0.9em;
366
  margin-left: 8px;
367
  }
368
 
369
  .source-snippet {
370
+ color: #e0e0e0;
371
  font-size: 0.9em;
372
  line-height: 1.4;
373
  }
 
376
  max-height: 400px;
377
  overflow-y: auto;
378
  padding: 1rem;
379
+ background: #2c2c2c;
 
 
 
 
 
 
380
  border-radius: 8px;
 
381
  margin-top: 1rem;
382
  }
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  .voice-selector {
385
  margin-top: 1rem;
386
+ background: #333333;
387
  border-radius: 8px;
388
  padding: 0.5rem;
389
  }
390
 
391
  .voice-selector select {
392
+ background: #444444 !important;
393
+ color: #ffffff !important;
394
+ border: 1px solid #555555 !important;
395
+ }
396
+ footer {
397
+ text-align: center;
398
+ padding: 1rem 0;
399
+ font-size: 0.9em;
400
+ color: #bbbbbb;
401
  }
402
  """
403
 
404
+ # ----------------------- Gradio Interface ----------------------- #
405
+ with gr.Blocks(title="AI Search Assistant", css=css) as demo:
406
  chat_history = gr.State([])
407
 
408
+ with gr.Column(id="header"):
409
  gr.Markdown("# 🔍 AI Search Assistant")
410
  gr.Markdown("### Powered by DeepSeek & Real-time Web Results with Voice")
411
 
 
428
  with gr.Row(elem_classes="results-container"):
429
  with gr.Column(scale=2):
430
  with gr.Column(elem_classes="answer-box"):
431
+ answer_output = gr.Markdown()
432
+ audio_output = gr.Audio(label="Voice Response")
433
+ with gr.Accordion("Chat History", open=False):
 
434
  chat_history_display = gr.Chatbot(elem_classes="chat-history")
435
  with gr.Column(scale=1):
436
+ with gr.Column():
437
  gr.Markdown("### Sources")
438
  sources_output = gr.HTML()
439
 
440
+ with gr.Row():
441
  gr.Examples(
442
  examples=[
443
  "musk explores blockchain for doge",
 
448
  inputs=search_input,
449
  label="Try these examples"
450
  )
451
+
 
452
  search_btn.click(
453
  fn=process_query,
454
  inputs=[search_input, chat_history, voice_select],
455
  outputs=[answer_output, sources_output, search_btn, chat_history_display, audio_output]
456
  )
 
 
457
  search_input.submit(
458
  fn=process_query,
459
  inputs=[search_input, chat_history, voice_select],
 
461
  )
462
 
463
  if __name__ == "__main__":
464
+ demo.launch(share=True)