diff --git a/docSite/content/zh-cn/docs/development/upgrading/4811.md b/docSite/content/zh-cn/docs/development/upgrading/4811.md
index c2aac6ad15ea..427ab90c1819 100644
--- a/docSite/content/zh-cn/docs/development/upgrading/4811.md
+++ b/docSite/content/zh-cn/docs/development/upgrading/4811.md
@@ -13,16 +13,13 @@ weight: 813
-------
-## V4.8.11 更新预告
+## V4.8.11 更新说明
1.
-2. 新增 - 工作流循环执行节点。
-3. 新增 - 工作流用户表单输入节点。
-4. 新增 - 插件输出,支持指定某些字段为工具调用结果。
-5. 新增 - 插件支持配置使用引导、全局变量和文件输入。
-6. 新增 - 简易模式支持新的版本管理方式。
-7. 新增 - 聊天记录滚动加载,不再只加载 30 条。
-8. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。
-9. 优化 - 工作流 handler 性能优化。
-10. 修复 - 知识库选择权限问题。
-11. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
+2. 新增 - 聊天记录滚动加载,不再只加载 30 条。
+3. 新增 - 工作流增加触摸板优先模式。
+4. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环。
+5. 优化 - 工作流 handler 性能优化。
+6. 优化 - 工作流快捷键,避免调试测试时也会触发。
+7. 修复 - 知识库选择权限问题。
+8. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts
index 022ad5fd18f3..d3ee88a17f5f 100644
--- a/packages/web/components/common/Icon/constants.ts
+++ b/packages/web/components/common/Icon/constants.ts
@@ -2,10 +2,8 @@
export const iconPaths = {
book: () => import('./icons/book.svg'),
- visible: () => import('./icons/visible.svg'),
change: () => import('./icons/change.svg'),
chatSend: () => import('./icons/chatSend.svg'),
- configmap: () => import('./icons/configmap.svg'),
closeSolid: () => import('./icons/closeSolid.svg'),
collectionLight: () => import('./icons/collectionLight.svg'),
collectionSolid: () => import('./icons/collectionSolid.svg'),
@@ -80,6 +78,7 @@ export const iconPaths = {
'common/voiceLight': () => import('./icons/common/voiceLight.svg'),
'common/warn': () => import('./icons/common/warn.svg'),
'common/wechatFill': () => import('./icons/common/wechatFill.svg'),
+ configmap: () => import('./icons/configmap.svg'),
copy: () => import('./icons/copy.svg'),
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
@@ -199,6 +198,7 @@ export const iconPaths = {
import('./icons/core/workflow/inputType/selectLLM.svg'),
'core/workflow/inputType/switch': () => import('./icons/core/workflow/inputType/switch.svg'),
'core/workflow/inputType/textarea': () => import('./icons/core/workflow/inputType/textarea.svg'),
+ 'core/workflow/mouse': () => import('./icons/core/workflow/mouse.svg'),
'core/workflow/publish': () => import('./icons/core/workflow/publish.svg'),
'core/workflow/redo': () => import('./icons/core/workflow/redo.svg'),
'core/workflow/revertVersion': () => import('./icons/core/workflow/revertVersion.svg'),
@@ -249,6 +249,7 @@ export const iconPaths = {
import('./icons/core/workflow/template/variableUpdate.svg'),
'core/workflow/template/workflowStart': () =>
import('./icons/core/workflow/template/workflowStart.svg'),
+ 'core/workflow/touchTable': () => import('./icons/core/workflow/touchTable.svg'),
'core/workflow/undo': () => import('./icons/core/workflow/undo.svg'),
'core/workflow/upload': () => import('./icons/core/workflow/upload.svg'),
'core/workflow/versionHistories': () => import('./icons/core/workflow/versionHistories.svg'),
@@ -332,5 +333,6 @@ export const iconPaths = {
text: () => import('./icons/text.svg'),
union: () => import('./icons/union.svg'),
user: () => import('./icons/user.svg'),
+ visible: () => import('./icons/visible.svg'),
wx: () => import('./icons/wx.svg')
};
diff --git a/packages/web/components/common/Icon/icons/core/workflow/mouse.svg b/packages/web/components/common/Icon/icons/core/workflow/mouse.svg
new file mode 100644
index 000000000000..38ceab93eff6
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/core/workflow/mouse.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/packages/web/components/common/Icon/icons/core/workflow/touchTable.svg b/packages/web/components/common/Icon/icons/core/workflow/touchTable.svg
new file mode 100644
index 000000000000..e4de14641b39
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/core/workflow/touchTable.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/packages/web/components/common/MySelect/MultipleRowSelect.tsx b/packages/web/components/common/MySelect/MultipleRowSelect.tsx
index 1af0e6a15218..c3efd53ba93b 100644
--- a/packages/web/components/common/MySelect/MultipleRowSelect.tsx
+++ b/packages/web/components/common/MySelect/MultipleRowSelect.tsx
@@ -35,6 +35,7 @@ const MultipleRowSelect = ({
return (
<>
{
return (
-
+
);
};
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx
index 0dc427592d75..000d4c674259 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/AppCard.tsx
@@ -33,10 +33,7 @@ const AppCard = ({
const { appDetail, onOpenInfoEdit, onOpenTeamTagModal, onDelApp, currentTab } =
useContextSelector(AppContext, (v) => v);
- const { historiesDefaultData, onSaveWorkflow, isSaving } = useContextSelector(
- WorkflowContext,
- (v) => v
- );
+ const { historiesDefaultData } = useContextSelector(WorkflowContext, (v) => v);
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
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 7485c33de0cc..4cf829be4c9a 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
@@ -1,55 +1,58 @@
-import React, { useEffect, useMemo } from 'react';
+import React, { useMemo } from 'react';
import { Background, ControlButton, MiniMap, Panel, useReactFlow, useViewport } from 'reactflow';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box } from '@chakra-ui/react';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
import styles from './index.module.scss';
import { maxZoom, minZoom } from '../index';
+import { useKeyPress } from 'ahooks';
+
+const buttonStyle = {
+ border: 'none',
+ borderRadius: '6px',
+ padding: '7px'
+};
const FlowController = React.memo(function FlowController() {
const { fitView, zoomIn, zoomOut } = useReactFlow();
const { zoom } = useViewport();
- const { undo, redo, canRedo, canUndo } = useContextSelector(WorkflowContext, (v) => v);
+ const {
+ undo,
+ redo,
+ canRedo,
+ canUndo,
+ workflowControlMode,
+ setWorkflowControlMode,
+ mouseInCanvas
+ } = useContextSelector(WorkflowContext, (v) => v);
const { t } = useTranslation();
const isMac = !window ? false : window.navigator.userAgent.toLocaleLowerCase().includes('mac');
- useEffect(() => {
- const keyDownHandler = (event: KeyboardEvent) => {
- if (
- (event.key === 'z' || event.key === 'Z') &&
- (event.ctrlKey || event.metaKey) &&
- event.shiftKey
- ) {
- event.preventDefault();
- redo();
- } else if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {
- event.preventDefault();
- undo();
- } else if ((event.key === '=' || event.key === '+') && (event.ctrlKey || event.metaKey)) {
- event.preventDefault();
- zoomIn();
- } else if (event.key === '-' && (event.ctrlKey || event.metaKey)) {
- event.preventDefault();
- zoomOut();
- }
- };
-
- document.addEventListener('keydown', keyDownHandler);
-
- return () => {
- document.removeEventListener('keydown', keyDownHandler);
- };
- }, [undo, redo, zoomIn, zoomOut]);
-
- const buttonStyle = {
- border: 'none',
- borderRadius: '6px',
- padding: '7px'
- };
+ // Controller shortcut key
+ useKeyPress(['ctrl.z', 'meta.z'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ undo();
+ });
+ useKeyPress(['ctrl.shift.z', 'meta.shift.z', 'ctrl.y', 'meta.y'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ redo();
+ });
+ useKeyPress(['ctrl.add', 'meta.add', 'ctrl.equalsign', 'meta.equalsign'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ zoomIn();
+ });
+ useKeyPress(['ctrl.dash', 'meta.dash'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ zoomOut();
+ });
const Render = useMemo(() => {
return (
@@ -79,6 +82,35 @@ const FlowController = React.memo(function FlowController() {
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
}}
>
+ {/* Control Mode */}
+
+ {
+ setWorkflowControlMode(workflowControlMode === 'select' ? 'drag' : 'select');
+ }}
+ style={{
+ ...buttonStyle
+ }}
+ className={`${styles.customControlButton}`}
+ >
+
+
+
+
+
+
{/* undo */}
zoomOut()}
@@ -121,7 +153,7 @@ const FlowController = React.memo(function FlowController() {
{/* zoom in */}
zoomIn()}
@@ -149,7 +181,20 @@ const FlowController = React.memo(function FlowController() {
>
);
- }, [isMac, t, undo, buttonStyle, canUndo, redo, canRedo, zoom, zoomOut, zoomIn, fitView]);
+ }, [
+ workflowControlMode,
+ isMac,
+ t,
+ undo,
+ canUndo,
+ redo,
+ canRedo,
+ zoom,
+ setWorkflowControlMode,
+ zoomOut,
+ zoomIn,
+ fitView
+ ]);
return Render;
});
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/SaveAndPublish.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/SaveAndPublish.tsx
index d0b85437e0f6..329c3bf3741c 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/SaveAndPublish.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/SaveAndPublish.tsx
@@ -3,7 +3,8 @@ import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
+
type FormType = {
versionName: string;
isPublish: boolean | undefined;
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 6d4205135da0..cfe61d1c28d9 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
@@ -1,20 +1,21 @@
-import { useCallback, useEffect, useState } from 'react';
+import { useCallback } from 'react';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useTranslation } from 'next-i18next';
-import { Node } from 'reactflow';
+import { Node, useKeyPress } from 'reactflow';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
import { useWorkflowUtils } from './useUtils';
+import { useKeyPress as useKeyPressEffect } from 'ahooks';
export const useKeyboard = () => {
const { t } = useTranslation();
- const { setNodes, onSaveWorkflow } = useContextSelector(WorkflowContext, (v) => v);
+ const { setNodes, mouseInCanvas } = useContextSelector(WorkflowContext, (v) => v);
const { copyData } = useCopyData();
const { computedNewNodeName } = useWorkflowUtils();
- const [isDowningCtrl, setIsDowningCtrl] = useState(false);
+ const isDowningCtrl = useKeyPress(['Meta', 'Control']);
const hasInputtingElement = useCallback(() => {
const activeElement = document.activeElement;
@@ -84,48 +85,20 @@ export const useKeyboard = () => {
} catch (error) {}
}, [computedNewNodeName, hasInputtingElement, setNodes]);
- const handleKeyDown = useCallback(
- (event: KeyboardEvent) => {
- if (event.ctrlKey || event.metaKey) {
- setIsDowningCtrl(true);
-
- switch (event.key) {
- case 'c':
- onCopy();
- break;
- case 'v':
- onParse();
- break;
- case 's':
- event.preventDefault();
-
- onSaveWorkflow();
- break;
- default:
- break;
- }
- }
- },
- [onCopy, onParse, onSaveWorkflow]
- );
-
- const handleKeyUp = useCallback((event: KeyboardEvent) => {
- setIsDowningCtrl(false);
- }, []);
-
- useEffect(() => {
- document.addEventListener('keydown', handleKeyDown);
- return () => {
- document.removeEventListener('keydown', handleKeyDown);
- };
- }, [handleKeyDown]);
-
- useEffect(() => {
- document.addEventListener('keyup', handleKeyUp);
- return () => {
- document.removeEventListener('keyup', handleKeyUp);
- };
- }, [handleKeyUp]);
+ useKeyPressEffect(['ctrl.c', 'meta.c'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ onCopy();
+ });
+ useKeyPressEffect(['ctrl.v', 'meta.v'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ onParse();
+ });
+ useKeyPressEffect(['ctrl.s', 'meta.s'], (e) => {
+ e.preventDefault();
+ if (!mouseInCanvas) return;
+ });
return {
isDowningCtrl
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 50f3df0bd12f..455ee74e37fa 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
@@ -1,5 +1,5 @@
import React from 'react';
-import ReactFlow, { NodeProps, ReactFlowProvider } from 'reactflow';
+import ReactFlow, { NodeProps, ReactFlowProvider, SelectionMode } from 'reactflow';
import { Box, IconButton, useDisclosure } from '@chakra-ui/react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
@@ -59,7 +59,10 @@ const edgeTypes = {
};
const Workflow = () => {
- const { nodes, edges, reactFlowWrapper } = useContextSelector(WorkflowContext, (v) => v);
+ const { nodes, edges, reactFlowWrapper, workflowControlMode } = useContextSelector(
+ WorkflowContext,
+ (v) => v
+ );
const {
handleNodesChange,
@@ -124,6 +127,7 @@ const Workflow = () => {
connectionLineStyle={connectionLineStyle}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
+ connectionRadius={50}
onNodesChange={handleNodesChange}
onEdgesChange={handleEdgeChange}
onConnect={customOnConnect}
@@ -131,6 +135,17 @@ const Workflow = () => {
onConnectEnd={onConnectEnd}
onEdgeMouseEnter={onEdgeMouseEnter}
onEdgeMouseLeave={onEdgeMouseLeave}
+ panOnScrollSpeed={2}
+ {...(workflowControlMode === 'select'
+ ? {
+ selectionMode: SelectionMode.Full,
+ selectNodesOnDrag: false,
+ selectionOnDrag: true,
+ selectionKeyCode: null,
+ panOnDrag: false,
+ panOnScroll: true
+ }
+ : {})}
>
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx
index 6ae5e2432ffd..6e71d3498a38 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeTools.tsx
@@ -35,7 +35,7 @@ const NodeTools = ({ data, selected }: NodeProps) => {
/>
-
+
);
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 864dadc15d9b..4156c6b0fd9e 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
@@ -4,15 +4,16 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import { Connection, Handle, Position } from 'reactflow';
import { useCallback, useMemo } from 'react';
-import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
-const handleSize = '14px';
+
+const handleSize = '16px';
type ToolHandleProps = BoxProps & {
nodeId: string;
+ show: boolean;
};
-export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
+export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
const { t } = useTranslation();
const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
@@ -22,44 +23,45 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
const connected = edges.some((edge) => edge.target === nodeId && edge.targetHandle === handleId);
// if top handle is connected, return null
- const hidden =
- !connected &&
- (connectingEdge?.handleId !== NodeOutputKeyEnum.selectedTools ||
- edges.some((edge) => edge.targetHandle === getHandleId(nodeId, 'target', 'top')));
+ const showHandle =
+ connected || (show && connectingEdge?.handleId === NodeOutputKeyEnum.selectedTools);
const Render = useMemo(() => {
- return hidden ? null : (
-
-
-
-
-
+ return (
+
+
+
);
- }, [handleId, hidden, t]);
+ }, [handleId, showHandle]);
return Render;
};
-export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
+export const ToolSourceHandle = () => {
const { t } = useTranslation();
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
@@ -86,7 +88,11 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
backgroundColor: 'transparent',
border: 'none',
width: handleSize,
- height: handleSize
+ height: handleSize,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ bottom: '-10px'
}}
type="source"
id={NodeOutputKeyEnum.selectedTools}
@@ -97,7 +103,7 @@ export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
w={handleSize}
h={handleSize}
border={'4px solid #8774EE'}
- transform={'translate(0,30%) rotate(45deg)'}
+ transform={'translate(0,0) rotate(45deg)'}
pointerEvents={'none'}
/>
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 afaa5b3f410c..406e828fc992 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
@@ -218,7 +218,7 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
return (
);
}, [styles, showHandle, transform, handleId, position]);
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 f74d7a28ad92..98e0a934dd6a 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
@@ -145,7 +145,7 @@ const NodeCard = (props: Props) => {
{/* debug */}
{/* tool target handle */}
- {showToolHandle && }
+
{/* avatar and name */}
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
index d50dfd49c72b..21f4ac0f130d 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx
@@ -54,6 +54,7 @@ const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const Render = useMemo(() => {
return (
{
const Render = useMemo(() => {
return (
= (changes: ChangesType[]) => void;
@@ -64,6 +65,7 @@ type WorkflowContextType = {
basicNodeTemplates: FlowNodeTemplateType[];
filterAppIds?: string[];
reactFlowWrapper: React.RefObject | null;
+ mouseInCanvas: boolean;
// nodes
nodes: Node[];
@@ -145,8 +147,6 @@ type WorkflowContextType = {
edges: StoreEdgeItemType[];
}
| undefined;
- onSaveWorkflow: () => Promise;
- isSaving: boolean;
// debug
workflowDebugData:
@@ -182,6 +182,10 @@ type WorkflowContextType = {
| undefined
>
>;
+
+ //
+ workflowControlMode?: 'drag' | 'select';
+ setWorkflowControlMode: (value?: SetState<'drag' | 'select'> | undefined) => void;
};
type DebugDataType = {
@@ -192,7 +196,6 @@ type DebugDataType = {
};
export const WorkflowContext = createContext({
- isSaving: false,
setConnectingEdge: function (
value: React.SetStateAction
): void {
@@ -205,6 +208,7 @@ export const WorkflowContext = createContext({
reactFlowWrapper: null,
nodes: [],
nodeList: [],
+ mouseInCanvas: false,
setNodes: function (
value: React.SetStateAction[]>
): void {
@@ -295,9 +299,6 @@ export const WorkflowContext = createContext({
| undefined {
throw new Error('Function not implemented.');
},
- onSaveWorkflow: function (): Promise {
- throw new Error('Function not implemented.');
- },
historiesDefaultData: undefined,
setHistoriesDefaultData: function (value: React.SetStateAction): void {
throw new Error('Function not implemented.');
@@ -323,7 +324,11 @@ export const WorkflowContext = createContext({
throw new Error('Function not implemented.');
},
canRedo: false,
- canUndo: false
+ canUndo: false,
+ workflowControlMode: 'drag',
+ setWorkflowControlMode: function (value?: SetState<'drag' | 'select'> | undefined): void {
+ throw new Error('Function not implemented.');
+ }
});
const WorkflowContextProvider = ({
@@ -337,9 +342,34 @@ const WorkflowContextProvider = ({
const { toast } = useToast();
const reactFlowWrapper = useRef(null);
- const { appDetail, setAppDetail, updateAppDetail } = useContextSelector(AppContext, (v) => v);
+ const { appDetail, setAppDetail } = useContextSelector(AppContext, (v) => v);
const appId = appDetail._id;
+ const [workflowControlMode, setWorkflowControlMode] = useLocalStorageState<'drag' | 'select'>(
+ 'workflow-control-mode',
+ {
+ defaultValue: 'select',
+ listenStorageChange: true
+ }
+ );
+
+ // Mouse in canvas
+ const [mouseInCanvas, setMouseInCanvas] = useState(false);
+ useEffect(() => {
+ const handleMouseInCanvas = (e: MouseEvent) => {
+ setMouseInCanvas(true);
+ };
+ const handleMouseOutCanvas = (e: MouseEvent) => {
+ setMouseInCanvas(false);
+ };
+ reactFlowWrapper?.current?.addEventListener('mouseenter', handleMouseInCanvas);
+ reactFlowWrapper?.current?.addEventListener('mouseleave', handleMouseOutCanvas);
+ return () => {
+ reactFlowWrapper?.current?.removeEventListener('mouseenter', handleMouseInCanvas);
+ reactFlowWrapper?.current?.removeEventListener('mouseleave', handleMouseOutCanvas);
+ };
+ }, [reactFlowWrapper?.current]);
+
/* edge */
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [hoverEdgeId, setHoverEdgeId] = useState();
@@ -586,33 +616,6 @@ const WorkflowContextProvider = ({
return storeNodes;
});
- /* save workflow */
- const { runAsync: onSaveWorkflow, loading: isSaving } = useRequest2(async () => {
- const { nodes } = await getWorkflowStore();
-
- // version preview / debug mode, not save
- if (appDetail.version !== 'v2' || historiesDefaultData || isSaving || !!workflowDebugData)
- return;
-
- const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges });
-
- // check valid
- if (storeWorkflow.nodes.length === 0 || storeWorkflow.edges.length === 0) {
- return;
- }
-
- try {
- await updateAppDetail({
- ...storeWorkflow,
- chatConfig: appDetail.chatConfig,
- //@ts-ignore
- version: 'v2'
- });
- } catch (error) {}
-
- return null;
- });
-
/* debug */
const [workflowDebugData, setWorkflowDebugData] = useState();
const onNextNodeDebug = useCallback(
@@ -944,6 +947,10 @@ const WorkflowContextProvider = ({
appId,
reactFlowWrapper,
basicNodeTemplates,
+ workflowControlMode,
+ setWorkflowControlMode,
+ mouseInCanvas,
+
// node
nodes,
setNodes,
@@ -984,8 +991,6 @@ const WorkflowContextProvider = ({
initData,
flowData2StoreDataAndCheck,
flowData2StoreData,
- onSaveWorkflow,
- isSaving,
// debug
workflowDebugData,
diff --git a/projects/app/src/pages/chat/team.tsx b/projects/app/src/pages/chat/team.tsx
index 6b96c2a2e358..5faec33c5a96 100644
--- a/projects/app/src/pages/chat/team.tsx
+++ b/projects/app/src/pages/chat/team.tsx
@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
-import { delChatRecordById, getChatHistories, getTeamChatInfo } from '@/web/core/chat/api';
+import { delChatRecordById, getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -11,7 +11,6 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
-import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';