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/dynamic-client.js
CHANGED
|
@@ -69,6 +69,8 @@ return {
|
|
|
69
69
|
"marker.regenerate": "\u5DF2\u91CD\u65B0\u751F\u6210\u56DE\u590D",
|
|
70
70
|
"marker.originalLabel": "\u539F\u8F93\u5165",
|
|
71
71
|
"marker.referenceHint": "\u70B9\u51FB\u5C55\u5F00\u67E5\u770B\u539F\u63D0\u95EE\uFF08\u4EC5\u4F5C\u5BF9\u7167\uFF0C\u4E0D\u4F1A\u8FDB\u5165\u6A21\u578B\u4E0A\u4E0B\u6587\uFF09",
|
|
72
|
+
"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",
|
|
73
|
+
"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",
|
|
72
74
|
"options.title": "\u6D88\u606F\u7F16\u8F91\u63D2\u4EF6",
|
|
73
75
|
"options.showOriginalInput": "\u7F16\u8F91\u540E\u663E\u793A\u539F\u63D0\u95EE\u5BF9\u7167",
|
|
74
76
|
"options.editFromScratch": "\u7F16\u8F91\u540E\u4ECE\u65B0\u5BF9\u8BDD\u5F00\u59CB\uFF08\u9690\u85CF\u6B64\u524D\u7684\u6D88\u606F\uFF0C\u9ED8\u8BA4\u5173\uFF09",
|
|
@@ -126,7 +128,17 @@ return {
|
|
|
126
128
|
"timeline.gitInitDesc": "\u5728\u5DE5\u4F5C\u533A\u6267\u884C git init\uFF08\u4EC5\u6DFB\u52A0 .gitignore \u4E0E\u4E00\u4E2A\u57FA\u7EBF\u63D0\u4EA4\uFF09\uFF0C\u7248\u672C\u56DE\u9000\u5C06\u4F18\u5148\u4F7F\u7528 git\u3002",
|
|
127
129
|
"timeline.gitInitConfirm": "\u786E\u5B9A\u8981\u521D\u59CB\u5316 git \u5417\uFF1F",
|
|
128
130
|
"error.generic": "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5",
|
|
129
|
-
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C"
|
|
131
|
+
"error.busy": "\u8BF7\u5148\u505C\u6B62\u5F53\u524D\u56DE\u590D\u518D\u64CD\u4F5C",
|
|
132
|
+
"view.fork": "\u5206\u53C9",
|
|
133
|
+
"fork.title": "\u5206\u53C9\u56FE",
|
|
134
|
+
"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",
|
|
135
|
+
"fork.refresh": "\u5237\u65B0",
|
|
136
|
+
"fork.spine": "\u5F53\u524D\u8DEF\u5F84",
|
|
137
|
+
"fork.shadowed": "\u88AB\u906E\u853D {count} \u4E2A\u8282\u70B9",
|
|
138
|
+
"fork.node.user": "\u7528\u6237\u6D88\u606F",
|
|
139
|
+
"fork.node.assistant": "\u52A9\u624B\u56DE\u590D",
|
|
140
|
+
"fork.node.tool": "\u5DE5\u5177\u7ED3\u679C",
|
|
141
|
+
"fork.histTitle": "\u5386\u53F2\u5206\u53C9\u70B9"
|
|
130
142
|
};
|
|
131
143
|
var en = {
|
|
132
144
|
"action.edit": "Edit",
|
|
@@ -144,6 +156,8 @@ return {
|
|
|
144
156
|
"marker.regenerate": "Reply regenerated",
|
|
145
157
|
"marker.originalLabel": "Original input",
|
|
146
158
|
"marker.referenceHint": "Click to expand the original input (reference only, never sent to the model)",
|
|
159
|
+
"marker.degradedHint": "This operation spans a large part of the conversation; content stays visible to protect your history (the log is intact).",
|
|
160
|
+
"marker.unionHint": 'About {count}% of the history is hidden in total; disable "Hide shadowed messages" in Settings \u2192 General to review the full history.',
|
|
147
161
|
"options.title": "Message editor plugin",
|
|
148
162
|
"options.showOriginalInput": "Show the original input after editing",
|
|
149
163
|
"options.editFromScratch": "Start a fresh conversation after editing (hide earlier messages, default off)",
|
|
@@ -201,7 +215,17 @@ return {
|
|
|
201
215
|
"timeline.gitInitDesc": "Runs git init in the workspace (adds a minimal .gitignore and a baseline commit); rollback will prefer git.",
|
|
202
216
|
"timeline.gitInitConfirm": "Initialize git in this workspace?",
|
|
203
217
|
"error.generic": "Operation failed; please try again",
|
|
204
|
-
"error.busy": "Stop the current reply before recalling or editing"
|
|
218
|
+
"error.busy": "Stop the current reply before recalling or editing",
|
|
219
|
+
"view.fork": "Fork",
|
|
220
|
+
"fork.title": "Fork map",
|
|
221
|
+
"fork.empty": "No forks yet. Recall / edit / regenerate / restore fork the path here.",
|
|
222
|
+
"fork.refresh": "Refresh",
|
|
223
|
+
"fork.spine": "Current path",
|
|
224
|
+
"fork.shadowed": "{count} shadowed nodes",
|
|
225
|
+
"fork.node.user": "User message",
|
|
226
|
+
"fork.node.assistant": "Assistant reply",
|
|
227
|
+
"fork.node.tool": "Tool result",
|
|
228
|
+
"fork.histTitle": "Historical fork points"
|
|
205
229
|
};
|
|
206
230
|
var SURFACE_TYPES = /* @__PURE__ */ new Set(["user/message", "assistant/message", "tool/result"]);
|
|
207
231
|
function isReplacementSurfaceEvent(event) {
|
|
@@ -357,23 +381,32 @@ return {
|
|
|
357
381
|
kind: "recall-marker",
|
|
358
382
|
target: "chat",
|
|
359
383
|
match: (event) => {
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
384
|
+
if (!isReplacementSurfaceEvent(event)) return null;
|
|
385
|
+
if (event.type === "assistant/message") {
|
|
386
|
+
const id = event.data?.message?.id;
|
|
387
|
+
if (!isMarkerId(id)) return null;
|
|
388
|
+
return { id: `marker:${id}`, role: "start" };
|
|
389
|
+
}
|
|
390
|
+
if (event.type === "user/message" && isCompactCheckpoint(event.data?.source)) {
|
|
391
|
+
return { id: `marker:compact:${event.seq}`, role: "start" };
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
364
394
|
},
|
|
365
395
|
start: (_context, match) => {
|
|
366
396
|
const event = match.event;
|
|
367
|
-
const
|
|
368
|
-
const
|
|
397
|
+
const compact = event.type === "user/message" && isCompactCheckpoint(event.data?.source);
|
|
398
|
+
const id = compact ? "" : String(event.data.message.id);
|
|
399
|
+
const legacy = !compact && isLegacyMarkerId(id);
|
|
369
400
|
return {
|
|
370
401
|
seq: event.seq,
|
|
371
402
|
time: event.time,
|
|
372
|
-
op: markerOpFromId(id),
|
|
403
|
+
op: compact ? "compaction" : markerOpFromId(id),
|
|
373
404
|
legacy,
|
|
374
|
-
|
|
375
|
-
//
|
|
376
|
-
//
|
|
405
|
+
compact,
|
|
406
|
+
// Legacy/compact markers never hide: their shadowed range is kept for
|
|
407
|
+
// the action-row suppression check (useShadowed) but empty for legacy
|
|
408
|
+
// so no row is hidden and no action row is suppressed for legacy
|
|
409
|
+
// markers (rename must never make visible content disappear).
|
|
377
410
|
shadowedSeqs: legacy ? [] : Array.isArray(event.sourceEventSeqs) ? event.sourceEventSeqs.slice() : [],
|
|
378
411
|
targetSeq: event.data?.editor?.targetSeq,
|
|
379
412
|
text: event.data?.editor?.text
|
|
@@ -385,6 +418,9 @@ return {
|
|
|
385
418
|
return chatNodeLike(context, "recall-marker", context.state.seq, context.state);
|
|
386
419
|
}
|
|
387
420
|
};
|
|
421
|
+
function isCompactCheckpoint(source) {
|
|
422
|
+
return Boolean(source) && source.kind === "plugin" && source.plugin === "compact";
|
|
423
|
+
}
|
|
388
424
|
function textOf(content) {
|
|
389
425
|
if (!Array.isArray(content)) return "";
|
|
390
426
|
return content.filter((block) => block && block.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n");
|
|
@@ -399,41 +435,111 @@ return {
|
|
|
399
435
|
return void 0;
|
|
400
436
|
});
|
|
401
437
|
}
|
|
402
|
-
|
|
438
|
+
var SHADOW_SAFETY_RATIO = 0.4;
|
|
439
|
+
function hiddenKeysFor(shadowedSeqs, nodes) {
|
|
440
|
+
if (!Array.isArray(shadowedSeqs) || shadowedSeqs.length === 0) return null;
|
|
441
|
+
const hidden = new Set(shadowedSeqs);
|
|
442
|
+
const keys = [];
|
|
443
|
+
for (const node of nodes.values()) {
|
|
444
|
+
if (node.kind === "recall-marker") continue;
|
|
445
|
+
if (node.kind === "turn-tail") {
|
|
446
|
+
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
447
|
+
if (typeof closingSeq === "number" && hidden.has(closingSeq)) keys.push(node.key);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (node.kind === "tool-call") {
|
|
451
|
+
const resultSeq = node.data?.root?.seq;
|
|
452
|
+
if (typeof resultSeq === "number" && hidden.has(resultSeq)) keys.push(node.key);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (typeof node.anchorSeq === "number") {
|
|
456
|
+
const anchored = node.anchorSeq % 1 === 0 ? node.anchorSeq : Math.ceil(node.anchorSeq);
|
|
457
|
+
if (hidden.has(anchored)) keys.push(node.key);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return keys.length === 0 ? null : keys;
|
|
461
|
+
}
|
|
462
|
+
var EMPTY_HIDE_PLAN = Object.freeze({
|
|
463
|
+
hiddenFor: () => null,
|
|
464
|
+
planFor: () => null,
|
|
465
|
+
unionRatio: 0,
|
|
466
|
+
firstMarkerKey: null
|
|
467
|
+
});
|
|
468
|
+
var PLUGIN_PSEUDO_KINDS = /* @__PURE__ */ new Set(["user-actions", "retrace-reference", "recall-marker"]);
|
|
469
|
+
function realRowCount(nodes) {
|
|
470
|
+
let count = 0;
|
|
471
|
+
for (const node of nodes.values()) {
|
|
472
|
+
if (typeof node.anchorSeq === "number" && !PLUGIN_PSEUDO_KINDS.has(node.kind)) count += 1;
|
|
473
|
+
}
|
|
474
|
+
return count;
|
|
475
|
+
}
|
|
476
|
+
var hidePlanCacheSnapshot = null;
|
|
477
|
+
var hidePlanCacheValue = null;
|
|
478
|
+
function useMarkerHidePlan(useSession) {
|
|
479
|
+
return useSession((snapshot) => {
|
|
480
|
+
if (hidePlanCacheSnapshot === snapshot) return hidePlanCacheValue;
|
|
481
|
+
const nodes = snapshot.chat.nodes;
|
|
482
|
+
const rowCount = realRowCount(nodes);
|
|
483
|
+
const markers = [];
|
|
484
|
+
for (const node of nodes.values()) {
|
|
485
|
+
if (node.kind === "recall-marker" && !node.data?.compact) markers.push(node);
|
|
486
|
+
}
|
|
487
|
+
if (markers.length === 0) {
|
|
488
|
+
hidePlanCacheSnapshot = snapshot;
|
|
489
|
+
hidePlanCacheValue = EMPTY_HIDE_PLAN;
|
|
490
|
+
return hidePlanCacheValue;
|
|
491
|
+
}
|
|
492
|
+
const plans = /* @__PURE__ */ new Map();
|
|
493
|
+
const union = /* @__PURE__ */ new Set();
|
|
494
|
+
for (const marker of markers) {
|
|
495
|
+
const keys = hiddenKeysFor(marker.data?.shadowedSeqs, nodes);
|
|
496
|
+
const degraded = keys !== null && rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO;
|
|
497
|
+
plans.set(marker.key, { keys: degraded ? null : keys, degraded });
|
|
498
|
+
if (keys !== null) for (const key of keys) union.add(key);
|
|
499
|
+
}
|
|
500
|
+
hidePlanCacheSnapshot = snapshot;
|
|
501
|
+
hidePlanCacheValue = {
|
|
502
|
+
planFor: (key) => plans.get(key) ?? null,
|
|
503
|
+
hiddenFor: (key) => plans.get(key)?.keys ?? null,
|
|
504
|
+
unionRatio: rowCount > 0 ? union.size / rowCount : 0,
|
|
505
|
+
firstMarkerKey: markers[0].key
|
|
506
|
+
};
|
|
507
|
+
return hidePlanCacheValue;
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
function rowHiddenByKey(snapshot, rowKey) {
|
|
511
|
+
if (rowKey === void 0 || rowKey === null) return false;
|
|
512
|
+
const nodes = snapshot.chat.nodes;
|
|
513
|
+
const rowCount = realRowCount(nodes);
|
|
514
|
+
for (const node of nodes.values()) {
|
|
515
|
+
if (node.kind !== "recall-marker" || node.data?.compact) continue;
|
|
516
|
+
const keys = hiddenKeysFor(node.data?.shadowedSeqs, nodes);
|
|
517
|
+
if (keys === null) continue;
|
|
518
|
+
if (rowCount > 0 && keys.length / rowCount > SHADOW_SAFETY_RATIO) continue;
|
|
519
|
+
if (keys.includes(rowKey)) return true;
|
|
520
|
+
}
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
function useSeqHidden(useSession, seq) {
|
|
403
524
|
return useSession((snapshot) => {
|
|
404
525
|
if (seq === void 0 || seq === null) return false;
|
|
405
526
|
for (const node of snapshot.chat.nodes.values()) {
|
|
406
|
-
if (node.kind
|
|
407
|
-
return
|
|
527
|
+
if (node.kind !== "recall-marker" && typeof node.anchorSeq === "number" && node.anchorSeq === seq) {
|
|
528
|
+
return rowHiddenByKey(snapshot, node.key);
|
|
408
529
|
}
|
|
409
530
|
}
|
|
410
531
|
return false;
|
|
411
532
|
});
|
|
412
533
|
}
|
|
413
|
-
|
|
414
|
-
function useHiddenKeys(useSession, shadowedSeqs) {
|
|
534
|
+
function useShadowed(useSession, seq) {
|
|
415
535
|
return useSession((snapshot) => {
|
|
416
|
-
if (
|
|
417
|
-
const hidden = new Set(shadowedSeqs);
|
|
418
|
-
const keys = [];
|
|
419
|
-
let rowCount = 0;
|
|
536
|
+
if (seq === void 0 || seq === null) return false;
|
|
420
537
|
for (const node of snapshot.chat.nodes.values()) {
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
if (node.kind === "turn-tail") {
|
|
424
|
-
const closingSeq = node.data?.closing?.finalNode?.seq;
|
|
425
|
-
if (typeof closingSeq === "number" && hidden.has(closingSeq)) keys.push(node.key);
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
if (node.kind === "tool-call") {
|
|
429
|
-
const resultSeq = node.data?.root?.seq;
|
|
430
|
-
if (typeof resultSeq === "number" && hidden.has(resultSeq)) keys.push(node.key);
|
|
431
|
-
continue;
|
|
538
|
+
if (node.kind === "recall-marker" && Array.isArray(node.data?.shadowedSeqs) && node.data.shadowedSeqs.includes(seq)) {
|
|
539
|
+
return true;
|
|
432
540
|
}
|
|
433
|
-
if (typeof node.anchorSeq === "number" && hidden.has(node.anchorSeq)) keys.push(node.key);
|
|
434
541
|
}
|
|
435
|
-
|
|
436
|
-
return keys.length === 0 ? null : keys;
|
|
542
|
+
return false;
|
|
437
543
|
});
|
|
438
544
|
}
|
|
439
545
|
function useMarkerDismissed(useSession, markerSeq, op) {
|
|
@@ -470,10 +576,11 @@ return {
|
|
|
470
576
|
}
|
|
471
577
|
function AssistantActions({ messageId, sessionId, useSession, t }) {
|
|
472
578
|
const seq = useMessageSeq(useSession, messageId);
|
|
579
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
473
580
|
const shadowed = useShadowed(useSession, seq);
|
|
474
581
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
475
582
|
const [failure, setFailure] = (0, import_react.useState)(null);
|
|
476
|
-
if (shadowed || seq === void 0) return null;
|
|
583
|
+
if (hidden || shadowed || seq === void 0) return null;
|
|
477
584
|
const run = (op) => {
|
|
478
585
|
setBusy(true);
|
|
479
586
|
setFailure(null);
|
|
@@ -516,11 +623,11 @@ return {
|
|
|
516
623
|
}
|
|
517
624
|
function ReferenceRow({ node, useSession, t }) {
|
|
518
625
|
const { seq, messageId } = node.data;
|
|
519
|
-
const
|
|
626
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
520
627
|
const markerRef = useEditReference(useSession, seq);
|
|
521
628
|
const referenceText = editReferences.get(messageId) ?? markerRef;
|
|
522
629
|
const config = useConfig();
|
|
523
|
-
if (
|
|
630
|
+
if (hidden) return null;
|
|
524
631
|
if (referenceText === null || !config.showOriginalInput) return null;
|
|
525
632
|
return (0, import_react.createElement)("div", { className: "dsh-rt-user-row" }, [
|
|
526
633
|
(0, import_react.createElement)("details", { className: "dsh-rt-reference" }, [
|
|
@@ -535,12 +642,13 @@ return {
|
|
|
535
642
|
}
|
|
536
643
|
function UserActionsRow({ node, sessionId, useSession, inputActions, t }) {
|
|
537
644
|
const { seq, messageId, content } = node.data;
|
|
645
|
+
const hidden = useSeqHidden(useSession, seq);
|
|
538
646
|
const shadowed = useShadowed(useSession, seq);
|
|
539
647
|
const [editing, setEditing] = (0, import_react.useState)(false);
|
|
540
648
|
const [draft, setDraft] = (0, import_react.useState)("");
|
|
541
649
|
const [busy, setBusy] = (0, import_react.useState)(false);
|
|
542
650
|
const [failure, setFailure] = (0, import_react.useState)(null);
|
|
543
|
-
if (shadowed) return null;
|
|
651
|
+
if (hidden || shadowed) return null;
|
|
544
652
|
const openEditor = () => {
|
|
545
653
|
setDraft(textOf(content));
|
|
546
654
|
setFailure(null);
|
|
@@ -633,15 +741,26 @@ return {
|
|
|
633
741
|
]);
|
|
634
742
|
}
|
|
635
743
|
function RecallMarkerRow({ node, useSession, t }) {
|
|
636
|
-
const { seq, op, shadowedSeqs, legacy } = node.data;
|
|
744
|
+
const { seq, op, shadowedSeqs, legacy, compact } = node.data;
|
|
745
|
+
if (compact) return null;
|
|
637
746
|
const dismissed = useMarkerDismissed(useSession, seq, op);
|
|
638
|
-
const
|
|
747
|
+
const hidePlan = useMarkerHidePlan(useSession);
|
|
748
|
+
const plan = hidePlan.planFor(node.key);
|
|
749
|
+
const hiddenKeys = legacy || !getConfig().hideShadowed ? null : plan?.keys ?? null;
|
|
639
750
|
const css = hiddenKeys === null ? null : hiddenKeys.map((key) => `[data-chat-anchor-key=${JSON.stringify(key)}]{display:none!important}`).join("");
|
|
640
751
|
const count = Array.isArray(shadowedSeqs) ? shadowedSeqs.length : 0;
|
|
641
752
|
const label = op === "recall" ? count > 1 ? t("marker.recallMany", { count }) : t("marker.recallOne") : op === "regenerate" ? t("marker.regenerate") : t("marker.edit");
|
|
753
|
+
const degradedHint = !legacy && plan?.degraded === true ? (0, import_react.createElement)("div", { key: "degraded", className: "dsh-rt-marker-hint" }, t("marker.degradedHint")) : null;
|
|
754
|
+
const unionHint = !legacy && hidePlan.firstMarkerKey === node.key && hidePlan.unionRatio > SHADOW_SAFETY_RATIO ? (0, import_react.createElement)(
|
|
755
|
+
"div",
|
|
756
|
+
{ key: "union", className: "dsh-rt-marker-hint" },
|
|
757
|
+
t("marker.unionHint", { count: Math.round(hidePlan.unionRatio * 100) })
|
|
758
|
+
) : null;
|
|
642
759
|
return (0, import_react.createElement)("div", { className: "dsh-rt-marker-block", "data-dismissed": dismissed || void 0 }, [
|
|
643
760
|
css !== null && (0, import_react.createElement)("style", { key: "hide", dangerouslySetInnerHTML: { __html: css } }),
|
|
644
|
-
!dismissed && (0, import_react.createElement)("div", { key: "label", className: "dsh-rt-marker", role: "status" }, label)
|
|
761
|
+
!dismissed && (0, import_react.createElement)("div", { key: "label", className: "dsh-rt-marker", role: "status" }, label),
|
|
762
|
+
!dismissed && degradedHint,
|
|
763
|
+
!dismissed && unionHint
|
|
645
764
|
]);
|
|
646
765
|
}
|
|
647
766
|
function OptionsRow({ t }) {
|
|
@@ -718,6 +837,7 @@ return {
|
|
|
718
837
|
.dsh-rt-error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px;max-width:min(525px,82%)}
|
|
719
838
|
.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}
|
|
720
839
|
.dsh-rt-marker{text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
|
|
840
|
+
.dsh-rt-marker-hint{text-align:center;color:var(--dsw-alias-state-warning-primary);font-size:11px;line-height:16px;margin-top:2px}
|
|
721
841
|
.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}
|
|
722
842
|
.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}
|
|
723
843
|
.dsh-rt-reference summary::-webkit-details-marker{display:none}
|
|
@@ -736,7 +856,8 @@ return {
|
|
|
736
856
|
.dsh-rt-retention-input:focus{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary)}
|
|
737
857
|
.dsh-rt-option input{accent-color:var(--dsw-alias-state-business-primary)}
|
|
738
858
|
/* ---- P1 timeline (conversation view tab) ---- */
|
|
739
|
-
|
|
859
|
+
|
|
860
|
+
.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}
|
|
740
861
|
.dsh-rt-timeline-head{display:flex;align-items:center;gap:8px;flex:none}
|
|
741
862
|
.dsh-rt-timeline-title{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px;flex:1}
|
|
742
863
|
.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}
|
|
@@ -773,7 +894,126 @@ return {
|
|
|
773
894
|
.dsh-rt-modal-buttons{display:flex;justify-content:flex-end;gap:8px;flex:none}
|
|
774
895
|
.dsh-rt-confirm{background:var(--dsw-alias-state-error-primary)}
|
|
775
896
|
.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}
|
|
897
|
+
.dsh-rt-fork-list{overflow-y:auto;flex:1;min-height:0;position:relative}
|
|
898
|
+
.dsh-rt-fork-spine-label{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px;flex:none}
|
|
899
|
+
.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}
|
|
900
|
+
.dsh-rt-fork-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
901
|
+
.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)}
|
|
902
|
+
.dsh-rt-fork-boundary .dsh-rt-fork-icon{color:var(--dsw-alias-state-warning-primary)}
|
|
903
|
+
.dsh-rt-fork-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
|
|
904
|
+
.dsh-rt-fork-line{display:flex;align-items:baseline;gap:8px;min-width:0}
|
|
905
|
+
.dsh-rt-fork-label{font-size:12px;font-weight:500;line-height:18px;color:var(--dsw-alias-label-primary);white-space:nowrap}
|
|
906
|
+
.dsh-rt-fork-seq{font-family:var(--dsw-font-mono);font-size:11px;line-height:18px;color:var(--dsw-alias-label-caption);flex:none}
|
|
907
|
+
.dsh-rt-fork-shadowed{font-size:11px;line-height:18px;color:var(--dsw-alias-state-warning-primary);flex:none}
|
|
908
|
+
.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}
|
|
909
|
+
.dsh-rt-fork-boundary{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-elevated)}
|
|
910
|
+
.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}
|
|
911
|
+
.dsh-rt-fork-hist-row{display:flex;align-items:flex-start;gap:8px;padding:4px 8px;border-radius:8px}
|
|
912
|
+
.dsh-rt-fork-hist-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
776
913
|
`;
|
|
914
|
+
var JUMP_PAGE_BUDGET = 24;
|
|
915
|
+
function switchToViewTab(viewId) {
|
|
916
|
+
const ORDER = { chat: 0, trajectory: 1, retrace: 2, "retrace-fork": 3 };
|
|
917
|
+
const index = ORDER[viewId];
|
|
918
|
+
if (index === void 0) {
|
|
919
|
+
console.warn(`[dsh-retrace] no tab order registered for "${viewId}"`);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const buttons = [...document.querySelectorAll('[role="tablist"]')].filter((tablist) => tablist.getBoundingClientRect().width > 0).flatMap((tablist) => [...tablist.querySelectorAll('[role="tab"]')]);
|
|
923
|
+
const button = buttons[index];
|
|
924
|
+
if (!button) {
|
|
925
|
+
console.warn(`[dsh-retrace] tab "${viewId}" (index ${index}) not found in the conversation tab bar`);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
button.click();
|
|
929
|
+
}
|
|
930
|
+
function nodeCountOf(nodes) {
|
|
931
|
+
if (!nodes) return 0;
|
|
932
|
+
if (typeof nodes.size === "number") return nodes.size;
|
|
933
|
+
return [...nodes.values()].length;
|
|
934
|
+
}
|
|
935
|
+
function waitForElement(selector, frames) {
|
|
936
|
+
return new Promise((resolve) => {
|
|
937
|
+
let remaining = frames;
|
|
938
|
+
const probe = () => {
|
|
939
|
+
const el = document.querySelector(selector);
|
|
940
|
+
if (el !== null) return resolve(el);
|
|
941
|
+
if (remaining-- <= 0) return resolve(null);
|
|
942
|
+
requestAnimationFrame(probe);
|
|
943
|
+
};
|
|
944
|
+
requestAnimationFrame(probe);
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
function flashKey(key) {
|
|
948
|
+
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
949
|
+
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
950
|
+
const tag = document.createElement("style");
|
|
951
|
+
tag.dataset.pluginCss = id;
|
|
952
|
+
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)}}`;
|
|
953
|
+
document.head.appendChild(tag);
|
|
954
|
+
setTimeout(() => tag.remove(), 3400);
|
|
955
|
+
}
|
|
956
|
+
async function jumpToAnchor(store, anchorSeq) {
|
|
957
|
+
switchToViewTab("chat");
|
|
958
|
+
if (!store || typeof store.loadOlder !== "function" || typeof store.getSnapshot !== "function") return;
|
|
959
|
+
const keyOfSeq = (seq) => {
|
|
960
|
+
const nodes = store.getSnapshot()?.chat?.nodes;
|
|
961
|
+
if (!nodes) return null;
|
|
962
|
+
for (const node of nodes.values()) {
|
|
963
|
+
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
964
|
+
}
|
|
965
|
+
return null;
|
|
966
|
+
};
|
|
967
|
+
let key = keyOfSeq(anchorSeq);
|
|
968
|
+
let pages = 0;
|
|
969
|
+
while (key === null && pages < JUMP_PAGE_BUDGET && store.hasMore) {
|
|
970
|
+
const before = nodeCountOf(store.getSnapshot()?.chat?.nodes);
|
|
971
|
+
await store.loadOlder();
|
|
972
|
+
const after = nodeCountOf(store.getSnapshot()?.chat?.nodes);
|
|
973
|
+
pages += 1;
|
|
974
|
+
if (after === before) break;
|
|
975
|
+
key = keyOfSeq(anchorSeq);
|
|
976
|
+
}
|
|
977
|
+
if (key === null) {
|
|
978
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
979
|
+
key = keyOfSeq(anchorSeq);
|
|
980
|
+
}
|
|
981
|
+
if (key === null) {
|
|
982
|
+
console.warn(`[dsh-retrace] jump target seq ${anchorSeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
986
|
+
if (el === null) return;
|
|
987
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
988
|
+
flashKey(key);
|
|
989
|
+
}
|
|
990
|
+
var NODE_ICONS = { "user/message": "\u{1F464}", "assistant/message": "\u{1F916}", "tool/result": "\u{1F527}" };
|
|
991
|
+
function nodeTypeLabel(type, t) {
|
|
992
|
+
if (type === "user/message") return t("fork.node.user");
|
|
993
|
+
if (type === "assistant/message") return t("fork.node.assistant");
|
|
994
|
+
if (type === "tool/result") return t("fork.node.tool");
|
|
995
|
+
return type;
|
|
996
|
+
}
|
|
997
|
+
function bindListHeight(listEl) {
|
|
998
|
+
if (!listEl) return () => {
|
|
999
|
+
};
|
|
1000
|
+
const measure = () => {
|
|
1001
|
+
const top = listEl.getBoundingClientRect().top;
|
|
1002
|
+
const height = Math.max(120, window.innerHeight - top - 16);
|
|
1003
|
+
if (Math.abs(listEl.clientHeight - height) > 4) {
|
|
1004
|
+
listEl.style.flex = "none";
|
|
1005
|
+
listEl.style.height = `${height}px`;
|
|
1006
|
+
}
|
|
1007
|
+
};
|
|
1008
|
+
measure();
|
|
1009
|
+
const ro = typeof ResizeObserver === "function" ? new ResizeObserver(measure) : null;
|
|
1010
|
+
ro?.observe(document.body);
|
|
1011
|
+
window.addEventListener("resize", measure);
|
|
1012
|
+
return () => {
|
|
1013
|
+
ro?.disconnect();
|
|
1014
|
+
window.removeEventListener("resize", measure);
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
777
1017
|
function ensureStyle() {
|
|
778
1018
|
if (typeof document === "undefined") return () => {
|
|
779
1019
|
};
|
|
@@ -841,63 +1081,7 @@ return {
|
|
|
841
1081
|
if (projected === void 0) refresh();
|
|
842
1082
|
refreshGit();
|
|
843
1083
|
}, []);
|
|
844
|
-
const
|
|
845
|
-
const jump = async (boundarySeq) => {
|
|
846
|
-
actions?.setView?.("chat");
|
|
847
|
-
const target = store;
|
|
848
|
-
if (!target || typeof target.loadOlder !== "function" || typeof target.getSnapshot !== "function") return;
|
|
849
|
-
const keyOfSeq = (seq) => {
|
|
850
|
-
const nodes = target.getSnapshot()?.chat?.nodes;
|
|
851
|
-
if (!nodes) return null;
|
|
852
|
-
for (const node of nodes.values()) {
|
|
853
|
-
if (typeof node.anchorSeq === "number" && node.anchorSeq === seq) return node.key;
|
|
854
|
-
}
|
|
855
|
-
return null;
|
|
856
|
-
};
|
|
857
|
-
let key = keyOfSeq(boundarySeq);
|
|
858
|
-
let pages = 0;
|
|
859
|
-
while (key === null && pages < JUMP_PAGE_BUDGET && target.hasMore) {
|
|
860
|
-
const before = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
861
|
-
await target.loadOlder();
|
|
862
|
-
const after = target.getSnapshot()?.chat?.nodes?.size ?? 0;
|
|
863
|
-
pages += 1;
|
|
864
|
-
if (after === before) break;
|
|
865
|
-
key = keyOfSeq(boundarySeq);
|
|
866
|
-
}
|
|
867
|
-
if (key === null) {
|
|
868
|
-
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
869
|
-
key = keyOfSeq(boundarySeq);
|
|
870
|
-
}
|
|
871
|
-
if (key === null) {
|
|
872
|
-
console.warn(`[dsh-retrace] jump target seq ${boundarySeq} not reached within ${JUMP_PAGE_BUDGET} pages`);
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
const el = await waitForElement(`[data-chat-anchor-key=${JSON.stringify(key)}]`, 90);
|
|
876
|
-
if (el === null) return;
|
|
877
|
-
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
878
|
-
flashKey(key);
|
|
879
|
-
};
|
|
880
|
-
function waitForElement(selector, frames) {
|
|
881
|
-
return new Promise((resolve) => {
|
|
882
|
-
let remaining = frames;
|
|
883
|
-
const probe = () => {
|
|
884
|
-
const el = document.querySelector(selector);
|
|
885
|
-
if (el !== null) return resolve(el);
|
|
886
|
-
if (remaining-- <= 0) return resolve(null);
|
|
887
|
-
requestAnimationFrame(probe);
|
|
888
|
-
};
|
|
889
|
-
requestAnimationFrame(probe);
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
function flashKey(key) {
|
|
893
|
-
const id = `dsh-rt-jump-${key.replace(/[^a-z0-9]/gi, "-")}`;
|
|
894
|
-
if (document.querySelector(`style[data-plugin-css="${id}"]`)) return;
|
|
895
|
-
const tag = document.createElement("style");
|
|
896
|
-
tag.dataset.pluginCss = id;
|
|
897
|
-
tag.textContent = `[data-chat-anchor-key=${JSON.stringify(key)}]{animation:dsh-rt-flash 1.6s ease-out 2}@keyframes dsh-rt-flash{0%,100%{background:transparent}30%,70%{background:var(--dsw-alias-state-business-primary)}55%{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,transparent)}}`;
|
|
898
|
-
document.head.appendChild(tag);
|
|
899
|
-
setTimeout(() => tag.remove(), 3400);
|
|
900
|
-
}
|
|
1084
|
+
const jump = (boundarySeq) => jumpToAnchor(store, boundarySeq);
|
|
901
1085
|
const requestPreview = (record) => {
|
|
902
1086
|
setPreviewScope("both");
|
|
903
1087
|
setPreview({ versionId: record.versionId, kind: record.kind, boundarySeq: record.boundarySeq, data: null, error: null });
|
|
@@ -930,6 +1114,10 @@ return {
|
|
|
930
1114
|
const ROW_H = 64;
|
|
931
1115
|
const list = versions ?? [];
|
|
932
1116
|
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));
|
|
1117
|
+
(0, import_react.useEffect)(() => {
|
|
1118
|
+
if (list.length === 0) return void 0;
|
|
1119
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-timeline-list"));
|
|
1120
|
+
}, [list.length]);
|
|
933
1121
|
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
934
1122
|
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
935
1123
|
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("timeline.title")),
|
|
@@ -967,7 +1155,7 @@ return {
|
|
|
967
1155
|
t,
|
|
968
1156
|
top: list.indexOf(record) * ROW_H,
|
|
969
1157
|
onPreview: () => requestPreview(record),
|
|
970
|
-
onTrajectory: () =>
|
|
1158
|
+
onTrajectory: () => switchToViewTab("trajectory"),
|
|
971
1159
|
onJump: () => jump(record.boundarySeq)
|
|
972
1160
|
}))
|
|
973
1161
|
])
|
|
@@ -1050,6 +1238,135 @@ return {
|
|
|
1050
1238
|
])
|
|
1051
1239
|
]);
|
|
1052
1240
|
}
|
|
1241
|
+
function ForkView({ sessionId, useProjection, t, actions, store }) {
|
|
1242
|
+
const [fork, setFork] = (0, import_react.useState)(null);
|
|
1243
|
+
const [loading, setLoading] = (0, import_react.useState)(false);
|
|
1244
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
1245
|
+
const [scrollTop, setScrollTop] = (0, import_react.useState)(0);
|
|
1246
|
+
const projected = typeof useProjection === "function" ? useProjection("retrace/forkmap") : void 0;
|
|
1247
|
+
const versions = typeof useProjection === "function" ? useProjection("retrace/versions") : void 0;
|
|
1248
|
+
const markerBySeq = new Map((versions?.versions ?? []).map((v) => [v.boundarySeq, v.markerText]));
|
|
1249
|
+
(0, import_react.useEffect)(() => {
|
|
1250
|
+
if (projected && Array.isArray(projected.nodes) && Array.isArray(projected.boundaries)) setFork(projected);
|
|
1251
|
+
}, [projected]);
|
|
1252
|
+
const refresh = () => {
|
|
1253
|
+
setLoading(true);
|
|
1254
|
+
setError(null);
|
|
1255
|
+
timelineGet(`/forkmap?sessionId=${encodeURIComponent(sessionId)}`).then((result) => {
|
|
1256
|
+
if (!result || result.ok !== true) throw new Error(result?.error?.message ?? "forkmap failed");
|
|
1257
|
+
setFork(result.value?.nodes ? result.value : null);
|
|
1258
|
+
}).catch((cause) => setError(cause?.message ?? "forkmap error")).finally(() => setLoading(false));
|
|
1259
|
+
};
|
|
1260
|
+
(0, import_react.useEffect)(() => {
|
|
1261
|
+
if (projected === void 0) refresh();
|
|
1262
|
+
}, []);
|
|
1263
|
+
const nodes = fork?.nodes ?? [];
|
|
1264
|
+
const boundaries = fork?.boundaries ?? [];
|
|
1265
|
+
const boundaryBySeq = new Map(boundaries.map((b) => [b.seq, b]));
|
|
1266
|
+
const spineSeqSet = new Set(nodes.map((n) => n.seq));
|
|
1267
|
+
const offSpine = boundaries.filter((b) => !spineSeqSet.has(b.seq));
|
|
1268
|
+
(0, import_react.useEffect)(() => {
|
|
1269
|
+
if (nodes.length === 0) return void 0;
|
|
1270
|
+
return bindListHeight(document.querySelector(".dsh-rt-view .dsh-rt-fork-list"));
|
|
1271
|
+
}, [nodes.length]);
|
|
1272
|
+
const ROW_H = 56;
|
|
1273
|
+
const visible = nodes.slice(
|
|
1274
|
+
Math.max(0, Math.floor(scrollTop / ROW_H) - 2),
|
|
1275
|
+
Math.min(nodes.length, Math.ceil(scrollTop / ROW_H) + Math.ceil(640 / ROW_H) + 2)
|
|
1276
|
+
);
|
|
1277
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-view" }, [
|
|
1278
|
+
(0, import_react.createElement)("div", { key: "head", className: "dsh-rt-timeline-head" }, [
|
|
1279
|
+
(0, import_react.createElement)("span", { key: "title", className: "dsh-rt-timeline-title" }, t("fork.title")),
|
|
1280
|
+
(0, import_react.createElement)("button", {
|
|
1281
|
+
key: "refresh",
|
|
1282
|
+
type: "button",
|
|
1283
|
+
className: "dsh-rt-chip",
|
|
1284
|
+
onClick: refresh
|
|
1285
|
+
}, t("fork.refresh"))
|
|
1286
|
+
]),
|
|
1287
|
+
error !== null && (0, import_react.createElement)("div", { key: "error", className: "dsh-rt-error" }, error),
|
|
1288
|
+
loading && (0, import_react.createElement)("div", { key: "loading", className: "dsh-rt-timeline-empty" }, t("timeline.loading")),
|
|
1289
|
+
!loading && nodes.length === 0 && (0, import_react.createElement)("div", { key: "empty", className: "dsh-rt-timeline-empty" }, t("fork.empty")),
|
|
1290
|
+
nodes.length > 0 && (0, import_react.createElement)("div", { key: "spine", className: "dsh-rt-fork-spine-label" }, t("fork.spine")),
|
|
1291
|
+
nodes.length > 0 && (0, import_react.createElement)("div", {
|
|
1292
|
+
key: "list",
|
|
1293
|
+
className: "dsh-rt-fork-list",
|
|
1294
|
+
onScroll: (event) => setScrollTop(event.target.scrollTop)
|
|
1295
|
+
}, [
|
|
1296
|
+
(0, import_react.createElement)("div", { key: "spacer", style: { height: `${nodes.length * ROW_H}px`, position: "relative" } }, [
|
|
1297
|
+
visible.map((node) => (0, import_react.createElement)(ForkRow, {
|
|
1298
|
+
key: node.seq,
|
|
1299
|
+
node,
|
|
1300
|
+
boundary: boundaryBySeq.get(node.seq),
|
|
1301
|
+
markerText: markerBySeq.get(node.seq),
|
|
1302
|
+
t,
|
|
1303
|
+
top: nodes.indexOf(node) * ROW_H,
|
|
1304
|
+
onJump: () => jumpToAnchor(store, node.seq)
|
|
1305
|
+
}))
|
|
1306
|
+
])
|
|
1307
|
+
]),
|
|
1308
|
+
offSpine.length > 0 && (0, import_react.createElement)("div", { key: "hist", className: "dsh-rt-fork-hist" }, [
|
|
1309
|
+
(0, import_react.createElement)("div", { key: "title", className: "dsh-rt-fork-spine-label" }, t("fork.histTitle")),
|
|
1310
|
+
offSpine.map((boundary) => (0, import_react.createElement)(ForkHistRow, {
|
|
1311
|
+
key: boundary.seq,
|
|
1312
|
+
boundary,
|
|
1313
|
+
markerText: markerBySeq.get(boundary.seq),
|
|
1314
|
+
t,
|
|
1315
|
+
onJump: () => jumpToAnchor(store, boundary.seq)
|
|
1316
|
+
}))
|
|
1317
|
+
])
|
|
1318
|
+
]);
|
|
1319
|
+
}
|
|
1320
|
+
function ForkRow({ node, boundary, markerText, t, top, onJump }) {
|
|
1321
|
+
const isBoundary = boundary !== void 0;
|
|
1322
|
+
const icon = isBoundary ? KIND_ICONS[boundary.kind] ?? "\u2022" : NODE_ICONS[node.type] ?? "\u2022";
|
|
1323
|
+
const label = isBoundary ? kindLabel(boundary.kind, t) : nodeTypeLabel(node.type, t);
|
|
1324
|
+
return (0, import_react.createElement)("div", {
|
|
1325
|
+
className: `dsh-rt-fork-row${isBoundary ? " dsh-rt-fork-boundary" : ""}`,
|
|
1326
|
+
style: { top: `${top}px` }
|
|
1327
|
+
}, [
|
|
1328
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, icon),
|
|
1329
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1330
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1331
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, label),
|
|
1332
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${node.seq}`),
|
|
1333
|
+
isBoundary && boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1334
|
+
"span",
|
|
1335
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1336
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1337
|
+
)
|
|
1338
|
+
]),
|
|
1339
|
+
isBoundary && markerText && (0, import_react.createElement)(
|
|
1340
|
+
"div",
|
|
1341
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1342
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1343
|
+
)
|
|
1344
|
+
]),
|
|
1345
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1346
|
+
]);
|
|
1347
|
+
}
|
|
1348
|
+
function ForkHistRow({ boundary, markerText, t, onJump }) {
|
|
1349
|
+
return (0, import_react.createElement)("div", { className: "dsh-rt-fork-hist-row" }, [
|
|
1350
|
+
(0, import_react.createElement)("span", { key: "icon", className: "dsh-rt-fork-icon" }, KIND_ICONS[boundary.kind] ?? "\u2022"),
|
|
1351
|
+
(0, import_react.createElement)("div", { key: "body", className: "dsh-rt-fork-body" }, [
|
|
1352
|
+
(0, import_react.createElement)("div", { key: "line", className: "dsh-rt-fork-line" }, [
|
|
1353
|
+
(0, import_react.createElement)("span", { key: "label", className: "dsh-rt-fork-label" }, kindLabel(boundary.kind, t)),
|
|
1354
|
+
(0, import_react.createElement)("span", { key: "seq", className: "dsh-rt-fork-seq" }, `#${boundary.seq}`),
|
|
1355
|
+
boundary.replacedSeqs.length > 0 && (0, import_react.createElement)(
|
|
1356
|
+
"span",
|
|
1357
|
+
{ key: "shadowed", className: "dsh-rt-fork-shadowed" },
|
|
1358
|
+
t("fork.shadowed", { count: boundary.replacedSeqs.length })
|
|
1359
|
+
)
|
|
1360
|
+
]),
|
|
1361
|
+
markerText && (0, import_react.createElement)(
|
|
1362
|
+
"div",
|
|
1363
|
+
{ key: "text", className: "dsh-rt-fork-text" },
|
|
1364
|
+
markerText.length > 140 ? `${markerText.slice(0, 140)}\u2026` : markerText
|
|
1365
|
+
)
|
|
1366
|
+
]),
|
|
1367
|
+
(0, import_react.createElement)("button", { key: "jump", type: "button", className: "dsh-rt-chip", onClick: onJump }, t("timeline.jump"))
|
|
1368
|
+
]);
|
|
1369
|
+
}
|
|
1053
1370
|
function apply(ctx) {
|
|
1054
1371
|
const disposeStyle = ensureStyle();
|
|
1055
1372
|
ctx.effect(() => () => disposeStyle(), "dsh-retrace: styles");
|
|
@@ -1057,9 +1374,12 @@ return {
|
|
|
1057
1374
|
const t = ctx.locale.bind(NS);
|
|
1058
1375
|
const conversationEvents = ctx.get("conversationEvents");
|
|
1059
1376
|
if (conversationEvents) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1377
|
+
const disposeDefinitions = [
|
|
1378
|
+
conversationEvents.register(userActionsDefinition),
|
|
1379
|
+
conversationEvents.register(userReferenceDefinition),
|
|
1380
|
+
conversationEvents.register(recallMarkerDefinition)
|
|
1381
|
+
];
|
|
1382
|
+
ctx.effect(() => () => disposeDefinitions.forEach((dispose) => dispose()), "dsh-retrace: conversation definitions");
|
|
1063
1383
|
}
|
|
1064
1384
|
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
|
|
1065
1385
|
name: "conversation.chat.assistant-actions",
|
|
@@ -1093,6 +1413,17 @@ return {
|
|
|
1093
1413
|
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1094
1414
|
})
|
|
1095
1415
|
}, RetraceView));
|
|
1416
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
1417
|
+
name: "conversation.view",
|
|
1418
|
+
id: "retrace-fork",
|
|
1419
|
+
order: 30,
|
|
1420
|
+
locale: NS,
|
|
1421
|
+
label: () => t("view.fork"),
|
|
1422
|
+
inject: (sessionId, actions) => ({
|
|
1423
|
+
actions,
|
|
1424
|
+
store: ctx.get?.("sessions")?.binding?.(sessionId)?.session
|
|
1425
|
+
})
|
|
1426
|
+
}, ForkView));
|
|
1096
1427
|
ctx.slots.inject("settings.general.item", () => ctx.slots.register({
|
|
1097
1428
|
name: "settings.general.item",
|
|
1098
1429
|
id: "retrace",
|