flowstudio 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/README.md +3 -3
  2. package/dist/ai_creator.d.ts +1 -1
  3. package/dist/ai_creator.js +24 -128
  4. package/dist/ai_creator.js.map +1 -1
  5. package/dist/api_calls.d.ts +2 -0
  6. package/dist/api_calls.js +147 -0
  7. package/dist/api_calls.js.map +1 -0
  8. package/dist/app_analytics.d.ts +1 -1
  9. package/dist/app_analytics.js +75 -69
  10. package/dist/app_analytics.js.map +1 -1
  11. package/dist/app_details.d.ts +1 -1
  12. package/dist/app_details.js +25 -24
  13. package/dist/app_details.js.map +1 -1
  14. package/dist/app_layout.d.ts +2 -2
  15. package/dist/app_layout.js +4 -4
  16. package/dist/app_layout.js.map +1 -1
  17. package/dist/app_settings.d.ts +1 -1
  18. package/dist/app_settings.js +76 -8
  19. package/dist/app_settings.js.map +1 -1
  20. package/dist/bottom_left_panel.d.ts +1 -0
  21. package/dist/bottom_left_panel.js +8 -0
  22. package/dist/bottom_left_panel.js.map +1 -0
  23. package/dist/code.d.ts +1 -1
  24. package/dist/code.js +6 -2
  25. package/dist/code.js.map +1 -1
  26. package/dist/components/ftable.d.ts +13 -0
  27. package/dist/components/ftable.js +65 -0
  28. package/dist/components/ftable.js.map +1 -0
  29. package/dist/components/gsheet/index.d.ts +1 -0
  30. package/dist/components/gsheet/index.js +21 -0
  31. package/dist/components/gsheet/index.js.map +1 -0
  32. package/dist/components/mention_input/index.d.ts +18 -1
  33. package/dist/components/mention_input/index.js +203 -111
  34. package/dist/components/mention_input/index.js.map +1 -1
  35. package/dist/components/paginator_table.d.ts +1 -1
  36. package/dist/components/paginator_table.js +2 -2
  37. package/dist/components/paginator_table.js.map +1 -1
  38. package/dist/components/sheet/index.d.ts +1 -1
  39. package/dist/components/sheet/index.js +4 -2
  40. package/dist/components/sheet/index.js.map +1 -1
  41. package/dist/components/ui/alert.d.ts +8 -0
  42. package/dist/components/ui/alert.js +23 -0
  43. package/dist/components/ui/alert.js.map +1 -0
  44. package/dist/components/ui/badge.d.ts +9 -0
  45. package/dist/components/ui/badge.js +21 -0
  46. package/dist/components/ui/badge.js.map +1 -0
  47. package/dist/components/ui/button.d.ts +11 -0
  48. package/dist/components/ui/button.js +35 -0
  49. package/dist/components/ui/button.js.map +1 -0
  50. package/dist/components/ui/card.d.ts +8 -0
  51. package/dist/components/ui/card.js +17 -0
  52. package/dist/components/ui/card.js.map +1 -0
  53. package/dist/components/ui/dialog.d.ts +19 -0
  54. package/dist/components/ui/dialog.js +33 -0
  55. package/dist/components/ui/dialog.js.map +1 -0
  56. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  57. package/dist/components/ui/dropdown-menu.js +46 -0
  58. package/dist/components/ui/dropdown-menu.js.map +1 -0
  59. package/dist/components/ui/form.d.ts +24 -0
  60. package/dist/components/ui/form.js +61 -0
  61. package/dist/components/ui/form.js.map +1 -0
  62. package/dist/components/ui/input-group.d.ts +16 -0
  63. package/dist/components/ui/input-group.js +65 -0
  64. package/dist/components/ui/input-group.js.map +1 -0
  65. package/dist/components/ui/input.d.ts +5 -0
  66. package/dist/components/ui/input.js +9 -0
  67. package/dist/components/ui/input.js.map +1 -0
  68. package/dist/components/ui/label.d.ts +5 -0
  69. package/dist/components/ui/label.js +10 -0
  70. package/dist/components/ui/label.js.map +1 -0
  71. package/dist/components/ui/popover.d.ts +7 -0
  72. package/dist/components/ui/popover.js +23 -0
  73. package/dist/components/ui/popover.js.map +1 -0
  74. package/dist/components/ui/scroll-area.d.ts +5 -0
  75. package/dist/components/ui/scroll-area.js +12 -0
  76. package/dist/components/ui/scroll-area.js.map +1 -0
  77. package/dist/components/ui/select.d.ts +13 -0
  78. package/dist/components/ui/select.js +37 -0
  79. package/dist/components/ui/select.js.map +1 -0
  80. package/dist/components/ui/separator.d.ts +4 -0
  81. package/dist/components/ui/separator.js +8 -0
  82. package/dist/components/ui/separator.js.map +1 -0
  83. package/dist/components/ui/sheet.d.ts +13 -0
  84. package/dist/components/ui/sheet.js +93 -0
  85. package/dist/components/ui/sheet.js.map +1 -0
  86. package/dist/components/ui/skeleton.d.ts +2 -0
  87. package/dist/components/ui/skeleton.js +7 -0
  88. package/dist/components/ui/skeleton.js.map +1 -0
  89. package/dist/components/ui/sonner.d.ts +3 -0
  90. package/dist/components/ui/sonner.js +42 -0
  91. package/dist/components/ui/sonner.js.map +1 -0
  92. package/dist/components/ui/spinner.d.ts +7 -0
  93. package/dist/components/ui/spinner.js +17 -0
  94. package/dist/components/ui/spinner.js.map +1 -0
  95. package/dist/components/ui/switch.d.ts +4 -0
  96. package/dist/components/ui/switch.js +8 -0
  97. package/dist/components/ui/switch.js.map +1 -0
  98. package/dist/components/ui/table.d.ts +10 -0
  99. package/dist/components/ui/table.js +21 -0
  100. package/dist/components/ui/table.js.map +1 -0
  101. package/dist/components/ui/tabs.d.ts +7 -0
  102. package/dist/components/ui/tabs.js +13 -0
  103. package/dist/components/ui/tabs.js.map +1 -0
  104. package/dist/components/ui/textarea.d.ts +5 -0
  105. package/dist/components/ui/textarea.js +9 -0
  106. package/dist/components/ui/textarea.js.map +1 -0
  107. package/dist/components/ui/tooltip.d.ts +7 -0
  108. package/dist/components/ui/tooltip.js +11 -0
  109. package/dist/components/ui/tooltip.js.map +1 -0
  110. package/dist/constants.js +5 -4
  111. package/dist/constants.js.map +1 -1
  112. package/dist/context.d.ts +1 -1
  113. package/dist/context.js +2 -2
  114. package/dist/context.js.map +1 -1
  115. package/dist/envs.d.ts +2 -2
  116. package/dist/envs.js +2 -150
  117. package/dist/envs.js.map +1 -1
  118. package/dist/functions.d.ts +2 -5
  119. package/dist/functions.js +2 -210
  120. package/dist/functions.js.map +1 -1
  121. package/dist/globals.css +264 -0
  122. package/dist/home.d.ts +1 -1
  123. package/dist/home.js +64 -26
  124. package/dist/home.js.map +1 -1
  125. package/dist/index.d.ts +19 -3
  126. package/dist/index.js +81 -22
  127. package/dist/index.js.map +1 -1
  128. package/dist/lib/pane-context.d.ts +10 -0
  129. package/dist/lib/pane-context.js +21 -0
  130. package/dist/lib/pane-context.js.map +1 -0
  131. package/dist/lib/toast.d.ts +12 -0
  132. package/dist/lib/toast.js +36 -0
  133. package/dist/lib/toast.js.map +1 -0
  134. package/dist/lib/utils.d.ts +2 -0
  135. package/dist/lib/utils.js +6 -0
  136. package/dist/lib/utils.js.map +1 -0
  137. package/dist/new_project.d.ts +1 -1
  138. package/dist/new_project.js +60 -41
  139. package/dist/new_project.js.map +1 -1
  140. package/dist/node.d.ts +2 -2
  141. package/dist/node.js +152 -39
  142. package/dist/node.js.map +1 -1
  143. package/dist/project_widget.d.ts +2 -1
  144. package/dist/project_widget.js +6 -2
  145. package/dist/project_widget.js.map +1 -1
  146. package/dist/secrets.d.ts +2 -0
  147. package/dist/secrets.js +130 -0
  148. package/dist/secrets.js.map +1 -0
  149. package/dist/simulator.d.ts +1 -1
  150. package/dist/simulator.js +184 -63
  151. package/dist/simulator.js.map +1 -1
  152. package/dist/studio_pane.d.ts +1 -1
  153. package/dist/studio_pane.js +319 -56
  154. package/dist/studio_pane.js.map +1 -1
  155. package/dist/studio_pane_old.d.ts +1 -1
  156. package/dist/studio_pane_old.js +2 -3
  157. package/dist/studio_pane_old.js.map +1 -1
  158. package/dist/studio_pane_old_new.d.ts +1 -1
  159. package/dist/studio_pane_old_new.js +2 -3
  160. package/dist/studio_pane_old_new.js.map +1 -1
  161. package/dist/styles/globals.css +59 -0
  162. package/dist/styles.css +2 -0
  163. package/dist/top_center_panel.d.ts +1 -1
  164. package/dist/top_center_panel.js +4 -3
  165. package/dist/top_center_panel.js.map +1 -1
  166. package/dist/top_left_panel.d.ts +1 -1
  167. package/dist/top_left_panel.js +7 -41
  168. package/dist/top_left_panel.js.map +1 -1
  169. package/dist/top_right_panel.d.ts +1 -1
  170. package/dist/top_right_panel.js +12 -13
  171. package/dist/top_right_panel.js.map +1 -1
  172. package/dist/triggers.d.ts +2 -2
  173. package/dist/triggers.js +56 -37
  174. package/dist/triggers.js.map +1 -1
  175. package/dist/zustand/store.d.ts +411 -3
  176. package/dist/zustand/store.js +4 -0
  177. package/dist/zustand/store.js.map +1 -1
  178. package/package.json +54 -26
  179. package/src/globals.css +264 -0
  180. package/src/styles/globals.css +59 -0
  181. package/dist/components/combo_box/index.d.ts +0 -2
  182. package/dist/components/combo_box/index.js +0 -150
  183. package/dist/components/combo_box/index.js.map +0 -1
  184. package/dist/components/combo_box/style.css +0 -10
  185. package/dist/components/date_picker/index.d.ts +0 -1
  186. package/dist/components/date_picker/index.js +0 -46
  187. package/dist/components/date_picker/index.js.map +0 -1
  188. package/dist/components/date_picker/style.css +0 -204
  189. package/dist/components/gtable.d.ts +0 -15
  190. package/dist/components/gtable.js +0 -122
  191. package/dist/components/gtable.js.map +0 -1
  192. package/src/components/combo_box/style.css +0 -10
  193. package/src/components/date_picker/style.css +0 -204
