@particle-academy/fancy-flow 0.51.1 → 0.53.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.
Files changed (39) hide show
  1. package/dist/{chunk-4GO2CHAY.js → chunk-CIBW5ATA.js} +166 -25
  2. package/dist/chunk-CIBW5ATA.js.map +1 -0
  3. package/dist/{chunk-OK7KJLWA.js → chunk-FG3C7LIG.js} +3 -3
  4. package/dist/{chunk-OK7KJLWA.js.map → chunk-FG3C7LIG.js.map} +1 -1
  5. package/dist/{chunk-SPINP4EJ.js → chunk-FH6MWLKD.js} +3 -3
  6. package/dist/{chunk-SPINP4EJ.js.map → chunk-FH6MWLKD.js.map} +1 -1
  7. package/dist/{chunk-OECW2E5D.js → chunk-FTGVVKWX.js} +4 -4
  8. package/dist/{chunk-OECW2E5D.js.map → chunk-FTGVVKWX.js.map} +1 -1
  9. package/dist/{chunk-RSRXKZ52.js → chunk-VWANLNL5.js} +3 -3
  10. package/dist/{chunk-RSRXKZ52.js.map → chunk-VWANLNL5.js.map} +1 -1
  11. package/dist/{chunk-VUU3CDBT.js → chunk-WCFRKXH5.js} +3 -3
  12. package/dist/{chunk-VUU3CDBT.js.map → chunk-WCFRKXH5.js.map} +1 -1
  13. package/dist/durable.cjs +121 -22
  14. package/dist/durable.cjs.map +1 -1
  15. package/dist/durable.js +1 -1
  16. package/dist/engine.cjs +122 -23
  17. package/dist/engine.cjs.map +1 -1
  18. package/dist/engine.js +3 -3
  19. package/dist/index.cjs +236 -57
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +84 -46
  22. package/dist/index.js.map +1 -1
  23. package/dist/registry.cjs +121 -22
  24. package/dist/registry.cjs.map +1 -1
  25. package/dist/registry.js +2 -2
  26. package/dist/runtime.cjs +122 -23
  27. package/dist/runtime.cjs.map +1 -1
  28. package/dist/runtime.js +3 -3
  29. package/dist/schema.cjs +121 -22
  30. package/dist/schema.cjs.map +1 -1
  31. package/dist/schema.js +2 -2
  32. package/dist/screens.cjs +121 -22
  33. package/dist/screens.cjs.map +1 -1
  34. package/dist/screens.js +4 -4
  35. package/dist/ux.cjs +121 -22
  36. package/dist/ux.cjs.map +1 -1
  37. package/dist/ux.js +1 -1
  38. package/package.json +2 -2
  39. package/dist/chunk-4GO2CHAY.js.map +0 -1
package/dist/engine.js CHANGED
@@ -1,8 +1,8 @@
1
- export { runCohort } from './chunk-VUU3CDBT.js';
1
+ export { runCohort } from './chunk-WCFRKXH5.js';
2
2
  import { decodePause } from './chunk-UEOE6B52.js';
3
3
  export { LEGACY_PAUSE_PREFIXES, PAUSE_PREFIX, decodePause, encodePause, isPause, pauseForHuman } from './chunk-UEOE6B52.js';
4
- import { runFlow, getNodeKind, resolveNodePorts } from './chunk-4GO2CHAY.js';
5
- export { RunIdentity, defaultConfigFor, escapeSegment, getNodeKind, kindIds, listNodeKinds, registerNodeKind, resolveKindId, runFlow, validateConfig } from './chunk-4GO2CHAY.js';
4
+ import { runFlow, getNodeKind, resolveNodePorts } from './chunk-CIBW5ATA.js';
5
+ export { RunIdentity, defaultConfigFor, escapeSegment, getNodeKind, kindIds, listNodeKinds, registerNodeKind, resolveKindId, runFlow, validateConfig } from './chunk-CIBW5ATA.js';
6
6
  import './chunk-F5RPRB7A.js';
7
7
  import { isResolutionFailure, registerLlmClient, registerWorkflowResolver } from './chunk-USL4FMFU.js';
8
8
 
package/dist/index.cjs CHANGED
@@ -1719,6 +1719,101 @@ var init_layout = __esm({
1719
1719
  init_dagre_esm();
1720
1720
  }
1721
1721
  });
