"use client"; /* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useRef, useMemo } from "react"; import classNames from "classnames"; import { toast } from "sonner"; import { useLocalStorage, useUpdateEffect } from "react-use"; import { ArrowUp, ChevronDown, Crosshair } from "lucide-react"; import { FaStopCircle } from "react-icons/fa"; import ProModal from "@/components/pro-modal"; import { Button } from "@/components/ui/button"; import { MODELS } from "@/lib/providers"; import { HtmlHistory } from "@/types"; import { InviteFriends } from "@/components/invite-friends"; import { Settings } from "@/components/editor/ask-ai/settings"; import { LoginModal } from "@/components/login-modal"; import { ReImagine } from "@/components/editor/ask-ai/re-imagine"; import Loading from "@/components/loading"; import { Checkbox } from "@/components/ui/checkbox"; import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip"; import { TooltipContent } from "@radix-ui/react-tooltip"; import { SelectedHtmlElement } from "./selected-html-element"; import { FollowUpTooltip } from "./follow-up-tooltip"; import { isTheSameHtml } from "@/lib/compare-html-diff"; export function AskAI({ html, setHtml, onScrollToBottom, isAiWorking, setisAiWorking, isEditableModeEnabled = false, selectedElement, setSelectedElement, setIsEditableModeEnabled, onNewPrompt, onSuccess, }: { html: string; setHtml: (html: string) => void; onScrollToBottom: () => void; isAiWorking: boolean; onNewPrompt: (prompt: string) => void; htmlHistory?: HtmlHistory[]; setisAiWorking: React.Dispatch>; onSuccess: (h: string, p: string, n?: number[][]) => void; isEditableModeEnabled: boolean; setIsEditableModeEnabled: React.Dispatch>; selectedElement?: HTMLElement | null; setSelectedElement: React.Dispatch>; }) { const refThink = useRef(null); const audio = useRef(null); const [open, setOpen] = useState(false); const [prompt, setPrompt] = useState(""); const [hasAsked, setHasAsked] = useState(false); const [previousPrompt, setPreviousPrompt] = useState(""); const [provider, setProvider] = useLocalStorage("provider", "auto"); const [model, setModel] = useLocalStorage("model", MODELS[0].value); const [openProvider, setOpenProvider] = useState(false); const [providerError, setProviderError] = useState(""); const [openProModal, setOpenProModal] = useState(false); const [think, setThink] = useState(undefined); const [openThink, setOpenThink] = useState(false); const [isThinking, setIsThinking] = useState(true); const [controller, setController] = useState(null); const [isFollowUp, setIsFollowUp] = useState(true); const callAi = async (redesignMarkdown?: string) => { if (isAiWorking) return; if (!redesignMarkdown && !prompt.trim()) return; setisAiWorking(true); setProviderError(""); setThink(""); setOpenThink(false); setIsThinking(true); let contentResponse = ""; let thinkResponse = ""; let lastRenderTime = 0; const abortController = new AbortController(); setController(abortController); try { onNewPrompt(prompt); if (isFollowUp && !redesignMarkdown && !isSameHtml) { const selectedElementHtml = selectedElement ? selectedElement.outerHTML : ""; const request = await fetch("/api/ask-ai", { method: "PUT", body: JSON.stringify({ prompt, provider, previousPrompt, model, html, selectedElementHtml, }), headers: { "Content-Type": "application/json", "x-forwarded-for": window.location.hostname, }, signal: abortController.signal, }); if (request && request.body) { const res = await request.json(); if (!request.ok) { if (res.openLogin) { setOpen(true); } else if (res.openSelectProvider) { setOpenProvider(true); setProviderError(res.message); } else if (res.openProModal) { setOpenProModal(true); } else { toast.error(res.message); } setisAiWorking(false); return; } setHtml(res.html); toast.success("AI responded successfully"); setPreviousPrompt(prompt); setPrompt(""); setisAiWorking(false); onSuccess(res.html, prompt, res.updatedLines); if (audio.current) audio.current.play(); } } else { const request = await fetch("/api/ask-ai", { method: "POST", body: JSON.stringify({ prompt, provider, model, html: isSameHtml ? "" : html, redesignMarkdown, }), headers: { "Content-Type": "application/json", "x-forwarded-for": window.location.hostname, }, signal: abortController.signal, }); if (request && request.body) { const reader = request.body.getReader(); const decoder = new TextDecoder("utf-8"); const selectedModel = MODELS.find( (m: { value: string }) => m.value === model ); let contentThink: string | undefined = undefined; const read = async () => { const { done, value } = await reader.read(); if (done) { const isJson = contentResponse.trim().startsWith("{") && contentResponse.trim().endsWith("}"); const jsonResponse = isJson ? JSON.parse(contentResponse) : null; if (jsonResponse && !jsonResponse.ok) { if (jsonResponse.openLogin) { setOpen(true); } else if (jsonResponse.openSelectProvider) { setOpenProvider(true); setProviderError(jsonResponse.message); } else if (jsonResponse.openProModal) { setOpenProModal(true); } else { toast.error(jsonResponse.message); } setisAiWorking(false); return; } toast.success("AI responded successfully"); setPreviousPrompt(prompt); setPrompt(""); setisAiWorking(false); setHasAsked(true); setModel(MODELS[0].value); if (audio.current) audio.current.play(); // Now we have the complete HTML including , so set it to be sure const finalDoc = contentResponse.match( /[\s\S]*<\/html>/ )?.[0]; if (finalDoc) { setHtml(finalDoc); } onSuccess(finalDoc ?? contentResponse, prompt); return; } const chunk = decoder.decode(value, { stream: true }); thinkResponse += chunk; if (selectedModel?.isThinker) { const thinkMatch = thinkResponse.match(/[\s\S]*/)?.[0]; if (thinkMatch && !thinkResponse?.includes("")) { if ((contentThink?.length ?? 0) < 3) { setOpenThink(true); } setThink(thinkMatch.replace("", "").trim()); contentThink += chunk; return read(); } } contentResponse += chunk; const newHtml = contentResponse.match( /[\s\S]*/ )?.[0]; if (newHtml) { setIsThinking(false); let partialDoc = newHtml; if ( partialDoc.includes("") && !partialDoc.includes("") ) { partialDoc += "\n"; } if ( partialDoc.includes("") ) { partialDoc += "\n"; } if (!partialDoc.includes("")) { partialDoc += "\n"; } // Throttle the re-renders to avoid flashing/flicker const now = Date.now(); if (now - lastRenderTime > 300) { setHtml(partialDoc); lastRenderTime = now; } if (partialDoc.length > 200) { onScrollToBottom(); } } read(); }; read(); } } } catch (error: any) { setisAiWorking(false); toast.error(error.message); if (error.openLogin) { setOpen(true); } } }; const stopController = () => { if (controller) { controller.abort(); setController(null); setisAiWorking(false); setThink(""); setOpenThink(false); setIsThinking(false); } }; useUpdateEffect(() => { if (refThink.current) { refThink.current.scrollTop = refThink.current.scrollHeight; } }, [think]); useUpdateEffect(() => { if (!isThinking) { setOpenThink(false); } }, [isThinking]); const isSameHtml = useMemo(() => { return isTheSameHtml(html); }, [html]); return (
{think && (
{ setOpenThink(!openThink); }} >

{isThinking ? "DeepSite is thinking..." : "DeepSite's plan"}

{think}

)} {selectedElement && (
setSelectedElement(null)} />
)}
{isAiWorking && (

AI is {isThinking ? "thinking" : "coding"}...{" "}

Stop generation
)} setPrompt(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { callAi(); } }} />
callAi(md)} /> {!isSameHtml && ( Select an element on the page to ask DeepSite edit it directly. )}
setOpen(false)} html={html} /> setOpenProModal(false)} /> {!isSameHtml && (
)}
); }