@genfeedai/workflow-ui 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/canvas.d.ts +22 -22
  2. package/dist/canvas.mjs +16 -16
  3. package/dist/{chunk-XPZAHIWY.mjs → chunk-2FUPL67V.mjs} +1592 -1044
  4. package/dist/{chunk-HWVTD2LC.mjs → chunk-53XDE62A.mjs} +818 -623
  5. package/dist/{chunk-PCIWWD37.mjs → chunk-7LV4UAUS.mjs} +19 -19
  6. package/dist/{chunk-7SKSRSS7.mjs → chunk-B4EAAKYF.mjs} +16 -16
  7. package/dist/{chunk-ZJD5WMR3.mjs → chunk-C6MQBJFC.mjs} +45 -13
  8. package/dist/{chunk-7H3WJJYS.mjs → chunk-ESVULCFY.mjs} +12 -6
  9. package/dist/{chunk-GWBGK3KL.mjs → chunk-FWJIAW2E.mjs} +82 -47
  10. package/dist/{chunk-R727OFBR.mjs → chunk-GPYIIWD5.mjs} +404 -350
  11. package/dist/{chunk-OQREHJXK.mjs → chunk-IYFWAJBB.mjs} +208 -203
  12. package/dist/{chunk-N5NJZTK4.mjs → chunk-MGLAKMDP.mjs} +23 -21
  13. package/dist/{chunk-LT3ZJJL6.mjs → chunk-OJWVEEMM.mjs} +497 -399
  14. package/dist/{chunk-ZD2BADZO.mjs → chunk-ORVDYXDP.mjs} +221 -175
  15. package/dist/{chunk-CV4M7CNU.mjs → chunk-QQVHGJ2G.mjs} +149 -142
  16. package/dist/{chunk-6PSJTBNV.mjs → chunk-U4QPE4CY.mjs} +387 -347
  17. package/dist/{chunk-EFXQT23N.mjs → chunk-VVQ4CH77.mjs} +5 -5
  18. package/dist/{chunk-VRN3UWE5.mjs → chunk-XRC3O5GK.mjs} +73 -73
  19. package/dist/{chunk-FT33LFII.mjs → chunk-YUIK4AHM.mjs} +1 -1
  20. package/dist/{chunk-FMJPFB6W.mjs → chunk-ZSITTZ4S.mjs} +630 -569
  21. package/dist/hooks.d.ts +37 -37
  22. package/dist/hooks.mjs +10 -10
  23. package/dist/index.d.ts +26 -11
  24. package/dist/index.mjs +105 -19
  25. package/dist/lib.d.ts +203 -203
  26. package/dist/lib.mjs +228 -198
  27. package/dist/nodes.d.ts +2 -2
  28. package/dist/nodes.mjs +12 -12
  29. package/dist/panels.d.ts +2 -3
  30. package/dist/panels.mjs +3 -3
  31. package/dist/provider.d.ts +2 -2
  32. package/dist/provider.mjs +2 -2
  33. package/dist/stores.d.ts +5 -5
  34. package/dist/stores.mjs +5 -5
  35. package/dist/toolbar.d.ts +42 -24
  36. package/dist/toolbar.mjs +4 -4
  37. package/dist/ui.d.ts +2 -2
  38. package/dist/ui.mjs +2 -2
  39. package/dist/{useCommentNavigation-BakbiiIc.d.ts → useRequiredInputs-ByoIS-fT.d.ts} +160 -160
  40. package/dist/{promptLibraryStore-Dl3Q3cP6.d.ts → workflowStore-Bsz0nd5c.d.ts} +368 -368
  41. package/dist/workflowStore-N2F7WIG3.mjs +2 -0
  42. package/package.json +77 -75
  43. package/src/styles/workflow-ui.css +56 -19
  44. package/dist/workflowStore-UAAKOOIK.mjs +0 -2
  45. package/dist/{types-IEKYuYhu.d.ts → types-CRXJnajq.d.ts} +1 -1
@@ -1,20 +1,20 @@
1
- import { Checkbox, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Slider, Label, Input, GridPositionSelector, ComparisonSlider } from './chunk-ZJD5WMR3.mjs';
2
- import { generateHandlesFromSchema, DEFAULT_IMAGE_MODEL, IMAGE_MODEL_MAP, IMAGE_MODEL_ID_MAP, IMAGE_MODELS, LIPSYNC_MODELS, LIPSYNC_SYNC_MODES, DEFAULT_LLM_MODEL, LLM_MODEL_MAP, LLM_MODEL_ID_MAP, LLM_MODELS, DEFAULT_VIDEO_MODEL, VIDEO_MODEL_MAP, VIDEO_MODEL_ID_MAP, VIDEO_MODELS, EASING_PRESETS, CubicBezierEditor, getMediaFromNode } from './chunk-6PSJTBNV.mjs';
3
- import { useNodeExecution, useCanGenerate, useModelSelection, useAutoLoadModelSchema, useAIGenNode, useAIGenNodeHeader, useMediaUpload, usePromptAutocomplete, useRequiredInputs } from './chunk-GWBGK3KL.mjs';
4
- import { Button } from './chunk-7SKSRSS7.mjs';
5
- import { getImageDimensions, getVideoMetadata } from './chunk-EFXQT23N.mjs';
6
- import { usePromptEditorStore, useAnnotationStore } from './chunk-CV4M7CNU.mjs';
7
- import { useUIStore, useExecutionStore } from './chunk-LT3ZJJL6.mjs';
8
- import { useWorkflowStore } from './chunk-R727OFBR.mjs';
9
- import { useWorkflowUIConfig } from './chunk-FT33LFII.mjs';
10
- import { usePromptLibraryStore } from './chunk-VRN3UWE5.mjs';
1
+ import { Checkbox, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Slider, Label, Input, GridPositionSelector, ComparisonSlider } from './chunk-C6MQBJFC.mjs';
2
+ import { useNodeExecution, useCanGenerate, useModelSelection, useAutoLoadModelSchema, useAIGenNode, useAIGenNodeHeader, useMediaUpload, usePromptAutocomplete, useRequiredInputs } from './chunk-FWJIAW2E.mjs';
3
+ import { Button } from './chunk-B4EAAKYF.mjs';
4
+ import { generateHandlesFromSchema, IMAGE_MODEL_MAP, DEFAULT_IMAGE_MODEL, IMAGE_MODEL_ID_MAP, IMAGE_MODELS, LIPSYNC_MODELS, LIPSYNC_SYNC_MODES, LLM_MODEL_MAP, DEFAULT_LLM_MODEL, LLM_MODEL_ID_MAP, LLM_MODELS, VIDEO_MODEL_MAP, DEFAULT_VIDEO_MODEL, VIDEO_MODEL_ID_MAP, VIDEO_MODELS, EASING_PRESETS, CubicBezierEditor, getMediaFromNode } from './chunk-U4QPE4CY.mjs';
5
+ import { getImageDimensions, getVideoMetadata } from './chunk-VVQ4CH77.mjs';
6
+ import { useWorkflowUIConfig } from './chunk-YUIK4AHM.mjs';
7
+ import { usePromptEditorStore, useAnnotationStore } from './chunk-QQVHGJ2G.mjs';
8
+ import { useUIStore, useExecutionStore } from './chunk-OJWVEEMM.mjs';
9
+ import { useWorkflowStore } from './chunk-GPYIIWD5.mjs';
10
+ import { usePromptLibraryStore } from './chunk-XRC3O5GK.mjs';
11
+ import { Wand2, Volume2, Video, Subtitles, Sparkles, Share2, Scissors, RefreshCw, Puzzle, Pencil, Navigation, Mic, MessageSquare, Maximize2, Maximize, LayoutGrid, Layers, Image as Image$1, Grid3X3, GitBranch, Film, FileVideo, FileText, Eye, Download, Crop, Columns2, CheckCircle, Brain, AudioLines, AtSign, ArrowRightToLine, ArrowLeftFromLine, Square, Lock, Unlock, Copy, RotateCcw, Loader2, ChevronDown, ChevronRight, AlertCircle, ImageIcon, Expand, Play, AlertTriangle, CheckCircle2, Upload, Link, X, Music, Save, Clock, Shapes, ChevronLeft, SplitSquareHorizontal, Zap, ZoomOut, ZoomIn } from 'lucide-react';
12
+ import Image4 from 'next/image';
13
+ import { memo, forwardRef, useRef, useState, useCallback, useEffect, useImperativeHandle, useMemo, Component, useLayoutEffect } from 'react';
11
14
  import { NODE_DEFINITIONS, NodeStatusEnum } from '@genfeedai/types';
12
15
  import { useUpdateNodeInternals, NodeResizer, Handle, Position } from '@xyflow/react';
13
16
  import { clsx } from 'clsx';
14
- import { GitBranch, ArrowLeftFromLine, ArrowRightToLine, Subtitles, Pencil, Columns2, LayoutGrid, Grid3X3, Puzzle, Navigation, Volume2, AudioLines, Mic, Maximize, Crop, RefreshCw, AtSign, Download, Eye, CheckCircle, Share2, Scissors, Layers, Wand2, Maximize2, Brain, Video, Sparkles, Film, FileVideo, FileText, MessageSquare, Image as Image$1, Square, Lock, Unlock, Copy, RotateCcw, Loader2, ChevronDown, ChevronRight, AlertCircle, ImageIcon, Expand, Play, AlertTriangle, CheckCircle2, Upload, Link, X, Music, Save, Clock, Shapes, ChevronLeft, SplitSquareHorizontal, Zap, ZoomOut, ZoomIn } from 'lucide-react';
15
- import { memo, forwardRef, useRef, useState, useCallback, useEffect, useImperativeHandle, useMemo, Component, useLayoutEffect } from 'react';
16
17
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
- import Image4 from 'next/image';
18
18
  import { createPortal } from 'react-dom';
19
19
  import { useShallow } from 'zustand/react/shallow';
20
20
  import { LUMA_ASPECT_RATIOS } from '@genfeedai/core';
@@ -22,16 +22,20 @@ import { LUMA_ASPECT_RATIOS } from '@genfeedai/core';
22
22
  var NodeErrorBoundary = class extends Component {
23
23
  constructor(props) {
24
24
  super(props);
25
- this.state = { hasError: false, error: null };
25
+ this.state = { error: null, hasError: false };
26
26
  }
27
27
  static getDerivedStateFromError(error) {
28
- return { hasError: true, error };
28
+ return { error, hasError: true };
29
29
  }
30
30
  componentDidCatch(error, errorInfo) {
31
- console.error(`Node ${this.props.nodeId} (${this.props.nodeType}) crashed:`, error, errorInfo);
31
+ console.error(
32
+ `Node ${this.props.nodeId} (${this.props.nodeType}) crashed:`,
33
+ error,
34
+ errorInfo
35
+ );
32
36
  }
33
37
  handleRetry = () => {
34
- this.setState({ hasError: false, error: null });
38
+ this.setState({ error: null, hasError: false });
35
39
  };
36
40
  render() {
37
41
  if (this.state.hasError) {
@@ -41,10 +45,19 @@ var NodeErrorBoundary = class extends Component {
41
45
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-destructive", children: "Node Error" }),
42
46
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground max-w-[200px] truncate", children: this.state.error?.message ?? "An unexpected error occurred" })
43
47
  ] }),
44
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: this.handleRetry, className: "gap-1.5", children: [
45
- /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3" }),
46
- "Retry"
47
- ] })
48
+ /* @__PURE__ */ jsxs(
49
+ Button,
50
+ {
51
+ variant: "outline",
52
+ size: "sm",
53
+ onClick: this.handleRetry,
54
+ className: "gap-1.5",
55
+ children: [
56
+ /* @__PURE__ */ jsx(RefreshCw, { className: "h-3 w-3" }),
57
+ "Retry"
58
+ ]
59
+ }
60
+ )
48
61
  ] });
49
62
  }
50
63
  return this.props.children;
@@ -60,10 +73,18 @@ function calculatePosition(anchorRect) {
60
73
  const placement = spaceAbove >= tooltipHeight + offset ? "top" : "bottom";
61
74
  const top = placement === "top" ? anchorRect.top - tooltipHeight - offset : anchorRect.bottom + offset;
62
75
  let left = anchorRect.left + anchorRect.width / 2 - tooltipWidth / 2;
63
- left = Math.max(padding, Math.min(left, viewportWidth - tooltipWidth - padding));
64
- return { top, left, placement };
76
+ left = Math.max(
77
+ padding,
78
+ Math.min(left, viewportWidth - tooltipWidth - padding)
79
+ );
80
+ return { left, placement, top };
65
81
  }
66
- function PreviewTooltip({ nodeType, nodeData, anchorRect, isVisible }) {
82
+ function PreviewTooltip({
83
+ nodeType,
84
+ nodeData,
85
+ anchorRect,
86
+ isVisible
87
+ }) {
67
88
  const [mounted, setMounted] = useState(false);
68
89
  const tooltipRef = useRef(null);
69
90
  const mediaInfo = useMemo(() => {
@@ -86,15 +107,24 @@ function PreviewTooltip({ nodeType, nodeData, anchorRect, isVisible }) {
86
107
  ref: tooltipRef,
87
108
  className: "fixed z-[100] pointer-events-none",
88
109
  style: {
89
- top: position.top,
90
110
  left: position.left,
91
111
  opacity: isVisible ? 1 : 0,
112
+ top: position.top,
92
113
  transform: `scale(${isVisible ? 1 : 0.95})`,
93
114
  transition: "opacity 150ms ease-out, transform 150ms ease-out"
94
115
  },
95
116
  children: /* @__PURE__ */ jsxs("div", { className: "w-[280px] bg-card border border-border rounded-lg shadow-xl overflow-hidden", children: [
96
117
  /* @__PURE__ */ jsxs("div", { className: "relative aspect-video bg-background", children: [
97
- mediaInfo.type === "image" && /* @__PURE__ */ jsx(Image4, { src: mediaInfo.url, alt: "Preview", fill: true, className: "object-contain", unoptimized: true }),
118
+ mediaInfo.type === "image" && /* @__PURE__ */ jsx(
119
+ Image4,
120
+ {
121
+ src: mediaInfo.url,
122
+ alt: "Preview",
123
+ fill: true,
124
+ className: "object-contain",
125
+ unoptimized: true
126
+ }
127
+ ),
98
128
  mediaInfo.type === "video" && /* @__PURE__ */ jsx(
99
129
  "video",
100
130
  {
@@ -120,47 +150,47 @@ function PreviewTooltip({ nodeType, nodeData, anchorRect, isVisible }) {
120
150
  return createPortal(content, document.body);
121
151
  }
122
152
  var ICON_MAP = {
123
- Image: Image$1,
124
- MessageSquare,
153
+ ArrowLeftFromLine,
154
+ // Composition icons
155
+ ArrowRightToLine,
156
+ AtSign,
157
+ AudioLines,
158
+ Brain,
159
+ CheckCircle,
160
+ Columns2,
161
+ Crop,
162
+ Download,
163
+ Eye,
125
164
  FileText,
126
165
  FileVideo,
127
166
  Film,
128
- Sparkles,
129
- Video,
130
- Brain,
131
- Maximize2,
132
- Wand2,
167
+ GitBranch,
168
+ Grid3X3,
169
+ Image: Image$1,
133
170
  Layers,
134
- Scissors,
135
- Share2,
136
- CheckCircle,
137
- Eye,
138
- Download,
139
- AtSign,
140
- RefreshCw: RefreshCw,
141
- Crop,
171
+ LayoutGrid,
142
172
  Maximize,
173
+ Maximize2,
174
+ MessageSquare,
143
175
  Mic,
144
- AudioLines,
145
- Volume2,
146
176
  Navigation,
147
- Puzzle,
148
- Grid3X3,
149
- LayoutGrid,
150
- Columns2,
151
177
  Pencil,
178
+ Puzzle,
179
+ RefreshCw: RefreshCw,
180
+ Scissors,
181
+ Share2,
182
+ Sparkles,
152
183
  Subtitles,
153
- // Composition icons
154
- ArrowRightToLine,
155
- ArrowLeftFromLine,
156
- GitBranch
184
+ Video,
185
+ Volume2,
186
+ Wand2
157
187
  };
158
188
  var HANDLE_COLORS = {
189
+ audio: "var(--handle-audio)",
159
190
  image: "var(--handle-image)",
160
- video: "var(--handle-video)",
161
- text: "var(--handle-text)",
162
191
  number: "var(--handle-number)",
163
- audio: "var(--handle-audio)"
192
+ text: "var(--handle-text)",
193
+ video: "var(--handle-video)"
164
194
  };
165
195
  function StatusIndicator({ status }) {
166
196
  switch (status) {
@@ -204,7 +234,9 @@ function BaseNodeComponent({
204
234
  const executeNode = useExecutionStore((state) => state.executeNode);
205
235
  const isRunning = useExecutionStore((state) => state.isRunning);
206
236
  const stopExecution = useExecutionStore((state) => state.stopExecution);
207
- const stopNodeExecution = useExecutionStore((state) => state.stopNodeExecution);
237
+ const stopNodeExecution = useExecutionStore(
238
+ (state) => state.stopNodeExecution
239
+ );
208
240
  const updateNodeInternals = useUpdateNodeInternals();
209
241
  const nodeDef = NODE_DEFINITIONS[type];
210
242
  const nodeData = data;
@@ -231,12 +263,17 @@ function BaseNodeComponent({
231
263
  return () => cancelAnimationFrame(rafId);
232
264
  }, [id, updateNodeInternals, handlesKey]);
233
265
  const isHighlighted = highlightedNodeIds.length === 0 || highlightedNodeIds.includes(id);
234
- const nodeExecuting = useExecutionStore((state) => state.activeNodeExecutions.has(id));
266
+ const nodeExecuting = useExecutionStore(
267
+ (state) => state.activeNodeExecutions.has(id)
268
+ );
235
269
  const handleRetry = useCallback(
236
270
  (e) => {
237
271
  e.stopPropagation();
238
272
  if (!nodeExecuting) {
239
- updateNodeData(id, { error: void 0, status: NodeStatusEnum.PROCESSING });
273
+ updateNodeData(id, {
274
+ error: void 0,
275
+ status: NodeStatusEnum.PROCESSING
276
+ });
240
277
  executeNode(id);
241
278
  }
242
279
  },
@@ -250,10 +287,17 @@ function BaseNodeComponent({
250
287
  } else if (nodeExecuting) {
251
288
  stopNodeExecution(id);
252
289
  } else {
253
- updateNodeData(id, { status: NodeStatusEnum.IDLE, error: void 0 });
290
+ updateNodeData(id, { error: void 0, status: NodeStatusEnum.IDLE });
254
291
  }
255
292
  },
256
- [id, isRunning, nodeExecuting, stopExecution, stopNodeExecution, updateNodeData]
293
+ [
294
+ id,
295
+ isRunning,
296
+ nodeExecuting,
297
+ stopExecution,
298
+ stopNodeExecution,
299
+ updateNodeData
300
+ ]
257
301
  );
258
302
  const handleCopyError = useCallback(
259
303
  async (e) => {
@@ -306,11 +350,11 @@ function BaseNodeComponent({
306
350
  toggleNodeLock(id);
307
351
  };
308
352
  const categoryCssVars = {
309
- input: "var(--category-input)",
310
353
  ai: "var(--category-ai)",
311
- processing: "var(--category-processing)",
354
+ composition: "var(--category-composition)",
355
+ input: "var(--category-input)",
312
356
  output: "var(--category-output)",
313
- composition: "var(--category-composition)"
357
+ processing: "var(--category-processing)"
314
358
  };
315
359
  const categoryColor = categoryCssVars[nodeDef.category] ?? categoryCssVars.input;
316
360
  const customColor = nodeData.color;
@@ -355,8 +399,8 @@ function BaseNodeComponent({
355
399
  ...customColor && { borderColor: customColor },
356
400
  // When resized, use explicit dimensions
357
401
  ...isResized && {
358
- width: width ? `${width}px` : void 0,
359
- height: height ? `${height}px` : void 0
402
+ height: height ? `${height}px` : void 0,
403
+ width: width ? `${width}px` : void 0
360
404
  }
361
405
  },
362
406
  onClick: () => selectNode(id),
@@ -374,8 +418,8 @@ function BaseNodeComponent({
374
418
  isConnectableEnd: !isDisabled,
375
419
  className: clsx("!w-3 !h-3", isDisabled && "opacity-30"),
376
420
  style: {
377
- top: `${(index + 1) / (sortedInputs.length + 1) * 100}%`,
378
- background: HANDLE_COLORS[input.type]
421
+ background: HANDLE_COLORS[input.type],
422
+ top: `${(index + 1) / (sortedInputs.length + 1) * 100}%`
379
423
  }
380
424
  },
381
425
  input.id
@@ -388,85 +432,96 @@ function BaseNodeComponent({
388
432
  position: Position.Right,
389
433
  id: output.id,
390
434
  className: "!w-3 !h-3 handle-output",
391
- style: { top: `${(index + 1) / (nodeDef.outputs.length + 1) * 100}%` }
435
+ style: {
436
+ top: `${(index + 1) / (nodeDef.outputs.length + 1) * 100}%`
437
+ }
392
438
  },
393
439
  output.id
394
440
  )),
395
- /* @__PURE__ */ jsxs("div", { className: clsx("flex flex-col", isResized && "flex-1 min-h-0 overflow-auto"), children: [
396
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5 border-b border-border", children: [
397
- /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-foreground" }),
398
- titleElement ?? /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-sm font-medium text-left text-foreground", children: title ?? nodeData.label }),
399
- !hideStatusIndicator && (isProcessing ? /* @__PURE__ */ jsx(
400
- Button,
401
- {
402
- variant: "ghost",
403
- size: "icon-sm",
404
- onClick: handleStopNode,
405
- className: "text-destructive hover:bg-destructive/20 hover:text-destructive",
406
- title: isRunning ? "Stop execution" : nodeExecuting ? "Stop node" : "Reset node",
407
- children: /* @__PURE__ */ jsx(Square, { className: "h-3.5 w-3.5 fill-current" })
408
- }
409
- ) : /* @__PURE__ */ jsx(StatusIndicator, { status: nodeData.status })),
410
- /* @__PURE__ */ jsx(
411
- Button,
412
- {
413
- variant: "ghost",
414
- size: "icon-sm",
415
- onClick: handleLockToggle,
416
- className: isLocked ? "text-chart-3" : "text-muted-foreground",
417
- title: isLocked ? "Unlock node (L)" : "Lock node (L)",
418
- children: isLocked ? /* @__PURE__ */ jsx(Lock, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Unlock, { className: "h-4 w-4" })
419
- }
441
+ /* @__PURE__ */ jsxs(
442
+ "div",
443
+ {
444
+ className: clsx(
445
+ "flex flex-col",
446
+ isResized && "flex-1 min-h-0 overflow-auto"
420
447
  ),
421
- headerActions
422
- ] }),
423
- /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col p-3 min-h-0", children: /* @__PURE__ */ jsxs(NodeErrorBoundary, { nodeId: id, nodeType: type, children: [
424
- nodeData.error && /* @__PURE__ */ jsxs("div", { className: "mb-3 rounded-md border border-destructive/30 bg-destructive/10 p-2", children: [
425
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
426
- /* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-destructive break-all", children: nodeData.error }),
448
+ children: [
449
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2.5 border-b border-border", children: [
450
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-foreground" }),
451
+ titleElement ?? /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-sm font-medium text-left text-foreground", children: title ?? nodeData.label }),
452
+ !hideStatusIndicator && (isProcessing ? /* @__PURE__ */ jsx(
453
+ Button,
454
+ {
455
+ variant: "ghost",
456
+ size: "icon-sm",
457
+ onClick: handleStopNode,
458
+ className: "text-destructive hover:bg-destructive/20 hover:text-destructive",
459
+ title: isRunning ? "Stop execution" : nodeExecuting ? "Stop node" : "Reset node",
460
+ children: /* @__PURE__ */ jsx(Square, { className: "h-3.5 w-3.5 fill-current" })
461
+ }
462
+ ) : /* @__PURE__ */ jsx(StatusIndicator, { status: nodeData.status })),
427
463
  /* @__PURE__ */ jsx(
428
464
  Button,
429
465
  {
430
466
  variant: "ghost",
431
467
  size: "icon-sm",
432
- onClick: handleCopyError,
433
- className: "flex-shrink-0 h-5 w-5 text-destructive/70 hover:bg-destructive/20 hover:text-destructive",
434
- title: "Copy error",
435
- children: /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
468
+ onClick: handleLockToggle,
469
+ className: isLocked ? "text-chart-3" : "text-muted-foreground",
470
+ title: isLocked ? "Unlock node (L)" : "Lock node (L)",
471
+ children: isLocked ? /* @__PURE__ */ jsx(Lock, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Unlock, { className: "h-4 w-4" })
436
472
  }
437
- )
473
+ ),
474
+ headerActions
438
475
  ] }),
439
- /* @__PURE__ */ jsxs(
440
- Button,
441
- {
442
- variant: "destructive",
443
- size: "sm",
444
- onClick: handleRetry,
445
- disabled: nodeExecuting,
446
- className: "mt-2 w-full",
447
- children: [
448
- /* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" }),
449
- "Retry"
450
- ]
451
- }
452
- )
453
- ] }),
454
- children,
455
- nodeData.status === "processing" && nodeData.progress !== void 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
456
- /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx(
457
- "div",
458
- {
459
- className: "h-full bg-primary transition-all duration-300",
460
- style: { width: `${nodeData.progress}%` }
461
- }
462
- ) }),
463
- /* @__PURE__ */ jsxs("span", { className: "mt-1 text-xs text-muted-foreground", children: [
464
- nodeData.progress,
465
- "%"
466
- ] })
467
- ] })
468
- ] }) })
469
- ] }),
476
+ /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col p-3 min-h-0", children: /* @__PURE__ */ jsxs(NodeErrorBoundary, { nodeId: id, nodeType: type, children: [
477
+ nodeData.error && /* @__PURE__ */ jsxs("div", { className: "mb-3 rounded-md border border-destructive/30 bg-destructive/10 p-2", children: [
478
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
479
+ /* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-destructive break-all", children: nodeData.error }),
480
+ /* @__PURE__ */ jsx(
481
+ Button,
482
+ {
483
+ variant: "ghost",
484
+ size: "icon-sm",
485
+ onClick: handleCopyError,
486
+ className: "flex-shrink-0 h-5 w-5 text-destructive/70 hover:bg-destructive/20 hover:text-destructive",
487
+ title: "Copy error",
488
+ children: /* @__PURE__ */ jsx(Copy, { className: "h-3 w-3" })
489
+ }
490
+ )
491
+ ] }),
492
+ /* @__PURE__ */ jsxs(
493
+ Button,
494
+ {
495
+ variant: "destructive",
496
+ size: "sm",
497
+ onClick: handleRetry,
498
+ disabled: nodeExecuting,
499
+ className: "mt-2 w-full",
500
+ children: [
501
+ /* @__PURE__ */ jsx(RotateCcw, { className: "h-3 w-3" }),
502
+ "Retry"
503
+ ]
504
+ }
505
+ )
506
+ ] }),
507
+ children,
508
+ nodeData.status === "processing" && nodeData.progress !== void 0 && /* @__PURE__ */ jsxs("div", { className: "mt-3", children: [
509
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx(
510
+ "div",
511
+ {
512
+ className: "h-full bg-primary transition-all duration-300",
513
+ style: { width: `${nodeData.progress}%` }
514
+ }
515
+ ) }),
516
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 text-xs text-muted-foreground", children: [
517
+ nodeData.progress,
518
+ "%"
519
+ ] })
520
+ ] })
521
+ ] }) })
522
+ ]
523
+ }
524
+ ),
470
525
  isLocked && /* @__PURE__ */ jsx("div", { className: "absolute -right-2 -top-2 rounded bg-chart-3 px-1.5 py-0.5 text-[10px] font-bold text-background", children: "LOCKED" })
