@agent-link/server 0.1.18 → 0.1.20
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 +55 -22
- package/web/style.css +24 -0
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
|
|
@@ -102,6 +112,15 @@ const App = {
|
|
|
102
112
|
const sessionId = ref('');
|
|
103
113
|
const error = ref('');
|
|
104
114
|
const messages = ref([]);
|
|
115
|
+
const visibleLimit = ref(50);
|
|
116
|
+
const hasMoreMessages = computed(() => messages.value.length > visibleLimit.value);
|
|
117
|
+
const visibleMessages = computed(() => {
|
|
118
|
+
if (messages.value.length <= visibleLimit.value) return messages.value;
|
|
119
|
+
return messages.value.slice(messages.value.length - visibleLimit.value);
|
|
120
|
+
});
|
|
121
|
+
function loadMoreMessages() {
|
|
122
|
+
visibleLimit.value += 50;
|
|
123
|
+
}
|
|
105
124
|
const inputText = ref('');
|
|
106
125
|
const isProcessing = ref(false);
|
|
107
126
|
const isCompacting = ref(false);
|
|
@@ -273,8 +292,8 @@ const App = {
|
|
|
273
292
|
// Progressive text reveal state
|
|
274
293
|
let pendingText = '';
|
|
275
294
|
let revealTimer = null;
|
|
276
|
-
const CHARS_PER_TICK =
|
|
277
|
-
const TICK_MS =
|
|
295
|
+
const CHARS_PER_TICK = 5;
|
|
296
|
+
const TICK_MS = 16;
|
|
278
297
|
|
|
279
298
|
function startReveal() {
|
|
280
299
|
if (revealTimer !== null) return;
|
|
@@ -336,11 +355,14 @@ const App = {
|
|
|
336
355
|
return match ? match[1] : null;
|
|
337
356
|
}
|
|
338
357
|
|
|
358
|
+
let _scrollTimer = null;
|
|
339
359
|
function scrollToBottom() {
|
|
340
|
-
|
|
360
|
+
if (_scrollTimer) return;
|
|
361
|
+
_scrollTimer = setTimeout(() => {
|
|
362
|
+
_scrollTimer = null;
|
|
341
363
|
const el = document.querySelector('.message-list');
|
|
342
364
|
if (el) el.scrollTop = el.scrollHeight;
|
|
343
|
-
});
|
|
365
|
+
}, 50);
|
|
344
366
|
}
|
|
345
367
|
|
|
346
368
|
// ── Auto-resize textarea ──
|
|
@@ -407,7 +429,7 @@ const App = {
|
|
|
407
429
|
|
|
408
430
|
// ── Rendered markdown for assistant messages ──
|
|
409
431
|
function getRenderedContent(msg) {
|
|
410
|
-
if (msg.role !== 'assistant') return msg.content;
|
|
432
|
+
if (msg.role !== 'assistant' && !msg.isCommandOutput) return msg.content;
|
|
411
433
|
return renderMarkdown(msg.content);
|
|
412
434
|
}
|
|
413
435
|
|
|
@@ -421,11 +443,10 @@ const App = {
|
|
|
421
443
|
}
|
|
422
444
|
|
|
423
445
|
// ── Check if previous message is also assistant (to suppress repeated label) ──
|
|
424
|
-
function isPrevAssistant(
|
|
425
|
-
const idx = messages.value.indexOf(msg);
|
|
446
|
+
function isPrevAssistant(idx) {
|
|
426
447
|
if (idx <= 0) return false;
|
|
427
|
-
const prev =
|
|
428
|
-
return prev.role === 'assistant' || prev.role === 'tool';
|
|
448
|
+
const prev = visibleMessages.value[idx - 1];
|
|
449
|
+
return prev && (prev.role === 'assistant' || prev.role === 'tool');
|
|
429
450
|
}
|
|
430
451
|
|
|
431
452
|
// ── Context summary toggle ──
|
|
@@ -664,6 +685,7 @@ const App = {
|
|
|
664
685
|
if (window.innerWidth <= 768) sidebarOpen.value = false;
|
|
665
686
|
// Clear current conversation
|
|
666
687
|
messages.value = [];
|
|
688
|
+
visibleLimit.value = 50;
|
|
667
689
|
messageIdCounter = 0;
|
|
668
690
|
streamingMessageId = null;
|
|
669
691
|
pendingText = '';
|
|
@@ -685,6 +707,7 @@ const App = {
|
|
|
685
707
|
// Auto-close sidebar on mobile
|
|
686
708
|
if (window.innerWidth <= 768) sidebarOpen.value = false;
|
|
687
709
|
messages.value = [];
|
|
710
|
+
visibleLimit.value = 50;
|
|
688
711
|
messageIdCounter = 0;
|
|
689
712
|
streamingMessageId = null;
|
|
690
713
|
pendingText = '';
|
|
@@ -973,6 +996,7 @@ const App = {
|
|
|
973
996
|
} else if (msg.type === 'workdir_changed') {
|
|
974
997
|
workDir.value = msg.workDir;
|
|
975
998
|
messages.value = [];
|
|
999
|
+
visibleLimit.value = 50;
|
|
976
1000
|
messageIdCounter = 0;
|
|
977
1001
|
streamingMessageId = null;
|
|
978
1002
|
pendingText = '';
|
|
@@ -1045,24 +1069,29 @@ const App = {
|
|
|
1045
1069
|
}
|
|
1046
1070
|
}
|
|
1047
1071
|
|
|
1048
|
-
// Apply syntax highlighting after DOM updates
|
|
1049
|
-
|
|
1050
|
-
|
|
1072
|
+
// Apply syntax highlighting after DOM updates (throttled)
|
|
1073
|
+
let _hlTimer = null;
|
|
1074
|
+
function scheduleHighlight() {
|
|
1075
|
+
if (_hlTimer) return;
|
|
1076
|
+
_hlTimer = setTimeout(() => {
|
|
1077
|
+
_hlTimer = null;
|
|
1051
1078
|
if (typeof hljs !== 'undefined') {
|
|
1052
1079
|
document.querySelectorAll('pre code:not([data-highlighted])').forEach(block => {
|
|
1053
1080
|
hljs.highlightElement(block);
|
|
1054
1081
|
block.dataset.highlighted = 'true';
|
|
1055
1082
|
});
|
|
1056
1083
|
}
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1084
|
+
}, 300);
|
|
1085
|
+
}
|
|
1086
|
+
watch(messages, () => { nextTick(scheduleHighlight); }, { deep: true });
|
|
1059
1087
|
|
|
1060
1088
|
onMounted(() => { connect(); });
|
|
1061
1089
|
onUnmounted(() => { if (ws) ws.close(); });
|
|
1062
1090
|
|
|
1063
1091
|
return {
|
|
1064
1092
|
status, agentName, hostname, workDir, sessionId, error,
|
|
1065
|
-
messages,
|
|
1093
|
+
messages, visibleMessages, hasMoreMessages, loadMoreMessages,
|
|
1094
|
+
inputText, isProcessing, isCompacting, canSend, inputRef,
|
|
1066
1095
|
sendMessage, handleKeydown, cancelExecution,
|
|
1067
1096
|
getRenderedContent, copyMessage, toggleTool, isPrevAssistant, toggleContextSummary,
|
|
1068
1097
|
getToolIcon, getToolSummary, isEditTool, getEditDiffHtml, getFormattedToolInput, autoResize,
|
|
@@ -1192,7 +1221,11 @@ const App = {
|
|
|
1192
1221
|
<span>Loading conversation history...</span>
|
|
1193
1222
|
</div>
|
|
1194
1223
|
|
|
1195
|
-
<div v-
|
|
1224
|
+
<div v-if="hasMoreMessages" class="load-more-wrapper">
|
|
1225
|
+
<button class="load-more-btn" @click="loadMoreMessages">Load earlier messages</button>
|
|
1226
|
+
</div>
|
|
1227
|
+
|
|
1228
|
+
<div v-for="(msg, msgIdx) in visibleMessages" :key="msg.id" :class="['message', 'message-' + msg.role]">
|
|
1196
1229
|
|
|
1197
1230
|
<!-- User message -->
|
|
1198
1231
|
<template v-if="msg.role === 'user'">
|
|
@@ -1214,7 +1247,7 @@ const App = {
|
|
|
1214
1247
|
|
|
1215
1248
|
<!-- Assistant message (markdown) -->
|
|
1216
1249
|
<template v-else-if="msg.role === 'assistant'">
|
|
1217
|
-
<div v-if="!isPrevAssistant(
|
|
1250
|
+
<div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">Claude</div>
|
|
1218
1251
|
<div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]">
|
|
1219
1252
|
<div class="message-actions">
|
|
1220
1253
|
<button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? 'Copied!' : 'Copy'">
|
package/web/style.css
CHANGED
|
@@ -1171,6 +1171,30 @@ body {
|
|
|
1171
1171
|
animation: spin 0.8s linear infinite;
|
|
1172
1172
|
}
|
|
1173
1173
|
|
|
1174
|
+
/* ── Load more button ── */
|
|
1175
|
+
.load-more-wrapper {
|
|
1176
|
+
display: flex;
|
|
1177
|
+
justify-content: center;
|
|
1178
|
+
padding: 0.75rem 0;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
.load-more-btn {
|
|
1182
|
+
background: var(--bg-tertiary);
|
|
1183
|
+
color: var(--text-secondary);
|
|
1184
|
+
border: 1px solid var(--border);
|
|
1185
|
+
border-radius: 6px;
|
|
1186
|
+
padding: 0.4rem 1.2rem;
|
|
1187
|
+
font-size: 0.8rem;
|
|
1188
|
+
cursor: pointer;
|
|
1189
|
+
transition: background 0.15s, color 0.15s;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
.load-more-btn:hover {
|
|
1193
|
+
background: var(--accent);
|
|
1194
|
+
color: #fff;
|
|
1195
|
+
border-color: var(--accent);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1174
1198
|
/* ── Typing indicator ── */
|
|
1175
1199
|
.typing-indicator {
|
|
1176
1200
|
display: flex;
|