@dilipod/ui 0.4.22 → 0.4.23
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/flowchart-diagram.d.ts +8 -0
- package/dist/components/flowchart-diagram.d.ts.map +1 -0
- package/dist/components/worker-spec.d.ts +35 -0
- package/dist/components/worker-spec.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +420 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +420 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/flowchart-diagram.tsx +319 -0
- package/src/components/worker-spec.tsx +389 -0
- package/src/index.ts +8 -0
package/package.json
CHANGED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
|
|
5
|
+
// ============================================
|
|
6
|
+
// Types
|
|
7
|
+
// ============================================
|
|
8
|
+
|
|
9
|
+
interface FlowNode {
|
|
10
|
+
id: string
|
|
11
|
+
label: string
|
|
12
|
+
type: 'action' | 'decision' | 'terminal'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FlowEdge {
|
|
16
|
+
from: string
|
|
17
|
+
to: string
|
|
18
|
+
label?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============================================
|
|
22
|
+
// Parser
|
|
23
|
+
// ============================================
|
|
24
|
+
|
|
25
|
+
function parseMermaidFlowchart(mermaid: string): { nodes: FlowNode[]; edges: FlowEdge[] } {
|
|
26
|
+
const nodes = new Map<string, FlowNode>()
|
|
27
|
+
const edges: FlowEdge[] = []
|
|
28
|
+
|
|
29
|
+
const lines = mermaid
|
|
30
|
+
.split(/\\n|\n/)
|
|
31
|
+
.map(l => l.trim())
|
|
32
|
+
.filter(l => l && !l.startsWith('flowchart') && !l.startsWith('graph'))
|
|
33
|
+
|
|
34
|
+
function parseNodeDef(str: string): { id: string; label?: string; type?: FlowNode['type'] } {
|
|
35
|
+
// Decision: D{Certificate Found?}
|
|
36
|
+
const decisionMatch = str.match(/^([A-Za-z0-9_]+)\{(.+?)\}$/)
|
|
37
|
+
if (decisionMatch) return { id: decisionMatch[1], label: decisionMatch[2], type: 'decision' }
|
|
38
|
+
// Action / terminal: A[Start] or A(Start) or A([Start])
|
|
39
|
+
const bracketMatch = str.match(/^([A-Za-z0-9_]+)\[?\(?\[?(.+?)\]?\)?\]?$/)
|
|
40
|
+
if (bracketMatch) {
|
|
41
|
+
const label = bracketMatch[2]
|
|
42
|
+
const isTerminal = /^(start|end|begin|finish|done)$/i.test(label)
|
|
43
|
+
return { id: bracketMatch[1], label, type: isTerminal ? 'terminal' : 'action' }
|
|
44
|
+
}
|
|
45
|
+
// Just an ID reference
|
|
46
|
+
return { id: str.trim() }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const line of lines) {
|
|
50
|
+
const edgeMatch = line.match(/^(.+?)\s*-->(?:\|(.+?)\|)?\s*(.+)$/)
|
|
51
|
+
if (!edgeMatch) continue
|
|
52
|
+
|
|
53
|
+
const leftRaw = edgeMatch[1].trim()
|
|
54
|
+
const edgeLabel = edgeMatch[2]?.trim()
|
|
55
|
+
const rightRaw = edgeMatch[3].trim()
|
|
56
|
+
|
|
57
|
+
const left = parseNodeDef(leftRaw)
|
|
58
|
+
const right = parseNodeDef(rightRaw)
|
|
59
|
+
|
|
60
|
+
if (left.label && !nodes.has(left.id)) {
|
|
61
|
+
nodes.set(left.id, { id: left.id, label: left.label, type: left.type || 'action' })
|
|
62
|
+
}
|
|
63
|
+
if (right.label && !nodes.has(right.id)) {
|
|
64
|
+
nodes.set(right.id, { id: right.id, label: right.label, type: right.type || 'action' })
|
|
65
|
+
}
|
|
66
|
+
if (!nodes.has(left.id)) {
|
|
67
|
+
nodes.set(left.id, { id: left.id, label: left.id, type: 'action' })
|
|
68
|
+
}
|
|
69
|
+
if (!nodes.has(right.id)) {
|
|
70
|
+
nodes.set(right.id, { id: right.id, label: right.id, type: 'action' })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
edges.push({ from: left.id, to: right.id, label: edgeLabel })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { nodes: Array.from(nodes.values()), edges }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// Layout Builder
|
|
81
|
+
// ============================================
|
|
82
|
+
|
|
83
|
+
function findMergePoint(
|
|
84
|
+
branchStarts: string[],
|
|
85
|
+
outgoing: Map<string, FlowEdge[]>,
|
|
86
|
+
): string | null {
|
|
87
|
+
const reachable = new Map<string, Set<string>>()
|
|
88
|
+
|
|
89
|
+
for (const start of branchStarts) {
|
|
90
|
+
const q = [start]
|
|
91
|
+
const seen = new Set<string>()
|
|
92
|
+
while (q.length > 0) {
|
|
93
|
+
const id = q.shift()!
|
|
94
|
+
if (seen.has(id)) continue
|
|
95
|
+
seen.add(id)
|
|
96
|
+
if (!reachable.has(id)) reachable.set(id, new Set())
|
|
97
|
+
reachable.get(id)!.add(start)
|
|
98
|
+
const outs = outgoing.get(id) || []
|
|
99
|
+
for (const e of outs) q.push(e.to)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const allBranches = new Set(branchStarts)
|
|
104
|
+
const q = [branchStarts[0]]
|
|
105
|
+
const seen = new Set<string>()
|
|
106
|
+
while (q.length > 0) {
|
|
107
|
+
const id = q.shift()!
|
|
108
|
+
if (seen.has(id)) continue
|
|
109
|
+
seen.add(id)
|
|
110
|
+
if (!allBranches.has(id) && reachable.get(id)?.size === branchStarts.length) {
|
|
111
|
+
return id
|
|
112
|
+
}
|
|
113
|
+
const outs = outgoing.get(id) || []
|
|
114
|
+
for (const e of outs) q.push(e.to)
|
|
115
|
+
}
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type LayoutItem =
|
|
120
|
+
| { type: 'node'; nodeId: string }
|
|
121
|
+
| { type: 'arrow'; label?: string }
|
|
122
|
+
| { type: 'branch'; decision: string; branches: { label?: string; items: LayoutItem[] }[]; mergeId: string | null }
|
|
123
|
+
|
|
124
|
+
function buildLayout(
|
|
125
|
+
startId: string,
|
|
126
|
+
outgoing: Map<string, FlowEdge[]>,
|
|
127
|
+
incoming: Map<string, FlowEdge[]>,
|
|
128
|
+
nodeMap: Map<string, FlowNode>,
|
|
129
|
+
visited: Set<string>
|
|
130
|
+
): LayoutItem[] {
|
|
131
|
+
const items: LayoutItem[] = []
|
|
132
|
+
let currentId: string | null = startId
|
|
133
|
+
|
|
134
|
+
while (currentId) {
|
|
135
|
+
if (visited.has(currentId)) break
|
|
136
|
+
|
|
137
|
+
const node = nodeMap.get(currentId)
|
|
138
|
+
if (!node) break
|
|
139
|
+
|
|
140
|
+
const outs: FlowEdge[] = outgoing.get(currentId) || []
|
|
141
|
+
|
|
142
|
+
if (outs.length <= 1) {
|
|
143
|
+
visited.add(currentId)
|
|
144
|
+
items.push({ type: 'node', nodeId: currentId })
|
|
145
|
+
if (outs.length === 1) {
|
|
146
|
+
const nextId: string = outs[0].to
|
|
147
|
+
if (visited.has(nextId)) break
|
|
148
|
+
items.push({ type: 'arrow', label: outs[0].label })
|
|
149
|
+
currentId = nextId
|
|
150
|
+
} else {
|
|
151
|
+
currentId = null
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
visited.add(currentId)
|
|
155
|
+
items.push({ type: 'node', nodeId: currentId })
|
|
156
|
+
|
|
157
|
+
const branchStarts: string[] = outs.map((e: FlowEdge) => e.to)
|
|
158
|
+
const mergeId = findMergePoint(branchStarts, outgoing)
|
|
159
|
+
|
|
160
|
+
const branches: { label?: string; items: LayoutItem[] }[] = []
|
|
161
|
+
for (const edge of outs) {
|
|
162
|
+
if (visited.has(edge.to) && edge.to !== mergeId) {
|
|
163
|
+
branches.push({ label: edge.label, items: [] })
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
const branchItems = buildLayout(edge.to, outgoing, incoming, nodeMap, visited)
|
|
167
|
+
branches.push({ label: edge.label, items: branchItems })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
items.push({ type: 'branch', decision: currentId, branches, mergeId })
|
|
171
|
+
|
|
172
|
+
if (mergeId && !visited.has(mergeId)) {
|
|
173
|
+
items.push({ type: 'arrow' })
|
|
174
|
+
currentId = mergeId
|
|
175
|
+
} else {
|
|
176
|
+
currentId = null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return items
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ============================================
|
|
185
|
+
// Render Components
|
|
186
|
+
// ============================================
|
|
187
|
+
|
|
188
|
+
function FlowArrow({ label }: { label?: string }) {
|
|
189
|
+
return (
|
|
190
|
+
<div className="flex flex-col items-center">
|
|
191
|
+
{label && (
|
|
192
|
+
<span className="text-[10px] font-medium text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded mb-0.5">
|
|
193
|
+
{label}
|
|
194
|
+
</span>
|
|
195
|
+
)}
|
|
196
|
+
<div className="w-px h-4 bg-gray-300" />
|
|
197
|
+
<div className="w-0 h-0 border-l-[4px] border-r-[4px] border-t-[5px] border-l-transparent border-r-transparent border-t-gray-300" />
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function FlowNodeBox({ node }: { node: FlowNode }) {
|
|
203
|
+
if (node.type === 'decision') {
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
className="bg-amber-50 border-2 border-amber-300 rounded-lg px-4 py-2.5 text-xs font-medium text-amber-800 text-center my-1"
|
|
207
|
+
style={{ minWidth: '120px' }}
|
|
208
|
+
>
|
|
209
|
+
<span className="text-amber-400 mr-1">◇</span>
|
|
210
|
+
{node.label}
|
|
211
|
+
</div>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
if (node.type === 'terminal') {
|
|
215
|
+
return (
|
|
216
|
+
<div className="bg-gray-100 border border-gray-200 rounded-full px-5 py-1.5 text-xs font-medium text-gray-500 text-center my-1">
|
|
217
|
+
{node.label}
|
|
218
|
+
</div>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
return (
|
|
222
|
+
<div className="bg-white border border-gray-200 rounded-sm px-4 py-2 text-xs font-medium text-[var(--black)] text-center shadow-sm my-1 max-w-[220px]">
|
|
223
|
+
{node.label}
|
|
224
|
+
</div>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function RenderLayoutItems({ items, nodeMap }: { items: LayoutItem[]; nodeMap: Map<string, FlowNode> }) {
|
|
229
|
+
return (
|
|
230
|
+
<>
|
|
231
|
+
{items.map((item, i) => {
|
|
232
|
+
if (item.type === 'node') {
|
|
233
|
+
const node = nodeMap.get(item.nodeId)
|
|
234
|
+
if (!node) return null
|
|
235
|
+
return <FlowNodeBox key={`node-${item.nodeId}`} node={node} />
|
|
236
|
+
}
|
|
237
|
+
if (item.type === 'arrow') {
|
|
238
|
+
return <FlowArrow key={`arrow-${i}`} label={item.label} />
|
|
239
|
+
}
|
|
240
|
+
if (item.type === 'branch') {
|
|
241
|
+
return (
|
|
242
|
+
<div key={`branch-${item.decision}-${i}`} className="flex flex-col items-center w-full">
|
|
243
|
+
<div className="w-px h-3 bg-gray-300" />
|
|
244
|
+
<div className="flex items-start justify-center gap-6 w-full">
|
|
245
|
+
{item.branches.map((branch, j) => (
|
|
246
|
+
<div key={j} className="flex flex-col items-center min-w-[100px]">
|
|
247
|
+
<div className="flex flex-col items-center">
|
|
248
|
+
{branch.label && (
|
|
249
|
+
<span className="text-[10px] font-medium text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded mb-1">
|
|
250
|
+
{branch.label}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
253
|
+
<div className="w-0 h-0 border-l-[4px] border-r-[4px] border-t-[5px] border-l-transparent border-r-transparent border-t-gray-300" />
|
|
254
|
+
</div>
|
|
255
|
+
<div className="flex flex-col items-center">
|
|
256
|
+
<RenderLayoutItems items={branch.items} nodeMap={nodeMap} />
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
{item.mergeId && (
|
|
262
|
+
<div className="w-px h-3 bg-gray-300" />
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
return null
|
|
268
|
+
})}
|
|
269
|
+
</>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ============================================
|
|
274
|
+
// Public Component
|
|
275
|
+
// ============================================
|
|
276
|
+
|
|
277
|
+
export interface FlowchartDiagramProps {
|
|
278
|
+
/** Mermaid flowchart syntax string */
|
|
279
|
+
mermaid: string
|
|
280
|
+
/** Optional className for the container */
|
|
281
|
+
className?: string
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function FlowchartDiagram({ mermaid, className }: FlowchartDiagramProps) {
|
|
285
|
+
const { nodes, edges } = parseMermaidFlowchart(mermaid)
|
|
286
|
+
|
|
287
|
+
if (nodes.length === 0) {
|
|
288
|
+
return (
|
|
289
|
+
<pre className="text-xs bg-white border border-gray-100 rounded-sm p-3 overflow-x-auto whitespace-pre-wrap">
|
|
290
|
+
{mermaid}
|
|
291
|
+
</pre>
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const outgoing = new Map<string, FlowEdge[]>()
|
|
296
|
+
const incoming = new Map<string, FlowEdge[]>()
|
|
297
|
+
for (const edge of edges) {
|
|
298
|
+
if (!outgoing.has(edge.from)) outgoing.set(edge.from, [])
|
|
299
|
+
outgoing.get(edge.from)!.push(edge)
|
|
300
|
+
if (!incoming.has(edge.to)) incoming.set(edge.to, [])
|
|
301
|
+
incoming.get(edge.to)!.push(edge)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const nodeMap = new Map(nodes.map(n => [n.id, n]))
|
|
305
|
+
|
|
306
|
+
const roots = nodes.filter(n => !incoming.has(n.id) || incoming.get(n.id)!.length === 0)
|
|
307
|
+
const startId = roots.length > 0 ? roots[0].id : nodes[0].id
|
|
308
|
+
|
|
309
|
+
const visited = new Set<string>()
|
|
310
|
+
const layout = buildLayout(startId, outgoing, incoming, nodeMap, visited)
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<div className={className}>
|
|
314
|
+
<div className="flex flex-col items-center py-2">
|
|
315
|
+
<RenderLayoutItems items={layout} nodeMap={nodeMap} />
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
)
|
|
319
|
+
}
|