@morlay/ui-conversation-message-actions 0.0.11 → 0.0.13

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.
Files changed (42) hide show
  1. package/README.md +0 -6
  2. package/dist/client.js +831 -0
  3. package/dist/index.d.mts +105 -0
  4. package/dist/index.mjs +3 -0
  5. package/dist/invariant.d.mts +7 -0
  6. package/dist/invariant.mjs +8 -0
  7. package/dist/plan-Bw4zoI4N.d.mts +86 -0
  8. package/dist/plan.d.mts +2 -0
  9. package/dist/plan.mjs +225 -0
  10. package/dist/src-CXYW0Bc0.mjs +408 -0
  11. package/dist/testing.d.mts +707 -0
  12. package/dist/testing.mjs +23690 -0
  13. package/package.json +60 -31
  14. package/src/client/chat-node/MessageEditDialog.module.css +24 -0
  15. package/src/client/chat-node/MessageEditDialog.tsx +71 -0
  16. package/src/client/chat-node/MessageIconActions.module.css +86 -0
  17. package/src/client/chat-node/MessageIconActions.tsx +189 -0
  18. package/src/client/chat-node/MessageItem.module.css +290 -0
  19. package/src/client/chat-node/MessageItem.tsx +198 -0
  20. package/src/client/chat-node/message-chrome.ts +61 -0
  21. package/src/client/chat-node/register.ts +33 -0
  22. package/src/client/chat-node/use-calendar-day.ts +23 -0
  23. package/src/client/controller.ts +327 -0
  24. package/src/client/css-modules.d.ts +4 -0
  25. package/src/client/import-action.tsx +74 -0
  26. package/src/client/index.ts +50 -0
  27. package/src/index.ts +650 -0
  28. package/src/invariant.ts +13 -0
  29. package/src/plan.ts +322 -0
  30. package/src/shared.ts +167 -0
  31. package/src/testing.ts +151 -0
  32. package/src/types.ts +73 -0
  33. package/lib/client.js +0 -2243
  34. package/lib/client.js.map +0 -1
  35. package/lib/index.d.mts +0 -211
  36. package/lib/index.d.mts.map +0 -1
  37. package/lib/index.mjs +0 -685
  38. package/lib/index.mjs.map +0 -1
  39. package/lib/invariant.d.mts +0 -15
  40. package/lib/invariant.d.mts.map +0 -1
  41. package/lib/invariant.mjs +0 -22
  42. package/lib/invariant.mjs.map +0 -1
