package formatter import ( "fmt" "strings" "time" "git.db.org.ai/dborg/internal/models" ) // FormatCreditUsageLogs renders raw credit_usage_log rows. func FormatCreditUsageLogs(resp *models.CreditUsageLogsResponse, asJSON bool) (string, error) { if asJSON { if err := PrintColorizedJSON(resp); err != nil { return "", err } return "", nil } if len(resp.Logs) == 0 { return fmt.Sprintf("%s\n", Gray("No usage logs found")), nil } var sb strings.Builder sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Credit Usage Logs")))) sb.WriteString(fmt.Sprintf("%s\n\n", Gray(strings.Repeat("─", 90)))) table := NewTable([]string{"ID", "Account", "Endpoint", "Path", "Cost", "Charged", "Status", "Time"}) for _, row := range resp.Logs { statusStr := Green(fmt.Sprintf("%d", row.StatusCode)) if row.StatusCode >= 400 { statusStr = Red(fmt.Sprintf("%d", row.StatusCode)) } else if row.StatusCode >= 300 { statusStr = Yellow(fmt.Sprintf("%d", row.StatusCode)) } chargedStr := Gray("0") if row.CreditsCharged > 0 { chargedStr = Yellow(fmt.Sprintf("%d", row.CreditsCharged)) } timeStr := row.CreatedAt if t, err := time.Parse(time.RFC3339, row.CreatedAt); err == nil { timeStr = t.Format("01-02 15:04:05") } else if t, err := time.Parse("2006-01-02T15:04:05Z", row.CreatedAt); err == nil { timeStr = t.Format("01-02 15:04:05") } table.AddRow( Gray(fmt.Sprintf("%d", row.ID)), Bold(row.AccountName), Cyan(row.Endpoint), Dim(truncate(row.Path, 30)), Gray(fmt.Sprintf("%d", row.EndpointCost)), chargedStr, statusStr, Gray(timeStr), ) } sb.WriteString(table.Render()) sb.WriteString(fmt.Sprintf("\n%s %d rows\n", Blue("Total:"), resp.Count)) return sb.String(), nil } // FormatCreditUsageByEndpoint renders per-endpoint aggregate stats. func FormatCreditUsageByEndpoint(resp *models.CreditUsageByEndpointResponse, asJSON bool) (string, error) { if asJSON { if err := PrintColorizedJSON(resp); err != nil { return "", err } return "", nil } if len(resp.Endpoints) == 0 { return fmt.Sprintf("%s\n", Gray("No endpoint usage data found")), nil } fromStr, toStr := formatTimeRange(resp.From, resp.To) var sb strings.Builder sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Usage by Endpoint")))) sb.WriteString(fmt.Sprintf("%s %s → %s\n\n", Gray(strings.Repeat("─", 60)), Gray(fromStr), Gray(toStr))) table := NewTable([]string{"Endpoint", "Total", "Success", "Cost", "Charged"}) var totalCalls, totalCharged int64 for _, row := range resp.Endpoints { successRate := "" if row.TotalCalls > 0 { pct := float64(row.SuccessCalls) / float64(row.TotalCalls) * 100 color := Green if pct < 50 { color = Red } else if pct < 80 { color = Yellow } successRate = color(fmt.Sprintf("%d (%.0f%%)", row.SuccessCalls, pct)) } chargedStr := Gray("0") if row.TotalCharged > 0 { chargedStr = Yellow(fmt.Sprintf("%d", row.TotalCharged)) } table.AddRow( Cyan(row.Endpoint), Bold(fmt.Sprintf("%d", row.TotalCalls)), successRate, Gray(fmt.Sprintf("%d ea", row.EndpointCost)), chargedStr, ) totalCalls += row.TotalCalls totalCharged += row.TotalCharged } sb.WriteString(table.Render()) sb.WriteString(fmt.Sprintf("\n%s %d calls %s %d credits charged\n", Blue("Total calls:"), totalCalls, Blue("Total charged:"), totalCharged, )) return sb.String(), nil } // FormatCreditUsageByAccount renders per-account per-endpoint aggregate stats. func FormatCreditUsageByAccount(resp *models.CreditUsageByAccountResponse, asJSON bool) (string, error) { if asJSON { if err := PrintColorizedJSON(resp); err != nil { return "", err } return "", nil } if len(resp.Accounts) == 0 { return fmt.Sprintf("%s\n", Gray("No account usage data found")), nil } fromStr, toStr := formatTimeRange(resp.From, resp.To) var sb strings.Builder sb.WriteString(fmt.Sprintf("\n%s\n", Bold(Cyan("Usage by Account")))) sb.WriteString(fmt.Sprintf("%s %s → %s\n\n", Gray(strings.Repeat("─", 60)), Gray(fromStr), Gray(toStr))) table := NewTable([]string{"Account", "Endpoint", "Calls", "Charged", "Last Call"}) // Group consecutive rows by account for visual separation var prevAccount string var totalCalls, totalCharged int64 for _, row := range resp.Accounts { nameStr := Bold(row.AccountName) if row.AccountName == prevAccount { nameStr = Dim(" ↳") } prevAccount = row.AccountName chargedStr := Gray("0") if row.TotalCharged > 0 { chargedStr = Yellow(fmt.Sprintf("%d", row.TotalCharged)) } lastCall := row.LastCall if t, err := time.Parse("2006-01-02 15:04:05", row.LastCall); err == nil { lastCall = t.Format("2006-01-02 15:04") } table.AddRow( nameStr, Cyan(row.Endpoint), fmt.Sprintf("%d", row.TotalCalls), chargedStr, Gray(lastCall), ) totalCalls += row.TotalCalls totalCharged += row.TotalCharged } sb.WriteString(table.Render()) sb.WriteString(fmt.Sprintf("\n%s %d %s %d credits charged\n", Blue("Total calls:"), totalCalls, Blue("Total charged:"), totalCharged, )) return sb.String(), nil } func formatTimeRange(from, to string) (string, string) { fromStr, toStr := from, to if t, err := time.Parse(time.RFC3339, from); err == nil { fromStr = t.Format("2006-01-02 15:04") } if t, err := time.Parse(time.RFC3339, to); err == nil { toStr = t.Format("2006-01-02 15:04") } return fromStr, toStr } func truncate(s string, n int) string { if len(s) <= n { return s } return s[:n-1] + "…" }