471
526
  ]
472
527
  }
@@ -508,7 +563,10 @@ function arePropsEqual(prev, next) {
508
563
  return true;
509
564
  }
510
565
  var BaseNode = memo(BaseNodeComponent, arePropsEqual);
511
- function ProcessingOverlayComponent({ label = "Generating...", onStop }) {
566
+ function ProcessingOverlayComponent({
567
+ label = "Generating...",
568
+ onStop
569
+ }) {
512
570
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm rounded-md", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
513
571
  /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-primary" }),
514
572
  /* @__PURE__ */ jsx("span", { className: "text-xs text-white/80", children: label }),
@@ -520,14 +578,14 @@ function ProcessingOverlayComponent({ label = "Generating...", onStop }) {
520
578
  }
521
579
  var ProcessingOverlay = memo(ProcessingOverlayComponent);
522
580
  var NEGATIVE_PROMPT_OPTIONS = [
523
- { value: "blurry", label: "Blurry" },
524
- { value: "distorted", label: "Distorted" },
525
- { value: "low quality", label: "Low Quality" },
526
- { value: "watermark", label: "Watermark" },
527
- { value: "text", label: "Text/Logos" },
528
- { value: "artifacts", label: "Artifacts" },
529
- { value: "grainy", label: "Grainy/Noisy" },
530
- { value: "oversaturated", label: "Oversaturated" }
581
+ { label: "Blurry", value: "blurry" },
582
+ { label: "Distorted", value: "distorted" },
583
+ { label: "Low Quality", value: "low quality" },
584
+ { label: "Watermark", value: "watermark" },
585
+ { label: "Text/Logos", value: "text" },
586
+ { label: "Artifacts", value: "artifacts" },
587
+ { label: "Grainy/Noisy", value: "grainy" },
588
+ { label: "Oversaturated", value: "oversaturated" }
531
589
  ];
532
590
  function parseNegativePrompt(value) {
533
591
  if (!value) return /* @__PURE__ */ new Set();
@@ -537,7 +595,9 @@ function parseNegativePrompt(value) {
537
595
  }
538
596
  function isKnownTerm(term) {
539
597
  const lowerTerm = term.toLowerCase();
540
- return NEGATIVE_PROMPT_OPTIONS.some((opt) => opt.value.toLowerCase() === lowerTerm);
598
+ return NEGATIVE_PROMPT_OPTIONS.some(
599
+ (opt) => opt.value.toLowerCase() === lowerTerm
600
+ );
541
601
  }
542
602
  function extractCustomTerms(value) {
543
603
  if (!value) return "";
@@ -558,7 +618,10 @@ function combineTerms(checkedValues, customText) {
558
618
  }
559
619
  return parts.join(", ");
560
620
  }
561
- function NegativePromptSelectorComponent({ value, onChange }) {
621
+ function NegativePromptSelectorComponent({
622
+ value,
623
+ onChange
624
+ }) {
562
625
  const [isExpanded, setIsExpanded] = useState(false);
563
626
  const checkedTerms = useMemo(() => parseNegativePrompt(value), [value]);
564
627
  const [customText, setCustomText] = useState(() => extractCustomTerms(value));
@@ -606,29 +669,36 @@ function NegativePromptSelectorComponent({ value, onChange }) {
606
669
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-3 gap-y-1.5", children: NEGATIVE_PROMPT_OPTIONS.map((option) => {
607
670
  const isChecked = checkedTerms.has(option.value.toLowerCase());
608
671
  const checkboxId = `negative-prompt-${option.value}`;
609
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 nodrag", children: [
610
- /* @__PURE__ */ jsx(
611
- Checkbox,
612
- {
613
- id: checkboxId,
614
- checked: isChecked,
615
- onCheckedChange: (checked) => {
616
- if (typeof checked === "boolean") {
617
- handleCheckboxChange(option.value, checked);
672
+ return /* @__PURE__ */ jsxs(
673
+ "div",
674
+ {
675
+ className: "flex items-center gap-1.5 nodrag",
676
+ children: [
677
+ /* @__PURE__ */ jsx(
678
+ Checkbox,
679
+ {
680
+ id: checkboxId,
681
+ checked: isChecked,
682
+ onCheckedChange: (checked) => {
683
+ if (typeof checked === "boolean") {
684
+ handleCheckboxChange(option.value, checked);
685
+ }
686
+ },
687
+ className: "w-3.5 h-3.5"
618
688
  }
619
- },
620
- className: "w-3.5 h-3.5"
621
- }
622
- ),
623
- /* @__PURE__ */ jsx(
624
- "label",
625
- {
626
- htmlFor: checkboxId,
627
- className: "text-sm text-foreground truncate cursor-pointer",
628
- children: option.label
629
- }
630
- )
631
- ] }, option.value);
689
+ ),
690
+ /* @__PURE__ */ jsx(
691
+ "label",
692
+ {
693
+ htmlFor: checkboxId,
694
+ className: "text-sm text-foreground truncate cursor-pointer",
695
+ children: option.label
696
+ }
697
+ )
698
+ ]
699
+ },
700
+ option.value
701
+ );
632
702
  }) }),
633
703
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1 mt-1", children: [
634
704
  /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: "Custom" }),
@@ -685,28 +755,32 @@ var DEFAULT_ENUM_VALUES = {
685
755
  "4:5",
686
756
  "5:4"
687
757
  ],
688
- // Output formats
689
- output_format: ["jpg", "png", "webp"],
690
- // Resolution options
691
- resolution: ["1K", "2K", "4K", "720p", "1080p"],
758
+ // Character orientation
759
+ character_orientation: ["image", "video"],
692
760
  // Video duration
693
761
  duration: ["4", "5", "6", "8", "10"],
694
762
  // Megapixels
695
763
  megapixels: ["0.25", "1", "2"],
764
+ // Video model mode
765
+ mode: ["std", "pro"],
766
+ // Luma models
767
+ model: ["photon-flash-1", "photon-1"],
768
+ // Output formats
769
+ output_format: ["jpg", "png", "webp"],
770
+ // Refine options
771
+ refine: ["no_refiner", "expert_ensemble_refiner", "base_image_refiner"],
772
+ // Resolution options
773
+ resolution: ["1K", "2K", "4K", "720p", "1080p"],
696
774
  // Safety filter level
697
- safety_filter_level: ["block_only_high", "block_medium_and_above", "block_low_and_above"],
775
+ safety_filter_level: [
776
+ "block_only_high",
777
+ "block_medium_and_above",
778
+ "block_low_and_above"
779
+ ],
698
780
  // Scheduler options
699
781
  scheduler: ["K_EULER", "K_EULER_ANCESTRAL", "DDIM", "DPM_SOLVER", "PNDM"],
700
- // Refine options
701
- refine: ["no_refiner", "expert_ensemble_refiner", "base_image_refiner"],
702
- // Video model mode
703
- mode: ["std", "pro"],
704
- // Character orientation
705
- character_orientation: ["image", "video"],
706
782
  // Sync mode
707
- sync_mode: ["loop", "bounce", "cut_off", "silence", "remap"],
708
- // Luma models
709
- model: ["photon-flash-1", "photon-1"]
783
+ sync_mode: ["loop", "bounce", "cut_off", "silence", "remap"]
710
784
  };
711
785
  function getEnumKey(refPath) {
712
786
  const parts = refPath.split("/");
@@ -725,10 +799,17 @@ function EnumSelect({
725
799
  }) {
726
800
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
727
801
  /* @__PURE__ */ jsx("label", { className: "text-xs text-muted-foreground", children: formatLabel(propertyKey, property.title) }),
728
- /* @__PURE__ */ jsxs(Select, { value: String(value ?? property.default ?? options[0]), onValueChange: onChange, children: [
729
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
730
- /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
731
- ] })
802
+ /* @__PURE__ */ jsxs(
803
+ Select,
804
+ {
805
+ value: String(value ?? property.default ?? options[0]),
806
+ onValueChange: onChange,
807
+ children: [
808
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
809
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
810
+ ]
811
+ }
812
+ )
732
813
  ] });
733
814
  }
734
815
  function SliderInput({
@@ -791,7 +872,14 @@ function CheckboxInput({
791
872
  }
792
873
  }
793
874
  ),
794
- /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "text-sm text-foreground cursor-pointer", children: formatLabel(propertyKey, property.title) })
875
+ /* @__PURE__ */ jsx(
876
+ "label",
877
+ {
878
+ htmlFor: inputId,
879
+ className: "text-sm text-foreground cursor-pointer",
880
+ children: formatLabel(propertyKey, property.title)
881
+ }
882
+ )
795
883
  ] });
796
884
  }
