"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useSession, signIn, signOut } from "next-auth/react"; import Link from "next/link"; import { Search, Settings, LogIn, LogOut, X, Plus, MessageSquare, Loader2, Menu, } from "lucide-react"; export default function HomeDashboard() { const router = useRouter(); const { data: session, status: authStatus } = useSession(); // Core data states const [characters, setCharacters] = useState([]); const [chats, setChats] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [userCredits, setUserCredits] = useState(50); const [showSidebar, setShowSidebar] = useState(false); // Create Character Modal states const [showCreateModal, setShowCreateModal] = useState(false); const [newChar, setNewChar] = useState({ name: "", avatar: "🤖", profile_url: "", description: "", personality: "", systemPrompt: "", greeting: "", is_public: true, }); const [isCreating, setIsCreating] = useState(false); useEffect(() => { fetchInitialData(); }, [authStatus]); useEffect(() => { if (session?.user) { setUserCredits(session.user.credits); } }, [session]); const fetchInitialData = async () => { try { setLoading(true); // Fetch all characters from DB const charRes = await fetch("/api/characters"); const charData = await charRes.json(); if (charData.characters) { setCharacters(charData.characters); } if (authStatus === "authenticated") { // Sync active chats from SQLite/DB const chatRes = await fetch("/api/chats"); const chatData = await chatRes.json(); if (chatData.chats) { setChats(chatData.chats); } } } catch (err) { console.error("Failed to load initial workspace data", err); } finally { setLoading(false); } }; // Triggers creation of a chat session const handleStartChat = async (characterId, characterName) => { if (authStatus !== "authenticated") { signIn("google"); return; } try { const res = await fetch("/api/chats", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ character_id: characterId }), }); const data = await res.json(); if (data.chat) { const slug = characterName.toLowerCase().replace(/ /g, "-"); router.push(`/${slug}/${data.chat.id}`); } } catch (err) { console.error("Failed to instantiate chat thread", err); } }; const handleCreateCharacter = async (e) => { e.preventDefault(); if (authStatus !== "authenticated") { signIn("google"); return; } try { setIsCreating(true); const res = await fetch("/api/characters", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(newChar), }); const data = await res.json(); if (data.character) { setCharacters((prev) => [...prev, data.character]); setShowCreateModal(false); setNewChar({ name: "", avatar: "🤖", profile_url: "", description: "", personality: "", systemPrompt: "", greeting: "", is_public: true, }); await handleStartChat(data.character.id, data.character.name); } } catch (error) { console.error(error); } finally { setIsCreating(false); } }; const executeUpgrade = () => { setUserCredits((prev) => prev + 100); setShowUpgradeModal(false); alert( "Successfully upgraded to c.ai+! Added 100 premium credits to your balance.", ); }; // Derive unique recent characters from chat history const recentCharactersMap = new Map(); chats.forEach((c) => { if (c.character && !recentCharactersMap.has(c.character.id)) { recentCharactersMap.set(c.character.id, c.character); } }); const recentCharacters = Array.from(recentCharactersMap.values()); // Filter characters based on search const filteredCharacters = characters.filter((c) => { const q = searchQuery.toLowerCase(); return ( c.name.toLowerCase().includes(q) || c.description.toLowerCase().includes(q) ); }); return (
{/* MOBILE BACKDROP */} {showSidebar && (
setShowSidebar(false)} /> )} {/* 1. SIDE NAVIGATION BAR */} {/* 2. MAIN CORE VIEWPORT */}
{/* HEADER BAR */}
Welcome back,

{session?.user?.name || "Guest"}

setSearchQuery(e.target.value)} placeholder="Search characters..." className="w-full pl-10 pr-4 py-2.5 bg-bg-card border border-divider/50 rounded-full text-xs focus:outline-none focus:bg-bg-card-hover focus:border-primary/50 text-primary-text placeholder-secondary-text transition duration-150" />
{/* CHARACTER GRID */}

Explore Characters

{filteredCharacters.map((char, idx) => (
handleStartChat(char.id, char.name)} className="bg-bg-card border border-divider/50 rounded p-2 flex gap-4 hover:border-primary/50 hover:bg-bg-card-hover transition duration-200 cursor-pointer shadow-lg" > {/* Character visual image */}
{char.profileUrl || (char.avatar.length > 2 && char.avatar.startsWith("http")) ? ( {char.name} ) : ( char.avatar )}
{/* Info block */}

{char.name}

{char.isCustom ? "Community Character" : "Official Preset"}

{char.description}

{/* Bottom metrics */}
Chat Now
))}
{filteredCharacters.length === 0 && !loading && (

No characters found matching "{searchQuery}".

)} {loading && (
Loading characters...
)}
{/* CREATE CHARACTER MODAL */} {showCreateModal && (

Create Custom Character

setNewChar({ ...newChar, name: e.target.value }) } type="text" className="w-full bg-bg-page border border-divider/50 rounded p-2.5 text-sm text-primary-text focus:outline-none focus:border-primary/50 transition placeholder-secondary-text" placeholder="e.g. Master Chief" />
setNewChar({ ...newChar, avatar: e.target.value }) } type="text" className="w-full bg-bg-page border border-divider/50 rounded p-2.5 text-sm text-primary-text focus:outline-none focus:border-primary/50 transition placeholder-secondary-text" placeholder="🤖" />
setNewChar({ ...newChar, profile_url: e.target.value }) } type="text" className="w-full bg-bg-page border border-divider/50 rounded p-2.5 text-sm text-primary-text focus:outline-none focus:border-primary/50 transition placeholder-secondary-text" placeholder="https://example.com/image.png" />
setNewChar({ ...newChar, description: e.target.value }) } type="text" className="w-full bg-bg-page border border-divider/50 rounded p-2.5 text-sm text-primary-text focus:outline-none focus:border-primary/50 transition placeholder-secondary-text" placeholder="A brief tagline shown in the UI." />
setNewChar({ ...newChar, personality: e.target.value }) } type="text" className="w-full bg-bg-page border border-divider/50 rounded p-2.5 text-sm text-primary-text focus:outline-none focus:border-primary/50 transition placeholder-secondary-text" placeholder="e.g. Sarcastic, brave, funny, protective." />