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 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; 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 | 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; [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; 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_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; 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; }>; daily_credits: number; daily_credits_remaining: number; daily_credits_used: number | null; daily_reserved_credits: number; daily_credits_date: string; }; export type RedeemCode = { id: string; prefix: string; credits: number; status: "active" | "redeemed" | "disabled" | string; batch?: string | null; batch_id?: string | null; batch_single_use?: boolean; note?: string | null; created_at?: string | null; expires_at?: string | null; credit_expires_days?: number | null; credit_expires_at?: string | null; 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; }; export type ShopLedgerEntry = { id: string; time: string; type: string; user_id?: string; code_id?: 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>; }; 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("/auth/login", { method: "POST", body: {}, headers: { Authorization: `Bearer ${normalizedAuthKey}`, }, redirectOnUnauthorized: false, }); } export async function fetchAccounts() { return httpRequest("/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("/api/accounts", { method: "POST", body: { tokens, ...(accounts.length > 0 ? { accounts } : {}), }, }); } export async function startAccountEmailLoginImport(email: string) { return httpRequest("/api/accounts/email-login/start", { method: "POST", body: { email }, }); } export async function verifyAccountEmailLoginImport(sessionId: string, code: string) { return httpRequest("/api/accounts/email-login/verify", { method: "POST", body: { session_id: sessionId, code }, }); } export async function deleteAccounts(tokens: string[]) { return httpRequest("/api/accounts", { method: "DELETE", body: { tokens }, }); } export async function refreshAccounts(accessTokens: string[]) { return httpRequest("/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({ 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("/api/accounts/update", { method: "POST", body: { access_token: accessToken, ...updates, }, }); } export async function generateImage(prompt: string, model?: ImageModel, size?: string) { return httpRequest( "/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( "/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("/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("/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(`/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("/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 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; }) { 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 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) { 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 ?? "" }, }); }