SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.5 KB · 83 lines typescript
Raw Blame History
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