diff --git a/surfsense_web/components/tool-ui/generate-resume.tsx b/surfsense_web/components/tool-ui/generate-resume.tsx new file mode 100644 index 000000000..ffd21b07b --- /dev/null +++ b/surfsense_web/components/tool-ui/generate-resume.tsx @@ -0,0 +1,233 @@ +"use client"; + +import type { ToolCallMessagePartProps } from "@assistant-ui/react"; +import { useAtomValue, useSetAtom } from "jotai"; +import { FileTextIcon } from "lucide-react"; +import { useParams, usePathname } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { z } from "zod"; +import { openReportPanelAtom, reportPanelAtom } from "@/atoms/chat/report-panel.atom"; +import { TextShimmerLoader } from "@/components/prompt-kit/loader"; +import { useMediaQuery } from "@/hooks/use-media-query"; + +const GenerateResumeArgsSchema = z.object({ + user_info: z.string(), + user_instructions: z.string().nullish(), + parent_report_id: z.number().nullish(), +}); + +const GenerateResumeResultSchema = z.object({ + status: z.enum(["ready", "failed"]), + report_id: z.number().nullish(), + title: z.string().nullish(), + content_type: z.string().nullish(), + message: z.string().nullish(), + error: z.string().nullish(), +}); + +type GenerateResumeArgs = z.infer; +type GenerateResumeResult = z.infer; + +function ResumeGeneratingState() { + return ( +
+
+
+

Resume

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+ ); +} + +function ResumeErrorState({ title, error }: { title: string; error: string }) { + return ( +
+
+
+ +

Resume Generation Failed

+
+
+
+
+

{title}

+

{error}

+
+
+ ); +} + +function ResumeCancelledState() { + return ( +
+
+
+ +

Resume Cancelled

+
+

Resume generation was cancelled

+
+
+ ); +} + +function ResumeCard({ + reportId, + title, + shareToken, + autoOpen = false, +}: { + reportId: number; + title: string; + shareToken?: string | null; + autoOpen?: boolean; +}) { + const openPanel = useSetAtom(openReportPanelAtom); + const panelState = useAtomValue(reportPanelAtom); + const isDesktop = useMediaQuery("(min-width: 768px)"); + const autoOpenedRef = useRef(false); + const [pdfThumbnailUrl, setPdfThumbnailUrl] = useState(null); + + useEffect(() => { + setPdfThumbnailUrl( + `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/reports/${reportId}/preview` + ); + + if (autoOpen && isDesktop && !autoOpenedRef.current) { + autoOpenedRef.current = true; + openPanel({ + reportId, + title, + shareToken, + contentType: "typst", + }); + } + }, [reportId, title, shareToken, autoOpen, isDesktop, openPanel]); + + const isActive = panelState.isOpen && panelState.reportId === reportId; + + const handleOpen = () => { + openPanel({ + reportId, + title, + shareToken, + contentType: "typst", + }); + }; + + return ( +
+ {/* biome-ignore lint/a11y/useSemanticElements: nested interactive content */} +
{ + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + handleOpen(); + } + }} + className="w-full text-left transition-colors hover:bg-muted/50 focus:outline-none focus-visible:outline-none cursor-pointer" + > +
+
+ +

{title}

+
+

Resume • Click to preview

+
+ +
+ +
+ {pdfThumbnailUrl ? ( +
+ + PDF Resume ready — click to view +
+ ) : ( +
+
+
+
+
+ )} +
+
+
+ ); +} + +export const GenerateResumeToolUI = ({ + result, + status, +}: ToolCallMessagePartProps) => { + const params = useParams(); + const pathname = usePathname(); + const isPublicRoute = pathname?.startsWith("/public/"); + const shareToken = isPublicRoute && typeof params?.token === "string" ? params.token : null; + + const sawRunningRef = useRef(false); + if (status.type === "running" || status.type === "requires-action") { + sawRunningRef.current = true; + } + + if (status.type === "running" || status.type === "requires-action") { + return ; + } + + if (status.type === "incomplete") { + if (status.reason === "cancelled") { + return ; + } + if (status.reason === "error") { + return ( + + ); + } + } + + if (!result) { + return ; + } + + if (result.status === "failed") { + return ( + + ); + } + + if (result.status === "ready" && result.report_id) { + return ( + + ); + } + + return ; +};