@agent-link/server 0.1.18 → 0.1.19
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 +35 -19
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -15,11 +15,16 @@ if (typeof marked !== 'undefined') {
|
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const _mdCache = new Map();
|
|
19
|
+
|
|
18
20
|
function renderMarkdown(text) {
|
|
19
21
|
if (!text) return '';
|
|
22
|
+
const cached = _mdCache.get(text);
|
|
23
|
+
if (cached) return cached;
|
|
24
|
+
let html;
|
|
20
25
|
try {
|
|
21
26
|
if (typeof marked !== 'undefined') {
|
|
22
|
-
|
|
27
|
+
html = marked.parse(text);
|
|
23
28
|
// Add copy buttons to code blocks
|
|
24
29
|
html = html.replace(/<pre><code([^>]*)>([\s\S]*?)<\/code><\/pre>/g,
|
|
25
30
|
(match, attrs, code) => {
|
|
@@ -36,11 +41,16 @@ function renderMarkdown(text) {
|
|
|
36
41
|
</div>`;
|
|
37
42
|
}
|
|
38
43
|
);
|
|
39
|
-
|
|
44
|
+
} else {
|
|
45
|
+
html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
40
46
|
}
|
|
41
|
-
} catch {
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
} catch {
|
|
48
|
+
html = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
49
|
+
}
|
|
50
|
+
// Only cache completed (non-streaming) messages; streaming text changes every tick
|
|
51
|
+
if (_mdCache.size > 500) _mdCache.clear();
|
|
52
|
+
_mdCache.set(text, html);
|
|
53
|
+
return html;
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
// Global code copy handler
|
|
@@ -273,8 +283,8 @@ const App = {
|
|
|
273
283
|
// Progressive text reveal state
|
|
274
284
|
let pendingText = '';
|
|
275
285
|
let revealTimer = null;
|
|
276
|
-
const CHARS_PER_TICK =
|
|
277
|
-
const TICK_MS =
|
|
286
|
+
const CHARS_PER_TICK = 5;
|
|
287
|
+
const TICK_MS = 16;
|
|
278
288
|
|
|
279
289
|
function startReveal() {
|
|
280
290
|
if (revealTimer !== null) return;
|
|
@@ -336,11 +346,14 @@ const App = {
|
|
|
336
346
|
return match ? match[1] : null;
|
|
337
347
|
}
|
|
338
348
|
|
|
349
|
+
let _scrollTimer = null;
|
|
339
350
|
function scrollToBottom() {
|
|
340
|
-
|
|
351
|
+
if (_scrollTimer) return;
|
|
352
|
+
_scrollTimer = setTimeout(() => {
|
|
353
|
+
_scrollTimer = null;
|
|
341
354
|
const el = document.querySelector('.message-list');
|
|
342
355
|
if (el) el.scrollTop = el.scrollHeight;
|
|
343
|
-
});
|
|
356
|
+
}, 50);
|
|
344
357
|
}
|
|
345
358
|
|
|
346
359
|
// ── Auto-resize textarea ──
|
|
@@ -407,7 +420,7 @@ const App = {
|
|
|
407
420
|
|
|
408
421
|
// ── Rendered markdown for assistant messages ──
|
|
409
422
|
function getRenderedContent(msg) {
|
|
410
|
-
if (msg.role !== 'assistant') return msg.content;
|
|
423
|
+
if (msg.role !== 'assistant' && !msg.isCommandOutput) return msg.content;
|
|
411
424
|
return renderMarkdown(msg.content);
|
|
412
425
|
}
|
|
413
426
|
|
|
@@ -421,8 +434,7 @@ const App = {
|
|
|
421
434
|
}
|
|
422
435
|
|
|
423
436
|
// ── Check if previous message is also assistant (to suppress repeated label) ──
|
|
424
|
-
function isPrevAssistant(
|
|
425
|
-
const idx = messages.value.indexOf(msg);
|
|
437
|
+
function isPrevAssistant(idx) {
|
|
426
438
|
if (idx <= 0) return false;
|
|
427
439
|
const prev = messages.value[idx - 1];
|
|
428
440
|
return prev.role === 'assistant' || prev.role === 'tool';
|
|
@@ -1045,17 +1057,21 @@ const App = {
|
|
|
1045
1057
|
}
|
|
1046
1058
|
}
|
|
1047
1059
|
|
|
1048
|
-
// Apply syntax highlighting after DOM updates
|
|
1049
|
-
|
|
1050
|
-
|
|
1060
|
+
// Apply syntax highlighting after DOM updates (throttled)
|
|
1061
|
+
let _hlTimer = null;
|
|
1062
|
+
function scheduleHighlight() {
|
|
1063
|
+
if (_hlTimer) return;
|
|
1064
|
+
_hlTimer = setTimeout(() => {
|
|
1065
|
+
_hlTimer = null;
|
|
1051
1066
|
if (typeof hljs !== 'undefined') {
|
|
1052
1067
|
document.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
1053
1068
|
hljs.highlightElement(block);
|
|
1054
1069
|
block.dataset.highlighted = 'true';
|
|
1055
1070
|
});
|
|
1056
1071
|
}
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1072
|
+
}, 300);
|
|
1073
|
+
}
|
|
1074
|
+
watch(messages, () => { nextTick(scheduleHighlight); }, { deep: true });
|
|
1059
1075
|
|
|
1060
1076
|
onMounted(() => { connect(); });
|
|
1061
1077
|
onUnmounted(() => { if (ws) ws.close(); });
|
|
@@ -1192,7 +1208,7 @@ const App = {
|
|
|
1192
1208
|
<span>Loading conversation history...</span>
|
|
1193
1209
|
</div>
|
|
1194
1210
|
|
|
1195
|
-
<div v-for="msg in messages" :key="msg.id" :class="['message', 'message-' + msg.role]">
|
|
1211
|
+
<div v-for="(msg, msgIdx) in messages" :key="msg.id" :class="['message', 'message-' + msg.role]">
|
|
1196
1212
|
|
|
1197
1213
|
<!-- User message -->
|
|
1198
1214
|
<template v-if="msg.role === 'user'">
|
|
@@ -1214,7 +1230,7 @@ const App = {
|
|
|
1214
1230
|
|
|
1215
1231
|
<!-- Assistant message (markdown) -->
|
|
1216
1232
|
<template v-else-if="msg.role === 'assistant'">
|
|
1217
|
-
<div v-if="!isPrevAssistant(
|
|
1233
|
+
<div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">Claude</div>
|
|
1218
1234
|
<div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]">
|
|
1219
1235
|
<div class="message-actions">
|
|
1220
1236
|
<button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? 'Copied!' : 'Copy'">
|