dsh-composer-rewind 2.0.0 → 2.0.2

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 (3) hide show
  1. package/README.md +2 -1
  2. package/client.js +41 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,6 +27,7 @@ Composer Rewind 是 DeepSeek Harness Web UI 的输入框状态历史插件。它
27
27
  - 停止输入约 **450ms** 后更新“当前草稿”;它始终只有一条,原地覆盖。
28
28
  - **发送、清空输入框、切换会话、关闭或刷新页面**时,当前草稿进入历史。
29
29
  - 10 分钟内同一草稿的前缀修改、回退会合并为一条,保留更完整的版本。
30
+ - 再次发送正文和附件都相同的记录时,不新增条目,而是把原条目刷新到列表最前面;附件按文件名、类型和字节大小判断是否同一文件。
30
31
  - 去空白后少于 3 个字符、且没有附件的内容会被忽略。
31
32
  - 最多保留 **300 条**,超出后丢弃最旧的一条。
32
33
 
@@ -37,7 +38,7 @@ Composer Rewind 是 DeepSeek Harness Web UI 的输入框状态历史插件。它
37
38
  - 顶部搜索框可按文字、附件名或配置筛选。
38
39
  - 记录按“当前草稿 / 今天 / 昨天 / 更早”分组。
39
40
  - `↑` / `↓` 选择,`Enter` 还原,`Esc` 关闭。
40
- - 鼠标悬停可查看完整文字、附件与当时的配置。
41
+ - 鼠标悬停可查看完整文字、附件与当时的配置;悬浮卡会自动避开屏幕边缘。
41
42
  - 每条历史记录支持还原、复制、删除;“清空”会删除全部历史与附件缓存。
42
43
  - 在输入框中按 `Ctrl+Alt+Z` 可直接回溯插入上一条,连续按下继续往前。
43
44
 
