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.
- package/dist/ai_creator.d.ts +1 -1
- package/dist/ai_creator.js +32 -128
- package/dist/ai_creator.js.map +1 -1
- package/dist/api_calls.d.ts +2 -0
- package/dist/api_calls.js +148 -0
- package/dist/api_calls.js.map +1 -0
- package/dist/app_analytics.d.ts +1 -1
- package/dist/app_analytics.js +75 -69
- package/dist/app_analytics.js.map +1 -1
- package/dist/app_details.d.ts +1 -1
- package/dist/app_details.js +25 -24
- package/dist/app_details.js.map +1 -1
- package/dist/app_layout.d.ts +2 -2
- package/dist/app_layout.js +4 -4
- package/dist/app_layout.js.map +1 -1
- package/dist/app_settings.d.ts +1 -1
- package/dist/app_settings.js +74 -7
- package/dist/app_settings.js.map +1 -1
- package/dist/bottom_left_panel.d.ts +1 -0
- package/dist/bottom_left_panel.js +8 -0
- package/dist/bottom_left_panel.js.map +1 -0
- package/dist/code.d.ts +1 -1
- package/dist/code.js +6 -2
- package/dist/code.js.map +1 -1
- package/dist/components/ftable.d.ts +13 -0
- package/dist/components/ftable.js +65 -0
- package/dist/components/ftable.js.map +1 -0
- package/dist/components/gsheet/index.d.ts +1 -0
- package/dist/components/gsheet/index.js +21 -0
- package/dist/components/gsheet/index.js.map +1 -0
- package/dist/components/mention_input/index.d.ts +18 -1
- package/dist/components/mention_input/index.js +203 -111
- package/dist/components/mention_input/index.js.map +1 -1
- package/dist/components/paginator_table.d.ts +1 -1
- package/dist/components/paginator_table.js +2 -2
- package/dist/components/paginator_table.js.map +1 -1
- package/dist/components/sheet/index.d.ts +1 -1
- package/dist/components/sheet/index.js +4 -2
- package/dist/components/sheet/index.js.map +1 -1
- package/dist/components/ui/alert.d.ts +8 -0
- package/dist/components/ui/alert.js +23 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/badge.d.ts +9 -0
- package/dist/components/ui/badge.js +21 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.js +35 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +8 -0
- package/dist/components/ui/card.js +17 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +19 -0
- package/dist/components/ui/dialog.js +33 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +27 -0
- package/dist/components/ui/dropdown-menu.js +46 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/form.d.ts +24 -0
- package/dist/components/ui/form.js +61 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +16 -0
- package/dist/components/ui/input-group.js +65 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input.d.ts +5 -0
- package/dist/components/ui/input.js +9 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +5 -0
- package/dist/components/ui/label.js +10 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/popover.d.ts +7 -0
- package/dist/components/ui/popover.js +23 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +5 -0
- package/dist/components/ui/scroll-area.js +12 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +13 -0
- package/dist/components/ui/select.js +37 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.js +8 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +13 -0
- package/dist/components/ui/sheet.js +93 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +2 -0
- package/dist/components/ui/skeleton.js +7 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +3 -0
- package/dist/components/ui/sonner.js +42 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +7 -0
- package/dist/components/ui/spinner.js +17 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.js +8 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +10 -0
- package/dist/components/ui/table.js +21 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +7 -0
- package/dist/components/ui/tabs.js +13 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +5 -0
- package/dist/components/ui/textarea.js +9 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +7 -0
- package/dist/components/ui/tooltip.js +11 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/constants.js +2 -2
- package/dist/constants.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.js +2 -2
- package/dist/context.js.map +1 -1
- package/dist/envs.d.ts +2 -2
- package/dist/envs.js +2 -150
- package/dist/envs.js.map +1 -1
- package/dist/functions.d.ts +2 -5
- package/dist/functions.js +2 -210
- package/dist/functions.js.map +1 -1
- package/dist/globals.css +253 -0
- package/dist/home.d.ts +1 -1
- package/dist/home.js +24 -14
- package/dist/home.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/pane-context.d.ts +10 -0
- package/dist/lib/pane-context.js +21 -0
- package/dist/lib/pane-context.js.map +1 -0
- package/dist/lib/toast.d.ts +12 -0
- package/dist/lib/toast.js +36 -0
- package/dist/lib/toast.js.map +1 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/new_project.d.ts +1 -1
- package/dist/new_project.js +60 -41
- package/dist/new_project.js.map +1 -1
- package/dist/node.d.ts +2 -2
- package/dist/node.js +157 -39
- package/dist/node.js.map +1 -1
- package/dist/project_widget.d.ts +2 -1
- package/dist/project_widget.js +6 -2
- package/dist/project_widget.js.map +1 -1
- package/dist/secrets.d.ts +2 -0
- package/dist/secrets.js +129 -0
- package/dist/secrets.js.map +1 -0
- package/dist/simulator.d.ts +1 -1
- package/dist/simulator.js +36 -33
- package/dist/simulator.js.map +1 -1
- package/dist/studio_pane.d.ts +1 -1
- package/dist/studio_pane.js +281 -56
- package/dist/studio_pane.js.map +1 -1
- package/dist/studio_pane_old.d.ts +1 -1
- package/dist/studio_pane_old.js +2 -3
- package/dist/studio_pane_old.js.map +1 -1
- package/dist/studio_pane_old_new.d.ts +1 -1
- package/dist/studio_pane_old_new.js +2 -3
- package/dist/studio_pane_old_new.js.map +1 -1
- package/dist/styles/globals.css +59 -0
- package/dist/styles.css +2 -0
- package/dist/top_center_panel.d.ts +1 -1
- package/dist/top_center_panel.js +4 -3
- package/dist/top_center_panel.js.map +1 -1
- package/dist/top_left_panel.d.ts +1 -1
- package/dist/top_left_panel.js +7 -41
- package/dist/top_left_panel.js.map +1 -1
- package/dist/top_right_panel.d.ts +1 -1
- package/dist/top_right_panel.js +12 -13
- package/dist/top_right_panel.js.map +1 -1
- package/dist/triggers.d.ts +2 -2
- package/dist/triggers.js +57 -37
- package/dist/triggers.js.map +1 -1
- package/dist/zustand/store.d.ts +319 -3
- package/package.json +51 -24
- package/src/globals.css +253 -0
- package/src/styles/globals.css +59 -0
- package/dist/components/combo_box/index.d.ts +0 -2
- package/dist/components/combo_box/index.js +0 -150
- package/dist/components/combo_box/index.js.map +0 -1
- package/dist/components/combo_box/style.css +0 -10
- package/dist/components/date_picker/index.d.ts +0 -1
- package/dist/components/date_picker/index.js +0 -46
- package/dist/components/date_picker/index.js.map +0 -1
- package/dist/components/date_picker/style.css +0 -204
- package/dist/components/gtable.d.ts +0 -15
- package/dist/components/gtable.js +0 -122
- package/dist/components/gtable.js.map +0 -1
- package/src/components/combo_box/style.css +0 -10
- package/src/components/date_picker/style.css +0 -204
package/dist/studio_pane.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
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 =
|
|
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
|
-
|
|
27
|
-
|
|
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_${
|
|
286
|
+
queryKey: [`project_${getIdentifier}`],
|
|
167
287
|
queryFn: async () => {
|
|
168
|
-
const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/projects/${
|
|
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: !!
|
|
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_${
|
|
299
|
+
queryKey: [`project_${getIdentifier}_api_calls`],
|
|
179
300
|
queryFn: async () => {
|
|
180
|
-
const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/
|
|
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: !!
|
|
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_${
|
|
312
|
+
queryKey: [`project_${getIdentifier}_triggers`],
|
|
191
313
|
queryFn: async () => {
|
|
192
|
-
const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/triggers?project=${
|
|
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: !!
|
|
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_${
|
|
325
|
+
queryKey: [`project_${getIdentifier}_secrets`],
|
|
203
326
|
queryFn: async () => {
|
|
204
|
-
const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/
|
|
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: !!
|
|
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_${
|
|
338
|
+
queryKey: [`project_${getIdentifier}_sessions`],
|
|
215
339
|
queryFn: async () => {
|
|
216
|
-
const res = await fetch(`${flow_constants.BACKEND_ENDPOINT}/sessions?project=${
|
|
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: !!
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
//
|
|
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:
|
|
246
|
-
sessions:
|
|
377
|
+
functions: functionsRef.current,
|
|
378
|
+
sessions: sessionsByNodeIdRef.current[props.id] || [],
|
|
379
|
+
secrets: envsRef.current,
|
|
247
380
|
} })),
|
|
248
|
-
|
|
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 === '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|