596 lines
15 KiB
TypeScript
596 lines
15 KiB
TypeScript
/**
|
||
* Admin Accounts API endpoints
|
||
* Handles AI platform account management for administrators
|
||
*/
|
||
|
||
import { apiClient } from '../client'
|
||
import type {
|
||
Account,
|
||
CreateAccountRequest,
|
||
UpdateAccountRequest,
|
||
PaginatedResponse,
|
||
AccountUsageInfo,
|
||
WindowStats,
|
||
ClaudeModel,
|
||
AccountUsageStatsResponse,
|
||
TempUnschedulableStatus,
|
||
AdminDataPayload,
|
||
AdminDataImportResult,
|
||
CheckMixedChannelRequest,
|
||
CheckMixedChannelResponse
|
||
} from '@/types'
|
||
|
||
/**
|
||
* List all accounts with pagination
|
||
* @param page - Page number (default: 1)
|
||
* @param pageSize - Items per page (default: 20)
|
||
* @param filters - Optional filters
|
||
* @returns Paginated list of accounts
|
||
*/
|
||
export async function list(
|
||
page: number = 1,
|
||
pageSize: number = 20,
|
||
filters?: {
|
||
platform?: string
|
||
type?: string
|
||
status?: string
|
||
group?: string
|
||
search?: string
|
||
},
|
||
options?: {
|
||
signal?: AbortSignal
|
||
}
|
||
): Promise<PaginatedResponse<Account>> {
|
||
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||
params: {
|
||
page,
|
||
page_size: pageSize,
|
||
...filters
|
||
},
|
||
signal: options?.signal
|
||
})
|
||
return data
|
||
}
|
||
|
||
export interface AccountListWithEtagResult {
|
||
notModified: boolean
|
||
etag: string | null
|
||
data: PaginatedResponse<Account> | null
|
||
}
|
||
|
||
export async function listWithEtag(
|
||
page: number = 1,
|
||
pageSize: number = 20,
|
||
filters?: {
|
||
platform?: string
|
||
type?: string
|
||
status?: string
|
||
search?: string
|
||
},
|
||
options?: {
|
||
signal?: AbortSignal
|
||
etag?: string | null
|
||
}
|
||
): Promise<AccountListWithEtagResult> {
|
||
const headers: Record<string, string> = {}
|
||
if (options?.etag) {
|
||
headers['If-None-Match'] = options.etag
|
||
}
|
||
|
||
const response = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||
params: {
|
||
page,
|
||
page_size: pageSize,
|
||
...filters
|
||
},
|
||
headers,
|
||
signal: options?.signal,
|
||
validateStatus: (status) => (status >= 200 && status < 300) || status === 304
|
||
})
|
||
|
||
const etagHeader = typeof response.headers?.etag === 'string' ? response.headers.etag : null
|
||
if (response.status === 304) {
|
||
return {
|
||
notModified: true,
|
||
etag: etagHeader,
|
||
data: null
|
||
}
|
||
}
|
||
|
||
return {
|
||
notModified: false,
|
||
etag: etagHeader,
|
||
data: response.data
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Get account by ID
|
||
* @param id - Account ID
|
||
* @returns Account details
|
||
*/
|
||
export async function getById(id: number): Promise<Account> {
|
||
const { data } = await apiClient.get<Account>(`/admin/accounts/${id}`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Create new account
|
||
* @param accountData - Account data
|
||
* @returns Created account
|
||
*/
|
||
export async function create(accountData: CreateAccountRequest): Promise<Account> {
|
||
const { data } = await apiClient.post<Account>('/admin/accounts', accountData)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Update account
|
||
* @param id - Account ID
|
||
* @param updates - Fields to update
|
||
* @returns Updated account
|
||
*/
|
||
export async function update(id: number, updates: UpdateAccountRequest): Promise<Account> {
|
||
const { data } = await apiClient.put<Account>(`/admin/accounts/${id}`, updates)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Check mixed-channel risk for account-group binding.
|
||
*/
|
||
export async function checkMixedChannelRisk(
|
||
payload: CheckMixedChannelRequest
|
||
): Promise<CheckMixedChannelResponse> {
|
||
const { data } = await apiClient.post<CheckMixedChannelResponse>('/admin/accounts/check-mixed-channel', payload)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Delete account
|
||
* @param id - Account ID
|
||
* @returns Success confirmation
|
||
*/
|
||
export async function deleteAccount(id: number): Promise<{ message: string }> {
|
||
const { data } = await apiClient.delete<{ message: string }>(`/admin/accounts/${id}`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Toggle account status
|
||
* @param id - Account ID
|
||
* @param status - New status
|
||
* @returns Updated account
|
||
*/
|
||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Account> {
|
||
return update(id, { status })
|
||
}
|
||
|
||
/**
|
||
* Test account connectivity
|
||
* @param id - Account ID
|
||
* @returns Test result
|
||
*/
|
||
export async function testAccount(id: number): Promise<{
|
||
success: boolean
|
||
message: string
|
||
latency_ms?: number
|
||
}> {
|
||
const { data } = await apiClient.post<{
|
||
success: boolean
|
||
message: string
|
||
latency_ms?: number
|
||
}>(`/admin/accounts/${id}/test`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Refresh account credentials
|
||
* @param id - Account ID
|
||
* @returns Updated account
|
||
*/
|
||
export async function refreshCredentials(id: number): Promise<Account> {
|
||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/refresh`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get account usage statistics
|
||
* @param id - Account ID
|
||
* @param days - Number of days (default: 30)
|
||
* @returns Account usage statistics with history, summary, and models
|
||
*/
|
||
export async function getStats(id: number, days: number = 30): Promise<AccountUsageStatsResponse> {
|
||
const { data } = await apiClient.get<AccountUsageStatsResponse>(`/admin/accounts/${id}/stats`, {
|
||
params: { days }
|
||
})
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Clear account error
|
||
* @param id - Account ID
|
||
* @returns Updated account
|
||
*/
|
||
export async function clearError(id: number): Promise<Account> {
|
||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/clear-error`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get account usage information (5h/7d window)
|
||
* @param id - Account ID
|
||
* @returns Account usage info
|
||
*/
|
||
export async function getUsage(id: number): Promise<AccountUsageInfo> {
|
||
const { data } = await apiClient.get<AccountUsageInfo>(`/admin/accounts/${id}/usage`)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Clear account rate limit status
|
||
* @param id - Account ID
|
||
* @returns Updated account
|
||
*/
|
||
export async function clearRateLimit(id: number): Promise<Account> {
|
||
const { data } = await apiClient.post<Account>(
|
||
`/admin/accounts/${id}/clear-rate-limit`
|
||
)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get temporary unschedulable status
|
||
* @param id - Account ID
|
||
* @returns Status with detail state if active
|
||
*/
|
||
export async function getTempUnschedulableStatus(id: number): Promise<TempUnschedulableStatus> {
|
||
const { data } = await apiClient.get<TempUnschedulableStatus>(
|
||
`/admin/accounts/${id}/temp-unschedulable`
|
||
)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Reset temporary unschedulable status
|
||
* @param id - Account ID
|
||
* @returns Success confirmation
|
||
*/
|
||
export async function resetTempUnschedulable(id: number): Promise<{ message: string }> {
|
||
const { data } = await apiClient.delete<{ message: string }>(
|
||
`/admin/accounts/${id}/temp-unschedulable`
|
||
)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Generate OAuth authorization URL
|
||
* @param endpoint - API endpoint path
|
||
* @param config - Proxy configuration
|
||
* @returns Auth URL and session ID
|
||
*/
|
||
export async function generateAuthUrl(
|
||
endpoint: string,
|
||
config: { proxy_id?: number }
|
||
): Promise<{ auth_url: string; session_id: string }> {
|
||
const { data } = await apiClient.post<{ auth_url: string; session_id: string }>(endpoint, config)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Exchange authorization code for tokens
|
||
* @param endpoint - API endpoint path
|
||
* @param exchangeData - Session ID, code, and optional proxy config
|
||
* @returns Token information
|
||
*/
|
||
export async function exchangeCode(
|
||
endpoint: string,
|
||
exchangeData: { session_id: string; code: string; state?: string; proxy_id?: number }
|
||
): Promise<Record<string, unknown>> {
|
||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, exchangeData)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Batch create accounts
|
||
* @param accounts - Array of account data
|
||
* @returns Results of batch creation
|
||
*/
|
||
export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
|
||
success: number
|
||
failed: number
|
||
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||
}> {
|
||
const { data } = await apiClient.post<{
|
||
success: number
|
||
failed: number
|
||
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||
}>('/admin/accounts/batch', { accounts })
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Batch update credentials fields for multiple accounts
|
||
* @param request - Batch update request containing account IDs, field name, and value
|
||
* @returns Results of batch update
|
||
*/
|
||
export async function batchUpdateCredentials(request: {
|
||
account_ids: number[]
|
||
field: string
|
||
value: any
|
||
}): Promise<{
|
||
success: number
|
||
failed: number
|
||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||
}> {
|
||
const { data } = await apiClient.post<{
|
||
success: number
|
||
failed: number
|
||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||
}>('/admin/accounts/batch-update-credentials', request)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Bulk update multiple accounts
|
||
* @param accountIds - Array of account IDs
|
||
* @param updates - Fields to update
|
||
* @returns Success confirmation
|
||
*/
|
||
export async function bulkUpdate(
|
||
accountIds: number[],
|
||
updates: Record<string, unknown>
|
||
): Promise<{
|
||
success: number
|
||
failed: number
|
||
success_ids?: number[]
|
||
failed_ids?: number[]
|
||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||
}> {
|
||
const { data } = await apiClient.post<{
|
||
success: number
|
||
failed: number
|
||
success_ids?: number[]
|
||
failed_ids?: number[]
|
||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||
}>('/admin/accounts/bulk-update', {
|
||
account_ids: accountIds,
|
||
...updates
|
||
})
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get account today statistics
|
||
* @param id - Account ID
|
||
* @returns Today's stats (requests, tokens, cost)
|
||
*/
|
||
export async function getTodayStats(id: number): Promise<WindowStats> {
|
||
const { data } = await apiClient.get<WindowStats>(`/admin/accounts/${id}/today-stats`)
|
||
return data
|
||
}
|
||
|
||
export interface BatchTodayStatsResponse {
|
||
stats: Record<string, WindowStats>
|
||
}
|
||
|
||
/**
|
||
* 批量获取多个账号的今日统计
|
||
* @param accountIds - 账号 ID 列表
|
||
* @returns 以账号 ID(字符串)为键的统计映射
|
||
*/
|
||
export async function getBatchTodayStats(accountIds: number[]): Promise<BatchTodayStatsResponse> {
|
||
const { data } = await apiClient.post<BatchTodayStatsResponse>('/admin/accounts/today-stats/batch', {
|
||
account_ids: accountIds
|
||
})
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Set account schedulable status
|
||
* @param id - Account ID
|
||
* @param schedulable - Whether the account should participate in scheduling
|
||
* @returns Updated account
|
||
*/
|
||
export async function setSchedulable(id: number, schedulable: boolean): Promise<Account> {
|
||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/schedulable`, {
|
||
schedulable
|
||
})
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get available models for an account
|
||
* @param id - Account ID
|
||
* @returns List of available models for this account
|
||
*/
|
||
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
||
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`)
|
||
return data
|
||
}
|
||
|
||
export interface CRSPreviewAccount {
|
||
crs_account_id: string
|
||
kind: string
|
||
name: string
|
||
platform: string
|
||
type: string
|
||
}
|
||
|
||
export interface PreviewFromCRSResult {
|
||
new_accounts: CRSPreviewAccount[]
|
||
existing_accounts: CRSPreviewAccount[]
|
||
}
|
||
|
||
export async function previewFromCrs(params: {
|
||
base_url: string
|
||
username: string
|
||
password: string
|
||
}): Promise<PreviewFromCRSResult> {
|
||
const { data } = await apiClient.post<PreviewFromCRSResult>('/admin/accounts/sync/crs/preview', params)
|
||
return data
|
||
}
|
||
|
||
export async function syncFromCrs(params: {
|
||
base_url: string
|
||
username: string
|
||
password: string
|
||
sync_proxies?: boolean
|
||
selected_account_ids?: string[]
|
||
}): Promise<{
|
||
created: number
|
||
updated: number
|
||
skipped: number
|
||
failed: number
|
||
items: Array<{
|
||
crs_account_id: string
|
||
kind: string
|
||
name: string
|
||
action: string
|
||
error?: string
|
||
}>
|
||
}> {
|
||
const { data } = await apiClient.post<{
|
||
created: number
|
||
updated: number
|
||
skipped: number
|
||
failed: number
|
||
items: Array<{
|
||
crs_account_id: string
|
||
kind: string
|
||
name: string
|
||
action: string
|
||
error?: string
|
||
}>
|
||
}>('/admin/accounts/sync/crs', params)
|
||
return data
|
||
}
|
||
|
||
export async function exportData(options?: {
|
||
ids?: number[]
|
||
filters?: {
|
||
platform?: string
|
||
type?: string
|
||
status?: string
|
||
search?: string
|
||
}
|
||
includeProxies?: boolean
|
||
}): Promise<AdminDataPayload> {
|
||
const params: Record<string, string> = {}
|
||
if (options?.ids && options.ids.length > 0) {
|
||
params.ids = options.ids.join(',')
|
||
} else if (options?.filters) {
|
||
const { platform, type, status, search } = options.filters
|
||
if (platform) params.platform = platform
|
||
if (type) params.type = type
|
||
if (status) params.status = status
|
||
if (search) params.search = search
|
||
}
|
||
if (options?.includeProxies === false) {
|
||
params.include_proxies = 'false'
|
||
}
|
||
const { data } = await apiClient.get<AdminDataPayload>('/admin/accounts/data', { params })
|
||
return data
|
||
}
|
||
|
||
export async function importData(payload: {
|
||
data: AdminDataPayload
|
||
skip_default_group_bind?: boolean
|
||
}): Promise<AdminDataImportResult> {
|
||
const { data } = await apiClient.post<AdminDataImportResult>('/admin/accounts/data', {
|
||
data: payload.data,
|
||
skip_default_group_bind: payload.skip_default_group_bind
|
||
})
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Get Antigravity default model mapping from backend
|
||
* @returns Default model mapping (from -> to)
|
||
*/
|
||
export async function getAntigravityDefaultModelMapping(): Promise<Record<string, string>> {
|
||
const { data } = await apiClient.get<Record<string, string>>(
|
||
'/admin/accounts/antigravity/default-model-mapping'
|
||
)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Refresh OpenAI token using refresh token
|
||
* @param refreshToken - The refresh token
|
||
* @param proxyId - Optional proxy ID
|
||
* @returns Token information including access_token, email, etc.
|
||
*/
|
||
export async function refreshOpenAIToken(
|
||
refreshToken: string,
|
||
proxyId?: number | null,
|
||
endpoint: string = '/admin/openai/refresh-token'
|
||
): Promise<Record<string, unknown>> {
|
||
const payload: { refresh_token: string; proxy_id?: number } = {
|
||
refresh_token: refreshToken
|
||
}
|
||
if (proxyId) {
|
||
payload.proxy_id = proxyId
|
||
}
|
||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
|
||
return data
|
||
}
|
||
|
||
/**
|
||
* Validate Sora session token and exchange to access token
|
||
* @param sessionToken - Sora session token
|
||
* @param proxyId - Optional proxy ID
|
||
* @param endpoint - API endpoint path
|
||
* @returns Token information including access_token
|
||
*/
|
||
export async function validateSoraSessionToken(
|
||
sessionToken: string,
|
||
proxyId?: number | null,
|
||
endpoint: string = '/admin/sora/st2at'
|
||
): Promise<Record<string, unknown>> {
|
||
const payload: { session_token: string; proxy_id?: number } = {
|
||
session_token: sessionToken
|
||
}
|
||
if (proxyId) {
|
||
payload.proxy_id = proxyId
|
||
}
|
||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
|
||
return data
|
||
}
|
||
|
||
export const accountsAPI = {
|
||
list,
|
||
listWithEtag,
|
||
getById,
|
||
create,
|
||
update,
|
||
checkMixedChannelRisk,
|
||
delete: deleteAccount,
|
||
toggleStatus,
|
||
testAccount,
|
||
refreshCredentials,
|
||
getStats,
|
||
clearError,
|
||
getUsage,
|
||
getTodayStats,
|
||
getBatchTodayStats,
|
||
clearRateLimit,
|
||
getTempUnschedulableStatus,
|
||
resetTempUnschedulable,
|
||
setSchedulable,
|
||
getAvailableModels,
|
||
generateAuthUrl,
|
||
exchangeCode,
|
||
refreshOpenAIToken,
|
||
validateSoraSessionToken,
|
||
batchCreate,
|
||
batchUpdateCredentials,
|
||
bulkUpdate,
|
||
previewFromCrs,
|
||
syncFromCrs,
|
||
exportData,
|
||
importData,
|
||
getAntigravityDefaultModelMapping
|
||
}
|
||
|
||
export default accountsAPI
|