feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.

This commit is contained in:
2026-01-30 07:40:35 +08:00
commit f4448bbef2
106 changed files with 19282 additions and 0 deletions

View File

@@ -0,0 +1,294 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { ChatGPTClient, getStatusText, getStatusColor, createChatGPTClient } from './chatgpt'
import type { AccountInput } from '../types'
describe('ChatGPTClient', () => {
let client: ChatGPTClient
let fetchMock: ReturnType<typeof vi.fn>
beforeEach(() => {
client = new ChatGPTClient()
fetchMock = vi.fn()
globalThis.fetch = fetchMock as typeof fetch
})
afterEach(() => {
vi.restoreAllMocks()
})
describe('checkAccount', () => {
it('should return active status for HTTP 200 with account info', async () => {
const mockResponse = {
accounts: [
{
account_id: 'test-account-id',
entitlement: {
subscription_plan: 'plus',
},
},
],
}
fetchMock.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.resolve(mockResponse),
})
const result = await client.checkAccount('valid-token')
expect(result.status).toBe('active')
expect(result.accountId).toBe('test-account-id')
expect(result.planType).toBe('plus')
expect(result.error).toBeUndefined()
})
it('should return active status for HTTP 200 without account info', async () => {
const mockResponse = {
accounts: [],
}
fetchMock.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.resolve(mockResponse),
})
const result = await client.checkAccount('valid-token')
expect(result.status).toBe('active')
expect(result.accountId).toBeUndefined()
expect(result.planType).toBe('unknown')
})
it('should return token_expired status for HTTP 401', async () => {
fetchMock.mockResolvedValueOnce({
status: 401,
statusText: 'Unauthorized',
json: () => Promise.reject(new Error('No JSON')),
})
const result = await client.checkAccount('expired-token')
expect(result.status).toBe('token_expired')
expect(result.error).toBe('Token 已过期')
})
it('should return banned status for HTTP 403', async () => {
fetchMock.mockResolvedValueOnce({
status: 403,
statusText: 'Forbidden',
json: () => Promise.reject(new Error('No JSON')),
})
const result = await client.checkAccount('banned-token')
expect(result.status).toBe('banned')
expect(result.error).toBe('账号已被封禁')
})
it('should return error status for other HTTP codes', async () => {
fetchMock.mockResolvedValueOnce({
status: 500,
statusText: 'Internal Server Error',
json: () => Promise.reject(new Error('No JSON')),
})
const result = await client.checkAccount('some-token')
expect(result.status).toBe('error')
expect(result.error).toBe('HTTP 500: Internal Server Error')
})
it('should return error status for network errors', async () => {
fetchMock.mockRejectedValueOnce(new Error('Network error'))
const result = await client.checkAccount('some-token')
expect(result.status).toBe('error')
expect(result.error).toBe('Network error')
})
it('should return error status for empty token', async () => {
const result = await client.checkAccount('')
expect(result.status).toBe('error')
expect(result.error).toBe('缺少 token')
expect(fetchMock).not.toHaveBeenCalled()
})
it('should return error status for whitespace-only token', async () => {
const result = await client.checkAccount(' ')
expect(result.status).toBe('error')
expect(result.error).toBe('缺少 token')
expect(fetchMock).not.toHaveBeenCalled()
})
it('should use correct API endpoint and headers', async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.resolve({ accounts: [] }),
})
await client.checkAccount('test-token')
expect(fetchMock).toHaveBeenCalledWith('/api/chatgpt/accounts/check/v4-2023-04-27', {
method: 'GET',
headers: {
Authorization: 'Bearer test-token',
'Content-Type': 'application/json',
},
})
})
it('should handle JSON parse errors gracefully for HTTP 200', async () => {
fetchMock.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.reject(new Error('Invalid JSON')),
})
const result = await client.checkAccount('valid-token')
// Should still return active since HTTP 200
expect(result.status).toBe('active')
expect(result.planType).toBe('unknown')
})
})
describe('batchCheck', () => {
it('should return empty array for empty input', async () => {
const results = await client.batchCheck([], { concurrency: 5 })
expect(results).toEqual([])
expect(fetchMock).not.toHaveBeenCalled()
})
it('should check all accounts and return results in order', async () => {
const accounts: AccountInput[] = [
{ account: 'user1@test.com', password: 'pass1', token: 'token1' },
{ account: 'user2@test.com', password: 'pass2', token: 'token2' },
{ account: 'user3@test.com', password: 'pass3', token: 'token3' },
]
// Mock responses for each account
fetchMock
.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () =>
Promise.resolve({
accounts: [{ account_id: 'id1', entitlement: { subscription_plan: 'plus' } }],
}),
})
.mockResolvedValueOnce({
status: 401,
statusText: 'Unauthorized',
})
.mockResolvedValueOnce({
status: 403,
statusText: 'Forbidden',
})
const results = await client.batchCheck(accounts, { concurrency: 3 })
expect(results).toHaveLength(3)
expect(results[0].status).toBe('active')
expect(results[0].accountId).toBe('id1')
expect(results[1].status).toBe('token_expired')
expect(results[2].status).toBe('banned')
})
it('should call onProgress callback for each account', async () => {
const accounts: AccountInput[] = [
{ account: 'user1@test.com', password: 'pass1', token: 'token1' },
{ account: 'user2@test.com', password: 'pass2', token: 'token2' },
]
fetchMock
.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.resolve({ accounts: [] }),
})
.mockResolvedValueOnce({
status: 200,
statusText: 'OK',
json: () => Promise.resolve({ accounts: [] }),
})
const onProgress = vi.fn()
await client.batchCheck(accounts, { concurrency: 2, onProgress })
expect(onProgress).toHaveBeenCalledTimes(2)
})
it('should respect concurrency limit', async () => {
const accounts: AccountInput[] = Array.from({ length: 10 }, (_, i) => ({
account: `user${i}@test.com`,
password: `pass${i}`,
token: `token${i}`,
}))
let maxConcurrent = 0
let currentConcurrent = 0
fetchMock.mockImplementation(async () => {
currentConcurrent++
maxConcurrent = Math.max(maxConcurrent, currentConcurrent)
// Simulate network delay
await new Promise((resolve) => setTimeout(resolve, 10))
currentConcurrent--
return {
status: 200,
statusText: 'OK',
json: () => Promise.resolve({ accounts: [] }),
}
})
await client.batchCheck(accounts, { concurrency: 3 })
// Max concurrent should not exceed the concurrency limit
expect(maxConcurrent).toBeLessThanOrEqual(3)
})
})
})
describe('getStatusText', () => {
it('should return correct Chinese text for each status', () => {
expect(getStatusText('pending')).toBe('待检查')
expect(getStatusText('checking')).toBe('检查中')
expect(getStatusText('active')).toBe('正常')
expect(getStatusText('banned')).toBe('封禁')
expect(getStatusText('token_expired')).toBe('过期')
expect(getStatusText('error')).toBe('错误')
})
})
describe('getStatusColor', () => {
it('should return correct color for each status', () => {
expect(getStatusColor('pending')).toBe('gray')
expect(getStatusColor('checking')).toBe('blue')
expect(getStatusColor('active')).toBe('green')
expect(getStatusColor('banned')).toBe('red')
expect(getStatusColor('token_expired')).toBe('orange')
expect(getStatusColor('error')).toBe('yellow')
})
})
describe('createChatGPTClient', () => {
it('should create a new ChatGPTClient instance', () => {
const client = createChatGPTClient()
expect(client).toBeInstanceOf(ChatGPTClient)
})
it('should create a client with custom base URL', () => {
const client = createChatGPTClient('https://custom.api.com')
expect(client).toBeInstanceOf(ChatGPTClient)
})
})

