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

+99
View File
@@ -0,0 +1,99 @@
import { useState } from "react";
import { useAuditStore } from "../audit";
import { Notice, PageHeader } from "../components";
import { Button, Surface, Tag } from "../../components/primitives";
interface DocRow {
id: string;
name: string;
chunks: number;
embedded: boolean;
}
const docs: DocRow[] = [
{ id: "1", name: "owner_identity.md", chunks: 1, embedded: false },
{ id: "2", name: "开发时间线.md", chunks: 6, embedded: false },
{ id: "3", name: "design/10-后台管理面板.md", chunks: 2, embedded: false },
];
export function RagModule(): React.ReactNode {
const log = useAuditStore((s) => s.log);
const [query, setQuery] = useState("");
const [hits, setHits] = useState<string[]>([]);
return (
<div className="space-y-5">
<PageHeader
title="知识库 RAG"
subtitle="文档列表(分块/嵌入状态) + 索引 + 检索测试"
actions={
<Button
variant="primary"
onClick={() => {
log("rag.upload", "ok");
alert("上传文档(占位):后端 M10+ 提供分块/嵌入。");
}}
>
上传文档
</Button>
}
/>
<Surface className="space-y-2">
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold">文档列表</h3>
<span className="text-xs text-[var(--mu-text-lo)]">{docs.length} 篇</span>
</div>
{docs.map((d) => (
<div key={d.id} className="flex items-center gap-2 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm">
<span className="font-medium">{d.name}</span>
<span className="text-xs text-[var(--mu-text-lo)]">{d.chunks} 分块</span>
<span className="ml-auto">
<Tag tone={d.embedded ? "ok" : "warn"}>{d.embedded ? "已嵌入" : "待嵌入"}</Tag>
</span>
<Button
size="sm"
variant="ghost"
onClick={() => {
log("rag.delete", "ok");
alert(`删除 ${d.name}(占位)`);
}}
>
删除
</Button>
</div>
))}
</Surface>
<Surface className="space-y-3">
<h3 className="text-sm font-semibold">检索测试</h3>
<div className="flex gap-2">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="输入查询…"
className="min-w-0 flex-1 rounded-[var(--mu-radius)] bg-[var(--mu-surface-2)] px-3 py-2 text-sm outline-none focus:ring-1 focus:ring-[var(--mu-accent)]"
/>
<Button
variant="primary"
onClick={() => {
log("rag.search", "ok");
setHits(query ? [`片段 1(命中 "${query}")`, `片段 2(命中 "${query}")`] : []);
}}
>
检索
</Button>
</div>
{hits.length > 0 ? (
<div className="space-y-1.5">
{hits.map((h) => (
<div key={h} className="rounded-[var(--mu-radius-sm)] bg-[var(--mu-surface-2)] px-3 py-2 text-xs text-[var(--mu-text-mid)]">{h}</div>
))}
</div>
) : null}
</Surface>
<Notice>分块/嵌入状态、向量索引与语义检索为后端 M10+ 提供;配置了 pgvector 增强后必须真实走语义路径(非空壳)。</Notice>
</div>
);
}