"use client"; import { useWorkflowStore } from "@/store/workflowStore"; import { WorkflowEdgeData } from "@/types"; import { useMemo, useEffect, useState, useRef } from "react"; export function EdgeToolbar() { const { edges, toggleEdgePause, removeEdge, setLoopCount } = useWorkflowStore(); const [clickPosition, setClickPosition] = useState<{ x: number; y: number } | null>(null); const previousSelectedEdgeId = useRef(null); const selectedEdge = useMemo( () => edges.find((edge) => edge.selected), [edges] ); // Helper function to compute the image connection sequence number const getImageSequenceNumber = (edge: typeof selectedEdge): number | null => { if (!edge) return null; // Only show for image connections const sourceHandle = edge.sourceHandle; const targetHandle = edge.targetHandle; const isImageConnection = (sourceHandle === "image" || sourceHandle?.startsWith("image-")) || (targetHandle === "image" || targetHandle?.startsWith("image-")); if (!isImageConnection) return null; // Find all image edges going to the same target + target handle const siblingEdges = edges.filter( (e) => e.target === edge.target && e.targetHandle === edge.targetHandle ); // If only one connection, no need for numbering if (siblingEdges.length <= 1) return null; // Sort by createdAt timestamp (fallback to edge ID for legacy edges without timestamp) const sorted = [...siblingEdges].sort((a, b) => { const timeA = (a.data as WorkflowEdgeData)?.createdAt || 0; const timeB = (b.data as WorkflowEdgeData)?.createdAt || 0; if (timeA !== timeB) return timeA - timeB; return a.id.localeCompare(b.id); }); const index = sorted.findIndex((e) => e.id === edge.id); return index >= 0 ? index + 1 : null; }; const sequenceNumber = getImageSequenceNumber(selectedEdge); // Track mouse position when edge selection changes useEffect(() => { const handleMouseDown = (e: MouseEvent) => { // Check if clicking on an edge const target = e.target as Element; if (target.closest('.react-flow__edge')) { setClickPosition({ x: e.clientX, y: e.clientY - 40 }); // 40px above click } }; document.addEventListener('mousedown', handleMouseDown); return () => document.removeEventListener('mousedown', handleMouseDown); }, []); // Reset click position when edge is deselected useEffect(() => { if (!selectedEdge && previousSelectedEdgeId.current) { setClickPosition(null); } previousSelectedEdgeId.current = selectedEdge?.id || null; }, [selectedEdge]); const toolbarPosition = clickPosition; const handleTogglePause = () => { if (selectedEdge) { toggleEdgePause(selectedEdge.id); } }; const handleLoopCountChange = (delta: number) => { if (selectedEdge) { setLoopCount(selectedEdge.id, loopCount + delta); } }; const handleDelete = () => { if (selectedEdge) { removeEdge(selectedEdge.id); } }; if (!toolbarPosition || !selectedEdge) return null; const hasPause = selectedEdge.data?.hasPause; const isLoop = selectedEdge.data?.isLoop; const loopCount = selectedEdge.data?.loopCount ?? 3; return (
{sequenceNumber !== null && ( Image {sequenceNumber} )} {isLoop && ( <> Loop {loopCount}
)} {!isLoop && ( )}
); }