agentgui 1.0.141 → 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 +15 -40
- 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
|
@@ -938,40 +938,29 @@ class AgentGUIClient {
|
|
|
938
938
|
if (!conversationId) return;
|
|
939
939
|
|
|
940
940
|
const pollState = this.chunkPollState;
|
|
941
|
-
if (pollState.isPolling) return;
|
|
941
|
+
if (pollState.isPolling) return;
|
|
942
942
|
|
|
943
943
|
pollState.isPolling = true;
|
|
944
944
|
pollState.lastFetchTimestamp = Date.now();
|
|
945
|
-
pollState.backoffDelay =
|
|
946
|
-
|
|
947
|
-
console.log('Starting chunk polling for conversation:', conversationId);
|
|
945
|
+
pollState.backoffDelay = 150;
|
|
946
|
+
pollState.sessionCheckCounter = 0;
|
|
948
947
|
|
|
949
948
|
const pollOnce = async () => {
|
|
950
949
|
if (!pollState.isPolling) return;
|
|
951
950
|
|
|
952
951
|
try {
|
|
953
|
-
|
|
954
|
-
if (this.state.currentSession?.id) {
|
|
952
|
+
pollState.sessionCheckCounter++;
|
|
953
|
+
if (pollState.sessionCheckCounter % 10 === 0 && this.state.currentSession?.id) {
|
|
955
954
|
const sessionResponse = await fetch(`${window.__BASE_URL}/api/sessions/${this.state.currentSession.id}`);
|
|
956
955
|
if (sessionResponse.ok) {
|
|
957
956
|
const { session } = await sessionResponse.json();
|
|
958
957
|
if (session && (session.status === 'complete' || session.status === 'error')) {
|
|
959
|
-
// Session has finished, trigger appropriate handler
|
|
960
958
|
if (session.status === 'complete') {
|
|
961
|
-
this.handleStreamingComplete({
|
|
962
|
-
sessionId: session.id,
|
|
963
|
-
conversationId: conversationId,
|
|
964
|
-
timestamp: Date.now()
|
|
965
|
-
});
|
|
959
|
+
this.handleStreamingComplete({ sessionId: session.id, conversationId, timestamp: Date.now() });
|
|
966
960
|
} else {
|
|
967
|
-
this.handleStreamingError({
|
|
968
|
-
sessionId: session.id,
|
|
969
|
-
conversationId: conversationId,
|
|
970
|
-
error: session.error || 'Unknown error',
|
|
971
|
-
timestamp: Date.now()
|
|
972
|
-
});
|
|
961
|
+
this.handleStreamingError({ sessionId: session.id, conversationId, error: session.error || 'Unknown error', timestamp: Date.now() });
|
|
973
962
|
}
|
|
974
|
-
return;
|
|
963
|
+
return;
|
|
975
964
|
}
|
|
976
965
|
}
|
|
977
966
|
}
|
|
@@ -979,42 +968,28 @@ class AgentGUIClient {
|
|
|
979
968
|
const chunks = await this.fetchChunks(conversationId, pollState.lastFetchTimestamp);
|
|
980
969
|
|
|
981
970
|
if (chunks.length > 0) {
|
|
982
|
-
|
|
983
|
-
pollState.backoffDelay = 100;
|
|
984
|
-
|
|
985
|
-
// Update last fetch timestamp
|
|
971
|
+
pollState.backoffDelay = 150;
|
|
986
972
|
const lastChunk = chunks[chunks.length - 1];
|
|
987
973
|
pollState.lastFetchTimestamp = lastChunk.created_at;
|
|
988
|
-
|
|
989
|
-
// Render new chunks
|
|
990
974
|
chunks.forEach(chunk => {
|
|
991
|
-
if (chunk.block && chunk.block.type)
|
|
992
|
-
this.renderChunk(chunk);
|
|
993
|
-
}
|
|
975
|
+
if (chunk.block && chunk.block.type) this.renderChunk(chunk);
|
|
994
976
|
});
|
|
977
|
+
} else {
|
|
978
|
+
pollState.backoffDelay = Math.min(pollState.backoffDelay + 50, 500);
|
|
995
979
|
}
|
|
996
980
|
|
|
997
|
-
// Schedule next poll
|
|
998
981
|
if (pollState.isPolling) {
|
|
999
|
-
pollState.pollTimer = setTimeout(pollOnce,
|
|
982
|
+
pollState.pollTimer = setTimeout(pollOnce, pollState.backoffDelay);
|
|
1000
983
|
}
|
|
1001
984
|
} catch (error) {
|
|
1002
|
-
console.warn('Chunk poll error
|
|
1003
|
-
|
|
1004
|
-
// Apply exponential backoff
|
|
1005
|
-
pollState.backoffDelay = Math.min(
|
|
1006
|
-
pollState.backoffDelay * 2,
|
|
1007
|
-
pollState.maxBackoffDelay
|
|
1008
|
-
);
|
|
1009
|
-
|
|
1010
|
-
// Schedule next poll with backoff
|
|
985
|
+
console.warn('Chunk poll error:', error.message);
|
|
986
|
+
pollState.backoffDelay = Math.min(pollState.backoffDelay * 2, pollState.maxBackoffDelay);
|
|
1011
987
|
if (pollState.isPolling) {
|
|
1012
988
|
pollState.pollTimer = setTimeout(pollOnce, pollState.backoffDelay);
|
|
1013
989
|
}
|
|
1014
990
|
}
|
|
1015
991
|
};
|
|
1016
992
|
|
|
1017
|
-
// Start polling loop
|
|
1018
993
|
pollOnce();
|
|
1019
994
|
}
|
|
1020
995
|
|
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
|
|