feat: awel flow layout

This commit is contained in:
Aralhi
2024-01-30 16:04:02 +08:00
parent b447bffaff
commit 2f85af3fc0
21 changed files with 1593 additions and 12 deletions

View File

@@ -182,6 +182,16 @@ const en = {
choice_agent_tip: 'Please choose an agent',
no_context_tip: 'Please enter your question',
Terminal: 'Terminal',
awel_flow: 'AWEL Flow',
save: 'Save',
add_node: 'Add Node',
no_node: 'No Node',
connect_warning: 'Nodes cannot be connected',
flow_modal_title: 'Save Flow',
flow_name: 'Flow Name',
flow_name_required: 'Please enter the flow name',
save_flow_success: 'Save flow success',
delete_flow_confirm: 'Are you sure you want to delete this flow?',
} as const;
export type I18nKeys = keyof typeof en;
@@ -370,6 +380,16 @@ const zh: Resources['translation'] = {
choice_agent_tip: '请选择代理',
no_context_tip: '请输入你的问题',
Terminal: '终端',
awel_flow: 'AWEL 工作流',
save: '保存',
add_node: '添加节点',
no_node: '没有可编排节点',
connect_warning: '节点无法连接',
flow_modal_title: '保存工作流',
flow_name: '工作流名称',
flow_name_required: '请输入工作流名称',
save_flow_success: '保存工作流成功',
delete_flow_confirm: '确定删除该工作流吗?',
} as const;
i18n.use(initReactI18next).init({

View File

@@ -1,5 +1,5 @@
import { AxiosRequestConfig } from 'axios';
import { GET, POST } from '.';
import { DELETE, GET, POST, PUT } from '.';
import { DbListResponse, DbSupportTypeResponse, PostDbParams, ChatFeedBackSchema } from '@/types/db';
import { DialogueListResponse, IChatDialogueSchema, NewDialogueParam, SceneResponse, ChatHistoryResponse, FeedBack, IDB } from '@/types/chat';
import { IModelData, StartModelParams, BaseModelParams, SupportModel } from '@/types/model';
@@ -32,6 +32,7 @@ import {
ISyncBatchResponse,
} from '@/types/knowledge';
import { UpdatePromptParams, IPrompt, PromptParams } from '@/types/prompt';
import { IFlow, IFlowNode, IFlowResponse, IFlowUpdateParam } from '@/types/flow';
/** App */
export const postScenes = () => {
@@ -249,3 +250,28 @@ export const updatePrompt = (data: UpdatePromptParams) => {
export const addPrompt = (data: UpdatePromptParams) => {
return POST<UpdatePromptParams, []>('/prompt/add', data);
};
/** AWEL Flow */
export const addFlow = (data: IFlowUpdateParam) => {
return POST<IFlowUpdateParam, IFlow>('/api/v1/serve/awel/flows', data);
};
export const getFlows = () => {
return GET<null, IFlowResponse>('/api/v1/serve/awel/flows');
};
export const getFlowById = (id: string) => {
return GET<null, IFlow>(`/api/v1/serve/awel/flows/${id}`);
};
export const updateFlowById = (id: string, data: IFlowUpdateParam) => {
return PUT<IFlowUpdateParam, IFlow>(`/api/v1/serve/awel/flows/${id}`, data);
};
export const deleteFlowById = (id: string) => {
return DELETE<null, null>(`/api/v1/serve/awel/flows/${id}`);
};
export const getFlowNodes = () => {
return GET<null, Array<IFlowNode>>(`/api/v1/serve/awel/nodes`);
};

View File

@@ -0,0 +1,120 @@
import { apiInterceptors, getFlowNodes } from '@/client/api';
import { IFlowNode } from '@/types/flow';
import { PlusOutlined } from '@ant-design/icons';
import { Avatar, Badge, Button, Collapse, CollapseProps, Divider, Empty, Input, List, Popover } from 'antd';
import React, { DragEvent, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
const { Search } = Input;
const AddNodes: React.FC = () => {
const { t } = useTranslation();
const [nodes, setNodes] = useState<Array<IFlowNode>>([]);
const [operators, setOperators] = useState<Array<IFlowNode>>([]);
const [resources, setResources] = useState<Array<IFlowNode>>([]);
useEffect(() => {
getNodes();
}, []);
async function getNodes() {
const [_, data] = await apiInterceptors(getFlowNodes());
if (data && data.length > 0) {
setNodes(data);
groupNodes(data);
}
}
function groupNodes(data: IFlowNode[]) {
// show operator nodes first, then show resource nodes
setOperators((data || []).filter((node) => node.flow_type === 'operator'));
setResources((data || []).filter((node) => node.flow_type === 'resource'));
}
const items: CollapseProps['items'] = useMemo(
() => [
{
key: 'operator',
label: 'Operator',
children: renderNodes(operators),
extra: <Badge showZero count={operators.length || 0} style={{ backgroundColor: operators.length > 0 ? '#52c41a' : '#7f9474' }} />,
},
{
key: 'resource',
label: 'Resource',
children: renderNodes(resources),
extra: <Badge showZero count={resources.length || 0} style={{ backgroundColor: resources.length > 0 ? '#52c41a' : '#7f9474' }} />,
},
],
[operators, resources],
);
function searchNode(val: string) {
if (!val) {
groupNodes(nodes);
} else {
const lowerSearchTerm = val.toLowerCase();
const searchOperators = operators.filter((node) => node.label.toLowerCase().includes(lowerSearchTerm));
const searchResources = resources.filter((node) => node.label.toLowerCase().includes(lowerSearchTerm));
setOperators(searchOperators);
setResources(searchResources);
}
}
function onDragStart(event: DragEvent, node: IFlowNode) {
event.dataTransfer.setData('application/reactflow', JSON.stringify(node));
event.dataTransfer.effectAllowed = 'move';
}
function renderNodes(nodes: Array<IFlowNode>) {
if (nodes?.length > 0) {
return (
<List
className="overflow-hidden overflow-y-auto"
itemLayout="horizontal"
dataSource={nodes}
renderItem={(node) => (
<List.Item
className="cursor-move hover:bg-[#F1F5F9] dark:hover:bg-theme-dark p-0 py-2"
draggable
onDragStart={(event) => onDragStart(event, node)}
>
<List.Item.Meta
className="flex items-center justify-center"
avatar={<Avatar src={node.icon || '/icons/node/default_node_icon.svg'} size={'large'} />}
title={<p className="line-clamp-1 font-medium">{node.label}</p>}
description={<p className="line-clamp-2">{node.description}</p>}
/>
</List.Item>
)}
/>
);
} else {
return <Empty className="px-2" description={t('no_node')} />;
}
}
return (
<Popover
placement="bottom"
trigger={['click']}
content={
<div className="w-[320px] overflow-hidden overflow-y-auto scrollbar-default">
<p className="my-4 font-bold">{t('add_node')}</p>
<Search placeholder="Search node" onSearch={searchNode} />
<Divider className="my-2" />
<Collapse className="max-h-[538px]" size="small" defaultActiveKey={['operator']} ghost items={items} />
</div>
}
>
<Button
type="primary"
className="flex items-center justify-center rounded-full left-4 top-4"
style={{ zIndex: 1050 }}
icon={<PlusOutlined />}
></Button>
</Popover>
);
};
export default AddNodes;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { getBezierPath, EdgeProps, BaseEdge, useReactFlow } from 'reactflow';
const ButtonEdge: React.FC<EdgeProps> = ({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style = {}, data, markerEnd }) => {
const [edgePath, edgeCenterX, edgeCenterY] = getBezierPath({
sourceX,
sourceY,
sourcePosition,
targetX,
targetY,
targetPosition,
});
const reactFlow = useReactFlow();
function onEdgeClick(event: React.MouseEvent, id: string) {
event.stopPropagation();
reactFlow.setEdges(reactFlow.getEdges().filter((edge) => edge.id !== id));
}
return (
<>
<BaseEdge id={id} style={style} path={edgePath} markerEnd={markerEnd} />
<foreignObject
width={40}
height={40}
x={edgeCenterX - 40 / 2}
y={edgeCenterY - 40 / 2}
className="bg-transparent w-10 h-10 relative"
requiredExtensions="http://www.w3.org/1999/xhtml"
>
<button
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-5 h-5 rounded-full bg-stone-400 dark:bg-zinc-700 cursor-pointer text-sm"
onClick={(event) => onEdgeClick(event, id)}
>
×
</button>
</foreignObject>
</>
);
};
export default ButtonEdge;

View File

@@ -0,0 +1,122 @@
import { IFlowNode, IFlowNodeParameter } from '@/types/flow';
import Image from 'next/image';
import NodeParamHandler from './node-param-handler';
import classNames from 'classnames';
import { useState } from 'react';
import NodeHandler from './node-handler';
import { Popover } from 'antd';
import { CopyOutlined, DeleteOutlined } from '@ant-design/icons';
import { useReactFlow } from 'reactflow';
type CanvasNodeProps = {
data: IFlowNode;
};
const ICON_PATH_PREFIX = '/icons/node/';
function TypeLabel({ label }: { label: string }) {
return <div className="w-full h-8 bg-stone-100 dark:bg-zinc-700 px-2 flex items-center justify-center">{label}</div>;
}
const CanvasNode: React.FC<CanvasNodeProps> = ({ data }) => {
const node = data;
const { inputs, outputs, flow_type: flowType } = node;
const parameters = orderParams(node.parameters || []);
const [isHovered, setIsHovered] = useState(false);
const reactFlow = useReactFlow();
function orderParams(params: Array<IFlowNodeParameter>) {
// we show resource params first, and build-int params last
const resourceParams = params.filter((param) => param.category === 'resource');
const commonParams = params.filter((param) => param.category === 'common');
return [...resourceParams, ...commonParams];
}
function onHover() {
setIsHovered(true);
}
function onLeave() {
setIsHovered(false);
}
function copyNode() {}
function deleteNode() {
const nodes = reactFlow.getNodes();
console.log('delete node', nodes);
reactFlow.setNodes((nodes) => {
const list = nodes.filter((item) => item.id !== node.id);
return list;
});
reactFlow.setEdges((edges) => edges.filter((edge) => edge.source !== node.id && edge.target !== node.id));
}
function renderOutput(data: IFlowNode) {
if (flowType === 'operator' && outputs?.length > 0) {
return (
<>
<TypeLabel label="Outputs" />
{(outputs || []).map((output, index) => (
<NodeHandler key={`${data.id}_input_${index}`} node={data} data={output} type="source" label="outputs" index={index} />
))}
</>
);
} else if (flowType === 'resource') {
// resource nodes show output default
return (
<>
<TypeLabel label="Outputs" />
<NodeHandler key={`${data.id}_input_0`} node={data} data={data} type="source" label="outputs" index={0} />
</>
);
}
}
return (
<Popover
placement="rightTop"
content={
<div>
<CopyOutlined className="block text-lg cursor-pointer" onClick={copyNode} />
<DeleteOutlined className="block text-lg cursor-pointer" onClick={deleteNode} />
</div>
}
>
<div
className={classNames('w-72 h-auto rounded-xl shadow-md p-0 border bg-white dark:bg-zinc-800 cursor-grab', {
'border-blue-500': node.selected || isHovered,
'border-stone-400 dark:border-white': !node.selected && !isHovered,
'border-dashed': flowType !== 'operator',
})}
onMouseEnter={onHover}
onMouseLeave={onLeave}
>
{/* icon and label */}
<div className="flex flex-row items-center p-2">
<Image src={node.icon || `${ICON_PATH_PREFIX}${node.name || 'default_node_icon'}.svg`} width={24} height={24} alt="" />
<p className="ml-2 text-lg font-bold text-ellipsis overflow-hidden whitespace-nowrap">{node.label}</p>
</div>
{inputs && inputs.length > 0 && (
<>
<TypeLabel label="Inputs" />
{(inputs || []).map((input, index) => (
<NodeHandler key={`${node.id}_input_${index}`} node={node} data={input} type="target" label="inputs" index={index} />
))}
</>
)}
{parameters && parameters.length > 0 && (
<>
<TypeLabel label="Parameters" />
{(parameters || []).map((parameter, index) => (
<NodeParamHandler key={`${node.id}_param_${index}`} node={node} data={parameter} label="parameters" index={index} />
))}
</>
)}
{renderOutput(node)}
</div>
</Popover>
);
};
export default CanvasNode;

View File

@@ -0,0 +1,57 @@
import { apiInterceptors, deleteFlowById } from '@/client/api';
import { IFlow } from '@/types/flow';
import { DeleteFilled, WarningOutlined } from '@ant-design/icons';
import { Modal } from 'antd';
import Link from 'next/link';
import React from 'react';
import { useTranslation } from 'react-i18next';
interface FlowCardProps {
flow: IFlow;
deleteCallback: (uid: string) => void;
}
const FlowCard: React.FC<FlowCardProps> = ({ flow, deleteCallback }) => {
const { t } = useTranslation();
const [modal, contextHolder] = Modal.useModal();
async function deleteFlow() {
const [res] = await apiInterceptors(deleteFlowById(flow.uid));
deleteCallback && deleteCallback(flow.uid);
}
return (
<>
<Link href={`/flow/canvas?id=${flow.uid}`}>
<div className="relative flex flex-col p-4 w-72 h-32 rounded justify-between cursor-pointer text-black bg-white shadow-[0_8px_16px_-10px_rgba(100,100,100,.08)] hover:shadow-[0_14px_20px_-10px_rgba(100,100,100,.15)] dark:bg-[#232734] dark:text-white dark:hover:border-white transition-[transfrom_shadow] duration-300 hover:-translate-y-1">
<div className="flex items-center">
<div className="flex flex-col">
<h2 className="text-lg font-semibold">{flow.name}</h2>
</div>
</div>
<DeleteFilled
className="absolute top-4 right-4 text-[#ff1b2e] !text-lg"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
modal.confirm({
title: t('Tips'),
icon: <WarningOutlined />,
content: t('delete_flow_confirm'),
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
async onOk() {
deleteFlow();
},
});
}}
/>
</div>
</Link>
{contextHolder}
</>
);
};
export default FlowCard;

View File

@@ -0,0 +1,67 @@
import { Tooltip, Typography, message } from 'antd';
import React from 'react';
import { Connection, Handle, Position, useReactFlow } from 'reactflow';
import RequiredIcon from './required-icon';
import { InfoCircleOutlined } from '@ant-design/icons';
import { IFlowNode, IFlowNodeInput, IFlowNodeOutput, IFlowNodeParameter } from '@/types/flow';
import { useTranslation } from 'react-i18next';
interface NodeHandlerProps {
node: IFlowNode;
data: IFlowNodeInput | IFlowNodeParameter | IFlowNodeOutput;
type: 'source' | 'target';
label: 'inputs' | 'outputs' | 'parameters';
index: number;
}
// render react flow handle item
const NodeHandler: React.FC<NodeHandlerProps> = ({ node, data, type, label, index }) => {
const { t } = useTranslation();
const reactflow = useReactFlow();
function isValidConnection(connection: Connection) {
const { sourceHandle, targetHandle, source, target } = connection;
const sourceNode = reactflow.getNode(source!);
const targetNode = reactflow.getNode(target!);
const { flow_type: sourceFlowType } = sourceNode?.data;
const { flow_type: targetFlowType } = targetNode?.data;
const sourceLabel = sourceHandle?.split('|')[1];
const targetLabel = targetHandle?.split('|')[1];
const sourceIndex = sourceHandle?.split('|')[2];
const targetIndex = targetHandle?.split('|')[2];
const targetTypeCls = targetNode?.data[targetLabel!][targetIndex!].type_cls;
if (sourceFlowType === targetFlowType && sourceFlowType === 'operator') {
// operator to operator, only type_cls matched can be connected
const sourceTypeCls = sourceNode?.data[sourceLabel!][sourceIndex!].type_cls;
return sourceTypeCls === targetTypeCls;
} else if (sourceFlowType === 'resource' && targetFlowType === 'operator') {
// resource to operator, check operator type_cls and resource parent_cls
const sourceParentCls = sourceNode?.data.parent_cls;
return sourceParentCls.includes(targetTypeCls);
}
message.warning(t('connect_warning'));
return false;
}
return (
<div className="relative flex items-center">
<Handle
className="w-2 h-2"
type={type}
position={type === 'source' ? Position.Right : Position.Left}
id={`${node.id}|${label}|${index}`}
isValidConnection={(connection) => isValidConnection(connection)}
/>
<Typography className="p-2">
{data.label}:<RequiredIcon optional={data.optional} />
{data.description && (
<Tooltip title={data.description}>
<InfoCircleOutlined className="ml-2" />
</Tooltip>
)}
</Typography>
</div>
);
};
export default NodeHandler;

View File

@@ -0,0 +1,74 @@
import { IFlowNode, IFlowNodeParameter } from '@/types/flow';
import { Checkbox, Input, InputNumber } from 'antd';
import React from 'react';
import RequiredIcon from './required-icon';
import NodeHandler from './node-handler';
interface NodeParamHandlerProps {
node: IFlowNode;
data: IFlowNodeParameter;
label: 'inputs' | 'outputs' | 'parameters';
index: number; // index of array
}
// render node parameters item
const NodeParamHandler: React.FC<NodeParamHandlerProps> = ({ node, data, label, index }) => {
function handleChange(value: any) {
data.value = value;
}
if (data.category === 'resource') {
return <NodeHandler node={node} data={data} type="target" label={label} index={index} />;
} else if (data.category === 'common') {
const defaultValue = data.default || data.value;
switch (data.type_name) {
case 'int':
return (
<div className="p-2 text-sm">
<p>
{data.label}:<RequiredIcon optional={data.optional} />
</p>
<InputNumber
className="w-full"
defaultValue={defaultValue}
onChange={(e) => {
handleChange(e.target.value);
}}
/>
</div>
);
case 'str':
return (
<div className="p-2 text-sm">
<p>
{data.label}:<RequiredIcon optional={data.optional} />
</p>
<Input
className="w-full"
defaultValue={defaultValue}
onChange={(e) => {
handleChange(e.target.value);
}}
/>
</div>
);
case 'bool':
return (
<div className="p-2 text-sm">
<p>
{data.label}:<RequiredIcon optional={data.optional} />
<Checkbox
className="ml-2"
defaultChecked={defaultValue}
onChange={(e) => {
handleChange(e.target.value);
}}
/>
</p>
</div>
);
}
}
};
export default NodeParamHandler;

View File

@@ -0,0 +1,10 @@
import React from 'react';
const RequiredIcon: React.FC<{ optional?: boolean | undefined }> = ({ optional }) => {
if (optional) {
return null;
}
return <span className="text-red-600 align-middle inline-block">&nbsp;*</span>;
};
export default RequiredIcon;

View File

@@ -16,6 +16,7 @@ import Icon, {
MenuOutlined,
SettingOutlined,
BuildOutlined,
ForkOutlined,
} from '@ant-design/icons';
import { Modal, message, Tooltip, Dropdown } from 'antd';
import { ItemType } from 'antd/es/menu/hooks/useItems';
@@ -42,7 +43,7 @@ type RouteItem = {
};
function menuItemStyle(active?: boolean) {
return `flex items-center h-12 hover:bg-[#F1F5F9] dark:hover:bg-theme-dark text-base w-full transition-colors whitespace-nowrap px-4 ${
return `flex items-center h-10 hover:bg-[#F1F5F9] dark:hover:bg-theme-dark text-base w-full transition-colors whitespace-nowrap px-4 ${
active ? 'bg-[#F1F5F9] dark:bg-theme-dark' : ''
}`;
}
@@ -64,10 +65,10 @@ function SideBar() {
const routes = useMemo(() => {
const items: RouteItem[] = [
{
key: 'prompt',
name: t('Prompt'),
icon: <MessageOutlined />,
path: '/prompt',
key: 'flow',
name: t('awel_flow'),
icon: <ForkOutlined />,
path: '/flow',
},
{
key: 'database',
@@ -93,6 +94,12 @@ function SideBar() {
path: '/agent',
icon: <BuildOutlined />,
},
{
key: 'prompt',
name: t('Prompt'),
icon: <MessageOutlined />,
path: '/prompt',
},
];
return items;
}, [i18n.language]);
@@ -318,7 +325,7 @@ function SideBar() {
</div>
{/* Settings */}
<div className="pt-4">
<div className="max-h-52 overflow-y-auto">
<div className="max-h-52 overflow-y-auto scrollbar-default">
{routes.map((item) => (
<Link key={item.key} href={item.path} className={`${menuItemStyle(pathname === item.path)} overflow-hidden`}>
<>

437
web/package-lock.json generated
View File

@@ -34,6 +34,7 @@
"react-i18next": "^13.2.0",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.10.2",
"rehype-raw": "6.1.1",
"remark-gfm": "^3.0.1",
"sql-formatter": "^12.2.4",
@@ -2427,6 +2428,102 @@
"react-dom": ">=16.9.0"
}
},
"node_modules/@reactflow/background": {
"version": "11.3.7",
"resolved": "https://registry.npmmirror.com/@reactflow/background/-/background-11.3.7.tgz",
"integrity": "sha512-PhkvoFtO/NXJgFtBvfbPwdR/6/dl25egQlFhKWS3T4aYa7rh80dvf6dF3t6+JXJS4q5ToYJizD2/n8/qylo1yQ==",
"dependencies": {
"@reactflow/core": "11.10.2",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/controls": {
"version": "11.2.7",
"resolved": "https://registry.npmmirror.com/@reactflow/controls/-/controls-11.2.7.tgz",
"integrity": "sha512-mugzVALH/SuKlVKk+JCRm1OXQ+p8e9+k8PCTIaqL+nBl+lPF8KA4uMm8ApsOvhuSAb2A80ezewpyvYHr0qSYVA==",
"dependencies": {
"@reactflow/core": "11.10.2",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/core": {
"version": "11.10.2",
"resolved": "https://registry.npmmirror.com/@reactflow/core/-/core-11.10.2.tgz",
"integrity": "sha512-/cbTxtFpfkIGReSVkcnQhS4Jx4VFY2AhPlJ5n0sbPtnR7OWowF9zodh5Yyzr4j1NOUoBgJ9h+UqGEwwY2dbAlw==",
"dependencies": {
"@types/d3": "^7.4.0",
"@types/d3-drag": "^3.0.1",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/minimap": {
"version": "11.7.7",
"resolved": "https://registry.npmmirror.com/@reactflow/minimap/-/minimap-11.7.7.tgz",
"integrity": "sha512-Pwqw31tJ663cJur6ypqyJU33nPckvTepmz96erdQZoHsfOyLmFj4nXT7afC30DJ48lp0nfNsw+028mlf7f/h4g==",
"dependencies": {
"@reactflow/core": "11.10.2",
"@types/d3-selection": "^3.0.3",
"@types/d3-zoom": "^3.0.1",
"classcat": "^5.0.3",
"d3-selection": "^3.0.0",
"d3-zoom": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-resizer": {
"version": "2.2.7",
"resolved": "https://registry.npmmirror.com/@reactflow/node-resizer/-/node-resizer-2.2.7.tgz",
"integrity": "sha512-BMBstmWNiklHnnAjHu8irkiPQ8/k8nnjzqlTql4acbVhD6Tsdxx/t/saOkELmfQODqGZNiPw9+pHcAHgtE6oNQ==",
"dependencies": {
"@reactflow/core": "11.10.2",
"classcat": "^5.0.4",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@reactflow/node-toolbar": {
"version": "1.3.7",
"resolved": "https://registry.npmmirror.com/@reactflow/node-toolbar/-/node-toolbar-1.3.7.tgz",
"integrity": "sha512-75moEQKg23YKA3A2DNSFhq719ZPmby5mpwOD+NO7ZffJ88oMS/2eY8l8qpA3hvb1PTBHDxyKazhJirW+f4t0Wg==",
"dependencies": {
"@reactflow/core": "11.10.2",
"classcat": "^5.0.3",
"zustand": "^4.4.1"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/@rushstack/eslint-patch": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz",
@@ -2446,11 +2543,227 @@
"resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-gbiHvCuBS9aXkE3OEDfS69bscNLTYtbbx2TQf6WyOu+4eCH1AH1gPSiDGF2UzwkRFAbqKNsC5F0mY0xcaEHCbg=="
},
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmmirror.com/@types/d3/-/d3-7.4.3.tgz",
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
"dependencies": {
"@types/d3-array": "*",
"@types/d3-axis": "*",
"@types/d3-brush": "*",
"@types/d3-chord": "*",
"@types/d3-color": "*",
"@types/d3-contour": "*",
"@types/d3-delaunay": "*",
"@types/d3-dispatch": "*",
"@types/d3-drag": "*",
"@types/d3-dsv": "*",
"@types/d3-ease": "*",
"@types/d3-fetch": "*",
"@types/d3-force": "*",
"@types/d3-format": "*",
"@types/d3-geo": "*",
"@types/d3-hierarchy": "*",
"@types/d3-interpolate": "*",
"@types/d3-path": "*",
"@types/d3-polygon": "*",
"@types/d3-quadtree": "*",
"@types/d3-random": "*",
"@types/d3-scale": "*",
"@types/d3-scale-chromatic": "*",
"@types/d3-selection": "*",
"@types/d3-shape": "*",
"@types/d3-time": "*",
"@types/d3-time-format": "*",
"@types/d3-timer": "*",
"@types/d3-transition": "*",
"@types/d3-zoom": "*"
}
},
"node_modules/@types/d3-array": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.1.tgz",
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
},
"node_modules/@types/d3-axis": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-axis/-/d3-axis-3.0.6.tgz",
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-brush": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-brush/-/d3-brush-3.0.6.tgz",
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-chord": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-chord/-/d3-chord-3.0.6.tgz",
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="
},
"node_modules/@types/d3-color": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz",
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
},
"node_modules/@types/d3-contour": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-contour/-/d3-contour-3.0.6.tgz",
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
"dependencies": {
"@types/d3-array": "*",
"@types/geojson": "*"
}
},
"node_modules/@types/d3-delaunay": {
"version": "6.0.4",
"resolved": "https://registry.npmmirror.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="
},
"node_modules/@types/d3-dispatch": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
"integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ=="
},
"node_modules/@types/d3-drag": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz",
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-dsv": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz",
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
},
"node_modules/@types/d3-fetch": {
"version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
"dependencies": {
"@types/d3-dsv": "*"
}
},
"node_modules/@types/d3-force": {
"version": "3.0.9",
"resolved": "https://registry.npmmirror.com/@types/d3-force/-/d3-force-3.0.9.tgz",
"integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA=="
},
"node_modules/@types/d3-format": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/d3-format/-/d3-format-3.0.4.tgz",
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="
},
"node_modules/@types/d3-geo": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/@types/d3-geo/-/d3-geo-3.1.0.tgz",
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
"dependencies": {
"@types/geojson": "*"
}
},
"node_modules/@types/d3-hierarchy": {
"version": "3.1.6",
"resolved": "https://registry.npmmirror.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz",
"integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.0.2.tgz",
"integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA=="
},
"node_modules/@types/d3-polygon": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="
},
"node_modules/@types/d3-quadtree": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="
},
"node_modules/@types/d3-random": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-random/-/d3-random-3.0.3.tgz",
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.8.tgz",
"integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-scale-chromatic": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz",
"integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw=="
},
"node_modules/@types/d3-selection": {
"version": "3.0.10",
"resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.10.tgz",
"integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg=="
},
"node_modules/@types/d3-shape": {
"version": "3.1.6",
"resolved": "https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.6.tgz",
"integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.3.tgz",
"integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw=="
},
"node_modules/@types/d3-time-format": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="
},
"node_modules/@types/d3-timer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-2.0.1.tgz",
"integrity": "sha512-TF8aoF5cHcLO7W7403blM7L1T+6NF3XMyN3fxyUolq2uOcFeicG/khQg/dGxiCJWoAcmYulYN7LYSRKO54IXaA==",
"peer": true
"integrity": "sha512-TF8aoF5cHcLO7W7403blM7L1T+6NF3XMyN3fxyUolq2uOcFeicG/khQg/dGxiCJWoAcmYulYN7LYSRKO54IXaA=="
},
"node_modules/@types/d3-transition": {
"version": "3.0.8",
"resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.8.tgz",
"integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==",
"dependencies": {
"@types/d3-selection": "*"
}
},
"node_modules/@types/d3-zoom": {
"version": "3.0.8",
"resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
"dependencies": {
"@types/d3-interpolate": "*",
"@types/d3-selection": "*"
}
},
"node_modules/@types/debug": {
"version": "4.1.8",
@@ -2460,6 +2773,11 @@
"@types/ms": "*"
}
},
"node_modules/@types/geojson": {
"version": "7946.0.13",
"resolved": "https://registry.npmmirror.com/@types/geojson/-/geojson-7946.0.13.tgz",
"integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ=="
},
"node_modules/@types/hast": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.5.tgz",
@@ -3420,6 +3738,11 @@
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"node_modules/classcat": {
"version": "5.0.4",
"resolved": "https://registry.npmmirror.com/classcat/-/classcat-5.0.4.tgz",
"integrity": "sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g=="
},
"node_modules/classnames": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
@@ -3639,6 +3962,18 @@
"node": ">=12"
}
},
"node_modules/d3-drag": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz",
"integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-selection": "3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-dsv": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
@@ -3756,6 +4091,14 @@
"node": ">=12"
}
},
"node_modules/d3-selection": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz",
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-shape": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
@@ -3772,11 +4115,44 @@
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz",
"integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw=="
},
"node_modules/d3-transition": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz",
"integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==",
"dependencies": {
"d3-color": "1 - 3",
"d3-dispatch": "1 - 3",
"d3-ease": "1 - 3",
"d3-interpolate": "1 - 3",
"d3-timer": "1 - 3"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"d3-selection": "2 - 3"
}
},
"node_modules/d3-voronoi": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
},
"node_modules/d3-zoom": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz",
"integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==",
"dependencies": {
"d3-dispatch": "1 - 3",
"d3-drag": "2 - 3",
"d3-interpolate": "1 - 3",
"d3-selection": "2 - 3",
"d3-transition": "2 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/dagre": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.5.tgz",
@@ -8565,6 +8941,23 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/reactflow": {
"version": "11.10.2",
"resolved": "https://registry.npmmirror.com/reactflow/-/reactflow-11.10.2.tgz",
"integrity": "sha512-tqQJfPEiIkXonT3piVYf+F9CvABI5e28t5I6rpaLTnO8YVCAOh1h0f+ziDKz0Bx9Y2B/mFgyz+H7LZeUp/+lhQ==",
"dependencies": {
"@reactflow/background": "11.3.7",
"@reactflow/controls": "11.2.7",
"@reactflow/core": "11.10.2",
"@reactflow/minimap": "11.7.7",
"@reactflow/node-resizer": "2.2.7",
"@reactflow/node-toolbar": "1.3.7"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -9322,6 +9715,11 @@
"resolved": "https://registry.npmjs.org/svg-path-parser/-/svg-path-parser-1.1.0.tgz",
"integrity": "sha512-jGCUqcQyXpfe38R7RFfhrMyfXcBmpMNJI/B+4CE9/Unkh98UporAc461GTthv+TVDuZXsBx7/WiwJb1Oh4tt4A=="
},
"node_modules/tailwind-scrollbar-hide": {
"version": "1.1.7",
"resolved": "https://registry.npmmirror.com/tailwind-scrollbar-hide/-/tailwind-scrollbar-hide-1.1.7.tgz",
"integrity": "sha512-X324n9OtpTmOMqEgDUEA/RgLrNfBF/jwJdctaPZDzB3mppxJk7TLIDmOreEDm1Bq4R9LSPu4Epf8VSdovNU+iA=="
},
"node_modules/tailwindcss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
@@ -9868,6 +10266,14 @@
"punycode": "^2.1.0"
}
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -10147,6 +10553,33 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.0.tgz",
"integrity": "sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==",
"dependencies": {
"use-sync-external-store": "1.2.0"
},
"engines": {
"node": ">=12.7.0"
},
"peerDependencies": {
"@types/react": ">=16.8",
"immer": ">=9.0.6",
"react": ">=16.8"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
}
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View File

@@ -37,6 +37,7 @@
"react-i18next": "^13.2.0",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.10.2",
"rehype-raw": "6.1.1",
"remark-gfm": "^3.0.1",
"sql-formatter": "^12.2.4",

View File

@@ -0,0 +1,213 @@
import { addFlow, apiInterceptors, getFlowById, updateFlowById } from '@/client/api';
import MuiLoading from '@/components/common/loading';
import AddNodes from '@/components/flow/add-nodes';
import ButtonEdge from '@/components/flow/button-edge';
import CanvasNode from '@/components/flow/canvas-node';
import { IFlowData } from '@/types/flow';
import { getUniqueNodeId, mapHumpToUnderline } from '@/utils/flow';
import { SaveOutlined } from '@ant-design/icons';
import { Divider, Input, Modal, message } from 'antd';
import { useSearchParams } from 'next/navigation';
import React, { DragEvent, useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ReactFlow, { Background, Connection, Controls, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow, Node } from 'reactflow';
import 'reactflow/dist/style.css';
interface Props {
// Define your component props here
}
const nodeTypes = { customNode: CanvasNode };
const edgeTypes = { buttonedge: ButtonEdge };
const Canvas: React.FC<Props> = () => {
const { t } = useTranslation();
const [messageApi, contextHolder] = message.useMessage();
const searchParams = useSearchParams();
const id = searchParams?.get('id') || '';
const reactFlow = useReactFlow();
const [loading, setLoading] = useState(false);
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [isModalVisible, setIsModalVisible] = useState(false);
const [name, setName] = useState('');
async function getFlowData() {
setLoading(true);
const [_, data] = await apiInterceptors(getFlowById(id));
if (data) {
const flowData = mapHumpToUnderline(data.flow_data);
setName(data.name);
setNodes(flowData.nodes.map((node) => ({ ...node, type: 'customNode' })));
setEdges(flowData.edges.map((edge) => ({ ...edge, type: 'buttonedge' })));
}
setLoading(false);
}
useEffect(() => {
id && getFlowData();
}, [id]);
function onNodesClick(event: any, clickedNode: Node) {
reactFlow.setNodes((nds) =>
nds.map((node) => {
if (node.id === clickedNode.id) {
node.data = {
...node.data,
selected: true,
};
} else {
node.data = {
...node.data,
selected: false,
};
}
return node;
}),
);
}
function onConnect(connection: Connection) {
const newEdge = {
...connection,
type: 'buttonedge',
id: `${connection.source}|${connection.target}`,
};
setEdges((eds) => addEdge(newEdge, eds));
}
const onDrop = useCallback(
(event: DragEvent) => {
event.preventDefault();
const reactFlowBounds = reactFlowWrapper.current!.getBoundingClientRect();
let nodeStr = event.dataTransfer.getData('application/reactflow');
if (!nodeStr || typeof nodeStr === 'undefined') {
return;
}
const nodeData = JSON.parse(nodeStr);
const position = reactFlow.screenToFlowPosition({
x: event.clientX - reactFlowBounds.left,
y: event.clientY - reactFlowBounds.top,
});
const nodeId = getUniqueNodeId(nodeData, reactFlow.getNodes());
const newNode = {
id: nodeId,
position,
type: 'customNode',
data: nodeData,
};
setNodes((nds) =>
nds.concat(newNode).map((node) => {
if (node.id === newNode.id) {
node.data = {
...node.data,
selected: true,
};
} else {
node.data = {
...node.data,
selected: false,
};
}
return node;
}),
);
},
[reactFlow],
);
const onDragOver = useCallback((event: DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
}, []);
function clickSave() {
if (id) {
handleSaveFlow();
} else {
setIsModalVisible(true);
}
}
async function handleSaveFlow() {
const reactFlowObject = mapHumpToUnderline(reactFlow.toObject() as IFlowData);
if (id) {
const [, , res] = await apiInterceptors(updateFlowById(id, { name, uid: id, flow_data: reactFlowObject }));
if (res?.success) {
messageApi.success(t('save_flow_success'));
} else if (res?.err_msg) {
messageApi.error(res?.err_msg);
}
} else {
if (!name) {
return messageApi.warning(t('flow_name_required'));
}
const [_, res] = await apiInterceptors(addFlow({ name, flow_data: reactFlowObject }));
if (res?.uid) {
messageApi.success(t('save_flow_success'));
const history = window.history;
history.pushState(null, '', `/flow/canvas?id=${res.uid}`);
}
setIsModalVisible(false);
}
}
return (
<div className="p-2">
<MuiLoading visible={loading} />
<div className="my-2 flex flex-row justify-end items-center">
<div className="w-8 h-8 rounded-md bg-stone-300 flext justify-center items-center text-center">
<SaveOutlined className="text-xl" onClick={clickSave} />
</div>
</div>
<Divider className="mt-2 mb-0" />
<div className="min-h-screen w-full h-full" ref={reactFlowWrapper}>
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeClick={onNodesClick}
onConnect={onConnect}
onDrop={onDrop}
onDragOver={onDragOver}
minZoom={0.1}
fitView
>
<Controls className="flex flex-row items-center" position="bottom-center" />
<Background color="#aaa" gap={16} />
<AddNodes />
</ReactFlow>
</div>
<Modal
title={t('flow_modal_title')}
open={isModalVisible}
onOk={handleSaveFlow}
onCancel={() => {
setIsModalVisible(false);
}}
>
<>
<p>{t('flow_name')}</p>
<Input
onChange={(e) => {
setName(e.target.value);
}}
/>
</>
</Modal>
{contextHolder}
</div>
);
};
export default function CanvasWrapper() {
return (
<ReactFlowProvider>
<Canvas />
</ReactFlowProvider>
);
}

49
web/pages/flow/index.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { apiInterceptors, getFlows } from '@/client/api';
import MuiLoading from '@/components/common/loading';
import FlowCard from '@/components/flow/flow-card';
import { IFlow } from '@/types/flow';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Empty } from 'antd';
import Link from 'next/link';
import React, { useEffect, useState } from 'react';
function Flow() {
const [loading, setLoading] = useState(false);
const [flowList, setFlowList] = useState<Array<IFlow>>([]);
async function getFlowList() {
setLoading(true);
const [_, data] = await apiInterceptors(getFlows());
setLoading(false);
setFlowList(data?.items ?? []);
}
useEffect(() => {
getFlowList();
}, []);
function updateFlowList(uid: string) {
setFlowList((flows) => flows.filter((flow) => flow.uid !== uid));
}
return (
<div className="relative p-4 md:p-6 bg-[#FAFAFA] dark:bg-transparent min-h-full overflow-y-auto">
<MuiLoading visible={loading} />
<div className="mb-4">
<Link href="/flow/canvas">
<Button type="primary" className="flex items-center" icon={<PlusOutlined />}>
New AWEL Flow
</Button>
</Link>
</div>
<div className="min-h-[600px] flex flex-wrap gap-2 md:gap-4 justify-start items-start">
{flowList.map((flow) => (
<FlowCard key={flow.uid} flow={flow} deleteCallback={updateFlowList} />
))}
{flowList.length === 0 && <Empty description="No flow found" />}
</div>
</div>
);
}
export default Flow;

View File

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="88" fill="none">
<path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
<path
fill="#FF9D0B"
d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
/>
<path
fill="#3A3B45"
d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
/>
<path
fill="#FF323D"
d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
/>
<path
fill="#3A3B45"
fillRule="evenodd"
d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
clip-rule="evenodd"
/>
<path
fill="#FF9D0B"
d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
/>
<path
fill="#FF9D0B"
d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="88" fill="none">
<path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
<path
fill="#FF9D0B"
d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
/>
<path
fill="#3A3B45"
d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
/>
<path
fill="#FF323D"
d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
/>
<path
fill="#3A3B45"
fillRule="evenodd"
d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
clip-rule="evenodd"
/>
<path
fill="#FF9D0B"
d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
/>
<path
fill="#FF9D0B"
d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="88" fill="none">
<path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
<path
fill="#FF9D0B"
d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
/>
<path
fill="#3A3B45"
d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
/>
<path
fill="#FF323D"
d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
/>
<path
fill="#3A3B45"
fillRule="evenodd"
d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
clip-rule="evenodd"
/>
<path
fill="#FF9D0B"
d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
/>
<path
fill="#FF9D0B"
d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="95" height="88" fill="none">
<path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
<path
fill="#FF9D0B"
d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
/>
<path
fill="#3A3B45"
d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
/>
<path
fill="#FF323D"
d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
/>
<path
fill="#3A3B45"
fillRule="evenodd"
d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
clip-rule="evenodd"
/>
<path
fill="#FF9D0B"
d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
/>
<path
fill="#FF9D0B"
d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
/>
<path
fill="#FFD21E"
d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -18,6 +18,9 @@ body {
color: #f7f7f7;
background-color: #151622;
}
.dark-sub-bg {
background-color: rgb(35, 38, 44);
}
.ant-btn-primary {
background-color: var(--antd-primary-color);
@@ -48,7 +51,10 @@ table tr td {
white-space: nowrap;
text-overflow: ellipsis;
}
.scrollbar-default::-webkit-scrollbar {
display: block;
width: 6px;
}
/* 自定义滚动条样式 */
::-webkit-scrollbar {
display: none;
@@ -70,7 +76,7 @@ table tr td {
color: white;
}
:where(.css-dev-only-do-not-override-18iikkb).ant-form-item .ant-form-item-label>label {
:where(.css-dev-only-do-not-override-18iikkb).ant-form-item .ant-form-item-label > label {
height: 36px;
}
@@ -78,4 +84,4 @@ table tr td {
to {
transform: rotate(360deg);
}
}
}

116
web/types/flow.ts Normal file
View File

@@ -0,0 +1,116 @@
import { Node } from 'reactflow';
export type IFlowUpdateParam = {
name: string;
uid?: string;
flow_data: IFlowData;
};
export type IFlow = {
uid: string;
name: string;
flow_data: IFlowData;
};
export type IFlowResponse = {
items: Array<IFlow>;
total_count: number;
total_pages: number;
page: number;
page_size: number;
};
export type IFlowNodeParameter = {
id: string;
type_name: string;
type_cls: string;
label: string;
name: string;
category: string;
optional: boolean;
default?: any;
placeholder?: any;
description: string;
options?: any;
value: any;
};
export type IFlowNodeInput = {
type_name: string;
type_cls: string;
label: string;
name: string;
description: string;
id: string;
optional?: boolean | undefined;
value: any;
};
export type IFlowNodeOutput = {
type_name: string;
type_cls: string;
label: string;
name: string;
description: string;
id: string;
optional?: boolean | undefined;
};
export type IFlowNode = Node & {
type_name: string;
type_cls: string;
type: string;
label: string;
name: string;
description: string;
category: string;
flow_type: 'resource' | 'operator';
icon?: string;
documentation_url?: null;
id: string;
tags?: any;
parameters: Array<IFlowNodeParameter>;
inputs: Array<IFlowNodeInput>;
outputs: Array<IFlowNodeOutput>;
version: string;
};
interface Position {
x: number;
y: number;
zoom: number;
}
// flodata, the data of the flow
export type IFlowDataNode = {
width: number;
height: number;
id: string;
position: Position;
position_absolute?: Position;
positionAbsolute?: Position;
data: IFlowNode;
};
export type IFlowDataEdge = {
source: string;
target: string;
source_handle?: string;
sourceHandle?: string;
target_handle?: string;
targetHandle?: string;
id: string;
type: string;
};
export type IFlowDataViewport = {
x: number;
y: number;
zoom: number;
};
export type IFlowData = {
nodes: Array<IFlowDataNode>;
edges: Array<IFlowDataEdge>;
viewport: IFlowDataViewport;
};

71
web/utils/flow.ts Normal file
View File

@@ -0,0 +1,71 @@
import { IFlowData, IFlowNode } from '@/types/flow';
import { Node } from 'reactflow';
export const getUniqueNodeId = (nodeData: IFlowNode, nodes: Node[]) => {
let count = 0;
nodes.forEach((node) => {
if (node.data.name === nodeData.name) {
count++;
}
});
return `${nodeData.id}_${count}`;
};
// 驼峰转下划线,接口协议字段命名规范
export const mapHumpToUnderline = (flowData: IFlowData) => {
/**
* sourceHandle -> source_handle,
* targetHandle -> target_handle,
* positionAbsolute -> position_absolute
*/
const { nodes, edges, ...rest } = flowData;
const newNodes = nodes.map((node) => {
const { positionAbsolute, ...rest } = node;
return {
position_absolute: positionAbsolute,
...rest,
};
});
const newEdges = edges.map((edge) => {
const { sourceHandle, targetHandle, ...rest } = edge;
return {
source_handle: sourceHandle,
target_handle: targetHandle,
...rest,
};
});
return {
nodes: newNodes,
edges: newEdges,
...rest,
};
};
export const mapUnderlineToHump = (flowData: IFlowData) => {
/**
* source_handle -> sourceHandle,
* target_handle -> targetHandle,
* position_absolute -> positionAbsolute
*/
const { nodes, edges, ...rest } = flowData;
const newNodes = nodes.map((node) => {
const { position_absolute, ...rest } = node;
return {
positionAbsolute: position_absolute,
...rest,
};
});
const newEdges = edges.map((edge) => {
const { source_handle, target_handle, ...rest } = edge;
return {
sourceHandle: source_handle,
targetHandle: target_handle,
...rest,
};
});
return {
nodes: newNodes,
edges: newEdges,
...rest,
};
};