import { useMemo } from "react"; import { Routes, Route, NavLink, Navigate } from "react-router-dom"; import { Activity, BarChart3, Clock, FileText, KeyRound, MessageSquare, Package, Settings, Puzzle, Sparkles, Terminal, Globe, Database, Shield, Wrench, Zap, Heart, Star, Code, Eye, } from "lucide-react"; import StatusPage from "@/pages/StatusPage"; import ConfigPage from "@/pages/ConfigPage"; import EnvPage from "@/pages/EnvPage"; import SessionsPage from "@/pages/SessionsPage"; import LogsPage from "@/pages/LogsPage"; import AnalyticsPage from "@/pages/AnalyticsPage"; import CronPage from "@/pages/CronPage"; import SkillsPage from "@/pages/SkillsPage"; import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { ThemeSwitcher } from "@/components/ThemeSwitcher"; import { useI18n } from "@/i18n"; import { usePlugins } from "@/plugins"; import type { RegisteredPlugin } from "@/plugins"; // --------------------------------------------------------------------------- // Built-in nav items // --------------------------------------------------------------------------- interface NavItem { path: string; label: string; labelKey?: string; icon: React.ComponentType<{ className?: string }>; } const BUILTIN_NAV: NavItem[] = [ { path: "/", labelKey: "status", label: "Status", icon: Activity }, { path: "/sessions", labelKey: "sessions", label: "Sessions", icon: MessageSquare }, { path: "/analytics", labelKey: "analytics", label: "Analytics", icon: BarChart3 }, { path: "/logs", labelKey: "logs", label: "Logs", icon: FileText }, { path: "/cron", labelKey: "cron", label: "Cron", icon: Clock }, { path: "/skills", labelKey: "skills", label: "Skills", icon: Package }, { path: "/config", labelKey: "config", label: "Config", icon: Settings }, { path: "/env", labelKey: "keys", label: "Keys", icon: KeyRound }, ]; // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Map of icon names plugins can use. Covers common choices without importing all of lucide. */ const ICON_MAP: Record> = { Activity, BarChart3, Clock, FileText, KeyRound, MessageSquare, Package, Settings, Puzzle, Sparkles, Terminal, Globe, Database, Shield, Wrench, Zap, Heart, Star, Code, Eye, }; /** Resolve a Lucide icon name to a component, fallback to Puzzle. */ function resolveIcon(name: string): React.ComponentType<{ className?: string }> { return ICON_MAP[name] ?? Puzzle; } /** Insert plugin nav items at the position specified in their manifest. */ function buildNavItems(builtIn: NavItem[], plugins: RegisteredPlugin[]): NavItem[] { const items = [...builtIn]; for (const { manifest } of plugins) { const pluginItem: NavItem = { path: manifest.tab.path, label: manifest.label, icon: resolveIcon(manifest.icon), }; const pos = manifest.tab.position ?? "end"; if (pos === "end") { items.push(pluginItem); } else if (pos.startsWith("after:")) { const target = "/" + pos.slice(6); const idx = items.findIndex((i) => i.path === target); items.splice(idx >= 0 ? idx + 1 : items.length, 0, pluginItem); } else if (pos.startsWith("before:")) { const target = "/" + pos.slice(7); const idx = items.findIndex((i) => i.path === target); items.splice(idx >= 0 ? idx : items.length, 0, pluginItem); } else { items.push(pluginItem); } } return items; } // --------------------------------------------------------------------------- // App // --------------------------------------------------------------------------- export default function App() { const { t } = useI18n(); const { plugins } = usePlugins(); const navItems = useMemo( () => buildNavItems(BUILTIN_NAV, plugins), [plugins], ); return (
Hermes Agent
{t.app.webUi}
} /> } /> } /> } /> } /> } /> } /> } /> {/* Plugin routes */} {plugins.map(({ manifest, component: PluginComponent }) => ( } /> ))} } />
); }