agentgui 1.0.139 → 1.0.141
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/static/index.html +8 -1
- package/static/js/client.js +104 -7
- package/static/js/streaming-renderer.js +21 -58
- package/static/js/voice.js +22 -4
package/package.json
CHANGED
package/static/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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="/
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' rx='20' fill='%233b82f6'/%3E%3Ctext x='50' y='68' font-size='50' font-family='sans-serif' font-weight='bold' fill='white' text-anchor='middle'%3EG%3C/text%3E%3C/svg%3E">
|
|
9
9
|
|
|
10
10
|
<link href="https://cdn.jsdelivr.net/npm/rippleui@1.12.1/dist/css/styles.css" rel="stylesheet">
|
|
11
11
|
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-dark.css" rel="stylesheet">
|
|
@@ -1511,6 +1511,13 @@
|
|
|
1511
1511
|
html.dark .block-tool-result.result-success .result-body { color: #bbf7d0; }
|
|
1512
1512
|
html.dark .block-tool-result.result-error .result-body { color: #fecaca; }
|
|
1513
1513
|
|
|
1514
|
+
.result-collapsible { margin: 0; }
|
|
1515
|
+
.result-collapsible > summary { list-style: none; }
|
|
1516
|
+
.result-collapsible > summary::-webkit-details-marker { display: none; }
|
|
1517
|
+
.result-collapsible > summary::marker { display: none; content: ''; }
|
|
1518
|
+
.result-collapsible > summary .status-label::before { content: '\25b6'; font-size: 0.6rem; margin-right: 0.375rem; display: inline-block; transition: transform 0.15s; }
|
|
1519
|
+
.result-collapsible[open] > summary .status-label::before { transform: rotate(90deg); }
|
|
1520
|
+
|
|
1514
1521
|
.block-tool-result .result-body.collapsed { max-height: 150px; position: relative; }
|
|
1515
1522
|
.block-tool-result .result-body.collapsed::after {
|
|
1516
1523
|
content: '';
|
package/static/js/client.js
CHANGED
|
@@ -348,7 +348,7 @@ class AgentGUIClient {
|
|
|
348
348
|
|
|
349
349
|
switch (data.type) {
|
|
350
350
|
case 'streaming_start':
|
|
351
|
-
this.handleStreamingStart(data);
|
|
351
|
+
this.handleStreamingStart(data).catch(e => console.error('handleStreamingStart error:', e));
|
|
352
352
|
break;
|
|
353
353
|
case 'streaming_progress':
|
|
354
354
|
this.handleStreamingProgress(data);
|
|
@@ -388,7 +388,7 @@ class AgentGUIClient {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
-
handleStreamingStart(data) {
|
|
391
|
+
async handleStreamingStart(data) {
|
|
392
392
|
console.log('Streaming started:', data);
|
|
393
393
|
|
|
394
394
|
// If this streaming event is for a different conversation than what we are viewing,
|
|
@@ -420,8 +420,60 @@ class AgentGUIClient {
|
|
|
420
420
|
if (outputEl) {
|
|
421
421
|
let messagesEl = outputEl.querySelector('.conversation-messages');
|
|
422
422
|
if (!messagesEl) {
|
|
423
|
-
|
|
423
|
+
// Load existing conversation history before starting the stream
|
|
424
|
+
const conv = this.state.currentConversation;
|
|
425
|
+
const wdInfo = conv?.workingDirectory ? ` - ${this.escapeHtml(conv.workingDirectory)}` : '';
|
|
426
|
+
outputEl.innerHTML = `
|
|
427
|
+
<div class="conversation-header">
|
|
428
|
+
<h2>${this.escapeHtml(conv?.title || 'Conversation')}</h2>
|
|
429
|
+
<p class="text-secondary">${conv?.agentType || 'unknown'} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
|
|
430
|
+
</div>
|
|
431
|
+
<div class="conversation-messages"></div>
|
|
432
|
+
`;
|
|
424
433
|
messagesEl = outputEl.querySelector('.conversation-messages');
|
|
434
|
+
// Load prior messages into the container
|
|
435
|
+
try {
|
|
436
|
+
const msgResp = await fetch(window.__BASE_URL + `/api/conversations/${data.conversationId}/messages`);
|
|
437
|
+
if (msgResp.ok) {
|
|
438
|
+
const msgData = await msgResp.json();
|
|
439
|
+
const priorChunks = await this.fetchChunks(data.conversationId, 0);
|
|
440
|
+
if (priorChunks.length > 0) {
|
|
441
|
+
const userMsgs = (msgData.messages || []).filter(m => m.role === 'user');
|
|
442
|
+
const sessionOrder = [];
|
|
443
|
+
const sessionGroups = {};
|
|
444
|
+
priorChunks.forEach(c => {
|
|
445
|
+
if (!sessionGroups[c.sessionId]) { sessionGroups[c.sessionId] = []; sessionOrder.push(c.sessionId); }
|
|
446
|
+
sessionGroups[c.sessionId].push(c);
|
|
447
|
+
});
|
|
448
|
+
let ui = 0;
|
|
449
|
+
sessionOrder.forEach(sid => {
|
|
450
|
+
const sList = sessionGroups[sid];
|
|
451
|
+
const sStart = sList[0].created_at;
|
|
452
|
+
while (ui < userMsgs.length && userMsgs[ui].created_at <= sStart) {
|
|
453
|
+
const m = userMsgs[ui++];
|
|
454
|
+
messagesEl.insertAdjacentHTML('beforeend', `<div class="message message-user" data-msg-id="${m.id}"><div class="message-role">User</div>${this.renderMessageContent(m.content)}<div class="message-timestamp">${new Date(m.created_at).toLocaleString()}</div></div>`);
|
|
455
|
+
}
|
|
456
|
+
const mDiv = document.createElement('div');
|
|
457
|
+
mDiv.className = 'message message-assistant';
|
|
458
|
+
mDiv.id = `message-${sid}`;
|
|
459
|
+
mDiv.innerHTML = '<div class="message-role">Assistant</div><div class="message-blocks streaming-blocks"></div>';
|
|
460
|
+
const bEl = mDiv.querySelector('.message-blocks');
|
|
461
|
+
sList.forEach(chunk => { if (chunk.block?.type) { const el = this.renderer.renderBlock(chunk.block, chunk); if (el) bEl.appendChild(el); } });
|
|
462
|
+
const ts = document.createElement('div'); ts.className = 'message-timestamp'; ts.textContent = new Date(sList[sList.length - 1].created_at).toLocaleString();
|
|
463
|
+
mDiv.appendChild(ts);
|
|
464
|
+
messagesEl.appendChild(mDiv);
|
|
465
|
+
});
|
|
466
|
+
while (ui < userMsgs.length) {
|
|
467
|
+
const m = userMsgs[ui++];
|
|
468
|
+
messagesEl.insertAdjacentHTML('beforeend', `<div class="message message-user" data-msg-id="${m.id}"><div class="message-role">User</div>${this.renderMessageContent(m.content)}<div class="message-timestamp">${new Date(m.created_at).toLocaleString()}</div></div>`);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
messagesEl.innerHTML = this.renderMessages(msgData.messages || []);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
} catch (e) {
|
|
475
|
+
console.warn('Failed to load prior messages for streaming view:', e);
|
|
476
|
+
}
|
|
425
477
|
}
|
|
426
478
|
const streamingDiv = document.createElement('div');
|
|
427
479
|
streamingDiv.className = 'message message-assistant streaming-message';
|
|
@@ -1181,17 +1233,48 @@ class AgentGUIClient {
|
|
|
1181
1233
|
// Render all chunks
|
|
1182
1234
|
const messagesEl = outputEl.querySelector('.conversation-messages');
|
|
1183
1235
|
if (chunks.length > 0) {
|
|
1184
|
-
//
|
|
1236
|
+
// Fetch user messages to interleave with session chunks
|
|
1237
|
+
let userMessages = [];
|
|
1238
|
+
try {
|
|
1239
|
+
const msgResp = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
|
|
1240
|
+
if (msgResp.ok) {
|
|
1241
|
+
const msgData = await msgResp.json();
|
|
1242
|
+
userMessages = (msgData.messages || []).filter(m => m.role === 'user');
|
|
1243
|
+
}
|
|
1244
|
+
} catch (_) {}
|
|
1245
|
+
|
|
1246
|
+
// Group chunks by session, preserving order
|
|
1247
|
+
const sessionOrder = [];
|
|
1185
1248
|
const sessionChunks = {};
|
|
1186
1249
|
chunks.forEach(chunk => {
|
|
1187
1250
|
if (!sessionChunks[chunk.sessionId]) {
|
|
1188
1251
|
sessionChunks[chunk.sessionId] = [];
|
|
1252
|
+
sessionOrder.push(chunk.sessionId);
|
|
1189
1253
|
}
|
|
1190
1254
|
sessionChunks[chunk.sessionId].push(chunk);
|
|
1191
1255
|
});
|
|
1192
1256
|
|
|
1193
|
-
//
|
|
1194
|
-
|
|
1257
|
+
// Build a timeline: match user messages to sessions by timestamp
|
|
1258
|
+
let userMsgIdx = 0;
|
|
1259
|
+
sessionOrder.forEach((sessionId) => {
|
|
1260
|
+
const sessionChunkList = sessionChunks[sessionId];
|
|
1261
|
+
const sessionStart = sessionChunkList[0].created_at;
|
|
1262
|
+
|
|
1263
|
+
// Render user messages that came before this session
|
|
1264
|
+
while (userMsgIdx < userMessages.length && userMessages[userMsgIdx].created_at <= sessionStart) {
|
|
1265
|
+
const msg = userMessages[userMsgIdx];
|
|
1266
|
+
const userDiv = document.createElement('div');
|
|
1267
|
+
userDiv.className = 'message message-user';
|
|
1268
|
+
userDiv.setAttribute('data-msg-id', msg.id);
|
|
1269
|
+
userDiv.innerHTML = `
|
|
1270
|
+
<div class="message-role">User</div>
|
|
1271
|
+
${this.renderMessageContent(msg.content)}
|
|
1272
|
+
<div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
|
|
1273
|
+
`;
|
|
1274
|
+
messagesEl.appendChild(userDiv);
|
|
1275
|
+
userMsgIdx++;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1195
1278
|
const isCurrentActiveSession = shouldResumeStreaming && latestSession && latestSession.id === sessionId;
|
|
1196
1279
|
const messageDiv = document.createElement('div');
|
|
1197
1280
|
messageDiv.className = `message message-assistant${isCurrentActiveSession ? ' streaming-message' : ''}`;
|
|
@@ -1208,7 +1291,6 @@ class AgentGUIClient {
|
|
|
1208
1291
|
}
|
|
1209
1292
|
});
|
|
1210
1293
|
|
|
1211
|
-
// Add streaming indicator for active session
|
|
1212
1294
|
if (isCurrentActiveSession) {
|
|
1213
1295
|
const indicatorDiv = document.createElement('div');
|
|
1214
1296
|
indicatorDiv.className = 'streaming-indicator';
|
|
@@ -1227,6 +1309,21 @@ class AgentGUIClient {
|
|
|
1227
1309
|
|
|
1228
1310
|
messagesEl.appendChild(messageDiv);
|
|
1229
1311
|
});
|
|
1312
|
+
|
|
1313
|
+
// Render any remaining user messages after the last session
|
|
1314
|
+
while (userMsgIdx < userMessages.length) {
|
|
1315
|
+
const msg = userMessages[userMsgIdx];
|
|
1316
|
+
const userDiv = document.createElement('div');
|
|
1317
|
+
userDiv.className = 'message message-user';
|
|
1318
|
+
userDiv.setAttribute('data-msg-id', msg.id);
|
|
1319
|
+
userDiv.innerHTML = `
|
|
1320
|
+
<div class="message-role">User</div>
|
|
1321
|
+
${this.renderMessageContent(msg.content)}
|
|
1322
|
+
<div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
|
|
1323
|
+
`;
|
|
1324
|
+
messagesEl.appendChild(userDiv);
|
|
1325
|
+
userMsgIdx++;
|
|
1326
|
+
}
|
|
1230
1327
|
} else {
|
|
1231
1328
|
// Fall back to messages if no chunks
|
|
1232
1329
|
const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
|
|
@@ -76,48 +76,18 @@ class StreamingRenderer {
|
|
|
76
76
|
* Setup DOM mutation observer for external changes
|
|
77
77
|
*/
|
|
78
78
|
setupDOMObserver() {
|
|
79
|
-
try {
|
|
80
|
-
this.observer = new MutationObserver(() => {
|
|
81
|
-
this.updateDOMNodeCount();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
this.observer.observe(this.outputContainer, {
|
|
85
|
-
childList: true,
|
|
86
|
-
subtree: true,
|
|
87
|
-
characterData: false,
|
|
88
|
-
attributes: false
|
|
89
|
-
});
|
|
90
|
-
} catch (e) {
|
|
91
|
-
console.warn('DOM observer setup failed:', e.message);
|
|
92
|
-
}
|
|
93
79
|
}
|
|
94
80
|
|
|
95
81
|
/**
|
|
96
82
|
* Setup resize observer for viewport changes
|
|
97
83
|
*/
|
|
98
84
|
setupResizeObserver() {
|
|
99
|
-
try {
|
|
100
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
101
|
-
this.updateVirtualScroll();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (this.scrollContainer) {
|
|
105
|
-
this.resizeObserver.observe(this.scrollContainer);
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {
|
|
108
|
-
console.warn('Resize observer setup failed:', e.message);
|
|
109
|
-
}
|
|
110
85
|
}
|
|
111
86
|
|
|
112
87
|
/**
|
|
113
88
|
* Setup scroll optimization and auto-scroll
|
|
114
89
|
*/
|
|
115
90
|
setupScrollOptimization() {
|
|
116
|
-
if (this.scrollContainer) {
|
|
117
|
-
this.scrollContainer.addEventListener('scroll', () => {
|
|
118
|
-
this.updateVirtualScroll();
|
|
119
|
-
}, { passive: true });
|
|
120
|
-
}
|
|
121
91
|
}
|
|
122
92
|
|
|
123
93
|
/**
|
|
@@ -1076,13 +1046,27 @@ class StreamingRenderer {
|
|
|
1076
1046
|
? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
|
1077
1047
|
: '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>';
|
|
1078
1048
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1049
|
+
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this));
|
|
1050
|
+
|
|
1051
|
+
if (isError) {
|
|
1052
|
+
div.innerHTML = `
|
|
1053
|
+
<div class="result-header">
|
|
1054
|
+
<span class="status-label">${iconSvg} Error</span>
|
|
1055
|
+
${toolUseId ? `<span class="result-id">${this.escapeHtml(toolUseId)}</span>` : ''}
|
|
1056
|
+
</div>
|
|
1057
|
+
${renderedContent}
|
|
1058
|
+
`;
|
|
1059
|
+
} else {
|
|
1060
|
+
div.innerHTML = `
|
|
1061
|
+
<details class="result-collapsible">
|
|
1062
|
+
<summary class="result-header" style="cursor:pointer;list-style:none;user-select:none">
|
|
1063
|
+
<span class="status-label">${iconSvg} Success</span>
|
|
1064
|
+
${toolUseId ? `<span class="result-id">${this.escapeHtml(toolUseId)}</span>` : ''}
|
|
1065
|
+
</summary>
|
|
1066
|
+
<div class="result-collapsible-body">${renderedContent}</div>
|
|
1067
|
+
</details>
|
|
1068
|
+
`;
|
|
1069
|
+
}
|
|
1086
1070
|
|
|
1087
1071
|
return div;
|
|
1088
1072
|
}
|
|
@@ -1697,28 +1681,7 @@ class StreamingRenderer {
|
|
|
1697
1681
|
}
|
|
1698
1682
|
}
|
|
1699
1683
|
|
|
1700
|
-
/**
|
|
1701
|
-
* Update virtual scroll based on viewport
|
|
1702
|
-
*/
|
|
1703
1684
|
updateVirtualScroll() {
|
|
1704
|
-
if (!this.scrollContainer) return;
|
|
1705
|
-
|
|
1706
|
-
// Calculate visible items
|
|
1707
|
-
const scrollTop = this.scrollContainer.scrollTop;
|
|
1708
|
-
const viewportHeight = this.scrollContainer.clientHeight;
|
|
1709
|
-
const itemHeight = 80; // Approximate item height
|
|
1710
|
-
|
|
1711
|
-
const firstVisible = Math.floor(scrollTop / itemHeight);
|
|
1712
|
-
const lastVisible = Math.ceil((scrollTop + viewportHeight) / itemHeight);
|
|
1713
|
-
|
|
1714
|
-
// Update visibility of DOM nodes
|
|
1715
|
-
const items = this.outputContainer?.querySelectorAll('[data-event-id]');
|
|
1716
|
-
if (!items) return;
|
|
1717
|
-
|
|
1718
|
-
items.forEach((item, index) => {
|
|
1719
|
-
const isVisible = index >= firstVisible && index <= lastVisible;
|
|
1720
|
-
item.style.display = isVisible ? '' : 'none';
|
|
1721
|
-
});
|
|
1722
1685
|
}
|
|
1723
1686
|
|
|
1724
1687
|
/**
|
package/static/js/voice.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { STT, TTS } from 'webtalk-sdk';
|
|
2
|
-
|
|
3
1
|
(function() {
|
|
4
2
|
const BASE = window.__BASE_URL || '';
|
|
3
|
+
let STT = null;
|
|
4
|
+
let TTS = null;
|
|
5
|
+
|
|
6
|
+
async function loadSDK() {
|
|
7
|
+
try {
|
|
8
|
+
const mod = await import(BASE + '/webtalk/sdk.js');
|
|
9
|
+
STT = mod.STT;
|
|
10
|
+
TTS = mod.TTS;
|
|
11
|
+
return true;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.warn('Webtalk SDK load failed:', e.message);
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
let stt = null;
|
|
6
18
|
let tts = null;
|
|
7
19
|
let isRecording = false;
|
|
@@ -19,8 +31,14 @@ import { STT, TTS } from 'webtalk-sdk';
|
|
|
19
31
|
setupUI();
|
|
20
32
|
setupStreamingListener();
|
|
21
33
|
setupAgentSelector();
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
var sdkLoaded = await loadSDK();
|
|
35
|
+
if (sdkLoaded) {
|
|
36
|
+
initSTT();
|
|
37
|
+
initTTS();
|
|
38
|
+
} else {
|
|
39
|
+
sttLoadPhase = 'failed';
|
|
40
|
+
updateMicState();
|
|
41
|
+
}
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
var sttLoadPhase = 'starting';
|