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

73 lines
2.4 KiB
TypeScript

import type { Editor } from "@tiptap/react"
import { ensureMinimalTipTapDoc } from "@/lib/drive/richtext-content"
export type DocsApplyAction =
| { action: "insert_text"; text: string }
| { action: "replace_selection"; text: string }
| { action: "append_paragraph"; text: string }
| { action: "set_content"; document: Record<string, unknown> }
export function parseDocsApplyPayload(raw: unknown): DocsApplyAction | null {
if (!raw || typeof raw !== "object") return null
const obj = raw as Record<string, unknown>
const action = obj.action
if (action === "insert_text" && typeof obj.text === "string") {
return { action, text: obj.text }
}
if (action === "replace_selection" && typeof obj.text === "string") {
return { action, text: obj.text }
}
if (action === "append_paragraph" && typeof obj.text === "string") {
return { action, text: obj.text }
}
if (action === "set_content" && obj.document && typeof obj.document === "object") {
return {
action,
document: obj.document as Record<string, unknown>,
}
}
return null
}
/** Extrait un bloc ```ulti-docs-apply depuis une réponse assistant. */
export function extractDocsApplyFromMarkdown(text: string): DocsApplyAction | null {
const match = text.match(/```ulti-docs-apply\s*([\s\S]*?)```/i)
if (!match?.[1]) return null
try {
return parseDocsApplyPayload(JSON.parse(match[1].trim()))
} catch {
return null
}
}
export function applyDocsAction(editor: Editor, cmd: DocsApplyAction): boolean {
if (!editor.isEditable) return false
switch (cmd.action) {
case "insert_text":
return editor.chain().focus().insertContent(cmd.text).run()
case "replace_selection":
if (editor.state.selection.empty) {
return editor.chain().focus().insertContent(cmd.text).run()
}
return editor.chain().focus().deleteSelection().insertContent(cmd.text).run()
case "append_paragraph": {
const blocks = cmd.text
.split(/\n{2,}/)
.map((p) => p.trim())
.filter(Boolean)
.map((p) => ({
type: "paragraph",
content: [{ type: "text", text: p }],
}))
if (blocks.length === 0) return false
return editor.chain().focus().insertContentAt(editor.state.doc.content.size, blocks).run()
}
case "set_content": {
const doc = ensureMinimalTipTapDoc(cmd.document)
return editor.commands.setContent(doc)
}
default:
return false
}
}