All checks were successful
release-tag / release-image (push) Successful in 2m10s
144 lines
6.1 KiB
Go
144 lines
6.1 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
type artifactUsageRow struct {
|
|
CreatedAt time.Time `json:"created_at"`
|
|
TaskID string `json:"task_id,omitempty"`
|
|
Kind string `json:"kind"`
|
|
Model string `json:"model"`
|
|
Endpoint string `json:"endpoint"`
|
|
Size string `json:"size"`
|
|
Quality string `json:"quality"`
|
|
RequestID string `json:"request_id,omitempty"`
|
|
InputTokens int64 `json:"input_tokens"`
|
|
InputTextTokens int64 `json:"input_text_tokens"`
|
|
InputImageTokens int64 `json:"input_image_tokens"`
|
|
OutputTokens int64 `json:"output_tokens"`
|
|
TotalTokens int64 `json:"total_tokens"`
|
|
EstimatedCostUSD *float64 `json:"estimated_cost_usd,omitempty"`
|
|
PricingBasis string `json:"pricing_basis,omitempty"`
|
|
}
|
|
|
|
func (s *Server) adminArtifactUsage(w http.ResponseWriter, r *http.Request) {
|
|
now := time.Now().UTC()
|
|
dayStartMS := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
|
|
if raw := r.URL.Query().Get("day_start_ms"); raw != "" {
|
|
if v, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
|
// Accept a browser-local midnight in a reasonable window. This lets the
|
|
// "today" KPI follow the admin's local calendar day without adding a
|
|
// timezone setting to the server.
|
|
candidate := time.UnixMilli(v).UTC()
|
|
if candidate.After(now.Add(-48*time.Hour)) && candidate.Before(now.Add(24*time.Hour)) {
|
|
dayStartMS = v
|
|
}
|
|
}
|
|
}
|
|
|
|
var todayCalls, todayCards, todayPriced int64
|
|
var todayCost float64
|
|
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),
|
|
COALESCE(sum(CASE WHEN kind='artifact' THEN 1 ELSE 0 END),0),
|
|
COALESCE(sum(CASE WHEN estimated_cost_usd IS NOT NULL THEN 1 ELSE 0 END),0),
|
|
COALESCE(sum(estimated_cost_usd),0)
|
|
FROM artifact_api_usage WHERE created_at>=?`, dayStartMS).Scan(&todayCalls, &todayCards, &todayPriced, &todayCost)
|
|
|
|
var cardCalls, pricedCardCalls int64
|
|
var totalCardCost, avgCardCost float64
|
|
var inputTokens, inputTextTokens, inputImageTokens, outputTokens, totalTokens int64
|
|
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),count(estimated_cost_usd),
|
|
COALESCE(sum(estimated_cost_usd),0),COALESCE(avg(estimated_cost_usd),0),
|
|
COALESCE(sum(input_tokens),0),COALESCE(sum(input_text_tokens),0),COALESCE(sum(input_image_tokens),0),
|
|
COALESCE(sum(output_tokens),0),COALESCE(sum(total_tokens),0)
|
|
FROM artifact_api_usage WHERE kind='artifact'`).Scan(
|
|
&cardCalls, &pricedCardCalls, &totalCardCost, &avgCardCost,
|
|
&inputTokens, &inputTextTokens, &inputImageTokens, &outputTokens, &totalTokens)
|
|
|
|
var anchorCalls int64
|
|
var anchorCost float64
|
|
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),COALESCE(sum(estimated_cost_usd),0)
|
|
FROM artifact_api_usage WHERE kind='character_anchor'`).Scan(&anchorCalls, &anchorCost)
|
|
|
|
var rollingCalls1H, rollingCalls24H int64
|
|
var rollingCost24H float64
|
|
oneHourMS := now.Add(-time.Hour).UnixMilli()
|
|
twentyFourHoursMS := now.Add(-24 * time.Hour).UnixMilli()
|
|
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT
|
|
COALESCE(sum(CASE WHEN created_at>=? THEN 1 ELSE 0 END),0),
|
|
count(*),COALESCE(sum(estimated_cost_usd),0)
|
|
FROM artifact_api_usage WHERE provider='openai' AND created_at>=?`, oneHourMS, twentyFourHoursMS).Scan(&rollingCalls1H, &rollingCalls24H, &rollingCost24H)
|
|
cfg := s.settings.Get()
|
|
circuitBlocked := (cfg.OpenAIMaxCalls1H > 0 && rollingCalls1H >= int64(cfg.OpenAIMaxCalls1H)) ||
|
|
(cfg.OpenAIMaxCalls24H > 0 && rollingCalls24H >= int64(cfg.OpenAIMaxCalls24H)) ||
|
|
(cfg.OpenAIMaxCost24HUSD > 0 && rollingCost24H+cfg.OpenAIBudgetReserveUSD > cfg.OpenAIMaxCost24HUSD)
|
|
|
|
recent := make([]artifactUsageRow, 0, 20)
|
|
rows, err := s.store.DB.QueryContext(r.Context(), `SELECT created_at,task_id,kind,model,endpoint,size,quality,request_id,
|
|
input_tokens,input_text_tokens,input_image_tokens,output_tokens,total_tokens,estimated_cost_usd,pricing_basis
|
|
FROM artifact_api_usage ORDER BY created_at DESC,id DESC LIMIT 20`)
|
|
if err == nil {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var createdMS int64
|
|
var taskID, requestID sql.NullString
|
|
var cost sql.NullFloat64
|
|
var row artifactUsageRow
|
|
if err := rows.Scan(&createdMS, &taskID, &row.Kind, &row.Model, &row.Endpoint, &row.Size, &row.Quality, &requestID,
|
|
&row.InputTokens, &row.InputTextTokens, &row.InputImageTokens, &row.OutputTokens, &row.TotalTokens, &cost, &row.PricingBasis); err != nil {
|
|
continue
|
|
}
|
|
row.CreatedAt = time.UnixMilli(createdMS).UTC()
|
|
if taskID.Valid {
|
|
row.TaskID = taskID.String
|
|
}
|
|
if requestID.Valid {
|
|
row.RequestID = requestID.String
|
|
}
|
|
if cost.Valid {
|
|
v := cost.Float64
|
|
row.EstimatedCostUSD = &v
|
|
}
|
|
recent = append(recent, row)
|
|
}
|
|
}
|
|
|
|
jsonOut(w, 200, map[string]any{
|
|
"day_start": time.UnixMilli(dayStartMS).UTC(),
|
|
"today_calls": todayCalls,
|
|
"today_cards": todayCards,
|
|
"today_priced_calls": todayPriced,
|
|
"today_cost_usd": todayCost,
|
|
"card_generations": cardCalls,
|
|
"priced_card_generations": pricedCardCalls,
|
|
"total_card_cost_usd": totalCardCost,
|
|
"avg_card_cost_usd": avgCardCost,
|
|
"cost_per_1000_usd": avgCardCost * 1000,
|
|
"anchor_generations": anchorCalls,
|
|
"anchor_cost_usd": anchorCost,
|
|
"card_usage": map[string]int64{
|
|
"input_tokens": inputTokens,
|
|
"input_text_tokens": inputTextTokens,
|
|
"input_image_tokens": inputImageTokens,
|
|
"output_tokens": outputTokens,
|
|
"total_tokens": totalTokens,
|
|
},
|
|
"circuit_breaker": map[string]any{
|
|
"blocked": circuitBlocked,
|
|
"calls_1h": rollingCalls1H,
|
|
"calls_24h": rollingCalls24H,
|
|
"cost_24h_usd": rollingCost24H,
|
|
"max_calls_1h": cfg.OpenAIMaxCalls1H,
|
|
"max_calls_24h": cfg.OpenAIMaxCalls24H,
|
|
"max_cost_24h_usd": cfg.OpenAIMaxCost24HUSD,
|
|
"reserve_per_call_usd": cfg.OpenAIBudgetReserveUSD,
|
|
},
|
|
"cost_method": "estimated from provider-reported token usage using pinned OpenAI standard public token rates; not invoice reconciliation",
|
|
"recent": recent,
|
|
})
|
|
}
|