Perf workflow (#1492)

* perf: handle edge check

* search model

* feat: plugin input can render all input; fix: plugin default value

* fix ts

* feat: plugin input support required
This commit is contained in:
Archer
2024-05-15 16:17:43 +08:00
committed by GitHub
parent cd876251b7
commit 8386f707cd
36 changed files with 256 additions and 160 deletions

View File

@@ -96,7 +96,7 @@ export const DatasetSelectModal = ({
_hover={{ color: 'red.500' }}
onClick={() => {
setSelectedDatasets((state) =>
state.filter((kb) => kb.datasetId !== item._id)
state.filter((dataset) => dataset.datasetId !== item._id)
);
}}
/>
@@ -141,7 +141,9 @@ export const DatasetSelectModal = ({
if (item.type === DatasetTypeEnum.folder) {
setParentId(item._id);
} else {
const vectorModel = selectedDatasets[0]?.vectorModel?.model;
const vectorModel = datasets.find(
(dataset) => dataset._id === selectedDatasets[0]?.datasetId
)?.vectorModel?.model;
if (vectorModel && vectorModel !== item.vectorModel.model) {
return toast({
@@ -149,10 +151,7 @@ export const DatasetSelectModal = ({
title: t('dataset.Select Dataset Tips')
});
}
setSelectedDatasets((state) => [
...state,
{ datasetId: item._id, vectorModel: item.vectorModel }
]);
setSelectedDatasets((state) => [...state, { datasetId: item._id }]);
}
}}
>

View File

@@ -26,6 +26,8 @@ import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
@@ -109,15 +111,23 @@ export const useDebug = () => {
const runtimeNode = runtimeNodes.find((node) => node.nodeId === runtimeNodeId);
if (!runtimeNode) return <></>;
const referenceInputs = runtimeNode.inputs.filter((input) => {
const renderInputs = runtimeNode.inputs.filter((input) => {
if (runtimeNode.flowNodeType === FlowNodeTypeEnum.pluginInput) return true;
if (checkInputIsReference(input)) return true;
if (input.required && !input.value) return true;
});
const { register, getValues, setValue, handleSubmit } = useForm<Record<string, any>>({
defaultValues: referenceInputs.reduce((acc, input) => {
//@ts-ignore
acc[input.key] = undefined;
defaultValues: renderInputs.reduce((acc: Record<string, any>, input) => {
const isReference = checkInputIsReference(input);
if (isReference) {
acc[input.key] = undefined;
} else if (typeof input.value === 'object') {
acc[input.key] = JSON.stringify(input.value, null, 2);
} else {
acc[input.key] = input.value;
}
return acc;
}, {})
});
@@ -153,73 +163,81 @@ export const useDebug = () => {
iconSrc="core/workflow/debugBlue"
title={t('core.workflow.Debug Node')}
maxW={['90vw', '35vw']}
px={0}
>
<Flex flexDirection={'column'} h={'100%'}>
<Box flex={'1 0 0'} overflow={'auto'}>
{referenceInputs.map((input) => {
<Box flex={'1 0 0'} overflow={'auto'} px={6}>
{renderInputs.map((input) => {
const required = input.required || false;
return (
<Box key={input.key} _notLast={{ mb: 4 }} px={1}>
<Box display={'inline-block'} position={'relative'} mb={1}>
{required && (
<Box position={'absolute'} right={-2} top={-1} color={'red.600'}>
*
</Box>
)}
{t(input.debugLabel || input.label)}
</Box>
{(() => {
if (input.valueType === WorkflowIOValueTypeEnum.string) {
return (
<Textarea
{...register(input.key, {
required
})}
placeholder={t(input.placeholder || '')}
bg={'myGray.50'}
/>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.number) {
return (
<NumberInput
step={input.step}
min={input.min}
max={input.max}
bg={'myGray.50'}
>
<NumberInputField
{...register(input.key, {
required: input.required,
min: input.min,
max: input.max,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
return <Switch size={'lg'} {...register(input.key)} />;
}
return (
<JsonEditor
bg={'myGray.50'}
placeholder={t(input.placeholder || '')}
resize
value={getValues(input.key)}
onChange={(e) => {
setValue(input.key, e);
}}
console.log(input.valueType);
const RenderInput = (() => {
if (input.valueType === WorkflowIOValueTypeEnum.string) {
return (
<Textarea
{...register(input.key, {
required
})}
placeholder={t(input.placeholder || '')}
bg={'myGray.50'}
/>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.number) {
return (
<NumberInput step={input.step} min={input.min} max={input.max} bg={'myGray.50'}>
<NumberInputField
{...register(input.key, {
required: input.required,
min: input.min,
max: input.max,
valueAsNumber: true
})}
/>
);
})()}
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
return (
<Box>
<Switch size={'lg'} {...register(input.key)} />
</Box>
);
}
if (typeof input.value === 'string') {
return (
<JsonEditor
bg={'myGray.50'}
placeholder={t(input.placeholder || '')}
resize
value={getValues(input.key)}
onChange={(e) => {
setValue(input.key, e);
}}
/>
);
}
})();
return !!RenderInput ? (
<Box key={input.key} _notLast={{ mb: 4 }} px={1}>
<Flex alignItems={'center'} mb={1}>
<Box position={'relative'}>
{required && (
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
*
</Box>
)}
{t(input.debugLabel || input.label)}
</Box>
{input.description && <QuestionTip ml={2} label={input.description} />}
</Flex>
{RenderInput}
</Box>
);
) : null;
})}
</Box>
<Flex py={2} justifyContent={'flex-end'}>

View File

@@ -35,7 +35,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.emptyNode]: NodeSimple,
[FlowNodeTypeEnum.globalVariable]: NodeSimple,
[FlowNodeTypeEnum.systemConfig]: dynamic(() => import('./nodes/NodeSystemConfig')),

View File

@@ -146,17 +146,9 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
if (!input) return;
setEditField({
...input,
inputType: input.renderTypeList[0],
valueType: input.valueType,
key: input.key,
label: input.label,
description: input.description,
isToolInput: !!input.toolDescription,
defaultValue: input.defaultValue,
maxLength: input.maxLength,
max: input.max,
min: input.min,
dynamicParamDefaultValue: input.dynamicParamDefaultValue
isToolInput: !!input.toolDescription
});
}}
onEdit={({ data, changeKey }) => {
@@ -165,20 +157,14 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const output = outputs.find((output) => output.key === editField.key);
const newInput: FlowNodeInputItemType = {
...data,
key: data.key,
valueType: data.valueType,
label: data.label || '',
renderTypeList: [data.inputType],
required: data.required,
description: data.description,
toolDescription: data.isToolInput ? data.description : undefined,
canEdit: true,
value: data.defaultValue,
editField: dynamicInputEditField,
maxLength: data.maxLength,
max: data.max,
min: data.min,
dynamicParamDefaultValue: data.dynamicParamDefaultValue
editField: dynamicInputEditField
};
const newOutput: FlowNodeOutputItemType = {
...(output as FlowNodeOutputItemType),

View File

@@ -24,6 +24,8 @@ import MySelect from '@fastgpt/web/components/common/MySelect';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import dynamic from 'next/dynamic';
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput/index';
import { useI18n } from '@/web/context/I18n';
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
const EmptyTip = dynamic(() => import('@fastgpt/web/components/common/EmptyTip'));
@@ -63,6 +65,7 @@ const FieldEditModal = ({
onSubmit: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
}) => {
const { t } = useTranslation();
const { workflowT } = useI18n();
const { toast } = useToast();
const showDynamicInputSelect =
!keys.includes(NodeInputKeyEnum.addInputParam) ||
@@ -274,7 +277,6 @@ const FieldEditModal = ({
);
const onSubmitError = useCallback(
(e: Object) => {
console.log(e);
for (const item of Object.values(e)) {
if (item.message) {
toast({
@@ -293,7 +295,6 @@ const FieldEditModal = ({
isOpen={true}
iconSrc="/imgs/workflow/extract.png"
title={t('core.module.edit.Field Edit')}
onClose={onClose}
maxW={['90vw', showInputTypeSelect ? '800px' : '400px']}
w={'100%'}
overflow={'unset'}
@@ -364,6 +365,10 @@ const FieldEditModal = ({
{/* input type config */}
{showInputTypeSelect && (
<Stack flex={1} gap={5}>
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}>{workflowT('Field required')}</Box>
<Switch {...register('required')} />
</Flex>
{showToolInput && (
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}></Box>
@@ -426,10 +431,16 @@ const FieldEditModal = ({
{showMaxLenInput && (
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}>{t('core.module.Max Length')}</Box>
<Input
<MyNumberInput
flex={'1 0 0'}
bg={'myGray.50'}
placeholder={t('core.module.Max Length placeholder')}
{...register('maxLength')}
value={maxLength}
onChange={(e) => {
// @ts-ignore
setValue('maxLength', e);
}}
// {...register('maxLength')}
/>
</Flex>
)}
@@ -437,11 +448,27 @@ const FieldEditModal = ({
<>
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}>{t('core.module.Max Value')}</Box>
<Input bg={'myGray.50'} type={'number'} {...register('max')} />
<MyNumberInput
flex={'1 0 0'}
bg={'myGray.50'}
value={watch('max')}
onChange={(e) => {
// @ts-ignore
setValue('max', e);
}}
/>
</Flex>
<Flex alignItems={'center'}>
<Box flex={'0 0 70px'}>{t('core.module.Min Value')}</Box>
<Input bg={'myGray.50'} type={'number'} {...register('min')} />
<MyNumberInput
flex={'1 0 0'}
bg={'myGray.50'}
value={watch('min')}
onChange={(e) => {
// @ts-ignore
setValue('min', e);
}}
/>
</Flex>
</>
)}

View File

@@ -55,7 +55,6 @@ const NodeCard = (props: Props) => {
maxW = '600px',
nodeId,
flowNodeType,
inputs,
selected,
menuForbid,
isTool = false,

View File

@@ -90,17 +90,13 @@ const InputLabel = ({ nodeId, input }: Props) => {
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
...input,
inputType: renderTypeList[0],
valueType: valueType,
key,
label,
description,
isToolInput: !!toolDescription,
defaultValue: input.defaultValue,
maxLength: input.maxLength,
max: input.max,
min: input.min,
dynamicParamDefaultValue: input.dynamicParamDefaultValue
isToolInput: !!toolDescription
})
}
/>

View File

@@ -106,7 +106,6 @@ const VariableTable = ({
keys={keys}
onClose={onCloseFieldEdit}
onSubmit={(e) => {
console.log(e);
if (!!createField && onCreate) {
onCreate(e);
} else if (!!editField && onEdit) {

View File

@@ -322,7 +322,6 @@ const WorkflowContextProvider = ({
item.key === props.key ? props.value : item
);
} else if (type === 'replaceInput') {
onDelEdge({ nodeId, targetHandle: getHandleId(nodeId, 'target', props.key) });
const oldInputIndex = node.data.inputs.findIndex((item) => item.key === props.key);
updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key);
setTimeout(() => {
@@ -351,7 +350,6 @@ const WorkflowContextProvider = ({
}
}
} else if (type === 'delInput') {
onDelEdge({ nodeId, targetHandle: getHandleId(nodeId, 'target', props.key) });
updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key);
} else if (type === 'updateOutput') {
updateObj.outputs = node.data.outputs.map((item) =>

View File

@@ -24,6 +24,15 @@ export const flowNode2StoreNodes = ({
outputs: item.data.outputs,
pluginId: item.data.pluginId
}));
// get all handle
const reactFlowViewport = document.querySelector('.react-flow__viewport');
// Gets the value of data-handleid on all elements below it whose data-handleid is not empty
const handleList =
reactFlowViewport?.querySelectorAll('[data-handleid]:not([data-handleid=""])') || [];
const handleIdList = Array.from(handleList).map(
(item) => item.getAttribute('data-handleid') || ''
);
const formatEdges: StoreEdgeItemType[] = edges
.map((item) => ({
source: item.source,
@@ -31,7 +40,11 @@ export const flowNode2StoreNodes = ({
sourceHandle: item.sourceHandle || '',
targetHandle: item.targetHandle || ''
}))
.filter((item) => item.sourceHandle && item.targetHandle);
.filter((item) => item.sourceHandle && item.targetHandle)
.filter(
// Filter out edges that do not have both sourceHandle and targetHandle
(item) => handleIdList.includes(item.sourceHandle) && handleIdList.includes(item.targetHandle)
);
return {
nodes: formatNodes,