Просмотр исходного кода

feat: save the parameters of the generate operator to the form field … (#1390)

### What problem does this PR solve?
feat: save the parameters of the generate operator to the form field of
the node #918

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.8.0
balibabu 1 год назад
Родитель
Сommit
edc61e9b4c
Аккаунт пользователя с таким Email не найден

+ 1
- 1
web/src/components/llm-setting-items/index.tsx Просмотреть файл

@@ -51,7 +51,7 @@ const LlmSettingItems = ({ prefix, formItemLayout = {} }: IProps) => {
<Divider></Divider>
<Form.Item
label={t('freedom')}
name="parameters"
name="parameter"
tooltip={t('freedomTip')}
{...formItemLayout}
initialValue={ModelVariableType.Precise}

+ 4
- 1
web/src/locales/en.ts Просмотреть файл

@@ -570,12 +570,15 @@ The above is the content you need to summarize.`,
retrievalDescription: `This component is for the process of retrieving relevent information from knowledge base. So, knowledgebases should be selected. If there's nothing retrieved, the 'Empty response' will be returned.`,
generateDescription: `This component is used to call LLM to generate text. Be careful about the prompt setting.`,
categorizeDescription: `This component is used to categorize text. Please specify the name, description and examples of the category. Every single category leads to different downstream components.`,
relevantDescription: `This component is used to judge if the retrieved information is relevent to user's question. 'Yes' represents that they're relevant. 'No' represents they're irrelevant.`,
relevantDescription: `This component is used to judge if the output of upstream is relevent to user's latest question. 'Yes' represents that they're relevant. 'No' represents they're irrelevant.`,
rewriteQuestionDescription: `This component is used to refine user's quesion. Typically, when a user's original question can't retrieve relevant information from knowledge base, this component help you change the question into a proper one which might be more consistant with the expressions in knowledge base. Only 'Retrieval' can be its downstreams.`,
messageDescription:
'This component is used to send user static information.',
keywordDescription:
'This component is used to send user static information.',
promptText: `Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:
{input}
The above is the content you need to summarize.`,
},
footer: {
profile: 'All rights reserved @ React',

+ 2
- 3
web/src/pages/flow/constant.tsx Просмотреть файл

@@ -145,13 +145,12 @@ const initialLlmBaseValues = {
};

export const initialGenerateValues = {
// parameters: ModelVariableType.Precise,
// temperatureEnabled: true,
...initialLlmBaseValues,
prompt: `Please summarize the following paragraphs. Be careful with the numbers, do not make things up. Paragraphs as following:
{cluster_content}
{input}
The above is the content you need to summarize.`,
cite: true,
parameters: [],
};

export const initialRewriteQuestionValues = {

+ 81
- 88
web/src/pages/flow/generate-form/dynamic-parameters.tsx Просмотреть файл

@@ -1,103 +1,96 @@
import { EditableCell, EditableRow } from '@/components/editable-cell';
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 { DeleteOutlined } from '@ant-design/icons';
import { Button, Flex, Select, Table, TableProps } from 'antd';
import { IGenerateParameter } from '../interface';

import {
useBuildComponentIdSelectOptions,
useHandleOperateParameters,
} from './hooks';
import styles from './index.less';

interface IProps {
nodeId?: string;
}

const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};

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>
))}
const options = useBuildComponentIdSelectOptions(nodeId);
const {
dataSource,
handleAdd,
handleRemove,
handleSave,
handleComponentIdChange,
} = useHandleOperateParameters(nodeId!);

<Button type="dashed" onClick={handleAdd} block>
+ Add Item
</Button>
</div>
);
}}
</Form.List>
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={options}
value={text}
onChange={handleComponentIdChange(record)}
/>
);
},
},
{
title: t('operation'),
dataIndex: 'operation',
width: 20,
key: 'operation',
align: 'center',
render(_, record) {
return <DeleteOutlined onClick={handleRemove(record.id)} />;
},
},
];

<Form.Item noStyle shouldUpdate>
{() => (
<Typography>
<pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
</Typography>
)}
</Form.Item>
</>
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>
);
};


+ 102
- 0
web/src/pages/flow/generate-form/hooks.ts Просмотреть файл

