### 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
| return handleChange; | 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]); | |||||
| }; |
| 'jinrongjie', | 'jinrongjie', | ||||
| ]; | ]; | ||||
| export const CrawlerResultOptions = ['markdown', 'html', 'content']; | 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', | |||||
| }; |
| 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; |
| 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, | |||||
| }; | |||||
| }; |
| import { useTranslate } from '@/hooks/common-hooks'; | 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 = { | type FieldType = { | ||||
| prologue?: string; | prologue?: string; | ||||
| const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { | const BeginForm = ({ onValuesChange, form }: IOperatorForm) => { | ||||
| const { t } = useTranslate('chat'); | 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 ( | 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> | |||||
| ); | ); | ||||
| }; | }; | ||||
| 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> | |||||
| ); | |||||
| }; |
| 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; |
| }; | }; | ||||
| export type IPosition = { top: number; right: number; idx: number }; | 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)[]; | |||||
| } |