import { NavLink, useNavigate } from "react-router-dom" import { useTranslation } from "react-i18next" import { FolderOpen, MessageSquare, Users, Microscope, LogOut, ClipboardCheck, Bot, Plus, ChevronDown, ChevronRight, Settings, HelpCircle, ChevronsUpDown } from "lucide-react" import { cn } from "@/lib/utils" import { useAuthStore } from "@/store/auth" import { authApi, ragApi, usersApi, agentProjectsApi, type AgentProject } from "@/lib/api" import { Button } from "@/components/ui/button" import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "@/components/ui/dropdown-menu" import SettingsDialog from "@/components/settings/SettingsDialog" import i18n from "@/i18n" import { useState, useEffect } from "react" const links = [ { to: "/projects", label: "nav.projects", icon: FolderOpen }, { to: "/agent", label: "nav.agent", icon: Bot }, { to: "/rag", label: "nav.rag", icon: MessageSquare }, { to: "/compliance", label: "nav.compliance", icon: ClipboardCheck }, ] const adminLinks = [ { to: "/admin/users", label: "nav.admin", icon: Users }, ] function getInitials(name: string | undefined): string { if (!name) return "?" return name .split(" ") .filter(Boolean) .slice(0, 2) .map(w => w[0].toUpperCase()) .join("") } export default function Sidebar() { const { t } = useTranslation() const { user, logout } = useAuthStore() const navigate = useNavigate() const [agentProjects, setAgentProjects] = useState([]) const [agentExpanded, setAgentExpanded] = useState(true) const [newProjectTitle, setNewProjectTitle] = useState("") const [creatingProject, setCreatingProject] = useState(false) const [showNewProjectInput, setShowNewProjectInput] = useState(false) const [models, setModels] = useState([]) const [currentModel, setCurrentModel] = useState("") const [currentOcrModel, setCurrentOcrModel] = useState("easyocr") const [currentVlmModel, setCurrentVlmModel] = useState("qwen3-vl:8b") const [ollamaUp, setOllamaUp] = useState(null) const [refreshing, setRefreshing] = useState(false) // OpenAI state const [openaiModels, setOpenaiModels] = useState<{ llm: string[]; vlm: string[]; embed: string[] }>({ llm: [], vlm: [], embed: [] }) const [currentEmbedModel, setCurrentEmbedModel] = useState("nomic-embed-text") const [openaiKeyConfigured, setOpenaiKeyConfigured] = useState(false) const [showKeyInput, setShowKeyInput] = useState(false) const [keyDraft, setKeyDraft] = useState("") const [savingKey, setSavingKey] = useState(false) const [keyError, setKeyError] = useState("") // Settings dialog const [settingsOpen, setSettingsOpen] = useState(false) async function loadModels() { setRefreshing(true) try { const r = await ragApi.status() setOllamaUp(r.data.ollama_reachable) setModels(r.data.models) setOpenaiModels({ llm: r.data.openai_llm_models ?? [], vlm: r.data.openai_vlm_models ?? [], embed: r.data.openai_embed_models ?? [], }) } catch { setOllamaUp(false) } finally { setRefreshing(false) } } useEffect(() => { loadModels() agentProjectsApi.listProjects().then(r => setAgentProjects(r.data)).catch(() => {}) usersApi.getSettings().then(async r => { const s = r.data setOpenaiKeyConfigured(s.openai_key_configured) if (s.rag_model) { setCurrentModel(s.rag_model) await ragApi.setModel(s.rag_model).catch(() => {}) } else { ragApi.getModel().then(r2 => setCurrentModel(r2.data.model)).catch(() => {}) } if (s.vlm_model) { setCurrentVlmModel(s.vlm_model) await ragApi.setVlmModel(s.vlm_model).catch(() => {}) } else { ragApi.getVlmModel().then(r2 => setCurrentVlmModel(r2.data.vlm_model)).catch(() => {}) } if (s.ocr_model) { setCurrentOcrModel(s.ocr_model) await ragApi.setOcrModel(s.ocr_model).catch(() => {}) } else { ragApi.getOcrModel().then(r2 => setCurrentOcrModel(r2.data.ocr_model)).catch(() => {}) } if (s.embed_model) { setCurrentEmbedModel(s.embed_model) await ragApi.setEmbedModel(s.embed_model).catch(() => {}) } else { ragApi.getEmbedModel().then(r2 => setCurrentEmbedModel(r2.data.embed_model)).catch(() => {}) } }).catch(() => { ragApi.getModel().then(r => setCurrentModel(r.data.model)).catch(() => {}) ragApi.getOcrModel().then(r => setCurrentOcrModel(r.data.ocr_model)).catch(() => {}) ragApi.getVlmModel().then(r => setCurrentVlmModel(r.data.vlm_model)).catch(() => {}) ragApi.getEmbedModel().then(r => setCurrentEmbedModel(r.data.embed_model)).catch(() => {}) }) }, []) async function handleEmbedModelChange(e: React.ChangeEvent) { const model = e.target.value setCurrentEmbedModel(model) try { await ragApi.setEmbedModel(model) } catch { /* no-op */ } usersApi.saveModelPreferences({ embed_model: model }).catch(() => {}) } async function handleSaveKey(e: React.FormEvent) { e.preventDefault() setKeyError("") setSavingKey(true) try { await usersApi.saveOpenAIKey(keyDraft) setOpenaiKeyConfigured(true) setShowKeyInput(false) setKeyDraft("") await loadModels() } catch { setKeyError(t("config.openai_key_error")) } finally { setSavingKey(false) } } async function handleDeleteKey() { try { await usersApi.deleteOpenAIKey() setOpenaiKeyConfigured(false) await loadModels() } catch { // no-op } } async function handleCreateProject() { if (!newProjectTitle.trim()) return setCreatingProject(true) try { const { data } = await agentProjectsApi.createProject(newProjectTitle.trim()) setAgentProjects(prev => [data, ...prev]) setNewProjectTitle("") setShowNewProjectInput(false) navigate(`/agent/projects/${data.id}`) } catch { // no-op } finally { setCreatingProject(false) } } async function handleModelChange(e: React.ChangeEvent) { const model = e.target.value setCurrentModel(model) try { await ragApi.setModel(model) } catch { /* no-op */ } usersApi.saveModelPreferences({ rag_model: model }).catch(() => {}) } async function handleOcrModelChange(e: React.ChangeEvent) { const model = e.target.value setCurrentOcrModel(model) try { await ragApi.setOcrModel(model) } catch { /* no-op */ } usersApi.saveModelPreferences({ ocr_model: model }).catch(() => {}) } async function handleVlmModelChange(e: React.ChangeEvent) { const model = e.target.value setCurrentVlmModel(model) try { await ragApi.setVlmModel(model) } catch { /* no-op */ } usersApi.saveModelPreferences({ vlm_model: model }).catch(() => {}) } async function handleLogout() { try { await authApi.logout() } catch { /* no-op */ } logout() } function toggleLang() { i18n.changeLanguage(i18n.language === "it" ? "en" : "it") } const initials = getInitials(user?.full_name) return ( ) }