diff --git a/surfsense_backend/app/agents/new_chat/tools/resume.py b/surfsense_backend/app/agents/new_chat/tools/resume.py index 6fd43d830..b1962f8d1 100644 --- a/surfsense_backend/app/agents/new_chat/tools/resume.py +++ b/surfsense_backend/app/agents/new_chat/tools/resume.py @@ -204,8 +204,19 @@ def _get_template(template_id: str | None = None) -> dict[str, str]: _MONTH_NAMES = [ - "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + "", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", ] @@ -452,7 +463,9 @@ def create_generate_resume_tool( ) return failed.id except Exception: - logger.exception("[generate_resume] Could not persist failed report row") + logger.exception( + "[generate_resume] Could not persist failed report row" + ) return None try: @@ -471,7 +484,9 @@ def create_generate_resume_tool( llm = await get_document_summary_llm(read_session, search_space_id) if not llm: - error_msg = "No LLM configured. Please configure a language model in Settings." + error_msg = ( + "No LLM configured. Please configure a language model in Settings." + ) report_id = await _save_failed_report(error_msg) return { "status": "failed", @@ -497,7 +512,8 @@ def create_generate_resume_tool( parent_body = _strip_header(parent_content) prompt = _REVISION_PROMPT.format( llm_reference=llm_reference, - user_instructions=user_instructions or "Improve and refine the resume.", + user_instructions=user_instructions + or "Improve and refine the resume.", previous_content=parent_body, ) else: @@ -553,7 +569,10 @@ def create_generate_resume_tool( if attempt == 0: dispatch_custom_event( "report_progress", - {"phase": "fixing", "message": "Fixing compilation issue..."}, + { + "phase": "fixing", + "message": "Fixing compilation issue...", + }, ) fix_prompt = _FIX_COMPILE_PROMPT.format( llm_reference=llm_reference, @@ -563,7 +582,9 @@ def create_generate_resume_tool( fix_response = await llm.ainvoke( [HumanMessage(content=fix_prompt)] ) - if fix_response.content and isinstance(fix_response.content, str): + if fix_response.content and isinstance( + fix_response.content, str + ): body = _strip_typst_fences(fix_response.content) body = _strip_imports(body) name = _extract_name(body) or name @@ -571,7 +592,9 @@ def create_generate_resume_tool( typst_source = header + body if compile_error: - error_msg = f"Typst compilation failed after 2 attempts: {compile_error}" + error_msg = ( + f"Typst compilation failed after 2 attempts: {compile_error}" + ) report_id = await _save_failed_report(error_msg) return { "status": "failed", diff --git a/surfsense_web/atoms/chat/report-panel.atom.ts b/surfsense_web/atoms/chat/report-panel.atom.ts index 60de50d67..c80230f05 100644 --- a/surfsense_web/atoms/chat/report-panel.atom.ts +++ b/surfsense_web/atoms/chat/report-panel.atom.ts @@ -42,7 +42,13 @@ export const openReportPanelAtom = atom( wordCount, shareToken, contentType, - }: { reportId: number; title: string; wordCount?: number; shareToken?: string | null; contentType?: string } + }: { + reportId: number; + title: string; + wordCount?: number; + shareToken?: string | null; + contentType?: string; + } ) => { if (!get(reportPanelAtom).isOpen) { set(preReportCollapsedAtom, get(rightPanelCollapsedAtom)); diff --git a/surfsense_web/components/report-panel/pdf-viewer.tsx b/surfsense_web/components/report-panel/pdf-viewer.tsx index 71b880324..c4980dd7e 100644 --- a/surfsense_web/components/report-panel/pdf-viewer.tsx +++ b/surfsense_web/components/report-panel/pdf-viewer.tsx @@ -1,9 +1,9 @@ "use client"; import { ZoomInIcon, ZoomOutIcon } from "lucide-react"; -import { useCallback, useEffect, useRef, useState } from "react"; -import * as pdfjsLib from "pdfjs-dist"; import type { PDFDocumentProxy, RenderTask } from "pdfjs-dist"; +import * as pdfjsLib from "pdfjs-dist"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; import { getAuthHeaders } from "@/lib/auth-utils"; @@ -50,7 +50,7 @@ export function PdfViewer({ pdfUrl, isPublic = false }: PdfViewerProps) { const dims = pageDimsRef.current[pageIndex]; return dims ? Math.floor(dims.height * scale) : 0; }, - [scale], + [scale] ); const getVisibleRange = useCallback(() => { @@ -285,14 +285,28 @@ export function PdfViewer({ pdfUrl, isPublic = false }: PdfViewerProps) { return (
{numPages > 0 && ( -
- {Math.round(scale * 100)}% -
@@ -303,7 +317,9 @@ export function PdfViewer({ pdfUrl, isPublic = false }: PdfViewerProps) { className={`relative flex-1 overflow-auto ${isPublic ? "bg-main-panel" : "bg-sidebar"}`} > {loading ? ( -
+
) : ( diff --git a/surfsense_web/components/report-panel/report-panel.tsx b/surfsense_web/components/report-panel/report-panel.tsx index 81f9365a3..591155757 100644 --- a/surfsense_web/components/report-panel/report-panel.tsx +++ b/surfsense_web/components/report-panel/report-panel.tsx @@ -308,42 +308,42 @@ export function ReportPanelContent({ )} - {/* Export — plain button for resume (typst), dropdown for others */} - {reportContent?.content_type === "typst" ? ( - - ) : ( - - - - - handleExport("pdf")} + disabled={isLoading || !reportContent?.content || exporting !== null} + className={`h-8 min-w-[100px] px-3.5 py-4 text-[15px] ${btnBg} select-none`} > - - - - )} + {exporting === "pdf" ? : "Download"} + + ) : ( + + + + + + + + + )} {/* Version switcher — only shown when multiple versions exist */} {versions.length > 1 && ( @@ -395,10 +395,10 @@ export function ReportPanelContent({
) : reportContent.content_type === "typst" ? ( - + ) : reportContent.content ? ( isReadOnly ? (
diff --git a/surfsense_web/components/tool-ui/generate-report.tsx b/surfsense_web/components/tool-ui/generate-report.tsx index eed16a6ac..32f97b6a4 100644 --- a/surfsense_web/components/tool-ui/generate-report.tsx +++ b/surfsense_web/components/tool-ui/generate-report.tsx @@ -99,7 +99,9 @@ function ReportErrorState({ title, error }: { title: string; error: string }) { {title && title !== "Report" && (

{title}

)} -

{error}

+

+ {error} +

); @@ -217,11 +219,11 @@ function ReportCard({
-
); @@ -101,7 +103,15 @@ function ThumbnailSkeleton() { ); } -function PdfThumbnail({ pdfUrl, onLoad, onError }: { pdfUrl: string; onLoad: () => void; onError: () => void }) { +function PdfThumbnail({ + pdfUrl, + onLoad, + onError, +}: { + pdfUrl: string; + onLoad: () => void; + onError: () => void; +}) { const wrapperRef = useRef(null); const canvasRef = useRef(null); const [ready, setReady] = useState(false); @@ -117,13 +127,22 @@ function PdfThumbnail({ pdfUrl, onLoad, onError }: { pdfUrl: string; onLoad: () }); const pdf = await loadingTask.promise; - if (cancelled) { pdf.destroy(); return; } + if (cancelled) { + pdf.destroy(); + return; + } const page = await pdf.getPage(1); - if (cancelled) { pdf.destroy(); return; } + if (cancelled) { + pdf.destroy(); + return; + } const canvas = canvasRef.current; - if (!canvas) { pdf.destroy(); return; } + if (!canvas) { + pdf.destroy(); + return; + } const containerWidth = wrapperRef.current?.clientWidth || 400; const unscaledViewport = page.getViewport({ scale: 1 }); @@ -152,15 +171,14 @@ function PdfThumbnail({ pdfUrl, onLoad, onError }: { pdfUrl: string; onLoad: () }; renderThumbnail(); - return () => { cancelled = true; }; + return () => { + cancelled = true; + }; }, [pdfUrl, onLoad, onError]); return (
- +
); } @@ -294,7 +312,9 @@ export const GenerateResumeToolUI = ({ return ( ); }