feat: support select field as tool output (#2798)

* feat: support select field as tool output

* defaultOutput
This commit is contained in:
papapatrick
2024-09-26 14:37:06 +08:00
committed by GitHub
parent 1cf76ee7df
commit cb6fe9d0da
13 changed files with 323 additions and 46 deletions

View File

@@ -25,13 +25,11 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useI18n } from '@/web/context/I18n';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
import { defaultInput } from '../render/RenderInput/FieldEditModal';
import PluginOutputEditModal, { defaultOutput } from './PluginOutputEditModal';
const FieldEditModal = dynamic(() => import('../render/RenderInput/FieldEditModal'));
const customInputConfig = {
const customOutputConfig = {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: true,
showDefaultValue: true
};
@@ -62,7 +60,7 @@ const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
onClick={() => setEditField(defaultInput)}
onClick={() => setEditField(defaultOutput)}
>
{t('common:common.Add New')}
</Button>
@@ -78,9 +76,9 @@ const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
</Box>
</Container>
{!!editField && (
<FieldEditModal
customInputConfig={customInputConfig}
defaultInput={editField}
<PluginOutputEditModal
customOutputConfig={customOutputConfig}
defaultOutput={editField}
keys={inputs.map((input) => input.key)}
onClose={() => setEditField(undefined)}
onSubmit={({ data }) => {
@@ -171,31 +169,46 @@ function Reference({
return (
<>
<Flex alignItems={'center'} mb={1}>
<FormLabel required={input.required}>{input.label}</FormLabel>
{input.description && <QuestionTip ml={0.5} label={input.description}></QuestionTip>}
{/* value */}
<ValueTypeLabel valueType={input.valueType} valueDesc={input.valueDesc} />
<Flex alignItems={'center'} justify={'space-between'} mb={1}>
<Flex>
<FormLabel required={input.required}>{input.label}</FormLabel>
{input.description && <QuestionTip ml={0.5} label={input.description}></QuestionTip>}
{/* value */}
<ValueTypeLabel valueType={input.valueType} valueDesc={input.valueDesc} />
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
ml={3}
color={'myGray.600'}
_hover={{ color: 'primary.500' }}
onClick={() => setEditField(input)}
/>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
ml={3}
color={'myGray.600'}
_hover={{ color: 'primary.500' }}
onClick={() => setEditField(input)}
/>
<MyIcon
className="delete"
name={'delete'}
w={'14px'}
color={'myGray.500'}
cursor={'pointer'}
ml={2}
_hover={{ color: 'red.600' }}
onClick={openConfirm(onDel)}
/>
</Flex>
<MyIcon
className="delete"
name={'delete'}
name={
input.isToolOutput !== false
? 'core/workflow/template/toolkitActive'
: 'core/workflow/template/toolkitInactive'
}
w={'14px'}
color={'myGray.500'}
cursor={'pointer'}
ml={2}
mr={2}
_hover={{ color: 'red.600' }}
onClick={openConfirm(onDel)}
onClick={() => setEditField(input)}
/>
</Flex>
<ReferSelector
@@ -206,9 +219,9 @@ function Reference({
/>
{!!editField && (
<FieldEditModal
defaultInput={editField}
customInputConfig={customInputConfig}
<PluginOutputEditModal
defaultOutput={editField}
customOutputConfig={customOutputConfig}
keys={keys}
onClose={() => setEditField(undefined)}
onSubmit={onUpdateField}

View File

@@ -0,0 +1,197 @@
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
import {
Box,
Button,
Flex,
Input,
ModalBody,
ModalFooter,
Stack,
Textarea,
Switch
} from '@chakra-ui/react';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import {
CustomFieldConfigType,
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 { useToast } from '@fastgpt/web/hooks/useToast';
import React, { useCallback, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
import { useMount } from 'ahooks';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
const PluginOutputEditModal = ({
customOutputConfig,
defaultOutput,
keys,
onClose,
onSubmit
}: {
customOutputConfig: CustomFieldConfigType;
defaultOutput: FlowNodeInputItemType;
keys: string[];
onClose: () => void;
onSubmit: (e: { data: FlowNodeInputItemType; isChangeKey: boolean }) => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const isEdit = !!defaultOutput.key;
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
defaultValues: { ...defaultOutput, isToolOutput: defaultOutput.isToolOutput !== false }
});
const inputType = FlowNodeInputTypeEnum.reference;
// value type select
const showValueTypeSelect = useMemo(() => {
if (
!customOutputConfig.selectValueTypeList ||
customOutputConfig.selectValueTypeList.length <= 1
)
return false;
if (inputType === FlowNodeInputTypeEnum.reference) return true;
return false;
}, [customOutputConfig.selectValueTypeList, inputType]);
const valueTypeSelectList = useMemo(() => {
if (!customOutputConfig.selectValueTypeList) return [];
const dataTypeSelectList = Object.values(FlowValueTypeMap).map((item) => ({
label: t(item.label as any),
value: item.value
}));
return dataTypeSelectList.filter((item) =>
customOutputConfig.selectValueTypeList?.includes(item.value)
);
}, [customOutputConfig.selectValueTypeList, t]);
const valueType = watch('valueType');
useMount(() => {
if (
customOutputConfig.selectValueTypeList &&
customOutputConfig.selectValueTypeList.length > 0 &&
!valueType
) {
setValue('valueType', customOutputConfig.selectValueTypeList[0]);
}
});
const onSubmitSuccess = useCallback(
(data: FlowNodeInputItemType) => {
const isChangeKey = defaultOutput.key !== data.key;
if (keys.includes(data.key)) {
if (!isEdit || isChangeKey) {
toast({
status: 'warning',
title: t('workflow:field_name_already_exists')
});
return;
}
}
data.key = data?.key?.trim();
data.label = data.key;
onSubmit({
data,
isChangeKey
});
onClose();
},
[defaultOutput.key, isEdit, keys, onClose, onSubmit, toast, t]
);
const onSubmitError = useCallback(
(e: Object) => {
for (const item of Object.values(e)) {
if (item.message) {
toast({
status: 'warning',
title: item.message
});
break;
}
}
},
[toast]
);
return (
<MyModal
isOpen={true}
iconSrc="core/workflow/template/pluginOutput"
title={isEdit ? t('workflow:edit_output') : t('workflow:add_new_output')}
overflow={'unset'}
>
<ModalBody w={'100%'} overflow={'auto'} display={'flex'} flexDirection={['column', 'row']}>
<Stack w={'100%'} spacing={3}>
{showValueTypeSelect && (
<Flex alignItems={'center'}>
<FormLabel flex={'0 0 70px'}>{t('common:core.module.Data Type')}</FormLabel>
<Box flex={1}>
<MySelect<WorkflowIOValueTypeEnum>
w={'full'}
list={valueTypeSelectList.filter(
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
)}
value={valueType}
onchange={(e) => {
setValue('valueType', e);
}}
/>
</Box>
</Flex>
)}
{/* key */}
<Flex mt={3} alignItems={'center'}>
<FormLabel flex={'0 0 70px'} required>
{t('common:core.module.Field Name')}
</FormLabel>
<Input
bg={'myGray.50'}
placeholder="appointment/sql"
{...register('key', {
required: true
})}
/>
</Flex>
<Flex mt={3} alignItems={'center'}>
<FormLabel flex={'0 0 70px'}>{t('workflow:input_description')}</FormLabel>
<Textarea bg={'myGray.50'} {...register('description', {})} />
</Flex>
<Flex mt={3} alignItems={'center'} justifyContent={'space-between'}>
<FormLabel>{t('workflow:is_tool_output')}</FormLabel>
<Switch {...register('isToolOutput')} />
</Flex>
</Stack>
</ModalBody>
<ModalFooter gap={3}>
<Button variant={'whiteBase'} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button onClick={handleSubmit(onSubmitSuccess, onSubmitError)}>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default PluginOutputEditModal;
export const defaultOutput: FlowNodeInputItemType = {
renderTypeList: [FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
canEdit: true,
key: '',
label: '',
isToolOutput: true
};