@runtypelabs/react-flow 1.0.40 → 1.0.41
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/CHANGELOG.md +8 -0
- package/dist/index.js +0 -5
- package/example/CHANGELOG.md +9 -0
- package/example/package.json +1 -1
- package/example/src/App.tsx +0 -58
- package/package.json +2 -2
- package/src/components/RuntypeFlowEditor.tsx +0 -21
- package/src/components/nodes/BaseNode.tsx +4 -20
- package/src/components/nodes/CodeNode.tsx +0 -6
- package/src/components/nodes/ConditionalNode.tsx +0 -9
- package/src/components/nodes/FetchUrlNode.tsx +0 -9
- package/src/components/nodes/PromptNode.tsx +0 -6
- package/src/components/nodes/SendEmailNode.tsx +0 -7
- package/src/hooks/useFlowValidation.ts +0 -36
- package/src/hooks/useRuntypeFlow.ts +0 -42
- package/src/index.ts +0 -4
- package/src/types/index.ts +0 -18
- package/src/utils/adapter.ts +1 -53
- package/src/utils/layout.ts +0 -23
- package/tsup.config.ts +0 -2
|
@@ -12,9 +12,6 @@ import type {
|
|
|
12
12
|
SendEmailStepConfig,
|
|
13
13
|
} from '../types'
|
|
14
14
|
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Validation Rules
|
|
17
|
-
// ============================================================================
|
|
18
15
|
|
|
19
16
|
interface ValidationRule {
|
|
20
17
|
validate: (step: FlowStep, allSteps: FlowStep[]) => ValidationError[]
|
|
@@ -24,9 +21,6 @@ interface WarningRule {
|
|
|
24
21
|
check: (step: FlowStep, allSteps: FlowStep[]) => ValidationWarning[]
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
// ============================================================================
|
|
28
|
-
// Prompt Step Validation
|
|
29
|
-
// ============================================================================
|
|
30
24
|
|
|
31
25
|
const promptValidation: ValidationRule = {
|
|
32
26
|
validate: (step) => {
|
|
@@ -61,9 +55,6 @@ const promptValidation: ValidationRule = {
|
|
|
61
55
|
},
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Fetch URL Step Validation
|
|
66
|
-
// ============================================================================
|
|
67
58
|
|
|
68
59
|
const fetchUrlValidation: ValidationRule = {
|
|
69
60
|
validate: (step) => {
|
|
@@ -77,7 +68,6 @@ const fetchUrlValidation: ValidationRule = {
|
|
|
77
68
|
message: 'URL is required',
|
|
78
69
|
})
|
|
79
70
|
} else {
|
|
80
|
-
// Validate URL format (allow template variables)
|
|
81
71
|
const url = config.http.url
|
|
82
72
|
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.includes('{{')) {
|
|
83
73
|
errors.push({
|
|
@@ -100,9 +90,6 @@ const fetchUrlValidation: ValidationRule = {
|
|
|
100
90
|
},
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
// ============================================================================
|
|
104
|
-
// Transform Data Step Validation
|
|
105
|
-
// ============================================================================
|
|
106
93
|
|
|
107
94
|
const transformDataValidation: ValidationRule = {
|
|
108
95
|
validate: (step) => {
|
|
@@ -129,9 +116,6 @@ const transformDataValidation: ValidationRule = {
|
|
|
129
116
|
},
|
|
130
117
|
}
|
|
131
118
|
|
|
132
|
-
// ============================================================================
|
|
133
|
-
// Conditional Step Validation
|
|
134
|
-
// ============================================================================
|
|
135
119
|
|
|
136
120
|
const conditionalValidation: ValidationRule = {
|
|
137
121
|
validate: (step, allSteps) => {
|
|
@@ -146,7 +130,6 @@ const conditionalValidation: ValidationRule = {
|
|
|
146
130
|
})
|
|
147
131
|
}
|
|
148
132
|
|
|
149
|
-
// Validate nested steps
|
|
150
133
|
if (config.trueSteps && config.trueSteps.length > 0) {
|
|
151
134
|
for (const nestedStep of config.trueSteps) {
|
|
152
135
|
const nestedErrors = validateStep(nestedStep, allSteps)
|
|
@@ -175,9 +158,6 @@ const conditionalValidation: ValidationRule = {
|
|
|
175
158
|
},
|
|
176
159
|
}
|
|
177
160
|
|
|
178
|
-
// ============================================================================
|
|
179
|
-
// Send Email Step Validation
|
|
180
|
-
// ============================================================================
|
|
181
161
|
|
|
182
162
|
const sendEmailValidation: ValidationRule = {
|
|
183
163
|
validate: (step) => {
|
|
@@ -191,7 +171,6 @@ const sendEmailValidation: ValidationRule = {
|
|
|
191
171
|
message: 'Recipient (To) is required',
|
|
192
172
|
})
|
|
193
173
|
} else {
|
|
194
|
-
// Validate email format (allow template variables)
|
|
195
174
|
const to = config.to
|
|
196
175
|
if (!to.includes('@') && !to.includes('{{')) {
|
|
197
176
|
errors.push({
|
|
@@ -230,9 +209,6 @@ const sendEmailValidation: ValidationRule = {
|
|
|
230
209
|
},
|
|
231
210
|
}
|
|
232
211
|
|
|
233
|
-
// ============================================================================
|
|
234
|
-
// Warning Rules
|
|
235
|
-
// ============================================================================
|
|
236
212
|
|
|
237
213
|
const outputVariableWarning: WarningRule = {
|
|
238
214
|
check: (step, allSteps) => {
|
|
@@ -240,7 +216,6 @@ const outputVariableWarning: WarningRule = {
|
|
|
240
216
|
const config = step.config as { outputVariable?: string }
|
|
241
217
|
|
|
242
218
|
if (config.outputVariable) {
|
|
243
|
-
// Check for duplicate output variables
|
|
244
219
|
const duplicates = allSteps.filter(
|
|
245
220
|
(s) =>
|
|
246
221
|
s.id !== step.id &&
|
|
@@ -288,9 +263,6 @@ const emptyBranchWarning: WarningRule = {
|
|
|
288
263
|
},
|
|
289
264
|
}
|
|
290
265
|
|
|
291
|
-
// ============================================================================
|
|
292
|
-
// Validation Logic
|
|
293
|
-
// ============================================================================
|
|
294
266
|
|
|
295
267
|
const validationRules: Record<string, ValidationRule> = {
|
|
296
268
|
prompt: promptValidation,
|
|
@@ -305,7 +277,6 @@ const warningRules: WarningRule[] = [outputVariableWarning, emptyBranchWarning]
|
|
|
305
277
|
function validateStep(step: FlowStep, allSteps: FlowStep[]): ValidationError[] {
|
|
306
278
|
const errors: ValidationError[] = []
|
|
307
279
|
|
|
308
|
-
// Common validation
|
|
309
280
|
if (!step.name?.trim()) {
|
|
310
281
|
errors.push({
|
|
311
282
|
stepId: step.id,
|
|
@@ -314,7 +285,6 @@ function validateStep(step: FlowStep, allSteps: FlowStep[]): ValidationError[] {
|
|
|
314
285
|
})
|
|
315
286
|
}
|
|
316
287
|
|
|
317
|
-
// Type-specific validation
|
|
318
288
|
const rule = validationRules[step.type]
|
|
319
289
|
if (rule) {
|
|
320
290
|
errors.push(...rule.validate(step, allSteps))
|
|
@@ -333,9 +303,6 @@ function checkWarnings(step: FlowStep, allSteps: FlowStep[]): ValidationWarning[
|
|
|
333
303
|
return warnings
|
|
334
304
|
}
|
|
335
305
|
|
|
336
|
-
// ============================================================================
|
|
337
|
-
// Hook
|
|
338
|
-
// ============================================================================
|
|
339
306
|
|
|
340
307
|
export interface UseFlowValidationOptions {
|
|
341
308
|
/** Steps to validate */
|
|
@@ -360,14 +327,12 @@ export interface UseFlowValidationReturn {
|
|
|
360
327
|
export function useFlowValidation(options: UseFlowValidationOptions): UseFlowValidationReturn {
|
|
361
328
|
const { steps: providedSteps, nodes } = options
|
|
362
329
|
|
|
363
|
-
// Extract steps from nodes if not provided directly
|
|
364
330
|
const steps = useMemo(() => {
|
|
365
331
|
if (providedSteps) return providedSteps
|
|
366
332
|
if (nodes) return nodes.map((n) => n.data.step)
|
|
367
333
|
return []
|
|
368
334
|
}, [providedSteps, nodes])
|
|
369
335
|
|
|
370
|
-
// Perform validation
|
|
371
336
|
const result = useMemo((): FlowValidationResult => {
|
|
372
337
|
const errors: ValidationError[] = []
|
|
373
338
|
const warnings: ValidationWarning[] = []
|
|
@@ -384,7 +349,6 @@ export function useFlowValidation(options: UseFlowValidationOptions): UseFlowVal
|
|
|
384
349
|
}
|
|
385
350
|
}, [steps])
|
|
386
351
|
|
|
387
|
-
// Helper functions
|
|
388
352
|
const validateStepFn = useCallback((step: FlowStep) => validateStep(step, steps), [steps])
|
|
389
353
|
|
|
390
354
|
const isStepValid = useCallback(
|
|
@@ -17,9 +17,6 @@ import {
|
|
|
17
17
|
import { autoLayout } from '../utils/layout'
|
|
18
18
|
import type { FlowStepType } from '../flow-step-types'
|
|
19
19
|
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Hook Options
|
|
22
|
-
// ============================================================================
|
|
23
20
|
|
|
24
21
|
export interface UseRuntypeFlowOptions {
|
|
25
22
|
/** Runtype API client instance */
|
|
@@ -38,9 +35,6 @@ export interface UseRuntypeFlowOptions {
|
|
|
38
35
|
autoLayoutOnLoad?: boolean
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
// ============================================================================
|
|
42
|
-
// Main Hook
|
|
43
|
-
// ============================================================================
|
|
44
38
|
|
|
45
39
|
export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowReturn {
|
|
46
40
|
const {
|
|
@@ -53,7 +47,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
53
47
|
autoLayoutOnLoad = true,
|
|
54
48
|
} = options
|
|
55
49
|
|
|
56
|
-
// State
|
|
57
50
|
const [flowId, setFlowId] = useState<string | null>(initialFlowId || null)
|
|
58
51
|
const [flowName, setFlowName] = useState(initialName)
|
|
59
52
|
const [flowDescription, setFlowDescription] = useState(initialDescription)
|
|
@@ -62,16 +55,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
62
55
|
const [error, setError] = useState<Error | null>(null)
|
|
63
56
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false)
|
|
64
57
|
|
|
65
|
-
// React Flow state
|
|
66
58
|
const [nodes, setNodes, onNodesChange] = useNodesState<RuntypeNode>([])
|
|
67
59
|
const [edges, setEdges, onEdgesChange] = useEdgesState<RuntypeEdge>([])
|
|
68
60
|
|
|
69
|
-
// Track last saved state for change detection
|
|
70
61
|
const lastSavedState = useRef<string>('')
|
|
71
62
|
|
|
72
|
-
// ============================================================================
|
|
73
|
-
// Handlers
|
|
74
|
-
// ============================================================================
|
|
75
63
|
|
|
76
64
|
const handleStepChange = useCallback(
|
|
77
65
|
(stepId: string, updates: Partial<FlowStep>) => {
|
|
@@ -117,9 +105,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
117
105
|
[setEdges]
|
|
118
106
|
)
|
|
119
107
|
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// API Operations
|
|
122
|
-
// ============================================================================
|
|
123
108
|
|
|
124
109
|
/**
|
|
125
110
|
* Load a flow by ID
|
|
@@ -130,10 +115,8 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
130
115
|
setError(null)
|
|
131
116
|
|
|
132
117
|
try {
|
|
133
|
-
// Get flow with embedded steps
|
|
134
118
|
const flow = await client.flows.get(id)
|
|
135
119
|
|
|
136
|
-
// Use flow steps from the flow response (consolidated endpoint)
|
|
137
120
|
const steps: FlowStep[] = Array.isArray(flow.flowSteps)
|
|
138
121
|
? flow.flowSteps.map((s) => ({
|
|
139
122
|
id: s.id,
|
|
@@ -145,13 +128,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
145
128
|
}))
|
|
146
129
|
: []
|
|
147
130
|
|
|
148
|
-
// Convert to React Flow nodes
|
|
149
131
|
let newNodes = flowStepsToNodes(steps, {
|
|
150
132
|
onChange: handleStepChange,
|
|
151
133
|
onDelete: handleStepDelete,
|
|
152
134
|
})
|
|
153
135
|
|
|
154
|
-
// Auto-layout if enabled
|
|
155
136
|
if (autoLayoutOnLoad) {
|
|
156
137
|
const newEdges = createEdgesFromNodes(newNodes)
|
|
157
138
|
newNodes = autoLayout(newNodes, newEdges)
|
|
@@ -161,14 +142,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
161
142
|
|
|
162
143
|
setFlowId(id)
|
|
163
144
|
setFlowName(flow.name || '')
|
|
164
|
-
// The flow GET response carries no `description` (the handler never
|
|
165
|
-
// selects it), so there is nothing to load into the editor here.
|
|
166
145
|
setFlowDescription('')
|
|
167
146
|
setNodes(newNodes)
|
|
168
147
|
setEdges(newEdges)
|
|
169
148
|
setHasUnsavedChanges(false)
|
|
170
149
|
|
|
171
|
-
// Store last saved state
|
|
172
150
|
lastSavedState.current = JSON.stringify(nodesToFlowSteps(newNodes))
|
|
173
151
|
} catch (err) {
|
|
174
152
|
setError(err instanceof Error ? err : new Error('Failed to load flow'))
|
|
@@ -194,11 +172,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
194
172
|
try {
|
|
195
173
|
const steps = nodesToFlowSteps(nodes)
|
|
196
174
|
|
|
197
|
-
// Update flow with metadata and steps in one atomic operation.
|
|
198
|
-
// `description` is intentionally omitted: the flow update endpoint does
|
|
199
|
-
// not persist it (the GET response carries no description either), so
|
|
200
|
-
// sending the local value here would be a no-op at best and could
|
|
201
|
-
// overwrite a description with an empty string if the schema ever changes.
|
|
202
175
|
await client.flows.update(flowId, {
|
|
203
176
|
name: flowName,
|
|
204
177
|
flowSteps: steps.map((step) => ({
|
|
@@ -308,7 +281,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
308
281
|
|
|
309
282
|
setNodes((nds) => [...nds, newNode])
|
|
310
283
|
|
|
311
|
-
// Add edge from previous node if exists
|
|
312
284
|
if (nodes.length > 0) {
|
|
313
285
|
const lastNode = nodes[nodes.length - 1]
|
|
314
286
|
setEdges((eds) => [
|
|
@@ -327,16 +299,11 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
327
299
|
[nodes, setNodes, setEdges, handleStepChange, handleStepDelete]
|
|
328
300
|
)
|
|
329
301
|
|
|
330
|
-
// ============================================================================
|
|
331
|
-
// Effects
|
|
332
|
-
// ============================================================================
|
|
333
302
|
|
|
334
|
-
// Load initial flow if flowId is provided
|
|
335
303
|
useEffect(() => {
|
|
336
304
|
if (initialFlowId) {
|
|
337
305
|
loadFlow(initialFlowId)
|
|
338
306
|
} else if (initialSteps.length > 0) {
|
|
339
|
-
// Initialize with provided steps
|
|
340
307
|
let newNodes = flowStepsToNodes(initialSteps, {
|
|
341
308
|
onChange: handleStepChange,
|
|
342
309
|
onDelete: handleStepDelete,
|
|
@@ -354,12 +321,10 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
354
321
|
}
|
|
355
322
|
}, []) // Only run on mount
|
|
356
323
|
|
|
357
|
-
// Notify onChange when nodes/edges change
|
|
358
324
|
useEffect(() => {
|
|
359
325
|
onChange?.(nodes, edges)
|
|
360
326
|
}, [nodes, edges, onChange])
|
|
361
327
|
|
|
362
|
-
// Detect unsaved changes
|
|
363
328
|
useEffect(() => {
|
|
364
329
|
const currentState = JSON.stringify(nodesToFlowSteps(nodes))
|
|
365
330
|
if (lastSavedState.current && currentState !== lastSavedState.current) {
|
|
@@ -367,26 +332,20 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
367
332
|
}
|
|
368
333
|
}, [nodes])
|
|
369
334
|
|
|
370
|
-
// ============================================================================
|
|
371
|
-
// Return
|
|
372
|
-
// ============================================================================
|
|
373
335
|
|
|
374
336
|
return {
|
|
375
|
-
// React Flow state
|
|
376
337
|
nodes,
|
|
377
338
|
edges,
|
|
378
339
|
onNodesChange: onNodesChange as unknown as (changes: unknown) => void,
|
|
379
340
|
onEdgesChange: onEdgesChange as unknown as (changes: unknown) => void,
|
|
380
341
|
onConnect: handleConnect as unknown as (connection: unknown) => void,
|
|
381
342
|
|
|
382
|
-
// Flow metadata
|
|
383
343
|
flowName,
|
|
384
344
|
flowDescription,
|
|
385
345
|
flowId,
|
|
386
346
|
setFlowName,
|
|
387
347
|
setFlowDescription,
|
|
388
348
|
|
|
389
|
-
// API operations
|
|
390
349
|
loadFlow,
|
|
391
350
|
saveFlow,
|
|
392
351
|
createFlow,
|
|
@@ -394,7 +353,6 @@ export function useRuntypeFlow(options: UseRuntypeFlowOptions): UseRuntypeFlowRe
|
|
|
394
353
|
updateStep,
|
|
395
354
|
addStep,
|
|
396
355
|
|
|
397
|
-
// Status
|
|
398
356
|
isLoading,
|
|
399
357
|
isSaving,
|
|
400
358
|
error,
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
// Types
|
|
2
1
|
export * from './types'
|
|
3
2
|
|
|
4
|
-
// Components
|
|
5
3
|
export { RuntypeFlowEditor } from './components/RuntypeFlowEditor'
|
|
6
4
|
export { BaseNode } from './components/nodes/BaseNode'
|
|
7
5
|
export { PromptNode } from './components/nodes/PromptNode'
|
|
@@ -10,11 +8,9 @@ export { CodeNode } from './components/nodes/CodeNode'
|
|
|
10
8
|
export { ConditionalNode } from './components/nodes/ConditionalNode'
|
|
11
9
|
export { SendEmailNode } from './components/nodes/SendEmailNode'
|
|
12
10
|
|
|
13
|
-
// Hooks
|
|
14
11
|
export { useRuntypeFlow } from './hooks/useRuntypeFlow'
|
|
15
12
|
export { useFlowValidation } from './hooks/useFlowValidation'
|
|
16
13
|
|
|
17
|
-
// Utilities
|
|
18
14
|
export {
|
|
19
15
|
flowStepsToNodes,
|
|
20
16
|
nodesToFlowSteps,
|
package/src/types/index.ts
CHANGED
|
@@ -2,9 +2,6 @@ import type { Node, Edge, NodeProps } from '@xyflow/react'
|
|
|
2
2
|
import type { FlowStepType, PromptStepMode } from '../flow-step-types'
|
|
3
3
|
import type { RuntypeClient } from '@runtypelabs/sdk'
|
|
4
4
|
|
|
5
|
-
// ============================================================================
|
|
6
|
-
// Flow Step Types (matching Runtype API)
|
|
7
|
-
// ============================================================================
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* Core FlowStep interface matching the Runtype API structure
|
|
@@ -124,9 +121,6 @@ export interface GenericStepConfig {
|
|
|
124
121
|
outputVariable?: string
|
|
125
122
|
}
|
|
126
123
|
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// React Flow Node Types
|
|
129
|
-
// ============================================================================
|
|
130
124
|
|
|
131
125
|
/**
|
|
132
126
|
* Base data structure for all Runtype nodes
|
|
@@ -154,9 +148,6 @@ export type RuntypeEdge = Edge<{ stepOrder?: number }>
|
|
|
154
148
|
*/
|
|
155
149
|
export type RuntypeNodeProps = NodeProps
|
|
156
150
|
|
|
157
|
-
// ============================================================================
|
|
158
|
-
// Node Type Registry
|
|
159
|
-
// ============================================================================
|
|
160
151
|
|
|
161
152
|
/**
|
|
162
153
|
* Supported node types that have custom components
|
|
@@ -178,9 +169,6 @@ export function isSupportedNodeType(type: FlowStepType): type is SupportedNodeTy
|
|
|
178
169
|
return SUPPORTED_NODE_TYPES.includes(type as SupportedNodeType)
|
|
179
170
|
}
|
|
180
171
|
|
|
181
|
-
// ============================================================================
|
|
182
|
-
// Editor Configuration
|
|
183
|
-
// ============================================================================
|
|
184
172
|
|
|
185
173
|
/**
|
|
186
174
|
* Configuration for the RuntypeFlowEditor component
|
|
@@ -233,9 +221,6 @@ export interface SavedFlow {
|
|
|
233
221
|
updatedAt?: string
|
|
234
222
|
}
|
|
235
223
|
|
|
236
|
-
// ============================================================================
|
|
237
|
-
// Hook Types
|
|
238
|
-
// ============================================================================
|
|
239
224
|
|
|
240
225
|
/**
|
|
241
226
|
* Return type for useRuntypeFlow hook
|
|
@@ -302,9 +287,6 @@ export interface ValidationWarning {
|
|
|
302
287
|
message: string
|
|
303
288
|
}
|
|
304
289
|
|
|
305
|
-
// ============================================================================
|
|
306
|
-
// Event Types
|
|
307
|
-
// ============================================================================
|
|
308
290
|
|
|
309
291
|
/**
|
|
310
292
|
* Event emitted when a step is updated
|
package/src/utils/adapter.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import type { FlowStep, RuntypeNode, RuntypeEdge, RuntypeNodeData } from '../types'
|
|
2
2
|
import type { FlowStepType } from '../flow-step-types'
|
|
3
3
|
|
|
4
|
-
// ============================================================================
|
|
5
|
-
// Constants
|
|
6
|
-
// ============================================================================
|
|
7
4
|
|
|
8
5
|
const NODE_WIDTH = 280
|
|
9
6
|
const NODE_HEIGHT = 200 // Increased for better card visibility
|
|
10
7
|
const NODE_SPACING_X = 350 // Horizontal space between main steps
|
|
11
8
|
const NODE_SPACING_Y = 80 // Vertical space between branch steps
|
|
12
|
-
const BRANCH_OFFSET_X = 350
|
|
9
|
+
const BRANCH_OFFSET_X = 350
|
|
13
10
|
const BRANCH_OFFSET_Y = -80 // Vertical offset for true branch (above center)
|
|
14
11
|
const FALSE_BRANCH_GAP = 100 // Gap between true and false branches
|
|
15
12
|
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// Flow Steps to React Flow Nodes
|
|
18
|
-
// ============================================================================
|
|
19
13
|
|
|
20
14
|
interface FlowStepsToNodesOptions {
|
|
21
15
|
onChange?: (stepId: string, updates: Partial<FlowStep>) => void
|
|
@@ -43,7 +37,6 @@ export function flowStepsToNodes(
|
|
|
43
37
|
parentId,
|
|
44
38
|
} = options ?? {}
|
|
45
39
|
|
|
46
|
-
// Sort steps by order
|
|
47
40
|
const sortedSteps = [...steps].sort((a, b) => a.order - b.order)
|
|
48
41
|
|
|
49
42
|
const nodes: RuntypeNode[] = []
|
|
@@ -75,23 +68,18 @@ export function flowStepsToNodes(
|
|
|
75
68
|
|
|
76
69
|
nodes.push(node)
|
|
77
70
|
|
|
78
|
-
// DEBUG: Log node positions
|
|
79
71
|
console.log(`[flowStepsToNodes] Placed "${step.name}" (${step.type}) at x=${currentX}`)
|
|
80
72
|
|
|
81
|
-
// Handle conditional branches - position to the right with true above, false below
|
|
82
73
|
if (step.type === 'conditional' && step.config) {
|
|
83
74
|
const config = step.config as { trueSteps?: FlowStep[]; falseSteps?: FlowStep[] }
|
|
84
75
|
const branchX = currentX + BRANCH_OFFSET_X
|
|
85
76
|
|
|
86
|
-
// Calculate branch lengths (horizontal extent)
|
|
87
77
|
const trueBranchLength = config.trueSteps?.length || 0
|
|
88
78
|
const falseBranchLength = config.falseSteps?.length || 0
|
|
89
79
|
const maxBranchLength = Math.max(trueBranchLength, falseBranchLength)
|
|
90
80
|
|
|
91
|
-
// Calculate true branch height (vertical extent for stacking)
|
|
92
81
|
const trueBranchHeight = trueBranchLength * (NODE_HEIGHT + NODE_SPACING_Y)
|
|
93
82
|
|
|
94
|
-
// True branch nodes (positioned to the right, above center)
|
|
95
83
|
if (config.trueSteps && config.trueSteps.length > 0) {
|
|
96
84
|
const trueBranchY = startPosition.y + BRANCH_OFFSET_Y
|
|
97
85
|
const trueBranchNodes = flowStepsToNodes(config.trueSteps, {
|
|
@@ -109,9 +97,7 @@ export function flowStepsToNodes(
|
|
|
109
97
|
maxBranchY = Math.max(maxBranchY, trueBranchY + trueBranchHeight)
|
|
110
98
|
}
|
|
111
99
|
|
|
112
|
-
// False branch nodes (positioned to the right, below center)
|
|
113
100
|
if (config.falseSteps && config.falseSteps.length > 0) {
|
|
114
|
-
// Position false branch below true branch (or at center + gap if no true branch)
|
|
115
101
|
const falseBranchY =
|
|
116
102
|
trueBranchLength > 0
|
|
117
103
|
? startPosition.y + BRANCH_OFFSET_Y + trueBranchHeight + FALSE_BRANCH_GAP
|
|
@@ -133,10 +119,6 @@ export function flowStepsToNodes(
|
|
|
133
119
|
maxBranchY = Math.max(maxBranchY, falseBranchY + falseBranchHeight)
|
|
134
120
|
}
|
|
135
121
|
|
|
136
|
-
// Skip past the conditional AND all its branch steps
|
|
137
|
-
// Branches start at branchX, so we need to account for:
|
|
138
|
-
// - The offset from conditional to first branch step (BRANCH_OFFSET_X)
|
|
139
|
-
// - All the branch steps (maxBranchLength * (NODE_WIDTH + NODE_SPACING_X))
|
|
140
122
|
if (maxBranchLength > 0) {
|
|
141
123
|
const advance = BRANCH_OFFSET_X + maxBranchLength * (NODE_WIDTH + NODE_SPACING_X)
|
|
142
124
|
console.log(
|
|
@@ -148,7 +130,6 @@ export function flowStepsToNodes(
|
|
|
148
130
|
currentX += NODE_WIDTH + NODE_SPACING_X
|
|
149
131
|
}
|
|
150
132
|
} else {
|
|
151
|
-
// Move to the next X position for horizontal layout (non-conditional steps)
|
|
152
133
|
currentX += NODE_WIDTH + NODE_SPACING_X
|
|
153
134
|
}
|
|
154
135
|
}
|
|
@@ -156,26 +137,20 @@ export function flowStepsToNodes(
|
|
|
156
137
|
return nodes
|
|
157
138
|
}
|
|
158
139
|
|
|
159
|
-
// ============================================================================
|
|
160
|
-
// React Flow Nodes to Flow Steps
|
|
161
|
-
// ============================================================================
|
|
162
140
|
|
|
163
141
|
/**
|
|
164
142
|
* Convert React Flow Node array back to Runtype FlowStep array
|
|
165
143
|
*/
|
|
166
144
|
export function nodesToFlowSteps(nodes: RuntypeNode[]): FlowStep[] {
|
|
167
|
-
// Filter out branch nodes (handled within conditional steps)
|
|
168
145
|
const topLevelNodes = nodes.filter(
|
|
169
146
|
(n) => !n.parentId && !n.id.includes('-true-') && !n.id.includes('-false-')
|
|
170
147
|
)
|
|
171
148
|
|
|
172
|
-
// Sort by X position to determine order (horizontal layout)
|
|
173
149
|
const sortedNodes = [...topLevelNodes].sort((a, b) => a.position.x - b.position.x)
|
|
174
150
|
|
|
175
151
|
return sortedNodes.map((node, index) => {
|
|
176
152
|
const step = node.data.step
|
|
177
153
|
|
|
178
|
-
// Handle conditional steps - extract nested steps
|
|
179
154
|
if (step.type === 'conditional') {
|
|
180
155
|
const trueSteps = extractBranchSteps(nodes, node.id, 'true')
|
|
181
156
|
const falseSteps = extractBranchSteps(nodes, node.id, 'false')
|
|
@@ -206,11 +181,9 @@ function extractBranchSteps(
|
|
|
206
181
|
parentId: string,
|
|
207
182
|
branch: 'true' | 'false'
|
|
208
183
|
): FlowStep[] {
|
|
209
|
-
// Match nodes with the branch prefix pattern
|
|
210
184
|
const branchPrefix = `${parentId}-${branch}-`
|
|
211
185
|
const branchNodes = nodes.filter((n) => n.id.startsWith(branchPrefix))
|
|
212
186
|
|
|
213
|
-
// Sort by X position (horizontal layout within branches)
|
|
214
187
|
const sortedBranchNodes = [...branchNodes].sort((a, b) => a.position.x - b.position.x)
|
|
215
188
|
|
|
216
189
|
return sortedBranchNodes.map((node, index) => ({
|
|
@@ -219,9 +192,6 @@ function extractBranchSteps(
|
|
|
219
192
|
}))
|
|
220
193
|
}
|
|
221
194
|
|
|
222
|
-
// ============================================================================
|
|
223
|
-
// Create Edges from Nodes
|
|
224
|
-
// ============================================================================
|
|
225
195
|
|
|
226
196
|
/**
|
|
227
197
|
* Create edges connecting nodes in sequence
|
|
@@ -229,22 +199,17 @@ function extractBranchSteps(
|
|
|
229
199
|
export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
230
200
|
const edges: RuntypeEdge[] = []
|
|
231
201
|
|
|
232
|
-
// Helper to check if a node is a branch node
|
|
233
202
|
const isBranchNode = (n: RuntypeNode) =>
|
|
234
203
|
n.parentId || n.id.includes('-true-') || n.id.includes('-false-')
|
|
235
204
|
|
|
236
|
-
// Get top-level nodes sorted by X position (horizontal layout)
|
|
237
205
|
const topLevelNodes = nodes
|
|
238
206
|
.filter((n) => !isBranchNode(n))
|
|
239
207
|
.sort((a, b) => a.position.x - b.position.x)
|
|
240
208
|
|
|
241
|
-
// Create sequential edges for top-level nodes
|
|
242
|
-
// Skip conditionals - their branches connect to the next step instead
|
|
243
209
|
for (let i = 0; i < topLevelNodes.length - 1; i++) {
|
|
244
210
|
const sourceNode = topLevelNodes[i]
|
|
245
211
|
const targetNode = topLevelNodes[i + 1]
|
|
246
212
|
|
|
247
|
-
// Skip edge from conditional - branches will connect to next step
|
|
248
213
|
if (sourceNode.data.step.type === 'conditional') {
|
|
249
214
|
continue
|
|
250
215
|
}
|
|
@@ -260,7 +225,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
260
225
|
})
|
|
261
226
|
}
|
|
262
227
|
|
|
263
|
-
// Create edges for conditional branches
|
|
264
228
|
const conditionalNodes = nodes.filter(
|
|
265
229
|
(n) => n.data.step.type === 'conditional' && !isBranchNode(n)
|
|
266
230
|
)
|
|
@@ -271,19 +235,14 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
271
235
|
const nextMainStep =
|
|
272
236
|
conditionalIndex < topLevelNodes.length - 1 ? topLevelNodes[conditionalIndex + 1] : null
|
|
273
237
|
|
|
274
|
-
// Find true branch nodes (contain -true- after the conditional ID)
|
|
275
|
-
// Sort by X position since branches flow horizontally
|
|
276
238
|
const trueBranchNodes = nodes
|
|
277
239
|
.filter((n) => n.id.startsWith(`${conditionalId}-true-`))
|
|
278
240
|
.sort((a, b) => a.position.x - b.position.x)
|
|
279
241
|
|
|
280
|
-
// Find false branch nodes (contain -false- after the conditional ID)
|
|
281
|
-
// Sort by X position since branches flow horizontally
|
|
282
242
|
const falseBranchNodes = nodes
|
|
283
243
|
.filter((n) => n.id.startsWith(`${conditionalId}-false-`))
|
|
284
244
|
.sort((a, b) => a.position.x - b.position.x)
|
|
285
245
|
|
|
286
|
-
// Connect conditional to first true branch node (branch goes right)
|
|
287
246
|
if (trueBranchNodes.length > 0) {
|
|
288
247
|
edges.push({
|
|
289
248
|
id: `edge-${conditionalId}-to-true-branch`,
|
|
@@ -300,7 +259,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
300
259
|
style: { stroke: '#22c55e', strokeWidth: 2 },
|
|
301
260
|
})
|
|
302
261
|
|
|
303
|
-
// Connect true branch nodes sequentially (horizontal connections)
|
|
304
262
|
for (let i = 0; i < trueBranchNodes.length - 1; i++) {
|
|
305
263
|
edges.push({
|
|
306
264
|
id: `edge-true-${trueBranchNodes[i].id}-${trueBranchNodes[i + 1].id}`,
|
|
@@ -313,7 +271,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
313
271
|
})
|
|
314
272
|
}
|
|
315
273
|
|
|
316
|
-
// Connect last true branch node to next main step (convergence)
|
|
317
274
|
if (nextMainStep) {
|
|
318
275
|
const lastTrueNode = trueBranchNodes[trueBranchNodes.length - 1]
|
|
319
276
|
edges.push({
|
|
@@ -327,7 +284,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
327
284
|
})
|
|
328
285
|
}
|
|
329
286
|
} else if (nextMainStep) {
|
|
330
|
-
// No true branch steps - connect conditional directly to next step via true handle
|
|
331
287
|
edges.push({
|
|
332
288
|
id: `edge-${conditionalId}-true-to-${nextMainStep.id}`,
|
|
333
289
|
source: conditionalId,
|
|
@@ -344,7 +300,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
344
300
|
})
|
|
345
301
|
}
|
|
346
302
|
|
|
347
|
-
// Connect conditional to first false branch node (branch goes right, below true)
|
|
348
303
|
if (falseBranchNodes.length > 0) {
|
|
349
304
|
edges.push({
|
|
350
305
|
id: `edge-${conditionalId}-to-false-branch`,
|
|
@@ -361,7 +316,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
361
316
|
style: { stroke: '#ef4444', strokeWidth: 2 },
|
|
362
317
|
})
|
|
363
318
|
|
|
364
|
-
// Connect false branch nodes sequentially (horizontal connections)
|
|
365
319
|
for (let i = 0; i < falseBranchNodes.length - 1; i++) {
|
|
366
320
|
edges.push({
|
|
367
321
|
id: `edge-false-${falseBranchNodes[i].id}-${falseBranchNodes[i + 1].id}`,
|
|
@@ -374,7 +328,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
374
328
|
})
|
|
375
329
|
}
|
|
376
330
|
|
|
377
|
-
// Connect last false branch node to next main step (convergence)
|
|
378
331
|
if (nextMainStep) {
|
|
379
332
|
const lastFalseNode = falseBranchNodes[falseBranchNodes.length - 1]
|
|
380
333
|
edges.push({
|
|
@@ -388,7 +341,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
388
341
|
})
|
|
389
342
|
}
|
|
390
343
|
} else if (nextMainStep) {
|
|
391
|
-
// No false branch steps - connect conditional directly to next step via false handle
|
|
392
344
|
edges.push({
|
|
393
345
|
id: `edge-${conditionalId}-false-to-${nextMainStep.id}`,
|
|
394
346
|
source: conditionalId,
|
|
@@ -405,7 +357,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
405
357
|
})
|
|
406
358
|
}
|
|
407
359
|
|
|
408
|
-
// Handle case where conditional has no branches - connect directly to next step
|
|
409
360
|
if (trueBranchNodes.length === 0 && falseBranchNodes.length === 0 && nextMainStep) {
|
|
410
361
|
edges.push({
|
|
411
362
|
id: `edge-${conditionalId}-to-${nextMainStep.id}`,
|
|
@@ -421,9 +372,6 @@ export function createEdgesFromNodes(nodes: RuntypeNode[]): RuntypeEdge[] {
|
|
|
421
372
|
return edges
|
|
422
373
|
}
|
|
423
374
|
|
|
424
|
-
// ============================================================================
|
|
425
|
-
// Utility Functions
|
|
426
|
-
// ============================================================================
|
|
427
375
|
|
|
428
376
|
/**
|
|
429
377
|
* Get default name for a step type
|