797
885
  function NumberInput({
@@ -843,96 +931,102 @@ function SchemaInputsComponent({
843
931
  if (!schema || sortedProperties.length === 0) {
844
932
  return null;
845
933
  }
846
- return /* @__PURE__ */ jsx("div", { className: `flex flex-col gap-2${disabled ? " pointer-events-none opacity-50" : ""}`, children: sortedProperties.map(([key, property]) => {
847
- const value = values[key];
848
- if (key === "negative_prompt" && property.type === "string") {
849
- return /* @__PURE__ */ jsx(
850
- NegativePromptSelector,
851
- {
852
- value: value ?? "",
853
- onChange: (v) => handleChange(key, v)
854
- },
855
- key
856
- );
857
- }
858
- if (property.allOf && property.allOf.length > 0) {
859
- const enumKey = getEnumKey(property.allOf[0].$ref);
860
- const options = enumValues?.[enumKey] ?? DEFAULT_ENUM_VALUES[enumKey] ?? [];
861
- if (options.length > 0) {
862
- const componentSchema = componentSchemas?.[enumKey];
863
- const enumType = componentSchema?.type;
864
- return /* @__PURE__ */ jsx(
865
- EnumSelect,
866
- {
867
- propertyKey: key,
868
- property,
869
- value,
870
- options,
871
- onChange: (v) => {
872
- if (enumType === "integer") {
873
- handleChange(key, Number.parseInt(v, 10));
874
- } else if (enumType === "number") {
875
- handleChange(key, Number.parseFloat(v));
876
- } else {
877
- handleChange(key, v);
878
- }
879
- }
880
- },
881
- key
882
- );
883
- }
884
- }
885
- if (property.enum) {
886
- return /* @__PURE__ */ jsx(
887
- EnumSelect,
888
- {
889
- propertyKey: key,
890
- property,
891
- value,
892
- options: property.enum,
893
- onChange: (v) => handleChange(key, v)
894
- },
895
- key
896
- );
897
- }
898
- if (property.type === "boolean") {
899
- return /* @__PURE__ */ jsx(
900
- CheckboxInput,
901
- {
902
- propertyKey: key,
903
- property,
904
- value,
905
- onChange: (v) => handleChange(key, v)
906
- },
907
- key
908
- );
909
- }
910
- if ((property.type === "integer" || property.type === "number") && property.minimum !== void 0 && property.maximum !== void 0) {
911
- return /* @__PURE__ */ jsx(
912
- SliderInput,
913
- {
914
- propertyKey: key,
915
- property,
916
- value,
917
- onChange: (v) => handleChange(key, v)
918
- },
919
- key
920
- );
921
- }
922
- if (property.type === "integer" || property.type === "number") {
923
- return /* @__PURE__ */ jsx(
924
- NumberInput,
925
- {
926
- propertyKey: key,
927
- property,
928
- value,
929
- onChange: (v) => handleChange(key, v)
930
- },
931
- key
932
- );
934
+ return /* @__PURE__ */ jsx(
935
+ "div",
936
+ {
937
+ className: `flex flex-col gap-2${disabled ? " pointer-events-none opacity-50" : ""}`,
938
+ children: sortedProperties.map(([key, property]) => {
939
+ const value = values[key];
940
+ if (key === "negative_prompt" && property.type === "string") {
941
+ return /* @__PURE__ */ jsx(
942
+ NegativePromptSelector,
943
+ {
944
+ value: value ?? "",
945
+ onChange: (v) => handleChange(key, v)
946
+ },
947
+ key
948
+ );
949
+ }
950
+ if (property.allOf && property.allOf.length > 0) {
951
+ const enumKey = getEnumKey(property.allOf[0].$ref);
952
+ const options = enumValues?.[enumKey] ?? DEFAULT_ENUM_VALUES[enumKey] ?? [];
953
+ if (options.length > 0) {
954
+ const componentSchema = componentSchemas?.[enumKey];
955
+ const enumType = componentSchema?.type;
956
+ return /* @__PURE__ */ jsx(
957
+ EnumSelect,
958
+ {
959
+ propertyKey: key,
960
+ property,
961
+ value,
962
+ options,
963
+ onChange: (v) => {
964
+ if (enumType === "integer") {
965
+ handleChange(key, Number.parseInt(v, 10));
966
+ } else if (enumType === "number") {
967
+ handleChange(key, Number.parseFloat(v));
968
+ } else {
969
+ handleChange(key, v);
970
+ }
971
+ }
972
+ },
973
+ key
974
+ );
975
+ }
976
+ }
977
+ if (property.enum) {
978
+ return /* @__PURE__ */ jsx(
979
+ EnumSelect,
980
+ {
981
+ propertyKey: key,
982
+ property,
983
+ value,
984
+ options: property.enum,
985
+ onChange: (v) => handleChange(key, v)
986
+ },
987
+ key
988
+ );
989
+ }
990
+ if (property.type === "boolean") {
991
+ return /* @__PURE__ */ jsx(
992
+ CheckboxInput,
993
+ {
994
+ propertyKey: key,
995
+ property,
996
+ value,
997
+ onChange: (v) => handleChange(key, v)
998
+ },
999
+ key
1000
+ );
1001
+ }
1002
+ if ((property.type === "integer" || property.type === "number") && property.minimum !== void 0 && property.maximum !== void 0) {
1003
+ return /* @__PURE__ */ jsx(
1004
+ SliderInput,
1005
+ {
1006
+ propertyKey: key,
1007
+ property,
1008
+ value,
1009
+ onChange: (v) => handleChange(key, v)
1010
+ },
1011
+ key
1012
+ );
1013
+ }
1014
+ if (property.type === "integer" || property.type === "number") {
1015
+ return /* @__PURE__ */ jsx(
1016
+ NumberInput,
1017
+ {
1018
+ propertyKey: key,
1019
+ property,
1020
+ value,
1021
+ onChange: (v) => handleChange(key, v)
1022
+ },
1023
+ key
1024
+ );
1025
+ }
1026
+ return null;
1027
+ })
933
1028
  }
934
- return null;
935
- }) });
1029
+ );
936
1030
  }
937
1031
  var SchemaInputs = memo(SchemaInputsComponent);
938
1032
  function ImageGenNodeComponent(props) {
@@ -942,9 +1036,9 @@ function ImageGenNodeComponent(props) {
942
1036
  const openNodeDetailModal = useUIStore((state) => state.openNodeDetailModal);
943
1037
  const { handleGenerate, handleStop } = useNodeExecution(id);
944
1038
  const { canGenerate } = useCanGenerate({
1039
+ inputSchema: nodeData.selectedModel?.inputSchema,
945
1040
  nodeId: id,
946
1041
  nodeType: type,
947
- inputSchema: nodeData.selectedModel?.inputSchema,
948
1042
  schemaParams: nodeData.schemaParams
949
1043
  });
950
1044
  const [isModelBrowserOpen, setIsModelBrowserOpen] = useState(false);
@@ -969,16 +1063,18 @@ function ImageGenNodeComponent(props) {
969
1063
  setTimeout(() => handleDownload(index), index * 100);
970
1064
  });
971
1065
  }, [nodeData.outputImages, handleDownload]);
972
- const { handleModelSelect } = useModelSelection({
973
- nodeId: id,
974
- modelMap: IMAGE_MODEL_MAP,
975
- fallbackModel: DEFAULT_IMAGE_MODEL
976
- });
1066
+ const { handleModelSelect } = useModelSelection(
1067
+ {
1068
+ fallbackModel: DEFAULT_IMAGE_MODEL,
1069
+ modelMap: IMAGE_MODEL_MAP,
1070
+ nodeId: id
1071
+ }
1072
+ );
977
1073
  useAutoLoadModelSchema({
978
1074
  currentModel: nodeData.model,
979
- selectedModel: nodeData.selectedModel,
980
1075
  modelIdMap: IMAGE_MODEL_ID_MAP,
981
- onModelSelect: handleModelSelect
1076
+ onModelSelect: handleModelSelect,
1077
+ selectedModel: nodeData.selectedModel
982
1078
  });
983
1079
  const {
984
1080
  schemaProperties,
@@ -988,8 +1084,8 @@ function ImageGenNodeComponent(props) {
988
1084
  componentSchemas
989
1085
  } = useAIGenNode({
990
1086
  nodeId: id,
991
- selectedModel: nodeData.selectedModel,
992
- schemaParams: nodeData.schemaParams
1087
+ schemaParams: nodeData.schemaParams,
1088
+ selectedModel: nodeData.selectedModel
993
1089
  });
994
1090
  const handleExpand = useCallback(() => {
995
1091
  openNodeDetailModal(id, "preview", selectedPreview ?? 0);
@@ -1001,14 +1097,14 @@ function ImageGenNodeComponent(props) {
1001
1097
  const isProcessing = nodeData.status === "processing";
1002
1098
  const handleModelBrowse = useCallback(() => setIsModelBrowserOpen(true), []);
1003
1099
  const { titleElement, headerActions } = useAIGenNodeHeader({
1004
- modelDisplayName,
1005
- isProcessing,
1006
1100
  canGenerate,
1007
1101
  hasOutput: !!nodeData.outputImage,
1008
- onModelBrowse: handleModelBrowse,
1102
+ isProcessing,
1103
+ modelDisplayName,
1104
+ onExpand: handleExpand,
1009
1105
  onGenerate: handleGenerate,
1010
- onStop: handleStop,
1011
- onExpand: handleExpand
1106
+ onModelBrowse: handleModelBrowse,
1107
+ onStop: handleStop
1012
1108
  });
1013
1109
  return /* @__PURE__ */ jsxs(
1014
1110
  BaseNode,
@@ -1042,10 +1138,19 @@ function ImageGenNodeComponent(props) {
1042
1138
  nodeData.outputImages.length,
1043
1139
  " images)"
1044
1140
  ] }),
1045
- /* @__PURE__ */ jsxs(Button, { variant: "link", size: "sm", onClick: handleDownloadAll, className: "h-auto p-0", children: [
1046
- /* @__PURE__ */ jsx(Download, { className: "w-3 h-3" }),
1047
- "Download All"
1048
- ] })
1141
+ /* @__PURE__ */ jsxs(
1142
+ Button,
1143
+ {
1144
+ variant: "link",
1145
+ size: "sm",
1146
+ onClick: handleDownloadAll,
1147
+ className: "h-auto p-0",
1148
+ children: [
1149
+ /* @__PURE__ */ jsx(Download, { className: "w-3 h-3" }),
1150
+ "Download All"
1151
+ ]
1152
+ }
1153
+ )
1049
1154
  ] }),
1050
1155
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-1", children: nodeData.outputImages.map((img, i) => /* @__PURE__ */ jsxs(
1051
1156
  "div",
@@ -1186,8 +1291,17 @@ function LipSyncNodeComponent(props) {
1186
1291
  const isSyncModel = nodeData.model.startsWith("sync/");
1187
1292
  const supportsImage = currentModel?.supportsImage ?? false;
1188
1293
  const headerActions = useMemo(
1189
- () => nodeData.outputVideo ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
1190
- [nodeData.outputVideo, handleExpand]
1294
+ () => nodeData.outputVideo ? /* @__PURE__ */ jsx(
1295
+ Button,
1296
+ {
1297
+ variant: "ghost",
1298
+ size: "icon-sm",
1299
+ onClick: handleExpand,
1300
+ title: "Expand preview",
1301
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
1302
+ }
1303
+ ) : null,
1304
+ [nodeData.outputVideo, handleExpand]
1191
1305
  );
1192
1306
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1193
1307
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1199,10 +1313,17 @@ function LipSyncNodeComponent(props) {
1199
1313
  ] }),
1200
1314
  isSyncModel && /* @__PURE__ */ jsxs("div", { children: [
1201
1315
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Sync Mode" }),
1202
- /* @__PURE__ */ jsxs(Select, { value: nodeData.syncMode, onValueChange: handleSyncModeChange, children: [
1203
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1204
- /* @__PURE__ */ jsx(SelectContent, { children: LIPSYNC_SYNC_MODES.map((mode) => /* @__PURE__ */ jsx(SelectItem, { value: mode.value, children: mode.label }, mode.value)) })
1205
- ] })
1316
+ /* @__PURE__ */ jsxs(
1317
+ Select,
1318
+ {
1319
+ value: nodeData.syncMode,
1320
+ onValueChange: handleSyncModeChange,
1321
+ children: [
1322
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1323
+ /* @__PURE__ */ jsx(SelectContent, { children: LIPSYNC_SYNC_MODES.map((mode) => /* @__PURE__ */ jsx(SelectItem, { value: mode.value, children: mode.label }, mode.value)) })
1324
+ ]
1325
+ }
1326
+ )
1206
1327
  ] }),
1207
1328
  /* @__PURE__ */ jsxs("div", { children: [
1208
1329
  /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--muted-foreground)]", children: [
@@ -1297,15 +1418,15 @@ function LLMNodeComponent(props) {
1297
1418
  const canGenerate = hasRequiredConnections && hasSystemPrompt && hasInputPrompt;
1298
1419
  const [isModelBrowserOpen, setIsModelBrowserOpen] = useState(false);
1299
1420
  const { handleModelSelect } = useModelSelection({
1300
- nodeId: id,
1421
+ fallbackModel: DEFAULT_LLM_MODEL,
1301
1422
  modelMap: LLM_MODEL_MAP,
1302
- fallbackModel: DEFAULT_LLM_MODEL
1423
+ nodeId: id
1303
1424
  });
1304
1425
  useAutoLoadModelSchema({
1305
1426
  currentModel: nodeData.model,
1306
- selectedModel: nodeData.selectedModel,
1307
1427
  modelIdMap: LLM_MODEL_ID_MAP,
1308
- onModelSelect: handleModelSelect
1428
+ onModelSelect: handleModelSelect,
1429
+ selectedModel: nodeData.selectedModel
1309
1430
  });
1310
1431
  const handleSystemPromptChange = useCallback(
1311
1432
  (e) => {
@@ -1321,7 +1442,9 @@ function LLMNodeComponent(props) {
1321
1442
  );
1322
1443
  const handleMaxTokensChange = useCallback(
1323
1444
  (e) => {
1324
- updateNodeData(id, { maxTokens: parseInt(e.target.value, 10) });
1445
+ updateNodeData(id, {
1446
+ maxTokens: parseInt(e.target.value, 10)
1447
+ });
1325
1448
  },
1326
1449
  [id, updateNodeData]
1327
1450
  );
@@ -1335,14 +1458,14 @@ function LLMNodeComponent(props) {
1335
1458
  const isProcessing = nodeData.status === "processing";
1336
1459
  const handleModelBrowse = useCallback(() => setIsModelBrowserOpen(true), []);
1337
1460
  const { titleElement, headerActions } = useAIGenNodeHeader({
1338
- modelDisplayName,
1339
- isProcessing,
1340
1461
  canGenerate,
1341
1462
  hasOutput: !!nodeData.outputText,
1342
- onModelBrowse: handleModelBrowse,
1463
+ isProcessing,
1464
+ modelDisplayName,
1465
+ onExpand: handleExpand,
1343
1466
  onGenerate: handleGenerate,
1344
- onStop: handleStop,
1345
- onExpand: handleExpand
1467
+ onModelBrowse: handleModelBrowse,
1468
+ onStop: handleStop
1346
1469
  });
1347
1470
  return /* @__PURE__ */ jsxs(
1348
1471
  BaseNode,
@@ -1461,31 +1584,35 @@ function LLMNodeComponent(props) {
1461
1584
  }
1462
1585
  var LLMNode = memo(LLMNodeComponent);
1463
1586
  var QUALITY_MODES = [
1464
- { value: "std", label: "Standard", description: "Faster processing" },
1465
- { value: "pro", label: "Pro", description: "Higher quality" }
1587
+ { description: "Faster processing", label: "Standard", value: "std" },
1588
+ { description: "Higher quality", label: "Pro", value: "pro" }
1466
1589
  ];
1467
1590
  var CHARACTER_ORIENTATIONS = [
1468
- { value: "image", label: "From Image" },
1469
- { value: "video", label: "From Video" }
1591
+ { label: "From Image", value: "image" },
1592
+ { label: "From Video", value: "video" }
1470
1593
  ];
1471
1594
  var MOTION_MODES = [
1472
1595
  {
1473
- value: "video_transfer",
1596
+ description: "Apply motion from reference video",
1474
1597
  label: "Video Transfer",
1475
- description: "Apply motion from reference video"
1598
+ value: "video_transfer"
1599
+ },
1600
+ { description: "Apply camera movements", label: "Camera", value: "camera" },
1601
+ {
1602
+ description: "Define motion path",
1603
+ label: "Trajectory",
1604
+ value: "trajectory"
1476
1605
  },
1477
- { value: "camera", label: "Camera", description: "Apply camera movements" },
1478
- { value: "trajectory", label: "Trajectory", description: "Define motion path" },
1479
- { value: "combined", label: "Combined", description: "Camera + Trajectory" }
1606
+ { description: "Camera + Trajectory", label: "Combined", value: "combined" }
1480
1607
  ];
1481
1608
  var ASPECT_RATIOS = [
1482
- { value: "16:9", label: "16:9 (Landscape)" },
1483
- { value: "9:16", label: "9:16 (Portrait)" },
1484
- { value: "1:1", label: "1:1 (Square)" }
1609
+ { label: "16:9 (Landscape)", value: "16:9" },
1610
+ { label: "9:16 (Portrait)", value: "9:16" },
1611
+ { label: "1:1 (Square)", value: "1:1" }
1485
1612
  ];
1486
1613
  var DURATIONS = [
1487
- { value: 5, label: "5 seconds" },
1488
- { value: 10, label: "10 seconds" }
1614
+ { label: "5 seconds", value: 5 },
1615
+ { label: "10 seconds", value: 10 }
1489
1616
  ];
1490
1617
  function MotionControlNodeComponent(props) {
1491
1618
  const { id, type, data } = props;
@@ -1499,7 +1626,9 @@ function MotionControlNodeComponent(props) {
1499
1626
  });
1500
1627
  const handleModeChange = useCallback(
1501
1628
  (value) => {
1502
- updateNodeData(id, { mode: value });
1629
+ updateNodeData(id, {
1630
+ mode: value
1631
+ });
1503
1632
  },
1504
1633
  [id, updateNodeData]
1505
1634
  );
@@ -1538,7 +1667,9 @@ function MotionControlNodeComponent(props) {
1538
1667
  const handleKeepOriginalSoundToggle = useCallback(
1539
1668
  (checked) => {
1540
1669
  if (typeof checked === "boolean") {
1541
- updateNodeData(id, { keepOriginalSound: checked });
1670
+ updateNodeData(id, {
1671
+ keepOriginalSound: checked
1672
+ });
1542
1673
  }
1543
1674
  },
1544
1675
  [id, updateNodeData]
@@ -1555,7 +1686,16 @@ function MotionControlNodeComponent(props) {
1555
1686
  const isVideoTransferMode = nodeData.mode === "video_transfer";
1556
1687
  const headerActions = useMemo(
1557
1688
  () => /* @__PURE__ */ jsxs(Fragment, { children: [
1558
- nodeData.outputVideo && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }),
1689
+ nodeData.outputVideo && /* @__PURE__ */ jsx(
1690
+ Button,
1691
+ {
1692
+ variant: "ghost",
1693
+ size: "icon-sm",
1694
+ onClick: handleExpand,
1695
+ title: "Expand preview",
1696
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
1697
+ }
1698
+ ),
1559
1699
  nodeData.status === "processing" ? /* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleStop, children: [
1560
1700
  /* @__PURE__ */ jsx(Square, { className: "h-4 w-4 fill-current" }),
1561
1701
  "Generating"
@@ -1573,7 +1713,14 @@ function MotionControlNodeComponent(props) {
1573
1713
  }
1574
1714
  )
1575
1715
  ] }),
1576
- [nodeData.outputVideo, nodeData.status, handleGenerate, handleStop, handleExpand, canGenerate]
1716
+ [
1717
+ nodeData.outputVideo,
1718
+ nodeData.status,
1719
+ handleGenerate,
1720
+ handleStop,
1721
+ handleExpand,
1722
+ canGenerate
1723
+ ]
1577
1724
  );
1578
1725
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, hideStatusIndicator: true, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1579
1726
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1586,14 +1733,21 @@ function MotionControlNodeComponent(props) {
1586
1733
  isVideoTransferMode && /* @__PURE__ */ jsxs(Fragment, { children: [
1587
1734
  /* @__PURE__ */ jsxs("div", { children: [
1588
1735
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Quality" }),
1589
- /* @__PURE__ */ jsxs(Select, { value: nodeData.qualityMode, onValueChange: handleQualityModeChange, children: [
1590
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1591
- /* @__PURE__ */ jsx(SelectContent, { children: QUALITY_MODES.map((mode) => /* @__PURE__ */ jsxs(SelectItem, { value: mode.value, children: [
1592
- mode.label,
1593
- " - ",
1594
- mode.description
1595
- ] }, mode.value)) })
1596
- ] })
1736
+ /* @__PURE__ */ jsxs(
1737
+ Select,
1738
+ {
1739
+ value: nodeData.qualityMode,
1740
+ onValueChange: handleQualityModeChange,
1741
+ children: [
1742
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1743
+ /* @__PURE__ */ jsx(SelectContent, { children: QUALITY_MODES.map((mode) => /* @__PURE__ */ jsxs(SelectItem, { value: mode.value, children: [
1744
+ mode.label,
1745
+ " - ",
1746
+ mode.description
1747
+ ] }, mode.value)) })
1748
+ ]
1749
+ }
1750
+ )
1597
1751
  ] }),
1598
1752
  /* @__PURE__ */ jsxs("div", { children: [
1599
1753
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Character Orientation" }),
@@ -1604,7 +1758,14 @@ function MotionControlNodeComponent(props) {
1604
1758
  onValueChange: handleCharacterOrientationChange,
1605
1759
  children: [
1606
1760
  /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1607
- /* @__PURE__ */ jsx(SelectContent, { children: CHARACTER_ORIENTATIONS.map((orientation) => /* @__PURE__ */ jsx(SelectItem, { value: orientation.value, children: orientation.label }, orientation.value)) })
1761
+ /* @__PURE__ */ jsx(SelectContent, { children: CHARACTER_ORIENTATIONS.map((orientation) => /* @__PURE__ */ jsx(
1762
+ SelectItem,
1763
+ {
1764
+ value: orientation.value,
1765
+ children: orientation.label
1766
+ },
1767
+ orientation.value
1768
+ )) })
1608
1769
  ]
1609
1770
  }
1610
1771
  )
@@ -1632,17 +1793,31 @@ function MotionControlNodeComponent(props) {
1632
1793
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1633
1794
  /* @__PURE__ */ jsxs("div", { children: [
1634
1795
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Duration" }),
1635
- /* @__PURE__ */ jsxs(Select, { value: String(nodeData.duration), onValueChange: handleDurationChange, children: [
1636
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1637
- /* @__PURE__ */ jsx(SelectContent, { children: DURATIONS.map((d) => /* @__PURE__ */ jsx(SelectItem, { value: String(d.value), children: d.label }, d.value)) })
1638
- ] })
1796
+ /* @__PURE__ */ jsxs(
1797
+ Select,
1798
+ {
1799
+ value: String(nodeData.duration),
1800
+ onValueChange: handleDurationChange,
1801
+ children: [
1802
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1803
+ /* @__PURE__ */ jsx(SelectContent, { children: DURATIONS.map((d) => /* @__PURE__ */ jsx(SelectItem, { value: String(d.value), children: d.label }, d.value)) })
1804
+ ]
1805
+ }
1806
+ )
1639
1807
  ] }),
1640
1808
  /* @__PURE__ */ jsxs("div", { children: [
1641
1809
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Aspect Ratio" }),
1642
- /* @__PURE__ */ jsxs(Select, { value: nodeData.aspectRatio, onValueChange: handleAspectRatioChange, children: [
1643
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1644
- /* @__PURE__ */ jsx(SelectContent, { children: ASPECT_RATIOS.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio.value, children: ratio.label }, ratio.value)) })
1645
- ] })
1810
+ /* @__PURE__ */ jsxs(
1811
+ Select,
1812
+ {
1813
+ value: nodeData.aspectRatio,
1814
+ onValueChange: handleAspectRatioChange,
1815
+ children: [
1816
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1817
+ /* @__PURE__ */ jsx(SelectContent, { children: ASPECT_RATIOS.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio.value, children: ratio.label }, ratio.value)) })
1818
+ ]
1819
+ }
1820
+ )
1646
1821
  ] })
1647
1822
  ] }),
1648
1823
  /* @__PURE__ */ jsxs("div", { children: [
@@ -1702,27 +1877,35 @@ function MotionControlNodeComponent(props) {
1702
1877
  var MotionControlNode = memo(MotionControlNodeComponent);
1703
1878
  var TTS_ENABLED = process.env.NEXT_PUBLIC_TTS_ENABLED === "true";
1704
1879
  var PROVIDERS = [
1705
- { value: "elevenlabs", label: "ElevenLabs" },
1706
- { value: "openai", label: "OpenAI" }
1880
+ { label: "ElevenLabs", value: "elevenlabs" },
1881
+ { label: "OpenAI", value: "openai" }
1707
1882
  ];
1708
1883
  var VOICES = [
1709
- { value: "rachel", label: "Rachel", description: "American female, calm" },
1710
- { value: "drew", label: "Drew", description: "American male, confident" },
1711
- { value: "clyde", label: "Clyde", description: "American male, war veteran" },
1712
- { value: "paul", label: "Paul", description: "American male, narrative" },
1713
- { value: "domi", label: "Domi", description: "American female, assertive" },
1714
- { value: "dave", label: "Dave", description: "British male, conversational" },
1715
- { value: "fin", label: "Fin", description: "Irish male, sailor" },
1716
- { value: "sarah", label: "Sarah", description: "American female, soft" },
1717
- { value: "antoni", label: "Antoni", description: "American male, friendly" },
1718
- { value: "thomas", label: "Thomas", description: "American male, calm" },
1719
- { value: "charlie", label: "Charlie", description: "Australian male, casual" },
1720
- { value: "emily", label: "Emily", description: "American female, calm" },
1721
- { value: "dorothy", label: "Dorothy", description: "British female, pleasant" },
1722
- { value: "josh", label: "Josh", description: "American male, deep" },
1723
- { value: "arnold", label: "Arnold", description: "American male, narrator" },
1724
- { value: "adam", label: "Adam", description: "American male, deep" },
1725
- { value: "sam", label: "Sam", description: "American male, raspy" }
1884
+ { description: "American female, calm", label: "Rachel", value: "rachel" },
1885
+ { description: "American male, confident", label: "Drew", value: "drew" },
1886
+ { description: "American male, war veteran", label: "Clyde", value: "clyde" },
1887
+ { description: "American male, narrative", label: "Paul", value: "paul" },
1888
+ { description: "American female, assertive", label: "Domi", value: "domi" },
1889
+ { description: "British male, conversational", label: "Dave", value: "dave" },
1890
+ { description: "Irish male, sailor", label: "Fin", value: "fin" },
1891
+ { description: "American female, soft", label: "Sarah", value: "sarah" },
1892
+ { description: "American male, friendly", label: "Antoni", value: "antoni" },
1893
+ { description: "American male, calm", label: "Thomas", value: "thomas" },
1894
+ {
1895
+ description: "Australian male, casual",
1896
+ label: "Charlie",
1897
+ value: "charlie"
1898
+ },
1899
+ { description: "American female, calm", label: "Emily", value: "emily" },
1900
+ {
1901
+ description: "British female, pleasant",
1902
+ label: "Dorothy",
1903
+ value: "dorothy"
1904
+ },
1905
+ { description: "American male, deep", label: "Josh", value: "josh" },
1906
+ { description: "American male, narrator", label: "Arnold", value: "arnold" },
1907
+ { description: "American male, deep", label: "Adam", value: "adam" },
1908
+ { description: "American male, raspy", label: "Sam", value: "sam" }
1726
1909
  ];
1727
1910
  function TextToSpeechNodeComponent(props) {
1728
1911
  const { id, type, data } = props;
@@ -1735,7 +1918,9 @@ function TextToSpeechNodeComponent(props) {
1735
1918
  });
1736
1919
  const handleProviderChange = useCallback(
1737
1920
  (value) => {
1738
- updateNodeData(id, { provider: value });
1921
+ updateNodeData(id, {
1922
+ provider: value
1923
+ });
1739
1924
  },
1740
1925
  [id, updateNodeData]
1741
1926
  );
@@ -1783,9 +1968,11 @@ function TextToSpeechNodeComponent(props) {
1783
1968
  /* @__PURE__ */ jsxs("div", { className: "text-amber-500", children: [
1784
1969
  /* @__PURE__ */ jsx("p", { className: "font-medium", children: "ElevenLabs not configured" }),
1785
1970
  /* @__PURE__ */ jsxs("p", { className: "text-amber-500/80 mt-0.5", children: [
1786
- "Set ",
1971
+ "Set",
1972
+ " ",
1787
1973
  /* @__PURE__ */ jsx("code", { className: "bg-amber-500/20 px-1 rounded", children: "ELEVENLABS_API_KEY" }),
1788
- " in API and",
1974
+ " ",
1975
+ "in API and",
1789
1976
  " ",
1790
1977
  /* @__PURE__ */ jsx("code", { className: "bg-amber-500/20 px-1 rounded", children: "NEXT_PUBLIC_TTS_ENABLED=true" }),
1791
1978
  " ",
@@ -1795,10 +1982,17 @@ function TextToSpeechNodeComponent(props) {
1795
1982
  ] }),
1796
1983
  /* @__PURE__ */ jsxs("div", { children: [
1797
1984
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Provider" }),
1798
- /* @__PURE__ */ jsxs(Select, { value: nodeData.provider, onValueChange: handleProviderChange, children: [
1799
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1800
- /* @__PURE__ */ jsx(SelectContent, { children: PROVIDERS.map((provider) => /* @__PURE__ */ jsx(SelectItem, { value: provider.value, children: provider.label }, provider.value)) })
1801
- ] })
1985
+ /* @__PURE__ */ jsxs(
1986
+ Select,
1987
+ {
1988
+ value: nodeData.provider,
1989
+ onValueChange: handleProviderChange,
1990
+ children: [
1991
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1992
+ /* @__PURE__ */ jsx(SelectContent, { children: PROVIDERS.map((provider) => /* @__PURE__ */ jsx(SelectItem, { value: provider.value, children: provider.label }, provider.value)) })
1993
+ ]
1994
+ }
1995
+ )
1802
1996
  ] }),
1803
1997
  /* @__PURE__ */ jsxs("div", { children: [
1804
1998
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Voice" }),
@@ -1896,15 +2090,15 @@ function TextToSpeechNodeComponent(props) {
1896
2090
  }
1897
2091
  var TextToSpeechNode = memo(TextToSpeechNodeComponent);
1898
2092
  var LANGUAGES = [
1899
- { value: "auto", label: "Auto-detect" },
1900
- { value: "en", label: "English" },
1901
- { value: "es", label: "Spanish" },
1902
- { value: "fr", label: "French" },
1903
- { value: "de", label: "German" },
1904
- { value: "ja", label: "Japanese" },
1905
- { value: "zh", label: "Chinese" },
1906
- { value: "ko", label: "Korean" },
1907
- { value: "pt", label: "Portuguese" }
2093
+ { label: "Auto-detect", value: "auto" },
2094
+ { label: "English", value: "en" },
2095
+ { label: "Spanish", value: "es" },
2096
+ { label: "French", value: "fr" },
2097
+ { label: "German", value: "de" },
2098
+ { label: "Japanese", value: "ja" },
2099
+ { label: "Chinese", value: "zh" },
2100
+ { label: "Korean", value: "ko" },
2101
+ { label: "Portuguese", value: "pt" }
1908
2102
  ];
1909
2103
  function TranscribeNodeComponent(props) {
1910
2104
  const { id, type, data } = props;
@@ -1918,7 +2112,9 @@ function TranscribeNodeComponent(props) {
1918
2112
  });
1919
2113
  const handleLanguageChange = useCallback(
1920
2114
  (value) => {
1921
- updateNodeData(id, { language: value });
2115
+ updateNodeData(id, {
2116
+ language: value
2117
+ });
1922
2118
  },
1923
2119
  [id, updateNodeData]
1924
2120
  );
@@ -1938,17 +2134,33 @@ function TranscribeNodeComponent(props) {
1938
2134
  openNodeDetailModal(id, "preview");
1939
2135
  }, [id, openNodeDetailModal]);
1940
2136
  const headerActions = useMemo(
1941
- () => nodeData.outputText ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
2137
+ () => nodeData.outputText ? /* @__PURE__ */ jsx(
2138
+ Button,
2139
+ {
2140
+ variant: "ghost",
2141
+ size: "icon-sm",
2142
+ onClick: handleExpand,
2143
+ title: "Expand preview",
2144
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
2145
+ }
2146
+ ) : null,
1942
2147
  [nodeData.outputText, handleExpand]
1943
2148
  );
1944
2149
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1945
2150
  /* @__PURE__ */ jsx("div", { className: "text-xs text-[var(--muted-foreground)]", children: "Using: Whisper Large V3" }),
1946
2151
  /* @__PURE__ */ jsxs("div", { children: [
1947
2152
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Language" }),
1948
- /* @__PURE__ */ jsxs(Select, { value: nodeData.language, onValueChange: handleLanguageChange, children: [
1949
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1950
- /* @__PURE__ */ jsx(SelectContent, { children: LANGUAGES.map((lang) => /* @__PURE__ */ jsx(SelectItem, { value: lang.value, children: lang.label }, lang.value)) })
1951
- ] })
2153
+ /* @__PURE__ */ jsxs(
2154
+ Select,
2155
+ {
2156
+ value: nodeData.language,
2157
+ onValueChange: handleLanguageChange,
2158
+ children: [
2159
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
2160
+ /* @__PURE__ */ jsx(SelectContent, { children: LANGUAGES.map((lang) => /* @__PURE__ */ jsx(SelectItem, { value: lang.value, children: lang.label }, lang.value)) })
2161
+ ]
2162
+ }
2163
+ )
1952
2164
  ] }),
1953
2165
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 nodrag", children: [
1954
2166
  /* @__PURE__ */ jsx(
@@ -2010,22 +2222,24 @@ function VideoGenNodeComponent(props) {
2010
2222
  const openNodeDetailModal = useUIStore((state) => state.openNodeDetailModal);
2011
2223
  const { handleGenerate, handleStop } = useNodeExecution(id);
2012
2224
  const { canGenerate } = useCanGenerate({
2225
+ inputSchema: nodeData.selectedModel?.inputSchema,
2013
2226
  nodeId: id,
2014
2227
  nodeType: type,
2015
- inputSchema: nodeData.selectedModel?.inputSchema,
2016
2228
  schemaParams: nodeData.schemaParams
2017
2229
  });
2018
2230
  const [isModelBrowserOpen, setIsModelBrowserOpen] = useState(false);
2019
- const { handleModelSelect } = useModelSelection({
2020
- nodeId: id,
2021
- modelMap: VIDEO_MODEL_MAP,
2022
- fallbackModel: DEFAULT_VIDEO_MODEL
2023
- });
2231
+ const { handleModelSelect } = useModelSelection(
2232
+ {
2233
+ fallbackModel: DEFAULT_VIDEO_MODEL,
2234
+ modelMap: VIDEO_MODEL_MAP,
2235
+ nodeId: id
2236
+ }
2237
+ );
2024
2238
  useAutoLoadModelSchema({
2025
2239
  currentModel: nodeData.model,
2026
- selectedModel: nodeData.selectedModel,
2027
2240
  modelIdMap: VIDEO_MODEL_ID_MAP,
2028
- onModelSelect: handleModelSelect
2241
+ onModelSelect: handleModelSelect,
2242
+ selectedModel: nodeData.selectedModel
2029
2243
  });
2030
2244
  const {
2031
2245
  schemaProperties,
@@ -2035,8 +2249,8 @@ function VideoGenNodeComponent(props) {
2035
2249
  componentSchemas
2036
2250
  } = useAIGenNode({
2037
2251
  nodeId: id,
2038
- selectedModel: nodeData.selectedModel,
2039
- schemaParams: nodeData.schemaParams
2252
+ schemaParams: nodeData.schemaParams,
2253
+ selectedModel: nodeData.selectedModel
2040
2254
  });
2041
2255
  const handleExpand = useCallback(() => {
2042
2256
  openNodeDetailModal(id, "preview");
@@ -2048,14 +2262,14 @@ function VideoGenNodeComponent(props) {
2048
2262
  const isProcessing = nodeData.status === "processing";
2049
2263
  const handleModelBrowse = useCallback(() => setIsModelBrowserOpen(true), []);
2050
2264
  const { titleElement, headerActions } = useAIGenNodeHeader({
2051
- modelDisplayName,
2052
- isProcessing,
2053
2265
  canGenerate,
2054
2266
  hasOutput: !!nodeData.outputVideo,
2055
- onModelBrowse: handleModelBrowse,
2267
+ isProcessing,
2268
+ modelDisplayName,
2269
+ onExpand: handleExpand,
2056
2270
  onGenerate: handleGenerate,
2057
- onStop: handleStop,
2058
- onExpand: handleExpand
2271
+ onModelBrowse: handleModelBrowse,
2272
+ onStop: handleStop
2059
2273
  });
2060
2274
  const disabledInputs = useMemo(() => {
2061
2275
  if (modelSupportsImageInput) return void 0;
@@ -2133,7 +2347,9 @@ function VoiceChangeNodeComponent(props) {
2133
2347
  const handlePreserveOriginalChange = useCallback(
2134
2348
  (checked) => {
2135
2349
  if (typeof checked === "boolean") {
2136
- updateNodeData(id, { preserveOriginalAudio: checked });
2350
+ updateNodeData(id, {
2351
+ preserveOriginalAudio: checked
2352
+ });
2137
2353
  }
2138
2354
  },
2139
2355
  [id, updateNodeData]
@@ -2152,7 +2368,16 @@ function VoiceChangeNodeComponent(props) {
2152
2368
  openNodeDetailModal(id, "preview");
2153
2369
  }, [id, openNodeDetailModal]);
2154
2370
  const headerActions = useMemo(
2155
- () => nodeData.outputVideo ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
2371
+ () => nodeData.outputVideo ? /* @__PURE__ */ jsx(
2372
+ Button,
2373
+ {
2374
+ variant: "ghost",
2375
+ size: "icon-sm",
2376
+ onClick: handleExpand,
2377
+ title: "Expand preview",
2378
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
2379
+ }
2380
+ ) : null,
2156
2381
  [nodeData.outputVideo, handleExpand]
2157
2382
  );
2158
2383
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
@@ -2238,291 +2463,43 @@ function VoiceChangeNodeComponent(props) {
2238
2463
  ] }) });
2239
2464
  }
2240
2465
  var VoiceChangeNode = memo(VoiceChangeNodeComponent);
2241
- var PROMPT_NODE_TYPES = ["prompt"];
2242
- function NodeDetailModal() {
2243
- const { activeModal, nodeDetailNodeId, nodeDetailStartIndex, closeNodeDetailModal } = useUIStore();
2244
- const { getNodeById } = useWorkflowStore();
2245
- const { openEditor } = usePromptEditorStore();
2246
- const [zoomLevel, setZoomLevel] = useState(1);
2247
- const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
2248
- const [isPanning, setIsPanning] = useState(false);
2249
- const [panStart, setPanStart] = useState({ x: 0, y: 0 });
2250
- const [currentIndex, setCurrentIndex] = useState(0);
2251
- const node = useMemo(() => {
2252
- if (!nodeDetailNodeId) return null;
2253
- return getNodeById(nodeDetailNodeId);
2254
- }, [nodeDetailNodeId, getNodeById]);
2255
- useEffect(() => {
2256
- if (activeModal !== "nodeDetail" || !node) return;
2257
- if (PROMPT_NODE_TYPES.includes(node.type)) {
2258
- const promptData = node.data;
2259
- closeNodeDetailModal();
2260
- openEditor(node.id, promptData.prompt ?? "");
2261
- }
2262
- }, [activeModal, node, closeNodeDetailModal, openEditor]);
2263
- const mediaInfo = useMemo(() => {
2264
- if (!node) return { url: null, type: null };
2265
- return getMediaFromNode(node.type, node.data);
2266
- }, [node]);
2267
- const nodeDef = useMemo(() => {
2268
- if (!node) return null;
2269
- return NODE_DEFINITIONS[node.type];
2270
- }, [node]);
2271
- const imageUrls = mediaInfo.urls ?? [];
2272
- const hasMultipleImages = imageUrls.length > 1;
2273
- const displayUrl = hasMultipleImages ? imageUrls[currentIndex] ?? mediaInfo.url : mediaInfo.url;
2274
- const goToPrevious = useCallback(() => {
2275
- setCurrentIndex((prev) => Math.max(prev - 1, 0));
2276
- setZoomLevel(1);
2277
- setPanOffset({ x: 0, y: 0 });
2278
- }, []);
2279
- const goToNext = useCallback(() => {
2280
- setCurrentIndex((prev) => Math.min(prev + 1, imageUrls.length - 1));
2281
- setZoomLevel(1);
2282
- setPanOffset({ x: 0, y: 0 });
2283
- }, [imageUrls.length]);
2284
- useEffect(() => {
2285
- setZoomLevel(1);
2286
- setPanOffset({ x: 0, y: 0 });
2287
- setCurrentIndex(nodeDetailStartIndex);
2288
- }, [nodeDetailNodeId, nodeDetailStartIndex]);
2289
- useEffect(() => {
2290
- const handleKeyDown = (e) => {
2291
- if (activeModal !== "nodeDetail") return;
2292
- if (e.key === "Escape") {
2293
- closeNodeDetailModal();
2294
- }
2295
- if (e.key === "+" || e.key === "=") {
2296
- setZoomLevel((prev) => Math.min(prev + 0.25, 4));
2297
- }
2298
- if (e.key === "-") {
2299
- setZoomLevel((prev) => Math.max(prev - 0.25, 0.25));
2300
- }
2301
- if (e.key === "0") {
2302
- setZoomLevel(1);
2303
- setPanOffset({ x: 0, y: 0 });
2304
- }
2305
- if (e.key === "ArrowLeft") {
2306
- goToPrevious();
2307
- }
2308
- if (e.key === "ArrowRight") {
2309
- goToNext();
2310
- }
2311
- };
2312
- window.addEventListener("keydown", handleKeyDown);
2313
- return () => window.removeEventListener("keydown", handleKeyDown);
2314
- }, [activeModal, closeNodeDetailModal, goToPrevious, goToNext]);
2315
- const handleMouseDown = useCallback(
2466
+ var INPUT_TYPES = [
2467
+ { label: "Image", value: "image" },
2468
+ { label: "Video", value: "video" },
2469
+ { label: "Text", value: "text" },
2470
+ { label: "Audio", value: "audio" },
2471
+ { label: "Number", value: "number" }
2472
+ ];
2473
+ function WorkflowInputNodeComponent(props) {
2474
+ const { id, data } = props;
2475
+ const nodeData = data;
2476
+ const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
2477
+ const handleNameChange = useCallback(
2316
2478
  (e) => {
2317
- if (zoomLevel > 1) {
2318
- setIsPanning(true);
2319
- setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
2320
- }
2479
+ updateNodeData(id, {
2480
+ inputName: e.target.value,
2481
+ label: `Input: ${e.target.value}`
2482
+ });
2321
2483
  },
2322
- [zoomLevel, panOffset]
2484
+ [id, updateNodeData]
2323
2485
  );
2324
- const handleMouseMove = useCallback(
2325
- (e) => {
2326
- if (isPanning) {
2327
- setPanOffset({
2328
- x: e.clientX - panStart.x,
2329
- y: e.clientY - panStart.y
2486
+ const handleTypeChange = useCallback(
2487
+ (value) => {
2488
+ updateNodeData(id, {
2489
+ inputType: value
2490
+ });
2491
+ },
2492
+ [id, updateNodeData]
2493
+ );
2494
+ const handleRequiredChange = useCallback(
2495
+ (checked) => {
2496
+ if (typeof checked === "boolean") {
2497
+ updateNodeData(id, {
2498
+ required: checked
2330
2499
  });
2331
2500
  }
2332
2501
  },
2333
- [isPanning, panStart]
2334
- );
2335
- const handleMouseUp = useCallback(() => {
2336
- setIsPanning(false);
2337
- }, []);
2338
- const handleDownload = useCallback(() => {
2339
- const url = displayUrl ?? mediaInfo.url;
2340
- if (!url) return;
2341
- const link = document.createElement("a");
2342
- link.href = url;
2343
- const suffix = hasMultipleImages ? `_${currentIndex + 1}` : "";
2344
- link.download = `${node?.data.label || "output"}${suffix}.${mediaInfo.type === "video" ? "mp4" : "png"}`;
2345
- document.body.appendChild(link);
2346
- link.click();
2347
- document.body.removeChild(link);
2348
- }, [displayUrl, mediaInfo, node, hasMultipleImages, currentIndex]);
2349
- if (activeModal !== "nodeDetail" || !node || !nodeDef) {
2350
- return null;
2351
- }
2352
- if (PROMPT_NODE_TYPES.includes(node.type)) {
2353
- return null;
2354
- }
2355
- const nodeData = node.data;
2356
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2357
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 bg-black/80", onClick: closeNodeDetailModal }),
2358
- /* @__PURE__ */ jsxs("div", { className: "fixed inset-4 z-50 flex flex-col bg-card rounded-lg border border-border shadow-xl overflow-hidden", children: [
2359
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
2360
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2361
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-foreground", children: nodeData.label }),
2362
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground bg-secondary px-2 py-0.5 rounded", children: nodeDef.label })
2363
- ] }),
2364
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2365
- displayUrl && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [
2366
- /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
2367
- "Download"
2368
- ] }),
2369
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: closeNodeDetailModal, children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" }) })
2370
- ] })
2371
- ] }),
2372
- /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxs(
2373
- "div",
2374
- {
2375
- className: "relative w-full h-full flex items-center justify-center bg-background overflow-hidden",
2376
- onMouseDown: handleMouseDown,
2377
- onMouseMove: handleMouseMove,
2378
- onMouseUp: handleMouseUp,
2379
- onMouseLeave: handleMouseUp,
2380
- style: { cursor: zoomLevel > 1 ? isPanning ? "grabbing" : "grab" : "default" },
2381
- children: [
2382
- displayUrl ? /* @__PURE__ */ jsxs(
2383
- "div",
2384
- {
2385
- className: "transition-transform duration-100",
2386
- style: {
2387
- transform: `scale(${zoomLevel}) translate(${panOffset.x / zoomLevel}px, ${panOffset.y / zoomLevel}px)`
2388
- },
2389
- children: [
2390
- mediaInfo.type === "image" && /* @__PURE__ */ jsx(
2391
- Image4,
2392
- {
2393
- src: displayUrl,
2394
- alt: nodeData.label,
2395
- width: 800,
2396
- height: 600,
2397
- className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] object-contain rounded-lg",
2398
- unoptimized: true
2399
- }
2400
- ),
2401
- mediaInfo.type === "video" && /* @__PURE__ */ jsx(
2402
- "video",
2403
- {
2404
- src: displayUrl,
2405
- controls: true,
2406
- autoPlay: true,
2407
- loop: true,
2408
- className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] rounded-lg"
2409
- }
2410
- )
2411
- ]
2412
- }
2413
- ) : /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-center", children: [
2414
- /* @__PURE__ */ jsx("p", { className: "text-lg", children: "No preview available" }),
2415
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: "Generate content to see the preview" })
2416
- ] }),
2417
- hasMultipleImages && currentIndex > 0 && /* @__PURE__ */ jsx(
2418
- Button,
2419
- {
2420
- variant: "ghost",
2421
- size: "icon-sm",
2422
- onClick: goToPrevious,
2423
- className: "absolute left-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
2424
- title: "Previous image (\u2190)",
2425
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
2426
- }
2427
- ),
2428
- hasMultipleImages && currentIndex < imageUrls.length - 1 && /* @__PURE__ */ jsx(
2429
- Button,
2430
- {
2431
- variant: "ghost",
2432
- size: "icon-sm",
2433
- onClick: goToNext,
2434
- className: "absolute right-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
2435
- title: "Next image (\u2192)",
2436
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
2437
- }
2438
- ),
2439
- hasMultipleImages && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 bg-card/80 border border-border rounded-full px-3 py-1 text-xs text-muted-foreground shadow-md", children: [
2440
- currentIndex + 1,
2441
- " / ",
2442
- imageUrls.length
2443
- ] }),
2444
- displayUrl && mediaInfo.type === "image" && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 right-4 flex items-center gap-2 bg-card border border-border rounded-lg p-1", children: [
2445
- /* @__PURE__ */ jsx(
2446
- Button,
2447
- {
2448
- variant: "ghost",
2449
- size: "icon-sm",
2450
- onClick: () => setZoomLevel((prev) => Math.max(prev - 0.25, 0.25)),
2451
- title: "Zoom out (-)",
2452
- children: /* @__PURE__ */ jsx(ZoomOut, { className: "w-4 h-4" })
2453
- }
2454
- ),
2455
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground w-12 text-center", children: [
2456
- Math.round(zoomLevel * 100),
2457
- "%"
2458
- ] }),
2459
- /* @__PURE__ */ jsx(
2460
- Button,
2461
- {
2462
- variant: "ghost",
2463
- size: "icon-sm",
2464
- onClick: () => setZoomLevel((prev) => Math.min(prev + 0.25, 4)),
2465
- title: "Zoom in (+)",
2466
- children: /* @__PURE__ */ jsx(ZoomIn, { className: "w-4 h-4" })
2467
- }
2468
- ),
2469
- /* @__PURE__ */ jsx(
2470
- Button,
2471
- {
2472
- variant: "ghost",
2473
- size: "sm",
2474
- onClick: () => {
2475
- setZoomLevel(1);
2476
- setPanOffset({ x: 0, y: 0 });
2477
- },
2478
- title: "Reset zoom (0)",
2479
- children: "Reset"
2480
- }
2481
- )
2482
- ] })
2483
- ]
2484
- }
2485
- ) })
2486
- ] })
2487
- ] });
2488
- }
2489
- var INPUT_TYPES = [
2490
- { value: "image", label: "Image" },
2491
- { value: "video", label: "Video" },
2492
- { value: "text", label: "Text" },
2493
- { value: "audio", label: "Audio" },
2494
- { value: "number", label: "Number" }
2495
- ];
2496
- function WorkflowInputNodeComponent(props) {
2497
- const { id, data } = props;
2498
- const nodeData = data;
2499
- const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
2500
- const handleNameChange = useCallback(
2501
- (e) => {
2502
- updateNodeData(id, {
2503
- inputName: e.target.value,
2504
- label: `Input: ${e.target.value}`
2505
- });
2506
- },
2507
- [id, updateNodeData]
2508
- );
2509
- const handleTypeChange = useCallback(
2510
- (value) => {
2511
- updateNodeData(id, {
2512
- inputType: value
2513
- });
2514
- },
2515
- [id, updateNodeData]
2516
- );
2517
- const handleRequiredChange = useCallback(
2518
- (checked) => {
2519
- if (typeof checked === "boolean") {
2520
- updateNodeData(id, {
2521
- required: checked
2522
- });
2523
- }
2524
- },
2525
- [id, updateNodeData]
2502
+ [id, updateNodeData]
2526
2503
  );
2527
2504
  const handleDescriptionChange = useCallback(
2528
2505
  (e) => {
@@ -2549,10 +2526,17 @@ function WorkflowInputNodeComponent(props) {
2549
2526
  ] }),
2550
2527
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2551
2528
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Data Type" }),
2552
- /* @__PURE__ */ jsxs(Select, { value: nodeData.inputType || "image", onValueChange: handleTypeChange, children: [
2553
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
2554
- /* @__PURE__ */ jsx(SelectContent, { children: INPUT_TYPES.map((type) => /* @__PURE__ */ jsx(SelectItem, { value: type.value, children: type.label }, type.value)) })
2555
- ] })
2529
+ /* @__PURE__ */ jsxs(
2530
+ Select,
2531
+ {
2532
+ value: nodeData.inputType || "image",
2533
+ onValueChange: handleTypeChange,
2534
+ children: [
2535
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
2536
+ /* @__PURE__ */ jsx(SelectContent, { children: INPUT_TYPES.map((type) => /* @__PURE__ */ jsx(SelectItem, { value: type.value, children: type.label }, type.value)) })
2537
+ ]
2538
+ }
2539
+ )
2556
2540
  ] }),
