BestLemoon commited on
Commit
40b21ae
·
1 Parent(s): 5b1588f

feat: 支持管理员交互式登录与主号 Codex 同步

Browse files
.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
- | `session` | ChatGPT 管理员的 `__Secure-next-auth.session-token`拼接 `.0` 和 `.1` |
 
 
 
 
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 分钟自动刷新数据,有任务执行时切换为 30 秒轮询,任务完成后自动恢复。
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
- running = _tasks.get(_current_task_id, {})
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.config import CHATGPT_ACCOUNT_ID
 
 
 
 
 
12
 
13
  logger = logging.getLogger(__name__)
14
 
@@ -18,7 +26,29 @@ SCREENSHOT_DIR = PROJECT_ROOT / "screenshots"
18
 
19
 
20
  class ChatGPTTeamAPI:
21
- """通过 Playwright 浏览器内 fetch 调用 ChatGPT 内部 API"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.account_id = CHATGPT_ACCOUNT_ID
 
 
30
  self.oai_device_id = str(uuid.uuid4())
31
-
32
- def start(self):
33
- """启动浏览器,注入 cookies,获取 access token"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # 先访问 chatgpt.com 过 Cloudflare
54
- logger.info("[ChatGPT] 访问 chatgpt.com 过 Cloudflare...")
55
- self.page.goto("https://chatgpt.com/", wait_until="domcontentloaded", timeout=60000)
56
- time.sleep(5)
 
 
 
 
 
 
 
 
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
- break
62
  logger.info("[ChatGPT] 等待 Cloudflare... (%ds)", i * 5)
63
  time.sleep(5)
64
 
65
- # 注入 session cookie(分片格式)
66
- # 检测 token 长度,超过 3800 就分片
67
  if len(session_token) > 3800:
68
- part0 = session_token[:3800]
69
- part1 = session_token[3800:]
70
- cookies = [
71
  {
72
  "name": "__Secure-next-auth.session-token.0",
73
- "value": part0,
74
- "domain": "chatgpt.com",
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": part1,
83
- "domain": "chatgpt.com",
84
  "path": "/",
85
  "httpOnly": True,
86
  "secure": True,
87
  "sameSite": "Lax",
88
  },
89
  ]
