agentgui 1.0.131 → 1.0.138
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 +46 -0
- package/bin/gmgui.cjs +1 -1
- package/database.js +10 -1
- package/package.json +2 -1
- package/server.js +75 -9
- package/setup-npm-token.sh +68 -0
- package/static/app.js +5 -3
- package/static/index.html +225 -3
- package/static/js/client.js +7 -3
- package/static/js/conversations.js +12 -5
- package/static/js/features.js +17 -17
- package/static/js/streaming-renderer.js +48 -24
- package/static/js/syntax-highlighter.js +1 -1
- package/static/js/voice.js +430 -0
- package/static/styles.css +86 -0
package/CLAUDE.md
CHANGED
|
@@ -128,6 +128,51 @@ Production ready - no additional configuration needed beyond:
|
|
|
128
128
|
2. Set PORT environment variable if needed
|
|
129
129
|
3. Run: `npm start` or `npm run dev` for development
|
|
130
130
|
|
|
131
|
+
## npm Publishing Setup
|
|
132
|
+
|
|
133
|
+
Automated npm publishing is configured via GitHub Actions with OIDC authentication. To complete setup:
|
|
134
|
+
|
|
135
|
+
### Step 1: Configure OIDC Trusted Publisher on npm.org
|
|
136
|
+
|
|
137
|
+
Visit: https://www.npmjs.com/package/agentgui/access
|
|
138
|
+
|
|
139
|
+
Click "Add Trusted Publisher" and fill in:
|
|
140
|
+
- **Publishing provider**: GitHub
|
|
141
|
+
- **Owner**: AnEntrypoint
|
|
142
|
+
- **Repository**: agentgui
|
|
143
|
+
- **Workflow file**: `.github/workflows/publish-npm.yml`
|
|
144
|
+
|
|
145
|
+
This requires npm account access with 2FA completion.
|
|
146
|
+
|
|
147
|
+
### Step 2: Trigger Publishing Workflow
|
|
148
|
+
|
|
149
|
+
Once OIDC is configured, push to main branch to trigger automatic publishing:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
git commit --allow-empty -m "test: verify npm publish with OIDC"
|
|
153
|
+
git push
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Monitor at: https://github.com/AnEntrypoint/agentgui/actions
|
|
157
|
+
|
|
158
|
+
### Optional: Add Granular Token Backup
|
|
159
|
+
|
|
160
|
+
Generate a 3-month granular access token for fallback authentication:
|
|
161
|
+
|
|
162
|
+
Visit: https://www.npmjs.com/settings/lanmower/tokens
|
|
163
|
+
|
|
164
|
+
Click "Generate New Token" → "Granular Access Token" and configure:
|
|
165
|
+
- **Name**: github-actions-3month
|
|
166
|
+
- **Permissions**: Read and write
|
|
167
|
+
- **Package**: agentgui
|
|
168
|
+
- **Expiration**: 90 days
|
|
169
|
+
- **Bypass 2FA**: enabled
|
|
170
|
+
|
|
171
|
+
Then add to GitHub Actions secrets:
|
|
172
|
+
```bash
|
|
173
|
+
gh secret set NPM_TOKEN --body "YOUR_TOKEN" --repo AnEntrypoint/agentgui
|
|
174
|
+
```
|
|
175
|
+
|
|
131
176
|
## Support
|
|
132
177
|
|
|
133
178
|
For issues, check:
|
|
@@ -135,3 +180,4 @@ For issues, check:
|
|
|
135
180
|
- Server logs for backend issues
|
|
136
181
|
- Database at `./data/agentgui.db` for data persistence
|
|
137
182
|
- WebSocket connection in Network tab (should show `/sync` as connected)
|
|
183
|
+
- GitHub Actions: https://github.com/AnEntrypoint/agentgui/actions for publishing errors
|
package/bin/gmgui.cjs
CHANGED
|
@@ -44,7 +44,7 @@ async function gmgui(args = []) {
|
|
|
44
44
|
return new Promise((resolve, reject) => {
|
|
45
45
|
const ps = spawn(runtime, [path.join(projectRoot, 'server.js')], {
|
|
46
46
|
cwd: projectRoot,
|
|
47
|
-
env: { ...process.env, PORT: port, BASE_URL: baseUrl },
|
|
47
|
+
env: { ...process.env, PORT: port, BASE_URL: baseUrl, STARTUP_CWD: process.cwd() },
|
|
48
48
|
stdio: 'inherit'
|
|
49
49
|
});
|
|
50
50
|
|
package/database.js
CHANGED
|
@@ -573,11 +573,19 @@ export const queries = {
|
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
const deleteStmt = db.transaction(() => {
|
|
576
|
+
const sessionIds = db.prepare('SELECT id FROM sessions WHERE conversationId = ?').all(id).map(r => r.id);
|
|
577
|
+
db.prepare('DELETE FROM stream_updates WHERE conversationId = ?').run(id);
|
|
576
578
|
db.prepare('DELETE FROM chunks WHERE conversationId = ?').run(id);
|
|
577
579
|
db.prepare('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
580
|
+
if (sessionIds.length > 0) {
|
|
581
|
+
const placeholders = sessionIds.map(() => '?').join(',');
|
|
582
|
+
db.prepare(`DELETE FROM stream_updates WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
583
|
+
db.prepare(`DELETE FROM chunks WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
584
|
+
db.prepare(`DELETE FROM events WHERE sessionId IN (${placeholders})`).run(...sessionIds);
|
|
585
|
+
}
|
|
578
586
|
db.prepare('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
|
579
587
|
db.prepare('DELETE FROM messages WHERE conversationId = ?').run(id);
|
|
580
|
-
db.prepare('
|
|
588
|
+
db.prepare('DELETE FROM conversations WHERE id = ?').run(id);
|
|
581
589
|
});
|
|
582
590
|
|
|
583
591
|
deleteStmt();
|
|
@@ -1064,6 +1072,7 @@ export const queries = {
|
|
|
1064
1072
|
}
|
|
1065
1073
|
|
|
1066
1074
|
const deleteStmt = db.transaction(() => {
|
|
1075
|
+
db.prepare('DELETE FROM stream_updates WHERE conversationId = ?').run(id);
|
|
1067
1076
|
db.prepare('DELETE FROM chunks WHERE conversationId = ?').run(id);
|
|
1068
1077
|
db.prepare('DELETE FROM events WHERE conversationId = ?').run(id);
|
|
1069
1078
|
db.prepare('DELETE FROM sessions WHERE conversationId = ?').run(id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.138",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"busboy": "^1.6.0",
|
|
27
27
|
"express": "^5.2.1",
|
|
28
28
|
"fsbrowse": "^0.2.13",
|
|
29
|
+
"webtalk": "github:anEntrypoint/realtime-whisper-webgpu",
|
|
29
30
|
"ws": "^8.14.2"
|
|
30
31
|
}
|
|
31
32
|
}
|
package/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
5
6
|
import { WebSocketServer } from 'ws';
|
|
6
7
|
import { execSync } from 'child_process';
|
|
@@ -11,7 +12,8 @@ import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
|
11
12
|
const require = createRequire(import.meta.url);
|
|
12
13
|
const express = require('express');
|
|
13
14
|
const Busboy = require('busboy');
|
|
14
|
-
const fsbrowse = require('
|
|
15
|
+
const fsbrowse = require('fsbrowse');
|
|
16
|
+
const { webtalk } = require('webtalk');
|
|
15
17
|
|
|
16
18
|
const SYSTEM_PROMPT = `Always write your responses in ripple-ui enhanced HTML. Avoid overriding light/dark mode CSS variables. Use all the benefits of HTML to express technical details with proper semantic markup, tables, code blocks, headings, and lists. Write clean, well-structured HTML that respects the existing design system.`;
|
|
17
19
|
|
|
@@ -28,12 +30,18 @@ const PORT = process.env.PORT || 3000;
|
|
|
28
30
|
const BASE_URL = (process.env.BASE_URL || '/gm').replace(/\/+$/, '');
|
|
29
31
|
const watch = process.argv.includes('--no-watch') ? false : (process.argv.includes('--watch') || process.env.HOT_RELOAD !== 'false');
|
|
30
32
|
|
|
33
|
+
const STARTUP_CWD = process.env.STARTUP_CWD || process.cwd();
|
|
31
34
|
const staticDir = path.join(__dirname, 'static');
|
|
32
35
|
if (!fs.existsSync(staticDir)) fs.mkdirSync(staticDir, { recursive: true });
|
|
33
36
|
|
|
34
37
|
// Express sub-app for fsbrowse file browser and file upload
|
|
35
38
|
const expressApp = express();
|
|
36
39
|
|
|
40
|
+
// Separate Express app for webtalk (STT/TTS) - isolated to contain COEP/COOP headers
|
|
41
|
+
const webtalkApp = express();
|
|
42
|
+
const webtalkInstance = webtalk(webtalkApp, { path: '/webtalk' });
|
|
43
|
+
webtalkInstance.init().catch(err => debugLog('Webtalk init: ' + err.message));
|
|
44
|
+
|
|
37
45
|
// File upload endpoint - copies dropped files to conversation workingDirectory
|
|
38
46
|
expressApp.post(BASE_URL + '/api/upload/:conversationId', (req, res) => {
|
|
39
47
|
try {
|
|
@@ -139,14 +147,72 @@ const server = http.createServer(async (req, res) => {
|
|
|
139
147
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
140
148
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
141
149
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
150
|
+
res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless');
|
|
151
|
+
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
|
152
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
142
153
|
if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
|
|
143
154
|
|
|
144
|
-
// Route file upload and fsbrowse requests through Express sub-app
|
|
145
155
|
const pathOnly = req.url.split('?')[0];
|
|
156
|
+
const webtalkPrefix = BASE_URL + '/webtalk';
|
|
157
|
+
const isWebtalkRoute = pathOnly.startsWith(webtalkPrefix) ||
|
|
158
|
+
pathOnly.startsWith(BASE_URL + '/api/tts-status') ||
|
|
159
|
+
pathOnly.startsWith(BASE_URL + '/assets/') ||
|
|
160
|
+
pathOnly.startsWith(BASE_URL + '/tts/') ||
|
|
161
|
+
pathOnly.startsWith(BASE_URL + '/models/') ||
|
|
162
|
+
pathOnly.startsWith('/webtalk') ||
|
|
163
|
+
pathOnly.startsWith('/assets/') ||
|
|
164
|
+
pathOnly.startsWith('/tts/') ||
|
|
165
|
+
pathOnly.startsWith('/models/');
|
|
166
|
+
if (isWebtalkRoute) {
|
|
167
|
+
const webtalkSdkDir = path.dirname(require.resolve('webtalk/package.json'));
|
|
168
|
+
const sdkFiles = { '/demo': 'app.html', '/sdk.js': 'sdk.js', '/stt.js': 'stt.js', '/tts.js': 'tts.js', '/tts-utils.js': 'tts-utils.js' };
|
|
169
|
+
let stripped = pathOnly.startsWith(webtalkPrefix) ? pathOnly.slice(webtalkPrefix.length) : (pathOnly.startsWith('/webtalk') ? pathOnly.slice('/webtalk'.length) : null);
|
|
170
|
+
if (stripped !== null && !sdkFiles[stripped] && !stripped.endsWith('.js') && sdkFiles[stripped + '.js']) stripped += '.js';
|
|
171
|
+
if (stripped !== null && sdkFiles[stripped]) {
|
|
172
|
+
const filePath = path.join(webtalkSdkDir, sdkFiles[stripped]);
|
|
173
|
+
return fs.readFile(filePath, 'utf-8', (err, content) => {
|
|
174
|
+
if (err) { res.writeHead(404); res.end('Not found'); return; }
|
|
175
|
+
if (stripped === '/demo') {
|
|
176
|
+
let patched = content
|
|
177
|
+
.replace(/from\s+['"](\/webtalk\/[^'"]+)['"]/g, (_, p) => `from '${BASE_URL}${p}'`)
|
|
178
|
+
.replace(/from\s+['"]\.\/([^'"]+)['"]/g, (_, p) => `from '${BASE_URL}/webtalk/${p}'`)
|
|
179
|
+
.replace('<head>', `<head>\n <script>window.__WEBTALK_BASE='${BASE_URL}';</script>`);
|
|
180
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cross-Origin-Embedder-Policy': 'credentialless', 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Resource-Policy': 'cross-origin' });
|
|
181
|
+
return res.end(patched);
|
|
182
|
+
}
|
|
183
|
+
let js = content;
|
|
184
|
+
const ensureExt = (mod) => mod.endsWith('.js') ? mod : mod + '.js';
|
|
185
|
+
if (js.includes('require(') || js.includes('module.exports')) {
|
|
186
|
+
js = js.replace(/const\s*\{([^}]+)\}\s*=\s*require\(['"]\.\/([^'"]+)['"]\);?/g, (_, names, mod) => `import {${names}} from '${BASE_URL}/webtalk/${ensureExt(mod)}';`);
|
|
187
|
+
js = js.replace(/const\s+(\w+)\s*=\s*require\(['"]\.\/([^'"]+)['"]\);?/g, (_, name, mod) => `import ${name} from '${BASE_URL}/webtalk/${ensureExt(mod)}';`);
|
|
188
|
+
js = js.replace(/module\.exports\s*=\s*\{([^}]+)\};?/, (_, names) => `export {${names.trim().replace(/\s+/g, ' ')} };`);
|
|
189
|
+
}
|
|
190
|
+
js = js.replace(/from\s+['"]\.\/([^'"]+)['"]/g, (_, p) => `from '${BASE_URL}/webtalk/${ensureExt(p)}'`);
|
|
191
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8', 'Cross-Origin-Resource-Policy': 'cross-origin' });
|
|
192
|
+
res.end(js);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (req.url.startsWith(BASE_URL)) req.url = req.url.slice(BASE_URL.length) || '/';
|
|
196
|
+
const origSetHeader = res.setHeader.bind(res);
|
|
197
|
+
res.setHeader = (name, value) => {
|
|
198
|
+
if (name.toLowerCase() === 'cross-origin-embedder-policy') return;
|
|
199
|
+
origSetHeader(name, value);
|
|
200
|
+
};
|
|
201
|
+
return webtalkApp(req, res);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Route file upload and fsbrowse requests through Express sub-app
|
|
146
205
|
if (pathOnly.startsWith(BASE_URL + '/api/upload/') || pathOnly.startsWith(BASE_URL + '/files/')) {
|
|
147
206
|
return expressApp(req, res);
|
|
148
207
|
}
|
|
149
208
|
|
|
209
|
+
if (req.url === '/favicon.ico' || req.url === BASE_URL + '/favicon.ico') {
|
|
210
|
+
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100" rx="20" fill="#3b82f6"/><text x="50" y="68" font-size="50" font-family="sans-serif" font-weight="bold" fill="white" text-anchor="middle">G</text></svg>';
|
|
211
|
+
res.writeHead(200, { 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=86400' });
|
|
212
|
+
res.end(svg);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
150
216
|
if (req.url === '/') { res.writeHead(302, { Location: BASE_URL + '/' }); res.end(); return; }
|
|
151
217
|
|
|
152
218
|
if (!req.url.startsWith(BASE_URL + '/') && req.url !== BASE_URL) {
|
|
@@ -424,16 +490,16 @@ const server = http.createServer(async (req, res) => {
|
|
|
424
490
|
|
|
425
491
|
if (routePath === '/api/home' && req.method === 'GET') {
|
|
426
492
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
427
|
-
res.end(JSON.stringify({ home:
|
|
493
|
+
res.end(JSON.stringify({ home: os.homedir(), cwd: STARTUP_CWD }));
|
|
428
494
|
return;
|
|
429
495
|
}
|
|
430
496
|
|
|
431
497
|
if (routePath === '/api/folders' && req.method === 'POST') {
|
|
432
498
|
const body = await parseBody(req);
|
|
433
|
-
const folderPath = body.path ||
|
|
499
|
+
const folderPath = body.path || STARTUP_CWD;
|
|
434
500
|
try {
|
|
435
501
|
const expandedPath = folderPath.startsWith('~') ?
|
|
436
|
-
folderPath.replace('~',
|
|
502
|
+
folderPath.replace('~', os.homedir()) : folderPath;
|
|
437
503
|
const entries = fs.readdirSync(expandedPath, { withFileTypes: true });
|
|
438
504
|
const folders = entries
|
|
439
505
|
.filter(e => e.isDirectory() && !e.name.startsWith('.'))
|
|
@@ -452,7 +518,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
452
518
|
const imagePath = routePath.slice('/api/image/'.length);
|
|
453
519
|
const decodedPath = decodeURIComponent(imagePath);
|
|
454
520
|
const expandedPath = decodedPath.startsWith('~') ?
|
|
455
|
-
decodedPath.replace('~',
|
|
521
|
+
decodedPath.replace('~', os.homedir()) : decodedPath;
|
|
456
522
|
const normalizedPath = path.normalize(expandedPath);
|
|
457
523
|
if (!normalizedPath.startsWith('/') || normalizedPath.includes('..')) {
|
|
458
524
|
res.writeHead(403); res.end('Forbidden'); return;
|
|
@@ -510,7 +576,7 @@ function serveFile(filePath, res) {
|
|
|
510
576
|
if (err) { res.writeHead(500); res.end('Server error'); return; }
|
|
511
577
|
let content = data.toString();
|
|
512
578
|
if (ext === '.html') {
|
|
513
|
-
const baseTag = `<script>window.__BASE_URL='${BASE_URL}';</script>`;
|
|
579
|
+
const baseTag = `<script>window.__BASE_URL='${BASE_URL}';</script>\n <script type="importmap">{"imports":{"webtalk-sdk":"${BASE_URL}/webtalk/sdk.js"}}</script>`;
|
|
514
580
|
content = content.replace('<head>', '<head>\n ' + baseTag);
|
|
515
581
|
if (watch) {
|
|
516
582
|
content += `\n<script>(function(){const ws=new WebSocket('ws://'+location.host+'${BASE_URL}/hot-reload');ws.onmessage=e=>{if(JSON.parse(e.data).type==='reload')location.reload()};})();</script>`;
|
|
@@ -556,7 +622,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
556
622
|
debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}`);
|
|
557
623
|
|
|
558
624
|
const conv = queries.getConversation(conversationId);
|
|
559
|
-
const cwd = conv?.workingDirectory ||
|
|
625
|
+
const cwd = conv?.workingDirectory || STARTUP_CWD;
|
|
560
626
|
const resumeSessionId = conv?.claudeSessionId || null;
|
|
561
627
|
|
|
562
628
|
let allBlocks = [];
|
|
@@ -762,7 +828,7 @@ async function processMessage(conversationId, messageId, content, agentId) {
|
|
|
762
828
|
debugLog(`[processMessage] Starting: conversationId=${conversationId}, agentId=${agentId}`);
|
|
763
829
|
|
|
764
830
|
const conv = queries.getConversation(conversationId);
|
|
765
|
-
const cwd = conv?.workingDirectory ||
|
|
831
|
+
const cwd = conv?.workingDirectory || STARTUP_CWD;
|
|
766
832
|
const resumeSessionId = conv?.claudeSessionId || null;
|
|
767
833
|
|
|
768
834
|
let contentStr = typeof content === 'object' ? JSON.stringify(content) : content;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "=========================================="
|
|
5
|
+
echo "npm Granular Access Token Setup (3-Month)"
|
|
6
|
+
echo "=========================================="
|
|
7
|
+
echo ""
|
|
8
|
+
echo "STEP 1: Manual token generation on npm.org"
|
|
9
|
+
echo "=========================================="
|
|
10
|
+
echo ""
|
|
11
|
+
echo "Please follow these steps:"
|
|
12
|
+
echo ""
|
|
13
|
+
echo "1. Open your browser and go to:"
|
|
14
|
+
echo " https://www.npmjs.com/settings/lanmower/tokens"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "2. Click 'Generate New Token'"
|
|
17
|
+
echo ""
|
|
18
|
+
echo "3. Select 'Granular Access Token'"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "4. Fill in the form:"
|
|
21
|
+
echo " - Token name: github-actions-3month"
|
|
22
|
+
echo " - Description: GitHub Actions npm publishing (3 month validity)"
|
|
23
|
+
echo " - Permissions: Read and write"
|
|
24
|
+
echo " - Packages: agentgui"
|
|
25
|
+
echo " - Expiration: 90 days"
|
|
26
|
+
echo " - Bypass 2FA: CHECKED"
|
|
27
|
+
echo ""
|
|
28
|
+
echo "5. Click 'Generate'"
|
|
29
|
+
echo ""
|
|
30
|
+
echo "6. COPY the token value (shown only once!)"
|
|
31
|
+
echo ""
|
|
32
|
+
echo "=========================================="
|
|
33
|
+
echo "STEP 2: Add token to GitHub Actions secret"
|
|
34
|
+
echo "=========================================="
|
|
35
|
+
echo ""
|
|
36
|
+
read -p "Paste your npm token here: " NPM_TOKEN
|
|
37
|
+
|
|
38
|
+
if [ -z "$NPM_TOKEN" ]; then
|
|
39
|
+
echo "Error: Token cannot be empty"
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo ""
|
|
44
|
+
echo "Setting GitHub Actions secret..."
|
|
45
|
+
|
|
46
|
+
gh secret set NPM_TOKEN --body "$NPM_TOKEN" --repo AnEntrypoint/agentgui
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
echo "Verifying secret was set..."
|
|
50
|
+
gh secret list --repo AnEntrypoint/agentgui | grep NPM_TOKEN
|
|
51
|
+
|
|
52
|
+
echo ""
|
|
53
|
+
echo "=========================================="
|
|
54
|
+
echo "STEP 3: Test the workflow"
|
|
55
|
+
echo "=========================================="
|
|
56
|
+
echo ""
|
|
57
|
+
echo "Creating test commit to trigger workflow..."
|
|
58
|
+
|
|
59
|
+
git -C /home/user/agentgui commit --allow-empty -m "test: verify npm publishing with 3-month token"
|
|
60
|
+
git -C /home/user/agentgui push
|
|
61
|
+
|
|
62
|
+
echo ""
|
|
63
|
+
echo "Monitor the workflow at:"
|
|
64
|
+
echo "https://github.com/AnEntrypoint/agentgui/actions"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "=========================================="
|
|
67
|
+
echo "Setup Complete!"
|
|
68
|
+
echo "=========================================="
|
package/static/app.js
CHANGED
|
@@ -494,7 +494,8 @@ class GMGUIApp {
|
|
|
494
494
|
codeLines.push(lines[i]);
|
|
495
495
|
i++;
|
|
496
496
|
}
|
|
497
|
-
|
|
497
|
+
const clCount = codeLines.length;
|
|
498
|
+
html += `<details class="collapsible-code"><summary class="collapsible-code-summary">${this.escapeHtml(lang)} - ${clCount} line${clCount !== 1 ? 's' : ''}</summary><div class="code-block" data-language="${this.escapeHtml(lang)}"><pre><code>${this.escapeHtml(codeLines.join('\n'))}</code></pre></div></details>`;
|
|
498
499
|
i++;
|
|
499
500
|
continue;
|
|
500
501
|
}
|
|
@@ -592,9 +593,10 @@ class GMGUIApp {
|
|
|
592
593
|
<div class="html-content">${code}</div>
|
|
593
594
|
</div>`;
|
|
594
595
|
} else {
|
|
595
|
-
|
|
596
|
+
const lcCount = code.split('\n').length;
|
|
597
|
+
return `<details class="collapsible-code"><summary class="collapsible-code-summary">${this.escapeHtml(language)} - ${lcCount} line${lcCount !== 1 ? 's' : ''}</summary><div class="code-block" data-language="${this.escapeHtml(language)}">
|
|
596
598
|
<pre><code>${this.escapeHtml(code)}</code></pre>
|
|
597
|
-
</div>`;
|
|
599
|
+
</div></details>`;
|
|
598
600
|
}
|
|
599
601
|
}
|
|
600
602
|
|
package/static/index.html
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
6
6
|
<meta name="description" content="AgentGUI - Real-time Claude Code Execution Visualization">
|
|
7
7
|
<title>AgentGUI</title>
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.ico">
|
|
8
9
|
|
|
9
|
-
<link href="https://
|
|
10
|
-
<link href="https://
|
|
10
|
+
<link href="https://cdn.jsdelivr.net/npm/rippleui@1.12.1/dist/css/styles.css" rel="stylesheet">
|
|
11
|
+
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-dark.css" rel="stylesheet">
|
|
11
12
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github-dark.min.css" rel="stylesheet">
|
|
12
13
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
|
|
13
14
|
|
|
@@ -821,6 +822,7 @@
|
|
|
821
822
|
/* ===== File browser ===== */
|
|
822
823
|
.file-browser-container { flex:1; min-height:0; overflow:hidden; }
|
|
823
824
|
.file-browser-iframe { width:100%; height:100%; border:none; }
|
|
825
|
+
.voice-iframe { width:100%; height:100%; border:none; }
|
|
824
826
|
|
|
825
827
|
/* ===== RESPONSIVE: MOBILE ===== */
|
|
826
828
|
@media (max-width: 768px) {
|
|
@@ -914,6 +916,191 @@
|
|
|
914
916
|
scrollbar-color: #475569 transparent;
|
|
915
917
|
}
|
|
916
918
|
|
|
919
|
+
/* ===== VOICE VIEW ===== */
|
|
920
|
+
.voice-container {
|
|
921
|
+
flex: 1;
|
|
922
|
+
min-height: 0;
|
|
923
|
+
overflow: hidden;
|
|
924
|
+
display: flex;
|
|
925
|
+
flex-direction: column;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
.voice-scroll {
|
|
929
|
+
flex: 1;
|
|
930
|
+
overflow-y: auto;
|
|
931
|
+
overflow-x: hidden;
|
|
932
|
+
padding: 1.5rem 2rem;
|
|
933
|
+
-webkit-overflow-scrolling: touch;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
.voice-messages {
|
|
937
|
+
display: flex;
|
|
938
|
+
flex-direction: column;
|
|
939
|
+
gap: 1rem;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.voice-block {
|
|
943
|
+
padding: 1rem 1.25rem;
|
|
944
|
+
border-radius: 0.75rem;
|
|
945
|
+
background: var(--color-bg-secondary);
|
|
946
|
+
line-height: 1.7;
|
|
947
|
+
font-size: 1rem;
|
|
948
|
+
max-width: 85%;
|
|
949
|
+
margin-right: auto;
|
|
950
|
+
position: relative;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.voice-block.speaking {
|
|
954
|
+
box-shadow: 0 0 0 2px var(--color-primary);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.voice-block-user {
|
|
958
|
+
background: var(--color-primary);
|
|
959
|
+
color: white;
|
|
960
|
+
margin-left: auto;
|
|
961
|
+
margin-right: 0;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.voice-input-section {
|
|
965
|
+
flex-shrink: 0;
|
|
966
|
+
background: var(--color-bg-primary);
|
|
967
|
+
padding: 0.75rem 1rem;
|
|
968
|
+
border-top: 1px solid var(--color-border);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.voice-input-wrapper {
|
|
972
|
+
display: flex;
|
|
973
|
+
gap: 0.5rem;
|
|
974
|
+
align-items: center;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.voice-transcript {
|
|
978
|
+
flex: 1;
|
|
979
|
+
min-height: 40px;
|
|
980
|
+
max-height: 100px;
|
|
981
|
+
padding: 0.5rem 0.875rem;
|
|
982
|
+
border-radius: 0.75rem;
|
|
983
|
+
background: var(--color-bg-secondary);
|
|
984
|
+
color: var(--color-text-primary);
|
|
985
|
+
font-size: 0.9375rem;
|
|
986
|
+
line-height: 1.5;
|
|
987
|
+
overflow-y: auto;
|
|
988
|
+
white-space: pre-wrap;
|
|
989
|
+
word-break: break-word;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.voice-transcript:empty::before {
|
|
993
|
+
content: attr(data-placeholder);
|
|
994
|
+
color: var(--color-text-secondary);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
.voice-mic-btn {
|
|
998
|
+
display: flex;
|
|
999
|
+
align-items: center;
|
|
1000
|
+
justify-content: center;
|
|
1001
|
+
width: 44px;
|
|
1002
|
+
height: 44px;
|
|
1003
|
+
background: var(--color-bg-secondary);
|
|
1004
|
+
color: var(--color-text-secondary);
|
|
1005
|
+
border: 2px solid var(--color-border);
|
|
1006
|
+
border-radius: 50%;
|
|
1007
|
+
cursor: pointer;
|
|
1008
|
+
flex-shrink: 0;
|
|
1009
|
+
transition: all 0.2s;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.voice-mic-btn:hover {
|
|
1013
|
+
border-color: var(--color-primary);
|
|
1014
|
+
color: var(--color-primary);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
.voice-mic-btn.loading {
|
|
1018
|
+
opacity: 0.5;
|
|
1019
|
+
cursor: wait;
|
|
1020
|
+
animation: mic-loading-spin 2s linear infinite;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
@keyframes mic-loading-spin {
|
|
1024
|
+
0% { border-color: var(--color-border); }
|
|
1025
|
+
50% { border-color: var(--color-primary); }
|
|
1026
|
+
100% { border-color: var(--color-border); }
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.voice-mic-btn.recording {
|
|
1030
|
+
background: var(--color-error);
|
|
1031
|
+
border-color: var(--color-error);
|
|
1032
|
+
color: white;
|
|
1033
|
+
animation: pulse 1s ease-in-out infinite;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
.voice-mic-btn svg {
|
|
1037
|
+
width: 20px;
|
|
1038
|
+
height: 20px;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
.voice-send-btn {
|
|
1042
|
+
flex-shrink: 0;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
.voice-tts-controls {
|
|
1046
|
+
display: flex;
|
|
1047
|
+
align-items: center;
|
|
1048
|
+
justify-content: space-between;
|
|
1049
|
+
margin-top: 0.5rem;
|
|
1050
|
+
font-size: 0.8rem;
|
|
1051
|
+
color: var(--color-text-secondary);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
.voice-toggle-label {
|
|
1055
|
+
display: flex;
|
|
1056
|
+
align-items: center;
|
|
1057
|
+
gap: 0.375rem;
|
|
1058
|
+
cursor: pointer;
|
|
1059
|
+
user-select: none;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.voice-toggle-label input[type="checkbox"] {
|
|
1063
|
+
accent-color: var(--color-primary);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
.voice-stop-btn {
|
|
1067
|
+
padding: 0.25rem 0.75rem;
|
|
1068
|
+
background: var(--color-bg-secondary);
|
|
1069
|
+
border: 1px solid var(--color-border);
|
|
1070
|
+
border-radius: 0.375rem;
|
|
1071
|
+
cursor: pointer;
|
|
1072
|
+
font-size: 0.75rem;
|
|
1073
|
+
color: var(--color-text-secondary);
|
|
1074
|
+
transition: all 0.15s;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.voice-stop-btn:hover {
|
|
1078
|
+
background: var(--color-error);
|
|
1079
|
+
color: white;
|
|
1080
|
+
border-color: var(--color-error);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.voice-empty {
|
|
1084
|
+
text-align: center;
|
|
1085
|
+
color: var(--color-text-secondary);
|
|
1086
|
+
padding: 4rem 2rem;
|
|
1087
|
+
font-size: 1rem;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.voice-empty-icon {
|
|
1091
|
+
font-size: 3rem;
|
|
1092
|
+
margin-bottom: 1rem;
|
|
1093
|
+
opacity: 0.3;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.voice-block .voice-result-stats {
|
|
1097
|
+
font-size: 0.8rem;
|
|
1098
|
+
color: var(--color-text-secondary);
|
|
1099
|
+
margin-top: 0.5rem;
|
|
1100
|
+
padding-top: 0.5rem;
|
|
1101
|
+
border-top: 1px solid var(--color-border);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
917
1104
|
/* ===== RESPONSIVE: TABLET ===== */
|
|
918
1105
|
@media (min-width: 769px) and (max-width: 1024px) {
|
|
919
1106
|
:root { --sidebar-width: 260px; }
|
|
@@ -1649,6 +1836,7 @@
|
|
|
1649
1836
|
<div class="view-toggle-bar" id="viewToggleBar" style="display:none;">
|
|
1650
1837
|
<button class="view-toggle-btn active" data-view="chat">Chat</button>
|
|
1651
1838
|
<button class="view-toggle-btn" data-view="files">Files</button>
|
|
1839
|
+
<button class="view-toggle-btn" data-view="voice">Voice</button>
|
|
1652
1840
|
</div>
|
|
1653
1841
|
|
|
1654
1842
|
<!-- Messages scroll area -->
|
|
@@ -1666,7 +1854,40 @@
|
|
|
1666
1854
|
|
|
1667
1855
|
<!-- File browser (hidden by default) -->
|
|
1668
1856
|
<div id="fileBrowserContainer" class="file-browser-container" style="display:none;">
|
|
1669
|
-
<iframe id="fileBrowserIframe" class="file-browser-iframe"
|
|
1857
|
+
<iframe id="fileBrowserIframe" class="file-browser-iframe"></iframe>
|
|
1858
|
+
</div>
|
|
1859
|
+
|
|
1860
|
+
<!-- Voice STT/TTS view -->
|
|
1861
|
+
<div id="voiceContainer" class="voice-container" style="display:none;">
|
|
1862
|
+
<div id="voiceScroll" class="voice-scroll">
|
|
1863
|
+
<div class="voice-messages" id="voiceMessages"></div>
|
|
1864
|
+
</div>
|
|
1865
|
+
<div class="voice-input-section">
|
|
1866
|
+
<div class="voice-input-wrapper">
|
|
1867
|
+
<select class="agent-selector voice-agent-selector" data-voice-agent-selector title="Select agent"></select>
|
|
1868
|
+
<div class="voice-transcript" id="voiceTranscript" data-placeholder="Tap mic and speak..."></div>
|
|
1869
|
+
<button class="voice-mic-btn" id="voiceMicBtn" title="Toggle recording" aria-label="Voice input">
|
|
1870
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1871
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
|
|
1872
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
|
|
1873
|
+
<line x1="12" y1="19" x2="12" y2="23"/>
|
|
1874
|
+
<line x1="8" y1="23" x2="16" y2="23"/>
|
|
1875
|
+
</svg>
|
|
1876
|
+
</button>
|
|
1877
|
+
<button class="send-btn voice-send-btn" id="voiceSendBtn" title="Send message" aria-label="Send message">
|
|
1878
|
+
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
1879
|
+
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
|
1880
|
+
</svg>
|
|
1881
|
+
</button>
|
|
1882
|
+
</div>
|
|
1883
|
+
<div class="voice-tts-controls">
|
|
1884
|
+
<label class="voice-toggle-label">
|
|
1885
|
+
<input type="checkbox" id="voiceTTSToggle" checked>
|
|
1886
|
+
<span>Auto-speak responses</span>
|
|
1887
|
+
</label>
|
|
1888
|
+
<button class="voice-stop-btn" id="voiceStopSpeaking" title="Stop speaking">Stop</button>
|
|
1889
|
+
</div>
|
|
1890
|
+
</div>
|
|
1670
1891
|
</div>
|
|
1671
1892
|
|
|
1672
1893
|
<!-- Input area: fixed at bottom -->
|
|
@@ -1730,6 +1951,7 @@
|
|
|
1730
1951
|
<script src="/gm/js/ui-components.js"></script>
|
|
1731
1952
|
<script src="/gm/js/conversations.js"></script>
|
|
1732
1953
|
<script src="/gm/js/client.js"></script>
|
|
1954
|
+
<script type="module" src="/gm/js/voice.js"></script>
|
|
1733
1955
|
<script src="/gm/js/features.js"></script>
|
|
1734
1956
|
|
|
1735
1957
|
<script>
|