@khorsheed/dsh-client-message-tools 0.1.0
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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.en.md +82 -0
- package/README.i18n.yaml +6 -0
- package/README.md +82 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +5824 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +644 -0
- package/lib/invariant.js +27 -0
- package/lib/tsconfig.client.tsbuildinfo +1 -0
- package/lib/tsconfig.host.tsbuildinfo +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +250 -0
- package/lib/typert.remote-client.d.ts +26 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +154 -0
- package/lib/types/client/ModelChip.d.ts +29 -0
- package/lib/types/client/ModelChip.d.ts.map +1 -0
- package/lib/types/client/ModelChip.js +86 -0
- package/lib/types/client/RestoredMessageView.d.ts +17 -0
- package/lib/types/client/RestoredMessageView.d.ts.map +1 -0
- package/lib/types/client/RestoredMessageView.js +9 -0
- package/lib/types/client/UserMessageView.d.ts +16 -0
- package/lib/types/client/UserMessageView.d.ts.map +1 -0
- package/lib/types/client/UserMessageView.js +172 -0
- package/lib/types/client/WithdrawnDividerView.d.ts +17 -0
- package/lib/types/client/WithdrawnDividerView.d.ts.map +1 -0
- package/lib/types/client/WithdrawnDividerView.js +59 -0
- package/lib/types/client/backfill.d.ts +19 -0
- package/lib/types/client/backfill.d.ts.map +1 -0
- package/lib/types/client/backfill.js +20 -0
- package/lib/types/client/dom-hider.d.ts +61 -0
- package/lib/types/client/dom-hider.d.ts.map +1 -0
- package/lib/types/client/dom-hider.js +195 -0
- package/lib/types/client/edit-in-place.d.ts +107 -0
- package/lib/types/client/edit-in-place.d.ts.map +1 -0
- package/lib/types/client/edit-in-place.js +97 -0
- package/lib/types/client/icons.d.ts +23 -0
- package/lib/types/client/icons.d.ts.map +1 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +39 -0
- package/lib/types/client/index.d.ts.map +1 -0
- package/lib/types/client/index.js +187 -0
- package/lib/types/client/locales.d.ts +74 -0
- package/lib/types/client/locales.d.ts.map +1 -0
- package/lib/types/client/locales.js +65 -0
- package/lib/types/client/slots.d.ts +44 -0
- package/lib/types/client/slots.d.ts.map +1 -0
- package/lib/types/client/slots.js +1 -0
- package/lib/types/client/withdraw-backfill.d.ts +25 -0
- package/lib/types/client/withdraw-backfill.d.ts.map +1 -0
- package/lib/types/client/withdraw-backfill.js +21 -0
- package/lib/types/client/withdrawn-node.d.ts +163 -0
- package/lib/types/client/withdrawn-node.d.ts.map +1 -0
- package/lib/types/client/withdrawn-node.js +297 -0
- package/lib/types/index.d.ts +69 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +175 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +25 -0
- package/lib/types/marker.d.ts +94 -0
- package/lib/types/marker.d.ts.map +1 -0
- package/lib/types/marker.js +131 -0
- package/lib/types/types.d.ts +116 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +1 -0
- package/lib/types/withdraw.d.ts +112 -0
- package/lib/types/withdraw.d.ts.map +1 -0
- package/lib/types/withdraw.js +279 -0
- package/package.json +108 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit choreography shared by the shadowed renderers: cancel the running turn
|
|
3
|
+
* (waiting for it to settle so its teardown writes land before the
|
|
4
|
+
* replacement), then apply the in-place edit, which itself triggers the
|
|
5
|
+
* regeneration turn. Kept free of services so the unit tests drive it with
|
|
6
|
+
* stub verbs.
|
|
7
|
+
*/
|
|
8
|
+
/** The verbs of one in-place edit, bound to a session by the caller. */
|
|
9
|
+
export interface EditInPlaceSteps {
|
|
10
|
+
/** Cancel the session's running turn. */
|
|
11
|
+
cancel: () => Promise<void>;
|
|
12
|
+
/** Wait until the cancelled turn has fully settled (bounded; never hangs). */
|
|
13
|
+
waitIdle: () => Promise<void>;
|
|
14
|
+
/** Apply the edit (single Remote call: replace + trigger regeneration). */
|
|
15
|
+
edit: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Edit one user message in place: when the session has a running turn, cancel
|
|
19
|
+
* it first (it is writing into the span the edit is about to shadow) and wait
|
|
20
|
+
* for the settle, then apply the edit. A failed cancel rejects without
|
|
21
|
+
* editing; a failed or timed-out settle wait rejects without editing too —
|
|
22
|
+
* editing before the cancelled turn's teardown has landed would leak the
|
|
23
|
+
* straggler tool results outside the replacement's span. An idle session is
|
|
24
|
+
* never cancelled.
|
|
25
|
+
* @param steps - the cancel/waitIdle/edit verbs.
|
|
26
|
+
* @param running - whether the session has a running turn right now.
|
|
27
|
+
* @returns completion; failures reject at the failing step.
|
|
28
|
+
*/
|
|
29
|
+
export declare function editInPlace(steps: EditInPlaceSteps, running: boolean): Promise<void>;
|
|
30
|
+
/** The verbs of one withdrawal, bound to a session by the caller. */
|
|
31
|
+
export interface WithdrawInPlaceSteps {
|
|
32
|
+
/** Cancel the session's running turn. */
|
|
33
|
+
cancel: () => Promise<void>;
|
|
34
|
+
/** Wait until the cancelled turn has fully settled (bounded; never hangs). */
|
|
35
|
+
waitIdle: () => Promise<void>;
|
|
36
|
+
/** Apply the withdrawal (single Remote call: surface replacement + flush). */
|
|
37
|
+
withdraw: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Withdraw one user message: when the session has a running turn, cancel it
|
|
41
|
+
* first (it is writing into the span the withdrawal is about to shadow) and
|
|
42
|
+
* wait for the settle, then apply the withdrawal. A failed cancel rejects
|
|
43
|
+
* without withdrawing; a failed or timed-out settle wait rejects without
|
|
44
|
+
* withdrawing too — withdrawing before the cancelled turn's teardown has
|
|
45
|
+
* landed would leave straggler chunks/assistant writes outside the
|
|
46
|
+
* replacement's span. An idle session is never cancelled.
|
|
47
|
+
* @param steps - the cancel/waitIdle/withdraw verbs.
|
|
48
|
+
* @param running - whether the session has a running turn right now.
|
|
49
|
+
* @returns completion; failures reject at the failing step.
|
|
50
|
+
*/
|
|
51
|
+
export declare function withdrawInPlace(steps: WithdrawInPlaceSteps, running: boolean): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* The minimal slice of the client conversation snapshot the settle probe
|
|
54
|
+
* reads (structurally satisfied by `ConversationSnapshot`, so the unit tests
|
|
55
|
+
* stub only these fields).
|
|
56
|
+
*/
|
|
57
|
+
export interface TurnSettleSnapshot {
|
|
58
|
+
running: boolean;
|
|
59
|
+
runningCalls: readonly unknown[];
|
|
60
|
+
chat: {
|
|
61
|
+
timeline: {
|
|
62
|
+
turnOrder: readonly number[];
|
|
63
|
+
turns: ReadonlyMap<number, {
|
|
64
|
+
readonly status: 'open' | 'closed' | 'unknown';
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether the cancelled turn's teardown has fully landed in the session log.
|
|
71
|
+
* `running` flips false FIRST (host status push), the cancelled tool results
|
|
72
|
+
* persist after that, and the `turn/end` event lands last — an edit computed
|
|
73
|
+
* in between shadows a span that ends before those stragglers, leaving orphan
|
|
74
|
+
* tool messages in the model context (the model API then rejects the
|
|
75
|
+
* request). The authoritative settle signal is the latest turn's timeline
|
|
76
|
+
* location closing (turn/end is written after every result); `running` and
|
|
77
|
+
* `runningCalls` are earlier short-circuits. An empty turn order means the
|
|
78
|
+
* running turn's `turn/start` has not streamed in yet — not settled either.
|
|
79
|
+
* @param snapshot - the session's current conversation snapshot.
|
|
80
|
+
* @returns true only when every teardown write of the latest turn has landed.
|
|
81
|
+
*/
|
|
82
|
+
export declare function turnSettled(snapshot: TurnSettleSnapshot): boolean;
|
|
83
|
+
/** Tuning of {@link waitForTurnSettled}; the defaults match production. */
|
|
84
|
+
export interface SettleWaitOptions {
|
|
85
|
+
/** Overall bound; the wait rejects when it expires (default 5s). */
|
|
86
|
+
timeoutMs?: number;
|
|
87
|
+
/** Poll interval between snapshot reads (default 100ms). */
|
|
88
|
+
intervalMs?: number;
|
|
89
|
+
/** Clock override for tests. */
|
|
90
|
+
now?: () => number;
|
|
91
|
+
/** Sleep override for tests. */
|
|
92
|
+
sleep?: (ms: number) => Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Poll until the cancelled turn has fully settled ({@link turnSettled}) or
|
|
96
|
+
* the bound expires. The wait is always bounded: on timeout it rejects, and
|
|
97
|
+
* the caller (editInPlace) never edits after a failed settle — editing then
|
|
98
|
+
* would race the cancelled turn's straggler writes onto the surface. An
|
|
99
|
+
* unresolvable snapshot (session binding gone) cannot prove the settle, so it
|
|
100
|
+
* is waited out and rejected like a timeout, never treated as settled.
|
|
101
|
+
* @param snapshot - reads the session's current snapshot (undefined when the
|
|
102
|
+
* binding is gone).
|
|
103
|
+
* @param options - timeout/interval/clock overrides.
|
|
104
|
+
* @returns resolution once settled; rejection on timeout.
|
|
105
|
+
*/
|
|
106
|
+
export declare function waitForTurnSettled(snapshot: () => TurnSettleSnapshot | undefined, options?: SettleWaitOptions): Promise<void>;
|
|
107
|
+
//# sourceMappingURL=edit-in-place.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edit-in-place.d.ts","sourceRoot":"","sources":["../../../src/client/edit-in-place.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,yCAAyC;IACzC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,2EAA2E;IAC3E,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAM1F;AAED,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,yCAAyC;IACzC,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAMlG;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,EAAE,SAAS,OAAO,EAAE,CAAA;IAChC,IAAI,EAAE;QACJ,QAAQ,EAAE;YACR,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;YAC5B,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE;gBAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAA;aAAE,CAAC,CAAA;SAC/E,CAAA;KACF,CAAA;CACF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAOjE;AAED,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,gCAAgC;IAChC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,kBAAkB,GAAG,SAAS,EAC9C,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit choreography shared by the shadowed renderers: cancel the running turn
|
|
3
|
+
* (waiting for it to settle so its teardown writes land before the
|
|
4
|
+
* replacement), then apply the in-place edit, which itself triggers the
|
|
5
|
+
* regeneration turn. Kept free of services so the unit tests drive it with
|
|
6
|
+
* stub verbs.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Edit one user message in place: when the session has a running turn, cancel
|
|
10
|
+
* it first (it is writing into the span the edit is about to shadow) and wait
|
|
11
|
+
* for the settle, then apply the edit. A failed cancel rejects without
|
|
12
|
+
* editing; a failed or timed-out settle wait rejects without editing too —
|
|
13
|
+
* editing before the cancelled turn's teardown has landed would leak the
|
|
14
|
+
* straggler tool results outside the replacement's span. An idle session is
|
|
15
|
+
* never cancelled.
|
|
16
|
+
* @param steps - the cancel/waitIdle/edit verbs.
|
|
17
|
+
* @param running - whether the session has a running turn right now.
|
|
18
|
+
* @returns completion; failures reject at the failing step.
|
|
19
|
+
*/
|
|
20
|
+
export async function editInPlace(steps, running) {
|
|
21
|
+
if (running) {
|
|
22
|
+
await steps.cancel();
|
|
23
|
+
await steps.waitIdle();
|
|
24
|
+
}
|
|
25
|
+
await steps.edit();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Withdraw one user message: when the session has a running turn, cancel it
|
|
29
|
+
* first (it is writing into the span the withdrawal is about to shadow) and
|
|
30
|
+
* wait for the settle, then apply the withdrawal. A failed cancel rejects
|
|
31
|
+
* without withdrawing; a failed or timed-out settle wait rejects without
|
|
32
|
+
* withdrawing too — withdrawing before the cancelled turn's teardown has
|
|
33
|
+
* landed would leave straggler chunks/assistant writes outside the
|
|
34
|
+
* replacement's span. An idle session is never cancelled.
|
|
35
|
+
* @param steps - the cancel/waitIdle/withdraw verbs.
|
|
36
|
+
* @param running - whether the session has a running turn right now.
|
|
37
|
+
* @returns completion; failures reject at the failing step.
|
|
38
|
+
*/
|
|
39
|
+
export async function withdrawInPlace(steps, running) {
|
|
40
|
+
if (running) {
|
|
41
|
+
await steps.cancel();
|
|
42
|
+
await steps.waitIdle();
|
|
43
|
+
}
|
|
44
|
+
await steps.withdraw();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Whether the cancelled turn's teardown has fully landed in the session log.
|
|
48
|
+
* `running` flips false FIRST (host status push), the cancelled tool results
|
|
49
|
+
* persist after that, and the `turn/end` event lands last — an edit computed
|
|
50
|
+
* in between shadows a span that ends before those stragglers, leaving orphan
|
|
51
|
+
* tool messages in the model context (the model API then rejects the
|
|
52
|
+
* request). The authoritative settle signal is the latest turn's timeline
|
|
53
|
+
* location closing (turn/end is written after every result); `running` and
|
|
54
|
+
* `runningCalls` are earlier short-circuits. An empty turn order means the
|
|
55
|
+
* running turn's `turn/start` has not streamed in yet — not settled either.
|
|
56
|
+
* @param snapshot - the session's current conversation snapshot.
|
|
57
|
+
* @returns true only when every teardown write of the latest turn has landed.
|
|
58
|
+
*/
|
|
59
|
+
export function turnSettled(snapshot) {
|
|
60
|
+
if (snapshot.running)
|
|
61
|
+
return false;
|
|
62
|
+
if (snapshot.runningCalls.length > 0)
|
|
63
|
+
return false;
|
|
64
|
+
const order = snapshot.chat.timeline.turnOrder;
|
|
65
|
+
const last = order[order.length - 1];
|
|
66
|
+
if (last === undefined)
|
|
67
|
+
return false;
|
|
68
|
+
return snapshot.chat.timeline.turns.get(last)?.status === 'closed';
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Poll until the cancelled turn has fully settled ({@link turnSettled}) or
|
|
72
|
+
* the bound expires. The wait is always bounded: on timeout it rejects, and
|
|
73
|
+
* the caller (editInPlace) never edits after a failed settle — editing then
|
|
74
|
+
* would race the cancelled turn's straggler writes onto the surface. An
|
|
75
|
+
* unresolvable snapshot (session binding gone) cannot prove the settle, so it
|
|
76
|
+
* is waited out and rejected like a timeout, never treated as settled.
|
|
77
|
+
* @param snapshot - reads the session's current snapshot (undefined when the
|
|
78
|
+
* binding is gone).
|
|
79
|
+
* @param options - timeout/interval/clock overrides.
|
|
80
|
+
* @returns resolution once settled; rejection on timeout.
|
|
81
|
+
*/
|
|
82
|
+
export async function waitForTurnSettled(snapshot, options = {}) {
|
|
83
|
+
const now = options.now ?? Date.now;
|
|
84
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve) => { setTimeout(resolve, ms); }));
|
|
85
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
86
|
+
const intervalMs = options.intervalMs ?? 100;
|
|
87
|
+
const deadline = now() + timeoutMs;
|
|
88
|
+
for (;;) {
|
|
89
|
+
const current = snapshot();
|
|
90
|
+
if (current !== undefined && turnSettled(current))
|
|
91
|
+
return;
|
|
92
|
+
if (now() >= deadline) {
|
|
93
|
+
throw new Error('message-tools: timed out waiting for the cancelled turn to settle');
|
|
94
|
+
}
|
|
95
|
+
await sleep(intervalMs);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned icons matching the official outline-icon conventions (16×16
|
|
3
|
+
* viewBox, `fill="currentColor"` path geometry) for glyphs ui-primitives
|
|
4
|
+
* does not ship.
|
|
5
|
+
*/
|
|
6
|
+
/** Props of one package-owned icon. */
|
|
7
|
+
export interface LocalIconProps {
|
|
8
|
+
/** Rendered edge length in px (icons are authored at 16). */
|
|
9
|
+
size?: number;
|
|
10
|
+
/** Optional composed class. */
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Undo arrow (withdraw/restore), rotate-ccw style: a near-full circle arc
|
|
15
|
+
* (~290°, opening at the top) with a corner arrowhead at its left end
|
|
16
|
+
* pointing the counterclockwise direction. ui-primitives ships no undo/revert
|
|
17
|
+
* glyph, and its refresh glyph carries reload semantics, so this package
|
|
18
|
+
* draws its own.
|
|
19
|
+
* @param props - size and className.
|
|
20
|
+
* @returns the icon element.
|
|
21
|
+
*/
|
|
22
|
+
export declare const IconUndoOutline16: ({ size, className }: LocalIconProps) => import("react").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../src/client/icons.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,wBAA8B,cAAc,gCAkBzE,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Undo arrow (withdraw/restore), rotate-ccw style: a near-full circle arc
|
|
4
|
+
* (~290°, opening at the top) with a corner arrowhead at its left end
|
|
5
|
+
* pointing the counterclockwise direction. ui-primitives ships no undo/revert
|
|
6
|
+
* glyph, and its refresh glyph carries reload semantics, so this package
|
|
7
|
+
* draws its own.
|
|
8
|
+
* @param props - size and className.
|
|
9
|
+
* @returns the icon element.
|
|
10
|
+
*/
|
|
11
|
+
export const IconUndoOutline16 = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M2.2 8.7 A5.8 5.8 0 1 0 5.9 3.3 L2.6 6.6" }), _jsx("path", { d: "M2.6 2.4 V6.6 H6.2", fill: "none" })] }));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* message-tools client plugin, browser half. Shadows the official
|
|
3
|
+
* user-message renderer (`conversation.chat.node`, key 'user', priority -1)
|
|
4
|
+
* with a visual clone that adds edit/withdraw actions and a real
|
|
5
|
+
* model-switch chip in the editor (over ui-model-selection's shared
|
|
6
|
+
* per-session directory, read optionally through `ctx.get`), registers the
|
|
7
|
+
* withdrawal divider Definition so a landed withdrawal leaves a marker row,
|
|
8
|
+
* and installs the DOM hider that drops every chat row inside a withdrawn
|
|
9
|
+
* span (all node kinds, with probe-based degradation). The messageTools
|
|
10
|
+
* Remote is mounted here through the official `ctx.remote.$mount` channel, so
|
|
11
|
+
* the plugin distributes as an independent package with no edits to core
|
|
12
|
+
* packages. Composing this plugin out of cordis.yml removes every surface it
|
|
13
|
+
* adds.
|
|
14
|
+
* @module @khorsheed/dsh-client-message-tools/client
|
|
15
|
+
*/
|
|
16
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
17
|
+
import type { TypertRemoteNamespaceMap } from '@deepseek-ai/dsh-typert-protocol';
|
|
18
|
+
/** The messageTools Remote namespace, as mounted by this plugin. */
|
|
19
|
+
export type MessageToolsRemote = TypertRemoteNamespaceMap['messageTools'];
|
|
20
|
+
/**
|
|
21
|
+
* Required services: slots, sessions, the remote channel, the scope-addressed
|
|
22
|
+
* conversation service, conversation node registration, and the copy.
|
|
23
|
+
* `remote.messageTools` is deliberately NOT an inject: this plugin both
|
|
24
|
+
* mounts the namespace (through `$mount` below) and consumes it, and the
|
|
25
|
+
* Cordis property proxy only resolves services declared in `inject` or
|
|
26
|
+
* provided by an ancestor fiber. Declaring it would deadlock the loader; the
|
|
27
|
+
* namespace is read back from the global store with `ctx.get` after the
|
|
28
|
+
* awaited mount.
|
|
29
|
+
*/
|
|
30
|
+
export declare const inject: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Client plugin body: mount the Remote, register the dictionaries, the
|
|
33
|
+
* withdrawal divider Definition, and the two `conversation.chat.node` entries
|
|
34
|
+
* (the divider row and the shadowed user renderer).
|
|
35
|
+
* @param ctx - client root context.
|
|
36
|
+
* @returns disposer unwinding the mounted Remote namespace.
|
|
37
|
+
*/
|
|
38
|
+
export declare function apply(ctx: ClientContext): Promise<() => Promise<void>>;
|
|
39
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,wCAAwC,CAAA;AAatF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAahF,oEAAoE;AACpE,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,cAAc,CAAC,CAAA;AAczE;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM,UAAkF,CAAA;AAErG;;;;;;GAMG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAoJ5E"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import messageToolsRemote from '@khorsheed/dsh-client-message-tools/remote';
|
|
2
|
+
import { installDomHider } from './dom-hider.js';
|
|
3
|
+
import { mergedDraft } from './backfill.js';
|
|
4
|
+
import { editInPlace, waitForTurnSettled, withdrawInPlace } from './edit-in-place.js';
|
|
5
|
+
import { en, zh } from './locales.js';
|
|
6
|
+
import { editedMessageDefinition, restoredAssistantMessageDefinition, restoredMessageDefinition, withdrawnDividerDefinition, } from './withdrawn-node.js';
|
|
7
|
+
import { UserMessageView } from './UserMessageView.js';
|
|
8
|
+
import { RestoredMessageView } from './RestoredMessageView.js';
|
|
9
|
+
import { WithdrawnDividerView } from './WithdrawnDividerView.js';
|
|
10
|
+
/** Dictionary namespace owned by this plugin. */
|
|
11
|
+
const NS = 'message-tools';
|
|
12
|
+
/** Never-changing directory stub used when ui-model-selection is not composed; the chip is never rendered then. */
|
|
13
|
+
const EMPTY_MODEL_DIRECTORY = {
|
|
14
|
+
getSnapshot: () => EMPTY_MODEL_DIRECTORY_STATE,
|
|
15
|
+
subscribe: () => () => { },
|
|
16
|
+
};
|
|
17
|
+
const EMPTY_MODEL_DIRECTORY_STATE = Object.freeze({
|
|
18
|
+
current: null, routable: null, groups: [], failures: [], status: 'idle', error: null,
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Required services: slots, sessions, the remote channel, the scope-addressed
|
|
22
|
+
* conversation service, conversation node registration, and the copy.
|
|
23
|
+
* `remote.messageTools` is deliberately NOT an inject: this plugin both
|
|
24
|
+
* mounts the namespace (through `$mount` below) and consumes it, and the
|
|
25
|
+
* Cordis property proxy only resolves services declared in `inject` or
|
|
26
|
+
* provided by an ancestor fiber. Declaring it would deadlock the loader; the
|
|
27
|
+
* namespace is read back from the global store with `ctx.get` after the
|
|
28
|
+
* awaited mount.
|
|
29
|
+
*/
|
|
30
|
+
export const inject = ['slots', 'sessions', 'remote', 'conversation', 'conversationEvents', 'locale'];
|
|
31
|
+
/**
|
|
32
|
+
* Client plugin body: mount the Remote, register the dictionaries, the
|
|
33
|
+
* withdrawal divider Definition, and the two `conversation.chat.node` entries
|
|
34
|
+
* (the divider row and the shadowed user renderer).
|
|
35
|
+
* @param ctx - client root context.
|
|
36
|
+
* @returns disposer unwinding the mounted Remote namespace.
|
|
37
|
+
*/
|
|
38
|
+
export async function apply(ctx) {
|
|
39
|
+
const disposers = [];
|
|
40
|
+
try {
|
|
41
|
+
disposers.push(await ctx.remote.$mount(messageToolsRemote));
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// A Remote already mounted by another composition fails loud at boot; the
|
|
45
|
+
// rest of the plugin still registers (the actions would answer an
|
|
46
|
+
// unmounted namespace with a typed RPC error, surfaced inline).
|
|
47
|
+
ctx.logger.error(error);
|
|
48
|
+
}
|
|
49
|
+
const remote = ctx.get('remote.messageTools');
|
|
50
|
+
const restore = async (sessionId, targetSeq) => {
|
|
51
|
+
const carried = await remote.restore({ sessionId, targetSeq });
|
|
52
|
+
if (!carried.ok)
|
|
53
|
+
throw new Error(`messageTools.restore transport: ${carried.error.code}`);
|
|
54
|
+
if (!carried.value.ok)
|
|
55
|
+
throw new Error(`messageTools.restore rejected: ${carried.value.error.code}`);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The draft-backfill path of the automatic post-withdrawal backfill:
|
|
59
|
+
* setDraft is the single public draft write path; the notice lands on this
|
|
60
|
+
* session's composer even across a session switch. Never sends.
|
|
61
|
+
*/
|
|
62
|
+
const backfill = (sessionId, text) => {
|
|
63
|
+
const actx = ctx.sessions.scope(sessionId);
|
|
64
|
+
if (actx === undefined)
|
|
65
|
+
throw new Error(`message-tools: session "${sessionId}" resolved no scope`);
|
|
66
|
+
const conversation = actx.get('conversation');
|
|
67
|
+
if (conversation === undefined)
|
|
68
|
+
throw new Error('message-tools: conversation service unavailable');
|
|
69
|
+
const input = conversation.input.for(actx);
|
|
70
|
+
input.setDraft(mergedDraft(input.state.getSnapshot().draft, text));
|
|
71
|
+
input.notify('info', t('withdrawn.backfilled'));
|
|
72
|
+
};
|
|
73
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'message-tools: dictionaries');
|
|
74
|
+
const t = ctx.locale.bind(NS);
|
|
75
|
+
ctx.conversationEvents.register(withdrawnDividerDefinition);
|
|
76
|
+
ctx.conversationEvents.register(restoredMessageDefinition);
|
|
77
|
+
ctx.conversationEvents.register(restoredAssistantMessageDefinition);
|
|
78
|
+
ctx.conversationEvents.register(editedMessageDefinition);
|
|
79
|
+
ctx.effect(() => installDomHider(ctx), 'message-tools: dom hider');
|
|
80
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
81
|
+
name: 'conversation.chat.node',
|
|
82
|
+
key: 'message-tools-withdrawn',
|
|
83
|
+
locale: NS,
|
|
84
|
+
inject: (sessionId) => ({
|
|
85
|
+
restoreMessage: targetSeq => restore(sessionId, targetSeq),
|
|
86
|
+
}),
|
|
87
|
+
}, WithdrawnDividerView));
|
|
88
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({ name: 'conversation.chat.node', key: 'message-tools-restored-assistant', locale: NS }, RestoredMessageView));
|
|
89
|
+
// The slot is declared by ui-conversation, whose apply order relative to
|
|
90
|
+
// this plugin is unconstrained: register through slots.inject so the shadow
|
|
91
|
+
// waits for the declaration instead of crashing the loader entry at boot.
|
|
92
|
+
// 'user' and 'steering' share the official renderer upstream, so both keys
|
|
93
|
+
// get the same shadow here (an admitted steering message is an append
|
|
94
|
+
// surface user/message; edit/withdraw work identically).
|
|
95
|
+
const entryInject = (sessionId) => {
|
|
96
|
+
const actx = ctx.sessions.scope(sessionId);
|
|
97
|
+
if (actx === undefined)
|
|
98
|
+
throw new Error(`message-tools: session "${sessionId}" resolved no scope`);
|
|
99
|
+
const conversation = actx.get('conversation');
|
|
100
|
+
if (conversation === undefined)
|
|
101
|
+
throw new Error('message-tools: conversation service unavailable');
|
|
102
|
+
// The model chip rides ui-model-selection's shared per-session
|
|
103
|
+
// directory — an optional service (ctx.get, never an inject edge, so a
|
|
104
|
+
// composition without it degrades to no chip instead of a loader wait).
|
|
105
|
+
const models = ctx.get('modelDirectories');
|
|
106
|
+
const directory = models === undefined || ctx.sessions.subagentAddress(sessionId) !== undefined
|
|
107
|
+
? undefined
|
|
108
|
+
: models.directoryFor(sessionId);
|
|
109
|
+
const withdraw = async (targetSeq) => {
|
|
110
|
+
const carried = await remote.withdraw({ sessionId, targetSeq });
|
|
111
|
+
if (!carried.ok)
|
|
112
|
+
throw new Error(`messageTools.withdraw transport: ${carried.error.code}`);
|
|
113
|
+
if (!carried.value.ok)
|
|
114
|
+
throw new Error(`messageTools.withdraw rejected: ${carried.value.error.code}`);
|
|
115
|
+
};
|
|
116
|
+
const edit = async (targetSeq, text) => {
|
|
117
|
+
const carried = await remote.edit({ sessionId, targetSeq, text });
|
|
118
|
+
if (!carried.ok)
|
|
119
|
+
throw new Error(`messageTools.edit transport: ${carried.error.code}`);
|
|
120
|
+
if (!carried.value.ok)
|
|
121
|
+
throw new Error(`messageTools.edit rejected: ${carried.value.error.code}`);
|
|
122
|
+
};
|
|
123
|
+
// After a cancel, `running` flips false BEFORE the turn's teardown
|
|
124
|
+
// finishes landing (cancelled tool results persist after the flip,
|
|
125
|
+
// turn/end last); a replacement's span must cover all of it, so wait
|
|
126
|
+
// for the turn to actually close. Bounded: a turn that never settles
|
|
127
|
+
// rejects instead of letting the edit/withdraw race the stragglers.
|
|
128
|
+
const waitIdle = () => waitForTurnSettled(() => ctx.sessions.binding(sessionId)?.session.getSnapshot());
|
|
129
|
+
return {
|
|
130
|
+
editMessage: (targetSeq, text) => editInPlace({
|
|
131
|
+
cancel: () => conversation.cancel(),
|
|
132
|
+
waitIdle,
|
|
133
|
+
edit: () => edit(targetSeq, text),
|
|
134
|
+
}, ctx.sessions.binding(sessionId)?.session.getSnapshot().running === true),
|
|
135
|
+
withdrawMessage: targetSeq => withdrawInPlace({
|
|
136
|
+
cancel: () => conversation.cancel(),
|
|
137
|
+
waitIdle,
|
|
138
|
+
withdraw: () => withdraw(targetSeq),
|
|
139
|
+
}, ctx.sessions.binding(sessionId)?.session.getSnapshot().running === true),
|
|
140
|
+
backfillDraft: (text) => { backfill(sessionId, text); },
|
|
141
|
+
modelsAvailable: directory !== undefined,
|
|
142
|
+
loadModels: () => {
|
|
143
|
+
// Load failures surface on the directory store itself.
|
|
144
|
+
directory?.load().catch(() => undefined);
|
|
145
|
+
},
|
|
146
|
+
selectModel: selection => directory === undefined
|
|
147
|
+
? Promise.resolve(false)
|
|
148
|
+
: directory.select(selection).then(() => true, () => false),
|
|
149
|
+
hooks: {
|
|
150
|
+
modelDirectory: directory === undefined ? EMPTY_MODEL_DIRECTORY : directory.store,
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
155
|
+
name: 'conversation.chat.node',
|
|
156
|
+
key: 'user',
|
|
157
|
+
priority: -1,
|
|
158
|
+
locale: NS,
|
|
159
|
+
inject: entryInject,
|
|
160
|
+
}, UserMessageView));
|
|
161
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
162
|
+
name: 'conversation.chat.node',
|
|
163
|
+
key: 'steering',
|
|
164
|
+
priority: -1,
|
|
165
|
+
locale: NS,
|
|
166
|
+
inject: entryInject,
|
|
167
|
+
}, UserMessageView));
|
|
168
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
169
|
+
name: 'conversation.chat.node',
|
|
170
|
+
key: 'message-tools-edited',
|
|
171
|
+
locale: NS,
|
|
172
|
+
inject: entryInject,
|
|
173
|
+
}, UserMessageView));
|
|
174
|
+
// Restored user-message replays get the same full renderer (bubble plus the
|
|
175
|
+
// copy/edit/withdraw action row): a restored row edits and withdraws like
|
|
176
|
+
// the message it replays.
|
|
177
|
+
ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
|
|
178
|
+
name: 'conversation.chat.node',
|
|
179
|
+
key: 'message-tools-restored',
|
|
180
|
+
locale: NS,
|
|
181
|
+
inject: entryInject,
|
|
182
|
+
}, UserMessageView));
|
|
183
|
+
return async () => {
|
|
184
|
+
for (const dispose of disposers.reverse())
|
|
185
|
+
await dispose();
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** `message-tools` namespace dictionaries. */
|
|
2
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
|
+
export declare const zh: {
|
|
4
|
+
'action.withdraw': string;
|
|
5
|
+
'editor.aria': string;
|
|
6
|
+
'editor.placeholder': string;
|
|
7
|
+
'editor.save': string;
|
|
8
|
+
'withdraw.title': string;
|
|
9
|
+
'withdraw.description': string;
|
|
10
|
+
'withdraw.acknowledge': string;
|
|
11
|
+
'withdraw.confirm': string;
|
|
12
|
+
'withdrawn.divider': string;
|
|
13
|
+
'withdrawn.expand': string;
|
|
14
|
+
'withdrawn.empty': string;
|
|
15
|
+
'withdrawn.entryUser': string;
|
|
16
|
+
'withdrawn.entryAssistant': string;
|
|
17
|
+
'withdrawn.restore': string;
|
|
18
|
+
'withdrawn.backfilled': string;
|
|
19
|
+
'withdrawn.restored': string;
|
|
20
|
+
'restored.assistant': string;
|
|
21
|
+
'withdrawn.restoreFailed': string;
|
|
22
|
+
'error.edit': string;
|
|
23
|
+
'edited.badge': string;
|
|
24
|
+
'error.withdraw': string;
|
|
25
|
+
'model.aria': string;
|
|
26
|
+
'model.menuAria': string;
|
|
27
|
+
'model.effort': string;
|
|
28
|
+
'model.providerDefault': string;
|
|
29
|
+
'model.empty': string;
|
|
30
|
+
'model.selectFailed': string;
|
|
31
|
+
'message.extraBlock': string;
|
|
32
|
+
'json.truncated': string;
|
|
33
|
+
};
|
|
34
|
+
/** The message-tools namespace key union. */
|
|
35
|
+
export type MessageToolsKey = keyof typeof zh;
|
|
36
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
37
|
+
interface LocaleNamespaceMap {
|
|
38
|
+
/** The user-message edit/withdraw controls' copy. */
|
|
39
|
+
'message-tools': MessageToolsKey;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
43
|
+
export declare const en: {
|
|
44
|
+
'action.withdraw': string;
|
|
45
|
+
'editor.aria': string;
|
|
46
|
+
'editor.placeholder': string;
|
|
47
|
+
'editor.save': string;
|
|
48
|
+
'withdraw.title': string;
|
|
49
|
+
'withdraw.description': string;
|
|
50
|
+
'withdraw.acknowledge': string;
|
|
51
|
+
'withdraw.confirm': string;
|
|
52
|
+
'withdrawn.divider': string;
|
|
53
|
+
'withdrawn.expand': string;
|
|
54
|
+
'withdrawn.empty': string;
|
|
55
|
+
'withdrawn.entryUser': string;
|
|
56
|
+
'withdrawn.entryAssistant': string;
|
|
57
|
+
'withdrawn.restore': string;
|
|
58
|
+
'withdrawn.backfilled': string;
|
|
59
|
+
'withdrawn.restored': string;
|
|
60
|
+
'restored.assistant': string;
|
|
61
|
+
'withdrawn.restoreFailed': string;
|
|
62
|
+
'error.edit': string;
|
|
63
|
+
'edited.badge': string;
|
|
64
|
+
'error.withdraw': string;
|
|
65
|
+
'model.aria': string;
|
|
66
|
+
'model.menuAria': string;
|
|
67
|
+
'model.effort': string;
|
|
68
|
+
'model.providerDefault': string;
|
|
69
|
+
'model.empty': string;
|
|
70
|
+
'model.selectFailed': string;
|
|
71
|
+
'message.extraBlock': string;
|
|
72
|
+
'json.truncated': string;
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAE9C,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BmB,CAAA;AAElC,6CAA6C;AAC7C,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,EAAE,CAAA;AAE7C,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,qDAAqD;QACrD,eAAe,EAAE,eAAe,CAAA;KACjC;CACF;AAED,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B4B,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** `message-tools` namespace dictionaries. */
|
|
2
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
|
+
export const zh = {
|
|
4
|
+
'action.withdraw': '撤回',
|
|
5
|
+
'editor.aria': '编辑消息',
|
|
6
|
+
'editor.placeholder': '编辑消息…',
|
|
7
|
+
'editor.save': '保存并重新发送',
|
|
8
|
+
'withdraw.title': '撤回这条消息?',
|
|
9
|
+
'withdraw.description': '撤回后,这条消息及其后的所有内容都会对模型隐藏;原文会回填到输入框,可再从撤回分隔线恢复到对话末尾。',
|
|
10
|
+
'withdraw.acknowledge': '我已了解撤回的影响',
|
|
11
|
+
'withdraw.confirm': '确认撤回',
|
|
12
|
+
'withdrawn.divider': '已撤回 {count} 条消息',
|
|
13
|
+
'withdrawn.expand': '展开查看撤回内容',
|
|
14
|
+
'withdrawn.empty': '撤回的内容不在当前已加载的历史中',
|
|
15
|
+
'withdrawn.entryUser': '用户消息原文',
|
|
16
|
+
'withdrawn.entryAssistant': '助手回复摘要',
|
|
17
|
+
'withdrawn.restore': '恢复到对话末尾',
|
|
18
|
+
'withdrawn.backfilled': '已回填到输入框,可编辑后发送',
|
|
19
|
+
'withdrawn.restored': '已恢复',
|
|
20
|
+
'restored.assistant': '已恢复 · 助手回复',
|
|
21
|
+
'withdrawn.restoreFailed': '恢复失败,请重试',
|
|
22
|
+
'error.edit': '编辑失败,请重试',
|
|
23
|
+
'edited.badge': '已编辑',
|
|
24
|
+
'error.withdraw': '撤回失败,请重试',
|
|
25
|
+
'model.aria': '切换模型,当前 {model}',
|
|
26
|
+
'model.menuAria': '模型与推理等级',
|
|
27
|
+
'model.effort': '推理等级',
|
|
28
|
+
'model.providerDefault': '默认',
|
|
29
|
+
'model.empty': '暂无可用模型',
|
|
30
|
+
'model.selectFailed': '模型切换失败',
|
|
31
|
+
'message.extraBlock': '附加内容块',
|
|
32
|
+
'json.truncated': '… 已截断,共 {total} 字符',
|
|
33
|
+
};
|
|
34
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
35
|
+
export const en = {
|
|
36
|
+
'action.withdraw': 'Withdraw',
|
|
37
|
+
'editor.aria': 'Edit message',
|
|
38
|
+
'editor.placeholder': 'Edit the message…',
|
|
39
|
+
'editor.save': 'Save and resend',
|
|
40
|
+
'withdraw.title': 'Withdraw this message?',
|
|
41
|
+
'withdraw.description': 'Once withdrawn, this message and everything after it will be hidden from the model; its text is backfilled into the composer, and it can be restored to the conversation tail from the withdrawal divider.',
|
|
42
|
+
'withdraw.acknowledge': 'I understand the effect of withdrawal',
|
|
43
|
+
'withdraw.confirm': 'Confirm withdrawal',
|
|
44
|
+
'withdrawn.divider': 'Withdrawn {count} messages',
|
|
45
|
+
'withdrawn.expand': 'Expand the withdrawn content',
|
|
46
|
+
'withdrawn.empty': 'The withdrawn content is outside the loaded history',
|
|
47
|
+
'withdrawn.entryUser': 'Original user message',
|
|
48
|
+
'withdrawn.entryAssistant': 'Assistant reply summary',
|
|
49
|
+
'withdrawn.restore': 'Restore to the end of the conversation',
|
|
50
|
+
'withdrawn.backfilled': 'Backfilled into the composer; edit and send',
|
|
51
|
+
'withdrawn.restored': 'Restored',
|
|
52
|
+
'restored.assistant': 'Restored · assistant reply',
|
|
53
|
+
'withdrawn.restoreFailed': 'Could not restore; please retry',
|
|
54
|
+
'error.edit': 'Could not apply the edit; please retry',
|
|
55
|
+
'edited.badge': 'Edited',
|
|
56
|
+
'error.withdraw': 'Could not withdraw the message; please retry',
|
|
57
|
+
'model.aria': 'Switch model, current {model}',
|
|
58
|
+
'model.menuAria': 'Model and reasoning effort',
|
|
59
|
+
'model.effort': 'Reasoning effort',
|
|
60
|
+
'model.providerDefault': 'Provider default',
|
|
61
|
+
'model.empty': 'No models available',
|
|
62
|
+
'model.selectFailed': 'Could not switch model',
|
|
63
|
+
'message.extraBlock': 'Extra content block',
|
|
64
|
+
'json.truncated': '… truncated, {total} characters total',
|
|
65
|
+
};
|