@particle-academy/fancy-flow 0.25.0 → 0.27.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/index.d.cts CHANGED
@@ -538,10 +538,19 @@ declare function FlowRunControls({ running, onRun, onCancel, onReset, className,
538
538
 
539
539
  type FlowRunFeedProps = {
540
540
  entries: FlowRunFeedEntry[];
541
+ /** Show the header bar (title + running badge + event count). Default true. */
542
+ showHeader?: boolean;
543
+ /** Header title. Default "Run feed". */
544
+ title?: string;
545
+ /** When true, a "running" badge shows in the header. */
546
+ running?: boolean;
541
547
  className?: string;
542
548
  style?: CSSProperties;
543
549
  };
544
- /** FlowRunFeed — scrolling log panel. Auto-scrolls to bottom on new entries. */
545
- declare function FlowRunFeed({ entries, className, style }: FlowRunFeedProps): react.JSX.Element;
550
+ /**
551
+ * FlowRunFeed — scrolling run log with a header (title + live event count).
552
+ * Auto-scrolls its body to the bottom on new entries.
553
+ */
554
+ declare function FlowRunFeed({ entries, showHeader, title, running, className, style }: FlowRunFeedProps): react.JSX.Element;
546
555
 
547
556
  export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
package/dist/index.d.ts CHANGED
@@ -538,10 +538,19 @@ declare function FlowRunControls({ running, onRun, onCancel, onReset, className,
538
538
 
539
539
  type FlowRunFeedProps = {
540
540
  entries: FlowRunFeedEntry[];
541
+ /** Show the header bar (title + running badge + event count). Default true. */
542
+ showHeader?: boolean;
543
+ /** Header title. Default "Run feed". */
544
+ title?: string;
545
+ /** When true, a "running" badge shows in the header. */
546
+ running?: boolean;
541
547
  className?: string;
542
548
  style?: CSSProperties;
543
549
  };
544
- /** FlowRunFeed — scrolling log panel. Auto-scrolls to bottom on new entries. */
545
- declare function FlowRunFeed({ entries, className, style }: FlowRunFeedProps): react.JSX.Element;
550
+ /**
551
+ * FlowRunFeed — scrolling run log with a header (title + live event count).
552
+ * Auto-scrolls its body to the bottom on new entries.
553
+ */
554
+ declare function FlowRunFeed({ entries, showHeader, title, running, className, style }: FlowRunFeedProps): react.JSX.Element;
546
555
 
547
556
  export { ActionNode, type AlignEdge, AutoLayoutOptions, ConfigField, ConfigFieldRenderer, type ConfigFieldRendererProps, ConnectionValidatorOptions, DecisionNode, ExecutorRegistry, FlowCanvas, type FlowCanvasProps, FlowEditor, type FlowEditorAction, type FlowEditorApi, type FlowEditorBuiltins, type FlowEditorProps, type FlowEditorSlots, FlowGraph, FlowNode, type FlowNodeRenderProps, FlowRunControls, type FlowRunControlsProps, FlowRunFeed, FlowRunFeedEntry, type FlowRunFeedProps, LaneNode, NodeCategory, NodeConfigPanel, type NodeConfigPanelProps, NodeKindDefinition, NodePalette, type NodePaletteProps, NodePort, type NodePortProps, type NodePortSide, type NodePortType, NodeRunStatus, NodeShell, type NodeShellProps, NoteNode, OutputNode, SubgraphNode, TriggerNode, WorkflowMetadata, WorkflowSchema, alignNodes, cloneSubgraph, defaultNodeTypes, defineNode, distributeNodes, paletteDropHandlers, reconnectEdge, useFlowEditor, useFlowEditorOptional };
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-ATQXZEVD.js';
4
- export { ANY_PORT_TYPE, BUILTIN_KINDS, LaneNode, NoteNode, RegistryNode, buildNodeTypes, createConnectionValidator, defaultPortCompatibility, registerBuiltinKinds, useFlowEditor, useFlowEditorOptional } from './chunk-ATQXZEVD.js';
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, useEffect, useMemo, useCallback, useRef, useImperativeHandle } from 'react';
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({
@@ -969,9 +969,13 @@ function NodeConfigPanel({
969
969
  return /* @__PURE__ */ jsxs("aside", { className: ["ff-panel", className ?? ""].filter(Boolean).join(" "), style, children: [
970
970
  header,
971
971
  /* @__PURE__ */ jsxs("header", { className: "ff-panel__header", children: [
972
- /* @__PURE__ */ jsx("span", { className: "ff-panel__kind-tag", children: kind.label }),
973
- kind.description && /* @__PURE__ */ jsx("p", { className: "ff-panel__kind-desc", children: kind.description })
972
+ /* @__PURE__ */ jsx("span", { className: "ff-panel__head-icon", style: { background: kind.accent ?? categoryAccent(kind.category) }, "aria-hidden": true, children: kind.icon }),
973
+ /* @__PURE__ */ jsxs("span", { className: "ff-panel__head-text", children: [
974
+ /* @__PURE__ */ jsx("span", { className: "ff-panel__head-kind", children: kind.label }),
975
+ /* @__PURE__ */ jsx("span", { className: "ff-panel__head-name", children: node.data.label || kind.label })
976
+ ] })
974
977
  ] }),
978
+ kind.description && /* @__PURE__ */ jsx("p", { className: "ff-panel__kind-desc", children: kind.description }),
975
979
  /* @__PURE__ */ jsxs("div", { className: "ff-panel__field", children: [
976
980
  /* @__PURE__ */ jsx("label", { className: "ff-panel__label", children: "Label" }),
977
981
  /* @__PURE__ */ jsx(
@@ -1046,22 +1050,161 @@ function FlowRunControls({ running, onRun, onCancel, onReset, className, style }
1046
1050
  onReset && /* @__PURE__ */ jsx("button", { type: "button", className: "ff-run-controls__btn ff-run-controls__btn--reset", onClick: onReset, disabled: running, children: "Reset" })
1047
1051
  ] });
1048
1052
  }
1049
- function FlowRunFeed({ entries, className, style }) {
1053
+ function FlowRunFeed({ entries, showHeader = true, title = "Run feed", running, className, style }) {
1050
1054
  const scrollRef = useRef(null);
1051
1055
  useEffect(() => {
1052
1056
  const el = scrollRef.current;
1053
1057
  if (el) el.scrollTop = el.scrollHeight;
1054
1058
  }, [entries.length]);
1055
- return /* @__PURE__ */ jsx("div", { className: ["ff-run-feed", className ?? ""].filter(Boolean).join(" "), style, ref: scrollRef, children: entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ff-run-feed__empty", children: "No run events yet." }) : entries.map((e) => /* @__PURE__ */ jsxs("div", { className: `ff-run-feed__row ff-run-feed__row--${e.level}`, children: [
1056
- /* @__PURE__ */ jsx("span", { className: "ff-run-feed__time", children: formatTime(e.at) }),
1057
- e.nodeId && /* @__PURE__ */ jsx("span", { className: "ff-run-feed__node", children: e.nodeId }),
1058
- /* @__PURE__ */ jsx("span", { className: "ff-run-feed__text", children: e.text })
1059
- ] }, e.id)) });
1059
+ return /* @__PURE__ */ jsxs("div", { className: ["ff-run-feed", className ?? ""].filter(Boolean).join(" "), style, children: [
1060
+ showHeader && /* @__PURE__ */ jsxs("div", { className: "ff-run-feed__header", children: [
1061
+ /* @__PURE__ */ jsx("span", { className: "ff-run-feed__title", "aria-hidden": true, children: "\u25B8" }),
1062
+ /* @__PURE__ */ jsx("span", { className: "ff-run-feed__title-text", children: title }),
1063
+ running && /* @__PURE__ */ jsx("span", { className: "ff-run-feed__badge", children: "running" }),
1064
+ /* @__PURE__ */ jsxs("span", { className: "ff-run-feed__count", children: [
1065
+ entries.length,
1066
+ " ",
1067
+ entries.length === 1 ? "event" : "events"
1068
+ ] })
1069
+ ] }),
1070
+ /* @__PURE__ */ jsx("div", { className: "ff-run-feed__body", ref: scrollRef, children: entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "ff-run-feed__empty", children: "No run events yet." }) : entries.map((e) => /* @__PURE__ */ jsxs("div", { className: `ff-run-feed__row ff-run-feed__row--${e.level}`, children: [
1071
+ /* @__PURE__ */ jsx("span", { className: "ff-run-feed__time", children: formatTime(e.at) }),
1072
+ e.nodeId && /* @__PURE__ */ jsx("span", { className: "ff-run-feed__node", children: e.nodeId }),
1073
+ /* @__PURE__ */ jsx("span", { className: "ff-run-feed__text", children: e.text })
1074
+ ] }, e.id)) })
1075
+ ] });
1060
1076
  }
1061
1077
  function formatTime(at) {
1062
1078
  const d = new Date(at);
1063
1079
  return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}.${Math.floor(d.getMilliseconds() / 100)}`;
1064
1080
  }
1081
+ function humanInputFields(config) {
1082
+ const raw = Array.isArray(config?.fields) ? config.fields : [];
1083
+ const fields = raw.filter((f) => f && typeof f === "object" && typeof f.key === "string" && f.key).map((f) => ({
1084
+ key: f.key,
1085
+ label: typeof f.label === "string" && f.label ? f.label : f.key,
1086
+ type: ["text", "textarea", "number", "select", "switch"].includes(f.type) ? f.type : "text",
1087
+ required: !!f.required,
1088
+ placeholder: typeof f.placeholder === "string" ? f.placeholder : void 0,
1089
+ options: Array.isArray(f.options) ? f.options : void 0,
1090
+ default: f.default
1091
+ }));
1092
+ if (fields.length) return fields;
1093
+ const title = typeof config?.title === "string" && config.title ? config.title : "Your answer";
1094
+ return [{ key: "value", label: title, type: "textarea", required: true }];
1095
+ }
1096
+ function initialValues(fields) {
1097
+ const v = {};
1098
+ for (const f of fields) v[f.key] = f.type === "switch" ? !!f.default : f.default ?? "";
1099
+ return v;
1100
+ }
1101
+ function HumanPrompt({ request, onCancel }) {
1102
+ const firstRef = useRef(null);
1103
+ useEffect(() => {
1104
+ firstRef.current?.focus();
1105
+ const onKey = (e) => {
1106
+ if (e.key === "Escape") onCancel();
1107
+ };
1108
+ window.addEventListener("keydown", onKey);
1109
+ return () => window.removeEventListener("keydown", onKey);
1110
+ }, [onCancel]);
1111
+ 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: [
1112
+ /* @__PURE__ */ jsx("div", { className: "ff-prompt__title", children: request.title }),
1113
+ request.kind === "approval" ? /* @__PURE__ */ jsx(ApprovalBody, { request, onCancel }) : /* @__PURE__ */ jsx(InputBody, { request, onCancel, firstRef })
1114
+ ] }) });
1115
+ }
1116
+ function ApprovalBody({ request, onCancel }) {
1117
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1118
+ request.description && /* @__PURE__ */ jsx("p", { className: "ff-prompt__desc", children: request.description }),
1119
+ /* @__PURE__ */ jsxs("div", { className: "ff-prompt__actions", children: [
1120
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
1121
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--danger", onClick: () => request.resolve(false), children: "Deny" }),
1122
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--primary", onClick: () => request.resolve(true), children: "Approve" })
1123
+ ] })
1124
+ ] });
1125
+ }
1126
+ function InputBody({
1127
+ request,
1128
+ onCancel,
1129
+ firstRef
1130
+ }) {
1131
+ const [values, setValues] = useState(() => initialValues(request.fields));
1132
+ const set = (k, v) => setValues((prev) => ({ ...prev, [k]: v }));
1133
+ const missing = request.fields.filter((f) => f.required && (values[f.key] === void 0 || values[f.key] === ""));
1134
+ const submit = () => {
1135
+ if (missing.length === 0) request.resolve(values);
1136
+ };
1137
+ return /* @__PURE__ */ jsxs(
1138
+ "form",
1139
+ {
1140
+ onSubmit: (e) => {
1141
+ e.preventDefault();
1142
+ submit();
1143
+ },
1144
+ onKeyDown: (e) => {
1145
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
1146
+ e.preventDefault();
1147
+ submit();
1148
+ }
1149
+ },
1150
+ children: [
1151
+ /* @__PURE__ */ jsx("div", { className: "ff-prompt__fields", children: request.fields.map((f, i) => /* @__PURE__ */ jsxs("label", { className: "ff-prompt__field", children: [
1152
+ /* @__PURE__ */ jsxs("span", { className: "ff-prompt__label", children: [
1153
+ f.label ?? f.key,
1154
+ f.required && /* @__PURE__ */ jsx("span", { className: "ff-prompt__req", children: " *" })
1155
+ ] }),
1156
+ renderControl(f, values[f.key], (v) => set(f.key, v), i === 0 ? firstRef : void 0)
1157
+ ] }, f.key)) }),
1158
+ /* @__PURE__ */ jsxs("div", { className: "ff-prompt__actions", children: [
1159
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ff-prompt__btn ff-prompt__btn--ghost", onClick: onCancel, children: "Cancel" }),
1160
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "ff-prompt__btn ff-prompt__btn--primary", disabled: missing.length > 0, children: request.submitLabel || "Continue" })
1161
+ ] })
1162
+ ]
1163
+ }
1164
+ );
1165
+ }
1166
+ function renderControl(f, value, onChange, ref) {
1167
+ const common = { className: "ff-prompt__input", placeholder: f.placeholder };
1168
+ if (f.type === "textarea") {
1169
+ return /* @__PURE__ */ jsx(
1170
+ "textarea",
1171
+ {
1172
+ ...common,
1173
+ ref,
1174
+ rows: 3,
1175
+ value: String(value ?? ""),
1176
+ onChange: (e) => onChange(e.target.value)
1177
+ }
1178
+ );
1179
+ }
1180
+ if (f.type === "switch") {
1181
+ return /* @__PURE__ */ jsx(
1182
+ "input",
1183
+ {
1184
+ type: "checkbox",
1185
+ className: "ff-prompt__switch",
1186
+ checked: !!value,
1187
+ onChange: (e) => onChange(e.target.checked)
1188
+ }
1189
+ );
1190
+ }
1191
+ if (f.type === "select" && f.options && f.options.length) {
1192
+ return /* @__PURE__ */ jsxs("select", { className: "ff-prompt__input", value: String(value ?? ""), onChange: (e) => onChange(e.target.value), children: [
1193
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Choose\u2026" }),
1194
+ f.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
1195
+ ] });
1196
+ }
1197
+ return /* @__PURE__ */ jsx(
1198
+ "input",
1199
+ {
1200
+ ...common,
1201
+ ref,
1202
+ type: f.type === "number" ? "number" : "text",
1203
+ value: String(value ?? ""),
1204
+ onChange: (e) => onChange(f.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
1205
+ }
1206
+ );
1207
+ }
1065
1208
  var FlowEditor = forwardRef(function FlowEditor2(props, ref) {
1066
1209
  return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(
1067
1210
  "div",
@@ -1095,6 +1238,41 @@ function FlowEditorInner({
1095
1238
  const internal = useFlowState(initial);
1096
1239
  const runner = useFlowRun();
1097
1240
  const rf = useReactFlow();
1241
+ const [prompt, setPrompt] = useState(null);
1242
+ const humanExecutors = useMemo(
1243
+ () => ({
1244
+ user_input: ({ node }) => new Promise((resolve) => {
1245
+ const cfg = node.data?.config ?? {};
1246
+ setPrompt({
1247
+ kind: "input",
1248
+ title: typeof cfg.title === "string" && cfg.title || "Your input",
1249
+ submitLabel: typeof cfg.submitLabel === "string" ? cfg.submitLabel : void 0,
1250
+ fields: humanInputFields(cfg),
1251
+ resolve: (values) => {
1252
+ setPrompt(null);
1253
+ resolve(values);
1254
+ }
1255
+ });
1256
+ }),
1257
+ human_approval: ({ node }) => new Promise((resolve) => {
1258
+ const cfg = node.data?.config ?? {};
1259
+ setPrompt({
1260
+ kind: "approval",
1261
+ title: typeof cfg.title === "string" && cfg.title || "Approve action",
1262
+ description: typeof cfg.description === "string" ? cfg.description : void 0,
1263
+ resolve: (approved) => {
1264
+ setPrompt(null);
1265
+ resolve({ branch: approved ? "approved" : "denied" });
1266
+ }
1267
+ });
1268
+ })
1269
+ }),
1270
+ []
1271
+ );
1272
+ const runExecutors = useMemo(() => ({ ...humanExecutors, ...executors }), [humanExecutors, executors]);
1273
+ useEffect(() => {
1274
+ if (!runner.running) setPrompt(null);
1275
+ }, [runner.running]);
1098
1276
  const controlled = value !== void 0;
1099
1277
  const baseFlow = controlled ? makeControlledFlowAdapter(value, onChange) : internal;
1100
1278
  const hist = useFlowHistory(baseFlow);
@@ -1404,7 +1582,7 @@ function FlowEditorInner({
1404
1582
  ({ autoLayout }) => flow.setGraph({ nodes: autoLayout({ nodes: flow.nodes, edges: flow.edges }, { scope: laneId }), edges: flow.edges })
1405
1583
  );
1406
1584
  },
1407
- run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, executors),
1585
+ run: () => runner.run({ nodes: flow.nodes, edges: flow.edges }, runExecutors),
1408
1586
  cancel: runner.cancel,
1409
1587
  reset: runner.reset,
1410
1588
  toWorkflow,
@@ -1416,7 +1594,7 @@ function FlowEditorInner({
1416
1594
  canUndo: hist.canUndo,
1417
1595
  canRedo: hist.canRedo
1418
1596
  };
1419
- }, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, executors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, addLane, hist, rf]);
1597
+ }, [flow, selectedId, selected, selectedEdgeId, selectedEdge, selectedIds, selectedNodes, runner, runExecutors, metadata, addNode, deleteNodes, deleteEdges, duplicateSelected, alignSelected, distributeSelected, copySelection, pasteClipboard, addLane, hist, rf]);
1420
1598
  useImperativeHandle(apiRef, () => api, [api]);
1421
1599
  const dropHandlers = paletteDropHandlers((kindName, evt) => {
1422
1600
  const point = rf.screenToFlowPosition({ x: evt.clientX, y: evt.clientY });
@@ -1590,7 +1768,11 @@ function FlowEditorInner({
1590
1768
  onCancel: () => setLabelEdit(null)
1591
1769
  }
1592
1770
  ),
1593
- showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, className: "ff-editor__feed" }))
1771
+ showFeed && (slots.feed ? slots.feed(api) : /* @__PURE__ */ jsx(FlowRunFeed, { entries: runner.feed, running: api.running, className: "ff-editor__feed" })),
1772
+ prompt && /* @__PURE__ */ jsx(HumanPrompt, { request: prompt, onCancel: () => {
1773
+ setPrompt(null);
1774
+ runner.cancel();
1775
+ } })
1594
1776
  ] }),
1595
1777
  showPanel && (slots.panel ? slots.panel(api) : /* @__PURE__ */ jsxs("div", { className: "ff-editor__panel-wrap", children: [
1596
1778
  /* @__PURE__ */ jsx(