diff --git a/webui/src/components/thread/PromptRail.tsx b/webui/src/components/thread/PromptRail.tsx index b7278f3f..6714ed2c 100644 --- a/webui/src/components/thread/PromptRail.tsx +++ b/webui/src/components/thread/PromptRail.tsx @@ -1,11 +1,4 @@ -import { - type RefObject, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { cn } from "@/lib/utils"; import type { UIMessage } from "@/lib/types"; @@ -29,6 +22,7 @@ interface MeasuredPrompt extends PromptAnchor { } interface PromptMarker { + answerPreview: string; count: number; ids: string[]; label: string; @@ -43,10 +37,11 @@ const DENSE_BUCKET_HEIGHT_PX = 12; const DENSE_BUCKET_FALLBACK_COUNT = 32; const DENSE_BUCKET_MAX_COUNT = 42; const MARKER_MIN_GAP_PX = 9; -const MARKER_BASE_WIDTH_PX = 16; -const MARKER_MAX_WIDTH_PX = 28; +const MARKER_BASE_WIDTH_PX = 9; +const MARKER_STACK_GAP_PX = 16; +const RAIL_FALLBACK_HEIGHT_PX = 300; const MEASURE_RETRY_FRAMES = 4; -const RAIL_REVEAL_MS = 1400; +const HOVER_MARKER_WIDTHS_PX = [28, 22, 16, 11]; export function PromptRail({ bottomOffset, @@ -57,22 +52,12 @@ export function PromptRail({ const promptAnchors = useMemo(() => userPromptAnchors(messages), [messages]); const [markers, setMarkers] = useState([]); const [activePromptId, setActivePromptId] = useState(null); - const [revealed, setRevealed] = useState(false); - const revealTimeoutRef = useRef(null); - - const revealTemporarily = useCallback(() => { - setRevealed(true); - if (revealTimeoutRef.current !== null) { - window.clearTimeout(revealTimeoutRef.current); - } - revealTimeoutRef.current = window.setTimeout(() => { - setRevealed(false); - revealTimeoutRef.current = null; - }, RAIL_REVEAL_MS); - }, []); + const [focusedMarkerIndex, setFocusedMarkerIndex] = useState(null); const updateMarkers = useCallback(() => { const scrollEl = scrollRef.current; + const nextRailHeight = railRef.current?.clientHeight ?? 0; + if (!scrollEl || promptAnchors.length < MIN_PROMPTS_FOR_RAIL) { setMarkers([]); setActivePromptId(null); @@ -87,7 +72,8 @@ export function PromptRail({ } const measured = measurePrompts(scrollEl, promptAnchors, scrollRange); - setMarkers(groupPromptMarkers(measured, railRef.current?.clientHeight ?? 0)); + const grouped = groupPromptMarkers(measured, nextRailHeight); + setMarkers(distributeMarkerPositions(grouped, nextRailHeight)); setActivePromptId(activePromptForScroll(measured, scrollEl.scrollTop)); }, [promptAnchors, scrollRef]); @@ -112,7 +98,6 @@ export function PromptRail({ let frame = 0; const schedule = () => { window.cancelAnimationFrame(frame); - revealTemporarily(); frame = window.requestAnimationFrame(updateMarkers); }; @@ -123,7 +108,7 @@ export function PromptRail({ scrollEl.removeEventListener("scroll", schedule); window.removeEventListener("resize", schedule); }; - }, [revealTemporarily, scrollRef, updateMarkers]); + }, [scrollRef, updateMarkers]); useEffect(() => { const scrollEl = scrollRef.current; @@ -134,77 +119,72 @@ export function PromptRail({ return () => observer.disconnect(); }, [scrollRef, updateMarkers]); - useEffect(() => { - return () => { - if (revealTimeoutRef.current !== null) { - window.clearTimeout(revealTimeoutRef.current); - } - }; - }, []); - if (markers.length === 0) return null; - const maxMarkerCount = Math.max(...markers.map((marker) => marker.count)); - const activeMarkerIndex = markers.findIndex((marker) => - marker.ids.includes(activePromptId ?? ""), - ); - return (