@dilipod/ui 0.4.14 → 0.4.16
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/components/workflow-flow.d.ts +29 -3
- package/dist/components/workflow-flow.d.ts.map +1 -1
- package/dist/components/workflow-viewer.d.ts +14 -0
- package/dist/components/workflow-viewer.d.ts.map +1 -1
- package/dist/index.js +382 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +383 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/workflow-flow.tsx +163 -5
- package/src/components/workflow-viewer.tsx +381 -78
package/package.json
CHANGED
|
@@ -31,13 +31,38 @@ export interface N8nWorkflow {
|
|
|
31
31
|
connections: Record<string, Record<string, Array<Array<{ node: string; type: string; index: number }>>>>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export interface SimBlock {
|
|
35
|
+
id: string
|
|
36
|
+
type: string
|
|
37
|
+
name: string
|
|
38
|
+
position?: { x: number; y: number }
|
|
39
|
+
subBlocks?: Record<string, unknown>
|
|
40
|
+
outputs?: Record<string, unknown>
|
|
41
|
+
enabled?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SimWorkflow {
|
|
45
|
+
blocks?: Record<string, SimBlock>
|
|
46
|
+
edges?: Array<{
|
|
47
|
+
id?: string
|
|
48
|
+
source?: string
|
|
49
|
+
target?: string
|
|
50
|
+
sourceHandle?: string
|
|
51
|
+
targetHandle?: string
|
|
52
|
+
}>
|
|
53
|
+
loops?: Record<string, unknown>
|
|
54
|
+
parallels?: Record<string, unknown>
|
|
55
|
+
}
|
|
56
|
+
|
|
34
57
|
export interface WorkflowFlowProps {
|
|
35
|
-
/** The
|
|
36
|
-
workflow: N8nWorkflow
|
|
58
|
+
/** The workflow to visualize (n8n or Sim format) */
|
|
59
|
+
workflow: N8nWorkflow | SimWorkflow
|
|
37
60
|
/** Height of the flow diagram container */
|
|
38
61
|
height?: number
|
|
39
62
|
/** Additional CSS class name */
|
|
40
63
|
className?: string
|
|
64
|
+
/** Platform type */
|
|
65
|
+
platform?: 'n8n' | 'sim'
|
|
41
66
|
}
|
|
42
67
|
|
|
43
68
|
// ============================================
|
|
@@ -46,6 +71,7 @@ export interface WorkflowFlowProps {
|
|
|
46
71
|
|
|
47
72
|
function getNodeTypeLabel(type: string): string {
|
|
48
73
|
const labels: Record<string, string> = {
|
|
74
|
+
// n8n types
|
|
49
75
|
'n8n-nodes-base.webhook': 'Webhook',
|
|
50
76
|
'n8n-nodes-base.scheduleTrigger': 'Schedule',
|
|
51
77
|
'n8n-nodes-base.if': 'Condition',
|
|
@@ -56,6 +82,21 @@ function getNodeTypeLabel(type: string): string {
|
|
|
56
82
|
'@n8n/n8n-nodes-langchain.agent': 'AI Agent',
|
|
57
83
|
'@n8n/n8n-nodes-langchain.lmChatOpenAi': 'OpenAI',
|
|
58
84
|
'@n8n/n8n-nodes-langchain.lmChatAnthropic': 'Anthropic',
|
|
85
|
+
// Sim Studio types
|
|
86
|
+
'starter': 'Webhook',
|
|
87
|
+
'webhook': 'Webhook',
|
|
88
|
+
'agent': 'AI Agent',
|
|
89
|
+
'llm': 'LLM',
|
|
90
|
+
'openai': 'OpenAI',
|
|
91
|
+
'anthropic': 'Anthropic',
|
|
92
|
+
'api': 'API Request',
|
|
93
|
+
'http_request': 'HTTP Request',
|
|
94
|
+
'condition': 'Condition',
|
|
95
|
+
'code': 'Code',
|
|
96
|
+
'response': 'Response',
|
|
97
|
+
'function': 'Function',
|
|
98
|
+
'evaluator': 'Evaluator',
|
|
99
|
+
'router': 'Router',
|
|
59
100
|
}
|
|
60
101
|
return labels[type] || type.split('.').pop()?.replace(/([A-Z])/g, ' $1').trim() || type
|
|
61
102
|
}
|
|
@@ -85,10 +126,127 @@ const nodeTypes = { custom: CustomNode }
|
|
|
85
126
|
// Main Component
|
|
86
127
|
// ============================================
|
|
87
128
|
|
|
88
|
-
export function WorkflowFlow({ workflow, height = 350, className = '' }: WorkflowFlowProps) {
|
|
129
|
+
export function WorkflowFlow({ workflow, height = 350, className = '', platform = 'n8n' }: WorkflowFlowProps) {
|
|
89
130
|
const { initialNodes, initialEdges } = useMemo(() => {
|
|
90
|
-
|
|
91
|
-
|
|
131
|
+
// Handle Sim Studio format (blocks object + edges array)
|
|
132
|
+
if (platform === 'sim') {
|
|
133
|
+
const simWorkflow = workflow as SimWorkflow
|
|
134
|
+
const blocks = simWorkflow.blocks || {}
|
|
135
|
+
const simEdges = simWorkflow.edges || []
|
|
136
|
+
const blockList = Object.values(blocks)
|
|
137
|
+
|
|
138
|
+
if (blockList.length === 0) {
|
|
139
|
+
return { initialNodes: [], initialEdges: [] }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Build adjacency for layout
|
|
143
|
+
const forwardEdges = new Map<string, string[]>()
|
|
144
|
+
const backwardEdges = new Map<string, string[]>()
|
|
145
|
+
|
|
146
|
+
simEdges.forEach(edge => {
|
|
147
|
+
const from = edge.source
|
|
148
|
+
const to = edge.target
|
|
149
|
+
if (from && to) {
|
|
150
|
+
if (!forwardEdges.has(from)) forwardEdges.set(from, [])
|
|
151
|
+
forwardEdges.get(from)!.push(to)
|
|
152
|
+
if (!backwardEdges.has(to)) backwardEdges.set(to, [])
|
|
153
|
+
backwardEdges.get(to)!.push(from)
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Find trigger blocks
|
|
158
|
+
const triggerBlocks = blockList.filter(b =>
|
|
159
|
+
b.type === 'starter' || b.type === 'webhook' || b.type === 'api'
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
const roots = triggerBlocks.length > 0
|
|
163
|
+
? triggerBlocks
|
|
164
|
+
: blockList.filter(b => !backwardEdges.has(b.id) || backwardEdges.get(b.id)!.length === 0)
|
|
165
|
+
|
|
166
|
+
// BFS to assign levels
|
|
167
|
+
const levels = new Map<string, number>()
|
|
168
|
+
const queue: string[] = []
|
|
169
|
+
|
|
170
|
+
roots.forEach(r => {
|
|
171
|
+
levels.set(r.id, 0)
|
|
172
|
+
queue.push(r.id)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const visited = new Set<string>()
|
|
176
|
+
while (queue.length > 0) {
|
|
177
|
+
const id = queue.shift()!
|
|
178
|
+
if (visited.has(id)) continue
|
|
179
|
+
visited.add(id)
|
|
180
|
+
|
|
181
|
+
const children = forwardEdges.get(id) || []
|
|
182
|
+
const myLevel = levels.get(id) || 0
|
|
183
|
+
|
|
184
|
+
children.forEach(child => {
|
|
185
|
+
const childLevel = levels.get(child)
|
|
186
|
+
if (childLevel === undefined || myLevel + 1 > childLevel) {
|
|
187
|
+
levels.set(child, myLevel + 1)
|
|
188
|
+
}
|
|
189
|
+
if (!visited.has(child)) {
|
|
190
|
+
queue.push(child)
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Handle disconnected blocks
|
|
196
|
+
blockList.forEach(block => {
|
|
197
|
+
if (!levels.has(block.id)) {
|
|
198
|
+
const maxLevel = Math.max(0, ...Array.from(levels.values()))
|
|
199
|
+
levels.set(block.id, maxLevel + 1)
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Group by level and position
|
|
204
|
+
const nodesByLevel = new Map<number, string[]>()
|
|
205
|
+
levels.forEach((level, id) => {
|
|
206
|
+
if (!nodesByLevel.has(level)) nodesByLevel.set(level, [])
|
|
207
|
+
nodesByLevel.get(level)!.push(id)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const xGap = 170
|
|
211
|
+
const yGap = 70
|
|
212
|
+
const positions = new Map<string, { x: number; y: number }>()
|
|
213
|
+
|
|
214
|
+
const sortedLevels = Array.from(nodesByLevel.keys()).sort((a, b) => a - b)
|
|
215
|
+
sortedLevels.forEach(level => {
|
|
216
|
+
const nodesInLevel = nodesByLevel.get(level)!
|
|
217
|
+
const totalHeight = (nodesInLevel.length - 1) * yGap
|
|
218
|
+
const startY = -totalHeight / 2
|
|
219
|
+
nodesInLevel.forEach((id, i) => {
|
|
220
|
+
positions.set(id, { x: level * xGap, y: startY + i * yGap })
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Create ReactFlow nodes
|
|
225
|
+
const nodes: Node[] = blockList.map(block => ({
|
|
226
|
+
id: block.id,
|
|
227
|
+
type: 'custom',
|
|
228
|
+
position: positions.get(block.id) || { x: 0, y: 0 },
|
|
229
|
+
data: { label: block.name || block.type, type: block.type },
|
|
230
|
+
}))
|
|
231
|
+
|
|
232
|
+
// Create edges
|
|
233
|
+
const edges: Edge[] = simEdges.map((edge, idx) => ({
|
|
234
|
+
id: edge.id || `edge-${idx}`,
|
|
235
|
+
source: edge.source || '',
|
|
236
|
+
target: edge.target || '',
|
|
237
|
+
type: 'smoothstep',
|
|
238
|
+
pathOptions: { borderRadius: 20 },
|
|
239
|
+
style: { stroke: '#94a3b8', strokeWidth: 1.5 },
|
|
240
|
+
markerEnd: { type: MarkerType.ArrowClosed, color: '#94a3b8', width: 14, height: 14 },
|
|
241
|
+
})).filter(e => e.source && e.target)
|
|
242
|
+
|
|
243
|
+
return { initialNodes: nodes, initialEdges: edges }
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle n8n format (nodes array + connections object)
|
|
247
|
+
const n8nWorkflow = workflow as N8nWorkflow
|
|
248
|
+
const n8nNodes = n8nWorkflow.nodes || []
|
|
249
|
+
const connections = n8nWorkflow.connections || {}
|
|
92
250
|
const nodeIdMap = new Map(n8nNodes.map(n => [n.name, n.id || n.name]))
|
|
93
251
|
|
|
94
252
|
// Build adjacency lists (forward and backward)
|