dsh-composer-rewind 2.0.4 → 2.1.0
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 +13 -2
- package/client.js +429 -12
- package/package.json +53 -53
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Composer Rewind 是 DeepSeek Harness Web UI 的输入框状态历史插件。它
|
|
|
8
8
|
|
|
9
9
|
- **完整记录**:输入框里的文字、图片和文件附件、@ 引用、/ 指令,以及当时的 DSH 配置。
|
|
10
10
|
- **完整恢复**:把一条历史还原成记录时的输入框,包括附件缩略图、引用与指令。
|
|
11
|
+
- **按会话归档**:历史跟着会话走,面板和 `Ctrl+Alt+Z` 只显示当前会话的记录,切换会话自动切换视图。
|
|
12
|
+
- **独立管理页**:在 Harness 设置的“输入框历史”里按会话浏览、删除、清空或导出全部记录。
|
|
11
13
|
- **不打断输入**:停顿约 450ms 才写入一次,连续打字只更新同一条草稿,不会堆出逐字记录。
|
|
12
14
|
- **快速找回**:搜索、方向键选择、回车还原;`Ctrl+Alt+Z` 连续回溯更早的输入内容。
|
|
13
15
|
- **原生外观**:复用 Harness 加号菜单的浮层外壳与主题 token,自动适配深色和浅色模式。
|
|
@@ -33,17 +35,26 @@ Composer Rewind 是 DeepSeek Harness Web UI 的输入框状态历史插件。它
|
|
|
33
35
|
|
|
34
36
|
## 使用
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
点击输入框左侧工具栏的时钟按钮打开历史面板。面板只显示**当前会话**的记录。
|
|
37
39
|
|
|
38
40
|
- 顶部搜索框可按文字、附件名或配置筛选。
|
|
39
41
|
- 记录按“当前草稿 / 今天 / 昨天 / 更早”分组。
|
|
40
42
|
- `↑` / `↓` 选择,`Enter` 还原,`Esc` 关闭。
|
|
41
43
|
- 鼠标悬停可查看完整文字、附件与当时的配置;悬浮卡会自动避开屏幕边缘。
|
|
42
44
|
- 每条历史记录支持还原、复制、删除;“清空”会删除全部历史与附件缓存。
|
|
43
|
-
- 在输入框中按 `Ctrl+Alt+Z`
|
|
45
|
+
- 在输入框中按 `Ctrl+Alt+Z` 可直接回溯插入当前会话的上一条,连续按下继续往前。
|
|
44
46
|
|
|
45
47
|
还原只写入输入框,**不会自动发送**,可以继续修改后再提交。
|
|
46
48
|
|
|
49
|
+
## 设置页
|
|
50
|
+
|
|
51
|
+
Harness 设置中新增“输入框历史”页面,用于跨会话管理记录:
|
|
52
|
+
|
|
53
|
+
- 顶部显示会话数、记录数、附件数与本插件占用的浏览器存储。
|
|
54
|
+
- 记录按会话分组,标出当前会话;每组可单独清空,也可逐条删除。
|
|
55
|
+
- 可一键清空全部记录,或导出 JSON。导出只含文字与元数据,不包含附件文件。
|
|
56
|
+
- 设置页不提供“还原”,需要还原时回到对应会话,用输入框左侧的历史面板。
|
|
57
|
+
|
|
47
58
|
## 恢复范围
|
|
48
59
|
|
|
49
60
|
还原一条记录时,插件按以下顺序恢复:
|
package/client.js
CHANGED
|
@@ -84,6 +84,33 @@ window.__ModuleLoader__.load({
|
|
|
84
84
|
failPermission: '权限',
|
|
85
85
|
failPreset: '使用模式',
|
|
86
86
|
failSettings: '配置',
|
|
87
|
+
settingsNav: '输入框历史',
|
|
88
|
+
settingsTitle: '输入框历史',
|
|
89
|
+
settingsHint: '按会话查看和管理已保存的输入内容、附件与配置。',
|
|
90
|
+
settingsStatSessions: '会话',
|
|
91
|
+
settingsStatEntries: '记录',
|
|
92
|
+
settingsStatAttachments: '附件',
|
|
93
|
+
settingsStatUsage: '站点存储',
|
|
94
|
+
settingsCurrentSession: '当前会话',
|
|
95
|
+
settingsExport: '导出 JSON',
|
|
96
|
+
settingsExportHint: '只导出文字与元数据,不含附件文件。',
|
|
97
|
+
settingsClearAll: '清空全部',
|
|
98
|
+
settingsClearAllConfirm: '确认清空全部?',
|
|
99
|
+
settingsClearSessionConfirm: '确认清空?',
|
|
100
|
+
settingsClearSession: '清空此会话',
|
|
101
|
+
settingsExpand: '展开全部',
|
|
102
|
+
settingsCollapse: '收起',
|
|
103
|
+
settingsEmpty: '还没有任何会话记录。',
|
|
104
|
+
settingsNoText: '(仅附件)',
|
|
105
|
+
settingsUnknownSession: '未关联会话',
|
|
106
|
+
settingsEntryCount: '条记录',
|
|
107
|
+
settingsAttachmentsUnit: '个附件',
|
|
108
|
+
settingsLastAt: '最后',
|
|
109
|
+
settingsDeleted: '已删除',
|
|
110
|
+
settingsSessionCleared: '该会话记录已清空',
|
|
111
|
+
settingsAllCleared: '全部记录已清空',
|
|
112
|
+
settingsExported: '已开始导出',
|
|
113
|
+
settingsExportEmpty: '没有可导出的记录',
|
|
87
114
|
},
|
|
88
115
|
en: {
|
|
89
116
|
trigger: 'Composer history',
|
|
@@ -142,6 +169,33 @@ window.__ModuleLoader__.load({
|
|
|
142
169
|
failPermission: 'permission',
|
|
143
170
|
failPreset: 'mode',
|
|
144
171
|
failSettings: 'setup',
|
|
172
|
+
settingsNav: 'Composer history',
|
|
173
|
+
settingsTitle: 'Composer history',
|
|
174
|
+
settingsHint: 'Browse and manage the contents, attachments and setup saved per session.',
|
|
175
|
+
settingsStatSessions: 'Sessions',
|
|
176
|
+
settingsStatEntries: 'Entries',
|
|
177
|
+
settingsStatAttachments: 'Attachments',
|
|
178
|
+
settingsStatUsage: 'Site storage',
|
|
179
|
+
settingsCurrentSession: 'Current session',
|
|
180
|
+
settingsExport: 'Export JSON',
|
|
181
|
+
settingsExportHint: 'Text and metadata only; attachment files are not included.',
|
|
182
|
+
settingsClearAll: 'Clear all',
|
|
183
|
+
settingsClearAllConfirm: 'Clear everything?',
|
|
184
|
+
settingsClearSessionConfirm: 'Clear this session?',
|
|
185
|
+
settingsClearSession: 'Clear session',
|
|
186
|
+
settingsExpand: 'Show all',
|
|
187
|
+
settingsCollapse: 'Show less',
|
|
188
|
+
settingsEmpty: 'No session history yet.',
|
|
189
|
+
settingsNoText: '(attachments only)',
|
|
190
|
+
settingsUnknownSession: 'Unlinked session',
|
|
191
|
+
settingsEntryCount: 'entries',
|
|
192
|
+
settingsAttachmentsUnit: 'attachments',
|
|
193
|
+
settingsLastAt: 'last',
|
|
194
|
+
settingsDeleted: 'Deleted',
|
|
195
|
+
settingsSessionCleared: 'Session history cleared',
|
|
196
|
+
settingsAllCleared: 'All history cleared',
|
|
197
|
+
settingsExported: 'Export started',
|
|
198
|
+
settingsExportEmpty: 'Nothing to export',
|
|
145
199
|
},
|
|
146
200
|
};
|
|
147
201
|
|
|
@@ -589,6 +643,11 @@ window.__ModuleLoader__.load({
|
|
|
589
643
|
commitEntries([]);
|
|
590
644
|
}
|
|
591
645
|
|
|
646
|
+
function clearSession(sessionId) {
|
|
647
|
+
const key = sessionId || '';
|
|
648
|
+
commitEntries(entries.filter((item) => (item.sessionId || '') !== key));
|
|
649
|
+
}
|
|
650
|
+
|
|
592
651
|
function patchEntryAttachments(id, info) {
|
|
593
652
|
if (!Array.isArray(info) || info.length === 0) return;
|
|
594
653
|
const byKey = new Map(info.map((item) => [item.key, item]));
|
|
@@ -1007,10 +1066,27 @@ window.__ModuleLoader__.load({
|
|
|
1007
1066
|
let lastState = null;
|
|
1008
1067
|
let lastCapture = null;
|
|
1009
1068
|
|
|
1069
|
+
const sessionListeners = new Set();
|
|
1070
|
+
|
|
1071
|
+
function snapshotSession() {
|
|
1072
|
+
return activeSessionId;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function subscribeSession(listener) {
|
|
1076
|
+
sessionListeners.add(listener);
|
|
1077
|
+
return () => sessionListeners.delete(listener);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function setActiveSession(sessionId) {
|
|
1081
|
+
if (!sessionId || sessionId === activeSessionId) return;
|
|
1082
|
+
activeSessionId = sessionId;
|
|
1083
|
+
for (const listener of sessionListeners) listener();
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1010
1086
|
function publishComposer(sessionId, inputActions) {
|
|
1011
1087
|
if (!sessionId || !inputActions) return;
|
|
1012
1088
|
composerBySession.set(sessionId, inputActions);
|
|
1013
|
-
|
|
1089
|
+
setActiveSession(sessionId);
|
|
1014
1090
|
}
|
|
1015
1091
|
|
|
1016
1092
|
function composerActions() {
|
|
@@ -1303,6 +1379,10 @@ window.__ModuleLoader__.load({
|
|
|
1303
1379
|
return useSyncExternalStore(subscribe, snapshotEntries, snapshotEntries);
|
|
1304
1380
|
}
|
|
1305
1381
|
|
|
1382
|
+
function useActiveSession() {
|
|
1383
|
+
return useSyncExternalStore(subscribeSession, snapshotSession, snapshotSession);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1306
1386
|
function useLive() {
|
|
1307
1387
|
return useSyncExternalStore(subscribe, snapshotLive, snapshotLive);
|
|
1308
1388
|
}
|
|
@@ -1453,6 +1533,61 @@ window.__ModuleLoader__.load({
|
|
|
1453
1533
|
return pieces.join(' ').toLowerCase();
|
|
1454
1534
|
}
|
|
1455
1535
|
|
|
1536
|
+
function setupLine(settings) {
|
|
1537
|
+
if (!settings) return '';
|
|
1538
|
+
const parts = [];
|
|
1539
|
+
if (settings.model && settings.model.model) parts.push(settings.model.model);
|
|
1540
|
+
if (settings.permission) parts.push(permissionLabel(settings.permission));
|
|
1541
|
+
if (settings.agentPreset) parts.push(presetLabel(settings.agentPreset));
|
|
1542
|
+
return parts.join(' · ');
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function sessionTitle(list) {
|
|
1546
|
+
for (const entry of list) {
|
|
1547
|
+
const text = (entry.text || '').replace(/\s+/g, ' ').trim();
|
|
1548
|
+
if (text) return text.length > 30 ? text.slice(0, 30) + '…' : text;
|
|
1549
|
+
}
|
|
1550
|
+
return t('settingsNoText');
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
function sessionGroups(list) {
|
|
1554
|
+
const map = new Map();
|
|
1555
|
+
for (const entry of list) {
|
|
1556
|
+
const id = entry.sessionId || '';
|
|
1557
|
+
if (!map.has(id)) map.set(id, { id, entries: [], at: 0, attachments: 0 });
|
|
1558
|
+
const group = map.get(id);
|
|
1559
|
+
group.entries.push(entry);
|
|
1560
|
+
group.at = Math.max(group.at, entry.at || 0);
|
|
1561
|
+
group.attachments += (entry.attachments || []).length;
|
|
1562
|
+
}
|
|
1563
|
+
return [...map.values()].sort((a, b) => b.at - a.at);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function exportHistory() {
|
|
1567
|
+
if (entries.length === 0) {
|
|
1568
|
+
toast(t('settingsExportEmpty'));
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
try {
|
|
1572
|
+
const payload = {
|
|
1573
|
+
plugin: 'dsh-composer-rewind',
|
|
1574
|
+
schema: 3,
|
|
1575
|
+
exportedAt: new Date().toISOString(),
|
|
1576
|
+
entries,
|
|
1577
|
+
};
|
|
1578
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
|
|
1579
|
+
const url = URL.createObjectURL(blob);
|
|
1580
|
+
const anchor = document.createElement('a');
|
|
1581
|
+
anchor.href = url;
|
|
1582
|
+
anchor.download = 'composer-rewind-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
1583
|
+
anchor.click();
|
|
1584
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1585
|
+
toast(t('settingsExported'));
|
|
1586
|
+
} catch {
|
|
1587
|
+
toast(t('copyFailed'));
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1456
1591
|
/* ------------------------------------------------------------------ *
|
|
1457
1592
|
* Styles
|
|
1458
1593
|
* ------------------------------------------------------------------ */
|
|
@@ -1740,6 +1875,68 @@ window.__ModuleLoader__.load({
|
|
|
1740
1875
|
line-height:14px!important;color:var(--dsw-alias-label-tertiary)!important;
|
|
1741
1876
|
background:color-mix(in srgb,var(--dsw-alias-label-primary) 6%,transparent)!important}
|
|
1742
1877
|
|
|
1878
|
+
/* Settings page: follows the harness settings card language. */
|
|
1879
|
+
.pv-set{display:flex;flex-direction:column;gap:16px;width:100%;max-width:720px;margin:0 auto;
|
|
1880
|
+
padding:4px 0 24px;box-sizing:border-box;color:var(--dsw-alias-label-primary);
|
|
1881
|
+
font-size:13px;line-height:1.5;text-align:left}
|
|
1882
|
+
.pv-set *{box-sizing:border-box}
|
|
1883
|
+
.pv-set-head h2{margin:0;font-size:16px;font-weight:600;line-height:1.4}
|
|
1884
|
+
.pv-set-head p{margin:4px 0 0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary)}
|
|
1885
|
+
.pv-set-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
|
1886
|
+
.pv-set-stat{display:flex;flex-direction:column;gap:2px;min-width:0;padding:10px 12px;
|
|
1887
|
+
border:1px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-lg,12px);
|
|
1888
|
+
background:var(--dsw-alias-bg-layer-1)}
|
|
1889
|
+
.pv-set-stat strong{font-size:16px;font-weight:600;line-height:1.3;font-variant-numeric:tabular-nums}
|
|
1890
|
+
.pv-set-stat span{font-size:11px;line-height:1.4;color:var(--dsw-alias-label-tertiary);
|
|
1891
|
+
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1892
|
+
.pv-set-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
1893
|
+
.pv-set-actions-note{flex:1 1 220px;min-width:0;font-size:11px;color:var(--dsw-alias-label-tertiary)}
|
|
1894
|
+
.pv-set-btn{height:28px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);
|
|
1895
|
+
border-radius:var(--dsw-radius-md,8px);background:transparent;color:var(--dsw-alias-label-primary);
|
|
1896
|
+
font-size:12px;line-height:26px;cursor:pointer;white-space:nowrap}
|
|
1897
|
+
.pv-set-btn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1898
|
+
.pv-set-btn[data-danger="true"]{color:var(--dsw-alias-state-error-primary)}
|
|
1899
|
+
.pv-set-btn-small{height:24px;padding:0 8px;font-size:11px;line-height:22px}
|
|
1900
|
+
.pv-set-groups{display:flex;flex-direction:column;gap:10px}
|
|
1901
|
+
.pv-set-group{border:1px solid var(--dsw-alias-border-l1);border-radius:var(--dsw-radius-lg,12px);
|
|
1902
|
+
background:var(--dsw-alias-bg-layer-1);overflow:hidden}
|
|
1903
|
+
.pv-set-group-head{display:flex;align-items:center;gap:8px;padding:9px 12px;
|
|
1904
|
+
border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
1905
|
+
.pv-set-group-title{display:flex;align-items:center;gap:6px;flex:1;min-width:0}
|
|
1906
|
+
.pv-set-group-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
1907
|
+
font-size:13px;font-weight:500}
|
|
1908
|
+
.pv-set-current{flex:none;padding:0 5px;border-radius:4px;font-size:10px;line-height:16px;
|
|
1909
|
+
color:var(--dsw-alias-brand-primary);
|
|
1910
|
+
background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}
|
|
1911
|
+
.pv-set-group-meta{flex:none;max-width:44%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
1912
|
+
font-size:11px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
1913
|
+
.pv-set-list{list-style:none;margin:0;padding:0}
|
|
1914
|
+
.pv-set-row{display:flex;align-items:flex-start;gap:8px;padding:9px 12px;
|
|
1915
|
+
border-top:1px solid var(--dsw-alias-border-l1)}
|
|
1916
|
+
.pv-set-row:first-child{border-top:0}
|
|
1917
|
+
.pv-set-row:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 4%,transparent)}
|
|
1918
|
+
.pv-set-row-main{flex:1;min-width:0}
|
|
1919
|
+
.pv-set-row-text{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
|
|
1920
|
+
white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;line-height:1.5}
|
|
1921
|
+
.pv-set-row-meta{display:flex;gap:8px;flex-wrap:wrap;margin-top:3px;font-size:11px;
|
|
1922
|
+
color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
1923
|
+
.pv-set-remove{flex:none;width:24px;height:24px;padding:0;border:0;border-radius:5px;
|
|
1924
|
+
background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer;
|
|
1925
|
+
display:inline-flex;align-items:center;justify-content:center}
|
|
1926
|
+
.pv-set-remove:hover{color:var(--dsw-alias-state-error-primary);
|
|
1927
|
+
background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}
|
|
1928
|
+
.pv-set-more{width:100%;height:28px;border:0;border-top:1px solid var(--dsw-alias-border-l1);
|
|
1929
|
+
background:transparent;color:var(--dsw-alias-label-tertiary);font-size:11px;cursor:pointer}
|
|
1930
|
+
.pv-set-more:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1931
|
+
.pv-set-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:42px 16px;
|
|
1932
|
+
color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l1);
|
|
1933
|
+
border-radius:var(--dsw-radius-lg,12px);background:var(--dsw-alias-bg-layer-1)}
|
|
1934
|
+
.pv-set-empty p{margin:0;font-size:12px}
|
|
1935
|
+
@media (max-width:560px){
|
|
1936
|
+
.pv-set-stats{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
1937
|
+
.pv-set-group-meta{display:none}
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1743
1940
|
@keyframes pv-fade{from{opacity:0}to{opacity:1}}
|
|
1744
1941
|
@keyframes pv-pop{from{opacity:0;transform:translateY(4px) scale(.994)}to{opacity:1;transform:none}}
|
|
1745
1942
|
@media (prefers-reduced-motion:reduce){
|
|
@@ -2088,7 +2285,6 @@ window.__ModuleLoader__.load({
|
|
|
2088
2285
|
stateRef.current = { state, sessionId };
|
|
2089
2286
|
lastState = { state, sessionId };
|
|
2090
2287
|
lastDraft = draft;
|
|
2091
|
-
activeSessionId = sessionId || activeSessionId;
|
|
2092
2288
|
|
|
2093
2289
|
useEffect(() => {
|
|
2094
2290
|
if (!sessionId) return;
|
|
@@ -2268,22 +2464,33 @@ window.__ModuleLoader__.load({
|
|
|
2268
2464
|
* Panel
|
|
2269
2465
|
* ------------------------------------------------------------------ */
|
|
2270
2466
|
|
|
2271
|
-
const recall = { index: -1, at: 0, text: '' };
|
|
2467
|
+
const recall = { index: -1, at: 0, text: '', sessionId: null };
|
|
2468
|
+
|
|
2469
|
+
function entriesForSession(sessionId) {
|
|
2470
|
+
if (!sessionId) return entries;
|
|
2471
|
+
return entries.filter((entry) => entry.sessionId === sessionId);
|
|
2472
|
+
}
|
|
2272
2473
|
|
|
2273
2474
|
function recallPrevious() {
|
|
2274
|
-
|
|
2475
|
+
const source = entriesForSession(activeSessionId);
|
|
2476
|
+
if (source.length === 0) {
|
|
2275
2477
|
toast(t('noHistory'));
|
|
2276
2478
|
return;
|
|
2277
2479
|
}
|
|
2278
2480
|
const now = Date.now();
|
|
2279
|
-
const sameSession =
|
|
2280
|
-
|
|
2281
|
-
|
|
2481
|
+
const sameSession =
|
|
2482
|
+
now - recall.at < 6000 &&
|
|
2483
|
+
recall.text !== '' &&
|
|
2484
|
+
recall.sessionId === activeSessionId &&
|
|
2485
|
+
normalize(currentDraft()) === recall.text;
|
|
2486
|
+
const index = sameSession ? Math.min(source.length - 1, recall.index + 1) : 0;
|
|
2487
|
+
const entry = source[index];
|
|
2282
2488
|
if (insertText(entry.text)) {
|
|
2283
2489
|
recall.index = index;
|
|
2284
2490
|
recall.at = now;
|
|
2285
2491
|
recall.text = entry.text;
|
|
2286
|
-
|
|
2492
|
+
recall.sessionId = activeSessionId;
|
|
2493
|
+
toast(t('inserted') + ' ' + (index + 1) + '/' + source.length);
|
|
2287
2494
|
} else {
|
|
2288
2495
|
toast(t('noComposer'));
|
|
2289
2496
|
}
|
|
@@ -2310,6 +2517,7 @@ window.__ModuleLoader__.load({
|
|
|
2310
2517
|
const open = useUiOpen();
|
|
2311
2518
|
const history = useEntries();
|
|
2312
2519
|
const liveDraft = useLive();
|
|
2520
|
+
const activeSession = useActiveSession();
|
|
2313
2521
|
const scheme = useScheme();
|
|
2314
2522
|
const [query, setQuery] = useState('');
|
|
2315
2523
|
const [activeId, setActiveId] = useState(null);
|
|
@@ -2331,11 +2539,13 @@ window.__ModuleLoader__.load({
|
|
|
2331
2539
|
: 0;
|
|
2332
2540
|
const isCurrent =
|
|
2333
2541
|
!!liveDraft &&
|
|
2334
|
-
liveDraft.sessionId ===
|
|
2542
|
+
liveDraft.sessionId === activeSession &&
|
|
2335
2543
|
normalize(liveDraft.text) === normalize(currentDraft()) &&
|
|
2336
2544
|
(liveDraft.attachments || []).length === liveCount;
|
|
2337
2545
|
const draftRow =
|
|
2338
|
-
liveDraft &&
|
|
2546
|
+
liveDraft &&
|
|
2547
|
+
(!activeSession || liveDraft.sessionId === activeSession) &&
|
|
2548
|
+
(liveDraft.text || (liveDraft.attachments || []).length > 0)
|
|
2339
2549
|
? {
|
|
2340
2550
|
id: '__live',
|
|
2341
2551
|
text: liveDraft.text,
|
|
@@ -2352,11 +2562,14 @@ window.__ModuleLoader__.load({
|
|
|
2352
2562
|
// both "current draft" and the newest history entry. Show it once; the
|
|
2353
2563
|
// history copy reappears as soon as the composer no longer holds it.
|
|
2354
2564
|
const liveSignature = draftRow ? entrySignature(draftRow.text, draftRow.attachments) : null;
|
|
2355
|
-
|
|
2565
|
+
// Entries belong to the session they were written in; keep the panel
|
|
2566
|
+
// and Ctrl+Alt+Z scoped to the session that owns the composer now.
|
|
2567
|
+
const scoped = activeSession ? entriesForSession(activeSession) : history;
|
|
2568
|
+
const list = scoped.filter((entry) => !liveSignature || entrySignature(entry.text, entry.attachments) !== liveSignature);
|
|
2356
2569
|
const matches = needle ? list.filter((entry) => haystack(entry).includes(needle)) : list;
|
|
2357
2570
|
if (draftRow && (!needle || haystack(draftRow).includes(needle))) return [draftRow, ...matches];
|
|
2358
2571
|
return matches;
|
|
2359
|
-
}, [history, liveDraft, query]);
|
|
2572
|
+
}, [history, liveDraft, query, activeSession]);
|
|
2360
2573
|
|
|
2361
2574
|
const groups = useMemo(() => {
|
|
2362
2575
|
const order = [];
|
|
@@ -2711,6 +2924,195 @@ window.__ModuleLoader__.load({
|
|
|
2711
2924
|
return h(React.Fragment, null, panel, h(Toast, { scheme }));
|
|
2712
2925
|
}
|
|
2713
2926
|
|
|
2927
|
+
/* ------------------------------------------------------------------ *
|
|
2928
|
+
* Settings page
|
|
2929
|
+
* ------------------------------------------------------------------ */
|
|
2930
|
+
|
|
2931
|
+
function SettingsPage() {
|
|
2932
|
+
const history = useEntries();
|
|
2933
|
+
const active = useActiveSession();
|
|
2934
|
+
const [confirmAll, setConfirmAll] = useState(false);
|
|
2935
|
+
const [confirmGroup, setConfirmGroup] = useState(null);
|
|
2936
|
+
const [expanded, setExpanded] = useState({});
|
|
2937
|
+
const [usage, setUsage] = useState(null);
|
|
2938
|
+
|
|
2939
|
+
useEffect(() => {
|
|
2940
|
+
const nav = typeof navigator !== 'undefined' ? navigator : null;
|
|
2941
|
+
if (!nav || !nav.storage || typeof nav.storage.estimate !== 'function') return undefined;
|
|
2942
|
+
let alive = true;
|
|
2943
|
+
nav.storage.estimate().then(
|
|
2944
|
+
(value) => {
|
|
2945
|
+
if (alive) setUsage(value && typeof value.usage === 'number' ? value.usage : null);
|
|
2946
|
+
},
|
|
2947
|
+
() => {},
|
|
2948
|
+
);
|
|
2949
|
+
return () => {
|
|
2950
|
+
alive = false;
|
|
2951
|
+
};
|
|
2952
|
+
}, [history]);
|
|
2953
|
+
|
|
2954
|
+
const groups = useMemo(() => sessionGroups(history), [history]);
|
|
2955
|
+
const attachmentCount = history.reduce((sum, entry) => sum + (entry.attachments || []).length, 0);
|
|
2956
|
+
|
|
2957
|
+
const clearGroup = (id) => {
|
|
2958
|
+
if (confirmGroup !== id) {
|
|
2959
|
+
setConfirmGroup(id);
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
clearSession(id);
|
|
2963
|
+
setConfirmGroup(null);
|
|
2964
|
+
setExpanded((value) => ({ ...value, [id]: false }));
|
|
2965
|
+
toast(t('settingsSessionCleared'));
|
|
2966
|
+
};
|
|
2967
|
+
|
|
2968
|
+
const clearEverything = () => {
|
|
2969
|
+
if (!confirmAll) {
|
|
2970
|
+
setConfirmAll(true);
|
|
2971
|
+
return;
|
|
2972
|
+
}
|
|
2973
|
+
clearAll();
|
|
2974
|
+
setConfirmAll(false);
|
|
2975
|
+
setExpanded({});
|
|
2976
|
+
toast(t('settingsAllCleared'));
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
const stat = (label, value) =>
|
|
2980
|
+
h(
|
|
2981
|
+
'div',
|
|
2982
|
+
{ className: 'pv-set-stat', key: label },
|
|
2983
|
+
h('strong', null, value),
|
|
2984
|
+
h('span', null, label),
|
|
2985
|
+
);
|
|
2986
|
+
|
|
2987
|
+
return h(
|
|
2988
|
+
'div',
|
|
2989
|
+
{ className: 'pv-set' },
|
|
2990
|
+
h(
|
|
2991
|
+
'header',
|
|
2992
|
+
{ className: 'pv-set-head' },
|
|
2993
|
+
h('h2', null, t('settingsTitle')),
|
|
2994
|
+
h('p', null, t('settingsHint')),
|
|
2995
|
+
),
|
|
2996
|
+
h(
|
|
2997
|
+
'div',
|
|
2998
|
+
{ className: 'pv-set-stats' },
|
|
2999
|
+
stat(t('settingsStatSessions'), String(groups.length)),
|
|
3000
|
+
stat(t('settingsStatEntries'), String(history.length)),
|
|
3001
|
+
stat(t('settingsStatAttachments'), String(attachmentCount)),
|
|
3002
|
+
stat(t('settingsStatUsage'), usage == null ? '—' : usage === 0 ? '0 B' : formatBytes(usage)),
|
|
3003
|
+
),
|
|
3004
|
+
h(
|
|
3005
|
+
'div',
|
|
3006
|
+
{ className: 'pv-set-actions' },
|
|
3007
|
+
h('span', { className: 'pv-set-actions-note' }, t('settingsExportHint')),
|
|
3008
|
+
h(
|
|
3009
|
+
'button',
|
|
3010
|
+
{ type: 'button', className: 'pv-set-btn', onClick: exportHistory },
|
|
3011
|
+
t('settingsExport'),
|
|
3012
|
+
),
|
|
3013
|
+
h(
|
|
3014
|
+
'button',
|
|
3015
|
+
{
|
|
3016
|
+
type: 'button',
|
|
3017
|
+
className: 'pv-set-btn',
|
|
3018
|
+
'data-danger': 'true',
|
|
3019
|
+
onClick: clearEverything,
|
|
3020
|
+
},
|
|
3021
|
+
confirmAll ? t('settingsClearAllConfirm') : t('settingsClearAll'),
|
|
3022
|
+
),
|
|
3023
|
+
),
|
|
3024
|
+
groups.length === 0
|
|
3025
|
+
? h('div', { className: 'pv-set-empty' }, h(Icon, { paths: ACTION_PATHS.search, size: 20 }), h('p', null, t('settingsEmpty')))
|
|
3026
|
+
: h(
|
|
3027
|
+
'div',
|
|
3028
|
+
{ className: 'pv-set-groups' },
|
|
3029
|
+
groups.map((group) => {
|
|
3030
|
+
const openGroup = !!expanded[group.id];
|
|
3031
|
+
const shown = openGroup ? group.entries : group.entries.slice(0, 6);
|
|
3032
|
+
return h(
|
|
3033
|
+
'section',
|
|
3034
|
+
{ className: 'pv-set-group', key: group.id || 'unknown' },
|
|
3035
|
+
h(
|
|
3036
|
+
'div',
|
|
3037
|
+
{ className: 'pv-set-group-head' },
|
|
3038
|
+
h(
|
|
3039
|
+
'div',
|
|
3040
|
+
{ className: 'pv-set-group-title' },
|
|
3041
|
+
h('span', { className: 'pv-set-group-name' }, group.id ? sessionTitle(group.entries) : t('settingsUnknownSession')),
|
|
3042
|
+
group.id === active && group.id ? h('span', { className: 'pv-set-current' }, t('settingsCurrentSession')) : null,
|
|
3043
|
+
),
|
|
3044
|
+
h(
|
|
3045
|
+
'span',
|
|
3046
|
+
{ className: 'pv-set-group-meta' },
|
|
3047
|
+
group.entries.length + ' ' + t('settingsEntryCount') +
|
|
3048
|
+
(group.attachments ? ' · ' + group.attachments + ' ' + t('settingsAttachmentsUnit') : '') +
|
|
3049
|
+
' · ' + t('settingsLastAt') + ' ' + formatTime(group.at) +
|
|
3050
|
+
(group.id ? ' · #' + group.id.slice(0, 8) : ''),
|
|
3051
|
+
),
|
|
3052
|
+
h(
|
|
3053
|
+
'button',
|
|
3054
|
+
{
|
|
3055
|
+
type: 'button',
|
|
3056
|
+
className: 'pv-set-btn pv-set-btn-small',
|
|
3057
|
+
onClick: () => clearGroup(group.id),
|
|
3058
|
+
},
|
|
3059
|
+
confirmGroup === group.id ? t('settingsClearSessionConfirm') : t('settingsClearSession'),
|
|
3060
|
+
),
|
|
3061
|
+
),
|
|
3062
|
+
h(
|
|
3063
|
+
'ul',
|
|
3064
|
+
{ className: 'pv-set-list' },
|
|
3065
|
+
shown.map((entry) =>
|
|
3066
|
+
h(
|
|
3067
|
+
'li',
|
|
3068
|
+
{ className: 'pv-set-row', key: entry.id },
|
|
3069
|
+
h(
|
|
3070
|
+
'div',
|
|
3071
|
+
{ className: 'pv-set-row-main' },
|
|
3072
|
+
h('div', { className: 'pv-set-row-text' }, entry.text || t('settingsNoText')),
|
|
3073
|
+
h(
|
|
3074
|
+
'div',
|
|
3075
|
+
{ className: 'pv-set-row-meta' },
|
|
3076
|
+
h('span', null, formatTime(entry.at)),
|
|
3077
|
+
entry.attachments && entry.attachments.length
|
|
3078
|
+
? h('span', null, entry.attachments.length + ' ' + t('settingsAttachmentsUnit'))
|
|
3079
|
+
: null,
|
|
3080
|
+
setupLine(entry.settings) ? h('span', null, setupLine(entry.settings)) : null,
|
|
3081
|
+
),
|
|
3082
|
+
),
|
|
3083
|
+
h(
|
|
3084
|
+
'button',
|
|
3085
|
+
{
|
|
3086
|
+
type: 'button',
|
|
3087
|
+
className: 'pv-set-remove',
|
|
3088
|
+
'aria-label': t('remove'),
|
|
3089
|
+
onClick: () => {
|
|
3090
|
+
removeEntry(entry.id);
|
|
3091
|
+
toast(t('settingsDeleted'));
|
|
3092
|
+
},
|
|
3093
|
+
},
|
|
3094
|
+
h(Icon, { paths: ACTION_PATHS.trash, size: 14 }),
|
|
3095
|
+
),
|
|
3096
|
+
),
|
|
3097
|
+
),
|
|
3098
|
+
),
|
|
3099
|
+
group.entries.length > 6
|
|
3100
|
+
? h(
|
|
3101
|
+
'button',
|
|
3102
|
+
{
|
|
3103
|
+
type: 'button',
|
|
3104
|
+
className: 'pv-set-more',
|
|
3105
|
+
onClick: () => setExpanded((value) => ({ ...value, [group.id]: !openGroup })),
|
|
3106
|
+
},
|
|
3107
|
+
openGroup ? t('settingsCollapse') : t('settingsExpand'),
|
|
3108
|
+
)
|
|
3109
|
+
: null,
|
|
3110
|
+
);
|
|
3111
|
+
}),
|
|
3112
|
+
),
|
|
3113
|
+
);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
2714
3116
|
function Toast(props) {
|
|
2715
3117
|
const message = useToast();
|
|
2716
3118
|
if (!message) return null;
|
|
@@ -2730,8 +3132,12 @@ window.__ModuleLoader__.load({
|
|
|
2730
3132
|
setLive,
|
|
2731
3133
|
setLiveSnapshot,
|
|
2732
3134
|
clearAll,
|
|
3135
|
+
clearSession,
|
|
2733
3136
|
removeEntry,
|
|
2734
3137
|
publishComposer,
|
|
3138
|
+
setActiveSession,
|
|
3139
|
+
activeSession: () => activeSessionId,
|
|
3140
|
+
entriesForSession,
|
|
2735
3141
|
insertText,
|
|
2736
3142
|
recallPrevious,
|
|
2737
3143
|
currentDraft,
|
|
@@ -2770,6 +3176,17 @@ window.__ModuleLoader__.load({
|
|
|
2770
3176
|
ctx.slots.inject('shell.overlay', () =>
|
|
2771
3177
|
ctx.slots.register({ name: 'shell.overlay', id: 'composer-rewind-panel', order: 90 }, Panel),
|
|
2772
3178
|
);
|
|
3179
|
+
ctx.slots.inject('settings.section', () =>
|
|
3180
|
+
ctx.slots.register(
|
|
3181
|
+
{
|
|
3182
|
+
name: 'settings.section',
|
|
3183
|
+
id: 'composer-rewind-settings',
|
|
3184
|
+
order: 30,
|
|
3185
|
+
label: () => t('settingsNav'),
|
|
3186
|
+
},
|
|
3187
|
+
SettingsPage,
|
|
3188
|
+
),
|
|
3189
|
+
);
|
|
2773
3190
|
// Release object URLs when the page goes away.
|
|
2774
3191
|
if (typeof window !== 'undefined' && typeof window.addEventListener === 'function') {
|
|
2775
3192
|
window.addEventListener('pagehide', () => {
|
package/package.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-composer-rewind",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "
|
|
6
|
-
"keywords": [
|
|
7
|
-
"dsh",
|
|
8
|
-
"dsh-plugin",
|
|
9
|
-
"deepseek-harness",
|
|
10
|
-
"composer-history",
|
|
11
|
-
"composer"
|
|
12
|
-
],
|
|
13
|
-
"repository": {
|
|
14
|
-
"type": "git",
|
|
15
|
-
"url": "git+https://github.com/QIUZAIYOU/dsh-composer-rewind.git"
|
|
16
|
-
},
|
|
17
|
-
"homepage": "https://github.com/QIUZAIYOU/dsh-composer-rewind#readme",
|
|
18
|
-
"bugs": {
|
|
19
|
-
"url": "https://github.com/QIUZAIYOU/dsh-composer-rewind/issues"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
},
|
|
24
|
-
"exports": {
|
|
25
|
-
".": "./index.js",
|
|
26
|
-
"./client": "./client.js",
|
|
27
|
-
"./locale/*.json": "./locale/*.json",
|
|
28
|
-
"./icon": "./icon.svg"
|
|
29
|
-
},
|
|
30
|
-
"files": [
|
|
31
|
-
"index.js",
|
|
32
|
-
"client.js",
|
|
33
|
-
"cordis.patch.yml",
|
|
34
|
-
"locale/*.json",
|
|
35
|
-
"icon.svg",
|
|
36
|
-
"README.md"
|
|
37
|
-
],
|
|
38
|
-
"dsh": {
|
|
39
|
-
"bundle": {
|
|
40
|
-
"patch": "./cordis.patch.yml"
|
|
41
|
-
},
|
|
42
|
-
"client": {
|
|
43
|
-
"platform": "web",
|
|
44
|
-
"immediately": true,
|
|
45
|
-
"external": [
|
|
46
|
-
"@deepseek-ai/dsh-client-ui-primitives"
|
|
47
|
-
],
|
|
48
|
-
"inject": [
|
|
49
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-composer-rewind",
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Per-session composer input history for the DeepSeek Harness Web UI: records composer contents, attachments, command tokens and the active DSH setup, restores the whole composer state, and manages history per session from a settings page.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"dsh",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"deepseek-harness",
|
|
10
|
+
"composer-history",
|
|
11
|
+
"composer"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/QIUZAIYOU/dsh-composer-rewind.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/QIUZAIYOU/dsh-composer-rewind#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/QIUZAIYOU/dsh-composer-rewind/issues"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": "./index.js",
|
|
26
|
+
"./client": "./client.js",
|
|
27
|
+
"./locale/*.json": "./locale/*.json",
|
|
28
|
+
"./icon": "./icon.svg"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"index.js",
|
|
32
|
+
"client.js",
|
|
33
|
+
"cordis.patch.yml",
|
|
34
|
+
"locale/*.json",
|
|
35
|
+
"icon.svg",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"dsh": {
|
|
39
|
+
"bundle": {
|
|
40
|
+
"patch": "./cordis.patch.yml"
|
|
41
|
+
},
|
|
42
|
+
"client": {
|
|
43
|
+
"platform": "web",
|
|
44
|
+
"immediately": true,
|
|
45
|
+
"external": [
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
47
|
+
],
|
|
48
|
+
"inject": [
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|