@burdenoff/fluidgrids-fe-sdk 2026.1014.4 → 2026.1014.6

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.
@@ -981,8 +981,8 @@ function isAdaptiveCard(data) {
981
981
  return data.type === "AdaptiveCard";
982
982
  }
983
983
  function isCustomFormat(data) {
984
- const record = data;
985
- return record.type !== "AdaptiveCard" && "inputs" in record;
984
+ const record2 = data;
985
+ return record2.type !== "AdaptiveCard" && "inputs" in record2;
986
986
  }
987
987
  function expandTemplate(obj, data) {
988
988
  if (typeof obj === "string") {
@@ -2454,643 +2454,339 @@ function isNodeTrigger(node) {
2454
2454
  return false;
2455
2455
  }
2456
2456
 
2457
- // src/hooks/useNodeExecution.ts
2458
- var TRIGGER_WORKFLOW = TriggerWorkflowDocument;
2459
- var EXECUTE_STEP = ExecuteStepDocument;
2460
- var WORKFLOW_RUN = WorkflowRunDocument;
2461
- var RUN_FROM_NODE = RunFromNodeDocument;
2462
- var EXECUTION_DETAIL = ExecutionDetailDocument;
2463
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
2464
- "COMPLETED",
2465
- "FAILED",
2466
- "CANCELLED",
2467
- "TERMINATED"
2468
- ]);
2469
- var POLL_INTERVAL = 1500;
2470
- var MAX_RUN_CREATION_WAIT = 6e4;
2471
- function calculateItemCount(output) {
2472
- if (!output || Object.keys(output).length === 0) {
2473
- return 0;
2457
+ // src/builder/lib/field-schema.ts
2458
+ function isRecord3(value) {
2459
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2460
+ }
2461
+ function isIdentifier(key) {
2462
+ return /^[A-Za-z_$][\w$]*$/.test(key);
2463
+ }
2464
+ function joinPath(base, key) {
2465
+ if (isIdentifier(key)) return base ? `${base}.${key}` : key;
2466
+ const bracket = `[${JSON.stringify(key)}]`;
2467
+ return base ? `${base}${bracket}` : bracket;
2468
+ }
2469
+ function pathToExpression(path) {
2470
+ if (!path) return "${output}";
2471
+ const accessor = path.startsWith("[") ? `output${path}` : `output.${path}`;
2472
+ return "${" + accessor + "}";
2473
+ }
2474
+ function fieldFromSpec(key, path, spec) {
2475
+ if (typeof spec === "string") {
2476
+ return { key, path, type: spec };
2474
2477
  }
2475
- const values = Object.values(output);
2476
- for (const value of values) {
2477
- if (Array.isArray(value)) {
2478
- return value.length;
2478
+ if (isRecord3(spec)) {
2479
+ const declaredType2 = typeof spec.type === "string" ? spec.type : void 0;
2480
+ const description = typeof spec.description === "string" ? spec.description : void 0;
2481
+ const required = spec.required === true;
2482
+ if (isRecord3(spec.properties)) {
2483
+ const children = Object.entries(spec.properties).map(
2484
+ ([k, v]) => fieldFromSpec(k, joinPath(path, k), v)
2485
+ );
2486
+ return { key, path, type: "object", required, description, children };
2479
2487
  }
2480
- if (typeof value === "object" && value !== null) {
2481
- const nestedValues = Object.values(value);
2482
- for (const nestedValue of nestedValues) {
2483
- if (Array.isArray(nestedValue)) {
2484
- return nestedValue.length;
2485
- }
2486
- }
2488
+ if (spec.items !== void 0) {
2489
+ const element = fieldFromSpec("0", `${path}[0]`, spec.items);
2490
+ return {
2491
+ key,
2492
+ path,
2493
+ type: "array",
2494
+ required,
2495
+ description,
2496
+ children: [element]
2497
+ };
2487
2498
  }
2499
+ return { key, path, type: declaredType2 ?? "any", required, description };
2488
2500
  }
2489
- return 1;
2501
+ return { key, path, type: "any" };
2490
2502
  }
2491
- function useNodeExecution() {
2492
- const pollTimerRef = useRef(null);
2493
- const pollStartRef = useRef(0);
2494
- const activeRunIdRef = useRef(null);
2495
- const loadRequestRef = useRef(0);
2496
- const workflow = useFlowStore((s) => s.workflow);
2497
- const startExecution = useFlowStore(
2498
- (s) => s.actions.execution.startExecution
2499
- );
2500
- const setRunMetadata = useFlowStore(
2501
- (s) => s.actions.execution.setRunMetadata
2502
- );
2503
- const setStepPlan = useFlowStore((s) => s.actions.execution.setStepPlan);
2504
- const addLog = useFlowStore((s) => s.actions.execution.addLog);
2505
- const updateLog = useFlowStore((s) => s.actions.execution.updateLog);
2506
- const setNodeStatus = useFlowStore((s) => s.actions.execution.setNodeStatus);
2507
- const setNodeItemCount = useFlowStore(
2508
- (s) => s.actions.execution.setNodeItemCount
2503
+ function fieldsFromIO(io, root = "") {
2504
+ if (!isRecord3(io)) return [];
2505
+ return Object.entries(io).map(
2506
+ ([key, spec]) => fieldFromSpec(key, joinPath(root, key), spec)
2509
2507
  );
2510
- const completeExecution = useFlowStore(
2511
- (s) => s.actions.execution.completeExecution
2508
+ }
2509
+ function typeOfValue(value) {
2510
+ if (value === null || value === void 0) return "any";
2511
+ if (Array.isArray(value)) return "array";
2512
+ if (typeof value === "object") return "object";
2513
+ if (typeof value === "number") return "number";
2514
+ if (typeof value === "boolean") return "boolean";
2515
+ if (typeof value === "string") return "string";
2516
+ return "any";
2517
+ }
2518
+ function fieldFromValue(key, path, value) {
2519
+ if (Array.isArray(value)) {
2520
+ const children = value.length > 0 ? [fieldFromValue("0", `${path}[0]`, value[0])] : void 0;
2521
+ return { key, path, type: "array", children };
2522
+ }
2523
+ if (isRecord3(value)) {
2524
+ const children = Object.entries(value).map(
2525
+ ([k, v]) => fieldFromValue(k, joinPath(path, k), v)
2526
+ );
2527
+ return { key, path, type: "object", children };
2528
+ }
2529
+ return { key, path, type: typeOfValue(value) };
2530
+ }
2531
+ function fieldsFromValue(value, root = "") {
2532
+ if (!isRecord3(value)) return [];
2533
+ return Object.entries(value).map(
2534
+ ([key, v]) => fieldFromValue(key, joinPath(root, key), v)
2512
2535
  );
2513
- const clearLogs = useFlowStore((s) => s.actions.execution.clearLogs);
2514
- const loadDebugRun = useFlowStore((s) => s.actions.execution.loadDebugRun);
2515
- const cleanup = useCallback(() => {
2516
- loadRequestRef.current += 1;
2517
- if (pollTimerRef.current) {
2518
- clearTimeout(pollTimerRef.current);
2519
- pollTimerRef.current = null;
2520
- }
2521
- activeRunIdRef.current = null;
2522
- }, []);
2523
- const findTriggerNode = useCallback(() => {
2524
- const { nodes, edges } = workflow;
2525
- const explicitTrigger = nodes.find(isNodeTrigger);
2526
- if (explicitTrigger) {
2527
- return explicitTrigger;
2536
+ }
2537
+ function declaredOutputFields(nodeType, op) {
2538
+ const name = op?.name;
2539
+ if (!name || nodeType?.startsWith("internal:")) {
2540
+ return fieldsFromIO(op?.output_fields);
2541
+ }
2542
+ const children = fieldsFromIO(op?.output_fields, name);
2543
+ if (children.length === 0) return [];
2544
+ return [{ key: name, path: name, type: "object", children }];
2545
+ }
2546
+ function normalizeOperations(raw) {
2547
+ if (Array.isArray(raw)) return raw;
2548
+ if (isRecord3(raw) && Array.isArray(raw.operations)) {
2549
+ return raw.operations;
2550
+ }
2551
+ return [];
2552
+ }
2553
+ function resolveNodeOperation(data, fallbackOperations2) {
2554
+ const nodeData = isRecord3(data?.nodeData) ? data?.nodeData : void 0;
2555
+ let operations = normalizeOperations(nodeData?.operations);
2556
+ if (operations.length === 0) {
2557
+ operations = normalizeOperations(data?.operations);
2558
+ }
2559
+ if (operations.length === 0 && fallbackOperations2 !== void 0) {
2560
+ operations = normalizeOperations(fallbackOperations2);
2561
+ }
2562
+ const opName = typeof data?.operation_name === "string" ? data.operation_name : void 0;
2563
+ const selected = operations.find((op) => op.name === opName) ?? operations[0];
2564
+ return { operations, selected };
2565
+ }
2566
+
2567
+ // src/builder/lib/parameter-values.ts
2568
+ function isRecord4(value) {
2569
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2570
+ }
2571
+ function record(value) {
2572
+ return isRecord4(value) ? value : void 0;
2573
+ }
2574
+ function declaredType(schema) {
2575
+ const type = typeof schema === "string" ? schema : record(schema)?.type;
2576
+ return typeof type === "string" ? type.toLowerCase().trim() : "";
2577
+ }
2578
+ function structuredParameterType(schema) {
2579
+ const type = declaredType(schema);
2580
+ if (type === "array" || /^array<.+>$/.test(type)) return "array";
2581
+ if (type === "object") return "object";
2582
+ return void 0;
2583
+ }
2584
+ function itemSchema(schema) {
2585
+ return record(schema)?.items ?? declaredType(schema).match(/^array<(.+)>$/)?.[1];
2586
+ }
2587
+ function matchesSchema(value, schema) {
2588
+ const type = declaredType(schema);
2589
+ const structured = structuredParameterType(schema);
2590
+ if (structured === "array") {
2591
+ return Array.isArray(value) && value.every((item) => matchesSchema(item, itemSchema(schema)));
2592
+ }
2593
+ if (structured === "object") {
2594
+ const object = record(value);
2595
+ if (!object) return false;
2596
+ const definition = record(schema);
2597
+ const required = definition?.required;
2598
+ if (Array.isArray(required) && required.some(
2599
+ (key) => typeof key === "string" && !Object.hasOwn(object, key)
2600
+ )) {
2601
+ return false;
2528
2602
  }
2529
- const nodesWithIncomingEdges = new Set(edges.map((e) => e.target));
2530
- const firstNode = nodes.find(
2531
- (node) => !nodesWithIncomingEdges.has(node.id)
2603
+ return Object.entries(record(definition?.properties) ?? {}).every(
2604
+ ([key, property]) => Object.hasOwn(object, key) ? matchesSchema(object[key], property) : record(property)?.required !== true
2532
2605
  );
2533
- if (firstNode) {
2534
- return firstNode;
2606
+ }
2607
+ if (type === "string") return typeof value === "string";
2608
+ if (type === "number")
2609
+ return typeof value === "number" && Number.isFinite(value);
2610
+ if (type === "integer")
2611
+ return typeof value === "number" && Number.isInteger(value);
2612
+ if (type === "boolean") return typeof value === "boolean";
2613
+ if (type === "null") return value === null;
2614
+ return true;
2615
+ }
2616
+ function parseParameterValue(schema, value) {
2617
+ const type = structuredParameterType(schema);
2618
+ if (!type) return { valid: true, value };
2619
+ if (value === void 0 || value === null || typeof value === "string" && !value.trim()) {
2620
+ return { valid: true, value: void 0 };
2621
+ }
2622
+ let parsed = value;
2623
+ if (typeof value === "string") {
2624
+ const text = value.trim();
2625
+ const elementType = declaredType(itemSchema(schema));
2626
+ const canSplit = type === "array" && (!elementType || elementType === "string");
2627
+ if (canSplit && !["[", "{", '"', "]", "}"].includes(text[0])) {
2628
+ parsed = text.split(",").map((item) => item.trim()).filter(Boolean);
2629
+ } else {
2630
+ try {
2631
+ parsed = JSON.parse(text);
2632
+ } catch {
2633
+ return { valid: false, error: `Enter valid JSON for this ${type}.` };
2634
+ }
2535
2635
  }
2536
- if (nodes.length > 0) {
2537
- return nodes[0];
2636
+ }
2637
+ if (type === "array" ? !Array.isArray(parsed) : !record(parsed)) {
2638
+ return { valid: false, error: `Expected a JSON ${type}.` };
2639
+ }
2640
+ if (!matchesSchema(parsed, schema)) {
2641
+ return {
2642
+ valid: false,
2643
+ error: "The value does not match the declared field types."
2644
+ };
2645
+ }
2646
+ return { valid: true, value: parsed };
2647
+ }
2648
+ function formatParameterValue(value) {
2649
+ if (value === void 0 || value === null) return "";
2650
+ return typeof value === "string" ? value : JSON.stringify(value, null, 2);
2651
+ }
2652
+ function normalizeNodeParameters(data, fallbackOperations2, mappedFields = /* @__PURE__ */ new Set()) {
2653
+ const operation = resolveNodeOperation(data, fallbackOperations2).selected;
2654
+ const fields = record(operation?.input_fields);
2655
+ const args = record(data.args);
2656
+ const state = record(args?.state) ?? {};
2657
+ if (!fields) return data;
2658
+ const structuredFields = Object.entries(fields).filter(
2659
+ ([name, schema]) => structuredParameterType(schema) && !mappedFields.has(name)
2660
+ );
2661
+ if (structuredFields.length === 0) return data;
2662
+ const normalized = new Map(Object.entries(state));
2663
+ for (const [name, schema] of structuredFields) {
2664
+ const result = parseParameterValue(
2665
+ schema,
2666
+ Object.hasOwn(state, name) ? state[name] : void 0
2667
+ );
2668
+ if (!result.valid) throw new Error(`${name}: ${result.error}`);
2669
+ if (result.value === void 0) normalized.delete(name);
2670
+ else normalized.set(name, result.value);
2671
+ }
2672
+ return { ...data, args: { ...args, state: Object.fromEntries(normalized) } };
2673
+ }
2674
+ function normalizeWorkflowParameters(nodes, edges, fallbackOperations2) {
2675
+ return nodes.map((node) => {
2676
+ if (node.data.disabled === true) return node;
2677
+ const mappedFields = new Set(
2678
+ edges.filter((edge) => edge.target === node.id).flatMap(
2679
+ (edge) => Object.entries(
2680
+ record(record(record(edge)?.mapping)?.definition) ?? {}
2681
+ ).filter(
2682
+ ([, definition]) => typeof definition === "string" ? definition.trim().length > 0 : definition !== void 0
2683
+ ).map(([name]) => name)
2684
+ )
2685
+ );
2686
+ try {
2687
+ return {
2688
+ ...node,
2689
+ data: normalizeNodeParameters(
2690
+ node.data,
2691
+ fallbackOperations2(node.type),
2692
+ mappedFields
2693
+ )
2694
+ };
2695
+ } catch (error) {
2696
+ throw new Error(
2697
+ `Node ${node.id}: ${error instanceof Error ? error.message : "Invalid parameter value"}`
2698
+ );
2538
2699
  }
2539
- console.error("[useNodeExecution] No nodes found in workflow");
2540
- return null;
2541
- }, [workflow]);
2542
- const getNodeName = useCallback(
2543
- (nodeId) => {
2544
- const node = workflow.nodes.find((n) => n.id === nodeId);
2545
- if (!node) return `Node ${nodeId}`;
2546
- const data = node.data;
2547
- return data?.operation_name || data?.name || node.type || `Node ${nodeId}`;
2700
+ });
2701
+ }
2702
+ function useDeleteNode() {
2703
+ const { getNode, getEdges, deleteElements } = useReactFlow();
2704
+ const deleteNodes = useFlowStore((state) => state.actions.nodes.deleteNodes);
2705
+ return useCallback(
2706
+ (id) => {
2707
+ const node = getNode(id);
2708
+ if (!node) return;
2709
+ const edges = getEdges();
2710
+ const connectedEdges = getConnectedEdges([node], edges);
2711
+ deleteElements({ nodes: [node], edges: connectedEdges }).then();
2712
+ deleteNodes([node]);
2548
2713
  },
2549
- [workflow.nodes]
2714
+ [deleteElements, getEdges, getNode, deleteNodes]
2550
2715
  );
2551
- const processNodeStates = useCallback(
2552
- (runId, nodeStates) => {
2553
- for (const nodeState of nodeStates) {
2554
- const logId = `${runId}-${nodeState.nodeId}`;
2555
- const rawStatus = nodeState.status.toLowerCase();
2556
- const status = rawStatus === "completed" ? "completed" : rawStatus === "failed" || rawStatus === "timeout" ? "failed" : rawStatus === "running" || rawStatus === "waiting" || rawStatus === "delayed" || rawStatus === "waiting_subworkflow" ? "running" : "pending";
2557
- if (status === "completed") {
2558
- setNodeStatus(nodeState.nodeId, "success");
2559
- if (nodeState.output) {
2560
- const itemCount = calculateItemCount(
2561
- nodeState.output
2562
- );
2563
- setNodeItemCount(nodeState.nodeId, itemCount);
2564
- }
2565
- } else if (status === "failed") {
2566
- setNodeStatus(nodeState.nodeId, "error");
2567
- } else if (status === "running") {
2568
- setNodeStatus(nodeState.nodeId, "running");
2569
- }
2570
- const existingLog = useFlowStore.getState().execution.logs.find((l) => l.id === logId);
2571
- if (!existingLog) {
2572
- if (status === "pending") continue;
2573
- const newLog = {
2574
- id: logId,
2575
- runId,
2576
- nodeId: nodeState.nodeId,
2577
- nodeType: nodeState.nodeType,
2578
- nodeName: getNodeName(nodeState.nodeId),
2579
- status,
2580
- inputArgs: nodeState.inputArgs ?? null,
2581
- output: nodeState.output ?? null,
2582
- error: nodeState.error ?? null,
2583
- duration: nodeState.duration ?? null,
2584
- startTime: nodeState.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
2585
- endTime: nodeState.endTime ?? null,
2586
- usedPinnedData: nodeState.usedPinnedData ?? false,
2587
- reusedFromRunId: nodeState.reusedFromRunId ?? null
2588
- };
2589
- addLog(newLog);
2590
- } else {
2591
- if (existingLog.status !== status || status === "completed" && !existingLog.output) {
2592
- updateLog(logId, {
2593
- status,
2594
- inputArgs: nodeState.inputArgs ?? existingLog.inputArgs,
2595
- output: nodeState.output ?? existingLog.output,
2596
- error: nodeState.error ?? existingLog.error,
2597
- duration: nodeState.duration ?? existingLog.duration,
2598
- endTime: nodeState.endTime ?? existingLog.endTime,
2599
- usedPinnedData: nodeState.usedPinnedData ?? existingLog.usedPinnedData,
2600
- reusedFromRunId: nodeState.reusedFromRunId ?? existingLog.reusedFromRunId
2601
- });
2602
- }
2603
- }
2604
- }
2605
- },
2606
- [setNodeStatus, setNodeItemCount, addLog, updateLog, getNodeName]
2607
- );
2608
- const pollRunUpdates = useCallback(
2609
- (runId) => {
2610
- const client = getFluidGridsClient();
2611
- pollStartRef.current = Date.now();
2612
- activeRunIdRef.current = runId;
2613
- const doPoll = async () => {
2614
- if (activeRunIdRef.current !== runId) {
2615
- return;
2616
- }
2617
- try {
2618
- const result = await client.query({
2619
- query: WORKFLOW_RUN,
2620
- variables: { id: runId },
2621
- fetchPolicy: "network-only"
2622
- });
2623
- if (activeRunIdRef.current !== runId) {
2624
- return;
2625
- }
2626
- const run = result.data?.workflowRun;
2627
- if (!run) {
2628
- console.warn(
2629
- "[useNodeExecution] Poll returned no run data for",
2630
- runId
2631
- );
2632
- if (Date.now() - pollStartRef.current < MAX_RUN_CREATION_WAIT) {
2633
- pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL);
2634
- } else {
2635
- console.warn(
2636
- "[useNodeExecution] Run data not found after timeout for",
2637
- runId
2638
- );
2639
- completeExecution();
2640
- cleanup();
2641
- }
2642
- return;
2643
- }
2644
- setRunMetadata({
2645
- runId,
2646
- workflowKey: run.workflowKey,
2647
- version: run.version,
2648
- sourceMode: run.mode
2649
- });
2650
- if (run.nodeStates?.length) {
2651
- processNodeStates(runId, run.nodeStates);
2652
- }
2653
- if (TERMINAL_STATUSES.has(run.status)) {
2654
- completeExecution();
2655
- cleanup();
2656
- return;
2657
- }
2658
- pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL);
2659
- } catch (error) {
2660
- console.error("[useNodeExecution] Poll error:", error);
2661
- if (activeRunIdRef.current !== runId) {
2662
- return;
2663
- }
2664
- pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL * 2);
2665
- }
2666
- };
2667
- pollTimerRef.current = setTimeout(doPoll, 800);
2668
- },
2669
- [processNodeStates, completeExecution, cleanup, setRunMetadata]
2716
+ }
2717
+
2718
+ // src/builder/contants/symbols.ts
2719
+ var NODE_TYPE_DRAG_DATA_FORMAT = "application/flow-builder.node-type";
2720
+ var NODE_OPERATION_DRAG_DATA_FORMAT = "application/flow-builder.node-operation";
2721
+ var STICKY_NOTE_TYPE = "sticky-note";
2722
+ var STICKY_COLORS = {
2723
+ yellow: "#F2C94C",
2724
+ orange: "#EB8934",
2725
+ blue: "#3B82F6",
2726
+ green: "#10B981",
2727
+ red: "#EF4444",
2728
+ purple: "#8B5CF6",
2729
+ gray: "#9CA3AF"
2730
+ };
2731
+ var DEFAULT_STICKY_COLOR = "yellow";
2732
+ var STICKY_DEFAULT_SIZE = { width: 260, height: 170 };
2733
+ var STICKY_MIN_SIZE = { width: 160, height: 90 };
2734
+ function ToolbarButton({
2735
+ title,
2736
+ onClick,
2737
+ danger,
2738
+ children
2739
+ }) {
2740
+ return /* @__PURE__ */ jsx(
2741
+ "button",
2742
+ {
2743
+ type: "button",
2744
+ title,
2745
+ onClick: (e) => {
2746
+ e.stopPropagation();
2747
+ onClick();
2748
+ },
2749
+ className: cn(
2750
+ "nodrag flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
2751
+ danger && "text-[var(--color-status-error-text)] hover:text-[var(--color-status-error-text)]"
2752
+ ),
2753
+ children
2754
+ }
2670
2755
  );
2671
- const executeStep = useCallback(
2672
- async (nodeId) => {
2673
- cleanup();
2674
- clearLogs();
2675
- const workflowKey = workflow.workflowKey || workflow.id;
2676
- const version = workflow.version || "1.0.0";
2677
- if (!workflowKey) {
2678
- console.error(
2679
- "[useNodeExecution] Workflow must be saved before step execution. No workflowKey found."
2680
- );
2681
- return {
2682
- success: false,
2683
- error: "Workflow must be saved before execution"
2684
- };
2685
- }
2686
- setNodeStatus(nodeId, "running");
2687
- try {
2688
- const client = getFluidGridsClient();
2689
- const result = throwIfResultError(
2690
- await client.mutate({
2691
- mutation: EXECUTE_STEP,
2692
- variables: {
2693
- input: {
2694
- workflowKey,
2695
- version,
2696
- nodeId
2697
- }
2698
- }
2699
- })
2756
+ }
2757
+ function StickyNoteNodeComponent({
2758
+ id,
2759
+ selected,
2760
+ data
2761
+ }) {
2762
+ const tr = useTr();
2763
+ const { setNodes, getNode } = useReactFlow();
2764
+ const zustandSetNodes = useFlowStore((s) => s.actions.nodes.setNodes);
2765
+ const deleteNode = useDeleteNode();
2766
+ const [editing, setEditing] = useState(false);
2767
+ const [showPalette, setShowPalette] = useState(false);
2768
+ const [draft, setDraft] = useState(String(data.content ?? ""));
2769
+ const textareaRef = useRef(null);
2770
+ const rootRef = useRef(null);
2771
+ useEffect(() => {
2772
+ const wrapper = rootRef.current?.closest(".react-flow__node");
2773
+ if (wrapper && wrapper.style.zIndex !== "-1") {
2774
+ wrapper.style.zIndex = "-1";
2775
+ }
2776
+ });
2777
+ const colorKey = typeof data.color === "string" && STICKY_COLORS[data.color] ? data.color : DEFAULT_STICKY_COLOR;
2778
+ const accent = STICKY_COLORS[colorKey];
2779
+ const patchData = useCallback(
2780
+ (patch) => {
2781
+ setNodes((nds) => {
2782
+ const updated = nds.map(
2783
+ (n) => n.id === id ? { ...n, data: { ...n.data, ...patch } } : n
2700
2784
  );
2701
- const stepResult = result.data?.executeStep;
2702
- if (stepResult) {
2703
- const backendRunId = stepResult.runId;
2704
- startExecution(nodeId, backendRunId, {
2705
- workflowKey: stepResult.workflowKey,
2706
- version: stepResult.version,
2707
- sourceMode: "TEST"
2708
- });
2709
- setStepPlan({
2710
- runId: backendRunId,
2711
- ...stepResult.plan,
2712
- sourceRunId: stepResult.plan.sourceRunId ?? null
2713
- });
2714
- const initialLog = {
2715
- id: `${backendRunId}-${nodeId}`,
2716
- runId: backendRunId,
2717
- nodeId,
2718
- nodeType: workflow.nodes.find((node) => node.id === nodeId)?.type || "unknown",
2719
- nodeName: getNodeName(nodeId),
2720
- status: "running",
2721
- inputArgs: null,
2722
- output: null,
2723
- error: null,
2724
- duration: null,
2725
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
2726
- endTime: null
2727
- };
2728
- addLog(initialLog);
2729
- pollRunUpdates(backendRunId);
2730
- return { success: true, runId: backendRunId, plan: stepResult.plan };
2731
- }
2732
- console.error("[useNodeExecution] No result from executeStep");
2733
- setNodeStatus(nodeId, "error");
2734
- return { success: false, error: "No result from executeStep" };
2735
- } catch (error) {
2736
- console.error("[useNodeExecution] Error executing step:", error);
2737
- const errorMessage = describeOperationError(error);
2738
- const errorRunId = nanoid$1();
2739
- const errorLog = {
2740
- id: `${errorRunId}-${nodeId}`,
2741
- runId: errorRunId,
2742
- nodeId,
2743
- nodeType: workflow.nodes.find((node) => node.id === nodeId)?.type || "unknown",
2744
- nodeName: getNodeName(nodeId),
2745
- status: "failed",
2746
- inputArgs: null,
2747
- output: null,
2748
- error: errorMessage,
2749
- duration: null,
2750
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
2751
- endTime: (/* @__PURE__ */ new Date()).toISOString()
2752
- };
2753
- addLog(errorLog);
2754
- setNodeStatus(nodeId, "error");
2755
- completeExecution();
2756
- return { success: false, error: errorMessage };
2757
- }
2785
+ zustandSetNodes(updated);
2786
+ return updated;
2787
+ });
2758
2788
  },
2759
- [
2760
- cleanup,
2761
- clearLogs,
2762
- startExecution,
2763
- setStepPlan,
2764
- workflow,
2765
- getNodeName,
2766
- addLog,
2767
- setNodeStatus,
2768
- pollRunUpdates,
2769
- completeExecution
2770
- ]
2771
- );
2772
- const executeFullWorkflow = useCallback(
2773
- async (customTriggerData) => {
2774
- const triggerNode = findTriggerNode();
2775
- if (!triggerNode) {
2776
- console.error("[useNodeExecution] No trigger node found in workflow");
2777
- return { success: false, error: "No trigger node found" };
2778
- }
2779
- cleanup();
2780
- clearLogs();
2781
- const workflowKey = workflow.workflowKey || workflow.id;
2782
- const version = workflow.version || "1.0.0";
2783
- if (!workflowKey) {
2784
- console.error(
2785
- "[useNodeExecution] Workflow must be saved before execution. No workflowKey found."
2786
- );
2787
- return {
2788
- success: false,
2789
- error: "Workflow must be saved before execution"
2790
- };
2791
- }
2792
- setNodeStatus(triggerNode.id, "running");
2793
- try {
2794
- const client = getFluidGridsClient();
2795
- const result = throwIfResultError(
2796
- await client.mutate({
2797
- mutation: TRIGGER_WORKFLOW,
2798
- variables: {
2799
- input: {
2800
- workflowKey,
2801
- version,
2802
- triggerNodeId: triggerNode.id,
2803
- triggerData: customTriggerData || {},
2804
- context: {},
2805
- // Builder "Test workflow" is always a manual/debug run.
2806
- // Pinned node data only applies to TEST executions, never a
2807
- // production trigger (webhook, schedule, API).
2808
- mode: "TEST"
2809
- }
2810
- }
2811
- })
2812
- );
2813
- const triggerResult = result.data?.triggerWorkflow;
2814
- if (triggerResult) {
2815
- const backendRunId = triggerResult.runId;
2816
- startExecution(triggerNode.id, backendRunId, {
2817
- workflowKey: triggerResult.workflowKey,
2818
- version: triggerResult.version,
2819
- sourceMode: "TEST"
2820
- });
2821
- pollRunUpdates(backendRunId);
2822
- return { success: true, runId: backendRunId };
2823
- }
2824
- console.error("[useNodeExecution] No result from trigger");
2825
- setNodeStatus(triggerNode.id, "error");
2826
- return { success: false, error: "No result from trigger" };
2827
- } catch (error) {
2828
- console.error("[useNodeExecution] Error triggering workflow:", error);
2829
- const errorMessage = describeOperationError(error);
2830
- const errorRunId = nanoid$1();
2831
- const errorLog = {
2832
- id: `${errorRunId}-${triggerNode.id}`,
2833
- runId: errorRunId,
2834
- nodeId: triggerNode.id,
2835
- nodeType: triggerNode.type || "unknown",
2836
- nodeName: getNodeName(triggerNode.id),
2837
- status: "failed",
2838
- inputArgs: null,
2839
- output: null,
2840
- error: errorMessage,
2841
- duration: null,
2842
- startTime: (/* @__PURE__ */ new Date()).toISOString(),
2843
- endTime: (/* @__PURE__ */ new Date()).toISOString()
2844
- };
2845
- addLog(errorLog);
2846
- setNodeStatus(triggerNode.id, "error");
2847
- completeExecution();
2848
- return { success: false, error: errorMessage };
2849
- }
2850
- },
2851
- [
2852
- findTriggerNode,
2853
- cleanup,
2854
- clearLogs,
2855
- startExecution,
2856
- workflow,
2857
- getNodeName,
2858
- addLog,
2859
- setNodeStatus,
2860
- pollRunUpdates,
2861
- completeExecution
2862
- ]
2863
- );
2864
- const executeFromNode = useCallback(
2865
- async (sourceRunId, nodeId) => {
2866
- cleanup();
2867
- clearLogs();
2868
- setNodeStatus(nodeId, "running");
2869
- try {
2870
- const client = getFluidGridsClient();
2871
- const result = throwIfResultError(
2872
- await client.mutate({
2873
- mutation: RUN_FROM_NODE,
2874
- variables: {
2875
- input: {
2876
- sourceRunId,
2877
- nodeId,
2878
- // Run-from-node is a debug affordance — always TEST, same
2879
- // rule as the other builder debug execution paths.
2880
- mode: "TEST"
2881
- }
2882
- }
2883
- })
2884
- );
2885
- const runFromNodeResult = result.data?.runFromNode;
2886
- if (runFromNodeResult) {
2887
- const backendRunId = runFromNodeResult.runId;
2888
- startExecution(nodeId, backendRunId, {
2889
- workflowKey: runFromNodeResult.workflowKey,
2890
- version: runFromNodeResult.version,
2891
- sourceMode: "TEST"
2892
- });
2893
- pollRunUpdates(backendRunId);
2894
- return { success: true, runId: backendRunId };
2895
- }
2896
- console.error("[useNodeExecution] No result from runFromNode");
2897
- setNodeStatus(nodeId, "error");
2898
- return { success: false, error: "No result from runFromNode" };
2899
- } catch (error) {
2900
- console.error("[useNodeExecution] Error running from node:", error);
2901
- const errorMessage = describeOperationError(error);
2902
- setNodeStatus(nodeId, "error");
2903
- completeExecution();
2904
- return { success: false, error: errorMessage };
2905
- }
2906
- },
2907
- [
2908
- cleanup,
2909
- clearLogs,
2910
- setNodeStatus,
2911
- startExecution,
2912
- pollRunUpdates,
2913
- completeExecution
2914
- ]
2915
- );
2916
- const loadExecution = useCallback(
2917
- async (runId) => {
2918
- cleanup();
2919
- const requestId = ++loadRequestRef.current;
2920
- try {
2921
- const client = getFluidGridsClient();
2922
- const result = throwIfResultError(
2923
- await client.query({
2924
- query: EXECUTION_DETAIL,
2925
- variables: { id: runId },
2926
- fetchPolicy: "network-only"
2927
- })
2928
- );
2929
- const run = result.data?.workflowRun;
2930
- if (!run) {
2931
- return { success: false, error: "Execution not found" };
2932
- }
2933
- if (run.mode !== "TEST") {
2934
- return {
2935
- success: false,
2936
- error: "Only TEST runs can be loaded for continuation."
2937
- };
2938
- }
2939
- if (!TERMINAL_STATUSES.has(run.status)) {
2940
- return {
2941
- success: false,
2942
- error: "Only terminal TEST runs can be loaded for continuation."
2943
- };
2944
- }
2945
- if (requestId !== loadRequestRef.current) {
2946
- return { success: false, error: "Execution load superseded." };
2947
- }
2948
- const currentWorkflow = useFlowStore.getState().workflow;
2949
- const currentWorkflowKey = currentWorkflow.workflowKey || currentWorkflow.id;
2950
- if (run.workflowKey !== currentWorkflowKey || run.version !== currentWorkflow.version) {
2951
- return {
2952
- success: false,
2953
- error: "The selected run belongs to a different workflow version."
2954
- };
2955
- }
2956
- const logs = (run.nodeStates ?? []).map((nodeState) => {
2957
- const rawStatus = nodeState.status.toLowerCase();
2958
- const status = rawStatus === "completed" ? "completed" : rawStatus === "failed" || rawStatus === "timeout" ? "failed" : rawStatus === "running" || rawStatus === "waiting" || rawStatus === "delayed" || rawStatus === "waiting_subworkflow" ? "running" : "pending";
2959
- return {
2960
- id: `${runId}-${nodeState.nodeId}`,
2961
- runId,
2962
- nodeId: nodeState.nodeId,
2963
- nodeType: nodeState.nodeType,
2964
- nodeName: getNodeName(nodeState.nodeId),
2965
- status,
2966
- inputArgs: nodeState.inputArgs ?? null,
2967
- output: nodeState.output ?? null,
2968
- error: nodeState.error ?? null,
2969
- duration: nodeState.duration ?? null,
2970
- startTime: nodeState.startTime ?? run.startedAt ?? "",
2971
- endTime: nodeState.endTime ?? null,
2972
- usedPinnedData: nodeState.usedPinnedData ?? false,
2973
- reusedFromRunId: nodeState.reusedFromRunId ?? null
2974
- };
2975
- });
2976
- loadDebugRun(runId, logs, {
2977
- workflowKey: run.workflowKey,
2978
- version: run.version,
2979
- sourceMode: run.mode
2980
- });
2981
- return { success: true, runId };
2982
- } catch (error) {
2983
- console.error("[useNodeExecution] Error loading execution:", error);
2984
- return { success: false, error: describeOperationError(error) };
2985
- }
2986
- },
2987
- [cleanup, getNodeName, loadDebugRun]
2988
- );
2989
- useEffect(() => {
2990
- return () => {
2991
- if (pollTimerRef.current) {
2992
- completeExecution();
2993
- }
2994
- cleanup();
2995
- };
2996
- }, [cleanup, completeExecution]);
2997
- return {
2998
- executeStep,
2999
- executeFullWorkflow,
3000
- executeFromNode,
3001
- loadExecution,
3002
- cleanup,
3003
- findTriggerNode
3004
- };
3005
- }
3006
- function useDeleteNode() {
3007
- const { getNode, getEdges, deleteElements } = useReactFlow();
3008
- const deleteNodes = useFlowStore((state) => state.actions.nodes.deleteNodes);
3009
- return useCallback(
3010
- (id) => {
3011
- const node = getNode(id);
3012
- if (!node) return;
3013
- const edges = getEdges();
3014
- const connectedEdges = getConnectedEdges([node], edges);
3015
- deleteElements({ nodes: [node], edges: connectedEdges }).then();
3016
- deleteNodes([node]);
3017
- },
3018
- [deleteElements, getEdges, getNode, deleteNodes]
3019
- );
3020
- }
3021
-
3022
- // src/builder/contants/symbols.ts
3023
- var NODE_TYPE_DRAG_DATA_FORMAT = "application/flow-builder.node-type";
3024
- var NODE_OPERATION_DRAG_DATA_FORMAT = "application/flow-builder.node-operation";
3025
- var STICKY_NOTE_TYPE = "sticky-note";
3026
- var STICKY_COLORS = {
3027
- yellow: "#F2C94C",
3028
- orange: "#EB8934",
3029
- blue: "#3B82F6",
3030
- green: "#10B981",
3031
- red: "#EF4444",
3032
- purple: "#8B5CF6",
3033
- gray: "#9CA3AF"
3034
- };
3035
- var DEFAULT_STICKY_COLOR = "yellow";
3036
- var STICKY_DEFAULT_SIZE = { width: 260, height: 170 };
3037
- var STICKY_MIN_SIZE = { width: 160, height: 90 };
3038
- function ToolbarButton({
3039
- title,
3040
- onClick,
3041
- danger,
3042
- children
3043
- }) {
3044
- return /* @__PURE__ */ jsx(
3045
- "button",
3046
- {
3047
- type: "button",
3048
- title,
3049
- onClick: (e) => {
3050
- e.stopPropagation();
3051
- onClick();
3052
- },
3053
- className: cn(
3054
- "nodrag flex size-6 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
3055
- danger && "text-[var(--color-status-error-text)] hover:text-[var(--color-status-error-text)]"
3056
- ),
3057
- children
3058
- }
3059
- );
3060
- }
3061
- function StickyNoteNodeComponent({
3062
- id,
3063
- selected,
3064
- data
3065
- }) {
3066
- const tr = useTr();
3067
- const { setNodes, getNode } = useReactFlow();
3068
- const zustandSetNodes = useFlowStore((s) => s.actions.nodes.setNodes);
3069
- const deleteNode = useDeleteNode();
3070
- const [editing, setEditing] = useState(false);
3071
- const [showPalette, setShowPalette] = useState(false);
3072
- const [draft, setDraft] = useState(String(data.content ?? ""));
3073
- const textareaRef = useRef(null);
3074
- const rootRef = useRef(null);
3075
- useEffect(() => {
3076
- const wrapper = rootRef.current?.closest(".react-flow__node");
3077
- if (wrapper && wrapper.style.zIndex !== "-1") {
3078
- wrapper.style.zIndex = "-1";
3079
- }
3080
- });
3081
- const colorKey = typeof data.color === "string" && STICKY_COLORS[data.color] ? data.color : DEFAULT_STICKY_COLOR;
3082
- const accent = STICKY_COLORS[colorKey];
3083
- const patchData = useCallback(
3084
- (patch) => {
3085
- setNodes((nds) => {
3086
- const updated = nds.map(
3087
- (n) => n.id === id ? { ...n, data: { ...n.data, ...patch } } : n
3088
- );
3089
- zustandSetNodes(updated);
3090
- return updated;
3091
- });
3092
- },
3093
- [id, setNodes, zustandSetNodes]
2789
+ [id, setNodes, zustandSetNodes]
3094
2790
  );
3095
2791
  const startEditing = useCallback(() => {
3096
2792
  setDraft(String(getNode(id)?.data?.content ?? ""));
@@ -3865,8 +3561,8 @@ function resolveOutputHandles(data) {
3865
3561
  if (!spec) return DEFAULT_HANDLES;
3866
3562
  if (isDynamicSpec(spec)) {
3867
3563
  const args = data?.args;
3868
- const record = args?.state?.[spec.fromStateRecord] ?? data?.[spec.fromStateRecord] ?? {};
3869
- const dynamic = Object.entries(record).filter(([, handleId]) => typeof handleId === "string" && handleId).map(([label, handleId]) => ({ id: handleId, label }));
3564
+ const record2 = args?.state?.[spec.fromStateRecord] ?? data?.[spec.fromStateRecord] ?? {};
3565
+ const dynamic = Object.entries(record2).filter(([, handleId]) => typeof handleId === "string" && handleId).map(([label, handleId]) => ({ id: handleId, label }));
3870
3566
  return [...dynamic, ...spec.extra ?? []];
3871
3567
  }
3872
3568
  if (spec.length === 0) return DEFAULT_HANDLES;
@@ -4283,7 +3979,11 @@ function useSaveWorkflow() {
4283
3979
  const partitioned = partitionStickyNotes(workflow.nodes ?? []);
4284
3980
  const preparedWorkflow = {
4285
3981
  ...workflow,
4286
- nodes: partitioned.nodes.map((n) => {
3982
+ nodes: normalizeWorkflowParameters(
3983
+ partitioned.nodes,
3984
+ workflow.edges ?? [],
3985
+ (type) => NODE_CONFIGS.find((config2) => config2.type === type)?.operations
3986
+ ).map((n) => {
4287
3987
  const slimData = slimNodeDataToCurrentOperation(
4288
3988
  n.data ?? {}
4289
3989
  );
@@ -5155,11 +4855,11 @@ function canRenderOperationPanel(selectedOperation, supportedOperations) {
5155
4855
  const operationName = resolveOperationName(selectedOperation, "");
5156
4856
  return operationName === "" || supportedOperations.includes(operationName);
5157
4857
  }
5158
- function isRecord3(value) {
4858
+ function isRecord5(value) {
5159
4859
  return typeof value === "object" && value !== null && !Array.isArray(value);
5160
4860
  }
5161
4861
  function readRecord2(value) {
5162
- return isRecord3(value) ? value : void 0;
4862
+ return isRecord5(value) ? value : void 0;
5163
4863
  }
5164
4864
  function readOperations(value) {
5165
4865
  const operations = Array.isArray(value) ? value : readRecord2(value)?.operations;
@@ -5185,955 +4885,1515 @@ function synchronizeOperationUpdate(currentData, updates, registeredOperations)
5185
4885
  nodeData: { ...manifest, operations: selected ? [selected] : [] }
5186
4886
  };
5187
4887
  }
5188
- function isAutoManagedConnectionAdapter(nodeType, adapter) {
5189
- const isBigConsoleDataSinkAutoAdapter = nodeType === "store/bigconsole_datasink" && adapter.name === "bigconsole" && adapter.provider === "bigconsole";
5190
- return isBigConsoleDataSinkAutoAdapter || adapter.mode === "auto" || adapter.autoProvision === true || adapter.autoManaged === true || adapter.hidden === true;
4888
+ function isAutoManagedConnectionAdapter(nodeType, adapter) {
4889
+ const isBigConsoleDataSinkAutoAdapter = nodeType === "store/bigconsole_datasink" && adapter.name === "bigconsole" && adapter.provider === "bigconsole";
4890
+ return isBigConsoleDataSinkAutoAdapter || adapter.mode === "auto" || adapter.autoProvision === true || adapter.autoManaged === true || adapter.hidden === true;
4891
+ }
4892
+ function getVisibleConnectionAdapters(nodeType, operationName, operations, nodeData) {
4893
+ const ops = (() => {
4894
+ const arr = Array.isArray(operations) ? operations : operations?.operations ?? [];
4895
+ return arr;
4896
+ })();
4897
+ const op = operationName ? ops.find((o) => o.name === operationName) : void 0;
4898
+ const adapters = op?.connectionAdapters ?? op?.credentialAdapters ?? nodeData?.connectionAdapters ?? nodeData?.credentialAdapters ?? [];
4899
+ return adapters.filter(
4900
+ (adapter) => !isAutoManagedConnectionAdapter(nodeType, adapter)
4901
+ );
4902
+ }
4903
+ function isAdapterCompatible(adapterFilter, adapterType, targetNodeKind, targetAdapterOutputType, targetNodeType) {
4904
+ if (adapterType && targetAdapterOutputType && adapterType !== targetAdapterOutputType) {
4905
+ return false;
4906
+ }
4907
+ if (adapterFilter?.kind && adapterFilter.kind.length > 0) {
4908
+ if (!targetNodeKind || !adapterFilter.kind.every((k) => targetNodeKind.includes(k))) {
4909
+ return false;
4910
+ }
4911
+ }
4912
+ if (adapterFilter?.excludeNodes && targetNodeType) {
4913
+ if (adapterFilter.excludeNodes.includes(targetNodeType)) {
4914
+ return false;
4915
+ }
4916
+ }
4917
+ return true;
4918
+ }
4919
+ function useCompatibleSubNodes(adapter) {
4920
+ return useMemo(() => {
4921
+ const out = [];
4922
+ for (const cfg of NODE_CONFIGS) {
4923
+ const nodeData = cfg.defaultData?.nodeData;
4924
+ if (!nodeData?.adapterOutputType && !nodeData?.supportsAdapterMode) {
4925
+ continue;
4926
+ }
4927
+ if (!isAdapterCompatible(
4928
+ adapter.filter,
4929
+ adapter.type,
4930
+ nodeData?.kind,
4931
+ nodeData?.adapterOutputType,
4932
+ cfg.type
4933
+ )) {
4934
+ continue;
4935
+ }
4936
+ const ops = cfg.operations ?? [];
4937
+ out.push({
4938
+ type: cfg.type,
4939
+ title: cfg.detail?.title || nodeData?.name || cfg.type,
4940
+ icon: cfg.detail?.icon || nodeData?.icon || "mynaui:puzzle",
4941
+ description: cfg.detail?.description || nodeData?.description,
4942
+ adapterOperation: ops.find((o) => o.isAdapterOperation)?.name
4943
+ });
4944
+ }
4945
+ return out.sort((a, b) => a.title.localeCompare(b.title));
4946
+ }, [adapter.filter, adapter.type]);
4947
+ }
4948
+ function AdapterPort({
4949
+ nodeId,
4950
+ adapter,
4951
+ index,
4952
+ total
4953
+ }) {
4954
+ const tr = useTr();
4955
+ const edges = useStore((s) => s.edges);
4956
+ const { getNode } = useReactFlow();
4957
+ const insertNode = useInsertNode();
4958
+ const onConnect = useFlowStore((s) => s.actions.edges.onConnect);
4959
+ const [pickerOpen, setPickerOpen] = useState(false);
4960
+ const candidates = useCompatibleSubNodes(adapter);
4961
+ const connectionCount = edges.filter(
4962
+ (e) => e.source === nodeId && e.sourceHandle === adapter.name
4963
+ ).length;
4964
+ const isAtMaxConnections = adapter.maxConnections != null && adapter.maxConnections !== void 0 && connectionCount >= adapter.maxConnections;
4965
+ const hasConnection = connectionCount > 0;
4966
+ const diamondColor = hasConnection ? "bg-[var(--color-accent-primary)]" : "bg-[var(--color-text-muted)]";
4967
+ const lineColor = hasConnection ? "before:bg-[var(--color-accent-primary)]" : "before:bg-[var(--color-border-strong)]";
4968
+ const addSubNode = useCallback(
4969
+ (candidate) => {
4970
+ setPickerOpen(false);
4971
+ const parent = getNode(nodeId);
4972
+ const origin = parent?.position ?? { x: 0, y: 0 };
4973
+ const parentHeight = parent?.measured?.height ?? 180;
4974
+ const SPACING = 180;
4975
+ const position = {
4976
+ x: origin.x + (index - (total - 1) / 2) * SPACING,
4977
+ y: origin.y + parentHeight + 190
4978
+ };
4979
+ const created = insertNode(
4980
+ candidate.type,
4981
+ position,
4982
+ candidate.adapterOperation
4983
+ );
4984
+ if (!created?.id) return;
4985
+ created.data = {
4986
+ ...created.data,
4987
+ isAdapterInstance: true
4988
+ };
4989
+ onConnect({
4990
+ source: nodeId,
4991
+ sourceHandle: adapter.name,
4992
+ target: created.id,
4993
+ targetHandle: "target"
4994
+ });
4995
+ },
4996
+ [getNode, nodeId, index, total, insertNode, onConnect, adapter.name]
4997
+ );
4998
+ return /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
4999
+ "div",
5000
+ {
5001
+ className: `p-0.5 m-0.5 text-center before:content-[''] before:h-[95%] before:left-1/2 before:-translate-x-1/2 before:w-0.5 ${lineColor} before:absolute before:top-0 before:pointer-events-none`,
5002
+ children: [
5003
+ /* @__PURE__ */ jsx("div", { className: "absolute -top-3 left-1/2 transform -translate-x-1/2 pointer-events-none", children: /* @__PURE__ */ jsx(
5004
+ "div",
5005
+ {
5006
+ className: `w-3 h-3 ${diamondColor} rotate-45 transition-transform hover:scale-110`
5007
+ }
5008
+ ) }),
5009
+ /* @__PURE__ */ jsx("div", { className: "mt-3 z-10 absolute left-1/2 transform -translate-x-1/2 font-medium bg-surface h-fit pointer-events-none", children: /* @__PURE__ */ jsxs("span", { className: "text-xs max-w-fit relative font-medium text-[var(--color-text-secondary)] whitespace-nowrap", children: [
5010
+ adapter?.name,
5011
+ adapter?.required && /* @__PURE__ */ jsx("span", { className: "text-[var(--color-status-error-text)] absolute -right-2 top-[1px]", children: "*" })
5012
+ ] }) }),
5013
+ /* @__PURE__ */ jsxs("div", { className: "mt-16 flex flex-col items-center gap-1.5", children: [
5014
+ !isAtMaxConnections && /* @__PURE__ */ jsx(
5015
+ "button",
5016
+ {
5017
+ type: "button",
5018
+ title: `Add ${adapter.name}`,
5019
+ "aria-label": `Add ${adapter.name}`,
5020
+ onClick: (e) => {
5021
+ e.stopPropagation();
5022
+ setPickerOpen((v) => !v);
5023
+ },
5024
+ className: "w-5 h-5 border-2 border-[var(--color-border-strong)] rounded text-[var(--color-text-secondary)] text-lg leading-none font-bold bg-surface flex items-center justify-center transition-colors hover:border-[var(--color-accent-primary)] hover:text-[var(--color-accent-primary)] cursor-pointer",
5025
+ children: "+"
5026
+ }
5027
+ ),
5028
+ isAtMaxConnections && /* @__PURE__ */ jsx(
5029
+ "div",
5030
+ {
5031
+ title: `${adapter.name} is full`,
5032
+ className: "w-5 h-5 border-2 border-[var(--color-border-subtle)] rounded text-[var(--color-text-disabled)] text-lg leading-none font-bold bg-surface flex items-center justify-center",
5033
+ children: "\u2022"
5034
+ }
5035
+ ),
5036
+ /* @__PURE__ */ jsx(
5037
+ Handle,
5038
+ {
5039
+ type: "source",
5040
+ position: Position.Bottom,
5041
+ id: adapter?.name,
5042
+ className: "!relative !top-auto !left-auto !right-auto !bottom-auto !transform-none !w-2.5 !h-2.5 !min-w-0 !min-h-0 !rounded-full !border-0 bg-[var(--color-accent-primary)]",
5043
+ isConnectable: !isAtMaxConnections,
5044
+ isValidConnection: (connection) => {
5045
+ if (!adapter.type && !adapter.filter) return true;
5046
+ const targetNode = getNode(connection.target);
5047
+ if (!targetNode) return true;
5048
+ const targetData = targetNode.data;
5049
+ const targetNodeData = targetData?.nodeData;
5050
+ return isAdapterCompatible(
5051
+ adapter.filter,
5052
+ adapter.type,
5053
+ targetNodeData?.kind,
5054
+ targetNodeData?.adapterOutputType,
5055
+ targetNode.type
5056
+ );
5057
+ }
5058
+ }
5059
+ )
5060
+ ] }),
5061
+ pickerOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
5062
+ /* @__PURE__ */ jsx(
5063
+ "div",
5064
+ {
5065
+ className: "fixed inset-0 z-40",
5066
+ onClick: () => setPickerOpen(false)
5067
+ }
5068
+ ),
5069
+ /* @__PURE__ */ jsxs("div", { className: "absolute z-50 top-[86px] left-1/2 -translate-x-1/2 w-60 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-elevated)] shadow-xl overflow-hidden text-left nodrag nowheel", children: [
5070
+ /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 border-b border-[var(--color-border-subtle)]", children: [
5071
+ /* @__PURE__ */ jsxs("p", { className: "text-xs font-semibold text-[var(--color-text-primary)]", children: [
5072
+ tr("fluidgrids.builder.adapterPort.addPrefix", "Add"),
5073
+ " ",
5074
+ adapter.name
5075
+ ] }),
5076
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-[var(--color-text-muted)]", children: [
5077
+ candidates.length,
5078
+ " compatible node",
5079
+ candidates.length === 1 ? "" : "s"
5080
+ ] })
5081
+ ] }),
5082
+ /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-y-auto", children: [
5083
+ candidates.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-3 text-[11px] text-[var(--color-text-muted)]", children: tr(
5084
+ "fluidgrids.builder.adapterPort.noCompatibleNodes",
5085
+ "No compatible node installed. Publish and install one from the Node Store first."
5086
+ ) }),
5087
+ candidates.map((c) => /* @__PURE__ */ jsxs(
5088
+ "button",
5089
+ {
5090
+ type: "button",
5091
+ onClick: (e) => {
5092
+ e.stopPropagation();
5093
+ addSubNode(c);
5094
+ },
5095
+ className: "w-full flex items-start gap-2 px-3 py-2 hover:bg-[var(--color-bg-elevated)] transition-colors text-left",
5096
+ children: [
5097
+ /* @__PURE__ */ jsx(
5098
+ NodeTypeIcon,
5099
+ {
5100
+ icon: c.icon,
5101
+ alt: c.title,
5102
+ className: "size-4 mt-0.5 shrink-0 text-[var(--color-accent-primary)]"
5103
+ }
5104
+ ),
5105
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
5106
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-[var(--color-text-primary)] truncate", children: c.title }),
5107
+ c.description && /* @__PURE__ */ jsx("span", { className: "block text-[10px] text-[var(--color-text-muted)] line-clamp-2", children: c.description })
5108
+ ] })
5109
+ ]
5110
+ },
5111
+ c.type
5112
+ ))
5113
+ ] })
5114
+ ] })
5115
+ ] })
5116
+ ]
5117
+ }
5118
+ ) });
5119
+ }
5120
+ function AdapterConnectors({
5121
+ nodeId,
5122
+ adapters
5123
+ }) {
5124
+ if (!adapters || adapters.length === 0) return null;
5125
+ return /* @__PURE__ */ jsx("div", { className: "left-0 w-full flex justify-around mt-[6.5px]", children: adapters.map((adapter, i) => /* @__PURE__ */ jsx(
5126
+ AdapterPort,
5127
+ {
5128
+ nodeId,
5129
+ adapter,
5130
+ index: i,
5131
+ total: adapters.length
5132
+ },
5133
+ adapter.name
5134
+ )) });
5191
5135
  }
5192
- function getVisibleConnectionAdapters(nodeType, operationName, operations, nodeData) {
5193
- const ops = (() => {
5194
- const arr = Array.isArray(operations) ? operations : operations?.operations ?? [];
5195
- return arr;
5196
- })();
5197
- const op = operationName ? ops.find((o) => o.name === operationName) : void 0;
5198
- const adapters = op?.connectionAdapters ?? op?.credentialAdapters ?? nodeData?.connectionAdapters ?? nodeData?.credentialAdapters ?? [];
5199
- return adapters.filter(
5200
- (adapter) => !isAutoManagedConnectionAdapter(nodeType, adapter)
5136
+ function SubNodeComponent({
5137
+ id,
5138
+ isConnectable,
5139
+ selected,
5140
+ data,
5141
+ nodeType
5142
+ }) {
5143
+ const meta = useMemo(() => getNodeDetail(nodeType), [nodeType]);
5144
+ const deleteNode = useDeleteNode();
5145
+ const [showNodePropertiesOf] = useFlowStore(
5146
+ useShallow((s) => [s.actions.sidebar.showNodePropertiesOf])
5147
+ );
5148
+ const openNdv = useFlowStore((s) => s.actions.ndv.open);
5149
+ const [nodeStatuses] = useFlowStore(
5150
+ useShallow((s) => [s.execution.nodeStatuses])
5151
+ );
5152
+ const nodeStatus = nodeStatuses[id] || "idle";
5153
+ const statusRing = nodeStatus === "success" ? "ring-2 ring-[var(--color-status-success-border)]" : nodeStatus === "error" ? "ring-2 ring-[var(--color-status-error-border)]" : nodeStatus === "running" ? "ring-2 ring-[var(--color-status-info-border)] animate-pulse" : "";
5154
+ const handleShowNodeProperties = useCallback(() => {
5155
+ openNdv({ nodeId: id, type: nodeType });
5156
+ showNodePropertiesOf({ id, type: nodeType });
5157
+ }, [id, nodeType, openNdv, showNodePropertiesOf]);
5158
+ return (
5159
+ // `relative` anchors the absolute delete button; `group` enables its hover reveal.
5160
+ /* @__PURE__ */ jsxs("div", { className: "relative group flex flex-col items-center", children: [
5161
+ /* @__PURE__ */ jsx(
5162
+ Handle,
5163
+ {
5164
+ type: "target",
5165
+ position: Position.Top,
5166
+ id: "target",
5167
+ isConnectable,
5168
+ className: "size-2.5 bg-[var(--color-accent-primary)] border-2 border-[var(--color-border-strong)]"
5169
+ }
5170
+ ),
5171
+ /* @__PURE__ */ jsx("div", { className: "w-0.5 h-4 border-l-2 border-dashed border-[var(--color-border-strong)]" }),
5172
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--color-text-muted)] font-medium mb-1", children: meta.title.includes("Model") ? "Model" : meta.title.includes("Memory") ? "Memory" : meta.title.includes("Tool") ? "Tool" : meta.title.includes("Parser") ? "Parser" : "Adapter" }),
5173
+ /* @__PURE__ */ jsx(
5174
+ "div",
5175
+ {
5176
+ className: `w-14 h-14 rounded-full bg-[var(--color-surface-elevated)] border-2 border-[var(--color-border-strong)] flex items-center justify-center shadow-lg cursor-pointer transition-all hover:border-[var(--color-accent-primary)] hover:shadow-xl ${statusRing}`,
5177
+ onDoubleClick: handleShowNodeProperties,
5178
+ "data-selected": selected,
5179
+ children: /* @__PURE__ */ jsx(
5180
+ NodeTypeIcon,
5181
+ {
5182
+ icon: meta.icon,
5183
+ alt: meta.title,
5184
+ className: "w-7 h-7 text-accent opacity-80"
5185
+ }
5186
+ )
5187
+ }
5188
+ ),
5189
+ /* @__PURE__ */ jsx("span", { className: "mt-1.5 text-xs text-[var(--color-text-secondary)] font-medium text-center max-w-24 leading-tight", children: data?.label || data?.args?.state?.operation || meta.title }),
5190
+ /* @__PURE__ */ jsx(
5191
+ "button",
5192
+ {
5193
+ onClick: () => deleteNode(id),
5194
+ className: "absolute -top-1 -right-1 w-4 h-4 rounded-full bg-[var(--color-status-error)] text-white text-[10px] flex items-center justify-center opacity-0 group-hover:opacity-100 hover:bg-[var(--color-status-error)] transition-opacity",
5195
+ children: "\xD7"
5196
+ }
5197
+ ),
5198
+ /* @__PURE__ */ jsx(AdapterConnectors, { nodeId: id, adapters: data?.nodeData?.nodeAdapters })
5199
+ ] })
5201
5200
  );
5202
5201
  }
5203
- function isAdapterCompatible(adapterFilter, adapterType, targetNodeKind, targetAdapterOutputType, targetNodeType) {
5204
- if (adapterType && targetAdapterOutputType && adapterType !== targetAdapterOutputType) {
5205
- return false;
5206
- }
5207
- if (adapterFilter?.kind && adapterFilter.kind.length > 0) {
5208
- if (!targetNodeKind || !adapterFilter.kind.every((k) => targetNodeKind.includes(k))) {
5209
- return false;
5210
- }
5211
- }
5212
- if (adapterFilter?.excludeNodes && targetNodeType) {
5213
- if (adapterFilter.excludeNodes.includes(targetNodeType)) {
5202
+ var SINGLE_CLICK_DELAY_MS = 230;
5203
+ var getNodeMetadata = (value) => {
5204
+ function NodeComponent(props) {
5205
+ const { id, isConnectable, selected, data, dragging } = props;
5206
+ const tr = useTr();
5207
+ const docsUrl = useNodeDocsUrl(
5208
+ value?.type,
5209
+ data?.nodeData?.version || data?.version
5210
+ );
5211
+ const { edges, nodeLookup } = useStore(({ edges: edges2, nodeLookup: nodeLookup2 }) => ({
5212
+ edges: edges2,
5213
+ nodeLookup: nodeLookup2
5214
+ }));
5215
+ const isAdapterSubNode = useMemo(() => {
5216
+ if (data?.isAdapterInstance) return true;
5217
+ const incomingEdges = edges.filter((e) => e.target === id);
5218
+ for (const edge of incomingEdges) {
5219
+ if (!edge.sourceHandle || edge.sourceHandle === "source") continue;
5220
+ const sourceNode = nodeLookup.get(edge.source);
5221
+ if (!sourceNode) continue;
5222
+ const sourceNodeData = sourceNode.data;
5223
+ const sourceAdapters = sourceNodeData?.nodeData?.nodeAdapters;
5224
+ if (sourceAdapters && Array.isArray(sourceAdapters)) {
5225
+ const adapterNames = sourceAdapters.map((a) => a.name);
5226
+ if (adapterNames.includes(edge.sourceHandle)) {
5227
+ return true;
5228
+ }
5229
+ }
5230
+ }
5214
5231
  return false;
5215
- }
5216
- }
5217
- return true;
5218
- }
5219
- function useCompatibleSubNodes(adapter) {
5220
- return useMemo(() => {
5221
- const out = [];
5222
- for (const cfg of NODE_CONFIGS) {
5223
- const nodeData = cfg.defaultData?.nodeData;
5224
- if (!nodeData?.adapterOutputType && !nodeData?.supportsAdapterMode) {
5225
- continue;
5232
+ }, [data?.isAdapterInstance, edges, id, nodeLookup]);
5233
+ const nodeType = value?.type;
5234
+ const meta = useMemo(() => getNodeDetail(nodeType), [nodeType]);
5235
+ const { setNodes } = useReactFlow();
5236
+ const [
5237
+ isExecuting,
5238
+ executingNodeId,
5239
+ nodeStatuses,
5240
+ nodeItemCounts,
5241
+ zustandSetNodes,
5242
+ renamingNodeId,
5243
+ toggleNodeDisabled,
5244
+ renameNode,
5245
+ startRename,
5246
+ endRename
5247
+ ] = useFlowStore(
5248
+ useShallow((s) => [
5249
+ s.execution.isExecuting,
5250
+ s.execution.executingNodeId,
5251
+ s.execution.nodeStatuses,
5252
+ s.execution.nodeItemCounts,
5253
+ s.actions.nodes.setNodes,
5254
+ s.workflow.renamingNodeId,
5255
+ s.actions.nodes.toggleDisabled,
5256
+ s.actions.nodes.renameNode,
5257
+ s.actions.nodes.startRename,
5258
+ s.actions.nodes.endRename
5259
+ ])
5260
+ );
5261
+ useEffect(() => {
5262
+ if (nodeType !== "integration/channel") return;
5263
+ const nodeData = data;
5264
+ const explicitRole = nodeData?.args?.state?.role;
5265
+ const hasIncomingEdge = edges.some((e) => e.target === id);
5266
+ const isAction = explicitRole === "action" || !explicitRole && hasIncomingEdge;
5267
+ const expectedOp = resolveOperationName(
5268
+ nodeData?.operation_name,
5269
+ isAction ? "sendChannelMessage" : "onChannelMessageReceived"
5270
+ );
5271
+ if (nodeData?.operation_name === expectedOp) return;
5272
+ setNodes((nds) => {
5273
+ const updated = nds.map(
5274
+ (n) => n.id === id ? {
5275
+ ...n,
5276
+ data: {
5277
+ ...n.data,
5278
+ ...synchronizeOperationUpdate(
5279
+ n.data,
5280
+ { operation_name: expectedOp },
5281
+ NODE_CONFIGS.find((config2) => config2.type === nodeType)?.operations
5282
+ )
5283
+ }
5284
+ } : n
5285
+ );
5286
+ zustandSetNodes(updated);
5287
+ return updated;
5288
+ });
5289
+ }, [nodeType, id, edges, data, setNodes, zustandSetNodes]);
5290
+ const { executeStep } = useNodeExecution();
5291
+ const deleteNode = useDeleteNode();
5292
+ const handleDeleteNode = () => {
5293
+ deleteNode(id);
5294
+ };
5295
+ const handleToggleDisabled = useCallback(() => {
5296
+ toggleNodeDisabled(id);
5297
+ }, [id, toggleNodeDisabled]);
5298
+ const isRenamingThisNode = renamingNodeId === id;
5299
+ const handleStartRename = useCallback(() => {
5300
+ startRename(id);
5301
+ }, [id, startRename]);
5302
+ const handleCommitRename = useCallback(
5303
+ (value2) => {
5304
+ renameNode(id, value2);
5305
+ },
5306
+ [id, renameNode]
5307
+ );
5308
+ const handleCancelRename = useCallback(() => {
5309
+ endRename();
5310
+ }, [endRename]);
5311
+ const openNdv = useFlowStore((s) => s.actions.ndv.open);
5312
+ const openQuickConfig = useFlowStore((s) => s.actions.quickConfig.open);
5313
+ const clickTimerRef = useRef(null);
5314
+ const wasDraggedRef = useRef(false);
5315
+ useEffect(() => {
5316
+ if (dragging) wasDraggedRef.current = true;
5317
+ }, [dragging]);
5318
+ useEffect(
5319
+ () => () => {
5320
+ if (clickTimerRef.current !== null)
5321
+ window.clearTimeout(clickTimerRef.current);
5322
+ },
5323
+ []
5324
+ );
5325
+ const handleCardClick = useCallback(() => {
5326
+ if (wasDraggedRef.current) {
5327
+ wasDraggedRef.current = false;
5328
+ return;
5226
5329
  }
5227
- if (!isAdapterCompatible(
5228
- adapter.filter,
5229
- adapter.type,
5230
- nodeData?.kind,
5231
- nodeData?.adapterOutputType,
5232
- cfg.type
5233
- )) {
5234
- continue;
5330
+ if (clickTimerRef.current !== null)
5331
+ window.clearTimeout(clickTimerRef.current);
5332
+ clickTimerRef.current = window.setTimeout(() => {
5333
+ clickTimerRef.current = null;
5334
+ openQuickConfig({
5335
+ nodeId: id,
5336
+ type: value?.type
5337
+ });
5338
+ }, SINGLE_CLICK_DELAY_MS);
5339
+ }, [id, openQuickConfig]);
5340
+ const handleOpenNdv = useCallback(() => {
5341
+ if (clickTimerRef.current !== null) {
5342
+ window.clearTimeout(clickTimerRef.current);
5343
+ clickTimerRef.current = null;
5235
5344
  }
5236
- const ops = cfg.operations ?? [];
5237
- out.push({
5238
- type: cfg.type,
5239
- title: cfg.detail?.title || nodeData?.name || cfg.type,
5240
- icon: cfg.detail?.icon || nodeData?.icon || "mynaui:puzzle",
5241
- description: cfg.detail?.description || nodeData?.description,
5242
- adapterOperation: ops.find((o) => o.isAdapterOperation)?.name
5345
+ openNdv({ nodeId: id, type: value?.type });
5346
+ }, [id, openNdv]);
5347
+ const handleExecuteStep = useCallback(() => {
5348
+ executeStep(id).then((_result) => {
5349
+ }).catch((error) => {
5350
+ console.error("[NodeComponent] Execute step error:", error);
5243
5351
  });
5244
- }
5245
- return out.sort((a, b) => a.title.localeCompare(b.title));
5246
- }, [adapter.filter, adapter.type]);
5247
- }
5248
- function AdapterPort({
5249
- nodeId,
5250
- adapter,
5251
- index,
5252
- total
5253
- }) {
5254
- const tr = useTr();
5255
- const edges = useStore((s) => s.edges);
5256
- const { getNode } = useReactFlow();
5257
- const insertNode = useInsertNode();
5258
- const onConnect = useFlowStore((s) => s.actions.edges.onConnect);
5259
- const [pickerOpen, setPickerOpen] = useState(false);
5260
- const candidates = useCompatibleSubNodes(adapter);
5261
- const connectionCount = edges.filter(
5262
- (e) => e.source === nodeId && e.sourceHandle === adapter.name
5263
- ).length;
5264
- const isAtMaxConnections = adapter.maxConnections != null && adapter.maxConnections !== void 0 && connectionCount >= adapter.maxConnections;
5265
- const hasConnection = connectionCount > 0;
5266
- const diamondColor = hasConnection ? "bg-[var(--color-accent-primary)]" : "bg-[var(--color-text-muted)]";
5267
- const lineColor = hasConnection ? "before:bg-[var(--color-accent-primary)]" : "before:bg-[var(--color-border-strong)]";
5268
- const addSubNode = useCallback(
5269
- (candidate) => {
5270
- setPickerOpen(false);
5271
- const parent = getNode(nodeId);
5272
- const origin = parent?.position ?? { x: 0, y: 0 };
5273
- const parentHeight = parent?.measured?.height ?? 180;
5274
- const SPACING = 180;
5275
- const position = {
5276
- x: origin.x + (index - (total - 1) / 2) * SPACING,
5277
- y: origin.y + parentHeight + 190
5278
- };
5279
- const created = insertNode(
5280
- candidate.type,
5281
- position,
5282
- candidate.adapterOperation
5352
+ }, [executeStep, id]);
5353
+ const missingCredentials = useMemo(() => {
5354
+ const visibleAdapters = getVisibleConnectionAdapters(
5355
+ String(value?.type ?? ""),
5356
+ data?.operation_name,
5357
+ value?.operations ?? data?.nodeData?.operations,
5358
+ data?.nodeData
5283
5359
  );
5284
- if (!created?.id) return;
5285
- created.data = {
5286
- ...created.data,
5287
- isAdapterInstance: true
5288
- };
5289
- onConnect({
5290
- source: nodeId,
5291
- sourceHandle: adapter.name,
5292
- target: created.id,
5293
- targetHandle: "target"
5294
- });
5295
- },
5296
- [getNode, nodeId, index, total, insertNode, onConnect, adapter.name]
5297
- );
5298
- return /* @__PURE__ */ jsx("div", { className: "relative", children: /* @__PURE__ */ jsxs(
5299
- "div",
5300
- {
5301
- className: `p-0.5 m-0.5 text-center before:content-[''] before:h-[95%] before:left-1/2 before:-translate-x-1/2 before:w-0.5 ${lineColor} before:absolute before:top-0 before:pointer-events-none`,
5302
- children: [
5303
- /* @__PURE__ */ jsx("div", { className: "absolute -top-3 left-1/2 transform -translate-x-1/2 pointer-events-none", children: /* @__PURE__ */ jsx(
5304
- "div",
5305
- {
5306
- className: `w-3 h-3 ${diamondColor} rotate-45 transition-transform hover:scale-110`
5307
- }
5308
- ) }),
5309
- /* @__PURE__ */ jsx("div", { className: "mt-3 z-10 absolute left-1/2 transform -translate-x-1/2 font-medium bg-surface h-fit pointer-events-none", children: /* @__PURE__ */ jsxs("span", { className: "text-xs max-w-fit relative font-medium text-[var(--color-text-secondary)] whitespace-nowrap", children: [
5310
- adapter?.name,
5311
- adapter?.required && /* @__PURE__ */ jsx("span", { className: "text-[var(--color-status-error-text)] absolute -right-2 top-[1px]", children: "*" })
5312
- ] }) }),
5313
- /* @__PURE__ */ jsxs("div", { className: "mt-16 flex flex-col items-center gap-1.5", children: [
5314
- !isAtMaxConnections && /* @__PURE__ */ jsx(
5315
- "button",
5316
- {
5317
- type: "button",
5318
- title: `Add ${adapter.name}`,
5319
- "aria-label": `Add ${adapter.name}`,
5320
- onClick: (e) => {
5321
- e.stopPropagation();
5322
- setPickerOpen((v) => !v);
5323
- },
5324
- className: "w-5 h-5 border-2 border-[var(--color-border-strong)] rounded text-[var(--color-text-secondary)] text-lg leading-none font-bold bg-surface flex items-center justify-center transition-colors hover:border-[var(--color-accent-primary)] hover:text-[var(--color-accent-primary)] cursor-pointer",
5325
- children: "+"
5326
- }
5327
- ),
5328
- isAtMaxConnections && /* @__PURE__ */ jsx(
5329
- "div",
5330
- {
5331
- title: `${adapter.name} is full`,
5332
- className: "w-5 h-5 border-2 border-[var(--color-border-subtle)] rounded text-[var(--color-text-disabled)] text-lg leading-none font-bold bg-surface flex items-center justify-center",
5333
- children: "\u2022"
5334
- }
5335
- ),
5336
- /* @__PURE__ */ jsx(
5337
- Handle,
5338
- {
5339
- type: "source",
5340
- position: Position.Bottom,
5341
- id: adapter?.name,
5342
- className: "!relative !top-auto !left-auto !right-auto !bottom-auto !transform-none !w-2.5 !h-2.5 !min-w-0 !min-h-0 !rounded-full !border-0 bg-[var(--color-accent-primary)]",
5343
- isConnectable: !isAtMaxConnections,
5344
- isValidConnection: (connection) => {
5345
- if (!adapter.type && !adapter.filter) return true;
5346
- const targetNode = getNode(connection.target);
5347
- if (!targetNode) return true;
5348
- const targetData = targetNode.data;
5349
- const targetNodeData = targetData?.nodeData;
5350
- return isAdapterCompatible(
5351
- adapter.filter,
5352
- adapter.type,
5353
- targetNodeData?.kind,
5354
- targetNodeData?.adapterOutputType,
5355
- targetNode.type
5356
- );
5357
- }
5358
- }
5359
- )
5360
- ] }),
5361
- pickerOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
5362
- /* @__PURE__ */ jsx(
5363
- "div",
5364
- {
5365
- className: "fixed inset-0 z-40",
5366
- onClick: () => setPickerOpen(false)
5367
- }
5368
- ),
5369
- /* @__PURE__ */ jsxs("div", { className: "absolute z-50 top-[86px] left-1/2 -translate-x-1/2 w-60 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-surface-elevated)] shadow-xl overflow-hidden text-left nodrag nowheel", children: [
5370
- /* @__PURE__ */ jsxs("div", { className: "px-3 py-2 border-b border-[var(--color-border-subtle)]", children: [
5371
- /* @__PURE__ */ jsxs("p", { className: "text-xs font-semibold text-[var(--color-text-primary)]", children: [
5372
- tr("fluidgrids.builder.adapterPort.addPrefix", "Add"),
5373
- " ",
5374
- adapter.name
5375
- ] }),
5376
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-[var(--color-text-muted)]", children: [
5377
- candidates.length,
5378
- " compatible node",
5379
- candidates.length === 1 ? "" : "s"
5380
- ] })
5381
- ] }),
5382
- /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-y-auto", children: [
5383
- candidates.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-3 text-[11px] text-[var(--color-text-muted)]", children: tr(
5384
- "fluidgrids.builder.adapterPort.noCompatibleNodes",
5385
- "No compatible node installed. Publish and install one from the Node Store first."
5386
- ) }),
5387
- candidates.map((c) => /* @__PURE__ */ jsxs(
5388
- "button",
5389
- {
5390
- type: "button",
5391
- onClick: (e) => {
5392
- e.stopPropagation();
5393
- addSubNode(c);
5394
- },
5395
- className: "w-full flex items-start gap-2 px-3 py-2 hover:bg-[var(--color-bg-elevated)] transition-colors text-left",
5396
- children: [
5397
- /* @__PURE__ */ jsx(
5398
- NodeTypeIcon,
5399
- {
5400
- icon: c.icon,
5401
- alt: c.title,
5402
- className: "size-4 mt-0.5 shrink-0 text-[var(--color-accent-primary)]"
5403
- }
5404
- ),
5405
- /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
5406
- /* @__PURE__ */ jsx("span", { className: "block text-xs text-[var(--color-text-primary)] truncate", children: c.title }),
5407
- c.description && /* @__PURE__ */ jsx("span", { className: "block text-[10px] text-[var(--color-text-muted)] line-clamp-2", children: c.description })
5408
- ] })
5409
- ]
5410
- },
5411
- c.type
5412
- ))
5413
- ] })
5414
- ] })
5415
- ] })
5416
- ]
5360
+ if (visibleAdapters.length === 0) return false;
5361
+ const selected2 = data?.args?.connections ?? data?.args?.credentials ?? [];
5362
+ return visibleAdapters.some(
5363
+ (adapter) => !selected2.some((sel) => sel.adapter === adapter.name)
5364
+ );
5365
+ }, [data]);
5366
+ if (isAdapterSubNode) {
5367
+ return /* @__PURE__ */ jsx(SubNodeComponent, { ...props, nodeType: value?.type });
5368
+ }
5369
+ const isThisNodeExecuting = isExecuting && executingNodeId === id;
5370
+ const nodeStatus = nodeStatuses[id] || "idle";
5371
+ const executionStatus = nodeStatus === "success" ? "success" : nodeStatus === "error" ? "error" : nodeStatus === "running" ? "running" : "idle";
5372
+ const itemCount = nodeItemCounts[id];
5373
+ const isDisabled = data?.disabled === true;
5374
+ const nodeLabel = data?.label || "";
5375
+ const nodeKinds = data?.nodeData?.kind;
5376
+ const isTrigger = isNodeTrigger({ type: value?.type, data });
5377
+ const savedManifest = data?.nodeData;
5378
+ const isRegistryFallback = meta.title === value?.type;
5379
+ const appName = (isRegistryFallback ? savedManifest?.name : meta.title) || meta.title || nodeType || tr("fluidgrids.builder.nodeCard.defaultAppName", "Node");
5380
+ const cardIcon = (isRegistryFallback ? savedManifest?.icon : meta.icon) || meta.icon;
5381
+ const operationDisplay = resolveOperationName(
5382
+ data?.operation_name,
5383
+ tr("fluidgrids.builder.nodeCard.notConfigured", "Not configured")
5384
+ );
5385
+ const nodeDescription = data?.nodeData?.description || meta?.description || "";
5386
+ const nodeInfoMetadata = {
5387
+ icon: meta.icon,
5388
+ title: meta.title,
5389
+ description: nodeDescription,
5390
+ nodeType: value?.type,
5391
+ version: data?.nodeData?.version || data?.version,
5392
+ kind: nodeKinds,
5393
+ operation: data?.operation_name,
5394
+ docsUrl
5395
+ };
5396
+ const isLogicNode = /(^|[:/])(if|switch|merge|loop|filter|branch|router|condition|aggregate)/.test(
5397
+ (value?.type || "").toLowerCase()
5398
+ );
5399
+ const roleAccent = isTrigger ? "#10b981" : isLogicNode ? "#f59e0b" : "#3b82f6";
5400
+ const outputHandles = resolveOutputHandles(
5401
+ data
5402
+ );
5403
+ const inputHandles = resolveInputHandles(
5404
+ data
5405
+ );
5406
+ const handleSpacer = outputHandlesSpacerHeight(
5407
+ Math.max(outputHandles.length, inputHandles.length)
5408
+ );
5409
+ const manifestKind = data?.nodeData?.kind;
5410
+ const isControlFlowNode = (value?.type || "").startsWith("internal:") && (Array.isArray(manifestKind) ? manifestKind.includes("control") : isLogicNode);
5411
+ if (isControlFlowNode) {
5412
+ const argsState = data?.args?.state;
5413
+ const rules = argsState?.rules;
5414
+ const mode = argsState?.mode;
5415
+ const logicSubtitle = Array.isArray(rules) ? `${rules.length} rule${rules.length === 1 ? "" : "s"}` : typeof mode === "string" ? mode : void 0;
5416
+ return /* @__PURE__ */ jsxs("div", { children: [
5417
+ /* @__PURE__ */ jsx(
5418
+ LogicNodeCard,
5419
+ {
5420
+ nodeId: id,
5421
+ title: data?.label || meta.title,
5422
+ subtitle: logicSubtitle,
5423
+ icon: meta.icon,
5424
+ accent: roleAccent,
5425
+ selected: !!selected,
5426
+ executionStatus,
5427
+ itemCount,
5428
+ isTrigger,
5429
+ isExecuting: isThisNodeExecuting,
5430
+ inputHandles,
5431
+ outputHandles,
5432
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0,
5433
+ onClick: handleCardClick,
5434
+ onDoubleClick: handleOpenNdv,
5435
+ onSettings: handleOpenNdv,
5436
+ onExecute: handleExecuteStep,
5437
+ onDelete: handleDeleteNode,
5438
+ infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
5439
+ disabled: isDisabled,
5440
+ onToggleDisabled: handleToggleDisabled,
5441
+ isRenaming: isRenamingThisNode,
5442
+ onStartRename: handleStartRename,
5443
+ onCommitRename: handleCommitRename,
5444
+ onCancelRename: handleCancelRename
5445
+ }
5446
+ ),
5447
+ /* @__PURE__ */ jsx(
5448
+ AdapterConnectors,
5449
+ {
5450
+ nodeId: id,
5451
+ adapters: data?.nodeData?.nodeAdapters
5452
+ }
5453
+ )
5454
+ ] });
5417
5455
  }
5418
- ) });
5419
- }
5420
- function AdapterConnectors({
5421
- nodeId,
5422
- adapters
5423
- }) {
5424
- if (!adapters || adapters.length === 0) return null;
5425
- return /* @__PURE__ */ jsx("div", { className: "left-0 w-full flex justify-around mt-[6.5px]", children: adapters.map((adapter, i) => /* @__PURE__ */ jsx(
5426
- AdapterPort,
5427
- {
5428
- nodeId,
5429
- adapter,
5430
- index: i,
5431
- total: adapters.length
5432
- },
5433
- adapter.name
5434
- )) });
5435
- }
5436
- function SubNodeComponent({
5437
- id,
5438
- isConnectable,
5439
- selected,
5440
- data,
5441
- nodeType
5442
- }) {
5443
- const meta = useMemo(() => getNodeDetail(nodeType), [nodeType]);
5444
- const deleteNode = useDeleteNode();
5445
- const [showNodePropertiesOf] = useFlowStore(
5446
- useShallow((s) => [s.actions.sidebar.showNodePropertiesOf])
5447
- );
5448
- const openNdv = useFlowStore((s) => s.actions.ndv.open);
5449
- const [nodeStatuses] = useFlowStore(
5450
- useShallow((s) => [s.execution.nodeStatuses])
5451
- );
5452
- const nodeStatus = nodeStatuses[id] || "idle";
5453
- const statusRing = nodeStatus === "success" ? "ring-2 ring-[var(--color-status-success-border)]" : nodeStatus === "error" ? "ring-2 ring-[var(--color-status-error-border)]" : nodeStatus === "running" ? "ring-2 ring-[var(--color-status-info-border)] animate-pulse" : "";
5454
- const handleShowNodeProperties = useCallback(() => {
5455
- openNdv({ nodeId: id, type: nodeType });
5456
- showNodePropertiesOf({ id, type: nodeType });
5457
- }, [id, nodeType, openNdv, showNodePropertiesOf]);
5458
- return (
5459
- // `relative` anchors the absolute delete button; `group` enables its hover reveal.
5460
- /* @__PURE__ */ jsxs("div", { className: "relative group flex flex-col items-center", children: [
5461
- /* @__PURE__ */ jsx(
5462
- Handle,
5463
- {
5464
- type: "target",
5465
- position: Position.Top,
5466
- id: "target",
5467
- isConnectable,
5468
- className: "size-2.5 bg-[var(--color-accent-primary)] border-2 border-[var(--color-border-strong)]"
5469
- }
5470
- ),
5471
- /* @__PURE__ */ jsx("div", { className: "w-0.5 h-4 border-l-2 border-dashed border-[var(--color-border-strong)]" }),
5472
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-[var(--color-text-muted)] font-medium mb-1", children: meta.title.includes("Model") ? "Model" : meta.title.includes("Memory") ? "Memory" : meta.title.includes("Tool") ? "Tool" : meta.title.includes("Parser") ? "Parser" : "Adapter" }),
5473
- /* @__PURE__ */ jsx(
5474
- "div",
5456
+ return /* @__PURE__ */ jsxs("div", { children: [
5457
+ /* @__PURE__ */ jsxs(
5458
+ NodeCard,
5475
5459
  {
5476
- className: `w-14 h-14 rounded-full bg-[var(--color-surface-elevated)] border-2 border-[var(--color-border-strong)] flex items-center justify-center shadow-lg cursor-pointer transition-all hover:border-[var(--color-accent-primary)] hover:shadow-xl ${statusRing}`,
5477
- onDoubleClick: handleShowNodeProperties,
5478
5460
  "data-selected": selected,
5479
- children: /* @__PURE__ */ jsx(
5480
- NodeTypeIcon,
5481
- {
5482
- icon: meta.icon,
5483
- alt: meta.title,
5484
- className: "w-7 h-7 text-accent opacity-80"
5485
- }
5486
- )
5461
+ selected,
5462
+ onClick: handleCardClick,
5463
+ onDoubleClick: handleOpenNdv,
5464
+ executionStatus,
5465
+ itemCount,
5466
+ isTrigger,
5467
+ accent: roleAccent,
5468
+ isExecuting: isThisNodeExecuting,
5469
+ onSettings: handleOpenNdv,
5470
+ onExecute: handleExecuteStep,
5471
+ onDelete: handleDeleteNode,
5472
+ infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
5473
+ disabled: isDisabled,
5474
+ onToggleDisabled: handleToggleDisabled,
5475
+ children: [
5476
+ /* @__PURE__ */ jsx(
5477
+ NodeCardHeader,
5478
+ {
5479
+ icon: cardIcon,
5480
+ appName,
5481
+ accentKey: value?.type || meta.title,
5482
+ missingCredentials,
5483
+ label: nodeLabel,
5484
+ isRenaming: isRenamingThisNode,
5485
+ onStartRename: handleStartRename,
5486
+ onCommitRename: handleCommitRename,
5487
+ onCancelRename: handleCancelRename
5488
+ }
5489
+ ),
5490
+ operationDisplay === "execute" && (value?.type || "").startsWith("internal:") || isTrigger && !resolveOperationName(data?.operation_name, "") ? /* @__PURE__ */ jsx("div", { className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5", children: /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-snug text-muted-foreground", children: nodeDescription }) }) : /* @__PURE__ */ jsx(
5491
+ NodeCardBody,
5492
+ {
5493
+ operationName: operationDisplay,
5494
+ description: nodeDescription
5495
+ }
5496
+ ),
5497
+ handleSpacer > 0 && /* @__PURE__ */ jsx("div", { style: { height: handleSpacer } }),
5498
+ !isTrigger && /* @__PURE__ */ jsx(
5499
+ InputHandles,
5500
+ {
5501
+ nodeId: id,
5502
+ handles: inputHandles,
5503
+ accent: roleAccent,
5504
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
5505
+ }
5506
+ ),
5507
+ /* @__PURE__ */ jsx(
5508
+ OutputHandles,
5509
+ {
5510
+ nodeId: id,
5511
+ handles: outputHandles,
5512
+ accent: roleAccent,
5513
+ isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
5514
+ }
5515
+ )
5516
+ ]
5487
5517
  }
5488
5518
  ),
5489
- /* @__PURE__ */ jsx("span", { className: "mt-1.5 text-xs text-[var(--color-text-secondary)] font-medium text-center max-w-24 leading-tight", children: data?.label || data?.args?.state?.operation || meta.title }),
5490
5519
  /* @__PURE__ */ jsx(
5491
- "button",
5520
+ AdapterConnectors,
5492
5521
  {
5493
- onClick: () => deleteNode(id),
5494
- className: "absolute -top-1 -right-1 w-4 h-4 rounded-full bg-[var(--color-status-error)] text-white text-[10px] flex items-center justify-center opacity-0 group-hover:opacity-100 hover:bg-[var(--color-status-error)] transition-opacity",
5495
- children: "\xD7"
5522
+ nodeId: id,
5523
+ adapters: data?.nodeData?.nodeAdapters
5496
5524
  }
5497
- ),
5498
- /* @__PURE__ */ jsx(AdapterConnectors, { nodeId: id, adapters: data?.nodeData?.nodeAdapters })
5499
- ] })
5500
- );
5501
- }
5502
- var SINGLE_CLICK_DELAY_MS = 230;
5503
- var getNodeMetadata = (value) => {
5504
- function NodeComponent(props) {
5505
- const { id, isConnectable, selected, data, dragging } = props;
5506
- const tr = useTr();
5507
- const docsUrl = useNodeDocsUrl(
5508
- value?.type,
5509
- data?.nodeData?.version || data?.version
5510
- );
5511
- const { edges, nodeLookup } = useStore(({ edges: edges2, nodeLookup: nodeLookup2 }) => ({
5512
- edges: edges2,
5513
- nodeLookup: nodeLookup2
5514
- }));
5515
- const isAdapterSubNode = useMemo(() => {
5516
- if (data?.isAdapterInstance) return true;
5517
- const incomingEdges = edges.filter((e) => e.target === id);
5518
- for (const edge of incomingEdges) {
5519
- if (!edge.sourceHandle || edge.sourceHandle === "source") continue;
5520
- const sourceNode = nodeLookup.get(edge.source);
5521
- if (!sourceNode) continue;
5522
- const sourceNodeData = sourceNode.data;
5523
- const sourceAdapters = sourceNodeData?.nodeData?.nodeAdapters;
5524
- if (sourceAdapters && Array.isArray(sourceAdapters)) {
5525
- const adapterNames = sourceAdapters.map((a) => a.name);
5526
- if (adapterNames.includes(edge.sourceHandle)) {
5527
- return true;
5525
+ )
5526
+ ] });
5527
+ }
5528
+ return {
5529
+ type: value?.type,
5530
+ node: memo(NodeComponent),
5531
+ detail: {
5532
+ icon: "mynaui:message-solid",
5533
+ title: "Gmail",
5534
+ description: "Send a text message to the user using different messaging platforms like WhatsApp, Messenger, etc."
5535
+ },
5536
+ connection: {
5537
+ inputs: 1,
5538
+ outputs: 1
5539
+ },
5540
+ defaultData: {
5541
+ operation_name: "",
5542
+ operation_type: "",
5543
+ version: value.defaultData?.version,
5544
+ nodeData: value.defaultData?.nodeData,
5545
+ args: value.defaultData?.args
5546
+ },
5547
+ propertyPanel: NodePropertyPanel
5548
+ };
5549
+ };
5550
+
5551
+ // src/builder/components/flow-builder/components/blocks/index.ts
5552
+ var NODE_ENDPOINT_CONFIGS = NodeEndpointConfigsDocument;
5553
+ var NODE_MANIFEST = NodeManifestDocument;
5554
+ var NODE_CONFIGS = [];
5555
+ async function fetchNodeManifest(nodeType, nodeVersion) {
5556
+ try {
5557
+ const client = getFluidGridsClient();
5558
+ const { data } = await client.query({
5559
+ query: NODE_MANIFEST,
5560
+ variables: { nodeType, version: nodeVersion }
5561
+ });
5562
+ return data?.nodeManifest ?? null;
5563
+ } catch (error) {
5564
+ const message = error instanceof Error ? error.message : "Unknown error";
5565
+ console.error(
5566
+ `Error fetching manifest for ${nodeType} v${nodeVersion}:`,
5567
+ message
5568
+ );
5569
+ return null;
5570
+ }
5571
+ }
5572
+ function pushStoreNodeConfig(manifest, publishType = "STORE") {
5573
+ if (NODE_CONFIGS.some((c) => c.type === manifest.nodeType)) return;
5574
+ const manifestOps = manifest.operations;
5575
+ const operations = Array.isArray(manifestOps) ? manifestOps : manifestOps?.operations ?? manifestOps;
5576
+ NODE_CONFIGS.push({
5577
+ type: manifest.nodeType,
5578
+ detail: {
5579
+ icon: manifest.icon || "mynaui:keyboard-solid",
5580
+ title: manifest.name,
5581
+ description: manifest.description,
5582
+ gradientColor: "yellow"
5583
+ },
5584
+ defaultData: {
5585
+ operation_name: "",
5586
+ operation_type: "",
5587
+ nodeData: manifest,
5588
+ version: manifest.version,
5589
+ args: {
5590
+ context: {
5591
+ node: {
5592
+ id: manifest.nodeType,
5593
+ kind: manifest.kind,
5594
+ type: manifest.nodeType
5528
5595
  }
5529
- }
5596
+ },
5597
+ state: {},
5598
+ credentials: []
5530
5599
  }
5531
- return false;
5532
- }, [data?.isAdapterInstance, edges, id, nodeLookup]);
5533
- const nodeType = value?.type;
5534
- const meta = useMemo(() => getNodeDetail(nodeType), [nodeType]);
5535
- const { setNodes } = useReactFlow();
5536
- const [
5537
- isExecuting,
5538
- executingNodeId,
5539
- nodeStatuses,
5540
- nodeItemCounts,
5541
- zustandSetNodes,
5542
- renamingNodeId,
5543
- toggleNodeDisabled,
5544
- renameNode,
5545
- startRename,
5546
- endRename
5547
- ] = useFlowStore(
5548
- useShallow((s) => [
5549
- s.execution.isExecuting,
5550
- s.execution.executingNodeId,
5551
- s.execution.nodeStatuses,
5552
- s.execution.nodeItemCounts,
5553
- s.actions.nodes.setNodes,
5554
- s.workflow.renamingNodeId,
5555
- s.actions.nodes.toggleDisabled,
5556
- s.actions.nodes.renameNode,
5557
- s.actions.nodes.startRename,
5558
- s.actions.nodes.endRename
5559
- ])
5560
- );
5561
- useEffect(() => {
5562
- if (nodeType !== "integration/channel") return;
5563
- const nodeData = data;
5564
- const explicitRole = nodeData?.args?.state?.role;
5565
- const hasIncomingEdge = edges.some((e) => e.target === id);
5566
- const isAction = explicitRole === "action" || !explicitRole && hasIncomingEdge;
5567
- const expectedOp = resolveOperationName(
5568
- nodeData?.operation_name,
5569
- isAction ? "sendChannelMessage" : "onChannelMessageReceived"
5600
+ },
5601
+ operations,
5602
+ publishType
5603
+ });
5604
+ }
5605
+ async function loadInstalledStoreNodes() {
5606
+ const globalClient = getGlobalStoreClient();
5607
+ if (!globalClient) return;
5608
+ const { workspaceId } = getFluidGridsConfig();
5609
+ if (!workspaceId) return;
5610
+ try {
5611
+ const { data } = await globalClient.query({
5612
+ query: GetMyNodeInstallationsDocument,
5613
+ variables: { workspaceId, limit: 200, offset: 0 },
5614
+ fetchPolicy: "network-only"
5615
+ });
5616
+ const installations = data?.myStoreAppInstallations?.items ?? [];
5617
+ let loaded = 0;
5618
+ for (const installation of installations) {
5619
+ if (installation.status !== "ACTIVE") continue;
5620
+ const manifest = installation.manifest;
5621
+ if (!manifest?.nodeType) continue;
5622
+ pushStoreNodeConfig(manifest, "STORE");
5623
+ loaded++;
5624
+ }
5625
+ if (loaded > 0) {
5626
+ console.log(
5627
+ `[Workflows] Loaded ${loaded} installed store node(s) from workspace installations.`
5570
5628
  );
5571
- if (nodeData?.operation_name === expectedOp) return;
5572
- setNodes((nds) => {
5573
- const updated = nds.map(
5574
- (n) => n.id === id ? {
5575
- ...n,
5576
- data: {
5577
- ...n.data,
5578
- ...synchronizeOperationUpdate(
5579
- n.data,
5580
- { operation_name: expectedOp },
5581
- NODE_CONFIGS.find((config2) => config2.type === nodeType)?.operations
5582
- )
5583
- }
5584
- } : n
5629
+ }
5630
+ } catch (error) {
5631
+ const message = error instanceof Error ? error.message : "Unknown error";
5632
+ console.warn("[Workflows] Could not load installed store nodes:", message);
5633
+ }
5634
+ }
5635
+ async function initializeNodeConfigs() {
5636
+ try {
5637
+ const client = await waitForFluidGridsClient(15e3, 150);
5638
+ if (!client) {
5639
+ console.warn(
5640
+ "[Workflows] Apollo client not available after timeout \u2014 loading store nodes only."
5641
+ );
5642
+ await loadInstalledStoreNodes();
5643
+ return;
5644
+ }
5645
+ const { data } = await client.query({
5646
+ query: NODE_ENDPOINT_CONFIGS,
5647
+ variables: {
5648
+ status: "AVAILABLE",
5649
+ pagination: { limit: 100 }
5650
+ }
5651
+ });
5652
+ const nodes = data?.nodeEndpointConfigs?.configs ?? [];
5653
+ if (!Array.isArray(nodes) || nodes.length === 0) {
5654
+ console.warn("No nodes found or received data is not an array.");
5655
+ await loadInstalledStoreNodes();
5656
+ return;
5657
+ }
5658
+ const manifestPromises = nodes.map(async (node) => {
5659
+ if (node?.nodeType && node?.nodeVersion) {
5660
+ try {
5661
+ return await fetchNodeManifest(node.nodeType, node.nodeVersion);
5662
+ } catch {
5663
+ console.error(
5664
+ `Failed to fetch manifest for node ${node.id} (${node.nodeType} v${node.nodeVersion})`
5665
+ );
5666
+ return null;
5667
+ }
5668
+ } else {
5669
+ console.warn(
5670
+ `Node ${node?.id ?? "unknown"} is missing nodeType or nodeVersion. Skipping manifest fetch.`
5585
5671
  );
5586
- zustandSetNodes(updated);
5587
- return updated;
5672
+ return null;
5673
+ }
5674
+ });
5675
+ const manifests = await Promise.all(manifestPromises);
5676
+ const successfulManifests = manifests.filter(
5677
+ (m) => m !== null
5678
+ );
5679
+ successfulManifests.forEach((manifest, index) => {
5680
+ const node = nodes[index];
5681
+ const manifestOps = manifest.operations;
5682
+ const operations = Array.isArray(manifestOps) ? manifestOps : manifestOps?.operations ?? manifestOps;
5683
+ NODE_CONFIGS.push({
5684
+ type: manifest.nodeType,
5685
+ // Use camelCase nodeType
5686
+ detail: {
5687
+ icon: manifest.icon || "mynaui:keyboard-solid",
5688
+ title: manifest.name,
5689
+ description: manifest.description,
5690
+ gradientColor: "yellow"
5691
+ // Default gradient for dynamic nodes
5692
+ },
5693
+ defaultData: {
5694
+ operation_name: "",
5695
+ operation_type: "",
5696
+ nodeData: manifest,
5697
+ version: manifest.version,
5698
+ args: {
5699
+ context: {
5700
+ node: {
5701
+ id: manifest.nodeType,
5702
+ kind: manifest.kind,
5703
+ type: manifest.nodeType
5704
+ }
5705
+ },
5706
+ state: {},
5707
+ credentials: []
5708
+ }
5709
+ },
5710
+ operations,
5711
+ publishType: node.publishType
5712
+ // Preserve publishType from GraphQL query
5588
5713
  });
5589
- }, [nodeType, id, edges, data, setNodes, zustandSetNodes]);
5590
- const { executeStep } = useNodeExecution();
5591
- const deleteNode = useDeleteNode();
5592
- const handleDeleteNode = () => {
5593
- deleteNode(id);
5714
+ });
5715
+ await loadInstalledStoreNodes();
5716
+ } catch (error) {
5717
+ const message = error instanceof Error ? error.message : "Unknown error";
5718
+ console.error("Failed to fetch nodes or manifests:", message);
5719
+ }
5720
+ }
5721
+ async function refetchStoreNodes() {
5722
+ try {
5723
+ NODE_CONFIGS.length = 0;
5724
+ NODES.length = 0;
5725
+ AVAILABLE_NODES.length = 0;
5726
+ for (const key of Object.keys(NODE_TYPES)) delete NODE_TYPES[key];
5727
+ for (const key of Object.keys(NODES_METADATA))
5728
+ delete NODES_METADATA[key];
5729
+ isInitialized = false;
5730
+ initPromise = null;
5731
+ const fallback = getNodeMetadata({ type: "__default_dynamic__" });
5732
+ if (fallback?.node) {
5733
+ NODE_TYPES["default"] = fallback.node;
5734
+ }
5735
+ NODE_TYPES[STICKY_NOTE_TYPE] = StickyNoteNode;
5736
+ await initializeNodeConfigs();
5737
+ rebuildNodeArrays();
5738
+ } catch (error) {
5739
+ console.error("[Blocks] Failed to refetch nodes:", error);
5740
+ }
5741
+ }
5742
+ var NODES = [];
5743
+ var NODE_TYPES = {};
5744
+ var NODES_METADATA = {};
5745
+ var AVAILABLE_NODES = [];
5746
+ var initPromise = null;
5747
+ var isInitialized = false;
5748
+ var nodeTypesListeners = /* @__PURE__ */ new Set();
5749
+ function onNodeTypesUpdate(listener) {
5750
+ nodeTypesListeners.add(listener);
5751
+ return () => nodeTypesListeners.delete(listener);
5752
+ }
5753
+ function notifyNodeTypesListeners() {
5754
+ nodeTypesListeners.forEach((listener) => listener());
5755
+ }
5756
+ function ensureNodeConfigsLoaded() {
5757
+ if (isInitialized) {
5758
+ return Promise.resolve();
5759
+ }
5760
+ if (!initPromise) {
5761
+ initPromise = initializeNodeConfigs().then(() => {
5762
+ if (NODE_CONFIGS.length > 0) {
5763
+ isInitialized = true;
5764
+ } else {
5765
+ initPromise = null;
5766
+ }
5767
+ rebuildNodeArrays();
5768
+ });
5769
+ }
5770
+ return initPromise;
5771
+ }
5772
+ function isNodeConfigsInitialized() {
5773
+ return isInitialized;
5774
+ }
5775
+ function rebuildNodeArrays() {
5776
+ NODE_CONFIGS.forEach((config2) => {
5777
+ const data = getNodeMetadata(config2);
5778
+ const nodeMetadata = {
5779
+ ...data,
5780
+ detail: { ...config2.detail }
5594
5781
  };
5595
- const handleToggleDisabled = useCallback(() => {
5596
- toggleNodeDisabled(id);
5597
- }, [id, toggleNodeDisabled]);
5598
- const isRenamingThisNode = renamingNodeId === id;
5599
- const handleStartRename = useCallback(() => {
5600
- startRename(id);
5601
- }, [id, startRename]);
5602
- const handleCommitRename = useCallback(
5603
- (value2) => {
5604
- renameNode(id, value2);
5605
- },
5606
- [id, renameNode]
5607
- );
5608
- const handleCancelRename = useCallback(() => {
5609
- endRename();
5610
- }, [endRename]);
5611
- const openNdv = useFlowStore((s) => s.actions.ndv.open);
5612
- const openQuickConfig = useFlowStore((s) => s.actions.quickConfig.open);
5613
- const clickTimerRef = useRef(null);
5614
- const wasDraggedRef = useRef(false);
5615
- useEffect(() => {
5616
- if (dragging) wasDraggedRef.current = true;
5617
- }, [dragging]);
5618
- useEffect(
5619
- () => () => {
5620
- if (clickTimerRef.current !== null)
5621
- window.clearTimeout(clickTimerRef.current);
5622
- },
5623
- []
5624
- );
5625
- const handleCardClick = useCallback(() => {
5626
- if (wasDraggedRef.current) {
5627
- wasDraggedRef.current = false;
5628
- return;
5782
+ if (!NODES.some((n) => n.type === nodeMetadata.type)) {
5783
+ NODES.push(nodeMetadata);
5784
+ NODE_TYPES[nodeMetadata.type] = nodeMetadata.node;
5785
+ NODES_METADATA[nodeMetadata.type] = {
5786
+ ...nodeMetadata,
5787
+ __details: { type: nodeMetadata.type, ...nodeMetadata.detail }
5788
+ };
5789
+ }
5790
+ });
5791
+ const newAvailableNodes = NODES.filter(
5792
+ (node) => node.available === void 0 || node.available
5793
+ ).map((node) => {
5794
+ const nodeConfig = NODE_CONFIGS.find((config2) => config2.type === node.type);
5795
+ return {
5796
+ type: node.type,
5797
+ icon: node.detail.icon,
5798
+ title: node.detail.title,
5799
+ description: node.detail.description,
5800
+ gradientColor: node.detail.gradientColor ?? "gray",
5801
+ manifest: node.defaultData?.nodeData,
5802
+ publishType: nodeConfig?.publishType
5803
+ // Include publishType
5804
+ };
5805
+ });
5806
+ AVAILABLE_NODES.length = 0;
5807
+ AVAILABLE_NODES.push(...newAvailableNodes);
5808
+ notifyNodeTypesListeners();
5809
+ }
5810
+ ensureNodeConfigsLoaded().catch((err) => {
5811
+ console.warn(
5812
+ "[Workflows] Background node config initialization failed:",
5813
+ err
5814
+ );
5815
+ });
5816
+ var fallbackDynamic = getNodeMetadata({ type: "__default_dynamic__" });
5817
+ if (fallbackDynamic?.node) {
5818
+ NODE_TYPES["default"] = fallbackDynamic.node;
5819
+ }
5820
+ NODE_TYPES[STICKY_NOTE_TYPE] = StickyNoteNode;
5821
+ function registerWorkflowNodeTypes(workflowNodes) {
5822
+ for (const node of workflowNodes) {
5823
+ if (node.type && !NODE_TYPES[node.type]) {
5824
+ const meta = getNodeMetadata({
5825
+ type: node.type,
5826
+ defaultData: {
5827
+ version: node.data?.version,
5828
+ nodeData: node.data?.nodeData
5829
+ },
5830
+ operations: node.data?.operations
5831
+ });
5832
+ if (meta?.node) {
5833
+ NODE_TYPES[node.type] = meta.node;
5629
5834
  }
5630
- if (clickTimerRef.current !== null)
5631
- window.clearTimeout(clickTimerRef.current);
5632
- clickTimerRef.current = window.setTimeout(() => {
5633
- clickTimerRef.current = null;
5634
- openQuickConfig({
5635
- nodeId: id,
5636
- type: value?.type
5637
- });
5638
- }, SINGLE_CLICK_DELAY_MS);
5639
- }, [id, openQuickConfig]);
5640
- const handleOpenNdv = useCallback(() => {
5641
- if (clickTimerRef.current !== null) {
5642
- window.clearTimeout(clickTimerRef.current);
5643
- clickTimerRef.current = null;
5835
+ }
5836
+ }
5837
+ }
5838
+
5839
+ // src/hooks/useNodeExecution.ts
5840
+ var TRIGGER_WORKFLOW = TriggerWorkflowDocument;
5841
+ var EXECUTE_STEP = ExecuteStepDocument;
5842
+ var WORKFLOW_RUN = WorkflowRunDocument;
5843
+ var RUN_FROM_NODE = RunFromNodeDocument;
5844
+ var EXECUTION_DETAIL = ExecutionDetailDocument;
5845
+ var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
5846
+ "COMPLETED",
5847
+ "FAILED",
5848
+ "CANCELLED",
5849
+ "TERMINATED"
5850
+ ]);
5851
+ var POLL_INTERVAL = 1500;
5852
+ var MAX_RUN_CREATION_WAIT = 6e4;
5853
+ function calculateItemCount(output) {
5854
+ if (!output || Object.keys(output).length === 0) {
5855
+ return 0;
5856
+ }
5857
+ const values = Object.values(output);
5858
+ for (const value of values) {
5859
+ if (Array.isArray(value)) {
5860
+ return value.length;
5861
+ }
5862
+ if (typeof value === "object" && value !== null) {
5863
+ const nestedValues = Object.values(value);
5864
+ for (const nestedValue of nestedValues) {
5865
+ if (Array.isArray(nestedValue)) {
5866
+ return nestedValue.length;
5867
+ }
5644
5868
  }
5645
- openNdv({ nodeId: id, type: value?.type });
5646
- }, [id, openNdv]);
5647
- const handleExecuteStep = useCallback(() => {
5648
- executeStep(id).then((_result) => {
5649
- }).catch((error) => {
5650
- console.error("[NodeComponent] Execute step error:", error);
5651
- });
5652
- }, [executeStep, id]);
5653
- const missingCredentials = useMemo(() => {
5654
- const visibleAdapters = getVisibleConnectionAdapters(
5655
- String(value?.type ?? ""),
5656
- data?.operation_name,
5657
- value?.operations ?? data?.nodeData?.operations,
5658
- data?.nodeData
5659
- );
5660
- if (visibleAdapters.length === 0) return false;
5661
- const selected2 = data?.args?.connections ?? data?.args?.credentials ?? [];
5662
- return visibleAdapters.some(
5663
- (adapter) => !selected2.some((sel) => sel.adapter === adapter.name)
5664
- );
5665
- }, [data]);
5666
- if (isAdapterSubNode) {
5667
- return /* @__PURE__ */ jsx(SubNodeComponent, { ...props, nodeType: value?.type });
5668
5869
  }
5669
- const isThisNodeExecuting = isExecuting && executingNodeId === id;
5670
- const nodeStatus = nodeStatuses[id] || "idle";
5671
- const executionStatus = nodeStatus === "success" ? "success" : nodeStatus === "error" ? "error" : nodeStatus === "running" ? "running" : "idle";
5672
- const itemCount = nodeItemCounts[id];
5673
- const isDisabled = data?.disabled === true;
5674
- const nodeLabel = data?.label || "";
5675
- const nodeKinds = data?.nodeData?.kind;
5676
- const isTrigger = isNodeTrigger({ type: value?.type, data });
5677
- const savedManifest = data?.nodeData;
5678
- const isRegistryFallback = meta.title === value?.type;
5679
- const appName = (isRegistryFallback ? savedManifest?.name : meta.title) || meta.title || nodeType || tr("fluidgrids.builder.nodeCard.defaultAppName", "Node");
5680
- const cardIcon = (isRegistryFallback ? savedManifest?.icon : meta.icon) || meta.icon;
5681
- const operationDisplay = resolveOperationName(
5682
- data?.operation_name,
5683
- tr("fluidgrids.builder.nodeCard.notConfigured", "Not configured")
5684
- );
5685
- const nodeDescription = data?.nodeData?.description || meta?.description || "";
5686
- const nodeInfoMetadata = {
5687
- icon: meta.icon,
5688
- title: meta.title,
5689
- description: nodeDescription,
5690
- nodeType: value?.type,
5691
- version: data?.nodeData?.version || data?.version,
5692
- kind: nodeKinds,
5693
- operation: data?.operation_name,
5694
- docsUrl
5695
- };
5696
- const isLogicNode = /(^|[:/])(if|switch|merge|loop|filter|branch|router|condition|aggregate)/.test(
5697
- (value?.type || "").toLowerCase()
5698
- );
5699
- const roleAccent = isTrigger ? "#10b981" : isLogicNode ? "#f59e0b" : "#3b82f6";
5700
- const outputHandles = resolveOutputHandles(
5701
- data
5702
- );
5703
- const inputHandles = resolveInputHandles(
5704
- data
5705
- );
5706
- const handleSpacer = outputHandlesSpacerHeight(
5707
- Math.max(outputHandles.length, inputHandles.length)
5870
+ }
5871
+ return 1;
5872
+ }
5873
+ function useNodeExecution() {
5874
+ const pollTimerRef = useRef(null);
5875
+ const pollStartRef = useRef(0);
5876
+ const activeRunIdRef = useRef(null);
5877
+ const loadRequestRef = useRef(0);
5878
+ const workflow = useFlowStore((s) => s.workflow);
5879
+ const startExecution = useFlowStore(
5880
+ (s) => s.actions.execution.startExecution
5881
+ );
5882
+ const setRunMetadata = useFlowStore(
5883
+ (s) => s.actions.execution.setRunMetadata
5884
+ );
5885
+ const setStepPlan = useFlowStore((s) => s.actions.execution.setStepPlan);
5886
+ const addLog = useFlowStore((s) => s.actions.execution.addLog);
5887
+ const updateLog = useFlowStore((s) => s.actions.execution.updateLog);
5888
+ const setNodeStatus = useFlowStore((s) => s.actions.execution.setNodeStatus);
5889
+ const setNodeItemCount = useFlowStore(
5890
+ (s) => s.actions.execution.setNodeItemCount
5891
+ );
5892
+ const completeExecution = useFlowStore(
5893
+ (s) => s.actions.execution.completeExecution
5894
+ );
5895
+ const clearLogs = useFlowStore((s) => s.actions.execution.clearLogs);
5896
+ const loadDebugRun = useFlowStore((s) => s.actions.execution.loadDebugRun);
5897
+ const cleanup = useCallback(() => {
5898
+ loadRequestRef.current += 1;
5899
+ if (pollTimerRef.current) {
5900
+ clearTimeout(pollTimerRef.current);
5901
+ pollTimerRef.current = null;
5902
+ }
5903
+ activeRunIdRef.current = null;
5904
+ }, []);
5905
+ const findTriggerNode = useCallback(() => {
5906
+ const { nodes, edges } = workflow;
5907
+ const explicitTrigger = nodes.find(isNodeTrigger);
5908
+ if (explicitTrigger) {
5909
+ return explicitTrigger;
5910
+ }
5911
+ const nodesWithIncomingEdges = new Set(edges.map((e) => e.target));
5912
+ const firstNode = nodes.find(
5913
+ (node) => !nodesWithIncomingEdges.has(node.id)
5708
5914
  );
5709
- const manifestKind = data?.nodeData?.kind;
5710
- const isControlFlowNode = (value?.type || "").startsWith("internal:") && (Array.isArray(manifestKind) ? manifestKind.includes("control") : isLogicNode);
5711
- if (isControlFlowNode) {
5712
- const argsState = data?.args?.state;
5713
- const rules = argsState?.rules;
5714
- const mode = argsState?.mode;
5715
- const logicSubtitle = Array.isArray(rules) ? `${rules.length} rule${rules.length === 1 ? "" : "s"}` : typeof mode === "string" ? mode : void 0;
5716
- return /* @__PURE__ */ jsxs("div", { children: [
5717
- /* @__PURE__ */ jsx(
5718
- LogicNodeCard,
5719
- {
5720
- nodeId: id,
5721
- title: data?.label || meta.title,
5722
- subtitle: logicSubtitle,
5723
- icon: meta.icon,
5724
- accent: roleAccent,
5725
- selected: !!selected,
5726
- executionStatus,
5727
- itemCount,
5728
- isTrigger,
5729
- isExecuting: isThisNodeExecuting,
5730
- inputHandles,
5731
- outputHandles,
5732
- isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0,
5733
- onClick: handleCardClick,
5734
- onDoubleClick: handleOpenNdv,
5735
- onSettings: handleOpenNdv,
5736
- onExecute: handleExecuteStep,
5737
- onDelete: handleDeleteNode,
5738
- infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
5739
- disabled: isDisabled,
5740
- onToggleDisabled: handleToggleDisabled,
5741
- isRenaming: isRenamingThisNode,
5742
- onStartRename: handleStartRename,
5743
- onCommitRename: handleCommitRename,
5744
- onCancelRename: handleCancelRename
5915
+ if (firstNode) {
5916
+ return firstNode;
5917
+ }
5918
+ if (nodes.length > 0) {
5919
+ return nodes[0];
5920
+ }
5921
+ console.error("[useNodeExecution] No nodes found in workflow");
5922
+ return null;
5923
+ }, [workflow]);
5924
+ const getNodeName = useCallback(
5925
+ (nodeId) => {
5926
+ const node = workflow.nodes.find((n) => n.id === nodeId);
5927
+ if (!node) return `Node ${nodeId}`;
5928
+ const data = node.data;
5929
+ return data?.operation_name || data?.name || node.type || `Node ${nodeId}`;
5930
+ },
5931
+ [workflow.nodes]
5932
+ );
5933
+ const processNodeStates = useCallback(
5934
+ (runId, nodeStates) => {
5935
+ for (const nodeState of nodeStates) {
5936
+ const logId = `${runId}-${nodeState.nodeId}`;
5937
+ const rawStatus = nodeState.status.toLowerCase();
5938
+ const status = rawStatus === "completed" ? "completed" : rawStatus === "failed" || rawStatus === "timeout" ? "failed" : rawStatus === "running" || rawStatus === "waiting" || rawStatus === "delayed" || rawStatus === "waiting_subworkflow" ? "running" : "pending";
5939
+ if (status === "completed") {
5940
+ setNodeStatus(nodeState.nodeId, "success");
5941
+ if (nodeState.output) {
5942
+ const itemCount = calculateItemCount(
5943
+ nodeState.output
5944
+ );
5945
+ setNodeItemCount(nodeState.nodeId, itemCount);
5745
5946
  }
5746
- ),
5747
- /* @__PURE__ */ jsx(
5748
- AdapterConnectors,
5749
- {
5750
- nodeId: id,
5751
- adapters: data?.nodeData?.nodeAdapters
5947
+ } else if (status === "failed") {
5948
+ setNodeStatus(nodeState.nodeId, "error");
5949
+ } else if (status === "running") {
5950
+ setNodeStatus(nodeState.nodeId, "running");
5951
+ }
5952
+ const existingLog = useFlowStore.getState().execution.logs.find((l) => l.id === logId);
5953
+ if (!existingLog) {
5954
+ if (status === "pending") continue;
5955
+ const newLog = {
5956
+ id: logId,
5957
+ runId,
5958
+ nodeId: nodeState.nodeId,
5959
+ nodeType: nodeState.nodeType,
5960
+ nodeName: getNodeName(nodeState.nodeId),
5961
+ status,
5962
+ inputArgs: nodeState.inputArgs ?? null,
5963
+ output: nodeState.output ?? null,
5964
+ error: nodeState.error ?? null,
5965
+ duration: nodeState.duration ?? null,
5966
+ startTime: nodeState.startTime ?? (/* @__PURE__ */ new Date()).toISOString(),
5967
+ endTime: nodeState.endTime ?? null,
5968
+ usedPinnedData: nodeState.usedPinnedData ?? false,
5969
+ reusedFromRunId: nodeState.reusedFromRunId ?? null
5970
+ };
5971
+ addLog(newLog);
5972
+ } else {
5973
+ if (existingLog.status !== status || status === "completed" && !existingLog.output) {
5974
+ updateLog(logId, {
5975
+ status,
5976
+ inputArgs: nodeState.inputArgs ?? existingLog.inputArgs,
5977
+ output: nodeState.output ?? existingLog.output,
5978
+ error: nodeState.error ?? existingLog.error,
5979
+ duration: nodeState.duration ?? existingLog.duration,
5980
+ endTime: nodeState.endTime ?? existingLog.endTime,
5981
+ usedPinnedData: nodeState.usedPinnedData ?? existingLog.usedPinnedData,
5982
+ reusedFromRunId: nodeState.reusedFromRunId ?? existingLog.reusedFromRunId
5983
+ });
5752
5984
  }
5753
- )
5754
- ] });
5755
- }
5756
- return /* @__PURE__ */ jsxs("div", { children: [
5757
- /* @__PURE__ */ jsxs(
5758
- NodeCard,
5759
- {
5760
- "data-selected": selected,
5761
- selected,
5762
- onClick: handleCardClick,
5763
- onDoubleClick: handleOpenNdv,
5764
- executionStatus,
5765
- itemCount,
5766
- isTrigger,
5767
- accent: roleAccent,
5768
- isExecuting: isThisNodeExecuting,
5769
- onSettings: handleOpenNdv,
5770
- onExecute: handleExecuteStep,
5771
- onDelete: handleDeleteNode,
5772
- infoButton: /* @__PURE__ */ jsx(NodeInfoButtonWithModal, { metadata: nodeInfoMetadata }),
5773
- disabled: isDisabled,
5774
- onToggleDisabled: handleToggleDisabled,
5775
- children: [
5776
- /* @__PURE__ */ jsx(
5777
- NodeCardHeader,
5778
- {
5779
- icon: cardIcon,
5780
- appName,
5781
- accentKey: value?.type || meta.title,
5782
- missingCredentials,
5783
- label: nodeLabel,
5784
- isRenaming: isRenamingThisNode,
5785
- onStartRename: handleStartRename,
5786
- onCommitRename: handleCommitRename,
5787
- onCancelRename: handleCancelRename
5788
- }
5789
- ),
5790
- operationDisplay === "execute" && (value?.type || "").startsWith("internal:") || isTrigger && !resolveOperationName(data?.operation_name, "") ? /* @__PURE__ */ jsx("div", { className: "rounded-b-xl border-t border-border/60 px-3 pb-3 pt-2.5", children: /* @__PURE__ */ jsx("p", { className: "line-clamp-2 text-xs leading-snug text-muted-foreground", children: nodeDescription }) }) : /* @__PURE__ */ jsx(
5791
- NodeCardBody,
5792
- {
5793
- operationName: operationDisplay,
5794
- description: nodeDescription
5795
- }
5796
- ),
5797
- handleSpacer > 0 && /* @__PURE__ */ jsx("div", { style: { height: handleSpacer } }),
5798
- !isTrigger && /* @__PURE__ */ jsx(
5799
- InputHandles,
5800
- {
5801
- nodeId: id,
5802
- handles: inputHandles,
5803
- accent: roleAccent,
5804
- isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
5805
- }
5806
- ),
5807
- /* @__PURE__ */ jsx(
5808
- OutputHandles,
5809
- {
5810
- nodeId: id,
5811
- handles: outputHandles,
5812
- accent: roleAccent,
5813
- isConnectable: typeof isConnectable === "boolean" ? isConnectable : void 0
5985
+ }
5986
+ }
5987
+ },
5988
+ [setNodeStatus, setNodeItemCount, addLog, updateLog, getNodeName]
5989
+ );
5990
+ const pollRunUpdates = useCallback(
5991
+ (runId) => {
5992
+ const client = getFluidGridsClient();
5993
+ pollStartRef.current = Date.now();
5994
+ activeRunIdRef.current = runId;
5995
+ const doPoll = async () => {
5996
+ if (activeRunIdRef.current !== runId) {
5997
+ return;
5998
+ }
5999
+ try {
6000
+ const result = await client.query({
6001
+ query: WORKFLOW_RUN,
6002
+ variables: { id: runId },
6003
+ fetchPolicy: "network-only"
6004
+ });
6005
+ if (activeRunIdRef.current !== runId) {
6006
+ return;
6007
+ }
6008
+ const run = result.data?.workflowRun;
6009
+ if (!run) {
6010
+ console.warn(
6011
+ "[useNodeExecution] Poll returned no run data for",
6012
+ runId
6013
+ );
6014
+ if (Date.now() - pollStartRef.current < MAX_RUN_CREATION_WAIT) {
6015
+ pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL);
6016
+ } else {
6017
+ console.warn(
6018
+ "[useNodeExecution] Run data not found after timeout for",
6019
+ runId
6020
+ );
6021
+ completeExecution();
6022
+ cleanup();
6023
+ }
6024
+ return;
6025
+ }
6026
+ setRunMetadata({
6027
+ runId,
6028
+ workflowKey: run.workflowKey,
6029
+ version: run.version,
6030
+ sourceMode: run.mode
6031
+ });
6032
+ if (run.nodeStates?.length) {
6033
+ processNodeStates(runId, run.nodeStates);
6034
+ }
6035
+ if (TERMINAL_STATUSES.has(run.status)) {
6036
+ completeExecution();
6037
+ cleanup();
6038
+ return;
6039
+ }
6040
+ pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL);
6041
+ } catch (error) {
6042
+ console.error("[useNodeExecution] Poll error:", error);
6043
+ if (activeRunIdRef.current !== runId) {
6044
+ return;
6045
+ }
6046
+ pollTimerRef.current = setTimeout(doPoll, POLL_INTERVAL * 2);
6047
+ }
6048
+ };
6049
+ pollTimerRef.current = setTimeout(doPoll, 800);
6050
+ },
6051
+ [processNodeStates, completeExecution, cleanup, setRunMetadata]
6052
+ );
6053
+ const executeStep = useCallback(
6054
+ async (nodeId) => {
6055
+ cleanup();
6056
+ clearLogs();
6057
+ const workflowKey = workflow.workflowKey || workflow.id;
6058
+ const version = workflow.version || "1.0.0";
6059
+ if (!workflowKey) {
6060
+ console.error(
6061
+ "[useNodeExecution] Workflow must be saved before step execution. No workflowKey found."
6062
+ );
6063
+ return {
6064
+ success: false,
6065
+ error: "Workflow must be saved before execution"
6066
+ };
6067
+ }
6068
+ setNodeStatus(nodeId, "running");
6069
+ try {
6070
+ normalizeWorkflowParameters(
6071
+ workflow.nodes.filter((node) => node.id === nodeId),
6072
+ workflow.edges,
6073
+ (type) => NODE_CONFIGS.find((config2) => config2.type === type)?.operations
6074
+ );
6075
+ const client = getFluidGridsClient();
6076
+ const result = throwIfResultError(
6077
+ await client.mutate({
6078
+ mutation: EXECUTE_STEP,
6079
+ variables: {
6080
+ input: {
6081
+ workflowKey,
6082
+ version,
6083
+ nodeId
5814
6084
  }
5815
- )
5816
- ]
5817
- }
5818
- ),
5819
- /* @__PURE__ */ jsx(
5820
- AdapterConnectors,
5821
- {
5822
- nodeId: id,
5823
- adapters: data?.nodeData?.nodeAdapters
6085
+ }
6086
+ })
6087
+ );
6088
+ const stepResult = result.data?.executeStep;
6089
+ if (stepResult) {
6090
+ const backendRunId = stepResult.runId;
6091
+ startExecution(nodeId, backendRunId, {
6092
+ workflowKey: stepResult.workflowKey,
6093
+ version: stepResult.version,
6094
+ sourceMode: "TEST"
6095
+ });
6096
+ setStepPlan({
6097
+ runId: backendRunId,
6098
+ ...stepResult.plan,
6099
+ sourceRunId: stepResult.plan.sourceRunId ?? null
6100
+ });
6101
+ const initialLog = {
6102
+ id: `${backendRunId}-${nodeId}`,
6103
+ runId: backendRunId,
6104
+ nodeId,
6105
+ nodeType: workflow.nodes.find((node) => node.id === nodeId)?.type || "unknown",
6106
+ nodeName: getNodeName(nodeId),
6107
+ status: "running",
6108
+ inputArgs: null,
6109
+ output: null,
6110
+ error: null,
6111
+ duration: null,
6112
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
6113
+ endTime: null
6114
+ };
6115
+ addLog(initialLog);
6116
+ pollRunUpdates(backendRunId);
6117
+ return { success: true, runId: backendRunId, plan: stepResult.plan };
5824
6118
  }
5825
- )
5826
- ] });
5827
- }
5828
- return {
5829
- type: value?.type,
5830
- node: memo(NodeComponent),
5831
- detail: {
5832
- icon: "mynaui:message-solid",
5833
- title: "Gmail",
5834
- description: "Send a text message to the user using different messaging platforms like WhatsApp, Messenger, etc."
5835
- },
5836
- connection: {
5837
- inputs: 1,
5838
- outputs: 1
5839
- },
5840
- defaultData: {
5841
- operation_name: "",
5842
- operation_type: "",
5843
- version: value.defaultData?.version,
5844
- nodeData: value.defaultData?.nodeData,
5845
- args: value.defaultData?.args
5846
- },
5847
- propertyPanel: NodePropertyPanel
5848
- };
5849
- };
5850
-
5851
- // src/builder/components/flow-builder/components/blocks/index.ts
5852
- var NODE_ENDPOINT_CONFIGS = NodeEndpointConfigsDocument;
5853
- var NODE_MANIFEST = NodeManifestDocument;
5854
- var NODE_CONFIGS = [];
5855
- async function fetchNodeManifest(nodeType, nodeVersion) {
5856
- try {
5857
- const client = getFluidGridsClient();
5858
- const { data } = await client.query({
5859
- query: NODE_MANIFEST,
5860
- variables: { nodeType, version: nodeVersion }
5861
- });
5862
- return data?.nodeManifest ?? null;
5863
- } catch (error) {
5864
- const message = error instanceof Error ? error.message : "Unknown error";
5865
- console.error(
5866
- `Error fetching manifest for ${nodeType} v${nodeVersion}:`,
5867
- message
5868
- );
5869
- return null;
5870
- }
5871
- }
5872
- function pushStoreNodeConfig(manifest, publishType = "STORE") {
5873
- if (NODE_CONFIGS.some((c) => c.type === manifest.nodeType)) return;
5874
- const manifestOps = manifest.operations;
5875
- const operations = Array.isArray(manifestOps) ? manifestOps : manifestOps?.operations ?? manifestOps;
5876
- NODE_CONFIGS.push({
5877
- type: manifest.nodeType,
5878
- detail: {
5879
- icon: manifest.icon || "mynaui:keyboard-solid",
5880
- title: manifest.name,
5881
- description: manifest.description,
5882
- gradientColor: "yellow"
5883
- },
5884
- defaultData: {
5885
- operation_name: "",
5886
- operation_type: "",
5887
- nodeData: manifest,
5888
- version: manifest.version,
5889
- args: {
5890
- context: {
5891
- node: {
5892
- id: manifest.nodeType,
5893
- kind: manifest.kind,
5894
- type: manifest.nodeType
5895
- }
5896
- },
5897
- state: {},
5898
- credentials: []
6119
+ console.error("[useNodeExecution] No result from executeStep");
6120
+ setNodeStatus(nodeId, "error");
6121
+ return { success: false, error: "No result from executeStep" };
6122
+ } catch (error) {
6123
+ console.error("[useNodeExecution] Error executing step:", error);
6124
+ const errorMessage = describeOperationError(error);
6125
+ const errorRunId = nanoid$1();
6126
+ const errorLog = {
6127
+ id: `${errorRunId}-${nodeId}`,
6128
+ runId: errorRunId,
6129
+ nodeId,
6130
+ nodeType: workflow.nodes.find((node) => node.id === nodeId)?.type || "unknown",
6131
+ nodeName: getNodeName(nodeId),
6132
+ status: "failed",
6133
+ inputArgs: null,
6134
+ output: null,
6135
+ error: errorMessage,
6136
+ duration: null,
6137
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
6138
+ endTime: (/* @__PURE__ */ new Date()).toISOString()
6139
+ };
6140
+ addLog(errorLog);
6141
+ setNodeStatus(nodeId, "error");
6142
+ completeExecution();
6143
+ return { success: false, error: errorMessage };
5899
6144
  }
5900
6145
  },
5901
- operations,
5902
- publishType
5903
- });
5904
- }
5905
- async function loadInstalledStoreNodes() {
5906
- const globalClient = getGlobalStoreClient();
5907
- if (!globalClient) return;
5908
- const { workspaceId } = getFluidGridsConfig();
5909
- if (!workspaceId) return;
5910
- try {
5911
- const { data } = await globalClient.query({
5912
- query: GetMyNodeInstallationsDocument,
5913
- variables: { workspaceId, limit: 200, offset: 0 },
5914
- fetchPolicy: "network-only"
5915
- });
5916
- const installations = data?.myStoreAppInstallations?.items ?? [];
5917
- let loaded = 0;
5918
- for (const installation of installations) {
5919
- if (installation.status !== "ACTIVE") continue;
5920
- const manifest = installation.manifest;
5921
- if (!manifest?.nodeType) continue;
5922
- pushStoreNodeConfig(manifest, "STORE");
5923
- loaded++;
5924
- }
5925
- if (loaded > 0) {
5926
- console.log(
5927
- `[Workflows] Loaded ${loaded} installed store node(s) from workspace installations.`
5928
- );
5929
- }
5930
- } catch (error) {
5931
- const message = error instanceof Error ? error.message : "Unknown error";
5932
- console.warn("[Workflows] Could not load installed store nodes:", message);
5933
- }
5934
- }
5935
- async function initializeNodeConfigs() {
5936
- try {
5937
- const client = await waitForFluidGridsClient(15e3, 150);
5938
- if (!client) {
5939
- console.warn(
5940
- "[Workflows] Apollo client not available after timeout \u2014 loading store nodes only."
5941
- );
5942
- await loadInstalledStoreNodes();
5943
- return;
5944
- }
5945
- const { data } = await client.query({
5946
- query: NODE_ENDPOINT_CONFIGS,
5947
- variables: {
5948
- status: "AVAILABLE",
5949
- pagination: { limit: 100 }
6146
+ [
6147
+ cleanup,
6148
+ clearLogs,
6149
+ startExecution,
6150
+ setStepPlan,
6151
+ workflow,
6152
+ getNodeName,
6153
+ addLog,
6154
+ setNodeStatus,
6155
+ pollRunUpdates,
6156
+ completeExecution
6157
+ ]
6158
+ );
6159
+ const executeFullWorkflow = useCallback(
6160
+ async (customTriggerData) => {
6161
+ const triggerNode = findTriggerNode();
6162
+ if (!triggerNode) {
6163
+ console.error("[useNodeExecution] No trigger node found in workflow");
6164
+ return { success: false, error: "No trigger node found" };
5950
6165
  }
5951
- });
5952
- const nodes = data?.nodeEndpointConfigs?.configs ?? [];
5953
- if (!Array.isArray(nodes) || nodes.length === 0) {
5954
- console.warn("No nodes found or received data is not an array.");
5955
- await loadInstalledStoreNodes();
5956
- return;
5957
- }
5958
- const manifestPromises = nodes.map(async (node) => {
5959
- if (node?.nodeType && node?.nodeVersion) {
5960
- try {
5961
- return await fetchNodeManifest(node.nodeType, node.nodeVersion);
5962
- } catch {
5963
- console.error(
5964
- `Failed to fetch manifest for node ${node.id} (${node.nodeType} v${node.nodeVersion})`
5965
- );
5966
- return null;
5967
- }
5968
- } else {
5969
- console.warn(
5970
- `Node ${node?.id ?? "unknown"} is missing nodeType or nodeVersion. Skipping manifest fetch.`
6166
+ cleanup();
6167
+ clearLogs();
6168
+ const workflowKey = workflow.workflowKey || workflow.id;
6169
+ const version = workflow.version || "1.0.0";
6170
+ if (!workflowKey) {
6171
+ console.error(
6172
+ "[useNodeExecution] Workflow must be saved before execution. No workflowKey found."
6173
+ );
6174
+ return {
6175
+ success: false,
6176
+ error: "Workflow must be saved before execution"
6177
+ };
6178
+ }
6179
+ setNodeStatus(triggerNode.id, "running");
6180
+ try {
6181
+ normalizeWorkflowParameters(
6182
+ workflow.nodes.filter((node) => node.data.disabled !== true),
6183
+ workflow.edges,
6184
+ (type) => NODE_CONFIGS.find((config2) => config2.type === type)?.operations
5971
6185
  );
5972
- return null;
6186
+ const client = getFluidGridsClient();
6187
+ const result = throwIfResultError(
6188
+ await client.mutate({
6189
+ mutation: TRIGGER_WORKFLOW,
6190
+ variables: {
6191
+ input: {
6192
+ workflowKey,
6193
+ version,
6194
+ triggerNodeId: triggerNode.id,
6195
+ triggerData: customTriggerData || {},
6196
+ context: {},
6197
+ // Builder "Test workflow" is always a manual/debug run.
6198
+ // Pinned node data only applies to TEST executions, never a
6199
+ // production trigger (webhook, schedule, API).
6200
+ mode: "TEST"
6201
+ }
6202
+ }
6203
+ })
6204
+ );
6205
+ const triggerResult = result.data?.triggerWorkflow;
6206
+ if (triggerResult) {
6207
+ const backendRunId = triggerResult.runId;
6208
+ startExecution(triggerNode.id, backendRunId, {
6209
+ workflowKey: triggerResult.workflowKey,
6210
+ version: triggerResult.version,
6211
+ sourceMode: "TEST"
6212
+ });
6213
+ pollRunUpdates(backendRunId);
6214
+ return { success: true, runId: backendRunId };
6215
+ }
6216
+ console.error("[useNodeExecution] No result from trigger");
6217
+ setNodeStatus(triggerNode.id, "error");
6218
+ return { success: false, error: "No result from trigger" };
6219
+ } catch (error) {
6220
+ console.error("[useNodeExecution] Error triggering workflow:", error);
6221
+ const errorMessage = describeOperationError(error);
6222
+ const errorRunId = nanoid$1();
6223
+ const errorLog = {
6224
+ id: `${errorRunId}-${triggerNode.id}`,
6225
+ runId: errorRunId,
6226
+ nodeId: triggerNode.id,
6227
+ nodeType: triggerNode.type || "unknown",
6228
+ nodeName: getNodeName(triggerNode.id),
6229
+ status: "failed",
6230
+ inputArgs: null,
6231
+ output: null,
6232
+ error: errorMessage,
6233
+ duration: null,
6234
+ startTime: (/* @__PURE__ */ new Date()).toISOString(),
6235
+ endTime: (/* @__PURE__ */ new Date()).toISOString()
6236
+ };
6237
+ addLog(errorLog);
6238
+ setNodeStatus(triggerNode.id, "error");
6239
+ completeExecution();
6240
+ return { success: false, error: errorMessage };
5973
6241
  }
5974
- });
5975
- const manifests = await Promise.all(manifestPromises);
5976
- const successfulManifests = manifests.filter(
5977
- (m) => m !== null
5978
- );
5979
- successfulManifests.forEach((manifest, index) => {
5980
- const node = nodes[index];
5981
- const manifestOps = manifest.operations;
5982
- const operations = Array.isArray(manifestOps) ? manifestOps : manifestOps?.operations ?? manifestOps;
5983
- NODE_CONFIGS.push({
5984
- type: manifest.nodeType,
5985
- // Use camelCase nodeType
5986
- detail: {
5987
- icon: manifest.icon || "mynaui:keyboard-solid",
5988
- title: manifest.name,
5989
- description: manifest.description,
5990
- gradientColor: "yellow"
5991
- // Default gradient for dynamic nodes
5992
- },
5993
- defaultData: {
5994
- operation_name: "",
5995
- operation_type: "",
5996
- nodeData: manifest,
5997
- version: manifest.version,
5998
- args: {
5999
- context: {
6000
- node: {
6001
- id: manifest.nodeType,
6002
- kind: manifest.kind,
6003
- type: manifest.nodeType
6242
+ },
6243
+ [
6244
+ findTriggerNode,
6245
+ cleanup,
6246
+ clearLogs,
6247
+ startExecution,
6248
+ workflow,
6249
+ getNodeName,
6250
+ addLog,
6251
+ setNodeStatus,
6252
+ pollRunUpdates,
6253
+ completeExecution
6254
+ ]
6255
+ );
6256
+ const executeFromNode = useCallback(
6257
+ async (sourceRunId, nodeId) => {
6258
+ cleanup();
6259
+ clearLogs();
6260
+ setNodeStatus(nodeId, "running");
6261
+ try {
6262
+ const client = getFluidGridsClient();
6263
+ const result = throwIfResultError(
6264
+ await client.mutate({
6265
+ mutation: RUN_FROM_NODE,
6266
+ variables: {
6267
+ input: {
6268
+ sourceRunId,
6269
+ nodeId,
6270
+ // Run-from-node is a debug affordance — always TEST, same
6271
+ // rule as the other builder debug execution paths.
6272
+ mode: "TEST"
6004
6273
  }
6005
- },
6006
- state: {},
6007
- credentials: []
6008
- }
6009
- },
6010
- operations,
6011
- publishType: node.publishType
6012
- // Preserve publishType from GraphQL query
6013
- });
6014
- });
6015
- await loadInstalledStoreNodes();
6016
- } catch (error) {
6017
- const message = error instanceof Error ? error.message : "Unknown error";
6018
- console.error("Failed to fetch nodes or manifests:", message);
6019
- }
6020
- }
6021
- async function refetchStoreNodes() {
6022
- try {
6023
- NODE_CONFIGS.length = 0;
6024
- NODES.length = 0;
6025
- AVAILABLE_NODES.length = 0;
6026
- for (const key of Object.keys(NODE_TYPES)) delete NODE_TYPES[key];
6027
- for (const key of Object.keys(NODES_METADATA))
6028
- delete NODES_METADATA[key];
6029
- isInitialized = false;
6030
- initPromise = null;
6031
- const fallback = getNodeMetadata({ type: "__default_dynamic__" });
6032
- if (fallback?.node) {
6033
- NODE_TYPES["default"] = fallback.node;
6034
- }
6035
- NODE_TYPES[STICKY_NOTE_TYPE] = StickyNoteNode;
6036
- await initializeNodeConfigs();
6037
- rebuildNodeArrays();
6038
- } catch (error) {
6039
- console.error("[Blocks] Failed to refetch nodes:", error);
6040
- }
6041
- }
6042
- var NODES = [];
6043
- var NODE_TYPES = {};
6044
- var NODES_METADATA = {};
6045
- var AVAILABLE_NODES = [];
6046
- var initPromise = null;
6047
- var isInitialized = false;
6048
- var nodeTypesListeners = /* @__PURE__ */ new Set();
6049
- function onNodeTypesUpdate(listener) {
6050
- nodeTypesListeners.add(listener);
6051
- return () => nodeTypesListeners.delete(listener);
6052
- }
6053
- function notifyNodeTypesListeners() {
6054
- nodeTypesListeners.forEach((listener) => listener());
6055
- }
6056
- function ensureNodeConfigsLoaded() {
6057
- if (isInitialized) {
6058
- return Promise.resolve();
6059
- }
6060
- if (!initPromise) {
6061
- initPromise = initializeNodeConfigs().then(() => {
6062
- if (NODE_CONFIGS.length > 0) {
6063
- isInitialized = true;
6064
- } else {
6065
- initPromise = null;
6274
+ }
6275
+ })
6276
+ );
6277
+ const runFromNodeResult = result.data?.runFromNode;
6278
+ if (runFromNodeResult) {
6279
+ const backendRunId = runFromNodeResult.runId;
6280
+ startExecution(nodeId, backendRunId, {
6281
+ workflowKey: runFromNodeResult.workflowKey,
6282
+ version: runFromNodeResult.version,
6283
+ sourceMode: "TEST"
6284
+ });
6285
+ pollRunUpdates(backendRunId);
6286
+ return { success: true, runId: backendRunId };
6287
+ }
6288
+ console.error("[useNodeExecution] No result from runFromNode");
6289
+ setNodeStatus(nodeId, "error");
6290
+ return { success: false, error: "No result from runFromNode" };
6291
+ } catch (error) {
6292
+ console.error("[useNodeExecution] Error running from node:", error);
6293
+ const errorMessage = describeOperationError(error);
6294
+ setNodeStatus(nodeId, "error");
6295
+ completeExecution();
6296
+ return { success: false, error: errorMessage };
6297
+ }
6298
+ },
6299
+ [
6300
+ cleanup,
6301
+ clearLogs,
6302
+ setNodeStatus,
6303
+ startExecution,
6304
+ pollRunUpdates,
6305
+ completeExecution
6306
+ ]
6307
+ );
6308
+ const loadExecution = useCallback(
6309
+ async (runId) => {
6310
+ cleanup();
6311
+ const requestId = ++loadRequestRef.current;
6312
+ try {
6313
+ const client = getFluidGridsClient();
6314
+ const result = throwIfResultError(
6315
+ await client.query({
6316
+ query: EXECUTION_DETAIL,
6317
+ variables: { id: runId },
6318
+ fetchPolicy: "network-only"
6319
+ })
6320
+ );
6321
+ const run = result.data?.workflowRun;
6322
+ if (!run) {
6323
+ return { success: false, error: "Execution not found" };
6324
+ }
6325
+ if (run.mode !== "TEST") {
6326
+ return {
6327
+ success: false,
6328
+ error: "Only TEST runs can be loaded for continuation."
6329
+ };
6330
+ }
6331
+ if (!TERMINAL_STATUSES.has(run.status)) {
6332
+ return {
6333
+ success: false,
6334
+ error: "Only terminal TEST runs can be loaded for continuation."
6335
+ };
6336
+ }
6337
+ if (requestId !== loadRequestRef.current) {
6338
+ return { success: false, error: "Execution load superseded." };
6339
+ }
6340
+ const currentWorkflow = useFlowStore.getState().workflow;
6341
+ const currentWorkflowKey = currentWorkflow.workflowKey || currentWorkflow.id;
6342
+ if (run.workflowKey !== currentWorkflowKey || run.version !== currentWorkflow.version) {
6343
+ return {
6344
+ success: false,
6345
+ error: "The selected run belongs to a different workflow version."
6346
+ };
6347
+ }
6348
+ const logs = (run.nodeStates ?? []).map((nodeState) => {
6349
+ const rawStatus = nodeState.status.toLowerCase();
6350
+ const status = rawStatus === "completed" ? "completed" : rawStatus === "failed" || rawStatus === "timeout" ? "failed" : rawStatus === "running" || rawStatus === "waiting" || rawStatus === "delayed" || rawStatus === "waiting_subworkflow" ? "running" : "pending";
6351
+ return {
6352
+ id: `${runId}-${nodeState.nodeId}`,
6353
+ runId,
6354
+ nodeId: nodeState.nodeId,
6355
+ nodeType: nodeState.nodeType,
6356
+ nodeName: getNodeName(nodeState.nodeId),
6357
+ status,
6358
+ inputArgs: nodeState.inputArgs ?? null,
6359
+ output: nodeState.output ?? null,
6360
+ error: nodeState.error ?? null,
6361
+ duration: nodeState.duration ?? null,
6362
+ startTime: nodeState.startTime ?? run.startedAt ?? "",
6363
+ endTime: nodeState.endTime ?? null,
6364
+ usedPinnedData: nodeState.usedPinnedData ?? false,
6365
+ reusedFromRunId: nodeState.reusedFromRunId ?? null
6366
+ };
6367
+ });
6368
+ loadDebugRun(runId, logs, {
6369
+ workflowKey: run.workflowKey,
6370
+ version: run.version,
6371
+ sourceMode: run.mode
6372
+ });
6373
+ return { success: true, runId };
6374
+ } catch (error) {
6375
+ console.error("[useNodeExecution] Error loading execution:", error);
6376
+ return { success: false, error: describeOperationError(error) };
6066
6377
  }
6067
- rebuildNodeArrays();
6068
- });
6069
- }
6070
- return initPromise;
6071
- }
6072
- function isNodeConfigsInitialized() {
6073
- return isInitialized;
6074
- }
6075
- function rebuildNodeArrays() {
6076
- NODE_CONFIGS.forEach((config2) => {
6077
- const data = getNodeMetadata(config2);
6078
- const nodeMetadata = {
6079
- ...data,
6080
- detail: { ...config2.detail }
6081
- };
6082
- if (!NODES.some((n) => n.type === nodeMetadata.type)) {
6083
- NODES.push(nodeMetadata);
6084
- NODE_TYPES[nodeMetadata.type] = nodeMetadata.node;
6085
- NODES_METADATA[nodeMetadata.type] = {
6086
- ...nodeMetadata,
6087
- __details: { type: nodeMetadata.type, ...nodeMetadata.detail }
6088
- };
6089
- }
6090
- });
6091
- const newAvailableNodes = NODES.filter(
6092
- (node) => node.available === void 0 || node.available
6093
- ).map((node) => {
6094
- const nodeConfig = NODE_CONFIGS.find((config2) => config2.type === node.type);
6095
- return {
6096
- type: node.type,
6097
- icon: node.detail.icon,
6098
- title: node.detail.title,
6099
- description: node.detail.description,
6100
- gradientColor: node.detail.gradientColor ?? "gray",
6101
- manifest: node.defaultData?.nodeData,
6102
- publishType: nodeConfig?.publishType
6103
- // Include publishType
6104
- };
6105
- });
6106
- AVAILABLE_NODES.length = 0;
6107
- AVAILABLE_NODES.push(...newAvailableNodes);
6108
- notifyNodeTypesListeners();
6109
- }
6110
- ensureNodeConfigsLoaded().catch((err) => {
6111
- console.warn(
6112
- "[Workflows] Background node config initialization failed:",
6113
- err
6378
+ },
6379
+ [cleanup, getNodeName, loadDebugRun]
6114
6380
  );
6115
- });
6116
- var fallbackDynamic = getNodeMetadata({ type: "__default_dynamic__" });
6117
- if (fallbackDynamic?.node) {
6118
- NODE_TYPES["default"] = fallbackDynamic.node;
6119
- }
6120
- NODE_TYPES[STICKY_NOTE_TYPE] = StickyNoteNode;
6121
- function registerWorkflowNodeTypes(workflowNodes) {
6122
- for (const node of workflowNodes) {
6123
- if (node.type && !NODE_TYPES[node.type]) {
6124
- const meta = getNodeMetadata({
6125
- type: node.type,
6126
- defaultData: {
6127
- version: node.data?.version,
6128
- nodeData: node.data?.nodeData
6129
- },
6130
- operations: node.data?.operations
6131
- });
6132
- if (meta?.node) {
6133
- NODE_TYPES[node.type] = meta.node;
6381
+ useEffect(() => {
6382
+ return () => {
6383
+ if (pollTimerRef.current) {
6384
+ completeExecution();
6134
6385
  }
6135
- }
6136
- }
6386
+ cleanup();
6387
+ };
6388
+ }, [cleanup, completeExecution]);
6389
+ return {
6390
+ executeStep,
6391
+ executeFullWorkflow,
6392
+ executeFromNode,
6393
+ loadExecution,
6394
+ cleanup,
6395
+ findTriggerNode
6396
+ };
6137
6397
  }
6138
6398
  var FluidGridsContext = createContext(
6139
6399
  null
@@ -20485,116 +20745,6 @@ var ChatPanel = () => {
20485
20745
  }
20486
20746
  );
20487
20747
  };
20488
-
20489
- // src/builder/lib/field-schema.ts
20490
- function isRecord4(value) {
20491
- return typeof value === "object" && value !== null && !Array.isArray(value);
20492
- }
20493
- function isIdentifier(key) {
20494
- return /^[A-Za-z_$][\w$]*$/.test(key);
20495
- }
20496
- function joinPath(base, key) {
20497
- if (isIdentifier(key)) return base ? `${base}.${key}` : key;
20498
- const bracket = `[${JSON.stringify(key)}]`;
20499
- return base ? `${base}${bracket}` : bracket;
20500
- }
20501
- function pathToExpression(path) {
20502
- if (!path) return "${output}";
20503
- const accessor = path.startsWith("[") ? `output${path}` : `output.${path}`;
20504
- return "${" + accessor + "}";
20505
- }
20506
- function fieldFromSpec(key, path, spec) {
20507
- if (typeof spec === "string") {
20508
- return { key, path, type: spec };
20509
- }
20510
- if (isRecord4(spec)) {
20511
- const declaredType = typeof spec.type === "string" ? spec.type : void 0;
20512
- const description = typeof spec.description === "string" ? spec.description : void 0;
20513
- const required = spec.required === true;
20514
- if (isRecord4(spec.properties)) {
20515
- const children = Object.entries(spec.properties).map(
20516
- ([k, v]) => fieldFromSpec(k, joinPath(path, k), v)
20517
- );
20518
- return { key, path, type: "object", required, description, children };
20519
- }
20520
- if (spec.items !== void 0) {
20521
- const element = fieldFromSpec("0", `${path}[0]`, spec.items);
20522
- return {
20523
- key,
20524
- path,
20525
- type: "array",
20526
- required,
20527
- description,
20528
- children: [element]
20529
- };
20530
- }
20531
- return { key, path, type: declaredType ?? "any", required, description };
20532
- }
20533
- return { key, path, type: "any" };
20534
- }
20535
- function fieldsFromIO(io, root = "") {
20536
- if (!isRecord4(io)) return [];
20537
- return Object.entries(io).map(
20538
- ([key, spec]) => fieldFromSpec(key, joinPath(root, key), spec)
20539
- );
20540
- }
20541
- function typeOfValue(value) {
20542
- if (value === null || value === void 0) return "any";
20543
- if (Array.isArray(value)) return "array";
20544
- if (typeof value === "object") return "object";
20545
- if (typeof value === "number") return "number";
20546
- if (typeof value === "boolean") return "boolean";
20547
- if (typeof value === "string") return "string";
20548
- return "any";
20549
- }
20550
- function fieldFromValue(key, path, value) {
20551
- if (Array.isArray(value)) {
20552
- const children = value.length > 0 ? [fieldFromValue("0", `${path}[0]`, value[0])] : void 0;
20553
- return { key, path, type: "array", children };
20554
- }
20555
- if (isRecord4(value)) {
20556
- const children = Object.entries(value).map(
20557
- ([k, v]) => fieldFromValue(k, joinPath(path, k), v)
20558
- );
20559
- return { key, path, type: "object", children };
20560
- }
20561
- return { key, path, type: typeOfValue(value) };
20562
- }
20563
- function fieldsFromValue(value, root = "") {
20564
- if (!isRecord4(value)) return [];
20565
- return Object.entries(value).map(
20566
- ([key, v]) => fieldFromValue(key, joinPath(root, key), v)
20567
- );
20568
- }
20569
- function declaredOutputFields(nodeType, op) {
20570
- const name = op?.name;
20571
- if (!name || nodeType?.startsWith("internal:")) {
20572
- return fieldsFromIO(op?.output_fields);
20573
- }
20574
- const children = fieldsFromIO(op?.output_fields, name);
20575
- if (children.length === 0) return [];
20576
- return [{ key: name, path: name, type: "object", children }];
20577
- }
20578
- function normalizeOperations(raw) {
20579
- if (Array.isArray(raw)) return raw;
20580
- if (isRecord4(raw) && Array.isArray(raw.operations)) {
20581
- return raw.operations;
20582
- }
20583
- return [];
20584
- }
20585
- function resolveNodeOperation(data, fallbackOperations2) {
20586
- const nodeData = isRecord4(data?.nodeData) ? data?.nodeData : void 0;
20587
- let operations = normalizeOperations(nodeData?.operations);
20588
- if (operations.length === 0) {
20589
- operations = normalizeOperations(data?.operations);
20590
- }
20591
- if (operations.length === 0 && fallbackOperations2 !== void 0) {
20592
- operations = normalizeOperations(fallbackOperations2);
20593
- }
20594
- const opName = typeof data?.operation_name === "string" ? data.operation_name : void 0;
20595
- const selected = operations.find((op) => op.name === opName) ?? operations[0];
20596
- return { operations, selected };
20597
- }
20598
20748
  var FIELD_PATH_MIME = "application/x-fg-field-path";
20599
20749
  var TYPE_ICON = {
20600
20750
  string: "mdi:format-text",
@@ -21705,6 +21855,65 @@ function NodeLoadOptionsSelect({
21705
21855
  }
21706
21856
  );
21707
21857
  }
21858
+ function StructuredParameterInput({
21859
+ name,
21860
+ schema,
21861
+ value,
21862
+ onChange,
21863
+ className,
21864
+ testId
21865
+ }) {
21866
+ const tr = useTr();
21867
+ const descriptionId = useId();
21868
+ const [draft, setDraft] = useState(() => ({
21869
+ value,
21870
+ text: formatParameterValue(value)
21871
+ }));
21872
+ const text = Object.is(draft.value, value) ? draft.text : formatParameterValue(value);
21873
+ const result = parseParameterValue(schema, text);
21874
+ const type = structuredParameterType(schema);
21875
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21876
+ /* @__PURE__ */ jsx(
21877
+ "textarea",
21878
+ {
21879
+ "aria-label": name,
21880
+ "aria-describedby": descriptionId,
21881
+ "aria-invalid": !result.valid,
21882
+ value: text,
21883
+ rows: 4,
21884
+ spellCheck: false,
21885
+ className,
21886
+ "data-testid": testId,
21887
+ placeholder: type === "array" ? "[]" : "{}",
21888
+ onChange: (event) => {
21889
+ const nextText = event.target.value;
21890
+ const nextResult = parseParameterValue(schema, nextText);
21891
+ const nextValue = nextResult.valid ? nextResult.value : nextText;
21892
+ setDraft({ value: nextValue, text: nextText });
21893
+ onChange(nextValue);
21894
+ }
21895
+ }
21896
+ ),
21897
+ /* @__PURE__ */ jsx(
21898
+ "span",
21899
+ {
21900
+ id: descriptionId,
21901
+ role: result.valid ? void 0 : "alert",
21902
+ className: result.valid ? "text-[11px] text-muted-foreground" : "text-[11px] text-destructive",
21903
+ children: result.valid ? type === "array" ? tr(
21904
+ "fluidgrids.builder.nodeParamsForm.arrayHint",
21905
+ "Enter a JSON array. String lists also accept comma-separated values."
21906
+ ) : tr(
21907
+ "fluidgrids.builder.nodeParamsForm.objectHint",
21908
+ "Enter a JSON object."
21909
+ ) : tr(
21910
+ "fluidgrids.builder.nodeParamsForm.structuredInputInvalid",
21911
+ result.error
21912
+ )
21913
+ }
21914
+ )
21915
+ ] });
21916
+ }
21708
21917
 
21709
21918
  // src/builder/lib/mapping-values.ts
21710
21919
  function isMappingValue(value) {
@@ -21822,6 +22031,7 @@ function paramsFromOperation(op) {
21822
22031
  description: typeof rec?.description === "string" ? rec.description : void 0,
21823
22032
  options,
21824
22033
  defaultValue: rec?.default,
22034
+ schema: spec,
21825
22035
  loadOptions
21826
22036
  };
21827
22037
  });
@@ -21986,18 +22196,24 @@ function useNodeConfig(target, options) {
21986
22196
  };
21987
22197
  const writeFixed = (field, value) => {
21988
22198
  if (!nodeId) return;
22199
+ const parameter = params.find((candidate) => candidate.name === field);
22200
+ const parsed = parseParameterValue(
22201
+ parameter?.schema ?? parameter?.type,
22202
+ value
22203
+ );
22204
+ const nextValue = parsed.valid ? parsed.value : value;
21989
22205
  const store = useFlowStore.getState();
21990
22206
  const next = store.workflow.nodes.map((n) => {
21991
22207
  if (n.id !== nodeId) return n;
21992
22208
  const data = { ...asRecord(n.data) ?? {} };
21993
22209
  const args = { ...asRecord(data.args) ?? {} };
21994
22210
  const state = { ...asRecord(args.state) ?? {} };
21995
- if (value === void 0 || value === null || value === "") {
22211
+ if (nextValue === void 0 || nextValue === null || nextValue === "") {
21996
22212
  delete state[field];
22213
+ args.state = state;
21997
22214
  } else {
21998
- state[field] = value;
22215
+ args.state = { ...state, [field]: nextValue };
21999
22216
  }
22000
- args.state = state;
22001
22217
  data.args = args;
22002
22218
  return { ...n, data };
22003
22219
  });
@@ -22436,6 +22652,17 @@ function NodeParamsForm({
22436
22652
  onChange: (v) => writeFixed(p.name, v),
22437
22653
  "data-testid": `${testIdPrefix}-param-${p.name}`
22438
22654
  }
22655
+ ) : structuredParameterType(p.schema ?? p.type) ? /* @__PURE__ */ jsx(
22656
+ StructuredParameterInput,
22657
+ {
22658
+ name: p.name,
22659
+ schema: p.schema ?? p.type,
22660
+ value: fixedValue,
22661
+ onChange: (value) => writeFixed(p.name, value),
22662
+ className: cn(inputCx2, "font-mono"),
22663
+ testId: `${testIdPrefix}-param-${p.name}`
22664
+ },
22665
+ `${config2.node?.id}:${config2.currentOp?.name}:${p.name}`
22439
22666
  ) : p.loadOptions ? /* @__PURE__ */ jsx(
22440
22667
  NodeLoadOptionsSelect,
22441
22668
  {
@@ -24371,7 +24598,7 @@ function QuickConfigPanelInner({
24371
24598
  }
24372
24599
 
24373
24600
  // src/utils/workflow-definition-diff.ts
24374
- function isRecord5(value) {
24601
+ function isRecord6(value) {
24375
24602
  return typeof value === "object" && value !== null && !Array.isArray(value);
24376
24603
  }
24377
24604
  function equalJson(left, right) {
@@ -24379,7 +24606,7 @@ function equalJson(left, right) {
24379
24606
  if (Array.isArray(left) && Array.isArray(right)) {
24380
24607
  return left.length === right.length && left.every((value, index) => equalJson(value, right[index]));
24381
24608
  }
24382
- if (isRecord5(left) && isRecord5(right)) {
24609
+ if (isRecord6(left) && isRecord6(right)) {
24383
24610
  const leftKeys = Object.keys(left).sort((a, b) => a.localeCompare(b));
24384
24611
  const rightKeys = Object.keys(right).sort((a, b) => a.localeCompare(b));
24385
24612
  return leftKeys.length === rightKeys.length && leftKeys.every(
@@ -24392,7 +24619,7 @@ function diffWorkflowDefinitionJson(before, after, maxEntries = 2e3) {
24392
24619
  const entries = [];
24393
24620
  const visit = (left, right, path) => {
24394
24621
  if (entries.length >= maxEntries || equalJson(left, right)) return;
24395
- if (isRecord5(left) && isRecord5(right)) {
24622
+ if (isRecord6(left) && isRecord6(right)) {
24396
24623
  const keys = [
24397
24624
  .../* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])
24398
24625
  ].sort((a, b) => a.localeCompare(b));