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 && (
- ) : (
-
-
-
)}