feat(web): workflow support undo/redo

This commit is contained in:
zhaoying
2026-04-20 16:08:26 +08:00
parent e7a400bb96
commit 10a91ec5cb
5 changed files with 67 additions and 9 deletions

View File

@@ -2508,6 +2508,7 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
arrange: 'Arrange',
redo: 'Redo',
undo: 'Undo',
fit: 'Fit View',
input: 'Input',
output: 'Output',

View File

@@ -2472,6 +2472,7 @@ export const zh = {
arrange: '整理',
redo: '重做',
undo: '撤销',
fit: '自适应',
input: '输入',
output: '输出',

View File

@@ -1,8 +1,9 @@
import type { FC } from 'react';
import { Select, Divider } from 'antd';
import { PlusOutlined, MinusOutlined, FileAddOutlined } from '@ant-design/icons'
import { Select, Divider, Tooltip } from 'antd';
import { PlusOutlined, MinusOutlined, FileAddOutlined, UndoOutlined, RedoOutlined } from '@ant-design/icons'
import clsx from 'clsx'
import { Node } from '@antv/x6';
import { useTranslation } from 'react-i18next'
import type { GraphRef } from '../types'
@@ -15,6 +16,10 @@ interface CanvasToolbarProps {
setIsHandMode: React.Dispatch<React.SetStateAction<boolean>>;
zoomLevel: number;
addNotes: () => void;
canUndo: boolean;
canRedo: boolean;
onUndo: () => void;
onRedo: () => void;
}
const CanvasToolbar: FC<CanvasToolbarProps> = ({
@@ -22,12 +27,13 @@ const CanvasToolbar: FC<CanvasToolbarProps> = ({
miniMapRef,
graphRef,
zoomLevel,
// canUndo,
// canRedo,
// onUndo,
// onRedo,
canUndo,
canRedo,
onUndo,
onRedo,
addNotes,
}) => {
const { t } = useTranslation()
return (
<>
{/* 小地图 */}
@@ -63,13 +69,16 @@ const CanvasToolbar: FC<CanvasToolbarProps> = ({
{ label: '125%', value: 125 },
{ label: '150%', value: 150 },
{ label: '200%', value: 200 },
{ label: '自适应', value: 'fit' },
{ label: t('workflow.fit'), value: 'fit' },
]}
variant='borderless'
size="small"
/>
<PlusOutlined className="rb:text-[16px] rb:cursor-pointer" onClick={() => graphRef.current?.zoom(0.1)} />
<Divider type="vertical" className="rb:h-4" />
<Tooltip title={`${t('workflow.undo')} (Ctrl+Z)`}><UndoOutlined className={clsx('rb:text-[16px]', canUndo ? 'rb:cursor-pointer' : 'rb:opacity-30 rb:cursor-not-allowed')} onClick={onUndo} /></Tooltip>
<Tooltip title={`${t('workflow.redo')} (Ctrl+Y)`}><RedoOutlined className={clsx('rb:text-[16px]', canRedo ? 'rb:cursor-pointer' : 'rb:opacity-30 rb:cursor-not-allowed')} onClick={onRedo} /></Tooltip>
<Divider type="vertical" className="rb:h-4" />
<FileAddOutlined onClick={addNotes} />
</div>
</>

View File

@@ -2,9 +2,10 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 15:17:48
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 23:17:50
* @Last Modified time: 2026-04-20 16:00:26
*/
import { Clipboard, Graph, Keyboard, MiniMap, Node, Snapline, type Edge } from '@antv/x6';
import { Clipboard, Graph, Keyboard, MiniMap, Node, Snapline, History, type Edge } from '@antv/x6';
import type { HistoryCommand as Command } from '@antv/x6/lib/plugin/history/type';
import { register } from '@antv/x6-react-shape';
import type { PortMetadata } from '@antv/x6/lib/model/port';
import { App } from 'antd';
@@ -63,6 +64,14 @@ export interface UseWorkflowGraphReturn {
copyEvent: () => boolean | void;
/** Handler for paste keyboard event */
parseEvent: () => boolean | void;
/** Whether undo is available */
canUndo: boolean;
/** Whether redo is available */
canRedo: boolean;
/** Undo last action */
undo: () => void;
/** Redo last undone action */
redo: () => void;
/** Function to save workflow configuration */
handleSave: (flag?: boolean) => Promise<unknown>;
/** Chat variables for workflow */
@@ -105,6 +114,8 @@ export const useWorkflowGraph = ({
const [config, setConfig] = useState<WorkflowConfig | null>(null);
const [chatVariables, setChatVariables] = useState<ChatVariable[]>([])
const featuresRef = useRef<FeaturesConfigForm | undefined>(undefined)
const [canUndo, setCanUndo] = useState(false)
const [canRedo, setCanRedo] = useState(false)
useEffect(() => {
if (!graphRef.current) return
@@ -469,6 +480,8 @@ export const useWorkflowGraph = ({
graphRef.current.getNodes().forEach(node => {
if (node.getData()?.cycle) node.toFront();
});
graphRef.current.enableHistory()
graphRef.current.cleanHistory()
}
}, 200)
}
@@ -504,6 +517,22 @@ export const useWorkflowGraph = ({
global: true,
}),
);
graphRef.current.use(
new History({
enabled: false,
beforeAddCommand(_event, args: any) {
const event = args?.key ? `cell:change:${args.key}` : _event;
if (event.startsWith('cell:change:') &&
event !== 'cell:change:position' &&
event !== 'cell:change:source' &&
event !== 'cell:change:target') return false;
},
}),
);
graphRef.current.on('history:change', ({ cmds }: { cmds: Command[] }) => {
setCanUndo(graphRef.current?.canUndo() ?? false)
setCanRedo(graphRef.current?.canRedo() ?? false)
})
};
// 显示/隐藏连接桩
// const showPorts = (show: boolean) => {
@@ -1077,6 +1106,9 @@ export const useWorkflowGraph = ({
graphRef.current.bindKey(['ctrl+v', 'cmd+v'], parseEvent);
// Delete selected nodes and edges
graphRef.current.bindKey(['ctrl+d', 'cmd+d', 'delete', 'backspace'], deleteEvent);
// Undo / Redo
graphRef.current.bindKey(['ctrl+z', 'cmd+z'], () => { graphRef.current?.undo(); return false; });
graphRef.current.bindKey(['ctrl+y', 'cmd+y', 'ctrl+shift+z', 'cmd+shift+z'], () => { graphRef.current?.redo(); return false; });
};
@@ -1390,6 +1422,9 @@ export const useWorkflowGraph = ({
return userVars
}
const undo = () => graphRef.current?.undo()
const redo = () => graphRef.current?.redo()
const handleSaveFeaturesConfig = (value?: FeaturesConfigForm) => {
const { statement = '' } = value?.opening_statement || {}
featuresRef.current = value
@@ -1449,5 +1484,9 @@ export const useWorkflowGraph = ({
handleSaveFeaturesConfig,
features: featuresRef.current,
getStartNodeVariables,
canUndo,
canRedo,
undo,
redo,
};
};

View File

@@ -39,6 +39,10 @@ const Workflow = forwardRef<WorkflowRef, { onFeaturesLoad?: (features: FeaturesC
handleSaveFeaturesConfig,
features,
getStartNodeVariables,
canUndo,
canRedo,
undo,
redo,
} = useWorkflowGraph({ containerRef, miniMapRef, onFeaturesLoad });
const onDragOver = (event: React.DragEvent) => {
@@ -96,6 +100,10 @@ const Workflow = forwardRef<WorkflowRef, { onFeaturesLoad?: (features: FeaturesC
setIsHandMode={setIsHandMode}
zoomLevel={zoomLevel}
addNotes={handleAddNotes}
canUndo={canUndo}
canRedo={canRedo}
onUndo={undo}
onRedo={redo}
/>
</div>