2557
2541
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 nodrag", children: [
2558
2542
  /* @__PURE__ */ jsx(
@@ -2587,11 +2571,11 @@ function WorkflowInputNodeComponent(props) {
2587
2571
  }
2588
2572
  var WorkflowInputNode = memo(WorkflowInputNodeComponent);
2589
2573
  var OUTPUT_TYPES = [
2590
- { value: "image", label: "Image" },
2591
- { value: "video", label: "Video" },
2592
- { value: "text", label: "Text" },
2593
- { value: "audio", label: "Audio" },
2594
- { value: "number", label: "Number" }
2574
+ { label: "Image", value: "image" },
2575
+ { label: "Video", value: "video" },
2576
+ { label: "Text", value: "text" },
2577
+ { label: "Audio", value: "audio" },
2578
+ { label: "Number", value: "number" }
2595
2579
  ];
2596
2580
  function WorkflowOutputNodeComponent(props) {
2597
2581
  const { id, data } = props;
@@ -2602,8 +2586,8 @@ function WorkflowOutputNodeComponent(props) {
2602
2586
  const handleNameChange = useCallback(
2603
2587
  (e) => {
2604
2588
  updateNodeData(id, {
2605
- outputName: e.target.value,
2606
- label: `Output: ${e.target.value}`
2589
+ label: `Output: ${e.target.value}`,
2590
+ outputName: e.target.value
2607
2591
  });
2608
2592
  },
2609
2593
  [id, updateNodeData]
@@ -2699,35 +2683,49 @@ function WorkflowOutputNodeComponent(props) {
2699
2683
  String(nodeData.inputValue).substring(0, 200),
2700
2684
  String(nodeData.inputValue).length > 200 && "..."
2701
2685
  ] }),
2702
- outputType === "audio" && /* @__PURE__ */ jsx("audio", { src: nodeData.inputValue, controls: true, className: "mt-1 w-full h-8" }),
2686
+ outputType === "audio" && /* @__PURE__ */ jsx(
2687
+ "audio",
2688
+ {
2689
+ src: nodeData.inputValue,
2690
+ controls: true,
2691
+ className: "mt-1 w-full h-8"
2692
+ }
2693
+ ),
2703
2694
  outputType === "number" && /* @__PURE__ */ jsx("div", { className: "mt-1 p-2 bg-secondary/50 rounded text-sm font-mono", children: String(nodeData.inputValue) })
2704
2695
  ] }),
