@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dilipod/ui",
3
- "version": "0.4.14",
3
+ "version": "0.4.16",
4
4
  "description": "Dilipod Design System - Shared UI components and styles",
5
5
  "author": "Dilipod <hello@dilipod.com>",
6
6
  "license": "MIT",
@@ -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 n8n workflow to visualize */
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
- const n8nNodes = workflow.nodes || []
91
- const connections = workflow.connections || {}
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)