"use client"; import { useCallback, useEffect, useRef, useImperativeHandle, forwardRef, useState } from "react"; import { useAuth } from "@/lib/auth/auth-context"; import { useChat } from "@/hooks/useChat"; import { useWebSocket } from "@/hooks/useWebSocket"; import { MessageList } from "./MessageList"; import { ChatInput } from "./ChatInput"; import type { Message } from "@/hooks/useChat"; export interface ChatRef { loadConversation: (conversationId: string) => Promise; startNewConversation: (projectId?: string | null) => void; getCurrentConversationId: () => string | null; } export interface NewConversationData { id: string; title: string | null; project_id: string | null; created_at: string; updated_at: string; } interface ChatProps { onConversationChange?: ( conversationId: string | null, conversationData?: NewConversationData ) => void; onProjectChange?: () => void; initialProjectId?: string | null; onInitialProjectHandled?: () => void; } const WAITING_QUIPS = [ "The AI is warming up... give it a moment.", "Loading the neural pathways...", "Waking up the LLM. It's not a morning model.", "Brewing some thoughts...", "The AI is stretching its parameters...", "Summoning intelligence from the void...", "Teaching electrons to think...", "Consulting the silicon oracle...", "The hamsters are spinning up the GPU...", "Defragmenting the neural networks...", ]; export const Chat = forwardRef(function Chat( { onConversationChange, onProjectChange: _onProjectChange, initialProjectId, onInitialProjectHandled: _onInitialProjectHandled, }, ref ) { void _onProjectChange; void _onInitialProjectHandled; const { user, isLoading: authLoading } = useAuth(); // Use the chat hook for state management const { messages, isLoading: isChatLoading, error, conversationId, conversationTitle, sendMessage, loadConversation, startNewConversation, clearError, } = useChat({ model: "llama3.2", ...(initialProjectId !== undefined && { projectId: initialProjectId }), }); // Connect to WebSocket for real-time updates (when we have a user) const { isConnected: isWsConnected } = useWebSocket( user?.id ?? "", // Use user ID as workspace ID for now "", // Token not needed since we use cookies { // Future: Add handlers for chat-related events // onChatMessage: (msg) => { ... } } ); const messagesEndRef = useRef(null); const inputRef = useRef(null); const [loadingQuip, setLoadingQuip] = useState(null); const quipTimerRef = useRef(null); const quipIntervalRef = useRef(null); // Expose methods to parent via ref useImperativeHandle(ref, () => ({ loadConversation: async (cId: string): Promise => { await loadConversation(cId); }, startNewConversation: (projectId?: string | null): void => { startNewConversation(projectId); }, getCurrentConversationId: (): string | null => conversationId, })); const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, []); useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); // Notify parent of conversation changes useEffect(() => { if (conversationId && conversationTitle) { onConversationChange?.(conversationId, { id: conversationId, title: conversationTitle, project_id: initialProjectId ?? null, created_at: new Date().toISOString(), updated_at: new Date().toISOString(), }); } else { onConversationChange?.(null); } }, [conversationId, conversationTitle, initialProjectId, onConversationChange]); // Global keyboard shortcut: Ctrl+/ to focus input useEffect(() => { const handleKeyDown = (e: KeyboardEvent): void => { if ((e.ctrlKey || e.metaKey) && e.key === "/") { e.preventDefault(); inputRef.current?.focus(); } }; document.addEventListener("keydown", handleKeyDown); return (): void => { document.removeEventListener("keydown", handleKeyDown); }; }, []); // Show loading quips useEffect(() => { if (isChatLoading) { // Show first quip after 3 seconds quipTimerRef.current = setTimeout(() => { setLoadingQuip(WAITING_QUIPS[Math.floor(Math.random() * WAITING_QUIPS.length)] ?? null); }, 3000); // Change quip every 5 seconds quipIntervalRef.current = setInterval(() => { setLoadingQuip(WAITING_QUIPS[Math.floor(Math.random() * WAITING_QUIPS.length)] ?? null); }, 5000); } else { // Clear timers when loading stops if (quipTimerRef.current) { clearTimeout(quipTimerRef.current); quipTimerRef.current = null; } if (quipIntervalRef.current) { clearInterval(quipIntervalRef.current); quipIntervalRef.current = null; } setLoadingQuip(null); } return (): void => { if (quipTimerRef.current) clearTimeout(quipTimerRef.current); if (quipIntervalRef.current) clearInterval(quipIntervalRef.current); }; }, [isChatLoading]); const handleSendMessage = useCallback( async (content: string) => { await sendMessage(content); }, [sendMessage] ); // Show loading state while auth is loading if (authLoading) { return (
Loading...
); } return (
{/* Connection Status Indicator */} {user && !isWsConnected && (
Reconnecting to server...
)} {/* Messages Area */}
{/* Error Alert */} {error && (
{error}
)} {/* Input Area */}
); });