2705
2696
  !hasValue && /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] text-muted-foreground text-center", children: "Connect an input to define the workflow output" }),
2706
2697
  /* @__PURE__ */ jsxs("div", { className: "text-[10px] text-muted-foreground text-center", children: [
2707
- "Input type: ",
2698
+ "Input type:",
2699
+ " ",
2708
2700
  /* @__PURE__ */ jsx("span", { className: "font-medium capitalize", children: outputType })
2709
2701
  ] })
2710
2702
  ] }) });
2711
2703
  }
2712
2704
  var WorkflowOutputNode = memo(WorkflowOutputNodeComponent);
2713
2705
  var HANDLE_COLORS2 = {
2706
+ audio: "var(--handle-audio)",
2714
2707
  image: "var(--handle-image)",
2715
- video: "var(--handle-video)",
2716
- text: "var(--handle-text)",
2717
2708
  number: "var(--handle-number)",
2718
- audio: "var(--handle-audio)"
2709
+ text: "var(--handle-text)",
2710
+ video: "var(--handle-video)"
2719
2711
  };
2720
2712
  var noopApi = {
2721
2713
  fetchReferencableWorkflows: async () => {
2722
- console.warn("[workflow-ui] WorkflowRefApi not configured: fetchReferencableWorkflows");
2714
+ console.warn(
2715
+ "[workflow-ui] WorkflowRefApi not configured: fetchReferencableWorkflows"
2716
+ );
2723
2717
  return [];
2724
2718
  },
2725
- validateReference: async () => {
2726
- console.warn("[workflow-ui] WorkflowRefApi not configured: validateReference");
2727
- },
2728
2719
  fetchWorkflowInterface: async () => {
2729
- console.warn("[workflow-ui] WorkflowRefApi not configured: fetchWorkflowInterface");
2720
+ console.warn(
2721
+ "[workflow-ui] WorkflowRefApi not configured: fetchWorkflowInterface"
2722
+ );
2730
2723
  return { inputs: [], outputs: [] };
2724
+ },
2725
+ validateReference: async () => {
2726
+ console.warn(
2727
+ "[workflow-ui] WorkflowRefApi not configured: validateReference"
2728
+ );
2731
2729
  }
2732
2730
  };
2733
2731
  var workflowRefApi = noopApi;
@@ -2767,11 +2765,11 @@ function WorkflowRefNodeComponent(props) {
2767
2765
  async (selectedId) => {
2768
2766
  if (!selectedId || selectedId === "none") {
2769
2767
  updateNodeData(id, {
2770
- referencedWorkflowId: null,
2771
- referencedWorkflowName: null,
2772
2768
  cachedInterface: null,
2773
2769
  inputMappings: {},
2774
- outputMappings: {}
2770
+ outputMappings: {},
2771
+ referencedWorkflowId: null,
2772
+ referencedWorkflowName: null
2775
2773
  });
2776
2774
  return;
2777
2775
  }
@@ -2794,12 +2792,12 @@ function WorkflowRefNodeComponent(props) {
2794
2792
  outputMappings[output.name] = null;
2795
2793
  }
2796
2794
  updateNodeData(id, {
2797
- referencedWorkflowId: selectedId,
2798
- referencedWorkflowName: selectedWorkflow.name,
2799
2795
  cachedInterface: selectedWorkflow.interface,
2800
2796
  inputMappings,
2797
+ label: `Subworkflow: ${selectedWorkflow.name}`,
2801
2798
  outputMappings,
2802
- label: `Subworkflow: ${selectedWorkflow.name}`
2799
+ referencedWorkflowId: selectedId,
2800
+ referencedWorkflowName: selectedWorkflow.name
2803
2801
  });
2804
2802
  setError(null);
2805
2803
  },
@@ -2812,13 +2810,17 @@ function WorkflowRefNodeComponent(props) {
2812
2810
  const response = await workflowRefApi.fetchWorkflowInterface(
2813
2811
  nodeData.referencedWorkflowId
2814
2812
  );
2815
- const inputMappings = { ...nodeData.inputMappings };
2813
+ const inputMappings = {
2814
+ ...nodeData.inputMappings
2815
+ };
2816
2816
  for (const input of response.inputs) {
2817
2817
  if (!(input.name in inputMappings)) {
2818
2818
  inputMappings[input.name] = null;
2819
2819
  }
2820
2820
  }
2821
- const outputMappings = { ...nodeData.outputMappings };
2821
+ const outputMappings = {
2822
+ ...nodeData.outputMappings
2823
+ };
2822
2824
  for (const output of response.outputs) {
2823
2825
  if (!(output.name in outputMappings)) {
2824
2826
  outputMappings[output.name] = null;
@@ -2841,21 +2843,17 @@ function WorkflowRefNodeComponent(props) {
2841
2843
  nodeData.outputMappings,
2842
2844
  updateNodeData
2843
2845
  ]);
2844
- const inputHandles = (nodeData.cachedInterface?.inputs ?? []).map(
2845
- (input) => ({
2846
- id: input.name,
2847
- type: input.type,
2848
- label: input.name,
2849
- required: input.required
2850
- })
2851
- );
2852
- const outputHandles = (nodeData.cachedInterface?.outputs ?? []).map(
2853
- (output) => ({
2854
- id: output.name,
2855
- type: output.type,
2856
- label: output.name
2857
- })
2858
- );
2846
+ const inputHandles = (nodeData.cachedInterface?.inputs ?? []).map((input) => ({
2847
+ id: input.name,
2848
+ label: input.name,
2849
+ required: input.required,
2850
+ type: input.type
2851
+ }));
2852
+ const outputHandles = (nodeData.cachedInterface?.outputs ?? []).map((output) => ({
2853
+ id: output.name,
2854
+ label: output.name,
2855
+ type: output.type
2856
+ }));
2859
2857
  const isSelected = selected;
2860
2858
  const hasWorkflow = !!nodeData.referencedWorkflowId;
2861
2859
  const isProcessing = nodeData.status === "processing";
@@ -2870,7 +2868,9 @@ function WorkflowRefNodeComponent(props) {
2870
2868
  ),
2871
2869
  style: {
2872
2870
  "--node-color": "var(--category-composition)",
2873
- ...isSelected && { "--tw-ring-color": "var(--category-composition)" }
2871
+ ...isSelected && {
2872
+ "--tw-ring-color": "var(--category-composition)"
2873
+ }
2874
2874
  },
2875
2875
  children: [
2876
2876
  inputHandles.map((input, index) => /* @__PURE__ */ jsx(
@@ -2881,8 +2881,8 @@ function WorkflowRefNodeComponent(props) {
2881
2881
  id: input.id,
2882
2882
  className: "!w-3 !h-3",
2883
2883
  style: {
2884
- top: `${(index + 1) / (inputHandles.length + 1) * 100}%`,
2885
- background: HANDLE_COLORS2[input.type]
2884
+ background: HANDLE_COLORS2[input.type],
2885
+ top: `${(index + 1) / (inputHandles.length + 1) * 100}%`
2886
2886
  },
2887
2887
  title: `${input.label} (${input.type})${input.required ? " - required" : ""}`
2888
2888
  },
@@ -2930,7 +2930,12 @@ function WorkflowRefNodeComponent(props) {
2930
2930
  onClick: handleRefreshInterface,
2931
2931
  disabled: isLoading,
2932
2932
  title: "Refresh interface",
2933
- children: /* @__PURE__ */ jsx(RefreshCw, { className: clsx("h-3 w-3", isLoading && "animate-spin") })
2933
+ children: /* @__PURE__ */ jsx(
2934
+ RefreshCw,
2935
+ {
2936
+ className: clsx("h-3 w-3", isLoading && "animate-spin")
2937
+ }
2938
+ )
2934
2939
  }
2935
2940
  )
2936
2941
  ] }),
@@ -2978,8 +2983,8 @@ function WorkflowRefNodeComponent(props) {
2978
2983
  id: output.id,
2979
2984
  className: "!w-3 !h-3",
2980
2985
  style: {
2981
- top: `${(index + 1) / (outputHandles.length + 1) * 100}%`,
2982
- background: HANDLE_COLORS2[output.type]
2986
+ background: HANDLE_COLORS2[output.type],
2987
+ top: `${(index + 1) / (outputHandles.length + 1) * 100}%`
2983
2988
  },
2984
2989
  title: `${output.label} (${output.type})`
2985
2990
  },
@@ -3008,8 +3013,8 @@ function AudioInputNodeComponent(props) {
3008
3013
  audio.onloadedmetadata = () => {
3009
3014
  updateNodeData(id, {
3010
3015
  audio: event.target?.result,
3011
- filename: file.name,
3012
3016
  duration: audio.duration,
3017
+ filename: file.name,
3013
3018
  source: "upload"
3014
3019
  });
3015
3020
  };
@@ -3022,8 +3027,8 @@ function AudioInputNodeComponent(props) {
3022
3027
  const handleRemoveAudio = useCallback(() => {
3023
3028
  updateNodeData(id, {
3024
3029
  audio: null,
3025
- filename: null,
3026
3030
  duration: null,
3031
+ filename: null,
3027
3032
  url: void 0
3028
3033
  });
3029
3034
  setUrlValue("");
@@ -3035,8 +3040,8 @@ function AudioInputNodeComponent(props) {
3035
3040
  audio.onloadedmetadata = () => {
3036
3041
  updateNodeData(id, {
3037
3042
  audio: urlValue,
3038
- filename: urlValue.split("/").pop() || "url-audio",
3039
3043
  duration: audio.duration,
3044
+ filename: urlValue.split("/").pop() || "url-audio",
3040
3045
  source: "url",
3041
3046
  url: urlValue
3042
3047
  });
@@ -3045,8 +3050,8 @@ function AudioInputNodeComponent(props) {
3045
3050
  audio.onerror = () => {
3046
3051
  updateNodeData(id, {
3047
3052
  audio: urlValue,
3048
- filename: urlValue.split("/").pop() || "url-audio",
3049
3053
  duration: null,
3054
+ filename: urlValue.split("/").pop() || "url-audio",
3050
3055
  source: "url",
3051
3056
  url: urlValue
3052
3057
  });
@@ -3096,7 +3101,16 @@ function AudioInputNodeComponent(props) {
3096
3101
  children: /* @__PURE__ */ jsx(Link, { className: "h-3.5 w-3.5" })
3097
3102
  }
3098
3103
  ),
3099
- nodeData.audio && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) })
3104
+ nodeData.audio && /* @__PURE__ */ jsx(
3105
+ Button,
3106
+ {
3107
+ variant: "ghost",
3108
+ size: "icon-sm",
3109
+ onClick: handleExpand,
3110
+ title: "Expand preview",
3111
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" })
3112
+ }
3113
+ )
3100
3114
  ] }),
3101
3115
  [showUrlInput, nodeData.audio, handleExpand]
3102
3116
  );
@@ -3183,39 +3197,48 @@ function ImageInputNodeComponent(props) {
3183
3197
  handleUrlSubmit,
3184
3198
  handleUrlKeyDown
3185
3199
  } = useMediaUpload({
3186
- nodeId: id,
3187
- mediaType: "image",
3188
- initialUrl: nodeData.url || "",
3189
- getMetadata: async (src) => {
3190
- const dims = await getImageDimensions(src);
3191
- return dims;
3192
- },
3200
+ buildRemoveUpdate: () => ({
3201
+ dimensions: null,
3202
+ filename: null,
3203
+ image: null,
3204
+ url: void 0
3205
+ }),
3193
3206
  buildUploadUpdate: (url, filename, metadata) => ({
3194
- image: url,
3195
- filename,
3196
3207
  dimensions: metadata,
3208
+ filename,
3209
+ image: url,
3197
3210
  source: "upload"
3198
3211
  }),
3199
3212
  buildUrlUpdate: (url, metadata) => ({
3200
- image: url,
3201
- filename: url.split("/").pop() || "url-image",
3202
3213
  dimensions: metadata,
3214
+ filename: url.split("/").pop() || "url-image",
3215
+ image: url,
3203
3216
  source: "url",
3204
3217
  url
3205
3218
  }),
3206
- buildRemoveUpdate: () => ({
3207
- image: null,
3208
- filename: null,
3209
- dimensions: null,
3210
- url: void 0
3211
- })
3219
+ getMetadata: async (src) => {
3220
+ const dims = await getImageDimensions(src);
3221
+ return dims;
3222
+ },
3223
+ initialUrl: nodeData.url || "",
3224
+ mediaType: "image",
3225
+ nodeId: id
3212
3226
  });
3213
3227
  const handleExpand = useCallback(() => {
3214
3228
  openNodeDetailModal(id, "preview");
3215
3229
  }, [id, openNodeDetailModal]);
3216
3230
  const headerActions = useMemo(
3217
3231
  () => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3218
- nodeData.image && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) }),
3232
+ nodeData.image && /* @__PURE__ */ jsx(
3233
+ Button,
3234
+ {
3235
+ variant: "ghost",
3236
+ size: "icon-sm",
3237
+ onClick: handleExpand,
3238
+ title: "Expand preview",
3239
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" })
3240
+ }
3241
+ ),
3219
3242
  /* @__PURE__ */ jsx(
3220
3243
  Button,
3221
3244
  {
@@ -3334,7 +3357,9 @@ function PromptConstructorNodeComponent(props) {
3334
3357
  }
3335
3358
  }, [nodeData.template, isEditing]);
3336
3359
  const availableVariables = useMemo(() => {
3337
- const connectedPromptNodes = edges.filter((e) => e.target === id && e.targetHandle === "text").map((e) => nodes.find((n) => n.id === e.source)).filter((n) => n !== void 0 && n.type === "prompt");
3360
+ const connectedPromptNodes = edges.filter((e) => e.target === id && e.targetHandle === "text").map((e) => nodes.find((n) => n.id === e.source)).filter(
3361
+ (n) => n !== void 0 && n.type === "prompt"
3362
+ );
3338
3363
  const vars = [];
3339
3364
  connectedPromptNodes.forEach((promptNode) => {
3340
3365
  const promptData = promptNode.data;
@@ -3342,8 +3367,8 @@ function PromptConstructorNodeComponent(props) {
3342
3367
  if (variableName) {
3343
3368
  vars.push({
3344
3369
  name: variableName,
3345
- value: promptData.prompt || "",
3346
- nodeId: promptNode.id
3370
+ nodeId: promptNode.id,
3371
+ value: promptData.prompt || ""
3347
3372
  });
3348
3373
  }
3349
3374
  });
@@ -3360,10 +3385,10 @@ function PromptConstructorNodeComponent(props) {
3360
3385
  closeAutocomplete
3361
3386
  } = usePromptAutocomplete({
3362
3387
  availableVariables,
3363
- textareaRef,
3364
3388
  localTemplate,
3389
+ onTemplateCommit: (newTemplate) => updateNodeData(id, { template: newTemplate }),
3365
3390
  setLocalTemplate,
3366
- onTemplateCommit: (newTemplate) => updateNodeData(id, { template: newTemplate })
3391
+ textareaRef
3367
3392
  });
3368
3393
  const unresolvedVars = useMemo(() => {
3369
3394
  const varPattern = /@(\w+)/g;
@@ -3392,16 +3417,26 @@ function PromptConstructorNodeComponent(props) {
3392
3417
  });
3393
3418
  const outputValue = resolved || null;
3394
3419
  if (outputValue !== nodeData.outputText) {
3395
- updateNodeData(id, { outputText: outputValue });
3420
+ updateNodeData(id, {
3421
+ outputText: outputValue
3422
+ });
3396
3423
  }
3397
- }, [nodeData.template, availableVariables, id, updateNodeData, nodeData.outputText]);
3424
+ }, [
3425
+ nodeData.template,
3426
+ availableVariables,
3427
+ id,
3428
+ updateNodeData,
3429
+ nodeData.outputText
3430
+ ]);
3398
3431
  const handleFocus = useCallback(() => {
3399
3432
  setIsEditing(true);
3400
3433
  }, []);
3401
3434
  const handleBlur = useCallback(() => {
3402
3435
  setIsEditing(false);
3403
3436
  if (localTemplate !== nodeData.template) {
3404
- updateNodeData(id, { template: localTemplate });
3437
+ updateNodeData(id, {
3438
+ template: localTemplate
3439
+ });
3405
3440
  }
3406
3441
  setTimeout(() => closeAutocomplete(), 200);
3407
3442
  }, [id, localTemplate, nodeData.template, updateNodeData, closeAutocomplete]);
@@ -3432,8 +3467,8 @@ function PromptConstructorNodeComponent(props) {
3432
3467
  {
3433
3468
  className: "absolute z-10 bg-popover border border-border rounded shadow-xl max-h-40 overflow-y-auto",
3434
3469
  style: {
3435
- top: autocompletePosition.top,
3436
- left: autocompletePosition.left
3470
+ left: autocompletePosition.left,
3471
+ top: autocompletePosition.top
3437
3472
  },
3438
3473
  children: filteredAutocompleteVars.map((variable, index) => /* @__PURE__ */ jsxs(
3439
3474
  "button",
@@ -3486,18 +3521,18 @@ function PromptNodeComponent(props) {
3486
3521
  if (!nodeData.prompt) return;
3487
3522
  openCreateModal({
3488
3523
  _id: "",
3489
- name: "",
3490
- description: "",
3491
- promptText: nodeData.prompt,
3492
- styleSettings: {},
3493
3524
  category: "custom",
3494
- tags: [],
3495
- useCount: 0,
3496
- isFeatured: false,
3497
- isSystem: false,
3498
- isDeleted: false,
3499
3525
  createdAt: "",
3500
- updatedAt: ""
3526
+ description: "",
3527
+ isDeleted: false,
3528
+ isFeatured: false,
3529
+ isSystem: false,
3530
+ name: "",
3531
+ promptText: nodeData.prompt,
3532
+ styleSettings: {},
3533
+ tags: [],
3534
+ updatedAt: "",
3535
+ useCount: 0
3501
3536
  });
3502
3537
  }, [nodeData.prompt, openCreateModal]);
3503
3538
  const handleExpand = useCallback(() => {
@@ -3505,7 +3540,16 @@ function PromptNodeComponent(props) {
3505
3540
  }, [id, nodeData.prompt, openEditor]);
3506
3541
  const titleElement = PromptPicker ? /* @__PURE__ */ jsx(PromptPicker, { onSelect: handleSelectFromLibrary, label: "Prompt" }) : /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: "Prompt" });
3507
3542
  const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
3508
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand editor", children: /* @__PURE__ */ jsx(Expand, { className: "w-3.5 h-3.5" }) }),
3543
+ /* @__PURE__ */ jsx(
3544
+ Button,
3545
+ {
3546
+ variant: "ghost",
3547
+ size: "icon-sm",
3548
+ onClick: handleExpand,
3549
+ title: "Expand editor",
3550
+ children: /* @__PURE__ */ jsx(Expand, { className: "w-3.5 h-3.5" })
3551
+ }
3552
+ ),
3509
3553
  /* @__PURE__ */ jsx(
3510
3554
  Button,
3511
3555
  {
@@ -3518,15 +3562,23 @@ function PromptNodeComponent(props) {
3518
3562
  }
3519
3563
  )
3520
3564
  ] });
3521
- return /* @__PURE__ */ jsx(BaseNode, { ...props, titleElement, headerActions, children: /* @__PURE__ */ jsx(
3522
- "textarea",
3565
+ return /* @__PURE__ */ jsx(
3566
+ BaseNode,
3523
3567
  {
3524
- value: nodeData.prompt || "",
3525
- onChange: handlePromptChange,
3526
- placeholder: "Enter your prompt...",
3527
- className: "nodrag nopan w-full flex-1 min-h-[80px] px-2 py-1.5 text-sm bg-background border border-border rounded resize-none focus:outline-none focus:ring-1 focus:ring-primary"
3568
+ ...props,
3569
+ titleElement,
3570
+ headerActions,
3571
+ children: /* @__PURE__ */ jsx(
3572
+ "textarea",
3573
+ {
3574
+ value: nodeData.prompt || "",
3575
+ onChange: handlePromptChange,
3576
+ placeholder: "Enter your prompt...",
3577
+ className: "nodrag nopan w-full flex-1 min-h-[80px] px-2 py-1.5 text-sm bg-background border border-border rounded resize-none focus:outline-none focus:ring-1 focus:ring-primary"
3578
+ }
3579
+ )
3528
3580
  }
3529
- ) });
3581
+ );
3530
3582
  }
3531
3583
  var PromptNode = memo(PromptNodeComponent);
