feat: add tool params node & tool params add array type (#2824)

* add tool params node

* add tool params enum

* node response

* tool add array type params

* fix tool params

* fix

* fix

* fix
This commit is contained in:
heheer
2024-09-28 15:58:55 +08:00
committed by GitHub
parent f2749cbb00
commit 1599d144ce
26 changed files with 438 additions and 28 deletions

View File

@@ -355,6 +355,12 @@ export const WholeResponseContent = ({
{/* form input */}
<Row label={t('workflow:form_input_result')} value={activeModule?.formInputResult} />
{/* tool params */}
<Row
label={t('workflow:tool_params.tool_params_result')}
value={activeModule?.toolParamsResult}
/>
</Box>
) : null;
};

View File

@@ -106,8 +106,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) {
return false;
}
// tool stop
if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) {
// tool stop or tool params
if (
!hasToolNode &&
(item.flowNodeType === FlowNodeTypeEnum.stopTool ||
item.flowNodeType === FlowNodeTypeEnum.toolParams)
) {
return false;
}
return true;

View File

@@ -48,6 +48,7 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.stopTool]: (data: NodeProps<FlowNodeItemType>) => (
<NodeSimple {...data} minW={'100px'} maxW={'300px'} />
),
[FlowNodeTypeEnum.toolParams]: dynamic(() => import('./nodes/NodeToolParams')),
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')),
[FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')),
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')),

View File

@@ -0,0 +1,172 @@
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
import { Box, Button, Flex, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MySelect from '@fastgpt/web/components/common/MySelect';
import React, { useCallback } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
const ToolParamsEditModal = ({
defaultValue = defaultEditFormData,
nodeId,
onClose
}: {
defaultValue: FlowNodeInputItemType;
nodeId: string;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
defaultValues: defaultValue
});
const valueType = watch('valueType');
const { runAsync: onClickSubmit } = useRequest2(
async (e: FlowNodeInputItemType) => {
e.key = e.key.trim();
const inputConfig: FlowNodeInputItemType = {
...e,
description: e.toolDescription,
label: e.key
};
if (defaultValue.key) {
// edit
onChangeNode({
nodeId,
type: 'replaceInput',
key: defaultValue.key,
value: inputConfig
});
onChangeNode({
nodeId,
type: 'replaceOutput',
key: defaultValue.key,
value: {
...e,
id: e.key,
label: e.key,
type: FlowNodeOutputTypeEnum.static
}
});
} else {
// create
onChangeNode({
nodeId,
type: 'addInput',
value: {
...e,
label: e.key
}
});
onChangeNode({
nodeId,
type: 'addOutput',
value: {
...e,
id: e.key,
label: e.key,
type: FlowNodeOutputTypeEnum.static
}
});
}
onClose();
},
{
onSuccess: () => {
onClose();
}
}
);
const onClickSubmitError = useCallback(
(e: Object) => {
for (const item of Object.values(e)) {
if (item.message) {
toast({
status: 'warning',
title: item.message
});
break;
}
}
},
[toast]
);
return (
<MyModal isOpen iconSrc="modal/edit" title={t('workflow:tool_field')} onClose={onClose}>
<ModalBody>
<Flex alignItems={'center'} mb={5}>
<FormLabel flex={'0 0 80px'}>{t('common:core.module.Data Type')}</FormLabel>
<Box flex={'1 0 0'}>
<MySelect
list={fnValueTypeSelect}
value={valueType}
onchange={(e: any) => {
setValue('valueType', e);
}}
/>
</Box>
</Flex>
<Flex alignItems={'center'} mb={5}>
<FormLabel flex={'0 0 80px'}>{t('workflow:tool_params.params_name')}</FormLabel>
<Input
bg={'myGray.50'}
{...register('key', {
required: true,
pattern: {
value: /^[a-zA-Z]+[0-9]*$/,
message: t('common:info.felid_message')
}
})}
placeholder={t('workflow:tool_params.params_name_placeholder')}
/>
</Flex>
<Flex alignItems={'center'} mb={5}>
<FormLabel flex={'0 0 80px'}>{t('workflow:tool_params.params_description')}</FormLabel>
<Input
bg={'myGray.50'}
{...register('toolDescription', {
required: true
})}
placeholder={t('workflow:tool_params.params_description_placeholder')}
/>
</Flex>
<Box>
<Flex alignItems={'center'} mb={2}>
<FormLabel>{t('workflow:tool_params.enum_values')}</FormLabel>
<QuestionTip label={t('workflow:tool_params.enum_values_tip')} />
</Flex>
<Textarea
bg={'myGray.50'}
{...register('enum')}
placeholder={t('workflow:tool_params.enum_placeholder')}
/>
</Box>
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button onClick={handleSubmit((data) => onClickSubmit(data), onClickSubmitError)}>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(ToolParamsEditModal);

View File

@@ -0,0 +1,117 @@
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import React, { useMemo, useState } from 'react';
import Container from '../../components/Container';
import {
Box,
Button,
Flex,
FormLabel,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { SmallAddIcon } from '@chakra-ui/icons';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
import ToolParamsEditModal from './ToolParamsEditModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const [editField, setEditField] = useState<FlowNodeInputItemType>();
const { nodeId, inputs } = data;
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => {
return (
<NodeCard selected={selected} {...data}>
<Container>
<Flex alignItems={'center'} justifyContent={'space-between'} mb={1.5}>
<FormLabel>{t('workflow:custom_tool_input')}</FormLabel>
<Button
variant={'whiteBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
onClick={() => setEditField(defaultEditFormData)}
>
{t('common:add_new_param')}
</Button>
{!!editField && (
<ToolParamsEditModal
defaultValue={editField}
nodeId={nodeId}
onClose={() => setEditField(undefined)}
/>
)}
</Flex>
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th>{t('workflow:tool_params.params_name')}</Th>
<Th>{t('workflow:tool_params.params_description')}</Th>
<Th>{t('common:common.Operation')}</Th>
</Tr>
</Thead>
<Tbody>
{inputs.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
>
<Td>{item.key}</Td>
<Td>{item.toolDescription}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => setEditField(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
onChangeNode({
nodeId,
type: 'delInput',
key: item.key
});
onChangeNode({
nodeId,
type: 'delOutput',
key: item.key
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Container>
</NodeCard>
);
}, [selected, data, t, editField, inputs, onChangeNode, nodeId]);
return Render;
};
export default React.memo(NodeToolParams);

View File

@@ -103,8 +103,9 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
(item) => item.renderType === updateItem.renderType
);
const nodeIds = nodeList.map((node) => node.nodeId);
const handleUpdate = (newValue: ReferenceValueProps | string) => {
if (isReferenceValue(newValue)) {
if (isReferenceValue(newValue, nodeIds)) {
onUpdateList(
updateList.map((update, i) =>
i === index ? { ...update, value: newValue as ReferenceValueProps } : update

View File

@@ -85,7 +85,7 @@ const EditFieldModal = ({
);
return (
<MyModal isOpen iconSrc="modal/edit" title={t('common:tool_field')} onClose={onClose}>
<MyModal isOpen iconSrc="modal/edit" title={t('workflow:tool_field')} onClose={onClose}>
<ModalBody>
<Flex alignItems={'center'} mb={5}>
<Box flex={'0 0 80px'}>{t('common:common.Require Input')}</Box>

View File

@@ -12,5 +12,17 @@ export const fnValueTypeSelect = [
{
label: WorkflowIOValueTypeEnum.boolean,
value: WorkflowIOValueTypeEnum.boolean
},
{
label: WorkflowIOValueTypeEnum.arrayString,
value: WorkflowIOValueTypeEnum.arrayString
},
{
label: WorkflowIOValueTypeEnum.arrayNumber,
value: WorkflowIOValueTypeEnum.arrayNumber
},
{
label: WorkflowIOValueTypeEnum.arrayBoolean,
value: WorkflowIOValueTypeEnum.arrayBoolean
}
];