feat(admin): upgrade admin console UI widgets, add plan & status management, and redesign system status dashboard
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ModelManager } from "../components/ModelManager";
|
||||
import { adminDatasources, type DatasourceKB } from "../api";
|
||||
import { adminDatasources, listModels, migrateKbStorage, type DatasourceKB, type MigrateKbResult } from "../api";
|
||||
|
||||
// 数据源 & RAG:真数据(全平台知识库清单,来自 sundynix_kb/sundynix_doc)。
|
||||
// 此前该页 GraphRAG 拓扑图与「向量/全文/图谱权重滑块」全 mock——而且权重概念本身是虚构的:
|
||||
// mcp-go 的 RRF 融合是各路等权的倒排互惠融合(rrfK=60 平滑常数),没有"每路占几成"的权重。
|
||||
// 故删假滑块+假拓扑,换成真数据源清单 + 诚实的检索管线说明。
|
||||
|
||||
const KIND_LABEL: Record<string, string> = { general: "通用", folder: "文件夹", project: "项目", case: "案例" };
|
||||
const fmtWords = (n: number) => (n >= 1e4 ? `${(n / 1e4).toFixed(1)}万` : `${n}`);
|
||||
|
||||
@@ -15,6 +10,17 @@ export function DatasourcesPage() {
|
||||
const [rows, setRows] = useState<DatasourceKB[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [err, setErr] = useState("");
|
||||
|
||||
// 运行时的 active embedding 模型展示
|
||||
const [activeEmbedding, setActiveEmbedding] = useState<string>("加载中…");
|
||||
|
||||
// 运维工具折叠状态
|
||||
const [opsOpen, setOpsOpen] = useState(false);
|
||||
|
||||
// 迁移 Modal 状态
|
||||
const [migrationModal, setMigrationModal] = useState<"none" | "confirm" | "running" | "done">("none");
|
||||
const [migrationResult, setMigrationResult] = useState<MigrateKbResult | null>(null);
|
||||
const [migrationErr, setMigrationErr] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
adminDatasources()
|
||||
@@ -25,19 +31,203 @@ export function DatasourcesPage() {
|
||||
})
|
||||
.catch((e) => setErr((e as Error).message))
|
||||
.finally(() => setLoading(false));
|
||||
|
||||
// 获取激活的 embedding 模型
|
||||
listModels("embedding")
|
||||
.then((models) => {
|
||||
const active = models.find((m) => m.active);
|
||||
if (active) {
|
||||
setActiveEmbedding(`${active.model} (${active.provider})`);
|
||||
} else {
|
||||
setActiveEmbedding("未配置/未激活 (将回退代码默认)");
|
||||
}
|
||||
})
|
||||
.catch(() => setActiveEmbedding("获取失败"));
|
||||
}, []);
|
||||
|
||||
const startMigration = async () => {
|
||||
setMigrationModal("running");
|
||||
setMigrationErr("");
|
||||
try {
|
||||
const res = await migrateKbStorage();
|
||||
setMigrationResult(res);
|
||||
setMigrationModal("done");
|
||||
} catch (e) {
|
||||
setMigrationErr((e as Error).message);
|
||||
setMigrationModal("confirm"); // 退回到确认态展示错误
|
||||
}
|
||||
};
|
||||
|
||||
const totalWords = rows.reduce((a, r) => a + r.total_words, 0);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Embedding 模型配置(真实组件) */}
|
||||
<ModelManager
|
||||
kind="embedding"
|
||||
title="Embedding 模型(embedding → mcp-go RAG)"
|
||||
baseUrlHint="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
modelHint="text-embedding-v3"
|
||||
/>
|
||||
{/* 顶部激活模型只读展示 & 引导 */}
|
||||
<div className="rounded-xl border border-gray-100 bg-white p-5 shadow-sm flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-gray-700">当前激活向量化模型 (Embedding)</h4>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<span className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></span>
|
||||
<code className="text-xs font-mono font-medium text-gray-800">{activeEmbedding}</code>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="#/models"
|
||||
onClick={(e) => {
|
||||
// 如果使用 hash 路由或 react-router,可正常导航。这里提示用户去模型页配置
|
||||
window.location.hash = "#/models";
|
||||
}}
|
||||
className="text-xs text-violet-600 hover:text-violet-700 font-medium hover:underline flex items-center gap-1"
|
||||
>
|
||||
前往模型管理修改 →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* 运维工具 (RAG 迁移) */}
|
||||
<div className="rounded-xl border border-gray-100 bg-white p-5 shadow-sm">
|
||||
<button
|
||||
onClick={() => setOpsOpen(!opsOpen)}
|
||||
className="flex w-full items-center justify-between font-semibold text-gray-700 focus:outline-none"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="h-4 w-4 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span className="text-sm font-semibold text-gray-700">运维工具</span>
|
||||
</div>
|
||||
<span className="text-xs text-gray-400">{opsOpen ? "收起 ▲" : "展开 ▼"}</span>
|
||||
</button>
|
||||
|
||||
{opsOpen && (
|
||||
<div className="mt-4 border-t border-gray-100 pt-4 space-y-4">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-3 text-xs bg-amber-50/50 border border-amber-100 p-4 rounded-lg">
|
||||
<div className="space-y-1">
|
||||
<div className="font-semibold text-amber-800 flex items-center gap-1.5">
|
||||
<span>知识库存储目录路径迁移</span>
|
||||
<span className="bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded text-[9px] font-bold">建议在低峰期操作</span>
|
||||
</div>
|
||||
<p className="text-gray-500 leading-relaxed max-w-xl">
|
||||
将平台全量知识库底层的存储目录规范,由早期版本的 <code className="bg-gray-100 px-1 rounded">owner/kb_id/doc</code> 升级为新版本统一的 <code className="bg-gray-100 px-1 rounded">space_id/kb_id/doc</code> 规范。此操作将遍历所有文件,并在数据库与对象存储中更新映射关系,迁移期间可能产生轻微检索延迟。
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setMigrationModal("confirm")}
|
||||
className="self-start md:self-center shrink-0 bg-amber-600 hover:bg-amber-700 text-white text-xs font-semibold py-2 px-4 rounded-lg shadow-sm transition"
|
||||
>
|
||||
开始迁移
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 迁移 Modal */}
|
||||
{migrationModal !== "none" && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
|
||||
<div className="relative mx-4 w-full max-w-md overflow-hidden rounded-2xl bg-white shadow-2xl">
|
||||
<div className="flex items-center justify-between border-b border-gray-100 px-6 py-5">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-gray-900">
|
||||
{migrationModal === "confirm" && "确认开始存储迁移?"}
|
||||
{migrationModal === "running" && "正在迁移中…"}
|
||||
{migrationModal === "done" && "迁移完成 🎉"}
|
||||
</h3>
|
||||
<p className="mt-0.5 text-xs text-gray-400">一键升级平台 KB 作用域至 space_id</p>
|
||||
</div>
|
||||
{migrationModal !== "running" && (
|
||||
<button
|
||||
onClick={() => setMigrationModal("none")}
|
||||
className="text-xl leading-none text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 px-6 py-5 text-sm">
|
||||
{migrationModal === "confirm" && (
|
||||
<>
|
||||
<p className="text-gray-600 leading-relaxed">
|
||||
您将触发全平台的数据目录热迁移。这会将旧版未挂载空间的独立知识库映射,统一调整至 Space 空间映射下。
|
||||
</p>
|
||||
<div className="rounded-lg border border-amber-100 bg-amber-50 p-3 text-xs text-amber-700 space-y-1">
|
||||
<div className="font-semibold">⚠️ 运维安全警示:</div>
|
||||
<div>· 这是一个高危数据库与存储热变更操作</div>
|
||||
<div>· 建议执行前对 <code className="font-mono bg-amber-100/50 px-1">sundynix_doc</code> 表进行备份</div>
|
||||
<div>· 确认执行后不可中途停止</div>
|
||||
</div>
|
||||
{migrationErr && (
|
||||
<div className="rounded-lg border border-rose-100 bg-rose-50 px-3 py-2 text-xs text-rose-600">
|
||||
操作失败:{migrationErr}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{migrationModal === "running" && (
|
||||
<div className="flex flex-col items-center justify-center py-6 space-y-3">
|
||||
<svg className="animate-spin h-8 w-8 text-amber-600" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
|
||||
</svg>
|
||||
<span className="text-xs text-gray-500 font-medium animate-pulse">正在扫描文档并重新入队,请勿关闭窗口…</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{migrationModal === "done" && migrationResult && (
|
||||
<div className="space-y-3">
|
||||
<div className="rounded-lg border border-emerald-100 bg-emerald-50 px-3 py-2 text-xs text-emerald-700">
|
||||
数据升级指令已成功执行,各文档已重新入队执行 RRF 索引重灌。
|
||||
</div>
|
||||
<div className="divide-y divide-gray-100 border rounded-xl overflow-hidden bg-gray-50/50">
|
||||
<div className="flex justify-between p-3 text-xs">
|
||||
<span className="text-gray-500">已扫描文档总数</span>
|
||||
<span className="font-semibold text-gray-800 font-mono">{migrationResult.total}</span>
|
||||
</div>
|
||||
<div className="flex justify-between p-3 text-xs">
|
||||
<span className="text-gray-500">入队重灌文档数</span>
|
||||
<span className="font-semibold text-amber-600 font-mono">{migrationResult.enqueued}</span>
|
||||
</div>
|
||||
<div className="flex justify-between p-3 text-xs">
|
||||
<span className="text-gray-500">跳过(已是新作用域)</span>
|
||||
<span className="font-semibold text-gray-600 font-mono">{migrationResult.skipped}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 border-t border-gray-100 px-6 py-4">
|
||||
{migrationModal === "confirm" && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setMigrationModal("none")}
|
||||
className="rounded-lg border border-gray-200 px-4 py-2 text-xs text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={() => void startMigration()}
|
||||
className="rounded-lg bg-amber-600 hover:bg-amber-700 px-4 py-2 text-xs font-semibold text-white shadow-sm transition"
|
||||
>
|
||||
确认迁移
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{migrationModal === "done" && (
|
||||
<button
|
||||
onClick={() => setMigrationModal("none")}
|
||||
className="rounded-lg bg-violet-600 hover:bg-violet-700 px-4 py-2 text-xs font-semibold text-white shadow-sm transition"
|
||||
>
|
||||
我知道了
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* 检索管线说明(诚实:三路 + RRF 等权融合,非可调权重) */}
|
||||
<div className="rounded-xl border border-gray-100 bg-white p-5 shadow-sm">
|
||||
|
||||
Reference in New Issue
Block a user