@particle-academy/fancy-flow 0.25.0 → 0.26.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/dist/{chunk-ATQXZEVD.js → chunk-YOFCVO2W.js} +3 -2
- package/dist/chunk-YOFCVO2W.js.map +1 -0
- package/dist/index.cjs +170 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +172 -6
- package/dist/index.js.map +1 -1
- package/dist/registry.cjs +1 -0
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/styles.css +138 -0
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-ATQXZEVD.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useFlowState, useFlowRun, useFlowHistory, applyOutputsToNodes, applyStatusesToNodes } from './chunk-OFW7GN3Y.js';
|
|
2
2
|
export { applyOutputsToNodes, applyStatusesToNodes, createHistory, useFlowHistory, useFlowRun, useFlowState } from './chunk-OFW7GN3Y.js';
|
|
3
|
-
import { buildNodeTypes, FlowEditorProvider, registerBuiltinKinds, NoteNode, createConnectionValidator } from './chunk-
|
|
4
|
-
export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-
|
|
3
|
+
import { buildNodeTypes, FlowEditorProvider, registerBuiltinKinds, NoteNode, createConnectionValidator } from './chunk-YOFCVO2W.js';
|
|
4
|
+
export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-YOFCVO2W.js';
|
|
5
5
|
import { ReactFlowProvider, useReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, Position, Handle, reconnectEdge, index, BackgroundVariant, Background, Controls, MiniMap, ViewportPortal } from './chunk-OWENS2H5.js';
|
|
6
6
|
export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
|
|
7
7
|
import './chunk-USL4FMFU.js';
|
|
@@ -13,7 +13,7 @@ import { onNodeKindsChanged, listNodeKinds, getNodeKind, defaultConfigFor, valid
|
|
|
13
13
|
export { categoryAccent, defaultConfigFor, getNodeKind, listNodeKinds, onNodeKindsChanged, registerNodeKind, validateConfig } from './chunk-Q6IPK64P.js';
|
|
14
14
|
import { getRichInputAdapter } from './chunk-F5RPRB7A.js';
|
|
15
15
|
export { RichInputPreview, getRichInputAdapter, isRichInputEnabled, onRichInputAdapterChanged, registerRichInputAdapter } from './chunk-F5RPRB7A.js';
|
|
16
|
-
import { memo, forwardRef, useState,
|
|
16
|
+
import { memo, forwardRef, useState, useMemo, useEffect, useCallback, useRef, useImperativeHandle } from 'react';
|
|
17
17
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
18
18
|
|
|
19
19
|
function NodeShellInner({
|
|
@@ -1062,6 +1062,133 @@ function formatTime(at) {
|
|
|
1062
1062
|
const d = new Date(at);
|
|
1063
1063
|
return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${Math.floor(d.getMilliseconds() / 100)}`;
|
|
1064
1064
|
}
|
|
1065
|
+
function humanInputFields(config) {
|
|
1066
|
+
const raw = Array.isArray(config?.fields) ? config.fields : [];
|
|
1067
|
+
const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
|
|
1068
|
+
key: f.key,
|
|
1069
|
+
label: typeof f.label === "string" && f.label ? f.label : f.key,
|
|
1070
|
+
type: ["text", "textarea", "number", "select", "switch"].includes(f.type) ? f.type : "text",
|
|
1071
|
+
required: !!f.required,
|
|
1072
|
+
placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
|
|
1073
|
+
options: Array.isArray(f.options) ? f.options : void 0,
|
|
1074
|
+
default: f.default
|
|
1075
|
+
}));
|
|
1076
|
+
if (fields.length) return fields;
|
|
1077
|
+
const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
|
|
1078
|
+
return [{ key: "value", label: title, type: "textarea", required: true }];
|
|
1079
|
+
}
|
|
1080
|
+
function initialValues(fields) {
|
|
1081
|
+
const v = {};
|
|
1082
|
+
for (const f of fields) v[f.key] = f.type === "switch" ? !!f.default : f.default ?? "";
|
|
1083
|
+
return v;
|
|
1084
|
+
}
|
|
1085
|
+
function HumanPrompt({ request, onCancel }) {
|
|
1086
|
+
const firstRef = useRef(null);
|
|
1087
|
+
useEffect(() => {
|
|
1088
|
+
firstRef.current?.focus();
|
|
1089
|
+
const onKey = (e) => {
|
|
1090
|
+
if (e.key === "Escape") onCancel();
|
|
1091
|
+
};
|
|
1092
|
+
window.addEventListener("keydown", onKey);
|
|
1093
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1094
|
+
}, [onCancel]);
|
|
1095
|
+
return /* @__PURE__ */ jsx("div", { className: "ff-prompt-overlay", role: "dialog", "aria-modal": "true", "aria-label": request.title, children: /* @__PURE__ */ jsxs("div", { className: "ff-prompt", onClick: (e) => e.stopPropagation(), children: [
|
|
1096
|
+
/* @__PURE__ */ jsx("div", { className: "ff-prompt__title", children: request.title }),
|
|
1097
|
+
request.kind === "approval" ? /* @__PURE__ */ jsx(ApprovalBody, { request, onCancel }) : /* @__PURE__ */ jsx(InputBody, { request, onCancel, firstRef })
|
|
1098
|
+
] }) });
|
|
1099
|
+
}
|
|
1100
|
+
function ApprovalBody({ request, onCancel }) {
|
|
1101
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1102
|
+
request.description && /* @__PURE__ */ jsx("p", { className: "ff-prompt__desc", children: request.description }),
|
|
1103
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-prompt__actions", children: [
|
|
1104
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
|
|
1105
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--danger", onClick: () => request.resolve(false), children: "Deny" }),
|
|
1106
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--primary", onClick: () => request.resolve(true), children: "Approve" })
|
|
1107
|
+
] })
|
|
1108
|
+
] });
|
|
1109
|
+
}
|
|
1110
|
+
function InputBody({
|
|
1111
|
+
request,
|
|
1112
|
+
onCancel,
|
|
1113
|
+
firstRef
|
|
1114
|
+
}) {
|
|
1115
|
+
const [values, setValues] = useState(() => initialValues(request.fields));
|
|
1116
|
+
const set = (k, v) => setValues((prev) => ({ ...prev, [k]: v }));
|
|
1117
|
+
const missing = request.fields.filter((f) => f.required && (values[f.key] === void 0 || values[f.key] === ""));
|
|
1118
|
+
const submit = () => {
|
|
1119
|
+
if (missing.length === 0) request.resolve(values);
|
|
1120
|
+
};
|
|
1121
|
+
return /* @__PURE__ */ jsxs(
|
|
1122
|
+
"form",
|
|
1123
|
+
{
|
|
1124
|
+
onSubmit: (e) => {
|
|
1125
|
+
e.preventDefault();
|
|
1126
|
+
submit();
|
|
1127
|
+
},
|
|
1128
|
+
onKeyDown: (e) => {
|
|
1129
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
1130
|
+
e.preventDefault();
|
|
1131
|
+
submit();
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
children: [
|
|
1135
|
+
/* @__PURE__ */ jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxs("label", { className: "ff-prompt__field", children: [
|
|
1136
|
+
/* @__PURE__ */ jsxs("span", { className: "ff-prompt__label", children: [
|
|
1137
|
+
f.label ?? f.key,
|
|
1138
|
+
f.required && /* @__PURE__ */ jsx("span", { className: "ff-prompt__req", children: " *" })
|
|
1139
|
+
] }),
|
|
1140
|
+
renderControl(f, values[f.key], (v) => set(f.key, v), i === 0 ? firstRef : void 0)
|
|
1141
|
+
] }, f.key)) }),
|
|
1142
|
+
/* @__PURE__ */ jsxs("div", { className: "ff-prompt__actions", children: [
|
|
1143
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
|
|
1144
|
+
/* @__PURE__ */ jsx("button", { type: "submit", className: "ff-prompt__btn ff-prompt__btn--primary", disabled: missing.length > 0, children: request.submitLabel || "Continue" })
|
|
1145
|
+
] })
|
|
1146
|
+
]
|
|
1147
|
+
}
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
function renderControl(f, value, onChange, ref) {
|
|
1151
|
+
const common = { className: "ff-prompt__input", placeholder: f.placeholder };
|
|
1152
|
+
if (f.type === "textarea") {
|
|
1153
|
+
return /* @__PURE__ */ jsx(
|
|
1154
|
+
"textarea",
|
|
1155
|
+
{
|
|
1156
|
+
...common,
|
|
1157
|
+
ref,
|
|
1158
|
+
rows: 3,
|
|
1159
|
+
value: String(value ?? ""),
|
|
1160
|
+
onChange: (e) => onChange(e.target.value)
|
|
1161
|
+
}
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
if (f.type === "switch") {
|
|
1165
|
+
return /* @__PURE__ */ jsx(
|
|
1166
|
+
"input",
|
|
1167
|
+
{
|
|
1168
|
+
type: "checkbox",
|
|
1169
|
+
className: "ff-prompt__switch",
|
|
1170
|
+
checked: !!value,
|
|
1171
|
+
onChange: (e) => onChange(e.target.checked)
|
|
1172
|
+
}
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
if (f.type === "select" && f.options && f.options.length) {
|
|
1176
|
+
return /* @__PURE__ */ jsxs("select", { className: "ff-prompt__input", value: String(value ?? ""), onChange: (e) => onChange(e.target.value), children: [
|
|
1177
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Choose\u2026" }),
|
|
1178
|
+
f.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
|
|
1179
|
+
] });
|
|
1180
|
+
}
|
|
1181
|
+
return /* @__PURE__ */ jsx(
|
|
1182
|
+
"input",
|
|
1183
|
+
{
|
|
1184
|
+
...common,
|
|
1185
|
+
ref,
|
|
1186
|
+
type: f.type === "number" ? "number" : "text",
|
|
1187
|
+
value: String(value ?? ""),
|
|
1188
|
+
onChange: (e) => onChange(f.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
|
|
1189
|
+
}
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1065
1192
|
var FlowEditor = forwardRef(function FlowEditor2(props, ref) {
|
|
1066
1193
|
return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(
|
|
1067
1194
|
"div",
|
|
@@ -1095,6 +1222,41 @@ function FlowEditorInner({
|
|
|
1095
1222
|
const internal = useFlowState(initial);
|
|
1096
1223
|
const runner = useFlowRun();
|
|
1097
1224
|
const rf = useReactFlow();
|
|
1225
|
+
const [prompt, setPrompt] = useState(null);
|
|
1226
|
+
const humanExecutors = useMemo(
|
|
1227
|
+
() => ({
|
|
1228
|
+
user_input: ({ node }) => new Promise((resolve) => {
|
|
1229
|
+
const cfg = node.data?.config ?? {};
|
|
1230
|
+
setPrompt({
|
|
1231
|
+
kind: "input",
|
|
1232
|
+
title: typeof cfg.title === "string" && cfg.title || "Your input",
|
|
1233
|
+
submitLabel: typeof cfg.submitLabel === "string" ? cfg.submitLabel : void 0,
|
|
1234
|
+
fields: humanInputFields(cfg),
|
|
1235
|
+
resolve: (values) => {
|
|
1236
|
+
setPrompt(null);
|
|
1237
|
+
resolve(values);
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
}),
|
|
1241
|
+
human_approval: ({ node }) => new Promise((resolve) => {
|
|
1242
|
+
const cfg = node.data?.config ?? {};
|
|
1243
|
+
setPrompt({
|
|
1244
|
+
kind: "approval",
|
|
1245
|
+
title: typeof cfg.title === "string" && cfg.title || "Approve action",
|
|
1246
|
+
description: typeof cfg.description === "string" ? cfg.description : void 0,
|
|
1247
|
+
resolve: (approved) => {
|
|
1248
|
+
setPrompt(null);
|
|
1249
|
+
resolve({ branch: approved ? "approved" : "denied" });
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
})
|
|
1253
|
+
}),
|
|
1254
|
+
[]
|
|
1255
|
+
);
|
|
1256
|
+
const runExecutors = useMemo(() => ({ ...humanExecutors, ...executors }), [humanExecutors, executors]);
|
|
1257
|
+
useEffect(() => {
|
|
1258
|
+
if (!runner.running) setPrompt(null);
|
|
1259
|
+
}, [runner.running]);
|
|
1098
1260
|
const controlled = value !== void 0;
|
|
1099
1261
|
const baseFlow = controlled ? makeControlledFlowAdapter(value, onChange) : internal;
|
|
1100
1262
|
const hist = useFlowHistory(baseFlow);
|
|
@@ -1404,7 +1566,7 @@ function FlowEditorInner({
|
|
|
1404
1566
|
({ autoLayout }) => flow.setGraph({ nodes: autoLayout({ nodes: flow.nodes, edges: flow.edges }, { scope: laneId }), edges: flow.edges })
|
|
1405
1567
|
);
|
|
1406
1568
|
},
|
|
1407
|
-
run: () => runner.run({ nodes: flow.nodes, edges: flow.edges },
|
|
1569
|
+
run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, runExecutors),
|
|
1408
1570
|
cancel: runner.cancel,
|
|
1409
1571
|
reset: runner.reset,
|
|
1410
1572
|
toWorkflow,
|
|
@@ -1416,7 +1578,7 @@ function FlowEditorInner({
|
|
|
1416
1578
|
canUndo: hist.canUndo,
|
|
1417
1579
|
canRedo: hist.canRedo
|
|
1418
1580
|
};
|
|
1419
|
-
}, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner,
|
|
1581
|
+
}, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, runExecutors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, addLane, hist, rf]);
|
|
1420
1582
|
useImperativeHandle(apiRef, () => api, [api]);
|
|
1421
1583
|
const dropHandlers = paletteDropHandlers((kindName, evt) => {
|
|
1422
1584
|
const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
|
|
@@ -1590,7 +1752,11 @@ function FlowEditorInner({
|
|
|
1590
1752
|
onCancel: () => setLabelEdit(null)
|
|
1591
1753
|
}
|
|
1592
1754
|
),
|
|
1593
|
-
showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" }))
|
|
1755
|
+
showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" })),
|
|
1756
|
+
prompt && /* @__PURE__ */ jsx(HumanPrompt, { request: prompt, onCancel: () => {
|
|
1757
|
+
setPrompt(null);
|
|
1758
|
+
runner.cancel();
|
|
1759
|
+
} })
|
|
1594
1760
|
] }),
|
|
1595
1761
|
showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxs("div", { className: "ff-editor__panel-wrap", children: [
|
|
1596
1762
|
/* @__PURE__ */ jsx(
|