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
+42
View File
@@ -553,6 +553,48 @@ func (h *Handler) KbSearch(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"hits": hits})
}
// AdminKbSearch: POST /api/v1/admin/kb/search —— 管理端「检索试验台」:按**完整作用域键**
// 检索任意租户的知识库(kb 形如 "<space_id>/<name>",由 /admin/datasources 清单给出)。
//
// 与用户侧 KbSearch 的关键差别:这里**不做 scopedKB 改写** —— 用户侧会把库名套上调用者
// 自己的 space_id,那样 admin 只能搜到自己空间的库,试验台就废了。跨租户是本端点的目的
// (运维排障:线上召回不准时,定位是 embedding、切块还是融合的问题)。
//
// mode: 空=生产混合(含 rerank)vector/fulltext/graph/hybrid=单路/纯融合(不 rerank)
// 用于逐路对比看是哪一路没召回。
func (h *Handler) AdminKbSearch(c *gin.Context) {
var body struct {
KB string `json:"kb"` // 完整作用域键 "<space_id>/<name>"
Q string `json:"q"`
TopK int `json:"topK"`
Mode string `json:"mode"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Q == "" || body.KB == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "kb 与 q 必填"})
return
}
args := map[string]any{"kb": body.KB, "q": body.Q}
if body.TopK > 0 {
args["topK"] = body.TopK
}
if body.Mode != "" {
args["mode"] = body.Mode
}
res, err := h.bus.CallTool(c.Request.Context(), contract.ToolSubjectGo("kb_search"),
&contract.ToolCall{Tool: "kb_search", Args: args})
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
if !res.OK {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": res.Error})
return
}
var hits []map[string]any
_ = json.Unmarshal([]byte(res.Content), &hits)
c.JSON(http.StatusOK, gin.H{"hits": hits})
}
// KbGraph: GET /api/v1/kb/graph?kb= —— 某知识库的图谱三元组(→ mcp-go kb_graphNeo4j)。
func (h *Handler) KbGraph(c *gin.Context) {
res, err := h.bus.CallTool(c.Request.Context(), contract.ToolSubjectGo("kb_graph"),