// Convert a ProseMirror JSON document into a plain-text string for LLM input. // Headings get markdown-style "##"; chip nodes render as their label so the // analyzer sees the same surface forms a human would. import "server-only"; interface PMNode { type: string; text?: string; attrs?: Record; content?: PMNode[]; } export function proseDocToPlainText(doc: unknown): string { if (!doc || typeof doc !== "object") return ""; const root = doc as PMNode; return walk(root, 0).trim(); } function walk(node: PMNode, depth: number): string { if (!node) return ""; if (node.type === "text") return node.text ?? ""; if (node.type === "chip") { const label = (node.attrs?.label as string | undefined) ?? ""; return label; } const children = (node.content ?? []).map(c => walk(c, depth + 1)).join(""); switch (node.type) { case "heading": { const level = typeof node.attrs?.level === "number" ? (node.attrs.level as number) : 1; const hash = "#".repeat(Math.max(1, Math.min(level, 6))); return `\n\n${hash} ${children}\n\n`; } case "paragraph": return `${children}\n\n`; case "bulletList": case "bullet_list": case "orderedList": case "ordered_list": return `${children}\n`; case "listItem": case "list_item": return `- ${children.trim()}\n`; case "hardBreak": case "hard_break": return "\n"; default: return children; } }