import type { Editor } from "@tiptap/react" import type { AiChatContext } from "@/lib/ai/chat-context" import { TIPTAP_SYNTAX_GUIDE } from "@/lib/ai/tiptap-syntax-guide" const MAX_CONTEXT_CHARS = 14_000 const MAX_JSON_CHARS = 8_000 export type DocsEditorSnapshot = { documentPath: string documentTitle: string sourcePath?: string plainText: string selectionText: string contentJson: Record contentJsonTruncated: string } export function snapshotDocsEditor( editor: Editor, meta: { path: string; title: string; sourcePath?: string } ): DocsEditorSnapshot { const { from, to } = editor.state.selection const plainText = editor.getText() const selectionText = from === to ? "" : editor.state.doc.textBetween(from, to, "\n", "\n") const contentJson = editor.getJSON() as Record const jsonRaw = JSON.stringify(contentJson) const contentJsonTruncated = jsonRaw.length > MAX_JSON_CHARS ? jsonRaw.slice(0, MAX_JSON_CHARS) + "…" : jsonRaw return { documentPath: meta.path, documentTitle: meta.title, sourcePath: meta.sourcePath, plainText: truncate(plainText, MAX_CONTEXT_CHARS), selectionText: truncate(selectionText, 4000), contentJson, contentJsonTruncated, } } export function docsContextFromSnapshot( snapshot: DocsEditorSnapshot, temporary = true ): AiChatContext { return { app: "docs", temporary, drivePath: snapshot.documentPath, documentTitle: snapshot.documentTitle, sourcePath: snapshot.sourcePath, documentExcerpt: snapshot.plainText, selectionText: snapshot.selectionText || undefined, documentJson: snapshot.contentJsonTruncated, } } export function docsSystemPromptExtra(context: AiChatContext): string { const lines = [ "Tu édites un document UltiDocs (TipTap). Tu peux lire le contenu ci-dessous et proposer des modifications.", "Pour appliquer une modification côté éditeur, renvoie un bloc JSON fenced:", '```ulti-docs-apply\n{ "action": "insert_text"|"replace_selection"|"append_paragraph"|"set_content", ... }\n```', "", TIPTAP_SYNTAX_GUIDE, ] if (context.documentTitle) { lines.push("", `Document: ${context.documentTitle}`) } if (context.drivePath) { lines.push(`Chemin sidecar: ${context.drivePath}`) } if (context.sourcePath) { lines.push(`Fichier source: ${context.sourcePath}`) } if (context.selectionText) { lines.push("", "Sélection utilisateur:", context.selectionText) } if (context.documentExcerpt) { lines.push("", "Contenu (texte):", context.documentExcerpt) } if (context.documentJson) { lines.push("", "Contenu (JSON tronqué si long):", context.documentJson) } return lines.join("\n") } function truncate(value: string, max: number): string { if (value.length <= max) return value return value.slice(0, max) + "…" }