agentgui 1.0.140 → 1.0.142
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 +60 -35
- package/static/js/client.js +119 -47
- package/static/js/voice.js +33 -25
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -40,6 +40,24 @@ const expressApp = express();
|
|
|
40
40
|
// Separate Express app for webtalk (STT/TTS) - isolated to contain COEP/COOP headers
|
|
41
41
|
const webtalkApp = express();
|
|
42
42
|
const webtalkInstance = webtalk(webtalkApp, { path: '/webtalk' });
|
|
43
|
+
|
|
44
|
+
const webtalkSdkDir = path.dirname(require.resolve('webtalk'));
|
|
45
|
+
const WASM_MIN_BYTES = 1000000;
|
|
46
|
+
const webtalkCriticalFiles = [
|
|
47
|
+
{ path: path.join(webtalkSdkDir, 'assets', 'ort-wasm-simd-threaded.jsep.wasm'), minBytes: WASM_MIN_BYTES }
|
|
48
|
+
];
|
|
49
|
+
for (const file of webtalkCriticalFiles) {
|
|
50
|
+
try {
|
|
51
|
+
if (fs.existsSync(file.path)) {
|
|
52
|
+
const stat = fs.statSync(file.path);
|
|
53
|
+
if (stat.size < file.minBytes) {
|
|
54
|
+
debugLog(`Removing corrupt file ${path.basename(file.path)} (${stat.size} bytes, need ${file.minBytes}+)`);
|
|
55
|
+
fs.unlinkSync(file.path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (e) { debugLog(`File check error: ${e.message}`); }
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
webtalkInstance.init().catch(err => debugLog('Webtalk init: ' + err.message));
|
|
44
62
|
|
|
45
63
|
// File upload endpoint - copies dropped files to conversation workingDirectory
|
|
@@ -164,7 +182,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
164
182
|
pathOnly.startsWith('/tts/') ||
|
|
165
183
|
pathOnly.startsWith('/models/');
|
|
166
184
|
if (isWebtalkRoute) {
|
|
167
|
-
const webtalkSdkDir = path.dirname(require.resolve('webtalk
|
|
185
|
+
const webtalkSdkDir = path.dirname(require.resolve('webtalk'));
|
|
168
186
|
const sdkFiles = { '/demo': 'app.html', '/sdk.js': 'sdk.js', '/stt.js': 'stt.js', '/tts.js': 'tts.js', '/tts-utils.js': 'tts-utils.js' };
|
|
169
187
|
let stripped = pathOnly.startsWith(webtalkPrefix) ? pathOnly.slice(webtalkPrefix.length) : (pathOnly.startsWith('/webtalk') ? pathOnly.slice('/webtalk'.length) : null);
|
|
170
188
|
if (stripped !== null && !sdkFiles[stripped] && !stripped.endsWith('.js') && sdkFiles[stripped + '.js']) stripped += '.js';
|
|
@@ -193,6 +211,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
193
211
|
});
|
|
194
212
|
}
|
|
195
213
|
if (req.url.startsWith(BASE_URL)) req.url = req.url.slice(BASE_URL.length) || '/';
|
|
214
|
+
const isModelOrAsset = pathOnly.includes('/models/') || pathOnly.includes('/assets/') || pathOnly.endsWith('.wasm') || pathOnly.endsWith('.onnx');
|
|
215
|
+
if (isModelOrAsset) {
|
|
216
|
+
res.setHeader('Cache-Control', 'public, max-age=604800, immutable');
|
|
217
|
+
}
|
|
196
218
|
const origSetHeader = res.setHeader.bind(res);
|
|
197
219
|
res.setHeader = (name, value) => {
|
|
198
220
|
if (name.toLowerCase() === 'cross-origin-embedder-policy') return;
|
|
@@ -569,46 +591,50 @@ const server = http.createServer(async (req, res) => {
|
|
|
569
591
|
}
|
|
570
592
|
});
|
|
571
593
|
|
|
594
|
+
const MIME_TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.wasm': 'application/wasm', '.onnx': 'application/octet-stream' };
|
|
595
|
+
|
|
572
596
|
function serveFile(filePath, res) {
|
|
573
597
|
const ext = path.extname(filePath).toLowerCase();
|
|
574
|
-
const
|
|
598
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
599
|
+
|
|
600
|
+
if (ext !== '.html') {
|
|
601
|
+
fs.stat(filePath, (err, stats) => {
|
|
602
|
+
if (err) { res.writeHead(500); res.end('Server error'); return; }
|
|
603
|
+
res.writeHead(200, {
|
|
604
|
+
'Content-Type': contentType,
|
|
605
|
+
'Content-Length': stats.size,
|
|
606
|
+
'Cache-Control': 'public, max-age=3600'
|
|
607
|
+
});
|
|
608
|
+
fs.createReadStream(filePath).pipe(res);
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
575
613
|
fs.readFile(filePath, (err, data) => {
|
|
576
614
|
if (err) { res.writeHead(500); res.end('Server error'); return; }
|
|
577
615
|
let content = data.toString();
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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>`;
|
|
583
|
-
}
|
|
616
|
+
const baseTag = `<script>window.__BASE_URL='${BASE_URL}';</script>\n <script type="importmap">{"imports":{"webtalk-sdk":"${BASE_URL}/webtalk/sdk.js"}}</script>`;
|
|
617
|
+
content = content.replace('<head>', '<head>\n ' + baseTag);
|
|
618
|
+
if (watch) {
|
|
619
|
+
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>`;
|
|
584
620
|
}
|
|
585
|
-
res.writeHead(200, { 'Content-Type':
|
|
621
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
586
622
|
res.end(content);
|
|
587
623
|
});
|
|
588
624
|
}
|
|
589
625
|
|
|
590
626
|
function persistChunkWithRetry(sessionId, conversationId, sequence, blockType, blockData, maxRetries = 3) {
|
|
591
|
-
let lastError = null;
|
|
592
|
-
const backoffs = [100, 200, 400];
|
|
593
|
-
|
|
594
627
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
595
628
|
try {
|
|
596
|
-
|
|
597
|
-
return chunk;
|
|
629
|
+
return queries.createChunk(sessionId, conversationId, sequence, blockType, blockData);
|
|
598
630
|
} catch (err) {
|
|
599
|
-
lastError = err;
|
|
600
631
|
debugLog(`[chunk] Persist attempt ${attempt + 1}/${maxRetries} failed: ${err.message}`);
|
|
601
|
-
if (attempt
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
while (Date.now() < endTime) {
|
|
605
|
-
// Synchronous sleep for backoff
|
|
606
|
-
}
|
|
632
|
+
if (attempt >= maxRetries - 1) {
|
|
633
|
+
debugLog(`[chunk] Failed to persist after ${maxRetries} retries: ${err.message}`);
|
|
634
|
+
return null;
|
|
607
635
|
}
|
|
608
636
|
}
|
|
609
637
|
}
|
|
610
|
-
|
|
611
|
-
debugLog(`[chunk] Failed to persist after ${maxRetries} retries: ${lastError?.message}`);
|
|
612
638
|
return null;
|
|
613
639
|
}
|
|
614
640
|
|
|
@@ -941,23 +967,22 @@ wss.on('connection', (ws, req) => {
|
|
|
941
967
|
}
|
|
942
968
|
});
|
|
943
969
|
|
|
970
|
+
const BROADCAST_TYPES = new Set([
|
|
971
|
+
'message_created', 'conversation_created', 'conversations_updated',
|
|
972
|
+
'conversation_deleted', 'queue_status', 'streaming_start',
|
|
973
|
+
'streaming_complete', 'streaming_error'
|
|
974
|
+
]);
|
|
975
|
+
|
|
944
976
|
function broadcastSync(event) {
|
|
977
|
+
if (syncClients.size === 0) return;
|
|
945
978
|
const data = JSON.stringify(event);
|
|
979
|
+
const isBroadcast = BROADCAST_TYPES.has(event.type);
|
|
946
980
|
|
|
947
981
|
for (const ws of syncClients) {
|
|
948
982
|
if (ws.readyState !== 1) continue;
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
if (event.sessionId && ws.subscriptions?.has(event.sessionId)) {
|
|
953
|
-
shouldSend = true;
|
|
954
|
-
} else if (event.conversationId && ws.subscriptions?.has(`conv-${event.conversationId}`)) {
|
|
955
|
-
shouldSend = true;
|
|
956
|
-
} else if (event.type === 'message_created' || event.type === 'conversation_created' || event.type === 'conversations_updated' || event.type === 'conversation_deleted' || event.type === 'queue_status' || event.type === 'streaming_start' || event.type === 'streaming_complete' || event.type === 'streaming_error') {
|
|
957
|
-
shouldSend = true;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
if (shouldSend) {
|
|
983
|
+
if (isBroadcast ||
|
|
984
|
+
(event.sessionId && ws.subscriptions?.has(event.sessionId)) ||
|
|
985
|
+
(event.conversationId && ws.subscriptions?.has(`conv-${event.conversationId}`))) {
|
|
961
986
|
ws.send(data);
|
|
962
987
|
}
|
|
963
988
|
}
|
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';
|
|
@@ -886,40 +938,29 @@ class AgentGUIClient {
|
|
|
886
938
|
if (!conversationId) return;
|
|
887
939
|
|
|
888
940
|
const pollState = this.chunkPollState;
|
|
889
|
-
if (pollState.isPolling) return;
|
|
941
|
+
if (pollState.isPolling) return;
|
|
890
942
|
|
|
891
943
|
pollState.isPolling = true;
|
|
892
944
|
pollState.lastFetchTimestamp = Date.now();
|
|
893
|
-
pollState.backoffDelay =
|
|
894
|
-
|
|
895
|
-
console.log('Starting chunk polling for conversation:', conversationId);
|
|
945
|
+
pollState.backoffDelay = 150;
|
|
946
|
+
pollState.sessionCheckCounter = 0;
|
|
896
947
|
|
|
897
948
|
const pollOnce = async () => {
|
|
898
949
|
if (!pollState.isPolling) return;
|
|
899
950
|
|
|
900
951
|
try {
|
|
901
|
-
|
|
902
|
-
if (this.state.currentSession?.id) {
|
|
952
|
+
pollState.sessionCheckCounter++;
|
|
953
|
+
if (pollState.sessionCheckCounter % 10 === 0 && this.state.currentSession?.id) {
|
|
903
954
|
const sessionResponse = await fetch(`${window.__BASE_URL}/api/sessions/${this.state.currentSession.id}`);
|
|
904
955
|
if (sessionResponse.ok) {
|
|
905
956
|
const { session } = await sessionResponse.json();
|
|
906
957
|
if (session && (session.status === 'complete' || session.status === 'error')) {
|
|
907
|
-
// Session has finished, trigger appropriate handler
|
|
908
958
|
if (session.status === 'complete') {
|
|
909
|
-
this.handleStreamingComplete({
|
|
910
|
-
sessionId: session.id,
|
|
911
|
-
conversationId: conversationId,
|
|
912
|
-
timestamp: Date.now()
|
|
913
|
-
});
|
|
959
|
+
this.handleStreamingComplete({ sessionId: session.id, conversationId, timestamp: Date.now() });
|
|
914
960
|
} else {
|
|
915
|
-
this.handleStreamingError({
|
|
916
|
-
sessionId: session.id,
|
|
917
|
-
conversationId: conversationId,
|
|
918
|
-
error: session.error || 'Unknown error',
|
|
919
|
-
timestamp: Date.now()
|
|
920
|
-
});
|
|
961
|
+
this.handleStreamingError({ sessionId: session.id, conversationId, error: session.error || 'Unknown error', timestamp: Date.now() });
|
|
921
962
|
}
|
|
922
|
-
return;
|
|
963
|
+
return;
|
|
923
964
|
}
|
|
924
965
|
}
|
|
925
966
|
}
|
|
@@ -927,42 +968,28 @@ class AgentGUIClient {
|
|
|
927
968
|
const chunks = await this.fetchChunks(conversationId, pollState.lastFetchTimestamp);
|
|
928
969
|
|
|
929
970
|
if (chunks.length > 0) {
|
|
930
|
-
|
|
931
|
-
pollState.backoffDelay = 100;
|
|
932
|
-
|
|
933
|
-
// Update last fetch timestamp
|
|
971
|
+
pollState.backoffDelay = 150;
|
|
934
972
|
const lastChunk = chunks[chunks.length - 1];
|
|
935
973
|
pollState.lastFetchTimestamp = lastChunk.created_at;
|
|
936
|
-
|
|
937
|
-
// Render new chunks
|
|
938
974
|
chunks.forEach(chunk => {
|
|
939
|
-
if (chunk.block && chunk.block.type)
|
|
940
|
-
this.renderChunk(chunk);
|
|
941
|
-
}
|
|
975
|
+
if (chunk.block && chunk.block.type) this.renderChunk(chunk);
|
|
942
976
|
});
|
|
977
|
+
} else {
|
|
978
|
+
pollState.backoffDelay = Math.min(pollState.backoffDelay + 50, 500);
|
|
943
979
|
}
|
|
944
980
|
|
|
945
|
-
// Schedule next poll
|
|
946
981
|
if (pollState.isPolling) {
|
|
947
|
-
pollState.pollTimer = setTimeout(pollOnce,
|
|
982
|
+
pollState.pollTimer = setTimeout(pollOnce, pollState.backoffDelay);
|
|
948
983
|
}
|
|
949
984
|
} catch (error) {
|
|
950
|
-
console.warn('Chunk poll error
|
|
951
|
-
|
|
952
|
-
// Apply exponential backoff
|
|
953
|
-
pollState.backoffDelay = Math.min(
|
|
954
|
-
pollState.backoffDelay * 2,
|
|
955
|
-
pollState.maxBackoffDelay
|
|
956
|
-
);
|
|
957
|
-
|
|
958
|
-
// Schedule next poll with backoff
|
|
985
|
+
console.warn('Chunk poll error:', error.message);
|
|
986
|
+
pollState.backoffDelay = Math.min(pollState.backoffDelay * 2, pollState.maxBackoffDelay);
|
|
959
987
|
if (pollState.isPolling) {
|
|
960
988
|
pollState.pollTimer = setTimeout(pollOnce, pollState.backoffDelay);
|
|
961
989
|
}
|
|
962
990
|
}
|
|
963
991
|
};
|
|
964
992
|
|
|
965
|
-
// Start polling loop
|
|
966
993
|
pollOnce();
|
|
967
994
|
}
|
|
968
995
|
|
|
@@ -1181,17 +1208,48 @@ class AgentGUIClient {
|
|
|
1181
1208
|
// Render all chunks
|
|
1182
1209
|
const messagesEl = outputEl.querySelector('.conversation-messages');
|
|
1183
1210
|
if (chunks.length > 0) {
|
|
1184
|
-
//
|
|
1211
|
+
// Fetch user messages to interleave with session chunks
|
|
1212
|
+
let userMessages = [];
|
|
1213
|
+
try {
|
|
1214
|
+
const msgResp = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
|
|
1215
|
+
if (msgResp.ok) {
|
|
1216
|
+
const msgData = await msgResp.json();
|
|
1217
|
+
userMessages = (msgData.messages || []).filter(m => m.role === 'user');
|
|
1218
|
+
}
|
|
1219
|
+
} catch (_) {}
|
|
1220
|
+
|
|
1221
|
+
// Group chunks by session, preserving order
|
|
1222
|
+
const sessionOrder = [];
|
|
1185
1223
|
const sessionChunks = {};
|
|
1186
1224
|
chunks.forEach(chunk => {
|
|
1187
1225
|
if (!sessionChunks[chunk.sessionId]) {
|
|
1188
1226
|
sessionChunks[chunk.sessionId] = [];
|
|
1227
|
+
sessionOrder.push(chunk.sessionId);
|
|
1189
1228
|
}
|
|
1190
1229
|
sessionChunks[chunk.sessionId].push(chunk);
|
|
1191
1230
|
});
|
|
1192
1231
|
|
|
1193
|
-
//
|
|
1194
|
-
|
|
1232
|
+
// Build a timeline: match user messages to sessions by timestamp
|
|
1233
|
+
let userMsgIdx = 0;
|
|
1234
|
+
sessionOrder.forEach((sessionId) => {
|
|
1235
|
+
const sessionChunkList = sessionChunks[sessionId];
|
|
1236
|
+
const sessionStart = sessionChunkList[0].created_at;
|
|
1237
|
+
|
|
1238
|
+
// Render user messages that came before this session
|
|
1239
|
+
while (userMsgIdx < userMessages.length && userMessages[userMsgIdx].created_at <= sessionStart) {
|
|
1240
|
+
const msg = userMessages[userMsgIdx];
|
|
1241
|
+
const userDiv = document.createElement('div');
|
|
1242
|
+
userDiv.className = 'message message-user';
|
|
1243
|
+
userDiv.setAttribute('data-msg-id', msg.id);
|
|
1244
|
+
userDiv.innerHTML = `
|
|
1245
|
+
<div class="message-role">User</div>
|
|
1246
|
+
${this.renderMessageContent(msg.content)}
|
|
1247
|
+
<div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
|
|
1248
|
+
`;
|
|
1249
|
+
messagesEl.appendChild(userDiv);
|
|
1250
|
+
userMsgIdx++;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1195
1253
|
const isCurrentActiveSession = shouldResumeStreaming && latestSession && latestSession.id === sessionId;
|
|
1196
1254
|
const messageDiv = document.createElement('div');
|
|
1197
1255
|
messageDiv.className = `message message-assistant${isCurrentActiveSession ? ' streaming-message' : ''}`;
|
|
@@ -1208,7 +1266,6 @@ class AgentGUIClient {
|
|
|
1208
1266
|
}
|
|
1209
1267
|
});
|
|
1210
1268
|
|
|
1211
|
-
// Add streaming indicator for active session
|
|
1212
1269
|
if (isCurrentActiveSession) {
|
|
1213
1270
|
const indicatorDiv = document.createElement('div');
|
|
1214
1271
|
indicatorDiv.className = 'streaming-indicator';
|
|
@@ -1227,6 +1284,21 @@ class AgentGUIClient {
|
|
|
1227
1284
|
|
|
1228
1285
|
messagesEl.appendChild(messageDiv);
|
|
1229
1286
|
});
|
|
1287
|
+
|
|
1288
|
+
// Render any remaining user messages after the last session
|
|
1289
|
+
while (userMsgIdx < userMessages.length) {
|
|
1290
|
+
const msg = userMessages[userMsgIdx];
|
|
1291
|
+
const userDiv = document.createElement('div');
|
|
1292
|
+
userDiv.className = 'message message-user';
|
|
1293
|
+
userDiv.setAttribute('data-msg-id', msg.id);
|
|
1294
|
+
userDiv.innerHTML = `
|
|
1295
|
+
<div class="message-role">User</div>
|
|
1296
|
+
${this.renderMessageContent(msg.content)}
|
|
1297
|
+
<div class="message-timestamp">${new Date(msg.created_at).toLocaleString()}</div>
|
|
1298
|
+
`;
|
|
1299
|
+
messagesEl.appendChild(userDiv);
|
|
1300
|
+
userMsgIdx++;
|
|
1301
|
+
}
|
|
1230
1302
|
} else {
|
|
1231
1303
|
// Fall back to messages if no chunks
|
|
1232
1304
|
const messagesResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}/messages`);
|
package/static/js/voice.js
CHANGED
|
@@ -127,32 +127,40 @@
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
async function initTTS() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
130
|
+
async function initTTS(retries) {
|
|
131
|
+
var maxRetries = retries || 3;
|
|
132
|
+
for (var attempt = 0; attempt < maxRetries; attempt++) {
|
|
133
|
+
try {
|
|
134
|
+
tts = new TTS({
|
|
135
|
+
basePath: BASE + '/webtalk',
|
|
136
|
+
apiBasePath: BASE,
|
|
137
|
+
onStatus: function() {},
|
|
138
|
+
onAudioReady: function(url) {
|
|
139
|
+
var audio = new Audio(url);
|
|
140
|
+
audio.onended = function() {
|
|
141
|
+
isSpeaking = false;
|
|
142
|
+
processQueue();
|
|
143
|
+
};
|
|
144
|
+
audio.onerror = function() {
|
|
145
|
+
isSpeaking = false;
|
|
146
|
+
processQueue();
|
|
147
|
+
};
|
|
148
|
+
audio.play().catch(function() {
|
|
149
|
+
isSpeaking = false;
|
|
150
|
+
processQueue();
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
await tts.init();
|
|
155
|
+
ttsReady = true;
|
|
156
|
+
return;
|
|
157
|
+
} catch (e) {
|
|
158
|
+
console.warn('TTS init attempt ' + (attempt + 1) + '/' + maxRetries + ' failed:', e.message);
|
|
159
|
+
tts = null;
|
|
160
|
+
if (attempt < maxRetries - 1) {
|
|
161
|
+
await new Promise(function(r) { setTimeout(r, 3000 * (attempt + 1)); });
|
|
150
162
|
}
|
|
151
|
-
}
|
|
152
|
-
await tts.init();
|
|
153
|
-
ttsReady = true;
|
|
154
|
-
} catch (e) {
|
|
155
|
-
console.warn('TTS init failed:', e.message);
|
|
163
|
+
}
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
|