agentgui 1.0.227 → 1.0.228
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/package.json +1 -1
- package/server.js +90 -9
- package/static/index.html +1 -0
- package/static/js/client.js +42 -1
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -24,6 +24,68 @@ async function ensurePocketTtsSetup(onProgress) {
|
|
|
24
24
|
return serverTTS.ensureInstalled(onProgress);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Model download manager
|
|
28
|
+
const modelDownloadState = {
|
|
29
|
+
downloading: false,
|
|
30
|
+
progress: null,
|
|
31
|
+
error: null,
|
|
32
|
+
complete: false
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
async function ensureModelsDownloaded() {
|
|
36
|
+
const { createRequire: cr } = await import('module');
|
|
37
|
+
const r = cr(import.meta.url);
|
|
38
|
+
const { checkAllFilesExist, downloadModels } = r('sttttsmodels');
|
|
39
|
+
|
|
40
|
+
if (checkAllFilesExist()) {
|
|
41
|
+
modelDownloadState.complete = true;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (modelDownloadState.downloading) {
|
|
46
|
+
// Wait for current download
|
|
47
|
+
while (modelDownloadState.downloading) {
|
|
48
|
+
await new Promise(r => setTimeout(r, 100));
|
|
49
|
+
}
|
|
50
|
+
return modelDownloadState.complete;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
modelDownloadState.downloading = true;
|
|
54
|
+
modelDownloadState.error = null;
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await downloadModels((progress) => {
|
|
58
|
+
modelDownloadState.progress = progress;
|
|
59
|
+
// Broadcast progress to all connected clients
|
|
60
|
+
broadcastSync({
|
|
61
|
+
type: 'model_download_progress',
|
|
62
|
+
progress: {
|
|
63
|
+
started: progress.started,
|
|
64
|
+
done: progress.done,
|
|
65
|
+
error: progress.error,
|
|
66
|
+
downloading: progress.downloading,
|
|
67
|
+
type: progress.type,
|
|
68
|
+
completedFiles: progress.completedFiles,
|
|
69
|
+
totalFiles: progress.totalFiles,
|
|
70
|
+
totalDownloaded: progress.totalDownloaded,
|
|
71
|
+
totalBytes: progress.totalBytes
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
modelDownloadState.complete = true;
|
|
76
|
+
return true;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
modelDownloadState.error = err.message;
|
|
79
|
+
broadcastSync({
|
|
80
|
+
type: 'model_download_progress',
|
|
81
|
+
progress: { error: err.message, done: true }
|
|
82
|
+
});
|
|
83
|
+
return false;
|
|
84
|
+
} finally {
|
|
85
|
+
modelDownloadState.downloading = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
27
89
|
function eagerTTS(text, conversationId, sessionId) {
|
|
28
90
|
getSpeech().then(speech => {
|
|
29
91
|
const status = speech.getStatus();
|
|
@@ -802,7 +864,14 @@ const server = http.createServer(async (req, res) => {
|
|
|
802
864
|
}
|
|
803
865
|
|
|
804
866
|
if (pathOnly === '/api/conversations' && req.method === 'GET') {
|
|
805
|
-
|
|
867
|
+
const conversations = queries.getConversationsList();
|
|
868
|
+
// Filter out stale streaming state for conversations not in activeExecutions
|
|
869
|
+
for (const conv of conversations) {
|
|
870
|
+
if (conv.isStreaming && !activeExecutions.has(conv.id)) {
|
|
871
|
+
conv.isStreaming = 0;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
sendJSON(req, res, 200, { conversations });
|
|
806
875
|
return;
|
|
807
876
|
}
|
|
808
877
|
|
|
@@ -1601,11 +1670,18 @@ const server = http.createServer(async (req, res) => {
|
|
|
1601
1670
|
pythonDetected: pyInfo.found,
|
|
1602
1671
|
pythonVersion: pyInfo.version || null,
|
|
1603
1672
|
setupMessage: baseStatus.ttsReady ? 'pocket-tts ready' : 'Will setup on first TTS request',
|
|
1673
|
+
modelsDownloading: modelDownloadState.downloading,
|
|
1674
|
+
modelsComplete: modelDownloadState.complete,
|
|
1675
|
+
modelsError: modelDownloadState.error,
|
|
1676
|
+
modelsProgress: modelDownloadState.progress,
|
|
1604
1677
|
});
|
|
1605
1678
|
} catch (err) {
|
|
1606
1679
|
sendJSON(req, res, 200, {
|
|
1607
1680
|
sttReady: false, ttsReady: false, sttLoading: false, ttsLoading: false,
|
|
1608
1681
|
setupMessage: 'Will setup on first TTS request',
|
|
1682
|
+
modelsDownloading: modelDownloadState.downloading,
|
|
1683
|
+
modelsComplete: modelDownloadState.complete,
|
|
1684
|
+
modelsError: modelDownloadState.error,
|
|
1609
1685
|
});
|
|
1610
1686
|
}
|
|
1611
1687
|
return;
|
|
@@ -2532,14 +2608,9 @@ function performAgentHealthCheck() {
|
|
|
2532
2608
|
markAgentDead(conversationId, entry, 'Agent process died unexpectedly');
|
|
2533
2609
|
} else if (now - entry.lastActivity > STUCK_AGENT_THRESHOLD_MS) {
|
|
2534
2610
|
debugLog(`[HEALTH] Agent PID ${entry.pid} for conv ${conversationId} has no activity for ${Math.round((now - entry.lastActivity) / 1000)}s`);
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
conversationId,
|
|
2539
|
-
error: 'Agent may be stuck (no activity for 10 minutes)',
|
|
2540
|
-
recoverable: true,
|
|
2541
|
-
timestamp: now
|
|
2542
|
-
});
|
|
2611
|
+
// Kill stuck agent and clear streaming state
|
|
2612
|
+
try { process.kill(entry.pid, 'SIGTERM'); } catch (e) {}
|
|
2613
|
+
markAgentDead(conversationId, entry, 'Agent was stuck (no activity for 10 minutes)');
|
|
2543
2614
|
}
|
|
2544
2615
|
} else {
|
|
2545
2616
|
if (now - entry.startTime > NO_PID_GRACE_PERIOD_MS) {
|
|
@@ -2567,6 +2638,16 @@ function onServerReady() {
|
|
|
2567
2638
|
// Resume interrupted streams after recovery
|
|
2568
2639
|
resumeInterruptedStreams().catch(err => console.error('[RESUME] Startup error:', err.message));
|
|
2569
2640
|
|
|
2641
|
+
// Start model downloads in background after server is ready
|
|
2642
|
+
setTimeout(() => {
|
|
2643
|
+
ensureModelsDownloaded().then(ok => {
|
|
2644
|
+
if (ok) console.log('[MODELS] Speech models ready');
|
|
2645
|
+
else console.log('[MODELS] Speech model download failed');
|
|
2646
|
+
}).catch(err => {
|
|
2647
|
+
console.error('[MODELS] Download error:', err.message);
|
|
2648
|
+
});
|
|
2649
|
+
}, 2000);
|
|
2650
|
+
|
|
2570
2651
|
getSpeech().then(s => s.preloadTTS()).catch(e => debugLog('[TTS] Preload failed: ' + e.message));
|
|
2571
2652
|
|
|
2572
2653
|
performAutoImport();
|
package/static/index.html
CHANGED
|
@@ -2271,6 +2271,7 @@
|
|
|
2271
2271
|
.connection-dot.unknown { background: #6b7280; }
|
|
2272
2272
|
.connection-dot.disconnected { background: #ef4444; animation: pulse 1.5s ease-in-out infinite; }
|
|
2273
2273
|
.connection-dot.reconnecting { background: #f59e0b; animation: pulse 1s ease-in-out infinite; }
|
|
2274
|
+
.connection-dot.downloading { background: #3b82f6; animation: pulse 1s ease-in-out infinite; }
|
|
2274
2275
|
|
|
2275
2276
|
.connection-tooltip {
|
|
2276
2277
|
position: absolute;
|
package/static/js/client.js
CHANGED
|
@@ -432,6 +432,9 @@ class AgentGUIClient {
|
|
|
432
432
|
case 'rate_limit_clear':
|
|
433
433
|
this.handleRateLimitClear(data);
|
|
434
434
|
break;
|
|
435
|
+
case 'model_download_progress':
|
|
436
|
+
this._handleModelDownloadProgress(data.progress);
|
|
437
|
+
break;
|
|
435
438
|
default:
|
|
436
439
|
break;
|
|
437
440
|
}
|
|
@@ -1833,7 +1836,7 @@ class AgentGUIClient {
|
|
|
1833
1836
|
}
|
|
1834
1837
|
|
|
1835
1838
|
_updateConnectionIndicator(quality) {
|
|
1836
|
-
if (this._indicatorDebounce) return;
|
|
1839
|
+
if (this._indicatorDebounce && !this._modelDownloadInProgress) return;
|
|
1837
1840
|
this._indicatorDebounce = true;
|
|
1838
1841
|
setTimeout(() => { this._indicatorDebounce = false; }, 1000);
|
|
1839
1842
|
|
|
@@ -1855,6 +1858,21 @@ class AgentGUIClient {
|
|
|
1855
1858
|
const label = indicator.querySelector('.connection-label');
|
|
1856
1859
|
if (!dot || !label) return;
|
|
1857
1860
|
|
|
1861
|
+
// Check if model download is in progress
|
|
1862
|
+
if (this._modelDownloadInProgress) {
|
|
1863
|
+
dot.className = 'connection-dot downloading';
|
|
1864
|
+
const progress = this._modelDownloadProgress;
|
|
1865
|
+
if (progress && progress.totalBytes > 0) {
|
|
1866
|
+
const pct = Math.round((progress.totalDownloaded / progress.totalBytes) * 100);
|
|
1867
|
+
label.textContent = `Models ${pct}%`;
|
|
1868
|
+
} else if (progress && progress.downloading) {
|
|
1869
|
+
label.textContent = 'Downloading...';
|
|
1870
|
+
} else {
|
|
1871
|
+
label.textContent = 'Loading models...';
|
|
1872
|
+
}
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1858
1876
|
dot.className = 'connection-dot';
|
|
1859
1877
|
if (quality === 'disconnected' || quality === 'reconnecting') {
|
|
1860
1878
|
dot.classList.add(quality);
|
|
@@ -1866,6 +1884,29 @@ class AgentGUIClient {
|
|
|
1866
1884
|
}
|
|
1867
1885
|
}
|
|
1868
1886
|
|
|
1887
|
+
_handleModelDownloadProgress(progress) {
|
|
1888
|
+
this._modelDownloadProgress = progress;
|
|
1889
|
+
|
|
1890
|
+
if (progress.error) {
|
|
1891
|
+
this._modelDownloadInProgress = false;
|
|
1892
|
+
console.error('[Models] Download error:', progress.error);
|
|
1893
|
+
this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
if (progress.done) {
|
|
1898
|
+
this._modelDownloadInProgress = false;
|
|
1899
|
+
console.log('[Models] Download complete');
|
|
1900
|
+
this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
if (progress.started || progress.downloading) {
|
|
1905
|
+
this._modelDownloadInProgress = true;
|
|
1906
|
+
this._updateConnectionIndicator(this.wsManager?.latency?.quality || 'unknown');
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1869
1910
|
_toggleConnectionTooltip() {
|
|
1870
1911
|
let tooltip = document.getElementById('connection-tooltip');
|
|
1871
1912
|
if (tooltip) { tooltip.remove(); return; }
|