@particle-academy/fancy-flow 0.8.0 → 0.10.0
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/README.md +104 -0
- package/dist/{chunk-BCXECQUC.js → chunk-6GGFEZH7.js} +3 -3
- package/dist/{chunk-BCXECQUC.js.map → chunk-6GGFEZH7.js.map} +1 -1
- package/dist/{chunk-WNVBXXOL.js → chunk-CPSOC27D.js} +43 -2
- package/dist/chunk-CPSOC27D.js.map +1 -0
- package/dist/chunk-F5RPRB7A.js +56 -0
- package/dist/chunk-F5RPRB7A.js.map +1 -0
- package/dist/{chunk-NVULCEDX.js → chunk-HNBO4HP3.js} +8 -4
- package/dist/chunk-HNBO4HP3.js.map +1 -0
- package/dist/{chunk-M2XKGQQL.js → chunk-NCPQDVUE.js} +178 -16
- package/dist/chunk-NCPQDVUE.js.map +1 -0
- package/dist/chunk-TITD5W4Y.js +26 -0
- package/dist/chunk-TITD5W4Y.js.map +1 -0
- package/dist/{chunk-QSSQRQN4.js → chunk-VEI743ZX.js} +3 -3
- package/dist/{chunk-QSSQRQN4.js.map → chunk-VEI743ZX.js.map} +1 -1
- package/dist/engine.cjs +32 -2
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.js +3 -1
- package/dist/index.cjs +716 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -6
- package/dist/index.d.ts +45 -6
- package/dist/index.js +417 -23
- package/dist/index.js.map +1 -1
- package/dist/registry/index.d.cts +112 -4
- package/dist/registry/index.d.ts +112 -4
- package/dist/registry.cjs +312 -31
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +4 -2
- package/dist/rich-input.cjs +56 -0
- package/dist/rich-input.cjs.map +1 -0
- package/dist/rich-input.d.cts +27 -0
- package/dist/rich-input.d.ts +27 -0
- package/dist/rich-input.js +37 -0
- package/dist/rich-input.js.map +1 -0
- package/dist/runtime.cjs +32 -2
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js +4 -2
- package/dist/schema.cjs +41 -0
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/styles.css +191 -0
- package/dist/styles.css.map +1 -1
- package/dist/types-BocBFh6l.d.ts +221 -0
- package/dist/types-DKqaUjF_.d.cts +221 -0
- package/dist/ux.cjs.map +1 -1
- package/dist/ux.d.cts +1 -1
- package/dist/ux.d.ts +1 -1
- package/dist/ux.js +1 -1
- package/package.json +24 -2
- package/dist/chunk-M2XKGQQL.js.map +0 -1
- package/dist/chunk-NVULCEDX.js.map +0 -1
- package/dist/chunk-WNVBXXOL.js.map +0 -1
- package/dist/types-C0wdN6QX.d.cts +0 -121
- package/dist/types-DnMe9Vsf.d.ts +0 -121
package/README.md
CHANGED
|
@@ -132,6 +132,110 @@ Swap the menu for your own with `slots.contextMenu`, or turn it off with
|
|
|
132
132
|
|
|
133
133
|
`onDelete(ids)` fires after either path, so a host can sync its own store.
|
|
134
134
|
|
|
135
|
+
### Connections — breaking and labelling
|
|
136
|
+
|
|
137
|
+
- **right-click a connection** → `Label…` / `Delete connection`,
|
|
138
|
+
- select a connection and press <kbd>Delete</kbd> / <kbd>Backspace</kbd>,
|
|
139
|
+
- `api.deleteEdges(ids)`, `api.deleteSelectedEdge()`, `api.setEdgeLabel(id, text)`.
|
|
140
|
+
|
|
141
|
+
Labels ride on the edge (`edge.label`), so they survive export/import. Clearing
|
|
142
|
+
a label removes the key rather than storing `""`.
|
|
143
|
+
|
|
144
|
+
Replace the menu with `slots.edgeContextMenu`, disable it with
|
|
145
|
+
`builtins={{ edgeContextMenu: false }}`, and hook `onEdgeDelete(ids)` to sync
|
|
146
|
+
your own store.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
<FlowEditor
|
|
150
|
+
onEdgeDelete={(ids) => console.log("broke", ids)}
|
|
151
|
+
slots={{
|
|
152
|
+
edgeContextMenu: (api, edgeId, close) => (
|
|
153
|
+
<button onClick={() => { api.setEdgeLabel(edgeId, "approved"); close(); }}>
|
|
154
|
+
Mark approved
|
|
155
|
+
</button>
|
|
156
|
+
),
|
|
157
|
+
}}
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Config fields
|
|
162
|
+
|
|
163
|
+
Node config is declared as a `configSchema` and rendered by `NodeConfigPanel`.
|
|
164
|
+
Alongside `text` / `textarea` / `number` / `select` / `switch` / `json` /
|
|
165
|
+
`expression` / `credential`:
|
|
166
|
+
|
|
167
|
+
- **`repeater`** — a list of objects, each row authored with its own
|
|
168
|
+
sub-schema. Reach for this instead of `type: "json"` whenever config is
|
|
169
|
+
list-shaped (form fields, routes, tool bindings); it keeps the panel the
|
|
170
|
+
single authoring surface for humans and keeps the shape introspectable for
|
|
171
|
+
agents.
|
|
172
|
+
- **`keyvalue`** — an editable `Record<string, string>` (filter maps, headers,
|
|
173
|
+
case→port tables). `valueOptions` constrains the values.
|
|
174
|
+
- **`document`** — an opaque rich document edited by a **host-supplied**
|
|
175
|
+
editor (see below).
|
|
176
|
+
|
|
177
|
+
A `text` field with `choices` renders as a select instead of a free-text input,
|
|
178
|
+
so a kind can gain a fixed set of options without changing its type or
|
|
179
|
+
migrating saved config. A stored value outside the list is preserved and shown
|
|
180
|
+
rather than dropped:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
{ type: "text", key: "region", label: "Region", choices: ["us-east", "eu-west"] }
|
|
184
|
+
{ type: "text", key: "tier", label: "Tier", choices: [{ value: "p1", label: "Priority 1" }] }
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Ports that follow config
|
|
188
|
+
|
|
189
|
+
`inputs` / `outputs` accept a function of the node's config, for kinds whose
|
|
190
|
+
branches *are* their config:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
outputs: (config) => config.routes.map((r) => ({ id: r.port, label: r.port })),
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Both the canvas and the runtime resolve ports through the same helper, so the
|
|
197
|
+
handles you see and the ports a run activates cannot drift apart. `switch_case`
|
|
198
|
+
and `llm_branch` are built this way.
|
|
199
|
+
|
|
200
|
+
### Rich human input
|
|
201
|
+
|
|
202
|
+
`rich_user_input` pauses a run on a fully authored page rather than a flat field
|
|
203
|
+
list, and previews that page **inside the node** using react-fancy's
|
|
204
|
+
`FauxClient` frame.
|
|
205
|
+
|
|
206
|
+
**The page it shows is a fancy-cms page** — the same `PageDoc`, rendered by the
|
|
207
|
+
same `CmsPage`, authored by the same `Editor`. fancy-flow defines no document
|
|
208
|
+
schema of its own; a step authored here stays a document fancy-cms can open.
|
|
209
|
+
|
|
210
|
+
Enable it with one import:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import "@particle-academy/fancy-flow/rich-input";
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
npm i @particle-academy/fancy-cms-ui @particle-academy/react-fancy
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Those two are **optional peers** — required only by this subpath. The main entry
|
|
221
|
+
never imports them, so a flow that has no rich input never pays for a CMS.
|
|
222
|
+
Without the import the node still registers and round-trips its config; it
|
|
223
|
+
renders a "how to enable" body instead of an empty card.
|
|
224
|
+
|
|
225
|
+
To pass a custom element registry (the same one you give `CmsPage` at runtime,
|
|
226
|
+
or the edit canvas renders your node types as blank placeholders):
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { useFancyCmsForRichInput } from "@particle-academy/fancy-flow/rich-input";
|
|
230
|
+
|
|
231
|
+
useFancyCmsForRichInput({ registry: myElements, data: previewData });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The underlying seam (`registerRichInputAdapter`) stays public if you need a
|
|
235
|
+
different document engine, and any kind can use a `document` field with
|
|
236
|
+
`NodeConfigPanel`'s `renderDocumentField`. But fancy-cms is the expected path —
|
|
237
|
+
the point is not duplicating a document model.
|
|
238
|
+
|
|
135
239
|
If you want none of the above chrome, skip `<FlowEditor>` entirely and compose
|
|
136
240
|
`useFlowState()` + `<FlowCanvas>` + `<NodePalette>` + `<NodeConfigPanel>`
|
|
137
241
|
yourself — they are all exported.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getNodeKind, defaultConfigFor, validateConfig } from './chunk-
|
|
1
|
+
import { getNodeKind, defaultConfigFor, validateConfig } from './chunk-CPSOC27D.js';
|
|
2
2
|
|
|
3
3
|
// src/schema/workflow-schema.ts
|
|
4
4
|
var WORKFLOW_SCHEMA_VERSION = 1;
|
|
@@ -106,5 +106,5 @@ function workflowToBlob(schema) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
export { WORKFLOW_SCHEMA_URL, WORKFLOW_SCHEMA_VERSION, exportWorkflow, importWorkflow, workflowToBlob };
|
|
109
|
-
//# sourceMappingURL=chunk-
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
109
|
+
//# sourceMappingURL=chunk-6GGFEZH7.js.map
|
|
110
|
+
//# sourceMappingURL=chunk-6GGFEZH7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-BCXECQUC.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\n\n/** Schema version. Bump on breaking shape changes; add migrations as needed. */\nexport const WORKFLOW_SCHEMA_VERSION = 1 as const;\nexport const WORKFLOW_SCHEMA_URL = \"https://particle.academy/schemas/workflow/v1.json\";\n\nexport type WorkflowSchema = {\n $schema: typeof WORKFLOW_SCHEMA_URL;\n version: typeof WORKFLOW_SCHEMA_VERSION;\n metadata?: WorkflowMetadata;\n graph: {\n nodes: WorkflowSchemaNode[];\n edges: WorkflowSchemaEdge[];\n };\n view?: {\n viewport?: { x: number; y: number; zoom: number };\n };\n};\n\nexport type WorkflowMetadata = {\n id?: string;\n name?: string;\n description?: string;\n createdAt?: number;\n updatedAt?: number;\n author?: string;\n tags?: string[];\n};\n\nexport type WorkflowSchemaNode = {\n id: string;\n /** Registry kind name (e.g. \"memory_store\"). */\n kind: string;\n position: { x: number; y: number };\n label?: string;\n description?: string;\n config?: Record<string, unknown>;\n};\n\nexport type WorkflowSchemaEdge = {\n id: string;\n source: string;\n target: string;\n sourceHandle?: string;\n targetHandle?: string;\n label?: string;\n};\n\nexport type ImportIssue = {\n level: \"error\" | \"warning\";\n nodeId?: string;\n edgeId?: string;\n message: string;\n};\n\nexport type ImportResult = {\n graph: FlowGraph;\n issues: ImportIssue[];\n /** True when the import produced a usable graph (errors may have been\n * rewritten to warnings via `lenient: true`). */\n ok: boolean;\n};\n\n/** Snapshot the in-memory graph as a portable WorkflowSchema. */\nexport function exportWorkflow(\n graph: FlowGraph,\n metadata?: WorkflowMetadata,\n view?: WorkflowSchema[\"view\"],\n): WorkflowSchema {\n return {\n $schema: WORKFLOW_SCHEMA_URL,\n version: WORKFLOW_SCHEMA_VERSION,\n metadata: metadata ? { ...metadata, updatedAt: Date.now() } : undefined,\n graph: {\n nodes: graph.nodes.map(toSchemaNode),\n edges: graph.edges.map(toSchemaEdge),\n },\n view,\n };\n}\n\nfunction toSchemaNode(n: FlowNode): WorkflowSchemaNode {\n const data: any = n.data ?? {};\n return {\n id: n.id,\n kind: data.kind ?? n.type ?? \"custom\",\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n config: data.config,\n };\n}\n\nfunction toSchemaEdge(e: FlowEdge): WorkflowSchemaEdge {\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle ?? undefined,\n targetHandle: e.targetHandle ?? undefined,\n label: typeof e.label === \"string\" ? e.label : undefined,\n };\n}\n\nexport type ImportOptions = {\n /** When true, unknown kinds become warnings + a \"custom\" placeholder\n * instead of errors. Default false. */\n lenient?: boolean;\n};\n\n/**\n * Hydrate a schema into runtime FlowGraph + validate kinds/configs against\n * the registry. Reports issues for unknown kinds, missing required config,\n * and dangling edges.\n */\nexport function importWorkflow(schema: unknown, options: ImportOptions = {}): ImportResult {\n const issues: ImportIssue[] = [];\n const lenient = options.lenient === true;\n\n if (!schema || typeof schema !== \"object\") {\n return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: \"error\", message: \"Schema is not an object.\" }] };\n }\n const s = schema as Partial<WorkflowSchema>;\n if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n if (!lenient) return { ok: false, graph: { nodes: [], edges: [] }, issues };\n }\n\n const rawNodes = s.graph?.nodes ?? [];\n const rawEdges = s.graph?.edges ?? [];\n\n const nodes: FlowNode[] = rawNodes.map((n) => {\n const kind = getNodeKind(n.kind);\n if (!kind) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n nodeId: n.id,\n message: `Unknown kind \"${n.kind}\" — register it before importing.`,\n });\n }\n const config = n.config ?? (kind ? defaultConfigFor(kind) : {});\n if (kind) {\n for (const iss of validateConfig(kind, config)) {\n issues.push({ level: \"warning\", nodeId: n.id, message: `${iss.key}: ${iss.message}` });\n }\n }\n return {\n id: n.id,\n type: n.kind,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n data: {\n kind: n.kind,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n config,\n } as any,\n };\n });\n\n const nodeIds = new Set(nodes.map((n) => n.id));\n const edges: FlowEdge[] = rawEdges\n .map((e) => {\n if (!nodeIds.has(e.source)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge source \"${e.source}\" not found.` });\n return null;\n }\n if (!nodeIds.has(e.target)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge target \"${e.target}\" not found.` });\n return null;\n }\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle,\n targetHandle: e.targetHandle,\n label: e.label,\n } as FlowEdge;\n })\n .filter((e): e is FlowEdge => e !== null);\n\n const ok = issues.every((i) => i.level !== \"error\");\n return { ok, graph: { nodes, edges }, issues };\n}\n\n/** Convenience: serialize a schema as a downloadable JSON Blob. */\nexport function workflowToBlob(schema: WorkflowSchema): Blob {\n return new Blob([JSON.stringify(schema, null, 2)], { type: \"application/json\" });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/schema/workflow-schema.ts"],"names":[],"mappings":"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","file":"chunk-6GGFEZH7.js","sourcesContent":["import type { FlowEdge, FlowGraph, FlowNode } from \"../types\";\nimport { defaultConfigFor, getNodeKind, validateConfig } from \"../registry/registry\";\n\n/** Schema version. Bump on breaking shape changes; add migrations as needed. */\nexport const WORKFLOW_SCHEMA_VERSION = 1 as const;\nexport const WORKFLOW_SCHEMA_URL = \"https://particle.academy/schemas/workflow/v1.json\";\n\nexport type WorkflowSchema = {\n $schema: typeof WORKFLOW_SCHEMA_URL;\n version: typeof WORKFLOW_SCHEMA_VERSION;\n metadata?: WorkflowMetadata;\n graph: {\n nodes: WorkflowSchemaNode[];\n edges: WorkflowSchemaEdge[];\n };\n view?: {\n viewport?: { x: number; y: number; zoom: number };\n };\n};\n\nexport type WorkflowMetadata = {\n id?: string;\n name?: string;\n description?: string;\n createdAt?: number;\n updatedAt?: number;\n author?: string;\n tags?: string[];\n};\n\nexport type WorkflowSchemaNode = {\n id: string;\n /** Registry kind name (e.g. \"memory_store\"). */\n kind: string;\n position: { x: number; y: number };\n label?: string;\n description?: string;\n config?: Record<string, unknown>;\n};\n\nexport type WorkflowSchemaEdge = {\n id: string;\n source: string;\n target: string;\n sourceHandle?: string;\n targetHandle?: string;\n label?: string;\n};\n\nexport type ImportIssue = {\n level: \"error\" | \"warning\";\n nodeId?: string;\n edgeId?: string;\n message: string;\n};\n\nexport type ImportResult = {\n graph: FlowGraph;\n issues: ImportIssue[];\n /** True when the import produced a usable graph (errors may have been\n * rewritten to warnings via `lenient: true`). */\n ok: boolean;\n};\n\n/** Snapshot the in-memory graph as a portable WorkflowSchema. */\nexport function exportWorkflow(\n graph: FlowGraph,\n metadata?: WorkflowMetadata,\n view?: WorkflowSchema[\"view\"],\n): WorkflowSchema {\n return {\n $schema: WORKFLOW_SCHEMA_URL,\n version: WORKFLOW_SCHEMA_VERSION,\n metadata: metadata ? { ...metadata, updatedAt: Date.now() } : undefined,\n graph: {\n nodes: graph.nodes.map(toSchemaNode),\n edges: graph.edges.map(toSchemaEdge),\n },\n view,\n };\n}\n\nfunction toSchemaNode(n: FlowNode): WorkflowSchemaNode {\n const data: any = n.data ?? {};\n return {\n id: n.id,\n kind: data.kind ?? n.type ?? \"custom\",\n position: { x: n.position.x, y: n.position.y },\n label: data.label,\n description: data.description,\n config: data.config,\n };\n}\n\nfunction toSchemaEdge(e: FlowEdge): WorkflowSchemaEdge {\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle ?? undefined,\n targetHandle: e.targetHandle ?? undefined,\n label: typeof e.label === \"string\" ? e.label : undefined,\n };\n}\n\nexport type ImportOptions = {\n /** When true, unknown kinds become warnings + a \"custom\" placeholder\n * instead of errors. Default false. */\n lenient?: boolean;\n};\n\n/**\n * Hydrate a schema into runtime FlowGraph + validate kinds/configs against\n * the registry. Reports issues for unknown kinds, missing required config,\n * and dangling edges.\n */\nexport function importWorkflow(schema: unknown, options: ImportOptions = {}): ImportResult {\n const issues: ImportIssue[] = [];\n const lenient = options.lenient === true;\n\n if (!schema || typeof schema !== \"object\") {\n return { ok: false, graph: { nodes: [], edges: [] }, issues: [{ level: \"error\", message: \"Schema is not an object.\" }] };\n }\n const s = schema as Partial<WorkflowSchema>;\n if (s.version !== WORKFLOW_SCHEMA_VERSION) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n message: `Unsupported workflow schema version: ${s.version} (expected ${WORKFLOW_SCHEMA_VERSION})`,\n });\n if (!lenient) return { ok: false, graph: { nodes: [], edges: [] }, issues };\n }\n\n const rawNodes = s.graph?.nodes ?? [];\n const rawEdges = s.graph?.edges ?? [];\n\n const nodes: FlowNode[] = rawNodes.map((n) => {\n const kind = getNodeKind(n.kind);\n if (!kind) {\n issues.push({\n level: lenient ? \"warning\" : \"error\",\n nodeId: n.id,\n message: `Unknown kind \"${n.kind}\" — register it before importing.`,\n });\n }\n const config = n.config ?? (kind ? defaultConfigFor(kind) : {});\n if (kind) {\n for (const iss of validateConfig(kind, config)) {\n issues.push({ level: \"warning\", nodeId: n.id, message: `${iss.key}: ${iss.message}` });\n }\n }\n return {\n id: n.id,\n type: n.kind,\n position: { x: n.position?.x ?? 0, y: n.position?.y ?? 0 },\n data: {\n kind: n.kind,\n label: n.label ?? kind?.label ?? n.kind,\n description: n.description,\n config,\n } as any,\n };\n });\n\n const nodeIds = new Set(nodes.map((n) => n.id));\n const edges: FlowEdge[] = rawEdges\n .map((e) => {\n if (!nodeIds.has(e.source)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge source \"${e.source}\" not found.` });\n return null;\n }\n if (!nodeIds.has(e.target)) {\n issues.push({ level: \"warning\", edgeId: e.id, message: `Edge target \"${e.target}\" not found.` });\n return null;\n }\n return {\n id: e.id,\n source: e.source,\n target: e.target,\n sourceHandle: e.sourceHandle,\n targetHandle: e.targetHandle,\n label: e.label,\n } as FlowEdge;\n })\n .filter((e): e is FlowEdge => e !== null);\n\n const ok = issues.every((i) => i.level !== \"error\");\n return { ok, graph: { nodes, edges }, issues };\n}\n\n/** Convenience: serialize a schema as a downloadable JSON Blob. */\nexport function workflowToBlob(schema: WorkflowSchema): Blob {\n return new Blob([JSON.stringify(schema, null, 2)], { type: \"application/json\" });\n}\n"]}
|
|
@@ -71,6 +71,47 @@ function validateField(field, value) {
|
|
|
71
71
|
case "json":
|
|
72
72
|
return null;
|
|
73
73
|
// permissive — just JSON-shaped
|
|
74
|
+
case "repeater": {
|
|
75
|
+
if (!Array.isArray(value)) return `${field.label} must be a list`;
|
|
76
|
+
if (field.minItems !== void 0 && value.length < field.minItems) {
|
|
77
|
+
return `${field.label} needs at least ${field.minItems}`;
|
|
78
|
+
}
|
|
79
|
+
if (field.maxItems !== void 0 && value.length > field.maxItems) {
|
|
80
|
+
return `${field.label} allows at most ${field.maxItems}`;
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i < value.length; i++) {
|
|
83
|
+
const row = value[i];
|
|
84
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
85
|
+
return `${field.label} item ${i + 1} must be an object`;
|
|
86
|
+
}
|
|
87
|
+
for (const sub of field.fields) {
|
|
88
|
+
const cell = row[sub.key];
|
|
89
|
+
if (sub.required && (cell === void 0 || cell === null || cell === "")) {
|
|
90
|
+
return `${field.label} item ${i + 1}: ${sub.label} is required`;
|
|
91
|
+
}
|
|
92
|
+
if (cell === void 0 || cell === null) continue;
|
|
93
|
+
const issue = validateField(sub, cell);
|
|
94
|
+
if (issue) return `${field.label} item ${i + 1}: ${issue}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
case "keyvalue": {
|
|
100
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
101
|
+
return `${field.label} must be a key/value map`;
|
|
102
|
+
}
|
|
103
|
+
const allowed = field.valueOptions?.map((o) => o.value);
|
|
104
|
+
for (const [k, v] of Object.entries(value)) {
|
|
105
|
+
if (typeof v !== "string") return `${field.label}: "${k}" must be a string`;
|
|
106
|
+
if (allowed && !allowed.includes(v)) {
|
|
107
|
+
return `${field.label}: "${k}" must be one of ${allowed.join(", ")}`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
case "document":
|
|
113
|
+
return null;
|
|
114
|
+
// opaque to fancy-flow — the host's editor owns its shape
|
|
74
115
|
default:
|
|
75
116
|
return null;
|
|
76
117
|
}
|
|
@@ -97,5 +138,5 @@ function categoryAccent(category) {
|
|
|
97
138
|
}
|
|
98
139
|
|
|
99
140
|
export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig };
|
|
100
|
-
//# sourceMappingURL=chunk-
|
|
101
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-CPSOC27D.js.map
|
|
142
|
+
//# sourceMappingURL=chunk-CPSOC27D.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry/registry.ts"],"names":[],"mappings":"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","file":"chunk-CPSOC27D.js","sourcesContent":["import type { ConfigField, NodeKindDefinition } from \"./types\";\n\nconst kinds = new Map<string, NodeKindDefinition<any, any, any>>();\nconst listeners = new Set<() => void>();\n\n/**\n * registerNodeKind — install a node kind in the global registry. Returns\n * an `unregister` function. Calling with the same name replaces the prior\n * registration (handy for HMR).\n */\nexport function registerNodeKind<TC = any, TI = any, TO = any>(\n definition: NodeKindDefinition<TC, TI, TO>,\n): () => void {\n kinds.set(definition.name, definition as NodeKindDefinition<any, any, any>);\n notify();\n return () => {\n if (kinds.get(definition.name) === (definition as any)) {\n kinds.delete(definition.name);\n notify();\n }\n };\n}\n\n/** Get a single kind by name, or null. */\nexport function getNodeKind(name: string): NodeKindDefinition | null {\n return (kinds.get(name) as NodeKindDefinition) ?? null;\n}\n\n/** List every registered kind, optionally filtered by category. */\nexport function listNodeKinds(category?: string): NodeKindDefinition[] {\n const all = Array.from(kinds.values()) as NodeKindDefinition[];\n return category ? all.filter((k) => k.category === category) : all;\n}\n\n/** Subscribe to registry changes. Returns an unsubscribe function. */\nexport function onNodeKindsChanged(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nfunction notify(): void {\n for (const l of listeners) l();\n}\n\n/** Fill in defaults from a kind's configSchema for newly-created nodes. */\nexport function defaultConfigFor(kind: NodeKindDefinition): Record<string, unknown> {\n const fromKind = kind.defaultConfig ? { ...(kind.defaultConfig as Record<string, unknown>) } : {};\n for (const field of kind.configSchema ?? []) {\n if (fromKind[field.key] !== undefined) continue;\n if (\"default\" in field && (field as any).default !== undefined) {\n fromKind[field.key] = (field as any).default;\n }\n }\n return fromKind;\n}\n\n/**\n * Validate a config object against a kind's schema. Returns an array of\n * issues (empty = valid). Validation is intentionally light — type\n * coercion + required-field checks. Hosts can layer Zod / Ajv on top.\n */\nexport function validateConfig(\n kind: NodeKindDefinition,\n config: Record<string, unknown>,\n): Array<{ key: string; message: string }> {\n const issues: Array<{ key: string; message: string }> = [];\n for (const field of kind.configSchema ?? []) {\n const value = config[field.key];\n if (field.required && (value === undefined || value === null || value === \"\")) {\n issues.push({ key: field.key, message: `${field.label} is required` });\n continue;\n }\n if (value === undefined || value === null) continue;\n const issue = validateField(field, value);\n if (issue) issues.push({ key: field.key, message: issue });\n }\n return issues;\n}\n\nfunction validateField(field: ConfigField, value: unknown): string | null {\n switch (field.type) {\n case \"text\":\n case \"textarea\":\n case \"expression\":\n case \"credential\":\n return typeof value === \"string\" ? null : `${field.label} must be a string`;\n case \"number\": {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return `${field.label} must be a number`;\n if (field.min !== undefined && value < field.min) return `${field.label} must be >= ${field.min}`;\n if (field.max !== undefined && value > field.max) return `${field.label} must be <= ${field.max}`;\n return null;\n }\n case \"switch\":\n return typeof value === \"boolean\" ? null : `${field.label} must be a boolean`;\n case \"select\": {\n const allowed = field.options.map((o) => o.value);\n return allowed.includes(String(value)) ? null : `${field.label} must be one of ${allowed.join(\", \")}`;\n }\n case \"json\":\n return null; // permissive — just JSON-shaped\n case \"repeater\": {\n if (!Array.isArray(value)) return `${field.label} must be a list`;\n if (field.minItems !== undefined && value.length < field.minItems) {\n return `${field.label} needs at least ${field.minItems}`;\n }\n if (field.maxItems !== undefined && value.length > field.maxItems) {\n return `${field.label} allows at most ${field.maxItems}`;\n }\n // Surface the first offending row so the author knows WHICH one.\n for (let i = 0; i < value.length; i++) {\n const row = value[i];\n if (!row || typeof row !== \"object\" || Array.isArray(row)) {\n return `${field.label} item ${i + 1} must be an object`;\n }\n for (const sub of field.fields) {\n const cell = (row as Record<string, unknown>)[sub.key];\n if (sub.required && (cell === undefined || cell === null || cell === \"\")) {\n return `${field.label} item ${i + 1}: ${sub.label} is required`;\n }\n if (cell === undefined || cell === null) continue;\n const issue = validateField(sub, cell);\n if (issue) return `${field.label} item ${i + 1}: ${issue}`;\n }\n }\n return null;\n }\n case \"keyvalue\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return `${field.label} must be a key/value map`;\n }\n const allowed = field.valueOptions?.map((o) => o.value);\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (typeof v !== \"string\") return `${field.label}: \"${k}\" must be a string`;\n if (allowed && !allowed.includes(v)) {\n return `${field.label}: \"${k}\" must be one of ${allowed.join(\", \")}`;\n }\n }\n return null;\n }\n case \"document\":\n return null; // opaque to fancy-flow — the host's editor owns its shape\n default:\n return null;\n }\n}\n\n/** Default accents per category. */\nexport function categoryAccent(category: string): string {\n switch (category) {\n case \"trigger\": return \"#10b981\";\n case \"logic\": return \"#f59e0b\";\n case \"data\": return \"#0ea5e9\";\n case \"ai\": return \"#8b5cf6\";\n case \"io\": return \"#3b82f6\";\n case \"human\": return \"#ec4899\";\n case \"output\": return \"#a855f7\";\n default: return \"#71717a\";\n }\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
// src/registry/rich-input.tsx
|
|
4
|
+
var adapter = null;
|
|
5
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
6
|
+
function registerRichInputAdapter(next) {
|
|
7
|
+
adapter = next;
|
|
8
|
+
for (const l of listeners) l();
|
|
9
|
+
return () => {
|
|
10
|
+
if (adapter === next) {
|
|
11
|
+
adapter = null;
|
|
12
|
+
for (const l of listeners) l();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function getRichInputAdapter() {
|
|
17
|
+
return adapter;
|
|
18
|
+
}
|
|
19
|
+
function isRichInputEnabled() {
|
|
20
|
+
return adapter !== null && (adapter.renderDocument !== void 0 || adapter.renderEditor !== void 0);
|
|
21
|
+
}
|
|
22
|
+
function onRichInputAdapterChanged(fn) {
|
|
23
|
+
listeners.add(fn);
|
|
24
|
+
return () => listeners.delete(fn);
|
|
25
|
+
}
|
|
26
|
+
function RichInputPreview({ config }) {
|
|
27
|
+
const a = getRichInputAdapter();
|
|
28
|
+
const title = typeof config.title === "string" && config.title.trim() !== "" ? config.title : "Untitled step";
|
|
29
|
+
const doc = config.document;
|
|
30
|
+
if (!a || !a.renderDocument && !a.renderEditor) {
|
|
31
|
+
return /* @__PURE__ */ jsxs("div", { className: "ff-rich-preview", children: [
|
|
32
|
+
/* @__PURE__ */ jsx("span", { className: "ff-rich-preview__title", children: title }),
|
|
33
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-rich-preview__unavailable", children: [
|
|
34
|
+
"Add ",
|
|
35
|
+
/* @__PURE__ */ jsx("code", { children: "@particle-academy/fancy-cms-ui" }),
|
|
36
|
+
" +",
|
|
37
|
+
" ",
|
|
38
|
+
/* @__PURE__ */ jsx("code", { children: "@particle-academy/react-fancy" }),
|
|
39
|
+
", then",
|
|
40
|
+
" ",
|
|
41
|
+
/* @__PURE__ */ jsx("code", { children: 'import "@particle-academy/fancy-flow/rich-input"' }),
|
|
42
|
+
"."
|
|
43
|
+
] })
|
|
44
|
+
] });
|
|
45
|
+
}
|
|
46
|
+
const body = doc === void 0 || doc === null ? /* @__PURE__ */ jsx("p", { className: "ff-rich-preview__unavailable", children: "Nothing authored yet." }) : a.renderDocument?.(doc) ?? null;
|
|
47
|
+
const Frame = a.FauxClient;
|
|
48
|
+
return /* @__PURE__ */ jsxs("div", { className: "ff-rich-preview", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "ff-rich-preview__title", children: title }),
|
|
50
|
+
/* @__PURE__ */ jsx("div", { className: "ff-rich-preview__frame", children: Frame ? /* @__PURE__ */ jsx(Frame, { ...a.frameProps ?? { variant: "browser" }, children: body }) : body })
|
|
51
|
+
] });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter };
|
|
55
|
+
//# sourceMappingURL=chunk-F5RPRB7A.js.map
|
|
56
|
+
//# sourceMappingURL=chunk-F5RPRB7A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry/rich-input.tsx"],"names":[],"mappings":"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","file":"chunk-F5RPRB7A.js","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Rich user input — the injection point.\n *\n * `rich_user_input` pauses a run on a fully authored page (long-form content,\n * required reading + confirmation, multi-section forms) rather than a flat\n * field list. That page IS a fancy-cms document — fancy-flow defines no\n * document schema of its own and never re-implements one.\n *\n * The wiring ships in the box. Import the subpath once and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * That module registers fancy-cms's `PageDoc` + `CmsPage` renderer + `Editor`\n * against this seam. It lives on a separate entry so `@particle-academy/\n * fancy-cms-ui` and `@particle-academy/react-fancy` stay OPTIONAL peers — most\n * flows never use a rich input, and a workflow editor should not drag a CMS\n * into every install.\n *\n * The seam stays public so a host can substitute a different document engine,\n * but that is the escape hatch, not the expected path.\n *\n * Until something registers, the node still registers and still round-trips\n * its config — it renders a \"how to enable\" body rather than an empty card.\n */\nexport type RichInputAdapter = {\n /**\n * react-fancy's `FauxClient` (or any component with the same shape) — a\n * frame that mimics a browser window or device and scales its content down\n * to fit. Used to preview the authored page inside the node card.\n */\n FauxClient?: ComponentType<any>;\n /**\n * Props for the frame — e.g. `{ variant: \"browser\", width: 1280, scale: \"fit\" }`\n * so a full-width page renders at its real width and scales down into the\n * node card instead of reflowing to a cramped layout that misrepresents it.\n */\n frameProps?: Record<string, unknown>;\n /** Render the stored document read-only, for the in-node preview. */\n renderDocument?: (doc: unknown) => ReactNode;\n /** Editor mounted in the config panel via `renderDocumentField`. */\n renderEditor?: (props: { value: unknown; onChange: (next: unknown) => void }) => ReactNode;\n};\n\nlet adapter: RichInputAdapter | null = null;\nconst listeners = new Set<() => void>();\n\n/** Install the host's document editor + preview frame. Returns an unregister fn. */\nexport function registerRichInputAdapter(next: RichInputAdapter): () => void {\n adapter = next;\n for (const l of listeners) l();\n return () => {\n if (adapter === next) {\n adapter = null;\n for (const l of listeners) l();\n }\n };\n}\n\n/** The registered adapter, or null when the host hasn't wired one. */\nexport function getRichInputAdapter(): RichInputAdapter | null {\n return adapter;\n}\n\n/** True once a host has wired an adapter — i.e. the node is usable. */\nexport function isRichInputEnabled(): boolean {\n return adapter !== null && (adapter.renderDocument !== undefined || adapter.renderEditor !== undefined);\n}\n\n/** Subscribe to adapter changes (so nodes re-render when it lands). */\nexport function onRichInputAdapterChanged(fn: () => void): () => void {\n listeners.add(fn);\n return () => listeners.delete(fn);\n}\n\n/**\n * RichInputPreview — the node card body. Shows the authored page inside a\n * FauxClient frame so an author can see, at a glance on the canvas, what the\n * person hitting this step will be looking at.\n *\n * Degrades in two steps rather than one: no adapter at all → install hint;\n * adapter but nothing authored yet → an empty frame with the step title. A\n * blank node body would read as \"broken\" in both cases.\n */\nexport function RichInputPreview({ config }: { config: Record<string, unknown> }) {\n const a = getRichInputAdapter();\n const title = typeof config.title === \"string\" && config.title.trim() !== \"\" ? config.title : \"Untitled step\";\n const doc = config.document;\n\n if (!a || (!a.renderDocument && !a.renderEditor)) {\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__unavailable\">\n Add <code>@particle-academy/fancy-cms-ui</code> +{\" \"}\n <code>@particle-academy/react-fancy</code>, then{\" \"}\n <code>import \"@particle-academy/fancy-flow/rich-input\"</code>.\n </div>\n </div>\n );\n }\n\n const body = doc === undefined || doc === null\n ? <p className=\"ff-rich-preview__unavailable\">Nothing authored yet.</p>\n : a.renderDocument?.(doc) ?? null;\n\n const Frame = a.FauxClient;\n\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__frame\">\n {Frame ? <Frame {...(a.frameProps ?? { variant: \"browser\" })}>{body}</Frame> : body}\n </div>\n </div>\n );\n}\n"]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { resolveNodePorts } from './chunk-TITD5W4Y.js';
|
|
2
|
+
import { getNodeKind } from './chunk-CPSOC27D.js';
|
|
3
|
+
|
|
1
4
|
// src/runtime/run-flow.ts
|
|
2
5
|
async function runFlow(graph, executors, onEvent = () => {
|
|
3
6
|
}, options = {}) {
|
|
@@ -129,10 +132,11 @@ function activatedPorts(node, result) {
|
|
|
129
132
|
return { ports: [r.branch], value: r.value ?? r };
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
|
-
const
|
|
133
|
-
|
|
135
|
+
const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
|
|
136
|
+
const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);
|
|
137
|
+
return { ports: declared?.length ? declared : ["out"], value: result };
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
export { runFlow };
|
|
137
|
-
//# sourceMappingURL=chunk-
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
141
|
+
//# sourceMappingURL=chunk-HNBO4HP3.js.map
|
|
142
|
+
//# sourceMappingURL=chunk-HNBO4HP3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/runtime/run-flow.ts"],"names":[],"mappings":"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","file":"chunk-HNBO4HP3.js","sourcesContent":["import type {\n ExecutorRegistry,\n FlowEdge,\n FlowGraph,\n FlowNode,\n NodeExecutor,\n RunEvent,\n} from \"../types\";\n// Both modules are React-free by design — the `/engine` entry must not pull in\n// React. Import them directly rather than via the `registry` barrel, which\n// re-exports the RegistryNode component.\nimport { getNodeKind } from \"../registry/registry\";\nimport { resolveNodePorts } from \"../registry/ports\";\n\nexport type RunOptions = {\n /** Stop the run after this many ms. Default: no timeout. */\n timeoutMs?: number;\n /** Abort signal — host can cancel the run. */\n signal?: AbortSignal;\n /** Initial inputs supplied to entry-point nodes (no incoming edges). */\n initialInputs?: Record<string, Record<string, unknown>>;\n};\n\nexport type RunResult = {\n ok: boolean;\n /** Outputs collected per node, keyed by node id. */\n outputs: Record<string, unknown>;\n /** Error captured if any node threw. */\n error?: string;\n};\n\n/**\n * runFlow — topological execution of a FlowGraph against an ExecutorRegistry.\n *\n * Each node runs once, when all upstream nodes have produced outputs on the\n * connected ports. Decision nodes (or any executor that returns `{ branch:\n * 'true' }`) can short-circuit specific output ports — only edges leaving\n * an \"active\" port propagate to downstream nodes.\n *\n * Cycles are detected and abort the run with an error.\n *\n * The `onEvent` callback receives a stream of `RunEvent`s — wire it to a\n * status feed, log panel, or store.\n */\nexport async function runFlow(\n graph: FlowGraph,\n executors: ExecutorRegistry,\n onEvent: (event: RunEvent) => void = () => {},\n options: RunOptions = {},\n): Promise<RunResult> {\n const { signal, initialInputs = {}, timeoutMs } = options;\n const outputs: Record<string, unknown> = {};\n const portValues = new Map<string, unknown>(); // key: `${nodeId}:${portId}`\n const completed = new Set<string>();\n const errors: string[] = [];\n\n // Topological order via Kahn's algorithm. We allow nodes to run as soon\n // as their incoming edges' source ports have produced values, so the\n // order here is just a deterministic baseline used for cycle detection.\n const order = topoSort(graph);\n if (order === null) {\n const msg = \"Cycle detected in flow graph — aborting.\";\n onEvent({ type: \"run-error\", error: msg });\n return { ok: false, outputs, error: msg };\n }\n\n const incomingByNode = indexIncoming(graph.edges);\n const timer = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;\n\n onEvent({ type: \"run-start\" });\n\n try {\n for (const node of order) {\n if (signal?.aborted) throw new Error(\"aborted\");\n if (errors.length) break;\n\n const incoming = incomingByNode.get(node.id) ?? [];\n\n // Run a node once any upstream branch reaches it. We iterate in\n // topological order, so by the time we reach this node every upstream\n // node has been processed — each incoming edge is therefore *settled*\n // (active or dead, never still-pending). Requiring ALL incoming edges to\n // be active wrongly skipped MERGE POINTS: when a Decision routes down one\n // branch, the other branch's edge stays dead forever, so an `every` check\n // skipped the shared continuation node and halted the run after the first\n // branch (#1). Run when AT LEAST ONE incoming edge is active —\n // collectInputs() only reads from the active ones. A genuine parallel\n // join still works: in topo order both of its inputs are already active.\n if (incoming.length > 0) {\n const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? \"out\"}`));\n if (!anyActive) {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"skipped\" });\n continue;\n }\n }\n\n // Note nodes are annotations — never executed.\n if (node.type === \"note\") {\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"idle\", text: \"annotation\" });\n continue;\n }\n\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"running\" });\n\n const inputs = collectInputs(node, incoming, portValues, initialInputs);\n const exec = pickExecutor(executors, node);\n if (!exec) {\n const msg = `No executor registered for kind=${node.type}`;\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n\n try {\n const result = await Promise.resolve(\n exec({\n node,\n inputs,\n abort: (reason) => { throw new Error(reason ?? \"aborted\"); },\n emit: onEvent,\n }),\n );\n outputs[node.id] = result;\n\n // Decide which output ports were activated. Three conventions:\n // 1) If result is `{ __port: \"out\", value: x }`, only that port emits.\n // 2) If result has `branch: <portId>`, only that port emits (decision sugar).\n // 3) Otherwise, the value is published on every declared output port.\n const activated = activatedPorts(node, result);\n for (const portId of activated.ports) {\n portValues.set(`${node.id}:${portId}`, activated.value);\n onEvent({ type: \"node-output\", nodeId: node.id, portId, value: activated.value });\n }\n completed.add(node.id);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"done\" });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n errors.push(msg);\n onEvent({ type: \"node-status\", nodeId: node.id, status: \"error\", text: msg });\n onEvent({ type: \"log\", nodeId: node.id, level: \"error\", message: msg });\n break;\n }\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const ok = errors.length === 0;\n onEvent({ type: \"run-end\", ok });\n return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };\n}\n\nfunction indexIncoming(edges: FlowEdge[]): Map<string, FlowEdge[]> {\n const map = new Map<string, FlowEdge[]>();\n for (const e of edges) {\n const list = map.get(e.target) ?? [];\n list.push(e);\n map.set(e.target, list);\n }\n return map;\n}\n\nfunction topoSort(graph: FlowGraph): FlowNode[] | null {\n const inDegree = new Map<string, number>();\n for (const n of graph.nodes) inDegree.set(n.id, 0);\n for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);\n const queue: string[] = [];\n for (const [id, d] of inDegree) if (d === 0) queue.push(id);\n const ordered: string[] = [];\n while (queue.length) {\n const id = queue.shift()!;\n ordered.push(id);\n for (const e of graph.edges) {\n if (e.source !== id) continue;\n const next = (inDegree.get(e.target) ?? 0) - 1;\n inDegree.set(e.target, next);\n if (next === 0) queue.push(e.target);\n }\n }\n if (ordered.length !== graph.nodes.length) return null;\n const byId = new Map(graph.nodes.map((n) => [n.id, n]));\n return ordered.map((id) => byId.get(id)!).filter(Boolean);\n}\n\nfunction collectInputs(\n node: FlowNode,\n incoming: FlowEdge[],\n portValues: Map<string, unknown>,\n initial: Record<string, Record<string, unknown>>,\n): Record<string, unknown> {\n const inputs: Record<string, unknown> = { ...(initial[node.id] ?? {}) };\n for (const e of incoming) {\n const portId = e.targetHandle ?? \"in\";\n const val = portValues.get(`${e.source}:${e.sourceHandle ?? \"out\"}`);\n inputs[portId] = val;\n }\n return inputs;\n}\n\nfunction pickExecutor(\n executors: ExecutorRegistry,\n node: FlowNode,\n): NodeExecutor | undefined {\n if (executors[node.id]) return executors[node.id];\n if (node.type && executors[node.type]) return executors[node.type];\n return executors[\"*\"];\n}\n\nfunction activatedPorts(node: FlowNode, result: unknown): { ports: string[]; value: unknown } {\n if (result && typeof result === \"object\") {\n const r = result as Record<string, unknown>;\n if (typeof r.__port === \"string\") {\n return { ports: [r.__port], value: r.value };\n }\n if (typeof r.branch === \"string\") {\n return { ports: [r.branch], value: r.value ?? r };\n }\n }\n // Resolve through the shared helper so the ports the runtime activates are\n // the same ones the canvas drew — including config-driven ports, which the\n // node's `data` does not carry. Falls back to a lone `out` when a node\n // declares nothing.\n const kind = getNodeKind((node.data as any)?.kind ?? node.type ?? \"\") ?? undefined;\n const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);\n return { ports: declared?.length ? declared : [\"out\"], value: result };\n}\n"]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Handle, Position } from './chunk-NF6NPY5N.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { resolveNodePorts, nodeConfig } from './chunk-TITD5W4Y.js';
|
|
3
|
+
import { getNodeKind, categoryAccent, registerNodeKind, listNodeKinds } from './chunk-CPSOC27D.js';
|
|
4
|
+
import { RichInputPreview } from './chunk-F5RPRB7A.js';
|
|
5
|
+
import { memo, useMemo, createElement } from 'react';
|
|
4
6
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
7
|
|
|
6
8
|
function RegistryNodeInner(props) {
|
|
@@ -22,9 +24,10 @@ function RegistryNodeInner(props) {
|
|
|
22
24
|
const data = props.data;
|
|
23
25
|
const status = data.status ?? "idle";
|
|
24
26
|
const accent = kind.accent ?? categoryAccent(kind.category);
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
27
|
+
const resolved = resolveNodePorts(props, kind);
|
|
28
|
+
const inputs = resolved.inputs ?? defaultInputs(kind.category);
|
|
29
|
+
const outputs = resolved.outputs ?? defaultOutputs(kind.category);
|
|
30
|
+
const config = nodeConfig(props);
|
|
28
31
|
const label = data.label ?? kind.label;
|
|
29
32
|
return /* @__PURE__ */ jsxs(
|
|
30
33
|
"div",
|
|
@@ -115,8 +118,39 @@ function previewValue(v) {
|
|
|
115
118
|
return "[object]";
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
function casePorts(cases) {
|
|
122
|
+
const byPort = /* @__PURE__ */ new Map();
|
|
123
|
+
if (cases && typeof cases === "object" && !Array.isArray(cases)) {
|
|
124
|
+
for (const [match, port] of Object.entries(cases)) {
|
|
125
|
+
if (typeof port !== "string" || port === "" || port === "default") continue;
|
|
126
|
+
const matches = byPort.get(port) ?? [];
|
|
127
|
+
matches.push(match);
|
|
128
|
+
byPort.set(port, matches);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const ports = [...byPort].map(([id, matches]) => ({
|
|
132
|
+
id,
|
|
133
|
+
label: matches.join("|")
|
|
134
|
+
}));
|
|
135
|
+
return [...ports, { id: "default", label: "default" }];
|
|
136
|
+
}
|
|
137
|
+
function routePorts(routes, fallback) {
|
|
138
|
+
const ports = [];
|
|
139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
140
|
+
if (Array.isArray(routes)) {
|
|
141
|
+
for (const route of routes) {
|
|
142
|
+
const id = route?.port;
|
|
143
|
+
if (typeof id !== "string" || id.trim() === "" || seen.has(id)) continue;
|
|
144
|
+
seen.add(id);
|
|
145
|
+
ports.push({ id, label: id });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (fallback !== false && !seen.has("fallback")) {
|
|
149
|
+
ports.push({ id: "fallback", label: "fallback" });
|
|
150
|
+
}
|
|
151
|
+
if (ports.length === 0) ports.push({ id: "out" });
|
|
152
|
+
return ports;
|
|
153
|
+
}
|
|
120
154
|
var HTTP_METHODS = [
|
|
121
155
|
{ type: "select", key: "method", label: "Method", options: [
|
|
122
156
|
{ value: "GET", label: "GET" },
|
|
@@ -185,15 +219,59 @@ var KINDS = [
|
|
|
185
219
|
configSchema: [
|
|
186
220
|
{ type: "text", key: "title", label: "Form title", default: "Need your input" },
|
|
187
221
|
{
|
|
188
|
-
type: "
|
|
222
|
+
type: "repeater",
|
|
189
223
|
key: "fields",
|
|
190
|
-
label: "Fields
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
label: "Fields",
|
|
225
|
+
description: "The form the run pauses on.",
|
|
226
|
+
titleKey: "label",
|
|
227
|
+
addLabel: "Add field",
|
|
228
|
+
minItems: 1,
|
|
229
|
+
fields: [
|
|
230
|
+
{ type: "text", key: "key", label: "Key", required: true, placeholder: "answer" },
|
|
231
|
+
{ type: "text", key: "label", label: "Label", required: true, placeholder: "Your answer" },
|
|
232
|
+
{
|
|
233
|
+
type: "select",
|
|
234
|
+
key: "type",
|
|
235
|
+
label: "Type",
|
|
236
|
+
default: "text",
|
|
237
|
+
options: [
|
|
238
|
+
{ value: "text", label: "Text" },
|
|
239
|
+
{ value: "textarea", label: "Long text" },
|
|
240
|
+
{ value: "number", label: "Number" },
|
|
241
|
+
{ value: "select", label: "Select" },
|
|
242
|
+
{ value: "switch", label: "Switch" }
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{ type: "switch", key: "required", label: "Required", default: false }
|
|
246
|
+
],
|
|
247
|
+
default: [{ key: "answer", label: "Your answer", type: "textarea", required: true }]
|
|
194
248
|
}
|
|
195
249
|
]
|
|
196
250
|
},
|
|
251
|
+
{
|
|
252
|
+
name: "rich_user_input",
|
|
253
|
+
category: "human",
|
|
254
|
+
label: "Rich User Input",
|
|
255
|
+
description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
|
|
256
|
+
icon: "\u25A4",
|
|
257
|
+
inputs: [{ id: "in" }],
|
|
258
|
+
outputs: [{ id: "out", label: "values" }],
|
|
259
|
+
configSchema: [
|
|
260
|
+
{ type: "text", key: "title", label: "Step title", default: "Please review" },
|
|
261
|
+
{
|
|
262
|
+
type: "document",
|
|
263
|
+
key: "document",
|
|
264
|
+
label: "Page content",
|
|
265
|
+
documentType: "stages",
|
|
266
|
+
description: "Authored with the host's document editor (fancy-cms Stages)."
|
|
267
|
+
},
|
|
268
|
+
{ type: "switch", key: "requireConfirm", label: "Require explicit confirmation", default: true },
|
|
269
|
+
{ type: "text", key: "submitLabel", label: "Submit button", default: "Continue" }
|
|
270
|
+
],
|
|
271
|
+
// Preview the authored page inside a FauxClient frame, so the canvas shows
|
|
272
|
+
// what the person hitting this step will actually see.
|
|
273
|
+
renderBody: (ctx) => createElement(RichInputPreview, { config: ctx.config ?? {} })
|
|
274
|
+
},
|
|
197
275
|
// ───────────── Logic ─────────────
|
|
198
276
|
{
|
|
199
277
|
name: "branch",
|
|
@@ -214,10 +292,24 @@ var KINDS = [
|
|
|
214
292
|
description: "Route to one of N labelled outputs based on a key.",
|
|
215
293
|
icon: "\u2933",
|
|
216
294
|
inputs: [{ id: "in" }],
|
|
217
|
-
|
|
295
|
+
// Ports ARE the config: every distinct port a case routes to becomes an
|
|
296
|
+
// output handle, plus the always-present `default`. Editing the cases map
|
|
297
|
+
// moves the ports on the canvas and the ports the runtime activates.
|
|
298
|
+
outputs: (config) => casePorts(config?.cases),
|
|
218
299
|
configSchema: [
|
|
219
300
|
{ type: "expression", key: "value", label: "Switch on", example: "{{ $json.kind }}", required: true },
|
|
220
|
-
{
|
|
301
|
+
{
|
|
302
|
+
type: "keyvalue",
|
|
303
|
+
key: "cases",
|
|
304
|
+
label: "Cases",
|
|
305
|
+
description: "Match value \u2192 output port. Unmatched input takes `default`.",
|
|
306
|
+
keyLabel: "When value is",
|
|
307
|
+
valueLabel: "Route to port",
|
|
308
|
+
keyPlaceholder: "a",
|
|
309
|
+
valuePlaceholder: "case_a",
|
|
310
|
+
addLabel: "Add case",
|
|
311
|
+
default: { a: "case_a", b: "case_b" }
|
|
312
|
+
}
|
|
221
313
|
]
|
|
222
314
|
},
|
|
223
315
|
{
|
|
@@ -372,6 +464,76 @@ var KINDS = [
|
|
|
372
464
|
{ type: "credential", key: "credential", label: "API credential", credentialType: "llm_credential" }
|
|
373
465
|
]
|
|
374
466
|
},
|
|
467
|
+
{
|
|
468
|
+
name: "llm_branch",
|
|
469
|
+
category: "ai",
|
|
470
|
+
label: "LLM Router",
|
|
471
|
+
description: "Let a model choose which route the flow takes.",
|
|
472
|
+
icon: "\u2727",
|
|
473
|
+
inputs: [{ id: "in" }],
|
|
474
|
+
// Each declared route is a port. The executor returns `{ __port: id }`
|
|
475
|
+
// (or `Port.only(id)` on the PHP runtime) to pick one.
|
|
476
|
+
outputs: (config) => routePorts(config?.routes, config?.fallback),
|
|
477
|
+
configSchema: [
|
|
478
|
+
{
|
|
479
|
+
type: "textarea",
|
|
480
|
+
key: "system",
|
|
481
|
+
label: "System prompt",
|
|
482
|
+
rows: 3,
|
|
483
|
+
description: "Optional framing for the routing decision."
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
type: "expression",
|
|
487
|
+
key: "prompt",
|
|
488
|
+
label: "What to route on",
|
|
489
|
+
example: "{{ $json.message }}",
|
|
490
|
+
required: true
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
type: "repeater",
|
|
494
|
+
key: "routes",
|
|
495
|
+
label: "Routes",
|
|
496
|
+
description: "The model picks exactly one. Descriptions are what it chooses between \u2014 make them distinct.",
|
|
497
|
+
titleKey: "port",
|
|
498
|
+
addLabel: "Add route",
|
|
499
|
+
minItems: 2,
|
|
500
|
+
fields: [
|
|
501
|
+
{ type: "text", key: "port", label: "Port", required: true, placeholder: "billing" },
|
|
502
|
+
{
|
|
503
|
+
type: "text",
|
|
504
|
+
key: "description",
|
|
505
|
+
label: "When to choose it",
|
|
506
|
+
required: true,
|
|
507
|
+
placeholder: "The user is asking about an invoice, refund, or payment."
|
|
508
|
+
}
|
|
509
|
+
],
|
|
510
|
+
default: [
|
|
511
|
+
{ port: "a", description: "Describe when the model should pick this route." },
|
|
512
|
+
{ port: "b", description: "Describe when the model should pick this route." }
|
|
513
|
+
]
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
type: "select",
|
|
517
|
+
key: "provider",
|
|
518
|
+
label: "Provider",
|
|
519
|
+
default: "anthropic",
|
|
520
|
+
options: [
|
|
521
|
+
{ value: "anthropic", label: "Anthropic" },
|
|
522
|
+
{ value: "openai", label: "OpenAI" },
|
|
523
|
+
{ value: "custom", label: "Custom" }
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
{ type: "text", key: "model", label: "Model", placeholder: "claude-sonnet-4-5" },
|
|
527
|
+
{
|
|
528
|
+
type: "switch",
|
|
529
|
+
key: "fallback",
|
|
530
|
+
label: "Add a `fallback` port",
|
|
531
|
+
default: true,
|
|
532
|
+
description: "Where the flow goes if the model returns no usable route."
|
|
533
|
+
},
|
|
534
|
+
{ type: "credential", key: "credential", label: "API credential", credentialType: "llm_credential" }
|
|
535
|
+
]
|
|
536
|
+
},
|
|
375
537
|
{
|
|
376
538
|
name: "tool_use",
|
|
377
539
|
category: "ai",
|
|
@@ -503,5 +665,5 @@ function buildNodeTypes() {
|
|
|
503
665
|
}
|
|
504
666
|
|
|
505
667
|
export { BUILTIN_KINDS, RegistryNode, buildNodeTypes, registerBuiltinKinds };
|
|
506
|
-
//# sourceMappingURL=chunk-
|
|
507
|
-
//# sourceMappingURL=chunk-
|
|
668
|
+
//# sourceMappingURL=chunk-NCPQDVUE.js.map
|
|
669
|
+
//# sourceMappingURL=chunk-NCPQDVUE.js.map
|