feat(web): add loop node; add chat variable;

This commit is contained in:
zhaoying
2026-01-04 20:00:10 +08:00
parent 4e3b8870c5
commit a66fb9eade
29 changed files with 1453 additions and 279 deletions

View File

@@ -0,0 +1,144 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, Select, Checkbox, InputNumber } from 'antd';
import { useTranslation } from 'react-i18next';
import type { ChatVariableModalRef } from './types'
import type { ChatVariable } from '../../types';
import RbModal from '@/components/RbModal'
const FormItem = Form.Item;
interface ChatVariableModalProps {
refresh: (value: ChatVariable, editIndex?: number) => void;
}
const types = [
'string',
'number',
'boolean',
]
const ChatVariableModal = forwardRef<ChatVariableModalRef, ChatVariableModalProps>(({
refresh
}, ref) => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm<ChatVariable>();
const [loading, setLoading] = useState(false)
const [editIndex, setEditIndex] = useState<number | undefined>(undefined)
const typeValue = Form.useWatch('type', form);
// 封装取消方法,添加关闭弹窗逻辑
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false)
setEditIndex(undefined)
};
const handleOpen = (variable?: ChatVariable, index?: number) => {
setVisible(true);
if (variable) {
form.setFieldsValue(variable)
setEditIndex(index)
} else {
form.resetFields();
setEditIndex(undefined)
}
};
// 封装保存方法,添加提交逻辑
const handleSave = () => {
form.validateFields().then((values) => {
refresh({ ...values }, editIndex)
handleClose()
})
}
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
handleOpen
}));
return (
<RbModal
title={editIndex !== undefined ? t('workflow.editChatVariable') : t('workflow.addChatVariable')}
open={visible}
onCancel={handleClose}
okText={t('common.save')}
onOk={handleSave}
confirmLoading={loading}
>
<Form
form={form}
layout="vertical"
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
>
<FormItem
name="name"
label={t('workflow.config.parameter-extractor.name')}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: t('workflow.config.parameter-extractor.invalidParamName') },
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="type"
label={t('workflow.config.parameter-extractor.type')}
rules={[{ required: true, message: t('common.pleaseSelect') }]}
>
<Select
placeholder={t('common.pleaseSelect')}
onChange={() => form.setFieldValue('default', undefined)}
options={types.map(key => ({
value: key,
label: t(`workflow.config.parameter-extractor.${key}`),
}))}
/>
</FormItem>
<FormItem
name="default"
label={t('workflow.config.parameter-extractor.default')}
>
<Form.Item noStyle shouldUpdate={(prevValues, currentValues) => prevValues.type !== currentValues.type}>
{({ getFieldValue }) => {
const type = getFieldValue('type');
if (type === 'number') {
return <InputNumber placeholder={t('common.enter')} style={{ width: '100%' }} />;
}
if (type === 'boolean') {
return (
<Select
placeholder={t('common.pleaseSelect')}
options={[
{ value: true, label: 'true' },
{ value: false, label: 'false' }
]}
/>
);
}
return <Input placeholder={t('common.enter')} />;
}}
</Form.Item>
</FormItem>
<FormItem
name="description"
label={t('workflow.config.parameter-extractor.desc')}
>
<Input.TextArea placeholder={t('common.enter')} />
</FormItem>
<FormItem
name="required"
valuePropName="checked"
>
<Checkbox>{t('workflow.config.parameter-extractor.required')}</Checkbox>
</FormItem>
</Form>
</RbModal>
);
});
export default ChatVariableModal;

View File

@@ -0,0 +1,113 @@
import React, { useState, useImperativeHandle, forwardRef, useRef } from 'react';
import { Button, Input, Space, Typography, Tooltip, message, List } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import type { ChatVariable, AddChatVariableRef } from '../../types';
import type { ChatVariableModalRef } from './types'
import RbDrawer from '@/components/RbDrawer';
import Empty from '@/components/Empty';
import ChatVariableModal from './ChatVariableModal';
interface AddChatVariableProps {
variables?: ChatVariable[];
onChange?: (variables: ChatVariable[]) => void;
disabled?: boolean;
maxVariables?: number;
}
const AddChatVariable = forwardRef<AddChatVariableRef, AddChatVariableProps>(({
variables = [],
onChange,
}, ref) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const chatVariableRef = useRef<ChatVariableModalRef>(null);
const handleAddVariable = () => {
chatVariableRef.current?.handleOpen()
};
const handleEdit = (index: number) => {
chatVariableRef.current?.handleOpen(variables[index], index)
}
const handleDelete = (index: number) => {
const list = [...variables]
list.splice(index, 1)
onChange && onChange(list)
}
const handleOpen = () => {
setOpen(true)
}
const handleSave = (value: ChatVariable, index?: number) => {
const list = [...variables]
if (index && index > -1) {
list[index] = value
} else {
list.push(value)
}
onChange && onChange(list)
}
// 暴露给父组件的方法
useImperativeHandle(ref, () => ({
handleOpen,
}));
return (
<RbDrawer
title={t('workflow.addvariable')}
open={open}
onClose={() => setOpen(false)}
>
<div>
<Button
type="primary"
className="rb:mb-3"
onClick={handleAddVariable}
>
+ {t('workflow.addChatVariable')}
</Button>
{variables.length === 0
? <Empty size={88} />
:
<List
grid={{ gutter: 12, column: 1 }}
dataSource={variables}
renderItem={(item, index) => (
<List.Item>
<div key={index} className="rb:group rb:relative rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:cursor-pointer rb:border rb:border-[#DFE4ED] rb:rounded-lg">
<div className="rb:flex rb:items-center rb:justify-between">
<div className="rb:leading-4">
<span className="rb:font-medium">{item.name}</span>
<span className="rb:text-[12px] rb:text-[#5B6167] rb:font-regular"> ({t(`workflow.config.parameter-extractor.${item.type}`)})</span>
</div>
<span className="rb:block rb:group-hover:hidden rb:text-[12px] rb:text-[#5B6167] rb:font-regular">{item.required ? t('workflow.config.parameter-extractor.required') : ''}</span>
</div>
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5 rb:wrap-break-word rb:line-clamp-1">{item.description}</div>
<Space size={12} className="rb:hidden! rb:group-hover:flex! rb:absolute rb:right-4 rb:top-[50%] rb:transform-[translateY(-50%)] rb:bg-white">
<div
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
onClick={() => handleEdit(index)}
></div>
<div
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
onClick={() => handleDelete(index)}
></div>
</Space>
</div>
</List.Item>
)}
/>
}
</div>
<ChatVariableModal
ref={chatVariableRef}
refresh={handleSave}
/>
</RbDrawer>
);
});
export default AddChatVariable;

View File

@@ -0,0 +1,24 @@
import type { ChatVariable } from '../../types'
export interface AddChatVariableProps {
variables?: ChatVariable[];
onChange?: (variables: ChatVariable[]) => void;
disabled?: boolean;
maxVariables?: number;
}
export interface VariableFormData {
name: string;
type: ChatVariable['type'];
description?: string;
required?: boolean;
defaultValue?: any;
}
export interface ChatVariableModalRef {
handleOpen: (value?: ChatVariable, index?: number) => void;
}
export interface ChatVariableModalRef {
handleOpen: (vo?: ChatVariable, index?: number) => void;
}