mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Feat: The delete button is displayed only when the cursor is hovered over the connection line #3221 (#8422)
### What problem does this PR solve? Feat: The delete button is displayed only when the cursor is hovered over the connection line #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BaseEdge,
|
BaseEdge,
|
||||||
|
Edge,
|
||||||
EdgeLabelRenderer,
|
EdgeLabelRenderer,
|
||||||
EdgeProps,
|
EdgeProps,
|
||||||
getBezierPath,
|
getBezierPath,
|
||||||
@ -8,7 +9,9 @@ import useGraphStore from '../../store';
|
|||||||
|
|
||||||
import { useTheme } from '@/components/theme-provider';
|
import { useTheme } from '@/components/theme-provider';
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { NodeHandleId, Operator } from '../../constant';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
export function ButtonEdge({
|
export function ButtonEdge({
|
||||||
@ -24,7 +27,9 @@ export function ButtonEdge({
|
|||||||
style = {},
|
style = {},
|
||||||
markerEnd,
|
markerEnd,
|
||||||
selected,
|
selected,
|
||||||
}: EdgeProps) {
|
data,
|
||||||
|
sourceHandleId,
|
||||||
|
}: EdgeProps<Edge<{ isHovered: boolean }>>) {
|
||||||
const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
|
const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
|
||||||
const [edgePath, labelX, labelY] = getBezierPath({
|
const [edgePath, labelX, labelY] = getBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
@ -72,6 +77,14 @@ export function ButtonEdge({
|
|||||||
return {};
|
return {};
|
||||||
}, [source, target, graphPath]);
|
}, [source, target, graphPath]);
|
||||||
|
|
||||||
|
const visible = useMemo(() => {
|
||||||
|
return (
|
||||||
|
data?.isHovered &&
|
||||||
|
sourceHandleId !== NodeHandleId.Tool && // The connection between the agent node and the tool node does not need to display the delete button
|
||||||
|
!target.startsWith(Operator.Tool)
|
||||||
|
);
|
||||||
|
}, [data?.isHovered, sourceHandleId, target]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<BaseEdge
|
<BaseEdge
|
||||||
@ -79,6 +92,7 @@ export function ButtonEdge({
|
|||||||
markerEnd={markerEnd}
|
markerEnd={markerEnd}
|
||||||
style={{ ...style, ...selectedStyle, ...highlightStyle }}
|
style={{ ...style, ...selectedStyle, ...highlightStyle }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EdgeLabelRenderer>
|
<EdgeLabelRenderer>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -93,9 +107,11 @@ export function ButtonEdge({
|
|||||||
className="nodrag nopan"
|
className="nodrag nopan"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
className={
|
className={cn(
|
||||||
theme === 'dark' ? styles.edgeButtonDark : styles.edgeButton
|
theme === 'dark' ? styles.edgeButtonDark : styles.edgeButton,
|
||||||
}
|
'invisible',
|
||||||
|
{ visible },
|
||||||
|
)}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onEdgeClick}
|
onClick={onEdgeClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -84,6 +84,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
onEdgesChange,
|
onEdgesChange,
|
||||||
onNodesChange,
|
onNodesChange,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
|
onEdgeMouseEnter,
|
||||||
|
onEdgeMouseLeave,
|
||||||
} = useSelectCanvasData();
|
} = useSelectCanvasData();
|
||||||
const isValidConnection = useValidateConnection();
|
const isValidConnection = useValidateConnection();
|
||||||
|
|
||||||
@ -170,6 +172,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
onSelectionChange={onSelectionChange}
|
onSelectionChange={onSelectionChange}
|
||||||
nodeOrigin={[0.5, 0]}
|
nodeOrigin={[0.5, 0]}
|
||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
|
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||||
|
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||||
defaultEdgeOptions={{
|
defaultEdgeOptions={{
|
||||||
type: 'buttonEdge',
|
type: 'buttonEdge',
|
||||||
markerEnd: 'logo',
|
markerEnd: 'logo',
|
||||||
|
|||||||
@ -5,14 +5,19 @@ import {
|
|||||||
} from '@/components/xyflow/tooltip-node';
|
} from '@/components/xyflow/tooltip-node';
|
||||||
import { Position } from '@xyflow/react';
|
import { Position } from '@xyflow/react';
|
||||||
import { Copy, Play, Trash2 } from 'lucide-react';
|
import { Copy, Play, Trash2 } from 'lucide-react';
|
||||||
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
|
import {
|
||||||
|
HTMLAttributes,
|
||||||
|
MouseEventHandler,
|
||||||
|
PropsWithChildren,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
import { Operator } from '../../constant';
|
import { Operator } from '../../constant';
|
||||||
import { useDuplicateNode } from '../../hooks';
|
import { useDuplicateNode } from '../../hooks';
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
function IconWrapper({ children }: PropsWithChildren) {
|
function IconWrapper({ children, ...props }: HTMLAttributes<HTMLDivElement>) {
|
||||||
return (
|
return (
|
||||||
<div className="p-1.5 bg-text-title rounded-sm cursor-pointer">
|
<div className="p-1.5 bg-text-title rounded-sm cursor-pointer" {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -30,7 +35,7 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
|||||||
(store) => store.deleteIterationNodeById,
|
(store) => store.deleteIterationNodeById,
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteNode: MouseEventHandler<SVGElement> = useCallback(
|
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (label === Operator.Iteration) {
|
if (label === Operator.Iteration) {
|
||||||
@ -44,7 +49,7 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
|||||||
|
|
||||||
const duplicateNode = useDuplicateNode();
|
const duplicateNode = useDuplicateNode();
|
||||||
|
|
||||||
const handleDuplicate: MouseEventHandler<SVGElement> = useCallback(
|
const handleDuplicate: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
duplicateNode(id, label);
|
duplicateNode(id, label);
|
||||||
@ -61,11 +66,11 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
|||||||
<IconWrapper>
|
<IconWrapper>
|
||||||
<Play className="size-3.5" />
|
<Play className="size-3.5" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<IconWrapper>
|
<IconWrapper onClick={handleDuplicate}>
|
||||||
<Copy className="size-3.5" onClick={handleDuplicate} />
|
<Copy className="size-3.5" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
<IconWrapper>
|
<IconWrapper onClick={deleteNode}>
|
||||||
<Trash2 className="size-3.5" onClick={deleteNode} />
|
<Trash2 className="size-3.5" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
</section>
|
</section>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
|
|||||||
@ -90,6 +90,8 @@ const selector = (state: RFState) => ({
|
|||||||
onConnect: state.onConnect,
|
onConnect: state.onConnect,
|
||||||
setNodes: state.setNodes,
|
setNodes: state.setNodes,
|
||||||
onSelectionChange: state.onSelectionChange,
|
onSelectionChange: state.onSelectionChange,
|
||||||
|
onEdgeMouseEnter: state.onEdgeMouseEnter,
|
||||||
|
onEdgeMouseLeave: state.onEdgeMouseLeave,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useSelectCanvasData = () => {
|
export const useSelectCanvasData = () => {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
|
EdgeMouseHandler,
|
||||||
OnConnect,
|
OnConnect,
|
||||||
OnEdgesChange,
|
OnEdgesChange,
|
||||||
OnNodesChange,
|
OnNodesChange,
|
||||||
@ -27,6 +28,7 @@ import {
|
|||||||
generateNodeNamesWithIncreasingIndex,
|
generateNodeNamesWithIncreasingIndex,
|
||||||
getOperatorIndex,
|
getOperatorIndex,
|
||||||
isEdgeEqual,
|
isEdgeEqual,
|
||||||
|
mapEdgeMouseEvent,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
export type RFState = {
|
export type RFState = {
|
||||||
@ -38,6 +40,9 @@ export type RFState = {
|
|||||||
clickedToolId: string; // currently selected tool id
|
clickedToolId: string; // currently selected tool id
|
||||||
onNodesChange: OnNodesChange<RAGFlowNodeType>;
|
onNodesChange: OnNodesChange<RAGFlowNodeType>;
|
||||||
onEdgesChange: OnEdgesChange;
|
onEdgesChange: OnEdgesChange;
|
||||||
|
onEdgeMouseEnter?: EdgeMouseHandler<Edge>;
|
||||||
|
/** This event handler is called when mouse of a user leaves an edge */
|
||||||
|
onEdgeMouseLeave?: EdgeMouseHandler<Edge>;
|
||||||
onConnect: OnConnect;
|
onConnect: OnConnect;
|
||||||
setNodes: (nodes: RAGFlowNodeType[]) => void;
|
setNodes: (nodes: RAGFlowNodeType[]) => void;
|
||||||
setEdges: (edges: Edge[]) => void;
|
setEdges: (edges: Edge[]) => void;
|
||||||
@ -98,6 +103,20 @@ const useGraphStore = create<RFState>()(
|
|||||||
edges: applyEdgeChanges(changes, get().edges),
|
edges: applyEdgeChanges(changes, get().edges),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onEdgeMouseEnter: (event, edge) => {
|
||||||
|
const { edges, setEdges } = get();
|
||||||
|
const edgeId = edge.id;
|
||||||
|
|
||||||
|
// Updates edge
|
||||||
|
setEdges(mapEdgeMouseEvent(edges, edgeId, true));
|
||||||
|
},
|
||||||
|
onEdgeMouseLeave: (event, edge) => {
|
||||||
|
const { edges, setEdges } = get();
|
||||||
|
const edgeId = edge.id;
|
||||||
|
|
||||||
|
// Updates edge
|
||||||
|
setEdges(mapEdgeMouseEvent(edges, edgeId, false));
|
||||||
|
},
|
||||||
onConnect: (connection: Connection) => {
|
onConnect: (connection: Connection) => {
|
||||||
const {
|
const {
|
||||||
deletePreviousEdgeOfClassificationNode,
|
deletePreviousEdgeOfClassificationNode,
|
||||||
|
|||||||
@ -466,3 +466,23 @@ export function getAgentNodeTools(agentNode?: RAGFlowNodeType) {
|
|||||||
const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []);
|
const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []);
|
||||||
return tools;
|
return tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapEdgeMouseEvent(
|
||||||
|
edges: Edge[],
|
||||||
|
edgeId: string,
|
||||||
|
isHovered: boolean,
|
||||||
|
) {
|
||||||
|
const nextEdges = edges.map((element) =>
|
||||||
|
element.id === edgeId
|
||||||
|
? {
|
||||||
|
...element,
|
||||||
|
data: {
|
||||||
|
...element.data,
|
||||||
|
isHovered,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: element,
|
||||||
|
);
|
||||||
|
|
||||||
|
return nextEdges;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user