mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-14 03:25:57 +08:00
### What problem does this PR solve? feat: add DynamicParameters #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
a7423e3a94
commit
f6159ee4d3
103
web/src/components/editable-cell.tsx
Normal file
103
web/src/components/editable-cell.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Form, FormInstance, Input, InputRef } from 'antd';
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const EditableContext = React.createContext<FormInstance<any> | null>(null);
|
||||||
|
|
||||||
|
interface EditableRowProps {
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
age: string;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditableRow: React.FC<EditableRowProps> = ({
|
||||||
|
index,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
return (
|
||||||
|
<Form form={form} component={false}>
|
||||||
|
<EditableContext.Provider value={form}>
|
||||||
|
<tr {...props} />
|
||||||
|
</EditableContext.Provider>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface EditableCellProps {
|
||||||
|
title: React.ReactNode;
|
||||||
|
editable: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
dataIndex: keyof Item;
|
||||||
|
record: Item;
|
||||||
|
handleSave: (record: Item) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditableCell: React.FC<EditableCellProps> = ({
|
||||||
|
title,
|
||||||
|
editable,
|
||||||
|
children,
|
||||||
|
dataIndex,
|
||||||
|
record,
|
||||||
|
handleSave,
|
||||||
|
...restProps
|
||||||
|
}) => {
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const inputRef = useRef<InputRef>(null);
|
||||||
|
const form = useContext(EditableContext)!;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editing) {
|
||||||
|
inputRef.current!.focus();
|
||||||
|
}
|
||||||
|
}, [editing]);
|
||||||
|
|
||||||
|
const toggleEdit = () => {
|
||||||
|
setEditing(!editing);
|
||||||
|
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
|
||||||
|
toggleEdit();
|
||||||
|
handleSave({ ...record, ...values });
|
||||||
|
} catch (errInfo) {
|
||||||
|
console.log('Save failed:', errInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let childNode = children;
|
||||||
|
|
||||||
|
if (editable) {
|
||||||
|
childNode = editing ? (
|
||||||
|
<Form.Item
|
||||||
|
style={{ margin: 0 }}
|
||||||
|
name={dataIndex}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: `${title} is required.`,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="editable-cell-value-wrap"
|
||||||
|
style={{ paddingRight: 24 }}
|
||||||
|
onClick={toggleEdit}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <td {...restProps}>{childNode}</td>;
|
||||||
|
};
|
@ -560,6 +560,10 @@ The above is the content you need to summarize.`,
|
|||||||
createFlow: 'Create a workflow',
|
createFlow: 'Create a workflow',
|
||||||
yes: 'Yes',
|
yes: 'Yes',
|
||||||
no: 'No',
|
no: 'No',
|
||||||
|
key: 'key',
|
||||||
|
componentId: 'component id',
|
||||||
|
add: 'Add',
|
||||||
|
operation: 'operation',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -6,6 +6,7 @@ const ExcludedNodesMap = {
|
|||||||
// exclude some nodes downstream of the classification node
|
// exclude some nodes downstream of the classification node
|
||||||
[Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin],
|
[Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin],
|
||||||
[Operator.Relevant]: [Operator.Begin],
|
[Operator.Relevant]: [Operator.Begin],
|
||||||
|
[Operator.Generate]: [Operator.Begin],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useBuildFormSelectOptions = (
|
export const useBuildFormSelectOptions = (
|
||||||
|
104
web/src/pages/flow/generate-form/dynamic-parameters.tsx
Normal file
104
web/src/pages/flow/generate-form/dynamic-parameters.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Card, Form, Input, Select, Typography } from 'antd';
|
||||||
|
import { useUpdateNodeInternals } from 'reactflow';
|
||||||
|
import { Operator } from '../constant';
|
||||||
|
import {
|
||||||
|
useBuildFormSelectOptions,
|
||||||
|
useHandleFormSelectChange,
|
||||||
|
} from '../form-hooks';
|
||||||
|
import { IGenerateParameter } from '../interface';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
nodeId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DynamicParameters = ({ nodeId }: IProps) => {
|
||||||
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
|
const form = Form.useFormInstance();
|
||||||
|
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
||||||
|
Operator.Categorize,
|
||||||
|
nodeId,
|
||||||
|
);
|
||||||
|
const { handleSelectChange } = useHandleFormSelectChange(nodeId);
|
||||||
|
const { t } = useTranslate('flow');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.List name="parameters">
|
||||||
|
{(fields, { add, remove }) => {
|
||||||
|
const handleAdd = () => {
|
||||||
|
const idx = fields.length;
|
||||||
|
add({ name: `parameter ${idx + 1}` });
|
||||||
|
if (nodeId) updateNodeInternals(nodeId);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{ display: 'flex', rowGap: 10, flexDirection: 'column' }}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
key={field.key}
|
||||||
|
extra={
|
||||||
|
<CloseOutlined
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
label={t('key')} // TODO: repeatability check
|
||||||
|
name={[field.name, 'key']}
|
||||||
|
rules={[{ required: true, message: t('nameMessage') }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t('componentId')}
|
||||||
|
name={[field.name, 'component_id']}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
options={buildCategorizeToOptions(
|
||||||
|
(form.getFieldValue(['parameters']) ?? [])
|
||||||
|
.map((x: IGenerateParameter) => x.component_id)
|
||||||
|
.filter(
|
||||||
|
(x: string) =>
|
||||||
|
x !==
|
||||||
|
form.getFieldValue([
|
||||||
|
'parameters',
|
||||||
|
field.name,
|
||||||
|
'component_id',
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
onChange={handleSelectChange(
|
||||||
|
form.getFieldValue(['parameters', field.name, 'key']),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button type="dashed" onClick={handleAdd} block>
|
||||||
|
+ Add Item
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
|
||||||
|
<Form.Item noStyle shouldUpdate>
|
||||||
|
{() => (
|
||||||
|
<Typography>
|
||||||
|
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicParameters;
|
21
web/src/pages/flow/generate-form/index.less
Normal file
21
web/src/pages/flow/generate-form/index.less
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.variableTable {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
.editableRow {
|
||||||
|
:global(.editable-cell) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.editable-cell-value-wrap) {
|
||||||
|
padding: 5px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 22px !important;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
:global(.editable-cell-value-wrap) {
|
||||||
|
padding: 4px 11px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import LlmSettingItems from '@/components/llm-setting-items';
|
import LLMSelect from '@/components/llm-select';
|
||||||
import { useTranslate } from '@/hooks/commonHooks';
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
import { Form, Input, Switch } from 'antd';
|
import { Form, Input, Switch } from 'antd';
|
||||||
import { useSetLlmSetting } from '../hooks';
|
import { useSetLlmSetting } from '../hooks';
|
||||||
import { IOperatorForm } from '../interface';
|
import { IOperatorForm } from '../interface';
|
||||||
|
import DynamicParameters from './next-dynamic-parameters';
|
||||||
|
|
||||||
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
||||||
const { t } = useTranslate('flow');
|
const { t } = useTranslate('flow');
|
||||||
@ -18,7 +19,13 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|||||||
form={form}
|
form={form}
|
||||||
onValuesChange={onValuesChange}
|
onValuesChange={onValuesChange}
|
||||||
>
|
>
|
||||||
<LlmSettingItems></LlmSettingItems>
|
<Form.Item
|
||||||
|
name={'llm_id'}
|
||||||
|
label={t('model', { keyPrefix: 'chat' })}
|
||||||
|
tooltip={t('modelTip', { keyPrefix: 'chat' })}
|
||||||
|
>
|
||||||
|
<LLMSelect></LLMSelect>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={['prompt']}
|
name={['prompt']}
|
||||||
label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })}
|
label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })}
|
||||||
@ -42,6 +49,7 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<DynamicParameters></DynamicParameters>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
135
web/src/pages/flow/generate-form/next-dynamic-parameters.tsx
Normal file
135
web/src/pages/flow/generate-form/next-dynamic-parameters.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { EditableCell, EditableRow } from '@/components/editable-cell';
|
||||||
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Flex, Select, Table, TableProps } from 'antd';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { IGenerateParameter } from '../interface';
|
||||||
|
|
||||||
|
import { Operator } from '../constant';
|
||||||
|
import { useBuildFormSelectOptions } from '../form-hooks';
|
||||||
|
import styles from './index.less';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
nodeId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const components = {
|
||||||
|
body: {
|
||||||
|
row: EditableRow,
|
||||||
|
cell: EditableCell,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const DynamicParameters = ({ nodeId }: IProps) => {
|
||||||
|
const [dataSource, setDataSource] = useState<IGenerateParameter[]>([]);
|
||||||
|
const { t } = useTranslate('flow');
|
||||||
|
|
||||||
|
const buildCategorizeToOptions = useBuildFormSelectOptions(
|
||||||
|
Operator.Generate,
|
||||||
|
nodeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemove = (id?: string) => () => {
|
||||||
|
const newData = dataSource.filter((item) => item.id !== id);
|
||||||
|
setDataSource(newData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setDataSource((state) => [
|
||||||
|
...state,
|
||||||
|
{
|
||||||
|
id: uuid(),
|
||||||
|
key: '',
|
||||||
|
component_id: undefined,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = (row: IGenerateParameter) => {
|
||||||
|
const newData = [...dataSource];
|
||||||
|
const index = newData.findIndex((item) => row.id === item.id);
|
||||||
|
const item = newData[index];
|
||||||
|
newData.splice(index, 1, {
|
||||||
|
...item,
|
||||||
|
...row,
|
||||||
|
});
|
||||||
|
setDataSource(newData);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {}, [dataSource]);
|
||||||
|
|
||||||
|
const handleOptionalChange = (row: IGenerateParameter) => (value: string) => {
|
||||||
|
const newData = [...dataSource];
|
||||||
|
const index = newData.findIndex((item) => row.id === item.id);
|
||||||
|
const item = newData[index];
|
||||||
|
newData.splice(index, 1, {
|
||||||
|
...item,
|
||||||
|
component_id: value,
|
||||||
|
});
|
||||||
|
setDataSource(newData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: TableProps<IGenerateParameter>['columns'] = [
|
||||||
|
{
|
||||||
|
title: t('key'),
|
||||||
|
dataIndex: 'key',
|
||||||
|
key: 'key',
|
||||||
|
onCell: (record: IGenerateParameter) => ({
|
||||||
|
record,
|
||||||
|
editable: true,
|
||||||
|
dataIndex: 'key',
|
||||||
|
title: 'key',
|
||||||
|
handleSave,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('componentId'),
|
||||||
|
dataIndex: 'component_id',
|
||||||
|
key: 'component_id',
|
||||||
|
align: 'center',
|
||||||
|
render(text, record) {
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
allowClear
|
||||||
|
options={buildCategorizeToOptions([])}
|
||||||
|
// onChange={handleSelectChange(
|
||||||
|
// form.getFieldValue(['parameters', field.name, 'key']),
|
||||||
|
// )}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('operation'),
|
||||||
|
dataIndex: 'operation',
|
||||||
|
width: 20,
|
||||||
|
key: 'operation',
|
||||||
|
align: 'center',
|
||||||
|
render(_, record) {
|
||||||
|
return <DeleteOutlined onClick={handleRemove(record.id)} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<Flex justify="end">
|
||||||
|
<Button size="small" onClick={handleAdd}>
|
||||||
|
{t('add')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Table
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
rowKey={'id'}
|
||||||
|
className={styles.variableTable}
|
||||||
|
components={components}
|
||||||
|
rowClassName={() => styles.editableRow}
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicParameters;
|
@ -45,6 +45,12 @@ export interface ICategorizeItem {
|
|||||||
to?: string;
|
to?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGenerateParameter {
|
||||||
|
id?: string;
|
||||||
|
key: string;
|
||||||
|
component_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type ICategorizeItemResult = Record<
|
export type ICategorizeItemResult = Record<
|
||||||
string,
|
string,
|
||||||
Omit<ICategorizeItem, 'name'>
|
Omit<ICategorizeItem, 'name'>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user