diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index dbeda7060..0ca64623f 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -335,7 +335,7 @@ export enum ContentTypes { raw = 'raw-text' } -export const ArrayTypeMap = { +export const ArrayTypeMap: Record = { [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 }; diff --git a/packages/global/core/workflow/runtime/utils.ts b/packages/global/core/workflow/runtime/utils.ts index 2e27bed0e..b48f7dd93 100644 --- a/packages/global/core/workflow/runtime/utils.ts +++ b/packages/global/core/workflow/runtime/utils.ts @@ -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; }) => { 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((val) => { return getReferenceVariableValue({ diff --git a/packages/global/core/workflow/template/system/variableUpdate/type.d.ts b/packages/global/core/workflow/template/system/variableUpdate/type.d.ts index 512353d0e..c0ac23ddf 100644 --- a/packages/global/core/workflow/template/system/variableUpdate/type.d.ts +++ b/packages/global/core/workflow/template/system/variableUpdate/type.d.ts @@ -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; }; diff --git a/packages/global/core/workflow/type/io.d.ts b/packages/global/core/workflow/type/io.d.ts index 7844bb3c5..3653e2a12 100644 --- a/packages/global/core/workflow/type/io.d.ts +++ b/packages/global/core/workflow/type/io.d.ts @@ -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; diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts index 5e9337859..d4a5a4a29 100644 --- a/packages/global/core/workflow/utils.ts +++ b/packages/global/core/workflow/utils.ts @@ -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) => { diff --git a/packages/service/core/chat/pushChatLog.ts b/packages/service/core/chat/pushChatLog.ts index 91d31e357..32d8fd524 100644 --- a/packages/service/core/chat/pushChatLog.ts +++ b/packages/service/core/chat/pushChatLog.ts @@ -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, - MongoChatItem.findById(chatItemIdAi).lean() as Promise + MongoChatItem.findById(chatItemIdHuman).lean(), + MongoChatItem.findById(chatItemIdAi).lean() as Promise ]); - 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}` }; diff --git a/packages/service/core/workflow/dispatch/loop/runLoop.ts b/packages/service/core/workflow/dispatch/loop/runLoop.ts index d7c66444d..eb0b8b57e 100644 --- a/packages/service/core/workflow/dispatch/loop/runLoop.ts +++ b/packages/service/core/workflow/dispatch/loop/runLoop.ts @@ -25,7 +25,7 @@ export const dispatchLoop = async (props: Props): Promise => { 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'); diff --git a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts index c32ba0307..a1f6af8b0 100644 --- a/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts +++ b/packages/service/core/workflow/dispatch/tools/runUpdateVar.ts @@ -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 => 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]) { diff --git a/projects/app/.env.template b/projects/app/.env.template index f6721c42c..6df2b8e4c 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -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 diff --git a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx index 70db4ab7d..8df93e258 100644 --- a/projects/app/src/pages/app/detail/components/Plugin/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Plugin/Header.tsx @@ -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, diff --git a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx index 640ee0063..2e28c6d7d 100644 --- a/projects/app/src/pages/app/detail/components/Workflow/Header.tsx +++ b/projects/app/src/pages/app/detail/components/Workflow/Header.tsx @@ -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, diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index 967ec14e6..a818068fa 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -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(() => { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx index 5eb6edeeb..7248ea8f1 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ButtonEdge.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx index 125c49918..1c6295320 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx index a9d8bbcad..a3e41227e 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/FlowController.tsx @@ -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(); } }); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx index 64b8dd53b..ac07af240 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx index 2a20c2d25..8400c4274 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useKeyboard.tsx @@ -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(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx index ef28baa43..78fdd7718 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx @@ -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); } }); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx index 200085f6b..af47a3386 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx @@ -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, diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx index 95d9bdadf..1fed1610b 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx @@ -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) => { 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) => { }; }, [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) => { 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) => { }, [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) => { value: JSON.parse(childrenNodeIdList) } }); - }, [childrenNodeIdList, nodeId, onChangeNode]); + resetParentNodeSizeAndPosition(nodeId); + }, [childrenNodeIdList]); + // Update child node position + const inputBoxRef = useRef(null); + const size = useSize(inputBoxRef); + const prevHeightRef = useRef(); // 添加 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 ( - + diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx index b979d499a..814b63838 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeDatasetConcat.tsx @@ -147,7 +147,7 @@ const VariableSelector = ({ }); const onSelect = useCallback( - (e: ReferenceItemValueType) => { + (e?: ReferenceItemValueType) => { if (!e) return; onChangeNode({ diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx index 2d6e4120c..d8dda9cff 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeHttp/index.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx index 129406265..33e19f2f3 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeIfElse/ListItem.tsx @@ -309,7 +309,7 @@ const VariableSelector = ({ }: { nodeId: string; variable?: ReferenceItemValueType; - onSelect: (e: ReferenceItemValueType) => void; + onSelect: (e?: ReferenceItemValueType) => void; }) => { const { t } = useTranslation(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx index 0448c575c..ab008746d 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/PluginOutput.tsx @@ -110,7 +110,8 @@ function Reference({ const [editField, setEditField] = useState(); const onSelect = useCallback( - (e: ReferenceValueType) => { + (e?: ReferenceValueType) => { + if (!e) return; onChangeNode({ nodeId, type: 'updateInput', diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx index 71eb32f9e..c6312aacf 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeVariableUpdate.tsx @@ -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) => { const { inputs = [], nodeId } = data; @@ -43,7 +43,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps) => 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) => (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) => 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(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx index 2e45ecd17..6137d094c 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ConnectionHandle.tsx @@ -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(() => { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx index 9eb2f76a9..ef7627ada 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/ToolHandle.tsx @@ -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( diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx index 4ea08cca7..9772fc808 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/Handle/index.tsx @@ -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; connectedStyle: Record; }) { - 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; connectedStyle: Record; }) { - 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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index e5d40e2c4..db430e9e5 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -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( diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx index cb5daaa70..87f816103 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/DynamicInputs/index.tsx @@ -123,7 +123,8 @@ function Reference({ const [editField, setEditField] = useState(); const onSelect = useCallback( - (e: ReferenceValueType) => { + (e?: ReferenceValueType) => { + if (!e) return; onChangeNode({ nodeId, type: 'replaceInput', diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx index d81d1218b..0e3c7629a 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -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', diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx index b612512be..a7c82f363 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/TextInput.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx index 37dc6e481..2a25ff93f 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx @@ -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); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx index f67a96a0f..fa98014a1 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/index.tsx @@ -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(); /* 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[], diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx index 56019632d..7ff25965a 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context/workflowInitContext.tsx @@ -56,7 +56,7 @@ type WorkflowActionContextType = { setEdges: Dispatch[]>>; onEdgesChange: OnChange; }; -export const WorkflowActionContext = createContext({ +export const WorkflowNodeEdgeContext = createContext({ setNodes: function ( value: React.SetStateAction[]> ): void { @@ -128,9 +128,9 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => { nodes }} > - + {children} - + ); }; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index f07514aa3..46e864b26 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -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.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; })