3532
3584
  function VideoInputNodeComponent(props) {
@@ -3545,51 +3597,51 @@ function VideoInputNodeComponent(props) {
3545
3597
  handleUrlSubmit,
3546
3598
  handleUrlKeyDown
3547
3599
  } = useMediaUpload({
3548
- nodeId: id,
3549
- mediaType: "video",
3550
- initialUrl: nodeData.url || "",
3551
- getMetadata: async (src) => {
3552
- const meta = await getVideoMetadata(src);
3553
- return meta;
3554
- },
3600
+ buildRemoveUpdate: () => ({
3601
+ dimensions: null,
3602
+ duration: null,
3603
+ filename: null,
3604
+ url: void 0,
3605
+ video: null
3606
+ }),
3555
3607
  buildUploadUpdate: (url, filename, metadata) => {
3556
3608
  const meta = metadata;
3557
3609
  return {
3558
- video: url,
3559
- filename,
3560
- duration: meta.duration,
3561
3610
  dimensions: meta.dimensions,
3562
- source: "upload"
3611
+ duration: meta.duration,
3612
+ filename,
3613
+ source: "upload",
3614
+ video: url
3563
3615
  };
3564
3616
  },
3565
3617
  buildUrlUpdate: (url, metadata) => {
3566
3618
  if (metadata) {
3567
3619
  const meta = metadata;
3568
3620
  return {
3569
- video: url,
3570
- filename: url.split("/").pop() || "url-video",
3621
+ dimensions: { height: meta.height, width: meta.width },
3571
3622
  duration: meta.duration,
3572
- dimensions: { width: meta.width, height: meta.height },
3623
+ filename: url.split("/").pop() || "url-video",
3573
3624
  source: "url",
3574
- url
3625
+ url,
3626
+ video: url
3575
3627
  };
3576
3628
  }
3577
3629
  return {
3578
- video: url,
3579
- filename: url.split("/").pop() || "url-video",
3580
- duration: null,
3581
3630
  dimensions: null,
3631
+ duration: null,
3632
+ filename: url.split("/").pop() || "url-video",
3582
3633
  source: "url",
3583
- url
3634
+ url,
3635
+ video: url
3584
3636
  };
3585
3637
  },
3586
- buildRemoveUpdate: () => ({
3587
- video: null,
3588
- filename: null,
3589
- duration: null,
3590
- dimensions: null,
3591
- url: void 0
3592
- })
3638
+ getMetadata: async (src) => {
3639
+ const meta = await getVideoMetadata(src);
3640
+ return meta;
3641
+ },
3642
+ initialUrl: nodeData.url || "",
3643
+ mediaType: "video",
3644
+ nodeId: id
3593
3645
  });
3594
3646
  const formatDuration = (seconds) => {
3595
3647
  if (!seconds) return "";
@@ -3602,7 +3654,16 @@ function VideoInputNodeComponent(props) {
3602
3654
  }, [id, openNodeDetailModal]);
3603
3655
  const headerActions = useMemo(
3604
3656
  () => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
3605
- nodeData.video && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" }) }),
3657
+ nodeData.video && /* @__PURE__ */ jsx(
3658
+ Button,
3659
+ {
3660
+ variant: "ghost",
3661
+ size: "icon-sm",
3662
+ onClick: handleExpand,
3663
+ title: "Expand preview",
3664
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3.5 w-3.5" })
3665
+ }
3666
+ ),
3606
3667
  /* @__PURE__ */ jsx(
3607
3668
  Button,
3608
3669
  {
@@ -3702,6 +3763,278 @@ function VideoInputNodeComponent(props) {
3702
3763
  ] });
3703
3764
  }
3704
3765
  var VideoInputNode = memo(VideoInputNodeComponent);
3766
+ var PROMPT_NODE_TYPES = ["prompt"];
3767
+ function NodeDetailModal() {
3768
+ const {
3769
+ activeModal,
3770
+ nodeDetailNodeId,
3771
+ nodeDetailStartIndex,
3772
+ closeNodeDetailModal
3773
+ } = useUIStore();
3774
+ const { getNodeById } = useWorkflowStore();
3775
+ const { openEditor } = usePromptEditorStore();
3776
+ const [zoomLevel, setZoomLevel] = useState(1);
3777
+ const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
3778
+ const [isPanning, setIsPanning] = useState(false);
3779
+ const [panStart, setPanStart] = useState({ x: 0, y: 0 });
3780
+ const [currentIndex, setCurrentIndex] = useState(0);
3781
+ const node = useMemo(() => {
3782
+ if (!nodeDetailNodeId) return null;
3783
+ return getNodeById(nodeDetailNodeId);
3784
+ }, [nodeDetailNodeId, getNodeById]);
3785
+ useEffect(() => {
3786
+ if (activeModal !== "nodeDetail" || !node) return;
3787
+ if (PROMPT_NODE_TYPES.includes(node.type)) {
3788
+ const promptData = node.data;
3789
+ closeNodeDetailModal();
3790
+ openEditor(node.id, promptData.prompt ?? "");
3791
+ }
3792
+ }, [activeModal, node, closeNodeDetailModal, openEditor]);
3793
+ const mediaInfo = useMemo(() => {
3794
+ if (!node) return { type: null, url: null };
3795
+ return getMediaFromNode(
3796
+ node.type,
3797
+ node.data
3798
+ );
3799
+ }, [node]);
3800
+ const nodeDef = useMemo(() => {
3801
+ if (!node) return null;
3802
+ return NODE_DEFINITIONS[node.type];
3803
+ }, [node]);
3804
+ const imageUrls = mediaInfo.urls ?? [];
3805
+ const hasMultipleImages = imageUrls.length > 1;
3806
+ const displayUrl = hasMultipleImages ? imageUrls[currentIndex] ?? mediaInfo.url : mediaInfo.url;
3807
+ const goToPrevious = useCallback(() => {
3808
+ setCurrentIndex((prev) => Math.max(prev - 1, 0));
3809
+ setZoomLevel(1);
3810
+ setPanOffset({ x: 0, y: 0 });
3811
+ }, []);
3812
+ const goToNext = useCallback(() => {
3813
+ setCurrentIndex((prev) => Math.min(prev + 1, imageUrls.length - 1));
3814
+ setZoomLevel(1);
3815
+ setPanOffset({ x: 0, y: 0 });
3816
+ }, [imageUrls.length]);
3817
+ useEffect(() => {
3818
+ setZoomLevel(1);
3819
+ setPanOffset({ x: 0, y: 0 });
3820
+ setCurrentIndex(nodeDetailStartIndex);
3821
+ }, [nodeDetailNodeId, nodeDetailStartIndex]);
3822
+ useEffect(() => {
3823
+ const handleKeyDown = (e) => {
3824
+ if (activeModal !== "nodeDetail") return;
3825
+ if (e.key === "Escape") {
3826
+ closeNodeDetailModal();
3827
+ }
3828
+ if (e.key === "+" || e.key === "=") {
3829
+ setZoomLevel((prev) => Math.min(prev + 0.25, 4));
3830
+ }
3831
+ if (e.key === "-") {
3832
+ setZoomLevel((prev) => Math.max(prev - 0.25, 0.25));
3833
+ }
3834
+ if (e.key === "0") {
3835
+ setZoomLevel(1);
3836
+ setPanOffset({ x: 0, y: 0 });
3837
+ }
3838
+ if (e.key === "ArrowLeft") {
3839
+ goToPrevious();
3840
+ }
3841
+ if (e.key === "ArrowRight") {
3842
+ goToNext();
3843
+ }
3844
+ };
3845
+ window.addEventListener("keydown", handleKeyDown);
3846
+ return () => window.removeEventListener("keydown", handleKeyDown);
3847
+ }, [activeModal, closeNodeDetailModal, goToPrevious, goToNext]);
3848
+ const handleMouseDown = useCallback(
3849
+ (e) => {
3850
+ if (zoomLevel > 1) {
3851
+ setIsPanning(true);
3852
+ setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
3853
+ }
3854
+ },
3855
+ [zoomLevel, panOffset]
3856
+ );
3857
+ const handleMouseMove = useCallback(
3858
+ (e) => {
3859
+ if (isPanning) {
3860
+ setPanOffset({
3861
+ x: e.clientX - panStart.x,
3862
+ y: e.clientY - panStart.y
3863
+ });
3864
+ }
3865
+ },
3866
+ [isPanning, panStart]
3867
+ );
3868
+ const handleMouseUp = useCallback(() => {
3869
+ setIsPanning(false);
3870
+ }, []);
3871
+ const handleDownload = useCallback(() => {
3872
+ const url = displayUrl ?? mediaInfo.url;
3873
+ if (!url) return;
3874
+ const link = document.createElement("a");
3875
+ link.href = url;
3876
+ const suffix = hasMultipleImages ? `_${currentIndex + 1}` : "";
3877
+ link.download = `${node?.data.label || "output"}${suffix}.${mediaInfo.type === "video" ? "mp4" : "png"}`;
3878
+ document.body.appendChild(link);
3879
+ link.click();
3880
+ document.body.removeChild(link);
3881
+ }, [displayUrl, mediaInfo, node, hasMultipleImages, currentIndex]);
3882
+ if (activeModal !== "nodeDetail" || !node || !nodeDef) {
3883
+ return null;
3884
+ }
3885
+ if (PROMPT_NODE_TYPES.includes(node.type)) {
3886
+ return null;
3887
+ }
3888
+ const nodeData = node.data;
3889
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3890
+ /* @__PURE__ */ jsx(
3891
+ "div",
3892
+ {
3893
+ className: "fixed inset-0 z-50 bg-black/80",
3894
+ onClick: closeNodeDetailModal
3895
+ }
3896
+ ),
3897
+ /* @__PURE__ */ jsxs("div", { className: "fixed inset-4 z-50 flex flex-col bg-card rounded-lg border border-border shadow-xl overflow-hidden", children: [
3898
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-border", children: [
3899
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3900
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-medium text-foreground", children: nodeData.label }),
3901
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground bg-secondary px-2 py-0.5 rounded", children: nodeDef.label })
3902
+ ] }),
3903
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3904
+ displayUrl && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [
3905
+ /* @__PURE__ */ jsx(Download, { className: "h-4 w-4 mr-1" }),
3906
+ "Download"
3907
+ ] }),
3908
+ /* @__PURE__ */ jsx(
3909
+ Button,
3910
+ {
3911
+ variant: "ghost",
3912
+ size: "icon-sm",
3913
+ onClick: closeNodeDetailModal,
3914
+ children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
3915
+ }
3916
+ )
3917
+ ] })
3918
+ ] }),
3919
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxs(
3920
+ "div",
3921
+ {
3922
+ className: "relative w-full h-full flex items-center justify-center bg-background overflow-hidden",
3923
+ onMouseDown: handleMouseDown,
3924
+ onMouseMove: handleMouseMove,
3925
+ onMouseUp: handleMouseUp,
3926
+ onMouseLeave: handleMouseUp,
3927
+ style: {
3928
+ cursor: zoomLevel > 1 ? isPanning ? "grabbing" : "grab" : "default"
3929
+ },
3930
+ children: [
3931
+ displayUrl ? /* @__PURE__ */ jsxs(
3932
+ "div",
3933
+ {
3934
+ className: "transition-transform duration-100",
3935
+ style: {
3936
+ transform: `scale(${zoomLevel}) translate(${panOffset.x / zoomLevel}px, ${panOffset.y / zoomLevel}px)`
3937
+ },
3938
+ children: [
3939
+ mediaInfo.type === "image" && /* @__PURE__ */ jsx(
3940
+ Image4,
3941
+ {
3942
+ src: displayUrl,
3943
+ alt: nodeData.label,
3944
+ width: 800,
3945
+ height: 600,
3946
+ className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] object-contain rounded-lg",
3947
+ unoptimized: true
3948
+ }
3949
+ ),
3950
+ mediaInfo.type === "video" && /* @__PURE__ */ jsx(
3951
+ "video",
3952
+ {
3953
+ src: displayUrl,
3954
+ controls: true,
3955
+ autoPlay: true,
3956
+ loop: true,
3957
+ className: "max-h-[calc(100vh-200px)] max-w-[calc(100vw-100px)] rounded-lg"
3958
+ }
3959
+ )
3960
+ ]
3961
+ }
3962
+ ) : /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-center", children: [
3963
+ /* @__PURE__ */ jsx("p", { className: "text-lg", children: "No preview available" }),
3964
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-2", children: "Generate content to see the preview" })
3965
+ ] }),
3966
+ hasMultipleImages && currentIndex > 0 && /* @__PURE__ */ jsx(
3967
+ Button,
3968
+ {
3969
+ variant: "ghost",
3970
+ size: "icon-sm",
3971
+ onClick: goToPrevious,
3972
+ className: "absolute left-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
3973
+ title: "Previous image (\u2190)",
3974
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
3975
+ }
3976
+ ),
3977
+ hasMultipleImages && currentIndex < imageUrls.length - 1 && /* @__PURE__ */ jsx(
3978
+ Button,
3979
+ {
3980
+ variant: "ghost",
3981
+ size: "icon-sm",
3982
+ onClick: goToNext,
3983
+ className: "absolute right-4 top-1/2 -translate-y-1/2 bg-card/80 hover:bg-card border border-border shadow-md",
3984
+ title: "Next image (\u2192)",
3985
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
3986
+ }
3987
+ ),
3988
+ hasMultipleImages && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 bg-card/80 border border-border rounded-full px-3 py-1 text-xs text-muted-foreground shadow-md", children: [
3989
+ currentIndex + 1,
3990
+ " / ",
3991
+ imageUrls.length
3992
+ ] }),
3993
+ displayUrl && mediaInfo.type === "image" && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 right-4 flex items-center gap-2 bg-card border border-border rounded-lg p-1", children: [
3994
+ /* @__PURE__ */ jsx(
3995
+ Button,
3996
+ {
3997
+ variant: "ghost",
3998
+ size: "icon-sm",
3999
+ onClick: () => setZoomLevel((prev) => Math.max(prev - 0.25, 0.25)),
4000
+ title: "Zoom out (-)",
4001
+ children: /* @__PURE__ */ jsx(ZoomOut, { className: "w-4 h-4" })
4002
+ }
4003
+ ),
4004
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground w-12 text-center", children: [
4005
+ Math.round(zoomLevel * 100),
4006
+ "%"
4007
+ ] }),
4008
+ /* @__PURE__ */ jsx(
4009
+ Button,
4010
+ {
4011
+ variant: "ghost",
4012
+ size: "icon-sm",
4013
+ onClick: () => setZoomLevel((prev) => Math.min(prev + 0.25, 4)),
4014
+ title: "Zoom in (+)",
4015
+ children: /* @__PURE__ */ jsx(ZoomIn, { className: "w-4 h-4" })
4016
+ }
4017
+ ),
4018
+ /* @__PURE__ */ jsx(
4019
+ Button,
4020
+ {
4021
+ variant: "ghost",
4022
+ size: "sm",
4023
+ onClick: () => {
4024
+ setZoomLevel(1);
4025
+ setPanOffset({ x: 0, y: 0 });
4026
+ },
4027
+ title: "Reset zoom (0)",
4028
+ children: "Reset"
4029
+ }
4030
+ )
4031
+ ] })
4032
+ ]
4033
+ }
4034
+ ) })
4035
+ ] })
4036
+ ] });
4037
+ }
3705
4038
  function getExtensionFromUrl(url, inputType) {
3706
4039
  try {
3707
4040
  const urlPath = new URL(url).pathname;
@@ -3805,10 +4138,18 @@ function DownloadNodeComponent(props) {
3805
4138
  fileExtension
3806
4139
  ] })
3807
4140
  ] }),
3808
- /* @__PURE__ */ jsxs(Button, { className: "w-full", onClick: handleDownload, disabled: isDownloading, children: [
3809
- isDownloading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
3810
- isDownloading ? "Downloading..." : "Download"
3811
- ] })
4141
+ /* @__PURE__ */ jsxs(
4142
+ Button,
4143
+ {
4144
+ className: "w-full",
4145
+ onClick: handleDownload,
4146
+ disabled: isDownloading,
4147
+ children: [
4148
+ isDownloading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" }),
4149
+ isDownloading ? "Downloading..." : "Download"
4150
+ ]
4151
+ }
4152
+ )
3812
4153
  ] }) : isConnected && isRunning ? /* @__PURE__ */ jsx("div", { className: "relative flex h-20 flex-col items-center justify-center rounded-md bg-black/20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2", children: [
3813
4154
  /* @__PURE__ */ jsx(Loader2, { className: "h-8 w-8 animate-spin text-primary" }),
3814
4155
  /* @__PURE__ */ jsx("span", { className: "text-xs text-white/80", children: "Generating..." })
@@ -3824,19 +4165,19 @@ function DownloadNodeComponent(props) {
3824
4165
  var DownloadNode = memo(DownloadNodeComponent);
3825
4166
  var OutputNode = DownloadNode;
3826
4167
  var PRESET_OPTIONS = [
3827
- { value: "linear", label: "Linear" },
3828
- { value: "easeIn", label: "Ease In" },
3829
- { value: "easeOut", label: "Ease Out" },
3830
- { value: "easeInOut", label: "Ease In Out" },
3831
- { value: "easeInQuad", label: "Ease In Quad" },
3832
- { value: "easeOutQuad", label: "Ease Out Quad" },
3833
- { value: "easeInOutQuad", label: "Ease In Out Quad" },
3834
- { value: "easeInCubic", label: "Ease In Cubic" },
3835
- { value: "easeOutCubic", label: "Ease Out Cubic" },
3836
- { value: "easeInOutCubic", label: "Ease In Out Cubic" },
3837
- { value: "easeInExpo", label: "Ease In Expo" },
3838
- { value: "easeOutExpo", label: "Ease Out Expo" },
3839
- { value: "easeInOutExpo", label: "Ease In Out Expo" }
4168
+ { label: "Linear", value: "linear" },
4169
+ { label: "Ease In", value: "easeIn" },
4170
+ { label: "Ease Out", value: "easeOut" },
4171
+ { label: "Ease In Out", value: "easeInOut" },
4172
+ { label: "Ease In Quad", value: "easeInQuad" },
4173
+ { label: "Ease Out Quad", value: "easeOutQuad" },
4174
+ { label: "Ease In Out Quad", value: "easeInOutQuad" },
4175
+ { label: "Ease In Cubic", value: "easeInCubic" },
4176
+ { label: "Ease Out Cubic", value: "easeOutCubic" },
4177
+ { label: "Ease In Out Cubic", value: "easeInOutCubic" },
4178
+ { label: "Ease In Expo", value: "easeInExpo" },
4179
+ { label: "Ease Out Expo", value: "easeOutExpo" },
4180
+ { label: "Ease In Out Expo", value: "easeInOutExpo" }
3840
4181
  ];
3841
4182
  function AnimationNodeComponent(props) {
3842
4183
  const { id, type, data } = props;
@@ -3855,8 +4196,8 @@ function AnimationNodeComponent(props) {
3855
4196
  (value) => {
3856
4197
  const preset = value;
3857
4198
  updateNodeData(id, {
3858
- preset,
3859
- customCurve: EASING_PRESETS[preset]
4199
+ customCurve: EASING_PRESETS[preset],
4200
+ preset
3860
4201
  });
3861
4202
  },
3862
4203
  [id, updateNodeData]
@@ -3877,7 +4218,16 @@ function AnimationNodeComponent(props) {
3877
4218
  openNodeDetailModal(id, "preview");
3878
4219
  }, [id, openNodeDetailModal]);
3879
4220
  const headerActions = useMemo(
3880
- () => nodeData.outputVideo ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
4221
+ () => nodeData.outputVideo ? /* @__PURE__ */ jsx(
4222
+ Button,
4223
+ {
4224
+ variant: "ghost",
4225
+ size: "icon-sm",
4226
+ onClick: handleExpand,
4227
+ title: "Expand preview",
4228
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
4229
+ }
4230
+ ) : null,
3881
4231
  [nodeData.outputVideo, handleExpand]
3882
4232
  );
3883
4233
  const curve = nodeData.customCurve ?? [0.645, 0.045, 0.355, 1];
@@ -3917,11 +4267,35 @@ function AnimationNodeComponent(props) {
3917
4267
  }
3918
4268
  ),
3919
4269
  nodeData.curveType === "preset" && /* @__PURE__ */ jsx("div", { className: "h-20 bg-[var(--background)] rounded border border-[var(--border)] p-2", children: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 100", className: "w-full h-full", children: [
3920
- /* @__PURE__ */ jsx("path", { d: pathD, fill: "none", stroke: "var(--primary)", strokeWidth: "2" }),
4270
+ /* @__PURE__ */ jsx(
4271
+ "path",
4272
+ {
4273
+ d: pathD,
4274
+ fill: "none",
4275
+ stroke: "var(--primary)",
4276
+ strokeWidth: "2"
4277
+ }
4278
+ ),
3921
4279
  /* @__PURE__ */ jsx("circle", { cx: "0", cy: "100", r: "3", fill: "var(--foreground)" }),
3922
4280
  /* @__PURE__ */ jsx("circle", { cx: "100", cy: "0", r: "3", fill: "var(--foreground)" }),
3923
- /* @__PURE__ */ jsx("circle", { cx: curve[0] * 100, cy: 100 - curve[1] * 100, r: "2", fill: "var(--accent)" }),
3924
- /* @__PURE__ */ jsx("circle", { cx: curve[2] * 100, cy: 100 - curve[3] * 100, r: "2", fill: "var(--accent)" })
4281
+ /* @__PURE__ */ jsx(
4282
+ "circle",
4283
+ {
4284
+ cx: curve[0] * 100,
4285
+ cy: 100 - curve[1] * 100,
4286
+ r: "2",
4287
+ fill: "var(--accent)"
4288
+ }
4289
+ ),
4290
+ /* @__PURE__ */ jsx(
4291
+ "circle",
4292
+ {
4293
+ cx: curve[2] * 100,
4294
+ cy: 100 - curve[3] * 100,
4295
+ r: "2",
4296
+ fill: "var(--accent)"
4297
+ }
4298
+ )
3925
4299
  ] }) }),
3926
4300
  /* @__PURE__ */ jsxs("div", { children: [
3927
4301
  /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--muted-foreground)]", children: [
@@ -3992,26 +4366,41 @@ function AnnotationNodeComponent(props) {
3992
4366
  const nodeData = data;
3993
4367
  const openAnnotation = useAnnotationStore((state) => state.openAnnotation);
3994
4368
  const openNodeDetailModal = useUIStore((state) => state.openNodeDetailModal);
3995
- const getConnectedInputs = useWorkflowStore((state) => state.getConnectedInputs);
4369
+ const getConnectedInputs = useWorkflowStore(
4370
+ (state) => state.getConnectedInputs
4371
+ );
3996
4372
  const connectedInputs = getConnectedInputs(id);
3997
4373
  const inputImage = connectedInputs.get("image") ?? nodeData.inputImage;
3998
4374
  const handleEditAnnotations = useCallback(() => {
3999
4375
  if (!inputImage) return;
4000
4376
  const shapes = nodeData.annotations?.map((ann) => ({
4377
+ fillColor: ann.fillColor,
4001
4378
  id: ann.id,
4002
- type: ann.type,
4003
4379
  strokeColor: ann.strokeColor,
4004
4380
  strokeWidth: ann.strokeWidth,
4005
- fillColor: ann.fillColor,
4381
+ type: ann.type,
4006
4382
  ...ann.props
4007
4383
  })) ?? [];
4008
- openAnnotation(id, inputImage, shapes);
4384
+ openAnnotation(
4385
+ id,
4386
+ inputImage,
4387
+ shapes
4388
+ );
4009
4389
  }, [id, inputImage, nodeData.annotations, openAnnotation]);
4010
4390
  const handleExpand = useCallback(() => {
4011
4391
  openNodeDetailModal(id, "preview");
4012
4392
  }, [id, openNodeDetailModal]);
4013
4393
  const headerActions = useMemo(
4014
- () => inputImage ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
4394
+ () => inputImage ? /* @__PURE__ */ jsx(
4395
+ Button,
4396
+ {
4397
+ variant: "ghost",
4398
+ size: "icon-sm",
4399
+ onClick: handleExpand,
4400
+ title: "Expand preview",
4401
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
4402
+ }
4403
+ ) : null,
4015
4404
  [inputImage, handleExpand]
4016
4405
  );
4017
4406
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -4208,7 +4597,7 @@ function ImageCompareNodeComponent(props) {
4208
4597
  }
4209
4598
  ),
4210
4599
  portrait: false,
4211
- style: { width: "100%", height: "100%" }
4600
+ style: { height: "100%", width: "100%" }
4212
4601
  }
4213
4602
  ),
