dsh-retrace 0.4.1 → 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 +1 -1
- package/README.zh.md +1 -1
- package/lib/client.bundle.js +123 -202
- package/lib/client.js +151 -233
- package/lib/dynamic-client.js +123 -202
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ in the conversation — all **inside the same session**, no session-switching.
|
|
|
51
51
|
|
|
52
52
|
**Versioning & rollback (live in 0.4.x)** — every rewind is also recorded as a **version**:
|
|
53
53
|
|
|
54
|
-
- 🕘 **Timeline** — the conversation
|
|
54
|
+
- 🕘 **Timeline** — a **Versions** tab in the conversation view (on par with the official 对话/轨迹 tabs, since 0.4.2): every version (type, time, message count, file-change badges, summary), pushed live via `session/projection` (no polling), windowed for long histories; event inspection reuses the official Trajectory ledger.
|
|
55
55
|
- ↩️ **Artifact rollback** — each version offers **context-only / artifacts-only / both** rollback with a dry-run preview; git-first (commit-free checkout of the listed paths) with content-addressed snapshot fallback. The rollback itself is recorded as a new version (`restore`) — rollback of a rollback.
|
|
56
56
|
- 🧭 **Jump-to-conversation** — one click from a version to that point in the conversation (auto-loads earlier history, anchor highlight).
|
|
57
57
|
- 🧹 **Bounded storage** — file snapshots keep the most recent N versions (default 50); a throttled background sweep prunes snapshots of truncated versions, keeping long sessions bounded.
|
package/README.zh.md
CHANGED
|
@@ -43,7 +43,7 @@ DeepSeek Harness 的对话是「只追加(append-only)」的事件日志,
|
|
|
43
43
|
|
|
44
44
|
**版本化与回退(0.4.x 已上线)** —— 每次回退都会被记录为一个**版本**:
|
|
45
45
|
|
|
46
|
-
- 🕘 **时间线** ——
|
|
46
|
+
- 🕘 **时间线** —— 会话视图新增「版本」Tab(与官方「对话/轨迹」平级,0.4.2 起):展示每个版本(类型/时间/消息数/文件变更徽标/摘要),经 `session/projection` 推送帧实时更新(零轮询),大列表窗口化渲染;事件原文查看复用官方「轨迹」台账。
|
|
47
47
|
- ↩️ **产物回退** —— 每个版本支持 仅对话 / 仅产物 / 两者 三种回退范围,先干跑预览再执行;git 优先(commit-free checkout 清单路径)+ 内容寻址快照兜底。回退本身记录为新版本(`restore`),可以再回退。
|
|
48
48
|
- 🧭 **跳转对话** —— 从时间线一键跳转到对话对应位置(自动翻页加载更早历史 + 锚点高亮)。
|
|
49
49
|
- 🧹 **存储有界** —— 文件快照只保留最近 N 个版本(默认 50);节流后台扫掠回收被截断版本的快照,长会话不膨胀。
|
package/lib/client.bundle.js
CHANGED
|
@@ -77,6 +77,7 @@ var zh = {
|
|
|
77
77
|
"timeline.open": "\u65F6\u95F4\u7EBF",
|
|
78
78
|
"timeline.openAria": "\u6253\u5F00\u4F1A\u8BDD\u65F6\u95F4\u7EBF",
|
|
79
79
|
"timeline.title": "\u7248\u672C\u65F6\u95F4\u7EBF",
|
|
80
|
+
"view.retrace": "\u7248\u672C",
|
|
80
81
|
"timeline.refresh": "\u5237\u65B0",
|
|
81
82
|
"timeline.close": "\u5173\u95ED",
|
|
82
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",
|
|
@@ -109,6 +110,7 @@ var zh = {
|
|
|
109
110
|
"timeline.cancel": "\u53D6\u6D88",
|
|
110
111
|
"timeline.busy": "\u56DE\u9000\u4E2D\u2026",
|
|
111
112
|
"timeline.detail": "\u8BE6\u60C5",
|
|
113
|
+
"timeline.trajectory": "\u8F68\u8FF9\u53F0\u8D26",
|
|
112
114
|
"timeline.jump": "\u8DF3\u8F6C",
|
|
113
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",
|
|
114
116
|
"timeline.gitRepo": "git \u4ED3\u5E93",
|
|
@@ -150,6 +152,7 @@ var en = {
|
|
|
150
152
|
"timeline.open": "Timeline",
|
|
151
153
|
"timeline.openAria": "Open the session timeline",
|
|
152
154
|
"timeline.title": "Version timeline",
|
|
155
|
+
"view.retrace": "Versions",
|
|
153
156
|
"timeline.refresh": "Refresh",
|
|
154
157
|
"timeline.close": "Close",
|
|
155
158
|
"timeline.empty": "No versions yet. Recall / edit / regenerate record a version here.",
|
|
@@ -182,6 +185,7 @@ var en = {
|
|
|
182
185
|
"timeline.cancel": "Cancel",
|
|
183
186
|
"timeline.busy": "Rolling back\u2026",
|
|
184
187
|
"timeline.detail": "Details",
|
|
188
|
+
"timeline.trajectory": "Trajectory",
|
|
185
189
|
"timeline.jump": "Jump",
|
|
186
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.",
|
|
187
191
|
"timeline.gitRepo": "git repository",
|
|
@@ -725,17 +729,13 @@ var CSS = `
|
|
|
725
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}
|
|
726
730
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
727
731
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
728
|
-
/* ---- P1 timeline ---- */
|
|
729
|
-
.dsh-rt-
|
|
730
|
-
.dsh-rt-timeline-trigger{min-height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:transparent;border:none;border-radius:6px;display:inline-flex;align-items:center;gap:4px;padding:3px 6px;font-size:12px;line-height:18px}
|
|
731
|
-
.dsh-rt-timeline-trigger:hover,.dsh-rt-timeline-trigger:focus-visible{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}
|
|
732
|
-
.dsh-rt-timeline-icon{font-size:13px}
|
|
733
|
-
.dsh-rt-timeline-panel{position:fixed;top:calc(100% + 6px);right:8px;z-index:2147483000;width:min(520px,calc(100vw - 32px));max-height:min(70vh,640px);box-sizing:border-box;display:flex;flex-direction:column;gap:6px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;padding:8px;overflow:hidden}
|
|
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
734
|
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
735
735
|
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
736
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
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:
|
|
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
739
|
.dsh-rt-timeline-empty{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;padding:12px 4px;text-align:center}
|
|
740
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
741
|
.dsh-rt-version:hover{background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
|
|
@@ -805,8 +805,7 @@ function fileCountLabel(counts, t) {
|
|
|
805
805
|
deleted: counts.deleted ?? 0
|
|
806
806
|
});
|
|
807
807
|
}
|
|
808
|
-
function
|
|
809
|
-
const [open, setOpen] = (0, import_react.useState)(false);
|
|
808
|
+
function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
810
809
|
const [versions, setVersions] = (0, import_react.useState)(null);
|
|
811
810
|
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
812
811
|
const [error, setError] = (0, import_react.useState)(null);
|
|
@@ -815,41 +814,11 @@ function TimelinePanel({ sessionId, useSession, useProjection, useSessions, t, c
|
|
|
815
814
|
const [preview, setPreview] = (0, import_react.useState)(null);
|
|
816
815
|
const [previewScope, setPreviewScope] = (0, import_react.useState)("both");
|
|
817
816
|
const [rollbackBusy, setRollbackBusy] = (0, import_react.useState)(false);
|
|
818
|
-
const [detail, setDetail] = (0, import_react.useState)(null);
|
|
819
|
-
const [jumpTarget, setJumpTarget] = (0, import_react.useState)(null);
|
|
820
|
-
const [jumpNotice, setJumpNotice] = (0, import_react.useState)(null);
|
|
821
817
|
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
822
|
-
const [panelPos, setPanelPos] = (0, import_react.useState)(null);
|
|
823
|
-
const rootRef = { current: null };
|
|
824
|
-
const triggerRef = { current: null };
|
|
825
|
-
const snapRef = { current: null };
|
|
826
818
|
const projected = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
827
|
-
useSession((snapshot) => {
|
|
828
|
-
snapRef.current = snapshot;
|
|
829
|
-
return snapshot.chat.nodes.size;
|
|
830
|
-
});
|
|
831
819
|
(0, import_react.useEffect)(() => {
|
|
832
820
|
if (projected && Array.isArray(projected.versions)) setVersions(projected.versions);
|
|
833
821
|
}, [projected]);
|
|
834
|
-
const anchorPanel = () => {
|
|
835
|
-
const rect = triggerRef.current?.getBoundingClientRect?.();
|
|
836
|
-
if (!rect) return;
|
|
837
|
-
setPanelPos({
|
|
838
|
-
top: rect.bottom + 6,
|
|
839
|
-
right: Math.max(8, window.innerWidth - rect.right)
|
|
840
|
-
});
|
|
841
|
-
};
|
|
842
|
-
(0, import_react.useEffect)(() => {
|
|
843
|
-
if (!open) return;
|
|
844
|
-
anchorPanel();
|
|
845
|
-
const onViewportChange = () => anchorPanel();
|
|
846
|
-
window.addEventListener("resize", onViewportChange);
|
|
847
|
-
window.addEventListener("scroll", onViewportChange, true);
|
|
848
|
-
return () => {
|
|
849
|
-
window.removeEventListener("resize", onViewportChange);
|
|
850
|
-
window.removeEventListener("scroll", onViewportChange, true);
|
|
851
|
-
};
|
|
852
|
-
}, [open]);
|
|
853
822
|
const refresh = () => {
|
|
854
823
|
setLoading(true);
|
|
855
824
|
setError(null);
|
|
@@ -862,77 +831,67 @@ function TimelinePanel({ sessionId, useSession, useProjection, useSessions, t, c
|
|
|
862
831
|
if (getConfig().git !== true) return;
|
|
863
832
|
timelineGet(`/git/status?sessionId=${encodeURIComponent(sessionId)}`).then((result) => setGit(result?.ok === true ? result.value : null)).catch(() => setGit(null));
|
|
864
833
|
};
|
|
865
|
-
const toggle = () => {
|
|
866
|
-
const next = !open;
|
|
867
|
-
setOpen(next);
|
|
868
|
-
if (next) {
|
|
869
|
-
if (projected === void 0) refresh();
|
|
870
|
-
refreshGit();
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
834
|
(0, import_react.useEffect)(() => {
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
};
|
|
878
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
879
|
-
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
880
|
-
}, [open]);
|
|
835
|
+
if (projected === void 0) refresh();
|
|
836
|
+
refreshGit();
|
|
837
|
+
}, []);
|
|
881
838
|
const JUMP_PAGE_BUDGET = 24;
|
|
882
839
|
const jump = async (boundarySeq) => {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
const
|
|
887
|
-
|
|
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);
|
|
888
852
|
let pages = 0;
|
|
889
|
-
while (pages < JUMP_PAGE_BUDGET &&
|
|
890
|
-
const before =
|
|
891
|
-
await
|
|
892
|
-
const after =
|
|
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;
|
|
893
857
|
pages += 1;
|
|
894
858
|
if (after === before) break;
|
|
859
|
+
key = keyOfSeq(boundarySeq);
|
|
895
860
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
setJumpTarget(null);
|
|
861
|
+
if (key === null) {
|
|
862
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
863
|
+
key = keyOfSeq(boundarySeq);
|
|
900
864
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const nodes = snapRef.current?.chat?.nodes;
|
|
904
|
-
if (!nodes) return false;
|
|
905
|
-
for (const node of nodes.values()) {
|
|
906
|
-
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return true;
|
|
907
|
-
}
|
|
908
|
-
return false;
|
|
909
|
-
}
|
|
910
|
-
const foundKey = useSession((snapshot) => {
|
|
911
|
-
if (jumpTarget === null) return null;
|
|
912
|
-
for (const node of snapshot.chat.nodes.values()) {
|
|
913
|
-
if (typeof node.anchorSeq === "number" && node.anchorSeq === jumpTarget) return node.key;
|
|
914
|
-
}
|
|
915
|
-
return null;
|
|
916
|
-
});
|
|
917
|
-
(0, import_react.useEffect)(() => {
|
|
918
|
-
if (foundKey === null || jumpTarget === null) return;
|
|
919
|
-
const el = document.querySelector(`[data-chat-anchor-key=${JSON.stringify(foundKey)}]`);
|
|
920
|
-
if (!el) {
|
|
921
|
-
setJumpNotice(t("timeline.jumpFailed"));
|
|
922
|
-
setJumpTarget(null);
|
|
865
|
+
if (key === null) {
|
|
866
|
+
console.warn(`[dsh-retrace] jump target seq ${boundarySeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
923
867
|
return;
|
|
924
868
|
}
|
|
869
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
870
|
+
if (el === null) return;
|
|
925
871
|
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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
|
+
}
|
|
936
895
|
const requestPreview = (record) => {
|
|
937
896
|
setPreviewScope("both");
|
|
938
897
|
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
@@ -954,12 +913,6 @@ function TimelinePanel({ sessionId, useSession, useProjection, useSessions, t, c
|
|
|
954
913
|
refreshGit();
|
|
955
914
|
});
|
|
956
915
|
};
|
|
957
|
-
const openDetail = (record) => {
|
|
958
|
-
setDetail({ seq: record.boundarySeq, data: null, error: null });
|
|
959
|
-
timelineGet(`/event?sessionId=${encodeURIComponent(sessionId)}&seq=${record.boundarySeq}&before=2&after=2`).then((result) => {
|
|
960
|
-
setDetail((prev) => prev && prev.seq === record.boundarySeq ? { ...prev, data: result?.ok === true ? result.value : null, error: result?.ok === true ? null : result?.error?.message ?? null } : prev);
|
|
961
|
-
});
|
|
962
|
-
};
|
|
963
916
|
const initGit = () => {
|
|
964
917
|
if (!window.confirm(t("timeline.gitInitConfirm"))) return;
|
|
965
918
|
setGitBusy(true);
|
|
@@ -970,89 +923,62 @@ function TimelinePanel({ sessionId, useSession, useProjection, useSessions, t, c
|
|
|
970
923
|
};
|
|
971
924
|
const ROW_H = 64;
|
|
972
925
|
const list = versions ?? [];
|
|
973
|
-
const visible = list.slice(Math.max(0, Math.floor(scrollTop / ROW_H) - 2), Math.min(list.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(
|
|
974
|
-
return (0, import_react.createElement)("div", { className: "dsh-rt-
|
|
975
|
-
(0, import_react.createElement)("
|
|
976
|
-
key: "
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}, [
|
|
984
|
-
(0, import_react.createElement)("span", { key: "i", className: "dsh-rt-timeline-icon" }, "\u29C9"),
|
|
985
|
-
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-timeline-label" }, t("timeline.open"))
|
|
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"))
|
|
986
936
|
]),
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
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)
|
|
991
956
|
}, [
|
|
992
|
-
(0, import_react.createElement)("div", { key: "
|
|
993
|
-
(0, import_react.createElement)(
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
disabled: gitBusy,
|
|
1015
|
-
onClick: initGit,
|
|
1016
|
-
title: t("timeline.gitInitDesc")
|
|
1017
|
-
}, t("timeline.gitInit"))
|
|
1018
|
-
]),
|
|
1019
|
-
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
1020
|
-
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1021
|
-
!loading && list.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("timeline.empty")),
|
|
1022
|
-
list.length > 0 && (0, import_react.createElement)("div", {
|
|
1023
|
-
key: "list",
|
|
1024
|
-
className: "dsh-rt-timeline-list",
|
|
1025
|
-
style: { height: `${Math.min(420, list.length * ROW_H)}px` },
|
|
1026
|
-
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1027
|
-
}, [
|
|
1028
|
-
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${list.length * ROW_H}px`, position: "relative" } }, [
|
|
1029
|
-
visible.map((record) => (0, import_react.createElement)(VersionRow, {
|
|
1030
|
-
key: record.versionId,
|
|
1031
|
-
record,
|
|
1032
|
-
t,
|
|
1033
|
-
top: list.indexOf(record) * ROW_H,
|
|
1034
|
-
onPreview: () => requestPreview(record),
|
|
1035
|
-
onDetail: () => openDetail(record),
|
|
1036
|
-
onJump: () => jump(record.boundarySeq)
|
|
1037
|
-
}))
|
|
1038
|
-
])
|
|
1039
|
-
]),
|
|
1040
|
-
preview && (0, import_react.createElement)(PreviewBox, {
|
|
1041
|
-
key: "preview",
|
|
1042
|
-
preview,
|
|
1043
|
-
scope: previewScope,
|
|
1044
|
-
setScope: setPreviewScope,
|
|
1045
|
-
busy: rollbackBusy,
|
|
1046
|
-
t,
|
|
1047
|
-
onConfirm: confirmRollback,
|
|
1048
|
-
onCancel: () => setPreview(null)
|
|
1049
|
-
}),
|
|
1050
|
-
detail && (0, import_react.createElement)(DetailBox, { key: "detail", detail, t, onClose: () => setDetail(null) }),
|
|
1051
|
-
jumpNotice !== null && (0, import_react.createElement)("div", { key: "jump", className: "dsh-rt-error" }, jumpNotice)
|
|
1052
|
-
])
|
|
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
|
+
})
|
|
1053
979
|
]);
|
|
1054
980
|
}
|
|
1055
|
-
function VersionRow({ record, top, t, onPreview,
|
|
981
|
+
function VersionRow({ record, top, t, onPreview, onTrajectory, onJump }) {
|
|
1056
982
|
return (0, import_react.createElement)("div", { className: "dsh-rt-version", style: { top: `${top}px` } }, [
|
|
1057
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"),
|
|
1058
984
|
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-version-body" }, [
|
|
@@ -1065,8 +991,8 @@ function VersionRow({ record, top, t, onPreview, onDetail, onJump }) {
|
|
|
1065
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
|
|
1066
992
|
]),
|
|
1067
993
|
(0, import_react.createElement)("span", { key: "actions", className: "dsh-rt-version-actions" }, [
|
|
1068
|
-
(0, import_react.createElement)("button", { key: "detail", type: "button", className: "dsh-rt-chip", onClick: onDetail }, t("timeline.detail")),
|
|
1069
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")),
|
|
1070
996
|
(0, import_react.createElement)("button", { key: "preview", type: "button", className: "dsh-rt-chip dsh-rt-chip-danger", onClick: onPreview }, t("timeline.preview"))
|
|
1071
997
|
])
|
|
1072
998
|
]);
|
|
@@ -1118,21 +1044,11 @@ function PreviewBox({ preview, scope, setScope, busy, t, onConfirm, onCancel })
|
|
|
1118
1044
|
])
|
|
1119
1045
|
]);
|
|
1120
1046
|
}
|
|
1121
|
-
function DetailBox({ detail, t, onClose }) {
|
|
1122
|
-
return (0, import_react.createElement)("div", { className: "dsh-rt-modal" }, [
|
|
1123
|
-
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-modal-title" }, [
|
|
1124
|
-
`${t("timeline.detail")} \xB7 seq ${detail.seq}`,
|
|
1125
|
-
(0, import_react.createElement)("button", { key: "close", type: "button", className: "dsh-rt-chip", onClick: onClose }, t("timeline.close"))
|
|
1126
|
-
]),
|
|
1127
|
-
detail.error && (0, import_react.createElement)("div", { key: "err", className: "dsh-rt-error" }, detail.error),
|
|
1128
|
-
detail.data === null && !detail.error && (0, import_react.createElement)("div", { key: "wait", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1129
|
-
detail.data && (0, import_react.createElement)("pre", { key: "json", className: "dsh-rt-modal-json" }, JSON.stringify(detail.data, null, 2))
|
|
1130
|
-
]);
|
|
1131
|
-
}
|
|
1132
1047
|
function apply(ctx) {
|
|
1133
1048
|
const disposeStyle = ensureStyle();
|
|
1134
1049
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
1135
1050
|
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-retrace: dictionaries");
|
|
1051
|
+
const t = ctx.locale.bind(NS);
|
|
1136
1052
|
const conversationEvents = ctx.get("conversationEvents");
|
|
1137
1053
|
if (conversationEvents) {
|
|
1138
1054
|
conversationEvents.register(userActionsDefinition);
|
|
@@ -1160,12 +1076,17 @@ function apply(ctx) {
|
|
|
1160
1076
|
key: "recall-marker",
|
|
1161
1077
|
locale: NS
|
|
1162
1078
|
}, RecallMarkerRow));
|
|
1163
|
-
ctx.slots.inject("conversation.
|
|
1164
|
-
name: "conversation.
|
|
1165
|
-
id: "retrace
|
|
1166
|
-
order:
|
|
1167
|
-
locale: NS
|
|
1168
|
-
|
|
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));
|
|
1169
1090
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
1170
1091
|
name: "settings.general.item",
|
|
1171
1092
|
id: "retrace",
|