dsh-rewind-plugin 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -175
- package/README.zh.md +77 -162
- package/docs/harness-reference.md +23 -0
- package/docs/release.md +90 -0
- package/docs/troubleshooting.md +39 -0
- package/docs/troubleshooting.zh.md +31 -0
- package/lib/client.js +249 -172
- package/lib/index.js +78 -49
- package/lib/types/client/hidden.d.ts +20 -1
- package/lib/types/client/index.d.ts +14 -18
- package/lib/types/client/popover.d.ts +24 -0
- package/lib/types/client/portals.d.ts +66 -0
- package/lib/types/client/styles.d.ts +0 -2
- package/package.json +7 -2
package/lib/client.js
CHANGED
|
@@ -32,12 +32,28 @@ __export(index_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(index_exports);
|
|
34
34
|
|
|
35
|
+
// src/client/portals.tsx
|
|
36
|
+
var import_react = require("react");
|
|
37
|
+
var import_react_dom = require("react-dom");
|
|
38
|
+
|
|
35
39
|
// src/client/hidden.ts
|
|
36
40
|
function targetOfOutcome(text) {
|
|
37
41
|
if (text === void 0) return void 0;
|
|
38
42
|
const match = text.match(/seq (\d+)/);
|
|
39
43
|
return match !== null ? Number(match[1]) : void 0;
|
|
40
44
|
}
|
|
45
|
+
function isExecutedRewindCommand(node, seq) {
|
|
46
|
+
if (node.name !== "rewind" || node.outcome?.kind !== "success") return false;
|
|
47
|
+
if (node.outcome.sourceEventSeq === void 0) return false;
|
|
48
|
+
const args = node.args ?? "";
|
|
49
|
+
return new RegExp(`(?:^|\\s)@${seq}(?:\\s|$)`).test(args);
|
|
50
|
+
}
|
|
51
|
+
function hasFileImpact(text) {
|
|
52
|
+
if (text === void 0) return true;
|
|
53
|
+
const match = text.match(/impact=(\d+)/);
|
|
54
|
+
if (match !== null) return Number(match[1]) > 0;
|
|
55
|
+
return text.includes("\u5C06\u5F71\u54CD");
|
|
56
|
+
}
|
|
41
57
|
function isPreviewCommand(command) {
|
|
42
58
|
return (command.args ?? "").includes("preview");
|
|
43
59
|
}
|
|
@@ -73,48 +89,7 @@ function hiddenSeqsOf(snap) {
|
|
|
73
89
|
return hidden;
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
// src/client/locales.ts
|
|
77
|
-
var zh = {
|
|
78
|
-
"button.aria": "\u56DE\u9000\u5230\u6B64\u6D88\u606F",
|
|
79
|
-
"button.title": "\u56DE\u9000",
|
|
80
|
-
"popover.title": "\u56DE\u9000\u5230\u8FD9\u6761\u6D88\u606F",
|
|
81
|
-
"popover.noText": "\uFF08\u65E0\u6587\u672C\uFF09",
|
|
82
|
-
"popover.chat": "\u4EC5\u56DE\u9000\u5BF9\u8BDD",
|
|
83
|
-
"popover.chat.hint": "\u53EA\u56DE\u9000\u6A21\u578B\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
84
|
-
"popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\u4EE3\u7801",
|
|
85
|
-
"popover.both.hint": "\u5BF9\u8BDD\u56DE\u9000\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
86
|
-
"popover.checking": "\u6B63\u5728\u68C0\u67E5\u6587\u4EF6\u53D8\u66F4\u2026",
|
|
87
|
-
"popover.noChanges": "\u6B64\u6D88\u606F\u4E4B\u540E\u6CA1\u6709\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4\uFF0C\u4EC5\u53EF\u56DE\u9000\u5BF9\u8BDD",
|
|
88
|
-
"popover.cancel": "\u53D6\u6D88",
|
|
89
|
-
"popover.impact.loading": "\u6B63\u5728\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\u2026",
|
|
90
|
-
"popover.impact.failed": "\u65E0\u6CD5\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\uFF1A{message}",
|
|
91
|
-
"popover.impact.none": "\u76EE\u6807\u4E4B\u540E\u6CA1\u6709\u8DDF\u8E2A\u5230\u7684\u5199\u7C7B\u53D8\u66F4\uFF0C\u65E0\u9700\u8FD8\u539F\u6587\u4EF6\u3002",
|
|
92
|
-
"popover.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
93
|
-
"popover.back": "\u8FD4\u56DE",
|
|
94
|
-
"guard.hint": "/rewind \u547D\u4EE4\u4E0D\u652F\u6301\u624B\u52A8\u8F93\u5165\uFF0C\u8BF7\u4F7F\u7528\u6D88\u606F\u65C1\u7684 \u21B6 \u56DE\u9000\u6309\u94AE"
|
|
95
|
-
};
|
|
96
|
-
var en = {
|
|
97
|
-
"button.aria": "Rewind to this message",
|
|
98
|
-
"button.title": "Rewind",
|
|
99
|
-
"popover.title": "Rewind to this message",
|
|
100
|
-
"popover.noText": "(no text)",
|
|
101
|
-
"popover.chat": "Rewind conversation only",
|
|
102
|
-
"popover.chat.hint": "Cut the model context only; workspace files stay untouched",
|
|
103
|
-
"popover.both": "Rewind conversation and code",
|
|
104
|
-
"popover.both.hint": "Cut the context and restore workspace files",
|
|
105
|
-
"popover.checking": "Checking for file changes\u2026",
|
|
106
|
-
"popover.noChanges": "No tracked file changes after this message; conversation-only rewind",
|
|
107
|
-
"popover.cancel": "Cancel",
|
|
108
|
-
"popover.impact.loading": "Fetching impact list\u2026",
|
|
109
|
-
"popover.impact.failed": "Could not fetch the impact list: {message}",
|
|
110
|
-
"popover.impact.none": "No tracked file changes after the target; nothing to restore.",
|
|
111
|
-
"popover.confirm": "Confirm rewind",
|
|
112
|
-
"popover.back": "Back",
|
|
113
|
-
"guard.hint": "/rewind cannot be typed manually \u2014 use the \u21B6 button on a message to rewind."
|
|
114
|
-
};
|
|
115
|
-
|
|
116
92
|
// src/client/styles.ts
|
|
117
|
-
var REWIND_ATTACHED = "data-dsh-rewind-attached";
|
|
118
93
|
var CLASS = {
|
|
119
94
|
button: "dsh-rewind-btn",
|
|
120
95
|
popover: "dsh-rewind-popover",
|
|
@@ -291,6 +266,9 @@ function formatTarget(t, seq, time, preview) {
|
|
|
291
266
|
const previewText = preview.length > 0 ? preview : t("popover.noText");
|
|
292
267
|
return `seq ${seq} \xB7 ${hh}:${mm} \xB7 ${previewText}`;
|
|
293
268
|
}
|
|
269
|
+
function stripImpactToken(text) {
|
|
270
|
+
return text.replace(/\n?impact=\d+\s*$/, "");
|
|
271
|
+
}
|
|
294
272
|
function findCommand(snapshot, match) {
|
|
295
273
|
let found;
|
|
296
274
|
for (const key of snapshot.chat.order) {
|
|
@@ -302,6 +280,18 @@ function findCommand(snapshot, match) {
|
|
|
302
280
|
}
|
|
303
281
|
return found;
|
|
304
282
|
}
|
|
283
|
+
function knownCommandSeqs(session, match) {
|
|
284
|
+
const known = /* @__PURE__ */ new Set();
|
|
285
|
+
const snapshot = session.getSnapshot();
|
|
286
|
+
for (const key of snapshot.chat.order) {
|
|
287
|
+
const node = snapshot.chat.nodes.get(key);
|
|
288
|
+
if (node !== void 0 && node.kind === "command") {
|
|
289
|
+
const command = node.data;
|
|
290
|
+
if (match(command)) known.add(command.seq);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return known;
|
|
294
|
+
}
|
|
305
295
|
function waitForCommand(session, match, timeoutMs = 8e3) {
|
|
306
296
|
return new Promise((resolve) => {
|
|
307
297
|
let settled = false;
|
|
@@ -328,9 +318,10 @@ function isPreviewFor(node, seq) {
|
|
|
328
318
|
return node.name === "rewind" && args.includes("preview") && new RegExp(`(?:^|\\s)@${seq}(?=\\s|$)`).test(args);
|
|
329
319
|
}
|
|
330
320
|
async function previewImpact(session, seq) {
|
|
321
|
+
const known = knownCommandSeqs(session, (node) => isPreviewFor(node, seq));
|
|
331
322
|
const result = await session.command(`/rewind preview @${seq} both`);
|
|
332
323
|
if (!result.ok || result.value?.matched !== true) return null;
|
|
333
|
-
return waitForCommand(session, (node) => isPreviewFor(node, seq));
|
|
324
|
+
return waitForCommand(session, (node) => isPreviewFor(node, seq) && !known.has(node.seq));
|
|
334
325
|
}
|
|
335
326
|
function el(tag, className, text) {
|
|
336
327
|
const node = document.createElement(tag);
|
|
@@ -375,11 +366,11 @@ function renderImpactStep(root, opts, back, cached) {
|
|
|
375
366
|
impact.textContent = t("popover.impact.failed", { message: outcome.text ?? "unknown error" });
|
|
376
367
|
return;
|
|
377
368
|
}
|
|
378
|
-
impact.textContent = outcome.text
|
|
369
|
+
impact.textContent = outcome.text === void 0 ? t("popover.impact.none") : stripImpactToken(outcome.text);
|
|
379
370
|
confirm.disabled = false;
|
|
380
371
|
confirm.addEventListener("click", () => {
|
|
381
372
|
closePopover();
|
|
382
|
-
|
|
373
|
+
opts.onRewind("both");
|
|
383
374
|
});
|
|
384
375
|
})().catch(() => {
|
|
385
376
|
impact.textContent = t("popover.impact.failed", { message: "unexpected error" });
|
|
@@ -399,7 +390,7 @@ function openPopover(opts) {
|
|
|
399
390
|
el("div", CLASS.popoverTarget, formatTarget(t, seq, time, preview)),
|
|
400
391
|
modeOption(t("popover.chat"), t("popover.chat.hint"), () => {
|
|
401
392
|
closePopover();
|
|
402
|
-
|
|
393
|
+
opts.onRewind("chat");
|
|
403
394
|
})
|
|
404
395
|
];
|
|
405
396
|
if (bothState.state === "noChanges") {
|
|
@@ -436,7 +427,6 @@ function openPopover(opts) {
|
|
|
436
427
|
renderModes();
|
|
437
428
|
document.body.append(root);
|
|
438
429
|
position();
|
|
439
|
-
const hasFileImpact = (text) => text === void 0 || text.includes("\u5C06\u5F71\u54CD");
|
|
440
430
|
void (async () => {
|
|
441
431
|
const outcome = await previewImpact(session, seq);
|
|
442
432
|
impactOutcome = outcome;
|
|
@@ -470,14 +460,8 @@ function openPopover(opts) {
|
|
|
470
460
|
};
|
|
471
461
|
}
|
|
472
462
|
|
|
473
|
-
// src/client/
|
|
474
|
-
var
|
|
475
|
-
var inject = ["sessions", "locale"];
|
|
476
|
-
var NS = "rewind";
|
|
477
|
-
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"]';
|
|
478
|
-
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
479
|
-
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
|
|
480
|
-
var COMPOSER_SELECTOR = "[data-input-scroll] textarea, textarea[data-phase]";
|
|
463
|
+
// src/client/portals.tsx
|
|
464
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
481
465
|
function messagePreviewOf(node) {
|
|
482
466
|
const text = node.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").join("").replace(/\s+/g, " ").trim();
|
|
483
467
|
return text.length <= 80 ? text : `${text.slice(0, 79)}\u2026`;
|
|
@@ -503,133 +487,234 @@ function fillComposer(text) {
|
|
|
503
487
|
textarea.focus();
|
|
504
488
|
return true;
|
|
505
489
|
}
|
|
506
|
-
function
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
seat.setAttribute(REWIND_ATTACHED, "");
|
|
569
|
-
};
|
|
570
|
-
const scan = () => {
|
|
571
|
-
for (const [key, button] of buttons) {
|
|
572
|
-
if (!button.isConnected) buttons.delete(key);
|
|
490
|
+
function userNodeOf(session, key) {
|
|
491
|
+
const node = session.getSnapshot().chat.nodes.get(key);
|
|
492
|
+
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") return void 0;
|
|
493
|
+
return node.data;
|
|
494
|
+
}
|
|
495
|
+
async function runRewindAndFill(session, seq, mode, currentSessionId) {
|
|
496
|
+
const known = knownCommandSeqs(session, (node) => isExecutedRewindCommand(node, seq));
|
|
497
|
+
const result = await session.command(`/rewind @${seq} ${mode}`);
|
|
498
|
+
if (!result.ok || result.value?.matched !== true) return;
|
|
499
|
+
const outcome = await waitForCommand(session, (node) => isExecutedRewindCommand(node, seq) && !known.has(node.seq), 2e4);
|
|
500
|
+
if (outcome === null || outcome.kind !== "success") return;
|
|
501
|
+
if (currentSessionId() !== session.sessionId) return;
|
|
502
|
+
const text = userTextAt(session, seq);
|
|
503
|
+
if (text === void 0 || text === "") return;
|
|
504
|
+
fillComposer(text);
|
|
505
|
+
}
|
|
506
|
+
var COMPOSER_SELECTOR = "[data-input-scroll] textarea, textarea[data-phase]";
|
|
507
|
+
var USER_SEAT_SELECTOR = '[data-chat-flow-kind="user"][data-chat-anchor-key], [data-chat-flow-kind="steering"][data-chat-anchor-key]';
|
|
508
|
+
var CHAT_SEAT_SELECTOR = "[data-chat-anchor-key]";
|
|
509
|
+
var ACTIONS_ROOT_SELECTOR = "[data-time-hover-root]";
|
|
510
|
+
function collectTargets(chat, hiddenSeqs) {
|
|
511
|
+
const rows = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const element of document.querySelectorAll(USER_SEAT_SELECTOR)) {
|
|
513
|
+
const key = element.dataset.chatAnchorKey;
|
|
514
|
+
if (key !== void 0) rows.set(key, element);
|
|
515
|
+
}
|
|
516
|
+
const targets = [];
|
|
517
|
+
for (const key of chat.order) {
|
|
518
|
+
const node = chat.nodes.get(key);
|
|
519
|
+
if (node === void 0 || node.kind !== "user" && node.kind !== "steering") continue;
|
|
520
|
+
const user = node.data;
|
|
521
|
+
if (hiddenSeqs.has(node.anchorSeq ?? user.seq)) continue;
|
|
522
|
+
const row = rows.get(key);
|
|
523
|
+
const messageRoot = row?.querySelector(ACTIONS_ROOT_SELECTOR);
|
|
524
|
+
const actions = messageRoot?.lastElementChild;
|
|
525
|
+
if (!(actions instanceof HTMLElement) || actions.querySelector("button") === null) continue;
|
|
526
|
+
targets.push({ key, container: actions, seq: user.seq, time: user.time, preview: messagePreviewOf(user) });
|
|
527
|
+
}
|
|
528
|
+
return targets;
|
|
529
|
+
}
|
|
530
|
+
function sameTargets(left, right) {
|
|
531
|
+
return left.length === right.length && left.every((target, index) => {
|
|
532
|
+
const other = right[index];
|
|
533
|
+
return other !== void 0 && target.key === other.key && target.container === other.container && target.seq === other.seq;
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
function RewindPortals({ sessionId, sessionOf, currentSessionId, t, subscribeLocale }) {
|
|
537
|
+
const [targets, setTargets] = (0, import_react.useState)([]);
|
|
538
|
+
const hidden = (0, import_react.useRef)(/* @__PURE__ */ new WeakSet());
|
|
539
|
+
const [, forceRender] = (0, import_react.useReducer)((count) => count + 1, 0);
|
|
540
|
+
(0, import_react.useEffect)(() => subscribeLocale(() => {
|
|
541
|
+
forceRender();
|
|
542
|
+
}), [subscribeLocale]);
|
|
543
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
544
|
+
let active = true;
|
|
545
|
+
let queued = false;
|
|
546
|
+
const refresh = () => {
|
|
547
|
+
if (!active) return;
|
|
548
|
+
const session = sessionOf(sessionId);
|
|
549
|
+
if (session === void 0) {
|
|
550
|
+
setTargets([]);
|
|
551
|
+
return;
|
|
573
552
|
}
|
|
574
|
-
const
|
|
575
|
-
const hiddenSeqs =
|
|
553
|
+
const chat = session.getSnapshot().chat;
|
|
554
|
+
const hiddenSeqs = hiddenSeqsOf(chat);
|
|
576
555
|
let hiddenCount = 0;
|
|
577
556
|
for (const seat of document.querySelectorAll(CHAT_SEAT_SELECTOR)) {
|
|
578
557
|
const key = seat.dataset.chatAnchorKey;
|
|
579
|
-
const anchor = key !== void 0
|
|
558
|
+
const anchor = key !== void 0 ? chat.nodes.get(key)?.anchorSeq : void 0;
|
|
580
559
|
if (anchor !== void 0 && hiddenSeqs.has(anchor)) {
|
|
581
560
|
seat.style.display = "none";
|
|
582
|
-
hidden.add(seat);
|
|
561
|
+
hidden.current.add(seat);
|
|
583
562
|
hiddenCount += 1;
|
|
584
|
-
} else if (hidden.has(seat)) {
|
|
563
|
+
} else if (hidden.current.has(seat)) {
|
|
585
564
|
seat.style.display = "";
|
|
586
|
-
hidden.delete(seat);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
if (session !== void 0) {
|
|
590
|
-
const snap = session.getSnapshot();
|
|
591
|
-
if (!fillBaselineTaken && snap.chat.order.length > 0) {
|
|
592
|
-
fillBaselineTaken = true;
|
|
593
|
-
for (const key of snap.chat.order) {
|
|
594
|
-
const node = snap.chat.nodes.get(key);
|
|
595
|
-
if (node?.kind === "command") {
|
|
596
|
-
const command = node.data;
|
|
597
|
-
if (command.seq > fillBaselineSeq) fillBaselineSeq = command.seq;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
565
|
+
hidden.current.delete(seat);
|
|
600
566
|
}
|
|
601
|
-
fillComposerForRewind(session, filledTargets);
|
|
602
567
|
}
|
|
603
568
|
if (hiddenSeqs.size > 0 || hiddenCount > 0) {
|
|
604
569
|
console.info(
|
|
605
570
|
`[dsh-rewind] hiding: ${hiddenCount} rows, seqs [${[...hiddenSeqs].slice(0, 20).join(", ")}${hiddenSeqs.size > 20 ? "\u2026" : ""}]`
|
|
606
571
|
);
|
|
607
572
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
}
|
|
573
|
+
const next = collectTargets(chat, hiddenSeqs);
|
|
574
|
+
setTargets((current) => sameTargets(current, next) ? current : next);
|
|
611
575
|
};
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const text = userTextAt(session, target);
|
|
627
|
-
if (text === void 0 || text === "") continue;
|
|
628
|
-
if (fillComposer(text)) filled.add(target);
|
|
629
|
-
}
|
|
576
|
+
const queueRefresh = () => {
|
|
577
|
+
if (queued || !active) return;
|
|
578
|
+
queued = true;
|
|
579
|
+
queueMicrotask(() => {
|
|
580
|
+
queued = false;
|
|
581
|
+
refresh();
|
|
582
|
+
});
|
|
583
|
+
};
|
|
584
|
+
refresh();
|
|
585
|
+
const observer = new MutationObserver(queueRefresh);
|
|
586
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style"] });
|
|
587
|
+
return () => {
|
|
588
|
+
active = false;
|
|
589
|
+
observer.disconnect();
|
|
630
590
|
};
|
|
591
|
+
}, [sessionId, sessionOf]);
|
|
592
|
+
return targets.map((target) => (0, import_react_dom.createPortal)(
|
|
593
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
594
|
+
RewindButton,
|
|
595
|
+
{
|
|
596
|
+
target,
|
|
597
|
+
sessionId,
|
|
598
|
+
sessionOf,
|
|
599
|
+
currentSessionId,
|
|
600
|
+
t
|
|
601
|
+
},
|
|
602
|
+
target.key
|
|
603
|
+
),
|
|
604
|
+
target.container,
|
|
605
|
+
target.key
|
|
606
|
+
));
|
|
607
|
+
}
|
|
608
|
+
function RewindButton({ target, sessionId, sessionOf, currentSessionId, t }) {
|
|
609
|
+
const onClick = (event) => {
|
|
610
|
+
event.stopPropagation();
|
|
611
|
+
const session = sessionOf(sessionId);
|
|
612
|
+
if (session === void 0) {
|
|
613
|
+
console.warn("[dsh-rewind] rewind button clicked with no session binding");
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const node = userNodeOf(session, target.key);
|
|
617
|
+
if (node === void 0) return;
|
|
618
|
+
openPopover({
|
|
619
|
+
session,
|
|
620
|
+
seq: node.seq,
|
|
621
|
+
time: node.time,
|
|
622
|
+
preview: messagePreviewOf(node),
|
|
623
|
+
anchor: event.currentTarget,
|
|
624
|
+
t,
|
|
625
|
+
onRewind: (mode) => {
|
|
626
|
+
void runRewindAndFill(session, node.seq, mode, currentSessionId);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
};
|
|
630
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
631
|
+
"button",
|
|
632
|
+
{
|
|
633
|
+
type: "button",
|
|
634
|
+
className: CLASS.button,
|
|
635
|
+
"aria-label": t("button.aria"),
|
|
636
|
+
title: t("button.title"),
|
|
637
|
+
onClick,
|
|
638
|
+
dangerouslySetInnerHTML: { __html: REWIND_ICON_SVG }
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function createRewindBridge(deps) {
|
|
643
|
+
return function RewindBridge({ sessionId }) {
|
|
644
|
+
return (0, import_react.createElement)(RewindPortals, { sessionId, ...deps });
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/client/locales.ts
|
|
649
|
+
var zh = {
|
|
650
|
+
"button.aria": "\u56DE\u9000\u5230\u6B64\u6D88\u606F",
|
|
651
|
+
"button.title": "\u56DE\u9000",
|
|
652
|
+
"popover.title": "\u56DE\u9000\u5230\u8FD9\u6761\u6D88\u606F",
|
|
653
|
+
"popover.noText": "\uFF08\u65E0\u6587\u672C\uFF09",
|
|
654
|
+
"popover.chat": "\u4EC5\u56DE\u9000\u5BF9\u8BDD",
|
|
655
|
+
"popover.chat.hint": "\u53EA\u56DE\u9000\u6A21\u578B\u4E0A\u4E0B\u6587\uFF0C\u4E0D\u52A8\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
656
|
+
"popover.both": "\u56DE\u9000\u5BF9\u8BDD\u548C\u4EE3\u7801",
|
|
657
|
+
"popover.both.hint": "\u5BF9\u8BDD\u56DE\u9000\u5E76\u8FD8\u539F\u5DE5\u4F5C\u533A\u6587\u4EF6",
|
|
658
|
+
"popover.checking": "\u6B63\u5728\u68C0\u67E5\u6587\u4EF6\u53D8\u66F4\u2026",
|
|
659
|
+
"popover.noChanges": "\u6B64\u6D88\u606F\u4E4B\u540E\u6CA1\u6709\u53EF\u8FD8\u539F\u7684\u6587\u4EF6\u53D8\u66F4\uFF0C\u4EC5\u53EF\u56DE\u9000\u5BF9\u8BDD",
|
|
660
|
+
"popover.cancel": "\u53D6\u6D88",
|
|
661
|
+
"popover.impact.loading": "\u6B63\u5728\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\u2026",
|
|
662
|
+
"popover.impact.failed": "\u65E0\u6CD5\u83B7\u53D6\u5F71\u54CD\u6E05\u5355\uFF1A{message}",
|
|
663
|
+
"popover.impact.none": "\u76EE\u6807\u4E4B\u540E\u6CA1\u6709\u8DDF\u8E2A\u5230\u7684\u5199\u7C7B\u53D8\u66F4\uFF0C\u65E0\u9700\u8FD8\u539F\u6587\u4EF6\u3002",
|
|
664
|
+
"popover.confirm": "\u786E\u8BA4\u56DE\u9000",
|
|
665
|
+
"popover.back": "\u8FD4\u56DE",
|
|
666
|
+
"guard.hint": "/rewind \u547D\u4EE4\u4E0D\u652F\u6301\u624B\u52A8\u8F93\u5165\uFF0C\u8BF7\u4F7F\u7528\u6D88\u606F\u65C1\u7684 \u21B6 \u56DE\u9000\u6309\u94AE"
|
|
667
|
+
};
|
|
668
|
+
var en = {
|
|
669
|
+
"button.aria": "Rewind to this message",
|
|
670
|
+
"button.title": "Rewind",
|
|
671
|
+
"popover.title": "Rewind to this message",
|
|
672
|
+
"popover.noText": "(no text)",
|
|
673
|
+
"popover.chat": "Rewind conversation only",
|
|
674
|
+
"popover.chat.hint": "Cut the model context only; workspace files stay untouched",
|
|
675
|
+
"popover.both": "Rewind conversation and code",
|
|
676
|
+
"popover.both.hint": "Cut the context and restore workspace files",
|
|
677
|
+
"popover.checking": "Checking for file changes\u2026",
|
|
678
|
+
"popover.noChanges": "No tracked file changes after this message; conversation-only rewind",
|
|
679
|
+
"popover.cancel": "Cancel",
|
|
680
|
+
"popover.impact.loading": "Fetching impact list\u2026",
|
|
681
|
+
"popover.impact.failed": "Could not fetch the impact list: {message}",
|
|
682
|
+
"popover.impact.none": "No tracked file changes after the target; nothing to restore.",
|
|
683
|
+
"popover.confirm": "Confirm rewind",
|
|
684
|
+
"popover.back": "Back",
|
|
685
|
+
"guard.hint": "/rewind cannot be typed manually \u2014 use the \u21B6 button on a message to rewind."
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/client/index.ts
|
|
689
|
+
var name = "dsh-rewind";
|
|
690
|
+
var inject = ["slots", "sessions", "locale"];
|
|
691
|
+
var NS = "rewind";
|
|
692
|
+
var HEADER_ACTIONS_SLOT = "conversation.session.header.actions";
|
|
693
|
+
var COMPOSER_SELECTOR2 = "[data-input-scroll] textarea, textarea[data-phase]";
|
|
694
|
+
function apply(ctx) {
|
|
695
|
+
ctx.effect(function* () {
|
|
696
|
+
yield ctx.locale.register(NS, { zh, en });
|
|
697
|
+
const t = ctx.locale.bind(NS);
|
|
698
|
+
const style = document.createElement("style");
|
|
699
|
+
style.dataset.plugin = "dsh-rewind";
|
|
700
|
+
style.textContent = STYLE;
|
|
701
|
+
document.head.appendChild(style);
|
|
702
|
+
const sessionOf = (sessionId) => ctx.sessions.binding(sessionId)?.session;
|
|
703
|
+
const currentSessionId = () => ctx.sessions.list.getSnapshot().current;
|
|
704
|
+
const subscribeLocale = (cb) => ctx.locale.subscribe(cb);
|
|
705
|
+
const slots = ctx.slots;
|
|
706
|
+
yield slots.inject(HEADER_ACTIONS_SLOT, () => slots.register(
|
|
707
|
+
{
|
|
708
|
+
name: HEADER_ACTIONS_SLOT,
|
|
709
|
+
// A distinct list-entry id keeps the bridge from shadowing any other
|
|
710
|
+
// header action; the entry renders portals only, never header UI.
|
|
711
|
+
id: "dsh-rewind-portals",
|
|
712
|
+
order: 1e3
|
|
713
|
+
},
|
|
714
|
+
createRewindBridge({ sessionOf, currentSessionId, t, subscribeLocale })
|
|
715
|
+
));
|
|
631
716
|
const MANUAL_REWIND = /^\s*\/rewind(?:\s|$)/i;
|
|
632
|
-
const composerTextarea = () => document.querySelector(
|
|
717
|
+
const composerTextarea = () => document.querySelector(COMPOSER_SELECTOR2);
|
|
633
718
|
const hasBlockedRewindDraft = () => {
|
|
634
719
|
const textarea = composerTextarea();
|
|
635
720
|
return textarea !== null && MANUAL_REWIND.test(textarea.value);
|
|
@@ -643,7 +728,7 @@ function apply(ctx) {
|
|
|
643
728
|
if (textarea === null) return;
|
|
644
729
|
const card = textarea.closest("[data-composer-card]");
|
|
645
730
|
const hint = document.createElement("div");
|
|
646
|
-
hint.className =
|
|
731
|
+
hint.className = "dsh-rewind-guard-hint";
|
|
647
732
|
hint.setAttribute("role", "status");
|
|
648
733
|
hint.textContent = t("guard.hint");
|
|
649
734
|
document.body.appendChild(hint);
|
|
@@ -681,19 +766,11 @@ function apply(ctx) {
|
|
|
681
766
|
};
|
|
682
767
|
document.addEventListener("keydown", onKeyDownGuard, true);
|
|
683
768
|
document.addEventListener("click", onClickGuard, true);
|
|
684
|
-
observer = new MutationObserver(scan);
|
|
685
|
-
observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style"] });
|
|
686
|
-
scan();
|
|
687
769
|
yield () => {
|
|
688
|
-
unsubscribeLocale();
|
|
689
770
|
document.removeEventListener("keydown", onKeyDownGuard, true);
|
|
690
771
|
document.removeEventListener("click", onClickGuard, true);
|
|
691
772
|
if (guardHintEl !== null) guardHintEl.remove();
|
|
692
773
|
if (guardHintTimer !== void 0) window.clearTimeout(guardHintTimer);
|
|
693
|
-
observer?.disconnect();
|
|
694
|
-
for (const button of buttons.values()) button.remove();
|
|
695
|
-
buttons.clear();
|
|
696
|
-
closePopover();
|
|
697
774
|
style.remove();
|
|
698
775
|
};
|
|
699
776
|
}, "dsh-rewind client lifecycle");
|