feat(frontend/admin): M10 后台管理面板(/admin hash 路由 + 15 大模块 + RBAC(owner/admin/viewer) + 审计 + Miuix);Docs/api-client.md v0.3 契约补全;tsc+vite build 通过

This commit is contained in:
Aska committed 2026-09-06 07:55:04 +08:00
1 parent 4c48e94659
commit f43eb17030
26 files changed
+2253 -4

No files matched your search

@@ -0,0 +1,106 @@
import { useState } from "react";
import { useAppStore } from "../../store/useAppStore";
import { useAuditStore } from "../audit";
import { Notice, PageHeader } from "../components";
import { Button, EmptyState, Surface, Tag } from "../../components/primitives";
import type { BackthinkItem } from "../../lib/types";
function BackthinkRow({ item }: { item: BackthinkItem }): React.ReactNode {
const markPushed = useAppStore((s) => s.markBackthinkPushed);
const [open, setOpen] = useState(false);
return (
<Surface className="space-y-2">
<div className="flex items-center gap-2 text-sm">
<Tag tone="neutral">{new Date(item.at).toLocaleTimeString()}</Tag>
<span className="font-medium">{item.trigger}</span>
<Tag tone={item.shouldNotify ? "warn" : "neutral"}>{item.shouldNotify ? "需要打扰" : "静默"}</Tag>
<Tag tone={item.pushed ? "ok" : "neutral"}>{item.pushed ? "已推送" : "未推送"}</Tag>
<span className="ml-auto">
<Button size="sm" variant="ghost" disabled={item.pushed} onClick={() => markPushed(item.id)}>
{item.pushed ? "已推送" : "推送结果"}
</Button>
</span>
</div>
{item.conclusion ? (
<div className="rounded-[var(--mu-radius-sm)] bg-[var(--mu-surface-2)] px-3 py-2 text-xs text-[var(--mu-text-mid)]">
{item.conclusion}
</div>
) : null}
<button
className="text-xs text-[var(--mu-accent-hi)]"
onClick={() => setOpen((v) => !v)}
>
{open ? "收起链路" : "查看完整链路导图"}
</button>
{open ? (
<div className="rounded-[var(--mu-radius-lg)] border border-dashed border-[var(--mu-glass-border)] px-4 py-8 text-center text-xs text-[var(--mu-text-lo)]">
链路导图占位 —— 后端 M10+ 提供思维链 / 流程图(graph_update 事件已接入实时通道)。
</div>
) : null}
</Surface>
);
}
export function BackthinkModule(): React.ReactNode {
const backthink = useAppStore((s) => s.backthink);
const log = useAuditStore((s) => s.log);
const [filter, setFilter] = useState<"all" | "notify" | "silent">("all");
const items = backthink.filter((b) => {
if (filter === "notify") return b.shouldNotify === true;
if (filter === "silent") return b.shouldNotify !== true;
return true;
});
return (
<div className="space-y-5">
<PageHeader
title="后台思考与主动"
subtitle="触发源 / 时间 / 结论 / 是否打扰 / 推送结果 · 打扰决策可解释"
actions={
<Button
variant="soft"
onClick={() => {
log("backthink.intervene", "ok");
alert("干预(占位):后端 M10+ 提供打断/重派。");
}}
>
干预
</Button>
}
/>
<div className="flex items-center gap-2">
<FilterPill active={filter === "all"} onClick={() => setFilter("all")}>全部</FilterPill>
<FilterPill active={filter === "notify"} onClick={() => setFilter("notify")}>需打扰</FilterPill>
<FilterPill active={filter === "silent"} onClick={() => setFilter("silent")}>静默</FilterPill>
</div>
{items.length === 0 ? (
<EmptyState icon="🌙" title="暂无后台思考" hint="后台任务完成时按 WS 事件实时呈现(backthink / thinking)。" />
) : (
<div className="space-y-3">
{items.map((b) => (
<BackthinkRow key={b.id} item={b} />
))}
</div>
)}
<Notice>数据源:/ws/chat 下行 backthink / thinking 事件(实时);导图与干预为后端 M10+ 提供。</Notice>
</div>
);
}
function FilterPill({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }): React.ReactNode {
return (
<button
onClick={onClick}
className={`rounded-[var(--mu-radius-pill)] px-3 py-1 text-xs transition-all duration-200 ease-[var(--mu-spring)] ${
active ? "bg-[var(--mu-accent)]/20 font-medium text-[var(--mu-accent-hi)]" : "bg-[var(--mu-surface-4)] text-[var(--mu-text-mid)]"
}`}
>
{children}
</button>
);
}