package/lib/client.js DELETED
@@ -1,2243 +0,0 @@
1
- window.__ModuleLoader__.load({
2
- id: "@morlay/ui-conversation-message-actions",
3
- factory: (require) => {
4
- var module = { exports: {} };
5
- var exports = module.exports;
6
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
- let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
8
- require("@deepseek-ai/dsh-client-ui-conversation/client");
9
- let react = require("react");
10
- let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
11
- let react_jsx_runtime = require("react/jsx-runtime");
12
- //#region src/shared.ts
13
- /** 同源 HTTP 端点(host 注册、client fetch)。 */
14
- const SESSION_EDITOR_PATH = "/session-editor";
15
- //#endregion
16
- //#region src/client/controller.ts
17
- function messageOf(error) {
18
- return error instanceof Error ? error.message : String(error);
19
- }
20
- function conversationRevision(snapshot) {
21
- const turnEnds = [...snapshot.turnEnds.entries()].map(([turn, seq]) => `${String(turn)}:${String(seq)}`).join(",");
22
- return [
23
- snapshot.openState,
24
- snapshot.removed,
25
- snapshot.hasMore,
26
- turnEnds
27
- ].join("|");
28
- }
29
- /** 一个会话共享一个稳定 controller(header + timeline 两个入口复用)。 */
30
- var SessionEditorController = class {
31
- sessionId;
32
- store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)({
33
- status: "idle",
34
- error: null,
35
- pending: null,
36
- timeline: null,
37
- sessionOpenError: false
38
- });
39
- face;
40
- ctx;
41
- sessions;
42
- sessionSource;
43
- sessionSourceDispose;
44
- sessionRevision;
45
- disposed = false;
46
- users = 0;
47
- navigationWaits = /* @__PURE__ */ new Set();
48
- constructor(ctx, sessionId) {
49
- this.sessionId = sessionId;
50
- this.ctx = ctx;
51
- this.sessions = ctx.get("sessions");
52
- this.face = {
53
- hooks: { sessionEditor: this.store },
54
- acquire: () => {
55
- this.users += 1;
56
- if (this.users === 1 && this.disposed) this.revive();
57
- return () => this.release();
58
- },
59
- load: () => {
60
- this.load();
61
- },
62
- edit: (message, text, cascade) => this.mutate({
63
- action: "edit",
64
- sessionId: this.sessionId,
65
- eventSeq: message.eventSeq,
66
- blockIndex: message.blockIndex,
67
- text,
68
- cascade
69
- }),
70
- retry: (turn, cascade) => this.mutate({
71
- action: "retry",
72
- sessionId: this.sessionId,
73
- turn,
74
- cascade
75
- }),
76
- reroll: () => this.mutate({
77
- action: "reroll",
78
- sessionId: this.sessionId
79
- }),
80
- rewind: (toBoundary) => this.mutate({
81
- action: "rewind",
82
- sessionId: this.sessionId,
83
- toBoundary
84
- }),
85
- cleanse: () => this.mutate({
86
- action: "cleanse",
87
- sessionId: this.sessionId
88
- }),
89
- openVersion: (sessionId) => this.openWhenListed(sessionId)
90
- };
91
- this.observe();
92
- }
93
- observe() {
94
- this.sessionSource = void 0;
95
- this.sessionSourceDispose?.();
96
- this.bindSessionSource();
97
- this.sessions.list.subscribe(() => this.invalidate());
98
- }
99
- bindSessionSource() {
100
- const source = this.sessions.binding(this.sessionId)?.session;
101
- if (source === this.sessionSource) return;
102
- this.sessionSourceDispose?.();
103
- this.sessionSource = source;
104
- this.sessionRevision = source === void 0 ? void 0 : conversationRevision(source.getSnapshot());
105
- this.store.update((state) => {
106
- state.sessionOpenError = source?.getSnapshot().openState === "error";
107
- });
108
- this.sessionSourceDispose = source?.subscribe(() => {
109
- this.store.update((state) => {
110
- state.sessionOpenError = source.getSnapshot().openState === "error";
111
- });
112
- this.invalidate();
113
- });
114
- }
115
- invalidate() {
116
- if (this.disposed || this.store.getSnapshot().status === "idle") return;
117
- this.load();
118
- }
119
- release() {
120
- this.users -= 1;
121
- if (this.users <= 0) this.dispose();
122
- }
123
- dispose() {
124
- if (this.disposed) return;
125
- this.disposed = true;
126
- this.sessionSourceDispose?.();
127
- this.sessionSourceDispose = void 0;
128
- this.sessionSource = void 0;
129
- this.sessionRevision = void 0;
130
- }
131
- revive() {
132
- this.disposed = false;
133
- this.observe();
134
- this.load();
135
- }
136
- async load() {
137
- if (this.disposed) return;
138
- this.store.update((state) => {
139
- state.status = "loading";
140
- state.error = null;
141
- });
142
- try {
143
- const response = await fetch(`${SESSION_EDITOR_PATH}?sessionId=${encodeURIComponent(this.sessionId)}`, {
144
- method: "GET",
145
- headers: { accept: "application/json" },
146
- cache: "no-store"
147
- });
148
- const value = await response.json();
149
- if (this.disposed) return;
150
- if (response.ok) this.store.update((state) => {
151
- state.status = "ready";
152
- state.error = null;
153
- state.timeline = value;
154
- });
155
- else {
156
- const error = value["error"];
157
- this.store.update((state) => {
158
- state.status = "error";
159
- state.error = typeof error === "string" ? error : `请求失败:HTTP ${String(response.status)}`;
160
- });
161
- }
162
- } catch (error) {
163
- if (this.disposed) return;
164
- this.store.update((state) => {
165
- state.status = "error";
166
- state.error = messageOf(error);
167
- });
168
- }
169
- }
170
- async mutate(operation) {
171
- if (this.store.getSnapshot().pending !== null) return false;
172
- this.store.update((state) => {
173
- state.pending = operation.action;
174
- state.error = null;
175
- });
176
- try {
177
- const response = await fetch(SESSION_EDITOR_PATH, {
178
- method: "POST",
179
- headers: {
180
- accept: "application/json",
181
- "content-type": "application/json"
182
- },
183
- body: JSON.stringify(operation)
184
- });
185
- const value = await response.json();
186
- if (this.disposed) return true;
187
- if (!response.ok) {
188
- const error = value["error"];
189
- throw new Error(typeof error === "string" ? error : `请求失败:HTTP ${String(response.status)}`);
190
- }
191
- this.store.update((state) => {
192
- state.pending = null;
193
- });
194
- const result = value;
195
- if (String(result.sessionId) !== String(this.sessionId)) {
196
- await this.openWhenListed(result.sessionId);
197
- return true;
198
- }
199
- const face = this.sessions.binding(this.sessionId)?.session;
200
- const resync = face.resync;
201
- if (resync !== void 0) try {
202
- await resync.call(face);
203
- this.load();
204
- return true;
205
- } catch {}
206
- location.reload();
207
- return true;
208
- } catch (error) {
209
- if (this.disposed) return false;
210
- this.store.update((state) => {
211
- state.pending = null;
212
- state.error = messageOf(error);
213
- });
214
- return false;
215
- }
216
- }
217
- /** 会话列表发布是导航的反应式依赖:等新版本出现在列表后再打开。 */
218
- openWhenListed(sessionId) {
219
- if (this.sessions.list.getSnapshot().byId[sessionId] !== void 0) {
220
- this.sessions.open(sessionId);
221
- return Promise.resolve();
222
- }
223
- return new Promise((resolve) => {
224
- let settled = false;
225
- let dispose = () => {};
226
- const finish = (open) => {
227
- if (settled) return;
228
- settled = true;
229
- dispose();
230
- this.navigationWaits.delete(cancel);
231
- if (open) this.sessions.open(sessionId);
232
- resolve();
233
- };
234
- const cancel = () => {
235
- finish(false);
236
- };
237
- this.navigationWaits.add(cancel);
238
- dispose = this.sessions.list.subscribe(() => {
239
- if (this.sessions.list.getSnapshot().byId[sessionId] === void 0) return;
240
- finish(true);
241
- });
242
- if (this.sessions.list.getSnapshot().byId[sessionId] !== void 0) finish(true);
243
- });
244
- }
245
- };
246
- //#endregion
247
- //#region src/client/chat-node/use-throttled-visual-update.ts
248
- /** Frame-throttled scheduling for non-essential visual alignment. */
249
- const DEFAULT_INTERVAL_FRAMES = 3;
250
- /**
251
- * Return a stable scheduler that coalesces visual updates over a frame interval.
252
- * @param update - DOM alignment to run after the throttle interval.
253
- * @param intervalFrames - frames to wait before applying the latest alignment.
254
- * @returns a stable function that schedules the latest update.
255
- */
256
- function useThrottledVisualUpdate(update, intervalFrames = DEFAULT_INTERVAL_FRAMES) {
257
- const updateRef = (0, react.useRef)(update);
258
- updateRef.current = update;
259
- const pendingFrameRef = (0, react.useRef)(null);
260
- (0, react.useLayoutEffect)(() => () => {
261
- if (pendingFrameRef.current === null) return;
262
- cancelAnimationFrame(pendingFrameRef.current);
263
- pendingFrameRef.current = null;
264
- }, []);
265
- return (0, react.useCallback)(() => {
266
- if (pendingFrameRef.current !== null) return;
267
- let remainingFrames = intervalFrames;
268
- const advance = () => {
269
- remainingFrames -= 1;
270
- if (remainingFrames > 0) {
271
- pendingFrameRef.current = requestAnimationFrame(advance);
272
- return;
273
- }
274
- pendingFrameRef.current = null;
275
- updateRef.current();
276
- };
277
- pendingFrameRef.current = requestAnimationFrame(advance);
278
- }, [intervalFrames]);
279
- }
280
- //#endregion
281
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/accessibility.module.css.mjs
282
- const css$10 = ".QUD6rG_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
283
- const tagId$10 = "@morlay/ui-conversation-message-actions/accessibility.module.css";
284
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$10) + "]") === null) {
285
- const tag = document.createElement("style");
286
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
287
- tag.dataset.pluginCss = tagId$10;
288
- tag.textContent = css$10;
289
- document.head.appendChild(tag);
290
- }
291
- var accessibility_module_css_default = { "visuallyHidden": "QUD6rG_visuallyHidden" };
292
- //#endregion
293
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/ReasoningRow.module.css.mjs
294
- const css$9 = ".puL2qa_root{flex-direction:column;display:flex}.puL2qa_row{position:relative;overflow:hidden}.puL2qa_root[data-state=running] .puL2qa_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite puL2qa_dsh-reasoning-row-sweep;position:absolute;left:0}@keyframes puL2qa_dsh-reasoning-row-sweep{0%{left:-300px}90%,to{left:100%}}.puL2qa_leading{flex-shrink:0}.puL2qa_chevron{color:var(--dsw-alias-label-secondary)}.puL2qa_title{font-weight:400}.puL2qa_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.puL2qa_summary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.puL2qa_summary[data-follow-end]{text-overflow:clip}.puL2qa_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}@media (prefers-reduced-motion:reduce){.puL2qa_root[data-state=running] .puL2qa_row:after{animation:none}}";
295
- const tagId$9 = "@morlay/ui-conversation-message-actions/ReasoningRow.module.css";
296
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$9) + "]") === null) {
297
- const tag = document.createElement("style");
298
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
299
- tag.dataset.pluginCss = tagId$9;
300
- tag.textContent = css$9;
301
- document.head.appendChild(tag);
302
- }
303
- var ReasoningRow_module_css_default = {
304
- "thinkBody": "puL2qa_thinkBody",
305
- "root": "puL2qa_root",
306
- "title": "puL2qa_title",
307
- "dsh-reasoning-row-sweep": "puL2qa_dsh-reasoning-row-sweep",
308
- "row": "puL2qa_row",
309
- "separator": "puL2qa_separator",
310
- "summary": "puL2qa_summary",
311
- "leading": "puL2qa_leading",
312
- "chevron": "puL2qa_chevron"
313
- };
314
- //#endregion
315
- //#region src/client/chat-node/ReasoningRow.tsx
316
- /** Assistant reasoning disclosure, independent of Tool-call presentation. */
317
- function firstLine(text) {
318
- const newline = text.indexOf("\n");
319
- return newline === -1 ? text : text.slice(0, newline);
320
- }
321
- function latestLine(text) {
322
- const visible = text.trimEnd();
323
- const newline = visible.lastIndexOf("\n");
324
- return newline === -1 ? visible : visible.slice(newline + 1);
325
- }
326
- /**
327
- * Render one assistant reasoning block as the Think disclosure row.
328
- * @param props.text - complete or streaming reasoning text.
329
- * @param props.running - whether this block is the streaming tail.
330
- * @param props.t - conversation locale seat for the running status.
331
- * @returns the reasoning disclosure.
332
- */
333
- function ReasoningRow({ text, running, t }) {
334
- const [expanded, setExpanded] = (0, react.useState)(false);
335
- const summaryRef = (0, react.useRef)(null);
336
- const summary = running ? latestLine(text) : firstLine(text);
337
- const scheduleSummaryScroll = useThrottledVisualUpdate(() => {
338
- const element = summaryRef.current;
339
- if (element === null) return;
340
- element.scrollLeft = running ? element.scrollWidth - element.clientWidth : 0;
341
- });
342
- (0, react.useEffect)(() => {
343
- scheduleSummaryScroll();
344
- }, [
345
- running,
346
- scheduleSummaryScroll,
347
- summary
348
- ]);
349
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
350
- className: ReasoningRow_module_css_default.root,
351
- "data-variant": "think",
352
- "data-state": running ? "running" : "ok",
353
- children: [running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
354
- className: accessibility_module_css_default.visuallyHidden,
355
- children: t("row.running")
356
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
357
- rowClassName: ReasoningRow_module_css_default.row,
358
- leadingClassName: ReasoningRow_module_css_default.leading,
359
- titleClassName: ReasoningRow_module_css_default.title,
360
- chevronClassName: ReasoningRow_module_css_default.chevron,
361
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, { size: 14 }),
362
- title: "Think",
363
- open: expanded,
364
- expandable: true,
365
- expandOnRowClick: true,
366
- onToggle: () => {
367
- setExpanded((value) => !value);
368
- },
369
- collapsedContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
370
- className: ReasoningRow_module_css_default.separator,
371
- "aria-hidden": true
372
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
373
- ref: summaryRef,
374
- className: ReasoningRow_module_css_default.summary,
375
- "data-follow-end": running || void 0,
376
- children: summary
377
- })] }),
378
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
379
- className: ReasoningRow_module_css_default.thinkBody,
380
- children: text
381
- })
382
- })]
383
- });
384
- }
385
- //#endregion
386
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/AssistantMarkdown.module.css.mjs
387
- const css$8 = ".KOyxmq_root{color:var(--dsw-alias-label-primary);flex-direction:column;font-size:16px;line-height:28px;display:flex}.KOyxmq_body{flex-direction:column;gap:16px;display:flex}.KOyxmq_stopped{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-tertiary);border-radius:6px;align-self:flex-start;padding:0 6px;font-size:11px;line-height:18px}.KOyxmq_actions{margin-top:16px;margin-left:-6px}";
388
- const tagId$8 = "@morlay/ui-conversation-message-actions/AssistantMarkdown.module.css";
389
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$8) + "]") === null) {
390
- const tag = document.createElement("style");
391
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
392
- tag.dataset.pluginCss = tagId$8;
393
- tag.textContent = css$8;
394
- document.head.appendChild(tag);
395
- }
396
- var AssistantMarkdown_module_css_default = {
397
- "stopped": "KOyxmq_stopped",
398
- "root": "KOyxmq_root",
399
- "actions": "KOyxmq_actions",
400
- "body": "KOyxmq_body"
401
- };
402
- //#endregion
403
- //#region src/client/chat-node/AssistantMarkdown.tsx
404
- /** Reasoning block as the Think variant summary row (figma 39:28304). */
405
- const AssistantMarkdown = (0, react.memo)(function AssistantMarkdown({ blocks, streaming, interrupted, renderMessageImages, mentions, t }) {
406
- const codeLabels = (0, react.useMemo)(() => ({
407
- copyLabel: t("copy"),
408
- copiedLabel: t("copied")
409
- }), [t]);
410
- const last = blocks.length - 1;
411
- if (!(streaming || interrupted === true || blocks.some((block) => block.kind !== "tool-call"))) return null;
412
- const rendered = [];
413
- for (let i = 0; i < blocks.length; i++) {
414
- const block = blocks[i];
415
- if (block === void 0) continue;
416
- switch (block.kind) {
417
- case "text":
418
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
419
- text: block.text,
420
- streaming,
421
- codeLabels,
422
- fileMentions: mentions
423
- }, i));
424
- break;
425
- case "reasoning":
426
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReasoningRow, {
427
- text: block.text,
428
- running: streaming && i === last,
429
- t
430
- }, i));
431
- break;
432
- case "image": {
433
- const start = i;
434
- const group = [block];
435
- while (i + 1 < blocks.length) {
436
- const next = blocks[i + 1];
437
- if (next === void 0 || next.kind !== "image") break;
438
- group.push(next);
439
- i += 1;
440
- }
441
- rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderMessageImages({
442
- images: group.map(({ attachment }) => ({ attachment })),
443
- align: "start"
444
- }) }, start));
445
- break;
446
- }
447
- case "tool-call": break;
448
- default: rendered.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
449
- label: t("message.unknownBlock"),
450
- payload: block.block,
451
- truncatedLabel: (total) => t("json.truncated", { total })
452
- }, i));
453
- }
454
- }
455
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
456
- className: AssistantMarkdown_module_css_default.root,
457
- "data-streaming": streaming || void 0,
458
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
459
- className: AssistantMarkdown_module_css_default.body,
460
- children: [rendered, interrupted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
461
- className: AssistantMarkdown_module_css_default.stopped,
462
- children: t("message.stopped")
463
- })]
464
- })
465
- });
466
- });
467
- //#endregion
468
- //#region src/client/chat-node/AssistantNodeView.tsx
469
- /** Streaming, settled, and interrupted Assistant states share one keyed renderer instance. */
470
- const AssistantNodeView = (0, react.memo)(function AssistantNodeView({ node, useTurnData, openFile, renderMessageImages, fileMentions, t }) {
471
- const data = node.data;
472
- const turn = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
473
- const tail = useTurnData("turn-tail");
474
- const owner = (0, react.useMemo)(() => {
475
- if (turn?.status !== "closed" || data.finalNode === void 0) return void 0;
476
- if (tail?.closing?.finalNode.seq !== data.finalNode.seq) return void 0;
477
- return {
478
- turn,
479
- seq: data.finalNode.seq,
480
- openFile
481
- };
482
- }, [
483
- data.finalNode,
484
- openFile,
485
- tail,
486
- turn
487
- ]);
488
- const mentions = (0, react.useMemo)(() => owner === void 0 ? void 0 : fileMentions(owner), [fileMentions, owner]);
489
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AssistantMarkdown, {
490
- blocks: data.blocks,
491
- streaming: data.status === "running",
492
- interrupted: data.status === "interrupted",
493
- renderMessageImages,
494
- mentions,
495
- t
496
- });
497
- });
498
- //#endregion
499
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/MessageItem.module.css.mjs
500
- const css$7 = ".okW0Ua_userRow{flex-direction:column;align-items:flex-end;gap:6px;display:flex}.okW0Ua_userStack{flex-direction:column;align-items:flex-end;gap:8px;min-width:0;max-width:min(525px,82%);display:flex}.okW0Ua_bubble{background:var(--dsw-specific-bubble);max-width:100%;color:var(--dsw-alias-label-primary);border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px}.okW0Ua_contextRow,.okW0Ua_compactionRow{padding:2px 0}.okW0Ua_compactionButton{width:100%;min-width:0;height:24px;color:inherit;font:inherit;text-align:left;background:0 0;border:none;border-radius:6px;align-items:center;padding:0;display:flex}.okW0Ua_compactionButton:not(:disabled){cursor:pointer}.okW0Ua_compactionButton:not(:disabled):hover{background:var(--dsw-alias-interactive-bg-hover)}.okW0Ua_compactionLeading{width:16px;height:16px;color:var(--dsw-alias-label-secondary);flex:none;place-items:center;margin-right:6px;display:inline-grid}.okW0Ua_compactionContextIcon,.okW0Ua_compactionDisclosureIcon{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.okW0Ua_compactionDisclosureIcon,.okW0Ua_compactionButton:not(:disabled):hover .okW0Ua_compactionContextIcon,.okW0Ua_compactionButton:not(:disabled):focus-visible .okW0Ua_compactionContextIcon{opacity:0}.okW0Ua_compactionButton:not(:disabled):hover .okW0Ua_compactionDisclosureIcon,.okW0Ua_compactionButton:not(:disabled):focus-visible .okW0Ua_compactionDisclosureIcon{opacity:1}.okW0Ua_compactionTitle{color:var(--dsw-alias-label-primary-dimmed);flex:none;font-size:14px;line-height:24px}.okW0Ua_compactionSep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.okW0Ua_compactionSummary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.okW0Ua_compactionBody{color:var(--dsw-alias-label-tertiary);padding:4px 0 4px 22px;font-size:14px;line-height:24px}.okW0Ua_retryRow{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.okW0Ua_retrySummary{width:fit-content;color:inherit;cursor:pointer;user-select:none;border-radius:3px;align-items:center;gap:7px;padding:2px 0;list-style:none;display:inline-flex}.okW0Ua_retrySummary::-webkit-details-marker{display:none}.okW0Ua_retrySummary:after{content:\"\";opacity:.8;border-bottom:1.5px solid;border-right:1.5px solid;width:6px;height:6px;transition:transform .12s;transform:rotate(-45deg)}.okW0Ua_retrySummary:hover{color:var(--dsw-alias-label-secondary)}.okW0Ua_retrySummary:focus-visible{outline:1.5px solid var(--dsw-alias-button-info-fill);outline-offset:2px}.okW0Ua_retryText{color:inherit}.okW0Ua_retryRow[data-active] .okW0Ua_retryText{background:linear-gradient(90deg, var(--dsw-alias-label-tertiary) 0%, var(--dsw-alias-label-tertiary) 40%, var(--dsw-alias-label-secondary) 50%, var(--dsw-alias-label-tertiary) 60%, var(--dsw-alias-label-tertiary) 100%);color:#0000;background-position:100%;background-size:200% 100%;background-clip:text;animation:1.6s ease-in-out infinite okW0Ua_retry-shimmer}.okW0Ua_retryRow[open] .okW0Ua_retrySummary:after{transform:rotate(45deg)}.okW0Ua_retryDetails{overflow-wrap:anywhere;gap:2px;margin-top:3px;padding-left:14px;font-size:12px;line-height:18px;display:grid}.okW0Ua_retryDetailLabel{color:var(--dsw-alias-label-secondary)}.okW0Ua_turnErrorRow{grid-template-columns:10px minmax(0,1fr) auto;align-items:start;gap:8px;padding:2px 0;font-size:13px;line-height:20px;display:grid}.okW0Ua_turnErrorDot{margin-top:5px}.okW0Ua_turnErrorCopy{overflow-wrap:anywhere;min-width:0}.okW0Ua_turnErrorTitle{color:var(--dsw-alias-state-error-primary);margin-right:6px;font-weight:600}.okW0Ua_turnErrorMessage{color:var(--dsw-alias-label-secondary)}.okW0Ua_turnErrorCode{color:var(--dsw-alias-label-tertiary);font:var(--dsw-font-markdown-code-block-small)}.okW0Ua_maxTokensTitle{color:var(--dsw-alias-state-warn-primary);margin-right:6px;font-weight:600}@keyframes okW0Ua_retry-shimmer{0%{background-position:100%}to{background-position:0}}@media (prefers-reduced-motion:reduce){.okW0Ua_retryRow[data-active] .okW0Ua_retryText{color:inherit;background:0 0;animation:none}}.okW0Ua_refChip{color:var(--dsw-alias-label-primary);white-space:nowrap;vertical-align:baseline;background:#6187d838;border-radius:6px;margin:0 2px;padding:0 8px;font-size:.85em;line-height:1.6;display:inline-block}.okW0Ua_confirmActions{justify-content:flex-end;gap:8px;display:flex}";
501
- const tagId$7 = "@morlay/ui-conversation-message-actions/MessageItem.module.css";
502
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$7) + "]") === null) {
503
- const tag = document.createElement("style");
504
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
505
- tag.dataset.pluginCss = tagId$7;
506
- tag.textContent = css$7;
507
- document.head.appendChild(tag);
508
- }
509
- var MessageItem_module_css_default = {
510
- "userStack": "okW0Ua_userStack",
511
- "contextRow": "okW0Ua_contextRow",
512
- "retryRow": "okW0Ua_retryRow",
513
- "retrySummary": "okW0Ua_retrySummary",
514
- "turnErrorMessage": "okW0Ua_turnErrorMessage",
515
- "maxTokensTitle": "okW0Ua_maxTokensTitle",
516
- "compactionBody": "okW0Ua_compactionBody",
517
- "retryDetails": "okW0Ua_retryDetails",
518
- "refChip": "okW0Ua_refChip",
519
- "turnErrorRow": "okW0Ua_turnErrorRow",
520
- "retryDetailLabel": "okW0Ua_retryDetailLabel",
521
- "confirmActions": "okW0Ua_confirmActions",
522
- "userRow": "okW0Ua_userRow",
523
- "compactionRow": "okW0Ua_compactionRow",
524
- "compactionButton": "okW0Ua_compactionButton",
525
- "compactionLeading": "okW0Ua_compactionLeading",
526
- "compactionDisclosureIcon": "okW0Ua_compactionDisclosureIcon",
527
- "compactionTitle": "okW0Ua_compactionTitle",
528
- "compactionSep": "okW0Ua_compactionSep",
529
- "compactionSummary": "okW0Ua_compactionSummary",
530
- "compactionContextIcon": "okW0Ua_compactionContextIcon",
531
- "turnErrorCode": "okW0Ua_turnErrorCode",
532
- "retryText": "okW0Ua_retryText",
533
- "turnErrorDot": "okW0Ua_turnErrorDot",
534
- "turnErrorCopy": "okW0Ua_turnErrorCopy",
535
- "retry-shimmer": "okW0Ua_retry-shimmer",
536
- "bubble": "okW0Ua_bubble",
537
- "turnErrorTitle": "okW0Ua_turnErrorTitle"
538
- };
539
- //#endregion
540
- //#region src/client/chat-node/CompactionItem.tsx
541
- /**
542
- * The collapsed-by-default compaction marker.
543
- * @param props - the marker node off the snapshot cache.
544
- * @returns the marker row, with the summary disclosure when one is available.
545
- */
546
- const CompactionItem = (0, react.memo)(function CompactionItem({ node, title, fallbackSummary, t }) {
547
- const [expanded, setExpanded] = (0, react.useState)(false);
548
- const expandable = node.summary !== null;
549
- const open = expandable && expanded;
550
- const summary = node.shadowedItemCount !== null && node.shadowedTokenCount !== null ? t("message.compaction.completed", {
551
- items: node.shadowedItemCount,
552
- tokens: node.shadowedTokenCount
553
- }) : fallbackSummary ?? (expandable ? t("message.compaction.expand") : t("message.compaction.unavailable"));
554
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
555
- className: MessageItem_module_css_default.compactionRow,
556
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
557
- type: "button",
558
- className: MessageItem_module_css_default.compactionButton,
559
- disabled: !expandable,
560
- "aria-expanded": expandable ? open : void 0,
561
- onClick: () => {
562
- setExpanded((value) => !value);
563
- },
564
- children: [
565
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
566
- className: MessageItem_module_css_default.compactionLeading,
567
- "aria-hidden": true,
568
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
569
- className: MessageItem_module_css_default.compactionContextIcon,
570
- "data-compaction-icon": "context",
571
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, {})
572
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
573
- className: MessageItem_module_css_default.compactionDisclosureIcon,
574
- "data-compaction-disclosure": open ? "expanded" : "collapsed",
575
- children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
576
- })]
577
- }),
578
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
579
- className: MessageItem_module_css_default.compactionTitle,
580
- children: title ?? t("message.compaction")
581
- }),
582
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
583
- className: MessageItem_module_css_default.compactionSep,
584
- "aria-hidden": true
585
- }),
586
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
587
- className: MessageItem_module_css_default.compactionSummary,
588
- children: summary
589
- })
590
- ]
591
- }), open && node.summary !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
592
- className: MessageItem_module_css_default.compactionBody,
593
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { text: node.summary })
594
- })]
595
- });
596
- });
597
- //#endregion
598
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/GenericCommandCard.module.css.mjs
599
- const css$6 = ".XF94Cq_root{flex-direction:column;display:flex}.XF94Cq_row{position:relative;overflow:hidden}.XF94Cq_root[data-state=running] .XF94Cq_row:after{content:\"\";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite XF94Cq_dsh-command-row-sweep;position:absolute;left:0}@keyframes XF94Cq_dsh-command-row-sweep{0%{left:-300px}90%,to{left:100%}}.XF94Cq_leading{flex-shrink:0}.XF94Cq_chevron{color:var(--dsw-alias-label-secondary)}.XF94Cq_title{font-weight:400}.XF94Cq_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.XF94Cq_summary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.XF94Cq_summary[data-error],.XF94Cq_body[data-error]{color:var(--dsw-alias-state-error-primary)}.XF94Cq_body{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-markdown-code-block);max-height:260px;color:var(--dsw-alias-label-primary);font:var(--dsw-font-markdown-code-block-small);white-space:pre-wrap;border-radius:12px;margin:4px 0 4px 4px;padding:12px 16px;overflow:auto}@media (prefers-reduced-motion:reduce){.XF94Cq_root[data-state=running] .XF94Cq_row:after{animation:none}}";
600
- const tagId$6 = "@morlay/ui-conversation-message-actions/GenericCommandCard.module.css";
601
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
602
- const tag = document.createElement("style");
603
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
604
- tag.dataset.pluginCss = tagId$6;
605
- tag.textContent = css$6;
606
- document.head.appendChild(tag);
607
- }
608
- var GenericCommandCard_module_css_default = {
609
- "title": "XF94Cq_title",
610
- "body": "XF94Cq_body",
611
- "root": "XF94Cq_root",
612
- "chevron": "XF94Cq_chevron",
613
- "dsh-command-row-sweep": "XF94Cq_dsh-command-row-sweep",
614
- "row": "XF94Cq_row",
615
- "leading": "XF94Cq_leading",
616
- "separator": "XF94Cq_separator",
617
- "summary": "XF94Cq_summary"
618
- };
619
- //#endregion
620
- //#region src/client/chat-node/GenericCommandCard.tsx
621
- /** Node state → row state semantic (running while unsettled; outcome kind after). */
622
- function stateOf(outcome) {
623
- if (outcome === null) return "running";
624
- return outcome.kind === "error" ? "error" : "ok";
625
- }
626
- function leadingFor(state) {
627
- return state === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "error" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconApiOutline14, { size: 14 });
628
- }
629
- function GenericCommandCard({ node, t, runningSummary }) {
630
- const [expanded, setExpanded] = (0, react.useState)(false);
631
- const text = node.outcome?.text;
632
- const summary = node.outcome === null ? runningSummary ?? t("command.running") : text ?? (node.outcome.kind === "error" ? t("command.failed") : t("command.done"));
633
- const title = node.name ?? t("command.title");
634
- const state = stateOf(node.outcome);
635
- const body = text !== void 0 && text.includes("\n") ? text : null;
636
- const open = expanded && body !== null;
637
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
638
- className: GenericCommandCard_module_css_default.root,
639
- "data-variant": "others",
640
- "data-state": state,
641
- children: [
642
- state === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
643
- className: accessibility_module_css_default.visuallyHidden,
644
- children: t("row.running")
645
- }),
646
- state === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
647
- className: accessibility_module_css_default.visuallyHidden,
648
- children: t("row.failed")
649
- }),
650
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
651
- rowClassName: GenericCommandCard_module_css_default.row,
652
- leadingClassName: GenericCommandCard_module_css_default.leading,
653
- titleClassName: GenericCommandCard_module_css_default.title,
654
- chevronClassName: GenericCommandCard_module_css_default.chevron,
655
- icon: leadingFor(state),
656
- title,
657
- open,
658
- expandable: body !== null,
659
- expandOnRowClick: true,
660
- keepContentWhenOpen: true,
661
- onToggle: () => {
662
- setExpanded((value) => !value);
663
- },
664
- collapsedContent: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
665
- className: GenericCommandCard_module_css_default.separator,
666
- "aria-hidden": true
667
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
668
- className: GenericCommandCard_module_css_default.summary,
669
- "data-error": state === "error" || void 0,
670
- children: summary
671
- })] }),
672
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
673
- className: GenericCommandCard_module_css_default.body,
674
- "data-error": state === "error" || void 0,
675
- children: body
676
- })
677
- })
678
- ]
679
- });
680
- }
681
- //#endregion
682
- //#region src/client/chat-node/CompactionCommandCard.tsx
683
- /** Render one manual compaction lifecycle without duplicating its checkpoint marker. */
684
- function CompactionCommandCard({ node, compaction, t }) {
685
- if (compaction !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactionItem, {
686
- node: compaction,
687
- title: "compact",
688
- fallbackSummary: node.outcome?.text ?? null,
689
- t
690
- });
691
- if (node.outcome !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCommandCard, {
692
- node,
693
- t
694
- });
695
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCommandCard, {
696
- node,
697
- t,
698
- runningSummary: t("message.compaction.running")
699
- });
700
- }
701
- //#endregion
702
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/ChatView.module.css.mjs
703
- const css$5 = "._3JTwCq_callRow{border-radius:6px}";
704
- const tagId$5 = "@morlay/ui-conversation-message-actions/ChatView.module.css";
705
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
706
- const tag = document.createElement("style");
707
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
708
- tag.dataset.pluginCss = tagId$5;
709
- tag.textContent = css$5;
710
- document.head.appendChild(tag);
711
- }
712
- var ChatView_module_css_default = { "callRow": "_3JTwCq_callRow" };
713
- //#endregion
714
- //#region src/client/chat-node/CommandNodeView.tsx
715
- /**
716
- * Ordinary command lifecycle renderer. 注意:shadow 注册(priority -1)无法
717
- * 声明 `conversation.chat.commandview` children(已被上游声明,重复声明会抛
718
- * 「already declared」),因此这里不消费 `renderSlot` 子 slot,直接渲染通用
719
- * command 卡片。
720
- */
721
- const CommandNodeView = (0, react.memo)(function CommandNodeView({ node, t }) {
722
- const command = node.data;
723
- const owner = (0, react.useMemo)(() => ({ node: command }), [command]);
724
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
725
- className: ChatView_module_css_default.callRow,
726
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GenericCommandCard, {
727
- ...owner,
728
- t
729
- })
730
- });
731
- });
732
- /** One integrated `/compact` command and compaction transaction renderer. */
733
- const ManualCompactionNodeView = (0, react.memo)(function ManualCompactionNodeView({ node, t }) {
734
- const data = node.data;
735
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
736
- className: ChatView_module_css_default.callRow,
737
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactionCommandCard, {
738
- node: data.command,
739
- ...data.compaction === null ? {} : { compaction: data.compaction },
740
- t
741
- })
742
- });
743
- });
744
- //#endregion
745
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/ContextBody.module.css.mjs
746
- const css$4 = ".emmM3G_text{color:var(--dsw-alias-label-secondary);font:inherit;white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.emmM3G_fields{border-top:1px solid var(--dsw-alias-line-secondary);flex-direction:column;gap:2px;margin:8px 0 0;padding-top:8px;display:flex}.emmM3G_field{gap:8px;min-width:0;display:flex}.emmM3G_fieldKey{min-width:96px;color:var(--dsw-alias-label-caption);flex:none}.emmM3G_fieldValue{min-width:0;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere;flex:auto;margin:0}.emmM3G_files{flex-wrap:wrap;gap:4px 12px;margin:0 0 8px;padding:0;list-style:none;display:flex}.emmM3G_file{align-items:baseline;gap:6px;min-width:0;display:flex}.emmM3G_filePath{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.emmM3G_fileAction{color:var(--dsw-alias-label-caption)}.emmM3G_catalogNotice{color:var(--dsw-alias-label-caption);margin:0 0 6px}.emmM3G_entries{flex-direction:column;gap:4px;margin:0;padding:0;list-style:none;display:flex}.emmM3G_entry{gap:8px;min-width:0;display:flex}.emmM3G_entryName{color:var(--dsw-alias-label-secondary);flex:none}.emmM3G_entryDescription{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;overflow:hidden}.emmM3G_sections{flex-direction:column;gap:8px;margin:0;display:flex}.emmM3G_section{flex-direction:column;gap:2px;min-width:0;display:flex}.emmM3G_sectionName{color:var(--dsw-alias-label-caption)}.emmM3G_sectionText{color:var(--dsw-alias-label-secondary);white-space:pre-wrap;overflow-wrap:anywhere;margin:0}.emmM3G_relaySender{color:var(--dsw-alias-label-caption);overflow-wrap:anywhere;margin:0 0 6px}.emmM3G_recalls{flex-direction:column;gap:2px;margin:0 0 8px;padding:0;list-style:none;display:flex}.emmM3G_recall{gap:8px;min-width:0;display:flex}.emmM3G_recallLabel{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}.emmM3G_recallCounts{color:var(--dsw-alias-label-caption);flex:none}";
747
- const tagId$4 = "@morlay/ui-conversation-message-actions/ContextBody.module.css";
748
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
749
- const tag = document.createElement("style");
750
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
751
- tag.dataset.pluginCss = tagId$4;
752
- tag.textContent = css$4;
753
- document.head.appendChild(tag);
754
- }
755
- var ContextBody_module_css_default = {
756
- "recallCounts": "emmM3G_recallCounts",
757
- "entryName": "emmM3G_entryName",
758
- "files": "emmM3G_files",
759
- "file": "emmM3G_file",
760
- "entries": "emmM3G_entries",
761
- "sectionName": "emmM3G_sectionName",
762
- "field": "emmM3G_field",
763
- "fieldValue": "emmM3G_fieldValue",
764
- "entry": "emmM3G_entry",
765
- "sections": "emmM3G_sections",
766
- "section": "emmM3G_section",
767
- "filePath": "emmM3G_filePath",
768
- "catalogNotice": "emmM3G_catalogNotice",
769
- "sectionText": "emmM3G_sectionText",
770
- "relaySender": "emmM3G_relaySender",
771
- "fields": "emmM3G_fields",
772
- "entryDescription": "emmM3G_entryDescription",
773
- "text": "emmM3G_text",
774
- "fileAction": "emmM3G_fileAction",
775
- "recalls": "emmM3G_recalls",
776
- "recall": "emmM3G_recall",
777
- "recallLabel": "emmM3G_recallLabel",
778
- "fieldKey": "emmM3G_fieldKey"
779
- };
780
- //#endregion
781
- //#region src/client/chat-node/ContextBody.tsx
782
- /** Model-facing text stays bounded at the disclosure, not at the producer. */
783
- const MAX_CHARS = 2e4;
784
- /** Rows a list body materializes before summarizing the remainder. */
785
- const MAX_ENTRIES = 200;
786
- /** One durable source narrowed to the readable-record shape; null for anything else. */
787
- function asRecord(value) {
788
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
789
- }
790
- /**
791
- * The content blocks as runs, IN THE ORDER the model received them.
792
- *
793
- * Adjacent text blocks join with no separator, matching how provider adapters
794
- * flatten them — inserting a line break would show the reader a line the model
795
- * never saw. An unknown block breaks the run and keeps its own fallback rather
796
- * than being hoisted past the text around it or vanishing; the block union is
797
- * merge-extensible, so a foreign log may interleave shapes this build does not
798
- * know.
799
- */
800
- function contentRuns(content) {
801
- const runs = [];
802
- for (const block of content) {
803
- if (block.type !== "text") {
804
- runs.push({ block });
805
- continue;
806
- }
807
- const last = runs[runs.length - 1];
808
- if (last !== void 0 && "text" in last) last.text += block.text;
809
- else runs.push({ text: block.text });
810
- }
811
- return runs;
812
- }
813
- /** Only the blocks this UI version does not know, for bodies that replace the text. */
814
- function unknownBlocks(content) {
815
- return contentRuns(content).flatMap((run) => "block" in run ? [run.block] : []);
816
- }
817
- /** The model-facing text, truncated to the display bound. */
818
- function boundedText(text, t) {
819
- return text.length > MAX_CHARS ? `${text.slice(0, MAX_CHARS)}\n${t("json.truncated", { total: text.length })}` : text;
820
- }
821
- /**
822
- * One source field rendered as a value row; nested shapes stay compact JSON.
823
- * Bounded on its own, because source fields are as unbounded as the text: an unknown
824
- * producer may record an arbitrarily large string or array.
825
- */
826
- function fieldValue(value, t) {
827
- return boundedText(typeof value === "string" ? value : typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value), t);
828
- }
829
- /**
830
- * Source fields as a key/value list. `kind` is always omitted because the
831
- * row header already names the producer. `form` is omitted only when a
832
- * dedicated body rendered for it — then the presentation the reader is looking
833
- * at IS that value. On the opaque fallback the declaration is kept, because
834
- * that is the one place a form this version cannot present would otherwise
835
- * disappear from the UI entirely.
836
- */
837
- function SourceFields({ source, formRendered, t }) {
838
- const record = asRecord(source);
839
- if (record === null) return null;
840
- const hidden = formRendered ? ["kind", "form"] : ["kind"];
841
- const rows = Object.entries(record).filter(([key]) => !hidden.includes(key));
842
- if (rows.length === 0) return null;
843
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
844
- className: ContextBody_module_css_default.fields,
845
- "data-context-fields": true,
846
- children: rows.map(([key, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
847
- className: ContextBody_module_css_default.field,
848
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
849
- className: ContextBody_module_css_default.fieldKey,
850
- children: key
851
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
852
- className: ContextBody_module_css_default.fieldValue,
853
- children: fieldValue(value, t)
854
- })]
855
- }, key))
856
- });
857
- }
858
- /**
859
- * Content blocks this UI version does not know, kept visible rather than
860
- * dropped: the block union is merge-extensible, so a newer or foreign log may
861
- * carry a shape this build has no presentation for.
862
- * @param props - The unrecognized blocks and the locale seat.
863
- * @returns One generic JSON block per unknown entry.
864
- */
865
- function UnknownBlocks({ blocks, t }) {
866
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: blocks.map((block, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
867
- label: t("message.unknownBlock"),
868
- payload: block,
869
- truncatedLabel: (total) => t("json.truncated", { total })
870
- }, index)) });
871
- }
872
- /**
873
- * The model-facing content of one context, shared by every form that shows it:
874
- * the text with its real line breaks, then any block this UI version does not
875
- * know, which keeps its own fallback rather than vanishing.
876
- * @param props - Durable content and the locale seat.
877
- * @returns The content blocks as the model received them.
878
- */
879
- function ModelFacingContent({ content, t }) {
880
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: contentRuns(content).map((run, index) => "text" in run ? run.text !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
881
- className: ContextBody_module_css_default.text,
882
- "data-context-text": true,
883
- children: boundedText(run.text, t)
884
- }, index) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
885
- label: t("message.unknownBlock"),
886
- payload: run.block,
887
- truncatedLabel: (total) => t("json.truncated", { total })
888
- }, index)) });
889
- }
890
- /**
891
- * Default presentation: the model-facing text as text, with its real line
892
- * breaks, and the remaining source fields beneath it. This is what every form
893
- * this UI version does not recognize renders as.
894
- * @param props - Durable content, its source, and the locale seat.
895
- * @returns The opaque context body.
896
- */
897
- function OpaqueBody({ content, source, t }) {
898
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFacingContent, {
899
- content,
900
- t
901
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceFields, {
902
- source,
903
- formRendered: false,
904
- t
905
- })] });
906
- }
907
- /**
908
- * Instruction changes read off the source, or null when the record is not a
909
- * usable instruction list.
910
- *
911
- * The read is all-or-nothing: silently dropping one unreadable entry would show
912
- * a confident, incomplete file list for a log this version cannot fully read.
913
- * Paths are deduplicated in first-seen order, matching how the header label is
914
- * derived from the same array.
915
- */
916
- function instructionChanges(source) {
917
- const record = asRecord(source);
918
- const list = record === null ? void 0 : record["changes"];
919
- if (!Array.isArray(list)) return null;
920
- const changes = [];
921
- const seen = /* @__PURE__ */ new Set();
922
- for (const entry of list) {
923
- const change = asRecord(entry);
924
- if (change === null) return null;
925
- const path = change["path"];
926
- if (typeof path !== "string" || path === "") return null;
927
- const action = change["action"];
928
- if (action !== "set" && action !== "replace" && action !== "remove") return null;
929
- const digest = change["digest"];
930
- if (seen.has(path)) continue;
931
- seen.add(path);
932
- changes.push({
933
- action,
934
- path,
935
- ...typeof digest === "string" ? { digest } : {}
936
- });
937
- }
938
- return changes.length === 0 ? null : changes;
939
- }
940
- /**
941
- * Locale key for one reconciled file. The baseline loads a file; a later delta
942
- * distinguishes a newly reconciled path from a rewritten one, which `set` and
943
- * `replace` already separate at the producer.
944
- * @param action - the durable change action.
945
- * @param baseline - whether this context is the startup/resume baseline.
946
- * @returns the key naming what happened to that file.
947
- */
948
- function instructionAction(action, baseline) {
949
- if (action === "remove") return "message.context.instructions.removed";
950
- if (baseline) return "message.context.instructions.loaded";
951
- return action === "set" ? "message.context.instructions.added" : "message.context.instructions.updated";
952
- }
953
- /**
954
- * `instructions` form: the files this context reconciled, then their text.
955
- *
956
- * The text keeps its `<system-reminder>` framing verbatim — the framing is part
957
- * of what the model read, so hiding it would misreport the request.
958
- * @param props - Durable content, its source, and the locale seat.
959
- * @returns The instructions context body, or the opaque body when the change
960
- * list is unreadable.
961
- */
962
- function InstructionsBody({ content, source, t }) {
963
- const changes = instructionChanges(source);
964
- if (changes === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, {
965
- content,
966
- source,
967
- t
968
- });
969
- const baseline = asRecord(source)?.["baseline"] === true;
970
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
971
- className: ContextBody_module_css_default.files,
972
- "data-context-files": true,
973
- children: changes.map((change) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
974
- className: ContextBody_module_css_default.file,
975
- title: change.digest,
976
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
977
- className: ContextBody_module_css_default.filePath,
978
- children: change.path
979
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
980
- className: ContextBody_module_css_default.fileAction,
981
- children: t(instructionAction(change.action, baseline))
982
- })]
983
- }, change.path))
984
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFacingContent, {
985
- content,
986
- t
987
- })] });
988
- }
989
- /**
990
- * Catalog entries read off the source, or null when the record is not a usable
991
- * catalog. All-or-nothing for the same reason as the instruction list: this body
992
- * replaces the model-facing text, so a partial list would hide the only complete
993
- * account of what the model read.
994
- */
995
- function catalogEntries(source) {
996
- const record = asRecord(source);
997
- const list = record === null ? void 0 : record["entries"];
998
- if (!Array.isArray(list)) return null;
999
- const entries = [];
1000
- for (const item of list) {
1001
- const entry = asRecord(item);
1002
- if (entry === null) return null;
1003
- const name = entry["name"];
1004
- const description = entry["description"];
1005
- if (typeof name !== "string" || name === "" || typeof description !== "string") return null;
1006
- entries.push({
1007
- name,
1008
- description
1009
- });
1010
- }
1011
- return entries;
1012
- }
1013
- /**
1014
- * `catalog` form: the published entries as a list, read from the source rather
1015
- * than re-parsed out of the model-facing prose.
1016
- *
1017
- * A catalog whose source carries no usable entries falls through to the opaque
1018
- * body, so an older or hand-edited log still shows its text.
1019
- * @param props - Durable content, its source, and the locale seat.
1020
- * @returns The catalog context body, or the opaque body when the entry list is
1021
- * unreadable.
1022
- */
1023
- function CatalogBody({ content, source, t }) {
1024
- const entries = catalogEntries(source);
1025
- if (entries === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, {
1026
- content,
1027
- source,
1028
- t
1029
- });
1030
- const update = asRecord(source)?.["update"] === true;
1031
- const shown = entries.slice(0, MAX_ENTRIES);
1032
- const rest = unknownBlocks(content);
1033
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1034
- update && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1035
- className: ContextBody_module_css_default.catalogNotice,
1036
- "data-context-catalog-update": true,
1037
- children: t("message.context.catalog.replaced")
1038
- }),
1039
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1040
- className: ContextBody_module_css_default.entries,
1041
- "data-context-entries": true,
1042
- children: shown.map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1043
- className: ContextBody_module_css_default.entry,
1044
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1045
- className: ContextBody_module_css_default.entryName,
1046
- children: entry.name
1047
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1048
- className: ContextBody_module_css_default.entryDescription,
1049
- children: entry.description
1050
- })]
1051
- }, index))
1052
- }),
1053
- shown.length < entries.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1054
- className: ContextBody_module_css_default.catalogNotice,
1055
- "data-context-entries-truncated": true,
1056
- children: t("message.context.catalog.more", { count: entries.length - shown.length })
1057
- }),
1058
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UnknownBlocks, {
1059
- blocks: rest,
1060
- t
1061
- })
1062
- ] });
1063
- }
1064
- /** Snapshot sections read off the source, or null when the record is unusable. */
1065
- function snapshotSections(source) {
1066
- const record = asRecord(source);
1067
- const list = record === null ? void 0 : record["sections"];
1068
- if (!Array.isArray(list)) return null;
1069
- const sections = [];
1070
- for (const item of list) {
1071
- const section = asRecord(item);
1072
- if (section === null) return null;
1073
- const name = section["name"];
1074
- const text = section["text"];
1075
- if (typeof name !== "string" || name === "" || typeof text !== "string") return null;
1076
- sections.push({
1077
- name,
1078
- text
1079
- });
1080
- }
1081
- return sections.length === 0 ? null : sections;
1082
- }
1083
- /**
1084
- * `snapshot` form: the named contributions this snapshot assembled, in order.
1085
- *
1086
- * The sections are the same bytes the model read, split at the boundaries the
1087
- * producer assembled them on, so a reader sees which subsystem contributed
1088
- * which state instead of one undifferentiated wall.
1089
- *
1090
- * One sentence of the model-facing text is NOT in any section: the producer's
1091
- * framing line declaring that this snapshot supersedes earlier ones. Unlike the
1092
- * `<system-reminder>` wrapper an instruction context carries — which wraps
1093
- * content and cannot be separated from it — that line states the form's own
1094
- * semantics, so the body states them as a caption instead of reprinting the
1095
- * joined prose beside the sections it was split from.
1096
- * @param props - Durable content, its source, and the locale seat.
1097
- * @returns The snapshot context body, or the opaque body when unreadable.
1098
- */
1099
- function SnapshotBody({ content, source, t }) {
1100
- const sections = snapshotSections(source);
1101
- /* v8 ignore next -- contextBody reads the sections before choosing this body. */
1102
- if (sections === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, {
1103
- content,
1104
- source,
1105
- t
1106
- });
1107
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1108
- className: ContextBody_module_css_default.catalogNotice,
1109
- "data-context-snapshot-supersedes": true,
1110
- children: t("message.context.snapshot.supersedes")
1111
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
1112
- className: ContextBody_module_css_default.sections,
1113
- "data-context-sections": true,
1114
- children: sections.map((section, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1115
- className: ContextBody_module_css_default.section,
1116
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1117
- className: ContextBody_module_css_default.sectionName,
1118
- children: section.name
1119
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1120
- className: ContextBody_module_css_default.sectionText,
1121
- children: boundedText(section.text, t)
1122
- })]
1123
- }, index))
1124
- })] });
1125
- }
1126
- /**
1127
- * `notice` form: what just happened, with the model-facing text beneath it.
1128
- *
1129
- * The one-line account also rides the collapsed row ({@link contextBody}), so a
1130
- * notice is usually readable without expanding at all.
1131
- * @param props - Durable content, its source, and the locale seat.
1132
- * @returns The notice context body.
1133
- */
1134
- function NoticeBody({ content, t }) {
1135
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFacingContent, {
1136
- content,
1137
- t
1138
- });
1139
- }
1140
- /**
1141
- * `relay` form: which agent sent this, then what it said.
1142
- *
1143
- * The sender is an opaque session id; it is shown as a field rather than a
1144
- * label, because this client cannot resolve it to a title.
1145
- * @param props - Durable content, its source, and the locale seat.
1146
- * @returns The relay context body.
1147
- */
1148
- function RelayBody({ content, source, t }) {
1149
- const sender = relaySender(source);
1150
- /* v8 ignore next -- contextBody resolves the sender before choosing this body. */
1151
- if (sender === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, {
1152
- content,
1153
- source,
1154
- t
1155
- });
1156
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1157
- className: ContextBody_module_css_default.relaySender,
1158
- "data-context-relay-sender": true,
1159
- children: t("message.context.relay.from", { session: sender })
1160
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFacingContent, {
1161
- content,
1162
- t
1163
- })] });
1164
- }
1165
- /** The sending agent's session id, or null when the record does not name one. */
1166
- function relaySender(source) {
1167
- const sender = asRecord(source)?.["senderSessionId"];
1168
- return typeof sender === "string" && sender !== "" ? sender : null;
1169
- }
1170
- /** Recalled sessions read off the source, or null when the record is unusable. */
1171
- function recalledSessions(source) {
1172
- const record = asRecord(source);
1173
- const list = record === null ? void 0 : record["references"];
1174
- if (!Array.isArray(list)) return null;
1175
- const sessions = [];
1176
- for (const item of list) {
1177
- const reference = asRecord(item);
1178
- if (reference === null) return null;
1179
- const label = reference["label"];
1180
- const retained = reference["retainedMessages"];
1181
- const omitted = reference["omittedMessages"];
1182
- const truncated = reference["truncated"];
1183
- if (typeof label !== "string" || label === "" || typeof retained !== "number" || typeof omitted !== "number" || typeof truncated !== "boolean") return null;
1184
- sessions.push({
1185
- label,
1186
- retained,
1187
- omitted,
1188
- truncated
1189
- });
1190
- }
1191
- return sessions.length === 0 ? null : sessions;
1192
- }
1193
- /**
1194
- * `recall` form: which sessions this material came from and how much of each
1195
- * survived the read, then the material itself.
1196
- *
1197
- * Completeness is the fact a reader needs first: recalled context is bounded on
1198
- * the way in, so a card that hid the omitted count would overstate what the
1199
- * model received.
1200
- * @param props - Durable content, its source, and the locale seat.
1201
- * @returns The recall context body, or the opaque body when unreadable.
1202
- */
1203
- function RecallBody({ content, source, t }) {
1204
- const sessions = recalledSessions(source);
1205
- if (sessions === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, {
1206
- content,
1207
- source,
1208
- t
1209
- });
1210
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
1211
- className: ContextBody_module_css_default.recalls,
1212
- "data-context-recalls": true,
1213
- children: sessions.map((session, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
1214
- className: ContextBody_module_css_default.recall,
1215
- children: [
1216
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1217
- className: ContextBody_module_css_default.recallLabel,
1218
- children: session.label
1219
- }),
1220
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1221
- className: ContextBody_module_css_default.recallCounts,
1222
- children: t("message.context.recall.counts", {
1223
- retained: session.retained,
1224
- omitted: session.omitted
1225
- })
1226
- }),
1227
- session.truncated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1228
- className: ContextBody_module_css_default.recallCounts,
1229
- children: t("message.context.recall.truncated")
1230
- })
1231
- ]
1232
- }, index))
1233
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelFacingContent, {
1234
- content,
1235
- t
1236
- })] });
1237
- }
1238
- /** The one-line account a `notice` puts on its collapsed row, when it records one. */
1239
- function noticeSummary(source) {
1240
- const summary = asRecord(source)?.["summary"];
1241
- return typeof summary === "string" && summary !== "" ? summary : null;
1242
- }
1243
- /**
1244
- * Choose the body for one context node.
1245
- *
1246
- * Returns the form the body actually rendered as, which is not always the
1247
- * declared one: a declared form whose fields are unreadable falls back to
1248
- * opaque, and the caller labels the row with what it really shows.
1249
- * `summary` is the collapsed row's one-line account, which only a `notice`
1250
- * records: its whole point is being readable without expanding.
1251
- * @param form - the producer-declared form projected onto the node.
1252
- * @param props - durable content, its source, and the locale seat.
1253
- * @returns the rendered form (null for opaque), its collapsed summary, and its body.
1254
- */
1255
- function contextBody(form, props) {
1256
- const opaque = {
1257
- rendered: null,
1258
- summary: null,
1259
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpaqueBody, { ...props })
1260
- };
1261
- switch (form) {
1262
- case "instructions": return instructionChanges(props.source) === null ? opaque : {
1263
- rendered: "instructions",
1264
- summary: null,
1265
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstructionsBody, { ...props })
1266
- };
1267
- case "catalog": return catalogEntries(props.source) === null ? opaque : {
1268
- rendered: "catalog",
1269
- summary: null,
1270
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CatalogBody, { ...props })
1271
- };
1272
- case "snapshot": return snapshotSections(props.source) === null ? opaque : {
1273
- rendered: "snapshot",
1274
- summary: null,
1275
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SnapshotBody, { ...props })
1276
- };
1277
- case "notice": {
1278
- const summary = noticeSummary(props.source);
1279
- return summary === null ? opaque : {
1280
- rendered: "notice",
1281
- summary,
1282
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoticeBody, { ...props })
1283
- };
1284
- }
1285
- case "relay": return relaySender(props.source) === null ? opaque : {
1286
- rendered: "relay",
1287
- summary: null,
1288
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RelayBody, { ...props })
1289
- };
1290
- case "recall": return recalledSessions(props.source) === null ? opaque : {
1291
- rendered: "recall",
1292
- summary: null,
1293
- body: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RecallBody, { ...props })
1294
- };
1295
- case null: return opaque;
1296
- /* v8 ignore next 4 -- closed-union backstop; the compiler rejects a new
1297
- KnownContextForm here rather than letting it degrade to opaque silently. */
1298
- default: throw new Error(`unreachable context form: ${String(form)}`);
1299
- }
1300
- }
1301
- //#endregion
1302
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/ContextInjectionRow.module.css.mjs
1303
- const css$3 = ".zwBaJG_root{min-width:0}.zwBaJG_root[data-open]{padding-bottom:4px}.zwBaJG_chevron{color:var(--dsw-alias-label-secondary)}.zwBaJG_sep{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.zwBaJG_source{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:none;font-size:14px;line-height:24px;overflow:hidden}.zwBaJG_summary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.zwBaJG_body{box-sizing:border-box;background:var(--dsw-alias-markdown-code-block);width:calc(100% - 22px);max-height:141px;color:var(--dsw-alias-label-tertiary);font:400 11px/16px var(--ds-font-family-code);border:none;border-radius:8px;margin:4px 0 0 22px;padding:10px 16px 12px 12px;overflow:auto}";
1304
- const tagId$3 = "@morlay/ui-conversation-message-actions/ContextInjectionRow.module.css";
1305
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1306
- const tag = document.createElement("style");
1307
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
1308
- tag.dataset.pluginCss = tagId$3;
1309
- tag.textContent = css$3;
1310
- document.head.appendChild(tag);
1311
- }
1312
- var ContextInjectionRow_module_css_default = {
1313
- "chevron": "zwBaJG_chevron",
1314
- "sep": "zwBaJG_sep",
1315
- "summary": "zwBaJG_summary",
1316
- "root": "zwBaJG_root",
1317
- "source": "zwBaJG_source",
1318
- "body": "zwBaJG_body"
1319
- };
1320
- //#endregion
1321
- //#region src/client/chat-node/ContextInjectionRow.tsx
1322
- /**
1323
- * Render logged context with the Tool calls disclosure chrome from Figma.
1324
- *
1325
- * The header names the role the context plays and, beside it, the producer the
1326
- * durable source identifies, so a reader can tell an injected skill catalog
1327
- * from a workspace instruction file or a recalled session without expanding.
1328
- * The expanded body follows the producer-declared form; an absent or unknown
1329
- * form renders the opaque body.
1330
- * @param props - Durable content, its projected producer role/name and form, and the locale seat.
1331
- * @returns A collapsed context row with a bounded, form-specific body.
1332
- */
1333
- function ContextInjectionRow({ content, source, provenance, form, t }) {
1334
- const [open, setOpen] = (0, react.useState)(false);
1335
- const { rendered, summary, body } = contextBody(form, {
1336
- content,
1337
- source,
1338
- t
1339
- });
1340
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
1341
- className: ContextInjectionRow_module_css_default.root,
1342
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBrowseOutline16, { size: 14 }),
1343
- chevronClassName: ContextInjectionRow_module_css_default.chevron,
1344
- title: t(provenance.role === "recall" ? "message.contextRecall" : "message.contextInjection"),
1345
- collapsedContent: provenance.label === null ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1346
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1347
- className: ContextInjectionRow_module_css_default.sep,
1348
- "aria-hidden": true
1349
- }),
1350
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1351
- className: ContextInjectionRow_module_css_default.source,
1352
- "data-context-source": true,
1353
- children: provenance.label
1354
- }),
1355
- summary !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1356
- className: ContextInjectionRow_module_css_default.sep,
1357
- "aria-hidden": true
1358
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1359
- className: ContextInjectionRow_module_css_default.summary,
1360
- "data-context-summary": true,
1361
- children: summary
1362
- })] })
1363
- ] }),
1364
- keepContentWhenOpen: true,
1365
- open,
1366
- expandable: true,
1367
- expandOnRowClick: true,
1368
- onToggle: () => {
1369
- setOpen((value) => !value);
1370
- },
1371
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1372
- className: ContextInjectionRow_module_css_default.body,
1373
- "data-context-injection-body": true,
1374
- "data-context-form": rendered ?? void 0,
1375
- children: body
1376
- })
1377
- });
1378
- }
1379
- //#endregion
1380
- //#region src/client/chat-node/message-chrome.ts
1381
- function pad2(n) {
1382
- return String(n).padStart(2, "0");
1383
- }
1384
- /**
1385
- * Local calendar-day epoch (ms at local midnight) for an instant.
1386
- * @param ms - Unix epoch ms.
1387
- * @returns Midnight of that local calendar day.
1388
- */
1389
- function startOfLocalDay(ms) {
1390
- const d = new Date(ms);
1391
- d.setHours(0, 0, 0, 0);
1392
- return d.getTime();
1393
- }
1394
- /**
1395
- * Delay until the next local midnight after `ms` (at least 1ms).
1396
- * @param ms - Unix epoch ms.
1397
- * @returns Milliseconds until the following local midnight.
1398
- */
1399
- function msUntilNextLocalMidnight(ms) {
1400
- const next = new Date(ms);
1401
- next.setHours(24, 0, 0, 0);
1402
- return Math.max(next.getTime() - ms, 1);
1403
- }
1404
- /**
1405
- * Localized elapsed-time label shared by running and settled turn chrome.
1406
- * @param ms - Elapsed duration in milliseconds (negatives clamp to zero).
1407
- * @param t - Translate seat supplying the duration templates.
1408
- * @returns Display string in whole seconds.
1409
- */
1410
- function formatRunDuration(ms, t) {
1411
- const total = Math.max(0, Math.floor(ms / 1e3));
1412
- const minutes = Math.floor(total / 60);
1413
- const seconds = total % 60;
1414
- return minutes > 0 ? t("duration.minutes", {
1415
- minutes,
1416
- seconds: String(seconds).padStart(2, "0")
1417
- }) : t("duration.seconds", { seconds });
1418
- }
1419
- /**
1420
- * Sub-turn latency figure: one decimal under ten seconds, whole seconds
1421
- * beyond. Unit-less so the locale template owns the second suffix.
1422
- * @param ms - Latency in milliseconds (negatives clamp to zero).
1423
- * @returns Display number in seconds without unit.
1424
- */
1425
- function formatLatencySeconds(ms) {
1426
- const s = Math.max(0, ms) / 1e3;
1427
- return s < 10 ? String(Math.round(s * 10) / 10) : String(Math.round(s));
1428
- }
1429
- /**
1430
- * Decode-throughput figure: whole tokens from ten up, one decimal below.
1431
- * @param tps - Tokens per second.
1432
- * @returns Display number without unit.
1433
- */
1434
- function formatTokensPerSecond(tps) {
1435
- const clamped = Math.max(0, tps);
1436
- return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
1437
- }
1438
- /**
1439
- * Compact local timestamp for message IconActions. Same calendar day →
1440
- * `HH:mm`; earlier this year → the `clock.md` date template + clock; other
1441
- * years → the `clock.ymd` template + clock. Pure: the date templates arrive
1442
- * through the caller's locale seat.
1443
- * @param time - Unix epoch ms from the source session event.
1444
- * @param t - translate seat supplying the `clock.md` / `clock.ymd` templates.
1445
- * @param now - Reference instant for the day/year cut (defaults to wall clock).
1446
- * @returns Date-aware clock string (24-hour, zero-padded time).
1447
- */
1448
- function formatMessageClock(time, t, now = Date.now()) {
1449
- const d = new Date(time);
1450
- const n = new Date(now);
1451
- const clock = `${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
1452
- if (d.getFullYear() === n.getFullYear() && d.getMonth() === n.getMonth() && d.getDate() === n.getDate()) return clock;
1453
- const params = {
1454
- y: d.getFullYear(),
1455
- m: d.getMonth() + 1,
1456
- d: d.getDate()
1457
- };
1458
- return `${d.getFullYear() === n.getFullYear() ? t("clock.md", params) : t("clock.ymd", params)} ${clock}`;
1459
- }
1460
- //#endregion
1461
- //#region src/client/chat-node/use-calendar-day.ts
1462
- /**
1463
- * Local calendar-day epoch that advances at each local midnight.
1464
- * @returns Midnight ms for the current local day; updates after the boundary.
1465
- */
1466
- function useCalendarDay() {
1467
- const [day, setDay] = (0, react.useState)(() => startOfLocalDay(Date.now()));
1468
- (0, react.useEffect)(() => {
1469
- let timer;
1470
- const arm = () => {
1471
- const now = Date.now();
1472
- setDay(startOfLocalDay(now));
1473
- timer = setTimeout(arm, msUntilNextLocalMidnight(now));
1474
- };
1475
- timer = setTimeout(arm, msUntilNextLocalMidnight(Date.now()));
1476
- return () => {
1477
- clearTimeout(timer);
1478
- };
1479
- }, []);
1480
- return day;
1481
- }
1482
- //#endregion
1483
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/MessageIconActions.module.css.mjs
1484
- const css$2 = ".lKOSUG_actions{align-items:center;gap:10px;height:28px;display:flex}.lKOSUG_timeStart{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-right:12px;font-size:14px;line-height:24px}.lKOSUG_timeEnd{color:var(--dsw-alias-label-tertiary);white-space:nowrap;padding-left:12px;font-size:14px;line-height:24px}.lKOSUG_runTimeDot{margin:0 10px}@media (hover:hover){[data-time-hover-root] :is(.lKOSUG_timeStart,.lKOSUG_timeEnd){opacity:0;transition:opacity 80ms}[data-time-hover-root]:hover :is(.lKOSUG_timeStart,.lKOSUG_timeEnd),[data-time-hover-root]:focus-within :is(.lKOSUG_timeStart,.lKOSUG_timeEnd){opacity:1}}.lKOSUG_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}.lKOSUG_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.lKOSUG_action[data-unavailable]{cursor:default;opacity:.4}.lKOSUG_action[data-unavailable]:hover{color:var(--dsw-alias-label-tertiary);background:0 0}.lKOSUG_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}";
1485
- const tagId$2 = "@morlay/ui-conversation-message-actions/MessageIconActions.module.css";
1486
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1487
- const tag = document.createElement("style");
1488
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
1489
- tag.dataset.pluginCss = tagId$2;
1490
- tag.textContent = css$2;
1491
- document.head.appendChild(tag);
1492
- }
1493
- var MessageIconActions_module_css_default = {
1494
- "timeStart": "lKOSUG_timeStart",
1495
- "runTimeDot": "lKOSUG_runTimeDot",
1496
- "actions": "lKOSUG_actions",
1497
- "timeEnd": "lKOSUG_timeEnd",
1498
- "visuallyHidden": "lKOSUG_visuallyHidden",
1499
- "action": "lKOSUG_action"
1500
- };
1501
- //#endregion
1502
- //#region src/client/chat-node/MessageIconActions.tsx
1503
- /**
1504
- * Copy / branch (/ clock) IconActions row shared by user and assistant chrome.
1505
- * @param props - Copy text, event time, clock side, branch callback, className.
1506
- * @returns The actions row element.
1507
- */
1508
- function MessageIconActions({ text, time, runMs, ttftMs, tokensPerSecond, clock, onBranch, branchUnavailable = false, className, extraActions, onEdit, onRetry, t }) {
1509
- const day = useCalendarDay();
1510
- const reasonId = (0, react.useId)();
1511
- const [copied, setCopied] = (0, react.useState)(false);
1512
- const copyPending = (0, react.useRef)(false);
1513
- const copyTimer = (0, react.useRef)(null);
1514
- const copyEpoch = (0, react.useRef)(0);
1515
- (0, react.useEffect)(() => () => {
1516
- copyEpoch.current += 1;
1517
- copyPending.current = false;
1518
- if (copyTimer.current !== null) clearTimeout(copyTimer.current);
1519
- }, []);
1520
- const onCopy = (0, react.useCallback)(() => {
1521
- if (copied || copyPending.current) return;
1522
- const epoch = copyEpoch.current;
1523
- copyPending.current = true;
1524
- (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(text).then((ok) => {
1525
- if (epoch !== copyEpoch.current) return;
1526
- copyPending.current = false;
1527
- if (!ok) return;
1528
- setCopied(true);
1529
- copyTimer.current = window.setTimeout(() => {
1530
- copyTimer.current = null;
1531
- setCopied(false);
1532
- }, 1e3);
1533
- });
1534
- }, [copied, text]);
1535
- const clockEl = time === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1536
- className: clock === "start" ? MessageIconActions_module_css_default.timeStart : MessageIconActions_module_css_default.timeEnd,
1537
- children: [
1538
- formatMessageClock(time, t, day),
1539
- runMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1540
- " ",
1541
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1542
- className: MessageIconActions_module_css_default.runTimeDot,
1543
- "aria-hidden": true,
1544
- children: "·"
1545
- }),
1546
- " ",
1547
- t("message.ranFor", { duration: formatRunDuration(runMs, t) })
1548
- ] }),
1549
- ttftMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1550
- " ",
1551
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1552
- className: MessageIconActions_module_css_default.runTimeDot,
1553
- "aria-hidden": true,
1554
- children: "·"
1555
- }),
1556
- " ",
1557
- t("message.ttft", { seconds: formatLatencySeconds(ttftMs) })
1558
- ] }),
1559
- tokensPerSecond !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1560
- " ",
1561
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1562
- className: MessageIconActions_module_css_default.runTimeDot,
1563
- "aria-hidden": true,
1564
- children: "·"
1565
- }),
1566
- " ",
1567
- t("message.tokensPerSecond", { tps: formatTokensPerSecond(tokensPerSecond) })
1568
- ] })
1569
- ]
1570
- });
1571
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1572
- className: className === void 0 ? MessageIconActions_module_css_default.actions : `${MessageIconActions_module_css_default.actions} ${className}`,
1573
- children: [
1574
- clock === "start" ? clockEl : null,
1575
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1576
- label: copied ? t("copied") : t("copy"),
1577
- side: "bottom",
1578
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1579
- type: "button",
1580
- className: MessageIconActions_module_css_default.action,
1581
- "aria-label": copied ? t("copied") : t("copy"),
1582
- onClick: onCopy,
1583
- children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, {})
1584
- })
1585
- }),
1586
- extraActions,
1587
- onEdit !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1588
- label: "编辑",
1589
- side: "bottom",
1590
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1591
- type: "button",
1592
- className: MessageIconActions_module_css_default.action,
1593
- "aria-label": "编辑",
1594
- onClick: onEdit,
1595
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {})
1596
- })
1597
- }),
1598
- onRetry !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1599
- label: "重试此回合",
1600
- side: "bottom",
1601
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1602
- type: "button",
1603
- className: MessageIconActions_module_css_default.action,
1604
- "aria-label": "重试此回合",
1605
- onClick: onRetry,
1606
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {})
1607
- })
1608
- }),
1609
- onBranch !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1610
- label: branchUnavailable ? t("message.branchUnavailable") : t("message.branch"),
1611
- side: "bottom",
1612
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1613
- type: "button",
1614
- className: MessageIconActions_module_css_default.action,
1615
- "aria-label": t("message.branch"),
1616
- "aria-disabled": branchUnavailable || void 0,
1617
- "aria-describedby": branchUnavailable ? reasonId : void 0,
1618
- "data-unavailable": branchUnavailable || void 0,
1619
- onClick: branchUnavailable ? void 0 : onBranch,
1620
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, {})
1621
- })
1622
- }),
1623
- onBranch !== void 0 && branchUnavailable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1624
- id: reasonId,
1625
- className: MessageIconActions_module_css_default.visuallyHidden,
1626
- children: t("message.branchUnavailable")
1627
- }),
1628
- clock === "end" ? clockEl : null
1629
- ]
1630
- });
1631
- }
1632
- //#endregion
1633
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/MessageEditDialog.module.css.mjs
1634
- const css$1 = "._7VqDXa_input{box-sizing:border-box;width:100%;min-height:96px;font:inherit;border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-text,inherit);resize:none;border-radius:22px;padding:10px 16px;font-size:16px;line-height:24px;overflow-y:auto}._7VqDXa_actions{justify-content:flex-end;gap:8px;display:flex}";
1635
- const tagId$1 = "@morlay/ui-conversation-message-actions/MessageEditDialog.module.css";
1636
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1637
- const tag = document.createElement("style");
1638
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
1639
- tag.dataset.pluginCss = tagId$1;
1640
- tag.textContent = css$1;
1641
- document.head.appendChild(tag);
1642
- }
1643
- var MessageEditDialog_module_css_default = {
1644
- "actions": "_7VqDXa_actions",
1645
- "input": "_7VqDXa_input"
1646
- };
1647
- //#endregion
1648
- //#region src/client/chat-node/MessageEditDialog.tsx
1649
- /**
1650
- * 消息编辑弹窗:与 dsh settings 同风格的 Modal(@deepseek-ai/dsh-client-ui-primitives)。
1651
- * 输入框复用 composer-card 的视觉(InputBar 同款 border/radius/surface/阴影
1652
- * token)与自动增长交互。保存成功后关闭;失败保持打开。
1653
- */
1654
- const BLOCK_TITLE = {
1655
- user: "编辑用户消息",
1656
- "assistant.reasoning": "编辑助手思考",
1657
- "assistant.response": "编辑助手回复"
1658
- };
1659
- /** 编辑弹窗:调用 onSave 保存;返回 true 关闭,false 保持打开。 */
1660
- function MessageEditDialog({ block, onSave, onClose }) {
1661
- const [text, setText] = (0, react.useState)(block.text);
1662
- const [saving, setSaving] = (0, react.useState)(false);
1663
- const inputRef = (0, react.useRef)(null);
1664
- /** composer-card 式自动增长:高度随内容(scrollHeight)自适应。 */
1665
- const autosize = () => {
1666
- const el = inputRef.current;
1667
- if (el === null) return;
1668
- el.style.height = "auto";
1669
- el.style.height = `${el.scrollHeight}px`;
1670
- };
1671
- const save = () => {
1672
- if (saving) return;
1673
- setSaving(true);
1674
- onSave(text).then((applied) => {
1675
- if (applied) onClose();
1676
- else setSaving(false);
1677
- });
1678
- };
1679
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1680
- open: true,
1681
- onClose,
1682
- title: BLOCK_TITLE[block.kind],
1683
- closeLabel: "关闭",
1684
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1685
- className: MessageEditDialog_module_css_default.actions,
1686
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1687
- variant: "outline",
1688
- onClick: onClose,
1689
- disabled: saving,
1690
- children: "取消"
1691
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1692
- variant: "primary",
1693
- onClick: save,
1694
- disabled: saving,
1695
- children: saving ? "保存中…" : "保存"
1696
- })]
1697
- }),
1698
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1699
- ref: inputRef,
1700
- className: MessageEditDialog_module_css_default.input,
1701
- value: text,
1702
- onChange: (event) => {
1703
- setText(event.target.value);
1704
- autosize();
1705
- },
1706
- autoFocus: true,
1707
- rows: 4
1708
- })
1709
- });
1710
- }
1711
- //#endregion
1712
- //#region src/client/chat-node/MessageItem.tsx
1713
- function contentParts(content) {
1714
- const texts = [];
1715
- const images = [];
1716
- const rest = [];
1717
- for (const block of content) {
1718
- const b = block;
1719
- if (b.type === "text" && typeof b.text === "string") texts.push(b.text);
1720
- else if (b.type === "image" && b.attachment !== void 0) images.push({ attachment: b.attachment });
1721
- else rest.push(block);
1722
- }
1723
- return {
1724
- text: texts.join(""),
1725
- images,
1726
- rest
1727
- };
1728
- }
1729
- function retrySeconds(milliseconds) {
1730
- return Math.max(1, Math.ceil(milliseconds / 1e3));
1731
- }
1732
- function ModelRetryItem({ node, active, t }) {
1733
- const deadline = (0, react.useMemo)(() => Date.now() + node.delayMs, [node.delayMs, node.seq]);
1734
- const scheduledSeconds = retrySeconds(node.delayMs);
1735
- const maximum = node.mode === "normal" ? node.maxRetries : "∞";
1736
- const [countdown, setCountdown] = (0, react.useState)(() => ({
1737
- deadline,
1738
- seconds: retrySeconds(deadline - Date.now())
1739
- }));
1740
- const remainingSeconds = countdown.deadline === deadline ? countdown.seconds : retrySeconds(deadline - Date.now());
1741
- (0, react.useEffect)(() => {
1742
- if (!active) return;
1743
- const updateCountdown = () => {
1744
- const next = retrySeconds(deadline - Date.now());
1745
- setCountdown((current) => current.deadline === deadline && current.seconds === next ? current : {
1746
- deadline,
1747
- seconds: next
1748
- });
1749
- return next;
1750
- };
1751
- if (updateCountdown() === 1) return;
1752
- const timer = window.setInterval(() => {
1753
- if (updateCountdown() === 1) window.clearInterval(timer);
1754
- }, 250);
1755
- return () => {
1756
- window.clearInterval(timer);
1757
- };
1758
- }, [active, deadline]);
1759
- const label = active ? t("message.retry.active") : node.retryState === "cancelled" ? t("message.retry.cancelled") : node.retryState === "started" ? t("message.retry.started") : t("message.retry.scheduled");
1760
- const seconds = active ? remainingSeconds : scheduledSeconds;
1761
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
1762
- className: MessageItem_module_css_default.retryRow,
1763
- "data-active": active || void 0,
1764
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
1765
- className: MessageItem_module_css_default.retrySummary,
1766
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1767
- className: MessageItem_module_css_default.retryText,
1768
- role: "status",
1769
- children: t("message.retry.status", {
1770
- label,
1771
- retry: node.retry,
1772
- maximum,
1773
- seconds
1774
- })
1775
- })
1776
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1777
- className: MessageItem_module_css_default.retryDetails,
1778
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1779
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1780
- className: MessageItem_module_css_default.retryDetailLabel,
1781
- children: t("message.retry.delay")
1782
- }),
1783
- Math.round(node.delayMs),
1784
- "ms"
1785
- ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1786
- className: MessageItem_module_css_default.retryDetailLabel,
1787
- children: t("message.retry.failure")
1788
- }), node.failure.message] })]
1789
- })]
1790
- });
1791
- }
1792
- /** Persistent, turn-positioned feedback for a terminal failure. */
1793
- function TurnErrorItem({ node, t }) {
1794
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1795
- className: MessageItem_module_css_default.turnErrorRow,
1796
- role: "status",
1797
- children: [
1798
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
1799
- state: "error",
1800
- className: MessageItem_module_css_default.turnErrorDot
1801
- }),
1802
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1803
- className: MessageItem_module_css_default.turnErrorCopy,
1804
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1805
- className: MessageItem_module_css_default.turnErrorTitle,
1806
- children: t("message.turnError")
1807
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1808
- className: MessageItem_module_css_default.turnErrorMessage,
1809
- children: node.message
1810
- })]
1811
- }),
1812
- node.code !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1813
- className: MessageItem_module_css_default.turnErrorCode,
1814
- children: node.code
1815
- })
1816
- ]
1817
- });
1818
- }
1819
- /** Persistent, turn-positioned notice for a turn ended at the output-token cap. */
1820
- function TurnMaxTokensItem({ t }) {
1821
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1822
- className: MessageItem_module_css_default.turnErrorRow,
1823
- role: "status",
1824
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
1825
- state: "warning",
1826
- className: MessageItem_module_css_default.turnErrorDot
1827
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1828
- className: MessageItem_module_css_default.turnErrorCopy,
1829
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1830
- className: MessageItem_module_css_default.maxTokensTitle,
1831
- children: t("message.maxTokens")
1832
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1833
- className: MessageItem_module_css_default.turnErrorMessage,
1834
- children: t("message.maxTokens.hint")
1835
- })]
1836
- })]
1837
- });
1838
- }
1839
- /**
1840
- * Display projection of reference forms in a user bubble (free geometry — no
1841
- * textarea alignment constraint here); everything else stays plain text. The
1842
- * logged model text remains the single truth; this is presentation only.
1843
- * Plain-text `/name` / `@name` word-boundary tokens decorate (the sent text
1844
- * IS the reference — the bubble uses the same plainest token
1845
- * scan as the composer, minus the lexicon: sent tokens were validated at
1846
- * compose time, so shape alone decorates).
1847
- */
1848
- function projectUserText(text) {
1849
- const re = /(^|\s)([/@][\w-]+)(?=\s|$)/g;
1850
- const parts = [];
1851
- let cursor = 0;
1852
- let m;
1853
- while ((m = re.exec(text)) !== null) {
1854
- const tokenStart = m.index + (m[1]?.length ?? 0);
1855
- const label = m[2] ?? "";
1856
- if (tokenStart > cursor) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text: text.slice(cursor, tokenStart) }, cursor));
1857
- parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1858
- className: MessageItem_module_css_default.refChip,
1859
- "data-ref-chip": label.startsWith("@") ? "subagent" : "skill",
1860
- children: label
1861
- }, tokenStart));
1862
- cursor = tokenStart + label.length;
1863
- }
1864
- if (parts.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text });
1865
- if (cursor < text.length) parts.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MessageText, { text: text.slice(cursor) }, cursor));
1866
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: parts });
1867
- }
1868
- /** Right-aligned bubble shared by user and steering rows. */
1869
- function UserStyleBubble({ content, renderMessageImages, actions, pending = false, t }) {
1870
- const { text, images, rest } = contentParts(content);
1871
- const truncated = (total) => t("json.truncated", { total });
1872
- const showBubble = text !== "" || rest.length > 0;
1873
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1874
- className: MessageItem_module_css_default.userRow,
1875
- "data-pending-steering": pending || void 0,
1876
- "data-time-hover-root": true,
1877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1878
- className: MessageItem_module_css_default.userStack,
1879
- children: [renderMessageImages({
1880
- images,
1881
- align: "end"
1882
- }), showBubble && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1883
- className: MessageItem_module_css_default.bubble,
1884
- children: [projectUserText(text), rest.map((block, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
1885
- label: t("message.extraBlock"),
1886
- payload: block,
1887
- truncatedLabel: truncated
1888
- }, i))]
1889
- })]
1890
- }), actions?.(text)]
1891
- });
1892
- }
1893
- /** User and admitted-steering keyed Chat renderer. */
1894
- const UserMessageNodeView = (0, react.memo)(function UserMessageNodeView({ node, renderMessageImages, t, edit, retry }) {
1895
- const data = node.data;
1896
- const [editing, setEditing] = (0, react.useState)(null);
1897
- const [confirmingRetry, setConfirmingRetry] = (0, react.useState)(false);
1898
- const turnLocation = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
1899
- const turn = turnLocation?.turn;
1900
- const retryable = turnLocation?.status === "closed";
1901
- const textBlockIndex = data.content.findIndex((block) => block.type === "text");
1902
- const textBlock = textBlockIndex === -1 ? void 0 : data.content[textBlockIndex];
1903
- const onEdit = textBlock === void 0 || turn === void 0 ? void 0 : () => {
1904
- setEditing({
1905
- key: `${node.anchorSeq}:${String(textBlockIndex)}`,
1906
- turn,
1907
- eventSeq: node.anchorSeq,
1908
- blockIndex: textBlockIndex,
1909
- kind: "user",
1910
- text: textBlock.text ?? "",
1911
- time: data.time
1912
- });
1913
- };
1914
- const onRetry = retryable && turn !== void 0 ? () => {
1915
- setConfirmingRetry(true);
1916
- } : void 0;
1917
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1918
- editing !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageEditDialog, {
1919
- block: editing,
1920
- onSave: (text) => edit(editing, text, "truncate"),
1921
- onClose: () => setEditing(null)
1922
- }),
1923
- confirmingRetry && turn !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
1924
- open: true,
1925
- onClose: () => setConfirmingRetry(false),
1926
- title: "重试回合",
1927
- closeLabel: "关闭",
1928
- description: `将重新生成第 ${turn} 轮的回复,并抛弃该回合之后的内容。`,
1929
- footer: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1930
- className: MessageItem_module_css_default.confirmActions,
1931
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1932
- variant: "outline",
1933
- onClick: () => setConfirmingRetry(false),
1934
- children: "取消"
1935
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1936
- variant: "primary",
1937
- onClick: () => {
1938
- setConfirmingRetry(false);
1939
- retry(turn, "truncate");
1940
- },
1941
- children: "确认重试"
1942
- })]
1943
- })
1944
- }),
1945
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserStyleBubble, {
1946
- content: data.content,
1947
- renderMessageImages,
1948
- t,
1949
- actions: (text) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageIconActions, {
1950
- text,
1951
- time: data.time,
1952
- clock: "start",
1953
- className: MessageItem_module_css_default.actions,
1954
- t,
1955
- onEdit,
1956
- onRetry
1957
- })
1958
- })
1959
- ] });
1960
- });
1961
- /** Injected-context keyed Chat renderer. */
1962
- const ContextMessageNodeView = (0, react.memo)(function ContextMessageNodeView({ node, t }) {
1963
- const data = node.data;
1964
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextInjectionRow, {
1965
- content: data.content,
1966
- source: data.source,
1967
- provenance: data.provenance,
1968
- form: data.form,
1969
- t
1970
- });
1971
- });
1972
- /** Automatic compaction keyed Chat renderer. */
1973
- const CompactionNodeView = (0, react.memo)(function CompactionNodeView({ node, t }) {
1974
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CompactionItem, {
1975
- node: node.data,
1976
- t
1977
- });
1978
- });
1979
- /** Correlated retry-chain keyed Chat renderer. */
1980
- const RetryNodeView = (0, react.memo)(function RetryNodeView({ node, t }) {
1981
- const data = node.data;
1982
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRetryItem, {
1983
- node: data.current,
1984
- active: data.current.retryState === "scheduled",
1985
- t
1986
- });
1987
- });
1988
- /** Terminal turn-error keyed Chat renderer. */
1989
- const TurnErrorNodeView = (0, react.memo)(function TurnErrorNodeView({ node, t }) {
1990
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TurnErrorItem, {
1991
- node: node.data,
1992
- t
1993
- });
1994
- });
1995
- /** Max-tokens turn-end notice keyed Chat renderer. */
1996
- const TurnMaxTokensNodeView = (0, react.memo)(function TurnMaxTokensNodeView({ t }) {
1997
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TurnMaxTokensItem, { t });
1998
- });
1999
- /** Explicit unknown-surface keyed Chat renderer. */
2000
- const UnknownNodeView = (0, react.memo)(function UnknownNodeView({ node, t }) {
2001
- const data = node.data;
2002
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2003
- className: MessageItem_module_css_default.contextRow,
2004
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.JsonBlock, {
2005
- label: t("message.unknownSurface", { type: data.type }),
2006
- payload: data.data,
2007
- truncatedLabel: (total) => t("json.truncated", { total })
2008
- })
2009
- });
2010
- });
2011
- //#endregion
2012
- //#region src/client/chat-node/turn-assistant.ts
2013
- /**
2014
- * Collect visible prose from one Assistant lifecycle.
2015
- * @param blocks - Assistant content blocks.
2016
- * @returns concatenated text blocks.
2017
- */
2018
- function assistantText(blocks) {
2019
- return blocks.flatMap((block) => block.kind === "text" ? [block.text] : []).join("");
2020
- }
2021
- //#endregion
2022
- //#region \0dsh-css:/Users/morlay/src/github.com/morlay/better-session/packages/ui-conversation-message-actions/src/client/chat-node/TurnTailNodeView.module.css.mjs
2023
- const css = ".ua5Kda_root{flex-direction:column;gap:16px;display:flex}.ua5Kda_actions{margin-left:-6px}";
2024
- const tagId = "@morlay/ui-conversation-message-actions/TurnTailNodeView.module.css";
2025
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2026
- const tag = document.createElement("style");
2027
- tag.dataset.plugin = "@morlay/ui-conversation-message-actions";
2028
- tag.dataset.pluginCss = tagId;
2029
- tag.textContent = css;
2030
- document.head.appendChild(tag);
2031
- }
2032
- var TurnTailNodeView_module_css_default = {
2033
- "actions": "ua5Kda_actions",
2034
- "root": "ua5Kda_root"
2035
- };
2036
- //#endregion
2037
- //#region src/client/chat-node/TurnTailNodeView.tsx
2038
- /**
2039
- * Turn-local actions over the Location index, independent of Assistant
2040
- * placement.
2041
- *
2042
- * 注意:本 renderer 是 `conversation.chat.node` 的 shadow 注册(priority -1),
2043
- * 而 `renderSlotChain` / `renderSlot` 只在注册声明了对应 children 时由 slot
2044
- * 链传入——turnTail / assistant-actions 的 children 已被上游(ui-conversation)
2045
- * 声明,shadow 注册再声明会抛「already declared」,因此这里不消费子 slot。
2046
- *
2047
- * 编辑 / 重试按钮只挂在 **user 消息**(UserMessageNodeView)上,助手尾部不
2048
- * 提供(避免对未闭合/无 user 输入的轮次误触)。
2049
- */
2050
- const TurnTailNodeView = (0, react.memo)(function TurnTailNodeView({ node, forkAt, t, useSession }) {
2051
- const data = node.data;
2052
- const hasLaterChatNode = useSession((snapshot) => snapshot.chat.locations.getTurn(data.turn).at(-1) !== node.key);
2053
- const turn = node.location.kind === "turn" || node.location.kind === "step" ? node.location.turn : void 0;
2054
- if (turn === void 0) return null;
2055
- const closing = data.closing;
2056
- if (closing === null) return null;
2057
- const runMs = turn.start === void 0 || turn.end === void 0 ? void 0 : Math.max(0, turn.end.time - turn.start.time);
2058
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2059
- className: TurnTailNodeView_module_css_default.root,
2060
- "data-turn-tail": data.turn,
2061
- "data-time-hover-root": true,
2062
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageIconActions, {
2063
- text: assistantText(closing.blocks),
2064
- time: closing.time,
2065
- runMs,
2066
- ttftMs: data.ttftMs,
2067
- tokensPerSecond: data.tokensPerSecond,
2068
- clock: "end",
2069
- onBranch: () => {
2070
- forkAt(closing.finalNode.seq);
2071
- },
2072
- branchUnavailable: data.branchUnavailable || hasLaterChatNode,
2073
- className: TurnTailNodeView_module_css_default.actions,
2074
- t
2075
- })
2076
- });
2077
- });
2078
- //#endregion
2079
- //#region src/client/chat-node/register.ts
2080
- /** `conversation` 字典命名空间(与上游一致)。 */
2081
- const NS = "conversation";
2082
- /** 注册全部 chat-node key;priority -1 替换上游渲染。 */
2083
- function registerChatNodeRenderers(ctx, controllerFor) {
2084
- const inject = (sessionId) => controllerFor(sessionId).face;
2085
- ctx.slots.register({
2086
- name: "conversation.chat.node",
2087
- key: "user",
2088
- locale: NS,
2089
- priority: -1,
2090
- inject
2091
- }, UserMessageNodeView);
2092
- ctx.slots.register({
2093
- name: "conversation.chat.node",
2094
- key: "steering",
2095
- locale: NS,
2096
- priority: -1,
2097
- inject
2098
- }, UserMessageNodeView);
2099
- ctx.slots.register({
2100
- name: "conversation.chat.node",
2101
- key: "context",
2102
- locale: NS,
2103
- priority: -1,
2104
- inject
2105
- }, ContextMessageNodeView);
2106
- ctx.slots.register({
2107
- name: "conversation.chat.node",
2108
- key: "assistant-step",
2109
- locale: NS,
2110
- priority: -1,
2111
- inject
2112
- }, AssistantNodeView);
2113
- ctx.slots.register({
2114
- name: "conversation.chat.node",
2115
- key: "command",
2116
- locale: NS,
2117
- priority: -1,
2118
- inject
2119
- }, CommandNodeView);
2120
- ctx.slots.register({
2121
- name: "conversation.chat.node",
2122
- key: "manual-compaction",
2123
- locale: NS,
2124
- priority: -1,
2125
- inject
2126
- }, ManualCompactionNodeView);
2127
- ctx.slots.register({
2128
- name: "conversation.chat.node",
2129
- key: "compaction",
2130
- locale: NS,
2131
- priority: -1,
2132
- inject
2133
- }, CompactionNodeView);
2134
- ctx.slots.register({
2135
- name: "conversation.chat.node",
2136
- key: "model-retry",
2137
- locale: NS,
2138
- priority: -1,
2139
- inject
2140
- }, RetryNodeView);
2141
- ctx.slots.register({
2142
- name: "conversation.chat.node",
2143
- key: "turn-error",
2144
- locale: NS,
2145
- priority: -1,
2146
- inject
2147
- }, TurnErrorNodeView);
2148
- ctx.slots.register({
2149
- name: "conversation.chat.node",
2150
- key: "turn-max-tokens",
2151
- locale: NS,
2152
- priority: -1,
2153
- inject
2154
- }, TurnMaxTokensNodeView);
2155
- ctx.slots.register({
2156
- name: "conversation.chat.node",
2157
- key: "turn-tail",
2158
- locale: NS,
2159
- priority: -1,
2160
- inject
2161
- }, TurnTailNodeView);
2162
- ctx.slots.register({
2163
- name: "conversation.chat.node",
2164
- key: "unknown",
2165
- locale: NS,
2166
- priority: -1,
2167
- inject
2168
- }, UnknownNodeView);
2169
- }
2170
- //#endregion
2171
- //#region src/client/cleanse-session-action.tsx
2172
- /**
2173
- * 会话头部「清洗会话」入口。
2174
- *
2175
- * 仅当会话历史加载失败(会话 snapshot 的 `openState === "error"`)时渲染:
2176
- * 点击调用服务端 cleanse,把该会话的 sourceEventSeqs / surfaceOp /
2177
- * shadowedRange 坐标一次性重写为稠密空间,成功后客户端 resync 重新加载
2178
- * 历史。注入面来自 `SessionEditorFace`(hooks → `useSessionEditor`,
2179
- * 方法 → `cleanse`)。
2180
- */
2181
- /**
2182
- * 历史加载失败时显示「清洗会话」按钮。
2183
- * @param props - 注入的 selector 钩子与清洗方法。
2184
- * @returns 按钮元素;会话未处于错误状态时返回 null。
2185
- */
2186
- function CleanseSessionAction({ useSessionEditor, cleanse }) {
2187
- const openError = useSessionEditor((s) => s.sessionOpenError);
2188
- const cleansing = useSessionEditor((s) => s.pending === "cleanse");
2189
- const [busy, setBusy] = (0, react.useState)(false);
2190
- const onClick = (0, react.useCallback)(() => {
2191
- setBusy(true);
2192
- cleanse().finally(() => setBusy(false));
2193
- }, [cleanse]);
2194
- if (!openError) return null;
2195
- const working = busy || cleansing;
2196
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
2197
- variant: "outline",
2198
- size: "sm",
2199
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, {}),
2200
- onClick,
2201
- disabled: working,
2202
- title: "清洗会话:把 provenance 坐标重写为稠密空间,修复历史加载失败",
2203
- children: working ? "清洗中…" : "清洗会话"
2204
- });
2205
- }
2206
- //#endregion
2207
- //#region src/client/index.ts
2208
- const inject = [
2209
- "slots",
2210
- "conversation",
2211
- "connection",
2212
- "sessions"
2213
- ];
2214
- /** 注册 UI 贡献 + chat-node renderer,共享同一 per-session controller。 */
2215
- function apply(ctx) {
2216
- const controllers = /* @__PURE__ */ new Map();
2217
- const controllerFor = (sessionId) => {
2218
- let controller = controllers.get(sessionId);
2219
- if (controller === void 0) {
2220
- controller = new SessionEditorController(ctx, sessionId);
2221
- controllers.set(sessionId, controller);
2222
- }
2223
- return controller;
2224
- };
2225
- ctx.on("connection/reset", () => {
2226
- for (const controller of controllers.values()) controller.load();
2227
- });
2228
- registerChatNodeRenderers(ctx, controllerFor);
2229
- ctx.slots.register({
2230
- name: "conversation.session.header.actions",
2231
- id: "session-editor.cleanse",
2232
- priority: -1,
2233
- inject: (sessionId) => controllerFor(sessionId).face
2234
- }, CleanseSessionAction);
2235
- }
2236
- //#endregion
2237
- exports.apply = apply;
2238
- exports.inject = inject;
2239
- return module.exports;
2240
- }
2241
- });
2242
-
2243
- //# sourceMappingURL=client.js.map