Spaces:
Paused
Paused
| 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"; | |
| export type Account = { | |
| access_token: string; | |
| type: AccountType; | |
| status: AccountStatus; | |
| quota: number; | |
| image_quota_unknown?: boolean; | |
| email?: string | null; | |
| password?: 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; | |
| }; | |
| 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 SettingsConfig = { | |
| proxy: string; | |
| 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; | |
| auto_remove_invalid_accounts?: boolean; | |
| auto_remove_rate_limited_accounts?: boolean; | |
| log_levels?: string[]; | |
| backup?: BackupSettings; | |
| backup_state?: BackupState; | |
| [key: string]: unknown; | |
| }; | |
| 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; | |
| 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; | |
| 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; | |
| }; | |
| export type UserKey = { | |
| id: string; | |
| name: string; | |
| role: "user"; | |
| enabled: boolean; | |
| created_at: string | null; | |
| last_used_at: string | null; | |
| }; | |
| export type RegisterConfig = { | |
| enabled: boolean; | |
| mail: { | |
| request_timeout: number; | |
| wait_timeout: number; | |
| wait_interval: number; | |
| providers: Array<Record<string, unknown>>; | |
| }; | |
| hero_sms: { | |
| enabled: boolean; | |
| api_key: string; | |
| service: string; | |
| country: number; | |
| country_pool: number[]; | |
| country_blacklist: number[]; | |
| operator: string; | |
| wait_timeout: number; | |
| poll_interval: number; | |
| reuse_activation_id: string; | |
| reuse_phone: string; | |
| auto_buy: boolean; | |
| min_price_usd: number | string; | |
| max_price_usd: number | string; | |
| cancel_on_send_fail: boolean; | |
| }; | |
| 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 type OpenAIKeyStatus = "unchecked" | "ok" | "invalid" | "rate_limited" | "forbidden" | "error" | string; | |
| export type OpenAIKeyItem = { | |
| id: string; | |
| name: string; | |
| key_hint: string; | |
| status: OpenAIKeyStatus; | |
| http_status?: number | null; | |
| models_count: number; | |
| sample_models: string[]; | |
| last_error?: string | null; | |
| last_checked_at?: string | null; | |
| created_at: string; | |
| updated_at: string; | |
| }; | |
| type OpenAIKeyListResponse = { | |
| items: OpenAIKeyItem[]; | |
| }; | |
| type OpenAIKeyMutationResponse = { | |
| item?: OpenAIKeyItem; | |
| items: OpenAIKeyItem[]; | |
| }; | |
| 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 createAccounts(tokens: string[]) { | |
| return httpRequest<AccountMutationResponse>("/api/accounts", { | |
| method: "POST", | |
| body: { tokens }, | |
| }); | |
| } | |
| 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 }, | |
| }); | |
| } | |
| export async function updateAccount( | |
| accessToken: string, | |
| updates: { | |
| type?: AccountType; | |
| status?: AccountStatus; | |
| quota?: number; | |
| }, | |
| ) { | |
| 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) { | |
| return httpRequest<ImageTask>("/api/image-tasks/generations", { | |
| method: "POST", | |
| body: { | |
| client_task_id: clientTaskId, | |
| prompt, | |
| ...(model ? { model } : {}), | |
| ...(size ? { size } : {}), | |
| }, | |
| }); | |
| } | |
| export async function createImageEditTask( | |
| clientTaskId: string, | |
| 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("client_task_id", clientTaskId); | |
| formData.append("prompt", prompt); | |
| if (model) { | |
| formData.append("model", model); | |
| } | |
| if (size) { | |
| formData.append("size", size); | |
| } | |
| 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 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 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 createUserKey(name: string) { | |
| return httpRequest<{ item: UserKey; key: string; items: UserKey[] }>("/api/auth/users", { | |
| method: "POST", | |
| body: { name }, | |
| }); | |
| } | |
| export async function updateUserKey(keyId: string, updates: { enabled?: boolean; name?: string; key?: string }) { | |
| return httpRequest<{ item: UserKey; items: UserKey[] }>(`/api/auth/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 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 startCodexRegister() { | |
| return httpRequest<{ register: RegisterConfig }>("/api/register/codex/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" }); | |
| } | |
| // ── Official OpenAI API Keys ────────────────────────────────────── | |
| export async function fetchOpenAIKeys() { | |
| return httpRequest<OpenAIKeyListResponse>("/api/openai-keys"); | |
| } | |
| export async function createOpenAIKey(name: string, key: string, check = true) { | |
| return httpRequest<Required<Pick<OpenAIKeyMutationResponse, "item">> & OpenAIKeyMutationResponse>("/api/openai-keys", { | |
| method: "POST", | |
| body: { name, key, check }, | |
| }); | |
| } | |
| export async function checkOpenAIKey(keyId: string) { | |
| return httpRequest<Required<Pick<OpenAIKeyMutationResponse, "item">> & OpenAIKeyMutationResponse>( | |
| `/api/openai-keys/${keyId}/check`, | |
| { method: "POST" }, | |
| ); | |
| } | |
| export async function deleteOpenAIKey(keyId: string) { | |
| return httpRequest<OpenAIKeyListResponse>(`/api/openai-keys/${keyId}`, { | |
| method: "DELETE", | |
| }); | |
| } | |
| // ── 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 ?? "" }, | |
| }); | |
| } | |