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 {
|
||||
BaseEdge,
|
||||
Edge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getBezierPath,
|
||||
@ -8,7 +9,9 @@ import useGraphStore from '../../store';
|
||||
|
||||
import { useTheme } from '@/components/theme-provider';
|
||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMemo } from 'react';
|
||||
import { NodeHandleId, Operator } from '../../constant';
|
||||
import styles from './index.less';
|
||||
|
||||
export function ButtonEdge({
|
||||
@ -24,7 +27,9 @@ export function ButtonEdge({
|
||||
style = {},
|
||||
markerEnd,
|
||||
selected,
|
||||
}: EdgeProps) {
|
||||
data,
|
||||
sourceHandleId,
|
||||
}: EdgeProps<Edge<{ isHovered: boolean }>>) {
|
||||
const deleteEdgeById = useGraphStore((state) => state.deleteEdgeById);
|
||||
const [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
@ -72,6 +77,14 @@ export function ButtonEdge({
|
||||
return {};
|
||||
}, [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 (
|
||||
<>
|
||||
<BaseEdge
|
||||
@ -79,6 +92,7 @@ export function ButtonEdge({
|
||||
markerEnd={markerEnd}
|
||||
style={{ ...style, ...selectedStyle, ...highlightStyle }}
|
||||
/>
|
||||
|
||||
<EdgeLabelRenderer>
|
||||
<div
|
||||
style={{
|
||||
@ -93,9 +107,11 @@ export function ButtonEdge({
|
||||
className="nodrag nopan"
|
||||
>
|
||||
<button
|
||||
className={
|
||||
theme === 'dark' ? styles.edgeButtonDark : styles.edgeButton
|
||||
}
|
||||
className={cn(
|
||||
theme === 'dark' ? styles.edgeButtonDark : styles.edgeButton,
|
||||
'invisible',
|
||||
{ visible },
|
||||
)}
|
||||
type="button"
|
||||
onClick={onEdgeClick}
|
||||
>
|
||||
|
||||
@ -84,6 +84,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
onEdgesChange,
|
||||
onNodesChange,
|
||||
onSelectionChange,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseLeave,
|
||||
} = useSelectCanvasData();
|
||||
const isValidConnection = useValidateConnection();
|
||||
|
||||
@ -170,6 +172,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
onSelectionChange={onSelectionChange}
|
||||
nodeOrigin={[0.5, 0]}
|
||||
isValidConnection={isValidConnection}
|
||||
onEdgeMouseEnter={onEdgeMouseEnter}
|
||||
onEdgeMouseLeave={onEdgeMouseLeave}
|
||||
defaultEdgeOptions={{
|
||||
type: 'buttonEdge',
|
||||
markerEnd: 'logo',
|
||||
|
||||
@ -5,14 +5,19 @@ import {
|
||||
} from '@/components/xyflow/tooltip-node';
|
||||
import { Position } from '@xyflow/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 { useDuplicateNode } from '../../hooks';
|
||||
import useGraphStore from '../../store';
|
||||
|
||||
function IconWrapper({ children }: PropsWithChildren) {
|
||||
function IconWrapper({ children, ...props }: HTMLAttributes<HTMLDivElement>) {
|
||||
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}
|
||||
</div>
|
||||
);
|
||||
@ -30,7 +35,7 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
||||
(store) => store.deleteIterationNodeById,
|
||||
);
|
||||
|
||||
const deleteNode: MouseEventHandler<SVGElement> = useCallback(
|
||||
const deleteNode: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (label === Operator.Iteration) {
|
||||
@ -44,7 +49,7 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
||||
|
||||
const duplicateNode = useDuplicateNode();
|
||||
|
||||
const handleDuplicate: MouseEventHandler<SVGElement> = useCallback(
|
||||
const handleDuplicate: MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
duplicateNode(id, label);
|
||||
@ -61,11 +66,11 @@ export function ToolBar({ selected, children, label, id }: ToolBarProps) {
|
||||
<IconWrapper>
|
||||
<Play className="size-3.5" />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Copy className="size-3.5" onClick={handleDuplicate} />
|
||||
<IconWrapper onClick={handleDuplicate}>
|
||||
<Copy className="size-3.5" />
|
||||
</IconWrapper>
|
||||
<IconWrapper>
|
||||
<Trash2 className="size-3.5" onClick={deleteNode} />
|
||||
<IconWrapper onClick={deleteNode}>
|
||||
<Trash2 className="size-3.5" />
|
||||
</IconWrapper>
|
||||
</section>
|
||||
</TooltipContent>
|
||||
|
||||
@ -90,6 +90,8 @@ const selector = (state: RFState) => ({
|
||||
onConnect: state.onConnect,
|
||||
setNodes: state.setNodes,
|
||||
onSelectionChange: state.onSelectionChange,
|
||||
onEdgeMouseEnter: state.onEdgeMouseEnter,
|
||||
onEdgeMouseLeave: state.onEdgeMouseLeave,
|
||||
});
|
||||
|
||||
export const useSelectCanvasData = () => {
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
EdgeMouseHandler,
|
||||
OnConnect,
|
||||
OnEdgesChange,
|
||||
OnNodesChange,
|
||||
@ -27,6 +28,7 @@ import {
|
||||
generateNodeNamesWithIncreasingIndex,
|
||||
getOperatorIndex,
|
||||
isEdgeEqual,
|
||||
mapEdgeMouseEvent,
|
||||
} from './utils';
|
||||
|
||||
export type RFState = {
|
||||
@ -38,6 +40,9 @@ export type RFState = {
|
||||
clickedToolId: string; // currently selected tool id
|
||||
onNodesChange: OnNodesChange<RAGFlowNodeType>;
|
||||
onEdgesChange: OnEdgesChange;
|
||||
onEdgeMouseEnter?: EdgeMouseHandler<Edge>;
|
||||
/** This event handler is called when mouse of a user leaves an edge */
|
||||
onEdgeMouseLeave?: EdgeMouseHandler<Edge>;
|
||||
onConnect: OnConnect;
|
||||
setNodes: (nodes: RAGFlowNodeType[]) => void;
|
||||
setEdges: (edges: Edge[]) => void;
|
||||
@ -98,6 +103,20 @@ const useGraphStore = create<RFState>()(
|
||||
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) => {
|
||||
const {
|
||||
deletePreviousEdgeOfClassificationNode,
|
||||
|
||||
@ -466,3 +466,23 @@ export function getAgentNodeTools(agentNode?: RAGFlowNodeType) {
|
||||
const tools: IAgentForm['tools'] = get(agentNode, 'data.form.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