@agent-link/server 0.1.184 → 0.1.185
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/web/app.js +18 -10
- package/web/css/base.css +19 -0
- package/web/css/file-browser.css +0 -4
- package/web/css/input.css +1 -5
- package/web/css/loop.css +3 -11
- package/web/css/sidebar.css +1 -11
- package/web/css/team.css +9 -9
- package/web/css/tools.css +2 -2
- package/web/modules/appHelpers.js +2 -1
- package/web/modules/backgroundRouting.js +12 -2
- package/web/modules/connection.js +8 -1
- package/web/modules/messageHelpers.js +17 -4
- package/web/modules/streaming.js +10 -6
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -406,8 +406,15 @@ const App = {
|
|
|
406
406
|
});
|
|
407
407
|
setFilePreview(filePreview);
|
|
408
408
|
|
|
409
|
-
// Track mobile state on resize
|
|
410
|
-
let
|
|
409
|
+
// Track mobile state on resize (rAF-throttled)
|
|
410
|
+
let _resizeRafId = 0;
|
|
411
|
+
let _resizeHandler = () => {
|
|
412
|
+
if (_resizeRafId) return;
|
|
413
|
+
_resizeRafId = requestAnimationFrame(() => {
|
|
414
|
+
_resizeRafId = 0;
|
|
415
|
+
isMobile.value = window.innerWidth <= 768;
|
|
416
|
+
});
|
|
417
|
+
};
|
|
411
418
|
window.addEventListener('resize', _resizeHandler);
|
|
412
419
|
|
|
413
420
|
// Close workdir menu on outside click or Escape
|
|
@@ -426,10 +433,11 @@ const App = {
|
|
|
426
433
|
|
|
427
434
|
// ── Computed ──
|
|
428
435
|
const hasInput = computed(() => !!(inputText.value.trim() || attachments.value.length > 0));
|
|
436
|
+
const hasPendingQuestion = computed(() => messages.value.some(m => m.role === 'ask-question' && !m.answered));
|
|
429
437
|
const canSend = computed(() =>
|
|
430
|
-
status.value === 'Connected' && hasInput.value && !isCompacting.value
|
|
431
|
-
&& !messages.value.some(m => m.role === 'ask-question' && !m.answered)
|
|
438
|
+
status.value === 'Connected' && hasInput.value && !isCompacting.value && !hasPendingQuestion.value
|
|
432
439
|
);
|
|
440
|
+
const hasStreamingMessage = computed(() => messages.value.some(m => m.isStreaming));
|
|
433
441
|
|
|
434
442
|
// ── Slash command menu ──
|
|
435
443
|
const slashMenuVisible = computed(() => {
|
|
@@ -695,7 +703,7 @@ const App = {
|
|
|
695
703
|
status, agentName, hostname, workDir, sessionId, error,
|
|
696
704
|
serverVersion, agentVersion, latency,
|
|
697
705
|
messages, visibleMessages, hasMoreMessages, loadMoreMessages,
|
|
698
|
-
inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages, usageStats,
|
|
706
|
+
inputText, isProcessing, isCompacting, canSend, hasInput, hasStreamingMessage, inputRef, queuedMessages, usageStats,
|
|
699
707
|
slashMenuVisible, filteredSlashCommands, slashMenuIndex, slashMenuOpen, selectSlashCommand, openSlashMenu,
|
|
700
708
|
sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
|
|
701
709
|
// Side question (/btw)
|
|
@@ -2033,7 +2041,7 @@ const App = {
|
|
|
2033
2041
|
</span>
|
|
2034
2042
|
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
2035
2043
|
</div>
|
|
2036
|
-
<div v-
|
|
2044
|
+
<div v-if="msg.expanded" class="tool-expand">
|
|
2037
2045
|
<div v-if="isEditTool(msg) && getEditDiffHtml(msg)" class="tool-diff" v-html="getEditDiffHtml(msg)"></div>
|
|
2038
2046
|
<div v-else-if="getFormattedToolInput(msg)" class="tool-input-formatted" v-html="getFormattedToolInput(msg)"></div>
|
|
2039
2047
|
<pre v-else-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
@@ -2090,7 +2098,7 @@ const App = {
|
|
|
2090
2098
|
</span>
|
|
2091
2099
|
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
2092
2100
|
</div>
|
|
2093
|
-
<div v-
|
|
2101
|
+
<div v-if="msg.expanded" class="tool-expand">
|
|
2094
2102
|
<div v-if="isEditTool(msg) && getEditDiffHtml(msg)" class="tool-diff" v-html="getEditDiffHtml(msg)"></div>
|
|
2095
2103
|
<div v-else-if="getFormattedToolInput(msg)" class="tool-input-formatted" v-html="getFormattedToolInput(msg)"></div>
|
|
2096
2104
|
<pre v-else-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
@@ -2394,7 +2402,7 @@ const App = {
|
|
|
2394
2402
|
</span>
|
|
2395
2403
|
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
2396
2404
|
</div>
|
|
2397
|
-
<div v-
|
|
2405
|
+
<div v-if="msg.expanded" class="tool-expand team-agent-tool-expand">
|
|
2398
2406
|
<pre v-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
2399
2407
|
<div v-if="msg.toolOutput" class="team-agent-tool-result">
|
|
2400
2408
|
<div class="team-agent-tool-result-label">{{ t('team.agentResult') }}</div>
|
|
@@ -2415,7 +2423,7 @@ const App = {
|
|
|
2415
2423
|
</span>
|
|
2416
2424
|
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
2417
2425
|
</div>
|
|
2418
|
-
<div v-
|
|
2426
|
+
<div v-if="msg.expanded" class="tool-expand">
|
|
2419
2427
|
<div v-if="isEditTool(msg) && getEditDiffHtml(msg)" class="tool-diff" v-html="getEditDiffHtml(msg)"></div>
|
|
2420
2428
|
<div v-else-if="getFormattedToolInput(msg)" class="tool-input-formatted" v-html="getFormattedToolInput(msg)"></div>
|
|
2421
2429
|
<pre v-else-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
@@ -2490,7 +2498,7 @@ const App = {
|
|
|
2490
2498
|
</div>
|
|
2491
2499
|
</div>
|
|
2492
2500
|
|
|
2493
|
-
<div v-if="isProcessing && !
|
|
2501
|
+
<div v-if="isProcessing && !hasStreamingMessage" class="typing-indicator">
|
|
2494
2502
|
<span></span><span></span><span></span>
|
|
2495
2503
|
</div>
|
|
2496
2504
|
</div>
|
package/web/css/base.css
CHANGED
|
@@ -236,6 +236,25 @@ body {
|
|
|
236
236
|
margin-top: 0.75rem;
|
|
237
237
|
}
|
|
238
238
|
|
|
239
|
+
/* ── Shared keyframes ── */
|
|
240
|
+
@keyframes spin {
|
|
241
|
+
to { transform: rotate(360deg); }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@keyframes pulse {
|
|
245
|
+
0%, 100% { opacity: 1; }
|
|
246
|
+
50% { opacity: 0.3; }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* ── Reduced motion ── */
|
|
250
|
+
@media (prefers-reduced-motion: reduce) {
|
|
251
|
+
*, *::before, *::after {
|
|
252
|
+
animation-duration: 0.01ms !important;
|
|
253
|
+
animation-iteration-count: 1 !important;
|
|
254
|
+
transition-duration: 0.01ms !important;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
239
258
|
/* ── Main body (sidebar + chat) ── */
|
|
240
259
|
.main-body {
|
|
241
260
|
flex: 1;
|
package/web/css/file-browser.css
CHANGED
package/web/css/input.css
CHANGED
|
@@ -225,7 +225,6 @@
|
|
|
225
225
|
align-items: center;
|
|
226
226
|
justify-content: center;
|
|
227
227
|
gap: 16px;
|
|
228
|
-
backdrop-filter: blur(2px);
|
|
229
228
|
}
|
|
230
229
|
.workdir-switching-spinner {
|
|
231
230
|
width: 36px;
|
|
@@ -233,10 +232,7 @@
|
|
|
233
232
|
border: 3px solid rgba(255, 255, 255, 0.2);
|
|
234
233
|
border-top-color: rgba(255, 255, 255, 0.8);
|
|
235
234
|
border-radius: 50%;
|
|
236
|
-
animation:
|
|
237
|
-
}
|
|
238
|
-
@keyframes workdir-spin {
|
|
239
|
-
to { transform: rotate(360deg); }
|
|
235
|
+
animation: spin 0.7s linear infinite;
|
|
240
236
|
}
|
|
241
237
|
.workdir-switching-text {
|
|
242
238
|
color: rgba(255, 255, 255, 0.9);
|
package/web/css/loop.css
CHANGED
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
border: 1px solid var(--border);
|
|
186
186
|
border-radius: 6px;
|
|
187
187
|
cursor: pointer;
|
|
188
|
-
transition:
|
|
188
|
+
transition: color 0.15s, border-color 0.15s;
|
|
189
189
|
white-space: nowrap;
|
|
190
190
|
}
|
|
191
191
|
.loop-action-btn:hover:not(:disabled) {
|
|
@@ -353,11 +353,7 @@
|
|
|
353
353
|
}
|
|
354
354
|
.loop-exec-status-running {
|
|
355
355
|
color: var(--accent);
|
|
356
|
-
animation:
|
|
357
|
-
}
|
|
358
|
-
@keyframes loop-spin {
|
|
359
|
-
from { transform: rotate(0deg); }
|
|
360
|
-
to { transform: rotate(360deg); }
|
|
356
|
+
animation: spin 1s linear infinite;
|
|
361
357
|
}
|
|
362
358
|
.loop-exec-status-success {
|
|
363
359
|
color: #10B981;
|
|
@@ -428,11 +424,7 @@
|
|
|
428
424
|
height: 8px;
|
|
429
425
|
border-radius: 50%;
|
|
430
426
|
background: var(--accent);
|
|
431
|
-
animation:
|
|
432
|
-
}
|
|
433
|
-
@keyframes loop-pulse {
|
|
434
|
-
0%, 100% { opacity: 1; }
|
|
435
|
-
50% { opacity: 0.3; }
|
|
427
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
436
428
|
}
|
|
437
429
|
|
|
438
430
|
/* ── Modal dialog (generic) ── */
|
package/web/css/sidebar.css
CHANGED
|
@@ -210,11 +210,6 @@
|
|
|
210
210
|
transform: rotate(-90deg);
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
-
@keyframes spin {
|
|
214
|
-
from { transform: rotate(0deg); }
|
|
215
|
-
to { transform: rotate(360deg); }
|
|
216
|
-
}
|
|
217
|
-
|
|
218
213
|
.spinning {
|
|
219
214
|
animation: spin 0.8s linear infinite;
|
|
220
215
|
}
|
|
@@ -329,12 +324,7 @@
|
|
|
329
324
|
background: var(--accent);
|
|
330
325
|
margin-right: 6px;
|
|
331
326
|
vertical-align: middle;
|
|
332
|
-
animation: pulse
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
@keyframes pulse-dot {
|
|
336
|
-
0%, 100% { opacity: 1; }
|
|
337
|
-
50% { opacity: 0.3; }
|
|
327
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
338
328
|
}
|
|
339
329
|
|
|
340
330
|
.session-meta {
|
package/web/css/team.css
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
font-size: 0.75rem;
|
|
22
22
|
font-weight: 500;
|
|
23
23
|
cursor: pointer;
|
|
24
|
-
transition:
|
|
24
|
+
transition: color 0.15s, background 0.15s, box-shadow 0.15s;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
.team-mode-btn.active {
|
|
@@ -194,7 +194,7 @@
|
|
|
194
194
|
color: var(--text-secondary);
|
|
195
195
|
font-size: 0.78rem;
|
|
196
196
|
cursor: pointer;
|
|
197
|
-
transition:
|
|
197
|
+
transition: color 0.15s, border-color 0.15s;
|
|
198
198
|
}
|
|
199
199
|
.team-lead-prompt-reset:hover {
|
|
200
200
|
color: var(--text-primary);
|
|
@@ -258,7 +258,7 @@
|
|
|
258
258
|
color: var(--text-secondary);
|
|
259
259
|
font-size: 0.85rem;
|
|
260
260
|
cursor: pointer;
|
|
261
|
-
transition:
|
|
261
|
+
transition: color 0.15s, border-color 0.15s;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
.team-create-cancel:hover {
|
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
padding: 14px 16px;
|
|
294
294
|
border: 1px solid var(--border);
|
|
295
295
|
border-radius: 10px;
|
|
296
|
-
transition:
|
|
296
|
+
transition: border-color 0.15s, background 0.15s;
|
|
297
297
|
background: linear-gradient(135deg, rgba(255,255,255,0.02) 0%, transparent 100%);
|
|
298
298
|
}
|
|
299
299
|
|
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
border: 1px solid var(--accent);
|
|
355
355
|
border-radius: 6px;
|
|
356
356
|
cursor: pointer;
|
|
357
|
-
transition:
|
|
357
|
+
transition: color 0.15s, background 0.15s;
|
|
358
358
|
white-space: nowrap;
|
|
359
359
|
}
|
|
360
360
|
|
|
@@ -500,7 +500,7 @@
|
|
|
500
500
|
color: var(--error);
|
|
501
501
|
font-size: 0.8rem;
|
|
502
502
|
cursor: pointer;
|
|
503
|
-
transition:
|
|
503
|
+
transition: background 0.15s;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
506
|
.team-dissolve-btn:hover {
|
|
@@ -515,7 +515,7 @@
|
|
|
515
515
|
color: var(--text-secondary);
|
|
516
516
|
font-size: 0.8rem;
|
|
517
517
|
cursor: pointer;
|
|
518
|
-
transition:
|
|
518
|
+
transition: color 0.15s, border-color 0.15s;
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
.team-back-btn:hover {
|
|
@@ -531,7 +531,7 @@
|
|
|
531
531
|
color: var(--accent);
|
|
532
532
|
font-size: 0.8rem;
|
|
533
533
|
cursor: pointer;
|
|
534
|
-
transition:
|
|
534
|
+
transition: background 0.15s;
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
.team-new-btn:hover {
|
|
@@ -1111,7 +1111,7 @@
|
|
|
1111
1111
|
color: var(--text-secondary);
|
|
1112
1112
|
font-size: 0.78rem;
|
|
1113
1113
|
cursor: pointer;
|
|
1114
|
-
transition:
|
|
1114
|
+
transition: color 0.15s, border-color 0.15s;
|
|
1115
1115
|
margin-right: 4px;
|
|
1116
1116
|
}
|
|
1117
1117
|
|
package/web/css/tools.css
CHANGED
|
@@ -44,7 +44,8 @@ export function createHighlightScheduler() {
|
|
|
44
44
|
_hlTimer = setTimeout(() => {
|
|
45
45
|
_hlTimer = null;
|
|
46
46
|
if (typeof hljs !== 'undefined') {
|
|
47
|
-
document.
|
|
47
|
+
const root = document.querySelector('.message-list') || document;
|
|
48
|
+
root.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
48
49
|
hljs.highlightElement(block);
|
|
49
50
|
block.dataset.highlighted = 'true';
|
|
50
51
|
});
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
// ── History batch building & background conversation routing ──────────────────
|
|
2
2
|
import { isContextSummary } from './messageHelpers.js';
|
|
3
3
|
|
|
4
|
+
function findLast(arr, predicate) {
|
|
5
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
6
|
+
if (predicate(arr[i])) return arr[i];
|
|
7
|
+
}
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
/**
|
|
5
12
|
* Convert a history array (from conversation_resumed) into a batch of UI messages.
|
|
6
13
|
* @param {Array} history - Array of {role, content, ...} from the agent
|
|
@@ -133,11 +140,14 @@ export function routeToBackgroundConversation(deps, convId, msg) {
|
|
|
133
140
|
const msgs = cache.messages;
|
|
134
141
|
const last = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
135
142
|
if (last && last.role === 'assistant' && last.isStreaming) {
|
|
136
|
-
last.
|
|
143
|
+
if (!last._chunks) last._chunks = [last.content];
|
|
144
|
+
last._chunks.push(data.delta);
|
|
145
|
+
last.content = last._chunks.join('');
|
|
137
146
|
} else {
|
|
138
147
|
msgs.push({
|
|
139
148
|
id: ++cache.messageIdCounter, role: 'assistant',
|
|
140
149
|
content: data.delta, isStreaming: true, timestamp: new Date(),
|
|
150
|
+
_chunks: [data.delta],
|
|
141
151
|
});
|
|
142
152
|
}
|
|
143
153
|
} else if (data.type === 'tool_use' && data.tools) {
|
|
@@ -206,7 +216,7 @@ export function routeToBackgroundConversation(deps, convId, msg) {
|
|
|
206
216
|
});
|
|
207
217
|
} else if (msg.status === 'completed') {
|
|
208
218
|
cache.isCompacting = false;
|
|
209
|
-
const startMsg =
|
|
219
|
+
const startMsg = findLast(cache.messages, m => m.isCompactStart && !m.compactDone);
|
|
210
220
|
if (startMsg) {
|
|
211
221
|
startMsg.content = 'Context compacted';
|
|
212
222
|
startMsg.compactDone = true;
|
|
@@ -7,6 +7,13 @@ const MAX_RECONNECT_ATTEMPTS = 50;
|
|
|
7
7
|
const RECONNECT_BASE_DELAY = 1000;
|
|
8
8
|
const RECONNECT_MAX_DELAY = 15000;
|
|
9
9
|
|
|
10
|
+
function findLast(arr, predicate) {
|
|
11
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
12
|
+
if (predicate(arr[i])) return arr[i];
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
10
17
|
/**
|
|
11
18
|
* Creates the WebSocket connection controller.
|
|
12
19
|
* @param {object} deps - All reactive state and callbacks needed
|
|
@@ -469,7 +476,7 @@ export function createConnection(deps) {
|
|
|
469
476
|
} else if (msg.status === 'completed') {
|
|
470
477
|
isCompacting.value = false;
|
|
471
478
|
// Update the start message to show completed
|
|
472
|
-
const startMsg =
|
|
479
|
+
const startMsg = findLast(messages.value, m => m.isCompactStart && !m.compactDone);
|
|
473
480
|
if (startMsg) {
|
|
474
481
|
startMsg.content = t('system.contextCompacted');
|
|
475
482
|
startMsg.compactDone = true;
|
|
@@ -4,6 +4,13 @@ import { renderMarkdown } from './markdown.js';
|
|
|
4
4
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
5
5
|
const CONTEXT_SUMMARY_PREFIX = 'This session is being continued from a previous conversation';
|
|
6
6
|
|
|
7
|
+
function parseToolInput(msg) {
|
|
8
|
+
if (msg._parsedInput !== undefined) return msg._parsedInput;
|
|
9
|
+
try { msg._parsedInput = JSON.parse(msg.toolInput); }
|
|
10
|
+
catch { msg._parsedInput = null; }
|
|
11
|
+
return msg._parsedInput;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
export function isContextSummary(text) {
|
|
8
15
|
return typeof text === 'string' && text.trimStart().startsWith(CONTEXT_SUMMARY_PREFIX);
|
|
9
16
|
}
|
|
@@ -28,6 +35,10 @@ export function formatTimestamp(ts) {
|
|
|
28
35
|
|
|
29
36
|
export function getRenderedContent(msg) {
|
|
30
37
|
if (msg.role !== 'assistant' && !msg.isCommandOutput) return msg.content;
|
|
38
|
+
if (msg.isStreaming) {
|
|
39
|
+
const t = msg.content || '';
|
|
40
|
+
return t.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
|
41
|
+
}
|
|
31
42
|
return renderMarkdown(msg.content);
|
|
32
43
|
}
|
|
33
44
|
|
|
@@ -55,9 +66,9 @@ export function toggleTool(msg) {
|
|
|
55
66
|
|
|
56
67
|
export function getToolSummary(msg, t) {
|
|
57
68
|
const name = msg.toolName;
|
|
58
|
-
const
|
|
69
|
+
const obj = parseToolInput(msg);
|
|
70
|
+
if (!obj) return '';
|
|
59
71
|
try {
|
|
60
|
-
const obj = JSON.parse(input);
|
|
61
72
|
if (name === 'Read' && obj.file_path) return obj.file_path;
|
|
62
73
|
if (name === 'Edit' && obj.file_path) return obj.file_path;
|
|
63
74
|
if (name === 'Write' && obj.file_path) return obj.file_path;
|
|
@@ -83,8 +94,9 @@ export function isEditTool(msg) {
|
|
|
83
94
|
|
|
84
95
|
export function getFormattedToolInput(msg, t) {
|
|
85
96
|
if (!msg.toolInput) return null;
|
|
97
|
+
const obj = parseToolInput(msg);
|
|
98
|
+
if (!obj) return null;
|
|
86
99
|
try {
|
|
87
|
-
const obj = JSON.parse(msg.toolInput);
|
|
88
100
|
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
89
101
|
const name = msg.toolName;
|
|
90
102
|
|
|
@@ -169,8 +181,9 @@ export function getFormattedToolInput(msg, t) {
|
|
|
169
181
|
}
|
|
170
182
|
|
|
171
183
|
export function getEditDiffHtml(msg, t) {
|
|
184
|
+
const obj = parseToolInput(msg);
|
|
185
|
+
if (!obj) return null;
|
|
172
186
|
try {
|
|
173
|
-
const obj = JSON.parse(msg.toolInput);
|
|
174
187
|
if (!obj.old_string && !obj.new_string) return null;
|
|
175
188
|
const esc = s => s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
176
189
|
const filePath = obj.file_path || '';
|
package/web/modules/streaming.js
CHANGED
|
@@ -34,19 +34,20 @@ export function createStreaming({ messages, scrollToBottom }) {
|
|
|
34
34
|
? messages.value.find(m => m.id === streamingMessageId)
|
|
35
35
|
: null;
|
|
36
36
|
|
|
37
|
+
const chunk = pendingText.slice(0, CHARS_PER_TICK);
|
|
38
|
+
pendingText = pendingText.slice(CHARS_PER_TICK);
|
|
39
|
+
|
|
37
40
|
if (!streamMsg) {
|
|
38
41
|
const id = ++messageIdCounter;
|
|
39
|
-
const chunk = pendingText.slice(0, CHARS_PER_TICK);
|
|
40
|
-
pendingText = pendingText.slice(CHARS_PER_TICK);
|
|
41
42
|
messages.value.push({
|
|
42
43
|
id, role: 'assistant', content: chunk,
|
|
43
44
|
isStreaming: true, timestamp: new Date(),
|
|
45
|
+
_chunks: [chunk],
|
|
44
46
|
});
|
|
45
47
|
streamingMessageId = id;
|
|
46
48
|
} else {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
streamMsg.content += chunk;
|
|
49
|
+
streamMsg._chunks.push(chunk);
|
|
50
|
+
streamMsg.content = streamMsg._chunks.join('');
|
|
50
51
|
}
|
|
51
52
|
scrollToBottom();
|
|
52
53
|
if (pendingText) revealTimer = setTimeout(revealTick, TICK_MS);
|
|
@@ -58,12 +59,15 @@ export function createStreaming({ messages, scrollToBottom }) {
|
|
|
58
59
|
const streamMsg = streamingMessageId !== null
|
|
59
60
|
? messages.value.find(m => m.id === streamingMessageId) : null;
|
|
60
61
|
if (streamMsg) {
|
|
61
|
-
streamMsg.
|
|
62
|
+
if (!streamMsg._chunks) streamMsg._chunks = [streamMsg.content];
|
|
63
|
+
streamMsg._chunks.push(pendingText);
|
|
64
|
+
streamMsg.content = streamMsg._chunks.join('');
|
|
62
65
|
} else {
|
|
63
66
|
const id = ++messageIdCounter;
|
|
64
67
|
messages.value.push({
|
|
65
68
|
id, role: 'assistant', content: pendingText,
|
|
66
69
|
isStreaming: true, timestamp: new Date(),
|
|
70
|
+
_chunks: [pendingText],
|
|
67
71
|
});
|
|
68
72
|
streamingMessageId = id;
|
|
69
73
|
}
|