- main.py:Executor(sandbox, http_client=…) 参数名与构造签名不符(http=…),致乾坤沙箱装配降级 503——修正 - 注册页手写 fetch 多拼一层 v1(/api/proxy/v1/auth/register → 后端 /api/v1/v1/* 404);BACKEND_ROOT 已含 /api/v1,proxy 调用不应再带——去掉 - /chat 页 M2b 已随线性会话退役,对话主入口为 /trees(镜树);六处残留链接(首页/登录/注册跳转/错误页/404 页/管理壳)全部改指 /trees - 回归:前端 type-check + 203 passed;后端百工+API 套件 45 passed
238 lines
7.6 KiB
TypeScript
238 lines
7.6 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { usePathname } from "next/navigation";
|
||
import { useCallback } from "react";
|
||
|
||
import type { ReactNode } from "react";
|
||
|
||
import {
|
||
AdminIcon,
|
||
AgentIcon,
|
||
AuditIcon,
|
||
GoalIcon,
|
||
MemoryIcon,
|
||
PerceptionIcon,
|
||
PlanIcon,
|
||
TenantIcon,
|
||
ToolsIcon,
|
||
TraceIcon,
|
||
UserIcon,
|
||
WorldIcon,
|
||
} from "@/components/ui/Icons";
|
||
import { cn } from "@/lib/utils/cn";
|
||
|
||
type AdminNavItem = { href: string; label: string; icon: ReactNode };
|
||
|
||
/**
|
||
* 司命台治理导航(横向 — 设计稿 topbar-nav)。
|
||
*/
|
||
const ADMIN_NAV: AdminNavItem[] = [
|
||
{ href: "/admin", label: "概览", icon: <AdminIcon /> },
|
||
{ href: "/admin/audit", label: "起居注", icon: <AuditIcon /> },
|
||
{ href: "/admin/traces", label: "链路追踪", icon: <TraceIcon /> },
|
||
{ href: "/admin/users", label: "用户管理", icon: <UserIcon /> },
|
||
{ href: "/admin/tenants", label: "多租户", icon: <TenantIcon /> },
|
||
{
|
||
href: "/admin/characters/templates",
|
||
label: "人物模板",
|
||
icon: <AdminIcon />,
|
||
},
|
||
];
|
||
|
||
/** 九枢治理导航(设计稿 pivot nav)。与 pivots/[pivot] 路由对齐 */
|
||
const PIVOT_NAV: AdminNavItem[] = [
|
||
{ href: "/admin/goals", label: "太一", icon: <GoalIcon /> },
|
||
{ href: "/admin/safety", label: "司命", icon: <AdminIcon /> },
|
||
{ href: "/admin/perception", label: "烛照", icon: <PerceptionIcon /> },
|
||
{ href: "/admin/world", label: "河图", icon: <WorldIcon /> },
|
||
{ href: "/admin/plans", label: "洛书", icon: <PlanIcon /> },
|
||
{ href: "/admin/memory", label: "息壤", icon: <MemoryIcon /> },
|
||
{ href: "/admin/tools", label: "百工", icon: <ToolsIcon /> },
|
||
{ href: "/admin/agents", label: "天工阁", icon: <AgentIcon /> },
|
||
];
|
||
|
||
/**
|
||
* 司命台外壳:独立冷色壳层(admin-shell → slate 色温分化)。
|
||
* 设计稿 admin-preview.html §4 AdminShell 完全对齐。
|
||
*
|
||
* 壳层特性:
|
||
* - 独立冷色 CSS 变量(.admin-shell 激活 `--slate` 色系)
|
||
* - 顶部横向双层导航(治理 nav + 九枢 pivot nav)
|
||
* - 主题切换 + 返回主域入口
|
||
* - iOS safe-area 适配
|
||
*/
|
||
export function AdminShell({ children }: { children: ReactNode }) {
|
||
const pathname = usePathname();
|
||
|
||
/** 设计稿 toggleTheme — 通过触发表单更改 / 自定义事件 */
|
||
const toggleTheme = useCallback(() => {
|
||
const root = document.documentElement;
|
||
const isDark = root.classList.contains("dark");
|
||
if (isDark) {
|
||
root.classList.remove("dark");
|
||
} else {
|
||
root.classList.add("dark");
|
||
}
|
||
// 持久化到 settings
|
||
try {
|
||
const raw = localStorage.getItem("hunjing-settings");
|
||
if (raw) {
|
||
const parsed = JSON.parse(raw);
|
||
if (parsed.state) {
|
||
parsed.state.theme = isDark ? "light" : "dark";
|
||
localStorage.setItem("hunjing-settings", JSON.stringify(parsed));
|
||
}
|
||
}
|
||
} catch {
|
||
// silent
|
||
}
|
||
}, []);
|
||
|
||
const navItemClass = (href: string, opts?: { pivot?: boolean }) =>
|
||
cn(
|
||
"inline-flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium transition-colors shrink-0",
|
||
opts?.pivot ? "text-xs" : "text-sm",
|
||
pathname === href || pathname.startsWith(`${href}/`)
|
||
? "bg-patina-wash text-patina"
|
||
: "text-muted hover:bg-overlay-hover hover:text-fg",
|
||
);
|
||
|
||
return (
|
||
<div className="relative flex min-h-screen flex-col admin-shell">
|
||
<header
|
||
className="sticky top-0 z-30 flex flex-wrap items-center gap-2 border-b px-4 py-2.5 md:gap-3 md:px-6"
|
||
style={{
|
||
background: "var(--glass)",
|
||
backdropFilter: "blur(16px) saturate(140%)",
|
||
borderColor: "var(--rule)",
|
||
paddingLeft: "calc(1rem + env(safe-area-inset-left, 0px))",
|
||
paddingRight: "calc(1rem + env(safe-area-inset-right, 0px))",
|
||
}}
|
||
>
|
||
{/* 品牌标志 — 设计稿 topbar-brand */}
|
||
<Link
|
||
href="/admin"
|
||
className="flex items-center gap-1.5 font-serif text-base font-bold tracking-wide text-fg shrink-0"
|
||
style={{ letterSpacing: "0.06em" }}
|
||
>
|
||
{/* 设计稿 SVG knob:描边铜镜无极标记 */}
|
||
<svg
|
||
className="shrink-0"
|
||
width="16"
|
||
height="16"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="1.6"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
>
|
||
<circle cx="12" cy="12" r="8.5" />
|
||
<circle cx="12" cy="12" r="4.5" opacity="0.45" />
|
||
<circle
|
||
cx="12"
|
||
cy="12"
|
||
r="1.6"
|
||
stroke="none"
|
||
fill="var(--cinnabar)"
|
||
/>
|
||
</svg>
|
||
司命台
|
||
</Link>
|
||
|
||
{/* 分隔符 */}
|
||
<span className="shrink-0 text-fg-faint opacity-50 select-none">/</span>
|
||
|
||
{/* 司命治理导航 — 设计稿 topbar-nav */}
|
||
<nav
|
||
aria-label="司命台导航"
|
||
className="order-3 flex w-full items-center gap-0.5 overflow-x-auto md:order-none md:w-auto md:flex-shrink"
|
||
>
|
||
{ADMIN_NAV.map((item) => (
|
||
<Link
|
||
key={item.href}
|
||
href={item.href}
|
||
className={navItemClass(item.href)}
|
||
>
|
||
{item.label}
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
|
||
{/* 九枢分隔 + 导航 — 设计稿中金色竖线 | 分隔 */}
|
||
<span className="mx-1 hidden shrink-0 select-none text-fg-faint opacity-40 md:inline">
|
||
|
|
||
</span>
|
||
<nav
|
||
aria-label="九枢导航"
|
||
className="order-4 flex w-full items-center gap-0.5 overflow-x-auto md:order-none md:w-auto"
|
||
>
|
||
{PIVOT_NAV.map((item) => (
|
||
<Link
|
||
key={item.href}
|
||
href={item.href}
|
||
className={navItemClass(item.href, { pivot: true })}
|
||
>
|
||
{item.label}
|
||
</Link>
|
||
))}
|
||
</nav>
|
||
|
||
{/* 弹性间隔 */}
|
||
<span className="flex-1 min-w-0" />
|
||
|
||
{/* 返回主域 — 设计稿 topbar-back */}
|
||
<Link
|
||
href="/trees"
|
||
className="flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-overlay-hover hover:text-fg"
|
||
title="返回主域"
|
||
>
|
||
<svg
|
||
width="12"
|
||
height="12"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
aria-hidden
|
||
>
|
||
<path
|
||
d="M10 4l-4 4 4 4"
|
||
stroke="currentColor"
|
||
strokeWidth="1.4"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
主域
|
||
</Link>
|
||
|
||
{/* 主题切换 — 设计稿 topbar-back 风格 */}
|
||
<button
|
||
type="button"
|
||
onClick={toggleTheme}
|
||
className="flex shrink-0 items-center gap-1 rounded-lg px-2 py-1 text-xs font-medium text-muted transition-colors hover:bg-overlay-hover hover:text-fg"
|
||
title="切换主题"
|
||
>
|
||
<svg
|
||
width="13"
|
||
height="13"
|
||
viewBox="0 0 16 16"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
strokeWidth="1.3"
|
||
strokeLinecap="round"
|
||
>
|
||
<circle cx="8" cy="8" r="3" />
|
||
<path d="M8 1v2M8 13v2M2 8h2M12 8h2M3.5 3.5l1.5 1.5M11 11l1.5 1.5M3.5 12.5l1.5-1.5M11 5l1.5-1.5" />
|
||
</svg>
|
||
<span id="theme-label-admin">{/* 客户端渲染时显示当前主题 */}</span>
|
||
</button>
|
||
</header>
|
||
|
||
<main className="flex min-h-0 flex-1 flex-col overflow-hidden">
|
||
{children}
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|