@enumura/chatbot-flow-editor
Version:
Visual chatbot flow editor - Development tool for creating conversational flows
1,280 lines • 51.3 kB
JavaScript
import require$$0, { useState, useRef, useCallback, useEffect, useMemo, lazy } from "react";
import { c as clsx, a as cva } from "./utils-BOllD6kY.js";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
var jsxRuntime = { exports: {} };
var reactJsxRuntime_production = {};
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_production;
function requireReactJsxRuntime_production() {
if (hasRequiredReactJsxRuntime_production) return reactJsxRuntime_production;
hasRequiredReactJsxRuntime_production = 1;
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
function jsxProd(type, config, maybeKey) {
var key = null;
void 0 !== maybeKey && (key = "" + maybeKey);
void 0 !== config.key && (key = "" + config.key);
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
config = maybeKey.ref;
return {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref: void 0 !== config ? config : null,
props: maybeKey
};
}
reactJsxRuntime_production.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_production.jsx = jsxProd;
reactJsxRuntime_production.jsxs = jsxProd;
return reactJsxRuntime_production;
}
var reactJsxRuntime_development = {};
/**
* @license React
* react-jsx-runtime.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredReactJsxRuntime_development;
function requireReactJsxRuntime_development() {
if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development;
hasRequiredReactJsxRuntime_development = 1;
"production" !== process.env.NODE_ENV && function() {
function getComponentNameFromType(type) {
if (null == type) return null;
if ("function" === typeof type)
return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null;
if ("string" === typeof type) return type;
switch (type) {
case REACT_FRAGMENT_TYPE:
return "Fragment";
case REACT_PROFILER_TYPE:
return "Profiler";
case REACT_STRICT_MODE_TYPE:
return "StrictMode";
case REACT_SUSPENSE_TYPE:
return "Suspense";
case REACT_SUSPENSE_LIST_TYPE:
return "SuspenseList";
case REACT_ACTIVITY_TYPE:
return "Activity";
}
if ("object" === typeof type)
switch ("number" === typeof type.tag && console.error(
"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."
), type.$$typeof) {
case REACT_PORTAL_TYPE:
return "Portal";
case REACT_CONTEXT_TYPE:
return (type.displayName || "Context") + ".Provider";
case REACT_CONSUMER_TYPE:
return (type._context.displayName || "Context") + ".Consumer";
case REACT_FORWARD_REF_TYPE:
var innerType = type.render;
type = type.displayName;
type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef");
return type;
case REACT_MEMO_TYPE:
return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE:
innerType = type._payload;
type = type._init;
try {
return getComponentNameFromType(type(innerType));
} catch (x) {
}
}
return null;
}
function testStringCoercion(value) {
return "" + value;
}
function checkKeyStringCoercion(value) {
try {
testStringCoercion(value);
var JSCompiler_inline_result = false;
} catch (e) {
JSCompiler_inline_result = true;
}
if (JSCompiler_inline_result) {
JSCompiler_inline_result = console;
var JSCompiler_temp_const = JSCompiler_inline_result.error;
var JSCompiler_inline_result$jscomp$0 = "function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag] || value.constructor.name || "Object";
JSCompiler_temp_const.call(
JSCompiler_inline_result,
"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",
JSCompiler_inline_result$jscomp$0
);
return testStringCoercion(value);
}
}
function getTaskName(type) {
if (type === REACT_FRAGMENT_TYPE) return "<>";
if ("object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE)
return "<...>";
try {
var name = getComponentNameFromType(type);
return name ? "<" + name + ">" : "<...>";
} catch (x) {
return "<...>";
}
}
function getOwner() {
var dispatcher = ReactSharedInternals.A;
return null === dispatcher ? null : dispatcher.getOwner();
}
function UnknownOwner() {
return Error("react-stack-top-frame");
}
function hasValidKey(config) {
if (hasOwnProperty.call(config, "key")) {
var getter = Object.getOwnPropertyDescriptor(config, "key").get;
if (getter && getter.isReactWarning) return false;
}
return void 0 !== config.key;
}
function defineKeyPropWarningGetter(props, displayName) {
function warnAboutAccessingKey() {
specialPropKeyWarningShown || (specialPropKeyWarningShown = true, console.error(
"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",
displayName
));
}
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, "key", {
get: warnAboutAccessingKey,
configurable: true
});
}
function elementRefGetterWithDeprecationWarning() {
var componentName = getComponentNameFromType(this.type);
didWarnAboutElementRef[componentName] || (didWarnAboutElementRef[componentName] = true, console.error(
"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release."
));
componentName = this.props.ref;
return void 0 !== componentName ? componentName : null;
}
function ReactElement(type, key, self, source, owner, props, debugStack, debugTask) {
self = props.ref;
type = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
props,
_owner: owner
};
null !== (void 0 !== self ? self : null) ? Object.defineProperty(type, "ref", {
enumerable: false,
get: elementRefGetterWithDeprecationWarning
}) : Object.defineProperty(type, "ref", { enumerable: false, value: null });
type._store = {};
Object.defineProperty(type._store, "validated", {
configurable: false,
enumerable: false,
writable: true,
value: 0
});
Object.defineProperty(type, "_debugInfo", {
configurable: false,
enumerable: false,
writable: true,
value: null
});
Object.defineProperty(type, "_debugStack", {
configurable: false,
enumerable: false,
writable: true,
value: debugStack
});
Object.defineProperty(type, "_debugTask", {
configurable: false,
enumerable: false,
writable: true,
value: debugTask
});
Object.freeze && (Object.freeze(type.props), Object.freeze(type));
return type;
}
function jsxDEVImpl(type, config, maybeKey, isStaticChildren, source, self, debugStack, debugTask) {
var children = config.children;
if (void 0 !== children)
if (isStaticChildren)
if (isArrayImpl(children)) {
for (isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++)
validateChildKeys(children[isStaticChildren]);
Object.freeze && Object.freeze(children);
} else
console.error(
"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead."
);
else validateChildKeys(children);
if (hasOwnProperty.call(config, "key")) {
children = getComponentNameFromType(type);
var keys = Object.keys(config).filter(function(k) {
return "key" !== k;
});
isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}";
didWarnAboutKeySpread[children + isStaticChildren] || (keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}", console.error(
'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />',
isStaticChildren,
children,
keys,
children
), didWarnAboutKeySpread[children + isStaticChildren] = true);
}
children = null;
void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), children = "" + maybeKey);
hasValidKey(config) && (checkKeyStringCoercion(config.key), children = "" + config.key);
if ("key" in config) {
maybeKey = {};
for (var propName in config)
"key" !== propName && (maybeKey[propName] = config[propName]);
} else maybeKey = config;
children && defineKeyPropWarningGetter(
maybeKey,
"function" === typeof type ? type.displayName || type.name || "Unknown" : type
);
return ReactElement(
type,
children,
self,
source,
getOwner(),
maybeKey,
debugStack,
debugTask
);
}
function validateChildKeys(node) {
"object" === typeof node && null !== node && node.$$typeof === REACT_ELEMENT_TYPE && node._store && (node._store.validated = 1);
}
var React = require$$0, REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function() {
return null;
};
React = {
"react-stack-bottom-frame": function(callStackForError) {
return callStackForError();
}
};
var specialPropKeyWarningShown;
var didWarnAboutElementRef = {};
var unknownOwnerDebugStack = React["react-stack-bottom-frame"].bind(
React,
UnknownOwner
)();
var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));
var didWarnAboutKeySpread = {};
reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE;
reactJsxRuntime_development.jsx = function(type, config, maybeKey, source, self) {
var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
false,
source,
self,
trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
reactJsxRuntime_development.jsxs = function(type, config, maybeKey, source, self) {
var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;
return jsxDEVImpl(
type,
config,
maybeKey,
true,
source,
self,
trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack,
trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask
);
};
}();
return reactJsxRuntime_development;
}
var hasRequiredJsxRuntime;
function requireJsxRuntime() {
if (hasRequiredJsxRuntime) return jsxRuntime.exports;
hasRequiredJsxRuntime = 1;
if (process.env.NODE_ENV === "production") {
jsxRuntime.exports = /* @__PURE__ */ requireReactJsxRuntime_production();
} else {
jsxRuntime.exports = /* @__PURE__ */ requireReactJsxRuntime_development();
}
return jsxRuntime.exports;
}
var jsxRuntimeExports = /* @__PURE__ */ requireJsxRuntime();
function cn(...inputs) {
return clsx(inputs);
}
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
}
);
function Button({
className,
variant,
size,
children,
render,
...props
}) {
const buttonClassName = cn(buttonVariants({ variant, size, className }));
if (render) {
return render({ className: buttonClassName });
}
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
"data-slot": "button",
className: buttonClassName,
...props,
children
}
);
}
function Card({ className, ...props }) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
"data-slot": "card",
className: cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
),
...props
}
);
}
function CardHeader({ className, ...props }) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
"data-slot": "card-header",
className: cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1 px-4 py-1 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-2",
className
),
...props
}
);
}
function CardTitle({ className, ...props }) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
"data-slot": "card-title",
className: cn("leading-none font-semibold text-sm", className),
...props
}
);
}
function CardContent({ className, ...props }) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
"data-slot": "card-content",
className: cn("px-4 py-2", className),
...props
}
);
}
function ScrollArea({
className,
children,
...props
}) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
ScrollAreaPrimitive.Root,
{
"data-slot": "scroll-area",
className: cn("relative", className),
...props,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
ScrollAreaPrimitive.Viewport,
{
"data-slot": "scroll-area-viewport",
className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1",
children
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollBar, {}),
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollAreaPrimitive.Corner, {})
]
}
);
}
function ScrollBar({
className,
orientation = "vertical",
...props
}) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
ScrollAreaPrimitive.ScrollAreaScrollbar,
{
"data-slot": "scroll-area-scrollbar",
orientation,
className: cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
className
),
...props,
children: /* @__PURE__ */ jsxRuntimeExports.jsx(
ScrollAreaPrimitive.ScrollAreaThumb,
{
"data-slot": "scroll-area-thumb",
className: "bg-border relative flex-1 rounded-full"
}
)
}
);
}
const FlowDiagram = ({
flow,
currentNodeId,
onNodeSelect,
onDeleteNode,
onAddNode
}) => {
const [isPanning, setIsPanning] = useState(false);
const [lastPanPoint, setLastPanPoint] = useState({ x: 0, y: 0 });
const scrollAreaRef = useRef(null);
const handleMouseDown = useCallback((e) => {
const target = e.target;
const isNodeClick = target.closest(".cursor-pointer") !== null;
if (!isNodeClick) {
setIsPanning(true);
setLastPanPoint({ x: e.clientX, y: e.clientY });
e.preventDefault();
}
}, []);
const handleMouseMove = useCallback((e) => {
if (!isPanning || !scrollAreaRef.current) return;
const deltaX = e.clientX - lastPanPoint.x;
const deltaY = e.clientY - lastPanPoint.y;
const viewport = scrollAreaRef.current.querySelector('[data-slot="scroll-area-viewport"]');
if (viewport) {
viewport.scrollLeft -= deltaX;
viewport.scrollTop -= deltaY;
}
setLastPanPoint({ x: e.clientX, y: e.clientY });
}, [isPanning, lastPanPoint]);
const handleMouseUp = useCallback(() => {
setIsPanning(false);
}, []);
const handleMouseLeave = useCallback(() => {
setIsPanning(false);
}, []);
const buildHierarchy = () => {
const rootNode = flow.find((node) => node.id === 1);
if (!rootNode) return null;
const nodeMap = flow.reduce((map, node) => {
map[node.id] = node;
return map;
}, {});
const visited = /* @__PURE__ */ new Set();
const buildNodeTree = (nodeId, depth, index) => {
if (visited.has(nodeId)) return null;
visited.add(nodeId);
const node = nodeMap[nodeId];
if (!node) return null;
const children = node.options.map((option, idx) => buildNodeTree(option.nextId, depth + 1, idx)).filter((child) => child !== null);
return {
node,
depth,
index,
children
};
};
return buildNodeTree(rootNode.id, 0, 0);
};
const renderNode = (item) => {
const { node, depth, children } = item;
const nodeDisplayId = node.hierarchyPath || node.id;
const titleLength = node.title.length;
const idLength = nodeDisplayId.toString().length;
const minWidth = Math.max(240, (titleLength + idLength) * 8 + 100);
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col mb-8", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start", children: [
depth > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
Array(depth).fill(0).map((_, i) => /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `w-10 ${i < depth - 1 ? "border-l-2 border-gray-500" : ""}` }, i)),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-10 h-10 border-b-4 border-l-4 border-gray-500 mr-3" })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: `relative px-5 py-3 rounded-lg shadow-md cursor-pointer border-2
${currentNodeId === node.id ? "bg-blue-50 border-blue-600 ring-2 ring-blue-200" : "bg-white border-gray-300"}`,
style: { minWidth: `${minWidth}px` },
onClick: () => onNodeSelect(node.id),
children: [
currentNodeId === node.id && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "absolute top-2 right-2 flex gap-1", children: [
onAddNode && /* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
className: "w-6 h-6 bg-green-50 hover:bg-green-100 border border-green-200 text-green-700 hover:text-green-800 rounded flex items-center justify-center text-xs transition-colors",
onClick: (e) => {
e.stopPropagation();
onAddNode(node.id);
},
title: "Add child node",
children: "+"
}
),
node.id !== 1 && onDeleteNode && /* @__PURE__ */ jsxRuntimeExports.jsx(
"button",
{
className: "w-6 h-6 bg-red-50 hover:bg-red-100 border border-red-200 text-red-700 hover:text-red-800 rounded flex items-center justify-center text-xs transition-colors",
onClick: (e) => {
e.stopPropagation();
onDeleteNode(node.id);
},
title: "Delete node",
children: "×"
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs font-medium text-gray-600 mb-1 pr-8", children: [
"Node ",
node.hierarchyPath || node.id
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-base font-bold text-gray-900 mb-2 pr-8 break-words", children: node.title }),
node.options.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-gray-800 mt-2 border-t border-gray-300 pt-2", children: node.options.map((option, optIdx) => {
const targetNode = flow.find((n) => n.id === option.nextId);
const targetDisplay = targetNode?.hierarchyPath || option.nextId;
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center py-1.5", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "mr-2 text-blue-600 font-bold", children: "•" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium flex-1 break-words", children: option.label }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs bg-gray-100 text-gray-700 px-1.5 py-0.5 rounded ml-2 break-all", children: [
"→ Node ",
targetDisplay
] })
] }, optIdx);
}) })
]
}
)
] }),
children.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col ml-10 mt-3", children: children.map((child) => renderNode(child)) })
] }, node.id);
};
const hierarchy = buildHierarchy();
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
ScrollArea,
{
ref: scrollAreaRef,
className: "h-full w-full",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
"div",
{
className: `p-6 min-w-max ${isPanning ? "cursor-grabbing" : "cursor-grab"}`,
"data-pannable": "true",
onMouseDown: handleMouseDown,
onMouseMove: handleMouseMove,
onMouseUp: handleMouseUp,
onMouseLeave: handleMouseLeave,
children: hierarchy && renderNode(hierarchy)
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(ScrollBar, { orientation: "horizontal" })
]
}
);
};
function Input({ className, type, ...props }) {
return /* @__PURE__ */ jsxRuntimeExports.jsx(
"input",
{
type,
"data-slot": "input",
className: cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
),
...props
}
);
}
const NodeEditor = ({
node,
flow,
onUpdateNode,
onAddOption,
onEditOption,
onRemoveOption
}) => {
const [title, setTitle] = useState(node.title);
useEffect(() => {
setTitle(node.title);
}, [node.id, node.title]);
const handleTitleChange = (e) => {
setTitle(e.target.value);
};
const handleSave = () => {
onUpdateNode(title);
};
const getNodeDisplayName = (nextId) => {
const targetNode = flow.find((n) => n.id === nextId);
if (targetNode?.hierarchyPath) {
return `Node${targetNode.hierarchyPath}`;
}
return `Node ${nextId}`;
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "h-full flex flex-col overflow-hidden", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "py-2 px-4 border-b shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center w-full", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-xl", children: node.hierarchyPath ? `Node ${node.hierarchyPath} Edit` : `Node ${node.id} Edit` }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
onClick: handleSave,
variant: "outline",
className: "bg-green-100 text-green-800 hover:bg-green-200 border-green-300 px-4 py-1 h-auto text-sm font-medium",
children: "Save"
}
)
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "px-4 pt-1 pb-4 flex-1 flex flex-col gap-2 overflow-hidden", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "shrink-0", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium mb-2", htmlFor: "node-title", children: "Title" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Input,
{
id: "node-title",
value: title,
onChange: handleTitleChange,
placeholder: "Enter node title",
className: "w-full"
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden min-h-0", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-3 shrink-0", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-medium", children: "Option" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
onClick: onAddOption,
size: "sm",
variant: "outline",
className: "bg-blue-50 text-blue-800 hover:bg-blue-100 border-blue-200",
children: "Add an option"
}
)
] }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "h-full w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-2 pr-3", children: [
node.options.map((opt, idx) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between bg-gray-50 p-3 rounded-md border border-gray-100", children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm truncate mr-2 flex-1", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-medium", children: opt.label }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-gray-500 text-xs ml-2", children: [
"→ ",
getNodeDisplayName(opt.nextId)
] })
] }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 shrink-0", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
size: "sm",
variant: "ghost",
className: "text-blue-600 h-8 px-2 py-1",
onClick: () => onEditOption(idx),
children: "Edit"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
size: "sm",
variant: "ghost",
className: "text-red-600 h-8 px-2 py-1",
onClick: () => onRemoveOption(idx),
children: "Delete"
}
)
] })
] }, idx)),
node.options.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-gray-500 text-sm p-4 text-center bg-gray-50 rounded-md", children: "No options available. Use the `Add an option` button to add one." })
] }) }) })
] })
] })
] });
};
function RefreshIcon({ className, size = 16 }) {
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"svg",
{
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
className,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 3v5h-5" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M3 21v-5h5" })
]
}
);
}
const ChatPreview = ({
flow,
onNodeSelect
}) => {
const [messages, setMessages] = useState([]);
const [currentNodeId, setCurrentNodeId] = useState(null);
const [isChatActive, setIsChatActive] = useState(false);
const scrollAreaRef = useRef(null);
const startChat = () => {
const rootNode = flow.find((node) => node.id === 1);
if (rootNode) {
setMessages([{
type: "bot",
content: rootNode.title,
nodeId: rootNode.id
}]);
setCurrentNodeId(rootNode.id);
setIsChatActive(true);
}
};
const resetChat = () => {
setMessages([]);
setCurrentNodeId(null);
setIsChatActive(false);
};
const handleOptionClick = (option) => {
setMessages((prev) => [
...prev,
{
type: "user",
content: option.label
}
]);
const nextNode = flow.find((node) => node.id === option.nextId);
if (nextNode) {
setTimeout(() => {
setMessages((prev) => [
...prev,
{
type: "bot",
content: nextNode.title,
nodeId: nextNode.id
}
]);
setCurrentNodeId(nextNode.id);
if (onNodeSelect) {
onNodeSelect(nextNode.id);
}
scrollToBottom();
}, 300);
}
};
const scrollToBottom = () => {
setTimeout(() => {
if (scrollAreaRef.current) {
const scrollContainer = scrollAreaRef.current;
scrollContainer.scrollTop = scrollContainer.scrollHeight;
}
}, 100);
};
const getCurrentNodeOptions = () => {
if (!currentNodeId) return [];
const currentNode = flow.find((node) => node.id === currentNodeId);
return currentNode?.options || [];
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "h-full flex flex-col overflow-hidden", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "py-3 px-4 border-b shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-xl", children: "Chat Preview" }),
isChatActive ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
Button,
{
variant: "outline",
size: "sm",
onClick: resetChat,
className: "flex items-center gap-1",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(RefreshIcon, { className: "h-4 w-4" }),
"Reset"
]
}
) : /* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
variant: "outline",
size: "sm",
onClick: startChat,
className: "bg-blue-50 hover:bg-blue-100 border-blue-200 text-blue-800",
children: "Start Chat"
}
)
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, { className: "flex-1 flex flex-col p-0 overflow-hidden", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "h-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-4 space-y-4", ref: scrollAreaRef, children: messages.length > 0 ? messages.map((message, index) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `flex ${message.type === "user" ? "justify-end" : "justify-start"}`, children: [
message.type === "bot" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start max-w-[80%]", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-blue-600 text-white flex items-center justify-center mr-2 text-xs", children: "Bot" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-blue-100 dark:bg-blue-900/30 p-3 rounded-lg text-gray-800 dark:text-gray-100", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: message.content }),
message.nodeId === currentNodeId && getCurrentNodeOptions().length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 pt-2 border-t border-blue-200 dark:border-blue-800", children: getCurrentNodeOptions().map((opt, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
variant: "outline",
className: "w-full justify-start font-normal text-left h-auto py-2 mb-2 bg-white hover:bg-gray-100 text-gray-800 border-gray-200",
onClick: () => handleOptionClick(opt),
children: opt.label
},
idx
)) })
] })
] }),
message.type === "user" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start max-w-[80%] flex-row-reverse", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-gray-600 text-white flex items-center justify-center ml-2 text-xs", children: "User" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white p-3 rounded-lg", children: message.content })
] })
] }, index)) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-muted-foreground p-6 text-center", children: "Click the `Start Chat` button to begin testing your chatbot." }) }) }) }),
isChatActive && currentNodeId && messages.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "border-t p-3 bg-muted/10 shrink-0 text-center", children: getCurrentNodeOptions().length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground py-2", children: "No options available (end of conversation)" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground", children: "↑ Click on options in the message to continue the conversation" }) })
] })
] });
};
const generateNewId = (flow) => {
const ids = flow.map((node) => node.id);
return Math.max(...ids, 0) + 1;
};
const generateNodePositions = (flow) => {
const positions = {};
const rootNode = flow.find((n) => n.id === 1);
if (!rootNode) return positions;
const nodeMap = flow.reduce((map, node) => {
map[node.id] = node;
return map;
}, {});
const visited = /* @__PURE__ */ new Set();
const calculatePositions = (nodeId, depth, index) => {
if (visited.has(nodeId)) return;
visited.add(nodeId);
const node = nodeMap[nodeId];
if (!node) return;
const yPos = depth * 70;
const xPos = index * 30;
positions[nodeId] = { x: xPos, y: yPos };
node.options.forEach((opt, idx) => {
calculatePositions(opt.nextId, depth + 1, index + idx);
});
};
calculatePositions(rootNode.id, 0, 0);
return positions;
};
const exportFlowToFile = (flow) => {
const jsonString = JSON.stringify(flow, null, 2);
const blob = new Blob([jsonString], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "chatbot-flow.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
const parseImportedJson = (jsonString) => {
try {
const parsedFlow = JSON.parse(jsonString);
if (Array.isArray(parsedFlow) && parsedFlow.length > 0) {
return parsedFlow;
}
return null;
} catch (error) {
console.error("Invalid JSON:", error);
return null;
}
};
const updateFlowWithHierarchyPaths = (flow) => {
const nodeMap = {};
flow.forEach((node) => {
nodeMap[node.id] = { ...node };
});
flow.forEach((node) => {
node.options.forEach((option) => {
const targetNode = nodeMap[option.nextId];
if (targetNode && !targetNode.parentId) {
targetNode.parentId = node.id;
}
});
});
const rootNode = nodeMap[1];
if (rootNode) {
rootNode.hierarchyPath = "1";
const setHierarchyPaths = (nodeId, parentPath) => {
const childNodes = Object.values(nodeMap).filter((n) => n.parentId === nodeId);
childNodes.forEach((childNode, index) => {
childNode.hierarchyPath = `${parentPath}-${index + 1}`;
setHierarchyPaths(childNode.id, childNode.hierarchyPath);
});
};
setHierarchyPaths(rootNode.id, rootNode.hierarchyPath);
}
return Object.values(nodeMap);
};
const AddNodeDialog = lazy(() => import("./AddNodeDialog-DIxG6bI_.js"));
const EditOptionDialog = lazy(() => import("./EditOptionDialog-cNj4IWo4.js"));
const ImportDialog = lazy(() => import("./ImportDialog-CoTnWKf6.js"));
const ExportDialog = lazy(() => import("./ExportDialog-NOv29bKb.js"));
const DeleteNodeDialog = lazy(() => import("./DeleteNodeDialog-B51ZXHTR.js"));
const initialFlow = [
{
id: 1,
title: "Welcome to the chatflow",
options: [
{ label: "Option 1", nextId: 2 },
{ label: "Option 2", nextId: 3 },
{ label: "Option 3", nextId: 4 }
],
hierarchyPath: "1"
},
{
id: 2,
title: "Node 1 title",
options: [],
parentId: 1,
hierarchyPath: "1-1"
},
{
id: 3,
title: "Node 2 title",
options: [],
parentId: 1,
hierarchyPath: "1-2"
},
{
id: 4,
title: "Node 3 title",
options: [],
parentId: 1,
hierarchyPath: "1-3"
}
];
function ChatbotEditor() {
const hierarchicalInitialFlow = useMemo(
() => updateFlowWithHierarchyPaths(initialFlow),
[]
);
const [flow, setFlow] = useState(hierarchicalInitialFlow);
const [currentNodeId, setCurrentNodeId] = useState(1);
const [dialogState, setDialogState] = useState({
addNode: false,
addOption: false,
import: false,
export: false,
deleteNode: false
});
const [editingOptionIndex, setEditingOptionIndex] = useState(null);
const [nodeToDelete, setNodeToDelete] = useState(null);
const currentNode = useMemo(() => {
const foundNode = flow.find((n) => n.id === currentNodeId);
return foundNode || flow[0] || initialFlow[0];
}, [flow, currentNodeId]);
const nodePositions = useMemo(
() => generateNodePositions(flow),
[flow]
);
const toggleDialog = useCallback((dialogName, isOpen) => {
setDialogState((prev) => ({ ...prev, [dialogName]: isOpen }));
}, []);
const handleNodeSelect = useCallback((nodeId) => {
setCurrentNodeId(nodeId);
}, []);
const handleOpenAddNode = useCallback((parentId) => {
setCurrentNodeId(parentId);
toggleDialog("addNode", true);
}, [toggleDialog]);
const handleAddNode = useCallback((title) => {
setFlow((prevFlow) => {
const newId = generateNewId(prevFlow);
const parentNode = prevFlow.find((node) => node.id === currentNodeId);
if (!parentNode) return prevFlow;
const parentPath = parentNode.hierarchyPath || parentNode.id.toString();
const siblings = prevFlow.filter((node) => node.parentId === parentNode.id);
const childIndex = siblings.length + 1;
const hierarchyPath = `${parentPath}-${childIndex}`;
const newNode = {
id: newId,
title,
options: [],
parentId: parentNode.id,
hierarchyPath
};
const updatedParentNode = {
...parentNode,
options: [
...parentNode.options,
{ label: `Option to ${title}`, nextId: newId }
]
};
const updatedFlow = prevFlow.map(
(node) => node.id === parentNode.id ? updatedParentNode : node
);
setCurrentNodeId(newId);
return [...updatedFlow, newNode];
});
toggleDialog("addNode", false);
}, [currentNodeId, toggleDialog]);
const handleUpdateNode = useCallback((title) => {
setFlow((prevFlow) => prevFlow.map(
(node) => node.id === currentNodeId ? { ...node, title } : node
));
}, [currentNodeId]);
const handleOpenAddOption = useCallback(() => {
setEditingOptionIndex(null);
toggleDialog("addOption", true);
}, [toggleDialog]);
const handleEditOption = useCallback((index) => {
setEditingOptionIndex(index);
toggleDialog("addOption", true);
}, [toggleDialog]);
const handleSaveOption = useCallback((label, nextId) => {
setFlow((prevFlow) => prevFlow.map((node) => {
if (node.id === currentNodeId) {
let updatedOptions;
if (editingOptionIndex !== null) {
updatedOptions = node.options.map(
(opt, idx) => idx === editingOptionIndex ? { label, nextId } : opt
);
} else {
updatedOptions = [
...node.options,
{ label, nextId }
];
}
return { ...node, options: updatedOptions };
}
return node;
}));
toggleDialog("addOption", false);
setEditingOptionIndex(null);
}, [currentNodeId, editingOptionIndex, toggleDialog]);
const handleRemoveOption = useCallback((index) => {
setFlow((prevFlow) => prevFlow.map((node) => {
if (node.id === currentNodeId) {
const newOptions = [...node.options];
newOptions.splice(index, 1);
return { ...node, options: newOptions };
}
return node;
}));
}, [currentNodeId]);
const handleDeleteNode = useCallback((nodeId) => {
if (nodeId === 1) {
return;
}
setNodeToDelete(nodeId);
toggleDialog("deleteNode", true);
}, [toggleDialog]);
const handleConfirmDeleteNode = useCallback(() => {
if (!nodeToDelete) return;
setFlow((prevFlow) => {
const updatedFlow = prevFlow.filter((node) => node.id !== nodeToDelete);
const cleanedFlow = updatedFlow.map((node) => ({
...node,
options: node.options.filter((option) => option.nextId !== nodeToDelete)
}));
if (currentNodeId === nodeToDelete) {
setCurrentNodeId(1);
}
return cleanedFlow;
});
setNodeToDelete(null);
}, [nodeToDelete, currentNodeId]);
const handleImport = useCallback((importedFlow) => {
setFlow(importedFlow);
if (importedFlow.length > 0 && importedFlow[0]) {
setCurrentNodeId(importedFlow[0].id);
}
toggleDialog("import", false);
}, [toggleDialog]);
const handleExport = useCallback(() => {
exportFlowToFile(flow);
}, [flow]);
const editingOption = useMemo(() => {
if (editingOptionIndex !== null && currentNode?.options?.[editingOptionIndex]) {
return currentNode.options[editingOptionIndex];
}
return null;
}, [currentNode?.options, editingOptionIndex]);
const getHierarchyPathFromNextId = (nextId) => {
const targetNode = flow.find((n) => n.id === nextId);
return targetNode?.hierarchyPath || nextId.toString();
};
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex h-screen w-full bg-background text-foreground p-4 gap-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-3/5 h-full flex flex-col", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, { className: "h-full flex flex-col overflow-hidden", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, { className: "py-2 px-4 border-b shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-xl", children: "Workflow Editor" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex space-x-2", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
variant: "outline",
className: "bg-blue-50 hover:bg-blue-100 border-blue-200 text-blue-800 px-4 py-1 h-auto text-sm font-medium",
onClick: () => toggleDialog("export", true),
children: "Export"
}
),
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
variant: "outline",
className: "bg-blue-50 hover:bg-blue-100 border-blue-200 text-blue-800 px-4 py-1 h-auto text-sm font-medium",
onClick: () => toggleDialog("import", true),
children: "Import"
}
)
] })
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, { className: "p-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
FlowDiagram,
{
flow,
nodePositions,
currentNodeId,
onNodeSelect: handleNodeSelect,
onDeleteNode: handleDeleteNode,
onAddNode: handleOpenAddNode
}
) })
] }) }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-2/5 h-full flex flex-col gap-4", children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-[calc(50%-8px)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
ChatPreview,
{
flow,
onNodeSelect: handleNodeSelect
}
) }),
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-[calc(50%-8px)]", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
NodeEditor,
{
node: currentNode,
flow,
onUpdateNode: handleUpdateNode,
onAddOption: handleOpenAddOption,
onEditOption: handleEditOption,
onRemoveOption: handleRemoveOption
}
) })
] }),
dialogState.addNode && /* @__PURE__ */ jsxRuntimeExports.jsx(
AddNodeDialog,
{
open: dialogState.addNode,
onClose: () => toggleDialog("addNode", false),
onAddNode: handleAddNode
}
),
dialogState.addOption && /* @__PURE__ */ jsxRuntimeExports.jsx(
EditOptionDialog,
{
open: dialogState.addOption,
onClose: () => {
toggleDialog("addOption", false);
setEditingOptionIndex(null);
},
flow,
initialLabel: editingOption?.label || "",
initialNextId: editingOption ? getHierarchyPathFromNextId(editingOption.nextId) : "",
isEditing: editingOptionIndex !== null,
onSaveOption: handleSaveOption
}
),
dialogState.import && /* @__PURE__ */ jsxRuntimeExports.jsx(
ImportDialog,
{
open: dialogState.import,
onClose: () => toggleDialog("import", false),
onImport: handleImport
}
),
dialogState.export && /* @__PURE__ */ jsxRuntimeExports.jsx(
ExportDialog,
{
open: dialogState.export,
onClose: () => toggleDialog("export", false),
flow,
onExport: handleExport
}
),
dialogState.deleteNode && nodeToDelete && /* @__PURE__ */ jsxRuntimeExports.jsx(
DeleteNodeDialog,
{
open: dialogState.deleteNode,
onClose: () => {
toggleDialog("deleteNode", false);
setNodeToDelete(null);
},
onConfirm: handleConfirmDeleteNode,
nodeTitle: flow.find((n) => n.id === nodeToDelete)?.title || "",
nodeId: nodeToDelete
}
)
] });
}
export {
Button as B,
ChatbotEditor as C,
Input as I,
ScrollArea as S,
Card as a,
CardContent as b,
CardHeader as c,
CardTitle as d,
cn as e,
jsxRuntimeExports as j,
parseImportedJson as p
};