@agent-link/server 0.1.17 → 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 +44 -44
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';
|
|
@@ -852,6 +864,15 @@ const App = {
|
|
|
852
864
|
isCompacting.value = false;
|
|
853
865
|
} else if (msg.type === 'claude_output') {
|
|
854
866
|
handleClaudeOutput(msg);
|
|
867
|
+
} else if (msg.type === 'command_output') {
|
|
868
|
+
flushReveal();
|
|
869
|
+
finalizeStreamingMsg();
|
|
870
|
+
messages.value.push({
|
|
871
|
+
id: ++messageIdCounter, role: 'user',
|
|
872
|
+
content: msg.content, isCommandOutput: true,
|
|
873
|
+
timestamp: new Date(),
|
|
874
|
+
});
|
|
875
|
+
scrollToBottom();
|
|
855
876
|
} else if (msg.type === 'context_compaction') {
|
|
856
877
|
if (msg.status === 'started') {
|
|
857
878
|
isCompacting.value = true;
|
|
@@ -1034,44 +1055,23 @@ const App = {
|
|
|
1034
1055
|
scrollToBottom();
|
|
1035
1056
|
return;
|
|
1036
1057
|
}
|
|
1037
|
-
|
|
1038
|
-
// Command output (e.g. /cost, /context) — user message with content containing stdout/stderr tags
|
|
1039
|
-
if (data.type === 'user' && data.message && data.message.content) {
|
|
1040
|
-
const raw = typeof data.message.content === 'string'
|
|
1041
|
-
? data.message.content
|
|
1042
|
-
: Array.isArray(data.message.content)
|
|
1043
|
-
? data.message.content.filter(b => b.type === 'text').map(b => b.text || '').join('')
|
|
1044
|
-
: '';
|
|
1045
|
-
if (raw.trim()) {
|
|
1046
|
-
const stdoutMatch = raw.match(/<local-command-stdout>([\s\S]*?)<\/local-command-stdout>/);
|
|
1047
|
-
const stderrMatch = raw.match(/<local-command-stderr>([\s\S]*?)<\/local-command-stderr>/);
|
|
1048
|
-
const cmdOutput = (stdoutMatch && stdoutMatch[1].trim()) || (stderrMatch && stderrMatch[1].trim());
|
|
1049
|
-
if (cmdOutput) {
|
|
1050
|
-
flushReveal();
|
|
1051
|
-
finalizeStreamingMsg();
|
|
1052
|
-
messages.value.push({
|
|
1053
|
-
id: ++messageIdCounter, role: 'user',
|
|
1054
|
-
content: cmdOutput, isCommandOutput: true,
|
|
1055
|
-
timestamp: new Date(),
|
|
1056
|
-
});
|
|
1057
|
-
scrollToBottom();
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
1058
|
}
|
|
1063
1059
|
|
|
1064
|
-
// Apply syntax highlighting after DOM updates
|
|
1065
|
-
|
|
1066
|
-
|
|
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;
|
|
1067
1066
|
if (typeof hljs !== 'undefined') {
|
|
1068
1067
|
document.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
1069
1068
|
hljs.highlightElement(block);
|
|
1070
1069
|
block.dataset.highlighted = 'true';
|
|
1071
1070
|
});
|
|
1072
1071
|
}
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1072
|
+
}, 300);
|
|
1073
|
+
}
|
|
1074
|
+
watch(messages, () => { nextTick(scheduleHighlight); }, { deep: true });
|
|
1075
1075
|
|
|
1076
1076
|
onMounted(() => { connect(); });
|
|
1077
1077
|
onUnmounted(() => { if (ws) ws.close(); });
|
|
@@ -1208,7 +1208,7 @@ const App = {
|
|
|
1208
1208
|
<span>Loading conversation history...</span>
|
|
1209
1209
|
</div>
|
|
1210
1210
|
|
|
1211
|
-
<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]">
|
|
1212
1212
|
|
|
1213
1213
|
<!-- User message -->
|
|
1214
1214
|
<template v-if="msg.role === 'user'">
|
|
@@ -1230,7 +1230,7 @@ const App = {
|
|
|
1230
1230
|
|
|
1231
1231
|
<!-- Assistant message (markdown) -->
|
|
1232
1232
|
<template v-else-if="msg.role === 'assistant'">
|
|
1233
|
-
<div v-if="!isPrevAssistant(
|
|
1233
|
+
<div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">Claude</div>
|
|
1234
1234
|
<div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]">
|
|
1235
1235
|
<div class="message-actions">
|
|
1236
1236
|
<button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? 'Copied!' : 'Copy'">
|