mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
Workflow experience optimization (#2681)
* perf: workflow handle connect * feat: pan drag mode * perf: workflow keyboard and touchTable adapt * perf: teaxtarea no wheel * remove render error
This commit is contained in:
@@ -68,7 +68,13 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
|
||||
return (
|
||||
<MyTooltip label={disableTip}>
|
||||
<MySelect isDisabled={!!disableTip} list={expandList} {...props} onchange={onSelect} />
|
||||
<MySelect
|
||||
className="nowheel"
|
||||
isDisabled={!!disableTip}
|
||||
list={expandList}
|
||||
{...props}
|
||||
onchange={onSelect}
|
||||
/>
|
||||
</MyTooltip>
|
||||
);
|
||||
};
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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 */}
|
||||
<MyTooltip
|
||||
label={
|
||||
workflowControlMode === 'select'
|
||||
? t('workflow:pan_priority')
|
||||
: t('workflow:mouse_priority')
|
||||
}
|
||||
>
|
||||
<ControlButton
|
||||
onClick={() => {
|
||||
setWorkflowControlMode(workflowControlMode === 'select' ? 'drag' : 'select');
|
||||
}}
|
||||
style={{
|
||||
...buttonStyle
|
||||
}}
|
||||
className={`${styles.customControlButton}`}
|
||||
>
|
||||
<MyIcon
|
||||
name={
|
||||
workflowControlMode === 'select'
|
||||
? 'core/workflow/touchTable'
|
||||
: 'core/workflow/mouse'
|
||||
}
|
||||
/>
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
|
||||
<Box w="1px" h="20px" bg="gray.200" mx={1.5}></Box>
|
||||
|
||||
{/* undo */}
|
||||
<MyTooltip label={isMac ? t('common:common.undo_tip_mac') : t('common:common.undo_tip')}>
|
||||
<ControlButton
|
||||
@@ -107,7 +139,7 @@ const FlowController = React.memo(function FlowController() {
|
||||
|
||||
{/* zoom out */}
|
||||
<MyTooltip
|
||||
label={isMac ? t('common:common.zoomout_tip_mac') : t('common:common.zoomout_tip')}
|
||||
label={isMac ? t('common:common.zoomin_tip_mac') : t('common:common.zoomin_tip')}
|
||||
>
|
||||
<ControlButton
|
||||
onClick={() => zoomOut()}
|
||||
@@ -121,7 +153,7 @@ const FlowController = React.memo(function FlowController() {
|
||||
|
||||
{/* zoom in */}
|
||||
<MyTooltip
|
||||
label={isMac ? t('common:common.zoomin_tip_mac') : t('common:common.zoomin_tip')}
|
||||
label={isMac ? t('common:common.zoomout_tip_mac') : t('common:common.zoomout_tip')}
|
||||
>
|
||||
<ControlButton
|
||||
onClick={() => zoomIn()}
|
||||
@@ -149,7 +181,20 @@ const FlowController = React.memo(function FlowController() {
|
||||
<Background />
|
||||
</>
|
||||
);
|
||||
}, [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;
|
||||
});
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<FlowController />
|
||||
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
|
||||
|
@@ -35,7 +35,7 @@ const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<ToolSourceHandle nodeId={nodeId} />
|
||||
<ToolSourceHandle />
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
|
@@ -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 : (
|
||||
<MyTooltip label={t('common:core.workflow.tool.Handle')} shouldWrapChildren={false}>
|
||||
<Handle
|
||||
style={{
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
width: handleSize,
|
||||
height: handleSize
|
||||
}}
|
||||
type="target"
|
||||
id={handleId}
|
||||
position={Position.Top}
|
||||
>
|
||||
<Box
|
||||
className="flow-handle"
|
||||
w={handleSize}
|
||||
h={handleSize}
|
||||
border={'4px solid #8774EE'}
|
||||
transform={'translate(0,-30%) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
visibility={'visible'}
|
||||
/>
|
||||
</Handle>
|
||||
</MyTooltip>
|
||||
return (
|
||||
<Handle
|
||||
style={{
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
width: handleSize,
|
||||
height: handleSize,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
top: '-10px',
|
||||
...(showHandle ? {} : { visibility: 'hidden' })
|
||||
}}
|
||||
type="target"
|
||||
id={handleId}
|
||||
position={Position.Top}
|
||||
isConnectableStart={false}
|
||||
>
|
||||
<Box
|
||||
className="flow-handle"
|
||||
w={handleSize}
|
||||
h={handleSize}
|
||||
border={'4px solid #8774EE'}
|
||||
transform={'translate(0,0) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
</Handle>
|
||||
);
|
||||
}, [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'}
|
||||
/>
|
||||
</Handle>
|
||||
|
@@ -218,7 +218,7 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
|
||||
return (
|
||||
<Handle
|
||||
style={
|
||||
!!styles && showHandle
|
||||
styles && showHandle
|
||||
? styles
|
||||
: {
|
||||
visibility: 'hidden',
|
||||
@@ -226,10 +226,10 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
|
||||
...handleSize
|
||||
}
|
||||
}
|
||||
isConnectableEnd={styles && showHandle}
|
||||
type="target"
|
||||
id={handleId}
|
||||
position={position}
|
||||
isConnectableStart={false}
|
||||
></Handle>
|
||||
);
|
||||
}, [styles, showHandle, transform, handleId, position]);
|
||||
|
@@ -145,7 +145,7 @@ const NodeCard = (props: Props) => {
|
||||
{/* debug */}
|
||||
<Box px={4} py={3}>
|
||||
{/* tool target handle */}
|
||||
{showToolHandle && <ToolTargetHandle nodeId={nodeId} />}
|
||||
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
|
||||
|
||||
{/* avatar and name */}
|
||||
<Flex alignItems={'center'}>
|
||||
|
@@ -54,6 +54,7 @@ const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<JSONEditor
|
||||
className="nowheel"
|
||||
bg={'white'}
|
||||
borderRadius={'sm'}
|
||||
placeholder={t(item.placeholder as any)}
|
||||
|
@@ -10,6 +10,7 @@ const SelectRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<MySelect
|
||||
className="nowheel"
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
|
@@ -6,7 +6,7 @@ import {
|
||||
storeNode2FlowNode
|
||||
} from '@/web/core/workflow/utils';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
@@ -48,7 +48,8 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { formatTime2YMDHMS, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
|
||||
import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider';
|
||||
import { cloneDeep, isEqual } from 'lodash';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { SetState } from 'ahooks/lib/createUseStorageState';
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
|
||||
@@ -64,6 +65,7 @@ type WorkflowContextType = {
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
filterAppIds?: string[];
|
||||
reactFlowWrapper: React.RefObject<HTMLDivElement> | null;
|
||||
mouseInCanvas: boolean;
|
||||
|
||||
// nodes
|
||||
nodes: Node<FlowNodeItemType, string | undefined>[];
|
||||
@@ -145,8 +147,6 @@ type WorkflowContextType = {
|
||||
edges: StoreEdgeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
onSaveWorkflow: () => Promise<null | undefined>;
|
||||
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<WorkflowContextType>({
|
||||
isSaving: false,
|
||||
setConnectingEdge: function (
|
||||
value: React.SetStateAction<OnConnectStartParams | undefined>
|
||||
): void {
|
||||
@@ -205,6 +208,7 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
reactFlowWrapper: null,
|
||||
nodes: [],
|
||||
nodeList: [],
|
||||
mouseInCanvas: false,
|
||||
setNodes: function (
|
||||
value: React.SetStateAction<Node<FlowNodeItemType, string | undefined>[]>
|
||||
): void {
|
||||
@@ -295,9 +299,6 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
| undefined {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onSaveWorkflow: function (): Promise<null | undefined> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
historiesDefaultData: undefined,
|
||||
setHistoriesDefaultData: function (value: React.SetStateAction<InitProps | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
@@ -323,7 +324,11 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
||||
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<HTMLDivElement>(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<string>();
|
||||
@@ -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<DebugDataType>();
|
||||
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,
|
||||
|
@@ -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';
|
||||
|
Reference in New Issue
Block a user