import { useState, useRef, useEffect } from "react"; import { ChevronUp, ChevronDown, Send, Search, Zap, Upload, X } from "lucide-react"; import { Streamdown } from "streamdown"; import ChatSidebar, { ChatSession } from "@/components/ChatSidebar"; /** * Chat Page with Sidebar and Local Storage * * Features: * - Ask | Imagine mode switcher at top * - Sidebar with chat history (local storage) * - Advanced prompt input with Search/Think toggles * - DeepSeek reasoning panel (collapsible) * - Rich response formatting * - File upload with OCR * - Image gallery for Imagine mode */ type ChatMode = "ask" | "imagine"; interface Message { id: string; role: "user" | "assistant"; content: string; reasoning?: string; timestamp: Date; } interface GeneratedImage { id: string; prompt: string; url: string; timestamp: Date; } export default function Chat() { // ======================================================================== // State Management // ======================================================================== const [mode, setMode] = useState("ask"); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); const [enableSearch, setEnableSearch] = useState(false); const [enableThinking, setEnableThinking] = useState(false); const [showReasoning, setShowReasoning] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); const [generatedImages, setGeneratedImages] = useState([]); const [galleryOpen, setGalleryOpen] = useState(false); const [sidebarOpen, setSidebarOpen] = useState(true); const [currentChatId, setCurrentChatId] = useState(null); const messagesEndRef = useRef(null); const fileInputRef = useRef(null); // ======================================================================== // API Configuration - UPDATE THIS WITH YOUR HUGGING FACE SPACE URL // ======================================================================== const API_BASE_URL = process.env.REACT_APP_API_URL || "http://localhost:3000"; // ======================================================================== // Local Storage Functions // ======================================================================== const generateChatId = () => `chat_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const saveChatToStorage = (chatId: string, msgs: Message[], title: string) => { // Save chat messages localStorage.setItem(`domify_chat_${chatId}`, JSON.stringify(msgs)); // Update chat metadata const savedChats = localStorage.getItem("domify_chats"); let chats: ChatSession[] = savedChats ? JSON.parse(savedChats) : []; const existingIndex = chats.findIndex((c) => c.id === chatId); const chatSession: ChatSession = { id: chatId, title: title || "Untitled Chat", timestamp: Date.now(), mode, messageCount: msgs.length, }; if (existingIndex >= 0) { chats[existingIndex] = chatSession; } else { chats.push(chatSession); } localStorage.setItem("domify_chats", JSON.stringify(chats)); }; const loadChatFromStorage = (chatId: string) => { const savedMessages = localStorage.getItem(`domify_chat_${chatId}`); if (savedMessages) { try { const parsed = JSON.parse(savedMessages); const msgs = parsed.map((m: any) => ({ ...m, timestamp: new Date(m.timestamp), })); setMessages(msgs); setCurrentChatId(chatId); } catch (error) { console.error("Error loading chat:", error); } } }; const createNewChat = () => { const chatId = generateChatId(); setMessages([]); setCurrentChatId(chatId); setGeneratedImages([]); setInput(""); }; // ======================================================================== // Auto-save chat to storage when messages change // ======================================================================== useEffect(() => { if (currentChatId && messages.length > 0) { const title = messages[0]?.content?.substring(0, 50) || "New Chat"; saveChatToStorage(currentChatId, messages, title); } }, [messages, currentChatId]); // ======================================================================== // Auto-scroll to latest message // ======================================================================== useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // ======================================================================== // Handle file upload // ======================================================================== const handleFileUpload = (e: React.ChangeEvent) => { const files = Array.from(e.target.files || []); setUploadedFiles((prev) => [...prev, ...files]); }; const removeFile = (index: number) => { setUploadedFiles((prev) => prev.filter((_, i) => i !== index)); }; // ======================================================================== // Handle message send (Ask mode) // ======================================================================== const handleSendMessage = async () => { if (!input.trim() && uploadedFiles.length === 0) return; // Create new chat if none exists if (!currentChatId) { createNewChat(); } const userMessage: Message = { id: Date.now().toString(), role: "user", content: input, timestamp: new Date(), }; setMessages((prev) => [...prev, userMessage]); setInput(""); setUploadedFiles([]); setIsLoading(true); try { // Call your backend API const response = await fetch(`${API_BASE_URL}/api/trpc/chat.send`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: input, enableSearch, enableThinking, history: messages.map((m) => ({ role: m.role, content: m.content, })), }), }); const data = await response.json(); if (data.result?.data) { const assistantMessage: Message = { id: (Date.now() + 1).toString(), role: "assistant", content: data.result.data.response, reasoning: data.result.data.reasoning, timestamp: new Date(), }; setMessages((prev) => [...prev, assistantMessage]); if (data.result.data.reasoning) { setShowReasoning(true); } } } catch (error) { console.error("Error sending message:", error); const errorMessage: Message = { id: (Date.now() + 1).toString(), role: "assistant", content: "Sorry, there was an error processing your request. Please try again.", timestamp: new Date(), }; setMessages((prev) => [...prev, errorMessage]); } finally { setIsLoading(false); } }; // ======================================================================== // Handle image generation (Imagine mode) // ======================================================================== const handleGenerateImage = async () => { if (!input.trim()) return; // Create new chat if none exists if (!currentChatId) { createNewChat(); } setIsLoading(true); try { const response = await fetch(`${API_BASE_URL}/api/trpc/imagine.generate`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: input }), }); const data = await response.json(); if (data.result?.data?.imageUrl) { const newImage: GeneratedImage = { id: Date.now().toString(), prompt: input, url: data.result.data.imageUrl, timestamp: new Date(), }; setGeneratedImages((prev) => [...prev, newImage]); setInput(""); setGalleryOpen(true); // Save to storage const chatId = currentChatId!; const savedChats = localStorage.getItem("domify_chats"); let chats: ChatSession[] = savedChats ? JSON.parse(savedChats) : []; const existingIndex = chats.findIndex((c) => c.id === chatId); if (existingIndex >= 0) { chats[existingIndex].messageCount = generatedImages.length + 1; localStorage.setItem("domify_chats", JSON.stringify(chats)); } } } catch (error) { console.error("Error generating image:", error); } finally { setIsLoading(false); } }; // ======================================================================== // Render: Ask Mode // ======================================================================== if (mode === "ask") { return (
{/* Sidebar */} { setMessages([]); setCurrentChatId(null); }} isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} /> {/* Main Content */}
{/* Header */}

Domify Academy Bot

{/* Messages Area */}
{messages.length === 0 ? (

Start a conversation with Domify Academy Bot

Ask questions, get reasoning, search online, and more

) : ( messages.map((msg) => (
{/* Reasoning Panel (if available) */} {msg.reasoning && msg.role === "assistant" && (
{showReasoning && (
{msg.reasoning}
)}
)} {/* Message Content */}
{msg.content}
{/* Timestamp */}

{msg.timestamp.toLocaleTimeString()}

)) )} {isLoading && (
)}
{/* Input Area */}
{/* File Preview */} {uploadedFiles.length > 0 && (
{uploadedFiles.map((file, idx) => (
{file.name}
))}
)} {/* Toggle Buttons */}
{/* Input Box */}