"use client" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import type { Editor } from "@tiptap/react" import { Sparkles, X, GripVertical } from "lucide-react" import { Button } from "@/components/ui/button" import { AiChatIframe } from "@/components/ai/ai-chat-iframe" import { docsContextFromSnapshot, docsSystemPromptExtra, snapshotDocsEditor, } from "@/lib/ai/docs-context" import { applyDocsAction, extractDocsApplyFromMarkdown, parseDocsApplyPayload, } from "@/lib/ai/docs-apply" import type { AiPostMessage } from "@/lib/ai/chat-context" import { useAiConfig, useAiQuota } from "@/lib/api/hooks/use-ai-queries" import { DOCS_AI_PANEL_MIN_WIDTH_PX, useDocsAiPanelStore, } from "@/lib/ai/use-docs-ai-panel" import { cn } from "@/lib/utils" export function DocsAiPanel({ editor, documentPath, documentTitle, sourcePath, editable, }: { editor: Editor | null documentPath: string documentTitle: string sourcePath?: string editable: boolean }) { const open = useDocsAiPanelStore((s) => s.open) const widthPx = useDocsAiPanelStore((s) => s.widthPx) const setWidthPx = useDocsAiPanelStore((s) => s.setWidthPx) const closePanel = useDocsAiPanelStore((s) => s.closePanel) const { data: config } = useAiConfig() const { data: quota } = useAiQuota(open && (config?.enabled ?? false)) const [contextTick, setContextTick] = useState(0) const resizeRef = useRef<{ startX: number; startW: number } | null>(null) useEffect(() => { if (!editor || !open) return const bump = () => setContextTick((n) => n + 1) editor.on("selectionUpdate", bump) editor.on("update", bump) return () => { editor.off("selectionUpdate", bump) editor.off("update", bump) } }, [editor, open]) const context = useMemo(() => { if (!editor || !open) { return { app: "docs" as const, temporary: true, drivePath: documentPath, documentTitle, sourcePath, } } const snap = snapshotDocsEditor(editor, { path: documentPath, title: documentTitle, sourcePath, }) return docsContextFromSnapshot(snap, true) }, [editor, open, documentPath, documentTitle, sourcePath, contextTick]) const handleApplyFromParent = useCallback( (payload: unknown) => { if (!editor) return const cmd = parseDocsApplyPayload(payload) if (cmd) applyDocsAction(editor, cmd) }, [editor] ) useEffect(() => { if (!open || !editor) return const onMessage = (event: MessageEvent) => { if (event.origin !== window.location.origin) return const data = event.data as AiPostMessage & { type?: string payload?: unknown text?: string } if (data?.type === "ULTI_DOCS_APPLY" && data.payload) { handleApplyFromParent(data.payload) } if (data?.type === "ULTI_ASSISTANT_TEXT" && typeof data.text === "string") { const cmd = extractDocsApplyFromMarkdown(data.text) if (cmd) applyDocsAction(editor, cmd) } } window.addEventListener("message", onMessage) return () => window.removeEventListener("message", onMessage) }, [open, editor, handleApplyFromParent]) useEffect(() => { if (!open || !editor) return ;(window as Window & { __ultiDocsApply?: (p: unknown) => void }).__ultiDocsApply = handleApplyFromParent return () => { delete (window as Window & { __ultiDocsApply?: (p: unknown) => void }).__ultiDocsApply } }, [open, editor, handleApplyFromParent]) const onResizePointerDown = (e: React.PointerEvent) => { e.preventDefault() resizeRef.current = { startX: e.clientX, startW: widthPx } const onMove = (ev: PointerEvent) => { const st = resizeRef.current if (!st) return const delta = st.startX - ev.clientX setWidthPx(st.startW + delta) } const onUp = () => { resizeRef.current = null window.removeEventListener("pointermove", onMove) window.removeEventListener("pointerup", onUp) } window.addEventListener("pointermove", onMove) window.addEventListener("pointerup", onUp) } if (!config?.enabled || !open) return null return (
UltiAI
{quota ? ( {quota.requests_remaining} req. ) : null}
{!editable ? (

Mode lecture — l'IA peut analyser le document mais pas le modifier.

) : null}
) } export function DocsAiPanelToggle({ disabled, className, }: { disabled?: boolean className?: string }) { const open = useDocsAiPanelStore((s) => s.open) const toggle = useDocsAiPanelStore((s) => s.toggle) const { data: config } = useAiConfig() if (!config?.enabled) return null return ( ) }