"use client"; import React, { useState, useRef, useEffect, useCallback } from "react"; import { useYuukiTheme } from "@/lib/theme-context"; import { ChatMessage, type ChatMsg } from "./chat-message"; import { ModelSelector } from "./model-selector"; import { ThemePanel } from "./theme-panel"; import { Send, Palette, Globe, Youtube, Loader2, Plus, Trash2, MessageSquare, Settings, X, } from "lucide-react"; import { cn } from "@/lib/utils"; interface Conversation { id: string; title: string; messages: ChatMsg[]; } export function ChatWindow() { const { accentColor } = useYuukiTheme(); const [conversations, setConversations] = useState([ { id: "1", title: "New Chat", messages: [] }, ]); const [activeConvId, setActiveConvId] = useState("1"); const [model, setModel] = useState("yuuki-best"); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); const [themeOpen, setThemeOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(false); const [researchEnabled, setResearchEnabled] = useState(false); const [youtubeEnabled, setYoutubeEnabled] = useState(false); const messagesEndRef = useRef(null); const inputRef = useRef(null); const activeConv = conversations.find((c) => c.id === activeConvId) || conversations[0]; const messages = activeConv.messages; const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, []); useEffect(() => { scrollToBottom(); }, [messages, scrollToBottom]); const updateMessages = (convId: string, msgs: ChatMsg[]) => { setConversations((prev) => prev.map((c) => (c.id === convId ? { ...c, messages: msgs } : c)) ); }; const updateTitle = (convId: string, firstMsg: string) => { setConversations((prev) => prev.map((c) => c.id === convId && c.title === "New Chat" ? { ...c, title: firstMsg.slice(0, 30) + (firstMsg.length > 30 ? "..." : "") } : c ) ); }; const createNewChat = () => { const id = Date.now().toString(); setConversations((prev) => [...prev, { id, title: "New Chat", messages: [] }]); setActiveConvId(id); setSidebarOpen(false); }; const deleteConversation = (id: string) => { setConversations((prev) => { const next = prev.filter((c) => c.id !== id); if (next.length === 0) { const newConv = { id: Date.now().toString(), title: "New Chat", messages: [] }; setActiveConvId(newConv.id); return [newConv]; } if (id === activeConvId) setActiveConvId(next[0].id); return next; }); }; const sendMessage = async () => { if (!input.trim() || loading) return; const userMsg: ChatMsg = { id: Date.now().toString(), role: "user", content: input.trim(), }; const newMessages = [...messages, userMsg]; updateMessages(activeConvId, newMessages); updateTitle(activeConvId, input.trim()); const currentInput = input.trim(); setInput(""); setLoading(true); // Resize textarea if (inputRef.current) inputRef.current.style.height = "auto"; try { // If research is enabled, do a parallel research call if (researchEnabled) { const resRes = await fetch("/api/research", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: currentInput }), }); const resData = await resRes.json(); if (resRes.ok) { const researchMsg: ChatMsg = { id: `res-${Date.now()}`, role: "assistant", content: resData.answer || "Research completed", type: "research", researchData: resData, }; const updated = [...newMessages, researchMsg]; updateMessages(activeConvId, updated); setLoading(false); return; } } // If YouTube is enabled, do a YouTube search if (youtubeEnabled) { const ytRes = await fetch("/api/youtube", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: currentInput }), }); const ytData = await ytRes.json(); if (ytRes.ok && ytData.videos?.length > 0) { const ytMsg: ChatMsg = { id: `yt-${Date.now()}`, role: "assistant", content: `Found ${ytData.videos.length} videos for "${currentInput}"`, type: "youtube", youtubeData: ytData, }; const updated = [...newMessages, ytMsg]; updateMessages(activeConvId, updated); setLoading(false); return; } } // Default: chat with model const streamingMsg: ChatMsg = { id: `ast-${Date.now()}`, role: "assistant", content: "", isStreaming: true, }; updateMessages(activeConvId, [...newMessages, streamingMsg]); const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages: newMessages.map((m) => ({ role: m.role, content: m.content })), model, }), }); const data = await response.json(); if (!response.ok) { const errorMsg: ChatMsg = { id: `err-${Date.now()}`, role: "assistant", content: `Error: ${data.error || "Something went wrong"}`, }; updateMessages(activeConvId, [...newMessages, errorMsg]); } else { const assistantMsg: ChatMsg = { id: data.id || `ast-${Date.now()}`, role: "assistant", content: data.content, }; updateMessages(activeConvId, [...newMessages, assistantMsg]); } } catch (err) { const errorMsg: ChatMsg = { id: `err-${Date.now()}`, role: "assistant", content: `Connection error: ${err instanceof Error ? err.message : "Please try again"}`, }; updateMessages(activeConvId, [...newMessages, errorMsg]); } finally { setLoading(false); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } }; const handleInputChange = (e: React.ChangeEvent) => { setInput(e.target.value); // Auto resize const el = e.target; el.style.height = "auto"; el.style.height = Math.min(el.scrollHeight, 150) + "px"; }; return (
{/* Sidebar overlay for mobile */} {sidebarOpen && (
setSidebarOpen(false)} /> )} {/* Sidebar */} {/* Main chat area */}
{/* Top bar */}
open
{/* Research toggle */} {/* YouTube toggle */} {/* Theme button (desktop only) */}
{/* Messages */}
{messages.length === 0 ? (
Y

Yuuki Chat

{researchEnabled ? "Research mode is active. Ask anything to search the web." : youtubeEnabled ? "YouTube mode is active. Search for videos." : `Using ${model.replace("yuuki-", "Yuuki ")} model. Start typing to chat.` }

{["Tell me about yourself", "Write a short poem", "Explain quantum computing"].map((s) => ( ))}
) : (
{messages.map((msg) => ( ))} {loading && (
)}
)}
{/* Input area */}
{/* Active mode indicator */} {(researchEnabled || youtubeEnabled) && (
{researchEnabled && ( <> Research mode active - results from Tavily )} {youtubeEnabled && ( <> YouTube mode active - searching videos )}
)}