import { Card, Stack, Text } from "@mantine/core"; import { usePubSub } from "create-pubsub/react"; import type { ChatMessage } from "gpt-tokenizer/GptEncoding"; import { type KeyboardEvent, Suspense, lazy, useCallback, useEffect, useState, } from "react"; import { ErrorBoundary } from "react-error-boundary"; import { generateFollowUpQuestion } from "../../modules/followUpQuestions"; import { handleEnterKeyDown } from "../../modules/keyboard"; import { addLogEntry } from "../../modules/logEntries"; import { chatGenerationStatePubSub, chatInputPubSub, followUpQuestionPubSub, settingsPubSub, } from "../../modules/pubSub"; import { generateChatResponse } from "../../modules/textGeneration"; const ChatHeader = lazy(() => import("./ChatHeader")); const MessageList = lazy(() => import("./MessageList")); const ChatInputArea = lazy(() => import("./ChatInputArea")); export interface ChatInterfaceProps { initialQuery?: string; initialResponse?: string; } export default function ChatInterface({ initialQuery, initialResponse, }: ChatInterfaceProps) { const [messages, setMessages] = useState([]); const [input, setInput] = usePubSub(chatInputPubSub); const [generationState, setGenerationState] = usePubSub( chatGenerationStatePubSub, ); const [, setFollowUpQuestion] = usePubSub(followUpQuestionPubSub); const [settings] = usePubSub(settingsPubSub); const [streamedResponse, setStreamedResponse] = useState(""); const regenerateFollowUpQuestion = useCallback( async (currentQuery: string, currentResponse: string) => { if (!currentResponse || !currentQuery.trim()) return; try { setGenerationState({ isGeneratingResponse: false, isGeneratingFollowUpQuestion: true, }); const newQuestion = await generateFollowUpQuestion({ topic: currentQuery, currentContent: currentResponse, }); setFollowUpQuestion(newQuestion); setGenerationState({ isGeneratingResponse: false, isGeneratingFollowUpQuestion: false, }); } catch (error) { setFollowUpQuestion(""); setGenerationState({ isGeneratingResponse: false, isGeneratingFollowUpQuestion: false, }); } }, [setFollowUpQuestion, setGenerationState], ); useEffect(() => { if (messages.length === 0 && initialQuery && initialResponse) { setMessages([ { role: "user", content: initialQuery }, { role: "assistant", content: initialResponse }, ]); regenerateFollowUpQuestion(initialQuery, initialResponse); } }, [ initialQuery, initialResponse, messages.length, regenerateFollowUpQuestion, ]); const handleSend = async (textToSend?: string) => { const currentInput = textToSend ?? input; if (currentInput.trim() === "" || generationState.isGeneratingResponse) return; const userMessage: ChatMessage = { role: "user", content: currentInput }; const newMessages: ChatMessage[] = [...messages, userMessage]; setMessages(newMessages); setInput(textToSend ? input : ""); setGenerationState({ ...generationState, isGeneratingResponse: true, }); setFollowUpQuestion(""); setStreamedResponse(""); try { const finalResponse = await generateChatResponse( newMessages, (partialResponse) => { setStreamedResponse(partialResponse); }, ); setMessages((prevMessages) => [ ...prevMessages, { role: "assistant", content: finalResponse }, ]); addLogEntry("AI response completed"); await regenerateFollowUpQuestion(currentInput, finalResponse); } catch (error) { addLogEntry(`Error in chat response: ${error}`); setMessages((prevMessages) => [ ...prevMessages, { role: "assistant", content: "Sorry, I encountered an error while generating a response.", }, ]); } finally { setGenerationState({ ...generationState, isGeneratingResponse: false, }); } }; const handleKeyDown = (event: KeyboardEvent) => { handleEnterKeyDown(event, settings, handleSend); }; return ( Loading header...}> Chat interface failed to load} > Loading messages...}> Loading input area...}> ); }