"use client"; import { appStore } from "@/app/store"; import { useChat, UseChatHelpers } from "@ai-sdk/react"; import { cn } from "lib/utils"; import { ReactNode, useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Button } from "ui/button"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle, } from "ui/drawer"; import PromptInput from "./prompt-input"; import { ErrorMessage, PreviewMessage } from "./message"; import { Settings2, X } from "lucide-react"; import { Separator } from "ui/separator"; import { DefaultChatTransport, UIMessage } from "ai"; import { useShallow } from "zustand/shallow"; import { isShortcutEvent, Shortcuts } from "lib/keyboard-shortcuts"; import { useTranslations } from "next-intl"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger, } from "ui/dialog"; import { DialogTitle } from "@radix-ui/react-dialog"; import { Textarea } from "ui/textarea"; import { Think } from "ui/think"; export function ChatBotTemporary() { const t = useTranslations("Chat.TemporaryChat"); const [temporaryChat, appStoreMutate] = appStore( useShallow((state) => [state.temporaryChat, state.mutate]), ); const [isInstructionsOpen, setIsInstructionsOpen] = useState(false); const setOpen = (bool: boolean) => { appStoreMutate({ temporaryChat: { ...temporaryChat, isOpen: bool, }, }); }; const [input, setInput] = useState(""); const { messages, sendMessage, clearError, status, setMessages, error, stop, } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat/temporary", prepareSendMessagesRequest: ({ messages }) => { const temporaryChat = appStore.getState().temporaryChat; return { body: { chatModel: temporaryChat.chatModel, instructions: temporaryChat.instructions, messages, }, }; }, }), experimental_throttle: 100, onError: () => { setMessages((prev) => prev.slice(0, -1)); }, }); const isLoading = useMemo( () => status === "streaming" || status === "submitted", [status], ); const reset = useCallback(() => { setMessages([]); clearError(); }, []); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (isShortcutEvent(e, Shortcuts.toggleTemporaryChat)) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); appStoreMutate((prev) => ({ temporaryChat: { ...prev.temporaryChat, isOpen: !prev.temporaryChat.isOpen, }, })); } else if ( temporaryChat.isOpen && isShortcutEvent(e, { shortcut: { command: true, key: "e", }, }) ) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); reset(); } else if ( temporaryChat.isOpen && isShortcutEvent(e, { shortcut: { command: true, key: "i", }, }) ) { e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); setIsInstructionsOpen((prev) => !prev); } }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [temporaryChat.isOpen]); return (

{t("temporaryChat")}

{ appStoreMutate({ temporaryChat: { ...temporaryChat, instructions }, }); }} > ); } function DrawerTemporaryContent({ messages, input, setInput, sendMessage, status, error, isLoading, setMessages, stop, }: { messages: UIMessage[]; input: string; setInput: (input: string) => void; sendMessage: UseChatHelpers["sendMessage"]; status: "submitted" | "streaming" | "ready" | "error"; isLoading: boolean; error: Error | undefined; setMessages: UseChatHelpers["setMessages"]; stop: UseChatHelpers["stop"]; }) { const containerRef = useRef(null); const t = useTranslations("Chat"); const autoScrollRef = useRef(false); const [temporaryChat, appStoreMutate] = appStore( useShallow((state) => [state.temporaryChat, state.mutate]), ); useEffect(() => { containerRef.current?.scrollTo({ top: containerRef.current?.scrollHeight, }); }, []); useEffect(() => { if (autoScrollRef.current) { containerRef.current?.scrollTo({ top: containerRef.current?.scrollHeight, }); } }, [messages]); useEffect(() => { if (isLoading) { autoScrollRef.current = true; const handleScroll = () => { const el = containerRef.current!; const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 20; if (!isAtBottom) { autoScrollRef.current = false; } }; containerRef.current?.addEventListener("scroll", handleScroll); return () => { containerRef.current?.removeEventListener("scroll", handleScroll); }; } }, [isLoading]); const setModel = useCallback((model) => { appStoreMutate({ temporaryChat: { ...temporaryChat, chatModel: model, }, }); }, []); useEffect(() => { if (!temporaryChat.chatModel) { appStoreMutate((state) => { if (!state.chatModel) return state; return { temporaryChat: { ...temporaryChat, chatModel: state.chatModel, }, }; }); } }, [Boolean(temporaryChat.chatModel)]); return (
{!messages.length && !error && (
{" "}

{t("TemporaryChat.thisChatWontBeSaved")}

)}
{messages.map((message, index) => { const isLastMessage = messages.length - 1 === index; return ( ); })} {isLoading && (
)} {error && }
); } function TemporaryChatInstructions({ instructions, onSave, children, isOpen, setIsOpen, }: { instructions: string; isOpen: boolean; setIsOpen: (isOpen: boolean) => void; onSave: (instructions: string) => void; children: ReactNode; }) { const [input, setInput] = useState(instructions); const t = useTranslations(); useEffect(() => { if (isOpen) { setInput(instructions); } }, [isOpen]); return ( {children} {t("Chat.TemporaryChat.temporaryChatInstructions")} {t("Chat.TemporaryChat.temporaryChatInstructionsDescription")}