feat(model-catalog): 模型选择器 - 支持展示往前推最近10次调用红绿状态指示条 (#768)
* feat(catalog): 模型可用性 - 模型窗口支持展示往前推最近10次调用的红绿状态条 在模型选择弹窗中,单纯展示 24h 成功率难以反映模型短期的真实状态。 现支持往前推取最近最多 10 次实际调用(create 请求)的执行结果: - 成功一次展示绿色小点,失败一次展示红色小点,一共固定显示 10 个槽位点 - 槽位未发生调用时展示半透明灰色点;鼠标悬浮显示成功与失败次数的详细统计 - 后端从当前时刻往前推查询最近的 create 日志,按时间演进正序(最右侧为最新)输出 recentResults 切片 验证: - 后端:`go test -v ./internal/app -run TestPublicModelAvailability` 通过 - 前端:`tsc --noEmit`、`bun run build` 通过;`bun test` 2210 项通过 - 新增 `web/test/model-picker-availability-runs.test.ts` 单测并完成反向验证(改坏时测试变红) - 后端已无损平滑重启并监听 8080 端口 * feat(create): 模型选择器 - 左侧列表模型项直接展示10点可用性状态条 用户在模型选择弹窗中,希望无需点进二级卡片就能直接感知模型的可用率。 现将 10 点状态指示条挂载到左侧模型目录列表的每一项中: - 在每一个模型的子描述栏右侧常驻展示 10 个圆点的状态条 - 成功=绿点、失败=红点、未调用=灰点;鼠标悬浮显示最近成功与失败统计 - 保持列表项排版整齐,小点与渠道描述自适应两端对齐 验证: - `tsc --noEmit`、`bun run build` 通过 - `bun test` 2211 项测试通过(唯一失败为上游基线问题) - `test/model-picker-availability-runs.test.ts` 补充左侧列表挂载断言并通过
This commit is contained in:
1 parent
054dab28cc
commit
8ec7e5a9a7
8 files changed
+239
-24
No files matched your search
@@ -52,6 +52,18 @@ func (s *Service) publicModelAvailability(channels []PublicChannelCatalog, now t
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recentLogs, _ := s.repo.PublicModelRecentLogs(channelIDs, 1000)
|
||||
recentMap := make(map[publicModelAvailabilityKey][]bool)
|
||||
for _, log := range recentLogs {
|
||||
key := publicModelAvailabilityKey{channelID: log.ChannelID, modelKey: log.Model}
|
||||
if _, ok := known[key]; !ok {
|
||||
continue
|
||||
}
|
||||
if len(recentMap[key]) < 10 {
|
||||
recentMap[key] = append(recentMap[key], log.Status == model.ApiCallStatusSucceeded)
|
||||
}
|
||||
}
|
||||
|
||||
last24 := make(map[publicModelAvailabilityKey]*publicModelAvailabilityAccumulator)
|
||||
trend := make([]map[publicModelAvailabilityKey]*publicModelAvailabilityAccumulator, 7)
|
||||
for index := range trend {
|
||||
@@ -73,10 +85,17 @@ func (s *Service) publicModelAvailability(channels []PublicChannelCatalog, now t
|
||||
|
||||
for key := range known {
|
||||
primary := last24[key]
|
||||
results := make([]bool, len(recentMap[key]))
|
||||
copy(results, recentMap[key])
|
||||
// 将倒序取出的切片反转,使得从左到右表示时间演进(最右侧为最新一次调用)
|
||||
for i, j := 0, len(results)-1; i < j; i, j = i+1, j-1 {
|
||||
results[i], results[j] = results[j], results[i]
|
||||
}
|
||||
item := &PublicModelAvailability{
|
||||
Trend7d: make([]PublicModelAvailabilityDay, 0, 7),
|
||||
DataState: publicAvailabilityState(sampleCount(primary), publicModelAvailabilityMinimumSamples),
|
||||
ComputedAt: now,
|
||||
Trend7d: make([]PublicModelAvailabilityDay, 0, 7),
|
||||
RecentResults: results,
|
||||
DataState: publicAvailabilityState(sampleCount(primary), publicModelAvailabilityMinimumSamples),
|
||||
ComputedAt: now,
|
||||
}
|
||||
if primary != nil {
|
||||
item.SampleCount = primary.total
|
||||
|
||||
@@ -68,14 +68,23 @@ func TestPublicModelAvailabilityUsesCreateAttemptsAndFixedSevenDayTrend(t *testi
|
||||
if len(ready.Trend7d) != 7 {
|
||||
t.Fatalf("trend length = %d, want 7", len(ready.Trend7d))
|
||||
}
|
||||
if len(ready.RecentResults) != 10 {
|
||||
t.Fatalf("ready recent results length = %d, want 10", len(ready.RecentResults))
|
||||
}
|
||||
|
||||
insufficient := availability[publicModelAvailabilityKey{channelID: "channel-1", modelKey: "model-b"}]
|
||||
if insufficient == nil || insufficient.DataState != PublicModelAvailabilityInsufficient || insufficient.SampleCount != 2 || insufficient.Rate24h != nil {
|
||||
t.Fatalf("insufficient availability = %#v", insufficient)
|
||||
}
|
||||
if len(insufficient.RecentResults) != 2 || insufficient.RecentResults[0] != false || insufficient.RecentResults[1] != true {
|
||||
t.Fatalf("insufficient recent results = %#v, want [false, true]", insufficient.RecentResults)
|
||||
}
|
||||
|
||||
noData := availability[publicModelAvailabilityKey{channelID: "channel-1", modelKey: "model-c"}]
|
||||
if noData == nil || noData.DataState != PublicModelAvailabilityNoData || noData.SampleCount != 0 || noData.Rate24h != nil {
|
||||
t.Fatalf("no-data availability = %#v", noData)
|
||||
}
|
||||
if len(noData.RecentResults) != 0 {
|
||||
t.Fatalf("no-data recent results length = %d, want 0", len(noData.RecentResults))
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,13 @@ type ModelCatalogResponse struct {
|
||||
// without changing the binary current availability decision above it.
|
||||
// Rate24h is nil for no-data or insufficient-sample windows.
|
||||
type PublicModelAvailability struct {
|
||||
Rate24h *float64 `json:"rate24h"`
|
||||
SampleCount int `json:"sampleCount24h"`
|
||||
Trend7d []PublicModelAvailabilityDay `json:"trend7d"`
|
||||
DataState PublicModelAvailabilityDataState `json:"dataState"`
|
||||
ComputedAt time.Time `json:"computedAt"`
|
||||
DataThrough *time.Time `json:"dataThrough,omitempty"`
|
||||
Rate24h *float64 `json:"rate24h"`
|
||||
SampleCount int `json:"sampleCount24h"`
|
||||
Trend7d []PublicModelAvailabilityDay `json:"trend7d"`
|
||||
RecentResults []bool `json:"recentResults"`
|
||||
DataState PublicModelAvailabilityDataState `json:"dataState"`
|
||||
ComputedAt time.Time `json:"computedAt"`
|
||||
DataThrough *time.Time `json:"dataThrough,omitempty"`
|
||||
}
|
||||
|
||||
type PublicModelAvailabilityDataState string
|
||||
|
||||
@@ -126,6 +126,26 @@ func (r *Repository) PublicModelAvailabilityLogs(filter PublicModelAvailabilityF
|
||||
return logs, query.Find(&logs).Error
|
||||
}
|
||||
|
||||
// PublicModelRecentLogs 从当前时间往前推查询指定渠道最近的调用日志(按时间倒序),用于计算每个模型最近的执行结果状态点。
|
||||
func (r *Repository) PublicModelRecentLogs(channelIDs []string, limit int) ([]model.ApiCallLog, error) {
|
||||
var logs []model.ApiCallLog
|
||||
if len(channelIDs) == 0 {
|
||||
return logs, nil
|
||||
}
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 500
|
||||
}
|
||||
query := r.db.Select("channel_id", "model", "status", "created_at").
|
||||
Where("api_call_logs.channel_id IN ?", channelIDs).
|
||||
Where("api_call_logs.user_id <> ''").
|
||||
Where("api_call_logs.model <> ''").
|
||||
Where("api_call_logs.request_kind = ?", "create").
|
||||
Where("api_call_logs.status IN ?", []model.ApiCallStatus{model.ApiCallStatusSucceeded, model.ApiCallStatusFailed}).
|
||||
Order("api_call_logs.created_at desc").
|
||||
Limit(limit)
|
||||
return logs, query.Find(&logs).Error
|
||||
}
|
||||
|
||||
func (r *Repository) AnalyticsActivities(filter AnalyticsFilter) ([]model.UserDailyActivity, error) {
|
||||
var activities []model.UserDailyActivity
|
||||
query := r.db.Where("day >= ? AND day < ?", filter.From, filter.To)
|
||||
|
||||
Reference in new issue
Block a user