@pipelex/mthds-ui 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2207 @@
1
+ "use client";
2
+ import {
3
+ DEFAULT_GRAPH_CONFIG,
4
+ EDGE_TYPE,
5
+ GRAPH_DIRECTION,
6
+ MAX_VISIBLE_CONTROLLER_CHILDREN,
7
+ applyControllers,
8
+ buildGraph,
9
+ getLayoutedElements,
10
+ getPipeBlueprint,
11
+ resolveConceptRef,
12
+ stuffDigestFromId
13
+ } from "../../chunk-CCUSQM3E.js";
14
+ import {
15
+ __spreadProps,
16
+ __spreadValues
17
+ } from "../../chunk-DDAAVRWG.js";
18
+
19
+ // src/graph/react/index.ts
20
+ import "./graph-core.css";
21
+ import "./detail/DetailPanel.css";
22
+ import "./stuff/StuffViewer.css";
23
+ import "./viewer/GraphToolbar.css";
24
+
25
+ // src/graph/react/viewer/GraphViewer.tsx
26
+ import React7 from "react";
27
+ import {
28
+ ReactFlow,
29
+ useNodesState,
30
+ useEdgesState,
31
+ Background,
32
+ BackgroundVariant
33
+ } from "@xyflow/react";
34
+
35
+ // src/graph/react/stuff/stuffViewerUtils.ts
36
+ function isSafeDisplayUrl(url) {
37
+ if (!url || typeof url !== "string") return false;
38
+ return url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://") || url.startsWith("/");
39
+ }
40
+ function isInlineRenderableUrl(url) {
41
+ if (!url || typeof url !== "string") return false;
42
+ return url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://") || url.startsWith("/");
43
+ }
44
+ function extractUrl(data) {
45
+ if (!data) return null;
46
+ if (typeof data === "string") {
47
+ return isSafeDisplayUrl(data) ? data : null;
48
+ }
49
+ if (typeof data !== "object") return null;
50
+ const obj = data;
51
+ const candidates = [obj.public_url, obj.src, obj.href, obj.uri, obj.url];
52
+ for (const candidate of candidates) {
53
+ if (isSafeDisplayUrl(candidate)) return candidate;
54
+ }
55
+ return null;
56
+ }
57
+ function extractInlineUrl(data) {
58
+ if (!data) return null;
59
+ if (typeof data === "string") {
60
+ return isInlineRenderableUrl(data) ? data : null;
61
+ }
62
+ if (typeof data !== "object") return null;
63
+ const obj = data;
64
+ const candidates = [obj.public_url, obj.src, obj.href, obj.uri, obj.url];
65
+ for (const candidate of candidates) {
66
+ if (isInlineRenderableUrl(candidate)) return candidate;
67
+ }
68
+ return null;
69
+ }
70
+ function extractStorageUri(data) {
71
+ if (!data) return null;
72
+ if (typeof data === "string") {
73
+ return data.startsWith("pipelex-storage://") ? data : null;
74
+ }
75
+ if (typeof data !== "object") return null;
76
+ const obj = data;
77
+ const candidates = [obj.uri, obj.url, obj.src, obj.href];
78
+ for (const candidate of candidates) {
79
+ if (typeof candidate === "string" && candidate.startsWith("pipelex-storage://")) {
80
+ return candidate;
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+ function extractFilename(data) {
86
+ if (!data || typeof data !== "object") return null;
87
+ const obj = data;
88
+ if (typeof obj.filename === "string" && obj.filename) return obj.filename;
89
+ return null;
90
+ }
91
+ function resolveMimeType(data, contentType, url) {
92
+ var _a;
93
+ if (contentType && contentType.includes("/")) return contentType;
94
+ if (data && typeof data === "object") {
95
+ const obj = data;
96
+ if (typeof obj.mime_type === "string" && obj.mime_type) return obj.mime_type;
97
+ }
98
+ const source = extractFilename(data) || url || (typeof data === "string" ? data : null);
99
+ if (source) {
100
+ const match = source.match(/\.([a-zA-Z0-9]+)(?:\?|#|$)/);
101
+ const ext = (_a = match == null ? void 0 : match[1]) == null ? void 0 : _a.toLowerCase();
102
+ if (ext) {
103
+ if (ext === "pdf") return "application/pdf";
104
+ if (["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"].includes(ext)) {
105
+ return `image/${ext === "jpg" ? "jpeg" : ext === "svg" ? "svg+xml" : ext}`;
106
+ }
107
+ }
108
+ }
109
+ return null;
110
+ }
111
+ function getHtmlTabLabel(contentType) {
112
+ if (contentType === "application/pdf") return "PDF";
113
+ if (contentType == null ? void 0 : contentType.startsWith("image/")) return "Image";
114
+ return "HTML";
115
+ }
116
+ function findStuffDataByDigest(graphspec, digest) {
117
+ var _a, _b, _c, _d;
118
+ for (const node of graphspec.nodes) {
119
+ for (const output of (_b = (_a = node.io) == null ? void 0 : _a.outputs) != null ? _b : []) {
120
+ if (output.digest === digest) {
121
+ return {
122
+ digest,
123
+ name: output.name,
124
+ concept: output.concept,
125
+ contentType: output.content_type,
126
+ data: output.data,
127
+ dataText: output.data_text,
128
+ dataHtml: output.data_html
129
+ };
130
+ }
131
+ }
132
+ }
133
+ for (const node of graphspec.nodes) {
134
+ for (const input of (_d = (_c = node.io) == null ? void 0 : _c.inputs) != null ? _d : []) {
135
+ if (input.digest === digest) {
136
+ return {
137
+ digest,
138
+ name: input.name,
139
+ concept: input.concept,
140
+ contentType: input.content_type,
141
+ data: input.data,
142
+ dataText: input.data_text,
143
+ dataHtml: input.data_html
144
+ };
145
+ }
146
+ }
147
+ }
148
+ return null;
149
+ }
150
+
151
+ // src/graph/react/stuff/StuffViewer.tsx
152
+ import React from "react";
153
+ import DOMPurify from "dompurify";
154
+ import { jsx, jsxs } from "react/jsx-runtime";
155
+ var ICON_COPY = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }) });
156
+ var ICON_CHECK = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) });
157
+ var ICON_DOWNLOAD = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z" }) });
158
+ var ICON_EXTERNAL = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" }) });
159
+ var ICON_LOCAL_FILE = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" }) });
160
+ function escapeHtml(text) {
161
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
162
+ }
163
+ function downloadBlob(content, filename, mimeType) {
164
+ const blob = new Blob([content], { type: mimeType });
165
+ const url = URL.createObjectURL(blob);
166
+ const link = document.createElement("a");
167
+ link.href = url;
168
+ link.download = filename;
169
+ link.click();
170
+ setTimeout(() => URL.revokeObjectURL(url), 1e4);
171
+ }
172
+ function downloadUrl(url, filename) {
173
+ const link = document.createElement("a");
174
+ link.href = url;
175
+ link.download = filename;
176
+ link.target = "_blank";
177
+ link.click();
178
+ }
179
+ function StuffViewer({ stuff, className, resolveStorageUrl }) {
180
+ var _a;
181
+ const [activeTab, setActiveTab] = React.useState("html");
182
+ const [copied, setCopied] = React.useState(false);
183
+ const contentRef = React.useRef(null);
184
+ const httpInlineUrl = React.useMemo(() => extractInlineUrl(stuff.data), [stuff.data]);
185
+ const httpExternalUrl = React.useMemo(() => extractUrl(stuff.data), [stuff.data]);
186
+ const storageUri = React.useMemo(() => extractStorageUri(stuff.data), [stuff.data]);
187
+ const filename = React.useMemo(() => extractFilename(stuff.data), [stuff.data]);
188
+ const [resolvedStorageUrl, setResolvedStorageUrl] = React.useState(null);
189
+ React.useEffect(() => {
190
+ setResolvedStorageUrl(null);
191
+ if (!storageUri || !resolveStorageUrl || httpInlineUrl) {
192
+ return;
193
+ }
194
+ let cancelled = false;
195
+ resolveStorageUrl(storageUri).then((url) => {
196
+ if (cancelled) return;
197
+ setResolvedStorageUrl(isInlineRenderableUrl(url) ? url : null);
198
+ }).catch(() => {
199
+ if (!cancelled) setResolvedStorageUrl(null);
200
+ });
201
+ return () => {
202
+ cancelled = true;
203
+ };
204
+ }, [storageUri, resolveStorageUrl, httpInlineUrl]);
205
+ const inlineUrl = httpInlineUrl != null ? httpInlineUrl : resolvedStorageUrl;
206
+ const externalUrl = httpExternalUrl != null ? httpExternalUrl : resolvedStorageUrl;
207
+ const effectiveMime = React.useMemo(
208
+ () => resolveMimeType(stuff.data, stuff.contentType, externalUrl != null ? externalUrl : storageUri),
209
+ [stuff.data, stuff.contentType, externalUrl, storageUri]
210
+ );
211
+ const isPdf = effectiveMime === "application/pdf";
212
+ const isImage = (_a = effectiveMime == null ? void 0 : effectiveMime.startsWith("image/")) != null ? _a : false;
213
+ const htmlTabLabel = getHtmlTabLabel(effectiveMime != null ? effectiveMime : stuff.contentType);
214
+ const jsonString = React.useMemo(() => {
215
+ if (stuff.data == null) return null;
216
+ try {
217
+ return JSON.stringify(stuff.data, null, 2);
218
+ } catch (e) {
219
+ return "[Unable to serialize data]";
220
+ }
221
+ }, [stuff.data]);
222
+ React.useEffect(() => {
223
+ if (activeTab !== "html" || !contentRef.current) return;
224
+ contentRef.current.querySelectorAll("a").forEach((link) => {
225
+ link.setAttribute("target", "_blank");
226
+ link.setAttribute("rel", "noopener noreferrer");
227
+ });
228
+ }, [activeTab, stuff.dataHtml]);
229
+ function renderMediaFallback(mediaLabel) {
230
+ const displayName = filename || stuff.name;
231
+ return /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-local-file", children: [
232
+ /* @__PURE__ */ jsx("div", { className: "stuff-viewer-local-file-icon", children: ICON_LOCAL_FILE }),
233
+ /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-local-file-info", children: [
234
+ displayName && /* @__PURE__ */ jsx("div", { className: "stuff-viewer-local-file-name", children: displayName }),
235
+ /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-local-file-hint", children: [
236
+ mediaLabel,
237
+ " \u2014 no preview available"
238
+ ] })
239
+ ] })
240
+ ] });
241
+ }
242
+ function renderContent() {
243
+ if (activeTab === "html") {
244
+ if (isPdf) {
245
+ if (inlineUrl) {
246
+ const pdfUrl = inlineUrl.includes("#") ? inlineUrl : `${inlineUrl}#pagemode=none`;
247
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-pdf", children: /* @__PURE__ */ jsx("embed", { src: pdfUrl, type: "application/pdf" }) });
248
+ }
249
+ return renderMediaFallback("PDF");
250
+ }
251
+ if (isImage) {
252
+ if (inlineUrl) {
253
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-image", children: /* @__PURE__ */ jsx("img", { src: inlineUrl, alt: stuff.name || "Image content" }) });
254
+ }
255
+ return renderMediaFallback("Image");
256
+ }
257
+ if (stuff.dataHtml) {
258
+ return /* @__PURE__ */ jsx(
259
+ "div",
260
+ {
261
+ className: "stuff-viewer-html",
262
+ dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(stuff.dataHtml) }
263
+ }
264
+ );
265
+ }
266
+ if (jsonString) {
267
+ return /* @__PURE__ */ jsx(
268
+ "pre",
269
+ {
270
+ className: "stuff-viewer-pre",
271
+ dangerouslySetInnerHTML: { __html: escapeHtml(jsonString) }
272
+ }
273
+ );
274
+ }
275
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No content available" });
276
+ }
277
+ if (activeTab === "json") {
278
+ if (jsonString) {
279
+ return /* @__PURE__ */ jsx(
280
+ "pre",
281
+ {
282
+ className: "stuff-viewer-pre",
283
+ dangerouslySetInnerHTML: { __html: escapeHtml(jsonString) }
284
+ }
285
+ );
286
+ }
287
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No JSON data available" });
288
+ }
289
+ if (stuff.dataText) {
290
+ return /* @__PURE__ */ jsx(
291
+ "pre",
292
+ {
293
+ className: "stuff-viewer-pre stuff-viewer-pre--nowrap",
294
+ dangerouslySetInnerHTML: { __html: escapeHtml(stuff.dataText) }
295
+ }
296
+ );
297
+ }
298
+ if (jsonString) {
299
+ return /* @__PURE__ */ jsx(
300
+ "pre",
301
+ {
302
+ className: "stuff-viewer-pre",
303
+ dangerouslySetInnerHTML: { __html: escapeHtml(jsonString) }
304
+ }
305
+ );
306
+ }
307
+ return /* @__PURE__ */ jsx("div", { className: "stuff-viewer-placeholder", children: "No text data available" });
308
+ }
309
+ function handleCopy() {
310
+ let textToCopy;
311
+ if (activeTab === "json") {
312
+ textToCopy = jsonString || "";
313
+ } else if (activeTab === "text") {
314
+ textToCopy = stuff.dataText || jsonString || "";
315
+ } else {
316
+ textToCopy = stuff.dataText || stuff.dataHtml || jsonString || "";
317
+ }
318
+ if (!textToCopy) return;
319
+ if (navigator.clipboard) {
320
+ navigator.clipboard.writeText(textToCopy).then(() => {
321
+ setCopied(true);
322
+ setTimeout(() => setCopied(false), 1500);
323
+ }).catch(() => {
324
+ });
325
+ }
326
+ }
327
+ function handleDownload() {
328
+ if (isImage && externalUrl) {
329
+ const ext = (effectiveMime == null ? void 0 : effectiveMime.split("/")[1]) || "png";
330
+ downloadUrl(externalUrl, `${stuff.name || "stuff"}.${ext}`);
331
+ return;
332
+ }
333
+ if (isPdf && externalUrl) {
334
+ downloadUrl(externalUrl, `${stuff.name || "stuff"}.pdf`);
335
+ return;
336
+ }
337
+ if (activeTab === "json") {
338
+ downloadBlob(jsonString || "{}", `${stuff.name || "stuff"}.json`, "application/json");
339
+ } else if (activeTab === "text") {
340
+ downloadBlob(
341
+ stuff.dataText || jsonString || "",
342
+ `${stuff.name || "stuff"}.txt`,
343
+ "text/plain"
344
+ );
345
+ } else {
346
+ downloadBlob(
347
+ stuff.dataHtml || jsonString || "",
348
+ `${stuff.name || "stuff"}.html`,
349
+ "text/html"
350
+ );
351
+ }
352
+ }
353
+ function handleOpenExternal() {
354
+ if (externalUrl) {
355
+ window.open(externalUrl, "_blank", "noopener,noreferrer");
356
+ }
357
+ }
358
+ const rootClass = ["stuff-viewer", className].filter(Boolean).join(" ");
359
+ return /* @__PURE__ */ jsxs("div", { className: rootClass, children: [
360
+ /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-header", children: [
361
+ /* @__PURE__ */ jsx("h3", { className: "stuff-viewer-title", children: stuff.name || "Data" }),
362
+ stuff.concept && /* @__PURE__ */ jsx("p", { className: "stuff-viewer-subtitle", children: stuff.concept })
363
+ ] }),
364
+ /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-toolbar", children: [
365
+ /* @__PURE__ */ jsx("div", { className: "stuff-viewer-tabs", children: ["html", "json", "text"].map((tab) => {
366
+ const label = tab === "html" ? htmlTabLabel : tab === "json" ? "JSON" : "Pretty";
367
+ const isActive = activeTab === tab;
368
+ return /* @__PURE__ */ jsx(
369
+ "button",
370
+ {
371
+ type: "button",
372
+ className: `stuff-viewer-tab${isActive ? " stuff-viewer-tab--active" : ""}`,
373
+ onClick: () => setActiveTab(tab),
374
+ children: label
375
+ },
376
+ tab
377
+ );
378
+ }) }),
379
+ /* @__PURE__ */ jsxs("div", { className: "stuff-viewer-actions", children: [
380
+ externalUrl && /* @__PURE__ */ jsx(
381
+ "button",
382
+ {
383
+ type: "button",
384
+ className: "stuff-viewer-action-btn",
385
+ onClick: handleOpenExternal,
386
+ title: "Open in new window",
387
+ "aria-label": "Open in new window",
388
+ children: ICON_EXTERNAL
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsx(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ className: `stuff-viewer-action-btn${copied ? " stuff-viewer-action-btn--copied" : ""}`,
396
+ onClick: handleCopy,
397
+ title: "Copy",
398
+ "aria-label": "Copy",
399
+ children: copied ? ICON_CHECK : ICON_COPY
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsx(
403
+ "button",
404
+ {
405
+ type: "button",
406
+ className: "stuff-viewer-action-btn",
407
+ onClick: handleDownload,
408
+ title: "Download",
409
+ "aria-label": "Download",
410
+ children: ICON_DOWNLOAD
411
+ }
412
+ )
413
+ ] })
414
+ ] }),
415
+ /* @__PURE__ */ jsx("div", { className: "stuff-viewer-content", ref: contentRef, children: renderContent() })
416
+ ] });
417
+ }
418
+
419
+ // src/graph/react/detail/DetailPanel.tsx
420
+ import React2 from "react";
421
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
422
+ function DetailPanel({
423
+ isOpen,
424
+ onClose,
425
+ children,
426
+ width,
427
+ isDragging,
428
+ onResizeHandleMouseDown,
429
+ closeOnEscape = true
430
+ }) {
431
+ React2.useEffect(() => {
432
+ if (!isOpen || !closeOnEscape) return;
433
+ const onKeyDown = (e) => {
434
+ if (e.key === "Escape") onClose();
435
+ };
436
+ document.addEventListener("keydown", onKeyDown);
437
+ return () => document.removeEventListener("keydown", onKeyDown);
438
+ }, [isOpen, closeOnEscape, onClose]);
439
+ const className = [
440
+ "detail-panel",
441
+ !isOpen && "detail-panel--closed",
442
+ isDragging && "detail-panel--dragging"
443
+ ].filter(Boolean).join(" ");
444
+ return /* @__PURE__ */ jsxs2("div", { className, style: width ? { width: `${width}px` } : void 0, children: [
445
+ onResizeHandleMouseDown && /* @__PURE__ */ jsx2(
446
+ "div",
447
+ {
448
+ className: "detail-panel-resize-handle",
449
+ onMouseDown: onResizeHandleMouseDown,
450
+ role: "separator",
451
+ "aria-orientation": "vertical",
452
+ "aria-label": "Resize panel"
453
+ }
454
+ ),
455
+ /* @__PURE__ */ jsx2("button", { className: "detail-panel-close", onClick: onClose, "aria-label": "Close panel", children: "x" }),
456
+ /* @__PURE__ */ jsx2("div", { className: "detail-panel-content", children })
457
+ ] });
458
+ }
459
+
460
+ // src/graph/react/detail/useResizable.ts
461
+ import React3 from "react";
462
+ var MAX_WIDTH_RATIO = 0.6;
463
+ function useResizable({
464
+ defaultWidth,
465
+ minWidth,
466
+ maxWidth,
467
+ containerRef
468
+ }) {
469
+ const [width, setWidth] = React3.useState(defaultWidth);
470
+ const [isDragging, setIsDragging] = React3.useState(false);
471
+ const dragRef = React3.useRef({ startX: 0, startWidth: 0, maxAllowed: maxWidth });
472
+ const rafRef = React3.useRef(null);
473
+ const handleMouseDown = React3.useCallback(
474
+ (e) => {
475
+ var _a;
476
+ e.preventDefault();
477
+ e.stopPropagation();
478
+ const containerWidth = (_a = containerRef == null ? void 0 : containerRef.current) == null ? void 0 : _a.getBoundingClientRect().width;
479
+ const maxAllowed = containerWidth ? Math.min(maxWidth, containerWidth * MAX_WIDTH_RATIO) : maxWidth;
480
+ dragRef.current = { startX: e.clientX, startWidth: width, maxAllowed };
481
+ setIsDragging(true);
482
+ document.body.style.userSelect = "none";
483
+ document.body.style.cursor = "col-resize";
484
+ },
485
+ [width, maxWidth, containerRef]
486
+ );
487
+ React3.useEffect(() => {
488
+ if (!isDragging) return;
489
+ const onMouseMove = (e) => {
490
+ if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
491
+ rafRef.current = requestAnimationFrame(() => {
492
+ const { startX, startWidth, maxAllowed } = dragRef.current;
493
+ const deltaX = startX - e.clientX;
494
+ const newWidth = Math.max(minWidth, Math.min(maxAllowed, startWidth + deltaX));
495
+ setWidth(newWidth);
496
+ rafRef.current = null;
497
+ });
498
+ };
499
+ const onMouseUp = () => {
500
+ setIsDragging(false);
501
+ document.body.style.userSelect = "";
502
+ document.body.style.cursor = "";
503
+ if (rafRef.current !== null) {
504
+ cancelAnimationFrame(rafRef.current);
505
+ rafRef.current = null;
506
+ }
507
+ };
508
+ document.addEventListener("mousemove", onMouseMove);
509
+ document.addEventListener("mouseup", onMouseUp);
510
+ return () => {
511
+ document.removeEventListener("mousemove", onMouseMove);
512
+ document.removeEventListener("mouseup", onMouseUp);
513
+ document.body.style.userSelect = "";
514
+ document.body.style.cursor = "";
515
+ if (rafRef.current !== null) {
516
+ cancelAnimationFrame(rafRef.current);
517
+ rafRef.current = null;
518
+ }
519
+ };
520
+ }, [isDragging, minWidth]);
521
+ return { width, isDragging, handleMouseDown };
522
+ }
523
+
524
+ // src/graph/react/detail/PipeDetailPanel.tsx
525
+ import React5 from "react";
526
+
527
+ // src/graph/react/detail/sections/shared.tsx
528
+ import { useState } from "react";
529
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
530
+ function formatDuration(seconds) {
531
+ if (seconds < 1) return `${(seconds * 1e3).toFixed(0)}ms`;
532
+ return `${seconds.toFixed(2)}s`;
533
+ }
534
+ function KV({
535
+ label,
536
+ value
537
+ }) {
538
+ if (value === null || value === void 0) return null;
539
+ return /* @__PURE__ */ jsxs3("div", { className: "detail-kv-row", children: [
540
+ /* @__PURE__ */ jsx3("span", { className: "detail-kv-key", children: label }),
541
+ /* @__PURE__ */ jsx3("span", { className: "detail-kv-value", children: String(value) })
542
+ ] });
543
+ }
544
+ function PromptBlock({
545
+ label,
546
+ text
547
+ }) {
548
+ if (!text) return null;
549
+ return /* @__PURE__ */ jsxs3("div", { children: [
550
+ /* @__PURE__ */ jsx3("div", { className: "detail-section-label", children: label }),
551
+ /* @__PURE__ */ jsx3("div", { className: "detail-prompt-block", children: text })
552
+ ] });
553
+ }
554
+ var EXPAND_ICON = /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", width: "10", height: "10", fill: "currentColor", children: /* @__PURE__ */ jsx3("path", { d: "M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" }) });
555
+ var COLLAPSE_ICON = /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", width: "10", height: "10", fill: "currentColor", children: /* @__PURE__ */ jsx3("path", { d: "M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" }) });
556
+ var COPY_ICON = /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", width: "10", height: "10", fill: "currentColor", children: /* @__PURE__ */ jsx3("path", { d: "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" }) });
557
+ var CHECK_ICON = /* @__PURE__ */ jsx3("svg", { viewBox: "0 0 24 24", width: "10", height: "10", fill: "currentColor", children: /* @__PURE__ */ jsx3("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) });
558
+ var promptActionStyle = {
559
+ all: "unset",
560
+ cursor: "pointer",
561
+ display: "flex",
562
+ alignItems: "center",
563
+ gap: 4,
564
+ fontSize: 9,
565
+ color: "#64748b",
566
+ padding: "2px 6px",
567
+ borderRadius: 3,
568
+ background: "rgba(255,255,255,0.04)",
569
+ border: "1px solid rgba(255,255,255,0.06)",
570
+ transition: "color 0.15s"
571
+ };
572
+ function PromptToggle({
573
+ label,
574
+ templateText,
575
+ renderedText
576
+ }) {
577
+ const [showTemplate, setShowTemplate] = useState(false);
578
+ const [copied, setCopied] = useState(false);
579
+ const [expanded, setExpanded] = useState(false);
580
+ const hasTemplate = !!templateText;
581
+ const hasRendered = !!renderedText;
582
+ if (!hasTemplate && !hasRendered) return null;
583
+ const canToggle = hasTemplate && hasRendered;
584
+ const activeText = canToggle ? showTemplate ? templateText : renderedText : templateText || renderedText;
585
+ function handleCopy() {
586
+ if (!activeText || !navigator.clipboard) return;
587
+ navigator.clipboard.writeText(activeText).then(() => {
588
+ setCopied(true);
589
+ setTimeout(() => setCopied(false), 1500);
590
+ }).catch(() => {
591
+ });
592
+ }
593
+ return /* @__PURE__ */ jsxs3("div", { children: [
594
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 4 }, children: [
595
+ /* @__PURE__ */ jsx3("div", { className: "detail-section-label", style: { marginBottom: 0 }, children: label }),
596
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
597
+ /* @__PURE__ */ jsx3(
598
+ "button",
599
+ {
600
+ onClick: () => setExpanded((prev) => !prev),
601
+ title: expanded ? "Collapse prompt" : "Expand prompt",
602
+ "aria-label": expanded ? "Collapse prompt" : "Expand prompt",
603
+ className: "detail-prompt-expand-btn",
604
+ children: expanded ? COLLAPSE_ICON : EXPAND_ICON
605
+ }
606
+ ),
607
+ /* @__PURE__ */ jsx3(
608
+ "button",
609
+ {
610
+ onClick: handleCopy,
611
+ title: "Copy prompt",
612
+ "aria-label": "Copy prompt",
613
+ style: __spreadProps(__spreadValues({}, promptActionStyle), {
614
+ color: copied ? "#10b981" : "#64748b"
615
+ }),
616
+ children: copied ? CHECK_ICON : COPY_ICON
617
+ }
618
+ ),
619
+ canToggle && /* @__PURE__ */ jsxs3(
620
+ "button",
621
+ {
622
+ onClick: () => setShowTemplate((prev) => !prev),
623
+ style: promptActionStyle,
624
+ children: [
625
+ /* @__PURE__ */ jsx3(
626
+ "span",
627
+ {
628
+ style: {
629
+ width: 22,
630
+ height: 12,
631
+ borderRadius: 6,
632
+ background: showTemplate ? "rgba(255,255,255,0.12)" : "#50FA7B33",
633
+ position: "relative",
634
+ display: "inline-block",
635
+ transition: "background 0.15s"
636
+ },
637
+ children: /* @__PURE__ */ jsx3(
638
+ "span",
639
+ {
640
+ style: {
641
+ position: "absolute",
642
+ top: 2,
643
+ left: showTemplate ? 2 : 12,
644
+ width: 8,
645
+ height: 8,
646
+ borderRadius: "50%",
647
+ background: showTemplate ? "#94a3b8" : "#50FA7B",
648
+ transition: "left 0.15s, background 0.15s"
649
+ }
650
+ }
651
+ )
652
+ }
653
+ ),
654
+ showTemplate ? "template" : "rendered"
655
+ ]
656
+ }
657
+ )
658
+ ] })
659
+ ] }),
660
+ /* @__PURE__ */ jsx3(
661
+ "div",
662
+ {
663
+ className: `detail-prompt-block ${expanded ? "detail-prompt-block--expanded" : "detail-prompt-block--collapsed"}`,
664
+ style: { marginTop: 6 },
665
+ children: activeText
666
+ }
667
+ )
668
+ ] });
669
+ }
670
+
671
+ // src/graph/react/detail/sections/PipeLLMDetail.tsx
672
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
673
+ function PipeLLMSection({
674
+ blueprint,
675
+ executionData
676
+ }) {
677
+ var _a, _b, _c, _d, _e;
678
+ const spec = blueprint.llm_prompt_spec;
679
+ const hasImageRefs = spec.user_image_references && spec.user_image_references.length > 0;
680
+ const hasDocRefs = spec.user_document_references && spec.user_document_references.length > 0;
681
+ const hasSysImageRefs = spec.system_image_references && spec.system_image_references.length > 0;
682
+ const hasSysDocRefs = spec.system_document_references && spec.system_document_references.length > 0;
683
+ const resolvedModel = executionData == null ? void 0 : executionData.resolved_model;
684
+ const resolvedModelForObject = executionData == null ? void 0 : executionData.resolved_model_for_object;
685
+ const renderedSystem = executionData == null ? void 0 : executionData.rendered_system_prompt;
686
+ const renderedUser = executionData == null ? void 0 : executionData.rendered_user_prompt;
687
+ const structuringPath = executionData == null ? void 0 : executionData.structuring_path;
688
+ const isMultipleOutput = executionData == null ? void 0 : executionData.is_multiple_output;
689
+ const modelDisplay = resolvedModel || ((_a = blueprint.llm_choices) == null ? void 0 : _a.for_text);
690
+ const modelForObjectDisplay = resolvedModelForObject || ((_b = blueprint.llm_choices) == null ? void 0 : _b.for_object);
691
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
692
+ /* @__PURE__ */ jsx4(KV, { label: "Model", value: modelDisplay }),
693
+ modelForObjectDisplay && modelForObjectDisplay !== modelDisplay && /* @__PURE__ */ jsx4(KV, { label: "Model (object)", value: modelForObjectDisplay }),
694
+ /* @__PURE__ */ jsx4(KV, { label: "Structuring", value: structuringPath || blueprint.structuring_method }),
695
+ /* @__PURE__ */ jsx4(KV, { label: "Multiple Output", value: isMultipleOutput }),
696
+ /* @__PURE__ */ jsx4(KV, { label: "Output Multiplicity", value: blueprint.output_multiplicity }),
697
+ /* @__PURE__ */ jsx4(KV, { label: "Prompt Category", value: (_c = spec.prompt_blueprint) == null ? void 0 : _c.category }),
698
+ hasImageRefs && /* @__PURE__ */ jsx4(KV, { label: "User Image Refs", value: `${spec.user_image_references.length} references` }),
699
+ hasDocRefs && /* @__PURE__ */ jsx4(KV, { label: "User Document Refs", value: `${spec.user_document_references.length} references` }),
700
+ hasSysImageRefs && /* @__PURE__ */ jsx4(KV, { label: "System Image Refs", value: `${spec.system_image_references.length} references` }),
701
+ hasSysDocRefs && /* @__PURE__ */ jsx4(KV, { label: "System Document Refs", value: `${spec.system_document_references.length} references` }),
702
+ /* @__PURE__ */ jsx4(
703
+ PromptToggle,
704
+ {
705
+ label: "System Prompt",
706
+ templateText: (_d = spec.system_prompt_blueprint) == null ? void 0 : _d.template,
707
+ renderedText: renderedSystem
708
+ }
709
+ ),
710
+ /* @__PURE__ */ jsx4(
711
+ PromptToggle,
712
+ {
713
+ label: "Prompt",
714
+ templateText: (_e = spec.prompt_blueprint) == null ? void 0 : _e.template,
715
+ renderedText: renderedUser
716
+ }
717
+ )
718
+ ] });
719
+ }
720
+ function LLMExecutionData(_props) {
721
+ return null;
722
+ }
723
+
724
+ // src/graph/react/detail/sections/PipeImgGenDetail.tsx
725
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
726
+ function PipeImgGenSection({
727
+ blueprint,
728
+ executionData
729
+ }) {
730
+ var _a, _b, _c;
731
+ const spec = blueprint.img_gen_prompt_blueprint;
732
+ const resolvedModel = executionData == null ? void 0 : executionData.resolved_model;
733
+ const renderedPrompt = executionData == null ? void 0 : executionData.rendered_prompt;
734
+ const renderedNegative = executionData == null ? void 0 : executionData.rendered_negative_prompt;
735
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
736
+ /* @__PURE__ */ jsx5(KV, { label: "Model", value: resolvedModel || blueprint.img_gen_choice }),
737
+ /* @__PURE__ */ jsx5(
738
+ PromptToggle,
739
+ {
740
+ label: "Prompt",
741
+ templateText: (_a = spec.prompt_blueprint) == null ? void 0 : _a.template,
742
+ renderedText: renderedPrompt
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsx5(
746
+ PromptToggle,
747
+ {
748
+ label: "Negative Prompt",
749
+ templateText: (_b = spec.negative_prompt_blueprint) == null ? void 0 : _b.template,
750
+ renderedText: renderedNegative
751
+ }
752
+ ),
753
+ /* @__PURE__ */ jsx5(KV, { label: "Aspect Ratio", value: blueprint.aspect_ratio }),
754
+ /* @__PURE__ */ jsx5(KV, { label: "Output Format", value: blueprint.output_format }),
755
+ /* @__PURE__ */ jsx5(KV, { label: "Background", value: blueprint.background }),
756
+ /* @__PURE__ */ jsx5(KV, { label: "Is Raw", value: blueprint.is_raw }),
757
+ /* @__PURE__ */ jsx5(KV, { label: "Seed", value: blueprint.seed }),
758
+ /* @__PURE__ */ jsx5(KV, { label: "Images", value: (_c = executionData == null ? void 0 : executionData.nb_images) != null ? _c : blueprint.output_multiplicity })
759
+ ] });
760
+ }
761
+ function ImgGenExecutionData(_props) {
762
+ return null;
763
+ }
764
+
765
+ // src/graph/react/detail/sections/PipeExtractDetail.tsx
766
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
767
+ function PipeExtractSection({
768
+ blueprint,
769
+ executionData
770
+ }) {
771
+ const resolvedModel = executionData == null ? void 0 : executionData.resolved_model;
772
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
773
+ /* @__PURE__ */ jsx6(KV, { label: "Model", value: resolvedModel || blueprint.extract_choice }),
774
+ /* @__PURE__ */ jsx6(KV, { label: "Document Variable", value: blueprint.document_stuff_name }),
775
+ /* @__PURE__ */ jsx6(KV, { label: "Caption Images", value: blueprint.should_caption_images }),
776
+ /* @__PURE__ */ jsx6(KV, { label: "Max Page Images", value: blueprint.max_page_images }),
777
+ /* @__PURE__ */ jsx6(KV, { label: "Include Page Views", value: blueprint.should_include_page_views }),
778
+ /* @__PURE__ */ jsx6(KV, { label: "Page Views DPI", value: blueprint.page_views_dpi }),
779
+ /* @__PURE__ */ jsx6(KV, { label: "Render JS", value: blueprint.render_js }),
780
+ /* @__PURE__ */ jsx6(KV, { label: "Include Raw HTML", value: blueprint.include_raw_html }),
781
+ /* @__PURE__ */ jsx6(KV, { label: "Image Variable", value: blueprint.image_stuff_name })
782
+ ] });
783
+ }
784
+ function ExtractExecutionData(_props) {
785
+ return null;
786
+ }
787
+
788
+ // src/graph/react/detail/sections/PipeSearchDetail.tsx
789
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
790
+ function PipeSearchSection({
791
+ blueprint,
792
+ executionData
793
+ }) {
794
+ const resolvedModel = executionData == null ? void 0 : executionData.resolved_model;
795
+ const renderedQuery = executionData == null ? void 0 : executionData.rendered_query;
796
+ return /* @__PURE__ */ jsxs7(Fragment4, { children: [
797
+ /* @__PURE__ */ jsx7(KV, { label: "Model", value: resolvedModel || blueprint.search_choice }),
798
+ /* @__PURE__ */ jsx7(
799
+ PromptToggle,
800
+ {
801
+ label: "Search Query",
802
+ templateText: blueprint.prompt_blueprint.template,
803
+ renderedText: renderedQuery
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsx7(KV, { label: "Max Results", value: blueprint.max_results_override }),
807
+ /* @__PURE__ */ jsx7(KV, { label: "Include Images", value: blueprint.include_images_override }),
808
+ /* @__PURE__ */ jsx7(KV, { label: "Structured Output", value: blueprint.is_structured_output }),
809
+ /* @__PURE__ */ jsx7(KV, { label: "From Date", value: blueprint.from_date }),
810
+ /* @__PURE__ */ jsx7(KV, { label: "To Date", value: blueprint.to_date }),
811
+ blueprint.include_domains && blueprint.include_domains.length > 0 && /* @__PURE__ */ jsx7(KV, { label: "Include Domains", value: blueprint.include_domains.join(", ") }),
812
+ blueprint.exclude_domains && blueprint.exclude_domains.length > 0 && /* @__PURE__ */ jsx7(KV, { label: "Exclude Domains", value: blueprint.exclude_domains.join(", ") })
813
+ ] });
814
+ }
815
+ function SearchExecutionData(_props) {
816
+ return null;
817
+ }
818
+
819
+ // src/graph/react/detail/sections/PipeComposeDetail.tsx
820
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
821
+ function formatLeafField(field) {
822
+ var _a, _b;
823
+ switch (field.method) {
824
+ case "from_var": {
825
+ const base = `\u2190 ${(_a = field.from_path) != null ? _a : "?"}`;
826
+ return field.list_to_dict_keyed_by ? `${base} (keyed by ${field.list_to_dict_keyed_by})` : base;
827
+ }
828
+ case "fixed":
829
+ return `= ${JSON.stringify(field.fixed_value)}`;
830
+ case "nested":
831
+ return "(nested construct)";
832
+ case "template":
833
+ return (_b = field.template) != null ? _b : "(empty template)";
834
+ }
835
+ }
836
+ var LONG_VALUE_THRESHOLD_CHARS = 60;
837
+ function isLongValue(text) {
838
+ return text.length > LONG_VALUE_THRESHOLD_CHARS || text.includes("\n");
839
+ }
840
+ function FieldBlock({ label, value }) {
841
+ return /* @__PURE__ */ jsxs8("div", { className: "detail-field-block", children: [
842
+ /* @__PURE__ */ jsx8("div", { className: "detail-field-block-label", children: label }),
843
+ /* @__PURE__ */ jsx8("div", { className: "detail-field-block-value", children: value })
844
+ ] });
845
+ }
846
+ function ConstructFieldsBlock({
847
+ blueprint,
848
+ depth,
849
+ resolvedFields
850
+ }) {
851
+ const leafFields = [];
852
+ const nestedFields = [];
853
+ const templateFields = [];
854
+ for (const [name, field] of Object.entries(blueprint.fields)) {
855
+ if (field.method === "template") {
856
+ templateFields.push([name, field]);
857
+ } else if (field.method === "nested") {
858
+ nestedFields.push([name, field]);
859
+ } else {
860
+ leafFields.push([name, field]);
861
+ }
862
+ }
863
+ const indentStyle = depth > 0 ? { paddingLeft: depth * 12 } : void 0;
864
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
865
+ leafFields.map(([name, field]) => {
866
+ const display = formatLeafField(field);
867
+ if (isLongValue(display)) {
868
+ return /* @__PURE__ */ jsx8("div", { style: indentStyle, children: /* @__PURE__ */ jsx8(FieldBlock, { label: name, value: display }) }, name);
869
+ }
870
+ return /* @__PURE__ */ jsx8("div", { style: indentStyle, children: /* @__PURE__ */ jsx8(KV, { label: name, value: display }) }, name);
871
+ }),
872
+ templateFields.map(([name, field]) => {
873
+ var _a;
874
+ const resolvedForField = resolvedFields == null ? void 0 : resolvedFields[name];
875
+ const renderedForField = typeof resolvedForField === "string" ? resolvedForField : void 0;
876
+ return /* @__PURE__ */ jsx8("div", { style: indentStyle, children: /* @__PURE__ */ jsx8(
877
+ PromptToggle,
878
+ {
879
+ label: `Template \u2014 ${name}`,
880
+ templateText: (_a = field.template) != null ? _a : null,
881
+ renderedText: renderedForField
882
+ }
883
+ ) }, name);
884
+ }),
885
+ nestedFields.map(([name, field]) => {
886
+ if (!field.nested) return null;
887
+ return /* @__PURE__ */ jsxs8("div", { children: [
888
+ /* @__PURE__ */ jsxs8(
889
+ "div",
890
+ {
891
+ className: "detail-nested-header",
892
+ style: depth > 0 ? { paddingLeft: depth * 12 } : void 0,
893
+ children: [
894
+ /* @__PURE__ */ jsx8("span", { className: "detail-nested-header-name", children: name }),
895
+ /* @__PURE__ */ jsxs8("span", { className: "detail-nested-header-meta", children: [
896
+ "nested \xB7 ",
897
+ Object.keys(field.nested.fields).length,
898
+ " field",
899
+ Object.keys(field.nested.fields).length === 1 ? "" : "s"
900
+ ] })
901
+ ]
902
+ }
903
+ ),
904
+ /* @__PURE__ */ jsx8(
905
+ ConstructFieldsBlock,
906
+ {
907
+ blueprint: field.nested,
908
+ depth: depth + 1,
909
+ resolvedFields: resolvedFields && typeof resolvedFields[name] === "object" && resolvedFields[name] !== null ? resolvedFields[name] : null
910
+ }
911
+ )
912
+ ] }, name);
913
+ })
914
+ ] });
915
+ }
916
+ function PipeComposeSection({
917
+ blueprint,
918
+ executionData
919
+ }) {
920
+ var _a;
921
+ const renderedText = executionData == null ? void 0 : executionData.rendered_text;
922
+ const composeMode = executionData == null ? void 0 : executionData.compose_mode;
923
+ const resolvedFields = (_a = executionData == null ? void 0 : executionData.resolved_fields) != null ? _a : null;
924
+ const constructBlueprint = blueprint.construct_blueprint;
925
+ const hasConstruct = constructBlueprint !== null && Object.keys(constructBlueprint.fields).length > 0;
926
+ return /* @__PURE__ */ jsxs8(Fragment5, { children: [
927
+ /* @__PURE__ */ jsx8(KV, { label: "Mode", value: composeMode || (hasConstruct ? "construct" : "template") }),
928
+ /* @__PURE__ */ jsx8(KV, { label: "Category", value: blueprint.category }),
929
+ /* @__PURE__ */ jsx8(KV, { label: "Templating Style", value: blueprint.templating_style }),
930
+ blueprint.template && /* @__PURE__ */ jsx8(
931
+ PromptToggle,
932
+ {
933
+ label: "Template",
934
+ templateText: blueprint.template,
935
+ renderedText
936
+ }
937
+ ),
938
+ hasConstruct && /* @__PURE__ */ jsxs8(Fragment5, { children: [
939
+ /* @__PURE__ */ jsx8("div", { className: "detail-section-label", children: "FIELDS" }),
940
+ /* @__PURE__ */ jsx8(
941
+ ConstructFieldsBlock,
942
+ {
943
+ blueprint: constructBlueprint,
944
+ depth: 0,
945
+ resolvedFields
946
+ }
947
+ )
948
+ ] })
949
+ ] });
950
+ }
951
+ function ComposeExecutionData(_props) {
952
+ return null;
953
+ }
954
+
955
+ // src/graph/react/detail/sections/PipeConditionDetail.tsx
956
+ import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
957
+ function PipeConditionSection({
958
+ blueprint,
959
+ executionData
960
+ }) {
961
+ const evaluatedExpression = executionData == null ? void 0 : executionData.evaluated_expression;
962
+ const selectedOutcome = executionData == null ? void 0 : executionData.selected_outcome;
963
+ return /* @__PURE__ */ jsxs9(Fragment6, { children: [
964
+ /* @__PURE__ */ jsx9(PromptBlock, { label: "Expression", text: blueprint.expression }),
965
+ evaluatedExpression && /* @__PURE__ */ jsx9(KV, { label: "Expression Result", value: evaluatedExpression }),
966
+ /* @__PURE__ */ jsxs9("div", { children: [
967
+ /* @__PURE__ */ jsx9("div", { className: "detail-section-label", children: "Outcomes" }),
968
+ Object.entries(blueprint.outcome_map).map(([outcome, pipeCode]) => /* @__PURE__ */ jsxs9(
969
+ "div",
970
+ {
971
+ className: "detail-kv-row",
972
+ style: selectedOutcome === pipeCode ? { background: "rgba(80,250,123,0.08)" } : void 0,
973
+ children: [
974
+ /* @__PURE__ */ jsx9("span", { className: "detail-kv-key", children: outcome }),
975
+ /* @__PURE__ */ jsx9("span", { className: "detail-kv-value", style: selectedOutcome === pipeCode ? { color: "#50FA7B" } : void 0, children: pipeCode })
976
+ ]
977
+ },
978
+ outcome
979
+ )),
980
+ /* @__PURE__ */ jsxs9("div", { className: "detail-kv-row", children: [
981
+ /* @__PURE__ */ jsx9("span", { className: "detail-kv-key", children: "default" }),
982
+ /* @__PURE__ */ jsx9("span", { className: "detail-kv-value", children: blueprint.default_outcome })
983
+ ] })
984
+ ] }),
985
+ /* @__PURE__ */ jsx9(KV, { label: "Alias Expression To", value: blueprint.add_alias_from_expression_to })
986
+ ] });
987
+ }
988
+ function ConditionExecutionData(_props) {
989
+ return null;
990
+ }
991
+
992
+ // src/graph/react/detail/sections/PipeSequenceDetail.tsx
993
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
994
+ function PipeSequenceSection({
995
+ blueprint
996
+ }) {
997
+ return /* @__PURE__ */ jsxs10("div", { children: [
998
+ /* @__PURE__ */ jsx10("div", { className: "detail-section-label", children: "Steps" }),
999
+ /* @__PURE__ */ jsx10("div", { className: "detail-steps-list", children: blueprint.sequential_sub_pipes.map((step, idx) => /* @__PURE__ */ jsxs10("div", { className: "detail-step-item", children: [
1000
+ /* @__PURE__ */ jsx10("span", { className: "detail-step-index", children: idx + 1 }),
1001
+ /* @__PURE__ */ jsx10("span", { className: "detail-step-code", children: step.pipe_code }),
1002
+ step.output_name && /* @__PURE__ */ jsxs10("span", { className: "detail-io-concept", children: [
1003
+ "-> ",
1004
+ step.output_name
1005
+ ] })
1006
+ ] }, idx)) })
1007
+ ] });
1008
+ }
1009
+ function SequenceExecutionData(_props) {
1010
+ return null;
1011
+ }
1012
+
1013
+ // src/graph/react/detail/sections/PipeParallelDetail.tsx
1014
+ import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1015
+ function PipeParallelSection({
1016
+ blueprint
1017
+ }) {
1018
+ return /* @__PURE__ */ jsxs11(Fragment7, { children: [
1019
+ /* @__PURE__ */ jsxs11("div", { children: [
1020
+ /* @__PURE__ */ jsx11("div", { className: "detail-section-label", children: "Branches" }),
1021
+ /* @__PURE__ */ jsx11("div", { className: "detail-steps-list", children: blueprint.parallel_sub_pipes.map((branch, idx) => /* @__PURE__ */ jsxs11("div", { className: "detail-step-item", children: [
1022
+ /* @__PURE__ */ jsx11("span", { className: "detail-step-code", children: branch.pipe_code }),
1023
+ branch.output_name && /* @__PURE__ */ jsxs11("span", { className: "detail-io-concept", children: [
1024
+ "-> ",
1025
+ branch.output_name
1026
+ ] })
1027
+ ] }, idx)) })
1028
+ ] }),
1029
+ /* @__PURE__ */ jsx11(KV, { label: "Add Each Output", value: blueprint.add_each_output }),
1030
+ /* @__PURE__ */ jsx11(KV, { label: "Combined Output", value: blueprint.combined_output })
1031
+ ] });
1032
+ }
1033
+ function ParallelExecutionData(_props) {
1034
+ return null;
1035
+ }
1036
+
1037
+ // src/graph/react/detail/sections/PipeBatchDetail.tsx
1038
+ import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1039
+ function PipeBatchSection({
1040
+ blueprint,
1041
+ executionData
1042
+ }) {
1043
+ const itemCount = executionData == null ? void 0 : executionData.item_count;
1044
+ return /* @__PURE__ */ jsxs12(Fragment8, { children: [
1045
+ /* @__PURE__ */ jsx12(KV, { label: "Branch Pipe", value: blueprint.branch_pipe_code }),
1046
+ /* @__PURE__ */ jsx12(KV, { label: "Input List Variable", value: blueprint.batch_params.input_list_stuff_name }),
1047
+ /* @__PURE__ */ jsx12(KV, { label: "Input Item Variable", value: blueprint.batch_params.input_item_stuff_name }),
1048
+ itemCount != null && /* @__PURE__ */ jsx12(KV, { label: "Items Processed", value: itemCount })
1049
+ ] });
1050
+ }
1051
+ function BatchExecutionData(_props) {
1052
+ return null;
1053
+ }
1054
+
1055
+ // src/graph/react/detail/PipeDetailPanel.tsx
1056
+ import { Fragment as Fragment9, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1057
+ var PIPE_TYPE_BADGES = {
1058
+ PipeLLM: "LLM",
1059
+ PipeExtract: "Extract",
1060
+ PipeCompose: "Compose",
1061
+ PipeImgGen: "ImgGen",
1062
+ PipeSearch: "Search",
1063
+ PipeFunc: "Func",
1064
+ PipeSequence: "Seq",
1065
+ PipeParallel: "Par",
1066
+ PipeCondition: "Cond",
1067
+ PipeBatch: "Batch"
1068
+ };
1069
+ var CONTROLLER_TYPES = /* @__PURE__ */ new Set([
1070
+ "PipeSequence",
1071
+ "PipeParallel",
1072
+ "PipeCondition",
1073
+ "PipeBatch"
1074
+ ]);
1075
+ var STATUS_COLORS = {
1076
+ succeeded: "#50FA7B",
1077
+ failed: "#FF5555",
1078
+ running: "#8BE9FD",
1079
+ scheduled: "#6272a4",
1080
+ skipped: "#6272a4"
1081
+ };
1082
+ function PipeDetailPanel({ node, spec, onConceptClick }) {
1083
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1084
+ const pipeType = (_a = node.pipe_type) != null ? _a : "PipeFunc";
1085
+ const isController = CONTROLLER_TYPES.has(pipeType);
1086
+ const badge = (_b = PIPE_TYPE_BADGES[pipeType]) != null ? _b : pipeType;
1087
+ const status = (_c = node.status) != null ? _c : "scheduled";
1088
+ const statusColor = (_d = STATUS_COLORS[status]) != null ? _d : "#6272a4";
1089
+ const blueprint = React5.useMemo(() => {
1090
+ var _a2, _b2;
1091
+ if (!node.pipe_code || !spec.pipe_registry) return void 0;
1092
+ const directKey = `${(_b2 = (_a2 = spec.pipeline_ref) == null ? void 0 : _a2.domain) != null ? _b2 : ""}.${node.pipe_code}`;
1093
+ const direct = getPipeBlueprint(spec, directKey);
1094
+ if (direct) return direct;
1095
+ for (const [ref, pipe] of Object.entries(spec.pipe_registry)) {
1096
+ if (ref.endsWith(`.${node.pipe_code}`)) return pipe;
1097
+ }
1098
+ return void 0;
1099
+ }, [node.pipe_code, spec]);
1100
+ const inputs = (_f = (_e = node.io) == null ? void 0 : _e.inputs) != null ? _f : [];
1101
+ const outputs = (_h = (_g = node.io) == null ? void 0 : _g.outputs) != null ? _h : [];
1102
+ const description = (_i = blueprint == null ? void 0 : blueprint.description) != null ? _i : node.description;
1103
+ return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1104
+ /* @__PURE__ */ jsxs13("div", { className: "detail-sticky-header", children: [
1105
+ /* @__PURE__ */ jsxs13("div", { className: "detail-header", children: [
1106
+ /* @__PURE__ */ jsx13("span", { className: `detail-badge ${isController ? "detail-badge--controller" : "detail-badge--operator"}`, children: badge }),
1107
+ /* @__PURE__ */ jsx13("span", { className: `detail-pipe-code ${isController ? "detail-pipe-code--controller" : ""}`, children: (_j = node.pipe_code) != null ? _j : "unknown" })
1108
+ ] }),
1109
+ /* @__PURE__ */ jsxs13("div", { className: "detail-status", children: [
1110
+ /* @__PURE__ */ jsx13("span", { className: "detail-status-dot", style: { background: statusColor } }),
1111
+ /* @__PURE__ */ jsx13("span", { className: "detail-status-label", style: { color: statusColor }, children: status }),
1112
+ ((_k = node.timing) == null ? void 0 : _k.duration) != null && /* @__PURE__ */ jsx13("span", { className: "detail-duration", children: formatDuration(node.timing.duration) })
1113
+ ] }),
1114
+ description && /* @__PURE__ */ jsx13("div", { className: "detail-description", children: description }),
1115
+ inputs.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1116
+ /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Inputs" }),
1117
+ /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: inputs.map((input, idx) => {
1118
+ var _a2;
1119
+ return /* @__PURE__ */ jsxs13(
1120
+ "div",
1121
+ {
1122
+ className: "detail-io-pill",
1123
+ style: { cursor: input.concept && onConceptClick ? "pointer" : void 0 },
1124
+ onClick: () => input.concept && (onConceptClick == null ? void 0 : onConceptClick(input.concept)),
1125
+ children: [
1126
+ /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: (_a2 = input.name) != null ? _a2 : "unnamed" }),
1127
+ input.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: input.concept })
1128
+ ]
1129
+ },
1130
+ idx
1131
+ );
1132
+ }) })
1133
+ ] }),
1134
+ outputs.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1135
+ /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Output" }),
1136
+ /* @__PURE__ */ jsx13("div", { className: "detail-io-list", children: outputs.map((output, idx) => {
1137
+ var _a2;
1138
+ return /* @__PURE__ */ jsxs13(
1139
+ "div",
1140
+ {
1141
+ className: "detail-io-pill",
1142
+ style: { cursor: output.concept && onConceptClick ? "pointer" : void 0 },
1143
+ onClick: () => output.concept && (onConceptClick == null ? void 0 : onConceptClick(output.concept)),
1144
+ children: [
1145
+ /* @__PURE__ */ jsx13("span", { className: "detail-io-name", children: (_a2 = output.name) != null ? _a2 : "unnamed" }),
1146
+ output.concept && /* @__PURE__ */ jsx13("span", { className: "detail-io-concept", children: output.concept })
1147
+ ]
1148
+ },
1149
+ idx
1150
+ );
1151
+ }) })
1152
+ ] }),
1153
+ (inputs.length > 0 || outputs.length > 0) && /* @__PURE__ */ jsx13("div", { style: { borderTop: "1px solid rgba(255,255,255,0.06)", margin: "4px 0" } })
1154
+ ] }),
1155
+ blueprint && /* @__PURE__ */ jsx13(BlueprintSection, { blueprint, executionData: node.execution_data }),
1156
+ node.execution_data && Object.keys(node.execution_data).length > 0 && /* @__PURE__ */ jsx13(ExecutionDataSection, { executionData: node.execution_data, pipeType }),
1157
+ node.error && /* @__PURE__ */ jsxs13("div", { className: "detail-error", children: [
1158
+ /* @__PURE__ */ jsx13("div", { className: "detail-error-type", children: node.error.error_type }),
1159
+ /* @__PURE__ */ jsx13("div", { className: "detail-error-message", children: node.error.message }),
1160
+ node.error.stack && /* @__PURE__ */ jsx13("div", { className: "detail-error-stack", children: node.error.stack })
1161
+ ] }),
1162
+ node.metrics && Object.keys(node.metrics).length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1163
+ /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Metrics" }),
1164
+ Object.entries(node.metrics).map(([key, value]) => /* @__PURE__ */ jsx13(KV, { label: key, value: typeof value === "number" ? value.toLocaleString() : String(value) }, key))
1165
+ ] }),
1166
+ node.tags && Object.keys(node.tags).length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
1167
+ /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Tags" }),
1168
+ /* @__PURE__ */ jsx13("div", { className: "detail-tags", children: Object.entries(node.tags).map(([key, value]) => /* @__PURE__ */ jsxs13("span", { className: "detail-tag", children: [
1169
+ /* @__PURE__ */ jsxs13("span", { className: "detail-tag-key", children: [
1170
+ key,
1171
+ ": "
1172
+ ] }),
1173
+ /* @__PURE__ */ jsx13("span", { className: "detail-tag-value", children: value })
1174
+ ] }, key)) })
1175
+ ] }),
1176
+ !blueprint && /* @__PURE__ */ jsx13("div", { className: "detail-not-available", children: "Blueprint not available" })
1177
+ ] });
1178
+ }
1179
+ function BlueprintSection({
1180
+ blueprint,
1181
+ executionData
1182
+ }) {
1183
+ switch (blueprint.type) {
1184
+ case "PipeLLM":
1185
+ return /* @__PURE__ */ jsx13(PipeLLMSection, { blueprint, executionData });
1186
+ case "PipeImgGen":
1187
+ return /* @__PURE__ */ jsx13(PipeImgGenSection, { blueprint, executionData });
1188
+ case "PipeCompose":
1189
+ return /* @__PURE__ */ jsx13(PipeComposeSection, { blueprint, executionData });
1190
+ case "PipeExtract":
1191
+ return /* @__PURE__ */ jsx13(PipeExtractSection, { blueprint, executionData });
1192
+ case "PipeSearch":
1193
+ return /* @__PURE__ */ jsx13(PipeSearchSection, { blueprint, executionData });
1194
+ case "PipeSequence":
1195
+ return /* @__PURE__ */ jsx13(PipeSequenceSection, { blueprint, executionData });
1196
+ case "PipeParallel":
1197
+ return /* @__PURE__ */ jsx13(PipeParallelSection, { blueprint, executionData });
1198
+ case "PipeCondition":
1199
+ return /* @__PURE__ */ jsx13(PipeConditionSection, { blueprint, executionData });
1200
+ case "PipeBatch":
1201
+ return /* @__PURE__ */ jsx13(PipeBatchSection, { blueprint, executionData });
1202
+ case "PipeFunc":
1203
+ return null;
1204
+ }
1205
+ }
1206
+ function ExecutionDataSection({
1207
+ executionData,
1208
+ pipeType
1209
+ }) {
1210
+ switch (pipeType) {
1211
+ case "PipeLLM":
1212
+ return /* @__PURE__ */ jsx13(LLMExecutionData, { data: executionData });
1213
+ case "PipeImgGen":
1214
+ return /* @__PURE__ */ jsx13(ImgGenExecutionData, { data: executionData });
1215
+ case "PipeExtract":
1216
+ return /* @__PURE__ */ jsx13(ExtractExecutionData, { data: executionData });
1217
+ case "PipeSearch":
1218
+ return /* @__PURE__ */ jsx13(SearchExecutionData, { data: executionData });
1219
+ case "PipeCompose":
1220
+ return /* @__PURE__ */ jsx13(ComposeExecutionData, { data: executionData });
1221
+ case "PipeCondition":
1222
+ return /* @__PURE__ */ jsx13(ConditionExecutionData, { data: executionData });
1223
+ case "PipeSequence":
1224
+ return /* @__PURE__ */ jsx13(SequenceExecutionData, { data: executionData });
1225
+ case "PipeParallel":
1226
+ return /* @__PURE__ */ jsx13(ParallelExecutionData, { data: executionData });
1227
+ case "PipeBatch":
1228
+ return /* @__PURE__ */ jsx13(BatchExecutionData, { data: executionData });
1229
+ default:
1230
+ return /* @__PURE__ */ jsx13(GenericExecutionData, { data: executionData });
1231
+ }
1232
+ }
1233
+ function GenericExecutionData({ data }) {
1234
+ const entries = Object.entries(data);
1235
+ if (entries.length === 0) return null;
1236
+ return /* @__PURE__ */ jsxs13(Fragment9, { children: [
1237
+ /* @__PURE__ */ jsx13("div", { className: "detail-section-label", children: "Execution" }),
1238
+ entries.map(([key, value]) => /* @__PURE__ */ jsx13(KV, { label: key, value: String(value) }, key))
1239
+ ] });
1240
+ }
1241
+
1242
+ // src/graph/react/detail/ConceptDetailPanel.tsx
1243
+ import { Fragment as Fragment10, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1244
+ function ConceptDetailPanel({ concept, ioData, isDryRun, resolveStorageUrl }) {
1245
+ return /* @__PURE__ */ jsxs14(Fragment10, { children: [
1246
+ /* @__PURE__ */ jsxs14("div", { className: "detail-header", children: [
1247
+ /* @__PURE__ */ jsx14("span", { className: "detail-concept-code", children: concept.code }),
1248
+ /* @__PURE__ */ jsx14("span", { className: "detail-concept-domain", children: concept.domain_code })
1249
+ ] }),
1250
+ concept.description && /* @__PURE__ */ jsx14("div", { className: "detail-description", children: concept.description }),
1251
+ concept.refines && /* @__PURE__ */ jsxs14("div", { className: "detail-refines", children: [
1252
+ "refines ",
1253
+ /* @__PURE__ */ jsx14("span", { className: "detail-refines-code", children: concept.refines })
1254
+ ] }),
1255
+ concept.json_schema ? /* @__PURE__ */ jsxs14("div", { children: [
1256
+ /* @__PURE__ */ jsx14("div", { className: "detail-section-label", children: "Structure" }),
1257
+ /* @__PURE__ */ jsx14(SchemaTable, { schema: concept.json_schema, isDryRun })
1258
+ ] }) : /* @__PURE__ */ jsx14("div", { className: "detail-not-available", children: "Schema not available" }),
1259
+ ioData && !isDryRun && /* @__PURE__ */ jsxs14("div", { children: [
1260
+ /* @__PURE__ */ jsx14("div", { className: "detail-section-label", children: "Data" }),
1261
+ /* @__PURE__ */ jsx14(StuffViewer, { stuff: toStuffViewerData(ioData), resolveStorageUrl })
1262
+ ] })
1263
+ ] });
1264
+ }
1265
+ function SchemaTable({
1266
+ schema,
1267
+ isDryRun
1268
+ }) {
1269
+ var _a;
1270
+ const properties = schema.properties;
1271
+ const required = new Set((_a = schema.required) != null ? _a : []);
1272
+ if (!properties || Object.keys(properties).length === 0) {
1273
+ return /* @__PURE__ */ jsx14("div", { className: "detail-not-available", children: "No fields defined" });
1274
+ }
1275
+ const fields = Object.entries(properties);
1276
+ const visibleFields = isDryRun ? fields.filter(([name]) => required.has(name)) : fields;
1277
+ return /* @__PURE__ */ jsxs14("table", { className: "detail-schema-table", children: [
1278
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsxs14("tr", { children: [
1279
+ /* @__PURE__ */ jsx14("th", { children: "Field" }),
1280
+ /* @__PURE__ */ jsx14("th", { children: "Type" }),
1281
+ /* @__PURE__ */ jsx14("th", {}),
1282
+ /* @__PURE__ */ jsx14("th", { children: "Description" })
1283
+ ] }) }),
1284
+ /* @__PURE__ */ jsx14("tbody", { children: visibleFields.map(([fieldName, fieldSchema]) => {
1285
+ var _a2;
1286
+ return /* @__PURE__ */ jsxs14("tr", { children: [
1287
+ /* @__PURE__ */ jsx14("td", { className: "detail-schema-field", children: fieldName }),
1288
+ /* @__PURE__ */ jsx14("td", { className: "detail-schema-type", children: extractType(fieldSchema) }),
1289
+ /* @__PURE__ */ jsx14("td", { children: required.has(fieldName) && /* @__PURE__ */ jsx14("span", { className: "detail-schema-required", children: "req" }) }),
1290
+ /* @__PURE__ */ jsx14("td", { children: (_a2 = fieldSchema.description) != null ? _a2 : "" })
1291
+ ] }, fieldName);
1292
+ }) })
1293
+ ] });
1294
+ }
1295
+ function extractType(schema) {
1296
+ var _a;
1297
+ if (schema.type) return String(schema.type);
1298
+ if (schema.anyOf) return "union";
1299
+ if (schema.allOf) return "all";
1300
+ if (schema.$ref) {
1301
+ const ref = String(schema.$ref);
1302
+ return (_a = ref.split("/").pop()) != null ? _a : "ref";
1303
+ }
1304
+ return "unknown";
1305
+ }
1306
+ function toStuffViewerData(ioData) {
1307
+ var _a;
1308
+ if ("digest" in ioData) return ioData;
1309
+ return {
1310
+ digest: (_a = ioData.digest) != null ? _a : "",
1311
+ name: ioData.name,
1312
+ concept: ioData.concept,
1313
+ contentType: ioData.content_type,
1314
+ data: ioData.data,
1315
+ dataText: ioData.data_text,
1316
+ dataHtml: ioData.data_html
1317
+ };
1318
+ }
1319
+
1320
+ // src/graph/react/rfTypes.ts
1321
+ function toAppNodes(nodes) {
1322
+ return nodes.map((n) => ({
1323
+ id: n.id,
1324
+ type: n.type,
1325
+ data: n.data,
1326
+ position: n.position,
1327
+ style: n.style,
1328
+ sourcePosition: n.sourcePosition,
1329
+ targetPosition: n.targetPosition,
1330
+ parentId: n.parentId,
1331
+ extent: n.extent,
1332
+ selected: n.selected
1333
+ }));
1334
+ }
1335
+ function toAppEdges(edges) {
1336
+ return edges.map((e) => ({
1337
+ id: e.id,
1338
+ source: e.source,
1339
+ target: e.target,
1340
+ type: e.type,
1341
+ animated: e.animated,
1342
+ label: e.label,
1343
+ labelStyle: e.labelStyle,
1344
+ labelBgStyle: e.labelBgStyle,
1345
+ labelBgPadding: e.labelBgPadding,
1346
+ labelBgBorderRadius: e.labelBgBorderRadius,
1347
+ style: e.style,
1348
+ markerEnd: e.markerEnd
1349
+ }));
1350
+ }
1351
+
1352
+ // src/graph/react/viewer/renderLabel.tsx
1353
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1354
+ function renderLabel(desc) {
1355
+ if (desc.kind === "pipe") {
1356
+ return /* @__PURE__ */ jsx15(
1357
+ "div",
1358
+ {
1359
+ style: {
1360
+ padding: "10px 14px",
1361
+ display: "flex",
1362
+ flexDirection: "column",
1363
+ gap: "2px",
1364
+ textAlign: "center",
1365
+ width: "100%",
1366
+ boxSizing: "border-box",
1367
+ minWidth: 0
1368
+ },
1369
+ title: desc.label,
1370
+ children: /* @__PURE__ */ jsx15(
1371
+ "span",
1372
+ {
1373
+ style: {
1374
+ fontFamily: "var(--font-mono)",
1375
+ fontSize: "13px",
1376
+ fontWeight: 600,
1377
+ color: "var(--color-pipe-text)",
1378
+ maxWidth: "100%",
1379
+ overflow: "hidden",
1380
+ textOverflow: "ellipsis",
1381
+ whiteSpace: "nowrap"
1382
+ },
1383
+ children: desc.label
1384
+ }
1385
+ )
1386
+ }
1387
+ );
1388
+ }
1389
+ return /* @__PURE__ */ jsxs15(
1390
+ "div",
1391
+ {
1392
+ style: {
1393
+ padding: "8px 24px",
1394
+ display: "flex",
1395
+ flexDirection: "column",
1396
+ alignItems: "center",
1397
+ gap: "2px",
1398
+ textAlign: "center",
1399
+ width: "100%",
1400
+ boxSizing: "border-box",
1401
+ minWidth: 0
1402
+ },
1403
+ title: desc.concept ? `${desc.label}: ${desc.concept}` : desc.label,
1404
+ children: [
1405
+ /* @__PURE__ */ jsx15(
1406
+ "span",
1407
+ {
1408
+ style: {
1409
+ fontFamily: "var(--font-mono)",
1410
+ fontSize: "12px",
1411
+ fontWeight: 600,
1412
+ color: "var(--color-stuff-text)",
1413
+ maxWidth: "100%",
1414
+ overflow: "hidden",
1415
+ textOverflow: "ellipsis",
1416
+ whiteSpace: "nowrap"
1417
+ },
1418
+ children: desc.label
1419
+ }
1420
+ ),
1421
+ desc.concept && /* @__PURE__ */ jsx15(
1422
+ "span",
1423
+ {
1424
+ style: {
1425
+ fontSize: "14px",
1426
+ color: "var(--color-stuff-text-dim)",
1427
+ maxWidth: "100%",
1428
+ overflow: "hidden",
1429
+ textOverflow: "ellipsis",
1430
+ whiteSpace: "nowrap"
1431
+ },
1432
+ children: desc.concept
1433
+ }
1434
+ )
1435
+ ]
1436
+ }
1437
+ );
1438
+ }
1439
+ function hydrateLabels(nodes) {
1440
+ return nodes.map((n) => {
1441
+ if (!n.data.labelDescriptor) return n;
1442
+ return __spreadProps(__spreadValues({}, n), {
1443
+ data: __spreadProps(__spreadValues({}, n.data), {
1444
+ label: renderLabel(n.data.labelDescriptor)
1445
+ })
1446
+ });
1447
+ });
1448
+ }
1449
+
1450
+ // src/graph/react/viewer/GraphToolbar.tsx
1451
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
1452
+ var ARROW_RIGHT_ICON = /* @__PURE__ */ jsxs16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1453
+ /* @__PURE__ */ jsx16("line", { x1: "5", y1: "12", x2: "19", y2: "12" }),
1454
+ /* @__PURE__ */ jsx16("polyline", { points: "12 5 19 12 12 19" })
1455
+ ] });
1456
+ var ARROW_DOWN_ICON = /* @__PURE__ */ jsxs16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1457
+ /* @__PURE__ */ jsx16("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1458
+ /* @__PURE__ */ jsx16("polyline", { points: "19 12 12 19 5 12" })
1459
+ ] });
1460
+ var MINUS_ICON = /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx16("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) });
1461
+ var PLUS_ICON = /* @__PURE__ */ jsxs16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1462
+ /* @__PURE__ */ jsx16("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
1463
+ /* @__PURE__ */ jsx16("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
1464
+ ] });
1465
+ var FIT_VIEW_ICON = /* @__PURE__ */ jsxs16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1466
+ /* @__PURE__ */ jsx16("polyline", { points: "15 3 21 3 21 9" }),
1467
+ /* @__PURE__ */ jsx16("polyline", { points: "9 21 3 21 3 15" }),
1468
+ /* @__PURE__ */ jsx16("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
1469
+ /* @__PURE__ */ jsx16("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
1470
+ ] });
1471
+ var BOXES_ICON = /* @__PURE__ */ jsxs16("svg", { viewBox: "0 0 24 24", width: "14", height: "14", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1472
+ /* @__PURE__ */ jsx16("path", { d: "M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" }),
1473
+ /* @__PURE__ */ jsx16("path", { d: "m7 16.5-4.74-2.85" }),
1474
+ /* @__PURE__ */ jsx16("path", { d: "m7 16.5 5-3" }),
1475
+ /* @__PURE__ */ jsx16("path", { d: "M7 16.5v5.17" }),
1476
+ /* @__PURE__ */ jsx16("path", { d: "M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" }),
1477
+ /* @__PURE__ */ jsx16("path", { d: "m17 16.5-5-3" }),
1478
+ /* @__PURE__ */ jsx16("path", { d: "m17 16.5 4.74-2.85" }),
1479
+ /* @__PURE__ */ jsx16("path", { d: "M17 16.5v5.17" }),
1480
+ /* @__PURE__ */ jsx16("path", { d: "M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0l-3 1.8Z" }),
1481
+ /* @__PURE__ */ jsx16("path", { d: "M12 8 7.26 5.15" }),
1482
+ /* @__PURE__ */ jsx16("path", { d: "m12 8 4.74-2.85" }),
1483
+ /* @__PURE__ */ jsx16("path", { d: "M12 13.5V8" })
1484
+ ] });
1485
+ function GraphToolbar({
1486
+ direction,
1487
+ onDirectionChange,
1488
+ showControllers,
1489
+ onShowControllersChange,
1490
+ onZoomIn,
1491
+ onZoomOut,
1492
+ onFitView,
1493
+ rightOffset = 0
1494
+ }) {
1495
+ const isVertical = direction === GRAPH_DIRECTION.TB || direction === GRAPH_DIRECTION.BT;
1496
+ const directionLabel = isVertical ? "Switch to horizontal layout" : "Switch to vertical layout";
1497
+ const controllersLabel = showControllers ? "Hide pipe controllers" : "Show pipe controllers \u2014 groups pipes by their controlling pipe";
1498
+ return /* @__PURE__ */ jsxs16(
1499
+ "div",
1500
+ {
1501
+ className: "graph-toolbar",
1502
+ style: { right: `${rightOffset + 8}px` },
1503
+ children: [
1504
+ /* @__PURE__ */ jsx16(
1505
+ "button",
1506
+ {
1507
+ type: "button",
1508
+ className: "graph-toolbar-btn",
1509
+ onClick: () => onDirectionChange(isVertical ? GRAPH_DIRECTION.LR : GRAPH_DIRECTION.TB),
1510
+ title: directionLabel,
1511
+ "aria-label": directionLabel,
1512
+ children: isVertical ? ARROW_RIGHT_ICON : ARROW_DOWN_ICON
1513
+ }
1514
+ ),
1515
+ /* @__PURE__ */ jsx16(
1516
+ "button",
1517
+ {
1518
+ type: "button",
1519
+ className: `graph-toolbar-btn${showControllers ? " graph-toolbar-btn--active" : ""}`,
1520
+ onClick: () => onShowControllersChange(!showControllers),
1521
+ title: controllersLabel,
1522
+ "aria-label": controllersLabel,
1523
+ children: BOXES_ICON
1524
+ }
1525
+ ),
1526
+ (onZoomOut || onZoomIn || onFitView) && /* @__PURE__ */ jsx16("div", { className: "graph-toolbar-separator" }),
1527
+ onZoomOut && /* @__PURE__ */ jsx16(
1528
+ "button",
1529
+ {
1530
+ type: "button",
1531
+ className: "graph-toolbar-btn",
1532
+ onClick: onZoomOut,
1533
+ title: "Zoom out",
1534
+ "aria-label": "Zoom out",
1535
+ children: MINUS_ICON
1536
+ }
1537
+ ),
1538
+ onZoomIn && /* @__PURE__ */ jsx16(
1539
+ "button",
1540
+ {
1541
+ type: "button",
1542
+ className: "graph-toolbar-btn",
1543
+ onClick: onZoomIn,
1544
+ title: "Zoom in",
1545
+ "aria-label": "Zoom in",
1546
+ children: PLUS_ICON
1547
+ }
1548
+ ),
1549
+ onFitView && /* @__PURE__ */ jsx16(
1550
+ "button",
1551
+ {
1552
+ type: "button",
1553
+ className: "graph-toolbar-btn",
1554
+ onClick: onFitView,
1555
+ title: "Fit view",
1556
+ "aria-label": "Fit view",
1557
+ children: FIT_VIEW_ICON
1558
+ }
1559
+ )
1560
+ ]
1561
+ }
1562
+ );
1563
+ }
1564
+
1565
+ // src/graph/react/nodes/controller/ControllerGroupNode.tsx
1566
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
1567
+ var CONTROLLER_CONFIG = {
1568
+ PipeSequence: { badge: "Sequence", icon: "\u2192" },
1569
+ PipeParallel: { badge: "Parallel", icon: "//" },
1570
+ PipeCondition: { badge: "Condition", icon: "\u25C7" },
1571
+ PipeBatch: { badge: "Batch", icon: "\u2261" }
1572
+ };
1573
+ function getControllerConfig(pipeType) {
1574
+ if (!pipeType) return { badge: "", icon: "" };
1575
+ return CONTROLLER_CONFIG[pipeType];
1576
+ }
1577
+ function getControllerModifier(pipeType) {
1578
+ if (!pipeType) return "";
1579
+ const key = pipeType.replace("Pipe", "").toLowerCase();
1580
+ return `controller-group--${key}`;
1581
+ }
1582
+ function ControllerGroupNode({ data }) {
1583
+ var _a, _b;
1584
+ const config = getControllerConfig(data.pipeType);
1585
+ const modifier = getControllerModifier(data.pipeType);
1586
+ const isCollapsible = (data.pipeType === "PipeParallel" || data.pipeType === "PipeBatch") && ((_a = data.childCount) != null ? _a : 0) > MAX_VISIBLE_CONTROLLER_CHILDREN;
1587
+ const hiddenCount = isCollapsible ? ((_b = data.childCount) != null ? _b : 0) - MAX_VISIBLE_CONTROLLER_CHILDREN : 0;
1588
+ return /* @__PURE__ */ jsxs17("div", { className: `controller-group-node ${modifier}`, children: [
1589
+ /* @__PURE__ */ jsxs17("div", { className: "controller-group-header", children: [
1590
+ /* @__PURE__ */ jsx17("span", { className: "controller-group-icon", children: config.icon }),
1591
+ /* @__PURE__ */ jsx17("span", { className: "controller-group-badge", children: config.badge }),
1592
+ data.label && /* @__PURE__ */ jsx17("span", { className: "controller-group-label", children: data.label })
1593
+ ] }),
1594
+ isCollapsible && /* @__PURE__ */ jsx17(
1595
+ "button",
1596
+ {
1597
+ className: "controller-group-collapse",
1598
+ onClick: (e) => {
1599
+ var _a2;
1600
+ e.stopPropagation();
1601
+ (_a2 = data.onToggleCollapse) == null ? void 0 : _a2.call(data);
1602
+ },
1603
+ children: data.isCollapsed ? `+${hiddenCount} hidden` : "collapse"
1604
+ }
1605
+ )
1606
+ ] });
1607
+ }
1608
+ var controllerNodeTypes = { controllerGroup: ControllerGroupNode };
1609
+
1610
+ // src/graph/react/nodes/pipe/PipeCardNode.tsx
1611
+ import { Handle, Position } from "@xyflow/react";
1612
+
1613
+ // src/graph/react/nodes/pipe/PipeCardBase.tsx
1614
+ import { useState as useState2 } from "react";
1615
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
1616
+ var PIPE_TYPE_BADGES2 = {
1617
+ PipeLLM: "LLM",
1618
+ PipeExtract: "Extract",
1619
+ PipeCompose: "Compose",
1620
+ PipeImgGen: "ImgGen",
1621
+ PipeSearch: "Search",
1622
+ PipeFunc: "Func"
1623
+ };
1624
+ var STATUS_CONFIG = {
1625
+ succeeded: { color: "#50FA7B", label: "Succeeded" },
1626
+ failed: { color: "#FF5555", label: "Failed" },
1627
+ running: { color: "#8BE9FD", label: "Running" },
1628
+ scheduled: { color: "#6272a4", label: "Scheduled" },
1629
+ skipped: { color: "#6272a4", label: "Skipped" }
1630
+ };
1631
+ var MAX_VISIBLE_INPUTS = 4;
1632
+ function getBadge(pipeType) {
1633
+ return PIPE_TYPE_BADGES2[pipeType];
1634
+ }
1635
+ function PipeCardBase({ data, children }) {
1636
+ var _a;
1637
+ const badge = getBadge(data.pipeType);
1638
+ const statusConfig = (_a = STATUS_CONFIG[data.status]) != null ? _a : STATUS_CONFIG.scheduled;
1639
+ const isRunning = data.status === "running";
1640
+ const [inputsExpanded, setInputsExpanded] = useState2(false);
1641
+ const hasMany = data.inputs.length > MAX_VISIBLE_INPUTS;
1642
+ const visibleInputs = hasMany && !inputsExpanded ? data.inputs.slice(0, MAX_VISIBLE_INPUTS) : data.inputs;
1643
+ const hiddenCount = data.inputs.length - MAX_VISIBLE_INPUTS;
1644
+ const dirClass = data.direction === "TB" ? "pipe-card--tb" : "pipe-card--lr";
1645
+ return /* @__PURE__ */ jsxs18("div", { className: `pipe-card ${dirClass}`, children: [
1646
+ /* @__PURE__ */ jsxs18("div", { className: "pipe-card-header", children: [
1647
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-badge", children: badge }),
1648
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-code", title: data.pipeCode, children: data.pipeCode }),
1649
+ /* @__PURE__ */ jsx18(
1650
+ "span",
1651
+ {
1652
+ className: "pipe-card-status",
1653
+ style: { color: statusConfig.color },
1654
+ title: statusConfig.label,
1655
+ children: /* @__PURE__ */ jsx18(
1656
+ "span",
1657
+ {
1658
+ className: `pipe-card-status-dot ${isRunning ? "pipe-card-status-dot--pulse" : ""}`,
1659
+ style: { background: statusConfig.color }
1660
+ }
1661
+ )
1662
+ }
1663
+ )
1664
+ ] }),
1665
+ data.description && /* @__PURE__ */ jsx18("span", { className: "pipe-card-description", title: data.description, children: data.description }),
1666
+ data.inputs.length > 0 && /* @__PURE__ */ jsxs18("div", { className: "pipe-card-io", children: [
1667
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-label", children: "INPUTS" }),
1668
+ /* @__PURE__ */ jsxs18("div", { className: "pipe-card-io-pills", children: [
1669
+ visibleInputs.map((input) => /* @__PURE__ */ jsxs18(
1670
+ "span",
1671
+ {
1672
+ className: "pipe-card-io-pill",
1673
+ title: `${input.name}: ${input.concept}`,
1674
+ children: [
1675
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-pill-name", children: input.name }),
1676
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-pill-concept", children: input.concept })
1677
+ ]
1678
+ },
1679
+ input.name
1680
+ )),
1681
+ hasMany && /* @__PURE__ */ jsx18(
1682
+ "button",
1683
+ {
1684
+ className: "pipe-card-io-more",
1685
+ onClick: (e) => {
1686
+ e.stopPropagation();
1687
+ setInputsExpanded((v) => !v);
1688
+ },
1689
+ children: inputsExpanded ? "show less" : `+${hiddenCount} more`
1690
+ }
1691
+ )
1692
+ ] })
1693
+ ] }),
1694
+ data.outputs.length > 0 && /* @__PURE__ */ jsxs18("div", { className: "pipe-card-io", children: [
1695
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-label", children: "OUTPUT" }),
1696
+ /* @__PURE__ */ jsx18("div", { className: "pipe-card-io-pills", children: data.outputs.map((output) => /* @__PURE__ */ jsxs18(
1697
+ "span",
1698
+ {
1699
+ className: "pipe-card-io-pill",
1700
+ title: `${output.name}: ${output.concept}`,
1701
+ children: [
1702
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-pill-name", children: output.name }),
1703
+ /* @__PURE__ */ jsx18("span", { className: "pipe-card-io-pill-concept", children: output.concept })
1704
+ ]
1705
+ },
1706
+ output.name
1707
+ )) })
1708
+ ] }),
1709
+ children
1710
+ ] });
1711
+ }
1712
+
1713
+ // src/graph/react/nodes/pipe/pipeCardRegistry.ts
1714
+ var PIPE_CARD_REGISTRY = {
1715
+ PipeLLM: PipeCardBase,
1716
+ PipeExtract: PipeCardBase,
1717
+ PipeCompose: PipeCardBase,
1718
+ PipeImgGen: PipeCardBase,
1719
+ PipeSearch: PipeCardBase,
1720
+ PipeFunc: PipeCardBase
1721
+ };
1722
+ function getPipeCardComponent(pipeType) {
1723
+ return PIPE_CARD_REGISTRY[pipeType];
1724
+ }
1725
+
1726
+ // src/graph/react/nodes/pipe/PipeCardNode.tsx
1727
+ import { Fragment as Fragment11, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
1728
+ function PipeCardNode({ data }) {
1729
+ var _a;
1730
+ const Card = (_a = getPipeCardComponent(data.pipeType)) != null ? _a : PipeCardBase;
1731
+ return /* @__PURE__ */ jsx19(Card, { data });
1732
+ }
1733
+ function PipeCardRFNode({
1734
+ data,
1735
+ sourcePosition = Position.Bottom,
1736
+ targetPosition = Position.Top
1737
+ }) {
1738
+ const payload = data.pipeCardData;
1739
+ if (!payload) return null;
1740
+ return /* @__PURE__ */ jsxs19(Fragment11, { children: [
1741
+ /* @__PURE__ */ jsx19(Handle, { type: "target", position: targetPosition }),
1742
+ /* @__PURE__ */ jsx19(PipeCardNode, { data: payload }),
1743
+ /* @__PURE__ */ jsx19(Handle, { type: "source", position: sourcePosition })
1744
+ ] });
1745
+ }
1746
+
1747
+ // src/graph/react/viewer/GraphViewer.tsx
1748
+ import { Fragment as Fragment12, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
1749
+ var nodeTypes = __spreadProps(__spreadValues({}, controllerNodeTypes), {
1750
+ pipeCard: PipeCardRFNode
1751
+ });
1752
+ function StuffNodeDetail({
1753
+ stuffData,
1754
+ graphspec,
1755
+ resolveStorageUrl
1756
+ }) {
1757
+ const conceptInfo = stuffData.concept && graphspec ? resolveConceptRef(graphspec, stuffData.concept) : void 0;
1758
+ return /* @__PURE__ */ jsx20(Fragment12, { children: conceptInfo ? /* @__PURE__ */ jsx20(
1759
+ ConceptDetailPanel,
1760
+ {
1761
+ concept: conceptInfo,
1762
+ ioData: stuffData,
1763
+ resolveStorageUrl
1764
+ }
1765
+ ) : (
1766
+ /* Fallback: just show the StuffViewer if no concept info */
1767
+ /* @__PURE__ */ jsx20(StuffViewer, { stuff: stuffData, resolveStorageUrl })
1768
+ ) });
1769
+ }
1770
+ function cloneCachedNodes(nodes) {
1771
+ return nodes.map((n) => __spreadProps(__spreadValues({}, n), {
1772
+ position: __spreadValues({}, n.position),
1773
+ data: __spreadValues({}, n.data),
1774
+ style: n.style ? __spreadValues({}, n.style) : void 0
1775
+ }));
1776
+ }
1777
+ function applyStatusOverrides(nodes, statusMap) {
1778
+ if (!statusMap || Object.keys(statusMap).length === 0) return nodes;
1779
+ return nodes.map((node) => {
1780
+ var _a;
1781
+ const pipeCode = node.data.pipeCode;
1782
+ if (!pipeCode || !Object.hasOwn(statusMap, pipeCode)) return node;
1783
+ const newStatus = statusMap[pipeCode];
1784
+ if (((_a = node.data.pipeCardData) == null ? void 0 : _a.status) === newStatus) return node;
1785
+ return __spreadProps(__spreadValues({}, node), {
1786
+ data: __spreadProps(__spreadValues({}, node.data), {
1787
+ nodeData: node.data.nodeData ? __spreadProps(__spreadValues({}, node.data.nodeData), { status: newStatus }) : node.data.nodeData,
1788
+ pipeCardData: node.data.pipeCardData ? __spreadProps(__spreadValues({}, node.data.pipeCardData), { status: newStatus }) : node.data.pipeCardData
1789
+ })
1790
+ });
1791
+ });
1792
+ }
1793
+ function GraphViewer(props) {
1794
+ const {
1795
+ graphspec,
1796
+ config = DEFAULT_GRAPH_CONFIG,
1797
+ initialDirection,
1798
+ initialShowControllers,
1799
+ hideToolbar = false,
1800
+ onNavigateToPipe,
1801
+ onStuffNodeClick,
1802
+ onReactFlowInit,
1803
+ statusMap,
1804
+ onNodeSelect,
1805
+ onPaneClick,
1806
+ renderDetailExtra,
1807
+ resolveStorageUrl
1808
+ } = props;
1809
+ const [direction, setDirection] = React7.useState(
1810
+ () => {
1811
+ var _a, _b;
1812
+ return (_b = (_a = initialDirection != null ? initialDirection : config.direction) != null ? _a : DEFAULT_GRAPH_CONFIG.direction) != null ? _b : GRAPH_DIRECTION.TB;
1813
+ }
1814
+ );
1815
+ const [showControllers, setShowControllers] = React7.useState(
1816
+ () => {
1817
+ var _a, _b;
1818
+ return (_b = (_a = initialShowControllers != null ? initialShowControllers : config.showControllers) != null ? _a : DEFAULT_GRAPH_CONFIG.showControllers) != null ? _b : false;
1819
+ }
1820
+ );
1821
+ const containerRef = React7.useRef(null);
1822
+ const [detailSelection, setDetailSelection] = React7.useState(null);
1823
+ const [conceptOverride, setConceptOverride] = React7.useState(null);
1824
+ const {
1825
+ width: panelWidth,
1826
+ isDragging: isPanelDragging,
1827
+ handleMouseDown: onResizeMouseDown
1828
+ } = useResizable({ defaultWidth: 380, minWidth: 280, maxWidth: 800, containerRef });
1829
+ React7.useEffect(() => {
1830
+ setDetailSelection(null);
1831
+ setConceptOverride(null);
1832
+ }, [graphspec]);
1833
+ React7.useEffect(() => {
1834
+ var _a;
1835
+ const el = containerRef.current;
1836
+ if (!el) return;
1837
+ const palette = (_a = config.paletteColors) != null ? _a : DEFAULT_GRAPH_CONFIG.paletteColors;
1838
+ if (!palette) return;
1839
+ for (const [cssVar, value] of Object.entries(palette)) {
1840
+ el.style.setProperty(cssVar, value);
1841
+ }
1842
+ return () => {
1843
+ for (const cssVar of Object.keys(palette)) {
1844
+ el.style.removeProperty(cssVar);
1845
+ }
1846
+ };
1847
+ }, [config.paletteColors]);
1848
+ const [nodes, setNodes, onNodesChange] = useNodesState([]);
1849
+ const [edges, setEdges, onEdgesChange] = useEdgesState([]);
1850
+ const reactFlowRef = React7.useRef(null);
1851
+ const initialDataRef = React7.useRef(null);
1852
+ const layoutCacheRef = React7.useRef(null);
1853
+ const [expandedControllers, setExpandedControllers] = React7.useState(/* @__PURE__ */ new Set());
1854
+ const toggleCollapse = React7.useCallback((controllerId) => {
1855
+ setExpandedControllers((prev) => {
1856
+ const next = new Set(prev);
1857
+ if (next.has(controllerId)) next.delete(controllerId);
1858
+ else next.add(controllerId);
1859
+ return next;
1860
+ });
1861
+ }, []);
1862
+ const edgeType = config.edgeType || EDGE_TYPE.DEFAULT;
1863
+ const layoutConfig = React7.useMemo(
1864
+ () => ({ nodesep: config.nodesep, ranksep: config.ranksep }),
1865
+ [config.nodesep, config.ranksep]
1866
+ );
1867
+ const showControllersRef = React7.useRef(showControllers);
1868
+ showControllersRef.current = showControllers;
1869
+ const directionRef = React7.useRef(direction);
1870
+ directionRef.current = direction;
1871
+ const layoutConfigRef = React7.useRef(layoutConfig);
1872
+ layoutConfigRef.current = layoutConfig;
1873
+ const initialZoomRef = React7.useRef(config.initialZoom);
1874
+ initialZoomRef.current = config.initialZoom;
1875
+ const panToTopRef = React7.useRef(config.panToTop);
1876
+ panToTopRef.current = config.panToTop;
1877
+ const expandedRef = React7.useRef(expandedControllers);
1878
+ expandedRef.current = expandedControllers;
1879
+ const toggleCollapseRef = React7.useRef(toggleCollapse);
1880
+ toggleCollapseRef.current = toggleCollapse;
1881
+ const statusMapRef = React7.useRef(statusMap);
1882
+ statusMapRef.current = statusMap;
1883
+ React7.useEffect(() => {
1884
+ if (!initialDataRef.current) return;
1885
+ let cancelled = false;
1886
+ (async () => {
1887
+ try {
1888
+ const data = initialDataRef.current;
1889
+ if (!data) return;
1890
+ const relayouted = await getLayoutedElements(
1891
+ data.nodes,
1892
+ data.edges,
1893
+ direction,
1894
+ layoutConfig,
1895
+ data._graphspec,
1896
+ data._analysis
1897
+ );
1898
+ if (cancelled) return;
1899
+ layoutCacheRef.current = {
1900
+ nodes: relayouted.nodes,
1901
+ edges: relayouted.edges,
1902
+ controllerPositions: relayouted.controllerPositions
1903
+ };
1904
+ const withControllers = applyControllers(
1905
+ cloneCachedNodes(relayouted.nodes),
1906
+ relayouted.edges,
1907
+ data._graphspec,
1908
+ data._analysis,
1909
+ showControllersRef.current,
1910
+ expandedRef.current,
1911
+ toggleCollapseRef.current,
1912
+ relayouted.controllerPositions
1913
+ );
1914
+ setNodes(
1915
+ applyStatusOverrides(
1916
+ toAppNodes(hydrateLabels(withControllers.nodes)),
1917
+ statusMapRef.current
1918
+ )
1919
+ );
1920
+ setEdges(toAppEdges(withControllers.edges));
1921
+ setTimeout(() => {
1922
+ if (!cancelled && reactFlowRef.current) {
1923
+ reactFlowRef.current.fitView({ padding: 0.1 });
1924
+ }
1925
+ }, 50);
1926
+ } catch (err) {
1927
+ console.error("[GraphViewer] ELK layout failed:", err);
1928
+ }
1929
+ })();
1930
+ return () => {
1931
+ cancelled = true;
1932
+ };
1933
+ }, [direction, layoutConfig]);
1934
+ React7.useEffect(() => {
1935
+ if (!layoutCacheRef.current || !initialDataRef.current) return;
1936
+ const cachedNodes = cloneCachedNodes(layoutCacheRef.current.nodes);
1937
+ const cachedEdges = layoutCacheRef.current.edges;
1938
+ const withControllers = applyControllers(
1939
+ cachedNodes,
1940
+ cachedEdges,
1941
+ initialDataRef.current._graphspec,
1942
+ initialDataRef.current._analysis,
1943
+ showControllers,
1944
+ expandedControllers,
1945
+ toggleCollapse,
1946
+ layoutCacheRef.current.controllerPositions
1947
+ );
1948
+ setNodes(
1949
+ applyStatusOverrides(toAppNodes(hydrateLabels(withControllers.nodes)), statusMapRef.current)
1950
+ );
1951
+ setEdges(toAppEdges(withControllers.edges));
1952
+ }, [showControllers, expandedControllers, toggleCollapse]);
1953
+ React7.useEffect(() => {
1954
+ if (!graphspec) {
1955
+ initialDataRef.current = null;
1956
+ layoutCacheRef.current = null;
1957
+ setNodes([]);
1958
+ setEdges([]);
1959
+ return;
1960
+ }
1961
+ let cancelled = false;
1962
+ setExpandedControllers(/* @__PURE__ */ new Set());
1963
+ const { graphData, analysis } = buildGraph(graphspec, edgeType);
1964
+ initialDataRef.current = {
1965
+ nodes: graphData.nodes,
1966
+ edges: graphData.edges,
1967
+ _analysis: analysis,
1968
+ _graphspec: graphspec
1969
+ };
1970
+ (async () => {
1971
+ try {
1972
+ const currentDirection = directionRef.current;
1973
+ const currentLayoutConfig = layoutConfigRef.current;
1974
+ const needsLayout = graphData.nodes.some(
1975
+ (n) => !n.position || n.position.x === 0 && n.position.y === 0
1976
+ );
1977
+ const layouted = needsLayout ? await getLayoutedElements(
1978
+ graphData.nodes,
1979
+ graphData.edges,
1980
+ currentDirection,
1981
+ currentLayoutConfig,
1982
+ graphspec,
1983
+ analysis
1984
+ ) : __spreadProps(__spreadValues({}, graphData), {
1985
+ controllerPositions: {}
1986
+ });
1987
+ if (cancelled) return;
1988
+ layoutCacheRef.current = {
1989
+ nodes: layouted.nodes,
1990
+ edges: layouted.edges,
1991
+ controllerPositions: layouted.controllerPositions
1992
+ };
1993
+ const withControllers = applyControllers(
1994
+ cloneCachedNodes(layouted.nodes),
1995
+ layouted.edges,
1996
+ graphspec,
1997
+ analysis,
1998
+ showControllersRef.current,
1999
+ expandedRef.current,
2000
+ toggleCollapseRef.current,
2001
+ layouted.controllerPositions
2002
+ );
2003
+ setNodes(
2004
+ applyStatusOverrides(
2005
+ toAppNodes(hydrateLabels(withControllers.nodes)),
2006
+ statusMapRef.current
2007
+ )
2008
+ );
2009
+ setEdges(toAppEdges(withControllers.edges));
2010
+ setTimeout(() => {
2011
+ if (!cancelled && reactFlowRef.current) {
2012
+ reactFlowRef.current.fitView({ padding: 0.1 });
2013
+ if (initialZoomRef.current !== void 0 && initialZoomRef.current !== null) {
2014
+ reactFlowRef.current.zoomTo(initialZoomRef.current);
2015
+ }
2016
+ if (panToTopRef.current) {
2017
+ const vp = reactFlowRef.current.getViewport();
2018
+ reactFlowRef.current.setViewport({ x: vp.x, y: 20, zoom: vp.zoom });
2019
+ }
2020
+ }
2021
+ }, 100);
2022
+ } catch (err) {
2023
+ console.error("[GraphViewer] ELK layout failed:", err);
2024
+ }
2025
+ })();
2026
+ return () => {
2027
+ cancelled = true;
2028
+ };
2029
+ }, [graphspec, edgeType]);
2030
+ React7.useEffect(() => {
2031
+ if (!layoutCacheRef.current || !initialDataRef.current) return;
2032
+ const cachedNodes = cloneCachedNodes(layoutCacheRef.current.nodes);
2033
+ const cachedEdges = layoutCacheRef.current.edges;
2034
+ const withControllers = applyControllers(
2035
+ cachedNodes,
2036
+ cachedEdges,
2037
+ initialDataRef.current._graphspec,
2038
+ initialDataRef.current._analysis,
2039
+ showControllersRef.current,
2040
+ expandedRef.current,
2041
+ toggleCollapseRef.current,
2042
+ layoutCacheRef.current.controllerPositions
2043
+ );
2044
+ setNodes(applyStatusOverrides(toAppNodes(hydrateLabels(withControllers.nodes)), statusMap));
2045
+ setEdges(toAppEdges(withControllers.edges));
2046
+ }, [statusMap]);
2047
+ const onNodeClick = React7.useCallback(
2048
+ (event, node) => {
2049
+ var _a;
2050
+ const nodeData = node.data;
2051
+ onNodeSelect == null ? void 0 : onNodeSelect(node.id, nodeData, event);
2052
+ if (nodeData.isController || nodeData.isPipe) {
2053
+ const code = nodeData.pipeCode || nodeData.labelText;
2054
+ if (code && onNavigateToPipe) {
2055
+ onNavigateToPipe(code, (_a = nodeData.pipeCardData) == null ? void 0 : _a.status);
2056
+ }
2057
+ } else if (nodeData.isStuff && onStuffNodeClick && graphspec) {
2058
+ const digest = stuffDigestFromId(node.id);
2059
+ const sd = findStuffDataByDigest(graphspec, digest);
2060
+ if (sd) onStuffNodeClick(sd);
2061
+ }
2062
+ setConceptOverride(null);
2063
+ if ((detailSelection == null ? void 0 : detailSelection.nodeId) === node.id && !conceptOverride) {
2064
+ setDetailSelection(null);
2065
+ } else if (nodeData.isPipe || nodeData.isController) {
2066
+ setDetailSelection({ kind: "pipe", nodeId: node.id, nodeData });
2067
+ } else if (nodeData.isStuff && graphspec) {
2068
+ const digest = stuffDigestFromId(node.id);
2069
+ const sd = findStuffDataByDigest(graphspec, digest);
2070
+ setDetailSelection({ kind: "stuff", nodeId: node.id, nodeData, stuffData: sd != null ? sd : void 0 });
2071
+ }
2072
+ setNodes((nds) => nds.map((n) => __spreadProps(__spreadValues({}, n), { selected: n.id === node.id })));
2073
+ },
2074
+ [setNodes, onNavigateToPipe, onNodeSelect, onStuffNodeClick, graphspec, detailSelection, conceptOverride]
2075
+ );
2076
+ const onInit = React7.useCallback(
2077
+ (reactFlowInstance) => {
2078
+ reactFlowRef.current = reactFlowInstance;
2079
+ if (onReactFlowInit) {
2080
+ onReactFlowInit(reactFlowInstance);
2081
+ }
2082
+ },
2083
+ [onReactFlowInit]
2084
+ );
2085
+ const handlePaneClick = React7.useCallback(() => {
2086
+ setDetailSelection(null);
2087
+ setConceptOverride(null);
2088
+ onPaneClick == null ? void 0 : onPaneClick();
2089
+ }, [onPaneClick]);
2090
+ const handleConceptClick = React7.useCallback(
2091
+ (conceptCode) => {
2092
+ if (!graphspec) return;
2093
+ const info = resolveConceptRef(graphspec, conceptCode);
2094
+ if (info) setConceptOverride(info);
2095
+ },
2096
+ [graphspec]
2097
+ );
2098
+ const selectedSpecNode = (detailSelection == null ? void 0 : detailSelection.kind) === "pipe" && graphspec ? graphspec.nodes.find((n) => n.pipe_code === detailSelection.nodeData.pipeCode) : void 0;
2099
+ const detailOpen = detailSelection !== null || conceptOverride !== null;
2100
+ return /* @__PURE__ */ jsxs20("div", { ref: containerRef, className: "react-flow-container", children: [
2101
+ /* @__PURE__ */ jsx20(
2102
+ ReactFlow,
2103
+ {
2104
+ nodes,
2105
+ edges,
2106
+ nodeTypes,
2107
+ onNodesChange,
2108
+ onEdgesChange,
2109
+ onNodeClick,
2110
+ onPaneClick: handlePaneClick,
2111
+ onInit,
2112
+ fitView: true,
2113
+ fitViewOptions: { padding: 0.1 },
2114
+ defaultEdgeOptions: { type: edgeType },
2115
+ panOnScroll: true,
2116
+ minZoom: 0.1,
2117
+ proOptions: { hideAttribution: true },
2118
+ children: /* @__PURE__ */ jsx20(
2119
+ Background,
2120
+ {
2121
+ variant: BackgroundVariant.Dots,
2122
+ gap: 20,
2123
+ size: 1,
2124
+ color: "var(--color-bg-dots)"
2125
+ }
2126
+ )
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ jsxs20(
2130
+ DetailPanel,
2131
+ {
2132
+ isOpen: detailOpen,
2133
+ onClose: handlePaneClick,
2134
+ width: panelWidth,
2135
+ isDragging: isPanelDragging,
2136
+ onResizeHandleMouseDown: onResizeMouseDown,
2137
+ children: [
2138
+ conceptOverride ? /* @__PURE__ */ jsx20(ConceptDetailPanel, { concept: conceptOverride }) : selectedSpecNode && graphspec ? /* @__PURE__ */ jsx20(
2139
+ PipeDetailPanel,
2140
+ {
2141
+ node: selectedSpecNode,
2142
+ spec: graphspec,
2143
+ onConceptClick: handleConceptClick
2144
+ }
2145
+ ) : (detailSelection == null ? void 0 : detailSelection.stuffData) ? /* @__PURE__ */ jsx20(
2146
+ StuffNodeDetail,
2147
+ {
2148
+ stuffData: detailSelection.stuffData,
2149
+ graphspec,
2150
+ resolveStorageUrl
2151
+ }
2152
+ ) : null,
2153
+ renderDetailExtra && detailSelection && !conceptOverride && renderDetailExtra(detailSelection.nodeId, detailSelection.nodeData)
2154
+ ]
2155
+ }
2156
+ ),
2157
+ !hideToolbar && /* @__PURE__ */ jsx20(
2158
+ GraphToolbar,
2159
+ {
2160
+ direction,
2161
+ onDirectionChange: setDirection,
2162
+ showControllers,
2163
+ onShowControllersChange: setShowControllers,
2164
+ onZoomIn: () => {
2165
+ var _a;
2166
+ return (_a = reactFlowRef.current) == null ? void 0 : _a.zoomIn();
2167
+ },
2168
+ onZoomOut: () => {
2169
+ var _a;
2170
+ return (_a = reactFlowRef.current) == null ? void 0 : _a.zoomOut();
2171
+ },
2172
+ onFitView: () => {
2173
+ var _a;
2174
+ return (_a = reactFlowRef.current) == null ? void 0 : _a.fitView({ padding: 0.1 });
2175
+ },
2176
+ rightOffset: detailOpen ? panelWidth : 0
2177
+ }
2178
+ )
2179
+ ] });
2180
+ }
2181
+ export {
2182
+ ConceptDetailPanel,
2183
+ ControllerGroupNode,
2184
+ DetailPanel,
2185
+ GraphViewer,
2186
+ PipeCardBase,
2187
+ PipeCardNode,
2188
+ PipeDetailPanel,
2189
+ StuffViewer,
2190
+ applyStatusOverrides,
2191
+ controllerNodeTypes,
2192
+ extractFilename,
2193
+ extractInlineUrl,
2194
+ extractStorageUri,
2195
+ extractUrl,
2196
+ findStuffDataByDigest,
2197
+ getHtmlTabLabel,
2198
+ hydrateLabels,
2199
+ isInlineRenderableUrl,
2200
+ isSafeDisplayUrl,
2201
+ renderLabel,
2202
+ resolveMimeType,
2203
+ toAppEdges,
2204
+ toAppNodes,
2205
+ useResizable
2206
+ };
2207
+ //# sourceMappingURL=index.js.map