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/client.bundle.js
CHANGED
|
@@ -37,7 +37,16 @@ var inject = ["slots", "locale", "conversationEvents"];
|
|
|
37
37
|
var NS = "retrace";
|
|
38
38
|
var ROUTE_BASE = "/api/plugins/retrace";
|
|
39
39
|
var MARKER_PREFIX = "retrace";
|
|
40
|
+
var MARKER_PREFIXES = [MARKER_PREFIX, "message-editor"];
|
|
41
|
+
var LEGACY_MARKER_PREFIXES = ["message-editor"];
|
|
42
|
+
function isMarkerId(id) {
|
|
43
|
+
return typeof id === "string" && MARKER_PREFIXES.some((p) => id.startsWith(`${p}-`));
|
|
44
|
+
}
|
|
45
|
+
function isLegacyMarkerId(id) {
|
|
46
|
+
return typeof id === "string" && LEGACY_MARKER_PREFIXES.some((p) => id.startsWith(`${p}-`));
|
|
47
|
+
}
|
|
40
48
|
var CONFIG_KEY = "dsh-retrace:config";
|
|
49
|
+
var LEGACY_CONFIG_KEYS = ["dsh-message-editor:config"];
|
|
41
50
|
var zh = {
|
|
42
51
|
"action.edit": "\u7F16\u8F91",
|
|
43
52
|
"action.editAria": "\u7F16\u8F91\u8FD9\u6761\u6D88\u606F",
|
|
@@ -56,13 +65,60 @@ var zh = {
|
|
|
56
65
|
"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",
|
|
57
66
|
"options.title": "\u6D88\u606F\u7F16\u8F91\u63D2\u4EF6",
|
|
58
67
|
"options.showOriginalInput": "\u7F16\u8F91\u540E\u663E\u793A\u539F\u63D0\u95EE\u5BF9\u7167",
|
|
59
|
-
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF09",
|
|
68
|
+
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF0C\u9ED8\u8BA4\u5173\uFF09",
|
|
69
|
+
"options.hideShadowed": "\u6309\u6807\u8BB0\u9690\u85CF\u88AB\u7F16\u8F91/\u64A4\u56DE\u7684\u6D88\u606F",
|
|
70
|
+
"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",
|
|
60
71
|
"options.versioning": "\u7248\u672C\u4E0E\u4EA7\u7269\u5FEB\u7167",
|
|
61
72
|
"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",
|
|
62
73
|
"options.git": "\u542F\u7528 git \u96C6\u6210",
|
|
63
74
|
"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",
|
|
64
75
|
"options.retention": "\u7248\u672C\u4FDD\u7559\u4E0A\u9650",
|
|
65
76
|
"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",
|
|
77
|
+
"timeline.open": "\u65F6\u95F4\u7EBF",
|
|
78
|
+
"timeline.openAria": "\u6253\u5F00\u4F1A\u8BDD\u65F6\u95F4\u7EBF",
|
|
79
|
+
"timeline.title": "\u7248\u672C\u65F6\u95F4\u7EBF",
|
|
80
|
+
"view.retrace": "\u7248\u672C",
|
|
81
|
+
"timeline.refresh": "\u5237\u65B0",
|
|
82
|
+
"timeline.close": "\u5173\u95ED",
|
|
83
|
+
"timeline.empty": "\u8FD8\u6CA1\u6709\u7248\u672C\u3002\u64A4\u56DE / \u7F16\u8F91 / \u91CD\u65B0\u751F\u6210\u4F1A\u5728\u6B64\u8BB0\u5F55\u7248\u672C\u3002",
|
|
84
|
+
"timeline.loading": "\u52A0\u8F7D\u4E2D\u2026",
|
|
85
|
+
"timeline.error": "\u65F6\u95F4\u7EBF\u52A0\u8F7D\u5931\u8D25",
|
|
86
|
+
"timeline.kind.recall": "\u64A4\u56DE",
|
|
87
|
+
"timeline.kind.edit": "\u7F16\u8F91\u91CD\u53D1",
|
|
88
|
+
"timeline.kind.regenerate": "\u91CD\u65B0\u751F\u6210",
|
|
89
|
+
"timeline.kind.restore": "\u6062\u590D",
|
|
90
|
+
"timeline.kind.compaction": "\u538B\u7F29",
|
|
91
|
+
"timeline.kind.replace": "\u66FF\u6362",
|
|
92
|
+
"timeline.messages": "{count} \u6761\u6D88\u606F",
|
|
93
|
+
"timeline.files": "{created} \u589E / {modified} \u6539 / {deleted} \u5220",
|
|
94
|
+
"timeline.filesNone": "\u65E0\u6587\u4EF6\u53D8\u66F4",
|
|
95
|
+
"timeline.preview": "\u56DE\u9000\u9884\u89C8",
|
|
96
|
+
"timeline.previewDesc": "\u5C06\u56DE\u9000\u5230\u7248\u672C {version}\uFF08{kind}\uFF09\u3002",
|
|
97
|
+
"timeline.contextOnly": "\u4EC5\u5BF9\u8BDD",
|
|
98
|
+
"timeline.contextOnlyDesc": "\u79FB\u9664\u8BE5\u7248\u672C\u4E4B\u540E\u7684\u6D88\u606F\uFF08\u4FDD\u7559\u65E5\u5FD7\u5BA1\u8BA1\u75D5\u8FF9\uFF09",
|
|
99
|
+
"timeline.artifactsOnly": "\u4EC5\u4EA7\u7269",
|
|
100
|
+
"timeline.artifactsOnlyDesc": "\u628A\u8BE5\u7248\u672C\u89E6\u78B0\u7684\u6587\u4EF6\u6062\u590D\u5230\u5F53\u65F6\u7684\u5167\u5BB9",
|
|
101
|
+
"timeline.both": "\u4E24\u8005",
|
|
102
|
+
"timeline.bothDesc": "\u5148\u56DE\u9000\u5BF9\u8BDD\uFF0C\u518D\u56DE\u9000\u4EA7\u7269",
|
|
103
|
+
"timeline.messagesRemoved": "\u5C06\u79FB\u9664 {count} \u6761\u6D88\u606F",
|
|
104
|
+
"timeline.noChanges": "\u5F53\u524D\u5DF2\u5728\u8BE5\u7248\u672C\u72B6\u6001\uFF0C\u65E0\u53D8\u5316",
|
|
105
|
+
"timeline.artifactsList": "\u4EA7\u7269\u52A8\u4F5C\uFF08{count}\uFF09",
|
|
106
|
+
"timeline.artifact.restore": "\u6062\u590D",
|
|
107
|
+
"timeline.artifact.delete": "\u5220\u9664",
|
|
108
|
+
"timeline.artifact.skip": "\u8DF3\u8FC7",
|
|
109
|
+
"timeline.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
110
|
+
"timeline.cancel": "\u53D6\u6D88",
|
|
111
|
+
"timeline.busy": "\u56DE\u9000\u4E2D\u2026",
|
|
112
|
+
"timeline.detail": "\u8BE6\u60C5",
|
|
113
|
+
"timeline.trajectory": "\u8F68\u8FF9\u53F0\u8D26",
|
|
114
|
+
"timeline.jump": "\u8DF3\u8F6C",
|
|
115
|
+
"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",
|
|
116
|
+
"timeline.gitRepo": "git \u4ED3\u5E93",
|
|
117
|
+
"timeline.gitHead": "HEAD {hash}",
|
|
118
|
+
"timeline.gitDirty": "\u5DE5\u4F5C\u533A\u6709\u672A\u63D0\u4EA4\u6539\u52A8",
|
|
119
|
+
"timeline.gitInit": "\u542F\u7528 git \u7248\u672C\u7BA1\u7406",
|
|
120
|
+
"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",
|
|
121
|
+
"timeline.gitInitConfirm": "\u786E\u5B9A\u8981\u521D\u59CB\u5316 git \u5417\uFF1F",
|
|
66
122
|
"error.generic": "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
67
123
|
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C"
|
|
68
124
|
};
|
|
@@ -84,13 +140,60 @@ var en = {
|
|
|
84
140
|
"marker.referenceHint": "Click to expand the original input (reference only, never sent to the model)",
|
|
85
141
|
"options.title": "Message editor plugin",
|
|
86
142
|
"options.showOriginalInput": "Show the original input after editing",
|
|
87
|
-
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages)",
|
|
143
|
+
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages, default off)",
|
|
144
|
+
"options.hideShadowed": "Hide shadowed messages per marker",
|
|
145
|
+
"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.",
|
|
88
146
|
"options.versioning": "Version & artifact snapshots",
|
|
89
147
|
"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).",
|
|
90
148
|
"options.git": "Git integration",
|
|
91
149
|
"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.",
|
|
92
150
|
"options.retention": "Version retention limit",
|
|
93
151
|
"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).",
|
|
152
|
+
"timeline.open": "Timeline",
|
|
153
|
+
"timeline.openAria": "Open the session timeline",
|
|
154
|
+
"timeline.title": "Version timeline",
|
|
155
|
+
"view.retrace": "Versions",
|
|
156
|
+
"timeline.refresh": "Refresh",
|
|
157
|
+
"timeline.close": "Close",
|
|
158
|
+
"timeline.empty": "No versions yet. Recall / edit / regenerate record a version here.",
|
|
159
|
+
"timeline.loading": "Loading\u2026",
|
|
160
|
+
"timeline.error": "Failed to load the timeline",
|
|
161
|
+
"timeline.kind.recall": "Recall",
|
|
162
|
+
"timeline.kind.edit": "Edit & resend",
|
|
163
|
+
"timeline.kind.regenerate": "Regenerate",
|
|
164
|
+
"timeline.kind.restore": "Restore",
|
|
165
|
+
"timeline.kind.compaction": "Compaction",
|
|
166
|
+
"timeline.kind.replace": "Replace",
|
|
167
|
+
"timeline.messages": "{count} messages",
|
|
168
|
+
"timeline.files": "{created} created / {modified} modified / {deleted} deleted",
|
|
169
|
+
"timeline.filesNone": "No file changes",
|
|
170
|
+
"timeline.preview": "Rollback preview",
|
|
171
|
+
"timeline.previewDesc": "Will roll back to version {version} ({kind}).",
|
|
172
|
+
"timeline.contextOnly": "Context only",
|
|
173
|
+
"timeline.contextOnlyDesc": "Remove messages after this version (the log audit trail stays)",
|
|
174
|
+
"timeline.artifactsOnly": "Artifacts only",
|
|
175
|
+
"timeline.artifactsOnlyDesc": "Restore the files this version touched to their state at that version",
|
|
176
|
+
"timeline.both": "Both",
|
|
177
|
+
"timeline.bothDesc": "Roll back the context first, then the artifacts",
|
|
178
|
+
"timeline.messagesRemoved": "{count} messages will be removed",
|
|
179
|
+
"timeline.noChanges": "Already at this version; nothing to change",
|
|
180
|
+
"timeline.artifactsList": "Artifact actions ({count})",
|
|
181
|
+
"timeline.artifact.restore": "restore",
|
|
182
|
+
"timeline.artifact.delete": "delete",
|
|
183
|
+
"timeline.artifact.skip": "skip",
|
|
184
|
+
"timeline.confirm": "Confirm rollback",
|
|
185
|
+
"timeline.cancel": "Cancel",
|
|
186
|
+
"timeline.busy": "Rolling back\u2026",
|
|
187
|
+
"timeline.detail": "Details",
|
|
188
|
+
"timeline.trajectory": "Trajectory",
|
|
189
|
+
"timeline.jump": "Jump",
|
|
190
|
+
"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.",
|
|
191
|
+
"timeline.gitRepo": "git repository",
|
|
192
|
+
"timeline.gitHead": "HEAD {hash}",
|
|
193
|
+
"timeline.gitDirty": "working tree has uncommitted changes",
|
|
194
|
+
"timeline.gitInit": "Enable git versioning",
|
|
195
|
+
"timeline.gitInitDesc": "Runs git init in the workspace (adds a minimal .gitignore and a baseline commit); rollback will prefer git.",
|
|
196
|
+
"timeline.gitInitConfirm": "Initialize git in this workspace?",
|
|
94
197
|
"error.generic": "Operation failed; please try again",
|
|
95
198
|
"error.busy": "Stop the current reply before recalling or editing"
|
|
96
199
|
};
|
|
@@ -117,14 +220,49 @@ function retraceConfigHeaders() {
|
|
|
117
220
|
const { versioning, git, retentionLimit } = getConfig();
|
|
118
221
|
return { "x-retrace-config": JSON.stringify({ versioning, git, retentionLimit }) };
|
|
119
222
|
}
|
|
120
|
-
var
|
|
223
|
+
var CONFIG_VERSION = 3;
|
|
224
|
+
var CONFIG_DEFAULTS = { version: CONFIG_VERSION, showOriginalInput: true, editFromScratch: false, hideShadowed: true, versioning: true, git: true, retentionLimit: 50, prewrite: true };
|
|
121
225
|
var configListeners = /* @__PURE__ */ new Set();
|
|
122
226
|
var configCache = readConfig();
|
|
123
227
|
var editReferences = /* @__PURE__ */ new Map();
|
|
228
|
+
function migrateConfig(parsed) {
|
|
229
|
+
const version = typeof parsed.version === "number" ? parsed.version : 1;
|
|
230
|
+
if (version >= CONFIG_VERSION) return { ...CONFIG_DEFAULTS, ...parsed };
|
|
231
|
+
return {
|
|
232
|
+
...CONFIG_DEFAULTS,
|
|
233
|
+
...parsed,
|
|
234
|
+
version: CONFIG_VERSION,
|
|
235
|
+
editFromScratch: CONFIG_DEFAULTS.editFromScratch,
|
|
236
|
+
hideShadowed: CONFIG_DEFAULTS.hideShadowed
|
|
237
|
+
};
|
|
238
|
+
}
|
|
124
239
|
function readConfig() {
|
|
125
240
|
try {
|
|
126
241
|
const raw = localStorage.getItem(CONFIG_KEY);
|
|
127
|
-
|
|
242
|
+
if (raw !== null) {
|
|
243
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
244
|
+
const migrated = migrateConfig(parsed);
|
|
245
|
+
const storedVersion = typeof parsed.version === "number" ? parsed.version : 1;
|
|
246
|
+
if (storedVersion < CONFIG_VERSION) {
|
|
247
|
+
try {
|
|
248
|
+
localStorage.setItem(CONFIG_KEY, JSON.stringify(migrated));
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return migrated;
|
|
253
|
+
}
|
|
254
|
+
for (const legacyKey of LEGACY_CONFIG_KEYS) {
|
|
255
|
+
const legacyRaw = localStorage.getItem(legacyKey);
|
|
256
|
+
if (legacyRaw !== null) {
|
|
257
|
+
const migrated = migrateConfig(legacyRaw ? JSON.parse(legacyRaw) : {});
|
|
258
|
+
try {
|
|
259
|
+
localStorage.setItem(CONFIG_KEY, JSON.stringify(migrated));
|
|
260
|
+
} catch {
|
|
261
|
+
}
|
|
262
|
+
return migrated;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return { ...CONFIG_DEFAULTS };
|
|
128
266
|
} catch {
|
|
129
267
|
return { ...CONFIG_DEFAULTS };
|
|
130
268
|
}
|
|
@@ -202,9 +340,11 @@ var userReferenceDefinition = {
|
|
|
202
340
|
}
|
|
203
341
|
};
|
|
204
342
|
function markerOpFromId(id) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
343
|
+
for (const p of MARKER_PREFIXES) {
|
|
344
|
+
if (id.startsWith(`${p}-recall-`)) return "recall";
|
|
345
|
+
if (id.startsWith(`${p}-edit-`)) return "edit";
|
|
346
|
+
if (id.startsWith(`${p}-regenerate-`)) return "regenerate";
|
|
347
|
+
}
|
|
208
348
|
return "edit";
|
|
209
349
|
}
|
|
210
350
|
var recallMarkerDefinition = {
|
|
@@ -213,16 +353,22 @@ var recallMarkerDefinition = {
|
|
|
213
353
|
match: (event) => {
|
|
214
354
|
if (event.type !== "assistant/message" || !isReplacementSurfaceEvent(event)) return null;
|
|
215
355
|
const id = event.data?.message?.id;
|
|
216
|
-
if (
|
|
356
|
+
if (!isMarkerId(id)) return null;
|
|
217
357
|
return { id: `marker:${id}`, role: "start" };
|
|
218
358
|
},
|
|
219
359
|
start: (_context, match) => {
|
|
220
360
|
const event = match.event;
|
|
361
|
+
const id = String(event.data.message.id);
|
|
362
|
+
const legacy = isLegacyMarkerId(id);
|
|
221
363
|
return {
|
|
222
364
|
seq: event.seq,
|
|
223
365
|
time: event.time,
|
|
224
|
-
op: markerOpFromId(
|
|
225
|
-
|
|
366
|
+
op: markerOpFromId(id),
|
|
367
|
+
legacy,
|
|
368
|
+
// Legacy markers never hide: treat their shadowed range as empty so the
|
|
369
|
+
// notice/reference render but no row is hidden and no action row is
|
|
370
|
+
// suppressed via useShadowed.
|
|
371
|
+
shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
|
|
226
372
|
targetSeq: event.data?.editor?.targetSeq,
|
|
227
373
|
text: event.data?.editor?.text
|
|
228
374
|
};
|
|
@@ -258,12 +404,15 @@ function useShadowed(useSession, seq) {
|
|
|
258
404
|
return false;
|
|
259
405
|
});
|
|
260
406
|
}
|
|
407
|
+
var SHADOW_SAFETY_RATIO = 0.4;
|
|
261
408
|
function useHiddenKeys(useSession, shadowedSeqs) {
|
|
262
409
|
return useSession((snapshot) => {
|
|
263
410
|
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
|
|
264
411
|
const hidden = new Set(shadowedSeqs);
|
|
265
412
|
const keys = [];
|
|
413
|
+
let rowCount = 0;
|
|
266
414
|
for (const node of snapshot.chat.nodes.values()) {
|
|
415
|
+
if (typeof node.anchorSeq === "number") rowCount += 1;
|
|
267
416
|
if (node.kind === "recall-marker") continue;
|
|
268
417
|
if (node.kind === "turn-tail") {
|
|
269
418
|
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
@@ -277,6 +426,7 @@ function useHiddenKeys(useSession, shadowedSeqs) {
|
|
|
277
426
|
}
|
|
278
427
|
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
279
428
|
}
|
|
429
|
+
if (keys.length > 0 && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) return null;
|
|
280
430
|
return keys.length === 0 ? null : keys;
|
|
281
431
|
});
|
|
282
432
|
}
|
|
@@ -477,9 +627,9 @@ function UserActionsRow({ node, sessionId, useSession, inputActions, t }) {
|
|
|
477
627
|
]);
|
|
478
628
|
}
|
|
479
629
|
function RecallMarkerRow({ node, useSession, t }) {
|
|
480
|
-
const { seq, op, shadowedSeqs } = node.data;
|
|
630
|
+
const { seq, op, shadowedSeqs, legacy } = node.data;
|
|
481
631
|
const dismissed = useMarkerDismissed(useSession, seq, op);
|
|
482
|
-
const hiddenKeys = useHiddenKeys(useSession, shadowedSeqs);
|
|
632
|
+
const hiddenKeys = legacy || !getConfig().hideShadowed ? null : useHiddenKeys(useSession, shadowedSeqs);
|
|
483
633
|
const css = hiddenKeys === null ? null : hiddenKeys.map((key) => `[data-chat-anchor-key=${JSON.stringify(key)}]{display:none!important}`).join("");
|
|
484
634
|
const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
|
|
485
635
|
const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
|
|
@@ -516,6 +666,7 @@ function OptionsRow({ t }) {
|
|
|
516
666
|
}),
|
|
517
667
|
(0, import_react.createElement)("span", null, t("options.editFromScratch"))
|
|
518
668
|
]),
|
|
669
|
+
optionRow("hideShadowed", "options.hideShadowed", "options.hideShadowedDesc"),
|
|
519
670
|
optionRow("versioning", "options.versioning", "options.versioningDesc"),
|
|
520
671
|
optionRow("git", "options.git", "options.gitDesc"),
|
|
521
672
|
(0, import_react.createElement)("div", { key: "retention", className: "dsh-rt-option dsh-rt-option-number" }, [
|
|
@@ -578,6 +729,44 @@ var CSS = `
|
|
|
578
729
|
.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}
|
|
579
730
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
580
731
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
732
|
+
/* ---- P1 timeline (conversation view tab) ---- */
|
|
733
|
+
.dsh-rt-view{box-sizing:border-box;display:flex;flex-direction:column;gap:6px;height:100%;min-height:0;padding:12px 16px 0}
|
|
734
|
+
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
735
|
+
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
736
|
+
.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}
|
|
737
|
+
.dsh-rt-timeline-git-text{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}
|
|
738
|
+
.dsh-rt-timeline-list{overflow-y:auto;flex:1;min-height:0;position:relative;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2)}
|
|
739
|
+
.dsh-rt-timeline-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding:12px 4px;text-align:center}
|
|
740
|
+
.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}
|
|
741
|
+
.dsh-rt-version:hover{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
|
|
742
|
+
.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}
|
|
743
|
+
.dsh-rt-version-kind-restore{background:var(--dsw-alias-state-success-bg);color:var(--dsw-alias-state-success-primary)}
|
|
744
|
+
.dsh-rt-version-kind-compaction{background:var(--dsw-alias-fill-l2);color:var(--dsw-alias-label-caption)}
|
|
745
|
+
.dsh-rt-version-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
746
|
+
.dsh-rt-version-line{display:flex;align-items:center;gap:8px;min-width:0;white-space:nowrap}
|
|
747
|
+
.dsh-rt-version-kind-label{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;line-height:16px}
|
|
748
|
+
.dsh-rt-version-time,.dsh-rt-version-msgs{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}
|
|
749
|
+
.dsh-rt-version-files{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis}
|
|
750
|
+
.dsh-rt-version-text{color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
751
|
+
.dsh-rt-version-actions{flex:none;display:inline-flex;gap:4px;opacity:0;transition:opacity .1s}
|
|
752
|
+
.dsh-rt-version:hover .dsh-rt-version-actions{opacity:1}
|
|
753
|
+
.dsh-rt-chip-danger{color:var(--dsw-alias-state-error-primary)}
|
|
754
|
+
.dsh-rt-chip-danger:hover{color:var(--dsw-alias-state-error-primary)}
|
|
755
|
+
.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)}
|
|
756
|
+
.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}
|
|
757
|
+
.dsh-rt-modal-sub{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:400}
|
|
758
|
+
.dsh-rt-modal-body{display:flex;flex-direction:column;gap:6px;overflow-y:auto;min-height:0}
|
|
759
|
+
.dsh-rt-modal-line{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
|
760
|
+
.dsh-rt-modal-files{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px;max-height:160px;overflow-y:auto}
|
|
761
|
+
.dsh-rt-modal-files li{display:flex;gap:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
762
|
+
.dsh-rt-art-restore{color:var(--dsw-alias-state-success-primary);flex:none}
|
|
763
|
+
.dsh-rt-art-delete{color:var(--dsw-alias-state-error-primary);flex:none}
|
|
764
|
+
.dsh-rt-art-skip{color:var(--dsw-alias-label-caption);flex:none}
|
|
765
|
+
.dsh-rt-art-path{font-family:var(--dsw-font-mono);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
766
|
+
.dsh-rt-modal-scope{display:flex;flex-direction:column;gap:4px}
|
|
767
|
+
.dsh-rt-modal-buttons{display:flex;justify-content:flex-end;gap:8px;flex:none}
|
|
768
|
+
.dsh-rt-confirm{background:var(--dsw-alias-state-error-primary)}
|
|
769
|
+
.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}
|
|
581
770
|
`;
|
|
582
771
|
function ensureStyle() {
|
|
583
772
|
if (typeof document === "undefined") return () => {
|
|
@@ -593,10 +782,273 @@ function ensureStyle() {
|
|
|
593
782
|
tag.remove();
|
|
594
783
|
};
|
|
595
784
|
}
|
|
785
|
+
var KIND_ICONS = { recall: "\u21A9", edit: "\u270E", regenerate: "\u21BB", restore: "\u27F2", compaction: "\u25A4", replace: "\u21C4" };
|
|
786
|
+
function kindLabel(kind, t) {
|
|
787
|
+
return t(`timeline.kind.${kind}`) || kind;
|
|
788
|
+
}
|
|
789
|
+
function timeLabel(ms) {
|
|
790
|
+
const date = new Date(ms);
|
|
791
|
+
return `${date.getMonth() + 1}-${date.getDate()} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
792
|
+
}
|
|
793
|
+
function timelineGet(path) {
|
|
794
|
+
return fetch(`${ROUTE_BASE}${path}`, { headers: retraceConfigHeaders() }).then((res) => {
|
|
795
|
+
if (res.status < 200 || res.status >= 300) throw new Error(`HTTP ${res.status}`);
|
|
796
|
+
return res.json();
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
function fileCountLabel(counts, t) {
|
|
800
|
+
const total = (counts?.created ?? 0) + (counts?.modified ?? 0) + (counts?.deleted ?? 0);
|
|
801
|
+
if (total === 0) return t("timeline.filesNone");
|
|
802
|
+
return t("timeline.files", {
|
|
803
|
+
created: counts.created ?? 0,
|
|
804
|
+
modified: counts.modified ?? 0,
|
|
805
|
+
deleted: counts.deleted ?? 0
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
809
|
+
const [versions, setVersions] = (0, import_react.useState)(null);
|
|
810
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
811
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
812
|
+
const [git, setGit] = (0, import_react.useState)(null);
|
|
813
|
+
const [gitBusy, setGitBusy] = (0, import_react.useState)(false);
|
|
814
|
+
const [preview, setPreview] = (0, import_react.useState)(null);
|
|
815
|
+
const [previewScope, setPreviewScope] = (0, import_react.useState)("both");
|
|
816
|
+
const [rollbackBusy, setRollbackBusy] = (0, import_react.useState)(false);
|
|
817
|
+
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
818
|
+
const projected = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
819
|
+
(0, import_react.useEffect)(() => {
|
|
820
|
+
if (projected && Array.isArray(projected.versions)) setVersions(projected.versions);
|
|
821
|
+
}, [projected]);
|
|
822
|
+
const refresh = () => {
|
|
823
|
+
setLoading(true);
|
|
824
|
+
setError(null);
|
|
825
|
+
timelineGet(`/versions?sessionId=${encodeURIComponent(sessionId)}`).then((result) => {
|
|
826
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message ?? "versions failed");
|
|
827
|
+
setVersions(result.value?.versions ?? []);
|
|
828
|
+
}).catch((cause) => setError(cause?.message ?? "timeline error")).finally(() => setLoading(false));
|
|
829
|
+
};
|
|
830
|
+
const refreshGit = () => {
|
|
831
|
+
if (getConfig().git !== true) return;
|
|
832
|
+
timelineGet(`/git/status?sessionId=${encodeURIComponent(sessionId)}`).then((result) => setGit(result?.ok === true ? result.value : null)).catch(() => setGit(null));
|
|
833
|
+
};
|
|
834
|
+
(0, import_react.useEffect)(() => {
|
|
835
|
+
if (projected === void 0) refresh();
|
|
836
|
+
refreshGit();
|
|
837
|
+
}, []);
|
|
838
|
+
const JUMP_PAGE_BUDGET = 24;
|
|
839
|
+
const jump = async (boundarySeq) => {
|
|
840
|
+
actions?.setView?.("chat");
|
|
841
|
+
const target = store;
|
|
842
|
+
if (!target || typeof target.loadOlder !== "function" || typeof target.getSnapshot !== "function") return;
|
|
843
|
+
const keyOfSeq = (seq) => {
|
|
844
|
+
const nodes = target.getSnapshot()?.chat?.nodes;
|
|
845
|
+
if (!nodes) return null;
|
|
846
|
+
for (const node of nodes.values()) {
|
|
847
|
+
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
};
|
|
851
|
+
let key = keyOfSeq(boundarySeq);
|
|
852
|
+
let pages = 0;
|
|
853
|
+
while (key === null && pages < JUMP_PAGE_BUDGET && target.hasMore) {
|
|
854
|
+
const before = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
855
|
+
await target.loadOlder();
|
|
856
|
+
const after = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
857
|
+
pages += 1;
|
|
858
|
+
if (after === before) break;
|
|
859
|
+
key = keyOfSeq(boundarySeq);
|
|
860
|
+
}
|
|
861
|
+
if (key === null) {
|
|
862
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
863
|
+
key = keyOfSeq(boundarySeq);
|
|
864
|
+
}
|
|
865
|
+
if (key === null) {
|
|
866
|
+
console.warn(`[dsh-retrace] jump target seq ${boundarySeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
870
|
+
if (el === null) return;
|
|
871
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
872
|
+
flashKey(key);
|
|
873
|
+
};
|
|
874
|
+
function waitForElement(selector, frames) {
|
|
875
|
+
return new Promise((resolve) => {
|
|
876
|
+
let remaining = frames;
|
|
877
|
+
const probe = () => {
|
|
878
|
+
const el = document.querySelector(selector);
|
|
879
|
+
if (el !== null) return resolve(el);
|
|
880
|
+
if (remaining-- <= 0) return resolve(null);
|
|
881
|
+
requestAnimationFrame(probe);
|
|
882
|
+
};
|
|
883
|
+
requestAnimationFrame(probe);
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
function flashKey(key) {
|
|
887
|
+
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
888
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
889
|
+
const tag = document.createElement("style");
|
|
890
|
+
tag.dataset.pluginCss = id;
|
|
891
|
+
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)}}`;
|
|
892
|
+
document.head.appendChild(tag);
|
|
893
|
+
setTimeout(() => tag.remove(), 3400);
|
|
894
|
+
}
|
|
895
|
+
const requestPreview = (record) => {
|
|
896
|
+
setPreviewScope("both");
|
|
897
|
+
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
898
|
+
callOp("rollback/preview", { sessionId, versionId: record.versionId, scope: "both" }).then((result) => {
|
|
899
|
+
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);
|
|
900
|
+
});
|
|
901
|
+
};
|
|
902
|
+
const confirmRollback = () => {
|
|
903
|
+
if (!preview) return;
|
|
904
|
+
setRollbackBusy(true);
|
|
905
|
+
callOp("rollback", { sessionId, versionId: preview.versionId, scope: previewScope }).then((result) => {
|
|
906
|
+
setRollbackBusy(false);
|
|
907
|
+
if (!result || result.ok !== true) {
|
|
908
|
+
setPreview((prev) => prev && { ...prev, error: result?.error?.message ?? "rollback failed" });
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
setPreview(null);
|
|
912
|
+
if (projected === void 0) refresh();
|
|
913
|
+
refreshGit();
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
const initGit = () => {
|
|
917
|
+
if (!window.confirm(t("timeline.gitInitConfirm"))) return;
|
|
918
|
+
setGitBusy(true);
|
|
919
|
+
callOp("git/init", { sessionId }).then((result) => {
|
|
920
|
+
setGitBusy(false);
|
|
921
|
+
refreshGit();
|
|
922
|
+
});
|
|
923
|
+
};
|
|
924
|
+
const ROW_H = 64;
|
|
925
|
+
const list = versions ?? [];
|
|
926
|
+
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));
|
|
927
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
928
|
+
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
929
|
+
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("timeline.title")),
|
|
930
|
+
(0, import_react.createElement)("button", {
|
|
931
|
+
key: "refresh",
|
|
932
|
+
type: "button",
|
|
933
|
+
className: "dsh-rt-chip",
|
|
934
|
+
onClick: refresh
|
|
935
|
+
}, t("timeline.refresh"))
|
|
936
|
+
]),
|
|
937
|
+
git !== null && git !== void 0 && (0, import_react.createElement)("div", { key: "git", className: "dsh-rt-timeline-git" }, [
|
|
938
|
+
git.headHash ? [
|
|
939
|
+
(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")}` : ""}`)
|
|
940
|
+
] : (0, import_react.createElement)("button", {
|
|
941
|
+
key: "init",
|
|
942
|
+
type: "button",
|
|
943
|
+
className: "dsh-rt-chip",
|
|
944
|
+
disabled: gitBusy,
|
|
945
|
+
onClick: initGit,
|
|
946
|
+
title: t("timeline.gitInitDesc")
|
|
947
|
+
}, t("timeline.gitInit"))
|
|
948
|
+
]),
|
|
949
|
+
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
950
|
+
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
951
|
+
!loading && list.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("timeline.empty")),
|
|
952
|
+
list.length > 0 && (0, import_react.createElement)("div", {
|
|
953
|
+
key: "list",
|
|
954
|
+
className: "dsh-rt-timeline-list",
|
|
955
|
+
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
956
|
+
}, [
|
|
957
|
+
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${list.length * ROW_H}px`, position: "relative" } }, [
|
|
958
|
+
visible.map((record) => (0, import_react.createElement)(VersionRow, {
|
|
959
|
+
key: record.versionId,
|
|
960
|
+
record,
|
|
961
|
+
t,
|
|
962
|
+
top: list.indexOf(record) * ROW_H,
|
|
963
|
+
onPreview: () => requestPreview(record),
|
|
964
|
+
onTrajectory: () => actions?.setView?.("trajectory"),
|
|
965
|
+
onJump: () => jump(record.boundarySeq)
|
|
966
|
+
}))
|
|
967
|
+
])
|
|
968
|
+
]),
|
|
969
|
+
preview && (0, import_react.createElement)(PreviewBox, {
|
|
970
|
+
key: "preview",
|
|
971
|
+
preview,
|
|
972
|
+
scope: previewScope,
|
|
973
|
+
setScope: setPreviewScope,
|
|
974
|
+
busy: rollbackBusy,
|
|
975
|
+
t,
|
|
976
|
+
onConfirm: confirmRollback,
|
|
977
|
+
onCancel: () => setPreview(null)
|
|
978
|
+
})
|
|
979
|
+
]);
|
|
980
|
+
}
|
|
981
|
+
function VersionRow({ record, top, t, onPreview, onTrajectory, onJump }) {
|
|
982
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-version", style: { top: `${top}px` } }, [
|
|
983
|
+
(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"),
|
|
984
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-version-body" }, [
|
|
985
|
+
(0, import_react.createElement)("div", { key: "line1", className: "dsh-rt-version-line" }, [
|
|
986
|
+
(0, import_react.createElement)("span", { key: "kind", className: "dsh-rt-version-kind-label" }, kindLabel(record.kind, t)),
|
|
987
|
+
(0, import_react.createElement)("span", { key: "time", className: "dsh-rt-version-time" }, timeLabel(record.createdAt)),
|
|
988
|
+
(0, import_react.createElement)("span", { key: "msgs", className: "dsh-rt-version-msgs" }, t("timeline.messages", { count: record.messageCount })),
|
|
989
|
+
(0, import_react.createElement)("span", { key: "files", className: "dsh-rt-version-files" }, fileCountLabel(record.fileCounts, t))
|
|
990
|
+
]),
|
|
991
|
+
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
|
|
992
|
+
]),
|
|
993
|
+
(0, import_react.createElement)("span", { key: "actions", className: "dsh-rt-version-actions" }, [
|
|
994
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump")),
|
|
995
|
+
(0, import_react.createElement)("button", { key: "trajectory", type: "button", className: "dsh-rt-chip", onClick: onTrajectory, title: t("timeline.trajectory") }, t("timeline.trajectory")),
|
|
996
|
+
(0, import_react.createElement)("button", { key: "preview", type: "button", className: "dsh-rt-chip dsh-rt-chip-danger", onClick: onPreview }, t("timeline.preview"))
|
|
997
|
+
])
|
|
998
|
+
]);
|
|
999
|
+
}
|
|
1000
|
+
function PreviewBox({ preview, scope, setScope, busy, t, onConfirm, onCancel }) {
|
|
1001
|
+
const data = preview.data;
|
|
1002
|
+
const scopes = [
|
|
1003
|
+
["context", "timeline.contextOnly", "timeline.contextOnlyDesc"],
|
|
1004
|
+
["artifacts", "timeline.artifactsOnly", "timeline.artifactsOnlyDesc"],
|
|
1005
|
+
["both", "timeline.both", "timeline.bothDesc"]
|
|
1006
|
+
];
|
|
1007
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-modal" }, [
|
|
1008
|
+
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-modal-title" }, [
|
|
1009
|
+
t("timeline.preview"),
|
|
1010
|
+
(0, import_react.createElement)("span", { key: "ver", className: "dsh-rt-modal-sub" }, t("timeline.previewDesc", { version: preview.versionId, kind: kindLabel(preview.kind, t) }))
|
|
1011
|
+
]),
|
|
1012
|
+
preview.error && (0, import_react.createElement)("div", { key: "err", className: "dsh-rt-error" }, preview.error),
|
|
1013
|
+
data === null && !preview.error && (0, import_react.createElement)("div", { key: "wait", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1014
|
+
data && (0, import_react.createElement)("div", { key: "body", className: "dsh-rt-modal-body" }, [
|
|
1015
|
+
(0, import_react.createElement)(
|
|
1016
|
+
"div",
|
|
1017
|
+
{ key: "ctx", className: "dsh-rt-modal-line" },
|
|
1018
|
+
data.context?.messages > 0 ? t("timeline.messagesRemoved", { count: data.context.messages }) : t("timeline.noChanges")
|
|
1019
|
+
),
|
|
1020
|
+
(0, import_react.createElement)("div", { key: "art", className: "dsh-rt-modal-line" }, t("timeline.artifactsList", { count: data.artifacts?.rows?.length ?? 0 })),
|
|
1021
|
+
(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 }, [
|
|
1022
|
+
(0, import_react.createElement)(
|
|
1023
|
+
"span",
|
|
1024
|
+
{ key: "a", className: `dsh-rt-art-${row.action}` },
|
|
1025
|
+
row.action === "skip" ? `${t("timeline.artifact.skip")} (${row.reason ?? ""})` : row.action === "delete" ? t("timeline.artifact.delete") : t("timeline.artifact.restore")
|
|
1026
|
+
),
|
|
1027
|
+
(0, import_react.createElement)("span", { key: "p", className: "dsh-rt-art-path" }, row.path)
|
|
1028
|
+
]))),
|
|
1029
|
+
(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" }, [
|
|
1030
|
+
(0, import_react.createElement)("input", { type: "radio", name: "rt-scope", checked: scope === value, onChange: () => setScope(value) }),
|
|
1031
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-text" }, [
|
|
1032
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-label" }, t(labelKey)),
|
|
1033
|
+
(0, import_react.createElement)("span", { className: "dsh-rt-option-desc" }, t(descKey))
|
|
1034
|
+
])
|
|
1035
|
+
])))
|
|
1036
|
+
]),
|
|
1037
|
+
(0, import_react.createElement)("div", { key: "buttons", className: "dsh-rt-modal-buttons" }, [
|
|
1038
|
+
(0, import_react.createElement)("button", { key: "cancel", type: "button", className: "dsh-rt-editor-cancel", disabled: busy, onClick: onCancel }, t("timeline.cancel")),
|
|
1039
|
+
(0, import_react.createElement)(
|
|
1040
|
+
"button",
|
|
1041
|
+
{ key: "confirm", type: "button", className: "dsh-rt-editor-send dsh-rt-confirm", disabled: busy || preview.error !== null, onClick: onConfirm },
|
|
1042
|
+
busy ? t("timeline.busy") : t("timeline.confirm")
|
|
1043
|
+
)
|
|
1044
|
+
])
|
|
1045
|
+
]);
|
|
1046
|
+
}
|
|
596
1047
|
function apply(ctx) {
|
|
597
1048
|
const disposeStyle = ensureStyle();
|
|
598
1049
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
599
1050
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-retrace: dictionaries");
|
|
1051
|
+
const t = ctx.locale.bind(NS);
|
|
600
1052
|
const conversationEvents = ctx.get("conversationEvents");
|
|
601
1053
|
if (conversationEvents) {
|
|
602
1054
|
conversationEvents.register(userActionsDefinition);
|
|
@@ -624,6 +1076,17 @@ function apply(ctx) {
|
|
|
624
1076
|
key: "recall-marker",
|
|
625
1077
|
locale: NS
|
|
626
1078
|
}, RecallMarkerRow));
|
|
1079
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1080
|
+
name: "conversation.view",
|
|
1081
|
+
id: "retrace",
|
|
1082
|
+
order: 20,
|
|
1083
|
+
locale: NS,
|
|
1084
|
+
label: () => t("view.retrace"),
|
|
1085
|
+
inject: (sessionId, actions) => ({
|
|
1086
|
+
actions,
|
|
1087
|
+
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1088
|
+
})
|
|
1089
|
+
}, RetraceView));
|
|
627
1090
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
628
1091
|
name: "settings.general.item",
|
|
629
1092
|
id: "retrace",
|