langchain_agentx_stream_ui 0.1.2 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AskUserQuestionToolBody-CyuNy_k5.d.ts → AskUserQuestionToolBody-BG1StdvE.d.ts} +3 -3
- package/dist/{MarkdownBlock-3iEW0Ubm.d.ts → MarkdownBlock-CcXZ5nTm.d.ts} +1 -1
- package/dist/{chunk-WM6Y6APP.js → chunk-6DYISADG.js} +2 -2
- package/dist/{chunk-MHK53ZHC.js → chunk-7CLAU74Y.js} +216 -56
- package/dist/chunk-7CLAU74Y.js.map +1 -0
- package/dist/{chunk-3B4WC67E.js → chunk-LBJFJF22.js} +307 -44
- package/dist/chunk-LBJFJF22.js.map +1 -0
- package/dist/chunk-WPW3TQQF.js +1900 -0
- package/dist/chunk-WPW3TQQF.js.map +1 -0
- package/dist/default.css +445 -31
- package/dist/index.d.ts +78 -31
- package/dist/index.js +46 -11
- package/dist/index.js.map +1 -1
- package/dist/{multi-session-DAHwfTNb.d.ts → multi-session-Dxyo3y7w.d.ts} +23 -3
- package/dist/multi-session.d.ts +4 -4
- package/dist/multi-session.js +4 -4
- package/dist/{nodes-DqhaBW7M.d.ts → nodes-BcyPw6Fr.d.ts} +12 -1
- package/dist/{sessionViewOptions-DLSFRQwZ.d.ts → sessionViewOptions-WM00Fpcn.d.ts} +1 -1
- package/dist/tools-presentation.d.ts +3 -3
- package/dist/tools.d.ts +5 -5
- package/dist/tools.js +2 -2
- package/dist/{types-aXj5TYSP.d.ts → types-BEXSHQG7.d.ts} +2 -1
- package/dist/virtual.d.ts +1 -1
- package/dist/virtual.js +2 -2
- package/package.json +6 -5
- package/dist/chunk-3B4WC67E.js.map +0 -1
- package/dist/chunk-A4W6B7C3.js +0 -1429
- package/dist/chunk-A4W6B7C3.js.map +0 -1
- package/dist/chunk-MHK53ZHC.js.map +0 -1
- /package/dist/{chunk-WM6Y6APP.js.map → chunk-6DYISADG.js.map} +0 -0
|
@@ -0,0 +1,1900 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InteractionBusContext,
|
|
3
|
+
ToolCallShell,
|
|
4
|
+
formatToolTitle,
|
|
5
|
+
getNoopInteractionBus,
|
|
6
|
+
resolveToolBody,
|
|
7
|
+
useInteractionBus
|
|
8
|
+
} from "./chunk-6DYISADG.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_SESSION_VIEW_OPTIONS,
|
|
11
|
+
MultiSessionStoreContext,
|
|
12
|
+
NodeRegistryContext,
|
|
13
|
+
SessionStoreContext,
|
|
14
|
+
SessionTimeline,
|
|
15
|
+
SessionViewOptionsContext,
|
|
16
|
+
ToolDisplayOptionsContext,
|
|
17
|
+
createEmptyTree,
|
|
18
|
+
reduceTree,
|
|
19
|
+
replayEvents,
|
|
20
|
+
resolveEffectiveToolBodyMode,
|
|
21
|
+
useInternalErrors,
|
|
22
|
+
useNodeTyped,
|
|
23
|
+
usePendingPermissionForTool,
|
|
24
|
+
useSessionStatus,
|
|
25
|
+
useSessionViewOptions,
|
|
26
|
+
useToolDisplayOptions
|
|
27
|
+
} from "./chunk-LBJFJF22.js";
|
|
28
|
+
import {
|
|
29
|
+
createDefaultToolRegistry
|
|
30
|
+
} from "./chunk-7CLAU74Y.js";
|
|
31
|
+
import {
|
|
32
|
+
MarkdownRendererContext
|
|
33
|
+
} from "./chunk-4RIOBLGB.js";
|
|
34
|
+
|
|
35
|
+
// src/view/markdown/diagramSmartFit.ts
|
|
36
|
+
function parseSvgLength(attr) {
|
|
37
|
+
if (!attr) return NaN;
|
|
38
|
+
const trimmed = attr.trim();
|
|
39
|
+
if (trimmed.endsWith("%")) return NaN;
|
|
40
|
+
const value = parseFloat(trimmed);
|
|
41
|
+
return Number.isFinite(value) && value > 0 ? value : NaN;
|
|
42
|
+
}
|
|
43
|
+
function resolveSvgContentSize(svg) {
|
|
44
|
+
const baseVal = svg.viewBox?.baseVal;
|
|
45
|
+
if (baseVal && baseVal.width > 0 && baseVal.height > 0) {
|
|
46
|
+
return { width: baseVal.width, height: baseVal.height };
|
|
47
|
+
}
|
|
48
|
+
const raw = svg.getAttribute("viewBox");
|
|
49
|
+
if (raw) {
|
|
50
|
+
const parts = raw.trim().split(/[\s,]+/).map(Number);
|
|
51
|
+
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
|
|
52
|
+
return { width: parts[2], height: parts[3] };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const parsedW = parseSvgLength(svg.getAttribute("width"));
|
|
56
|
+
const parsedH = parseSvgLength(svg.getAttribute("height"));
|
|
57
|
+
return {
|
|
58
|
+
width: Number.isFinite(parsedW) ? parsedW : svg.clientWidth || 0,
|
|
59
|
+
height: Number.isFinite(parsedH) ? parsedH : svg.clientHeight || 0
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function prepareSvgForInteraction(svg, layout = "fill") {
|
|
63
|
+
svg.style.pointerEvents = "none";
|
|
64
|
+
svg.style.display = "block";
|
|
65
|
+
if (layout === "natural") {
|
|
66
|
+
svg.style.maxWidth = "100%";
|
|
67
|
+
svg.style.width = "auto";
|
|
68
|
+
svg.style.height = "auto";
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
svg.style.maxWidth = "none";
|
|
72
|
+
const { width, height } = resolveSvgContentSize(svg);
|
|
73
|
+
const widthAttr = svg.getAttribute("width") ?? "";
|
|
74
|
+
const heightAttr = svg.getAttribute("height") ?? "";
|
|
75
|
+
if (width > 0 && (!widthAttr || widthAttr.includes("%"))) {
|
|
76
|
+
svg.setAttribute("width", String(width));
|
|
77
|
+
svg.style.width = `${width}px`;
|
|
78
|
+
}
|
|
79
|
+
if (height > 0 && (!heightAttr || heightAttr.includes("%"))) {
|
|
80
|
+
svg.setAttribute("height", String(height));
|
|
81
|
+
svg.style.height = `${height}px`;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readElementLayoutSize(element) {
|
|
85
|
+
const rect = element.getBoundingClientRect();
|
|
86
|
+
if (rect.width > 1 && rect.height > 1) {
|
|
87
|
+
return { width: rect.width, height: rect.height };
|
|
88
|
+
}
|
|
89
|
+
if (element instanceof HTMLElement && element.offsetWidth > 1 && element.offsetHeight > 1) {
|
|
90
|
+
return { width: element.offsetWidth, height: element.offsetHeight };
|
|
91
|
+
}
|
|
92
|
+
return { width: 0, height: 0 };
|
|
93
|
+
}
|
|
94
|
+
function measureDiagramContentSize(svg, layout = "fill") {
|
|
95
|
+
prepareSvgForInteraction(svg, layout);
|
|
96
|
+
const host = svg.parentElement;
|
|
97
|
+
if (host instanceof HTMLElement) {
|
|
98
|
+
const hostSize = readElementLayoutSize(host);
|
|
99
|
+
if (hostSize.width > 1 && hostSize.height > 1) {
|
|
100
|
+
return hostSize;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const svgSize = readElementLayoutSize(svg);
|
|
104
|
+
if (svgSize.width > 1 && svgSize.height > 1) {
|
|
105
|
+
return svgSize;
|
|
106
|
+
}
|
|
107
|
+
return resolveSvgContentSize(svg);
|
|
108
|
+
}
|
|
109
|
+
function computeViewportFitTransform(container, svg, options = {}) {
|
|
110
|
+
const layout = options.layout ?? "fill";
|
|
111
|
+
const padding = options.padding ?? (layout === "natural" ? 1 : 0.96);
|
|
112
|
+
if (layout === "natural") {
|
|
113
|
+
prepareSvgForInteraction(svg, "natural");
|
|
114
|
+
return { scale: 1, x: 0, y: 0 };
|
|
115
|
+
}
|
|
116
|
+
const { width: contentW, height: contentH } = measureDiagramContentSize(svg, layout);
|
|
117
|
+
const cw = container.clientWidth;
|
|
118
|
+
const ch = container.clientHeight;
|
|
119
|
+
if (contentW < 1 || contentH < 1 || cw < 8 || ch < 32) {
|
|
120
|
+
return { x: 0, y: 0, scale: 1 };
|
|
121
|
+
}
|
|
122
|
+
const maxScale = options.maxScale ?? Number.POSITIVE_INFINITY;
|
|
123
|
+
const scaleX = cw * padding / contentW;
|
|
124
|
+
const scaleY = ch * padding / contentH;
|
|
125
|
+
const scale = Math.min(scaleX, scaleY, maxScale);
|
|
126
|
+
return {
|
|
127
|
+
scale,
|
|
128
|
+
x: (cw - contentW * scale) / 2,
|
|
129
|
+
y: (ch - contentH * scale) / 2
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/view/markdown/DiagramZoomShell.tsx
|
|
134
|
+
import {
|
|
135
|
+
useCallback,
|
|
136
|
+
useLayoutEffect,
|
|
137
|
+
useRef,
|
|
138
|
+
useState
|
|
139
|
+
} from "react";
|
|
140
|
+
import {
|
|
141
|
+
TransformComponent,
|
|
142
|
+
TransformWrapper
|
|
143
|
+
} from "react-zoom-pan-pinch";
|
|
144
|
+
|
|
145
|
+
// src/view/markdown/fitDiagramToContainer.ts
|
|
146
|
+
function fitDiagramToContainer(ctx, container, options = {}) {
|
|
147
|
+
const svg = container.querySelector("svg");
|
|
148
|
+
if (!(svg instanceof SVGSVGElement)) return 1;
|
|
149
|
+
const measureContainer = container.querySelector(".react-transform-wrapper");
|
|
150
|
+
const target = measureContainer instanceof HTMLElement ? measureContainer : container;
|
|
151
|
+
const { x, y, scale } = computeViewportFitTransform(target, svg, options);
|
|
152
|
+
ctx.resetTransform(0);
|
|
153
|
+
ctx.setTransform(x, y, scale, 0);
|
|
154
|
+
return scale;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/view/markdown/DiagramZoomShell.tsx
|
|
158
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
159
|
+
var MIN_VIEWPORT_PX = 8;
|
|
160
|
+
var MAX_INITIAL_FIT_ATTEMPTS = 48;
|
|
161
|
+
function DiagramZoomShell({
|
|
162
|
+
fixedSvg,
|
|
163
|
+
contentHash,
|
|
164
|
+
className = "",
|
|
165
|
+
layout = "fill",
|
|
166
|
+
viewportPointerHandlers,
|
|
167
|
+
toolbarExtra
|
|
168
|
+
}) {
|
|
169
|
+
const viewportRef = useRef(null);
|
|
170
|
+
const transformRef = useRef(null);
|
|
171
|
+
const fittedForRef = useRef(null);
|
|
172
|
+
const fittingRef = useRef(false);
|
|
173
|
+
const [zoomPct, setZoomPct] = useState(100);
|
|
174
|
+
const isNatural = layout === "natural";
|
|
175
|
+
const wrapperStyle = isNatural ? { width: "100%", height: "auto" } : { width: "100%", height: "100%" };
|
|
176
|
+
const applyFit = useCallback(() => {
|
|
177
|
+
if (fittingRef.current) return;
|
|
178
|
+
const viewport = viewportRef.current;
|
|
179
|
+
const ctx = transformRef.current;
|
|
180
|
+
if (!viewport || !ctx) return;
|
|
181
|
+
fittingRef.current = true;
|
|
182
|
+
try {
|
|
183
|
+
const svg = viewport.querySelector("svg");
|
|
184
|
+
if (svg instanceof SVGSVGElement && isNatural) {
|
|
185
|
+
prepareSvgForInteraction(svg, "natural");
|
|
186
|
+
}
|
|
187
|
+
const scale = fitDiagramToContainer(ctx, viewport, {
|
|
188
|
+
layout,
|
|
189
|
+
maxScale: isNatural ? 1 : Number.POSITIVE_INFINITY
|
|
190
|
+
});
|
|
191
|
+
setZoomPct((prev) => {
|
|
192
|
+
const next = Math.round(scale * 100);
|
|
193
|
+
return prev === next ? prev : next;
|
|
194
|
+
});
|
|
195
|
+
} finally {
|
|
196
|
+
fittingRef.current = false;
|
|
197
|
+
}
|
|
198
|
+
}, [isNatural, layout]);
|
|
199
|
+
const runInitialFit = useCallback(() => {
|
|
200
|
+
if (fittedForRef.current === contentHash) return;
|
|
201
|
+
const viewport = viewportRef.current;
|
|
202
|
+
const ctx = transformRef.current;
|
|
203
|
+
const svg = viewport?.querySelector("svg");
|
|
204
|
+
const minH = isNatural ? MIN_VIEWPORT_PX : 32;
|
|
205
|
+
if (!viewport || !ctx || !(svg instanceof SVGSVGElement) || viewport.clientWidth < MIN_VIEWPORT_PX || !isNatural && viewport.clientHeight < minH) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
applyFit();
|
|
209
|
+
fittedForRef.current = contentHash;
|
|
210
|
+
}, [applyFit, contentHash, isNatural]);
|
|
211
|
+
useLayoutEffect(() => {
|
|
212
|
+
fittedForRef.current = null;
|
|
213
|
+
}, [contentHash, fixedSvg, layout]);
|
|
214
|
+
useLayoutEffect(() => {
|
|
215
|
+
if (!fixedSvg) return;
|
|
216
|
+
let cancelled = false;
|
|
217
|
+
let attempts = 0;
|
|
218
|
+
const tryFit = () => {
|
|
219
|
+
if (cancelled || fittedForRef.current === contentHash) return;
|
|
220
|
+
runInitialFit();
|
|
221
|
+
if (fittedForRef.current === contentHash) return;
|
|
222
|
+
attempts += 1;
|
|
223
|
+
if (attempts < MAX_INITIAL_FIT_ATTEMPTS) window.requestAnimationFrame(tryFit);
|
|
224
|
+
};
|
|
225
|
+
tryFit();
|
|
226
|
+
return () => {
|
|
227
|
+
cancelled = true;
|
|
228
|
+
};
|
|
229
|
+
}, [fixedSvg, contentHash, layout, runInitialFit]);
|
|
230
|
+
const zoomIn = useCallback(() => {
|
|
231
|
+
transformRef.current?.zoomIn();
|
|
232
|
+
}, []);
|
|
233
|
+
const zoomOut = useCallback(() => {
|
|
234
|
+
transformRef.current?.zoomOut();
|
|
235
|
+
}, []);
|
|
236
|
+
return /* @__PURE__ */ jsxs(
|
|
237
|
+
"div",
|
|
238
|
+
{
|
|
239
|
+
className: [
|
|
240
|
+
"diagram-zoom-shell",
|
|
241
|
+
isNatural ? "diagram-zoom-shell--natural" : "diagram-zoom-shell--fill",
|
|
242
|
+
className
|
|
243
|
+
].filter(Boolean).join(" "),
|
|
244
|
+
children: [
|
|
245
|
+
/* @__PURE__ */ jsxs(
|
|
246
|
+
"div",
|
|
247
|
+
{
|
|
248
|
+
className: "diagram-zoom-shell__toolbar",
|
|
249
|
+
onPointerDown: (event) => event.stopPropagation(),
|
|
250
|
+
onClick: (event) => event.stopPropagation(),
|
|
251
|
+
children: [
|
|
252
|
+
/* @__PURE__ */ jsx(ShellButton, { label: "Zoom in", onClick: zoomIn, children: "+" }),
|
|
253
|
+
/* @__PURE__ */ jsxs("span", { className: "diagram-zoom-shell__zoom", children: [
|
|
254
|
+
zoomPct,
|
|
255
|
+
"%"
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ jsx(ShellButton, { label: "Zoom out", onClick: zoomOut, children: "\u2212" }),
|
|
258
|
+
/* @__PURE__ */ jsx(ShellButton, { label: "Fit diagram", onClick: applyFit, children: "\u21BA" }),
|
|
259
|
+
toolbarExtra
|
|
260
|
+
]
|
|
261
|
+
}
|
|
262
|
+
),
|
|
263
|
+
/* @__PURE__ */ jsx(
|
|
264
|
+
"div",
|
|
265
|
+
{
|
|
266
|
+
ref: viewportRef,
|
|
267
|
+
className: [
|
|
268
|
+
"diagram-zoom-shell__viewport",
|
|
269
|
+
isNatural ? "diagram-zoom-shell__viewport--natural" : ""
|
|
270
|
+
].filter(Boolean).join(" "),
|
|
271
|
+
...viewportPointerHandlers,
|
|
272
|
+
children: /* @__PURE__ */ jsx(
|
|
273
|
+
TransformWrapper,
|
|
274
|
+
{
|
|
275
|
+
minScale: 0.1,
|
|
276
|
+
maxScale: 4,
|
|
277
|
+
centerOnInit: false,
|
|
278
|
+
limitToBounds: false,
|
|
279
|
+
wheel: { step: 0.07 },
|
|
280
|
+
panning: { velocityDisabled: true },
|
|
281
|
+
doubleClick: { disabled: true },
|
|
282
|
+
onInit: (ctx) => {
|
|
283
|
+
transformRef.current = ctx;
|
|
284
|
+
},
|
|
285
|
+
onTransformed: (_ctx, state) => {
|
|
286
|
+
const next = Math.round(state.scale * 100);
|
|
287
|
+
setZoomPct((prev) => prev === next ? prev : next);
|
|
288
|
+
},
|
|
289
|
+
children: /* @__PURE__ */ jsx(
|
|
290
|
+
TransformComponent,
|
|
291
|
+
{
|
|
292
|
+
wrapperStyle,
|
|
293
|
+
contentClass: "!block !w-fit !h-fit",
|
|
294
|
+
children: /* @__PURE__ */ jsx(
|
|
295
|
+
"div",
|
|
296
|
+
{
|
|
297
|
+
className: [
|
|
298
|
+
"diagram-zoom-shell__content",
|
|
299
|
+
isNatural ? "block w-full [&_svg]:mx-auto" : "inline-block [&_svg]:block [&_svg]:max-w-none"
|
|
300
|
+
].join(" "),
|
|
301
|
+
dangerouslySetInnerHTML: { __html: fixedSvg }
|
|
302
|
+
}
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
},
|
|
307
|
+
contentHash
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
]
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
function ShellButton({
|
|
316
|
+
label,
|
|
317
|
+
onClick,
|
|
318
|
+
children
|
|
319
|
+
}) {
|
|
320
|
+
return /* @__PURE__ */ jsx(
|
|
321
|
+
"button",
|
|
322
|
+
{
|
|
323
|
+
type: "button",
|
|
324
|
+
"aria-label": label,
|
|
325
|
+
title: label,
|
|
326
|
+
className: "diagram-zoom-shell__btn",
|
|
327
|
+
onClick,
|
|
328
|
+
children
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/view/markdown/DiagramZoomView.tsx
|
|
334
|
+
import { useState as useState2 } from "react";
|
|
335
|
+
|
|
336
|
+
// src/view/markdown/DiagramZoomFullscreenOverlay.tsx
|
|
337
|
+
import { useEffect } from "react";
|
|
338
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
339
|
+
function DiagramZoomFullscreenOverlay({
|
|
340
|
+
isOpen,
|
|
341
|
+
title,
|
|
342
|
+
onClose,
|
|
343
|
+
headerControls,
|
|
344
|
+
children
|
|
345
|
+
}) {
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (!isOpen) return;
|
|
348
|
+
const onKeyDown = (event) => {
|
|
349
|
+
if (event.key === "Escape") onClose();
|
|
350
|
+
};
|
|
351
|
+
const prevOverflow = document.body.style.overflow;
|
|
352
|
+
document.body.style.overflow = "hidden";
|
|
353
|
+
window.addEventListener("keydown", onKeyDown);
|
|
354
|
+
return () => {
|
|
355
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
356
|
+
document.body.style.overflow = prevOverflow;
|
|
357
|
+
};
|
|
358
|
+
}, [isOpen, onClose]);
|
|
359
|
+
if (!isOpen) return null;
|
|
360
|
+
return /* @__PURE__ */ jsx2(
|
|
361
|
+
"div",
|
|
362
|
+
{
|
|
363
|
+
className: "diagram-zoom-fs-overlay",
|
|
364
|
+
role: "dialog",
|
|
365
|
+
"aria-modal": "true",
|
|
366
|
+
"aria-label": title,
|
|
367
|
+
onClick: (event) => {
|
|
368
|
+
if (event.target === event.currentTarget) onClose();
|
|
369
|
+
},
|
|
370
|
+
children: /* @__PURE__ */ jsxs2(
|
|
371
|
+
"div",
|
|
372
|
+
{
|
|
373
|
+
className: "diagram-zoom-fs-panel",
|
|
374
|
+
onClick: (event) => event.stopPropagation(),
|
|
375
|
+
children: [
|
|
376
|
+
/* @__PURE__ */ jsxs2("div", { className: "diagram-zoom-fs-header", children: [
|
|
377
|
+
/* @__PURE__ */ jsx2("span", { className: "diagram-zoom-fs-title", children: title }),
|
|
378
|
+
headerControls
|
|
379
|
+
] }),
|
|
380
|
+
/* @__PURE__ */ jsx2("div", { className: "diagram-zoom-fs-stage", children })
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/view/markdown/usePointerClick.ts
|
|
389
|
+
import { useRef as useRef2 } from "react";
|
|
390
|
+
var DEFAULT_MOVE_THRESHOLD_PX = 6;
|
|
391
|
+
function didPointerDrag(start, end, moveThresholdPx = DEFAULT_MOVE_THRESHOLD_PX) {
|
|
392
|
+
return Math.hypot(end.x - start.x, end.y - start.y) > moveThresholdPx;
|
|
393
|
+
}
|
|
394
|
+
function usePointerClick(onClick, moveThresholdPx = DEFAULT_MOVE_THRESHOLD_PX) {
|
|
395
|
+
const startRef = useRef2(null);
|
|
396
|
+
const movedRef = useRef2(false);
|
|
397
|
+
return {
|
|
398
|
+
onPointerDown(event) {
|
|
399
|
+
startRef.current = { x: event.clientX, y: event.clientY };
|
|
400
|
+
movedRef.current = false;
|
|
401
|
+
},
|
|
402
|
+
onPointerMove(event) {
|
|
403
|
+
const start = startRef.current;
|
|
404
|
+
if (!start) return;
|
|
405
|
+
const dx = event.clientX - start.x;
|
|
406
|
+
const dy = event.clientY - start.y;
|
|
407
|
+
if (Math.hypot(dx, dy) > moveThresholdPx) {
|
|
408
|
+
movedRef.current = true;
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
onPointerUp(event) {
|
|
412
|
+
const start = startRef.current;
|
|
413
|
+
if (start && didPointerDrag(start, { x: event.clientX, y: event.clientY }, moveThresholdPx)) {
|
|
414
|
+
movedRef.current = true;
|
|
415
|
+
}
|
|
416
|
+
if (!movedRef.current && startRef.current) {
|
|
417
|
+
onClick();
|
|
418
|
+
}
|
|
419
|
+
startRef.current = null;
|
|
420
|
+
movedRef.current = false;
|
|
421
|
+
},
|
|
422
|
+
onPointerCancel() {
|
|
423
|
+
startRef.current = null;
|
|
424
|
+
movedRef.current = false;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/view/markdown/DiagramZoomView.tsx
|
|
430
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
431
|
+
function DiagramZoomView({
|
|
432
|
+
fixedSvg,
|
|
433
|
+
contentHash,
|
|
434
|
+
title,
|
|
435
|
+
fullscreenEnabled = true,
|
|
436
|
+
className = ""
|
|
437
|
+
}) {
|
|
438
|
+
const [isFullscreen, setIsFullscreen] = useState2(false);
|
|
439
|
+
const [inlineRestoreKey, setInlineRestoreKey] = useState2(0);
|
|
440
|
+
const openFullscreen = () => {
|
|
441
|
+
if (fixedSvg) setIsFullscreen(true);
|
|
442
|
+
};
|
|
443
|
+
const closeFullscreen = () => {
|
|
444
|
+
setIsFullscreen(false);
|
|
445
|
+
setInlineRestoreKey((key) => key + 1);
|
|
446
|
+
};
|
|
447
|
+
const canvasPointer = usePointerClick(openFullscreen);
|
|
448
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
449
|
+
/* @__PURE__ */ jsx3("div", { className: `diagram-zoom-inline ${className}`.trim(), children: /* @__PURE__ */ jsx3(
|
|
450
|
+
DiagramZoomShell,
|
|
451
|
+
{
|
|
452
|
+
fixedSvg,
|
|
453
|
+
contentHash,
|
|
454
|
+
layout: "natural",
|
|
455
|
+
viewportPointerHandlers: fullscreenEnabled ? canvasPointer : void 0,
|
|
456
|
+
toolbarExtra: fullscreenEnabled ? /* @__PURE__ */ jsx3(ShellIconButton, { label: "View fullscreen", onClick: openFullscreen, children: /* @__PURE__ */ jsx3(FullscreenIcon, {}) }) : void 0
|
|
457
|
+
},
|
|
458
|
+
`inline:${contentHash}:${inlineRestoreKey}`
|
|
459
|
+
) }),
|
|
460
|
+
fullscreenEnabled ? /* @__PURE__ */ jsx3(
|
|
461
|
+
DiagramZoomFullscreenOverlay,
|
|
462
|
+
{
|
|
463
|
+
isOpen: isFullscreen,
|
|
464
|
+
title,
|
|
465
|
+
onClose: closeFullscreen,
|
|
466
|
+
headerControls: /* @__PURE__ */ jsx3(ShellIconButton, { label: "Close", onClick: closeFullscreen, children: "\xD7" }),
|
|
467
|
+
children: /* @__PURE__ */ jsx3(
|
|
468
|
+
DiagramZoomShell,
|
|
469
|
+
{
|
|
470
|
+
fixedSvg,
|
|
471
|
+
contentHash: `fs:${contentHash}`,
|
|
472
|
+
layout: "fill"
|
|
473
|
+
}
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
) : null
|
|
477
|
+
] });
|
|
478
|
+
}
|
|
479
|
+
function stopPointerPropagation(event) {
|
|
480
|
+
event.stopPropagation();
|
|
481
|
+
}
|
|
482
|
+
function ShellIconButton({
|
|
483
|
+
label,
|
|
484
|
+
onClick,
|
|
485
|
+
children
|
|
486
|
+
}) {
|
|
487
|
+
return /* @__PURE__ */ jsx3(
|
|
488
|
+
"button",
|
|
489
|
+
{
|
|
490
|
+
type: "button",
|
|
491
|
+
"aria-label": label,
|
|
492
|
+
title: label,
|
|
493
|
+
className: "diagram-zoom-shell__btn",
|
|
494
|
+
onClick,
|
|
495
|
+
onPointerDown: stopPointerPropagation,
|
|
496
|
+
children
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
function FullscreenIcon() {
|
|
501
|
+
return /* @__PURE__ */ jsxs3(
|
|
502
|
+
"svg",
|
|
503
|
+
{
|
|
504
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
505
|
+
width: "14",
|
|
506
|
+
height: "14",
|
|
507
|
+
viewBox: "0 0 24 24",
|
|
508
|
+
fill: "none",
|
|
509
|
+
stroke: "currentColor",
|
|
510
|
+
strokeWidth: "2",
|
|
511
|
+
strokeLinecap: "round",
|
|
512
|
+
strokeLinejoin: "round",
|
|
513
|
+
"aria-hidden": "true",
|
|
514
|
+
children: [
|
|
515
|
+
/* @__PURE__ */ jsx3("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
516
|
+
/* @__PURE__ */ jsx3("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
|
|
517
|
+
/* @__PURE__ */ jsx3("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
|
|
518
|
+
/* @__PURE__ */ jsx3("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
519
|
+
]
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/view/markdown/fixDiagramSvg.ts
|
|
525
|
+
function fixDiagramSvg(svg) {
|
|
526
|
+
if (!svg.includes("<svg")) return svg;
|
|
527
|
+
let result = svg;
|
|
528
|
+
if (!/pointer-events\s*:\s*none/i.test(result)) {
|
|
529
|
+
result = result.replace("<svg", '<svg style="pointer-events:none;"');
|
|
530
|
+
}
|
|
531
|
+
return normalizeSvgPercentDimensions(result);
|
|
532
|
+
}
|
|
533
|
+
function normalizeSvgPercentDimensions(svg) {
|
|
534
|
+
const openTagMatch = svg.match(/<svg\b([^>]*)>/i);
|
|
535
|
+
if (!openTagMatch) return svg;
|
|
536
|
+
const attrs = openTagMatch[1] ?? "";
|
|
537
|
+
const viewBoxMatch = attrs.match(/\bviewBox\s*=\s*["']([^"']+)["']/i);
|
|
538
|
+
if (!viewBoxMatch) return svg;
|
|
539
|
+
const parts = viewBoxMatch[1].trim().split(/[\s,]+/).map(Number);
|
|
540
|
+
if (parts.length !== 4 || parts[2] <= 0 || parts[3] <= 0) return svg;
|
|
541
|
+
const vbW = parts[2];
|
|
542
|
+
const vbH = parts[3];
|
|
543
|
+
let nextAttrs = attrs;
|
|
544
|
+
const widthMatch = nextAttrs.match(/\bwidth\s*=\s*["']([^"']*)["']/i);
|
|
545
|
+
if (!widthMatch || widthMatch[1].includes("%")) {
|
|
546
|
+
nextAttrs = widthMatch ? nextAttrs.replace(/\bwidth\s*=\s*["'][^"']*["']/i, `width="${vbW}"`) : `${nextAttrs} width="${vbW}"`;
|
|
547
|
+
}
|
|
548
|
+
const heightMatch = nextAttrs.match(/\bheight\s*=\s*["']([^"']*)["']/i);
|
|
549
|
+
if (!heightMatch || heightMatch[1].includes("%")) {
|
|
550
|
+
nextAttrs = heightMatch ? nextAttrs.replace(/\bheight\s*=\s*["'][^"']*["']/i, `height="${vbH}"`) : `${nextAttrs} height="${vbH}"`;
|
|
551
|
+
}
|
|
552
|
+
if (nextAttrs === attrs) return svg;
|
|
553
|
+
return svg.replace(openTagMatch[0], `<svg${nextAttrs}>`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/view/markdown/mermaidTheme.ts
|
|
557
|
+
var MERMAID_LIGHT_LABEL_COLOR = "#334155";
|
|
558
|
+
var MERMAID_LIGHT_THEME_VARIABLES = {
|
|
559
|
+
darkMode: false,
|
|
560
|
+
background: "transparent",
|
|
561
|
+
/* flowchart 节点三档:muted indigo / sage / violet(仅 fill / stroke) */
|
|
562
|
+
primaryColor: "#eef2ff",
|
|
563
|
+
primaryTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
564
|
+
primaryBorderColor: "#c7d2fe",
|
|
565
|
+
secondaryColor: "#ecfdf5",
|
|
566
|
+
secondaryTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
567
|
+
secondaryBorderColor: "#a7f3d0",
|
|
568
|
+
tertiaryColor: "#f5f3ff",
|
|
569
|
+
tertiaryTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
570
|
+
tertiaryBorderColor: "#ddd6fe",
|
|
571
|
+
mainBkg: "#eef2ff",
|
|
572
|
+
secondBkg: "#ecfdf5",
|
|
573
|
+
tertiaryBkg: "#f5f3ff",
|
|
574
|
+
/* 全局与连线标签 */
|
|
575
|
+
lineColor: "#94a3b8",
|
|
576
|
+
textColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
577
|
+
titleColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
578
|
+
edgeLabelBackground: "transparent",
|
|
579
|
+
/* subgraph / cluster */
|
|
580
|
+
clusterBkg: "#f8fafc",
|
|
581
|
+
clusterBorder: "#e2e8f0",
|
|
582
|
+
/* sequence */
|
|
583
|
+
actorBkg: "#f8fafc",
|
|
584
|
+
actorBorder: "#cbd5e1",
|
|
585
|
+
actorTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
586
|
+
actorLineColor: "#94a3b8",
|
|
587
|
+
signalColor: "#64748b",
|
|
588
|
+
signalTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
589
|
+
labelBoxBkgColor: "#f8fafc",
|
|
590
|
+
labelBoxBorderColor: "#e2e8f0",
|
|
591
|
+
labelTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
592
|
+
noteBkgColor: "#f1f5f9",
|
|
593
|
+
noteTextColor: MERMAID_LIGHT_LABEL_COLOR,
|
|
594
|
+
noteBorderColor: "#cbd5e1"
|
|
595
|
+
};
|
|
596
|
+
var MERMAID_LABEL_TEXT_SELECTORS = `
|
|
597
|
+
.edgeLabel,
|
|
598
|
+
.edgeLabel span,
|
|
599
|
+
.edgeLabel p,
|
|
600
|
+
.label,
|
|
601
|
+
.nodeLabel,
|
|
602
|
+
.nodeLabel span,
|
|
603
|
+
.flowchart-label text,
|
|
604
|
+
.cluster-label,
|
|
605
|
+
.cluster-label span,
|
|
606
|
+
text.actor,
|
|
607
|
+
.messageText,
|
|
608
|
+
text.sequenceText,
|
|
609
|
+
.noteText,
|
|
610
|
+
.titleText
|
|
611
|
+
`;
|
|
612
|
+
var MERMAID_THEME_CSS = `
|
|
613
|
+
.edgeLabel,
|
|
614
|
+
.edgeLabel p {
|
|
615
|
+
background-color: transparent !important;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
${MERMAID_LABEL_TEXT_SELECTORS} {
|
|
619
|
+
fill: var(--lax-mermaid-label-color, var(--lax-foreground, ${MERMAID_LIGHT_LABEL_COLOR})) !important;
|
|
620
|
+
color: var(--lax-mermaid-label-color, var(--lax-foreground, ${MERMAID_LIGHT_LABEL_COLOR})) !important;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
[data-theme="dark"] .node rect,
|
|
624
|
+
[data-theme="dark"] .node circle,
|
|
625
|
+
[data-theme="dark"] .node ellipse,
|
|
626
|
+
[data-theme="dark"] .node polygon,
|
|
627
|
+
[data-theme="dark"] .node path,
|
|
628
|
+
[data-theme="dark"] g.node rect,
|
|
629
|
+
[data-theme="dark"] g.node circle,
|
|
630
|
+
[data-theme="dark"] g.node ellipse,
|
|
631
|
+
[data-theme="dark"] g.node polygon,
|
|
632
|
+
[data-theme="dark"] g.node path {
|
|
633
|
+
fill: var(--lax-mermaid-node-fill, var(--lax-card-bg, var(--card-bg, transparent))) !important;
|
|
634
|
+
stroke: var(--lax-border-color, var(--border-color, currentColor)) !important;
|
|
635
|
+
}
|
|
636
|
+
[data-theme="dark"] .edgePath .path,
|
|
637
|
+
[data-theme="dark"] .flowchart-link,
|
|
638
|
+
[data-theme="dark"] .edgePaths path {
|
|
639
|
+
stroke: var(--lax-mermaid-edge-color, var(--lax-accent-primary, var(--accent-primary, currentColor))) !important;
|
|
640
|
+
}
|
|
641
|
+
[data-theme="dark"] .cluster rect,
|
|
642
|
+
[data-theme="dark"] .cluster polygon,
|
|
643
|
+
[data-theme="dark"] .actor {
|
|
644
|
+
fill: var(--lax-mermaid-node-fill, var(--lax-card-bg, var(--card-bg, transparent))) !important;
|
|
645
|
+
stroke: var(--lax-border-color, var(--border-color, currentColor)) !important;
|
|
646
|
+
}
|
|
647
|
+
[data-theme="dark"] .messageLine0,
|
|
648
|
+
[data-theme="dark"] .messageLine1 {
|
|
649
|
+
stroke: var(--lax-mermaid-edge-color, var(--lax-accent-primary, var(--accent-primary, currentColor))) !important;
|
|
650
|
+
}
|
|
651
|
+
`;
|
|
652
|
+
var DEFAULT_MERMAID_INIT = {
|
|
653
|
+
theme: "base",
|
|
654
|
+
maxTextSize: 1e5
|
|
655
|
+
};
|
|
656
|
+
var MERMAID_THEME_CSS_VAR_NAMES = [
|
|
657
|
+
"--lax-mermaid-node-fill",
|
|
658
|
+
"--lax-mermaid-label-color",
|
|
659
|
+
"--lax-mermaid-edge-color",
|
|
660
|
+
"--lax-bg-surface",
|
|
661
|
+
"--lax-diagram-bg",
|
|
662
|
+
"--lax-card-bg",
|
|
663
|
+
"--lax-foreground",
|
|
664
|
+
"--lax-border-color",
|
|
665
|
+
"--lax-accent-primary"
|
|
666
|
+
];
|
|
667
|
+
|
|
668
|
+
// src/view/markdown/initMermaid.ts
|
|
669
|
+
var mermaidInitPromise = null;
|
|
670
|
+
var activeInitOptions = { ...DEFAULT_MERMAID_INIT };
|
|
671
|
+
function buildInitializeConfig(options) {
|
|
672
|
+
const theme = options.theme ?? DEFAULT_MERMAID_INIT.theme;
|
|
673
|
+
const useBasePalette = theme === "base";
|
|
674
|
+
return {
|
|
675
|
+
startOnLoad: false,
|
|
676
|
+
theme,
|
|
677
|
+
securityLevel: "strict",
|
|
678
|
+
suppressErrorRendering: true,
|
|
679
|
+
logLevel: "error",
|
|
680
|
+
maxTextSize: options.maxTextSize ?? DEFAULT_MERMAID_INIT.maxTextSize,
|
|
681
|
+
htmlLabels: true,
|
|
682
|
+
themeCSS: MERMAID_THEME_CSS,
|
|
683
|
+
...useBasePalette && {
|
|
684
|
+
themeVariables: {
|
|
685
|
+
...MERMAID_LIGHT_THEME_VARIABLES,
|
|
686
|
+
...options.themeVariables
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
flowchart: {
|
|
690
|
+
htmlLabels: true,
|
|
691
|
+
curve: "basis",
|
|
692
|
+
nodeSpacing: 60,
|
|
693
|
+
rankSpacing: 60,
|
|
694
|
+
padding: 20
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function configureMermaid(options) {
|
|
699
|
+
activeInitOptions = { ...DEFAULT_MERMAID_INIT, ...options };
|
|
700
|
+
mermaidInitPromise = null;
|
|
701
|
+
}
|
|
702
|
+
async function loadMermaid() {
|
|
703
|
+
if (typeof window === "undefined") return null;
|
|
704
|
+
if (!mermaidInitPromise) {
|
|
705
|
+
mermaidInitPromise = import("mermaid").then((mod) => {
|
|
706
|
+
const mermaid = mod.default;
|
|
707
|
+
mermaid.initialize(buildInitializeConfig(activeInitOptions));
|
|
708
|
+
return mermaid;
|
|
709
|
+
}).catch(() => null);
|
|
710
|
+
}
|
|
711
|
+
return mermaidInitPromise;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/view/markdown/MermaidDiagram.tsx
|
|
715
|
+
import { useEffect as useEffect5, useId, useRef as useRef4, useState as useState6 } from "react";
|
|
716
|
+
|
|
717
|
+
// src/view/markdown/mermaidDisplayUtils.ts
|
|
718
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
719
|
+
function isDarkMode() {
|
|
720
|
+
if (typeof document === "undefined") return false;
|
|
721
|
+
const root = document.documentElement;
|
|
722
|
+
const dataTheme = root.getAttribute("data-theme");
|
|
723
|
+
if (dataTheme === "dark") return true;
|
|
724
|
+
if (dataTheme === "light") return false;
|
|
725
|
+
if (root.classList.contains("dark")) return true;
|
|
726
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
730
|
+
}
|
|
731
|
+
function syncDarkThemeOnSvgElement(svg, dark) {
|
|
732
|
+
if (!svg) return;
|
|
733
|
+
if (dark) svg.setAttribute("data-theme", "dark");
|
|
734
|
+
else svg.removeAttribute("data-theme");
|
|
735
|
+
}
|
|
736
|
+
function syncMermaidThemeTokensOnSvgElement(svg, host, tokenNames) {
|
|
737
|
+
if (!svg || !host) return;
|
|
738
|
+
const computed = getComputedStyle(host);
|
|
739
|
+
for (const name of tokenNames) {
|
|
740
|
+
const value = computed.getPropertyValue(name).trim();
|
|
741
|
+
if (value) svg.style.setProperty(name, value);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
function useDarkMode() {
|
|
745
|
+
const [dark, setDark] = useState3(false);
|
|
746
|
+
useEffect2(() => {
|
|
747
|
+
const update = () => setDark(isDarkMode());
|
|
748
|
+
update();
|
|
749
|
+
const mq = typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
|
|
750
|
+
mq?.addEventListener("change", update);
|
|
751
|
+
const observer = new MutationObserver(update);
|
|
752
|
+
observer.observe(document.documentElement, {
|
|
753
|
+
attributes: true,
|
|
754
|
+
attributeFilter: ["class", "data-theme"]
|
|
755
|
+
});
|
|
756
|
+
return () => {
|
|
757
|
+
mq?.removeEventListener("change", update);
|
|
758
|
+
observer.disconnect();
|
|
759
|
+
};
|
|
760
|
+
}, []);
|
|
761
|
+
return dark;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/view/markdown/useCopyText.ts
|
|
765
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
|
|
766
|
+
|
|
767
|
+
// src/view/markdown/copyToClipboard.ts
|
|
768
|
+
async function copyToClipboard(text) {
|
|
769
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
770
|
+
try {
|
|
771
|
+
await navigator.clipboard.writeText(text);
|
|
772
|
+
return true;
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (typeof document === "undefined") return false;
|
|
777
|
+
try {
|
|
778
|
+
const textArea = document.createElement("textarea");
|
|
779
|
+
textArea.value = text;
|
|
780
|
+
textArea.style.position = "fixed";
|
|
781
|
+
textArea.style.left = "-9999px";
|
|
782
|
+
document.body.appendChild(textArea);
|
|
783
|
+
textArea.select();
|
|
784
|
+
const ok = document.execCommand("copy");
|
|
785
|
+
document.body.removeChild(textArea);
|
|
786
|
+
return ok;
|
|
787
|
+
} catch {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/view/markdown/useCopyText.ts
|
|
793
|
+
function useCopyText(text, resetMs = 2e3) {
|
|
794
|
+
const [copied, setCopied] = useState4(false);
|
|
795
|
+
const timerRef = useRef3();
|
|
796
|
+
useEffect3(() => {
|
|
797
|
+
return () => {
|
|
798
|
+
if (timerRef.current !== void 0) {
|
|
799
|
+
window.clearTimeout(timerRef.current);
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
}, []);
|
|
803
|
+
const copy = useCallback2(async () => {
|
|
804
|
+
const ok = await copyToClipboard(text);
|
|
805
|
+
if (!ok) return;
|
|
806
|
+
setCopied(true);
|
|
807
|
+
if (timerRef.current !== void 0) {
|
|
808
|
+
window.clearTimeout(timerRef.current);
|
|
809
|
+
}
|
|
810
|
+
timerRef.current = window.setTimeout(() => setCopied(false), resetMs);
|
|
811
|
+
}, [text, resetMs]);
|
|
812
|
+
return { copied, copy };
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// src/view/markdown/useDebouncedValue.ts
|
|
816
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
817
|
+
function useDebouncedValue(value, delayMs) {
|
|
818
|
+
const [debounced, setDebounced] = useState5(value);
|
|
819
|
+
useEffect4(() => {
|
|
820
|
+
const timer = window.setTimeout(() => setDebounced(value), delayMs);
|
|
821
|
+
return () => window.clearTimeout(timer);
|
|
822
|
+
}, [value, delayMs]);
|
|
823
|
+
return debounced;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/view/markdown/MermaidDiagram.tsx
|
|
827
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
828
|
+
var MERMAID_RENDER_DEBOUNCE_MS = 350;
|
|
829
|
+
function MermaidDiagram({
|
|
830
|
+
chart,
|
|
831
|
+
className = "",
|
|
832
|
+
fullscreenEnabled = true
|
|
833
|
+
}) {
|
|
834
|
+
const [rawSvg, setRawSvg] = useState6("");
|
|
835
|
+
const [error, setError] = useState6(null);
|
|
836
|
+
const { copied, copy } = useCopyText(chart);
|
|
837
|
+
const wrapRef = useRef4(null);
|
|
838
|
+
const rawSvgRef = useRef4("");
|
|
839
|
+
const reactId = useId().replace(/:/g, "");
|
|
840
|
+
const renderId = `lax-mermaid-${reactId}`;
|
|
841
|
+
const isDark = useDarkMode();
|
|
842
|
+
const debouncedChart = useDebouncedValue(chart.trim(), MERMAID_RENDER_DEBOUNCE_MS);
|
|
843
|
+
useEffect5(() => {
|
|
844
|
+
rawSvgRef.current = rawSvg;
|
|
845
|
+
}, [rawSvg]);
|
|
846
|
+
useEffect5(() => {
|
|
847
|
+
if (!debouncedChart) return;
|
|
848
|
+
let cancelled = false;
|
|
849
|
+
const renderChart = async () => {
|
|
850
|
+
const mermaid = await loadMermaid();
|
|
851
|
+
if (cancelled) return;
|
|
852
|
+
if (!mermaid) {
|
|
853
|
+
if (!rawSvgRef.current) {
|
|
854
|
+
setError("mermaid peer dependency is not installed");
|
|
855
|
+
}
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
setError(null);
|
|
860
|
+
const { svg: renderedSvg } = await mermaid.render(renderId, debouncedChart);
|
|
861
|
+
if (!cancelled) {
|
|
862
|
+
setRawSvg(renderedSvg);
|
|
863
|
+
}
|
|
864
|
+
} catch (err) {
|
|
865
|
+
if (!cancelled && !rawSvgRef.current) {
|
|
866
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
867
|
+
setError(message);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
void renderChart();
|
|
872
|
+
return () => {
|
|
873
|
+
cancelled = true;
|
|
874
|
+
};
|
|
875
|
+
}, [debouncedChart, renderId]);
|
|
876
|
+
const fixedSvg = rawSvg ? fixDiagramSvg(rawSvg) : "";
|
|
877
|
+
const contentHash = `mermaid:${debouncedChart}:${fixedSvg.length}`;
|
|
878
|
+
useEffect5(() => {
|
|
879
|
+
const host = wrapRef.current;
|
|
880
|
+
const svg = host?.querySelector("svg");
|
|
881
|
+
if (!(svg instanceof SVGSVGElement)) return;
|
|
882
|
+
syncDarkThemeOnSvgElement(svg, isDark);
|
|
883
|
+
syncMermaidThemeTokensOnSvgElement(svg, host, MERMAID_THEME_CSS_VAR_NAMES);
|
|
884
|
+
}, [isDark, fixedSvg]);
|
|
885
|
+
const handleCopy = () => void copy();
|
|
886
|
+
const toolbar = /* @__PURE__ */ jsxs4("div", { className: "lax-mermaid__toolbar", children: [
|
|
887
|
+
/* @__PURE__ */ jsx4("span", { className: "lax-mermaid__label", children: "Mermaid" }),
|
|
888
|
+
/* @__PURE__ */ jsx4("div", { className: "lax-mermaid__actions", children: /* @__PURE__ */ jsx4("button", { type: "button", className: "lax-mermaid__copy", onClick: handleCopy, children: copied ? "Copied" : "Copy" }) })
|
|
889
|
+
] });
|
|
890
|
+
if (error) {
|
|
891
|
+
return /* @__PURE__ */ jsxs4("div", { className: `lax-mermaid lax-mermaid--error ${className}`.trim(), children: [
|
|
892
|
+
toolbar,
|
|
893
|
+
/* @__PURE__ */ jsx4("pre", { className: "lax-mermaid__fallback", children: chart }),
|
|
894
|
+
/* @__PURE__ */ jsx4("p", { className: "lax-mermaid__error-text", children: error })
|
|
895
|
+
] });
|
|
896
|
+
}
|
|
897
|
+
if (!fixedSvg) {
|
|
898
|
+
return /* @__PURE__ */ jsxs4("div", { className: `lax-mermaid lax-mermaid--loading ${className}`.trim(), children: [
|
|
899
|
+
/* @__PURE__ */ jsxs4("span", { className: "lax-mermaid__loading-dots", "aria-hidden": "true", children: [
|
|
900
|
+
/* @__PURE__ */ jsx4("span", {}),
|
|
901
|
+
/* @__PURE__ */ jsx4("span", {}),
|
|
902
|
+
/* @__PURE__ */ jsx4("span", {})
|
|
903
|
+
] }),
|
|
904
|
+
/* @__PURE__ */ jsx4("span", { className: "lax-mermaid__loading-text", children: "Rendering diagram\u2026" })
|
|
905
|
+
] });
|
|
906
|
+
}
|
|
907
|
+
return /* @__PURE__ */ jsxs4("div", { className: `lax-mermaid ${className}`.trim(), ref: wrapRef, children: [
|
|
908
|
+
toolbar,
|
|
909
|
+
/* @__PURE__ */ jsx4("div", { className: "lax-diagram-canvas-wrap", children: /* @__PURE__ */ jsx4(
|
|
910
|
+
DiagramZoomView,
|
|
911
|
+
{
|
|
912
|
+
fixedSvg,
|
|
913
|
+
contentHash,
|
|
914
|
+
title: "Diagram",
|
|
915
|
+
fullscreenEnabled
|
|
916
|
+
}
|
|
917
|
+
) })
|
|
918
|
+
] });
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// src/view/markdown/PlantUMLBlock.tsx
|
|
922
|
+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
923
|
+
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
924
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
925
|
+
function PlantUMLBlock({ content, className = "" }) {
|
|
926
|
+
const { copied, copy } = useCopyText(content);
|
|
927
|
+
return /* @__PURE__ */ jsxs5("div", { className: `lax-plantuml lax-code-block lax-code-block--rich ${className}`.trim(), children: [
|
|
928
|
+
/* @__PURE__ */ jsxs5("div", { className: "lax-code-block__header", children: [
|
|
929
|
+
/* @__PURE__ */ jsx5("span", { className: "lax-code-block__lang", children: "PlantUML" }),
|
|
930
|
+
/* @__PURE__ */ jsx5(
|
|
931
|
+
"button",
|
|
932
|
+
{
|
|
933
|
+
type: "button",
|
|
934
|
+
className: `lax-code-block__copy ${copied ? "lax-code-block__copy--copied" : ""}`,
|
|
935
|
+
onClick: () => void copy(),
|
|
936
|
+
children: copied ? "Copied" : "Copy"
|
|
937
|
+
}
|
|
938
|
+
)
|
|
939
|
+
] }),
|
|
940
|
+
/* @__PURE__ */ jsx5(
|
|
941
|
+
SyntaxHighlighter,
|
|
942
|
+
{
|
|
943
|
+
language: "text",
|
|
944
|
+
style: oneDark,
|
|
945
|
+
customStyle: { margin: 0, padding: "0.75rem 1rem", background: "transparent" },
|
|
946
|
+
showLineNumbers: true,
|
|
947
|
+
wrapLongLines: true,
|
|
948
|
+
children: content
|
|
949
|
+
}
|
|
950
|
+
)
|
|
951
|
+
] });
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/view/markdown/plantumlConfig.ts
|
|
955
|
+
var DEFAULT_PLANTUML_SERVER_URL = "https://kroki.io";
|
|
956
|
+
var plantumlServerUrl = DEFAULT_PLANTUML_SERVER_URL;
|
|
957
|
+
var configListeners = /* @__PURE__ */ new Set();
|
|
958
|
+
function notifyConfigListeners() {
|
|
959
|
+
configListeners.forEach((listener) => listener());
|
|
960
|
+
}
|
|
961
|
+
function configurePlantumlServer(url) {
|
|
962
|
+
const next = url.replace(/\/$/, "");
|
|
963
|
+
if (next === plantumlServerUrl) return;
|
|
964
|
+
plantumlServerUrl = next;
|
|
965
|
+
notifyConfigListeners();
|
|
966
|
+
}
|
|
967
|
+
function getPlantumlServerUrl() {
|
|
968
|
+
return plantumlServerUrl;
|
|
969
|
+
}
|
|
970
|
+
function subscribePlantumlServerConfig(listener) {
|
|
971
|
+
configListeners.add(listener);
|
|
972
|
+
return () => {
|
|
973
|
+
configListeners.delete(listener);
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/view/markdown/PlantUMLDiagram.tsx
|
|
978
|
+
import { useEffect as useEffect6, useRef as useRef5, useState as useState7 } from "react";
|
|
979
|
+
|
|
980
|
+
// src/view/markdown/plantumlRender.ts
|
|
981
|
+
function normalizePlantumlSource(source) {
|
|
982
|
+
const trimmed = source.trim();
|
|
983
|
+
if (!trimmed) return trimmed;
|
|
984
|
+
if (trimmed.includes("@startuml")) return trimmed;
|
|
985
|
+
return `@startuml
|
|
986
|
+
${trimmed}
|
|
987
|
+
@enduml`;
|
|
988
|
+
}
|
|
989
|
+
async function fetchPlantumlSvg(source, serverUrl = getPlantumlServerUrl()) {
|
|
990
|
+
const base = serverUrl.replace(/\/$/, "");
|
|
991
|
+
const url = base.startsWith("/") ? base : `${base}/`;
|
|
992
|
+
const diagramSource = normalizePlantumlSource(source);
|
|
993
|
+
const res = await fetch(url, {
|
|
994
|
+
method: "POST",
|
|
995
|
+
headers: {
|
|
996
|
+
"Content-Type": "application/json",
|
|
997
|
+
Accept: "image/svg+xml"
|
|
998
|
+
},
|
|
999
|
+
body: JSON.stringify({
|
|
1000
|
+
diagram_source: diagramSource,
|
|
1001
|
+
diagram_type: "plantuml",
|
|
1002
|
+
output_format: "svg"
|
|
1003
|
+
})
|
|
1004
|
+
});
|
|
1005
|
+
if (!res.ok) {
|
|
1006
|
+
const detail = await res.text().catch(() => "");
|
|
1007
|
+
const suffix = detail ? `: ${detail.slice(0, 200)}` : "";
|
|
1008
|
+
throw new Error(`PlantUML render failed (${res.status})${suffix}`);
|
|
1009
|
+
}
|
|
1010
|
+
const svg = await res.text();
|
|
1011
|
+
if (!svg.includes("<svg")) {
|
|
1012
|
+
throw new Error("PlantUML render returned non-SVG response");
|
|
1013
|
+
}
|
|
1014
|
+
return fixDiagramSvg(svg);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/view/markdown/PlantUMLDiagram.tsx
|
|
1018
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1019
|
+
var PLANTUML_RENDER_DEBOUNCE_MS = 350;
|
|
1020
|
+
function PlantUMLDiagram({
|
|
1021
|
+
content,
|
|
1022
|
+
className = "",
|
|
1023
|
+
serverUrl,
|
|
1024
|
+
fullscreenEnabled = true,
|
|
1025
|
+
sourceOnly = false
|
|
1026
|
+
}) {
|
|
1027
|
+
const [fixedSvg, setFixedSvg] = useState7("");
|
|
1028
|
+
const [error, setError] = useState7(null);
|
|
1029
|
+
const { copied, copy } = useCopyText(content);
|
|
1030
|
+
const svgRef = useRef5("");
|
|
1031
|
+
const debouncedContent = useDebouncedValue(content.trim(), PLANTUML_RENDER_DEBOUNCE_MS);
|
|
1032
|
+
const [serverConfigRev, setServerConfigRev] = useState7(0);
|
|
1033
|
+
useEffect6(() => subscribePlantumlServerConfig(() => {
|
|
1034
|
+
setServerConfigRev((rev) => rev + 1);
|
|
1035
|
+
}), []);
|
|
1036
|
+
useEffect6(() => {
|
|
1037
|
+
svgRef.current = fixedSvg;
|
|
1038
|
+
}, [fixedSvg]);
|
|
1039
|
+
useEffect6(() => {
|
|
1040
|
+
if (!debouncedContent || sourceOnly) return;
|
|
1041
|
+
let cancelled = false;
|
|
1042
|
+
const render = async () => {
|
|
1043
|
+
if (typeof window === "undefined") return;
|
|
1044
|
+
try {
|
|
1045
|
+
setError(null);
|
|
1046
|
+
const rendered = await fetchPlantumlSvg(
|
|
1047
|
+
debouncedContent,
|
|
1048
|
+
serverUrl
|
|
1049
|
+
);
|
|
1050
|
+
if (!cancelled) setFixedSvg(rendered);
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
if (!cancelled) {
|
|
1053
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1054
|
+
setError(message);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
void render();
|
|
1059
|
+
return () => {
|
|
1060
|
+
cancelled = true;
|
|
1061
|
+
};
|
|
1062
|
+
}, [debouncedContent, serverUrl, sourceOnly, serverConfigRev]);
|
|
1063
|
+
if (sourceOnly) {
|
|
1064
|
+
return /* @__PURE__ */ jsx6(PlantUMLBlock, { content, className });
|
|
1065
|
+
}
|
|
1066
|
+
const handleCopy = () => void copy();
|
|
1067
|
+
const contentHash = `plantuml:${debouncedContent}:${fixedSvg.length}`;
|
|
1068
|
+
const toolbar = /* @__PURE__ */ jsxs6("div", { className: "lax-plantuml__toolbar", children: [
|
|
1069
|
+
/* @__PURE__ */ jsx6("span", { className: "lax-plantuml__label", children: "PlantUML" }),
|
|
1070
|
+
/* @__PURE__ */ jsx6("div", { className: "lax-plantuml__actions", children: /* @__PURE__ */ jsx6("button", { type: "button", className: "lax-plantuml__copy", onClick: handleCopy, children: copied ? "Copied" : "Copy" }) })
|
|
1071
|
+
] });
|
|
1072
|
+
if (error) {
|
|
1073
|
+
return /* @__PURE__ */ jsxs6("div", { className: `lax-plantuml lax-plantuml--error ${className}`.trim(), children: [
|
|
1074
|
+
toolbar,
|
|
1075
|
+
/* @__PURE__ */ jsx6("pre", { className: "lax-plantuml__fallback", children: content }),
|
|
1076
|
+
/* @__PURE__ */ jsx6("p", { className: "lax-plantuml__error-text", children: error })
|
|
1077
|
+
] });
|
|
1078
|
+
}
|
|
1079
|
+
if (!fixedSvg) {
|
|
1080
|
+
return /* @__PURE__ */ jsxs6("div", { className: `lax-plantuml lax-plantuml--loading ${className}`.trim(), children: [
|
|
1081
|
+
/* @__PURE__ */ jsxs6("span", { className: "lax-plantuml__loading-dots", "aria-hidden": "true", children: [
|
|
1082
|
+
/* @__PURE__ */ jsx6("span", {}),
|
|
1083
|
+
/* @__PURE__ */ jsx6("span", {}),
|
|
1084
|
+
/* @__PURE__ */ jsx6("span", {})
|
|
1085
|
+
] }),
|
|
1086
|
+
/* @__PURE__ */ jsx6("span", { className: "lax-plantuml__loading-text", children: "Rendering diagram\u2026" })
|
|
1087
|
+
] });
|
|
1088
|
+
}
|
|
1089
|
+
return /* @__PURE__ */ jsxs6("div", { className: `lax-plantuml ${className}`.trim(), children: [
|
|
1090
|
+
toolbar,
|
|
1091
|
+
/* @__PURE__ */ jsx6("div", { className: "lax-diagram-canvas-wrap", children: /* @__PURE__ */ jsx6(
|
|
1092
|
+
DiagramZoomView,
|
|
1093
|
+
{
|
|
1094
|
+
fixedSvg,
|
|
1095
|
+
contentHash,
|
|
1096
|
+
title: "PlantUML",
|
|
1097
|
+
fullscreenEnabled
|
|
1098
|
+
}
|
|
1099
|
+
) })
|
|
1100
|
+
] });
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/view/markdown/RichMarkdown.tsx
|
|
1104
|
+
import { useMemo } from "react";
|
|
1105
|
+
import ReactMarkdown from "react-markdown";
|
|
1106
|
+
import remarkGfm from "remark-gfm";
|
|
1107
|
+
|
|
1108
|
+
// src/view/markdown/markdownUtils.ts
|
|
1109
|
+
var MD_SYNTAX_RE = /[#*`|[>\-_~]|\n\n|^\d+\. |\n\d+\. /;
|
|
1110
|
+
function hasMarkdownSyntax(content) {
|
|
1111
|
+
const sample = content.length > 500 ? content.slice(0, 500) : content;
|
|
1112
|
+
return MD_SYNTAX_RE.test(sample);
|
|
1113
|
+
}
|
|
1114
|
+
function hashContent(content) {
|
|
1115
|
+
let hash = 5381;
|
|
1116
|
+
for (let i = 0; i < content.length; i++) {
|
|
1117
|
+
hash = (hash << 5) + hash + content.charCodeAt(i) & 4294967295;
|
|
1118
|
+
}
|
|
1119
|
+
return hash.toString(36);
|
|
1120
|
+
}
|
|
1121
|
+
function collapseExcessiveBlankLines(text) {
|
|
1122
|
+
return text.replace(/\n{3,}/g, "\n\n").trim();
|
|
1123
|
+
}
|
|
1124
|
+
function paragraphHasContent(children) {
|
|
1125
|
+
if (children == null || children === false) return false;
|
|
1126
|
+
if (typeof children === "string") return children.trim().length > 0;
|
|
1127
|
+
if (typeof children === "number") return true;
|
|
1128
|
+
if (Array.isArray(children)) return children.some(paragraphHasContent);
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/view/markdown/RichMarkdownStreamContext.tsx
|
|
1133
|
+
import { createContext, useContext } from "react";
|
|
1134
|
+
var DEFAULT_OPTIONS = { deferDiagrams: false };
|
|
1135
|
+
var RichMarkdownStreamContext = createContext(DEFAULT_OPTIONS);
|
|
1136
|
+
function useRichMarkdownStream() {
|
|
1137
|
+
return useContext(RichMarkdownStreamContext);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/view/markdown/richMarkdownComponents.tsx
|
|
1141
|
+
import { Prism as SyntaxHighlighter2 } from "react-syntax-highlighter";
|
|
1142
|
+
import { oneDark as oneDark2 } from "react-syntax-highlighter/dist/esm/styles/prism";
|
|
1143
|
+
|
|
1144
|
+
// src/view/markdown/headingIds.ts
|
|
1145
|
+
var headingCounter = 0;
|
|
1146
|
+
function generateHeadingId(text) {
|
|
1147
|
+
const slug = text.trim().toLowerCase().replace(/[^\w\u4e00-\u9fff]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1148
|
+
if (slug) return slug;
|
|
1149
|
+
headingCounter += 1;
|
|
1150
|
+
return `heading-${headingCounter}`;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/view/markdown/richMarkdownComponents.tsx
|
|
1154
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1155
|
+
function RichCodeBlock({ language, content }) {
|
|
1156
|
+
const { copied, copy } = useCopyText(content);
|
|
1157
|
+
return /* @__PURE__ */ jsxs7("div", { className: "lax-code-block lax-code-block--rich", children: [
|
|
1158
|
+
/* @__PURE__ */ jsxs7("div", { className: "lax-code-block__header", children: [
|
|
1159
|
+
/* @__PURE__ */ jsx7("span", { className: "lax-code-block__lang", children: language || "code" }),
|
|
1160
|
+
/* @__PURE__ */ jsx7(
|
|
1161
|
+
"button",
|
|
1162
|
+
{
|
|
1163
|
+
type: "button",
|
|
1164
|
+
className: `lax-code-block__copy ${copied ? "lax-code-block__copy--copied" : ""}`,
|
|
1165
|
+
onClick: () => void copy(),
|
|
1166
|
+
children: copied ? "Copied" : "Copy"
|
|
1167
|
+
}
|
|
1168
|
+
)
|
|
1169
|
+
] }),
|
|
1170
|
+
/* @__PURE__ */ jsx7(
|
|
1171
|
+
SyntaxHighlighter2,
|
|
1172
|
+
{
|
|
1173
|
+
language: language || "text",
|
|
1174
|
+
style: oneDark2,
|
|
1175
|
+
customStyle: { margin: 0, padding: "0.75rem 1rem", background: "transparent" },
|
|
1176
|
+
showLineNumbers: true,
|
|
1177
|
+
wrapLongLines: true,
|
|
1178
|
+
children: content
|
|
1179
|
+
}
|
|
1180
|
+
)
|
|
1181
|
+
] });
|
|
1182
|
+
}
|
|
1183
|
+
function heading(children, level, className) {
|
|
1184
|
+
const text = children != null ? String(children) : "";
|
|
1185
|
+
const id = generateHeadingId(text);
|
|
1186
|
+
const Tag = level;
|
|
1187
|
+
return /* @__PURE__ */ jsx7(Tag, { id, className: `lax-markdown__heading ${className}`, children });
|
|
1188
|
+
}
|
|
1189
|
+
function RichMarkdownCode(props) {
|
|
1190
|
+
const { deferDiagrams } = useRichMarkdownStream();
|
|
1191
|
+
const { inline, className, children, ...rest } = props;
|
|
1192
|
+
const match = /language-(\w+)/.exec(className || "");
|
|
1193
|
+
const codeContent = children != null ? String(children).replace(/\n$/, "") : "";
|
|
1194
|
+
if (!inline && match?.[1] === "mermaid" && codeContent.trim()) {
|
|
1195
|
+
if (deferDiagrams) {
|
|
1196
|
+
return /* @__PURE__ */ jsx7(RichCodeBlock, { language: "mermaid", content: codeContent });
|
|
1197
|
+
}
|
|
1198
|
+
return /* @__PURE__ */ jsx7(MermaidDiagram, { chart: codeContent });
|
|
1199
|
+
}
|
|
1200
|
+
if (!inline && match?.[1] === "plantuml" && codeContent.trim()) {
|
|
1201
|
+
if (deferDiagrams) {
|
|
1202
|
+
return /* @__PURE__ */ jsx7(RichCodeBlock, { language: "plantuml", content: codeContent });
|
|
1203
|
+
}
|
|
1204
|
+
return /* @__PURE__ */ jsx7(PlantUMLDiagram, { content: codeContent });
|
|
1205
|
+
}
|
|
1206
|
+
if (!inline && match) {
|
|
1207
|
+
return /* @__PURE__ */ jsx7(RichCodeBlock, { language: match[1], content: codeContent });
|
|
1208
|
+
}
|
|
1209
|
+
return /* @__PURE__ */ jsx7("code", { className: "lax-markdown__inline-code", ...rest, children });
|
|
1210
|
+
}
|
|
1211
|
+
var RICH_MARKDOWN_COMPONENTS = {
|
|
1212
|
+
code: RichMarkdownCode,
|
|
1213
|
+
h1({ children }) {
|
|
1214
|
+
return heading(children, "h1", "lax-markdown__h1");
|
|
1215
|
+
},
|
|
1216
|
+
h2({ children }) {
|
|
1217
|
+
return heading(children, "h2", "lax-markdown__h2");
|
|
1218
|
+
},
|
|
1219
|
+
h3({ children }) {
|
|
1220
|
+
return heading(children, "h3", "lax-markdown__h3");
|
|
1221
|
+
},
|
|
1222
|
+
h4({ children }) {
|
|
1223
|
+
return heading(children, "h4", "lax-markdown__h4");
|
|
1224
|
+
},
|
|
1225
|
+
h5({ children }) {
|
|
1226
|
+
return heading(children, "h5", "lax-markdown__h5");
|
|
1227
|
+
},
|
|
1228
|
+
h6({ children }) {
|
|
1229
|
+
return heading(children, "h6", "lax-markdown__h6");
|
|
1230
|
+
},
|
|
1231
|
+
p({ children }) {
|
|
1232
|
+
if (!paragraphHasContent(children)) return null;
|
|
1233
|
+
return /* @__PURE__ */ jsx7("p", { className: "lax-markdown__paragraph", children });
|
|
1234
|
+
},
|
|
1235
|
+
ul({ children }) {
|
|
1236
|
+
return /* @__PURE__ */ jsx7("ul", { className: "lax-markdown__list lax-markdown__ul", children });
|
|
1237
|
+
},
|
|
1238
|
+
ol({ children }) {
|
|
1239
|
+
return /* @__PURE__ */ jsx7("ol", { className: "lax-markdown__list lax-markdown__ol", children });
|
|
1240
|
+
},
|
|
1241
|
+
li({ children }) {
|
|
1242
|
+
return /* @__PURE__ */ jsx7("li", { className: "lax-markdown__li", children });
|
|
1243
|
+
},
|
|
1244
|
+
a({ children, href }) {
|
|
1245
|
+
return /* @__PURE__ */ jsx7("a", { href, className: "lax-markdown__link", target: "_blank", rel: "noopener noreferrer", children });
|
|
1246
|
+
},
|
|
1247
|
+
blockquote({ children }) {
|
|
1248
|
+
return /* @__PURE__ */ jsx7("blockquote", { className: "lax-markdown__blockquote", children });
|
|
1249
|
+
},
|
|
1250
|
+
table({ children }) {
|
|
1251
|
+
return /* @__PURE__ */ jsx7("div", { className: "lax-markdown__table-wrapper", children: /* @__PURE__ */ jsx7("table", { className: "lax-markdown__table", children }) });
|
|
1252
|
+
},
|
|
1253
|
+
thead({ children }) {
|
|
1254
|
+
return /* @__PURE__ */ jsx7("thead", { className: "lax-markdown__thead", children });
|
|
1255
|
+
},
|
|
1256
|
+
tbody({ children }) {
|
|
1257
|
+
return /* @__PURE__ */ jsx7("tbody", { className: "lax-markdown__tbody", children });
|
|
1258
|
+
},
|
|
1259
|
+
tr({ children }) {
|
|
1260
|
+
return /* @__PURE__ */ jsx7("tr", { className: "lax-markdown__tr", children });
|
|
1261
|
+
},
|
|
1262
|
+
th({ children }) {
|
|
1263
|
+
return /* @__PURE__ */ jsx7("th", { className: "lax-markdown__th", children });
|
|
1264
|
+
},
|
|
1265
|
+
td({ children }) {
|
|
1266
|
+
return /* @__PURE__ */ jsx7("td", { className: "lax-markdown__td", children });
|
|
1267
|
+
},
|
|
1268
|
+
hr() {
|
|
1269
|
+
return /* @__PURE__ */ jsx7("hr", { className: "lax-markdown__hr" });
|
|
1270
|
+
},
|
|
1271
|
+
strong({ children }) {
|
|
1272
|
+
return /* @__PURE__ */ jsx7("strong", { className: "lax-markdown__strong", children });
|
|
1273
|
+
},
|
|
1274
|
+
em({ children }) {
|
|
1275
|
+
return /* @__PURE__ */ jsx7("em", { className: "lax-markdown__em", children });
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
// src/view/markdown/RichMarkdown.tsx
|
|
1280
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1281
|
+
function RichMarkdown({
|
|
1282
|
+
content,
|
|
1283
|
+
className = "",
|
|
1284
|
+
deferDiagrams = false
|
|
1285
|
+
}) {
|
|
1286
|
+
const processed = useMemo(() => collapseExcessiveBlankLines(content), [content]);
|
|
1287
|
+
if (!processed) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
if (!hasMarkdownSyntax(processed)) {
|
|
1291
|
+
return /* @__PURE__ */ jsx8("p", { className: `lax-markdown__paragraph ${className}`.trim(), children: processed });
|
|
1292
|
+
}
|
|
1293
|
+
return /* @__PURE__ */ jsx8(RichMarkdownStreamContext.Provider, { value: { deferDiagrams }, children: /* @__PURE__ */ jsx8("div", { className: className || void 0, children: /* @__PURE__ */ jsx8(ReactMarkdown, { remarkPlugins: [remarkGfm], components: RICH_MARKDOWN_COMPONENTS, children: processed }) }) });
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/view/nodes/ReasoningNode.tsx
|
|
1297
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1298
|
+
var REASONING_DOT = "\u25CF";
|
|
1299
|
+
function ReasoningBubble({ nodeId }) {
|
|
1300
|
+
const node = useNodeTyped(nodeId, "reasoning");
|
|
1301
|
+
if (!node) return null;
|
|
1302
|
+
const text = node.accumulated.trim();
|
|
1303
|
+
if (!text) return null;
|
|
1304
|
+
return /* @__PURE__ */ jsx9("div", { className: "lax-reasoning-bubble", "data-status": node.status, "data-round-id": node.roundId, children: /* @__PURE__ */ jsxs8("div", { className: "lax-reasoning-bubble__body", children: [
|
|
1305
|
+
/* @__PURE__ */ jsx9("span", { className: "lax-reasoning-bubble__dot", "aria-hidden": "true", children: REASONING_DOT }),
|
|
1306
|
+
/* @__PURE__ */ jsx9("span", { className: "lax-reasoning-bubble__content", children: node.accumulated })
|
|
1307
|
+
] }) });
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// src/view/tools/presentation/StreamingMarkdown.tsx
|
|
1311
|
+
import { useRef as useRef6 } from "react";
|
|
1312
|
+
import { marked } from "marked";
|
|
1313
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1314
|
+
var TOKEN_CACHE_MAX = 500;
|
|
1315
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
1316
|
+
function cachedLexer(content) {
|
|
1317
|
+
if (!hasMarkdownSyntax(content)) {
|
|
1318
|
+
return [{
|
|
1319
|
+
type: "paragraph",
|
|
1320
|
+
raw: content,
|
|
1321
|
+
text: content,
|
|
1322
|
+
tokens: [{
|
|
1323
|
+
type: "text",
|
|
1324
|
+
raw: content,
|
|
1325
|
+
text: content
|
|
1326
|
+
}]
|
|
1327
|
+
}];
|
|
1328
|
+
}
|
|
1329
|
+
const key = hashContent(content);
|
|
1330
|
+
const hit = tokenCache.get(key);
|
|
1331
|
+
if (hit) {
|
|
1332
|
+
tokenCache.delete(key);
|
|
1333
|
+
tokenCache.set(key, hit);
|
|
1334
|
+
return hit;
|
|
1335
|
+
}
|
|
1336
|
+
const tokens = marked.lexer(content);
|
|
1337
|
+
if (tokenCache.size >= TOKEN_CACHE_MAX) {
|
|
1338
|
+
const first = tokenCache.keys().next().value;
|
|
1339
|
+
if (first !== void 0) tokenCache.delete(first);
|
|
1340
|
+
}
|
|
1341
|
+
tokenCache.set(key, tokens);
|
|
1342
|
+
return tokens;
|
|
1343
|
+
}
|
|
1344
|
+
function StreamingMarkdown({ children }) {
|
|
1345
|
+
"use no memo";
|
|
1346
|
+
const stablePrefixRef = useRef6("");
|
|
1347
|
+
if (!children.startsWith(stablePrefixRef.current)) {
|
|
1348
|
+
stablePrefixRef.current = "";
|
|
1349
|
+
}
|
|
1350
|
+
const boundary = stablePrefixRef.current.length;
|
|
1351
|
+
const tokens = cachedLexer(children.substring(boundary));
|
|
1352
|
+
let lastContentIdx = tokens.length - 1;
|
|
1353
|
+
while (lastContentIdx >= 0 && tokens[lastContentIdx].type === "space") {
|
|
1354
|
+
lastContentIdx--;
|
|
1355
|
+
}
|
|
1356
|
+
let advance = 0;
|
|
1357
|
+
for (let i = 0; i < lastContentIdx; i++) {
|
|
1358
|
+
advance += tokens[i].raw.length;
|
|
1359
|
+
}
|
|
1360
|
+
if (advance > 0) {
|
|
1361
|
+
stablePrefixRef.current = children.substring(0, boundary + advance);
|
|
1362
|
+
}
|
|
1363
|
+
const stablePrefix = stablePrefixRef.current;
|
|
1364
|
+
const unstableSuffix = children.substring(stablePrefix.length);
|
|
1365
|
+
return /* @__PURE__ */ jsxs9("div", { className: "lax-markdown lax-markdown--rich", children: [
|
|
1366
|
+
stablePrefix ? /* @__PURE__ */ jsx10(RichMarkdown, { content: stablePrefix }, `stable-${hashContent(stablePrefix)}`) : null,
|
|
1367
|
+
unstableSuffix ? /* @__PURE__ */ jsx10(
|
|
1368
|
+
RichMarkdown,
|
|
1369
|
+
{
|
|
1370
|
+
content: unstableSuffix,
|
|
1371
|
+
deferDiagrams: true
|
|
1372
|
+
},
|
|
1373
|
+
`unstable-${hashContent(unstableSuffix)}`
|
|
1374
|
+
) : null
|
|
1375
|
+
] });
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/view/nodes/TextNode.tsx
|
|
1379
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1380
|
+
function TextBubble({ nodeId }) {
|
|
1381
|
+
const node = useNodeTyped(nodeId, "text");
|
|
1382
|
+
if (!node) return null;
|
|
1383
|
+
return /* @__PURE__ */ jsx11("div", { className: "lax-text-bubble", "data-status": node.status, children: /* @__PURE__ */ jsxs10("div", { className: "lax-text-bubble__body", children: [
|
|
1384
|
+
/* @__PURE__ */ jsx11("span", { className: "lax-text-bubble__dot", "aria-hidden": "true", children: REASONING_DOT }),
|
|
1385
|
+
/* @__PURE__ */ jsx11("div", { className: "lax-text-bubble__content lax-text-bubble__content--markdown", children: /* @__PURE__ */ jsx11(StreamingMarkdown, { children: node.accumulated }) })
|
|
1386
|
+
] }) });
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
// src/view/nodes/ToolCallNode.tsx
|
|
1390
|
+
import { useEffect as useEffect7, useState as useState8 } from "react";
|
|
1391
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1392
|
+
function ToolCallCard({ nodeId, layout = "default" }) {
|
|
1393
|
+
const node = useNodeTyped(nodeId, "tool_call");
|
|
1394
|
+
const { registry, defaultBodyMode } = useToolDisplayOptions();
|
|
1395
|
+
const viewOptions = useSessionViewOptions();
|
|
1396
|
+
const pendingPermission = usePendingPermissionForTool(node?.toolName ?? "");
|
|
1397
|
+
const effectiveDefault = resolveEffectiveToolBodyMode(viewOptions, defaultBodyMode);
|
|
1398
|
+
const [bodyMode, setBodyMode] = useState8(effectiveDefault);
|
|
1399
|
+
useEffect7(() => {
|
|
1400
|
+
setBodyMode(resolveEffectiveToolBodyMode(viewOptions, defaultBodyMode));
|
|
1401
|
+
}, [viewOptions.displayMode, viewOptions.verbose, defaultBodyMode]);
|
|
1402
|
+
if (!node) return null;
|
|
1403
|
+
if (node.hidden) return null;
|
|
1404
|
+
const details = node.details;
|
|
1405
|
+
const inputStream = details?.inputStream ?? node.inputDelta;
|
|
1406
|
+
const result = node.result;
|
|
1407
|
+
const displayBody = result?.display ?? null;
|
|
1408
|
+
const hasSubagentProgress = (node.subagentProgress?.length ?? 0) > 0;
|
|
1409
|
+
const hasSubagentTranscript = (node.subagentTranscript?.length ?? 0) > 0;
|
|
1410
|
+
const showBody = hasSubagentProgress || hasSubagentTranscript || !(node.status === "running" && result == null);
|
|
1411
|
+
const BodyComponent = resolveToolBody(node.toolName, displayBody, registry);
|
|
1412
|
+
const title = formatToolTitle(
|
|
1413
|
+
node.toolName,
|
|
1414
|
+
details?.input ?? node.input,
|
|
1415
|
+
displayBody,
|
|
1416
|
+
result?.summary,
|
|
1417
|
+
result?.meta
|
|
1418
|
+
);
|
|
1419
|
+
const bodyProps = {
|
|
1420
|
+
nodeId,
|
|
1421
|
+
toolName: node.toolName,
|
|
1422
|
+
status: node.status,
|
|
1423
|
+
input: details?.input ?? node.input,
|
|
1424
|
+
inputStream,
|
|
1425
|
+
result,
|
|
1426
|
+
progress: node.progress ?? void 0,
|
|
1427
|
+
subagentProgress: node.subagentProgress,
|
|
1428
|
+
subagentTranscript: node.subagentTranscript,
|
|
1429
|
+
subagentSessions: node.subagentSessions,
|
|
1430
|
+
bodyMode,
|
|
1431
|
+
onBodyModeChange: setBodyMode
|
|
1432
|
+
};
|
|
1433
|
+
const errorMessage = result?.error?.message ?? null;
|
|
1434
|
+
const permissionWait = viewOptions.permissionUiMode !== "standalone" && node.status === "running" ? pendingPermission : void 0;
|
|
1435
|
+
return /* @__PURE__ */ jsx12(
|
|
1436
|
+
ToolCallShell,
|
|
1437
|
+
{
|
|
1438
|
+
status: node.status,
|
|
1439
|
+
title,
|
|
1440
|
+
progress: node.progress ?? void 0,
|
|
1441
|
+
errorMessage,
|
|
1442
|
+
layout,
|
|
1443
|
+
permissionWait,
|
|
1444
|
+
permissionUiMode: viewOptions.permissionUiMode,
|
|
1445
|
+
children: showBody ? /* @__PURE__ */ jsx12("div", { className: "lax-tool-shell__body", children: /* @__PURE__ */ jsx12(BodyComponent, { ...bodyProps }) }) : null
|
|
1446
|
+
}
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// src/view/nodes/StepNode.tsx
|
|
1451
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1452
|
+
function StepCard({ nodeId }) {
|
|
1453
|
+
const node = useNodeTyped(nodeId, "step");
|
|
1454
|
+
if (!node) return null;
|
|
1455
|
+
return /* @__PURE__ */ jsx13("details", { className: "lax-step-card", "data-status": node.status, children: /* @__PURE__ */ jsx13("summary", { className: "lax-step-card__summary", children: node.label }) });
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/view/nodes/ErrorNode.tsx
|
|
1459
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1460
|
+
function ErrorCard({ nodeId }) {
|
|
1461
|
+
const node = useNodeTyped(nodeId, "error");
|
|
1462
|
+
if (!node) return null;
|
|
1463
|
+
return /* @__PURE__ */ jsxs11("div", { className: "lax-error-card", role: "alert", children: [
|
|
1464
|
+
/* @__PURE__ */ jsx14("div", { className: "lax-error-card__message", children: node.message }),
|
|
1465
|
+
node.errorType ? /* @__PURE__ */ jsx14("div", { className: "lax-error-card__type", children: node.errorType }) : null
|
|
1466
|
+
] });
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/view/nodes/UnknownNode.tsx
|
|
1470
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1471
|
+
function UnknownCard({ nodeId }) {
|
|
1472
|
+
const node = useNodeTyped(nodeId, "unknown");
|
|
1473
|
+
if (!node) return null;
|
|
1474
|
+
return /* @__PURE__ */ jsxs12("div", { className: "lax-unknown-card", children: [
|
|
1475
|
+
/* @__PURE__ */ jsxs12("div", { className: "lax-unknown-card__type", children: [
|
|
1476
|
+
"Unknown: ",
|
|
1477
|
+
node.eventType
|
|
1478
|
+
] }),
|
|
1479
|
+
/* @__PURE__ */ jsx15("pre", { className: "lax-unknown-card__data", children: JSON.stringify(node.rawData, null, 2) })
|
|
1480
|
+
] });
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/view/nodes/PermissionNode.tsx
|
|
1484
|
+
import { useState as useState9 } from "react";
|
|
1485
|
+
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1486
|
+
function PermissionCard({ nodeId }) {
|
|
1487
|
+
const node = useNodeTyped(nodeId, "permission");
|
|
1488
|
+
const bus = useInteractionBus();
|
|
1489
|
+
const [resolvedLocally, setResolvedLocally] = useState9(false);
|
|
1490
|
+
if (!node) return null;
|
|
1491
|
+
const pending = node.status === "pending" && !resolvedLocally;
|
|
1492
|
+
const onAllow = () => {
|
|
1493
|
+
void bus.resolvePermission({ requestId: node.requestId, allow: true });
|
|
1494
|
+
setResolvedLocally(true);
|
|
1495
|
+
};
|
|
1496
|
+
const onDeny = () => {
|
|
1497
|
+
void bus.resolvePermission({ requestId: node.requestId, allow: false });
|
|
1498
|
+
setResolvedLocally(true);
|
|
1499
|
+
};
|
|
1500
|
+
return /* @__PURE__ */ jsxs13(
|
|
1501
|
+
"div",
|
|
1502
|
+
{
|
|
1503
|
+
className: "lax-permission-card",
|
|
1504
|
+
"data-status": pending ? "pending" : "resolved",
|
|
1505
|
+
"data-request-id": node.requestId,
|
|
1506
|
+
children: [
|
|
1507
|
+
/* @__PURE__ */ jsxs13("div", { className: "lax-permission-card__header", children: [
|
|
1508
|
+
/* @__PURE__ */ jsx16("span", { className: "lax-permission-card__label", children: "Permission" }),
|
|
1509
|
+
/* @__PURE__ */ jsx16("span", { className: "lax-permission-card__tool", children: node.toolName })
|
|
1510
|
+
] }),
|
|
1511
|
+
/* @__PURE__ */ jsx16("div", { className: "lax-permission-card__message", children: node.message }),
|
|
1512
|
+
node.askPrompt && node.askPrompt !== node.message ? /* @__PURE__ */ jsx16("div", { className: "lax-permission-card__prompt", children: node.askPrompt }) : null,
|
|
1513
|
+
pending ? /* @__PURE__ */ jsxs13("div", { className: "lax-permission-card__actions", children: [
|
|
1514
|
+
/* @__PURE__ */ jsx16("button", { type: "button", className: "lax-permission-card__allow", onClick: onAllow, children: "Allow" }),
|
|
1515
|
+
/* @__PURE__ */ jsx16("button", { type: "button", className: "lax-permission-card__deny", onClick: onDeny, children: "Deny" })
|
|
1516
|
+
] }) : /* @__PURE__ */ jsx16("div", { className: "lax-permission-card__resolved", children: "Resolved" })
|
|
1517
|
+
]
|
|
1518
|
+
}
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// src/view/nodes/MemorySavedNode.tsx
|
|
1523
|
+
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1524
|
+
function MemorySavedRow({ nodeId }) {
|
|
1525
|
+
const node = useNodeTyped(nodeId, "memory_saved");
|
|
1526
|
+
if (!node) return null;
|
|
1527
|
+
const hasPaths = node.writtenPaths.length > 0;
|
|
1528
|
+
return /* @__PURE__ */ jsxs14(
|
|
1529
|
+
"details",
|
|
1530
|
+
{
|
|
1531
|
+
className: "lax-memory-saved",
|
|
1532
|
+
"data-testid": "lax-memory-saved",
|
|
1533
|
+
"data-node-id": nodeId,
|
|
1534
|
+
children: [
|
|
1535
|
+
/* @__PURE__ */ jsxs14("summary", { className: "lax-memory-saved__summary", children: [
|
|
1536
|
+
/* @__PURE__ */ jsx17("span", { className: "lax-memory-saved__icon", "aria-hidden": "true", children: "\u{1F4BE}" }),
|
|
1537
|
+
/* @__PURE__ */ jsx17("span", { className: "lax-memory-saved__hint", children: node.displayHint })
|
|
1538
|
+
] }),
|
|
1539
|
+
hasPaths ? /* @__PURE__ */ jsx17("ul", { className: "lax-memory-saved__paths", children: node.writtenPaths.map((p) => /* @__PURE__ */ jsx17("li", { className: "lax-memory-saved__path", children: /* @__PURE__ */ jsx17("code", { children: p }) }, p)) }) : null
|
|
1540
|
+
]
|
|
1541
|
+
}
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/view/NodeRegistry.ts
|
|
1546
|
+
var DEFAULT_WIDGETS = /* @__PURE__ */ new Map([
|
|
1547
|
+
["text", TextBubble],
|
|
1548
|
+
["reasoning", ReasoningBubble],
|
|
1549
|
+
["tool_call", ToolCallCard],
|
|
1550
|
+
["step", StepCard],
|
|
1551
|
+
["error", ErrorCard],
|
|
1552
|
+
["unknown", UnknownCard],
|
|
1553
|
+
["permission", PermissionCard],
|
|
1554
|
+
["memory_saved", MemorySavedRow]
|
|
1555
|
+
]);
|
|
1556
|
+
var NodeRegistry = class _NodeRegistry {
|
|
1557
|
+
widgets;
|
|
1558
|
+
constructor(widgets) {
|
|
1559
|
+
this.widgets = widgets;
|
|
1560
|
+
}
|
|
1561
|
+
static default() {
|
|
1562
|
+
return new _NodeRegistry(new Map(DEFAULT_WIDGETS));
|
|
1563
|
+
}
|
|
1564
|
+
override(kind, widget) {
|
|
1565
|
+
const next = new Map(this.widgets);
|
|
1566
|
+
next.set(kind, widget);
|
|
1567
|
+
return new _NodeRegistry(next);
|
|
1568
|
+
}
|
|
1569
|
+
register(kind, widget) {
|
|
1570
|
+
return this.override(kind, widget);
|
|
1571
|
+
}
|
|
1572
|
+
get(kind) {
|
|
1573
|
+
return this.widgets.get(kind);
|
|
1574
|
+
}
|
|
1575
|
+
has(kind) {
|
|
1576
|
+
return this.widgets.has(kind);
|
|
1577
|
+
}
|
|
1578
|
+
};
|
|
1579
|
+
|
|
1580
|
+
// src/view/defaultRegistry.ts
|
|
1581
|
+
function createDefaultRegistry() {
|
|
1582
|
+
return NodeRegistry.default();
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/core/sessionStore.ts
|
|
1586
|
+
import { createStore } from "zustand/vanilla";
|
|
1587
|
+
|
|
1588
|
+
// src/core/dedupe.ts
|
|
1589
|
+
function shouldSkipDuplicateEvent(seenEventIds, sseEventId) {
|
|
1590
|
+
if (!sseEventId) return false;
|
|
1591
|
+
return seenEventIds.has(sseEventId);
|
|
1592
|
+
}
|
|
1593
|
+
function markEventIdSeen(seenEventIds, sseEventId) {
|
|
1594
|
+
if (sseEventId) seenEventIds.add(sseEventId);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// src/core/sessionStore.ts
|
|
1598
|
+
var DEFAULT_MAX_SESSIONS = 10;
|
|
1599
|
+
function touchOrder(order, sessionId) {
|
|
1600
|
+
return [...order.filter((id) => id !== sessionId), sessionId];
|
|
1601
|
+
}
|
|
1602
|
+
function safeReduce(tree, event, eventIndex, options) {
|
|
1603
|
+
try {
|
|
1604
|
+
return reduceTree(tree, event, eventIndex, options);
|
|
1605
|
+
} catch (err) {
|
|
1606
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1607
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
1608
|
+
if (import.meta.env?.DEV) {
|
|
1609
|
+
console.error("[langchain_agentx_stream_ui] reducer error:", err);
|
|
1610
|
+
}
|
|
1611
|
+
const reducerError = {
|
|
1612
|
+
eventIndex,
|
|
1613
|
+
eventType: event.event_type,
|
|
1614
|
+
message,
|
|
1615
|
+
stack
|
|
1616
|
+
};
|
|
1617
|
+
return {
|
|
1618
|
+
...tree,
|
|
1619
|
+
internalErrors: [...tree.internalErrors, reducerError]
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
function evictSessions(sessions, order, maxSessions, activeSessionId) {
|
|
1624
|
+
let nextSessions = { ...sessions };
|
|
1625
|
+
let nextOrder = [...order];
|
|
1626
|
+
while (nextOrder.length > maxSessions) {
|
|
1627
|
+
const candidate = nextOrder.find((id) => id !== activeSessionId) ?? nextOrder[0];
|
|
1628
|
+
if (!candidate) break;
|
|
1629
|
+
const { [candidate]: _removed, ...rest } = nextSessions;
|
|
1630
|
+
nextSessions = rest;
|
|
1631
|
+
nextOrder = nextOrder.filter((id) => id !== candidate);
|
|
1632
|
+
}
|
|
1633
|
+
return { sessions: nextSessions, order: nextOrder };
|
|
1634
|
+
}
|
|
1635
|
+
function createMultiSessionStore(options) {
|
|
1636
|
+
const maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
1637
|
+
const reduceOpts = {
|
|
1638
|
+
tierOverrides: options?.tierOverrides,
|
|
1639
|
+
collectDebug: options?.debug === true
|
|
1640
|
+
};
|
|
1641
|
+
const replayOpts = {
|
|
1642
|
+
tierOverrides: options?.tierOverrides,
|
|
1643
|
+
collectDebug: options?.debug === true
|
|
1644
|
+
};
|
|
1645
|
+
const seenBySession = /* @__PURE__ */ new Map();
|
|
1646
|
+
function getSeenSet(sessionId) {
|
|
1647
|
+
let set = seenBySession.get(sessionId);
|
|
1648
|
+
if (!set) {
|
|
1649
|
+
set = /* @__PURE__ */ new Set();
|
|
1650
|
+
seenBySession.set(sessionId, set);
|
|
1651
|
+
}
|
|
1652
|
+
return set;
|
|
1653
|
+
}
|
|
1654
|
+
return createStore((set, get) => ({
|
|
1655
|
+
sessions: {},
|
|
1656
|
+
activeSessionId: options?.activeSessionId ?? null,
|
|
1657
|
+
order: [],
|
|
1658
|
+
getSessionTree(sessionId) {
|
|
1659
|
+
return get().sessions[sessionId]?.tree;
|
|
1660
|
+
},
|
|
1661
|
+
ensureSession(sessionId, initialEvents) {
|
|
1662
|
+
const state = get();
|
|
1663
|
+
if (state.sessions[sessionId]) return;
|
|
1664
|
+
const tree = initialEvents && initialEvents.length > 0 ? replayEvents(initialEvents, replayOpts) : createEmptyTree();
|
|
1665
|
+
const slice = {
|
|
1666
|
+
tree,
|
|
1667
|
+
eventCount: initialEvents?.length ?? 0
|
|
1668
|
+
};
|
|
1669
|
+
let order = touchOrder(state.order, sessionId);
|
|
1670
|
+
let sessions = { ...state.sessions, [sessionId]: slice };
|
|
1671
|
+
({ sessions, order } = evictSessions(sessions, order, maxSessions, state.activeSessionId));
|
|
1672
|
+
set({
|
|
1673
|
+
sessions,
|
|
1674
|
+
order,
|
|
1675
|
+
activeSessionId: state.activeSessionId ?? sessionId
|
|
1676
|
+
});
|
|
1677
|
+
},
|
|
1678
|
+
applyEvent(sessionId, event, ctx) {
|
|
1679
|
+
const sseEventId = ctx?.sseEventId;
|
|
1680
|
+
const seen = getSeenSet(sessionId);
|
|
1681
|
+
if (shouldSkipDuplicateEvent(seen, sseEventId)) return;
|
|
1682
|
+
const state = get();
|
|
1683
|
+
let slice = state.sessions[sessionId];
|
|
1684
|
+
if (!slice) {
|
|
1685
|
+
get().ensureSession(sessionId);
|
|
1686
|
+
slice = get().sessions[sessionId];
|
|
1687
|
+
}
|
|
1688
|
+
const reduced = safeReduce(slice.tree, event, slice.eventCount, reduceOpts);
|
|
1689
|
+
const nextTree = sseEventId ? { ...reduced, meta: { ...reduced.meta, lastEventId: sseEventId } } : reduced;
|
|
1690
|
+
const nextSlice = {
|
|
1691
|
+
tree: nextTree,
|
|
1692
|
+
eventCount: slice.eventCount + 1
|
|
1693
|
+
};
|
|
1694
|
+
markEventIdSeen(seen, sseEventId);
|
|
1695
|
+
let sessions = { ...state.sessions, [sessionId]: nextSlice };
|
|
1696
|
+
let order = touchOrder(state.order, sessionId);
|
|
1697
|
+
({ sessions, order } = evictSessions(sessions, order, maxSessions, state.activeSessionId));
|
|
1698
|
+
set({ sessions, order });
|
|
1699
|
+
},
|
|
1700
|
+
applyEvents(sessionId, events) {
|
|
1701
|
+
for (const event of events) {
|
|
1702
|
+
get().applyEvent(sessionId, event);
|
|
1703
|
+
}
|
|
1704
|
+
},
|
|
1705
|
+
setActiveSessionId(id) {
|
|
1706
|
+
set({ activeSessionId: id });
|
|
1707
|
+
},
|
|
1708
|
+
removeSession(sessionId) {
|
|
1709
|
+
const state = get();
|
|
1710
|
+
if (!state.sessions[sessionId]) return;
|
|
1711
|
+
seenBySession.delete(sessionId);
|
|
1712
|
+
const { [sessionId]: _removed, ...sessions } = state.sessions;
|
|
1713
|
+
const order = state.order.filter((id) => id !== sessionId);
|
|
1714
|
+
const activeSessionId = state.activeSessionId === sessionId ? order[order.length - 1] ?? null : state.activeSessionId;
|
|
1715
|
+
set({ sessions, order, activeSessionId });
|
|
1716
|
+
},
|
|
1717
|
+
replaySession(sessionId, events) {
|
|
1718
|
+
const tree = replayEvents(events, replayOpts);
|
|
1719
|
+
const slice = { tree, eventCount: events.length };
|
|
1720
|
+
const state = get();
|
|
1721
|
+
let order = touchOrder(state.order, sessionId);
|
|
1722
|
+
let sessions = { ...state.sessions, [sessionId]: slice };
|
|
1723
|
+
({ sessions, order } = evictSessions(sessions, order, maxSessions, state.activeSessionId));
|
|
1724
|
+
set({ sessions, order });
|
|
1725
|
+
}
|
|
1726
|
+
}));
|
|
1727
|
+
}
|
|
1728
|
+
function createActiveSessionBridge(multiStore) {
|
|
1729
|
+
const bridge = createStore(() => ({
|
|
1730
|
+
tree: createEmptyTree(),
|
|
1731
|
+
eventCount: 0,
|
|
1732
|
+
applyEvent(event, ctx) {
|
|
1733
|
+
const id = multiStore.getState().activeSessionId;
|
|
1734
|
+
if (id) multiStore.getState().applyEvent(id, event, ctx);
|
|
1735
|
+
},
|
|
1736
|
+
applyEvents(events) {
|
|
1737
|
+
const id = multiStore.getState().activeSessionId;
|
|
1738
|
+
if (id) multiStore.getState().applyEvents(id, events);
|
|
1739
|
+
},
|
|
1740
|
+
reset() {
|
|
1741
|
+
const id = multiStore.getState().activeSessionId;
|
|
1742
|
+
if (id) multiStore.getState().replaySession(id, []);
|
|
1743
|
+
}
|
|
1744
|
+
}));
|
|
1745
|
+
const sync = () => {
|
|
1746
|
+
const { activeSessionId, sessions } = multiStore.getState();
|
|
1747
|
+
const slice = activeSessionId ? sessions[activeSessionId] : void 0;
|
|
1748
|
+
bridge.setState({
|
|
1749
|
+
tree: slice?.tree ?? createEmptyTree(),
|
|
1750
|
+
eventCount: slice?.eventCount ?? 0
|
|
1751
|
+
});
|
|
1752
|
+
};
|
|
1753
|
+
multiStore.subscribe(sync);
|
|
1754
|
+
sync();
|
|
1755
|
+
return bridge;
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// src/view/MultiAgentSession.tsx
|
|
1759
|
+
import { useEffect as useEffect8, useMemo as useMemo2, useRef as useRef7 } from "react";
|
|
1760
|
+
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1761
|
+
function DebugPanel() {
|
|
1762
|
+
const status = useSessionStatus();
|
|
1763
|
+
const errors = useInternalErrors();
|
|
1764
|
+
if (errors.length === 0) return null;
|
|
1765
|
+
return /* @__PURE__ */ jsxs15("div", { className: "lax-debug-panel", "data-testid": "lax-debug-panel", children: [
|
|
1766
|
+
/* @__PURE__ */ jsxs15("div", { children: [
|
|
1767
|
+
"status: ",
|
|
1768
|
+
status
|
|
1769
|
+
] }),
|
|
1770
|
+
/* @__PURE__ */ jsx18("ul", { children: errors.map((err, i) => /* @__PURE__ */ jsxs15("li", { children: [
|
|
1771
|
+
"[",
|
|
1772
|
+
err.eventType,
|
|
1773
|
+
"] ",
|
|
1774
|
+
err.message
|
|
1775
|
+
] }, `${err.eventIndex}-${i}`)) })
|
|
1776
|
+
] });
|
|
1777
|
+
}
|
|
1778
|
+
function MultiAgentSession({
|
|
1779
|
+
sessions,
|
|
1780
|
+
activeSessionId: controlledActiveId,
|
|
1781
|
+
defaultActiveSessionId,
|
|
1782
|
+
onActiveSessionChange,
|
|
1783
|
+
maxSessions,
|
|
1784
|
+
tierOverrides,
|
|
1785
|
+
debug = false,
|
|
1786
|
+
registry,
|
|
1787
|
+
onError,
|
|
1788
|
+
virtualized = false,
|
|
1789
|
+
virtualizeThreshold,
|
|
1790
|
+
interactionBus,
|
|
1791
|
+
toolDisplayRegistry,
|
|
1792
|
+
defaultBodyMode = "preview",
|
|
1793
|
+
groupParallelTools = false,
|
|
1794
|
+
permissionUiMode = "standalone",
|
|
1795
|
+
markdownRenderer,
|
|
1796
|
+
children
|
|
1797
|
+
}) {
|
|
1798
|
+
const multiStoreRef = useRef7(null);
|
|
1799
|
+
const bridgeRef = useRef7(null);
|
|
1800
|
+
if (multiStoreRef.current === null) {
|
|
1801
|
+
const defaultId = defaultActiveSessionId ?? controlledActiveId ?? sessions[0]?.sessionId ?? null;
|
|
1802
|
+
multiStoreRef.current = createMultiSessionStore({
|
|
1803
|
+
maxSessions,
|
|
1804
|
+
tierOverrides,
|
|
1805
|
+
debug,
|
|
1806
|
+
activeSessionId: defaultId
|
|
1807
|
+
});
|
|
1808
|
+
bridgeRef.current = createActiveSessionBridge(multiStoreRef.current);
|
|
1809
|
+
for (const cfg of sessions) {
|
|
1810
|
+
multiStoreRef.current.getState().ensureSession(cfg.sessionId, cfg.initialEvents);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
const registryRef = useMemo2(() => registry ?? createDefaultRegistry(), [registry]);
|
|
1814
|
+
const busRef = useMemo2(
|
|
1815
|
+
() => interactionBus ?? getNoopInteractionBus(),
|
|
1816
|
+
[interactionBus]
|
|
1817
|
+
);
|
|
1818
|
+
const toolDisplayRef = useMemo2(
|
|
1819
|
+
() => ({
|
|
1820
|
+
registry: toolDisplayRegistry ?? createDefaultToolRegistry(),
|
|
1821
|
+
defaultBodyMode
|
|
1822
|
+
}),
|
|
1823
|
+
[toolDisplayRegistry, defaultBodyMode]
|
|
1824
|
+
);
|
|
1825
|
+
const sessionViewRef = useMemo2(
|
|
1826
|
+
() => ({
|
|
1827
|
+
...DEFAULT_SESSION_VIEW_OPTIONS,
|
|
1828
|
+
groupParallelTools,
|
|
1829
|
+
permissionUiMode,
|
|
1830
|
+
verboseReasoning: debug
|
|
1831
|
+
}),
|
|
1832
|
+
[groupParallelTools, permissionUiMode, debug]
|
|
1833
|
+
);
|
|
1834
|
+
useEffect8(() => {
|
|
1835
|
+
if (controlledActiveId === void 0) return;
|
|
1836
|
+
multiStoreRef.current.getState().setActiveSessionId(controlledActiveId);
|
|
1837
|
+
}, [controlledActiveId]);
|
|
1838
|
+
useEffect8(() => {
|
|
1839
|
+
if (!onActiveSessionChange) return;
|
|
1840
|
+
return multiStoreRef.current.subscribe((state) => {
|
|
1841
|
+
onActiveSessionChange(state.activeSessionId);
|
|
1842
|
+
});
|
|
1843
|
+
}, [onActiveSessionChange]);
|
|
1844
|
+
useEffect8(() => {
|
|
1845
|
+
const multiStore = multiStoreRef.current;
|
|
1846
|
+
const controllers = sessions.map((cfg) => {
|
|
1847
|
+
const controller = new AbortController();
|
|
1848
|
+
multiStore.getState().ensureSession(cfg.sessionId, cfg.initialEvents);
|
|
1849
|
+
void cfg.source.start((event, ctx) => {
|
|
1850
|
+
multiStore.getState().applyEvent(cfg.sessionId, event, ctx);
|
|
1851
|
+
}, controller.signal).catch((err) => {
|
|
1852
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1853
|
+
onError?.(error, cfg.sessionId);
|
|
1854
|
+
});
|
|
1855
|
+
return controller;
|
|
1856
|
+
});
|
|
1857
|
+
return () => {
|
|
1858
|
+
for (const c of controllers) c.abort();
|
|
1859
|
+
};
|
|
1860
|
+
}, [sessions, onError]);
|
|
1861
|
+
return /* @__PURE__ */ jsx18(MultiSessionStoreContext.Provider, { value: multiStoreRef.current, children: /* @__PURE__ */ jsx18(SessionStoreContext.Provider, { value: bridgeRef.current, children: /* @__PURE__ */ jsx18(InteractionBusContext.Provider, { value: busRef, children: /* @__PURE__ */ jsx18(NodeRegistryContext.Provider, { value: registryRef, children: /* @__PURE__ */ jsx18(MarkdownRendererContext.Provider, { value: markdownRenderer, children: /* @__PURE__ */ jsx18(ToolDisplayOptionsContext.Provider, { value: toolDisplayRef, children: /* @__PURE__ */ jsx18(SessionViewOptionsContext.Provider, { value: sessionViewRef, children: /* @__PURE__ */ jsxs15("div", { className: "lax-agent-session lax-multi-agent-session", "data-testid": "lax-multi-agent-session", children: [
|
|
1862
|
+
children ?? /* @__PURE__ */ jsx18(
|
|
1863
|
+
SessionTimeline,
|
|
1864
|
+
{
|
|
1865
|
+
virtualized,
|
|
1866
|
+
virtualizeThreshold,
|
|
1867
|
+
groupParallelTools
|
|
1868
|
+
}
|
|
1869
|
+
),
|
|
1870
|
+
debug ? /* @__PURE__ */ jsx18(DebugPanel, {}) : null
|
|
1871
|
+
] }) }) }) }) }) }) }) });
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
export {
|
|
1875
|
+
shouldSkipDuplicateEvent,
|
|
1876
|
+
markEventIdSeen,
|
|
1877
|
+
computeViewportFitTransform,
|
|
1878
|
+
DiagramZoomShell,
|
|
1879
|
+
DiagramZoomView,
|
|
1880
|
+
fixDiagramSvg,
|
|
1881
|
+
configureMermaid,
|
|
1882
|
+
MermaidDiagram,
|
|
1883
|
+
PlantUMLBlock,
|
|
1884
|
+
configurePlantumlServer,
|
|
1885
|
+
PlantUMLDiagram,
|
|
1886
|
+
RichMarkdown,
|
|
1887
|
+
ReasoningBubble,
|
|
1888
|
+
TextBubble,
|
|
1889
|
+
ToolCallCard,
|
|
1890
|
+
StepCard,
|
|
1891
|
+
ErrorCard,
|
|
1892
|
+
UnknownCard,
|
|
1893
|
+
PermissionCard,
|
|
1894
|
+
NodeRegistry,
|
|
1895
|
+
createDefaultRegistry,
|
|
1896
|
+
createMultiSessionStore,
|
|
1897
|
+
createActiveSessionBridge,
|
|
1898
|
+
MultiAgentSession
|
|
1899
|
+
};
|
|
1900
|
+
//# sourceMappingURL=chunk-WPW3TQQF.js.map
|