dsh-retrace 0.3.0 → 0.4.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 +40 -27
- package/README.zh.md +37 -28
- package/lib/artifact-store.js +9 -0
- package/lib/client.bundle.js +475 -12
- package/lib/client.js +600 -13
- package/lib/dynamic-client.js +475 -12
- package/lib/dynamic-host.js +107 -80
- package/lib/git-adapter.js +177 -0
- package/lib/host-core.js +107 -80
- package/lib/http.js +104 -3
- package/lib/index.js +15 -2
- package/lib/prewrite-guard.js +71 -0
- package/lib/projection/versions.js +32 -10
- package/lib/rollback.js +283 -0
- package/lib/version-index.js +15 -4
- package/lib/versioning.js +188 -1
- package/package.json +13 -10
package/lib/dynamic-client.js
CHANGED
|
@@ -43,7 +43,16 @@ return {
|
|
|
43
43
|
var NS = "retrace";
|
|
44
44
|
var ROUTE_BASE = "/api/plugins/retrace";
|
|
45
45
|
var MARKER_PREFIX = "retrace";
|
|
46
|
+
var MARKER_PREFIXES = [MARKER_PREFIX, "message-editor"];
|
|
47
|
+
var LEGACY_MARKER_PREFIXES = ["message-editor"];
|
|
48
|
+
function isMarkerId(id) {
|
|
49
|
+
return typeof id === "string" && MARKER_PREFIXES.some((p) => id.startsWith(`${p}-`));
|
|
50
|
+
}
|
|
51
|
+
function isLegacyMarkerId(id) {
|
|
52
|
+
return typeof id === "string" && LEGACY_MARKER_PREFIXES.some((p) => id.startsWith(`${p}-`));
|
|
53
|
+
}
|
|
46
54
|
var CONFIG_KEY = "dsh-retrace:config";
|
|
55
|
+
var LEGACY_CONFIG_KEYS = ["dsh-message-editor:config"];
|
|
47
56
|
var zh = {
|
|
48
57
|
"action.edit": "\u7F16\u8F91",
|
|
49
58
|
"action.editAria": "\u7F16\u8F91\u8FD9\u6761\u6D88\u606F",
|
|
@@ -62,13 +71,60 @@ return {
|
|
|
62
71
|
"marker.referenceHint": "\u70B9\u51FB\u5C55\u5F00\u67E5\u770B\u539F\u63D0\u95EE\uFF08\u4EC5\u4F5C\u5BF9\u7167\uFF0C\u4E0D\u4F1A\u8FDB\u5165\u6A21\u578B\u4E0A\u4E0B\u6587\uFF09",
|
|
63
72
|
"options.title": "\u6D88\u606F\u7F16\u8F91\u63D2\u4EF6",
|
|
64
73
|
"options.showOriginalInput": "\u7F16\u8F91\u540E\u663E\u793A\u539F\u63D0\u95EE\u5BF9\u7167",
|
|
65
|
-
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF09",
|
|
74
|
+
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF0C\u9ED8\u8BA4\u5173\uFF09",
|
|
75
|
+
"options.hideShadowed": "\u6309\u6807\u8BB0\u9690\u85CF\u88AB\u7F16\u8F91/\u64A4\u56DE\u7684\u6D88\u606F",
|
|
76
|
+
"options.hideShadowedDesc": "\u5F00\uFF08\u9ED8\u8BA4\uFF09\uFF1A\u64A4\u56DE/\u7F16\u8F91/\u91CD\u65B0\u751F\u6210\u6309\u6807\u8BB0\u9690\u85CF\u88AB\u66FF\u6362\u7684\u90A3\u4E00\u8F6E\u6D88\u606F\u3002\u5173\uFF1A\u6240\u6709\u6D88\u606F\u4FDD\u6301\u53EF\u89C1\uFF0C\u6807\u8BB0\u4EC5\u663E\u793A\u63D0\u793A\u4E0E\u5BF9\u7167\uFF08\u67E5\u770B\u5B8C\u6574\u5386\u53F2\u7528\uFF09\u3002\u4E00\u6B21\u64CD\u4F5C\u8981\u9690\u85CF\u8D85\u8FC7 40% \u7684\u5BF9\u8BDD\u65F6\u81EA\u52A8\u964D\u7EA7\u4E3A\u4E0D\u9690\u85CF\u3002",
|
|
66
77
|
"options.versioning": "\u7248\u672C\u4E0E\u4EA7\u7269\u5FEB\u7167",
|
|
67
78
|
"options.versioningDesc": "\u5F00\uFF1A\u6BCF\u6B21\u64A4\u56DE/\u7F16\u8F91\u8BB0\u5F55\u4E00\u4E2A\u7248\u672C\uFF08\u6D88\u606F\u4E0E\u89E6\u78B0\u6587\u4EF6\uFF09\uFF0C\u63D0\u4F9B\u65F6\u95F4\u7EBF\u4E0E\u4EA7\u7269\u56DE\u9000\uFF1B\u5173\uFF1A\u4EC5\u56DE\u9000\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u8BB0\u5F55\u7248\u672C\u3001\u4E0D\u8FFD\u8E2A\u4EA7\u7269\uFF08\u6700\u7701\u8D44\u6E90\uFF09\u3002",
|
|
68
79
|
"options.git": "\u542F\u7528 git \u96C6\u6210",
|
|
69
80
|
"options.gitDesc": "\u5F00\uFF1A\u5DE5\u4F5C\u533A\u662F git \u4ED3\u5E93\u65F6\u7528 git \u8BB0\u5F55\u4E0E\u56DE\u9000\uFF08\u4E0D\u81EA\u52A8\u63D0\u4EA4\u3001\u4E0D\u52A8\u4F60\u7684\u5206\u652F\uFF09\uFF0C\u975E\u4ED3\u5E93\u53EF\u5728\u65F6\u95F4\u7EBF\u91CC\u4E00\u952E\u542F\u7528\uFF1B\u5173\uFF1A\u4E00\u5F8B\u7528\u5185\u7F6E\u5FEB\u7167\uFF08\u5B58\u4E8E ~/.dsh\uFF09\uFF0C\u4E0D\u89E6\u78B0\u5DE5\u4F5C\u533A git \u72B6\u6001\uFF0C\u529F\u80FD\u7B49\u4EF7\u3002",
|
|
70
81
|
"options.retention": "\u7248\u672C\u4FDD\u7559\u4E0A\u9650",
|
|
71
82
|
"options.retentionDesc": "\u6587\u4EF6\u5FEB\u7167\u53EA\u4FDD\u7559\u6700\u8FD1 N \u4E2A\u7248\u672C\uFF0C\u8D85\u51FA\u81EA\u52A8\u6E05\u7406\u6700\u65E7\u7684\uFF1B\u65F6\u95F4\u7EBF\u8BB0\u5F55\u4E0E\u5BA1\u8BA1\u75D5\u8FF9\u59CB\u7EC8\u4FDD\u7559\u3002",
|
|
83
|
+
"timeline.open": "\u65F6\u95F4\u7EBF",
|
|
84
|
+
"timeline.openAria": "\u6253\u5F00\u4F1A\u8BDD\u65F6\u95F4\u7EBF",
|
|
85
|
+
"timeline.title": "\u7248\u672C\u65F6\u95F4\u7EBF",
|
|
86
|
+
"view.retrace": "\u7248\u672C",
|
|
87
|
+
"timeline.refresh": "\u5237\u65B0",
|
|
88
|
+
"timeline.close": "\u5173\u95ED",
|
|
89
|
+
"timeline.empty": "\u8FD8\u6CA1\u6709\u7248\u672C\u3002\u64A4\u56DE / \u7F16\u8F91 / \u91CD\u65B0\u751F\u6210\u4F1A\u5728\u6B64\u8BB0\u5F55\u7248\u672C\u3002",
|
|
90
|
+
"timeline.loading": "\u52A0\u8F7D\u4E2D\u2026",
|
|
91
|
+
"timeline.error": "\u65F6\u95F4\u7EBF\u52A0\u8F7D\u5931\u8D25",
|
|
92
|
+
"timeline.kind.recall": "\u64A4\u56DE",
|
|
93
|
+
"timeline.kind.edit": "\u7F16\u8F91\u91CD\u53D1",
|
|
94
|
+
"timeline.kind.regenerate": "\u91CD\u65B0\u751F\u6210",
|
|
95
|
+
"timeline.kind.restore": "\u6062\u590D",
|
|
96
|
+
"timeline.kind.compaction": "\u538B\u7F29",
|
|
97
|
+
"timeline.kind.replace": "\u66FF\u6362",
|
|
98
|
+
"timeline.messages": "{count} \u6761\u6D88\u606F",
|
|
99
|
+
"timeline.files": "{created} \u589E / {modified} \u6539 / {deleted} \u5220",
|
|
100
|
+
"timeline.filesNone": "\u65E0\u6587\u4EF6\u53D8\u66F4",
|
|
101
|
+
"timeline.preview": "\u56DE\u9000\u9884\u89C8",
|
|
102
|
+
"timeline.previewDesc": "\u5C06\u56DE\u9000\u5230\u7248\u672C {version}\uFF08{kind}\uFF09\u3002",
|
|
103
|
+
"timeline.contextOnly": "\u4EC5\u5BF9\u8BDD",
|
|
104
|
+
"timeline.contextOnlyDesc": "\u79FB\u9664\u8BE5\u7248\u672C\u4E4B\u540E\u7684\u6D88\u606F\uFF08\u4FDD\u7559\u65E5\u5FD7\u5BA1\u8BA1\u75D5\u8FF9\uFF09",
|
|
105
|
+
"timeline.artifactsOnly": "\u4EC5\u4EA7\u7269",
|
|
106
|
+
"timeline.artifactsOnlyDesc": "\u628A\u8BE5\u7248\u672C\u89E6\u78B0\u7684\u6587\u4EF6\u6062\u590D\u5230\u5F53\u65F6\u7684\u5167\u5BB9",
|
|
107
|
+
"timeline.both": "\u4E24\u8005",
|
|
108
|
+
"timeline.bothDesc": "\u5148\u56DE\u9000\u5BF9\u8BDD\uFF0C\u518D\u56DE\u9000\u4EA7\u7269",
|
|
109
|
+
"timeline.messagesRemoved": "\u5C06\u79FB\u9664 {count} \u6761\u6D88\u606F",
|
|
110
|
+
"timeline.noChanges": "\u5F53\u524D\u5DF2\u5728\u8BE5\u7248\u672C\u72B6\u6001\uFF0C\u65E0\u53D8\u5316",
|
|
111
|
+
"timeline.artifactsList": "\u4EA7\u7269\u52A8\u4F5C\uFF08{count}\uFF09",
|
|
112
|
+
"timeline.artifact.restore": "\u6062\u590D",
|
|
113
|
+
"timeline.artifact.delete": "\u5220\u9664",
|
|
114
|
+
"timeline.artifact.skip": "\u8DF3\u8FC7",
|
|
115
|
+
"timeline.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
116
|
+
"timeline.cancel": "\u53D6\u6D88",
|
|
117
|
+
"timeline.busy": "\u56DE\u9000\u4E2D\u2026",
|
|
118
|
+
"timeline.detail": "\u8BE6\u60C5",
|
|
119
|
+
"timeline.trajectory": "\u8F68\u8FF9\u53F0\u8D26",
|
|
120
|
+
"timeline.jump": "\u8DF3\u8F6C",
|
|
121
|
+
"timeline.jumpFailed": "\u8BE5\u7248\u672C\u5728\u8F83\u8FDC\u7684\u8FC7\u53BB\uFF08\u8D85\u51FA\u81EA\u52A8\u52A0\u8F7D\u9884\u7B97\uFF09\uFF0C\u65E0\u6CD5\u76F4\u63A5\u5B9A\u4F4D\u3002\u8BF7\u5411\u4E0A\u6EDA\u52A8\u52A0\u8F7D\u66F4\u65E9\u6D88\u606F\u540E\u91CD\u8BD5\uFF1B\u6216\u7528\u300C\u8BE6\u60C5\u300D\u67E5\u770B\u8BE5\u7248\u672C\u5F53\u65F6\u7684\u4E8B\u4EF6\u539F\u6587\u3002",
|
|
122
|
+
"timeline.gitRepo": "git \u4ED3\u5E93",
|
|
123
|
+
"timeline.gitHead": "HEAD {hash}",
|
|
124
|
+
"timeline.gitDirty": "\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u6539\u52A8",
|
|
125
|
+
"timeline.gitInit": "\u542F\u7528 git \u7248\u672C\u7BA1\u7406",
|
|
126
|
+
"timeline.gitInitDesc": "\u5728\u5DE5\u4F5C\u533A\u6267\u884C git init\uFF08\u4EC5\u6DFB\u52A0 .gitignore \u4E0E\u4E00\u4E2A\u57FA\u7EBF\u63D0\u4EA4\uFF09\uFF0C\u7248\u672C\u56DE\u9000\u5C06\u4F18\u5148\u4F7F\u7528 git\u3002",
|
|
127
|
+
"timeline.gitInitConfirm": "\u786E\u5B9A\u8981\u521D\u59CB\u5316 git \u5417\uFF1F",
|
|
72
128
|
"error.generic": "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
73
129
|
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C"
|
|
74
130
|
};
|
|
@@ -90,13 +146,60 @@ return {
|
|
|
90
146
|
"marker.referenceHint": "Click to expand the original input (reference only, never sent to the model)",
|
|
91
147
|
"options.title": "Message editor plugin",
|
|
92
148
|
"options.showOriginalInput": "Show the original input after editing",
|
|
93
|
-
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages)",
|
|
149
|
+
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages, default off)",
|
|
150
|
+
"options.hideShadowed": "Hide shadowed messages per marker",
|
|
151
|
+
"options.hideShadowedDesc": "On (default): recall/edit/regenerate hide the replaced round per their markers. Off: every message stays visible; markers only show the notice and reference (use to review full history). A single op that would hide more than 40% of the conversation degrades to notice-only automatically.",
|
|
94
152
|
"options.versioning": "Version & artifact snapshots",
|
|
95
153
|
"options.versioningDesc": "On: every recall/edit records a version (messages and touched files) powering the timeline and artifact rollback. Off: only rewinds context \u2014 no version records, no artifact tracking (lightest).",
|
|
96
154
|
"options.git": "Git integration",
|
|
97
155
|
"options.gitDesc": "On: uses git to record and roll back when the workspace is a repository (never auto-commits, never touches your branches); non-repo workspaces can enable git from the timeline. Off: built-in snapshots under ~/.dsh only \u2014 the plugin never touches the workspace git state; equivalent features.",
|
|
98
156
|
"options.retention": "Version retention limit",
|
|
99
157
|
"options.retentionDesc": "File snapshots are kept for the most recent N versions; older ones are pruned automatically (timeline records and the audit trail are always kept).",
|
|
158
|
+
"timeline.open": "Timeline",
|
|
159
|
+
"timeline.openAria": "Open the session timeline",
|
|
160
|
+
"timeline.title": "Version timeline",
|
|
161
|
+
"view.retrace": "Versions",
|
|
162
|
+
"timeline.refresh": "Refresh",
|
|
163
|
+
"timeline.close": "Close",
|
|
164
|
+
"timeline.empty": "No versions yet. Recall / edit / regenerate record a version here.",
|
|
165
|
+
"timeline.loading": "Loading\u2026",
|
|
166
|
+
"timeline.error": "Failed to load the timeline",
|
|
167
|
+
"timeline.kind.recall": "Recall",
|
|
168
|
+
"timeline.kind.edit": "Edit & resend",
|
|
169
|
+
"timeline.kind.regenerate": "Regenerate",
|
|
170
|
+
"timeline.kind.restore": "Restore",
|
|
171
|
+
"timeline.kind.compaction": "Compaction",
|
|
172
|
+
"timeline.kind.replace": "Replace",
|
|
173
|
+
"timeline.messages": "{count} messages",
|
|
174
|
+
"timeline.files": "{created} created / {modified} modified / {deleted} deleted",
|
|
175
|
+
"timeline.filesNone": "No file changes",
|
|
176
|
+
"timeline.preview": "Rollback preview",
|
|
177
|
+
"timeline.previewDesc": "Will roll back to version {version} ({kind}).",
|
|
178
|
+
"timeline.contextOnly": "Context only",
|
|
179
|
+
"timeline.contextOnlyDesc": "Remove messages after this version (the log audit trail stays)",
|
|
180
|
+
"timeline.artifactsOnly": "Artifacts only",
|
|
181
|
+
"timeline.artifactsOnlyDesc": "Restore the files this version touched to their state at that version",
|
|
182
|
+
"timeline.both": "Both",
|
|
183
|
+
"timeline.bothDesc": "Roll back the context first, then the artifacts",
|
|
184
|
+
"timeline.messagesRemoved": "{count} messages will be removed",
|
|
185
|
+
"timeline.noChanges": "Already at this version; nothing to change",
|
|
186
|
+
"timeline.artifactsList": "Artifact actions ({count})",
|
|
187
|
+
"timeline.artifact.restore": "restore",
|
|
188
|
+
"timeline.artifact.delete": "delete",
|
|
189
|
+
"timeline.artifact.skip": "skip",
|
|
190
|
+
"timeline.confirm": "Confirm rollback",
|
|
191
|
+
"timeline.cancel": "Cancel",
|
|
192
|
+
"timeline.busy": "Rolling back\u2026",
|
|
193
|
+
"timeline.detail": "Details",
|
|
194
|
+
"timeline.trajectory": "Trajectory",
|
|
195
|
+
"timeline.jump": "Jump",
|
|
196
|
+
"timeline.jumpFailed": "This version lies too far back (beyond the auto-load budget) to locate directly. Scroll up to load earlier messages, or use Details to read the original event text of this version.",
|
|
197
|
+
"timeline.gitRepo": "git repository",
|
|
198
|
+
"timeline.gitHead": "HEAD {hash}",
|
|
199
|
+
"timeline.gitDirty": "working tree has uncommitted changes",
|
|
200
|
+
"timeline.gitInit": "Enable git versioning",
|
|
201
|
+
"timeline.gitInitDesc": "Runs git init in the workspace (adds a minimal .gitignore and a baseline commit); rollback will prefer git.",
|
|
202
|
+
"timeline.gitInitConfirm": "Initialize git in this workspace?",
|
|
100
203
|
"error.generic": "Operation failed; please try again",
|
|
101
204
|
"error.busy": "Stop the current reply before recalling or editing"
|
|
102
205
|
};
|
|
@@ -123,14 +226,49 @@ return {
|
|
|
123
226
|
const { versioning, git, retentionLimit } = getConfig();
|
|
124
227
|
return { "x-retrace-config": JSON.stringify({ versioning, git, retentionLimit }) };
|
|
125
228
|
}
|
|
126
|
-
var
|
|
229
|
+
var CONFIG_VERSION = 3;
|
|
230
|
+
var CONFIG_DEFAULTS = { version: CONFIG_VERSION, showOriginalInput: true, editFromScratch: false, hideShadowed: true, versioning: true, git: true, retentionLimit: 50, prewrite: true };
|
|
127
231
|
var configListeners = /* @__PURE__ */ new Set();
|
|
128
232
|
var configCache = readConfig();
|
|
129
233
|
var editReferences = /* @__PURE__ */ new Map();
|
|
234
|
+
function migrateConfig(parsed) {
|
|
235
|
+
const version = typeof parsed.version === "number" ? parsed.version : 1;
|
|
236
|
+
if (version >= CONFIG_VERSION) return { ...CONFIG_DEFAULTS, ...parsed };
|
|
237
|
+
return {
|
|
238
|
+
...CONFIG_DEFAULTS,
|
|
239
|
+
...parsed,
|
|
240
|
+
version: CONFIG_VERSION,
|
|
241
|
+
editFromScratch: CONFIG_DEFAULTS.editFromScratch,
|
|
242
|
+
hideShadowed: CONFIG_DEFAULTS.hideShadowed
|
|
243
|
+
};
|
|
244
|
+
}
|
|
130
245
|
function readConfig() {
|
|
131
246
|
try {
|
|
132
247
|
const raw = localStorage.getItem(CONFIG_KEY);
|
|
133
|
-
|
|
248
|
+
if (raw !== null) {
|
|
249
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
250
|
+
const migrated = migrateConfig(parsed);
|
|
251
|
+
const storedVersion = typeof parsed.version === "number" ? parsed.version : 1;
|
|
252
|
+
if (storedVersion < CONFIG_VERSION) {
|
|
253
|
+
try {
|
|
254
|
+
localStorage.setItem(CONFIG_KEY, JSON.stringify(migrated));
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return migrated;
|
|
259
|
+
}
|
|
260
|
+
for (const legacyKey of LEGACY_CONFIG_KEYS) {
|
|
261
|
+
const legacyRaw = localStorage.getItem(legacyKey);
|
|
262
|
+
if (legacyRaw !== null) {
|
|
263
|
+
const migrated = migrateConfig(legacyRaw ? JSON.parse(legacyRaw) : {});
|
|
264
|
+
try {
|
|
265
|
+
localStorage.setItem(CONFIG_KEY, JSON.stringify(migrated));
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
return migrated;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return { ...CONFIG_DEFAULTS };
|
|
134
272
|
} catch {
|
|
135
273
|
return { ...CONFIG_DEFAULTS };
|
|
136
274
|
}
|
|
@@ -208,9 +346,11 @@ return {
|
|
|
208
346
|
}
|
|
209
347
|
};
|
|
210
348
|
function markerOpFromId(id) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
349
|
+
for (const p of MARKER_PREFIXES) {
|
|
350
|
+
if (id.startsWith(`${p}-recall-`)) return "recall";
|
|
351
|
+
if (id.startsWith(`${p}-edit-`)) return "edit";
|
|
352
|
+
if (id.startsWith(`${p}-regenerate-`)) return "regenerate";
|
|
353
|
+
}
|
|
214
354
|
return "edit";
|
|
215
355
|
}
|
|
216
356
|
var recallMarkerDefinition = {
|
|
@@ -219,16 +359,22 @@ return {
|
|
|
219
359
|
match: (event) => {
|
|
220
360
|
if (event.type !== "assistant/message" || !isReplacementSurfaceEvent(event)) return null;
|
|
221
361
|
const id = event.data?.message?.id;
|
|
222
|
-
if (
|
|
362
|
+
if (!isMarkerId(id)) return null;
|
|
223
363
|
return { id: `marker:${id}`, role: "start" };
|
|
224
364
|
},
|
|
225
365
|
start: (_context, match) => {
|
|
226
366
|
const event = match.event;
|
|
367
|
+
const id = String(event.data.message.id);
|
|
368
|
+
const legacy = isLegacyMarkerId(id);
|
|
227
369
|
return {
|
|
228
370
|
seq: event.seq,
|
|
229
371
|
time: event.time,
|
|
230
|
-
op: markerOpFromId(
|
|
231
|
-
|
|
372
|
+
op: markerOpFromId(id),
|
|
373
|
+
legacy,
|
|
374
|
+
// Legacy markers never hide: treat their shadowed range as empty so the
|
|
375
|
+
// notice/reference render but no row is hidden and no action row is
|
|
376
|
+
// suppressed via useShadowed.
|
|
377
|
+
shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
|
|
232
378
|
targetSeq: event.data?.editor?.targetSeq,
|
|
233
379
|
text: event.data?.editor?.text
|
|
234
380
|
};
|
|
@@ -264,12 +410,15 @@ return {
|
|
|
264
410
|
return false;
|
|
265
411
|
});
|
|
266
412
|
}
|
|
413
|
+
var SHADOW_SAFETY_RATIO = 0.4;
|
|
267
414
|
function useHiddenKeys(useSession, shadowedSeqs) {
|
|
268
415
|
return useSession((snapshot) => {
|
|
269
416
|
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
|
|
270
417
|
const hidden = new Set(shadowedSeqs);
|
|
271
418
|
const keys = [];
|
|
419
|
+
let rowCount = 0;
|
|
272
420
|
for (const node of snapshot.chat.nodes.values()) {
|
|
421
|
+
if (typeof node.anchorSeq === "number") rowCount += 1;
|
|
273
422
|
if (node.kind === "recall-marker") continue;
|
|
274
423
|
if (node.kind === "turn-tail") {
|
|
275
424
|
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
@@ -283,6 +432,7 @@ return {
|
|
|
283
432
|
}
|
|
284
433
|
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
285
434
|
}
|
|
435
|
+
if (keys.length > 0 && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) return null;
|
|
286
436
|
return keys.length === 0 ? null : keys;
|
|
287
437
|
});
|
|
288
438
|
}
|
|
@@ -483,9 +633,9 @@ return {
|
|
|
483
633
|
]);
|
|
484
634
|
}
|
|
485
635
|
function RecallMarkerRow({ node, useSession, t }) {
|
|
486
|
-
const { seq, op, shadowedSeqs } = node.data;
|
|
636
|
+
const { seq, op, shadowedSeqs, legacy } = node.data;
|
|
487
637
|
const dismissed = useMarkerDismissed(useSession, seq, op);
|
|
488
|
-
const hiddenKeys = useHiddenKeys(useSession, shadowedSeqs);
|
|
638
|
+
const hiddenKeys = legacy || !getConfig().hideShadowed ? null : useHiddenKeys(useSession, shadowedSeqs);
|
|
489
639
|
const css = hiddenKeys === null ? null : hiddenKeys.map((key) => `[data-chat-anchor-key=${JSON.stringify(key)}]{display:none!important}`).join("");
|
|
490
640
|
const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
|
|
491
641
|
const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
|
|
@@ -522,6 +672,7 @@ return {
|
|
|
522
672
|
}),
|
|
523
673
|
(0, import_react.createElement)("span", null, t("options.editFromScratch"))
|
|
524
674
|
]),
|
|
675
|
+
optionRow("hideShadowed", "options.hideShadowed", "options.hideShadowedDesc"),
|
|
525
676
|
optionRow("versioning", "options.versioning", "options.versioningDesc"),
|
|
526
677
|
optionRow("git", "options.git", "options.gitDesc"),
|
|
527
678
|
(0, import_react.createElement)("div", { key: "retention", className: "dsh-rt-option dsh-rt-option-number" }, [
|
|
@@ -584,6 +735,44 @@ return {
|
|
|
584
735
|
.dsh-rt-retention-input{width:64px;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-elevated);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:2px 6px;font:inherit;font-size:13px;outline:none;margin-top:1px}
|
|
585
736
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
586
737
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
738
|
+
/* ---- P1 timeline (conversation view tab) ---- */
|
|
739
|
+
.dsh-rt-view{box-sizing:border-box;display:flex;flex-direction:column;gap:6px;height:100%;min-height:0;padding:12px 16px 0}
|
|
740
|
+
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
741
|
+
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
742
|
+
.dsh-rt-timeline-git{display:flex;align-items:center;gap:6px;flex:none;border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;padding:4px 8px}
|
|
743
|
+
.dsh-rt-timeline-git-text{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}
|
|
744
|
+
.dsh-rt-timeline-list{overflow-y:auto;flex:1;min-height:0;position:relative;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2)}
|
|
745
|
+
.dsh-rt-timeline-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding:12px 4px;text-align:center}
|
|
746
|
+
.dsh-rt-version{position:absolute;left:0;right:0;height:60px;box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;border:1px solid transparent;border-radius:10px;padding:6px 8px}
|
|
747
|
+
.dsh-rt-version:hover{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
|
|
748
|
+
.dsh-rt-version-kind{flex:none;width:22px;height:22px;display:inline-flex;justify-content:center;align-items:center;border-radius:6px;background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-secondary);font-size:12px;margin-top:1px}
|
|
749
|
+
.dsh-rt-version-kind-restore{background:var(--dsw-alias-state-success-bg);color:var(--dsw-alias-state-success-primary)}
|
|
750
|
+
.dsh-rt-version-kind-compaction{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-caption)}
|
|
751
|
+
.dsh-rt-version-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
752
|
+
.dsh-rt-version-line{display:flex;align-items:center;gap:8px;min-width:0;white-space:nowrap}
|
|
753
|
+
.dsh-rt-version-kind-label{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:16px}
|
|
754
|
+
.dsh-rt-version-time,.dsh-rt-version-msgs{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
755
|
+
.dsh-rt-version-files{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis}
|
|
756
|
+
.dsh-rt-version-text{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
757
|
+
.dsh-rt-version-actions{flex:none;display:inline-flex;gap:4px;opacity:0;transition:opacity .1s}
|
|
758
|
+
.dsh-rt-version:hover .dsh-rt-version-actions{opacity:1}
|
|
759
|
+
.dsh-rt-chip-danger{color:var(--dsw-alias-state-error-primary)}
|
|
760
|
+
.dsh-rt-chip-danger:hover{color:var(--dsw-alias-state-error-primary)}
|
|
761
|
+
.dsh-rt-modal{position:absolute;inset:0;z-index:130;display:flex;flex-direction:column;gap:8px;box-sizing:border-box;border-radius:12px;background:var(--dsw-specific-menu);padding:10px;box-shadow:var(--dsw-shadow-lv3)}
|
|
762
|
+
.dsh-rt-modal-title{display:flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px}
|
|
763
|
+
.dsh-rt-modal-sub{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:400}
|
|
764
|
+
.dsh-rt-modal-body{display:flex;flex-direction:column;gap:6px;overflow-y:auto;min-height:0}
|
|
765
|
+
.dsh-rt-modal-line{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
766
|
+
.dsh-rt-modal-files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:160px;overflow-y:auto}
|
|
767
|
+
.dsh-rt-modal-files li{display:flex;gap:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
768
|
+
.dsh-rt-art-restore{color:var(--dsw-alias-state-success-primary);flex:none}
|
|
769
|
+
.dsh-rt-art-delete{color:var(--dsw-alias-state-error-primary);flex:none}
|
|
770
|
+
.dsh-rt-art-skip{color:var(--dsw-alias-label-caption);flex:none}
|
|
771
|
+
.dsh-rt-art-path{font-family:var(--dsw-font-mono);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
772
|
+
.dsh-rt-modal-scope{display:flex;flex-direction:column;gap:4px}
|
|
773
|
+
.dsh-rt-modal-buttons{display:flex;justify-content:flex-end;gap:8px;flex:none}
|
|
774
|
+
.dsh-rt-confirm{background:var(--dsw-alias-state-error-primary)}
|
|
775
|
+
.dsh-rt-modal-json{margin:0;overflow:auto;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-elevated);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:8px;font-family:var(--dsw-font-mono);font-size:11px;line-height:15px;white-space:pre-wrap;word-break:break-all}
|
|
587
776
|
`;
|
|
588
777
|
function ensureStyle() {
|
|
589
778
|
if (typeof document === "undefined") return () => {
|
|
@@ -599,10 +788,273 @@ return {
|
|
|
599
788
|
tag.remove();
|
|
600
789
|
};
|
|
601
790
|
}
|
|
791
|
+
var KIND_ICONS = { recall: "\u21A9", edit: "\u270E", regenerate: "\u21BB", restore: "\u27F2", compaction: "\u25A4", replace: "\u21C4" };
|
|
792
|
+
function kindLabel(kind, t) {
|
|
793
|
+
return t(`timeline.kind.${kind}`) || kind;
|
|
794
|
+
}
|
|
795
|
+
function timeLabel(ms) {
|
|
796
|
+
const date = new Date(ms);
|
|
797
|
+
return `${date.getMonth() + 1}-${date.getDate()} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
798
|
+
}
|
|
799
|
+
function timelineGet(path) {
|
|
800
|
+
return fetch(`${ROUTE_BASE}${path}`, { headers: retraceConfigHeaders() }).then((res) => {
|
|
801
|
+
if (res.status < 200 || res.status >= 300) throw new Error(`HTTP ${res.status}`);
|
|
802
|
+
return res.json();
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
function fileCountLabel(counts, t) {
|
|
806
|
+
const total = (counts?.created ?? 0) + (counts?.modified ?? 0) + (counts?.deleted ?? 0);
|
|
807
|
+
if (total === 0) return t("timeline.filesNone");
|
|
808
|
+
return t("timeline.files", {
|
|
809
|
+
created: counts.created ?? 0,
|
|
810
|
+
modified: counts.modified ?? 0,
|
|
811
|
+
deleted: counts.deleted ?? 0
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
815
|
+
const [versions, setVersions] = (0, import_react.useState)(null);
|
|
816
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
817
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
818
|
+
const [git, setGit] = (0, import_react.useState)(null);
|
|
819
|
+
const [gitBusy, setGitBusy] = (0, import_react.useState)(false);
|
|
820
|
+
const [preview, setPreview] = (0, import_react.useState)(null);
|
|
821
|
+
const [previewScope, setPreviewScope] = (0, import_react.useState)("both");
|
|
822
|
+
const [rollbackBusy, setRollbackBusy] = (0, import_react.useState)(false);
|
|
823
|
+
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
824
|
+
const projected = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
825
|
+
(0, import_react.useEffect)(() => {
|
|
826
|
+
if (projected && Array.isArray(projected.versions)) setVersions(projected.versions);
|
|
827
|
+
}, [projected]);
|
|
828
|
+
const refresh = () => {
|
|
829
|
+
setLoading(true);
|
|
830
|
+
setError(null);
|
|
831
|
+
timelineGet(`/versions?sessionId=${encodeURIComponent(sessionId)}`).then((result) => {
|
|
832
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message ?? "versions failed");
|
|
833
|
+
setVersions(result.value?.versions ?? []);
|
|
834
|
+
}).catch((cause) => setError(cause?.message ?? "timeline error")).finally(() => setLoading(false));
|
|
835
|
+
};
|
|
836
|
+
const refreshGit = () => {
|
|
837
|
+
if (getConfig().git !== true) return;
|
|
838
|
+
timelineGet(`/git/status?sessionId=${encodeURIComponent(sessionId)}`).then((result) => setGit(result?.ok === true ? result.value : null)).catch(() => setGit(null));
|
|
839
|
+
};
|
|
840
|
+
(0, import_react.useEffect)(() => {
|
|
841
|
+
if (projected === void 0) refresh();
|
|
842
|
+
refreshGit();
|
|
843
|
+
}, []);
|
|
844
|
+
const JUMP_PAGE_BUDGET = 24;
|
|
845
|
+
const jump = async (boundarySeq) => {
|
|
846
|
+
actions?.setView?.("chat");
|
|
847
|
+
const target = store;
|
|
848
|
+
if (!target || typeof target.loadOlder !== "function" || typeof target.getSnapshot !== "function") return;
|
|
849
|
+
const keyOfSeq = (seq) => {
|
|
850
|
+
const nodes = target.getSnapshot()?.chat?.nodes;
|
|
851
|
+
if (!nodes) return null;
|
|
852
|
+
for (const node of nodes.values()) {
|
|
853
|
+
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
854
|
+
}
|
|
855
|
+
return null;
|
|
856
|
+
};
|
|
857
|
+
let key = keyOfSeq(boundarySeq);
|
|
858
|
+
let pages = 0;
|
|
859
|
+
while (key === null && pages < JUMP_PAGE_BUDGET && target.hasMore) {
|
|
860
|
+
const before = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
861
|
+
await target.loadOlder();
|
|
862
|
+
const after = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
863
|
+
pages += 1;
|
|
864
|
+
if (after === before) break;
|
|
865
|
+
key = keyOfSeq(boundarySeq);
|
|
866
|
+
}
|
|
867
|
+
if (key === null) {
|
|
868
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
869
|
+
key = keyOfSeq(boundarySeq);
|
|
870
|
+
}
|
|
871
|
+
if (key === null) {
|
|
872
|
+
console.warn(`[dsh-retrace] jump target seq ${boundarySeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
876
|
+
if (el === null) return;
|
|
877
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
878
|
+
flashKey(key);
|
|
879
|
+
};
|
|
880
|
+
function waitForElement(selector, frames) {
|
|
881
|
+
return new Promise((resolve) => {
|
|
882
|
+
let remaining = frames;
|
|
883
|
+
const probe = () => {
|
|
884
|
+
const el = document.querySelector(selector);
|
|
885
|
+
if (el !== null) return resolve(el);
|
|
886
|
+
if (remaining-- <= 0) return resolve(null);
|
|
887
|
+
requestAnimationFrame(probe);
|
|
888
|
+
};
|
|
889
|
+
requestAnimationFrame(probe);
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
function flashKey(key) {
|
|
893
|
+
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
894
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
895
|
+
const tag = document.createElement("style");
|
|
896
|
+
tag.dataset.pluginCss = id;
|
|
897
|
+
tag.textContent = `[data-chat-anchor-key=${JSON.stringify(key)}]{animation:dsh-rt-flash 1.6s ease-out 2}@keyframes dsh-rt-flash{0%,100%{background:transparent}30%,70%{background:var(--dsw-alias-state-business-primary)}55%{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,transparent)}}`;
|
|
898
|
+
document.head.appendChild(tag);
|
|
899
|
+
setTimeout(() => tag.remove(), 3400);
|
|
900
|
+
}
|
|
901
|
+
const requestPreview = (record) => {
|
|
902
|
+
setPreviewScope("both");
|
|
903
|
+
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
904
|
+
callOp("rollback/preview", { sessionId, versionId: record.versionId, scope: "both" }).then((result) => {
|
|
905
|
+
setPreview((prev) => prev && prev.versionId === record.versionId ? { ...prev, data: result?.ok === true ? result.value : null, error: result?.ok === true ? null : result?.error?.message ?? null } : prev);
|
|
906
|
+
});
|
|
907
|
+
};
|
|
908
|
+
const confirmRollback = () => {
|
|
909
|
+
if (!preview) return;
|
|
910
|
+
setRollbackBusy(true);
|
|
911
|
+
callOp("rollback", { sessionId, versionId: preview.versionId, scope: previewScope }).then((result) => {
|
|
912
|
+
setRollbackBusy(false);
|
|
913
|
+
if (!result || result.ok !== true) {
|
|
914
|
+
setPreview((prev) => prev && { ...prev, error: result?.error?.message ?? "rollback failed" });
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
setPreview(null);
|
|
918
|
+
if (projected === void 0) refresh();
|
|
919
|
+
refreshGit();
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
const initGit = () => {
|
|
923
|
+
if (!window.confirm(t("timeline.gitInitConfirm"))) return;
|
|
924
|
+
setGitBusy(true);
|
|
925
|
+
callOp("git/init", { sessionId }).then((result) => {
|
|
926
|
+
setGitBusy(false);
|
|
927
|
+
refreshGit();
|
|
928
|
+
});
|
|
929
|
+
};
|
|
930
|
+
const ROW_H = 64;
|
|
931
|
+
const list = versions ?? [];
|
|
932
|
+
const visible = list.slice(Math.max(0, Math.floor(scrollTop / ROW_H) - 2), Math.min(list.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2));
|
|
933
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
934
|
+
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
935
|
+
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("timeline.title")),
|
|
936
|
+
(0, import_react.createElement)("button", {
|
|
937
|
+
key: "refresh",
|
|
938
|
+
type: "button",
|
|
939
|
+
className: "dsh-rt-chip",
|
|
940
|
+
onClick: refresh
|
|
941
|
+
}, t("timeline.refresh"))
|
|
942
|
+
]),
|
|
943
|
+
git !== null && git !== void 0 && (0, import_react.createElement)("div", { key: "git", className: "dsh-rt-timeline-git" }, [
|
|
944
|
+
git.headHash ? [
|
|
945
|
+
(0, import_react.createElement)("span", { key: "r", className: "dsh-rt-timeline-git-text" }, `${t("timeline.gitRepo")} \xB7 ${t("timeline.gitHead", { hash: git.headHash.slice(0, 8) })}${git.dirty ? ` \xB7 ${t("timeline.gitDirty")}` : ""}`)
|
|
946
|
+
] : (0, import_react.createElement)("button", {
|
|
947
|
+
key: "init",
|
|
948
|
+
type: "button",
|
|
949
|
+
className: "dsh-rt-chip",
|
|
950
|
+
disabled: gitBusy,
|
|
951
|
+
onClick: initGit,
|
|
952
|
+
title: t("timeline.gitInitDesc")
|
|
953
|
+
}, t("timeline.gitInit"))
|
|
954
|
+
]),
|
|
955
|
+
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
956
|
+
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
957
|
+
!loading && list.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("timeline.empty")),
|
|
958
|
+
list.length > 0 && (0, import_react.createElement)("div", {
|
|
959
|
+
key: "list",
|
|
960
|
+
className: "dsh-rt-timeline-list",
|
|
961
|
+
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
962
|
+
}, [
|
|
963
|
+
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${list.length * ROW_H}px`, position: "relative" } }, [
|
|
964
|
+
visible.map((record) => (0, import_react.createElement)(VersionRow, {
|
|
965
|
+
key: record.versionId,
|
|
966
|
+
record,
|
|
967
|
+
t,
|
|
968
|
+
top: list.indexOf(record) * ROW_H,
|
|
969
|
+
onPreview: () => requestPreview(record),
|
|
970
|
+
onTrajectory: () => actions?.setView?.("trajectory"),
|
|
971
|
+
onJump: () => jump(record.boundarySeq)
|
|
972
|
+
}))
|
|
973
|
+
])
|
|
974
|
+
]),
|
|
975
|
+
preview && (0, import_react.createElement)(PreviewBox, {
|
|
976
|
+
key: "preview",
|
|
977
|
+
preview,
|
|
978
|
+
scope: previewScope,
|
|
979
|
+
setScope: setPreviewScope,
|
|
980
|
+
busy: rollbackBusy,
|
|
981
|
+
t,
|
|
982
|
+
onConfirm: confirmRollback,
|
|
983
|
+
onCancel: () => setPreview(null)
|
|
984
|
+
})
|
|
985
|
+
]);
|
|
986
|
+
}
|
|
987
|
+
function VersionRow({ record, top, t, onPreview, onTrajectory, onJump }) {
|
|
988
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-version", style: { top: `${top}px` } }, [
|
|
989
|
+
(0, import_react.createElement)("span", { key: "kind", className: `dsh-rt-version-kind dsh-rt-version-kind-${record.kind}`, title: kindLabel(record.kind, t) }, KIND_ICONS[record.kind] ?? "\u2022"),
|
|
990
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-version-body" }, [
|
|
991
|
+
(0, import_react.createElement)("div", { key: "line1", className: "dsh-rt-version-line" }, [
|
|
992
|
+
(0, import_react.createElement)("span", { key: "kind", className: "dsh-rt-version-kind-label" }, kindLabel(record.kind, t)),
|
|
993
|
+
(0, import_react.createElement)("span", { key: "time", className: "dsh-rt-version-time" }, timeLabel(record.createdAt)),
|
|
994
|
+
(0, import_react.createElement)("span", { key: "msgs", className: "dsh-rt-version-msgs" }, t("timeline.messages", { count: record.messageCount })),
|
|
995
|
+
(0, import_react.createElement)("span", { key: "files", className: "dsh-rt-version-files" }, fileCountLabel(record.fileCounts, t))
|
|
996
|
+
]),
|
|
997
|
+
record.markerText ? (0, import_react.createElement)("div", { key: "text", className: "dsh-rt-version-text" }, record.markerText.length > 120 ? `${record.markerText.slice(0, 120)}\u2026` : record.markerText) : null
|
|
998
|
+
]),
|
|
999
|
+
(0, import_react.createElement)("span", { key: "actions", className: "dsh-rt-version-actions" }, [
|
|
1000
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump")),
|
|
1001
|
+
(0, import_react.createElement)("button", { key: "trajectory", type: "button", className: "dsh-rt-chip", onClick: onTrajectory, title: t("timeline.trajectory") }, t("timeline.trajectory")),
|
|
1002
|
+
(0, import_react.createElement)("button", { key: "preview", type: "button", className: "dsh-rt-chip dsh-rt-chip-danger", onClick: onPreview }, t("timeline.preview"))
|
|
1003
|
+
])
|
|
1004
|
+
]);
|
|
1005
|
+
}
|
|
1006
|
+
function PreviewBox({ preview, scope, setScope, busy, t, onConfirm, onCancel }) {
|
|
1007
|
+
const data = preview.data;
|
|
1008
|
+
const scopes = [
|
|
1009
|
+
["context", "timeline.contextOnly", "timeline.contextOnlyDesc"],
|
|
1010
|
+
["artifacts", "timeline.artifactsOnly", "timeline.artifactsOnlyDesc"],
|
|
1011
|
+
["both", "timeline.both", "timeline.bothDesc"]
|
|
1012
|
+
];
|
|
1013
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-modal" }, [
|
|
1014
|
+
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-modal-title" }, [
|
|
1015
|
+
t("timeline.preview"),
|
|
1016
|
+
(0, import_react.createElement)("span", { key: "ver", className: "dsh-rt-modal-sub" }, t("timeline.previewDesc", { version: preview.versionId, kind: kindLabel(preview.kind, t) }))
|
|
1017
|
+
]),
|
|
1018
|
+
preview.error && (0, import_react.createElement)("div", { key: "err", className: "dsh-rt-error" }, preview.error),
|
|
1019
|
+
data === null && !preview.error && (0, import_react.createElement)("div", { key: "wait", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1020
|
+
data && (0, import_react.createElement)("div", { key: "body", className: "dsh-rt-modal-body" }, [
|
|
1021
|
+
(0, import_react.createElement)(
|
|
1022
|
+
"div",
|
|
1023
|
+
{ key: "ctx", className: "dsh-rt-modal-line" },
|
|
1024
|
+
data.context?.messages > 0 ? t("timeline.messagesRemoved", { count: data.context.messages }) : t("timeline.noChanges")
|
|
1025
|
+
),
|
|
1026
|
+
(0, import_react.createElement)("div", { key: "art", className: "dsh-rt-modal-line" }, t("timeline.artifactsList", { count: data.artifacts?.rows?.length ?? 0 })),
|
|
1027
|
+
(0, import_react.createElement)("ul", { key: "files", className: "dsh-rt-modal-files" }, (data.artifacts?.rows ?? []).slice(0, 12).map((row) => (0, import_react.createElement)("li", { key: row.path }, [
|
|
1028
|
+
(0, import_react.createElement)(
|
|
1029
|
+
"span",
|
|
1030
|
+
{ key: "a", className: `dsh-rt-art-${row.action}` },
|
|
1031
|
+
row.action === "skip" ? `${t("timeline.artifact.skip")} (${row.reason ?? ""})` : row.action === "delete" ? t("timeline.artifact.delete") : t("timeline.artifact.restore")
|
|
1032
|
+
),
|
|
1033
|
+
(0, import_react.createElement)("span", { key: "p", className: "dsh-rt-art-path" }, row.path)
|
|
1034
|
+
]))),
|
|
1035
|
+
(0, import_react.createElement)("div", { key: "scope", className: "dsh-rt-modal-scope" }, scopes.map(([value, labelKey, descKey]) => (0, import_react.createElement)("label", { key: value, className: "dsh-rt-option" }, [
|
|
1036
|
+
(0, import_react.createElement)("input", { type: "radio", name: "rt-scope", checked: scope === value, onChange: () => setScope(value) }),
|
|
1037
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-text" }, [
|
|
1038
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-label" }, t(labelKey)),
|
|
1039
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-desc" }, t(descKey))
|
|
1040
|
+
])
|
|
1041
|
+
])))
|
|
1042
|
+
]),
|
|
1043
|
+
(0, import_react.createElement)("div", { key: "buttons", className: "dsh-rt-modal-buttons" }, [
|
|
1044
|
+
(0, import_react.createElement)("button", { key: "cancel", type: "button", className: "dsh-rt-editor-cancel", disabled: busy, onClick: onCancel }, t("timeline.cancel")),
|
|
1045
|
+
(0, import_react.createElement)(
|
|
1046
|
+
"button",
|
|
1047
|
+
{ key: "confirm", type: "button", className: "dsh-rt-editor-send dsh-rt-confirm", disabled: busy || preview.error !== null, onClick: onConfirm },
|
|
1048
|
+
busy ? t("timeline.busy") : t("timeline.confirm")
|
|
1049
|
+
)
|
|
1050
|
+
])
|
|
1051
|
+
]);
|
|
1052
|
+
}
|
|
602
1053
|
function apply(ctx) {
|
|
603
1054
|
const disposeStyle = ensureStyle();
|
|
604
1055
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
605
1056
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-retrace: dictionaries");
|
|
1057
|
+
const t = ctx.locale.bind(NS);
|
|
606
1058
|
const conversationEvents = ctx.get("conversationEvents");
|
|
607
1059
|
if (conversationEvents) {
|
|
608
1060
|
conversationEvents.register(userActionsDefinition);
|
|
@@ -630,6 +1082,17 @@ return {
|
|
|
630
1082
|
key: "recall-marker",
|
|
631
1083
|
locale: NS
|
|
632
1084
|
}, RecallMarkerRow));
|
|
1085
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1086
|
+
name: "conversation.view",
|
|
1087
|
+
id: "retrace",
|
|
1088
|
+
order: 20,
|
|
1089
|
+
locale: NS,
|
|
1090
|
+
label: () => t("view.retrace"),
|
|
1091
|
+
inject: (sessionId, actions) => ({
|
|
1092
|
+
actions,
|
|
1093
|
+
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1094
|
+
})
|
|
1095
|
+
}, RetraceView));
|
|
633
1096
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
634
1097
|
name: "settings.general.item",
|
|
635
1098
|
id: "retrace",
|