flowstudio 0.0.29 → 0.0.31

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 (190) hide show
  1. package/dist/ai_creator.d.ts +1 -1
  2. package/dist/ai_creator.js +32 -128
  3. package/dist/ai_creator.js.map +1 -1
  4. package/dist/api_calls.d.ts +2 -0
  5. package/dist/api_calls.js +148 -0
  6. package/dist/api_calls.js.map +1 -0
  7. package/dist/app_analytics.d.ts +1 -1
  8. package/dist/app_analytics.js +75 -69
  9. package/dist/app_analytics.js.map +1 -1
  10. package/dist/app_details.d.ts +1 -1
  11. package/dist/app_details.js +25 -24
  12. package/dist/app_details.js.map +1 -1
  13. package/dist/app_layout.d.ts +2 -2
  14. package/dist/app_layout.js +4 -4
  15. package/dist/app_layout.js.map +1 -1
  16. package/dist/app_settings.d.ts +1 -1
  17. package/dist/app_settings.js +74 -7
  18. package/dist/app_settings.js.map +1 -1
  19. package/dist/bottom_left_panel.d.ts +1 -0
  20. package/dist/bottom_left_panel.js +8 -0
  21. package/dist/bottom_left_panel.js.map +1 -0
  22. package/dist/code.d.ts +1 -1
  23. package/dist/code.js +6 -2
  24. package/dist/code.js.map +1 -1
  25. package/dist/components/ftable.d.ts +13 -0
  26. package/dist/components/ftable.js +65 -0
  27. package/dist/components/ftable.js.map +1 -0
  28. package/dist/components/gsheet/index.d.ts +1 -0
  29. package/dist/components/gsheet/index.js +21 -0
  30. package/dist/components/gsheet/index.js.map +1 -0
  31. package/dist/components/mention_input/index.d.ts +18 -1
  32. package/dist/components/mention_input/index.js +203 -111
  33. package/dist/components/mention_input/index.js.map +1 -1
  34. package/dist/components/paginator_table.d.ts +1 -1
  35. package/dist/components/paginator_table.js +2 -2
  36. package/dist/components/paginator_table.js.map +1 -1
  37. package/dist/components/sheet/index.d.ts +1 -1
  38. package/dist/components/sheet/index.js +4 -2
  39. package/dist/components/sheet/index.js.map +1 -1
  40. package/dist/components/ui/alert.d.ts +8 -0
  41. package/dist/components/ui/alert.js +23 -0
  42. package/dist/components/ui/alert.js.map +1 -0
  43. package/dist/components/ui/badge.d.ts +9 -0
  44. package/dist/components/ui/badge.js +21 -0
  45. package/dist/components/ui/badge.js.map +1 -0
  46. package/dist/components/ui/button.d.ts +11 -0
  47. package/dist/components/ui/button.js +35 -0
  48. package/dist/components/ui/button.js.map +1 -0
  49. package/dist/components/ui/card.d.ts +8 -0
  50. package/dist/components/ui/card.js +17 -0
  51. package/dist/components/ui/card.js.map +1 -0
  52. package/dist/components/ui/dialog.d.ts +19 -0
  53. package/dist/components/ui/dialog.js +33 -0
  54. package/dist/components/ui/dialog.js.map +1 -0
  55. package/dist/components/ui/dropdown-menu.d.ts +27 -0
  56. package/dist/components/ui/dropdown-menu.js +46 -0
  57. package/dist/components/ui/dropdown-menu.js.map +1 -0
  58. package/dist/components/ui/form.d.ts +24 -0
  59. package/dist/components/ui/form.js +61 -0
  60. package/dist/components/ui/form.js.map +1 -0
  61. package/dist/components/ui/input-group.d.ts +16 -0
  62. package/dist/components/ui/input-group.js +65 -0
  63. package/dist/components/ui/input-group.js.map +1 -0
  64. package/dist/components/ui/input.d.ts +5 -0
  65. package/dist/components/ui/input.js +9 -0
  66. package/dist/components/ui/input.js.map +1 -0
  67. package/dist/components/ui/label.d.ts +5 -0
  68. package/dist/components/ui/label.js +10 -0
  69. package/dist/components/ui/label.js.map +1 -0
  70. package/dist/components/ui/popover.d.ts +7 -0
  71. package/dist/components/ui/popover.js +23 -0
  72. package/dist/components/ui/popover.js.map +1 -0
  73. package/dist/components/ui/scroll-area.d.ts +5 -0
  74. package/dist/components/ui/scroll-area.js +12 -0
  75. package/dist/components/ui/scroll-area.js.map +1 -0
  76. package/dist/components/ui/select.d.ts +13 -0
  77. package/dist/components/ui/select.js +37 -0
  78. package/dist/components/ui/select.js.map +1 -0
  79. package/dist/components/ui/separator.d.ts +4 -0
  80. package/dist/components/ui/separator.js +8 -0
  81. package/dist/components/ui/separator.js.map +1 -0
  82. package/dist/components/ui/sheet.d.ts +13 -0
  83. package/dist/components/ui/sheet.js +93 -0
  84. package/dist/components/ui/sheet.js.map +1 -0
  85. package/dist/components/ui/skeleton.d.ts +2 -0
  86. package/dist/components/ui/skeleton.js +7 -0
  87. package/dist/components/ui/skeleton.js.map +1 -0
  88. package/dist/components/ui/sonner.d.ts +3 -0
  89. package/dist/components/ui/sonner.js +42 -0
  90. package/dist/components/ui/sonner.js.map +1 -0
  91. package/dist/components/ui/spinner.d.ts +7 -0
  92. package/dist/components/ui/spinner.js +17 -0
  93. package/dist/components/ui/spinner.js.map +1 -0
  94. package/dist/components/ui/switch.d.ts +4 -0
  95. package/dist/components/ui/switch.js +8 -0
  96. package/dist/components/ui/switch.js.map +1 -0
  97. package/dist/components/ui/table.d.ts +10 -0
  98. package/dist/components/ui/table.js +21 -0
  99. package/dist/components/ui/table.js.map +1 -0
  100. package/dist/components/ui/tabs.d.ts +7 -0
  101. package/dist/components/ui/tabs.js +13 -0
  102. package/dist/components/ui/tabs.js.map +1 -0
  103. package/dist/components/ui/textarea.d.ts +5 -0
  104. package/dist/components/ui/textarea.js +9 -0
  105. package/dist/components/ui/textarea.js.map +1 -0
  106. package/dist/components/ui/tooltip.d.ts +7 -0
  107. package/dist/components/ui/tooltip.js +11 -0
  108. package/dist/components/ui/tooltip.js.map +1 -0
  109. package/dist/constants.js +2 -2
  110. package/dist/constants.js.map +1 -1
  111. package/dist/context.d.ts +1 -1
  112. package/dist/context.js +2 -2
  113. package/dist/context.js.map +1 -1
  114. package/dist/envs.d.ts +2 -2
  115. package/dist/envs.js +2 -150
  116. package/dist/envs.js.map +1 -1
  117. package/dist/functions.d.ts +2 -5
  118. package/dist/functions.js +2 -210
  119. package/dist/functions.js.map +1 -1
  120. package/dist/globals.css +253 -0
  121. package/dist/home.d.ts +1 -1
  122. package/dist/home.js +24 -14
  123. package/dist/home.js.map +1 -1
  124. package/dist/index.d.ts +2 -2
  125. package/dist/index.js +57 -16
  126. package/dist/index.js.map +1 -1
  127. package/dist/lib/pane-context.d.ts +10 -0
  128. package/dist/lib/pane-context.js +21 -0
  129. package/dist/lib/pane-context.js.map +1 -0
  130. package/dist/lib/toast.d.ts +12 -0
  131. package/dist/lib/toast.js +36 -0
  132. package/dist/lib/toast.js.map +1 -0
  133. package/dist/lib/utils.d.ts +2 -0
  134. package/dist/lib/utils.js +6 -0
  135. package/dist/lib/utils.js.map +1 -0
  136. package/dist/new_project.d.ts +1 -1
  137. package/dist/new_project.js +60 -41
  138. package/dist/new_project.js.map +1 -1
  139. package/dist/node.d.ts +2 -2
  140. package/dist/node.js +157 -39
  141. package/dist/node.js.map +1 -1
  142. package/dist/project_widget.d.ts +2 -1
  143. package/dist/project_widget.js +6 -2
  144. package/dist/project_widget.js.map +1 -1
  145. package/dist/secrets.d.ts +2 -0
  146. package/dist/secrets.js +129 -0
  147. package/dist/secrets.js.map +1 -0
  148. package/dist/simulator.d.ts +1 -1
  149. package/dist/simulator.js +36 -33
  150. package/dist/simulator.js.map +1 -1
  151. package/dist/studio_pane.d.ts +1 -1
  152. package/dist/studio_pane.js +281 -56
  153. package/dist/studio_pane.js.map +1 -1
  154. package/dist/studio_pane_old.d.ts +1 -1
  155. package/dist/studio_pane_old.js +2 -3
  156. package/dist/studio_pane_old.js.map +1 -1
  157. package/dist/studio_pane_old_new.d.ts +1 -1
  158. package/dist/studio_pane_old_new.js +2 -3
  159. package/dist/studio_pane_old_new.js.map +1 -1
  160. package/dist/styles/globals.css +59 -0
  161. package/dist/styles.css +2 -0
  162. package/dist/top_center_panel.d.ts +1 -1
  163. package/dist/top_center_panel.js +4 -3
  164. package/dist/top_center_panel.js.map +1 -1
  165. package/dist/top_left_panel.d.ts +1 -1
  166. package/dist/top_left_panel.js +7 -41
  167. package/dist/top_left_panel.js.map +1 -1
  168. package/dist/top_right_panel.d.ts +1 -1
  169. package/dist/top_right_panel.js +12 -13
  170. package/dist/top_right_panel.js.map +1 -1
  171. package/dist/triggers.d.ts +2 -2
  172. package/dist/triggers.js +57 -37
  173. package/dist/triggers.js.map +1 -1
  174. package/dist/zustand/store.d.ts +319 -3
  175. package/package.json +51 -24
  176. package/src/globals.css +253 -0
  177. package/src/styles/globals.css +59 -0
  178. package/dist/components/combo_box/index.d.ts +0 -2
  179. package/dist/components/combo_box/index.js +0 -150
  180. package/dist/components/combo_box/index.js.map +0 -1
  181. package/dist/components/combo_box/style.css +0 -10
  182. package/dist/components/date_picker/index.d.ts +0 -1
  183. package/dist/components/date_picker/index.js +0 -46
  184. package/dist/components/date_picker/index.js.map +0 -1
  185. package/dist/components/date_picker/style.css +0 -204
  186. package/dist/components/gtable.d.ts +0 -15
  187. package/dist/components/gtable.js +0 -122
  188. package/dist/components/gtable.js.map +0 -1
  189. package/src/components/combo_box/style.css +0 -10
  190. 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
