agentgui 1.0.130 → 1.0.131
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/database.js +1 -1
- package/package.json +1 -1
- package/server.js +2 -1
- package/static/index.html +20 -0
- package/static/js/client.js +59 -5
- package/static/js/conversations.js +22 -1
package/database.js
CHANGED
|
@@ -280,7 +280,7 @@ export const queries = {
|
|
|
280
280
|
|
|
281
281
|
getConversationsList() {
|
|
282
282
|
const stmt = db.prepare(
|
|
283
|
-
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
283
|
+
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
284
284
|
);
|
|
285
285
|
return stmt.all('deleted');
|
|
286
286
|
},
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -550,6 +550,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
550
550
|
const startTime = Date.now();
|
|
551
551
|
activeExecutions.set(conversationId, true);
|
|
552
552
|
queries.setIsStreaming(conversationId, true);
|
|
553
|
+
queries.updateSession(sessionId, { status: 'active' });
|
|
553
554
|
|
|
554
555
|
try {
|
|
555
556
|
debugLog(`[stream] Starting: conversationId=${conversationId}, sessionId=${sessionId}`);
|
|
@@ -886,7 +887,7 @@ function broadcastSync(event) {
|
|
|
886
887
|
shouldSend = true;
|
|
887
888
|
} else if (event.conversationId && ws.subscriptions?.has(`conv-${event.conversationId}`)) {
|
|
888
889
|
shouldSend = true;
|
|
889
|
-
} else if (event.type === 'message_created' || event.type === 'conversation_created' || event.type === 'conversations_updated' || event.type === 'conversation_deleted' || event.type === 'queue_status') {
|
|
890
|
+
} 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') {
|
|
890
891
|
shouldSend = true;
|
|
891
892
|
}
|
|
892
893
|
|
package/static/index.html
CHANGED
|
@@ -161,6 +161,26 @@
|
|
|
161
161
|
|
|
162
162
|
.conversation-item.active .conversation-item-meta { color: rgba(255,255,255,0.7); }
|
|
163
163
|
|
|
164
|
+
.conversation-streaming-badge {
|
|
165
|
+
display: inline-flex;
|
|
166
|
+
align-items: center;
|
|
167
|
+
margin-right: 0.375rem;
|
|
168
|
+
vertical-align: middle;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.streaming-dot {
|
|
172
|
+
display: inline-block;
|
|
173
|
+
width: 0.5rem;
|
|
174
|
+
height: 0.5rem;
|
|
175
|
+
border-radius: 50%;
|
|
176
|
+
background-color: var(--color-success);
|
|
177
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.conversation-item.active .streaming-dot {
|
|
181
|
+
background-color: #fff;
|
|
182
|
+
}
|
|
183
|
+
|
|
164
184
|
.conversation-item {
|
|
165
185
|
display: flex;
|
|
166
186
|
align-items: center;
|
package/static/js/client.js
CHANGED
|
@@ -344,6 +344,9 @@ class AgentGUIClient {
|
|
|
344
344
|
|
|
345
345
|
handleWebSocketMessage(data) {
|
|
346
346
|
try {
|
|
347
|
+
// Dispatch to window so other modules (conversations.js) can listen
|
|
348
|
+
window.dispatchEvent(new CustomEvent('ws-message', { detail: data }));
|
|
349
|
+
|
|
347
350
|
switch (data.type) {
|
|
348
351
|
case 'streaming_start':
|
|
349
352
|
this.handleStreamingStart(data);
|
|
@@ -388,6 +391,15 @@ class AgentGUIClient {
|
|
|
388
391
|
|
|
389
392
|
handleStreamingStart(data) {
|
|
390
393
|
console.log('Streaming started:', data);
|
|
394
|
+
|
|
395
|
+
// If this streaming event is for a different conversation than what we are viewing,
|
|
396
|
+
// just track the state but do not modify the DOM or start polling
|
|
397
|
+
if (this.state.currentConversation?.id !== data.conversationId) {
|
|
398
|
+
console.log('Streaming started for non-active conversation:', data.conversationId);
|
|
399
|
+
this.emit('streaming:start', data);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
391
403
|
this.state.isStreaming = true;
|
|
392
404
|
this.state.currentSession = {
|
|
393
405
|
id: data.sessionId,
|
|
@@ -490,8 +502,21 @@ class AgentGUIClient {
|
|
|
490
502
|
|
|
491
503
|
handleStreamingError(data) {
|
|
492
504
|
console.error('Streaming error:', data);
|
|
505
|
+
|
|
506
|
+
const conversationId = data.conversationId || this.state.currentSession?.conversationId;
|
|
507
|
+
|
|
508
|
+
// If this event is for a conversation we are NOT currently viewing, just track state
|
|
509
|
+
if (conversationId && this.state.currentConversation?.id !== conversationId) {
|
|
510
|
+
console.log('Streaming error for non-active conversation:', conversationId);
|
|
511
|
+
this.emit('streaming:error', data);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
493
515
|
this.state.isStreaming = false;
|
|
494
516
|
|
|
517
|
+
// Stop polling for chunks
|
|
518
|
+
this.stopChunkPolling();
|
|
519
|
+
|
|
495
520
|
const sessionId = data.sessionId || this.state.currentSession?.id;
|
|
496
521
|
const streamingEl = document.getElementById(`streaming-${sessionId}`);
|
|
497
522
|
if (streamingEl) {
|
|
@@ -507,6 +532,16 @@ class AgentGUIClient {
|
|
|
507
532
|
|
|
508
533
|
handleStreamingComplete(data) {
|
|
509
534
|
console.log('Streaming completed:', data);
|
|
535
|
+
|
|
536
|
+
const conversationId = data.conversationId || this.state.currentSession?.conversationId;
|
|
537
|
+
|
|
538
|
+
// If this event is for a conversation we are NOT currently viewing, just track state
|
|
539
|
+
if (conversationId && this.state.currentConversation?.id !== conversationId) {
|
|
540
|
+
console.log('Streaming completed for non-active conversation:', conversationId);
|
|
541
|
+
this.emit('streaming:complete', data);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
510
545
|
this.state.isStreaming = false;
|
|
511
546
|
|
|
512
547
|
// Stop polling for chunks
|
|
@@ -528,7 +563,6 @@ class AgentGUIClient {
|
|
|
528
563
|
}
|
|
529
564
|
|
|
530
565
|
// Save scroll position after streaming completes
|
|
531
|
-
const conversationId = data.conversationId || this.state.currentSession?.conversationId;
|
|
532
566
|
if (conversationId) {
|
|
533
567
|
this.saveScrollPosition(conversationId);
|
|
534
568
|
}
|
|
@@ -1099,9 +1133,21 @@ class AgentGUIClient {
|
|
|
1099
1133
|
|
|
1100
1134
|
async loadConversationMessages(conversationId) {
|
|
1101
1135
|
try {
|
|
1136
|
+
// Save scroll position of current conversation before switching
|
|
1137
|
+
if (this.state.currentConversation?.id) {
|
|
1138
|
+
this.saveScrollPosition(this.state.currentConversation.id);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1102
1141
|
// Stop any existing polling when switching conversations
|
|
1103
1142
|
this.stopChunkPolling();
|
|
1104
1143
|
|
|
1144
|
+
// Clear streaming state from previous conversation view
|
|
1145
|
+
// (the actual streaming continues on the server, we just stop tracking it on the UI side)
|
|
1146
|
+
if (this.state.isStreaming && this.state.currentConversation?.id !== conversationId) {
|
|
1147
|
+
this.state.isStreaming = false;
|
|
1148
|
+
this.state.currentSession = null;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1105
1151
|
const convResponse = await fetch(window.__BASE_URL + `/api/conversations/${conversationId}`);
|
|
1106
1152
|
const { conversation, isActivelyStreaming, latestSession } = await convResponse.json();
|
|
1107
1153
|
this.state.currentConversation = conversation;
|
|
@@ -1114,7 +1160,10 @@ class AgentGUIClient {
|
|
|
1114
1160
|
}
|
|
1115
1161
|
|
|
1116
1162
|
// Check if there's an active streaming session that needs to be resumed
|
|
1117
|
-
|
|
1163
|
+
// isActivelyStreaming comes from the server checking both in-memory activeExecutions map
|
|
1164
|
+
// AND database session status. Use it as primary signal, with session status as confirmation.
|
|
1165
|
+
const shouldResumeStreaming = isActivelyStreaming && latestSession &&
|
|
1166
|
+
(latestSession.status === 'active' || latestSession.status === 'pending');
|
|
1118
1167
|
|
|
1119
1168
|
// Try to fetch chunks first (Wave 3 architecture)
|
|
1120
1169
|
try {
|
|
@@ -1202,17 +1251,22 @@ class AgentGUIClient {
|
|
|
1202
1251
|
startTime: latestSession.created_at
|
|
1203
1252
|
};
|
|
1204
1253
|
|
|
1205
|
-
// Subscribe to WebSocket updates
|
|
1254
|
+
// Subscribe to WebSocket updates for BOTH conversation and session
|
|
1206
1255
|
if (this.wsManager.isConnected) {
|
|
1207
1256
|
this.wsManager.subscribeToSession(latestSession.id);
|
|
1257
|
+
this.wsManager.sendMessage({ type: 'subscribe', conversationId });
|
|
1208
1258
|
}
|
|
1209
1259
|
|
|
1260
|
+
// Update URL with session ID
|
|
1261
|
+
this.updateUrlForConversation(conversationId, latestSession.id);
|
|
1262
|
+
|
|
1210
1263
|
// Get the timestamp of the last chunk to start polling from
|
|
1264
|
+
// Use the last chunk's created_at to avoid re-fetching already-rendered chunks
|
|
1211
1265
|
const lastChunkTime = chunks.length > 0
|
|
1212
1266
|
? chunks[chunks.length - 1].created_at
|
|
1213
|
-
:
|
|
1267
|
+
: 0;
|
|
1214
1268
|
|
|
1215
|
-
// Start polling for new chunks
|
|
1269
|
+
// Start polling for new chunks from where we left off
|
|
1216
1270
|
this.chunkPollState.lastFetchTimestamp = lastChunkTime;
|
|
1217
1271
|
this.startChunkPolling(conversationId);
|
|
1218
1272
|
|
|
@@ -12,6 +12,7 @@ class ConversationManager {
|
|
|
12
12
|
this.emptyEl = document.querySelector('[data-conversation-empty]');
|
|
13
13
|
this.newBtn = document.querySelector('[data-new-conversation]');
|
|
14
14
|
this.sidebarEl = document.querySelector('[data-sidebar]');
|
|
15
|
+
this.streamingConversations = new Set();
|
|
15
16
|
|
|
16
17
|
this.folderBrowser = {
|
|
17
18
|
modal: null,
|
|
@@ -191,6 +192,14 @@ class ConversationManager {
|
|
|
191
192
|
|
|
192
193
|
const data = await res.json();
|
|
193
194
|
this.conversations = data.conversations || [];
|
|
195
|
+
|
|
196
|
+
// Seed streaming state from database isStreaming flag
|
|
197
|
+
for (const conv of this.conversations) {
|
|
198
|
+
if (conv.isStreaming === 1 || conv.isStreaming === true) {
|
|
199
|
+
this.streamingConversations.add(conv.id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
this.render();
|
|
195
204
|
} catch (err) {
|
|
196
205
|
console.error('Failed to load conversations:', err);
|
|
@@ -225,6 +234,8 @@ class ConversationManager {
|
|
|
225
234
|
li.dataset.convId = conv.id;
|
|
226
235
|
if (conv.id === this.activeId) li.classList.add('active');
|
|
227
236
|
|
|
237
|
+
const isStreaming = conv.isStreaming === 1 || conv.isStreaming === true || this.streamingConversations?.has(conv.id);
|
|
238
|
+
|
|
228
239
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
229
240
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
230
241
|
const agent = conv.agentType || 'unknown';
|
|
@@ -232,9 +243,13 @@ class ConversationManager {
|
|
|
232
243
|
const metaParts = [agent, timestamp];
|
|
233
244
|
if (wd) metaParts.push(wd);
|
|
234
245
|
|
|
246
|
+
const streamingBadge = isStreaming
|
|
247
|
+
? '<span class="conversation-streaming-badge" title="Streaming in progress"><span class="streaming-dot"></span></span>'
|
|
248
|
+
: '';
|
|
249
|
+
|
|
235
250
|
li.innerHTML = `
|
|
236
251
|
<div class="conversation-item-content">
|
|
237
|
-
<div class="conversation-item-title">${this.escapeHtml(title)}</div>
|
|
252
|
+
<div class="conversation-item-title">${streamingBadge}${this.escapeHtml(title)}</div>
|
|
238
253
|
<div class="conversation-item-meta">${metaParts.join(' • ')}</div>
|
|
239
254
|
</div>
|
|
240
255
|
<button class="conversation-item-delete" title="Delete conversation" data-delete-conv="${conv.id}">
|
|
@@ -335,6 +350,12 @@ class ConversationManager {
|
|
|
335
350
|
this.updateConversation(msg.conversation.id, msg.conversation);
|
|
336
351
|
} else if (msg.type === 'conversation_deleted') {
|
|
337
352
|
this.deleteConversation(msg.conversationId);
|
|
353
|
+
} else if (msg.type === 'streaming_start' && msg.conversationId) {
|
|
354
|
+
this.streamingConversations.add(msg.conversationId);
|
|
355
|
+
this.render();
|
|
356
|
+
} else if ((msg.type === 'streaming_complete' || msg.type === 'streaming_error') && msg.conversationId) {
|
|
357
|
+
this.streamingConversations.delete(msg.conversationId);
|
|
358
|
+
this.render();
|
|
338
359
|
}
|
|
339
360
|
});
|
|
340
361
|
}
|