Merge pull request #428 from SuanmoSuanyangTechnology/feature/workflow_import_zy
Feature/workflow import zy
This commit is contained in:
26
web/src/assets/images/workflow/unknown.svg
Normal file
26
web/src/assets/images/workflow/unknown.svg
Normal 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 |
@@ -23,6 +23,7 @@ export interface ChatItem {
|
||||
status?: string;
|
||||
subContent?: Record<string, any>[];
|
||||
files?: any[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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: '情感引擎配置',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}</>
|
||||
)
|
||||
}]}
|
||||
/>
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user