@@ -1,8 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Box } from '@radix-ui/themes';
4
- import { ReactFlow, useNodesState, useEdgesState, addEdge, Controls, Background, Panel, SmoothStepEdge, useReactFlow, reconnectEdge, useKeyPress } from '@xyflow/react';
5
- import GNode, { FNode } from './node';
3
+ import { ReactFlow, useNodesState, useEdgesState, addEdge, Background, Panel, SmoothStepEdge, useReactFlow, reconnectEdge, useKeyPress } from '@xyflow/react';
4
+ import GNode from './node';
6
5
  import ContextMenu from './context';
7
6
  import { useConfigStore } from './zustand/store';
8
7
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -15,17 +14,26 @@ import TopLeftPanel from './top_left_panel';
15
14
  import './studio.css';
16
15
  import AppAnalytics from './app_analytics';
17
16
  import flow_constants from './constants';
17
+ import BottomLeftPanel from './bottom_left_panel';
18
+ import { toast } from './lib/toast';
19
+ const CustomSmoothstepEdge = memo(({ data, ...props }) => {
20
+ return _jsx(SmoothStepEdge, { ...props, data: data, style: { strokeWidth: 2 } });
21
+ });
18
22
  // Constants
19
23
  const MAX_HISTORY_SIZE = 50;
