- 记录失败请求并在用户端/管理端展示;分类下拉改用统一 Select 组件 - 模型过滤改后端 ILIKE 模糊匹配;新增「Key 名称」列(含已删除标记)与按 Key 过滤;时间列移至末列 - 用户可见「已删除 key 失败请求」:OpsErrorLogFilter 加 MatchDeletedKeyOwner,用户侧归属 放宽为 (user_id OR deleted_key_owner_user_id),让 key 原所有者能看到删除 key 后继续请求 导致的认证失败记录(他人仍 NotFound,不泄露存在性) - 迁移 148:ops_error_logs 用户+时间索引 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
345 lines
8.2 KiB
TypeScript
345 lines
8.2 KiB
TypeScript
/**
|
|
* Usage tracking API endpoints
|
|
* Handles usage logs and statistics retrieval
|
|
*/
|
|
|
|
import { apiClient } from './client'
|
|
import type {
|
|
UsageLog,
|
|
UsageQueryParams,
|
|
UsageStatsResponse,
|
|
PaginatedResponse,
|
|
TrendDataPoint,
|
|
ModelStat,
|
|
UserErrorRequest,
|
|
UserErrorRequestDetail,
|
|
UserErrorListParams
|
|
} from '@/types'
|
|
|
|
// ==================== Dashboard Types ====================
|
|
|
|
export interface PlatformDashboardStats {
|
|
platform: string
|
|
total_requests: number
|
|
total_tokens: number
|
|
total_actual_cost: number
|
|
today_requests: number
|
|
today_tokens: number
|
|
today_actual_cost: number
|
|
}
|
|
|
|
export interface UserDashboardStats {
|
|
total_api_keys: number
|
|
active_api_keys: number
|
|
total_requests: number
|
|
total_input_tokens: number
|
|
total_output_tokens: number
|
|
total_cache_creation_tokens: number
|
|
total_cache_read_tokens: number
|
|
total_tokens: number
|
|
total_cost: number // 标准计费
|
|
total_actual_cost: number // 实际扣除
|
|
today_requests: number
|
|
today_input_tokens: number
|
|
today_output_tokens: number
|
|
today_cache_creation_tokens: number
|
|
today_cache_read_tokens: number
|
|
today_tokens: number
|
|
today_cost: number // 今日标准计费
|
|
today_actual_cost: number // 今日实际扣除
|
|
average_duration_ms: number
|
|
rpm: number // 近5分钟平均每分钟请求数
|
|
tpm: number // 近5分钟平均每分钟Token数
|
|
by_platform?: PlatformDashboardStats[]
|
|
}
|
|
|
|
export interface TrendParams {
|
|
start_date?: string
|
|
end_date?: string
|
|
granularity?: 'day' | 'hour'
|
|
}
|
|
|
|
export interface TrendResponse {
|
|
trend: TrendDataPoint[]
|
|
start_date: string
|
|
end_date: string
|
|
granularity: string
|
|
}
|
|
|
|
export interface ModelStatsResponse {
|
|
models: ModelStat[]
|
|
start_date: string
|
|
end_date: string
|
|
}
|
|
|
|
export interface ApiKeyDailyUsagePoint {
|
|
date: string
|
|
requests: number
|
|
input_tokens: number
|
|
output_tokens: number
|
|
cache_read_tokens: number
|
|
cache_write_tokens: number
|
|
total_tokens: number
|
|
cost: number
|
|
actual_cost: number
|
|
}
|
|
|
|
export interface ApiKeyDailyUsageResponse {
|
|
items: ApiKeyDailyUsagePoint[]
|
|
days: number
|
|
start_date: string
|
|
end_date: string
|
|
}
|
|
|
|
/**
|
|
* List usage logs with optional filters
|
|
* @param page - Page number (default: 1)
|
|
* @param pageSize - Items per page (default: 20)
|
|
* @param apiKeyId - Filter by API key ID
|
|
* @returns Paginated list of usage logs
|
|
*/
|
|
export async function list(
|
|
page: number = 1,
|
|
pageSize: number = 20,
|
|
apiKeyId?: number
|
|
): Promise<PaginatedResponse<UsageLog>> {
|
|
const params: UsageQueryParams = {
|
|
page,
|
|
page_size: pageSize
|
|
}
|
|
|
|
if (apiKeyId !== undefined) {
|
|
params.api_key_id = apiKeyId
|
|
}
|
|
|
|
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get usage logs with advanced query parameters
|
|
* @param params - Query parameters for filtering and pagination
|
|
* @returns Paginated list of usage logs
|
|
*/
|
|
export async function query(
|
|
params: UsageQueryParams & { sort_by?: string; sort_order?: 'asc' | 'desc' },
|
|
config: { signal?: AbortSignal } = {}
|
|
): Promise<PaginatedResponse<UsageLog>> {
|
|
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
|
...config,
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get usage statistics for a specific period
|
|
* @param period - Time period ('today', 'week', 'month', 'year')
|
|
* @param apiKeyId - Optional API key ID filter
|
|
* @returns Usage statistics
|
|
*/
|
|
export async function getStats(
|
|
period: string = 'today',
|
|
apiKeyId?: number
|
|
): Promise<UsageStatsResponse> {
|
|
const params: Record<string, unknown> = { period }
|
|
|
|
if (apiKeyId !== undefined) {
|
|
params.api_key_id = apiKeyId
|
|
}
|
|
|
|
const { data } = await apiClient.get<UsageStatsResponse>('/usage/stats', {
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get usage statistics for a date range
|
|
* @param startDate - Start date (YYYY-MM-DD format)
|
|
* @param endDate - End date (YYYY-MM-DD format)
|
|
* @param apiKeyId - Optional API key ID filter
|
|
* @returns Usage statistics
|
|
*/
|
|
export async function getStatsByDateRange(
|
|
startDate: string,
|
|
endDate: string,
|
|
apiKeyId?: number
|
|
): Promise<UsageStatsResponse> {
|
|
const params: Record<string, unknown> = {
|
|
start_date: startDate,
|
|
end_date: endDate
|
|
}
|
|
|
|
if (apiKeyId !== undefined) {
|
|
params.api_key_id = apiKeyId
|
|
}
|
|
|
|
const { data } = await apiClient.get<UsageStatsResponse>('/usage/stats', {
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get usage by date range
|
|
* @param startDate - Start date (YYYY-MM-DD format)
|
|
* @param endDate - End date (YYYY-MM-DD format)
|
|
* @param apiKeyId - Optional API key ID filter
|
|
* @returns Usage logs within date range
|
|
*/
|
|
export async function getByDateRange(
|
|
startDate: string,
|
|
endDate: string,
|
|
apiKeyId?: number
|
|
): Promise<PaginatedResponse<UsageLog>> {
|
|
const params: UsageQueryParams = {
|
|
start_date: startDate,
|
|
end_date: endDate,
|
|
page: 1,
|
|
page_size: 100
|
|
}
|
|
|
|
if (apiKeyId !== undefined) {
|
|
params.api_key_id = apiKeyId
|
|
}
|
|
|
|
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get detailed usage log by ID
|
|
* @param id - Usage log ID
|
|
* @returns Usage log details
|
|
*/
|
|
export async function getById(id: number): Promise<UsageLog> {
|
|
const { data } = await apiClient.get<UsageLog>(`/usage/${id}`)
|
|
return data
|
|
}
|
|
|
|
// ==================== Dashboard API ====================
|
|
|
|
/**
|
|
* Get user dashboard statistics
|
|
* @returns Dashboard statistics for current user
|
|
*/
|
|
export async function getDashboardStats(): Promise<UserDashboardStats> {
|
|
const { data } = await apiClient.get<UserDashboardStats>('/usage/dashboard/stats')
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get user usage trend data
|
|
* @param params - Query parameters for filtering
|
|
* @returns Usage trend data for current user
|
|
*/
|
|
export async function getDashboardTrend(params?: TrendParams): Promise<TrendResponse> {
|
|
const { data } = await apiClient.get<TrendResponse>('/usage/dashboard/trend', { params })
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get user model usage statistics
|
|
* @param params - Query parameters for filtering
|
|
* @returns Model usage statistics for current user
|
|
*/
|
|
export async function getDashboardModels(params?: {
|
|
start_date?: string
|
|
end_date?: string
|
|
}): Promise<ModelStatsResponse> {
|
|
const { data } = await apiClient.get<ModelStatsResponse>('/usage/dashboard/models', { params })
|
|
return data
|
|
}
|
|
|
|
/**
|
|
* Get daily usage details for one API key owned by the current user.
|
|
* @param apiKeyId - API key ID
|
|
* @param days - Number of days to include (1-90)
|
|
* @returns Daily usage detail rows
|
|
*/
|
|
export async function getMyApiKeyDailyUsage(
|
|
apiKeyId: number,
|
|
days: number = 30
|
|
): Promise<ApiKeyDailyUsageResponse> {
|
|
const { data } = await apiClient.get<ApiKeyDailyUsageResponse>(
|
|
`/user/api-keys/${apiKeyId}/usage/daily`,
|
|
{ params: { days } }
|
|
)
|
|
return data
|
|
}
|
|
|
|
export interface BatchApiKeyUsageStats {
|
|
api_key_id: number
|
|
today_actual_cost: number
|
|
total_actual_cost: number
|
|
}
|
|
|
|
export interface BatchApiKeysUsageResponse {
|
|
stats: Record<string, BatchApiKeyUsageStats>
|
|
}
|
|
|
|
/**
|
|
* Get batch usage stats for user's own API keys
|
|
* @param apiKeyIds - Array of API key IDs
|
|
* @param options - Optional request options
|
|
* @returns Usage stats map keyed by API key ID
|
|
*/
|
|
export async function getDashboardApiKeysUsage(
|
|
apiKeyIds: number[],
|
|
options?: {
|
|
signal?: AbortSignal
|
|
}
|
|
): Promise<BatchApiKeysUsageResponse> {
|
|
const { data } = await apiClient.post<BatchApiKeysUsageResponse>(
|
|
'/usage/dashboard/api-keys-usage',
|
|
{
|
|
api_key_ids: apiKeyIds
|
|
},
|
|
{
|
|
signal: options?.signal
|
|
}
|
|
)
|
|
return data
|
|
}
|
|
|
|
export async function listMyErrorRequests(
|
|
params: UserErrorListParams,
|
|
config: { signal?: AbortSignal } = {}
|
|
): Promise<PaginatedResponse<UserErrorRequest>> {
|
|
const { data } = await apiClient.get<PaginatedResponse<UserErrorRequest>>('/usage/errors', {
|
|
...config,
|
|
params
|
|
})
|
|
return data
|
|
}
|
|
|
|
export async function getMyErrorDetail(id: number): Promise<UserErrorRequestDetail> {
|
|
const { data } = await apiClient.get<UserErrorRequestDetail>(`/usage/errors/${id}`)
|
|
return data
|
|
}
|
|
|
|
export const usageAPI = {
|
|
list,
|
|
query,
|
|
getStats,
|
|
getStatsByDateRange,
|
|
getByDateRange,
|
|
getById,
|
|
// Dashboard
|
|
getDashboardStats,
|
|
getDashboardTrend,
|
|
getDashboardModels,
|
|
getMyApiKeyDailyUsage,
|
|
getDashboardApiKeysUsage,
|
|
// Error requests
|
|
listMyErrorRequests,
|
|
getMyErrorDetail,
|
|
}
|
|
|
|
export default usageAPI
|