@wendongfly/myhi 1.3.53 → 1.3.54

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/dist/chat.html CHANGED
@@ -664,7 +664,12 @@
664
664
  let outputBuffer = '';
665
665
  let outputTimer = null;
666
666
  const OUTPUT_DELAY = 600; // 输出合并延迟(ms),越大越少DOM操作
667
- const MAX_MESSAGES = 150; // 聊天区最大消息数
667
+ const MAX_MESSAGES = 500; // 聊天区最大消息数;用户主动向上翻历史时临时禁用 trim
668
+ // 历史分页加载(jsonl 是真相源;500 内存上限和 150 DOM 上限都不够长会话回顾)
669
+ let _historyOffset = 0; // 已加载的最老消息距最新的偏移(含初次 agent:history 回放的)
670
+ let _historyHasMore = true; // 后端还有更老消息
671
+ let _historyLoading = false; // 防抖锁
672
+ let _userScrolledUp = false; // 用户已主动往上翻 → 关掉 trim 防被自动删
668
673
 
669
674
  // 命令历史
670
675
  const cmdHistory = [];
@@ -1442,6 +1447,11 @@
1442
1447
  chatArea.innerHTML = '';
1443
1448
  endStream(); endToolGroup(); removeThinking();
1444
1449
 
1450
+ // 重置历史分页状态:从最新一批开始算偏移
1451
+ _historyOffset = history.length;
1452
+ _historyHasMore = true;
1453
+ _userScrolledUp = false;
1454
+
1445
1455
  for (const msg of history) {
1446
1456
  endStream(); // 每条历史消息都是独立的,不要流式合并
1447
1457
  if (msg.type === 'user' && msg.content) {
@@ -1489,6 +1499,96 @@
1489
1499
  scrollToBottom();
1490
1500
  });
1491
1501
 
1502
+ // ── 历史分页:向上滚动到顶部触发加载更老批次 ──
1503
+ async function loadOlderHistory() {
1504
+ if (_historyLoading || !_historyHasMore) return;
1505
+ _historyLoading = true;
1506
+ // 顶部插入"加载中"提示
1507
+ const indicator = document.createElement('div');
1508
+ indicator.className = 'msg msg-status';
1509
+ indicator.id = '_hist_loading_indicator';
1510
+ indicator.textContent = '— 加载更早的历史… —';
1511
+ chatArea.insertBefore(indicator, chatArea.firstChild);
1512
+ // 记录滚动锚点(保持视觉位置)
1513
+ const prevScrollHeight = chatArea.scrollHeight;
1514
+ const prevScrollTop = chatArea.scrollTop;
1515
+ try {
1516
+ const r = await fetch(`/api/agent/history/${SESSION_ID}?offset=${_historyOffset}&limit=50`);
1517
+ const d = await r.json();
1518
+ indicator.remove();
1519
+ if (!d.ok || !d.messages?.length) {
1520
+ _historyHasMore = false;
1521
+ const end = document.createElement('div');
1522
+ end.className = 'msg msg-status';
1523
+ end.textContent = '— 已到最早 —';
1524
+ chatArea.insertBefore(end, chatArea.firstChild);
1525
+ return;
1526
+ }
1527
+ // 准备 fragment,按 history 渲染逻辑创建 DOM,然后整体 prepend
1528
+ const frag = document.createDocumentFragment();
1529
+ for (const msg of d.messages) {
1530
+ if (msg.type === 'user' && msg.content) {
1531
+ const el = document.createElement('div');
1532
+ el.className = 'msg msg-input';
1533
+ el.innerHTML = `<div><div class="bubble">${escHtml(msg.content)}</div><div class="meta">${msg.timestamp ? new Date(msg.timestamp).toLocaleString('zh-CN') : ''}</div></div>`;
1534
+ frag.appendChild(el);
1535
+ } else if (msg.type === 'assistant' && msg.message?.content) {
1536
+ const texts = [], tools = [];
1537
+ for (const block of msg.message.content) {
1538
+ if (block.type === 'text' && block.text) texts.push(block.text);
1539
+ else if (block.type === 'tool_use') tools.push({ name: block.name || '工具', input: block.input || {} });
1540
+ }
1541
+ if (texts.length) {
1542
+ const el = document.createElement('div');
1543
+ el.className = 'msg msg-assistant';
1544
+ const content = document.createElement('div');
1545
+ content.className = 'content';
1546
+ content.innerHTML = renderMarkdown(texts.join('\n\n'));
1547
+ el.appendChild(content);
1548
+ frag.appendChild(el);
1549
+ }
1550
+ // 历史里工具卡简化为状态行:用户翻历史主要看对话文本,不需要交互
1551
+ for (const t of tools) {
1552
+ const el = document.createElement('div');
1553
+ el.className = 'msg msg-status';
1554
+ el.textContent = '— 🔧 ' + t.name + ' —';
1555
+ frag.appendChild(el);
1556
+ }
1557
+ } else if (msg.type === 'result') {
1558
+ const el = document.createElement('div');
1559
+ el.className = 'msg msg-status';
1560
+ const cost = msg.total_cost_usd ? ` ($${msg.total_cost_usd.toFixed(4)})` : '';
1561
+ el.textContent = '— 完成' + cost + ' —';
1562
+ frag.appendChild(el);
1563
+ }
1564
+ }
1565
+ chatArea.insertBefore(frag, chatArea.firstChild);
1566
+ _historyOffset += d.messages.length;
1567
+ _historyHasMore = d.hasMore;
1568
+ // 保持滚动位置:新内容在顶部插入后,scrollTop 加上新增高度
1569
+ chatArea.scrollTop = prevScrollTop + (chatArea.scrollHeight - prevScrollHeight);
1570
+ } catch (e) {
1571
+ indicator.textContent = '— 加载失败: ' + e.message + ' —';
1572
+ setTimeout(() => indicator.remove(), 3000);
1573
+ } finally {
1574
+ _historyLoading = false;
1575
+ }
1576
+ }
1577
+
1578
+ chatArea.addEventListener('scroll', () => {
1579
+ // 用户主动往上翻 → 禁用自动 trim,避免被删
1580
+ if (chatArea.scrollTop < chatArea.scrollHeight - chatArea.clientHeight - 100) {
1581
+ _userScrolledUp = true;
1582
+ } else if (chatArea.scrollTop > chatArea.scrollHeight - chatArea.clientHeight - 20) {
1583
+ // 回到接近底部 → 恢复 trim
1584
+ _userScrolledUp = false;
1585
+ }
1586
+ // 接近顶部 100px → 拉更老历史
1587
+ if (chatArea.scrollTop < 100 && _historyHasMore && !_historyLoading) {
1588
+ loadOlderHistory();
1589
+ }
1590
+ });
1591
+
1492
1592
  socket.on('agent:busy', (busy) => {
1493
1593
  if (busy) { showThinking(); setWorkState('thinking'); }
1494
1594
  else { removeThinking(); setWorkState('idle'); }
@@ -1952,6 +2052,7 @@
1952
2052
  window.goBack = function() { window.location.href = '/'; };
1953
2053
  function scrollToBottom() { requestAnimationFrame(() => { chatArea.scrollTop = chatArea.scrollHeight; }); }
1954
2054
  function trimMessages() {
2055
+ if (_userScrolledUp) return; // 用户在往上翻历史,禁用自动删
1955
2056
  const msgs = chatArea.querySelectorAll('.msg');
1956
2057
  if (msgs.length > MAX_MESSAGES) {
1957
2058
  const remove = msgs.length - MAX_MESSAGES;