import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Plus, Loader2, Bot, FolderCode } from "lucide-react"; import { api, type Project, type Session, type ClaudeMdFile } from "@/lib/api"; import { OutputCacheProvider } from "@/lib/outputCache"; import { TabProvider } from "@/contexts/TabContext"; import { ThemeProvider } from "@/contexts/ThemeContext"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { ProjectList } from "@/components/ProjectList"; import { SessionList } from "@/components/SessionList"; import { RunningClaudeSessions } from "@/components/RunningClaudeSessions"; import { Topbar } from "@/components/Topbar"; import { MarkdownEditor } from "@/components/MarkdownEditor"; import { ClaudeFileEditor } from "@/components/ClaudeFileEditor"; import { Settings } from "@/components/Settings"; import { CCAgents } from "@/components/CCAgents"; import { UsageDashboard } from "@/components/UsageDashboard"; import { MCPManager } from "@/components/MCPManager"; import { NFOCredits } from "@/components/NFOCredits"; import { ClaudeBinaryDialog } from "@/components/ClaudeBinaryDialog"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { ProjectSettings } from '@/components/ProjectSettings'; import { TabManager } from "@/components/TabManager"; import { TabContent } from "@/components/TabContent"; import { AgentsModal } from "@/components/AgentsModal"; import { useTabState } from "@/hooks/useTabState"; type View = | "welcome" | "projects" | "editor" | "claude-file-editor" | "settings" | "cc-agents" | "create-agent" | "github-agents" | "agent-execution" | "agent-run-view" | "mcp" | "usage-dashboard" | "project-settings" | "tabs"; // New view for tab-based interface /** * AppContent component - Contains the main app logic, wrapped by providers */ function AppContent() { const [view, setView] = useState("tabs"); const { createClaudeMdTab, createSettingsTab, createUsageTab, createMCPTab } = useTabState(); const [projects, setProjects] = useState([]); const [selectedProject, setSelectedProject] = useState(null); const [sessions, setSessions] = useState([]); const [editingClaudeFile, setEditingClaudeFile] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showNFO, setShowNFO] = useState(false); const [showClaudeBinaryDialog, setShowClaudeBinaryDialog] = useState(false); const [toast, setToast] = useState<{ message: string; type: "success" | "error" | "info" } | null>(null); const [projectForSettings, setProjectForSettings] = useState(null); const [previousView] = useState("welcome"); const [showAgentsModal, setShowAgentsModal] = useState(false); // Load projects on mount when in projects view useEffect(() => { if (view === "projects") { loadProjects(); } else if (view === "welcome") { // Reset loading state for welcome view setLoading(false); } }, [view]); // Keyboard shortcuts for tab navigation useEffect(() => { if (view !== "tabs") return; const handleKeyDown = (e: KeyboardEvent) => { const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; const modKey = isMac ? e.metaKey : e.ctrlKey; if (modKey) { switch (e.key) { case 't': e.preventDefault(); window.dispatchEvent(new CustomEvent('create-chat-tab')); break; case 'w': e.preventDefault(); window.dispatchEvent(new CustomEvent('close-current-tab')); break; case 'Tab': e.preventDefault(); if (e.shiftKey) { window.dispatchEvent(new CustomEvent('switch-to-previous-tab')); } else { window.dispatchEvent(new CustomEvent('switch-to-next-tab')); } break; default: // Handle number keys 1-9 if (e.key >= '1' && e.key <= '9') { e.preventDefault(); const index = parseInt(e.key) - 1; window.dispatchEvent(new CustomEvent('switch-to-tab-by-index', { detail: { index } })); } break; } } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [view]); // Listen for Claude not found events useEffect(() => { const handleClaudeNotFound = () => { setShowClaudeBinaryDialog(true); }; window.addEventListener('claude-not-found', handleClaudeNotFound as EventListener); return () => { window.removeEventListener('claude-not-found', handleClaudeNotFound as EventListener); }; }, []); /** * Loads all projects from the ~/.claude/projects directory */ const loadProjects = async () => { try { setLoading(true); setError(null); const projectList = await api.listProjects(); setProjects(projectList); } catch (err) { console.error("Failed to load projects:", err); setError("Failed to load projects. Please ensure ~/.claude directory exists."); } finally { setLoading(false); } }; /** * Handles project selection and loads its sessions */ const handleProjectClick = async (project: Project) => { try { setLoading(true); setError(null); const sessionList = await api.getProjectSessions(project.id); setSessions(sessionList); setSelectedProject(project); } catch (err) { console.error("Failed to load sessions:", err); setError("Failed to load sessions for this project."); } finally { setLoading(false); } }; /** * Opens a new Claude Code session in the interactive UI */ const handleNewSession = async () => { handleViewChange("tabs"); // The tab system will handle creating a new chat tab }; /** * Returns to project list view */ const handleBack = () => { setSelectedProject(null); setSessions([]); }; /** * Handles editing a CLAUDE.md file from a project */ const handleEditClaudeFile = (file: ClaudeMdFile) => { setEditingClaudeFile(file); handleViewChange("claude-file-editor"); }; /** * Returns from CLAUDE.md file editor to projects view */ const handleBackFromClaudeFileEditor = () => { setEditingClaudeFile(null); handleViewChange("projects"); }; /** * Handles view changes with navigation protection */ const handleViewChange = (newView: View) => { // No need for navigation protection with tabs since sessions stay open setView(newView); }; /** * Handles navigating to hooks configuration */ const handleProjectSettings = (project: Project) => { setProjectForSettings(project); handleViewChange("project-settings"); }; const renderContent = () => { switch (view) { case "welcome": return (
{/* Welcome Header */}

