Merge pull request #428 from SuanmoSuanyangTechnology/feature/workflow_import_zy

Feature/workflow import zy
This commit is contained in:
yingzhao
2026-03-02 18:29:25 +08:00
committed by GitHub
14 changed files with 205 additions and 70 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>未知节点</title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="应用管理-工作流-配置-结束" transform="translate(-81, -222)">
<g id="未知节点" transform="translate(81, 222)">
<rect id="矩形" fill="#4DA8FF" x="0" y="0" width="24" height="24" rx="8"></rect>
<g id="未知" transform="translate(4, 4)" stroke="#FFFFFF">
<g id="编组-29" transform="translate(1.5, 1.5)">
<g id="编组-32" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.2">
<rect id="矩形" x="0" y="0" width="2.78571429" height="2.78571429" rx="0.5"></rect>
<rect id="矩形备份-2" x="10.2142857" y="0" width="2.78571429" height="2.78571429" rx="0.5"></rect>
<rect id="矩形" x="0" y="10.2142857" width="2.78571429" height="2.78571429" rx="0.5"></rect>
<rect id="矩形备份-2" x="10.2142857" y="10.2142857" width="2.78571429" height="2.78571429" rx="0.5"></rect>
<line x1="1.39285714" y1="2.78571429" x2="1.39285714" y2="10.2142857" id="路径-10"></line>
<line x1="11.6071429" y1="2.78571429" x2="11.6071429" y2="10.2142857" id="路径-10"></line>
<line x1="2.78571429" y1="1.39285714" x2="10.2142857" y2="1.39285714" id="路径-11"></line>
<line x1="2.78571429" y1="11.6071429" x2="10.2218696" y2="11.6071429" id="路径-12"></line>
</g>
<path d="M6.52432916,9.87401948 C6.13772983,9.87401948 5.82432916,9.56061881 5.82432916,9.17401948 C5.82432916,8.78742016 6.13772983,8.47401948 6.52432916,8.47401948 C6.91092848,8.47401948 7.22432916,8.78742016 7.22432916,9.17401948 C7.22432916,9.56061881 6.91092848,9.87401948 6.52432916,9.87401948 Z M8.31902118,5.62806989 C8.07271219,6.00231919 7.78239354,6.33608471 7.45624872,6.61995827 C7.28497017,6.76209568 7.1395834,6.94091014 7.02820004,7.14642499 C6.96128207,7.37469384 6.93624197,7.61622669 6.95463661,7.85600603 L5.97147432,7.85600603 L5.97147432,7.55843743 C5.96278552,7.25062126 6.0177749,6.94479831 6.131988,6.66574207 C6.29645546,6.367538 6.50993979,6.10844775 6.7606742,5.90275181 C6.96315112,5.72946983 7.15324854,5.53812192 7.32917462,5.33051172 C7.42118807,5.19526753 7.47065705,5.02865038 7.46962638,4.85745421 C7.46993348,4.61851645 7.37673645,4.39189307 7.21546905,4.23943033 C7.02808491,4.06206133 6.7899692,3.96970505 6.54664986,3.98002011 C6.30313821,3.97665394 6.06636072,4.07119573 5.87783981,4.24706618 C5.68380967,4.4710619 5.55131388,4.75479898 5.49660839,5.06346565 C5.4631688,5.29236377 4.49338418,5.39154635 4.50003404,4.92612469 C4.52964278,4.40848145 4.74202371,3.92666766 5.08863079,3.59089435 C5.47706668,3.20376734 5.97741851,2.99447314 6.49314835,3.003393 C7.03166324,2.97113454 7.56128279,3.16980909 7.97792275,3.56037182 C8.31228969,3.88267896 8.5034992,4.35809104 8.49960592,4.85745421 C8.50560934,5.12879049 8.44291047,5.3963476 8.31902118,5.62806989 L8.31902118,5.62806989 Z" id="形状结合" stroke-width="0.2" fill="#FFFFFF" fill-rule="nonzero"></path>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -23,6 +23,7 @@ export interface ChatItem {
status?: string;
subContent?: Record<string, any>[];
files?: any[];
error?: string;
}
/**

View File

@@ -1341,7 +1341,9 @@ export const en = {
dynamicMatchSkill: 'Dynamic Match Skill',
executeTask: 'Execute Task',
importWorkflow: 'Import Workflow',
import: 'Import Application',
importWorkflow: 'Third-Party Workflow',
importThirdParty: 'Import Workflow',
platform: 'Source Platform',
upload: 'Upload & Parse',
complex: 'Compatibility Analysis',
@@ -1355,6 +1357,7 @@ export const en = {
gotoList: 'Return to Application List',
gotoDetail: 'View Details',
dify: 'Dify',
pleaseUploadFile: 'Please upload workflow file',
},
userMemory: {
userMemory: 'User Memory',
@@ -1985,6 +1988,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
evolutionAndGovernance: 'Evolution & Governance',
self_optimization: 'Self Optimization',
process_evolution: 'Process Evolution',
unknown: 'Unknown Node',
clickToConfigure: 'Click to configure node parameters',
nodeProperties: 'Node Properties',

View File

@@ -736,7 +736,9 @@ export const zh = {
dynamicMatchSkill: '动态匹配技能',
executeTask: '执行任务',
importWorkflow: '导入工作流',
import: '导入应用',
importWorkflow: '第三方工作流',
importThirdParty: '导入工作流',
platform: '来源平台',
upload: '上传与解析',
complex: '兼容性分析',
@@ -751,6 +753,7 @@ export const zh = {
gotoList: '返回应用列表',
gotoDetail: '查看详情',
dify: 'Dify',
pleaseUploadFile: '请上传工作流文件',
},
table: {
totalRecords: '共 {{total}} 条记录'
@@ -1982,6 +1985,7 @@ export const zh = {
evolutionAndGovernance: '演化与治理',
self_optimization: '自我优化',
process_evolution: '流程演化',
unknown: '未知节点',
clickToConfigure: '点击配置节点参数',
nodeProperties: '节点属性',
@@ -2169,6 +2173,9 @@ export const zh = {
output_variables: '输出变量',
refreshTip: '同步函数签名至代码',
},
unknown: {
replaceNodeType: '替换节点'
},
name: '键',
type: '类型',
value: '值',
@@ -2200,7 +2207,8 @@ export const zh = {
iteration: '迭代',
input_cycle_vars: '初始循环变量',
output_cycle_vars: '最终循环变量',
}
},
sureReplace: '确认替换',
},
emotionEngine: {
emotionEngineConfig: '情感引擎配置',

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-28 14:08:14
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-28 16:20:40
* @Last Modified time: 2026-03-02 17:39:49
*/
/**
* UploadWorkflowModal Component
@@ -14,7 +14,7 @@
* 4. Completed - Show success message and options
*/
import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
import { Form, Select, Steps, Flex, Alert, Input, Button, Result } from 'antd';
import { Form, Select, Steps, Flex, Alert, Input, Button, Result, message } from 'antd';
import { useTranslation } from 'react-i18next';
import type { UploadWorkflowModalData, UploadData, UploadWorkflowModalRef } from '../types'
@@ -92,18 +92,22 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
switch(current) {
case 0: // Step 1: Upload file
if (!values.file || values.file.length === 0) {
message.warning(t('application.pleaseUploadFile'));
return;
}
const formData = new FormData();
setFirstFormData(values);
formData.append('platform', values.platform);
formData.append('file', values.file[0]);
// Call import workflow API
importWorkflow(formData)
.then(res => {
const response = res as UploadData;
const { errors, warnings } = response;
setData(response);
// Navigate to error/warning step if any, otherwise go to confirmation
if (errors.length || warnings.length) {
setCurrent(1);
@@ -203,7 +207,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
{t('common.cancel')}
</Button>,
<Button
key="submit"
key="nextStep"
type="primary"
loading={loading}
onClick={handleSave}
@@ -215,7 +219,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
return null;
default: // Steps 1-2
return [
<Button onClick={handleClose}>
<Button key="cancel" onClick={handleClose}>
{t('common.cancel')}
</Button>,
<Button key="back" onClick={handleLastStep}>
@@ -235,7 +239,7 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
return (
<RbModal
title={t('application.importWorkflow')}
title={t('application.importThirdParty')}
open={visible}
onCancel={handleClose}
okText={t('application.nextStep')}
@@ -262,7 +266,10 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
platform: 'dify'
}}
>
<Form.Item name="platform" label={t('application.platform')}>
<Form.Item
name="platform" label={t('application.platform')}
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<Select
placeholder={t('common.pleaseSelect')}
options={['dify'].map(value => ({
@@ -270,16 +277,17 @@ const UploadWorkflowModal = forwardRef<UploadWorkflowModalRef, UploadWorkflowMod
}))}
/>
</Form.Item>
<Form.Item name="file" valuePropName="fileList" noStyle>
<Form.Item
name="file"
valuePropName="fileList"
noStyle
>
<UploadFiles
isAutoUpload={false}
isCanDrag={true}
fileSize={100}
maxCount={1}
fileType={['yml', 'yaml', 'zip', 'json']}
onChange={(fileList) => {
console.log('文件列表变化:', fileList);
}}
/>
</Form.Item>
</Form>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:34:12
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-09 13:52:22
* @Last Modified time: 2026-03-02 17:48:51
*/
/**
* Application Management Page
@@ -12,7 +12,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Row, Col, App, Select, Space } from 'antd';
import { Button, Row, Col, App, Select, Space, Dropdown } from 'antd';
import clsx from 'clsx';
import { DeleteOutlined } from '@ant-design/icons';
import { useSearchParams } from 'react-router-dom'
@@ -86,6 +86,13 @@ const ApplicationManagement: React.FC = () => {
const handleImport = () => {
uploadWorkflowModalRef.current?.handleOpen()
}
const handleClick = ({ key }: { key: string } ) => {
switch (key) {
case 'thirdParty':
handleImport()
break;
}
}
return (
<>
<Row gutter={16} className="rb:mb-4">
@@ -111,9 +118,16 @@ const ApplicationManagement: React.FC = () => {
</Col>
<Col span={12} className="rb:text-right">
<Space size={12}>
<Button onClick={handleImport}>
{t('application.importWorkflow')}
</Button>
<Dropdown
menu={{ items: [
{ key: 'thirdParty', label: t('application.importWorkflow') },
], onClick: handleClick }}
placement="bottomRight"
>
<Button>
{t('application.import')}
</Button>
</Dropdown>
<Button type="primary" onClick={handleCreate}>
{t('application.createApplication')}
</Button>

View File

@@ -320,7 +320,8 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
newList[lastIndex] = {
...newList[lastIndex],
status,
content: newList[lastIndex].content === '' ? null : newList[lastIndex].content
error,
content: newList[lastIndex].content === '' ? null : newList[lastIndex].content,
}
}
return newList

View File

@@ -217,14 +217,20 @@ const Runtime: FC<{ item: ChatItem; index: number;}> = ({
children: (
detail
? (
<div className="rb:bg-[#FBFDFF] rb:rounded-md">
<Button type="link" icon={<ArrowLeftOutlined />} onClick={() => setDetail(null)} className="rb:px-0! rb:text-[12px]!">
{t('common.return')}
</Button>
{renderDetailChild(detail.subContent)}
</div>
)
: renderChild(item.subContent)
<div className="rb:bg-[#FBFDFF] rb:rounded-md">
<Button type="link" icon={<ArrowLeftOutlined />} onClick={() => setDetail(null)} className="rb:px-0! rb:text-[12px]!">
{t('common.return')}
</Button>
{renderDetailChild(detail.subContent)}
</div>
)
: <>
{item.error
? <div className={clsx("rb:bg-[#FBFDFF] rb:rounded-md rb:py-2 rb:px-3 ", getStatus('failed'))}>
<Markdown content={item.error} />
</div>
: renderChild(item.subContent)
}</>
)
}]}
/>

View File

@@ -30,7 +30,8 @@ export interface LexicalEditorProps {
lineHeight?: number;
size?: 'default' | 'small';
type?: 'input' | 'textarea',
language?: 'string' | 'jinja2'
language?: 'string' | 'jinja2';
className?: string;
}
const theme = {
@@ -58,7 +59,9 @@ const Editor: FC<LexicalEditorProps> =({
variant = 'borderless',
size = 'default',
type = 'textarea',
language = 'string'
language = 'string',
height,
className
}) => {
const [_count, setCount] = useState(0);
const [enableJinja2, setEnableJinja2] = useState(false)
@@ -156,23 +159,23 @@ const Editor: FC<LexicalEditorProps> =({
};
const minheight = useMemo(() => {
if (type === 'input') {
return `${size === 'small' ? 26 : 30}px`
return `${height ? height : size === 'small' ? 28 : 30}px`
}
return `${size === 'small' ? 60 : 120}px`
}, [type, size])
return `${height ? height : size === 'small' ? 60 : 120}px`
}, [type, size, height])
const fontSize = useMemo(() => {
return `${size === 'small' ? 12 : 14}px`
}, [size])
const lineHeight = useMemo(() => {
return `${size === 'small' ? 16 : 20}px`
return `${height ? height : size === 'small' ? 16 : 20}px`
}, [size])
const placeHolderMinheight = useMemo(() => {
return `${size === 'small' ? 16 : 30}px`
}, [type, size])
return `${height ? height : size === 'small' ? 16 : 30}px`
}, [type, size, height])
return (
<LexicalComposer initialConfig={initialConfig}>
<div style={{ position: 'relative' }}>
<div style={{ position: 'relative' }} className={className}>
<RichTextPlugin
contentEditable={
enableLineNumbers ? (

View File

@@ -1,9 +1,11 @@
import { type FC, useMemo } from 'react';
import { useTranslation } from 'react-i18next'
import { Button, Select, Table, Form, type TableProps } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin';
import Empty from '@/components/Empty';
import VariableSelect from '../VariableSelect';
import Editor from '../../Editor'
export interface TableRow {
key?: string;
@@ -21,7 +23,7 @@ interface EditableTableProps {
size?: "small"
}
const EditableTable: React.FC<EditableTableProps> = ({
const EditableTable: FC<EditableTableProps> = ({
parentName,
title,
options = [],
@@ -37,6 +39,13 @@ const EditableTable: React.FC<EditableTableProps> = ({
...(typeOptions.length > 0 && { type: typeOptions[0].value })
});
// Filter options based on boolean type if needed
const booleanFilterOptions = useMemo(() => {
return filterBooleanType
? options.filter(option => option.dataType !== 'boolean')
: options
}, [options, filterBooleanType])
const getColumns = (remove: (index: number) => void): TableProps<TableRow>['columns'] => {
const hasType = typeOptions.length > 0;
const cellClassName="rb:p-1!"
@@ -49,14 +58,12 @@ const EditableTable: React.FC<EditableTableProps> = ({
className: cellClassName,
render: (_: any, __: TableRow, index: number) => (
<Form.Item name={[index, 'name']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
// size="small"
options={options}
filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false}
<Editor
options={booleanFilterOptions}
type="input"
className={contentClassName}
size={size}
height={16}
/>
</Form.Item>
)
@@ -101,19 +108,17 @@ const EditableTable: React.FC<EditableTableProps> = ({
{(form) => {
const currentType = form.getFieldValue([...Array.isArray(parentName) ? parentName : [parentName], index, 'type']);
const filteredOptions = currentType === 'file'
? options.filter(option => option.dataType === 'file')
: options;
? booleanFilterOptions.filter(option => option.dataType.includes('file'))
: booleanFilterOptions;
return (
<Form.Item name={[index, 'value']} noStyle>
<VariableSelect
placeholder={t('common.pleaseSelect')}
// size="small"
<Editor
options={filteredOptions}
filterBooleanType={filterBooleanType}
popupMatchSelectWidth={false}
type="input"
className={contentClassName}
size={size}
height={16}
/>
</Form.Item>
);

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-09 18:35:43
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-09 18:35:43
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-02 17:24:51
*/
import { type FC, useRef, useState } from "react";
import { useTranslation } from 'react-i18next'
@@ -13,7 +13,6 @@ import Editor from '../../Editor'
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
import AuthConfigModal from './AuthConfigModal'
import type { AuthConfigModalRef, HttpRequestConfigForm } from './types'
import VariableSelect from "../VariableSelect";
import MessageEditor from '../MessageEditor'
import EditableTable from './EditableTable'
import { portTextAttrs } from '../../../constant'
@@ -159,7 +158,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
<EditableTable
size="small"
parentName={['body', 'data']}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number')}
options={options.filter(vo => vo.dataType === 'string' || vo.dataType === 'number' || vo.dataType.includes('file'))}
typeOptions={[
{ label: 'text', value: 'text' },
{ label: 'file', value: 'file' }
@@ -201,10 +200,10 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
}
{values?.body?.content_type === 'binary' &&
<Form.Item name={['body', 'data']} noStyle>
<VariableSelect
<Editor
placeholder={t('common.pleaseSelect')}
options={options.filter(vo => vo.dataType.includes('file'))}
filterBooleanType={true}
type="input"
size="small"
/>
</Form.Item>

View File

@@ -1,14 +1,14 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-03 15:39:59
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-11 12:07:06
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-02 17:06:41
*/
import { type FC, useEffect, useState, useMemo } from "react";
import clsx from 'clsx'
import { useTranslation } from 'react-i18next'
import { Graph, Node } from '@antv/x6';
import { Form, Input, Select, InputNumber, Switch, Divider, Space } from 'antd'
import { Form, Input, Select, InputNumber, Switch, Divider, Space, Button } from 'antd'
import { CaretDownOutlined, CaretRightOutlined } from '@ant-design/icons';
import type { NodeConfig, NodeProperties, ChatVariable } from '../../types'
@@ -36,6 +36,7 @@ import Editor, { type LexicalEditorProps } from "../Editor";
import RbSlider from './RbSlider'
import JinjaRender from './JinjaRender'
import CodeExecution from './CodeExecution'
import { nodeLibrary } from '../../constant';
/**
* Props for Properties component
@@ -69,7 +70,8 @@ interface PropertiesProps {
const Properties: FC<PropertiesProps> = ({
selectedNode,
graphRef,
chatVariables
chatVariables,
blankClick
}) => {
const { t } = useTranslation()
const [form] = Form.useForm<NodeConfig>();
@@ -80,9 +82,8 @@ const Properties: FC<PropertiesProps> = ({
useEffect(() => {
if (selectedNode?.getData()?.id) {
setOutputCollapsed(true)
} else {
form.resetFields()
}
form.resetFields()
}, [selectedNode?.getData()?.id])
useEffect(() => {
@@ -94,7 +95,7 @@ const Properties: FC<PropertiesProps> = ({
initialValue[key] = config[key].defaultValue
}
})
form.setFieldsValue({
type,
id: selectedNode.id,
@@ -380,6 +381,41 @@ const Properties: FC<PropertiesProps> = ({
}
}
console.log('variableList', variableList, currentNodeVariables)
const handleSureReplace = () => {
const { replaceNode } = values;
const nodeLibraryConfig = [...nodeLibrary]
.flatMap(category => category.nodes)
.find(n => n.type === replaceNode)
if (replaceNode && nodeLibraryConfig) {
// Preserve existing config values when switching node types
const currentData = selectedNode?.data || {};
const currentConfig = currentData.config || {};
const newConfig = nodeLibraryConfig.config || {};
// Merge configs: keep existing values for matching keys, add new keys from template
const mergedConfig: Record<string, any> = {};
Object.keys(newConfig).forEach(key => {
if (currentConfig[key] && currentConfig[key].defaultValue !== undefined) {
// Preserve existing value if it exists
mergedConfig[key] = {
...newConfig[key],
defaultValue: currentConfig[key].defaultValue
};
} else {
// Use new config template
mergedConfig[key] = { ...newConfig[key] };
}
});
selectedNode?.setData({
...currentData,
...nodeLibraryConfig,
config: mergedConfig
})
blankClick()
}
}
return (
<div className={clsx("rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3 rb:pb-6", styles.properties)}>
@@ -399,8 +435,27 @@ const Properties: FC<PropertiesProps> = ({
<Form.Item name="id" label="ID">
<Input disabled />
</Form.Item>
{selectedNode?.data?.type === 'http-request'
{selectedNode?.data?.type === 'unknown'
? <>
<Form.Item name="replaceNode" label={t('workflow.config.unknown.replaceNodeType')}>
<Select
options={nodeLibrary.map(category => ({
label: t(`workflow.${category.category}`),
options: category.nodes.filter(item => !['cycle-start', 'break'].includes(item.type)).map(node => ({
label: <div className="rb:flex rb:items-center rb:gap-2 rb:flex-1">
<img src={node.icon} className="rb:size-3.5" />
<div className="rb:wrap-break-word rb:line-clamp-1">{t(`workflow.${node.type}`)}</div>
</div>,
value: node.type
}))
}))}
placeholder={t('common.pleaseSelect')}
allowClear
/>
</Form.Item>
<Button type="primary" size="small" className="rb:text-[12px]!" onClick={handleSureReplace}>{t('workflow.sureReplace')}</Button>
</>
: selectedNode?.data?.type === 'http-request'
? <HttpRequest
options={variableList}
selectedNode={selectedNode}

View File

@@ -47,6 +47,7 @@ import breakIcon from '@/assets/images/workflow/break.png'
import assignerIcon from '@/assets/images/workflow/assigner.png'
import memoryReadIcon from '@/assets/images/workflow/memory-read.png'
import memoryWriteIcon from '@/assets/images/workflow/memory-write.png'
import unknownIcon from '@/assets/images/workflow/unknown.svg'
import { memoryConfigListUrl } from '@/api/memory'
@@ -524,6 +525,10 @@ export const nodeLibrary: NodeLibrary[] = [
// ]
// },
];
export const unknownNode = {
type: 'unknown',
icon: unknownIcon
}
export const nodeWidth = 240;
/**

View File

@@ -12,7 +12,7 @@ import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '
import { register } from '@antv/x6-react-shape';
import type { PortMetadata } from '@antv/x6/lib/model/port';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth } from '../constant';
import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color, portTextAttrs, defaultAbsolutePortGroups, nodeWidth, unknownNode } from '../constant';
import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types';
import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
@@ -128,7 +128,7 @@ export const useWorkflowGraph = ({
if (nodes.length) {
const nodeList = nodes.map(node => {
const { id, type, name, position, config = {} } = node
let nodeLibraryConfig = [...nodeLibrary]
let nodeLibraryConfig = [...nodeLibrary, { nodes: [unknownNode] }]
.flatMap(category => category.nodes)
.find(n => n.type === type)
nodeLibraryConfig = JSON.parse(JSON.stringify({ config: {}, ...nodeLibraryConfig })) as NodeProperties