feat(admin): 数据源页转 RAG 运维台 + 支付/模型菜单重组 + 概览升级为仪表盘

后端:
- 新增 POST /admin/kb/search:管理端跨租户检索,支持 mode 指定单路
  (vector/fulltext/graph/hybrid),不走 scopedKB(否则会被强制锁到调用者
  自己的 space,跨租户排障就没法做了)
- KB 清单补 space_id(检索键是 <space_id>/<name>,缺它前端拼不出 key)

前端:
- 数据源&RAG 页补「检索试验台」:同一 query 并排跑生产链路 + 四路诊断,
  召回不准时能直接定位是向量/分词/图谱哪一环挂了
- 支付拆成「配置 / 订单与对账」两个子页,挂到运维 > 支付 下;
  导航支持二级菜单(NavParent 命中子路由自动展开)
- SettingsPage → ModelConfigPage「模型配置」,模型参数与计费规则合一
- 概览 → 仪表盘:并入计费与用量(UsagePage → UsageSection),
  去掉系统健康拓扑(与服务状态页重复,同一份 /admin/status 数据)
- 全局隐藏滚动条(保留滚动)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-20 11:17:54 +08:00
parent ac4fd00e28
commit 79b110afd0
16 changed files with 577 additions and 237 deletions
+59 -35
View File
@@ -5,8 +5,7 @@ import { lazy, type ReactNode } from "react";
const DashboardPage = lazy(() => import("./pages/DashboardPage").then((m) => ({ default: m.DashboardPage })));
const UsagePage = lazy(() => import("./pages/UsagePage").then((m) => ({ default: m.UsagePage })));
const SettingsPage = lazy(() => import("./pages/SettingsPage").then((m) => ({ default: m.SettingsPage })));
const ModelConfigPage = lazy(() => import("./pages/ModelConfigPage").then((m) => ({ default: m.ModelConfigPage })));
const DatasourcesPage = lazy(() => import("./pages/DatasourcesPage").then((m) => ({ default: m.DatasourcesPage })));
const StatusPage = lazy(() => import("./pages/StatusPage").then((m) => ({ default: m.StatusPage })));
const TasksPage = lazy(() => import("./pages/TasksPage").then((m) => ({ default: m.TasksPage })));
@@ -15,52 +14,33 @@ const TenantsPage = lazy(() => import("./pages/TenantsPage").then((m) => ({ defa
const GuardrailsPage = lazy(() => import("./pages/GuardrailsPage").then((m) => ({ default: m.GuardrailsPage })));
const PromptsPage = lazy(() => import("./pages/PromptsPage").then((m) => ({ default: m.PromptsPage })));
const AuditPage = lazy(() => import("./pages/AuditPage").then((m) => ({ default: m.AuditPage })));
const PaymentConfigPage = lazy(() => import("./pages/PaymentConfigPage").then((m) => ({ default: m.PaymentConfigPage })));
const PaymentOrdersPage = lazy(() => import("./pages/PaymentOrdersPage").then((m) => ({ default: m.PaymentOrdersPage })));
const SpacesPage = lazy(() => import("./pages/SpacesPage").then((m) => ({ default: m.SpacesPage })));
export interface RouteDef {
path: string;
label: string;
group: string;
/** 二级菜单名。同 group 内 parent 相同的若干条会聚成一个可展开子菜单(如 运维 > 支付 > 配置/订单与对账)。 */
parent?: string;
ready?: boolean;
element: ReactNode;
}
/** 侧栏一个节点:要么是单条路由,要么是带子项的可展开二级菜单。 */
export type NavNode =
| { kind: "item"; route: RouteDef }
| { kind: "parent"; label: string; items: RouteDef[] };
export const routes: RouteDef[] = [
{
path: "dashboard",
label: "概览",
label: "仪表盘",
group: "分析",
ready: true,
element: <DashboardPage />,
},
{
path: "usage",
label: "计费 & 用量",
group: "分析",
ready: true,
element: <UsagePage />,
},
{
path: "settings",
label: "系统配置",
group: "配置",
ready: true,
element: <SettingsPage />,
},
{
path: "datasources",
label: "数据源 & RAG",
group: "配置",
ready: true,
element: <DatasourcesPage />,
},
{
path: "prompts",
label: "提示词",
group: "配置",
ready: true,
element: <PromptsPage />,
},
{
path: "status",
label: "服务状态",
@@ -89,6 +69,43 @@ export const routes: RouteDef[] = [
ready: true,
element: <AuditPage />,
},
{
path: "models",
label: "模型配置",
group: "运维",
ready: true,
element: <ModelConfigPage />,
},
{
path: "datasources",
label: "数据源 & RAG",
group: "运维",
ready: true,
element: <DatasourcesPage />,
},
{
path: "prompts",
label: "提示词",
group: "运维",
ready: true,
element: <PromptsPage />,
},
{
path: "payment/config",
label: "配置",
group: "运维",
parent: "支付",
ready: true,
element: <PaymentConfigPage />,
},
{
path: "payment/orders",
label: "订单与对账",
group: "运维",
parent: "支付",
ready: true,
element: <PaymentOrdersPage />,
},
{
path: "tenants",
label: "租户 & 用户",
@@ -115,15 +132,22 @@ export const routes: RouteDef[] = [
export const defaultPath = "dashboard";
// 派生分组导航(保持注册顺序)。
export function navGroups(): Array<{ group: string; items: RouteDef[] }> {
const out: Array<{ group: string; items: RouteDef[] }> = [];
export function navGroups(): Array<{ group: string; nodes: NavNode[] }> {
const out: Array<{ group: string; nodes: NavNode[] }> = [];
for (const r of routes) {
let g = out.find((x) => x.group === r.group);
if (!g) {
g = { group: r.group, items: [] };
g = { group: r.group, nodes: [] };
out.push(g);
}
g.items.push(r);
if (!r.parent) {
g.nodes.push({ kind: "item", route: r });
continue;
}
// 同 parent 的归到同一个可展开节点(首次出现决定它在组内的位置)。
const exist = g.nodes.find((n): n is Extract<NavNode, { kind: "parent" }> => n.kind === "parent" && n.label === r.parent);
if (exist) exist.items.push(r);
else g.nodes.push({ kind: "parent", label: r.parent, items: [r] });
}
return out;
}