Spaces:
Paused
Paused
BestLemoon commited on
Commit ·
40b21ae
1
Parent(s): 5b1588f
feat: 支持管理员交互式登录与主号 Codex 同步
Browse files- .env.example +0 -4
- .gitignore +1 -0
- README.md +45 -7
- src/autoteam/account_ops.py +163 -0
- src/autoteam/admin_state.py +136 -0
- src/autoteam/api.py +429 -12
- src/autoteam/chatgpt_api.py +509 -110
- src/autoteam/codex_auth.py +650 -96
- src/autoteam/config.py +0 -1
- src/autoteam/cpa_sync.py +21 -0
- src/autoteam/manager.py +309 -45
- src/autoteam/web/dist/assets/index-BQe62tYy.js +0 -17
- src/autoteam/web/dist/assets/index-CqIsKPRy.js +18 -0
- src/autoteam/web/dist/assets/{index-CNWPOsK5.css → index-DD5HUIdi.css} +1 -1
- src/autoteam/web/dist/index.html +2 -2
- web/src/App.vue +35 -8
- web/src/api.js +15 -0
- web/src/components/Dashboard.vue +56 -1
- web/src/components/Settings.vue +480 -33
- web/src/components/TaskPanel.vue +14 -4
.env.example
CHANGED
|
@@ -4,10 +4,6 @@ CLOUDMAIL_EMAIL=admin@example.com
|
|
| 4 |
CLOUDMAIL_PASSWORD=your_password
|
| 5 |
CLOUDMAIL_DOMAIN=@example.com
|
| 6 |
|
| 7 |
-
# ChatGPT Team
|
| 8 |
-
CHATGPT_ACCOUNT_ID=your_account_id
|
| 9 |
-
CHATGPT_WORKSPACE_NAME=your_workspace_name
|
| 10 |
-
|
| 11 |
# CPA (CLIProxyAPI)
|
| 12 |
CPA_URL=http://127.0.0.1:8317
|
| 13 |
CPA_KEY=your_key
|
|
|
|
| 4 |
CLOUDMAIL_PASSWORD=your_password
|
| 5 |
CLOUDMAIL_DOMAIN=@example.com
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# CPA (CLIProxyAPI)
|
| 8 |
CPA_URL=http://127.0.0.1:8317
|
| 9 |
CPA_KEY=your_key
|
.gitignore
CHANGED
|
@@ -7,6 +7,7 @@ screenshots/
|
|
| 7 |
auth_state/
|
| 8 |
auths/
|
| 9 |
accounts.json
|
|
|
|
| 10 |
session
|
| 11 |
bearer_token
|
| 12 |
test.py
|
|
|
|
| 7 |
auth_state/
|
| 8 |
auths/
|
| 9 |
accounts.json
|
| 10 |
+
state.json
|
| 11 |
session
|
| 12 |
bearer_token
|
| 13 |
test.py
|
README.md
CHANGED
|
@@ -55,17 +55,20 @@ cp .env.example .env # 复制配置模板,填入实际值
|
|
| 55 |
| 配置项 | 说明 |
|
| 56 |
|--------|------|
|
| 57 |
| **CloudMail** | 临时邮箱服务地址和凭据 |
|
| 58 |
-
| **ChatGPT** | Team Account ID(从 ChatGPT admin 页面获取),Workspace 名称可自动检测 |
|
| 59 |
| **CPA** | CLIProxyAPI 地址和管理密钥 |
|
| 60 |
| **AUTO_CHECK_THRESHOLD** | 额度低于此百分比触发轮转,默认 `10`(可在 Web 面板修改) |
|
| 61 |
| **AUTO_CHECK_INTERVAL** | 巡检间隔(秒),默认 `300`(5 分钟) |
|
| 62 |
| **AUTO_CHECK_MIN_LOW** | 至少几个账号低于阈值才触发轮转,默认 `2` |
|
| 63 |
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
### 使用
|
| 71 |
|
|
@@ -75,6 +78,8 @@ uv run autoteam <command> [args]
|
|
| 75 |
|
| 76 |
| 命令 | 说明 |
|
| 77 |
|------|------|
|
|
|
|
|
|
|
| 78 |
| `status` | 查看所有账号状态(自动同步 Team 实际成员) |
|
| 79 |
| `check` | 检查 active 账号额度,低于阈值标记 exhausted,token 失效按历史额度判断 |
|
| 80 |
| `rotate [N]` | 智能轮转:检查额度 → 移出低于阈值的 → 验证旧号额度后复用 → 补满到 N 个(默认 5) |
|
|
@@ -90,6 +95,25 @@ uv run autoteam <command> [args]
|
|
| 90 |
uv run autoteam rotate
|
| 91 |
```
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
### HTTP API
|
| 94 |
|
| 95 |
启动 API 服务器后,所有管理功能均可通过 HTTP 调用,方便对接定时任务平台、Web 面板等外部系统。
|
|
@@ -111,7 +135,20 @@ uv run autoteam api --port 9000 # 自定义端口
|
|
| 111 |
| GET | `/api/accounts` | 所有账号列表 |
|
| 112 |
| GET | `/api/accounts/active` | 活跃账号 |
|
| 113 |
| GET | `/api/accounts/standby` | 待命账号 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
| POST | `/api/sync` | 同步认证文件到 CPA |
|
|
|
|
| 115 |
| GET | `/api/cpa/files` | CPA 认证文件列表 |
|
| 116 |
| GET | `/api/config/auto-check` | 获取巡检配置 |
|
| 117 |
| PUT | `/api/config/auto-check` | 修改巡检配置(运行时生效) |
|
|
@@ -153,12 +190,13 @@ curl http://localhost:8787/api/tasks/a1b2c3d4e5f6
|
|
| 153 |
|
| 154 |
**面板功能:**
|
| 155 |
|
|
|
|
| 156 |
- **Dashboard** — 账号统计卡片(活跃/待命/用完/总计)+ 账号表格(实时额度、颜色标签、重置时间)
|
| 157 |
-
- **操作面板** — 一键执行轮转、检查额度、补满、添加、清理、同步 CPA
|
| 158 |
- **任���历史** — 所有任务记录,实时状态跟踪(等待中/执行中/已完成/失败),耗时统计
|
| 159 |
- **巡检设置** — 可视化配置巡检间隔、额度阈值、触发账号数,修改后运行时立即生效
|
| 160 |
|
| 161 |
-
面板每 10 分钟自动刷新数据,有任务执行时切换为
|
| 162 |
|
| 163 |
**前端开发(可选):**
|
| 164 |
|
|
|
|
| 55 |
| 配置项 | 说明 |
|
| 56 |
|--------|------|
|
| 57 |
| **CloudMail** | 临时邮箱服务地址和凭据 |
|
|
|
|
| 58 |
| **CPA** | CLIProxyAPI 地址和管理密钥 |
|
| 59 |
| **AUTO_CHECK_THRESHOLD** | 额度低于此百分比触发轮转,默认 `10`(可在 Web 面板修改) |
|
| 60 |
| **AUTO_CHECK_INTERVAL** | 巡检间隔(秒),默认 `300`(5 分钟) |
|
| 61 |
| **AUTO_CHECK_MIN_LOW** | 至少几个账号低于阈值才触发轮转,默认 `2` |
|
| 62 |
|
| 63 |
+
管理员登录改为首次启动后在 Web 页面内完成,系统会自动保存:
|
| 64 |
|
| 65 |
+
- 主号邮箱
|
| 66 |
+
- session token
|
| 67 |
+
- 主号密码(仅当登录流程要求输入密码时保存,供主号 Codex 同步复用)
|
| 68 |
+
- Workspace / account ID
|
| 69 |
+
- workspace 名称
|
| 70 |
+
|
| 71 |
+
以上信息统一写入项目根目录下的 `state.json` 文件,不再额外保存 `session`。
|
| 72 |
|
| 73 |
### 使用
|
| 74 |
|
|
|
|
| 78 |
|
| 79 |
| 命令 | 说明 |
|
| 80 |
|------|------|
|
| 81 |
+
| `admin-login [--email]` | 交互式完成管理员主号登录,按提示输入邮箱、密码或验证码 |
|
| 82 |
+
| `main-codex-sync` | 交互式同步主号 Codex 到 CPA,按提示输入密码或验证码 |
|
| 83 |
| `status` | 查看所有账号状态(自动同步 Team 实际成员) |
|
| 84 |
| `check` | 检查 active 账号额度,低于阈值标记 exhausted,token 失效按历史额度判断 |
|
| 85 |
| `rotate [N]` | 智能轮转:检查额度 → 移出低于阈值的 → 验证旧号额度后复用 → 补满到 N 个(默认 5) |
|
|
|
|
| 95 |
uv run autoteam rotate
|
| 96 |
```
|
| 97 |
|
| 98 |
+
首次配置管理员主号也可以直接用命令行:
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
uv run autoteam admin-login
|
| 102 |
+
uv run autoteam admin-login --email you@example.com
|
| 103 |
+
uv run autoteam main-codex-sync
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
### 主号 Codex Sync
|
| 107 |
+
|
| 108 |
+
`main-codex-sync` 用于把管理员主号的 Codex 登录态单独同步到 CPA。
|
| 109 |
+
|
| 110 |
+
- 前置条件:先完成 `admin-login`,让系统拿到主号邮箱、session token、workspace/account ID
|
| 111 |
+
- 交互方式:命令行或 Web 都可以发起;如果流程要求密码或邮箱验证码,会继续提示输入
|
| 112 |
+
- 同步结果:成功后会生成 `auths/codex-main-*.json`,并立即推送到 CPA
|
| 113 |
+
- 作用范围:这是主号专用的 Codex 认证,不会加入账号轮转池,也不会写入普通子号的账号列表
|
| 114 |
+
|
| 115 |
+
普通的 `sync` 只是把当前已有的认证文件重新推送到 CPA;`main-codex-sync` 则是在需要时重新完成一次主号 Codex 登录,并把新的主号认证同步过去。
|
| 116 |
+
|
| 117 |
### HTTP API
|
| 118 |
|
| 119 |
启动 API 服务器后,所有管理功能均可通过 HTTP 调用,方便对接定时任务平台、Web 面板等外部系统。
|
|
|
|
| 135 |
| GET | `/api/accounts` | 所有账号列表 |
|
| 136 |
| GET | `/api/accounts/active` | 活跃账号 |
|
| 137 |
| GET | `/api/accounts/standby` | 待命账号 |
|
| 138 |
+
| GET | `/api/admin/status` | 当前管理员登录状态 |
|
| 139 |
+
| POST | `/api/admin/login/start` | 开始管理员登录 `{"email":"admin@example.com"}` |
|
| 140 |
+
| POST | `/api/admin/login/password` | 提交密码 `{"password":"..."}` |
|
| 141 |
+
| POST | `/api/admin/login/code` | 提交邮箱验证码 `{"code":"123456"}` |
|
| 142 |
+
| POST | `/api/admin/login/workspace` | 提交组织选择 `{"option_id":"0"}` |
|
| 143 |
+
| POST | `/api/admin/login/cancel` | 取消正在进行的管理员登录 |
|
| 144 |
+
| POST | `/api/admin/logout` | 清除已保存的管理员登录态 |
|
| 145 |
+
| GET | `/api/main-codex/status` | 当前主号 Codex 同步状态 |
|
| 146 |
+
| POST | `/api/main-codex/start` | 开始主号 Codex 同步 |
|
| 147 |
+
| POST | `/api/main-codex/password` | 提交主号密码 `{"password":"..."}` |
|
| 148 |
+
| POST | `/api/main-codex/code` | 提交主号验证码 `{"code":"123456"}` |
|
| 149 |
+
| POST | `/api/main-codex/cancel` | 取消正在进行的主号 Codex 同步 |
|
| 150 |
| POST | `/api/sync` | 同步认证文件到 CPA |
|
| 151 |
+
| POST | `/api/sync/main-codex` | 兼容旧接口,等价于 `/api/main-codex/start` |
|
| 152 |
| GET | `/api/cpa/files` | CPA 认证文件列表 |
|
| 153 |
| GET | `/api/config/auto-check` | 获取巡检配置 |
|
| 154 |
| PUT | `/api/config/auto-check` | 修改巡检配置(运行时生效) |
|
|
|
|
| 190 |
|
| 191 |
**面板功能:**
|
| 192 |
|
| 193 |
+
- **管理员登录** — 首次启动直接输入主号邮箱,按页面提示继续提交密码或邮箱验证码,成功后自动保存登录态
|
| 194 |
- **Dashboard** — 账号统计卡片(活跃/待命/用完/总计)+ 账号表格(实时额度、颜色标签、重置时间)
|
| 195 |
+
- **操作面板** — 一键执行轮转、检查额度、补满、添加、清理、同步 CPA、同步主号 Codex;未完成管理员登录时自动禁用
|
| 196 |
- **任���历史** — 所有任务记录,实时状态跟踪(等待中/执行中/已完成/失败),耗时统计
|
| 197 |
- **巡检设置** — 可视化配置巡检间隔、额度阈值、触发账号数,修改后运行时立即生效
|
| 198 |
|
| 199 |
+
面板每 10 分钟自动刷新数据,有任务执行或管理员登录进行中时切换为 10 秒轮询,结束后自动恢复。
|
| 200 |
|
| 201 |
**前端开发(可选):**
|
| 202 |
|
src/autoteam/account_ops.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""账号资源清理与远端对账操作。"""
|
| 2 |
+
|
| 3 |
+
import logging
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
from autoteam.accounts import find_account, load_accounts, save_accounts
|
| 7 |
+
from autoteam.admin_state import get_chatgpt_account_id
|
| 8 |
+
from autoteam.cloudmail import CloudMailClient
|
| 9 |
+
from autoteam.cpa_sync import delete_from_cpa, list_cpa_files, sync_to_cpa
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
| 14 |
+
AUTH_DIR = PROJECT_ROOT / "auths"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def fetch_team_state(chatgpt_api):
|
| 18 |
+
"""读取 Team 成员和邀请状态。"""
|
| 19 |
+
account_id = get_chatgpt_account_id()
|
| 20 |
+
members = []
|
| 21 |
+
invites = []
|
| 22 |
+
|
| 23 |
+
users_resp = chatgpt_api._api_fetch("GET", f"/backend-api/accounts/{account_id}/users")
|
| 24 |
+
if users_resp["status"] == 200:
|
| 25 |
+
import json
|
| 26 |
+
|
| 27 |
+
data = json.loads(users_resp["body"])
|
| 28 |
+
members = data.get("items", data.get("users", data.get("members", [])))
|
| 29 |
+
|
| 30 |
+
invites_resp = chatgpt_api._api_fetch("GET", f"/backend-api/accounts/{account_id}/invites")
|
| 31 |
+
if invites_resp["status"] == 200:
|
| 32 |
+
import json
|
| 33 |
+
|
| 34 |
+
data = json.loads(invites_resp["body"])
|
| 35 |
+
invites = data if isinstance(data, list) else data.get("invites", data.get("account_invites", []))
|
| 36 |
+
|
| 37 |
+
return members, invites
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def delete_managed_account(
|
| 41 |
+
email,
|
| 42 |
+
*,
|
| 43 |
+
remove_remote=True,
|
| 44 |
+
remove_cloudmail=True,
|
| 45 |
+
sync_cpa_after=True,
|
| 46 |
+
chatgpt_api=None,
|
| 47 |
+
mail_client=None,
|
| 48 |
+
remote_state=None,
|
| 49 |
+
):
|
| 50 |
+
"""
|
| 51 |
+
删除本地管理账号及其衍生资源。
|
| 52 |
+
返回 cleanup 摘要,设计为幂等操作。
|
| 53 |
+
"""
|
| 54 |
+
email_l = email.lower()
|
| 55 |
+
accounts = load_accounts()
|
| 56 |
+
acc = find_account(accounts, email)
|
| 57 |
+
|
| 58 |
+
cleanup = {
|
| 59 |
+
"local_record": False,
|
| 60 |
+
"local_auth_files": [],
|
| 61 |
+
"cpa_files": [],
|
| 62 |
+
"team_member_removed": False,
|
| 63 |
+
"invite_removed": False,
|
| 64 |
+
"cloudmail_deleted": False,
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
members = []
|
| 68 |
+
invites = []
|
| 69 |
+
own_chatgpt = None
|
| 70 |
+
own_mail_client = None
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
account_id = get_chatgpt_account_id()
|
| 74 |
+
if remove_remote:
|
| 75 |
+
if remote_state is not None:
|
| 76 |
+
members, invites = remote_state
|
| 77 |
+
else:
|
| 78 |
+
if chatgpt_api is None:
|
| 79 |
+
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 80 |
+
|
| 81 |
+
own_chatgpt = ChatGPTTeamAPI()
|
| 82 |
+
own_chatgpt.start()
|
| 83 |
+
chatgpt_api = own_chatgpt
|
| 84 |
+
members, invites = fetch_team_state(chatgpt_api)
|
| 85 |
+
|
| 86 |
+
member_matches = [
|
| 87 |
+
m for m in members if (m.get("email", "") or "").lower() == email_l
|
| 88 |
+
]
|
| 89 |
+
for member in member_matches:
|
| 90 |
+
user_id = member.get("user_id") or member.get("id")
|
| 91 |
+
if not user_id:
|
| 92 |
+
continue
|
| 93 |
+
result = chatgpt_api._api_fetch(
|
| 94 |
+
"DELETE",
|
| 95 |
+
f"/backend-api/accounts/{account_id}/users/{user_id}",
|
| 96 |
+
)
|
| 97 |
+
if result["status"] not in (200, 204):
|
| 98 |
+
raise RuntimeError(f"移除 Team 成员失败: {email}")
|
| 99 |
+
cleanup["team_member_removed"] = True
|
| 100 |
+
|
| 101 |
+
invite_matches = []
|
| 102 |
+
for inv in invites:
|
| 103 |
+
inv_email = (inv.get("email_address") or inv.get("email") or "").lower()
|
| 104 |
+
if inv_email == email_l:
|
| 105 |
+
invite_matches.append(inv)
|
| 106 |
+
|
| 107 |
+
for inv in invite_matches:
|
| 108 |
+
invite_id = inv.get("id")
|
| 109 |
+
if not invite_id:
|
| 110 |
+
continue
|
| 111 |
+
result = chatgpt_api._api_fetch(
|
| 112 |
+
"DELETE",
|
| 113 |
+
f"/backend-api/accounts/{account_id}/invites/{invite_id}",
|
| 114 |
+
)
|
| 115 |
+
if result["status"] not in (200, 204):
|
| 116 |
+
raise RuntimeError(f"取消 Team 邀请失败: {email}")
|
| 117 |
+
cleanup["invite_removed"] = True
|
| 118 |
+
|
| 119 |
+
auth_candidates = set()
|
| 120 |
+
if acc and acc.get("auth_file"):
|
| 121 |
+
auth_candidates.add(Path(acc["auth_file"]))
|
| 122 |
+
auth_candidates.update(AUTH_DIR.glob(f"codex-{email}-*.json"))
|
| 123 |
+
|
| 124 |
+
for path in sorted(auth_candidates):
|
| 125 |
+
if path.exists():
|
| 126 |
+
path.unlink()
|
| 127 |
+
cleanup["local_auth_files"].append(path.name)
|
| 128 |
+
logger.info("[账号] 已删除本地 auth: %s", path.name)
|
| 129 |
+
|
| 130 |
+
cpa_names = set(cleanup["local_auth_files"])
|
| 131 |
+
for item in list_cpa_files():
|
| 132 |
+
item_email = (item.get("email") or "").lower()
|
| 133 |
+
item_name = item.get("name") or ""
|
| 134 |
+
if item_email == email_l or item_name in cpa_names:
|
| 135 |
+
if delete_from_cpa(item_name):
|
| 136 |
+
cleanup["cpa_files"].append(item_name)
|
| 137 |
+
|
| 138 |
+
if acc:
|
| 139 |
+
accounts = [item for item in accounts if item["email"].lower() != email_l]
|
| 140 |
+
save_accounts(accounts)
|
| 141 |
+
cleanup["local_record"] = True
|
| 142 |
+
logger.info("[账号] 已删除本地记录: %s", email)
|
| 143 |
+
|
| 144 |
+
cloudmail_account_id = acc.get("cloudmail_account_id")
|
| 145 |
+
if remove_cloudmail and cloudmail_account_id:
|
| 146 |
+
try:
|
| 147 |
+
if mail_client is None:
|
| 148 |
+
own_mail_client = CloudMailClient()
|
| 149 |
+
own_mail_client.login()
|
| 150 |
+
mail_client = own_mail_client
|
| 151 |
+
resp = mail_client.delete_account(cloudmail_account_id)
|
| 152 |
+
if resp.get("code") == 200:
|
| 153 |
+
cleanup["cloudmail_deleted"] = True
|
| 154 |
+
except Exception as exc:
|
| 155 |
+
logger.warning("[账号] 删除 CloudMail 账户失败: %s", exc)
|
| 156 |
+
|
| 157 |
+
if sync_cpa_after:
|
| 158 |
+
sync_to_cpa()
|
| 159 |
+
|
| 160 |
+
return cleanup
|
| 161 |
+
finally:
|
| 162 |
+
if own_chatgpt:
|
| 163 |
+
own_chatgpt.stop()
|
src/autoteam/admin_state.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""管理员登录态持久化。
|
| 2 |
+
|
| 3 |
+
统一使用项目根目录下的 `state.json` 文件保存:
|
| 4 |
+
- session_token
|
| 5 |
+
- email
|
| 6 |
+
- password
|
| 7 |
+
- account_id
|
| 8 |
+
- workspace_name
|
| 9 |
+
- updated_at
|
| 10 |
+
|
| 11 |
+
兼容:
|
| 12 |
+
- 旧的纯文本 `session`(仅保存 session token)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
| 21 |
+
STATE_FILE = PROJECT_ROOT / "state.json"
|
| 22 |
+
LEGACY_SESSION_FILE = PROJECT_ROOT / "session"
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _normalize_state(data):
|
| 26 |
+
if not isinstance(data, dict):
|
| 27 |
+
return {}
|
| 28 |
+
return {
|
| 29 |
+
"email": data.get("email", "") or "",
|
| 30 |
+
"session_token": data.get("session_token", "") or "",
|
| 31 |
+
"password": data.get("password", "") or "",
|
| 32 |
+
"account_id": data.get("account_id", "") or "",
|
| 33 |
+
"workspace_name": data.get("workspace_name", "") or "",
|
| 34 |
+
"updated_at": data.get("updated_at"),
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _load_state_from_file(path: Path):
|
| 39 |
+
if not path.exists():
|
| 40 |
+
return {}
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
raw = path.read_text().strip()
|
| 44 |
+
except Exception:
|
| 45 |
+
return {}
|
| 46 |
+
|
| 47 |
+
if not raw:
|
| 48 |
+
return {}
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
return _normalize_state(json.loads(raw))
|
| 52 |
+
except Exception:
|
| 53 |
+
# 兼容旧版纯文本 session 文件
|
| 54 |
+
return {
|
| 55 |
+
"email": "",
|
| 56 |
+
"session_token": raw,
|
| 57 |
+
"account_id": "",
|
| 58 |
+
"workspace_name": "",
|
| 59 |
+
"updated_at": path.stat().st_mtime,
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _save_state(state):
|
| 64 |
+
STATE_FILE.write_text(json.dumps(_normalize_state(state), indent=2, ensure_ascii=False))
|
| 65 |
+
os.chmod(STATE_FILE, 0o600)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _migrate_legacy_state():
|
| 69 |
+
if STATE_FILE.exists():
|
| 70 |
+
return
|
| 71 |
+
state = _load_state_from_file(LEGACY_SESSION_FILE)
|
| 72 |
+
if state:
|
| 73 |
+
_save_state(state)
|
| 74 |
+
try:
|
| 75 |
+
LEGACY_SESSION_FILE.unlink()
|
| 76 |
+
except Exception:
|
| 77 |
+
pass
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def load_admin_state():
|
| 81 |
+
_migrate_legacy_state()
|
| 82 |
+
return _load_state_from_file(STATE_FILE)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def save_admin_state(state):
|
| 86 |
+
_save_state(state)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def update_admin_state(**kwargs):
|
| 90 |
+
state = load_admin_state()
|
| 91 |
+
state.update(kwargs)
|
| 92 |
+
state["updated_at"] = time.time()
|
| 93 |
+
save_admin_state(state)
|
| 94 |
+
return state
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def clear_admin_state():
|
| 98 |
+
if STATE_FILE.exists():
|
| 99 |
+
STATE_FILE.unlink()
|
| 100 |
+
if LEGACY_SESSION_FILE.exists():
|
| 101 |
+
LEGACY_SESSION_FILE.unlink()
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def get_admin_email():
|
| 105 |
+
return load_admin_state().get("email", "")
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def get_admin_session_token():
|
| 109 |
+
return load_admin_state().get("session_token", "")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def get_chatgpt_account_id():
|
| 113 |
+
state = load_admin_state()
|
| 114 |
+
return state.get("account_id", "") or os.environ.get("CHATGPT_ACCOUNT_ID", "")
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def get_admin_password():
|
| 118 |
+
return load_admin_state().get("password", "")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def get_chatgpt_workspace_name():
|
| 122 |
+
state = load_admin_state()
|
| 123 |
+
return state.get("workspace_name", "")
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def get_admin_state_summary():
|
| 127 |
+
state = load_admin_state()
|
| 128 |
+
return {
|
| 129 |
+
"configured": bool(state.get("session_token") and state.get("account_id")),
|
| 130 |
+
"email": state.get("email", ""),
|
| 131 |
+
"account_id": state.get("account_id", ""),
|
| 132 |
+
"workspace_name": state.get("workspace_name", ""),
|
| 133 |
+
"session_present": bool(state.get("session_token")),
|
| 134 |
+
"password_saved": bool(state.get("password")),
|
| 135 |
+
"updated_at": state.get("updated_at"),
|
| 136 |
+
}
|
src/autoteam/api.py
CHANGED
|
@@ -28,9 +28,45 @@ app = FastAPI(
|
|
| 28 |
_tasks: dict[str, dict] = {}
|
| 29 |
_playwright_lock = threading.Lock()
|
| 30 |
_current_task_id: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
MAX_TASK_HISTORY = 50
|
| 32 |
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
def _prune_tasks():
|
| 35 |
"""保留最近 MAX_TASK_HISTORY 个任务"""
|
| 36 |
if len(_tasks) <= MAX_TASK_HISTORY:
|
|
@@ -68,18 +104,7 @@ def _run_task(task_id: str, func, *args, **kwargs):
|
|
| 68 |
def _start_task(command: str, func, params: dict, *args, **kwargs) -> dict:
|
| 69 |
"""创建并启动后台任务,返回任务信息"""
|
| 70 |
if not _playwright_lock.acquire(blocking=False):
|
| 71 |
-
|
| 72 |
-
raise HTTPException(
|
| 73 |
-
status_code=409,
|
| 74 |
-
detail={
|
| 75 |
-
"message": "有任务正在执行,请等待完成后再试",
|
| 76 |
-
"running_task": {
|
| 77 |
-
"task_id": _current_task_id,
|
| 78 |
-
"command": running.get("command", "unknown"),
|
| 79 |
-
"started_at": running.get("started_at"),
|
| 80 |
-
},
|
| 81 |
-
},
|
| 82 |
-
)
|
| 83 |
_playwright_lock.release()
|
| 84 |
|
| 85 |
task_id = uuid.uuid4().hex[:12]
|
|
@@ -115,15 +140,366 @@ class CleanupParams(BaseModel):
|
|
| 115 |
max_seats: Optional[int] = None
|
| 116 |
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
def _sanitize_account(acc: dict) -> dict:
|
| 119 |
"""脱敏账号信息(去掉 password 等敏感字段)"""
|
| 120 |
return {k: v for k, v in acc.items() if k not in ("password", "cloudmail_account_id")}
|
| 121 |
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
# ---------------------------------------------------------------------------
|
| 124 |
# 同步端点
|
| 125 |
# ---------------------------------------------------------------------------
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
@app.get("/api/accounts")
|
| 128 |
def get_accounts():
|
| 129 |
"""获取所有账号列表"""
|
|
@@ -147,6 +523,41 @@ def get_standby():
|
|
| 147 |
return [_sanitize_account(a) for a in accounts]
|
| 148 |
|
| 149 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
@app.get("/api/status")
|
| 151 |
def get_status():
|
| 152 |
"""获取所有账号状态 + active 账号实时额度"""
|
|
@@ -191,6 +602,12 @@ def post_sync():
|
|
| 191 |
return {"message": "同步完成"}
|
| 192 |
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
@app.get("/api/cpa/files")
|
| 195 |
def get_cpa_files():
|
| 196 |
"""获取 CPA 中的认证文件列表"""
|
|
|
|
| 28 |
_tasks: dict[str, dict] = {}
|
| 29 |
_playwright_lock = threading.Lock()
|
| 30 |
_current_task_id: Optional[str] = None
|
| 31 |
+
_admin_login_api = None
|
| 32 |
+
_admin_login_step: Optional[str] = None
|
| 33 |
+
_main_codex_flow = None
|
| 34 |
+
_main_codex_step: Optional[str] = None
|
| 35 |
MAX_TASK_HISTORY = 50
|
| 36 |
|
| 37 |
|
| 38 |
+
def _current_busy_detail(default_message: str):
|
| 39 |
+
if _admin_login_api:
|
| 40 |
+
return {
|
| 41 |
+
"message": default_message,
|
| 42 |
+
"running_task": {
|
| 43 |
+
"task_id": "admin-login",
|
| 44 |
+
"command": "admin-login",
|
| 45 |
+
"started_at": None,
|
| 46 |
+
},
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
if _main_codex_flow:
|
| 50 |
+
return {
|
| 51 |
+
"message": default_message,
|
| 52 |
+
"running_task": {
|
| 53 |
+
"task_id": "main-codex-sync",
|
| 54 |
+
"command": "main-codex-sync",
|
| 55 |
+
"started_at": None,
|
| 56 |
+
},
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
running = _tasks.get(_current_task_id, {})
|
| 60 |
+
return {
|
| 61 |
+
"message": default_message,
|
| 62 |
+
"running_task": {
|
| 63 |
+
"task_id": _current_task_id,
|
| 64 |
+
"command": running.get("command", "unknown"),
|
| 65 |
+
"started_at": running.get("started_at"),
|
| 66 |
+
},
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
|
| 70 |
def _prune_tasks():
|
| 71 |
"""保留最近 MAX_TASK_HISTORY 个任务"""
|
| 72 |
if len(_tasks) <= MAX_TASK_HISTORY:
|
|
|
|
| 104 |
def _start_task(command: str, func, params: dict, *args, **kwargs) -> dict:
|
| 105 |
"""创建并启动后台任务,返回任务信息"""
|
| 106 |
if not _playwright_lock.acquire(blocking=False):
|
| 107 |
+
raise HTTPException(status_code=409, detail=_current_busy_detail("有任务正在执行,请等待完成后再试"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
_playwright_lock.release()
|
| 109 |
|
| 110 |
task_id = uuid.uuid4().hex[:12]
|
|
|
|
| 140 |
max_seats: Optional[int] = None
|
| 141 |
|
| 142 |
|
| 143 |
+
class AdminEmailParams(BaseModel):
|
| 144 |
+
email: str
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
class AdminPasswordParams(BaseModel):
|
| 148 |
+
password: str
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class AdminCodeParams(BaseModel):
|
| 152 |
+
code: str
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class AdminWorkspaceParams(BaseModel):
|
| 156 |
+
option_id: str
|
| 157 |
+
|
| 158 |
+
|
| 159 |
def _sanitize_account(acc: dict) -> dict:
|
| 160 |
"""脱敏账号信息(去掉 password 等敏感字段)"""
|
| 161 |
return {k: v for k, v in acc.items() if k not in ("password", "cloudmail_account_id")}
|
| 162 |
|
| 163 |
|
| 164 |
+
def _admin_status():
|
| 165 |
+
from autoteam.admin_state import get_admin_state_summary
|
| 166 |
+
|
| 167 |
+
status = get_admin_state_summary()
|
| 168 |
+
status["login_step"] = _admin_login_step
|
| 169 |
+
status["login_in_progress"] = _admin_login_api is not None
|
| 170 |
+
if _admin_login_api and _admin_login_step == "workspace_required":
|
| 171 |
+
status["workspace_options"] = getattr(_admin_login_api, "workspace_options_cache", []) or []
|
| 172 |
+
else:
|
| 173 |
+
status["workspace_options"] = []
|
| 174 |
+
return status
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _main_codex_status():
|
| 178 |
+
return {
|
| 179 |
+
"in_progress": _main_codex_flow is not None,
|
| 180 |
+
"step": _main_codex_step,
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def _finish_admin_login(completed: dict):
|
| 185 |
+
global _admin_login_api, _admin_login_step
|
| 186 |
+
api = _admin_login_api
|
| 187 |
+
try:
|
| 188 |
+
info = api.complete_admin_login()
|
| 189 |
+
finally:
|
| 190 |
+
if api:
|
| 191 |
+
api.stop()
|
| 192 |
+
_admin_login_api = None
|
| 193 |
+
_admin_login_step = None
|
| 194 |
+
if _playwright_lock.locked():
|
| 195 |
+
_playwright_lock.release()
|
| 196 |
+
return {"status": "completed", "admin": _admin_status(), "info": info}
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def _set_pending_admin_login(api, step):
|
| 200 |
+
global _admin_login_api, _admin_login_step
|
| 201 |
+
_admin_login_api = api
|
| 202 |
+
_admin_login_step = step
|
| 203 |
+
return {"status": step, "admin": _admin_status()}
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def _finish_main_codex_sync():
|
| 207 |
+
global _main_codex_flow, _main_codex_step
|
| 208 |
+
flow = _main_codex_flow
|
| 209 |
+
try:
|
| 210 |
+
info = flow.complete()
|
| 211 |
+
finally:
|
| 212 |
+
if flow:
|
| 213 |
+
flow.stop()
|
| 214 |
+
_main_codex_flow = None
|
| 215 |
+
_main_codex_step = None
|
| 216 |
+
if _playwright_lock.locked():
|
| 217 |
+
_playwright_lock.release()
|
| 218 |
+
return {
|
| 219 |
+
"status": "completed",
|
| 220 |
+
"message": "主号 Codex 已同步到 CPA",
|
| 221 |
+
"codex": _main_codex_status(),
|
| 222 |
+
"info": info,
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _set_pending_main_codex_sync(flow, step):
|
| 227 |
+
global _main_codex_flow, _main_codex_step
|
| 228 |
+
_main_codex_flow = flow
|
| 229 |
+
_main_codex_step = step
|
| 230 |
+
return {"status": step, "codex": _main_codex_status()}
|
| 231 |
+
|
| 232 |
+
|
| 233 |
# ---------------------------------------------------------------------------
|
| 234 |
# 同步端点
|
| 235 |
# ---------------------------------------------------------------------------
|
| 236 |
|
| 237 |
+
|
| 238 |
+
@app.get("/api/admin/status")
|
| 239 |
+
def get_admin_status():
|
| 240 |
+
"""获取管理员登录状态。"""
|
| 241 |
+
return _admin_status()
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
@app.get("/api/main-codex/status")
|
| 245 |
+
def get_main_codex_status():
|
| 246 |
+
"""获取主号 Codex 同步状态。"""
|
| 247 |
+
return _main_codex_status()
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
@app.post("/api/admin/login/start")
|
| 251 |
+
def post_admin_login_start(params: AdminEmailParams):
|
| 252 |
+
"""开始管理员登录流程。"""
|
| 253 |
+
global _admin_login_api, _admin_login_step
|
| 254 |
+
|
| 255 |
+
if _admin_login_api:
|
| 256 |
+
_admin_login_api.stop()
|
| 257 |
+
_admin_login_api = None
|
| 258 |
+
_admin_login_step = None
|
| 259 |
+
if _playwright_lock.locked():
|
| 260 |
+
_playwright_lock.release()
|
| 261 |
+
|
| 262 |
+
if not _playwright_lock.acquire(blocking=False):
|
| 263 |
+
raise HTTPException(status_code=409, detail=_current_busy_detail("有任务正在执行,请等待完成后再进行管理员登录"))
|
| 264 |
+
|
| 265 |
+
try:
|
| 266 |
+
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 267 |
+
|
| 268 |
+
logger.info("[API] 开始管理员登录: %s", params.email.strip())
|
| 269 |
+
api = ChatGPTTeamAPI()
|
| 270 |
+
result = api.begin_admin_login(params.email.strip())
|
| 271 |
+
step = result["step"]
|
| 272 |
+
logger.info("[API] 管理员登录 start 返回: step=%s detail=%s", step, result.get("detail"))
|
| 273 |
+
if step == "completed":
|
| 274 |
+
_admin_login_api = api
|
| 275 |
+
return _finish_admin_login(result)
|
| 276 |
+
if step in ("password_required", "code_required", "workspace_required"):
|
| 277 |
+
return _set_pending_admin_login(api, step)
|
| 278 |
+
api.stop()
|
| 279 |
+
_playwright_lock.release()
|
| 280 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "无法识别管理员登录步骤")
|
| 281 |
+
except Exception as exc:
|
| 282 |
+
logger.exception("[API] 管理员登录 start 失败")
|
| 283 |
+
if _playwright_lock.locked():
|
| 284 |
+
_playwright_lock.release()
|
| 285 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
@app.post("/api/admin/login/password")
|
| 289 |
+
def post_admin_login_password(params: AdminPasswordParams):
|
| 290 |
+
"""提交管理员密码。"""
|
| 291 |
+
global _admin_login_api, _admin_login_step
|
| 292 |
+
if not _admin_login_api or _admin_login_step != "password_required":
|
| 293 |
+
raise HTTPException(status_code=409, detail="当前没有等待密码的管理员登录流程")
|
| 294 |
+
|
| 295 |
+
try:
|
| 296 |
+
logger.info("[API] 提交管理员密码 | current_step=%s", _admin_login_step)
|
| 297 |
+
result = _admin_login_api.submit_admin_password(params.password)
|
| 298 |
+
step = result["step"]
|
| 299 |
+
logger.info("[API] 管理员密码提交返回: step=%s detail=%s", step, result.get("detail"))
|
| 300 |
+
if step == "completed":
|
| 301 |
+
return _finish_admin_login(result)
|
| 302 |
+
if step in ("password_required", "code_required", "workspace_required"):
|
| 303 |
+
_admin_login_step = step
|
| 304 |
+
return {"status": step, "admin": _admin_status()}
|
| 305 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "管理员密码登录失败")
|
| 306 |
+
except HTTPException:
|
| 307 |
+
raise
|
| 308 |
+
except Exception as exc:
|
| 309 |
+
logger.exception("[API] 管理员密码提交失败")
|
| 310 |
+
_admin_login_api.stop()
|
| 311 |
+
_admin_login_api = None
|
| 312 |
+
_admin_login_step = None
|
| 313 |
+
if _playwright_lock.locked():
|
| 314 |
+
_playwright_lock.release()
|
| 315 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
@app.post("/api/admin/login/code")
|
| 319 |
+
def post_admin_login_code(params: AdminCodeParams):
|
| 320 |
+
"""提交管理员验证码。"""
|
| 321 |
+
global _admin_login_api, _admin_login_step
|
| 322 |
+
if not _admin_login_api or _admin_login_step != "code_required":
|
| 323 |
+
raise HTTPException(status_code=409, detail="当前没有等待验证码的管理员登录流程")
|
| 324 |
+
|
| 325 |
+
try:
|
| 326 |
+
logger.info("[API] 提交管理员验证码 | current_step=%s code_len=%d", _admin_login_step, len(params.code.strip()))
|
| 327 |
+
result = _admin_login_api.submit_admin_code(params.code.strip())
|
| 328 |
+
step = result["step"]
|
| 329 |
+
logger.info("[API] 管理员验证码提交返回: step=%s detail=%s", step, result.get("detail"))
|
| 330 |
+
if step == "completed":
|
| 331 |
+
return _finish_admin_login(result)
|
| 332 |
+
if step in ("password_required", "code_required", "workspace_required"):
|
| 333 |
+
_admin_login_step = step
|
| 334 |
+
return {"status": step, "admin": _admin_status()}
|
| 335 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "管理员验证码登录失败")
|
| 336 |
+
except HTTPException:
|
| 337 |
+
raise
|
| 338 |
+
except Exception as exc:
|
| 339 |
+
logger.exception("[API] 管理员验证码提交失败")
|
| 340 |
+
_admin_login_api.stop()
|
| 341 |
+
_admin_login_api = None
|
| 342 |
+
_admin_login_step = None
|
| 343 |
+
if _playwright_lock.locked():
|
| 344 |
+
_playwright_lock.release()
|
| 345 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
@app.post("/api/admin/login/workspace")
|
| 349 |
+
def post_admin_login_workspace(params: AdminWorkspaceParams):
|
| 350 |
+
"""提交管理员 workspace 选择。"""
|
| 351 |
+
global _admin_login_api, _admin_login_step
|
| 352 |
+
if not _admin_login_api or _admin_login_step != "workspace_required":
|
| 353 |
+
raise HTTPException(status_code=409, detail="当前没有等待组织选择的管理员登录流程")
|
| 354 |
+
|
| 355 |
+
try:
|
| 356 |
+
logger.info("[API] 提交管理员 workspace 选择 | option_id=%s", params.option_id)
|
| 357 |
+
result = _admin_login_api.select_workspace_option(params.option_id)
|
| 358 |
+
step = result["step"]
|
| 359 |
+
logger.info("[API] 管理员 workspace 选择返回: step=%s detail=%s", step, result.get("detail"))
|
| 360 |
+
if step == "completed":
|
| 361 |
+
return _finish_admin_login(result)
|
| 362 |
+
if step in ("password_required", "code_required", "workspace_required"):
|
| 363 |
+
_admin_login_step = step
|
| 364 |
+
return {"status": step, "admin": _admin_status()}
|
| 365 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "管理员组织选择失败")
|
| 366 |
+
except HTTPException:
|
| 367 |
+
raise
|
| 368 |
+
except Exception as exc:
|
| 369 |
+
logger.exception("[API] 管理员 workspace 选择失败")
|
| 370 |
+
_admin_login_api.stop()
|
| 371 |
+
_admin_login_api = None
|
| 372 |
+
_admin_login_step = None
|
| 373 |
+
if _playwright_lock.locked():
|
| 374 |
+
_playwright_lock.release()
|
| 375 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
@app.post("/api/admin/login/cancel")
|
| 379 |
+
def post_admin_login_cancel():
|
| 380 |
+
"""取消管理员登录流程。"""
|
| 381 |
+
global _admin_login_api, _admin_login_step
|
| 382 |
+
if _admin_login_api:
|
| 383 |
+
_admin_login_api.stop()
|
| 384 |
+
_admin_login_api = None
|
| 385 |
+
_admin_login_step = None
|
| 386 |
+
if _playwright_lock.locked():
|
| 387 |
+
_playwright_lock.release()
|
| 388 |
+
return {"message": "管理员登录已取消", "admin": _admin_status()}
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
@app.post("/api/admin/logout")
|
| 392 |
+
def post_admin_logout():
|
| 393 |
+
"""清除已保存的管理员登录态。"""
|
| 394 |
+
from autoteam.admin_state import clear_admin_state
|
| 395 |
+
|
| 396 |
+
if _admin_login_api:
|
| 397 |
+
post_admin_login_cancel()
|
| 398 |
+
clear_admin_state()
|
| 399 |
+
return {"message": "管理员登录态已清除", "admin": _admin_status()}
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
@app.post("/api/main-codex/start")
|
| 403 |
+
def post_main_codex_start():
|
| 404 |
+
"""开始主号 Codex 登录并同步到 CPA。"""
|
| 405 |
+
global _main_codex_flow, _main_codex_step
|
| 406 |
+
|
| 407 |
+
if _main_codex_flow:
|
| 408 |
+
_main_codex_flow.stop()
|
| 409 |
+
_main_codex_flow = None
|
| 410 |
+
_main_codex_step = None
|
| 411 |
+
if _playwright_lock.locked():
|
| 412 |
+
_playwright_lock.release()
|
| 413 |
+
|
| 414 |
+
if not _playwright_lock.acquire(blocking=False):
|
| 415 |
+
raise HTTPException(status_code=409, detail=_current_busy_detail("有任务正在执行,请等待完成后再同步主号 Codex"))
|
| 416 |
+
|
| 417 |
+
try:
|
| 418 |
+
from autoteam.codex_auth import MainCodexSyncFlow
|
| 419 |
+
|
| 420 |
+
flow = MainCodexSyncFlow()
|
| 421 |
+
result = flow.start()
|
| 422 |
+
step = result["step"]
|
| 423 |
+
if step == "completed":
|
| 424 |
+
_main_codex_flow = flow
|
| 425 |
+
return _finish_main_codex_sync()
|
| 426 |
+
if step in ("password_required", "code_required"):
|
| 427 |
+
return _set_pending_main_codex_sync(flow, step)
|
| 428 |
+
flow.stop()
|
| 429 |
+
_playwright_lock.release()
|
| 430 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "无法识别主号 Codex 登录步骤")
|
| 431 |
+
except Exception as exc:
|
| 432 |
+
if _playwright_lock.locked():
|
| 433 |
+
_playwright_lock.release()
|
| 434 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
@app.post("/api/main-codex/password")
|
| 438 |
+
def post_main_codex_password(params: AdminPasswordParams):
|
| 439 |
+
"""提交主号 Codex 登录密码。"""
|
| 440 |
+
global _main_codex_flow, _main_codex_step
|
| 441 |
+
if not _main_codex_flow or _main_codex_step != "password_required":
|
| 442 |
+
raise HTTPException(status_code=409, detail="当前没有等待密码的主号 Codex 登录流程")
|
| 443 |
+
|
| 444 |
+
try:
|
| 445 |
+
result = _main_codex_flow.submit_password(params.password)
|
| 446 |
+
step = result["step"]
|
| 447 |
+
if step == "completed":
|
| 448 |
+
return _finish_main_codex_sync()
|
| 449 |
+
if step in ("password_required", "code_required"):
|
| 450 |
+
_main_codex_step = step
|
| 451 |
+
return {"status": step, "codex": _main_codex_status()}
|
| 452 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "主号 Codex 密码登录失败")
|
| 453 |
+
except HTTPException:
|
| 454 |
+
raise
|
| 455 |
+
except Exception as exc:
|
| 456 |
+
_main_codex_flow.stop()
|
| 457 |
+
_main_codex_flow = None
|
| 458 |
+
_main_codex_step = None
|
| 459 |
+
if _playwright_lock.locked():
|
| 460 |
+
_playwright_lock.release()
|
| 461 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
@app.post("/api/main-codex/code")
|
| 465 |
+
def post_main_codex_code(params: AdminCodeParams):
|
| 466 |
+
"""提交主号 Codex 登录验证码。"""
|
| 467 |
+
global _main_codex_flow, _main_codex_step
|
| 468 |
+
if not _main_codex_flow or _main_codex_step != "code_required":
|
| 469 |
+
raise HTTPException(status_code=409, detail="当前没有等待验证码的主号 Codex 登录流程")
|
| 470 |
+
|
| 471 |
+
try:
|
| 472 |
+
result = _main_codex_flow.submit_code(params.code.strip())
|
| 473 |
+
step = result["step"]
|
| 474 |
+
if step == "completed":
|
| 475 |
+
return _finish_main_codex_sync()
|
| 476 |
+
if step in ("password_required", "code_required"):
|
| 477 |
+
_main_codex_step = step
|
| 478 |
+
return {"status": step, "codex": _main_codex_status()}
|
| 479 |
+
raise HTTPException(status_code=400, detail=result.get("detail") or "主号 Codex 验证���登录失败")
|
| 480 |
+
except HTTPException:
|
| 481 |
+
raise
|
| 482 |
+
except Exception as exc:
|
| 483 |
+
_main_codex_flow.stop()
|
| 484 |
+
_main_codex_flow = None
|
| 485 |
+
_main_codex_step = None
|
| 486 |
+
if _playwright_lock.locked():
|
| 487 |
+
_playwright_lock.release()
|
| 488 |
+
raise HTTPException(status_code=400, detail=str(exc))
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
@app.post("/api/main-codex/cancel")
|
| 492 |
+
def post_main_codex_cancel():
|
| 493 |
+
"""取消主号 Codex 登录流程。"""
|
| 494 |
+
global _main_codex_flow, _main_codex_step
|
| 495 |
+
if _main_codex_flow:
|
| 496 |
+
_main_codex_flow.stop()
|
| 497 |
+
_main_codex_flow = None
|
| 498 |
+
_main_codex_step = None
|
| 499 |
+
if _playwright_lock.locked():
|
| 500 |
+
_playwright_lock.release()
|
| 501 |
+
return {"message": "主号 Codex 登录已取消", "codex": _main_codex_status()}
|
| 502 |
+
|
| 503 |
@app.get("/api/accounts")
|
| 504 |
def get_accounts():
|
| 505 |
"""获取所有账号列表"""
|
|
|
|
| 523 |
return [_sanitize_account(a) for a in accounts]
|
| 524 |
|
| 525 |
|
| 526 |
+
@app.delete("/api/accounts/{email}")
|
| 527 |
+
def delete_account(email: str):
|
| 528 |
+
"""删除本地管理账号及其关联资源。"""
|
| 529 |
+
if not _playwright_lock.acquire(blocking=False):
|
| 530 |
+
running = _tasks.get(_current_task_id, {})
|
| 531 |
+
raise HTTPException(
|
| 532 |
+
status_code=409,
|
| 533 |
+
detail={
|
| 534 |
+
"message": "有任务正在执行,请等待完成后再删除账号",
|
| 535 |
+
"running_task": {
|
| 536 |
+
"task_id": _current_task_id,
|
| 537 |
+
"command": running.get("command", "unknown"),
|
| 538 |
+
"started_at": running.get("started_at"),
|
| 539 |
+
},
|
| 540 |
+
},
|
| 541 |
+
)
|
| 542 |
+
|
| 543 |
+
try:
|
| 544 |
+
from autoteam.accounts import load_accounts
|
| 545 |
+
from autoteam.account_ops import delete_managed_account
|
| 546 |
+
|
| 547 |
+
accounts = load_accounts()
|
| 548 |
+
if not any(a["email"].lower() == email.lower() for a in accounts):
|
| 549 |
+
raise HTTPException(status_code=404, detail="账号不存在")
|
| 550 |
+
|
| 551 |
+
cleanup = delete_managed_account(email)
|
| 552 |
+
return {
|
| 553 |
+
"message": "账号删除完成",
|
| 554 |
+
"deleted_email": email,
|
| 555 |
+
"cleanup": cleanup,
|
| 556 |
+
}
|
| 557 |
+
finally:
|
| 558 |
+
_playwright_lock.release()
|
| 559 |
+
|
| 560 |
+
|
| 561 |
@app.get("/api/status")
|
| 562 |
def get_status():
|
| 563 |
"""获取所有账号状态 + active 账号实时额度"""
|
|
|
|
| 602 |
return {"message": "同步完成"}
|
| 603 |
|
| 604 |
|
| 605 |
+
@app.post("/api/sync/main-codex")
|
| 606 |
+
def post_sync_main_codex():
|
| 607 |
+
"""兼容旧接口:开始主号 Codex 登录并同步到 CPA。"""
|
| 608 |
+
return post_main_codex_start()
|
| 609 |
+
|
| 610 |
+
|
| 611 |
@app.get("/api/cpa/files")
|
| 612 |
def get_cpa_files():
|
| 613 |
"""获取 CPA 中的认证文件列表"""
|
src/autoteam/chatgpt_api.py
CHANGED
|
@@ -3,12 +3,20 @@ import autoteam.display # noqa: F401
|
|
| 3 |
|
| 4 |
import json
|
| 5 |
import logging
|
|
|
|
|
|
|
| 6 |
import time
|
| 7 |
import uuid
|
| 8 |
from pathlib import Path
|
|
|
|
| 9 |
from playwright.sync_api import sync_playwright
|
| 10 |
|
| 11 |
-
from autoteam.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
| 14 |
|
|
@@ -18,7 +26,29 @@ SCREENSHOT_DIR = PROJECT_ROOT / "screenshots"
|
|
| 18 |
|
| 19 |
|
| 20 |
class ChatGPTTeamAPI:
|
| 21 |
-
"""通过
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
def __init__(self):
|
| 24 |
self.playwright = None
|
|
@@ -26,19 +56,34 @@ class ChatGPTTeamAPI:
|
|
| 26 |
self.context = None
|
| 27 |
self.page = None
|
| 28 |
self.access_token = None
|
| 29 |
-
self.
|
|
|
|
|
|
|
| 30 |
self.oai_device_id = str(uuid.uuid4())
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
SCREENSHOT_DIR.mkdir(exist_ok=True)
|
| 35 |
-
|
| 36 |
-
# 读取 session cookies
|
| 37 |
-
session_file = BASE_DIR / "session"
|
| 38 |
-
if not session_file.exists():
|
| 39 |
-
raise FileNotFoundError("请先把 ChatGPT session token 写入 ./session 文件")
|
| 40 |
-
session_token = session_file.read_text().strip()
|
| 41 |
-
|
| 42 |
self.playwright = sync_playwright().start()
|
| 43 |
self.browser = self.playwright.chromium.launch(
|
| 44 |
headless=False,
|
|
@@ -50,28 +95,34 @@ class ChatGPTTeamAPI:
|
|
| 50 |
)
|
| 51 |
self.page = self.context.new_page()
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
|
|
|
| 58 |
for i in range(12):
|
| 59 |
html = self.page.content()[:1000].lower()
|
| 60 |
if "verify you are human" not in html and "challenge" not in self.page.url:
|
| 61 |
-
|
| 62 |
logger.info("[ChatGPT] 等待 Cloudflare... (%ds)", i * 5)
|
| 63 |
time.sleep(5)
|
| 64 |
|
| 65 |
-
|
| 66 |
-
# 检测 token 长度,超过 3800 就分片
|
| 67 |
if len(session_token) > 3800:
|
| 68 |
-
|
| 69 |
-
part1 = session_token[3800:]
|
| 70 |
-
cookies = [
|
| 71 |
{
|
| 72 |
"name": "__Secure-next-auth.session-token.0",
|
| 73 |
-
"value":
|
| 74 |
-
"domain":
|
| 75 |
"path": "/",
|
| 76 |
"httpOnly": True,
|
| 77 |
"secure": True,
|
|
@@ -79,34 +130,79 @@ class ChatGPTTeamAPI:
|
|
| 79 |
},
|
| 80 |
{
|
| 81 |
"name": "__Secure-next-auth.session-token.1",
|
| 82 |
-
"value":
|
| 83 |
-
"domain":
|
| 84 |
"path": "/",
|
| 85 |
"httpOnly": True,
|
| 86 |
"secure": True,
|
| 87 |
"sameSite": "Lax",
|
| 88 |
},
|
| 89 |
]
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
"domain": "chatgpt.com",
|
| 95 |
"path": "/",
|
| 96 |
-
"httpOnly": True,
|
| 97 |
"secure": True,
|
| 98 |
"sameSite": "Lax",
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
# 加上 account cookie
|
| 102 |
-
cookies.append({
|
| 103 |
-
"name": "_account",
|
| 104 |
-
"value": self.account_id,
|
| 105 |
-
"domain": "chatgpt.com",
|
| 106 |
-
"path": "/",
|
| 107 |
-
"secure": True,
|
| 108 |
-
"sameSite": "Lax",
|
| 109 |
-
})
|
| 110 |
cookies.append({
|
| 111 |
"name": "oai-did",
|
| 112 |
"value": self.oai_device_id,
|
|
@@ -115,30 +211,372 @@ class ChatGPTTeamAPI:
|
|
| 115 |
"secure": True,
|
| 116 |
"sameSite": "Lax",
|
| 117 |
})
|
| 118 |
-
|
| 119 |
self.context.add_cookies(cookies)
|
|
|
|
| 120 |
logger.info("[ChatGPT] 已注入 session cookies")
|
| 121 |
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
self._fetch_access_token()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
-
|
| 126 |
-
self._auto_detect_workspace()
|
| 127 |
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
|
| 132 |
-
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
return
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
-
# 用 account_id 调 API 获取 workspace 信息
|
| 140 |
-
result = self._api_fetch("GET", f"/backend-api/accounts/{self.account_id}/invites")
|
| 141 |
-
# invites 接口不返回名称,换用 settings
|
| 142 |
result = self.page.evaluate('''async (accountId) => {
|
| 143 |
try {
|
| 144 |
const resp = await fetch("/backend-api/accounts/" + accountId + "/settings", {
|
|
@@ -149,23 +587,18 @@ class ChatGPTTeamAPI:
|
|
| 149 |
}''', self.account_id)
|
| 150 |
|
| 151 |
if result and result.get("workspace_name"):
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
| 155 |
|
| 156 |
-
# fallback: 从 admin 页面提取 workspace 名称
|
| 157 |
try:
|
| 158 |
self.page.goto("https://chatgpt.com/admin", wait_until="domcontentloaded", timeout=30000)
|
| 159 |
-
|
| 160 |
-
_t.sleep(5)
|
| 161 |
-
# workspace 名称通常是 admin 页面侧边栏中的大标题
|
| 162 |
name = self.page.evaluate('''() => {
|
| 163 |
-
// 找侧边栏或页面标题中的 workspace 名称
|
| 164 |
-
// admin 页面结构:侧边栏有 workspace 名称作为标题
|
| 165 |
const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [class*="name"]');
|
| 166 |
for (const h of headings) {
|
| 167 |
const text = h.textContent.trim();
|
| 168 |
-
// 跳过通用标题
|
| 169 |
if (text && text.length < 50 && text.length > 1
|
| 170 |
&& !["常规", "成员", "设置", "General", "Members", "Settings"].includes(text)) {
|
| 171 |
return text;
|
|
@@ -174,23 +607,23 @@ class ChatGPTTeamAPI:
|
|
| 174 |
return null;
|
| 175 |
}''')
|
| 176 |
if name:
|
| 177 |
-
|
|
|
|
| 178 |
logger.info("[ChatGPT] 自动检测到 workspace 名称: %s", name)
|
| 179 |
-
return
|
| 180 |
except Exception:
|
| 181 |
pass
|
| 182 |
|
| 183 |
-
logger.warning("[ChatGPT] 未能自动获取 workspace 名称
|
|
|
|
| 184 |
|
| 185 |
def _fetch_access_token(self):
|
| 186 |
-
"""通过浏览器 fetch 获取 access token"""
|
| 187 |
result = self.page.evaluate('''async () => {
|
| 188 |
try {
|
| 189 |
const resp = await fetch("/api/auth/session");
|
| 190 |
const data = await resp.json();
|
| 191 |
return { ok: true, data: data };
|
| 192 |
} catch(e) {
|
| 193 |
-
// session 接口可能不返回 token,试 /backend-api/me
|
| 194 |
return { ok: false, error: e.message };
|
| 195 |
}
|
| 196 |
}''')
|
|
@@ -200,35 +633,17 @@ class ChatGPTTeamAPI:
|
|
| 200 |
logger.info("[ChatGPT] 已获取 access token")
|
| 201 |
return
|
| 202 |
|
| 203 |
-
# 尝试通过 sentinel chat requirements 获取 token
|
| 204 |
-
# 先试试 /backend-api/sentinel/chat-requirements
|
| 205 |
-
result2 = self.page.evaluate('''async () => {
|
| 206 |
-
try {
|
| 207 |
-
const resp = await fetch("/backend-api/sentinel/chat-requirements", {
|
| 208 |
-
method: "POST",
|
| 209 |
-
headers: { "Content-Type": "application/json" },
|
| 210 |
-
body: JSON.stringify({})
|
| 211 |
-
});
|
| 212 |
-
return { status: resp.status, text: await resp.text() };
|
| 213 |
-
} catch(e) {
|
| 214 |
-
return { error: e.message };
|
| 215 |
-
}
|
| 216 |
-
}''')
|
| 217 |
-
|
| 218 |
-
# 如果以上都拿不到,用 session 文件里可能有的 bearer token
|
| 219 |
bearer_file = BASE_DIR / "bearer_token"
|
| 220 |
if bearer_file.exists():
|
| 221 |
self.access_token = bearer_file.read_text().strip()
|
| 222 |
logger.info("[ChatGPT] 从 bearer_token 文件加载 access token")
|
| 223 |
return
|
| 224 |
|
| 225 |
-
# 最后手段:导航到 chatgpt.com 让前端 JS 获取 token,然后从 localStorage 读取
|
| 226 |
logger.info("[ChatGPT] 尝试通过页面获取 access token...")
|
| 227 |
self.page.goto("https://chatgpt.com/", wait_until="networkidle", timeout=60000)
|
| 228 |
time.sleep(10)
|
| 229 |
|
| 230 |
token = self.page.evaluate('''() => {
|
| 231 |
-
// 尝试多种方式
|
| 232 |
try {
|
| 233 |
const keys = Object.keys(localStorage);
|
| 234 |
for (const key of keys) {
|
|
@@ -238,16 +653,6 @@ class ChatGPTTeamAPI:
|
|
| 238 |
}
|
| 239 |
}
|
| 240 |
} catch(e) {}
|
| 241 |
-
|
| 242 |
-
// 尝试从 cookie 读取
|
| 243 |
-
try {
|
| 244 |
-
const cookies = document.cookie.split(";");
|
| 245 |
-
for (const c of cookies) {
|
| 246 |
-
if (c.trim().startsWith("oai-sc=")) {
|
| 247 |
-
return null; // not the right one
|
| 248 |
-
}
|
| 249 |
-
}
|
| 250 |
-
} catch(e) {}
|
| 251 |
return null;
|
| 252 |
}''')
|
| 253 |
|
|
@@ -258,7 +663,6 @@ class ChatGPTTeamAPI:
|
|
| 258 |
logger.warning("[ChatGPT] 未能获取 access token,将尝试无 token 调用")
|
| 259 |
|
| 260 |
def _api_fetch(self, method, path, body=None):
|
| 261 |
-
"""在浏览器内用 fetch 调用 ChatGPT API"""
|
| 262 |
headers_js = {
|
| 263 |
"Content-Type": "application/json",
|
| 264 |
"chatgpt-account-id": self.account_id,
|
|
@@ -280,14 +684,12 @@ class ChatGPTTeamAPI:
|
|
| 280 |
}
|
| 281 |
}'''
|
| 282 |
|
| 283 |
-
|
| 284 |
js_code,
|
| 285 |
[method, f"https://chatgpt.com{path}", headers_js, json.dumps(body) if body else None],
|
| 286 |
)
|
| 287 |
-
return result
|
| 288 |
|
| 289 |
def invite_member(self, email, seat_type="usage_based"):
|
| 290 |
-
"""邀请邮箱加入 Team。新账号用 usage_based 绕过限制,旧账号用 default。"""
|
| 291 |
path = f"/backend-api/accounts/{self.account_id}/invites"
|
| 292 |
body = {
|
| 293 |
"email_addresses": [email],
|
|
@@ -301,7 +703,6 @@ class ChatGPTTeamAPI:
|
|
| 301 |
|
| 302 |
status = result["status"]
|
| 303 |
resp_body = result["body"]
|
| 304 |
-
|
| 305 |
logger.info("[ChatGPT] 响应状态: %d", status)
|
| 306 |
|
| 307 |
try:
|
|
@@ -311,7 +712,6 @@ class ChatGPTTeamAPI:
|
|
| 311 |
data = resp_body
|
| 312 |
logger.debug("[ChatGPT] 响应内容: %s", resp_body[:500])
|
| 313 |
|
| 314 |
-
# 新账号用 usage_based 绕过后,需要改回 default
|
| 315 |
if status == 200 and seat_type == "usage_based" and isinstance(data, dict):
|
| 316 |
invites = data.get("account_invites", [])
|
| 317 |
for inv in invites:
|
|
@@ -322,7 +722,6 @@ class ChatGPTTeamAPI:
|
|
| 322 |
return status, data
|
| 323 |
|
| 324 |
def _update_invite_seat_type(self, invite_id, seat_type):
|
| 325 |
-
"""修改 pending invite 的 seat_type"""
|
| 326 |
path = f"/backend-api/accounts/{self.account_id}/invites/{invite_id}"
|
| 327 |
body = {"seat_type": seat_type}
|
| 328 |
|
|
@@ -335,7 +734,6 @@ class ChatGPTTeamAPI:
|
|
| 335 |
logger.error("[ChatGPT] 修改 seat_type 失败: %d %s", result['status'], result['body'][:200])
|
| 336 |
|
| 337 |
def list_invites(self):
|
| 338 |
-
"""获取当前邀请列表"""
|
| 339 |
path = f"/backend-api/accounts/{self.account_id}/invites"
|
| 340 |
result = self._api_fetch("GET", path)
|
| 341 |
try:
|
|
@@ -344,7 +742,6 @@ class ChatGPTTeamAPI:
|
|
| 344 |
return result["body"]
|
| 345 |
|
| 346 |
def stop(self):
|
| 347 |
-
"""关闭浏览器"""
|
| 348 |
try:
|
| 349 |
if self.browser:
|
| 350 |
self.browser.close()
|
|
@@ -356,4 +753,6 @@ class ChatGPTTeamAPI:
|
|
| 356 |
except Exception:
|
| 357 |
pass
|
| 358 |
self.browser = None
|
|
|
|
|
|
|
| 359 |
self.playwright = None
|
|
|
|
| 3 |
|
| 4 |
import json
|
| 5 |
import logging
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
import time
|
| 9 |
import uuid
|
| 10 |
from pathlib import Path
|
| 11 |
+
|
| 12 |
from playwright.sync_api import sync_playwright
|
| 13 |
|
| 14 |
+
from autoteam.admin_state import (
|
| 15 |
+
get_admin_session_token,
|
| 16 |
+
get_chatgpt_account_id,
|
| 17 |
+
get_chatgpt_workspace_name,
|
| 18 |
+
update_admin_state,
|
| 19 |
+
)
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
class ChatGPTTeamAPI:
|
| 29 |
+
"""通过浏览器内 fetch 调用 ChatGPT Team 内部 API。"""
|
| 30 |
+
|
| 31 |
+
EMAIL_INPUT_SELECTORS = [
|
| 32 |
+
'input[name="email"]',
|
| 33 |
+
'input[id="email-input"]',
|
| 34 |
+
'input[id="email"]',
|
| 35 |
+
'input[type="email"]',
|
| 36 |
+
'input[placeholder*="email" i]',
|
| 37 |
+
'input[placeholder*="邮箱"]',
|
| 38 |
+
'input[autocomplete="email"]',
|
| 39 |
+
'input[autocomplete="username"]',
|
| 40 |
+
]
|
| 41 |
+
PASSWORD_INPUT_SELECTORS = [
|
| 42 |
+
'input[name="password"]',
|
| 43 |
+
'input[type="password"]',
|
| 44 |
+
]
|
| 45 |
+
CODE_INPUT_SELECTORS = [
|
| 46 |
+
'input[name="code"]',
|
| 47 |
+
'input[placeholder*="验证码"]',
|
| 48 |
+
'input[placeholder*="code" i]',
|
| 49 |
+
'input[inputmode="numeric"]',
|
| 50 |
+
'input[autocomplete="one-time-code"]',
|
| 51 |
+
]
|
| 52 |
|
| 53 |
def __init__(self):
|
| 54 |
self.playwright = None
|
|
|
|
| 56 |
self.context = None
|
| 57 |
self.page = None
|
| 58 |
self.access_token = None
|
| 59 |
+
self.session_token = None
|
| 60 |
+
self.account_id = get_chatgpt_account_id()
|
| 61 |
+
self.workspace_name = get_chatgpt_workspace_name()
|
| 62 |
self.oai_device_id = str(uuid.uuid4())
|
| 63 |
+
self.login_email = None
|
| 64 |
+
self.login_password = None
|
| 65 |
+
self.workspace_options_cache = []
|
| 66 |
+
|
| 67 |
+
def _visible_locator_in_frames(self, selectors, timeout_ms=5000):
|
| 68 |
+
selector = ", ".join(selectors)
|
| 69 |
+
deadline = time.time() + timeout_ms / 1000
|
| 70 |
+
|
| 71 |
+
while time.time() < deadline:
|
| 72 |
+
frames = [self.page.main_frame]
|
| 73 |
+
frames.extend(frame for frame in self.page.frames if frame != self.page.main_frame)
|
| 74 |
+
for frame in frames:
|
| 75 |
+
try:
|
| 76 |
+
locator = frame.locator(selector).first
|
| 77 |
+
if locator.is_visible(timeout=250):
|
| 78 |
+
return locator
|
| 79 |
+
except Exception:
|
| 80 |
+
pass
|
| 81 |
+
time.sleep(0.2)
|
| 82 |
+
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
def _launch_browser(self):
|
| 86 |
SCREENSHOT_DIR.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
self.playwright = sync_playwright().start()
|
| 88 |
self.browser = self.playwright.chromium.launch(
|
| 89 |
headless=False,
|
|
|
|
| 95 |
)
|
| 96 |
self.page = self.context.new_page()
|
| 97 |
|
| 98 |
+
def _log_login_state(self, label):
|
| 99 |
+
try:
|
| 100 |
+
body_excerpt = self.page.locator("body").inner_text(timeout=1500)[:300].replace("\n", " ")
|
| 101 |
+
except Exception:
|
| 102 |
+
body_excerpt = ""
|
| 103 |
+
|
| 104 |
+
logger.info(
|
| 105 |
+
"[ChatGPT] %s | URL=%s | body=%s",
|
| 106 |
+
label,
|
| 107 |
+
self.page.url,
|
| 108 |
+
body_excerpt,
|
| 109 |
+
)
|
| 110 |
|
| 111 |
+
def _wait_for_cloudflare(self):
|
| 112 |
for i in range(12):
|
| 113 |
html = self.page.content()[:1000].lower()
|
| 114 |
if "verify you are human" not in html and "challenge" not in self.page.url:
|
| 115 |
+
return
|
| 116 |
logger.info("[ChatGPT] 等待 Cloudflare... (%ds)", i * 5)
|
| 117 |
time.sleep(5)
|
| 118 |
|
| 119 |
+
def _build_session_cookies(self, session_token, domain):
|
|
|
|
| 120 |
if len(session_token) > 3800:
|
| 121 |
+
return [
|
|
|
|
|
|
|
| 122 |
{
|
| 123 |
"name": "__Secure-next-auth.session-token.0",
|
| 124 |
+
"value": session_token[:3800],
|
| 125 |
+
"domain": domain,
|
| 126 |
"path": "/",
|
| 127 |
"httpOnly": True,
|
| 128 |
"secure": True,
|
|
|
|
| 130 |
},
|
| 131 |
{
|
| 132 |
"name": "__Secure-next-auth.session-token.1",
|
| 133 |
+
"value": session_token[3800:],
|
| 134 |
+
"domain": domain,
|
| 135 |
"path": "/",
|
| 136 |
"httpOnly": True,
|
| 137 |
"secure": True,
|
| 138 |
"sameSite": "Lax",
|
| 139 |
},
|
| 140 |
]
|
| 141 |
+
return [{
|
| 142 |
+
"name": "__Secure-next-auth.session-token",
|
| 143 |
+
"value": session_token,
|
| 144 |
+
"domain": domain,
|
| 145 |
+
"path": "/",
|
| 146 |
+
"httpOnly": True,
|
| 147 |
+
"secure": True,
|
| 148 |
+
"sameSite": "Lax",
|
| 149 |
+
}]
|
| 150 |
+
|
| 151 |
+
def _click_auth_button(self, field, labels):
|
| 152 |
+
label_re = re.compile(rf"^(?:{'|'.join(re.escape(label) for label in labels)})$", re.I)
|
| 153 |
+
try:
|
| 154 |
+
form = field.locator("xpath=ancestor::form[1]").first
|
| 155 |
+
btn = form.get_by_role("button", name=label_re).first
|
| 156 |
+
if btn.is_visible(timeout=2000):
|
| 157 |
+
btn.click()
|
| 158 |
+
return True
|
| 159 |
+
except Exception:
|
| 160 |
+
pass
|
| 161 |
+
|
| 162 |
+
try:
|
| 163 |
+
form = field.locator("xpath=ancestor::form[1]").first
|
| 164 |
+
btn = form.locator('button[type="submit"], input[type="submit"]').first
|
| 165 |
+
if btn.is_visible(timeout=2000):
|
| 166 |
+
btn.click()
|
| 167 |
+
return True
|
| 168 |
+
except Exception:
|
| 169 |
+
pass
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
field.press("Enter")
|
| 173 |
+
return True
|
| 174 |
+
except Exception:
|
| 175 |
+
return False
|
| 176 |
+
|
| 177 |
+
def _extract_session_token(self):
|
| 178 |
+
cookies = self.context.cookies()
|
| 179 |
+
session_parts = {}
|
| 180 |
+
session_token = None
|
| 181 |
+
for cookie in cookies:
|
| 182 |
+
name = cookie["name"]
|
| 183 |
+
if name == "__Secure-next-auth.session-token":
|
| 184 |
+
session_token = cookie["value"]
|
| 185 |
+
elif name.startswith("__Secure-next-auth.session-token."):
|
| 186 |
+
suffix = name.rsplit(".", 1)[-1]
|
| 187 |
+
session_parts[suffix] = cookie["value"]
|
| 188 |
+
|
| 189 |
+
if not session_token and session_parts:
|
| 190 |
+
session_token = "".join(session_parts[k] for k in sorted(session_parts))
|
| 191 |
+
|
| 192 |
+
self.session_token = session_token
|
| 193 |
+
return session_token
|
| 194 |
+
|
| 195 |
+
def _inject_session(self, session_token):
|
| 196 |
+
cookies = self._build_session_cookies(session_token, "chatgpt.com")
|
| 197 |
+
if self.account_id:
|
| 198 |
+
cookies.append({
|
| 199 |
+
"name": "_account",
|
| 200 |
+
"value": self.account_id,
|
| 201 |
"domain": "chatgpt.com",
|
| 202 |
"path": "/",
|
|
|
|
| 203 |
"secure": True,
|
| 204 |
"sameSite": "Lax",
|
| 205 |
+
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
cookies.append({
|
| 207 |
"name": "oai-did",
|
| 208 |
"value": self.oai_device_id,
|
|
|
|
| 211 |
"secure": True,
|
| 212 |
"sameSite": "Lax",
|
| 213 |
})
|
|
|
|
| 214 |
self.context.add_cookies(cookies)
|
| 215 |
+
self.session_token = session_token
|
| 216 |
logger.info("[ChatGPT] 已注入 session cookies")
|
| 217 |
|
| 218 |
+
def _open_login_page(self):
|
| 219 |
+
self.page.goto("https://chatgpt.com/auth/login", wait_until="domcontentloaded", timeout=60000)
|
| 220 |
+
time.sleep(5)
|
| 221 |
+
self._wait_for_cloudflare()
|
| 222 |
+
self._log_login_state("打开登录页后")
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
login_btn = self.page.locator('button:has-text("登录"), button:has-text("Log in")').first
|
| 226 |
+
if login_btn.is_visible(timeout=3000):
|
| 227 |
+
login_btn.click()
|
| 228 |
+
time.sleep(2)
|
| 229 |
+
self._log_login_state("点击登录按钮后")
|
| 230 |
+
except Exception:
|
| 231 |
+
pass
|
| 232 |
+
|
| 233 |
+
def _list_workspace_options(self):
|
| 234 |
+
url = (self.page.url or "").lower()
|
| 235 |
+
if "workspace" not in url and "organization" not in url:
|
| 236 |
+
return []
|
| 237 |
+
|
| 238 |
+
logger.info("[ChatGPT] 检测到 workspace 选择页,开始收集组织候选 | URL=%s", self.page.url)
|
| 239 |
+
try:
|
| 240 |
+
self.page.screenshot(path=str(SCREENSHOT_DIR / "admin_login_workspace_before_select.png"), full_page=True)
|
| 241 |
+
except Exception:
|
| 242 |
+
pass
|
| 243 |
+
|
| 244 |
+
candidates = []
|
| 245 |
+
seen_texts = set()
|
| 246 |
+
exclude_keywords = (
|
| 247 |
+
"personal account",
|
| 248 |
+
"personal",
|
| 249 |
+
"个人账户",
|
| 250 |
+
"个人账号",
|
| 251 |
+
"free",
|
| 252 |
+
"免费",
|
| 253 |
+
"new organization",
|
| 254 |
+
"新组织",
|
| 255 |
+
"create organization",
|
| 256 |
+
"创建组织",
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
for selector in ('button', '[role="button"]', 'a', '[role="option"]'):
|
| 260 |
+
try:
|
| 261 |
+
for loc in self.page.locator(selector).all():
|
| 262 |
+
try:
|
| 263 |
+
if not loc.is_visible(timeout=200):
|
| 264 |
+
continue
|
| 265 |
+
text = loc.inner_text(timeout=500).strip()
|
| 266 |
+
except Exception:
|
| 267 |
+
continue
|
| 268 |
+
|
| 269 |
+
if not text or text in seen_texts:
|
| 270 |
+
continue
|
| 271 |
+
seen_texts.add(text)
|
| 272 |
+
|
| 273 |
+
text_l = text.lower()
|
| 274 |
+
if len(text) > 80:
|
| 275 |
+
continue
|
| 276 |
+
kind = "fallback" if any(key in text_l for key in exclude_keywords) else "preferred"
|
| 277 |
+
candidates.append({
|
| 278 |
+
"id": str(len(candidates)),
|
| 279 |
+
"label": text,
|
| 280 |
+
"kind": kind,
|
| 281 |
+
})
|
| 282 |
+
except Exception:
|
| 283 |
+
pass
|
| 284 |
+
|
| 285 |
+
logger.info("[ChatGPT] workspace 候选数: %d | candidates=%s", len(candidates), [c["label"] for c in candidates])
|
| 286 |
+
self.workspace_options_cache = candidates
|
| 287 |
+
return candidates
|
| 288 |
+
|
| 289 |
+
def list_workspace_options(self):
|
| 290 |
+
if self.workspace_options_cache:
|
| 291 |
+
return self.workspace_options_cache
|
| 292 |
+
return self._list_workspace_options()
|
| 293 |
+
|
| 294 |
+
def select_workspace_option(self, option_id):
|
| 295 |
+
options = self._list_workspace_options()
|
| 296 |
+
for option in options:
|
| 297 |
+
if option["id"] != str(option_id):
|
| 298 |
+
continue
|
| 299 |
+
|
| 300 |
+
label = option["label"]
|
| 301 |
+
# 重新按同样顺序扫描并点击对应索引
|
| 302 |
+
current_options = []
|
| 303 |
+
seen_texts = set()
|
| 304 |
+
for selector in ('button', '[role="button"]', 'a', '[role="option"]'):
|
| 305 |
+
try:
|
| 306 |
+
for loc in self.page.locator(selector).all():
|
| 307 |
+
try:
|
| 308 |
+
if not loc.is_visible(timeout=200):
|
| 309 |
+
continue
|
| 310 |
+
text = loc.inner_text(timeout=500).strip()
|
| 311 |
+
except Exception:
|
| 312 |
+
continue
|
| 313 |
+
if not text or text in seen_texts or len(text) > 80:
|
| 314 |
+
continue
|
| 315 |
+
seen_texts.add(text)
|
| 316 |
+
current_options.append((str(len(current_options)), text, loc))
|
| 317 |
+
except Exception:
|
| 318 |
+
pass
|
| 319 |
+
|
| 320 |
+
for cur_id, cur_label, loc in current_options:
|
| 321 |
+
if cur_id == str(option_id):
|
| 322 |
+
logger.info("[ChatGPT] 用户选择 workspace: %s", cur_label)
|
| 323 |
+
loc.click()
|
| 324 |
+
time.sleep(3)
|
| 325 |
+
self.workspace_options_cache = []
|
| 326 |
+
self._log_login_state("选择 workspace 后")
|
| 327 |
+
step, detail = self._detect_login_step()
|
| 328 |
+
logger.info("[ChatGPT] 选择 workspace 后结果: %s | detail=%s", step, detail)
|
| 329 |
+
return {"step": step, "detail": detail}
|
| 330 |
+
|
| 331 |
+
raise RuntimeError(f"未找到可点击的 workspace 选项: {label}")
|
| 332 |
+
|
| 333 |
+
raise RuntimeError(f"无效的 workspace 选项: {option_id}")
|
| 334 |
+
|
| 335 |
+
def _detect_login_step(self):
|
| 336 |
+
if "accounts.google.com" in self.page.url:
|
| 337 |
+
logger.warning("[ChatGPT] 登录步骤检测: 误跳转 Google | URL=%s", self.page.url)
|
| 338 |
+
return "error", "误跳转到了 Google 登录"
|
| 339 |
+
|
| 340 |
+
if "workspace" in (self.page.url or "").lower() or "organization" in (self.page.url or "").lower():
|
| 341 |
+
logger.info("[ChatGPT] 登录步骤检测: workspace 页面 | URL=%s", self.page.url)
|
| 342 |
+
return "workspace_required", None
|
| 343 |
+
|
| 344 |
+
if "email-verification" in self.page.url:
|
| 345 |
+
logger.info("[ChatGPT] 登录步骤检测: code_required | URL=%s", self.page.url)
|
| 346 |
+
return "code_required", None
|
| 347 |
+
|
| 348 |
+
if self._visible_locator_in_frames(self.CODE_INPUT_SELECTORS, timeout_ms=1200):
|
| 349 |
+
logger.info("[ChatGPT] 登录步骤检测: code_required | URL=%s", self.page.url)
|
| 350 |
+
return "code_required", None
|
| 351 |
+
|
| 352 |
+
if self._visible_locator_in_frames(self.PASSWORD_INPUT_SELECTORS, timeout_ms=1200):
|
| 353 |
+
logger.info("[ChatGPT] 登录步骤检测: password_required | URL=%s", self.page.url)
|
| 354 |
+
return "password_required", None
|
| 355 |
+
|
| 356 |
+
session_token = self._extract_session_token()
|
| 357 |
+
if session_token:
|
| 358 |
+
logger.info("[ChatGPT] 登录步骤检测: completed(session) | URL=%s", self.page.url)
|
| 359 |
+
return "completed", None
|
| 360 |
+
|
| 361 |
+
if "chatgpt.com" in self.page.url and "auth" not in self.page.url:
|
| 362 |
+
logger.info("[ChatGPT] 登录步骤检测: completed(chatgpt) | URL=%s", self.page.url)
|
| 363 |
+
return "completed", None
|
| 364 |
+
|
| 365 |
+
logger.info("[ChatGPT] 登录步骤检测: unknown | URL=%s", self.page.url)
|
| 366 |
+
return "unknown", self.page.url
|
| 367 |
+
|
| 368 |
+
def begin_admin_login(self, email):
|
| 369 |
+
self.login_email = email
|
| 370 |
+
if not self.browser:
|
| 371 |
+
self._launch_browser()
|
| 372 |
+
|
| 373 |
+
logger.info("[ChatGPT] 开始管理员���录: %s", email)
|
| 374 |
+
self.page.goto("https://chatgpt.com/", wait_until="domcontentloaded", timeout=60000)
|
| 375 |
+
time.sleep(5)
|
| 376 |
+
self._wait_for_cloudflare()
|
| 377 |
+
self._log_login_state("进入 chatgpt.com 后")
|
| 378 |
+
self._open_login_page()
|
| 379 |
+
|
| 380 |
+
step, detail = self._detect_login_step()
|
| 381 |
+
if step == "workspace_required":
|
| 382 |
+
self._list_workspace_options()
|
| 383 |
+
if step in ("password_required", "code_required", "workspace_required", "completed", "error"):
|
| 384 |
+
logger.info("[ChatGPT] 管理员登录初始步骤: %s | detail=%s", step, detail)
|
| 385 |
+
return {"step": step, "detail": detail}
|
| 386 |
+
|
| 387 |
+
email_input = self._visible_locator_in_frames(self.EMAIL_INPUT_SELECTORS, timeout_ms=15000)
|
| 388 |
+
if not email_input:
|
| 389 |
+
try:
|
| 390 |
+
self.page.screenshot(path=str(SCREENSHOT_DIR / "admin_login_missing_email.png"), full_page=True)
|
| 391 |
+
except Exception:
|
| 392 |
+
pass
|
| 393 |
+
body_excerpt = ""
|
| 394 |
+
try:
|
| 395 |
+
body_excerpt = self.page.locator("body").inner_text(timeout=2000)[:300]
|
| 396 |
+
except Exception:
|
| 397 |
+
pass
|
| 398 |
+
raise RuntimeError(f"未找到管理员邮箱输入框,当前 URL: {self.page.url},页面片段: {body_excerpt}")
|
| 399 |
+
|
| 400 |
+
email_input.fill(email)
|
| 401 |
+
time.sleep(0.5)
|
| 402 |
+
self._click_auth_button(email_input, ["Continue", "继续"])
|
| 403 |
+
time.sleep(3)
|
| 404 |
+
self._log_login_state("管理员邮箱提交后")
|
| 405 |
+
|
| 406 |
+
step, detail = self._detect_login_step()
|
| 407 |
+
if step == "workspace_required":
|
| 408 |
+
self._list_workspace_options()
|
| 409 |
+
logger.info("[ChatGPT] 管理员邮箱提交结果: %s | detail=%s", step, detail)
|
| 410 |
+
return {"step": step, "detail": detail}
|
| 411 |
+
|
| 412 |
+
def submit_admin_password(self, password):
|
| 413 |
+
self.login_password = password
|
| 414 |
+
password_input = self._visible_locator_in_frames(self.PASSWORD_INPUT_SELECTORS, timeout_ms=5000)
|
| 415 |
+
if not password_input:
|
| 416 |
+
raise RuntimeError("当前不是密码输入步骤")
|
| 417 |
+
|
| 418 |
+
logger.info("[ChatGPT] 提交管理员密码前 | URL=%s", self.page.url)
|
| 419 |
+
password_input.fill(password)
|
| 420 |
+
time.sleep(0.5)
|
| 421 |
+
self._click_auth_button(password_input, ["Continue", "继续", "Log in"])
|
| 422 |
+
time.sleep(8)
|
| 423 |
+
self._log_login_state("管理员密码提交后")
|
| 424 |
+
|
| 425 |
+
step, detail = self._detect_login_step()
|
| 426 |
+
if step == "workspace_required":
|
| 427 |
+
self._list_workspace_options()
|
| 428 |
+
logger.info("[ChatGPT] 管理员密码提交结果: %s | detail=%s", step, detail)
|
| 429 |
+
return {"step": step, "detail": detail}
|
| 430 |
+
|
| 431 |
+
def submit_admin_code(self, code):
|
| 432 |
+
code_input = self._visible_locator_in_frames(self.CODE_INPUT_SELECTORS, timeout_ms=5000)
|
| 433 |
+
if not code_input and "email-verification" not in self.page.url:
|
| 434 |
+
raise RuntimeError("当前不是验证码输入步骤")
|
| 435 |
+
|
| 436 |
+
logger.info("[ChatGPT] 提交管理员验证码前 | URL=%s | code_len=%d", self.page.url, len(code))
|
| 437 |
+
try:
|
| 438 |
+
self.page.screenshot(path=str(SCREENSHOT_DIR / "admin_login_code_before_submit.png"), full_page=True)
|
| 439 |
+
except Exception:
|
| 440 |
+
pass
|
| 441 |
+
code_input.fill(code)
|
| 442 |
+
time.sleep(0.5)
|
| 443 |
+
self._click_auth_button(code_input, ["Continue", "继续", "Verify"])
|
| 444 |
+
time.sleep(8)
|
| 445 |
+
try:
|
| 446 |
+
self.page.screenshot(path=str(SCREENSHOT_DIR / "admin_login_code_after_submit.png"), full_page=True)
|
| 447 |
+
except Exception:
|
| 448 |
+
pass
|
| 449 |
+
self._log_login_state("管理员验证码提交后")
|
| 450 |
+
|
| 451 |
+
step, detail = self._detect_login_step()
|
| 452 |
+
if step == "workspace_required":
|
| 453 |
+
self._list_workspace_options()
|
| 454 |
+
logger.info("[ChatGPT] 管理员验证码提交结果: %s | detail=%s", step, detail)
|
| 455 |
+
return {"step": step, "detail": detail}
|
| 456 |
+
|
| 457 |
+
def _guess_account_info(self):
|
| 458 |
+
try:
|
| 459 |
+
data = self.page.evaluate('''async () => {
|
| 460 |
+
const out = {};
|
| 461 |
+
for (const path of ['/backend-api/accounts', '/backend-api/me', '/api/auth/session']) {
|
| 462 |
+
try {
|
| 463 |
+
const resp = await fetch(path);
|
| 464 |
+
out[path] = { status: resp.status, data: await resp.json() };
|
| 465 |
+
} catch (e) {
|
| 466 |
+
out[path] = { error: String(e) };
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
return out;
|
| 470 |
+
}''')
|
| 471 |
+
except Exception:
|
| 472 |
+
data = {}
|
| 473 |
+
|
| 474 |
+
candidates = []
|
| 475 |
+
|
| 476 |
+
def walk(node):
|
| 477 |
+
if isinstance(node, dict):
|
| 478 |
+
account_id = node.get("account_id") or node.get("id")
|
| 479 |
+
name = node.get("workspace_name") or node.get("name") or node.get("display_name")
|
| 480 |
+
if isinstance(account_id, str) and len(account_id) >= 8:
|
| 481 |
+
candidates.append({
|
| 482 |
+
"account_id": account_id,
|
| 483 |
+
"workspace_name": name or "",
|
| 484 |
+
"type": str(node.get("type", "")),
|
| 485 |
+
})
|
| 486 |
+
for value in node.values():
|
| 487 |
+
walk(value)
|
| 488 |
+
elif isinstance(node, list):
|
| 489 |
+
for item in node:
|
| 490 |
+
walk(item)
|
| 491 |
+
|
| 492 |
+
walk(data)
|
| 493 |
+
|
| 494 |
+
chosen = None
|
| 495 |
+
for cand in candidates:
|
| 496 |
+
if cand["workspace_name"] and cand["workspace_name"].lower() not in ("personal",):
|
| 497 |
+
chosen = cand
|
| 498 |
+
break
|
| 499 |
+
if not chosen and candidates:
|
| 500 |
+
chosen = candidates[0]
|
| 501 |
+
|
| 502 |
+
try:
|
| 503 |
+
self.page.goto("https://chatgpt.com/admin", wait_until="domcontentloaded", timeout=30000)
|
| 504 |
+
time.sleep(5)
|
| 505 |
+
dom_name = self.page.evaluate('''() => {
|
| 506 |
+
const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [class*="name"]');
|
| 507 |
+
for (const h of headings) {
|
| 508 |
+
const text = h.textContent.trim();
|
| 509 |
+
if (text && text.length < 50 && text.length > 1
|
| 510 |
+
&& !["常规", "成员", "设置", "General", "Members", "Settings"].includes(text)) {
|
| 511 |
+
return text;
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
return null;
|
| 515 |
+
}''')
|
| 516 |
+
except Exception:
|
| 517 |
+
dom_name = None
|
| 518 |
+
|
| 519 |
+
account_id = (chosen or {}).get("account_id") or self.account_id
|
| 520 |
+
workspace_name = (chosen or {}).get("workspace_name") or dom_name or self.workspace_name
|
| 521 |
+
return account_id, workspace_name
|
| 522 |
+
|
| 523 |
+
def complete_admin_login(self):
|
| 524 |
+
session_token = self._extract_session_token()
|
| 525 |
+
if not session_token:
|
| 526 |
+
raise RuntimeError("管理员登录成功后未提取到 session token")
|
| 527 |
+
|
| 528 |
self._fetch_access_token()
|
| 529 |
+
account_id, workspace_name = self._guess_account_info()
|
| 530 |
+
if account_id:
|
| 531 |
+
self.account_id = account_id
|
| 532 |
+
if workspace_name:
|
| 533 |
+
self.workspace_name = workspace_name
|
| 534 |
+
|
| 535 |
+
payload = dict(
|
| 536 |
+
email=self.login_email or "",
|
| 537 |
+
session_token=session_token,
|
| 538 |
+
account_id=self.account_id,
|
| 539 |
+
workspace_name=self.workspace_name,
|
| 540 |
+
)
|
| 541 |
+
if self.login_password:
|
| 542 |
+
payload["password"] = self.login_password
|
| 543 |
|
| 544 |
+
update_admin_state(**payload)
|
|
|
|
| 545 |
|
| 546 |
+
logger.info("[ChatGPT] 管理员登录状态已保存")
|
| 547 |
+
return {
|
| 548 |
+
"email": self.login_email or "",
|
| 549 |
+
"account_id": self.account_id,
|
| 550 |
+
"workspace_name": self.workspace_name,
|
| 551 |
+
"session_len": len(session_token),
|
| 552 |
+
}
|
| 553 |
|
| 554 |
+
def start(self):
|
| 555 |
+
"""用已保存的管理员 session 启动 Team API 客户端。"""
|
| 556 |
+
session_token = get_admin_session_token()
|
| 557 |
+
self.account_id = get_chatgpt_account_id()
|
| 558 |
+
self.workspace_name = get_chatgpt_workspace_name()
|
| 559 |
+
if not session_token:
|
| 560 |
+
raise FileNotFoundError("请先完成管理员登录")
|
| 561 |
+
if not self.account_id:
|
| 562 |
+
raise RuntimeError("缺少已保存的 workspace/account ID,请重新完成管理员登录")
|
| 563 |
+
|
| 564 |
+
self._launch_browser()
|
| 565 |
+
logger.info("[ChatGPT] 访问 chatgpt.com 过 Cloudflare...")
|
| 566 |
+
self.page.goto("https://chatgpt.com/", wait_until="domcontentloaded", timeout=60000)
|
| 567 |
+
time.sleep(5)
|
| 568 |
+
self._wait_for_cloudflare()
|
| 569 |
+
self._inject_session(session_token)
|
| 570 |
+
self._fetch_access_token()
|
| 571 |
+
self._auto_detect_workspace()
|
| 572 |
|
| 573 |
+
def _auto_detect_workspace(self):
|
| 574 |
+
if self.workspace_name:
|
| 575 |
+
return self.workspace_name
|
| 576 |
+
if not self.account_id:
|
| 577 |
+
logger.warning("[ChatGPT] 未能自动获取 workspace 名称:account_id 缺失")
|
| 578 |
+
return ""
|
| 579 |
|
|
|
|
|
|
|
|
|
|
| 580 |
result = self.page.evaluate('''async (accountId) => {
|
| 581 |
try {
|
| 582 |
const resp = await fetch("/backend-api/accounts/" + accountId + "/settings", {
|
|
|
|
| 587 |
}''', self.account_id)
|
| 588 |
|
| 589 |
if result and result.get("workspace_name"):
|
| 590 |
+
self.workspace_name = result["workspace_name"]
|
| 591 |
+
update_admin_state(workspace_name=self.workspace_name, account_id=self.account_id)
|
| 592 |
+
logger.info("[ChatGPT] 自动检测到 workspace 名称: %s", self.workspace_name)
|
| 593 |
+
return self.workspace_name
|
| 594 |
|
|
|
|
| 595 |
try:
|
| 596 |
self.page.goto("https://chatgpt.com/admin", wait_until="domcontentloaded", timeout=30000)
|
| 597 |
+
time.sleep(5)
|
|
|
|
|
|
|
| 598 |
name = self.page.evaluate('''() => {
|
|
|
|
|
|
|
| 599 |
const headings = document.querySelectorAll('h1, h2, h3, [class*="title"], [class*="name"]');
|
| 600 |
for (const h of headings) {
|
| 601 |
const text = h.textContent.trim();
|
|
|
|
| 602 |
if (text && text.length < 50 && text.length > 1
|
| 603 |
&& !["常规", "成员", "设置", "General", "Members", "Settings"].includes(text)) {
|
| 604 |
return text;
|
|
|
|
| 607 |
return null;
|
| 608 |
}''')
|
| 609 |
if name:
|
| 610 |
+
self.workspace_name = name
|
| 611 |
+
update_admin_state(workspace_name=self.workspace_name, account_id=self.account_id)
|
| 612 |
logger.info("[ChatGPT] 自动检测到 workspace 名称: %s", name)
|
| 613 |
+
return name
|
| 614 |
except Exception:
|
| 615 |
pass
|
| 616 |
|
| 617 |
+
logger.warning("[ChatGPT] 未能自动获取 workspace 名称")
|
| 618 |
+
return ""
|
| 619 |
|
| 620 |
def _fetch_access_token(self):
|
|
|
|
| 621 |
result = self.page.evaluate('''async () => {
|
| 622 |
try {
|
| 623 |
const resp = await fetch("/api/auth/session");
|
| 624 |
const data = await resp.json();
|
| 625 |
return { ok: true, data: data };
|
| 626 |
} catch(e) {
|
|
|
|
| 627 |
return { ok: false, error: e.message };
|
| 628 |
}
|
| 629 |
}''')
|
|
|
|
| 633 |
logger.info("[ChatGPT] 已获取 access token")
|
| 634 |
return
|
| 635 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
bearer_file = BASE_DIR / "bearer_token"
|
| 637 |
if bearer_file.exists():
|
| 638 |
self.access_token = bearer_file.read_text().strip()
|
| 639 |
logger.info("[ChatGPT] 从 bearer_token 文件加载 access token")
|
| 640 |
return
|
| 641 |
|
|
|
|
| 642 |
logger.info("[ChatGPT] 尝试通过页面获取 access token...")
|
| 643 |
self.page.goto("https://chatgpt.com/", wait_until="networkidle", timeout=60000)
|
| 644 |
time.sleep(10)
|
| 645 |
|
| 646 |
token = self.page.evaluate('''() => {
|
|
|
|
| 647 |
try {
|
| 648 |
const keys = Object.keys(localStorage);
|
| 649 |
for (const key of keys) {
|
|
|
|
| 653 |
}
|
| 654 |
}
|
| 655 |
} catch(e) {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
return null;
|
| 657 |
}''')
|
| 658 |
|
|
|
|
| 663 |
logger.warning("[ChatGPT] 未能获取 access token,将尝试无 token 调用")
|
| 664 |
|
| 665 |
def _api_fetch(self, method, path, body=None):
|
|
|
|
| 666 |
headers_js = {
|
| 667 |
"Content-Type": "application/json",
|
| 668 |
"chatgpt-account-id": self.account_id,
|
|
|
|
| 684 |
}
|
| 685 |
}'''
|
| 686 |
|
| 687 |
+
return self.page.evaluate(
|
| 688 |
js_code,
|
| 689 |
[method, f"https://chatgpt.com{path}", headers_js, json.dumps(body) if body else None],
|
| 690 |
)
|
|
|
|
| 691 |
|
| 692 |
def invite_member(self, email, seat_type="usage_based"):
|
|
|
|
| 693 |
path = f"/backend-api/accounts/{self.account_id}/invites"
|
| 694 |
body = {
|
| 695 |
"email_addresses": [email],
|
|
|
|
| 703 |
|
| 704 |
status = result["status"]
|
| 705 |
resp_body = result["body"]
|
|
|
|
| 706 |
logger.info("[ChatGPT] 响应状态: %d", status)
|
| 707 |
|
| 708 |
try:
|
|
|
|
| 712 |
data = resp_body
|
| 713 |
logger.debug("[ChatGPT] 响应内容: %s", resp_body[:500])
|
| 714 |
|
|
|
|
| 715 |
if status == 200 and seat_type == "usage_based" and isinstance(data, dict):
|
| 716 |
invites = data.get("account_invites", [])
|
| 717 |
for inv in invites:
|
|
|
|
| 722 |
return status, data
|
| 723 |
|
| 724 |
def _update_invite_seat_type(self, invite_id, seat_type):
|
|
|
|
| 725 |
path = f"/backend-api/accounts/{self.account_id}/invites/{invite_id}"
|
| 726 |
body = {"seat_type": seat_type}
|
| 727 |
|
|
|
|
| 734 |
logger.error("[ChatGPT] 修改 seat_type 失败: %d %s", result['status'], result['body'][:200])
|
| 735 |
|
| 736 |
def list_invites(self):
|
|
|
|
| 737 |
path = f"/backend-api/accounts/{self.account_id}/invites"
|
| 738 |
result = self._api_fetch("GET", path)
|
| 739 |
try:
|
|
|
|
| 742 |
return result["body"]
|
| 743 |
|
| 744 |
def stop(self):
|
|
|
|
| 745 |
try:
|
| 746 |
if self.browser:
|
| 747 |
self.browser.close()
|
|
|
|
| 753 |
except Exception:
|
| 754 |
pass
|
| 755 |
self.browser = None
|
| 756 |
+
self.context = None
|
| 757 |
+
self.page = None
|
| 758 |
self.playwright = None
|
src/autoteam/codex_auth.py
CHANGED
|
@@ -7,11 +7,21 @@ import logging
|
|
| 7 |
import time
|
| 8 |
import base64
|
| 9 |
import os
|
|
|
|
| 10 |
import secrets
|
| 11 |
import urllib.parse
|
| 12 |
from pathlib import Path
|
| 13 |
from playwright.sync_api import sync_playwright
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
@@ -54,6 +64,128 @@ def _screenshot(page, name):
|
|
| 54 |
page.screenshot(path=str(SCREENSHOT_DIR / name), full_page=True)
|
| 55 |
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
def login_codex_via_browser(email, password, mail_client=None):
|
| 58 |
"""
|
| 59 |
通过 Playwright 自动完成 Codex OAuth 登录。
|
|
@@ -63,20 +195,9 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 63 |
code_verifier, code_challenge = _generate_pkce()
|
| 64 |
state = secrets.token_urlsafe(16)
|
| 65 |
|
| 66 |
-
|
| 67 |
|
| 68 |
-
|
| 69 |
-
params = {
|
| 70 |
-
"client_id": CODEX_CLIENT_ID,
|
| 71 |
-
"response_type": "code",
|
| 72 |
-
"redirect_uri": CODEX_REDIRECT_URI,
|
| 73 |
-
"scope": "openid email profile offline_access",
|
| 74 |
-
"state": state,
|
| 75 |
-
"code_challenge": code_challenge,
|
| 76 |
-
"code_challenge_method": "S256",
|
| 77 |
-
"prompt": "consent", # 强制显示 consent 页面(重新选 workspace)
|
| 78 |
-
}
|
| 79 |
-
auth_url = f"{CODEX_AUTH_URL}?{urllib.parse.urlencode(params)}"
|
| 80 |
|
| 81 |
logger.info("[Codex] 开始 OAuth 登录: %s", email)
|
| 82 |
|
|
@@ -94,23 +215,23 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 94 |
|
| 95 |
# === Step 0: 先登录 ChatGPT 并切换到 Team workspace ===
|
| 96 |
# 登录前就注入 _account cookie,引导登录流程进入 Team workspace
|
| 97 |
-
if
|
| 98 |
context.add_cookies([{
|
| 99 |
"name": "_account",
|
| 100 |
-
"value":
|
| 101 |
"domain": "chatgpt.com",
|
| 102 |
"path": "/",
|
| 103 |
"secure": True,
|
| 104 |
"sameSite": "Lax",
|
| 105 |
}, {
|
| 106 |
"name": "_account",
|
| 107 |
-
"value":
|
| 108 |
"domain": "auth.openai.com",
|
| 109 |
"path": "/",
|
| 110 |
"secure": True,
|
| 111 |
"sameSite": "Lax",
|
| 112 |
}])
|
| 113 |
-
logger.debug("[Codex] 登录前已注入 _account cookie = %s",
|
| 114 |
|
| 115 |
logger.info("[Codex] 先登录 ChatGPT 选择 Team workspace...")
|
| 116 |
_page = context.new_page()
|
|
@@ -136,11 +257,7 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 136 |
if ei.is_visible(timeout=5000):
|
| 137 |
ei.fill(email)
|
| 138 |
time.sleep(0.5)
|
| 139 |
-
|
| 140 |
-
if submit.is_visible(timeout=3000):
|
| 141 |
-
submit.click()
|
| 142 |
-
else:
|
| 143 |
-
_page.locator('button:has-text("Continue"), button:has-text("继续")').last.click()
|
| 144 |
time.sleep(3)
|
| 145 |
except Exception:
|
| 146 |
pass
|
|
@@ -151,11 +268,7 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 151 |
if pi.is_visible(timeout=5000):
|
| 152 |
pi.fill(password)
|
| 153 |
time.sleep(0.5)
|
| 154 |
-
|
| 155 |
-
if submit.is_visible(timeout=3000):
|
| 156 |
-
submit.click()
|
| 157 |
-
else:
|
| 158 |
-
_page.locator('button:has-text("Continue"), button:has-text("继续")').last.click()
|
| 159 |
time.sleep(8)
|
| 160 |
except Exception:
|
| 161 |
pass
|
|
@@ -191,11 +304,12 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 191 |
|
| 192 |
# 如果是 workspace 选择页面,选择 Team
|
| 193 |
if "workspace" in _page.url:
|
|
|
|
| 194 |
logger.info("[Codex] 检测到 workspace 选择页面...")
|
| 195 |
try:
|
| 196 |
-
ws_btn = _page.locator(f'text="{
|
| 197 |
-
if
|
| 198 |
-
logger.info("[Codex] 选择 workspace: %s",
|
| 199 |
ws_btn.click()
|
| 200 |
time.sleep(5)
|
| 201 |
else:
|
|
@@ -251,37 +365,48 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 251 |
_screenshot(page, "codex_01_auth_page.png")
|
| 252 |
|
| 253 |
# 输入邮箱(注意避免点到 Google/Microsoft/Apple 第三方登录按钮)
|
| 254 |
-
email_input = page.locator('input[name="email"], input[id="email-input"], input[id="email"]').first
|
| 255 |
try:
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
email_input.fill(email)
|
| 258 |
time.sleep(0.5)
|
| 259 |
-
|
| 260 |
-
submit_btn = email_input.locator("xpath=ancestor::form//button[contains(text(),'Continue') or @type='submit']").first
|
| 261 |
-
if submit_btn.is_visible(timeout=3000):
|
| 262 |
-
submit_btn.click()
|
| 263 |
-
else:
|
| 264 |
-
# fallback: 页面上最后一个 Continue 按钮(第三方登录按钮在上方)
|
| 265 |
-
page.locator('button:has-text("Continue")').last.click()
|
| 266 |
time.sleep(3)
|
| 267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
except Exception:
|
| 269 |
_screenshot(page, "codex_02_no_email.png")
|
| 270 |
|
| 271 |
# 输入密码
|
| 272 |
-
pwd_input = page.locator('input[name="password"], input[type="password"]').first
|
| 273 |
try:
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
pwd_input.fill(password)
|
| 276 |
time.sleep(0.5)
|
| 277 |
-
|
| 278 |
-
submit_btn = pwd_input.locator("xpath=ancestor::form//button[contains(text(),'Continue') or contains(text(),'Log in') or @type='submit']").first
|
| 279 |
-
if submit_btn.is_visible(timeout=3000):
|
| 280 |
-
submit_btn.click()
|
| 281 |
-
else:
|
| 282 |
-
page.locator('button:has-text("Continue"), button:has-text("Log in")').last.click()
|
| 283 |
time.sleep(5)
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
except Exception:
|
| 286 |
_screenshot(page, "codex_03_no_password.png")
|
| 287 |
|
|
@@ -380,14 +505,14 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 380 |
page_text = page.inner_text("body")[:1000]
|
| 381 |
|
| 382 |
# 选择 Team workspace(用配置的名称精确匹配)
|
| 383 |
-
|
| 384 |
-
if
|
| 385 |
try:
|
| 386 |
-
ws_btn = page.locator(f'text="{
|
| 387 |
if ws_btn.is_visible(timeout=2000):
|
| 388 |
ws_btn.click()
|
| 389 |
time.sleep(1)
|
| 390 |
-
logger.info("[Codex] 已选择 workspace: %s (step %d)",
|
| 391 |
except Exception:
|
| 392 |
pass
|
| 393 |
|
|
@@ -479,39 +604,473 @@ def login_codex_via_browser(email, password, mail_client=None):
|
|
| 479 |
logger.error("[Codex] OAuth 登录失败: 未获取到 authorization code")
|
| 480 |
return None
|
| 481 |
|
| 482 |
-
|
| 483 |
|
| 484 |
-
# 用 auth code 换 token
|
| 485 |
-
import requests
|
| 486 |
-
resp = requests.post(CODEX_TOKEN_URL, data={
|
| 487 |
-
"grant_type": "authorization_code",
|
| 488 |
-
"client_id": CODEX_CLIENT_ID,
|
| 489 |
-
"code": auth_code,
|
| 490 |
-
"redirect_uri": CODEX_REDIRECT_URI,
|
| 491 |
-
"code_verifier": code_verifier,
|
| 492 |
-
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
|
| 493 |
|
| 494 |
-
|
| 495 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
return None
|
| 497 |
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
-
|
| 514 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
|
| 516 |
|
| 517 |
def save_auth_file(bundle):
|
|
@@ -530,23 +1089,19 @@ def save_auth_file(bundle):
|
|
| 530 |
|
| 531 |
filename = f"codex-{email}-{plan_type}-{hash_id}.json"
|
| 532 |
filepath = AUTH_DIR / filename
|
|
|
|
| 533 |
|
| 534 |
-
auth_data = {
|
| 535 |
-
"type": "codex",
|
| 536 |
-
"id_token": bundle.get("id_token", ""),
|
| 537 |
-
"access_token": bundle.get("access_token", ""),
|
| 538 |
-
"refresh_token": bundle.get("refresh_token", ""),
|
| 539 |
-
"account_id": account_id,
|
| 540 |
-
"email": email,
|
| 541 |
-
"expired": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(bundle.get("expired", 0))),
|
| 542 |
-
"last_refresh": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 543 |
-
}
|
| 544 |
|
| 545 |
-
|
| 546 |
-
|
|
|
|
| 547 |
|
| 548 |
-
|
| 549 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 550 |
|
| 551 |
|
| 552 |
def check_codex_quota(access_token, account_id=None):
|
|
@@ -558,8 +1113,7 @@ def check_codex_quota(access_token, account_id=None):
|
|
| 558 |
import requests
|
| 559 |
|
| 560 |
if not account_id:
|
| 561 |
-
|
| 562 |
-
account_id = CHATGPT_ACCOUNT_ID
|
| 563 |
|
| 564 |
headers = {
|
| 565 |
"Authorization": f"Bearer {access_token}",
|
|
|
|
| 7 |
import time
|
| 8 |
import base64
|
| 9 |
import os
|
| 10 |
+
import re
|
| 11 |
import secrets
|
| 12 |
import urllib.parse
|
| 13 |
from pathlib import Path
|
| 14 |
from playwright.sync_api import sync_playwright
|
| 15 |
|
| 16 |
+
from autoteam.admin_state import (
|
| 17 |
+
get_admin_email,
|
| 18 |
+
get_admin_password,
|
| 19 |
+
get_admin_session_token,
|
| 20 |
+
get_chatgpt_account_id,
|
| 21 |
+
get_chatgpt_workspace_name,
|
| 22 |
+
update_admin_state,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
logger = logging.getLogger(__name__)
|
| 26 |
|
| 27 |
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
|
|
| 64 |
page.screenshot(path=str(SCREENSHOT_DIR / name), full_page=True)
|
| 65 |
|
| 66 |
|
| 67 |
+
def _build_auth_url(code_challenge, state):
|
| 68 |
+
params = {
|
| 69 |
+
"client_id": CODEX_CLIENT_ID,
|
| 70 |
+
"response_type": "code",
|
| 71 |
+
"redirect_uri": CODEX_REDIRECT_URI,
|
| 72 |
+
"scope": "openid email profile offline_access",
|
| 73 |
+
"state": state,
|
| 74 |
+
"code_challenge": code_challenge,
|
| 75 |
+
"code_challenge_method": "S256",
|
| 76 |
+
"prompt": "consent",
|
| 77 |
+
}
|
| 78 |
+
return f"{CODEX_AUTH_URL}?{urllib.parse.urlencode(params)}"
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def _exchange_auth_code(auth_code, code_verifier, fallback_email=None):
|
| 82 |
+
logger.info("[Codex] 获取到 auth code,交换 token...")
|
| 83 |
+
|
| 84 |
+
import requests
|
| 85 |
+
|
| 86 |
+
resp = requests.post(CODEX_TOKEN_URL, data={
|
| 87 |
+
"grant_type": "authorization_code",
|
| 88 |
+
"client_id": CODEX_CLIENT_ID,
|
| 89 |
+
"code": auth_code,
|
| 90 |
+
"redirect_uri": CODEX_REDIRECT_URI,
|
| 91 |
+
"code_verifier": code_verifier,
|
| 92 |
+
}, headers={"Content-Type": "application/x-www-form-urlencoded"})
|
| 93 |
+
|
| 94 |
+
if resp.status_code != 200:
|
| 95 |
+
logger.error("[Codex] Token 交换失败: %d %s", resp.status_code, resp.text[:200])
|
| 96 |
+
return None
|
| 97 |
+
|
| 98 |
+
token_data = resp.json()
|
| 99 |
+
id_token = token_data.get("id_token", "")
|
| 100 |
+
claims = _parse_jwt_payload(id_token)
|
| 101 |
+
auth_claims = claims.get("https://api.openai.com/auth", {})
|
| 102 |
+
|
| 103 |
+
bundle = {
|
| 104 |
+
"access_token": token_data.get("access_token"),
|
| 105 |
+
"refresh_token": token_data.get("refresh_token"),
|
| 106 |
+
"id_token": id_token,
|
| 107 |
+
"account_id": auth_claims.get("chatgpt_account_id", ""),
|
| 108 |
+
"email": claims.get("email", fallback_email or ""),
|
| 109 |
+
"plan_type": auth_claims.get("chatgpt_plan_type", "unknown"),
|
| 110 |
+
"expired": time.time() + token_data.get("expires_in", 3600),
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
logger.info("[Codex] 登录成功: %s (plan: %s)", bundle["email"], bundle["plan_type"])
|
| 114 |
+
return bundle
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _write_auth_file(filepath, bundle):
|
| 118 |
+
filepath = Path(filepath)
|
| 119 |
+
filepath.parent.mkdir(exist_ok=True)
|
| 120 |
+
|
| 121 |
+
auth_data = {
|
| 122 |
+
"type": "codex",
|
| 123 |
+
"id_token": bundle.get("id_token", ""),
|
| 124 |
+
"access_token": bundle.get("access_token", ""),
|
| 125 |
+
"refresh_token": bundle.get("refresh_token", ""),
|
| 126 |
+
"account_id": bundle.get("account_id", ""),
|
| 127 |
+
"email": bundle.get("email", ""),
|
| 128 |
+
"expired": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(bundle.get("expired", 0))),
|
| 129 |
+
"last_refresh": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
filepath.write_text(json.dumps(auth_data, indent=2))
|
| 133 |
+
os.chmod(filepath, 0o600)
|
| 134 |
+
logger.info("[Codex] 认证文件已保存: %s", filepath)
|
| 135 |
+
return str(filepath)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def _click_primary_auth_button(page, field, labels):
|
| 139 |
+
"""
|
| 140 |
+
只点击当前输入框所在表单的主按钮,避免误点 Continue with Google/Apple/Microsoft。
|
| 141 |
+
"""
|
| 142 |
+
label_re = re.compile(rf"^(?:{'|'.join(re.escape(label) for label in labels)})$", re.I)
|
| 143 |
+
|
| 144 |
+
try:
|
| 145 |
+
form = field.locator("xpath=ancestor::form[1]").first
|
| 146 |
+
btn = form.get_by_role("button", name=label_re).first
|
| 147 |
+
if btn.is_visible(timeout=2000):
|
| 148 |
+
btn.click()
|
| 149 |
+
return True
|
| 150 |
+
except Exception:
|
| 151 |
+
pass
|
| 152 |
+
|
| 153 |
+
try:
|
| 154 |
+
form = field.locator("xpath=ancestor::form[1]").first
|
| 155 |
+
btn = form.locator('button[type="submit"], input[type="submit"]').first
|
| 156 |
+
if btn.is_visible(timeout=2000):
|
| 157 |
+
btn.click()
|
| 158 |
+
return True
|
| 159 |
+
except Exception:
|
| 160 |
+
pass
|
| 161 |
+
|
| 162 |
+
try:
|
| 163 |
+
btn = page.get_by_role("button", name=label_re).last
|
| 164 |
+
if btn.is_visible(timeout=2000):
|
| 165 |
+
btn.click()
|
| 166 |
+
return True
|
| 167 |
+
except Exception:
|
| 168 |
+
pass
|
| 169 |
+
|
| 170 |
+
try:
|
| 171 |
+
field.press("Enter")
|
| 172 |
+
return True
|
| 173 |
+
except Exception:
|
| 174 |
+
return False
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def _is_google_redirect(page):
|
| 178 |
+
url = (page.url or "").lower()
|
| 179 |
+
if "accounts.google.com" in url:
|
| 180 |
+
return True
|
| 181 |
+
|
| 182 |
+
try:
|
| 183 |
+
text = page.locator("body").inner_text(timeout=1000).lower()
|
| 184 |
+
return "sign in with google" in text[:300]
|
| 185 |
+
except Exception:
|
| 186 |
+
return False
|
| 187 |
+
|
| 188 |
+
|
| 189 |
def login_codex_via_browser(email, password, mail_client=None):
|
| 190 |
"""
|
| 191 |
通过 Playwright 自动完成 Codex OAuth 登录。
|
|
|
|
| 195 |
code_verifier, code_challenge = _generate_pkce()
|
| 196 |
state = secrets.token_urlsafe(16)
|
| 197 |
|
| 198 |
+
chatgpt_account_id = get_chatgpt_account_id()
|
| 199 |
|
| 200 |
+
auth_url = _build_auth_url(code_challenge, state)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
logger.info("[Codex] 开始 OAuth 登录: %s", email)
|
| 203 |
|
|
|
|
| 215 |
|
| 216 |
# === Step 0: 先登录 ChatGPT 并切换到 Team workspace ===
|
| 217 |
# 登录前就注入 _account cookie,引导登录流程进入 Team workspace
|
| 218 |
+
if chatgpt_account_id:
|
| 219 |
context.add_cookies([{
|
| 220 |
"name": "_account",
|
| 221 |
+
"value": chatgpt_account_id,
|
| 222 |
"domain": "chatgpt.com",
|
| 223 |
"path": "/",
|
| 224 |
"secure": True,
|
| 225 |
"sameSite": "Lax",
|
| 226 |
}, {
|
| 227 |
"name": "_account",
|
| 228 |
+
"value": chatgpt_account_id,
|
| 229 |
"domain": "auth.openai.com",
|
| 230 |
"path": "/",
|
| 231 |
"secure": True,
|
| 232 |
"sameSite": "Lax",
|
| 233 |
}])
|
| 234 |
+
logger.debug("[Codex] 登录前已注入 _account cookie = %s", chatgpt_account_id)
|
| 235 |
|
| 236 |
logger.info("[Codex] 先登录 ChatGPT 选择 Team workspace...")
|
| 237 |
_page = context.new_page()
|
|
|
|
| 257 |
if ei.is_visible(timeout=5000):
|
| 258 |
ei.fill(email)
|
| 259 |
time.sleep(0.5)
|
| 260 |
+
_click_primary_auth_button(_page, ei, ["Continue", "继续"])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
time.sleep(3)
|
| 262 |
except Exception:
|
| 263 |
pass
|
|
|
|
| 268 |
if pi.is_visible(timeout=5000):
|
| 269 |
pi.fill(password)
|
| 270 |
time.sleep(0.5)
|
| 271 |
+
_click_primary_auth_button(_page, pi, ["Continue", "继续", "Log in"])
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
time.sleep(8)
|
| 273 |
except Exception:
|
| 274 |
pass
|
|
|
|
| 304 |
|
| 305 |
# 如果是 workspace 选择页面,选择 Team
|
| 306 |
if "workspace" in _page.url:
|
| 307 |
+
workspace_name = get_chatgpt_workspace_name()
|
| 308 |
logger.info("[Codex] 检测到 workspace 选择页面...")
|
| 309 |
try:
|
| 310 |
+
ws_btn = _page.locator(f'text="{workspace_name}"').first
|
| 311 |
+
if workspace_name and ws_btn.is_visible(timeout=3000):
|
| 312 |
+
logger.info("[Codex] 选择 workspace: %s", workspace_name)
|
| 313 |
ws_btn.click()
|
| 314 |
time.sleep(5)
|
| 315 |
else:
|
|
|
|
| 365 |
_screenshot(page, "codex_01_auth_page.png")
|
| 366 |
|
| 367 |
# 输入邮箱(注意避免点到 Google/Microsoft/Apple 第三方登录按钮)
|
|
|
|
| 368 |
try:
|
| 369 |
+
for attempt in range(2):
|
| 370 |
+
email_input = page.locator('input[name="email"], input[id="email-input"], input[id="email"]').first
|
| 371 |
+
if not email_input.is_visible(timeout=5000):
|
| 372 |
+
break
|
| 373 |
+
|
| 374 |
email_input.fill(email)
|
| 375 |
time.sleep(0.5)
|
| 376 |
+
_click_primary_auth_button(page, email_input, ["Continue", "继续"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
time.sleep(3)
|
| 378 |
+
|
| 379 |
+
if not _is_google_redirect(page):
|
| 380 |
+
break
|
| 381 |
+
|
| 382 |
+
_screenshot(page, f"codex_02_google_redirect_attempt{attempt+1}.png")
|
| 383 |
+
logger.warning("[Codex] 邮箱步骤误跳转到 Google 登录,返回重试... (attempt %d)", attempt + 1)
|
| 384 |
+
page.go_back(wait_until="domcontentloaded", timeout=30000)
|
| 385 |
+
time.sleep(2)
|
| 386 |
+
_screenshot(page, "codex_02_after_email.png")
|
| 387 |
except Exception:
|
| 388 |
_screenshot(page, "codex_02_no_email.png")
|
| 389 |
|
| 390 |
# 输入密码
|
|
|
|
| 391 |
try:
|
| 392 |
+
for attempt in range(2):
|
| 393 |
+
pwd_input = page.locator('input[name="password"], input[type="password"]').first
|
| 394 |
+
if not pwd_input.is_visible(timeout=5000):
|
| 395 |
+
break
|
| 396 |
+
|
| 397 |
pwd_input.fill(password)
|
| 398 |
time.sleep(0.5)
|
| 399 |
+
_click_primary_auth_button(page, pwd_input, ["Continue", "继续", "Log in"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
time.sleep(5)
|
| 401 |
+
|
| 402 |
+
if not _is_google_redirect(page):
|
| 403 |
+
break
|
| 404 |
+
|
| 405 |
+
_screenshot(page, f"codex_03_google_redirect_attempt{attempt+1}.png")
|
| 406 |
+
logger.warning("[Codex] 密码步骤误跳转到 Google 登录,返回重试... (attempt %d)", attempt + 1)
|
| 407 |
+
page.go_back(wait_until="domcontentloaded", timeout=30000)
|
| 408 |
+
time.sleep(2)
|
| 409 |
+
_screenshot(page, "codex_03_after_password.png")
|
| 410 |
except Exception:
|
| 411 |
_screenshot(page, "codex_03_no_password.png")
|
| 412 |
|
|
|
|
| 505 |
page_text = page.inner_text("body")[:1000]
|
| 506 |
|
| 507 |
# 选择 Team workspace(用配置的名称精确匹配)
|
| 508 |
+
workspace_name = get_chatgpt_workspace_name()
|
| 509 |
+
if workspace_name:
|
| 510 |
try:
|
| 511 |
+
ws_btn = page.locator(f'text="{workspace_name}"').first
|
| 512 |
if ws_btn.is_visible(timeout=2000):
|
| 513 |
ws_btn.click()
|
| 514 |
time.sleep(1)
|
| 515 |
+
logger.info("[Codex] 已选择 workspace: %s (step %d)", workspace_name, step + 1)
|
| 516 |
except Exception:
|
| 517 |
pass
|
| 518 |
|
|
|
|
| 604 |
logger.error("[Codex] OAuth 登录失败: 未获取到 authorization code")
|
| 605 |
return None
|
| 606 |
|
| 607 |
+
return _exchange_auth_code(auth_code, code_verifier, fallback_email=email)
|
| 608 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
|
| 610 |
+
def login_codex_via_session():
|
| 611 |
+
"""使用主号 session 直接完成 Codex OAuth 登录。"""
|
| 612 |
+
code_verifier, code_challenge = _generate_pkce()
|
| 613 |
+
state = secrets.token_urlsafe(16)
|
| 614 |
+
auth_url = _build_auth_url(code_challenge, state)
|
| 615 |
+
|
| 616 |
+
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 617 |
+
|
| 618 |
+
logger.info("[Codex] 开始使用 session 登录主号 Codex...")
|
| 619 |
+
auth_code = None
|
| 620 |
+
chatgpt = ChatGPTTeamAPI()
|
| 621 |
+
|
| 622 |
+
try:
|
| 623 |
+
chatgpt.start()
|
| 624 |
+
session_token = chatgpt.session_token
|
| 625 |
+
if not session_token:
|
| 626 |
+
logger.error("[Codex] 主号会话中未提取到 session token")
|
| 627 |
+
return None
|
| 628 |
+
cookies = []
|
| 629 |
+
if len(session_token) > 3800:
|
| 630 |
+
cookies.extend([
|
| 631 |
+
{
|
| 632 |
+
"name": "__Secure-next-auth.session-token.0",
|
| 633 |
+
"value": session_token[:3800],
|
| 634 |
+
"domain": "auth.openai.com",
|
| 635 |
+
"path": "/",
|
| 636 |
+
"httpOnly": True,
|
| 637 |
+
"secure": True,
|
| 638 |
+
"sameSite": "Lax",
|
| 639 |
+
},
|
| 640 |
+
{
|
| 641 |
+
"name": "__Secure-next-auth.session-token.1",
|
| 642 |
+
"value": session_token[3800:],
|
| 643 |
+
"domain": "auth.openai.com",
|
| 644 |
+
"path": "/",
|
| 645 |
+
"httpOnly": True,
|
| 646 |
+
"secure": True,
|
| 647 |
+
"sameSite": "Lax",
|
| 648 |
+
},
|
| 649 |
+
])
|
| 650 |
+
else:
|
| 651 |
+
cookies.append({
|
| 652 |
+
"name": "__Secure-next-auth.session-token",
|
| 653 |
+
"value": session_token,
|
| 654 |
+
"domain": "auth.openai.com",
|
| 655 |
+
"path": "/",
|
| 656 |
+
"httpOnly": True,
|
| 657 |
+
"secure": True,
|
| 658 |
+
"sameSite": "Lax",
|
| 659 |
+
})
|
| 660 |
+
|
| 661 |
+
cookies.extend([
|
| 662 |
+
{
|
| 663 |
+
"name": "_account",
|
| 664 |
+
"value": chatgpt.account_id,
|
| 665 |
+
"domain": "auth.openai.com",
|
| 666 |
+
"path": "/",
|
| 667 |
+
"secure": True,
|
| 668 |
+
"sameSite": "Lax",
|
| 669 |
+
},
|
| 670 |
+
{
|
| 671 |
+
"name": "oai-did",
|
| 672 |
+
"value": chatgpt.oai_device_id,
|
| 673 |
+
"domain": "auth.openai.com",
|
| 674 |
+
"path": "/",
|
| 675 |
+
"secure": True,
|
| 676 |
+
"sameSite": "Lax",
|
| 677 |
+
},
|
| 678 |
+
])
|
| 679 |
+
chatgpt.context.add_cookies(cookies)
|
| 680 |
+
page = chatgpt.context.new_page()
|
| 681 |
+
|
| 682 |
+
def on_request(request):
|
| 683 |
+
nonlocal auth_code
|
| 684 |
+
url = request.url
|
| 685 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in url:
|
| 686 |
+
parsed = urllib.parse.urlparse(url)
|
| 687 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 688 |
+
auth_code = qs.get("code", [None])[0]
|
| 689 |
+
|
| 690 |
+
def on_response(response):
|
| 691 |
+
nonlocal auth_code
|
| 692 |
+
url = response.url
|
| 693 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in url and not auth_code:
|
| 694 |
+
parsed = urllib.parse.urlparse(url)
|
| 695 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 696 |
+
auth_code = qs.get("code", [None])[0]
|
| 697 |
+
|
| 698 |
+
def open_oauth_page(tag):
|
| 699 |
+
page.goto(auth_url, wait_until="domcontentloaded", timeout=60000)
|
| 700 |
+
time.sleep(3)
|
| 701 |
+
_screenshot(page, f"codex_main_{tag}.png")
|
| 702 |
+
|
| 703 |
+
page.on("request", on_request)
|
| 704 |
+
page.on("response", on_response)
|
| 705 |
+
open_oauth_page("01_auth_page")
|
| 706 |
+
|
| 707 |
+
needs_login = False
|
| 708 |
+
try:
|
| 709 |
+
email_input = page.locator('input[name="email"], input[id="email-input"], input[id="email"]').first
|
| 710 |
+
needs_login = email_input.is_visible(timeout=3000)
|
| 711 |
+
except Exception:
|
| 712 |
+
needs_login = False
|
| 713 |
+
|
| 714 |
+
if needs_login:
|
| 715 |
+
logger.warning("[Codex] 主号 OAuth 先落到了登录页,尝试先建立 ChatGPT 登录态后重试...")
|
| 716 |
+
page.goto("https://chatgpt.com/auth/login", wait_until="domcontentloaded", timeout=60000)
|
| 717 |
+
time.sleep(5)
|
| 718 |
+
_screenshot(page, "codex_main_login_bootstrap.png")
|
| 719 |
+
open_oauth_page("02_auth_retry")
|
| 720 |
+
|
| 721 |
+
try:
|
| 722 |
+
email_input = page.locator('input[name="email"], input[id="email-input"], input[id="email"]').first
|
| 723 |
+
if email_input.is_visible(timeout=3000):
|
| 724 |
+
logger.error("[Codex] session 无法直接用于主号 Codex OAuth,仍落在登录页")
|
| 725 |
+
_screenshot(page, "codex_main_invalid_session.png")
|
| 726 |
+
return None
|
| 727 |
+
except Exception:
|
| 728 |
+
pass
|
| 729 |
+
|
| 730 |
+
for step in range(10):
|
| 731 |
+
if auth_code:
|
| 732 |
+
break
|
| 733 |
+
|
| 734 |
+
try:
|
| 735 |
+
workspace_name = get_chatgpt_workspace_name()
|
| 736 |
+
if "workspace" in page.url and workspace_name:
|
| 737 |
+
ws_btn = page.locator(f'text="{workspace_name}"').first
|
| 738 |
+
if ws_btn.is_visible(timeout=2000):
|
| 739 |
+
ws_btn.click()
|
| 740 |
+
time.sleep(2)
|
| 741 |
+
logger.info("[Codex] 主号选择 workspace: %s", workspace_name)
|
| 742 |
+
continue
|
| 743 |
+
except Exception:
|
| 744 |
+
pass
|
| 745 |
+
|
| 746 |
+
try:
|
| 747 |
+
consent_btn = page.locator(
|
| 748 |
+
'button:has-text("继续"), button:has-text("Continue"), button:has-text("Allow")'
|
| 749 |
+
).first
|
| 750 |
+
if consent_btn.is_visible(timeout=3000):
|
| 751 |
+
logger.info("[Codex] 主号点击继续/授权 (step %d)...", step + 1)
|
| 752 |
+
consent_btn.click()
|
| 753 |
+
time.sleep(4)
|
| 754 |
+
continue
|
| 755 |
+
except Exception:
|
| 756 |
+
pass
|
| 757 |
+
|
| 758 |
+
try:
|
| 759 |
+
cur = page.url
|
| 760 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in cur:
|
| 761 |
+
parsed = urllib.parse.urlparse(cur)
|
| 762 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 763 |
+
auth_code = qs.get("code", [None])[0]
|
| 764 |
+
if auth_code:
|
| 765 |
+
break
|
| 766 |
+
except Exception:
|
| 767 |
+
pass
|
| 768 |
+
|
| 769 |
+
time.sleep(1)
|
| 770 |
+
|
| 771 |
+
if not auth_code:
|
| 772 |
+
_screenshot(page, "codex_main_no_callback.png")
|
| 773 |
+
logger.warning("[Codex] 主号未获取到 auth code,当前 URL: %s", page.url)
|
| 774 |
+
return None
|
| 775 |
+
finally:
|
| 776 |
+
chatgpt.stop()
|
| 777 |
+
|
| 778 |
+
return _exchange_auth_code(auth_code, code_verifier)
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
class MainCodexSyncFlow:
|
| 782 |
+
EMAIL_SELECTORS = [
|
| 783 |
+
'input[name="email"]',
|
| 784 |
+
'input[id="email-input"]',
|
| 785 |
+
'input[id="email"]',
|
| 786 |
+
'input[type="email"]',
|
| 787 |
+
'input[placeholder*="email" i]',
|
| 788 |
+
'input[placeholder*="邮箱"]',
|
| 789 |
+
'input[autocomplete="email"]',
|
| 790 |
+
'input[autocomplete="username"]',
|
| 791 |
+
]
|
| 792 |
+
PASSWORD_SELECTORS = [
|
| 793 |
+
'input[name="password"]',
|
| 794 |
+
'input[type="password"]',
|
| 795 |
+
]
|
| 796 |
+
CODE_SELECTORS = [
|
| 797 |
+
'input[name="code"]',
|
| 798 |
+
'input[placeholder*="验证码"]',
|
| 799 |
+
'input[placeholder*="code" i]',
|
| 800 |
+
'input[inputmode="numeric"]',
|
| 801 |
+
'input[autocomplete="one-time-code"]',
|
| 802 |
+
]
|
| 803 |
+
|
| 804 |
+
def __init__(self):
|
| 805 |
+
self.email = get_admin_email()
|
| 806 |
+
self.password = get_admin_password()
|
| 807 |
+
self.workspace_name = get_chatgpt_workspace_name()
|
| 808 |
+
self.account_id = get_chatgpt_account_id()
|
| 809 |
+
self.session_token = get_admin_session_token()
|
| 810 |
+
self.code_verifier, code_challenge = _generate_pkce()
|
| 811 |
+
self.state = secrets.token_urlsafe(16)
|
| 812 |
+
self.auth_url = _build_auth_url(code_challenge, self.state)
|
| 813 |
+
self.auth_code = None
|
| 814 |
+
self.chatgpt = None
|
| 815 |
+
self.page = None
|
| 816 |
+
|
| 817 |
+
def _visible_locator(self, selectors, timeout_ms=5000):
|
| 818 |
+
if not self.page:
|
| 819 |
+
return None
|
| 820 |
+
|
| 821 |
+
selector = ", ".join(selectors)
|
| 822 |
+
deadline = time.time() + timeout_ms / 1000
|
| 823 |
+
while time.time() < deadline:
|
| 824 |
+
frames = [self.page.main_frame]
|
| 825 |
+
frames.extend(frame for frame in self.page.frames if frame != self.page.main_frame)
|
| 826 |
+
for frame in frames:
|
| 827 |
+
try:
|
| 828 |
+
locator = frame.locator(selector).first
|
| 829 |
+
if locator.is_visible(timeout=250):
|
| 830 |
+
return locator
|
| 831 |
+
except Exception:
|
| 832 |
+
pass
|
| 833 |
+
time.sleep(0.2)
|
| 834 |
return None
|
| 835 |
|
| 836 |
+
def _detect_step(self):
|
| 837 |
+
if self.auth_code:
|
| 838 |
+
return "completed", None
|
| 839 |
+
|
| 840 |
+
cur = self.page.url if self.page else ""
|
| 841 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in cur:
|
| 842 |
+
parsed = urllib.parse.urlparse(cur)
|
| 843 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 844 |
+
self.auth_code = qs.get("code", [None])[0]
|
| 845 |
+
if self.auth_code:
|
| 846 |
+
return "completed", None
|
| 847 |
+
|
| 848 |
+
if self._visible_locator(self.CODE_SELECTORS, timeout_ms=800):
|
| 849 |
+
return "code_required", None
|
| 850 |
+
if self._visible_locator(self.PASSWORD_SELECTORS, timeout_ms=800):
|
| 851 |
+
return "password_required", None
|
| 852 |
+
if self._visible_locator(self.EMAIL_SELECTORS, timeout_ms=800):
|
| 853 |
+
return "email_required", None
|
| 854 |
+
return "unknown", cur
|
| 855 |
+
|
| 856 |
+
def _attach_callback_listeners(self):
|
| 857 |
+
def on_request(request):
|
| 858 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in request.url:
|
| 859 |
+
parsed = urllib.parse.urlparse(request.url)
|
| 860 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 861 |
+
self.auth_code = qs.get("code", [None])[0]
|
| 862 |
|
| 863 |
+
def on_response(response):
|
| 864 |
+
if self.auth_code:
|
| 865 |
+
return
|
| 866 |
+
if f"localhost:{CODEX_CALLBACK_PORT}/auth/callback" in response.url:
|
| 867 |
+
parsed = urllib.parse.urlparse(response.url)
|
| 868 |
+
qs = urllib.parse.parse_qs(parsed.query)
|
| 869 |
+
self.auth_code = qs.get("code", [None])[0]
|
| 870 |
+
|
| 871 |
+
self.page.on("request", on_request)
|
| 872 |
+
self.page.on("response", on_response)
|
| 873 |
+
|
| 874 |
+
def _inject_auth_cookies(self):
|
| 875 |
+
cookies = []
|
| 876 |
+
if len(self.session_token) > 3800:
|
| 877 |
+
cookies.extend([
|
| 878 |
+
{
|
| 879 |
+
"name": "__Secure-next-auth.session-token.0",
|
| 880 |
+
"value": self.session_token[:3800],
|
| 881 |
+
"domain": "auth.openai.com",
|
| 882 |
+
"path": "/",
|
| 883 |
+
"httpOnly": True,
|
| 884 |
+
"secure": True,
|
| 885 |
+
"sameSite": "Lax",
|
| 886 |
+
},
|
| 887 |
+
{
|
| 888 |
+
"name": "__Secure-next-auth.session-token.1",
|
| 889 |
+
"value": self.session_token[3800:],
|
| 890 |
+
"domain": "auth.openai.com",
|
| 891 |
+
"path": "/",
|
| 892 |
+
"httpOnly": True,
|
| 893 |
+
"secure": True,
|
| 894 |
+
"sameSite": "Lax",
|
| 895 |
+
},
|
| 896 |
+
])
|
| 897 |
+
else:
|
| 898 |
+
cookies.append({
|
| 899 |
+
"name": "__Secure-next-auth.session-token",
|
| 900 |
+
"value": self.session_token,
|
| 901 |
+
"domain": "auth.openai.com",
|
| 902 |
+
"path": "/",
|
| 903 |
+
"httpOnly": True,
|
| 904 |
+
"secure": True,
|
| 905 |
+
"sameSite": "Lax",
|
| 906 |
+
})
|
| 907 |
|
| 908 |
+
if self.account_id:
|
| 909 |
+
cookies.append({
|
| 910 |
+
"name": "_account",
|
| 911 |
+
"value": self.account_id,
|
| 912 |
+
"domain": "auth.openai.com",
|
| 913 |
+
"path": "/",
|
| 914 |
+
"secure": True,
|
| 915 |
+
"sameSite": "Lax",
|
| 916 |
+
})
|
| 917 |
+
|
| 918 |
+
cookies.append({
|
| 919 |
+
"name": "oai-did",
|
| 920 |
+
"value": self.chatgpt.oai_device_id,
|
| 921 |
+
"domain": "auth.openai.com",
|
| 922 |
+
"path": "/",
|
| 923 |
+
"secure": True,
|
| 924 |
+
"sameSite": "Lax",
|
| 925 |
+
})
|
| 926 |
+
self.chatgpt.context.add_cookies(cookies)
|
| 927 |
+
|
| 928 |
+
def _click_workspace_or_consent(self):
|
| 929 |
+
acted = False
|
| 930 |
+
|
| 931 |
+
try:
|
| 932 |
+
if "workspace" in self.page.url and self.workspace_name:
|
| 933 |
+
ws_btn = self.page.locator(f'text="{self.workspace_name}"').first
|
| 934 |
+
if ws_btn.is_visible(timeout=1000):
|
| 935 |
+
ws_btn.click()
|
| 936 |
+
logger.info("[Codex] 主号选择 workspace: %s", self.workspace_name)
|
| 937 |
+
time.sleep(2)
|
| 938 |
+
acted = True
|
| 939 |
+
except Exception:
|
| 940 |
+
pass
|
| 941 |
+
|
| 942 |
+
try:
|
| 943 |
+
consent_btn = self.page.locator(
|
| 944 |
+
'button:has-text("继续"), button:has-text("Continue"), button:has-text("Allow")'
|
| 945 |
+
).first
|
| 946 |
+
if consent_btn.is_visible(timeout=1000):
|
| 947 |
+
consent_btn.click()
|
| 948 |
+
logger.info("[Codex] 主号点击继续/授权")
|
| 949 |
+
time.sleep(3)
|
| 950 |
+
acted = True
|
| 951 |
+
except Exception:
|
| 952 |
+
pass
|
| 953 |
+
|
| 954 |
+
return acted
|
| 955 |
+
|
| 956 |
+
def _auto_fill_email(self):
|
| 957 |
+
email_input = self._visible_locator(self.EMAIL_SELECTORS, timeout_ms=1000)
|
| 958 |
+
if not email_input or not self.email:
|
| 959 |
+
return False
|
| 960 |
+
|
| 961 |
+
email_input.fill(self.email)
|
| 962 |
+
time.sleep(0.5)
|
| 963 |
+
_click_primary_auth_button(self.page, email_input, ["Continue", "继续", "Log in"])
|
| 964 |
+
time.sleep(3)
|
| 965 |
+
return True
|
| 966 |
+
|
| 967 |
+
def _auto_fill_password(self):
|
| 968 |
+
password_input = self._visible_locator(self.PASSWORD_SELECTORS, timeout_ms=1000)
|
| 969 |
+
if not password_input or not self.password:
|
| 970 |
+
return False
|
| 971 |
+
|
| 972 |
+
password_input.fill(self.password)
|
| 973 |
+
time.sleep(0.5)
|
| 974 |
+
_click_primary_auth_button(self.page, password_input, ["Continue", "继续", "Log in"])
|
| 975 |
+
time.sleep(5)
|
| 976 |
+
return True
|
| 977 |
+
|
| 978 |
+
def _advance(self, attempts=12):
|
| 979 |
+
for _ in range(attempts):
|
| 980 |
+
step, detail = self._detect_step()
|
| 981 |
+
if step == "completed":
|
| 982 |
+
return {"step": "completed", "detail": detail}
|
| 983 |
+
if step == "code_required":
|
| 984 |
+
return {"step": "code_required", "detail": detail}
|
| 985 |
+
if step == "password_required" and not self.password:
|
| 986 |
+
return {"step": "password_required", "detail": detail}
|
| 987 |
+
|
| 988 |
+
if step == "email_required":
|
| 989 |
+
if self._auto_fill_email():
|
| 990 |
+
continue
|
| 991 |
+
return {"step": "email_required", "detail": detail}
|
| 992 |
+
|
| 993 |
+
if step == "password_required" and self.password:
|
| 994 |
+
if self._auto_fill_password():
|
| 995 |
+
continue
|
| 996 |
+
|
| 997 |
+
if self._click_workspace_or_consent():
|
| 998 |
+
continue
|
| 999 |
+
|
| 1000 |
+
time.sleep(1)
|
| 1001 |
+
|
| 1002 |
+
final_step, detail = self._detect_step()
|
| 1003 |
+
return {"step": final_step, "detail": detail}
|
| 1004 |
+
|
| 1005 |
+
def start(self):
|
| 1006 |
+
if not self.session_token:
|
| 1007 |
+
raise RuntimeError("缺少管理员 session,请先完成管理员登录")
|
| 1008 |
+
if not self.email:
|
| 1009 |
+
raise RuntimeError("缺少管理员邮箱,请先完成管理员登录")
|
| 1010 |
+
|
| 1011 |
+
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 1012 |
+
|
| 1013 |
+
self.chatgpt = ChatGPTTeamAPI()
|
| 1014 |
+
self.chatgpt.start()
|
| 1015 |
+
self.page = self.chatgpt.context.new_page()
|
| 1016 |
+
self._attach_callback_listeners()
|
| 1017 |
+
self._inject_auth_cookies()
|
| 1018 |
+
self.page.goto(self.auth_url, wait_until="domcontentloaded", timeout=60000)
|
| 1019 |
+
time.sleep(3)
|
| 1020 |
+
return self._advance()
|
| 1021 |
+
|
| 1022 |
+
def submit_password(self, password):
|
| 1023 |
+
self.password = password
|
| 1024 |
+
update_admin_state(password=password)
|
| 1025 |
+
password_input = self._visible_locator(self.PASSWORD_SELECTORS, timeout_ms=5000)
|
| 1026 |
+
if not password_input:
|
| 1027 |
+
raise RuntimeError("当前主号 Codex 登录不是密码输入步骤")
|
| 1028 |
+
|
| 1029 |
+
password_input.fill(password)
|
| 1030 |
+
time.sleep(0.5)
|
| 1031 |
+
_click_primary_auth_button(self.page, password_input, ["Continue", "继续", "Log in"])
|
| 1032 |
+
time.sleep(5)
|
| 1033 |
+
return self._advance()
|
| 1034 |
+
|
| 1035 |
+
def submit_code(self, code):
|
| 1036 |
+
code_input = self._visible_locator(self.CODE_SELECTORS, timeout_ms=5000)
|
| 1037 |
+
if not code_input:
|
| 1038 |
+
raise RuntimeError("当前主号 Codex 登录不是验证码输入步骤")
|
| 1039 |
+
|
| 1040 |
+
code_input.fill(code)
|
| 1041 |
+
time.sleep(0.5)
|
| 1042 |
+
_click_primary_auth_button(self.page, code_input, ["Continue", "继续", "Verify"])
|
| 1043 |
+
time.sleep(5)
|
| 1044 |
+
return self._advance()
|
| 1045 |
+
|
| 1046 |
+
def complete(self):
|
| 1047 |
+
if not self.auth_code:
|
| 1048 |
+
raise RuntimeError("未获取到主号 Codex authorization code")
|
| 1049 |
+
|
| 1050 |
+
bundle = _exchange_auth_code(self.auth_code, self.code_verifier, fallback_email=self.email)
|
| 1051 |
+
if not bundle:
|
| 1052 |
+
raise RuntimeError("主号 Codex token 交换失败")
|
| 1053 |
+
|
| 1054 |
+
from autoteam.cpa_sync import sync_main_codex_to_cpa
|
| 1055 |
+
|
| 1056 |
+
filepath = save_main_auth_file(bundle)
|
| 1057 |
+
sync_main_codex_to_cpa(filepath)
|
| 1058 |
+
return {
|
| 1059 |
+
"email": bundle.get("email"),
|
| 1060 |
+
"auth_file": filepath,
|
| 1061 |
+
"plan_type": bundle.get("plan_type"),
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
def stop(self):
|
| 1065 |
+
if self.chatgpt:
|
| 1066 |
+
self.chatgpt.stop()
|
| 1067 |
+
self.chatgpt = None
|
| 1068 |
+
self.page = None
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
def login_main_codex():
|
| 1072 |
+
"""主号 Codex 登录:使用已保存的管理员 session。"""
|
| 1073 |
+
return login_codex_via_session()
|
| 1074 |
|
| 1075 |
|
| 1076 |
def save_auth_file(bundle):
|
|
|
|
| 1089 |
|
| 1090 |
filename = f"codex-{email}-{plan_type}-{hash_id}.json"
|
| 1091 |
filepath = AUTH_DIR / filename
|
| 1092 |
+
return _write_auth_file(filepath, bundle)
|
| 1093 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
+
def save_main_auth_file(bundle):
|
| 1096 |
+
"""保存主号 Codex 认证文件,不进入账号池。"""
|
| 1097 |
+
account_id = bundle.get("account_id") or hashlib.md5(bundle.get("email", "main").encode()).hexdigest()[:8]
|
| 1098 |
|
| 1099 |
+
for old in AUTH_DIR.glob("codex-main-*.json"):
|
| 1100 |
+
old.unlink()
|
| 1101 |
+
logger.info("[Codex] 清理旧主号文件: %s", old.name)
|
| 1102 |
+
|
| 1103 |
+
filepath = AUTH_DIR / f"codex-main-{account_id}.json"
|
| 1104 |
+
return _write_auth_file(filepath, bundle)
|
| 1105 |
|
| 1106 |
|
| 1107 |
def check_codex_quota(access_token, account_id=None):
|
|
|
|
| 1113 |
import requests
|
| 1114 |
|
| 1115 |
if not account_id:
|
| 1116 |
+
account_id = get_chatgpt_account_id()
|
|
|
|
| 1117 |
|
| 1118 |
headers = {
|
| 1119 |
"Authorization": f"Bearer {access_token}",
|
src/autoteam/config.py
CHANGED
|
@@ -23,7 +23,6 @@ CLOUDMAIL_DOMAIN = os.environ.get("CLOUDMAIL_DOMAIN", "")
|
|
| 23 |
|
| 24 |
# ChatGPT Team 配置
|
| 25 |
CHATGPT_ACCOUNT_ID = os.environ.get("CHATGPT_ACCOUNT_ID", "")
|
| 26 |
-
CHATGPT_WORKSPACE_NAME = os.environ.get("CHATGPT_WORKSPACE_NAME", "")
|
| 27 |
|
| 28 |
# CPA (CLIProxyAPI) 配置
|
| 29 |
CPA_URL = os.environ.get("CPA_URL", "")
|
|
|
|
| 23 |
|
| 24 |
# ChatGPT Team 配置
|
| 25 |
CHATGPT_ACCOUNT_ID = os.environ.get("CHATGPT_ACCOUNT_ID", "")
|
|
|
|
| 26 |
|
| 27 |
# CPA (CLIProxyAPI) 配置
|
| 28 |
CPA_URL = os.environ.get("CPA_URL", "")
|
src/autoteam/cpa_sync.py
CHANGED
|
@@ -125,3 +125,24 @@ def sync_to_cpa():
|
|
| 125 |
final_cpa = list_cpa_files()
|
| 126 |
final_local_managed = [f for f in final_cpa if f.get("email", "").lower() in local_emails]
|
| 127 |
logger.info("[CPA] CPA 中本地管理: %d, 本地 active: %d", len(final_local_managed), len(active_files))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
final_cpa = list_cpa_files()
|
| 126 |
final_local_managed = [f for f in final_cpa if f.get("email", "").lower() in local_emails]
|
| 127 |
logger.info("[CPA] CPA 中本地管理: %d, 本地 active: %d", len(final_local_managed), len(active_files))
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def sync_main_codex_to_cpa(filepath):
|
| 131 |
+
"""同步主号 Codex 认证文件到 CPA。"""
|
| 132 |
+
filepath = Path(filepath)
|
| 133 |
+
if not filepath.exists():
|
| 134 |
+
raise FileNotFoundError(f"主号认证文件不存在: {filepath}")
|
| 135 |
+
|
| 136 |
+
name = filepath.name
|
| 137 |
+
existing = {item.get("name"): item for item in list_cpa_files()}
|
| 138 |
+
|
| 139 |
+
for old_name in existing:
|
| 140 |
+
if old_name and old_name.startswith("codex-main-"):
|
| 141 |
+
logger.info("[CPA] 删除旧主号文件: %s", old_name)
|
| 142 |
+
delete_from_cpa(old_name)
|
| 143 |
+
|
| 144 |
+
if not upload_to_cpa(filepath):
|
| 145 |
+
raise RuntimeError(f"上传主号认证文件失败: {name}")
|
| 146 |
+
|
| 147 |
+
logger.info("[CPA] 主号 Codex 已同步: %s", name)
|
| 148 |
+
return {"uploaded": name}
|
src/autoteam/manager.py
CHANGED
|
@@ -21,6 +21,7 @@ import autoteam.display # noqa: F401 — 自动设置虚拟显示器
|
|
| 21 |
import sys
|
| 22 |
import os
|
| 23 |
import json
|
|
|
|
| 24 |
import logging
|
| 25 |
import time
|
| 26 |
from pathlib import Path
|
|
@@ -30,13 +31,15 @@ from autoteam.accounts import (
|
|
| 30 |
get_active_accounts, get_standby_accounts, get_next_reusable_account,
|
| 31 |
STATUS_ACTIVE, STATUS_EXHAUSTED, STATUS_STANDBY, STATUS_PENDING,
|
| 32 |
)
|
|
|
|
| 33 |
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 34 |
from autoteam.cloudmail import CloudMailClient
|
|
|
|
| 35 |
from autoteam.codex_auth import (
|
| 36 |
login_codex_via_browser, save_auth_file, check_codex_quota,
|
| 37 |
-
refresh_access_token,
|
|
|
|
| 38 |
)
|
| 39 |
-
from autoteam.config import CHATGPT_ACCOUNT_ID
|
| 40 |
from autoteam.cpa_sync import sync_to_cpa
|
| 41 |
|
| 42 |
logger = logging.getLogger(__name__)
|
|
@@ -46,6 +49,7 @@ MAIL_TIMEOUT = int(os.environ.get("MAIL_TIMEOUT", "180"))
|
|
| 46 |
|
| 47 |
def sync_account_states(chatgpt_api=None):
|
| 48 |
"""根据 Team 实际成员列表同步本地账号状态"""
|
|
|
|
| 49 |
accounts = load_accounts()
|
| 50 |
if not accounts:
|
| 51 |
return
|
|
@@ -62,7 +66,7 @@ def sync_account_states(chatgpt_api=None):
|
|
| 62 |
return
|
| 63 |
|
| 64 |
try:
|
| 65 |
-
path = f"/backend-api/accounts/{
|
| 66 |
result = chatgpt_api._api_fetch("GET", path)
|
| 67 |
if result["status"] != 200:
|
| 68 |
return
|
|
@@ -266,6 +270,62 @@ def cmd_check():
|
|
| 266 |
threshold = AUTO_CHECK_THRESHOLD
|
| 267 |
|
| 268 |
accounts = load_accounts()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
all_active = [a for a in accounts if a["status"] == STATUS_ACTIVE]
|
| 270 |
|
| 271 |
# 区分:有认证文件的 vs 无认证文件的
|
|
@@ -412,8 +472,9 @@ def cmd_check():
|
|
| 412 |
|
| 413 |
def remove_from_team(chatgpt_api, email):
|
| 414 |
"""将账号从 Team 中移除"""
|
|
|
|
| 415 |
# 先获取成员列表找到 user_id
|
| 416 |
-
path = f"/backend-api/accounts/{
|
| 417 |
result = chatgpt_api._api_fetch("GET", path)
|
| 418 |
|
| 419 |
if result["status"] != 200:
|
|
@@ -441,7 +502,7 @@ def remove_from_team(chatgpt_api, email):
|
|
| 441 |
return True
|
| 442 |
|
| 443 |
# 删除成员
|
| 444 |
-
delete_path = f"/backend-api/accounts/{
|
| 445 |
result = chatgpt_api._api_fetch("DELETE", delete_path)
|
| 446 |
|
| 447 |
if result["status"] in (200, 204):
|
|
@@ -511,7 +572,8 @@ def _check_pending_invites(chatgpt_api, mail_client):
|
|
| 511 |
"""
|
| 512 |
import uuid
|
| 513 |
|
| 514 |
-
|
|
|
|
| 515 |
if result["status"] != 200:
|
| 516 |
return []
|
| 517 |
|
|
@@ -562,23 +624,27 @@ def _check_pending_invites(chatgpt_api, mail_client):
|
|
| 562 |
return completed
|
| 563 |
|
| 564 |
|
| 565 |
-
def
|
| 566 |
-
"""
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
|
| 574 |
-
# Step 1: 创建临时邮箱
|
| 575 |
-
account_id, email = mail_client.create_temp_email()
|
| 576 |
-
password = f"Tmp_{uuid.uuid4().hex[:12]}!"
|
| 577 |
|
| 578 |
-
|
| 579 |
-
|
|
|
|
|
|
|
| 580 |
|
| 581 |
-
# Step 3: 直接去 ChatGPT 注册页面
|
| 582 |
logger.info("[直接注册] %s", email)
|
| 583 |
signup_url = "https://chatgpt.com/auth/login"
|
| 584 |
|
|
@@ -596,7 +662,6 @@ def create_account_direct(mail_client):
|
|
| 596 |
page.goto(signup_url, wait_until="domcontentloaded", timeout=60000)
|
| 597 |
time.sleep(5)
|
| 598 |
|
| 599 |
-
# 等待 Cloudflare
|
| 600 |
for i in range(12):
|
| 601 |
html = page.content()[:2000].lower()
|
| 602 |
if "verify you are human" not in html and "challenge" not in page.url:
|
|
@@ -606,9 +671,10 @@ def create_account_direct(mail_client):
|
|
| 606 |
|
| 607 |
screenshot(page, "direct_01_login_page.png")
|
| 608 |
|
| 609 |
-
# 点击注册
|
| 610 |
try:
|
| 611 |
-
signup_btn = page.locator(
|
|
|
|
|
|
|
| 612 |
if signup_btn.is_visible(timeout=5000):
|
| 613 |
signup_btn.click()
|
| 614 |
time.sleep(3)
|
|
@@ -617,35 +683,62 @@ def create_account_direct(mail_client):
|
|
| 617 |
|
| 618 |
screenshot(page, "direct_02_signup.png")
|
| 619 |
|
| 620 |
-
# 输入邮箱
|
| 621 |
logger.info("[直接注册] 输入邮箱: %s", email)
|
| 622 |
-
email_input = page.locator('input[name="email"], input[type="email"]').first
|
| 623 |
try:
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
email_input.fill(email)
|
| 626 |
time.sleep(0.5)
|
| 627 |
-
|
| 628 |
time.sleep(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
except Exception:
|
| 630 |
pass
|
| 631 |
|
| 632 |
screenshot(page, "direct_03_after_email.png")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 633 |
|
| 634 |
-
# 输入密码
|
| 635 |
-
pwd_input = page.locator('input[type="password"]').first
|
| 636 |
try:
|
| 637 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
logger.info("[直接注册] 设置密码")
|
| 639 |
pwd_input.fill(password)
|
| 640 |
time.sleep(0.5)
|
| 641 |
-
|
| 642 |
time.sleep(5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
except Exception:
|
| 644 |
pass
|
| 645 |
|
| 646 |
screenshot(page, "direct_04_after_password.png")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
|
| 648 |
-
# 等待验证码
|
| 649 |
code_input = None
|
| 650 |
try:
|
| 651 |
code_input = page.locator('input[name="code"], input[placeholder*="验证码"], input[placeholder*="code" i]').first
|
|
@@ -656,6 +749,7 @@ def create_account_direct(mail_client):
|
|
| 656 |
|
| 657 |
if code_input:
|
| 658 |
import re
|
|
|
|
| 659 |
logger.info("[直接注册] 等待验证码...")
|
| 660 |
verification_code = None
|
| 661 |
start_t = time.time()
|
|
@@ -677,24 +771,22 @@ def create_account_direct(mail_client):
|
|
| 677 |
logger.info("[直接注册] 输入验证码: %s", verification_code)
|
| 678 |
code_input.fill(verification_code)
|
| 679 |
time.sleep(0.5)
|
| 680 |
-
|
| 681 |
time.sleep(8)
|
| 682 |
else:
|
| 683 |
logger.error("[直接注册] 未收到验证码")
|
| 684 |
browser.close()
|
| 685 |
-
return
|
| 686 |
|
| 687 |
screenshot(page, "direct_05_after_code.png")
|
| 688 |
logger.info("[直接注册] 当前 URL: %s", page.url)
|
| 689 |
|
| 690 |
-
# 填写个人信息(全名 + 年龄/生日)
|
| 691 |
name_input = page.locator('input[name="name"]').first
|
| 692 |
try:
|
| 693 |
if name_input.is_visible(timeout=5000):
|
| 694 |
name_input.fill("User")
|
| 695 |
time.sleep(0.5)
|
| 696 |
|
| 697 |
-
# 自适应年龄/生日
|
| 698 |
spinbuttons = page.locator('[role="spinbutton"]').all()
|
| 699 |
if len(spinbuttons) >= 3:
|
| 700 |
try:
|
|
@@ -717,7 +809,7 @@ def create_account_direct(mail_client):
|
|
| 717 |
except Exception:
|
| 718 |
pass
|
| 719 |
|
| 720 |
-
|
| 721 |
time.sleep(8)
|
| 722 |
except Exception:
|
| 723 |
pass
|
|
@@ -725,7 +817,6 @@ def create_account_direct(mail_client):
|
|
| 725 |
screenshot(page, "direct_06_after_profile.png")
|
| 726 |
logger.info("[直接注册] 当前 URL: %s", page.url)
|
| 727 |
|
| 728 |
-
# 可能需要选择 workspace 或自动加入
|
| 729 |
try:
|
| 730 |
join_btn = page.locator('button:has-text("Accept"), button:has-text("Join"), button:has-text("加入")').first
|
| 731 |
if join_btn.is_visible(timeout=5000):
|
|
@@ -736,19 +827,53 @@ def create_account_direct(mail_client):
|
|
| 736 |
|
| 737 |
screenshot(page, "direct_07_final.png")
|
| 738 |
|
| 739 |
-
# 检查结果
|
| 740 |
current_url = page.url
|
| 741 |
-
success = "chatgpt.com" in current_url and "auth" not in current_url
|
| 742 |
if success:
|
| 743 |
logger.info("[直接注册] 注册成功并已加入 workspace!")
|
| 744 |
else:
|
| 745 |
logger.warning("[直接注册] 注册可能未完成,URL: %s", current_url)
|
| 746 |
|
| 747 |
browser.close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
|
| 749 |
if not success:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 750 |
return None
|
| 751 |
|
|
|
|
|
|
|
| 752 |
# Step 4: Codex 登录
|
| 753 |
bundle = login_codex_via_browser(email, password, mail_client=mail_client)
|
| 754 |
if bundle:
|
|
@@ -1118,9 +1243,140 @@ def cmd_add():
|
|
| 1118 |
chatgpt.stop()
|
| 1119 |
|
| 1120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1121 |
def get_team_member_count(chatgpt_api):
|
| 1122 |
"""获取当前 Team 成员数"""
|
| 1123 |
-
|
|
|
|
| 1124 |
result = chatgpt_api._api_fetch("GET", path)
|
| 1125 |
if result["status"] != 200:
|
| 1126 |
return -1
|
|
@@ -1188,6 +1444,7 @@ def cmd_fill(target=5):
|
|
| 1188 |
|
| 1189 |
def cmd_cleanup(max_seats=None):
|
| 1190 |
"""清理多余的 Team 成员,只移除本地 accounts.json 中管理的账号"""
|
|
|
|
| 1191 |
accounts = load_accounts()
|
| 1192 |
local_emails = {a["email"].lower() for a in accounts}
|
| 1193 |
|
|
@@ -1200,7 +1457,7 @@ def cmd_cleanup(max_seats=None):
|
|
| 1200 |
|
| 1201 |
try:
|
| 1202 |
# 获取当前成员列表
|
| 1203 |
-
path = f"/backend-api/accounts/{
|
| 1204 |
result = chatgpt._api_fetch("GET", path)
|
| 1205 |
|
| 1206 |
if result["status"] != 200:
|
|
@@ -1258,7 +1515,7 @@ def cmd_cleanup(max_seats=None):
|
|
| 1258 |
email = m.get("email", "")
|
| 1259 |
user_id = m.get("user_id") or m.get("id")
|
| 1260 |
|
| 1261 |
-
delete_path = f"/backend-api/accounts/{
|
| 1262 |
result = chatgpt._api_fetch("DELETE", delete_path)
|
| 1263 |
|
| 1264 |
if result["status"] in (200, 204):
|
|
@@ -1268,7 +1525,7 @@ def cmd_cleanup(max_seats=None):
|
|
| 1268 |
logger.error("[清理] 移除 %s 失败: %d", email, result['status'])
|
| 1269 |
|
| 1270 |
# 取消 pending invites 中本地管理的
|
| 1271 |
-
inv_result = chatgpt._api_fetch("GET", f"/backend-api/accounts/{
|
| 1272 |
if inv_result["status"] == 200:
|
| 1273 |
inv_data = json.loads(inv_result["body"])
|
| 1274 |
invites = inv_data if isinstance(inv_data, list) else inv_data.get("invites", inv_data.get("account_invites", []))
|
|
@@ -1277,7 +1534,7 @@ def cmd_cleanup(max_seats=None):
|
|
| 1277 |
inv_id = inv.get("id")
|
| 1278 |
if inv_email in local_emails and inv_id:
|
| 1279 |
del_result = chatgpt._api_fetch("DELETE",
|
| 1280 |
-
f"/backend-api/accounts/{
|
| 1281 |
if del_result["status"] in (200, 204):
|
| 1282 |
logger.info("[清理] 已取消邀请 %s", inv_email)
|
| 1283 |
|
|
@@ -1302,6 +1559,9 @@ def main():
|
|
| 1302 |
rotate_p = sub.add_parser("rotate", help="智能轮转(检查额度 → 移出 → 复用旧号 → 万不得已才创建新号)")
|
| 1303 |
rotate_p.add_argument("target", type=int, nargs="?", default=5, help="目标成员数(默认 5)")
|
| 1304 |
sub.add_parser("add", help="手动添加一个新账号")
|
|
|
|
|
|
|
|
|
|
| 1305 |
|
| 1306 |
fill_p = sub.add_parser("fill", help="补满 Team 成员到指定数量")
|
| 1307 |
fill_p.add_argument("target", type=int, nargs="?", default=5, help="目标成员数(默认 5)")
|
|
@@ -1329,6 +1589,10 @@ def main():
|
|
| 1329 |
cmd_rotate(args.target)
|
| 1330 |
elif args.command == "add":
|
| 1331 |
cmd_add()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1332 |
elif args.command == "fill":
|
| 1333 |
cmd_fill(args.target)
|
| 1334 |
elif args.command == "cleanup":
|
|
|
|
| 21 |
import sys
|
| 22 |
import os
|
| 23 |
import json
|
| 24 |
+
import getpass
|
| 25 |
import logging
|
| 26 |
import time
|
| 27 |
from pathlib import Path
|
|
|
|
| 31 |
get_active_accounts, get_standby_accounts, get_next_reusable_account,
|
| 32 |
STATUS_ACTIVE, STATUS_EXHAUSTED, STATUS_STANDBY, STATUS_PENDING,
|
| 33 |
)
|
| 34 |
+
from autoteam.admin_state import get_chatgpt_account_id, get_admin_state_summary
|
| 35 |
from autoteam.chatgpt_api import ChatGPTTeamAPI
|
| 36 |
from autoteam.cloudmail import CloudMailClient
|
| 37 |
+
from autoteam.account_ops import delete_managed_account, fetch_team_state
|
| 38 |
from autoteam.codex_auth import (
|
| 39 |
login_codex_via_browser, save_auth_file, check_codex_quota,
|
| 40 |
+
refresh_access_token, _click_primary_auth_button, _is_google_redirect,
|
| 41 |
+
MainCodexSyncFlow,
|
| 42 |
)
|
|
|
|
| 43 |
from autoteam.cpa_sync import sync_to_cpa
|
| 44 |
|
| 45 |
logger = logging.getLogger(__name__)
|
|
|
|
| 49 |
|
| 50 |
def sync_account_states(chatgpt_api=None):
|
| 51 |
"""根据 Team 实际成员列表同步本地账号状态"""
|
| 52 |
+
account_id = get_chatgpt_account_id()
|
| 53 |
accounts = load_accounts()
|
| 54 |
if not accounts:
|
| 55 |
return
|
|
|
|
| 66 |
return
|
| 67 |
|
| 68 |
try:
|
| 69 |
+
path = f"/backend-api/accounts/{account_id}/users"
|
| 70 |
result = chatgpt_api._api_fetch("GET", path)
|
| 71 |
if result["status"] != 200:
|
| 72 |
return
|
|
|
|
| 270 |
threshold = AUTO_CHECK_THRESHOLD
|
| 271 |
|
| 272 |
accounts = load_accounts()
|
| 273 |
+
|
| 274 |
+
pending_accounts = [a for a in accounts if a["status"] == STATUS_PENDING]
|
| 275 |
+
if pending_accounts:
|
| 276 |
+
logger.info("[检查] 对账 %d 个 pending 账号...", len(pending_accounts))
|
| 277 |
+
chatgpt = None
|
| 278 |
+
mail_client = None
|
| 279 |
+
deleted_pending = 0
|
| 280 |
+
try:
|
| 281 |
+
chatgpt = ChatGPTTeamAPI()
|
| 282 |
+
chatgpt.start()
|
| 283 |
+
members, invites = fetch_team_state(chatgpt)
|
| 284 |
+
team_emails = {(m.get("email", "") or "").lower() for m in members}
|
| 285 |
+
invite_emails = {
|
| 286 |
+
((inv.get("email_address") or inv.get("email") or "")).lower()
|
| 287 |
+
for inv in invites
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
for acc in pending_accounts:
|
| 291 |
+
email = acc["email"]
|
| 292 |
+
email_l = email.lower()
|
| 293 |
+
|
| 294 |
+
if email_l in team_emails:
|
| 295 |
+
logger.info("[检查] pending 账号已在 Team 中,转为 active: %s", email)
|
| 296 |
+
update_account(email, status=STATUS_ACTIVE)
|
| 297 |
+
continue
|
| 298 |
+
|
| 299 |
+
if email_l in invite_emails:
|
| 300 |
+
logger.info("[检查] pending 账号仍存在远端邀请,保留: %s", email)
|
| 301 |
+
continue
|
| 302 |
+
|
| 303 |
+
logger.warning("[检查] pending 账号为失败孤儿,删除: %s", email)
|
| 304 |
+
if mail_client is None:
|
| 305 |
+
mail_client = CloudMailClient()
|
| 306 |
+
mail_client.login()
|
| 307 |
+
delete_managed_account(
|
| 308 |
+
email,
|
| 309 |
+
remove_remote=True,
|
| 310 |
+
remove_cloudmail=True,
|
| 311 |
+
sync_cpa_after=False,
|
| 312 |
+
chatgpt_api=chatgpt,
|
| 313 |
+
mail_client=mail_client,
|
| 314 |
+
remote_state=(members, invites),
|
| 315 |
+
)
|
| 316 |
+
deleted_pending += 1
|
| 317 |
+
except Exception as exc:
|
| 318 |
+
logger.warning("[检查] pending 对账失败,跳过本轮清理: %s", exc)
|
| 319 |
+
finally:
|
| 320 |
+
if chatgpt and chatgpt.browser:
|
| 321 |
+
chatgpt.stop()
|
| 322 |
+
|
| 323 |
+
if deleted_pending:
|
| 324 |
+
logger.info("[检查] 已删除 %d 个失败 pending 账号", deleted_pending)
|
| 325 |
+
sync_to_cpa()
|
| 326 |
+
|
| 327 |
+
accounts = load_accounts()
|
| 328 |
+
|
| 329 |
all_active = [a for a in accounts if a["status"] == STATUS_ACTIVE]
|
| 330 |
|
| 331 |
# 区分:有认证文件的 vs 无认证文件的
|
|
|
|
| 472 |
|
| 473 |
def remove_from_team(chatgpt_api, email):
|
| 474 |
"""将账号从 Team 中移除"""
|
| 475 |
+
account_id = get_chatgpt_account_id()
|
| 476 |
# 先获取成员列表找到 user_id
|
| 477 |
+
path = f"/backend-api/accounts/{account_id}/users"
|
| 478 |
result = chatgpt_api._api_fetch("GET", path)
|
| 479 |
|
| 480 |
if result["status"] != 200:
|
|
|
|
| 502 |
return True
|
| 503 |
|
| 504 |
# 删除成员
|
| 505 |
+
delete_path = f"/backend-api/accounts/{account_id}/users/{target_user_id}"
|
| 506 |
result = chatgpt_api._api_fetch("DELETE", delete_path)
|
| 507 |
|
| 508 |
if result["status"] in (200, 204):
|
|
|
|
| 572 |
"""
|
| 573 |
import uuid
|
| 574 |
|
| 575 |
+
account_id = get_chatgpt_account_id()
|
| 576 |
+
result = chatgpt_api._api_fetch("GET", f"/backend-api/accounts/{account_id}/invites")
|
| 577 |
if result["status"] != 200:
|
| 578 |
return []
|
| 579 |
|
|
|
|
| 624 |
return completed
|
| 625 |
|
| 626 |
|
| 627 |
+
def _is_email_in_team(email):
|
| 628 |
+
"""检查邮箱是否已实际进入 Team。"""
|
| 629 |
+
chatgpt = None
|
| 630 |
+
try:
|
| 631 |
+
chatgpt = ChatGPTTeamAPI()
|
| 632 |
+
chatgpt.start()
|
| 633 |
+
members, _ = fetch_team_state(chatgpt)
|
| 634 |
+
return any((m.get("email", "") or "").lower() == email.lower() for m in members)
|
| 635 |
+
except Exception as exc:
|
| 636 |
+
logger.warning("[直接注册] 检查 Team 成员失败: %s", exc)
|
| 637 |
+
return False
|
| 638 |
+
finally:
|
| 639 |
+
if chatgpt and chatgpt.browser:
|
| 640 |
+
chatgpt.stop()
|
| 641 |
|
|
|
|
|
|
|
|
|
|
| 642 |
|
| 643 |
+
def _register_direct_once(mail_client, email, password):
|
| 644 |
+
"""执行一次直接注册,返回是否完成注册并进入 Team。"""
|
| 645 |
+
from autoteam.invite import screenshot
|
| 646 |
+
from playwright.sync_api import sync_playwright
|
| 647 |
|
|
|
|
| 648 |
logger.info("[直接注册] %s", email)
|
| 649 |
signup_url = "https://chatgpt.com/auth/login"
|
| 650 |
|
|
|
|
| 662 |
page.goto(signup_url, wait_until="domcontentloaded", timeout=60000)
|
| 663 |
time.sleep(5)
|
| 664 |
|
|
|
|
| 665 |
for i in range(12):
|
| 666 |
html = page.content()[:2000].lower()
|
| 667 |
if "verify you are human" not in html and "challenge" not in page.url:
|
|
|
|
| 671 |
|
| 672 |
screenshot(page, "direct_01_login_page.png")
|
| 673 |
|
|
|
|
| 674 |
try:
|
| 675 |
+
signup_btn = page.locator(
|
| 676 |
+
'button:has-text("注册"), button:has-text("Sign up"), a:has-text("Sign up"), a:has-text("注册")'
|
| 677 |
+
).first
|
| 678 |
if signup_btn.is_visible(timeout=5000):
|
| 679 |
signup_btn.click()
|
| 680 |
time.sleep(3)
|
|
|
|
| 683 |
|
| 684 |
screenshot(page, "direct_02_signup.png")
|
| 685 |
|
|
|
|
| 686 |
logger.info("[直接注册] 输入邮箱: %s", email)
|
|
|
|
| 687 |
try:
|
| 688 |
+
for attempt in range(2):
|
| 689 |
+
email_input = page.locator('input[name="email"], input[type="email"]').first
|
| 690 |
+
if not email_input.is_visible(timeout=5000):
|
| 691 |
+
break
|
| 692 |
+
|
| 693 |
email_input.fill(email)
|
| 694 |
time.sleep(0.5)
|
| 695 |
+
_click_primary_auth_button(page, email_input, ["Continue", "继续"])
|
| 696 |
time.sleep(3)
|
| 697 |
+
|
| 698 |
+
if not _is_google_redirect(page):
|
| 699 |
+
break
|
| 700 |
+
|
| 701 |
+
screenshot(page, f"direct_03_google_redirect_attempt{attempt+1}.png")
|
| 702 |
+
logger.warning("[直接注册] 邮箱步骤误跳转到 Google 登录,返回重试... (attempt %d)", attempt + 1)
|
| 703 |
+
page.go_back(wait_until="domcontentloaded", timeout=30000)
|
| 704 |
+
time.sleep(2)
|
| 705 |
except Exception:
|
| 706 |
pass
|
| 707 |
|
| 708 |
screenshot(page, "direct_03_after_email.png")
|
| 709 |
+
if _is_google_redirect(page):
|
| 710 |
+
logger.warning("[直接注册] 邮箱步骤仍停留在 Google 登录页")
|
| 711 |
+
browser.close()
|
| 712 |
+
return False
|
| 713 |
|
|
|
|
|
|
|
| 714 |
try:
|
| 715 |
+
for attempt in range(2):
|
| 716 |
+
pwd_input = page.locator('input[type="password"]').first
|
| 717 |
+
if not pwd_input.is_visible(timeout=5000):
|
| 718 |
+
break
|
| 719 |
+
|
| 720 |
logger.info("[直接注册] 设置密码")
|
| 721 |
pwd_input.fill(password)
|
| 722 |
time.sleep(0.5)
|
| 723 |
+
_click_primary_auth_button(page, pwd_input, ["Continue", "继续", "Log in"])
|
| 724 |
time.sleep(5)
|
| 725 |
+
|
| 726 |
+
if not _is_google_redirect(page):
|
| 727 |
+
break
|
| 728 |
+
|
| 729 |
+
screenshot(page, f"direct_04_google_redirect_attempt{attempt+1}.png")
|
| 730 |
+
logger.warning("[直接注册] 密码步骤误跳转到 Google 登录,返回重试... (attempt %d)", attempt + 1)
|
| 731 |
+
page.go_back(wait_until="domcontentloaded", timeout=30000)
|
| 732 |
+
time.sleep(2)
|
| 733 |
except Exception:
|
| 734 |
pass
|
| 735 |
|
| 736 |
screenshot(page, "direct_04_after_password.png")
|
| 737 |
+
if _is_google_redirect(page):
|
| 738 |
+
logger.warning("[直接注册] 密码步骤仍停留在 Google 登录页")
|
| 739 |
+
browser.close()
|
| 740 |
+
return False
|
| 741 |
|
|
|
|
| 742 |
code_input = None
|
| 743 |
try:
|
| 744 |
code_input = page.locator('input[name="code"], input[placeholder*="验证码"], input[placeholder*="code" i]').first
|
|
|
|
| 749 |
|
| 750 |
if code_input:
|
| 751 |
import re
|
| 752 |
+
|
| 753 |
logger.info("[直接注册] 等待验证码...")
|
| 754 |
verification_code = None
|
| 755 |
start_t = time.time()
|
|
|
|
| 771 |
logger.info("[直接注册] 输入验证码: %s", verification_code)
|
| 772 |
code_input.fill(verification_code)
|
| 773 |
time.sleep(0.5)
|
| 774 |
+
_click_primary_auth_button(page, code_input, ["Continue", "继续"])
|
| 775 |
time.sleep(8)
|
| 776 |
else:
|
| 777 |
logger.error("[直接注册] 未收到验证码")
|
| 778 |
browser.close()
|
| 779 |
+
return False
|
| 780 |
|
| 781 |
screenshot(page, "direct_05_after_code.png")
|
| 782 |
logger.info("[直接注册] 当前 URL: %s", page.url)
|
| 783 |
|
|
|
|
| 784 |
name_input = page.locator('input[name="name"]').first
|
| 785 |
try:
|
| 786 |
if name_input.is_visible(timeout=5000):
|
| 787 |
name_input.fill("User")
|
| 788 |
time.sleep(0.5)
|
| 789 |
|
|
|
|
| 790 |
spinbuttons = page.locator('[role="spinbutton"]').all()
|
| 791 |
if len(spinbuttons) >= 3:
|
| 792 |
try:
|
|
|
|
| 809 |
except Exception:
|
| 810 |
pass
|
| 811 |
|
| 812 |
+
_click_primary_auth_button(page, name_input, ["完成帐户创建", "Continue", "继续"])
|
| 813 |
time.sleep(8)
|
| 814 |
except Exception:
|
| 815 |
pass
|
|
|
|
| 817 |
screenshot(page, "direct_06_after_profile.png")
|
| 818 |
logger.info("[直接注册] 当前 URL: %s", page.url)
|
| 819 |
|
|
|
|
| 820 |
try:
|
| 821 |
join_btn = page.locator('button:has-text("Accept"), button:has-text("Join"), button:has-text("加入")').first
|
| 822 |
if join_btn.is_visible(timeout=5000):
|
|
|
|
| 827 |
|
| 828 |
screenshot(page, "direct_07_final.png")
|
| 829 |
|
|
|
|
| 830 |
current_url = page.url
|
| 831 |
+
success = "chatgpt.com" in current_url and "auth" not in current_url and not _is_google_redirect(page)
|
| 832 |
if success:
|
| 833 |
logger.info("[直接注册] 注册成功并已加入 workspace!")
|
| 834 |
else:
|
| 835 |
logger.warning("[直接注册] 注册可能未完成,URL: %s", current_url)
|
| 836 |
|
| 837 |
browser.close()
|
| 838 |
+
return success
|
| 839 |
+
|
| 840 |
+
|
| 841 |
+
def create_account_direct(mail_client):
|
| 842 |
+
"""
|
| 843 |
+
直接注册模式(域名已配置自动加入 workspace,不需要邀请)。
|
| 844 |
+
流程:创建邮箱 → 注册 ChatGPT → 自动加入 workspace → Codex 登录
|
| 845 |
+
"""
|
| 846 |
+
import uuid
|
| 847 |
+
|
| 848 |
+
account_id, email = mail_client.create_temp_email()
|
| 849 |
+
password = f"Tmp_{uuid.uuid4().hex[:12]}!"
|
| 850 |
+
|
| 851 |
+
success = False
|
| 852 |
+
for attempt in range(3):
|
| 853 |
+
logger.info("[直接注册] 开始第 %d/3 次注册尝试: %s", attempt + 1, email)
|
| 854 |
+
success = _register_direct_once(mail_client, email, password)
|
| 855 |
+
if success:
|
| 856 |
+
break
|
| 857 |
+
|
| 858 |
+
if _is_email_in_team(email):
|
| 859 |
+
logger.info("[直接注册] 远端确认账号已在 Team 中,视为注册成功: %s", email)
|
| 860 |
+
success = True
|
| 861 |
+
break
|
| 862 |
+
|
| 863 |
+
if attempt < 2:
|
| 864 |
+
logger.warning("[直接注册] 注册失败且账号不在 Team 中,60 秒后重试: %s", email)
|
| 865 |
+
time.sleep(60)
|
| 866 |
|
| 867 |
if not success:
|
| 868 |
+
logger.error("[直接注册] 连续 3 次注册失败,删除临时账号: %s", email)
|
| 869 |
+
try:
|
| 870 |
+
mail_client.delete_account(account_id)
|
| 871 |
+
except Exception as exc:
|
| 872 |
+
logger.warning("[直接注册] 删���失败临时邮箱异常: %s", exc)
|
| 873 |
return None
|
| 874 |
|
| 875 |
+
add_account(email, password, cloudmail_account_id=account_id)
|
| 876 |
+
|
| 877 |
# Step 4: Codex 登录
|
| 878 |
bundle = login_codex_via_browser(email, password, mail_client=mail_client)
|
| 879 |
if bundle:
|
|
|
|
| 1243 |
chatgpt.stop()
|
| 1244 |
|
| 1245 |
|
| 1246 |
+
def cmd_admin_login(email=None):
|
| 1247 |
+
"""交互式完成管理员登录并保存到 state.json。"""
|
| 1248 |
+
email = (email or "").strip()
|
| 1249 |
+
if not email:
|
| 1250 |
+
email = input("管理员邮箱: ").strip()
|
| 1251 |
+
|
| 1252 |
+
if not email:
|
| 1253 |
+
logger.error("[管理员登录] 邮箱不能为空")
|
| 1254 |
+
return None
|
| 1255 |
+
|
| 1256 |
+
chatgpt = ChatGPTTeamAPI()
|
| 1257 |
+
|
| 1258 |
+
try:
|
| 1259 |
+
logger.info("[管理员登录] 开始: %s", email)
|
| 1260 |
+
result = chatgpt.begin_admin_login(email)
|
| 1261 |
+
step = result.get("step")
|
| 1262 |
+
|
| 1263 |
+
while True:
|
| 1264 |
+
if step == "completed":
|
| 1265 |
+
info = chatgpt.complete_admin_login()
|
| 1266 |
+
logger.info("[管理员登录] 登录完成: %s", info.get("email") or email)
|
| 1267 |
+
if info.get("account_id"):
|
| 1268 |
+
logger.info("[管理员登录] Workspace ID: %s", info["account_id"])
|
| 1269 |
+
if info.get("workspace_name"):
|
| 1270 |
+
logger.info("[管理员登录] Workspace 名称: %s", info["workspace_name"])
|
| 1271 |
+
return info
|
| 1272 |
+
|
| 1273 |
+
if step == "password_required":
|
| 1274 |
+
password = getpass.getpass("管理员密码(留空取消): ")
|
| 1275 |
+
if not password:
|
| 1276 |
+
logger.warning("[管理员登录] 已取消")
|
| 1277 |
+
return None
|
| 1278 |
+
result = chatgpt.submit_admin_password(password)
|
| 1279 |
+
step = result.get("step")
|
| 1280 |
+
continue
|
| 1281 |
+
|
| 1282 |
+
if step == "code_required":
|
| 1283 |
+
code = input("邮箱验证码(留空取消): ").strip()
|
| 1284 |
+
if not code:
|
| 1285 |
+
logger.warning("[管理员登录] 已取消")
|
| 1286 |
+
return None
|
| 1287 |
+
result = chatgpt.submit_admin_code(code)
|
| 1288 |
+
step = result.get("step")
|
| 1289 |
+
continue
|
| 1290 |
+
|
| 1291 |
+
if step == "workspace_required":
|
| 1292 |
+
options = chatgpt.list_workspace_options()
|
| 1293 |
+
if not options:
|
| 1294 |
+
raise RuntimeError("当前需要选择组织,但未获取到可选项")
|
| 1295 |
+
|
| 1296 |
+
logger.info("[管理员登录] 请选择要进入的 workspace:")
|
| 1297 |
+
for idx, option in enumerate(options, 1):
|
| 1298 |
+
suffix = " [推荐]" if option.get("kind") == "preferred" else ""
|
| 1299 |
+
logger.info("[管理员登录] %d. %s%s", idx, option["label"], suffix)
|
| 1300 |
+
|
| 1301 |
+
choice = input("选择序号(留空取消): ").strip()
|
| 1302 |
+
if not choice:
|
| 1303 |
+
logger.warning("[管理员登录] 已取消")
|
| 1304 |
+
return None
|
| 1305 |
+
if not choice.isdigit():
|
| 1306 |
+
raise RuntimeError(f"无效的序号: {choice}")
|
| 1307 |
+
|
| 1308 |
+
selected_index = int(choice) - 1
|
| 1309 |
+
if selected_index < 0 or selected_index >= len(options):
|
| 1310 |
+
raise RuntimeError(f"序号超出范围: {choice}")
|
| 1311 |
+
|
| 1312 |
+
result = chatgpt.select_workspace_option(options[selected_index]["id"])
|
| 1313 |
+
step = result.get("step")
|
| 1314 |
+
continue
|
| 1315 |
+
|
| 1316 |
+
detail = result.get("detail") or "无法识别管理员登录步骤"
|
| 1317 |
+
raise RuntimeError(detail)
|
| 1318 |
+
|
| 1319 |
+
except KeyboardInterrupt:
|
| 1320 |
+
logger.warning("[管理员登录] 已中断")
|
| 1321 |
+
return None
|
| 1322 |
+
finally:
|
| 1323 |
+
chatgpt.stop()
|
| 1324 |
+
|
| 1325 |
+
|
| 1326 |
+
def cmd_main_codex_sync():
|
| 1327 |
+
"""交互式同步主号 Codex 认证到 CPA。"""
|
| 1328 |
+
state = get_admin_state_summary()
|
| 1329 |
+
if not state.get("session_present") or not state.get("email"):
|
| 1330 |
+
logger.error("[主号 Codex] 缺少管理员登录态,请先执行 admin-login")
|
| 1331 |
+
return None
|
| 1332 |
+
|
| 1333 |
+
flow = MainCodexSyncFlow()
|
| 1334 |
+
try:
|
| 1335 |
+
logger.info("[主号 Codex] 开始同步: %s", state.get("email"))
|
| 1336 |
+
result = flow.start()
|
| 1337 |
+
step = result.get("step")
|
| 1338 |
+
|
| 1339 |
+
while True:
|
| 1340 |
+
if step == "completed":
|
| 1341 |
+
info = flow.complete()
|
| 1342 |
+
logger.info("[主号 Codex] 同步完成: %s", info.get("email") or state.get("email"))
|
| 1343 |
+
if info.get("plan_type"):
|
| 1344 |
+
logger.info("[主号 Codex] Plan: %s", info["plan_type"])
|
| 1345 |
+
if info.get("auth_file"):
|
| 1346 |
+
logger.info("[主号 Codex] Auth 文件: %s", info["auth_file"])
|
| 1347 |
+
return info
|
| 1348 |
+
|
| 1349 |
+
if step == "password_required":
|
| 1350 |
+
password = getpass.getpass("主号密码(留空取消): ")
|
| 1351 |
+
if not password:
|
| 1352 |
+
logger.warning("[主号 Codex] 已取消")
|
| 1353 |
+
return None
|
| 1354 |
+
result = flow.submit_password(password)
|
| 1355 |
+
step = result.get("step")
|
| 1356 |
+
continue
|
| 1357 |
+
|
| 1358 |
+
if step == "code_required":
|
| 1359 |
+
code = input("主号验证码(留空取消): ").strip()
|
| 1360 |
+
if not code:
|
| 1361 |
+
logger.warning("[主号 Codex] 已取消")
|
| 1362 |
+
return None
|
| 1363 |
+
result = flow.submit_code(code)
|
| 1364 |
+
step = result.get("step")
|
| 1365 |
+
continue
|
| 1366 |
+
|
| 1367 |
+
detail = result.get("detail") or "无法识别主号 Codex 登录步骤"
|
| 1368 |
+
raise RuntimeError(detail)
|
| 1369 |
+
except KeyboardInterrupt:
|
| 1370 |
+
logger.warning("[主号 Codex] 已中断")
|
| 1371 |
+
return None
|
| 1372 |
+
finally:
|
| 1373 |
+
flow.stop()
|
| 1374 |
+
|
| 1375 |
+
|
| 1376 |
def get_team_member_count(chatgpt_api):
|
| 1377 |
"""获取当前 Team 成员数"""
|
| 1378 |
+
account_id = get_chatgpt_account_id()
|
| 1379 |
+
path = f"/backend-api/accounts/{account_id}/users"
|
| 1380 |
result = chatgpt_api._api_fetch("GET", path)
|
| 1381 |
if result["status"] != 200:
|
| 1382 |
return -1
|
|
|
|
| 1444 |
|
| 1445 |
def cmd_cleanup(max_seats=None):
|
| 1446 |
"""清理多余的 Team 成员,只移除本地 accounts.json 中管理的账号"""
|
| 1447 |
+
account_id = get_chatgpt_account_id()
|
| 1448 |
accounts = load_accounts()
|
| 1449 |
local_emails = {a["email"].lower() for a in accounts}
|
| 1450 |
|
|
|
|
| 1457 |
|
| 1458 |
try:
|
| 1459 |
# 获取当前成员列表
|
| 1460 |
+
path = f"/backend-api/accounts/{account_id}/users"
|
| 1461 |
result = chatgpt._api_fetch("GET", path)
|
| 1462 |
|
| 1463 |
if result["status"] != 200:
|
|
|
|
| 1515 |
email = m.get("email", "")
|
| 1516 |
user_id = m.get("user_id") or m.get("id")
|
| 1517 |
|
| 1518 |
+
delete_path = f"/backend-api/accounts/{account_id}/users/{user_id}"
|
| 1519 |
result = chatgpt._api_fetch("DELETE", delete_path)
|
| 1520 |
|
| 1521 |
if result["status"] in (200, 204):
|
|
|
|
| 1525 |
logger.error("[清理] 移除 %s 失败: %d", email, result['status'])
|
| 1526 |
|
| 1527 |
# 取消 pending invites 中本地管理的
|
| 1528 |
+
inv_result = chatgpt._api_fetch("GET", f"/backend-api/accounts/{account_id}/invites")
|
| 1529 |
if inv_result["status"] == 200:
|
| 1530 |
inv_data = json.loads(inv_result["body"])
|
| 1531 |
invites = inv_data if isinstance(inv_data, list) else inv_data.get("invites", inv_data.get("account_invites", []))
|
|
|
|
| 1534 |
inv_id = inv.get("id")
|
| 1535 |
if inv_email in local_emails and inv_id:
|
| 1536 |
del_result = chatgpt._api_fetch("DELETE",
|
| 1537 |
+
f"/backend-api/accounts/{account_id}/invites/{inv_id}")
|
| 1538 |
if del_result["status"] in (200, 204):
|
| 1539 |
logger.info("[清理] 已取消邀请 %s", inv_email)
|
| 1540 |
|
|
|
|
| 1559 |
rotate_p = sub.add_parser("rotate", help="智能轮转(检查额度 → 移出 → 复用旧号 → 万不得已才创建新号)")
|
| 1560 |
rotate_p.add_argument("target", type=int, nargs="?", default=5, help="目标成员数(默认 5)")
|
| 1561 |
sub.add_parser("add", help="手动添加一个新账号")
|
| 1562 |
+
admin_login_p = sub.add_parser("admin-login", help="交互式完成管理员主号登录")
|
| 1563 |
+
admin_login_p.add_argument("--email", help="管理员邮箱;不传则运行时交互输入")
|
| 1564 |
+
sub.add_parser("main-codex-sync", help="交互式同步主号 Codex 到 CPA")
|
| 1565 |
|
| 1566 |
fill_p = sub.add_parser("fill", help="补满 Team 成员到指定数量")
|
| 1567 |
fill_p.add_argument("target", type=int, nargs="?", default=5, help="目标成员数(默认 5)")
|
|
|
|
| 1589 |
cmd_rotate(args.target)
|
| 1590 |
elif args.command == "add":
|
| 1591 |
cmd_add()
|
| 1592 |
+
elif args.command == "admin-login":
|
| 1593 |
+
cmd_admin_login(args.email)
|
| 1594 |
+
elif args.command == "main-codex-sync":
|
| 1595 |
+
cmd_main_codex_sync()
|
| 1596 |
elif args.command == "fill":
|
| 1597 |
cmd_fill(args.target)
|
| 1598 |
elif args.command == "cleanup":
|
src/autoteam/web/dist/assets/index-BQe62tYy.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const o of i.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&n(o)}).observe(document,{childList:!0,subtree:!0});function s(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerPolicy&&(i.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?i.credentials="include":r.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function n(r){if(r.ep)return;r.ep=!0;const i=s(r);fetch(r.href,i)}})();/**
|
| 2 |
-
* @vue/shared v3.5.32
|
| 3 |
-
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 4 |
-
* @license MIT
|
| 5 |
-
**/function Ls(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const k={},ot=[],Re=()=>{},qn=()=>!1,ns=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),rs=e=>e.startsWith("onUpdate:"),ne=Object.assign,ks=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},Qr=Object.prototype.hasOwnProperty,N=(e,t)=>Qr.call(e,t),R=Array.isArray,lt=e=>Ft(e)==="[object Map]",Gn=e=>Ft(e)==="[object Set]",hn=e=>Ft(e)==="[object Date]",I=e=>typeof e=="function",Z=e=>typeof e=="string",Ie=e=>typeof e=="symbol",H=e=>e!==null&&typeof e=="object",Jn=e=>(H(e)||I(e))&&I(e.then)&&I(e.catch),Yn=Object.prototype.toString,Ft=e=>Yn.call(e),ei=e=>Ft(e).slice(8,-1),zn=e=>Ft(e)==="[object Object]",Vs=e=>Z(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xt=Ls(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),is=e=>{const t=Object.create(null);return(s=>t[s]||(t[s]=e(s)))},ti=/-\w/g,_e=is(e=>e.replace(ti,t=>t.slice(1).toUpperCase())),si=/\B([A-Z])/g,st=is(e=>e.replace(si,"-$1").toLowerCase()),Xn=is(e=>e.charAt(0).toUpperCase()+e.slice(1)),ms=is(e=>e?`on${Xn(e)}`:""),Me=(e,t)=>!Object.is(e,t),Wt=(e,...t)=>{for(let s=0;s<e.length;s++)e[s](...t)},Zn=(e,t,s,n=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},Us=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let pn;const os=()=>pn||(pn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Ks(e){if(R(e)){const t={};for(let s=0;s<e.length;s++){const n=e[s],r=Z(n)?oi(n):Ks(n);if(r)for(const i in r)t[i]=r[i]}return t}else if(Z(e)||H(e))return e}const ni=/;(?![^(]*\))/g,ri=/:([^]+)/,ii=/\/\*[^]*?\*\//g;function oi(e){const t={};return e.replace(ii,"").split(ni).forEach(s=>{if(s){const n=s.split(ri);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function pe(e){let t="";if(Z(e))t=e;else if(R(e))for(let s=0;s<e.length;s++){const n=pe(e[s]);n&&(t+=n+" ")}else if(H(e))for(const s in e)e[s]&&(t+=s+" ");return t.trim()}const li="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",ci=Ls(li);function Qn(e){return!!e||e===""}function fi(e,t){if(e.length!==t.length)return!1;let s=!0;for(let n=0;s&&n<e.length;n++)s=Bs(e[n],t[n]);return s}function Bs(e,t){if(e===t)return!0;let s=hn(e),n=hn(t);if(s||n)return s&&n?e.getTime()===t.getTime():!1;if(s=Ie(e),n=Ie(t),s||n)return e===t;if(s=R(e),n=R(t),s||n)return s&&n?fi(e,t):!1;if(s=H(e),n=H(t),s||n){if(!s||!n)return!1;const r=Object.keys(e).length,i=Object.keys(t).length;if(r!==i)return!1;for(const o in e){const l=e.hasOwnProperty(o),c=t.hasOwnProperty(o);if(l&&!c||!l&&c||!Bs(e[o],t[o]))return!1}}return String(e)===String(t)}const er=e=>!!(e&&e.__v_isRef===!0),B=e=>Z(e)?e:e==null?"":R(e)||H(e)&&(e.toString===Yn||!I(e.toString))?er(e)?B(e.value):JSON.stringify(e,tr,2):String(e),tr=(e,t)=>er(t)?tr(e,t.value):lt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,r],i)=>(s[bs(n,i)+" =>"]=r,s),{})}:Gn(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>bs(s))}:Ie(t)?bs(t):H(t)&&!R(t)&&!zn(t)?String(t):t,bs=(e,t="")=>{var s;return Ie(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/**
|
| 6 |
-
* @vue/reactivity v3.5.32
|
| 7 |
-
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 8 |
-
* @license MIT
|
| 9 |
-
**/let ue;class ui{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.__v_skip=!0,this.parent=ue,!t&&ue&&(this.index=(ue.scopes||(ue.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].pause();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].resume();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].resume()}}run(t){if(this._active){const s=ue;try{return ue=this,t()}finally{ue=s}}}on(){++this._on===1&&(this.prevScope=ue,ue=this)}off(){this._on>0&&--this._on===0&&(ue=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s<n;s++)this.effects[s].stop();for(this.effects.length=0,s=0,n=this.cleanups.length;s<n;s++)this.cleanups[s]();if(this.cleanups.length=0,this.scopes){for(s=0,n=this.scopes.length;s<n;s++)this.scopes[s].stop(!0);this.scopes.length=0}if(!this.detached&&this.parent&&!t){const r=this.parent.scopes.pop();r&&r!==this&&(this.parent.scopes[this.index]=r,r.index=this.index)}this.parent=void 0}}}function ai(){return ue}let K;const ys=new WeakSet;class sr{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.flags=5,this.next=void 0,this.cleanup=void 0,this.scheduler=void 0,ue&&ue.active&&ue.effects.push(this)}pause(){this.flags|=64}resume(){this.flags&64&&(this.flags&=-65,ys.has(this)&&(ys.delete(this),this.trigger()))}notify(){this.flags&2&&!(this.flags&32)||this.flags&8||rr(this)}run(){if(!(this.flags&1))return this.fn();this.flags|=2,gn(this),ir(this);const t=K,s=xe;K=this,xe=!0;try{return this.fn()}finally{or(this),K=t,xe=s,this.flags&=-3}}stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)Gs(t);this.deps=this.depsTail=void 0,gn(this),this.onStop&&this.onStop(),this.flags&=-2}}trigger(){this.flags&64?ys.add(this):this.scheduler?this.scheduler():this.runIfDirty()}runIfDirty(){Os(this)&&this.run()}get dirty(){return Os(this)}}let nr=0,vt,wt;function rr(e,t=!1){if(e.flags|=8,t){e.next=wt,wt=e;return}e.next=vt,vt=e}function Ws(){nr++}function qs(){if(--nr>0)return;if(wt){let t=wt;for(wt=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;vt;){let t=vt;for(vt=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function ir(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function or(e){let t,s=e.depsTail,n=s;for(;n;){const r=n.prevDep;n.version===-1?(n===s&&(s=r),Gs(n),di(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=r}e.deps=t,e.depsTail=s}function Os(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(lr(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function lr(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Ot)||(e.globalVersion=Ot,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Os(e))))return;e.flags|=2;const t=e.dep,s=K,n=xe;K=e,xe=!0;try{ir(e);const r=e.fn(e._value);(t.version===0||Me(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{K=s,xe=n,or(e),e.flags&=-3}}function Gs(e,t=!1){const{dep:s,prevSub:n,nextSub:r}=e;if(n&&(n.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let i=s.computed.deps;i;i=i.nextDep)Gs(i,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function di(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let xe=!0;const cr=[];function ke(){cr.push(xe),xe=!1}function Ve(){const e=cr.pop();xe=e===void 0?!0:e}function gn(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=K;K=void 0;try{t()}finally{K=s}}}let Ot=0;class hi{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class Js{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!K||!xe||K===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==K)s=this.activeLink=new hi(K,this),K.deps?(s.prevDep=K.depsTail,K.depsTail.nextDep=s,K.depsTail=s):K.deps=K.depsTail=s,fr(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=K.depsTail,s.nextDep=void 0,K.depsTail.nextDep=s,K.depsTail=s,K.deps===s&&(K.deps=n)}return s}trigger(t){this.version++,Ot++,this.notify(t)}notify(t){Ws();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{qs()}}}function fr(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)fr(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Ps=new WeakMap,et=Symbol(""),$s=Symbol(""),Pt=Symbol("");function te(e,t,s){if(xe&&K){let n=Ps.get(e);n||Ps.set(e,n=new Map);let r=n.get(s);r||(n.set(s,r=new Js),r.map=n,r.key=s),r.track()}}function He(e,t,s,n,r,i){const o=Ps.get(e);if(!o){Ot++;return}const l=c=>{c&&c.trigger()};if(Ws(),t==="clear")o.forEach(l);else{const c=R(e),d=c&&Vs(s);if(c&&s==="length"){const u=Number(n);o.forEach((h,m)=>{(m==="length"||m===Pt||!Ie(m)&&m>=u)&&l(h)})}else switch((s!==void 0||o.has(void 0))&&l(o.get(s)),d&&l(o.get(Pt)),t){case"add":c?d&&l(o.get("length")):(l(o.get(et)),lt(e)&&l(o.get($s)));break;case"delete":c||(l(o.get(et)),lt(e)&&l(o.get($s)));break;case"set":lt(e)&&l(o.get(et));break}}qs()}function nt(e){const t=j(e);return t===e?t:(te(t,"iterate",Pt),ye(e)?t:t.map(ve))}function ls(e){return te(e=j(e),"iterate",Pt),e}function Pe(e,t){return Ue(e)?ut(tt(e)?ve(t):t):ve(t)}const pi={__proto__:null,[Symbol.iterator](){return _s(this,Symbol.iterator,e=>Pe(this,e))},concat(...e){return nt(this).concat(...e.map(t=>R(t)?nt(t):t))},entries(){return _s(this,"entries",e=>(e[1]=Pe(this,e[1]),e))},every(e,t){return De(this,"every",e,t,void 0,arguments)},filter(e,t){return De(this,"filter",e,t,s=>s.map(n=>Pe(this,n)),arguments)},find(e,t){return De(this,"find",e,t,s=>Pe(this,s),arguments)},findIndex(e,t){return De(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return De(this,"findLast",e,t,s=>Pe(this,s),arguments)},findLastIndex(e,t){return De(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return De(this,"forEach",e,t,void 0,arguments)},includes(...e){return xs(this,"includes",e)},indexOf(...e){return xs(this,"indexOf",e)},join(e){return nt(this).join(e)},lastIndexOf(...e){return xs(this,"lastIndexOf",e)},map(e,t){return De(this,"map",e,t,void 0,arguments)},pop(){return bt(this,"pop")},push(...e){return bt(this,"push",e)},reduce(e,...t){return mn(this,"reduce",e,t)},reduceRight(e,...t){return mn(this,"reduceRight",e,t)},shift(){return bt(this,"shift")},some(e,t){return De(this,"some",e,t,void 0,arguments)},splice(...e){return bt(this,"splice",e)},toReversed(){return nt(this).toReversed()},toSorted(e){return nt(this).toSorted(e)},toSpliced(...e){return nt(this).toSpliced(...e)},unshift(...e){return bt(this,"unshift",e)},values(){return _s(this,"values",e=>Pe(this,e))}};function _s(e,t,s){const n=ls(e),r=n[t]();return n!==e&&!ye(e)&&(r._next=r.next,r.next=()=>{const i=r._next();return i.done||(i.value=s(i.value)),i}),r}const gi=Array.prototype;function De(e,t,s,n,r,i){const o=ls(e),l=o!==e&&!ye(e),c=o[t];if(c!==gi[t]){const h=c.apply(e,i);return l?ve(h):h}let d=s;o!==e&&(l?d=function(h,m){return s.call(this,Pe(e,h),m,e)}:s.length>2&&(d=function(h,m){return s.call(this,h,m,e)}));const u=c.call(o,d,n);return l&&r?r(u):u}function mn(e,t,s,n){const r=ls(e),i=r!==e&&!ye(e);let o=s,l=!1;r!==e&&(i?(l=n.length===0,o=function(d,u,h){return l&&(l=!1,d=Pe(e,d)),s.call(this,d,Pe(e,u),h,e)}):s.length>3&&(o=function(d,u,h){return s.call(this,d,u,h,e)}));const c=r[t](o,...n);return l?Pe(e,c):c}function xs(e,t,s){const n=j(e);te(n,"iterate",Pt);const r=n[t](...s);return(r===-1||r===!1)&&Zs(s[0])?(s[0]=j(s[0]),n[t](...s)):r}function bt(e,t,s=[]){ke(),Ws();const n=j(e)[t].apply(e,s);return qs(),Ve(),n}const mi=Ls("__proto__,__v_isRef,__isVue"),ur=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ie));function bi(e){Ie(e)||(e=String(e));const t=j(this);return te(t,"has",e),t.hasOwnProperty(e)}class ar{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const r=this._isReadonly,i=this._isShallow;if(s==="__v_isReactive")return!r;if(s==="__v_isReadonly")return r;if(s==="__v_isShallow")return i;if(s==="__v_raw")return n===(r?i?Ai:gr:i?pr:hr).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const o=R(t);if(!r){let c;if(o&&(c=pi[s]))return c;if(s==="hasOwnProperty")return bi}const l=Reflect.get(t,s,se(t)?t:n);if((Ie(s)?ur.has(s):mi(s))||(r||te(t,"get",s),i))return l;if(se(l)){const c=o&&Vs(s)?l:l.value;return r&&H(c)?Rs(c):c}return H(l)?r?Rs(l):zs(l):l}}class dr extends ar{constructor(t=!1){super(!1,t)}set(t,s,n,r){let i=t[s];const o=R(t)&&Vs(s);if(!this._isShallow){const d=Ue(i);if(!ye(n)&&!Ue(n)&&(i=j(i),n=j(n)),!o&&se(i)&&!se(n))return d||(i.value=n),!0}const l=o?Number(s)<t.length:N(t,s),c=Reflect.set(t,s,n,se(t)?t:r);return t===j(r)&&(l?Me(n,i)&&He(t,"set",s,n):He(t,"add",s,n)),c}deleteProperty(t,s){const n=N(t,s);t[s];const r=Reflect.deleteProperty(t,s);return r&&n&&He(t,"delete",s,void 0),r}has(t,s){const n=Reflect.has(t,s);return(!Ie(s)||!ur.has(s))&&te(t,"has",s),n}ownKeys(t){return te(t,"iterate",R(t)?"length":et),Reflect.ownKeys(t)}}class yi extends ar{constructor(t=!1){super(!0,t)}set(t,s){return!0}deleteProperty(t,s){return!0}}const _i=new dr,xi=new yi,vi=new dr(!0);const Ms=e=>e,Ut=e=>Reflect.getPrototypeOf(e);function wi(e,t,s){return function(...n){const r=this.__v_raw,i=j(r),o=lt(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,d=r[e](...n),u=s?Ms:t?ut:ve;return!t&&te(i,"iterate",c?$s:et),ne(Object.create(d),{next(){const{value:h,done:m}=d.next();return m?{value:h,done:m}:{value:l?[u(h[0]),u(h[1])]:u(h),done:m}}})}}function Kt(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Si(e,t){const s={get(r){const i=this.__v_raw,o=j(i),l=j(r);e||(Me(r,l)&&te(o,"get",r),te(o,"get",l));const{has:c}=Ut(o),d=t?Ms:e?ut:ve;if(c.call(o,r))return d(i.get(r));if(c.call(o,l))return d(i.get(l));i!==o&&i.get(r)},get size(){const r=this.__v_raw;return!e&&te(j(r),"iterate",et),r.size},has(r){const i=this.__v_raw,o=j(i),l=j(r);return e||(Me(r,l)&&te(o,"has",r),te(o,"has",l)),r===l?i.has(r):i.has(r)||i.has(l)},forEach(r,i){const o=this,l=o.__v_raw,c=j(l),d=t?Ms:e?ut:ve;return!e&&te(c,"iterate",et),l.forEach((u,h)=>r.call(i,d(u),d(h),o))}};return ne(s,e?{add:Kt("add"),set:Kt("set"),delete:Kt("delete"),clear:Kt("clear")}:{add(r){const i=j(this),o=Ut(i),l=j(r),c=!t&&!ye(r)&&!Ue(r)?l:r;return o.has.call(i,c)||Me(r,c)&&o.has.call(i,r)||Me(l,c)&&o.has.call(i,l)||(i.add(c),He(i,"add",c,c)),this},set(r,i){!t&&!ye(i)&&!Ue(i)&&(i=j(i));const o=j(this),{has:l,get:c}=Ut(o);let d=l.call(o,r);d||(r=j(r),d=l.call(o,r));const u=c.call(o,r);return o.set(r,i),d?Me(i,u)&&He(o,"set",r,i):He(o,"add",r,i),this},delete(r){const i=j(this),{has:o,get:l}=Ut(i);let c=o.call(i,r);c||(r=j(r),c=o.call(i,r)),l&&l.call(i,r);const d=i.delete(r);return c&&He(i,"delete",r,void 0),d},clear(){const r=j(this),i=r.size!==0,o=r.clear();return i&&He(r,"clear",void 0,void 0),o}}),["keys","values","entries",Symbol.iterator].forEach(r=>{s[r]=wi(r,e,t)}),s}function Ys(e,t){const s=Si(e,t);return(n,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?n:Reflect.get(N(s,r)&&r in n?s:n,r,i)}const Ti={get:Ys(!1,!1)},Ci={get:Ys(!1,!0)},Ei={get:Ys(!0,!1)};const hr=new WeakMap,pr=new WeakMap,gr=new WeakMap,Ai=new WeakMap;function Oi(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Pi(e){return e.__v_skip||!Object.isExtensible(e)?0:Oi(ei(e))}function zs(e){return Ue(e)?e:Xs(e,!1,_i,Ti,hr)}function $i(e){return Xs(e,!1,vi,Ci,pr)}function Rs(e){return Xs(e,!0,xi,Ei,gr)}function Xs(e,t,s,n,r){if(!H(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=Pi(e);if(i===0)return e;const o=r.get(e);if(o)return o;const l=new Proxy(e,i===2?n:s);return r.set(e,l),l}function tt(e){return Ue(e)?tt(e.__v_raw):!!(e&&e.__v_isReactive)}function Ue(e){return!!(e&&e.__v_isReadonly)}function ye(e){return!!(e&&e.__v_isShallow)}function Zs(e){return e?!!e.__v_raw:!1}function j(e){const t=e&&e.__v_raw;return t?j(t):e}function Mi(e){return!N(e,"__v_skip")&&Object.isExtensible(e)&&Zn(e,"__v_skip",!0),e}const ve=e=>H(e)?zs(e):e,ut=e=>H(e)?Rs(e):e;function se(e){return e?e.__v_isRef===!0:!1}function he(e){return Ri(e,!1)}function Ri(e,t){return se(e)?e:new Ii(e,t)}class Ii{constructor(t,s){this.dep=new Js,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:j(t),this._value=s?t:ve(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||ye(t)||Ue(t);t=n?t:j(t),Me(t,s)&&(this._rawValue=t,this._value=n?t:ve(t),this.dep.trigger())}}function Fi(e){return se(e)?e.value:e}const Di={get:(e,t,s)=>t==="__v_raw"?e:Fi(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const r=e[t];return se(r)&&!se(s)?(r.value=s,!0):Reflect.set(e,t,s,n)}};function mr(e){return tt(e)?e:new Proxy(e,Di)}class ji{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new Js(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Ot-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&K!==this)return rr(this,!0),!0}get value(){const t=this.dep.track();return lr(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Ni(e,t,s=!1){let n,r;return I(e)?n=e:(n=e.get,r=e.set),new ji(n,r,s)}const Bt={},Xt=new WeakMap;let Qe;function Hi(e,t=!1,s=Qe){if(s){let n=Xt.get(s);n||Xt.set(s,n=[]),n.push(e)}}function Li(e,t,s=k){const{immediate:n,deep:r,once:i,scheduler:o,augmentJob:l,call:c}=s,d=M=>r?M:ye(M)||r===!1||r===0?Le(M,1):Le(M);let u,h,m,T,$=!1,E=!1;if(se(e)?(h=()=>e.value,$=ye(e)):tt(e)?(h=()=>d(e),$=!0):R(e)?(E=!0,$=e.some(M=>tt(M)||ye(M)),h=()=>e.map(M=>{if(se(M))return M.value;if(tt(M))return d(M);if(I(M))return c?c(M,2):M()})):I(e)?t?h=c?()=>c(e,2):e:h=()=>{if(m){ke();try{m()}finally{Ve()}}const M=Qe;Qe=u;try{return c?c(e,3,[T]):e(T)}finally{Qe=M}}:h=Re,t&&r){const M=h,Q=r===!0?1/0:r;h=()=>Le(M(),Q)}const q=ai(),G=()=>{u.stop(),q&&q.active&&ks(q.effects,u)};if(i&&t){const M=t;t=(...Q)=>{M(...Q),G()}}let F=E?new Array(e.length).fill(Bt):Bt;const W=M=>{if(!(!(u.flags&1)||!u.dirty&&!M))if(t){const Q=u.run();if(r||$||(E?Q.some((Be,we)=>Me(Be,F[we])):Me(Q,F))){m&&m();const Be=Qe;Qe=u;try{const we=[Q,F===Bt?void 0:E&&F[0]===Bt?[]:F,T];F=Q,c?c(t,3,we):t(...we)}finally{Qe=Be}}}else u.run()};return l&&l(W),u=new sr(h),u.scheduler=o?()=>o(W,!1):W,T=M=>Hi(M,!1,u),m=u.onStop=()=>{const M=Xt.get(u);if(M){if(c)c(M,4);else for(const Q of M)Q();Xt.delete(u)}},t?n?W(!0):F=u.run():o?o(W.bind(null,!0),!0):u.run(),G.pause=u.pause.bind(u),G.resume=u.resume.bind(u),G.stop=G,G}function Le(e,t=1/0,s){if(t<=0||!H(e)||e.__v_skip||(s=s||new Map,(s.get(e)||0)>=t))return e;if(s.set(e,t),t--,se(e))Le(e.value,t,s);else if(R(e))for(let n=0;n<e.length;n++)Le(e[n],t,s);else if(Gn(e)||lt(e))e.forEach(n=>{Le(n,t,s)});else if(zn(e)){for(const n in e)Le(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&Le(e[n],t,s)}return e}/**
|
| 10 |
-
* @vue/runtime-core v3.5.32
|
| 11 |
-
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 12 |
-
* @license MIT
|
| 13 |
-
**/function Dt(e,t,s,n){try{return n?e(...n):e()}catch(r){cs(r,t,s)}}function Fe(e,t,s,n){if(I(e)){const r=Dt(e,t,s,n);return r&&Jn(r)&&r.catch(i=>{cs(i,t,s)}),r}if(R(e)){const r=[];for(let i=0;i<e.length;i++)r.push(Fe(e[i],t,s,n));return r}}function cs(e,t,s,n=!0){const r=t?t.vnode:null,{errorHandler:i,throwUnhandledErrorInProduction:o}=t&&t.appContext.config||k;if(t){let l=t.parent;const c=t.proxy,d=`https://vuejs.org/error-reference/#runtime-${s}`;for(;l;){const u=l.ec;if(u){for(let h=0;h<u.length;h++)if(u[h](e,c,d)===!1)return}l=l.parent}if(i){ke(),Dt(i,null,10,[e,c,d]),Ve();return}}ki(e,s,r,n,o)}function ki(e,t,s,n=!0,r=!1){if(r)throw e;console.error(e)}const le=[];let Oe=-1;const ct=[];let qe=null,rt=0;const br=Promise.resolve();let Zt=null;function Vi(e){const t=Zt||br;return e?t.then(this?e.bind(this):e):t}function Ui(e){let t=Oe+1,s=le.length;for(;t<s;){const n=t+s>>>1,r=le[n],i=$t(r);i<e||i===e&&r.flags&2?t=n+1:s=n}return t}function Qs(e){if(!(e.flags&1)){const t=$t(e),s=le[le.length-1];!s||!(e.flags&2)&&t>=$t(s)?le.push(e):le.splice(Ui(t),0,e),e.flags|=1,yr()}}function yr(){Zt||(Zt=br.then(xr))}function Ki(e){R(e)?ct.push(...e):qe&&e.id===-1?qe.splice(rt+1,0,e):e.flags&1||(ct.push(e),e.flags|=1),yr()}function bn(e,t,s=Oe+1){for(;s<le.length;s++){const n=le[s];if(n&&n.flags&2){if(e&&n.id!==e.uid)continue;le.splice(s,1),s--,n.flags&4&&(n.flags&=-2),n(),n.flags&4||(n.flags&=-2)}}}function _r(e){if(ct.length){const t=[...new Set(ct)].sort((s,n)=>$t(s)-$t(n));if(ct.length=0,qe){qe.push(...t);return}for(qe=t,rt=0;rt<qe.length;rt++){const s=qe[rt];s.flags&4&&(s.flags&=-2),s.flags&8||s(),s.flags&=-2}qe=null,rt=0}}const $t=e=>e.id==null?e.flags&2?-1:1/0:e.id;function xr(e){try{for(Oe=0;Oe<le.length;Oe++){const t=le[Oe];t&&!(t.flags&8)&&(t.flags&4&&(t.flags&=-2),Dt(t,t.i,t.i?15:14),t.flags&4||(t.flags&=-2))}}finally{for(;Oe<le.length;Oe++){const t=le[Oe];t&&(t.flags&=-2)}Oe=-1,le.length=0,_r(),Zt=null,(le.length||ct.length)&&xr()}}let be=null,vr=null;function Qt(e){const t=be;return be=e,vr=e&&e.type.__scopeId||null,t}function Bi(e,t=be,s){if(!t||e._n)return e;const n=(...r)=>{n._d&&On(-1);const i=Qt(t);let o;try{o=e(...r)}finally{Qt(i),n._d&&On(1)}return o};return n._n=!0,n._c=!0,n._d=!0,n}function qt(e,t){if(be===null)return e;const s=hs(be),n=e.dirs||(e.dirs=[]);for(let r=0;r<t.length;r++){let[i,o,l,c=k]=t[r];i&&(I(i)&&(i={mounted:i,updated:i}),i.deep&&Le(o),n.push({dir:i,instance:s,value:o,oldValue:void 0,arg:l,modifiers:c}))}return e}function Xe(e,t,s,n){const r=e.dirs,i=t&&t.dirs;for(let o=0;o<r.length;o++){const l=r[o];i&&(l.oldValue=i[o].value);let c=l.dir[n];c&&(ke(),Fe(c,s,8,[e.el,l,e,t]),Ve())}}function Wi(e,t){if(ce){let s=ce.provides;const n=ce.parent&&ce.parent.provides;n===s&&(s=ce.provides=Object.create(n)),s[e]=t}}function Gt(e,t,s=!1){const n=Wo();if(n||ft){let r=ft?ft._context.provides:n?n.parent==null||n.ce?n.vnode.appContext&&n.vnode.appContext.provides:n.parent.provides:void 0;if(r&&e in r)return r[e];if(arguments.length>1)return s&&I(t)?t.call(n&&n.proxy):t}}const qi=Symbol.for("v-scx"),Gi=()=>Gt(qi);function vs(e,t,s){return wr(e,t,s)}function wr(e,t,s=k){const{immediate:n,deep:r,flush:i,once:o}=s,l=ne({},s),c=t&&n||!t&&i!=="post";let d;if(It){if(i==="sync"){const T=Gi();d=T.__watcherHandles||(T.__watcherHandles=[])}else if(!c){const T=()=>{};return T.stop=Re,T.resume=Re,T.pause=Re,T}}const u=ce;l.call=(T,$,E)=>Fe(T,u,$,E);let h=!1;i==="post"?l.scheduler=T=>{fe(T,u&&u.suspense)}:i!=="sync"&&(h=!0,l.scheduler=(T,$)=>{$?T():Qs(T)}),l.augmentJob=T=>{t&&(T.flags|=4),h&&(T.flags|=2,u&&(T.id=u.uid,T.i=u))};const m=Li(e,t,l);return It&&(d?d.push(m):c&&m()),m}function Ji(e,t,s){const n=this.proxy,r=Z(e)?e.includes(".")?Sr(n,e):()=>n[e]:e.bind(n,n);let i;I(t)?i=t:(i=t.handler,s=t);const o=jt(this),l=wr(r,i.bind(n),s);return o(),l}function Sr(e,t){const s=t.split(".");return()=>{let n=e;for(let r=0;r<s.length&&n;r++)n=n[s[r]];return n}}const Yi=Symbol("_vte"),zi=e=>e.__isTeleport,Xi=Symbol("_leaveCb");function en(e,t){e.shapeFlag&6&&e.component?(e.transition=t,en(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Tr(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function yn(e,t){let s;return!!((s=Object.getOwnPropertyDescriptor(e,t))&&!s.configurable)}const es=new WeakMap;function St(e,t,s,n,r=!1){if(R(e)){e.forEach((E,q)=>St(E,t&&(R(t)?t[q]:t),s,n,r));return}if(Tt(n)&&!r){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&St(e,t,s,n.component.subTree);return}const i=n.shapeFlag&4?hs(n.component):n.el,o=r?null:i,{i:l,r:c}=e,d=t&&t.r,u=l.refs===k?l.refs={}:l.refs,h=l.setupState,m=j(h),T=h===k?qn:E=>yn(u,E)?!1:N(m,E),$=(E,q)=>!(q&&yn(u,q));if(d!=null&&d!==c){if(_n(t),Z(d))u[d]=null,T(d)&&(h[d]=null);else if(se(d)){const E=t;$(d,E.k)&&(d.value=null),E.k&&(u[E.k]=null)}}if(I(c))Dt(c,l,12,[o,u]);else{const E=Z(c),q=se(c);if(E||q){const G=()=>{if(e.f){const F=E?T(c)?h[c]:u[c]:$()||!e.k?c.value:u[e.k];if(r)R(F)&&ks(F,i);else if(R(F))F.includes(i)||F.push(i);else if(E)u[c]=[i],T(c)&&(h[c]=u[c]);else{const W=[i];$(c,e.k)&&(c.value=W),e.k&&(u[e.k]=W)}}else E?(u[c]=o,T(c)&&(h[c]=o)):q&&($(c,e.k)&&(c.value=o),e.k&&(u[e.k]=o))};if(o){const F=()=>{G(),es.delete(e)};F.id=-1,es.set(e,F),fe(F,s)}else _n(e),G()}}}function _n(e){const t=es.get(e);t&&(t.flags|=8,es.delete(e))}os().requestIdleCallback;os().cancelIdleCallback;const Tt=e=>!!e.type.__asyncLoader,Cr=e=>e.type.__isKeepAlive;function Zi(e,t){Er(e,"a",t)}function Qi(e,t){Er(e,"da",t)}function Er(e,t,s=ce){const n=e.__wdc||(e.__wdc=()=>{let r=s;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(fs(t,n,s),s){let r=s.parent;for(;r&&r.parent;)Cr(r.parent.vnode)&&eo(n,t,s,r),r=r.parent}}function eo(e,t,s,n){const r=fs(t,e,n,!0);sn(()=>{ks(n[t],r)},s)}function fs(e,t,s=ce,n=!1){if(s){const r=s[e]||(s[e]=[]),i=t.__weh||(t.__weh=(...o)=>{ke();const l=jt(s),c=Fe(t,s,e,o);return l(),Ve(),c});return n?r.unshift(i):r.push(i),i}}const Ke=e=>(t,s=ce)=>{(!It||e==="sp")&&fs(e,(...n)=>t(...n),s)},to=Ke("bm"),tn=Ke("m"),so=Ke("bu"),no=Ke("u"),ro=Ke("bum"),sn=Ke("um"),io=Ke("sp"),oo=Ke("rtg"),lo=Ke("rtc");function co(e,t=ce){fs("ec",e,t)}const fo=Symbol.for("v-ndc");function Ct(e,t,s,n){let r;const i=s,o=R(e);if(o||Z(e)){const l=o&&tt(e);let c=!1,d=!1;l&&(c=!ye(e),d=Ue(e),e=ls(e)),r=new Array(e.length);for(let u=0,h=e.length;u<h;u++)r[u]=t(c?d?ut(ve(e[u])):ve(e[u]):e[u],u,void 0,i)}else if(typeof e=="number"){r=new Array(e);for(let l=0;l<e;l++)r[l]=t(l+1,l,void 0,i)}else if(H(e))if(e[Symbol.iterator])r=Array.from(e,(l,c)=>t(l,c,void 0,i));else{const l=Object.keys(e);r=new Array(l.length);for(let c=0,d=l.length;c<d;c++){const u=l[c];r[c]=t(e[u],u,c,i)}}else r=[];return r}const Is=e=>e?Gr(e)?hs(e):Is(e.parent):null,Et=ne(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Is(e.parent),$root:e=>Is(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Or(e),$forceUpdate:e=>e.f||(e.f=()=>{Qs(e.update)}),$nextTick:e=>e.n||(e.n=Vi.bind(e.proxy)),$watch:e=>Ji.bind(e)}),ws=(e,t)=>e!==k&&!e.__isScriptSetup&&N(e,t),uo={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:r,props:i,accessCache:o,type:l,appContext:c}=e;if(t[0]!=="$"){const m=o[t];if(m!==void 0)switch(m){case 1:return n[t];case 2:return r[t];case 4:return s[t];case 3:return i[t]}else{if(ws(n,t))return o[t]=1,n[t];if(r!==k&&N(r,t))return o[t]=2,r[t];if(N(i,t))return o[t]=3,i[t];if(s!==k&&N(s,t))return o[t]=4,s[t];Fs&&(o[t]=0)}}const d=Et[t];let u,h;if(d)return t==="$attrs"&&te(e.attrs,"get",""),d(e);if((u=l.__cssModules)&&(u=u[t]))return u;if(s!==k&&N(s,t))return o[t]=4,s[t];if(h=c.config.globalProperties,N(h,t))return h[t]},set({_:e},t,s){const{data:n,setupState:r,ctx:i}=e;return ws(r,t)?(r[t]=s,!0):n!==k&&N(n,t)?(n[t]=s,!0):N(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:r,props:i,type:o}},l){let c;return!!(s[l]||e!==k&&l[0]!=="$"&&N(e,l)||ws(t,l)||N(i,l)||N(n,l)||N(Et,l)||N(r.config.globalProperties,l)||(c=o.__cssModules)&&c[l])},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:N(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function xn(e){return R(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let Fs=!0;function ao(e){const t=Or(e),s=e.proxy,n=e.ctx;Fs=!1,t.beforeCreate&&vn(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:d,created:u,beforeMount:h,mounted:m,beforeUpdate:T,updated:$,activated:E,deactivated:q,beforeDestroy:G,beforeUnmount:F,destroyed:W,unmounted:M,render:Q,renderTracked:Be,renderTriggered:we,errorCaptured:We,serverPrefetch:Nt,expose:Je,inheritAttrs:ht,components:Ht,directives:Lt,filters:ps}=t;if(d&&ho(d,n,null),o)for(const J in o){const V=o[J];I(V)&&(n[J]=V.bind(s))}if(r){const J=r.call(s,s);H(J)&&(e.data=zs(J))}if(Fs=!0,i)for(const J in i){const V=i[J],Ye=I(V)?V.bind(s,s):I(V.get)?V.get.bind(s,s):Re,kt=!I(V)&&I(V.set)?V.set.bind(s):Re,ze=Yr({get:Ye,set:kt});Object.defineProperty(n,J,{enumerable:!0,configurable:!0,get:()=>ze.value,set:Se=>ze.value=Se})}if(l)for(const J in l)Ar(l[J],n,s,J);if(c){const J=I(c)?c.call(s):c;Reflect.ownKeys(J).forEach(V=>{Wi(V,J[V])})}u&&vn(u,e,"c");function re(J,V){R(V)?V.forEach(Ye=>J(Ye.bind(s))):V&&J(V.bind(s))}if(re(to,h),re(tn,m),re(so,T),re(no,$),re(Zi,E),re(Qi,q),re(co,We),re(lo,Be),re(oo,we),re(ro,F),re(sn,M),re(io,Nt),R(Je))if(Je.length){const J=e.exposed||(e.exposed={});Je.forEach(V=>{Object.defineProperty(J,V,{get:()=>s[V],set:Ye=>s[V]=Ye,enumerable:!0})})}else e.exposed||(e.exposed={});Q&&e.render===Re&&(e.render=Q),ht!=null&&(e.inheritAttrs=ht),Ht&&(e.components=Ht),Lt&&(e.directives=Lt),Nt&&Tr(e)}function ho(e,t,s=Re){R(e)&&(e=Ds(e));for(const n in e){const r=e[n];let i;H(r)?"default"in r?i=Gt(r.from||n,r.default,!0):i=Gt(r.from||n):i=Gt(r),se(i)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[n]=i}}function vn(e,t,s){Fe(R(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function Ar(e,t,s,n){let r=n.includes(".")?Sr(s,n):()=>s[n];if(Z(e)){const i=t[e];I(i)&&vs(r,i)}else if(I(e))vs(r,e.bind(s));else if(H(e))if(R(e))e.forEach(i=>Ar(i,t,s,n));else{const i=I(e.handler)?e.handler.bind(s):t[e.handler];I(i)&&vs(r,i,e)}}function Or(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!s&&!n?c=t:(c={},r.length&&r.forEach(d=>ts(c,d,o,!0)),ts(c,t,o)),H(t)&&i.set(t,c),c}function ts(e,t,s,n=!1){const{mixins:r,extends:i}=t;i&&ts(e,i,s,!0),r&&r.forEach(o=>ts(e,o,s,!0));for(const o in t)if(!(n&&o==="expose")){const l=po[o]||s&&s[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const po={data:wn,props:Sn,emits:Sn,methods:_t,computed:_t,beforeCreate:oe,created:oe,beforeMount:oe,mounted:oe,beforeUpdate:oe,updated:oe,beforeDestroy:oe,beforeUnmount:oe,destroyed:oe,unmounted:oe,activated:oe,deactivated:oe,errorCaptured:oe,serverPrefetch:oe,components:_t,directives:_t,watch:mo,provide:wn,inject:go};function wn(e,t){return t?e?function(){return ne(I(e)?e.call(this,this):e,I(t)?t.call(this,this):t)}:t:e}function go(e,t){return _t(Ds(e),Ds(t))}function Ds(e){if(R(e)){const t={};for(let s=0;s<e.length;s++)t[e[s]]=e[s];return t}return e}function oe(e,t){return e?[...new Set([].concat(e,t))]:t}function _t(e,t){return e?ne(Object.create(null),e,t):t}function Sn(e,t){return e?R(e)&&R(t)?[...new Set([...e,...t])]:ne(Object.create(null),xn(e),xn(t??{})):t}function mo(e,t){if(!e)return t;if(!t)return e;const s=ne(Object.create(null),e);for(const n in t)s[n]=oe(e[n],t[n]);return s}function Pr(){return{app:null,config:{isNativeTag:qn,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let bo=0;function yo(e,t){return function(n,r=null){I(n)||(n=ne({},n)),r!=null&&!H(r)&&(r=null);const i=Pr(),o=new WeakSet,l=[];let c=!1;const d=i.app={_uid:bo++,_component:n,_props:r,_container:null,_context:i,_instance:null,version:Xo,get config(){return i.config},set config(u){},use(u,...h){return o.has(u)||(u&&I(u.install)?(o.add(u),u.install(d,...h)):I(u)&&(o.add(u),u(d,...h))),d},mixin(u){return i.mixins.includes(u)||i.mixins.push(u),d},component(u,h){return h?(i.components[u]=h,d):i.components[u]},directive(u,h){return h?(i.directives[u]=h,d):i.directives[u]},mount(u,h,m){if(!c){const T=d._ceVNode||me(n,r);return T.appContext=i,m===!0?m="svg":m===!1&&(m=void 0),e(T,u,m),c=!0,d._container=u,u.__vue_app__=d,hs(T.component)}},onUnmount(u){l.push(u)},unmount(){c&&(Fe(l,d._instance,16),e(null,d._container),delete d._container.__vue_app__)},provide(u,h){return i.provides[u]=h,d},runWithContext(u){const h=ft;ft=d;try{return u()}finally{ft=h}}};return d}}let ft=null;const _o=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${_e(t)}Modifiers`]||e[`${st(t)}Modifiers`];function xo(e,t,...s){if(e.isUnmounted)return;const n=e.vnode.props||k;let r=s;const i=t.startsWith("update:"),o=i&&_o(n,t.slice(7));o&&(o.trim&&(r=s.map(u=>Z(u)?u.trim():u)),o.number&&(r=s.map(Us)));let l,c=n[l=ms(t)]||n[l=ms(_e(t))];!c&&i&&(c=n[l=ms(st(t))]),c&&Fe(c,e,6,r);const d=n[l+"Once"];if(d){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Fe(d,e,6,r)}}const vo=new WeakMap;function $r(e,t,s=!1){const n=s?vo:t.emitsCache,r=n.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!I(e)){const c=d=>{const u=$r(d,t,!0);u&&(l=!0,ne(o,u))};!s&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(H(e)&&n.set(e,null),null):(R(i)?i.forEach(c=>o[c]=null):ne(o,i),H(e)&&n.set(e,o),o)}function us(e,t){return!e||!ns(t)?!1:(t=t.slice(2).replace(/Once$/,""),N(e,t[0].toLowerCase()+t.slice(1))||N(e,st(t))||N(e,t))}function Tn(e){const{type:t,vnode:s,proxy:n,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:d,renderCache:u,props:h,data:m,setupState:T,ctx:$,inheritAttrs:E}=e,q=Qt(e);let G,F;try{if(s.shapeFlag&4){const M=r||n,Q=M;G=$e(d.call(Q,M,u,h,T,m,$)),F=l}else{const M=t;G=$e(M.length>1?M(h,{attrs:l,slots:o,emit:c}):M(h,null)),F=t.props?l:wo(l)}}catch(M){At.length=0,cs(M,e,1),G=me(Ge)}let W=G;if(F&&E!==!1){const M=Object.keys(F),{shapeFlag:Q}=W;M.length&&Q&7&&(i&&M.some(rs)&&(F=So(F,i)),W=at(W,F,!1,!0))}return s.dirs&&(W=at(W,null,!1,!0),W.dirs=W.dirs?W.dirs.concat(s.dirs):s.dirs),s.transition&&en(W,s.transition),G=W,Qt(q),G}const wo=e=>{let t;for(const s in e)(s==="class"||s==="style"||ns(s))&&((t||(t={}))[s]=e[s]);return t},So=(e,t)=>{const s={};for(const n in e)(!rs(n)||!(n.slice(9)in t))&&(s[n]=e[n]);return s};function To(e,t,s){const{props:n,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,d=i.emitsOptions;if(t.dirs||t.transition)return!0;if(s&&c>=0){if(c&1024)return!0;if(c&16)return n?Cn(n,o,d):!!o;if(c&8){const u=t.dynamicProps;for(let h=0;h<u.length;h++){const m=u[h];if(Mr(o,n,m)&&!us(d,m))return!0}}}else return(r||l)&&(!l||!l.$stable)?!0:n===o?!1:n?o?Cn(n,o,d):!0:!!o;return!1}function Cn(e,t,s){const n=Object.keys(t);if(n.length!==Object.keys(e).length)return!0;for(let r=0;r<n.length;r++){const i=n[r];if(Mr(t,e,i)&&!us(s,i))return!0}return!1}function Mr(e,t,s){const n=e[s],r=t[s];return s==="style"&&H(n)&&H(r)?!Bs(n,r):n!==r}function Co({vnode:e,parent:t,suspense:s},n){for(;t;){const r=t.subTree;if(r.suspense&&r.suspense.activeBranch===e&&(r.suspense.vnode.el=r.el=n,e=r),r===e)(e=t.vnode).el=n,t=t.parent;else break}s&&s.activeBranch===e&&(s.vnode.el=n)}const Rr={},Ir=()=>Object.create(Rr),Fr=e=>Object.getPrototypeOf(e)===Rr;function Eo(e,t,s,n=!1){const r={},i=Ir();e.propsDefaults=Object.create(null),Dr(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);s?e.props=n?r:$i(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function Ao(e,t,s,n){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=j(r),[c]=e.propsOptions;let d=!1;if((n||o>0)&&!(o&16)){if(o&8){const u=e.vnode.dynamicProps;for(let h=0;h<u.length;h++){let m=u[h];if(us(e.emitsOptions,m))continue;const T=t[m];if(c)if(N(i,m))T!==i[m]&&(i[m]=T,d=!0);else{const $=_e(m);r[$]=js(c,l,$,T,e,!1)}else T!==i[m]&&(i[m]=T,d=!0)}}}else{Dr(e,t,r,i)&&(d=!0);let u;for(const h in l)(!t||!N(t,h)&&((u=st(h))===h||!N(t,u)))&&(c?s&&(s[h]!==void 0||s[u]!==void 0)&&(r[h]=js(c,l,h,void 0,e,!0)):delete r[h]);if(i!==l)for(const h in i)(!t||!N(t,h))&&(delete i[h],d=!0)}d&&He(e.attrs,"set","")}function Dr(e,t,s,n){const[r,i]=e.propsOptions;let o=!1,l;if(t)for(let c in t){if(xt(c))continue;const d=t[c];let u;r&&N(r,u=_e(c))?!i||!i.includes(u)?s[u]=d:(l||(l={}))[u]=d:us(e.emitsOptions,c)||(!(c in n)||d!==n[c])&&(n[c]=d,o=!0)}if(i){const c=j(s),d=l||k;for(let u=0;u<i.length;u++){const h=i[u];s[h]=js(r,c,h,d[h],e,!N(d,h))}}return o}function js(e,t,s,n,r,i){const o=e[s];if(o!=null){const l=N(o,"default");if(l&&n===void 0){const c=o.default;if(o.type!==Function&&!o.skipFactory&&I(c)){const{propsDefaults:d}=r;if(s in d)n=d[s];else{const u=jt(r);n=d[s]=c.call(null,t),u()}}else n=c;r.ce&&r.ce._setProp(s,n)}o[0]&&(i&&!l?n=!1:o[1]&&(n===""||n===st(s))&&(n=!0))}return n}const Oo=new WeakMap;function jr(e,t,s=!1){const n=s?Oo:t.propsCache,r=n.get(e);if(r)return r;const i=e.props,o={},l=[];let c=!1;if(!I(e)){const u=h=>{c=!0;const[m,T]=jr(h,t,!0);ne(o,m),T&&l.push(...T)};!s&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!i&&!c)return H(e)&&n.set(e,ot),ot;if(R(i))for(let u=0;u<i.length;u++){const h=_e(i[u]);En(h)&&(o[h]=k)}else if(i)for(const u in i){const h=_e(u);if(En(h)){const m=i[u],T=o[h]=R(m)||I(m)?{type:m}:ne({},m),$=T.type;let E=!1,q=!0;if(R($))for(let G=0;G<$.length;++G){const F=$[G],W=I(F)&&F.name;if(W==="Boolean"){E=!0;break}else W==="String"&&(q=!1)}else E=I($)&&$.name==="Boolean";T[0]=E,T[1]=q,(E||N(T,"default"))&&l.push(h)}}const d=[o,l];return H(e)&&n.set(e,d),d}function En(e){return e[0]!=="$"&&!xt(e)}const nn=e=>e==="_"||e==="_ctx"||e==="$stable",rn=e=>R(e)?e.map($e):[$e(e)],Po=(e,t,s)=>{if(t._n)return t;const n=Bi((...r)=>rn(t(...r)),s);return n._c=!1,n},Nr=(e,t,s)=>{const n=e._ctx;for(const r in e){if(nn(r))continue;const i=e[r];if(I(i))t[r]=Po(r,i,n);else if(i!=null){const o=rn(i);t[r]=()=>o}}},Hr=(e,t)=>{const s=rn(t);e.slots.default=()=>s},Lr=(e,t,s)=>{for(const n in t)(s||!nn(n))&&(e[n]=t[n])},$o=(e,t,s)=>{const n=e.slots=Ir();if(e.vnode.shapeFlag&32){const r=t._;r?(Lr(n,t,s),s&&Zn(n,"_",r,!0)):Nr(t,n)}else t&&Hr(e,t)},Mo=(e,t,s)=>{const{vnode:n,slots:r}=e;let i=!0,o=k;if(n.shapeFlag&32){const l=t._;l?s&&l===1?i=!1:Lr(r,t,s):(i=!t.$stable,Nr(t,r)),o=t}else t&&(Hr(e,t),o={default:1});if(i)for(const l in r)!nn(l)&&o[l]==null&&delete r[l]},fe=jo;function Ro(e){return Io(e)}function Io(e,t){const s=os();s.__VUE__=!0;const{insert:n,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:d,setElementText:u,parentNode:h,nextSibling:m,setScopeId:T=Re,insertStaticContent:$}=e,E=(f,a,p,_=null,g=null,b=null,S=void 0,w=null,v=!!a.dynamicChildren)=>{if(f===a)return;f&&!yt(f,a)&&(_=Vt(f),Se(f,g,b,!0),f=null),a.patchFlag===-2&&(v=!1,a.dynamicChildren=null);const{type:y,ref:O,shapeFlag:C}=a;switch(y){case as:q(f,a,p,_);break;case Ge:G(f,a,p,_);break;case Ts:f==null&&F(a,p,_,S);break;case ae:Ht(f,a,p,_,g,b,S,w,v);break;default:C&1?Q(f,a,p,_,g,b,S,w,v):C&6?Lt(f,a,p,_,g,b,S,w,v):(C&64||C&128)&&y.process(f,a,p,_,g,b,S,w,v,gt)}O!=null&&g?St(O,f&&f.ref,b,a||f,!a):O==null&&f&&f.ref!=null&&St(f.ref,null,b,f,!0)},q=(f,a,p,_)=>{if(f==null)n(a.el=l(a.children),p,_);else{const g=a.el=f.el;a.children!==f.children&&d(g,a.children)}},G=(f,a,p,_)=>{f==null?n(a.el=c(a.children||""),p,_):a.el=f.el},F=(f,a,p,_)=>{[f.el,f.anchor]=$(f.children,a,p,_,f.el,f.anchor)},W=({el:f,anchor:a},p,_)=>{let g;for(;f&&f!==a;)g=m(f),n(f,p,_),f=g;n(a,p,_)},M=({el:f,anchor:a})=>{let p;for(;f&&f!==a;)p=m(f),r(f),f=p;r(a)},Q=(f,a,p,_,g,b,S,w,v)=>{if(a.type==="svg"?S="svg":a.type==="math"&&(S="mathml"),f==null)Be(a,p,_,g,b,S,w,v);else{const y=f.el&&f.el._isVueCE?f.el:null;try{y&&y._beginPatch(),Nt(f,a,g,b,S,w,v)}finally{y&&y._endPatch()}}},Be=(f,a,p,_,g,b,S,w)=>{let v,y;const{props:O,shapeFlag:C,transition:A,dirs:P}=f;if(v=f.el=o(f.type,b,O&&O.is,O),C&8?u(v,f.children):C&16&&We(f.children,v,null,_,g,Ss(f,b),S,w),P&&Xe(f,null,_,"created"),we(v,f,f.scopeId,S,_),O){for(const L in O)L!=="value"&&!xt(L)&&i(v,L,null,O[L],b,_);"value"in O&&i(v,"value",null,O.value,b),(y=O.onVnodeBeforeMount)&&Ae(y,_,f)}P&&Xe(f,null,_,"beforeMount");const D=Fo(g,A);D&&A.beforeEnter(v),n(v,a,p),((y=O&&O.onVnodeMounted)||D||P)&&fe(()=>{try{y&&Ae(y,_,f),D&&A.enter(v),P&&Xe(f,null,_,"mounted")}finally{}},g)},we=(f,a,p,_,g)=>{if(p&&T(f,p),_)for(let b=0;b<_.length;b++)T(f,_[b]);if(g){let b=g.subTree;if(a===b||Kr(b.type)&&(b.ssContent===a||b.ssFallback===a)){const S=g.vnode;we(f,S,S.scopeId,S.slotScopeIds,g.parent)}}},We=(f,a,p,_,g,b,S,w,v=0)=>{for(let y=v;y<f.length;y++){const O=f[y]=w?Ne(f[y]):$e(f[y]);E(null,O,a,p,_,g,b,S,w)}},Nt=(f,a,p,_,g,b,S)=>{const w=a.el=f.el;let{patchFlag:v,dynamicChildren:y,dirs:O}=a;v|=f.patchFlag&16;const C=f.props||k,A=a.props||k;let P;if(p&&Ze(p,!1),(P=A.onVnodeBeforeUpdate)&&Ae(P,p,a,f),O&&Xe(a,f,p,"beforeUpdate"),p&&Ze(p,!0),(C.innerHTML&&A.innerHTML==null||C.textContent&&A.textContent==null)&&u(w,""),y?Je(f.dynamicChildren,y,w,p,_,Ss(a,g),b):S||V(f,a,w,null,p,_,Ss(a,g),b,!1),v>0){if(v&16)ht(w,C,A,p,g);else if(v&2&&C.class!==A.class&&i(w,"class",null,A.class,g),v&4&&i(w,"style",C.style,A.style,g),v&8){const D=a.dynamicProps;for(let L=0;L<D.length;L++){const U=D[L],X=C[U],ee=A[U];(ee!==X||U==="value")&&i(w,U,X,ee,g,p)}}v&1&&f.children!==a.children&&u(w,a.children)}else!S&&y==null&&ht(w,C,A,p,g);((P=A.onVnodeUpdated)||O)&&fe(()=>{P&&Ae(P,p,a,f),O&&Xe(a,f,p,"updated")},_)},Je=(f,a,p,_,g,b,S)=>{for(let w=0;w<a.length;w++){const v=f[w],y=a[w],O=v.el&&(v.type===ae||!yt(v,y)||v.shapeFlag&198)?h(v.el):p;E(v,y,O,null,_,g,b,S,!0)}},ht=(f,a,p,_,g)=>{if(a!==p){if(a!==k)for(const b in a)!xt(b)&&!(b in p)&&i(f,b,a[b],null,g,_);for(const b in p){if(xt(b))continue;const S=p[b],w=a[b];S!==w&&b!=="value"&&i(f,b,w,S,g,_)}"value"in p&&i(f,"value",a.value,p.value,g)}},Ht=(f,a,p,_,g,b,S,w,v)=>{const y=a.el=f?f.el:l(""),O=a.anchor=f?f.anchor:l("");let{patchFlag:C,dynamicChildren:A,slotScopeIds:P}=a;P&&(w=w?w.concat(P):P),f==null?(n(y,p,_),n(O,p,_),We(a.children||[],p,O,g,b,S,w,v)):C>0&&C&64&&A&&f.dynamicChildren&&f.dynamicChildren.length===A.length?(Je(f.dynamicChildren,A,p,g,b,S,w),(a.key!=null||g&&a===g.subTree)&&kr(f,a,!0)):V(f,a,p,O,g,b,S,w,v)},Lt=(f,a,p,_,g,b,S,w,v)=>{a.slotScopeIds=w,f==null?a.shapeFlag&512?g.ctx.activate(a,p,_,S,v):ps(a,p,_,g,b,S,v):ln(f,a,v)},ps=(f,a,p,_,g,b,S)=>{const w=f.component=Bo(f,_,g);if(Cr(f)&&(w.ctx.renderer=gt),qo(w,!1,S),w.asyncDep){if(g&&g.registerDep(w,re,S),!f.el){const v=w.subTree=me(Ge);G(null,v,a,p),f.placeholder=v.el}}else re(w,f,a,p,g,b,S)},ln=(f,a,p)=>{const _=a.component=f.component;if(To(f,a,p))if(_.asyncDep&&!_.asyncResolved){J(_,a,p);return}else _.next=a,_.update();else a.el=f.el,_.vnode=a},re=(f,a,p,_,g,b,S)=>{const w=()=>{if(f.isMounted){let{next:C,bu:A,u:P,parent:D,vnode:L}=f;{const Ce=Vr(f);if(Ce){C&&(C.el=L.el,J(f,C,S)),Ce.asyncDep.then(()=>{fe(()=>{f.isUnmounted||y()},g)});return}}let U=C,X;Ze(f,!1),C?(C.el=L.el,J(f,C,S)):C=L,A&&Wt(A),(X=C.props&&C.props.onVnodeBeforeUpdate)&&Ae(X,D,C,L),Ze(f,!0);const ee=Tn(f),Te=f.subTree;f.subTree=ee,E(Te,ee,h(Te.el),Vt(Te),f,g,b),C.el=ee.el,U===null&&Co(f,ee.el),P&&fe(P,g),(X=C.props&&C.props.onVnodeUpdated)&&fe(()=>Ae(X,D,C,L),g)}else{let C;const{el:A,props:P}=a,{bm:D,m:L,parent:U,root:X,type:ee}=f,Te=Tt(a);Ze(f,!1),D&&Wt(D),!Te&&(C=P&&P.onVnodeBeforeMount)&&Ae(C,U,a),Ze(f,!0);{X.ce&&X.ce._hasShadowRoot()&&X.ce._injectChildStyle(ee,f.parent?f.parent.type:void 0);const Ce=f.subTree=Tn(f);E(null,Ce,p,_,f,g,b),a.el=Ce.el}if(L&&fe(L,g),!Te&&(C=P&&P.onVnodeMounted)){const Ce=a;fe(()=>Ae(C,U,Ce),g)}(a.shapeFlag&256||U&&Tt(U.vnode)&&U.vnode.shapeFlag&256)&&f.a&&fe(f.a,g),f.isMounted=!0,a=p=_=null}};f.scope.on();const v=f.effect=new sr(w);f.scope.off();const y=f.update=v.run.bind(v),O=f.job=v.runIfDirty.bind(v);O.i=f,O.id=f.uid,v.scheduler=()=>Qs(O),Ze(f,!0),y()},J=(f,a,p)=>{a.component=f;const _=f.vnode.props;f.vnode=a,f.next=null,Ao(f,a.props,_,p),Mo(f,a.children,p),ke(),bn(f),Ve()},V=(f,a,p,_,g,b,S,w,v=!1)=>{const y=f&&f.children,O=f?f.shapeFlag:0,C=a.children,{patchFlag:A,shapeFlag:P}=a;if(A>0){if(A&128){kt(y,C,p,_,g,b,S,w,v);return}else if(A&256){Ye(y,C,p,_,g,b,S,w,v);return}}P&8?(O&16&&pt(y,g,b),C!==y&&u(p,C)):O&16?P&16?kt(y,C,p,_,g,b,S,w,v):pt(y,g,b,!0):(O&8&&u(p,""),P&16&&We(C,p,_,g,b,S,w,v))},Ye=(f,a,p,_,g,b,S,w,v)=>{f=f||ot,a=a||ot;const y=f.length,O=a.length,C=Math.min(y,O);let A;for(A=0;A<C;A++){const P=a[A]=v?Ne(a[A]):$e(a[A]);E(f[A],P,p,null,g,b,S,w,v)}y>O?pt(f,g,b,!0,!1,C):We(a,p,_,g,b,S,w,v,C)},kt=(f,a,p,_,g,b,S,w,v)=>{let y=0;const O=a.length;let C=f.length-1,A=O-1;for(;y<=C&&y<=A;){const P=f[y],D=a[y]=v?Ne(a[y]):$e(a[y]);if(yt(P,D))E(P,D,p,null,g,b,S,w,v);else break;y++}for(;y<=C&&y<=A;){const P=f[C],D=a[A]=v?Ne(a[A]):$e(a[A]);if(yt(P,D))E(P,D,p,null,g,b,S,w,v);else break;C--,A--}if(y>C){if(y<=A){const P=A+1,D=P<O?a[P].el:_;for(;y<=A;)E(null,a[y]=v?Ne(a[y]):$e(a[y]),p,D,g,b,S,w,v),y++}}else if(y>A)for(;y<=C;)Se(f[y],g,b,!0),y++;else{const P=y,D=y,L=new Map;for(y=D;y<=A;y++){const de=a[y]=v?Ne(a[y]):$e(a[y]);de.key!=null&&L.set(de.key,y)}let U,X=0;const ee=A-D+1;let Te=!1,Ce=0;const mt=new Array(ee);for(y=0;y<ee;y++)mt[y]=0;for(y=P;y<=C;y++){const de=f[y];if(X>=ee){Se(de,g,b,!0);continue}let Ee;if(de.key!=null)Ee=L.get(de.key);else for(U=D;U<=A;U++)if(mt[U-D]===0&&yt(de,a[U])){Ee=U;break}Ee===void 0?Se(de,g,b,!0):(mt[Ee-D]=y+1,Ee>=Ce?Ce=Ee:Te=!0,E(de,a[Ee],p,null,g,b,S,w,v),X++)}const un=Te?Do(mt):ot;for(U=un.length-1,y=ee-1;y>=0;y--){const de=D+y,Ee=a[de],an=a[de+1],dn=de+1<O?an.el||Ur(an):_;mt[y]===0?E(null,Ee,p,dn,g,b,S,w,v):Te&&(U<0||y!==un[U]?ze(Ee,p,dn,2):U--)}}},ze=(f,a,p,_,g=null)=>{const{el:b,type:S,transition:w,children:v,shapeFlag:y}=f;if(y&6){ze(f.component.subTree,a,p,_);return}if(y&128){f.suspense.move(a,p,_);return}if(y&64){S.move(f,a,p,gt);return}if(S===ae){n(b,a,p);for(let C=0;C<v.length;C++)ze(v[C],a,p,_);n(f.anchor,a,p);return}if(S===Ts){W(f,a,p);return}if(_!==2&&y&1&&w)if(_===0)w.beforeEnter(b),n(b,a,p),fe(()=>w.enter(b),g);else{const{leave:C,delayLeave:A,afterLeave:P}=w,D=()=>{f.ctx.isUnmounted?r(b):n(b,a,p)},L=()=>{b._isLeaving&&b[Xi](!0),C(b,()=>{D(),P&&P()})};A?A(b,D,L):L()}else n(b,a,p)},Se=(f,a,p,_=!1,g=!1)=>{const{type:b,props:S,ref:w,children:v,dynamicChildren:y,shapeFlag:O,patchFlag:C,dirs:A,cacheIndex:P,memo:D}=f;if(C===-2&&(g=!1),w!=null&&(ke(),St(w,null,p,f,!0),Ve()),P!=null&&(a.renderCache[P]=void 0),O&256){a.ctx.deactivate(f);return}const L=O&1&&A,U=!Tt(f);let X;if(U&&(X=S&&S.onVnodeBeforeUnmount)&&Ae(X,a,f),O&6)Zr(f.component,p,_);else{if(O&128){f.suspense.unmount(p,_);return}L&&Xe(f,null,a,"beforeUnmount"),O&64?f.type.remove(f,a,p,gt,_):y&&!y.hasOnce&&(b!==ae||C>0&&C&64)?pt(y,a,p,!1,!0):(b===ae&&C&384||!g&&O&16)&&pt(v,a,p),_&&cn(f)}const ee=D!=null&&P==null;(U&&(X=S&&S.onVnodeUnmounted)||L||ee)&&fe(()=>{X&&Ae(X,a,f),L&&Xe(f,null,a,"unmounted"),ee&&(f.el=null)},p)},cn=f=>{const{type:a,el:p,anchor:_,transition:g}=f;if(a===ae){Xr(p,_);return}if(a===Ts){M(f);return}const b=()=>{r(p),g&&!g.persisted&&g.afterLeave&&g.afterLeave()};if(f.shapeFlag&1&&g&&!g.persisted){const{leave:S,delayLeave:w}=g,v=()=>S(p,b);w?w(f.el,b,v):v()}else b()},Xr=(f,a)=>{let p;for(;f!==a;)p=m(f),r(f),f=p;r(a)},Zr=(f,a,p)=>{const{bum:_,scope:g,job:b,subTree:S,um:w,m:v,a:y}=f;An(v),An(y),_&&Wt(_),g.stop(),b&&(b.flags|=8,Se(S,f,a,p)),w&&fe(w,a),fe(()=>{f.isUnmounted=!0},a)},pt=(f,a,p,_=!1,g=!1,b=0)=>{for(let S=b;S<f.length;S++)Se(f[S],a,p,_,g)},Vt=f=>{if(f.shapeFlag&6)return Vt(f.component.subTree);if(f.shapeFlag&128)return f.suspense.next();const a=m(f.anchor||f.el),p=a&&a[Yi];return p?m(p):a};let gs=!1;const fn=(f,a,p)=>{let _;f==null?a._vnode&&(Se(a._vnode,null,null,!0),_=a._vnode.component):E(a._vnode||null,f,a,null,null,null,p),a._vnode=f,gs||(gs=!0,bn(_),_r(),gs=!1)},gt={p:E,um:Se,m:ze,r:cn,mt:ps,mc:We,pc:V,pbc:Je,n:Vt,o:e};return{render:fn,hydrate:void 0,createApp:yo(fn)}}function Ss({type:e,props:t},s){return s==="svg"&&e==="foreignObject"||s==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:s}function Ze({effect:e,job:t},s){s?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Fo(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function kr(e,t,s=!1){const n=e.children,r=t.children;if(R(n)&&R(r))for(let i=0;i<n.length;i++){const o=n[i];let l=r[i];l.shapeFlag&1&&!l.dynamicChildren&&((l.patchFlag<=0||l.patchFlag===32)&&(l=r[i]=Ne(r[i]),l.el=o.el),!s&&l.patchFlag!==-2&&kr(o,l)),l.type===as&&(l.patchFlag===-1&&(l=r[i]=Ne(l)),l.el=o.el),l.type===Ge&&!l.el&&(l.el=o.el)}}function Do(e){const t=e.slice(),s=[0];let n,r,i,o,l;const c=e.length;for(n=0;n<c;n++){const d=e[n];if(d!==0){if(r=s[s.length-1],e[r]<d){t[n]=r,s.push(n);continue}for(i=0,o=s.length-1;i<o;)l=i+o>>1,e[s[l]]<d?i=l+1:o=l;d<e[s[i]]&&(i>0&&(t[n]=s[i-1]),s[i]=n)}}for(i=s.length,o=s[i-1];i-- >0;)s[i]=o,o=t[o];return s}function Vr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Vr(t)}function An(e){if(e)for(let t=0;t<e.length;t++)e[t].flags|=8}function Ur(e){if(e.placeholder)return e.placeholder;const t=e.component;return t?Ur(t.subTree):null}const Kr=e=>e.__isSuspense;function jo(e,t){t&&t.pendingBranch?R(e)?t.effects.push(...e):t.effects.push(e):Ki(e)}const ae=Symbol.for("v-fgt"),as=Symbol.for("v-txt"),Ge=Symbol.for("v-cmt"),Ts=Symbol.for("v-stc"),At=[];let ge=null;function Y(e=!1){At.push(ge=e?null:[])}function No(){At.pop(),ge=At[At.length-1]||null}let Mt=1;function On(e,t=!1){Mt+=e,e<0&&ge&&t&&(ge.hasOnce=!0)}function Br(e){return e.dynamicChildren=Mt>0?ge||ot:null,No(),Mt>0&&ge&&ge.push(e),e}function z(e,t,s,n,r,i){return Br(x(e,t,s,n,r,i,!0))}function Ho(e,t,s,n,r){return Br(me(e,t,s,n,r,!0))}function Wr(e){return e?e.__v_isVNode===!0:!1}function yt(e,t){return e.type===t.type&&e.key===t.key}const qr=({key:e})=>e??null,Jt=({ref:e,ref_key:t,ref_for:s})=>(typeof e=="number"&&(e=""+e),e!=null?Z(e)||se(e)||I(e)?{i:be,r:e,k:t,f:!!s}:e:null);function x(e,t=null,s=null,n=0,r=null,i=e===ae?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&qr(t),ref:t&&Jt(t),scopeId:vr,slotScopeIds:null,children:s,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:n,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:be};return l?(on(c,s),i&128&&e.normalize(c)):s&&(c.shapeFlag|=Z(s)?8:16),Mt>0&&!o&&ge&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&ge.push(c),c}const me=Lo;function Lo(e,t=null,s=null,n=0,r=null,i=!1){if((!e||e===fo)&&(e=Ge),Wr(e)){const l=at(e,t,!0);return s&&on(l,s),Mt>0&&!i&&ge&&(l.shapeFlag&6?ge[ge.indexOf(e)]=l:ge.push(l)),l.patchFlag=-2,l}if(zo(e)&&(e=e.__vccOpts),t){t=ko(t);let{class:l,style:c}=t;l&&!Z(l)&&(t.class=pe(l)),H(c)&&(Zs(c)&&!R(c)&&(c=ne({},c)),t.style=Ks(c))}const o=Z(e)?1:Kr(e)?128:zi(e)?64:H(e)?4:I(e)?2:0;return x(e,t,s,n,r,o,i,!0)}function ko(e){return e?Zs(e)||Fr(e)?ne({},e):e:null}function at(e,t,s=!1,n=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,d=t?Vo(r||{},t):r,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:d,key:d&&qr(d),ref:t&&t.ref?s&&i?R(i)?i.concat(Jt(t)):[i,Jt(t)]:Jt(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ae?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&at(e.ssContent),ssFallback:e.ssFallback&&at(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&n&&en(u,c.clone(u)),u}function ds(e=" ",t=0){return me(as,null,e,t)}function Rt(e="",t=!1){return t?(Y(),Ho(Ge,null,e)):me(Ge,null,e)}function $e(e){return e==null||typeof e=="boolean"?me(Ge):R(e)?me(ae,null,e.slice()):Wr(e)?Ne(e):me(as,null,String(e))}function Ne(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:at(e)}function on(e,t){let s=0;const{shapeFlag:n}=e;if(t==null)t=null;else if(R(t))s=16;else if(typeof t=="object")if(n&65){const r=t.default;r&&(r._c&&(r._d=!1),on(e,r()),r._c&&(r._d=!0));return}else{s=32;const r=t._;!r&&!Fr(t)?t._ctx=be:r===3&&be&&(be.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else I(t)?(t={default:t,_ctx:be},s=32):(t=String(t),n&64?(s=16,t=[ds(t)]):s=8);e.children=t,e.shapeFlag|=s}function Vo(...e){const t={};for(let s=0;s<e.length;s++){const n=e[s];for(const r in n)if(r==="class")t.class!==n.class&&(t.class=pe([t.class,n.class]));else if(r==="style")t.style=Ks([t.style,n.style]);else if(ns(r)){const i=t[r],o=n[r];o&&i!==o&&!(R(i)&&i.includes(o))?t[r]=i?[].concat(i,o):o:o==null&&i==null&&!rs(r)&&(t[r]=o)}else r!==""&&(t[r]=n[r])}return t}function Ae(e,t,s,n=null){Fe(e,t,7,[s,n])}const Uo=Pr();let Ko=0;function Bo(e,t,s){const n=e.type,r=(t?t.appContext:e.appContext)||Uo,i={uid:Ko++,vnode:e,type:n,parent:t,appContext:r,root:null,next:null,subTree:null,effect:null,update:null,job:null,scope:new ui(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(r.provides),ids:t?t.ids:["",0,0],accessCache:null,renderCache:[],components:null,directives:null,propsOptions:jr(n,r),emitsOptions:$r(n,r),emit:null,emitted:null,propsDefaults:k,inheritAttrs:n.inheritAttrs,ctx:k,data:k,props:k,attrs:k,slots:k,refs:k,setupState:k,setupContext:null,suspense:s,suspenseId:s?s.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return i.ctx={_:i},i.root=t?t.root:i,i.emit=xo.bind(null,i),e.ce&&e.ce(i),i}let ce=null;const Wo=()=>ce||be;let ss,Ns;{const e=os(),t=(s,n)=>{let r;return(r=e[s])||(r=e[s]=[]),r.push(n),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};ss=t("__VUE_INSTANCE_SETTERS__",s=>ce=s),Ns=t("__VUE_SSR_SETTERS__",s=>It=s)}const jt=e=>{const t=ce;return ss(e),e.scope.on(),()=>{e.scope.off(),ss(t)}},Pn=()=>{ce&&ce.scope.off(),ss(null)};function Gr(e){return e.vnode.shapeFlag&4}let It=!1;function qo(e,t=!1,s=!1){t&&Ns(t);const{props:n,children:r}=e.vnode,i=Gr(e);Eo(e,n,i,t),$o(e,r,s||t);const o=i?Go(e,t):void 0;return t&&Ns(!1),o}function Go(e,t){const s=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,uo);const{setup:n}=s;if(n){ke();const r=e.setupContext=n.length>1?Yo(e):null,i=jt(e),o=Dt(n,e,0,[e.props,r]),l=Jn(o);if(Ve(),i(),(l||e.sp)&&!Tt(e)&&Tr(e),l){if(o.then(Pn,Pn),t)return o.then(c=>{$n(e,c)}).catch(c=>{cs(c,e,0)});e.asyncDep=o}else $n(e,o)}else Jr(e)}function $n(e,t,s){I(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:H(t)&&(e.setupState=mr(t)),Jr(e)}function Jr(e,t,s){const n=e.type;e.render||(e.render=n.render||Re);{const r=jt(e);ke();try{ao(e)}finally{Ve(),r()}}}const Jo={get(e,t){return te(e,"get",""),e[t]}};function Yo(e){const t=s=>{e.exposed=s||{}};return{attrs:new Proxy(e.attrs,Jo),slots:e.slots,emit:e.emit,expose:t}}function hs(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(mr(Mi(e.exposed)),{get(t,s){if(s in t)return t[s];if(s in Et)return Et[s](e)},has(t,s){return s in t||s in Et}})):e.proxy}function zo(e){return I(e)&&"__vccOpts"in e}const Yr=(e,t)=>Ni(e,t,It),Xo="3.5.32";/**
|
| 14 |
-
* @vue/runtime-dom v3.5.32
|
| 15 |
-
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 16 |
-
* @license MIT
|
| 17 |
-
**/let Hs;const Mn=typeof window<"u"&&window.trustedTypes;if(Mn)try{Hs=Mn.createPolicy("vue",{createHTML:e=>e})}catch{}const zr=Hs?e=>Hs.createHTML(e):e=>e,Zo="http://www.w3.org/2000/svg",Qo="http://www.w3.org/1998/Math/MathML",je=typeof document<"u"?document:null,Rn=je&&je.createElement("template"),el={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?je.createElementNS(Zo,e):t==="mathml"?je.createElementNS(Qo,e):s?je.createElement(e,{is:s}):je.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>je.createTextNode(e),createComment:e=>je.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>je.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,i){const o=s?s.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===i||!(r=r.nextSibling)););else{Rn.innerHTML=zr(n==="svg"?`<svg>${e}</svg>`:n==="mathml"?`<math>${e}</math>`:e);const l=Rn.content;if(n==="svg"||n==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,s)}return[o?o.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},tl=Symbol("_vtc");function sl(e,t,s){const n=e[tl];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const In=Symbol("_vod"),nl=Symbol("_vsh"),rl=Symbol(""),il=/(?:^|;)\s*display\s*:/;function ol(e,t,s){const n=e.style,r=Z(s);let i=!1;if(s&&!r){if(t)if(Z(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();s[l]==null&&Yt(n,l,"")}else for(const o in t)s[o]==null&&Yt(n,o,"");for(const o in s)o==="display"&&(i=!0),Yt(n,o,s[o])}else if(r){if(t!==s){const o=n[rl];o&&(s+=";"+o),n.cssText=s,i=il.test(s)}}else t&&e.removeAttribute("style");In in e&&(e[In]=i?n.display:"",e[nl]&&(n.display="none"))}const Fn=/\s*!important$/;function Yt(e,t,s){if(R(s))s.forEach(n=>Yt(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=ll(e,t);Fn.test(s)?e.setProperty(st(n),s.replace(Fn,""),"important"):e[n]=s}}const Dn=["Webkit","Moz","ms"],Cs={};function ll(e,t){const s=Cs[t];if(s)return s;let n=_e(t);if(n!=="filter"&&n in e)return Cs[t]=n;n=Xn(n);for(let r=0;r<Dn.length;r++){const i=Dn[r]+n;if(i in e)return Cs[t]=i}return t}const jn="http://www.w3.org/1999/xlink";function Nn(e,t,s,n,r,i=ci(t)){n&&t.startsWith("xlink:")?s==null?e.removeAttributeNS(jn,t.slice(6,t.length)):e.setAttributeNS(jn,t,s):s==null||i&&!Qn(s)?e.removeAttribute(t):e.setAttribute(t,i?"":Ie(s)?String(s):s)}function Hn(e,t,s,n,r){if(t==="innerHTML"||t==="textContent"){s!=null&&(e[t]=t==="innerHTML"?zr(s):s);return}const i=e.tagName;if(t==="value"&&i!=="PROGRESS"&&!i.includes("-")){const l=i==="OPTION"?e.getAttribute("value")||"":e.value,c=s==null?e.type==="checkbox"?"on":"":String(s);(l!==c||!("_value"in e))&&(e.value=c),s==null&&e.removeAttribute(t),e._value=s;return}let o=!1;if(s===""||s==null){const l=typeof e[t];l==="boolean"?s=Qn(s):s==null&&l==="string"?(s="",o=!0):l==="number"&&(s=0,o=!0)}try{e[t]=s}catch{}o&&e.removeAttribute(r||t)}function it(e,t,s,n){e.addEventListener(t,s,n)}function cl(e,t,s,n){e.removeEventListener(t,s,n)}const Ln=Symbol("_vei");function fl(e,t,s,n,r=null){const i=e[Ln]||(e[Ln]={}),o=i[t];if(n&&o)o.value=n;else{const[l,c]=ul(t);if(n){const d=i[t]=hl(n,r);it(e,l,d,c)}else o&&(cl(e,l,o,c),i[t]=void 0)}}const kn=/(?:Once|Passive|Capture)$/;function ul(e){let t;if(kn.test(e)){t={};let n;for(;n=e.match(kn);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[e[2]===":"?e.slice(3):st(e.slice(2)),t]}let Es=0;const al=Promise.resolve(),dl=()=>Es||(al.then(()=>Es=0),Es=Date.now());function hl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;Fe(pl(n,s.value),t,5,[n])};return s.value=e,s.attached=dl(),s}function pl(e,t){if(R(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const Vn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,gl=(e,t,s,n,r,i)=>{const o=r==="svg";t==="class"?sl(e,n,o):t==="style"?ol(e,s,n):ns(t)?rs(t)||fl(e,t,s,n,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ml(e,t,n,o))?(Hn(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Nn(e,t,n,o,i,t!=="value")):e._isVueCE&&(bl(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!Z(n)))?Hn(e,_e(t),n,i,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Nn(e,t,n,o))};function ml(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Vn(t)&&I(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Vn(t)&&Z(s)?!1:t in e}function bl(e,t){const s=e._def.props;if(!s)return!1;const n=_e(t);return Array.isArray(s)?s.some(r=>_e(r)===n):Object.keys(s).some(r=>_e(r)===n)}const Un=e=>{const t=e.props["onUpdate:modelValue"]||!1;return R(t)?s=>Wt(t,s):t};function yl(e){e.target.composing=!0}function Kn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const As=Symbol("_assign");function Bn(e,t,s){return t&&(e=e.trim()),s&&(e=Us(e)),e}const zt={created(e,{modifiers:{lazy:t,trim:s,number:n}},r){e[As]=Un(r);const i=n||r.props&&r.props.type==="number";it(e,t?"change":"input",o=>{o.target.composing||e[As](Bn(e.value,s,i))}),(s||i)&&it(e,"change",()=>{e.value=Bn(e.value,s,i)}),t||(it(e,"compositionstart",yl),it(e,"compositionend",Kn),it(e,"change",Kn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:r,number:i}},o){if(e[As]=Un(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?Us(e.value):e.value,c=t??"";if(l===c)return;const d=e.getRootNode();(d instanceof Document||d instanceof ShadowRoot)&&d.activeElement===e&&e.type!=="range"&&(n&&t===s||r&&e.value.trim()===c)||(e.value=c)}},_l=ne({patchProp:gl},el);let Wn;function xl(){return Wn||(Wn=Ro(_l))}const vl=((...e)=>{const t=xl().createApp(...e),{mount:s}=t;return t.mount=n=>{const r=Sl(n);if(!r)return;const i=t._component;!I(i)&&!i.render&&!i.template&&(i.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const o=s(r,!1,wl(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),o},t});function wl(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Sl(e){return Z(e)?document.querySelector(e):e}const Tl="/api";async function ie(e,t,s=null){var o;const n={method:e,headers:{"Content-Type":"application/json"}};s&&(n.body=JSON.stringify(s));const r=await fetch(`${Tl}${t}`,n),i=await r.json();if(!r.ok){const l=((o=i==null?void 0:i.detail)==null?void 0:o.message)||(i==null?void 0:i.detail)||`HTTP ${r.status}`;throw new Error(l)}return i}const dt={getStatus:()=>ie("GET","/status"),getAccounts:()=>ie("GET","/accounts"),getActiveAccounts:()=>ie("GET","/accounts/active"),getStandbyAccounts:()=>ie("GET","/accounts/standby"),getCpaFiles:()=>ie("GET","/cpa/files"),postSync:()=>ie("POST","/sync"),startRotate:(e=5)=>ie("POST","/tasks/rotate",{target:e}),startCheck:()=>ie("POST","/tasks/check"),startAdd:()=>ie("POST","/tasks/add"),startFill:(e=5)=>ie("POST","/tasks/fill",{target:e}),startCleanup:(e=null)=>ie("POST","/tasks/cleanup",{max_seats:e}),getTasks:()=>ie("GET","/tasks"),getTask:e=>ie("GET",`/tasks/${e}`),getAutoCheckConfig:()=>ie("GET","/config/auto-check"),setAutoCheckConfig:e=>ie("PUT","/config/auto-check",e)},Cl={key:0},El={class:"grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6"},Al={class:"text-sm text-gray-400"},Ol={class:"bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"},Pl={class:"overflow-x-auto"},$l={class:"w-full text-sm"},Ml={class:"px-4 py-3 text-gray-500"},Rl={class:"px-4 py-3 font-mono text-xs"},Il={class:"px-4 py-3"},Fl={class:"px-4 py-3 text-gray-400 text-xs"},Dl={class:"px-4 py-3 text-gray-400 text-xs"},jl={key:1,class:"space-y-4"},Nl={class:"grid grid-cols-2 sm:grid-cols-4 gap-4"},Hl={__name:"Dashboard",props:{status:Object,loading:Boolean},setup(e){const t=e,s=Yr(()=>{if(!t.status)return[];const u=t.status.summary;return[{label:"活跃",value:u.active,color:"text-green-400"},{label:"待命",value:u.standby,color:"text-yellow-400"},{label:"额度用完",value:u.exhausted,color:"text-red-400"},{label:"总计",value:u.total,color:"text-white"}]});function n(u){return{active:"bg-green-500/10 text-green-400",exhausted:"bg-red-500/10 text-red-400",standby:"bg-yellow-500/10 text-yellow-400",pending:"bg-gray-500/10 text-gray-400"}[u]||"bg-gray-500/10 text-gray-400"}function r(u){return{active:"bg-green-400",exhausted:"bg-red-400",standby:"bg-yellow-400",pending:"bg-gray-400"}[u]||"bg-gray-400"}function i(u){return{active:"Active",exhausted:"Used up",standby:"Standby",pending:"Pending"}[u]||u}function o(u,h){var $,E;const m=((E=($=t.status)==null?void 0:$.quota_cache)==null?void 0:E[u.email])||u.last_quota;return m?100-((h==="primary"?m.primary_pct:m.weekly_pct)||0):null}function l(u,h){const m=o(u,h);return m!==null?`${m}%`:"-"}function c(u,h){var E,q;const m=((q=(E=t.status)==null?void 0:E.quota_cache)==null?void 0:q[u.email])||u.last_quota;if(!m)return"-";const T=h==="primary"?m.primary_resets_at:m.weekly_resets_at;if(!T)return"-";const $=new Date(T*1e3);return`${String($.getMonth()+1).padStart(2,"0")}-${String($.getDate()).padStart(2,"0")} ${String($.getHours()).padStart(2,"0")}:${String($.getMinutes()).padStart(2,"0")}`}function d(u){return u===null?"text-gray-500":u>30?"text-green-400":u>0?"text-yellow-400":"text-red-400"}return(u,h)=>e.status?(Y(),z("div",Cl,[x("div",El,[(Y(!0),z(ae,null,Ct(s.value,m=>(Y(),z("div",{key:m.label,class:"bg-gray-900 border border-gray-800 rounded-xl p-4"},[x("div",Al,B(m.label),1),x("div",{class:pe(["text-3xl font-bold mt-1",m.color])},B(m.value),3)]))),128))]),x("div",Ol,[h[1]||(h[1]=x("div",{class:"px-4 py-3 border-b border-gray-800"},[x("h2",{class:"text-lg font-semibold text-white"},"���号列表")],-1)),x("div",Pl,[x("table",$l,[h[0]||(h[0]=x("thead",null,[x("tr",{class:"text-gray-400 text-left border-b border-gray-800"},[x("th",{class:"px-4 py-3 font-medium"},"#"),x("th",{class:"px-4 py-3 font-medium"},"邮箱"),x("th",{class:"px-4 py-3 font-medium"},"状态"),x("th",{class:"px-4 py-3 font-medium text-right"},"5h 剩余"),x("th",{class:"px-4 py-3 font-medium text-right"},"周 剩余"),x("th",{class:"px-4 py-3 font-medium"},"5h 重置"),x("th",{class:"px-4 py-3 font-medium"},"周 重置")])],-1)),x("tbody",null,[(Y(!0),z(ae,null,Ct(e.status.accounts,(m,T)=>(Y(),z("tr",{key:m.email,class:"border-b border-gray-800/50 hover:bg-gray-800/30 transition"},[x("td",Ml,B(T+1),1),x("td",Rl,B(m.email),1),x("td",Il,[x("span",{class:pe(["inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium",n(m.status)])},[x("span",{class:pe(["w-1.5 h-1.5 rounded-full",r(m.status)])},null,2),ds(" "+B(i(m.status)),1)],2)]),x("td",{class:pe(["px-4 py-3 text-right font-mono",d(o(m,"primary"))])},B(l(m,"primary")),3),x("td",{class:pe(["px-4 py-3 text-right font-mono",d(o(m,"weekly"))])},B(l(m,"weekly")),3),x("td",Fl,B(c(m,"primary")),1),x("td",Dl,B(c(m,"weekly")),1)]))),128))])])])])])):e.loading?(Y(),z("div",jl,[x("div",Nl,[(Y(),z(ae,null,Ct(4,m=>x("div",{key:m,class:"bg-gray-900 border border-gray-800 rounded-xl p-4 h-20 animate-pulse"})),64))]),h[2]||(h[2]=x("div",{class:"bg-gray-900 border border-gray-800 rounded-xl h-64 animate-pulse"},null,-1))])):Rt("",!0)}},Ll={class:"mt-6 bg-gray-900 border border-gray-800 rounded-xl p-4"},kl={class:"flex flex-wrap gap-3"},Vl=["onClick","disabled"],Ul={key:0,class:"mt-4 flex items-center gap-3"},Kl={class:"text-sm text-gray-400"},Bl={__name:"TaskPanel",props:{runningTask:Object},emits:["task-started","refresh"],setup(e,{emit:t}){const s=t,n=[{key:"rotate",label:"智能轮转",method:"startRotate",needParam:!0,paramName:"target",style:"bg-blue-600 text-white border-blue-500"},{key:"check",label:"检查额度",method:"startCheck",needParam:!1,style:"bg-emerald-600 text-white border-emerald-500"},{key:"fill",label:"补满成员",method:"startFill",needParam:!0,paramName:"target",style:"bg-violet-600 text-white border-violet-500"},{key:"add",label:"添加账号",method:"startAdd",needParam:!1,style:"bg-amber-600 text-white border-amber-500"},{key:"cleanup",label:"清理成员",method:"startCleanup",needParam:!1,style:"bg-rose-600 text-white border-rose-500"},{key:"sync",label:"同步 CPA",method:"postSync",needParam:!1,sync:!0,style:"bg-gray-700 text-white border-gray-600"}],r=he(!1),i=he(""),o=he(5),l=he(null),c=he(""),d=he("");async function u(T){if(c.value="",T.needParam){l.value=T,i.value=T.paramName==="target"?"目标成员数":"最大席位",o.value=5,r.value=!0;return}await m(T)}async function h(){r.value=!1,l.value&&(await m(l.value,o.value),l.value=null)}async function m(T,$){try{if(T.sync){const E=await dt[T.method]();c.value=E.message||"操作完成",d.value="bg-green-500/10 text-green-400 border border-green-500/20",s("refresh")}else{const E=await dt[T.method]($);c.value=`任务已提交: ${E.task_id}`,d.value="bg-blue-500/10 text-blue-400 border border-blue-500/20",s("task-started")}}catch(E){c.value=E.message,d.value="bg-red-500/10 text-red-400 border border-red-500/20"}setTimeout(()=>{c.value=""},8e3)}return(T,$)=>(Y(),z("div",Ll,[$[2]||($[2]=x("h2",{class:"text-lg font-semibold text-white mb-4"},"操作",-1)),x("div",kl,[(Y(),z(ae,null,Ct(n,E=>x("button",{key:E.key,onClick:q=>u(E),disabled:!!e.runningTask,class:pe(["px-4 py-2 rounded-lg text-sm font-medium transition border",e.runningTask?"bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed":`${E.style} hover:opacity-80`])},B(E.label),11,Vl)),64))]),r.value?(Y(),z("div",Ul,[x("label",Kl,B(i.value)+":",1),qt(x("input",{"onUpdate:modelValue":$[0]||($[0]=E=>o.value=E),type:"number",min:"1",max:"20",class:"w-20 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[zt,o.value,void 0,{number:!0}]]),x("button",{onClick:h,class:"px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition"}," 确认执行 "),x("button",{onClick:$[1]||($[1]=E=>r.value=!1),class:"px-3 py-1.5 text-gray-400 hover:text-white text-sm transition"}," 取消 ")])):Rt("",!0),c.value?(Y(),z("div",{key:1,class:pe(["mt-4 px-4 py-3 rounded-lg text-sm",d.value])},B(c.value),3)):Rt("",!0)]))}},Wl={class:"mt-6 bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"},ql={key:0,class:"px-4 py-8 text-center text-gray-500 text-sm"},Gl={key:1,class:"overflow-x-auto"},Jl={class:"w-full text-sm"},Yl={class:"px-4 py-3 font-mono text-xs text-gray-400"},zl={class:"px-4 py-3"},Xl={class:"px-2 py-0.5 bg-gray-800 rounded text-xs font-medium text-gray-300"},Zl={class:"px-4 py-3 text-xs text-gray-400"},Ql={class:"px-4 py-3"},ec={key:0,class:"animate-spin inline-block w-3 h-3 border-2 border-current border-t-transparent rounded-full"},tc={class:"px-4 py-3 text-xs text-gray-400"},sc={class:"px-4 py-3 text-xs text-gray-400"},nc={__name:"TaskHistory",props:{tasks:{type:Array,default:()=>[]}},setup(e){function t(c){return{pending:"text-gray-400",running:"text-yellow-400",completed:"text-green-400",failed:"text-red-400"}[c]||"text-gray-400"}function s(c){return{pending:"bg-gray-400",completed:"bg-green-400",failed:"bg-red-400"}[c]||"bg-gray-400"}function n(c){return{pending:"等待中",running:"执行中",completed:"已完成",failed:"失败"}[c]||c}function r(c){if(!c)return"-";const d=new Date(c*1e3);return`${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")} ${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}:${String(d.getSeconds()).padStart(2,"0")}`}function i(c){const d=c.started_at||c.created_at,u=c.finished_at||(c.status==="running"?Date.now()/1e3:null);if(!d||!u)return"-";const h=Math.round(u-d);return h<60?`${h}s`:`${Math.floor(h/60)}m ${h%60}s`}function o(c){return!c||Object.keys(c).length===0?"-":Object.entries(c).map(([d,u])=>`${d}=${u}`).join(", ")}function l(c){return c==null?"-":typeof c=="string"?c:JSON.stringify(c)}return(c,d)=>(Y(),z("div",Wl,[d[1]||(d[1]=x("div",{class:"px-4 py-3 border-b border-gray-800"},[x("h2",{class:"text-lg font-semibold text-white"},"任务历史")],-1)),e.tasks.length===0?(Y(),z("div",ql," 暂无任务记录 ")):(Y(),z("div",Gl,[x("table",Jl,[d[0]||(d[0]=x("thead",null,[x("tr",{class:"text-gray-400 text-left border-b border-gray-800"},[x("th",{class:"px-4 py-3 font-medium"},"任务 ID"),x("th",{class:"px-4 py-3 font-medium"},"命令"),x("th",{class:"px-4 py-3 font-medium"},"参数"),x("th",{class:"px-4 py-3 font-medium"},"状态"),x("th",{class:"px-4 py-3 font-medium"},"创建时间"),x("th",{class:"px-4 py-3 font-medium"},"耗时"),x("th",{class:"px-4 py-3 font-medium"},"结果")])],-1)),x("tbody",null,[(Y(!0),z(ae,null,Ct(e.tasks,u=>(Y(),z("tr",{key:u.task_id,class:"border-b border-gray-800/50 hover:bg-gray-800/30 transition"},[x("td",Yl,B(u.task_id),1),x("td",zl,[x("span",Xl,B(u.command),1)]),x("td",Zl,B(o(u.params)),1),x("td",Ql,[x("span",{class:pe(["inline-flex items-center gap-1.5 text-xs font-medium",t(u.status)])},[u.status==="running"?(Y(),z("span",ec)):(Y(),z("span",{key:1,class:pe(["w-1.5 h-1.5 rounded-full",s(u.status)])},null,2)),ds(" "+B(n(u.status)),1)],2)]),x("td",tc,B(r(u.created_at)),1),x("td",sc,B(i(u)),1),x("td",{class:pe(["px-4 py-3 text-xs max-w-xs truncate",u.error?"text-red-400":"text-gray-400"])},B(u.error||l(u.result)),3)]))),128))])])]))]))}},rc={class:"mt-6 bg-gray-900 border border-gray-800 rounded-xl p-4"},ic={class:"flex items-center justify-between mb-4"},oc={key:0,class:"text-xs text-green-400 transition"},lc={class:"grid grid-cols-1 sm:grid-cols-3 gap-4"},cc={class:"flex items-center gap-2"},fc={class:"flex items-center gap-2"},uc={class:"flex items-center gap-2"},ac={class:"mt-3 flex items-center justify-between"},dc={class:"text-xs text-gray-500"},hc=["disabled"],pc={__name:"Settings",setup(e){const t=he({interval:5,threshold:10,min_low:2}),s=he(!1),n=he(!1);tn(async()=>{try{const i=await dt.getAutoCheckConfig();t.value={interval:Math.round(i.interval/60),threshold:i.threshold,min_low:i.min_low}}catch(i){console.error("加载巡检配置失败:",i)}});async function r(){s.value=!0,n.value=!1;try{const i=await dt.setAutoCheckConfig({interval:t.value.interval*60,threshold:t.value.threshold,min_low:t.value.min_low});t.value={interval:Math.round(i.interval/60),threshold:i.threshold,min_low:i.min_low},n.value=!0,setTimeout(()=>{n.value=!1},3e3)}catch(i){console.error("保存失败:",i)}finally{s.value=!1}}return(i,o)=>(Y(),z("div",rc,[x("div",ic,[o[3]||(o[3]=x("h2",{class:"text-lg font-semibold text-white"},"巡检设置",-1)),n.value?(Y(),z("span",oc,"已保存")):Rt("",!0)]),x("div",lc,[x("div",null,[o[5]||(o[5]=x("label",{class:"block text-sm text-gray-400 mb-1"},"巡检间隔",-1)),x("div",cc,[qt(x("input",{"onUpdate:modelValue":o[0]||(o[0]=l=>t.value.interval=l),type:"number",min:"1",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[zt,t.value.interval,void 0,{number:!0}]]),o[4]||(o[4]=x("span",{class:"text-sm text-gray-500 shrink-0"},"分钟",-1))])]),x("div",null,[o[7]||(o[7]=x("label",{class:"block text-sm text-gray-400 mb-1"},"额度阈值",-1)),x("div",fc,[qt(x("input",{"onUpdate:modelValue":o[1]||(o[1]=l=>t.value.threshold=l),type:"number",min:"1",max:"100",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[zt,t.value.threshold,void 0,{number:!0}]]),o[6]||(o[6]=x("span",{class:"text-sm text-gray-500 shrink-0"},"%",-1))])]),x("div",null,[o[9]||(o[9]=x("label",{class:"block text-sm text-gray-400 mb-1"},"触发账号数",-1)),x("div",uc,[qt(x("input",{"onUpdate:modelValue":o[2]||(o[2]=l=>t.value.min_low=l),type:"number",min:"1",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[zt,t.value.min_low,void 0,{number:!0}]]),o[8]||(o[8]=x("span",{class:"text-sm text-gray-500 shrink-0"},"个",-1))])])]),x("div",ac,[x("p",dc," 每 "+B(t.value.interval)+" 分钟检查一次,"+B(t.value.min_low)+" 个以上账号剩余低于 "+B(t.value.threshold)+"% 时自动轮转 ",1),x("button",{onClick:r,disabled:s.value,class:"px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"},B(s.value?"保存中...":"保存"),9,hc)])]))}},gc={class:"max-w-7xl mx-auto px-4 py-6"},mc={class:"flex items-center justify-between mb-8"},bc={class:"flex items-center gap-3"},yc={key:0,class:"flex items-center gap-2 text-sm text-yellow-400"},_c=["disabled"],xc={__name:"App",setup(e){const t=he(null),s=he([]),n=he(!1),r=he(null);let i=null;async function o(){n.value=!0;try{const[u,h]=await Promise.all([dt.getStatus(),dt.getTasks()]);t.value=u,s.value=h,r.value=h.find(m=>m.status==="running"||m.status==="pending")||null}catch(u){console.error("刷新失败:",u)}finally{n.value=!1}}function l(){c(1e4),o()}function c(u=6e5){d(),i=setInterval(async()=>{await o(),!r.value&&u<6e5&&c(6e5)},u)}function d(){i&&(clearInterval(i),i=null)}return tn(()=>{o(),c(6e5)}),sn(()=>{d()}),(u,h)=>(Y(),z("div",gc,[x("header",mc,[h[1]||(h[1]=x("div",null,[x("h1",{class:"text-2xl font-bold text-white"},"AutoTeam"),x("p",{class:"text-sm text-gray-400 mt-1"},"ChatGPT Team 账号自动轮转管理")],-1)),x("div",bc,[r.value?(Y(),z("span",yc,[h[0]||(h[0]=x("span",{class:"animate-spin inline-block w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full"},null,-1)),ds(" "+B(r.value.command)+" 执行中... ",1)])):Rt("",!0),x("button",{onClick:o,disabled:n.value,class:"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 text-sm rounded-lg border border-gray-700 transition disabled:opacity-50"},B(n.value?"刷新中...":"刷新"),9,_c)])]),me(Hl,{status:t.value,loading:n.value},null,8,["status","loading"]),me(Bl,{"running-task":r.value,onTaskStarted:l,onRefresh:o},null,8,["running-task"]),me(nc,{tasks:s.value},null,8,["tasks"]),me(pc)]))}};vl(xc).mount("#app");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/autoteam/web/dist/assets/index-CqIsKPRy.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))n(r);new MutationObserver(r=>{for(const o of r)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function s(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function n(r){if(r.ep)return;r.ep=!0;const o=s(r);fetch(r.href,o)}})();/**
|
| 2 |
+
* @vue/shared v3.5.32
|
| 3 |
+
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 4 |
+
* @license MIT
|
| 5 |
+
**/function Xs(e){const t=Object.create(null);for(const s of e.split(","))t[s]=1;return s=>s in t}const Q={},wt=[],Ne=()=>{},Qn=()=>!1,bs=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),ys=e=>e.startsWith("onUpdate:"),he=Object.assign,Zs=(e,t)=>{const s=e.indexOf(t);s>-1&&e.splice(s,1)},no=Object.prototype.hasOwnProperty,J=(e,t)=>no.call(e,t),j=Array.isArray,St=e=>Yt(e)==="[object Map]",xs=e=>Yt(e)==="[object Set]",vn=e=>Yt(e)==="[object Date]",V=e=>typeof e=="function",oe=e=>typeof e=="string",Ue=e=>typeof e=="symbol",X=e=>e!==null&&typeof e=="object",er=e=>(X(e)||V(e))&&V(e.then)&&V(e.catch),tr=Object.prototype.toString,Yt=e=>tr.call(e),ro=e=>Yt(e).slice(8,-1),sr=e=>Yt(e)==="[object Object]",Qs=e=>oe(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Dt=Xs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),vs=e=>{const t=Object.create(null);return(s=>t[s]||(t[s]=e(s)))},oo=/-\w/g,Oe=vs(e=>e.replace(oo,t=>t.slice(1).toUpperCase())),io=/\B([A-Z])/g,ht=vs(e=>e.replace(io,"-$1").toLowerCase()),nr=vs(e=>e.charAt(0).toUpperCase()+e.slice(1)),Ms=vs(e=>e?`on${nr(e)}`:""),Le=(e,t)=>!Object.is(e,t),os=(e,...t)=>{for(let s=0;s<e.length;s++)e[s](...t)},rr=(e,t,s,n=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:n,value:s})},_s=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let _n;const ws=()=>_n||(_n=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function en(e){if(j(e)){const t={};for(let s=0;s<e.length;s++){const n=e[s],r=oe(n)?co(n):en(n);if(r)for(const o in r)t[o]=r[o]}return t}else if(oe(e)||X(e))return e}const lo=/;(?![^(]*\))/g,ao=/:([^]+)/,uo=/\/\*[^]*?\*\//g;function co(e){const t={};return e.replace(uo,"").split(lo).forEach(s=>{if(s){const n=s.split(ao);n.length>1&&(t[n[0].trim()]=n[1].trim())}}),t}function ce(e){let t="";if(oe(e))t=e;else if(j(e))for(let s=0;s<e.length;s++){const n=ce(e[s]);n&&(t+=n+" ")}else if(X(e))for(const s in e)e[s]&&(t+=s+" ");return t.trim()}const fo="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",po=Xs(fo);function or(e){return!!e||e===""}function ho(e,t){if(e.length!==t.length)return!1;let s=!0;for(let n=0;s&&n<e.length;n++)s=zt(e[n],t[n]);return s}function zt(e,t){if(e===t)return!0;let s=vn(e),n=vn(t);if(s||n)return s&&n?e.getTime()===t.getTime():!1;if(s=Ue(e),n=Ue(t),s||n)return e===t;if(s=j(e),n=j(t),s||n)return s&&n?ho(e,t):!1;if(s=X(e),n=X(t),s||n){if(!s||!n)return!1;const r=Object.keys(e).length,o=Object.keys(t).length;if(r!==o)return!1;for(const i in e){const l=e.hasOwnProperty(i),a=t.hasOwnProperty(i);if(l&&!a||!l&&a||!zt(e[i],t[i]))return!1}}return String(e)===String(t)}function go(e,t){return e.findIndex(s=>zt(s,t))}const ir=e=>!!(e&&e.__v_isRef===!0),F=e=>oe(e)?e:e==null?"":j(e)||X(e)&&(e.toString===tr||!V(e.toString))?ir(e)?F(e.value):JSON.stringify(e,lr,2):String(e),lr=(e,t)=>ir(t)?lr(e,t.value):St(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((s,[n,r],o)=>(s[$s(n,o)+" =>"]=r,s),{})}:xs(t)?{[`Set(${t.size})`]:[...t.values()].map(s=>$s(s))}:Ue(t)?$s(t):X(t)&&!j(t)&&!sr(t)?String(t):t,$s=(e,t="")=>{var s;return Ue(e)?`Symbol(${(s=e.description)!=null?s:t})`:e};/**
|
| 6 |
+
* @vue/reactivity v3.5.32
|
| 7 |
+
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 8 |
+
* @license MIT
|
| 9 |
+
**/let ve;class mo{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.__v_skip=!0,this.parent=ve,!t&&ve&&(this.index=(ve.scopes||(ve.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].pause();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,s;if(this.scopes)for(t=0,s=this.scopes.length;t<s;t++)this.scopes[t].resume();for(t=0,s=this.effects.length;t<s;t++)this.effects[t].resume()}}run(t){if(this._active){const s=ve;try{return ve=this,t()}finally{ve=s}}}on(){++this._on===1&&(this.prevScope=ve,ve=this)}off(){this._on>0&&--this._on===0&&(ve=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let s,n;for(s=0,n=this.effects.length;s<n;s++)this.effects[s].stop();for(this.effects.length=0,s=0,n=this.cleanups.length;s<n;s++)this.cleanups[s]();if(this.cleanups.length=0,this.scopes){for(s=0,n=this.scopes.length;s<n;s++)this.scopes[s].stop(!0);this.scopes.length=0}if(!this.detached&&this.parent&&!t){const r=this.parent.scopes.pop();r&&r!==this&&(this.parent.scopes[this.index]=r,r.index=this.index)}this.parent=void 0}}}function bo(){return ve}let te;const ks=new WeakSet;class ar{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.flags=5,this.next=void 0,this.cleanup=void 0,this.scheduler=void 0,ve&&ve.active&&ve.effects.push(this)}pause(){this.flags|=64}resume(){this.flags&64&&(this.flags&=-65,ks.has(this)&&(ks.delete(this),this.trigger()))}notify(){this.flags&2&&!(this.flags&32)||this.flags&8||cr(this)}run(){if(!(this.flags&1))return this.fn();this.flags|=2,wn(this),fr(this);const t=te,s=Ee;te=this,Ee=!0;try{return this.fn()}finally{dr(this),te=t,Ee=s,this.flags&=-3}}stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)nn(t);this.deps=this.depsTail=void 0,wn(this),this.onStop&&this.onStop(),this.flags&=-2}}trigger(){this.flags&64?ks.add(this):this.scheduler?this.scheduler():this.runIfDirty()}runIfDirty(){Ns(this)&&this.run()}get dirty(){return Ns(this)}}let ur=0,jt,Ht;function cr(e,t=!1){if(e.flags|=8,t){e.next=Ht,Ht=e;return}e.next=jt,jt=e}function tn(){ur++}function sn(){if(--ur>0)return;if(Ht){let t=Ht;for(Ht=void 0;t;){const s=t.next;t.next=void 0,t.flags&=-9,t=s}}let e;for(;jt;){let t=jt;for(jt=void 0;t;){const s=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(n){e||(e=n)}t=s}}if(e)throw e}function fr(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function dr(e){let t,s=e.depsTail,n=s;for(;n;){const r=n.prevDep;n.version===-1?(n===s&&(s=r),nn(n),yo(n)):t=n,n.dep.activeLink=n.prevActiveLink,n.prevActiveLink=void 0,n=r}e.deps=t,e.depsTail=s}function Ns(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(pr(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function pr(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Wt)||(e.globalVersion=Wt,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Ns(e))))return;e.flags|=2;const t=e.dep,s=te,n=Ee;te=e,Ee=!0;try{fr(e);const r=e.fn(e._value);(t.version===0||Le(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{te=s,Ee=n,dr(e),e.flags&=-3}}function nn(e,t=!1){const{dep:s,prevSub:n,nextSub:r}=e;if(n&&(n.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=n,e.nextSub=void 0),s.subs===e&&(s.subs=n,!n&&s.computed)){s.computed.flags&=-5;for(let o=s.computed.deps;o;o=o.nextDep)nn(o,!0)}!t&&!--s.sc&&s.map&&s.map.delete(s.key)}function yo(e){const{prevDep:t,nextDep:s}=e;t&&(t.nextDep=s,e.prevDep=void 0),s&&(s.prevDep=t,e.nextDep=void 0)}let Ee=!0;const hr=[];function Ze(){hr.push(Ee),Ee=!1}function Qe(){const e=hr.pop();Ee=e===void 0?!0:e}function wn(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const s=te;te=void 0;try{t()}finally{te=s}}}let Wt=0;class xo{constructor(t,s){this.sub=t,this.dep=s,this.version=s.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class rn{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!te||!Ee||te===this.computed)return;let s=this.activeLink;if(s===void 0||s.sub!==te)s=this.activeLink=new xo(te,this),te.deps?(s.prevDep=te.depsTail,te.depsTail.nextDep=s,te.depsTail=s):te.deps=te.depsTail=s,gr(s);else if(s.version===-1&&(s.version=this.version,s.nextDep)){const n=s.nextDep;n.prevDep=s.prevDep,s.prevDep&&(s.prevDep.nextDep=n),s.prevDep=te.depsTail,s.nextDep=void 0,te.depsTail.nextDep=s,te.depsTail=s,te.deps===s&&(te.deps=n)}return s}trigger(t){this.version++,Wt++,this.notify(t)}notify(t){tn();try{for(let s=this.subs;s;s=s.prevSub)s.sub.notify()&&s.sub.dep.notify()}finally{sn()}}}function gr(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let n=t.deps;n;n=n.nextDep)gr(n)}const s=e.dep.subs;s!==e&&(e.prevSub=s,s&&(s.nextSub=e)),e.dep.subs=e}}const Vs=new WeakMap,dt=Symbol(""),Us=Symbol(""),Bt=Symbol("");function de(e,t,s){if(Ee&&te){let n=Vs.get(e);n||Vs.set(e,n=new Map);let r=n.get(s);r||(n.set(s,r=new rn),r.map=n,r.key=s),r.track()}}function ze(e,t,s,n,r,o){const i=Vs.get(e);if(!i){Wt++;return}const l=a=>{a&&a.trigger()};if(tn(),t==="clear")i.forEach(l);else{const a=j(e),d=a&&Qs(s);if(a&&s==="length"){const c=Number(n);i.forEach((g,S)=>{(S==="length"||S===Bt||!Ue(S)&&S>=c)&&l(g)})}else switch((s!==void 0||i.has(void 0))&&l(i.get(s)),d&&l(i.get(Bt)),t){case"add":a?d&&l(i.get("length")):(l(i.get(dt)),St(e)&&l(i.get(Us)));break;case"delete":a||(l(i.get(dt)),St(e)&&l(i.get(Us)));break;case"set":St(e)&&l(i.get(dt));break}}sn()}function vt(e){const t=G(e);return t===e?t:(de(t,"iterate",Bt),Pe(e)?t:t.map(Me))}function Ss(e){return de(e=G(e),"iterate",Bt),e}function je(e,t){return et(e)?Ot(pt(e)?Me(t):t):Me(t)}const vo={__proto__:null,[Symbol.iterator](){return Is(this,Symbol.iterator,e=>je(this,e))},concat(...e){return vt(this).concat(...e.map(t=>j(t)?vt(t):t))},entries(){return Is(this,"entries",e=>(e[1]=je(this,e[1]),e))},every(e,t){return qe(this,"every",e,t,void 0,arguments)},filter(e,t){return qe(this,"filter",e,t,s=>s.map(n=>je(this,n)),arguments)},find(e,t){return qe(this,"find",e,t,s=>je(this,s),arguments)},findIndex(e,t){return qe(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return qe(this,"findLast",e,t,s=>je(this,s),arguments)},findLastIndex(e,t){return qe(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return qe(this,"forEach",e,t,void 0,arguments)},includes(...e){return Rs(this,"includes",e)},indexOf(...e){return Rs(this,"indexOf",e)},join(e){return vt(this).join(e)},lastIndexOf(...e){return Rs(this,"lastIndexOf",e)},map(e,t){return qe(this,"map",e,t,void 0,arguments)},pop(){return It(this,"pop")},push(...e){return It(this,"push",e)},reduce(e,...t){return Sn(this,"reduce",e,t)},reduceRight(e,...t){return Sn(this,"reduceRight",e,t)},shift(){return It(this,"shift")},some(e,t){return qe(this,"some",e,t,void 0,arguments)},splice(...e){return It(this,"splice",e)},toReversed(){return vt(this).toReversed()},toSorted(e){return vt(this).toSorted(e)},toSpliced(...e){return vt(this).toSpliced(...e)},unshift(...e){return It(this,"unshift",e)},values(){return Is(this,"values",e=>je(this,e))}};function Is(e,t,s){const n=Ss(e),r=n[t]();return n!==e&&!Pe(e)&&(r._next=r.next,r.next=()=>{const o=r._next();return o.done||(o.value=s(o.value)),o}),r}const _o=Array.prototype;function qe(e,t,s,n,r,o){const i=Ss(e),l=i!==e&&!Pe(e),a=i[t];if(a!==_o[t]){const g=a.apply(e,o);return l?Me(g):g}let d=s;i!==e&&(l?d=function(g,S){return s.call(this,je(e,g),S,e)}:s.length>2&&(d=function(g,S){return s.call(this,g,S,e)}));const c=a.call(i,d,n);return l&&r?r(c):c}function Sn(e,t,s,n){const r=Ss(e),o=r!==e&&!Pe(e);let i=s,l=!1;r!==e&&(o?(l=n.length===0,i=function(d,c,g){return l&&(l=!1,d=je(e,d)),s.call(this,d,je(e,c),g,e)}):s.length>3&&(i=function(d,c,g){return s.call(this,d,c,g,e)}));const a=r[t](i,...n);return l?je(e,a):a}function Rs(e,t,s){const n=G(e);de(n,"iterate",Bt);const r=n[t](...s);return(r===-1||r===!1)&&un(s[0])?(s[0]=G(s[0]),n[t](...s)):r}function It(e,t,s=[]){Ze(),tn();const n=G(e)[t].apply(e,s);return sn(),Qe(),n}const wo=Xs("__proto__,__v_isRef,__isVue"),mr=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Ue));function So(e){Ue(e)||(e=String(e));const t=G(this);return de(t,"has",e),t.hasOwnProperty(e)}class br{constructor(t=!1,s=!1){this._isReadonly=t,this._isShallow=s}get(t,s,n){if(s==="__v_skip")return t.__v_skip;const r=this._isReadonly,o=this._isShallow;if(s==="__v_isReactive")return!r;if(s==="__v_isReadonly")return r;if(s==="__v_isShallow")return o;if(s==="__v_raw")return n===(r?o?Io:_r:o?vr:xr).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(n)?t:void 0;const i=j(t);if(!r){let a;if(i&&(a=vo[s]))return a;if(s==="hasOwnProperty")return So}const l=Reflect.get(t,s,pe(t)?t:n);if((Ue(s)?mr.has(s):wo(s))||(r||de(t,"get",s),o))return l;if(pe(l)){const a=i&&Qs(s)?l:l.value;return r&&X(a)?Ws(a):a}return X(l)?r?Ws(l):ln(l):l}}class yr extends br{constructor(t=!1){super(!1,t)}set(t,s,n,r){let o=t[s];const i=j(t)&&Qs(s);if(!this._isShallow){const d=et(o);if(!Pe(n)&&!et(n)&&(o=G(o),n=G(n)),!i&&pe(o)&&!pe(n))return d||(o.value=n),!0}const l=i?Number(s)<t.length:J(t,s),a=Reflect.set(t,s,n,pe(t)?t:r);return t===G(r)&&(l?Le(n,o)&&ze(t,"set",s,n):ze(t,"add",s,n)),a}deleteProperty(t,s){const n=J(t,s);t[s];const r=Reflect.deleteProperty(t,s);return r&&n&&ze(t,"delete",s,void 0),r}has(t,s){const n=Reflect.has(t,s);return(!Ue(s)||!mr.has(s))&&de(t,"has",s),n}ownKeys(t){return de(t,"iterate",j(t)?"length":dt),Reflect.ownKeys(t)}}class Co extends br{constructor(t=!1){super(!0,t)}set(t,s){return!0}deleteProperty(t,s){return!0}}const To=new yr,Ao=new Co,Po=new yr(!0);const Ks=e=>e,ss=e=>Reflect.getPrototypeOf(e);function Oo(e,t,s){return function(...n){const r=this.__v_raw,o=G(r),i=St(o),l=e==="entries"||e===Symbol.iterator&&i,a=e==="keys"&&i,d=r[e](...n),c=s?Ks:t?Ot:Me;return!t&&de(o,"iterate",a?Us:dt),he(Object.create(d),{next(){const{value:g,done:S}=d.next();return S?{value:g,done:S}:{value:l?[c(g[0]),c(g[1])]:c(g),done:S}}})}}function ns(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Eo(e,t){const s={get(r){const o=this.__v_raw,i=G(o),l=G(r);e||(Le(r,l)&&de(i,"get",r),de(i,"get",l));const{has:a}=ss(i),d=t?Ks:e?Ot:Me;if(a.call(i,r))return d(o.get(r));if(a.call(i,l))return d(o.get(l));o!==i&&o.get(r)},get size(){const r=this.__v_raw;return!e&&de(G(r),"iterate",dt),r.size},has(r){const o=this.__v_raw,i=G(o),l=G(r);return e||(Le(r,l)&&de(i,"has",r),de(i,"has",l)),r===l?o.has(r):o.has(r)||o.has(l)},forEach(r,o){const i=this,l=i.__v_raw,a=G(l),d=t?Ks:e?Ot:Me;return!e&&de(a,"iterate",dt),l.forEach((c,g)=>r.call(o,d(c),d(g),i))}};return he(s,e?{add:ns("add"),set:ns("set"),delete:ns("delete"),clear:ns("clear")}:{add(r){const o=G(this),i=ss(o),l=G(r),a=!t&&!Pe(r)&&!et(r)?l:r;return i.has.call(o,a)||Le(r,a)&&i.has.call(o,r)||Le(l,a)&&i.has.call(o,l)||(o.add(a),ze(o,"add",a,a)),this},set(r,o){!t&&!Pe(o)&&!et(o)&&(o=G(o));const i=G(this),{has:l,get:a}=ss(i);let d=l.call(i,r);d||(r=G(r),d=l.call(i,r));const c=a.call(i,r);return i.set(r,o),d?Le(o,c)&&ze(i,"set",r,o):ze(i,"add",r,o),this},delete(r){const o=G(this),{has:i,get:l}=ss(o);let a=i.call(o,r);a||(r=G(r),a=i.call(o,r)),l&&l.call(o,r);const d=o.delete(r);return a&&ze(o,"delete",r,void 0),d},clear(){const r=G(this),o=r.size!==0,i=r.clear();return o&&ze(r,"clear",void 0,void 0),i}}),["keys","values","entries",Symbol.iterator].forEach(r=>{s[r]=Oo(r,e,t)}),s}function on(e,t){const s=Eo(e,t);return(n,r,o)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?n:Reflect.get(J(s,r)&&r in n?s:n,r,o)}const Mo={get:on(!1,!1)},$o={get:on(!1,!0)},ko={get:on(!0,!1)};const xr=new WeakMap,vr=new WeakMap,_r=new WeakMap,Io=new WeakMap;function Ro(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Fo(e){return e.__v_skip||!Object.isExtensible(e)?0:Ro(ro(e))}function ln(e){return et(e)?e:an(e,!1,To,Mo,xr)}function Do(e){return an(e,!1,Po,$o,vr)}function Ws(e){return an(e,!0,Ao,ko,_r)}function an(e,t,s,n,r){if(!X(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=Fo(e);if(o===0)return e;const i=r.get(e);if(i)return i;const l=new Proxy(e,o===2?n:s);return r.set(e,l),l}function pt(e){return et(e)?pt(e.__v_raw):!!(e&&e.__v_isReactive)}function et(e){return!!(e&&e.__v_isReadonly)}function Pe(e){return!!(e&&e.__v_isShallow)}function un(e){return e?!!e.__v_raw:!1}function G(e){const t=e&&e.__v_raw;return t?G(t):e}function jo(e){return!J(e,"__v_skip")&&Object.isExtensible(e)&&rr(e,"__v_skip",!0),e}const Me=e=>X(e)?ln(e):e,Ot=e=>X(e)?Ws(e):e;function pe(e){return e?e.__v_isRef===!0:!1}function q(e){return Ho(e,!1)}function Ho(e,t){return pe(e)?e:new Lo(e,t)}class Lo{constructor(t,s){this.dep=new rn,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=s?t:G(t),this._value=s?t:Me(t),this.__v_isShallow=s}get value(){return this.dep.track(),this._value}set value(t){const s=this._rawValue,n=this.__v_isShallow||Pe(t)||et(t);t=n?t:G(t),Le(t,s)&&(this._rawValue=t,this._value=n?t:Me(t),this.dep.trigger())}}function No(e){return pe(e)?e.value:e}const Vo={get:(e,t,s)=>t==="__v_raw"?e:No(Reflect.get(e,t,s)),set:(e,t,s,n)=>{const r=e[t];return pe(r)&&!pe(s)?(r.value=s,!0):Reflect.set(e,t,s,n)}};function wr(e){return pt(e)?e:new Proxy(e,Vo)}class Uo{constructor(t,s,n){this.fn=t,this.setter=s,this._value=void 0,this.dep=new rn(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Wt-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!s,this.isSSR=n}notify(){if(this.flags|=16,!(this.flags&8)&&te!==this)return cr(this,!0),!0}get value(){const t=this.dep.track();return pr(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Ko(e,t,s=!1){let n,r;return V(e)?n=e:(n=e.get,r=e.set),new Uo(n,r,s)}const rs={},us=new WeakMap;let ct;function Wo(e,t=!1,s=ct){if(s){let n=us.get(s);n||us.set(s,n=[]),n.push(e)}}function Bo(e,t,s=Q){const{immediate:n,deep:r,once:o,scheduler:i,augmentJob:l,call:a}=s,d=w=>r?w:Pe(w)||r===!1||r===0?Xe(w,1):Xe(w);let c,g,S,E,P=!1,T=!1;if(pe(e)?(g=()=>e.value,P=Pe(e)):pt(e)?(g=()=>d(e),P=!0):j(e)?(T=!0,P=e.some(w=>pt(w)||Pe(w)),g=()=>e.map(w=>{if(pe(w))return w.value;if(pt(w))return d(w);if(V(w))return a?a(w,2):w()})):V(e)?t?g=a?()=>a(e,2):e:g=()=>{if(S){Ze();try{S()}finally{Qe()}}const w=ct;ct=c;try{return a?a(e,3,[E]):e(E)}finally{ct=w}}:g=Ne,t&&r){const w=g,B=r===!0?1/0:r;g=()=>Xe(w(),B)}const H=bo(),U=()=>{c.stop(),H&&H.active&&Zs(H.effects,c)};if(o&&t){const w=t;t=(...B)=>{w(...B),U()}}let v=T?new Array(e.length).fill(rs):rs;const I=w=>{if(!(!(c.flags&1)||!c.dirty&&!w))if(t){const B=c.run();if(r||P||(T?B.some((ne,K)=>Le(ne,v[K])):Le(B,v))){S&&S();const ne=ct;ct=c;try{const K=[B,v===rs?void 0:T&&v[0]===rs?[]:v,E];v=B,a?a(t,3,K):t(...K)}finally{ct=ne}}}else c.run()};return l&&l(I),c=new ar(g),c.scheduler=i?()=>i(I,!1):I,E=w=>Wo(w,!1,c),S=c.onStop=()=>{const w=us.get(c);if(w){if(a)a(w,4);else for(const B of w)B();us.delete(c)}},t?n?I(!0):v=c.run():i?i(I.bind(null,!0),!0):c.run(),U.pause=c.pause.bind(c),U.resume=c.resume.bind(c),U.stop=U,U}function Xe(e,t=1/0,s){if(t<=0||!X(e)||e.__v_skip||(s=s||new Map,(s.get(e)||0)>=t))return e;if(s.set(e,t),t--,pe(e))Xe(e.value,t,s);else if(j(e))for(let n=0;n<e.length;n++)Xe(e[n],t,s);else if(xs(e)||St(e))e.forEach(n=>{Xe(n,t,s)});else if(sr(e)){for(const n in e)Xe(e[n],t,s);for(const n of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,n)&&Xe(e[n],t,s)}return e}/**
|
| 10 |
+
* @vue/runtime-core v3.5.32
|
| 11 |
+
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 12 |
+
* @license MIT
|
| 13 |
+
**/function Xt(e,t,s,n){try{return n?e(...n):e()}catch(r){Cs(r,t,s)}}function Ke(e,t,s,n){if(V(e)){const r=Xt(e,t,s,n);return r&&er(r)&&r.catch(o=>{Cs(o,t,s)}),r}if(j(e)){const r=[];for(let o=0;o<e.length;o++)r.push(Ke(e[o],t,s,n));return r}}function Cs(e,t,s,n=!0){const r=t?t.vnode:null,{errorHandler:o,throwUnhandledErrorInProduction:i}=t&&t.appContext.config||Q;if(t){let l=t.parent;const a=t.proxy,d=`https://vuejs.org/error-reference/#runtime-${s}`;for(;l;){const c=l.ec;if(c){for(let g=0;g<c.length;g++)if(c[g](e,a,d)===!1)return}l=l.parent}if(o){Ze(),Xt(o,null,10,[e,a,d]),Qe();return}}qo(e,s,r,n,i)}function qo(e,t,s,n=!0,r=!1){if(r)throw e;console.error(e)}const me=[];let De=-1;const Ct=[];let rt=null,_t=0;const Sr=Promise.resolve();let cs=null;function Cr(e){const t=cs||Sr;return e?t.then(this?e.bind(this):e):t}function Go(e){let t=De+1,s=me.length;for(;t<s;){const n=t+s>>>1,r=me[n],o=qt(r);o<e||o===e&&r.flags&2?t=n+1:s=n}return t}function cn(e){if(!(e.flags&1)){const t=qt(e),s=me[me.length-1];!s||!(e.flags&2)&&t>=qt(s)?me.push(e):me.splice(Go(t),0,e),e.flags|=1,Tr()}}function Tr(){cs||(cs=Sr.then(Pr))}function Jo(e){j(e)?Ct.push(...e):rt&&e.id===-1?rt.splice(_t+1,0,e):e.flags&1||(Ct.push(e),e.flags|=1),Tr()}function Cn(e,t,s=De+1){for(;s<me.length;s++){const n=me[s];if(n&&n.flags&2){if(e&&n.id!==e.uid)continue;me.splice(s,1),s--,n.flags&4&&(n.flags&=-2),n(),n.flags&4||(n.flags&=-2)}}}function Ar(e){if(Ct.length){const t=[...new Set(Ct)].sort((s,n)=>qt(s)-qt(n));if(Ct.length=0,rt){rt.push(...t);return}for(rt=t,_t=0;_t<rt.length;_t++){const s=rt[_t];s.flags&4&&(s.flags&=-2),s.flags&8||s(),s.flags&=-2}rt=null,_t=0}}const qt=e=>e.id==null?e.flags&2?-1:1/0:e.id;function Pr(e){try{for(De=0;De<me.length;De++){const t=me[De];t&&!(t.flags&8)&&(t.flags&4&&(t.flags&=-2),Xt(t,t.i,t.i?15:14),t.flags&4||(t.flags&=-2))}}finally{for(;De<me.length;De++){const t=me[De];t&&(t.flags&=-2)}De=-1,me.length=0,Ar(),cs=null,(me.length||Ct.length)&&Pr()}}let Ae=null,Or=null;function fs(e){const t=Ae;return Ae=e,Or=e&&e.type.__scopeId||null,t}function Yo(e,t=Ae,s){if(!t||e._n)return e;const n=(...r)=>{n._d&&Fn(-1);const o=fs(t);let i;try{i=e(...r)}finally{fs(o),n._d&&Fn(1)}return i};return n._n=!0,n._c=!0,n._d=!0,n}function Fe(e,t){if(Ae===null)return e;const s=Os(Ae),n=e.dirs||(e.dirs=[]);for(let r=0;r<t.length;r++){let[o,i,l,a=Q]=t[r];o&&(V(o)&&(o={mounted:o,updated:o}),o.deep&&Xe(i),n.push({dir:o,instance:s,value:i,oldValue:void 0,arg:l,modifiers:a}))}return e}function at(e,t,s,n){const r=e.dirs,o=t&&t.dirs;for(let i=0;i<r.length;i++){const l=r[i];o&&(l.oldValue=o[i].value);let a=l.dir[n];a&&(Ze(),Ke(a,s,8,[e.el,l,e,t]),Qe())}}function zo(e,t){if(ye){let s=ye.provides;const n=ye.parent&&ye.parent.provides;n===s&&(s=ye.provides=Object.create(n)),s[e]=t}}function is(e,t,s=!1){const n=Yi();if(n||At){let r=At?At._context.provides:n?n.parent==null||n.ce?n.vnode.appContext&&n.vnode.appContext.provides:n.parent.provides:void 0;if(r&&e in r)return r[e];if(arguments.length>1)return s&&V(t)?t.call(n&&n.proxy):t}}const Xo=Symbol.for("v-scx"),Zo=()=>is(Xo);function Lt(e,t,s){return Er(e,t,s)}function Er(e,t,s=Q){const{immediate:n,deep:r,flush:o,once:i}=s,l=he({},s),a=t&&n||!t&&o!=="post";let d;if(Jt){if(o==="sync"){const E=Zo();d=E.__watcherHandles||(E.__watcherHandles=[])}else if(!a){const E=()=>{};return E.stop=Ne,E.resume=Ne,E.pause=Ne,E}}const c=ye;l.call=(E,P,T)=>Ke(E,c,P,T);let g=!1;o==="post"?l.scheduler=E=>{xe(E,c&&c.suspense)}:o!=="sync"&&(g=!0,l.scheduler=(E,P)=>{P?E():cn(E)}),l.augmentJob=E=>{t&&(E.flags|=4),g&&(E.flags|=2,c&&(E.id=c.uid,E.i=c))};const S=Bo(e,t,l);return Jt&&(d?d.push(S):a&&S()),S}function Qo(e,t,s){const n=this.proxy,r=oe(e)?e.includes(".")?Mr(n,e):()=>n[e]:e.bind(n,n);let o;V(t)?o=t:(o=t.handler,s=t);const i=Qt(this),l=Er(r,o.bind(n),s);return i(),l}function Mr(e,t){const s=t.split(".");return()=>{let n=e;for(let r=0;r<s.length&&n;r++)n=n[s[r]];return n}}const ei=Symbol("_vte"),ti=e=>e.__isTeleport,si=Symbol("_leaveCb");function fn(e,t){e.shapeFlag&6&&e.component?(e.transition=t,fn(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function $r(e){e.ids=[e.ids[0]+e.ids[2]+++"-",0,0]}function Tn(e,t){let s;return!!((s=Object.getOwnPropertyDescriptor(e,t))&&!s.configurable)}const ds=new WeakMap;function Nt(e,t,s,n,r=!1){if(j(e)){e.forEach((T,H)=>Nt(T,t&&(j(t)?t[H]:t),s,n,r));return}if(Vt(n)&&!r){n.shapeFlag&512&&n.type.__asyncResolved&&n.component.subTree.component&&Nt(e,t,s,n.component.subTree);return}const o=n.shapeFlag&4?Os(n.component):n.el,i=r?null:o,{i:l,r:a}=e,d=t&&t.r,c=l.refs===Q?l.refs={}:l.refs,g=l.setupState,S=G(g),E=g===Q?Qn:T=>Tn(c,T)?!1:J(S,T),P=(T,H)=>!(H&&Tn(c,H));if(d!=null&&d!==a){if(An(t),oe(d))c[d]=null,E(d)&&(g[d]=null);else if(pe(d)){const T=t;P(d,T.k)&&(d.value=null),T.k&&(c[T.k]=null)}}if(V(a))Xt(a,l,12,[i,c]);else{const T=oe(a),H=pe(a);if(T||H){const U=()=>{if(e.f){const v=T?E(a)?g[a]:c[a]:P()||!e.k?a.value:c[e.k];if(r)j(v)&&Zs(v,o);else if(j(v))v.includes(o)||v.push(o);else if(T)c[a]=[o],E(a)&&(g[a]=c[a]);else{const I=[o];P(a,e.k)&&(a.value=I),e.k&&(c[e.k]=I)}}else T?(c[a]=i,E(a)&&(g[a]=i)):H&&(P(a,e.k)&&(a.value=i),e.k&&(c[e.k]=i))};if(i){const v=()=>{U(),ds.delete(e)};v.id=-1,ds.set(e,v),xe(v,s)}else An(e),U()}}}function An(e){const t=ds.get(e);t&&(t.flags|=8,ds.delete(e))}ws().requestIdleCallback;ws().cancelIdleCallback;const Vt=e=>!!e.type.__asyncLoader,kr=e=>e.type.__isKeepAlive;function ni(e,t){Ir(e,"a",t)}function ri(e,t){Ir(e,"da",t)}function Ir(e,t,s=ye){const n=e.__wdc||(e.__wdc=()=>{let r=s;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Ts(t,n,s),s){let r=s.parent;for(;r&&r.parent;)kr(r.parent.vnode)&&oi(n,t,s,r),r=r.parent}}function oi(e,t,s,n){const r=Ts(t,e,n,!0);pn(()=>{Zs(n[t],r)},s)}function Ts(e,t,s=ye,n=!1){if(s){const r=s[e]||(s[e]=[]),o=t.__weh||(t.__weh=(...i)=>{Ze();const l=Qt(s),a=Ke(t,s,e,i);return l(),Qe(),a});return n?r.unshift(o):r.push(o),o}}const tt=e=>(t,s=ye)=>{(!Jt||e==="sp")&&Ts(e,(...n)=>t(...n),s)},ii=tt("bm"),dn=tt("m"),li=tt("bu"),ai=tt("u"),ui=tt("bum"),pn=tt("um"),ci=tt("sp"),fi=tt("rtg"),di=tt("rtc");function pi(e,t=ye){Ts("ec",e,t)}const hi=Symbol.for("v-ndc");function Tt(e,t,s,n){let r;const o=s,i=j(e);if(i||oe(e)){const l=i&&pt(e);let a=!1,d=!1;l&&(a=!Pe(e),d=et(e),e=Ss(e)),r=new Array(e.length);for(let c=0,g=e.length;c<g;c++)r[c]=t(a?d?Ot(Me(e[c])):Me(e[c]):e[c],c,void 0,o)}else if(typeof e=="number"){r=new Array(e);for(let l=0;l<e;l++)r[l]=t(l+1,l,void 0,o)}else if(X(e))if(e[Symbol.iterator])r=Array.from(e,(l,a)=>t(l,a,void 0,o));else{const l=Object.keys(e);r=new Array(l.length);for(let a=0,d=l.length;a<d;a++){const c=l[a];r[a]=t(e[c],c,a,o)}}else r=[];return r}const Bs=e=>e?eo(e)?Os(e):Bs(e.parent):null,Ut=he(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Bs(e.parent),$root:e=>Bs(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>Fr(e),$forceUpdate:e=>e.f||(e.f=()=>{cn(e.update)}),$nextTick:e=>e.n||(e.n=Cr.bind(e.proxy)),$watch:e=>Qo.bind(e)}),Fs=(e,t)=>e!==Q&&!e.__isScriptSetup&&J(e,t),gi={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:s,setupState:n,data:r,props:o,accessCache:i,type:l,appContext:a}=e;if(t[0]!=="$"){const S=i[t];if(S!==void 0)switch(S){case 1:return n[t];case 2:return r[t];case 4:return s[t];case 3:return o[t]}else{if(Fs(n,t))return i[t]=1,n[t];if(r!==Q&&J(r,t))return i[t]=2,r[t];if(J(o,t))return i[t]=3,o[t];if(s!==Q&&J(s,t))return i[t]=4,s[t];qs&&(i[t]=0)}}const d=Ut[t];let c,g;if(d)return t==="$attrs"&&de(e.attrs,"get",""),d(e);if((c=l.__cssModules)&&(c=c[t]))return c;if(s!==Q&&J(s,t))return i[t]=4,s[t];if(g=a.config.globalProperties,J(g,t))return g[t]},set({_:e},t,s){const{data:n,setupState:r,ctx:o}=e;return Fs(r,t)?(r[t]=s,!0):n!==Q&&J(n,t)?(n[t]=s,!0):J(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=s,!0)},has({_:{data:e,setupState:t,accessCache:s,ctx:n,appContext:r,props:o,type:i}},l){let a;return!!(s[l]||e!==Q&&l[0]!=="$"&&J(e,l)||Fs(t,l)||J(o,l)||J(n,l)||J(Ut,l)||J(r.config.globalProperties,l)||(a=i.__cssModules)&&a[l])},defineProperty(e,t,s){return s.get!=null?e._.accessCache[t]=0:J(s,"value")&&this.set(e,t,s.value,null),Reflect.defineProperty(e,t,s)}};function Pn(e){return j(e)?e.reduce((t,s)=>(t[s]=null,t),{}):e}let qs=!0;function mi(e){const t=Fr(e),s=e.proxy,n=e.ctx;qs=!1,t.beforeCreate&&On(t.beforeCreate,e,"bc");const{data:r,computed:o,methods:i,watch:l,provide:a,inject:d,created:c,beforeMount:g,mounted:S,beforeUpdate:E,updated:P,activated:T,deactivated:H,beforeDestroy:U,beforeUnmount:v,destroyed:I,unmounted:w,render:B,renderTracked:ne,renderTriggered:K,errorCaptured:_e,serverPrefetch:gt,expose:We,inheritAttrs:it,components:mt,directives:bt,filters:Mt}=t;if(d&&bi(d,n,null),i)for(const se in i){const z=i[se];V(z)&&(n[se]=z.bind(s))}if(r){const se=r.call(s,s);X(se)&&(e.data=ln(se))}if(qs=!0,o)for(const se in o){const z=o[se],_=V(z)?z.bind(s,s):V(z.get)?z.get.bind(s,s):Ne,$=!V(z)&&V(z.set)?z.set.bind(s):Ne,we=Ve({get:_,set:$});Object.defineProperty(n,se,{enumerable:!0,configurable:!0,get:()=>we.value,set:fe=>we.value=fe})}if(l)for(const se in l)Rr(l[se],n,s,se);if(a){const se=V(a)?a.call(s):a;Reflect.ownKeys(se).forEach(z=>{zo(z,se[z])})}c&&On(c,e,"c");function ae(se,z){j(z)?z.forEach(_=>se(_.bind(s))):z&&se(z.bind(s))}if(ae(ii,g),ae(dn,S),ae(li,E),ae(ai,P),ae(ni,T),ae(ri,H),ae(pi,_e),ae(di,ne),ae(fi,K),ae(ui,v),ae(pn,w),ae(ci,gt),j(We))if(We.length){const se=e.exposed||(e.exposed={});We.forEach(z=>{Object.defineProperty(se,z,{get:()=>s[z],set:_=>s[z]=_,enumerable:!0})})}else e.exposed||(e.exposed={});B&&e.render===Ne&&(e.render=B),it!=null&&(e.inheritAttrs=it),mt&&(e.components=mt),bt&&(e.directives=bt),gt&&$r(e)}function bi(e,t,s=Ne){j(e)&&(e=Gs(e));for(const n in e){const r=e[n];let o;X(r)?"default"in r?o=is(r.from||n,r.default,!0):o=is(r.from||n):o=is(r),pe(o)?Object.defineProperty(t,n,{enumerable:!0,configurable:!0,get:()=>o.value,set:i=>o.value=i}):t[n]=o}}function On(e,t,s){Ke(j(e)?e.map(n=>n.bind(t.proxy)):e.bind(t.proxy),t,s)}function Rr(e,t,s,n){let r=n.includes(".")?Mr(s,n):()=>s[n];if(oe(e)){const o=t[e];V(o)&&Lt(r,o)}else if(V(e))Lt(r,e.bind(s));else if(X(e))if(j(e))e.forEach(o=>Rr(o,t,s,n));else{const o=V(e.handler)?e.handler.bind(s):t[e.handler];V(o)&&Lt(r,o,e)}}function Fr(e){const t=e.type,{mixins:s,extends:n}=t,{mixins:r,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,l=o.get(t);let a;return l?a=l:!r.length&&!s&&!n?a=t:(a={},r.length&&r.forEach(d=>ps(a,d,i,!0)),ps(a,t,i)),X(t)&&o.set(t,a),a}function ps(e,t,s,n=!1){const{mixins:r,extends:o}=t;o&&ps(e,o,s,!0),r&&r.forEach(i=>ps(e,i,s,!0));for(const i in t)if(!(n&&i==="expose")){const l=yi[i]||s&&s[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const yi={data:En,props:Mn,emits:Mn,methods:Ft,computed:Ft,beforeCreate:ge,created:ge,beforeMount:ge,mounted:ge,beforeUpdate:ge,updated:ge,beforeDestroy:ge,beforeUnmount:ge,destroyed:ge,unmounted:ge,activated:ge,deactivated:ge,errorCaptured:ge,serverPrefetch:ge,components:Ft,directives:Ft,watch:vi,provide:En,inject:xi};function En(e,t){return t?e?function(){return he(V(e)?e.call(this,this):e,V(t)?t.call(this,this):t)}:t:e}function xi(e,t){return Ft(Gs(e),Gs(t))}function Gs(e){if(j(e)){const t={};for(let s=0;s<e.length;s++)t[e[s]]=e[s];return t}return e}function ge(e,t){return e?[...new Set([].concat(e,t))]:t}function Ft(e,t){return e?he(Object.create(null),e,t):t}function Mn(e,t){return e?j(e)&&j(t)?[...new Set([...e,...t])]:he(Object.create(null),Pn(e),Pn(t??{})):t}function vi(e,t){if(!e)return t;if(!t)return e;const s=he(Object.create(null),e);for(const n in t)s[n]=ge(e[n],t[n]);return s}function Dr(){return{app:null,config:{isNativeTag:Qn,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let _i=0;function wi(e,t){return function(n,r=null){V(n)||(n=he({},n)),r!=null&&!X(r)&&(r=null);const o=Dr(),i=new WeakSet,l=[];let a=!1;const d=o.app={_uid:_i++,_component:n,_props:r,_container:null,_context:o,_instance:null,version:tl,get config(){return o.config},set config(c){},use(c,...g){return i.has(c)||(c&&V(c.install)?(i.add(c),c.install(d,...g)):V(c)&&(i.add(c),c(d,...g))),d},mixin(c){return o.mixins.includes(c)||o.mixins.push(c),d},component(c,g){return g?(o.components[c]=g,d):o.components[c]},directive(c,g){return g?(o.directives[c]=g,d):o.directives[c]},mount(c,g,S){if(!a){const E=d._ceVNode||Te(n,r);return E.appContext=o,S===!0?S="svg":S===!1&&(S=void 0),e(E,c,S),a=!0,d._container=c,c.__vue_app__=d,Os(E.component)}},onUnmount(c){l.push(c)},unmount(){a&&(Ke(l,d._instance,16),e(null,d._container),delete d._container.__vue_app__)},provide(c,g){return o.provides[c]=g,d},runWithContext(c){const g=At;At=d;try{return c()}finally{At=g}}};return d}}let At=null;const Si=(e,t)=>t==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Oe(t)}Modifiers`]||e[`${ht(t)}Modifiers`];function Ci(e,t,...s){if(e.isUnmounted)return;const n=e.vnode.props||Q;let r=s;const o=t.startsWith("update:"),i=o&&Si(n,t.slice(7));i&&(i.trim&&(r=s.map(c=>oe(c)?c.trim():c)),i.number&&(r=s.map(_s)));let l,a=n[l=Ms(t)]||n[l=Ms(Oe(t))];!a&&o&&(a=n[l=Ms(ht(t))]),a&&Ke(a,e,6,r);const d=n[l+"Once"];if(d){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ke(d,e,6,r)}}const Ti=new WeakMap;function jr(e,t,s=!1){const n=s?Ti:t.emitsCache,r=n.get(e);if(r!==void 0)return r;const o=e.emits;let i={},l=!1;if(!V(e)){const a=d=>{const c=jr(d,t,!0);c&&(l=!0,he(i,c))};!s&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!o&&!l?(X(e)&&n.set(e,null),null):(j(o)?o.forEach(a=>i[a]=null):he(i,o),X(e)&&n.set(e,i),i)}function As(e,t){return!e||!bs(t)?!1:(t=t.slice(2).replace(/Once$/,""),J(e,t[0].toLowerCase()+t.slice(1))||J(e,ht(t))||J(e,t))}function $n(e){const{type:t,vnode:s,proxy:n,withProxy:r,propsOptions:[o],slots:i,attrs:l,emit:a,render:d,renderCache:c,props:g,data:S,setupState:E,ctx:P,inheritAttrs:T}=e,H=fs(e);let U,v;try{if(s.shapeFlag&4){const w=r||n,B=w;U=He(d.call(B,w,c,g,E,S,P)),v=l}else{const w=t;U=He(w.length>1?w(g,{attrs:l,slots:i,emit:a}):w(g,null)),v=t.props?l:Ai(l)}}catch(w){Kt.length=0,Cs(w,e,1),U=Te(ot)}let I=U;if(v&&T!==!1){const w=Object.keys(v),{shapeFlag:B}=I;w.length&&B&7&&(o&&w.some(ys)&&(v=Pi(v,o)),I=Et(I,v,!1,!0))}return s.dirs&&(I=Et(I,null,!1,!0),I.dirs=I.dirs?I.dirs.concat(s.dirs):s.dirs),s.transition&&fn(I,s.transition),U=I,fs(H),U}const Ai=e=>{let t;for(const s in e)(s==="class"||s==="style"||bs(s))&&((t||(t={}))[s]=e[s]);return t},Pi=(e,t)=>{const s={};for(const n in e)(!ys(n)||!(n.slice(9)in t))&&(s[n]=e[n]);return s};function Oi(e,t,s){const{props:n,children:r,component:o}=e,{props:i,children:l,patchFlag:a}=t,d=o.emitsOptions;if(t.dirs||t.transition)return!0;if(s&&a>=0){if(a&1024)return!0;if(a&16)return n?kn(n,i,d):!!i;if(a&8){const c=t.dynamicProps;for(let g=0;g<c.length;g++){const S=c[g];if(Hr(i,n,S)&&!As(d,S))return!0}}}else return(r||l)&&(!l||!l.$stable)?!0:n===i?!1:n?i?kn(n,i,d):!0:!!i;return!1}function kn(e,t,s){const n=Object.keys(t);if(n.length!==Object.keys(e).length)return!0;for(let r=0;r<n.length;r++){const o=n[r];if(Hr(t,e,o)&&!As(s,o))return!0}return!1}function Hr(e,t,s){const n=e[s],r=t[s];return s==="style"&&X(n)&&X(r)?!zt(n,r):n!==r}function Ei({vnode:e,parent:t,suspense:s},n){for(;t;){const r=t.subTree;if(r.suspense&&r.suspense.activeBranch===e&&(r.suspense.vnode.el=r.el=n,e=r),r===e)(e=t.vnode).el=n,t=t.parent;else break}s&&s.activeBranch===e&&(s.vnode.el=n)}const Lr={},Nr=()=>Object.create(Lr),Vr=e=>Object.getPrototypeOf(e)===Lr;function Mi(e,t,s,n=!1){const r={},o=Nr();e.propsDefaults=Object.create(null),Ur(e,t,r,o);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);s?e.props=n?r:Do(r):e.type.props?e.props=r:e.props=o,e.attrs=o}function $i(e,t,s,n){const{props:r,attrs:o,vnode:{patchFlag:i}}=e,l=G(r),[a]=e.propsOptions;let d=!1;if((n||i>0)&&!(i&16)){if(i&8){const c=e.vnode.dynamicProps;for(let g=0;g<c.length;g++){let S=c[g];if(As(e.emitsOptions,S))continue;const E=t[S];if(a)if(J(o,S))E!==o[S]&&(o[S]=E,d=!0);else{const P=Oe(S);r[P]=Js(a,l,P,E,e,!1)}else E!==o[S]&&(o[S]=E,d=!0)}}}else{Ur(e,t,r,o)&&(d=!0);let c;for(const g in l)(!t||!J(t,g)&&((c=ht(g))===g||!J(t,c)))&&(a?s&&(s[g]!==void 0||s[c]!==void 0)&&(r[g]=Js(a,l,g,void 0,e,!0)):delete r[g]);if(o!==l)for(const g in o)(!t||!J(t,g))&&(delete o[g],d=!0)}d&&ze(e.attrs,"set","")}function Ur(e,t,s,n){const[r,o]=e.propsOptions;let i=!1,l;if(t)for(let a in t){if(Dt(a))continue;const d=t[a];let c;r&&J(r,c=Oe(a))?!o||!o.includes(c)?s[c]=d:(l||(l={}))[c]=d:As(e.emitsOptions,a)||(!(a in n)||d!==n[a])&&(n[a]=d,i=!0)}if(o){const a=G(s),d=l||Q;for(let c=0;c<o.length;c++){const g=o[c];s[g]=Js(r,a,g,d[g],e,!J(d,g))}}return i}function Js(e,t,s,n,r,o){const i=e[s];if(i!=null){const l=J(i,"default");if(l&&n===void 0){const a=i.default;if(i.type!==Function&&!i.skipFactory&&V(a)){const{propsDefaults:d}=r;if(s in d)n=d[s];else{const c=Qt(r);n=d[s]=a.call(null,t),c()}}else n=a;r.ce&&r.ce._setProp(s,n)}i[0]&&(o&&!l?n=!1:i[1]&&(n===""||n===ht(s))&&(n=!0))}return n}const ki=new WeakMap;function Kr(e,t,s=!1){const n=s?ki:t.propsCache,r=n.get(e);if(r)return r;const o=e.props,i={},l=[];let a=!1;if(!V(e)){const c=g=>{a=!0;const[S,E]=Kr(g,t,!0);he(i,S),E&&l.push(...E)};!s&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}if(!o&&!a)return X(e)&&n.set(e,wt),wt;if(j(o))for(let c=0;c<o.length;c++){const g=Oe(o[c]);In(g)&&(i[g]=Q)}else if(o)for(const c in o){const g=Oe(c);if(In(g)){const S=o[c],E=i[g]=j(S)||V(S)?{type:S}:he({},S),P=E.type;let T=!1,H=!0;if(j(P))for(let U=0;U<P.length;++U){const v=P[U],I=V(v)&&v.name;if(I==="Boolean"){T=!0;break}else I==="String"&&(H=!1)}else T=V(P)&&P.name==="Boolean";E[0]=T,E[1]=H,(T||J(E,"default"))&&l.push(g)}}const d=[i,l];return X(e)&&n.set(e,d),d}function In(e){return e[0]!=="$"&&!Dt(e)}const hn=e=>e==="_"||e==="_ctx"||e==="$stable",gn=e=>j(e)?e.map(He):[He(e)],Ii=(e,t,s)=>{if(t._n)return t;const n=Yo((...r)=>gn(t(...r)),s);return n._c=!1,n},Wr=(e,t,s)=>{const n=e._ctx;for(const r in e){if(hn(r))continue;const o=e[r];if(V(o))t[r]=Ii(r,o,n);else if(o!=null){const i=gn(o);t[r]=()=>i}}},Br=(e,t)=>{const s=gn(t);e.slots.default=()=>s},qr=(e,t,s)=>{for(const n in t)(s||!hn(n))&&(e[n]=t[n])},Ri=(e,t,s)=>{const n=e.slots=Nr();if(e.vnode.shapeFlag&32){const r=t._;r?(qr(n,t,s),s&&rr(n,"_",r,!0)):Wr(t,n)}else t&&Br(e,t)},Fi=(e,t,s)=>{const{vnode:n,slots:r}=e;let o=!0,i=Q;if(n.shapeFlag&32){const l=t._;l?s&&l===1?o=!1:qr(r,t,s):(o=!t.$stable,Wr(t,r)),i=t}else t&&(Br(e,t),i={default:1});if(o)for(const l in r)!hn(l)&&i[l]==null&&delete r[l]},xe=Ni;function Di(e){return ji(e)}function ji(e,t){const s=ws();s.__VUE__=!0;const{insert:n,remove:r,patchProp:o,createElement:i,createText:l,createComment:a,setText:d,setElementText:c,parentNode:g,nextSibling:S,setScopeId:E=Ne,insertStaticContent:P}=e,T=(u,f,h,x=null,m=null,b=null,O=void 0,A=null,C=!!f.dynamicChildren)=>{if(u===f)return;u&&!Rt(u,f)&&(x=lt(u),fe(u,m,b,!0),u=null),f.patchFlag===-2&&(C=!1,f.dynamicChildren=null);const{type:y,ref:R,shapeFlag:M}=f;switch(y){case Ps:H(u,f,h,x);break;case ot:U(u,f,h,x);break;case js:u==null&&v(f,h,x,O);break;case be:mt(u,f,h,x,m,b,O,A,C);break;default:M&1?B(u,f,h,x,m,b,O,A,C):M&6?bt(u,f,h,x,m,b,O,A,C):(M&64||M&128)&&y.process(u,f,h,x,m,b,O,A,C,nt)}R!=null&&m?Nt(R,u&&u.ref,b,f||u,!f):R==null&&u&&u.ref!=null&&Nt(u.ref,null,b,u,!0)},H=(u,f,h,x)=>{if(u==null)n(f.el=l(f.children),h,x);else{const m=f.el=u.el;f.children!==u.children&&d(m,f.children)}},U=(u,f,h,x)=>{u==null?n(f.el=a(f.children||""),h,x):f.el=u.el},v=(u,f,h,x)=>{[u.el,u.anchor]=P(u.children,f,h,x,u.el,u.anchor)},I=({el:u,anchor:f},h,x)=>{let m;for(;u&&u!==f;)m=S(u),n(u,h,x),u=m;n(f,h,x)},w=({el:u,anchor:f})=>{let h;for(;u&&u!==f;)h=S(u),r(u),u=h;r(f)},B=(u,f,h,x,m,b,O,A,C)=>{if(f.type==="svg"?O="svg":f.type==="math"&&(O="mathml"),u==null)ne(f,h,x,m,b,O,A,C);else{const y=u.el&&u.el._isVueCE?u.el:null;try{y&&y._beginPatch(),gt(u,f,m,b,O,A,C)}finally{y&&y._endPatch()}}},ne=(u,f,h,x,m,b,O,A)=>{let C,y;const{props:R,shapeFlag:M,transition:k,dirs:D}=u;if(C=u.el=i(u.type,b,R&&R.is,R),M&8?c(C,u.children):M&16&&_e(u.children,C,null,x,m,Ds(u,b),O,A),D&&at(u,null,x,"created"),K(C,u,u.scopeId,O,x),R){for(const Z in R)Z!=="value"&&!Dt(Z)&&o(C,Z,null,R[Z],b,x);"value"in R&&o(C,"value",null,R.value,b),(y=R.onVnodeBeforeMount)&&Re(y,x,u)}D&&at(u,null,x,"beforeMount");const W=Hi(m,k);W&&k.beforeEnter(C),n(C,f,h),((y=R&&R.onVnodeMounted)||W||D)&&xe(()=>{try{y&&Re(y,x,u),W&&k.enter(C),D&&at(u,null,x,"mounted")}finally{}},m)},K=(u,f,h,x,m)=>{if(h&&E(u,h),x)for(let b=0;b<x.length;b++)E(u,x[b]);if(m){let b=m.subTree;if(f===b||zr(b.type)&&(b.ssContent===f||b.ssFallback===f)){const O=m.vnode;K(u,O,O.scopeId,O.slotScopeIds,m.parent)}}},_e=(u,f,h,x,m,b,O,A,C=0)=>{for(let y=C;y<u.length;y++){const R=u[y]=A?Ye(u[y]):He(u[y]);T(null,R,f,h,x,m,b,O,A)}},gt=(u,f,h,x,m,b,O)=>{const A=f.el=u.el;let{patchFlag:C,dynamicChildren:y,dirs:R}=f;C|=u.patchFlag&16;const M=u.props||Q,k=f.props||Q;let D;if(h&&ut(h,!1),(D=k.onVnodeBeforeUpdate)&&Re(D,h,f,u),R&&at(f,u,h,"beforeUpdate"),h&&ut(h,!0),(M.innerHTML&&k.innerHTML==null||M.textContent&&k.textContent==null)&&c(A,""),y?We(u.dynamicChildren,y,A,h,x,Ds(f,m),b):O||z(u,f,A,null,h,x,Ds(f,m),b,!1),C>0){if(C&16)it(A,M,k,h,m);else if(C&2&&M.class!==k.class&&o(A,"class",null,k.class,m),C&4&&o(A,"style",M.style,k.style,m),C&8){const W=f.dynamicProps;for(let Z=0;Z<W.length;Z++){const ee=W[Z],re=M[ee],ue=k[ee];(ue!==re||ee==="value")&&o(A,ee,re,ue,m,h)}}C&1&&u.children!==f.children&&c(A,f.children)}else!O&&y==null&&it(A,M,k,h,m);((D=k.onVnodeUpdated)||R)&&xe(()=>{D&&Re(D,h,f,u),R&&at(f,u,h,"updated")},x)},We=(u,f,h,x,m,b,O)=>{for(let A=0;A<f.length;A++){const C=u[A],y=f[A],R=C.el&&(C.type===be||!Rt(C,y)||C.shapeFlag&198)?g(C.el):h;T(C,y,R,null,x,m,b,O,!0)}},it=(u,f,h,x,m)=>{if(f!==h){if(f!==Q)for(const b in f)!Dt(b)&&!(b in h)&&o(u,b,f[b],null,m,x);for(const b in h){if(Dt(b))continue;const O=h[b],A=f[b];O!==A&&b!=="value"&&o(u,b,A,O,m,x)}"value"in h&&o(u,"value",f.value,h.value,m)}},mt=(u,f,h,x,m,b,O,A,C)=>{const y=f.el=u?u.el:l(""),R=f.anchor=u?u.anchor:l("");let{patchFlag:M,dynamicChildren:k,slotScopeIds:D}=f;D&&(A=A?A.concat(D):D),u==null?(n(y,h,x),n(R,h,x),_e(f.children||[],h,R,m,b,O,A,C)):M>0&&M&64&&k&&u.dynamicChildren&&u.dynamicChildren.length===k.length?(We(u.dynamicChildren,k,h,m,b,O,A),(f.key!=null||m&&f===m.subTree)&&Gr(u,f,!0)):z(u,f,h,R,m,b,O,A,C)},bt=(u,f,h,x,m,b,O,A,C)=>{f.slotScopeIds=A,u==null?f.shapeFlag&512?m.ctx.activate(f,h,x,O,C):Mt(f,h,x,m,b,O,C):es(u,f,C)},Mt=(u,f,h,x,m,b,O)=>{const A=u.component=Ji(u,x,m);if(kr(u)&&(A.ctx.renderer=nt),zi(A,!1,O),A.asyncDep){if(m&&m.registerDep(A,ae,O),!u.el){const C=A.subTree=Te(ot);U(null,C,f,h),u.placeholder=C.el}}else ae(A,u,f,h,m,b,O)},es=(u,f,h)=>{const x=f.component=u.component;if(Oi(u,f,h))if(x.asyncDep&&!x.asyncResolved){se(x,f,h);return}else x.next=f,x.update();else f.el=u.el,x.vnode=f},ae=(u,f,h,x,m,b,O)=>{const A=()=>{if(u.isMounted){let{next:M,bu:k,u:D,parent:W,vnode:Z}=u;{const ke=Jr(u);if(ke){M&&(M.el=Z.el,se(u,M,O)),ke.asyncDep.then(()=>{xe(()=>{u.isUnmounted||y()},m)});return}}let ee=M,re;ut(u,!1),M?(M.el=Z.el,se(u,M,O)):M=Z,k&&os(k),(re=M.props&&M.props.onVnodeBeforeUpdate)&&Re(re,W,M,Z),ut(u,!0);const ue=$n(u),$e=u.subTree;u.subTree=ue,T($e,ue,g($e.el),lt($e),u,m,b),M.el=ue.el,ee===null&&Ei(u,ue.el),D&&xe(D,m),(re=M.props&&M.props.onVnodeUpdated)&&xe(()=>Re(re,W,M,Z),m)}else{let M;const{el:k,props:D}=f,{bm:W,m:Z,parent:ee,root:re,type:ue}=u,$e=Vt(f);ut(u,!1),W&&os(W),!$e&&(M=D&&D.onVnodeBeforeMount)&&Re(M,ee,f),ut(u,!0);{re.ce&&re.ce._hasShadowRoot()&&re.ce._injectChildStyle(ue,u.parent?u.parent.type:void 0);const ke=u.subTree=$n(u);T(null,ke,h,x,u,m,b),f.el=ke.el}if(Z&&xe(Z,m),!$e&&(M=D&&D.onVnodeMounted)){const ke=f;xe(()=>Re(M,ee,ke),m)}(f.shapeFlag&256||ee&&Vt(ee.vnode)&&ee.vnode.shapeFlag&256)&&u.a&&xe(u.a,m),u.isMounted=!0,f=h=x=null}};u.scope.on();const C=u.effect=new ar(A);u.scope.off();const y=u.update=C.run.bind(C),R=u.job=C.runIfDirty.bind(C);R.i=u,R.id=u.uid,C.scheduler=()=>cn(R),ut(u,!0),y()},se=(u,f,h)=>{f.component=u;const x=u.vnode.props;u.vnode=f,u.next=null,$i(u,f.props,x,h),Fi(u,f.children,h),Ze(),Cn(u),Qe()},z=(u,f,h,x,m,b,O,A,C=!1)=>{const y=u&&u.children,R=u?u.shapeFlag:0,M=f.children,{patchFlag:k,shapeFlag:D}=f;if(k>0){if(k&128){$(y,M,h,x,m,b,O,A,C);return}else if(k&256){_(y,M,h,x,m,b,O,A,C);return}}D&8?(R&16&&st(y,m,b),M!==y&&c(h,M)):R&16?D&16?$(y,M,h,x,m,b,O,A,C):st(y,m,b,!0):(R&8&&c(h,""),D&16&&_e(M,h,x,m,b,O,A,C))},_=(u,f,h,x,m,b,O,A,C)=>{u=u||wt,f=f||wt;const y=u.length,R=f.length,M=Math.min(y,R);let k;for(k=0;k<M;k++){const D=f[k]=C?Ye(f[k]):He(f[k]);T(u[k],D,h,null,m,b,O,A,C)}y>R?st(u,m,b,!0,!1,M):_e(f,h,x,m,b,O,A,C,M)},$=(u,f,h,x,m,b,O,A,C)=>{let y=0;const R=f.length;let M=u.length-1,k=R-1;for(;y<=M&&y<=k;){const D=u[y],W=f[y]=C?Ye(f[y]):He(f[y]);if(Rt(D,W))T(D,W,h,null,m,b,O,A,C);else break;y++}for(;y<=M&&y<=k;){const D=u[M],W=f[k]=C?Ye(f[k]):He(f[k]);if(Rt(D,W))T(D,W,h,null,m,b,O,A,C);else break;M--,k--}if(y>M){if(y<=k){const D=k+1,W=D<R?f[D].el:x;for(;y<=k;)T(null,f[y]=C?Ye(f[y]):He(f[y]),h,W,m,b,O,A,C),y++}}else if(y>k)for(;y<=M;)fe(u[y],m,b,!0),y++;else{const D=y,W=y,Z=new Map;for(y=W;y<=k;y++){const Se=f[y]=C?Ye(f[y]):He(f[y]);Se.key!=null&&Z.set(Se.key,y)}let ee,re=0;const ue=k-W+1;let $e=!1,ke=0;const kt=new Array(ue);for(y=0;y<ue;y++)kt[y]=0;for(y=D;y<=M;y++){const Se=u[y];if(re>=ue){fe(Se,m,b,!0);continue}let Ie;if(Se.key!=null)Ie=Z.get(Se.key);else for(ee=W;ee<=k;ee++)if(kt[ee-W]===0&&Rt(Se,f[ee])){Ie=ee;break}Ie===void 0?fe(Se,m,b,!0):(kt[Ie-W]=y+1,Ie>=ke?ke=Ie:$e=!0,T(Se,f[Ie],h,null,m,b,O,A,C),re++)}const bn=$e?Li(kt):wt;for(ee=bn.length-1,y=ue-1;y>=0;y--){const Se=W+y,Ie=f[Se],yn=f[Se+1],xn=Se+1<R?yn.el||Yr(yn):x;kt[y]===0?T(null,Ie,h,xn,m,b,O,A,C):$e&&(ee<0||y!==bn[ee]?we(Ie,h,xn,2):ee--)}}},we=(u,f,h,x,m=null)=>{const{el:b,type:O,transition:A,children:C,shapeFlag:y}=u;if(y&6){we(u.component.subTree,f,h,x);return}if(y&128){u.suspense.move(f,h,x);return}if(y&64){O.move(u,f,h,nt);return}if(O===be){n(b,f,h);for(let M=0;M<C.length;M++)we(C[M],f,h,x);n(u.anchor,f,h);return}if(O===js){I(u,f,h);return}if(x!==2&&y&1&&A)if(x===0)A.beforeEnter(b),n(b,f,h),xe(()=>A.enter(b),m);else{const{leave:M,delayLeave:k,afterLeave:D}=A,W=()=>{u.ctx.isUnmounted?r(b):n(b,f,h)},Z=()=>{b._isLeaving&&b[si](!0),M(b,()=>{W(),D&&D()})};k?k(b,W,Z):Z()}else n(b,f,h)},fe=(u,f,h,x=!1,m=!1)=>{const{type:b,props:O,ref:A,children:C,dynamicChildren:y,shapeFlag:R,patchFlag:M,dirs:k,cacheIndex:D,memo:W}=u;if(M===-2&&(m=!1),A!=null&&(Ze(),Nt(A,null,h,u,!0),Qe()),D!=null&&(f.renderCache[D]=void 0),R&256){f.ctx.deactivate(u);return}const Z=R&1&&k,ee=!Vt(u);let re;if(ee&&(re=O&&O.onVnodeBeforeUnmount)&&Re(re,f,u),R&6)ts(u.component,h,x);else{if(R&128){u.suspense.unmount(h,x);return}Z&&at(u,null,f,"beforeUnmount"),R&64?u.type.remove(u,f,h,nt,x):y&&!y.hasOnce&&(b!==be||M>0&&M&64)?st(y,f,h,!1,!0):(b===be&&M&384||!m&&R&16)&&st(C,f,h),x&&Be(u)}const ue=W!=null&&D==null;(ee&&(re=O&&O.onVnodeUnmounted)||Z||ue)&&xe(()=>{re&&Re(re,f,u),Z&&at(u,null,f,"unmounted"),ue&&(u.el=null)},h)},Be=u=>{const{type:f,el:h,anchor:x,transition:m}=u;if(f===be){yt(h,x);return}if(f===js){w(u);return}const b=()=>{r(h),m&&!m.persisted&&m.afterLeave&&m.afterLeave()};if(u.shapeFlag&1&&m&&!m.persisted){const{leave:O,delayLeave:A}=m,C=()=>O(h,b);A?A(u.el,b,C):C()}else b()},yt=(u,f)=>{let h;for(;u!==f;)h=S(u),r(u),u=h;r(f)},ts=(u,f,h)=>{const{bum:x,scope:m,job:b,subTree:O,um:A,m:C,a:y}=u;Rn(C),Rn(y),x&&os(x),m.stop(),b&&(b.flags|=8,fe(O,u,f,h)),A&&xe(A,f),xe(()=>{u.isUnmounted=!0},f)},st=(u,f,h,x=!1,m=!1,b=0)=>{for(let O=b;O<u.length;O++)fe(u[O],f,h,x,m)},lt=u=>{if(u.shapeFlag&6)return lt(u.component.subTree);if(u.shapeFlag&128)return u.suspense.next();const f=S(u.anchor||u.el),h=f&&f[ei];return h?S(h):f};let xt=!1;const $t=(u,f,h)=>{let x;u==null?f._vnode&&(fe(f._vnode,null,null,!0),x=f._vnode.component):T(f._vnode||null,u,f,null,null,null,h),f._vnode=u,xt||(xt=!0,Cn(x),Ar(),xt=!1)},nt={p:T,um:fe,m:we,r:Be,mt:Mt,mc:_e,pc:z,pbc:We,n:lt,o:e};return{render:$t,hydrate:void 0,createApp:wi($t)}}function Ds({type:e,props:t},s){return s==="svg"&&e==="foreignObject"||s==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:s}function ut({effect:e,job:t},s){s?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Hi(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Gr(e,t,s=!1){const n=e.children,r=t.children;if(j(n)&&j(r))for(let o=0;o<n.length;o++){const i=n[o];let l=r[o];l.shapeFlag&1&&!l.dynamicChildren&&((l.patchFlag<=0||l.patchFlag===32)&&(l=r[o]=Ye(r[o]),l.el=i.el),!s&&l.patchFlag!==-2&&Gr(i,l)),l.type===Ps&&(l.patchFlag===-1&&(l=r[o]=Ye(l)),l.el=i.el),l.type===ot&&!l.el&&(l.el=i.el)}}function Li(e){const t=e.slice(),s=[0];let n,r,o,i,l;const a=e.length;for(n=0;n<a;n++){const d=e[n];if(d!==0){if(r=s[s.length-1],e[r]<d){t[n]=r,s.push(n);continue}for(o=0,i=s.length-1;o<i;)l=o+i>>1,e[s[l]]<d?o=l+1:i=l;d<e[s[o]]&&(o>0&&(t[n]=s[o-1]),s[o]=n)}}for(o=s.length,i=s[o-1];o-- >0;)s[o]=i,i=t[i];return s}function Jr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Jr(t)}function Rn(e){if(e)for(let t=0;t<e.length;t++)e[t].flags|=8}function Yr(e){if(e.placeholder)return e.placeholder;const t=e.component;return t?Yr(t.subTree):null}const zr=e=>e.__isSuspense;function Ni(e,t){t&&t.pendingBranch?j(e)?t.effects.push(...e):t.effects.push(e):Jo(e)}const be=Symbol.for("v-fgt"),Ps=Symbol.for("v-txt"),ot=Symbol.for("v-cmt"),js=Symbol.for("v-stc"),Kt=[];let Ce=null;function L(e=!1){Kt.push(Ce=e?null:[])}function Vi(){Kt.pop(),Ce=Kt[Kt.length-1]||null}let Gt=1;function Fn(e,t=!1){Gt+=e,e<0&&Ce&&t&&(Ce.hasOnce=!0)}function Xr(e){return e.dynamicChildren=Gt>0?Ce||wt:null,Vi(),Gt>0&&Ce&&Ce.push(e),e}function N(e,t,s,n,r,o){return Xr(p(e,t,s,n,r,o,!0))}function Ui(e,t,s,n,r){return Xr(Te(e,t,s,n,r,!0))}function Zr(e){return e?e.__v_isVNode===!0:!1}function Rt(e,t){return e.type===t.type&&e.key===t.key}const Qr=({key:e})=>e??null,ls=({ref:e,ref_key:t,ref_for:s})=>(typeof e=="number"&&(e=""+e),e!=null?oe(e)||pe(e)||V(e)?{i:Ae,r:e,k:t,f:!!s}:e:null);function p(e,t=null,s=null,n=0,r=null,o=e===be?0:1,i=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Qr(t),ref:t&&ls(t),scopeId:Or,slotScopeIds:null,children:s,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:n,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:Ae};return l?(mn(a,s),o&128&&e.normalize(a)):s&&(a.shapeFlag|=oe(s)?8:16),Gt>0&&!i&&Ce&&(a.patchFlag>0||o&6)&&a.patchFlag!==32&&Ce.push(a),a}const Te=Ki;function Ki(e,t=null,s=null,n=0,r=null,o=!1){if((!e||e===hi)&&(e=ot),Zr(e)){const l=Et(e,t,!0);return s&&mn(l,s),Gt>0&&!o&&Ce&&(l.shapeFlag&6?Ce[Ce.indexOf(e)]=l:Ce.push(l)),l.patchFlag=-2,l}if(el(e)&&(e=e.__vccOpts),t){t=Wi(t);let{class:l,style:a}=t;l&&!oe(l)&&(t.class=ce(l)),X(a)&&(un(a)&&!j(a)&&(a=he({},a)),t.style=en(a))}const i=oe(e)?1:zr(e)?128:ti(e)?64:X(e)?4:V(e)?2:0;return p(e,t,s,n,r,i,o,!0)}function Wi(e){return e?un(e)||Vr(e)?he({},e):e:null}function Et(e,t,s=!1,n=!1){const{props:r,ref:o,patchFlag:i,children:l,transition:a}=e,d=t?Bi(r||{},t):r,c={__v_isVNode:!0,__v_skip:!0,type:e.type,props:d,key:d&&Qr(d),ref:t&&t.ref?s&&o?j(o)?o.concat(ls(t)):[o,ls(t)]:ls(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==be?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Et(e.ssContent),ssFallback:e.ssFallback&&Et(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&n&&fn(c,a.clone(c)),c}function Zt(e=" ",t=0){return Te(Ps,null,e,t)}function le(e="",t=!1){return t?(L(),Ui(ot,null,e)):Te(ot,null,e)}function He(e){return e==null||typeof e=="boolean"?Te(ot):j(e)?Te(be,null,e.slice()):Zr(e)?Ye(e):Te(Ps,null,String(e))}function Ye(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Et(e)}function mn(e,t){let s=0;const{shapeFlag:n}=e;if(t==null)t=null;else if(j(t))s=16;else if(typeof t=="object")if(n&65){const r=t.default;r&&(r._c&&(r._d=!1),mn(e,r()),r._c&&(r._d=!0));return}else{s=32;const r=t._;!r&&!Vr(t)?t._ctx=Ae:r===3&&Ae&&(Ae.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else V(t)?(t={default:t,_ctx:Ae},s=32):(t=String(t),n&64?(s=16,t=[Zt(t)]):s=8);e.children=t,e.shapeFlag|=s}function Bi(...e){const t={};for(let s=0;s<e.length;s++){const n=e[s];for(const r in n)if(r==="class")t.class!==n.class&&(t.class=ce([t.class,n.class]));else if(r==="style")t.style=en([t.style,n.style]);else if(bs(r)){const o=t[r],i=n[r];i&&o!==i&&!(j(o)&&o.includes(i))?t[r]=o?[].concat(o,i):i:i==null&&o==null&&!ys(r)&&(t[r]=i)}else r!==""&&(t[r]=n[r])}return t}function Re(e,t,s,n=null){Ke(e,t,7,[s,n])}const qi=Dr();let Gi=0;function Ji(e,t,s){const n=e.type,r=(t?t.appContext:e.appContext)||qi,o={uid:Gi++,vnode:e,type:n,parent:t,appContext:r,root:null,next:null,subTree:null,effect:null,update:null,job:null,scope:new mo(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(r.provides),ids:t?t.ids:["",0,0],accessCache:null,renderCache:[],components:null,directives:null,propsOptions:Kr(n,r),emitsOptions:jr(n,r),emit:null,emitted:null,propsDefaults:Q,inheritAttrs:n.inheritAttrs,ctx:Q,data:Q,props:Q,attrs:Q,slots:Q,refs:Q,setupState:Q,setupContext:null,suspense:s,suspenseId:s?s.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return o.ctx={_:o},o.root=t?t.root:o,o.emit=Ci.bind(null,o),e.ce&&e.ce(o),o}let ye=null;const Yi=()=>ye||Ae;let hs,Ys;{const e=ws(),t=(s,n)=>{let r;return(r=e[s])||(r=e[s]=[]),r.push(n),o=>{r.length>1?r.forEach(i=>i(o)):r[0](o)}};hs=t("__VUE_INSTANCE_SETTERS__",s=>ye=s),Ys=t("__VUE_SSR_SETTERS__",s=>Jt=s)}const Qt=e=>{const t=ye;return hs(e),e.scope.on(),()=>{e.scope.off(),hs(t)}},Dn=()=>{ye&&ye.scope.off(),hs(null)};function eo(e){return e.vnode.shapeFlag&4}let Jt=!1;function zi(e,t=!1,s=!1){t&&Ys(t);const{props:n,children:r}=e.vnode,o=eo(e);Mi(e,n,o,t),Ri(e,r,s||t);const i=o?Xi(e,t):void 0;return t&&Ys(!1),i}function Xi(e,t){const s=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,gi);const{setup:n}=s;if(n){Ze();const r=e.setupContext=n.length>1?Qi(e):null,o=Qt(e),i=Xt(n,e,0,[e.props,r]),l=er(i);if(Qe(),o(),(l||e.sp)&&!Vt(e)&&$r(e),l){if(i.then(Dn,Dn),t)return i.then(a=>{jn(e,a)}).catch(a=>{Cs(a,e,0)});e.asyncDep=i}else jn(e,i)}else to(e)}function jn(e,t,s){V(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:X(t)&&(e.setupState=wr(t)),to(e)}function to(e,t,s){const n=e.type;e.render||(e.render=n.render||Ne);{const r=Qt(e);Ze();try{mi(e)}finally{Qe(),r()}}}const Zi={get(e,t){return de(e,"get",""),e[t]}};function Qi(e){const t=s=>{e.exposed=s||{}};return{attrs:new Proxy(e.attrs,Zi),slots:e.slots,emit:e.emit,expose:t}}function Os(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(wr(jo(e.exposed)),{get(t,s){if(s in t)return t[s];if(s in Ut)return Ut[s](e)},has(t,s){return s in t||s in Ut}})):e.proxy}function el(e){return V(e)&&"__vccOpts"in e}const Ve=(e,t)=>Ko(e,t,Jt),tl="3.5.32";/**
|
| 14 |
+
* @vue/runtime-dom v3.5.32
|
| 15 |
+
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
| 16 |
+
* @license MIT
|
| 17 |
+
**/let zs;const Hn=typeof window<"u"&&window.trustedTypes;if(Hn)try{zs=Hn.createPolicy("vue",{createHTML:e=>e})}catch{}const so=zs?e=>zs.createHTML(e):e=>e,sl="http://www.w3.org/2000/svg",nl="http://www.w3.org/1998/Math/MathML",Je=typeof document<"u"?document:null,Ln=Je&&Je.createElement("template"),rl={insert:(e,t,s)=>{t.insertBefore(e,s||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,s,n)=>{const r=t==="svg"?Je.createElementNS(sl,e):t==="mathml"?Je.createElementNS(nl,e):s?Je.createElement(e,{is:s}):Je.createElement(e);return e==="select"&&n&&n.multiple!=null&&r.setAttribute("multiple",n.multiple),r},createText:e=>Je.createTextNode(e),createComment:e=>Je.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Je.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,s,n,r,o){const i=s?s.previousSibling:t.lastChild;if(r&&(r===o||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),s),!(r===o||!(r=r.nextSibling)););else{Ln.innerHTML=so(n==="svg"?`<svg>${e}</svg>`:n==="mathml"?`<math>${e}</math>`:e);const l=Ln.content;if(n==="svg"||n==="mathml"){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,s)}return[i?i.nextSibling:t.firstChild,s?s.previousSibling:t.lastChild]}},ol=Symbol("_vtc");function il(e,t,s){const n=e[ol];n&&(t=(t?[t,...n]:[...n]).join(" ")),t==null?e.removeAttribute("class"):s?e.setAttribute("class",t):e.className=t}const Nn=Symbol("_vod"),ll=Symbol("_vsh"),al=Symbol(""),ul=/(?:^|;)\s*display\s*:/;function cl(e,t,s){const n=e.style,r=oe(s);let o=!1;if(s&&!r){if(t)if(oe(t))for(const i of t.split(";")){const l=i.slice(0,i.indexOf(":")).trim();s[l]==null&&as(n,l,"")}else for(const i in t)s[i]==null&&as(n,i,"");for(const i in s)i==="display"&&(o=!0),as(n,i,s[i])}else if(r){if(t!==s){const i=n[al];i&&(s+=";"+i),n.cssText=s,o=ul.test(s)}}else t&&e.removeAttribute("style");Nn in e&&(e[Nn]=o?n.display:"",e[ll]&&(n.display="none"))}const Vn=/\s*!important$/;function as(e,t,s){if(j(s))s.forEach(n=>as(e,t,n));else if(s==null&&(s=""),t.startsWith("--"))e.setProperty(t,s);else{const n=fl(e,t);Vn.test(s)?e.setProperty(ht(n),s.replace(Vn,""),"important"):e[n]=s}}const Un=["Webkit","Moz","ms"],Hs={};function fl(e,t){const s=Hs[t];if(s)return s;let n=Oe(t);if(n!=="filter"&&n in e)return Hs[t]=n;n=nr(n);for(let r=0;r<Un.length;r++){const o=Un[r]+n;if(o in e)return Hs[t]=o}return t}const Kn="http://www.w3.org/1999/xlink";function Wn(e,t,s,n,r,o=po(t)){n&&t.startsWith("xlink:")?s==null?e.removeAttributeNS(Kn,t.slice(6,t.length)):e.setAttributeNS(Kn,t,s):s==null||o&&!or(s)?e.removeAttribute(t):e.setAttribute(t,o?"":Ue(s)?String(s):s)}function Bn(e,t,s,n,r){if(t==="innerHTML"||t==="textContent"){s!=null&&(e[t]=t==="innerHTML"?so(s):s);return}const o=e.tagName;if(t==="value"&&o!=="PROGRESS"&&!o.includes("-")){const l=o==="OPTION"?e.getAttribute("value")||"":e.value,a=s==null?e.type==="checkbox"?"on":"":String(s);(l!==a||!("_value"in e))&&(e.value=a),s==null&&e.removeAttribute(t),e._value=s;return}let i=!1;if(s===""||s==null){const l=typeof e[t];l==="boolean"?s=or(s):s==null&&l==="string"?(s="",i=!0):l==="number"&&(s=0,i=!0)}try{e[t]=s}catch{}i&&e.removeAttribute(r||t)}function ft(e,t,s,n){e.addEventListener(t,s,n)}function dl(e,t,s,n){e.removeEventListener(t,s,n)}const qn=Symbol("_vei");function pl(e,t,s,n,r=null){const o=e[qn]||(e[qn]={}),i=o[t];if(n&&i)i.value=n;else{const[l,a]=hl(t);if(n){const d=o[t]=bl(n,r);ft(e,l,d,a)}else i&&(dl(e,l,i,a),o[t]=void 0)}}const Gn=/(?:Once|Passive|Capture)$/;function hl(e){let t;if(Gn.test(e)){t={};let n;for(;n=e.match(Gn);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[e[2]===":"?e.slice(3):ht(e.slice(2)),t]}let Ls=0;const gl=Promise.resolve(),ml=()=>Ls||(gl.then(()=>Ls=0),Ls=Date.now());function bl(e,t){const s=n=>{if(!n._vts)n._vts=Date.now();else if(n._vts<=s.attached)return;Ke(yl(n,s.value),t,5,[n])};return s.value=e,s.attached=ml(),s}function yl(e,t){if(j(t)){const s=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{s.call(e),e._stopped=!0},t.map(n=>r=>!r._stopped&&n&&n(r))}else return t}const Jn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,xl=(e,t,s,n,r,o)=>{const i=r==="svg";t==="class"?il(e,n,i):t==="style"?cl(e,s,n):bs(t)?ys(t)||pl(e,t,s,n,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):vl(e,t,n,i))?(Bn(e,t,n),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Wn(e,t,n,i,o,t!=="value")):e._isVueCE&&(_l(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!oe(n)))?Bn(e,Oe(t),n,o,t):(t==="true-value"?e._trueValue=n:t==="false-value"&&(e._falseValue=n),Wn(e,t,n,i))};function vl(e,t,s,n){if(n)return!!(t==="innerHTML"||t==="textContent"||t in e&&Jn(t)&&V(s));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Jn(t)&&oe(s)?!1:t in e}function _l(e,t){const s=e._def.props;if(!s)return!1;const n=Oe(t);return Array.isArray(s)?s.some(r=>Oe(r)===n):Object.keys(s).some(r=>Oe(r)===n)}const gs=e=>{const t=e.props["onUpdate:modelValue"]||!1;return j(t)?s=>os(t,s):t};function wl(e){e.target.composing=!0}function Yn(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Pt=Symbol("_assign");function zn(e,t,s){return t&&(e=e.trim()),s&&(e=_s(e)),e}const Ge={created(e,{modifiers:{lazy:t,trim:s,number:n}},r){e[Pt]=gs(r);const o=n||r.props&&r.props.type==="number";ft(e,t?"change":"input",i=>{i.target.composing||e[Pt](zn(e.value,s,o))}),(s||o)&&ft(e,"change",()=>{e.value=zn(e.value,s,o)}),t||(ft(e,"compositionstart",wl),ft(e,"compositionend",Yn),ft(e,"change",Yn))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:s,modifiers:{lazy:n,trim:r,number:o}},i){if(e[Pt]=gs(i),e.composing)return;const l=(o||e.type==="number")&&!/^0\d/.test(e.value)?_s(e.value):e.value,a=t??"";if(l===a)return;const d=e.getRootNode();(d instanceof Document||d instanceof ShadowRoot)&&d.activeElement===e&&e.type!=="range"&&(n&&t===s||r&&e.value.trim()===a)||(e.value=a)}},Sl={deep:!0,created(e,{value:t,modifiers:{number:s}},n){const r=xs(t);ft(e,"change",()=>{const o=Array.prototype.filter.call(e.options,i=>i.selected).map(i=>s?_s(ms(i)):ms(i));e[Pt](e.multiple?r?new Set(o):o:o[0]),e._assigning=!0,Cr(()=>{e._assigning=!1})}),e[Pt]=gs(n)},mounted(e,{value:t}){Xn(e,t)},beforeUpdate(e,t,s){e[Pt]=gs(s)},updated(e,{value:t}){e._assigning||Xn(e,t)}};function Xn(e,t){const s=e.multiple,n=j(t);if(!(s&&!n&&!xs(t))){for(let r=0,o=e.options.length;r<o;r++){const i=e.options[r],l=ms(i);if(s)if(n){const a=typeof l;a==="string"||a==="number"?i.selected=t.some(d=>String(d)===String(l)):i.selected=go(t,l)>-1}else i.selected=t.has(l);else if(zt(ms(i),t)){e.selectedIndex!==r&&(e.selectedIndex=r);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function ms(e){return"_value"in e?e._value:e.value}const Cl=he({patchProp:xl},rl);let Zn;function Tl(){return Zn||(Zn=Di(Cl))}const Al=((...e)=>{const t=Tl().createApp(...e),{mount:s}=t;return t.mount=n=>{const r=Ol(n);if(!r)return;const o=t._component;!V(o)&&!o.render&&!o.template&&(o.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const i=s(r,!1,Pl(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),i},t});function Pl(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function Ol(e){return oe(e)?document.querySelector(e):e}const El="/api";async function Y(e,t,s=null){var i;const n={method:e,headers:{"Content-Type":"application/json"}};s&&(n.body=JSON.stringify(s));const r=await fetch(`${El}${t}`,n),o=await r.json();if(!r.ok){const l=((i=o==null?void 0:o.detail)==null?void 0:i.message)||(o==null?void 0:o.detail)||`HTTP ${r.status}`;throw new Error(l)}return o}const ie={getStatus:()=>Y("GET","/status"),getAdminStatus:()=>Y("GET","/admin/status"),getMainCodexStatus:()=>Y("GET","/main-codex/status"),getAccounts:()=>Y("GET","/accounts"),getActiveAccounts:()=>Y("GET","/accounts/active"),getStandbyAccounts:()=>Y("GET","/accounts/standby"),deleteAccount:e=>Y("DELETE",`/accounts/${encodeURIComponent(e)}`),getCpaFiles:()=>Y("GET","/cpa/files"),startAdminLogin:e=>Y("POST","/admin/login/start",{email:e}),submitAdminPassword:e=>Y("POST","/admin/login/password",{password:e}),submitAdminCode:e=>Y("POST","/admin/login/code",{code:e}),submitAdminWorkspace:e=>Y("POST","/admin/login/workspace",{option_id:e}),cancelAdminLogin:()=>Y("POST","/admin/login/cancel"),logoutAdmin:()=>Y("POST","/admin/logout"),startMainCodexSync:()=>Y("POST","/main-codex/start"),submitMainCodexPassword:e=>Y("POST","/main-codex/password",{password:e}),submitMainCodexCode:e=>Y("POST","/main-codex/code",{code:e}),cancelMainCodexSync:()=>Y("POST","/main-codex/cancel"),postSync:()=>Y("POST","/sync"),postSyncMainCodex:()=>Y("POST","/sync/main-codex"),startRotate:(e=5)=>Y("POST","/tasks/rotate",{target:e}),startCheck:()=>Y("POST","/tasks/check"),startAdd:()=>Y("POST","/tasks/add"),startFill:(e=5)=>Y("POST","/tasks/fill",{target:e}),startCleanup:(e=null)=>Y("POST","/tasks/cleanup",{max_seats:e}),getTasks:()=>Y("GET","/tasks"),getTask:e=>Y("GET",`/tasks/${e}`),getAutoCheckConfig:()=>Y("GET","/config/auto-check"),setAutoCheckConfig:e=>Y("PUT","/config/auto-check",e)},Ml={key:0},$l={class:"grid grid-cols-2 sm:grid-cols-4 gap-4 mb-6"},kl={class:"text-sm text-gray-400"},Il={class:"bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"},Rl={key:1,class:"mx-4 mt-4 px-4 py-3 rounded-lg text-sm border bg-amber-500/10 text-amber-300 border-amber-500/20"},Fl={class:"overflow-x-auto"},Dl={class:"w-full text-sm"},jl={class:"px-4 py-3 text-gray-500"},Hl={class:"px-4 py-3 font-mono text-xs"},Ll={class:"px-4 py-3"},Nl={class:"px-4 py-3 text-gray-400 text-xs"},Vl={class:"px-4 py-3 text-gray-400 text-xs"},Ul={class:"px-4 py-3 text-right"},Kl=["onClick","disabled"],Wl={key:1,class:"space-y-4"},Bl={class:"grid grid-cols-2 sm:grid-cols-4 gap-4"},ql={__name:"Dashboard",props:{status:Object,loading:Boolean,runningTask:Object,adminStatus:{type:Object,default:null}},emits:["refresh"],setup(e,{emit:t}){const s=e,n=t,r=q(""),o=q(""),i=q(""),l=Ve(()=>{var v;return!!((v=s.adminStatus)!=null&&v.configured)}),a=Ve(()=>!!s.runningTask||!l.value),d=Ve(()=>{if(!s.status)return[];const v=s.status.summary;return[{label:"活跃",value:v.active,color:"text-green-400"},{label:"待命",value:v.standby,color:"text-yellow-400"},{label:"额度用完",value:v.exhausted,color:"text-red-400"},{label:"总计",value:v.total,color:"text-white"}]});function c(v){return{active:"bg-green-500/10 text-green-400",exhausted:"bg-red-500/10 text-red-400",standby:"bg-yellow-500/10 text-yellow-400",pending:"bg-gray-500/10 text-gray-400"}[v]||"bg-gray-500/10 text-gray-400"}function g(v){return{active:"bg-green-400",exhausted:"bg-red-400",standby:"bg-yellow-400",pending:"bg-gray-400"}[v]||"bg-gray-400"}function S(v){return{active:"Active",exhausted:"Used up",standby:"Standby",pending:"Pending"}[v]||v}function E(v,I){var ne,K;const w=((K=(ne=s.status)==null?void 0:ne.quota_cache)==null?void 0:K[v.email])||v.last_quota;return w?100-((I==="primary"?w.primary_pct:w.weekly_pct)||0):null}function P(v,I){const w=E(v,I);return w!==null?`${w}%`:"-"}function T(v,I){var K,_e;const w=((_e=(K=s.status)==null?void 0:K.quota_cache)==null?void 0:_e[v.email])||v.last_quota;if(!w)return"-";const B=I==="primary"?w.primary_resets_at:w.weekly_resets_at;if(!B)return"-";const ne=new Date(B*1e3);return`${String(ne.getMonth()+1).padStart(2,"0")}-${String(ne.getDate()).padStart(2,"0")} ${String(ne.getHours()).padStart(2,"0")}:${String(ne.getMinutes()).padStart(2,"0")}`}function H(v){return v===null?"text-gray-500":v>30?"text-green-400":v>0?"text-yellow-400":"text-red-400"}async function U(v){if(!(a.value||!window.confirm(`确认删除账号 ${v}?
|
| 18 |
+
这会同时清理本地记录、CPA、Team/Invite 和 CloudMail。`))){r.value=v,o.value="";try{const w=await ie.deleteAccount(v);o.value=w.message||`已删除 ${v}`,i.value="bg-green-500/10 text-green-400 border-green-500/20",n("refresh")}catch(w){o.value=w.message,i.value="bg-red-500/10 text-red-400 border-red-500/20"}finally{r.value="",setTimeout(()=>{o.value=""},8e3)}}}return(v,I)=>e.status?(L(),N("div",Ml,[p("div",$l,[(L(!0),N(be,null,Tt(d.value,w=>(L(),N("div",{key:w.label,class:"bg-gray-900 border border-gray-800 rounded-xl p-4"},[p("div",kl,F(w.label),1),p("div",{class:ce(["text-3xl font-bold mt-1",w.color])},F(w.value),3)]))),128))]),p("div",Il,[I[1]||(I[1]=p("div",{class:"px-4 py-3 border-b border-gray-800"},[p("h2",{class:"text-lg font-semibold text-white"},"账号列表")],-1)),o.value?(L(),N("div",{key:0,class:ce(["mx-4 mt-4 px-4 py-3 rounded-lg text-sm border",i.value])},F(o.value),3)):le("",!0),l.value?le("",!0):(L(),N("div",Rl," 未完成管理员登录,删除账号按钮已禁用。 ")),p("div",Fl,[p("table",Dl,[I[0]||(I[0]=p("thead",null,[p("tr",{class:"text-gray-400 text-left border-b border-gray-800"},[p("th",{class:"px-4 py-3 font-medium"},"#"),p("th",{class:"px-4 py-3 font-medium"},"邮箱"),p("th",{class:"px-4 py-3 font-medium"},"状态"),p("th",{class:"px-4 py-3 font-medium text-right"},"5h 剩余"),p("th",{class:"px-4 py-3 font-medium text-right"},"周 剩余"),p("th",{class:"px-4 py-3 font-medium"},"5h 重置"),p("th",{class:"px-4 py-3 font-medium"},"周 重置"),p("th",{class:"px-4 py-3 font-medium text-right"},"操作")])],-1)),p("tbody",null,[(L(!0),N(be,null,Tt(e.status.accounts,(w,B)=>(L(),N("tr",{key:w.email,class:"border-b border-gray-800/50 hover:bg-gray-800/30 transition"},[p("td",jl,F(B+1),1),p("td",Hl,F(w.email),1),p("td",Ll,[p("span",{class:ce(["inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium",c(w.status)])},[p("span",{class:ce(["w-1.5 h-1.5 rounded-full",g(w.status)])},null,2),Zt(" "+F(S(w.status)),1)],2)]),p("td",{class:ce(["px-4 py-3 text-right font-mono",H(E(w,"primary"))])},F(P(w,"primary")),3),p("td",{class:ce(["px-4 py-3 text-right font-mono",H(E(w,"weekly"))])},F(P(w,"weekly")),3),p("td",Nl,F(T(w,"primary")),1),p("td",Vl,F(T(w,"weekly")),1),p("td",Ul,[p("button",{onClick:ne=>U(w.email),disabled:a.value||r.value===w.email,class:ce(["px-3 py-1.5 rounded-lg text-xs font-medium border transition",a.value||r.value===w.email?"bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed":"bg-rose-600/10 text-rose-400 border-rose-500/30 hover:bg-rose-600/20"])},F(r.value===w.email?"删除中...":"删除"),11,Kl)])]))),128))])])])])])):e.loading?(L(),N("div",Wl,[p("div",Bl,[(L(),N(be,null,Tt(4,w=>p("div",{key:w,class:"bg-gray-900 border border-gray-800 rounded-xl p-4 h-20 animate-pulse"})),64))]),I[2]||(I[2]=p("div",{class:"bg-gray-900 border border-gray-800 rounded-xl h-64 animate-pulse"},null,-1))])):le("",!0)}},Gl={class:"mt-6 bg-gray-900 border border-gray-800 rounded-xl p-4"},Jl={key:0,class:"mb-4 px-4 py-3 rounded-lg text-sm border bg-amber-500/10 text-amber-300 border-amber-500/20"},Yl={class:"flex flex-wrap gap-3"},zl=["onClick","disabled"],Xl={key:1,class:"mt-4 flex items-center gap-3"},Zl={class:"text-sm text-gray-400"},Ql=["disabled"],ea={__name:"TaskPanel",props:{runningTask:Object,adminStatus:{type:Object,default:null}},emits:["task-started","refresh"],setup(e,{emit:t}){const s=e,n=t,r=[{key:"rotate",label:"智能轮转",method:"startRotate",needParam:!0,paramName:"target",style:"bg-blue-600 text-white border-blue-500"},{key:"check",label:"检查额度",method:"startCheck",needParam:!1,style:"bg-emerald-600 text-white border-emerald-500"},{key:"fill",label:"补满成员",method:"startFill",needParam:!0,paramName:"target",style:"bg-violet-600 text-white border-violet-500"},{key:"add",label:"添加账号",method:"startAdd",needParam:!1,style:"bg-amber-600 text-white border-amber-500"},{key:"cleanup",label:"清理成员",method:"startCleanup",needParam:!1,style:"bg-rose-600 text-white border-rose-500"},{key:"sync",label:"同步 CPA",method:"postSync",needParam:!1,sync:!0,style:"bg-gray-700 text-white border-gray-600"}],o=q(!1),i=q(""),l=q(5),a=q(null),d=q(""),c=q(""),g=Ve(()=>{var H;return!!((H=s.adminStatus)!=null&&H.configured)}),S=Ve(()=>!!s.runningTask||!g.value);async function E(H){if(!S.value){if(d.value="",H.needParam){a.value=H,i.value=H.paramName==="target"?"目标成员数":"最大席位",l.value=5,o.value=!0;return}await T(H)}}async function P(){o.value=!1,a.value&&(await T(a.value,l.value),a.value=null)}async function T(H,U){try{if(H.sync){const v=await ie[H.method]();d.value=v.message||"操作完成",c.value="bg-green-500/10 text-green-400 border border-green-500/20",n("refresh")}else{const v=await ie[H.method](U);d.value=`任务已提交: ${v.task_id}`,c.value="bg-blue-500/10 text-blue-400 border border-blue-500/20",n("task-started")}}catch(v){d.value=v.message,c.value="bg-red-500/10 text-red-400 border border-red-500/20"}setTimeout(()=>{d.value=""},8e3)}return(H,U)=>(L(),N("div",Gl,[U[2]||(U[2]=p("h2",{class:"text-lg font-semibold text-white mb-4"},"操作",-1)),g.value?le("",!0):(L(),N("div",Jl," 需要先在下方“管理员登录”里完成主号登录,管理操作才会开放。 ")),p("div",Yl,[(L(),N(be,null,Tt(r,v=>p("button",{key:v.key,onClick:I=>E(v),disabled:S.value,class:ce(["px-4 py-2 rounded-lg text-sm font-medium transition border",S.value?"bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed":`${v.style} hover:opacity-80`])},F(v.label),11,zl)),64))]),o.value?(L(),N("div",Xl,[p("label",Zl,F(i.value)+":",1),Fe(p("input",{"onUpdate:modelValue":U[0]||(U[0]=v=>l.value=v),type:"number",min:"1",max:"20",class:"w-20 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[Ge,l.value,void 0,{number:!0}]]),p("button",{onClick:P,disabled:S.value,class:"px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition"}," 确认执行 ",8,Ql),p("button",{onClick:U[1]||(U[1]=v=>o.value=!1),class:"px-3 py-1.5 text-gray-400 hover:text-white text-sm transition"}," 取消 ")])):le("",!0),d.value?(L(),N("div",{key:2,class:ce(["mt-4 px-4 py-3 rounded-lg text-sm",c.value])},F(d.value),3)):le("",!0)]))}},ta={class:"mt-6 bg-gray-900 border border-gray-800 rounded-xl overflow-hidden"},sa={key:0,class:"px-4 py-8 text-center text-gray-500 text-sm"},na={key:1,class:"overflow-x-auto"},ra={class:"w-full text-sm"},oa={class:"px-4 py-3 font-mono text-xs text-gray-400"},ia={class:"px-4 py-3"},la={class:"px-2 py-0.5 bg-gray-800 rounded text-xs font-medium text-gray-300"},aa={class:"px-4 py-3 text-xs text-gray-400"},ua={class:"px-4 py-3"},ca={key:0,class:"animate-spin inline-block w-3 h-3 border-2 border-current border-t-transparent rounded-full"},fa={class:"px-4 py-3 text-xs text-gray-400"},da={class:"px-4 py-3 text-xs text-gray-400"},pa={__name:"TaskHistory",props:{tasks:{type:Array,default:()=>[]}},setup(e){function t(a){return{pending:"text-gray-400",running:"text-yellow-400",completed:"text-green-400",failed:"text-red-400"}[a]||"text-gray-400"}function s(a){return{pending:"bg-gray-400",completed:"bg-green-400",failed:"bg-red-400"}[a]||"bg-gray-400"}function n(a){return{pending:"等待中",running:"执行中",completed:"已完成",failed:"失败"}[a]||a}function r(a){if(!a)return"-";const d=new Date(a*1e3);return`${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")} ${String(d.getHours()).padStart(2,"0")}:${String(d.getMinutes()).padStart(2,"0")}:${String(d.getSeconds()).padStart(2,"0")}`}function o(a){const d=a.started_at||a.created_at,c=a.finished_at||(a.status==="running"?Date.now()/1e3:null);if(!d||!c)return"-";const g=Math.round(c-d);return g<60?`${g}s`:`${Math.floor(g/60)}m ${g%60}s`}function i(a){return!a||Object.keys(a).length===0?"-":Object.entries(a).map(([d,c])=>`${d}=${c}`).join(", ")}function l(a){return a==null?"-":typeof a=="string"?a:JSON.stringify(a)}return(a,d)=>(L(),N("div",ta,[d[1]||(d[1]=p("div",{class:"px-4 py-3 border-b border-gray-800"},[p("h2",{class:"text-lg font-semibold text-white"},"任务历史")],-1)),e.tasks.length===0?(L(),N("div",sa," 暂无任务记录 ")):(L(),N("div",na,[p("table",ra,[d[0]||(d[0]=p("thead",null,[p("tr",{class:"text-gray-400 text-left border-b border-gray-800"},[p("th",{class:"px-4 py-3 font-medium"},"任务 ID"),p("th",{class:"px-4 py-3 font-medium"},"命令"),p("th",{class:"px-4 py-3 font-medium"},"参数"),p("th",{class:"px-4 py-3 font-medium"},"状态"),p("th",{class:"px-4 py-3 font-medium"},"创建时间"),p("th",{class:"px-4 py-3 font-medium"},"耗时"),p("th",{class:"px-4 py-3 font-medium"},"结果")])],-1)),p("tbody",null,[(L(!0),N(be,null,Tt(e.tasks,c=>(L(),N("tr",{key:c.task_id,class:"border-b border-gray-800/50 hover:bg-gray-800/30 transition"},[p("td",oa,F(c.task_id),1),p("td",ia,[p("span",la,F(c.command),1)]),p("td",aa,F(i(c.params)),1),p("td",ua,[p("span",{class:ce(["inline-flex items-center gap-1.5 text-xs font-medium",t(c.status)])},[c.status==="running"?(L(),N("span",ca)):(L(),N("span",{key:1,class:ce(["w-1.5 h-1.5 rounded-full",s(c.status)])},null,2)),Zt(" "+F(n(c.status)),1)],2)]),p("td",fa,F(r(c.created_at)),1),p("td",da,F(o(c)),1),p("td",{class:ce(["px-4 py-3 text-xs max-w-xs truncate",c.error?"text-red-400":"text-gray-400"])},F(c.error||l(c.result)),3)]))),128))])])]))]))}},ha={class:"mt-6 space-y-6"},ga={class:"bg-gray-900 border border-gray-800 rounded-xl p-4"},ma={class:"flex items-center justify-between gap-4 mb-4"},ba={key:1,class:"grid grid-cols-1 md:grid-cols-2 gap-3 text-sm"},ya={class:"px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg"},xa={class:"font-mono text-white break-all"},va={class:"px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg"},_a={class:"font-mono text-white break-all"},wa={class:"px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg md:col-span-2"},Sa={class:"text-white"},Ca={class:"px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg md:col-span-2"},Ta={class:"text-white"},Aa={key:2,class:"mt-4"},Pa={key:0,class:"flex flex-col sm:flex-row gap-3"},Oa=["disabled"],Ea={key:1,class:"flex flex-wrap gap-3"},Ma=["disabled"],$a=["disabled"],ka={key:3,class:"space-y-4"},Ia={class:"text-sm text-gray-300"},Ra={class:"font-mono"},Fa={key:0,class:"flex flex-col sm:flex-row gap-3"},Da=["disabled"],ja=["disabled"],Ha={key:1,class:"flex flex-col sm:flex-row gap-3"},La=["disabled"],Na=["disabled"],Va={key:2,class:"space-y-3"},Ua=["disabled"],Ka=["value"],Wa=["disabled"],Ba={key:3,class:"text-xs text-blue-300"},qa={class:"flex justify-end"},Ga=["disabled"],Ja={key:4,class:"mt-4 space-y-4 border-t border-gray-800 pt-4"},Ya={key:0,class:"flex flex-col sm:flex-row gap-3"},za=["disabled"],Xa=["disabled"],Za={key:1,class:"flex flex-col sm:flex-row gap-3"},Qa=["disabled"],eu=["disabled"],tu={key:2,class:"text-xs text-cyan-300"},su={class:"flex justify-end"},nu=["disabled"],ru={class:"bg-gray-900 border border-gray-800 rounded-xl p-4"},ou={class:"flex items-center justify-between mb-4"},iu={key:0,class:"text-xs text-green-400 transition"},lu={class:"grid grid-cols-1 sm:grid-cols-3 gap-4"},au={class:"flex items-center gap-2"},uu={class:"flex items-center gap-2"},cu={class:"flex items-center gap-2"},fu={class:"mt-3 flex items-center justify-between gap-3"},du={class:"text-xs text-gray-500"},pu=["disabled"],hu={__name:"Settings",props:{adminStatus:{type:Object,default:null},codexStatus:{type:Object,default:null}},emits:["refresh","admin-progress"],setup(e,{emit:t}){const s=e,n=t,r=q({interval:5,threshold:10,min_low:2}),o=q(!1),i=q(!1),l=q(""),a=q(""),d=q(""),c=q(""),g=q(""),S=q(""),E=q(""),P=q(!1),T=q(!1),H=q(""),U=q(""),v=q(""),I=q(""),w=Ve(()=>{var _;return!!((_=s.adminStatus)!=null&&_.configured)}),B=Ve(()=>{var _;return!!((_=s.adminStatus)!=null&&_.login_in_progress)}),ne=Ve(()=>{var _;return!!((_=s.codexStatus)!=null&&_.in_progress)});Lt(()=>s.adminStatus,_=>{var $,we,fe;if(_!=null&&_.configured&&_.email&&(l.value=_.email),_!=null&&_.login_in_progress||(a.value="",d.value="",c.value="",v.value="",g.value=(_==null?void 0:_.email)||g.value),(_==null?void 0:_.login_step)==="workspace_required"&&!c.value){const Be=($=_==null?void 0:_.workspace_options)==null?void 0:$.find(yt=>yt.kind==="preferred");c.value=(Be==null?void 0:Be.id)||((fe=(we=_==null?void 0:_.workspace_options)==null?void 0:we[0])==null?void 0:fe.id)||""}},{immediate:!0}),Lt(()=>s.codexStatus,_=>{_!=null&&_.in_progress||(S.value="",E.value="",I.value="")},{immediate:!0}),dn(async()=>{try{const _=await ie.getAutoCheckConfig();r.value={interval:Math.round(_.interval/60),threshold:_.threshold,min_low:_.min_low}}catch(_){console.error("加载巡检配置失败:",_)}});function K(_,$="success"){H.value=_,U.value=$==="success"?"bg-green-500/10 text-green-400 border-green-500/20":"bg-red-500/10 text-red-400 border-red-500/20",window.clearTimeout(K._timer),K._timer=window.setTimeout(()=>{H.value=""},8e3)}async function _e(){P.value=!0,v.value="正在打开管理员登录页...";try{g.value=l.value;const _=await ie.startAdminLogin(l.value);K(_.status==="completed"?"管理员登录完成":"已进入下一步登录流程"),n("admin-progress")}catch(_){K(_.message,"error")}finally{P.value=!1,v.value=""}}async function gt(){P.value=!0,v.value="密码已提交,正在等待登录页响应...";try{const _=await ie.submitAdminPassword(a.value);K(_.status==="completed"?"管理员登录完成":"���码已提交,请继续下一步"),n("admin-progress")}catch(_){K(_.message,"error")}finally{P.value=!1,v.value=""}}async function We(){P.value=!0,v.value="验证码已提交,正在等待登录页响应,通常需要 5 到 10 秒...";try{const _=await ie.submitAdminCode(d.value);K(_.status==="completed"?"管理员登录完成":"验证码已提交,请继续下一步"),n("admin-progress")}catch(_){K(_.message,"error")}finally{P.value=!1,v.value=""}}async function it(){P.value=!0,v.value="组织选择已提交,正在等待登录页响应...";try{const _=await ie.submitAdminWorkspace(c.value);K(_.status==="completed"?"管理员登录完成":"组织选择已提交,请继续下一步"),n("admin-progress")}catch(_){K(_.message,"error")}finally{P.value=!1,v.value=""}}async function mt(){P.value=!0;try{await ie.cancelAdminLogin(),a.value="",d.value="",K("管理员登录已取消"),n("refresh")}catch(_){K(_.message,"error")}finally{P.value=!1}}async function bt(){P.value=!0;try{await ie.logoutAdmin(),a.value="",d.value="",K("管理员登录态已清除"),n("refresh")}catch(_){K(_.message,"error")}finally{P.value=!1}}async function Mt(){T.value=!0,I.value="正在打开主号 Codex 登录页...";try{const _=await ie.startMainCodexSync();K(_.status==="completed"?_.message||"主号 Codex 已同步":"主号 Codex 登录进入下一步"),n("admin-progress")}catch(_){K(_.message,"error")}finally{T.value=!1,I.value=""}}async function es(){T.value=!0,I.value="密码已提交,正在等待主号 Codex 登录页响应...";try{const _=await ie.submitMainCodexPassword(S.value);K(_.status==="completed"?_.message||"主号 Codex 已同步":"主号 Codex 密码已提交"),n("admin-progress")}catch(_){K(_.message,"error")}finally{T.value=!1,I.value=""}}async function ae(){T.value=!0,I.value="验证码已提交,正在等待主号 Codex 登录页响应,通常需要 5 到 10 秒...";try{const _=await ie.submitMainCodexCode(E.value);K(_.status==="completed"?_.message||"主号 Codex 已同步":"主号 Codex 验证码已提交"),n("admin-progress")}catch(_){K(_.message,"error")}finally{T.value=!1,I.value=""}}async function se(){T.value=!0;try{await ie.cancelMainCodexSync(),K("主号 Codex 登录已取消"),n("refresh")}catch(_){K(_.message,"error")}finally{T.value=!1}}async function z(){o.value=!0,i.value=!1;try{const _=await ie.setAutoCheckConfig({interval:r.value.interval*60,threshold:r.value.threshold,min_low:r.value.min_low});r.value={interval:Math.round(_.interval/60),threshold:_.threshold,min_low:_.min_low},i.value=!0,setTimeout(()=>{i.value=!1},3e3)}catch(_){console.error("保存失败:",_)}finally{o.value=!1}}return(_,$)=>{var we,fe,Be,yt,ts,st,lt,xt,$t,nt,Es;return L(),N("div",ha,[p("div",ga,[p("div",ma,[$[9]||($[9]=p("div",null,[p("h2",{class:"text-lg font-semibold text-white"},"管理员登录"),p("p",{class:"text-sm text-gray-400 mt-1"}," 首次启动先在这里完成主号登录,系统会统一写入单个 state.json 文件,保存邮箱、session、workspace ID、workspace 名称;如果你走了密码登录,也会保留密码供主号 Codex 复用。 ")],-1)),p("span",{class:ce(["min-w-[72px] px-3 py-1.5 rounded-full text-xs text-center whitespace-nowrap border",w.value?"bg-green-500/10 text-green-400 border-green-500/20":B.value?"bg-yellow-500/10 text-yellow-300 border-yellow-500/20":"bg-gray-800 text-gray-400 border-gray-700"])},F(w.value?"已配置":B.value?"登录中":"未配置"),3)]),H.value?(L(),N("div",{key:0,class:ce(["mb-4 px-4 py-3 rounded-lg text-sm border",U.value])},F(H.value),3)):le("",!0),w.value&&!B.value?(L(),N("div",ba,[p("div",ya,[$[10]||($[10]=p("div",{class:"text-gray-500 mb-1"},"管理员邮箱",-1)),p("div",xa,F(((we=s.adminStatus)==null?void 0:we.email)||"-"),1)]),p("div",va,[$[11]||($[11]=p("div",{class:"text-gray-500 mb-1"},"Workspace ID",-1)),p("div",_a,F(((fe=s.adminStatus)==null?void 0:fe.account_id)||"-"),1)]),p("div",wa,[$[12]||($[12]=p("div",{class:"text-gray-500 mb-1"},"Workspace 名称",-1)),p("div",Sa,F(((Be=s.adminStatus)==null?void 0:Be.workspace_name)||"未识别"),1)]),p("div",Ca,[$[13]||($[13]=p("div",{class:"text-gray-500 mb-1"},"管理员密码",-1)),p("div",Ta,F((yt=s.adminStatus)!=null&&yt.password_saved?"已保存,可用于主号 Codex 登录":"未保存"),1)])])):le("",!0),B.value?le("",!0):(L(),N("div",Aa,[w.value?ne.value?le("",!0):(L(),N("div",Ea,[p("button",{onClick:Mt,disabled:P.value||T.value,class:"px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"},F(T.value?"同步中...":"同步主号 Codex 到 CPA"),9,Ma),p("button",{onClick:bt,disabled:P.value||T.value,class:"px-4 py-2 bg-rose-700/80 hover:bg-rose-700 text-white text-sm rounded-lg transition disabled:opacity-50"},F(P.value?"处理中...":"清除登录态"),9,$a)])):(L(),N("div",Pa,[Fe(p("input",{"onUpdate:modelValue":$[0]||($[0]=u=>l.value=u),type:"email",autocomplete:"username",placeholder:"输入主号邮箱",class:"flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[Ge,l.value,void 0,{trim:!0}]]),p("button",{onClick:_e,disabled:P.value||!l.value,class:"px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"},F(P.value?"提交中...":"开始登录"),9,Oa)]))])),B.value?(L(),N("div",ka,[p("div",Ia,[$[14]||($[14]=Zt(" 当前邮箱: ",-1)),p("span",Ra,F(g.value||((ts=s.adminStatus)==null?void 0:ts.email)||"-"),1)]),((st=s.adminStatus)==null?void 0:st.login_step)==="password_required"?(L(),N("div",Fa,[Fe(p("input",{"onUpdate:modelValue":$[1]||($[1]=u=>a.value=u),type:"password",autocomplete:"current-password",placeholder:"输入主号密码",disabled:P.value,class:"flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,8,Da),[[Ge,a.value]]),p("button",{onClick:gt,disabled:P.value||!a.value,class:"px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"},F(P.value?"提交中...":"提交密码"),9,ja)])):((lt=s.adminStatus)==null?void 0:lt.login_step)==="code_required"?(L(),N("div",Ha,[Fe(p("input",{"onUpdate:modelValue":$[2]||($[2]=u=>d.value=u),type:"text",inputmode:"numeric",autocomplete:"one-time-code",placeholder:"输入邮箱验证码",disabled:P.value,class:"flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,8,La),[[Ge,d.value,void 0,{trim:!0}]]),p("button",{onClick:We,disabled:P.value||!d.value,class:"px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50 disabled:bg-gray-700 disabled:hover:bg-gray-700"},F(P.value?"提交中...":"提交验证码"),9,Na)])):((xt=s.adminStatus)==null?void 0:xt.login_step)==="workspace_required"?(L(),N("div",Va,[$[16]||($[16]=p("div",{class:"text-sm text-gray-300"}," 请选择要进入的组织 / workspace ",-1)),Fe(p("select",{"onUpdate:modelValue":$[3]||($[3]=u=>c.value=u),disabled:P.value,class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},[$[15]||($[15]=p("option",{disabled:"",value:""},"请选择组织",-1)),(L(!0),N(be,null,Tt((($t=s.adminStatus)==null?void 0:$t.workspace_options)||[],u=>(L(),N("option",{key:u.id,value:u.id},F(u.label)+F(u.kind==="fallback"?" (可能是个人/免费)":""),9,Ka))),128))],8,Ua),[[Sl,c.value]]),p("button",{onClick:it,disabled:P.value||!c.value,class:"px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50 disabled:bg-gray-700 disabled:hover:bg-gray-700"},F(P.value?"提交中...":"确认组织选择"),9,Wa)])):le("",!0),P.value&&v.value?(L(),N("div",Ba,F(v.value),1)):le("",!0),p("div",qa,[p("button",{onClick:mt,disabled:P.value,class:"px-4 py-2 bg-gray-800 hover:bg-gray-700 text-sm text-gray-200 rounded-lg border border-gray-700 transition disabled:opacity-50"}," 取消登录 ",8,Ga)])])):le("",!0),ne.value?(L(),N("div",Ja,[$[17]||($[17]=p("div",{class:"text-sm text-gray-300"}," 主号 Codex 登录继续中 ",-1)),((nt=s.codexStatus)==null?void 0:nt.step)==="password_required"?(L(),N("div",Ya,[Fe(p("input",{"onUpdate:modelValue":$[4]||($[4]=u=>S.value=u),type:"password",autocomplete:"current-password",placeholder:"输入主号密码",disabled:T.value,class:"flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,8,za),[[Ge,S.value]]),p("button",{onClick:es,disabled:T.value||!S.value,class:"px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"},F(T.value?"提交中...":"提交密码"),9,Xa)])):((Es=s.codexStatus)==null?void 0:Es.step)==="code_required"?(L(),N("div",Za,[Fe(p("input",{"onUpdate:modelValue":$[5]||($[5]=u=>E.value=u),type:"text",inputmode:"numeric",autocomplete:"one-time-code",placeholder:"输入主号 Codex 验证码",disabled:T.value,class:"flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,8,Qa),[[Ge,E.value,void 0,{trim:!0}]]),p("button",{onClick:ae,disabled:T.value||!E.value,class:"px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"},F(T.value?"提交中...":"提交验证码"),9,eu)])):le("",!0),T.value&&I.value?(L(),N("div",tu,F(I.value),1)):le("",!0),p("div",su,[p("button",{onClick:se,disabled:T.value,class:"px-4 py-2 bg-gray-800 hover:bg-gray-700 text-sm text-gray-200 rounded-lg border border-gray-700 transition disabled:opacity-50"}," 取消主号 Codex 登录 ",8,nu)])])):le("",!0)]),p("div",ru,[p("div",ou,[$[18]||($[18]=p("h2",{class:"text-lg font-semibold text-white"},"巡检设置",-1)),i.value?(L(),N("span",iu,"已保存")):le("",!0)]),p("div",lu,[p("div",null,[$[20]||($[20]=p("label",{class:"block text-sm text-gray-400 mb-1"},"巡检间隔",-1)),p("div",au,[Fe(p("input",{"onUpdate:modelValue":$[6]||($[6]=u=>r.value.interval=u),type:"number",min:"1",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[Ge,r.value.interval,void 0,{number:!0}]]),$[19]||($[19]=p("span",{class:"text-sm text-gray-500 shrink-0"},"分钟",-1))])]),p("div",null,[$[22]||($[22]=p("label",{class:"block text-sm text-gray-400 mb-1"},"额度阈值",-1)),p("div",uu,[Fe(p("input",{"onUpdate:modelValue":$[7]||($[7]=u=>r.value.threshold=u),type:"number",min:"1",max:"100",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[Ge,r.value.threshold,void 0,{number:!0}]]),$[21]||($[21]=p("span",{class:"text-sm text-gray-500 shrink-0"},"%",-1))])]),p("div",null,[$[24]||($[24]=p("label",{class:"block text-sm text-gray-400 mb-1"},"触发账号数",-1)),p("div",cu,[Fe(p("input",{"onUpdate:modelValue":$[8]||($[8]=u=>r.value.min_low=u),type:"number",min:"1",class:"w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"},null,512),[[Ge,r.value.min_low,void 0,{number:!0}]]),$[23]||($[23]=p("span",{class:"text-sm text-gray-500 shrink-0"},"个",-1))])])]),p("div",fu,[p("p",du," 每 "+F(r.value.interval)+" 分钟检查一次,"+F(r.value.min_low)+" 个以上账号剩余低于 "+F(r.value.threshold)+"% 时自动轮转 ",1),p("button",{onClick:z,disabled:o.value,class:"px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"},F(o.value?"保存中...":"保存"),9,pu)])])])}}},gu={class:"max-w-7xl mx-auto px-4 py-6"},mu={class:"flex items-center justify-between mb-8"},bu={class:"flex items-center gap-3"},yu={key:0,class:"flex items-center gap-2 text-sm text-yellow-400"},xu=["disabled"],vu={__name:"App",setup(e){const t=q(null),s=q(null),n=q(null),r=q([]),o=q(!1),i=q(null),l=Ve(()=>{var P,T;return(P=s.value)!=null&&P.login_in_progress?{command:"admin-login"}:(T=n.value)!=null&&T.in_progress?{command:"main-codex-sync"}:i.value});let a=null;async function d(){o.value=!0;try{const[P,T,H,U]=await Promise.all([ie.getStatus(),ie.getTasks(),ie.getAdminStatus(),ie.getMainCodexStatus()]);t.value=P,r.value=T,s.value=H,n.value=U,i.value=T.find(v=>v.status==="running"||v.status==="pending")||null}catch(P){console.error("刷新失败:",P)}finally{o.value=!1}}function c(){S(1e4),d()}function g(){S(1e4),d()}function S(P=6e5){E(),a=setInterval(async()=>{await d(),!l.value&&P<6e5&&S(6e5)},P)}function E(){a&&(clearInterval(a),a=null)}return dn(()=>{d(),S(6e5)}),pn(()=>{E()}),(P,T)=>(L(),N("div",gu,[p("header",mu,[T[1]||(T[1]=p("div",null,[p("h1",{class:"text-2xl font-bold text-white"},"AutoTeam"),p("p",{class:"text-sm text-gray-400 mt-1"},"ChatGPT Team 账号自动轮转管理")],-1)),p("div",bu,[l.value?(L(),N("span",yu,[T[0]||(T[0]=p("span",{class:"animate-spin inline-block w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full"},null,-1)),Zt(" "+F(l.value.command==="admin-login"?"管理员登录中...":l.value.command==="main-codex-sync"?"主号 Codex 同步中...":`${l.value.command} 执行中...`),1)])):le("",!0),p("button",{onClick:d,disabled:o.value,class:"px-3 py-1.5 bg-gray-800 hover:bg-gray-700 text-sm rounded-lg border border-gray-700 transition disabled:opacity-50"},F(o.value?"刷新中...":"刷新"),9,xu)])]),Te(ql,{status:t.value,loading:o.value,"running-task":l.value,"admin-status":s.value,onRefresh:d},null,8,["status","loading","running-task","admin-status"]),Te(ea,{"running-task":l.value,"admin-status":s.value,onTaskStarted:c,onRefresh:d},null,8,["running-task","admin-status"]),Te(pa,{tasks:r.value},null,8,["tasks"]),Te(hu,{"admin-status":s.value,"codex-status":n.value,onRefresh:d,onAdminProgress:g},null,8,["admin-status","codex-status"])]))}};Al(vu).mount("#app");
|
src/autoteam/web/dist/assets/{index-CNWPOsK5.css → index-DD5HUIdi.css}
RENAMED
|
@@ -1 +1 @@
|
|
| 1 |
-
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-1\.5{height:.375rem}.h-20{height:5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-64{height:16rem}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-20{width:5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-full{width:100%}.max-w-7xl{max-width:80rem}.max-w-xs{max-width:20rem}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-amber-500{--tw-border-opacity: 1;border-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-500\/20{border-color:#3b82f633}.border-current{border-color:currentColor}.border-emerald-500{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-gray-800\/50{border-color:#1f293780}.border-green-500\/20{border-color:#22c55e33}.border-red-500\/20{border-color:#ef444433}.border-rose-500{--tw-border-opacity: 1;border-color:rgb(244 63 94 / var(--tw-border-opacity, 1))}.border-violet-500{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity, 1))}.border-yellow-400{--tw-border-opacity: 1;border-color:rgb(250 204 21 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-500\/10{background-color:#6b72801a}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity, 1))}.bg-violet-600{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.hover\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800\/30:hover{background-color:#1f29374d}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:opacity-80:hover{opacity:.8}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:640px){.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|
|
|
|
| 1 |
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mt-1{margin-top:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-1\.5{height:.375rem}.h-20{height:5rem}.h-3{height:.75rem}.h-4{height:1rem}.h-64{height:16rem}.min-h-screen{min-height:100vh}.w-1\.5{width:.375rem}.w-20{width:5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-full{width:100%}.min-w-\[72px\]{min-width:72px}.max-w-7xl{max-width:80rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-not-allowed{cursor:not-allowed}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-between{justify-content:space-between}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-amber-500{--tw-border-opacity: 1;border-color:rgb(245 158 11 / var(--tw-border-opacity, 1))}.border-amber-500\/20{border-color:#f59e0b33}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-500\/20{border-color:#3b82f633}.border-current{border-color:currentColor}.border-emerald-500{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.border-gray-600{--tw-border-opacity: 1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-gray-700{--tw-border-opacity: 1;border-color:rgb(55 65 81 / var(--tw-border-opacity, 1))}.border-gray-800{--tw-border-opacity: 1;border-color:rgb(31 41 55 / var(--tw-border-opacity, 1))}.border-gray-800\/50{border-color:#1f293780}.border-green-500\/20{border-color:#22c55e33}.border-red-500\/20{border-color:#ef444433}.border-rose-500{--tw-border-opacity: 1;border-color:rgb(244 63 94 / var(--tw-border-opacity, 1))}.border-rose-500\/30{border-color:#f43f5e4d}.border-violet-500{--tw-border-opacity: 1;border-color:rgb(139 92 246 / var(--tw-border-opacity, 1))}.border-yellow-400{--tw-border-opacity: 1;border-color:rgb(250 204 21 / var(--tw-border-opacity, 1))}.border-yellow-500\/20{border-color:#eab30833}.border-t-transparent{border-top-color:transparent}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-600{--tw-bg-opacity: 1;background-color:rgb(217 119 6 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-600{--tw-bg-opacity: 1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-cyan-700{--tw-bg-opacity: 1;background-color:rgb(14 116 144 / var(--tw-bg-opacity, 1))}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity, 1))}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-500\/10{background-color:#6b72801a}.bg-gray-700{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(31 41 55 / var(--tw-bg-opacity, 1))}.bg-gray-800\/60{background-color:#1f293799}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(17 24 39 / var(--tw-bg-opacity, 1))}.bg-gray-950{--tw-bg-opacity: 1;background-color:rgb(3 7 18 / var(--tw-bg-opacity, 1))}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-rose-600{--tw-bg-opacity: 1;background-color:rgb(225 29 72 / var(--tw-bg-opacity, 1))}.bg-rose-600\/10{background-color:#e11d481a}.bg-rose-700\/80{background-color:#be123ccc}.bg-violet-600{--tw-bg-opacity: 1;background-color:rgb(124 58 237 / var(--tw-bg-opacity, 1))}.bg-yellow-400{--tw-bg-opacity: 1;background-color:rgb(250 204 21 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-blue-300{--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-cyan-300{--tw-text-opacity: 1;color:rgb(103 232 249 / var(--tw-text-opacity, 1))}.text-gray-100{--tw-text-opacity: 1;color:rgb(243 244 246 / var(--tw-text-opacity, 1))}.text-gray-200{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity: 1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-300{--tw-text-opacity: 1;color:rgb(253 224 71 / var(--tw-text-opacity, 1))}.text-yellow-400{--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif}.hover\:bg-blue-500:hover{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.hover\:bg-cyan-600:hover{--tw-bg-opacity: 1;background-color:rgb(8 145 178 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-700:hover{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.hover\:bg-gray-800\/30:hover{background-color:#1f29374d}.hover\:bg-rose-600\/20:hover{background-color:#e11d4833}.hover\:bg-rose-700:hover{--tw-bg-opacity: 1;background-color:rgb(190 18 60 / var(--tw-bg-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:opacity-80:hover{opacity:.8}.focus\:border-blue-500:focus{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:bg-gray-700:disabled{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:hover\:bg-gray-700:hover:disabled{--tw-bg-opacity: 1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}@media(min-width:640px){.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}}@media(min-width:768px){.md\:col-span-2{grid-column:span 2 / span 2}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
src/autoteam/web/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>AutoTeam</title>
|
| 7 |
-
<script type="module" crossorigin src="/assets/index-
|
| 8 |
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
| 9 |
</head>
|
| 10 |
<body class="bg-gray-950 text-gray-100 min-h-screen">
|
| 11 |
<div id="app"></div>
|
|
|
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
<title>AutoTeam</title>
|
| 7 |
+
<script type="module" crossorigin src="/assets/index-CqIsKPRy.js"></script>
|
| 8 |
+
<link rel="stylesheet" crossorigin href="/assets/index-DD5HUIdi.css">
|
| 9 |
</head>
|
| 10 |
<body class="bg-gray-950 text-gray-100 min-h-screen">
|
| 11 |
<div id="app"></div>
|
web/src/App.vue
CHANGED
|
@@ -7,9 +7,13 @@
|
|
| 7 |
<p class="text-sm text-gray-400 mt-1">ChatGPT Team 账号自动轮转管理</p>
|
| 8 |
</div>
|
| 9 |
<div class="flex items-center gap-3">
|
| 10 |
-
<span v-if="
|
| 11 |
<span class="animate-spin inline-block w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full"></span>
|
| 12 |
-
{{
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
</span>
|
| 14 |
<button @click="refresh" :disabled="loading"
|
| 15 |
class="px-3 py-1.5 bg-gray-800 hover:bg-gray-700 text-sm rounded-lg border border-gray-700 transition disabled:opacity-50">
|
|
@@ -19,21 +23,21 @@
|
|
| 19 |
</header>
|
| 20 |
|
| 21 |
<!-- Dashboard -->
|
| 22 |
-
<Dashboard :status="status" :loading="loading" />
|
| 23 |
|
| 24 |
<!-- Task Panel -->
|
| 25 |
-
<TaskPanel :running-task="
|
| 26 |
|
| 27 |
<!-- Task History -->
|
| 28 |
<TaskHistory :tasks="tasks" />
|
| 29 |
|
| 30 |
<!-- Settings -->
|
| 31 |
-
<Settings />
|
| 32 |
</div>
|
| 33 |
</template>
|
| 34 |
|
| 35 |
<script setup>
|
| 36 |
-
import { ref, onMounted, onUnmounted } from 'vue'
|
| 37 |
import { api } from './api.js'
|
| 38 |
import Dashboard from './components/Dashboard.vue'
|
| 39 |
import TaskPanel from './components/TaskPanel.vue'
|
|
@@ -41,18 +45,36 @@ import TaskHistory from './components/TaskHistory.vue'
|
|
| 41 |
import Settings from './components/Settings.vue'
|
| 42 |
|
| 43 |
const status = ref(null)
|
|
|
|
|
|
|
| 44 |
const tasks = ref([])
|
| 45 |
const loading = ref(false)
|
| 46 |
const runningTask = ref(null)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
let pollTimer = null
|
| 49 |
|
| 50 |
async function refresh() {
|
| 51 |
loading.value = true
|
| 52 |
try {
|
| 53 |
-
const [s, t] = await Promise.all([
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
status.value = s
|
| 55 |
tasks.value = t
|
|
|
|
|
|
|
| 56 |
runningTask.value = t.find(t => t.status === 'running' || t.status === 'pending') || null
|
| 57 |
} catch (e) {
|
| 58 |
console.error('刷新失败:', e)
|
|
@@ -67,12 +89,17 @@ function onTaskStarted() {
|
|
| 67 |
refresh()
|
| 68 |
}
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
function startPolling(interval = 600000) {
|
| 71 |
stopPolling()
|
| 72 |
pollTimer = setInterval(async () => {
|
| 73 |
await refresh()
|
| 74 |
// 如果没有运行中的任务,降回慢轮询
|
| 75 |
-
if (!
|
| 76 |
startPolling(600000)
|
| 77 |
}
|
| 78 |
}, interval)
|
|
|
|
| 7 |
<p class="text-sm text-gray-400 mt-1">ChatGPT Team 账号自动轮转管理</p>
|
| 8 |
</div>
|
| 9 |
<div class="flex items-center gap-3">
|
| 10 |
+
<span v-if="busyTask" class="flex items-center gap-2 text-sm text-yellow-400">
|
| 11 |
<span class="animate-spin inline-block w-4 h-4 border-2 border-yellow-400 border-t-transparent rounded-full"></span>
|
| 12 |
+
{{ busyTask.command === 'admin-login'
|
| 13 |
+
? '管理员登录中...'
|
| 14 |
+
: busyTask.command === 'main-codex-sync'
|
| 15 |
+
? '主号 Codex 同步中...'
|
| 16 |
+
: `${busyTask.command} 执行中...` }}
|
| 17 |
</span>
|
| 18 |
<button @click="refresh" :disabled="loading"
|
| 19 |
class="px-3 py-1.5 bg-gray-800 hover:bg-gray-700 text-sm rounded-lg border border-gray-700 transition disabled:opacity-50">
|
|
|
|
| 23 |
</header>
|
| 24 |
|
| 25 |
<!-- Dashboard -->
|
| 26 |
+
<Dashboard :status="status" :loading="loading" :running-task="busyTask" :admin-status="adminStatus" @refresh="refresh" />
|
| 27 |
|
| 28 |
<!-- Task Panel -->
|
| 29 |
+
<TaskPanel :running-task="busyTask" :admin-status="adminStatus" @task-started="onTaskStarted" @refresh="refresh" />
|
| 30 |
|
| 31 |
<!-- Task History -->
|
| 32 |
<TaskHistory :tasks="tasks" />
|
| 33 |
|
| 34 |
<!-- Settings -->
|
| 35 |
+
<Settings :admin-status="adminStatus" :codex-status="codexStatus" @refresh="refresh" @admin-progress="onAdminProgress" />
|
| 36 |
</div>
|
| 37 |
</template>
|
| 38 |
|
| 39 |
<script setup>
|
| 40 |
+
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
| 41 |
import { api } from './api.js'
|
| 42 |
import Dashboard from './components/Dashboard.vue'
|
| 43 |
import TaskPanel from './components/TaskPanel.vue'
|
|
|
|
| 45 |
import Settings from './components/Settings.vue'
|
| 46 |
|
| 47 |
const status = ref(null)
|
| 48 |
+
const adminStatus = ref(null)
|
| 49 |
+
const codexStatus = ref(null)
|
| 50 |
const tasks = ref([])
|
| 51 |
const loading = ref(false)
|
| 52 |
const runningTask = ref(null)
|
| 53 |
+
const busyTask = computed(() => {
|
| 54 |
+
if (adminStatus.value?.login_in_progress) {
|
| 55 |
+
return { command: 'admin-login' }
|
| 56 |
+
}
|
| 57 |
+
if (codexStatus.value?.in_progress) {
|
| 58 |
+
return { command: 'main-codex-sync' }
|
| 59 |
+
}
|
| 60 |
+
return runningTask.value
|
| 61 |
+
})
|
| 62 |
|
| 63 |
let pollTimer = null
|
| 64 |
|
| 65 |
async function refresh() {
|
| 66 |
loading.value = true
|
| 67 |
try {
|
| 68 |
+
const [s, t, admin, codex] = await Promise.all([
|
| 69 |
+
api.getStatus(),
|
| 70 |
+
api.getTasks(),
|
| 71 |
+
api.getAdminStatus(),
|
| 72 |
+
api.getMainCodexStatus(),
|
| 73 |
+
])
|
| 74 |
status.value = s
|
| 75 |
tasks.value = t
|
| 76 |
+
adminStatus.value = admin
|
| 77 |
+
codexStatus.value = codex
|
| 78 |
runningTask.value = t.find(t => t.status === 'running' || t.status === 'pending') || null
|
| 79 |
} catch (e) {
|
| 80 |
console.error('刷新失败:', e)
|
|
|
|
| 89 |
refresh()
|
| 90 |
}
|
| 91 |
|
| 92 |
+
function onAdminProgress() {
|
| 93 |
+
startPolling(10000)
|
| 94 |
+
refresh()
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
function startPolling(interval = 600000) {
|
| 98 |
stopPolling()
|
| 99 |
pollTimer = setInterval(async () => {
|
| 100 |
await refresh()
|
| 101 |
// 如果没有运行中的任务,降回慢轮询
|
| 102 |
+
if (!busyTask.value && interval < 600000) {
|
| 103 |
startPolling(600000)
|
| 104 |
}
|
| 105 |
}, interval)
|
web/src/api.js
CHANGED
|
@@ -17,12 +17,27 @@ async function request(method, path, body = null) {
|
|
| 17 |
|
| 18 |
export const api = {
|
| 19 |
getStatus: () => request('GET', '/status'),
|
|
|
|
|
|
|
| 20 |
getAccounts: () => request('GET', '/accounts'),
|
| 21 |
getActiveAccounts: () => request('GET', '/accounts/active'),
|
| 22 |
getStandbyAccounts: () => request('GET', '/accounts/standby'),
|
|
|
|
| 23 |
getCpaFiles: () => request('GET', '/cpa/files'),
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
postSync: () => request('POST', '/sync'),
|
|
|
|
| 26 |
|
| 27 |
startRotate: (target = 5) => request('POST', '/tasks/rotate', { target }),
|
| 28 |
startCheck: () => request('POST', '/tasks/check'),
|
|
|
|
| 17 |
|
| 18 |
export const api = {
|
| 19 |
getStatus: () => request('GET', '/status'),
|
| 20 |
+
getAdminStatus: () => request('GET', '/admin/status'),
|
| 21 |
+
getMainCodexStatus: () => request('GET', '/main-codex/status'),
|
| 22 |
getAccounts: () => request('GET', '/accounts'),
|
| 23 |
getActiveAccounts: () => request('GET', '/accounts/active'),
|
| 24 |
getStandbyAccounts: () => request('GET', '/accounts/standby'),
|
| 25 |
+
deleteAccount: (email) => request('DELETE', `/accounts/${encodeURIComponent(email)}`),
|
| 26 |
getCpaFiles: () => request('GET', '/cpa/files'),
|
| 27 |
|
| 28 |
+
startAdminLogin: (email) => request('POST', '/admin/login/start', { email }),
|
| 29 |
+
submitAdminPassword: (password) => request('POST', '/admin/login/password', { password }),
|
| 30 |
+
submitAdminCode: (code) => request('POST', '/admin/login/code', { code }),
|
| 31 |
+
submitAdminWorkspace: (optionId) => request('POST', '/admin/login/workspace', { option_id: optionId }),
|
| 32 |
+
cancelAdminLogin: () => request('POST', '/admin/login/cancel'),
|
| 33 |
+
logoutAdmin: () => request('POST', '/admin/logout'),
|
| 34 |
+
startMainCodexSync: () => request('POST', '/main-codex/start'),
|
| 35 |
+
submitMainCodexPassword: (password) => request('POST', '/main-codex/password', { password }),
|
| 36 |
+
submitMainCodexCode: (code) => request('POST', '/main-codex/code', { code }),
|
| 37 |
+
cancelMainCodexSync: () => request('POST', '/main-codex/cancel'),
|
| 38 |
+
|
| 39 |
postSync: () => request('POST', '/sync'),
|
| 40 |
+
postSyncMainCodex: () => request('POST', '/sync/main-codex'),
|
| 41 |
|
| 42 |
startRotate: (target = 5) => request('POST', '/tasks/rotate', { target }),
|
| 43 |
startCheck: () => request('POST', '/tasks/check'),
|
web/src/components/Dashboard.vue
CHANGED
|
@@ -14,6 +14,12 @@
|
|
| 14 |
<div class="px-4 py-3 border-b border-gray-800">
|
| 15 |
<h2 class="text-lg font-semibold text-white">账号列表</h2>
|
| 16 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
<div class="overflow-x-auto">
|
| 18 |
<table class="w-full text-sm">
|
| 19 |
<thead>
|
|
@@ -25,6 +31,7 @@
|
|
| 25 |
<th class="px-4 py-3 font-medium text-right">周 剩余</th>
|
| 26 |
<th class="px-4 py-3 font-medium">5h 重置</th>
|
| 27 |
<th class="px-4 py-3 font-medium">周 重置</th>
|
|
|
|
| 28 |
</tr>
|
| 29 |
</thead>
|
| 30 |
<tbody>
|
|
@@ -47,6 +54,17 @@
|
|
| 47 |
</td>
|
| 48 |
<td class="px-4 py-3 text-gray-400 text-xs">{{ quotaReset(acc, 'primary') }}</td>
|
| 49 |
<td class="px-4 py-3 text-gray-400 text-xs">{{ quotaReset(acc, 'weekly') }}</td>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
</tr>
|
| 51 |
</tbody>
|
| 52 |
</table>
|
|
@@ -64,12 +82,25 @@
|
|
| 64 |
</template>
|
| 65 |
|
| 66 |
<script setup>
|
| 67 |
-
import { computed } from 'vue'
|
|
|
|
| 68 |
|
| 69 |
const props = defineProps({
|
| 70 |
status: Object,
|
| 71 |
loading: Boolean,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
const cards = computed(() => {
|
| 75 |
if (!props.status) return []
|
|
@@ -131,4 +162,28 @@ function pctColor(val) {
|
|
| 131 |
if (val > 0) return 'text-yellow-400'
|
| 132 |
return 'text-red-400'
|
| 133 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
</script>
|
|
|
|
| 14 |
<div class="px-4 py-3 border-b border-gray-800">
|
| 15 |
<h2 class="text-lg font-semibold text-white">账号列表</h2>
|
| 16 |
</div>
|
| 17 |
+
<div v-if="message" class="mx-4 mt-4 px-4 py-3 rounded-lg text-sm border" :class="messageClass">
|
| 18 |
+
{{ message }}
|
| 19 |
+
</div>
|
| 20 |
+
<div v-if="!adminReady" class="mx-4 mt-4 px-4 py-3 rounded-lg text-sm border bg-amber-500/10 text-amber-300 border-amber-500/20">
|
| 21 |
+
未完成管理员登录,删除账号按钮已禁用。
|
| 22 |
+
</div>
|
| 23 |
<div class="overflow-x-auto">
|
| 24 |
<table class="w-full text-sm">
|
| 25 |
<thead>
|
|
|
|
| 31 |
<th class="px-4 py-3 font-medium text-right">周 剩余</th>
|
| 32 |
<th class="px-4 py-3 font-medium">5h 重置</th>
|
| 33 |
<th class="px-4 py-3 font-medium">周 重置</th>
|
| 34 |
+
<th class="px-4 py-3 font-medium text-right">操作</th>
|
| 35 |
</tr>
|
| 36 |
</thead>
|
| 37 |
<tbody>
|
|
|
|
| 54 |
</td>
|
| 55 |
<td class="px-4 py-3 text-gray-400 text-xs">{{ quotaReset(acc, 'primary') }}</td>
|
| 56 |
<td class="px-4 py-3 text-gray-400 text-xs">{{ quotaReset(acc, 'weekly') }}</td>
|
| 57 |
+
<td class="px-4 py-3 text-right">
|
| 58 |
+
<button
|
| 59 |
+
@click="removeAccount(acc.email)"
|
| 60 |
+
:disabled="deleteDisabled || deletingEmail === acc.email"
|
| 61 |
+
class="px-3 py-1.5 rounded-lg text-xs font-medium border transition"
|
| 62 |
+
:class="deleteDisabled || deletingEmail === acc.email
|
| 63 |
+
? 'bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed'
|
| 64 |
+
: 'bg-rose-600/10 text-rose-400 border-rose-500/30 hover:bg-rose-600/20'">
|
| 65 |
+
{{ deletingEmail === acc.email ? '删除中...' : '删除' }}
|
| 66 |
+
</button>
|
| 67 |
+
</td>
|
| 68 |
</tr>
|
| 69 |
</tbody>
|
| 70 |
</table>
|
|
|
|
| 82 |
</template>
|
| 83 |
|
| 84 |
<script setup>
|
| 85 |
+
import { computed, ref } from 'vue'
|
| 86 |
+
import { api } from '../api.js'
|
| 87 |
|
| 88 |
const props = defineProps({
|
| 89 |
status: Object,
|
| 90 |
loading: Boolean,
|
| 91 |
+
runningTask: Object,
|
| 92 |
+
adminStatus: {
|
| 93 |
+
type: Object,
|
| 94 |
+
default: null,
|
| 95 |
+
},
|
| 96 |
})
|
| 97 |
+
const emit = defineEmits(['refresh'])
|
| 98 |
+
|
| 99 |
+
const deletingEmail = ref('')
|
| 100 |
+
const message = ref('')
|
| 101 |
+
const messageClass = ref('')
|
| 102 |
+
const adminReady = computed(() => !!props.adminStatus?.configured)
|
| 103 |
+
const deleteDisabled = computed(() => !!props.runningTask || !adminReady.value)
|
| 104 |
|
| 105 |
const cards = computed(() => {
|
| 106 |
if (!props.status) return []
|
|
|
|
| 162 |
if (val > 0) return 'text-yellow-400'
|
| 163 |
return 'text-red-400'
|
| 164 |
}
|
| 165 |
+
|
| 166 |
+
async function removeAccount(email) {
|
| 167 |
+
if (deleteDisabled.value) return
|
| 168 |
+
|
| 169 |
+
const ok = window.confirm(`确认删除账号 ${email}?\n这会同时清理本地记录、CPA、Team/Invite 和 CloudMail。`)
|
| 170 |
+
if (!ok) return
|
| 171 |
+
|
| 172 |
+
deletingEmail.value = email
|
| 173 |
+
message.value = ''
|
| 174 |
+
try {
|
| 175 |
+
const result = await api.deleteAccount(email)
|
| 176 |
+
message.value = result.message || `已删除 ${email}`
|
| 177 |
+
messageClass.value = 'bg-green-500/10 text-green-400 border-green-500/20'
|
| 178 |
+
emit('refresh')
|
| 179 |
+
} catch (e) {
|
| 180 |
+
message.value = e.message
|
| 181 |
+
messageClass.value = 'bg-red-500/10 text-red-400 border-red-500/20'
|
| 182 |
+
} finally {
|
| 183 |
+
deletingEmail.value = ''
|
| 184 |
+
setTimeout(() => {
|
| 185 |
+
message.value = ''
|
| 186 |
+
}, 8000)
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
</script>
|
web/src/components/Settings.vue
CHANGED
|
@@ -1,57 +1,344 @@
|
|
| 1 |
<template>
|
| 2 |
-
<div class="mt-6
|
| 3 |
-
<div class="
|
| 4 |
-
<
|
| 5 |
-
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
</div>
|
| 16 |
</div>
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
<div class="flex
|
| 20 |
-
<input
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
</div>
|
| 24 |
</div>
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
<div class="
|
| 28 |
-
<
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
</div>
|
| 32 |
</div>
|
| 33 |
</div>
|
| 34 |
|
| 35 |
-
<div class="
|
| 36 |
-
<
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
<
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
</template>
|
| 46 |
|
| 47 |
<script setup>
|
| 48 |
-
import { ref, onMounted } from 'vue'
|
| 49 |
import { api } from '../api.js'
|
| 50 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
const form = ref({ interval: 5, threshold: 10, min_low: 2 })
|
| 52 |
const saving = ref(false)
|
| 53 |
const saved = ref(false)
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
onMounted(async () => {
|
| 56 |
try {
|
| 57 |
const cfg = await api.getAutoCheckConfig()
|
|
@@ -65,6 +352,166 @@ onMounted(async () => {
|
|
| 65 |
}
|
| 66 |
})
|
| 67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
async function save() {
|
| 69 |
saving.value = true
|
| 70 |
saved.value = false
|
|
|
|
| 1 |
<template>
|
| 2 |
+
<div class="mt-6 space-y-6">
|
| 3 |
+
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
| 4 |
+
<div class="flex items-center justify-between gap-4 mb-4">
|
| 5 |
+
<div>
|
| 6 |
+
<h2 class="text-lg font-semibold text-white">管理员登录</h2>
|
| 7 |
+
<p class="text-sm text-gray-400 mt-1">
|
| 8 |
+
首次启动先在这里完成主号登录,系统会统一写入单个 state.json 文件,保存邮箱、session、workspace ID、workspace 名称;如果你走了密码登录,也会保留密码供主号 Codex 复用。
|
| 9 |
+
</p>
|
| 10 |
+
</div>
|
| 11 |
+
<span
|
| 12 |
+
class="min-w-[72px] px-3 py-1.5 rounded-full text-xs text-center whitespace-nowrap border"
|
| 13 |
+
:class="adminConfigured
|
| 14 |
+
? 'bg-green-500/10 text-green-400 border-green-500/20'
|
| 15 |
+
: adminBusy
|
| 16 |
+
? 'bg-yellow-500/10 text-yellow-300 border-yellow-500/20'
|
| 17 |
+
: 'bg-gray-800 text-gray-400 border-gray-700'"
|
| 18 |
+
>
|
| 19 |
+
{{ adminConfigured ? '已配置' : adminBusy ? '登录中' : '未配置' }}
|
| 20 |
+
</span>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<div v-if="message" class="mb-4 px-4 py-3 rounded-lg text-sm border" :class="messageClass">
|
| 24 |
+
{{ message }}
|
| 25 |
+
</div>
|
| 26 |
|
| 27 |
+
<div v-if="adminConfigured && !adminBusy" class="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
| 28 |
+
<div class="px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg">
|
| 29 |
+
<div class="text-gray-500 mb-1">管理员邮箱</div>
|
| 30 |
+
<div class="font-mono text-white break-all">{{ props.adminStatus?.email || '-' }}</div>
|
| 31 |
+
</div>
|
| 32 |
+
<div class="px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg">
|
| 33 |
+
<div class="text-gray-500 mb-1">Workspace ID</div>
|
| 34 |
+
<div class="font-mono text-white break-all">{{ props.adminStatus?.account_id || '-' }}</div>
|
| 35 |
+
</div>
|
| 36 |
+
<div class="px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg md:col-span-2">
|
| 37 |
+
<div class="text-gray-500 mb-1">Workspace 名称</div>
|
| 38 |
+
<div class="text-white">{{ props.adminStatus?.workspace_name || '未识别' }}</div>
|
| 39 |
+
</div>
|
| 40 |
+
<div class="px-3 py-3 bg-gray-800/60 border border-gray-800 rounded-lg md:col-span-2">
|
| 41 |
+
<div class="text-gray-500 mb-1">管理员密码</div>
|
| 42 |
+
<div class="text-white">{{ props.adminStatus?.password_saved ? '已保存,可用于主号 Codex 登录' : '未保存' }}</div>
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
+
|
| 46 |
+
<div v-if="!adminBusy" class="mt-4">
|
| 47 |
+
<div v-if="!adminConfigured" class="flex flex-col sm:flex-row gap-3">
|
| 48 |
+
<input
|
| 49 |
+
v-model.trim="email"
|
| 50 |
+
type="email"
|
| 51 |
+
autocomplete="username"
|
| 52 |
+
placeholder="输入主号邮箱"
|
| 53 |
+
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 54 |
+
/>
|
| 55 |
+
<button
|
| 56 |
+
@click="startLogin"
|
| 57 |
+
:disabled="submitting || !email"
|
| 58 |
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 59 |
+
>
|
| 60 |
+
{{ submitting ? '提交中...' : '开始登录' }}
|
| 61 |
+
</button>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div v-else-if="!codexBusy" class="flex flex-wrap gap-3">
|
| 65 |
+
<button
|
| 66 |
+
@click="syncMainCodex"
|
| 67 |
+
:disabled="submitting || syncingMain"
|
| 68 |
+
class="px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 69 |
+
>
|
| 70 |
+
{{ syncingMain ? '同步中...' : '同步主号 Codex 到 CPA' }}
|
| 71 |
+
</button>
|
| 72 |
+
<button
|
| 73 |
+
@click="logoutAdmin"
|
| 74 |
+
:disabled="submitting || syncingMain"
|
| 75 |
+
class="px-4 py-2 bg-rose-700/80 hover:bg-rose-700 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 76 |
+
>
|
| 77 |
+
{{ submitting ? '处理中...' : '清除登录态' }}
|
| 78 |
+
</button>
|
| 79 |
</div>
|
| 80 |
</div>
|
| 81 |
+
|
| 82 |
+
<div v-if="adminBusy" class="space-y-4">
|
| 83 |
+
<div class="text-sm text-gray-300">
|
| 84 |
+
当前邮箱: <span class="font-mono">{{ loginEmail || props.adminStatus?.email || '-' }}</span>
|
| 85 |
+
</div>
|
| 86 |
+
|
| 87 |
+
<div v-if="props.adminStatus?.login_step === 'password_required'" class="flex flex-col sm:flex-row gap-3">
|
| 88 |
+
<input
|
| 89 |
+
v-model="password"
|
| 90 |
+
type="password"
|
| 91 |
+
autocomplete="current-password"
|
| 92 |
+
placeholder="输入主号密码"
|
| 93 |
+
:disabled="submitting"
|
| 94 |
+
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 95 |
+
/>
|
| 96 |
+
<button
|
| 97 |
+
@click="submitPassword"
|
| 98 |
+
:disabled="submitting || !password"
|
| 99 |
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 100 |
+
>
|
| 101 |
+
{{ submitting ? '提交中...' : '提交密码' }}
|
| 102 |
+
</button>
|
| 103 |
+
</div>
|
| 104 |
+
|
| 105 |
+
<div v-else-if="props.adminStatus?.login_step === 'code_required'" class="flex flex-col sm:flex-row gap-3">
|
| 106 |
+
<input
|
| 107 |
+
v-model.trim="code"
|
| 108 |
+
type="text"
|
| 109 |
+
inputmode="numeric"
|
| 110 |
+
autocomplete="one-time-code"
|
| 111 |
+
placeholder="输入邮箱验证码"
|
| 112 |
+
:disabled="submitting"
|
| 113 |
+
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 114 |
+
/>
|
| 115 |
+
<button
|
| 116 |
+
@click="submitCode"
|
| 117 |
+
:disabled="submitting || !code"
|
| 118 |
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50 disabled:bg-gray-700 disabled:hover:bg-gray-700"
|
| 119 |
+
>
|
| 120 |
+
{{ submitting ? '提交中...' : '提交验证码' }}
|
| 121 |
+
</button>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<div v-else-if="props.adminStatus?.login_step === 'workspace_required'" class="space-y-3">
|
| 125 |
+
<div class="text-sm text-gray-300">
|
| 126 |
+
请选择要进入的组织 / workspace
|
| 127 |
+
</div>
|
| 128 |
+
<select
|
| 129 |
+
v-model="workspaceOptionId"
|
| 130 |
+
:disabled="submitting"
|
| 131 |
+
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 132 |
+
>
|
| 133 |
+
<option disabled value="">请选择组织</option>
|
| 134 |
+
<option
|
| 135 |
+
v-for="opt in props.adminStatus?.workspace_options || []"
|
| 136 |
+
:key="opt.id"
|
| 137 |
+
:value="opt.id"
|
| 138 |
+
>
|
| 139 |
+
{{ opt.label }}{{ opt.kind === 'fallback' ? ' (可能是个人/免费)' : '' }}
|
| 140 |
+
</option>
|
| 141 |
+
</select>
|
| 142 |
+
<button
|
| 143 |
+
@click="submitWorkspace"
|
| 144 |
+
:disabled="submitting || !workspaceOptionId"
|
| 145 |
+
class="px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50 disabled:bg-gray-700 disabled:hover:bg-gray-700"
|
| 146 |
+
>
|
| 147 |
+
{{ submitting ? '提交中...' : '确认组织选择' }}
|
| 148 |
+
</button>
|
| 149 |
+
</div>
|
| 150 |
+
|
| 151 |
+
<div v-if="submitting && adminSubmittingHint" class="text-xs text-blue-300">
|
| 152 |
+
{{ adminSubmittingHint }}
|
| 153 |
+
</div>
|
| 154 |
+
|
| 155 |
+
<div class="flex justify-end">
|
| 156 |
+
<button
|
| 157 |
+
@click="cancelLogin"
|
| 158 |
+
:disabled="submitting"
|
| 159 |
+
class="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-sm text-gray-200 rounded-lg border border-gray-700 transition disabled:opacity-50"
|
| 160 |
+
>
|
| 161 |
+
取消登录
|
| 162 |
+
</button>
|
| 163 |
+
</div>
|
| 164 |
+
</div>
|
| 165 |
+
|
| 166 |
+
<div v-if="codexBusy" class="mt-4 space-y-4 border-t border-gray-800 pt-4">
|
| 167 |
+
<div class="text-sm text-gray-300">
|
| 168 |
+
主号 Codex 登录继续中
|
| 169 |
+
</div>
|
| 170 |
+
|
| 171 |
+
<div v-if="props.codexStatus?.step === 'password_required'" class="flex flex-col sm:flex-row gap-3">
|
| 172 |
+
<input
|
| 173 |
+
v-model="codexPassword"
|
| 174 |
+
type="password"
|
| 175 |
+
autocomplete="current-password"
|
| 176 |
+
placeholder="输入主号密码"
|
| 177 |
+
:disabled="syncingMain"
|
| 178 |
+
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 179 |
+
/>
|
| 180 |
+
<button
|
| 181 |
+
@click="submitMainCodexPassword"
|
| 182 |
+
:disabled="syncingMain || !codexPassword"
|
| 183 |
+
class="px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 184 |
+
>
|
| 185 |
+
{{ syncingMain ? '提交中...' : '提交密码' }}
|
| 186 |
+
</button>
|
| 187 |
+
</div>
|
| 188 |
+
|
| 189 |
+
<div v-else-if="props.codexStatus?.step === 'code_required'" class="flex flex-col sm:flex-row gap-3">
|
| 190 |
+
<input
|
| 191 |
+
v-model.trim="codexCode"
|
| 192 |
+
type="text"
|
| 193 |
+
inputmode="numeric"
|
| 194 |
+
autocomplete="one-time-code"
|
| 195 |
+
placeholder="输入主号 Codex 验证码"
|
| 196 |
+
:disabled="syncingMain"
|
| 197 |
+
class="flex-1 px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500"
|
| 198 |
+
/>
|
| 199 |
+
<button
|
| 200 |
+
@click="submitMainCodexCode"
|
| 201 |
+
:disabled="syncingMain || !codexCode"
|
| 202 |
+
class="px-4 py-2 bg-cyan-700 hover:bg-cyan-600 text-white text-sm rounded-lg transition disabled:opacity-50"
|
| 203 |
+
>
|
| 204 |
+
{{ syncingMain ? '提交中...' : '提交验证码' }}
|
| 205 |
+
</button>
|
| 206 |
+
</div>
|
| 207 |
+
|
| 208 |
+
<div v-if="syncingMain && codexSubmittingHint" class="text-xs text-cyan-300">
|
| 209 |
+
{{ codexSubmittingHint }}
|
| 210 |
+
</div>
|
| 211 |
+
|
| 212 |
+
<div class="flex justify-end">
|
| 213 |
+
<button
|
| 214 |
+
@click="cancelMainCodexSync"
|
| 215 |
+
:disabled="syncingMain"
|
| 216 |
+
class="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-sm text-gray-200 rounded-lg border border-gray-700 transition disabled:opacity-50"
|
| 217 |
+
>
|
| 218 |
+
取消主号 Codex 登录
|
| 219 |
+
</button>
|
| 220 |
</div>
|
| 221 |
</div>
|
| 222 |
</div>
|
| 223 |
|
| 224 |
+
<div class="bg-gray-900 border border-gray-800 rounded-xl p-4">
|
| 225 |
+
<div class="flex items-center justify-between mb-4">
|
| 226 |
+
<h2 class="text-lg font-semibold text-white">巡检设置</h2>
|
| 227 |
+
<span v-if="saved" class="text-xs text-green-400 transition">已保存</span>
|
| 228 |
+
</div>
|
| 229 |
+
|
| 230 |
+
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
| 231 |
+
<div>
|
| 232 |
+
<label class="block text-sm text-gray-400 mb-1">巡检间隔</label>
|
| 233 |
+
<div class="flex items-center gap-2">
|
| 234 |
+
<input v-model.number="form.interval" type="number" min="1"
|
| 235 |
+
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500" />
|
| 236 |
+
<span class="text-sm text-gray-500 shrink-0">分钟</span>
|
| 237 |
+
</div>
|
| 238 |
+
</div>
|
| 239 |
+
<div>
|
| 240 |
+
<label class="block text-sm text-gray-400 mb-1">额度阈值</label>
|
| 241 |
+
<div class="flex items-center gap-2">
|
| 242 |
+
<input v-model.number="form.threshold" type="number" min="1" max="100"
|
| 243 |
+
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500" />
|
| 244 |
+
<span class="text-sm text-gray-500 shrink-0">%</span>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
<div>
|
| 248 |
+
<label class="block text-sm text-gray-400 mb-1">触发账号数</label>
|
| 249 |
+
<div class="flex items-center gap-2">
|
| 250 |
+
<input v-model.number="form.min_low" type="number" min="1"
|
| 251 |
+
class="w-full px-3 py-2 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500" />
|
| 252 |
+
<span class="text-sm text-gray-500 shrink-0">个</span>
|
| 253 |
+
</div>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
|
| 257 |
+
<div class="mt-3 flex items-center justify-between gap-3">
|
| 258 |
+
<p class="text-xs text-gray-500">
|
| 259 |
+
每 {{ form.interval }} 分钟检查一次,{{ form.min_low }} 个以上账号剩余低于 {{ form.threshold }}% 时自动轮转
|
| 260 |
+
</p>
|
| 261 |
+
<button @click="save" :disabled="saving"
|
| 262 |
+
class="px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50">
|
| 263 |
+
{{ saving ? '保存中...' : '保存' }}
|
| 264 |
+
</button>
|
| 265 |
+
</div>
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
</template>
|
| 269 |
|
| 270 |
<script setup>
|
| 271 |
+
import { computed, ref, watch, onMounted } from 'vue'
|
| 272 |
import { api } from '../api.js'
|
| 273 |
|
| 274 |
+
const props = defineProps({
|
| 275 |
+
adminStatus: {
|
| 276 |
+
type: Object,
|
| 277 |
+
default: null,
|
| 278 |
+
},
|
| 279 |
+
codexStatus: {
|
| 280 |
+
type: Object,
|
| 281 |
+
default: null,
|
| 282 |
+
},
|
| 283 |
+
})
|
| 284 |
+
|
| 285 |
+
const emit = defineEmits(['refresh', 'admin-progress'])
|
| 286 |
+
|
| 287 |
const form = ref({ interval: 5, threshold: 10, min_low: 2 })
|
| 288 |
const saving = ref(false)
|
| 289 |
const saved = ref(false)
|
| 290 |
|
| 291 |
+
const email = ref('')
|
| 292 |
+
const password = ref('')
|
| 293 |
+
const code = ref('')
|
| 294 |
+
const workspaceOptionId = ref('')
|
| 295 |
+
const loginEmail = ref('')
|
| 296 |
+
const codexPassword = ref('')
|
| 297 |
+
const codexCode = ref('')
|
| 298 |
+
const submitting = ref(false)
|
| 299 |
+
const syncingMain = ref(false)
|
| 300 |
+
const message = ref('')
|
| 301 |
+
const messageClass = ref('')
|
| 302 |
+
const adminSubmittingHint = ref('')
|
| 303 |
+
const codexSubmittingHint = ref('')
|
| 304 |
+
|
| 305 |
+
const adminConfigured = computed(() => !!props.adminStatus?.configured)
|
| 306 |
+
const adminBusy = computed(() => !!props.adminStatus?.login_in_progress)
|
| 307 |
+
const codexBusy = computed(() => !!props.codexStatus?.in_progress)
|
| 308 |
+
|
| 309 |
+
watch(
|
| 310 |
+
() => props.adminStatus,
|
| 311 |
+
(next) => {
|
| 312 |
+
if (next?.configured && next.email) {
|
| 313 |
+
email.value = next.email
|
| 314 |
+
}
|
| 315 |
+
if (!next?.login_in_progress) {
|
| 316 |
+
password.value = ''
|
| 317 |
+
code.value = ''
|
| 318 |
+
workspaceOptionId.value = ''
|
| 319 |
+
adminSubmittingHint.value = ''
|
| 320 |
+
loginEmail.value = next?.email || loginEmail.value
|
| 321 |
+
}
|
| 322 |
+
if (next?.login_step === 'workspace_required' && !workspaceOptionId.value) {
|
| 323 |
+
const preferred = next?.workspace_options?.find(opt => opt.kind === 'preferred')
|
| 324 |
+
workspaceOptionId.value = preferred?.id || next?.workspace_options?.[0]?.id || ''
|
| 325 |
+
}
|
| 326 |
+
},
|
| 327 |
+
{ immediate: true },
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
watch(
|
| 331 |
+
() => props.codexStatus,
|
| 332 |
+
(next) => {
|
| 333 |
+
if (!next?.in_progress) {
|
| 334 |
+
codexPassword.value = ''
|
| 335 |
+
codexCode.value = ''
|
| 336 |
+
codexSubmittingHint.value = ''
|
| 337 |
+
}
|
| 338 |
+
},
|
| 339 |
+
{ immediate: true },
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
onMounted(async () => {
|
| 343 |
try {
|
| 344 |
const cfg = await api.getAutoCheckConfig()
|
|
|
|
| 352 |
}
|
| 353 |
})
|
| 354 |
|
| 355 |
+
function setMessage(text, type = 'success') {
|
| 356 |
+
message.value = text
|
| 357 |
+
messageClass.value = type === 'success'
|
| 358 |
+
? 'bg-green-500/10 text-green-400 border-green-500/20'
|
| 359 |
+
: 'bg-red-500/10 text-red-400 border-red-500/20'
|
| 360 |
+
window.clearTimeout(setMessage._timer)
|
| 361 |
+
setMessage._timer = window.setTimeout(() => {
|
| 362 |
+
message.value = ''
|
| 363 |
+
}, 8000)
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
async function startLogin() {
|
| 367 |
+
submitting.value = true
|
| 368 |
+
adminSubmittingHint.value = '正在打开管理员登录页...'
|
| 369 |
+
try {
|
| 370 |
+
loginEmail.value = email.value
|
| 371 |
+
const result = await api.startAdminLogin(email.value)
|
| 372 |
+
setMessage(result.status === 'completed' ? '管理员登录完成' : '已进入下一步登录流程')
|
| 373 |
+
emit('admin-progress')
|
| 374 |
+
} catch (e) {
|
| 375 |
+
setMessage(e.message, 'error')
|
| 376 |
+
} finally {
|
| 377 |
+
submitting.value = false
|
| 378 |
+
adminSubmittingHint.value = ''
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
async function submitPassword() {
|
| 383 |
+
submitting.value = true
|
| 384 |
+
adminSubmittingHint.value = '密码已提交,正在等待登录页响应...'
|
| 385 |
+
try {
|
| 386 |
+
const result = await api.submitAdminPassword(password.value)
|
| 387 |
+
setMessage(result.status === 'completed' ? '管理员登录完成' : '密码已提交,请继续下一步')
|
| 388 |
+
emit('admin-progress')
|
| 389 |
+
} catch (e) {
|
| 390 |
+
setMessage(e.message, 'error')
|
| 391 |
+
} finally {
|
| 392 |
+
submitting.value = false
|
| 393 |
+
adminSubmittingHint.value = ''
|
| 394 |
+
}
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
async function submitCode() {
|
| 398 |
+
submitting.value = true
|
| 399 |
+
adminSubmittingHint.value = '验证码已提交,正在等待登录页响应,通常需要 5 到 10 秒...'
|
| 400 |
+
try {
|
| 401 |
+
const result = await api.submitAdminCode(code.value)
|
| 402 |
+
setMessage(result.status === 'completed' ? '管理员登录完成' : '验证码已提交,请继续下一步')
|
| 403 |
+
emit('admin-progress')
|
| 404 |
+
} catch (e) {
|
| 405 |
+
setMessage(e.message, 'error')
|
| 406 |
+
} finally {
|
| 407 |
+
submitting.value = false
|
| 408 |
+
adminSubmittingHint.value = ''
|
| 409 |
+
}
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
async function submitWorkspace() {
|
| 413 |
+
submitting.value = true
|
| 414 |
+
adminSubmittingHint.value = '组织选择已提交,正在等待登录页响应...'
|
| 415 |
+
try {
|
| 416 |
+
const result = await api.submitAdminWorkspace(workspaceOptionId.value)
|
| 417 |
+
setMessage(result.status === 'completed' ? '管理员登录完成' : '组织选择已提交,请继续下一步')
|
| 418 |
+
emit('admin-progress')
|
| 419 |
+
} catch (e) {
|
| 420 |
+
setMessage(e.message, 'error')
|
| 421 |
+
} finally {
|
| 422 |
+
submitting.value = false
|
| 423 |
+
adminSubmittingHint.value = ''
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
async function cancelLogin() {
|
| 428 |
+
submitting.value = true
|
| 429 |
+
try {
|
| 430 |
+
await api.cancelAdminLogin()
|
| 431 |
+
password.value = ''
|
| 432 |
+
code.value = ''
|
| 433 |
+
setMessage('管理员登录已取消')
|
| 434 |
+
emit('refresh')
|
| 435 |
+
} catch (e) {
|
| 436 |
+
setMessage(e.message, 'error')
|
| 437 |
+
} finally {
|
| 438 |
+
submitting.value = false
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
async function logoutAdmin() {
|
| 443 |
+
submitting.value = true
|
| 444 |
+
try {
|
| 445 |
+
await api.logoutAdmin()
|
| 446 |
+
password.value = ''
|
| 447 |
+
code.value = ''
|
| 448 |
+
setMessage('管理员登录态已清除')
|
| 449 |
+
emit('refresh')
|
| 450 |
+
} catch (e) {
|
| 451 |
+
setMessage(e.message, 'error')
|
| 452 |
+
} finally {
|
| 453 |
+
submitting.value = false
|
| 454 |
+
}
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
async function syncMainCodex() {
|
| 458 |
+
syncingMain.value = true
|
| 459 |
+
codexSubmittingHint.value = '正在打开主号 Codex 登录页...'
|
| 460 |
+
try {
|
| 461 |
+
const result = await api.startMainCodexSync()
|
| 462 |
+
setMessage(result.status === 'completed' ? (result.message || '主号 Codex 已同步') : '主号 Codex 登录进入下一步')
|
| 463 |
+
emit('admin-progress')
|
| 464 |
+
} catch (e) {
|
| 465 |
+
setMessage(e.message, 'error')
|
| 466 |
+
} finally {
|
| 467 |
+
syncingMain.value = false
|
| 468 |
+
codexSubmittingHint.value = ''
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
async function submitMainCodexPassword() {
|
| 473 |
+
syncingMain.value = true
|
| 474 |
+
codexSubmittingHint.value = '密码已提交,正在等待主号 Codex 登录页响应...'
|
| 475 |
+
try {
|
| 476 |
+
const result = await api.submitMainCodexPassword(codexPassword.value)
|
| 477 |
+
setMessage(result.status === 'completed' ? (result.message || '主号 Codex 已同步') : '主号 Codex 密码已提交')
|
| 478 |
+
emit('admin-progress')
|
| 479 |
+
} catch (e) {
|
| 480 |
+
setMessage(e.message, 'error')
|
| 481 |
+
} finally {
|
| 482 |
+
syncingMain.value = false
|
| 483 |
+
codexSubmittingHint.value = ''
|
| 484 |
+
}
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
async function submitMainCodexCode() {
|
| 488 |
+
syncingMain.value = true
|
| 489 |
+
codexSubmittingHint.value = '验证码已提交,正在等待主号 Codex 登录页响应,通常需要 5 到 10 秒...'
|
| 490 |
+
try {
|
| 491 |
+
const result = await api.submitMainCodexCode(codexCode.value)
|
| 492 |
+
setMessage(result.status === 'completed' ? (result.message || '主号 Codex 已同步') : '主号 Codex 验证码已提交')
|
| 493 |
+
emit('admin-progress')
|
| 494 |
+
} catch (e) {
|
| 495 |
+
setMessage(e.message, 'error')
|
| 496 |
+
} finally {
|
| 497 |
+
syncingMain.value = false
|
| 498 |
+
codexSubmittingHint.value = ''
|
| 499 |
+
}
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
async function cancelMainCodexSync() {
|
| 503 |
+
syncingMain.value = true
|
| 504 |
+
try {
|
| 505 |
+
await api.cancelMainCodexSync()
|
| 506 |
+
setMessage('主号 Codex 登录已取消')
|
| 507 |
+
emit('refresh')
|
| 508 |
+
} catch (e) {
|
| 509 |
+
setMessage(e.message, 'error')
|
| 510 |
+
} finally {
|
| 511 |
+
syncingMain.value = false
|
| 512 |
+
}
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
async function save() {
|
| 516 |
saving.value = true
|
| 517 |
saved.value = false
|
web/src/components/TaskPanel.vue
CHANGED
|
@@ -1,12 +1,15 @@
|
|
| 1 |
<template>
|
| 2 |
<div class="mt-6 bg-gray-900 border border-gray-800 rounded-xl p-4">
|
| 3 |
<h2 class="text-lg font-semibold text-white mb-4">操作</h2>
|
|
|
|
|
|
|
|
|
|
| 4 |
<div class="flex flex-wrap gap-3">
|
| 5 |
<button v-for="action in actions" :key="action.key"
|
| 6 |
@click="execute(action)"
|
| 7 |
-
:disabled="
|
| 8 |
class="px-4 py-2 rounded-lg text-sm font-medium transition border"
|
| 9 |
-
:class="
|
| 10 |
? 'bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed'
|
| 11 |
: `${action.style} hover:opacity-80`">
|
| 12 |
{{ action.label }}
|
|
@@ -18,7 +21,7 @@
|
|
| 18 |
<label class="text-sm text-gray-400">{{ paramLabel }}:</label>
|
| 19 |
<input v-model.number="paramValue" type="number" min="1" max="20"
|
| 20 |
class="w-20 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500" />
|
| 21 |
-
<button @click="confirmAction"
|
| 22 |
class="px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition">
|
| 23 |
确认执行
|
| 24 |
</button>
|
|
@@ -36,11 +39,15 @@
|
|
| 36 |
</template>
|
| 37 |
|
| 38 |
<script setup>
|
| 39 |
-
import { ref } from 'vue'
|
| 40 |
import { api } from '../api.js'
|
| 41 |
|
| 42 |
const props = defineProps({
|
| 43 |
runningTask: Object,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
})
|
| 45 |
const emit = defineEmits(['task-started', 'refresh'])
|
| 46 |
|
|
@@ -59,8 +66,11 @@ const paramValue = ref(5)
|
|
| 59 |
const pendingAction = ref(null)
|
| 60 |
const message = ref('')
|
| 61 |
const messageClass = ref('')
|
|
|
|
|
|
|
| 62 |
|
| 63 |
async function execute(action) {
|
|
|
|
| 64 |
message.value = ''
|
| 65 |
if (action.needParam) {
|
| 66 |
pendingAction.value = action
|
|
|
|
| 1 |
<template>
|
| 2 |
<div class="mt-6 bg-gray-900 border border-gray-800 rounded-xl p-4">
|
| 3 |
<h2 class="text-lg font-semibold text-white mb-4">操作</h2>
|
| 4 |
+
<div v-if="!adminReady" class="mb-4 px-4 py-3 rounded-lg text-sm border bg-amber-500/10 text-amber-300 border-amber-500/20">
|
| 5 |
+
需要先在下方“管理员登录”里完成主号登录,管理操作才会开放。
|
| 6 |
+
</div>
|
| 7 |
<div class="flex flex-wrap gap-3">
|
| 8 |
<button v-for="action in actions" :key="action.key"
|
| 9 |
@click="execute(action)"
|
| 10 |
+
:disabled="disabled"
|
| 11 |
class="px-4 py-2 rounded-lg text-sm font-medium transition border"
|
| 12 |
+
:class="disabled
|
| 13 |
? 'bg-gray-800 text-gray-500 border-gray-700 cursor-not-allowed'
|
| 14 |
: `${action.style} hover:opacity-80`">
|
| 15 |
{{ action.label }}
|
|
|
|
| 21 |
<label class="text-sm text-gray-400">{{ paramLabel }}:</label>
|
| 22 |
<input v-model.number="paramValue" type="number" min="1" max="20"
|
| 23 |
class="w-20 px-3 py-1.5 bg-gray-800 border border-gray-700 rounded-lg text-sm text-white focus:outline-none focus:border-blue-500" />
|
| 24 |
+
<button @click="confirmAction" :disabled="disabled"
|
| 25 |
class="px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition">
|
| 26 |
确认执行
|
| 27 |
</button>
|
|
|
|
| 39 |
</template>
|
| 40 |
|
| 41 |
<script setup>
|
| 42 |
+
import { computed, ref } from 'vue'
|
| 43 |
import { api } from '../api.js'
|
| 44 |
|
| 45 |
const props = defineProps({
|
| 46 |
runningTask: Object,
|
| 47 |
+
adminStatus: {
|
| 48 |
+
type: Object,
|
| 49 |
+
default: null,
|
| 50 |
+
},
|
| 51 |
})
|
| 52 |
const emit = defineEmits(['task-started', 'refresh'])
|
| 53 |
|
|
|
|
| 66 |
const pendingAction = ref(null)
|
| 67 |
const message = ref('')
|
| 68 |
const messageClass = ref('')
|
| 69 |
+
const adminReady = computed(() => !!props.adminStatus?.configured)
|
| 70 |
+
const disabled = computed(() => !!props.runningTask || !adminReady.value)
|
| 71 |
|
| 72 |
async function execute(action) {
|
| 73 |
+
if (disabled.value) return
|
| 74 |
message.value = ''
|
| 75 |
if (action.needParam) {
|
| 76 |
pendingAction.value = action
|