4.8.12 dev (#2928)

* perf: optimize global variables (#2863)

* feat: add global variable types

* add global variables to debug

* fix select dnd

* unify InputTypeConfig params

* feat: http node url support variables (#2891)

* feat: http node url support variables

* change to prompt editor

* fix: global variables (#2892)

* fix global variables

* fix type

* perf: global variables

* perf: workflow delete node error (#2905)

* update lock

* update 4812 doc

* feat: add node course url config (#2897)

* feat: add node course url config

* change plugin course url

* change default doc url

* change url store

* delete unused code

* fix: global variable (#2915)

* fix: global variable

* add comment

* fix: interactive check

* locj

* perf: debug switch to global tab when click run & global var default reset (#2925)

* fix: tool course url

* fix: global var default value & wrap variable form (#2926)

* fix: add dataset tags not update render (#2927)

* feat: tool will save histories

* perf: global variables code

* perf: FE_DOMAIN config

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-10-15 20:23:18 +08:00
committed by shilin66
parent 14351d8b8b
commit d6cf5470be
58 changed files with 955 additions and 669 deletions

View File

@@ -2,17 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import {
Box,
Button,
ModalFooter,
ModalBody,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
Flex,
Switch,
Input,
FormControl,
Table,
Thead,
Tbody,
@@ -20,7 +10,7 @@ import {
Th,
Td,
TableContainer,
useDisclosure
Stack
} from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import {
@@ -31,38 +21,34 @@ import {
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useForm } from 'react-hook-form';
import { useFieldArray } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyRadio from '@/components/common/MyRadio';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
import MySelect from '@fastgpt/web/components/common/MySelect';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import InputTypeConfig from '@/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
export const defaultVariable: VariableItemType = {
id: nanoid(),
key: 'key',
label: 'label',
key: '',
label: '',
type: VariableInputEnum.input,
description: '',
required: true,
maxLen: 50,
enums: [{ value: '' }],
valueType: WorkflowIOValueTypeEnum.string
};
export const addVariable = () => {
const newVariable = { ...defaultVariable, key: '', id: '' };
return newVariable;
type InputItemType = VariableItemType & {
list: { label: string; value: string }[];
};
const valueTypeMap = {
[VariableInputEnum.input]: WorkflowIOValueTypeEnum.string,
[VariableInputEnum.select]: WorkflowIOValueTypeEnum.string,
[VariableInputEnum.textarea]: WorkflowIOValueTypeEnum.string,
[VariableInputEnum.custom]: WorkflowIOValueTypeEnum.any
export const addVariable = () => {
const newVariable = { ...defaultVariable, key: '', id: '', list: [{ value: '', label: '' }] };
return newVariable;
};
const VariableEdit = ({
@@ -74,46 +60,37 @@ const VariableEdit = ({
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const [refresh, setRefresh] = useState(false);
const VariableTypeList = useMemo(
const form = useForm<VariableItemType>();
const { setValue, reset, watch, getValues } = form;
const value = getValues();
const type = watch('type');
const valueType = watch('valueType');
const max = watch('max');
const min = watch('min');
const defaultValue = watch('defaultValue');
const inputTypeList = useMemo(
() =>
Object.entries(variableMap).map(([key, value]) => ({
title: t(value.title as any),
icon: value.icon,
value: key
Object.values(variableMap).map((item) => ({
icon: item.icon,
label: t(item.label as any),
value: item.value,
defaultValueType: item.defaultValueType,
description: item.description ? t(item.description as any) : ''
})),
[t]
);
const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure();
const {
setValue,
reset: resetEdit,
register: registerEdit,
getValues: getValuesEdit,
setValue: setValuesEdit,
control: editVariableController,
handleSubmit: handleSubmitEdit,
watch
} = useForm<{ variable: VariableItemType }>();
const variableType = watch('variable.type');
const valueType = watch('variable.valueType');
const {
fields: selectEnums,
append: appendEnums,
remove: removeEnums
} = useFieldArray({
control: editVariableController,
name: 'variable.enums'
});
const defaultValueType = useMemo(() => {
const item = inputTypeList.find((item) => item.value === type);
return item?.defaultValueType;
}, [inputTypeList, type]);
const formatVariables = useMemo(() => {
const results = formatEditorVariablePickerIcon(variables);
return results.map((item) => {
const variable = variables.find((variable) => variable.key === item.key);
return results.map<VariableItemType & { icon?: string }>((item) => {
const variable = variables.find((variable) => variable.key === item.key)!;
return {
...variable,
icon: item.icon
@@ -121,45 +98,12 @@ const VariableEdit = ({
});
}, [variables]);
const valueTypeSelectList = useMemo(
() =>
Object.values(FlowValueTypeMap)
.map((item) => ({
label: t(item.label as any),
value: item.value
}))
.filter(
(item) =>
![
WorkflowIOValueTypeEnum.arrayAny,
WorkflowIOValueTypeEnum.selectApp,
WorkflowIOValueTypeEnum.selectDataset,
WorkflowIOValueTypeEnum.dynamic
].includes(item.value)
),
[t]
);
const showValueTypeSelect = variableType === VariableInputEnum.custom;
const onSubmitSuccess = useCallback(
(data: InputItemType, action: 'confirm' | 'continue') => {
data.label = data?.label?.trim();
const onSubmit = useCallback(
({ variable }: { variable: VariableItemType }) => {
variable.key = variable.key.trim();
// check select
if (variable.type === VariableInputEnum.select) {
const enums = variable.enums.filter((item) => item.value);
if (enums.length === 0) {
toast({
status: 'warning',
title: t('common:core.module.variable.variable option is required')
});
return;
}
}
// check repeat key
const existingVariable = variables.find(
(item) => item.key === variable.key && item.id !== variable.id
(item) => item.label === data.label && item.id !== data.id
);
if (existingVariable) {
toast({
@@ -169,32 +113,59 @@ const VariableEdit = ({
return;
}
// set valuetype based on variable.type
variable.valueType =
variable.type === VariableInputEnum.custom
? variable.valueType
: valueTypeMap[variable.type];
data.key = data.label;
data.enums = data.list;
// set default required value based on variableType
if (variable.type === VariableInputEnum.custom) {
variable.required = false;
if (data.type === VariableInputEnum.custom) {
data.required = false;
}
if (data.type === VariableInputEnum.numberInput) {
data.valueType = WorkflowIOValueTypeEnum.number;
}
const onChangeVariable = [...variables];
// update
if (variable.id) {
const index = variables.findIndex((item) => item.id === variable.id);
onChangeVariable[index] = variable;
if (data.id) {
const index = variables.findIndex((item) => item.id === data.id);
onChangeVariable[index] = data;
} else {
onChangeVariable.push({
...variable,
...data,
id: nanoid()
});
}
onChange(onChangeVariable);
onCloseEdit();
if (action === 'confirm') {
onChange(onChangeVariable);
reset({});
} else if (action === 'continue') {
onChange(onChangeVariable);
toast({
status: 'success',
title: t('common:common.Add Success')
});
reset({
...addVariable(),
defaultValue: ''
});
}
},
[onChange, onCloseEdit, t, toast, variables]
[variables, toast, t, onChange, reset]
);
const onSubmitError = useCallback(
(e: Object) => {
for (const item of Object.values(e)) {
if (item.message) {
toast({
status: 'warning',
title: item.message
});
break;
}
}
},
[toast]
);
return (
@@ -212,8 +183,7 @@ const VariableEdit = ({
size={'sm'}
mr={'-5px'}
onClick={() => {
resetEdit({ variable: addVariable() });
onOpenEdit();
reset(addVariable());
}}
>
{t('common:common.Add New')}
@@ -232,7 +202,7 @@ const VariableEdit = ({
w={'18px !important'}
p={0}
/>
<Th fontSize={'mini'}>{t('common:core.module.variable.variable name')}</Th>
<Th fontSize={'mini'}>{t('workflow:Variable_name')}</Th>
<Th fontSize={'mini'}>{t('common:core.module.variable.key')}</Th>
<Th fontSize={'mini'}>{t('common:common.Require Input')}</Th>
<Th fontSize={'mini'} borderRadius={'none !important'}></Th>
@@ -241,8 +211,8 @@ const VariableEdit = ({
<Tbody>
{formatVariables.map((item) => (
<Tr key={item.id}>
<Td textAlign={'center'} p={0} pl={3}>
<MyIcon name={item.icon as any} w={'14px'} color={'myGray.500'} />
<Td p={0} pl={3}>
<MyIcon name={item.icon as any} w={'16px'} color={'myGray.500'} />
</Td>
<Td>{item.label}</Td>
<Td>{item.key}</Td>
@@ -254,8 +224,11 @@ const VariableEdit = ({
w={'16px'}
cursor={'pointer'}
onClick={() => {
resetEdit({ variable: item });
onOpenEdit();
const formattedItem = {
...item,
list: item.enums || []
};
reset(formattedItem);
}}
/>
<MyIcon
@@ -274,160 +247,100 @@ const VariableEdit = ({
</TableContainer>
</Box>
)}
{/* Edit modal */}
<MyModal
iconSrc="core/app/simpleMode/variable"
title={t('common:core.module.Variable Setting')}
isOpen={isOpenEdit}
onClose={onCloseEdit}
maxW={['90vw', '500px']}
>
<ModalBody>
{variableType !== VariableInputEnum.custom && (
<Flex alignItems={'center'}>
<FormLabel w={'70px'}>{t('common:common.Require Input')}</FormLabel>
<Switch {...registerEdit('variable.required')} />
</Flex>
)}
<Flex mt={5} alignItems={'center'}>
<FormLabel w={'80px'}>{t('common:core.module.variable.variable name')}</FormLabel>
<Input
{...registerEdit('variable.label', {
required: t('common:core.module.variable.variable name is required')
})}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<FormLabel w={'80px'}>{t('common:core.module.variable.key')}</FormLabel>
<Input
{...registerEdit('variable.key', {
required: t('common:core.module.variable.key is required')
})}
/>
</Flex>
<Flex mt={5} alignItems={'center'}>
<FormLabel w={'80px'}>{t('workflow:value_type')}</FormLabel>
{showValueTypeSelect ? (
<Box flex={1}>
<MySelect<WorkflowIOValueTypeEnum>
list={valueTypeSelectList.filter(
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
)}
value={valueType}
onchange={(e) => {
setValue('variable.valueType', e);
}}
/>
</Box>
) : (
<Box fontSize={'14px'}>{valueTypeMap[variableType]}</Box>
)}
</Flex>
<FormLabel mt={5} mb={2}>
{t('common:core.workflow.Variable.Variable type')}
</FormLabel>
<MyRadio
gridGap={4}
gridTemplateColumns={'repeat(2,1fr)'}
value={variableType}
list={VariableTypeList}
color={'myGray.600'}
hiddenCircle
onChange={(e) => {
setValuesEdit('variable.type', e as any);
setRefresh(!refresh);
}}
/>
{/* desc */}
{variableMap[variableType]?.desc && (
<Box mt={2} fontSize={'sm'} color={'myGray.500'} whiteSpace={'pre-wrap'}>
{t(variableMap[variableType].desc as any)}
</Box>
)}
{variableType === VariableInputEnum.input && (
<>
<FormLabel mt={5} mb={2}>
{t('common:core.module.variable.text max length')}
{!!Object.keys(value).length && (
<MyModal
iconSrc="core/app/simpleMode/variable"
title={t('common:core.module.Variable Setting')}
isOpen={true}
onClose={() => reset({})}
maxW={['90vw', '928px']}
w={'100%'}
isCentered
>
<Flex h={'560px'}>
<Stack gap={4} p={8}>
<FormLabel color={'myGray.600'} fontWeight={'medium'}>
{t('workflow:Variable.Variable type')}
</FormLabel>
<Box>
<NumberInput max={500} min={1} step={1} position={'relative'}>
<NumberInputField
{...registerEdit('variable.maxLen', {
min: 1,
max: 500,
valueAsNumber: true
})}
max={500}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Box>
</>
)}
{variableType === VariableInputEnum.select && (
<>
<Box mt={5} mb={2}>
{t('common:core.module.variable.variable options')}
</Box>
<Box>
{selectEnums.map((item, i) => (
<Flex key={item.id} mb={2} alignItems={'center'}>
<FormControl>
<Input
{...registerEdit(`variable.enums.${i}.value`, {
required: t(
'common:core.module.variable.variable option is value is required'
)
})}
/>
</FormControl>
{selectEnums.length > 1 && (
<Flex flexDirection={'column'} gap={4}></Flex>
<Box display={'grid'} gridTemplateColumns={'repeat(2, 1fr)'} gap={4}>
{inputTypeList.map((item) => {
const isSelected = type === item.value;
return (
<Box
display={'flex'}
key={item.label}
border={isSelected ? '1px solid #3370FF' : '1px solid #DFE2EA'}
p={3}
rounded={'6px'}
fontWeight={'medium'}
fontSize={'14px'}
alignItems={'center'}
cursor={'pointer'}
boxShadow={isSelected ? '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)' : 'none'}
_hover={{
'& > svg': {
color: 'primary.600'
},
'& > span': {
color: 'myGray.900'
},
border: '1px solid #3370FF',
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
}}
onClick={() => {
const defaultValIsNumber = !isNaN(Number(value.defaultValue));
// 如果切换到 numberInput不是数字则清空
if (
item.value === VariableInputEnum.select ||
(item.value === VariableInputEnum.numberInput && !defaultValIsNumber)
) {
setValue('defaultValue', '');
}
setValue('type', item.value);
}}
>
<MyIcon
ml={3}
name={'delete'}
w={'16px'}
cursor={'pointer'}
p={2}
borderRadius={'md'}
_hover={{ bg: 'red.100' }}
onClick={() => removeEnums(i)}
name={item.icon as any}
w={'20px'}
mr={1.5}
color={isSelected ? 'primary.600' : 'myGray.400'}
/>
)}
</Flex>
))}
<Box
as="span"
color={isSelected ? 'myGray.900' : 'inherit'}
pr={4}
whiteSpace="nowrap"
>
{item.label}
</Box>
{item.description && (
<QuestionTip label={item.description as string} ml={1} />
)}
</Box>
);
})}
</Box>
<Button
variant={'solid'}
w={'100%'}
textAlign={'left'}
leftIcon={<SmallAddIcon />}
bg={'myGray.100 !important'}
onClick={() => appendEnums({ value: '' })}
>
{t('common:core.module.variable add option')}
</Button>
</>
)}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onCloseEdit}>
{t('common:common.Close')}
</Button>
<Button onClick={handleSubmitEdit(onSubmit)}>
{getValuesEdit('variable.id')
? t('common:common.Confirm Update')
: t('common:common.Add New')}
</Button>
</ModalFooter>
</MyModal>
</Stack>
<InputTypeConfig
form={form}
type={'variable'}
isEdit={!!value.key}
inputType={type}
valueType={valueType}
defaultValue={defaultValue}
defaultValueType={defaultValueType}
max={max}
min={min}
onClose={() => reset({})}
onSubmitSuccess={onSubmitSuccess}
onSubmitError={onSubmitError}
/>
</Flex>
</MyModal>
)}
</Box>
);
};

View File

@@ -81,16 +81,11 @@ const ChatInput = ({
const canSendMessage = havInput && !hasFileUploading;
// Upload files
useRequest2(
async () => {
uploadFiles();
},
{
manual: false,
errorToast: t('common:upload_file_error'),
refreshDeps: [fileList, outLinkAuthData, chatId]
}
);
useRequest2(uploadFiles, {
manual: false,
errorToast: t('common:upload_file_error'),
refreshDeps: [fileList, outLinkAuthData, chatId]
});
/* on send */
const handleSend = useCallback(

View File

@@ -1,7 +1,18 @@
import React from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import { Controller, UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
import { Box, Button, Card, FormControl, Input, Textarea } from '@chakra-ui/react';
import {
Box,
Button,
Card,
Input,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Textarea
} from '@chakra-ui/react';
import ChatAvatar from './ChatAvatar';
import { MessageCardStyle } from '../constants';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
@@ -10,6 +21,113 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { ChatBoxInputFormType } from '../type.d';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../Provider';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { useDeepCompareEffect } from 'ahooks';
import { VariableItemType } from '@fastgpt/global/core/app/type';
export const VariableInputItem = ({
item,
variablesForm
}: {
item: VariableItemType;
variablesForm: UseFormReturn<any>;
}) => {
const { register, control, setValue } = variablesForm;
return (
<Box key={item.id} mb={4} pl={1}>
<Box
as={'label'}
display={'flex'}
position={'relative'}
mb={1}
alignItems={'center'}
w={'full'}
>
{item.label}
{item.required && (
<Box
position={'absolute'}
top={'-2px'}
left={'-8px'}
color={'red.500'}
fontWeight={'bold'}
>
*
</Box>
)}
{item.description && <QuestionTip ml={1} label={item.description} />}
</Box>
{item.type === VariableInputEnum.input && (
<Input
maxLength={item.maxLength || 4000}
bg={'myGray.50'}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.textarea && (
<Textarea
{...register(item.key, {
required: item.required
})}
rows={5}
bg={'myGray.50'}
maxLength={item.maxLength || 4000}
/>
)}
{item.type === VariableInputEnum.select && (
<Controller
key={item.key}
control={control}
name={item.key}
rules={{ required: item.required }}
render={({ field: { ref, value } }) => {
return (
<MySelect
ref={ref}
width={'100%'}
list={(item.enums || []).map((item: { value: any }) => ({
label: item.value,
value: item.value
}))}
value={value}
onchange={(e) => setValue(item.key, e)}
/>
);
}}
/>
)}
{item.type === VariableInputEnum.numberInput && (
<Controller
key={item.key}
control={control}
name={item.key}
rules={{ required: item.required, min: item.min, max: item.max }}
render={({ field: { ref, value, onChange } }) => (
<NumberInput
step={1}
min={item.min}
max={item.max}
bg={'white'}
rounded={'md'}
clampValueOnBlur={false}
value={value}
onChange={(valueString) => onChange(Number(valueString))}
>
<NumberInputField ref={ref} bg={'white'} />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
)}
/>
)}
</Box>
);
};
const VariableInput = ({
chatForm,
@@ -21,13 +139,22 @@ const VariableInput = ({
const { t } = useTranslation();
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
const { register, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
const { reset, handleSubmit: handleSubmitChat } = variablesForm;
const defaultValues = useMemo(() => {
return variableList.reduce((acc: Record<string, any>, item) => {
acc[item.key] = item.defaultValue;
return acc;
}, {});
}, [variableList]);
useDeepCompareEffect(() => {
reset(defaultValues);
}, [defaultValues]);
return (
<Box py={3}>
{/* avatar */}
<ChatAvatar src={appAvatar} type={'AI'} />
{/* message */}
<Box textAlign={'left'}>
<Card
order={2}
@@ -38,74 +165,21 @@ const VariableInput = ({
boxShadow={'0 0 8px rgba(0,0,0,0.15)'}
>
{variableList.map((item) => (
<Box key={item.id} mb={4}>
<Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
{item.label}
{item.required && (
<Box
position={'absolute'}
top={'-2px'}
right={'-10px'}
color={'red.500'}
fontWeight={'bold'}
>
*
</Box>
)}
</Box>
{item.type === VariableInputEnum.input && (
<Input
bg={'myWhite.400'}
{...register(item.key, {
required: item.required
})}
/>
)}
{item.type === VariableInputEnum.textarea && (
<Textarea
bg={'myWhite.400'}
{...register(item.key, {
required: item.required
})}
rows={5}
maxLength={4000}
/>
)}
{item.type === VariableInputEnum.select && (
<Controller
key={item.key}
control={control}
name={item.key}
rules={{ required: item.required }}
render={({ field: { ref, value } }) => {
return (
<MySelect
ref={ref}
width={'100%'}
list={(item.enums || []).map((item) => ({
label: item.value,
value: item.value
}))}
value={value}
onchange={(e) => setValue(item.key, e)}
/>
);
}}
/>
)}
</Box>
<VariableInputItem key={item.id} item={item} variablesForm={variablesForm} />
))}
{!chatStarted && (
<Button
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
onClick={handleSubmitChat(() => {
chatForm.setValue('chatStarted', true);
})}
>
{t('common:core.chat.Start Chat')}
</Button>
<Box>
<Button
leftIcon={<MyIcon name={'core/chat/chatFill'} w={'16px'} />}
size={'sm'}
maxW={'100px'}
onClick={handleSubmitChat(() => {
chatForm.setValue('chatStarted', true);
})}
>
{t('common:core.chat.Start Chat')}
</Button>
</Box>
)}
</Card>
</Box>

View File

@@ -181,7 +181,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
});
// Update file url
copyFile.url = `${location.origin}${previewUrl}`;
copyFile.url = previewUrl;
updateFiles(fileIndex, copyFile);
} catch (error) {
errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!);

View File

@@ -495,7 +495,8 @@ const ChatBox = (
// 这里,无论是否为交互模式,最后都是 Human 的消息。
const messages = chats2GPTMessages({
messages: newChatList.slice(0, -1),
reserveId: true
reserveId: true,
reserveTool: true
});
const {

View File

@@ -57,9 +57,9 @@ export const checkIsInteractiveByHistories = (chatHistories: ChatSiteItemType[])
) {
const params = lastMessageValue.interactive.params;
// 如果用户选择了,则不认为是交互模式(可能是上一轮以交互结尾,发起的新的一轮对话)
if ('userSelectOptions' in params && 'userSelectedVal' in params) {
if ('userSelectOptions' in params) {
return !params.userSelectedVal;
} else if ('inputForm' in params && 'submitted' in params) {
} else if ('inputForm' in params) {
return !params.submitted;
}
}

View File

@@ -228,7 +228,8 @@ const PluginRunContextProvider = ({
value: []
}
],
reserveId: true
reserveId: true,
reserveTool: true
});
try {