"use client"; import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; import { Message } from "ai"; import { useChat } from "ai/react"; import { useRef, useState, ReactElement } from "react"; import type { FormEvent } from "react"; import { ChatMessageBubble } from "@/components/ChatMessageBubble"; import { IntermediateStep } from "./IntermediateStep"; export function ChatWindow(props: { endpoint: string; emptyStateComponent: ReactElement; placeholder?: string; titleText?: string; emoji?: string; showIntermediateStepsToggle?: boolean; }) { const messageContainerRef = useRef(null); const { endpoint, emptyStateComponent, placeholder, titleText = "An LLM", showIntermediateStepsToggle, emoji, } = props; const [showIntermediateSteps, setShowIntermediateSteps] = useState(false); const [intermediateStepsLoading, setIntermediateStepsLoading] = useState(false); const intemediateStepsToggle = showIntermediateStepsToggle && (
setShowIntermediateSteps(e.target.checked)} >
); const [sourcesForMessages, setSourcesForMessages] = useState< Record >({}); const { messages, input, setInput, handleInputChange, handleSubmit, isLoading: chatEndpointIsLoading, setMessages, } = useChat({ api: endpoint, onResponse(response) { const sourcesHeader = response.headers.get("x-sources"); const sources = sourcesHeader ? JSON.parse(Buffer.from(sourcesHeader, "base64").toString("utf8")) : []; const messageIndexHeader = response.headers.get("x-message-index"); if (sources.length && messageIndexHeader !== null) { setSourcesForMessages({ ...sourcesForMessages, [messageIndexHeader]: sources, }); } }, streamMode: "text", onError: (e) => { toast(e.message, { theme: "dark", }); }, }); async function sendMessage(e: FormEvent) { e.preventDefault(); if (messageContainerRef.current) { messageContainerRef.current.classList.add("grow"); } if (!messages.length) { await new Promise((resolve) => setTimeout(resolve, 300)); } if (chatEndpointIsLoading ?? intermediateStepsLoading) { return; } if (!showIntermediateSteps) { handleSubmit(e); // Some extra work to show intermediate steps properly } else { setIntermediateStepsLoading(true); setInput(""); const messagesWithUserReply = messages.concat({ id: messages.length.toString(), content: input, role: "user", }); setMessages(messagesWithUserReply); const response = await fetch(endpoint, { method: "POST", body: JSON.stringify({ messages: messagesWithUserReply, show_intermediate_steps: true, }), }); const json = await response.json(); setIntermediateStepsLoading(false); if (response.status === 200) { const responseMessages: Message[] = json.messages; // Represent intermediate steps as system messages for display purposes // TODO: Add proper support for tool messages const toolCallMessages = responseMessages.filter( (responseMessage: Message) => { return ( (responseMessage.role === "assistant" && !!responseMessage.tool_calls?.length) || responseMessage.role === "tool" ); }, ); const intermediateStepMessages = []; for (let i = 0; i < toolCallMessages.length; i += 2) { const aiMessage = toolCallMessages[i]; const toolMessage = toolCallMessages[i + 1]; intermediateStepMessages.push({ id: (messagesWithUserReply.length + i / 2).toString(), role: "system" as const, content: JSON.stringify({ action: aiMessage.tool_calls?.[0], observation: toolMessage.content, }), }); } const newMessages = messagesWithUserReply; for (const message of intermediateStepMessages) { newMessages.push(message); setMessages([...newMessages]); await new Promise((resolve) => setTimeout(resolve, 1000 + Math.random() * 1000), ); } setMessages([ ...newMessages, { id: newMessages.length.toString(), content: responseMessages[responseMessages.length - 1].content, role: "assistant", }, ]); } else { if (json.error) { toast(json.error, { theme: "dark", }); throw new Error(json.error); } } } } return (
0 ? "border" : ""}`} >

0 ? "" : "hidden"} text-2xl`}> {emoji} {titleText}

{messages.length === 0 ? emptyStateComponent : ""}
{messages.length > 0 ? [...messages].reverse().map((m, i) => { const sourceKey = (messages.length - 1 - i).toString(); return m.role === "system" ? ( ) : ( ); }) : ""}
{intemediateStepsToggle}
); }