BranchIndicator.tsx (2227B)
1 import { useState, useRef, useEffect, useCallback } from "react"; 2 3 export interface Branch { 4 session_id: string; 5 parent_session_id: string; 6 fork_message_index: number; 7 } 8 9 interface BranchIndicatorProps { 10 branches: Branch[]; 11 currentSessionId: string; 12 onSwitchBranch?: (sessionId: string) => void; 13 } 14 15 function branchCountText(count: number): string { 16 return count === 1 ? "1 branch" : `${count} branches`; 17 } 18 19 export function BranchIndicator({ 20 branches, 21 currentSessionId, 22 onSwitchBranch, 23 }: BranchIndicatorProps) { 24 const [open, setOpen] = useState(false); 25 const ref = useRef<HTMLDivElement>(null); 26 27 const handleClickOutside = useCallback((e: MouseEvent) => { 28 if (ref.current && !ref.current.contains(e.target as Node)) { 29 setOpen(false); 30 } 31 }, []); 32 33 useEffect(() => { 34 document.addEventListener("mousedown", handleClickOutside); 35 return () => document.removeEventListener("mousedown", handleClickOutside); 36 }, [handleClickOutside]); 37 38 if (branches.length === 0) return null; 39 40 return ( 41 <div ref={ref} className="relative inline-block"> 42 <button 43 type="button" 44 onClick={() => setOpen(!open)} 45 className="text-xs text-blue-400 hover:text-blue-300 transition-colors" 46 > 47 ⑂ {branchCountText(branches.length)} 48 </button> 49 50 {open && ( 51 <div className="absolute left-0 top-full z-10 mt-1 w-48 rounded-lg border border-gray-700 bg-gray-900 shadow-lg"> 52 {branches.map((branch) => ( 53 <button 54 key={branch.session_id} 55 type="button" 56 onClick={() => { 57 onSwitchBranch?.(branch.session_id); 58 setOpen(false); 59 }} 60 className={`block w-full px-3 py-2 text-left text-xs transition-colors ${ 61 branch.session_id === currentSessionId 62 ? "bg-blue-600/20 text-blue-300" 63 : "text-gray-300 hover:bg-gray-800" 64 }`} 65 > 66 {branch.session_id} 67 <span className="ml-1 text-gray-500"> 68 (msg {branch.fork_message_index}) 69 </span> 70 </button> 71 ))} 72 </div> 73 )} 74 </div> 75 ); 76 }