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';