@zqagent/shared 0.1.0 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/hub-api.cjs CHANGED
@@ -41,6 +41,7 @@ __export(hub_api_exports, {
41
41
  hubAdminRefund: () => hubAdminRefund,
42
42
  hubAdminRegisterAttempts: () => hubAdminRegisterAttempts,
43
43
  hubAdminRemoveSocialQr: () => hubAdminRemoveSocialQr,
44
+ hubAdminResetPassword: () => hubAdminResetPassword,
44
45
  hubAdminSetRole: () => hubAdminSetRole,
45
46
  hubAdminSocialQrs: () => hubAdminSocialQrs,
46
47
  hubAdminTopup: () => hubAdminTopup,
@@ -120,7 +121,9 @@ __export(hub_api_exports, {
120
121
  hubUpdateProfile: () => hubUpdateProfile,
121
122
  hubWallet: () => hubWallet,
122
123
  hubWalletTopup: () => hubWalletTopup,
123
- setHubBaseResolver: () => setHubBaseResolver
124
+ isSessionInvalidError: () => isSessionInvalidError,
125
+ setHubBaseResolver: () => setHubBaseResolver,
126
+ setHubSessionInvalidHandler: () => setHubSessionInvalidHandler
124
127
  });
125
128
  module.exports = __toCommonJS(hub_api_exports);
126
129
  function activityKey(o) {
@@ -140,6 +143,16 @@ function resolveBase(baseUrl) {
140
143
  return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;
141
144
  }
142
145
  var HUB_REQUEST_TIMEOUT_MS = 3e4;
146
+ var SESSION_INVALID_CODES = /* @__PURE__ */ new Set(["unauthorized", "account_disabled", "account_deleted"]);
147
+ function isSessionInvalidError(e) {
148
+ const { status, code } = e ?? {};
149
+ if (status === 401) return true;
150
+ return typeof code === "string" && SESSION_INVALID_CODES.has(code);
151
+ }
152
+ var sessionInvalidHandler = null;
153
+ function setHubSessionInvalidHandler(handler) {
154
+ sessionInvalidHandler = handler;
155
+ }
143
156
  async function req(baseUrl, token, path, init = {}) {
144
157
  const headers = {
145
158
  ...init.body ? { "Content-Type": "application/json" } : {},
@@ -171,6 +184,12 @@ async function req(baseUrl, token, path, init = {}) {
171
184
  const e = new Error(err?.message ?? `HTTP ${res.status}`);
172
185
  e.code = err?.code;
173
186
  e.status = res.status;
187
+ if (sessionInvalidHandler && !path.startsWith("/api/hub/auth/") && isSessionInvalidError(e)) {
188
+ try {
189
+ sessionInvalidHandler({ status: res.status, code: e.code });
190
+ } catch {
191
+ }
192
+ }
174
193
  throw e;
175
194
  }
176
195
  return payload?.data;
@@ -529,6 +548,14 @@ async function hubAdminSetRole(baseUrl, token, userId, role) {
529
548
  { method: "PATCH", body: JSON.stringify({ role }) }
530
549
  );
531
550
  }
551
+ async function hubAdminResetPassword(baseUrl, token, userId, newPassword) {
552
+ return await req(
553
+ baseUrl,
554
+ token,
555
+ `/api/hub/admin/users/${encodeURIComponent(userId)}/password`,
556
+ { method: "POST", body: JSON.stringify({ newPassword }) }
557
+ );
558
+ }
532
559
  async function hubAdminNotifyUser(baseUrl, token, userId, input) {
533
560
  return await req(
534
561
  baseUrl,
@@ -817,6 +844,7 @@ async function hubStatement(baseUrl, token, range = {}) {
817
844
  hubAdminRefund,
818
845
  hubAdminRegisterAttempts,
819
846
  hubAdminRemoveSocialQr,
847
+ hubAdminResetPassword,
820
848
  hubAdminSetRole,
821
849
  hubAdminSocialQrs,
822
850
  hubAdminTopup,
@@ -896,6 +924,8 @@ async function hubStatement(baseUrl, token, range = {}) {
896
924
  hubUpdateProfile,
897
925
  hubWallet,
898
926
  hubWalletTopup,
899
- setHubBaseResolver
927
+ isSessionInvalidError,
928
+ setHubBaseResolver,
929
+ setHubSessionInvalidHandler
900
930
  });
901
931
  //# sourceMappingURL=hub-api.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hub-api.ts"],"sourcesContent":["/**\n * Hub REST 客户端(浏览器/桌面共用,纯 fetch,无 Node 依赖)\n *\n * 账号/节点管理/节点/指令代理/消息/事件流。浏览器只持会话令牌,节点密钥留在 Hub。\n * 供 packages/portal(统一控制台:桌面壳内嵌 + 浏览器用户中心)复用。\n */\nimport type { FleetCredential, FleetNodeStats, SessionStateEntry } from \"./fleet.js\";\n\n/**\n * 列表排序键:任务请求开始时间优先,旧数据(该字段上线前)回退完成时间。\n *\n * 工作台的节点 / 项目 / 任务三处列表统一用它倒序,保证「最近在跑什么」排最前。\n * 返回空串表示这个条目从没跑过任务,倒序时自然落到最后。\n */\nexport function activityKey(o: {\n lastStartedAt?: string | null;\n lastCompletedAt?: string | null;\n}): string {\n return o.lastStartedAt ?? o.lastCompletedAt ?? \"\";\n}\n\n/**\n * 按最近一次任务请求的开始时间倒序(最新的在前)。\n *\n * `tieBreak` 只在时间完全相同时使用(如从没跑过的条目之间),保证顺序稳定、\n * 刷新不会跳动。\n */\nexport function compareByActivity<\n T extends { lastStartedAt?: string | null; lastCompletedAt?: string | null },\n>(a: T, b: T, tieBreak?: (x: T, y: T) => number): number {\n const ak = activityKey(a);\n const bk = activityKey(b);\n if (ak !== bk) return bk.localeCompare(ak);\n return tieBreak ? tieBreak(a, b) : 0;\n}\n\n/**\n * Hub 基址解析钩子:宿主可把逻辑 Hub 基址映射到「本机引擎中转」地址\n * (桌面端经 127.0.0.1 转发,绕开浏览器跨域)。默认恒等。\n */\nlet baseUrlResolver: ((baseUrl: string) => string) | null = null;\n\nexport function setHubBaseResolver(fn: ((baseUrl: string) => string) | null): void {\n baseUrlResolver = fn;\n}\n\nfunction resolveBase(baseUrl: string): string {\n return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;\n}\n\n\nexport interface HubFleetSummary {\n id: string;\n name: string;\n /** 系统内置「未分组」 */\n ungrouped?: boolean;\n nodeCount: number;\n createdAt: string;\n}\n\n/** 节点级汇总统计(节点 info 上报) */\nexport interface HubNodeTotals {\n projects: number;\n sessions: number;\n running: number;\n waiting: number;\n}\n\nexport interface HubNodeSummary {\n nodeId: string;\n label?: string;\n /** 计费状态;suspended = 余额不足暂停(缺省视为可用) */\n billingStatus?: \"active\" | \"suspended\";\n billingSuspendedReason?: \"insufficient\";\n online: boolean;\n lastSeen?: string;\n platform?: string;\n mode?: string;\n version?: string;\n /** 节点自报主 IP(新版节点上报;旧版缺省) */\n ip?: string;\n projects: Array<{\n id: string;\n name: string;\n sessionCount?: number;\n running?: number;\n waiting?: number;\n /** 项目工作目录(节点上报;旧版节点缺省) */\n workspaceRoot?: string | null;\n }>;\n /** 项目/会话汇总(新版节点上报;旧版为 null) */\n totals?: HubNodeTotals | null;\n /**\n * 会话状态快照(只含 projectId / sessionId / name / status)。\n *\n * 工作台/节点首页用它建立初始视图,之后的变更由账号级 WSS 的\n * `session_status` 通知增量推送。明细(消息/模型/token)不在这里。\n */\n sessions?: SessionStateEntry[] | null;\n /** 累计运行统计(新版节点上报;旧版为 null) */\n stats?: FleetNodeStats | null;\n /**\n * 该节点下任一任务最近一次请求的开始时间(节点列表按它倒序)。\n * 从未发起过任务时为 null(旧版节点也不上报)。\n */\n lastStartedAt?: string | null;\n}\n\n/** 节点详情(含节点管理归属与创建时间) */\nexport interface HubNodeDetail extends HubNodeSummary {\n fleetId: string;\n fleetName: string;\n createdAt: string;\n /** 访问身份:owner = 自己的节点,operator = 别人共享给我 */\n access?: HubNodeAccessView;\n}\n\n/** 当前账号对某节点的访问身份 */\nexport interface HubNodeAccessView {\n role: \"owner\" | \"operator\";\n /** 共享到期时刻(ISO);owner 为 null */\n expiresAt: string | null;\n /** 共享期间是否允许消耗所有者的模型额度 */\n allowModelSpend: boolean;\n /** 所有者账号(掩码);owner 自己看时为空串 */\n ownerLabel?: string;\n}\n\n/** 共享给我的节点(列表项,形状与节点摘要对齐并附加授权信息) */\nexport interface HubSharedNode extends HubNodeSummary {\n fleetId: string;\n fleetName: string;\n ownerLabel: string;\n grantId: string;\n expiresAt: string;\n allowModelSpend: boolean;\n}\n\n/** 共享授权(owner 视角) */\nexport interface HubNodeGrantView {\n id: string;\n nodeId: string;\n toUser: { id: string; label: string };\n role: \"operator\";\n allowModelSpend: boolean;\n expiresAt: string;\n status: \"active\" | \"expired\" | \"revoked\";\n createdAt: string;\n revokedAt: string | null;\n revokedReason: \"owner\" | \"self\" | \"node_deleted\" | \"account_deleted\" | null;\n}\n\n/**\n * 我共享出去的授权(跨节点聚合,设备列表用)。\n *\n * 与 `HubNodeGrantView` 的区别:多带节点名与分组名,便于在「设备」列表里\n * 直接给出「哪台机器 · 共享给谁 · 何时共享 · 何时到期」,不用再逐个节点查。\n * 已撤销的授权不在列表里(节点删除会级联撤销)。\n */\nexport interface HubOutgoingGrant {\n nodeId: string;\n /** 节点名;节点从未命名时为 null(前端回退显示 nodeId) */\n label: string | null;\n fleetId: string;\n fleetName: string;\n grantId: string;\n toUser: { id: string; label: string };\n allowModelSpend: boolean;\n /** 共享时间(首次授权时刻;延长不会刷新它) */\n createdAt: string;\n /** 到期时刻(ISO,绝对时间) */\n expiresAt: string;\n /** 只区分生效 / 已过期:已撤销的不返回 */\n status: \"active\" | \"expired\";\n}\n\n/** 节点操作记录的一条 */\nexport interface HubNodeActivityItem {\n id: string;\n ts: string;\n nodeId: string;\n actor: {\n userId: string;\n label: string;\n /**\n * 以什么身份操作:owner = 节点所有者,operator = 共享接收者。\n * unknown = actor 账号已不存在(或回写时无法解析),前端据此不臆断身份。\n */\n via: \"owner\" | \"operator\" | \"unknown\";\n };\n action: string;\n /** 服务端兜底摘要(前端优先按 action + 结构化字段自行渲染多语言文案) */\n summary: string;\n commandType: string | null;\n projectId: string | null;\n sessionId: string | null;\n result: \"ok\" | \"denied\" | \"error\";\n /**\n * 本条动作的对象补充,按 action 取值:\n * - rename_node / rename_node_self:新名称\n * - move_node:移动到的分组名\n * - grant_create / grant_extend / grant_revoke / grant_leave:被授权的账号(已掩码)\n */\n target: string | null;\n /** 发起客户端(web / desktop / cli…);旧记录为 null */\n client: string | null;\n /** 命令级记录的请求 ID(排障时对得上节点日志);非命令记录为 null */\n reqId: string | null;\n}\n\n/** 项目总览里的单条任务(会话) */\nexport interface HubProjectTask {\n sessionId: string;\n name: string | null;\n status: string;\n lastUserMessage: string | null;\n /** 该任务最近一次请求的开始时间(排序键) */\n lastStartedAt: string | null;\n lastCompletedAt: string | null;\n}\n\n/** 项目总览行:跨节点聚合的项目视图 */\nexport interface HubProjectRow {\n projectId: string;\n name: string;\n workspaceRoot: string | null;\n nodeId: string;\n nodeLabel: string;\n fleetId: string;\n fleetName: string;\n online: boolean;\n sessionCount: number;\n running: number;\n waiting: number;\n /** 该项目下所有任务里最近一次请求的开始时间(列表按它倒序) */\n lastStartedAt: string | null;\n /** 该项目下所有任务最近一次成功完成的时刻(ISO 8601;从未完成时 null) */\n lastCompletedAt: string | null;\n /** 任务总数(tasks 最多回传前 20 条) */\n taskCount: number;\n tasks: HubProjectTask[];\n}\n\n/** 项目总览分页响应 */\nexport interface HubProjectPage {\n total: number;\n items: HubProjectRow[];\n}\n\n/** 任务总览行:跨节点聚合的会话视图(数据来自 Hub 保存的节点快照) */\nexport interface HubTaskRow {\n sessionId: string;\n name: string | null;\n status: string;\n lastUserMessage: string | null;\n /** 该任务最近一次请求的开始时间(列表按它倒序) */\n lastStartedAt: string | null;\n lastCompletedAt: string | null;\n projectId: string;\n projectName: string;\n nodeId: string;\n nodeLabel: string;\n fleetId: string;\n fleetName: string;\n online: boolean;\n}\n\n/** 任务筛选口径的计数(始终基于全部任务,不受筛选影响) */\nexport interface HubTaskCounts {\n all: number;\n running: number;\n waiting: number;\n error: number;\n completed: number;\n}\n\n/** 任务总览分页响应 */\nexport interface HubTaskPage {\n total: number;\n counts: HubTaskCounts;\n items: HubTaskRow[];\n}\n\nexport interface HubPlan {\n id: \"free\" | \"pro\" | \"team\";\n name: string;\n priceMonthly: number;\n priceYearly: number;\n limits: { maxNodes: number; maxFleets: number; notifications: boolean; historyDays: number };\n}\n\nexport interface HubSubscriptionView {\n plan: string;\n planExpiresAt: string | null;\n limits: { maxNodes: number; maxFleets: number; notifications: boolean; historyDays: number };\n usage: { fleets: number; nodes: number };\n}\n\nexport interface HubOrderView {\n id: string;\n plan: string;\n period: string;\n amount: number;\n provider: string;\n status: string;\n createdAt: string;\n paidAt?: string;\n /** plan=套餐(已停售);wallet/api_topup=账户充值 */\n purpose?: \"plan\" | \"wallet\" | \"api_topup\";\n}\n\nexport interface HubUserView {\n id: string;\n email: string | null;\n phone: string | null;\n /** 可选昵称(仅展示;缺省时前端回退到邮箱/手机号) */\n nickname?: string | null;\n /** 可选头像(`data:image/...;base64,...`,客户端已裁成 256×256) */\n avatar?: string | null;\n plan: string;\n /** 是否已设置可用密码(短信登录自动注册后为 false) */\n passwordSet?: boolean;\n /** 系统管理员角色(缺省 user);仅 admin 可进入 /admin */\n role?: \"user\" | \"admin\";\n /** 账号状态(缺省 active) */\n status?: \"active\" | \"disabled\" | \"deleted\";\n}\n\nexport interface HubCaptchaView {\n captchaId: string;\n svg: string;\n /** 仅调试模式返回(HUB_SMS_DEBUG=1) */\n debugCode?: string;\n}\n\nexport type HubSmsPurpose = \"register\" | \"login\" | \"reset\";\n\nexport interface HubAuthInput {\n email?: string;\n phone?: string;\n password?: string;\n code?: string;\n emailCode?: string;\n /** 注册时可选填写的昵称(最长 32 个字符;之后可在个人中心修改) */\n nickname?: string;\n}\n\nexport interface HubPairingView {\n /** 对外可达的网关 wss 地址(节点凭此直连 Rust 网关;Hub 未配置时为缺省) */\n gatewayUrl?: string;\n fleetToken: string;\n fleetSecret: string;\n expiresAt?: string;\n}\n\nexport interface HubEnrollmentView {\n code: string;\n expiresAt: string;\n}\n\nexport interface HubNotificationView {\n id: string;\n userId: string;\n type: string;\n title: string;\n body?: string;\n link?: string;\n readAt?: string;\n createdAt: string;\n}\n\n/** 单次 Hub 请求超时。弱网下 fetch 默认没有超时,会一直挂到 undici 的 300s 兜底。 */\nconst HUB_REQUEST_TIMEOUT_MS = 30_000;\n\n/**\n * Hub 请求入口。\n *\n * 只有 **GET** 做重试(幂等);POST/PATCH 重试可能造成重复下单/重复接入,\n * 因此一次失败就直接抛出。4xx 属于确定性错误,不重试。\n */\nasync function req(\n baseUrl: string,\n token: string | null,\n path: string,\n init: RequestInit = {},\n): Promise<unknown> {\n const headers: Record<string, string> = {\n ...(init.body ? { \"Content-Type\": \"application/json\" } : {}),\n ...((init.headers as Record<string, string>) ?? {}),\n };\n if (token) headers.Authorization = `Bearer ${token}`;\n const url = `${resolveBase(baseUrl).replace(/\\/$/, \"\")}${path}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const attempts = method === \"GET\" ? 3 : 1;\n // 旧运行时(部分小程序基础库)没有 AbortSignal.timeout:降级为无超时,\n // 保持原行为而不是直接抛 TypeError。\n const newTimeoutSignal = (): AbortSignal | undefined =>\n typeof AbortSignal.timeout === \"function\"\n ? AbortSignal.timeout(HUB_REQUEST_TIMEOUT_MS)\n : undefined;\n\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n const res = await fetch(url, {\n ...init,\n headers,\n // 每次重试都要新的 signal:超时后的 signal 已 aborted,复用会立刻失败\n signal: init.signal ?? newTimeoutSignal(),\n });\n const text = await res.text();\n let payload: unknown = null;\n try {\n payload = text ? JSON.parse(text) : null;\n } catch {\n // 响应被截断(弱网常见):当作可重试的网络错误\n throw new Error(`Hub 响应非法(HTTP ${res.status})`);\n }\n if (!res.ok) {\n const err = (payload as { error?: { code?: string; message?: string } })?.error;\n const e = new Error(err?.message ?? `HTTP ${res.status}`) as Error & {\n code?: string;\n status?: number;\n };\n e.code = err?.code;\n e.status = res.status;\n throw e;\n }\n return (payload as { data?: unknown })?.data;\n } catch (e) {\n lastError = e;\n const status = (e as { status?: number }).status;\n const retryable = status === undefined || status >= 500;\n if (i + 1 >= attempts || !retryable) throw e;\n // 指数退避 + 抖动:多个节点/页面同时重试时不要形成同步冲击\n await new Promise((r) => setTimeout(r, 300 * 2 ** i + Math.random() * 200));\n }\n }\n throw lastError;\n}\n\nexport async function hubRegister(\n baseUrl: string,\n email: string,\n password: string,\n): Promise<{ token: string }> {\n return hubRegisterAccount(baseUrl, { email, password });\n}\n\n/** 注册(邮箱或手机号;手机号需先获取短信验证码;可选填写昵称) */\nexport async function hubRegisterAccount(\n baseUrl: string,\n input: HubAuthInput,\n): Promise<{ token: string; user?: HubUserView }> {\n return (await req(baseUrl, null, \"/api/hub/auth/register\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { token: string; user?: HubUserView };\n}\n\nexport async function hubLogin(\n baseUrl: string,\n email: string,\n password: string,\n): Promise<{ token: string }> {\n return hubLoginAccount(baseUrl, { email, password });\n}\n\n/** 登录(邮箱+密码,或手机号+密码/短信验证码;手机号验证码登录未注册时自动注册) */\nexport async function hubLoginAccount(\n baseUrl: string,\n input: HubAuthInput,\n): Promise<{ token: string; user?: HubUserView; autoRegistered?: boolean }> {\n return (await req(baseUrl, null, \"/api/hub/auth/login\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { token: string; user?: HubUserView; autoRegistered?: boolean };\n}\n\n/** 图形验证码(发送短信前必填) */\nexport async function hubCaptcha(baseUrl: string): Promise<HubCaptchaView> {\n return (await req(baseUrl, null, \"/api/hub/auth/captcha\")) as HubCaptchaView;\n}\n\n/** 发送短信验证码(purpose:register/login/reset;本机当天第 4 次起需图形验证码) */\nexport async function hubSendSms(\n baseUrl: string,\n input: {\n phone: string;\n purpose: HubSmsPurpose;\n captchaId?: string;\n captcha?: string;\n /** 本机设备标识(登录页持久化生成);缺省时服务端按出口 IP 计数 */\n clientId?: string;\n },\n): Promise<{ sent: boolean; expiresIn: number; debugCode?: string }> {\n return (await req(baseUrl, null, \"/api/hub/auth/sms/send\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { sent: boolean; expiresIn: number; debugCode?: string };\n}\n\n/** 验证码发送状态:本机当天已发次数与本次是否需要图形验证码 */\nexport interface HubSendCodeStatus {\n captchaRequired: boolean;\n /** 本机今天已发送次数 */\n used: number;\n /** 本机今天还能免图形验证码发送的次数 */\n remaining: number;\n}\n\n/**\n * 查询发送状态(决定登录/注册/找回密码页是否显示图形验证码)。\n *\n * 计数维度是本机设备标识 + 自然日,与手机号、邮箱无关。\n */\nexport async function hubSendCodeStatus(\n baseUrl: string,\n clientId?: string,\n): Promise<HubSendCodeStatus> {\n const qs = clientId ? `?clientId=${encodeURIComponent(clientId)}` : \"\";\n return (await req(baseUrl, null, `/api/hub/auth/code/status${qs}`)) as HubSendCodeStatus;\n}\n\n/**\n * 短信发送状态(旧接口,兼容既有调用)。\n *\n * 与 `hubSendCodeStatus` 等价,只是沿用 `/auth/sms/status` 路径。\n */\nexport async function hubSmsStatus(\n baseUrl: string,\n phone: string,\n clientId?: string,\n): Promise<HubSendCodeStatus> {\n const qs = new URLSearchParams({ phone });\n if (clientId) qs.set(\"clientId\", clientId);\n return (await req(\n baseUrl,\n null,\n `/api/hub/auth/sms/status?${qs.toString()}`,\n )) as HubSendCodeStatus;\n}\n\n/** 发送邮箱验证码(注册校验 / 找回密码;策略与短信一致) */\nexport async function hubSendEmailCode(\n baseUrl: string,\n input: {\n email: string;\n /** 缺省 register;reset 用于找回密码(要求邮箱已注册) */\n purpose?: \"register\" | \"reset\";\n /** 仅在本机当天第 4 次起需要 */\n captchaId?: string;\n captcha?: string;\n /** 本机设备标识;缺省时服务端按出口 IP 计数 */\n clientId?: string;\n },\n): Promise<{ sent: boolean; expiresIn: number; debugCode?: string }> {\n return (await req(baseUrl, null, \"/api/hub/auth/email/send\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { sent: boolean; expiresIn: number; debugCode?: string };\n}\n\n/** 忘记密码:手机号 + 短信验证码,或邮箱 + 邮箱验证码 */\nexport async function hubResetPassword(\n baseUrl: string,\n input: { phone?: string; email?: string; code: string; newPassword: string },\n): Promise<{ changed: boolean }> {\n return (await req(baseUrl, null, \"/api/hub/auth/password/reset\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { changed: boolean };\n}\n\n/** 当前用户(id/email/nickname/生效套餐) */\nexport async function hubMe(baseUrl: string, token: string): Promise<HubUserView> {\n return (await req(baseUrl, token, \"/api/hub/me\")) as HubUserView;\n}\n\n/**\n * 更新个人资料(昵称 / 头像)。\n *\n * 字段按需更新:不传的字段保持原样。`nickname` / `avatar` 传空串或 `null` 表示清除。\n */\nexport async function hubUpdateProfile(\n baseUrl: string,\n token: string,\n input: { nickname?: string; avatar?: string | null },\n): Promise<{ nickname: string | null; avatar?: string | null }> {\n return (await req(baseUrl, token, \"/api/hub/me\", {\n method: \"PATCH\",\n body: JSON.stringify(input),\n })) as { nickname: string | null; avatar?: string | null };\n}\n\n/** 网关账号级订阅涉及的节点(nodeId → 归属节点管理) */\nexport interface HubGatewayFleet {\n id: string;\n nodes: string[];\n}\n\n/** 账号级实时通道的网关凭据(M5:控制台/用户中心直连 Rust 网关) */\nexport interface HubGatewayCredentialView {\n /** 对外可达的网关 wss 地址 */\n url: string;\n /**\n * 已签名终端令牌(scope = 本人全部节点)。\n *\n * `null` 表示账号下没有任何节点——不必建连接(网关会拒绝空 scope 的令牌)。\n */\n credential: {\n /** base64(wire `Auth`),原样作为 AUTH 帧载荷 */\n auth: string;\n /** 过期时刻(毫秒) */\n expires: number;\n scope: string[];\n kind: \"terminal\";\n client: string;\n } | null;\n fleets: HubGatewayFleet[];\n}\n\n/** 单节点终端凭据(`POST .../terminal-credential` 的返回体) */\nexport interface HubTerminalCredential {\n /** 对外可达的网关 wss 地址(与令牌一并返回,客户端一次请求即可建连) */\n url: string;\n /** base64(wire `Auth`),原样作为 AUTH 帧载荷 */\n auth: string;\n expires: number;\n scope: string[];\n kind: \"terminal\";\n client: string;\n}\n\n/**\n * 获取**单个节点**的终端网关凭据(节点控制台页用)。\n *\n * 与 `hubRealtimeCredential`(账号级,覆盖本人全部节点)的区别是授权范围:\n * 这里只授权该节点,最小权限。\n */\nexport async function hubTerminalCredential(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n client = \"web\",\n): Promise<HubTerminalCredential> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/terminal-credential`,\n { method: \"POST\", body: JSON.stringify({ client }) },\n )) as HubTerminalCredential;\n}\n\n/**\n * 获取账号级终端网关凭据。\n *\n * 取代从前那套 broker 直连账号:令牌授权范围\n * = 本人全部节点,供账号级实时通道一条 WSS 订阅所有节点。\n */\nexport async function hubRealtimeCredential(\n baseUrl: string,\n token: string,\n client = \"web\",\n): Promise<HubGatewayCredentialView> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/realtime-credential?client=${encodeURIComponent(client)}`,\n )) as HubGatewayCredentialView;\n}\n\n/** 修改密码(手机号账号需短信验证码,可附原密码) */\nexport async function hubChangePassword(\n baseUrl: string,\n token: string,\n input: { oldPassword?: string; newPassword: string; code?: string },\n): Promise<{ changed: boolean }> {\n return (await req(baseUrl, token, \"/api/hub/auth/password\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { changed: boolean };\n}\n\nexport async function hubListFleets(\n baseUrl: string,\n token: string,\n): Promise<HubFleetSummary[]> {\n return (await req(baseUrl, token, \"/api/hub/fleets\")) as HubFleetSummary[];\n}\n\nexport async function hubCreateFleet(\n baseUrl: string,\n token: string,\n name: string,\n): Promise<HubFleetSummary> {\n return (await req(baseUrl, token, \"/api/hub/fleets\", {\n method: \"POST\",\n body: JSON.stringify({ name }),\n })) as HubFleetSummary;\n}\n\nexport async function hubDeleteFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}`, { method: \"DELETE\" });\n}\n\n/** 重命名分组 */\nexport async function hubUpdateFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n name: string,\n): Promise<HubFleetSummary> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ name }),\n })) as HubFleetSummary;\n}\n\n/** 节点管理配对凭据(网关地址 + fleetToken/fleetSecret,供手工接入节点) */\nexport async function hubFleetPairing(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubPairingView> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/pairing`)) as HubPairingView;\n}\n\n/** 生成一次性注册码(15 分钟有效) */\nexport async function hubCreateEnrollment(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubEnrollmentView> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/enrollment`, {\n method: \"POST\",\n })) as HubEnrollmentView;\n}\n\nexport async function hubListNodes(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubNodeSummary[]> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes`)) as HubNodeSummary[];\n}\n\n/** 项目总览(跨节点聚合,按最后完成任务时间倒序分页;`q` 按项目 / 节点 / 路径模糊过滤) */\nexport async function hubListProjects(\n baseUrl: string,\n token: string,\n opts: { limit?: number; offset?: number; q?: string } = {},\n): Promise<HubProjectPage> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 20), 1), 200);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n const q = (opts.q ?? \"\").trim();\n return (await req(\n baseUrl,\n token,\n `/api/hub/projects?limit=${limit}&offset=${offset}${q ? `&q=${encodeURIComponent(q)}` : \"\"}`,\n )) as HubProjectPage;\n}\n\n/**\n * 任务总览(跨节点聚合,按需要人处理 / 运行中 / 完成时间排序分页)。\n *\n * `status` 与工作台筛选条一致:all / running / waiting / error / completed。\n * `q` 按任务名 / 项目 / 节点 / 最近消息模糊过滤(服务端过滤,分页与总数才准确)。\n */\nexport async function hubListTasks(\n baseUrl: string,\n token: string,\n opts: { limit?: number; offset?: number; status?: string; q?: string } = {},\n): Promise<HubTaskPage> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 50), 1), 500);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n const status = opts.status ?? \"all\";\n const q = (opts.q ?? \"\").trim();\n return (await req(\n baseUrl,\n token,\n `/api/hub/tasks?limit=${limit}&offset=${offset}&status=${encodeURIComponent(status)}` +\n (q ? `&q=${encodeURIComponent(q)}` : \"\"),\n )) as HubTaskPage;\n}\n\n/** 单节点详情(状态/统计/项目/归属) */\nexport async function hubGetNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<HubNodeDetail> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n )) as HubNodeDetail;\n}\n\n/**\n * 单节点详情(不依赖 fleetId 的统一入口)。\n *\n * 共享成员手里只有 nodeId + owner 的 fleetId(从共享列表拿到);新代码用这条,\n * 避免为了读详情先拼一条自己无权访问的 fleet 路径。\n */\nexport async function hubGetNodeById(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<HubNodeDetail> {\n return (await req(baseUrl, token, `/api/hub/nodes/${encodeURIComponent(nodeId)}`)) as HubNodeDetail;\n}\n\n/** 共享给我的节点列表 */\nexport async function hubSharedNodes(baseUrl: string, token: string): Promise<HubSharedNode[]> {\n return (await req(baseUrl, token, \"/api/hub/shared/nodes\")) as HubSharedNode[];\n}\n\n/**\n * 我共享出去的授权列表(跨节点聚合)。\n *\n * 只返回未撤销的授权(生效中 + 已过期),供设备列表展示「共享给谁、何时\n * 共享、何时到期」;要撤销仍需走单节点的 `hubRevokeNodeGrant`。\n */\nexport async function hubOutgoingGrants(\n baseUrl: string,\n token: string,\n): Promise<HubOutgoingGrant[]> {\n return (await req(baseUrl, token, \"/api/hub/shared/outgoing\")) as HubOutgoingGrant[];\n}\n\n/** 共享授权列表(仅节点所有者可调) */\nexport async function hubListNodeGrants(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<HubNodeGrantView[]> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants`,\n )) as HubNodeGrantView[];\n}\n\n/**\n * 新建 / 延长共享授权。\n *\n * `expiresAt` 为 ISO 字符串(前端 datetime-local 给出,**精确到分**);\n * `expiresInDays` 是快捷天数(两者二选一,服务端统一收敛为绝对时刻)。\n * 同一账号重复授权 = 延长,不会产生第二条记录。\n */\nexport async function hubCreateNodeGrant(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n input: { account: string; expiresAt?: string; expiresInDays?: number; allowModelSpend?: boolean },\n): Promise<{ grant: HubNodeGrantView }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants`,\n { method: \"POST\", body: JSON.stringify(input) },\n )) as { grant: HubNodeGrantView };\n}\n\n/** 撤销共享授权(仅节点所有者) */\nexport async function hubRevokeNodeGrant(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n grantId: string,\n): Promise<{ revoked: boolean }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants/${grantId}`,\n { method: \"DELETE\" },\n )) as { revoked: boolean };\n}\n\n/** 退出共享(被授权人主动放弃) */\nexport async function hubLeaveSharedNode(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<{ left: boolean }> {\n return (await req(baseUrl, token, `/api/hub/shared/nodes/${encodeURIComponent(nodeId)}/leave`, {\n method: \"POST\",\n })) as { left: boolean };\n}\n\n/**\n * 节点操作记录(owner 与共享成员看到**同一份**)。\n *\n * 命令级条目由网关回写(`GATEWAY_HUB_ACTIVITY_URL`);未配置时只有控制面操作\n * (改名/挪组/进入控制台/共享关系变更)。\n */\nexport async function hubNodeActivity(\n baseUrl: string,\n token: string,\n nodeId: string,\n opts: { limit?: number; offset?: number } = {},\n): Promise<{ total: number; items: HubNodeActivityItem[] }> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 50), 1), 200);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n return (await req(\n baseUrl,\n token,\n `/api/hub/nodes/${encodeURIComponent(nodeId)}/activity?limit=${limit}&offset=${offset}`,\n )) as { total: number; items: HubNodeActivityItem[] };\n}\n\nexport async function hubEnrollNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n label?: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes`, {\n method: \"POST\",\n body: JSON.stringify({ nodeId, label }),\n });\n}\n\n/**\n * 凭一次性注册码接入节点管理(公开接口,无需登录令牌)。\n *\n * 成功后返回节点管理密钥与网关地址,供节点写入 `fleet.json` 并连接。\n * 注册码一次性、短时有效;同一 nodeId 不可被其他节点管理纳管(防抢注)。\n */\nexport async function hubEnrollByCode(\n baseUrl: string,\n input: { code: string; nodeId: string; label?: string },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, null, \"/api/hub/enroll\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\n/**\n * 凭 Hub 账号接入节点(公开接口,无需令牌)。\n *\n * 节点首次启动时交互式输入账号,校验通过即领取节点管理密钥并绑定 nodeId;\n * 节点默认归入内置「未分组」(分组为空的节点在控制台集中显示)。\n */\nexport async function hubEnrollByAccount(\n baseUrl: string,\n input: {\n email?: string;\n phone?: string;\n password?: string;\n code?: string;\n nodeId: string;\n label?: string;\n },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, null, \"/api/hub/enroll/account\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\n/**\n * 凭「已登录的 Hub 令牌」接入节点(需登录)。\n *\n * 桌面端在用户登录成功后调用。相比 `/enroll/account` 的优势:短信验证码\n * 登录时验证码已被消费,无法再拿它校验一次;而登录令牌在手,就能直接换取\n * 节点管理凭据——「登录后本机自动接入、可直接用内置模型」因此覆盖所有登录方式。\n */\nexport async function hubEnrollByToken(\n baseUrl: string,\n token: string,\n input: { nodeId: string; label?: string },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, token, \"/api/hub/enroll/token\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\nexport async function hubDeleteNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes/${nodeId}`, { method: \"DELETE\" });\n}\n\n/** 重命名节点:Hub 更新 label 并下发节点写入本地配置 */\nexport async function hubUpdateNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n label: string,\n): Promise<{ nodeId: string; label: string | null }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n { method: \"PATCH\", body: JSON.stringify({ label }) },\n )) as { nodeId: string; label: string | null };\n}\n\n/**\n * 节点自助改名:节点用自身的 fleetToken 鉴权,把显示名写进 Hub 记录。\n *\n * 与 `hubUpdateNode`(用户令牌)落在同一份数据上,区别只是鉴权主体:\n * 本机没登录 Hub 用户态时(`zqagent rename`),节点只能凭自己的凭据改。\n */\nexport async function hubUpdateNodeLabelSelf(\n baseUrl: string,\n fleetToken: string,\n input: { nodeId: string; label: string },\n): Promise<{ nodeId: string; label: string }> {\n return (await req(baseUrl, fleetToken, \"/api/hub/nodes/self/label\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { nodeId: string; label: string };\n}\n\n/** 将节点移动到目标分组(仅改 Hub 记录;分组是控制面元数据,无需下发节点) */\nexport async function hubMoveNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n targetFleetId: string,\n): Promise<{ nodeId: string; fleetId: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n { method: \"PATCH\", body: JSON.stringify({ fleetId: targetFleetId }) },\n )) as { nodeId: string; fleetId: string };\n}\n\n/** 套餐列表(公开) */\nexport async function hubPlans(baseUrl: string): Promise<HubPlan[]> {\n return (await req(baseUrl, null, \"/api/hub/plans\")) as HubPlan[];\n}\n\n/** 当前订阅(套餐/到期/限额/用量) */\nexport async function hubSubscription(\n baseUrl: string,\n token: string,\n): Promise<HubSubscriptionView> {\n return (await req(baseUrl, token, \"/api/hub/subscription\")) as HubSubscriptionView;\n}\n\n/** 可用支付渠道 */\nexport async function hubChannels(\n baseUrl: string,\n token: string,\n): Promise<Array<{ name: string; configured: boolean; aggregate?: boolean }>> {\n return (await req(baseUrl, token, \"/api/hub/billing/channels\")) as Array<{\n name: string;\n configured: boolean;\n aggregate?: boolean;\n }>;\n}\n\n/** 我的订单 */\nexport async function hubOrders(baseUrl: string, token: string): Promise<HubOrderView[]> {\n return (await req(baseUrl, token, \"/api/hub/orders\")) as HubOrderView[];\n}\n\n/**\n * 主动同步订单状态。\n *\n * 扫码支付后前端轮询这里:回调可能丢失或延迟,后端会向渠道查询真实状态\n * 并在支付成功时完成入账。\n */\nexport async function hubSyncOrder(\n baseUrl: string,\n token: string,\n orderId: string,\n): Promise<{ order: HubOrderView; paid: boolean }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/orders/${encodeURIComponent(orderId)}/sync`,\n )) as { order: HubOrderView; paid: boolean };\n}\n\n\n\n/** 站内消息列表(含未读数) */\nexport async function hubNotifications(\n baseUrl: string,\n token: string,\n limit = 50,\n): Promise<{ items: HubNotificationView[]; unread: number }> {\n return (await req(baseUrl, token, `/api/hub/notifications?limit=${limit}`)) as {\n items: HubNotificationView[];\n unread: number;\n };\n}\n\n/** 标记站内消息已读(all=true 或指定 ids) */\nexport async function hubMarkNotificationsRead(\n baseUrl: string,\n token: string,\n input: { ids?: string[]; all?: boolean },\n): Promise<{ updated: number; unread: number }> {\n return (await req(baseUrl, token, \"/api/hub/notifications/read\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { updated: number; unread: number };\n}\n\n/** 账户级用量聚合(跨节点) */\nexport interface HubUsageAgg {\n calls: number;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n costUsd: number;\n unpriced: number;\n}\n\nexport interface HubUsageView {\n total: HubUsageAgg;\n nodes: HubUsageNodeRow[];\n days: { key: string; agg: HubUsageAgg }[];\n /**\n * 按项目拆分(同一项目在不同节点上分多条)。\n * `projectName` 由 Hub 从节点项目快照补齐;旧版节点不上报项目维度时为空数组。\n */\n projects?: HubUsageProjectRow[];\n from?: string;\n to?: string;\n}\n\n/** 用量按节点拆分行 */\nexport interface HubUsageNodeRow {\n nodeId: string;\n /** 节点展示名(Hub 节点记录里的 label;未命名时为 null,前端回退到 nodeId) */\n nodeLabel?: string | null;\n agg: HubUsageAgg;\n updatedAt: string;\n}\n\n/** 用量按项目拆分行 */\nexport interface HubUsageProjectRow {\n nodeId: string;\n /** 所属节点展示名(用于「项目已删除只剩 id」时定位) */\n nodeLabel?: string | null;\n projectId: string;\n /** 项目展示名(节点快照里取;节点已删除或旧版节点为 null) */\n projectName: string | null;\n agg: HubUsageAgg;\n}\n\nexport async function hubGetUsage(\n baseUrl: string,\n token: string,\n params: { from?: string; to?: string; groupBy?: \"node\" | \"day\" } = {},\n): Promise<HubUsageView> {\n const q = new URLSearchParams();\n if (params.from) q.set(\"from\", params.from);\n if (params.to) q.set(\"to\", params.to);\n if (params.groupBy) q.set(\"groupBy\", params.groupBy);\n const qs = q.toString();\n return (await req(baseUrl, token, `/api/hub/usage${qs ? `?${qs}` : \"\"}`)) as HubUsageView;\n}\n\n/** 节点上报用量(按天覆盖,幂等) */\nexport async function hubNodeUsageReport(\n baseUrl: string,\n token: string,\n nodeId: string,\n payload: {\n total?: unknown;\n byDay?: Record<string, unknown>;\n /** 项目维度:projectId → { total, byDay } */\n projects?: Record<string, unknown>;\n since?: string;\n },\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/nodes/${nodeId}/usage`, {\n method: \"POST\",\n body: JSON.stringify(payload),\n });\n}\n\n// ===================== 系统管理员(/admin;仅 role=admin) =====================\n\nexport type HubAdminRole = \"user\" | \"admin\";\nexport type HubAdminUserStatus = \"active\" | \"disabled\" | \"deleted\";\n\n/** 管理端用户行(列表与详情共用) */\nexport interface HubAdminUserRow {\n id: string;\n email: string | null;\n phone: string | null;\n nickname: string | null;\n plan: string;\n planExpiresAt: string | null;\n role: HubAdminRole;\n status: HubAdminUserStatus;\n disabledAt?: string | null;\n deletedAt?: string | null;\n createdAt: string;\n passwordSet: boolean;\n fleetCount: number;\n nodeCount: number;\n onlineCount: number;\n subscription: {\n plan: string;\n period: string;\n startedAt: string;\n expiresAt: string;\n channel: string;\n amount: number;\n } | null;\n ordersCount: number;\n paidCount: number;\n paidCents: number;\n /** 仅列表 ?withUsage=1 时返回 */\n usage?: HubUsageAgg;\n}\n\n/** 管理端总览 */\nexport interface HubAdminOverview {\n users: {\n total: number;\n active: number;\n disabled: number;\n deleted: number;\n admins: number;\n newLast7d: number;\n };\n fleets: { total: number };\n nodes: { total: number; online: number };\n orders: {\n total: number;\n paid: number;\n created: number;\n refunded: number;\n revenueCents: number;\n revenue30dCents: number;\n byProvider: Array<{ provider: string; count: number; amount: number }>;\n };\n plans: { free: number; pro: number; team: number };\n usage: HubUsageAgg | null;\n}\n\n/** 管理端节点(含网关实时状态) */\nexport interface HubAdminNode {\n nodeId: string;\n fleetId: string;\n label?: string;\n createdAt: string;\n state: {\n nodeId: string;\n online: boolean;\n lastSeen?: string;\n platform?: string;\n mode?: string;\n version?: string;\n ip?: string;\n projects?: Array<{ id: string; name: string; sessionCount?: number; running?: number; waiting?: number }>;\n totals?: HubNodeTotals | null;\n stats?: FleetNodeStats | null;\n };\n}\n\n/** 管理端订单行(含下单用户标识) */\nexport interface HubAdminOrderRow {\n id: string;\n userId: string;\n plan: string;\n period: string;\n amount: number;\n currency: string;\n provider: string;\n status: string;\n createdAt: string;\n paidAt?: string;\n providerTxnId?: string;\n user: { id: string; email: string | null; phone: string | null } | null;\n}\n\n/** 管理端用户详情 */\nexport interface HubAdminUserDetail {\n summary: HubAdminUserRow;\n fleets: Array<{\n id: string;\n ownerId: string;\n name: string;\n ungrouped?: boolean;\n nodeCount: number;\n createdAt: string;\n }>;\n nodes: HubAdminNode[];\n orders: HubAdminOrderRow[];\n subscription: HubAdminUserRow[\"subscription\"];\n usage: HubUsageView | null;\n}\n\nexport interface HubAdminAuditEntry {\n ts: string;\n userId?: string;\n action: string;\n detail?: Record<string, unknown>;\n}\n\n/** 注册尝试记录(成功/失败都记;供管理后台监控注册问题) */\nexport interface HubAdminRegisterAttempt {\n id: string;\n ts: string;\n /** register=注册表单;auto_login=短信验证码登录时自动注册 */\n source: \"register\" | \"auto_login\";\n success: boolean;\n email: string | null;\n phone: string | null;\n nickname: string | null;\n /** 注册成功时为新用户 id */\n userId: string | null;\n /** 失败错误码(bad_request / email_exists / phone_exists / email_code_invalid…) */\n errorCode: string | null;\n errorMessage: string | null;\n ip: string | null;\n userAgent: string | null;\n}\n\nfunction adminQuery(params: Record<string, string | number | undefined>): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === \"\") continue;\n q.set(k, String(v));\n }\n const qs = q.toString();\n return qs ? `?${qs}` : \"\";\n}\n\n/** 总览统计 */\nexport async function hubAdminOverview(\n baseUrl: string,\n token: string,\n): Promise<HubAdminOverview> {\n return (await req(baseUrl, token, \"/api/hub/admin/overview\")) as HubAdminOverview;\n}\n\n/** 用户列表(q 匹配 id/邮箱/手机号) */\nexport async function hubAdminUsers(\n baseUrl: string,\n token: string,\n params: {\n q?: string;\n status?: string;\n role?: string;\n plan?: string;\n withUsage?: boolean;\n limit?: number;\n offset?: number;\n } = {},\n): Promise<{ total: number; items: HubAdminUserRow[] }> {\n const qs = adminQuery({\n q: params.q,\n status: params.status,\n role: params.role,\n plan: params.plan,\n withUsage: params.withUsage ? 1 : undefined,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/users${qs}`)) as {\n total: number;\n items: HubAdminUserRow[];\n };\n}\n\n/** 用户详情(分组/节点/订单/订阅/用量) */\nexport async function hubAdminUserDetail(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<HubAdminUserDetail> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}`,\n )) as HubAdminUserDetail;\n}\n\n/** 修改套餐 / 有效期(planExpiresAt 传 null 清除) */\nexport async function hubAdminUpdateUser(\n baseUrl: string,\n token: string,\n userId: string,\n patch: { plan?: string; planExpiresAt?: string | null },\n): Promise<HubAdminUserRow> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}`, {\n method: \"PATCH\",\n body: JSON.stringify(patch),\n })) as HubAdminUserRow;\n}\n\nexport async function hubAdminDisableUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/disable`,\n { method: \"POST\" },\n )) as { id: string; status: string };\n}\n\nexport async function hubAdminEnableUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/enable`,\n { method: \"POST\" },\n )) as { id: string; status: string };\n}\n\n/** 软删用户(数据保留,可用 enable 恢复) */\nexport async function hubAdminDeleteUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}`, {\n method: \"DELETE\",\n })) as { id: string; status: string };\n}\n\n/** 设置/取消管理员(服务端守卫最后一个管理员) */\nexport async function hubAdminSetRole(\n baseUrl: string,\n token: string,\n userId: string,\n role: HubAdminRole,\n): Promise<{ id: string; role: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/role`,\n { method: \"PATCH\", body: JSON.stringify({ role }) },\n )) as { id: string; role: string };\n}\n\n/** 给用户发送站内通知 */\nexport async function hubAdminNotifyUser(\n baseUrl: string,\n token: string,\n userId: string,\n input: { title: string; body?: string; link?: string },\n): Promise<{ sent: boolean; notificationId: string | null }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/notify`,\n { method: \"POST\", body: JSON.stringify(input) },\n )) as { sent: boolean; notificationId: string | null };\n}\n\n/** 管理端删除分组(节点归入 owner 的「未分组」) */\nexport async function hubAdminDeleteFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<{ deleted: boolean; movedNodes: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/fleets/${encodeURIComponent(fleetId)}`, {\n method: \"DELETE\",\n })) as { deleted: boolean; movedNodes: number };\n}\n\n/** 管理端删除节点(仅移除 Hub 纳管记录) */\nexport async function hubAdminDeleteNode(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<{ deleted: boolean }> {\n return (await req(baseUrl, token, `/api/hub/admin/nodes/${encodeURIComponent(nodeId)}`, {\n method: \"DELETE\",\n })) as { deleted: boolean };\n}\n\n/** 全站订单(可按状态/渠道/日期/关键词过滤) */\nexport async function hubAdminOrders(\n baseUrl: string,\n token: string,\n params: {\n status?: string;\n provider?: string;\n q?: string;\n from?: string;\n to?: string;\n limit?: number;\n offset?: number;\n } = {},\n): Promise<{ total: number; items: HubAdminOrderRow[] }> {\n const qs = adminQuery({\n status: params.status,\n provider: params.provider,\n q: params.q,\n from: params.from,\n to: params.to,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/orders${qs}`)) as {\n total: number;\n items: HubAdminOrderRow[];\n };\n}\n\n/** 订单手动标记:paid(激活套餐,幂等)/ refunded(仅状态+通知) */\nexport async function hubAdminMarkOrder(\n baseUrl: string,\n token: string,\n orderId: string,\n input: { status: \"paid\" | \"refunded\"; note?: string },\n): Promise<HubAdminOrderRow> {\n return (await req(baseUrl, token, `/api/hub/admin/orders/${encodeURIComponent(orderId)}/mark`, {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as HubAdminOrderRow;\n}\n\n/** 审计日志(最新在前) */\nexport async function hubAdminAudit(\n baseUrl: string,\n token: string,\n params: { userId?: string; action?: string; limit?: number; offset?: number } = {},\n): Promise<{ total: number; entries: HubAdminAuditEntry[] }> {\n const qs = adminQuery({\n userId: params.userId,\n action: params.action,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/audit${qs}`)) as {\n total: number;\n entries: HubAdminAuditEntry[];\n };\n}\n\n/** 注册记录(最新在前;success 过滤结果,q 模糊匹配邮箱/手机号/昵称/IP) */\nexport async function hubAdminRegisterAttempts(\n baseUrl: string,\n token: string,\n params: { q?: string; success?: boolean; limit?: number; offset?: number } = {},\n): Promise<{ total: number; entries: HubAdminRegisterAttempt[] }> {\n const qs = adminQuery({\n q: params.q,\n success: params.success === undefined ? undefined : params.success ? 1 : 0,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/register-attempts${qs}`)) as {\n total: number;\n entries: HubAdminRegisterAttempt[];\n };\n}\n\n/**\n * 账号级站内消息流(fetch 流式读取;EventSource 不能带 Authorization 头)。\n *\n * 「去 EMQX」后这是站内消息**唯一**的实时通道(从前是 MQTT 的账号通知 topic)。\n * 断开期间由 `hubNotifications` 轮询兜底。\n */\nexport function hubNotificationStream(\n baseUrl: string,\n token: string,\n onNotification: (n: HubNotificationView) => void,\n onClosed?: () => void,\n onActivity?: () => void,\n): () => void {\n const controller = new AbortController();\n let cancelled = false;\n const url = `${resolveBase(baseUrl).replace(/\\/$/, \"\")}/api/hub/notifications/stream`;\n (async () => {\n try {\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { Accept: \"text/event-stream\", Authorization: `Bearer ${token}` },\n });\n if (!res.ok || !res.body) {\n onClosed?.();\n return;\n }\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n // 收到任何字节(含注释心跳)都算活跃,避免误判空闲后频繁重连\n onActivity?.();\n buf += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buf.slice(0, idx);\n buf = buf.slice(idx + 2);\n const data = frame\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"data:\"))\n .map((l) => l.slice(5).trim())\n .join(\"\\n\");\n if (!data) continue;\n try {\n onNotification(JSON.parse(data) as HubNotificationView);\n } catch {\n // 忽略非法载荷\n }\n }\n }\n } catch {\n // 连接中断\n } finally {\n if (!cancelled) onClosed?.();\n }\n })();\n return () => {\n cancelled = true;\n controller.abort();\n };\n}\n\n// ===================== 任务通知设置(Hub 订阅网关 NOTIFY 后远程通知) =====================\n\nexport type HubNotifyChannelType = \"email\" | \"sms\" | \"wechat\" | \"webhook\";\n\nexport type HubWebhookFormat = \"generic\" | \"onebot\" | \"wecom\" | \"dingtalk\" | \"feishu\";\n\nexport interface HubNotifyChannelView {\n id: string;\n type: HubNotifyChannelType;\n enabled: boolean;\n /**\n * email: `{ to, subjectPrefix? }`\n * sms: `{ phone, includeLink? }`\n * wechat: `{ openid, templateId?, fields?, url? }`\n * webhook: `{ url, format, targetType?, target? }`\n */\n config: Record<string, string>;\n}\n\nexport interface HubNotifySettingsView {\n enabled: boolean;\n events: {\n approval: boolean;\n question: boolean;\n completed: boolean;\n error: boolean;\n };\n channels: HubNotifyChannelView[];\n cooldownSeconds?: number;\n /** 聚合窗口(秒):窗口内事件合并成一封邮件;0 = 每条立即发送 */\n digestSeconds?: number;\n quietHours?: { start: string; end: string };\n}\n\nexport interface HubNotifyChannelAvailability {\n email: boolean;\n sms: boolean;\n wechat: boolean;\n webhook: boolean;\n}\n\nexport interface HubNotifySettingsResponse {\n settings: HubNotifySettingsView;\n /** 当前套餐是否允许通知渠道(free 为 false) */\n planAllows: boolean;\n maxChannels: number;\n /** Hub 是否已启用告警订阅(缺网关地址/终端密钥时为 false) */\n alertsEnabled: boolean;\n /** 服务端各渠道后端是否就绪(未配置时发送必然失败) */\n available: HubNotifyChannelAvailability;\n}\n\nexport interface HubAlertEventView {\n id: string;\n userId: string;\n nodeId: string;\n nodeLabel?: string;\n fleetId: string;\n projectId?: string;\n sessionId: string;\n sessionName?: string | null;\n kind: \"approval_request\" | \"question_asked\" | \"turn_completed\" | \"turn_stopped\" | \"error\";\n title: string;\n body: string;\n link?: string;\n createdAt: string;\n}\n\nexport interface HubNotifyDeliveryResult {\n channelId: string;\n type: HubNotifyChannelType;\n ok: boolean;\n error?: string;\n}\n\n/** 读取当前账号的通知设置 */\nexport async function hubNotifySettings(\n baseUrl: string,\n token: string,\n): Promise<HubNotifySettingsResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/settings\")) as HubNotifySettingsResponse;\n}\n\n/** 保存通知设置(服务端校验;free 套餐返回 402) */\nexport async function hubSaveNotifySettings(\n baseUrl: string,\n token: string,\n settings: HubNotifySettingsView,\n): Promise<{ settings: HubNotifySettingsView }> {\n return (await req(baseUrl, token, \"/api/hub/notify/settings\", {\n method: \"PUT\",\n body: JSON.stringify(settings),\n })) as { settings: HubNotifySettingsView };\n}\n\n/**\n * 工作台置顶项(账号级,跨设备同步)。\n *\n * 键由「层级路径」拼成,用于在服务端排序前置顶项:\n * nodes `fleetId/nodeId`\n * projects `fleetId/nodeId/projectId`\n * tasks `fleetId/nodeId/projectId/sessionId`\n *\n * 存用户实体(PG 是 `hub_users.data` jsonb),新增不需要迁移。\n */\nexport interface HubPins {\n nodes: string[];\n projects: string[];\n tasks: string[];\n}\n\n/** 读取当前账号的置顶项 */\nexport async function hubPins(baseUrl: string, token: string): Promise<HubPins> {\n return (await req(baseUrl, token, \"/api/hub/pins\")) as HubPins;\n}\n\n/** 整体保存置顶项(前端本地已是全量,切换一次就回写一次) */\nexport async function hubSavePins(\n baseUrl: string,\n token: string,\n pins: HubPins,\n): Promise<{ pins: HubPins }> {\n return (await req(baseUrl, token, \"/api/hub/pins\", {\n method: \"PUT\",\n body: JSON.stringify(pins),\n })) as { pins: HubPins };\n}\n\n/** 向所有启用渠道发送一条测试通知 */\nexport async function hubTestNotify(\n baseUrl: string,\n token: string,\n): Promise<{ alert: HubAlertEventView; results: HubNotifyDeliveryResult[] }> {\n return (await req(baseUrl, token, \"/api/hub/notify/test\", {\n method: \"POST\",\n })) as { alert: HubAlertEventView; results: HubNotifyDeliveryResult[] };\n}\n\n/** 最近的任务告警(内存记录,用于设置页/排查) */\nexport async function hubNotifyEvents(\n baseUrl: string,\n token: string,\n limit = 50,\n): Promise<{ items: HubAlertEventView[] }> {\n const qs = `?limit=${encodeURIComponent(String(limit))}`;\n return (await req(baseUrl, token, `/api/hub/notify/events${qs}`)) as {\n items: HubAlertEventView[];\n };\n}\n\n// ===================== 账号级发件 SMTP(Portal 配置,授权码加密存储) =====================\n\nexport interface HubSmtpView {\n host: string;\n port: number;\n secure: boolean;\n user?: string;\n from?: string;\n /** 是否已保存授权码(接口永不回传明文) */\n hasPass: boolean;\n}\n\nexport interface HubSmtpResponse {\n smtp: HubSmtpView | null;\n /** 服务端是否配置了 HUB_SMTP_*(未配账号级时用它发) */\n serverConfigured: boolean;\n /** 是否允许在 Portal 里配置(服务端启用了加密存储) */\n editable: boolean;\n}\n\n/** 读取账号级发件配置(脱敏) */\nexport async function hubSmtpSettings(\n baseUrl: string,\n token: string,\n): Promise<HubSmtpResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\")) as HubSmtpResponse;\n}\n\n/**\n * 保存账号级发件配置。\n *\n * `pass` 省略表示保留原授权码;传空串表示清空(内网无认证 SMTP)。\n */\nexport async function hubSaveSmtpSettings(\n baseUrl: string,\n token: string,\n input: {\n host: string;\n port: number;\n secure: boolean;\n user?: string;\n pass?: string;\n from?: string;\n },\n): Promise<{ smtp: HubSmtpView }> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\", {\n method: \"PUT\",\n body: JSON.stringify(input),\n })) as { smtp: HubSmtpView };\n}\n\n/** 清除账号级发件配置(回落到服务端 HUB_SMTP_*) */\nexport async function hubClearSmtpSettings(\n baseUrl: string,\n token: string,\n): Promise<HubSmtpResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\", {\n method: \"DELETE\",\n })) as HubSmtpResponse;\n}\n\n/** 示例邮件预览(服务端用该账号真实节点快照渲染) */\nexport async function hubNotifyPreview(\n baseUrl: string,\n token: string,\n): Promise<{ subject: string; text: string; html: string; nodeCount: number }> {\n return (await req(baseUrl, token, \"/api/hub/notify/preview\")) as {\n subject: string;\n text: string;\n html: string;\n nodeCount: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// 志前大模型 API(余额/用量/充值只读聚合 + 开通申请)\n// ---------------------------------------------------------------------------\n\n/** 节点按天计费配置 */\nexport interface HubNodeBillingConfigView {\n currency: \"CNY\";\n /** 免费可管理的节点数;0 = 没有免费额度 */\n freeNodes: number;\n /** 每个计费节点每天的价格(分) */\n dailyPriceCents: number;\n /** 余额可维持天数的预警阈值(天),降序 */\n warnDays: number[];\n}\n\nexport interface HubNodeBillingConfigResponse {\n pricing: HubNodeBillingConfigView;\n isDefault: boolean;\n examples: Array<{\n /** 节点总台数(含免费额度) */\n totalNodes: number;\n billableNodes: number;\n dailyYuan: number;\n monthlyYuan: number;\n }>;\n}\n\n/** 官网用户群二维码(管理后台上传,官网运行时按固定 URL 拉取) */\nexport interface HubCommunityQrView {\n /** `data:image/png;base64,...` */\n dataUrl: string;\n updatedAt: string;\n}\n\nexport interface HubCommunityQrResponse {\n qr: HubCommunityQrView | null;\n}\n\n/** 官网二维码列表项(管理后台;含图片数据,供预览) */\nexport interface HubSocialQrItem {\n /** 稳定标识,站点用它拼图片地址 `/api/hub/community/qrs/<id>` */\n id: string;\n /** 展示名称,如「企业微信群」「公众号」「抖音」 */\n name: string;\n /** `data:image/png;base64,...` */\n dataUrl: string;\n updatedAt: string;\n /** 是否在官网展示;下线只是隐藏,图片仍保留 */\n online: boolean;\n}\n\nexport interface HubSocialQrsResponse {\n items: HubSocialQrItem[];\n}\n\nexport interface HubZqaiApiRequest {\n at: string;\n purpose: string;\n monthlyVolume?: string;\n contact?: string;\n status: \"pending\" | \"approved\" | \"rejected\";\n}\n\nexport interface HubZqaiBalance {\n currency: string;\n available: number;\n available_micros: number;\n reserved: number;\n ledger_balance: number | null;\n source: string;\n as_of: string;\n}\n\nexport interface HubZqaiUsageSummary {\n requests: number;\n prompt_tokens: number;\n cached_tokens: number;\n completion_tokens: number;\n reasoning_tokens: number;\n charge: number;\n charge_micros: number;\n failed_requests: number;\n}\n\nexport interface HubZqaiUsageBucket extends HubZqaiUsageSummary {\n day?: string;\n model?: string;\n}\n\nexport interface HubZqaiUsage {\n from: string;\n to: string;\n group_by: string;\n summary: HubZqaiUsageSummary;\n items: HubZqaiUsageBucket[];\n}\n\nexport interface HubZqaiRecharge {\n id: number;\n created_at: string;\n amount: number;\n amount_micros: number;\n balance_after: number;\n note: string | null;\n /**\n * 支付渠道(wechat / alipay / zqpay)。engine 流水本身不带渠道,\n * hub 按 note 里的订单号回查本地订单补出来;运营调账 / 注册赠送为 null。\n */\n provider?: string | null;\n /**\n * 聚合收单(provider = zqpay)下用户实际选的支付方式(wechat / alipay)。\n * 直连渠道或回查不到订单时为 null —— 前端只显示 provider。\n */\n channel?: string | null;\n}\n\nexport interface HubZqaiRecharges {\n count: number;\n total: number;\n total_micros: number;\n items: HubZqaiRecharge[];\n}\n\nexport interface HubApiServiceView {\n linked: boolean;\n tenantId: number | null;\n gatewayConfigured: boolean;\n request: HubZqaiApiRequest | null;\n balance: HubZqaiBalance | null;\n usage: HubZqaiUsage | null;\n recharges: HubZqaiRecharges | null;\n /** 节点计费概览(没有套餐上限,只有「可用 / 计费 / 免费」三个数) */\n nodes: {\n total: number;\n /** 可用总数(含免费额度) */\n activeTotal: number;\n freeNodes: number;\n billable: number;\n active: number;\n };\n /** 明文 Key(仅返回给本人;页面上默认打码) */\n apiKey?: string | null;\n /** 打码后的 Key,可安全地展示在截图里 */\n apiKeyMasked?: string | null;\n /** 调用地址,页面拼示例代码用 */\n apiBaseUrl?: string;\n /** 自动开通是否启用(关闭时走「提交申请」的老流程) */\n provisionEnabled?: boolean;\n /** 最近一次自动开通失败的原因 */\n provisionError?: string | null;\n /** 累计获得的注册赠金(元) */\n bonusYuan?: number;\n}\n\nexport async function hubApiService(\n baseUrl: string,\n token: string,\n opts: { from?: string; to?: string; groupBy?: \"day\" | \"model\" } = {},\n): Promise<HubApiServiceView> {\n const q = new URLSearchParams();\n if (opts.from) q.set(\"from\", opts.from);\n if (opts.to) q.set(\"to\", opts.to);\n if (opts.groupBy) q.set(\"groupBy\", opts.groupBy);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/api-service${suffix}`)) as HubApiServiceView;\n}\n\n/**\n * 重置 API Key:旧 Key 立即失效。\n *\n * 注意:engine 每 5 秒轮询配置,**新 Key 生效、旧 Key 失效都有约 5 秒延迟**。\n * 页面上的提示要说明这一点,否则用户重置后立刻测试会看到旧 Key 仍可用。\n */\nexport async function hubRotateApiKey(\n baseUrl: string,\n token: string,\n): Promise<{ apiKey: string; apiKeyMasked: string; tenantId: number }> {\n return (await req(baseUrl, token, \"/api/hub/api-service/key/rotate\", {\n method: \"POST\",\n })) as { apiKey: string; apiKeyMasked: string; tenantId: number };\n}\n\nexport async function hubRequestApiService(\n baseUrl: string,\n token: string,\n input: { purpose: string; monthlyVolume?: string; contact?: string },\n): Promise<{ submitted: boolean; request: HubZqaiApiRequest }> {\n return (await req(baseUrl, token, \"/api/hub/api-service/request\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { submitted: boolean; request: HubZqaiApiRequest };\n}\n\nexport async function hubAdminNodeBilling(\n baseUrl: string,\n token: string,\n): Promise<HubNodeBillingConfigResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/node-billing\")) as HubNodeBillingConfigResponse;\n}\n\nexport async function hubAdminUpdateNodeBilling(\n baseUrl: string,\n token: string,\n pricing: HubNodeBillingConfigView,\n): Promise<{ pricing: HubNodeBillingConfigView }> {\n return (await req(baseUrl, token, \"/api/hub/admin/node-billing\", {\n method: \"PUT\",\n body: JSON.stringify(pricing),\n })) as { pricing: HubNodeBillingConfigView };\n}\n\n/** 充值来源:user=本人自助充值 / admin=运营代充 / bonus=注册赠金 / other=历史数据 */\nexport type HubAdminRechargeSource = \"user\" | \"admin\" | \"bonus\" | \"other\";\n\n/** 管理后台看单个用户的账户:余额 + 消费列表 + 充值列表 */\nexport interface HubAdminWalletView {\n userId: string;\n /** 余额(分);null = 暂时读不到,不等于 0 */\n balanceCents: number | null;\n /** 已预留未结算(分) */\n reservedCents: number | null;\n currency: string;\n /** 是否已接入 engine 且该用户已开户;false 时余额/流水全为 null */\n linked: boolean;\n tenantId: number | null;\n /**\n * 消费列表。\n *\n * engine 只提供按天 / 按模型的聚合(没有逐条调用流水),所以这里是汇总行,\n * `groupBy` 决定 `day` 有值还是 `model` 有值。\n */\n usage: {\n groupBy: \"day\" | \"model\";\n from: string;\n to: string;\n summary: {\n requests: number;\n promptTokens: number;\n completionTokens: number;\n chargeCents: number;\n failedRequests: number;\n };\n items: Array<{\n day: string | null;\n model: string | null;\n requests: number;\n promptTokens: number;\n completionTokens: number;\n chargeCents: number;\n }>;\n } | null;\n /** 充值列表(engine 流水),带来源标记 */\n entries: Array<{\n id: string;\n deltaCents: number;\n balanceAfterCents: number;\n ref: string;\n /** 去掉机器标记后的说明 */\n note?: string;\n source: HubAdminRechargeSource;\n /** 仅 source=admin 时有值 */\n adminName: string | null;\n /** 支付渠道(本人自助充值时由订单回查) */\n provider: string | null;\n createdAt: string;\n }>;\n}\n\n/** 管理后台查看某个用户的余额、消费与充值记录 */\nexport async function hubAdminWallet(\n baseUrl: string,\n token: string,\n userId: string,\n opts: { from?: string; to?: string; groupBy?: \"day\" | \"model\" } = {},\n): Promise<HubAdminWalletView> {\n const q = new URLSearchParams({ userId });\n if (opts.from) q.set(\"from\", opts.from);\n if (opts.to) q.set(\"to\", opts.to);\n if (opts.groupBy) q.set(\"groupBy\", opts.groupBy);\n return (await req(baseUrl, token, `/api/hub/admin/wallets?${q}`)) as HubAdminWalletView;\n}\n\n/**\n * 运营代充:直接给目标用户的账户余额入账。\n *\n * 记录会带来源标记(`运营代充 · <说明> · adm:<ref>`),管理后台与用户侧的\n * 充值列表都能看出这一笔是运营代为充值的。\n */\nexport async function hubAdminTopup(\n baseUrl: string,\n token: string,\n userId: string,\n input: { amountYuan: number; note?: string },\n): Promise<{ userId: string; balanceCents: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${userId}/wallet`, {\n method: \"POST\",\n body: JSON.stringify({ deltaYuan: input.amountYuan, note: input.note ?? \"\" }),\n })) as { userId: string; balanceCents: number };\n}\n\n/** 读取官网用户群二维码(管理后台) */\nexport async function hubAdminCommunity(\n baseUrl: string,\n token: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\")) as HubCommunityQrResponse;\n}\n\n/** 保存官网用户群二维码;dataUrl 形如 `data:image/png;base64,...` */\nexport async function hubAdminUpdateCommunity(\n baseUrl: string,\n token: string,\n dataUrl: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\", {\n method: \"PUT\",\n body: JSON.stringify({ dataUrl }),\n })) as HubCommunityQrResponse;\n}\n\n/** 清除官网用户群二维码(官网回退到内置占位图) */\nexport async function hubAdminClearCommunity(\n baseUrl: string,\n token: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\", {\n method: \"DELETE\",\n })) as HubCommunityQrResponse;\n}\n\n/** 官网二维码列表(全部项,含未上线;后台预览用) */\nexport async function hubAdminSocialQrs(\n baseUrl: string,\n token: string,\n): Promise<HubSocialQrsResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community/qrs\")) as HubSocialQrsResponse;\n}\n\n/** 新增一个二维码(名称 + 图片 data URL) */\nexport async function hubAdminAddSocialQr(\n baseUrl: string,\n token: string,\n input: { id?: string; name: string; dataUrl: string; online?: boolean },\n): Promise<{ item: HubSocialQrItem }> {\n return (await req(baseUrl, token, \"/api/hub/admin/community/qrs\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { item: HubSocialQrItem };\n}\n\n/** 局部更新:改名 / 换图 / 上线下线,只传需要改的字段 */\nexport async function hubAdminUpdateSocialQr(\n baseUrl: string,\n token: string,\n id: string,\n patch: { name?: string; dataUrl?: string; online?: boolean },\n): Promise<{ item: HubSocialQrItem }> {\n return (await req(baseUrl, token, `/api/hub/admin/community/qrs/${encodeURIComponent(id)}`, {\n method: \"PUT\",\n body: JSON.stringify(patch),\n })) as { item: HubSocialQrItem };\n}\n\n/** 删除一个二维码 */\nexport async function hubAdminRemoveSocialQr(\n baseUrl: string,\n token: string,\n id: string,\n): Promise<{ ok: boolean }> {\n return (await req(baseUrl, token, `/api/hub/admin/community/qrs/${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n })) as { ok: boolean };\n}\n\n/** 绑定 zqai 租户,或驳回申请(reject: true) */\nexport async function hubAdminLinkZqai(\n baseUrl: string,\n token: string,\n userId: string,\n input: { tenantId?: number; reject?: boolean },\n): Promise<Record<string, unknown>> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}/zqai`, {\n method: \"PUT\",\n body: JSON.stringify(input),\n })) as Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// 账户余额与节点计费(全系统只有一个余额)\n// ---------------------------------------------------------------------------\n\nexport interface HubWalletEntryView {\n id: string;\n userId: string;\n deltaCents: number;\n balanceAfterCents: number;\n ref: string;\n note?: string;\n createdAt: string;\n /** 支付渠道(wechat / alipay);非下单充值(调账 / 赠送)为 null */\n provider?: string | null;\n}\n\n/** 节点计费概览:可用台数由余额决定 */\nexport interface HubNodeBillingView {\n freeNodes: number;\n /** 每个计费节点每天的价格(分) */\n dailyPriceCents: number;\n /** 节点总台数(含免费额度) */\n totalNodes: number;\n /** 当前可用节点总数(含免费额度那几台) */\n activeTotalNodes: number;\n /** 计费节点数 = max(0, totalNodes - freeNodes) */\n billableNodes: number;\n /** 当前可用的计费节点数 */\n activeNodes: number;\n /** 因余额不足被暂停的计费节点数 */\n suspendedNodes: number;\n status: \"active\" | \"suspended\";\n /** 已结算到哪一天 */\n billedThrough: string | null;\n /** true = 不参与按天计费(admin 账号 / license 实例),UI 应隐藏余额预警 */\n exempt: boolean;\n /** 余额还能撑几天;无计费可用节点时为 null */\n daysRemaining: number | null;\n /** 预警阈值,降序(如 [15,7,3]) */\n warnDays: number[];\n /** 命中的预警档位;未命中为 null */\n warningTier: number | null;\n}\n\n/**\n * 账户余额 + 节点计费概览。\n *\n * 余额只有一份(zqai 租户账本):API 调用与节点计费都从它扣,\n * 充值也只有一条链路。`balanceCents` 为 null 表示暂时读不到(engine 未接入/抖动),\n * 而不是「余额为 0」。\n */\nexport interface HubWalletView {\n balanceCents: number | null;\n /** 充值记录(来自 engine 的流水) */\n entries: HubWalletEntryView[];\n nodeBilling: HubNodeBillingView;\n}\n\nexport async function hubWallet(baseUrl: string, token: string): Promise<HubWalletView> {\n return (await req(baseUrl, token, \"/api/hub/wallet\")) as HubWalletView;\n}\n\nexport async function hubWalletTopup(\n baseUrl: string,\n token: string,\n input: { amountYuan: number; provider?: string },\n): Promise<{\n order: { id: string; amount: number; status: string };\n mode: string;\n qrContent: string | null;\n redirectUrl: string | null;\n}> {\n return (await req(baseUrl, token, \"/api/hub/wallet/topup\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\nexport async function hubApiTopup(\n baseUrl: string,\n token: string,\n input: { amountYuan: number; provider?: string; channel?: \"wechat\" | \"alipay\" | \"any\" },\n): Promise<{\n order: { id: string; amount: number; status: string };\n mode: string;\n qrContent: string | null;\n redirectUrl: string | null;\n}> {\n return (await req(baseUrl, token, \"/api/hub/api-service/topup\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\n// ---------------------------------------------------------------------------\n// 退款 / 财务报表 / 账单(P3)\n// ---------------------------------------------------------------------------\n\nexport interface HubBillingReport {\n object: string;\n currency: string;\n from: string;\n to: string;\n wallet: {\n topupCents: number;\n /** 运营代充入账(来自审计;这笔钱不走支付渠道) */\n adminTopupCents: number;\n refundCents: number;\n /** 席位按天扣费合计 */\n seatCents: number;\n adjustCents: number;\n netCents: number;\n entries: number;\n /** 余额在 engine 侧,hub 不再持有快照;null 表示需要到 engine 查 */\n closingBalanceCents: number | null;\n };\n orders: {\n walletTopup: { count: number; cents: number };\n apiTopup: { count: number; cents: number };\n plan: { count: number; cents: number };\n refundedCount: number;\n refundedCents: number;\n };\n nodeBilling: {\n /** 有计费节点的用户数 */\n billedUsers: number;\n /** 有节点因余额被暂停的用户数 */\n suspendedUsers: number;\n activeBillableNodes: number;\n dailyPriceCents: number;\n freeNodes: number;\n /** 按「可用计费节点 × 日单价 × 30」估算 */\n mrrCents: number;\n };\n users: { total: number; paidInRange: number };\n /** 按天序列:区间内每天一条(无数据的天补 0),用于趋势与逐日对账 */\n series: HubBillingDayPoint[];\n /** 运营代充:明细 + 汇总 */\n adminTopups: {\n count: number;\n cents: number;\n items: HubAdminTopupRow[];\n };\n /** 订单明细(新 → 旧) */\n orderItems: HubBillingOrderRow[];\n}\n\n/** 报表里的一天 */\nexport interface HubBillingDayPoint {\n /** YYYY-MM-DD(UTC,与审计/账本口径一致) */\n day: string;\n /** 用户自助充值 */\n topupCents: number;\n /** 运营代充 */\n adminTopupCents: number;\n /** 退款 */\n refundCents: number;\n /** 席位扣费 */\n seatCents: number;\n /** 套餐收入 */\n planCents: number;\n orderCount: number;\n /** 净流入 = 充值 + 代充 + 套餐 − 退款 */\n netCents: number;\n}\n\n/** 一笔运营代充 */\nexport interface HubAdminTopupRow {\n ts: string;\n adminId: string;\n adminName: string | null;\n targetUserId: string;\n targetName: string | null;\n deltaCents: number;\n reason: string | null;\n ref: string | null;\n}\n\n/** 一条订单明细 */\nexport interface HubBillingOrderRow {\n id: string;\n createdAt: string;\n paidAt: string | null;\n userId: string;\n userName: string | null;\n purpose: \"plan\" | \"wallet\" | \"api_topup\";\n plan: string;\n period: string;\n amountCents: number;\n provider: string;\n status: string;\n}\n\nexport async function hubAdminBillingReport(\n baseUrl: string,\n token: string,\n range: { from?: string; to?: string } = {},\n): Promise<HubBillingReport> {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/admin/reports/billing${suffix}`)) as HubBillingReport;\n}\n\n/** 报表 CSV 下载地址(前端直接用带 token 的 fetch 拉,或拼到 a[href])。 */\nexport function hubBillingExportUrl(\n baseUrl: string,\n range: { from?: string; to?: string } = {},\n): string {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return `${resolveBase(baseUrl).replace(/\\/$/, \"\")}/api/hub/admin/reports/billing/export${suffix}`;\n}\n\nexport async function hubAdminRefund(\n baseUrl: string,\n token: string,\n userId: string,\n input: { amountYuan: number; reason: string; orderId?: string },\n): Promise<{ userId: string; orderId: string | null; amountCents: number; balanceCents?: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}/refund`, {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\nexport interface HubStatement {\n object: string;\n currency: string;\n from: string;\n to: string;\n user: { id: string; email: string | null; phone: string | null };\n nodeBilling: {\n freeNodes: number;\n dailyPriceCents: number;\n billableNodes: number;\n status: string;\n billedThrough: string | null;\n };\n totals: {\n walletTopupCents: number;\n nodeChargeCents: number;\n /** 模型调用扣费(engine 按 token 自动扣) */\n modelChargeCents: number;\n /** 总支出 = 节点 + 模型 */\n spendCents: number;\n refundCents: number;\n adjustCents: number;\n /** engine 不可达时为 null(不要当成 0) */\n closingBalanceCents: number | null;\n balanceSource: string | null;\n };\n nodeCharges: Array<{\n at: string;\n /** 计费自然日(东八区) */\n day: string;\n amountCents: number;\n /** 计费节点总数 */\n billableNodes: number;\n /** 当天实际可用(付得起)的计费节点数 */\n activeNodes: number;\n dailyPriceCents: number;\n }>;\n entries: Array<{\n at: string;\n ref: string;\n amountCents: number;\n balanceAfterCents: number | null;\n note: string | null;\n channel: \"engine\" | \"order\";\n /** 支付渠道(wechat / alipay);非下单充值(调账 / 赠送)为 null */\n provider?: string | null;\n }>;\n modelUsage: {\n summary: Record<string, number | string>;\n items: Array<Record<string, unknown>>;\n } | null;\n orders: Array<{\n id: string;\n at: string;\n purpose: string;\n plan: string;\n period: string;\n amountCents: number;\n status: string;\n provider: string;\n paidAt: string | null;\n }>;\n}\n\nexport async function hubStatement(\n baseUrl: string,\n token: string,\n range: { from?: string; to?: string } = {},\n): Promise<HubStatement> {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/statement${suffix}`)) as HubStatement;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/hub-api.ts"],"sourcesContent":["/**\n * Hub REST 客户端(浏览器/桌面共用,纯 fetch,无 Node 依赖)\n *\n * 账号/节点管理/节点/指令代理/消息/事件流。浏览器只持会话令牌,节点密钥留在 Hub。\n * 供 packages/portal(统一控制台:桌面壳内嵌 + 浏览器用户中心)复用。\n */\nimport type { FleetCredential, FleetNodeStats, SessionStateEntry } from \"./fleet.js\";\n\n/**\n * 列表排序键:任务请求开始时间优先,旧数据(该字段上线前)回退完成时间。\n *\n * 工作台的节点 / 项目 / 任务三处列表统一用它倒序,保证「最近在跑什么」排最前。\n * 返回空串表示这个条目从没跑过任务,倒序时自然落到最后。\n */\nexport function activityKey(o: {\n lastStartedAt?: string | null;\n lastCompletedAt?: string | null;\n}): string {\n return o.lastStartedAt ?? o.lastCompletedAt ?? \"\";\n}\n\n/**\n * 按最近一次任务请求的开始时间倒序(最新的在前)。\n *\n * `tieBreak` 只在时间完全相同时使用(如从没跑过的条目之间),保证顺序稳定、\n * 刷新不会跳动。\n */\nexport function compareByActivity<\n T extends { lastStartedAt?: string | null; lastCompletedAt?: string | null },\n>(a: T, b: T, tieBreak?: (x: T, y: T) => number): number {\n const ak = activityKey(a);\n const bk = activityKey(b);\n if (ak !== bk) return bk.localeCompare(ak);\n return tieBreak ? tieBreak(a, b) : 0;\n}\n\n/**\n * Hub 基址解析钩子:宿主可把逻辑 Hub 基址映射到「本机引擎中转」地址\n * (桌面端经 127.0.0.1 转发,绕开浏览器跨域)。默认恒等。\n */\nlet baseUrlResolver: ((baseUrl: string) => string) | null = null;\n\nexport function setHubBaseResolver(fn: ((baseUrl: string) => string) | null): void {\n baseUrlResolver = fn;\n}\n\nfunction resolveBase(baseUrl: string): string {\n return baseUrlResolver ? baseUrlResolver(baseUrl) : baseUrl;\n}\n\n\nexport interface HubFleetSummary {\n id: string;\n name: string;\n /** 系统内置「未分组」 */\n ungrouped?: boolean;\n nodeCount: number;\n createdAt: string;\n}\n\n/** 节点级汇总统计(节点 info 上报) */\nexport interface HubNodeTotals {\n projects: number;\n sessions: number;\n running: number;\n waiting: number;\n}\n\nexport interface HubNodeSummary {\n nodeId: string;\n label?: string;\n /** 计费状态;suspended = 余额不足暂停(缺省视为可用) */\n billingStatus?: \"active\" | \"suspended\";\n billingSuspendedReason?: \"insufficient\";\n online: boolean;\n lastSeen?: string;\n platform?: string;\n mode?: string;\n version?: string;\n /** 节点自报主 IP(新版节点上报;旧版缺省) */\n ip?: string;\n projects: Array<{\n id: string;\n name: string;\n sessionCount?: number;\n running?: number;\n waiting?: number;\n /** 项目工作目录(节点上报;旧版节点缺省) */\n workspaceRoot?: string | null;\n }>;\n /** 项目/会话汇总(新版节点上报;旧版为 null) */\n totals?: HubNodeTotals | null;\n /**\n * 会话状态快照(只含 projectId / sessionId / name / status)。\n *\n * 工作台/节点首页用它建立初始视图,之后的变更由账号级 WSS 的\n * `session_status` 通知增量推送。明细(消息/模型/token)不在这里。\n */\n sessions?: SessionStateEntry[] | null;\n /** 累计运行统计(新版节点上报;旧版为 null) */\n stats?: FleetNodeStats | null;\n /**\n * 该节点下任一任务最近一次请求的开始时间(节点列表按它倒序)。\n * 从未发起过任务时为 null(旧版节点也不上报)。\n */\n lastStartedAt?: string | null;\n}\n\n/** 节点详情(含节点管理归属与创建时间) */\nexport interface HubNodeDetail extends HubNodeSummary {\n fleetId: string;\n fleetName: string;\n createdAt: string;\n /** 访问身份:owner = 自己的节点,operator = 别人共享给我 */\n access?: HubNodeAccessView;\n}\n\n/** 当前账号对某节点的访问身份 */\nexport interface HubNodeAccessView {\n role: \"owner\" | \"operator\";\n /** 共享到期时刻(ISO);owner 为 null */\n expiresAt: string | null;\n /** 共享期间是否允许消耗所有者的模型额度 */\n allowModelSpend: boolean;\n /** 所有者账号(掩码);owner 自己看时为空串 */\n ownerLabel?: string;\n}\n\n/** 共享给我的节点(列表项,形状与节点摘要对齐并附加授权信息) */\nexport interface HubSharedNode extends HubNodeSummary {\n fleetId: string;\n fleetName: string;\n ownerLabel: string;\n grantId: string;\n expiresAt: string;\n allowModelSpend: boolean;\n}\n\n/** 共享授权(owner 视角) */\nexport interface HubNodeGrantView {\n id: string;\n nodeId: string;\n toUser: { id: string; label: string };\n role: \"operator\";\n allowModelSpend: boolean;\n expiresAt: string;\n status: \"active\" | \"expired\" | \"revoked\";\n createdAt: string;\n revokedAt: string | null;\n revokedReason: \"owner\" | \"self\" | \"node_deleted\" | \"account_deleted\" | null;\n}\n\n/**\n * 我共享出去的授权(跨节点聚合,设备列表用)。\n *\n * 与 `HubNodeGrantView` 的区别:多带节点名与分组名,便于在「设备」列表里\n * 直接给出「哪台机器 · 共享给谁 · 何时共享 · 何时到期」,不用再逐个节点查。\n * 已撤销的授权不在列表里(节点删除会级联撤销)。\n */\nexport interface HubOutgoingGrant {\n nodeId: string;\n /** 节点名;节点从未命名时为 null(前端回退显示 nodeId) */\n label: string | null;\n fleetId: string;\n fleetName: string;\n grantId: string;\n toUser: { id: string; label: string };\n allowModelSpend: boolean;\n /** 共享时间(首次授权时刻;延长不会刷新它) */\n createdAt: string;\n /** 到期时刻(ISO,绝对时间) */\n expiresAt: string;\n /** 只区分生效 / 已过期:已撤销的不返回 */\n status: \"active\" | \"expired\";\n}\n\n/** 节点操作记录的一条 */\nexport interface HubNodeActivityItem {\n id: string;\n ts: string;\n nodeId: string;\n actor: {\n userId: string;\n label: string;\n /**\n * 以什么身份操作:owner = 节点所有者,operator = 共享接收者。\n * unknown = actor 账号已不存在(或回写时无法解析),前端据此不臆断身份。\n */\n via: \"owner\" | \"operator\" | \"unknown\";\n };\n action: string;\n /** 服务端兜底摘要(前端优先按 action + 结构化字段自行渲染多语言文案) */\n summary: string;\n commandType: string | null;\n projectId: string | null;\n sessionId: string | null;\n result: \"ok\" | \"denied\" | \"error\";\n /**\n * 本条动作的对象补充,按 action 取值:\n * - rename_node / rename_node_self:新名称\n * - move_node:移动到的分组名\n * - grant_create / grant_extend / grant_revoke / grant_leave:被授权的账号(已掩码)\n */\n target: string | null;\n /** 发起客户端(web / desktop / cli…);旧记录为 null */\n client: string | null;\n /** 命令级记录的请求 ID(排障时对得上节点日志);非命令记录为 null */\n reqId: string | null;\n}\n\n/** 项目总览里的单条任务(会话) */\nexport interface HubProjectTask {\n sessionId: string;\n name: string | null;\n status: string;\n lastUserMessage: string | null;\n /** 该任务最近一次请求的开始时间(排序键) */\n lastStartedAt: string | null;\n lastCompletedAt: string | null;\n}\n\n/** 项目总览行:跨节点聚合的项目视图 */\nexport interface HubProjectRow {\n projectId: string;\n name: string;\n workspaceRoot: string | null;\n nodeId: string;\n nodeLabel: string;\n fleetId: string;\n fleetName: string;\n online: boolean;\n sessionCount: number;\n running: number;\n waiting: number;\n /** 该项目下所有任务里最近一次请求的开始时间(列表按它倒序) */\n lastStartedAt: string | null;\n /** 该项目下所有任务最近一次成功完成的时刻(ISO 8601;从未完成时 null) */\n lastCompletedAt: string | null;\n /** 任务总数(tasks 最多回传前 20 条) */\n taskCount: number;\n tasks: HubProjectTask[];\n}\n\n/** 项目总览分页响应 */\nexport interface HubProjectPage {\n total: number;\n items: HubProjectRow[];\n}\n\n/** 任务总览行:跨节点聚合的会话视图(数据来自 Hub 保存的节点快照) */\nexport interface HubTaskRow {\n sessionId: string;\n name: string | null;\n status: string;\n lastUserMessage: string | null;\n /** 该任务最近一次请求的开始时间(列表按它倒序) */\n lastStartedAt: string | null;\n lastCompletedAt: string | null;\n projectId: string;\n projectName: string;\n nodeId: string;\n nodeLabel: string;\n fleetId: string;\n fleetName: string;\n online: boolean;\n}\n\n/** 任务筛选口径的计数(始终基于全部任务,不受筛选影响) */\nexport interface HubTaskCounts {\n all: number;\n running: number;\n waiting: number;\n error: number;\n completed: number;\n}\n\n/** 任务总览分页响应 */\nexport interface HubTaskPage {\n total: number;\n counts: HubTaskCounts;\n items: HubTaskRow[];\n}\n\nexport interface HubPlan {\n id: \"free\" | \"pro\" | \"team\";\n name: string;\n priceMonthly: number;\n priceYearly: number;\n limits: { maxNodes: number; maxFleets: number; notifications: boolean; historyDays: number };\n}\n\nexport interface HubSubscriptionView {\n plan: string;\n planExpiresAt: string | null;\n limits: { maxNodes: number; maxFleets: number; notifications: boolean; historyDays: number };\n usage: { fleets: number; nodes: number };\n}\n\nexport interface HubOrderView {\n id: string;\n plan: string;\n period: string;\n amount: number;\n provider: string;\n status: string;\n createdAt: string;\n paidAt?: string;\n /** plan=套餐(已停售);wallet/api_topup=账户充值 */\n purpose?: \"plan\" | \"wallet\" | \"api_topup\";\n}\n\nexport interface HubUserView {\n id: string;\n email: string | null;\n phone: string | null;\n /** 可选昵称(仅展示;缺省时前端回退到邮箱/手机号) */\n nickname?: string | null;\n /** 可选头像(`data:image/...;base64,...`,客户端已裁成 256×256) */\n avatar?: string | null;\n plan: string;\n /** 是否已设置可用密码(短信登录自动注册后为 false) */\n passwordSet?: boolean;\n /** 系统管理员角色(缺省 user);仅 admin 可进入 /admin */\n role?: \"user\" | \"admin\";\n /** 账号状态(缺省 active) */\n status?: \"active\" | \"disabled\" | \"deleted\";\n}\n\nexport interface HubCaptchaView {\n captchaId: string;\n svg: string;\n /** 仅调试模式返回(HUB_SMS_DEBUG=1) */\n debugCode?: string;\n}\n\nexport type HubSmsPurpose = \"register\" | \"login\" | \"reset\";\n\nexport interface HubAuthInput {\n email?: string;\n phone?: string;\n password?: string;\n code?: string;\n emailCode?: string;\n /** 注册时可选填写的昵称(最长 32 个字符;之后可在个人中心修改) */\n nickname?: string;\n}\n\nexport interface HubPairingView {\n /** 对外可达的网关 wss 地址(节点凭此直连 Rust 网关;Hub 未配置时为缺省) */\n gatewayUrl?: string;\n fleetToken: string;\n fleetSecret: string;\n expiresAt?: string;\n}\n\nexport interface HubEnrollmentView {\n code: string;\n expiresAt: string;\n}\n\nexport interface HubNotificationView {\n id: string;\n userId: string;\n type: string;\n title: string;\n body?: string;\n link?: string;\n readAt?: string;\n createdAt: string;\n}\n\n/** 单次 Hub 请求超时。弱网下 fetch 默认没有超时,会一直挂到 undici 的 300s 兜底。 */\nconst HUB_REQUEST_TIMEOUT_MS = 30_000;\n\n/** 会话已不可用的错误码:令牌失效、账号被封禁或软删 */\nconst SESSION_INVALID_CODES = new Set([\"unauthorized\", \"account_disabled\", \"account_deleted\"]);\n\n/**\n * 判断某个请求错误是否意味着「当前会话已不可用」(需要重新登录)。\n *\n * 依据 HTTP 状态与错误码,不看文案——文案会随语言与措辞调整。\n */\nexport function isSessionInvalidError(e: unknown): boolean {\n const { status, code } = (e ?? {}) as { status?: number; code?: string };\n if (status === 401) return true;\n return typeof code === \"string\" && SESSION_INVALID_CODES.has(code);\n}\n\nexport type HubSessionInvalidHandler = (info: { status: number; code?: string }) => void;\n\n/**\n * 会话失效的全局回调(401 / 账号被封禁或软删)。\n *\n * 由上层注册,统一清掉本地登录态并提示用户。`/api/hub/auth/*` 自身的 401\n * 不触发——那是「密码错误」,不是「会话过期」。\n */\nlet sessionInvalidHandler: HubSessionInvalidHandler | null = null;\n\nexport function setHubSessionInvalidHandler(\n handler: HubSessionInvalidHandler | null,\n): void {\n sessionInvalidHandler = handler;\n}\n\n/**\n * Hub 请求入口。\n *\n * 只有 **GET** 做重试(幂等);POST/PATCH 重试可能造成重复下单/重复接入,\n * 因此一次失败就直接抛出。4xx 属于确定性错误,不重试。\n */\nasync function req(\n baseUrl: string,\n token: string | null,\n path: string,\n init: RequestInit = {},\n): Promise<unknown> {\n const headers: Record<string, string> = {\n ...(init.body ? { \"Content-Type\": \"application/json\" } : {}),\n ...((init.headers as Record<string, string>) ?? {}),\n };\n if (token) headers.Authorization = `Bearer ${token}`;\n const url = `${resolveBase(baseUrl).replace(/\\/$/, \"\")}${path}`;\n const method = (init.method ?? \"GET\").toUpperCase();\n const attempts = method === \"GET\" ? 3 : 1;\n // 旧运行时(部分小程序基础库)没有 AbortSignal.timeout:降级为无超时,\n // 保持原行为而不是直接抛 TypeError。\n const newTimeoutSignal = (): AbortSignal | undefined =>\n typeof AbortSignal.timeout === \"function\"\n ? AbortSignal.timeout(HUB_REQUEST_TIMEOUT_MS)\n : undefined;\n\n let lastError: unknown;\n for (let i = 0; i < attempts; i++) {\n try {\n const res = await fetch(url, {\n ...init,\n headers,\n // 每次重试都要新的 signal:超时后的 signal 已 aborted,复用会立刻失败\n signal: init.signal ?? newTimeoutSignal(),\n });\n const text = await res.text();\n let payload: unknown = null;\n try {\n payload = text ? JSON.parse(text) : null;\n } catch {\n // 响应被截断(弱网常见):当作可重试的网络错误\n throw new Error(`Hub 响应非法(HTTP ${res.status})`);\n }\n if (!res.ok) {\n const err = (payload as { error?: { code?: string; message?: string } })?.error;\n const e = new Error(err?.message ?? `HTTP ${res.status}`) as Error & {\n code?: string;\n status?: number;\n };\n e.code = err?.code;\n e.status = res.status;\n // 会话被作废(封禁/软删/改密)时通知上层退出登录;认证接口自己的 401\n // 表示密码错误,不能当成会话失效。\n if (sessionInvalidHandler && !path.startsWith(\"/api/hub/auth/\") && isSessionInvalidError(e)) {\n try {\n sessionInvalidHandler({ status: res.status, code: e.code });\n } catch {\n // 回调异常不影响把错误抛给调用方\n }\n }\n throw e;\n }\n return (payload as { data?: unknown })?.data;\n } catch (e) {\n lastError = e;\n const status = (e as { status?: number }).status;\n const retryable = status === undefined || status >= 500;\n if (i + 1 >= attempts || !retryable) throw e;\n // 指数退避 + 抖动:多个节点/页面同时重试时不要形成同步冲击\n await new Promise((r) => setTimeout(r, 300 * 2 ** i + Math.random() * 200));\n }\n }\n throw lastError;\n}\n\nexport async function hubRegister(\n baseUrl: string,\n email: string,\n password: string,\n): Promise<{ token: string }> {\n return hubRegisterAccount(baseUrl, { email, password });\n}\n\n/** 注册(邮箱或手机号;手机号需先获取短信验证码;可选填写昵称) */\nexport async function hubRegisterAccount(\n baseUrl: string,\n input: HubAuthInput,\n): Promise<{ token: string; user?: HubUserView }> {\n return (await req(baseUrl, null, \"/api/hub/auth/register\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { token: string; user?: HubUserView };\n}\n\nexport async function hubLogin(\n baseUrl: string,\n email: string,\n password: string,\n): Promise<{ token: string }> {\n return hubLoginAccount(baseUrl, { email, password });\n}\n\n/** 登录(邮箱+密码,或手机号+密码/短信验证码;手机号验证码登录未注册时自动注册) */\nexport async function hubLoginAccount(\n baseUrl: string,\n input: HubAuthInput,\n): Promise<{ token: string; user?: HubUserView; autoRegistered?: boolean }> {\n return (await req(baseUrl, null, \"/api/hub/auth/login\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { token: string; user?: HubUserView; autoRegistered?: boolean };\n}\n\n/** 图形验证码(发送短信前必填) */\nexport async function hubCaptcha(baseUrl: string): Promise<HubCaptchaView> {\n return (await req(baseUrl, null, \"/api/hub/auth/captcha\")) as HubCaptchaView;\n}\n\n/** 发送短信验证码(purpose:register/login/reset;本机当天第 4 次起需图形验证码) */\nexport async function hubSendSms(\n baseUrl: string,\n input: {\n phone: string;\n purpose: HubSmsPurpose;\n captchaId?: string;\n captcha?: string;\n /** 本机设备标识(登录页持久化生成);缺省时服务端按出口 IP 计数 */\n clientId?: string;\n },\n): Promise<{ sent: boolean; expiresIn: number; debugCode?: string }> {\n return (await req(baseUrl, null, \"/api/hub/auth/sms/send\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { sent: boolean; expiresIn: number; debugCode?: string };\n}\n\n/** 验证码发送状态:本机当天已发次数与本次是否需要图形验证码 */\nexport interface HubSendCodeStatus {\n captchaRequired: boolean;\n /** 本机今天已发送次数 */\n used: number;\n /** 本机今天还能免图形验证码发送的次数 */\n remaining: number;\n}\n\n/**\n * 查询发送状态(决定登录/注册/找回密码页是否显示图形验证码)。\n *\n * 计数维度是本机设备标识 + 自然日,与手机号、邮箱无关。\n */\nexport async function hubSendCodeStatus(\n baseUrl: string,\n clientId?: string,\n): Promise<HubSendCodeStatus> {\n const qs = clientId ? `?clientId=${encodeURIComponent(clientId)}` : \"\";\n return (await req(baseUrl, null, `/api/hub/auth/code/status${qs}`)) as HubSendCodeStatus;\n}\n\n/**\n * 短信发送状态(旧接口,兼容既有调用)。\n *\n * 与 `hubSendCodeStatus` 等价,只是沿用 `/auth/sms/status` 路径。\n */\nexport async function hubSmsStatus(\n baseUrl: string,\n phone: string,\n clientId?: string,\n): Promise<HubSendCodeStatus> {\n const qs = new URLSearchParams({ phone });\n if (clientId) qs.set(\"clientId\", clientId);\n return (await req(\n baseUrl,\n null,\n `/api/hub/auth/sms/status?${qs.toString()}`,\n )) as HubSendCodeStatus;\n}\n\n/** 发送邮箱验证码(注册校验 / 找回密码;策略与短信一致) */\nexport async function hubSendEmailCode(\n baseUrl: string,\n input: {\n email: string;\n /** 缺省 register;reset 用于找回密码(要求邮箱已注册) */\n purpose?: \"register\" | \"reset\";\n /** 仅在本机当天第 4 次起需要 */\n captchaId?: string;\n captcha?: string;\n /** 本机设备标识;缺省时服务端按出口 IP 计数 */\n clientId?: string;\n },\n): Promise<{ sent: boolean; expiresIn: number; debugCode?: string }> {\n return (await req(baseUrl, null, \"/api/hub/auth/email/send\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { sent: boolean; expiresIn: number; debugCode?: string };\n}\n\n/** 忘记密码:手机号 + 短信验证码,或邮箱 + 邮箱验证码 */\nexport async function hubResetPassword(\n baseUrl: string,\n input: { phone?: string; email?: string; code: string; newPassword: string },\n): Promise<{ changed: boolean }> {\n return (await req(baseUrl, null, \"/api/hub/auth/password/reset\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { changed: boolean };\n}\n\n/** 当前用户(id/email/nickname/生效套餐) */\nexport async function hubMe(baseUrl: string, token: string): Promise<HubUserView> {\n return (await req(baseUrl, token, \"/api/hub/me\")) as HubUserView;\n}\n\n/**\n * 更新个人资料(昵称 / 头像)。\n *\n * 字段按需更新:不传的字段保持原样。`nickname` / `avatar` 传空串或 `null` 表示清除。\n */\nexport async function hubUpdateProfile(\n baseUrl: string,\n token: string,\n input: { nickname?: string; avatar?: string | null },\n): Promise<{ nickname: string | null; avatar?: string | null }> {\n return (await req(baseUrl, token, \"/api/hub/me\", {\n method: \"PATCH\",\n body: JSON.stringify(input),\n })) as { nickname: string | null; avatar?: string | null };\n}\n\n/** 网关账号级订阅涉及的节点(nodeId → 归属节点管理) */\nexport interface HubGatewayFleet {\n id: string;\n nodes: string[];\n}\n\n/** 账号级实时通道的网关凭据(M5:控制台/用户中心直连 Rust 网关) */\nexport interface HubGatewayCredentialView {\n /** 对外可达的网关 wss 地址 */\n url: string;\n /**\n * 已签名终端令牌(scope = 本人全部节点)。\n *\n * `null` 表示账号下没有任何节点——不必建连接(网关会拒绝空 scope 的令牌)。\n */\n credential: {\n /** base64(wire `Auth`),原样作为 AUTH 帧载荷 */\n auth: string;\n /** 过期时刻(毫秒) */\n expires: number;\n scope: string[];\n kind: \"terminal\";\n client: string;\n } | null;\n fleets: HubGatewayFleet[];\n}\n\n/** 单节点终端凭据(`POST .../terminal-credential` 的返回体) */\nexport interface HubTerminalCredential {\n /** 对外可达的网关 wss 地址(与令牌一并返回,客户端一次请求即可建连) */\n url: string;\n /** base64(wire `Auth`),原样作为 AUTH 帧载荷 */\n auth: string;\n expires: number;\n scope: string[];\n kind: \"terminal\";\n client: string;\n}\n\n/**\n * 获取**单个节点**的终端网关凭据(节点控制台页用)。\n *\n * 与 `hubRealtimeCredential`(账号级,覆盖本人全部节点)的区别是授权范围:\n * 这里只授权该节点,最小权限。\n */\nexport async function hubTerminalCredential(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n client = \"web\",\n): Promise<HubTerminalCredential> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/terminal-credential`,\n { method: \"POST\", body: JSON.stringify({ client }) },\n )) as HubTerminalCredential;\n}\n\n/**\n * 获取账号级终端网关凭据。\n *\n * 取代从前那套 broker 直连账号:令牌授权范围\n * = 本人全部节点,供账号级实时通道一条 WSS 订阅所有节点。\n */\nexport async function hubRealtimeCredential(\n baseUrl: string,\n token: string,\n client = \"web\",\n): Promise<HubGatewayCredentialView> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/realtime-credential?client=${encodeURIComponent(client)}`,\n )) as HubGatewayCredentialView;\n}\n\n/**\n * 修改密码(手机号账号需短信验证码,可附原密码)。\n *\n * 改密后服务端会作废该账号已签发的全部令牌,并返回一个新令牌用于当前设备续期:\n * 调用方必须用 `token` 覆盖本地保存的令牌,否则下一次请求就会被判为未登录。\n */\nexport async function hubChangePassword(\n baseUrl: string,\n token: string,\n input: { oldPassword?: string; newPassword: string; code?: string },\n): Promise<{ changed: boolean; token?: string }> {\n return (await req(baseUrl, token, \"/api/hub/auth/password\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { changed: boolean; token?: string };\n}\n\nexport async function hubListFleets(\n baseUrl: string,\n token: string,\n): Promise<HubFleetSummary[]> {\n return (await req(baseUrl, token, \"/api/hub/fleets\")) as HubFleetSummary[];\n}\n\nexport async function hubCreateFleet(\n baseUrl: string,\n token: string,\n name: string,\n): Promise<HubFleetSummary> {\n return (await req(baseUrl, token, \"/api/hub/fleets\", {\n method: \"POST\",\n body: JSON.stringify({ name }),\n })) as HubFleetSummary;\n}\n\nexport async function hubDeleteFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}`, { method: \"DELETE\" });\n}\n\n/** 重命名分组 */\nexport async function hubUpdateFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n name: string,\n): Promise<HubFleetSummary> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}`, {\n method: \"PATCH\",\n body: JSON.stringify({ name }),\n })) as HubFleetSummary;\n}\n\n/** 节点管理配对凭据(网关地址 + fleetToken/fleetSecret,供手工接入节点) */\nexport async function hubFleetPairing(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubPairingView> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/pairing`)) as HubPairingView;\n}\n\n/** 生成一次性注册码(15 分钟有效) */\nexport async function hubCreateEnrollment(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubEnrollmentView> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/enrollment`, {\n method: \"POST\",\n })) as HubEnrollmentView;\n}\n\nexport async function hubListNodes(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<HubNodeSummary[]> {\n return (await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes`)) as HubNodeSummary[];\n}\n\n/** 项目总览(跨节点聚合,按最后完成任务时间倒序分页;`q` 按项目 / 节点 / 路径模糊过滤) */\nexport async function hubListProjects(\n baseUrl: string,\n token: string,\n opts: { limit?: number; offset?: number; q?: string } = {},\n): Promise<HubProjectPage> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 20), 1), 200);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n const q = (opts.q ?? \"\").trim();\n return (await req(\n baseUrl,\n token,\n `/api/hub/projects?limit=${limit}&offset=${offset}${q ? `&q=${encodeURIComponent(q)}` : \"\"}`,\n )) as HubProjectPage;\n}\n\n/**\n * 任务总览(跨节点聚合,按需要人处理 / 运行中 / 完成时间排序分页)。\n *\n * `status` 与工作台筛选条一致:all / running / waiting / error / completed。\n * `q` 按任务名 / 项目 / 节点 / 最近消息模糊过滤(服务端过滤,分页与总数才准确)。\n */\nexport async function hubListTasks(\n baseUrl: string,\n token: string,\n opts: { limit?: number; offset?: number; status?: string; q?: string } = {},\n): Promise<HubTaskPage> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 50), 1), 500);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n const status = opts.status ?? \"all\";\n const q = (opts.q ?? \"\").trim();\n return (await req(\n baseUrl,\n token,\n `/api/hub/tasks?limit=${limit}&offset=${offset}&status=${encodeURIComponent(status)}` +\n (q ? `&q=${encodeURIComponent(q)}` : \"\"),\n )) as HubTaskPage;\n}\n\n/** 单节点详情(状态/统计/项目/归属) */\nexport async function hubGetNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<HubNodeDetail> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n )) as HubNodeDetail;\n}\n\n/**\n * 单节点详情(不依赖 fleetId 的统一入口)。\n *\n * 共享成员手里只有 nodeId + owner 的 fleetId(从共享列表拿到);新代码用这条,\n * 避免为了读详情先拼一条自己无权访问的 fleet 路径。\n */\nexport async function hubGetNodeById(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<HubNodeDetail> {\n return (await req(baseUrl, token, `/api/hub/nodes/${encodeURIComponent(nodeId)}`)) as HubNodeDetail;\n}\n\n/** 共享给我的节点列表 */\nexport async function hubSharedNodes(baseUrl: string, token: string): Promise<HubSharedNode[]> {\n return (await req(baseUrl, token, \"/api/hub/shared/nodes\")) as HubSharedNode[];\n}\n\n/**\n * 我共享出去的授权列表(跨节点聚合)。\n *\n * 只返回未撤销的授权(生效中 + 已过期),供设备列表展示「共享给谁、何时\n * 共享、何时到期」;要撤销仍需走单节点的 `hubRevokeNodeGrant`。\n */\nexport async function hubOutgoingGrants(\n baseUrl: string,\n token: string,\n): Promise<HubOutgoingGrant[]> {\n return (await req(baseUrl, token, \"/api/hub/shared/outgoing\")) as HubOutgoingGrant[];\n}\n\n/** 共享授权列表(仅节点所有者可调) */\nexport async function hubListNodeGrants(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<HubNodeGrantView[]> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants`,\n )) as HubNodeGrantView[];\n}\n\n/**\n * 新建 / 延长共享授权。\n *\n * `expiresAt` 为 ISO 字符串(前端 datetime-local 给出,**精确到分**);\n * `expiresInDays` 是快捷天数(两者二选一,服务端统一收敛为绝对时刻)。\n * 同一账号重复授权 = 延长,不会产生第二条记录。\n */\nexport async function hubCreateNodeGrant(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n input: { account: string; expiresAt?: string; expiresInDays?: number; allowModelSpend?: boolean },\n): Promise<{ grant: HubNodeGrantView }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants`,\n { method: \"POST\", body: JSON.stringify(input) },\n )) as { grant: HubNodeGrantView };\n}\n\n/** 撤销共享授权(仅节点所有者) */\nexport async function hubRevokeNodeGrant(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n grantId: string,\n): Promise<{ revoked: boolean }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}/grants/${grantId}`,\n { method: \"DELETE\" },\n )) as { revoked: boolean };\n}\n\n/** 退出共享(被授权人主动放弃) */\nexport async function hubLeaveSharedNode(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<{ left: boolean }> {\n return (await req(baseUrl, token, `/api/hub/shared/nodes/${encodeURIComponent(nodeId)}/leave`, {\n method: \"POST\",\n })) as { left: boolean };\n}\n\n/**\n * 节点操作记录(owner 与共享成员看到**同一份**)。\n *\n * 命令级条目由网关回写(`GATEWAY_HUB_ACTIVITY_URL`);未配置时只有控制面操作\n * (改名/挪组/进入控制台/共享关系变更)。\n */\nexport async function hubNodeActivity(\n baseUrl: string,\n token: string,\n nodeId: string,\n opts: { limit?: number; offset?: number } = {},\n): Promise<{ total: number; items: HubNodeActivityItem[] }> {\n const limit = Math.min(Math.max(Math.trunc(opts.limit ?? 50), 1), 200);\n const offset = Math.max(Math.trunc(opts.offset ?? 0), 0);\n return (await req(\n baseUrl,\n token,\n `/api/hub/nodes/${encodeURIComponent(nodeId)}/activity?limit=${limit}&offset=${offset}`,\n )) as { total: number; items: HubNodeActivityItem[] };\n}\n\nexport async function hubEnrollNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n label?: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes`, {\n method: \"POST\",\n body: JSON.stringify({ nodeId, label }),\n });\n}\n\n/**\n * 凭一次性注册码接入节点管理(公开接口,无需登录令牌)。\n *\n * 成功后返回节点管理密钥与网关地址,供节点写入 `fleet.json` 并连接。\n * 注册码一次性、短时有效;同一 nodeId 不可被其他节点管理纳管(防抢注)。\n */\nexport async function hubEnrollByCode(\n baseUrl: string,\n input: { code: string; nodeId: string; label?: string },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, null, \"/api/hub/enroll\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\n/**\n * 凭 Hub 账号接入节点(公开接口,无需令牌)。\n *\n * 节点首次启动时交互式输入账号,校验通过即领取节点管理密钥并绑定 nodeId;\n * 节点默认归入内置「未分组」(分组为空的节点在控制台集中显示)。\n */\nexport async function hubEnrollByAccount(\n baseUrl: string,\n input: {\n email?: string;\n phone?: string;\n password?: string;\n code?: string;\n nodeId: string;\n label?: string;\n },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, null, \"/api/hub/enroll/account\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\n/**\n * 凭「已登录的 Hub 令牌」接入节点(需登录)。\n *\n * 桌面端在用户登录成功后调用。相比 `/enroll/account` 的优势:短信验证码\n * 登录时验证码已被消费,无法再拿它校验一次;而登录令牌在手,就能直接换取\n * 节点管理凭据——「登录后本机自动接入、可直接用内置模型」因此覆盖所有登录方式。\n */\nexport async function hubEnrollByToken(\n baseUrl: string,\n token: string,\n input: { nodeId: string; label?: string },\n): Promise<FleetCredential> {\n const data = (await req(baseUrl, token, \"/api/hub/enroll/token\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as Omit<FleetCredential, \"hubUrl\">;\n return { hubUrl: baseUrl.replace(/\\/$/, \"\"), ...data };\n}\n\nexport async function hubDeleteNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/fleets/${fleetId}/nodes/${nodeId}`, { method: \"DELETE\" });\n}\n\n/** 重命名节点:Hub 更新 label 并下发节点写入本地配置 */\nexport async function hubUpdateNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n label: string,\n): Promise<{ nodeId: string; label: string | null }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n { method: \"PATCH\", body: JSON.stringify({ label }) },\n )) as { nodeId: string; label: string | null };\n}\n\n/**\n * 节点自助改名:节点用自身的 fleetToken 鉴权,把显示名写进 Hub 记录。\n *\n * 与 `hubUpdateNode`(用户令牌)落在同一份数据上,区别只是鉴权主体:\n * 本机没登录 Hub 用户态时(`zqagent rename`),节点只能凭自己的凭据改。\n */\nexport async function hubUpdateNodeLabelSelf(\n baseUrl: string,\n fleetToken: string,\n input: { nodeId: string; label: string },\n): Promise<{ nodeId: string; label: string }> {\n return (await req(baseUrl, fleetToken, \"/api/hub/nodes/self/label\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { nodeId: string; label: string };\n}\n\n/** 将节点移动到目标分组(仅改 Hub 记录;分组是控制面元数据,无需下发节点) */\nexport async function hubMoveNode(\n baseUrl: string,\n token: string,\n fleetId: string,\n nodeId: string,\n targetFleetId: string,\n): Promise<{ nodeId: string; fleetId: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/fleets/${fleetId}/nodes/${encodeURIComponent(nodeId)}`,\n { method: \"PATCH\", body: JSON.stringify({ fleetId: targetFleetId }) },\n )) as { nodeId: string; fleetId: string };\n}\n\n/** 套餐列表(公开) */\nexport async function hubPlans(baseUrl: string): Promise<HubPlan[]> {\n return (await req(baseUrl, null, \"/api/hub/plans\")) as HubPlan[];\n}\n\n/** 当前订阅(套餐/到期/限额/用量) */\nexport async function hubSubscription(\n baseUrl: string,\n token: string,\n): Promise<HubSubscriptionView> {\n return (await req(baseUrl, token, \"/api/hub/subscription\")) as HubSubscriptionView;\n}\n\n/** 可用支付渠道 */\nexport async function hubChannels(\n baseUrl: string,\n token: string,\n): Promise<Array<{ name: string; configured: boolean; aggregate?: boolean }>> {\n return (await req(baseUrl, token, \"/api/hub/billing/channels\")) as Array<{\n name: string;\n configured: boolean;\n aggregate?: boolean;\n }>;\n}\n\n/** 我的订单 */\nexport async function hubOrders(baseUrl: string, token: string): Promise<HubOrderView[]> {\n return (await req(baseUrl, token, \"/api/hub/orders\")) as HubOrderView[];\n}\n\n/**\n * 主动同步订单状态。\n *\n * 扫码支付后前端轮询这里:回调可能丢失或延迟,后端会向渠道查询真实状态\n * 并在支付成功时完成入账。\n */\nexport async function hubSyncOrder(\n baseUrl: string,\n token: string,\n orderId: string,\n): Promise<{ order: HubOrderView; paid: boolean }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/orders/${encodeURIComponent(orderId)}/sync`,\n )) as { order: HubOrderView; paid: boolean };\n}\n\n\n\n/** 站内消息列表(含未读数) */\nexport async function hubNotifications(\n baseUrl: string,\n token: string,\n limit = 50,\n): Promise<{ items: HubNotificationView[]; unread: number }> {\n return (await req(baseUrl, token, `/api/hub/notifications?limit=${limit}`)) as {\n items: HubNotificationView[];\n unread: number;\n };\n}\n\n/** 标记站内消息已读(all=true 或指定 ids) */\nexport async function hubMarkNotificationsRead(\n baseUrl: string,\n token: string,\n input: { ids?: string[]; all?: boolean },\n): Promise<{ updated: number; unread: number }> {\n return (await req(baseUrl, token, \"/api/hub/notifications/read\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { updated: number; unread: number };\n}\n\n/** 账户级用量聚合(跨节点) */\nexport interface HubUsageAgg {\n calls: number;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n costUsd: number;\n unpriced: number;\n}\n\nexport interface HubUsageView {\n total: HubUsageAgg;\n nodes: HubUsageNodeRow[];\n days: { key: string; agg: HubUsageAgg }[];\n /**\n * 按项目拆分(同一项目在不同节点上分多条)。\n * `projectName` 由 Hub 从节点项目快照补齐;旧版节点不上报项目维度时为空数组。\n */\n projects?: HubUsageProjectRow[];\n from?: string;\n to?: string;\n}\n\n/** 用量按节点拆分行 */\nexport interface HubUsageNodeRow {\n nodeId: string;\n /** 节点展示名(Hub 节点记录里的 label;未命名时为 null,前端回退到 nodeId) */\n nodeLabel?: string | null;\n agg: HubUsageAgg;\n updatedAt: string;\n}\n\n/** 用量按项目拆分行 */\nexport interface HubUsageProjectRow {\n nodeId: string;\n /** 所属节点展示名(用于「项目已删除只剩 id」时定位) */\n nodeLabel?: string | null;\n projectId: string;\n /** 项目展示名(节点快照里取;节点已删除或旧版节点为 null) */\n projectName: string | null;\n agg: HubUsageAgg;\n}\n\nexport async function hubGetUsage(\n baseUrl: string,\n token: string,\n params: { from?: string; to?: string; groupBy?: \"node\" | \"day\" } = {},\n): Promise<HubUsageView> {\n const q = new URLSearchParams();\n if (params.from) q.set(\"from\", params.from);\n if (params.to) q.set(\"to\", params.to);\n if (params.groupBy) q.set(\"groupBy\", params.groupBy);\n const qs = q.toString();\n return (await req(baseUrl, token, `/api/hub/usage${qs ? `?${qs}` : \"\"}`)) as HubUsageView;\n}\n\n/** 节点上报用量(按天覆盖,幂等) */\nexport async function hubNodeUsageReport(\n baseUrl: string,\n token: string,\n nodeId: string,\n payload: {\n total?: unknown;\n byDay?: Record<string, unknown>;\n /** 项目维度:projectId → { total, byDay } */\n projects?: Record<string, unknown>;\n since?: string;\n },\n): Promise<void> {\n await req(baseUrl, token, `/api/hub/nodes/${nodeId}/usage`, {\n method: \"POST\",\n body: JSON.stringify(payload),\n });\n}\n\n// ===================== 系统管理员(/admin;仅 role=admin) =====================\n\nexport type HubAdminRole = \"user\" | \"admin\";\nexport type HubAdminUserStatus = \"active\" | \"disabled\" | \"deleted\";\n\n/** 管理端用户行(列表与详情共用) */\nexport interface HubAdminUserRow {\n id: string;\n email: string | null;\n phone: string | null;\n nickname: string | null;\n plan: string;\n planExpiresAt: string | null;\n role: HubAdminRole;\n status: HubAdminUserStatus;\n disabledAt?: string | null;\n deletedAt?: string | null;\n createdAt: string;\n passwordSet: boolean;\n fleetCount: number;\n nodeCount: number;\n onlineCount: number;\n subscription: {\n plan: string;\n period: string;\n startedAt: string;\n expiresAt: string;\n channel: string;\n amount: number;\n } | null;\n ordersCount: number;\n paidCount: number;\n paidCents: number;\n /** 仅列表 ?withUsage=1 时返回 */\n usage?: HubUsageAgg;\n}\n\n/** 管理端总览 */\nexport interface HubAdminOverview {\n users: {\n total: number;\n active: number;\n disabled: number;\n deleted: number;\n admins: number;\n newLast7d: number;\n };\n fleets: { total: number };\n nodes: { total: number; online: number };\n orders: {\n total: number;\n paid: number;\n created: number;\n refunded: number;\n revenueCents: number;\n revenue30dCents: number;\n byProvider: Array<{ provider: string; count: number; amount: number }>;\n };\n plans: { free: number; pro: number; team: number };\n usage: HubUsageAgg | null;\n}\n\n/** 管理端节点(含网关实时状态) */\nexport interface HubAdminNode {\n nodeId: string;\n fleetId: string;\n label?: string;\n createdAt: string;\n state: {\n nodeId: string;\n online: boolean;\n lastSeen?: string;\n platform?: string;\n mode?: string;\n version?: string;\n ip?: string;\n projects?: Array<{ id: string; name: string; sessionCount?: number; running?: number; waiting?: number }>;\n totals?: HubNodeTotals | null;\n stats?: FleetNodeStats | null;\n };\n}\n\n/** 管理端订单行(含下单用户标识) */\nexport interface HubAdminOrderRow {\n id: string;\n userId: string;\n plan: string;\n period: string;\n amount: number;\n currency: string;\n provider: string;\n status: string;\n createdAt: string;\n paidAt?: string;\n providerTxnId?: string;\n user: { id: string; email: string | null; phone: string | null } | null;\n}\n\n/** 管理端用户详情 */\nexport interface HubAdminUserDetail {\n summary: HubAdminUserRow;\n fleets: Array<{\n id: string;\n ownerId: string;\n name: string;\n ungrouped?: boolean;\n nodeCount: number;\n createdAt: string;\n }>;\n nodes: HubAdminNode[];\n orders: HubAdminOrderRow[];\n subscription: HubAdminUserRow[\"subscription\"];\n usage: HubUsageView | null;\n}\n\nexport interface HubAdminAuditEntry {\n ts: string;\n userId?: string;\n action: string;\n detail?: Record<string, unknown>;\n}\n\n/** 注册尝试记录(成功/失败都记;供管理后台监控注册问题) */\nexport interface HubAdminRegisterAttempt {\n id: string;\n ts: string;\n /** register=注册表单;auto_login=短信验证码登录时自动注册 */\n source: \"register\" | \"auto_login\";\n success: boolean;\n email: string | null;\n phone: string | null;\n nickname: string | null;\n /** 注册成功时为新用户 id */\n userId: string | null;\n /** 失败错误码(bad_request / email_exists / phone_exists / email_code_invalid…) */\n errorCode: string | null;\n errorMessage: string | null;\n ip: string | null;\n userAgent: string | null;\n}\n\nfunction adminQuery(params: Record<string, string | number | undefined>): string {\n const q = new URLSearchParams();\n for (const [k, v] of Object.entries(params)) {\n if (v === undefined || v === \"\") continue;\n q.set(k, String(v));\n }\n const qs = q.toString();\n return qs ? `?${qs}` : \"\";\n}\n\n/** 总览统计 */\nexport async function hubAdminOverview(\n baseUrl: string,\n token: string,\n): Promise<HubAdminOverview> {\n return (await req(baseUrl, token, \"/api/hub/admin/overview\")) as HubAdminOverview;\n}\n\n/** 用户列表(q 匹配 id/邮箱/手机号) */\nexport async function hubAdminUsers(\n baseUrl: string,\n token: string,\n params: {\n q?: string;\n status?: string;\n role?: string;\n plan?: string;\n withUsage?: boolean;\n limit?: number;\n offset?: number;\n } = {},\n): Promise<{ total: number; items: HubAdminUserRow[] }> {\n const qs = adminQuery({\n q: params.q,\n status: params.status,\n role: params.role,\n plan: params.plan,\n withUsage: params.withUsage ? 1 : undefined,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/users${qs}`)) as {\n total: number;\n items: HubAdminUserRow[];\n };\n}\n\n/** 用户详情(分组/节点/订单/订阅/用量) */\nexport async function hubAdminUserDetail(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<HubAdminUserDetail> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}`,\n )) as HubAdminUserDetail;\n}\n\n/** 修改套餐 / 有效期(planExpiresAt 传 null 清除) */\nexport async function hubAdminUpdateUser(\n baseUrl: string,\n token: string,\n userId: string,\n patch: { plan?: string; planExpiresAt?: string | null },\n): Promise<HubAdminUserRow> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}`, {\n method: \"PATCH\",\n body: JSON.stringify(patch),\n })) as HubAdminUserRow;\n}\n\nexport async function hubAdminDisableUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/disable`,\n { method: \"POST\" },\n )) as { id: string; status: string };\n}\n\nexport async function hubAdminEnableUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/enable`,\n { method: \"POST\" },\n )) as { id: string; status: string };\n}\n\n/** 软删用户(数据保留,可用 enable 恢复) */\nexport async function hubAdminDeleteUser(\n baseUrl: string,\n token: string,\n userId: string,\n): Promise<{ id: string; status: string }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}`, {\n method: \"DELETE\",\n })) as { id: string; status: string };\n}\n\n/** 设置/取消管理员(服务端守卫最后一个管理员) */\nexport async function hubAdminSetRole(\n baseUrl: string,\n token: string,\n userId: string,\n role: HubAdminRole,\n): Promise<{ id: string; role: string }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/role`,\n { method: \"PATCH\", body: JSON.stringify({ role }) },\n )) as { id: string; role: string };\n}\n\n/** 管理员重置用户密码(无需原密码):重置后用户可用新密码登录 */\nexport async function hubAdminResetPassword(\n baseUrl: string,\n token: string,\n userId: string,\n newPassword: string,\n): Promise<{ id: string; changed: boolean }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/password`,\n { method: \"POST\", body: JSON.stringify({ newPassword }) },\n )) as { id: string; changed: boolean };\n}\n\n/** 给用户发送站内通知 */\nexport async function hubAdminNotifyUser(\n baseUrl: string,\n token: string,\n userId: string,\n input: { title: string; body?: string; link?: string },\n): Promise<{ sent: boolean; notificationId: string | null }> {\n return (await req(\n baseUrl,\n token,\n `/api/hub/admin/users/${encodeURIComponent(userId)}/notify`,\n { method: \"POST\", body: JSON.stringify(input) },\n )) as { sent: boolean; notificationId: string | null };\n}\n\n/** 管理端删除分组(节点归入 owner 的「未分组」) */\nexport async function hubAdminDeleteFleet(\n baseUrl: string,\n token: string,\n fleetId: string,\n): Promise<{ deleted: boolean; movedNodes: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/fleets/${encodeURIComponent(fleetId)}`, {\n method: \"DELETE\",\n })) as { deleted: boolean; movedNodes: number };\n}\n\n/** 管理端删除节点(仅移除 Hub 纳管记录) */\nexport async function hubAdminDeleteNode(\n baseUrl: string,\n token: string,\n nodeId: string,\n): Promise<{ deleted: boolean }> {\n return (await req(baseUrl, token, `/api/hub/admin/nodes/${encodeURIComponent(nodeId)}`, {\n method: \"DELETE\",\n })) as { deleted: boolean };\n}\n\n/** 全站订单(可按状态/渠道/日期/关键词过滤) */\nexport async function hubAdminOrders(\n baseUrl: string,\n token: string,\n params: {\n status?: string;\n provider?: string;\n q?: string;\n from?: string;\n to?: string;\n limit?: number;\n offset?: number;\n } = {},\n): Promise<{ total: number; items: HubAdminOrderRow[] }> {\n const qs = adminQuery({\n status: params.status,\n provider: params.provider,\n q: params.q,\n from: params.from,\n to: params.to,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/orders${qs}`)) as {\n total: number;\n items: HubAdminOrderRow[];\n };\n}\n\n/** 订单手动标记:paid(激活套餐,幂等)/ refunded(仅状态+通知) */\nexport async function hubAdminMarkOrder(\n baseUrl: string,\n token: string,\n orderId: string,\n input: { status: \"paid\" | \"refunded\"; note?: string },\n): Promise<HubAdminOrderRow> {\n return (await req(baseUrl, token, `/api/hub/admin/orders/${encodeURIComponent(orderId)}/mark`, {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as HubAdminOrderRow;\n}\n\n/** 审计日志(最新在前) */\nexport async function hubAdminAudit(\n baseUrl: string,\n token: string,\n params: { userId?: string; action?: string; limit?: number; offset?: number } = {},\n): Promise<{ total: number; entries: HubAdminAuditEntry[] }> {\n const qs = adminQuery({\n userId: params.userId,\n action: params.action,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/audit${qs}`)) as {\n total: number;\n entries: HubAdminAuditEntry[];\n };\n}\n\n/** 注册记录(最新在前;success 过滤结果,q 模糊匹配邮箱/手机号/昵称/IP) */\nexport async function hubAdminRegisterAttempts(\n baseUrl: string,\n token: string,\n params: { q?: string; success?: boolean; limit?: number; offset?: number } = {},\n): Promise<{ total: number; entries: HubAdminRegisterAttempt[] }> {\n const qs = adminQuery({\n q: params.q,\n success: params.success === undefined ? undefined : params.success ? 1 : 0,\n limit: params.limit,\n offset: params.offset,\n });\n return (await req(baseUrl, token, `/api/hub/admin/register-attempts${qs}`)) as {\n total: number;\n entries: HubAdminRegisterAttempt[];\n };\n}\n\n/**\n * 账号级站内消息流(fetch 流式读取;EventSource 不能带 Authorization 头)。\n *\n * 「去 EMQX」后这是站内消息**唯一**的实时通道(从前是 MQTT 的账号通知 topic)。\n * 断开期间由 `hubNotifications` 轮询兜底。\n */\nexport function hubNotificationStream(\n baseUrl: string,\n token: string,\n onNotification: (n: HubNotificationView) => void,\n onClosed?: () => void,\n onActivity?: () => void,\n): () => void {\n const controller = new AbortController();\n let cancelled = false;\n const url = `${resolveBase(baseUrl).replace(/\\/$/, \"\")}/api/hub/notifications/stream`;\n (async () => {\n try {\n const res = await fetch(url, {\n signal: controller.signal,\n headers: { Accept: \"text/event-stream\", Authorization: `Bearer ${token}` },\n });\n if (!res.ok || !res.body) {\n onClosed?.();\n return;\n }\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buf = \"\";\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n // 收到任何字节(含注释心跳)都算活跃,避免误判空闲后频繁重连\n onActivity?.();\n buf += decoder.decode(value, { stream: true });\n let idx: number;\n while ((idx = buf.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buf.slice(0, idx);\n buf = buf.slice(idx + 2);\n const data = frame\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"data:\"))\n .map((l) => l.slice(5).trim())\n .join(\"\\n\");\n if (!data) continue;\n try {\n onNotification(JSON.parse(data) as HubNotificationView);\n } catch {\n // 忽略非法载荷\n }\n }\n }\n } catch {\n // 连接中断\n } finally {\n if (!cancelled) onClosed?.();\n }\n })();\n return () => {\n cancelled = true;\n controller.abort();\n };\n}\n\n// ===================== 任务通知设置(Hub 订阅网关 NOTIFY 后远程通知) =====================\n\nexport type HubNotifyChannelType = \"email\" | \"sms\" | \"wechat\" | \"webhook\";\n\nexport type HubWebhookFormat = \"generic\" | \"onebot\" | \"wecom\" | \"dingtalk\" | \"feishu\";\n\nexport interface HubNotifyChannelView {\n id: string;\n type: HubNotifyChannelType;\n enabled: boolean;\n /**\n * email: `{ to, subjectPrefix? }`\n * sms: `{ phone, includeLink? }`\n * wechat: `{ openid, templateId?, fields?, url? }`\n * webhook: `{ url, format, targetType?, target? }`\n */\n config: Record<string, string>;\n}\n\nexport interface HubNotifySettingsView {\n enabled: boolean;\n events: {\n approval: boolean;\n question: boolean;\n completed: boolean;\n error: boolean;\n };\n channels: HubNotifyChannelView[];\n cooldownSeconds?: number;\n /** 聚合窗口(秒):窗口内事件合并成一封邮件;0 = 每条立即发送 */\n digestSeconds?: number;\n quietHours?: { start: string; end: string };\n}\n\nexport interface HubNotifyChannelAvailability {\n email: boolean;\n sms: boolean;\n wechat: boolean;\n webhook: boolean;\n}\n\nexport interface HubNotifySettingsResponse {\n settings: HubNotifySettingsView;\n /** 当前套餐是否允许通知渠道(free 为 false) */\n planAllows: boolean;\n maxChannels: number;\n /** Hub 是否已启用告警订阅(缺网关地址/终端密钥时为 false) */\n alertsEnabled: boolean;\n /** 服务端各渠道后端是否就绪(未配置时发送必然失败) */\n available: HubNotifyChannelAvailability;\n}\n\nexport interface HubAlertEventView {\n id: string;\n userId: string;\n nodeId: string;\n nodeLabel?: string;\n fleetId: string;\n projectId?: string;\n sessionId: string;\n sessionName?: string | null;\n kind: \"approval_request\" | \"question_asked\" | \"turn_completed\" | \"turn_stopped\" | \"error\";\n title: string;\n body: string;\n link?: string;\n createdAt: string;\n}\n\nexport interface HubNotifyDeliveryResult {\n channelId: string;\n type: HubNotifyChannelType;\n ok: boolean;\n error?: string;\n}\n\n/** 读取当前账号的通知设置 */\nexport async function hubNotifySettings(\n baseUrl: string,\n token: string,\n): Promise<HubNotifySettingsResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/settings\")) as HubNotifySettingsResponse;\n}\n\n/** 保存通知设置(服务端校验;free 套餐返回 402) */\nexport async function hubSaveNotifySettings(\n baseUrl: string,\n token: string,\n settings: HubNotifySettingsView,\n): Promise<{ settings: HubNotifySettingsView }> {\n return (await req(baseUrl, token, \"/api/hub/notify/settings\", {\n method: \"PUT\",\n body: JSON.stringify(settings),\n })) as { settings: HubNotifySettingsView };\n}\n\n/**\n * 工作台置顶项(账号级,跨设备同步)。\n *\n * 键由「层级路径」拼成,用于在服务端排序前置顶项:\n * nodes `fleetId/nodeId`\n * projects `fleetId/nodeId/projectId`\n * tasks `fleetId/nodeId/projectId/sessionId`\n *\n * 存用户实体(PG 是 `hub_users.data` jsonb),新增不需要迁移。\n */\nexport interface HubPins {\n nodes: string[];\n projects: string[];\n tasks: string[];\n}\n\n/** 读取当前账号的置顶项 */\nexport async function hubPins(baseUrl: string, token: string): Promise<HubPins> {\n return (await req(baseUrl, token, \"/api/hub/pins\")) as HubPins;\n}\n\n/** 整体保存置顶项(前端本地已是全量,切换一次就回写一次) */\nexport async function hubSavePins(\n baseUrl: string,\n token: string,\n pins: HubPins,\n): Promise<{ pins: HubPins }> {\n return (await req(baseUrl, token, \"/api/hub/pins\", {\n method: \"PUT\",\n body: JSON.stringify(pins),\n })) as { pins: HubPins };\n}\n\n/** 向所有启用渠道发送一条测试通知 */\nexport async function hubTestNotify(\n baseUrl: string,\n token: string,\n): Promise<{ alert: HubAlertEventView; results: HubNotifyDeliveryResult[] }> {\n return (await req(baseUrl, token, \"/api/hub/notify/test\", {\n method: \"POST\",\n })) as { alert: HubAlertEventView; results: HubNotifyDeliveryResult[] };\n}\n\n/** 最近的任务告警(内存记录,用于设置页/排查) */\nexport async function hubNotifyEvents(\n baseUrl: string,\n token: string,\n limit = 50,\n): Promise<{ items: HubAlertEventView[] }> {\n const qs = `?limit=${encodeURIComponent(String(limit))}`;\n return (await req(baseUrl, token, `/api/hub/notify/events${qs}`)) as {\n items: HubAlertEventView[];\n };\n}\n\n// ===================== 账号级发件 SMTP(Portal 配置,授权码加密存储) =====================\n\nexport interface HubSmtpView {\n host: string;\n port: number;\n secure: boolean;\n user?: string;\n from?: string;\n /** 是否已保存授权码(接口永不回传明文) */\n hasPass: boolean;\n}\n\nexport interface HubSmtpResponse {\n smtp: HubSmtpView | null;\n /** 服务端是否配置了 HUB_SMTP_*(未配账号级时用它发) */\n serverConfigured: boolean;\n /** 是否允许在 Portal 里配置(服务端启用了加密存储) */\n editable: boolean;\n}\n\n/** 读取账号级发件配置(脱敏) */\nexport async function hubSmtpSettings(\n baseUrl: string,\n token: string,\n): Promise<HubSmtpResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\")) as HubSmtpResponse;\n}\n\n/**\n * 保存账号级发件配置。\n *\n * `pass` 省略表示保留原授权码;传空串表示清空(内网无认证 SMTP)。\n */\nexport async function hubSaveSmtpSettings(\n baseUrl: string,\n token: string,\n input: {\n host: string;\n port: number;\n secure: boolean;\n user?: string;\n pass?: string;\n from?: string;\n },\n): Promise<{ smtp: HubSmtpView }> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\", {\n method: \"PUT\",\n body: JSON.stringify(input),\n })) as { smtp: HubSmtpView };\n}\n\n/** 清除账号级发件配置(回落到服务端 HUB_SMTP_*) */\nexport async function hubClearSmtpSettings(\n baseUrl: string,\n token: string,\n): Promise<HubSmtpResponse> {\n return (await req(baseUrl, token, \"/api/hub/notify/smtp\", {\n method: \"DELETE\",\n })) as HubSmtpResponse;\n}\n\n/** 示例邮件预览(服务端用该账号真实节点快照渲染) */\nexport async function hubNotifyPreview(\n baseUrl: string,\n token: string,\n): Promise<{ subject: string; text: string; html: string; nodeCount: number }> {\n return (await req(baseUrl, token, \"/api/hub/notify/preview\")) as {\n subject: string;\n text: string;\n html: string;\n nodeCount: number;\n };\n}\n\n// ---------------------------------------------------------------------------\n// 志前大模型 API(余额/用量/充值只读聚合 + 开通申请)\n// ---------------------------------------------------------------------------\n\n/** 节点按天计费配置 */\nexport interface HubNodeBillingConfigView {\n currency: \"CNY\";\n /** 免费可管理的节点数;0 = 没有免费额度 */\n freeNodes: number;\n /** 每个计费节点每天的价格(分) */\n dailyPriceCents: number;\n /** 余额可维持天数的预警阈值(天),降序 */\n warnDays: number[];\n}\n\nexport interface HubNodeBillingConfigResponse {\n pricing: HubNodeBillingConfigView;\n isDefault: boolean;\n examples: Array<{\n /** 节点总台数(含免费额度) */\n totalNodes: number;\n billableNodes: number;\n dailyYuan: number;\n monthlyYuan: number;\n }>;\n}\n\n/** 官网用户群二维码(管理后台上传,官网运行时按固定 URL 拉取) */\nexport interface HubCommunityQrView {\n /** `data:image/png;base64,...` */\n dataUrl: string;\n updatedAt: string;\n}\n\nexport interface HubCommunityQrResponse {\n qr: HubCommunityQrView | null;\n}\n\n/** 官网二维码列表项(管理后台;含图片数据,供预览) */\nexport interface HubSocialQrItem {\n /** 稳定标识,站点用它拼图片地址 `/api/hub/community/qrs/<id>` */\n id: string;\n /** 展示名称,如「企业微信群」「公众号」「抖音」 */\n name: string;\n /** `data:image/png;base64,...` */\n dataUrl: string;\n updatedAt: string;\n /** 是否在官网展示;下线只是隐藏,图片仍保留 */\n online: boolean;\n}\n\nexport interface HubSocialQrsResponse {\n items: HubSocialQrItem[];\n}\n\nexport interface HubZqaiApiRequest {\n at: string;\n purpose: string;\n monthlyVolume?: string;\n contact?: string;\n status: \"pending\" | \"approved\" | \"rejected\";\n}\n\nexport interface HubZqaiBalance {\n currency: string;\n available: number;\n available_micros: number;\n reserved: number;\n ledger_balance: number | null;\n source: string;\n as_of: string;\n}\n\nexport interface HubZqaiUsageSummary {\n requests: number;\n prompt_tokens: number;\n cached_tokens: number;\n completion_tokens: number;\n reasoning_tokens: number;\n charge: number;\n charge_micros: number;\n failed_requests: number;\n}\n\nexport interface HubZqaiUsageBucket extends HubZqaiUsageSummary {\n day?: string;\n model?: string;\n}\n\nexport interface HubZqaiUsage {\n from: string;\n to: string;\n group_by: string;\n summary: HubZqaiUsageSummary;\n items: HubZqaiUsageBucket[];\n}\n\nexport interface HubZqaiRecharge {\n id: number;\n created_at: string;\n amount: number;\n amount_micros: number;\n balance_after: number;\n note: string | null;\n /**\n * 支付渠道(wechat / alipay / zqpay)。engine 流水本身不带渠道,\n * hub 按 note 里的订单号回查本地订单补出来;运营调账 / 注册赠送为 null。\n */\n provider?: string | null;\n /**\n * 聚合收单(provider = zqpay)下用户实际选的支付方式(wechat / alipay)。\n * 直连渠道或回查不到订单时为 null —— 前端只显示 provider。\n */\n channel?: string | null;\n}\n\nexport interface HubZqaiRecharges {\n count: number;\n total: number;\n total_micros: number;\n items: HubZqaiRecharge[];\n}\n\nexport interface HubApiServiceView {\n linked: boolean;\n tenantId: number | null;\n gatewayConfigured: boolean;\n request: HubZqaiApiRequest | null;\n balance: HubZqaiBalance | null;\n usage: HubZqaiUsage | null;\n recharges: HubZqaiRecharges | null;\n /** 节点计费概览(没有套餐上限,只有「可用 / 计费 / 免费」三个数) */\n nodes: {\n total: number;\n /** 可用总数(含免费额度) */\n activeTotal: number;\n freeNodes: number;\n billable: number;\n active: number;\n };\n /** 明文 Key(仅返回给本人;页面上默认打码) */\n apiKey?: string | null;\n /** 打码后的 Key,可安全地展示在截图里 */\n apiKeyMasked?: string | null;\n /** 调用地址,页面拼示例代码用 */\n apiBaseUrl?: string;\n /** 自动开通是否启用(关闭时走「提交申请」的老流程) */\n provisionEnabled?: boolean;\n /** 最近一次自动开通失败的原因 */\n provisionError?: string | null;\n /** 累计获得的注册赠金(元) */\n bonusYuan?: number;\n}\n\nexport async function hubApiService(\n baseUrl: string,\n token: string,\n opts: { from?: string; to?: string; groupBy?: \"day\" | \"model\" } = {},\n): Promise<HubApiServiceView> {\n const q = new URLSearchParams();\n if (opts.from) q.set(\"from\", opts.from);\n if (opts.to) q.set(\"to\", opts.to);\n if (opts.groupBy) q.set(\"groupBy\", opts.groupBy);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/api-service${suffix}`)) as HubApiServiceView;\n}\n\n/**\n * 重置 API Key:旧 Key 立即失效。\n *\n * 注意:engine 每 5 秒轮询配置,**新 Key 生效、旧 Key 失效都有约 5 秒延迟**。\n * 页面上的提示要说明这一点,否则用户重置后立刻测试会看到旧 Key 仍可用。\n */\nexport async function hubRotateApiKey(\n baseUrl: string,\n token: string,\n): Promise<{ apiKey: string; apiKeyMasked: string; tenantId: number }> {\n return (await req(baseUrl, token, \"/api/hub/api-service/key/rotate\", {\n method: \"POST\",\n })) as { apiKey: string; apiKeyMasked: string; tenantId: number };\n}\n\nexport async function hubRequestApiService(\n baseUrl: string,\n token: string,\n input: { purpose: string; monthlyVolume?: string; contact?: string },\n): Promise<{ submitted: boolean; request: HubZqaiApiRequest }> {\n return (await req(baseUrl, token, \"/api/hub/api-service/request\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { submitted: boolean; request: HubZqaiApiRequest };\n}\n\nexport async function hubAdminNodeBilling(\n baseUrl: string,\n token: string,\n): Promise<HubNodeBillingConfigResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/node-billing\")) as HubNodeBillingConfigResponse;\n}\n\nexport async function hubAdminUpdateNodeBilling(\n baseUrl: string,\n token: string,\n pricing: HubNodeBillingConfigView,\n): Promise<{ pricing: HubNodeBillingConfigView }> {\n return (await req(baseUrl, token, \"/api/hub/admin/node-billing\", {\n method: \"PUT\",\n body: JSON.stringify(pricing),\n })) as { pricing: HubNodeBillingConfigView };\n}\n\n/** 充值来源:user=本人自助充值 / admin=运营代充 / bonus=注册赠金 / other=历史数据 */\nexport type HubAdminRechargeSource = \"user\" | \"admin\" | \"bonus\" | \"other\";\n\n/** 管理后台看单个用户的账户:余额 + 消费列表 + 充值列表 */\nexport interface HubAdminWalletView {\n userId: string;\n /** 余额(分);null = 暂时读不到,不等于 0 */\n balanceCents: number | null;\n /** 已预留未结算(分) */\n reservedCents: number | null;\n currency: string;\n /** 是否已接入 engine 且该用户已开户;false 时余额/流水全为 null */\n linked: boolean;\n tenantId: number | null;\n /**\n * 消费列表。\n *\n * engine 只提供按天 / 按模型的聚合(没有逐条调用流水),所以这里是汇总行,\n * `groupBy` 决定 `day` 有值还是 `model` 有值。\n */\n usage: {\n groupBy: \"day\" | \"model\";\n from: string;\n to: string;\n summary: {\n requests: number;\n promptTokens: number;\n completionTokens: number;\n chargeCents: number;\n failedRequests: number;\n };\n items: Array<{\n day: string | null;\n model: string | null;\n requests: number;\n promptTokens: number;\n completionTokens: number;\n chargeCents: number;\n }>;\n } | null;\n /** 充值列表(engine 流水),带来源标记 */\n entries: Array<{\n id: string;\n deltaCents: number;\n balanceAfterCents: number;\n ref: string;\n /** 去掉机器标记后的说明 */\n note?: string;\n source: HubAdminRechargeSource;\n /** 仅 source=admin 时有值 */\n adminName: string | null;\n /** 支付渠道(本人自助充值时由订单回查) */\n provider: string | null;\n createdAt: string;\n }>;\n}\n\n/** 管理后台查看某个用户的余额、消费与充值记录 */\nexport async function hubAdminWallet(\n baseUrl: string,\n token: string,\n userId: string,\n opts: { from?: string; to?: string; groupBy?: \"day\" | \"model\" } = {},\n): Promise<HubAdminWalletView> {\n const q = new URLSearchParams({ userId });\n if (opts.from) q.set(\"from\", opts.from);\n if (opts.to) q.set(\"to\", opts.to);\n if (opts.groupBy) q.set(\"groupBy\", opts.groupBy);\n return (await req(baseUrl, token, `/api/hub/admin/wallets?${q}`)) as HubAdminWalletView;\n}\n\n/**\n * 运营代充:直接给目标用户的账户余额入账。\n *\n * 记录会带来源标记(`运营代充 · <说明> · adm:<ref>`),管理后台与用户侧的\n * 充值列表都能看出这一笔是运营代为充值的。\n */\nexport async function hubAdminTopup(\n baseUrl: string,\n token: string,\n userId: string,\n input: { amountYuan: number; note?: string },\n): Promise<{ userId: string; balanceCents: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${userId}/wallet`, {\n method: \"POST\",\n body: JSON.stringify({ deltaYuan: input.amountYuan, note: input.note ?? \"\" }),\n })) as { userId: string; balanceCents: number };\n}\n\n/** 读取官网用户群二维码(管理后台) */\nexport async function hubAdminCommunity(\n baseUrl: string,\n token: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\")) as HubCommunityQrResponse;\n}\n\n/** 保存官网用户群二维码;dataUrl 形如 `data:image/png;base64,...` */\nexport async function hubAdminUpdateCommunity(\n baseUrl: string,\n token: string,\n dataUrl: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\", {\n method: \"PUT\",\n body: JSON.stringify({ dataUrl }),\n })) as HubCommunityQrResponse;\n}\n\n/** 清除官网用户群二维码(官网回退到内置占位图) */\nexport async function hubAdminClearCommunity(\n baseUrl: string,\n token: string,\n): Promise<HubCommunityQrResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community\", {\n method: \"DELETE\",\n })) as HubCommunityQrResponse;\n}\n\n/** 官网二维码列表(全部项,含未上线;后台预览用) */\nexport async function hubAdminSocialQrs(\n baseUrl: string,\n token: string,\n): Promise<HubSocialQrsResponse> {\n return (await req(baseUrl, token, \"/api/hub/admin/community/qrs\")) as HubSocialQrsResponse;\n}\n\n/** 新增一个二维码(名称 + 图片 data URL) */\nexport async function hubAdminAddSocialQr(\n baseUrl: string,\n token: string,\n input: { id?: string; name: string; dataUrl: string; online?: boolean },\n): Promise<{ item: HubSocialQrItem }> {\n return (await req(baseUrl, token, \"/api/hub/admin/community/qrs\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as { item: HubSocialQrItem };\n}\n\n/** 局部更新:改名 / 换图 / 上线下线,只传需要改的字段 */\nexport async function hubAdminUpdateSocialQr(\n baseUrl: string,\n token: string,\n id: string,\n patch: { name?: string; dataUrl?: string; online?: boolean },\n): Promise<{ item: HubSocialQrItem }> {\n return (await req(baseUrl, token, `/api/hub/admin/community/qrs/${encodeURIComponent(id)}`, {\n method: \"PUT\",\n body: JSON.stringify(patch),\n })) as { item: HubSocialQrItem };\n}\n\n/** 删除一个二维码 */\nexport async function hubAdminRemoveSocialQr(\n baseUrl: string,\n token: string,\n id: string,\n): Promise<{ ok: boolean }> {\n return (await req(baseUrl, token, `/api/hub/admin/community/qrs/${encodeURIComponent(id)}`, {\n method: \"DELETE\",\n })) as { ok: boolean };\n}\n\n/** 绑定 zqai 租户,或驳回申请(reject: true) */\nexport async function hubAdminLinkZqai(\n baseUrl: string,\n token: string,\n userId: string,\n input: { tenantId?: number; reject?: boolean },\n): Promise<Record<string, unknown>> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}/zqai`, {\n method: \"PUT\",\n body: JSON.stringify(input),\n })) as Record<string, unknown>;\n}\n\n// ---------------------------------------------------------------------------\n// 账户余额与节点计费(全系统只有一个余额)\n// ---------------------------------------------------------------------------\n\nexport interface HubWalletEntryView {\n id: string;\n userId: string;\n deltaCents: number;\n balanceAfterCents: number;\n ref: string;\n note?: string;\n createdAt: string;\n /** 支付渠道(wechat / alipay);非下单充值(调账 / 赠送)为 null */\n provider?: string | null;\n}\n\n/** 节点计费概览:可用台数由余额决定 */\nexport interface HubNodeBillingView {\n freeNodes: number;\n /** 每个计费节点每天的价格(分) */\n dailyPriceCents: number;\n /** 节点总台数(含免费额度) */\n totalNodes: number;\n /** 当前可用节点总数(含免费额度那几台) */\n activeTotalNodes: number;\n /** 计费节点数 = max(0, totalNodes - freeNodes) */\n billableNodes: number;\n /** 当前可用的计费节点数 */\n activeNodes: number;\n /** 因余额不足被暂停的计费节点数 */\n suspendedNodes: number;\n status: \"active\" | \"suspended\";\n /** 已结算到哪一天 */\n billedThrough: string | null;\n /** true = 不参与按天计费(admin 账号 / license 实例),UI 应隐藏余额预警 */\n exempt: boolean;\n /** 余额还能撑几天;无计费可用节点时为 null */\n daysRemaining: number | null;\n /** 预警阈值,降序(如 [15,7,3]) */\n warnDays: number[];\n /** 命中的预警档位;未命中为 null */\n warningTier: number | null;\n}\n\n/**\n * 账户余额 + 节点计费概览。\n *\n * 余额只有一份(zqai 租户账本):API 调用与节点计费都从它扣,\n * 充值也只有一条链路。`balanceCents` 为 null 表示暂时读不到(engine 未接入/抖动),\n * 而不是「余额为 0」。\n */\nexport interface HubWalletView {\n balanceCents: number | null;\n /** 充值记录(来自 engine 的流水) */\n entries: HubWalletEntryView[];\n nodeBilling: HubNodeBillingView;\n}\n\nexport async function hubWallet(baseUrl: string, token: string): Promise<HubWalletView> {\n return (await req(baseUrl, token, \"/api/hub/wallet\")) as HubWalletView;\n}\n\nexport async function hubWalletTopup(\n baseUrl: string,\n token: string,\n input: { amountYuan: number; provider?: string },\n): Promise<{\n order: { id: string; amount: number; status: string };\n mode: string;\n qrContent: string | null;\n redirectUrl: string | null;\n}> {\n return (await req(baseUrl, token, \"/api/hub/wallet/topup\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\nexport async function hubApiTopup(\n baseUrl: string,\n token: string,\n input: { amountYuan: number; provider?: string; channel?: \"wechat\" | \"alipay\" | \"any\" },\n): Promise<{\n order: { id: string; amount: number; status: string };\n mode: string;\n qrContent: string | null;\n redirectUrl: string | null;\n}> {\n return (await req(baseUrl, token, \"/api/hub/api-service/topup\", {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\n// ---------------------------------------------------------------------------\n// 退款 / 财务报表 / 账单(P3)\n// ---------------------------------------------------------------------------\n\nexport interface HubBillingReport {\n object: string;\n currency: string;\n from: string;\n to: string;\n wallet: {\n topupCents: number;\n /** 运营代充入账(来自审计;这笔钱不走支付渠道) */\n adminTopupCents: number;\n refundCents: number;\n /** 席位按天扣费合计 */\n seatCents: number;\n adjustCents: number;\n netCents: number;\n entries: number;\n /** 余额在 engine 侧,hub 不再持有快照;null 表示需要到 engine 查 */\n closingBalanceCents: number | null;\n };\n orders: {\n walletTopup: { count: number; cents: number };\n apiTopup: { count: number; cents: number };\n plan: { count: number; cents: number };\n refundedCount: number;\n refundedCents: number;\n };\n nodeBilling: {\n /** 有计费节点的用户数 */\n billedUsers: number;\n /** 有节点因余额被暂停的用户数 */\n suspendedUsers: number;\n activeBillableNodes: number;\n dailyPriceCents: number;\n freeNodes: number;\n /** 按「可用计费节点 × 日单价 × 30」估算 */\n mrrCents: number;\n };\n users: { total: number; paidInRange: number };\n /** 按天序列:区间内每天一条(无数据的天补 0),用于趋势与逐日对账 */\n series: HubBillingDayPoint[];\n /** 运营代充:明细 + 汇总 */\n adminTopups: {\n count: number;\n cents: number;\n items: HubAdminTopupRow[];\n };\n /** 订单明细(新 → 旧) */\n orderItems: HubBillingOrderRow[];\n}\n\n/** 报表里的一天 */\nexport interface HubBillingDayPoint {\n /** YYYY-MM-DD(UTC,与审计/账本口径一致) */\n day: string;\n /** 用户自助充值 */\n topupCents: number;\n /** 运营代充 */\n adminTopupCents: number;\n /** 退款 */\n refundCents: number;\n /** 席位扣费 */\n seatCents: number;\n /** 套餐收入 */\n planCents: number;\n orderCount: number;\n /** 净流入 = 充值 + 代充 + 套餐 − 退款 */\n netCents: number;\n}\n\n/** 一笔运营代充 */\nexport interface HubAdminTopupRow {\n ts: string;\n adminId: string;\n adminName: string | null;\n targetUserId: string;\n targetName: string | null;\n deltaCents: number;\n reason: string | null;\n ref: string | null;\n}\n\n/** 一条订单明细 */\nexport interface HubBillingOrderRow {\n id: string;\n createdAt: string;\n paidAt: string | null;\n userId: string;\n userName: string | null;\n purpose: \"plan\" | \"wallet\" | \"api_topup\";\n plan: string;\n period: string;\n amountCents: number;\n provider: string;\n status: string;\n}\n\nexport async function hubAdminBillingReport(\n baseUrl: string,\n token: string,\n range: { from?: string; to?: string } = {},\n): Promise<HubBillingReport> {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/admin/reports/billing${suffix}`)) as HubBillingReport;\n}\n\n/** 报表 CSV 下载地址(前端直接用带 token 的 fetch 拉,或拼到 a[href])。 */\nexport function hubBillingExportUrl(\n baseUrl: string,\n range: { from?: string; to?: string } = {},\n): string {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return `${resolveBase(baseUrl).replace(/\\/$/, \"\")}/api/hub/admin/reports/billing/export${suffix}`;\n}\n\nexport async function hubAdminRefund(\n baseUrl: string,\n token: string,\n userId: string,\n input: { amountYuan: number; reason: string; orderId?: string },\n): Promise<{ userId: string; orderId: string | null; amountCents: number; balanceCents?: number }> {\n return (await req(baseUrl, token, `/api/hub/admin/users/${encodeURIComponent(userId)}/refund`, {\n method: \"POST\",\n body: JSON.stringify(input),\n })) as never;\n}\n\nexport interface HubStatement {\n object: string;\n currency: string;\n from: string;\n to: string;\n user: { id: string; email: string | null; phone: string | null };\n nodeBilling: {\n freeNodes: number;\n dailyPriceCents: number;\n billableNodes: number;\n status: string;\n billedThrough: string | null;\n };\n totals: {\n walletTopupCents: number;\n nodeChargeCents: number;\n /** 模型调用扣费(engine 按 token 自动扣) */\n modelChargeCents: number;\n /** 总支出 = 节点 + 模型 */\n spendCents: number;\n refundCents: number;\n adjustCents: number;\n /** engine 不可达时为 null(不要当成 0) */\n closingBalanceCents: number | null;\n balanceSource: string | null;\n };\n nodeCharges: Array<{\n at: string;\n /** 计费自然日(东八区) */\n day: string;\n amountCents: number;\n /** 计费节点总数 */\n billableNodes: number;\n /** 当天实际可用(付得起)的计费节点数 */\n activeNodes: number;\n dailyPriceCents: number;\n }>;\n entries: Array<{\n at: string;\n ref: string;\n amountCents: number;\n balanceAfterCents: number | null;\n note: string | null;\n channel: \"engine\" | \"order\";\n /** 支付渠道(wechat / alipay);非下单充值(调账 / 赠送)为 null */\n provider?: string | null;\n }>;\n modelUsage: {\n summary: Record<string, number | string>;\n items: Array<Record<string, unknown>>;\n } | null;\n orders: Array<{\n id: string;\n at: string;\n purpose: string;\n plan: string;\n period: string;\n amountCents: number;\n status: string;\n provider: string;\n paidAt: string | null;\n }>;\n}\n\nexport async function hubStatement(\n baseUrl: string,\n token: string,\n range: { from?: string; to?: string } = {},\n): Promise<HubStatement> {\n const q = new URLSearchParams();\n if (range.from) q.set(\"from\", range.from);\n if (range.to) q.set(\"to\", range.to);\n const suffix = q.toString() ? `?${q}` : \"\";\n return (await req(baseUrl, token, `/api/hub/statement${suffix}`)) as HubStatement;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcO,SAAS,YAAY,GAGjB;AACT,SAAO,EAAE,iBAAiB,EAAE,mBAAmB;AACjD;AAQO,SAAS,kBAEd,GAAM,GAAM,UAA2C;AACvD,QAAM,KAAK,YAAY,CAAC;AACxB,QAAM,KAAK,YAAY,CAAC;AACxB,MAAI,OAAO,GAAI,QAAO,GAAG,cAAc,EAAE;AACzC,SAAO,WAAW,SAAS,GAAG,CAAC,IAAI;AACrC;AAMA,IAAI,kBAAwD;AAErD,SAAS,mBAAmB,IAAgD;AACjF,oBAAkB;AACpB;AAEA,SAAS,YAAY,SAAyB;AAC5C,SAAO,kBAAkB,gBAAgB,OAAO,IAAI;AACtD;AAoUA,IAAM,yBAAyB;AAG/B,IAAM,wBAAwB,oBAAI,IAAI,CAAC,gBAAgB,oBAAoB,iBAAiB,CAAC;AAOtF,SAAS,sBAAsB,GAAqB;AACzD,QAAM,EAAE,QAAQ,KAAK,IAAK,KAAK,CAAC;AAChC,MAAI,WAAW,IAAK,QAAO;AAC3B,SAAO,OAAO,SAAS,YAAY,sBAAsB,IAAI,IAAI;AACnE;AAUA,IAAI,wBAAyD;AAEtD,SAAS,4BACd,SACM;AACN,0BAAwB;AAC1B;AAQA,eAAe,IACb,SACA,OACA,MACA,OAAoB,CAAC,GACH;AAClB,QAAM,UAAkC;AAAA,IACtC,GAAI,KAAK,OAAO,EAAE,gBAAgB,mBAAmB,IAAI,CAAC;AAAA,IAC1D,GAAK,KAAK,WAAsC,CAAC;AAAA,EACnD;AACA,MAAI,MAAO,SAAQ,gBAAgB,UAAU,KAAK;AAClD,QAAM,MAAM,GAAG,YAAY,OAAO,EAAE,QAAQ,OAAO,EAAE,CAAC,GAAG,IAAI;AAC7D,QAAM,UAAU,KAAK,UAAU,OAAO,YAAY;AAClD,QAAM,WAAW,WAAW,QAAQ,IAAI;AAGxC,QAAM,mBAAmB,MACvB,OAAO,YAAY,YAAY,aAC3B,YAAY,QAAQ,sBAAsB,IAC1C;AAEN,MAAI;AACJ,WAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B,GAAG;AAAA,QACH;AAAA;AAAA,QAEA,QAAQ,KAAK,UAAU,iBAAiB;AAAA,MAC1C,CAAC;AACD,YAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,UAAI,UAAmB;AACvB,UAAI;AACF,kBAAU,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,MACtC,QAAQ;AAEN,cAAM,IAAI,MAAM,0CAAiB,IAAI,MAAM,QAAG;AAAA,MAChD;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,MAAO,SAA6D;AAC1E,cAAM,IAAI,IAAI,MAAM,KAAK,WAAW,QAAQ,IAAI,MAAM,EAAE;AAIxD,UAAE,OAAO,KAAK;AACd,UAAE,SAAS,IAAI;AAGf,YAAI,yBAAyB,CAAC,KAAK,WAAW,gBAAgB,KAAK,sBAAsB,CAAC,GAAG;AAC3F,cAAI;AACF,kCAAsB,EAAE,QAAQ,IAAI,QAAQ,MAAM,EAAE,KAAK,CAAC;AAAA,UAC5D,QAAQ;AAAA,UAER;AAAA,QACF;AACA,cAAM;AAAA,MACR;AACA,aAAQ,SAAgC;AAAA,IAC1C,SAAS,GAAG;AACV,kBAAY;AACZ,YAAM,SAAU,EAA0B;AAC1C,YAAM,YAAY,WAAW,UAAa,UAAU;AACpD,UAAI,IAAI,KAAK,YAAY,CAAC,UAAW,OAAM;AAE3C,YAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC;AAAA,IAC5E;AAAA,EACF;AACA,QAAM;AACR;AAEA,eAAsB,YACpB,SACA,OACA,UAC4B;AAC5B,SAAO,mBAAmB,SAAS,EAAE,OAAO,SAAS,CAAC;AACxD;AAGA,eAAsB,mBACpB,SACA,OACgD;AAChD,SAAQ,MAAM,IAAI,SAAS,MAAM,0BAA0B;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,eAAsB,SACpB,SACA,OACA,UAC4B;AAC5B,SAAO,gBAAgB,SAAS,EAAE,OAAO,SAAS,CAAC;AACrD;AAGA,eAAsB,gBACpB,SACA,OAC0E;AAC1E,SAAQ,MAAM,IAAI,SAAS,MAAM,uBAAuB;AAAA,IACtD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,WAAW,SAA0C;AACzE,SAAQ,MAAM,IAAI,SAAS,MAAM,uBAAuB;AAC1D;AAGA,eAAsB,WACpB,SACA,OAQmE;AACnE,SAAQ,MAAM,IAAI,SAAS,MAAM,0BAA0B;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAgBA,eAAsB,kBACpB,SACA,UAC4B;AAC5B,QAAM,KAAK,WAAW,aAAa,mBAAmB,QAAQ,CAAC,KAAK;AACpE,SAAQ,MAAM,IAAI,SAAS,MAAM,4BAA4B,EAAE,EAAE;AACnE;AAOA,eAAsB,aACpB,SACA,OACA,UAC4B;AAC5B,QAAM,KAAK,IAAI,gBAAgB,EAAE,MAAM,CAAC;AACxC,MAAI,SAAU,IAAG,IAAI,YAAY,QAAQ;AACzC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,4BAA4B,GAAG,SAAS,CAAC;AAAA,EAC3C;AACF;AAGA,eAAsB,iBACpB,SACA,OAUmE;AACnE,SAAQ,MAAM,IAAI,SAAS,MAAM,4BAA4B;AAAA,IAC3D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,iBACpB,SACA,OAC+B;AAC/B,SAAQ,MAAM,IAAI,SAAS,MAAM,gCAAgC;AAAA,IAC/D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,MAAM,SAAiB,OAAqC;AAChF,SAAQ,MAAM,IAAI,SAAS,OAAO,aAAa;AACjD;AAOA,eAAsB,iBACpB,SACA,OACA,OAC8D;AAC9D,SAAQ,MAAM,IAAI,SAAS,OAAO,eAAe;AAAA,IAC/C,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AA+CA,eAAsB,sBACpB,SACA,OACA,SACA,QACA,SAAS,OACuB;AAChC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,IAC9D,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC,EAAE;AAAA,EACrD;AACF;AAQA,eAAsB,sBACpB,SACA,OACA,SAAS,OAC0B;AACnC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,uCAAuC,mBAAmB,MAAM,CAAC;AAAA,EACnE;AACF;AAQA,eAAsB,kBACpB,SACA,OACA,OAC+C;AAC/C,SAAQ,MAAM,IAAI,SAAS,OAAO,0BAA0B;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,eAAsB,cACpB,SACA,OAC4B;AAC5B,SAAQ,MAAM,IAAI,SAAS,OAAO,iBAAiB;AACrD;AAEA,eAAsB,eACpB,SACA,OACA,MAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,mBAAmB;AAAA,IACnD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,EAC/B,CAAC;AACH;AAEA,eAAsB,eACpB,SACA,OACA,SACe;AACf,QAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,IAAI,EAAE,QAAQ,SAAS,CAAC;AAC9E;AAGA,eAAsB,eACpB,SACA,OACA,SACA,MAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,IAAI;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC;AAAA,EAC/B,CAAC;AACH;AAGA,eAAsB,gBACpB,SACA,OACA,SACyB;AACzB,SAAQ,MAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,UAAU;AACxE;AAGA,eAAsB,oBACpB,SACA,OACA,SAC4B;AAC5B,SAAQ,MAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,eAAe;AAAA,IACzE,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,eAAsB,aACpB,SACA,OACA,SAC2B;AAC3B,SAAQ,MAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,QAAQ;AACtE;AAGA,eAAsB,gBACpB,SACA,OACA,OAAwD,CAAC,GAChC;AACzB,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,SAAS,EAAE,GAAG,CAAC,GAAG,GAAG;AACrE,QAAM,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,UAAU,CAAC,GAAG,CAAC;AACvD,QAAM,KAAK,KAAK,KAAK,IAAI,KAAK;AAC9B,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,2BAA2B,KAAK,WAAW,MAAM,GAAG,IAAI,MAAM,mBAAmB,CAAC,CAAC,KAAK,EAAE;AAAA,EAC5F;AACF;AAQA,eAAsB,aACpB,SACA,OACA,OAAyE,CAAC,GACpD;AACtB,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,SAAS,EAAE,GAAG,CAAC,GAAG,GAAG;AACrE,QAAM,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,UAAU,CAAC,GAAG,CAAC;AACvD,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,KAAK,KAAK,KAAK,IAAI,KAAK;AAC9B,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,KAAK,WAAW,MAAM,WAAW,mBAAmB,MAAM,CAAC,MAChF,IAAI,MAAM,mBAAmB,CAAC,CAAC,KAAK;AAAA,EACzC;AACF;AAGA,eAAsB,WACpB,SACA,OACA,SACA,QACwB;AACxB,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,EAChE;AACF;AAQA,eAAsB,eACpB,SACA,OACA,QACwB;AACxB,SAAQ,MAAM,IAAI,SAAS,OAAO,kBAAkB,mBAAmB,MAAM,CAAC,EAAE;AAClF;AAGA,eAAsB,eAAe,SAAiB,OAAyC;AAC7F,SAAQ,MAAM,IAAI,SAAS,OAAO,uBAAuB;AAC3D;AAQA,eAAsB,kBACpB,SACA,OAC6B;AAC7B,SAAQ,MAAM,IAAI,SAAS,OAAO,0BAA0B;AAC9D;AAGA,eAAsB,kBACpB,SACA,OACA,SACA,QAC6B;AAC7B,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,EAChE;AACF;AASA,eAAsB,mBACpB,SACA,OACA,SACA,QACA,OACsC;AACtC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,IAC9D,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,KAAK,EAAE;AAAA,EAChD;AACF;AAGA,eAAsB,mBACpB,SACA,OACA,SACA,QACA,SAC+B;AAC/B,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC,WAAW,OAAO;AAAA,IAChF,EAAE,QAAQ,SAAS;AAAA,EACrB;AACF;AAGA,eAAsB,mBACpB,SACA,OACA,QAC4B;AAC5B,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB,mBAAmB,MAAM,CAAC,UAAU;AAAA,IAC7F,QAAQ;AAAA,EACV,CAAC;AACH;AAQA,eAAsB,gBACpB,SACA,OACA,QACA,OAA4C,CAAC,GACa;AAC1D,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,SAAS,EAAE,GAAG,CAAC,GAAG,GAAG;AACrE,QAAM,SAAS,KAAK,IAAI,KAAK,MAAM,KAAK,UAAU,CAAC,GAAG,CAAC;AACvD,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,kBAAkB,mBAAmB,MAAM,CAAC,mBAAmB,KAAK,WAAW,MAAM;AAAA,EACvF;AACF;AAEA,eAAsB,cACpB,SACA,OACA,SACA,QACA,OACe;AACf,QAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,UAAU;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,QAAQ,MAAM,CAAC;AAAA,EACxC,CAAC;AACH;AAQA,eAAsB,gBACpB,SACA,OAC0B;AAC1B,QAAM,OAAQ,MAAM,IAAI,SAAS,MAAM,mBAAmB;AAAA,IACxD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACD,SAAO,EAAE,QAAQ,QAAQ,QAAQ,OAAO,EAAE,GAAG,GAAG,KAAK;AACvD;AAQA,eAAsB,mBACpB,SACA,OAQ0B;AAC1B,QAAM,OAAQ,MAAM,IAAI,SAAS,MAAM,2BAA2B;AAAA,IAChE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACD,SAAO,EAAE,QAAQ,QAAQ,QAAQ,OAAO,EAAE,GAAG,GAAG,KAAK;AACvD;AASA,eAAsB,iBACpB,SACA,OACA,OAC0B;AAC1B,QAAM,OAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB;AAAA,IAC/D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACD,SAAO,EAAE,QAAQ,QAAQ,QAAQ,OAAO,EAAE,GAAG,GAAG,KAAK;AACvD;AAEA,eAAsB,cACpB,SACA,OACA,SACA,QACe;AACf,QAAM,IAAI,SAAS,OAAO,mBAAmB,OAAO,UAAU,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAC9F;AAGA,eAAsB,cACpB,SACA,OACA,SACA,QACA,OACmD;AACnD,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,IAC9D,EAAE,QAAQ,SAAS,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC,EAAE;AAAA,EACrD;AACF;AAQA,eAAsB,uBACpB,SACA,YACA,OAC4C;AAC5C,SAAQ,MAAM,IAAI,SAAS,YAAY,6BAA6B;AAAA,IAClE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,YACpB,SACA,OACA,SACA,QACA,eAC8C;AAC9C,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,OAAO,UAAU,mBAAmB,MAAM,CAAC;AAAA,IAC9D,EAAE,QAAQ,SAAS,MAAM,KAAK,UAAU,EAAE,SAAS,cAAc,CAAC,EAAE;AAAA,EACtE;AACF;AAGA,eAAsB,SAAS,SAAqC;AAClE,SAAQ,MAAM,IAAI,SAAS,MAAM,gBAAgB;AACnD;AAGA,eAAsB,gBACpB,SACA,OAC8B;AAC9B,SAAQ,MAAM,IAAI,SAAS,OAAO,uBAAuB;AAC3D;AAGA,eAAsB,YACpB,SACA,OAC4E;AAC5E,SAAQ,MAAM,IAAI,SAAS,OAAO,2BAA2B;AAK/D;AAGA,eAAsB,UAAU,SAAiB,OAAwC;AACvF,SAAQ,MAAM,IAAI,SAAS,OAAO,iBAAiB;AACrD;AAQA,eAAsB,aACpB,SACA,OACA,SACiD;AACjD,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,mBAAmB,mBAAmB,OAAO,CAAC;AAAA,EAChD;AACF;AAKA,eAAsB,iBACpB,SACA,OACA,QAAQ,IACmD;AAC3D,SAAQ,MAAM,IAAI,SAAS,OAAO,gCAAgC,KAAK,EAAE;AAI3E;AAGA,eAAsB,yBACpB,SACA,OACA,OAC8C;AAC9C,SAAQ,MAAM,IAAI,SAAS,OAAO,+BAA+B;AAAA,IAC/D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AA8CA,eAAsB,YACpB,SACA,OACA,SAAmE,CAAC,GAC7C;AACvB,QAAM,IAAI,IAAI,gBAAgB;AAC9B,MAAI,OAAO,KAAM,GAAE,IAAI,QAAQ,OAAO,IAAI;AAC1C,MAAI,OAAO,GAAI,GAAE,IAAI,MAAM,OAAO,EAAE;AACpC,MAAI,OAAO,QAAS,GAAE,IAAI,WAAW,OAAO,OAAO;AACnD,QAAM,KAAK,EAAE,SAAS;AACtB,SAAQ,MAAM,IAAI,SAAS,OAAO,iBAAiB,KAAK,IAAI,EAAE,KAAK,EAAE,EAAE;AACzE;AAGA,eAAsB,mBACpB,SACA,OACA,QACA,SAOe;AACf,QAAM,IAAI,SAAS,OAAO,kBAAkB,MAAM,UAAU;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,OAAO;AAAA,EAC9B,CAAC;AACH;AA+IA,SAAS,WAAW,QAA6D;AAC/E,QAAM,IAAI,IAAI,gBAAgB;AAC9B,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC3C,QAAI,MAAM,UAAa,MAAM,GAAI;AACjC,MAAE,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,EACpB;AACA,QAAM,KAAK,EAAE,SAAS;AACtB,SAAO,KAAK,IAAI,EAAE,KAAK;AACzB;AAGA,eAAsB,iBACpB,SACA,OAC2B;AAC3B,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB;AAC7D;AAGA,eAAsB,cACpB,SACA,OACA,SAQI,CAAC,GACiD;AACtD,QAAM,KAAK,WAAW;AAAA,IACpB,GAAG,OAAO;AAAA,IACV,QAAQ,OAAO;AAAA,IACf,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,WAAW,OAAO,YAAY,IAAI;AAAA,IAClC,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,uBAAuB,EAAE,EAAE;AAI/D;AAGA,eAAsB,mBACpB,SACA,OACA,QAC6B;AAC7B,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,EACpD;AACF;AAGA,eAAsB,mBACpB,SACA,OACA,QACA,OAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,mBAAmB,MAAM,CAAC,IAAI;AAAA,IACtF,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,eAAsB,oBACpB,SACA,OACA,QACyC;AACzC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,IAClD,EAAE,QAAQ,OAAO;AAAA,EACnB;AACF;AAEA,eAAsB,mBACpB,SACA,OACA,QACyC;AACzC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,IAClD,EAAE,QAAQ,OAAO;AAAA,EACnB;AACF;AAGA,eAAsB,mBACpB,SACA,OACA,QACyC;AACzC,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,mBAAmB,MAAM,CAAC,IAAI;AAAA,IACtF,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,gBACpB,SACA,OACA,QACA,MACuC;AACvC,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,IAClD,EAAE,QAAQ,SAAS,MAAM,KAAK,UAAU,EAAE,KAAK,CAAC,EAAE;AAAA,EACpD;AACF;AAGA,eAAsB,sBACpB,SACA,OACA,QACA,aAC2C;AAC3C,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,IAClD,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,EAAE,YAAY,CAAC,EAAE;AAAA,EAC1D;AACF;AAGA,eAAsB,mBACpB,SACA,OACA,QACA,OAC2D;AAC3D,SAAQ,MAAM;AAAA,IACZ;AAAA,IACA;AAAA,IACA,wBAAwB,mBAAmB,MAAM,CAAC;AAAA,IAClD,EAAE,QAAQ,QAAQ,MAAM,KAAK,UAAU,KAAK,EAAE;AAAA,EAChD;AACF;AAGA,eAAsB,oBACpB,SACA,OACA,SACmD;AACnD,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB,mBAAmB,OAAO,CAAC,IAAI;AAAA,IACxF,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,mBACpB,SACA,OACA,QAC+B;AAC/B,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,mBAAmB,MAAM,CAAC,IAAI;AAAA,IACtF,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,eACpB,SACA,OACA,SAQI,CAAC,GACkD;AACvD,QAAM,KAAK,WAAW;AAAA,IACpB,QAAQ,OAAO;AAAA,IACf,UAAU,OAAO;AAAA,IACjB,GAAG,OAAO;AAAA,IACV,MAAM,OAAO;AAAA,IACb,IAAI,OAAO;AAAA,IACX,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,EAAE,EAAE;AAIhE;AAGA,eAAsB,kBACpB,SACA,OACA,SACA,OAC2B;AAC3B,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB,mBAAmB,OAAO,CAAC,SAAS;AAAA,IAC7F,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,cACpB,SACA,OACA,SAAgF,CAAC,GACtB;AAC3D,QAAM,KAAK,WAAW;AAAA,IACpB,QAAQ,OAAO;AAAA,IACf,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,uBAAuB,EAAE,EAAE;AAI/D;AAGA,eAAsB,yBACpB,SACA,OACA,SAA6E,CAAC,GACd;AAChE,QAAM,KAAK,WAAW;AAAA,IACpB,GAAG,OAAO;AAAA,IACV,SAAS,OAAO,YAAY,SAAY,SAAY,OAAO,UAAU,IAAI;AAAA,IACzE,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,mCAAmC,EAAE,EAAE;AAI3E;AAQO,SAAS,sBACd,SACA,OACA,gBACA,UACA,YACY;AACZ,QAAM,aAAa,IAAI,gBAAgB;AACvC,MAAI,YAAY;AAChB,QAAM,MAAM,GAAG,YAAY,OAAO,EAAE,QAAQ,OAAO,EAAE,CAAC;AACtD,GAAC,YAAY;AACX,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC3B,QAAQ,WAAW;AAAA,QACnB,SAAS,EAAE,QAAQ,qBAAqB,eAAe,UAAU,KAAK,GAAG;AAAA,MAC3E,CAAC;AACD,UAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAAM;AACxB,mBAAW;AACX;AAAA,MACF;AACA,YAAM,SAAS,IAAI,KAAK,UAAU;AAClC,YAAM,UAAU,IAAI,YAAY;AAChC,UAAI,MAAM;AACV,iBAAS;AACP,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AAEV,qBAAa;AACb,eAAO,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAC7C,YAAI;AACJ,gBAAQ,MAAM,IAAI,QAAQ,MAAM,MAAM,GAAG;AACvC,gBAAM,QAAQ,IAAI,MAAM,GAAG,GAAG;AAC9B,gBAAM,IAAI,MAAM,MAAM,CAAC;AACvB,gBAAM,OAAO,MACV,MAAM,IAAI,EACV,OAAO,CAAC,MAAM,EAAE,WAAW,OAAO,CAAC,EACnC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAC5B,KAAK,IAAI;AACZ,cAAI,CAAC,KAAM;AACX,cAAI;AACF,2BAAe,KAAK,MAAM,IAAI,CAAwB;AAAA,UACxD,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF,QAAQ;AAAA,IAER,UAAE;AACA,UAAI,CAAC,UAAW,YAAW;AAAA,IAC7B;AAAA,EACF,GAAG;AACH,SAAO,MAAM;AACX,gBAAY;AACZ,eAAW,MAAM;AAAA,EACnB;AACF;AA8EA,eAAsB,kBACpB,SACA,OACoC;AACpC,SAAQ,MAAM,IAAI,SAAS,OAAO,0BAA0B;AAC9D;AAGA,eAAsB,sBACpB,SACA,OACA,UAC8C;AAC9C,SAAQ,MAAM,IAAI,SAAS,OAAO,4BAA4B;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,QAAQ;AAAA,EAC/B,CAAC;AACH;AAmBA,eAAsB,QAAQ,SAAiB,OAAiC;AAC9E,SAAQ,MAAM,IAAI,SAAS,OAAO,eAAe;AACnD;AAGA,eAAsB,YACpB,SACA,OACA,MAC4B;AAC5B,SAAQ,MAAM,IAAI,SAAS,OAAO,iBAAiB;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,IAAI;AAAA,EAC3B,CAAC;AACH;AAGA,eAAsB,cACpB,SACA,OAC2E;AAC3E,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB;AAAA,IACxD,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,gBACpB,SACA,OACA,QAAQ,IACiC;AACzC,QAAM,KAAK,UAAU,mBAAmB,OAAO,KAAK,CAAC,CAAC;AACtD,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB,EAAE,EAAE;AAGjE;AAuBA,eAAsB,gBACpB,SACA,OAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,sBAAsB;AAC1D;AAOA,eAAsB,oBACpB,SACA,OACA,OAQgC;AAChC,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB;AAAA,IACxD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,qBACpB,SACA,OAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB;AAAA,IACxD,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,iBACpB,SACA,OAC6E;AAC7E,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB;AAM7D;AA4JA,eAAsB,cACpB,SACA,OACA,OAAkE,CAAC,GACvC;AAC5B,QAAM,IAAI,IAAI,gBAAgB;AAC9B,MAAI,KAAK,KAAM,GAAE,IAAI,QAAQ,KAAK,IAAI;AACtC,MAAI,KAAK,GAAI,GAAE,IAAI,MAAM,KAAK,EAAE;AAChC,MAAI,KAAK,QAAS,GAAE,IAAI,WAAW,KAAK,OAAO;AAC/C,QAAM,SAAS,EAAE,SAAS,IAAI,IAAI,CAAC,KAAK;AACxC,SAAQ,MAAM,IAAI,SAAS,OAAO,uBAAuB,MAAM,EAAE;AACnE;AAQA,eAAsB,gBACpB,SACA,OACqE;AACrE,SAAQ,MAAM,IAAI,SAAS,OAAO,mCAAmC;AAAA,IACnE,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,eAAsB,qBACpB,SACA,OACA,OAC6D;AAC7D,SAAQ,MAAM,IAAI,SAAS,OAAO,gCAAgC;AAAA,IAChE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,eAAsB,oBACpB,SACA,OACuC;AACvC,SAAQ,MAAM,IAAI,SAAS,OAAO,6BAA6B;AACjE;AAEA,eAAsB,0BACpB,SACA,OACA,SACgD;AAChD,SAAQ,MAAM,IAAI,SAAS,OAAO,+BAA+B;AAAA,IAC/D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,OAAO;AAAA,EAC9B,CAAC;AACH;AA4DA,eAAsB,eACpB,SACA,OACA,QACA,OAAkE,CAAC,GACtC;AAC7B,QAAM,IAAI,IAAI,gBAAgB,EAAE,OAAO,CAAC;AACxC,MAAI,KAAK,KAAM,GAAE,IAAI,QAAQ,KAAK,IAAI;AACtC,MAAI,KAAK,GAAI,GAAE,IAAI,MAAM,KAAK,EAAE;AAChC,MAAI,KAAK,QAAS,GAAE,IAAI,WAAW,KAAK,OAAO;AAC/C,SAAQ,MAAM,IAAI,SAAS,OAAO,0BAA0B,CAAC,EAAE;AACjE;AAQA,eAAsB,cACpB,SACA,OACA,QACA,OACmD;AACnD,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,MAAM,WAAW;AAAA,IACzE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,WAAW,MAAM,YAAY,MAAM,MAAM,QAAQ,GAAG,CAAC;AAAA,EAC9E,CAAC;AACH;AAGA,eAAsB,kBACpB,SACA,OACiC;AACjC,SAAQ,MAAM,IAAI,SAAS,OAAO,0BAA0B;AAC9D;AAGA,eAAsB,wBACpB,SACA,OACA,SACiC;AACjC,SAAQ,MAAM,IAAI,SAAS,OAAO,4BAA4B;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,EAClC,CAAC;AACH;AAGA,eAAsB,uBACpB,SACA,OACiC;AACjC,SAAQ,MAAM,IAAI,SAAS,OAAO,4BAA4B;AAAA,IAC5D,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,kBACpB,SACA,OAC+B;AAC/B,SAAQ,MAAM,IAAI,SAAS,OAAO,8BAA8B;AAClE;AAGA,eAAsB,oBACpB,SACA,OACA,OACoC;AACpC,SAAQ,MAAM,IAAI,SAAS,OAAO,gCAAgC;AAAA,IAChE,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,uBACpB,SACA,OACA,IACA,OACoC;AACpC,SAAQ,MAAM,IAAI,SAAS,OAAO,gCAAgC,mBAAmB,EAAE,CAAC,IAAI;AAAA,IAC1F,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAGA,eAAsB,uBACpB,SACA,OACA,IAC0B;AAC1B,SAAQ,MAAM,IAAI,SAAS,OAAO,gCAAgC,mBAAmB,EAAE,CAAC,IAAI;AAAA,IAC1F,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,eAAsB,iBACpB,SACA,OACA,QACA,OACkC;AAClC,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,mBAAmB,MAAM,CAAC,SAAS;AAAA,IAC3F,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AA4DA,eAAsB,UAAU,SAAiB,OAAuC;AACtF,SAAQ,MAAM,IAAI,SAAS,OAAO,iBAAiB;AACrD;AAEA,eAAsB,eACpB,SACA,OACA,OAMC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,yBAAyB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,eAAsB,YACpB,SACA,OACA,OAMC;AACD,SAAQ,MAAM,IAAI,SAAS,OAAO,8BAA8B;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAqGA,eAAsB,sBACpB,SACA,OACA,QAAwC,CAAC,GACd;AAC3B,QAAM,IAAI,IAAI,gBAAgB;AAC9B,MAAI,MAAM,KAAM,GAAE,IAAI,QAAQ,MAAM,IAAI;AACxC,MAAI,MAAM,GAAI,GAAE,IAAI,MAAM,MAAM,EAAE;AAClC,QAAM,SAAS,EAAE,SAAS,IAAI,IAAI,CAAC,KAAK;AACxC,SAAQ,MAAM,IAAI,SAAS,OAAO,iCAAiC,MAAM,EAAE;AAC7E;AAGO,SAAS,oBACd,SACA,QAAwC,CAAC,GACjC;AACR,QAAM,IAAI,IAAI,gBAAgB;AAC9B,MAAI,MAAM,KAAM,GAAE,IAAI,QAAQ,MAAM,IAAI;AACxC,MAAI,MAAM,GAAI,GAAE,IAAI,MAAM,MAAM,EAAE;AAClC,QAAM,SAAS,EAAE,SAAS,IAAI,IAAI,CAAC,KAAK;AACxC,SAAO,GAAG,YAAY,OAAO,EAAE,QAAQ,OAAO,EAAE,CAAC,wCAAwC,MAAM;AACjG;AAEA,eAAsB,eACpB,SACA,OACA,QACA,OACiG;AACjG,SAAQ,MAAM,IAAI,SAAS,OAAO,wBAAwB,mBAAmB,MAAM,CAAC,WAAW;AAAA,IAC7F,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU,KAAK;AAAA,EAC5B,CAAC;AACH;AAkEA,eAAsB,aACpB,SACA,OACA,QAAwC,CAAC,GAClB;AACvB,QAAM,IAAI,IAAI,gBAAgB;AAC9B,MAAI,MAAM,KAAM,GAAE,IAAI,QAAQ,MAAM,IAAI;AACxC,MAAI,MAAM,GAAI,GAAE,IAAI,MAAM,MAAM,EAAE;AAClC,QAAM,SAAS,EAAE,SAAS,IAAI,IAAI,CAAC,KAAK;AACxC,SAAQ,MAAM,IAAI,SAAS,OAAO,qBAAqB,MAAM,EAAE;AACjE;","names":[]}