diff --git a/studio/frontend/src/components/llama-update-banner.tsx b/studio/frontend/src/components/llama-update-banner.tsx index 8919c24cc7..c8a4dd1e40 100644 --- a/studio/frontend/src/components/llama-update-banner.tsx +++ b/studio/frontend/src/components/llama-update-banner.tsx @@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"; import { useLlamaUpdateCheck } from "@/hooks/use-llama-update-check"; import { toast } from "@/lib/toast"; import { AnimatePresence, motion } from "motion/react"; -import { type ReactElement } from "react"; +import { type ReactElement, useEffect, useRef } from "react"; const EASE_OUT_QUART: [number, number, number, number] = [0.165, 0.84, 0.44, 1]; @@ -14,9 +14,9 @@ interface LlamaUpdateBannerProps { } /** - * Non-invasive "Update llama.cpp" affordance. Appears bottom-right when a newer - * prebuilt is available, fades on its own after ~10s, and re-surfaces hourly. - * Clicking Update swaps the prebuilt in place via POST /api/llama/update. + * Non-invasive "Update llama.cpp" affordance. Appears bottom-right ~1s after a + * newer prebuilt is detected and stays up until dismissed (click outside / X) + * or updated. Clicking Update swaps the prebuilt in place via POST /api/llama/update. */ export function LlamaUpdateBanner({ enabled = true, @@ -32,16 +32,38 @@ export function LlamaUpdateBanner({ `llama.cpp updated to ${result.tag ?? "the latest build"}. Reload your model to use it.`, ); } else if (result) { - toast.error(`llama.cpp update failed: ${result.error ?? "unknown error"}`); + toast.error( + `llama.cpp update failed: ${result.error ?? "unknown error"}`, + ); } } - const show = visible && status != null && (status.update_available || applying); + const show = + visible && status != null && (status.update_available || applying); + const bannerRef = useRef(null); + + // Dismiss when the user clicks anything outside the banner. Kept off while an + // update is applying so the progress stays visible. + useEffect(() => { + if (!show || applying) return; + function onPointerDown(event: PointerEvent) { + if ( + bannerRef.current && + !bannerRef.current.contains(event.target as Node) + ) { + dismiss(); + } + } + document.addEventListener("pointerdown", onPointerDown, true); + return () => + document.removeEventListener("pointerdown", onPointerDown, true); + }, [show, applying, dismiss]); return ( {show ? (
- {!applying ? ( + {applying ? null : ( - ) : null} + )}