/** * THE SESSION MARKDOWN EXPORT (owner, release polish 2026-10-06): the * user-facing export the session-markdown engine was built for — the * same document the extractor reads, minted anchorless (anchors are * extraction provenance; a learner's export carries no message ids). * * GET /api/chats/:id/markdown → the whole session as one markdown file * (Content-Disposition attachment; filename from the slugified title). * 404 unknown chat. Both session kinds export — practice sessions ride * the same engine (problem/conclude results render as tool blocks, * markers are invisible by the engine's law). */ import { sessionMarkdown } from "@/src/core"; import { getChat, applicationLanguage } from "@/src/server"; /** Filename-safe title (own module scope on purpose: tiny + pure). */ function slugify(title: string): string { const slug = title .toLowerCase() .normalize("NFKD") .replace(/[^\p{L}\p{N}]+/gu, "-") .replace(/^-+|-+$/g, ""); return slug.length > 0 ? slug : "session"; } export async function GET(_req: Request, ctx: { params: Promise<{ id: string }> }): Promise { const { id } = await ctx.params; const record = getChat(id); if (!record) return Response.json({ error: "Unknown chat" }, { status: 404 }); const markdown = sessionMarkdown(record.messages, { title: record.title, anchors: false, language: applicationLanguage(_req.headers.get("accept-language")) }); return new Response(markdown, { headers: { "content-type": "text/markdown; charset=utf-8", "content-disposition": `attachment; filename="${/^[a-z0-9-]+$/.test(slugify(record.title)) ? slugify(record.title) : "session"}.md"; filename*=UTF-8''${encodeURIComponent(slugify(record.title))}.md`, }, }); }