@morlay/ui-conversation-message-actions 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +87 -0
- package/cordis.patch.yml +9 -0
- package/lib/client.js +2243 -0
- package/lib/client.js.map +1 -0
- package/lib/index.d.mts +208 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +675 -0
- package/lib/index.mjs.map +1 -0
- package/lib/invariant.d.mts +15 -0
- package/lib/invariant.d.mts.map +1 -0
- package/lib/invariant.mjs +22 -0
- package/lib/invariant.mjs.map +1 -0
- package/package.json +77 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,2243 @@
|
|
|
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
|
+
"row": "puL2qa_row",
|
|
305
|
+
"thinkBody": "puL2qa_thinkBody",
|
|
306
|
+
"chevron": "puL2qa_chevron",
|
|
307
|
+
"root": "puL2qa_root",
|
|
308
|
+
"dsh-reasoning-row-sweep": "puL2qa_dsh-reasoning-row-sweep",
|
|
309
|
+
"title": "puL2qa_title",
|
|
310
|
+
"separator": "puL2qa_separator",
|
|
311
|
+
"summary": "puL2qa_summary",
|
|
312
|
+
"leading": "puL2qa_leading"
|
|
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
|
+
"body": "KOyxmq_body",
|
|
398
|
+
"stopped": "KOyxmq_stopped",
|
|
399
|
+
"actions": "KOyxmq_actions",
|
|
400
|
+
"root": "KOyxmq_root"
|
|
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
|
+
"turnErrorMessage": "okW0Ua_turnErrorMessage",
|
|
511
|
+
"compactionSep": "okW0Ua_compactionSep",
|
|
512
|
+
"compactionTitle": "okW0Ua_compactionTitle",
|
|
513
|
+
"compactionSummary": "okW0Ua_compactionSummary",
|
|
514
|
+
"retryText": "okW0Ua_retryText",
|
|
515
|
+
"compactionContextIcon": "okW0Ua_compactionContextIcon",
|
|
516
|
+
"retryRow": "okW0Ua_retryRow",
|
|
517
|
+
"retryDetailLabel": "okW0Ua_retryDetailLabel",
|
|
518
|
+
"maxTokensTitle": "okW0Ua_maxTokensTitle",
|
|
519
|
+
"userRow": "okW0Ua_userRow",
|
|
520
|
+
"compactionLeading": "okW0Ua_compactionLeading",
|
|
521
|
+
"refChip": "okW0Ua_refChip",
|
|
522
|
+
"confirmActions": "okW0Ua_confirmActions",
|
|
523
|
+
"bubble": "okW0Ua_bubble",
|
|
524
|
+
"turnErrorRow": "okW0Ua_turnErrorRow",
|
|
525
|
+
"turnErrorCopy": "okW0Ua_turnErrorCopy",
|
|
526
|
+
"contextRow": "okW0Ua_contextRow",
|
|
527
|
+
"userStack": "okW0Ua_userStack",
|
|
528
|
+
"turnErrorCode": "okW0Ua_turnErrorCode",
|
|
529
|
+
"retrySummary": "okW0Ua_retrySummary",
|
|
530
|
+
"retry-shimmer": "okW0Ua_retry-shimmer",
|
|
531
|
+
"compactionButton": "okW0Ua_compactionButton",
|
|
532
|
+
"turnErrorDot": "okW0Ua_turnErrorDot",
|
|
533
|
+
"compactionRow": "okW0Ua_compactionRow",
|
|
534
|
+
"compactionBody": "okW0Ua_compactionBody",
|
|
535
|
+
"compactionDisclosureIcon": "okW0Ua_compactionDisclosureIcon",
|
|
536
|
+
"retryDetails": "okW0Ua_retryDetails",
|
|
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
|
+
"chevron": "XF94Cq_chevron",
|
|
611
|
+
"dsh-command-row-sweep": "XF94Cq_dsh-command-row-sweep",
|
|
612
|
+
"separator": "XF94Cq_separator",
|
|
613
|
+
"leading": "XF94Cq_leading",
|
|
614
|
+
"row": "XF94Cq_row",
|
|
615
|
+
"root": "XF94Cq_root",
|
|
616
|
+
"summary": "XF94Cq_summary",
|
|
617
|
+
"body": "XF94Cq_body"
|
|
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
|
+
"fieldValue": "emmM3G_fieldValue",
|
|
757
|
+
"fieldKey": "emmM3G_fieldKey",
|
|
758
|
+
"catalogNotice": "emmM3G_catalogNotice",
|
|
759
|
+
"files": "emmM3G_files",
|
|
760
|
+
"fileAction": "emmM3G_fileAction",
|
|
761
|
+
"text": "emmM3G_text",
|
|
762
|
+
"sectionName": "emmM3G_sectionName",
|
|
763
|
+
"file": "emmM3G_file",
|
|
764
|
+
"relaySender": "emmM3G_relaySender",
|
|
765
|
+
"recall": "emmM3G_recall",
|
|
766
|
+
"entries": "emmM3G_entries",
|
|
767
|
+
"entry": "emmM3G_entry",
|
|
768
|
+
"fields": "emmM3G_fields",
|
|
769
|
+
"filePath": "emmM3G_filePath",
|
|
770
|
+
"sections": "emmM3G_sections",
|
|
771
|
+
"recallCounts": "emmM3G_recallCounts",
|
|
772
|
+
"field": "emmM3G_field",
|
|
773
|
+
"entryName": "emmM3G_entryName",
|
|
774
|
+
"entryDescription": "emmM3G_entryDescription",
|
|
775
|
+
"sectionText": "emmM3G_sectionText",
|
|
776
|
+
"recalls": "emmM3G_recalls",
|
|
777
|
+
"recallLabel": "emmM3G_recallLabel",
|
|
778
|
+
"section": "emmM3G_section"
|
|
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
|
+
"sep": "zwBaJG_sep",
|
|
1314
|
+
"body": "zwBaJG_body",
|
|
1315
|
+
"chevron": "zwBaJG_chevron",
|
|
1316
|
+
"root": "zwBaJG_root",
|
|
1317
|
+
"summary": "zwBaJG_summary",
|
|
1318
|
+
"source": "zwBaJG_source"
|
|
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
|
+
"actions": "lKOSUG_actions",
|
|
1495
|
+
"action": "lKOSUG_action",
|
|
1496
|
+
"timeEnd": "lKOSUG_timeEnd",
|
|
1497
|
+
"timeStart": "lKOSUG_timeStart",
|
|
1498
|
+
"runTimeDot": "lKOSUG_runTimeDot",
|
|
1499
|
+
"visuallyHidden": "lKOSUG_visuallyHidden"
|
|
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
|