feat(site): improve cost analytics view (#23069)

Surfaces cache token data in the analytics views and fixes table
spacing.

### Changes

- **Cache token columns**: Added cache read and cache write token counts
to all analytics views (user and admin), from SQL queries through Go SDK
types to the frontend tables and summary cards.
- **Table spacing fix**: Replaced the bare React fragment in
`ChatCostSummaryView` with a `space-y-6` container so the model and chat
breakdown tables no longer overlap.

### Data flow

`chat_messages` table already stores `cache_read_tokens` and
`cache_creation_tokens` (and uses them for cost calculation). This PR
aggregates and displays them alongside input/output tokens in:

- Summary cards (6 cards: Total Cost, Input, Output, Cache Read, Cache
Write, Messages)
- Per-model breakdown table
- Per-chat breakdown table
- Admin per-user table
This commit is contained in:
Michael Suchacz
2026-03-14 01:22:00 -05:00
committed by GitHub
parent f6976fd6c1
commit 969066b55e
11 changed files with 252 additions and 106 deletions
+40 -32
View File
@@ -432,15 +432,17 @@ func (api *API) chatCostSummary(rw http.ResponseWriter, r *http.Request) {
}
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ChatCostSummary{
StartDate: startDate,
EndDate: endDate,
TotalCostMicros: summary.TotalCostMicros,
PricedMessageCount: summary.PricedMessageCount,
UnpricedMessageCount: summary.UnpricedMessageCount,
TotalInputTokens: summary.TotalInputTokens,
TotalOutputTokens: summary.TotalOutputTokens,
ByModel: modelBreakdowns,
ByChat: chatBreakdowns,
StartDate: startDate,
EndDate: endDate,
TotalCostMicros: summary.TotalCostMicros,
PricedMessageCount: summary.PricedMessageCount,
UnpricedMessageCount: summary.UnpricedMessageCount,
TotalInputTokens: summary.TotalInputTokens,
TotalOutputTokens: summary.TotalOutputTokens,
TotalCacheReadTokens: summary.TotalCacheReadTokens,
TotalCacheCreationTokens: summary.TotalCacheCreationTokens,
ByModel: modelBreakdowns,
ByChat: chatBreakdowns,
})
}
@@ -2488,39 +2490,45 @@ func convertChatCostModelBreakdown(model database.GetChatCostPerModelRow) coders
displayName = model.Model
}
return codersdk.ChatCostModelBreakdown{
ModelConfigID: model.ModelConfigID,
DisplayName: displayName,
Provider: model.Provider,
Model: model.Model,
TotalCostMicros: model.TotalCostMicros,
MessageCount: model.MessageCount,
TotalInputTokens: model.TotalInputTokens,
TotalOutputTokens: model.TotalOutputTokens,
ModelConfigID: model.ModelConfigID,
DisplayName: displayName,
Provider: model.Provider,
Model: model.Model,
TotalCostMicros: model.TotalCostMicros,
MessageCount: model.MessageCount,
TotalInputTokens: model.TotalInputTokens,
TotalOutputTokens: model.TotalOutputTokens,
TotalCacheReadTokens: model.TotalCacheReadTokens,
TotalCacheCreationTokens: model.TotalCacheCreationTokens,
}
}
func convertChatCostChatBreakdown(chat database.GetChatCostPerChatRow) codersdk.ChatCostChatBreakdown {
return codersdk.ChatCostChatBreakdown{
RootChatID: chat.RootChatID,
ChatTitle: chat.ChatTitle,
TotalCostMicros: chat.TotalCostMicros,
MessageCount: chat.MessageCount,
TotalInputTokens: chat.TotalInputTokens,
TotalOutputTokens: chat.TotalOutputTokens,
RootChatID: chat.RootChatID,
ChatTitle: chat.ChatTitle,
TotalCostMicros: chat.TotalCostMicros,
MessageCount: chat.MessageCount,
TotalInputTokens: chat.TotalInputTokens,
TotalOutputTokens: chat.TotalOutputTokens,
TotalCacheReadTokens: chat.TotalCacheReadTokens,
TotalCacheCreationTokens: chat.TotalCacheCreationTokens,
}
}
func convertChatCostUserRollup(user database.GetChatCostPerUserRow) codersdk.ChatCostUserRollup {
return codersdk.ChatCostUserRollup{
UserID: user.UserID,
Username: user.Username,
Name: user.Name,
AvatarURL: user.AvatarURL,
TotalCostMicros: user.TotalCostMicros,
MessageCount: user.MessageCount,
ChatCount: user.ChatCount,
TotalInputTokens: user.TotalInputTokens,
TotalOutputTokens: user.TotalOutputTokens,
UserID: user.UserID,
Username: user.Username,
Name: user.Name,
AvatarURL: user.AvatarURL,
TotalCostMicros: user.TotalCostMicros,
MessageCount: user.MessageCount,
ChatCount: user.ChatCount,
TotalInputTokens: user.TotalInputTokens,
TotalOutputTokens: user.TotalOutputTokens,
TotalCacheReadTokens: user.TotalCacheReadTokens,
TotalCacheCreationTokens: user.TotalCacheCreationTokens,
}
}
+62 -34
View File
@@ -3259,7 +3259,9 @@ WITH chat_costs AS (
OR cm.cache_read_tokens IS NOT NULL
)::bigint AS message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM chat_messages cm
JOIN chats c ON c.id = cm.chat_id
WHERE c.owner_id = $1::uuid
@@ -3274,7 +3276,9 @@ SELECT
cc.total_cost_micros,
cc.message_count,
cc.total_input_tokens,
cc.total_output_tokens
cc.total_output_tokens,
cc.total_cache_read_tokens,
cc.total_cache_creation_tokens
FROM chat_costs cc
LEFT JOIN chats rc ON rc.id = cc.root_chat_id
ORDER BY cc.total_cost_micros DESC
@@ -3287,12 +3291,14 @@ type GetChatCostPerChatParams struct {
}
type GetChatCostPerChatRow struct {
RootChatID uuid.UUID `db:"root_chat_id" json:"root_chat_id"`
ChatTitle string `db:"chat_title" json:"chat_title"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
RootChatID uuid.UUID `db:"root_chat_id" json:"root_chat_id"`
ChatTitle string `db:"chat_title" json:"chat_title"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCacheReadTokens int64 `db:"total_cache_read_tokens" json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `db:"total_cache_creation_tokens" json:"total_cache_creation_tokens"`
}
// Per-root-chat cost breakdown for a single user within a date range.
@@ -3314,6 +3320,8 @@ func (q *sqlQuerier) GetChatCostPerChat(ctx context.Context, arg GetChatCostPerC
&i.MessageCount,
&i.TotalInputTokens,
&i.TotalOutputTokens,
&i.TotalCacheReadTokens,
&i.TotalCacheCreationTokens,
); err != nil {
return nil, err
}
@@ -3343,7 +3351,9 @@ SELECT
OR cm.cache_read_tokens IS NOT NULL
)::bigint AS message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -3368,14 +3378,16 @@ type GetChatCostPerModelParams struct {
}
type GetChatCostPerModelRow struct {
ModelConfigID uuid.UUID `db:"model_config_id" json:"model_config_id"`
DisplayName string `db:"display_name" json:"display_name"`
Provider string `db:"provider" json:"provider"`
Model string `db:"model" json:"model"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
ModelConfigID uuid.UUID `db:"model_config_id" json:"model_config_id"`
DisplayName string `db:"display_name" json:"display_name"`
Provider string `db:"provider" json:"provider"`
Model string `db:"model" json:"model"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCacheReadTokens int64 `db:"total_cache_read_tokens" json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `db:"total_cache_creation_tokens" json:"total_cache_creation_tokens"`
}
// Per-model cost breakdown for a single user within a date range.
@@ -3398,6 +3410,8 @@ func (q *sqlQuerier) GetChatCostPerModel(ctx context.Context, arg GetChatCostPer
&i.MessageCount,
&i.TotalInputTokens,
&i.TotalOutputTokens,
&i.TotalCacheReadTokens,
&i.TotalCacheCreationTokens,
); err != nil {
return nil, err
}
@@ -3429,7 +3443,9 @@ WITH chat_cost_users AS (
)::bigint AS message_count,
COUNT(DISTINCT COALESCE(c.root_chat_id, c.id))::bigint AS chat_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -3460,6 +3476,8 @@ SELECT
chat_count,
total_input_tokens,
total_output_tokens,
total_cache_read_tokens,
total_cache_creation_tokens,
COUNT(*) OVER()::bigint AS total_count
FROM
chat_cost_users
@@ -3481,16 +3499,18 @@ type GetChatCostPerUserParams struct {
}
type GetChatCostPerUserRow struct {
UserID uuid.UUID `db:"user_id" json:"user_id"`
Username string `db:"username" json:"username"`
Name string `db:"name" json:"name"`
AvatarURL string `db:"avatar_url" json:"avatar_url"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
ChatCount int64 `db:"chat_count" json:"chat_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCount int64 `db:"total_count" json:"total_count"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
Username string `db:"username" json:"username"`
Name string `db:"name" json:"name"`
AvatarURL string `db:"avatar_url" json:"avatar_url"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
MessageCount int64 `db:"message_count" json:"message_count"`
ChatCount int64 `db:"chat_count" json:"chat_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCacheReadTokens int64 `db:"total_cache_read_tokens" json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `db:"total_cache_creation_tokens" json:"total_cache_creation_tokens"`
TotalCount int64 `db:"total_count" json:"total_count"`
}
// Deployment-wide per-user cost rollup within a date range.
@@ -3520,6 +3540,8 @@ func (q *sqlQuerier) GetChatCostPerUser(ctx context.Context, arg GetChatCostPerU
&i.ChatCount,
&i.TotalInputTokens,
&i.TotalOutputTokens,
&i.TotalCacheReadTokens,
&i.TotalCacheCreationTokens,
&i.TotalCount,
); err != nil {
return nil, err
@@ -3552,7 +3574,9 @@ SELECT
)
)::bigint AS unpriced_message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -3571,11 +3595,13 @@ type GetChatCostSummaryParams struct {
}
type GetChatCostSummaryRow struct {
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
PricedMessageCount int64 `db:"priced_message_count" json:"priced_message_count"`
UnpricedMessageCount int64 `db:"unpriced_message_count" json:"unpriced_message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCostMicros int64 `db:"total_cost_micros" json:"total_cost_micros"`
PricedMessageCount int64 `db:"priced_message_count" json:"priced_message_count"`
UnpricedMessageCount int64 `db:"unpriced_message_count" json:"unpriced_message_count"`
TotalInputTokens int64 `db:"total_input_tokens" json:"total_input_tokens"`
TotalOutputTokens int64 `db:"total_output_tokens" json:"total_output_tokens"`
TotalCacheReadTokens int64 `db:"total_cache_read_tokens" json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `db:"total_cache_creation_tokens" json:"total_cache_creation_tokens"`
}
// Aggregate cost summary for a single user within a date range.
@@ -3589,6 +3615,8 @@ func (q *sqlQuerier) GetChatCostSummary(ctx context.Context, arg GetChatCostSumm
&i.UnpricedMessageCount,
&i.TotalInputTokens,
&i.TotalOutputTokens,
&i.TotalCacheReadTokens,
&i.TotalCacheCreationTokens,
)
return i, err
}
+17 -5
View File
@@ -526,7 +526,9 @@ SELECT
)
)::bigint AS unpriced_message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -554,7 +556,9 @@ SELECT
OR cm.cache_read_tokens IS NOT NULL
)::bigint AS message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -587,7 +591,9 @@ WITH chat_costs AS (
OR cm.cache_read_tokens IS NOT NULL
)::bigint AS message_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM chat_messages cm
JOIN chats c ON c.id = cm.chat_id
WHERE c.owner_id = @owner_id::uuid
@@ -602,7 +608,9 @@ SELECT
cc.total_cost_micros,
cc.message_count,
cc.total_input_tokens,
cc.total_output_tokens
cc.total_output_tokens,
cc.total_cache_read_tokens,
cc.total_cache_creation_tokens
FROM chat_costs cc
LEFT JOIN chats rc ON rc.id = cc.root_chat_id
ORDER BY cc.total_cost_micros DESC;
@@ -626,7 +634,9 @@ WITH chat_cost_users AS (
)::bigint AS message_count,
COUNT(DISTINCT COALESCE(c.root_chat_id, c.id))::bigint AS chat_count,
COALESCE(SUM(cm.input_tokens), 0)::bigint AS total_input_tokens,
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens
COALESCE(SUM(cm.output_tokens), 0)::bigint AS total_output_tokens,
COALESCE(SUM(cm.cache_read_tokens), 0)::bigint AS total_cache_read_tokens,
COALESCE(SUM(cm.cache_creation_tokens), 0)::bigint AS total_cache_creation_tokens
FROM
chat_messages cm
JOIN
@@ -657,6 +667,8 @@ SELECT
chat_count,
total_input_tokens,
total_output_tokens,
total_cache_read_tokens,
total_cache_creation_tokens,
COUNT(*) OVER()::bigint AS total_count
FROM
chat_cost_users
+40 -32
View File
@@ -733,50 +733,58 @@ type ChatCostUsersOptions struct {
// ChatCostSummary is the response from the chat cost summary endpoint.
type ChatCostSummary struct {
StartDate time.Time `json:"start_date" format:"date-time"`
EndDate time.Time `json:"end_date" format:"date-time"`
TotalCostMicros int64 `json:"total_cost_micros"`
PricedMessageCount int64 `json:"priced_message_count"`
UnpricedMessageCount int64 `json:"unpriced_message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
ByModel []ChatCostModelBreakdown `json:"by_model"`
ByChat []ChatCostChatBreakdown `json:"by_chat"`
StartDate time.Time `json:"start_date" format:"date-time"`
EndDate time.Time `json:"end_date" format:"date-time"`
TotalCostMicros int64 `json:"total_cost_micros"`
PricedMessageCount int64 `json:"priced_message_count"`
UnpricedMessageCount int64 `json:"unpriced_message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
ByModel []ChatCostModelBreakdown `json:"by_model"`
ByChat []ChatCostChatBreakdown `json:"by_chat"`
}
// ChatCostModelBreakdown contains per-model cost aggregation.
type ChatCostModelBreakdown struct {
ModelConfigID uuid.UUID `json:"model_config_id" format:"uuid"`
DisplayName string `json:"display_name"`
Provider string `json:"provider"`
Model string `json:"model"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
ModelConfigID uuid.UUID `json:"model_config_id" format:"uuid"`
DisplayName string `json:"display_name"`
Provider string `json:"provider"`
Model string `json:"model"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
}
// ChatCostChatBreakdown contains per-root-chat cost aggregation.
type ChatCostChatBreakdown struct {
RootChatID uuid.UUID `json:"root_chat_id" format:"uuid"`
ChatTitle string `json:"chat_title"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
RootChatID uuid.UUID `json:"root_chat_id" format:"uuid"`
ChatTitle string `json:"chat_title"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
}
// ChatCostUserRollup contains per-user cost aggregation for admin views.
type ChatCostUserRollup struct {
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
Name string `json:"name"`
AvatarURL string `json:"avatar_url"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
ChatCount int64 `json:"chat_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
UserID uuid.UUID `json:"user_id" format:"uuid"`
Username string `json:"username"`
Name string `json:"name"`
AvatarURL string `json:"avatar_url"`
TotalCostMicros int64 `json:"total_cost_micros"`
MessageCount int64 `json:"message_count"`
ChatCount int64 `json:"chat_count"`
TotalInputTokens int64 `json:"total_input_tokens"`
TotalOutputTokens int64 `json:"total_output_tokens"`
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
}
// ChatCostUsersResponse is the response from the admin chat cost users endpoint.
+8
View File
@@ -1082,6 +1082,8 @@ export interface ChatCostChatBreakdown {
readonly message_count: number;
readonly total_input_tokens: number;
readonly total_output_tokens: number;
readonly total_cache_read_tokens: number;
readonly total_cache_creation_tokens: number;
}
// From codersdk/chats.go
@@ -1097,6 +1099,8 @@ export interface ChatCostModelBreakdown {
readonly message_count: number;
readonly total_input_tokens: number;
readonly total_output_tokens: number;
readonly total_cache_read_tokens: number;
readonly total_cache_creation_tokens: number;
}
// From codersdk/chats.go
@@ -1111,6 +1115,8 @@ export interface ChatCostSummary {
readonly unpriced_message_count: number;
readonly total_input_tokens: number;
readonly total_output_tokens: number;
readonly total_cache_read_tokens: number;
readonly total_cache_creation_tokens: number;
readonly by_model: readonly ChatCostModelBreakdown[];
readonly by_chat: readonly ChatCostChatBreakdown[];
}
@@ -1138,6 +1144,8 @@ export interface ChatCostUserRollup {
readonly chat_count: number;
readonly total_input_tokens: number;
readonly total_output_tokens: number;
readonly total_cache_read_tokens: number;
readonly total_cache_creation_tokens: number;
}
// From codersdk/chats.go
@@ -49,6 +49,8 @@ const mockAnalyticsSummary: TypesGen.ChatCostSummary = {
unpriced_message_count: 1,
total_input_tokens: 123_456,
total_output_tokens: 654_321,
total_cache_read_tokens: 9_876,
total_cache_creation_tokens: 5_432,
by_model: [
{
model_config_id: "model-config-1",
@@ -59,6 +61,8 @@ const mockAnalyticsSummary: TypesGen.ChatCostSummary = {
message_count: 9,
total_input_tokens: 100_000,
total_output_tokens: 200_000,
total_cache_read_tokens: 7_654,
total_cache_creation_tokens: 3_210,
},
],
by_chat: [
@@ -69,6 +73,8 @@ const mockAnalyticsSummary: TypesGen.ChatCostSummary = {
message_count: 5,
total_input_tokens: 60_000,
total_output_tokens: 80_000,
total_cache_read_tokens: 4_321,
total_cache_creation_tokens: 1_234,
},
],
};
@@ -88,6 +94,8 @@ const mockUsageUsers: TypesGen.ChatCostUsersResponse = {
chat_count: 3,
total_input_tokens: 120_000,
total_output_tokens: 45_000,
total_cache_read_tokens: 6_789,
total_cache_creation_tokens: 2_468,
},
],
};
@@ -13,6 +13,8 @@ const buildSummary = (
unpriced_message_count: 0,
total_input_tokens: 123_456,
total_output_tokens: 654_321,
total_cache_read_tokens: 9_876,
total_cache_creation_tokens: 5_432,
by_model: [
{
model_config_id: "model-config-1",
@@ -23,6 +25,8 @@ const buildSummary = (
message_count: 9,
total_input_tokens: 100_000,
total_output_tokens: 200_000,
total_cache_read_tokens: 7_654,
total_cache_creation_tokens: 3_210,
},
],
by_chat: [
@@ -33,6 +37,8 @@ const buildSummary = (
message_count: 5,
total_input_tokens: 60_000,
total_output_tokens: 80_000,
total_cache_read_tokens: 4_321,
total_cache_creation_tokens: 1_234,
},
],
...overrides,
@@ -61,8 +61,8 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
}
return (
<>
<div className="grid grid-cols-2 gap-4 md:grid-cols-4">
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
Total Cost
@@ -87,6 +87,22 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
{formatTokenCount(summary.total_output_tokens)}
</p>
</div>
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
Cache Read
</p>
<p className="mt-1 text-2xl font-semibold text-content-primary">
{formatTokenCount(summary.total_cache_read_tokens)}
</p>
</div>
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
Cache Write
</p>
<p className="mt-1 text-2xl font-semibold text-content-primary">
{formatTokenCount(summary.total_cache_creation_tokens)}
</p>
</div>
<div className="rounded-lg border border-border-default bg-surface-secondary p-4">
<p className="text-xs font-medium uppercase tracking-wide text-content-secondary">
Messages
@@ -128,6 +144,12 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
</TableHead>
<TableHead className="px-4 py-3 text-right">Input</TableHead>
<TableHead className="px-4 py-3 text-right">Output</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Read
</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Write
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -154,6 +176,12 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(model.total_output_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(model.total_cache_read_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(model.total_cache_creation_tokens)}
</TableCell>
</TableRow>
))}
</TableBody>
@@ -171,6 +199,12 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
</TableHead>
<TableHead className="px-4 py-3 text-right">Input</TableHead>
<TableHead className="px-4 py-3 text-right">Output</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Read
</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Write
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -198,6 +232,12 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(chat.total_output_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(chat.total_cache_read_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(chat.total_cache_creation_tokens)}
</TableCell>
</TableRow>
))}
</TableBody>
@@ -205,6 +245,6 @@ export const ChatCostSummaryView: FC<ChatCostSummaryViewProps> = ({
</div>
</>
)}
</>
</div>
);
};
@@ -90,6 +90,8 @@ const buildUsageUser = (
chat_count: 3,
total_input_tokens: 120_000,
total_output_tokens: 45_000,
total_cache_read_tokens: 6_789,
total_cache_creation_tokens: 2_468,
...overrides,
});
@@ -109,6 +111,8 @@ const mockUsageUsers: ChatCostUsersResponse = {
chat_count: 2,
total_input_tokens: 80_000,
total_output_tokens: 30_000,
total_cache_read_tokens: 4_321,
total_cache_creation_tokens: 1_234,
}),
],
};
@@ -121,6 +125,8 @@ const mockUsageSummary: ChatCostSummary = {
unpriced_message_count: 0,
total_input_tokens: 120_000,
total_output_tokens: 45_000,
total_cache_read_tokens: 6_789,
total_cache_creation_tokens: 2_468,
by_model: [
{
model_config_id: "model-cfg-1",
@@ -131,6 +137,8 @@ const mockUsageSummary: ChatCostSummary = {
message_count: 12,
total_input_tokens: 120_000,
total_output_tokens: 45_000,
total_cache_read_tokens: 6_789,
total_cache_creation_tokens: 2_468,
},
],
by_chat: [
@@ -141,6 +149,8 @@ const mockUsageSummary: ChatCostSummary = {
message_count: 12,
total_input_tokens: 120_000,
total_output_tokens: 45_000,
total_cache_read_tokens: 6_789,
total_cache_creation_tokens: 2_468,
},
],
};
@@ -129,6 +129,12 @@ const UserRow: FC<{
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(user.total_output_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(user.total_cache_read_tokens)}
</TableCell>
<TableCell className="px-4 py-3 text-right">
{formatTokenCount(user.total_cache_creation_tokens)}
</TableCell>
</TableRow>
);
};
@@ -306,6 +312,12 @@ const UsageContent: FC = () => {
<TableHead className="px-4 py-3 text-right">
Output Tokens
</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Read
</TableHead>
<TableHead className="px-4 py-3 text-right">
Cache Write
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -14,6 +14,8 @@ const mockSummary: TypesGen.ChatCostSummary = {
unpriced_message_count: 1,
total_input_tokens: 123_456,
total_output_tokens: 654_321,
total_cache_read_tokens: 9_876,
total_cache_creation_tokens: 5_432,
by_model: [
{
model_config_id: "model-config-1",
@@ -24,6 +26,8 @@ const mockSummary: TypesGen.ChatCostSummary = {
message_count: 9,
total_input_tokens: 100_000,
total_output_tokens: 200_000,
total_cache_read_tokens: 7_654,
total_cache_creation_tokens: 3_210,
},
],
by_chat: [
@@ -34,6 +38,8 @@ const mockSummary: TypesGen.ChatCostSummary = {
message_count: 5,
total_input_tokens: 60_000,
total_output_tokens: 80_000,
total_cache_read_tokens: 4_321,
total_cache_creation_tokens: 1_234,
},
],
};