enzostvs's picture
enzostvs HF Staff
add prompts text file
39c9ffc
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useState } from "react";
import { RiSparkling2Fill } from "react-icons/ri";
import { GrSend } from "react-icons/gr";
import classNames from "classnames";
import { toast } from "react-toastify";
import { useLocalStorage } from "react-use";
import { MdPreview } from "react-icons/md";
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 SpeechPrompt from "../speech-prompt/speech-prompt";
function AskAI({
html,
setHtml,
onScrollToBottom,
isAiWorking,
setisAiWorking,
setView,
onNewPrompt,
}: {
html: string;
setHtml: (html: string) => void;
onScrollToBottom: () => void;
isAiWorking: boolean;
onNewPrompt: (prompt: string) => void;
setView: React.Dispatch<React.SetStateAction<"editor" | "preview">>;
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
}) {
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 [openProvider, setOpenProvider] = useState(false);
const [providerError, setProviderError] = useState("");
const [openProModal, setOpenProModal] = useState(false);
const audio = new Audio(SuccessSound);
audio.volume = 0.5;
const callAi = async () => {
if (isAiWorking || !prompt.trim()) return;
setisAiWorking(true);
setProviderError("");
let contentResponse = "";
let lastRenderTime = 0;
try {
onNewPrompt(prompt);
const request = await fetch("/api/ask-ai", {
method: "POST",
body: JSON.stringify({
prompt,
provider,
...(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 read = async () => {
const { done, value } = await reader.read();
if (done) {
toast.success("AI responded successfully");
setPrompt("");
setPreviousPrompt(prompt);
setisAiWorking(false);
setHasAsked(true);
audio.play();
setView("preview");
// Now we have the complete HTML including </html>, so set it to be sure
const finalDoc = contentResponse.match(
/<!DOCTYPE html>[\s\S]*<\/html>/
)?.[0];
if (finalDoc) {
setHtml(finalDoc);
}
return;
}
const chunk = decoder.decode(value, { stream: true });
contentResponse += chunk;
const newHtml = contentResponse.match(/<!DOCTYPE html>[\s\S]*/)?.[0];
if (newHtml) {
// Force-close the HTML tag so the iframe doesn't render half-finished markup
let partialDoc = newHtml;
if (!partialDoc.includes("</html>")) {
partialDoc += "\n</html>";
}
// 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();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
setisAiWorking(false);
toast.error(error.message);
if (error.openLogin) {
setOpen(true);
}
}
};
return (
<div
className={`bg-gray-950 rounded-xl py-2 lg:py-2.5 pl-3.5 lg:pl-4 pr-2 lg:pr-2.5 absolute lg:sticky bottom-3 left-3 lg:bottom-4 lg:left-4 w-[calc(100%-1.5rem)] lg:w-[calc(100%-2rem)] z-10 group ${
isAiWorking ? "animate-pulse" : ""
}`}
>
{defaultHTML !== html && (
<button
className="bg-white lg:hidden -translate-y-[calc(100%+8px)] absolute left-0 top-0 shadow-md text-gray-950 text-xs font-medium py-2 px-3 lg:px-4 rounded-lg flex items-center gap-2 border border-gray-100 hover:brightness-150 transition-all duration-100 cursor-pointer"
onClick={() => setView("preview")}
>
<MdPreview className="text-sm" />
View Preview
</button>
)}
<div className="w-full relative flex items-center justify-between">
<RiSparkling2Fill className="text-lg lg:text-xl text-gray-500 group-focus-within:text-pink-500" />
<input
type="text"
disabled={isAiWorking}
className="w-full bg-transparent max-lg:text-sm outline-none px-3 text-white placeholder:text-gray-500 font-code"
placeholder={
hasAsked ? "What do you want to ask AI next?" : "Ask AI anything..."
}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
callAi();
}
}}
/>
<div className="flex items-center justify-end gap-2">
{/* <SpeechPrompt setPrompt={setPrompt} /> */}
<Settings
provider={provider as string}
onChange={setProvider}
open={openProvider}
error={providerError}
onClose={setOpenProvider}
/>
<button
disabled={isAiWorking}
className="relative overflow-hidden cursor-pointer flex-none flex items-center justify-center rounded-full text-sm font-semibold size-8 text-center bg-pink-500 hover:bg-pink-400 text-white shadow-sm dark:shadow-highlight/20 disabled:bg-gray-300 disabled:text-gray-500 disabled:cursor-not-allowed disabled:hover:bg-gray-300"
onClick={callAi}
>
<GrSend className="-translate-x-[1px]" />
</button>
</div>
</div>
<div
className={classNames(
"h-screen w-screen bg-black/20 fixed left-0 top-0 z-10",
{
"opacity-0 pointer-events-none": !open,
}
)}
onClick={() => setOpen(false)}
></div>
<div
className={classNames(
"absolute top-0 -translate-y-[calc(100%+8px)] right-0 z-10 w-80 bg-white border border-gray-200 rounded-lg shadow-lg transition-all duration-75 overflow-hidden",
{
"opacity-0 pointer-events-none": !open,
}
)}
>
<Login html={html}>
<p className="text-gray-500 text-sm mb-3">
You reached the limit of free AI usage. Please login to continue.
</p>
</Login>
</div>
<ProModal
html={html}
open={openProModal}
onClose={() => setOpenProModal(false)}
/>
</div>
);
}
export default AskAI;