"use client"; import { FileDownIcon } from "lucide-react"; import { useState, type RefObject } from "react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; type ResponsePdfDownloadButtonProps = { contentRef: RefObject; fileName?: string; siteLogo?: string | null; siteName?: string | null; title?: string; }; function escapeHtml(value: string) { return value .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function getSafeTitle(value?: string | null) { return String(value || "").replace(/\s+/g, " ").trim(); } function getSafeFileName(value?: string | null) { const normalized = getSafeTitle(value || "Claw 分析报告") .replace(/[\\/:*?"<>|]/g, "_") .slice(0, 80); return normalized || "Claw 分析报告"; } function clonePrintableStyles(targetWindow: Window) { document .querySelectorAll( 'link[rel="stylesheet"], style' ) .forEach((node) => { targetWindow.document.head.appendChild(node.cloneNode(true)); }); } function createPrintOverrides(targetWindow: Window) { const style = targetWindow.document.createElement("style"); style.textContent = ` :root { --background: #ffffff; --foreground: #111827; --border: #e5e7eb; --card: #ffffff; --muted: #f3f4f6; --muted-foreground: #6b7280; --primary: #111827; } html, body { margin: 0; background: #ffffff; color: #111827; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif; } body { -webkit-print-color-adjust: exact; print-color-adjust: exact; } .claw-pdf-shell { box-sizing: border-box; width: 1120px; max-width: 100%; margin: 0 auto; padding: 32px; background: #ffffff; } .claw-pdf-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding-bottom: 18px; margin-bottom: 24px; border-bottom: 1px solid #e5e7eb; } .claw-pdf-brand { display: flex; min-width: 0; align-items: center; gap: 12px; } .claw-pdf-logo, .claw-pdf-logo-fallback { width: 38px; height: 38px; flex: 0 0 auto; border-radius: 10px; } .claw-pdf-logo { object-fit: contain; background: #ffffff; } .claw-pdf-logo-fallback { display: inline-flex; align-items: center; justify-content: center; background: #111827; color: #ffffff; font-size: 15px; font-weight: 700; } .claw-pdf-site { font-size: 18px; line-height: 1.2; font-weight: 700; color: #111827; } .claw-pdf-title { margin-top: 4px; font-size: 12px; line-height: 1.4; color: #6b7280; } .claw-pdf-time { flex: 0 0 auto; text-align: right; font-size: 11px; color: #6b7280; } .claw-pdf-content { font-size: 14px; line-height: 1.7; } [data-pdf-exclude="true"] { display: none !important; } .chat-card-x-scroll { overflow: visible !important; padding-bottom: 0 !important; scrollbar-width: none !important; } .chat-card-x-scroll::-webkit-scrollbar { display: none !important; } table { page-break-inside: auto; } tr, svg, img { page-break-inside: avoid; } svg { max-width: 100% !important; height: auto; } @page { size: A4 landscape; margin: 12mm; } @media print { .claw-pdf-shell { width: auto; max-width: none; padding: 0; } } `; targetWindow.document.head.appendChild(style); } export function ResponsePdfDownloadButton({ contentRef, fileName, siteLogo, siteName, title = "Claw 分析报告", }: ResponsePdfDownloadButtonProps) { const [isPreparing, setIsPreparing] = useState(false); const resolvedSiteName = getSafeTitle(siteName) || "AI Chatbot"; const resolvedTitle = getSafeTitle(title) || "Claw 分析报告"; const handleDownloadPdf = () => { const contentElement = contentRef.current; if (!contentElement) { toast.error("未找到可导出的内容"); return; } const printWindow = window.open( "", "_blank", "width=1200,height=820" ); if (!printWindow) { toast.error("浏览器阻止了 PDF 导出窗口,请允许弹窗后重试"); return; } setIsPreparing(true); try { const safeDocumentTitle = getSafeFileName(fileName || resolvedTitle); printWindow.document.open(); printWindow.document.write( `${escapeHtml( safeDocumentTitle )}` ); printWindow.document.close(); clonePrintableStyles(printWindow); createPrintOverrides(printWindow); const shell = printWindow.document.createElement("main"); shell.className = "claw-pdf-shell"; const header = printWindow.document.createElement("header"); header.className = "claw-pdf-header"; const brand = printWindow.document.createElement("div"); brand.className = "claw-pdf-brand"; if (siteLogo) { const logo = printWindow.document.createElement("img"); logo.className = "claw-pdf-logo"; logo.alt = resolvedSiteName; logo.src = siteLogo; brand.appendChild(logo); } else { const fallback = printWindow.document.createElement("span"); fallback.className = "claw-pdf-logo-fallback"; fallback.textContent = resolvedSiteName.slice(0, 1).toUpperCase(); brand.appendChild(fallback); } const brandText = printWindow.document.createElement("div"); brandText.innerHTML = `
${escapeHtml( resolvedSiteName )}
${escapeHtml(resolvedTitle)}
`; brand.appendChild(brandText); const time = printWindow.document.createElement("div"); time.className = "claw-pdf-time"; time.textContent = new Date().toLocaleString("zh-CN", { hour12: false, }); header.appendChild(brand); header.appendChild(time); shell.appendChild(header); const content = printWindow.document.createElement("section"); content.className = "claw-pdf-content"; const clonedContent = contentElement.cloneNode(true) as HTMLElement; clonedContent .querySelectorAll('[data-pdf-exclude="true"]') .forEach((node) => node.remove()); content.appendChild(clonedContent); shell.appendChild(content); printWindow.document.body.appendChild(shell); window.setTimeout(() => { setIsPreparing(false); printWindow.focus(); printWindow.print(); }, 600); } catch (error) { setIsPreparing(false); printWindow.close(); console.error("[Response PDF] export failed:", error); toast.error("PDF 导出失败,请稍后重试"); } }; return ( ); }