mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 01:39:00 +08:00
### 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)
This commit is contained in:
parent
9fc092a911
commit
1fe9a2e6fd
@ -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',
|
||||
};
|
||||
|
68
web/src/pages/flow/form/begin-form/begin-dynamic-options.tsx
Normal file
68
web/src/pages/flow/form/begin-form/begin-dynamic-options.tsx
Normal file
@ -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;
|
50
web/src/pages/flow/form/begin-form/hooks.ts
Normal file
50
web/src/pages/flow/form/begin-form/hooks.ts
Normal file
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
113
web/src/pages/flow/form/begin-form/paramater-modal.tsx
Normal file
113
web/src/pages/flow/form/begin-form/paramater-modal.tsx
Normal file
@ -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>
|
||||
);
|
||||
};
|
71
web/src/pages/flow/form/begin-form/query-table.tsx
Normal file
71
web/src/pages/flow/form/begin-form/query-table.tsx
Normal file
@ -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)[];
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user