Merge pull request #428 from SuanmoSuanyangTechnology/feature/workflow_import_zy
Feature/workflow import zy
This commit is contained in:
@@ -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