package httpapi import ( "net/http" "time" "ai-service/internal/model" ) type dashboardResponse struct { At time.Time `json:"at"` Summary dashboardSummary `json:"summary"` Stats *model.Stats `json:"stats"` Providers providersStatusResponse `json:"providers"` Infra infraStatusResponse `json:"infra"` Jobs []*model.JobSummary `json:"jobs"` } type dashboardSummary struct { Pending int64 `json:"pending"` Running int64 `json:"running"` Done int64 `json:"done"` Failed int64 `json:"failed"` Cancelled int64 `json:"cancelled"` Total int64 `json:"total"` } func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { now := time.Now().UTC() ctx, cancel := contextWithTimeout(r, 12*time.Second) defer cancel() stats, err := s.store.Stats(ctx) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } jobs, err := s.store.ListJobSummaries(ctx, model.JobFilter{ Statuses: []string{model.StatusFailed, model.StatusRunning}, Limit: 40, }) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } resp := dashboardResponse{ At: now, Summary: summarizeQueues(stats), Stats: stats, Providers: providersStatusResponse{ At: now, Providers: []providerStatus{ s.checkLLM(ctx), s.checkAudioLLM(ctx, "voxtral-small", s.cfg.VoxtralBaseURL, s.cfg.VoxtralAPIKey, s.cfg.VoxtralModel, s.cfg.VoxtralTimeout), }, }, Infra: loadInfraSnapshot(r, s.cfg), Jobs: jobs, } writeJSON(w, http.StatusOK, resp) } func summarizeQueues(stats *model.Stats) dashboardSummary { var out dashboardSummary if stats == nil { return out } for _, row := range stats.Queues { switch row.Status { case model.StatusPending: out.Pending += row.Total case model.StatusRunning: out.Running += row.Total case model.StatusDone: out.Done += row.Total case model.StatusFailed: out.Failed += row.Total case model.StatusCancelled: out.Cancelled += row.Total } out.Total += row.Total } return out }