huang_de_jun commited on
Commit
d951f86
·
1 Parent(s): 0a9d06f

feat: move question generation to background thread and increase audio chunk to 20s

Browse files
Files changed (1) hide show
  1. app.py +69 -36
app.py CHANGED
@@ -7,6 +7,7 @@ generate as the talk progresses.
7
 
8
  import os
9
  import tempfile
 
10
  import numpy as np
11
  import gradio as gr
12
  from dotenv import load_dotenv
@@ -20,7 +21,8 @@ load_dotenv()
20
 
21
  # Constants
22
  SAMPLE_RATE = 16000
23
- CHUNK_DURATION = 5 # Process audio every 5 seconds
 
24
  MIN_WORDS_FOR_QUESTIONS = 50 # Start generating questions after this many words
25
  WORDS_BETWEEN_GENERATIONS = 50 # Generate new questions every N new words
26
  MAX_WORDS_CAP = 500 # Stop processing new content after this many words (rate limiting)
@@ -37,10 +39,41 @@ def get_initial_state():
37
  "transcript": "",
38
  "questions_html": format_questions_html([]),
39
  "log_html": format_activity_log([]),
40
- "speaker_name": ""
 
41
  }
42
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  def process_streaming_audio(
45
  audio_chunk,
46
  state: dict
@@ -102,9 +135,11 @@ def process_streaming_audio(
102
  state
103
  )
104
 
105
- # Process the buffered audio
106
  audio_to_process = state["audio_buffer"][:samples_needed]
107
- state["audio_buffer"] = state["audio_buffer"][samples_needed:]
 
 
108
 
109
  try:
110
  # Save to temp file for Whisper
@@ -149,33 +184,31 @@ def process_streaming_audio(
149
  # Check if we should generate questions
150
  should_generate = (
151
  current_word_count >= MIN_WORDS_FOR_QUESTIONS and
152
- words_since_last >= WORDS_BETWEEN_GENERATIONS
 
153
  )
154
 
155
  if should_generate:
156
  state["last_word_count"] = current_word_count
 
157
 
158
- # Generate questions
159
- speaker = state.get("speaker_name", "")
160
- questions, activity_log, questions_html, log_html = generate_questions_sync(
161
- context=ctx,
162
- existing_questions=state.get("questions", []),
163
- activity_log=activity_log,
164
- speaker_name=speaker
165
  )
 
166
 
167
- state["questions"] = questions
168
- state["questions_html"] = questions_html
169
- state["activity_log"] = activity_log
170
- state["log_html"] = log_html
171
-
172
- status = f'<div class="status-bar">✨ Generated {len(questions)} questions | {current_word_count} words</div>'
173
  else:
174
  # Update log HTML
175
  state["log_html"] = format_activity_log(activity_log)
176
 
177
- # Show waiting status if not enough words yet
178
- if current_word_count < MIN_WORDS_FOR_QUESTIONS:
 
 
179
  need_more = MIN_WORDS_FOR_QUESTIONS - current_word_count
180
  status = f'<div class="status-bar">🎤 Listening... {current_word_count} words (need {need_more} more to start generating)</div>'
181
 
@@ -256,23 +289,23 @@ def add_manual_context(text: str, state: dict) -> tuple[str, str, str, str, dict
256
 
257
  status = f'<div class="status-bar">Added text. Total: {current_word_count} words</div>'
258
 
259
- # Generate questions if enough content
260
- if current_word_count >= MIN_WORDS_FOR_QUESTIONS:
261
- speaker = state.get("speaker_name", "")
262
- questions, activity_log, questions_html, log_html = generate_questions_sync(
263
- context=ctx,
264
- existing_questions=state.get("questions", []),
265
- activity_log=activity_log,
266
- speaker_name=speaker
267
- )
268
-
269
- state["questions"] = questions
270
- state["questions_html"] = questions_html
271
- state["activity_log"] = activity_log
272
- state["log_html"] = log_html
273
  state["last_word_count"] = current_word_count
 
 
 
 
 
 
 
 
 
274
 
275
- status = f'<div class="status-bar"> Generated {len(questions)} questions | {current_word_count} words</div>'
 
 
 
276
  else:
277
  state["log_html"] = format_activity_log(activity_log)
278
  need_more = MIN_WORDS_FOR_QUESTIONS - current_word_count
@@ -449,7 +482,7 @@ with gr.Blocks(css=custom_css, title="Ask The Right Question") as demo:
449
  streaming=True,
450
  label="Click to start listening"
451
  )
452
- gr.Markdown("*Audio is processed every 5 seconds*")
453
 
454
  with gr.Tab("Paste Text"):
455
  manual_text = gr.Textbox(
 
7
 
8
  import os
9
  import tempfile
10
+ import threading
11
  import numpy as np
12
  import gradio as gr
13
  from dotenv import load_dotenv
 
21
 
22
  # Constants
23
  SAMPLE_RATE = 16000
24
+ CHUNK_DURATION = 20 # Process audio every 20 seconds (longer = better context for Whisper)
25
+ OVERLAP_DURATION = 2 # Keep 2 seconds overlap between chunks to prevent word cutoffs
26
  MIN_WORDS_FOR_QUESTIONS = 50 # Start generating questions after this many words
27
  WORDS_BETWEEN_GENERATIONS = 50 # Generate new questions every N new words
28
  MAX_WORDS_CAP = 500 # Stop processing new content after this many words (rate limiting)
 
39
  "transcript": "",
40
  "questions_html": format_questions_html([]),
41
  "log_html": format_activity_log([]),
42
+ "speaker_name": "",
43
+ "generation_in_progress": False # Track if background generation is running
44
  }
45
 
46
 
47
+ def generate_questions_background(state: dict):
48
+ """
49
+ Generate questions in background thread.
50
+ Updates state dict directly (shared reference).
51
+ """
52
+ try:
53
+ ctx = state["context"]
54
+ speaker = state.get("speaker_name", "")
55
+ activity_log = state.get("activity_log", [])
56
+
57
+ questions, activity_log, questions_html, log_html = generate_questions_sync(
58
+ context=ctx,
59
+ existing_questions=state.get("questions", []),
60
+ activity_log=activity_log,
61
+ speaker_name=speaker
62
+ )
63
+
64
+ # Update state with results
65
+ state["questions"] = questions
66
+ state["questions_html"] = questions_html
67
+ state["activity_log"] = activity_log
68
+ state["log_html"] = log_html
69
+
70
+ except Exception as e:
71
+ state["activity_log"].append({"type": "error", "msg": str(e)})
72
+ state["log_html"] = format_activity_log(state["activity_log"])
73
+ finally:
74
+ state["generation_in_progress"] = False
75
+
76
+
77
  def process_streaming_audio(
78
  audio_chunk,
79
  state: dict
 
135
  state
136
  )
137
 
138
+ # Process the buffered audio with overlap
139
  audio_to_process = state["audio_buffer"][:samples_needed]
140
+ # Keep last OVERLAP_DURATION seconds for next chunk to prevent word cutoffs
141
+ overlap_samples = sample_rate * OVERLAP_DURATION
142
+ state["audio_buffer"] = state["audio_buffer"][samples_needed - overlap_samples:]
143
 
144
  try:
145
  # Save to temp file for Whisper
 
184
  # Check if we should generate questions
185
  should_generate = (
186
  current_word_count >= MIN_WORDS_FOR_QUESTIONS and
187
+ words_since_last >= WORDS_BETWEEN_GENERATIONS and
188
+ not state.get("generation_in_progress", False) # Don't start if already generating
189
  )
190
 
191
  if should_generate:
192
  state["last_word_count"] = current_word_count
193
+ state["generation_in_progress"] = True
194
 
195
+ # Start background thread for question generation (non-blocking)
196
+ thread = threading.Thread(
197
+ target=generate_questions_background,
198
+ args=(state,),
199
+ daemon=True
 
 
200
  )
201
+ thread.start()
202
 
203
+ status = f'<div class="status-bar">🎤 Listening... {current_word_count} words | ⚙️ Generating questions...</div>'
 
 
 
 
 
204
  else:
205
  # Update log HTML
206
  state["log_html"] = format_activity_log(activity_log)
207
 
208
+ # Show appropriate status
209
+ if state.get("generation_in_progress", False):
210
+ status = f'<div class="status-bar">🎤 Listening... {current_word_count} words | ⚙️ Generating questions...</div>'
211
+ elif current_word_count < MIN_WORDS_FOR_QUESTIONS:
212
  need_more = MIN_WORDS_FOR_QUESTIONS - current_word_count
213
  status = f'<div class="status-bar">🎤 Listening... {current_word_count} words (need {need_more} more to start generating)</div>'
214
 
 
289
 
290
  status = f'<div class="status-bar">Added text. Total: {current_word_count} words</div>'
291
 
292
+ # Generate questions if enough content and not already generating
293
+ if current_word_count >= MIN_WORDS_FOR_QUESTIONS and not state.get("generation_in_progress", False):
 
 
 
 
 
 
 
 
 
 
 
 
294
  state["last_word_count"] = current_word_count
295
+ state["generation_in_progress"] = True
296
+
297
+ # Start background thread for question generation (non-blocking)
298
+ thread = threading.Thread(
299
+ target=generate_questions_background,
300
+ args=(state,),
301
+ daemon=True
302
+ )
303
+ thread.start()
304
 
305
+ status = f'<div class="status-bar">Added text. Total: {current_word_count} words | ⚙️ Generating questions...</div>'
306
+ elif state.get("generation_in_progress", False):
307
+ state["log_html"] = format_activity_log(activity_log)
308
+ status = f'<div class="status-bar">Added text. Total: {current_word_count} words | ⚙️ Already generating...</div>'
309
  else:
310
  state["log_html"] = format_activity_log(activity_log)
311
  need_more = MIN_WORDS_FOR_QUESTIONS - current_word_count
 
482
  streaming=True,
483
  label="Click to start listening"
484
  )
485
+ gr.Markdown("*Audio is processed every 20 seconds*")
486
 
487
  with gr.Tab("Paste Text"):
488
  manual_text = gr.Textbox(