feat(webui): polish agent output and app discovery
This commit is contained in:
@@ -49,6 +49,7 @@ export function PromptRail({
|
||||
scrollRef,
|
||||
}: PromptRailProps) {
|
||||
const railRef = useRef<HTMLDivElement>(null);
|
||||
const measuredPromptsRef = useRef<MeasuredPrompt[]>([]);
|
||||
const promptAnchors = useMemo(() => userPromptAnchors(messages), [messages]);
|
||||
const [markers, setMarkers] = useState<PromptMarker[]>([]);
|
||||
const [activePromptId, setActivePromptId] = useState<string | null>(null);
|
||||
@@ -59,6 +60,7 @@ export function PromptRail({
|
||||
const nextRailHeight = railRef.current?.clientHeight ?? 0;
|
||||
|
||||
if (!scrollEl || promptAnchors.length < MIN_PROMPTS_FOR_RAIL) {
|
||||
measuredPromptsRef.current = [];
|
||||
setMarkers([]);
|
||||
setActivePromptId(null);
|
||||
return;
|
||||
@@ -66,17 +68,26 @@ export function PromptRail({
|
||||
|
||||
const scrollRange = scrollEl.scrollHeight - scrollEl.clientHeight;
|
||||
if (scrollRange < RAIL_MIN_SCROLL_RANGE_PX) {
|
||||
measuredPromptsRef.current = [];
|
||||
setMarkers([]);
|
||||
setActivePromptId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const measured = measurePrompts(scrollEl, promptAnchors, scrollRange);
|
||||
measuredPromptsRef.current = measured;
|
||||
const grouped = groupPromptMarkers(measured, nextRailHeight);
|
||||
setMarkers(distributeMarkerPositions(grouped, nextRailHeight));
|
||||
setActivePromptId(activePromptForScroll(measured, scrollEl.scrollTop));
|
||||
}, [promptAnchors, scrollRef]);
|
||||
|
||||
const updateActivePrompt = useCallback(() => {
|
||||
const scrollEl = scrollRef.current;
|
||||
if (!scrollEl) return;
|
||||
const next = activePromptForScroll(measuredPromptsRef.current, scrollEl.scrollTop);
|
||||
setActivePromptId((current) => current === next ? current : next);
|
||||
}, [scrollRef]);
|
||||
|
||||
useEffect(() => {
|
||||
let frame = 0;
|
||||
let remainingFrames = MEASURE_RETRY_FRAMES;
|
||||
@@ -95,20 +106,26 @@ export function PromptRail({
|
||||
const scrollEl = scrollRef.current;
|
||||
if (!scrollEl) return undefined;
|
||||
|
||||
let frame = 0;
|
||||
const schedule = () => {
|
||||
window.cancelAnimationFrame(frame);
|
||||
frame = window.requestAnimationFrame(updateMarkers);
|
||||
let scrollFrame = 0;
|
||||
let resizeFrame = 0;
|
||||
const scheduleActivePrompt = () => {
|
||||
window.cancelAnimationFrame(scrollFrame);
|
||||
scrollFrame = window.requestAnimationFrame(updateActivePrompt);
|
||||
};
|
||||
const scheduleMeasurement = () => {
|
||||
window.cancelAnimationFrame(resizeFrame);
|
||||
resizeFrame = window.requestAnimationFrame(updateMarkers);
|
||||
};
|
||||
|
||||
scrollEl.addEventListener("scroll", schedule, { passive: true });
|
||||
window.addEventListener("resize", schedule);
|
||||
scrollEl.addEventListener("scroll", scheduleActivePrompt, { passive: true });
|
||||
window.addEventListener("resize", scheduleMeasurement);
|
||||
return () => {
|
||||
window.cancelAnimationFrame(frame);
|
||||
scrollEl.removeEventListener("scroll", schedule);
|
||||
window.removeEventListener("resize", schedule);
|
||||
window.cancelAnimationFrame(scrollFrame);
|
||||
window.cancelAnimationFrame(resizeFrame);
|
||||
scrollEl.removeEventListener("scroll", scheduleActivePrompt);
|
||||
window.removeEventListener("resize", scheduleMeasurement);
|
||||
};
|
||||
}, [scrollRef, updateMarkers]);
|
||||
}, [scrollRef, updateActivePrompt, updateMarkers]);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollEl = scrollRef.current;
|
||||
@@ -309,16 +326,20 @@ function activePromptForScroll(
|
||||
scrollTop: number,
|
||||
): string | null {
|
||||
if (measured.length === 0) return null;
|
||||
let active = measured[0];
|
||||
const cursor = scrollTop + 96;
|
||||
for (const prompt of measured) {
|
||||
if (prompt.top <= cursor) {
|
||||
active = prompt;
|
||||
continue;
|
||||
let lower = 0;
|
||||
let upper = measured.length - 1;
|
||||
let activeIndex = 0;
|
||||
while (lower <= upper) {
|
||||
const middle = Math.floor((lower + upper) / 2);
|
||||
if (measured[middle].top <= cursor) {
|
||||
activeIndex = middle;
|
||||
lower = middle + 1;
|
||||
} else {
|
||||
upper = middle - 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return active.id;
|
||||
return measured[activeIndex].id;
|
||||
}
|
||||
|
||||
function groupedPromptLabel(count: number, latestLabel: string): string {
|
||||
|
||||
Reference in New Issue
Block a user