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:
@@ -180,6 +180,76 @@ func (p *Postgres) CountTasks(ctx context.Context) (int64, error) {
|
||||
return n, err
|
||||
}
|
||||
|
||||
// DayCount 是「某天 / 某状态 → 计数」的一行(工作台趋势/分布用)。
|
||||
type DayCount struct {
|
||||
Key string `json:"key"`
|
||||
Count int64 `json:"count"`
|
||||
}
|
||||
|
||||
// Overview 是工作台概览的聚合数据。任务/评测为实例级(Task 无 owner,单租户部署即全量),
|
||||
// 知识库为 owner 级。降级模式(db==nil)返回零值。
|
||||
type Overview struct {
|
||||
TasksToday int64 `json:"tasks_today"`
|
||||
TasksTotal int64 `json:"tasks_total"`
|
||||
StatusCount []DayCount `json:"status_count"` // 近 7 天各终态分布
|
||||
TaskTrend []DayCount `json:"task_trend"` // 近 7 天每日任务数(MM-DD)
|
||||
EvalAvg float64 `json:"eval_avg"` // 综合分均值
|
||||
FaithfulAvg float64 `json:"faithful_avg"` // 忠实度均值(仅有来源的)
|
||||
EvalCount int64 `json:"eval_count"`
|
||||
KBDocs int64 `json:"kb_docs"` // owner 文档数
|
||||
KBCount int64 `json:"kb_count"` // owner 知识库数
|
||||
}
|
||||
|
||||
// StatsOverview 聚合工作台概览(几条轻量查询)。owner 用于知识库口径。
|
||||
func (p *Postgres) StatsOverview(ctx context.Context, owner string) *Overview {
|
||||
o := &Overview{StatusCount: []DayCount{}, TaskTrend: []DayCount{}}
|
||||
if p.db == nil {
|
||||
return o
|
||||
}
|
||||
db := p.db.WithContext(ctx)
|
||||
startOfDay := time.Now().Truncate(24 * time.Hour)
|
||||
|
||||
db.Model(&Task{}).Count(&o.TasksTotal)
|
||||
db.Model(&Task{}).Where("created_at >= ?", startOfDay).Count(&o.TasksToday)
|
||||
|
||||
// 近 7 天每日任务数(按日期分组,缺的天补 0 在前端/此处处理)。
|
||||
db.Model(&Task{}).
|
||||
Select("to_char(created_at, 'MM-DD') as key, count(*) as count").
|
||||
Where("created_at >= ?", time.Now().AddDate(0, 0, -6).Truncate(24*time.Hour)).
|
||||
Group("key").Order("key").Scan(&o.TaskTrend)
|
||||
|
||||
// 近 7 天终态分布。
|
||||
db.Model(&Task{}).
|
||||
Select("status as key, count(*) as count").
|
||||
Where("created_at >= ?", time.Now().AddDate(0, 0, -6)).
|
||||
Group("status").Scan(&o.StatusCount)
|
||||
|
||||
// 评测均值(综合 + 忠实度仅算有来源的)。
|
||||
var ev struct {
|
||||
Avg float64
|
||||
Faithful float64
|
||||
N int64
|
||||
}
|
||||
db.Model(&Eval{}).Select("coalesce(avg(overall),0) as avg, coalesce(avg(nullif(faithful,0)),0) as faithful, count(*) as n").Scan(&ev)
|
||||
o.EvalAvg, o.FaithfulAvg, o.EvalCount = ev.Avg, ev.Faithful, ev.N
|
||||
|
||||
if owner != "" {
|
||||
db.Model(&Doc{}).Where("owner = ?", owner).Count(&o.KBDocs)
|
||||
db.Model(&KB{}).Where("owner = ?", owner).Count(&o.KBCount)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// RecentTasks 返回最近 n 条任务(工作台「近期运行」feed)。
|
||||
func (p *Postgres) RecentTasks(ctx context.Context, n int) []Task {
|
||||
if p.db == nil {
|
||||
return nil
|
||||
}
|
||||
var out []Task
|
||||
p.db.WithContext(ctx).Order("created_at desc").Limit(n).Find(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Close 释放底层连接。
|
||||
func (p *Postgres) Close() {
|
||||
if p.db == nil {
|
||||
|
||||
Reference in New Issue
Block a user