* fix if-else find variables (#92)

* fix if-else find variables

* change workflow output type

* fix tooltip style

* fix

* 4.8 (#93)

* api middleware

* perf: app version histories

* faq

* perf: value type show

* fix: ts

* fix: Run the same node multiple times

* feat: auto save workflow

* perf: auto save workflow

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-27 12:21:01 +08:00
committed by GitHub
parent c8412e7dc9
commit d407e87dd9
87 changed files with 1607 additions and 1779 deletions

View File

@@ -36,6 +36,7 @@ import { postWorkflowDebug } from '@/web/core/workflow/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { delay } from '@fastgpt/global/common/system/utils';
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
@@ -83,7 +84,7 @@ export type useFlowProviderStoreType = {
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => void;
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
splitToolInputs: (
inputs: FlowNodeInputItemType[],
nodeId: string
@@ -147,7 +148,10 @@ const StateContext = createContext<useFlowProviderStoreType>({
hasToolNode: false,
connectingEdge: undefined,
basicNodeTemplates: [],
initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }): void {
initData: function (e: {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}): Promise<void> {
throw new Error('Function not implemented.');
},
hoverNodeId: undefined,
@@ -608,16 +612,14 @@ export const FlowProvider = ({
);
const initData = useCallback(
(e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })));
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })));
setTimeout(() => {
onFixView();
}, 100);
await delay(200);
},
[setEdges, setNodes, onFixView]
[setEdges, setNodes]
);
useEffect(() => {

View File

@@ -10,13 +10,11 @@ import { SmallAddIcon } from '@chakra-ui/icons';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat';
import { useFlowProviderStore } from '../FlowProvider';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import RenderOutput from './render/RenderOutput';
import Reference from './render/RenderInput/templates/Reference';
import IOTitle from '../components/IOTitle';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
@@ -47,46 +45,13 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return maxTokens;
}, [llmModelList, nodeList]);
const RenderQuoteList = useMemo(() => {
return (
<Box mt={-2}>
{quotes.map((quote, i) => (
<Box key={quote.key} _notLast={{ mb: 4 }}>
<Flex alignItems={'center'}>
<Box fontWeight={'medium'} color={'myGray.600'}>
{t('core.chat.Quote')}
{i + 1}
</Box>
<MyIcon
ml={2}
w={'14px'}
name={'delete'}
cursor={'pointer'}
color={'myGray.600'}
_hover={{ color: 'red.600' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'delInput',
key: quote.key
});
}}
/>
</Flex>
<Reference nodeId={nodeId} item={quote} />
</Box>
))}
</Box>
);
}, [nodeId, onChangeNode, quotes, t]);
const onAddField = useCallback(() => {
onChangeNode({
nodeId,
type: 'addInput',
value: getOneQuoteInputTemplate()
value: getOneQuoteInputTemplate({ index: quotes.length + 1 })
});
}, [nodeId, onChangeNode]);
}, [nodeId, onChangeNode, quotes.length]);
const CustomComponent = useMemo(() => {
return {
@@ -141,7 +106,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<NodeCard minW={'400px'} selected={selected} {...data}>
<Container position={'relative'}>
<RenderInput nodeId={nodeId} flowInputList={inputs} CustomComponent={CustomComponent} />
{RenderQuoteList}
{/* {RenderQuoteList} */}
</Container>
<Container>
<IOTitle text={t('common.Output')} />

View File

@@ -123,7 +123,7 @@ const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: 'condition',
key: NodeInputKeyEnum.condition,
value: {
...conditionInput,
value: conditionInput.value === 'OR' ? 'AND' : 'OR'
@@ -297,7 +297,11 @@ const ConditionSelect = ({
valueType === WorkflowIOValueTypeEnum.datasetQuote ||
valueType === WorkflowIOValueTypeEnum.dynamic ||
valueType === WorkflowIOValueTypeEnum.selectApp ||
valueType === WorkflowIOValueTypeEnum.tools
valueType === WorkflowIOValueTypeEnum.arrayBoolean ||
valueType === WorkflowIOValueTypeEnum.arrayNumber ||
valueType === WorkflowIOValueTypeEnum.arrayObject ||
valueType === WorkflowIOValueTypeEnum.arrayString ||
valueType === WorkflowIOValueTypeEnum.object
)
return arrayConditionList;

View File

@@ -5,21 +5,14 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import dynamic from 'next/dynamic';
import { Box, Button, Flex } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import {
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import Container from '../components/Container';
import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import {
FlowNodeInputItemType,
FlowNodeOutputItemType
} from '@fastgpt/global/core/workflow/type/io';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import RenderInput from './render/RenderInput';
import { getNanoid } from '@fastgpt/global/common/string/tools';
const FieldEditModal = dynamic(() => import('./render/FieldEditModal'));
@@ -37,7 +30,7 @@ const createEditField: EditInputFieldMapType = {
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs } = data;
const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore();
const [createField, setCreateField] = useState<EditNodeFieldType>();
@@ -93,13 +86,6 @@ const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
canEdit: true,
editField: createEditField
};
const newOutput: FlowNodeOutputItemType = {
id: getNanoid(),
key: data.key,
valueType: data.valueType,
label: data.label,
type: FlowNodeOutputTypeEnum.static
};
onChangeNode({
nodeId,

View File

@@ -22,7 +22,7 @@ const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
}}
{...data}
>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} textAlign={'end'}>
<Container>
<IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>

View File

@@ -149,13 +149,6 @@ const FieldEditModal = ({
[showDynamicInputSelect, t]
);
const dataTypeSelectList = Object.values(FlowValueTypeMap)
.slice(0, -2)
.map((item) => ({
label: t(item.label),
value: item.value
}));
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
defaultValues: {
...defaultValue,
@@ -224,6 +217,13 @@ const FieldEditModal = ({
return inputType === FlowNodeInputTypeEnum.addInputParam;
}, [inputType]);
const slicedTypeMap = Object.values(FlowValueTypeMap).slice(0, -1);
const dataTypeSelectList = slicedTypeMap.map((item) => ({
label: t(item.label),
value: item.value
}));
const onSubmitSuccess = useCallback(
(data: EditNodeFieldType) => {
data.key = data?.key?.trim();
@@ -270,7 +270,7 @@ const FieldEditModal = ({
changeKey: !keys.includes(data.key)
});
},
[defaultField.key, keys, onSubmit, showValueTypeSelect, t, toast]
[defaultField.key, inputTypeList, keys, onSubmit, showValueTypeSelect, t, toast]
);
const onSubmitError = useCallback(
(e: Object) => {

View File

@@ -28,17 +28,9 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
(connectingEdge?.handleId !== NodeOutputKeyEnum.selectedTools ||
edges.some((edge) => edge.targetHandle === getHandleId(nodeId, 'target', 'top')));
const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools];
const Render = useMemo(() => {
return (
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
<Handle
style={{
borderRadius: '0',
@@ -63,7 +55,7 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
</Handle>
</MyTooltip>
);
}, [handleId, hidden, t, valueTypeMap?.description, valueTypeMap?.label]);
}, [handleId, hidden, t]);
return Render;
};
@@ -72,8 +64,6 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const { setEdges } = useFlowProviderStore();
const valueTypeMap = FlowValueTypeMap[WorkflowIOValueTypeEnum.tools];
/* onConnect edge, delete tool input and switch */
const onConnect = useCallback(
(e: Connection) => {
@@ -90,13 +80,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const Render = useMemo(() => {
return (
<MyTooltip
label={t('app.module.type', {
type: t(valueTypeMap?.label),
description: valueTypeMap?.description
})}
shouldWrapChildren={false}
>
<MyTooltip label={t('core.workflow.tool.Handle')} shouldWrapChildren={false}>
<Handle
style={{
borderRadius: '0',
@@ -120,7 +104,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
</Handle>
</MyTooltip>
);
}, [onConnect, t, valueTypeMap?.description, valueTypeMap?.label]);
}, [onConnect, t]);
return Render;
};

View File

@@ -513,11 +513,14 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
{/* result */}
{debugResult.showResult && (
<Card
className="nowheel"
position={'absolute'}
right={'-430px'}
top={0}
zIndex={10}
w={'420px'}
maxH={'540px'}
overflowY={'auto'}
border={'base'}
>
{/* Status header */}

View File

@@ -1,7 +1,4 @@
import {
FlowNodeInputItemType,
FlowNodeOutputItemType
} from '@fastgpt/global/core/workflow/type/io.d';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../../FlowProvider';
@@ -14,7 +11,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import dynamic from 'next/dynamic';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import ValueTypeLabel from '../ValueTypeLabel';
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
@@ -37,16 +33,10 @@ const InputLabel = ({ nodeId, input }: Props) => {
renderTypeList,
valueType,
canEdit,
key,
value
key
} = input;
const [editField, setEditField] = useState<EditNodeFieldType>();
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : ''),
[t, valueType]
);
const onChangeRenderType = useCallback(
(e: string) => {
const index = renderTypeList.findIndex((item) => item === e) || 0;
@@ -84,35 +74,35 @@ const InputLabel = ({ nodeId, input }: Props) => {
)}
</Box>
{/* value type */}
{renderType === FlowNodeInputTypeEnum.reference && !!valueTypeLabel && (
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel>
)}
{renderType === FlowNodeInputTypeEnum.reference && <ValueTypeLabel valueType={valueType} />}
{/* edit config */}
{canEdit && (
<>
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
ml={3}
color={'myGray.600'}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
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
})
}
/>
{input.editField && Object.keys(input.editField).length > 0 && (
<MyIcon
name={'common/settingLight'}
w={'14px'}
cursor={'pointer'}
ml={3}
color={'myGray.600'}
_hover={{ color: 'primary.500' }}
onClick={() =>
setEditField({
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
})
}
/>
)}
<MyIcon
className="delete"
name={'delete'}
@@ -207,8 +197,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
selectedTypeIndex,
t,
toolDescription,
valueType,
valueTypeLabel
valueType
]);
return RenderLabel;

View File

@@ -20,7 +20,7 @@ import {
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import PromptTemplate from '@/components/PromptTemplate';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Reference from './Reference';
import { getSystemVariables } from '@/web/core/app/utils';
@@ -152,7 +152,7 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
<Box position={'relative'} color={'myGray.600'} fontWeight={'medium'}>
{t('core.module.Dataset quote.label')}
</Box>
<ValueTypeLabel>{t('core.module.valueType.datasetQuote')}</ValueTypeLabel>
<ValueTypeLabel valueType={WorkflowIOValueTypeEnum.datasetQuote} />
<MyTooltip label={t('core.module.Setting quote prompt')}>
<MyIcon

View File

@@ -6,7 +6,6 @@ import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/const
import { SourceHandle } from '../Handle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import ValueTypeLabel from '../ValueTypeLabel';
@@ -14,11 +13,6 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
const { t } = useTranslation();
const { label = '', description, valueType } = output;
const valueTypeLabel = useMemo(
() => (valueType ? t(FlowValueTypeMap[valueType]?.label) : '-'),
[t, valueType]
);
const Render = useMemo(() => {
return (
<Box position={'relative'}>
@@ -34,11 +28,15 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
}
: {})}
>
<Box position={'relative'} mr={1}>
<Box
position={'relative'}
mr={1}
ml={output.type === FlowNodeOutputTypeEnum.source ? 1 : 0}
>
{t(label)}
</Box>
{description && <QuestionTip label={t(description)} />}
<ValueTypeLabel>{valueTypeLabel}</ValueTypeLabel>
<ValueTypeLabel valueType={valueType} />
</Flex>
{output.type === FlowNodeOutputTypeEnum.source && (
<SourceHandle
@@ -50,7 +48,7 @@ const OutputLabel = ({ nodeId, output }: { nodeId: string; output: FlowNodeOutpu
)}
</Box>
);
}, [description, output.key, output.type, label, nodeId, t, valueTypeLabel]);
}, [output.type, output.key, t, label, description, valueType, nodeId]);
return Render;
};

View File

@@ -1,57 +0,0 @@
import React, { useMemo } from 'react';
import { Box, BoxProps } from '@chakra-ui/react';
import { Handle, OnConnect, Position } from 'reactflow';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import MyTooltip from '@/components/MyTooltip';
import { useTranslation } from 'next-i18next';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
interface Props extends BoxProps {
handleKey: string;
valueType?: WorkflowIOValueTypeEnum;
}
const TargetHandle = ({ handleKey, valueType, ...props }: Props) => {
const { t } = useTranslation();
const valType = valueType ?? WorkflowIOValueTypeEnum.any;
const valueStyle = useMemo(
() =>
valueType && FlowValueTypeMap[valueType]
? FlowValueTypeMap[valueType]?.handlerStyle
: FlowValueTypeMap[WorkflowIOValueTypeEnum.any]?.handlerStyle,
[valueType]
);
return (
<Box
position={'absolute'}
top={'50%'}
left={'-18px'}
transform={'translate(0,-50%)'}
{...props}
>
<MyTooltip
label={t('app.module.type', {
type: t(FlowValueTypeMap[valType]?.label),
description: FlowValueTypeMap[valType]?.description
})}
>
<Handle
style={{
width: '14px',
height: '14px',
borderWidth: '3.5px',
backgroundColor: 'white',
...valueStyle
}}
type="target"
id={handleKey}
position={Position.Left}
/>
</MyTooltip>
</Box>
);
};
export default React.memo(TargetHandle);

View File

@@ -1,21 +1,33 @@
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box } from '@chakra-ui/react';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import React from 'react';
const ValueTypeLabel = ({ children }: { children: React.ReactNode }) => {
return (
<Box
bg={'myGray.100'}
color={'myGray.500'}
border={'base'}
borderRadius={'sm'}
ml={2}
px={1}
py={0.5}
fontSize={'11px'}
>
{children}
</Box>
);
const ValueTypeLabel = ({ valueType }: { valueType?: WorkflowIOValueTypeEnum }) => {
const valueTypeData = valueType ? FlowValueTypeMap[valueType] : undefined;
const label = valueTypeData?.label || '';
const description = valueTypeData?.description || '';
return !!label ? (
<MyTooltip label={description}>
<Box
bg={'myGray.100'}
color={'myGray.500'}
border={'base'}
borderRadius={'sm'}
ml={2}
px={1}
h={6}
display={'flex'}
alignItems={'center'}
fontSize={'11px'}
>
{label}
</Box>
</MyTooltip>
) : null;
};
export default React.memo(ValueTypeLabel);

View File

@@ -1,17 +1,6 @@
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import {
Box,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Button,
Flex
} from '@chakra-ui/react';
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import type {
EditInputFieldMapType,