diff --git a/surfsense_web/components/tool-ui/generate-resume.tsx b/surfsense_web/components/tool-ui/generate-resume.tsx index d540c7d18..cc00ce3e4 100644 --- a/surfsense_web/components/tool-ui/generate-resume.tsx +++ b/surfsense_web/components/tool-ui/generate-resume.tsx @@ -3,12 +3,11 @@ import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useAtomValue, useSetAtom } from "jotai"; import { useParams, usePathname } from "next/navigation"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import * as pdfjsLib from "pdfjs-dist"; import { z } from "zod"; import { openReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; -import { Spinner } from "@/components/ui/spinner"; import { useMediaQuery } from "@/hooks/use-media-query"; import { getAuthHeaders } from "@/lib/auth-utils"; @@ -88,10 +87,22 @@ function ResumeCancelledState() { ); } -function PdfThumbnail({ pdfUrl }: { pdfUrl: string }) { +function ThumbnailSkeleton() { + return ( +
+
+
+
+
+
+
+ ); +} + +function PdfThumbnail({ pdfUrl, onLoad, onError }: { pdfUrl: string; onLoad: () => void; onError: () => void }) { + const wrapperRef = useRef(null); const canvasRef = useRef(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(false); + const [ready, setReady] = useState(false); useEffect(() => { let cancelled = false; @@ -112,53 +123,43 @@ function PdfThumbnail({ pdfUrl }: { pdfUrl: string }) { const canvas = canvasRef.current; if (!canvas) { pdf.destroy(); return; } - const containerWidth = canvas.parentElement?.clientWidth || 400; + const containerWidth = wrapperRef.current?.clientWidth || 400; const unscaledViewport = page.getViewport({ scale: 1 }); const fitScale = containerWidth / unscaledViewport.width; const viewport = page.getViewport({ scale: fitScale }); const dpr = window.devicePixelRatio || 1; - canvas.width = Math.floor(viewport.width * dpr); - canvas.height = Math.floor(viewport.height * dpr); - canvas.style.width = `${Math.floor(viewport.width)}px`; - canvas.style.height = `${Math.floor(viewport.height)}px`; + canvas.width = Math.ceil(viewport.width * dpr); + canvas.height = Math.ceil(viewport.height * dpr); await page.render({ canvas, viewport, transform: dpr !== 1 ? [dpr, 0, 0, dpr, 0, 0] : undefined, }).promise; - if (!cancelled) setLoading(false); + + if (!cancelled) { + setReady(true); + onLoad(); + } pdf.destroy(); } catch { - if (!cancelled) { - setError(true); - setLoading(false); - } + if (!cancelled) onError(); } }; renderThumbnail(); return () => { cancelled = true; }; - }, [pdfUrl]); - - if (error) { - return

Preview unavailable

; - } + }, [pdfUrl, onLoad, onError]); return ( - <> - {loading && ( -
- -
- )} +
- +
); } @@ -178,6 +179,7 @@ function ResumeCard({ const isDesktop = useMediaQuery("(min-width: 768px)"); const autoOpenedRef = useRef(false); const [pdfUrl, setPdfUrl] = useState(null); + const [thumbState, setThumbState] = useState<"loading" | "ready" | "error">("loading"); useEffect(() => { setPdfUrl( @@ -195,6 +197,9 @@ function ResumeCard({ } }, [reportId, title, shareToken, autoOpen, isDesktop, openPanel]); + const onThumbLoad = useCallback(() => setThumbState("ready"), []); + const onThumbError = useCallback(() => setThumbState("error"), []); + const isActive = panelState.isOpen && panelState.reportId === reportId; const handleOpen = () => { @@ -223,22 +228,22 @@ function ResumeCard({
- {pdfUrl ? ( + {thumbState === "loading" && } + {thumbState === "error" && ( +

Preview unavailable

+ )} + {pdfUrl && (
- +
- ) : ( -
- -
)}