Feat: The Begin and IterationStart operators cannot be deleted using shortcut keys #4287 (#4288)

### What problem does this PR solve?

Feat: The Begin and IterationStart operators cannot be deleted using
shortcut keys #4287

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2024-12-30 17:47:47 +08:00
committed by GitHub
parent 54908ebd30
commit d1971e988a
42 changed files with 391 additions and 246 deletions

View File

@ -1,5 +1,5 @@
import { NodeMouseHandler, useReactFlow } from '@xyflow/react';
import { useCallback, useRef, useState } from 'react';
import { NodeMouseHandler, useReactFlow } from 'reactflow';
import styles from './index.less';

View File

@ -3,7 +3,7 @@ import {
EdgeLabelRenderer,
EdgeProps,
getBezierPath,
} from 'reactflow';
} from '@xyflow/react';
import useGraphStore from '../../store';
import { useTheme } from '@/components/theme-provider';

View File

@ -4,14 +4,16 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { FolderInput, FolderOutput } from 'lucide-react';
import ReactFlow, {
import {
Background,
ConnectionMode,
ControlButton,
Controls,
} from 'reactflow';
import 'reactflow/dist/style.css';
NodeTypes,
ReactFlow,
} from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { FolderInput, FolderOutput } from 'lucide-react';
import ChatDrawer from '../chat/drawer';
import FormDrawer from '../flow-drawer';
import {
@ -20,6 +22,7 @@ import {
useValidateConnection,
useWatchNodeFormDataChange,
} from '../hooks';
import { useBeforeDelete } from '../hooks/use-before-delete';
import { useHandleExportOrImportJsonFile } from '../hooks/use-export-json';
import { useShowDrawer } from '../hooks/use-show-drawer';
import JsonUploadModal from '../json-upload-modal';
@ -43,7 +46,7 @@ import { RewriteNode } from './node/rewrite-node';
import { SwitchNode } from './node/switch-node';
import { TemplateNode } from './node/template-node';
const nodeTypes = {
const nodeTypes: NodeTypes = {
ragNode: RagNode,
categorizeNode: CategorizeNode,
beginNode: BeginNode,
@ -113,6 +116,8 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
hideDrawer,
});
const { handleBeforeDelete } = useBeforeDelete();
useWatchNodeFormDataChange();
return (
@ -165,6 +170,7 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
zIndex: 1001, // https://github.com/xyflow/xyflow/discussions/3498
}}
deleteKeyCode={['Delete', 'Backspace']}
onBeforeDelete={handleBeforeDelete}
>
<Background />
<Controls>

View File

