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.
- package/README.md +2 -1
- package/client.js +41 -27
- 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(
|
|
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'
|
|
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
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
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
|
-
|
|
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'
|
|
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
|
-
|
|
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.
|
|
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": [
|