voice-mode 2.34.2__py3-none-any.whl → 4.0.1__py3-none-any.whl
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_mode/__version__.py +1 -1
- voice_mode/cli.py +5 -0
- voice_mode/cli_commands/transcribe.py +141 -0
- voice_mode/config.py +139 -37
- voice_mode/frontend/.next/BUILD_ID +1 -0
- voice_mode/frontend/.next/app-build-manifest.json +28 -0
- voice_mode/frontend/.next/app-path-routes-manifest.json +1 -0
- voice_mode/frontend/.next/build-manifest.json +32 -0
- voice_mode/frontend/.next/export-marker.json +1 -0
- voice_mode/frontend/.next/images-manifest.json +1 -0
- voice_mode/frontend/.next/next-minimal-server.js.nft.json +1 -0
- voice_mode/frontend/.next/next-server.js.nft.json +1 -0
- voice_mode/frontend/.next/package.json +1 -0
- voice_mode/frontend/.next/prerender-manifest.json +1 -0
- voice_mode/frontend/.next/react-loadable-manifest.json +1 -0
- voice_mode/frontend/.next/required-server-files.json +1 -0
- voice_mode/frontend/.next/routes-manifest.json +1 -0
- voice_mode/frontend/.next/server/app/_not-found/page.js +1 -0
- voice_mode/frontend/.next/server/app/_not-found/page.js.nft.json +1 -0
- voice_mode/frontend/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- voice_mode/frontend/.next/server/app/_not-found.html +1 -0
- voice_mode/frontend/.next/server/app/_not-found.meta +6 -0
- voice_mode/frontend/.next/server/app/_not-found.rsc +9 -0
- voice_mode/frontend/.next/server/app/api/connection-details/route.js +12 -0
- voice_mode/frontend/.next/server/app/api/connection-details/route.js.nft.json +1 -0
- voice_mode/frontend/.next/server/app/favicon.ico/route.js +12 -0
- voice_mode/frontend/.next/server/app/favicon.ico/route.js.nft.json +1 -0
- voice_mode/frontend/.next/server/app/favicon.ico.body +0 -0
- voice_mode/frontend/.next/server/app/favicon.ico.meta +1 -0
- voice_mode/frontend/.next/server/app/index.html +1 -0
- voice_mode/frontend/.next/server/app/index.meta +5 -0
- voice_mode/frontend/.next/server/app/index.rsc +7 -0
- voice_mode/frontend/.next/server/app/page.js +11 -0
- voice_mode/frontend/.next/server/app/page.js.nft.json +1 -0
- voice_mode/frontend/.next/server/app/page_client-reference-manifest.js +1 -0
- voice_mode/frontend/.next/server/app-paths-manifest.json +6 -0
- voice_mode/frontend/.next/server/chunks/463.js +1 -0
- voice_mode/frontend/.next/server/chunks/682.js +6 -0
- voice_mode/frontend/.next/server/chunks/948.js +2 -0
- voice_mode/frontend/.next/server/chunks/994.js +2 -0
- voice_mode/frontend/.next/server/chunks/font-manifest.json +1 -0
- voice_mode/frontend/.next/server/font-manifest.json +1 -0
- voice_mode/frontend/.next/server/functions-config-manifest.json +1 -0
- voice_mode/frontend/.next/server/interception-route-rewrite-manifest.js +1 -0
- voice_mode/frontend/.next/server/middleware-build-manifest.js +1 -0
- voice_mode/frontend/.next/server/middleware-manifest.json +6 -0
- voice_mode/frontend/.next/server/middleware-react-loadable-manifest.js +1 -0
- voice_mode/frontend/.next/server/next-font-manifest.js +1 -0
- voice_mode/frontend/.next/server/next-font-manifest.json +1 -0
- voice_mode/frontend/.next/server/pages/404.html +1 -0
- voice_mode/frontend/.next/server/pages/500.html +1 -0
- voice_mode/frontend/.next/server/pages/_app.js +1 -0
- voice_mode/frontend/.next/server/pages/_app.js.nft.json +1 -0
- voice_mode/frontend/.next/server/pages/_document.js +1 -0
- voice_mode/frontend/.next/server/pages/_document.js.nft.json +1 -0
- voice_mode/frontend/.next/server/pages/_error.js +1 -0
- voice_mode/frontend/.next/server/pages/_error.js.nft.json +1 -0
- voice_mode/frontend/.next/server/pages-manifest.json +1 -0
- voice_mode/frontend/.next/server/server-reference-manifest.js +1 -0
- voice_mode/frontend/.next/server/server-reference-manifest.json +1 -0
- voice_mode/frontend/.next/server/webpack-runtime.js +1 -0
- voice_mode/frontend/.next/standalone/.next/BUILD_ID +1 -0
- voice_mode/frontend/.next/standalone/.next/app-build-manifest.json +28 -0
- voice_mode/frontend/.next/standalone/.next/app-path-routes-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/build-manifest.json +32 -0
- voice_mode/frontend/.next/standalone/.next/package.json +1 -0
- voice_mode/frontend/.next/standalone/.next/prerender-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/react-loadable-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/required-server-files.json +1 -0
- voice_mode/frontend/.next/standalone/.next/routes-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.html +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.meta +6 -0
- voice_mode/frontend/.next/standalone/.next/server/app/_not-found.rsc +9 -0
- voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js +12 -0
- voice_mode/frontend/.next/standalone/.next/server/app/api/connection-details/route.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js +12 -0
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico/route.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico.body +0 -0
- voice_mode/frontend/.next/standalone/.next/server/app/favicon.ico.meta +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/index.html +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/index.meta +5 -0
- voice_mode/frontend/.next/standalone/.next/server/app/index.rsc +7 -0
- voice_mode/frontend/.next/standalone/.next/server/app/page.js +11 -0
- voice_mode/frontend/.next/standalone/.next/server/app/page.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/app-paths-manifest.json +6 -0
- voice_mode/frontend/.next/standalone/.next/server/chunks/463.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/chunks/682.js +6 -0
- voice_mode/frontend/.next/standalone/.next/server/chunks/948.js +2 -0
- voice_mode/frontend/.next/standalone/.next/server/chunks/994.js +2 -0
- voice_mode/frontend/.next/standalone/.next/server/font-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/middleware-build-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/middleware-manifest.json +6 -0
- voice_mode/frontend/.next/standalone/.next/server/middleware-react-loadable-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/next-font-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/404.html +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/500.html +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_app.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_app.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_document.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_document.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_error.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages/_error.js.nft.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/pages-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.js +1 -0
- voice_mode/frontend/.next/standalone/.next/server/server-reference-manifest.json +1 -0
- voice_mode/frontend/.next/standalone/.next/server/webpack-runtime.js +1 -0
- voice_mode/frontend/.next/standalone/package.json +40 -0
- voice_mode/frontend/.next/standalone/server.js +38 -0
- voice_mode/frontend/.next/static/c5TIe90lGzrESrqJkkXQa/_buildManifest.js +1 -0
- voice_mode/frontend/.next/static/c5TIe90lGzrESrqJkkXQa/_ssgManifest.js +1 -0
- voice_mode/frontend/.next/static/chunks/117-40bc79a2b97edb21.js +2 -0
- voice_mode/frontend/.next/static/chunks/144d3bae-2d5f122b82426d88.js +1 -0
- voice_mode/frontend/.next/static/chunks/471-bd4b96a33883dfa2.js +3 -0
- voice_mode/frontend/.next/static/chunks/app/_not-found/page-5011050e402ab9c8.js +1 -0
- voice_mode/frontend/.next/static/chunks/app/layout-0074dd8ab91cdbe0.js +1 -0
- voice_mode/frontend/.next/static/chunks/app/page-ae5f3aa9d9ba5993.js +1 -0
- voice_mode/frontend/.next/static/chunks/fd9d1056-af324d327b243cf1.js +1 -0
- voice_mode/frontend/.next/static/chunks/framework-f66176bb897dc684.js +1 -0
- voice_mode/frontend/.next/static/chunks/main-3163eca598b76a9f.js +1 -0
- voice_mode/frontend/.next/static/chunks/main-app-233f6c633f73ae84.js +1 -0
- voice_mode/frontend/.next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
- voice_mode/frontend/.next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
- voice_mode/frontend/.next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- voice_mode/frontend/.next/static/chunks/webpack-0ea9b80f19935b70.js +1 -0
- voice_mode/frontend/.next/static/css/a2f49a47752b5010.css +3 -0
- voice_mode/frontend/.next/static/media/01099be941da1820-s.woff2 +0 -0
- voice_mode/frontend/.next/static/media/39883d31a7792467-s.p.woff2 +0 -0
- voice_mode/frontend/.next/static/media/6368404d2e8d66fe-s.woff2 +0 -0
- voice_mode/frontend/.next/trace +43 -0
- voice_mode/frontend/.next/types/app/api/connection-details/route.ts +343 -0
- voice_mode/frontend/.next/types/app/layout.ts +79 -0
- voice_mode/frontend/.next/types/app/page.ts +79 -0
- voice_mode/frontend/.next/types/package.json +1 -0
- voice_mode/frontend/package-lock.json +154 -1
- voice_mode/providers.py +7 -8
- voice_mode/resources/configuration.py +2 -2
- voice_mode/tools/configuration_management.py +106 -5
- voice_mode/tools/converse.py +98 -0
- voice_mode/tools/service.py +1 -7
- voice_mode/tools/transcription/__init__.py +14 -0
- voice_mode/tools/transcription/backends.py +287 -0
- voice_mode/tools/transcription/core.py +136 -0
- voice_mode/tools/transcription/formats.py +144 -0
- voice_mode/tools/transcription/types.py +52 -0
- voice_mode/utils/services/kokoro_helpers.py +16 -3
- {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/METADATA +5 -2
- voice_mode-4.0.1.dist-info/RECORD +255 -0
- voice_mode/voice_preferences.py +0 -125
- voice_mode-2.34.2.dist-info/RECORD +0 -116
- {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/WHEEL +0 -0
- {voice_mode-2.34.2.dist-info → voice_mode-4.0.1.dist-info}/entry_points.txt +0 -0
voice_mode/__version__.py
CHANGED
voice_mode/cli.py
CHANGED
@@ -1359,13 +1359,18 @@ def cli():
|
|
1359
1359
|
|
1360
1360
|
# Import subcommand groups
|
1361
1361
|
from voice_mode.cli_commands import exchanges as exchanges_cmd
|
1362
|
+
from voice_mode.cli_commands import transcribe as transcribe_cmd
|
1362
1363
|
|
1363
1364
|
# Add subcommands to legacy CLI
|
1364
1365
|
cli.add_command(exchanges_cmd.exchanges)
|
1366
|
+
cli.add_command(transcribe_cmd.transcribe)
|
1365
1367
|
|
1366
1368
|
# Add exchanges to main CLI
|
1367
1369
|
voice_mode_main_cli.add_command(exchanges_cmd.exchanges)
|
1368
1370
|
|
1371
|
+
# Add transcribe to main CLI
|
1372
|
+
voice_mode_main_cli.add_command(transcribe_cmd.transcribe)
|
1373
|
+
|
1369
1374
|
|
1370
1375
|
# Converse command - direct voice conversation from CLI
|
1371
1376
|
@voice_mode_main_cli.command()
|
@@ -0,0 +1,141 @@
|
|
1
|
+
"""CLI command for audio transcription."""
|
2
|
+
|
3
|
+
import click
|
4
|
+
import json
|
5
|
+
import asyncio
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import Optional
|
8
|
+
|
9
|
+
from voice_mode.tools.transcription import (
|
10
|
+
transcribe_audio,
|
11
|
+
TranscriptionBackend,
|
12
|
+
OutputFormat
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
@click.group()
|
17
|
+
def transcribe():
|
18
|
+
"""Audio transcription with word-level timestamps."""
|
19
|
+
pass
|
20
|
+
|
21
|
+
|
22
|
+
@transcribe.command("audio")
|
23
|
+
@click.argument('audio_file', type=click.Path(exists=True))
|
24
|
+
@click.option('--words', is_flag=True, help='Include word-level timestamps')
|
25
|
+
@click.option(
|
26
|
+
'--backend',
|
27
|
+
type=click.Choice(['openai', 'whisperx', 'whisper-cpp']),
|
28
|
+
default='openai',
|
29
|
+
help='Transcription backend to use'
|
30
|
+
)
|
31
|
+
@click.option(
|
32
|
+
'--format',
|
33
|
+
'output_format',
|
34
|
+
type=click.Choice(['json', 'srt', 'vtt', 'csv']),
|
35
|
+
default='json',
|
36
|
+
help='Output format for transcription'
|
37
|
+
)
|
38
|
+
@click.option('--output', '-o', type=click.Path(), help='Save transcription to file')
|
39
|
+
@click.option('--language', help='Language code (e.g., en, es, fr)')
|
40
|
+
@click.option('--model', default='whisper-1', help='Model to use (for OpenAI backend)')
|
41
|
+
def audio_command(
|
42
|
+
audio_file: str,
|
43
|
+
words: bool,
|
44
|
+
backend: str,
|
45
|
+
output_format: str,
|
46
|
+
output: Optional[str],
|
47
|
+
language: Optional[str],
|
48
|
+
model: str
|
49
|
+
):
|
50
|
+
"""
|
51
|
+
Transcribe audio with optional word-level timestamps.
|
52
|
+
|
53
|
+
Examples:
|
54
|
+
|
55
|
+
voice-mode transcribe audio recording.mp3
|
56
|
+
|
57
|
+
voice-mode transcribe audio interview.wav --words
|
58
|
+
|
59
|
+
voice-mode transcribe audio podcast.mp3 --words --format srt -o subtitles.srt
|
60
|
+
|
61
|
+
voice-mode transcribe audio spanish.mp3 --language es --backend whisperx
|
62
|
+
"""
|
63
|
+
async def run():
|
64
|
+
# Perform transcription
|
65
|
+
result = await transcribe_audio(
|
66
|
+
audio_file=audio_file,
|
67
|
+
word_timestamps=words,
|
68
|
+
backend=TranscriptionBackend(backend),
|
69
|
+
output_format=OutputFormat(output_format),
|
70
|
+
language=language,
|
71
|
+
model=model
|
72
|
+
)
|
73
|
+
|
74
|
+
# Check for errors
|
75
|
+
if not result.get("success", False):
|
76
|
+
error_msg = result.get("error", "Unknown error occurred")
|
77
|
+
click.echo(f"Error: {error_msg}", err=True)
|
78
|
+
return
|
79
|
+
|
80
|
+
# Format output
|
81
|
+
if output_format == 'json':
|
82
|
+
# Remove internal fields for cleaner output
|
83
|
+
output_result = {k: v for k, v in result.items()
|
84
|
+
if k not in ['formatted_content']}
|
85
|
+
content = json.dumps(output_result, indent=2)
|
86
|
+
elif "formatted_content" in result:
|
87
|
+
content = result["formatted_content"]
|
88
|
+
else:
|
89
|
+
# Fallback to JSON if format conversion failed
|
90
|
+
content = json.dumps(result, indent=2)
|
91
|
+
|
92
|
+
# Write output
|
93
|
+
if output:
|
94
|
+
Path(output).write_text(content)
|
95
|
+
click.echo(f"Transcription saved to {output}")
|
96
|
+
else:
|
97
|
+
click.echo(content)
|
98
|
+
|
99
|
+
# Run async function
|
100
|
+
asyncio.run(run())
|
101
|
+
|
102
|
+
|
103
|
+
# For backward compatibility, also provide a direct command
|
104
|
+
@click.command('transcribe-audio')
|
105
|
+
@click.argument('audio_file', type=click.Path(exists=True))
|
106
|
+
@click.option('--words', is_flag=True, help='Include word-level timestamps')
|
107
|
+
@click.option(
|
108
|
+
'--backend',
|
109
|
+
type=click.Choice(['openai', 'whisperx', 'whisper-cpp']),
|
110
|
+
default='openai',
|
111
|
+
help='Transcription backend'
|
112
|
+
)
|
113
|
+
@click.option(
|
114
|
+
'--format',
|
115
|
+
'output_format',
|
116
|
+
type=click.Choice(['json', 'srt', 'vtt', 'csv']),
|
117
|
+
default='json',
|
118
|
+
help='Output format'
|
119
|
+
)
|
120
|
+
@click.option('--output', '-o', type=click.Path(), help='Save to file')
|
121
|
+
@click.option('--language', help='Language code')
|
122
|
+
@click.option('--model', default='whisper-1', help='Model to use')
|
123
|
+
def transcribe_audio_command(
|
124
|
+
audio_file: str,
|
125
|
+
words: bool,
|
126
|
+
backend: str,
|
127
|
+
output_format: str,
|
128
|
+
output: Optional[str],
|
129
|
+
language: Optional[str],
|
130
|
+
model: str
|
131
|
+
):
|
132
|
+
"""Direct transcription command for backward compatibility."""
|
133
|
+
audio_command.callback(
|
134
|
+
audio_file=audio_file,
|
135
|
+
words=words,
|
136
|
+
backend=backend,
|
137
|
+
output_format=output_format,
|
138
|
+
output=output,
|
139
|
+
language=language,
|
140
|
+
model=model
|
141
|
+
)
|
voice_mode/config.py
CHANGED
@@ -15,21 +15,66 @@ from datetime import datetime
|
|
15
15
|
|
16
16
|
# ==================== ENVIRONMENT CONFIGURATION ====================
|
17
17
|
|
18
|
+
def find_voicemode_env_files() -> list[Path]:
|
19
|
+
"""
|
20
|
+
Find .voicemode.env files by walking up the directory tree.
|
21
|
+
|
22
|
+
Looks for (in order of priority - closest to current directory wins):
|
23
|
+
1. .voicemode.env in current or parent directories
|
24
|
+
2. .voicemode/voicemode.env in current or parent directories
|
25
|
+
3. ~/.voicemode/voicemode.env in user home (global config)
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
List of Path objects in loading order (global first, then project-specific)
|
29
|
+
"""
|
30
|
+
config_files = []
|
31
|
+
|
32
|
+
# First add global config (lowest priority - loaded first)
|
33
|
+
global_config = Path.home() / ".voicemode" / "voicemode.env"
|
34
|
+
|
35
|
+
# Backwards compatibility: check for old filename
|
36
|
+
if not global_config.exists():
|
37
|
+
old_global = Path.home() / ".voicemode" / ".voicemode.env"
|
38
|
+
if old_global.exists():
|
39
|
+
global_config = old_global
|
40
|
+
|
41
|
+
if global_config.exists():
|
42
|
+
config_files.append(global_config)
|
43
|
+
|
44
|
+
# Then walk up directory tree for project-specific configs (higher priority)
|
45
|
+
current_dir = Path.cwd()
|
46
|
+
project_configs = []
|
47
|
+
|
48
|
+
while current_dir != current_dir.parent:
|
49
|
+
# Check for standalone .voicemode.env first
|
50
|
+
standalone_file = current_dir / ".voicemode.env"
|
51
|
+
if standalone_file.exists():
|
52
|
+
project_configs.append(standalone_file)
|
53
|
+
break # Stop at first found (closest wins)
|
54
|
+
|
55
|
+
# Then check .voicemode/voicemode.env
|
56
|
+
dir_file = current_dir / ".voicemode" / "voicemode.env"
|
57
|
+
# Skip if this is the global config file (already added)
|
58
|
+
if dir_file.exists() and dir_file != global_config:
|
59
|
+
project_configs.append(dir_file)
|
60
|
+
break # Stop at first found (closest wins)
|
61
|
+
|
62
|
+
current_dir = current_dir.parent
|
63
|
+
|
64
|
+
# Add project configs (they were collected closest-first, so add as-is)
|
65
|
+
config_files.extend(project_configs)
|
66
|
+
|
67
|
+
return config_files
|
68
|
+
|
69
|
+
|
18
70
|
def load_voicemode_env():
|
19
|
-
"""Load configuration from voicemode.env
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if old_path.exists():
|
27
|
-
config_path = old_path
|
28
|
-
print(f"Warning: Using deprecated .voicemode.env - please rename to voicemode.env")
|
29
|
-
|
30
|
-
if not config_path.exists():
|
31
|
-
# Create default template
|
32
|
-
config_path.parent.mkdir(parents=True, exist_ok=True)
|
71
|
+
"""Load configuration from voicemode.env files, with cascading from global to project-specific."""
|
72
|
+
config_files = find_voicemode_env_files()
|
73
|
+
|
74
|
+
# If no config files found, create default global config
|
75
|
+
if not config_files:
|
76
|
+
default_path = Path.home() / ".voicemode" / "voicemode.env"
|
77
|
+
default_path.parent.mkdir(parents=True, exist_ok=True)
|
33
78
|
default_config = '''# Voice Mode Configuration File
|
34
79
|
# This file is automatically generated and can be customized
|
35
80
|
# Environment variables always take precedence over this file
|
@@ -66,8 +111,8 @@ def load_voicemode_env():
|
|
66
111
|
# Comma-separated list of STT endpoints
|
67
112
|
# VOICEMODE_STT_BASE_URLS=http://127.0.0.1:2022/v1,https://api.openai.com/v1
|
68
113
|
|
69
|
-
# Comma-separated list of preferred voices
|
70
|
-
#
|
114
|
+
# Comma-separated list of preferred voices
|
115
|
+
# VOICEMODE_VOICES=af_sky,alloy
|
71
116
|
|
72
117
|
# Comma-separated list of preferred models
|
73
118
|
# VOICEMODE_TTS_MODELS=tts-1,tts-1-hd,gpt-4o-mini-tts
|
@@ -127,26 +172,28 @@ def load_voicemode_env():
|
|
127
172
|
# LIVEKIT_API_KEY=devkey
|
128
173
|
# LIVEKIT_API_SECRET=secret
|
129
174
|
'''
|
130
|
-
with open(
|
175
|
+
with open(default_path, 'w') as f:
|
131
176
|
f.write(default_config)
|
132
|
-
os.chmod(
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
177
|
+
os.chmod(default_path, 0o600) # Secure permissions
|
178
|
+
config_files = [default_path]
|
179
|
+
|
180
|
+
# Load configuration from all files in order (global first, project-specific last)
|
181
|
+
for config_path in config_files:
|
182
|
+
if config_path.exists():
|
183
|
+
with open(config_path, 'r') as f:
|
184
|
+
for line in f:
|
185
|
+
line = line.strip()
|
186
|
+
# Skip comments and empty lines
|
187
|
+
if not line or line.startswith('#'):
|
188
|
+
continue
|
189
|
+
# Parse KEY=VALUE format
|
190
|
+
if '=' in line:
|
191
|
+
key, value = line.split('=', 1)
|
192
|
+
key = key.strip()
|
193
|
+
value = value.strip()
|
194
|
+
# Only set if not already in environment (env vars take precedence)
|
195
|
+
if key and key not in os.environ:
|
196
|
+
os.environ[key] = value
|
150
197
|
|
151
198
|
# Load configuration file before other configuration
|
152
199
|
load_voicemode_env()
|
@@ -222,13 +269,68 @@ def parse_comma_list(env_var: str, fallback: str) -> list:
|
|
222
269
|
# New provider endpoint lists configuration
|
223
270
|
TTS_BASE_URLS = parse_comma_list("VOICEMODE_TTS_BASE_URLS", "http://127.0.0.1:8880/v1,https://api.openai.com/v1")
|
224
271
|
STT_BASE_URLS = parse_comma_list("VOICEMODE_STT_BASE_URLS", "http://127.0.0.1:2022/v1,https://api.openai.com/v1")
|
225
|
-
TTS_VOICES = parse_comma_list("
|
272
|
+
TTS_VOICES = parse_comma_list("VOICEMODE_VOICES", "af_sky,alloy")
|
226
273
|
TTS_MODELS = parse_comma_list("VOICEMODE_TTS_MODELS", "tts-1,tts-1-hd,gpt-4o-mini-tts")
|
227
274
|
|
275
|
+
# Voice preferences cache
|
276
|
+
_cached_voice_preferences: Optional[list] = None
|
277
|
+
_voice_preferences_loaded = False
|
278
|
+
|
279
|
+
def get_voice_preferences() -> list[str]:
|
280
|
+
"""
|
281
|
+
Get voice preferences from configuration.
|
282
|
+
|
283
|
+
Uses the VOICEMODE_VOICES configuration which is loaded from:
|
284
|
+
1. Environment variables (highest priority)
|
285
|
+
2. Project-specific .voicemode.env files
|
286
|
+
3. Global ~/.voicemode/voicemode.env file
|
287
|
+
4. Built-in defaults
|
288
|
+
|
289
|
+
Returns:
|
290
|
+
List of voice names in preference order
|
291
|
+
"""
|
292
|
+
global _cached_voice_preferences, _voice_preferences_loaded
|
293
|
+
|
294
|
+
# Return cached preferences if already loaded
|
295
|
+
if _voice_preferences_loaded:
|
296
|
+
return _cached_voice_preferences or []
|
297
|
+
|
298
|
+
_voice_preferences_loaded = True
|
299
|
+
|
300
|
+
# Get voices from TTS_VOICES configuration
|
301
|
+
_cached_voice_preferences = TTS_VOICES.copy()
|
302
|
+
|
303
|
+
logger.info(f"Voice preferences loaded: {_cached_voice_preferences}")
|
304
|
+
return _cached_voice_preferences
|
305
|
+
|
306
|
+
def clear_voice_preferences_cache():
|
307
|
+
"""Clear the voice preferences cache, forcing a reload on next access."""
|
308
|
+
global _cached_voice_preferences, _voice_preferences_loaded
|
309
|
+
_cached_voice_preferences = None
|
310
|
+
_voice_preferences_loaded = False
|
311
|
+
logger.debug("Voice preferences cache cleared")
|
312
|
+
|
313
|
+
def reload_configuration():
|
314
|
+
"""Reload configuration from files and clear all caches."""
|
315
|
+
# Clear voice preferences cache
|
316
|
+
clear_voice_preferences_cache()
|
317
|
+
|
318
|
+
# Reload environment configuration
|
319
|
+
load_voicemode_env()
|
320
|
+
|
321
|
+
# Update global configuration variables
|
322
|
+
global TTS_VOICES, TTS_MODELS, TTS_BASE_URLS, STT_BASE_URLS
|
323
|
+
TTS_BASE_URLS = parse_comma_list("VOICEMODE_TTS_BASE_URLS", "http://127.0.0.1:8880/v1,https://api.openai.com/v1")
|
324
|
+
STT_BASE_URLS = parse_comma_list("VOICEMODE_STT_BASE_URLS", "http://127.0.0.1:2022/v1,https://api.openai.com/v1")
|
325
|
+
TTS_VOICES = parse_comma_list("VOICEMODE_VOICES", "af_sky,alloy")
|
326
|
+
TTS_MODELS = parse_comma_list("VOICEMODE_TTS_MODELS", "tts-1,tts-1-hd,gpt-4o-mini-tts")
|
327
|
+
|
328
|
+
logger.info("Configuration reloaded successfully")
|
329
|
+
|
228
330
|
# Legacy variables have been removed - use the new list-based configuration:
|
229
331
|
# - VOICEMODE_TTS_BASE_URLS (comma-separated list)
|
230
332
|
# - VOICEMODE_STT_BASE_URLS (comma-separated list)
|
231
|
-
# -
|
333
|
+
# - VOICEMODE_VOICES (comma-separated list)
|
232
334
|
# - VOICEMODE_TTS_MODELS (comma-separated list)
|
233
335
|
|
234
336
|
# LiveKit configuration
|
@@ -0,0 +1 @@
|
|
1
|
+
c5TIe90lGzrESrqJkkXQa
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"pages": {
|
3
|
+
"/_not-found/page": [
|
4
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
5
|
+
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
6
|
+
"static/chunks/117-40bc79a2b97edb21.js",
|
7
|
+
"static/chunks/main-app-233f6c633f73ae84.js",
|
8
|
+
"static/chunks/app/_not-found/page-5011050e402ab9c8.js"
|
9
|
+
],
|
10
|
+
"/layout": [
|
11
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
12
|
+
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
13
|
+
"static/chunks/117-40bc79a2b97edb21.js",
|
14
|
+
"static/chunks/main-app-233f6c633f73ae84.js",
|
15
|
+
"static/css/a2f49a47752b5010.css",
|
16
|
+
"static/chunks/app/layout-0074dd8ab91cdbe0.js"
|
17
|
+
],
|
18
|
+
"/page": [
|
19
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
20
|
+
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
21
|
+
"static/chunks/117-40bc79a2b97edb21.js",
|
22
|
+
"static/chunks/main-app-233f6c633f73ae84.js",
|
23
|
+
"static/chunks/144d3bae-2d5f122b82426d88.js",
|
24
|
+
"static/chunks/471-bd4b96a33883dfa2.js",
|
25
|
+
"static/chunks/app/page-ae5f3aa9d9ba5993.js"
|
26
|
+
]
|
27
|
+
}
|
28
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"/_not-found/page":"/_not-found","/favicon.ico/route":"/favicon.ico","/page":"/","/api/connection-details/route":"/api/connection-details"}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"polyfillFiles": [
|
3
|
+
"static/chunks/polyfills-42372ed130431b0a.js"
|
4
|
+
],
|
5
|
+
"devFiles": [],
|
6
|
+
"ampDevFiles": [],
|
7
|
+
"lowPriorityFiles": [
|
8
|
+
"static/c5TIe90lGzrESrqJkkXQa/_buildManifest.js",
|
9
|
+
"static/c5TIe90lGzrESrqJkkXQa/_ssgManifest.js"
|
10
|
+
],
|
11
|
+
"rootMainFiles": [
|
12
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
13
|
+
"static/chunks/fd9d1056-af324d327b243cf1.js",
|
14
|
+
"static/chunks/117-40bc79a2b97edb21.js",
|
15
|
+
"static/chunks/main-app-233f6c633f73ae84.js"
|
16
|
+
],
|
17
|
+
"pages": {
|
18
|
+
"/_app": [
|
19
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
20
|
+
"static/chunks/framework-f66176bb897dc684.js",
|
21
|
+
"static/chunks/main-3163eca598b76a9f.js",
|
22
|
+
"static/chunks/pages/_app-72b849fbd24ac258.js"
|
23
|
+
],
|
24
|
+
"/_error": [
|
25
|
+
"static/chunks/webpack-0ea9b80f19935b70.js",
|
26
|
+
"static/chunks/framework-f66176bb897dc684.js",
|
27
|
+
"static/chunks/main-3163eca598b76a9f.js",
|
28
|
+
"static/chunks/pages/_error-7ba65e1336b92748.js"
|
29
|
+
]
|
30
|
+
},
|
31
|
+
"ampFirstPages": []
|
32
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":1,"hasExportPathMap":false,"exportTrailingSlash":false,"isNextImageImported":false}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":1,"images":{"deviceSizes":[640,750,828,1080,1200,1920,2048,3840],"imageSizes":[16,32,48,64,96,128,256,384],"path":"/_next/image","loader":"default","loaderFile":"","domains":[],"disableStaticImages":false,"minimumCacheTTL":60,"formats":["image/webp"],"dangerouslyAllowSVG":false,"contentSecurityPolicy":"script-src 'none'; frame-src 'none'; sandbox;","contentDispositionType":"inline","remotePatterns":[],"unoptimized":false,"sizes":[640,750,828,1080,1200,1920,2048,3840,16,32,48,64,96,128,256,384]}}
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":1,"files":["../node_modules/styled-jsx/index.js","../node_modules/styled-jsx/package.json","../node_modules/styled-jsx/dist/index/index.js","../node_modules/react/package.json","../node_modules/react/index.js","../node_modules/client-only/package.json","../node_modules/react/cjs/react.production.min.js","../node_modules/client-only/index.js","../node_modules/styled-jsx/style.js","../node_modules/next/dist/compiled/next-server/server.runtime.prod.js","../node_modules/next/package.json","../node_modules/next/dist/lib/constants.js","../node_modules/next/dist/server/body-streams.js","../node_modules/next/dist/lib/picocolors.js","../node_modules/next/dist/shared/lib/constants.js","../node_modules/next/dist/server/web/utils.js","../node_modules/next/dist/client/components/app-router-headers.js","../node_modules/next/dist/server/lib/trace/tracer.js","../node_modules/next/dist/server/lib/trace/constants.js","../node_modules/next/dist/client/components/static-generation-async-storage.external.js","../node_modules/next/dist/shared/lib/error-source.js","../node_modules/next/dist/shared/lib/modern-browserslist-target.js","../node_modules/next/dist/compiled/debug/package.json","../node_modules/next/dist/client/components/static-generation-async-storage-instance.js","../node_modules/next/dist/shared/lib/runtime-config.external.js","../node_modules/next/dist/compiled/debug/index.js","../node_modules/next/dist/compiled/ws/package.json","../node_modules/next/dist/compiled/node-html-parser/package.json","../node_modules/next/dist/compiled/lru-cache/package.json","../node_modules/@swc/helpers/_/_interop_require_default/package.json","../node_modules/next/dist/compiled/ws/index.js","../node_modules/next/dist/compiled/node-html-parser/index.js","../node_modules/next/dist/compiled/lru-cache/index.js","../node_modules/next/dist/client/components/async-local-storage.js","../node_modules/next/dist/compiled/@opentelemetry/api/package.json","../node_modules/@swc/helpers/package.json","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/parseStack.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/nodeStackFrames.js","../node_modules/next/dist/compiled/jsonwebtoken/package.json","../node_modules/next/dist/client/components/react-dev-overlay/server/middleware.js","../node_modules/@swc/helpers/cjs/_interop_require_default.cjs","../node_modules/next/dist/compiled/@opentelemetry/api/index.js","../node_modules/next/dist/compiled/jsonwebtoken/index.js","../node_modules/next/dist/compiled/browserslist/package.json","../node_modules/next/dist/compiled/browserslist/index.js","../node_modules/next/dist/client/components/react-dev-overlay/server/shared.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/getRawSourceMap.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/launchEditor.js","../node_modules/next/dist/compiled/babel/code-frame.js","../node_modules/next/dist/compiled/json5/package.json","../node_modules/next/dist/compiled/semver/package.json","../node_modules/next/dist/compiled/babel/package.json","../node_modules/next/dist/lib/semver-noop.js","../node_modules/next/dist/compiled/json5/index.js","../node_modules/next/dist/compiled/semver/index.js","../node_modules/next/dist/compiled/stacktrace-parser/package.json","../node_modules/next/dist/compiled/source-map08/package.json","../node_modules/caniuse-lite/dist/unpacker/agents.js","../node_modules/caniuse-lite/dist/unpacker/feature.js","../node_modules/caniuse-lite/dist/unpacker/region.js","../node_modules/next/dist/compiled/babel/bundle.js","../node_modules/next/dist/client/components/react-dev-overlay/internal/helpers/getSourceMapUrl.js","../node_modules/next/dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js","../node_modules/next/dist/compiled/source-map08/source-map.js","../node_modules/caniuse-lite/package.json","../node_modules/next/dist/compiled/babel/core.js","../node_modules/caniuse-lite/data/agents.js","../node_modules/caniuse-lite/dist/unpacker/browsers.js","../node_modules/caniuse-lite/dist/unpacker/browserVersions.js","../node_modules/caniuse-lite/dist/lib/statuses.js","../node_modules/caniuse-lite/dist/lib/supported.js","../node_modules/next/dist/compiled/data-uri-to-buffer/package.json","../node_modules/next/dist/compiled/shell-quote/package.json","../node_modules/next/dist/compiled/data-uri-to-buffer/index.js","../node_modules/next/dist/compiled/shell-quote/index.js","../node_modules/caniuse-lite/data/browsers.js","../node_modules/caniuse-lite/data/browserVersions.js","../node_modules/next/dist/compiled/babel-packages/package.json","../node_modules/next/dist/compiled/babel-packages/packages-bundle.js","../node_modules/next/dist/compiled/babel/parser.js","../node_modules/next/dist/compiled/babel/traverse.js","../node_modules/next/dist/compiled/babel/types.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/amp-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/html-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/loadable-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/loadable.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/router-context.js","../node_modules/next/dist/server/future/route-modules/app-page/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/future/route-modules/app-page/module.compiled.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/amp-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/app-router-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/entrypoints.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/head-manager-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/hooks-client-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/html-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/image-config-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/loadable-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/loadable.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/router-context.js","../node_modules/next/dist/server/future/route-modules/pages/vendored/contexts/server-inserted-html.js","../node_modules/next/dist/server/future/route-modules/pages/module.compiled.js"]}
|