mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-07-21 04:14:32 +08:00
### What problem does this PR solve? feat: Support shortcut keys to copy nodes #3283 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
8b6e272197
commit
63f7d3bae2
@ -125,7 +125,6 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|||||||
onNodeClick={onNodeClick}
|
onNodeClick={onNodeClick}
|
||||||
onPaneClick={onPaneClick}
|
onPaneClick={onPaneClick}
|
||||||
onInit={setReactFlowInstance}
|
onInit={setReactFlowInstance}
|
||||||
// onKeyUp={handleKeyUp}
|
|
||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={onSelectionChange}
|
||||||
nodeOrigin={[0.5, 0]}
|
nodeOrigin={[0.5, 0]}
|
||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
@ -141,6 +140,18 @@ function FlowCanvas({ chatDrawerVisible, hideChatDrawer }: IProps) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
deleteKeyCode={['Delete', 'Backspace']}
|
deleteKeyCode={['Delete', 'Backspace']}
|
||||||
|
onPaste={(...params) => {
|
||||||
|
console.info('onPaste:', ...params);
|
||||||
|
}}
|
||||||
|
onPasteCapture={(...params) => {
|
||||||
|
console.info('onPasteCapture:', ...params);
|
||||||
|
}}
|
||||||
|
onCopy={(...params) => {
|
||||||
|
console.info('onCopy:', ...params);
|
||||||
|
}}
|
||||||
|
onCopyCapture={(...params) => {
|
||||||
|
console.info('onCopyCapture:', ...params);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Background />
|
<Background />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
@ -3,7 +3,7 @@ import { CopyOutlined } from '@ant-design/icons';
|
|||||||
import { Flex, MenuProps } from 'antd';
|
import { Flex, MenuProps } from 'antd';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useGetNodeName } from '../../hooks';
|
import { useDuplicateNode } from '../../hooks';
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -15,21 +15,17 @@ interface IProps {
|
|||||||
const NodeDropdown = ({ id, iconFontColor, label }: IProps) => {
|
const NodeDropdown = ({ id, iconFontColor, label }: IProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
const deleteNodeById = useGraphStore((store) => store.deleteNodeById);
|
||||||
const duplicateNodeById = useGraphStore((store) => store.duplicateNode);
|
|
||||||
const getNodeName = useGetNodeName();
|
|
||||||
|
|
||||||
const deleteNode = useCallback(() => {
|
const deleteNode = useCallback(() => {
|
||||||
deleteNodeById(id);
|
deleteNodeById(id);
|
||||||
}, [id, deleteNodeById]);
|
}, [id, deleteNodeById]);
|
||||||
|
|
||||||
const duplicateNode = useCallback(() => {
|
const duplicateNode = useDuplicateNode();
|
||||||
duplicateNodeById(id, getNodeName(label));
|
|
||||||
}, [duplicateNodeById, id, getNodeName, label]);
|
|
||||||
|
|
||||||
const items: MenuProps['items'] = [
|
const items: MenuProps['items'] = [
|
||||||
{
|
{
|
||||||
key: '2',
|
key: '2',
|
||||||
onClick: duplicateNode,
|
onClick: () => duplicateNode(id, label),
|
||||||
label: (
|
label: (
|
||||||
<Flex justify={'space-between'}>
|
<Flex justify={'space-between'}>
|
||||||
{t('common.copy')}
|
{t('common.copy')}
|
||||||
|
@ -4,7 +4,6 @@ import { IGraph } from '@/interfaces/database/flow';
|
|||||||
import { useIsFetching } from '@tanstack/react-query';
|
import { useIsFetching } from '@tanstack/react-query';
|
||||||
import React, {
|
import React, {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
KeyboardEventHandler,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@ -20,7 +19,6 @@ import {
|
|||||||
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logic-hooks';
|
import { useFetchModelId, useSendMessageWithSse } from '@/hooks/logic-hooks';
|
||||||
import { Variable } from '@/interfaces/database/chat';
|
import { Variable } from '@/interfaces/database/chat';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import { useDebounceEffect } from 'ahooks';
|
|
||||||
import { FormInstance, message } from 'antd';
|
import { FormInstance, message } from 'antd';
|
||||||
import { humanId } from 'human-id';
|
import { humanId } from 'human-id';
|
||||||
import { lowerFirst } from 'lodash';
|
import { lowerFirst } from 'lodash';
|
||||||
@ -253,20 +251,6 @@ export const useShowDrawer = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useHandleKeyUp = () => {
|
|
||||||
const deleteEdge = useGraphStore((state) => state.deleteEdge);
|
|
||||||
const handleKeyUp: KeyboardEventHandler = useCallback(
|
|
||||||
(e) => {
|
|
||||||
if (e.code === 'Delete') {
|
|
||||||
deleteEdge();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[deleteEdge],
|
|
||||||
);
|
|
||||||
|
|
||||||
return { handleKeyUp };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useSaveGraph = () => {
|
export const useSaveGraph = () => {
|
||||||
const { data } = useFetchFlow();
|
const { data } = useFetchFlow();
|
||||||
const { setFlow } = useSetFlow();
|
const { setFlow } = useSetFlow();
|
||||||
@ -284,20 +268,6 @@ export const useSaveGraph = () => {
|
|||||||
return { saveGraph };
|
return { saveGraph };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWatchGraphChange = () => {
|
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
|
||||||
const edges = useGraphStore((state) => state.edges);
|
|
||||||
useDebounceEffect(
|
|
||||||
() => {
|
|
||||||
// console.info('useDebounceEffect');
|
|
||||||
},
|
|
||||||
[nodes, edges],
|
|
||||||
{
|
|
||||||
wait: 1000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleFormValuesChange = (id?: string) => {
|
export const useHandleFormValuesChange = (id?: string) => {
|
||||||
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
const updateNodeForm = useGraphStore((state) => state.updateNodeForm);
|
||||||
const handleValuesChange = useCallback(
|
const handleValuesChange = useCallback(
|
||||||
@ -348,8 +318,6 @@ export const useFetchDataOnMount = () => {
|
|||||||
setGraphInfo(data?.dsl?.graph ?? ({} as IGraph));
|
setGraphInfo(data?.dsl?.graph ?? ({} as IGraph));
|
||||||
}, [setGraphInfo, data]);
|
}, [setGraphInfo, data]);
|
||||||
|
|
||||||
useWatchGraphChange();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refetch();
|
refetch();
|
||||||
}, [refetch]);
|
}, [refetch]);
|
||||||
@ -640,3 +608,63 @@ export const useGetComponentLabelByValue = (nodeId: string) => {
|
|||||||
);
|
);
|
||||||
return getLabel;
|
return getLabel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useDuplicateNode = () => {
|
||||||
|
const duplicateNodeById = useGraphStore((store) => store.duplicateNode);
|
||||||
|
const getNodeName = useGetNodeName();
|
||||||
|
|
||||||
|
const duplicateNode = useCallback(
|
||||||
|
(id: string, label: string) => {
|
||||||
|
duplicateNodeById(id, getNodeName(label));
|
||||||
|
},
|
||||||
|
[duplicateNodeById, getNodeName],
|
||||||
|
);
|
||||||
|
|
||||||
|
return duplicateNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCopyPaste = () => {
|
||||||
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
|
const duplicateNode = useDuplicateNode();
|
||||||
|
|
||||||
|
const onCopyCapture = useCallback(
|
||||||
|
(event: ClipboardEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const nodesStr = JSON.stringify(
|
||||||
|
nodes.filter((n) => n.selected && n.data.label !== Operator.Begin),
|
||||||
|
);
|
||||||
|
|
||||||
|
event.clipboardData?.setData('agent:nodes', nodesStr);
|
||||||
|
},
|
||||||
|
[nodes],
|
||||||
|
);
|
||||||
|
|
||||||
|
const onPasteCapture = useCallback(
|
||||||
|
(event: ClipboardEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const nodes = JSON.parse(
|
||||||
|
event.clipboardData?.getData('agent:nodes') || '[]',
|
||||||
|
) as Node[] | undefined;
|
||||||
|
if (nodes) {
|
||||||
|
nodes.forEach((n) => {
|
||||||
|
duplicateNode(n.id, n.data.label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[duplicateNode],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('copy', onCopyCapture);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('copy', onCopyCapture);
|
||||||
|
};
|
||||||
|
}, [onCopyCapture]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('paste', onPasteCapture);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('paste', onPasteCapture);
|
||||||
|
};
|
||||||
|
}, [onPasteCapture]);
|
||||||
|
};
|
||||||
|
@ -5,7 +5,7 @@ import { ReactFlowProvider } from 'reactflow';
|
|||||||
import FlowCanvas from './canvas';
|
import FlowCanvas from './canvas';
|
||||||
import Sider from './flow-sider';
|
import Sider from './flow-sider';
|
||||||
import FlowHeader from './header';
|
import FlowHeader from './header';
|
||||||
import { useFetchDataOnMount } from './hooks';
|
import { useCopyPaste, useFetchDataOnMount } from './hooks';
|
||||||
|
|
||||||
const { Content } = Layout;
|
const { Content } = Layout;
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ function RagFlow() {
|
|||||||
} = useSetModalState();
|
} = useSetModalState();
|
||||||
|
|
||||||
useFetchDataOnMount();
|
useFetchDataOnMount();
|
||||||
|
useCopyPaste();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
@ -236,8 +236,8 @@ const useGraphStore = create<RFState>()(
|
|||||||
const { getNode, addNode, generateNodeName } = get();
|
const { getNode, addNode, generateNodeName } = get();
|
||||||
const node = getNode(id);
|
const node = getNode(id);
|
||||||
const position = {
|
const position = {
|
||||||
x: (node?.position?.x || 0) + 30,
|
x: (node?.position?.x || 0) + 50,
|
||||||
y: (node?.position?.y || 0) + 20,
|
y: (node?.position?.y || 0) + 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
addNode({
|
addNode({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user