feat: Add component Invoke #2908 (#3067)

### What problem does this PR solve?

feat: Add component Invoke #2908

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2024-10-28 18:56:38 +08:00
committed by GitHub
parent d133cc043b
commit 497bc1438a
14 changed files with 407 additions and 0 deletions

View File

@ -12,6 +12,7 @@ import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg';
import { ReactComponent as GithubIcon } from '@/assets/svg/github.svg';
import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg';
import { ReactComponent as GoogleIcon } from '@/assets/svg/google.svg';
import { ReactComponent as InvokeIcon } from '@/assets/svg/invoke-ai.svg';
import { ReactComponent as Jin10Icon } from '@/assets/svg/jin10.svg';
import { ReactComponent as KeywordIcon } from '@/assets/svg/keyword.svg';
import { ReactComponent as NoteIcon } from '@/assets/svg/note.svg';
@ -75,6 +76,7 @@ export enum Operator {
TuShare = 'TuShare',
Note = 'Note',
Crawler = 'Crawler',
Invoke = 'Invoke',
}
export const CommonOperatorList = Object.values(Operator).filter(
@ -113,6 +115,7 @@ export const operatorIconMap = {
[Operator.TuShare]: TuShareIcon,
[Operator.Note]: NoteIcon,
[Operator.Crawler]: CrawlerIcon,
[Operator.Invoke]: InvokeIcon,
};
export const operatorMap: Record<
@ -239,6 +242,9 @@ export const operatorMap: Record<
[Operator.Crawler]: {
backgroundColor: '#dee0e2',
},
[Operator.Invoke]: {
backgroundColor: '#dee0e2',
},
};
export const componentMenuList = [
@ -332,6 +338,9 @@ export const componentMenuList = [
{
name: Operator.Crawler,
},
{
name: Operator.Invoke,
},
];
export const initialRetrievalValues = {
@ -509,6 +518,18 @@ export const initialCrawlerValues = {
extract_type: 'markdown',
};
export const initialInvokeValues = {
url: 'http://',
method: 'GET',
timeout: 60,
headers: `{
"Accept": "*/*",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}`,
proxy: 'http://',
};
export const CategorizeAnchorPointPositions = [
{ top: 1, right: 34 },
{ top: 8, right: 18 },
@ -621,6 +642,7 @@ export const NodeMap = {
[Operator.TuShare]: 'ragNode',
[Operator.Note]: 'noteNode',
[Operator.Crawler]: 'ragNode',
[Operator.Invoke]: 'ragNode',
};
export const LanguageOptions = [

View File

@ -20,6 +20,7 @@ import GenerateForm from '../form/generate-form';
import GithubForm from '../form/github-form';
import GoogleForm from '../form/google-form';
import GoogleScholarForm from '../form/google-scholar-form';
import InvokeForm from '../form/invoke-form';
import Jin10Form from '../form/jin10-form';
import KeywordExtractForm from '../form/keyword-extract-form';
import MessageForm from '../form/message-form';
@ -74,6 +75,9 @@ const FormMap = {
[Operator.Jin10]: Jin10Form,
[Operator.TuShare]: TuShareForm,
[Operator.Crawler]: CrawlerForm,
[Operator.Invoke]: InvokeForm,
[Operator.Concentrator]: <></>,
[Operator.Note]: <></>,
};
const EmptyContent = () => <div></div>;

View File

@ -0,0 +1,119 @@
import { EditableCell, EditableRow } from '@/components/editable-cell';
import { useTranslate } from '@/hooks/common-hooks';
import { DeleteOutlined } from '@ant-design/icons';
import { Button, Flex, Input, Select, Table, TableProps } from 'antd';
import { useBuildComponentIdSelectOptions } from '../../hooks';
import { IInvokeVariable } from '../../interface';
import { useHandleOperateParameters } from './hooks';
import { trim } from 'lodash';
import styles from './index.less';
interface IProps {
nodeId?: string;
}
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const DynamicVariables = ({ nodeId }: IProps) => {
const { t } = useTranslate('flow');
const options = useBuildComponentIdSelectOptions(nodeId);
const {
dataSource,
handleAdd,
handleRemove,
handleSave,
handleComponentIdChange,
handleValueChange,
} = useHandleOperateParameters(nodeId!);
const columns: TableProps<IInvokeVariable>['columns'] = [
{
title: t('key'),
dataIndex: 'key',
key: 'key',
// width: 40,
onCell: (record: IInvokeVariable) => ({
record,
editable: true,
dataIndex: 'key',
title: 'key',
handleSave,
}),
},
{
title: t('componentId'),
dataIndex: 'component_id',
key: 'component_id',
align: 'center',
width: 140,
render(text, record) {
return (
<Select
style={{ width: '100%' }}
allowClear
options={options}
value={text}
disabled={trim(record.value) !== ''}
onChange={handleComponentIdChange(record)}
/>
);
},
},
{
title: t('value'),
dataIndex: 'value',
key: 'value',
align: 'center',
width: 140,
render(text, record) {
return (
<Input
value={text}
disabled={!!record.component_id}
onChange={handleValueChange(record)}
/>
);
},
},
{
title: t('operation'),
dataIndex: 'operation',
width: 20,
key: 'operation',
align: 'center',
fixed: 'right',
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}
scroll={{ x: true }}
bordered
/>
</section>
);
};
export default DynamicVariables;

View File

@ -0,0 +1,87 @@
import get from 'lodash/get';
import { ChangeEventHandler, useCallback, useMemo } from 'react';
import { v4 as uuid } from 'uuid';
import { IGenerateParameter, IInvokeVariable } from '../../interface';
import useGraphStore from '../../store';
export const useHandleOperateParameters = (nodeId: string) => {
const { getNode, updateNodeForm } = useGraphStore((state) => state);
const node = getNode(nodeId);
const dataSource: IGenerateParameter[] = useMemo(
() => get(node, 'data.form.variables', []) as IGenerateParameter[],
[node],
);
const changeValue = useCallback(
(row: IInvokeVariable, field: string, value: string) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
[field]: value,
});
updateNodeForm(nodeId, { variables: newData });
},
[dataSource, nodeId, updateNodeForm],
);
const handleComponentIdChange = useCallback(
(row: IInvokeVariable) => (value: string) => {
changeValue(row, 'component_id', value);
},
[changeValue],
);
const handleValueChange = useCallback(
(row: IInvokeVariable): ChangeEventHandler<HTMLInputElement> =>
(e) => {
changeValue(row, 'value', e.target.value);
},
[changeValue],
);
const handleRemove = useCallback(
(id?: string) => () => {
const newData = dataSource.filter((item) => item.id !== id);
updateNodeForm(nodeId, { variables: newData });
},
[updateNodeForm, nodeId, dataSource],
);
const handleAdd = useCallback(() => {
updateNodeForm(nodeId, {
variables: [
...dataSource,
{
id: uuid(),
key: '',
component_id: undefined,
value: '',
},
],
});
}, [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, { variables: newData });
};
return {
handleAdd,
handleRemove,
handleComponentIdChange,
handleValueChange,
handleSave,
dataSource,
};
};

View 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: 30px !important;
}
&:hover {
:global(.editable-cell-value-wrap) {
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
}
}