4214
4603
  /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-2 bg-black/50 text-white text-[10px] font-medium px-2 py-1 rounded pointer-events-none", children: "A" }),
@@ -4221,9 +4610,9 @@ function ImageCompareNodeComponent(props) {
4221
4610
  }
4222
4611
  var ImageCompareNode = memo(ImageCompareNodeComponent);
4223
4612
  var OUTPUT_FORMATS = [
4224
- { value: "jpg", label: "JPEG" },
4225
- { value: "png", label: "PNG" },
4226
- { value: "webp", label: "WebP" }
4613
+ { label: "JPEG", value: "jpg" },
4614
+ { label: "PNG", value: "png" },
4615
+ { label: "WebP", value: "webp" }
4227
4616
  ];
4228
4617
  function ImageGridSplitNodeComponent(props) {
4229
4618
  const { id, data } = props;
@@ -4233,14 +4622,20 @@ function ImageGridSplitNodeComponent(props) {
4233
4622
  const [selectedPreview, setSelectedPreview] = useState(null);
4234
4623
  const handleRowsChange = useCallback(
4235
4624
  (e) => {
4236
- const value = Math.min(10, Math.max(1, Number.parseInt(e.target.value, 10) || 1));
4625
+ const value = Math.min(
4626
+ 10,
4627
+ Math.max(1, Number.parseInt(e.target.value, 10) || 1)
4628
+ );
4237
4629
  updateNodeData(id, { gridRows: value });
4238
4630
  },
4239
4631
  [id, updateNodeData]
4240
4632
  );
4241
4633
  const handleColsChange = useCallback(
4242
4634
  (e) => {
4243
- const value = Math.min(10, Math.max(1, Number.parseInt(e.target.value, 10) || 1));
4635
+ const value = Math.min(
4636
+ 10,
4637
+ Math.max(1, Number.parseInt(e.target.value, 10) || 1)
4638
+ );
4244
4639
  updateNodeData(id, { gridCols: value });
4245
4640
  },
4246
4641
  [id, updateNodeData]
@@ -4331,15 +4726,16 @@ function ImageGridSplitNodeComponent(props) {
4331
4726
  nodeData.gridRows,
4332
4727
  "\xD7",
4333
4728
  nodeData.gridCols,
4334
- " grid)"
4729
+ " ",
4730
+ "grid)"
4335
4731
  ] }),
4336
4732
  /* @__PURE__ */ jsx(
4337
4733
  "div",
4338
4734
  {
4339
4735
  className: "mt-2 grid gap-0.5 mx-auto w-20 aspect-square bg-[var(--border)] rounded overflow-hidden",
4340
4736
  style: {
4341
- gridTemplateRows: `repeat(${nodeData.gridRows}, 1fr)`,
4342
- gridTemplateColumns: `repeat(${nodeData.gridCols}, 1fr)`
4737
+ gridTemplateColumns: `repeat(${nodeData.gridCols}, 1fr)`,
4738
+ gridTemplateRows: `repeat(${nodeData.gridRows}, 1fr)`
4343
4739
  },
4344
4740
  children: Array.from({ length: totalCells }).map((_, i) => /* @__PURE__ */ jsx(
4345
4741
  "div",
@@ -4372,10 +4768,17 @@ function ImageGridSplitNodeComponent(props) {
4372
4768
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
4373
4769
  /* @__PURE__ */ jsxs("div", { children: [
4374
4770
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Format" }),
4375
- /* @__PURE__ */ jsxs(Select, { value: nodeData.outputFormat, onValueChange: handleFormatChange, children: [
4376
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
4377
- /* @__PURE__ */ jsx(SelectContent, { children: OUTPUT_FORMATS.map((f) => /* @__PURE__ */ jsx(SelectItem, { value: f.value, children: f.label }, f.value)) })
4378
- ] })
4771
+ /* @__PURE__ */ jsxs(
4772
+ Select,
4773
+ {
4774
+ value: nodeData.outputFormat,
4775
+ onValueChange: handleFormatChange,
4776
+ children: [
4777
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
4778
+ /* @__PURE__ */ jsx(SelectContent, { children: OUTPUT_FORMATS.map((f) => /* @__PURE__ */ jsx(SelectItem, { value: f.value, children: f.label }, f.value)) })
4779
+ ]
4780
+ }
4781
+ )
4379
4782
  ] }),
4380
4783
  /* @__PURE__ */ jsxs("div", { children: [
4381
4784
  /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--muted-foreground)]", children: [
@@ -4402,10 +4805,19 @@ function ImageGridSplitNodeComponent(props) {
4402
4805
  nodeData.outputImages.length,
4403
4806
  " images)"
4404
4807
  ] }),
4405
- /* @__PURE__ */ jsxs(Button, { variant: "link", size: "sm", onClick: handleDownloadAll, className: "h-auto p-0", children: [
4406
- /* @__PURE__ */ jsx(Download, { className: "w-3 h-3" }),
4407
- "Download All"
4408
- ] })
4808
+ /* @__PURE__ */ jsxs(
4809
+ Button,
4810
+ {
4811
+ variant: "link",
4812
+ size: "sm",
4813
+ onClick: handleDownloadAll,
4814
+ className: "h-auto p-0",
4815
+ children: [
4816
+ /* @__PURE__ */ jsx(Download, { className: "w-3 h-3" }),
4817
+ "Download All"
4818
+ ]
4819
+ }
4820
+ )
4409
4821
  ] }),
4410
4822
  /* @__PURE__ */ jsx(
4411
4823
  "div",
@@ -4420,7 +4832,14 @@ function ImageGridSplitNodeComponent(props) {
4420
4832
  className: "relative group aspect-square rounded overflow-hidden border border-[var(--border)] cursor-pointer",
4421
4833
  onClick: () => setSelectedPreview(selectedPreview === index ? null : index),
4422
4834
  children: [
4423
- /* @__PURE__ */ jsx("img", { src: img, alt: `Cell ${index + 1}`, className: "w-full h-full object-cover" }),
4835
+ /* @__PURE__ */ jsx(
4836
+ "img",
4837
+ {
4838
+ src: img,
4839
+ alt: `Cell ${index + 1}`,
4840
+ className: "w-full h-full object-cover"
4841
+ }
4842
+ ),
4424
4843
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition flex items-center justify-center gap-1", children: /* @__PURE__ */ jsx(
4425
4844
  Button,
4426
4845
  {
@@ -4538,7 +4957,8 @@ function OutputGalleryNodeComponent(props) {
4538
4957
  setLightboxIndex((prev) => {
4539
4958
  if (prev === null) return null;
4540
4959
  if (direction === "prev" && prev > 0) return prev - 1;
4541
- if (direction === "next" && prev < imageCountRef.current - 1) return prev + 1;
4960
+ if (direction === "next" && prev < imageCountRef.current - 1)
4961
+ return prev + 1;
4542
4962
  return prev;
4543
4963
  });
4544
4964
  }, []);
@@ -4576,7 +4996,9 @@ function OutputGalleryNodeComponent(props) {
4576
4996
  img.onerror = () => resolve(blob);
4577
4997
  img.src = image;
4578
4998
  });
4579
- await navigator.clipboard.write([new ClipboardItem({ "image/png": pngBlob })]);
4999
+ await navigator.clipboard.write([
5000
+ new ClipboardItem({ "image/png": pngBlob })
5001
+ ]);
4580
5002
  } catch {
4581
5003
  }
4582
5004
  }, [lightboxIndex]);
@@ -4604,7 +5026,14 @@ function OutputGalleryNodeComponent(props) {
4604
5026
  {
4605
5027
  onClick: () => openLightbox(idx),
4606
5028
  className: "aspect-square rounded border border-border hover:border-primary overflow-hidden transition-colors",
4607
- children: /* @__PURE__ */ jsx("img", { src: img, alt: `Image ${idx + 1}`, className: "w-full h-full object-cover" })
5029
+ children: /* @__PURE__ */ jsx(
5030
+ "img",
5031
+ {
5032
+ src: img,
5033
+ alt: `Image ${idx + 1}`,
5034
+ className: "w-full h-full object-cover"
5035
+ }
5036
+ )
4608
5037
  },
4609
5038
  img
4610
5039
  )) }) }) }),
@@ -4615,69 +5044,76 @@ function OutputGalleryNodeComponent(props) {
4615
5044
  className: "fixed inset-0 bg-black/90 z-[100] flex items-center justify-center p-8",
4616
5045
  onClick: closeLightbox,
4617
5046
  onContextMenu: (e) => e.stopPropagation(),
4618
- children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-full max-h-full", onClick: (e) => e.stopPropagation(), children: [
4619
- /* @__PURE__ */ jsx(
4620
- "img",
4621
- {
4622
- src: displayImages[lightboxIndex],
4623
- alt: `Gallery image ${lightboxIndex + 1}`,
4624
- className: "max-w-full max-h-[90vh] object-contain rounded"
4625
- }
4626
- ),
4627
- /* @__PURE__ */ jsx(
4628
- "button",
4629
- {
4630
- onClick: closeLightbox,
4631
- className: "absolute top-4 right-4 w-8 h-8 bg-white/10 hover:bg-white/20 rounded text-white text-sm transition-colors flex items-center justify-center",
4632
- children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
4633
- }
4634
- ),
4635
- /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 flex items-center gap-2", children: [
4636
- /* @__PURE__ */ jsxs(
4637
- "button",
4638
- {
4639
- onClick: downloadImage,
4640
- className: "px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded text-white text-xs font-medium transition-colors flex items-center gap-1.5",
4641
- children: [
4642
- /* @__PURE__ */ jsx(Download, { className: "w-3.5 h-3.5" }),
4643
- "Download"
4644
- ]
4645
- }
4646
- ),
4647
- /* @__PURE__ */ jsxs(
4648
- "button",
4649
- {
4650
- onClick: copyImage,
4651
- className: "px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded text-white text-xs font-medium transition-colors flex items-center gap-1.5",
4652
- children: [
4653
- /* @__PURE__ */ jsx(Copy, { className: "w-3.5 h-3.5" }),
4654
- "Copy"
4655
- ]
4656
- }
4657
- )
4658
- ] }),
4659
- lightboxIndex > 0 && /* @__PURE__ */ jsx(
4660
- "button",
4661
- {
4662
- onClick: () => navigateLightbox("prev"),
4663
- className: "absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/10 hover:bg-white/20 rounded-full text-white transition-colors flex items-center justify-center",
4664
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
4665
- }
4666
- ),
4667
- lightboxIndex < displayImages.length - 1 && /* @__PURE__ */ jsx(
4668
- "button",
4669
- {
4670
- onClick: () => navigateLightbox("next"),
4671
- className: "absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/10 hover:bg-white/20 rounded-full text-white transition-colors flex items-center justify-center",
4672
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
4673
- }
4674
- ),
4675
- /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 px-3 py-1.5 bg-black/50 rounded text-white text-xs font-medium", children: [
4676
- lightboxIndex + 1,
4677
- " / ",
4678
- displayImages.length
4679
- ] })
4680
- ] })
5047
+ children: /* @__PURE__ */ jsxs(
5048
+ "div",
5049
+ {
5050
+ className: "relative max-w-full max-h-full",
5051
+ onClick: (e) => e.stopPropagation(),
5052
+ children: [
5053
+ /* @__PURE__ */ jsx(
5054
+ "img",
5055
+ {
5056
+ src: displayImages[lightboxIndex],
5057
+ alt: `Gallery image ${lightboxIndex + 1}`,
5058
+ className: "max-w-full max-h-[90vh] object-contain rounded"
5059
+ }
5060
+ ),
5061
+ /* @__PURE__ */ jsx(
5062
+ "button",
5063
+ {
5064
+ onClick: closeLightbox,
5065
+ className: "absolute top-4 right-4 w-8 h-8 bg-white/10 hover:bg-white/20 rounded text-white text-sm transition-colors flex items-center justify-center",
5066
+ children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
5067
+ }
5068
+ ),
5069
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 flex items-center gap-2", children: [
5070
+ /* @__PURE__ */ jsxs(
5071
+ "button",
5072
+ {
5073
+ onClick: downloadImage,
5074
+ className: "px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded text-white text-xs font-medium transition-colors flex items-center gap-1.5",
5075
+ children: [
5076
+ /* @__PURE__ */ jsx(Download, { className: "w-3.5 h-3.5" }),
5077
+ "Download"
5078
+ ]
5079
+ }
5080
+ ),
5081
+ /* @__PURE__ */ jsxs(
5082
+ "button",
5083
+ {
5084
+ onClick: copyImage,
5085
+ className: "px-3 py-1.5 bg-white/10 hover:bg-white/20 rounded text-white text-xs font-medium transition-colors flex items-center gap-1.5",
5086
+ children: [
5087
+ /* @__PURE__ */ jsx(Copy, { className: "w-3.5 h-3.5" }),
5088
+ "Copy"
5089
+ ]
5090
+ }
5091
+ )
5092
+ ] }),
5093
+ lightboxIndex > 0 && /* @__PURE__ */ jsx(
5094
+ "button",
5095
+ {
5096
+ onClick: () => navigateLightbox("prev"),
5097
+ className: "absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/10 hover:bg-white/20 rounded-full text-white transition-colors flex items-center justify-center",
5098
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "w-5 h-5" })
5099
+ }
5100
+ ),
5101
+ lightboxIndex < displayImages.length - 1 && /* @__PURE__ */ jsx(
5102
+ "button",
5103
+ {
5104
+ onClick: () => navigateLightbox("next"),
5105
+ className: "absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/10 hover:bg-white/20 rounded-full text-white transition-colors flex items-center justify-center",
5106
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "w-5 h-5" })
5107
+ }
5108
+ ),
5109
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 px-3 py-1.5 bg-black/50 rounded text-white text-xs font-medium", children: [
5110
+ lightboxIndex + 1,
5111
+ " / ",
5112
+ displayImages.length
5113
+ ] })
5114
+ ]
5115
+ }
5116
+ )
4681
5117
  }
4682
5118
  ),
4683
5119
  document.body
@@ -4686,17 +5122,17 @@ function OutputGalleryNodeComponent(props) {
4686
5122
  }
4687
5123
  var OutputGalleryNode = memo(OutputGalleryNodeComponent);
4688
5124
  var MODELS = [
4689
- { value: "photon-flash-1", label: "Photon Flash", price: "$0.01" },
4690
- { value: "photon-1", label: "Photon", price: "$0.03" }
5125
+ { label: "Photon Flash", price: "$0.01", value: "photon-flash-1" },
5126
+ { label: "Photon", price: "$0.03", value: "photon-1" }
4691
5127
  ];
4692
5128
  var ASPECT_RATIOS2 = [
4693
- { value: "9:16", label: "9:16 (TikTok/Reels)" },
4694
- { value: "16:9", label: "16:9 (YouTube)" },
4695
- { value: "1:1", label: "1:1 (Square)" },
4696
- { value: "4:3", label: "4:3" },
4697
- { value: "3:4", label: "3:4" },
4698
- { value: "9:21", label: "9:21" },
4699
- { value: "21:9", label: "21:9 (Ultrawide)" }
5129
+ { label: "9:16 (TikTok/Reels)", value: "9:16" },
5130
+ { label: "16:9 (YouTube)", value: "16:9" },
5131
+ { label: "1:1 (Square)", value: "1:1" },
5132
+ { label: "4:3", value: "4:3" },
5133
+ { label: "3:4", value: "3:4" },
5134
+ { label: "9:21", value: "9:21" },
5135
+ { label: "21:9 (Ultrawide)", value: "21:9" }
4700
5136
  ];
4701
5137
  function ReframeNodeComponent(props) {
4702
5138
  const { id, data } = props;
@@ -4768,10 +5204,17 @@ function ReframeNodeComponent(props) {
4768
5204
  ] }),
4769
5205
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
4770
5206
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Target Aspect Ratio" }),
4771
- /* @__PURE__ */ jsxs(Select, { value: nodeData.aspectRatio, onValueChange: handleAspectRatioChange, children: [
4772
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
4773
- /* @__PURE__ */ jsx(SelectContent, { children: ASPECT_RATIOS2.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio.value, children: ratio.label }, ratio.value)) })
4774
- ] })
5207
+ /* @__PURE__ */ jsxs(
5208
+ Select,
5209
+ {
5210
+ value: nodeData.aspectRatio,
5211
+ onValueChange: handleAspectRatioChange,
5212
+ children: [
5213
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5214
+ /* @__PURE__ */ jsx(SelectContent, { children: ASPECT_RATIOS2.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio.value, children: ratio.label }, ratio.value)) })
5215
+ ]
5216
+ }
5217
+ )
4775
5218
  ] }),
4776
5219
  /* @__PURE__ */ jsx(
4777
5220
  GridPositionSelector,
@@ -4921,10 +5364,17 @@ function ResizeNodeComponent(props) {
4921
5364
  ] }),
4922
5365
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
4923
5366
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Target Aspect Ratio" }),
4924
- /* @__PURE__ */ jsxs(Select, { value: nodeData.targetAspectRatio, onValueChange: handleAspectRatioChange, children: [
4925
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
4926
- /* @__PURE__ */ jsx(SelectContent, { children: LUMA_ASPECT_RATIOS.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio, children: ratio }, ratio)) })
4927
- ] })
5367
+ /* @__PURE__ */ jsxs(
5368
+ Select,
5369
+ {
5370
+ value: nodeData.targetAspectRatio,
5371
+ onValueChange: handleAspectRatioChange,
5372
+ children: [
5373
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5374
+ /* @__PURE__ */ jsx(SelectContent, { children: LUMA_ASPECT_RATIOS.map((ratio) => /* @__PURE__ */ jsx(SelectItem, { value: ratio, children: ratio }, ratio)) })
5375
+ ]
5376
+ }
5377
+ )
4928
5378
  ] }),
4929
5379
  /* @__PURE__ */ jsx(
4930
5380
  GridPositionSelector,
@@ -4995,15 +5445,15 @@ function ResizeNodeComponent(props) {
4995
5445
  }
4996
5446
  var ResizeNode = memo(ResizeNodeComponent);
4997
5447
  var STYLE_OPTIONS = [
4998
- { value: "modern", label: "Modern" },
4999
- { value: "default", label: "Default" },
5000
- { value: "minimal", label: "Minimal" },
5001
- { value: "bold", label: "Bold" }
5448
+ { label: "Modern", value: "modern" },
5449
+ { label: "Default", value: "default" },
5450
+ { label: "Minimal", value: "minimal" },
5451
+ { label: "Bold", value: "bold" }
5002
5452
  ];
5003
5453
  var POSITION_OPTIONS = [
5004
- { value: "bottom", label: "Bottom" },
5005
- { value: "center", label: "Center" },
5006
- { value: "top", label: "Top" }
5454
+ { label: "Bottom", value: "bottom" },
5455
+ { label: "Center", value: "center" },
5456
+ { label: "Top", value: "top" }
5007
5457
  ];
5008
5458
  function SubtitleNodeComponent(props) {
5009
5459
  const { id, data } = props;
@@ -5018,7 +5468,9 @@ function SubtitleNodeComponent(props) {
5018
5468
  );
5019
5469
  const handlePositionChange = useCallback(
5020
5470
  (value) => {
5021
- updateNodeData(id, { position: value });
5471
+ updateNodeData(id, {
5472
+ position: value
5473
+ });
5022
5474
  },
5023
5475
  [id, updateNodeData]
5024
5476
  );
@@ -5050,10 +5502,17 @@ function SubtitleNodeComponent(props) {
5050
5502
  ] }),
5051
5503
  /* @__PURE__ */ jsxs("div", { children: [
5052
5504
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)] block mb-1", children: "Position" }),
5053
- /* @__PURE__ */ jsxs(Select, { value: nodeData.position, onValueChange: handlePositionChange, children: [
5054
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5055
- /* @__PURE__ */ jsx(SelectContent, { children: POSITION_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
5056
- ] })
5505
+ /* @__PURE__ */ jsxs(
5506
+ Select,
5507
+ {
5508
+ value: nodeData.position,
5509
+ onValueChange: handlePositionChange,
5510
+ children: [
5511
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5512
+ /* @__PURE__ */ jsx(SelectContent, { children: POSITION_OPTIONS.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)) })
5513
+ ]
5514
+ }
5515
+ )
5057
5516
  ] }),
