"use client"; import { useEffect, useState, useCallback } from "react"; import { FileManagerTree } from "./workspace/file-manager-tree"; import { ProfileSwitcher } from "./workspace/profile-switcher"; import { CreateWorkspaceDialog } from "./workspace/create-workspace-dialog"; // --- Types --- type WebSession = { id: string; title: string; createdAt: number; updatedAt: number; messageCount: number; }; type SkillEntry = { name: string; description: string; emoji?: string; source: string; }; type MemoryFile = { name: string; sizeBytes: number; }; type TreeNode = { name: string; path: string; type: "object" | "document" | "folder" | "file" | "database" | "report"; icon?: string; defaultView?: "table" | "kanban"; children?: TreeNode[]; }; type SidebarSection = "chats" | "skills" | "memories" | "workspace" | "reports"; type SidebarProps = { onSessionSelect?: (sessionId: string) => void; onNewSession?: () => void; activeSessionId?: string; refreshKey?: number; }; // --- Helpers --- function timeAgo(ts: number): string { const diff = Date.now() - ts; const seconds = Math.floor(diff / 1000); if (seconds < 60) {return `${seconds}s ago`;} const minutes = Math.floor(seconds / 60); if (minutes < 60) {return `${minutes}m ago`;} const hours = Math.floor(minutes / 60); if (hours < 24) {return `${hours}h ago`;} const days = Math.floor(hours / 24); return `${days}d ago`; } // --- Section Components --- function ChatsSection({ sessions, onSessionSelect, activeSessionId, }: { sessions: WebSession[]; onSessionSelect?: (sessionId: string) => void; activeSessionId?: string; }) { const [searchTerm, setSearchTerm] = useState(""); const filteredSessions = sessions.filter((s) => s.title.toLowerCase().includes(searchTerm.toLowerCase()), ); return (
{sessions.length > 3 && (
setSearchTerm(e.target.value)} placeholder="Search chats..." className="w-full px-3 py-1.5 text-xs bg-[var(--color-bg)] border border-[var(--color-border)] rounded-md text-[var(--color-text)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-1 focus:ring-[var(--color-accent)] focus:border-transparent" />
)} {filteredSessions.length === 0 ? (

{searchTerm ? "No matching chats." : "No chats yet. Send a message to start."}

) : (
{filteredSessions.map((s) => { const isActive = s.id === activeSessionId; return (
onSessionSelect?.(s.id)} className={`mx-2 px-3 py-2 rounded-lg hover:bg-[var(--color-surface-hover)] cursor-pointer transition-colors ${ isActive ? "bg-[var(--color-surface-hover)] border-l-2 border-[var(--color-accent)]" : "" }`} >
{s.title} {timeAgo(s.updatedAt)}
{s.messageCount > 0 && (

{s.messageCount} message{s.messageCount !== 1 ? "s" : ""}

)}
); })}
)}
); } function SkillsSection({ skills }: { skills: SkillEntry[] }) { if (skills.length === 0) { return

No skills found.

; } return (
{skills.map((skill) => (
{skill.emoji && {skill.emoji}} {skill.name} {skill.source}
{skill.description && (

{skill.description}

)}
))}
); } function MemoriesSection({ mainMemory, dailyLogs, }: { mainMemory: string | null; dailyLogs: MemoryFile[]; }) { const [expanded, setExpanded] = useState(false); return (
{mainMemory ? (
{expanded && (
              {mainMemory}
            
)}
) : (

No MEMORY.md found.

)} {dailyLogs.length > 0 && (

Daily logs ({dailyLogs.length})

{dailyLogs.slice(0, 10).map((log) => (
{log.name} {(log.sizeBytes / 1024).toFixed(1)}kb
))} {dailyLogs.length > 10 && (

...and {dailyLogs.length - 10} more

)}
)}
); } // --- Workspace Section (uses FileManagerTree in compact mode) --- function WorkspaceSection({ tree, onRefresh }: { tree: TreeNode[]; onRefresh: () => void }) { const handleSelect = useCallback((node: TreeNode) => { // Navigate to workspace page for actionable items if (node.type === "object" || node.type === "document" || node.type === "file" || node.type === "database" || node.type === "report") { window.location.href = `/workspace?path=${encodeURIComponent(node.path)}`; } }, []); if (tree.length === 0) { return (

No workspace data yet.

); } return (
{/* Full workspace link */} Open full workspace
); } // --- Reports Section --- function ReportsSection({ tree }: { tree: TreeNode[] }) { // Collect all report nodes from the tree (recursive) const reports: TreeNode[] = []; function collect(nodes: TreeNode[]) { for (const n of nodes) { if (n.type === "report") {reports.push(n);} if (n.children) {collect(n.children);} } } collect(tree); if (reports.length === 0) { return (

No reports yet. Ask the agent to create one.

); } return (
{reports.map((report) => ( {report.name.replace(/\.report\.json$/, "")} ))}
); } // --- Collapsible Header --- function SectionHeader({ title, count, isOpen, onToggle, }: { title: string; count?: number; isOpen: boolean; onToggle: () => void; }) { return ( ); } // --- Main Sidebar --- export function Sidebar({ onSessionSelect, onNewSession, activeSessionId, refreshKey, }: SidebarProps) { const [openSections, setOpenSections] = useState>(new Set(["chats", "workspace"])); const [webSessions, setWebSessions] = useState([]); const [skills, setSkills] = useState([]); const [mainMemory, setMainMemory] = useState(null); const [dailyLogs, setDailyLogs] = useState([]); const [workspaceTree, setWorkspaceTree] = useState([]); const [loading, setLoading] = useState(true); const [showCreateWorkspace, setShowCreateWorkspace] = useState(false); const [sidebarRefreshKey, setSidebarRefreshKey] = useState(0); const toggleSection = (section: SidebarSection) => { setOpenSections((prev) => { const next = new Set(prev); if (next.has(section)) {next.delete(section);} else {next.add(section);} return next; }); }; // Full sidebar re-fetch after profile switch or workspace creation const handleProfileSwitch = useCallback(() => { setSidebarRefreshKey((k) => k + 1); }, []); // Fetch sidebar data (re-runs when refreshKey or sidebarRefreshKey changes) useEffect(() => { async function load() { setLoading(true); try { const [webSessionsRes, skillsRes, memoriesRes, workspaceRes] = await Promise.all([ fetch("/api/web-sessions").then((r) => r.json()), fetch("/api/skills").then((r) => r.json()), fetch("/api/memories").then((r) => r.json()), fetch("/api/workspace/tree").then((r) => r.json()).catch(() => ({ tree: [] })), ]); setWebSessions(webSessionsRes.sessions ?? []); setSkills(skillsRes.skills ?? []); setMainMemory(memoriesRes.mainMemory ?? null); setDailyLogs(memoriesRes.dailyLogs ?? []); setWorkspaceTree(workspaceRes.tree ?? []); } catch (err) { console.error("Failed to load sidebar data:", err); } finally { setLoading(false); } } void load(); }, [refreshKey, sidebarRefreshKey]); const refreshWorkspace = useCallback(async () => { try { const res = await fetch("/api/workspace/tree"); const data = await res.json(); setWorkspaceTree(data.tree ?? []); } catch { // ignore } }, []); return ( ); }