20
- const AUTO_SAVE_DELAY = 1000;
24
+ const AUTO_SAVE_DELAY = 3000;
21
25
  const QUERY_STALE_TIME = 5 * 60 * 1000; // 5 minutes
22
26
  const QUERY_CACHE_TIME = 10 * 60 * 1000; // 10 minutes
23
27
  // Node templates for performance
24
28
  const NODE_TEMPLATES = {
25
- function: {
26
- type: 'fNode',
27
- data: { label: '', function: '' }
28
- },
29
+ // function: {
30
+ // type: 'fNode' as const,
31
+ // data: { label: '', function: '' }
32
+ // },
33
+ // gotonode: {
34
+ // type: 'gotoNode' as const,
35
+ // data: { label: '', function: '' }
36
+ // },
29
37
  default: {
30
38
  type: 'gNode',
31
39
  data: {
@@ -40,6 +48,132 @@ const NODE_TEMPLATES = {
40
48
  }
41
49
  }
42
50
  };
51
+ /**
52
+ * Auto-arrange nodes for horizontal left-to-right flow
53
+ * - Nodes are arranged in levels (columns) from left to right
54
+ * - Within each level, nodes are ordered top-to-bottom based on their parent positions
55
+ * - This creates a clean hierarchical layout that respects input source ordering
56
+ *
57
+ * Nodes: any[] where each node has at least `.id` and optional `.data?.type === 'start'`
58
+ * Edges: any[] where each edge has `.source` and `.target` (ids)
59
+ */
60
+ function autoArrangeNodes(nodes, edges, options = {}) {
61
+ if (!nodes || nodes.length === 0)
62
+ return nodes;
63
+ const { horizontalSpacing = 80, verticalSpacing = 50, nodeWidth: DEFAULT_WIDTH = 250, nodeHeight: DEFAULT_HEIGHT = 200, } = options;
64
+ // Use the actual rendered size when available (React Flow populates node.measured after paint)
65
+ const nw = (n) => { var _a, _b, _c; return (_c = (_b = (_a = n.measured) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : n.width) !== null && _c !== void 0 ? _c : DEFAULT_WIDTH; };
66
+ const nh = (n) => { var _a, _b, _c; return (_c = (_b = (_a = n.measured) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : n.height) !== null && _c !== void 0 ? _c : DEFAULT_HEIGHT; };
67
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
68
+ // ── 1. Build adjacency (deduplicate, skip self-loops) ────────────────────
69
+ const nodeSet = new Set(nodes.map((n) => n.id));
70
+ const childMap = new Map();
71
+ const parentMap = new Map();
72
+ nodes.forEach((n) => { childMap.set(n.id, []); parentMap.set(n.id, []); });
73
+ const seenEdge = new Set();
74
+ edges.forEach((e) => {
75
+ const key = `${e.source}|${e.target}`;
76
+ if (nodeSet.has(e.source) && nodeSet.has(e.target) && e.source !== e.target && !seenEdge.has(key)) {
77
+ seenEdge.add(key);
78
+ childMap.get(e.source).push(e.target);
79
+ parentMap.get(e.target).push(e.source);
80
+ }
81
+ });
82
+ // ── 2. Shortest-path BFS layering ────────────────────────────────────────
83
+ const levelMap = new Map();
84
+ const visited = new Set();
85
+ const roots = nodes.filter((n) => { var _a, _b; return ((_b = (_a = parentMap.get(n.id)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0; });
86
+ const queue = (roots.length > 0 ? roots : [nodes[0]])
87
+ .map((n) => ({ id: n.id, level: 0 }));
88
+ while (queue.length > 0) {
89
+ const { id, level } = queue.shift();
90
+ if (visited.has(id))
91
+ continue;
92
+ visited.add(id);
93
+ levelMap.set(id, level);
94
+ (childMap.get(id) || []).forEach((c) => {
95
+ if (!visited.has(c))
96
+ queue.push({ id: c, level: level + 1 });
97
+ });
98
+ }
99
+ let extraCol = levelMap.size > 0 ? Math.max(...Array.from(levelMap.values())) + 1 : 0;
100
+ nodes.forEach((n) => { if (!levelMap.has(n.id))
101
+ levelMap.set(n.id, extraCol++); });
102
+ // ── 3. Group into columns ─────────────────────────────────────────────────
103
+ const columns = new Map();
104
+ levelMap.forEach((lv, id) => {
105
+ if (!columns.has(lv))
106
+ columns.set(lv, []);
107
+ columns.get(lv).push(id);
108
+ });
109
+ const sortedLevels = Array.from(columns.keys()).sort((a, b) => a - b);
110
+ // ── 4. Compute column X positions using actual node widths ────────────────
111
+ const colX = [];
112
+ let curX = 0;
113
+ sortedLevels.forEach((lv, colIdx) => {
114
+ colX[colIdx] = curX;
115
+ const maxW = Math.max(...(columns.get(lv).map(id => nw(nodeById.get(id)))));
116
+ curX += maxW + horizontalSpacing;
117
+ });
118
+ // ── 5. Place nodes — sequential Y using actual heights, centred on parents ─
119
+ const pos = new Map();
120
+ sortedLevels.forEach((lv, colIdx) => {
121
+ const group = columns.get(lv);
122
+ const x = colX[colIdx];
123
+ // Sort within column by average parent Y-centre (minimises edge crossings)
124
+ if (colIdx > 0) {
125
+ group.sort((a, b) => {
126
+ const midY = (id) => {
127
+ const ps = parentMap.get(id) || [];
128
+ if (!ps.length)
129
+ return 0;
130
+ return ps.reduce((s, p) => {
131
+ const pp = pos.get(p);
132
+ return s + (pp ? pp.y + nh(nodeById.get(p)) / 2 : 0);
133
+ }, 0) / ps.length;
134
+ };
135
+ return midY(a) - midY(b);
136
+ });
137
+ }
138
+ // Total column height = sum of actual node heights + gaps between them
139
+ const heights = group.map((id) => nh(nodeById.get(id)));
140
+ const totalHeight = heights.reduce((s, h) => s + h, 0)
141
+ + (group.length - 1) * verticalSpacing;
142
+ // Centre point: Y=0 for first column, parent mid-Y centroid for the rest
143
+ let colCenterY = 0;
144
+ if (colIdx > 0) {
145
+ const pys = [];
146
+ group.forEach((id) => {
147
+ ;
148
+ (parentMap.get(id) || []).forEach((p) => {
149
+ const pp = pos.get(p);
150
+ if (pp)
151
+ pys.push(pp.y + nh(nodeById.get(p)) / 2);
152
+ });
153
+ });
154
+ if (pys.length > 0)
155
+ colCenterY = pys.reduce((s, y) => s + y, 0) / pys.length;
156
+ }
157
+ // Place nodes sequentially from the top of the column, never with a fixed step —
158
+ // each node starts immediately after the actual bottom of the previous one.
159
+ let y = colCenterY - totalHeight / 2;
160
+ group.forEach((id, i) => {
161
+ pos.set(id, { x, y });
162
+ y += heights[i] + verticalSpacing;
163
+ });
164
+ });
165
+ // ── 6. Shift to match the canvas top-left bounding box ───────────────────
166
+ const originX = Math.min(...nodes.map((n) => { var _a, _b; return (_b = (_a = n.position) === null || _a === void 0 ? void 0 : _a.x) !== null && _b !== void 0 ? _b : 0; }));
167
+ const originY = Math.min(...nodes.map((n) => { var _a, _b; return (_b = (_a = n.position) === null || _a === void 0 ? void 0 : _a.y) !== null && _b !== void 0 ? _b : 0; }));
168
+ const minPX = Math.min(...Array.from(pos.values()).map(p => p.x));
169
+ const minPY = Math.min(...Array.from(pos.values()).map(p => p.y));
170
+ return nodes.map((n) => {
171
+ const p = pos.get(n.id);
172
+ return p
173
+ ? { ...n, position: { x: p.x + (originX - minPX), y: p.y + (originY - minPY) } }
174
+ : n;
175
+ });
176
+ }
43
177
  // Debounce utility
44
178
  function useDebounce(callback, delay) {
45
179
  const timeoutRef = useRef(null);
@@ -89,15 +223,21 @@ function useFlowHistory() {
89
223
  return { addToHistory, undo, redo, historyIndex, historySize: history.length };
90
224
  }
91
225
  // Custom hook for auto-save functionality
92
- function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory) {
226
+ function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory, isDraggingRef, isConnectingRef) {
93
227
  const [saving, setSaving] = useState(false);
94
228
  const lastSavedStateRef = useRef('');
95
229
  const performSave = useCallback(async () => {
96
230
  if (!rfInstance || !identifier)
97
231
  return;
232
+ const flow = rfInstance.toObject();
233
+ // CRITICAL: Never save empty flows unless it's intentional
234
+ // Prevent saving if both nodes and edges are empty (likely a race condition)
235
+ if (!flow.nodes || !flow.edges || (flow.nodes.length === 0 && flow.edges.length === 0)) {
236
+ console.warn('Prevented saving empty flow - possible race condition');
237
+ return;
238
+ }
98
239
  setSaving(true);
99
240
  try {
100
- const flow = rfInstance.toObject();
101
241
  const response = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${identifier}`, {
102
242
  method: 'PATCH',
103
243
  headers: {
@@ -121,6 +261,14 @@ function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory
121
261
  const saveWithHistory = useCallback(() => {
122
262
  if (!rfInstance)
123
263
  return;
264
+ // Skip adding to history during drag and connection operations
265
+ if (isDraggingRef.current || isConnectingRef.current)
266
+ return;
267
+ // CRITICAL: Don't save if nodes/edges are undefined or both empty
268
+ if (!nodes || !edges || (nodes.length === 0 && edges.length === 0)) {
269
+ console.warn('Skipping save - empty or undefined state');
270
+ return;
271
+ }
124
272
  const currentState = {
125
273
  nodes: nodes === null || nodes === void 0 ? void 0 : nodes.map(n => ({ ...n })),
126
274
  edges: edges === null || edges === void 0 ? void 0 : edges.map(e => ({ ...e })),
@@ -160,67 +308,81 @@ function useContextMenu() {
160
308
  };
161
309
  }
162
310
  export default function StudioPane({ api_key, projectsRefetch, identifier, providers, provider, theme }) {
311
+ var _a;
163
312
  const { stateType } = useConfigStore();
313
+ const stateTypeRef = useRef(stateType);
314
+ useEffect(() => { stateTypeRef.current = stateType; }, [stateType]);
315
+ const [getIdentifier, setIdentifier] = useState(identifier);
316
+ useEffect(() => {
317
+ setIdentifier(identifier);
318
+ // Reset the loaded flag when switching projects
319
+ hasLoadedInitialData.current = false;
320
+ }, [identifier]);
164
321
  // Optimized queries with better caching
165
322
  const { data: project, status: projectStatus, refetch: projectRefetch } = useQuery({
166
- queryKey: [`project_${identifier}`],
323
+ queryKey: [`project_${getIdentifier}`],
167
324
  queryFn: async () => {
168
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${identifier}`, {
325
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${getIdentifier}`, {
169
326
  headers: { "X-API-KEY": `${api_key}` }
170
327
  });
171
328
  return res === null || res === void 0 ? void 0 : res.json();
172
329
  },
173
- enabled: !!identifier,
330
+ enabled: !!getIdentifier,
174
331
  staleTime: QUERY_STALE_TIME,
175
- gcTime: QUERY_CACHE_TIME
332
+ gcTime: QUERY_CACHE_TIME,
333
+ placeholderData: (previousData) => previousData
176
334
  });
177
335
  const { data: functions, refetch: functionsRefetch } = useQuery({
178
- queryKey: [`project_${identifier}_functions`],
336
+ queryKey: [`project_${getIdentifier}_api_calls`],
179
337
  queryFn: async () => {
180
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/functions?project=${identifier}`, {
338
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/api-calls?project=${getIdentifier}`, {
181
339
  headers: { "X-API-KEY": `${api_key}` }
182
340
  });
183
341
  return res === null || res === void 0 ? void 0 : res.json();
184
342
  },
185
- enabled: !!identifier,
343
+ enabled: !!getIdentifier,
186
344
  staleTime: QUERY_STALE_TIME,
187
- gcTime: QUERY_CACHE_TIME
345
+ gcTime: QUERY_CACHE_TIME,
346
+ placeholderData: (previousData) => previousData
188
347
  });
189
348
  const { data: triggers, refetch: triggersRefetch } = useQuery({
190
- queryKey: [`project_${identifier}_triggers`],
349
+ queryKey: [`project_${getIdentifier}_triggers`],
191
350
  queryFn: async () => {
192
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/triggers?project=${identifier}`, {
351
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/triggers?project=${getIdentifier}`, {
193
352
  headers: { "X-API-KEY": `${api_key}` }
194
353
  });
195
354
  return res === null || res === void 0 ? void 0 : res.json();
196
355
  },
197
- enabled: !!identifier,
356
+ enabled: !!getIdentifier,
198
357
  staleTime: QUERY_STALE_TIME,
199
- gcTime: QUERY_CACHE_TIME
358
+ gcTime: QUERY_CACHE_TIME,
359
+ placeholderData: (previousData) => previousData
200
360
  });
201
361
  const { data: envs, refetch: envsRefetch } = useQuery({
202
- queryKey: [`project_${identifier}_envs`],
362
+ queryKey: [`project_${getIdentifier}_secrets`],
203
363
  queryFn: async () => {
204
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/envs?project=${identifier}`, {
364
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/secrets?project=${getIdentifier}`, {
205
365
  headers: { "X-API-KEY": `${api_key}` }
206
366
  });
207
367
  return res === null || res === void 0 ? void 0 : res.json();
208
368
  },
209
- enabled: !!identifier,
369
+ enabled: !!getIdentifier,
210
370
  staleTime: QUERY_STALE_TIME,
211
- gcTime: QUERY_CACHE_TIME
371
+ gcTime: QUERY_CACHE_TIME,
372
+ placeholderData: (previousData) => previousData
212
373
  });
213
374
  const { data: sessions, status: sessionsStatus, refetch: sessionsRefetch } = useQuery({
214
- queryKey: [`project_${identifier}_sessions`],
375
+ queryKey: [`project_${getIdentifier}_sessions`],
215
376
  queryFn: async () => {
216
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/sessions?project=${identifier}`, {
377
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/sessions?project=${getIdentifier}`, {
217
378
  headers: { "X-API-KEY": `${api_key}` }
218
379
  });
219
380
  return res === null || res === void 0 ? void 0 : res.json();
220
381
  },
221
- enabled: !!identifier,
382
+ enabled: !!getIdentifier,
222
383
  staleTime: QUERY_STALE_TIME,
223
- gcTime: QUERY_CACHE_TIME
384
+ gcTime: QUERY_CACHE_TIME,
385
+ placeholderData: (previousData) => previousData
224
386
  });
225
387
  // Optimized session lookup - compute once, use many times
226
388
  const sessionsByNodeId = useMemo(() => {
@@ -235,57 +397,99 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
235
397
  }, {});
236
398
  }, [sessions === null || sessions === void 0 ? void 0 : sessions.results]);
237
399
  ////////////////// Nodes Management (React Flow) /////////////////////////////
238
- const CustomSmoothstepEdge = memo(({ data, ...props }) => {
239
- return _jsx(SmoothStepEdge, { ...props, data: data, style: { strokeWidth: 2 } });
240
- });
241
- // Optimized node types with memoized session lookup
400
+ const [nodes, setNodes, onNodesChange] = useNodesState((project === null || project === void 0 ? void 0 : project.nodes) || []);
401
+ const [edges, setEdges, onEdgesChange] = useEdgesState((project === null || project === void 0 ? void 0 : project.edges) || []);
402
+ // Refs hold latest data so nodeTypes can be stable (no deps) while nodes
403
+ // still read current functions/sessions/envs at render time.
404
+ const functionsRef = useRef(undefined);
405
+ const sessionsByNodeIdRef = useRef({});
406
+ const envsRef = useRef([]);
407
+ functionsRef.current = functions === null || functions === void 0 ? void 0 : functions.results;
408
+ sessionsByNodeIdRef.current = sessionsByNodeId;
409
+ envsRef.current = (_a = envs === null || envs === void 0 ? void 0 : envs.results) !== null && _a !== void 0 ? _a : [];
410
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
411
  const nodeTypes = useMemo(() => ({
243
412
  gNode: (props) => (_jsx(GNode, { ...props, data: {
244
413
  ...props.data,
245
- functions: functions === null || functions === void 0 ? void 0 : functions.results,
246
- sessions: sessionsByNodeId[props.id] || []
414
+ functions: functionsRef.current,
415
+ sessions: sessionsByNodeIdRef.current[props.id] || [],
416
+ secrets: envsRef.current,
247
417
  } })),
248
- fNode: (props) => (_jsx(FNode, { ...props, data: {
249
- ...props.data,
250
- functions: functions === null || functions === void 0 ? void 0 : functions.results,
251
- sessions: sessionsByNodeId[props.id] || []
252
- } }))
253
- }), [functions === null || functions === void 0 ? void 0 : functions.results, sessionsByNodeId]);
418
+ }), []);
254
419
  const edgeTypes = useMemo(() => ({
255
420
  default: memo((props) => (_jsx(CustomSmoothstepEdge, { ...props, deletable: true })))
256
421
  }), []);
257
- const [nodes, setNodes, onNodesChange] = useNodesState((project === null || project === void 0 ? void 0 : project.nodes) || []);
258
- const [edges, setEdges, onEdgesChange] = useEdgesState((project === null || project === void 0 ? void 0 : project.edges) || []);
259
422
  const [rfInstance, setRfInstance] = useState(null);
260
423
  const { setViewport, screenToFlowPosition, getNodes, getEdges } = useReactFlow();
261
424
  const isReconnectingRef = useRef(false);
425
+ const hasLoadedInitialData = useRef(false);
426
+ const isDraggingRef = useRef(false);
427
+ const isConnectingRef = useRef(false);
262
428
  // Custom hooks
263
429
  const { addToHistory, undo, redo, historyIndex, historySize } = useFlowHistory();
264
- const { performSave, saveWithHistory, saving } = useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory);
430
+ const { performSave, saveWithHistory, saving } = useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory, isDraggingRef, isConnectingRef);
265
431
  const { showContextMenu, contextMenuPosition, handleContextMenu, setShowContextMenu } = useContextMenu();
266
432
  // Key Presses
267
- const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s']);
268
- const cmdAndZPressed = useKeyPress(['Meta+z', 'Strg+z']);
269
- const cmdAndYPressed = useKeyPress(['Meta+y', 'Strg+y']);
433
+ const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s', 'Ctrl+s']);
434
+ const cmdAndZPressed = useKeyPress(['Meta+z', 'Strg+z', 'Ctrl+z']);
435
+ const cmdAndYPressed = useKeyPress(['Meta+y', 'Strg+y', 'Ctrl+y']);
270
436
  ////////////// onMount and Initial Load //////////////
271
437
  useEffect(() => {
272
438
  // Component initialization logic can go here
273
439
  }, []);
274
440
  useEffect(() => {
275
441
  var _a, _b, _c;
442
+ // Skip resetting canvas when in live mode — live mode manages its own source
443
+ if (stateTypeRef.current === 'live')
444
+ return;
276
445
  if (project === null || project === void 0 ? void 0 : project.flow_draft) {
277
446
  const { x = 0, y = 0, zoom = 1 } = ((_a = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _a === void 0 ? void 0 : _a.viewport) || {};
278
447
  setNodes(((_b = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _b === void 0 ? void 0 : _b.nodes) || []);
279
448
  setEdges(((_c = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _c === void 0 ? void 0 : _c.edges) || []);
280
449
  setViewport({ x, y, zoom });
450
+ setTimeout(() => {
451
+ hasLoadedInitialData.current = true;
452
+ }, 500);
281
453
  }
282
454
  }, [project, setNodes, setEdges, setViewport]);
455
+ const prevStateTypeRef = useRef(stateType);
456
+ useEffect(() => {
457
+ var _a, _b, _c, _d, _e, _f;
458
+ const didSwitch = prevStateTypeRef.current !== stateType;
459
+ prevStateTypeRef.current = stateType;
460
+ if (stateType === 'live') {
461
+ hasLoadedInitialData.current = false;
462
+ // Use published flow if it has nodes; fall back to draft if not yet published
463
+ const flowSource = (((_b = (_a = project === null || project === void 0 ? void 0 : project.flow) === null || _a === void 0 ? void 0 : _a.nodes) === null || _b === void 0 ? void 0 : _b.length) > 0) ? project.flow : project === null || project === void 0 ? void 0 : project.flow_draft;
464
+ if (!((_c = flowSource === null || flowSource === void 0 ? void 0 : flowSource.nodes) === null || _c === void 0 ? void 0 : _c.length))
465
+ return;
466
+ const { x = 0, y = 0, zoom = 1 } = (flowSource === null || flowSource === void 0 ? void 0 : flowSource.viewport) || {};
467
+ setNodes((flowSource === null || flowSource === void 0 ? void 0 : flowSource.nodes) || []);
468
+ setEdges((flowSource === null || flowSource === void 0 ? void 0 : flowSource.edges) || []);
469
+ setViewport({ x, y, zoom });
470
+ }
471
+ else if (didSwitch && stateType === 'design' && (project === null || project === void 0 ? void 0 : project.flow_draft)) {
472
+ // Only reload draft when actually switching back — not on every project refetch
473
+ hasLoadedInitialData.current = false;
474
+ const { x = 0, y = 0, zoom = 1 } = ((_d = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _d === void 0 ? void 0 : _d.viewport) || {};
475
+ setNodes(((_e = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _e === void 0 ? void 0 : _e.nodes) || []);
476
+ setEdges(((_f = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _f === void 0 ? void 0 : _f.edges) || []);
477
+ setViewport({ x, y, zoom });
478
+ setTimeout(() => { hasLoadedInitialData.current = true; }, 500);
479
+ }
480
+ }, [stateType, project]);
283
481
  useEffect(() => {
284
482
  setEdges((eds) => eds.map((edge) => ({
285
483
  ...edge,
286
- animated: stateType === 'sessions'
484
+ animated: stateType === 'live'
287
485
  })));
288
486
  }, [stateType, setEdges]);
487
+ useEffect(() => {
488
+ if (stateType !== 'live')
489
+ return;
490
+ const interval = setInterval(() => { sessionsRefetch(); }, 15000);
491
+ return () => clearInterval(interval);
492
+ }, [stateType, sessionsRefetch]);
289
493
  //////////////////////////// Connections /////////////////////////////////
290
494
  const onConnect = useCallback((connection) => {
291
495
  setEdges((eds) => {
@@ -299,9 +503,14 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
299
503
  });
300
504
  }, [setEdges]);
301
505
  const onConnectStart = useCallback(() => {
302
- // isReconnectingRef.current = false;
506
+ isConnectingRef.current = true;
303
507
  }, []);
304
508
  const onConnectEnd = useCallback((event, connectionState) => {
509
+ isConnectingRef.current = false;
510
+ // Trigger saveWithHistory after connection ends to capture final state
511
+ setTimeout(() => {
512
+ saveWithHistory();
513
+ }, 0);
305
514
  if (!connectionState.isValid && !connectionState.toNode) {
306
515
  if (!connectionState.toNode && !isReconnectingRef.current) {
307
516
  const { clientX, clientY } = 'changedTouches' in event ? event.changedTouches[0] : event;
@@ -393,6 +602,10 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
393
602
  setNodes(prevState.nodes);
394
603
  setEdges(prevState.edges);
395
604
  setViewport(prevState.viewport);
605
+ toast.success("Undo successful");
606
+ }
607
+ else {
608
+ toast.info("Nothing to undo");
396
609
  }
397
610
  }, [undo, setNodes, setEdges, setViewport]);
398
611
  const handleRedo = useCallback(() => {
@@ -401,6 +614,10 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
401
614
  setNodes(nextState.nodes);
402
615
  setEdges(nextState.edges);
403
616
  setViewport(nextState.viewport);
617
+ toast.success("Redo successful");
618
+ }
619
+ else {
620
+ toast.info("Nothing to redo");
404
621
  }
405
622
  }, [redo, setNodes, setEdges, setViewport]);
406
623
  useEffect(() => {
@@ -411,6 +628,19 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
411
628
  handleRedo();
412
629
  }
413
630
  }, [cmdAndZPressed, cmdAndYPressed, handleUndo, handleRedo]);
631
+ ////////////////// Auto Arrange Nodes /////////////////////////////
632
+ const handleAutoArrange = useCallback(() => {
633
+ // Use rfInstance.getNodes() so each node carries its actual measured dimensions
634
+ const liveNodes = rfInstance ? rfInstance.getNodes() : nodes;
635
+ const arrangedNodes = autoArrangeNodes(liveNodes, edges);
636
+ setNodes(arrangedNodes);
637
+ addToHistory({ nodes: arrangedNodes, edges, viewport: rfInstance === null || rfInstance === void 0 ? void 0 : rfInstance.getViewport() });
638
+ performSave();
639
+ // Wait for React to repaint the new positions before fitting the view
640
+ setTimeout(() => {
641
+ rfInstance === null || rfInstance === void 0 ? void 0 : rfInstance.fitView({ padding: 0.1, duration: 400 });
642
+ }, 50);
643
+ }, [nodes, edges, setNodes, addToHistory, rfInstance, performSave]);
414
644
  ////////////////// For Context Menu /////////////////////////////
415
645
  const createNode = useCallback((type) => {
416
646
  if ((contextMenuPosition === null || contextMenuPosition === void 0 ? void 0 : contextMenuPosition.x) != null && (contextMenuPosition === null || contextMenuPosition === void 0 ? void 0 : contextMenuPosition.y) != null) {
@@ -423,7 +653,8 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
423
653
  while (existingLabels.has(newLabel)) {
424
654
  newLabel = `${type}_label_${counter++}`;
425
655
  }
426
- const template = type === 'function' ? NODE_TEMPLATES.function : NODE_TEMPLATES.default;
656
+ // const template = type === 'function' ? NODE_TEMPLATES.function : type === 'gotonode' ? NODE_TEMPLATES.gotonode : NODE_TEMPLATES.default;
657
+ const template = NODE_TEMPLATES.default;
427
658
  return [
428
659
  ...prevNodes,
429
660
  {
@@ -433,9 +664,21 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
433
664
  data: {
434
665
  ...template.data,
435
666
  label: newLabel,
436
- ...(type === 'function' ? { function: '' } : {
667
+ type,
668
+ ...(type === 'api' ? {
669
+ api: '',
670
+ response: {
671
+ source: 'api_call',
672
+ inputs: [{ id: 1 }]
673
+ }
674
+ } : type === 'gotonode' ? {
675
+ node: '',
676
+ response: {
677
+ source: 'gotonode',
678
+ inputs: [{ id: 1 }]
679
+ }
680
+ } : {
437
681
  message: '',
438
- type,
439
682
  response: {
440
683
  validation: 'alpha-numeric',
441
684
  source: 'manual',
@@ -450,10 +693,30 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
450
693
  }, [contextMenuPosition, setNodes]);
451
694
  //////////////////// Auto Save //////////////////
452
695
  useEffect(() => {
453
- if (rfInstance && identifier && (nodes.length > 0 || edges.length > 0)) {
696
+ // CRITICAL: Only autosave if we have valid data and identifier matches
697
+ // This prevents saving empty state during tab switches or initial load
698
+ if (stateType !== 'live' &&
699
+ hasLoadedInitialData.current &&
700
+ rfInstance &&
701
+ getIdentifier &&
702
+ identifier === getIdentifier &&
703
+ nodes &&
704
+ edges &&
705
+ (nodes.length > 0 || edges.length > 0)) {
454
706
  saveWithHistory();
455
707
  }
456
- }, [nodes, edges, rfInstance, identifier, saveWithHistory]);
708
+ }, [nodes, edges, rfInstance, getIdentifier, identifier, saveWithHistory, stateType]);
709
+ ///////////////// Drag Event Handlers ////////////////
710
+ const onNodeDragStart = useCallback(() => {
711
+ isDraggingRef.current = true;
712
+ }, []);
713
+ const onNodeDragStop = useCallback(() => {
714
+ isDraggingRef.current = false;
715
+ // Trigger saveWithHistory after drag ends to capture final state
716
+ setTimeout(() => {
717
+ saveWithHistory();
718
+ }, 0);
719
+ }, [saveWithHistory]);
457
720
  ///////////////// Delete Key Code ////////////////
458
721
  const onSelectionChange = useCallback(({ nodes, edges }) => {
459
722
  // console.log('Selected nodes:', nodes)
@@ -469,6 +732,6 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
469
732
  window.addEventListener('keydown', handleKeyDown);
470
733
  return () => window.removeEventListener('keydown', handleKeyDown);
471
734
  }, [setNodes, setEdges]);
472
- return (_jsxs(Box, { position: 'absolute', top: '0', right: '0', bottom: '0px', left: '0', style: {}, children: [_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesDelete: onNodesDelete, onEdgesDelete: onEdgesDelete, onSelectionChange: onSelectionChange, onConnectEnd: onConnectEnd, onConnectStart: onConnectStart, onReconnect: onReconnect, onReconnectStart: onReconnectStart, onReconnectEnd: onReconnectEnd, onInit: setRfInstance, snapToGrid: true, fitView: true, minZoom: 0.1, maxZoom: 3, colorMode: theme, onPaneContextMenu: handleContextMenu, deleteKeyCode: null, attributionPosition: "bottom-right", children: [_jsx(Controls, {}), _jsx(Background, { gap: 12, size: 1 }), _jsx(Panel, { position: "top-center", children: _jsx(TopCenterPanel, { projectId: identifier }) }), _jsx(Panel, { position: "top-left", children: _jsx(TopLeftPanel, { api_key: api_key, projectId: identifier, createNode: createNode, onSave: onSave, saving: saving, rfInstance: rfInstance, redo: handleRedo, undo: handleUndo, historyIndex: historyIndex }) }), _jsx(Panel, { position: "top-right", children: _jsx(TopRightPanel, { api_key: api_key, projectId: identifier, createNode: createNode, onSave: onSave, saving: saving, rfInstance: rfInstance }) })] }), _jsx(AppAnalytics, { api_key: api_key, project: project, sessions: sessions, sessionsStatus: sessionsStatus, refetch: sessionsRefetch }), _jsx(AppSettings, { api_key: api_key, project: project, projectsRefetch: projectsRefetch, providers: providers, provider: provider, triggers: triggers, triggersRefetch: triggersRefetch, functions: functions, functionsRefetch: functionsRefetch, envs: envs, envsRefetch: envsRefetch }), _jsx(ContextMenu, { api_key: api_key, open: showContextMenu, setOpen: setShowContextMenu, createNode: createNode, menuPosition: contextMenuPosition })] }));
735
+ return (_jsxs("div", { className: 'absolute top-0 right-0 bottom-0 left-0', children: [_jsxs(ReactFlow, { nodes: nodes, edges: edges, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, onConnect: onConnect, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesDelete: onNodesDelete, onEdgesDelete: onEdgesDelete, onSelectionChange: onSelectionChange, onConnectEnd: onConnectEnd, onConnectStart: onConnectStart, onReconnect: onReconnect, onReconnectStart: onReconnectStart, onNodeDragStart: onNodeDragStart, onNodeDragStop: onNodeDragStop, onReconnectEnd: onReconnectEnd, onInit: setRfInstance, snapToGrid: true, fitView: true, minZoom: 0.1, maxZoom: 3, colorMode: theme, onPaneContextMenu: handleContextMenu, deleteKeyCode: null, attributionPosition: "bottom-right", children: [_jsx(Background, { gap: 12, size: 1 }), _jsx(Panel, { position: "top-center", children: _jsx(TopCenterPanel, { projectId: getIdentifier }) }), _jsx(Panel, { position: "top-left", children: _jsx(TopLeftPanel, { api_key: api_key, projectId: getIdentifier, createNode: createNode, onSave: onSave, saving: saving, rfInstance: rfInstance, redo: handleRedo, undo: handleUndo, historyIndex: historyIndex, historySize: historySize, autoArrange: handleAutoArrange }) }), _jsx(Panel, { position: "bottom-left", children: _jsx(BottomLeftPanel, { api_key: api_key, projectId: getIdentifier, createNode: createNode, onSave: onSave, saving: saving, rfInstance: rfInstance, redo: handleRedo, undo: handleUndo, historyIndex: historyIndex, autoArrange: handleAutoArrange }) }), _jsx(Panel, { position: "top-right", children: _jsx(TopRightPanel, { api_key: api_key, projectId: getIdentifier, createNode: createNode, onSave: onSave, saving: saving, rfInstance: rfInstance }) })] }), _jsx(AppAnalytics, { api_key: api_key, project: project, sessions: sessions, sessionsStatus: sessionsStatus, refetch: sessionsRefetch }), _jsx(AppSettings, { api_key: api_key, project: project, projectRefetch: projectRefetch, projectsRefetch: projectsRefetch, providers: providers, provider: provider, triggers: triggers, triggersRefetch: triggersRefetch, functions: functions, functionsRefetch: functionsRefetch, envs: envs, envsRefetch: envsRefetch }), _jsx(ContextMenu, { api_key: api_key, open: showContextMenu, setOpen: setShowContextMenu, createNode: createNode, menuPosition: contextMenuPosition })] }));
473
736
  }
474
737
  //# sourceMappingURL=studio_pane.js.map