253
frontend/src/api/chatgpt.ts Normal file
View File

@@ -0,0 +1,253 @@
import type { AccountInput, AccountStatus, CheckedAccount, CheckResult } from '../types'
import type { ChatGPTCheckResponse } from './types'
/**
* ChatGPT API 检查端点
* 通过 nginx 代理访问,避免 CORS 问题
* 原始 API: https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27
*/
const CHATGPT_CHECK_API = '/api/chatgpt/accounts/check/v4-2023-04-27'
/**
* HTTP 状态码到账号状态的映射
* 根据 requirements.md A3 定义:
* - HTTP 200 → active (账号正常)
* - HTTP 401 → token_expired (Token 已过期)
* - HTTP 403 → banned (账号被封禁)
* - 其他 → error (网络错误等)
*/
function mapHttpStatusToAccountStatus(httpStatus: number): AccountStatus {
switch (httpStatus) {
case 200:
return 'active'
case 401:
return 'token_expired'
case 403:
return 'banned'
default:
return 'error'
}
}
/**
* 获取 HTTP 状态码对应的错误消息
*/
function getErrorMessageForStatus(httpStatus: number, statusText: string): string | undefined {
switch (httpStatus) {
case 200:
return undefined
case 401:
return 'Token 已过期'
case 403:
return '账号已被封禁'
default:
return `HTTP ${httpStatus}: ${statusText}`
}
}
/**
* ChatGPT API 客户端
* 用于检查 ChatGPT 账号状态
*/
export class ChatGPTClient {
private baseUrl: string
constructor(baseUrl: string = '') {
this.baseUrl = baseUrl
}
/**
* 检查单个账号状态
* @param token - ChatGPT access_token
* @returns CheckResult 包含状态、account_id、plan_type 等信息
*
* API: GET https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27
* Headers: Authorization: Bearer {token}
*
* 状态映射 (requirements.md A3):
* - HTTP 200 → active
* - HTTP 401 → token_expired
* - HTTP 403 → banned
* - 其他 → error
*/
async checkAccount(token: string): Promise<CheckResult> {
// 处理空 token 的情况
if (!token || token.trim() === '') {
return {
status: 'error',
error: '缺少 token',
}
}
try {
const response = await fetch(`${this.baseUrl}${CHATGPT_CHECK_API}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
})
const status = mapHttpStatusToAccountStatus(response.status)
const errorMessage = getErrorMessageForStatus(response.status, response.statusText)
if (response.status === 200) {
try {
const data: ChatGPTCheckResponse = await response.json()
const accountInfo = data.accounts?.[0]
if (accountInfo) {
return {
status: 'active',
accountId: accountInfo.account_id,
planType: accountInfo.entitlement?.subscription_plan || 'free',
}
}
// 200 响应但没有账号信息
return {
status: 'active',
accountId: undefined,
planType: 'unknown',
}
} catch {
// JSON 解析失败,但 HTTP 200 仍视为 active
return {
status: 'active',
accountId: undefined,
planType: 'unknown',
}
}
}
return {
status,
error: errorMessage,
}
} catch (error) {
return {
status: 'error',
error: error instanceof Error ? error.message : '网络错误',
}
}
}
/**
* 批量检查账号(带并发控制)
* @param accounts - 待检查的账号列表
* @param options.concurrency - 并发数量(默认 20
* @param options.onProgress - 进度回调,每检查完一个账号调用一次
* @returns 检查完成的账号列表
*
* 使用队列 + Promise 实现并发控制,确保任意时刻活跃请求数 ≤ concurrency
*/
async batchCheck(
accounts: AccountInput[],
options: {
concurrency: number
onProgress?: (result: CheckedAccount, index: number) => void
}
): Promise<CheckedAccount[]> {
const { concurrency, onProgress } = options
// 空数组直接返回
if (accounts.length === 0) {
return []
}
const results: CheckedAccount[] = new Array(accounts.length)
const queue: number[] = [...Array(accounts.length).keys()]
let activeCount = 0
let completedCount = 0
return new Promise((resolve) => {
const processNext = () => {
// 所有任务完成
if (completedCount === accounts.length) {
resolve(results)
return
}
// 启动新任务,直到达到并发限制或队列为空
while (activeCount < concurrency && queue.length > 0) {
const index = queue.shift()!
activeCount++
// 异步处理单个账号
this.processAccount(accounts[index], index)
.then((checkedAccount) => {
results[index] = checkedAccount
onProgress?.(checkedAccount, index)
})
.finally(() => {
activeCount--
completedCount++
// 继续处理下一个
processNext()
})
}
}
// 开始处理
processNext()
})
}
/**
* 处理单个账号检查
* @private
*/
private async processAccount(account: AccountInput, index: number): Promise<CheckedAccount> {
const checkResult = await this.checkAccount(account.token)
return {
...account,
id: index,
status: checkResult.status,
accountId: checkResult.accountId,
planType: checkResult.planType,
error: checkResult.error,
}
}
}
/**
* 解析账号状态为中文描述
*/
export function getStatusText(status: AccountStatus): string {
const statusMap: Record<AccountStatus, string> = {
pending: '待检查',
checking: '检查中',
active: '正常',
banned: '封禁',
token_expired: '过期',
error: '错误',
}
return statusMap[status] || status
}
/**
* 获取状态对应的颜色类名
*/
export function getStatusColor(status: AccountStatus): string {
const colorMap: Record<AccountStatus, string> = {
pending: 'gray',
checking: 'blue',
active: 'green',
banned: 'red',
token_expired: 'orange',
error: 'yellow',
}
return colorMap[status] || 'gray'
}
/**
* 创建 ChatGPT 客户端实例
* @param baseUrl - 可选的基础 URL默认为空使用相对路径
*/
export function createChatGPTClient(baseUrl: string = ''): ChatGPTClient {
return new ChatGPTClient(baseUrl)
}
// 导出默认客户端实例(使用相对路径,通过 nginx 代理)
export const chatGPTClient = new ChatGPTClient()

View File

@@ -0,0 +1,4 @@
// API Layer barrel export
export * from './types'
export * from './s2a'
export * from './chatgpt'

155
frontend/src/api/s2a.ts Normal file
View File

@@ -0,0 +1,155 @@
import type {
DashboardStatsResponse,
DashboardTrendResponse,
AccountListResponse,
AccountResponse,
CreateAccountPayload,
OAuthCreatePayload,
GroupResponse,
ProxyResponse,
TestAccountResponse,
} from './types'
import type { AccountListParams } from '../types'
// 使用后端代理 API 来避免 CORS 问题
const PROXY_BASE = 'http://localhost:8088/api/s2a/proxy'
export class S2AClient {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(_config: { baseUrl: string; apiKey: string }) {
// 不再使用直接配置,通过后端代理
}
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
// 将 /api/v1/admin/* 转换为代理路径
const proxyEndpoint = endpoint.replace('/api/v1/admin', '')
const url = `${PROXY_BASE}${proxyEndpoint}`
const headers: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
}
const response = await fetch(url, {
...options,
headers,
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
errorData.message || errorData.error || `HTTP ${response.status}: ${response.statusText}`
)
}
return response.json()
}
// Dashboard APIs
async getDashboardStats(): Promise<DashboardStatsResponse> {
return this.request<DashboardStatsResponse>('/dashboard/stats')
}
async getDashboardTrend(granularity: 'day' | 'hour' = 'day'): Promise<DashboardTrendResponse> {
return this.request<DashboardTrendResponse>(
`/dashboard/trend?granularity=${granularity}`
)
}
// Account APIs
async getAccounts(params: AccountListParams = {}): Promise<AccountListResponse> {
const searchParams = new URLSearchParams()
if (params.page) searchParams.set('page', params.page.toString())
if (params.page_size) searchParams.set('page_size', params.page_size.toString())
if (params.platform) searchParams.set('platform', params.platform)
if (params.type) searchParams.set('type', params.type)
if (params.status) searchParams.set('status', params.status)
if (params.search) searchParams.set('search', params.search)
const queryString = searchParams.toString()
const endpoint = `/accounts${queryString ? `?${queryString}` : ''}`
return this.request<AccountListResponse>(endpoint)
}
async getAccount(id: number): Promise<AccountResponse> {
return this.request<AccountResponse>(`/accounts/${id}`)
}
async createAccount(data: CreateAccountPayload): Promise<AccountResponse> {
return this.request<AccountResponse>('/accounts', {
method: 'POST',
body: JSON.stringify(data),
})
}
async createFromOAuth(data: OAuthCreatePayload): Promise<AccountResponse> {
return this.request<AccountResponse>('/openai/create-from-oauth', {
method: 'POST',
body: JSON.stringify(data),
})
}
async updateAccount(id: number, data: Partial<CreateAccountPayload>): Promise<AccountResponse> {
return this.request<AccountResponse>(`/accounts/${id}`, {
method: 'PUT',
body: JSON.stringify(data),
})
}
async deleteAccount(id: number): Promise<void> {
await this.request<void>(`/accounts/${id}`, {
method: 'DELETE',
})
}
async testAccount(id: number): Promise<TestAccountResponse> {
return this.request<TestAccountResponse>(`/accounts/${id}/test`, {
method: 'POST',
})
}
async refreshAccountToken(id: number): Promise<AccountResponse> {
return this.request<AccountResponse>(`/accounts/${id}/refresh`, {
method: 'POST',
})
}
async clearAccountError(id: number): Promise<AccountResponse> {
return this.request<AccountResponse>(`/accounts/${id}/clear-error`, {
method: 'POST',
})
}
// Group APIs
async getGroups(): Promise<GroupResponse[]> {
const response = await this.request<{ data: GroupResponse[] }>('/groups/all')
return response.data || []
}
// Proxy APIs
async getProxies(): Promise<ProxyResponse[]> {
const response = await this.request<{ data: ProxyResponse[] }>('/proxies/all')
return response.data || []
}
async testProxy(id: number): Promise<TestAccountResponse> {
return this.request<TestAccountResponse>(`/proxies/${id}/test`, {
method: 'POST',
})
}
// Connection test
async testConnection(): Promise<boolean> {
try {
await this.getDashboardStats()
return true
} catch {
return false
}
}
}
// 创建默认客户端实例的工厂函数
export function createS2AClient(baseUrl: string, apiKey: string): S2AClient {
return new S2AClient({ baseUrl, apiKey })
}

522
frontend/src/api/types.ts Normal file
View File

@@ -0,0 +1,522 @@
// =============================================================================
// S2A API 响应类型
// 基于 requirements.md Appendix A2 定义
// =============================================================================
// -----------------------------------------------------------------------------
// Dashboard 相关接口响应
// -----------------------------------------------------------------------------
/**
* Dashboard Stats 响应
* API: GET /api/v1/admin/dashboard/stats
* 获取号池统计账号数、请求数、Token消耗等
*/
export interface DashboardStatsResponse {
total_accounts: number
normal_accounts: number
error_accounts: number
ratelimit_accounts: number
overload_accounts: number
today_requests: number
today_tokens: number
today_cost: number
total_requests: number
total_tokens: number
total_cost: number
rpm: number
tpm: number
}
/**
* Dashboard Trend 响应
* API: GET /api/v1/admin/dashboard/trend
* 获取使用趋势(支持 granularity=day/hour
*/
export interface DashboardTrendResponse {
data: TrendDataPoint[]
}
export interface TrendDataPoint {
date: string
requests: number
tokens: number
cost: number
}
/**
* Dashboard Models 响应
* API: GET /api/v1/admin/dashboard/models
* 获取模型使用统计
*/
export interface DashboardModelsResponse {
data: ModelUsageStats[]
}
export interface ModelUsageStats {
model: string
requests: number
tokens: number
cost: number
percentage: number
}
/**
* Dashboard Users Trend 响应
* API: GET /api/v1/admin/dashboard/users-trend
* 获取用户使用趋势
*/
export interface DashboardUsersTrendResponse {
data: UsersTrendDataPoint[]
}
export interface UsersTrendDataPoint {
date: string
active_users: number
new_users: number
total_users: number
}
// -----------------------------------------------------------------------------
// 账号管理接口响应
// -----------------------------------------------------------------------------
/**
* 账号列表响应
* API: GET /api/v1/admin/accounts
* 获取账号列表(支持分页、筛选)
*/
export interface AccountListResponse {
data: AccountResponse[]
total: number
page: number
page_size: number
}
/**
* 单个账号响应
* API: GET /api/v1/admin/accounts/:id
* 对应 requirements.md A5 Account 数据结构
*/
export interface AccountResponse {
id: number
name: string
notes?: string
platform: 'openai' | 'anthropic' | 'gemini'
type: 'oauth' | 'access_token' | 'apikey' | 'setup-token'
credentials: Record<string, unknown>
extra?: Record<string, unknown>
proxy_id?: number
concurrency: number
priority: number
rate_multiplier?: number
status: 'active' | 'inactive' | 'error'
error_message?: string
schedulable: boolean
last_used_at?: string
expires_at?: string
auto_pause_on_expired: boolean
created_at: string
updated_at: string
current_concurrency?: number
current_window_cost?: number
active_sessions?: number
}
/**
* 账号统计响应
* API: GET /api/v1/admin/accounts/:id/stats
* 获取账号使用统计
*/
export interface AccountStatsResponse {
account_id: number
total_requests: number
total_tokens: number
total_cost: number
today_requests: number
today_tokens: number
today_cost: number
last_used_at?: string
error_count: number
success_rate: number
}
/**
* 创建账号请求
* API: POST /api/v1/admin/accounts
* 对应 requirements.md A4 access_token 类型账号
*/
export interface CreateAccountPayload {
name: string
platform: 'openai' | 'anthropic' | 'gemini'
type: 'access_token'
credentials: {
access_token: string
refresh_token?: string
email?: string
}
concurrency?: number
priority?: number
group_ids?: number[]
proxy_id?: number | null
auto_pause_on_expired?: boolean
}
/**
* 批量更新账号请求
* API: POST /api/v1/admin/accounts/bulk-update
*/
export interface BulkUpdateAccountsPayload {
ids: number[]
updates: {
status?: 'active' | 'inactive'
concurrency?: number
priority?: number
group_ids?: number[]
proxy_id?: number | null
}
}
/**
* 批量更新账号响应
*/
export interface BulkUpdateAccountsResponse {
success: boolean
updated_count: number
failed_count: number
errors?: string[]
}
// -----------------------------------------------------------------------------
// OpenAI OAuth 接口响应
// -----------------------------------------------------------------------------
/**
* OAuth 创建账号请求
* API: POST /api/v1/admin/openai/create-from-oauth
* 对应 requirements.md A4 OAuth 类型账号
*/
export interface OAuthCreatePayload {
session_id: string
code: string
name?: string
concurrency?: number
priority?: number
group_ids?: number[]
proxy_id?: number | null
}
/**
* 生成 OAuth 授权 URL 请求
* API: POST /api/v1/admin/openai/generate-auth-url
*/
export interface GenerateAuthUrlPayload {
redirect_uri?: string
}
/**
* 生成 OAuth 授权 URL 响应
*/
export interface GenerateAuthUrlResponse {
auth_url: string
session_id: string
}
/**
* 交换授权码请求
* API: POST /api/v1/admin/openai/exchange-code
*/
export interface ExchangeCodePayload {
session_id: string
code: string
}
/**
* 交换授权码响应
*/
export interface ExchangeCodeResponse {
access_token: string
refresh_token?: string
expires_in?: number
token_type: string
}
/**
* 刷新 Token 请求
* API: POST /api/v1/admin/openai/refresh-token
*/
export interface RefreshTokenPayload {
refresh_token: string
}
/**
* 刷新 Token 响应
*/
export interface RefreshTokenResponse {
access_token: string
refresh_token?: string
expires_in?: number
token_type: string
}
// -----------------------------------------------------------------------------
// 分组管理接口响应
// -----------------------------------------------------------------------------
/**
* 分组响应
* API: GET /api/v1/admin/groups, GET /api/v1/admin/groups/all
*/
export interface GroupResponse {
id: number
name: string
description?: string
created_at: string
updated_at: string
}
/**
* 分组统计响应
* API: GET /api/v1/admin/groups/:id/stats
*/
export interface GroupStatsResponse {
group_id: number
total_accounts: number
active_accounts: number
error_accounts: number
total_requests: number
total_tokens: number
total_cost: number
}
// -----------------------------------------------------------------------------
// 代理管理接口响应
// -----------------------------------------------------------------------------
/**
* 代理响应
* API: GET /api/v1/admin/proxies, GET /api/v1/admin/proxies/all
*/
export interface ProxyResponse {
id: number
name: string
url: string
status: 'active' | 'inactive' | 'error'
created_at: string
updated_at: string
}
/**
* 测试代理/账号响应
* API: POST /api/v1/admin/proxies/:id/test, POST /api/v1/admin/accounts/:id/test
*/
export interface TestAccountResponse {
success: boolean
message?: string
latency?: number
}
// -----------------------------------------------------------------------------
// 运维监控接口响应
// -----------------------------------------------------------------------------
/**
* 并发统计响应
* API: GET /api/v1/admin/ops/concurrency
*/
export interface OpsConcurrencyResponse {
total_concurrency: number
used_concurrency: number
available_concurrency: number
accounts: AccountConcurrencyInfo[]
}
export interface AccountConcurrencyInfo {
account_id: number
account_name: string
max_concurrency: number
current_concurrency: number
}
/**
* 账号可用性响应
* API: GET /api/v1/admin/ops/account-availability
*/
export interface OpsAccountAvailabilityResponse {
total_accounts: number
available_accounts: number
unavailable_accounts: number
availability_rate: number
accounts: AccountAvailabilityInfo[]
}
export interface AccountAvailabilityInfo {
account_id: number
account_name: string
status: 'available' | 'unavailable' | 'rate_limited' | 'error'
reason?: string
}
/**
* 实时流量响应
* API: GET /api/v1/admin/ops/realtime-traffic
*/
export interface OpsRealtimeTrafficResponse {
current_rpm: number
current_tpm: number
peak_rpm: number
peak_tpm: number
requests_last_minute: number
tokens_last_minute: number
}
/**
* 运维仪表盘概览响应
* API: GET /api/v1/admin/ops/dashboard/overview
*/
export interface OpsDashboardOverviewResponse {
health_score: number
total_accounts: number
healthy_accounts: number
warning_accounts: number
error_accounts: number
current_load: number
max_load: number
alerts: OpsAlert[]
}
export interface OpsAlert {
id: string
level: 'info' | 'warning' | 'error' | 'critical'
message: string
timestamp: string
account_id?: number
}
/**
* 错误趋势响应
* API: GET /api/v1/admin/ops/dashboard/error-trend
*/
export interface OpsErrorTrendResponse {
data: ErrorTrendDataPoint[]
}
export interface ErrorTrendDataPoint {
date: string
total_errors: number
rate_limit_errors: number
auth_errors: number
network_errors: number
other_errors: number
}
// =============================================================================
// ChatGPT API 响应类型
// 基于 requirements.md Requirement 1.2 定义
// =============================================================================
/**
* 账号检查响应
* API: GET https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27
* Headers: Authorization: Bearer {token}
*/
export interface ChatGPTCheckResponse {
accounts: ChatGPTAccountInfo[]
}
export interface ChatGPTAccountInfo {
account_id: string
account: ChatGPTAccountDetails
features: string[]
entitlement: ChatGPTEntitlement
last_active_subscription: ChatGPTLastActiveSubscription
is_eligible_for_yearly_plus_subscription: boolean
}
export interface ChatGPTAccountDetails {
account_user_id: string
processor: {
a001: {
has_customer_object: boolean
}
}
account_user_role: string
plan_type: string
is_most_recent_expired_subscription_gratis: boolean
has_previously_paid_subscription: boolean
name: string | null
profile_picture_id: string | null
profile_picture_url: string | null
structure: string
is_deactivated: boolean
is_disabled: boolean
// SAM (Security Account Management) 相关字段
is_sam_enforced: boolean
is_sam_enabled: boolean
is_sam_compliant: boolean
is_sam_grace_period: boolean
is_sam_grace_period_expired: boolean
is_sam_grace_period_expiring_soon: boolean
is_sam_grace_period_expiring_today: boolean
is_sam_grace_period_expiring_tomorrow: boolean
is_sam_grace_period_expiring_in_two_days: boolean
is_sam_grace_period_expiring_in_three_days: boolean
is_sam_grace_period_expiring_in_four_days: boolean
is_sam_grace_period_expiring_in_five_days: boolean
is_sam_grace_period_expiring_in_six_days: boolean
is_sam_grace_period_expiring_in_seven_days: boolean
}
export interface ChatGPTEntitlement {
subscription_id: string | null
has_active_subscription: boolean
subscription_plan: string
expires_at: string | null
}
export interface ChatGPTLastActiveSubscription {
subscription_id: string | null
purchase_origin_platform: string
will_renew: boolean
}
// =============================================================================
// 通用 API 类型
// =============================================================================
/**
* API 错误响应
* 通用错误响应格式
*/
export interface ApiErrorResponse {
error: string
message?: string
code?: string
details?: Record<string, unknown>
}
/**
* 分页请求参数
*/
export interface PaginationParams {
page?: number
page_size?: number
}
/**
* 分页响应包装
*/
export interface PaginatedResponse<T> {
data: T[]
total: number
page: number
page_size: number
total_pages: number
}
/**
* 通用列表响应包装
*/
export interface ListResponse<T> {
data: T[]
}