agentgui 1.0.197 → 1.0.199
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 +140 -2
- package/static/index.html +33 -2
- package/static/js/agent-auth.js +144 -0
- package/static/js/client.js +62 -14
- package/static/js/streaming-renderer.js +5 -4
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -410,6 +410,46 @@ const server = http.createServer(async (req, res) => {
|
|
|
410
410
|
return;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
const queueMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/queue$/);
|
|
414
|
+
if (queueMatch && req.method === 'GET') {
|
|
415
|
+
const conversationId = queueMatch[1];
|
|
416
|
+
const conv = queries.getConversation(conversationId);
|
|
417
|
+
if (!conv) { sendJSON(req, res, 404, { error: 'Conversation not found' }); return; }
|
|
418
|
+
const queue = messageQueues.get(conversationId) || [];
|
|
419
|
+
sendJSON(req, res, 200, { queue });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const queueItemMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/queue\/([^/]+)$/);
|
|
424
|
+
if (queueItemMatch && req.method === 'DELETE') {
|
|
425
|
+
const conversationId = queueItemMatch[1];
|
|
426
|
+
const messageId = queueItemMatch[2];
|
|
427
|
+
const queue = messageQueues.get(conversationId);
|
|
428
|
+
if (!queue) { sendJSON(req, res, 404, { error: 'Queue not found' }); return; }
|
|
429
|
+
const index = queue.findIndex(q => q.messageId === messageId);
|
|
430
|
+
if (index === -1) { sendJSON(req, res, 404, { error: 'Queued message not found' }); return; }
|
|
431
|
+
queue.splice(index, 1);
|
|
432
|
+
if (queue.length === 0) messageQueues.delete(conversationId);
|
|
433
|
+
broadcastSync({ type: 'queue_status', conversationId, queueLength: queue?.length || 0, timestamp: Date.now() });
|
|
434
|
+
sendJSON(req, res, 200, { deleted: true });
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (queueItemMatch && req.method === 'PATCH') {
|
|
439
|
+
const conversationId = queueItemMatch[1];
|
|
440
|
+
const messageId = queueItemMatch[2];
|
|
441
|
+
const body = await parseBody(req);
|
|
442
|
+
const queue = messageQueues.get(conversationId);
|
|
443
|
+
if (!queue) { sendJSON(req, res, 404, { error: 'Queue not found' }); return; }
|
|
444
|
+
const item = queue.find(q => q.messageId === messageId);
|
|
445
|
+
if (!item) { sendJSON(req, res, 404, { error: 'Queued message not found' }); return; }
|
|
446
|
+
if (body.content !== undefined) item.content = body.content;
|
|
447
|
+
if (body.agentId !== undefined) item.agentId = body.agentId;
|
|
448
|
+
broadcastSync({ type: 'queue_updated', conversationId, messageId, content: item.content, agentId: item.agentId, timestamp: Date.now() });
|
|
449
|
+
sendJSON(req, res, 200, { updated: true, item });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
413
453
|
const messageMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/messages\/([^/]+)$/);
|
|
414
454
|
if (messageMatch && req.method === 'GET') {
|
|
415
455
|
const msg = queries.getMessage(messageMatch[2]);
|
|
@@ -573,7 +613,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
573
613
|
if (!pkg.scripts || !pkg.scripts[script]) { sendJSON(req, res, 400, { error: `Script "${script}" not found` }); return; }
|
|
574
614
|
} catch { sendJSON(req, res, 400, { error: 'No package.json' }); return; }
|
|
575
615
|
|
|
576
|
-
const
|
|
616
|
+
const childEnv = { ...process.env, FORCE_COLOR: '1' };
|
|
617
|
+
delete childEnv.PORT;
|
|
618
|
+
delete childEnv.BASE_URL;
|
|
619
|
+
delete childEnv.HOT_RELOAD;
|
|
620
|
+
const child = spawn('npm', ['run', script], { cwd: wd, stdio: ['ignore', 'pipe', 'pipe'], detached: true, env: childEnv });
|
|
577
621
|
activeScripts.set(conversationId, { process: child, script, startTime: Date.now() });
|
|
578
622
|
broadcastSync({ type: 'script_started', conversationId, script, timestamp: Date.now() });
|
|
579
623
|
|
|
@@ -616,6 +660,100 @@ const server = http.createServer(async (req, res) => {
|
|
|
616
660
|
return;
|
|
617
661
|
}
|
|
618
662
|
|
|
663
|
+
if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
|
|
664
|
+
const statuses = discoveredAgents.map(agent => {
|
|
665
|
+
const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
|
|
666
|
+
try {
|
|
667
|
+
if (agent.id === 'claude-code') {
|
|
668
|
+
const credFile = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
669
|
+
if (fs.existsSync(credFile)) {
|
|
670
|
+
const creds = JSON.parse(fs.readFileSync(credFile, 'utf-8'));
|
|
671
|
+
if (creds.claudeAiOauth && creds.claudeAiOauth.expiresAt > Date.now()) {
|
|
672
|
+
status.authenticated = true;
|
|
673
|
+
status.detail = creds.claudeAiOauth.subscriptionType || 'authenticated';
|
|
674
|
+
} else {
|
|
675
|
+
status.detail = 'expired';
|
|
676
|
+
}
|
|
677
|
+
} else {
|
|
678
|
+
status.detail = 'no credentials';
|
|
679
|
+
}
|
|
680
|
+
} else if (agent.id === 'gemini') {
|
|
681
|
+
const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
682
|
+
if (fs.existsSync(acctFile)) {
|
|
683
|
+
const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
|
|
684
|
+
if (accts.active) {
|
|
685
|
+
status.authenticated = true;
|
|
686
|
+
status.detail = accts.active;
|
|
687
|
+
} else {
|
|
688
|
+
status.detail = 'logged out';
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
status.detail = 'no credentials';
|
|
692
|
+
}
|
|
693
|
+
} else if (agent.id === 'opencode') {
|
|
694
|
+
const out = execSync('opencode auth list 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
|
695
|
+
const countMatch = out.match(/(\d+)\s+credentials?/);
|
|
696
|
+
if (countMatch && parseInt(countMatch[1], 10) > 0) {
|
|
697
|
+
status.authenticated = true;
|
|
698
|
+
status.detail = countMatch[1] + ' credential(s)';
|
|
699
|
+
} else {
|
|
700
|
+
status.detail = 'no credentials';
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
status.detail = 'unknown';
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
status.detail = 'check failed';
|
|
707
|
+
}
|
|
708
|
+
return status;
|
|
709
|
+
});
|
|
710
|
+
sendJSON(req, res, 200, { agents: statuses });
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const agentAuthMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/auth$/);
|
|
715
|
+
if (agentAuthMatch && req.method === 'POST') {
|
|
716
|
+
const agentId = agentAuthMatch[1];
|
|
717
|
+
const agent = discoveredAgents.find(a => a.id === agentId);
|
|
718
|
+
if (!agent) { sendJSON(req, res, 404, { error: 'Agent not found' }); return; }
|
|
719
|
+
|
|
720
|
+
const authCommands = {
|
|
721
|
+
'claude-code': { cmd: 'claude', args: ['setup-token'] },
|
|
722
|
+
'opencode': { cmd: 'opencode', args: ['auth', 'login'] },
|
|
723
|
+
'gemini': { cmd: 'gemini', args: [] }
|
|
724
|
+
};
|
|
725
|
+
const authCmd = authCommands[agentId];
|
|
726
|
+
if (!authCmd) { sendJSON(req, res, 400, { error: 'No auth command for this agent' }); return; }
|
|
727
|
+
|
|
728
|
+
const conversationId = '__agent_auth__';
|
|
729
|
+
if (activeScripts.has(conversationId)) {
|
|
730
|
+
sendJSON(req, res, 409, { error: 'Auth process already running' });
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const child = spawn(authCmd.cmd, authCmd.args, {
|
|
735
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
736
|
+
env: { ...process.env, FORCE_COLOR: '1' }
|
|
737
|
+
});
|
|
738
|
+
activeScripts.set(conversationId, { process: child, script: 'auth-' + agentId, startTime: Date.now() });
|
|
739
|
+
broadcastSync({ type: 'script_started', conversationId, script: 'auth-' + agentId, agentId, timestamp: Date.now() });
|
|
740
|
+
|
|
741
|
+
const onData = (stream) => (chunk) => {
|
|
742
|
+
broadcastSync({ type: 'script_output', conversationId, data: chunk.toString(), stream, timestamp: Date.now() });
|
|
743
|
+
};
|
|
744
|
+
child.stdout.on('data', onData('stdout'));
|
|
745
|
+
child.stderr.on('data', onData('stderr'));
|
|
746
|
+
child.on('error', (err) => {
|
|
747
|
+
activeScripts.delete(conversationId);
|
|
748
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 1, error: err.message, timestamp: Date.now() });
|
|
749
|
+
});
|
|
750
|
+
child.on('close', (code) => {
|
|
751
|
+
activeScripts.delete(conversationId);
|
|
752
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: code || 0, timestamp: Date.now() });
|
|
753
|
+
});
|
|
754
|
+
sendJSON(req, res, 200, { ok: true, agentId, pid: child.pid });
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
619
757
|
|
|
620
758
|
if (pathOnly === '/api/import/claude-code' && req.method === 'GET') {
|
|
621
759
|
const result = queries.importClaudeCodeConversations();
|
|
@@ -1378,7 +1516,7 @@ wss.on('connection', (ws, req) => {
|
|
|
1378
1516
|
|
|
1379
1517
|
const BROADCAST_TYPES = new Set([
|
|
1380
1518
|
'message_created', 'conversation_created', 'conversation_updated',
|
|
1381
|
-
'conversations_updated', 'conversation_deleted', 'queue_status',
|
|
1519
|
+
'conversations_updated', 'conversation_deleted', 'queue_status', 'queue_updated',
|
|
1382
1520
|
'streaming_start', 'streaming_complete', 'streaming_error',
|
|
1383
1521
|
'rate_limit_hit', 'rate_limit_clear',
|
|
1384
1522
|
'script_started', 'script_stopped', 'script_output'
|
package/static/index.html
CHANGED
|
@@ -447,12 +447,12 @@
|
|
|
447
447
|
.script-buttons { display: flex; gap: 0.25rem; align-items: center; }
|
|
448
448
|
.header-icon-btn {
|
|
449
449
|
display: flex; align-items: center; justify-content: center;
|
|
450
|
-
width:
|
|
450
|
+
width: 36px; height: 36px; background: none; border: none;
|
|
451
451
|
border-radius: 0.375rem; cursor: pointer; color: var(--color-text-secondary);
|
|
452
452
|
transition: background-color 0.15s, color 0.15s;
|
|
453
453
|
}
|
|
454
454
|
.header-icon-btn:hover { background-color: var(--color-bg-primary); color: var(--color-text-primary); }
|
|
455
|
-
.header-icon-btn svg { width:
|
|
455
|
+
.header-icon-btn svg { width: 18px; height: 18px; }
|
|
456
456
|
#scriptStartBtn { color: var(--color-success); }
|
|
457
457
|
#scriptStartBtn:hover { background-color: rgba(16,185,129,0.1); color: var(--color-success); }
|
|
458
458
|
.script-dev-btn { color: var(--color-info); }
|
|
@@ -460,6 +460,32 @@
|
|
|
460
460
|
.script-stop-btn { color: var(--color-error); }
|
|
461
461
|
.script-stop-btn:hover { background-color: rgba(239,68,68,0.1); color: var(--color-error); }
|
|
462
462
|
|
|
463
|
+
.agent-auth-btn { color: var(--color-text-secondary); position: relative; }
|
|
464
|
+
.agent-auth-btn.auth-ok { color: var(--color-success); }
|
|
465
|
+
.agent-auth-btn.auth-warn { color: var(--color-warning); }
|
|
466
|
+
.agent-auth-btn:hover { background-color: var(--color-bg-primary); }
|
|
467
|
+
.agent-auth-dropdown {
|
|
468
|
+
position: absolute; top: 100%; right: 0; z-index: 100;
|
|
469
|
+
min-width: 200px; padding: 0.25rem 0;
|
|
470
|
+
background: var(--color-bg-secondary); border: 1px solid var(--color-border);
|
|
471
|
+
border-radius: 0.5rem; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
472
|
+
display: none;
|
|
473
|
+
}
|
|
474
|
+
.agent-auth-dropdown.open { display: block; }
|
|
475
|
+
.agent-auth-item {
|
|
476
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
477
|
+
padding: 0.5rem 0.75rem; cursor: pointer; font-size: 0.8125rem;
|
|
478
|
+
color: var(--color-text-primary); border: none; background: none; width: 100%;
|
|
479
|
+
text-align: left;
|
|
480
|
+
}
|
|
481
|
+
.agent-auth-item:hover { background: var(--color-bg-primary); }
|
|
482
|
+
.agent-auth-dot {
|
|
483
|
+
width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
|
|
484
|
+
}
|
|
485
|
+
.agent-auth-dot.ok { background: var(--color-success); }
|
|
486
|
+
.agent-auth-dot.missing { background: var(--color-warning); }
|
|
487
|
+
.agent-auth-dot.unknown { background: var(--color-text-secondary); }
|
|
488
|
+
|
|
463
489
|
.terminal-container {
|
|
464
490
|
flex: 1; display: flex; flex-direction: column; overflow: hidden; background: #1e1e1e;
|
|
465
491
|
}
|
|
@@ -2234,6 +2260,10 @@
|
|
|
2234
2260
|
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="5" y="5" width="14" height="14" rx="1"></rect></svg>
|
|
2235
2261
|
</button>
|
|
2236
2262
|
</div>
|
|
2263
|
+
<button class="header-icon-btn agent-auth-btn" id="agentAuthBtn" title="Agent authentication" aria-label="Agent authentication" style="display:none;">
|
|
2264
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
|
|
2265
|
+
<div class="agent-auth-dropdown" id="agentAuthDropdown"></div>
|
|
2266
|
+
</button>
|
|
2237
2267
|
<div class="status-badge">
|
|
2238
2268
|
<div class="status-indicator" data-status="disconnected"></div>
|
|
2239
2269
|
<span id="connectionStatus" data-status-indicator>Disconnected</span>
|
|
@@ -2378,6 +2408,7 @@
|
|
|
2378
2408
|
<script type="module" src="/gm/js/voice.js"></script>
|
|
2379
2409
|
<script defer src="/gm/js/features.js"></script>
|
|
2380
2410
|
<script defer src="/gm/js/script-runner.js"></script>
|
|
2411
|
+
<script defer src="/gm/js/agent-auth.js"></script>
|
|
2381
2412
|
|
|
2382
2413
|
<script>
|
|
2383
2414
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var BASE = window.__BASE_URL || '';
|
|
3
|
+
var btn = document.getElementById('agentAuthBtn');
|
|
4
|
+
var dropdown = document.getElementById('agentAuthDropdown');
|
|
5
|
+
var agents = [];
|
|
6
|
+
var authRunning = false;
|
|
7
|
+
var AUTH_CONV_ID = '__agent_auth__';
|
|
8
|
+
|
|
9
|
+
function init() {
|
|
10
|
+
if (!btn || !dropdown) return;
|
|
11
|
+
btn.addEventListener('click', toggleDropdown);
|
|
12
|
+
document.addEventListener('click', function(e) {
|
|
13
|
+
if (!btn.contains(e.target)) closeDropdown();
|
|
14
|
+
});
|
|
15
|
+
window.addEventListener('conversation-selected', function() { fetchAuthStatus(); });
|
|
16
|
+
window.addEventListener('ws-message', onWsMessage);
|
|
17
|
+
fetchAuthStatus();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fetchAuthStatus() {
|
|
21
|
+
fetch(BASE + '/api/agents/auth-status')
|
|
22
|
+
.then(function(r) { return r.json(); })
|
|
23
|
+
.then(function(data) {
|
|
24
|
+
agents = data.agents || [];
|
|
25
|
+
updateButton();
|
|
26
|
+
renderDropdown();
|
|
27
|
+
})
|
|
28
|
+
.catch(function() {});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function updateButton() {
|
|
32
|
+
if (agents.length === 0) { btn.style.display = 'none'; return; }
|
|
33
|
+
btn.style.display = 'flex';
|
|
34
|
+
var allOk = agents.every(function(a) { return a.authenticated; });
|
|
35
|
+
var anyMissing = agents.some(function(a) { return !a.authenticated; });
|
|
36
|
+
btn.classList.toggle('auth-ok', allOk);
|
|
37
|
+
btn.classList.toggle('auth-warn', anyMissing);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function renderDropdown() {
|
|
41
|
+
dropdown.innerHTML = '';
|
|
42
|
+
agents.forEach(function(agent) {
|
|
43
|
+
var item = document.createElement('button');
|
|
44
|
+
item.className = 'agent-auth-item';
|
|
45
|
+
var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
|
|
46
|
+
item.innerHTML = '<span class="agent-auth-dot ' + dotClass + '"></span>' +
|
|
47
|
+
'<span>' + escapeHtml(agent.name) + '</span>' +
|
|
48
|
+
'<span style="margin-left:auto;font-size:0.7rem;color:var(--color-text-secondary)">' + escapeHtml(agent.detail) + '</span>';
|
|
49
|
+
item.addEventListener('click', function(e) {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
closeDropdown();
|
|
52
|
+
triggerAuth(agent.id);
|
|
53
|
+
});
|
|
54
|
+
dropdown.appendChild(item);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toggleDropdown(e) {
|
|
59
|
+
e.stopPropagation();
|
|
60
|
+
dropdown.classList.toggle('open');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function closeDropdown() {
|
|
64
|
+
dropdown.classList.remove('open');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function triggerAuth(agentId) {
|
|
68
|
+
if (authRunning) return;
|
|
69
|
+
fetch(BASE + '/api/agents/' + agentId + '/auth', {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: { 'Content-Type': 'application/json' },
|
|
72
|
+
body: '{}'
|
|
73
|
+
})
|
|
74
|
+
.then(function(r) { return r.json(); })
|
|
75
|
+
.then(function(data) {
|
|
76
|
+
if (data.ok) {
|
|
77
|
+
authRunning = true;
|
|
78
|
+
showTerminalTab();
|
|
79
|
+
switchToTerminalView();
|
|
80
|
+
var term = getTerminal();
|
|
81
|
+
if (term) {
|
|
82
|
+
term.clear();
|
|
83
|
+
term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
.catch(function() {});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function onWsMessage(e) {
|
|
91
|
+
var data = e.detail;
|
|
92
|
+
if (!data || data.conversationId !== AUTH_CONV_ID) return;
|
|
93
|
+
if (data.type === 'script_started') {
|
|
94
|
+
authRunning = true;
|
|
95
|
+
showTerminalTab();
|
|
96
|
+
switchToTerminalView();
|
|
97
|
+
var term = getTerminal();
|
|
98
|
+
if (term) {
|
|
99
|
+
term.clear();
|
|
100
|
+
term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n');
|
|
101
|
+
}
|
|
102
|
+
} else if (data.type === 'script_output') {
|
|
103
|
+
showTerminalTab();
|
|
104
|
+
var term = getTerminal();
|
|
105
|
+
if (term) term.write(data.data);
|
|
106
|
+
} else if (data.type === 'script_stopped') {
|
|
107
|
+
authRunning = false;
|
|
108
|
+
var term = getTerminal();
|
|
109
|
+
var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
|
|
110
|
+
if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
|
|
111
|
+
setTimeout(fetchAuthStatus, 1000);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function showTerminalTab() {
|
|
116
|
+
var tabBtn = document.getElementById('terminalTabBtn');
|
|
117
|
+
if (tabBtn) tabBtn.style.display = '';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function switchToTerminalView() {
|
|
121
|
+
var bar = document.getElementById('viewToggleBar');
|
|
122
|
+
if (!bar) return;
|
|
123
|
+
var termBtn = bar.querySelector('[data-view="terminal"]');
|
|
124
|
+
if (termBtn) termBtn.click();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getTerminal() {
|
|
128
|
+
return window.scriptRunner ? window.scriptRunner.getTerminal() : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function escapeHtml(s) {
|
|
132
|
+
var d = document.createElement('div');
|
|
133
|
+
d.textContent = s;
|
|
134
|
+
return d.innerHTML;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (document.readyState === 'loading') {
|
|
138
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
139
|
+
} else {
|
|
140
|
+
init();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
window.agentAuth = { refresh: fetchAuthStatus };
|
|
144
|
+
})();
|
package/static/js/client.js
CHANGED
|
@@ -372,6 +372,9 @@ class AgentGUIClient {
|
|
|
372
372
|
case 'queue_status':
|
|
373
373
|
this.handleQueueStatus(data);
|
|
374
374
|
break;
|
|
375
|
+
case 'queue_updated':
|
|
376
|
+
this.handleQueueUpdated(data);
|
|
377
|
+
break;
|
|
375
378
|
case 'rate_limit_hit':
|
|
376
379
|
this.handleRateLimitHit(data);
|
|
377
380
|
break;
|
|
@@ -478,7 +481,7 @@ class AgentGUIClient {
|
|
|
478
481
|
const bFrag = document.createDocumentFragment();
|
|
479
482
|
sList.forEach(chunk => {
|
|
480
483
|
if (!chunk.block?.type) return;
|
|
481
|
-
const el = this.renderer.renderBlock(chunk.block, chunk);
|
|
484
|
+
const el = this.renderer.renderBlock(chunk.block, chunk, bFrag);
|
|
482
485
|
if (!el) return;
|
|
483
486
|
if (chunk.block.type === 'tool_result') {
|
|
484
487
|
const lastInFrag = bFrag.lastElementChild;
|
|
@@ -699,21 +702,68 @@ class AgentGUIClient {
|
|
|
699
702
|
|
|
700
703
|
handleQueueStatus(data) {
|
|
701
704
|
if (data.conversationId !== this.state.currentConversation?.id) return;
|
|
705
|
+
this.fetchAndRenderQueue(data.conversationId);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
handleQueueUpdated(data) {
|
|
709
|
+
if (data.conversationId !== this.state.currentConversation?.id) return;
|
|
710
|
+
this.fetchAndRenderQueue(data.conversationId);
|
|
711
|
+
}
|
|
702
712
|
|
|
713
|
+
async fetchAndRenderQueue(conversationId) {
|
|
703
714
|
const outputEl = document.querySelector('.conversation-messages');
|
|
704
715
|
if (!outputEl) return;
|
|
705
716
|
|
|
706
|
-
|
|
707
|
-
|
|
717
|
+
try {
|
|
718
|
+
const response = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/queue`);
|
|
719
|
+
const { queue } = await response.json();
|
|
720
|
+
|
|
721
|
+
let queueEl = outputEl.querySelector('.queue-indicator');
|
|
722
|
+
if (!queue || queue.length === 0) {
|
|
723
|
+
if (queueEl) queueEl.remove();
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
|
|
708
727
|
if (!queueEl) {
|
|
709
728
|
queueEl = document.createElement('div');
|
|
710
729
|
queueEl.className = 'queue-indicator';
|
|
711
|
-
queueEl.style.cssText = 'padding:0.5rem 1rem;margin:0.5rem 0;border-radius:0.375rem;background:var(--color-warning);color:#000;font-size:0.875rem;text-align:center;';
|
|
712
730
|
outputEl.appendChild(queueEl);
|
|
713
731
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
732
|
+
|
|
733
|
+
queueEl.innerHTML = queue.map((q, i) => `
|
|
734
|
+
<div class="queue-item" data-message-id="${q.messageId}" style="padding:0.5rem 1rem;margin:0.5rem 0;border-radius:0.375rem;background:var(--color-warning);color:#000;font-size:0.875rem;display:flex;align-items:center;gap:0.5rem;">
|
|
735
|
+
<span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${i + 1}. ${this.escapeHtml(q.content)}</span>
|
|
736
|
+
<button class="queue-edit-btn" data-index="${i}" style="padding:0.25rem 0.5rem;background:transparent;border:1px solid #000;border-radius:0.25rem;cursor:pointer;font-size:0.75rem;">Edit</button>
|
|
737
|
+
<button class="queue-delete-btn" data-index="${i}" style="padding:0.25rem 0.5rem;background:transparent;border:1px solid #000;border-radius:0.25rem;cursor:pointer;font-size:0.75rem;">Delete</button>
|
|
738
|
+
</div>
|
|
739
|
+
`).join('');
|
|
740
|
+
|
|
741
|
+
queueEl.querySelectorAll('.queue-delete-btn').forEach(btn => {
|
|
742
|
+
btn.addEventListener('click', async (e) => {
|
|
743
|
+
const index = parseInt(e.target.dataset.index);
|
|
744
|
+
const msgId = queue[index].messageId;
|
|
745
|
+
if (confirm('Delete this queued message?')) {
|
|
746
|
+
await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/queue/${msgId}`, { method: 'DELETE' });
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
queueEl.querySelectorAll('.queue-edit-btn').forEach(btn => {
|
|
752
|
+
btn.addEventListener('click', (e) => {
|
|
753
|
+
const index = parseInt(e.target.dataset.index);
|
|
754
|
+
const q = queue[index];
|
|
755
|
+
const newContent = prompt('Edit message:', q.content);
|
|
756
|
+
if (newContent !== null && newContent !== q.content) {
|
|
757
|
+
fetch(window.__BASE_URL + `/api/conversations/${conversationId}/queue/${q.messageId}`, {
|
|
758
|
+
method: 'PATCH',
|
|
759
|
+
headers: { 'Content-Type': 'application/json' },
|
|
760
|
+
body: JSON.stringify({ content: newContent })
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
} catch (err) {
|
|
766
|
+
console.error('Failed to fetch queue:', err);
|
|
717
767
|
}
|
|
718
768
|
}
|
|
719
769
|
|
|
@@ -1160,8 +1210,8 @@ class AgentGUIClient {
|
|
|
1160
1210
|
if (!streamingEl) return;
|
|
1161
1211
|
const blocksEl = streamingEl.querySelector('.streaming-blocks');
|
|
1162
1212
|
if (!blocksEl) return;
|
|
1163
|
-
const element = this.renderer.renderBlock(chunk.block, chunk);
|
|
1164
|
-
if (!element) return;
|
|
1213
|
+
const element = this.renderer.renderBlock(chunk.block, chunk, blocksEl);
|
|
1214
|
+
if (!element) { this.scrollToBottom(); return; }
|
|
1165
1215
|
if (chunk.block.type === 'tool_result') {
|
|
1166
1216
|
const matchById = chunk.block.tool_use_id && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${chunk.block.tool_use_id}"]`);
|
|
1167
1217
|
const lastEl = blocksEl.lastElementChild;
|
|
@@ -1187,8 +1237,8 @@ class AgentGUIClient {
|
|
|
1187
1237
|
const blocksEl = streamingEl.querySelector('.streaming-blocks');
|
|
1188
1238
|
if (!blocksEl) continue;
|
|
1189
1239
|
for (const chunk of groups[sid]) {
|
|
1190
|
-
const el = this.renderer.renderBlock(chunk.block, chunk);
|
|
1191
|
-
if (!el) continue;
|
|
1240
|
+
const el = this.renderer.renderBlock(chunk.block, chunk, blocksEl);
|
|
1241
|
+
if (!el) { appended = true; continue; }
|
|
1192
1242
|
if (chunk.block.type === 'tool_result') {
|
|
1193
1243
|
const matchById = chunk.block.tool_use_id && blocksEl.querySelector(`.block-tool-use[data-tool-use-id="${chunk.block.tool_use_id}"]`);
|
|
1194
1244
|
const lastEl = blocksEl.lastElementChild;
|
|
@@ -1271,7 +1321,6 @@ class AgentGUIClient {
|
|
|
1271
1321
|
*/
|
|
1272
1322
|
disableControls() {
|
|
1273
1323
|
if (this.ui.sendButton) this.ui.sendButton.disabled = true;
|
|
1274
|
-
if (this.ui.agentSelector) this.ui.agentSelector.disabled = true;
|
|
1275
1324
|
}
|
|
1276
1325
|
|
|
1277
1326
|
/**
|
|
@@ -1279,7 +1328,6 @@ class AgentGUIClient {
|
|
|
1279
1328
|
*/
|
|
1280
1329
|
enableControls() {
|
|
1281
1330
|
if (this.ui.sendButton) this.ui.sendButton.disabled = false;
|
|
1282
|
-
if (this.ui.agentSelector) this.ui.agentSelector.disabled = false;
|
|
1283
1331
|
}
|
|
1284
1332
|
|
|
1285
1333
|
/**
|
|
@@ -1501,7 +1549,7 @@ class AgentGUIClient {
|
|
|
1501
1549
|
const blockFrag = document.createDocumentFragment();
|
|
1502
1550
|
sessionChunkList.forEach(chunk => {
|
|
1503
1551
|
if (!chunk.block?.type) return;
|
|
1504
|
-
const element = this.renderer.renderBlock(chunk.block, chunk);
|
|
1552
|
+
const element = this.renderer.renderBlock(chunk.block, chunk, blockFrag);
|
|
1505
1553
|
if (!element) return;
|
|
1506
1554
|
if (chunk.block.type === 'tool_result') {
|
|
1507
1555
|
const lastInFrag = blockFrag.lastElementChild;
|
|
@@ -328,13 +328,13 @@ class StreamingRenderer {
|
|
|
328
328
|
/**
|
|
329
329
|
* Render Claude message blocks with beautiful styling
|
|
330
330
|
*/
|
|
331
|
-
renderBlock(block, context = {}) {
|
|
331
|
+
renderBlock(block, context = {}, targetContainer = null) {
|
|
332
332
|
if (!block || !block.type) return null;
|
|
333
333
|
|
|
334
334
|
try {
|
|
335
335
|
switch (block.type) {
|
|
336
336
|
case 'text':
|
|
337
|
-
return this.renderBlockText(block, context);
|
|
337
|
+
return this.renderBlockText(block, context, targetContainer);
|
|
338
338
|
case 'code':
|
|
339
339
|
return this.renderBlockCode(block, context);
|
|
340
340
|
case 'thinking':
|
|
@@ -363,7 +363,7 @@ class StreamingRenderer {
|
|
|
363
363
|
/**
|
|
364
364
|
* Render text block with semantic HTML
|
|
365
365
|
*/
|
|
366
|
-
renderBlockText(block, context) {
|
|
366
|
+
renderBlockText(block, context, targetContainer = null) {
|
|
367
367
|
const text = block.text || '';
|
|
368
368
|
const isHtml = this.containsHtmlTags(text);
|
|
369
369
|
const cached = this.renderCache.get(text);
|
|
@@ -373,7 +373,8 @@ class StreamingRenderer {
|
|
|
373
373
|
this.renderCache.set(text, html);
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
-
const
|
|
376
|
+
const container = targetContainer || this.outputContainer;
|
|
377
|
+
const lastChild = container && container.lastElementChild;
|
|
377
378
|
if (lastChild && lastChild.classList.contains('block-text') && !isHtml && !lastChild.classList.contains('html-content')) {
|
|
378
379
|
lastChild.innerHTML += html;
|
|
379
380
|
return null;
|