Welcome to Claudia

{/* Navigation Cards */}
{/* CC Agents Card */} handleViewChange("cc-agents")} >

CC Agents

{/* CC Projects Card */} handleViewChange("projects")} >

CC Projects

); case "cc-agents": return ( handleViewChange("welcome")} /> ); case "editor": return (
handleViewChange("welcome")} />
); case "settings": return (
handleViewChange("welcome")} />
); case "projects": return (
{/* Header with back button */}

CC Projects

Browse your Claude Code sessions

{/* Error display */} {error && ( {error} )} {/* Loading state */} {loading && (
)} {/* Content */} {!loading && ( {selectedProject ? ( ) : ( {/* New session button at the top */} {/* Running Claude Sessions */} {/* Project list */} {projects.length > 0 ? ( ) : (

No projects found in ~/.claude/projects

)}
)}
)}
); case "claude-file-editor": return editingClaudeFile ? ( ) : null; case "tabs": return (
); case "usage-dashboard": return ( handleViewChange("welcome")} /> ); case "mcp": return ( handleViewChange("welcome")} /> ); case "project-settings": if (projectForSettings) { return ( { setProjectForSettings(null); handleViewChange(previousView || "projects"); }} /> ); } break; default: return null; } }; return (
{/* Topbar */} createClaudeMdTab()} onSettingsClick={() => createSettingsTab()} onUsageClick={() => createUsageTab()} onMCPClick={() => createMCPTab()} onInfoClick={() => setShowNFO(true)} onAgentsClick={() => setShowAgentsModal(true)} /> {/* Main Content */}
{renderContent()}
{/* NFO Credits Modal */} {showNFO && setShowNFO(false)} />} {/* Agents Modal */} {/* Claude Binary Dialog */} { setToast({ message: "Claude binary path saved successfully", type: "success" }); // Trigger a refresh of the Claude version check window.location.reload(); }} onError={(message) => setToast({ message, type: "error" })} /> {/* Toast Container */} {toast && ( setToast(null)} /> )}
); } /** * Main App component - Wraps the app with providers */ function App() { return ( ); } export default App;