mirror of
https://github.com/labring/FastGPT.git
synced 2025-08-02 12:48:30 +00:00
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:
@@ -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 }]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@@ -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'}>
|
||||
|
@@ -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')),
|
||||
|
@@ -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),
|
||||
|
@@ -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>
|
||||
</>
|
||||
)}
|
||||
|
@@ -55,7 +55,6 @@ const NodeCard = (props: Props) => {
|
||||
maxW = '600px',
|
||||
nodeId,
|
||||
flowNodeType,
|
||||
inputs,
|
||||
selected,
|
||||
menuForbid,
|
||||
isTool = false,
|
||||
|
@@ -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
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
@@ -106,7 +106,6 @@ const VariableTable = ({
|
||||
keys={keys}
|
||||
onClose={onCloseFieldEdit}
|
||||
onSubmit={(e) => {
|
||||
console.log(e);
|
||||
if (!!createField && onCreate) {
|
||||
onCreate(e);
|
||||
} else if (!!editField && onEdit) {
|
||||
|
@@ -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) =>
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user