网站搜索

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.ts
export 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 有问题,无法打开。