View File

@ -0,0 +1,72 @@
import Editor from '@monaco-editor/react';
import { Form, Input, InputNumber, Select, Space } from 'antd';
import { useTranslation } from 'react-i18next';
import { useSetLlmSetting } from '../../hooks';
import { IOperatorForm } from '../../interface';
import DynamicVariables from './dynamic-variables';
enum Method {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
}
const MethodOptions = [Method.GET, Method.POST, Method.PUT].map((x) => ({
label: x,
value: x,
}));
interface TimeoutInputProps {
value?: number;
onChange?: (value: number | null) => void;
}
const TimeoutInput = ({ value, onChange }: TimeoutInputProps) => {
const { t } = useTranslation();
return (
<Space>
<InputNumber value={value} onChange={onChange} /> {t('common.s')}
</Space>
);
};
const InvokeForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const { t } = useTranslation();
useSetLlmSetting(form);
return (
<>
<Form
name="basic"
autoComplete="off"
form={form}
onValuesChange={onValuesChange}
layout={'vertical'}
>
<Form.Item name={'url'} label={t('flow.url')}>
<Input />
</Form.Item>
<Form.Item
name={'method'}
label={t('flow.method')}
initialValue={Method.GET}
>
<Select options={MethodOptions} />
</Form.Item>
<Form.Item name={'timeout'} label={t('flow.timeout')}>
<TimeoutInput></TimeoutInput>
</Form.Item>
<Form.Item name={'headers'} label={t('flow.headers')}>
<Editor height={200} defaultLanguage="json" theme="vs-dark" />
</Form.Item>
<Form.Item name={'proxy'} label={t('flow.proxy')}>
<Input />
</Form.Item>
<DynamicVariables nodeId={node?.id}></DynamicVariables>
</Form>
</>
);
};
export default InvokeForm;

View File

@ -49,6 +49,7 @@ import {
initialGithubValues,
initialGoogleScholarValues,
initialGoogleValues,
initialInvokeValues,
initialJin10Values,
initialKeywordExtractValues,
initialMessageValues,
@ -132,6 +133,7 @@ export const useInitializeOperatorParams = () => {
[Operator.TuShare]: initialTuShareValues,
[Operator.Note]: initialNoteValues,
[Operator.Crawler]: initialCrawlerValues,
[Operator.Invoke]: initialInvokeValues,
};
}, [llmId]);

View File

@ -51,6 +51,10 @@ export interface IGenerateParameter {
component_id?: string;
}
export interface IInvokeVariable extends IGenerateParameter {
value?: string;
}
export type ICategorizeItemResult = Record<
string,
Omit<ICategorizeItem, 'name'>