### What problem does this PR solve? feat: Add input parameter to begin operator #3355 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.14.0
| @@ -557,3 +557,24 @@ export const useHandleChunkMethodSelectChange = (form: FormInstance) => { | |||
| return handleChange; | |||
| }; | |||
| // reset form fields when modal is form, closed | |||
| export const useResetFormOnCloseModal = ({ | |||
| form, | |||
| visible, | |||
| }: { | |||
| form: FormInstance; | |||
| visible?: boolean; | |||
| }) => { | |||
| const prevOpenRef = useRef<boolean>(); | |||
| useEffect(() => { | |||
| prevOpenRef.current = visible; | |||
| }, [visible]); | |||
| const prevOpen = prevOpenRef.current; | |||
| useEffect(() => { | |||
| if (!visible && prevOpen) { | |||
| form.resetFields(); | |||
| } | |||
| }, [form, prevOpen, visible]); | |||
| }; | |||
| @@ -2859,3 +2859,23 @@ export const TuShareSrcOptions = [ | |||
| 'jinrongjie', | |||
| ]; | |||
| export const CrawlerResultOptions = ['markdown', 'html', 'content']; | |||
| export enum BeginQueryType { | |||
| Line = 'line', | |||
| Paragraph = 'paragraph', | |||
| Options = 'options', | |||
| File = 'file', | |||
| Integer = 'integer', | |||
| Boolean = 'boolean', | |||
| Url = 'url', | |||
| } | |||
| export const BeginQueryTypeMap = { | |||
| [BeginQueryType.Line]: 'input', | |||
| [BeginQueryType.Paragraph]: 'textarea', | |||
| [BeginQueryType.Options]: 'select', | |||
| [BeginQueryType.File]: 'file', | |||
| [BeginQueryType.Integer]: 'inputnumber', | |||
| [BeginQueryType.Boolean]: 'switch', | |||
| [BeginQueryType.Url]: 'input', | |||
| }; | |||
| @@ -0,0 +1,68 @@ | |||
| import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Form, Input } from 'antd'; | |||
| const BeginDynamicOptions = () => { | |||
| return ( | |||
| <Form.List | |||
| name="options" | |||
| rules={[ | |||
| { | |||
| validator: async (_, names) => { | |||
| if (!names || names.length < 1) { | |||
| return Promise.reject(new Error('At least 1 option')); | |||
| } | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| {(fields, { add, remove }, { errors }) => ( | |||
| <> | |||
| {fields.map((field, index) => ( | |||
| <Form.Item | |||
| label={index === 0 ? 'Options' : ''} | |||
| required={false} | |||
| key={field.key} | |||
| > | |||
| <Form.Item | |||
| {...field} | |||
| validateTrigger={['onChange', 'onBlur']} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| whitespace: true, | |||
| message: 'Please input option or delete this field.', | |||
| }, | |||
| ]} | |||
| noStyle | |||
| > | |||
| <Input | |||
| placeholder="option" | |||
| style={{ width: '90%', marginRight: 16 }} | |||
| /> | |||
| </Form.Item> | |||
| {fields.length > 1 ? ( | |||
| <MinusCircleOutlined | |||
| className="dynamic-delete-button" | |||
| onClick={() => remove(field.name)} | |||
| /> | |||
| ) : null} | |||
| </Form.Item> | |||
| ))} | |||
| <Form.Item> | |||
| <Button | |||
| type="dashed" | |||
| onClick={() => add()} | |||
| icon={<PlusOutlined />} | |||
| block | |||
| > | |||
| Add field | |||
| </Button> | |||
| <Form.ErrorList errors={errors} /> | |||
| </Form.Item> | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| ); | |||
| }; | |||
| export default BeginDynamicOptions; | |||
| @@ -0,0 +1,50 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { BeginQuery, IOperatorForm } from '../../interface'; | |||
| export const useEditQueryRecord = ({ form, onValuesChange }: IOperatorForm) => { | |||
| const { setRecord, currentRecord } = useSetSelectedRecord<BeginQuery>(); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [index, setIndex] = useState(-1); | |||
| const otherThanCurrentQuery = useMemo(() => { | |||
| const query: BeginQuery[] = form?.getFieldValue('query') || []; | |||
| return query.filter((item, idx) => idx !== index); | |||
| }, [form, index]); | |||
| const handleEditRecord = useCallback( | |||
| (record: BeginQuery) => { | |||
| const query: BeginQuery[] = form?.getFieldValue('query') || []; | |||
| const nextQuery: BeginQuery[] = | |||
| index > -1 ? query.toSpliced(index, 1, record) : [...query, record]; | |||
| onValuesChange?.( | |||
| { query: nextQuery }, | |||
| { query: nextQuery, prologue: form?.getFieldValue('prologue') }, | |||
| ); | |||
| hideModal(); | |||
| }, | |||
| [form, hideModal, index, onValuesChange], | |||
| ); | |||
| const handleShowModal = useCallback( | |||
| (idx?: number, record?: BeginQuery) => { | |||
| setIndex(idx ?? -1); | |||
| setRecord(record ?? ({} as BeginQuery)); | |||
| showModal(); | |||
| }, | |||
| [setRecord, showModal], | |||
| ); | |||
| return { | |||
| ok: handleEditRecord, | |||
| currentRecord, | |||
| setRecord, | |||
| visible, | |||
| hideModal, | |||
| showModal: handleShowModal, | |||
| otherThanCurrentQuery, | |||
| }; | |||
| }; | |||
| @@ -1,6 +1,10 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Form, Input } from 'antd'; | |||
| import { IOperatorForm } from '../../interface'; | |||
| import { Button, Form, Input } from 'antd'; | |||
| import { useCallback } from 'react'; | |||
| import { BeginQuery, IOperatorForm } from '../../interface'; | |||
| import { useEditQueryRecord } from './hooks'; | |||
| import { ModalForm } from './paramater-modal'; | |||
| import QueryTable from './query-table'; | |||
| type FieldType = { | |||
| prologue?: string; | |||
| @@ -8,25 +12,95 @@ type FieldType = { | |||
| const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { | |||
| const { t } = useTranslate('chat'); | |||
| const { | |||
| ok, | |||
| currentRecord, | |||
| visible, | |||
| hideModal, | |||
| showModal, | |||
| otherThanCurrentQuery, | |||
| } = useEditQueryRecord({ | |||
| form, | |||
| onValuesChange, | |||
| }); | |||
| const handleDeleteRecord = useCallback( | |||
| (idx: number) => { | |||
| const query = form?.getFieldValue('query') || []; | |||
| const nextQuery = query.filter( | |||
| (item: BeginQuery, index: number) => index !== idx, | |||
| ); | |||
| onValuesChange?.( | |||
| { query: nextQuery }, | |||
| { query: nextQuery, prologue: form?.getFieldValue('prologue') }, | |||
| ); | |||
| }, | |||
| [form, onValuesChange], | |||
| ); | |||
| return ( | |||
| <Form | |||
| name="basic" | |||
| labelCol={{ span: 8 }} | |||
| wrapperCol={{ span: 16 }} | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| <Form.Provider | |||
| onFormFinish={(name, { values }) => { | |||
| if (name === 'queryForm') { | |||
| ok(values as BeginQuery); | |||
| } | |||
| }} | |||
| > | |||
| <Form.Item<FieldType> | |||
| name={'prologue'} | |||
| label={t('setAnOpener')} | |||
| tooltip={t('setAnOpenerTip')} | |||
| initialValue={t('setAnOpenerInitial')} | |||
| <Form | |||
| name="basicForm" | |||
| onValuesChange={onValuesChange} | |||
| autoComplete="off" | |||
| form={form} | |||
| layout="vertical" | |||
| > | |||
| <Input.TextArea autoSize={{ minRows: 5 }} /> | |||
| </Form.Item> | |||
| </Form> | |||
| <Form.Item<FieldType> | |||
| name={'prologue'} | |||
| label={t('setAnOpener')} | |||
| tooltip={t('setAnOpenerTip')} | |||
| initialValue={t('setAnOpenerInitial')} | |||
| > | |||
| <Input.TextArea autoSize={{ minRows: 5 }} /> | |||
| </Form.Item> | |||
| {/* Create a hidden field to make Form instance record this */} | |||
| <Form.Item name="query" noStyle /> | |||
| <Form.Item | |||
| label="Query List" | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.query !== curValues.query | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const query: BeginQuery[] = getFieldValue('query') || []; | |||
| return ( | |||
| <QueryTable | |||
| data={query} | |||
| showModal={showModal} | |||
| deleteRecord={handleDeleteRecord} | |||
| ></QueryTable> | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| <Button | |||
| htmlType="button" | |||
| style={{ margin: '0 8px' }} | |||
| onClick={() => showModal()} | |||
| block | |||
| > | |||
| Add + | |||
| </Button> | |||
| {visible && ( | |||
| <ModalForm | |||
| visible={visible} | |||
| hideModal={hideModal} | |||
| initialValue={currentRecord} | |||
| onOk={ok} | |||
| otherThanCurrentQuery={otherThanCurrentQuery} | |||
| /> | |||
| )} | |||
| </Form> | |||
| </Form.Provider> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,113 @@ | |||
| import { useResetFormOnCloseModal } from '@/hooks/logic-hooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { Form, Input, Modal, Select, Switch } from 'antd'; | |||
| import { DefaultOptionType } from 'antd/es/select'; | |||
| import { useEffect, useMemo } from 'react'; | |||
| import { BeginQueryType } from '../../constant'; | |||
| import { BeginQuery } from '../../interface'; | |||
| import BeginDynamicOptions from './begin-dynamic-options'; | |||
| export const ModalForm = ({ | |||
| visible, | |||
| initialValue, | |||
| hideModal, | |||
| otherThanCurrentQuery, | |||
| }: IModalProps<BeginQuery> & { | |||
| initialValue: BeginQuery; | |||
| otherThanCurrentQuery: BeginQuery[]; | |||
| }) => { | |||
| const [form] = Form.useForm(); | |||
| const options = useMemo(() => { | |||
| return Object.values(BeginQueryType).reduce<DefaultOptionType[]>( | |||
| (pre, cur) => { | |||
| return [ | |||
| ...pre, | |||
| { | |||
| label: cur, | |||
| value: cur, | |||
| }, | |||
| ]; | |||
| }, | |||
| [], | |||
| ); | |||
| }, []); | |||
| useResetFormOnCloseModal({ | |||
| form, | |||
| visible: visible, | |||
| }); | |||
| useEffect(() => { | |||
| form.setFieldsValue(initialValue); | |||
| }, [form, initialValue]); | |||
| const onOk = () => { | |||
| form.submit(); | |||
| }; | |||
| return ( | |||
| <Modal | |||
| title="Begin query" | |||
| open={visible} | |||
| onOk={onOk} | |||
| onCancel={hideModal} | |||
| centered | |||
| > | |||
| <Form form={form} layout="vertical" name="queryForm" autoComplete="false"> | |||
| <Form.Item | |||
| name="type" | |||
| label="Type" | |||
| rules={[{ required: true }]} | |||
| initialValue={BeginQueryType.Line} | |||
| > | |||
| <Select options={options} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name="key" | |||
| label="Key" | |||
| rules={[ | |||
| { required: true }, | |||
| () => ({ | |||
| validator(_, value) { | |||
| if ( | |||
| !value || | |||
| !otherThanCurrentQuery.some((x) => x.key === value) | |||
| ) { | |||
| return Promise.resolve(); | |||
| } | |||
| return Promise.reject(new Error('The key cannot be repeated!')); | |||
| }, | |||
| }), | |||
| ]} | |||
| > | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item name="name" label="Name" rules={[{ required: true }]}> | |||
| <Input /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| name="optional" | |||
| label={'Optional'} | |||
| valuePropName="checked" | |||
| initialValue={false} | |||
| > | |||
| <Switch /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| shouldUpdate={(prevValues, curValues) => | |||
| prevValues.type !== curValues.type | |||
| } | |||
| > | |||
| {({ getFieldValue }) => { | |||
| const type: BeginQueryType = getFieldValue('type'); | |||
| return ( | |||
| type === BeginQueryType.Options && ( | |||
| <BeginDynamicOptions></BeginDynamicOptions> | |||
| ) | |||
| ); | |||
| }} | |||
| </Form.Item> | |||
| </Form> | |||
| </Modal> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,71 @@ | |||
| import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; | |||
| import type { TableProps } from 'antd'; | |||
| import { Space, Table, Tooltip } from 'antd'; | |||
| import { BeginQuery } from '../../interface'; | |||
| interface IProps { | |||
| data: BeginQuery[]; | |||
| deleteRecord(index: number): void; | |||
| showModal(index: number, record: BeginQuery): void; | |||
| } | |||
| const QueryTable = ({ data, deleteRecord, showModal }: IProps) => { | |||
| const columns: TableProps<BeginQuery>['columns'] = [ | |||
| { | |||
| title: 'Key', | |||
| dataIndex: 'key', | |||
| key: 'key', | |||
| ellipsis: { | |||
| showTitle: false, | |||
| }, | |||
| render: (key) => ( | |||
| <Tooltip placement="topLeft" title={key}> | |||
| {key} | |||
| </Tooltip> | |||
| ), | |||
| }, | |||
| { | |||
| title: 'Name', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| ellipsis: { | |||
| showTitle: false, | |||
| }, | |||
| render: (name) => ( | |||
| <Tooltip placement="topLeft" title={name}> | |||
| {name} | |||
| </Tooltip> | |||
| ), | |||
| }, | |||
| { | |||
| title: 'Type', | |||
| dataIndex: 'type', | |||
| key: 'type', | |||
| }, | |||
| { | |||
| title: 'Optional', | |||
| dataIndex: 'optional', | |||
| key: 'optional', | |||
| render: (optional) => (optional ? 'Yes' : 'No'), | |||
| }, | |||
| { | |||
| title: 'Action', | |||
| key: 'action', | |||
| render: (_, record, idx) => ( | |||
| <Space> | |||
| <EditOutlined onClick={() => showModal(idx, record)} /> | |||
| <DeleteOutlined | |||
| className="cursor-pointer" | |||
| onClick={() => deleteRecord(idx)} | |||
| /> | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <Table<BeginQuery> columns={columns} dataSource={data} pagination={false} /> | |||
| ); | |||
| }; | |||
| export default QueryTable; | |||
| @@ -100,3 +100,12 @@ export type NodeData = { | |||
| }; | |||
| export type IPosition = { top: number; right: number; idx: number }; | |||
| export interface BeginQuery { | |||
| key: string; | |||
| type: string; | |||
| value: string; | |||
| optional: boolean; | |||
| name: string; | |||
| options: (number | string | boolean)[]; | |||
| } | |||