246 lines
9.8 KiB
TypeScript
246 lines
9.8 KiB
TypeScript
import { type FC, useEffect, useState, useRef } from "react";
|
|
import { useTranslation } from 'react-i18next'
|
|
import { Graph, Node } from '@antv/x6';
|
|
import { Form, Input, Button, Select, InputNumber, Slider, Space, Divider, App } from 'antd'
|
|
|
|
import type { NodeConfig, NodeProperties, StartVariableItem, VariableEditModalRef } from '../../types'
|
|
import Empty from '@/components/Empty';
|
|
import emptyIcon from '@/assets/images/workflow/empty.png'
|
|
import CustomSelect from "@/components/CustomSelect";
|
|
import VariableEditModal from './VariableEditModal';
|
|
import MessageEditor from './MessageEditor'
|
|
|
|
interface PropertiesProps {
|
|
selectedNode?: Node | null;
|
|
setSelectedNode: (node: Node | null) => void;
|
|
graphRef: React.MutableRefObject<Graph | undefined>;
|
|
blankClick: () => void;
|
|
deleteEvent: () => void;
|
|
copyEvent: () => void;
|
|
parseEvent: () => void;
|
|
}
|
|
const Properties: FC<PropertiesProps> = ({
|
|
selectedNode,
|
|
graphRef,
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const { modal } = App.useApp()
|
|
const [form] = Form.useForm<NodeConfig>();
|
|
const [configs, setConfigs] = useState<Record<string,NodeConfig>>({} as Record<string,NodeConfig>)
|
|
const values = Form.useWatch([], form);
|
|
const variableModalRef = useRef<VariableEditModalRef>(null)
|
|
const [editIndex, setEditIndex] = useState<number | null>(null)
|
|
|
|
useEffect(() => {
|
|
form.resetFields()
|
|
if (selectedNode && form) {
|
|
const { type = 'default', name = '', config } = selectedNode.getData() || {}
|
|
const initialValue: Record<string, any> = {}
|
|
Object.keys(config || {}).forEach(key => {
|
|
if (config && config[key] && 'defaultValue' in config[key]) {
|
|
initialValue[key] = config[key].defaultValue
|
|
}
|
|
})
|
|
|
|
form.setFieldsValue({
|
|
type,
|
|
id: selectedNode.id,
|
|
name,
|
|
...initialValue,
|
|
})
|
|
setConfigs(config || {})
|
|
}
|
|
}, [selectedNode, form])
|
|
|
|
const updateNodeLabel = (newLabel: string) => {
|
|
if (selectedNode && form) {
|
|
const nodeData = selectedNode.data as NodeProperties;
|
|
selectedNode.setAttrByPath('text/text', `${nodeData.icon} ${newLabel}`);
|
|
selectedNode.setData({ ...selectedNode.data, name: newLabel });
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
console.log('values', values)
|
|
if (values && selectedNode) {
|
|
const { id, ...rest } = values
|
|
|
|
|
|
Object.keys(values).forEach(key => {
|
|
if (selectedNode.data?.config[key]) {
|
|
selectedNode.data.config[key].defaultValue = values[key]
|
|
}
|
|
})
|
|
|
|
selectedNode?.setData({ ...selectedNode.data, ...rest })
|
|
}
|
|
}, [values, selectedNode])
|
|
|
|
const handleAddVariable = () => {
|
|
variableModalRef.current?.handleOpen()
|
|
}
|
|
const handleEditVariable = (index: number, vo: StartVariableItem) => {
|
|
variableModalRef.current?.handleOpen(vo)
|
|
setEditIndex(index)
|
|
}
|
|
const handleRefreshVariable = (value: StartVariableItem) => {
|
|
if (!selectedNode) return
|
|
if (editIndex !== null) {
|
|
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
|
|
defaultValue[editIndex] = value
|
|
selectedNode.data.config.variables.defaultValue = [...defaultValue]
|
|
} else {
|
|
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
|
|
selectedNode.data.config.variables.defaultValue = [...defaultValue, value]
|
|
}
|
|
selectedNode?.setData({ ...selectedNode.data})
|
|
|
|
setConfigs({ ...selectedNode.data.config})
|
|
}
|
|
const handleDeleteVariable = (index: number, vo: StartVariableItem) => {
|
|
if (!selectedNode) return
|
|
|
|
modal.confirm({
|
|
title: t('common.confirmDeleteDesc', { name: vo.name }),
|
|
okText: t('common.delete'),
|
|
okType: 'danger',
|
|
onOk: () => {
|
|
const defaultValue = selectedNode.data.config.variables.defaultValue ?? []
|
|
defaultValue.splice(index, 1)
|
|
selectedNode.data.config.variables.defaultValue = [...defaultValue]
|
|
|
|
selectedNode?.setData({ ...selectedNode.data })
|
|
|
|
setConfigs({ ...selectedNode.data.config })
|
|
}
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div className="rb:w-75 rb:fixed rb:right-0 rb:top-16 rb:bottom-0 rb:p-3">
|
|
<div className="rb:font-medium rb:leading-5 rb:mb-3">{t('workflow.nodeProperties')}</div>
|
|
{!selectedNode
|
|
? <Empty url={emptyIcon} size={140} className="rb:h-full rb:mx-15" title={t('workflow.empty')} />
|
|
: <Form form={form} layout="vertical" className="rb:h-[calc(100%-20px)] rb:overflow-y-auto">
|
|
<Form.Item name="name" label={t('workflow.nodeName')}>
|
|
<Input
|
|
placeholder={t('common.pleaseEnter')}
|
|
onChange={(e) => {
|
|
updateNodeLabel(e.target.value);
|
|
}}
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item name="id" label="ID">
|
|
<Input disabled />
|
|
</Form.Item>
|
|
{configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => {
|
|
const config = configs[key] || {}
|
|
|
|
if (selectedNode.data?.type === 'start' && key === 'variables' && config.type === 'define') {
|
|
return (
|
|
<div key={key}>
|
|
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.75">
|
|
<div className="rb:leading-5">
|
|
{t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
|
</div>
|
|
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button>
|
|
</div>
|
|
|
|
<Space size={4} direction="vertical" className="rb:w-full">
|
|
{Array.isArray(config.defaultValue) && config.defaultValue?.map((vo, index) =>
|
|
<div key={`${vo.name}}-${index}`} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md rb:group rb:cursor-pointer">
|
|
<span>{vo.name}·{vo.description}</span>
|
|
|
|
<div className="rb:group-hover:hidden rb:flex rb:items-center rb:gap-1">
|
|
{vo.required && <span>{t('workflow.config.start.required')}</span>}
|
|
{vo.type}
|
|
</div>
|
|
<Space className="rb:hidden! rb:group-hover:flex!">
|
|
<div
|
|
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
|
onClick={() => handleEditVariable(index, vo)}
|
|
></div>
|
|
<div
|
|
className="rb:w-4.5 rb:h-4.5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
|
onClick={() => handleDeleteVariable(index, vo)}
|
|
></div>
|
|
</Space>
|
|
</div>
|
|
)}
|
|
<Divider size="small" />
|
|
{config.sys?.map((vo, index) =>
|
|
<div key={index} className="rb:p-[4px_8px] rb:text-[12px] rb:text-[#5B6167] rb:flex rb:items-center rb:justify-between rb:border rb:border-[#DFE4ED] rb:rounded-md">
|
|
<div>
|
|
<span>sys.{vo.name}</span>
|
|
</div>
|
|
{vo.type}
|
|
</div>
|
|
)}
|
|
</Space>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (selectedNode.data?.type === 'llm' && key === 'messages' && config.type === 'define') {
|
|
return (
|
|
<Form.Item key={key} name={key}>
|
|
<MessageEditor selectedNode={selectedNode} graphRef={graphRef} />
|
|
</Form.Item>
|
|
)
|
|
}
|
|
if (selectedNode.data?.type === 'end' && key === 'output') {
|
|
return (
|
|
<Form.Item key={key} name={key}>
|
|
<MessageEditor isArray={false} parentName={key} selectedNode={selectedNode} graphRef={graphRef} />
|
|
</Form.Item>
|
|
)
|
|
}
|
|
|
|
if (config.type === 'define') {
|
|
return null
|
|
}
|
|
|
|
return (
|
|
<Form.Item
|
|
key={key}
|
|
name={key}
|
|
label={t(`workflow.config.${selectedNode.data.type}.${key}`)}
|
|
>
|
|
{config.type === 'input'
|
|
? <Input placeholder={t('common.pleaseEnter')} />
|
|
: config.type === 'textarea'
|
|
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
|
: config.type === 'select'
|
|
? <Select
|
|
options={config.options}
|
|
placeholder={t('common.pleaseSelect')}
|
|
/>
|
|
: config.type === 'inputNumber'
|
|
? <InputNumber />
|
|
: config.type === 'slider'
|
|
? <Slider min={config.min} max={config.max} step={config.step} />
|
|
: config.type === 'customSelect'
|
|
? <CustomSelect
|
|
placeholder={t('common.pleaseSelect')}
|
|
url={config.url as string}
|
|
params={config.params}
|
|
hasAll={false}
|
|
valueKey={config.valueKey}
|
|
labelKey={config.labelKey}
|
|
/>
|
|
: null
|
|
}
|
|
</Form.Item>
|
|
)
|
|
})}
|
|
</Form>
|
|
}
|
|
|
|
<VariableEditModal
|
|
ref={variableModalRef}
|
|
refresh={handleRefreshVariable}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
export default Properties; |