@@ -0,0 +1,102 @@
import get from 'lodash/get';
import { useCallback, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import { Operator } from '../constant';
import { IGenerateParameter } from '../interface';
import useGraphStore from '../store';

// exclude nodes with branches
const ExcludedNodes = [Operator.Categorize, Operator.Relevant];

export const useBuildComponentIdSelectOptions = (nodeId?: string) => {
const nodes = useGraphStore((state) => state.nodes);

const options = useMemo(() => {
return nodes
.filter(
(x) =>
x.id !== nodeId && !ExcludedNodes.some((y) => y === x.data.label),
)
.map((x) => ({ label: x.data.name, value: x.id }));
}, [nodes, nodeId]);

return options;
};

export const useHandleOperateParameters = (nodeId: string) => {
const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.parameters', []),
[node],
);

// const [x, setDataSource] = useState<IGenerateParameter[]>([]);

const handleComponentIdChange = useCallback(
(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,
});

updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
},
[updateNodeForm, nodeId, dataSource],
);

const handleRemove = useCallback(
(id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
},
[updateNodeForm, nodeId, dataSource],
);

const handleAdd = useCallback(() => {
// setDataSource((state) => [
// ...state,
// {
// id: uuid(),
// key: '',
// component_id: undefined,
// },
// ]);
updateNodeForm(nodeId, {
parameters: [
...dataSource,
{
id: uuid(),
key: '',
component_id: undefined,
},
],
});
}, [dataSource, nodeId, updateNodeForm]);

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,
});

updateNodeForm(nodeId, { parameters: newData });
// setDataSource(newData);
};

return {
handleAdd,
handleRemove,
handleComponentIdChange,
handleSave,
dataSource,
};
};

+ 1
- 1
web/src/pages/flow/generate-form/index.less Просмотреть файл

@@ -9,7 +9,7 @@
:global(.editable-cell-value-wrap) {
padding: 5px 12px;
cursor: pointer;
height: 22px !important;
height: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {

+ 4
- 4
web/src/pages/flow/generate-form/index.tsx Просмотреть файл

@@ -3,9 +3,9 @@ import { useTranslate } from '@/hooks/commonHooks';
import { Form, Input, Switch } from 'antd';
import { useSetLlmSetting } from '../hooks';
import { IOperatorForm } from '../interface';
import DynamicParameters from './next-dynamic-parameters';
import DynamicParameters from './dynamic-parameters';

const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslate('flow');

useSetLlmSetting(form);
@@ -29,7 +29,7 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
<Form.Item
name={['prompt']}
label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })}
initialValue={t('promptText', { keyPrefix: 'knowledgeConfiguration' })}
initialValue={t('promptText')}
tooltip={t('promptTip', { keyPrefix: 'knowledgeConfiguration' })}
rules={[
{
@@ -49,7 +49,7 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
>
<Switch />
</Form.Item>
<DynamicParameters></DynamicParameters>
<DynamicParameters nodeId={node?.id}></DynamicParameters>
</Form>
);
};

+ 0
- 135
web/src/pages/flow/generate-form/next-dynamic-parameters.tsx Просмотреть файл

@@ -1,135 +0,0 @@
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;

+ 11
- 1
web/src/pages/flow/store.ts Просмотреть файл

@@ -189,7 +189,17 @@ const useGraphStore = create<RFState>()(
set({
nodes: get().nodes.map((node) => {
if (node.id === nodeId) {
node.data = { ...node.data, form: values };
// node.data = {
// ...node.data,
// form: { ...node.data.form, ...values },
// };
return {
...node,
data: {
...node.data,
form: { ...node.data.form, ...values },
},
} as any;
}

return node;

+ 1
- 1
web/src/utils/form.ts Просмотреть файл

@@ -20,7 +20,7 @@ export const excludeUnEnabledVariables = (
export const removeUselessFieldsFromValues = (values: any, prefix?: string) => {
const nextValues: any = omit(values, [
...Object.keys(variableEnabledFieldMap),
'parameters',
'parameter',
...excludeUnEnabledVariables(values, prefix),
]);


Загрузка…
Отмена
Сохранить