package/client.js CHANGED
@@ -351,8 +351,18 @@ window.__ModuleLoader__.load({
351
351
  return clean;
352
352
  }
353
353
 
354
+ /**
355
+ * Identity of one attachment for de-duplication. The runtime id changes
356
+ * every time the same file is uploaded again, so it cannot be part of the
357
+ * signature; name + byte size + kind is stable across uploads. The id stays
358
+ * on the record for restore and blob lookup.
359
+ */
360
+ function attachmentFingerprint(item) {
361
+ return [item.kind || 'file', item.name || 'file', item.size || 0].join('');
362
+ }
363
+
354
364
  function attachmentKeys(attachments) {
355
- return (attachments || []).map((item) => item.key).join('|');
365
+ return (attachments || []).map(attachmentFingerprint).join('|');
356
366
  }
357
367
 
358
368
  function sameAttachmentKeys(left, right) {
@@ -435,7 +445,6 @@ window.__ModuleLoader__.load({
435
445
 
436
446
  let entries = loadEntries();
437
447
  let live = loadLive();
438
- let lastRetiredSignature = '';
439
448
  const listeners = new Set();
440
449
 
441
450
  function emit() {
@@ -499,8 +508,6 @@ window.__ModuleLoader__.load({
499
508
  if (attachments.length === 0 && isJunk(text)) return false;
500
509
 
501
510
  const signature = entrySignature(text, attachments);
502
- if (lastRetiredSignature === signature) return false;
503
-
504
511
  const now = Date.now();
505
512
  const fresh = {
506
513
  id: uid(),
@@ -520,9 +527,9 @@ window.__ModuleLoader__.load({
520
527
  at: now,
521
528
  references: fresh.references,
522
529
  command: fresh.command,
530
+ attachments: fresh.attachments,
523
531
  settings: fresh.settings || head.settings,
524
532
  };
525
- lastRetiredSignature = signature;
526
533
  commitEntries([refreshed, ...entries.slice(1)]);
527
534
  return true;
528
535
  }
@@ -532,19 +539,16 @@ window.__ModuleLoader__.load({
532
539
  // Typing more of the same draft: replace the head so references,
533
540
  // attachments and setup all describe the newest revision.
534
541
  if (sameAttachments && text.startsWith(head.text)) {
535
- lastRetiredSignature = signature;
536
542
  commitEntries([{ ...fresh, id: head.id }, ...entries.slice(1)]);
537
543
  return true;
538
544
  }
539
545
  // Backspacing inside a draft we already hold: keep the fuller head.
540
546
  if (sameAttachments && head.text.startsWith(text)) {
541
- lastRetiredSignature = signature;
542
547
  commitEntries([{ ...head, at: now, settings: fresh.settings || head.settings }, ...entries.slice(1)]);
543
548
  return true;
544
549
  }
545
550
  }
546
551
 
547
- lastRetiredSignature = signature;
548
552
  commitEntries([
549
553
  fresh,
550
554
  ...entries.filter((item) => entrySignature(item.text, item.attachments) !== signature),
@@ -582,7 +586,6 @@ window.__ModuleLoader__.load({
582
586
  }
583
587
 
584
588
  function clearAll() {
585
- lastRetiredSignature = '';
586
589
  commitEntries([]);
587
590
  }
588
591
 
@@ -1867,7 +1870,6 @@ window.__ModuleLoader__.load({
1867
1870
  {
1868
1871
  className: 'pv-thumb',
1869
1872
  style,
1870
- title: label,
1871
1873
  'data-pending': pending ? '1' : '0',
1872
1874
  'data-missing': missing ? '1' : '0',
1873
1875
  },
@@ -1916,7 +1918,7 @@ window.__ModuleLoader__.load({
1916
1918
  entry.live && !entry.isCurrent ? h('span', { className: 'pv-live' }, t('draft')) : null,
1917
1919
  h(
1918
1920
  'span',
1919
- { className: 'pv-text', title: text || t('attachmentsOnly') },
1921
+ { className: 'pv-text' },
1920
1922
  text || h('span', { className: 'pv-muted' }, t('attachmentsOnly')),
1921
1923
  ),
1922
1924
  h(AttachmentStrip, { attachments, size: 20, limit: 3 }),
@@ -1934,7 +1936,6 @@ window.__ModuleLoader__.load({
1934
1936
  {
1935
1937
  type: 'button',
1936
1938
  className: 'pv-act',
1937
- title: t('insert'),
1938
1939
  'aria-label': t('insert'),
1939
1940
  disabled: busy,
1940
1941
  onClick: (event) => {
@@ -1949,7 +1950,6 @@ window.__ModuleLoader__.load({
1949
1950
  {
1950
1951
  type: 'button',
1951
1952
  className: 'pv-act',
1952
- title: t('copy'),
1953
1953
  'aria-label': t('copy'),
1954
1954
  onClick: (event) => {
1955
1955
  event.stopPropagation();
@@ -1966,7 +1966,6 @@ window.__ModuleLoader__.load({
1966
1966
  type: 'button',
1967
1967
  className: 'pv-act',
1968
1968
  'data-tone': 'danger',
1969
- title: t('remove'),
1970
1969
  'aria-label': t('remove'),
1971
1970
  onClick: (event) => {
1972
1971
  event.stopPropagation();
@@ -1981,22 +1980,25 @@ window.__ModuleLoader__.load({
1981
1980
 
1982
1981
  function HoverCard(props) {
1983
1982
  const { entry, rect, scheme } = props;
1984
- const width = 430;
1985
- const margin = 12;
1986
- let left = rect.right + 14;
1987
- if (left + width > window.innerWidth - margin) left = Math.max(margin, rect.left - width - 14);
1988
- let top = Math.max(margin, rect.top);
1989
- const estimated = 320;
1990
- if (top + estimated > window.innerHeight - margin) {
1991
- top = Math.max(margin, window.innerHeight - estimated - margin);
1992
- }
1983
+ const cardRef = useRef(null);
1984
+ const [pos, setPos] = useState(null);
1985
+ useEffect(() => {
1986
+ const node = cardRef.current;
1987
+ if (!node || !node.offsetWidth) return;
1988
+ const placed = placeHoverCard(rect, node.offsetWidth, node.offsetHeight);
1989
+ setPos({ key: entry.id, left: placed.left, top: placed.top });
1990
+ }, [entry.id, rect]);
1993
1991
  const settings = entry.settings;
1994
1992
  return h(
1995
1993
  'div',
1996
1994
  {
1997
1995
  className: 'pv-hover',
1998
1996
  'data-scheme': scheme,
1999
- style: { left: left + 'px', top: top + 'px' },
1997
+ ref: cardRef,
1998
+ style:
1999
+ pos && pos.key === entry.id
2000
+ ? { left: pos.left + 'px', top: pos.top + 'px' }
2001
+ : { left: '0px', top: '0px', visibility: 'hidden' },
2000
2002
  onMouseDown: (event) => event.stopPropagation(),
2001
2003
  onMouseEnter: props.onMouseEnter,
2002
2004
  onMouseLeave: props.onMouseLeave,
@@ -2020,7 +2022,7 @@ window.__ModuleLoader__.load({
2020
2022
  'div',
2021
2023
  { key: attachment.key || index, className: 'pv-hover-row' },
2022
2024
  h(AttachmentThumb, { attachment, size: 26 }),
2023
- h('span', { className: 'pv-hover-name', title: attachment.name }, attachment.name || 'file'),
2025
+ h('span', { className: 'pv-hover-name' }, attachment.name || 'file'),
2024
2026
  h(
2025
2027
  'span',
2026
2028
  { className: 'pv-hover-size' },
@@ -2202,7 +2204,19 @@ window.__ModuleLoader__.load({
2202
2204
  ? Math.max(12, Math.min(rect.left, window.innerWidth - width - 12))
2203
2205
  : Math.max(12, (window.innerWidth - width) / 2);
2204
2206
  const bottom = rect ? Math.max(12, window.innerHeight - rect.top + 6) : 96;
2205
- return { left, width, bottom, maxHeight: Math.max(220, Math.min(520, bottom - 16)) };
2207
+ const above = rect ? rect.top - 24 : window.innerHeight - 140;
2208
+ return { left, width, bottom, maxHeight: Math.max(160, Math.min(320, above)) };
2209
+ }
2210
+
2211
+ function placeHoverCard(rect, width, height) {
2212
+ const margin = 10;
2213
+ let left = rect.right + 14;
2214
+ if (left + width > window.innerWidth - margin) left = rect.left - width - 14;
2215
+ left = Math.max(margin, Math.min(left, window.innerWidth - width - margin));
2216
+ let top = rect.top;
2217
+ if (top + height > window.innerHeight - margin) top = rect.bottom - height;
2218
+ top = Math.max(margin, Math.min(top, window.innerHeight - height - margin));
2219
+ return { left: left, top: top };
2206
2220
  }
2207
2221
 
2208
2222
  function dayBucket(at) {
@@ -2714,10 +2728,10 @@ window.__ModuleLoader__.load({
2714
2728
  formatTime,
2715
2729
  permissionLabel,
2716
2730
  presetLabel,
2731
+ placeHoverCard,
2717
2732
  reset: () => {
2718
2733
  entries = [];
2719
2734
  live = null;
2720
- lastRetiredSignature = '';
2721
2735
  recall.index = -1;
2722
2736
  recall.at = 0;
2723
2737
  recall.text = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-composer-rewind",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "type": "module",
5
5
  "description": "Composer input history for the DeepSeek Harness Web UI: records composer contents, attachments, command tokens and the active DSH setup, then restores the whole composer state.",
6
6
  "keywords": [