4.8.13 test (#3098)
* perf: loop node refresh * rename context * comment * fix: ts * perf: push chat log
This commit is contained in:
@@ -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
|
||||
};
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
4
packages/global/core/workflow/type/io.d.ts
vendored
4
packages/global/core/workflow/type/io.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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}`
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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]) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>(() => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'}>
|
||||
|
||||
@@ -147,7 +147,7 @@ const VariableSelector = ({
|
||||
});
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: ReferenceItemValueType) => {
|
||||
(e?: ReferenceItemValueType) => {
|
||||
if (!e) return;
|
||||
|
||||
onChangeNode({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -309,7 +309,7 @@ const VariableSelector = ({
|
||||
}: {
|
||||
nodeId: string;
|
||||
variable?: ReferenceItemValueType;
|
||||
onSelect: (e: ReferenceItemValueType) => void;
|
||||
onSelect: (e?: ReferenceItemValueType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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[],
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user