import React, { useEffect, useRef, useState } from 'react'; import { getApiUrl } from '../config'; import BottomMenu from './BottomMenu'; import FlappyGoose from './FlappyGoose'; import GooseMessage from './GooseMessage'; import Input from './Input'; import { type View } from '../App'; import LoadingGoose from './LoadingGoose'; import MoreMenu from './MoreMenu'; import { Card } from './ui/card'; import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area'; import UserMessage from './UserMessage'; import { askAi } from '../utils/askAI'; import Splash from './Splash'; import 'react-toastify/dist/ReactToastify.css'; import { useMessageStream } from '../hooks/useMessageStream'; import { Message, createUserMessage, getTextContent } from '../types/message'; export interface ChatType { id: number; title: string; messages: Message[]; } export default function ChatView({ setView }: { setView: (view: View) => void }) { const [chat, setChat] = useState(() => { return { id: 1, title: 'Chat 1', messages: [], }; }); const [messageMetadata, setMessageMetadata] = useState>({}); const [hasMessages, setHasMessages] = useState(false); const [lastInteractionTime, setLastInteractionTime] = useState(Date.now()); const [showGame, setShowGame] = useState(false); const scrollRef = useRef(null); const { messages, append, stop, isLoading, error, setMessages, input: _input, setInput: _setInput, handleInputChange: _handleInputChange, handleSubmit: _submitMessage, } = useMessageStream({ api: getApiUrl('/reply'), initialMessages: chat?.messages || [], onFinish: async (message, _reason) => { window.electron.stopPowerSaveBlocker(); // Extract text content from the message to pass to askAi const messageText = getTextContent(message); const fetchResponses = await askAi(messageText); setMessageMetadata((prev) => ({ ...prev, [message.id || '']: fetchResponses })); const timeSinceLastInteraction = Date.now() - lastInteractionTime; window.electron.logInfo('last interaction:' + lastInteractionTime); if (timeSinceLastInteraction > 60000) { // 60000ms = 1 minute window.electron.showNotification({ title: 'Goose finished the task.', body: 'Click here to expand.', }); } }, onToolCall: (toolCall) => { // Handle tool calls if needed console.log('Tool call received:', toolCall); // Implement tool call handling logic here }, }); // Update chat messages when they change useEffect(() => { setChat((prevChat) => ({ ...prevChat, messages })); }, [messages]); useEffect(() => { if (messages.length > 0) { setHasMessages(true); } }, [messages]); // Handle submit const handleSubmit = (e: React.FormEvent) => { window.electron.startPowerSaveBlocker(); const customEvent = e as CustomEvent; const content = customEvent.detail?.value || ''; if (content.trim()) { setLastInteractionTime(Date.now()); append(createUserMessage(content)); if (scrollRef.current?.scrollToBottom) { scrollRef.current.scrollToBottom(); } } }; if (error) { console.log('Error:', error); } const onStopGoose = () => { stop(); setLastInteractionTime(Date.now()); window.electron.stopPowerSaveBlocker(); // Handle stopping the message stream const lastMessage = messages[messages.length - 1]; if (lastMessage && lastMessage.role === 'user') { // Remove the last user message if it's the most recent one if (messages.length > 1) { setMessages(messages.slice(0, -1)); } else { setMessages([]); } } // Note: Tool call interruption handling would need to be implemented // differently with the new message format }; // Filter out standalone tool response messages for rendering // They will be shown as part of the tool invocation in the assistant message const filteredMessages = messages.filter((message) => { // Keep all assistant messages and user messages that aren't just tool responses if (message.role === 'assistant') return true; // For user messages, check if they're only tool responses if (message.role === 'user') { const hasOnlyToolResponses = message.content.every((c) => c.type === 'toolResponse'); const hasTextContent = message.content.some((c) => c.type === 'text'); // Keep the message if it has text content or is not just tool responses return hasTextContent || !hasOnlyToolResponses; } return true; }); return (
{messages.length === 0 ? ( append(createUserMessage(text))} /> ) : ( {filteredMessages.map((message, index) => (
{message.role === 'user' ? ( ) : ( append(createUserMessage(text))} /> )}
))} {error && (
{error.message || 'Honk! Goose experienced an error while responding'}
{ // Find the last user message const lastUserMessage = messages.reduceRight( (found, m) => found || (m.role === 'user' ? m : null), null as Message | null ); if (lastUserMessage) { append(lastUserMessage); } }} > Retry Last Message
)}
)}
{isLoading && }
{showGame && setShowGame(false)} />}
); }