voice-vibecoder 2.12.3__tar.gz → 2.13.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/CHANGELOG.md +13 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/PKG-INFO +1 -1
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/pyproject.toml +1 -1
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/__init__.py +1 -1
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/cli_serve.py +17 -5
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/config.py +149 -38
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/server.py +2 -5
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/session.py +17 -7
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/screen.py +3 -7
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/setup.py +195 -56
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/wizard.py +44 -14
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/voice_providers/__init__.py +26 -2
- voice_vibecoder-2.13.0/src/voice_vibecoder/voice_providers/gemini.py +395 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/.github/workflows/ci.yml +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/.github/workflows/release.yml +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/.gitignore +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/LICENSE +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/README.md +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/app.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/index.html +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/app_config.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/audio.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/code_providers/__init__.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/code_providers/claude.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/code_providers/cursor.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/code_providers/openclaw.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/code_providers/registry.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/instances.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/__init__.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/agent_task.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/definitions.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/dispatch.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/file_tree.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/tools/handlers.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/__init__.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/callbacks.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/diff_view.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/help_modal.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/panels.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/state.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/styles.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/ui/update_modal.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/voice_providers/openai.py +0 -0
- {voice_vibecoder-2.12.3 → voice_vibecoder-2.13.0}/src/voice_vibecoder/worktrees.py +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v2.13.0 (2026-02-21)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Add silence duration and prefix padding options
|
|
10
|
+
([`0d7d89c`](https://github.com/snokam/voice-vibecoder/commit/0d7d89cc238cbe396b0fe30a0fbf666118e8faf5))
|
|
11
|
+
|
|
12
|
+
### Refactoring
|
|
13
|
+
|
|
14
|
+
- **config**: Store voice and VAD settings per provider
|
|
15
|
+
([`fca2e8b`](https://github.com/snokam/voice-vibecoder/commit/fca2e8bf20ee16bc66a29ffb0b0c1544d3c39c35))
|
|
16
|
+
|
|
17
|
+
|
|
5
18
|
## v2.12.3 (2026-02-21)
|
|
6
19
|
|
|
7
20
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: voice-vibecoder
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.13.0
|
|
4
4
|
Summary: Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code
|
|
5
5
|
Project-URL: Homepage, https://github.com/snokam/voice-vibecoder
|
|
6
6
|
Project-URL: Repository, https://github.com/snokam/voice-vibecoder
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "voice-vibecoder"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.13.0"
|
|
8
8
|
description = "Olaf The Vibecoder — a voice-controlled coding assistant using OpenAI Realtime API + Claude Code"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -35,22 +35,34 @@ def _detect_git_root() -> Path:
|
|
|
35
35
|
|
|
36
36
|
def _apply_env_overrides(settings: RealtimeSettings) -> RealtimeSettings:
|
|
37
37
|
"""Override settings with VOICE_* environment variables when present."""
|
|
38
|
-
|
|
38
|
+
applied = []
|
|
39
|
+
|
|
40
|
+
# Top-level overrides
|
|
41
|
+
simple_map = {
|
|
39
42
|
"VOICE_PROVIDER": "provider",
|
|
40
43
|
"VOICE_API_KEY": "api_key",
|
|
41
|
-
"VOICE_AZURE_ENDPOINT": "azure_endpoint",
|
|
42
|
-
"VOICE_AZURE_DEPLOYMENT": "azure_deployment",
|
|
43
44
|
"VOICE_LANGUAGE": "language",
|
|
44
45
|
"VOICE_PERMISSION_MODE": "permission_mode",
|
|
45
46
|
}
|
|
46
|
-
|
|
47
|
-
for env_var, attr in env_map.items():
|
|
47
|
+
for env_var, attr in simple_map.items():
|
|
48
48
|
value = os.environ.get(env_var)
|
|
49
49
|
if value:
|
|
50
50
|
setattr(settings, attr, value)
|
|
51
51
|
safe = env_var if "KEY" not in env_var else f"{env_var}=***"
|
|
52
52
|
applied.append(safe)
|
|
53
53
|
|
|
54
|
+
# Nested provider overrides
|
|
55
|
+
nested_map = {
|
|
56
|
+
"VOICE_AZURE_ENDPOINT": (settings.azure, "endpoint"),
|
|
57
|
+
"VOICE_AZURE_DEPLOYMENT": (settings.azure, "deployment"),
|
|
58
|
+
"VOICE_GEMINI_MODEL": (settings.gemini, "model"),
|
|
59
|
+
}
|
|
60
|
+
for env_var, (obj, attr) in nested_map.items():
|
|
61
|
+
value = os.environ.get(env_var)
|
|
62
|
+
if value:
|
|
63
|
+
setattr(obj, attr, value)
|
|
64
|
+
applied.append(env_var)
|
|
65
|
+
|
|
54
66
|
if applied:
|
|
55
67
|
logger.info("Env overrides applied: %s", ", ".join(applied))
|
|
56
68
|
return settings
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from dataclasses import dataclass, asdict, field
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Literal
|
|
8
9
|
|
|
@@ -228,7 +229,7 @@ def get_system_instructions(
|
|
|
228
229
|
return base
|
|
229
230
|
|
|
230
231
|
|
|
231
|
-
Provider = Literal["openai", "azure"]
|
|
232
|
+
Provider = Literal["openai", "azure", "gemini"]
|
|
232
233
|
|
|
233
234
|
VOICES = ["ash", "ballad", "coral", "sage", "shimmer", "alloy", "echo", "verse"]
|
|
234
235
|
|
|
@@ -239,6 +240,10 @@ AZURE_DEFAULT_ENDPOINT = ""
|
|
|
239
240
|
AZURE_DEFAULT_DEPLOYMENT = ""
|
|
240
241
|
AZURE_API_VERSION = "2024-10-01-preview"
|
|
241
242
|
|
|
243
|
+
GEMINI_WS_URL = "wss://generativelanguage.googleapis.com/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent"
|
|
244
|
+
GEMINI_DEFAULT_MODEL = "models/gemini-2.5-flash-native-audio-latest"
|
|
245
|
+
GEMINI_VOICES = ["Kore", "Aoede", "Charon", "Fenrir", "Puck", "Leda", "Orus", "Zephyr"]
|
|
246
|
+
|
|
242
247
|
# Module-level configurable path — set via configure_paths()
|
|
243
248
|
from voice_vibecoder.app_config import _default_config_dir
|
|
244
249
|
|
|
@@ -259,6 +264,38 @@ def configure_paths(config_dir: Path) -> None:
|
|
|
259
264
|
SETTINGS_PATH = config_dir / "realtime.json"
|
|
260
265
|
|
|
261
266
|
|
|
267
|
+
@dataclass
|
|
268
|
+
class OpenAIConfig:
|
|
269
|
+
"""Per-provider settings for OpenAI."""
|
|
270
|
+
model: str = OPENAI_DEFAULT_MODEL
|
|
271
|
+
voice: str = "ash"
|
|
272
|
+
vad_threshold: float = 0.8
|
|
273
|
+
silence_duration_ms: int = 700
|
|
274
|
+
prefix_padding_ms: int = 300
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@dataclass
|
|
278
|
+
class AzureConfig:
|
|
279
|
+
"""Per-provider settings for Azure OpenAI."""
|
|
280
|
+
endpoint: str = AZURE_DEFAULT_ENDPOINT
|
|
281
|
+
deployment: str = AZURE_DEFAULT_DEPLOYMENT
|
|
282
|
+
voice: str = "ash"
|
|
283
|
+
vad_threshold: float = 0.8
|
|
284
|
+
silence_duration_ms: int = 700
|
|
285
|
+
prefix_padding_ms: int = 300
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@dataclass
|
|
289
|
+
class GeminiConfig:
|
|
290
|
+
"""Per-provider settings for Gemini."""
|
|
291
|
+
model: str = GEMINI_DEFAULT_MODEL
|
|
292
|
+
voice: str = "Puck"
|
|
293
|
+
start_sensitivity: str = "HIGH" # "HIGH" or "LOW"
|
|
294
|
+
end_sensitivity: str = "LOW" # "HIGH" or "LOW"
|
|
295
|
+
silence_duration_ms: int = 700
|
|
296
|
+
prefix_padding_ms: int = 100
|
|
297
|
+
|
|
298
|
+
|
|
262
299
|
class RealtimeSettings:
|
|
263
300
|
"""Persisted settings for the Realtime voice coding feature."""
|
|
264
301
|
|
|
@@ -266,17 +303,10 @@ class RealtimeSettings:
|
|
|
266
303
|
self,
|
|
267
304
|
provider: Provider = "openai",
|
|
268
305
|
api_key: str = "",
|
|
269
|
-
#
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
azure_deployment: str = AZURE_DEFAULT_DEPLOYMENT,
|
|
274
|
-
# Voice & session
|
|
275
|
-
voice: str = "ash",
|
|
276
|
-
# VAD — higher threshold = less sensitive to background noise
|
|
277
|
-
vad_threshold: float = 0.8,
|
|
278
|
-
silence_duration_ms: int = 700,
|
|
279
|
-
prefix_padding_ms: int = 300,
|
|
306
|
+
# Per-provider configs
|
|
307
|
+
openai: OpenAIConfig | None = None,
|
|
308
|
+
azure: AzureConfig | None = None,
|
|
309
|
+
gemini: GeminiConfig | None = None,
|
|
280
310
|
# Agent
|
|
281
311
|
agent_timeout: int = 300,
|
|
282
312
|
# Language
|
|
@@ -290,33 +320,51 @@ class RealtimeSettings:
|
|
|
290
320
|
) -> None:
|
|
291
321
|
self.provider = provider
|
|
292
322
|
self.api_key = api_key
|
|
293
|
-
self.
|
|
294
|
-
self.
|
|
295
|
-
self.
|
|
296
|
-
self.voice = voice
|
|
297
|
-
self.vad_threshold = vad_threshold
|
|
298
|
-
self.silence_duration_ms = silence_duration_ms
|
|
299
|
-
self.prefix_padding_ms = prefix_padding_ms
|
|
323
|
+
self.openai = openai or OpenAIConfig()
|
|
324
|
+
self.azure = azure or AzureConfig()
|
|
325
|
+
self.gemini = gemini or GeminiConfig()
|
|
300
326
|
self.agent_timeout = agent_timeout
|
|
301
327
|
self.language = language
|
|
302
328
|
self.permission_mode = permission_mode
|
|
303
329
|
self.input_mode = input_mode
|
|
304
330
|
self.enabled_agents: list[str] = enabled_agents or ["claude"]
|
|
305
331
|
|
|
332
|
+
@property
|
|
333
|
+
def active_voice(self) -> str:
|
|
334
|
+
"""Return the voice setting for the active provider."""
|
|
335
|
+
if self.provider == "azure":
|
|
336
|
+
return self.azure.voice
|
|
337
|
+
if self.provider == "gemini":
|
|
338
|
+
return self.gemini.voice
|
|
339
|
+
return self.openai.voice
|
|
340
|
+
|
|
341
|
+
@property
|
|
342
|
+
def active_provider_config(self) -> OpenAIConfig | AzureConfig | GeminiConfig:
|
|
343
|
+
"""Return the config object for the active provider."""
|
|
344
|
+
if self.provider == "azure":
|
|
345
|
+
return self.azure
|
|
346
|
+
if self.provider == "gemini":
|
|
347
|
+
return self.gemini
|
|
348
|
+
return self.openai
|
|
349
|
+
|
|
306
350
|
@property
|
|
307
351
|
def ws_url(self) -> str:
|
|
308
352
|
if self.provider == "azure":
|
|
309
353
|
return (
|
|
310
|
-
f"wss://{self.
|
|
354
|
+
f"wss://{self.azure.endpoint}/openai/realtime"
|
|
311
355
|
f"?api-version={AZURE_API_VERSION}"
|
|
312
|
-
f"&deployment={self.
|
|
356
|
+
f"&deployment={self.azure.deployment}"
|
|
313
357
|
)
|
|
314
|
-
|
|
358
|
+
if self.provider == "gemini":
|
|
359
|
+
return f"{GEMINI_WS_URL}?key={self.api_key}"
|
|
360
|
+
return f"{OPENAI_WS_URL}?model={self.openai.model}"
|
|
315
361
|
|
|
316
362
|
@property
|
|
317
363
|
def ws_headers(self) -> dict[str, str]:
|
|
318
364
|
if self.provider == "azure":
|
|
319
365
|
return {"api-key": self.api_key}
|
|
366
|
+
if self.provider == "gemini":
|
|
367
|
+
return {}
|
|
320
368
|
return {
|
|
321
369
|
"Authorization": f"Bearer {self.api_key}",
|
|
322
370
|
"OpenAI-Beta": "realtime=v1",
|
|
@@ -329,8 +377,10 @@ class RealtimeSettings:
|
|
|
329
377
|
@property
|
|
330
378
|
def provider_label(self) -> str:
|
|
331
379
|
if self.provider == "azure":
|
|
332
|
-
return f"Azure ({self.
|
|
333
|
-
|
|
380
|
+
return f"Azure ({self.azure.deployment})"
|
|
381
|
+
if self.provider == "gemini":
|
|
382
|
+
return f"Gemini ({self.gemini.model})"
|
|
383
|
+
return f"OpenAI ({self.openai.model})"
|
|
334
384
|
|
|
335
385
|
def save(self) -> None:
|
|
336
386
|
SETTINGS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -340,13 +390,9 @@ class RealtimeSettings:
|
|
|
340
390
|
return {
|
|
341
391
|
"provider": self.provider,
|
|
342
392
|
"api_key": self.api_key,
|
|
343
|
-
"
|
|
344
|
-
"
|
|
345
|
-
"
|
|
346
|
-
"voice": self.voice,
|
|
347
|
-
"vad_threshold": self.vad_threshold,
|
|
348
|
-
"silence_duration_ms": self.silence_duration_ms,
|
|
349
|
-
"prefix_padding_ms": self.prefix_padding_ms,
|
|
393
|
+
"openai": asdict(self.openai),
|
|
394
|
+
"azure": asdict(self.azure),
|
|
395
|
+
"gemini": asdict(self.gemini),
|
|
350
396
|
"agent_timeout": self.agent_timeout,
|
|
351
397
|
"language": self.language,
|
|
352
398
|
"permission_mode": self.permission_mode,
|
|
@@ -360,16 +406,15 @@ class RealtimeSettings:
|
|
|
360
406
|
return cls()
|
|
361
407
|
try:
|
|
362
408
|
data = json.loads(SETTINGS_PATH.read_text())
|
|
409
|
+
# Migrate flat format → nested format
|
|
410
|
+
if "openai_model" in data or "vad_threshold" in data:
|
|
411
|
+
data = cls._migrate_flat(data)
|
|
363
412
|
return cls(
|
|
364
413
|
provider=data.get("provider", "openai"),
|
|
365
414
|
api_key=data.get("api_key", ""),
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
voice=data.get("voice", "ash"),
|
|
370
|
-
vad_threshold=data.get("vad_threshold", 0.8),
|
|
371
|
-
silence_duration_ms=data.get("silence_duration_ms", 700),
|
|
372
|
-
prefix_padding_ms=data.get("prefix_padding_ms", 300),
|
|
415
|
+
openai=cls._load_openai(data.get("openai", {})),
|
|
416
|
+
azure=cls._load_azure(data.get("azure", {})),
|
|
417
|
+
gemini=cls._load_gemini(data.get("gemini", {})),
|
|
373
418
|
agent_timeout=data.get("agent_timeout", 300),
|
|
374
419
|
language=data.get("language", "en"),
|
|
375
420
|
permission_mode=data.get("permission_mode", "bypass"),
|
|
@@ -379,6 +424,72 @@ class RealtimeSettings:
|
|
|
379
424
|
except (json.JSONDecodeError, OSError):
|
|
380
425
|
return cls()
|
|
381
426
|
|
|
427
|
+
@staticmethod
|
|
428
|
+
def _load_openai(d: dict) -> OpenAIConfig:
|
|
429
|
+
return OpenAIConfig(
|
|
430
|
+
model=d.get("model", OPENAI_DEFAULT_MODEL),
|
|
431
|
+
voice=d.get("voice", "ash"),
|
|
432
|
+
vad_threshold=d.get("vad_threshold", 0.8),
|
|
433
|
+
silence_duration_ms=d.get("silence_duration_ms", 700),
|
|
434
|
+
prefix_padding_ms=d.get("prefix_padding_ms", 300),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
@staticmethod
|
|
438
|
+
def _load_azure(d: dict) -> AzureConfig:
|
|
439
|
+
return AzureConfig(
|
|
440
|
+
endpoint=d.get("endpoint", AZURE_DEFAULT_ENDPOINT),
|
|
441
|
+
deployment=d.get("deployment", AZURE_DEFAULT_DEPLOYMENT),
|
|
442
|
+
voice=d.get("voice", "ash"),
|
|
443
|
+
vad_threshold=d.get("vad_threshold", 0.8),
|
|
444
|
+
silence_duration_ms=d.get("silence_duration_ms", 700),
|
|
445
|
+
prefix_padding_ms=d.get("prefix_padding_ms", 300),
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
@staticmethod
|
|
449
|
+
def _load_gemini(d: dict) -> GeminiConfig:
|
|
450
|
+
return GeminiConfig(
|
|
451
|
+
model=d.get("model", GEMINI_DEFAULT_MODEL),
|
|
452
|
+
voice=d.get("voice", "Puck"),
|
|
453
|
+
start_sensitivity=d.get("start_sensitivity", "HIGH"),
|
|
454
|
+
end_sensitivity=d.get("end_sensitivity", "LOW"),
|
|
455
|
+
silence_duration_ms=d.get("silence_duration_ms", 700),
|
|
456
|
+
prefix_padding_ms=d.get("prefix_padding_ms", 100),
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
@staticmethod
|
|
460
|
+
def _migrate_flat(data: dict) -> dict:
|
|
461
|
+
"""Migrate old flat-key format into nested provider dicts."""
|
|
462
|
+
voice = data.get("voice", "ash")
|
|
463
|
+
vad_t = data.get("vad_threshold", 0.8)
|
|
464
|
+
silence = data.get("silence_duration_ms", 700)
|
|
465
|
+
prefix = data.get("prefix_padding_ms", 300)
|
|
466
|
+
|
|
467
|
+
data["openai"] = {
|
|
468
|
+
"model": data.pop("openai_model", OPENAI_DEFAULT_MODEL),
|
|
469
|
+
"voice": voice,
|
|
470
|
+
"vad_threshold": vad_t,
|
|
471
|
+
"silence_duration_ms": silence,
|
|
472
|
+
"prefix_padding_ms": prefix,
|
|
473
|
+
}
|
|
474
|
+
data["azure"] = {
|
|
475
|
+
"endpoint": data.pop("azure_endpoint", AZURE_DEFAULT_ENDPOINT),
|
|
476
|
+
"deployment": data.pop("azure_deployment", AZURE_DEFAULT_DEPLOYMENT),
|
|
477
|
+
"voice": voice,
|
|
478
|
+
"vad_threshold": vad_t,
|
|
479
|
+
"silence_duration_ms": silence,
|
|
480
|
+
"prefix_padding_ms": prefix,
|
|
481
|
+
}
|
|
482
|
+
data["gemini"] = {
|
|
483
|
+
"model": data.pop("gemini_model", GEMINI_DEFAULT_MODEL),
|
|
484
|
+
"voice": voice if voice in GEMINI_VOICES else "Puck",
|
|
485
|
+
"start_sensitivity": "HIGH",
|
|
486
|
+
"end_sensitivity": "HIGH",
|
|
487
|
+
}
|
|
488
|
+
# Remove migrated flat keys
|
|
489
|
+
for k in ("voice", "vad_threshold", "silence_duration_ms", "prefix_padding_ms"):
|
|
490
|
+
data.pop(k, None)
|
|
491
|
+
return data
|
|
492
|
+
|
|
382
493
|
@staticmethod
|
|
383
494
|
def clear() -> None:
|
|
384
495
|
try:
|
|
@@ -40,7 +40,7 @@ from voice_vibecoder.app_config import VoiceConfig
|
|
|
40
40
|
from voice_vibecoder.config import RealtimeSettings
|
|
41
41
|
from voice_vibecoder.instances import InstanceStatus
|
|
42
42
|
from voice_vibecoder.session import RealtimeSession
|
|
43
|
-
from voice_vibecoder.voice_providers
|
|
43
|
+
from voice_vibecoder.voice_providers import create_provider
|
|
44
44
|
|
|
45
45
|
logger = logging.getLogger(__name__)
|
|
46
46
|
|
|
@@ -70,10 +70,7 @@ class WebSocketBridge:
|
|
|
70
70
|
self._user_jwt = user_jwt
|
|
71
71
|
|
|
72
72
|
async def run(self) -> None:
|
|
73
|
-
provider =
|
|
74
|
-
ws_url=self._settings.ws_url,
|
|
75
|
-
ws_headers=self._settings.ws_headers,
|
|
76
|
-
)
|
|
73
|
+
provider = create_provider(self._settings)
|
|
77
74
|
|
|
78
75
|
self._session = RealtimeSession(
|
|
79
76
|
provider=provider,
|
|
@@ -30,6 +30,7 @@ from voice_vibecoder.voice_providers import (
|
|
|
30
30
|
AudioCommitted,
|
|
31
31
|
AudioDelta,
|
|
32
32
|
FunctionCall,
|
|
33
|
+
GeminiVADSettings,
|
|
33
34
|
ResponseCreated,
|
|
34
35
|
ResponseDone,
|
|
35
36
|
SpeechStarted,
|
|
@@ -142,17 +143,26 @@ class RealtimeSession:
|
|
|
142
143
|
active_instances=get_active_instances_text(),
|
|
143
144
|
)
|
|
144
145
|
|
|
145
|
-
vad: VADSettings | None = None
|
|
146
|
+
vad: VADSettings | GeminiVADSettings | None = None
|
|
146
147
|
if s.input_mode != "ptt":
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
if s.provider == "gemini":
|
|
149
|
+
vad = GeminiVADSettings(
|
|
150
|
+
start_sensitivity=s.gemini.start_sensitivity,
|
|
151
|
+
end_sensitivity=s.gemini.end_sensitivity,
|
|
152
|
+
silence_duration_ms=s.gemini.silence_duration_ms,
|
|
153
|
+
prefix_padding_ms=s.gemini.prefix_padding_ms,
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
cfg = s.openai if s.provider == "openai" else s.azure
|
|
157
|
+
vad = VADSettings(
|
|
158
|
+
threshold=cfg.vad_threshold,
|
|
159
|
+
silence_duration_ms=cfg.silence_duration_ms,
|
|
160
|
+
prefix_padding_ms=cfg.prefix_padding_ms,
|
|
161
|
+
)
|
|
152
162
|
|
|
153
163
|
await self._provider.configure(
|
|
154
164
|
instructions=instructions,
|
|
155
|
-
voice=s.
|
|
165
|
+
voice=s.active_voice,
|
|
156
166
|
tools=get_tool_definitions(),
|
|
157
167
|
vad=vad,
|
|
158
168
|
)
|
|
@@ -174,7 +174,7 @@ class VoiceCodingScreen(PanelMixin, CallbackMixin, Screen):
|
|
|
174
174
|
self._input_mode = settings.input_mode
|
|
175
175
|
|
|
176
176
|
self.app.call_from_thread(
|
|
177
|
-
self._log, f"[dim]{settings.provider_label} | voice: {settings.
|
|
177
|
+
self._log, f"[dim]{settings.provider_label} | voice: {settings.active_voice}[/dim]"
|
|
178
178
|
)
|
|
179
179
|
self.app.call_from_thread(self._log, "[dim]Connecting...[/dim]")
|
|
180
180
|
self.app.call_from_thread(self._start_session, settings)
|
|
@@ -315,14 +315,10 @@ class VoiceCodingScreen(PanelMixin, CallbackMixin, Screen):
|
|
|
315
315
|
# -- Session lifecycle --
|
|
316
316
|
|
|
317
317
|
def _start_session(self, settings) -> None:
|
|
318
|
-
from voice_vibecoder.voice_providers
|
|
318
|
+
from voice_vibecoder.voice_providers import create_provider
|
|
319
319
|
from voice_vibecoder.session import RealtimeSession
|
|
320
320
|
|
|
321
|
-
provider =
|
|
322
|
-
ws_url=settings.ws_url,
|
|
323
|
-
ws_headers=settings.ws_headers,
|
|
324
|
-
transcription_model="gpt-4o-mini-transcribe",
|
|
325
|
-
)
|
|
321
|
+
provider = create_provider(settings)
|
|
326
322
|
|
|
327
323
|
self._session = RealtimeSession(
|
|
328
324
|
provider=provider,
|