1722
+
1723
+ // src/components/FlowEditor/human-fields.ts
1724
+ var HUMAN_FIELD_TYPE_ALIASES = {
1725
+ text: "text",
1726
+ string: "text",
1727
+ str: "text",
1728
+ input: "text",
1729
+ textarea: "textarea",
1730
+ long_text: "textarea",
1731
+ longtext: "textarea",
1732
+ "long-text": "textarea",
1733
+ multiline: "textarea",
1734
+ paragraph: "textarea",
1735
+ markdown: "textarea",
1736
+ number: "number",
1737
+ numeric: "number",
1738
+ integer: "number",
1739
+ int: "number",
1740
+ float: "number",
1741
+ decimal: "number",
1742
+ select: "select",
1743
+ enum: "select",
1744
+ choice: "select",
1745
+ choices: "select",
1746
+ dropdown: "select",
1747
+ options: "select",
1748
+ radio: "select",
1749
+ switch: "switch",
1750
+ bool: "switch",
1751
+ boolean: "switch",
1752
+ checkbox: "switch",
1753
+ toggle: "switch",
1754
+ date: "date",
1755
+ datetime: "datetime",
1756
+ "datetime-local": "datetime",
1757
+ datetimelocal: "datetime",
1758
+ timestamp: "datetime",
1759
+ time: "time",
1760
+ email: "email",
1761
+ "e-mail": "email",
1762
+ url: "url",
1763
+ uri: "url",
1764
+ link: "url",
1765
+ tel: "tel",
1766
+ phone: "tel",
1767
+ telephone: "tel",
1768
+ password: "password",
1769
+ secret: "password"
1770
+ };
1771
+ function humanFieldType(raw) {
1772
+ if (typeof raw !== "string") return "text";
1773
+ return HUMAN_FIELD_TYPE_ALIASES[raw.trim().toLowerCase()] ?? "text";
1774
+ }
1775
+ function humanFieldOptions(raw) {
1776
+ const entries = [];
1777
+ if (Array.isArray(raw)) {
1778
+ for (const item of raw) {
1779
+ if (typeof item === "string" || typeof item === "number") {
1780
+ const value = String(item);
1781
+ if (value !== "") entries.push({ value, label: value });
1782
+ continue;
1783
+ }
1784
+ if (item && typeof item === "object") {
1785
+ const value = item.value;
1786
+ if (value === void 0 || value === null || value === "") continue;
1787
+ const label = item.label;
1788
+ entries.push({
1789
+ value: String(value),
1790
+ label: typeof label === "string" && label !== "" ? label : String(value)
1791
+ });
1792
+ }
1793
+ }
1794
+ } else if (raw && typeof raw === "object") {
1795
+ for (const [value, label] of Object.entries(raw)) {
1796
+ if (value === "") continue;
1797
+ entries.push({ value, label: typeof label === "string" && label !== "" ? label : value });
1798
+ }
1799
+ }
1800
+ return entries.length ? entries : void 0;
1801
+ }
1802
+ function humanInputFields(config) {
1803
+ const raw = Array.isArray(config?.fields) ? config.fields : [];
1804
+ const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
1805
+ key: f.key,
1806
+ label: typeof f.label === "string" && f.label ? f.label : f.key,
1807
+ type: humanFieldType(f.type),
1808
+ required: !!f.required,
1809
+ placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
1810
+ options: humanFieldOptions(f.options ?? f.choices),
1811
+ default: f.default
1812
+ }));
1813
+ if (fields.length) return fields;
1814
+ const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
1815
+ return [{ key: "value", label: title, type: "textarea", required: true }];
1816
+ }
1722
1817
  var adapter = null;
1723
1818
  var listeners = /* @__PURE__ */ new Set();
