agentgui 1.0.513 → 1.0.515
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 +104 -1
- package/package.json +1 -1
- package/server.js +14 -106
- package/static/js/voice.js +38 -0
- package/test-fixes.mjs +103 -0
package/CLAUDE.md
CHANGED
|
@@ -91,6 +91,30 @@ All routes are prefixed with `BASE_URL` (default `/gm`).
|
|
|
91
91
|
- `POST /api/tools/update` - Batch update all tools with available updates
|
|
92
92
|
- `POST /api/tools/refresh-all` - Refresh all tool statuses from package manager
|
|
93
93
|
|
|
94
|
+
## Tool Update System
|
|
95
|
+
|
|
96
|
+
Tool updates are managed through a complete pipeline:
|
|
97
|
+
|
|
98
|
+
**Update Flow:**
|
|
99
|
+
1. Frontend (`static/js/tools-manager.js`) initiates POST to `/api/tools/{id}/update`
|
|
100
|
+
2. Server (`server.js` lines 1904-1961 for individual, 1973-2003 for batch) spawns bunx process
|
|
101
|
+
3. Tool manager (`lib/tool-manager.js` lines 400-432) executes `bunx <package>` and detects new version
|
|
102
|
+
4. Version is saved to database: `queries.updateToolStatus(toolId, { version, status: 'installed' })`
|
|
103
|
+
5. WebSocket broadcasts `tool_update_complete` with version and status data
|
|
104
|
+
6. Frontend updates UI and removes tool from `operationInProgress` set
|
|
105
|
+
|
|
106
|
+
**Critical Detail:** When updating tools in batch (`/api/tools/update`), the version parameter MUST be included in the database update call (line 1986 in server.js). This ensures database persistence across page reloads.
|
|
107
|
+
|
|
108
|
+
**Version Detection Sources** (`lib/tool-manager.js` lines 26-87):
|
|
109
|
+
- Claude Code: `~/.claude/plugins/{pluginId}/plugin.json`
|
|
110
|
+
- OpenCode: `~/.config/opencode/agents/{pluginId}/plugin.json`
|
|
111
|
+
- Gemini CLI: `~/.gemini/extensions/{pluginId}/plugin.json`
|
|
112
|
+
- Kilo: `~/.config/kilo/agents/{pluginId}/plugin.json`
|
|
113
|
+
|
|
114
|
+
**Database Schema** (`database.js` lines 168-199):
|
|
115
|
+
- Table: `tool_installations` (toolId, version, status, installed_at, error_message)
|
|
116
|
+
- Table: `tool_install_history` (action, status, error_message for audit trail)
|
|
117
|
+
|
|
94
118
|
## Tool Detection System
|
|
95
119
|
|
|
96
120
|
The system auto-detects installed AI coding tools via `bunx` package resolution:
|
|
@@ -99,7 +123,13 @@ The system auto-detects installed AI coding tools via `bunx` package resolution:
|
|
|
99
123
|
- **Kilo**: `@kilocode/cli` package (id: gm-kilo)
|
|
100
124
|
- **Claude Code**: `@anthropic-ai/claude-code` package (id: gm-cc)
|
|
101
125
|
|
|
102
|
-
Tool
|
|
126
|
+
Tool configuration in `lib/tool-manager.js` TOOLS array includes id, name, pkg, and pluginId. Each tool has a different plugin folder name than its npm package name:
|
|
127
|
+
- Claude Code: pkg='@anthropic-ai/claude-code', pluginId='gm' (stored at ~/.claude/plugins/gm/)
|
|
128
|
+
- Gemini CLI: pkg='@google/gemini-cli', pluginId='gm' (stored at ~/.gemini/extensions/gm/)
|
|
129
|
+
- Kilo: pkg='@kilocode/cli', pluginId='@kilocode/cli' (stored at ~/.config/kilo/agents/@kilocode/cli/)
|
|
130
|
+
- OpenCode: pkg='opencode-ai', pluginId='opencode-ai' (stored at ~/.config/opencode/agents/opencode-ai/)
|
|
131
|
+
|
|
132
|
+
Detection happens by spawning `bunx <package> --version` to check if tools are installed. Version detection uses pluginId to find the correct plugin.json file. Response from `/api/tools` includes: id, name, pkg, installed, status (one of: installed|needs_update|not_installed), isUpToDate, upgradeNeeded, hasUpdate. Frontend displays tools in UI and updates based on installation status.
|
|
103
133
|
|
|
104
134
|
### Tool Installation and Update UI Flow
|
|
105
135
|
|
|
@@ -162,3 +192,76 @@ Speech models (~470MB total) are downloaded automatically on server startup. No
|
|
|
162
192
|
|
|
163
193
|
### Cache Location
|
|
164
194
|
Models are stored at `~/.gmgui/models/` (whisper in `onnx-community/whisper-base/`, TTS in `tts/`).
|
|
195
|
+
|
|
196
|
+
## Tool Update Process Fix
|
|
197
|
+
|
|
198
|
+
### Issue
|
|
199
|
+
Tool update/install operations would complete successfully but the version display in the UI would not update to reflect the new version.
|
|
200
|
+
|
|
201
|
+
### Root Cause
|
|
202
|
+
The WebSocket broadcast event for tool update/install completion was missing the `version` field. The server was sending only the `freshStatus` object (which contains `installedVersion`), but not including the extracted `version` field from the tool-manager result.
|
|
203
|
+
|
|
204
|
+
Frontend expected: `data.data.version`
|
|
205
|
+
Backend was sending: only `data.data.installedVersion`
|
|
206
|
+
|
|
207
|
+
### Solution
|
|
208
|
+
Updated WebSocket broadcasts in `server.js`:
|
|
209
|
+
- Line 1883: Install endpoint now includes `version` in broadcast data
|
|
210
|
+
- Line 1942: Update endpoint now includes `version` in broadcast data
|
|
211
|
+
- Line 1987: Legacy install endpoint now saves `version` to database
|
|
212
|
+
|
|
213
|
+
The broadcasts now include both the immediately-detected `version` field and the comprehensive `freshStatus` object, ensuring the frontend has complete information to update the UI correctly.
|
|
214
|
+
|
|
215
|
+
### Testing
|
|
216
|
+
After update/install completes:
|
|
217
|
+
1. WebSocket event `tool_update_complete` or `tool_install_complete` is broadcast
|
|
218
|
+
2. Frontend receives complete data with `version`, `installedVersion`, `isUpToDate`, etc.
|
|
219
|
+
3. UI version display updates to show new version
|
|
220
|
+
4. Status reverts to "Installed" or "Up-to-date" accordingly
|
|
221
|
+
|
|
222
|
+
## Tool Update Testing & Diagnostics
|
|
223
|
+
|
|
224
|
+
A comprehensive diagnostic page is available at `http://localhost:3000/gm/tool-update-test.html` (`static/tool-update-test.html`) with 7 interactive test sections:
|
|
225
|
+
|
|
226
|
+
1. **API Connection Test** - Verifies server HTTP connectivity
|
|
227
|
+
2. **Get Tools Status** - Lists all tools with their current status, versions, and update availability
|
|
228
|
+
3. **WebSocket Connection Test** - Tests real-time event streaming (ping/pong)
|
|
229
|
+
4. **Single Tool Update Test** - Triggers update for a specific tool and monitors completion
|
|
230
|
+
5. **Event Stream Monitoring** - Watches all WebSocket events in real-time
|
|
231
|
+
6. **Database Status** - Checks database accessibility and tool persistence
|
|
232
|
+
7. **System Info** - Displays environment and configuration details
|
|
233
|
+
|
|
234
|
+
### Batch Update Fix (Critical)
|
|
235
|
+
|
|
236
|
+
**Issue:** When updating all tools via `/api/tools/update` endpoint, tool versions were not persisted to the database because the `version` parameter was missing from the `updateToolStatus` call.
|
|
237
|
+
|
|
238
|
+
**Location:** `server.js` line 1986 in the batch update handler (`/api/tools/update`)
|
|
239
|
+
|
|
240
|
+
**Fix Applied:**
|
|
241
|
+
```javascript
|
|
242
|
+
// BEFORE (missing version):
|
|
243
|
+
queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
|
|
244
|
+
|
|
245
|
+
// AFTER (version preserved):
|
|
246
|
+
const version = result.version || null;
|
|
247
|
+
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Impact:** Ensures tool versions are correctly saved after batch updates, enabling the UI to display accurate version information and update status across page reloads.
|
|
251
|
+
|
|
252
|
+
### Testing Tool Updates
|
|
253
|
+
|
|
254
|
+
**Manual Steps:**
|
|
255
|
+
1. Open `http://localhost:3000/gm/tool-update-test.html`
|
|
256
|
+
2. Click "Get Tools List" and note current versions
|
|
257
|
+
3. Click "Start Update" for a tool (e.g., gm-cc)
|
|
258
|
+
4. Monitor WebSocket events - you should see `tool_update_progress` and `tool_update_complete`
|
|
259
|
+
5. Click "Check Status" to verify version was saved to database
|
|
260
|
+
6. Reload the page - versions should persist
|
|
261
|
+
|
|
262
|
+
**Expected Outcomes:**
|
|
263
|
+
- Individual tool update: version saved ✓
|
|
264
|
+
- Batch tool update: version saved for all tools ✓
|
|
265
|
+
- Database persists across page reload ✓
|
|
266
|
+
- Frontend shows "Up-to-date" or "Update available" ✓
|
|
267
|
+
- Tool install history records the action ✓
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -1880,7 +1880,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1880
1880
|
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
1881
1881
|
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1882
1882
|
console.log(`[TOOLS-API] Fresh status after install for ${toolId}:`, JSON.stringify(freshStatus));
|
|
1883
|
-
broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, ...freshStatus } });
|
|
1883
|
+
broadcastSync({ type: 'tool_install_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
1884
1884
|
queries.addToolInstallHistory(toolId, 'install', 'success', null);
|
|
1885
1885
|
} else {
|
|
1886
1886
|
console.error(`[TOOLS-API] Install failed for ${toolId}:`, result.error);
|
|
@@ -1939,7 +1939,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1939
1939
|
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
1940
1940
|
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1941
1941
|
console.log(`[TOOLS-API] Fresh status after update for ${toolId}:`, JSON.stringify(freshStatus));
|
|
1942
|
-
broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, ...freshStatus } });
|
|
1942
|
+
broadcastSync({ type: 'tool_update_complete', toolId, data: { success: true, version, ...freshStatus } });
|
|
1943
1943
|
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
1944
1944
|
} else {
|
|
1945
1945
|
console.error(`[TOOLS-API] Update failed for ${toolId}:`, result.error);
|
|
@@ -1970,133 +1970,43 @@ const server = http.createServer(async (req, res) => {
|
|
|
1970
1970
|
return;
|
|
1971
1971
|
}
|
|
1972
1972
|
|
|
1973
|
-
|
|
1974
|
-
// Handle POST /api/tools/{toolId}/install - individual tool install
|
|
1975
|
-
const installMatch = pathOnly.match(/^\/api\/tools\/([^\/]+)\/install$/);
|
|
1976
|
-
if (installMatch && req.method === 'POST') {
|
|
1977
|
-
const toolId = installMatch[1];
|
|
1978
|
-
sendJSON(req, res, 200, { installing: true, toolId });
|
|
1979
|
-
setImmediate(async () => {
|
|
1980
|
-
try {
|
|
1981
|
-
const result = await toolManager.install(toolId, (msg) => {
|
|
1982
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1983
|
-
wsOptimizer.broadcast({ type: 'tool_install_progress', toolId, data: msg });
|
|
1984
|
-
}
|
|
1985
|
-
});
|
|
1986
|
-
if (result.success) {
|
|
1987
|
-
queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
|
|
1988
|
-
queries.addToolInstallHistory(toolId, 'install', 'success', null);
|
|
1989
|
-
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
1990
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1991
|
-
wsOptimizer.broadcast({ type: 'tool_install_complete', toolId, data: { ...result, ...freshStatus } });
|
|
1992
|
-
}
|
|
1993
|
-
} else {
|
|
1994
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
1995
|
-
queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
|
|
1996
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
1997
|
-
wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: result });
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
} catch (err) {
|
|
2001
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
|
|
2002
|
-
queries.addToolInstallHistory(toolId, 'install', 'failed', err.message);
|
|
2003
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2004
|
-
wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error: err.message } });
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
});
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
// Handle POST /api/tools/{toolId}/update - individual tool update
|
|
2012
|
-
const updateMatch = pathOnly.match(/^\/api\/tools\/([^\/]+)\/update$/);
|
|
2013
|
-
if (updateMatch && req.method === 'POST') {
|
|
2014
|
-
const toolId = updateMatch[1];
|
|
2015
|
-
sendJSON(req, res, 200, { updating: true, toolId });
|
|
2016
|
-
setImmediate(async () => {
|
|
2017
|
-
try {
|
|
2018
|
-
const result = await toolManager.update(toolId, (msg) => {
|
|
2019
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2020
|
-
wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
|
|
2021
|
-
}
|
|
2022
|
-
});
|
|
2023
|
-
if (result.success) {
|
|
2024
|
-
queries.updateToolStatus(toolId, { status: 'installed', installed_at: Date.now() });
|
|
2025
|
-
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
2026
|
-
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
2027
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2028
|
-
wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
|
|
2029
|
-
}
|
|
2030
|
-
} else {
|
|
2031
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
2032
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
2033
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2034
|
-
wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
} catch (err) {
|
|
2038
|
-
queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
|
|
2039
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
|
|
2040
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2041
|
-
wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
|
|
2042
|
-
}
|
|
2043
|
-
}
|
|
2044
|
-
});
|
|
2045
|
-
return;
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
1973
|
if (pathOnly === '/api/tools/update' && req.method === 'POST') {
|
|
2049
1974
|
sendJSON(req, res, 200, { updating: true, toolCount: 4 });
|
|
2050
|
-
|
|
2051
|
-
wsOptimizer.broadcast({ type: 'tools_update_started', tools: ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'] });
|
|
2052
|
-
}
|
|
1975
|
+
broadcastSync({ type: 'tools_update_started', tools: ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'] });
|
|
2053
1976
|
setImmediate(async () => {
|
|
2054
1977
|
const toolIds = ['gm-cc', 'gm-oc', 'gm-gc', 'gm-kilo'];
|
|
2055
1978
|
const results = {};
|
|
2056
1979
|
for (const toolId of toolIds) {
|
|
2057
1980
|
try {
|
|
2058
1981
|
const result = await toolManager.update(toolId, (msg) => {
|
|
2059
|
-
|
|
2060
|
-
wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
|
|
2061
|
-
}
|
|
1982
|
+
broadcastSync({ type: 'tool_update_progress', toolId, data: msg });
|
|
2062
1983
|
});
|
|
2063
1984
|
results[toolId] = result;
|
|
2064
1985
|
if (result.success) {
|
|
2065
|
-
|
|
1986
|
+
const version = result.version || null;
|
|
1987
|
+
queries.updateToolStatus(toolId, { status: 'installed', version, installed_at: Date.now() });
|
|
2066
1988
|
queries.addToolInstallHistory(toolId, 'update', 'success', null);
|
|
2067
1989
|
const freshStatus = await toolManager.checkToolStatusAsync(toolId);
|
|
2068
|
-
|
|
2069
|
-
wsOptimizer.broadcast({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
|
|
2070
|
-
}
|
|
1990
|
+
broadcastSync({ type: 'tool_update_complete', toolId, data: { ...result, ...freshStatus } });
|
|
2071
1991
|
} else {
|
|
2072
1992
|
queries.updateToolStatus(toolId, { status: 'failed', error_message: result.error });
|
|
2073
1993
|
queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
|
|
2074
|
-
|
|
2075
|
-
wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: result });
|
|
2076
|
-
}
|
|
1994
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: result });
|
|
2077
1995
|
}
|
|
2078
1996
|
} catch (err) {
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
queries.addToolInstallHistory(toolId, 'update', 'failed', error);
|
|
2083
|
-
if (wsOptimizer && wsOptimizer.broadcast) {
|
|
2084
|
-
wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { error } });
|
|
2085
|
-
}
|
|
1997
|
+
queries.updateToolStatus(toolId, { status: 'failed', error_message: err.message });
|
|
1998
|
+
queries.addToolInstallHistory(toolId, 'update', 'failed', err.message);
|
|
1999
|
+
broadcastSync({ type: 'tool_update_failed', toolId, data: { success: false, error: err.message } });
|
|
2086
2000
|
}
|
|
2087
2001
|
}
|
|
2088
|
-
|
|
2089
|
-
wsOptimizer.broadcast({ type: 'tools_update_complete', data: results });
|
|
2090
|
-
}
|
|
2002
|
+
broadcastSync({ type: 'tools_update_complete', data: results });
|
|
2091
2003
|
});
|
|
2092
2004
|
return;
|
|
2093
2005
|
}
|
|
2094
2006
|
|
|
2095
2007
|
if (pathOnly === '/api/tools/refresh-all' && req.method === 'POST') {
|
|
2096
2008
|
sendJSON(req, res, 200, { refreshing: true, toolCount: 4 });
|
|
2097
|
-
|
|
2098
|
-
wsOptimizer.broadcast({ type: 'tools_refresh_started' });
|
|
2099
|
-
}
|
|
2009
|
+
broadcastSync({ type: 'tools_refresh_started' });
|
|
2100
2010
|
setImmediate(async () => {
|
|
2101
2011
|
const tools = toolManager.getAllTools();
|
|
2102
2012
|
for (const tool of tools) {
|
|
@@ -2112,9 +2022,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
2112
2022
|
}
|
|
2113
2023
|
}
|
|
2114
2024
|
}
|
|
2115
|
-
|
|
2116
|
-
wsOptimizer.broadcast({ type: 'tools_refresh_complete', data: tools });
|
|
2117
|
-
}
|
|
2025
|
+
broadcastSync({ type: 'tools_refresh_complete', data: tools });
|
|
2118
2026
|
});
|
|
2119
2027
|
return;
|
|
2120
2028
|
}
|
package/static/js/voice.js
CHANGED
|
@@ -453,6 +453,43 @@
|
|
|
453
453
|
});
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
function preGenerateTTS(text) {
|
|
457
|
+
if (!ttsEnabled) return;
|
|
458
|
+
var clean = text.replace(/<[^>]*>/g, '').trim();
|
|
459
|
+
if (!clean) return;
|
|
460
|
+
var parts = [];
|
|
461
|
+
if (typeof agentGUIClient !== 'undefined' && agentGUIClient && typeof agentGUIClient.parseMarkdownCodeBlocks === 'function') {
|
|
462
|
+
parts = agentGUIClient.parseMarkdownCodeBlocks(clean);
|
|
463
|
+
} else {
|
|
464
|
+
parts = [{ type: 'text', content: clean }];
|
|
465
|
+
}
|
|
466
|
+
parts.forEach(function(part) {
|
|
467
|
+
if (part.type === 'code') return;
|
|
468
|
+
var segment = part.content.trim();
|
|
469
|
+
if (!segment) return;
|
|
470
|
+
var cacheKey = selectedVoiceId + ':' + segment;
|
|
471
|
+
if (ttsAudioCache.has(cacheKey)) return;
|
|
472
|
+
var optimizedText = optimizePromptForSpeech(segment);
|
|
473
|
+
fetch(BASE + '/api/tts', {
|
|
474
|
+
method: 'POST',
|
|
475
|
+
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
body: JSON.stringify({ text: optimizedText, voiceId: selectedVoiceId })
|
|
477
|
+
}).then(function(resp) {
|
|
478
|
+
if (!resp.ok) throw new Error('TTS pre-generation failed: ' + resp.status);
|
|
479
|
+
return resp.arrayBuffer();
|
|
480
|
+
}).then(function(buf) {
|
|
481
|
+
var blob = new Blob([buf], { type: 'audio/wav' });
|
|
482
|
+
if (ttsAudioCache.size >= TTS_CLIENT_CACHE_MAX) {
|
|
483
|
+
var oldest = ttsAudioCache.keys().next().value;
|
|
484
|
+
ttsAudioCache.delete(oldest);
|
|
485
|
+
}
|
|
486
|
+
ttsAudioCache.set(cacheKey, blob);
|
|
487
|
+
}).catch(function(err) {
|
|
488
|
+
console.warn('[Voice] TTS pre-generation failed:', err);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
456
493
|
function processQueue() {
|
|
457
494
|
if (isSpeaking || speechQueue.length === 0) return;
|
|
458
495
|
if (ttsDisabledUntilReset) {
|
|
@@ -783,6 +820,7 @@
|
|
|
783
820
|
var div = addVoiceBlock(block.text, isUser);
|
|
784
821
|
if (div && isNew && ttsEnabled && blockRole === 'assistant') {
|
|
785
822
|
div.classList.add('speaking');
|
|
823
|
+
preGenerateTTS(block.text);
|
|
786
824
|
speak(block.text);
|
|
787
825
|
setTimeout(function() { div.classList.remove('speaking'); }, 2000);
|
|
788
826
|
}
|
package/test-fixes.mjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test script for validating the two fixes:
|
|
5
|
+
* 1. Agent selector visibility in chat view
|
|
6
|
+
* 2. TTS streaming pre-generation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
console.log('=== AgentGUI Fix Validation ===\n');
|
|
13
|
+
|
|
14
|
+
// Test 1: Verify agent selectors are NOT hidden on desktop
|
|
15
|
+
console.log('Test 1: Agent Selector Visibility');
|
|
16
|
+
console.log('-----------------------------------');
|
|
17
|
+
const html = fs.readFileSync('./static/index.html', 'utf-8');
|
|
18
|
+
const beforeMobileMedia = html.substring(0, html.indexOf('@media (max-width: 480px)'));
|
|
19
|
+
const cliHiddenOnDesktop = beforeMobileMedia.includes('.cli-selector { display: none; }');
|
|
20
|
+
const modelHiddenOnDesktop = beforeMobileMedia.includes('.model-selector { display: none; }');
|
|
21
|
+
|
|
22
|
+
if (!cliHiddenOnDesktop && !modelHiddenOnDesktop) {
|
|
23
|
+
console.log('✓ PASS: Agent selectors are NOT hidden by CSS on desktop');
|
|
24
|
+
console.log(' - CLI selector will be visible when populated');
|
|
25
|
+
console.log(' - Model selector will be visible when populated');
|
|
26
|
+
} else {
|
|
27
|
+
console.log('❌ FAIL: Agent selectors are still hidden on desktop');
|
|
28
|
+
if (cliHiddenOnDesktop) console.log(' - .cli-selector has display:none');
|
|
29
|
+
if (modelHiddenOnDesktop) console.log(' - .model-selector has display:none');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Verify selectors are in HTML
|
|
33
|
+
const hasSelectors = html.includes('data-cli-selector') &&
|
|
34
|
+
html.includes('data-agent-selector') &&
|
|
35
|
+
html.includes('data-model-selector');
|
|
36
|
+
if (hasSelectors) {
|
|
37
|
+
console.log('✓ PASS: All selector elements are present in HTML');
|
|
38
|
+
} else {
|
|
39
|
+
console.log('❌ FAIL: Some selector elements are missing');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test 2: Verify TTS pre-generation is implemented
|
|
43
|
+
console.log('\nTest 2: TTS Streaming Pre-generation');
|
|
44
|
+
console.log('--------------------------------------');
|
|
45
|
+
const voiceJs = fs.readFileSync('./static/js/voice.js', 'utf-8');
|
|
46
|
+
const hasPreGenerateFunction = voiceJs.includes('function preGenerateTTS(text)');
|
|
47
|
+
const callsPreGenerate = voiceJs.includes('preGenerateTTS(block.text)');
|
|
48
|
+
const usesCache = voiceJs.includes('if (ttsAudioCache.has(cacheKey)) return;');
|
|
49
|
+
const fetchesTTS = voiceJs.match(/fetch\(BASE \+ '\/api\/tts'/g)?.length >= 2; // Should be in both preGenerate and processQueue
|
|
50
|
+
|
|
51
|
+
if (hasPreGenerateFunction && callsPreGenerate && usesCache && fetchesTTS) {
|
|
52
|
+
console.log('✓ PASS: TTS pre-generation is fully implemented');
|
|
53
|
+
console.log(' - preGenerateTTS function exists');
|
|
54
|
+
console.log(' - Called when assistant text arrives');
|
|
55
|
+
console.log(' - Uses cache to avoid duplicate generation');
|
|
56
|
+
console.log(' - Audio generation happens in background');
|
|
57
|
+
} else {
|
|
58
|
+
console.log('❌ FAIL: TTS pre-generation incomplete');
|
|
59
|
+
if (!hasPreGenerateFunction) console.log(' - Missing preGenerateTTS function');
|
|
60
|
+
if (!callsPreGenerate) console.log(' - Not called in handleVoiceBlock');
|
|
61
|
+
if (!usesCache) console.log(' - Missing cache check');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Test 3: Check that client.js populates selectors correctly
|
|
65
|
+
console.log('\nTest 3: Selector Population Logic');
|
|
66
|
+
console.log('-----------------------------------');
|
|
67
|
+
const clientJs = fs.readFileSync('./static/js/client.js', 'utf-8');
|
|
68
|
+
const setsDisplay = clientJs.includes("this.ui.cliSelector.style.display = 'inline-block'");
|
|
69
|
+
const populatesOptions = clientJs.includes('.innerHTML = displayAgents');
|
|
70
|
+
|
|
71
|
+
if (setsDisplay && populatesOptions) {
|
|
72
|
+
console.log('✓ PASS: Client.js correctly populates and displays selectors');
|
|
73
|
+
console.log(' - Sets display to inline-block when agents loaded');
|
|
74
|
+
console.log(' - Populates options from agent list');
|
|
75
|
+
} else {
|
|
76
|
+
console.log('❌ FAIL: Selector population logic may be broken');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Summary
|
|
80
|
+
console.log('\n=== SUMMARY ===');
|
|
81
|
+
const allTestsPass = !cliHiddenOnDesktop && !modelHiddenOnDesktop &&
|
|
82
|
+
hasSelectors && hasPreGenerateFunction &&
|
|
83
|
+
callsPreGenerate && usesCache &&
|
|
84
|
+
setsDisplay && populatesOptions;
|
|
85
|
+
|
|
86
|
+
if (allTestsPass) {
|
|
87
|
+
console.log('✓ ALL TESTS PASSED\n');
|
|
88
|
+
console.log('Manual Testing Instructions:');
|
|
89
|
+
console.log('1. Start server: npm run dev');
|
|
90
|
+
console.log('2. Open http://localhost:3000/gm/');
|
|
91
|
+
console.log('3. Check chat input area - you should see:');
|
|
92
|
+
console.log(' - CLI selector dropdown (e.g., "Claude")');
|
|
93
|
+
console.log(' - Model selector dropdown (e.g., "Sonnet 4.5")');
|
|
94
|
+
console.log(' - Microphone button for voice input');
|
|
95
|
+
console.log('4. Open Voice tab and enable "Auto-speak responses"');
|
|
96
|
+
console.log('5. Send a message and observe:');
|
|
97
|
+
console.log(' - Text streams in as agent responds');
|
|
98
|
+
console.log(' - Voice playback starts immediately after completion (no delay)');
|
|
99
|
+
console.log(' - Audio was pre-generated during streaming');
|
|
100
|
+
} else {
|
|
101
|
+
console.log('❌ SOME TESTS FAILED - Review output above\n');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|