agentgui 1.0.207 → 1.0.209
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.
- package/CLAUDE.md +50 -0
- package/lib/speech.js +12 -1
- package/lib/webtalk-patch.js +33 -0
- package/lib/windows-pocket-tts-setup.js +209 -0
- package/package.json +1 -1
- package/readme.md +22 -0
- package/server.js +75 -2
package/CLAUDE.md
CHANGED
|
@@ -89,3 +89,53 @@ Server broadcasts:
|
|
|
89
89
|
- `streaming_complete` - Execution finished
|
|
90
90
|
- `streaming_error` - Execution failed
|
|
91
91
|
- `conversation_created`, `conversation_updated`, `conversation_deleted`
|
|
92
|
+
- `tts_setup_progress` - Windows pocket-tts setup progress (step, status, message)
|
|
93
|
+
|
|
94
|
+
## Pocket-TTS Windows Setup (Reliability for Slow/Bad Internet)
|
|
95
|
+
|
|
96
|
+
On Windows, text-to-speech uses pocket-tts which requires Python and pip install. The setup process is now resilient to slow/unreliable connections:
|
|
97
|
+
|
|
98
|
+
### Features
|
|
99
|
+
- **Extended timeouts**: 120s for pip install (accommodates slow connections)
|
|
100
|
+
- **Retry logic**: 3 attempts with exponential backoff (1s, 2s delays)
|
|
101
|
+
- **Progress reporting**: Real-time updates via WebSocket to UI
|
|
102
|
+
- **Partial install cleanup**: Failed venvs are removed to allow retry
|
|
103
|
+
- **Installation verification**: Binary validation via `--version` check
|
|
104
|
+
- **Concurrent waiting**: Multiple simultaneous requests wait for single setup (600s timeout)
|
|
105
|
+
|
|
106
|
+
### Configuration (lib/windows-pocket-tts-setup.js)
|
|
107
|
+
```javascript
|
|
108
|
+
const CONFIG = {
|
|
109
|
+
PIP_TIMEOUT: 120000, // 2 minutes
|
|
110
|
+
VENV_CREATION_TIMEOUT: 30000, // 30 seconds
|
|
111
|
+
MAX_RETRIES: 3, // 3 attempts
|
|
112
|
+
RETRY_DELAY_MS: 1000, // 1 second initial
|
|
113
|
+
RETRY_BACKOFF_MULTIPLIER: 2, // 2x exponential
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Network Requirements
|
|
118
|
+
- **Minimum**: 50 kbps sustained, < 5s latency, < 10% packet loss
|
|
119
|
+
- **Recommended**: 256+ kbps, < 2s latency, < 1% packet loss
|
|
120
|
+
- **Expected time on slow connection**: 2-6 minutes with retries
|
|
121
|
+
|
|
122
|
+
### Progress Messages
|
|
123
|
+
During TTS setup on first use, WebSocket broadcasts:
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"type": "tts_setup_progress",
|
|
127
|
+
"step": "detecting-python|creating-venv|installing|verifying",
|
|
128
|
+
"status": "in-progress|success|error",
|
|
129
|
+
"message": "descriptive status message with retry count if applicable"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Recovery Behavior
|
|
134
|
+
1. Network timeout → auto-retry with backoff
|
|
135
|
+
2. Partial venv → auto-cleanup before retry
|
|
136
|
+
3. Failed verification → auto-cleanup and error
|
|
137
|
+
4. Concurrent requests → first starts setup, others wait up to 600s
|
|
138
|
+
5. Interrupted setup → cleanup allows fresh retry
|
|
139
|
+
|
|
140
|
+
### Testing
|
|
141
|
+
Setup validates by running pocket-tts binary with `--version` flag to confirm functional installation, not just file existence.
|
package/lib/speech.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
4
5
|
import http from 'http';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { patchWebtalkForWindows } from './webtalk-patch.js';
|
|
6
8
|
|
|
7
9
|
const require = createRequire(import.meta.url);
|
|
8
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -11,6 +13,8 @@ const ROOT = path.dirname(__dirname);
|
|
|
11
13
|
const serverSTT = require('webtalk/server-stt');
|
|
12
14
|
const serverTTS = require('webtalk/server-tts');
|
|
13
15
|
|
|
16
|
+
patchWebtalkForWindows(serverTTS);
|
|
17
|
+
|
|
14
18
|
const EXTRA_VOICE_DIRS = [path.join(ROOT, 'voices')];
|
|
15
19
|
|
|
16
20
|
const POCKET_TTS_VOICES = [
|
|
@@ -120,7 +124,14 @@ function getStatus() {
|
|
|
120
124
|
function preloadTTS() {
|
|
121
125
|
const defaultVoice = serverTTS.findVoiceFile('custom_cleetus', EXTRA_VOICE_DIRS) || '/config/voices/cleetus.wav';
|
|
122
126
|
const voicePath = fs.existsSync(defaultVoice) ? defaultVoice : null;
|
|
123
|
-
|
|
127
|
+
const options = {
|
|
128
|
+
binaryPaths: [
|
|
129
|
+
path.join(os.homedir(), '.gmgui', 'pocket-venv', 'Scripts', 'pocket-tts.exe'),
|
|
130
|
+
path.join(os.homedir(), '.gmgui', 'pocket-venv', 'bin', 'pocket-tts.exe'),
|
|
131
|
+
path.join(os.homedir(), '.gmgui', 'pocket-venv', 'bin', 'pocket-tts'),
|
|
132
|
+
]
|
|
133
|
+
};
|
|
134
|
+
serverTTS.start(voicePath, options).then(ok => {
|
|
124
135
|
if (ok) console.log('[TTS] pocket-tts sidecar started');
|
|
125
136
|
else console.log('[TTS] pocket-tts failed to start');
|
|
126
137
|
}).catch(err => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
export function patchWebtalkForWindows(serverTTS) {
|
|
6
|
+
if (process.platform !== 'win32') return;
|
|
7
|
+
|
|
8
|
+
const venvDir = path.join(os.homedir(), '.gmgui', 'pocket-venv');
|
|
9
|
+
|
|
10
|
+
// Check if pocket-tts exists at Windows paths
|
|
11
|
+
const windowsBinaries = [
|
|
12
|
+
path.join(venvDir, 'Scripts', 'pocket-tts.exe'),
|
|
13
|
+
path.join(venvDir, 'bin', 'pocket-tts.exe'),
|
|
14
|
+
path.join(venvDir, 'bin', 'pocket-tts'),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const found = windowsBinaries.find(p => fs.existsSync(p));
|
|
18
|
+
|
|
19
|
+
if (found) {
|
|
20
|
+
// Patch the start function to use the correct binary
|
|
21
|
+
const originalStart = serverTTS.start;
|
|
22
|
+
|
|
23
|
+
serverTTS.start = function(voicePath, options) {
|
|
24
|
+
if (!options) options = {};
|
|
25
|
+
if (!options.binaryPaths) options.binaryPaths = [];
|
|
26
|
+
|
|
27
|
+
// Ensure Windows paths are first
|
|
28
|
+
options.binaryPaths = [...windowsBinaries, ...options.binaryPaths];
|
|
29
|
+
|
|
30
|
+
return originalStart.call(this, voicePath, options);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { execSync, spawnSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const PYTHON_VERSION_MIN = [3, 9];
|
|
7
|
+
const VENV_DIR = path.join(os.homedir(), '.gmgui', 'pocket-venv');
|
|
8
|
+
const isWin = process.platform === 'win32';
|
|
9
|
+
const EXECUTABLE_NAME = isWin ? 'pocket-tts.exe' : 'pocket-tts';
|
|
10
|
+
|
|
11
|
+
const CONFIG = {
|
|
12
|
+
PIP_TIMEOUT: 120000,
|
|
13
|
+
VENV_CREATION_TIMEOUT: 30000,
|
|
14
|
+
MAX_RETRIES: 3,
|
|
15
|
+
RETRY_DELAY_MS: 1000,
|
|
16
|
+
RETRY_BACKOFF_MULTIPLIER: 2,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function getPocketTtsPath() {
|
|
20
|
+
if (isWin) {
|
|
21
|
+
return path.join(VENV_DIR, 'Scripts', EXECUTABLE_NAME);
|
|
22
|
+
}
|
|
23
|
+
return path.join(VENV_DIR, 'bin', EXECUTABLE_NAME);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function detectPython() {
|
|
27
|
+
try {
|
|
28
|
+
const versionOutput = execSync('python --version', { encoding: 'utf-8', timeout: 10000 }).trim();
|
|
29
|
+
const match = versionOutput.match(/(\d+)\.(\d+)/);
|
|
30
|
+
if (!match) return { found: false, version: null, error: 'Could not parse version' };
|
|
31
|
+
|
|
32
|
+
const major = parseInt(match[1], 10);
|
|
33
|
+
const minor = parseInt(match[2], 10);
|
|
34
|
+
const versionOk = major > PYTHON_VERSION_MIN[0] || (major === PYTHON_VERSION_MIN[0] && minor >= PYTHON_VERSION_MIN[1]);
|
|
35
|
+
|
|
36
|
+
if (!versionOk) {
|
|
37
|
+
return { found: true, version: `${major}.${minor}`, error: `Python ${major}.${minor} found but ${PYTHON_VERSION_MIN[0]}.${PYTHON_VERSION_MIN[1]}+ required` };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { found: true, version: `${major}.${minor}`, error: null };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return { found: false, version: null, error: 'Python not found in PATH' };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isSetup() {
|
|
47
|
+
const exePath = getPocketTtsPath();
|
|
48
|
+
return fs.existsSync(exePath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cleanupPartialInstall() {
|
|
52
|
+
try {
|
|
53
|
+
if (fs.existsSync(VENV_DIR)) {
|
|
54
|
+
fs.rmSync(VENV_DIR, { recursive: true, force: true });
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(`Failed to cleanup partial install: ${e.message}`);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function verifyInstallation() {
|
|
64
|
+
const exePath = getPocketTtsPath();
|
|
65
|
+
if (!fs.existsSync(exePath)) {
|
|
66
|
+
return { valid: false, error: `Binary not found at ${exePath}` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const versionOutput = execSync(`"${exePath}" --version`, { encoding: 'utf-8', timeout: 10000, stdio: 'pipe' });
|
|
71
|
+
return { valid: true, version: versionOutput.trim() };
|
|
72
|
+
} catch (e) {
|
|
73
|
+
return { valid: false, error: `Binary exists but failed verification: ${e.message}` };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function executeWithRetry(fn, stepName, maxRetries = CONFIG.MAX_RETRIES) {
|
|
78
|
+
let lastError = null;
|
|
79
|
+
let delayMs = CONFIG.RETRY_DELAY_MS;
|
|
80
|
+
|
|
81
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
82
|
+
try {
|
|
83
|
+
return await fn(attempt);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
lastError = e;
|
|
86
|
+
if (attempt < maxRetries) {
|
|
87
|
+
console.log(`Attempt ${attempt}/${maxRetries} failed for ${stepName}, retrying in ${delayMs}ms`);
|
|
88
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
89
|
+
delayMs *= CONFIG.RETRY_BACKOFF_MULTIPLIER;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const msg = `${stepName} failed after ${maxRetries} attempts: ${lastError.message || lastError}`;
|
|
95
|
+
throw new Error(msg);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function install(onProgress) {
|
|
99
|
+
const pythonDetect = detectPython();
|
|
100
|
+
|
|
101
|
+
if (!pythonDetect.found) {
|
|
102
|
+
const msg = pythonDetect.error || 'Python not found';
|
|
103
|
+
if (onProgress) onProgress({ step: 'detecting-python', status: 'error', message: msg });
|
|
104
|
+
return { success: false, error: msg };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (pythonDetect.error) {
|
|
108
|
+
if (onProgress) onProgress({ step: 'detecting-python', status: 'error', message: pythonDetect.error });
|
|
109
|
+
return { success: false, error: pythonDetect.error };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (onProgress) onProgress({ step: 'detecting-python', status: 'success', message: `Found Python ${pythonDetect.version}` });
|
|
113
|
+
|
|
114
|
+
if (isSetup()) {
|
|
115
|
+
const verify = verifyInstallation();
|
|
116
|
+
if (verify.valid) {
|
|
117
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'success', message: 'pocket-tts already installed' });
|
|
118
|
+
return { success: true };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (onProgress) onProgress({ step: 'creating-venv', status: 'in-progress', message: `Creating virtual environment at ${VENV_DIR}` });
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await executeWithRetry(async (attempt) => {
|
|
126
|
+
return execSync(`python -m venv "${VENV_DIR}"`, {
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
stdio: 'pipe',
|
|
129
|
+
timeout: CONFIG.VENV_CREATION_TIMEOUT,
|
|
130
|
+
});
|
|
131
|
+
}, 'venv creation', 2);
|
|
132
|
+
|
|
133
|
+
if (onProgress) onProgress({ step: 'creating-venv', status: 'success', message: 'Virtual environment created' });
|
|
134
|
+
} catch (e) {
|
|
135
|
+
const msg = `Failed to create venv: ${e.message || e}`;
|
|
136
|
+
if (onProgress) onProgress({ step: 'creating-venv', status: 'error', message: msg });
|
|
137
|
+
cleanupPartialInstall();
|
|
138
|
+
return { success: false, error: msg };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (onProgress) onProgress({ step: 'installing', status: 'in-progress', message: 'Installing pocket-tts via pip (this may take 2-5 minutes on slow connections)' });
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await executeWithRetry(async (attempt) => {
|
|
145
|
+
if (attempt > 1 && onProgress) {
|
|
146
|
+
onProgress({ step: 'installing', status: 'in-progress', message: `Installing pocket-tts (attempt ${attempt}/${CONFIG.MAX_RETRIES})` });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const pipCmd = isWin
|
|
150
|
+
? `"${path.join(VENV_DIR, 'Scripts', 'pip')}" install --no-cache-dir pocket-tts`
|
|
151
|
+
: `"${path.join(VENV_DIR, 'bin', 'pip')}" install --no-cache-dir pocket-tts`;
|
|
152
|
+
|
|
153
|
+
return execSync(pipCmd, {
|
|
154
|
+
encoding: 'utf-8',
|
|
155
|
+
stdio: 'pipe',
|
|
156
|
+
timeout: CONFIG.PIP_TIMEOUT,
|
|
157
|
+
env: { ...process.env, PIP_DEFAULT_TIMEOUT: '120' },
|
|
158
|
+
});
|
|
159
|
+
}, 'pip install', CONFIG.MAX_RETRIES);
|
|
160
|
+
|
|
161
|
+
if (onProgress) onProgress({ step: 'installing', status: 'success', message: 'pocket-tts installed successfully' });
|
|
162
|
+
} catch (e) {
|
|
163
|
+
const msg = `Failed to install pocket-tts: ${e.message || e}`;
|
|
164
|
+
if (onProgress) onProgress({ step: 'installing', status: 'error', message: msg });
|
|
165
|
+
cleanupPartialInstall();
|
|
166
|
+
return { success: false, error: msg };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'in-progress', message: 'Verifying installation' });
|
|
170
|
+
|
|
171
|
+
const verify = verifyInstallation();
|
|
172
|
+
if (!verify.valid) {
|
|
173
|
+
const msg = verify.error || 'Installation verification failed';
|
|
174
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'error', message: msg });
|
|
175
|
+
cleanupPartialInstall();
|
|
176
|
+
return { success: false, error: msg };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const exePath = getPocketTtsPath();
|
|
180
|
+
const binDir = path.join(VENV_DIR, 'bin');
|
|
181
|
+
const binExePath = path.join(binDir, 'pocket-tts');
|
|
182
|
+
|
|
183
|
+
if (isWin) {
|
|
184
|
+
try {
|
|
185
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
186
|
+
} catch (e) {}
|
|
187
|
+
|
|
188
|
+
const exeWithExt = path.join(binDir, 'pocket-tts.exe');
|
|
189
|
+
if (fs.existsSync(exePath) && !fs.existsSync(exeWithExt)) {
|
|
190
|
+
try {
|
|
191
|
+
fs.copyFileSync(exePath, exeWithExt);
|
|
192
|
+
} catch (e) {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const batchFile = path.join(binDir, 'pocket-tts.bat');
|
|
196
|
+
if (!fs.existsSync(batchFile) && fs.existsSync(exeWithExt)) {
|
|
197
|
+
try {
|
|
198
|
+
const batchContent = `@echo off\nsetlocal enabledelayedexpansion\nset PYTHONUNBUFFERED=1\nset HF_HUB_DISABLE_SYMLINKS_WARNING=1\n"${exeWithExt}" %*\n`;
|
|
199
|
+
fs.writeFileSync(batchFile, batchContent, 'utf-8');
|
|
200
|
+
} catch (e) {}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (onProgress) onProgress({ step: 'verifying', status: 'success', message: `pocket-tts ready (${verify.version})` });
|
|
205
|
+
|
|
206
|
+
return { success: true };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export { detectPython, isSetup, install, getPocketTtsPath, VENV_DIR, CONFIG, cleanupPartialInstall, verifyInstallation };
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -37,6 +37,28 @@ Open `http://localhost:3000` in your browser.
|
|
|
37
37
|
- `static/` - Browser client with streaming renderer, WebSocket manager, and HTML templates
|
|
38
38
|
- `bin/gmgui.cjs` - CLI entry point for `npx agentgui`
|
|
39
39
|
|
|
40
|
+
## Text-to-Speech on Windows
|
|
41
|
+
|
|
42
|
+
On Windows, AgentGUI automatically sets up pocket-tts (text-to-speech) on your first TTS request. No manual setup required.
|
|
43
|
+
|
|
44
|
+
### What Happens
|
|
45
|
+
1. Server detects Python 3.9+ installation
|
|
46
|
+
2. Creates virtual environment at `~/.gmgui/pocket-venv`
|
|
47
|
+
3. Installs pocket-tts via pip
|
|
48
|
+
4. All subsequent TTS requests use cached installation
|
|
49
|
+
|
|
50
|
+
### Requirements
|
|
51
|
+
- Python 3.9+ (check with `python --version`)
|
|
52
|
+
- ~200 MB free disk space
|
|
53
|
+
- Internet connection for first setup
|
|
54
|
+
|
|
55
|
+
### Troubleshooting
|
|
56
|
+
- **Python not found**: Download from https://www.python.org and ensure "Add Python to PATH" is checked
|
|
57
|
+
- **Setup fails**: Check that you have write access to your home directory (~/.gmgui/)
|
|
58
|
+
- **Manual cleanup**: Delete `%USERPROFILE%\.gmgui\pocket-venv` and try again
|
|
59
|
+
|
|
60
|
+
For manual setup or detailed troubleshooting, see the setup instructions in the code or check `/api/speech-status` endpoint for error details.
|
|
61
|
+
|
|
40
62
|
## Configuration
|
|
41
63
|
|
|
42
64
|
| Variable | Default | Description |
|
package/server.js
CHANGED
|
@@ -11,12 +11,46 @@ import { createRequire } from 'module';
|
|
|
11
11
|
import { OAuth2Client } from 'google-auth-library';
|
|
12
12
|
import { queries } from './database.js';
|
|
13
13
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
14
|
+
import { isSetup, install, detectPython } from './lib/windows-pocket-tts-setup.js';
|
|
14
15
|
let speechModule = null;
|
|
15
16
|
async function getSpeech() {
|
|
16
17
|
if (!speechModule) speechModule = await import('./lib/speech.js');
|
|
17
18
|
return speechModule;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
const pocketTtsSetupState = { attempted: false, ready: false, error: null, inProgress: false };
|
|
22
|
+
|
|
23
|
+
async function ensurePocketTtsSetup(onProgress) {
|
|
24
|
+
if (pocketTtsSetupState.attempted) {
|
|
25
|
+
return pocketTtsSetupState.ready;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (pocketTtsSetupState.inProgress) {
|
|
29
|
+
let waited = 0;
|
|
30
|
+
const MAX_WAIT = 600000;
|
|
31
|
+
while (pocketTtsSetupState.inProgress && waited < MAX_WAIT) {
|
|
32
|
+
await new Promise(r => setTimeout(r, 100));
|
|
33
|
+
waited += 100;
|
|
34
|
+
}
|
|
35
|
+
return pocketTtsSetupState.ready;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pocketTtsSetupState.inProgress = true;
|
|
39
|
+
|
|
40
|
+
if (onProgress) onProgress({ step: 'detecting-python', status: 'in-progress', message: 'Detecting Python installation' });
|
|
41
|
+
|
|
42
|
+
const result = await install((msg) => {
|
|
43
|
+
if (onProgress) onProgress(msg);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
pocketTtsSetupState.attempted = true;
|
|
47
|
+
pocketTtsSetupState.ready = result.success;
|
|
48
|
+
pocketTtsSetupState.error = result.error || null;
|
|
49
|
+
pocketTtsSetupState.inProgress = false;
|
|
50
|
+
|
|
51
|
+
return pocketTtsSetupState.ready;
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
function eagerTTS(text, conversationId, sessionId) {
|
|
21
55
|
getSpeech().then(speech => {
|
|
22
56
|
const status = speech.getStatus();
|
|
@@ -1263,6 +1297,25 @@ const server = http.createServer(async (req, res) => {
|
|
|
1263
1297
|
sendJSON(req, res, 400, { error: 'No text provided' });
|
|
1264
1298
|
return;
|
|
1265
1299
|
}
|
|
1300
|
+
|
|
1301
|
+
if (!pocketTtsSetupState.attempted && process.platform === 'win32') {
|
|
1302
|
+
const setupOk = await ensurePocketTtsSetup((msg) => {
|
|
1303
|
+
broadcastSync({ type: 'tts_setup_progress', ...msg });
|
|
1304
|
+
});
|
|
1305
|
+
if (!setupOk) {
|
|
1306
|
+
sendJSON(req, res, 503, { error: pocketTtsSetupState.error || 'pocket-tts setup failed', retryable: false });
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// After successful setup, start the TTS sidecar if not already running
|
|
1311
|
+
const speech = await getSpeech();
|
|
1312
|
+
if (speech.preloadTTS) {
|
|
1313
|
+
speech.preloadTTS();
|
|
1314
|
+
// Wait a bit for it to start
|
|
1315
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1266
1319
|
const speech = await getSpeech();
|
|
1267
1320
|
const status = speech.getStatus();
|
|
1268
1321
|
if (status.ttsError) {
|
|
@@ -1322,9 +1375,29 @@ const server = http.createServer(async (req, res) => {
|
|
|
1322
1375
|
if (pathOnly === '/api/speech-status' && req.method === 'GET') {
|
|
1323
1376
|
try {
|
|
1324
1377
|
const { getStatus } = await getSpeech();
|
|
1325
|
-
|
|
1378
|
+
const baseStatus = getStatus();
|
|
1379
|
+
const pythonDetect = detectPython();
|
|
1380
|
+
const statusWithSetup = {
|
|
1381
|
+
...baseStatus,
|
|
1382
|
+
pythonDetected: pythonDetect.found,
|
|
1383
|
+
pythonVersion: pythonDetect.version,
|
|
1384
|
+
pocketTtsSetup: {
|
|
1385
|
+
ready: pocketTtsSetupState.ready,
|
|
1386
|
+
attempted: pocketTtsSetupState.attempted,
|
|
1387
|
+
error: pocketTtsSetupState.error,
|
|
1388
|
+
},
|
|
1389
|
+
setupMessage: pocketTtsSetupState.error || (pocketTtsSetupState.ready ? 'pocket-tts ready' : 'Will setup on first TTS request'),
|
|
1390
|
+
};
|
|
1391
|
+
sendJSON(req, res, 200, statusWithSetup);
|
|
1326
1392
|
} catch (err) {
|
|
1327
|
-
|
|
1393
|
+
const pythonDetect = detectPython();
|
|
1394
|
+
sendJSON(req, res, 200, {
|
|
1395
|
+
sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false,
|
|
1396
|
+
pythonDetected: pythonDetect.found,
|
|
1397
|
+
pythonVersion: pythonDetect.version,
|
|
1398
|
+
pocketTtsSetup: { ready: false, attempted: false, error: null },
|
|
1399
|
+
setupMessage: 'Will setup on first TTS request',
|
|
1400
|
+
});
|
|
1328
1401
|
}
|
|
1329
1402
|
return;
|
|
1330
1403
|
}
|