/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useRef } from "react"; import { RiSparkling2Fill } from "react-icons/ri"; import { GrSend } from "react-icons/gr"; import classNames from "classnames"; import { toast } from "sonner"; import { useLocalStorage, useUpdateEffect } from "react-use"; import { ChevronDown } from "lucide-react"; import Login from "../login/login"; import { defaultHTML } from "../../../utils/consts"; import SuccessSound from "./../../assets/success.mp3"; import Settings from "../settings/settings"; import ProModal from "../pro-modal/pro-modal"; import { Button } from "../ui/button"; // @ts-expect-error not needed import { MODELS } from "./../../../utils/providers"; function AskAI({ html, setHtml, onScrollToBottom, isAiWorking, setisAiWorking, onNewPrompt, onSuccess, }: { html: string; setHtml: (html: string) => void; onScrollToBottom: () => void; isAiWorking: boolean; onNewPrompt: (prompt: string) => void; setisAiWorking: React.Dispatch>; onSuccess: (h: string, p: string) => void; }) { const refThink = 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 audio = new Audio(SuccessSound); audio.volume = 0.5; const callAi = async () => { if (isAiWorking || !prompt.trim()) return; setisAiWorking(true); setProviderError(""); setThink(""); setOpenThink(false); setIsThinking(true); let contentResponse = ""; let lastRenderTime = 0; try { onNewPrompt(prompt); const request = await fetch("/api/ask-ai", { method: "POST", body: JSON.stringify({ prompt, provider, model, ...(html === defaultHTML ? {} : { html }), ...(previousPrompt ? { previousPrompt } : {}), }), headers: { "Content-Type": "application/json", }, }); if (request && request.body) { if (!request.ok) { const res = await request.json(); 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; } 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) { toast.success("AI responded successfully"); setPreviousPrompt(prompt); setPrompt(""); setisAiWorking(false); setHasAsked(true); audio.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 }); contentResponse += chunk; if (selectedModel?.isThinker) { const thinkMatch = contentResponse.match(/[\s\S]*/)?.[0]; if (thinkMatch && !contentResponse?.includes("")) { if ((contentThink?.length ?? 0) < 3) { setOpenThink(true); } setThink(thinkMatch.replace("", "").trim()); contentThink += chunk; } } const newHtml = contentResponse.match(/[\s\S]*/)?.[0]; if (newHtml) { setIsThinking(false); let partialDoc = newHtml; 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); } } }; useUpdateEffect(() => { if (refThink.current) { refThink.current.scrollTop = refThink.current.scrollHeight; } }, [think]); useUpdateEffect(() => { if (!isThinking) { setOpenThink(false); } }, [isThinking]); // TODO: auto scroll is not working properly, fix it return (
{think && (
{ setOpenThink(!openThink); }} >

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

{think}

)}
{isAiWorking && (

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

)} setPrompt(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { callAi(); } }} />
{/* */}
setOpen(false)} >

You reached the limit of free AI usage. Please login to continue.

setOpenProModal(false)} />
); } export default AskAI;