typescript - 使用 Puppeteer 的 HTML 转 PDF 上传到 Supabase 后 PDF 无法打开
scan/[scanID]/page.tsx"use client";
import Navbar from "@/components/navbar";
import { Button } from "@/components/ui/button";
import { Check } from "lucide-react";
import React, { useEffect, useState } from "react";
import { findScanById, generatePDF, uploadPDF } from "../actions";
import { redirect } from "next/navigation";
import { handleDownload } from "@/utils/handleDownload";
import { createClerkSupabaseClient } from "@/utils/supabase/client";
const ScanDetails = ({ params }: { params: { scanId: string } }) => {
const [pdfBlob, setPdfBlob] = useState<Blob | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPDF = async () => {
try {
const scan = await findScanById(params.scanId);
console.log({ scan });
if (scan.length === 0) {
redirect("/dashboard");
return;
}
const supabase = createClerkSupabaseClient();
let { data: old, error } = await supabase.storage
.from("resumes")
.download(`scans/${params.scanId}.pdf`);
if (!old) {
const pdfBuffer = await generatePDF({
htmlContent: scan[0].ai_output,
});
console.log("Trying to upload pdf:", pdfBuffer);
await uploadPDF(params.scanId, pdfBuffer);
({ data: old, error } = await supabase.storage
.from("resumes")
.download(`scans/${params.scanId}.pdf`));
}
setPdfBlob(old);
setLoading(false);
} catch (error) {
console.error("Failed to fetch or generate PDF:", error);
}
};
fetchPDF();
}, [params.scanId]);
return (
<div className="min-h-screen flex flex-col">
<Navbar />
<div className="w-full flex-1 flex items-center px-8">
<div className="w-full flex gap-24">
<div className="bg-white w-[30%] py-10 justify-between h-[40rem] rounded-xl flex flex-col items-center">
<div className="text-center flex flex-col items-center">
<div className="rounded-full w-20 h-20 bg-green-400 flex justify-center items-center">
<Check className="text-white" size={24} />
</div>
<h1 className="text-3xl pt-8">
Your resume is <br /> now tailored.
</h1>
</div>
<Button onClick={() => handleDownload(pdfBlob)} disabled={loading}>
Download Resume
</Button>
</div>
<div
id="resume-content"
className="w-[70%] h-[40rem] rounded-xl border-2 border-gray-700 bg-white overflow-y-auto"
></div>
</div>
</div>
</div>
);
};
export default ScanDetails;
scan/actions.ts"use server";
import { createClerkSupabaseClient } from "@/utils/supabase/server";
import puppeteer from "puppeteer";
export const findScanById = async (id: string) => {
const supabase = await createClerkSupabaseClient();
const { data, error } = await supabase.from("Tailors").select().eq("id", id);
if (error) {
throw error;
}
return data;
};
export const generatePDF = async ({ htmlContent }: { htmlContent: string }) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlContent, { waitUntil: "domcontentloaded" });
await page.emulateMediaType("screen");
const pdfBuffer = await page.pdf({
margin: { top: 0, right: 0, bottom: 0, left: 0 },
printBackground: true,
format: "A4",
});
await browser.close();
return pdfBuffer;
};
export const uploadPDF = async (scanId: string, pdfBuffer: Buffer) => {
const supabase = await createClerkSupabaseClient();
const { data, error } = await supabase.storage
.from("resumes")
.upload(`scans/${scanId}.pdf`, pdfBuffer, {
cacheControl: "3600",
upsert: true,
contentType: "application/pdf",
});
if (error) throw new Error(`Failed to upload PDF: ${error.message}`);
return data;
};
utils/handleDownload.tsexport const handleDownload = (pdfBlob: Blob | null) => {
if (pdfBlob) {
const url = URL.createObjectURL(pdfBlob);
const a = document.createElement("a");
a.href = url;
a.download = "resume.pdf"; // The filename you want the file to have
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Release the object URL after download
URL.revokeObjectURL(url);
}
};
我正在开发一个应用程序,允许用户根据职位描述定制他们的简历。当他们上传职位描述和简历时,AI 会定制简历并以 HTML 形式返回,然后我使用 Puppeteer 将其生成 PDF 文档。在 AI 返回响应后,我会将用户引导到扫描页面,他们可以在该页面下载新的简历。它的工作原理是先将简历上传到 Supabase 存储,然后再下载并提供给用户。我遇到的问题是,当文件被下载时,PDF 似乎无法正常工作,也无法预览。为什么会这样?即使我在 Supabase 仪表板上检查简历本身,它已经上传,但 PDF 有问题,无法打开。