feat(desktop): 工作台重做为实时仪表盘 + /stats/overview 聚合端点(Tier1 UI 升级)
桌面端首屏原是静态宣传页(stat 全硬编码、唯一活数据是网关在线),"太单调"。 重做为活的驾驶舱: 后端 GET /api/v1/stats/overview(聚合,几条轻量查询): - 任务今日/累计、7 日趋势、近 7 天终态分布(实例级,Task 无 owner) - 评测均分 + 忠实度均值 + 计数;知识库文档/库数(owner 级) - token 今日 + 7 日趋势(Redis 日计数)、近期运行 feed、服务健康(复用 health 口径) - store.StatsOverview + RecentTasks。 前端 Home 重写为仪表盘:4 指标卡(今日任务/Token/评测均分/知识库)带 SVG 火花线、 近期运行 feed(状态点+相对时间,点进运行观测)、7 日任务量柱图、服务健康灯带、能力入口、 快捷动作。5s 轮询刷新。配色沿用现有 ink 暗底 + brand/accent。 live(vite dev + 预览截图):登录后仪表盘渲染真实活数据(今日任务/Token/评测 1.00/近期运行/ 服务全绿),无控制台报错。tsc+vite 构建通过,gateway 全绿。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -374,6 +374,53 @@ func (h *Handler) SetMemory(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok", "message": res.Content})
|
||||
}
|
||||
|
||||
// StatsOverview: GET /api/v1/stats/overview —— 工作台仪表盘聚合数据
|
||||
// (任务/评测实例级 + 知识库 owner 级 + token 用量 7 日 + 服务健康 + 近期运行)。
|
||||
func (h *Handler) StatsOverview(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
uid := userID(c)
|
||||
ov := h.db.StatsOverview(ctx, uid)
|
||||
|
||||
// token 用量:今日 + 近 7 日(按用户按天,来自 Redis 计数)。
|
||||
now := time.Now()
|
||||
today := now.Format("20060102")
|
||||
var tokenTrend []gin.H
|
||||
for i := 6; i >= 0; i-- {
|
||||
d := now.AddDate(0, 0, -i)
|
||||
tokenTrend = append(tokenTrend, gin.H{
|
||||
"key": d.Format("01-02"), "count": h.cache.GetUsage(ctx, uid, d.Format("20060102")),
|
||||
})
|
||||
}
|
||||
|
||||
// 近期运行 feed。
|
||||
recent := make([]gin.H, 0, 8)
|
||||
for _, t := range h.db.RecentTasks(ctx, 8) {
|
||||
recent = append(recent, gin.H{
|
||||
"task_id": t.TaskID, "status": t.Status, "detail": t.Detail, "at": t.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// 服务健康(与 Health 同口径:本地可判 + milvus/neo4j 经 mcp-go)。
|
||||
services := gin.H{"gateway": true, "nats": true, "db": h.db.Enabled(), "redis": h.cache.Enabled(), "milvus": false, "neo4j": false}
|
||||
cctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
if res, err := h.bus.CallTool(cctx, contract.ToolSubjectGo("health"), &contract.ToolCall{Tool: "health"}); err == nil && res != nil && res.OK {
|
||||
var sub map[string]bool
|
||||
if json.Unmarshal([]byte(res.Content), &sub) == nil {
|
||||
services["milvus"], services["neo4j"] = sub["milvus"], sub["neo4j"]
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"tasks_today": ov.TasksToday, "tasks_total": ov.TasksTotal,
|
||||
"status_count": ov.StatusCount, "task_trend": ov.TaskTrend,
|
||||
"eval_avg": ov.EvalAvg, "faithful_avg": ov.FaithfulAvg, "eval_count": ov.EvalCount,
|
||||
"kb_docs": ov.KBDocs, "kb_count": ov.KBCount,
|
||||
"tokens_today": h.cache.GetUsage(ctx, uid, today), "daily_budget": userDailyTokenBudget(),
|
||||
"token_trend": tokenTrend, "recent_runs": recent, "services": services,
|
||||
})
|
||||
}
|
||||
|
||||
// ListMemory: GET /api/v1/memory —— 列出当前用户的全部偏好(结构化,供记忆面板)。
|
||||
func (h *Handler) ListMemory(c *gin.Context) {
|
||||
res, err := h.bus.CallTool(c.Request.Context(), contract.ToolSubjectGo("memory_list"),
|
||||
|
||||
Reference in New Issue
Block a user