package api import ( "encoding/json" "os" "path/filepath" "sync" "time" "github.com/muyue/muyue/internal/config" ) type consumptionEntry struct { Date string `json:"date"` Tokens int `json:"tokens"` Requests int `json:"requests"` } type providerConsumption struct { Name string `json:"name"` Daily []consumptionEntry `json:"daily"` Total int `json:"total_tokens"` Requests int `json:"total_requests"` } type consumptionStore struct { mu sync.Mutex providers map[string]*providerConsumption } func newConsumptionStore() *consumptionStore { cs := &consumptionStore{ providers: make(map[string]*providerConsumption), } cs.load() cs.prune() return cs } func (cs *consumptionStore) Record(providerName string, tokens int) { if tokens <= 0 || providerName == "" { return } cs.mu.Lock() defer cs.mu.Unlock() today := time.Now().UTC().Format("2006-01-02") p, ok := cs.providers[providerName] if !ok { p = &providerConsumption{Name: providerName} cs.providers[providerName] = p } p.Total += tokens p.Requests++ if len(p.Daily) > 0 && p.Daily[len(p.Daily)-1].Date == today { p.Daily[len(p.Daily)-1].Tokens += tokens p.Daily[len(p.Daily)-1].Requests++ } else { p.Daily = append(p.Daily, consumptionEntry{ Date: today, Tokens: tokens, Requests: 1, }) } cs.save() } func (cs *consumptionStore) GetAll() map[string]*providerConsumption { cs.mu.Lock() defer cs.mu.Unlock() result := make(map[string]*providerConsumption) for k, v := range cs.providers { pc := *v daily := make([]consumptionEntry, len(v.Daily)) copy(daily, v.Daily) pc.Daily = daily result[k] = &pc } return result } func (cs *consumptionStore) prune() { cutoff := time.Now().UTC().AddDate(0, 0, -7).Format("2006-01-02") for _, p := range cs.providers { filtered := make([]consumptionEntry, 0) for _, d := range p.Daily { if d.Date >= cutoff { filtered = append(filtered, d) } } p.Daily = filtered } } func (cs *consumptionStore) filePath() string { dir, err := config.ConfigDir() if err != nil { return "" } return filepath.Join(dir, "consumption.json") } func (cs *consumptionStore) load() { fp := cs.filePath() if fp == "" { return } data, err := os.ReadFile(fp) if err != nil { return } json.Unmarshal(data, &cs.providers) } func (cs *consumptionStore) save() { fp := cs.filePath() if fp == "" { return } data, _ := json.Marshal(cs.providers) os.WriteFile(fp, data, 0644) }