@ -1,22 +1,23 @@
import { useTheme } from '@/components/theme-provider';
import { IBeginNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import get from 'lodash/get';
import { useTranslation } from 'react-i18next';
import { Handle, NodeProps, Position } from 'reactflow';
import {
BeginQueryType,
BeginQueryTypeIconMap,
Operator,
operatorMap,
} from '../../constant';
import { BeginQuery, NodeData } from '../../interface';
import { BeginQuery } from '../../interface';
import OperatorIcon from '../../operator-icon';
import { RightHandleStyle } from './handle-icon';
import styles from './index.less';
// TODO: do not allow other nodes to connect to this node
export function BeginNode({ selected, data }: NodeProps<NodeData>) {
export function BeginNode({ selected, data }: NodeProps<IBeginNode>) {
const { t } = useTranslation();
const query: BeginQuery[] = get(data, 'form.query', []);
const { theme } = useTheme();

View File

@ -1,4 +1,4 @@
import { Handle, Position } from 'reactflow';
import { Handle, Position } from '@xyflow/react';
import React from 'react';
import styles from './index.less';

View File

@ -1,16 +1,20 @@
import LLMLabel from '@/components/llm-select/llm-label';
import { useTheme } from '@/components/theme-provider';
import { ICategorizeNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { RightHandleStyle } from './handle-icon';
import { useBuildCategorizeHandlePositions } from './hooks';
import styles from './index.less';
import NodeHeader from './node-header';
export function CategorizeNode({ id, data, selected }: NodeProps<NodeData>) {
export function CategorizeNode({
id,
data,
selected,
}: NodeProps<ICategorizeNode>) {
const { positions } = useBuildCategorizeHandlePositions({ data, id });
const { theme } = useTheme();
return (

View File

@ -1,8 +1,8 @@
import { IEmailNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useState } from 'react';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -12,7 +12,7 @@ export function EmailNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IEmailNode>) {
const [showDetails, setShowDetails] = useState(false);
return (

View File

@ -1,11 +1,12 @@
import LLMLabel from '@/components/llm-select/llm-label';
import { useTheme } from '@/components/theme-provider';
import { IGenerateNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
import { IGenerateParameter, NodeData } from '../../interface';
import { IGenerateParameter } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -15,7 +16,7 @@ export function GenerateNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IGenerateNode>) {
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
const getLabel = useGetComponentLabelByValue(id);
const { theme } = useTheme();

View File

@ -10,11 +10,11 @@ export const HandleIcon = () => {
};
export const RightHandleStyle: CSSProperties = {
right: -5,
right: 0,
};
export const LeftHandleStyle: CSSProperties = {
left: -7,
left: 0,
};
export default HandleIcon;

View File

@ -1,12 +1,13 @@
import { useUpdateNodeInternals } from '@xyflow/react';
import get from 'lodash/get';
import { useEffect, useMemo } from 'react';
import { useUpdateNodeInternals } from 'reactflow';
import { SwitchElseTo } from '../../constant';
import {
ICategorizeItemResult,
ISwitchCondition,
NodeData,
} from '../../interface';
RAGFlowNodeType,
} from '@/interfaces/database/flow';
import { generateSwitchHandleText } from '../../utils';
export const useBuildCategorizeHandlePositions = ({
@ -14,7 +15,7 @@ export const useBuildCategorizeHandlePositions = ({
id,
}: {
id: string;
data: NodeData;
data: RAGFlowNodeType['data'];
}) => {
const updateNodeInternals = useUpdateNodeInternals();
@ -54,7 +55,7 @@ export const useBuildSwitchHandlePositions = ({
id,
}: {
id: string;
data: NodeData;
data: RAGFlowNodeType['data'];
}) => {
const updateNodeInternals = useUpdateNodeInternals();

View File

@ -1,7 +1,7 @@
import { useTheme } from '@/components/theme-provider';
import { IRagNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -11,7 +11,7 @@ export function RagNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IRagNode>) {
const { theme } = useTheme();
return (
<section

View File

@ -1,10 +1,10 @@
import { useTheme } from '@/components/theme-provider';
import { IInvokeNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { useTranslation } from 'react-i18next';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -14,7 +14,7 @@ export function InvokeNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IInvokeNode>) {
const { t } = useTranslation();
const { theme } = useTheme();
const url = get(data, 'form.url');

View File

@ -1,8 +1,11 @@
import { useTheme } from '@/components/theme-provider';
import {
IIterationNode,
IIterationStartNode,
} from '@/interfaces/database/flow';
import { cn } from '@/lib/utils';
import { Handle, NodeProps, NodeResizeControl, Position } from '@xyflow/react';
import { ListRestart } from 'lucide-react';
import { Handle, NodeProps, NodeResizeControl, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -19,7 +22,11 @@ function ResizeIcon() {
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
style={{ position: 'absolute', right: 5, bottom: 5 }}
style={{
position: 'absolute',
right: 5,
bottom: 5,
}}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="16 20 20 20 20 16" />
@ -33,6 +40,7 @@ function ResizeIcon() {
const controlStyle = {
background: 'transparent',
border: 'none',
cursor: 'nwse-resize',
};
export function IterationNode({
@ -40,7 +48,7 @@ export function IterationNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IIterationNode>) {
const { theme } = useTheme();
return (
@ -93,7 +101,7 @@ export function IterationNode({
export function IterationStartNode({
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IIterationStartNode>) {
const { theme } = useTheme();
return (

View File

@ -1,9 +1,9 @@
import LLMLabel from '@/components/llm-select/llm-label';
import { useTheme } from '@/components/theme-provider';
import { IKeywordNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -13,7 +13,7 @@ export function KeywordNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IKeywordNode>) {
const { theme } = useTheme();
return (
<section

View File

@ -1,7 +1,7 @@
import { useTheme } from '@/components/theme-provider';
import { ILogicNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -11,7 +11,7 @@ export function LogicNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<ILogicNode>) {
const { theme } = useTheme();
return (
<section

View File

@ -1,9 +1,9 @@
import { useTheme } from '@/components/theme-provider';
import { IMessageNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -13,7 +13,7 @@ export function MessageNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IMessageNode>) {
const messages: string[] = get(data, 'form.messages', []);
const { theme } = useTheme();
return (

View File

@ -1,11 +1,11 @@
import { NodeProps, NodeResizeControl } from '@xyflow/react';
import { Flex, Form, Input } from 'antd';
import classNames from 'classnames';
import { NodeProps, NodeResizeControl } from 'reactflow';
import { NodeData } from '../../interface';
import NodeDropdown from './dropdown';
import SvgIcon from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { INoteNode } from '@/interfaces/database/flow';
import { memo, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -21,7 +21,7 @@ const controlStyle = {
border: 'none',
};
function NoteNode({ data, id }: NodeProps<NodeData>) {
function NoteNode({ data, id }: NodeProps<INoteNode>) {
const { t } = useTranslation();
const [form] = Form.useForm();
const { theme } = useTheme();

View File

@ -1,16 +1,16 @@
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { RightHandleStyle } from './handle-icon';
import { useTheme } from '@/components/theme-provider';
import { IRelevantNode } from '@/interfaces/database/flow';
import { get } from 'lodash';
import { useReplaceIdWithName } from '../../hooks';
import styles from './index.less';
import NodeHeader from './node-header';
export function RelevantNode({ id, data, selected }: NodeProps<NodeData>) {
export function RelevantNode({ id, data, selected }: NodeProps<IRelevantNode>) {
const yes = get(data, 'form.yes');
const no = get(data, 'form.no');
const replaceIdWithName = useReplaceIdWithName();

View File

@ -1,12 +1,12 @@
import { useTheme } from '@/components/theme-provider';
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
import { IRetrievalNode } from '@/interfaces/database/flow';
import { UserOutlined } from '@ant-design/icons';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Avatar, Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { useMemo } from 'react';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -16,7 +16,7 @@ export function RetrievalNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IRetrievalNode>) {
const knowledgeBaseIds: string[] = get(data, 'form.kb_ids', []);
const { theme } = useTheme();
const { list: knowledgeList } = useFetchKnowledgeList(true);

View File

@ -1,9 +1,9 @@
import LLMLabel from '@/components/llm-select/llm-label';
import { useTheme } from '@/components/theme-provider';
import { IRewriteNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { NodeData } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -13,7 +13,7 @@ export function RewriteNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<IRewriteNode>) {
const { theme } = useTheme();
return (
<section

View File

@ -1,9 +1,9 @@
import { useTheme } from '@/components/theme-provider';
import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Divider, Flex } from 'antd';
import classNames from 'classnames';
import { Handle, NodeProps, Position } from 'reactflow';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
import { ISwitchCondition, NodeData } from '../../interface';
import { RightHandleStyle } from './handle-icon';
import { useBuildSwitchHandlePositions } from './hooks';
import styles from './index.less';
@ -54,7 +54,7 @@ const ConditionBlock = ({
);
};
export function SwitchNode({ id, data, selected }: NodeProps<NodeData>) {
export function SwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) {
const { positions } = useBuildSwitchHandlePositions({ data, id });
const { theme } = useTheme();
return (

View File

@ -1,13 +1,14 @@
import { useTheme } from '@/components/theme-provider';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { Handle, NodeProps, Position } from 'reactflow';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
import { IGenerateParameter, NodeData } from '../../interface';
import { IGenerateParameter } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import NodeHeader from './node-header';
import { ITemplateNode } from '@/interfaces/database/flow';
import styles from './index.less';
export function TemplateNode({
@ -15,7 +16,7 @@ export function TemplateNode({
data,
isConnectable = true,
selected,
}: NodeProps<NodeData>) {
}: NodeProps<ITemplateNode>) {
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
const getLabel = useGetComponentLabelByValue(id);
const { theme } = useTheme();