+ // bnode: {
34
+ // type: 'bNode' as const,
35
+ // data: { label: '', function: '' }
36
+ // },
29
37
  default: {
30
38
  type: 'gNode',
31
39
  data: {
@@ -40,6 +48,95 @@ 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 = 300, // Horizontal offset between levels
64
+ verticalSpacing = 150, // Vertical offset between nodes
65
+ startX = 100, // Starting X position
66
+ startY = 100, // Starting Y position
67
+ nodeWidth: NODE_WIDTH = 250, nodeHeight: NODE_HEIGHT = 180, } = options;
68
+ // Build adjacency lists
69
+ const incomingEdges = new Map();
70
+ const outgoingEdges = new Map();
71
+ nodes.forEach((n) => {
72
+ incomingEdges.set(n.id, []);
73
+ outgoingEdges.set(n.id, []);
74
+ });
75
+ edges.forEach((e) => {
76
+ if (incomingEdges.has(e.target)) {
77
+ incomingEdges.get(e.target).push(e.source);
78
+ }
79
+ if (outgoingEdges.has(e.source)) {
80
+ outgoingEdges.get(e.source).push(e.target);
81
+ }
82
+ });
83
+ // Find starting nodes (no incoming edges)
84
+ const startNodes = nodes.filter((n) => { var _a, _b; return ((_b = (_a = incomingEdges.get(n.id)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0; });
85
+ // BFS level assignment
86
+ const levels = [];
87
+ const visited = new Set();
88
+ const queue = startNodes.map((n) => ({
89
+ id: n.id,
90
+ level: 0,
91
+ }));
92
+ while (queue.length > 0) {
93
+ const { id, level } = queue.shift();
94
+ if (!levels[level])
95
+ levels[level] = [];
96
+ if (!visited.has(id)) {
97
+ levels[level].push(id);
98
+ visited.add(id);
99
+ const children = outgoingEdges.get(id) || [];
100
+ children.forEach((childId) => {
101
+ if (!visited.has(childId))
102
+ queue.push({ id: childId, level: level + 1 });
103
+ });
104
+ }
105
+ }
106
+ // Assign positions level by level (left → right)
107
+ const updatedNodes = nodes.map((n) => ({ ...n }));
108
+ const nodeMap = new Map(updatedNodes.map((n) => [n.id, n]));
109
+ const nodePositions = new Map();
110
+ levels.forEach((levelNodes, levelIndex) => {
111
+ const x = startX + levelIndex * (NODE_WIDTH + horizontalSpacing);
112
+ // Sort nodes in this level based on their parent positions (top to bottom)
113
+ if (levelIndex > 0) {
114
+ levelNodes.sort((a, b) => {
115
+ const parentsA = incomingEdges.get(a) || [];
116
+ const parentsB = incomingEdges.get(b) || [];
117
+ // Calculate average Y position of parents
118
+ const avgYA = parentsA.length > 0
119
+ ? parentsA.reduce((sum, parentId) => { var _a; return sum + (((_a = nodePositions.get(parentId)) === null || _a === void 0 ? void 0 : _a.y) || 0); }, 0) / parentsA.length
120
+ : 0;
121
+ const avgYB = parentsB.length > 0
122
+ ? parentsB.reduce((sum, parentId) => { var _a; return sum + (((_a = nodePositions.get(parentId)) === null || _a === void 0 ? void 0 : _a.y) || 0); }, 0) / parentsB.length
123
+ : 0;
124
+ return avgYA - avgYB;
125
+ });
126
+ }
127
+ const totalHeight = levelNodes.length * (NODE_HEIGHT + verticalSpacing);
128
+ const baseY = startY - totalHeight / 2;
129
+ levelNodes.forEach((nodeId, idx) => {
130
+ const y = baseY + idx * (NODE_HEIGHT + verticalSpacing);
131
+ const node = nodeMap.get(nodeId);
132
+ if (node) {
133
+ node.position = { x, y };
134
+ nodePositions.set(nodeId, { x, y });
135
+ }
136
+ });
137
+ });
138
+ return Array.from(nodeMap.values());
139
+ }
43
140
  // Debounce utility
44
141
  function useDebounce(callback, delay) {
45
142
  const timeoutRef = useRef(null);
@@ -89,15 +186,21 @@ function useFlowHistory() {
89
186
  return { addToHistory, undo, redo, historyIndex, historySize: history.length };
90
187
  }
91
188
  // Custom hook for auto-save functionality
92
- function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory) {
189
+ function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory, isDraggingRef, isConnectingRef) {
93
190
  const [saving, setSaving] = useState(false);
94
191
  const lastSavedStateRef = useRef('');
95
192
  const performSave = useCallback(async () => {
96
193
  if (!rfInstance || !identifier)
97
194
  return;
195
+ const flow = rfInstance.toObject();
196
+ // CRITICAL: Never save empty flows unless it's intentional
197
+ // Prevent saving if both nodes and edges are empty (likely a race condition)
198
+ if (!flow.nodes || !flow.edges || (flow.nodes.length === 0 && flow.edges.length === 0)) {
199
+ console.warn('Prevented saving empty flow - possible race condition');
200
+ return;
201
+ }
98
202
  setSaving(true);
99
203
  try {
100
- const flow = rfInstance.toObject();
101
204
  const response = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${identifier}`, {
102
205
  method: 'PATCH',
103
206
  headers: {
@@ -121,6 +224,14 @@ function useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory
121
224
  const saveWithHistory = useCallback(() => {
122
225
  if (!rfInstance)
123
226
  return;
227
+ // Skip adding to history during drag and connection operations
228
+ if (isDraggingRef.current || isConnectingRef.current)
229
+ return;
230
+ // CRITICAL: Don't save if nodes/edges are undefined or both empty
231
+ if (!nodes || !edges || (nodes.length === 0 && edges.length === 0)) {
232
+ console.warn('Skipping save - empty or undefined state');
233
+ return;
234
+ }
124
235
  const currentState = {
125
236
  nodes: nodes === null || nodes === void 0 ? void 0 : nodes.map(n => ({ ...n })),
126
237
  edges: edges === null || edges === void 0 ? void 0 : edges.map(e => ({ ...e })),
@@ -160,67 +271,81 @@ function useContextMenu() {
160
271
  };
161
272
  }
162
273
  export default function StudioPane({ api_key, projectsRefetch, identifier, providers, provider, theme }) {
274
+ var _a;
163
275
  const { stateType } = useConfigStore();
276
+ const stateTypeRef = useRef(stateType);
277
+ useEffect(() => { stateTypeRef.current = stateType; }, [stateType]);
278
+ const [getIdentifier, setIdentifier] = useState(identifier);
279
+ useEffect(() => {
280
+ setIdentifier(identifier);
281
+ // Reset the loaded flag when switching projects
282
+ hasLoadedInitialData.current = false;
283
+ }, [identifier]);
164
284
  // Optimized queries with better caching
165
285
  const { data: project, status: projectStatus, refetch: projectRefetch } = useQuery({
166
- queryKey: [`project_${identifier}`],
286
+ queryKey: [`project_${getIdentifier}`],
167
287
  queryFn: async () => {
168
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${identifier}`, {
288
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${getIdentifier}`, {
169
289
  headers: { "X-API-KEY": `${api_key}` }
170
290
  });
171
291
  return res === null || res === void 0 ? void 0 : res.json();
172
292
  },
173
- enabled: !!identifier,
293
+ enabled: !!getIdentifier,
174
294
  staleTime: QUERY_STALE_TIME,
175
- gcTime: QUERY_CACHE_TIME
295
+ gcTime: QUERY_CACHE_TIME,
296
+ placeholderData: (previousData) => previousData
176
297
  });
177
298
  const { data: functions, refetch: functionsRefetch } = useQuery({
178
- queryKey: [`project_${identifier}_functions`],
299
+ queryKey: [`project_${getIdentifier}_api_calls`],
179
300
  queryFn: async () => {
180
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/functions?project=${identifier}`, {
301
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/api-calls?project=${getIdentifier}`, {
181
302
  headers: { "X-API-KEY": `${api_key}` }
182
303
  });
183
304
  return res === null || res === void 0 ? void 0 : res.json();
184
305
  },
185
- enabled: !!identifier,
306
+ enabled: !!getIdentifier,
186
307
  staleTime: QUERY_STALE_TIME,
187
- gcTime: QUERY_CACHE_TIME
308
+ gcTime: QUERY_CACHE_TIME,
309
+ placeholderData: (previousData) => previousData
188
310
  });
189
311
  const { data: triggers, refetch: triggersRefetch } = useQuery({
190
- queryKey: [`project_${identifier}_triggers`],
312
+ queryKey: [`project_${getIdentifier}_triggers`],
191
313
  queryFn: async () => {
192
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/triggers?project=${identifier}`, {
314
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/triggers?project=${getIdentifier}`, {
193
315
  headers: { "X-API-KEY": `${api_key}` }
194
316
  });
195
317
  return res === null || res === void 0 ? void 0 : res.json();
196
318
  },
197
- enabled: !!identifier,
319
+ enabled: !!getIdentifier,
198
320
  staleTime: QUERY_STALE_TIME,
199
- gcTime: QUERY_CACHE_TIME
321
+ gcTime: QUERY_CACHE_TIME,
322
+ placeholderData: (previousData) => previousData
200
323
  });
201
324
  const { data: envs, refetch: envsRefetch } = useQuery({
202
- queryKey: [`project_${identifier}_envs`],
325
+ queryKey: [`project_${getIdentifier}_secrets`],
203
326
  queryFn: async () => {
204
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/envs?project=${identifier}`, {
327
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/secrets?project=${getIdentifier}`, {
205
328
  headers: { "X-API-KEY": `${api_key}` }
206
329
  });
207
330
  return res === null || res === void 0 ? void 0 : res.json();
208
331
  },
209
- enabled: !!identifier,
332
+ enabled: !!getIdentifier,
210
333
  staleTime: QUERY_STALE_TIME,
211
- gcTime: QUERY_CACHE_TIME
334
+ gcTime: QUERY_CACHE_TIME,
335
+ placeholderData: (previousData) => previousData
212
336
  });
213
337
  const { data: sessions, status: sessionsStatus, refetch: sessionsRefetch } = useQuery({
214
- queryKey: [`project_${identifier}_sessions`],
338
+ queryKey: [`project_${getIdentifier}_sessions`],
215
339
  queryFn: async () => {
216
- const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/sessions?project=${identifier}`, {
340
+ const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/sessions?project=${getIdentifier}`, {
217
341
  headers: { "X-API-KEY": `${api_key}` }
218
342
  });
219
343
  return res === null || res === void 0 ? void 0 : res.json();
220
344
  },
221
- enabled: !!identifier,
345
+ enabled: !!getIdentifier,
222
346
  staleTime: QUERY_STALE_TIME,
223
- gcTime: QUERY_CACHE_TIME
347
+ gcTime: QUERY_CACHE_TIME,
348
+ placeholderData: (previousData) => previousData
224
349
  });
225
350
  // Optimized session lookup - compute once, use many times
226
351
  const sessionsByNodeId = useMemo(() => {
@@ -235,57 +360,99 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
235
360
  }, {});
236
361
  }, [sessions === null || sessions === void 0 ? void 0 : sessions.results]);
237
362
  ////////////////// 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
363
+ const [nodes, setNodes, onNodesChange] = useNodesState((project === null || project === void 0 ? void 0 : project.nodes) || []);
364
+ const [edges, setEdges, onEdgesChange] = useEdgesState((project === null || project === void 0 ? void 0 : project.edges) || []);
365
+ // Refs hold latest data so nodeTypes can be stable (no deps) while nodes
366
+ // still read current functions/sessions/envs at render time.
367
+ const functionsRef = useRef(undefined);
368
+ const sessionsByNodeIdRef = useRef({});
369
+ const envsRef = useRef([]);
370
+ functionsRef.current = functions === null || functions === void 0 ? void 0 : functions.results;
371
+ sessionsByNodeIdRef.current = sessionsByNodeId;
372
+ envsRef.current = (_a = envs === null || envs === void 0 ? void 0 : envs.results) !== null && _a !== void 0 ? _a : [];
373
+ // eslint-disable-next-line react-hooks/exhaustive-deps
242
374
  const nodeTypes = useMemo(() => ({
243
375
  gNode: (props) => (_jsx(GNode, { ...props, data: {
244
376
  ...props.data,
245
- functions: functions === null || functions === void 0 ? void 0 : functions.results,
246
- sessions: sessionsByNodeId[props.id] || []
377
+ functions: functionsRef.current,
378
+ sessions: sessionsByNodeIdRef.current[props.id] || [],
379
+ secrets: envsRef.current,
247
380
  } })),
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]);
381
+ }), []);
254
382
  const edgeTypes = useMemo(() => ({
255
383
  default: memo((props) => (_jsx(CustomSmoothstepEdge, { ...props, deletable: true })))
256
384
  }), []);
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
385
  const [rfInstance, setRfInstance] = useState(null);
260
386
  const { setViewport, screenToFlowPosition, getNodes, getEdges } = useReactFlow();
261
387
  const isReconnectingRef = useRef(false);
388
+ const hasLoadedInitialData = useRef(false);
389
+ const isDraggingRef = useRef(false);
390
+ const isConnectingRef = useRef(false);
262
391
  // Custom hooks
263
392
  const { addToHistory, undo, redo, historyIndex, historySize } = useFlowHistory();
264
- const { performSave, saveWithHistory, saving } = useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory);
393
+ const { performSave, saveWithHistory, saving } = useAutoSave(rfInstance, identifier, api_key, nodes, edges, addToHistory, isDraggingRef, isConnectingRef);
265
394
  const { showContextMenu, contextMenuPosition, handleContextMenu, setShowContextMenu } = useContextMenu();
266
395
  // 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']);
396
+ const cmdAndSPressed = useKeyPress(['Meta+s', 'Strg+s', 'Ctrl+s']);
397
+ const cmdAndZPressed = useKeyPress(['Meta+z', 'Strg+z', 'Ctrl+z']);
398
+ const cmdAndYPressed = useKeyPress(['Meta+y', 'Strg+y', 'Ctrl+y']);
270
399
  ////////////// onMount and Initial Load //////////////
271
400
  useEffect(() => {
272
401
  // Component initialization logic can go here
273
402
  }, []);
274
403
  useEffect(() => {
275
404
  var _a, _b, _c;
405
+ // Skip resetting canvas when in live mode — live mode manages its own source
406
+ if (stateTypeRef.current === 'live')
407
+ return;
276
408
  if (project === null || project === void 0 ? void 0 : project.flow_draft) {
277
409
  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
410
  setNodes(((_b = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _b === void 0 ? void 0 : _b.nodes) || []);
279
411
  setEdges(((_c = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _c === void 0 ? void 0 : _c.edges) || []);
280
412
  setViewport({ x, y, zoom });
413
+ setTimeout(() => {
414
+ hasLoadedInitialData.current = true;
415
+ }, 500);
281
416
  }
282
417
  }, [project, setNodes, setEdges, setViewport]);
418
+ const prevStateTypeRef = useRef(stateType);
419
+ useEffect(() => {
420
+ var _a, _b, _c, _d, _e, _f;
421
+ const didSwitch = prevStateTypeRef.current !== stateType;
422
+ prevStateTypeRef.current = stateType;
423
+ if (stateType === 'live') {
424
+ hasLoadedInitialData.current = false;
425
+ // Use published flow if it has nodes; fall back to draft if not yet published
426
+ 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;
427
+ if (!((_c = flowSource === null || flowSource === void 0 ? void 0 : flowSource.nodes) === null || _c === void 0 ? void 0 : _c.length))
428
+ return;
429
+ const { x = 0, y = 0, zoom = 1 } = (flowSource === null || flowSource === void 0 ? void 0 : flowSource.viewport) || {};
430
+ setNodes((flowSource === null || flowSource === void 0 ? void 0 : flowSource.nodes) || []);
431
+ setEdges((flowSource === null || flowSource === void 0 ? void 0 : flowSource.edges) || []);
432
+ setViewport({ x, y, zoom });
433
+ }
434
+ else if (didSwitch && stateType === 'design' && (project === null || project === void 0 ? void 0 : project.flow_draft)) {
435
+ // Only reload draft when actually switching back — not on every project refetch
436
+ hasLoadedInitialData.current = false;
437
+ 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) || {};
438
+ setNodes(((_e = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _e === void 0 ? void 0 : _e.nodes) || []);
439
+ setEdges(((_f = project === null || project === void 0 ? void 0 : project.flow_draft) === null || _f === void 0 ? void 0 : _f.edges) || []);
440
+ setViewport({ x, y, zoom });
441
+ setTimeout(() => { hasLoadedInitialData.current = true; }, 500);
442
+ }
443
+ }, [stateType, project]);
283
444
  useEffect(() => {
284
445
  setEdges((eds) => eds.map((edge) => ({
285
446
  ...edge,
286
- animated: stateType === 'sessions'
447
+ animated: stateType === 'live'
287
448
  })));
288
449
  }, [stateType, setEdges]);
450
+ useEffect(() => {
451
+ if (stateType !== 'live')
452
+ return;
453
+ const interval = setInterval(() => { sessionsRefetch(); }, 15000);
454
+ return () => clearInterval(interval);
455
+ }, [stateType, sessionsRefetch]);
289
456
  //////////////////////////// Connections /////////////////////////////////
290
457
  const onConnect = useCallback((connection) => {
291
458
  setEdges((eds) => {
@@ -299,9 +466,14 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
299
466
  });
300
467
  }, [setEdges]);
301
468
  const onConnectStart = useCallback(() => {
302
- // isReconnectingRef.current = false;
469
+ isConnectingRef.current = true;
303
470
  }, []);
304
471
  const onConnectEnd = useCallback((event, connectionState) => {
472
+ isConnectingRef.current = false;
473
+ // Trigger saveWithHistory after connection ends to capture final state
474
+ setTimeout(() => {
475
+ saveWithHistory();
476
+ }, 0);
305
477
  if (!connectionState.isValid && !connectionState.toNode) {
306
478
  if (!connectionState.toNode && !isReconnectingRef.current) {
307
479
  const { clientX, clientY } = 'changedTouches' in event ? event.changedTouches[0] : event;
@@ -393,6 +565,10 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
393
565
  setNodes(prevState.nodes);
394
566
  setEdges(prevState.edges);
395
567
  setViewport(prevState.viewport);
568
+ toast.success("Undo successful");
569
+ }
570
+ else {
571
+ toast.info("Nothing to undo");
396
572
  }
397
573
  }, [undo, setNodes, setEdges, setViewport]);
398
574
  const handleRedo = useCallback(() => {
@@ -401,6 +577,10 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
401
577
  setNodes(nextState.nodes);
402
578
  setEdges(nextState.edges);
403
579
  setViewport(nextState.viewport);
580
+ toast.success("Redo successful");
581
+ }
582
+ else {
583
+ toast.info("Nothing to redo");
404
584
  }
405
585
  }, [redo, setNodes, setEdges, setViewport]);
406
586
  useEffect(() => {
@@ -411,6 +591,18 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
411
591
  handleRedo();
412
592
  }
413
593
  }, [cmdAndZPressed, cmdAndYPressed, handleUndo, handleRedo]);
594
+ ////////////////// Auto Arrange Nodes /////////////////////////////
595
+ const handleAutoArrange = useCallback(() => {
596
+ setNodes((currentNodes) => {
597
+ const arrangedNodes = autoArrangeNodes(currentNodes, edges, {
598
+ horizontalSpacing: 300, // Horizontal offset between levels
599
+ verticalSpacing: 150, // Vertical offset between nodes
600
+ startX: 100, // Starting X position
601
+ startY: 100 // Starting Y position
602
+ });
603
+ return arrangedNodes;
604
+ });
605
+ }, [edges, setNodes]);
414
606
  ////////////////// For Context Menu /////////////////////////////
415
607
  const createNode = useCallback((type) => {
416
608
  if ((contextMenuPosition === null || contextMenuPosition === void 0 ? void 0 : contextMenuPosition.x) != null && (contextMenuPosition === null || contextMenuPosition === void 0 ? void 0 : contextMenuPosition.y) != null) {
@@ -423,7 +615,8 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
423
615
  while (existingLabels.has(newLabel)) {
424
616
  newLabel = `${type}_label_${counter++}`;
425
617
  }
426
- const template = type === 'function' ? NODE_TEMPLATES.function : NODE_TEMPLATES.default;
618
+ // const template = type === 'function' ? NODE_TEMPLATES.function : type === 'bnode' ? NODE_TEMPLATES.bnode : NODE_TEMPLATES.default;
619
+ const template = NODE_TEMPLATES.default;
427
620
  return [
428
621
  ...prevNodes,
429
622
  {
@@ -433,9 +626,21 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
433
626
  data: {
434
627
  ...template.data,
435
628
  label: newLabel,
436
- ...(type === 'function' ? { function: '' } : {
629
+ type,
630
+ ...(type === 'api' ? {
631
+ api: '',
632
+ response: {
633
+ source: 'api_call',
634
+ inputs: [{ id: 1 }]
635
+ }
636
+ } : type === 'bnode' ? {
637
+ node: '',
638
+ response: {
639
+ source: 'bnode',
640
+ inputs: [{ id: 1 }]
641
+ }
642
+ } : {
437
643
  message: '',
438
- type,
439
644
  response: {
440
645
  validation: 'alpha-numeric',
441
646
  source: 'manual',
@@ -450,10 +655,30 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
450
655
  }, [contextMenuPosition, setNodes]);
451
656
  //////////////////// Auto Save //////////////////
452
657
  useEffect(() => {
453
- if (rfInstance && identifier && (nodes.length > 0 || edges.length > 0)) {
658
+ // CRITICAL: Only autosave if we have valid data and identifier matches
659
+ // This prevents saving empty state during tab switches or initial load
660
+ if (stateType !== 'live' &&
661
+ hasLoadedInitialData.current &&
662
+ rfInstance &&
663
+ getIdentifier &&
664
+ identifier === getIdentifier &&
665
+ nodes &&
666
+ edges &&
667
+ (nodes.length > 0 || edges.length > 0)) {
454
668
  saveWithHistory();
455
669
  }
456
- }, [nodes, edges, rfInstance, identifier, saveWithHistory]);
670
+ }, [nodes, edges, rfInstance, getIdentifier, identifier, saveWithHistory, stateType]);
671
+ ///////////////// Drag Event Handlers ////////////////
672
+ const onNodeDragStart = useCallback(() => {
673
+ isDraggingRef.current = true;
674
+ }, []);
675
+ const onNodeDragStop = useCallback(() => {
676
+ isDraggingRef.current = false;
677
+ // Trigger saveWithHistory after drag ends to capture final state
678
+ setTimeout(() => {
679
+ saveWithHistory();
680
+ }, 0);
681
+ }, [saveWithHistory]);
457
682
  ///////////////// Delete Key Code ////////////////
458
683
  const onSelectionChange = useCallback(({ nodes, edges }) => {
459
684
  // console.log('Selected nodes:', nodes)
@@ -469,6 +694,6 @@ export default function StudioPane({ api_key, projectsRefetch, identifier, provi
469
694
  window.addEventListener('keydown', handleKeyDown);
470
695
  return () => window.removeEventListener('keydown', handleKeyDown);
471
696
  }, [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 })] }));
697
+ 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, 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
698
  }
474
699
  //# sourceMappingURL=studio_pane.js.map