- Added configuration options for requiring human approval before applying LLM-generated MCP patches. - Updated Docker setup to include skills directory. - Integrated skills management into the backend, allowing for procedural guides and skill matching. - Refactored database initialization to apply Alembic migrations. - Enhanced task approval process to handle MCP patch applications with optional approval. - Introduced new schemas for skills and updated existing APIs to support skills functionality. This commit lays the groundwork for improved agent capabilities and better management of MCP development processes.
121 lines
4.3 KiB
TypeScript
121 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
import { useAuth, roleAtLeast } from "@/lib/auth";
|
|
|
|
const NAV = [
|
|
{ href: "/", label: "Dashboard", icon: "▦", min: "readonly" as const },
|
|
{ href: "/tasks", label: "Tasks", icon: "✦", min: "readonly" as const },
|
|
{ href: "/inventory", label: "Vessels", icon: "▤", min: "readonly" as const },
|
|
{ href: "/mcp", label: "MCP Servers", icon: "⚇", min: "readonly" as const },
|
|
{ href: "/rules", label: "Rules", icon: "☰", min: "support" as const },
|
|
{ href: "/skills", label: "Skills", icon: "◎", min: "readonly" as const },
|
|
{ href: "/models", label: "Models", icon: "◈", min: "admin" as const },
|
|
{ href: "/users", label: "Users", icon: "♟", min: "admin" as const },
|
|
];
|
|
|
|
export default function AppShell({ children }: { children: React.ReactNode }) {
|
|
const { user, loading, logout } = useAuth();
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const [navOpen, setNavOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!loading && !user) router.replace("/login");
|
|
}, [loading, user, router]);
|
|
|
|
// Close the mobile drawer on navigation.
|
|
useEffect(() => {
|
|
setNavOpen(false);
|
|
}, [pathname]);
|
|
|
|
if (loading || !user) {
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center gap-2 text-muted">
|
|
<span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
Loading…
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const sidebar = (
|
|
<>
|
|
<div className="mb-8 flex items-center gap-2 px-2">
|
|
<div className="grid h-9 w-9 place-items-center rounded-xl bg-gradient-to-br from-accent to-accent2 font-bold text-white">
|
|
A
|
|
</div>
|
|
<div>
|
|
<div className="text-sm font-semibold">Agentic OS</div>
|
|
<div className="text-[11px] text-muted">troubleshooting</div>
|
|
</div>
|
|
</div>
|
|
<nav className="flex flex-1 flex-col gap-1">
|
|
{NAV.filter((n) => roleAtLeast(user.role, n.min)).map((n) => {
|
|
const active = pathname === n.href || (n.href !== "/" && pathname.startsWith(n.href));
|
|
return (
|
|
<Link
|
|
key={n.href}
|
|
href={n.href}
|
|
aria-current={active ? "page" : undefined}
|
|
className={`flex items-center gap-3 rounded-xl px-3 py-2 text-sm transition ${
|
|
active ? "bg-accent/15 text-white" : "text-slate-300 hover:bg-panel2"
|
|
}`}
|
|
>
|
|
<span className="text-base opacity-80" aria-hidden="true">
|
|
{n.icon}
|
|
</span>
|
|
{n.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
<div className="mt-4 border-t border-edge pt-4">
|
|
<div className="px-2 text-sm">{user.full_name || user.email}</div>
|
|
<div className="mb-3 px-2 text-[11px] uppercase tracking-wide text-muted">{user.role}</div>
|
|
<button onClick={logout} className="btn-ghost w-full">
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
return (
|
|
<div className="flex min-h-screen">
|
|
{/* Mobile top bar */}
|
|
<header className="fixed inset-x-0 top-0 z-30 flex items-center gap-3 border-b border-edge bg-panel/90 px-4 py-3 backdrop-blur md:hidden">
|
|
<button
|
|
aria-label="Open menu"
|
|
aria-expanded={navOpen}
|
|
onClick={() => setNavOpen(true)}
|
|
className="btn-ghost btn-sm"
|
|
>
|
|
☰
|
|
</button>
|
|
<span className="text-sm font-semibold">Agentic OS</span>
|
|
</header>
|
|
|
|
{/* Mobile drawer */}
|
|
{navOpen && (
|
|
<div className="fixed inset-0 z-40 md:hidden" onClick={() => setNavOpen(false)}>
|
|
<div className="absolute inset-0 bg-black/60" />
|
|
<aside
|
|
className="absolute left-0 top-0 flex h-full w-64 flex-col border-r border-edge bg-panel p-4"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{sidebar}
|
|
</aside>
|
|
</div>
|
|
)}
|
|
|
|
{/* Desktop sidebar */}
|
|
<aside className="hidden w-60 flex-col border-r border-edge bg-panel/60 p-4 md:flex">
|
|
{sidebar}
|
|
</aside>
|
|
|
|
<main className="flex-1 overflow-y-auto p-4 pt-20 md:p-8 md:pt-8">{children}</main>
|
|
</div>
|
|
);
|
|
}
|