import { useState, useEffect, useCallback } from 'react'; import { Play, Square, Trash2, FolderOpen, ChevronRight, ChevronDown, Settings, Server, Plus, X, Folder, ArrowUp, Loader2, LogOut, User, Shield } from 'lucide-react'; import { useSessionManager } from '../contexts/SessionContext'; import { useAuth } from '../contexts/AuthContext'; const RECENT_DIRS_KEY = 'claude-webui-recent-dirs'; const MAX_RECENT_DIRS = 10; // Load recent directories from localStorage function loadRecentDirs() { try { const stored = localStorage.getItem(RECENT_DIRS_KEY); return stored ? JSON.parse(stored) : {}; } catch { return {}; } } // Save recent directories to localStorage function saveRecentDirs(dirs) { try { localStorage.setItem(RECENT_DIRS_KEY, JSON.stringify(dirs)); } catch (e) { console.error('Failed to save recent dirs:', e); } } // Add a directory to recent list for a host function addRecentDir(hostId, path) { const recent = loadRecentDirs(); const hostRecent = recent[hostId] || []; const filtered = hostRecent.filter(p => p !== path); const updated = [path, ...filtered].slice(0, MAX_RECENT_DIRS); recent[hostId] = updated; saveRecentDirs(recent); return updated; } export function Sidebar({ open, onToggle }) { const { focusedSessionId, focusedSession, startClaudeSession, stopClaudeSession, clearMessages, updateSessionConfig, } = useSessionManager(); const { user, authEnabled, logout, isAdmin } = useAuth(); const [hosts, setHosts] = useState([]); const [recentDirs, setRecentDirs] = useState([]); const [showBrowser, setShowBrowser] = useState(false); const [browserPath, setBrowserPath] = useState('~'); const [browserDirs, setBrowserDirs] = useState([]); const [browserLoading, setBrowserLoading] = useState(false); const [browserError, setBrowserError] = useState(null); const [dropdownOpen, setDropdownOpen] = useState(false); // Current session values const currentHost = focusedSession?.host || 'neko'; const currentProject = focusedSession?.project || '/home/sumdex/projects'; const sessionActive = focusedSession?.active || false; const resumeSession = focusedSession?.resumeOnStart ?? true; // Fetch hosts on mount useEffect(() => { fetch('/api/hosts', { credentials: 'include' }) .then(res => res.json()) .then(data => { setHosts(data.hosts || []); }) .catch(console.error); }, []); // Load recent directories when host changes useEffect(() => { if (!currentHost) return; const recent = loadRecentDirs(); setRecentDirs(recent[currentHost] || []); }, [currentHost]); // Handle selecting a directory const handleSelectDir = useCallback((path) => { if (!focusedSessionId) return; updateSessionConfig(focusedSessionId, { project: path }); const updated = addRecentDir(currentHost, path); setRecentDirs(updated); setDropdownOpen(false); setShowBrowser(false); }, [focusedSessionId, currentHost, updateSessionConfig]); // Handle host change const handleSelectHost = useCallback((hostId) => { if (!focusedSessionId || sessionActive) return; updateSessionConfig(focusedSessionId, { host: hostId }); }, [focusedSessionId, sessionActive, updateSessionConfig]); // Handle resume toggle const handleToggleResume = useCallback(() => { if (!focusedSessionId) return; updateSessionConfig(focusedSessionId, { resumeOnStart: !resumeSession }); }, [focusedSessionId, resumeSession, updateSessionConfig]); // Browse directories on host const browsePath = useCallback(async (path) => { if (!currentHost) return; setBrowserLoading(true); setBrowserError(null); try { const res = await fetch(`/api/browse?host=${currentHost}&path=${encodeURIComponent(path)}`, { credentials: 'include', }); const data = await res.json(); if (data.error) { setBrowserError(data.error); return; } setBrowserPath(data.currentPath); setBrowserDirs(data.directories || []); } catch (err) { setBrowserError(err.message); } finally { setBrowserLoading(false); } }, [currentHost]); // Open browser const openBrowser = useCallback(() => { setShowBrowser(true); setDropdownOpen(false); browsePath('~'); }, [browsePath]); // Get display name for path const getDisplayName = (path) => { const parts = path.split('/'); return parts[parts.length - 1] || path; }; // Start/stop session handlers const handleStartSession = useCallback(() => { if (focusedSessionId) { startClaudeSession(focusedSessionId); } }, [focusedSessionId, startClaudeSession]); const handleStopSession = useCallback(() => { if (focusedSessionId) { stopClaudeSession(focusedSessionId); } }, [focusedSessionId, stopClaudeSession]); const handleClearMessages = useCallback(() => { if (focusedSessionId) { clearMessages(focusedSessionId); } }, [focusedSessionId, clearMessages]); // No session selected if (!focusedSession) { return ( ); } return ( ); }