dsh-retrace 0.4.2 → 0.4.5
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 +435 -104
- package/lib/client.js +634 -132
- package/lib/dynamic-client.js +435 -104
- package/lib/forkmap.js +106 -0
- package/lib/http.js +14 -0
- package/lib/projection/forkmap.js +69 -0
- package/lib/versioning.js +21 -6
- package/package.json +1 -1
package/lib/client.bundle.js
CHANGED
|
@@ -63,6 +63,8 @@ var zh = {
|
|
|
63
63
|
"marker.regenerate": "\u5DF2\u91CD\u65B0\u751F\u6210\u56DE\u590D",
|
|
64
64
|
"marker.originalLabel": "\u539F\u8F93\u5165",
|
|
65
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",
|
|
66
|
+
"marker.degradedHint": "\u6B64\u64CD\u4F5C\u6D89\u53CA\u5927\u8303\u56F4\u5BF9\u8BDD\uFF0C\u4E3A\u4FDD\u62A4\u5386\u53F2\u672A\u9690\u85CF\u5185\u5BB9\uFF08\u65E5\u5FD7\u5B8C\u597D\uFF09\u3002",
|
|
67
|
+
"marker.unionHint": "\u5DF2\u7D2F\u79EF\u9690\u85CF\u7EA6 {count}% \u7684\u5386\u53F2\u6D88\u606F\uFF1B\u53EF\u5728 \u8BBE\u7F6E\u2192\u901A\u7528 \u5173\u95ED\u300C\u6309\u6807\u8BB0\u9690\u85CF\u300D\u67E5\u770B\u5B8C\u6574\u5386\u53F2\u3002",
|
|
66
68
|
"options.title": "\u6D88\u606F\u7F16\u8F91\u63D2\u4EF6",
|
|
67
69
|
"options.showOriginalInput": "\u7F16\u8F91\u540E\u663E\u793A\u539F\u63D0\u95EE\u5BF9\u7167",
|
|
68
70
|
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF0C\u9ED8\u8BA4\u5173\uFF09",
|
|
@@ -120,7 +122,17 @@ var zh = {
|
|
|
120
122
|
"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
123
|
"timeline.gitInitConfirm": "\u786E\u5B9A\u8981\u521D\u59CB\u5316 git \u5417\uFF1F",
|
|
122
124
|
"error.generic": "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
123
|
-
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C"
|
|
125
|
+
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C",
|
|
126
|
+
"view.fork": "\u5206\u53C9",
|
|
127
|
+
"fork.title": "\u5206\u53C9\u56FE",
|
|
128
|
+
"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",
|
|
129
|
+
"fork.refresh": "\u5237\u65B0",
|
|
130
|
+
"fork.spine": "\u5F53\u524D\u8DEF\u5F84",
|
|
131
|
+
"fork.shadowed": "\u88AB\u906E\u853D {count} \u4E2A\u8282\u70B9",
|
|
132
|
+
"fork.node.user": "\u7528\u6237\u6D88\u606F",
|
|
133
|
+
"fork.node.assistant": "\u52A9\u624B\u56DE\u590D",
|
|
134
|
+
"fork.node.tool": "\u5DE5\u5177\u7ED3\u679C",
|
|
135
|
+
"fork.histTitle": "\u5386\u53F2\u5206\u53C9\u70B9"
|
|
124
136
|
};
|
|
125
137
|
var en = {
|
|
126
138
|
"action.edit": "Edit",
|
|
@@ -138,6 +150,8 @@ var en = {
|
|
|
138
150
|
"marker.regenerate": "Reply regenerated",
|
|
139
151
|
"marker.originalLabel": "Original input",
|
|
140
152
|
"marker.referenceHint": "Click to expand the original input (reference only, never sent to the model)",
|
|
153
|
+
"marker.degradedHint": "This operation spans a large part of the conversation; content stays visible to protect your history (the log is intact).",
|
|
154
|
+
"marker.unionHint": 'About {count}% of the history is hidden in total; disable "Hide shadowed messages" in Settings \u2192 General to review the full history.',
|
|
141
155
|
"options.title": "Message editor plugin",
|
|
142
156
|
"options.showOriginalInput": "Show the original input after editing",
|
|
143
157
|
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages, default off)",
|
|
@@ -195,7 +209,17 @@ var en = {
|
|
|
195
209
|
"timeline.gitInitDesc": "Runs git init in the workspace (adds a minimal .gitignore and a baseline commit); rollback will prefer git.",
|
|
196
210
|
"timeline.gitInitConfirm": "Initialize git in this workspace?",
|
|
197
211
|
"error.generic": "Operation failed; please try again",
|
|
198
|
-
"error.busy": "Stop the current reply before recalling or editing"
|
|
212
|
+
"error.busy": "Stop the current reply before recalling or editing",
|
|
213
|
+
"view.fork": "Fork",
|
|
214
|
+
"fork.title": "Fork map",
|
|
215
|
+
"fork.empty": "No forks yet. Recall / edit / regenerate / restore fork the path here.",
|
|
216
|
+
"fork.refresh": "Refresh",
|
|
217
|
+
"fork.spine": "Current path",
|
|
218
|
+
"fork.shadowed": "{count} shadowed nodes",
|
|
219
|
+
"fork.node.user": "User message",
|
|
220
|
+
"fork.node.assistant": "Assistant reply",
|
|
221
|
+
"fork.node.tool": "Tool result",
|
|
222
|
+
"fork.histTitle": "Historical fork points"
|
|
199
223
|
};
|
|
200
224
|
var SURFACE_TYPES = /* @__PURE__ */ new Set(["user/message", "assistant/message", "tool/result"]);
|
|
201
225
|
function isReplacementSurfaceEvent(event) {
|
|
@@ -351,23 +375,32 @@ var recallMarkerDefinition = {
|
|
|
351
375
|
kind: "recall-marker",
|
|
352
376
|
target: "chat",
|
|
353
377
|
match: (event) => {
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
378
|
+
if (!isReplacementSurfaceEvent(event)) return null;
|
|
379
|
+
if (event.type === "assistant/message") {
|
|
380
|
+
const id = event.data?.message?.id;
|
|
381
|
+
if (!isMarkerId(id)) return null;
|
|
382
|
+
return { id: `marker:${id}`, role: "start" };
|
|
383
|
+
}
|
|
384
|
+
if (event.type === "user/message" && isCompactCheckpoint(event.data?.source)) {
|
|
385
|
+
return { id: `marker:compact:${event.seq}`, role: "start" };
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
358
388
|
},
|
|
359
389
|
start: (_context, match) => {
|
|
360
390
|
const event = match.event;
|
|
361
|
-
const
|
|
362
|
-
const
|
|
391
|
+
const compact = event.type === "user/message" && isCompactCheckpoint(event.data?.source);
|
|
392
|
+
const id = compact ? "" : String(event.data.message.id);
|
|
393
|
+
const legacy = !compact && isLegacyMarkerId(id);
|
|
363
394
|
return {
|
|
364
395
|
seq: event.seq,
|
|
365
396
|
time: event.time,
|
|
366
|
-
op: markerOpFromId(id),
|
|
397
|
+
op: compact ? "compaction" : markerOpFromId(id),
|
|
367
398
|
legacy,
|
|
368
|
-
|
|
369
|
-
//
|
|
370
|
-
//
|
|
399
|
+
compact,
|
|
400
|
+
// Legacy/compact markers never hide: their shadowed range is kept for
|
|
401
|
+
// the action-row suppression check (useShadowed) but empty for legacy
|
|
402
|
+
// so no row is hidden and no action row is suppressed for legacy
|
|
403
|
+
// markers (rename must never make visible content disappear).
|
|
371
404
|
shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
|
|
372
405
|
targetSeq: event.data?.editor?.targetSeq,
|
|
373
406
|
text: event.data?.editor?.text
|
|
@@ -379,6 +412,9 @@ var recallMarkerDefinition = {
|
|
|
379
412
|
return chatNodeLike(context, "recall-marker", context.state.seq, context.state);
|
|
380
413
|
}
|
|
381
414
|
};
|
|
415
|
+
function isCompactCheckpoint(source) {
|
|
416
|
+
return Boolean(source) && source.kind === "plugin" && source.plugin === "compact";
|
|
417
|
+
}
|
|
382
418
|
function textOf(content) {
|
|
383
419
|
if (!Array.isArray(content)) return "";
|
|
384
420
|
return content.filter((block) => block && block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
@@ -393,41 +429,111 @@ function useMessageSeq(useSession, messageId) {
|
|
|
393
429
|
return void 0;
|
|
394
430
|
});
|
|
395
431
|
}
|
|
396
|
-
|
|
432
|
+
var SHADOW_SAFETY_RATIO = 0.4;
|
|
433
|
+
function hiddenKeysFor(shadowedSeqs, nodes) {
|
|
434
|
+
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
|
|
435
|
+
const hidden = new Set(shadowedSeqs);
|
|
436
|
+
const keys = [];
|
|
437
|
+
for (const node of nodes.values()) {
|
|
438
|
+
if (node.kind === "recall-marker") continue;
|
|
439
|
+
if (node.kind === "turn-tail") {
|
|
440
|
+
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
441
|
+
if (typeof closingSeq === "number" && hidden.has(closingSeq)) keys.push(node.key);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (node.kind === "tool-call") {
|
|
445
|
+
const resultSeq = node.data?.root?.seq;
|
|
446
|
+
if (typeof resultSeq === "number" && hidden.has(resultSeq)) keys.push(node.key);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (typeof node.anchorSeq === "number") {
|
|
450
|
+
const anchored = node.anchorSeq % 1 === 0 ? node.anchorSeq : Math.ceil(node.anchorSeq);
|
|
451
|
+
if (hidden.has(anchored)) keys.push(node.key);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return keys.length === 0 ? null : keys;
|
|
455
|
+
}
|
|
456
|
+
var EMPTY_HIDE_PLAN = Object.freeze({
|
|
457
|
+
hiddenFor: () => null,
|
|
458
|
+
planFor: () => null,
|
|
459
|
+
unionRatio: 0,
|
|
460
|
+
firstMarkerKey: null
|
|
461
|
+
});
|
|
462
|
+
var PLUGIN_PSEUDO_KINDS = /* @__PURE__ */ new Set(["user-actions", "retrace-reference", "recall-marker"]);
|
|
463
|
+
function realRowCount(nodes) {
|
|
464
|
+
let count = 0;
|
|
465
|
+
for (const node of nodes.values()) {
|
|
466
|
+
if (typeof node.anchorSeq === "number" && !PLUGIN_PSEUDO_KINDS.has(node.kind)) count += 1;
|
|
467
|
+
}
|
|
468
|
+
return count;
|
|
469
|
+
}
|
|
470
|
+
var hidePlanCacheSnapshot = null;
|
|
471
|
+
var hidePlanCacheValue = null;
|
|
472
|
+
function useMarkerHidePlan(useSession) {
|
|
473
|
+
return useSession((snapshot) => {
|
|
474
|
+
if (hidePlanCacheSnapshot === snapshot) return hidePlanCacheValue;
|
|
475
|
+
const nodes = snapshot.chat.nodes;
|
|
476
|
+
const rowCount = realRowCount(nodes);
|
|
477
|
+
const markers = [];
|
|
478
|
+
for (const node of nodes.values()) {
|
|
479
|
+
if (node.kind === "recall-marker" && !node.data?.compact) markers.push(node);
|
|
480
|
+
}
|
|
481
|
+
if (markers.length === 0) {
|
|
482
|
+
hidePlanCacheSnapshot = snapshot;
|
|
483
|
+
hidePlanCacheValue = EMPTY_HIDE_PLAN;
|
|
484
|
+
return hidePlanCacheValue;
|
|
485
|
+
}
|
|
486
|
+
const plans = /* @__PURE__ */ new Map();
|
|
487
|
+
const union = /* @__PURE__ */ new Set();
|
|
488
|
+
for (const marker of markers) {
|
|
489
|
+
const keys = hiddenKeysFor(marker.data?.shadowedSeqs, nodes);
|
|
490
|
+
const degraded = keys !== null && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO;
|
|
491
|
+
plans.set(marker.key, { keys: degraded ? null : keys, degraded });
|
|
492
|
+
if (keys !== null) for (const key of keys) union.add(key);
|
|
493
|
+
}
|
|
494
|
+
hidePlanCacheSnapshot = snapshot;
|
|
495
|
+
hidePlanCacheValue = {
|
|
496
|
+
planFor: (key) => plans.get(key) ?? null,
|
|
497
|
+
hiddenFor: (key) => plans.get(key)?.keys ?? null,
|
|
498
|
+
unionRatio: rowCount > 0 ? union.size / rowCount : 0,
|
|
499
|
+
firstMarkerKey: markers[0].key
|
|
500
|
+
};
|
|
501
|
+
return hidePlanCacheValue;
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function rowHiddenByKey(snapshot, rowKey) {
|
|
505
|
+
if (rowKey === void 0 || rowKey === null) return false;
|
|
506
|
+
const nodes = snapshot.chat.nodes;
|
|
507
|
+
const rowCount = realRowCount(nodes);
|
|
508
|
+
for (const node of nodes.values()) {
|
|
509
|
+
if (node.kind !== "recall-marker" || node.data?.compact) continue;
|
|
510
|
+
const keys = hiddenKeysFor(node.data?.shadowedSeqs, nodes);
|
|
511
|
+
if (keys === null) continue;
|
|
512
|
+
if (rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) continue;
|
|
513
|
+
if (keys.includes(rowKey)) return true;
|
|
514
|
+
}
|
|
515
|
+
return false;
|
|
516
|
+
}
|
|
517
|
+
function useSeqHidden(useSession, seq) {
|
|
397
518
|
return useSession((snapshot) => {
|
|
398
519
|
if (seq === void 0 || seq === null) return false;
|
|
399
520
|
for (const node of snapshot.chat.nodes.values()) {
|
|
400
|
-
if (node.kind
|
|
401
|
-
return
|
|
521
|
+
if (node.kind !== "recall-marker" && typeof node.anchorSeq === "number" && node.anchorSeq === seq) {
|
|
522
|
+
return rowHiddenByKey(snapshot, node.key);
|
|
402
523
|
}
|
|
403
524
|
}
|
|
404
525
|
return false;
|
|
405
526
|
});
|
|
406
527
|
}
|
|
407
|
-
|
|
408
|
-
function useHiddenKeys(useSession, shadowedSeqs) {
|
|
528
|
+
function useShadowed(useSession, seq) {
|
|
409
529
|
return useSession((snapshot) => {
|
|
410
|
-
if (
|
|
411
|
-
const hidden = new Set(shadowedSeqs);
|
|
412
|
-
const keys = [];
|
|
413
|
-
let rowCount = 0;
|
|
530
|
+
if (seq === void 0 || seq === null) return false;
|
|
414
531
|
for (const node of snapshot.chat.nodes.values()) {
|
|
415
|
-
if (
|
|
416
|
-
|
|
417
|
-
if (node.kind === "turn-tail") {
|
|
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;
|
|
532
|
+
if (node.kind === "recall-marker" && Array.isArray(node.data?.shadowedSeqs) && node.data.shadowedSeqs.includes(seq)) {
|
|
533
|
+
return true;
|
|
426
534
|
}
|
|
427
|
-
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
428
535
|
}
|
|
429
|
-
|
|
430
|
-
return keys.length === 0 ? null : keys;
|
|
536
|
+
return false;
|
|
431
537
|
});
|
|
432
538
|
}
|
|
433
539
|
function useMarkerDismissed(useSession, markerSeq, op) {
|
|
@@ -464,10 +570,11 @@ function useEditReference(useSession, mySeq) {
|
|
|
464
570
|
}
|
|
465
571
|
function AssistantActions({ messageId, sessionId, useSession, t }) {
|
|
466
572
|
const seq = useMessageSeq(useSession, messageId);
|
|
573
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
467
574
|
const shadowed = useShadowed(useSession, seq);
|
|
468
575
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
469
576
|
const [failure, setFailure] = (0, import_react.useState)(null);
|
|
470
|
-
if (shadowed || seq === void 0) return null;
|
|
577
|
+
if (hidden || shadowed || seq === void 0) return null;
|
|
471
578
|
const run = (op) => {
|
|
472
579
|
setBusy(true);
|
|
473
580
|
setFailure(null);
|
|
@@ -510,11 +617,11 @@ function AssistantActions({ messageId, sessionId, useSession, t }) {
|
|
|
510
617
|
}
|
|
511
618
|
function ReferenceRow({ node, useSession, t }) {
|
|
512
619
|
const { seq, messageId } = node.data;
|
|
513
|
-
const
|
|
620
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
514
621
|
const markerRef = useEditReference(useSession, seq);
|
|
515
622
|
const referenceText = editReferences.get(messageId) ?? markerRef;
|
|
516
623
|
const config = useConfig();
|
|
517
|
-
if (
|
|
624
|
+
if (hidden) return null;
|
|
518
625
|
if (referenceText === null || !config.showOriginalInput) return null;
|
|
519
626
|
return (0, import_react.createElement)("div", { className: "dsh-rt-user-row" }, [
|
|
520
627
|
(0, import_react.createElement)("details", { className: "dsh-rt-reference" }, [
|
|
@@ -529,12 +636,13 @@ function ReferenceRow({ node, useSession, t }) {
|
|
|
529
636
|
}
|
|
530
637
|
function UserActionsRow({ node, sessionId, useSession, inputActions, t }) {
|
|
531
638
|
const { seq, messageId, content } = node.data;
|
|
639
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
532
640
|
const shadowed = useShadowed(useSession, seq);
|
|
533
641
|
const [editing, setEditing] = (0, import_react.useState)(false);
|
|
534
642
|
const [draft, setDraft] = (0, import_react.useState)("");
|
|
535
643
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
536
644
|
const [failure, setFailure] = (0, import_react.useState)(null);
|
|
537
|
-
if (shadowed) return null;
|
|
645
|
+
if (hidden || shadowed) return null;
|
|
538
646
|
const openEditor = () => {
|
|
539
647
|
setDraft(textOf(content));
|
|
540
648
|
setFailure(null);
|
|
@@ -627,15 +735,26 @@ function UserActionsRow({ node, sessionId, useSession, inputActions, t }) {
|
|
|
627
735
|
]);
|
|
628
736
|
}
|
|
629
737
|
function RecallMarkerRow({ node, useSession, t }) {
|
|
630
|
-
const { seq, op, shadowedSeqs, legacy } = node.data;
|
|
738
|
+
const { seq, op, shadowedSeqs, legacy, compact } = node.data;
|
|
739
|
+
if (compact) return null;
|
|
631
740
|
const dismissed = useMarkerDismissed(useSession, seq, op);
|
|
632
|
-
const
|
|
741
|
+
const hidePlan = useMarkerHidePlan(useSession);
|
|
742
|
+
const plan = hidePlan.planFor(node.key);
|
|
743
|
+
const hiddenKeys = legacy || !getConfig().hideShadowed ? null : plan?.keys ?? null;
|
|
633
744
|
const css = hiddenKeys === null ? null : hiddenKeys.map((key) => `[data-chat-anchor-key=${JSON.stringify(key)}]{display:none!important}`).join("");
|
|
634
745
|
const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
|
|
635
746
|
const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
|
|
747
|
+
const degradedHint = !legacy && plan?.degraded === true ? (0, import_react.createElement)("div", { key: "degraded", className: "dsh-rt-marker-hint" }, t("marker.degradedHint")) : null;
|
|
748
|
+
const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
|
|
749
|
+
"div",
|
|
750
|
+
{ key: "union", className: "dsh-rt-marker-hint" },
|
|
751
|
+
t("marker.unionHint", { count: Math.round(hidePlan.unionRatio * 100) })
|
|
752
|
+
) : null;
|
|
636
753
|
return (0, import_react.createElement)("div", { className: "dsh-rt-marker-block", "data-dismissed": dismissed || void 0 }, [
|
|
637
754
|
css !== null && (0, import_react.createElement)("style", { key: "hide", dangerouslySetInnerHTML: { __html: css } }),
|
|
638
|
-
!dismissed && (0, import_react.createElement)("div", { key: "label", className: "dsh-rt-marker", role: "status" }, label)
|
|
755
|
+
!dismissed && (0, import_react.createElement)("div", { key: "label", className: "dsh-rt-marker", role: "status" }, label),
|
|
756
|
+
!dismissed && degradedHint,
|
|
757
|
+
!dismissed && unionHint
|
|
639
758
|
]);
|
|
640
759
|
}
|
|
641
760
|
function OptionsRow({ t }) {
|
|
@@ -712,6 +831,7 @@ var CSS = `
|
|
|
712
831
|
.dsh-rt-error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;max-width:min(525px,82%)}
|
|
713
832
|
.dsh-rt-marker-block{display:flex;flex-direction:column;align-items:center;gap:4px;width:100%;max-width:var(--dsh-chat-content-width);box-sizing:border-box;margin:0 auto;padding:2px 0}
|
|
714
833
|
.dsh-rt-marker{text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
|
|
834
|
+
.dsh-rt-marker-hint{text-align:center;color:var(--dsw-alias-state-warning-primary);font-size:11px;line-height:16px;margin-top:2px}
|
|
715
835
|
.dsh-rt-reference{width:min(525px,82%);box-sizing:border-box;border:1px dashed var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-elevated);border-radius:10px;padding:2px 12px}
|
|
716
836
|
.dsh-rt-reference summary{color:var(--dsw-alias-label-caption);cursor:pointer;user-select:none;font-size:12px;line-height:22px;list-style:none;display:inline-flex;align-items:center;gap:6px;max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
717
837
|
.dsh-rt-reference summary::-webkit-details-marker{display:none}
|
|
@@ -730,7 +850,8 @@ var CSS = `
|
|
|
730
850
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
731
851
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
732
852
|
/* ---- P1 timeline (conversation view tab) ---- */
|
|
733
|
-
|
|
853
|
+
|
|
854
|
+
.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
855
|
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
735
856
|
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
736
857
|
.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 +888,126 @@ var CSS = `
|
|
|
767
888
|
.dsh-rt-modal-buttons{display:flex;justify-content:flex-end;gap:8px;flex:none}
|
|
768
889
|
.dsh-rt-confirm{background:var(--dsw-alias-state-error-primary)}
|
|
769
890
|
.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}
|
|
891
|
+
.dsh-rt-fork-list{overflow-y:auto;flex:1;min-height:0;position:relative}
|
|
892
|
+
.dsh-rt-fork-spine-label{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;flex:none}
|
|
893
|
+
.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}
|
|
894
|
+
.dsh-rt-fork-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
895
|
+
.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)}
|
|
896
|
+
.dsh-rt-fork-boundary .dsh-rt-fork-icon{color:var(--dsw-alias-state-warning-primary)}
|
|
897
|
+
.dsh-rt-fork-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
898
|
+
.dsh-rt-fork-line{display:flex;align-items:baseline;gap:8px;min-width:0}
|
|
899
|
+
.dsh-rt-fork-label{font-size:12px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
900
|
+
.dsh-rt-fork-seq{font-family:var(--dsw-font-mono);font-size:11px;line-height:18px;color:var(--dsw-alias-label-caption);flex:none}
|
|
901
|
+
.dsh-rt-fork-shadowed{font-size:11px;line-height:18px;color:var(--dsw-alias-state-warning-primary);flex:none}
|
|
902
|
+
.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}
|
|
903
|
+
.dsh-rt-fork-boundary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-elevated)}
|
|
904
|
+
.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}
|
|
905
|
+
.dsh-rt-fork-hist-row{display:flex;align-items:flex-start;gap:8px;padding:4px 8px;border-radius:8px}
|
|
906
|
+
.dsh-rt-fork-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
770
907
|
`;
|
|
908
|
+
var JUMP_PAGE_BUDGET = 24;
|
|
909
|
+
function switchToViewTab(viewId) {
|
|
910
|
+
const ORDER = { chat: 0, trajectory: 1, retrace: 2, "retrace-fork": 3 };
|
|
911
|
+
const index = ORDER[viewId];
|
|
912
|
+
if (index === void 0) {
|
|
913
|
+
console.warn(`[dsh-retrace] no tab order registered for "${viewId}"`);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const buttons = [...document.querySelectorAll('[role="tablist"]')].filter((tablist) => tablist.getBoundingClientRect().width > 0).flatMap((tablist) => [...tablist.querySelectorAll('[role="tab"]')]);
|
|
917
|
+
const button = buttons[index];
|
|
918
|
+
if (!button) {
|
|
919
|
+
console.warn(`[dsh-retrace] tab "${viewId}" (index ${index}) not found in the conversation tab bar`);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
button.click();
|
|
923
|
+
}
|
|
924
|
+
function nodeCountOf(nodes) {
|
|
925
|
+
if (!nodes) return 0;
|
|
926
|
+
if (typeof nodes.size === "number") return nodes.size;
|
|
927
|
+
return [...nodes.values()].length;
|
|
928
|
+
}
|
|
929
|
+
function waitForElement(selector, frames) {
|
|
930
|
+
return new Promise((resolve) => {
|
|
931
|
+
let remaining = frames;
|
|
932
|
+
const probe = () => {
|
|
933
|
+
const el = document.querySelector(selector);
|
|
934
|
+
if (el !== null) return resolve(el);
|
|
935
|
+
if (remaining-- <= 0) return resolve(null);
|
|
936
|
+
requestAnimationFrame(probe);
|
|
937
|
+
};
|
|
938
|
+
requestAnimationFrame(probe);
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
function flashKey(key) {
|
|
942
|
+
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
943
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
944
|
+
const tag = document.createElement("style");
|
|
945
|
+
tag.dataset.pluginCss = id;
|
|
946
|
+
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)}}`;
|
|
947
|
+
document.head.appendChild(tag);
|
|
948
|
+
setTimeout(() => tag.remove(), 3400);
|
|
949
|
+
}
|
|
950
|
+
async function jumpToAnchor(store, anchorSeq) {
|
|
951
|
+
switchToViewTab("chat");
|
|
952
|
+
if (!store || typeof store.loadOlder !== "function" || typeof store.getSnapshot !== "function") return;
|
|
953
|
+
const keyOfSeq = (seq) => {
|
|
954
|
+
const nodes = store.getSnapshot()?.chat?.nodes;
|
|
955
|
+
if (!nodes) return null;
|
|
956
|
+
for (const node of nodes.values()) {
|
|
957
|
+
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
958
|
+
}
|
|
959
|
+
return null;
|
|
960
|
+
};
|
|
961
|
+
let key = keyOfSeq(anchorSeq);
|
|
962
|
+
let pages = 0;
|
|
963
|
+
while (key === null && pages < JUMP_PAGE_BUDGET && store.hasMore) {
|
|
964
|
+
const before = nodeCountOf(store.getSnapshot()?.chat?.nodes);
|
|
965
|
+
await store.loadOlder();
|
|
966
|
+
const after = nodeCountOf(store.getSnapshot()?.chat?.nodes);
|
|
967
|
+
pages += 1;
|
|
968
|
+
if (after === before) break;
|
|
969
|
+
key = keyOfSeq(anchorSeq);
|
|
970
|
+
}
|
|
971
|
+
if (key === null) {
|
|
972
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
973
|
+
key = keyOfSeq(anchorSeq);
|
|
974
|
+
}
|
|
975
|
+
if (key === null) {
|
|
976
|
+
console.warn(`[dsh-retrace] jump target seq ${anchorSeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
980
|
+
if (el === null) return;
|
|
981
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
982
|
+
flashKey(key);
|
|
983
|
+
}
|
|
984
|
+
var NODE_ICONS = { "user/message": "\u{1F464}", "assistant/message": "\u{1F916}", "tool/result": "\u{1F527}" };
|
|
985
|
+
function nodeTypeLabel(type, t) {
|
|
986
|
+
if (type === "user/message") return t("fork.node.user");
|
|
987
|
+
if (type === "assistant/message") return t("fork.node.assistant");
|
|
988
|
+
if (type === "tool/result") return t("fork.node.tool");
|
|
989
|
+
return type;
|
|
990
|
+
}
|
|
991
|
+
function bindListHeight(listEl) {
|
|
992
|
+
if (!listEl) return () => {
|
|
993
|
+
};
|
|
994
|
+
const measure = () => {
|
|
995
|
+
const top = listEl.getBoundingClientRect().top;
|
|
996
|
+
const height = Math.max(120, window.innerHeight - top - 16);
|
|
997
|
+
if (Math.abs(listEl.clientHeight - height) > 4) {
|
|
998
|
+
listEl.style.flex = "none";
|
|
999
|
+
listEl.style.height = `${height}px`;
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
measure();
|
|
1003
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
|
|
1004
|
+
ro?.observe(document.body);
|
|
1005
|
+
window.addEventListener("resize", measure);
|
|
1006
|
+
return () => {
|
|
1007
|
+
ro?.disconnect();
|
|
1008
|
+
window.removeEventListener("resize", measure);
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
771
1011
|
function ensureStyle() {
|
|
772
1012
|
if (typeof document === "undefined") return () => {
|
|
773
1013
|
};
|
|
@@ -835,63 +1075,7 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
835
1075
|
if (projected === void 0) refresh();
|
|
836
1076
|
refreshGit();
|
|
837
1077
|
}, []);
|
|
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
|
-
}
|
|
1078
|
+
const jump = (boundarySeq) => jumpToAnchor(store, boundarySeq);
|
|
895
1079
|
const requestPreview = (record) => {
|
|
896
1080
|
setPreviewScope("both");
|
|
897
1081
|
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
@@ -924,6 +1108,10 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
924
1108
|
const ROW_H = 64;
|
|
925
1109
|
const list = versions ?? [];
|
|
926
1110
|
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));
|
|
1111
|
+
(0, import_react.useEffect)(() => {
|
|
1112
|
+
if (list.length === 0) return void 0;
|
|
1113
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-timeline-list"));
|
|
1114
|
+
}, [list.length]);
|
|
927
1115
|
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
928
1116
|
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
929
1117
|
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("timeline.title")),
|
|
@@ -961,7 +1149,7 @@ function RetraceView({ sessionId, useProjection, t, actions, store }) {
|
|
|
961
1149
|
t,
|
|
962
1150
|
top: list.indexOf(record) * ROW_H,
|
|
963
1151
|
onPreview: () => requestPreview(record),
|
|
964
|
-
onTrajectory: () =>
|
|
1152
|
+
onTrajectory: () => switchToViewTab("trajectory"),
|
|
965
1153
|
onJump: () => jump(record.boundarySeq)
|
|
966
1154
|
}))
|
|
967
1155
|
])
|
|
@@ -1044,6 +1232,135 @@ function PreviewBox({ preview, scope, setScope, busy, t, onConfirm, onCancel })
|
|
|
1044
1232
|
])
|
|
1045
1233
|
]);
|
|
1046
1234
|
}
|
|
1235
|
+
function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
1236
|
+
const [fork, setFork] = (0, import_react.useState)(null);
|
|
1237
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
1238
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
1239
|
+
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
1240
|
+
const projected = typeof useProjection === "function" ? useProjection("retrace/forkmap") : void 0;
|
|
1241
|
+
const versions = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
1242
|
+
const markerBySeq = new Map((versions?.versions ?? []).map((v) => [v.boundarySeq, v.markerText]));
|
|
1243
|
+
(0, import_react.useEffect)(() => {
|
|
1244
|
+
if (projected && Array.isArray(projected.nodes) && Array.isArray(projected.boundaries)) setFork(projected);
|
|
1245
|
+
}, [projected]);
|
|
1246
|
+
const refresh = () => {
|
|
1247
|
+
setLoading(true);
|
|
1248
|
+
setError(null);
|
|
1249
|
+
timelineGet(`/forkmap?sessionId=${encodeURIComponent(sessionId)}`).then((result) => {
|
|
1250
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message ?? "forkmap failed");
|
|
1251
|
+
setFork(result.value?.nodes ? result.value : null);
|
|
1252
|
+
}).catch((cause) => setError(cause?.message ?? "forkmap error")).finally(() => setLoading(false));
|
|
1253
|
+
};
|
|
1254
|
+
(0, import_react.useEffect)(() => {
|
|
1255
|
+
if (projected === void 0) refresh();
|
|
1256
|
+
}, []);
|
|
1257
|
+
const nodes = fork?.nodes ?? [];
|
|
1258
|
+
const boundaries = fork?.boundaries ?? [];
|
|
1259
|
+
const boundaryBySeq = new Map(boundaries.map((b) => [b.seq, b]));
|
|
1260
|
+
const spineSeqSet = new Set(nodes.map((n) => n.seq));
|
|
1261
|
+
const offSpine = boundaries.filter((b) => !spineSeqSet.has(b.seq));
|
|
1262
|
+
(0, import_react.useEffect)(() => {
|
|
1263
|
+
if (nodes.length === 0) return void 0;
|
|
1264
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-fork-list"));
|
|
1265
|
+
}, [nodes.length]);
|
|
1266
|
+
const ROW_H = 56;
|
|
1267
|
+
const visible = nodes.slice(
|
|
1268
|
+
Math.max(0, Math.floor(scrollTop / ROW_H) - 2),
|
|
1269
|
+
Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2)
|
|
1270
|
+
);
|
|
1271
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
1272
|
+
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
1273
|
+
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("fork.title")),
|
|
1274
|
+
(0, import_react.createElement)("button", {
|
|
1275
|
+
key: "refresh",
|
|
1276
|
+
type: "button",
|
|
1277
|
+
className: "dsh-rt-chip",
|
|
1278
|
+
onClick: refresh
|
|
1279
|
+
}, t("fork.refresh"))
|
|
1280
|
+
]),
|
|
1281
|
+
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
1282
|
+
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1283
|
+
!loading && nodes.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("fork.empty")),
|
|
1284
|
+
nodes.length > 0 && (0, import_react.createElement)("div", { key: "spine", className: "dsh-rt-fork-spine-label" }, t("fork.spine")),
|
|
1285
|
+
nodes.length > 0 && (0, import_react.createElement)("div", {
|
|
1286
|
+
key: "list",
|
|
1287
|
+
className: "dsh-rt-fork-list",
|
|
1288
|
+
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1289
|
+
}, [
|
|
1290
|
+
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${nodes.length * ROW_H}px`, position: "relative" } }, [
|
|
1291
|
+
visible.map((node) => (0, import_react.createElement)(ForkRow, {
|
|
1292
|
+
key: node.seq,
|
|
1293
|
+
node,
|
|
1294
|
+
boundary: boundaryBySeq.get(node.seq),
|
|
1295
|
+
markerText: markerBySeq.get(node.seq),
|
|
1296
|
+
t,
|
|
1297
|
+
top: nodes.indexOf(node) * ROW_H,
|
|
1298
|
+
onJump: () => jumpToAnchor(store, node.seq)
|
|
1299
|
+
}))
|
|
1300
|
+
])
|
|
1301
|
+
]),
|
|
1302
|
+
offSpine.length > 0 && (0, import_react.createElement)("div", { key: "hist", className: "dsh-rt-fork-hist" }, [
|
|
1303
|
+
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-fork-spine-label" }, t("fork.histTitle")),
|
|
1304
|
+
offSpine.map((boundary) => (0, import_react.createElement)(ForkHistRow, {
|
|
1305
|
+
key: boundary.seq,
|
|
1306
|
+
boundary,
|
|
1307
|
+
markerText: markerBySeq.get(boundary.seq),
|
|
1308
|
+
t,
|
|
1309
|
+
onJump: () => jumpToAnchor(store, boundary.seq)
|
|
1310
|
+
}))
|
|
1311
|
+
])
|
|
1312
|
+
]);
|
|
1313
|
+
}
|
|
1314
|
+
function ForkRow({ node, boundary, markerText, t, top, onJump }) {
|
|
1315
|
+
const isBoundary = boundary !== void 0;
|
|
1316
|
+
const icon = isBoundary ? KIND_ICONS[boundary.kind] ?? "\u2022" : NODE_ICONS[node.type] ?? "\u2022";
|
|
1317
|
+
const label = isBoundary ? kindLabel(boundary.kind, t) : nodeTypeLabel(node.type, t);
|
|
1318
|
+
return (0, import_react.createElement)("div", {
|
|
1319
|
+
className: `dsh-rt-fork-row${isBoundary ? " dsh-rt-fork-boundary" : ""}`,
|
|
1320
|
+
style: { top: `${top}px` }
|
|
1321
|
+
}, [
|
|
1322
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, icon),
|
|
1323
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1324
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1325
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, label),
|
|
1326
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${node.seq}`),
|
|
1327
|
+
isBoundary && boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1328
|
+
"span",
|
|
1329
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1330
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1331
|
+
)
|
|
1332
|
+
]),
|
|
1333
|
+
isBoundary && markerText && (0, import_react.createElement)(
|
|
1334
|
+
"div",
|
|
1335
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1336
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1337
|
+
)
|
|
1338
|
+
]),
|
|
1339
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1340
|
+
]);
|
|
1341
|
+
}
|
|
1342
|
+
function ForkHistRow({ boundary, markerText, t, onJump }) {
|
|
1343
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-fork-hist-row" }, [
|
|
1344
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, KIND_ICONS[boundary.kind] ?? "\u2022"),
|
|
1345
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1346
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1347
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, kindLabel(boundary.kind, t)),
|
|
1348
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${boundary.seq}`),
|
|
1349
|
+
boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1350
|
+
"span",
|
|
1351
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1352
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1353
|
+
)
|
|
1354
|
+
]),
|
|
1355
|
+
markerText && (0, import_react.createElement)(
|
|
1356
|
+
"div",
|
|
1357
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1358
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1359
|
+
)
|
|
1360
|
+
]),
|
|
1361
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1362
|
+
]);
|
|
1363
|
+
}
|
|
1047
1364
|
function apply(ctx) {
|
|
1048
1365
|
const disposeStyle = ensureStyle();
|
|
1049
1366
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
@@ -1051,9 +1368,12 @@ function apply(ctx) {
|
|
|
1051
1368
|
const t = ctx.locale.bind(NS);
|
|
1052
1369
|
const conversationEvents = ctx.get("conversationEvents");
|
|
1053
1370
|
if (conversationEvents) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1371
|
+
const disposeDefinitions = [
|
|
1372
|
+
conversationEvents.register(userActionsDefinition),
|
|
1373
|
+
conversationEvents.register(userReferenceDefinition),
|
|
1374
|
+
conversationEvents.register(recallMarkerDefinition)
|
|
1375
|
+
];
|
|
1376
|
+
ctx.effect(() => () => disposeDefinitions.forEach((dispose) => dispose()), "dsh-retrace: conversation definitions");
|
|
1057
1377
|
}
|
|
1058
1378
|
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
|
|
1059
1379
|
name: "conversation.chat.assistant-actions",
|
|
@@ -1087,6 +1407,17 @@ function apply(ctx) {
|
|
|
1087
1407
|
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1088
1408
|
})
|
|
1089
1409
|
}, RetraceView));
|
|
1410
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1411
|
+
name: "conversation.view",
|
|
1412
|
+
id: "retrace-fork",
|
|
1413
|
+
order: 30,
|
|
1414
|
+
locale: NS,
|
|
1415
|
+
label: () => t("view.fork"),
|
|
1416
|
+
inject: (sessionId, actions) => ({
|
|
1417
|
+
actions,
|
|
1418
|
+
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1419
|
+
})
|
|
1420
|
+
}, ForkView));
|
|
1090
1421
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
1091
1422
|
name: "settings.general.item",
|
|
1092
1423
|
id: "retrace",
|