ultisuite-client/lib/ai/docs-context.ts
R3D347HR4Y 303b2b1074
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wow
2026-06-11 01:22:40 +02:00

93 lines
2.8 KiB
TypeScript

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<string, unknown>
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<string, unknown>
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) + "…"
}