dsh-retrace 0.4.2 → 0.4.3
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/lib/client.bundle.js +326 -80
- package/lib/client.js +434 -101
- package/lib/dynamic-client.js +326 -80
- package/lib/forkmap.js +106 -0
- package/lib/http.js +14 -0
- package/lib/projection/forkmap.js +69 -0
- package/lib/versioning.js +16 -5
- package/package.json +1 -1
package/lib/client.bundle.js
CHANGED
|
@@ -120,7 +120,17 @@ var zh = {
|
|
|
120
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
121
|
"timeline.gitInitConfirm": "\u786E\u5B9A\u8981\u521D\u59CB\u5316 git \u5417\uFF1F",
|
|
122
122
|
"error.generic": "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
123
|
-
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C"
|
|
123
|
+
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C",
|
|
124
|
+
"view.fork": "\u5206\u53C9",
|
|
125
|
+
"fork.title": "\u5206\u53C9\u56FE",
|
|
126
|
+
"fork.empty": "\u8FD8\u6CA1\u6709\u5206\u53C9\u3002\u64A4\u56DE / \u7F16\u8F91 / \u91CD\u65B0\u751F\u6210 / \u6062\u590D\u4F1A\u5728\u6B64\u4EA7\u751F\u5206\u53C9\u3002",
|
|
127
|
+
"fork.refresh": "\u5237\u65B0",
|
|
128
|
+
"fork.spine": "\u5F53\u524D\u8DEF\u5F84",
|
|
129
|
+
"fork.shadowed": "\u88AB\u906E\u853D {count} \u4E2A\u8282\u70B9",
|
|
130
|
+
"fork.node.user": "\u7528\u6237\u6D88\u606F",
|
|
131
|
+
"fork.node.assistant": "\u52A9\u624B\u56DE\u590D",
|
|
132
|
+
"fork.node.tool": "\u5DE5\u5177\u7ED3\u679C",
|
|
133
|
+
"fork.histTitle": "\u5386\u53F2\u5206\u53C9\u70B9"
|
|
124
134
|
};
|
|
125
135
|
var en = {
|
|
126
136
|
"action.edit": "Edit",
|
|
@@ -195,7 +205,17 @@ var en = {
|
|
|
195
205
|
"timeline.gitInitDesc": "Runs git init in the workspace (adds a minimal .gitignore and a baseline commit); rollback will prefer git.",
|
|
196
206
|
"timeline.gitInitConfirm": "Initialize git in this workspace?",
|
|
197
207
|
"error.generic": "Operation failed; please try again",
|
|
198
|
-
"error.busy": "Stop the current reply before recalling or editing"
|
|
208
|
+
"error.busy": "Stop the current reply before recalling or editing",
|
|
209
|
+
"view.fork": "Fork",
|
|
210
|
+
"fork.title": "Fork map",
|
|
211
|
+
"fork.empty": "No forks yet. Recall / edit / regenerate / restore fork the path here.",
|
|
212
|
+
"fork.refresh": "Refresh",
|
|
213
|
+
"fork.spine": "Current path",
|
|
214
|
+
"fork.shadowed": "{count} shadowed nodes",
|
|
215
|
+
"fork.node.user": "User message",
|
|
216
|
+
"fork.node.assistant": "Assistant reply",
|
|
217
|
+
"fork.node.tool": "Tool result",
|
|
218
|
+
"fork.histTitle": "Historical fork points"
|
|
199
219
|
};
|
|
200
220
|
var SURFACE_TYPES = /* @__PURE__ */ new Set(["user/message", "assistant/message", "tool/result"]);
|
|
201
221
|
function isReplacementSurfaceEvent(event) {
|
|
@@ -405,29 +425,51 @@ function useShadowed(useSession, seq) {
|
|
|
405
425
|
});
|
|
406
426
|
}
|
|
407
427
|
var SHADOW_SAFETY_RATIO = 0.4;
|
|
408
|
-
function
|
|
428
|
+
function hiddenKeysFor(shadowedSeqs, nodes) {
|
|
429
|
+
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
|
|
430
|
+
const hidden = new Set(shadowedSeqs);
|
|
431
|
+
const keys = [];
|
|
432
|
+
for (const node of nodes.values()) {
|
|
433
|
+
if (node.kind === "recall-marker") continue;
|
|
434
|
+
if (node.kind === "turn-tail") {
|
|
435
|
+
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
436
|
+
if (typeof closingSeq === "number" && hidden.has(closingSeq)) keys.push(node.key);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (node.kind === "tool-call") {
|
|
440
|
+
const resultSeq = node.data?.root?.seq;
|
|
441
|
+
if (typeof resultSeq === "number" && hidden.has(resultSeq)) keys.push(node.key);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
445
|
+
}
|
|
446
|
+
return keys.length === 0 ? null : keys;
|
|
447
|
+
}
|
|
448
|
+
var EMPTY_HIDE_PLAN = { degraded: false, hiddenFor: () => null };
|
|
449
|
+
function useMarkerHidePlan(useSession) {
|
|
409
450
|
return useSession((snapshot) => {
|
|
410
|
-
|
|
411
|
-
const hidden = new Set(shadowedSeqs);
|
|
412
|
-
const keys = [];
|
|
451
|
+
const nodes = snapshot.chat.nodes;
|
|
413
452
|
let rowCount = 0;
|
|
414
|
-
|
|
453
|
+
const markers = [];
|
|
454
|
+
for (const node of nodes.values()) {
|
|
415
455
|
if (typeof node.anchorSeq === "number") rowCount += 1;
|
|
416
|
-
if (node.kind === "recall-marker")
|
|
417
|
-
|
|
418
|
-
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
419
|
-
if (typeof closingSeq === "number" && hidden.has(closingSeq)) keys.push(node.key);
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
if (node.kind === "tool-call") {
|
|
423
|
-
const resultSeq = node.data?.root?.seq;
|
|
424
|
-
if (typeof resultSeq === "number" && hidden.has(resultSeq)) keys.push(node.key);
|
|
425
|
-
continue;
|
|
456
|
+
if (node.kind === "recall-marker") {
|
|
457
|
+
markers.push({ key: node.key, shadowedSeqs: node.data?.shadowedSeqs });
|
|
426
458
|
}
|
|
427
|
-
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
428
459
|
}
|
|
429
|
-
if (
|
|
430
|
-
|
|
460
|
+
if (markers.length === 0) return EMPTY_HIDE_PLAN;
|
|
461
|
+
const union = /* @__PURE__ */ new Set();
|
|
462
|
+
const perMarker = /* @__PURE__ */ new Map();
|
|
463
|
+
for (const marker of markers) {
|
|
464
|
+
const keys = hiddenKeysFor(marker.shadowedSeqs, nodes);
|
|
465
|
+
perMarker.set(marker.key, keys);
|
|
466
|
+
if (keys !== null) for (const key of keys) union.add(key);
|
|
467
|
+
}
|
|
468
|
+
const degraded = rowCount > 0 && union.size / rowCount > SHADOW_SAFETY_RATIO;
|
|
469
|
+
return {
|
|
470
|
+
degraded,
|
|
471
|
+
hiddenFor: (key) => degraded ? null : perMarker.get(key) ?? null
|
|
472
|
+
};
|
|
431
473
|
});
|
|
432
474
|
}
|
|
433
475
|
function useMarkerDismissed(useSession, markerSeq, op) {
|
|
@@ -629,7 +671,8 @@ function UserActionsRow({ node, sessionId, useSession, inputActions, t }) {
|
|
|
629
671
|
function RecallMarkerRow({ node, useSession, t }) {
|
|
630
672
|
const { seq, op, shadowedSeqs, legacy } = node.data;
|
|
631
673
|
const dismissed = useMarkerDismissed(useSession, seq, op);
|
|
632
|
-
const
|
|
674
|
+
const hidePlan = useMarkerHidePlan(useSession);
|
|
675
|
+
const hiddenKeys = legacy || !getConfig().hideShadowed ? null : hidePlan.hiddenFor(node.key);
|
|
633
676
|
const css = hiddenKeys === null ? null : hiddenKeys.map((key) => `[data-chat-anchor-key=${JSON.stringify(key)}]{display:none!important}`).join("");
|
|
634
677
|
const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
|
|
635
678
|
const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
|
|
@@ -730,7 +773,8 @@ var CSS = `
|
|
|
730
773
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
731
774
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
732
775
|
/* ---- P1 timeline (conversation view tab) ---- */
|
|
733
|
-
|
|
776
|
+
|
|
777
|
+
.dsh-rt-view{box-sizing:border-box;display:flex;flex-direction:column;gap:6px;flex:1 1 0%;min-height:0;overflow:hidden;padding:12px 16px 0}
|
|
734
778
|
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
735
779
|
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
736
780
|
.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}
|
|
@@ -767,7 +811,121 @@ var CSS = `
|
|
|
767
811
|
.dsh-rt-modal-buttons{display:flex;justify-content:flex-end;gap:8px;flex:none}
|
|
768
812
|
.dsh-rt-confirm{background:var(--dsw-alias-state-error-primary)}
|
|
769
813
|
.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}
|
|
814
|
+
.dsh-rt-fork-list{overflow-y:auto;flex:1;min-height:0;position:relative}
|
|
815
|
+
.dsh-rt-fork-spine-label{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;flex:none}
|
|
816
|
+
.dsh-rt-fork-row{position:absolute;left:0;right:0;box-sizing:border-box;display:flex;align-items:flex-start;gap:8px;padding:6px 8px;border-radius:8px}
|
|
817
|
+
.dsh-rt-fork-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
818
|
+
.dsh-rt-fork-icon{flex:none;width:22px;height:22px;display:inline-flex;justify-content:center;align-items:center;font-size:13px;color:var(--dsw-alias-label-secondary)}
|
|
819
|
+
.dsh-rt-fork-boundary .dsh-rt-fork-icon{color:var(--dsw-alias-state-warning-primary)}
|
|
820
|
+
.dsh-rt-fork-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
821
|
+
.dsh-rt-fork-line{display:flex;align-items:baseline;gap:8px;min-width:0}
|
|
822
|
+
.dsh-rt-fork-label{font-size:12px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
823
|
+
.dsh-rt-fork-seq{font-family:var(--dsw-font-mono);font-size:11px;line-height:18px;color:var(--dsw-alias-label-caption);flex:none}
|
|
824
|
+
.dsh-rt-fork-shadowed{font-size:11px;line-height:18px;color:var(--dsw-alias-state-warning-primary);flex:none}
|
|
825
|
+
.dsh-rt-fork-text{font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
826
|
+
.dsh-rt-fork-boundary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-elevated)}
|
|
827
|
+
.dsh-rt-fork-hist{display:flex;flex-direction:column;gap:4px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:8px;overflow-y:auto;flex:none}
|
|
828
|
+
.dsh-rt-fork-hist-row{display:flex;align-items:flex-start;gap:8px;padding:4px 8px;border-radius:8px}
|
|
829
|
+
.dsh-rt-fork-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
770
830
|
`;
|
|
831
|
+
var JUMP_PAGE_BUDGET = 24;
|
|
832
|
+
function switchToViewTab(viewId) {
|
|
833
|
+
const ORDER = { chat: 0, trajectory: 1, retrace: 2, "retrace-fork": 3 };
|
|
834
|
+
const index = ORDER[viewId];
|
|
835
|
+
if (index === void 0) {
|
|
836
|
+
console.warn(`[dsh-retrace] no tab order registered for "${viewId}"`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const buttons = [...document.querySelectorAll('[role="tablist"] [role="tab"]')];
|
|
840
|
+
const button = buttons[index];
|
|
841
|
+
if (!button) {
|
|
842
|
+
console.warn(`[dsh-retrace] tab "${viewId}" (index ${index}) not found in the conversation tab bar`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
button.click();
|
|
846
|
+
}
|
|
847
|
+
function waitForElement(selector, frames) {
|
|
848
|
+
return new Promise((resolve) => {
|
|
849
|
+
let remaining = frames;
|
|
850
|
+
const probe = () => {
|
|
851
|
+
const el = document.querySelector(selector);
|
|
852
|
+
if (el !== null) return resolve(el);
|
|
853
|
+
if (remaining-- <= 0) return resolve(null);
|
|
854
|
+
requestAnimationFrame(probe);
|
|
855
|
+
};
|
|
856
|
+
requestAnimationFrame(probe);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
function flashKey(key) {
|
|
860
|
+
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
861
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
862
|
+
const tag = document.createElement("style");
|
|
863
|
+
tag.dataset.pluginCss = id;
|
|
864
|
+
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)}}`;
|
|
865
|
+
document.head.appendChild(tag);
|
|
866
|
+
setTimeout(() => tag.remove(), 3400);
|
|
867
|
+
}
|
|
868
|
+
async function jumpToAnchor(store, anchorSeq) {
|
|
869
|
+
switchToViewTab("chat");
|
|
870
|
+
if (!store || typeof store.loadOlder !== "function" || typeof store.getSnapshot !== "function") return;
|
|
871
|
+
const keyOfSeq = (seq) => {
|
|
872
|
+
const nodes = store.getSnapshot()?.chat?.nodes;
|
|
873
|
+
if (!nodes) return null;
|
|
874
|
+
for (const node of nodes.values()) {
|
|
875
|
+
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
876
|
+
}
|
|
877
|
+
return null;
|
|
878
|
+
};
|
|
879
|
+
let key = keyOfSeq(anchorSeq);
|
|
880
|
+
let pages = 0;
|
|
881
|
+
while (key === null && pages < JUMP_PAGE_BUDGET && store.hasMore) {
|
|
882
|
+
const before = store.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
883
|
+
await store.loadOlder();
|
|
884
|
+
const after = store.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
885
|
+
pages += 1;
|
|
886
|
+
if (after === before) break;
|
|
887
|
+
key = keyOfSeq(anchorSeq);
|
|
888
|
+
}
|
|
889
|
+
if (key === null) {
|
|
890
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
891
|
+
key = keyOfSeq(anchorSeq);
|
|
892
|
+
}
|
|
893
|
+
if (key === null) {
|
|
894
|
+
console.warn(`[dsh-retrace] jump target seq ${anchorSeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
898
|
+
if (el === null) return;
|
|
899
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
900
|
+
flashKey(key);
|
|
901
|
+
}
|
|
902
|
+
var NODE_ICONS = { "user/message": "\u{1F464}", "assistant/message": "\u{1F916}", "tool/result": "\u{1F527}" };
|
|
903
|
+
function nodeTypeLabel(type, t) {
|
|
904
|
+
if (type === "user/message") return t("fork.node.user");
|
|
905
|
+
if (type === "assistant/message") return t("fork.node.assistant");
|
|
906
|
+
if (type === "tool/result") return t("fork.node.tool");
|
|
907
|
+
return type;
|
|
908
|
+
}
|
|
909
|
+
function bindListHeight(listEl) {
|
|
910
|
+
if (!listEl) return () => {
|
|
911
|
+
};
|
|
912
|
+
const measure = () => {
|
|
913
|
+
const top = listEl.getBoundingClientRect().top;
|
|
914
|
+
const height = Math.max(120, window.innerHeight - top - 16);
|
|
915
|
+
if (Math.abs(listEl.clientHeight - height) > 4) {
|
|
916
|
+
listEl.style.flex = "none";
|
|
917
|
+
listEl.style.height = `${height}px`;
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
measure();
|
|
921
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
|
|
922
|
+
ro?.observe(document.body);
|
|
923
|
+
window.addEventListener("resize", measure);
|
|
924
|
+
return () => {
|
|
925
|
+
ro?.disconnect();
|
|
926
|
+
window.removeEventListener("resize", measure);
|
|
927
|
+
};
|
|
928
|
+
}
|
|
771
929
|
function ensureStyle() {
|
|
772
930
|
if (typeof document === "undefined") return () => {
|
|
773
931
|
};
|
|
@@ -835,63 +993,7 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
835
993
|
if (projected === void 0) refresh();
|
|
836
994
|
refreshGit();
|
|
837
995
|
}, []);
|
|
838
|
-
const
|
|
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
|
-
}
|
|
996
|
+
const jump = (boundarySeq) => jumpToAnchor(store, boundarySeq);
|
|
895
997
|
const requestPreview = (record) => {
|
|
896
998
|
setPreviewScope("both");
|
|
897
999
|
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
@@ -924,6 +1026,10 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
924
1026
|
const ROW_H = 64;
|
|
925
1027
|
const list = versions ?? [];
|
|
926
1028
|
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));
|
|
1029
|
+
(0, import_react.useEffect)(() => {
|
|
1030
|
+
if (list.length === 0) return void 0;
|
|
1031
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-timeline-list"));
|
|
1032
|
+
}, [list.length]);
|
|
927
1033
|
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
928
1034
|
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
929
1035
|
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("timeline.title")),
|
|
@@ -961,7 +1067,7 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
961
1067
|
t,
|
|
962
1068
|
top: list.indexOf(record) * ROW_H,
|
|
963
1069
|
onPreview: () => requestPreview(record),
|
|
964
|
-
onTrajectory: () =>
|
|
1070
|
+
onTrajectory: () => switchToViewTab("trajectory"),
|
|
965
1071
|
onJump: () => jump(record.boundarySeq)
|
|
966
1072
|
}))
|
|
967
1073
|
])
|
|
@@ -1044,6 +1150,135 @@ function PreviewBox({ preview, scope, setScope, busy, t, onConfirm, onCancel })
|
|
|
1044
1150
|
])
|
|
1045
1151
|
]);
|
|
1046
1152
|
}
|
|
1153
|
+
function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
1154
|
+
const [fork, setFork] = (0, import_react.useState)(null);
|
|
1155
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
1156
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
1157
|
+
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
1158
|
+
const projected = typeof useProjection === "function" ? useProjection("retrace/forkmap") : void 0;
|
|
1159
|
+
const versions = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
1160
|
+
const markerBySeq = new Map((versions?.versions ?? []).map((v) => [v.boundarySeq, v.markerText]));
|
|
1161
|
+
(0, import_react.useEffect)(() => {
|
|
1162
|
+
if (projected && Array.isArray(projected.nodes) && Array.isArray(projected.boundaries)) setFork(projected);
|
|
1163
|
+
}, [projected]);
|
|
1164
|
+
const refresh = () => {
|
|
1165
|
+
setLoading(true);
|
|
1166
|
+
setError(null);
|
|
1167
|
+
timelineGet(`/forkmap?sessionId=${encodeURIComponent(sessionId)}`).then((result) => {
|
|
1168
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message ?? "forkmap failed");
|
|
1169
|
+
setFork(result.value?.nodes ? result.value : null);
|
|
1170
|
+
}).catch((cause) => setError(cause?.message ?? "forkmap error")).finally(() => setLoading(false));
|
|
1171
|
+
};
|
|
1172
|
+
(0, import_react.useEffect)(() => {
|
|
1173
|
+
if (projected === void 0) refresh();
|
|
1174
|
+
}, []);
|
|
1175
|
+
const nodes = fork?.nodes ?? [];
|
|
1176
|
+
const boundaries = fork?.boundaries ?? [];
|
|
1177
|
+
const boundaryBySeq = new Map(boundaries.map((b) => [b.seq, b]));
|
|
1178
|
+
const spineSeqSet = new Set(nodes.map((n) => n.seq));
|
|
1179
|
+
const offSpine = boundaries.filter((b) => !spineSeqSet.has(b.seq));
|
|
1180
|
+
(0, import_react.useEffect)(() => {
|
|
1181
|
+
if (nodes.length === 0) return void 0;
|
|
1182
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-fork-list"));
|
|
1183
|
+
}, [nodes.length]);
|
|
1184
|
+
const ROW_H = 56;
|
|
1185
|
+
const visible = nodes.slice(
|
|
1186
|
+
Math.max(0, Math.floor(scrollTop / ROW_H) - 2),
|
|
1187
|
+
Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2)
|
|
1188
|
+
);
|
|
1189
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
1190
|
+
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
1191
|
+
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("fork.title")),
|
|
1192
|
+
(0, import_react.createElement)("button", {
|
|
1193
|
+
key: "refresh",
|
|
1194
|
+
type: "button",
|
|
1195
|
+
className: "dsh-rt-chip",
|
|
1196
|
+
onClick: refresh
|
|
1197
|
+
}, t("fork.refresh"))
|
|
1198
|
+
]),
|
|
1199
|
+
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
1200
|
+
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1201
|
+
!loading && nodes.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("fork.empty")),
|
|
1202
|
+
nodes.length > 0 && (0, import_react.createElement)("div", { key: "spine", className: "dsh-rt-fork-spine-label" }, t("fork.spine")),
|
|
1203
|
+
nodes.length > 0 && (0, import_react.createElement)("div", {
|
|
1204
|
+
key: "list",
|
|
1205
|
+
className: "dsh-rt-fork-list",
|
|
1206
|
+
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1207
|
+
}, [
|
|
1208
|
+
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${nodes.length * ROW_H}px`, position: "relative" } }, [
|
|
1209
|
+
visible.map((node) => (0, import_react.createElement)(ForkRow, {
|
|
1210
|
+
key: node.seq,
|
|
1211
|
+
node,
|
|
1212
|
+
boundary: boundaryBySeq.get(node.seq),
|
|
1213
|
+
markerText: markerBySeq.get(node.seq),
|
|
1214
|
+
t,
|
|
1215
|
+
top: nodes.indexOf(node) * ROW_H,
|
|
1216
|
+
onJump: () => jumpToAnchor(store, node.seq)
|
|
1217
|
+
}))
|
|
1218
|
+
])
|
|
1219
|
+
]),
|
|
1220
|
+
offSpine.length > 0 && (0, import_react.createElement)("div", { key: "hist", className: "dsh-rt-fork-hist" }, [
|
|
1221
|
+
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-fork-spine-label" }, t("fork.histTitle")),
|
|
1222
|
+
offSpine.map((boundary) => (0, import_react.createElement)(ForkHistRow, {
|
|
1223
|
+
key: boundary.seq,
|
|
1224
|
+
boundary,
|
|
1225
|
+
markerText: markerBySeq.get(boundary.seq),
|
|
1226
|
+
t,
|
|
1227
|
+
onJump: () => jumpToAnchor(store, boundary.seq)
|
|
1228
|
+
}))
|
|
1229
|
+
])
|
|
1230
|
+
]);
|
|
1231
|
+
}
|
|
1232
|
+
function ForkRow({ node, boundary, markerText, t, top, onJump }) {
|
|
1233
|
+
const isBoundary = boundary !== void 0;
|
|
1234
|
+
const icon = isBoundary ? KIND_ICONS[boundary.kind] ?? "\u2022" : NODE_ICONS[node.type] ?? "\u2022";
|
|
1235
|
+
const label = isBoundary ? kindLabel(boundary.kind, t) : nodeTypeLabel(node.type, t);
|
|
1236
|
+
return (0, import_react.createElement)("div", {
|
|
1237
|
+
className: `dsh-rt-fork-row${isBoundary ? " dsh-rt-fork-boundary" : ""}`,
|
|
1238
|
+
style: { top: `${top}px` }
|
|
1239
|
+
}, [
|
|
1240
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, icon),
|
|
1241
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1242
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1243
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, label),
|
|
1244
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${node.seq}`),
|
|
1245
|
+
isBoundary && boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1246
|
+
"span",
|
|
1247
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1248
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1249
|
+
)
|
|
1250
|
+
]),
|
|
1251
|
+
isBoundary && markerText && (0, import_react.createElement)(
|
|
1252
|
+
"div",
|
|
1253
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1254
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1255
|
+
)
|
|
1256
|
+
]),
|
|
1257
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1258
|
+
]);
|
|
1259
|
+
}
|
|
1260
|
+
function ForkHistRow({ boundary, markerText, t, onJump }) {
|
|
1261
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-fork-hist-row" }, [
|
|
1262
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, KIND_ICONS[boundary.kind] ?? "\u2022"),
|
|
1263
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1264
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1265
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, kindLabel(boundary.kind, t)),
|
|
1266
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${boundary.seq}`),
|
|
1267
|
+
boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1268
|
+
"span",
|
|
1269
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1270
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1271
|
+
)
|
|
1272
|
+
]),
|
|
1273
|
+
markerText && (0, import_react.createElement)(
|
|
1274
|
+
"div",
|
|
1275
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1276
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1277
|
+
)
|
|
1278
|
+
]),
|
|
1279
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1280
|
+
]);
|
|
1281
|
+
}
|
|
1047
1282
|
function apply(ctx) {
|
|
1048
1283
|
const disposeStyle = ensureStyle();
|
|
1049
1284
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
@@ -1087,6 +1322,17 @@ function apply(ctx) {
|
|
|
1087
1322
|
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1088
1323
|
})
|
|
1089
1324
|
}, RetraceView));
|
|
1325
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1326
|
+
name: "conversation.view",
|
|
1327
|
+
id: "retrace-fork",
|
|
1328
|
+
order: 30,
|
|
1329
|
+
locale: NS,
|
|
1330
|
+
label: () => t("view.fork"),
|
|
1331
|
+
inject: (sessionId, actions) => ({
|
|
1332
|
+
actions,
|
|
1333
|
+
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1334
|
+
})
|
|
1335
|
+
}, ForkView));
|
|
1090
1336
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
1091
1337
|
name: "settings.general.item",
|
|
1092
1338
|
id: "retrace",
|