From 2366f66f33c21db8214fcf13be4f545a2283ff94 Mon Sep 17 00:00:00 2001 From: zyycn Date: Mon, 27 Jul 2026 01:52:00 +0800 Subject: [PATCH] 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 --- frontend/src/api/modules/accounts.ts | 205 ++++++++++- frontend/src/api/modules/api-keys.ts | 50 ++- frontend/src/api/modules/auth.ts | 18 +- frontend/src/api/modules/dashboard.ts | 211 ++++++++++- frontend/src/api/modules/settings.ts | 38 +- frontend/src/api/modules/system.ts | 50 ++- frontend/src/api/modules/usage.ts | 336 +++++++++++++++++- frontend/src/api/request.ts | 16 +- .../src/components/base/BaseTable/columns.ts | 5 +- .../src/components/base/BaseTable/index.vue | 4 +- .../components/AccountUsageWindow.vue | 2 +- .../api-keys/composables/useApiKeyUse.ts | 3 +- .../api-keys/composables/useApiKeysQuery.ts | 5 +- .../components/AccountOverviewCard.vue | 2 +- .../usage/components/UsageDiagnosticCard.vue | 8 +- 15 files changed, 900 insertions(+), 53 deletions(-) diff --git a/frontend/src/api/modules/accounts.ts b/frontend/src/api/modules/accounts.ts index 316b985ff..bcaa5bbd8 100644 --- a/frontend/src/api/modules/accounts.ts +++ b/frontend/src/api/modules/accounts.ts @@ -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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ 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({ url: '/api/admin/accounts/oauth/complete', method: 'POST', data, diff --git a/frontend/src/api/modules/api-keys.ts b/frontend/src/api/modules/api-keys.ts index 560a21846..0181204c0 100644 --- a/frontend/src/api/modules/api-keys.ts +++ b/frontend/src/api/modules/api-keys.ts @@ -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({ 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({ 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({ 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({ 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({ 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({ url: '/api/admin/client-keys/enable', method: 'POST', data, diff --git a/frontend/src/api/modules/auth.ts b/frontend/src/api/modules/auth.ts index 47df44660..b1d8a6d06 100644 --- a/frontend/src/api/modules/auth.ts +++ b/frontend/src/api/modules/auth.ts @@ -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({ url: '/api/admin/auth/login', method: 'POST', data, @@ -9,14 +21,14 @@ export function login(data: object) { } export function getAuthStatus() { - return request({ + return request({ url: '/api/admin/auth/status', method: 'GET', }) } export function logout() { - return request({ + return request({ url: '/api/admin/auth/logout', method: 'POST', }) diff --git a/frontend/src/api/modules/dashboard.ts b/frontend/src/api/modules/dashboard.ts index bdda1b8c8..cf8ddfc3b 100644 --- a/frontend/src/api/modules/dashboard.ts +++ b/frontend/src/api/modules/dashboard.ts @@ -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({ 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({ url: '/api/admin/dashboard/trend', method: 'GET', params: data, diff --git a/frontend/src/api/modules/settings.ts b/frontend/src/api/modules/settings.ts index 7d8c631cb..1b3e673de 100644 --- a/frontend/src/api/modules/settings.ts +++ b/frontend/src/api/modules/settings.ts @@ -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 + 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({ url: '/api/admin/settings', method: 'GET', }) } export function updateSettings(data: object) { - return request({ + return request({ url: '/api/admin/settings/update', method: 'POST', data, @@ -16,21 +44,21 @@ export function updateSettings(data: object) { } export function getAdminApiKeyStatus() { - return request({ + return request({ url: '/api/admin/settings/admin-api-key', method: 'GET', }) } export function regenerateAdminApiKey() { - return request({ + return request({ url: '/api/admin/settings/admin-api-key/regenerate', method: 'POST', }) } export function deleteAdminApiKey() { - return request({ + return request({ url: '/api/admin/settings/admin-api-key/delete', method: 'POST', }) diff --git a/frontend/src/api/modules/system.ts b/frontend/src/api/modules/system.ts index 262134778..21e0ebb51 100644 --- a/frontend/src/api/modules/system.ts +++ b/frontend/src/api/modules/system.ts @@ -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({ 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({ 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({ url: '/api/admin/system/update', method: 'POST', data, @@ -25,7 +67,7 @@ export function performSystemUpdate(data: object) { } export function restartSystem() { - return request({ + return request({ url: '/api/admin/system/restart', method: 'POST', }) diff --git a/frontend/src/api/modules/usage.ts b/frontend/src/api/modules/usage.ts index 7ca0e0819..cf9c28f88 100644 --- a/frontend/src/api/modules/usage.ts +++ b/frontend/src/api/modules/usage.ts @@ -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({ 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({ 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({ 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({ 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({ 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({ url: '/api/admin/usage/insights/diagnostics', method: 'GET', params: data, diff --git a/frontend/src/api/request.ts b/frontend/src/api/request.ts index f5ed72ae2..eff1b87b5 100644 --- a/frontend/src/api/request.ts +++ b/frontend/src/api/request.ts @@ -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(config: AxiosRequestConfig): Promise { + const response = await http.request({ ...config, }) if (isApiEnvelope(response.data)) { - return response.data.data + return response.data.data as T } - return response.data + return response.data as T } diff --git a/frontend/src/components/base/BaseTable/columns.ts b/frontend/src/components/base/BaseTable/columns.ts index cdf84104d..8e9f2dd8f 100644 --- a/frontend/src/components/base/BaseTable/columns.ts +++ b/frontend/src/components/base/BaseTable/columns.ts @@ -1,6 +1,7 @@ import { clamp, sumBy } from 'es-toolkit' -export type TableRow = Record +// 行类型保持结构化约束;动态列 key 的取值统一走 cellValue。 +export type TableRow = object export interface BaseTableColumn { key: string @@ -111,7 +112,7 @@ export function alignClass(column: BaseTableColumn) { } export function cellValue(row: TableRow, key: string) { - return row[key] + return (row as Record)[key] } function isEmptyCellValue(value: unknown) { diff --git a/frontend/src/components/base/BaseTable/index.vue b/frontend/src/components/base/BaseTable/index.vue index cfa2518de..913309ae9 100644 --- a/frontend/src/components/base/BaseTable/index.vue +++ b/frontend/src/components/base/BaseTable/index.vue @@ -1,4 +1,4 @@ -