90
- else:
91
- cookies = [{
92
- "name": "__Secure-next-auth.session-token",
93
- "value": session_token,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- # 获取 access token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  self._fetch_access_token()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
- # 自动检测 account_id 和 workspace_name(如果未配置)
126
- self._auto_detect_workspace()
127
 
128
- def _auto_detect_workspace(self):
129
- """自动获取 workspace 名称(需要 CHATGPT_ACCOUNT_ID 已配置)"""
130
- from autoteam import config
 
 
 
 
131
 
132
- if config.CHATGPT_WORKSPACE_NAME:
133
- return # 配置
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- if not config.CHATGPT_ACCOUNT_ID:
136
- logger.warning("[ChatGPT] 请在 .env 中配置 CHATGPT_ACCOUNT_ID")
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
- config.CHATGPT_WORKSPACE_NAME = result["workspace_name"]
153
- logger.info("[ChatGPT] 自动检测到 workspace 名称: %s", result['workspace_name'])
154
- return
 
155
 
156
- # fallback: 从 admin 页面提取 workspace 名称
157
  try:
158
  self.page.goto("https://chatgpt.com/admin", wait_until="domcontentloaded", timeout=30000)
159
- import time as _t
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
- config.CHATGPT_WORKSPACE_NAME = name
 
178
  logger.info("[ChatGPT] 自动检测到 workspace 名称: %s", name)
179
- return
180
  except Exception:
181
  pass
182
 
183
- logger.warning("[ChatGPT] 未能自动获取 workspace 名称,请在 .env 中配置 CHATGPT_WORKSPACE_NAME")
 
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
- result = self.page.evaluate(
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
- from autoteam.config import CHATGPT_ACCOUNT_ID
67
 
68
- # 构建 OAuth URL
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 CHATGPT_ACCOUNT_ID:
98
  context.add_cookies([{
99
  "name": "_account",
100
- "value": CHATGPT_ACCOUNT_ID,
101
  "domain": "chatgpt.com",
102
  "path": "/",
103
  "secure": True,
104
  "sameSite": "Lax",
105
  }, {
106
  "name": "_account",
107
- "value": CHATGPT_ACCOUNT_ID,
108
  "domain": "auth.openai.com",
109
  "path": "/",
110
  "secure": True,
111
  "sameSite": "Lax",
112
  }])
113
- logger.debug("[Codex] 登录前已注入 _account cookie = %s", CHATGPT_ACCOUNT_ID)
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
- submit = ei.locator("xpath=ancestor::form//button[contains(text(),'Continue') or contains(text(),'继续') or @type='submit']").first
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
- submit = pi.locator("xpath=ancestor::form//button[contains(text(),'Continue') or contains(text(),'继续') or @type='submit']").first
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="{CHATGPT_WORKSPACE_NAME}"').first
197
- if CHATGPT_WORKSPACE_NAME and ws_btn.is_visible(timeout=3000):
198
- logger.info("[Codex] 选择 workspace: %s", CHATGPT_WORKSPACE_NAME)
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
- if email_input.is_visible(timeout=5000):
 
 
 
 
257
  email_input.fill(email)
258
  time.sleep(0.5)
259
- # 点邮箱表单的 Continue,用邮箱输入框的父 form 内定位避免误点第三方登录
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
- _screenshot(page, "codex_02_after_email.png")
 
 
 
 
 
 
 
 
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
- if pwd_input.is_visible(timeout=5000):
 
 
 
 
275
  pwd_input.fill(password)
276
  time.sleep(0.5)
277
- # 同样用父 form 内定位
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
- _screenshot(page, "codex_03_after_password.png")
 
 
 
 
 
 
 
 
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
- from autoteam.config import CHATGPT_WORKSPACE_NAME
384
- if CHATGPT_WORKSPACE_NAME:
385
  try:
386
- ws_btn = page.locator(f'text="{CHATGPT_WORKSPACE_NAME}"').first
387
  if ws_btn.is_visible(timeout=2000):
388
  ws_btn.click()
389
  time.sleep(1)
390
- logger.info("[Codex] 已选择 workspace: %s (step %d)", CHATGPT_WORKSPACE_NAME, step + 1)
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
- logger.info("[Codex] 获取到 auth code,交换 token...")
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
- if resp.status_code != 200:
495
- logger.error("[Codex] Token 交换失败: %d %s", resp.status_code, resp.text[:200])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  return None
497
 
498
- token_data = resp.json()
499
- id_token = token_data.get("id_token", "")
500
- claims = _parse_jwt_payload(id_token)
501
- auth_claims = claims.get("https://api.openai.com/auth", {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
 
503
- bundle = {
504
- "access_token": token_data.get("access_token"),
505
- "refresh_token": token_data.get("refresh_token"),
506
- "id_token": id_token,
507
- "account_id": auth_claims.get("chatgpt_account_id", ""),
508
- "email": claims.get("email", email),
509
- "plan_type": auth_claims.get("chatgpt_plan_type", "unknown"),
510
- "expired": time.time() + token_data.get("expires_in", 3600),
511
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
- logger.info("[Codex] 登录成功: %s (plan: %s)", bundle['email'], bundle['plan_type'])
514
- return bundle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- filepath.write_text(json.dumps(auth_data, indent=2))
546
- os.chmod(filepath, 0o600)
 
547
 
548
- logger.info("[Codex] 认证文件已保存: %s", filepath)
549
- return str(filepath)
 
 
 
 
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
- from autoteam.config import CHATGPT_ACCOUNT_ID
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/{CHATGPT_ACCOUNT_ID}/users"
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/{CHATGPT_ACCOUNT_ID}/users"
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/{CHATGPT_ACCOUNT_ID}/users/{target_user_id}"
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
- result = chatgpt_api._api_fetch("GET", f"/backend-api/accounts/{CHATGPT_ACCOUNT_ID}/invites")
 
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 create_account_direct(mail_client):
566
- """
567
- 直接注册模式(域名已配置自动加入 workspace,不需要邀请)。
568
- 流程:创建邮箱 → 注册 ChatGPT → 自动加入 workspace → Codex 登录
569
- """
570
- from autoteam.invite import register_with_invite, screenshot
571
- from playwright.sync_api import sync_playwright
572
- import uuid
 
 
 
 
 
 
573
 
574
- # Step 1: 创建临时邮箱
575
- account_id, email = mail_client.create_temp_email()
576
- password = f"Tmp_{uuid.uuid4().hex[:12]}!"
577
 
578
- # Step 2: 记录账号
579
- add_account(email, password, cloudmail_account_id=account_id)
 
 
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('button:has-text("注册"), button:has-text("Sign up"), a:has-text("Sign up"), a:has-text("注册")').first
 
 
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
- if email_input.is_visible(timeout=5000):
 
 
 
 
625
  email_input.fill(email)
626
  time.sleep(0.5)
627
- page.locator('button:has-text("Continue"), button:has-text("继续"), button[type="submit"]').first.click()
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
- if pwd_input.is_visible(timeout=5000):
 
 
 
 
638
  logger.info("[直接注册] 设置密码")
639
  pwd_input.fill(password)
640
  time.sleep(0.5)
641
- page.locator('button:has-text("Continue"), button:has-text("继续"), button[type="submit"]').first.click()
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
- page.locator('button:has-text("Continue"), button:has-text("继续"), button[type="submit"]').first.click()
681
  time.sleep(8)
682
  else:
683
  logger.error("[直接注册] 未收到验证码")
684
  browser.close()
685
- return None
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
- page.locator('button:has-text("完成帐户创建"), button:has-text("Continue"), button:has-text("继续"), button[type="submit"]').first.click()
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
- path = f"/backend-api/accounts/{CHATGPT_ACCOUNT_ID}/users"
 
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/{CHATGPT_ACCOUNT_ID}/users"
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/{CHATGPT_ACCOUNT_ID}/users/{user_id}"
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/{CHATGPT_ACCOUNT_ID}/invites")
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/{CHATGPT_ACCOUNT_ID}/invites/{inv_id}")
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-BQe62tYy.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-CNWPOsK5.css">
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="runningTask" 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
- {{ runningTask.command }} 执行中...
 
 
 
 
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="runningTask" @task-started="onTaskStarted" @refresh="refresh" />
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([api.getStatus(), api.getTasks()])
 
 
 
 
 
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 (!runningTask.value && interval < 600000) {
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 bg-gray-900 border border-gray-800 rounded-xl p-4">
3
- <div class="flex items-center justify-between mb-4">
4
- <h2 class="text-lg font-semibold text-white">巡检设置</h2>
5
- <span v-if="saved" class="text-xs text-green-400 transition">已保存</span>
6
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
- <div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
9
- <div>
10
- <label class="block text-sm text-gray-400 mb-1">巡检间隔</label>
11
- <div class="flex items-center gap-2">
12
- <input v-model.number="form.interval" type="number" min="1"
13
- 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" />
14
- <span class="text-sm text-gray-500 shrink-0">分钟</span>
 
 
 
 
 
 
 
 
 
15
  </div>
16
  </div>
17
- <div>
18
- <label class="block text-sm text-gray-400 mb-1">额度阈值</label>
19
- <div class="flex items-center gap-2">
20
- <input v-model.number="form.threshold" type="number" min="1" max="100"
21
- 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" />
22
- <span class="text-sm text-gray-500 shrink-0">%</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  </div>
24
  </div>
25
- <div>
26
- <label class="block text-sm text-gray-400 mb-1">触发账号数</label>
27
- <div class="flex items-center gap-2">
28
- <input v-model.number="form.min_low" type="number" min="1"
29
- 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" />
30
- <span class="text-sm text-gray-500 shrink-0">个</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  </div>
32
  </div>
33
  </div>
34
 
35
- <div class="mt-3 flex items-center justify-between">
36
- <p class="text-xs text-gray-500">
37
- {{ form.interval }} 分钟查一次,{{ form.min_low }} 个以上账号剩余低于 {{ form.threshold }}% 时自动轮转
38
- </p>
39
- <button @click="save" :disabled="saving"
40
- class="px-4 py-1.5 bg-blue-600 hover:bg-blue-500 text-white text-sm rounded-lg transition disabled:opacity-50">
41
- {{ saving ? '保存中...' : '保存' }}
42
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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="!!runningTask"
8
  class="px-4 py-2 rounded-lg text-sm font-medium transition border"
9
- :class="runningTask
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