5058
5517
  /* @__PURE__ */ jsxs("div", { children: [
5059
5518
  /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--muted-foreground)] block mb-1", children: [
@@ -5086,7 +5545,14 @@ function SubtitleNodeComponent(props) {
5086
5545
  /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--muted-foreground)]", children: nodeData.fontColor })
5087
5546
  ] }),
5088
5547
  nodeData.outputVideo && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
5089
- /* @__PURE__ */ jsx("video", { src: nodeData.outputVideo, className: "w-full h-20 object-cover rounded", muted: true }),
5548
+ /* @__PURE__ */ jsx(
5549
+ "video",
5550
+ {
5551
+ src: nodeData.outputVideo,
5552
+ className: "w-full h-20 object-cover rounded",
5553
+ muted: true
5554
+ }
5555
+ ),
5090
5556
  /* @__PURE__ */ jsx(
5091
5557
  Button,
5092
5558
  {
@@ -5117,31 +5583,31 @@ function SubtitleNodeComponent(props) {
5117
5583
  }
5118
5584
  var SubtitleNode = memo(SubtitleNodeComponent);
5119
5585
  var IMAGE_MODELS2 = [
5120
- { value: "topaz-standard-v2", label: "Standard V2" },
5121
- { value: "topaz-low-res-v2", label: "Low Resolution V2" },
5122
- { value: "topaz-cgi", label: "CGI" },
5123
- { value: "topaz-high-fidelity-v2", label: "High Fidelity V2" },
5124
- { value: "topaz-text-refine", label: "Text Refine" }
5586
+ { label: "Standard V2", value: "topaz-standard-v2" },
5587
+ { label: "Low Resolution V2", value: "topaz-low-res-v2" },
5588
+ { label: "CGI", value: "topaz-cgi" },
5589
+ { label: "High Fidelity V2", value: "topaz-high-fidelity-v2" },
5590
+ { label: "Text Refine", value: "topaz-text-refine" }
5125
5591
  ];
5126
5592
  var VIDEO_MODELS2 = [
5127
- { value: "topaz-video", label: "Topaz Video Upscale" }
5593
+ { label: "Topaz Video Upscale", value: "topaz-video" }
5128
5594
  ];
5129
5595
  var UPSCALE_FACTORS = [
5130
- { value: "None", label: "None (enhance only)" },
5131
- { value: "2x", label: "2x" },
5132
- { value: "4x", label: "4x" },
5133
- { value: "6x", label: "6x" }
5596
+ { label: "None (enhance only)", value: "None" },
5597
+ { label: "2x", value: "2x" },
5598
+ { label: "4x", value: "4x" },
5599
+ { label: "6x", value: "6x" }
5134
5600
  ];
5135
5601
  var RESOLUTIONS = [
5136
- { value: "720p", label: "720p (HD)" },
5137
- { value: "1080p", label: "1080p (Full HD)" },
5138
- { value: "4k", label: "4K (Ultra HD)" }
5602
+ { label: "720p (HD)", value: "720p" },
5603
+ { label: "1080p (Full HD)", value: "1080p" },
5604
+ { label: "4K (Ultra HD)", value: "4k" }
5139
5605
  ];
5140
5606
  var FPS_OPTIONS = [
5141
- { value: 15, label: "15 fps" },
5142
- { value: 24, label: "24 fps (Film)" },
5143
- { value: 30, label: "30 fps" },
5144
- { value: 60, label: "60 fps (Smooth)" }
5607
+ { label: "15 fps", value: 15 },
5608
+ { label: "24 fps (Film)", value: 24 },
5609
+ { label: "30 fps", value: 30 },
5610
+ { label: "60 fps (Smooth)", value: 60 }
5145
5611
  ];
5146
5612
  function UpscaleNodeComponent(props) {
5147
5613
  const { id, data } = props;
@@ -5162,19 +5628,25 @@ function UpscaleNodeComponent(props) {
5162
5628
  );
5163
5629
  const handleFactorChange = useCallback(
5164
5630
  (value) => {
5165
- updateNodeData(id, { upscaleFactor: value });
5631
+ updateNodeData(id, {
5632
+ upscaleFactor: value
5633
+ });
5166
5634
  },
5167
5635
  [id, updateNodeData]
5168
5636
  );
5169
5637
  const handleFormatChange = useCallback(
5170
5638
  (value) => {
5171
- updateNodeData(id, { outputFormat: value });
5639
+ updateNodeData(id, {
5640
+ outputFormat: value
5641
+ });
5172
5642
  },
5173
5643
  [id, updateNodeData]
5174
5644
  );
5175
5645
  const handleResolutionChange = useCallback(
5176
5646
  (value) => {
5177
- updateNodeData(id, { targetResolution: value });
5647
+ updateNodeData(id, {
5648
+ targetResolution: value
5649
+ });
5178
5650
  },
5179
5651
  [id, updateNodeData]
5180
5652
  );
@@ -5241,6 +5713,10 @@ function UpscaleNodeComponent(props) {
5241
5713
  }, [isPlaying]);
5242
5714
  const getPriceEstimate = useCallback(() => {
5243
5715
  const priceMap = {
5716
+ "4k-15": 0.187,
5717
+ "4k-24": 0.299,
5718
+ "4k-30": 0.373,
5719
+ "4k-60": 0.747,
5244
5720
  "720p-15": 0.014,
5245
5721
  "720p-24": 0.022,
5246
5722
  "720p-30": 0.027,
@@ -5248,11 +5724,7 @@ function UpscaleNodeComponent(props) {
5248
5724
  "1080p-15": 0.051,
5249
5725
  "1080p-24": 0.081,
5250
5726
  "1080p-30": 0.101,
5251
- "1080p-60": 0.203,
5252
- "4k-15": 0.187,
5253
- "4k-24": 0.299,
5254
- "4k-30": 0.373,
5255
- "4k-60": 0.747
5727
+ "1080p-60": 0.203
5256
5728
  };
5257
5729
  const key = `${nodeData.targetResolution}-${nodeData.targetFps}`;
5258
5730
  const perFiveSeconds = priceMap[key] ?? 0.101;
@@ -5260,7 +5732,16 @@ function UpscaleNodeComponent(props) {
5260
5732
  }, [nodeData.targetResolution, nodeData.targetFps]);
5261
5733
  const models = inputType === "video" ? VIDEO_MODELS2 : IMAGE_MODELS2;
5262
5734
  const headerActions = useMemo(
5263
- () => hasOutput ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
5735
+ () => hasOutput ? /* @__PURE__ */ jsx(
5736
+ Button,
5737
+ {
5738
+ variant: "ghost",
5739
+ size: "icon-sm",
5740
+ onClick: handleExpand,
5741
+ title: "Expand preview",
5742
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
5743
+ }
5744
+ ) : null,
5264
5745
  [hasOutput, handleExpand]
5265
5746
  );
5266
5747
  return /* @__PURE__ */ jsx(BaseNode, { ...props, headerActions, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -5279,20 +5760,34 @@ function UpscaleNodeComponent(props) {
5279
5760
  inputType === "image" && /* @__PURE__ */ jsxs(Fragment, { children: [
5280
5761
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5281
5762
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Upscale Factor" }),
5282
- /* @__PURE__ */ jsxs(Select, { value: nodeData.upscaleFactor, onValueChange: handleFactorChange, children: [
5283
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5284
- /* @__PURE__ */ jsx(SelectContent, { children: UPSCALE_FACTORS.map((factor) => /* @__PURE__ */ jsx(SelectItem, { value: factor.value, children: factor.label }, factor.value)) })
5285
- ] })
5763
+ /* @__PURE__ */ jsxs(
5764
+ Select,
5765
+ {
5766
+ value: nodeData.upscaleFactor,
5767
+ onValueChange: handleFactorChange,
5768
+ children: [
5769
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5770
+ /* @__PURE__ */ jsx(SelectContent, { children: UPSCALE_FACTORS.map((factor) => /* @__PURE__ */ jsx(SelectItem, { value: factor.value, children: factor.label }, factor.value)) })
5771
+ ]
5772
+ }
5773
+ )
5286
5774
  ] }),
5287
5775
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5288
5776
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Output Format" }),
5289
- /* @__PURE__ */ jsxs(Select, { value: nodeData.outputFormat, onValueChange: handleFormatChange, children: [
5290
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5291
- /* @__PURE__ */ jsxs(SelectContent, { children: [
5292
- /* @__PURE__ */ jsx(SelectItem, { value: "png", children: "PNG" }),
5293
- /* @__PURE__ */ jsx(SelectItem, { value: "jpg", children: "JPG" })
5294
- ] })
5295
- ] })
5777
+ /* @__PURE__ */ jsxs(
5778
+ Select,
5779
+ {
5780
+ value: nodeData.outputFormat,
5781
+ onValueChange: handleFormatChange,
5782
+ children: [
5783
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5784
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
5785
+ /* @__PURE__ */ jsx(SelectItem, { value: "png", children: "PNG" }),
5786
+ /* @__PURE__ */ jsx(SelectItem, { value: "jpg", children: "JPG" })
5787
+ ] })
5788
+ ]
5789
+ }
5790
+ )
5296
5791
  ] }),
5297
5792
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
5298
5793
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 nodrag", children: [
@@ -5304,7 +5799,14 @@ function UpscaleNodeComponent(props) {
5304
5799
  onCheckedChange: handleFaceEnhancementToggle
5305
5800
  }
5306
5801
  ),
5307
- /* @__PURE__ */ jsx(Label, { htmlFor: `${id}-face-enhance`, className: "text-xs cursor-pointer", children: "Face Enhancement" })
5802
+ /* @__PURE__ */ jsx(
5803
+ Label,
5804
+ {
5805
+ htmlFor: `${id}-face-enhance`,
5806
+ className: "text-xs cursor-pointer",
5807
+ children: "Face Enhancement"
5808
+ }
5809
+ )
5308
5810
  ] }),
5309
5811
  nodeData.faceEnhancement && /* @__PURE__ */ jsxs("div", { className: "space-y-2 pl-1", children: [
5310
5812
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
@@ -5351,17 +5853,31 @@ function UpscaleNodeComponent(props) {
5351
5853
  inputType === "video" && /* @__PURE__ */ jsxs(Fragment, { children: [
5352
5854
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5353
5855
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Target Resolution" }),
5354
- /* @__PURE__ */ jsxs(Select, { value: nodeData.targetResolution, onValueChange: handleResolutionChange, children: [
5355
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5356
- /* @__PURE__ */ jsx(SelectContent, { children: RESOLUTIONS.map((res) => /* @__PURE__ */ jsx(SelectItem, { value: res.value, children: res.label }, res.value)) })
5357
- ] })
5856
+ /* @__PURE__ */ jsxs(
5857
+ Select,
5858
+ {
5859
+ value: nodeData.targetResolution,
5860
+ onValueChange: handleResolutionChange,
5861
+ children: [
5862
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5863
+ /* @__PURE__ */ jsx(SelectContent, { children: RESOLUTIONS.map((res) => /* @__PURE__ */ jsx(SelectItem, { value: res.value, children: res.label }, res.value)) })
5864
+ ]
5865
+ }
5866
+ )
5358
5867
  ] }),
5359
5868
  /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
5360
5869
  /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Target Frame Rate" }),
5361
- /* @__PURE__ */ jsxs(Select, { value: String(nodeData.targetFps), onValueChange: handleFpsChange, children: [
5362
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5363
- /* @__PURE__ */ jsx(SelectContent, { children: FPS_OPTIONS.map((fps) => /* @__PURE__ */ jsx(SelectItem, { value: String(fps.value), children: fps.label }, fps.value)) })
5364
- ] })
5870
+ /* @__PURE__ */ jsxs(
5871
+ Select,
5872
+ {
5873
+ value: String(nodeData.targetFps),
5874
+ onValueChange: handleFpsChange,
5875
+ children: [
5876
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-9 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5877
+ /* @__PURE__ */ jsx(SelectContent, { children: FPS_OPTIONS.map((fps) => /* @__PURE__ */ jsx(SelectItem, { value: String(fps.value), children: fps.label }, fps.value)) })
5878
+ ]
5879
+ }
5880
+ )
5365
5881
  ] }),
5366
5882
  /* @__PURE__ */ jsxs("div", { className: "text-[10px] text-muted-foreground bg-secondary/50 rounded px-2 py-1", children: [
5367
5883
  "Estimated cost: ",
@@ -5487,8 +6003,8 @@ function UpscaleNodeComponent(props) {
5487
6003
  }
5488
6004
  var UpscaleNode = memo(UpscaleNodeComponent);
5489
6005
  var SELECTION_MODES = [
5490
- { value: "last", label: "Last Frame" },
5491
- { value: "first", label: "First Frame" }
6006
+ { label: "Last Frame", value: "last" },
6007
+ { label: "First Frame", value: "first" }
5492
6008
  ];
5493
6009
  function formatTime(seconds) {
5494
6010
  const mins = Math.floor(seconds / 60);
@@ -5502,7 +6018,9 @@ function VideoFrameExtractNodeComponent(props) {
5502
6018
  const executeNode = useExecutionStore((state) => state.executeNode);
5503
6019
  const handleModeChange = useCallback(
5504
6020
  (value) => {
5505
- updateNodeData(id, { selectionMode: value });
6021
+ updateNodeData(id, {
6022
+ selectionMode: value
6023
+ });
5506
6024
  },
5507
6025
  [id, updateNodeData]
5508
6026
  );
@@ -5514,10 +6032,17 @@ function VideoFrameExtractNodeComponent(props) {
5514
6032
  /* @__PURE__ */ jsx("div", { className: "text-xs text-[var(--muted-foreground)]", children: nodeData.videoDuration ? `Source: ${formatTime(nodeData.videoDuration)}` : "Connect video to extract frame" }),
5515
6033
  /* @__PURE__ */ jsxs("div", { children: [
5516
6034
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)] block mb-1", children: "Frame Selection" }),
5517
- /* @__PURE__ */ jsxs(Select, { value: nodeData.selectionMode, onValueChange: handleModeChange, children: [
5518
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5519
- /* @__PURE__ */ jsx(SelectContent, { children: SELECTION_MODES.map((mode) => /* @__PURE__ */ jsx(SelectItem, { value: mode.value, children: mode.label }, mode.value)) })
5520
- ] })
6035
+ /* @__PURE__ */ jsxs(
6036
+ Select,
6037
+ {
6038
+ value: nodeData.selectionMode,
6039
+ onValueChange: handleModeChange,
6040
+ children: [
6041
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
6042
+ /* @__PURE__ */ jsx(SelectContent, { children: SELECTION_MODES.map((mode) => /* @__PURE__ */ jsx(SelectItem, { value: mode.value, children: mode.label }, mode.value)) })
6043
+ ]
6044
+ }
6045
+ )
5521
6046
  ] }),
5522
6047
  nodeData.outputImage && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
5523
6048
  /* @__PURE__ */ jsx(
@@ -5560,14 +6085,14 @@ function VideoFrameExtractNodeComponent(props) {
5560
6085
  }
5561
6086
  var VideoFrameExtractNode = memo(VideoFrameExtractNodeComponent);
5562
6087
  var TRANSITIONS = [
5563
- { value: "cut", label: "Cut (No transition)" },
5564
- { value: "crossfade", label: "Crossfade" },
5565
- { value: "fade", label: "Fade to Black" },
5566
- { value: "wipe", label: "Wipe" }
6088
+ { label: "Cut (No transition)", value: "cut" },
6089
+ { label: "Crossfade", value: "crossfade" },
6090
+ { label: "Fade to Black", value: "fade" },
6091
+ { label: "Wipe", value: "wipe" }
5567
6092
  ];
5568
6093
  var AUDIO_CODECS = [
5569
- { value: "aac", label: "AAC", hint: "Best compatibility" },
5570
- { value: "mp3", label: "MP3", hint: "Legacy fallback" }
6094
+ { hint: "Best compatibility", label: "AAC", value: "aac" },
6095
+ { hint: "Legacy fallback", label: "MP3", value: "mp3" }
5571
6096
  ];
5572
6097
  function VideoStitchNodeComponent(props) {
5573
6098
  const { id, data } = props;
@@ -5640,10 +6165,17 @@ function VideoStitchNodeComponent(props) {
5640
6165
  ] }),
5641
6166
  /* @__PURE__ */ jsxs("div", { children: [
5642
6167
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Transition" }),
5643
- /* @__PURE__ */ jsxs(Select, { value: nodeData.transitionType, onValueChange: handleTransitionChange, children: [
5644
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5645
- /* @__PURE__ */ jsx(SelectContent, { children: TRANSITIONS.map((t) => /* @__PURE__ */ jsx(SelectItem, { value: t.value, children: t.label }, t.value)) })
5646
- ] })
6168
+ /* @__PURE__ */ jsxs(
6169
+ Select,
6170
+ {
6171
+ value: nodeData.transitionType,
6172
+ onValueChange: handleTransitionChange,
6173
+ children: [
6174
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
6175
+ /* @__PURE__ */ jsx(SelectContent, { children: TRANSITIONS.map((t) => /* @__PURE__ */ jsx(SelectItem, { value: t.value, children: t.label }, t.value)) })
6176
+ ]
6177
+ }
6178
+ )
5647
6179
  ] }),
5648
6180
  nodeData.transitionType !== "cut" && /* @__PURE__ */ jsxs("div", { children: [
5649
6181
  /* @__PURE__ */ jsxs("label", { className: "text-xs text-[var(--muted-foreground)]", children: [
@@ -5683,14 +6215,21 @@ function VideoStitchNodeComponent(props) {
5683
6215
  ] }),
5684
6216
  /* @__PURE__ */ jsxs("div", { children: [
5685
6217
  /* @__PURE__ */ jsx("label", { className: "text-xs text-[var(--muted-foreground)]", children: "Audio Codec" }),
5686
- /* @__PURE__ */ jsxs(Select, { value: nodeData.audioCodec, onValueChange: handleAudioCodecChange, children: [
5687
- /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
5688
- /* @__PURE__ */ jsx(SelectContent, { children: AUDIO_CODECS.map((codec) => /* @__PURE__ */ jsxs(SelectItem, { value: codec.value, children: [
5689
- codec.label,
5690
- " - ",
5691
- codec.hint
5692
- ] }, codec.value)) })
5693
- ] })
6218
+ /* @__PURE__ */ jsxs(
6219
+ Select,
6220
+ {
6221
+ value: nodeData.audioCodec,
6222
+ onValueChange: handleAudioCodecChange,
6223
+ children: [
6224
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "nodrag h-8 w-full", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
6225
+ /* @__PURE__ */ jsx(SelectContent, { children: AUDIO_CODECS.map((codec) => /* @__PURE__ */ jsxs(SelectItem, { value: codec.value, children: [
6226
+ codec.label,
6227
+ " - ",
6228
+ codec.hint
6229
+ ] }, codec.value)) })
6230
+ ]
6231
+ }
6232
+ )
5694
6233
  ] }),
5695
6234
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 nodrag", children: [
5696
6235
  /* @__PURE__ */ jsx(
@@ -5804,7 +6343,16 @@ function VideoTrimNodeComponent(props) {
5804
6343
  openNodeDetailModal(id, "preview");
5805
6344
  }, [id, openNodeDetailModal]);
5806
6345
  const headerActions = useMemo(
5807
- () => nodeData.outputVideo ? /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: handleExpand, title: "Expand preview", children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" }) }) : null,
6346
+ () => nodeData.outputVideo ? /* @__PURE__ */ jsx(
6347
+ Button,
6348
+ {
6349
+ variant: "ghost",
6350
+ size: "icon-sm",
6351
+ onClick: handleExpand,
6352
+ title: "Expand preview",
6353
+ children: /* @__PURE__ */ jsx(Expand, { className: "h-3 w-3" })
6354
+ }
6355
+ ) : null,
5808
6356
  [nodeData.outputVideo, handleExpand]
5809
6357
  );
5810
6358
  const trimDuration = nodeData.endTime - nodeData.startTime;
@@ -5914,36 +6462,36 @@ var VideoTrimNode = memo(VideoTrimNodeComponent);
5914
6462
 
5915
6463
  // src/nodes/index.ts
5916
6464
  var nodeTypes = {
6465
+ animation: AnimationNode,
6466
+ annotation: AnnotationNode,
5917
6467
  // Input nodes
5918
6468
  audioInput: AudioInputNode,
5919
- imageInput: ImageInputNode,
5920
- videoInput: VideoInputNode,
5921
- prompt: PromptNode,
5922
- promptConstructor: PromptConstructorNode,
6469
+ // Output nodes
6470
+ download: DownloadNode,
6471
+ imageCompare: ImageCompareNode,
5923
6472
  // AI nodes
5924
6473
  imageGen: ImageGenNode,
5925
- videoGen: VideoGenNode,
5926
- llm: LLMNode,
6474
+ imageGridSplit: ImageGridSplitNode,
6475
+ imageInput: ImageInputNode,
5927
6476
  lipSync: LipSyncNode,
5928
- voiceChange: VoiceChangeNode,
5929
- textToSpeech: TextToSpeechNode,
5930
- transcribe: TranscribeNode,
6477
+ llm: LLMNode,
5931
6478
  motionControl: MotionControlNode,
6479
+ outputGallery: OutputGalleryNode,
6480
+ prompt: PromptNode,
6481
+ promptConstructor: PromptConstructorNode,
6482
+ reframe: ReframeNode,
5932
6483
  // Processing nodes
5933
6484
  resize: ResizeNode,
5934
- animation: AnimationNode,
5935
- annotation: AnnotationNode,
5936
- imageGridSplit: ImageGridSplitNode,
5937
- outputGallery: OutputGalleryNode,
5938
- imageCompare: ImageCompareNode,
6485
+ subtitle: SubtitleNode,
6486
+ textToSpeech: TextToSpeechNode,
6487
+ transcribe: TranscribeNode,
6488
+ upscale: UpscaleNode,
6489
+ videoFrameExtract: VideoFrameExtractNode,
6490
+ videoGen: VideoGenNode,
6491
+ videoInput: VideoInputNode,
5939
6492
  videoStitch: VideoStitchNode,
5940
6493
  videoTrim: VideoTrimNode,
5941
- videoFrameExtract: VideoFrameExtractNode,
5942
- reframe: ReframeNode,
5943
- upscale: UpscaleNode,
5944
- subtitle: SubtitleNode,
5945
- // Output nodes
5946
- download: DownloadNode,
6494
+ voiceChange: VoiceChangeNode,
5947
6495
  // Composition nodes (workflow-as-node)
5948
6496
  workflowInput: WorkflowInputNode,
5949
6497
  workflowOutput: WorkflowOutputNode,