noumanjavaid commited on
Commit
a7e7850
·
verified ·
1 Parent(s): 6a0c907

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +72 -77
src/streamlit_app.py CHANGED
@@ -6,7 +6,7 @@ import base64
6
  import io
7
  import threading
8
  import traceback
9
- import atexit # Correctly imported
10
  import time
11
  import logging
12
  from dotenv import load_dotenv
@@ -30,6 +30,7 @@ from streamlit_webrtc import (
30
  # --- Configuration ---
31
  load_dotenv()
32
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(threadName)s - %(levelname)s - %(message)s')
 
33
 
34
  # Audio configuration
35
  PYAUDIO_FORMAT = pyaudio.paInt16
@@ -46,7 +47,9 @@ VIDEO_FPS_TO_GEMINI = 2
46
  VIDEO_API_RESIZE = (1024, 1024)
47
 
48
  # !!! IMPORTANT: Verify this model name is correct for the Live API !!!
 
49
  MODEL_NAME = "models/gemini-2.0-flash-live-001"
 
50
 
51
  MEDICAL_ASSISTANT_SYSTEM_PROMPT = """You are an AI Medical Assistant. Your primary function is to analyze visual information from the user's camera or screen and respond via voice.
52
 
@@ -65,12 +68,17 @@ Example of a disclaimer you might use: "As an AI assistant, I can describe what
65
  """
66
 
67
  # --- PyAudio Global Instance and Cleanup ---
68
- pya = pyaudio.PyAudio()
69
- def cleanup_pyaudio():
70
- logging.info("Terminating PyAudio instance.")
71
- if pya:
72
- pya.terminate()
73
- atexit.register(cleanup_pyaudio)
 
 
 
 
 
74
 
75
  # --- Global Queues - Declare as None, initialize later ---
76
  video_frames_to_gemini_q: asyncio.Queue = None
@@ -83,6 +91,7 @@ client = None
83
  if GEMINI_API_KEY:
84
  try:
85
  client = genai.Client(http_options={"api_version": "v1beta"}, api_key=GEMINI_API_KEY)
 
86
  except Exception as e:
87
  st.error(f"Failed to initialize Gemini client: {e}")
88
  logging.critical(f"Gemini client initialization failed: {e}", exc_info=True)
@@ -92,16 +101,14 @@ else:
92
  logging.critical("GEMINI_API_KEY not found.")
93
  st.stop()
94
 
95
- # Gemini LiveConnectConfig - Simplified for debugging ConnectionClosedError
96
- # Removed speech_config temporarily. If connection works now, the issue was voice config.
97
- # If it still fails, check MODEL_NAME and API Key permissions.
98
  LIVE_CONNECT_CONFIG = types.LiveConnectConfig(
99
- response_modalities=["audio", "text"],
100
- # speech_config=types.SpeechConfig( # Temporarily commented out for debugging
101
- # voice_config=types.VoiceConfig(prebuilt_voice_config=types.PrebuiltVoiceConfig(voice_name="Zephyr"))
102
- # ),
103
  )
104
- logging.info(f"Using LiveConnectConfig: {LIVE_CONNECT_CONFIG}") # Log the config being used
105
 
106
  # --- Backend Gemini Interaction Loop ---
107
  class GeminiInteractionLoop:
@@ -128,11 +135,13 @@ class GeminiInteractionLoop:
128
  await asyncio.sleep(0.1); return None
129
  try:
130
  video_frame = await asyncio.wait_for(video_frames_to_gemini_q.get(), timeout=0.02)
 
131
  video_frames_to_gemini_q.task_done(); return video_frame
132
  except asyncio.TimeoutError: pass
133
  except Exception as e: logging.error(f"Error getting video from queue: {e}", exc_info=True)
134
  try:
135
  audio_chunk = await asyncio.wait_for(audio_chunks_to_gemini_q.get(), timeout=0.02)
 
136
  audio_chunks_to_gemini_q.task_done(); return audio_chunk
137
  except asyncio.TimeoutError: return None
138
  except Exception as e: logging.error(f"Error getting audio from queue: {e}", exc_info=True); return None
@@ -140,10 +149,11 @@ class GeminiInteractionLoop:
140
  while self.is_running:
141
  if not self.gemini_session: await asyncio.sleep(0.1); continue
142
  media_data = await get_media_from_queues()
 
143
  if media_data and self.gemini_session and self.is_running:
144
  try: await self.gemini_session.send(input=media_data)
145
  except Exception as e: logging.error(f"Error sending media chunk to Gemini: {e}", exc_info=True)
146
- elif not media_data: await asyncio.sleep(0.05)
147
  except asyncio.CancelledError: logging.info("Task cancelled: stream_media_to_gemini.")
148
  finally: logging.info("Task finished: stream_media_to_gemini.")
149
 
@@ -163,8 +173,8 @@ class GeminiInteractionLoop:
163
  if text_response := chunk.text:
164
  logging.info(f"Gemini text response: {text_response[:100]}")
165
  if 'chat_messages' not in st.session_state: st.session_state.chat_messages = []
166
- # This direct update might cause issues if multiple updates happen before rerun
167
  st.session_state.chat_messages = st.session_state.chat_messages + [{"role": "assistant", "content": text_response}]
 
168
  except types.generation_types.StopCandidateException: logging.info("Gemini response stream ended normally.")
169
  except Exception as e:
170
  if self.is_running: logging.error(f"Error receiving from Gemini: {e}", exc_info=True)
@@ -174,8 +184,10 @@ class GeminiInteractionLoop:
174
 
175
  async def play_gemini_audio(self):
176
  logging.info("Task started: Play Gemini audio responses.")
177
- # Note: This task might fail in environments without proper ALSA setup (like HF Spaces default)
178
- # Error handling below will log the error but allow other tasks to continue.
 
 
179
  try:
180
  while audio_from_gemini_playback_q is None and self.is_running: await asyncio.sleep(0.1)
181
  if not self.is_running: return
@@ -187,32 +199,33 @@ class GeminiInteractionLoop:
187
  while self.is_running:
188
  try:
189
  audio_chunk = await asyncio.wait_for(audio_from_gemini_playback_q.get(), timeout=1.0)
 
190
  if audio_chunk: await asyncio.to_thread(self.playback_stream.write, audio_chunk)
191
  if audio_chunk: audio_from_gemini_playback_q.task_done()
192
  except asyncio.TimeoutError: continue
193
  except Exception as e: logging.error(f"Error playing audio chunk: {e}", exc_info=True); await asyncio.sleep(0.01)
194
  except Exception as e:
195
- logging.error(f"Failed to open or use PyAudio playback stream: {e}", exc_info=True)
196
- # Don't set self.is_running = False here, just log the playback failure
197
  finally:
198
  if self.playback_stream:
199
  logging.info("Stopping and closing PyAudio playback stream.")
200
  try:
201
  await asyncio.to_thread(self.playback_stream.stop_stream)
202
  await asyncio.to_thread(self.playback_stream.close)
203
- except Exception as e_close:
204
- logging.error(f"Error closing playback stream: {e_close}", exc_info=True)
205
  self.playback_stream = None
206
  logging.info("Task finished: play_gemini_audio.")
207
 
208
  def signal_stop(self):
209
  logging.info("Signal to stop GeminiInteractionLoop received.")
210
  self.is_running = False
211
- for q in [video_frames_to_gemini_q, audio_chunks_to_gemini_q, audio_from_gemini_playback_q]:
212
- if q:
213
- try: q.put_nowait(None)
214
- except asyncio.QueueFull: logging.warning(f"Queue was full when trying to put sentinel for stop signal.")
215
- except Exception as e: logging.error(f"Error putting sentinel in queue: {e}", exc_info=True)
 
 
216
 
217
  async def run_main_loop(self):
218
  global video_frames_to_gemini_q, audio_chunks_to_gemini_q, audio_from_gemini_playback_q
@@ -228,11 +241,10 @@ class GeminiInteractionLoop:
228
 
229
  if client is None: logging.critical("Gemini client is None in run_main_loop. Aborting."); return
230
 
231
- session_task_group = None # Define before try block
232
  try:
233
  async with client.aio.live.connect(model=MODEL_NAME, config=LIVE_CONNECT_CONFIG) as session:
234
  self.gemini_session = session
235
- logging.info("Gemini session established with API.")
236
  try:
237
  logging.info("Sending system prompt to Gemini...")
238
  await self.gemini_session.send(input=MEDICAL_ASSISTANT_SYSTEM_PROMPT, end_of_turn=False)
@@ -241,37 +253,35 @@ class GeminiInteractionLoop:
241
  logging.error(f"Failed to send system prompt: {e}", exc_info=True)
242
  self.is_running = False; return
243
 
244
- async with asyncio.TaskGroup() as tg:
245
- session_task_group = tg # Assign task group
 
246
  logging.info("Creating async tasks for Gemini interaction...")
247
- tg.create_task(self.stream_media_to_gemini(), name="stream_media_to_gemini")
248
- tg.create_task(self.process_gemini_responses(), name="process_gemini_responses")
249
- tg.create_task(self.play_gemini_audio(), name="play_gemini_audio")
250
- logging.info("All Gemini interaction tasks created in TaskGroup.")
251
- logging.info("Gemini TaskGroup finished execution.")
 
 
 
 
 
 
252
 
253
  except asyncio.CancelledError: logging.info("GeminiInteractionLoop.run_main_loop() was cancelled.")
254
- # Removed ExceptionGroup handling as it's not available in Python 3.9
255
- except Exception as e:
256
- # Log general exceptions, including the ConnectionClosedError if it happens here
257
  logging.error(f"Exception in GeminiInteractionLoop run_main_loop: {type(e).__name__}: {e}", exc_info=True)
258
  finally:
259
  logging.info("GeminiInteractionLoop.run_main_loop() finishing...")
260
- self.is_running = False # Ensure flag is set
261
- # Cancel any potentially lingering tasks if TaskGroup didn't exit cleanly (though it should)
262
- if session_task_group and not session_task_group._closed:
263
- logging.warning("TaskGroup did not close cleanly, attempting cancellation.")
264
- try:
265
- session_task_group.cancel() # Attempt cancellation if needed
266
- except Exception as e_cancel:
267
- logging.error(f"Error cancelling task group: {e_cancel}")
268
-
269
- self.gemini_session = None # Session closed by async with
270
- # Clear global queues
271
  video_frames_to_gemini_q = None
272
  audio_chunks_to_gemini_q = None
273
  audio_from_gemini_playback_q = None
274
- logging.info("GeminiInteractionLoop finished and queues cleared.")
275
 
276
  # --- WebRTC Media Processors ---
277
  class VideoProcessor(VideoProcessorBase):
@@ -312,6 +322,8 @@ class AudioProcessor(AudioProcessorBase):
312
  if audio_chunks_to_gemini_q is None: return
313
  for frame in audio_frames:
314
  audio_data = frame.planes[0].to_bytes()
 
 
315
  mime_type = f"audio/L16;rate={frame.sample_rate};channels={frame.layout.channels}"
316
  api_data = {"data": audio_data, "mime_type": mime_type}
317
  try:
@@ -348,7 +360,6 @@ def run_streamlit_app():
348
  st.markdown("Utilizing Gemini Live API via WebRTC on Hugging Face Spaces")
349
  st.info("Remember: This AI cannot provide medical diagnoses. Always consult a healthcare professional for medical advice.")
350
 
351
-
352
  with st.sidebar:
353
  st.header("Session Control")
354
  if not st.session_state.gemini_session_active:
@@ -356,8 +367,6 @@ def run_streamlit_app():
356
  st.session_state.gemini_session_active = True
357
  st.session_state.chat_messages = [{"role": "system", "content": "Assistant activating. Please allow camera/microphone access in your browser if prompted."}]
358
 
359
- # Queues will be initialized inside GeminiInteractionLoop's thread
360
-
361
  gemini_loop = GeminiInteractionLoop()
362
  st.session_state.gemini_loop_instance = gemini_loop
363
  threading.Thread(target=lambda: asyncio.run(gemini_loop.run_main_loop()), name="GeminiLoopThread", daemon=True).start()
@@ -401,7 +410,7 @@ def run_streamlit_app():
401
 
402
  if webrtc_ctx.state.playing:
403
  st.caption("WebRTC connected. Streaming your camera and microphone.")
404
- elif st.session_state.gemini_session_active:
405
  st.caption("WebRTC attempting to connect. Ensure camera/microphone permissions are granted in your browser.")
406
  if hasattr(webrtc_ctx.state, 'error') and webrtc_ctx.state.error:
407
  st.error(f"WebRTC Connection Error: {webrtc_ctx.state.error}")
@@ -409,16 +418,13 @@ def run_streamlit_app():
409
  st.info("Click 'Start Session' in the sidebar to enable the live feed and assistant.")
410
 
411
  st.subheader("Chat with Assistant")
412
- # Use a container with a fixed height for scrollable chat
413
  chat_container = st.container()
414
  with chat_container:
415
- # Display messages from session state
416
  messages = st.session_state.get('chat_messages', [])
417
  for msg in messages:
418
  with st.chat_message(msg["role"]):
419
  st.write(msg["content"])
420
-
421
- # Chat input outside the container
422
  user_chat_input = st.chat_input(
423
  "Type your message...",
424
  key="user_chat_input_box",
@@ -426,39 +432,28 @@ def run_streamlit_app():
426
  )
427
 
428
  if user_chat_input:
429
- # Append user message immediately for responsiveness
430
- st.session_state.chat_messages = st.session_state.get('chat_messages', []) + [{"role": "user", "content": user_chat_input}]
 
431
 
432
- # Send to backend
433
  loop_instance = st.session_state.get('gemini_loop_instance')
434
  if loop_instance and loop_instance.async_event_loop and loop_instance.gemini_session:
435
  if loop_instance.async_event_loop.is_running():
436
- # Use run_coroutine_threadsafe to call async func from Streamlit thread
437
  future = asyncio.run_coroutine_threadsafe(
438
  loop_instance.send_text_input_to_gemini(user_chat_input),
439
  loop_instance.async_event_loop
440
  )
441
- try:
442
- future.result(timeout=2) # Optional: wait briefly for confirmation
443
- except TimeoutError:
444
- logging.warning("Timed out waiting for send_text_input_to_gemini confirmation.")
445
- except Exception as e:
446
- logging.error(f"Error calling send_text_input_to_gemini: {e}", exc_info=True)
447
-
448
  else: st.error("Session event loop is not running. Cannot send message.")
449
  elif not loop_instance or not st.session_state.gemini_session_active:
450
  st.error("Session is not active. Please start a session to send messages.")
451
  else: st.warning("Session components not fully ready. Please wait a moment.")
452
-
453
- # Rerun to display the user message and potentially any quick text response from AI
454
  st.rerun()
455
 
456
  if __name__ == "__main__":
457
- # Final check before running the app
458
  if client is None:
459
- # Log critical error if client is still None (should have been caught earlier)
460
  logging.critical("Gemini client could not be initialized. Application cannot start.")
461
- # Display error in Streamlit if possible (might not render if client init failed early)
462
- st.error("CRITICAL ERROR: Gemini client failed to initialize. Check API Key and logs.")
463
  else:
464
  run_streamlit_app()
 
6
  import io
7
  import threading
8
  import traceback
9
+ import atexit
10
  import time
11
  import logging
12
  from dotenv import load_dotenv
 
30
  # --- Configuration ---
31
  load_dotenv()
32
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(threadName)s - %(levelname)s - %(message)s')
33
+ logging.info("Application starting up...")
34
 
35
  # Audio configuration
36
  PYAUDIO_FORMAT = pyaudio.paInt16
 
47
  VIDEO_API_RESIZE = (1024, 1024)
48
 
49
  # !!! IMPORTANT: Verify this model name is correct for the Live API !!!
50
+ # This is a common point of failure for ConnectionClosedError.
51
  MODEL_NAME = "models/gemini-2.0-flash-live-001"
52
+ logging.info(f"Using Gemini Model: {MODEL_NAME}")
53
 
54
  MEDICAL_ASSISTANT_SYSTEM_PROMPT = """You are an AI Medical Assistant. Your primary function is to analyze visual information from the user's camera or screen and respond via voice.
55
 
 
68
  """
69
 
70
  # --- PyAudio Global Instance and Cleanup ---
71
+ pya = None
72
+ try:
73
+ pya = pyaudio.PyAudio()
74
+ def cleanup_pyaudio():
75
+ logging.info("Terminating PyAudio instance.")
76
+ if pya: pya.terminate()
77
+ atexit.register(cleanup_pyaudio)
78
+ logging.info("PyAudio initialized successfully.")
79
+ except Exception as e_pyaudio:
80
+ logging.warning(f"PyAudio initialization failed (expected in some server environments): {e_pyaudio}")
81
+ pya = None
82
 
83
  # --- Global Queues - Declare as None, initialize later ---
84
  video_frames_to_gemini_q: asyncio.Queue = None
 
91
  if GEMINI_API_KEY:
92
  try:
93
  client = genai.Client(http_options={"api_version": "v1beta"}, api_key=GEMINI_API_KEY)
94
+ logging.info("Gemini client initialized successfully.")
95
  except Exception as e:
96
  st.error(f"Failed to initialize Gemini client: {e}")
97
  logging.critical(f"Gemini client initialization failed: {e}", exc_info=True)
 
101
  logging.critical("GEMINI_API_KEY not found.")
102
  st.stop()
103
 
104
+ # Gemini LiveConnectConfig - HIGHLY SIMPLIFIED FOR DEBUGGING ConnectionClosedError
105
+ # Start with the absolute minimum. If this connects, incrementally add back features.
106
+ # If this still fails, the issue is likely MODEL_NAME or API Key/Project permissions.
107
  LIVE_CONNECT_CONFIG = types.LiveConnectConfig(
108
+ response_modalities=["text"], # Start with text only
109
+ # speech_config=None, # Explicitly None or omit
 
 
110
  )
111
+ logging.info(f"Attempting connection with highly simplified LiveConnectConfig: {LIVE_CONNECT_CONFIG}")
112
 
113
  # --- Backend Gemini Interaction Loop ---
114
  class GeminiInteractionLoop:
 
135
  await asyncio.sleep(0.1); return None
136
  try:
137
  video_frame = await asyncio.wait_for(video_frames_to_gemini_q.get(), timeout=0.02)
138
+ if video_frame is None: return None # Sentinel received
139
  video_frames_to_gemini_q.task_done(); return video_frame
140
  except asyncio.TimeoutError: pass
141
  except Exception as e: logging.error(f"Error getting video from queue: {e}", exc_info=True)
142
  try:
143
  audio_chunk = await asyncio.wait_for(audio_chunks_to_gemini_q.get(), timeout=0.02)
144
+ if audio_chunk is None: return None # Sentinel received
145
  audio_chunks_to_gemini_q.task_done(); return audio_chunk
146
  except asyncio.TimeoutError: return None
147
  except Exception as e: logging.error(f"Error getting audio from queue: {e}", exc_info=True); return None
 
149
  while self.is_running:
150
  if not self.gemini_session: await asyncio.sleep(0.1); continue
151
  media_data = await get_media_from_queues()
152
+ if media_data is None and not self.is_running: break # Sentinel and stop signal
153
  if media_data and self.gemini_session and self.is_running:
154
  try: await self.gemini_session.send(input=media_data)
155
  except Exception as e: logging.error(f"Error sending media chunk to Gemini: {e}", exc_info=True)
156
+ elif not media_data: await asyncio.sleep(0.05) # No data, yield
157
  except asyncio.CancelledError: logging.info("Task cancelled: stream_media_to_gemini.")
158
  finally: logging.info("Task finished: stream_media_to_gemini.")
159
 
 
173
  if text_response := chunk.text:
174
  logging.info(f"Gemini text response: {text_response[:100]}")
175
  if 'chat_messages' not in st.session_state: st.session_state.chat_messages = []
 
176
  st.session_state.chat_messages = st.session_state.chat_messages + [{"role": "assistant", "content": text_response}]
177
+ # Consider using st.rerun() via a thread-safe mechanism if immediate UI update is critical
178
  except types.generation_types.StopCandidateException: logging.info("Gemini response stream ended normally.")
179
  except Exception as e:
180
  if self.is_running: logging.error(f"Error receiving from Gemini: {e}", exc_info=True)
 
184
 
185
  async def play_gemini_audio(self):
186
  logging.info("Task started: Play Gemini audio responses.")
187
+ if pya is None:
188
+ logging.warning("PyAudio not available. Audio playback task will not run.")
189
+ return
190
+
191
  try:
192
  while audio_from_gemini_playback_q is None and self.is_running: await asyncio.sleep(0.1)
193
  if not self.is_running: return
 
199
  while self.is_running:
200
  try:
201
  audio_chunk = await asyncio.wait_for(audio_from_gemini_playback_q.get(), timeout=1.0)
202
+ if audio_chunk is None and not self.is_running: break # Sentinel and stop signal
203
  if audio_chunk: await asyncio.to_thread(self.playback_stream.write, audio_chunk)
204
  if audio_chunk: audio_from_gemini_playback_q.task_done()
205
  except asyncio.TimeoutError: continue
206
  except Exception as e: logging.error(f"Error playing audio chunk: {e}", exc_info=True); await asyncio.sleep(0.01)
207
  except Exception as e:
208
+ logging.error(f"Failed to open or use PyAudio playback stream (might be expected in this environment): {e}", exc_info=True)
 
209
  finally:
210
  if self.playback_stream:
211
  logging.info("Stopping and closing PyAudio playback stream.")
212
  try:
213
  await asyncio.to_thread(self.playback_stream.stop_stream)
214
  await asyncio.to_thread(self.playback_stream.close)
215
+ except Exception as e_close: logging.error(f"Error closing playback stream: {e_close}", exc_info=True)
 
216
  self.playback_stream = None
217
  logging.info("Task finished: play_gemini_audio.")
218
 
219
  def signal_stop(self):
220
  logging.info("Signal to stop GeminiInteractionLoop received.")
221
  self.is_running = False
222
+ for q_name, q_obj_ref in [("video_q", video_frames_to_gemini_q),
223
+ ("audio_in_q", audio_chunks_to_gemini_q),
224
+ ("audio_out_q", audio_from_gemini_playback_q)]:
225
+ if q_obj_ref:
226
+ try: q_obj_ref.put_nowait(None)
227
+ except asyncio.QueueFull: logging.warning(f"Queue {q_name} was full when trying to put sentinel for stop signal.")
228
+ except Exception as e: logging.error(f"Error putting sentinel in {q_name}: {e}", exc_info=True)
229
 
230
  async def run_main_loop(self):
231
  global video_frames_to_gemini_q, audio_chunks_to_gemini_q, audio_from_gemini_playback_q
 
241
 
242
  if client is None: logging.critical("Gemini client is None in run_main_loop. Aborting."); return
243
 
 
244
  try:
245
  async with client.aio.live.connect(model=MODEL_NAME, config=LIVE_CONNECT_CONFIG) as session:
246
  self.gemini_session = session
247
+ logging.info(f"Gemini session established with API for model {MODEL_NAME}.")
248
  try:
249
  logging.info("Sending system prompt to Gemini...")
250
  await self.gemini_session.send(input=MEDICAL_ASSISTANT_SYSTEM_PROMPT, end_of_turn=False)
 
253
  logging.error(f"Failed to send system prompt: {e}", exc_info=True)
254
  self.is_running = False; return
255
 
256
+ # Python 3.9 does not have asyncio.TaskGroup, so manage tasks individually
257
+ tasks = []
258
+ try:
259
  logging.info("Creating async tasks for Gemini interaction...")
260
+ tasks.append(asyncio.create_task(self.stream_media_to_gemini(), name="stream_media_to_gemini"))
261
+ tasks.append(asyncio.create_task(self.process_gemini_responses(), name="process_gemini_responses"))
262
+ tasks.append(asyncio.create_task(self.play_gemini_audio(), name="play_gemini_audio"))
263
+ logging.info("All Gemini interaction tasks created.")
264
+ await asyncio.gather(*tasks) # Wait for all tasks to complete
265
+ except Exception as e_gather: # Catch errors from tasks gathered
266
+ logging.error(f"Error during asyncio.gather: {e_gather}", exc_info=True)
267
+ for task in tasks:
268
+ if not task.done(): task.cancel() # Cancel pending tasks
269
+ await asyncio.gather(*tasks, return_exceptions=True) # Wait for cancellations
270
+ logging.info("Gemini interaction tasks finished or cancelled.")
271
 
272
  except asyncio.CancelledError: logging.info("GeminiInteractionLoop.run_main_loop() was cancelled.")
273
+ except Exception as e: # General catch-all, including ConnectionClosedError
 
 
274
  logging.error(f"Exception in GeminiInteractionLoop run_main_loop: {type(e).__name__}: {e}", exc_info=True)
275
  finally:
276
  logging.info("GeminiInteractionLoop.run_main_loop() finishing...")
277
+ self.is_running = False # Ensure flag is set for all tasks
278
+ self.signal_stop() # Send sentinels again to be sure
279
+ self.gemini_session = None
280
+ # Clear global queues by setting them to None
 
 
 
 
 
 
 
281
  video_frames_to_gemini_q = None
282
  audio_chunks_to_gemini_q = None
283
  audio_from_gemini_playback_q = None
284
+ logging.info("GeminiInteractionLoop finished and global queues set to None.")
285
 
286
  # --- WebRTC Media Processors ---
287
  class VideoProcessor(VideoProcessorBase):
 
322
  if audio_chunks_to_gemini_q is None: return
323
  for frame in audio_frames:
324
  audio_data = frame.planes[0].to_bytes()
325
+ # Note: Ensure this mime_type and the actual audio data format (sample rate, channels, bit depth)
326
+ # are compatible with what the Gemini Live API expects for PCM audio.
327
  mime_type = f"audio/L16;rate={frame.sample_rate};channels={frame.layout.channels}"
328
  api_data = {"data": audio_data, "mime_type": mime_type}
329
  try:
 
360
  st.markdown("Utilizing Gemini Live API via WebRTC on Hugging Face Spaces")
361
  st.info("Remember: This AI cannot provide medical diagnoses. Always consult a healthcare professional for medical advice.")
362
 
 
363
  with st.sidebar:
364
  st.header("Session Control")
365
  if not st.session_state.gemini_session_active:
 
367
  st.session_state.gemini_session_active = True
368
  st.session_state.chat_messages = [{"role": "system", "content": "Assistant activating. Please allow camera/microphone access in your browser if prompted."}]
369
 
 
 
370
  gemini_loop = GeminiInteractionLoop()
371
  st.session_state.gemini_loop_instance = gemini_loop
372
  threading.Thread(target=lambda: asyncio.run(gemini_loop.run_main_loop()), name="GeminiLoopThread", daemon=True).start()
 
410
 
411
  if webrtc_ctx.state.playing:
412
  st.caption("WebRTC connected. Streaming your camera and microphone.")
413
+ elif st.session_state.gemini_session_active: # Check if session is supposed to be active
414
  st.caption("WebRTC attempting to connect. Ensure camera/microphone permissions are granted in your browser.")
415
  if hasattr(webrtc_ctx.state, 'error') and webrtc_ctx.state.error:
416
  st.error(f"WebRTC Connection Error: {webrtc_ctx.state.error}")
 
418
  st.info("Click 'Start Session' in the sidebar to enable the live feed and assistant.")
419
 
420
  st.subheader("Chat with Assistant")
 
421
  chat_container = st.container()
422
  with chat_container:
 
423
  messages = st.session_state.get('chat_messages', [])
424
  for msg in messages:
425
  with st.chat_message(msg["role"]):
426
  st.write(msg["content"])
427
+
 
428
  user_chat_input = st.chat_input(
429
  "Type your message...",
430
  key="user_chat_input_box",
 
432
  )
433
 
434
  if user_chat_input:
435
+ current_messages = st.session_state.get('chat_messages', [])
436
+ current_messages.append({"role": "user", "content": user_chat_input})
437
+ st.session_state.chat_messages = current_messages
438
 
 
439
  loop_instance = st.session_state.get('gemini_loop_instance')
440
  if loop_instance and loop_instance.async_event_loop and loop_instance.gemini_session:
441
  if loop_instance.async_event_loop.is_running():
 
442
  future = asyncio.run_coroutine_threadsafe(
443
  loop_instance.send_text_input_to_gemini(user_chat_input),
444
  loop_instance.async_event_loop
445
  )
446
+ try: future.result(timeout=2)
447
+ except TimeoutError: logging.warning("Timed out waiting for send_text_input_to_gemini confirmation.")
448
+ except Exception as e: logging.error(f"Error calling send_text_input_to_gemini: {e}", exc_info=True)
 
 
 
 
449
  else: st.error("Session event loop is not running. Cannot send message.")
450
  elif not loop_instance or not st.session_state.gemini_session_active:
451
  st.error("Session is not active. Please start a session to send messages.")
452
  else: st.warning("Session components not fully ready. Please wait a moment.")
 
 
453
  st.rerun()
454
 
455
  if __name__ == "__main__":
 
456
  if client is None:
 
457
  logging.critical("Gemini client could not be initialized. Application cannot start.")
 
 
458
  else:
459
  run_streamlit_app()