agentgui 1.0.756 → 1.0.758
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/lib/routes-speech.js +173 -0
- package/lib/ws-handlers-conv.js +16 -0
- package/package.json +1 -1
- package/server.js +4 -201
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getSpeech, ensurePocketTtsSetup, ensureModelsDownloaded, modelDownloadState } from './speech-manager.js';
|
|
2
|
+
|
|
3
|
+
export function register(deps) {
|
|
4
|
+
const { sendJSON, parseBody, broadcastSync, debugLog } = deps;
|
|
5
|
+
const routes = {};
|
|
6
|
+
|
|
7
|
+
routes['POST /api/stt'] = async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const chunks = [];
|
|
10
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
11
|
+
const audioBuffer = Buffer.concat(chunks);
|
|
12
|
+
if (audioBuffer.length === 0) { sendJSON(req, res, 400, { error: 'No audio data' }); return; }
|
|
13
|
+
broadcastSync({ type: 'stt_progress', status: 'transcribing', percentComplete: 0 });
|
|
14
|
+
const { transcribe } = await getSpeech();
|
|
15
|
+
const text = await transcribe(audioBuffer);
|
|
16
|
+
const finalText = (text || '').trim();
|
|
17
|
+
broadcastSync({ type: 'stt_progress', status: 'completed', percentComplete: 100, transcript: finalText });
|
|
18
|
+
sendJSON(req, res, 200, { text: finalText });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
debugLog('[STT] Error: ' + err.message);
|
|
21
|
+
let errorMsg = err.message || 'STT failed';
|
|
22
|
+
if (errorMsg.includes('VERS_1.21') || errorMsg.includes('onnxruntime')) {
|
|
23
|
+
errorMsg = 'STT model load failed: onnxruntime version mismatch. Try: npm install or npm ci';
|
|
24
|
+
} else if (errorMsg.includes('not valid JSON') || errorMsg.includes('Unexpected token') || errorMsg.includes('corrupted files cleared')) {
|
|
25
|
+
try {
|
|
26
|
+
const speech = await getSpeech();
|
|
27
|
+
const cleared = speech.clearCorruptedSTTCache();
|
|
28
|
+
speech.resetSTTError();
|
|
29
|
+
await ensureModelsDownloaded();
|
|
30
|
+
errorMsg = 'STT cache was corrupted, re-downloaded models. Please try again.';
|
|
31
|
+
} catch (e) {
|
|
32
|
+
errorMsg = 'STT model load failed: corrupted cache. Recovery attempted, try again.';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
broadcastSync({ type: 'stt_progress', status: 'failed', percentComplete: 0, error: errorMsg });
|
|
36
|
+
if (!res.headersSent) sendJSON(req, res, 500, { error: errorMsg });
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
routes['GET /api/voices'] = async (req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const { getVoices } = await getSpeech();
|
|
43
|
+
sendJSON(req, res, 200, { ok: true, voices: getVoices() });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
sendJSON(req, res, 200, { ok: true, voices: [] });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
routes['POST /api/tts'] = async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const body = await parseBody(req);
|
|
52
|
+
const text = body.text || '';
|
|
53
|
+
const voiceId = body.voiceId || null;
|
|
54
|
+
if (!text) { sendJSON(req, res, 400, { error: 'No text provided' }); return; }
|
|
55
|
+
if (process.platform === 'win32') {
|
|
56
|
+
const setupOk = await ensurePocketTtsSetup((msg) => {
|
|
57
|
+
broadcastSync({ type: 'tts_setup_progress', ...msg });
|
|
58
|
+
});
|
|
59
|
+
if (!setupOk) { sendJSON(req, res, 503, { error: 'pocket-tts setup failed', retryable: false }); return; }
|
|
60
|
+
const speech = await getSpeech();
|
|
61
|
+
if (speech.preloadTTS) { speech.preloadTTS(); await new Promise(r => setTimeout(r, 2000)); }
|
|
62
|
+
}
|
|
63
|
+
const speech = await getSpeech();
|
|
64
|
+
const wavBuffer = await speech.synthesize(text, voiceId);
|
|
65
|
+
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': wavBuffer.length });
|
|
66
|
+
res.end(wavBuffer);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
debugLog('[TTS] Error: ' + err.message);
|
|
69
|
+
const isModelError = /model.*load|pipeline.*failed|failed to load/i.test(err.message);
|
|
70
|
+
const statusCode = isModelError ? 503 : 500;
|
|
71
|
+
if (!res.headersSent) sendJSON(req, res, statusCode, { error: err.message || 'TTS failed', retryable: !isModelError });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
routes['POST /api/tts-stream'] = async (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const body = await parseBody(req);
|
|
78
|
+
const text = body.text || '';
|
|
79
|
+
const voiceId = body.voiceId || null;
|
|
80
|
+
if (!text) { sendJSON(req, res, 400, { error: 'No text provided' }); return; }
|
|
81
|
+
const speech = await getSpeech();
|
|
82
|
+
const gen = speech.synthesizeStream(text, voiceId);
|
|
83
|
+
const firstResult = await gen.next();
|
|
84
|
+
if (firstResult.done) { sendJSON(req, res, 500, { error: 'TTS stream returned no audio', retryable: true }); return; }
|
|
85
|
+
res.writeHead(200, {
|
|
86
|
+
'Content-Type': 'application/octet-stream',
|
|
87
|
+
'Transfer-Encoding': 'chunked',
|
|
88
|
+
'X-Content-Type': 'audio/wav-stream',
|
|
89
|
+
'Cache-Control': 'no-cache'
|
|
90
|
+
});
|
|
91
|
+
const writeChunk = (wavChunk) => {
|
|
92
|
+
const lenBuf = Buffer.alloc(4);
|
|
93
|
+
lenBuf.writeUInt32BE(wavChunk.length, 0);
|
|
94
|
+
res.write(lenBuf);
|
|
95
|
+
res.write(wavChunk);
|
|
96
|
+
};
|
|
97
|
+
writeChunk(firstResult.value);
|
|
98
|
+
for await (const wavChunk of gen) writeChunk(wavChunk);
|
|
99
|
+
res.end();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
debugLog('[TTS-STREAM] Error: ' + err.message);
|
|
102
|
+
const isModelError = /model.*load|pipeline.*failed|failed to load/i.test(err.message);
|
|
103
|
+
if (!res.headersSent) sendJSON(req, res, isModelError ? 503 : 500, { error: err.message || 'TTS stream failed', retryable: !isModelError });
|
|
104
|
+
else res.end();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
routes['GET /api/speech-status'] = async (req, res) => {
|
|
109
|
+
try {
|
|
110
|
+
const { getStatus } = await getSpeech();
|
|
111
|
+
const baseStatus = getStatus();
|
|
112
|
+
sendJSON(req, res, 200, {
|
|
113
|
+
...baseStatus,
|
|
114
|
+
setupMessage: baseStatus.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
|
|
115
|
+
modelsDownloading: modelDownloadState.downloading,
|
|
116
|
+
modelsComplete: modelDownloadState.complete,
|
|
117
|
+
modelsError: modelDownloadState.error,
|
|
118
|
+
modelsProgress: modelDownloadState.progress,
|
|
119
|
+
});
|
|
120
|
+
} catch (err) {
|
|
121
|
+
sendJSON(req, res, 200, {
|
|
122
|
+
sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false,
|
|
123
|
+
setupMessage: 'Will setup on first TTS request',
|
|
124
|
+
modelsDownloading: modelDownloadState.downloading,
|
|
125
|
+
modelsComplete: modelDownloadState.complete,
|
|
126
|
+
modelsError: modelDownloadState.error,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
routes['POST /api/speech-status'] = async (req, res) => {
|
|
132
|
+
const body = await parseBody(req);
|
|
133
|
+
if (body.forceDownload) {
|
|
134
|
+
if (modelDownloadState.complete) { sendJSON(req, res, 200, { ok: true, modelsComplete: true, message: 'Models already ready' }); return; }
|
|
135
|
+
if (!modelDownloadState.downloading) {
|
|
136
|
+
modelDownloadState.error = null;
|
|
137
|
+
ensureModelsDownloaded().then(ok => {
|
|
138
|
+
broadcastSync({ type: 'model_download_progress', progress: { done: true, complete: ok, error: ok ? null : 'Download failed' } });
|
|
139
|
+
}).catch(err => {
|
|
140
|
+
broadcastSync({ type: 'model_download_progress', progress: { done: true, error: err.message } });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
sendJSON(req, res, 200, { ok: true, message: 'Starting model download' });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
sendJSON(req, res, 400, { error: 'Unknown request' });
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
routes['_match'] = (method, pathOnly) => {
|
|
150
|
+
const key = `${method} ${pathOnly}`;
|
|
151
|
+
if (routes[key]) return routes[key];
|
|
152
|
+
if (method === 'GET' && pathOnly.startsWith('/api/tts-cache/')) return routes['_tts-cache'];
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
routes['_tts-cache'] = async (req, res, pathOnly) => {
|
|
157
|
+
const cacheKey = decodeURIComponent(pathOnly.slice('/api/tts-cache/'.length));
|
|
158
|
+
try {
|
|
159
|
+
const speech = await getSpeech();
|
|
160
|
+
const cached = speech.ttsCacheGet(cacheKey);
|
|
161
|
+
if (cached) {
|
|
162
|
+
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': cached.length, 'Cache-Control': 'public, max-age=3600' });
|
|
163
|
+
res.end(cached);
|
|
164
|
+
} else {
|
|
165
|
+
sendJSON(req, res, 404, { error: 'not cached' });
|
|
166
|
+
}
|
|
167
|
+
} catch (err) {
|
|
168
|
+
sendJSON(req, res, 500, { error: err.message });
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return routes;
|
|
173
|
+
}
|
package/lib/ws-handlers-conv.js
CHANGED
|
@@ -143,6 +143,22 @@ export function register(router, deps) {
|
|
|
143
143
|
return { markdown: md, title: conv.title };
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
router.handle('conv.import', (p) => {
|
|
147
|
+
if (!p.conversation || !p.messages) throw Object.assign(new Error('Missing conversation or messages'), { code: 400 });
|
|
148
|
+
const src = p.conversation;
|
|
149
|
+
const conv = queries.createConversation(
|
|
150
|
+
src.agentId || src.agentType || 'claude-code',
|
|
151
|
+
src.title || 'Imported Conversation',
|
|
152
|
+
src.workingDirectory || null,
|
|
153
|
+
src.model || null
|
|
154
|
+
);
|
|
155
|
+
for (const msg of p.messages) {
|
|
156
|
+
queries.createMessage(conv.id, msg.role || 'user', msg.content || '');
|
|
157
|
+
}
|
|
158
|
+
broadcastSync({ type: 'conversation_created', conversation: queries.getConversation(conv.id) });
|
|
159
|
+
return { conversation: queries.getConversation(conv.id), importedMessages: p.messages.length };
|
|
160
|
+
});
|
|
161
|
+
|
|
146
162
|
router.handle('conv.sync', (p) => {
|
|
147
163
|
const conv = queries.getConversation(p.id);
|
|
148
164
|
if (!conv) notFound();
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { initializeDescriptors, getAgentDescriptor } from './lib/agent-descripto
|
|
|
19
19
|
import { findCommand, queryACPServerAgents, discoverAgents, discoverExternalACPServers, initializeAgentDiscovery } from './lib/agent-discovery.js';
|
|
20
20
|
import { getGeminiOAuthCreds, startGeminiOAuth, exchangeGeminiOAuthCode, handleGeminiOAuthCallback, getGeminiOAuthStatus, getGeminiOAuthState } from './lib/oauth-gemini.js';
|
|
21
21
|
import { initSpeechManager, getSpeech, ensurePocketTtsSetup, voiceCacheManager, modelDownloadState, broadcastModelProgress, ensureModelsDownloaded, eagerTTS } from './lib/speech-manager.js';
|
|
22
|
+
import { register as registerSpeechRoutes } from './lib/routes-speech.js';
|
|
22
23
|
import { startCodexOAuth, exchangeCodexOAuthCode, handleCodexOAuthCallback, getCodexOAuthStatus, getCodexOAuthState, CODEX_HOME, CODEX_AUTH_FILE } from './lib/oauth-codex.js';
|
|
23
24
|
import { WSOptimizer } from './lib/ws-optimizer.js';
|
|
24
25
|
import { WsRouter } from './lib/ws-protocol.js';
|
|
@@ -2317,207 +2318,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
2317
2318
|
return;
|
|
2318
2319
|
}
|
|
2319
2320
|
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
const chunks = [];
|
|
2323
|
-
for await (const chunk of req) chunks.push(chunk);
|
|
2324
|
-
const audioBuffer = Buffer.concat(chunks);
|
|
2325
|
-
if (audioBuffer.length === 0) {
|
|
2326
|
-
sendJSON(req, res, 400, { error: 'No audio data' });
|
|
2327
|
-
return;
|
|
2328
|
-
}
|
|
2329
|
-
broadcastSync({ type: 'stt_progress', status: 'transcribing', percentComplete: 0 });
|
|
2330
|
-
const { transcribe } = await getSpeech();
|
|
2331
|
-
const text = await transcribe(audioBuffer);
|
|
2332
|
-
const finalText = (text || '').trim();
|
|
2333
|
-
broadcastSync({ type: 'stt_progress', status: 'completed', percentComplete: 100, transcript: finalText });
|
|
2334
|
-
sendJSON(req, res, 200, { text: finalText });
|
|
2335
|
-
} catch (err) {
|
|
2336
|
-
debugLog('[STT] Error: ' + err.message);
|
|
2337
|
-
let errorMsg = err.message || 'STT failed';
|
|
2338
|
-
if (errorMsg.includes('VERS_1.21') || errorMsg.includes('onnxruntime')) {
|
|
2339
|
-
errorMsg = 'STT model load failed: onnxruntime version mismatch. Try: npm install or npm ci';
|
|
2340
|
-
} else if (errorMsg.includes('not valid JSON') || errorMsg.includes('Unexpected token') || errorMsg.includes('corrupted files cleared')) {
|
|
2341
|
-
try {
|
|
2342
|
-
const speech = await getSpeech();
|
|
2343
|
-
const cleared = speech.clearCorruptedSTTCache();
|
|
2344
|
-
speech.resetSTTError();
|
|
2345
|
-
console.log('[STT] Cleared', cleared, 'corrupted files and reset error state');
|
|
2346
|
-
await ensureModelsDownloaded();
|
|
2347
|
-
errorMsg = 'STT cache was corrupted, re-downloaded models. Please try again.';
|
|
2348
|
-
} catch (e) {
|
|
2349
|
-
console.warn('[STT] Recovery failed:', e.message);
|
|
2350
|
-
errorMsg = 'STT model load failed: corrupted cache. Recovery attempted, try again.';
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
broadcastSync({ type: 'stt_progress', status: 'failed', percentComplete: 0, error: errorMsg });
|
|
2354
|
-
if (!res.headersSent) sendJSON(req, res, 500, { error: errorMsg });
|
|
2355
|
-
}
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
if (pathOnly === '/api/voices' && req.method === 'GET') {
|
|
2360
|
-
try {
|
|
2361
|
-
const { getVoices } = await getSpeech();
|
|
2362
|
-
sendJSON(req, res, 200, { ok: true, voices: getVoices() });
|
|
2363
|
-
} catch (err) {
|
|
2364
|
-
sendJSON(req, res, 200, { ok: true, voices: [] });
|
|
2365
|
-
}
|
|
2366
|
-
return;
|
|
2367
|
-
}
|
|
2368
|
-
|
|
2369
|
-
if (pathOnly === '/api/tts' && req.method === 'POST') {
|
|
2370
|
-
try {
|
|
2371
|
-
const body = await parseBody(req);
|
|
2372
|
-
const text = body.text || '';
|
|
2373
|
-
const voiceId = body.voiceId || null;
|
|
2374
|
-
if (!text) {
|
|
2375
|
-
sendJSON(req, res, 400, { error: 'No text provided' });
|
|
2376
|
-
return;
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
if (process.platform === 'win32') {
|
|
2380
|
-
const setupOk = await ensurePocketTtsSetup((msg) => {
|
|
2381
|
-
broadcastSync({ type: 'tts_setup_progress', ...msg });
|
|
2382
|
-
});
|
|
2383
|
-
if (!setupOk) {
|
|
2384
|
-
sendJSON(req, res, 503, { error: 'pocket-tts setup failed', retryable: false });
|
|
2385
|
-
return;
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
// After successful setup, start the TTS sidecar if not already running
|
|
2389
|
-
const speech = await getSpeech();
|
|
2390
|
-
if (speech.preloadTTS) {
|
|
2391
|
-
speech.preloadTTS();
|
|
2392
|
-
// Wait a bit for it to start
|
|
2393
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
|
|
2397
|
-
const speech = await getSpeech();
|
|
2398
|
-
const wavBuffer = await speech.synthesize(text, voiceId);
|
|
2399
|
-
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': wavBuffer.length });
|
|
2400
|
-
res.end(wavBuffer);
|
|
2401
|
-
} catch (err) {
|
|
2402
|
-
debugLog('[TTS] Error: ' + err.message);
|
|
2403
|
-
const isModelError = /model.*load|pipeline.*failed|failed to load/i.test(err.message);
|
|
2404
|
-
const statusCode = isModelError ? 503 : 500;
|
|
2405
|
-
if (!res.headersSent) sendJSON(req, res, statusCode, { error: err.message || 'TTS failed', retryable: !isModelError });
|
|
2406
|
-
}
|
|
2407
|
-
return;
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
if (pathOnly === '/api/tts-stream' && req.method === 'POST') {
|
|
2411
|
-
try {
|
|
2412
|
-
const body = await parseBody(req);
|
|
2413
|
-
const text = body.text || '';
|
|
2414
|
-
const voiceId = body.voiceId || null;
|
|
2415
|
-
if (!text) {
|
|
2416
|
-
sendJSON(req, res, 400, { error: 'No text provided' });
|
|
2417
|
-
return;
|
|
2418
|
-
}
|
|
2419
|
-
const speech = await getSpeech();
|
|
2420
|
-
const gen = speech.synthesizeStream(text, voiceId);
|
|
2421
|
-
const firstResult = await gen.next();
|
|
2422
|
-
if (firstResult.done) {
|
|
2423
|
-
sendJSON(req, res, 500, { error: 'TTS stream returned no audio', retryable: true });
|
|
2424
|
-
return;
|
|
2425
|
-
}
|
|
2426
|
-
res.writeHead(200, {
|
|
2427
|
-
'Content-Type': 'application/octet-stream',
|
|
2428
|
-
'Transfer-Encoding': 'chunked',
|
|
2429
|
-
'X-Content-Type': 'audio/wav-stream',
|
|
2430
|
-
'Cache-Control': 'no-cache'
|
|
2431
|
-
});
|
|
2432
|
-
const writeChunk = (wavChunk) => {
|
|
2433
|
-
const lenBuf = Buffer.alloc(4);
|
|
2434
|
-
lenBuf.writeUInt32BE(wavChunk.length, 0);
|
|
2435
|
-
res.write(lenBuf);
|
|
2436
|
-
res.write(wavChunk);
|
|
2437
|
-
};
|
|
2438
|
-
writeChunk(firstResult.value);
|
|
2439
|
-
for await (const wavChunk of gen) {
|
|
2440
|
-
writeChunk(wavChunk);
|
|
2441
|
-
}
|
|
2442
|
-
res.end();
|
|
2443
|
-
} catch (err) {
|
|
2444
|
-
debugLog('[TTS-STREAM] Error: ' + err.message);
|
|
2445
|
-
const isModelError = /model.*load|pipeline.*failed|failed to load/i.test(err.message);
|
|
2446
|
-
const statusCode = isModelError ? 503 : 500;
|
|
2447
|
-
if (!res.headersSent) sendJSON(req, res, statusCode, { error: err.message || 'TTS stream failed', retryable: !isModelError });
|
|
2448
|
-
else res.end();
|
|
2449
|
-
}
|
|
2450
|
-
return;
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
if (pathOnly.startsWith('/api/tts-cache/') && req.method === 'GET') {
|
|
2454
|
-
const cacheKey = decodeURIComponent(pathOnly.slice('/api/tts-cache/'.length));
|
|
2455
|
-
try {
|
|
2456
|
-
const speech = await getSpeech();
|
|
2457
|
-
const cached = speech.ttsCacheGet(cacheKey);
|
|
2458
|
-
if (cached) {
|
|
2459
|
-
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': cached.length, 'Cache-Control': 'public, max-age=3600' });
|
|
2460
|
-
res.end(cached);
|
|
2461
|
-
} else {
|
|
2462
|
-
sendJSON(req, res, 404, { error: 'not cached' });
|
|
2463
|
-
}
|
|
2464
|
-
} catch (err) {
|
|
2465
|
-
sendJSON(req, res, 500, { error: err.message });
|
|
2466
|
-
}
|
|
2467
|
-
return;
|
|
2468
|
-
}
|
|
2469
|
-
|
|
2470
|
-
if (pathOnly === '/api/speech-status' && req.method === 'GET') {
|
|
2471
|
-
try {
|
|
2472
|
-
const { getStatus } = await getSpeech();
|
|
2473
|
-
const baseStatus = getStatus();
|
|
2474
|
-
sendJSON(req, res, 200, {
|
|
2475
|
-
...baseStatus,
|
|
2476
|
-
setupMessage: baseStatus.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
|
|
2477
|
-
modelsDownloading: modelDownloadState.downloading,
|
|
2478
|
-
modelsComplete: modelDownloadState.complete,
|
|
2479
|
-
modelsError: modelDownloadState.error,
|
|
2480
|
-
modelsProgress: modelDownloadState.progress,
|
|
2481
|
-
});
|
|
2482
|
-
} catch (err) {
|
|
2483
|
-
sendJSON(req, res, 200, {
|
|
2484
|
-
sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false,
|
|
2485
|
-
setupMessage: 'Will setup on first TTS request',
|
|
2486
|
-
modelsDownloading: modelDownloadState.downloading,
|
|
2487
|
-
modelsComplete: modelDownloadState.complete,
|
|
2488
|
-
modelsError: modelDownloadState.error,
|
|
2489
|
-
});
|
|
2490
|
-
}
|
|
2491
|
-
return;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
if (pathOnly === '/api/speech-status' && req.method === 'POST') {
|
|
2495
|
-
const body = await parseBody(req);
|
|
2496
|
-
if (body.forceDownload) {
|
|
2497
|
-
if (modelDownloadState.complete) {
|
|
2498
|
-
sendJSON(req, res, 200, { ok: true, modelsComplete: true, message: 'Models already ready' });
|
|
2499
|
-
return;
|
|
2500
|
-
}
|
|
2501
|
-
if (!modelDownloadState.downloading) {
|
|
2502
|
-
modelDownloadState.error = null;
|
|
2503
|
-
ensureModelsDownloaded().then(ok => {
|
|
2504
|
-
broadcastSync({
|
|
2505
|
-
type: 'model_download_progress',
|
|
2506
|
-
progress: { done: true, complete: ok, error: ok ? null : 'Download failed' }
|
|
2507
|
-
});
|
|
2508
|
-
}).catch(err => {
|
|
2509
|
-
broadcastSync({
|
|
2510
|
-
type: 'model_download_progress',
|
|
2511
|
-
progress: { done: true, error: err.message }
|
|
2512
|
-
});
|
|
2513
|
-
});
|
|
2514
|
-
}
|
|
2515
|
-
sendJSON(req, res, 200, { ok: true, message: 'Starting model download' });
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
sendJSON(req, res, 400, { error: 'Unknown request' });
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2321
|
+
const speechHandler = _speechRoutes._match(req.method, pathOnly);
|
|
2322
|
+
if (speechHandler) { await speechHandler(req, res, pathOnly); return; }
|
|
2521
2323
|
|
|
2522
2324
|
if (pathOnly === '/api/clone' && req.method === 'POST') {
|
|
2523
2325
|
const body = await parseBody(req);
|
|
@@ -3742,6 +3544,7 @@ function broadcastSync(event) {
|
|
|
3742
3544
|
const wsRouter = new WsRouter();
|
|
3743
3545
|
|
|
3744
3546
|
initSpeechManager({ broadcastSync, syncClients, queries });
|
|
3547
|
+
const _speechRoutes = registerSpeechRoutes({ sendJSON, parseBody, broadcastSync, debugLog });
|
|
3745
3548
|
|
|
3746
3549
|
registerConvHandlers(wsRouter, {
|
|
3747
3550
|
queries, activeExecutions, rateLimitState,
|