@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/web/app.js +35 -19
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-link/server",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "AgentLink relay server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
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
- let html = marked.parse(text);
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
- return html;
44
+ } else {
45
+ html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
40
46
  }
41
- } catch {}
42
- // Fallback: escape HTML
43
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
47
+ } catch {
48
+ html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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 = 3;
277
- const TICK_MS = 12;
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
- nextTick(() => {
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(msg) {
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
- watch(messages, () => {
1050
- nextTick(() => {
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
- }, { deep: true });
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(msg)" class="message-role-label assistant-label">Claude</div>
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'">