TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import type { ExportFormat } from "@/lib/client/types";3import { toast } from "@/components/ui/toast";45/** Client-side export formats: the server formats plus `pdf` (= HTML opened in a new tab + `window.print()`). */6export type ClientExportFormat = ExportFormat | "pdf";78export interface ExportOption {9 format: ClientExportFormat;10 label: string;11 hint: string;12 description: string;13}1415export const EXPORT_OPTIONS: ExportOption[] = [16 { format: "markdown", label: "Markdown", hint: ".md", description: "Readable transcript with headings and code blocks." },17 { format: "txt", label: "Plain text", hint: ".txt", description: "No markup, easy to paste anywhere." },18 { format: "json", label: "JSON", hint: ".json", description: "Full data: messages, parts, usage, settings." },19 { format: "html", label: "HTML", hint: ".html", description: "Self-contained page with print styles." },20 { format: "pdf", label: "PDF (print)", hint: "Print dialog", description: "Opens a print-optimized page — choose “Save as PDF”." },21];2223export function exportUrl(conversationId: string, format: ClientExportFormat, opts: { download?: boolean; print?: boolean } = {}): string {24 const sp = new URLSearchParams();25 sp.set("format", format === "pdf" ? "html" : format);26 if (opts.download) sp.set("download", "1");27 if (opts.print) sp.set("print", "1");28 return `/api/conversations/${encodeURIComponent(conversationId)}/export?${sp.toString()}`;29}3031function filenameFrom(res: Response, fallback: string): string {32 const cd = res.headers.get("content-disposition") ?? "";33 return /filename="?([^";]+)"?/.exec(cd)?.[1] ?? fallback;34}3536async function download(url: string, fallbackName: string) {37 const res = await fetch(url, { credentials: "same-origin" });38 if (!res.ok) {39 let msg = `Export failed (${res.status})`;40 try {41 const body = (await res.json()) as { error?: { message?: string } };42 if (body.error?.message) msg = body.error.message;43 } catch {44 /* not json */45 }46 throw new Error(msg);47 }48 const blob = await res.blob();49 const a = document.createElement("a");50 a.href = URL.createObjectURL(blob);51 a.download = filenameFrom(res, fallbackName);52 document.body.appendChild(a);53 a.click();54 a.remove();55 setTimeout(() => URL.revokeObjectURL(a.href), 1000);56}5758/**59 * Export a conversation from the browser.60 * - json / markdown / txt / html → file download61 * - pdf → opens the HTML export in a new tab which triggers `window.print()` ("Save as PDF"). Falls back to an62 * HTML download when the pop-up is blocked.63 * Shows toasts; resolves when done. Throws only on network/API failure (already toasted).64 */65export async function exportConversation(conversationId: string, format: ClientExportFormat): Promise<void> {66 try {67 if (format === "pdf") {68 const w = window.open(exportUrl(conversationId, "pdf", { print: true }), "_blank", "noopener");69 if (!w) {70 await download(exportUrl(conversationId, "html", { download: true }), "conversation.html");71 toast.warning("Pop-up blocked", "The HTML version was downloaded instead — open it and use Print → Save as PDF.");72 }73 return;74 }75 const ext = format === "markdown" ? "md" : format;76 await download(exportUrl(conversationId, format, { download: true }), `conversation.${ext}`);77 toast.success("Export ready", "Your download should start now.");78 } catch (e) {79 toast.error("Export failed", e instanceof Error ? e.message : undefined);80 throw e;81 }82}83