"use client"; import { Command, CommandInput } from "../../ui/command"; import { ArrowUp } from "lucide-react"; import { useEditor } from "novel"; import { addAIHighlight } from "novel"; import { useCallback, useState } from "react"; import Markdown from "react-markdown"; import { toast } from "sonner"; import { Button } from "../../ui/button"; import Magic from "../../ui/icons/magic"; import { ScrollArea } from "../../ui/scroll-area"; import AICompletionCommands from "./ai-completion-command"; import AISelectorCommands from "./ai-selector-commands"; import { LoadingOutlined } from "@ant-design/icons"; import { resolveServiceURL } from "~/core/api/resolve-service-url"; import { fetchStream } from "~/core/sse"; //TODO: I think it makes more sense to create a custom Tiptap extension for this functionality https://tiptap.dev/docs/editor/ai/introduction interface AISelectorProps { open: boolean; onOpenChange: (open: boolean) => void; } function useProseCompletion() { const [completion, setCompletion] = useState(""); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const complete = useCallback( async (prompt: string, options?: { body?: Record }) => { setIsLoading(true); setError(null); try { const response = await fetchStream( resolveServiceURL("/api/prose/generate"), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ prompt, ...options?.body, }), }, ); let fullText = ""; // Process the streaming response for await (const chunk of response) { fullText += chunk.data; setCompletion(fullText); } setIsLoading(false); return fullText; } catch (e) { const error = e instanceof Error ? e : new Error("An error occurred"); setError(error); toast.error(error.message); setIsLoading(false); throw error; } }, [], ); const reset = useCallback(() => { setCompletion(""); setError(null); setIsLoading(false); }, []); return { completion, complete, isLoading, error, reset, }; } export function AISelector({ onOpenChange }: AISelectorProps) { const { editor } = useEditor(); const [inputValue, setInputValue] = useState(""); const { completion, complete, isLoading } = useProseCompletion(); if (!editor) return null; const hasCompletion = completion.length > 0; return ( {hasCompletion && (
{completion}
)} {isLoading && (
AI is thinking
)} {!isLoading && ( <>
addAIHighlight(editor)} />
{hasCompletion ? ( { editor.chain().unsetHighlight().focus().run(); onOpenChange(false); }} completion={completion} /> ) : ( complete(value, { body: { option } }) } /> )} )}
); }