refactor(web): type the API layer end to end

Make request generic so the axios envelope is unwrapped as a typed value,
and thread the known response shapes through every api module and their
call sites so a wire rename surfaces as a compile error instead of silent
any. Type-only: no runtime or visual change.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
zyycn
2026-07-27 01:52:00 +08:00
co-authored by Claude
parent cc0303c65e
commit 2366f66f33
15 changed files with 900 additions and 53 deletions
+192 -13
View File
@@ -1,7 +1,186 @@
import request from '../request'
export interface AccountQuotaWindow {
key: string
group: string
windowSeconds: number | null
labelDisplay: string
usedPercent: number | null
usedPercentDisplay: string
localUsage?: unknown
resetAtDisplay: string
}
export interface AccountQuota {
refreshedAtDisplay: string
windows: AccountQuotaWindow[]
}
export interface AccountCurrencyCost {
currency: string
estimatedAmount: string
estimatedAmountDisplay: string
}
export interface AccountModelUsage {
model: string
requestCount: number
requestCountDisplay: string
successRate: number | null
successRateDisplay: string
inputTokens: number | null
inputTokensDisplay: string
outputTokens: number | null
outputTokensDisplay: string
cachedTokens: number | null
cachedTokensDisplay: string
imageInputTokens: number | null
imageInputTokensDisplay: string
imageOutputTokens: number | null
imageOutputTokensDisplay: string
imageRequestCount: number
imageRequestCountDisplay: string
imageRequestFailedCount: number
imageRequestFailedCountDisplay: string
totalTokens: number | null
totalTokensDisplay: string
billingAmountUsd: string | null
billingAmountUsdDisplay: string
costEstimateStatus: string
knownCostCount: number
partialCostCount: number
unknownCostCount: number
costs: AccountCurrencyCost[]
lastUsedAt: string
lastUsedAtDisplay: string
}
export interface AccountUsage {
requestCount: number | null
requestCountDisplay: string
inputTokens: number | null
inputTokensDisplay: string
outputTokens: number | null
outputTokensDisplay: string
cachedTokens: number | null
cachedTokensDisplay: string
imageInputTokens: number | null
imageInputTokensDisplay: string
imageOutputTokens: number | null
imageOutputTokensDisplay: string
imageRequestCount: number | null
imageRequestCountDisplay: string
imageRequestFailedCount: number | null
imageRequestFailedCountDisplay: string
totalTokens: number | null
totalTokensDisplay: string
createdTokens: number | null
createdTokensDisplay: string
readTokens: number | null
readTokensDisplay: string
lastUsedAt: string | null
lastUsedAtDisplay: string
costEstimateStatus: string
knownCostCount: number | null
partialCostCount: number | null
unknownCostCount: number | null
costs: AccountCurrencyCost[]
models: AccountModelUsage[]
}
export interface Account {
id: string
name: string
provider: string
resourceRef: string
email: string | null
accountId: string | null
userId: string | null
label: string | null
planType: string | null
authenticationKind: string
hasRefreshToken: boolean
status: string
displayStatus: string
tokenRefreshing: boolean
availability: string
enabled: boolean
credentialRevision: number
stateRevision: number | null
accessTokenExpiresAt: string | null
accessTokenExpiresAtDisplay: string | null
refreshTokenExpiresAt: string | null
nextRefreshAt: string | null
addedAt: string
addedAtDisplay: string
updatedAt: string
updatedAtDisplay: string
quota: AccountQuota
usage: AccountUsage
}
export interface AccountPageMeta {
page: number
pageSize: number
total: number
totalPages: number
}
export interface AccountSummary {
total: number
active: number
quotaExhausted: number
unavailable: number
}
export interface AccountListResponse {
configRevision: number
items: Account[]
page: AccountPageMeta
summary: AccountSummary
}
export interface AccountRefreshResponse {
configRevision: number
account: Account
result?: string
error?: string
}
export interface AccountQuotaResponse {
account: Account
}
export interface AccountModelsResponse {
models: Array<{ id: string, label: string }>
}
export interface AccountImportResponse {
configRevision: number
importedCount: number
accountIds: string[]
}
export interface AccountMutationResponse {
configRevision: number
accountId: string
credentialRevision?: number
}
export interface AccountDeletionResponse {
configRevision: number
deletedCount: number
accountIds: string[]
}
export interface AccountOAuthStartResponse {
flowId: string
authorizationUrl: string
expiresAt: string
}
export function getAccounts(data: object) {
return request({
return request<AccountListResponse>({
url: '/api/admin/accounts',
method: 'GET',
params: data,
@@ -9,7 +188,7 @@ export function getAccounts(data: object) {
}
export function exportAccounts(data: object) {
return request({
return request<unknown>({
url: '/api/admin/accounts/export',
method: 'GET',
params: data,
@@ -17,7 +196,7 @@ export function exportAccounts(data: object) {
}
export function refreshAccount(data: object) {
return request({
return request<AccountRefreshResponse>({
url: '/api/admin/accounts/refresh',
method: 'POST',
data,
@@ -25,7 +204,7 @@ export function refreshAccount(data: object) {
}
export function getAccountQuota(data: object) {
return request({
return request<AccountQuotaResponse>({
url: '/api/admin/accounts/quota',
method: 'GET',
params: data,
@@ -33,7 +212,7 @@ export function getAccountQuota(data: object) {
}
export function refreshAccountQuota(data: object) {
return request({
return request<AccountQuotaResponse>({
url: '/api/admin/accounts/quota/refresh',
method: 'POST',
data,
@@ -41,7 +220,7 @@ export function refreshAccountQuota(data: object) {
}
export function getAccountModels(data: object) {
return request({
return request<AccountModelsResponse>({
url: '/api/admin/accounts/models',
method: 'GET',
params: data,
@@ -49,7 +228,7 @@ export function getAccountModels(data: object) {
}
export function refreshAccountModels(data: object) {
return request({
return request<AccountModelsResponse>({
url: '/api/admin/accounts/models/refresh',
method: 'POST',
data,
@@ -57,7 +236,7 @@ export function refreshAccountModels(data: object) {
}
export function importAccounts(data: object) {
return request({
return request<AccountImportResponse>({
url: '/api/admin/accounts/import',
method: 'POST',
data,
@@ -65,7 +244,7 @@ export function importAccounts(data: object) {
}
export function enableAccount(data: object) {
return request({
return request<AccountMutationResponse>({
url: '/api/admin/accounts/enable',
method: 'POST',
data,
@@ -73,7 +252,7 @@ export function enableAccount(data: object) {
}
export function disableAccount(data: object) {
return request({
return request<AccountMutationResponse>({
url: '/api/admin/accounts/disable',
method: 'POST',
data,
@@ -81,7 +260,7 @@ export function disableAccount(data: object) {
}
export function deleteAccounts(data: object) {
return request({
return request<AccountDeletionResponse>({
url: '/api/admin/accounts/delete',
method: 'POST',
data,
@@ -89,7 +268,7 @@ export function deleteAccounts(data: object) {
}
export function startAccountOAuth(data: object) {
return request({
return request<AccountOAuthStartResponse>({
url: '/api/admin/accounts/oauth/start',
method: 'POST',
data,
@@ -97,7 +276,7 @@ export function startAccountOAuth(data: object) {
}
export function completeAccountOAuth(data: object) {
return request({
return request<AccountMutationResponse>({
url: '/api/admin/accounts/oauth/complete',
method: 'POST',
data,
+44 -6
View File
@@ -1,7 +1,45 @@
import request from '../request'
export interface ApiKey {
id: string
name: string
label: string | null
providerKind: string
prefix: string
enabled: boolean
maxConcurrency: number
requestsPerMinute: number
createdAt: string
updatedAt: string
lastUsedAt: string | null
}
export interface ApiKeyListResponse {
configRevision: number
items: ApiKey[]
nextCursor: string | null
total: number
}
export interface ApiKeyCreateResponse {
configRevision: number
id: string
prefix: string
plaintextKey: string
}
export interface ApiKeyRevealResponse {
id: string
plaintextKey: string
}
export interface ApiKeyMutationResponse {
configRevision: number
id: string
}
export function getApiKeys(data: object) {
return request({
return request<ApiKeyListResponse>({
url: '/api/admin/client-keys',
method: 'GET',
params: data,
@@ -9,7 +47,7 @@ export function getApiKeys(data: object) {
}
export function createApiKey(data: object) {
return request({
return request<ApiKeyCreateResponse>({
url: '/api/admin/client-keys/create',
method: 'POST',
data,
@@ -17,7 +55,7 @@ export function createApiKey(data: object) {
}
export function revealApiKey(data: object) {
return request({
return request<ApiKeyRevealResponse>({
url: '/api/admin/client-keys/reveal',
method: 'GET',
params: data,
@@ -25,7 +63,7 @@ export function revealApiKey(data: object) {
}
export function deleteApiKey(data: object) {
return request({
return request<ApiKeyMutationResponse>({
url: '/api/admin/client-keys/delete',
method: 'POST',
data,
@@ -33,7 +71,7 @@ export function deleteApiKey(data: object) {
}
export function disableApiKey(data: object) {
return request({
return request<ApiKeyMutationResponse>({
url: '/api/admin/client-keys/disable',
method: 'POST',
data,
@@ -41,7 +79,7 @@ export function disableApiKey(data: object) {
}
export function enableApiKey(data: object) {
return request({
return request<ApiKeyMutationResponse>({
url: '/api/admin/client-keys/enable',
method: 'POST',
data,
+15 -3
View File
@@ -1,7 +1,19 @@
import request from '../request'
export interface LoginResponse {
expiresAt: string
}
export interface AuthStatusResponse {
authenticated: boolean
}
export interface LogoutResponse {
message: string
}
export function login(data: object) {
return request({
return request<LoginResponse>({
url: '/api/admin/auth/login',
method: 'POST',
data,
@@ -9,14 +21,14 @@ export function login(data: object) {
}
export function getAuthStatus() {
return request({
return request<AuthStatusResponse>({
url: '/api/admin/auth/status',
method: 'GET',
})
}
export function logout() {
return request({
return request<LogoutResponse>({
url: '/api/admin/auth/logout',
method: 'POST',
})
+209 -2
View File
@@ -1,7 +1,214 @@
import type { UsageRecord } from './usage'
import request from '../request'
export type DashboardTrendKind = 'usage' | 'latency' | 'errors'
export type DashboardHealthStatus
= 'future' | 'no_data' | 'unavailable' | 'unstable' | 'low_sample' | 'stable'
export interface DashboardTrendPoint {
time: string
bucket: string
label: string
requests: string
requestsValue: number
inputTokens: string
inputTokensValue: number
outputTokens: string
outputTokensValue: number
cachedTokens: string
cachedTokensValue: number
cacheHitRateValue: number
tokensValue: number
errors: string
errorsValue: number
latency: string
latencyValue: number | null
firstTokenLatency: string
firstTokenLatencyValue: number | null
maxLatency: string
maxLatencyValue: number | null
minLatency: string
minLatencyValue: number | null
successRate: string
successRateValue: number | null
}
export interface DashboardTrendSummaryItem {
label: string
value: string
ratio: string | null
}
export interface DashboardTrendResponse {
kind: DashboardTrendKind
points: DashboardTrendPoint[]
summary: DashboardTrendSummaryItem[]
}
export interface DashboardCards {
credentials: {
total: string
totalValue: number
available: string
availableValue: number
unavailable: string
unavailableValue: number
}
traffic: {
todayRequests: string
todayRequestsValue: number
yesterdayRequestsValue: number
totalRequests: string
}
tokens: {
todayTokens: string
todayTokensValue: number
yesterdayTokensValue: number
totalTokens: string
totalBillingAmountUsd: string
}
cache: {
todayHitRate: string
todayHitRateValue: number | null
yesterdayHitRateValue: number | null
totalHitRate: string
totalCachedTokens: string
averageFirstTokenLatencyMs: string
}
}
export interface DashboardHealthTimelinePoint {
time: string
status: DashboardHealthStatus
reliabilityDisplay: string
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
}
export interface DashboardHealthTimeline {
title: string
description: string
reliabilityDisplay: string
status: DashboardHealthStatus
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
points: DashboardHealthTimelinePoint[]
}
export interface DashboardWireProfile {
provider: string
product: string
version: string
build?: string
target: {
osType: string
osVersion: string
arch: string
terminal: string
}
userAgent: string
attributes: Array<{ label: string, value: string }>
verifiedAt?: string
release?: {
status: 'unchecked' | 'aligned' | 'review_required' | 'check_failed'
checkedAt?: string
latestVersion?: string
latestBuild?: string
publishedAt?: string
minimumSystemVersion?: string
hardwareRequirements?: string
downloadUrl?: string
downloadSize?: number
signaturePresent?: boolean
error?: string
}
}
export interface DashboardAccountRequestBucket {
bucketStart: string
requestCount: number
}
export interface DashboardAccountUsage {
id: string
provider: string
authenticationKind: string
email: string
planType: string | null
tokens: string
requestCount: number
requestBuckets: DashboardAccountRequestBucket[]
quotaUsedPercent: number | null
lastUsed: string
}
export interface DashboardCredentialUsage {
id: string
displayName: string
planType: string | null
tokens: string
tokensValue: number | null
lastUsed: string
provider: string
availability: string
requestCount: number
}
export interface DashboardPoolSummary {
total: number
active: number
expired: number
quotaExhausted: number
refreshing: number | null
disabled: number
banned: number
}
export interface DashboardCapacityInfo {
maxConcurrentPerAccount: number
totalSlots: number
usedSlots: number | null
availableSlots: number | null
}
export interface DashboardRequestMetrics {
requestCount: number
successCount: number
failureCount: number
cancelledCount: number
incompleteCount: number
callerErrorCount: number
inputTokens: number
outputTokens: number
cachedTokens: number
cacheWriteTokens: number
reasoningTokens: number
totalTokens: number
}
export interface DashboardSummaryResponse {
cards: DashboardCards
trend: DashboardTrendResponse
healthTimeline: DashboardHealthTimeline
wireProfiles: DashboardWireProfile[]
accountUsage: DashboardAccountUsage[]
credentialUsage: DashboardCredentialUsage[]
usageRecords: UsageRecord[]
poolSummary: DashboardPoolSummary
capacityInfo: DashboardCapacityInfo
rotationStrategy: string
logicalRequests: DashboardRequestMetrics
}
export function getDashboardSummary(data: object) {
return request({
return request<DashboardSummaryResponse>({
url: '/api/admin/dashboard/summary',
method: 'GET',
params: data,
@@ -9,7 +216,7 @@ export function getDashboardSummary(data: object) {
}
export function getDashboardTrend(data: object) {
return request({
return request<DashboardTrendResponse>({
url: '/api/admin/dashboard/trend',
method: 'GET',
params: data,
+33 -5
View File
@@ -1,14 +1,42 @@
import request from '../request'
export type RotationStrategy = 'smart' | 'quota_reset_priority' | 'round_robin' | 'sticky'
export interface RuntimeSettings {
configRevision: number
modelMappings: Record<string, string>
refreshMarginSeconds: number
refreshConcurrency: number
maxConcurrentPerAccount: number
requestIntervalMs: number
rotationStrategy: RotationStrategy
usageRetentionDays: number
opsEventRetentionDays: number
auditRetentionDays: number
updatedAt: string
}
export interface AdminApiKeyStatus {
exists: boolean
}
export interface RegeneratedAdminApiKey {
key: string
}
export interface DeletedAdminApiKey {
message: string
}
export function getSettings() {
return request({
return request<RuntimeSettings>({
url: '/api/admin/settings',
method: 'GET',
})
}
export function updateSettings(data: object) {
return request({
return request<RuntimeSettings>({
url: '/api/admin/settings/update',
method: 'POST',
data,
@@ -16,21 +44,21 @@ export function updateSettings(data: object) {
}
export function getAdminApiKeyStatus() {
return request({
return request<AdminApiKeyStatus>({
url: '/api/admin/settings/admin-api-key',
method: 'GET',
})
}
export function regenerateAdminApiKey() {
return request({
return request<RegeneratedAdminApiKey>({
url: '/api/admin/settings/admin-api-key/regenerate',
method: 'POST',
})
}
export function deleteAdminApiKey() {
return request({
return request<DeletedAdminApiKey>({
url: '/api/admin/settings/admin-api-key/delete',
method: 'POST',
})
+46 -4
View File
@@ -1,7 +1,49 @@
import request from '../request'
export interface SystemVersion {
version: string
gitSha: string
buildTime: string
deploymentMode: string
deploymentModeLabel: string
updateChannel: string
latestVersion: string
hasUpdate: boolean
updateCached: boolean
updateWarning: string | null
}
export interface SystemUpdateDetail {
currentVersion: string
latestVersion: string
hasUpdate: boolean
deploymentMode: string
deploymentModeLabel: string
buildType: string
buildTypeLabel: string
releaseUrl: string | null
notes: string | null
cached: boolean
updateSupported: boolean
unsupportedReason: string | null
warning: string | null
}
export interface SystemUpdateAccepted {
operationId: string
deploymentMode: string
message: string
needRestart: boolean
targetVersion: string
}
export interface SystemRestartAccepted {
message: string
operationId: string
}
export function getSystemVersion(timeout = 0) {
return request({
return request<SystemVersion>({
url: '/api/admin/system/version',
method: 'GET',
...(timeout ? { timeout } : {}),
@@ -9,7 +51,7 @@ export function getSystemVersion(timeout = 0) {
}
export function getSystemUpdateDetail(data: object) {
return request({
return request<SystemUpdateDetail>({
url: '/api/admin/system/update/detail',
method: 'GET',
params: data,
@@ -17,7 +59,7 @@ export function getSystemUpdateDetail(data: object) {
}
export function performSystemUpdate(data: object) {
return request({
return request<SystemUpdateAccepted>({
url: '/api/admin/system/update',
method: 'POST',
data,
@@ -25,7 +67,7 @@ export function performSystemUpdate(data: object) {
}
export function restartSystem() {
return request({
return request<SystemRestartAccepted>({
url: '/api/admin/system/restart',
method: 'POST',
})
+330 -6
View File
@@ -1,7 +1,331 @@
import request from '../request'
export interface UsagePageMeta {
page: number
pageSize: number
total: number
totalPages: number
}
export interface UsageTokenDetails {
inputTokens: number | null
outputTokens: number | null
cachedTokens: number | null
cacheWriteTokens: number | null
reasoningTokens: number | null
imageInputTokens: number | null
imageOutputTokens: number | null
totalTokens: number | null
inputTokensDisplay: string
outputTokensDisplay: string
cachedTokensDisplay: string
cacheWriteTokensDisplay: string
reasoningTokensDisplay: string
imageInputTokensDisplay: string
imageOutputTokensDisplay: string
totalTokensDisplay: string
}
export interface UsageCost {
currency: string
estimatedAmount: string
}
export interface UsageCostCoverage {
known: number
partial: number
unknown: number
notBillable: number
}
export interface UsageBilling {
inputAmountDisplay: string
outputAmountDisplay: string
cacheReadAmountDisplay: string
cacheWriteAmountDisplay: string
standardAmountDisplay: string
totalAmountDisplay: string
inputPriceDisplay: string
outputPriceDisplay: string
cacheReadPriceDisplay: string
cacheWritePriceDisplay: string
serviceTierDisplay: string
multiplierDisplay: string
}
export interface UsageLatencyDetails {
transportDecisionWaitMs?: number
wsConnectMs?: number
upstreamHeadersMs?: number
firstEventMs?: number
firstReasoningMs?: number
firstTextMs?: number
firstTokenMs?: number
openaiProcessingMs?: number
}
// 已知字段之外还会平铺 Provider 安全观测字段,因此保留索引签名。
export interface UsageRecordMetadata {
[key: string]: unknown
protocol: string
logicalOutcome: string
attemptCount: number
requestedModel: string
upstreamModel?: string
clientIp?: string
userAgent?: string
reasoningEffort?: string
reasoningPreset?: string
compact: boolean
requestKind?: string
subagentKind?: string
transport?: string
httpVersion?: string
clientStatusCode?: number
upstreamStatusCode?: number
responseId?: string
upstreamRequestId?: string
websocketPool: { kind: string } | null
imageGenerationRequested: boolean
imageGenerationSucceeded: boolean | null
latencyDetails: UsageLatencyDetails
}
export interface UsageRecord {
id: string
requestId: string
clientApiKeyId: string | null
kind: string
provider: string | null
authenticationKind: string | null
accountId: string | null
accountEmail: string | null
route: string
model: string
requestedModel: string | null
upstreamModel: string | null
serviceTier: string | null
statusCode: number | null
transport: string | null
attemptIndex: number | null
attemptCount: number
responseId: string | null
upstreamRequestId: string | null
latencyMs: number | null
firstTokenMs: number | null
inputTokens: number | null
outputTokens: number | null
cachedTokens: number | null
cacheWriteTokens: number | null
reasoningTokens: number | null
imageInputTokens: number | null
imageOutputTokens: number | null
message: string
metadata: UsageRecordMetadata
createdAt: string
createdAtDisplay: string
clientIp: string | null
userAgent: string | null
reasoningEffort: string | null
reasoningPreset: string | null
compact: boolean | null
requestKind: string | null
subagentKind: string | null
tokenDetails: UsageTokenDetails
billing: UsageBilling | null
costs: UsageCost[]
costCoverage: UsageCostCoverage
firstTokenLatencyMs: number | null
firstTokenLatencyMsDisplay: string
latencyMsDisplay: string
logicalOutcome: string
}
export interface UsageAttempt {
id: string
attemptIndex: number
trigger: string
provider: string
model: string
transport: string
sendState: string
outcome: string
downstreamCommitted: boolean
statusCode: number | null
providerErrorCode: string | null
failureClass: string | null
costEstimateStatus: string
estimatedCostAmount: string | null
estimatedCostCurrency: string | null
inputTokens: number | null
outputTokens: number | null
cachedTokens: number | null
totalTokens: number | null
firstTokenMs: number | null
latencyMs: number | null
credentialName: string | null
accountEmail: string | null
startedAt: string
completedAt: string | null
}
export type UsageRecordDetail = UsageRecord & {
attempts: UsageAttempt[]
}
export interface UsageRecordsResponse {
items: UsageRecord[]
page: UsagePageMeta
nextCursor: string | null
}
export interface OpsErrorMetadata {
source: string
component: string
attemptId: string | null
accountLabel: string | null
}
export interface OpsError {
id: string
requestId: string | null
clientApiKeyId: string | null
kind: string
provider: string | null
accountId: string | null
route: string
model: string | null
clientStatusCode: number | null
upstreamStatusCode: number | null
transport: string | null
attemptIndex: number | null
failureClass: string
responseId: string | null
upstreamRequestId: string | null
latencyMs: number | null
message: string
metadata: OpsErrorMetadata
createdAt: string
createdAtDisplay: string
}
export interface OpsErrorsResponse {
items: OpsError[]
page: UsagePageMeta
nextCursor: string | null
}
export interface UsageSummaryResponse {
totalRequests: string
inputTokens: string
outputTokens: string
cachedTokens: string
cacheWriteTokens: string
totalTokens: string
averageLatencyMs: string
}
export interface UsageOverviewHealthPoint {
bucket: string
label: string
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
errorRate: number
}
export interface UsageOverviewHealth {
totalRequests: number
successRequests: number
failedRequests: number
cancelledRequests: number
incompleteRequests: number
callerErrorRequests: number
successRate: number
requestChangeRate: number | null
successRateChange: number | null
points: UsageOverviewHealthPoint[]
}
export interface UsageOverviewPerformancePoint {
bucket: string
label: string
latencyP50Ms: number | null
latencyP95Ms: number | null
latencyP99Ms: number | null
firstTokenP50Ms: number | null
firstTokenP95Ms: number | null
firstTokenP99Ms: number | null
}
export interface UsageOverviewPerformance {
latencyP50Ms: number | null
latencyP95Ms: number | null
latencyP99Ms: number | null
firstTokenP50Ms: number | null
firstTokenP95Ms: number | null
firstTokenP99Ms: number | null
latencyCoverage: number
firstTokenCoverage: number
points: UsageOverviewPerformancePoint[]
}
export interface UsageOverviewCostPoint {
bucket: string
label: string
inputTokens: number
outputTokens: number
cachedTokens: number
totalTokens: number
estimatedCost: string | null
standardCost: string | null
cachedTokenRate: number
cacheHitRequestRate: number | null
}
export interface UsageOverviewCost {
estimatedCost: string | null
standardCost: string | null
costPerRequest: string | null
tokensPerRequest: number
cachedTokenRate: number
cacheHitRequestRate: number | null
inputTokens: number
outputTokens: number
cachedTokens: number
totalTokens: number
points: UsageOverviewCostPoint[]
}
export interface UsageInsightsOverviewResponse {
granularity: string
health: UsageOverviewHealth
performance: UsageOverviewPerformance
cost: UsageOverviewCost
}
export interface UsageDiagnosticItem {
name: string
requestCount: number
successCount: number
errorCount: number
errorRate: number
requestShare: number
averageLatencyMs: number | null
estimatedCost: string | null
attemptCount: number
totalTokens: number
}
export interface UsageDiagnosticsResponse {
dimension: string
items: UsageDiagnosticItem[]
}
export function getUsageRecords(data: object) {
return request({
return request<UsageRecordsResponse>({
url: '/api/admin/usage/records',
method: 'GET',
params: data,
@@ -9,7 +333,7 @@ export function getUsageRecords(data: object) {
}
export function getOpsErrors(data: object) {
return request({
return request<OpsErrorsResponse>({
url: '/api/admin/operations/errors',
method: 'GET',
params: data,
@@ -17,7 +341,7 @@ export function getOpsErrors(data: object) {
}
export function getUsageRecordDetail(data: object) {
return request({
return request<UsageRecordDetail>({
url: '/api/admin/usage/records/detail',
method: 'GET',
params: data,
@@ -25,7 +349,7 @@ export function getUsageRecordDetail(data: object) {
}
export function getUsageRecordSummary(data: object) {
return request({
return request<UsageSummaryResponse>({
url: '/api/admin/usage/records/summary',
method: 'GET',
params: data,
@@ -33,7 +357,7 @@ export function getUsageRecordSummary(data: object) {
}
export function getUsageRecordInsightsOverview(data: object) {
return request({
return request<UsageInsightsOverviewResponse>({
url: '/api/admin/usage/insights/overview',
method: 'GET',
params: data,
@@ -41,7 +365,7 @@ export function getUsageRecordInsightsOverview(data: object) {
}
export function getUsageRecordInsightsDiagnostics(data: object) {
return request({
return request<UsageDiagnosticsResponse>({
url: '/api/admin/usage/insights/diagnostics',
method: 'GET',
params: data,
+11 -5
View File
@@ -70,7 +70,13 @@ http.interceptors.response.use(
},
)
function isApiEnvelope(value: any) {
interface ApiEnvelope {
code: number
message: string
data: unknown
}
function isApiEnvelope(value: unknown): value is ApiEnvelope {
return (
typeof value === 'object'
&& value !== null
@@ -80,14 +86,14 @@ function isApiEnvelope(value: any) {
)
}
export default async function request(config: AxiosRequestConfig) {
const response = await http.request({
export default async function request<T = unknown>(config: AxiosRequestConfig): Promise<T> {
const response = await http.request<unknown>({
...config,
})
if (isApiEnvelope(response.data)) {
return response.data.data
return response.data.data as T
}
return response.data
return response.data as T
}
@@ -1,6 +1,7 @@
import { clamp, sumBy } from 'es-toolkit'
export type TableRow = Record<string, unknown>
// 行类型保持结构化约束;动态列 key 的取值统一走 cellValue。
export type TableRow = object
export interface BaseTableColumn<Row extends TableRow = TableRow> {
key: string
@@ -111,7 +112,7 @@ export function alignClass<Row extends TableRow>(column: BaseTableColumn<Row>) {
}
export function cellValue(row: TableRow, key: string) {
return row[key]
return (row as Record<string, unknown>)[key]
}
function isEmptyCellValue(value: unknown) {
@@ -1,4 +1,4 @@
<script setup lang="ts" generic="Row extends Record<string, unknown> = Record<string, unknown>">
<script setup lang="ts" generic="Row extends object = Record<string, unknown>">
import type { BaseTableColumn, BaseTableSort, ResolvedTableColumn } from './columns'
import type { BaseTablePagination as BaseTablePaginationConfig } from './pagination'
@@ -149,7 +149,7 @@ function getRowKey(row: Row, index: number) {
return props.rowKey(row, index)
}
const value = row[props.rowKey]
const value = cellValue(row, props.rowKey)
return typeof value === 'string' || typeof value === 'number' ? value : index
}
@@ -167,7 +167,7 @@ const requestBars = computed(() => {
:aria-label="window.labelDisplay"
aria-valuemin="0"
aria-valuemax="100"
:aria-valuenow="window.usedPercent"
:aria-valuenow="window.usedPercent ?? undefined"
>
<div
class="h-full rounded-full transition-[width,background-color] duration-200"
@@ -7,7 +7,8 @@ import { toast } from '@/components/base/BaseToast'
import { errorMessage } from '@/utils/async'
import { buildCodexCcSwitchImportDeeplink } from '../utils/ccswitchImport'
type ApiKeyRow = Awaited<ReturnType<typeof getApiKeys>>['items'][number]
// “使用密钥”弹窗展示明文时,在列表行上补挂 reveal 得到的完整 key。
type ApiKeyRow = Awaited<ReturnType<typeof getApiKeys>>['items'][number] & { key?: string }
// 密钥使用与 CCSwitch 导入编排:服务根地址推导、deeplink 跳转、
// “使用密钥”弹窗的明文补全与打开。
@@ -14,7 +14,10 @@ export function useApiKeysQuery() {
const pageSize = shallowRef(20)
const total = shallowRef(0)
const apiKeys = shallowRef<
Array<Awaited<ReturnType<typeof getApiKeys>>['items'][number]>
Array<Awaited<ReturnType<typeof getApiKeys>>['items'][number] & {
createdAtDisplay: string
lastUsedAtDisplay: string
}>
>([])
const loading = shallowRef(false)
const configRevision = shallowRef(0)
@@ -140,7 +140,7 @@ const statusBars = computed(() => {
if (!p || p.total === 0)
return []
const active = (p.active / p.total) * 100
const refreshing = (p.refreshing / p.total) * 100
const refreshing = ((p.refreshing ?? 0) / p.total) * 100
const quota = (p.quotaExhausted / p.total) * 100
const unavailable = ((p.expired + p.disabled + p.banned) / p.total) * 100
return [
@@ -82,7 +82,13 @@ const sortedItems = computed(() =>
),
)
const displayItems = computed(() =>
// P95 列消费的 latencyP95Ms 目前不在诊断聚合响应中,缺失时渲染占位符。
type DiagnosticDisplayItem = Diagnostics['items'][number] & {
nameDisplay: ReturnType<typeof diagnosticNameDisplay>
latencyP95Ms?: number
}
const displayItems = computed<DiagnosticDisplayItem[]>(() =>
sortedItems.value.map(item => ({
...item,
nameDisplay: diagnosticNameDisplay(item.name),