4.8.13 test (#3098)

* perf: loop node refresh

* rename context

* comment

* fix: ts

* perf: push chat log
This commit is contained in:
Archer
2024-11-08 16:02:33 +08:00
committed by archer
parent 0a238845ab
commit c5022654ca
36 changed files with 303 additions and 221 deletions

View File

@@ -335,7 +335,7 @@ export enum ContentTypes {
raw = 'raw-text'
}
export const ArrayTypeMap = {
export const ArrayTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum> = {
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
[WorkflowIOValueTypeEnum.boolean]: WorkflowIOValueTypeEnum.arrayBoolean,
@@ -343,5 +343,12 @@ export const ArrayTypeMap = {
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.arrayString,
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.arrayNumber,
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.arrayBoolean,
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.arrayObject
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.chatHistory]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.datasetQuote]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.dynamic]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.selectDataset]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.selectApp]: WorkflowIOValueTypeEnum.arrayObject,
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.arrayAny,
[WorkflowIOValueTypeEnum.any]: WorkflowIOValueTypeEnum.arrayAny
};

View File

@@ -5,7 +5,7 @@ import { StoreNodeItemType } from '../type/node';
import { StoreEdgeItemType } from '../type/edge';
import { RuntimeEdgeItemType, RuntimeNodeItemType } from './type';
import { VARIABLE_NODE_ID } from '../constants';
import { isReferenceValueFormat } from '../utils';
import { isValidReferenceValueFormat } from '../utils';
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
@@ -235,20 +235,19 @@ export const getReferenceVariableValue = ({
nodes,
variables
}: {
value: ReferenceValueType;
value?: ReferenceValueType;
nodes: RuntimeNodeItemType[];
variables: Record<string, any>;
}) => {
if (!value) return undefined;
const nodeIds = nodes.map((node) => node.nodeId);
// handle single reference value
if (isReferenceValueFormat(value)) {
if (isValidReferenceValueFormat(value)) {
const sourceNodeId = value[0];
const outputId = value[1];
if (sourceNodeId === VARIABLE_NODE_ID && outputId) {
if (sourceNodeId === VARIABLE_NODE_ID) {
if (!outputId) return undefined;
return variables[outputId];
}
@@ -264,7 +263,7 @@ export const getReferenceVariableValue = ({
if (
Array.isArray(value) &&
value.length > 0 &&
value.every((item) => isReferenceValueFormat(item))
value.every((item) => isValidReferenceValueFormat(item))
) {
const result = value.map<any>((val) => {
return getReferenceVariableValue({

View File

@@ -4,7 +4,7 @@ import { WorkflowIOValueTypeEnum } from '../../../constants';
export type TUpdateListItem = {
variable?: ReferenceItemValueType;
value: ReferenceValueType; // input: ['',value], reference: [nodeId,outputId]
value?: ReferenceValueType; // input: ['',value], reference: [nodeId,outputId]
valueType?: WorkflowIOValueTypeEnum;
renderType: FlowNodeInputTypeEnum.input | FlowNodeInputTypeEnum.reference;
};

View File

@@ -80,6 +80,6 @@ export type FlowNodeOutputItemType = {
customFieldConfig?: CustomFieldConfigType;
};
export type ReferenceItemValueType = undefined | [string, string];
export type ReferenceArrayValueType = undefined | [string, string][];
export type ReferenceItemValueType = [string, string | undefined];
export type ReferenceArrayValueType = ReferenceItemValueType[];
export type ReferenceValueType = ReferenceItemValueType | ReferenceArrayValueType;

View File

@@ -12,7 +12,12 @@ import {
VARIABLE_NODE_ID,
NodeOutputKeyEnum
} from './constants';
import { FlowNodeInputItemType, FlowNodeOutputItemType } from './type/io.d';
import {
FlowNodeInputItemType,
FlowNodeOutputItemType,
ReferenceArrayValueType,
ReferenceItemValueType
} from './type/io.d';
import { StoreNodeItemType } from './type/node';
import type {
VariableItemType,
@@ -300,28 +305,37 @@ export const formatEditorVariablePickerIcon = (
}));
};
export const isReferenceValue = (value: any, nodeIds: string[]): value is [string, string] => {
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== 'string') return false;
// Check the value is a valid reference value format: [variableId, outputId]
export const isValidReferenceValueFormat = (value: any): value is ReferenceItemValueType => {
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
};
/*
Check whether the value([variableId, outputId]) value is a valid reference value:
1. The value must be an array of length 2
2. The first item of the array must be one of VARIABLE_NODE_ID or nodeIds
*/
export const isValidReferenceValue = (
value: any,
nodeIds: string[]
): value is ReferenceItemValueType => {
if (!isValidReferenceValueFormat(value)) return false;
const validIdSet = new Set([VARIABLE_NODE_ID, ...nodeIds]);
return validIdSet.has(value[0]);
};
export const isReferenceValueFormat = (value: any): value is [string, string] => {
return (
Array.isArray(value) &&
value.length === 2 &&
typeof value[0] === 'string' &&
typeof value[1] === 'string'
);
};
export const isReferenceValueArray = (
/*
Check whether the value([variableId, outputId][]) value is a valid reference value array:
1. The value must be an array
2. The array must contain at least one element
3. Each element in the array must be a valid reference value
*/
export const isValidArrayReferenceValue = (
value: any,
nodeIds: string[]
): value is [string, string][] => {
if (!Array.isArray(value) || value.length === 0) return false;
): value is ReferenceArrayValueType => {
if (!Array.isArray(value)) return false;
return value.every((item) => isReferenceValue(item, nodeIds));
return value.every((item) => isValidReferenceValue(item, nodeIds));
};
export const getElseIFLabel = (i: number) => {

View File

@@ -2,7 +2,7 @@ import { addLog } from '../../common/system/log';
import { MongoChatItem } from './chatItemSchema';
import { MongoChat } from './chatSchema';
import axios from 'axios';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { AIChatItemType, ChatItemType } from '@fastgpt/global/core/chat/type';
export type Metadata = {
[key: string]: {
@@ -81,17 +81,15 @@ const pushChatLogInternal = async ({
metadata?: Metadata;
}) => {
const [chatItemHuman, chatItemAi] = await Promise.all([
MongoChatItem.findById(chatItemIdHuman).lean() as Promise<ChatItem>,
MongoChatItem.findById(chatItemIdAi).lean() as Promise<ChatItem>
MongoChatItem.findById(chatItemIdHuman).lean(),
MongoChatItem.findById(chatItemIdAi).lean() as Promise<AIChatItemType>
]);
const [chat] = (await MongoChat.find({ chatId }).lean()) as {
title: string;
outLinkUid: string | undefined;
tmbId: string;
teamId: string;
metadata: Object;
source: string;
}[];
if (!chatItemHuman || !chatItemAi) {
return;
}
const chat = await MongoChat.findOne({ chatId }).lean();
// addLog.warn('ChatLogDebug', chat);
// addLog.warn('ChatLogDebug', { chatItemHuman, chatItemAi });
@@ -114,10 +112,7 @@ const pushChatLogInternal = async ({
return;
}
const responseData = chatItemAi.responseData;
let responseTime = 0;
responseData.forEach((item) => {
responseTime += item.runningTime;
});
const responseTime = responseData?.reduce((acc, item) => acc + (item?.runningTime ?? 0), 0) || 0;
const chatLog: ChatLog = {
title: chat.title,
@@ -138,6 +133,7 @@ const pushChatLogInternal = async ({
responseTime: responseTime * 1000,
metadata: metadataString,
sourceName: chat.source ?? '-',
// @ts-ignore
createdAt: new Date(chatItemAi.time).getTime(),
sourceId: `crbeer-fastgpt-${appId}`
};

View File

@@ -25,7 +25,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
user,
node: { name }
} = props;
const { loopInputArray = [], childrenNodeIdList } = params;
const { loopInputArray = [], childrenNodeIdList = [] } = params;
if (!Array.isArray(loopInputArray)) {
return Promise.reject('Input value is not an array');

View File

@@ -11,7 +11,7 @@ import {
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
import { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { removeSystemVariable, valueTypeFormat } from '../utils';
import { isReferenceValue } from '@fastgpt/global/core/workflow/utils';
import { isValidReferenceValue } from '@fastgpt/global/core/workflow/utils';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.updateList]: TUpdateListItem[];
@@ -27,13 +27,17 @@ export const dispatchUpdateVariable = async (props: Props): Promise<Response> =>
const result = updateList.map((item) => {
const variable = item.variable;
if (!isReferenceValue(variable, nodeIds)) {
if (!isValidReferenceValue(variable, nodeIds)) {
return null;
}
const varNodeId = variable[0];
const varKey = variable[1];
if (!varKey) {
return null;
}
const value = (() => {
// If first item is empty, it means it is a input value
if (!item.value?.[0]) {

View File

@@ -50,5 +50,5 @@ WORKFLOW_MAX_LOOP_TIMES=50
# 对话日志推送服务
# URL/INTERVAL 为空时不推送
CHAT_LOG_URL=http://localhost:8080
CHAT_LOG_INTERVAL=10000
# CHAT_LOG_URL=http://localhost:8080
# CHAT_LOG_INTERVAL=10000

View File

@@ -30,7 +30,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from '../Workflow/components/SaveButton';
import PublishHistories from '../PublishHistoriesSlider';
import {
WorkflowActionContext,
WorkflowNodeEdgeContext,
WorkflowInitContext
} from '../WorkflowComponents/context/workflowInitContext';
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
@@ -50,7 +50,7 @@ const Header = () => {
} = useDisclosure();
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const {
flowData2StoreData,
flowData2StoreDataAndCheck,

View File

@@ -30,7 +30,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import SaveButton from './components/SaveButton';
import PublishHistories from '../PublishHistoriesSlider';
import {
WorkflowActionContext,
WorkflowNodeEdgeContext,
WorkflowInitContext
} from '../WorkflowComponents/context/workflowInitContext';
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
@@ -54,7 +54,7 @@ const Header = () => {
} = useDisclosure();
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const {
flowData2StoreData,
flowData2StoreDataAndCheck,

View File

@@ -50,7 +50,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart';
import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { WorkflowActionContext } from '../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../context/workflowInitContext';
type ModuleTemplateListProps = {
isOpen: boolean;
@@ -389,7 +389,7 @@ const RenderList = React.memo(function RenderList({
const { computedNewNodeName } = useWorkflowUtils();
const { toast } = useToast();
const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const formatTemplates = useMemo<NodeTemplateListType>(() => {

View File

@@ -6,12 +6,12 @@ import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/w
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useThrottleEffect } from 'ahooks';
import { WorkflowActionContext, WorkflowInitContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../../context/workflowInitContext';
import { WorkflowEventContext } from '../../context/workflowEventContext';
const ButtonEdge = (props: EdgeProps) => {
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
const onEdgesChange = useContextSelector(WorkflowActionContext, (v) => v.onEdgesChange);
const onEdgesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onEdgesChange);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData);
const hoverEdgeId = useContextSelector(WorkflowEventContext, (v) => v.hoverEdgeId);

View File

@@ -7,12 +7,12 @@ import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comme
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useReactFlow } from 'reactflow';
import { WorkflowActionContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
import { WorkflowEventContext } from '../../context/workflowEventContext';
const ContextMenu = () => {
const { t } = useTranslation();
const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
const menu = useContextSelector(WorkflowEventContext, (v) => v.menu);
const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu);

View File

@@ -51,13 +51,12 @@ const FlowController = React.memo(function FlowController() {
e.stopPropagation();
if (!mouseInCanvas) return;
const isUndo = e.key.toLowerCase() === 'z' && !e.shiftKey;
const isRedo = (e.key.toLowerCase() === 'z' && e.shiftKey) || e.key.toLowerCase() === 'y';
if (isUndo) {
undo();
} else if (isRedo) {
if (isRedo) {
redo();
} else {
undo();
}
});

View File

@@ -34,7 +34,7 @@ import { AppContext } from '../../../context';
import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyTextarea from '@/components/common/Textarea/MyTextarea';
import { WorkflowActionContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
@@ -50,9 +50,9 @@ export const useDebug = () => {
const { t } = useTranslation();
const { toast } = useToast();
const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
const getNodes = useContextSelector(WorkflowActionContext, (v) => v.getNodes);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
const getNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.getNodes);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug);

View File

@@ -8,13 +8,13 @@ import { useContextSelector } from 'use-context-selector';
import { useWorkflowUtils } from './useUtils';
import { useKeyPress as useKeyPressEffect } from 'ahooks';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { WorkflowActionContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
import { WorkflowEventContext } from '../../context/workflowEventContext';
export const useKeyboard = () => {
const { t } = useTranslation();
const getNodes = useContextSelector(WorkflowActionContext, (v) => v.getNodes);
const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
const getNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.getNodes);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
const mouseInCanvas = useContextSelector(WorkflowEventContext, (v) => v.mouseInCanvas);
const { copyData } = useCopyData();

View File

@@ -31,7 +31,7 @@ import {
Input_Template_Node_Width
} from '@fastgpt/global/core/workflow/template/input';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { WorkflowActionContext, WorkflowInitContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../../context/workflowInitContext';
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import { AppContext } from '../../../context';
import { WorkflowEventContext } from '../../context/workflowEventContext';
@@ -278,10 +278,10 @@ export const useWorkflow = () => {
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
const nodes = useContextSelector(WorkflowInitContext, (state) => state.nodes);
const onNodesChange = useContextSelector(WorkflowActionContext, (state) => state.onNodesChange);
const edges = useContextSelector(WorkflowActionContext, (state) => state.edges);
const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
const onEdgesChange = useContextSelector(WorkflowActionContext, (v) => v.onEdgesChange);
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
const edges = useContextSelector(WorkflowNodeEdgeContext, (state) => state.edges);
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
const onEdgesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onEdgesChange);
const { setConnectingEdge, nodeList, onChangeNode, pushPastSnapshot } = useContextSelector(
WorkflowContext,
(v) => v
@@ -294,20 +294,9 @@ export const useWorkflow = () => {
// Loop node size and position
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
const { childNodes, loopNode } = nodes.reduce(
(acc, node) => {
if (node.data.parentNodeId === parentId) {
acc.childNodes.push(node);
}
if (node.id === parentId) {
acc.loopNode = node;
}
return acc;
},
{ childNodes: [] as Node[], loopNode: undefined as Node | undefined }
);
const rect = getNodesBounds(childNodes);
const childNodes = nodes.filter((node) => node.data.parentNodeId === parentId);
const rect = getNodesBounds(childNodes);
// Calculate parent node size with minimum width/height constraints
const width = Math.max(rect.width + 80, 840);
const height = Math.max(rect.height + 80, 600);
@@ -332,9 +321,6 @@ export const useWorkflow = () => {
}
});
// Calculate position offset
const offsetHeight = loopNode?.height ? loopNode.height - height - 380 : 0;
// Update parentNode position
onNodesChange([
{
@@ -342,7 +328,7 @@ export const useWorkflow = () => {
type: 'position',
position: {
x: rect.x - 70,
y: rect.y - (320 + offsetHeight)
y: rect.y - 320
}
}
]);
@@ -392,15 +378,6 @@ export const useWorkflow = () => {
// Check if a node is placed on top of a loop node
const checkNodeOverLoopNode = useMemoizedFn((node: Node) => {
if (!node) return;
// 获取所有与当前节点相交的节点
const intersections = getIntersectingNodes(node);
// 获取所有与当前节点相交的节点中,类型为 loop 的节点且它不能是折叠状态
const parentNode = intersections.find(
(item) => !item.data.isFolded && item.type === FlowNodeTypeEnum.loop
);
const unSupportedTypes = [
FlowNodeTypeEnum.workflowStart,
FlowNodeTypeEnum.loop,
@@ -409,7 +386,16 @@ export const useWorkflow = () => {
FlowNodeTypeEnum.systemConfig
];
if (parentNode && !node.data.parentNodeId) {
if (!node || node.data.parentNodeId) return;
// 获取所有与当前节点相交的节点
const intersections = getIntersectingNodes(node);
// 获取所有与当前节点相交的节点中,类型为 loop 的节点且它不能是折叠状态
const parentNode = intersections.find(
(item) => !item.data.isFolded && item.type === FlowNodeTypeEnum.loop
);
if (parentNode) {
if (unSupportedTypes.includes(node.type as FlowNodeTypeEnum)) {
return toast({
status: 'warning',
@@ -427,8 +413,6 @@ export const useWorkflow = () => {
setEdges((state) =>
state.filter((edge) => edge.source !== node.id && edge.target !== node.id)
);
resetParentNodeSizeAndPosition(parentNode.id);
}
});

View File

@@ -18,7 +18,7 @@ import { useWorkflow } from './hooks/useWorkflow';
import HelperLines from './components/HelperLines';
import FlowController from './components/FlowController';
import ContextMenu from './components/ContextMenu';
import { WorkflowActionContext, WorkflowInitContext } from '../context/workflowInitContext';
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../context/workflowInitContext';
import { WorkflowEventContext } from '../context/workflowEventContext';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
@@ -66,7 +66,7 @@ const edgeTypes = {
const Workflow = () => {
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const reactFlowWrapper = useContextSelector(WorkflowEventContext, (v) => v.reactFlowWrapper);
const workflowControlMode = useContextSelector(
WorkflowEventContext,

View File

@@ -5,8 +5,8 @@
*/
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import React, { useEffect, useMemo, useRef, useCallback } from 'react';
import { Background, NodeProps } from 'reactflow';
import React, { useEffect, useMemo, useRef } from 'react';
import { Background, NodePositionChange, NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import Container from '../../components/Container';
import IOTitle from '../../components/IOTitle';
@@ -26,18 +26,27 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
import { AppContext } from '../../../../context';
import { isReferenceValue, isReferenceValueArray } from '@fastgpt/global/core/workflow/utils';
import { ReferenceItemValueType } from '@fastgpt/global/core/workflow/type/io';
import { isValidArrayReferenceValue } from '@fastgpt/global/core/workflow/utils';
import { ReferenceArrayValueType } from '@fastgpt/global/core/workflow/type/io';
import { useWorkflow } from '../../hooks/useWorkflow';
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
import { useSize } from 'ahooks';
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const { nodeId, inputs, outputs, isFolded } = data;
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
const { appDetail } = useContextSelector(AppContext, (v) => v);
const getNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.getNodes);
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onNodesChange);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const { resetParentNodeSizeAndPosition } = useWorkflow();
const loopInputArray = inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray);
const loopInputArray = useMemo(
() => inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray),
[inputs]
);
const { nodeWidth, nodeHeight } = useMemo(() => {
return {
@@ -46,23 +55,28 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
};
}, [inputs]);
const childrenNodeIdList = useMemo(() => {
return JSON.stringify(
nodeList.filter((node) => node.parentNodeId === nodeId).map((node) => node.nodeId)
);
}, [nodeId, nodeList]);
// Detect and update array input type
// Update array input type
// Computed the reference value type
const newValueType = useMemo(() => {
if (!loopInputArray) return WorkflowIOValueTypeEnum.arrayAny;
const value = loopInputArray.value as ReferenceArrayValueType;
if (
!value ||
value.length === 0 ||
!isValidArrayReferenceValue(
value,
nodeList.map((node) => node.nodeId)
)
)
return WorkflowIOValueTypeEnum.arrayAny;
const nodeIds = nodeList.map((node) => node.nodeId);
const globalVariables = getWorkflowGlobalVariables({
nodes: nodeList,
chatConfig: appDetail.chatConfig
});
const getValueType = (value: ReferenceItemValueType) => {
const valueType = ((value) => {
if (value?.[0] === VARIABLE_NODE_ID) {
return globalVariables.find((item) => item.key === value[1])?.valueType;
} else {
@@ -70,21 +84,8 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const output = node?.outputs.find((output) => output.id === value?.[1]);
return output?.valueType;
}
};
const valueType = (() => {
if (isReferenceValue(loopInputArray?.value, nodeIds)) {
return getValueType(loopInputArray?.value);
} else if (isReferenceValueArray(loopInputArray?.value, nodeIds)) {
return getValueType(loopInputArray?.value?.[0]);
} else {
return WorkflowIOValueTypeEnum.arrayAny;
}
})();
const type = ArrayTypeMap[valueType as keyof typeof ArrayTypeMap];
return type ?? WorkflowIOValueTypeEnum.arrayAny;
})(value[0]);
return ArrayTypeMap[valueType as keyof typeof ArrayTypeMap] ?? WorkflowIOValueTypeEnum.arrayAny;
}, [appDetail.chatConfig, loopInputArray, nodeList]);
useEffect(() => {
@@ -101,6 +102,11 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
}, [newValueType]);
// Update childrenNodeIdList
const childrenNodeIdList = useMemo(() => {
return JSON.stringify(
nodeList.filter((node) => node.parentNodeId === nodeId).map((node) => node.nodeId)
);
}, [nodeId, nodeList]);
useEffect(() => {
onChangeNode({
nodeId,
@@ -111,20 +117,47 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
value: JSON.parse(childrenNodeIdList)
}
});
}, [childrenNodeIdList, nodeId, onChangeNode]);
resetParentNodeSizeAndPosition(nodeId);
}, [childrenNodeIdList]);
// Update child node position
const inputBoxRef = useRef<HTMLDivElement>(null);
const size = useSize(inputBoxRef);
const prevHeightRef = useRef<number>(); // 添加 ref 来存储前一个高度值
useEffect(() => {
setTimeout(() => {
resetParentNodeSizeAndPosition(nodeId);
}, 0);
}, [loopInputArray, nodeId, resetParentNodeSizeAndPosition]);
if (!size?.height) return;
if (prevHeightRef.current === size.height) return;
const diffHeight = prevHeightRef.current ? size.height - prevHeightRef.current : 0;
prevHeightRef.current = size.height;
if (diffHeight === 0) return;
// Get the height of the input box
// Computed input
const nodes = getNodes();
const childNodes = nodes.filter((n) => n.data.parentNodeId === nodeId);
const childNodesChange: NodePositionChange[] = childNodes.map((node) => {
return {
type: 'position',
id: node.id,
position: {
x: node.position.x,
y: node.position.y + diffHeight
}
};
});
console.log(childNodesChange);
onNodesChange(childNodesChange);
}, [size?.height]);
const Render = useMemo(() => {
return (
<NodeCard selected={selected} maxW="full" menuForbid={{ copy: true }} {...data}>
<Container position={'relative'} flex={1}>
<IOTitle text={t('common:common.Input')} />
<Box mb={6} maxW={'500px'}>
<Box mb={6} maxW={'500px'} ref={inputBoxRef}>
<RenderInput nodeId={nodeId} flowInputList={inputs} />
</Box>
<FormLabel required fontWeight={'medium'} mb={3} color={'myGray.600'}>

View File

@@ -147,7 +147,7 @@ const VariableSelector = ({
});
const onSelect = useCallback(
(e: ReferenceItemValueType) => {
(e?: ReferenceItemValueType) => {
if (!e) return;
onChangeNode({

View File

@@ -49,7 +49,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getEditorVariables } from '../../../utils';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { WorkflowActionContext } from '../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
const defaultFormBody = {
@@ -82,7 +82,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
const { t } = useTranslation();
const { toast } = useToast();
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const { appDetail } = useContextSelector(AppContext, (v) => v);
@@ -259,7 +259,7 @@ export function RenderHttpProps({
const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const { appDetail } = useContextSelector(AppContext, (v) => v);

View File

@@ -309,7 +309,7 @@ const VariableSelector = ({
}: {
nodeId: string;
variable?: ReferenceItemValueType;
onSelect: (e: ReferenceItemValueType) => void;
onSelect: (e?: ReferenceItemValueType) => void;
}) => {
const { t } = useTranslation();

View File

@@ -110,7 +110,8 @@ function Reference({
const [editField, setEditField] = useState<FlowNodeInputItemType>();
const onSelect = useCallback(
(e: ReferenceValueType) => {
(e?: ReferenceValueType) => {
if (!e) return;
onChangeNode({
nodeId,
type: 'updateInput',

View File

@@ -34,7 +34,7 @@ import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useCreation, useMemoizedFn } from 'ahooks';
import { getEditorVariables } from '../../utils';
import { isArray } from 'lodash';
import { WorkflowActionContext } from '../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext';
const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { inputs = [], nodeId } = data;
@@ -43,7 +43,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const menuList = useRef([
{
@@ -104,14 +104,14 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
(item) => item.renderType === updateItem.renderType
);
const handleUpdate = (newValue: ReferenceValueType | string) => {
const handleUpdate = (newValue?: ReferenceValueType | string) => {
if (typeof newValue === 'string') {
onUpdateList(
updateList.map((update, i) =>
i === index ? { ...update, value: ['', newValue] } : update
)
);
} else {
} else if (newValue) {
onUpdateList(
updateList.map((update, i) =>
i === index ? { ...update, value: newValue as ReferenceItemValueType } : update
@@ -181,7 +181,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
if (i === index) {
return {
...update,
value: ['', ''],
value: undefined,
renderType:
updateItem.renderType === FlowNodeInputTypeEnum.input
? FlowNodeInputTypeEnum.reference
@@ -318,7 +318,7 @@ const VariableSelector = ({
nodeId: string;
variable?: ReferenceValueType;
valueType?: WorkflowIOValueTypeEnum;
onSelect: (e: ReferenceValueType) => void;
onSelect: (e?: ReferenceValueType) => void;
}) => {
const { t } = useTranslation();

View File

@@ -5,7 +5,7 @@ import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../../context';
import { WorkflowActionContext } from '../../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../../context/workflowInitContext';
export const ConnectionSourceHandle = ({
nodeId,
@@ -14,7 +14,7 @@ export const ConnectionSourceHandle = ({
nodeId: string;
isFoldNode?: boolean;
}) => {
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const { connectingEdge, nodeList } = useContextSelector(WorkflowContext, (ctx) => ctx);
const { showSourceHandle, RightHandle, LeftHandlee, TopHandlee, BottomHandlee } = useMemo(() => {
@@ -137,7 +137,7 @@ export const ConnectionTargetHandle = React.memo(function ConnectionTargetHandle
}: {
nodeId: string;
}) {
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const { connectingEdge, nodeList } = useContextSelector(WorkflowContext, (ctx) => ctx);
const { LeftHandle, rightHandle, topHandle, bottomHandle } = useMemo(() => {

View File

@@ -6,7 +6,7 @@ import { Connection, Handle, Position } from 'reactflow';
import { useCallback, useMemo } from 'react';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
import { WorkflowActionContext } from '../../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../../context/workflowInitContext';
const handleSize = '16px';
@@ -17,7 +17,7 @@ type ToolHandleProps = BoxProps & {
export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const handleId = NodeOutputKeyEnum.selectedTools;
@@ -65,7 +65,7 @@ export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
export const ToolSourceHandle = () => {
const { t } = useTranslation();
const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
/* onConnect edge, delete tool input and switch */
const onConnect = useCallback(

View File

@@ -6,7 +6,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../../context';
import MyIcon from '@fastgpt/web/components/common/Icon';
import {
WorkflowActionContext,
WorkflowNodeEdgeContext,
WorkflowInitContext
} from '../../../../context/workflowInitContext';
import { WorkflowEventContext } from '../../../../context/workflowEventContext';
@@ -29,7 +29,7 @@ const MySourceHandle = React.memo(function MySourceHandle({
highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>;
}) {
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const nodes = useContextSelector(WorkflowInitContext, (v) => v.nodes);
const hoverNodeId = useContextSelector(WorkflowEventContext, (v) => v.hoverNodeId);
@@ -154,7 +154,7 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>;
}) {
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const connected = edges.some((edge) => edge.targetHandle === handleId);

View File

@@ -26,7 +26,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
import { getDocPath } from '@/web/common/system/doc';
import { WorkflowActionContext } from '../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
import { WorkflowEventContext } from '../../../context/workflowEventContext';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
@@ -394,8 +394,8 @@ const MenuRender = React.memo(function MenuRender({
const { t } = useTranslation();
const { openDebugNode, DebugInputModal } = useDebug();
const setNodes = useContextSelector(WorkflowActionContext, (v) => v.setNodes);
const setEdges = useContextSelector(WorkflowActionContext, (v) => v.setEdges);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes);
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
const { computedNewNodeName } = useWorkflowUtils();
const onCopyNode = useCallback(

View File

@@ -123,7 +123,8 @@ function Reference({
const [editField, setEditField] = useState<FlowNodeInputItemType>();
const onSelect = useCallback(
(e: ReferenceValueType) => {
(e?: ReferenceValueType) => {
if (!e) return;
onChangeNode({
nodeId,
type: 'replaceInput',

View File

@@ -21,7 +21,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { AppContext } from '@/pages/app/detail/components/context';
import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext';
const MultipleRowSelect = dynamic(() =>
import('@fastgpt/web/components/common/MySelect/MultipleRowSelect').then(
@@ -64,7 +64,7 @@ export const useReference = ({
}) => {
const { t } = useTranslation();
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// 获取可选的变量列表
@@ -119,7 +119,7 @@ const Reference = ({ item, nodeId }: RenderInputProps) => {
const isArray = item.valueType?.includes('array') ?? false;
const onSelect = useCallback(
(e: ReferenceValueType) => {
(e?: ReferenceValueType) => {
onChangeNode({
nodeId,
type: 'updateInput',

View File

@@ -7,12 +7,12 @@ import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponent
import { useCreation } from 'ahooks';
import { AppContext } from '@/pages/app/detail/components/context';
import { getEditorVariables } from '../../../../../utils';
import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext';
const TextInputRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);

View File

@@ -7,11 +7,11 @@ import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponent
import { useCreation } from 'ahooks';
import { AppContext } from '@/pages/app/detail/components/context';
import { getEditorVariables } from '../../../../../utils';
import { WorkflowActionContext } from '../../../../../context/workflowInitContext';
import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext';
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation();
const edges = useContextSelector(WorkflowActionContext, (v) => v.edges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.edges);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);

View File

@@ -39,9 +39,16 @@ import { useTranslation } from 'next-i18next';
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
import { cloneDeep } from 'lodash';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';
import WorkflowInitContextProvider, { WorkflowActionContext } from './workflowInitContext';
import WorkflowInitContextProvider, { WorkflowNodeEdgeContext } from './workflowInitContext';
import WorkflowEventContextProvider from './workflowEventContext';
/*
Context
1. WorkflowInitContext: 带 nodes
2. WorkflowNodeEdgeContext: 除了 nodes 外的nodes 操作。以及 edges 和其操作
3. WorkflowContextProvider: 旧的 context未拆分
4. WorkflowEventContextProvider一些边缘的 event
*/
export const ReactFlowCustomProvider = ({
templates,
children
@@ -322,8 +329,8 @@ const WorkflowContextProvider = ({
const appId = appDetail._id;
/* edge */
const edges = useContextSelector(WorkflowActionContext, (state) => state.edges);
const setEdges = useContextSelector(WorkflowActionContext, (state) => state.setEdges);
const edges = useContextSelector(WorkflowNodeEdgeContext, (state) => state.edges);
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (state) => state.setEdges);
const onDelEdge = useCallback(
({
nodeId,
@@ -351,9 +358,12 @@ const WorkflowContextProvider = ({
const [connectingEdge, setConnectingEdge] = useState<OnConnectStartParams>();
/* node */
const setNodes = useContextSelector(WorkflowActionContext, (state) => state.setNodes);
const getNodes = useContextSelector(WorkflowActionContext, (state) => state.getNodes);
const nodeListString = useContextSelector(WorkflowActionContext, (state) => state.nodeListString);
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (state) => state.setNodes);
const getNodes = useContextSelector(WorkflowNodeEdgeContext, (state) => state.getNodes);
const nodeListString = useContextSelector(
WorkflowNodeEdgeContext,
(state) => state.nodeListString
);
const nodeList = useMemo(
() => JSON.parse(nodeListString) as FlowNodeItemType[],

View File

@@ -56,7 +56,7 @@ type WorkflowActionContextType = {
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
onEdgesChange: OnChange<EdgeChange>;
};
export const WorkflowActionContext = createContext<WorkflowActionContextType>({
export const WorkflowNodeEdgeContext = createContext<WorkflowActionContextType>({
setNodes: function (
value: React.SetStateAction<Node<FlowNodeItemType, string | undefined>[]>
): void {
@@ -128,9 +128,9 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
nodes
}}
>
<WorkflowActionContext.Provider value={actionContextValue}>
<WorkflowNodeEdgeContext.Provider value={actionContextValue}>
{children}
</WorkflowActionContext.Provider>
</WorkflowNodeEdgeContext.Provider>
</WorkflowInitContext.Provider>
);
};

View File

@@ -16,15 +16,15 @@ import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNo
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { getGlobalVariableNode } from './adapt';
import { VARIABLE_NODE_ID, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import {
formatEditorVariablePickerIcon,
getAppChatConfig,
getGuideModule,
isReferenceValue,
isReferenceValueFormat
isValidArrayReferenceValue,
isValidReferenceValue
} from '@fastgpt/global/core/workflow/utils';
import { TFunction } from 'next-i18next';
import {
@@ -263,17 +263,72 @@ export const getRefData = ({
};
};
// 根据数据类型,过滤无效的节点输出
export const filterWorkflowNodeOutputsByType = (
outputs: FlowNodeOutputItemType[],
valueType: WorkflowIOValueTypeEnum
): FlowNodeOutputItemType[] => {
const validTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum[]> = {
[WorkflowIOValueTypeEnum.string]: [WorkflowIOValueTypeEnum.string],
[WorkflowIOValueTypeEnum.number]: [WorkflowIOValueTypeEnum.number],
[WorkflowIOValueTypeEnum.boolean]: [WorkflowIOValueTypeEnum.boolean],
[WorkflowIOValueTypeEnum.object]: [WorkflowIOValueTypeEnum.object],
[WorkflowIOValueTypeEnum.arrayString]: [
WorkflowIOValueTypeEnum.string,
WorkflowIOValueTypeEnum.arrayString,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.arrayNumber]: [
WorkflowIOValueTypeEnum.number,
WorkflowIOValueTypeEnum.arrayNumber,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.arrayBoolean]: [
WorkflowIOValueTypeEnum.boolean,
WorkflowIOValueTypeEnum.arrayBoolean,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.arrayObject]: [
WorkflowIOValueTypeEnum.object,
WorkflowIOValueTypeEnum.arrayObject,
WorkflowIOValueTypeEnum.arrayAny,
WorkflowIOValueTypeEnum.chatHistory,
WorkflowIOValueTypeEnum.datasetQuote,
WorkflowIOValueTypeEnum.dynamic,
WorkflowIOValueTypeEnum.selectDataset,
WorkflowIOValueTypeEnum.selectApp
],
[WorkflowIOValueTypeEnum.chatHistory]: [
WorkflowIOValueTypeEnum.chatHistory,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.datasetQuote]: [
WorkflowIOValueTypeEnum.datasetQuote,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.dynamic]: [
WorkflowIOValueTypeEnum.dynamic,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.selectDataset]: [
WorkflowIOValueTypeEnum.selectDataset,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.selectApp]: [
WorkflowIOValueTypeEnum.selectApp,
WorkflowIOValueTypeEnum.arrayAny
],
[WorkflowIOValueTypeEnum.arrayAny]: [WorkflowIOValueTypeEnum.arrayAny],
[WorkflowIOValueTypeEnum.any]: [WorkflowIOValueTypeEnum.arrayAny]
};
return outputs.filter(
(output) =>
valueType === WorkflowIOValueTypeEnum.any ||
valueType === WorkflowIOValueTypeEnum.arrayAny ||
!output.valueType ||
output.valueType === WorkflowIOValueTypeEnum.any ||
output.valueType === valueType ||
valueType?.replace('array', '').toLowerCase() === output.valueType
validTypeMap[valueType].includes(output.valueType)
);
};
@@ -341,46 +396,25 @@ export const checkWorkflowNodeAndConnection = ({
if (input.value === undefined) return true;
}
if (
node.data.flowNodeType === FlowNodeTypeEnum.pluginOutput &&
(input.value?.length === 0 ||
(isReferenceValue(input.value, nodeIds) && !input.value?.[1]))
) {
return true;
}
// Check plugin output
// if (
// node.data.flowNodeType === FlowNodeTypeEnum.pluginOutput &&
// (input.value?.length === 0 ||
// (isValidReferenceValue(input.value, nodeIds) && !input.value?.[1]))
// ) {
// return true;
// }
// check reference invalid
const renderType = input.renderTypeList[input.selectedTypeIndex || 0];
if (renderType === FlowNodeInputTypeEnum.reference) {
const checkReference = (value: [string, string]) => {
if (value[0] === VARIABLE_NODE_ID) {
return false;
}
const sourceNode = nodes.find((item) => item.data.nodeId === value[0]);
if (!sourceNode) {
if (input.valueType?.startsWith('array')) {
if (input.required && (!input.value || input.value.length === 0)) {
return true;
}
const sourceOutput = filterWorkflowNodeOutputsByType(
sourceNode.data.outputs,
input.valueType as WorkflowIOValueTypeEnum
).find((item) => item.id === value[1]);
return !sourceOutput;
};
// Old format
if (isReferenceValueFormat(input.value)) {
return input.required && checkReference(input.value);
return isValidArrayReferenceValue(input.value, nodeIds);
}
// New format
return input.value.some((inputItem: ReferenceItemValueType) => {
if (!Array.isArray(inputItem) || inputItem.length !== 2) {
return true;
}
return checkReference(inputItem as [string, string]);
});
return input.required && !isValidReferenceValue(input.value, nodeIds);
}
return false;
})