kamcio1989 commited on
Commit
44fe8e4
ยท
verified ยท
1 Parent(s): 90ab460

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +463 -0
  2. requirements.txt +4 -0
app.py ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import asyncio
3
+ import json
4
+ import base64
5
+ import numpy as np
6
+ from dataclasses import dataclass, field
7
+ from typing import AsyncIterator, Callable
8
+ import threading
9
+ import queue
10
+
11
+ # Mock WebRTC and Gemini integration - in production, use actual WebRTC libraries
12
+ # and Google's Gemini Live API with proper authentication
13
+
14
+ @dataclass
15
+ class GeminiConfig:
16
+ """Configuration for Gemini Live API connection."""
17
+ model: str = "gemini-2.0-flash-exp"
18
+ api_key: str = ""
19
+ voice: str = "Puck" # Puck, Charon, Kore, Fenrir, Aoede
20
+ response_modalities: list = field(default_factory=lambda: ["AUDIO", "TEXT"])
21
+
22
+ class WebRTCGeminiClient:
23
+ """
24
+ WebRTC client for Gemini Live API.
25
+ Handles real-time bidirectional streaming of audio and text.
26
+ """
27
+
28
+ def __init__(self, config: GeminiConfig = None):
29
+ self.config = config or GeminiConfig()
30
+ self.is_connected = False
31
+ self.audio_input_queue = queue.Queue()
32
+ self.audio_output_queue = queue.Queue()
33
+ self.text_output_queue = queue.Queue()
34
+ self._running = False
35
+ self._thread = None
36
+
37
+ def connect(self) -> bool:
38
+ """Establish WebRTC connection to Gemini Live API."""
39
+ # In production: Initialize WebRTC peer connection
40
+ # Connect to Gemini Live API endpoint
41
+ # Set up ICE servers, SDP exchange, etc.
42
+ self.is_connected = True
43
+ self._running = True
44
+ self._thread = threading.Thread(target=self._simulate_gemini_loop)
45
+ self._thread.start()
46
+ return True
47
+
48
+ def disconnect(self):
49
+ """Close WebRTC connection."""
50
+ self._running = False
51
+ self.is_connected = False
52
+ if self._thread:
53
+ self._thread.join(timeout=2)
54
+
55
+ def _simulate_gemini_loop(self):
56
+ """Simulate Gemini responses for demo purposes."""
57
+ # In production: This would handle actual WebRTC data channels
58
+ # Receive audio/text from Gemini, send user audio/text to Gemini
59
+
60
+ responses = [
61
+ "Hello! I'm your Gemini Live assistant. How can I help you today?",
62
+ "I can see and hear you in real-time. Feel free to ask me anything!",
63
+ "That's interesting! Tell me more about what you're working on.",
64
+ "I understand. Let me think about that for a moment...",
65
+ "Great question! Here's what I know about that topic...",
66
+ "I'm processing your request. One moment please.",
67
+ "I can help with that! Let me provide some guidance.",
68
+ "Thanks for sharing that with me. Is there anything else you'd like to discuss?",
69
+ ]
70
+
71
+ import random
72
+ import time
73
+
74
+ idx = 0
75
+ while self._running:
76
+ time.sleep(0.1)
77
+
78
+ # Simulate receiving audio/text from Gemini
79
+ if random.random() < 0.02 and self.audio_output_queue.qsize() < 5:
80
+ # Simulate text response
81
+ if idx < len(responses):
82
+ self.text_output_queue.put({
83
+ "type": "text",
84
+ "content": responses[idx]
85
+ })
86
+ idx = (idx + 1) % len(responses)
87
+
88
+ # Simulate audio response (would be actual PCM audio in production)
89
+ sample_rate = 24000
90
+ duration = 2.0 # seconds
91
+ t = np.linspace(0, duration, int(sample_rate * duration))
92
+ # Generate synthetic speech-like audio
93
+ audio = np.sin(2 * np.pi * 200 * t) * np.exp(-t * 2)
94
+ audio = (audio * 32767).astype(np.int16)
95
+ self.audio_output_queue.put({
96
+ "type": "audio",
97
+ "data": (sample_rate, audio)
98
+ })
99
+
100
+ def send_audio(self, audio_data: tuple) -> bool:
101
+ """Send audio chunk to Gemini."""
102
+ # audio_data: (sample_rate, numpy_array)
103
+ if not self.is_connected:
104
+ return False
105
+ self.audio_input_queue.put(audio_data)
106
+ return True
107
+
108
+ def send_text(self, text: str) -> bool:
109
+ """Send text message to Gemini."""
110
+ if not self.is_connected:
111
+ return False
112
+ # In production: Send via WebRTC data channel
113
+ return True
114
+
115
+ def get_audio_response(self) -> dict | None:
116
+ """Get audio response from Gemini if available."""
117
+ try:
118
+ return self.audio_output_queue.get_nowait()
119
+ except queue.Empty:
120
+ return None
121
+
122
+ def get_text_response(self) -> dict | None:
123
+ """Get text response from Gemini if available."""
124
+ try:
125
+ return self.text_output_queue.get_nowait()
126
+ except queue.Empty:
127
+ return None
128
+
129
+
130
+ class GeminiLiveChat:
131
+ """Main application class for Gemini Live Chat."""
132
+
133
+ def __init__(self):
134
+ self.client = WebRTCGeminiClient()
135
+ self.chat_history = []
136
+ self.is_streaming = False
137
+ self.audio_buffer = []
138
+
139
+ def start_session(self, api_key: str, voice: str, enable_audio: bool, enable_text: bool) -> str:
140
+ """Initialize Gemini Live session."""
141
+ if not api_key or len(api_key) < 10:
142
+ return "โŒ Error: Please provide a valid Gemini API key"
143
+
144
+ config = GeminiConfig(
145
+ api_key=api_key,
146
+ voice=voice,
147
+ response_modalities=["AUDIO" if enable_audio else "", "TEXT" if enable_text else ""]
148
+ )
149
+ config.response_modalities = [m for m in config.response_modalities if m]
150
+
151
+ self.client = WebRTCGeminiClient(config)
152
+
153
+ try:
154
+ success = self.client.connect()
155
+ if success:
156
+ self.is_streaming = True
157
+ return f"โœ… Connected to Gemini Live!\n๐ŸŽค Voice: {voice}\n๐Ÿ“ก Modalities: {', '.join(config.response_modalities)}"
158
+ else:
159
+ return "โŒ Failed to connect. Please check your API key and try again."
160
+ except Exception as e:
161
+ return f"โŒ Connection error: {str(e)}"
162
+
163
+ def stop_session(self) -> str:
164
+ """End Gemini Live session."""
165
+ self.is_streaming = False
166
+ self.client.disconnect()
167
+ return "โน๏ธ Session ended. Thanks for chatting!"
168
+
169
+ def process_audio_stream(self, audio_data: tuple | None) -> tuple:
170
+ """
171
+ Process incoming audio from microphone and return audio response.
172
+ This is called continuously during streaming.
173
+ """
174
+ if not self.is_streaming or audio_data is None:
175
+ # Return silence when not streaming
176
+ sample_rate = 24000
177
+ silence = np.zeros(sample_rate // 10, dtype=np.int16) # 100ms silence
178
+ return (sample_rate, silence)
179
+
180
+ # Send user audio to Gemini
181
+ self.client.send_audio(audio_data)
182
+
183
+ # Check for Gemini audio response
184
+ response = self.client.get_audio_response()
185
+ if response and response.get("type") == "audio":
186
+ return response["data"]
187
+
188
+ # Return silence if no response yet
189
+ sample_rate = 24000
190
+ silence = np.zeros(sample_rate // 10, dtype=np.int16)
191
+ return (sample_rate, silence)
192
+
193
+ def get_text_updates(self) -> str:
194
+ """Get text responses from Gemini."""
195
+ if not self.is_streaming:
196
+ return self._format_chat_history()
197
+
198
+ # Check for new text responses
199
+ while True:
200
+ response = self.client.get_text_response()
201
+ if response is None:
202
+ break
203
+ if response.get("type") == "text":
204
+ self.chat_history.append({
205
+ "role": "assistant",
206
+ "content": response["content"]
207
+ })
208
+
209
+ return self._format_chat_history()
210
+
211
+ def _format_chat_history(self) -> str:
212
+ """Format chat history for display."""
213
+ if not self.chat_history:
214
+ return "No messages yet. Start speaking or type a message!"
215
+
216
+ formatted = []
217
+ for msg in self.chat_history:
218
+ role_emoji = "๐Ÿ‘ค" if msg["role"] == "user" else "๐Ÿค–"
219
+ formatted.append(f"{role_emoji} **{msg['role'].title()}**: {msg['content']}")
220
+
221
+ return "\n\n".join(formatted)
222
+
223
+ def send_text_message(self, message: str) -> str:
224
+ """Send a text message to Gemini."""
225
+ if not self.is_streaming:
226
+ return "โš ๏ธ Please start a session first!"
227
+
228
+ if not message.strip():
229
+ return self._format_chat_history()
230
+
231
+ self.chat_history.append({
232
+ "role": "user",
233
+ "content": message.strip()
234
+ })
235
+
236
+ self.client.send_text(message.strip())
237
+ return self.get_text_updates()
238
+
239
+ def clear_history(self) -> str:
240
+ """Clear chat history."""
241
+ self.chat_history = []
242
+ return "History cleared."
243
+
244
+
245
+ # Global app instance
246
+ app = GeminiLiveChat()
247
+
248
+
249
+ def create_gemini_live_chat():
250
+ """Create the Gradio 6 WebRTC Gemini Live Chat interface."""
251
+
252
+ with gr.Blocks() as demo:
253
+ # Header with anycoder link
254
+ gr.Markdown("""
255
+ # ๐ŸŽ™๏ธ WebRTC Gemini Live Chat Agent
256
+
257
+ Real-time voice and text conversation with Google's Gemini AI using WebRTC for ultra-low latency.
258
+
259
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
260
+ """)
261
+
262
+ with gr.Row():
263
+ # Left panel - Controls
264
+ with gr.Column(scale=1):
265
+ gr.Markdown("### โš™๏ธ Session Settings")
266
+
267
+ api_key_input = gr.Textbox(
268
+ label="Gemini API Key",
269
+ placeholder="Enter your Gemini API key...",
270
+ type="password",
271
+ info="Get your key at makersuite.google.com"
272
+ )
273
+
274
+ voice_select = gr.Dropdown(
275
+ choices=["Puck", "Charon", "Kore", "Fenrir", "Aoede"],
276
+ value="Puck",
277
+ label="Voice",
278
+ info="Select Gemini's voice"
279
+ )
280
+
281
+ with gr.Row():
282
+ enable_audio = gr.Checkbox(
283
+ label="Audio Output",
284
+ value=True,
285
+ info="Receive voice responses"
286
+ )
287
+ enable_text = gr.Checkbox(
288
+ label="Text Output",
289
+ value=True,
290
+ info="Receive text responses"
291
+ )
292
+
293
+ with gr.Row():
294
+ start_btn = gr.Button("โ–ถ๏ธ Start Session", variant="primary")
295
+ stop_btn = gr.Button("โน๏ธ Stop", variant="stop")
296
+
297
+ status_output = gr.Textbox(
298
+ label="Status",
299
+ value="Ready to connect. Enter your API key and click Start.",
300
+ lines=3
301
+ )
302
+
303
+ gr.Markdown("---")
304
+ gr.Markdown("### ๐Ÿ’ฌ Text Input")
305
+
306
+ text_input = gr.Textbox(
307
+ label="Type a message",
308
+ placeholder="Or speak naturally...",
309
+ lines=2
310
+ )
311
+
312
+ with gr.Row():
313
+ send_btn = gr.Button("๐Ÿ“ค Send", variant="secondary")
314
+ clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear")
315
+
316
+ gr.Markdown("---")
317
+ gr.Markdown("""
318
+ ### ๐Ÿ“‹ Instructions
319
+
320
+ 1. Enter your Gemini API key
321
+ 2. Click **Start Session**
322
+ 3. Allow microphone access
323
+ 4. Speak naturally or type messages
324
+ 5. Gemini responds in real-time with voice and text
325
+
326
+ **Note**: This demo simulates WebRTC streaming.
327
+ For production, use actual WebRTC libraries with
328
+ Google's Gemini Live API.
329
+ """)
330
+
331
+ # Right panel - Chat and Audio
332
+ with gr.Column(scale=2):
333
+ gr.Markdown("### ๐Ÿ”Š Live Audio Stream")
334
+
335
+ # Audio streaming component - input from mic, output to speakers
336
+ audio_stream = gr.Audio(
337
+ label="Live Conversation",
338
+ sources=["microphone"],
339
+ streaming=True,
340
+ autoplay=True,
341
+ waveform_options=gr.WaveformOptions(
342
+ waveform_color="#4285f4",
343
+ waveform_progress_color="#ea4335",
344
+ show_recording_waveform=True
345
+ )
346
+ )
347
+
348
+ gr.Markdown("### ๐Ÿ’ฌ Conversation Transcript")
349
+
350
+ chat_display = gr.Markdown(
351
+ value="No messages yet. Start a session to begin chatting!",
352
+ label="Chat History"
353
+ )
354
+
355
+ # Event handlers
356
+
357
+ def on_start(api_key, voice, audio_enabled, text_enabled):
358
+ status = app.start_session(api_key, voice, audio_enabled, text_enabled)
359
+ return status
360
+
361
+ def on_stop():
362
+ status = app.stop_session()
363
+ return status
364
+
365
+ def on_send(message):
366
+ return app.send_text_message(message)
367
+
368
+ def on_clear():
369
+ return app.clear_history()
370
+
371
+ def audio_callback(audio_data):
372
+ # Process audio bidirectionally
373
+ return app.process_audio_stream(audio_data)
374
+
375
+ def update_chat():
376
+ # Poll for text updates
377
+ return app.get_text_updates()
378
+
379
+ # Connect events with Gradio 6 syntax
380
+
381
+ start_btn.click(
382
+ fn=on_start,
383
+ inputs=[api_key_input, voice_select, enable_audio, enable_text],
384
+ outputs=status_output,
385
+ api_visibility="public"
386
+ )
387
+
388
+ stop_btn.click(
389
+ fn=on_stop,
390
+ inputs=None,
391
+ outputs=status_output,
392
+ api_visibility="public"
393
+ )
394
+
395
+ send_btn.click(
396
+ fn=on_send,
397
+ inputs=text_input,
398
+ outputs=chat_display,
399
+ api_visibility="public"
400
+ ).then(
401
+ fn=lambda: "", # Clear text input after sending
402
+ outputs=text_input
403
+ )
404
+
405
+ clear_btn.click(
406
+ fn=on_clear,
407
+ outputs=chat_display,
408
+ api_visibility="public"
409
+ )
410
+
411
+ # Audio streaming - Gradio 6 stream event
412
+ audio_stream.stream(
413
+ fn=audio_callback,
414
+ inputs=audio_stream,
415
+ outputs=audio_stream,
416
+ time_limit=300, # 5 minutes max per session
417
+ stream_every=0.1, # 100ms chunks
418
+ concurrency_limit=1,
419
+ api_visibility="private"
420
+ )
421
+
422
+ # Timer for text updates
423
+ timer = gr.Timer(0.5, active=True)
424
+ timer.tick(
425
+ fn=update_chat,
426
+ outputs=chat_display,
427
+ api_visibility="private"
428
+ )
429
+
430
+ # Also update on start/stop
431
+ start_btn.click(fn=update_chat, outputs=chat_display)
432
+ stop_btn.click(fn=update_chat, outputs=chat_display)
433
+
434
+ return demo
435
+
436
+
437
+ # Create and launch the app
438
+ with gr.Blocks() as demo:
439
+ create_gemini_live_chat()
440
+
441
+ demo.launch(
442
+ theme=gr.themes.Soft(
443
+ primary_hue="blue",
444
+ secondary_hue="indigo",
445
+ neutral_hue="slate",
446
+ font=gr.themes.GoogleFont("Inter"),
447
+ text_size="md",
448
+ spacing_size="md",
449
+ radius_size="lg"
450
+ ).set(
451
+ button_primary_background_fill="*primary_600",
452
+ button_primary_background_fill_hover="*primary_700",
453
+ block_title_text_weight="600",
454
+ block_background_fill="*neutral_50"
455
+ ),
456
+ footer_links=[
457
+ {"label": "Built with anycoder", "url": "https://huggingface.co/spaces/akhaliq/anycoder"},
458
+ {"label": "Gradio", "url": "https://gradio.app"},
459
+ {"label": "API", "url": "/docs"}
460
+ ],
461
+ pwa=True,
462
+ title="WebRTC Gemini Live Chat"
463
+ )
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ dataclasses
2
+ gradio>=6.0.2
3
+ numpy
4
+ queue