1724
1819
  function registerRichInputAdapter(next) {
@@ -11220,7 +11315,7 @@ async function runFlow(graph, executors, onEvent = () => {
11220
11315
  const exec = pickExecutor(executors, node);
11221
11316
  if (!exec) {
11222
11317
  const tried = executorLookupIds(node);
11223
- const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id2) => `"${id2}"`).join(", ")} and the wildcard "*". Key your registry by one of those.`;
11318
+ const msg = `No executor registered for kind=${node.type} \u2014 tried ${tried.map((id2) => `"${id2}"`).join(", ")}. Key your registry by one of those.`;
11224
11319
  errors.push(msg);
11225
11320
  onEvent({ type: "node-status", nodeId: node.id, status: "error", text: msg });
11226
11321
  onEvent({ type: "log", nodeId: node.id, level: "error", message: msg });
@@ -11316,30 +11411,28 @@ function collectInputs(node, incoming, portValues, initial, props, declaresProps
11316
11411
  function executorLookupIds(node) {
11317
11412
  const ids = [node.id];
11318
11413
  if (node.type) ids.push(node.type);
11319
- const declared = node.data?.kind;
11320
- const kindName = typeof declared === "string" && declared !== "" ? declared : node.type;
11321
- if (kindName && kindName !== node.type) ids.push(kindName);
11322
- for (const name of [kindName, node.type]) {
11323
- const kind = name ? getNodeKind(name) : null;
11324
- if (!kind) continue;
11325
- for (const id2 of kindIds(kind)) ids.push(id2);
11414
+ const typeKind = node.type ? getNodeKind(node.type) : null;
11415
+ if (typeKind) {
11416
+ for (const id2 of kindIds(typeKind)) ids.push(id2);
11417
+ } else {
11418
+ const declared = node.data?.kind;
11419
+ const kindName = typeof declared === "string" && declared !== "" ? declared : null;
11420
+ if (kindName) {
11421
+ ids.push(kindName);
11422
+ const dataKind = getNodeKind(kindName);
11423
+ if (dataKind) {
11424
+ for (const id2 of kindIds(dataKind)) ids.push(id2);
11425
+ }
11426
+ }
11326
11427
  }
11428
+ ids.push("*");
11327
11429
  return [...new Set(ids)];
11328
11430
  }
11329
11431
  function pickExecutor(executors, node) {
11330
- if (executors[node.id]) return executors[node.id];
11331
- if (node.type && executors[node.type]) return executors[node.type];
11332
- const declared = node.data?.kind;
11333
- const kindName = typeof declared === "string" && declared !== "" ? declared : node.type;
11334
- if (kindName && kindName !== node.type && executors[kindName]) return executors[kindName];
11335
- for (const name of [kindName, node.type]) {
11336
- const kind = name ? getNodeKind(name) : null;
11337
- if (!kind) continue;
11338
- for (const id2 of kindIds(kind)) {
11339
- if (executors[id2]) return executors[id2];
11340
- }
11432
+ for (const id2 of executorLookupIds(node)) {
11433
+ if (executors[id2]) return executors[id2];
11341
11434
  }
11342
- return executors["*"];
11435
+ return void 0;
11343
11436
  }
11344
11437
  function activatedPorts(node, result) {
11345
11438
  if (result && typeof result === "object") {
@@ -11450,6 +11543,20 @@ var subflowExecutor = async (ctx) => {
11450
11543
  };
11451
11544
 
11452
11545
  // src/registry/builtin.ts
11546
+ var HUMAN_FIELD_OUTPUT_TYPE = {
11547
+ text: "string",
11548
+ textarea: "string",
11549
+ select: "string",
11550
+ date: "string",
11551
+ datetime: "string",
11552
+ time: "string",
11553
+ email: "string",
11554
+ url: "string",
11555
+ tel: "string",
11556
+ password: "string",
11557
+ number: "number",
11558
+ switch: "boolean"
11559
+ };
11453
11560
  function casePorts(cases) {
11454
11561
  const byPort = /* @__PURE__ */ new Map();
11455
11562
  if (cases && typeof cases === "object" && !Array.isArray(cases)) {
@@ -11547,7 +11654,17 @@ var KINDS = [
11547
11654
  name: "@particle-academy/user_input",
11548
11655
  // The keys an author declared on THIS node — the case a static list cannot
11549
11656
  // express, and the one issue #5 named.
11550
- outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({ path: f.key, type: "string", description: f.label })),
11657
+ //
11658
+ // The TYPE comes from the same normalizer the form renders with, so the
11659
+ // variable picker agrees with what the run actually resolves. It used to say
11660
+ // `string` for every field, which was wrong the moment a field was a number
11661
+ // or a switch — the picker told an author `{{ $json.age }}` was text while
11662
+ // the form handed the next node a number.
11663
+ outputShape: (config) => (config.fields ?? []).filter((f) => typeof f.key === "string" && f.key !== "").map((f) => ({
11664
+ path: f.key,
11665
+ type: HUMAN_FIELD_OUTPUT_TYPE[humanFieldType(f.type)],
11666
+ description: f.label
11667
+ })),
11551
11668
  aliases: ["user_input", "@fancy/user_input"],
11552
11669
  pausesForHuman: "input",
11553
11670
  category: "human",
@@ -11574,14 +11691,38 @@ var KINDS = [
11574
11691
  key: "type",
11575
11692
  label: "Type",
11576
11693
  default: "text",
11694
+ // Every control `HumanPrompt` can render. A type the form supports
11695
+ // but the panel cannot select is reachable only by hand-editing the
11696
+ // workflow JSON — which is the audience this panel exists for.
11577
11697
  options: [
11578
11698
  { value: "text", label: "Text" },
11579
11699
  { value: "textarea", label: "Long text" },
11580
11700
  { value: "number", label: "Number" },
11581
11701
  { value: "select", label: "Select" },
11582
- { value: "switch", label: "Switch" }
11702
+ { value: "switch", label: "Switch (yes / no)" },
11703
+ { value: "date", label: "Date" },
11704
+ { value: "datetime", label: "Date + time" },
11705
+ { value: "time", label: "Time" },
11706
+ { value: "email", label: "Email" },
11707
+ { value: "url", label: "URL" },
11708
+ { value: "tel", label: "Phone" },
11709
+ { value: "password", label: "Password" }
11583
11710
  ]
11584
11711
  },
11712
+ {
11713
+ // Without this a panel-authored select had nothing to choose from,
11714
+ // so it rendered an empty dropdown — the type was declarable and
11715
+ // unusable in the same breath.
11716
+ type: "keyvalue",
11717
+ key: "options",
11718
+ label: "Choices",
11719
+ description: "Select fields only. Left is the value the flow receives, right is what the person sees.",
11720
+ keyLabel: "Value",
11721
+ valueLabel: "Label",
11722
+ keyPlaceholder: "small",
11723
+ valuePlaceholder: "Small",
11724
+ addLabel: "Add choice"
11725
+ },
11585
11726
  { type: "switch", key: "required", label: "Required", default: false }
11586
11727
  ],
11587
11728
  default: [{ key: "answer", label: "Your answer", type: "textarea", required: true }]
@@ -14531,24 +14672,28 @@ function buildNodeTypes() {
14531
14672
  }
14532
14673
  return map;
14533
14674
  }
14534
- function humanInputFields(config) {
14535
- const raw = Array.isArray(config?.fields) ? config.fields : [];
14536
- const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
14537
- key: f.key,
14538
- label: typeof f.label === "string" && f.label ? f.label : f.key,
14539
- type: ["text", "textarea", "number", "select", "switch"].includes(f.type) ? f.type : "text",
14540
- required: !!f.required,
14541
- placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
14542
- options: Array.isArray(f.options) ? f.options : void 0,
14543
- default: f.default
14544
- }));
14545
- if (fields.length) return fields;
14546
- const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
14547
- return [{ key: "value", label: title, type: "textarea", required: true }];
14548
- }
14675
+ var INPUT_TYPE = {
14676
+ text: "text",
14677
+ number: "number",
14678
+ date: "date",
14679
+ datetime: "datetime-local",
14680
+ time: "time",
14681
+ email: "email",
14682
+ url: "url",
14683
+ tel: "tel",
14684
+ password: "password"
14685
+ };
14549
14686
  function initialValues(fields) {
14550
14687
  const v2 = {};
14551
- for (const f of fields) v2[f.key] = f.type === "switch" ? !!f.default : f.default ?? "";
14688
+ for (const f of fields) {
14689
+ if (f.type === "switch") {
14690
+ v2[f.key] = !!f.default;
14691
+ } else if (f.type === "number") {
14692
+ v2[f.key] = typeof f.default === "number" ? f.default : f.default === void 0 || f.default === "" ? "" : Number(f.default);
14693
+ } else {
14694
+ v2[f.key] = f.default ?? "";
14695
+ }
14696
+ }
14552
14697
  return v2;
14553
14698
  }
14554
14699
  function HumanPrompt({ request, onCancel }) {
@@ -14583,6 +14728,8 @@ function InputBody({
14583
14728
  }) {
14584
14729
  const [values, setValues] = ReactExports.useState(() => initialValues(request.fields));
14585
14730
  const set3 = (k2, v2) => setValues((prev) => ({ ...prev, [k2]: v2 }));
14731
+ const uid = ReactExports.useId();
14732
+ const fieldId = (key) => `${uid}-${key}`;
14586
14733
  const missing = request.fields.filter((f) => f.required && (values[f.key] === void 0 || values[f.key] === ""));
14587
14734
  const submit = () => {
14588
14735
  if (missing.length === 0) request.resolve(values);
@@ -14601,12 +14748,21 @@ function InputBody({
14601
14748
  }
14602
14749
  },
14603
14750
  children: [
14604
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-prompt__field", children: [
14605
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ff-prompt__label", children: [
14751
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-prompt__field", children: [
14752
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ff-prompt__label", htmlFor: fieldId(f.key), children: [
14606
14753
  f.label ?? f.key,
14607
- f.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-prompt__req", children: " *" })
14754
+ f.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-prompt__req", "aria-hidden": "true", children: " *" })
14608
14755
  ] }),
14609
- renderControl(f, values[f.key], (v2) => set3(f.key, v2), i === 0 ? firstRef : void 0)
14756
+ /* @__PURE__ */ jsxRuntime.jsx(
14757
+ FieldControl,
14758
+ {
14759
+ field: f,
14760
+ id: fieldId(f.key),
14761
+ value: values[f.key],
14762
+ onChange: (v2) => set3(f.key, v2),
14763
+ autoFocusRef: i === 0 ? firstRef : void 0
14764
+ }
14765
+ )
14610
14766
  ] }, f.key)) }),
14611
14767
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-prompt__actions", children: [
14612
14768
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
@@ -14616,45 +14772,68 @@ function InputBody({
14616
14772
  }
14617
14773
  );
14618
14774
  }
14619
- function renderControl(f, value, onChange, ref) {
14620
- const common = { className: "ff-prompt__input", placeholder: f.placeholder };
14621
- if (f.type === "textarea") {
14775
+ function FieldControl({
14776
+ field,
14777
+ id: id2,
14778
+ value,
14779
+ onChange,
14780
+ autoFocusRef
14781
+ }) {
14782
+ const handle = { id: id2, "data-ff-field": field.key };
14783
+ if (field.type === "textarea") {
14622
14784
  return /* @__PURE__ */ jsxRuntime.jsx(
14623
14785
  "textarea",
14624
14786
  {
14625
- ...common,
14626
- ref,
14787
+ ...handle,
14788
+ className: "ff-prompt__input",
14789
+ placeholder: field.placeholder,
14790
+ ref: autoFocusRef,
14627
14791
  rows: 3,
14628
14792
  value: String(value ?? ""),
14629
14793
  onChange: (e) => onChange(e.target.value)
14630
14794
  }
14631
14795
  );
14632
14796
  }
14633
- if (f.type === "switch") {
14797
+ if (field.type === "switch") {
14634
14798
  return /* @__PURE__ */ jsxRuntime.jsx(
14635
14799
  "input",
14636
14800
  {
14801
+ ...handle,
14637
14802
  type: "checkbox",
14638
14803
  className: "ff-prompt__switch",
14804
+ ref: autoFocusRef,
14639
14805
  checked: !!value,
14640
14806
  onChange: (e) => onChange(e.target.checked)
14641
14807
  }
14642
14808
  );
14643
14809
  }
14644
- if (f.type === "select" && f.options && f.options.length) {
14645
- return /* @__PURE__ */ jsxRuntime.jsxs("select", { className: "ff-prompt__input", value: String(value ?? ""), onChange: (e) => onChange(e.target.value), children: [
14646
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Choose\u2026" }),
14647
- f.options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
14648
- ] });
14810
+ if (field.type === "select") {
14811
+ const options = field.options ?? [];
14812
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14813
+ "select",
14814
+ {
14815
+ ...handle,
14816
+ className: "ff-prompt__input",
14817
+ ref: autoFocusRef,
14818
+ value: String(value ?? ""),
14819
+ onChange: (e) => onChange(e.target.value),
14820
+ children: [
14821
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: options.length ? "Choose\u2026" : "No choices configured" }),
14822
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o.value, children: o.label }, o.value))
14823
+ ]
14824
+ }
14825
+ );
14649
14826
  }
14650
14827
  return /* @__PURE__ */ jsxRuntime.jsx(
14651
14828
  "input",
14652
14829
  {
14653
- ...common,
14654
- ref,
14655
- type: f.type === "number" ? "number" : "text",
14830
+ ...handle,
14831
+ className: "ff-prompt__input",
14832
+ placeholder: field.placeholder,
14833
+ ref: autoFocusRef,
14834
+ type: INPUT_TYPE[field.type ?? "text"] ?? "text",
14656
14835
  value: String(value ?? ""),
14657
- onChange: (e) => onChange(f.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
14836
+ onChange: (e) => onChange(field.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
14658
14837
  }
14659
14838
  );
14660
14839
  }