Spaces:
Paused
Paused
File size: 8,463 Bytes
4283ada 1f4cfcb b7e2b8e 4283ada b7e2b8e 4283ada b7e2b8e 4283ada 3482523 4283ada b7e2b8e 4283ada b7e2b8e 462f5f6 f82773a b7e2b8e 4283ada 40b21ae a08d7b4 4283ada 1f4cfcb 40b21ae 3df0958 4af1754 ad9c2d9 7c59963 4283ada 40b21ae b6bcc3c 40b21ae ceeba19 40b21ae ceeba19 a08d7b4 40b21ae 4283ada 2ddc3ee 8e8726e 40b21ae 4283ada 5aff1af 4283ada 5aff1af 3df0958 4283ada 3df0958 95df048 a0dfc48 3df0958 478c16c 3df0958 e6cc775 090976a a0dfc48 876283e a0dfc48 4283ada a4ea50b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | const BASE = '/api'
export function getApiKey() {
return localStorage.getItem('autoteam_api_key') || ''
}
export function setApiKey(key) {
localStorage.setItem('autoteam_api_key', key)
}
export function clearApiKey() {
localStorage.removeItem('autoteam_api_key')
}
async function request(method, path, body = null) {
const headers = { 'Content-Type': 'application/json' }
const key = getApiKey()
if (key) {
headers['Authorization'] = `Bearer ${key}`
}
const opts = { method, headers }
if (body) opts.body = JSON.stringify(body)
const resp = await fetch(`${BASE}${path}`, opts)
let data
try {
data = await resp.json()
} catch {
const err = new Error(`HTTP ${resp.status}: 服务器返回了非 JSON 响应`)
err.status = resp.status
throw err
}
if (!resp.ok) {
const msg = data?.detail?.message || data?.detail || `HTTP ${resp.status}`
const err = new Error(msg)
err.status = resp.status
throw err
}
return data
}
export const api = {
checkAuth: () => request('GET', '/auth/check'),
getSetupStatus: () => request('GET', '/setup/status'),
saveSetup: (config) => request('POST', '/setup/save', config),
// SPEC-1 §4.2 — 三步分阶段 mail provider 探测
probeMailProvider: (payload) => request('POST', '/mail-provider/probe', payload),
getStatus: () => request('GET', '/status'),
getAdminStatus: () => request('GET', '/admin/status'),
getMainCodexStatus: () => request('GET', '/main-codex/status'),
getManualAccountStatus: () => request('GET', '/manual-account/status'),
getAccounts: () => request('GET', '/accounts'),
getActiveAccounts: () => request('GET', '/accounts/active'),
getStandbyAccounts: () => request('GET', '/accounts/standby'),
bulkDisableAccounts: (emails) => request('POST', '/accounts/bulk/disable', { emails }),
bulkEnableAccounts: (emails) => request('POST', '/accounts/bulk/enable', { emails }),
disableAccount: (email) => request('POST', `/accounts/${encodeURIComponent(email)}/disable`),
enableAccount: (email) => request('POST', `/accounts/${encodeURIComponent(email)}/enable`),
deleteAccount: (email) => request('DELETE', `/accounts/${encodeURIComponent(email)}`),
deleteAccountsBatch: (emails, continueOnError = true) => request('POST', '/accounts/delete-batch', { emails, continue_on_error: continueOnError }),
loginAccount: (email) => request('POST', '/accounts/login', { email }),
getCodexAuth: (email) => request('GET', `/accounts/${encodeURIComponent(email)}/codex-auth`),
kickAccount: (email) => request('POST', `/accounts/${encodeURIComponent(email)}/kick`),
getCpaFiles: () => request('GET', '/cpa/files'),
startAdminLogin: (email) => request('POST', '/admin/login/start', { email }),
submitAdminSession: (email, sessionToken) => request('POST', '/admin/login/session', { email, session_token: sessionToken }),
submitAdminPassword: (password) => request('POST', '/admin/login/password', { password }),
submitAdminCode: (code) => request('POST', '/admin/login/code', { code }),
submitAdminWorkspace: (optionId) => request('POST', '/admin/login/workspace', { option_id: optionId }),
cancelAdminLogin: () => request('POST', '/admin/login/cancel'),
logoutAdmin: () => request('POST', '/admin/logout'),
startMainCodexSync: () => request('POST', '/main-codex/start'),
startMainCodexLogin: () => request('POST', '/main-codex/login'),
submitMainCodexPassword: (password) => request('POST', '/main-codex/password', { password }),
submitMainCodexCode: (code) => request('POST', '/main-codex/code', { code }),
cancelMainCodexSync: () => request('POST', '/main-codex/cancel'),
deleteMainCodexRemoteFiles: () => request('POST', '/main-codex/delete-remote-files'),
deleteMainCodexFromRemoteFiles: () => request('POST', '/main-codex/delete-remote-files'),
deleteMainCodexCpa: () => request('POST', '/main-codex/delete-cpa'),
startManualAccount: () => request('POST', '/manual-account/start'),
submitManualAccountCallback: (redirectUrl) => request('POST', '/manual-account/callback', { redirect_url: redirectUrl }),
cancelManualAccount: () => request('POST', '/manual-account/cancel'),
postSync: () => request('POST', '/sync'),
postSyncFromCpa: () => request('POST', '/sync/from-cpa'),
postSyncAccounts: () => request('POST', '/sync/accounts'),
postSyncMainCodex: () => request('POST', '/sync/main-codex'),
startRotate: (target = 3) => request('POST', '/tasks/rotate', { target }),
startCheck: () => request('POST', '/tasks/check'),
startAdd: () => request('POST', '/tasks/add'),
startFill: (target = 3) => request('POST', '/tasks/fill', { target, leave_workspace: false }),
startFillPersonal: (count = 1) => request('POST', '/tasks/fill', { target: count, leave_workspace: true }),
startCleanup: (maxSeats = null) => request('POST', '/tasks/cleanup', { max_seats: maxSeats }),
getTasks: () => request('GET', '/tasks'),
getTask: (id) => request('GET', `/tasks/${id}`),
cancelTask: () => request('POST', '/tasks/cancel'),
getAutoCheckConfig: () => request('GET', '/config/auto-check'),
setAutoCheckConfig: (cfg) => request('PUT', '/config/auto-check', cfg),
getRegisterDomain: () => request('GET', '/config/register-domain'),
setRegisterDomain: (domain, verify = true) => request('PUT', '/config/register-domain', { domain, verify }),
// SPEC-2 — 邀请席位偏好(default/codex)
getPreferredSeatType: () => request('GET', '/config/preferred-seat-type'),
putPreferredSeatType: (value) => request('PUT', '/config/preferred-seat-type', { value }),
// SPEC-2 — sync_account_states 被踢探测节流(并发上限 + 冷却分钟)
getSyncProbe: () => request('GET', '/config/sync-probe'),
putSyncProbe: (payload) => request('PUT', '/config/sync-probe', payload),
getRegisterFailures: (limit = 50) => request('GET', `/register-failures?limit=${limit}`),
// Round 8 — SPEC-2 v1.5 §6.2:母号订阅健康度独立端点
getMasterHealth: (forceRefresh = false) => request(
'GET', `/admin/master-health${forceRefresh ? '?force_refresh=1' : ''}`,
),
// Round 11 — realtime-probe.md v1.0 §2:子号实时探活 + 模型清单
// POST /api/accounts/{email}/probe — 强制刷新单个子号的可用性 (cheap_codex_smoke + check_codex_quota)
// 副作用:落 last_quota_check_at,绕过 sync_account_states 30min 节流
probeAccount: (email, forceCodexSmoke = true) => request(
'POST', `/accounts/${encodeURIComponent(email)}/probe`,
{ force_codex_smoke: forceCodexSmoke },
),
// GET /api/accounts/{email}/models — 用 access_token 调 /backend-api/models 拿可用模型列表
getAccountModels: (email) => request(
'GET', `/accounts/${encodeURIComponent(email)}/models`,
),
getTeamMembers: () => request('GET', '/team/members'),
removeTeamMember: (payload) => request('POST', '/team/members/remove', payload),
getLogs: (limit = 100, since = 0) => request('GET', `/logs?limit=${limit}&since=${since}`),
}
// Round 7 FR-D7 — 把后端 task["error"] 字符串关键字解析为 UI 可消费的语义错误。
// Round 6 P1.3 决策让 manager 在 task["error"] 中携带 `phone_required` / `register_blocked`
// 关键字串。前端 UI 在 getTask 轮询命中 task.error 后调用本函数,根据 category 给出
// 友好 toast 而不是裸字符串。
//
// 入参:task — 后端 GET /api/tasks/{id} 的响应对象(含可选 error 字段)
// 返回:
// { category: 'phone_required', friendly_message: '该账号需要绑定手机才能完成 OAuth', detail }
// { category: 'register_blocked', friendly_message: '...', detail }
// { category: 'generic', friendly_message: '<原 error 字符串>', detail }
// null — task 没有 error 字段(任务进行中或成功)
export function parseTaskError(task) {
if (!task || !task.error) return null
const errStr = String(task.error)
const lower = errStr.toLowerCase()
if (lower.includes('phone_required')) {
return {
category: 'phone_required',
friendly_message: '该账号需要绑定手机才能完成 OAuth',
detail: errStr,
}
}
if (lower.includes('register_blocked')) {
return {
category: 'register_blocked',
friendly_message: '该账号注册被阻断,请检查 OAuth 状态',
detail: errStr,
}
}
return {
category: 'generic',
friendly_message: errStr,
detail: errStr,
}
}
|