Spaces:
Runtime error
Runtime error
| import { httpRequest, request } from "@/lib/request"; | |
| export type AccountType = string; | |
| export type AccountStatus = "正常" | "限流" | "异常" | "禁用"; | |
| export type ImageModel = "gpt-image-2" | "codex-gpt-image-2"; | |
| export type AuthRole = "admin" | "user" | "normal"; | |
| export type Account = { | |
| access_token: string; | |
| type: AccountType; | |
| export_type?: string | null; | |
| status: AccountStatus; | |
| quota: number; | |
| image_quota_unknown?: boolean; | |
| email?: string | null; | |
| expired?: string | null; | |
| id_token?: string | null; | |
| account_id?: string | null; | |
| last_refresh?: string | null; | |
| refresh_token?: string | null; | |
| user_id?: string | null; | |
| limits_progress?: Array<{ | |
| feature_name?: string; | |
| remaining?: number; | |
| reset_after?: string; | |
| }>; | |
| default_model_slug?: string | null; | |
| restore_at?: string | null; | |
| success: number; | |
| fail: number; | |
| last_used_at?: string | null; | |
| owner_id?: string; | |
| owner_role?: string; | |
| owner_name?: string; | |
| shared?: boolean; | |
| shared_cleanup_failures?: number; | |
| }; | |
| type AccountListResponse = { | |
| items: Account[]; | |
| }; | |
| type AccountMutationResponse = { | |
| items: Account[]; | |
| added?: number; | |
| skipped?: number; | |
| removed?: number; | |
| refreshed?: number; | |
| errors?: Array<{ access_token: string; error: string }>; | |
| }; | |
| type AccountRefreshResponse = { | |
| items: Account[]; | |
| refreshed: number; | |
| errors: Array<{ access_token: string; error: string }>; | |
| }; | |
| type AccountUpdateResponse = { | |
| item: Account; | |
| items: Account[]; | |
| }; | |
| export type AccountEmailLoginImportResponse = { | |
| status: "pending_code" | "completed"; | |
| session_id?: string; | |
| email: string; | |
| message: string; | |
| created_account?: boolean; | |
| generated_password?: string; | |
| result?: AccountMutationResponse; | |
| }; | |
| export type AccountImportPayload = { | |
| access_token: string; | |
| accessToken?: string; | |
| type?: string; | |
| export_type?: string; | |
| email?: string; | |
| expired?: string; | |
| id_token?: string; | |
| account_id?: string; | |
| last_refresh?: string; | |
| refresh_token?: string; | |
| [key: string]: unknown; | |
| }; | |
| export type AccountExportFormat = "json" | "zip"; | |
| export type FreeAccountCleanupAction = "mark_abnormal" | "delete"; | |
| export type FreeAccountCleanupSettings = { | |
| enabled: boolean; | |
| interval_minutes: number | string; | |
| failure_threshold: number | string; | |
| register_precheck_enabled: boolean; | |
| action: FreeAccountCleanupAction; | |
| }; | |
| export type RedeemCodeCleanupSettings = { | |
| enabled: boolean; | |
| interval_minutes: number | string; | |
| delete_expired: boolean; | |
| delete_disabled: boolean; | |
| archive_redeemed: boolean; | |
| redeemed_older_than_days: number | string; | |
| limit: number | string; | |
| }; | |
| export type SettingsConfig = { | |
| proxy: string; | |
| base_url?: string; | |
| image_proxy_base_url?: string; | |
| global_system_prompt?: string; | |
| sensitive_words?: string[]; | |
| ai_review?: { | |
| enabled?: boolean; | |
| base_url?: string; | |
| api_key?: string; | |
| model?: string; | |
| prompt?: string; | |
| }; | |
| refresh_account_interval_minute?: number | string; | |
| image_retention_days?: number | string; | |
| image_poll_timeout_secs?: number | string; | |
| image_account_concurrency?: number | string; | |
| normal_user_default_credits?: number | string; | |
| normal_user_daily_credits?: number | string; | |
| wechat_login_enabled?: boolean; | |
| wechat_auto_register_enabled?: boolean; | |
| internal_user_login_session_duration_hours?: number | string; | |
| normal_user_login_session_duration_hours?: number | string; | |
| login_session_duration_hours?: number | string; | |
| site_title?: string; | |
| site_logo_url?: string; | |
| site_header_title?: string; | |
| site_external_link_label?: string; | |
| site_external_link_url?: string; | |
| site_external_link_icon_url?: string; | |
| site_external_link_icon_svg?: string; | |
| shop_purchase_url?: string; | |
| auto_remove_invalid_accounts?: boolean; | |
| auto_remove_rate_limited_accounts?: boolean; | |
| free_account_cleanup?: FreeAccountCleanupSettings; | |
| redeem_code_cleanup?: RedeemCodeCleanupSettings; | |
| cleanup_shared_conversations?: boolean; | |
| log_levels?: string[]; | |
| backup?: BackupSettings; | |
| backup_state?: BackupState; | |
| image_storage?: ImageStorageSettings; | |
| announcements?: Announcement[]; | |
| [key: string]: unknown; | |
| }; | |
| export type AnnouncementType = "notice" | "timeline"; | |
| export type AnnouncementTarget = "internal" | "normal" | "all"; | |
| export type Announcement = { | |
| id: string; | |
| type: AnnouncementType; | |
| target: AnnouncementTarget; | |
| title: string; | |
| content: string; | |
| enabled: boolean; | |
| created_at: string; | |
| updated_at: string; | |
| }; | |
| export type ImageStorageMode = "local" | "webdav" | "both"; | |
| export type ImageStorageSettings = { | |
| enabled: boolean; | |
| mode: ImageStorageMode; | |
| webdav_url: string; | |
| webdav_username: string; | |
| webdav_password: string; | |
| webdav_root_path: string; | |
| public_base_url: string; | |
| }; | |
| export type BackupInclude = { | |
| config: boolean; | |
| register: boolean; | |
| cpa: boolean; | |
| sub2api: boolean; | |
| logs: boolean; | |
| image_tasks: boolean; | |
| accounts_snapshot: boolean; | |
| auth_keys_snapshot: boolean; | |
| images: boolean; | |
| }; | |
| export type BackupSettings = { | |
| enabled: boolean; | |
| provider: "cloudflare_r2" | string; | |
| account_id: string; | |
| access_key_id: string; | |
| secret_access_key: string; | |
| bucket: string; | |
| prefix: string; | |
| interval_minutes: number | string; | |
| rotation_keep: number | string; | |
| encrypt: boolean; | |
| passphrase: string; | |
| include: BackupInclude; | |
| }; | |
| export type BackupState = { | |
| running: boolean; | |
| last_started_at?: string | null; | |
| last_finished_at?: string | null; | |
| last_status?: string; | |
| last_error?: string | null; | |
| last_object_key?: string | null; | |
| }; | |
| export type BackupItem = { | |
| key: string; | |
| name: string; | |
| size: number; | |
| updated_at?: string | null; | |
| encrypted: boolean; | |
| }; | |
| export type BackupDetail = { | |
| key: string; | |
| name: string; | |
| encrypted: boolean; | |
| created_at?: string | null; | |
| trigger?: string | null; | |
| app_version?: string | null; | |
| storage_backend?: Record<string, unknown> | null; | |
| files: Array<{ | |
| name: string; | |
| exists: boolean; | |
| content_type?: string; | |
| size: number; | |
| sha256?: string; | |
| }>; | |
| snapshots: Array<{ | |
| name: string; | |
| count: number; | |
| }>; | |
| }; | |
| export type ManagedImage = { | |
| rel: string; | |
| path?: string; | |
| name: string; | |
| date: string; | |
| size: number; | |
| url: string; | |
| thumbnail_url?: string; | |
| created_at: string; | |
| storage?: "local" | "webdav" | "both" | string; | |
| local?: boolean; | |
| webdav?: boolean; | |
| width?: number; | |
| height?: number; | |
| tags?: string[]; | |
| }; | |
| export type SystemLog = { | |
| id: string; | |
| time: string; | |
| type: "call" | "account" | string; | |
| summary?: string; | |
| detail?: Record<string, unknown>; | |
| [key: string]: unknown; | |
| }; | |
| export type ImageResponse = { | |
| created: number; | |
| data: Array<{ b64_json?: string; url?: string; revised_prompt?: string }>; | |
| }; | |
| export type ImageTask = { | |
| id: string; | |
| status: "queued" | "running" | "success" | "error"; | |
| mode: "generate" | "edit"; | |
| model?: ImageModel; | |
| size?: string; | |
| created_at: string; | |
| started_at?: string | null; | |
| updated_at: string; | |
| data?: Array<{ b64_json?: string; url?: string; revised_prompt?: string }>; | |
| error?: string; | |
| }; | |
| type ImageTaskListResponse = { | |
| items: ImageTask[]; | |
| missing_ids: string[]; | |
| }; | |
| export type LoginResponse = { | |
| ok: boolean; | |
| version: string; | |
| role: AuthRole; | |
| subject_id: string; | |
| name: string; | |
| account_pool_enabled: boolean; | |
| login_session_duration_hours?: number; | |
| key?: string; | |
| created?: boolean; | |
| }; | |
| export type BrandingConfig = { | |
| site_title?: string; | |
| site_logo_url?: string; | |
| site_header_title?: string; | |
| site_external_link_label?: string; | |
| site_external_link_url?: string; | |
| site_external_link_icon_url?: string; | |
| site_external_link_icon_svg?: string; | |
| }; | |
| export type UserKey = { | |
| id: string; | |
| name: string; | |
| role: "user" | "normal"; | |
| enabled: boolean; | |
| account_pool_enabled?: boolean; | |
| created_at: string | null; | |
| last_used_at: string | null; | |
| credits?: number; | |
| paid_credits?: number; | |
| reserved_credits?: number; | |
| used_credits?: number; | |
| total_recharged?: number; | |
| permanent_credits?: number; | |
| expiring_credits?: number; | |
| expiring_reserved_credits?: number; | |
| package_credits?: number; | |
| package_grants?: PackageGrant[]; | |
| credit_grants?: Array<{ | |
| id?: string; | |
| remaining?: number; | |
| reserved?: number; | |
| expires_at?: string; | |
| source?: string; | |
| }>; | |
| daily_credits?: number; | |
| daily_credits_remaining?: number; | |
| daily_credits_used?: number; | |
| daily_bonus_credits?: number; | |
| daily_reserved_credits?: number; | |
| daily_credits_date?: string; | |
| has_api_key_display?: boolean; | |
| wechat_openid?: string; | |
| wechat_nickname?: string; | |
| wechat_avatar?: string; | |
| wechat_bound_at?: string | null; | |
| }; | |
| export type MyProfile = { | |
| id: string; | |
| name: string; | |
| role: AuthRole | string; | |
| created_at?: string | null; | |
| account_pool_enabled?: boolean; | |
| wechat_openid?: string; | |
| wechat_bound_at?: string | null; | |
| }; | |
| export type CreditSummary = { | |
| role: AuthRole | string; | |
| metered: boolean; | |
| credits: number | null; | |
| paid_credits: number | null; | |
| reserved_credits: number; | |
| used_credits: number | null; | |
| total_recharged: number | null; | |
| permanent_credits: number | null; | |
| expiring_credits: number; | |
| expiring_reserved_credits: number; | |
| credit_grants: Array<{ | |
| id?: string; | |
| remaining?: number; | |
| reserved?: number; | |
| expires_at?: string; | |
| source?: string; | |
| }>; | |
| package_credits: number; | |
| package_grants: PackageGrant[]; | |
| daily_credits: number; | |
| daily_credits_remaining: number; | |
| daily_credits_used: number | null; | |
| daily_bonus_credits: number; | |
| daily_reserved_credits: number; | |
| daily_credits_date: string; | |
| }; | |
| export type PackagePlan = { | |
| name?: string | null; | |
| duration_days?: number | null; | |
| daily_limit?: number | null; | |
| weekly_limit?: number | null; | |
| monthly_limit?: number | null; | |
| total_limit?: number | null; | |
| }; | |
| export type PackageGrant = PackagePlan & { | |
| id?: string | null; | |
| remaining?: number | null; | |
| created_at?: string | null; | |
| starts_at?: string | null; | |
| expires_at?: string | null; | |
| source?: string | null; | |
| code_id?: string | null; | |
| used_total?: number | null; | |
| reserved_total?: number | null; | |
| used_daily?: number | null; | |
| reserved_daily?: number | null; | |
| used_weekly?: number | null; | |
| reserved_weekly?: number | null; | |
| used_monthly?: number | null; | |
| reserved_monthly?: number | null; | |
| }; | |
| export type RedeemCode = { | |
| id: string; | |
| code?: string | null; | |
| prefix: string; | |
| credits: number; | |
| status: "active" | "redeemed" | "disabled" | string; | |
| code_type?: "normal" | "daily_gift" | "package" | string | null; | |
| batch?: string | null; | |
| batch_id?: string | null; | |
| batch_single_use?: boolean; | |
| note?: string | null; | |
| created_at?: string | null; | |
| starts_at?: string | null; | |
| expires_at?: string | null; | |
| daily_gift_date?: string | null; | |
| package_plan?: PackagePlan | null; | |
| credit_expires_days?: number | null; | |
| credit_expires_at?: string | null; | |
| is_not_started?: boolean; | |
| is_expired?: boolean; | |
| redeemed_by?: string | null; | |
| redeemed_at?: string | null; | |
| redeemed_user?: { | |
| id?: string | null; | |
| name?: string | null; | |
| wechat_openid?: string | null; | |
| wechat_nickname?: string | null; | |
| } | null; | |
| cleanup_reason?: string; | |
| }; | |
| export type RedeemCodeCleanupSummary = { | |
| matched: number; | |
| deleted: number; | |
| archived: number; | |
| archive_path?: string | null; | |
| expired: number; | |
| disabled: number; | |
| redeemed: number; | |
| limit: number; | |
| }; | |
| export type RedeemCodeCleanupResponse = { | |
| dry_run: boolean; | |
| summary: RedeemCodeCleanupSummary; | |
| archive?: { | |
| archived: number; | |
| path?: string | null; | |
| }; | |
| items: RedeemCode[]; | |
| all_items?: RedeemCode[]; | |
| }; | |
| export type ShopLedgerEntry = { | |
| id: string; | |
| time: string; | |
| type: string; | |
| source?: string; | |
| user_id?: string; | |
| code_id?: string; | |
| code_type?: string; | |
| delta?: number; | |
| credits?: number; | |
| batch?: string; | |
| note?: string; | |
| }; | |
| export type RegisterConfig = { | |
| enabled: boolean; | |
| mail: { | |
| request_timeout: number; | |
| wait_timeout: number; | |
| wait_interval: number; | |
| providers: Array<Record<string, unknown>>; | |
| }; | |
| proxy: string; | |
| total: number; | |
| threads: number; | |
| mode: "total" | "quota" | "available"; | |
| target_quota: number; | |
| target_available: number; | |
| check_interval: number; | |
| stats: { | |
| job_id?: string; | |
| success: number; | |
| fail: number; | |
| done: number; | |
| running: number; | |
| threads: number; | |
| elapsed_seconds?: number; | |
| avg_seconds?: number; | |
| success_rate?: number; | |
| current_quota?: number; | |
| current_available?: number; | |
| started_at?: string; | |
| updated_at?: string; | |
| finished_at?: string; | |
| }; | |
| logs?: Array<{ | |
| time: string; | |
| text: string; | |
| level: string; | |
| }>; | |
| }; | |
| export async function login(authKey: string) { | |
| const normalizedAuthKey = String(authKey || "").trim(); | |
| return httpRequest<LoginResponse>("/auth/login", { | |
| method: "POST", | |
| body: {}, | |
| headers: { | |
| Authorization: `Bearer ${normalizedAuthKey}`, | |
| }, | |
| redirectOnUnauthorized: false, | |
| }); | |
| } | |
| export async function fetchAccounts() { | |
| return httpRequest<AccountListResponse>("/api/accounts"); | |
| } | |
| export async function fetchQuotaSummary(strategy?: string, allowShared = true) { | |
| const params = new URLSearchParams(); | |
| if (strategy) { | |
| params.set("strategy", strategy); | |
| } | |
| params.set("allow_shared", allowShared ? "true" : "false"); | |
| return httpRequest<{ quota: number; label?: string; unknown?: boolean; count?: number }>( | |
| `/api/accounts/quota?${params.toString()}`, | |
| ); | |
| } | |
| export async function createAccounts(tokens: string[], accounts: AccountImportPayload[] = []) { | |
| return httpRequest<AccountMutationResponse>("/api/accounts", { | |
| method: "POST", | |
| body: { | |
| tokens, | |
| ...(accounts.length > 0 ? { accounts } : {}), | |
| }, | |
| }); | |
| } | |
| export async function startAccountEmailLoginImport(email: string) { | |
| return httpRequest<AccountEmailLoginImportResponse>("/api/accounts/email-login/start", { | |
| method: "POST", | |
| body: { email }, | |
| }); | |
| } | |
| export async function verifyAccountEmailLoginImport(sessionId: string, code: string) { | |
| return httpRequest<AccountEmailLoginImportResponse>("/api/accounts/email-login/verify", { | |
| method: "POST", | |
| body: { session_id: sessionId, code }, | |
| }); | |
| } | |
| export async function deleteAccounts(tokens: string[]) { | |
| return httpRequest<AccountMutationResponse>("/api/accounts", { | |
| method: "DELETE", | |
| body: { tokens }, | |
| }); | |
| } | |
| export async function refreshAccounts(accessTokens: string[]) { | |
| return httpRequest<AccountRefreshResponse>("/api/accounts/refresh", { | |
| method: "POST", | |
| body: { access_tokens: accessTokens }, | |
| }); | |
| } | |
| function getFilenameFromDisposition(value: unknown, fallback: string) { | |
| const disposition = typeof value === "string" ? value : ""; | |
| const utf8Match = disposition.match(/filename\*=UTF-8''([^;]+)/i); | |
| if (utf8Match?.[1]) { | |
| return decodeURIComponent(utf8Match[1].replace(/"/g, "")); | |
| } | |
| const match = disposition.match(/filename="?([^";]+)"?/i); | |
| return match?.[1] || fallback; | |
| } | |
| export async function exportAccounts(format: AccountExportFormat, accessTokens: string[] = []) { | |
| const response = await request.request<Blob>({ | |
| url: "/api/accounts/export", | |
| method: "POST", | |
| data: { | |
| format, | |
| access_tokens: accessTokens, | |
| }, | |
| responseType: "blob", | |
| }); | |
| return { | |
| blob: response.data, | |
| filename: getFilenameFromDisposition(response.headers["content-disposition"], `codex-accounts.${format}`), | |
| }; | |
| } | |
| export async function updateAccount( | |
| accessToken: string, | |
| updates: { | |
| type?: AccountType; | |
| status?: AccountStatus; | |
| quota?: number; | |
| shared?: boolean; | |
| }, | |
| ) { | |
| return httpRequest<AccountUpdateResponse>("/api/accounts/update", { | |
| method: "POST", | |
| body: { | |
| access_token: accessToken, | |
| ...updates, | |
| }, | |
| }); | |
| } | |
| export async function generateImage(prompt: string, model?: ImageModel, size?: string) { | |
| return httpRequest<ImageResponse>( | |
| "/v1/images/generations", | |
| { | |
| method: "POST", | |
| body: { | |
| prompt, | |
| ...(model ? { model } : {}), | |
| ...(size ? { size } : {}), | |
| n: 1, | |
| response_format: "b64_json", | |
| }, | |
| }, | |
| ); | |
| } | |
| export async function editImage(files: File | File[], prompt: string, model?: ImageModel, size?: string) { | |
| const formData = new FormData(); | |
| const uploadFiles = Array.isArray(files) ? files : [files]; | |
| uploadFiles.forEach((file) => { | |
| formData.append("image", file); | |
| }); | |
| formData.append("prompt", prompt); | |
| if (model) { | |
| formData.append("model", model); | |
| } | |
| if (size) { | |
| formData.append("size", size); | |
| } | |
| formData.append("n", "1"); | |
| return httpRequest<ImageResponse>( | |
| "/v1/images/edits", | |
| { | |
| method: "POST", | |
| body: formData, | |
| }, | |
| ); | |
| } | |
| export async function createImageGenerationTask( | |
| clientTaskId: string, | |
| prompt: string, | |
| model?: ImageModel, | |
| size?: string, | |
| memory?: boolean, | |
| memoryConversationId?: string, | |
| accountPoolStrategy?: string, | |
| ) { | |
| return httpRequest<ImageTask>("/api/image-tasks/generations", { | |
| method: "POST", | |
| body: { | |
| client_task_id: clientTaskId, | |
| prompt, | |
| ...(model ? { model } : {}), | |
| ...(size ? { size } : {}), | |
| ...(memory ? { memory: true } : {}), | |
| ...(memory && memoryConversationId ? { memory_conversation_id: memoryConversationId } : {}), | |
| ...(accountPoolStrategy ? { account_pool_strategy: accountPoolStrategy } : {}), | |
| }, | |
| }); | |
| } | |
| export async function createImageEditTask( | |
| clientTaskId: string, | |
| files: File | File[], | |
| prompt: string, | |
| model?: ImageModel, | |
| size?: string, | |
| memory?: boolean, | |
| memoryConversationId?: string, | |
| memoryReset?: boolean, | |
| accountPoolStrategy?: string, | |
| ) { | |
| const formData = new FormData(); | |
| const uploadFiles = Array.isArray(files) ? files : [files]; | |
| uploadFiles.forEach((file) => { | |
| formData.append("image", file); | |
| }); | |
| formData.append("client_task_id", clientTaskId); | |
| formData.append("prompt", prompt); | |
| if (model) { | |
| formData.append("model", model); | |
| } | |
| if (size) { | |
| formData.append("size", size); | |
| } | |
| if (memory) { | |
| formData.append("memory", "true"); | |
| } | |
| if (memory && memoryConversationId) { | |
| formData.append("memory_conversation_id", memoryConversationId); | |
| } | |
| if (memory && memoryReset) { | |
| formData.append("memory_reset", "true"); | |
| } | |
| if (accountPoolStrategy) { | |
| formData.append("account_pool_strategy", accountPoolStrategy); | |
| } | |
| return httpRequest<ImageTask>("/api/image-tasks/edits", { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| } | |
| export async function fetchImageTasks(ids: string[]) { | |
| const params = new URLSearchParams(); | |
| if (ids.length > 0) { | |
| params.set("ids", ids.join(",")); | |
| } | |
| return httpRequest<ImageTaskListResponse>(`/api/image-tasks${params.toString() ? `?${params.toString()}` : ""}`); | |
| } | |
| export async function fetchSettingsConfig() { | |
| return httpRequest<{ config: SettingsConfig }>("/api/settings"); | |
| } | |
| export async function fetchAnnouncements() { | |
| return httpRequest<{ items: Announcement[] }>("/api/announcements"); | |
| } | |
| export async function updateSettingsConfig(settings: SettingsConfig) { | |
| return httpRequest<{ config: SettingsConfig }>("/api/settings", { | |
| method: "POST", | |
| body: settings, | |
| }); | |
| } | |
| export async function testBackupConnection() { | |
| return httpRequest<{ result: { ok: boolean; status: number } }>("/api/backup/test", { | |
| method: "POST", | |
| body: {}, | |
| }); | |
| } | |
| export async function testImageStorageConnection() { | |
| return httpRequest<{ result: { ok: boolean; status: number; error?: string | null } }>("/api/image-storage/test", { | |
| method: "POST", | |
| body: {}, | |
| }); | |
| } | |
| export async function syncImageStorage() { | |
| return httpRequest<{ result: { uploaded: number; skipped: number; failed: number } }>("/api/image-storage/sync", { | |
| method: "POST", | |
| body: {}, | |
| }); | |
| } | |
| export async function fetchBackups() { | |
| return httpRequest<{ items: BackupItem[]; state: BackupState; settings: BackupSettings }>("/api/backups"); | |
| } | |
| export async function runBackupNow() { | |
| return httpRequest<{ result: { key: string; size: number; encrypted: boolean } }>("/api/backups/run", { | |
| method: "POST", | |
| body: {}, | |
| }); | |
| } | |
| export async function deleteBackup(key: string) { | |
| return httpRequest<{ ok: boolean }>("/api/backups/delete", { | |
| method: "POST", | |
| body: { key }, | |
| }); | |
| } | |
| export async function fetchBackupDetail(key: string) { | |
| const params = new URLSearchParams(); | |
| params.set("key", key); | |
| return httpRequest<{ item: BackupDetail }>(`/api/backups/detail?${params.toString()}`); | |
| } | |
| export function getBackupDownloadUrl(key: string) { | |
| const params = new URLSearchParams(); | |
| params.set("key", key); | |
| return `/api/backups/download?${params.toString()}`; | |
| } | |
| export async function fetchManagedImages(filters: { start_date?: string; end_date?: string }) { | |
| const params = new URLSearchParams(); | |
| if (filters.start_date) params.set("start_date", filters.start_date); | |
| if (filters.end_date) params.set("end_date", filters.end_date); | |
| return httpRequest<{ items: ManagedImage[]; groups: Array<{ date: string; items: ManagedImage[] }> }>( | |
| `/api/images${params.toString() ? `?${params.toString()}` : ""}`, | |
| ); | |
| } | |
| export async function deleteManagedImages(body: { paths?: string[]; start_date?: string; end_date?: string; all_matching?: boolean }) { | |
| return httpRequest<{ removed: number }>("/api/images/delete", { method: "POST", body }); | |
| } | |
| export async function downloadImages(paths: string[]) { | |
| const response = await request.post("/api/images/download", { paths }, { responseType: "blob" }); | |
| const blob = response.data as Blob; | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = "images.zip"; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| export async function downloadSingleImage(path: string) { | |
| const response = await request.get(`/api/images/download/${path}`, { responseType: "blob" }); | |
| const blob = response.data as Blob; | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = path.split("/").pop() || "image.png"; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| export async function fetchImageTags() { | |
| return httpRequest<{ tags: string[] }>("/api/images/tags"); | |
| } | |
| export async function setImageTags(path: string, tags: string[]) { | |
| return httpRequest<{ ok: boolean; tags: string[] }>("/api/images/tags", { | |
| method: "POST", | |
| body: { path, tags }, | |
| }); | |
| } | |
| export async function deleteImageTag(tag: string) { | |
| return httpRequest<{ ok: boolean; removed_from: number }>(`/api/images/tags/${encodeURIComponent(tag)}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| export async function fetchSystemLogs(filters: { type?: string; start_date?: string; end_date?: string }) { | |
| const params = new URLSearchParams(); | |
| if (filters.type) params.set("type", filters.type); | |
| if (filters.start_date) params.set("start_date", filters.start_date); | |
| if (filters.end_date) params.set("end_date", filters.end_date); | |
| return httpRequest<{ items: SystemLog[] }>(`/api/logs${params.toString() ? `?${params.toString()}` : ""}`); | |
| } | |
| export async function deleteSystemLogs(ids: string[]) { | |
| return httpRequest<{ removed: number }>("/api/logs/delete", { | |
| method: "POST", | |
| body: { ids }, | |
| }); | |
| } | |
| export async function fetchUserKeys() { | |
| return httpRequest<{ items: UserKey[] }>("/api/auth/users"); | |
| } | |
| export async function wechatLogin(code: string) { | |
| return httpRequest<LoginResponse & { | |
| key: string; | |
| credits?: CreditSummary; | |
| wechat?: { openid?: string; nickname?: string; avatar?: string }; | |
| }>("/api/auth/wechat/login", { | |
| method: "POST", | |
| body: { code }, | |
| redirectOnUnauthorized: false, | |
| }); | |
| } | |
| export async function fetchWechatLoginStatus() { | |
| return httpRequest<{ enabled: boolean; auto_register_enabled?: boolean; login_session_duration_hours?: number }>("/api/auth/wechat/status", { | |
| redirectOnUnauthorized: false, | |
| }); | |
| } | |
| export async function fetchBranding() { | |
| return httpRequest<{ branding: BrandingConfig }>("/api/branding", { | |
| redirectOnUnauthorized: false, | |
| }); | |
| } | |
| export async function fetchNormalUserKeys() { | |
| return httpRequest<{ items: UserKey[] }>("/api/auth/normal-users"); | |
| } | |
| export async function createUserKey(name: string) { | |
| return httpRequest<{ item: UserKey; key: string; items: UserKey[] }>("/api/auth/users", { | |
| method: "POST", | |
| body: { name }, | |
| }); | |
| } | |
| export async function createNormalUserKey(name: string) { | |
| return httpRequest<{ item: UserKey; key: string; items: UserKey[] }>("/api/auth/normal-users", { | |
| method: "POST", | |
| body: { name }, | |
| }); | |
| } | |
| export async function updateUserKey(keyId: string, updates: { enabled?: boolean; account_pool_enabled?: boolean; name?: string; key?: string }) { | |
| return httpRequest<{ item: UserKey; items: UserKey[] }>(`/api/auth/users/${keyId}`, { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function updateNormalUserKey(keyId: string, updates: { enabled?: boolean; account_pool_enabled?: boolean; name?: string; key?: string }) { | |
| return httpRequest<{ item: UserKey; items: UserKey[] }>(`/api/auth/normal-users/${keyId}`, { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function deleteUserKey(keyId: string) { | |
| return httpRequest<{ items: UserKey[] }>(`/api/auth/users/${keyId}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| export async function deleteNormalUserKey(keyId: string) { | |
| return httpRequest<{ items: UserKey[] }>(`/api/auth/normal-users/${keyId}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| export async function fetchMyCredits() { | |
| return httpRequest<{ credits: CreditSummary }>("/api/me/credits"); | |
| } | |
| export async function fetchMyRedeemCodes() { | |
| return httpRequest<{ items: RedeemCode[] }>("/api/me/redeem-codes"); | |
| } | |
| export async function fetchMyProfile() { | |
| return httpRequest<{ profile: MyProfile }>("/api/me/profile"); | |
| } | |
| export async function updateMyProfile(name: string) { | |
| return httpRequest<{ profile: MyProfile }>("/api/me/profile", { | |
| method: "POST", | |
| body: { name }, | |
| }); | |
| } | |
| export async function fetchMyApiKey() { | |
| return httpRequest<{ api_key: string }>("/api/me/api-key"); | |
| } | |
| export async function redeemCode(code: string) { | |
| return httpRequest<{ item: RedeemCode; credits: CreditSummary }>("/api/redeem", { | |
| method: "POST", | |
| body: { code }, | |
| }); | |
| } | |
| export async function previewRedeemCode(code: string) { | |
| return httpRequest<{ item: RedeemCode; redeemable: boolean; message: string }>("/api/redeem/preview", { | |
| method: "POST", | |
| body: { code }, | |
| }); | |
| } | |
| export async function fetchShopCodes() { | |
| return httpRequest<{ items: RedeemCode[] }>("/api/shop/codes"); | |
| } | |
| export async function fetchShopPublicConfig() { | |
| return httpRequest<{ purchase_url: string }>("/api/shop/public"); | |
| } | |
| export async function createRedeemCodes(body: { | |
| count: number; | |
| credits: number; | |
| prefix?: string; | |
| batch?: string; | |
| note?: string; | |
| validity_days?: number | null; | |
| credit_expires_days?: number | null; | |
| batch_single_use?: boolean; | |
| code_type?: string; | |
| daily_gift_date?: string; | |
| package_name?: string; | |
| package_duration_days?: number; | |
| package_daily_limit?: number | null; | |
| package_weekly_limit?: number | null; | |
| package_monthly_limit?: number | null; | |
| package_total_limit?: number | null; | |
| }) { | |
| return httpRequest<{ codes: string[]; items: RedeemCode[]; all_items: RedeemCode[] }>("/api/shop/codes", { | |
| method: "POST", | |
| body, | |
| }); | |
| } | |
| export async function disableRedeemCode(codeId: string) { | |
| return httpRequest<{ item: RedeemCode; items: RedeemCode[] }>("/api/shop/codes/disable", { | |
| method: "POST", | |
| body: { code_id: codeId }, | |
| }); | |
| } | |
| export async function enableRedeemCode(codeId: string) { | |
| return httpRequest<{ item: RedeemCode; items: RedeemCode[] }>("/api/shop/codes/enable", { | |
| method: "POST", | |
| body: { code_id: codeId }, | |
| }); | |
| } | |
| export async function deleteRedeemCode(codeId: string) { | |
| return httpRequest<{ item: RedeemCode; items: RedeemCode[] }>("/api/shop/codes/delete", { | |
| method: "POST", | |
| body: { code_id: codeId }, | |
| }); | |
| } | |
| export async function cleanupRedeemCodes(body: { | |
| delete_expired: boolean; | |
| delete_disabled: boolean; | |
| archive_redeemed: boolean; | |
| redeemed_older_than_days: number; | |
| limit: number; | |
| dry_run: boolean; | |
| }) { | |
| return httpRequest<RedeemCodeCleanupResponse>("/api/shop/codes/cleanup", { | |
| method: "POST", | |
| body, | |
| }); | |
| } | |
| export async function fetchShopLedger() { | |
| return httpRequest<{ items: ShopLedgerEntry[] }>("/api/shop/ledger"); | |
| } | |
| export async function adjustNormalUserCredits(keyId: string, delta: number, note = "") { | |
| return httpRequest<{ item: UserKey; items: UserKey[] }>(`/api/shop/users/${keyId}/credits`, { | |
| method: "POST", | |
| body: { delta, note }, | |
| }); | |
| } | |
| export async function fetchRegisterConfig() { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register"); | |
| } | |
| export async function updateRegisterConfig(updates: Partial<RegisterConfig>) { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register", { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function startRegister() { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register/start", { method: "POST" }); | |
| } | |
| export async function stopRegister() { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register/stop", { method: "POST" }); | |
| } | |
| export async function resetRegister() { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register/reset", { method: "POST" }); | |
| } | |
| // ── CPA (CLIProxyAPI) ────────────────────────────────────────────── | |
| export type CPAPool = { | |
| id: string; | |
| name: string; | |
| base_url: string; | |
| import_job?: CPAImportJob | null; | |
| }; | |
| export type CPARemoteFile = { | |
| name: string; | |
| email: string; | |
| }; | |
| export type CPAImportJob = { | |
| job_id: string; | |
| status: "pending" | "running" | "completed" | "failed"; | |
| created_at: string; | |
| updated_at: string; | |
| total: number; | |
| completed: number; | |
| added: number; | |
| skipped: number; | |
| refreshed: number; | |
| failed: number; | |
| errors: Array<{ name: string; error: string }>; | |
| }; | |
| export async function fetchCPAPools() { | |
| return httpRequest<{ pools: CPAPool[] }>("/api/cpa/pools"); | |
| } | |
| export async function createCPAPool(pool: { name: string; base_url: string; secret_key: string }) { | |
| return httpRequest<{ pool: CPAPool; pools: CPAPool[] }>("/api/cpa/pools", { | |
| method: "POST", | |
| body: pool, | |
| }); | |
| } | |
| export async function updateCPAPool( | |
| poolId: string, | |
| updates: { name?: string; base_url?: string; secret_key?: string }, | |
| ) { | |
| return httpRequest<{ pool: CPAPool; pools: CPAPool[] }>(`/api/cpa/pools/${poolId}`, { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function deleteCPAPool(poolId: string) { | |
| return httpRequest<{ pools: CPAPool[] }>(`/api/cpa/pools/${poolId}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| export async function fetchCPAPoolFiles(poolId: string) { | |
| return httpRequest<{ pool_id: string; files: CPARemoteFile[] }>(`/api/cpa/pools/${poolId}/files`); | |
| } | |
| export async function startCPAImport(poolId: string, names: string[]) { | |
| return httpRequest<{ import_job: CPAImportJob | null }>(`/api/cpa/pools/${poolId}/import`, { | |
| method: "POST", | |
| body: { names }, | |
| }); | |
| } | |
| export async function fetchCPAPoolImportJob(poolId: string) { | |
| return httpRequest<{ import_job: CPAImportJob | null }>(`/api/cpa/pools/${poolId}/import`); | |
| } | |
| // ── Sub2API ──────────────────────────────────────────────────────── | |
| export type Sub2APIServer = { | |
| id: string; | |
| name: string; | |
| base_url: string; | |
| email: string; | |
| has_api_key: boolean; | |
| group_id: string; | |
| import_job?: CPAImportJob | null; | |
| }; | |
| export type Sub2APIRemoteAccount = { | |
| id: string; | |
| name: string; | |
| email: string; | |
| plan_type: string; | |
| status: string; | |
| expires_at: string; | |
| has_refresh_token: boolean; | |
| }; | |
| export type Sub2APIRemoteGroup = { | |
| id: string; | |
| name: string; | |
| description: string; | |
| platform: string; | |
| status: string; | |
| account_count: number; | |
| active_account_count: number; | |
| }; | |
| export async function fetchSub2APIServers() { | |
| return httpRequest<{ servers: Sub2APIServer[] }>("/api/sub2api/servers"); | |
| } | |
| export async function createSub2APIServer(server: { | |
| name: string; | |
| base_url: string; | |
| email: string; | |
| password: string; | |
| api_key: string; | |
| group_id: string; | |
| }) { | |
| return httpRequest<{ server: Sub2APIServer; servers: Sub2APIServer[] }>("/api/sub2api/servers", { | |
| method: "POST", | |
| body: server, | |
| }); | |
| } | |
| export async function updateSub2APIServer( | |
| serverId: string, | |
| updates: { | |
| name?: string; | |
| base_url?: string; | |
| email?: string; | |
| password?: string; | |
| api_key?: string; | |
| group_id?: string; | |
| }, | |
| ) { | |
| return httpRequest<{ server: Sub2APIServer; servers: Sub2APIServer[] }>(`/api/sub2api/servers/${serverId}`, { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function fetchSub2APIServerGroups(serverId: string) { | |
| return httpRequest<{ server_id: string; groups: Sub2APIRemoteGroup[] }>( | |
| `/api/sub2api/servers/${serverId}/groups`, | |
| ); | |
| } | |
| export async function deleteSub2APIServer(serverId: string) { | |
| return httpRequest<{ servers: Sub2APIServer[] }>(`/api/sub2api/servers/${serverId}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| export async function fetchSub2APIServerAccounts(serverId: string) { | |
| return httpRequest<{ server_id: string; accounts: Sub2APIRemoteAccount[] }>( | |
| `/api/sub2api/servers/${serverId}/accounts`, | |
| ); | |
| } | |
| export async function startSub2APIImport(serverId: string, accountIds: string[]) { | |
| return httpRequest<{ import_job: CPAImportJob | null }>(`/api/sub2api/servers/${serverId}/import`, { | |
| method: "POST", | |
| body: { account_ids: accountIds }, | |
| }); | |
| } | |
| export async function fetchSub2APIImportJob(serverId: string) { | |
| return httpRequest<{ import_job: CPAImportJob | null }>(`/api/sub2api/servers/${serverId}/import`); | |
| } | |
| // ── Upstream proxy ──────────────────────────────────────────────── | |
| export type ProxySettings = { | |
| enabled: boolean; | |
| url: string; | |
| }; | |
| export type ProxyTestResult = { | |
| ok: boolean; | |
| status: number; | |
| latency_ms: number; | |
| error: string | null; | |
| }; | |
| export async function fetchProxy() { | |
| return httpRequest<{ proxy: ProxySettings }>("/api/proxy"); | |
| } | |
| export async function updateProxy(updates: { enabled?: boolean; url?: string }) { | |
| return httpRequest<{ proxy: ProxySettings }>("/api/proxy", { | |
| method: "POST", | |
| body: updates, | |
| }); | |
| } | |
| export async function testProxy(url?: string) { | |
| return httpRequest<{ result: ProxyTestResult }>("/api/proxy/test", { | |
| method: "POST", | |
| body: { url: url ?? "" }, | |
| }); | |
| } | |