waygate-open 1.6.19 → 1.6.21

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/README.md CHANGED
@@ -8,8 +8,8 @@
8
8
  [![NestJS](https://img.shields.io/badge/NestJS-12-E0234E?style=flat-square)](https://nestjs.com)
9
9
  [![SQLite](https://img.shields.io/badge/SQLite-better--sqlite3-003B57?style=flat-square)](https://github.com/WiseLibs/better-sqlite3)
10
10
  [![Node](https://img.shields.io/badge/Node-%E2%89%A522.9-3C873A?style=flat-square)](https://nodejs.org)
11
- [![version](https://img.shields.io/badge/version-1.6.19-4A38C0?style=flat-square)](https://github.com/zhanghaojie513/waygate-open/releases)
12
- [![tests](https://img.shields.io/badge/tests-750%20passed-0A7247?style=flat-square)](./server)
11
+ [![version](https://img.shields.io/badge/version-1.6.21-4A38C0?style=flat-square)](https://github.com/zhanghaojie513/waygate-open/releases)
12
+ [![tests](https://img.shields.io/badge/tests-754%20passed-0A7247?style=flat-square)](./server)
13
13
 
14
14
  ![途关 · Waygate](docs/img/intro.png)
15
15
 
@@ -45,27 +45,40 @@
45
45
  - **请求日志**:提供商列按「模型 id → 目录归属」解析(`requests` 无 provider_id,绑定名只代表路由偏好);SSE 流也留痕;**游标(keyset)分页**——翻页期间的新写入不产生重复行;按 `logs.retainDays` 每日自动清理过期日志(启动即补跑停机期间欠的账)
46
46
  - **积分与用量**:趋势图(Token / 请求数双口径,**等距时间轴**,空桶补零)、按模型 / 按提供商拆分、缓存命中率、缓存 token 计量(Anthropic 口径:prompt = input + cache_creation + cache_read)
47
47
  - **积分账本**:MiniMax `credit/details` 真实余额(精确到 0.01);凭证 refresh token 自动续期 + 每日签到(专用账号后即可代签);WorkBuddy 资源包聚合余额(`get-user-resource` POST 实测口径)
48
- - **Qoder 签到与积分**(v1.6.0,积分口径 v1.6.1 更正):`campaigns` 签到代领(每日 100 Credits,进**资源包**)+ 积分账本并入概览。走 `openapi.qoder.com.cn`,Bearer 令牌鉴权(**无需 COSY 签名**——那套只用于聊天路径),不顶掉桌面登录。**余额口径**:`v2/me/usage` 给真实余额(`userQuota` 套餐内 + `addOnQuota` 资源包),账本「发放/剩余/已消耗」为真实值;该端点**不提供到期时刻**,故「最近过期」列显示 `—`。另注意 `credits-summary.totalCredits` 是**历史累计消耗**(等于逐日用量之和),与余额不是一回事,单列展示不混算
48
+ - **Qoder 签到与积分**(v1.6.0,积分口径 v1.6.1 更正):`campaigns` 签到代领(每日 100 Credits,进**资源包**)。走 `openapi.qoder.com.cn`,Bearer 令牌鉴权(**无需 COSY 签名**——那套只用于聊天路径),不顶掉桌面登录。**余额口径**:`v2/me/usage` 给真实余额(`userQuota` 套餐内 + `addOnQuota` 资源包),展示为「发放/剩余/已消耗」;该端点**不提供到期时刻**,故「最近过期」显示 `—`。另注意 `credits-summary.totalCredits` 是**历史累计消耗**(等于逐日用量之和),与余额不是一回事,单列展示不混算
49
49
  - **WorkBuddy 领取域**(v1.4.0;v1.6.11 起与转发统一):签到代领 + 成长任务 + 连登兑换走**用户会话域**(OAuth,`providers/<id>.oauth`,多会话共存不互踢)——正是这条链路让 `ck_` 得以整体退役。诚实边界:任务多为「体验 XX」型,前置条件不满足就如实记 `skipped`,**绝不伪造行为去凑任务**;补登卡/抽奖等资源消耗型操作不自动化
50
- - **容错**:Qoder 零内容断流**自动重试一次**(对客户端无感);上游 11128 渠道校验自动降级重试
50
+ - **容错**:Qoder 零内容断流**自动重试一次**(对客户端无感);上游 11128 渠道校验自动降级重试;**11128 渠道熔断**(连续 3 次「降级重试也 11128」即开闸,冷却期内未打上游、未占并发槽、未计费,毫秒级 502 + `retry-after`,阵发过去自动半开放行探测)——上游阵发期每个失败要烧 ~65s,并发槽是全局单计数,不加熔断会烧满槽饿死其他提供商;**只计渠道级信号**(内容级 11128 降级能过的不计入),避免一个客户端的请求特征变成全提供商的闸门
51
+ - **审计不丢行**:流式请求在途期间密钥被删时,落库撞 `key_id` 外键会丢整行 —— 现自动降级 `key_id = NULL` 重试保住整行(tokens / 积分 / 状态 / 耗时照常),`client_id` / `key_hint` 快照仍在,note 标记 `key_deleted_inflight`
51
52
  - **控制台**:React 19 单页,明暗双主题(跟随系统/手动,侧栏底部切换;官网与控制台同套主题——棱镜场景浅色下以 `multiply` 显形、深色下以 `screen`),CSS 伪元素 tooltip 体系,同形状骨架屏;页间数据先旧后新(SWR:重进页面即刻渲染上次数据、后台刷新无缝替换,自己的写操作写穿缓存不闪旧态);「提供商健康」只读总览
52
53
  - **性能**:路由级代码分割:官网与控制台互不加载对方代码,首屏 gzip <100K;vendor 独立 chunk 享 30d `immutable` 长缓存(发版只重下业务 chunk);概览 5s TTL 缓存(写操作即清)、SQL 语句按文本缓存复用、凭证文件 stat 失效缓存、last_seen 节流落库——同进程转发 SSE,省下的每次查询/写库都是还给流的事件循环时间;Nginx gzip(level 5)压缩传输
53
54
 
54
55
  ## 快速开始
55
56
 
56
- ### 方式一:npm 包(推荐,一条命令)
57
+ ### 方式一:npm 包(推荐)
58
+
59
+ **试用 / 一次性运行** —— `npx` 首次会自动下载到缓存并执行,不必先全局安装:
57
60
 
58
61
  ```bash
59
62
  npx waygate-open
60
63
  ```
61
64
 
62
- 起本地服务并**自动打开浏览器**到控制台(默认 <http://127.0.0.1:8787/console>)。
65
+ **长期使用** —— 装成全局命令,之后直接敲 `waygate-open`(不再经 npx,启动更快、版本可控):
66
+
67
+ ```bash
68
+ npm install -g waygate-open
69
+ waygate-open
70
+ ```
71
+
72
+ 想看固定版本而不是跟着 `latest` 走:`npx waygate-open@1.6.21`。
73
+
74
+ 两种方式行为一致:起本地服务并**自动打开浏览器**到控制台(默认 <http://127.0.0.1:8787/console>)。
63
75
  首次启动的管理口令默认是 **`waygate`** —— 仅当只监听回环(`127.0.0.1`)时套用这个
64
76
  默认值;对外监听时改为随机生成并打印在启动日志里。登录后建议去「设置 → 管理口令」改掉。
65
77
 
66
- 数据默认落在 `~/.waygate/open/`(库与密钥都在那里)。
78
+ 数据默认落在 `~/.waygate/open/`(库与密钥都在那里)—— 与工作目录无关,
79
+ 所以全局安装后在任意目录启动都读同一份数据。
67
80
 
68
- 常用参数(都有等价环境变量):
81
+ 常用参数(都有等价环境变量;全局安装后把 `npx waygate-open` 换成 `waygate-open` 即可):
69
82
 
70
83
  ```bash
71
84
  npx waygate-open --port 9000 # 换端口(RELAY_PORT)
@@ -104,9 +117,27 @@ npm run dev:mock # 或:纯前端开发,内置 Mock 数据(口令
104
117
  | `RELAY_UPSTREAM_KEY` | 可选:无 OAuth 时代的全局出站凭证(**v1.6.11 起 WorkBuddy 走 OAuth,不再需要**) |
105
118
  | `RELAY_SECRET` | HMAC:密钥哈希 + 会话令牌签名,丢失等于全部密钥作废 |
106
119
  | `RELAY_ADMIN_PASSWORD_HASH` | 管理口令 scrypt 哈希(不设则四级回落,首启打印一次性口令) |
107
- | `RELAY_DATA_DIR` / `RELAY_PORT` | 数据目录(默认 `./data`)与端口(默认 8787) |
120
+ | `RELAY_DATA_DIR` / `RELAY_PORT` | 数据目录与端口(默认 8787)。**数据目录默认值随形态不同**:源码模式 = `./data`(相对启动目录);npm 包(`npx` / 全局)由 bin 传 `~/.waygate/open`,与工作目录无关 |
121
+
122
+ ### 第一步:导入客户端凭证
123
+
124
+ 三家凭证都来自各客户端的**官方授权**(用户拿不到、也无法手填),入口只有一个:
125
+ 控制台「提供商管理 → 导入客户端」(或某一行上的「重新导入授权」用于换账号 / 刷新令牌)。
126
+
127
+ | 客户端 | 授权方式 | 你需要做什么 |
128
+ | --- | --- | --- |
129
+ | **WorkBuddy** | 官方 OAuth 设备授权 | 弹窗里点「打开授权页」→ 在浏览器确认 → 回来自动落盘 |
130
+ | **MiniMax Code** | PKCE 设备码 | 同上(授权页显示设备码时确认即可) |
131
+ | **Qoder CN** | PKCE 设备授权 | 同上(未授权时上游回 404,属正常等待态) |
132
+
133
+ > [!NOTE]
134
+ > 私有状态(`device_code` / `code_verifier`)**只存服务端**,绝不回传浏览器。
135
+ > 授权窗口最多等 2 分钟;超时后可用命令续上(`--help` 看对应命令名)。
136
+
137
+ **同一个客户端出现在两台机器**(例如 relay 跑在服务器、客户端在你本机)时,
138
+ 服务端读不到本机登录态 —— 这是正常的,按弹窗提示用「本机推送命令」从你的机器推上去即可。
108
139
 
109
- **Qoder CN 凭证**(若要用第三家):从本机 Qoder 客户端登录态一键导入,无需手抄令牌:
140
+ **Qoder CN 凭证**(若要用第三家):也可从本机 Qoder 客户端登录态一键导入,无需手抄令牌:
110
141
 
111
142
  ```bash
112
143
  cd server && node tools/qoder-import-credential.mjs <provider_id>
@@ -170,7 +201,7 @@ curl http://127.0.0.1:8787/v1/chat/completions \
170
201
  | 提供商 | 协议 | 凭证形态 | 目录来源 | 积分口径 |
171
202
  | --- | --- | --- | --- | --- |
172
203
  | **WorkBuddy** | `openai`(`/v2/chat/completions`) | **官方 OAuth 设备授权**(转发与领取域同一凭证,v1.6.11 起;原 `ck_` 密钥已退役) | 上游拉取 + 逐项实测 | 按次回报 `usage.credit`(实扣)+ 资源包聚合余额 |
173
- | **MiniMax Code** | `anthropic`(`/mavis/api/v1/llm/v1/messages`) | OAuth refresh token(`mmort_`,自动换新) | 内置种子(网关无清单端点) | **独立账本** `credit/details`(精确到 0.01);单次不回报 → 记 `null` 不编造 |
204
+ | **MiniMax Code** | `anthropic`(`/mavis/api/v1/llm/v1/messages`) | OAuth refresh token(`mmort_`,自动换新) | **上游真拉取** `/mavis/api/v1/models`(失败回落内置种子) | **独立账本** `credit/details`(精确到 0.01);单次不回报 → 记 `null` 不编造 |
174
205
  | **Qoder CN** | `qoder`(COSY 协议,`/algo/…/agent_chat_generation`) | 客户端登录态身份 JSON(`dt-`/`drt-`) | 网关 `/model/list` 真拉取(13 模型) | SSE `usage.credits` × `billable`(免费档实扣 0);余额走 `openapi` 的 `v2/me/usage`(套餐内 + 资源包,Bearer 无 COSY 签名);到期时刻上游不提供 |
175
206
 
176
207
  **路由规则**:模型 id 先跨全部生效提供商**精确匹配(大小写敏感)**,再按提供商内模糊回落。声明行(seed/upstream/manual)优先于 observed 行——observed 泄漏曾导致幽灵目录行抢走别家请求(已修)。
@@ -297,7 +328,7 @@ mindmap
297
328
 
298
329
  ```bash
299
330
  cd server
300
- npm test # 727 项单测(vitest):路由优先级、口径不变式、三种方言翻译、失败落库、趋势桶补齐…
331
+ npm test # 754 项单测(vitest):路由优先级、口径不变式、三种方言翻译、失败落库、趋势桶补齐…
301
332
  npm run typecheck # tsc --noEmit
302
333
  npm run smoke:boot # 真起 dist:healthz / 登录 / 目录 / 设置四关
303
334
  ```
@@ -353,10 +384,11 @@ npm run dev # 联调:代理到 127.0.0.1:8787(推荐)
353
384
  ```
354
385
  server/ NestJS + Fastify + better-sqlite3(单文件 SQLite)
355
386
  src/proxy/ 转发核心:路由解析、三种方言出口(openai/anthropic/qoder)、失败落库
387
+ channel-breaker.ts 11128 渠道熔断状态机(按提供商独立、内存态、时钟可注入)
356
388
  anthropic-upstream/ MiniMax Code 出站(oauth 令牌、账本、SSE 解码、签到)
357
389
  qoder-upstream/ Qoder CN 出站(COSY 会话/签名/自定义 base64、SSE 解码、签到与积分账本)
358
390
  src/api/ 控制台 API:概览聚合、密钥、目录、提供商、设置(含设置元数据目录)
359
- src/catalog/ 目录同步(上游拉取 / observed 并入 / 幽灵行防护 / Qoder 拉取)
391
+ src/catalog/ 目录同步(上游拉取 / observed 并入 / 幽灵行防护 / Qoder 与 MiniMax 拉取)
360
392
  src/store/ 迁移、仓储与日志留存清理(schema.sql 即迁移事实源)
361
393
  src/common/ 跨模块小件(凭证文件的 stat 失效进程内缓存等)
362
394
  tools/ 运维与验收脚本(smoke:boot、凭证导入、迁移…)
@@ -391,4 +423,4 @@ tools/ 仓库级工具(lint 门禁、token 合并、截图
391
423
 
392
424
  ---
393
425
 
394
- 版本约定:`1.0.0` 起 API 契约稳定(当前 `1.6.19`,见 `server/src/version.ts`;历史版本见 [Releases](https://github.com/zhanghaojie513/waygate-open/releases))。
426
+ 版本约定:`1.0.0` 起 API 契约稳定(当前 `1.6.21`,见 `server/src/version.ts`;历史版本见 [Releases](https://github.com/zhanghaojie513/waygate-open/releases))。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygate-open",
3
- "version": "1.6.19",
3
+ "version": "1.6.21",
4
4
  "description": "途关 Waygate · 本地启动 —— 把 WorkBuddy / MiniMax Code / Qoder CN 的客户端订阅转成 OpenAI / Anthropic 兼容 base_url(自带控制台,一条命令起服务并自动打开浏览器)",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -12,13 +12,22 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.RequestsRepository = void 0;
15
+ exports.RequestsRepository = exports.ORPHANED_KEY_NOTE = void 0;
16
16
  const common_1 = require("@nestjs/common");
17
17
  const db_1 = require("../db");
18
18
  const cached_stmt_1 = require("../cached-stmt");
19
19
  const key_usage_cache_1 = require("../key-usage-cache");
20
20
  const aggregates_repo_1 = require("./aggregates.repo");
21
21
  const SORT_WHITELIST = new Set(['ts', 'ms', 'credits', 'status', 'prompt_tokens', 'completion_tokens']);
22
+ /** [v1.6.28] 落库时发现密钥已被删除(在途请求晚于删除)后追加的标记,见 writeLog。 */
23
+ exports.ORPHANED_KEY_NOTE = 'key_deleted_inflight';
24
+ /** better-sqlite3 的 FK 违例:code 是 SQLITE_CONSTRAINT_FOREIGNKEY(787),message 含 FOREIGN KEY。 */
25
+ function isForeignKeyFailure(err) {
26
+ const e = err;
27
+ if (!e)
28
+ return false;
29
+ return e.code === 'SQLITE_CONSTRAINT_FOREIGNKEY' || /FOREIGN KEY constraint failed/i.test(String(e.message ?? ''));
30
+ }
22
31
  let RequestsRepository = class RequestsRepository {
23
32
  db;
24
33
  aggregates;
@@ -57,19 +66,45 @@ let RequestsRepository = class RequestsRepository {
57
66
  }
58
67
  /** 一次请求 = 一行。与 aggregates 的递增在同一事务内(api-contract §6)。 */
59
68
  log(row) {
60
- const id = this.logTx(row);
61
- // 事务已提交才更新用量缓存(事务内更新的话,回滚会让缓存多计)
62
- this.usage.onInsert(row.keyId, row.totalTokens, row.remoteIp);
69
+ const { id, effectiveKeyId } = this.logTx(row);
70
+ // 事务已提交才更新用量缓存(事务内更新的话,回滚会让缓存多计)。
71
+ // 密钥已在途删除时 effectiveKeyId 为 null:那份计数已无归属,不再喂给缓存。
72
+ this.usage.onInsert(effectiveKeyId, row.totalTokens, row.remoteIp);
63
73
  return id;
64
74
  }
65
75
  /** 事务体:插入请求行 + 5 个聚合桶递增(由构造期建好的 logTx 包在同一事务里执行)。 */
66
76
  writeLog(row) {
67
- const info = this.insertStmt.run({
68
- ...row,
69
- reasoningTokens: row.reasoningTokens ?? 0,
70
- stream: row.stream ? 1 : 0,
71
- createdAt: Date.now(),
72
- });
77
+ let info;
78
+ let effectiveKeyId = row.keyId;
79
+ try {
80
+ info = this.insertStmt.run({
81
+ ...row,
82
+ reasoningTokens: row.reasoningTokens ?? 0,
83
+ stream: row.stream ? 1 : 0,
84
+ createdAt: Date.now(),
85
+ });
86
+ }
87
+ catch (err) {
88
+ /* [v1.6.28] 密钥在请求**在途期间**被删:`key_id` 的外键是 ON DELETE SET NULL,
89
+ 但那只在删除时清理**已有**子行;本请求晚于删除落库,INSERT 指向已消失的父行
90
+ ⇒ FOREIGN KEY constraint failed,整行审计(tokens/积分/状态/耗时)被丢掉。
91
+ (生产实证:2026-10-17 探针在流未结束时删临时密钥。)
92
+ 处置:把 key_id 降级为 NULL 重试一次,保住整行。client_id / key_hint 是文本
93
+ 快照、不受外键约束,仍能说明「这条是谁发的」—— 不改成 unidentified,那样会
94
+ 把已识别的流量错记成未识别,污染客户端排行。行上追加标记便于事后辨认。 */
95
+ if (!isForeignKeyFailure(err) || row.keyId === null)
96
+ throw err;
97
+ effectiveKeyId = null;
98
+ info = this.insertStmt.run({
99
+ ...row,
100
+ keyId: null,
101
+ keyHint: row.keyHint ?? null,
102
+ note: [row.note, exports.ORPHANED_KEY_NOTE].filter(Boolean).join(','),
103
+ reasoningTokens: row.reasoningTokens ?? 0,
104
+ stream: row.stream ? 1 : 0,
105
+ createdAt: Date.now(),
106
+ });
107
+ }
73
108
  const errors = row.status >= 400 ? 1 : 0;
74
109
  const delta = {
75
110
  requests: 1,
@@ -90,7 +125,7 @@ let RequestsRepository = class RequestsRepository {
90
125
  this.aggregates.upsert(hour, 'status', String(row.status), delta);
91
126
  this.aggregates.upsert(hour, 'total', '-', delta);
92
127
  this.aggregates.upsert(day, 'total', '-', delta);
93
- return Number(info.lastInsertRowid);
128
+ return { id: Number(info.lastInsertRowid), effectiveKeyId };
94
129
  }
95
130
  buildWhere(q) {
96
131
  const clauses = [];
@@ -12,4 +12,4 @@ exports.VERSION = void 0;
12
12
  * `version` 一致,不一致直接 fail build —— 两处版本永远不会悄悄分叉。
13
13
  * 升版本 = 同时改 package.json 与这里(构建会逼你对齐)。
14
14
  */
15
- exports.VERSION = '1.6.19';
15
+ exports.VERSION = '1.6.21';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygate-server",
3
- "version": "1.6.19",
3
+ "version": "1.6.21",
4
4
  "private": true,
5
5
  "description": "驿站 · Relay — OpenAI 兼容反向代理与控制台(后端)",
6
6
  "engines": {
@@ -0,0 +1,5 @@
1
+ import{D as e,J as t,K as n,S as r,_ as i,a,g as o,h as s,it as c,n as l,t as u,tt as d,z as f}from"./vendor-befEYGHM.js";import{A as p,D as m,M as h,a as g,c as _,g as v,j as y,m as b,r as x,v as S,y as C}from"./index-BMxTTq4E.js";import{a as w,c as T,l as E,n as D,o as O,r as k,s as A,t as j}from"./Select-CrLEZJdb.js";import{t as M}from"./Modal-B-rIrvXS.js";import{n as N}from"./PageSkeleton-DG-Dj4_9.js";var P=c(),F=d(),I={live:`live`,off:`off`,exp:`exp`};function L({rows:e,clientByKey:t,providerNameById:n,pendingId:r,onToggle:i,onEdit:c,onDelete:u}){return(0,F.jsx)(`div`,{className:`tbl-scroll`,children:(0,F.jsxs)(`table`,{className:`tbl`,children:[(0,F.jsxs)(`caption`,{className:`sr-only`,children:[`API Key 列表,共 `,e.length,` 把密钥`]}),(0,F.jsx)(`thead`,{children:(0,F.jsxs)(`tr`,{children:[(0,F.jsx)(`th`,{scope:`col`,children:`名称 / 密钥`}),(0,F.jsx)(`th`,{scope:`col`,children:`绑定提供商`}),(0,F.jsx)(`th`,{scope:`col`,children:`权限范围`}),(0,F.jsx)(`th`,{scope:`col`,className:`r`,children:`近24h请求`}),(0,F.jsx)(`th`,{scope:`col`,className:`r`,children:`最后使用`}),(0,F.jsx)(`th`,{scope:`col`,children:`状态 / 到期`}),(0,F.jsx)(`th`,{scope:`col`,className:`r`,style:{width:184},children:`操作`})]})}),(0,F.jsx)(`tbody`,{children:e.map(e=>{let d=p(e.enabled,e.expiresAt),f=r===e.id,h=t?.get(e.id),_=e.clients??[];return(0,F.jsxs)(`tr`,{children:[(0,F.jsxs)(`td`,{children:[(0,F.jsx)(`div`,{className:`td-strong`,children:e.name}),(0,F.jsx)(`div`,{className:`td-sub td-mono`,children:e.hint})]}),(0,F.jsx)(`td`,{children:_.length>0?(0,F.jsx)(g,{content:`绑定提供商(路由优先级从高到低):\n${_.map(e=>n?.get(e)??e).join(`
2
+ `)}`,children:(0,F.jsx)(`span`,{className:`key-bindings`,children:(()=>{let e=_[0],t=n?.get(e)??e,r=h?.name===t?h:void 0;return(0,F.jsxs)(F.Fragment,{children:[r?(0,F.jsx)(`a`,{href:`#/clients?clientId=${encodeURIComponent(r.clientId)}`,children:t}):t,_.length>1?(0,F.jsx)(`i`,{className:`kb-rank`,children:`优先`}):null]})})()})}):h?(0,F.jsx)(`a`,{href:`#/clients?clientId=${encodeURIComponent(h.clientId)}`,children:h.name}):(0,F.jsx)(`span`,{className:`td-sub`,children:`未绑定`})}),(0,F.jsx)(`td`,{children:C(e.scope)}),(0,F.jsx)(`td`,{className:`r td-mono`,children:v(e.requests24h)}),(0,F.jsx)(`td`,{className:`r td-mono`,children:S(e.lastSeenAt)}),(0,F.jsxs)(`td`,{children:[(0,F.jsxs)(`span`,{style:{display:`inline-flex`,alignItems:`center`,gap:8,whiteSpace:`nowrap`},children:[(0,F.jsx)(E,{tone:I[d],children:m[d]}),(0,F.jsx)(`span`,{className:`td-sub td-mono`,children:e.expiresAt?`至 ${b(e.expiresAt)}`:`永不过期`})]}),e.maxIps!==null||e.tokenQuota!==null?(0,F.jsxs)(`div`,{className:`td-sub td-mono`,style:{marginTop:2},children:[e.maxIps===null?null:`IP ${e.usedIps??0}/${e.maxIps}`,e.maxIps!==null&&e.tokenQuota!==null?` · `:null,e.tokenQuota===null?null:`${v(e.usedTokens??0)}/${v(e.tokenQuota)} tok`,e.ipWhitelist&&e.ipWhitelist.length>0?` · 白名单 ${e.ipWhitelist.length} 条`:null]}):null]}),(0,F.jsx)(`td`,{className:`r`,children:(0,F.jsxs)(`span`,{style:{display:`inline-flex`,gap:8,justifyContent:`flex-start`},children:[(0,F.jsx)(g,{content:`改名 / 改绑定 / 访问控制(不动权限范围,密钥与哈希不受影响)`,children:f?(0,F.jsx)(`span`,{tabIndex:0,children:(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:f,onClick:()=>c(e),children:[(0,F.jsx)(l,{size:12}),`修改`]})}):(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,onClick:()=>c(e),children:[(0,F.jsx)(l,{size:12}),`修改`]})}),(0,F.jsx)(g,{content:d===`exp`?`已过期,需重新创建`:e.enabled?`停用后立即失效`:`重新启用`,children:f||d===`exp`?(0,F.jsx)(`span`,{tabIndex:0,children:(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:!0,onClick:()=>i(e),children:[e.enabled?(0,F.jsx)(o,{size:12}):(0,F.jsx)(s,{size:12}),e.enabled?`停用`:`启用`]})}):(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,onClick:()=>i(e),children:[e.enabled?(0,F.jsx)(o,{size:12}):(0,F.jsx)(s,{size:12}),e.enabled?`停用`:`启用`]})}),(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill danger`,disabled:f,onClick:()=>u(e),children:[(0,F.jsx)(a,{size:12}),`删除`]})]})})]},e.id)})})]})})}var R={ipText:``,maxIps:``,tokenQuota:``};function z(e){return{ipText:e.ipWhitelist.join(`
3
+ `),maxIps:e.maxIps===null?``:String(e.maxIps),tokenQuota:e.tokenQuota===null?``:String(e.tokenQuota)}}function B(e){return e.ipText.trim()!==``||e.maxIps.trim()!==``||e.tokenQuota.trim()!==``}function V(e){let t=e.trim();if(!t)return null;let n=t.indexOf(`/`);if(n>0){let e=t.slice(0,n),r=t.slice(n+1);return!/^\d{1,2}$/.test(r)||Number(r)>32?`「${t}」的掩码位数只能是 0-32`:H(e)?null:`「${t}」CIDR 只支持 IPv4 网段`}return H(t)||t.includes(`:`)&&/^[0-9a-fA-F:]+$/.test(t)?null:`「${t}」不是合法的 IP 地址(支持 IPv4、IPv6、IPv4 CIDR)`}function H(e){let t=e.split(`.`);return t.length===4&&t.every(e=>/^\d{1,3}$/.test(e)&&Number(e)<=255)}function U(e){let t=e.ipText.split(/[\n,]+/).map(e=>e.trim()).filter(e=>e!==``);for(let e of t){let t=V(e);if(t)return{error:`IP 白名单:${t}`}}let n=null;if(e.maxIps.trim()!==``&&(n=Number(e.maxIps),!Number.isInteger(n)||n<1))return{error:`最大 IP 数必须是正整数(留空 = 不限)。`};let r=null;return e.tokenQuota.trim()!==``&&(r=Number(e.tokenQuota),!Number.isInteger(r)||r<1)?{error:`Token 配额必须是正整数(留空 = 不限)。`}:{ipWhitelist:t,maxIps:n,tokenQuota:r}}function W(e){let t=[],n=e.ipText.split(/[\n,]+/).map(e=>e.trim()).filter(e=>e!==``).length;return n>0&&t.push(`白名单 ${n} 条`),e.maxIps.trim()!==``&&t.push(`最大 ${e.maxIps.trim()} IP`),e.tokenQuota.trim()!==``&&t.push(`配额 ${Number(e.tokenQuota).toLocaleString()} tok`),t.length>0?t.join(` · `):`未限制`}function G({draft:e,onChange:t,defaultOpen:r=!1}){let[i,a]=(0,P.useState)(r||B(e));return(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsxs)(`button`,{type:`button`,className:`policy-toggle`,"aria-expanded":i,onClick:()=>a(e=>!e),children:[(0,F.jsx)(n,{size:14,"aria-hidden":!0}),(0,F.jsxs)(`span`,{children:[`访问控制`,i?``:`(可选,留空 = 不限)`]}),i?null:(0,F.jsx)(`span`,{className:`policy-sum`,children:W(e)})]}),i?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`textarea`,{className:`field-input text`,style:{width:`100%`,minHeight:48,resize:`vertical`,fontFamily:`var(--font-mono)`,marginTop:8},value:e.ipText,placeholder:`IP 白名单,每行一个:
4
+ 203.0.113.7
5
+ 10.0.0.0/8`,onChange:n=>t({...e,ipText:n.target.value})}),(0,F.jsxs)(`div`,{style:{display:`flex`,gap:12,marginTop:8},children:[(0,F.jsxs)(`label`,{style:{flex:1,display:`flex`,flexDirection:`column`,gap:4},children:[(0,F.jsx)(`span`,{className:`ov-hint`,children:`最大 IP 数(留空 = 不限)`}),(0,F.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},type:`number`,min:1,value:e.maxIps,placeholder:`如 3`,onChange:n=>t({...e,maxIps:n.target.value})})]}),(0,F.jsxs)(`label`,{style:{flex:1,display:`flex`,flexDirection:`column`,gap:4},children:[(0,F.jsx)(`span`,{className:`ov-hint`,children:`Token 配额(留空 = 不限)`}),(0,F.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},type:`number`,min:1,value:e.tokenQuota,placeholder:`如 1000000`,onChange:n=>t({...e,tokenQuota:n.target.value})})]})]}),(0,F.jsxs)(`div`,{className:`ov-hint`,style:{marginTop:6},children:[`白名单只放行名单内来源;最大 IP 数挡密钥转播后的`,(0,F.jsx)(`b`,{children:`新增`}),`来源;配额按累计 tokens 软校验(最后一笔可超出)。`]})]}):null]})}var K=[{value:`all`,label:`全部模型`},{value:`family`,label:`仅某系`},{value:`tag`,label:`仅某标签`},{value:`models`,label:`指定模型`}],q=[`kimi`,`deepseek`,`glm`,`minimax`,`hy4`,`hy3`,`space`,`step`],J=[`限时免费`,`限时折扣`,`夜间折扣`,`订阅优先`];function Y({onClose:e,onCreated:t}){let[n,i]=(0,P.useState)(``),[a,o]=(0,P.useState)(`all`),[s,c]=(0,P.useState)(q[0]),[l,u]=(0,P.useState)(J[0]),[d,f]=(0,P.useState)([]),p=y(()=>_.catalog(),[],{cacheKey:`catalog`}),m=y(()=>_.providers(),[],{cacheKey:`providers`}),[g,v]=(0,P.useState)([]),[b,S]=(0,P.useState)(R),[C,w]=(0,P.useState)(!1),[T,E]=(0,P.useState)(null),[D,O]=(0,P.useState)(null),A=(p.data?.models??[]).filter(e=>e.enabled),N=e=>f(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]),I=a===`all`?{type:`all`}:a===`family`?{type:`family`,value:s}:a===`tag`?{type:`tag`,value:l}:{type:`models`,value:d},L=a===`models`&&d.length===0,z=(m.data?.items??[]).map(e=>({value:e.id,label:e.active?`${e.name}(已启用)`:e.name})).sort((e,t)=>e.label.localeCompare(t.label,`zh-Hans-CN`)),B=e=>v(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]),V=(e,t)=>v(n=>{let r=[...n],i=e+t;return i<0||i>=r.length?n:([r[e],r[i]]=[r[i],r[e]],r)}),H=e=>z.find(t=>t.value===e)?.label??e,W=!!n.trim()&&!C&&!L;return D?(0,F.jsxs)(M,{title:`密钥已创建`,onClose:e,footer:(0,F.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:e,children:`我已保存,关闭`}),children:[(0,F.jsxs)(`div`,{className:`ov-lose`,children:[(0,F.jsx)(`b`,{children:`会失去什么:`}),D.warning]}),(0,F.jsx)(X,{value:D.key}),(0,F.jsxs)(`div`,{className:`ov-hint`,children:[`服务端只保存哈希。这串明文不会再出现,丢失只能删除重建。 下游可用 `,(0,F.jsx)(`span`,{className:`code-chip`,children:`GET /v1/usage`}),`(带这把密钥)自查配额余量与有效期 —— 纯只读,不产生调用。`]})]}):(0,F.jsxs)(M,{title:`新建密钥`,onClose:e,footer:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:e,children:`取消`}),(0,F.jsxs)(`button`,{type:`submit`,form:`create-key-form`,className:`btn btn-primary btn-sm`,disabled:!W,children:[C?(0,F.jsx)(r,{size:14}):null,C?`创建中…`:`创建密钥`]})]}),children:[(0,F.jsx)(`p`,{className:`ov-sub`,children:`给一个提供商或用途起名。绑定提供商名后,识别规则第 2 条「密钥归属」就能直接命中。`}),(0,F.jsxs)(`form`,{id:`create-key-form`,onSubmit:async e=>{if(e.preventDefault(),!W)return;let r=U(b);if(`error`in r){E(new h(r.error,400,0));return}w(!0),E(null);try{let e=await _.createKey({name:n.trim(),scope:I,clients:g,client:g[0]??null,ipWhitelist:r.ipWhitelist,maxIps:r.maxIps,tokenQuota:r.tokenQuota});O(e),t()}catch(e){E(e instanceof h?e:new h(String(e),0,0))}finally{w(!1)}},style:{display:`flex`,flexDirection:`column`,gap:16},children:[(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{htmlFor:`ck-name`,children:`密钥名称`}),(0,F.jsx)(`input`,{id:`ck-name`,className:`field-input text`,style:{width:`100%`},value:n,autoFocus:!0,placeholder:`例如:Cursor · 笔记本`,onChange:e=>i(e.target.value)})]}),(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{children:`权限范围`}),(0,F.jsx)(j,{value:a,options:K,onChange:o,width:180}),a===`family`?(0,F.jsx)(j,{value:s,options:q.map(e=>({value:e,label:`${e} 系`})),onChange:c,width:180}):null,a===`tag`?(0,F.jsx)(j,{value:l,options:J.map(e=>({value:e,label:e})),onChange:u,width:180}):null,a===`models`?(0,F.jsx)(`div`,{className:`scope-models`,children:p.loading?(0,F.jsx)(`div`,{className:`ov-hint`,children:`正在读取模型清单…`}):A.length===0?(0,F.jsx)(`div`,{className:`ov-hint`,children:`模型清单为空。先去「提供商」面板执行一次「获取模型」。`}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsxs)(`div`,{className:`ov-hint`,children:[`已选 `,d.length,` 个`,L?` —— 至少选一个才能创建`:``]}),(0,F.jsx)(`div`,{className:`scope-models-list`,children:A.map(e=>(0,F.jsxs)(`label`,{className:`scope-model`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:d.includes(e.id),onChange:()=>N(e.id)}),(0,F.jsx)(`span`,{className:`code-chip`,children:e.id}),(0,F.jsx)(`i`,{children:e.displayName})]},e.id))})]})}):null]}),(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{children:`绑定提供商(可多选,可排序)`}),m.loading?(0,F.jsx)(`div`,{className:`ov-hint`,children:`正在读取提供商…`}):z.length===0?(0,F.jsx)(`div`,{className:`ov-hint`,children:`还没有登记任何提供商 —— 先去「提供商管理」页登记。`}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`div`,{className:`pick-list`,children:z.map(e=>{let t=g.includes(e.value);return(0,F.jsxs)(`label`,{className:`pick-row${t?` on`:``}`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:t,onChange:()=>B(e.value)}),(0,F.jsx)(`span`,{className:`pick-name`,children:e.label})]},e.value)})}),(0,F.jsxs)(`div`,{className:`picked-head`,children:[(0,F.jsxs)(`span`,{children:[`已选 `,g.length,` 个`]}),g.length>1?(0,F.jsx)(`span`,{className:`picked-tip`,children:`用 ↑↓ 调整优先级`}):null]}),g.length>0?(0,F.jsx)(`div`,{className:`picked-list`,children:g.map((e,t)=>(0,F.jsxs)(`div`,{className:`picked-item`,children:[(0,F.jsx)(`span`,{className:`picked-rank`,children:t+1}),(0,F.jsx)(`span`,{className:`picked-name`,children:H(e)}),(0,F.jsxs)(`span`,{className:`picked-actions`,children:[(0,F.jsx)(`button`,{type:`button`,className:`pick-mv`,disabled:t===0,onClick:()=>V(t,-1),"aria-label":`上移`,children:`↑`}),(0,F.jsx)(`button`,{type:`button`,className:`pick-mv`,disabled:t===g.length-1,onClick:()=>V(t,1),"aria-label":`下移`,children:`↓`}),(0,F.jsx)(`button`,{type:`button`,className:`pick-rm`,onClick:()=>B(e),"aria-label":`移除`,children:`×`})]})]},e))}):null,(0,F.jsx)(`div`,{className:`ov-hint`,children:`勾选后这个 Key 的请求会归到对应提供商名下;第 1 位是首选(用量归因与后续路由都优先用它)。`})]})]}),(0,F.jsx)(G,{draft:b,onChange:S}),T?(0,F.jsx)(x,{tone:`error`,children:k(T)}):null]})]})}function X({value:e}){let[n,r]=(0,P.useState)(!1),i=async()=>{try{await navigator.clipboard.writeText(e),r(!0),setTimeout(()=>r(!1),2e3)}catch{r(!1)}};return(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{children:`完整密钥(只此一次)`}),(0,F.jsxs)(`div`,{className:`key-plain`,children:[(0,F.jsx)(`span`,{style:{flex:1},children:e}),(0,F.jsxs)(`button`,{type:`button`,className:`copy`,"aria-label":n?`已复制完整密钥`:`复制完整密钥`,onClick:()=>void i(),children:[n?(0,F.jsx)(t,{size:13}):(0,F.jsx)(f,{size:13}),n?`已复制`:`复制`]})]})]})}function Z({row:e,onClose:t,onSaved:n}){let[i,a]=(0,P.useState)(e.name),[o,s]=(0,P.useState)(e.clients??(e.client?[e.client]:[])),[c,l]=(0,P.useState)(()=>z({ipWhitelist:e.ipWhitelist??[],maxIps:e.maxIps??null,tokenQuota:e.tokenQuota??null})),[u,d]=(0,P.useState)(!1),[f,p]=(0,P.useState)(null),m=y(()=>_.providers(),[],{cacheKey:`providers`}),g=(()=>{let e=(m.data?.items??[]).map(e=>({value:e.id,label:e.active?`${e.name}(已启用)`:e.name})).sort((e,t)=>e.label.localeCompare(t.label,`zh-Hans-CN`));for(let t of o)e.some(e=>e.value===t)||e.push({value:t,label:`${t}(已删除,当前不在列表)`});return e})(),v=e=>s(t=>t.includes(e)?t.filter(t=>t!==e):[...t,e]),b=(e,t)=>s(n=>{let r=[...n],i=e+t;return i<0||i>=r.length?n:([r[e],r[i]]=[r[i],r[e]],r)}),S=e=>g.find(t=>t.value===e)?.label??e,C=i.trim(),w=C!==e.name,T=e.clients??(e.client?[e.client]:[]),E=JSON.stringify(o)!==JSON.stringify(T),D=U(c),O=!(`error`in D)&&(JSON.stringify(D.ipWhitelist)!==JSON.stringify(e.ipWhitelist??[])||D.maxIps!==(e.maxIps??null)||D.tokenQuota!==(e.tokenQuota??null)),A=!!C&&(w||E||O)&&!u;return(0,F.jsxs)(M,{title:`修改密钥`,onClose:t,footer:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:t,children:`取消`}),(0,F.jsxs)(`button`,{type:`submit`,form:`edit-key-form`,className:`btn btn-primary btn-sm`,disabled:!A,children:[u?(0,F.jsx)(r,{size:14}):null,u?`保存中…`:`保存`]})]}),children:[(0,F.jsx)(`p`,{className:`ov-sub`,children:`改的是「叫什么」和「绑哪家」,不动作废 —— 这把密钥的明文和哈希都不受影响,已接入的客户端无需更换。`}),(0,F.jsxs)(`form`,{id:`edit-key-form`,onSubmit:async r=>{if(r.preventDefault(),A){if(`error`in D){p(new h(D.error,400,0));return}d(!0),p(null);try{let r={};w&&(r.name=C),E&&(r.clients=o),O&&(r.ipWhitelist=D.ipWhitelist,r.maxIps=D.maxIps,r.tokenQuota=D.tokenQuota),await _.patchKey(e.id,r),n(),t()}catch(e){p(e instanceof h?e:new h(String(e),0,0))}finally{d(!1)}}},style:{display:`flex`,flexDirection:`column`,gap:16},children:[(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{htmlFor:`ek-name`,children:`密钥名称`}),(0,F.jsx)(`input`,{id:`ek-name`,className:`field-input text`,style:{width:`100%`},value:i,autoFocus:!0,onChange:e=>a(e.target.value)}),(0,F.jsxs)(`div`,{className:`ov-hint`,children:[`当前:`,e.name,` · `,e.hint]})]}),(0,F.jsxs)(`div`,{className:`ov-field`,children:[(0,F.jsx)(`label`,{children:`绑定提供商(可多选,可排序)`}),m.loading?(0,F.jsx)(`div`,{className:`ov-hint`,children:`正在读取提供商…`}):g.length===0?(0,F.jsx)(`div`,{className:`ov-hint`,children:`还没有登记任何提供商 —— 先去「提供商管理」页登记。`}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`div`,{className:`pick-list`,children:g.map(e=>{let t=o.includes(e.value);return(0,F.jsxs)(`label`,{className:`pick-row${t?` on`:``}`,children:[(0,F.jsx)(`input`,{type:`checkbox`,checked:t,onChange:()=>v(e.value)}),(0,F.jsx)(`span`,{className:`pick-name`,children:e.label})]},e.value)})}),(0,F.jsxs)(`div`,{className:`picked-head`,children:[(0,F.jsxs)(`span`,{children:[`已选 `,o.length,` 个`]}),o.length>1?(0,F.jsx)(`span`,{className:`picked-tip`,children:`用 ↑↓ 调整优先级`}):null]}),o.length>0?(0,F.jsx)(`div`,{className:`picked-list`,children:o.map((e,t)=>(0,F.jsxs)(`div`,{className:`picked-item`,children:[(0,F.jsx)(`span`,{className:`picked-rank`,children:t+1}),(0,F.jsx)(`span`,{className:`picked-name`,children:S(e)}),(0,F.jsxs)(`span`,{className:`picked-actions`,children:[(0,F.jsx)(`button`,{type:`button`,className:`pick-mv`,disabled:t===0,onClick:()=>b(t,-1),"aria-label":`上移`,children:`↑`}),(0,F.jsx)(`button`,{type:`button`,className:`pick-mv`,disabled:t===o.length-1,onClick:()=>b(t,1),"aria-label":`下移`,children:`↓`}),(0,F.jsx)(`button`,{type:`button`,className:`pick-rm`,onClick:()=>v(e),"aria-label":`移除`,children:`×`})]})]},e))}):null,(0,F.jsx)(`div`,{className:`ov-hint`,children:`全部取消即解绑。绑定只影响用量归因(第 1 位是首选),不改变权限范围。`})]})]}),(0,F.jsx)(G,{draft:c,onChange:l}),(e.maxIps!==null||e.tokenQuota!==null)&&(0,F.jsxs)(`div`,{className:`ov-hint`,children:[`当前累计:`,e.maxIps===null?``:`${e.usedIps??0} / ${e.maxIps} 个来源 IP`,e.maxIps!==null&&e.tokenQuota!==null?` · `:``,e.tokenQuota===null?``:`${(e.usedTokens??0).toLocaleString()} / ${e.tokenQuota.toLocaleString()} tokens`]}),(0,F.jsxs)(`div`,{className:`ov-hint`,children:[`权限范围(`,(0,F.jsx)(`b`,{children:`不可改`}),`):它是授权面,要换范围删除后重建 —— 明文只显示一次,本就该重新分发。`]}),f?(0,F.jsx)(x,{tone:`error`,children:k(f)}):null]})]})}function Q(){return new URLSearchParams(window.location.hash.split(`?`)[1]??``).get(`clientId`)||null}function $(){let t=y(()=>_.keys(),[],{cacheKey:`keys`}),n=y(()=>_.clients(`30d`),[],{cacheKey:`clients:30d`}),a=y(()=>_.providers(),[],{cacheKey:`providers`}),o=new Map((a.data?.items??[]).map(e=>[e.id,e.name])),[s,c]=(0,P.useState)(Q),[l,d]=(0,P.useState)(!1),[f,m]=(0,P.useState)(null),[g,b]=(0,P.useState)(null),[S,C]=(0,P.useState)(null),[E,j]=(0,P.useState)(null);(0,P.useEffect)(()=>{let e=()=>c(Q());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]);let I=(0,P.useMemo)(()=>{let e=new Map;for(let t of n.data?.items??[]){let n={clientId:t.clientId,name:t.client};e.set(t.clientId,n);for(let r of t.boundKeyIds)e.set(r,n)}return e},[n.data]),R=n.data?.items.find(e=>e.clientId===s),z=t.data??[],B=s?z.filter(e=>e.id===s||R?.boundKeyIds.includes(e.id)):z,V=!!(s&&n.initial&&n.loading),H={total:B.length,live:0,off:0,exp:0};for(let e of B)H[p(e.enabled,e.expiresAt)]+=1;let U=e=>t.data&&!V?v(e):`--`,W=()=>{t.reload(),n.reload()},G=async e=>{if(!f){m(e.id),j(null);try{await _.patchKey(e.id,{enabled:!e.enabled}),t.reload()}catch(e){j(e instanceof h?e:new h(String(e),0,0))}finally{m(null)}}},K=async()=>{if(!S||f)return;let e=S;m(e.id),j(null);try{await _.deleteKey(e.id),C(null),W()}catch(e){j(e instanceof h?e:new h(String(e),0,0)),C(null)}finally{m(null)}},q=()=>{window.location.hash=`#/keys`,c(null)};return(0,F.jsxs)(`div`,{className:`view on`,children:[(0,F.jsx)(A,{title:`API Keys`,actions:(0,F.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>d(!0),children:[(0,F.jsx)(i,{size:14}),`新建API Key`]})}),(0,F.jsx)(`div`,{className:`view-body stack`,children:t.initial&&t.loading?(0,F.jsx)(N,{}):(0,F.jsxs)(F.Fragment,{children:[E?(0,F.jsx)(x,{tone:`error`,children:k(E)}):null,(0,F.jsx)(O,{items:[{label:`密钥总数`,value:U(H.total),detail:s?`客户端筛选下 ${U(H.total)} 把`:`全部密钥`},{label:`启用`,value:U(H.live),tone:`live`,detail:`未过期且启用`},{label:`停用`,value:U(H.off),detail:`已停用且未过期`},{label:`过期`,value:U(H.exp),tone:`amber`,detail:`expiresAt 已过`}]}),s?(0,F.jsxs)(`div`,{className:`note`,children:[(0,F.jsxs)(`span`,{children:[`关联客户端:`,R?(0,F.jsx)(`a`,{href:`#/clients?clientId=${encodeURIComponent(R.clientId)}`,children:R.client}):`未绑定`]}),(0,F.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:q,children:[(0,F.jsx)(u,{size:14}),`清除筛选`]})]}):null,n.error?(0,F.jsx)(D,{kind:`error`,title:`客户端关联数据没取到`,detail:k(n.error),onRetry:n.reload}):null,(0,F.jsxs)(w,{title:`密钥列表`,aside:(0,F.jsxs)(T,{children:[U(B.length),` 个 · 明文仅在创建时显示一次`]}),children:[t.error?(0,F.jsx)(D,{kind:`error`,title:`密钥数据没取到`,detail:k(t.error),onRetry:t.reload}):V?(0,F.jsx)(D,{kind:`loading`,title:`正在读取客户端关联…`}):B.length===0?s?(0,F.jsx)(D,{kind:`empty`,title:`没有关联的密钥`,children:(0,F.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:q,children:[(0,F.jsx)(u,{size:14}),`清除筛选`]})}):(0,F.jsx)(D,{kind:`empty`,title:`还没有任何密钥`,detail:`没有密钥时 /v1/* 会拒绝所有请求。`,children:(0,F.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>d(!0),children:[(0,F.jsx)(e,{size:14}),`创建第一把密钥`]})}):(0,F.jsx)(L,{rows:B,clientByKey:I,providerNameById:o,pendingId:f,onToggle:e=>void G(e),onEdit:b,onDelete:C}),(0,F.jsxs)(`div`,{className:`footer-note`,children:[`完整密钥只在创建时显示一次,服务端只保存哈希;停用后立即失效,可随时重新启用。改名与改绑定不会动作废 —— 已接入的客户端不受影响。`,(0,F.jsx)(`br`,{}),`上面三格互斥、可相加等于总数:`,(0,F.jsx)(`b`,{children:`过期优先于启停判断`}),`(expiresAt 已过即归过期,不再看启用状态),所以一把同时「启用」又「过期」的密钥只会计进「过期」。`]})]})]})}),l?(0,F.jsx)(Y,{onClose:()=>d(!1),onCreated:W}):null,g?(0,F.jsx)(Z,{row:g,onClose:()=>b(null),onSaved:W}):null,S?(0,F.jsx)(M,{title:`删除这把密钥?`,onClose:()=>{f||C(null)},footer:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:!!f,onClick:()=>C(null),children:`取消(保留)`}),(0,F.jsxs)(`button`,{type:`button`,className:`btn btn-danger btn-sm`,disabled:!!f,onClick:()=>void K(),children:[f?(0,F.jsx)(r,{size:14,className:`spin`}):null,f?`删除中…`:`确认删除`]})]}),children:(0,F.jsxs)(`div`,{className:`ov-lose`,children:[(0,F.jsx)(`b`,{children:`会失去什么:`}),`密钥「`,S.name,`」(`,S.hint,`)立即失效,任何还在用它的客户端会立刻收到 401, 需要重新建密钥并逐个替换。此操作不可撤销。`]})}):null]})}export{$ as ApiKeysPage};
@@ -1 +1 @@
1
- import{tt as e}from"./vendor-befEYGHM.js";import{_ as t,d as n,g as r,p as i,x as a}from"./index-C-pZwUmz.js";var o=e();function s({metrics:e}){return(0,o.jsxs)(`div`,{className:`credit-banner`,children:[(0,o.jsxs)(`div`,{className:`cb-left`,children:[(0,o.jsx)(`div`,{className:`cb-label`,children:`本窗口总 tokens`}),(0,o.jsx)(`div`,{children:(0,o.jsx)(`span`,{className:`cb-num`,children:a(e.totalTokens)})}),(0,o.jsx)(`div`,{className:`cb-why`,children:(0,o.jsxs)(`span`,{children:[`输入 `,a(e.promptTokens),` · 输出 `,a(e.completionTokens)]})})]}),(0,o.jsxs)(`div`,{className:`cb-mid`,children:[(0,o.jsx)(`div`,{className:`row`,children:(0,o.jsxs)(`span`,{className:`t`,children:[`经本服务转发已用 `,i(e.creditsUsed),` 积分`]})}),(0,o.jsxs)(`div`,{className:`cb-note`,children:[`仅统计经本服务转发的请求(取上游实扣 usage.credit;上游未回报的行积分未知,不计入合计`,e.creditsUnknownCount>0?`,本窗口 ${r(e.creditsUnknownCount)} 条`:``,`)。 官方客户端直连的消耗不计入 —— 账户总消耗与余额以官方客户端为准。`]})]}),(0,o.jsxs)(`div`,{className:`cb-right`,children:[(0,o.jsx)(`div`,{className:`cb-label`,children:`缓存命中率`}),(0,o.jsx)(`div`,{className:`cb-num`,style:{fontSize:22},children:n(e.cacheHitRate)}),(0,o.jsxs)(`div`,{className:`cb-note`,children:[a(e.cachedTokens),` / `,a(e.promptTokens),` 输入 tokens`]})]})]})}var c={credits:`消耗积分`,tokens:`Token 消耗`,requests:`请求次数`};function l(e,t){return e[t]??0}function u(e,t){return t===`credits`?i(e):t===`tokens`?a(e):r(e)}function d(e){return e===`credits`?`积分`:e===`tokens`?`tokens`:`次`}function f({rows:e,metric:n,windowLabel:r,total:i,totalCount:a}){let s=e.slice(0,6),f=Math.max(0,a-s.length),p=Math.max(0,i);return(0,o.jsxs)(`div`,{className:`client-metric-tip`,children:[(0,o.jsxs)(`div`,{className:`client-metric-tip-head`,children:[(0,o.jsxs)(`div`,{children:[(0,o.jsx)(`strong`,{children:`客户端拆分`}),(0,o.jsxs)(`span`,{children:[r,` · `,c[n]]})]}),(0,o.jsxs)(`span`,{className:`client-metric-tip-tag`,children:[`TOP `,s.length]})]}),(0,o.jsxs)(`div`,{className:`client-metric-tip-total`,children:[(0,o.jsx)(`span`,{children:`窗口合计`}),(0,o.jsxs)(`b`,{children:[u(p,n),` `,(0,o.jsx)(`em`,{children:d(n)})]})]}),(0,o.jsx)(`div`,{className:`client-metric-tip-list`,children:s.map((e,r)=>{let i=l(e,n),a=p>0?i/p:0;return(0,o.jsxs)(`div`,{className:`client-metric-tip-row`,children:[(0,o.jsxs)(`div`,{className:`client-metric-tip-row-top`,children:[(0,o.jsx)(`span`,{className:`client-metric-tip-rank`,children:String(r+1).padStart(2,`0`)}),(0,o.jsx)(`span`,{className:`client-metric-tip-name`,children:e.client??`未归属`}),(0,o.jsxs)(`b`,{children:[u(i,n),` `,(0,o.jsx)(`em`,{children:d(n)})]})]}),(0,o.jsxs)(`div`,{className:`client-metric-tip-row-bottom`,children:[(0,o.jsx)(`span`,{className:`client-metric-tip-track`,children:(0,o.jsx)(`i`,{style:{width:`${Math.max(0,Math.min(100,a*100))}%`}})}),(0,o.jsx)(`span`,{children:t(a)})]})]},e.clientId??e.client??r)})}),f>0?(0,o.jsxs)(`div`,{className:`client-metric-tip-note`,children:[`另有 `,f,` 个客户端未展开,完整明细见按客户端列表。`]}):null]})}export{s as n,f as t};
1
+ import{tt as e}from"./vendor-befEYGHM.js";import{_ as t,d as n,g as r,p as i,x as a}from"./index-BMxTTq4E.js";var o=e();function s({metrics:e}){return(0,o.jsxs)(`div`,{className:`credit-banner`,children:[(0,o.jsxs)(`div`,{className:`cb-left`,children:[(0,o.jsx)(`div`,{className:`cb-label`,children:`本窗口总 tokens`}),(0,o.jsx)(`div`,{children:(0,o.jsx)(`span`,{className:`cb-num`,children:a(e.totalTokens)})}),(0,o.jsx)(`div`,{className:`cb-why`,children:(0,o.jsxs)(`span`,{children:[`输入 `,a(e.promptTokens),` · 输出 `,a(e.completionTokens)]})})]}),(0,o.jsxs)(`div`,{className:`cb-mid`,children:[(0,o.jsx)(`div`,{className:`row`,children:(0,o.jsxs)(`span`,{className:`t`,children:[`经本服务转发已用 `,i(e.creditsUsed),` 积分`]})}),(0,o.jsxs)(`div`,{className:`cb-note`,children:[`仅统计经本服务转发的请求(取上游实扣 usage.credit;上游未回报的行积分未知,不计入合计`,e.creditsUnknownCount>0?`,本窗口 ${r(e.creditsUnknownCount)} 条`:``,`)。 官方客户端直连的消耗不计入 —— 账户总消耗与余额以官方客户端为准。`]})]}),(0,o.jsxs)(`div`,{className:`cb-right`,children:[(0,o.jsx)(`div`,{className:`cb-label`,children:`缓存命中率`}),(0,o.jsx)(`div`,{className:`cb-num`,style:{fontSize:22},children:n(e.cacheHitRate)}),(0,o.jsxs)(`div`,{className:`cb-note`,children:[a(e.cachedTokens),` / `,a(e.promptTokens),` 输入 tokens`]})]})]})}var c={credits:`消耗积分`,tokens:`Token 消耗`,requests:`请求次数`};function l(e,t){return e[t]??0}function u(e,t){return t===`credits`?i(e):t===`tokens`?a(e):r(e)}function d(e){return e===`credits`?`积分`:e===`tokens`?`tokens`:`次`}function f({rows:e,metric:n,windowLabel:r,total:i,totalCount:a}){let s=e.slice(0,6),f=Math.max(0,a-s.length),p=Math.max(0,i);return(0,o.jsxs)(`div`,{className:`client-metric-tip`,children:[(0,o.jsxs)(`div`,{className:`client-metric-tip-head`,children:[(0,o.jsxs)(`div`,{children:[(0,o.jsx)(`strong`,{children:`客户端拆分`}),(0,o.jsxs)(`span`,{children:[r,` · `,c[n]]})]}),(0,o.jsxs)(`span`,{className:`client-metric-tip-tag`,children:[`TOP `,s.length]})]}),(0,o.jsxs)(`div`,{className:`client-metric-tip-total`,children:[(0,o.jsx)(`span`,{children:`窗口合计`}),(0,o.jsxs)(`b`,{children:[u(p,n),` `,(0,o.jsx)(`em`,{children:d(n)})]})]}),(0,o.jsx)(`div`,{className:`client-metric-tip-list`,children:s.map((e,r)=>{let i=l(e,n),a=p>0?i/p:0;return(0,o.jsxs)(`div`,{className:`client-metric-tip-row`,children:[(0,o.jsxs)(`div`,{className:`client-metric-tip-row-top`,children:[(0,o.jsx)(`span`,{className:`client-metric-tip-rank`,children:String(r+1).padStart(2,`0`)}),(0,o.jsx)(`span`,{className:`client-metric-tip-name`,children:e.client??`未归属`}),(0,o.jsxs)(`b`,{children:[u(i,n),` `,(0,o.jsx)(`em`,{children:d(n)})]})]}),(0,o.jsxs)(`div`,{className:`client-metric-tip-row-bottom`,children:[(0,o.jsx)(`span`,{className:`client-metric-tip-track`,children:(0,o.jsx)(`i`,{style:{width:`${Math.max(0,Math.min(100,a*100))}%`}})}),(0,o.jsx)(`span`,{children:t(a)})]})]},e.clientId??e.client??r)})}),f>0?(0,o.jsxs)(`div`,{className:`client-metric-tip-note`,children:[`另有 `,f,` 个客户端未展开,完整明细见按客户端列表。`]}):null]})}export{s as n,f as t};
@@ -1 +1 @@
1
- import{H as e,I as t,J as n,L as r,Q as i,S as a,U as o,a as s,it as c,m as l,n as u,tt as d,z as f}from"./vendor-befEYGHM.js";import{M as p,a as m,c as h,g,j as _,k as v,p as y,r as b,t as x,v as S}from"./index-C-pZwUmz.js";import{a as C,c as w,n as T,r as E,s as D,t as O}from"./Select-u39f55hZ.js";import{t as k}from"./Modal-B-rIrvXS.js";import{t as A}from"./PageSkeleton-DG-Dj4_9.js";import{n as j,t as M}from"./sharedWindow-FDfq75PI.js";import{t as N}from"./Drawer-CleQGhRU.js";import{t as P}from"./Switch-0KA1AbiV.js";var F=c(),I=d();function L({rows:e,byProvider:t,busyId:n,onEdit:r,onModels:a,onSignin:o,onGrowthRun:c,onOauthImport:d,onActivate:f,onDeactivate:p,onDelete:h}){let _=()=>new Intl.DateTimeFormat(`en-CA`,{timeZone:`Asia/Shanghai`,year:`numeric`,month:`2-digit`,day:`2-digit`}).format(new Date),v=new Map((t??[]).map(e=>[e.providerId??``,e]));return(0,I.jsx)(`div`,{className:`tbl-scroll`,children:(0,I.jsxs)(`table`,{className:`tbl`,children:[(0,I.jsxs)(`caption`,{className:`sr-only`,children:[`提供商列表,共 `,e.length,` 家提供商`]}),(0,I.jsx)(`thead`,{children:(0,I.jsxs)(`tr`,{children:[(0,I.jsx)(`th`,{scope:`col`,children:`提供商`}),(0,I.jsx)(`th`,{scope:`col`,children:`密钥`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`最后活跃`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`操作`})]})}),(0,I.jsx)(`tbody`,{children:e.map(t=>{let b=n===t.id;return(0,I.jsxs)(`tr`,{className:`prov-row`,children:[(0,I.jsxs)(`td`,{className:`td-strong`,children:[t.name,t.active?(0,I.jsx)(w,{tone:`live`,children:`已启用`}):(0,I.jsx)(w,{tone:`ghost`,children:`未启用`}),t.protocol===`anthropic`?(0,I.jsx)(m,{content:`Anthropic Messages 方言(mavis 网关):模型目录用内置清单,凭证为 refresh token 自动换新`,children:(0,I.jsx)(`span`,{className:`pill pill-ghost`,children:`Anthropic`})}):t.protocol===`qoder`?(0,I.jsx)(m,{content:`Qoder CN 网关(COSY 协议):免费档 billable:false 实扣 0;目录从网关 /model/list 拉取;凭证为客户端身份 JSON(不自动刷新)`,children:(0,I.jsx)(`span`,{className:`pill pill-ghost`,children:`Qoder`})}):null,(0,I.jsx)(`div`,{className:`td-sub td-mono`,children:(0,I.jsx)(m,{content:(0,I.jsx)(I.Fragment,{children:(0,I.jsxs)(`div`,{className:`client-model-tip`,children:[(0,I.jsxs)(`div`,{className:`client-model-tip-head`,children:[(0,I.jsxs)(`div`,{className:`client-model-tip-title`,children:[(0,I.jsx)(`strong`,{children:t.name}),(0,I.jsx)(`span`,{children:t.baseUrl})]}),(0,I.jsx)(`span`,{className:`client-model-tip-tag`,children:t.active?`已启用`:`未启用`})]}),(0,I.jsxs)(`div`,{className:`client-model-tip-summary`,children:[(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`span`,{children:`模型总数`}),(0,I.jsx)(`b`,{children:g(t.modelCount??0)})]}),(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`span`,{children:`已启用`}),(0,I.jsx)(`b`,{children:g(t.modelEnabled??0)})]})]}),(()=>{let e=t.credits?.available?{remaining:t.credits.remaining,consumed:t.credits.consumed}:t.wbCredits?{remaining:t.wbCredits.remaining,consumed:t.wbCredits.consumed}:t.qoderCredits?{remaining:t.qoderCredits.remaining,consumed:t.qoderCredits.consumed}:null;return e&&(e.remaining!==null||e.consumed!==null)?(0,I.jsxs)(`div`,{className:`client-metric-tip-total`,children:[(0,I.jsx)(`span`,{children:`剩余积分`}),(0,I.jsxs)(`b`,{children:[y(e.remaining??0),` `,(0,I.jsxs)(`em`,{children:[`已用 `,y(e.consumed??0)]})]})]}):null})(),t.credits?.stale||t.qoderCredits?.stale?(0,I.jsx)(`div`,{className:`client-model-tip-note`,children:`⚠ 积分数据偏旧(账本快照超时)`}):null]})}),children:(0,I.jsxs)(`span`,{className:`prov-model-count`,children:[g(t.modelCount??0),` 个模型`]})})})]}),(0,I.jsxs)(`td`,{className:`td-cred`,children:[(0,I.jsx)(m,{content:`供应商访问密钥(掩码显示;全量只存在服务器上)`,children:(0,I.jsx)(`span`,{className:`cred-mask`,children:t.credentialMask})}),(0,I.jsx)(`span`,{className:`up-badge${t.configured?``:` warn`}`,children:t.configured?`有效`:`未配置`})]}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?g(v.get(t.id)?.requests??0):`—`}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?y(v.get(t.id)?.credits??0):`—`}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?S(v.get(t.id)?.lastSeenAt??null):`—`}),(0,I.jsx)(`td`,{className:`r`,children:(0,I.jsxs)(`span`,{className:`prov-row-actions`,children:[(0,I.jsx)(m,{content:`改名称与上游地址;凭证可在抽屉内「重新导入授权」`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:b,onClick:()=>r(t),children:[(0,I.jsx)(u,{size:12}),`配置`]})}),(0,I.jsx)(m,{content:`查看这家自己的模型清单(启停只作用于该提供商分区)`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||!t.active,onClick:()=>{!b&&t.active&&a(t)},children:[(0,I.jsx)(l,{size:12}),`模型列表`]})}),t.credKind===`oauth_minimax`||t.protocol===`qoder`?(0,I.jsx)(m,{content:(()=>{let e=t.protocol===`qoder`?`10:05`:`08:05`,n=t.signin?.checkedOn?`最近签到 ${t.signin.checkedOn}${t.signin.lastPoints?` · +${t.signin.lastPoints} 积分`:``}(relay 每天 ${e} 自动签)`:`今天还没签到 —— 点一下立即签(relay 每天 ${e} 也会自动签)`;return t.protocol===`qoder`?(0,I.jsxs)(I.Fragment,{children:[n,(0,I.jsx)(`br`,{}),`Qoder 官方标注:每日 10:00(UTC+8)刷新,领取后 30 天有效。`]}):n})(),children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||t.signin?.checkedOn===_(),onClick:()=>{b||t.signin?.checkedOn===_()||o(t)},children:[(0,I.jsx)(i,{size:12}),t.signin?.checkedOn===_()?`已签到`:`签到`]})}):null,t.protocol===`openai`?t.wbOAuth?.configured?(0,I.jsx)(m,{content:t.wbSignin?.checkedOn===_()?`今天已签到过(明天 08:05 自动跑;任务面板在设置 → 提供商健康)`:`立即签到 + 顺带跑一轮成长任务与连登兑换(每天 08:05 也会自动跑)。转发不受影响。`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||t.wbSignin?.checkedOn===_(),onClick:()=>{!b&&t.wbSignin?.checkedOn!==_()&&c(t)},children:[(0,I.jsx)(i,{size:12}),t.wbSignin?.checkedOn===_()?`已签到`:`签到`]})}):(0,I.jsx)(m,{content:`导入桌面端 OAuth 凭证后可代领签到/任务(上游不允许 API 密钥代领)。粘贴 tools/wb-oauth-export.mjs 的输出;转发仍走 API 密钥。`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b,onClick:()=>{b||d(t)},children:[(0,I.jsx)(i,{size:12}),`导入OAuth`]})}):null,t.active?(0,I.jsx)(m,{content:`退出出站路由(目录与凭证保留,可再启用)`,children:(0,I.jsx)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b,onClick:()=>{b||p(t)},children:`停用`})}):(0,I.jsx)(`button`,{type:`button`,className:`mini-pill`,disabled:b,onClick:()=>f(t),children:`启用`}),(0,I.jsx)(m,{content:t.active?`不能删除已启用的提供商 —— 先停用它`:e.length<=1?`至少保留一个提供商`:`删除这个提供商(凭证文件一并删除)`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill danger`,"aria-disabled":b||t.active||e.length<=1,onClick:()=>{!b&&!t.active&&e.length>1&&h(t)},children:[(0,I.jsx)(s,{size:12}),`删除`]})})]})})]},t.id)})})]})})}function R({command:e}){let[t,r]=(0,F.useState)(!1);if(!e)return null;let i=async()=>{try{await navigator.clipboard.writeText(e),r(!0),setTimeout(()=>r(!1),2e3)}catch{r(!1)}};return(0,I.jsxs)(`div`,{className:`cred-guide-cmd`,children:[(0,I.jsx)(`code`,{children:e}),(0,I.jsxs)(`button`,{type:`button`,className:`copy`,"aria-label":t?`已复制命令`:`复制命令`,onClick:()=>void i(),children:[t?(0,I.jsx)(n,{size:13}):(0,I.jsx)(f,{size:13}),t?`已复制`:`复制`]})]})}var z=[{protocol:`openai`,label:`WorkBuddy`,desc:`OpenAI 方言`,needsAuth:!0},{protocol:`anthropic`,label:`MiniMax Code`,desc:`Anthropic 方言`,needsAuth:!0},{protocol:`qoder`,label:`Qoder CN`,desc:`COSY 协议`,needsAuth:!0}],B={openai:`https://copilot.tencent.com`,anthropic:`https://agent.minimax.cn`,qoder:`https://gateway.qoder.com.cn`},V=2500,H=3e5;function U({provider:e,onClose:n,onImported:r}){let i=x(),[o,s]=(0,F.useState)(e?`confirm`:`pick`),[c,l]=(0,F.useState)(e?.protocol??`anthropic`),[u,d]=(0,F.useState)(e?.name??``),[f,m]=(0,F.useState)(``),[g,_]=(0,F.useState)(``),[v,y]=(0,F.useState)(!1),[S,C]=(0,F.useState)(null),w=(0,F.useRef)(e?.id??null),T=(0,F.useRef)(!1),D=(0,F.useRef)(!1),O=z.find(e=>e.protocol===c);(0,F.useEffect)(()=>()=>{D.current=!0;let e=w.current;e&&h.providerImportDeviceCancel(e).catch(()=>void 0)},[]);let A=(0,F.useCallback)(async()=>{y(!0),C(null);try{let e=w.current;e||(e=(await h.createProvider({name:u.trim()||O.label,baseUrl:B[c]??B.openai,credential:``,protocol:c})).item.id,w.current=e,T.current=!0);let t=await h.providerImportDeviceStart(e);if(!t.ok||!t.authUrl){C(new p(t.message??`申请授权失败`,0,0)),s(`confirm`),y(!1);return}m(t.authUrl),_(t.userCode??``),window.open(t.authUrl,`_blank`,`noopener`),s(`waiting`),y(!1);let n=Date.now()+H;for(;!D.current&&Date.now()<n;){if(await new Promise(e=>setTimeout(e,V)),D.current)return;let t=await h.providerImportDevicePoll(e);if(D.current)return;if(t.status===`done`){s(`done`),i.push({tone:`success`,message:`${u.trim()||O.label} 授权已导入。`}),r();return}if(t.status===`error`){C(new p(t.message??`授权失败`,0,0)),s(`confirm`);return}}D.current||(C(new p(`授权超时(5 分钟)—— 请重新发起导入。`,0,0)),s(`confirm`))}catch(e){C(e instanceof p?e:new p(String(e),0,0)),s(`confirm`),y(!1)}},[O.label,u,r,c,i]);if(o===`done`)return(0,I.jsxs)(k,{title:`授权已导入`,onClose:n,footer:(0,I.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:n,children:`完成`}),children:[(0,I.jsx)(b,{tone:`success`,children:(0,I.jsxs)(`span`,{children:[u.trim()||O.label,` 的授权已通过官方通道导入。`]})}),e?null:(0,I.jsxs)(`p`,{className:`ov-sub`,style:{marginTop:12},children:[`回到列表点`,(0,I.jsx)(`b`,{children:`试通`}),`确认可用,再按需启用(启用后它才参与出站路由)。`]})]});if(o===`waiting`)return(0,I.jsx)(k,{title:`等待你完成授权`,onClose:n,children:(0,I.jsxs)(`div`,{className:`cred-guide`,children:[(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`已为你打开 `,(0,I.jsx)(`b`,{children:O.label}),` 的授权页 —— `,(0,I.jsx)(`b`,{children:`请在浏览器里完成授权`}),`, 本窗口会自动检测结果,无需回到终端。`]}),g?(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`若页面要求输入短码:`,(0,I.jsx)(`span`,{className:`code-chip`,children:g})]}):null,(0,I.jsx)(R,{command:f}),(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,I.jsx)(a,{size:14}),`正在等待授权完成…(最多 5 分钟;没弹出浏览器就点上面的链接)`]}),S?(0,I.jsx)(b,{tone:`error`,children:E(S)}):null]})});let j=o===`confirm`;return(0,I.jsx)(k,{title:e?`重新导入授权 · ${e.name}`:`导入客户端`,onClose:n,footer:(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:n,children:`取消`}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:v,onClick:()=>{if(!j){s(`confirm`);return}A()},children:[v?(0,I.jsx)(a,{size:14}):(0,I.jsx)(t,{size:14}),v?`正在申请授权…`:j?`确定,前往授权页`:`导入`]})]}),children:j?(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(b,{tone:`warn`,children:(0,I.jsxs)(`span`,{children:[`即将跳转至 `,(0,I.jsx)(`b`,{children:O.label}),` 的授权页面(`,(0,I.jsx)(`b`,{children:`新标签页`}),`)。 请在页面里登录并完成授权,授权完成后本窗口会自动检测。`]})}),(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{marginTop:12},children:[`授权只用于取本工具的凭证;令牌保存在`,(0,I.jsx)(`b`,{children:`本机服务端`}),`(0600),不会回传到浏览器。`]}),c===`anthropic`?(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{marginTop:8},children:[`⚠️ MiniMax 的凭证是`,(0,I.jsx)(`b`,{children:`同账号单会话`}),`:请用`,(0,I.jsx)(`b`,{children:`专用账号`}),`, 否则会与日常客户端互相顶下线。`]}):null,S?(0,I.jsx)(b,{tone:`error`,children:E(S)}):null]}):(0,I.jsxs)(I.Fragment,{children:[(0,I.jsxs)(`p`,{className:`ov-sub`,children:[`选一个客户端,用它的`,(0,I.jsx)(`b`,{children:`官方授权`}),`拿到凭证(凭证在客户端登录态里,用户拿不到、无需手填)。`]}),(0,I.jsxs)(`div`,{className:`ov-field`,children:[(0,I.jsx)(`label`,{children:`客户端类型`}),(0,I.jsx)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:6},children:z.map(e=>(0,I.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:8,cursor:`pointer`},children:[(0,I.jsx)(`input`,{type:`radio`,name:`import-client`,checked:c===e.protocol,onChange:()=>l(e.protocol)}),(0,I.jsxs)(`span`,{children:[(0,I.jsx)(`b`,{children:e.label}),(0,I.jsx)(`span`,{className:`ov-hint`,style:{marginLeft:8},children:e.desc})]})]},e.protocol))})]}),(0,I.jsxs)(`div`,{className:`ov-field`,children:[(0,I.jsx)(`label`,{htmlFor:`imp-name`,children:`名称(可留空)`}),(0,I.jsx)(`input`,{id:`imp-name`,className:`field-input text`,style:{width:`100%`},value:u,placeholder:O.label,onChange:e=>d(e.target.value)})]})]})})}var W={upstream:`上游`,seed:`种子`,manual:`手工`},G={upstream:`从上游模型目录拉取到的`,seed:`来自本地种子清单(上游拉取失败时的回落)`,manual:`人工添加的`};function K({catalog:t,catalogLoading:n,catalogMsg:r,catalogError:i,onCatalogRefresh:s,onCatalogReload:c,onModelToggled:u}){let[d,f]=(0,F.useState)(null),p=async e=>{f(e.id);try{await u(e.id,!e.enabled)}finally{f(null)}};return(0,I.jsxs)(`div`,{className:`up-col`,children:[(0,I.jsxs)(`div`,{className:`up-models-head`,children:[(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`b`,{children:`可用模型`}),(0,I.jsx)(`span`,{children:t?`${t.models.length} 个 · ${t.models.filter(e=>e.enabled).length} 个已启用`:`尚未获取`})]}),(0,I.jsx)(`div`,{className:`up-models-actions`,children:n?(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:!0,children:[(0,I.jsx)(a,{size:14,className:`spin`,style:{animation:`spin 1s linear infinite`}}),`正在实测模型…`]}):(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:s,children:[(0,I.jsx)(l,{size:14}),`获取模型`]})})]}),n?(0,I.jsxs)(`div`,{className:`up-progress`,role:`status`,children:[(0,I.jsx)(a,{size:14,className:`spin`,style:{animation:`spin 1s linear infinite`},"aria-hidden":!0}),(0,I.jsxs)(`span`,{children:[`正在逐个实测模型(每个 1 token)…这一步会真打上游,`,(0,I.jsx)(`b`,{children:`可能要几十秒`}),`,请不要关掉页面; 完成后结果会出现在这里,模型清单也会自动刷新。`]})]}):null,r?(0,I.jsx)(b,{tone:r.kind===`error`?`error`:r.kind===`partial`?`warn`:`success`,children:r.text}):null,i?(0,I.jsx)(T,{kind:`error`,title:`模型清单没拉到`,detail:E(i),onRetry:c}):n&&!t?(0,I.jsx)(T,{kind:`loading`,title:`正在向上游获取模型…`}):!t||t.models.length===0?(0,I.jsx)(T,{kind:`empty`,title:`还没有模型清单`,detail:`启用提供商后会自动同步一次它自己的模型清单;这里也可以手动点「获取模型」逐项实测是否真的能调通。`}):(0,I.jsx)(`div`,{className:`up-models`,children:t.models.map(t=>(0,I.jsxs)(`div`,{className:`up-model${t.enabled?``:` off`}`,children:[(0,I.jsxs)(`div`,{className:`um-info`,children:[(0,I.jsxs)(`b`,{children:[(0,I.jsx)(`span`,{className:`um-k`,children:`模型名:`}),t.displayName,(0,I.jsx)(m,{content:G[t.source],children:(0,I.jsx)(`span`,{className:`um-src src-${t.source}`,children:W[t.source]})}),t.tag?(0,I.jsx)(m,{content:`上游计价标签,随目录同步更新`,children:(0,I.jsx)(`span`,{className:`mu-tag`,children:t.tag})}):null]}),(0,I.jsxs)(`i`,{className:`code-chip`,children:[(0,I.jsx)(`span`,{className:`um-k`,children:`模型ID:`}),t.id]})]}),(0,I.jsxs)(`div`,{className:`um-meta`,children:[t.verified?(0,I.jsx)(m,{content:`实测可调通`,children:(0,I.jsxs)(`span`,{className:`um-ok`,children:[(0,I.jsx)(o,{size:12}),`可用`]})}):(0,I.jsx)(m,{content:`实测未通过(可能未订阅)`,children:(0,I.jsxs)(`span`,{className:`um-bad`,children:[(0,I.jsx)(e,{size:12}),`未通过`]})}),t.multiplier==null?(0,I.jsx)(m,{content:`接口与本地快照都没有该模型的倍率 —— 不编造`,children:(0,I.jsx)(`span`,{className:`um-mult unknown`,children:`倍率未知`})}):(0,I.jsx)(m,{content:t.source===`upstream`?`倍率取自上游 /v3/config 接口实时值,随目录刷新与每日同步更新。`:`接口未覆盖该模型,倍率为内置快照,可能随官方调价过期。`,children:(0,I.jsxs)(`span`,{className:`um-mult`,children:[`×`,t.multiplier]})}),(0,I.jsx)(P,{checked:t.enabled,disabled:d===t.id,label:`${t.enabled?`停用`:`启用`} ${t.displayName}`,onChange:()=>void p(t)})]})]},t.id))}),(0,I.jsxs)(`div`,{className:`up-foot`,children:[`实测会真打上游、消耗少量积分(每模型 1 token)。上游不接受的模型会标「未通过」, 但`,(0,I.jsx)(`b`,{children:`不删除`}),` —— 订阅以后可能又开,随时可以再拉一次。 倍率以 /v3/config 接口实时值为准(目录刷新与每日自动同步都会更新), 接口未覆盖的模型回落内置快照。`,(0,I.jsx)(`br`,{}),`启用提供商时会同步这家自己的清单;已有提供商的目录互不覆盖,启用/停用选择也分别保留。`]})]})}function q({window:e}){let t=_(()=>h.providers(),[],{cacheKey:`providers`}),[n,i]=(0,F.useState)(null),o=_(()=>h.catalog(n?.id),[n?.id],{cacheKey:n?.id?`catalog:${n.id}`:`catalog`}),s=_(()=>h.clients(e),[e],{cacheKey:`clients:${e}`}),[c,l]=(0,F.useState)(!1),[u,d]=(0,F.useState)(null),[f,g]=(0,F.useState)(null),[v,y]=(0,F.useState)(null),[S,D]=(0,F.useState)(null),[O,j]=(0,F.useState)(null),[M,P]=(0,F.useState)(null),[R,z]=(0,F.useState)(!1),B=x(),V=t.data?.items??[],H=s.data?.byProvider??[],W=()=>{t.reload(),o.reload(),s.reload()},G=async e=>{g(e.id),j(null);try{await h.activateProvider(e.id),P(null),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},q=async e=>{g(e.id),j(null);try{await h.deactivateProvider(e.id),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},Y=async e=>{g(e.id),j(null);try{let n=await h.providerSigninNow(e.id);P({kind:n.ok?`ok`:`error`,text:n.message}),n.ok?B.push(n.outcome===`none`?{tone:`info`,message:`${e.name}:${n.message}`}:{tone:`success`,message:`${e.name} 签到成功${n.points?` · +${n.points} 积分`:``}。`}):B.push({tone:`error`,message:`${e.name} 签到失败:${n.message}`}),t.reload()}catch(t){let n=t instanceof p?t:new p(String(t),0,0);j(n),B.push({tone:`error`,message:`${e.name} 签到失败:${n.message||`请求未完成`}`})}finally{g(null)}},X=async e=>{g(e.id),j(null);try{let n=await h.providerGrowthRun(e.id),r=n.tasks?.filter(e=>e.outcome===`claimed`).length??0,i=n.tasks?.filter(e=>e.outcome===`already`).length??0,a=n.tasks?.filter(e=>e.outcome===`skipped`).length??0,o=n.ok?`领取完成:签到 ${n.signin?.points??0} 积分;任务新领 ${r}、已领过 ${i}、待完成 ${a};本轮合计 +${n.creditsGained??0}。`:n.message??`领取失败。`;P({kind:n.ok?`ok`:`error`,text:o}),B.push(n.ok?{tone:`success`,message:`${e.name}:${o}`}:{tone:`error`,message:`${e.name} 领取失败:${n.message??`未知原因`}`}),t.reload()}catch(t){let n=t instanceof p?t:new p(String(t),0,0);j(n),B.push({tone:`error`,message:`${e.name} 领取失败:${n.message||`请求未完成`}`})}finally{g(null)}},Z=async e=>{let n=globalThis.prompt?.(`粘贴 WorkBuddy OAuth 凭证 JSON(tools/wb-oauth-export.mjs 的输出):`,``)??null;if(!n)return;let r;try{r=JSON.parse(n)}catch{P({kind:`error`,text:`不是合法 JSON —— 请完整复制导出文件内容。`});return}g(e.id),j(null);try{let n=await h.providerOauthImport(e.id,r);P({kind:n.ok?`ok`:`error`,text:n.message}),t.reload()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},Q=async()=>{if(!S||f)return;let e=S;g(e.id),j(null);try{await h.deleteProvider(e.id),D(null),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0)),D(null)}finally{g(null)}},ee=async()=>{if(!R){z(!0),P(null),j(null);try{let e=await h.refreshCatalog(),r=e.configPulled===!1?` 倍率未从接口取到,沿用本地快照。`:e.multipliersUpdated?` 倍率已按接口同步(${e.multipliersUpdated} 项有更新)。`:``,i=e.perProvider?.find(e=>e.id===n?.id),a=i?.checked??e.checked,s=i?.ok??e.ok,c=i?.failed.length??e.failed.length;P(c>0?{kind:`partial`,text:`已实测 ${a} 个模型:${s} 个可用、${c} 个未通过(可能未订阅)。未通过的不会删除,订阅后可再拉一次。${r}`}:{kind:`ok`,text:`已实测 ${a} 个模型,全部可用。${r}`}),o.reload(),t.reload()}catch(e){let t=e instanceof p?e:new p(String(e),0,0);P({kind:`error`,text:`获取模型失败:${E(t)}`})}finally{z(!1)}}},te=async(e,t)=>{j(null);try{await h.setModelEnabled(e,t,n?.id),o.reload()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}},$=V.filter(e=>e.active).length;return(0,I.jsxs)(I.Fragment,{children:[O?(0,I.jsx)(b,{tone:`error`,children:E(O)}):null,s.error?(0,I.jsxs)(b,{tone:`error`,actions:(0,I.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:s.reload,children:`重试`}),children:[`提供商用量没取到:`,E(s.error)]}):null,(0,I.jsxs)(C,{title:`提供商`,aside:(0,I.jsxs)(`span`,{className:`panel-aside-row`,children:[$>0?(0,I.jsxs)(w,{tone:`live`,dot:!0,children:[`已启用 `,$,` 家`]}):null,(0,I.jsx)(m,{content:`选一个客户端(WorkBuddy / MiniMax Code / Qoder CN),用它的官方授权导入凭证`,children:(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>l(!0),children:[(0,I.jsx)(r,{size:14}),`导入客户端`]})})]}),padded:!1,children:[t.initial&&t.loading?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(A,{})}):t.error?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(T,{kind:`error`,title:`提供商列表没取到`,detail:E(t.error),onRetry:t.reload})}):V.length===0?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(T,{kind:`empty`,title:`还没有任何提供商`,detail:`先登记一个上游,再按需启用它的出站路由。`})}):(0,I.jsx)(L,{rows:V,byProvider:H,busyId:f,onEdit:y,onSignin:e=>void Y(e),onGrowthRun:e=>void X(e),onOauthImport:e=>void Z(e),onModels:e=>{P(null),i(e)},onActivate:e=>void G(e),onDeactivate:e=>void q(e),onDelete:D}),(0,I.jsxs)(`div`,{className:`footer-note`,style:{padding:`0 20px 18px`},children:[`启用提供商会让它加入出站路由,并同步它自己的模型清单;已启用的提供商可以并行生效,模型按各自目录路由。 三家凭证都来自各客户端的`,(0,I.jsx)(`b`,{children:`官方授权`}),`(用户无需也无法手填)——点「重新导入授权」即可换账号或刷新令牌; 令牌只存在服务端,任何接口都读不回完整值。`,(0,I.jsx)(`br`,{}),`行内三列用量按`,(0,I.jsx)(`b`,{children:`提供商`}),`归属:由「模型 id → 生效目录的提供商」解析(请求记录本身没有 provider 维度)。模型不属于任何生效目录的行归到「未归属」,不冒充某一家。`]})]}),c?(0,I.jsx)(U,{onClose:()=>{l(!1),W()},onImported:()=>W()}):null,u?(0,I.jsx)(U,{provider:{id:u.id,name:u.name,protocol:u.protocol},onClose:()=>{d(null),W()},onImported:()=>W()}):null,v?(0,I.jsx)(J,{provider:v,onClose:()=>y(null),onSaved:()=>{y(null),W()},onReimport:()=>{let e=v;y(null),e&&d(e)}}):null,n?(0,I.jsx)(N,{title:`模型列表 · ${n.name}`,subtitle:n.baseUrl,onClose:()=>i(null),children:(0,I.jsx)(`div`,{className:`drawer-body`,children:(0,I.jsx)(K,{catalog:o.data,catalogLoading:R,catalogMsg:M,catalogError:o.error,onCatalogRefresh:()=>void ee(),onCatalogReload:o.reload,onModelToggled:te})})}):null,S?(0,I.jsx)(k,{title:`删除这个提供商?`,onClose:()=>{f||D(null)},footer:(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:!!f,onClick:()=>D(null),children:`取消(保留)`}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-danger btn-sm`,disabled:!!f,onClick:()=>void Q(),children:[f?(0,I.jsx)(a,{size:14,className:`spin`}):null,f?`删除中…`:`确认删除`]})]}),children:(0,I.jsxs)(`div`,{className:`ov-lose`,children:[(0,I.jsx)(`b`,{children:`会失去什么:`}),`提供商「`,S.name,`」(`,S.baseUrl,`)的登记与它的凭证文件一并删除, 需要重新登记并填一次 ck_ 密钥。此操作不可撤销。`]})}):null]})}function J({provider:e,onClose:t,onSaved:i,onReimport:o}){let[s,c]=(0,F.useState)(e.name),[l,u]=(0,F.useState)(e.baseUrl),[d,f]=(0,F.useState)(!1),[g,_]=(0,F.useState)(!1),[v,y]=(0,F.useState)(null),[x,S]=(0,F.useState)(null),[C,w]=(0,F.useState)(!1),[T,D]=(0,F.useState)(null),O=async()=>{if(!C){w(!0),D(null),y(null);try{let t=await h.providerImportLocal(e.id);if(t.ok)D({kind:`ok`,text:t.message+(t.detail?`\n${t.detail}`:``)}),i();else if(t.outcome===`unavailable`){let t=e.protocol===`qoder`?`qoder`:`minimax`;D({kind:`unavailable`,text:`本服务所在机器上读不到客户端登录态(远程部署的正常情形)。`,command:`node tools/import-credential-push.mjs ${location.origin} ${t} --provider ${e.id}`})}else D({kind:`error`,text:t.message+(t.detail?`\n${t.detail}`:``)})}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{w(!1)}}},k=async t=>{if(t.preventDefault(),!d){f(!0),y(null);try{let t={};s.trim()!==e.name&&(t.name=s.trim()),l.trim()!==e.baseUrl&&(t.baseUrl=l.trim()),Object.keys(t).length>0&&await h.patchProvider(e.id,t),i()}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{f(!1)}}},A=async()=>{if(!g){_(!0),y(null),S(null);try{S(await h.testProvider(e.id,void 0))}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{_(!1)}}};return(0,I.jsx)(N,{title:`配置提供商 · ${e.name}`,subtitle:e.baseUrl,onClose:t,children:(0,I.jsxs)(`div`,{className:`drawer-body`,children:[v?(0,I.jsx)(b,{tone:`error`,children:E(v)}):null,(0,I.jsxs)(`form`,{onSubmit:e=>void k(e),className:`prov-edit`,style:{marginTop:6},children:[(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`名称`}),(0,I.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},value:s,onChange:e=>c(e.target.value),"aria-label":`提供商名称`})]}),(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`地址`}),(0,I.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},value:l,placeholder:`https://api.example.com`,onChange:e=>u(e.target.value),"aria-label":`提供商地址`})]}),(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`凭证`}),(0,I.jsxs)(`div`,{className:`cred-guide`,children:[(0,I.jsx)(`div`,{className:`cred-guide-note`,children:e.protocol===`openai`?(0,I.jsxs)(I.Fragment,{children:[`凭证来自 WorkBuddy `,(0,I.jsx)(`b`,{children:`官方 OAuth 授权`}),`(桌面端 / 插件同一条通道),无需手填。`]}):e.protocol===`anthropic`?(0,I.jsxs)(I.Fragment,{children:[`凭证是客户端登录态里的 `,(0,I.jsx)(`span`,{className:`code-chip`,children:`mmort_…`}),` refresh token。 请用`,(0,I.jsx)(`b`,{children:`专用账号`}),`(与桌面端共用会互相顶下线)。`]}):(0,I.jsxs)(I.Fragment,{children:[`凭证来自 Qoder 客户端登录态(一段`,(0,I.jsx)(`b`,{children:`身份 JSON`}),`)。桌面客户端与它`,(0,I.jsx)(`b`,{children:`互不影响`}),`。`]})}),(0,I.jsxs)(`div`,{className:`cred-guide-actions`,children:[(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:d,onClick:()=>o(),children:[(0,I.jsx)(r,{size:14}),`重新导入授权`]}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:C,onClick:()=>void O(),children:[C?(0,I.jsx)(a,{size:14}):(0,I.jsx)(r,{size:14}),`从本机客户端读取`]})]}),T?(0,I.jsx)(b,{tone:T.kind===`ok`?`success`:T.kind===`unavailable`?`warn`:`error`,children:(0,I.jsxs)(`span`,{children:[T.text,T.command?(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`br`,{}),`在`,(0,I.jsx)(`b`,{children:`装了客户端的那台机器`}),`的仓库目录下跑:`,(0,I.jsx)(R,{command:T.command})]}):null]})}):null,e.protocol===`qoder`?(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`本服务`,(0,I.jsx)(`b`,{children:`不自动刷新`}),` Qoder 令牌;过期后重跑一次导入即可。`]}):null]})]}),(0,I.jsxs)(`div`,{className:`up-actions`,children:[(0,I.jsxs)(`button`,{type:`submit`,className:`btn btn-primary btn-sm`,disabled:d,children:[d?(0,I.jsx)(a,{size:14}):(0,I.jsx)(n,{size:14}),`保存`]}),(0,I.jsx)(m,{content:`用已导入的授权测这家上游是否还通`,children:(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:g,onClick:()=>void A(),children:[g?(0,I.jsx)(a,{size:14}):null,`测试`]})})]})]}),x?(0,I.jsx)(b,{tone:x.ok?`success`:`warn`,children:(0,I.jsx)(`span`,{children:x.ok?(0,I.jsxs)(I.Fragment,{children:[`通了:`,e.name,` 接受了这次 1 token 探测(`,x.elapsedMs,`ms)。`]}):x.httpStatus===null?(0,I.jsxs)(I.Fragment,{children:[`没连上(网络层):`,x.reason??`未知原因`,`。先查地址与出网,再看凭证。`]}):(0,I.jsxs)(I.Fragment,{children:[`连上了但被拒(HTTP `,x.httpStatus,x.upstreamCode===null?``:` · 业务码 ${x.upstreamCode}`,`)。`,x.upstreamCode===11102?`业务码 11102 = 账号没订阅这个模型。key 可能是好的,换个模型再测或直接切换。`:`通常是凭证问题;换一把再测。`]})})}):null,(0,I.jsx)(`div`,{className:`up-note`,style:{marginTop:18},children:(0,I.jsxs)(`span`,{children:[`凭证只存在`,(0,I.jsx)(`b`,{children:`服务端`}),`:界面只显示账号摘要或掩码,任何接口都读不回完整值。`]})})]})})}function Y(){let[e,t]=(0,F.useState)(()=>M()??`today`);return(0,I.jsxs)(`div`,{className:`view on`,children:[(0,I.jsx)(D,{title:`提供商管理`,actions:(0,I.jsx)(O,{value:e,options:v,onChange:e=>{j(e),t(e)},width:148})}),(0,I.jsx)(`div`,{className:`view-body stack`,children:(0,I.jsx)(q,{window:e})})]})}export{Y as ClientsPage};
1
+ import{H as e,I as t,J as n,L as r,Q as i,S as a,U as o,a as s,it as c,m as l,n as u,tt as d,z as f}from"./vendor-befEYGHM.js";import{M as p,a as m,c as h,g,j as _,k as v,p as y,r as b,t as x,v as S}from"./index-BMxTTq4E.js";import{a as C,c as w,n as T,r as E,s as D,t as O}from"./Select-CrLEZJdb.js";import{t as k}from"./Modal-B-rIrvXS.js";import{t as A}from"./PageSkeleton-DG-Dj4_9.js";import{n as j,t as M}from"./sharedWindow-FDfq75PI.js";import{t as N}from"./Drawer-CleQGhRU.js";import{t as P}from"./Switch-0KA1AbiV.js";var F=c(),I=d();function L({rows:e,byProvider:t,busyId:n,onEdit:r,onModels:a,onSignin:o,onGrowthRun:c,onOauthImport:d,onActivate:f,onDeactivate:p,onDelete:h}){let _=()=>new Intl.DateTimeFormat(`en-CA`,{timeZone:`Asia/Shanghai`,year:`numeric`,month:`2-digit`,day:`2-digit`}).format(new Date),v=new Map((t??[]).map(e=>[e.providerId??``,e]));return(0,I.jsx)(`div`,{className:`tbl-scroll`,children:(0,I.jsxs)(`table`,{className:`tbl`,children:[(0,I.jsxs)(`caption`,{className:`sr-only`,children:[`提供商列表,共 `,e.length,` 家提供商`]}),(0,I.jsx)(`thead`,{children:(0,I.jsxs)(`tr`,{children:[(0,I.jsx)(`th`,{scope:`col`,children:`提供商`}),(0,I.jsx)(`th`,{scope:`col`,children:`密钥`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`最后活跃`}),(0,I.jsx)(`th`,{scope:`col`,className:`r`,children:`操作`})]})}),(0,I.jsx)(`tbody`,{children:e.map(t=>{let b=n===t.id;return(0,I.jsxs)(`tr`,{className:`prov-row`,children:[(0,I.jsxs)(`td`,{className:`td-strong`,children:[t.name,t.active?(0,I.jsx)(w,{tone:`live`,children:`已启用`}):(0,I.jsx)(w,{tone:`ghost`,children:`未启用`}),t.protocol===`anthropic`?(0,I.jsx)(m,{content:`Anthropic Messages 方言(mavis 网关):模型目录用内置清单,凭证为 refresh token 自动换新`,children:(0,I.jsx)(`span`,{className:`pill pill-ghost`,children:`Anthropic`})}):t.protocol===`qoder`?(0,I.jsx)(m,{content:`Qoder CN 网关(COSY 协议):免费档 billable:false 实扣 0;目录从网关 /model/list 拉取;凭证为客户端身份 JSON(不自动刷新)`,children:(0,I.jsx)(`span`,{className:`pill pill-ghost`,children:`Qoder`})}):null,(0,I.jsx)(`div`,{className:`td-sub td-mono`,children:(0,I.jsx)(m,{content:(0,I.jsx)(I.Fragment,{children:(0,I.jsxs)(`div`,{className:`client-model-tip`,children:[(0,I.jsxs)(`div`,{className:`client-model-tip-head`,children:[(0,I.jsxs)(`div`,{className:`client-model-tip-title`,children:[(0,I.jsx)(`strong`,{children:t.name}),(0,I.jsx)(`span`,{children:t.baseUrl})]}),(0,I.jsx)(`span`,{className:`client-model-tip-tag`,children:t.active?`已启用`:`未启用`})]}),(0,I.jsxs)(`div`,{className:`client-model-tip-summary`,children:[(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`span`,{children:`模型总数`}),(0,I.jsx)(`b`,{children:g(t.modelCount??0)})]}),(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`span`,{children:`已启用`}),(0,I.jsx)(`b`,{children:g(t.modelEnabled??0)})]})]}),(()=>{let e=t.credits?.available?{remaining:t.credits.remaining,consumed:t.credits.consumed}:t.wbCredits?{remaining:t.wbCredits.remaining,consumed:t.wbCredits.consumed}:t.qoderCredits?{remaining:t.qoderCredits.remaining,consumed:t.qoderCredits.consumed}:null;return e&&(e.remaining!==null||e.consumed!==null)?(0,I.jsxs)(`div`,{className:`client-metric-tip-total`,children:[(0,I.jsx)(`span`,{children:`剩余积分`}),(0,I.jsxs)(`b`,{children:[y(e.remaining??0),` `,(0,I.jsxs)(`em`,{children:[`已用 `,y(e.consumed??0)]})]})]}):null})(),t.credits?.stale||t.qoderCredits?.stale?(0,I.jsx)(`div`,{className:`client-model-tip-note`,children:`⚠ 积分数据偏旧(账本快照超时)`}):null]})}),children:(0,I.jsxs)(`span`,{className:`prov-model-count`,children:[g(t.modelCount??0),` 个模型`]})})})]}),(0,I.jsxs)(`td`,{className:`td-cred`,children:[(0,I.jsx)(m,{content:`供应商访问密钥(掩码显示;全量只存在服务器上)`,children:(0,I.jsx)(`span`,{className:`cred-mask`,children:t.credentialMask})}),(0,I.jsx)(`span`,{className:`up-badge${t.configured?``:` warn`}`,children:t.configured?`有效`:`未配置`})]}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?g(v.get(t.id)?.requests??0):`—`}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?y(v.get(t.id)?.credits??0):`—`}),(0,I.jsx)(`td`,{className:`r td-mono`,children:t.active?S(v.get(t.id)?.lastSeenAt??null):`—`}),(0,I.jsx)(`td`,{className:`r`,children:(0,I.jsxs)(`span`,{className:`prov-row-actions`,children:[(0,I.jsx)(m,{content:`改名称与上游地址;凭证可在抽屉内「重新导入授权」`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:b,onClick:()=>r(t),children:[(0,I.jsx)(u,{size:12}),`配置`]})}),(0,I.jsx)(m,{content:`查看这家自己的模型清单(启停只作用于该提供商分区)`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||!t.active,onClick:()=>{!b&&t.active&&a(t)},children:[(0,I.jsx)(l,{size:12}),`模型列表`]})}),t.credKind===`oauth_minimax`||t.protocol===`qoder`?(0,I.jsx)(m,{content:(()=>{let e=t.protocol===`qoder`?`10:05`:`08:05`,n=t.signin?.checkedOn?`最近签到 ${t.signin.checkedOn}${t.signin.lastPoints?` · +${t.signin.lastPoints} 积分`:``}(relay 每天 ${e} 自动签)`:`今天还没签到 —— 点一下立即签(relay 每天 ${e} 也会自动签)`;return t.protocol===`qoder`?(0,I.jsxs)(I.Fragment,{children:[n,(0,I.jsx)(`br`,{}),`Qoder 官方标注:每日 10:00(UTC+8)刷新,领取后 30 天有效。`]}):n})(),children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||t.signin?.checkedOn===_(),onClick:()=>{b||t.signin?.checkedOn===_()||o(t)},children:[(0,I.jsx)(i,{size:12}),t.signin?.checkedOn===_()?`已签到`:`签到`]})}):null,t.protocol===`openai`?t.wbOAuth?.configured?(0,I.jsx)(m,{content:t.wbSignin?.checkedOn===_()?`今天已签到过(明天 08:05 自动跑;任务面板在设置 → 提供商健康)`:`立即签到 + 顺带跑一轮成长任务与连登兑换(每天 08:05 也会自动跑)。转发不受影响。`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b||t.wbSignin?.checkedOn===_(),onClick:()=>{!b&&t.wbSignin?.checkedOn!==_()&&c(t)},children:[(0,I.jsx)(i,{size:12}),t.wbSignin?.checkedOn===_()?`已签到`:`签到`]})}):(0,I.jsx)(m,{content:`导入桌面端 OAuth 凭证后可代领签到/任务(上游不允许 API 密钥代领)。粘贴 tools/wb-oauth-export.mjs 的输出;转发仍走 API 密钥。`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b,onClick:()=>{b||d(t)},children:[(0,I.jsx)(i,{size:12}),`导入OAuth`]})}):null,t.active?(0,I.jsx)(m,{content:`退出出站路由(目录与凭证保留,可再启用)`,children:(0,I.jsx)(`button`,{type:`button`,className:`mini-pill`,"aria-disabled":b,onClick:()=>{b||p(t)},children:`停用`})}):(0,I.jsx)(`button`,{type:`button`,className:`mini-pill`,disabled:b,onClick:()=>f(t),children:`启用`}),(0,I.jsx)(m,{content:t.active?`不能删除已启用的提供商 —— 先停用它`:e.length<=1?`至少保留一个提供商`:`删除这个提供商(凭证文件一并删除)`,children:(0,I.jsxs)(`button`,{type:`button`,className:`mini-pill danger`,"aria-disabled":b||t.active||e.length<=1,onClick:()=>{!b&&!t.active&&e.length>1&&h(t)},children:[(0,I.jsx)(s,{size:12}),`删除`]})})]})})]},t.id)})})]})})}function R({command:e}){let[t,r]=(0,F.useState)(!1);if(!e)return null;let i=async()=>{try{await navigator.clipboard.writeText(e),r(!0),setTimeout(()=>r(!1),2e3)}catch{r(!1)}};return(0,I.jsxs)(`div`,{className:`cred-guide-cmd`,children:[(0,I.jsx)(`code`,{children:e}),(0,I.jsxs)(`button`,{type:`button`,className:`copy`,"aria-label":t?`已复制命令`:`复制命令`,onClick:()=>void i(),children:[t?(0,I.jsx)(n,{size:13}):(0,I.jsx)(f,{size:13}),t?`已复制`:`复制`]})]})}var z=[{protocol:`openai`,label:`WorkBuddy`,desc:`OpenAI 方言`,needsAuth:!0},{protocol:`anthropic`,label:`MiniMax Code`,desc:`Anthropic 方言`,needsAuth:!0},{protocol:`qoder`,label:`Qoder CN`,desc:`COSY 协议`,needsAuth:!0}],B={openai:`https://copilot.tencent.com`,anthropic:`https://agent.minimax.cn`,qoder:`https://gateway.qoder.com.cn`},V=2500,H=3e5;function U({provider:e,onClose:n,onImported:r}){let i=x(),[o,s]=(0,F.useState)(e?`confirm`:`pick`),[c,l]=(0,F.useState)(e?.protocol??`anthropic`),[u,d]=(0,F.useState)(e?.name??``),[f,m]=(0,F.useState)(``),[g,_]=(0,F.useState)(``),[v,y]=(0,F.useState)(!1),[S,C]=(0,F.useState)(null),w=(0,F.useRef)(e?.id??null),T=(0,F.useRef)(!1),D=(0,F.useRef)(!1),O=z.find(e=>e.protocol===c);(0,F.useEffect)(()=>()=>{D.current=!0;let e=w.current;e&&h.providerImportDeviceCancel(e).catch(()=>void 0)},[]);let A=(0,F.useCallback)(async()=>{y(!0),C(null);try{let e=w.current;e||(e=(await h.createProvider({name:u.trim()||O.label,baseUrl:B[c]??B.openai,credential:``,protocol:c})).item.id,w.current=e,T.current=!0);let t=await h.providerImportDeviceStart(e);if(!t.ok||!t.authUrl){C(new p(t.message??`申请授权失败`,0,0)),s(`confirm`),y(!1);return}m(t.authUrl),_(t.userCode??``),window.open(t.authUrl,`_blank`,`noopener`),s(`waiting`),y(!1);let n=Date.now()+H;for(;!D.current&&Date.now()<n;){if(await new Promise(e=>setTimeout(e,V)),D.current)return;let t=await h.providerImportDevicePoll(e);if(D.current)return;if(t.status===`done`){s(`done`),i.push({tone:`success`,message:`${u.trim()||O.label} 授权已导入。`}),r();return}if(t.status===`error`){C(new p(t.message??`授权失败`,0,0)),s(`confirm`);return}}D.current||(C(new p(`授权超时(5 分钟)—— 请重新发起导入。`,0,0)),s(`confirm`))}catch(e){C(e instanceof p?e:new p(String(e),0,0)),s(`confirm`),y(!1)}},[O.label,u,r,c,i]);if(o===`done`)return(0,I.jsxs)(k,{title:`授权已导入`,onClose:n,footer:(0,I.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:n,children:`完成`}),children:[(0,I.jsx)(b,{tone:`success`,children:(0,I.jsxs)(`span`,{children:[u.trim()||O.label,` 的授权已通过官方通道导入。`]})}),e?null:(0,I.jsxs)(`p`,{className:`ov-sub`,style:{marginTop:12},children:[`回到列表点`,(0,I.jsx)(`b`,{children:`试通`}),`确认可用,再按需启用(启用后它才参与出站路由)。`]})]});if(o===`waiting`)return(0,I.jsx)(k,{title:`等待你完成授权`,onClose:n,children:(0,I.jsxs)(`div`,{className:`cred-guide`,children:[(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`已为你打开 `,(0,I.jsx)(`b`,{children:O.label}),` 的授权页 —— `,(0,I.jsx)(`b`,{children:`请在浏览器里完成授权`}),`, 本窗口会自动检测结果,无需回到终端。`]}),g?(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`若页面要求输入短码:`,(0,I.jsx)(`span`,{className:`code-chip`,children:g})]}):null,(0,I.jsx)(R,{command:f}),(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{display:`flex`,alignItems:`center`,gap:8},children:[(0,I.jsx)(a,{size:14}),`正在等待授权完成…(最多 5 分钟;没弹出浏览器就点上面的链接)`]}),S?(0,I.jsx)(b,{tone:`error`,children:E(S)}):null]})});let j=o===`confirm`;return(0,I.jsx)(k,{title:e?`重新导入授权 · ${e.name}`:`导入客户端`,onClose:n,footer:(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:n,children:`取消`}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:v,onClick:()=>{if(!j){s(`confirm`);return}A()},children:[v?(0,I.jsx)(a,{size:14}):(0,I.jsx)(t,{size:14}),v?`正在申请授权…`:j?`确定,前往授权页`:`导入`]})]}),children:j?(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(b,{tone:`warn`,children:(0,I.jsxs)(`span`,{children:[`即将跳转至 `,(0,I.jsx)(`b`,{children:O.label}),` 的授权页面(`,(0,I.jsx)(`b`,{children:`新标签页`}),`)。 请在页面里登录并完成授权,授权完成后本窗口会自动检测。`]})}),(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{marginTop:12},children:[`授权只用于取本工具的凭证;令牌保存在`,(0,I.jsx)(`b`,{children:`本机服务端`}),`(0600),不会回传到浏览器。`]}),c===`anthropic`?(0,I.jsxs)(`div`,{className:`cred-guide-note`,style:{marginTop:8},children:[`⚠️ MiniMax 的凭证是`,(0,I.jsx)(`b`,{children:`同账号单会话`}),`:请用`,(0,I.jsx)(`b`,{children:`专用账号`}),`, 否则会与日常客户端互相顶下线。`]}):null,S?(0,I.jsx)(b,{tone:`error`,children:E(S)}):null]}):(0,I.jsxs)(I.Fragment,{children:[(0,I.jsxs)(`p`,{className:`ov-sub`,children:[`选一个客户端,用它的`,(0,I.jsx)(`b`,{children:`官方授权`}),`拿到凭证(凭证在客户端登录态里,用户拿不到、无需手填)。`]}),(0,I.jsxs)(`div`,{className:`ov-field`,children:[(0,I.jsx)(`label`,{children:`客户端类型`}),(0,I.jsx)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:6},children:z.map(e=>(0,I.jsxs)(`label`,{style:{display:`flex`,alignItems:`center`,gap:8,cursor:`pointer`},children:[(0,I.jsx)(`input`,{type:`radio`,name:`import-client`,checked:c===e.protocol,onChange:()=>l(e.protocol)}),(0,I.jsxs)(`span`,{children:[(0,I.jsx)(`b`,{children:e.label}),(0,I.jsx)(`span`,{className:`ov-hint`,style:{marginLeft:8},children:e.desc})]})]},e.protocol))})]}),(0,I.jsxs)(`div`,{className:`ov-field`,children:[(0,I.jsx)(`label`,{htmlFor:`imp-name`,children:`名称(可留空)`}),(0,I.jsx)(`input`,{id:`imp-name`,className:`field-input text`,style:{width:`100%`},value:u,placeholder:O.label,onChange:e=>d(e.target.value)})]})]})})}var W={upstream:`上游`,seed:`种子`,manual:`手工`},G={upstream:`从上游模型目录拉取到的`,seed:`来自本地种子清单(上游拉取失败时的回落)`,manual:`人工添加的`};function K({catalog:t,catalogLoading:n,catalogMsg:r,catalogError:i,onCatalogRefresh:s,onCatalogReload:c,onModelToggled:u}){let[d,f]=(0,F.useState)(null),p=async e=>{f(e.id);try{await u(e.id,!e.enabled)}finally{f(null)}};return(0,I.jsxs)(`div`,{className:`up-col`,children:[(0,I.jsxs)(`div`,{className:`up-models-head`,children:[(0,I.jsxs)(`div`,{children:[(0,I.jsx)(`b`,{children:`可用模型`}),(0,I.jsx)(`span`,{children:t?`${t.models.length} 个 · ${t.models.filter(e=>e.enabled).length} 个已启用`:`尚未获取`})]}),(0,I.jsx)(`div`,{className:`up-models-actions`,children:n?(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:!0,children:[(0,I.jsx)(a,{size:14,className:`spin`,style:{animation:`spin 1s linear infinite`}}),`正在实测模型…`]}):(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:s,children:[(0,I.jsx)(l,{size:14}),`获取模型`]})})]}),n?(0,I.jsxs)(`div`,{className:`up-progress`,role:`status`,children:[(0,I.jsx)(a,{size:14,className:`spin`,style:{animation:`spin 1s linear infinite`},"aria-hidden":!0}),(0,I.jsxs)(`span`,{children:[`正在逐个实测模型(每个 1 token)…这一步会真打上游,`,(0,I.jsx)(`b`,{children:`可能要几十秒`}),`,请不要关掉页面; 完成后结果会出现在这里,模型清单也会自动刷新。`]})]}):null,r?(0,I.jsx)(b,{tone:r.kind===`error`?`error`:r.kind===`partial`?`warn`:`success`,children:r.text}):null,i?(0,I.jsx)(T,{kind:`error`,title:`模型清单没拉到`,detail:E(i),onRetry:c}):n&&!t?(0,I.jsx)(T,{kind:`loading`,title:`正在向上游获取模型…`}):!t||t.models.length===0?(0,I.jsx)(T,{kind:`empty`,title:`还没有模型清单`,detail:`启用提供商后会自动同步一次它自己的模型清单;这里也可以手动点「获取模型」逐项实测是否真的能调通。`}):(0,I.jsx)(`div`,{className:`up-models`,children:t.models.map(t=>(0,I.jsxs)(`div`,{className:`up-model${t.enabled?``:` off`}`,children:[(0,I.jsxs)(`div`,{className:`um-info`,children:[(0,I.jsxs)(`b`,{children:[(0,I.jsx)(`span`,{className:`um-k`,children:`模型名:`}),t.displayName,(0,I.jsx)(m,{content:G[t.source],children:(0,I.jsx)(`span`,{className:`um-src src-${t.source}`,children:W[t.source]})}),t.tag?(0,I.jsx)(m,{content:`上游计价标签,随目录同步更新`,children:(0,I.jsx)(`span`,{className:`mu-tag`,children:t.tag})}):null]}),(0,I.jsxs)(`i`,{className:`code-chip`,children:[(0,I.jsx)(`span`,{className:`um-k`,children:`模型ID:`}),t.id]})]}),(0,I.jsxs)(`div`,{className:`um-meta`,children:[t.verified?(0,I.jsx)(m,{content:`实测可调通`,children:(0,I.jsxs)(`span`,{className:`um-ok`,children:[(0,I.jsx)(o,{size:12}),`可用`]})}):(0,I.jsx)(m,{content:`实测未通过(可能未订阅)`,children:(0,I.jsxs)(`span`,{className:`um-bad`,children:[(0,I.jsx)(e,{size:12}),`未通过`]})}),t.multiplier==null?(0,I.jsx)(m,{content:`接口与本地快照都没有该模型的倍率 —— 不编造`,children:(0,I.jsx)(`span`,{className:`um-mult unknown`,children:`倍率未知`})}):(0,I.jsx)(m,{content:t.source===`upstream`?`倍率取自上游 /v3/config 接口实时值,随目录刷新与每日同步更新。`:`接口未覆盖该模型,倍率为内置快照,可能随官方调价过期。`,children:(0,I.jsxs)(`span`,{className:`um-mult`,children:[`×`,t.multiplier]})}),(0,I.jsx)(P,{checked:t.enabled,disabled:d===t.id,label:`${t.enabled?`停用`:`启用`} ${t.displayName}`,onChange:()=>void p(t)})]})]},t.id))}),(0,I.jsxs)(`div`,{className:`up-foot`,children:[`实测会真打上游、消耗少量积分(每模型 1 token)。上游不接受的模型会标「未通过」, 但`,(0,I.jsx)(`b`,{children:`不删除`}),` —— 订阅以后可能又开,随时可以再拉一次。 倍率以 /v3/config 接口实时值为准(目录刷新与每日自动同步都会更新), 接口未覆盖的模型回落内置快照。`,(0,I.jsx)(`br`,{}),`启用提供商时会同步这家自己的清单;已有提供商的目录互不覆盖,启用/停用选择也分别保留。`]})]})}function q({window:e}){let t=_(()=>h.providers(),[],{cacheKey:`providers`}),[n,i]=(0,F.useState)(null),o=_(()=>h.catalog(n?.id),[n?.id],{cacheKey:n?.id?`catalog:${n.id}`:`catalog`}),s=_(()=>h.clients(e),[e],{cacheKey:`clients:${e}`}),[c,l]=(0,F.useState)(!1),[u,d]=(0,F.useState)(null),[f,g]=(0,F.useState)(null),[v,y]=(0,F.useState)(null),[S,D]=(0,F.useState)(null),[O,j]=(0,F.useState)(null),[M,P]=(0,F.useState)(null),[R,z]=(0,F.useState)(!1),B=x(),V=t.data?.items??[],H=s.data?.byProvider??[],W=()=>{t.reload(),o.reload(),s.reload()},G=async e=>{g(e.id),j(null);try{await h.activateProvider(e.id),P(null),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},q=async e=>{g(e.id),j(null);try{await h.deactivateProvider(e.id),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},Y=async e=>{g(e.id),j(null);try{let n=await h.providerSigninNow(e.id);P({kind:n.ok?`ok`:`error`,text:n.message}),n.ok?B.push(n.outcome===`none`?{tone:`info`,message:`${e.name}:${n.message}`}:{tone:`success`,message:`${e.name} 签到成功${n.points?` · +${n.points} 积分`:``}。`}):B.push({tone:`error`,message:`${e.name} 签到失败:${n.message}`}),t.reload()}catch(t){let n=t instanceof p?t:new p(String(t),0,0);j(n),B.push({tone:`error`,message:`${e.name} 签到失败:${n.message||`请求未完成`}`})}finally{g(null)}},X=async e=>{g(e.id),j(null);try{let n=await h.providerGrowthRun(e.id),r=n.tasks?.filter(e=>e.outcome===`claimed`).length??0,i=n.tasks?.filter(e=>e.outcome===`already`).length??0,a=n.tasks?.filter(e=>e.outcome===`skipped`).length??0,o=n.ok?`领取完成:签到 ${n.signin?.points??0} 积分;任务新领 ${r}、已领过 ${i}、待完成 ${a};本轮合计 +${n.creditsGained??0}。`:n.message??`领取失败。`;P({kind:n.ok?`ok`:`error`,text:o}),B.push(n.ok?{tone:`success`,message:`${e.name}:${o}`}:{tone:`error`,message:`${e.name} 领取失败:${n.message??`未知原因`}`}),t.reload()}catch(t){let n=t instanceof p?t:new p(String(t),0,0);j(n),B.push({tone:`error`,message:`${e.name} 领取失败:${n.message||`请求未完成`}`})}finally{g(null)}},Z=async e=>{let n=globalThis.prompt?.(`粘贴 WorkBuddy OAuth 凭证 JSON(tools/wb-oauth-export.mjs 的输出):`,``)??null;if(!n)return;let r;try{r=JSON.parse(n)}catch{P({kind:`error`,text:`不是合法 JSON —— 请完整复制导出文件内容。`});return}g(e.id),j(null);try{let n=await h.providerOauthImport(e.id,r);P({kind:n.ok?`ok`:`error`,text:n.message}),t.reload()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}finally{g(null)}},Q=async()=>{if(!S||f)return;let e=S;g(e.id),j(null);try{await h.deleteProvider(e.id),D(null),W()}catch(e){j(e instanceof p?e:new p(String(e),0,0)),D(null)}finally{g(null)}},ee=async()=>{if(!R){z(!0),P(null),j(null);try{let e=await h.refreshCatalog(),r=e.configPulled===!1?` 倍率未从接口取到,沿用本地快照。`:e.multipliersUpdated?` 倍率已按接口同步(${e.multipliersUpdated} 项有更新)。`:``,i=e.perProvider?.find(e=>e.id===n?.id),a=i?.checked??e.checked,s=i?.ok??e.ok,c=i?.failed.length??e.failed.length;P(c>0?{kind:`partial`,text:`已实测 ${a} 个模型:${s} 个可用、${c} 个未通过(可能未订阅)。未通过的不会删除,订阅后可再拉一次。${r}`}:{kind:`ok`,text:`已实测 ${a} 个模型,全部可用。${r}`}),o.reload(),t.reload()}catch(e){let t=e instanceof p?e:new p(String(e),0,0);P({kind:`error`,text:`获取模型失败:${E(t)}`})}finally{z(!1)}}},te=async(e,t)=>{j(null);try{await h.setModelEnabled(e,t,n?.id),o.reload()}catch(e){j(e instanceof p?e:new p(String(e),0,0))}},$=V.filter(e=>e.active).length;return(0,I.jsxs)(I.Fragment,{children:[O?(0,I.jsx)(b,{tone:`error`,children:E(O)}):null,s.error?(0,I.jsxs)(b,{tone:`error`,actions:(0,I.jsx)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,onClick:s.reload,children:`重试`}),children:[`提供商用量没取到:`,E(s.error)]}):null,(0,I.jsxs)(C,{title:`提供商`,aside:(0,I.jsxs)(`span`,{className:`panel-aside-row`,children:[$>0?(0,I.jsxs)(w,{tone:`live`,dot:!0,children:[`已启用 `,$,` 家`]}):null,(0,I.jsx)(m,{content:`选一个客户端(WorkBuddy / MiniMax Code / Qoder CN),用它的官方授权导入凭证`,children:(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>l(!0),children:[(0,I.jsx)(r,{size:14}),`导入客户端`]})})]}),padded:!1,children:[t.initial&&t.loading?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(A,{})}):t.error?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(T,{kind:`error`,title:`提供商列表没取到`,detail:E(t.error),onRetry:t.reload})}):V.length===0?(0,I.jsx)(`div`,{style:{padding:`0 20px 18px`},children:(0,I.jsx)(T,{kind:`empty`,title:`还没有任何提供商`,detail:`先登记一个上游,再按需启用它的出站路由。`})}):(0,I.jsx)(L,{rows:V,byProvider:H,busyId:f,onEdit:y,onSignin:e=>void Y(e),onGrowthRun:e=>void X(e),onOauthImport:e=>void Z(e),onModels:e=>{P(null),i(e)},onActivate:e=>void G(e),onDeactivate:e=>void q(e),onDelete:D}),(0,I.jsxs)(`div`,{className:`footer-note`,style:{padding:`0 20px 18px`},children:[`启用提供商会让它加入出站路由,并同步它自己的模型清单;已启用的提供商可以并行生效,模型按各自目录路由。 三家凭证都来自各客户端的`,(0,I.jsx)(`b`,{children:`官方授权`}),`(用户无需也无法手填)——点「重新导入授权」即可换账号或刷新令牌; 令牌只存在服务端,任何接口都读不回完整值。`,(0,I.jsx)(`br`,{}),`行内三列用量按`,(0,I.jsx)(`b`,{children:`提供商`}),`归属:由「模型 id → 生效目录的提供商」解析(请求记录本身没有 provider 维度)。模型不属于任何生效目录的行归到「未归属」,不冒充某一家。`]})]}),c?(0,I.jsx)(U,{onClose:()=>{l(!1),W()},onImported:()=>W()}):null,u?(0,I.jsx)(U,{provider:{id:u.id,name:u.name,protocol:u.protocol},onClose:()=>{d(null),W()},onImported:()=>W()}):null,v?(0,I.jsx)(J,{provider:v,onClose:()=>y(null),onSaved:()=>{y(null),W()},onReimport:()=>{let e=v;y(null),e&&d(e)}}):null,n?(0,I.jsx)(N,{title:`模型列表 · ${n.name}`,subtitle:n.baseUrl,onClose:()=>i(null),children:(0,I.jsx)(`div`,{className:`drawer-body`,children:(0,I.jsx)(K,{catalog:o.data,catalogLoading:R,catalogMsg:M,catalogError:o.error,onCatalogRefresh:()=>void ee(),onCatalogReload:o.reload,onModelToggled:te})})}):null,S?(0,I.jsx)(k,{title:`删除这个提供商?`,onClose:()=>{f||D(null)},footer:(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:!!f,onClick:()=>D(null),children:`取消(保留)`}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-danger btn-sm`,disabled:!!f,onClick:()=>void Q(),children:[f?(0,I.jsx)(a,{size:14,className:`spin`}):null,f?`删除中…`:`确认删除`]})]}),children:(0,I.jsxs)(`div`,{className:`ov-lose`,children:[(0,I.jsx)(`b`,{children:`会失去什么:`}),`提供商「`,S.name,`」(`,S.baseUrl,`)的登记与它的凭证文件一并删除, 需要重新登记并填一次 ck_ 密钥。此操作不可撤销。`]})}):null]})}function J({provider:e,onClose:t,onSaved:i,onReimport:o}){let[s,c]=(0,F.useState)(e.name),[l,u]=(0,F.useState)(e.baseUrl),[d,f]=(0,F.useState)(!1),[g,_]=(0,F.useState)(!1),[v,y]=(0,F.useState)(null),[x,S]=(0,F.useState)(null),[C,w]=(0,F.useState)(!1),[T,D]=(0,F.useState)(null),O=async()=>{if(!C){w(!0),D(null),y(null);try{let t=await h.providerImportLocal(e.id);if(t.ok)D({kind:`ok`,text:t.message+(t.detail?`\n${t.detail}`:``)}),i();else if(t.outcome===`unavailable`){let t=e.protocol===`qoder`?`qoder`:`minimax`;D({kind:`unavailable`,text:`本服务所在机器上读不到客户端登录态(远程部署的正常情形)。`,command:`node tools/import-credential-push.mjs ${location.origin} ${t} --provider ${e.id}`})}else D({kind:`error`,text:t.message+(t.detail?`\n${t.detail}`:``)})}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{w(!1)}}},k=async t=>{if(t.preventDefault(),!d){f(!0),y(null);try{let t={};s.trim()!==e.name&&(t.name=s.trim()),l.trim()!==e.baseUrl&&(t.baseUrl=l.trim()),Object.keys(t).length>0&&await h.patchProvider(e.id,t),i()}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{f(!1)}}},A=async()=>{if(!g){_(!0),y(null),S(null);try{S(await h.testProvider(e.id,void 0))}catch(e){y(e instanceof p?e:new p(String(e),0,0))}finally{_(!1)}}};return(0,I.jsx)(N,{title:`配置提供商 · ${e.name}`,subtitle:e.baseUrl,onClose:t,children:(0,I.jsxs)(`div`,{className:`drawer-body`,children:[v?(0,I.jsx)(b,{tone:`error`,children:E(v)}):null,(0,I.jsxs)(`form`,{onSubmit:e=>void k(e),className:`prov-edit`,style:{marginTop:6},children:[(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`名称`}),(0,I.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},value:s,onChange:e=>c(e.target.value),"aria-label":`提供商名称`})]}),(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`地址`}),(0,I.jsx)(`input`,{className:`field-input text`,style:{width:`100%`},value:l,placeholder:`https://api.example.com`,onChange:e=>u(e.target.value),"aria-label":`提供商地址`})]}),(0,I.jsxs)(`div`,{className:`up-row`,children:[(0,I.jsx)(`div`,{className:`up-lab`,children:`凭证`}),(0,I.jsxs)(`div`,{className:`cred-guide`,children:[(0,I.jsx)(`div`,{className:`cred-guide-note`,children:e.protocol===`openai`?(0,I.jsxs)(I.Fragment,{children:[`凭证来自 WorkBuddy `,(0,I.jsx)(`b`,{children:`官方 OAuth 授权`}),`(桌面端 / 插件同一条通道),无需手填。`]}):e.protocol===`anthropic`?(0,I.jsxs)(I.Fragment,{children:[`凭证是客户端登录态里的 `,(0,I.jsx)(`span`,{className:`code-chip`,children:`mmort_…`}),` refresh token。 请用`,(0,I.jsx)(`b`,{children:`专用账号`}),`(与桌面端共用会互相顶下线)。`]}):(0,I.jsxs)(I.Fragment,{children:[`凭证来自 Qoder 客户端登录态(一段`,(0,I.jsx)(`b`,{children:`身份 JSON`}),`)。桌面客户端与它`,(0,I.jsx)(`b`,{children:`互不影响`}),`。`]})}),(0,I.jsxs)(`div`,{className:`cred-guide-actions`,children:[(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,disabled:d,onClick:()=>o(),children:[(0,I.jsx)(r,{size:14}),`重新导入授权`]}),(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:C,onClick:()=>void O(),children:[C?(0,I.jsx)(a,{size:14}):(0,I.jsx)(r,{size:14}),`从本机客户端读取`]})]}),T?(0,I.jsx)(b,{tone:T.kind===`ok`?`success`:T.kind===`unavailable`?`warn`:`error`,children:(0,I.jsxs)(`span`,{children:[T.text,T.command?(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`br`,{}),`在`,(0,I.jsx)(`b`,{children:`装了客户端的那台机器`}),`的仓库目录下跑:`,(0,I.jsx)(R,{command:T.command})]}):null]})}):null,e.protocol===`qoder`?(0,I.jsxs)(`div`,{className:`cred-guide-note`,children:[`本服务`,(0,I.jsx)(`b`,{children:`不自动刷新`}),` Qoder 令牌;过期后重跑一次导入即可。`]}):null]})]}),(0,I.jsxs)(`div`,{className:`up-actions`,children:[(0,I.jsxs)(`button`,{type:`submit`,className:`btn btn-primary btn-sm`,disabled:d,children:[d?(0,I.jsx)(a,{size:14}):(0,I.jsx)(n,{size:14}),`保存`]}),(0,I.jsx)(m,{content:`用已导入的授权测这家上游是否还通`,children:(0,I.jsxs)(`button`,{type:`button`,className:`btn btn-quiet btn-sm`,disabled:g,onClick:()=>void A(),children:[g?(0,I.jsx)(a,{size:14}):null,`测试`]})})]})]}),x?(0,I.jsx)(b,{tone:x.ok?`success`:`warn`,children:(0,I.jsx)(`span`,{children:x.ok?(0,I.jsxs)(I.Fragment,{children:[`通了:`,e.name,` 接受了这次 1 token 探测(`,x.elapsedMs,`ms)。`]}):x.httpStatus===null?(0,I.jsxs)(I.Fragment,{children:[`没连上(网络层):`,x.reason??`未知原因`,`。先查地址与出网,再看凭证。`]}):(0,I.jsxs)(I.Fragment,{children:[`连上了但被拒(HTTP `,x.httpStatus,x.upstreamCode===null?``:` · 业务码 ${x.upstreamCode}`,`)。`,x.upstreamCode===11102?`业务码 11102 = 账号没订阅这个模型。key 可能是好的,换个模型再测或直接切换。`:`通常是凭证问题;换一把再测。`]})})}):null,(0,I.jsx)(`div`,{className:`up-note`,style:{marginTop:18},children:(0,I.jsxs)(`span`,{children:[`凭证只存在`,(0,I.jsx)(`b`,{children:`服务端`}),`:界面只显示账号摘要或掩码,任何接口都读不回完整值。`]})})]})})}function Y(){let[e,t]=(0,F.useState)(()=>M()??`today`);return(0,I.jsxs)(`div`,{className:`view on`,children:[(0,I.jsx)(D,{title:`提供商管理`,actions:(0,I.jsx)(O,{value:e,options:v,onChange:e=>{j(e),t(e)},width:148})}),(0,I.jsx)(`div`,{className:`view-body stack`,children:(0,I.jsx)(q,{window:e})})]})}export{Y as ClientsPage};
@@ -1,4 +1,4 @@
1
- import{b as e,it as t,t as n,tt as r}from"./vendor-befEYGHM.js";import{c as i,i as a,n as o,o as s,s as c,x as l}from"./index-C-pZwUmz.js";import{t as u}from"./accessUrl-DbL5DLDz.js";var d=t(),f=`1.6.19`,p={totalTokens:193e4,cacheHitRate:62.4,requestsToday:1284,successRate:99.7};function m(e,t=0){let n=e.toFixed(t).split(`.`);return n[0]=n[0].replace(/\B(?=(\d{3})+(?!\d))/g,`,`),n.join(`.`)}function h(e,t=1){return e==null?`—`:`${e.toFixed(t)}%`}function g(e){if(e&&typeof e==`object`){let t=e;return{ok:t.ok!==!1,version:typeof t.version==`string`?t.version:void 0}}return{ok:!0}}function _(){let e=c(),[t,n]=(0,d.useState)(!0),[r,a]=(0,d.useState)(f),[o,s]=(0,d.useState)(p),[l,u]=(0,d.useState)(!1);return(0,d.useEffect)(()=>{let e=!0,t=async()=>{try{let t=await fetch(`/healthz`,{headers:{Accept:`application/json`}});if(!t.ok)throw Error(`healthz ${t.status}`);let r=g(await t.json());if(!e)return;n(r.ok),r.version&&a(r.version)}catch{e&&n(!1)}};t();let r=window.setInterval(()=>void t(),3e4);return()=>{e=!1,window.clearInterval(r)}},[]),(0,d.useEffect)(()=>{if(e.status!==`authed`){u(!1),s(p);return}let t=!0;return i.overview(`24h`).then(e=>{t&&(s({totalTokens:e.metrics.totalTokens,cacheHitRate:e.metrics.cacheHitRate*100,requestsToday:e.metrics.requests,successRate:e.metrics.successRate*100}),u(!0))}).catch(()=>{t&&(u(!1),s(p))}),()=>{t=!1}},[e.status]),{online:t,version:r,real:l,numbers:o}}function v(){var e=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,t=!0,n=[],r=[],i={totalTokens:`本窗口 tokens`,cacheHitRate:`缓存命中率`,requestsToday:`今日请求`,successRate:`今日成功率`};function a(){var e=window.RelayDemo;if(e&&e.summary){var t=[];[].forEach.call(document.querySelectorAll(`[data-demo]`),function(n){var r=n.getAttribute(`data-demo`),a=e.summary[r];if(typeof a==`number`){var o=parseInt(n.getAttribute(`data-dec`)||`0`,10),s=n.textContent.trim().replace(/,/g,``);s!==``&&Math.abs(parseFloat(s)-a)>10**-o/2&&t.push((i[r]||r)+`:页面上写的 `+s+`,数据层是 `+a),n.setAttribute(`data-count`,a),n.textContent=p(a,o)}}),t.length&&window.console&&console.warn(`[page] 首页演示数字与 demo-data 不一致(已按数据层显示):
1
+ import{b as e,it as t,t as n,tt as r}from"./vendor-befEYGHM.js";import{c as i,i as a,n as o,o as s,s as c,x as l}from"./index-BMxTTq4E.js";import{t as u}from"./accessUrl-DbL5DLDz.js";var d=t(),f=`1.6.21`,p={totalTokens:193e4,cacheHitRate:62.4,requestsToday:1284,successRate:99.7};function m(e,t=0){let n=e.toFixed(t).split(`.`);return n[0]=n[0].replace(/\B(?=(\d{3})+(?!\d))/g,`,`),n.join(`.`)}function h(e,t=1){return e==null?`—`:`${e.toFixed(t)}%`}function g(e){if(e&&typeof e==`object`){let t=e;return{ok:t.ok!==!1,version:typeof t.version==`string`?t.version:void 0}}return{ok:!0}}function _(){let e=c(),[t,n]=(0,d.useState)(!0),[r,a]=(0,d.useState)(f),[o,s]=(0,d.useState)(p),[l,u]=(0,d.useState)(!1);return(0,d.useEffect)(()=>{let e=!0,t=async()=>{try{let t=await fetch(`/healthz`,{headers:{Accept:`application/json`}});if(!t.ok)throw Error(`healthz ${t.status}`);let r=g(await t.json());if(!e)return;n(r.ok),r.version&&a(r.version)}catch{e&&n(!1)}};t();let r=window.setInterval(()=>void t(),3e4);return()=>{e=!1,window.clearInterval(r)}},[]),(0,d.useEffect)(()=>{if(e.status!==`authed`){u(!1),s(p);return}let t=!0;return i.overview(`24h`).then(e=>{t&&(s({totalTokens:e.metrics.totalTokens,cacheHitRate:e.metrics.cacheHitRate*100,requestsToday:e.metrics.requests,successRate:e.metrics.successRate*100}),u(!0))}).catch(()=>{t&&(u(!1),s(p))}),()=>{t=!1}},[e.status]),{online:t,version:r,real:l,numbers:o}}function v(){var e=window.matchMedia(`(prefers-reduced-motion: reduce)`).matches,t=!0,n=[],r=[],i={totalTokens:`本窗口 tokens`,cacheHitRate:`缓存命中率`,requestsToday:`今日请求`,successRate:`今日成功率`};function a(){var e=window.RelayDemo;if(e&&e.summary){var t=[];[].forEach.call(document.querySelectorAll(`[data-demo]`),function(n){var r=n.getAttribute(`data-demo`),a=e.summary[r];if(typeof a==`number`){var o=parseInt(n.getAttribute(`data-dec`)||`0`,10),s=n.textContent.trim().replace(/,/g,``);s!==``&&Math.abs(parseFloat(s)-a)>10**-o/2&&t.push((i[r]||r)+`:页面上写的 `+s+`,数据层是 `+a),n.setAttribute(`data-count`,a),n.textContent=p(a,o)}}),t.length&&window.console&&console.warn(`[page] 首页演示数字与 demo-data 不一致(已按数据层显示):
2
2
  · `+t.join(`
3
3
  · `))}}function o(e){var t=!1;return function(){t||(t=!0,requestAnimationFrame(function(){t=!1,e()}))}}function s(){var e=[].slice.call(document.querySelectorAll(`.reveal`));if(e.length){if(!(`IntersectionObserver`in window)){e.forEach(function(e){e.classList.add(`in`)});return}var t=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(e.target.classList.add(`in`),t.unobserve(e.target))})},{rootMargin:`0px 0px -10% 0px`,threshold:.1});r.push(t),e.forEach(function(e){t.observe(e)})}}var c=document.querySelector(`.nav`);function l(){c&&c.classList.toggle(`stuck`,window.pageYOffset>28)}var u=[].slice.call(document.querySelectorAll(`.rail a`)),d=u.map(function(e){return document.querySelector(e.getAttribute(`href`))});function f(){if(u.length){for(var e=window.pageYOffset+window.innerHeight*.42,t=0,n=0;n<d.length;n+=1){var r=d[n];r&&r.offsetTop<=e&&(t=n)}for(var i=0;i<u.length;i+=1)u[i].classList.toggle(`on`,i===t)}}function p(e,t){var n=e.toFixed(t).split(`.`);return n[0]=n[0].replace(/\B(?=(\d{3})+(?!\d))/g,`,`),n.join(`.`)}function m(e){var n=parseFloat(e.getAttribute(`data-count`));if(isNaN(n))return;var r=parseInt(e.getAttribute(`data-dec`)||`0`,10),i=1500,a=0;function o(s){if(t){a||=s;var c=Math.min(1,(s-a)/i);e.textContent=p(n*(c===1?1:1-2**(-10*c)),r),c<1?requestAnimationFrame(o):e.textContent=p(n,r)}}requestAnimationFrame(o)}function h(){var t=[].slice.call(document.querySelectorAll(`[data-count]`));if(t.length){if(e||!(`IntersectionObserver`in window)){t.forEach(function(e){var t=parseFloat(e.getAttribute(`data-count`)),n=parseInt(e.getAttribute(`data-dec`)||`0`,10);isNaN(t)||(e.textContent=p(t,n))});return}var n=new IntersectionObserver(function(e){e.forEach(function(e){e.isIntersecting&&(m(e.target),n.unobserve(e.target))})},{threshold:.6});r.push(n),t.forEach(function(e){n.observe(e)})}}function g(){var i=[].slice.call(document.querySelectorAll(`[data-type]`));if(i.length&&!e&&`IntersectionObserver`in window){var a=new IntersectionObserver(function(e){e.forEach(function(e){if(e.isIntersecting){var r=e.target;a.unobserve(r);var i=r.getAttribute(`data-type`),o=0;r.textContent=``,r.classList.add(`typing`),(function e(){if(t){if(o>i.length){r.classList.remove(`typing`);return}r.textContent=i.slice(0,o),o+=1,n.push(window.setTimeout(e,34))}})()}})},{threshold:.8});r.push(a),i.forEach(function(e){a.observe(e)})}}var _=o(function(){l(),f()}),v=o(function(){f()}),y=function(){l(),f()};return a(),s(),h(),g(),l(),f(),window.addEventListener(`scroll`,_,{passive:!0}),window.addEventListener(`resize`,v),window.addEventListener(`load`,y),function(){t=!1,window.removeEventListener(`scroll`,_),window.removeEventListener(`resize`,v),window.removeEventListener(`load`,y),r.forEach(function(e){e.disconnect()}),n.forEach(function(e){clearTimeout(e)}),r=[],n=[]}}var y=r(),b=[{href:`#top`,label:`概览`},{href:`#ways`,label:`运行方式`},{href:`#quickstart`,label:`快速开始`},{href:`#clients`,label:`客户端配置`},{href:`#status`,label:`运行状态`},{href:`#cta`,label:`开始使用`}];function x(){return(0,y.jsx)(`nav`,{className:`rail`,"aria-label":`章节`,children:b.map(e=>(0,y.jsxs)(`a`,{href:e.href,children:[(0,y.jsx)(`i`,{}),(0,y.jsx)(`b`,{children:e.label})]},e.href))})}function S({version:t}){let[r,i]=(0,d.useState)(!1),o=()=>i(!1);return(0,y.jsxs)(`nav`,{className:`nav${r?` menu-open`:``}`,children:[(0,y.jsxs)(`a`,{className:`brand`,href:`#top`,onClick:o,children:[(0,y.jsx)(s,{size:28,id:`relay-mark-nav`}),(0,y.jsx)(`b`,{children:`途关`}),(0,y.jsx)(`span`,{children:`Waygate`})]}),(0,y.jsxs)(`div`,{className:`nav-links`,children:[(0,y.jsx)(`a`,{href:`#ways`,onClick:o,children:`运行方式`}),(0,y.jsx)(`a`,{href:`#quickstart`,onClick:o,children:`快速开始`}),(0,y.jsx)(`a`,{href:`#clients`,onClick:o,children:`客户端配置`}),(0,y.jsx)(`a`,{href:`#status`,onClick:o,children:`运行状态`})]}),(0,y.jsxs)(`div`,{className:`nav-right`,style:{display:`flex`,alignItems:`center`,gap:14,marginLeft:`auto`},children:[(0,y.jsxs)(`span`,{className:`pill pill-brand`,children:[`v`,t]}),(0,y.jsx)(`a`,{className:`btn btn-primary btn-sm`,href:a(),children:`打开控制台`}),(0,y.jsx)(`button`,{type:`button`,className:`nav-menu-toggle`,"aria-label":r?`关闭导航菜单`:`打开导航菜单`,"aria-controls":`landing-mobile-nav`,"aria-expanded":r,onClick:()=>i(e=>!e),children:r?(0,y.jsx)(n,{size:18,"aria-hidden":`true`}):(0,y.jsx)(e,{size:18,"aria-hidden":`true`})})]}),(0,y.jsxs)(`div`,{id:`landing-mobile-nav`,className:`mobile-nav`,"aria-hidden":!r,children:[(0,y.jsx)(`a`,{href:`#ways`,onClick:o,children:`运行方式`}),(0,y.jsx)(`a`,{href:`#quickstart`,onClick:o,children:`快速开始`}),(0,y.jsx)(`a`,{href:`#clients`,onClick:o,children:`客户端配置`}),(0,y.jsx)(`a`,{href:`#status`,onClick:o,children:`运行状态`})]})]})}function C(e,t=0,n){if(e==null)return{children:`—`};let r={"data-count":e,children:m(e,t)};return t&&(r[`data-dec`]=t),n&&(r[`data-demo`]=n),r}var w={live:`var(--signal-live)`,offline:`var(--warn-amber)`};function T(e=window.location.hostname){return/^\d{1,3}(\.\d{1,3}){3}$/.test(e)||e.includes(`:`)}function E(e){return T(e)?`自签证书`:`公共 CA`}var D=e=>e;function O({text:e,className:t,label:n,idle:r=`复制`,done:i=`已复制`}){let[a,o]=(0,d.useState)(!1),s=(0,d.useRef)(null);(0,d.useEffect)(()=>()=>{s.current&&window.clearTimeout(s.current)},[]);let c=(0,d.useCallback)(()=>{let t=navigator.clipboard;t&&typeof t.writeText==`function`&&t.writeText(e).then(()=>{o(!0),s.current&&window.clearTimeout(s.current),s.current=window.setTimeout(()=>o(!1),1200)},()=>void 0)},[e]);return(0,y.jsx)(`button`,{className:t,type:`button`,"aria-label":n,onClick:c,children:a?i:r})}var k=`{"x":0,"y":0,"z":0,"rx":0,"ry":0,"rz":0,"fog":0.16,"dim":0.90}`,A=[`# 克隆并构建`,`git clone <仓库地址> && cd relay-station`,`npm ci --prefix server && npm run build --prefix server`,`npm ci --prefix web && npm run build --prefix web`,``,`# 启动 systemd 服务(TLS 由 nginx 终止)`,`sudo systemctl start relay`].join(`
4
4
  `);function j({status:e}){let{online:t,real:n,numbers:r}=e,i=u(),a=r.totalTokens==null?`—`:l(r.totalTokens),o=t?void 0:D({color:`var(--warn-amber)`,background:`color-mix(in srgb, var(--warn-amber) 7%, transparent)`,borderColor:`var(--warn-amber-line)`});return(0,y.jsxs)(`header`,{className:`hero`,id:`top`,"data-cam":k,children:[(0,y.jsxs)(`div`,{className:`wrap`,children:[(0,y.jsxs)(`div`,{className:`hero-grid`,children:[(0,y.jsxs)(`div`,{className:`hero-inner`,children:[(0,y.jsxs)(`div`,{className:`hero-badge reveal`,style:o,children:[(0,y.jsx)(`i`,{className:t?`dot dot-live`:`dot`,style:{color:t?w.live:w.offline}}),t?`服务运行中`:`服务离线`,` · 本窗口 tokens`,` `,(0,y.jsx)(`span`,{className:`n`,children:a}),n?null:`(示例)`]}),(0,y.jsxs)(`div`,{className:`hero-title reveal`,children:[(0,y.jsxs)(`h1`,{className:`hero-h1 display-1`,children:[(0,y.jsx)(`span`,{className:`rv-line`,children:(0,y.jsx)(`span`,{style:D({"--i":0}),children:`把自己的订阅积分`})}),(0,y.jsx)(`span`,{className:`rv-line`,children:(0,y.jsxs)(`span`,{style:D({"--i":1}),children:[`变成一个 `,(0,y.jsx)(`span`,{className:`mk grad-text`,children:`base_url`})]})})]}),(0,y.jsx)(`div`,{className:`hero-flip`,"aria-hidden":`true`,children:(0,y.jsxs)(`h1`,{className:`hero-h1 display-1`,children:[`把自己的订阅积分`,(0,y.jsx)(`br`,{}),`变成一个 `,(0,y.jsx)(`span`,{className:`mk grad-text`,children:`base_url`})]})})]}),(0,y.jsx)(`p`,{className:`hero-sub reveal`,style:D({"--i":3}),children:`部署在你自己机器上,把你的 AI 订阅积分(WorkBuddy / MiniMax / Qoder)暴露成 OpenAI 兼容接口。 IDE、聊天客户端、手机、脚本共用同一个 Key,积分消耗实时可见。`}),(0,y.jsxs)(`div`,{className:`hero-acts reveal`,style:D({"--i":4}),children:[(0,y.jsx)(O,{className:`btn btn-primary`,text:A,label:`复制部署命令`,idle:`复制部署命令`}),(0,y.jsx)(`a`,{className:`btn btn-ghost`,href:`#clients`,children:`查看客户端配置`})]}),(0,y.jsxs)(`div`,{className:`trust reveal`,style:D({"--i":5}),children:[(0,y.jsx)(`span`,{children:`只监听本机回环`}),(0,y.jsx)(`span`,{children:`外发直连上游,无中转`}),(0,y.jsx)(`span`,{children:`请求日志保存在本地`})]})]}),(0,y.jsx)(`aside`,{className:`hero-side reveal`,style:D({"--i":2}),children:(0,y.jsxs)(`div`,{className:`glass access`,children:[(0,y.jsxs)(`div`,{className:`access-h`,children:[(0,y.jsx)(`span`,{className:`eyebrow`,style:D({"--c":`var(--text-ghost)`}),children:`接入地址`}),(0,y.jsxs)(`span`,{className:t?`pill pill-live`:`pill pill-amber`,children:[(0,y.jsx)(`i`,{className:t?`dot dot-live`:`dot`}),t?`可用`:`离线`]})]}),(0,y.jsxs)(`div`,{className:`access-url`,children:[(0,y.jsx)(`code`,{children:i}),(0,y.jsx)(O,{className:`copy`,text:i,label:`复制接入地址`})]}),(0,y.jsx)(`div`,{className:`access-note`,children:`把它填进任何支持 OpenAI 兼容协议的工具`}),(0,y.jsxs)(`dl`,{className:`kv`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`dt`,{children:`协议`}),(0,y.jsx)(`dd`,{children:`OpenAI v1 · Anthropic`})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`dt`,{children:`日志保留`}),(0,y.jsx)(`dd`,{children:`默认 30 天`})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`dt`,{children:`TLS`}),(0,y.jsx)(`dd`,{children:E()})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`dt`,{children:`部署形态`}),(0,y.jsx)(`dd`,{children:`systemd + nginx`})]})]})]})})]}),(0,y.jsxs)(`div`,{className:`panel-wrap glass reveal`,style:D({"--i":6}),children:[(0,y.jsxs)(`div`,{className:`panel-left`,children:[(0,y.jsxs)(`div`,{className:`winbar`,children:[(0,y.jsx)(`i`,{style:{background:`var(--danger-red)`}}),(0,y.jsx)(`i`,{style:{background:`var(--warn-amber)`}}),(0,y.jsx)(`i`,{style:{background:`var(--signal-live)`}}),(0,y.jsx)(`span`,{className:`fname`,children:`deploy.sh`}),(0,y.jsx)(`span`,{className:`wtag`,children:`SHELL`}),(0,y.jsx)(`span`,{className:`wsp`}),(0,y.jsx)(O,{className:`wbtn`,text:A})]}),(0,y.jsxs)(`div`,{className:`panel-code`,children:[(0,y.jsxs)(`div`,{className:`cl`,children:[(0,y.jsx)(`i`,{children:`1`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`c`,children:`# Ubuntu + systemd(TLS 由 nginx 终止)`})})]}),(0,y.jsxs)(`div`,{className:`cl`,children:[(0,y.jsx)(`i`,{children:`2`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`fn`,children:`$ sudo systemctl start relay`})})]}),(0,y.jsxs)(`div`,{className:`cl`,children:[(0,y.jsx)(`i`,{children:`3`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`fn`,children:`$ sudo systemctl status relay`})})]}),(0,y.jsxs)(`div`,{className:`cl`,children:[(0,y.jsx)(`i`,{children:`4`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`fn`,children:`$ sudo systemctl restart relay`})})]}),(0,y.jsxs)(`div`,{className:`cl hl`,children:[(0,y.jsx)(`i`,{children:`5`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`c`,children:`# 首次启动打印管理口令(仅此一次)`})})]}),(0,y.jsxs)(`div`,{className:`cl`,children:[(0,y.jsx)(`i`,{children:`6`}),(0,y.jsx)(`span`,{children:(0,y.jsx)(`span`,{className:`c`,children:`# API Key 在控制台「客户端与密钥」页新建`})})]})]})]}),(0,y.jsxs)(`div`,{className:`panel-right`,children:[(0,y.jsxs)(`div`,{className:`pr-head`,children:[(0,y.jsx)(`h4`,{children:`连接状态`}),(0,y.jsx)(`span`,{className:`pr-count`,children:t?`示例 · 3 个客户端`:`服务离线`})]}),(0,y.jsxs)(`div`,{className:`conn`,children:[(0,y.jsx)(`i`,{className:`dot dot-live`,style:{color:w.live}}),(0,y.jsx)(`span`,{className:`cname`,children:`Claude Code`}),(0,y.jsx)(`span`,{className:`dev`,children:`本机`}),(0,y.jsx)(`span`,{className:`lat`,children:`12ms`}),(0,y.jsx)(`span`,{className:`pill pill-live`,children:`在线`})]}),(0,y.jsxs)(`div`,{className:`conn`,children:[(0,y.jsx)(`i`,{className:`dot dot-live`,style:{color:w.live}}),(0,y.jsx)(`span`,{className:`cname`,children:`Cherry Studio`}),(0,y.jsx)(`span`,{className:`dev`,children:`桌面`}),(0,y.jsx)(`span`,{className:`lat`,children:`48ms`}),(0,y.jsx)(`span`,{className:`pill pill-live`,children:`在线`})]}),(0,y.jsxs)(`div`,{className:`conn`,children:[(0,y.jsx)(`i`,{className:`dot`,style:{color:w.offline}}),(0,y.jsx)(`span`,{className:`cname`,children:`Cursor`}),(0,y.jsx)(`span`,{className:`dev`,children:`笔记本`}),(0,y.jsx)(`span`,{className:`lat`,children:`—`}),(0,y.jsx)(`span`,{className:`pill pill-amber`,children:`待机`})]}),(0,y.jsxs)(`div`,{className:`panel-metrics`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`div`,{className:`k`,children:`本窗口 tokens`}),(0,y.jsx)(`div`,{className:`v num live`,style:{color:w.live},children:a})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`div`,{className:`k`,children:`缓存命中率`}),(0,y.jsx)(`div`,{className:`v num`,children:h(r.cacheHitRate)})]}),(0,y.jsxs)(`div`,{children:[(0,y.jsx)(`div`,{className:`k`,children:`今日请求`}),(0,y.jsx)(`div`,{className:`v num`,children:r.requestsToday==null?`—`:m(r.requestsToday)})]})]})]})]})]}),(0,y.jsxs)(`div`,{className:`scroll-cue`,children:[(0,y.jsx)(`span`,{className:`tick`}),`SCROLL`]})]})}var M=`{"x":-26,"y":-16,"z":-150,"rx":2.5,"ry":3,"rz":0,"fog":0.30,"dim":0.44}`;function N(){return(0,y.jsx)(`section`,{className:`strip`,"data-cam":M,children:(0,y.jsxs)(`div`,{className:`wrap reveal`,children:[(0,y.jsxs)(`div`,{className:`cap`,children:[`任何支持 `,(0,y.jsx)(`b`,{children:`OpenAI 兼容协议`}),` 的工具,填上地址就能用`]}),(0,y.jsxs)(`div`,{className:`logos`,children:[(0,y.jsx)(`span`,{children:`Claude Code`}),(0,y.jsx)(`span`,{children:`Cursor`}),(0,y.jsx)(`span`,{children:`Cherry Studio`}),(0,y.jsx)(`span`,{children:`ChatBox`}),(0,y.jsx)(`span`,{children:`LobeChat`}),(0,y.jsx)(`span`,{children:`Continue`}),(0,y.jsx)(`span`,{className:`cn`,children:`你的脚本`})]})]})})}var P=`{"x":-118,"y":-38,"z":-310,"rx":5,"ry":7,"rz":-1.5,"fog":0.44,"dim":0.40}`,F={color:`var(--accent-cyan)`},I={fill:`none`,stroke:`currentColor`,strokeWidth:1.6};function L(){return(0,y.jsx)(`section`,{className:`section`,id:`ways`,"data-cam":P,children:(0,y.jsx)(`div`,{className:`wrap`,children:(0,y.jsxs)(`div`,{className:`split`,children:[(0,y.jsxs)(`div`,{className:`split-l reveal`,children:[(0,y.jsxs)(`div`,{className:`eyebrow`,children:[(0,y.jsx)(`span`,{className:`idx`,children:`02`}),`运行方式`]}),(0,y.jsxs)(`h2`,{className:`display-2`,children:[`一个进程,`,(0,y.jsx)(`br`,{}),`管住所有客户端的积分`]}),(0,y.jsx)(`p`,{children:`不用为每个客户端单独登录、单独算积分。服务跑在你自己的机器上,所有请求走同一个出口。`})]}),(0,y.jsxs)(`ul`,{className:`linelist`,children:[(0,y.jsxs)(`li`,{className:`reveal`,children:[(0,y.jsx)(`span`,{className:`ll-ico`,children:(0,y.jsxs)(`svg`,{width:`20`,height:`20`,viewBox:`0 0 20 20`,"aria-hidden":`true`,style:F,...I,children:[(0,y.jsx)(`circle`,{cx:`6.5`,cy:`10`,r:`3.5`}),(0,y.jsx)(`path`,{d:`M10 10h8M15 10v3M17.5 10v3`,strokeLinecap:`round`})]})}),(0,y.jsxs)(`span`,{className:`ll-tx`,children:[(0,y.jsx)(`b`,{children:`一个 Key,多端共用`}),(0,y.jsx)(`i`,{children:`桌面客户端、IDE、手机、脚本共用同一个密钥,控制台按客户端拆开看用量。`})]})]}),(0,y.jsxs)(`li`,{className:`reveal`,style:D({"--i":1}),children:[(0,y.jsx)(`span`,{className:`ll-ico`,children:(0,y.jsxs)(`svg`,{width:`20`,height:`20`,viewBox:`0 0 20 20`,"aria-hidden":`true`,style:F,...I,children:[(0,y.jsx)(`path`,{d:`M4 10h4M12 10h4`,strokeLinecap:`round`}),(0,y.jsx)(`circle`,{cx:`10`,cy:`10`,r:`2`}),(0,y.jsx)(`path`,{d:`M12 10V5.5M12 10v4.5`,strokeLinecap:`round`})]})}),(0,y.jsxs)(`span`,{className:`ll-tx`,children:[(0,y.jsx)(`b`,{children:`请求只在你自己的机器上流转`}),(0,y.jsx)(`i`,{children:`应用默认只监听本机回环,对外由你自己的 nginx 暴露,没有第三方中转。`})]})]}),(0,y.jsxs)(`li`,{className:`reveal`,style:D({"--i":2}),children:[(0,y.jsx)(`span`,{className:`ll-ico`,children:(0,y.jsxs)(`svg`,{width:`20`,height:`20`,viewBox:`0 0 20 20`,"aria-hidden":`true`,style:F,...I,children:[(0,y.jsx)(`rect`,{x:`2`,y:`4.5`,width:`16`,height:`11`,rx:`2.5`}),(0,y.jsx)(`path`,{d:`M2 8.5h16`}),(0,y.jsx)(`path`,{d:`M4.5 13h4`,strokeLinecap:`round`})]})}),(0,y.jsxs)(`span`,{className:`ll-tx`,children:[(0,y.jsx)(`b`,{children:`用量与消耗实时同步`}),(0,y.jsx)(`i`,{children:`本窗口总 tokens、缓存命中率、每条请求消耗多少积分,控制台里一眼看清。`})]})]}),(0,y.jsxs)(`li`,{className:`reveal`,style:D({"--i":3}),children:[(0,y.jsx)(`span`,{className:`ll-ico`,children:(0,y.jsx)(`svg`,{width:`20`,height:`20`,viewBox:`0 0 20 20`,"aria-hidden":`true`,style:F,...I,children:(0,y.jsx)(`path`,{d:`M10 2.5 16.5 5v5c0 4-2.8 6.4-6.5 7.5C6.3 16.4 3.5 14 3.5 10V5L10 2.5Z`,strokeLinejoin:`round`})})}),(0,y.jsxs)(`span`,{className:`ll-tx`,children:[(0,y.jsx)(`b`,{children:`请求日志与超限保护`}),(0,y.jsx)(`i`,{children:`每条请求都落库、可回溯;超过并发上限时直接回 429,失败不会被静默吞掉。`})]})]})]})]})})})}var R=`{"x":58,"y":-70,"z":-480,"rx":10,"ry":-5,"rz":1.5,"fog":0.52,"dim":0.36}`;function z({version:e}){return(0,y.jsx)(`section`,{className:`section`,id:`quickstart`,"data-cam":R,children:(0,y.jsxs)(`div`,{className:`wrap`,children:[(0,y.jsxs)(`div`,{className:`sec-head reveal`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsxs)(`div`,{className:`eyebrow`,children:[(0,y.jsx)(`span`,{className:`idx`,children:`03`}),`快速开始`]}),(0,y.jsx)(`h2`,{className:`display-2`,style:{marginTop:20},children:`三步,十分钟内跑起来`})]}),(0,y.jsx)(`p`,{className:`lead`,children:`自己的一台机器就够。首次启动打印管理口令(只显示一次),客户端的 Key 在控制台新建。`})]}),(0,y.jsxs)(`ol`,{className:`steps`,children:[(0,y.jsxs)(`li`,{className:`reveal`,children:[(0,y.jsx)(`div`,{className:`st-n`,children:`01`}),(0,y.jsx)(`h3`,{children:`部署服务`}),(0,y.jsx)(`p`,{children:`克隆仓库,构建前后端,装成 systemd 服务(nginx 在外层终止 TLS)。首次启动即打印管理口令。`}),(0,y.jsx)(`div`,{className:`cmd`,style:{marginTop:8},children:`git clone … → npm ci → npm run build → systemctl start`})]}),(0,y.jsxs)(`li`,{className:`reveal`,style:D({"--i":1}),children:[(0,y.jsx)(`div`,{className:`st-n`,children:`02`}),(0,y.jsx)(`h3`,{children:`生成密钥`}),(0,y.jsx)(`p`,{children:`在控制台「客户端与密钥」页新建密钥,按客户端命名,可以随时停用或删除。`}),(0,y.jsx)(`div`,{className:`cmd`,children:`控制台 → 客户端与密钥 → 新建密钥`})]}),(0,y.jsxs)(`li`,{className:`reveal`,style:D({"--i":2}),children:[(0,y.jsx)(`div`,{className:`st-n`,children:`03`}),(0,y.jsx)(`h3`,{children:`填进客户端`}),(0,y.jsx)(`p`,{children:`把 base_url 填成控制台的同源地址,模型名照抄,其余代码不用改。`})]})]}),(0,y.jsxs)(`div`,{className:`term reveal`,children:[(0,y.jsxs)(`div`,{className:`term-bar`,children:[(0,y.jsx)(`i`,{className:`dot dot-live`,style:{color:`var(--signal-live)`}}),(0,y.jsx)(`span`,{children:`首次启动输出`}),(0,y.jsxs)(`span`,{className:`term-tag`,children:[`relay · v`,e]})]}),(0,y.jsxs)(`div`,{className:`term-body`,children:[(0,y.jsxs)(`div`,{className:`tl`,children:[(0,y.jsx)(`span`,{className:`pr`,children:`$`}),` sudo systemctl start relay`]}),(0,y.jsx)(`div`,{className:`tl out`,children:`Waygate · 首次启动,已生成管理口令(此口令只显示这一次)`}),(0,y.jsxs)(`div`,{className:`tl out`,children:[`口令:`,(0,y.jsx)(`span`,{className:`hl`,children:`••••••••••••••••`})]}),(0,y.jsx)(`div`,{className:`tl out`,children:`请立即复制保存。忘记口令时:控制台「设置 → 管理口令」重置。`}),(0,y.jsx)(`div`,{className:`tl ok`,children:`Waygate 已在 http://127.0.0.1:8787 就绪(默认仅回环,不对公网直接暴露)`})]})]})]})})}var B=`{"x":148,"y":-94,"z":-630,"rx":6,"ry":-11,"rz":-2.5,"fog":0.60,"dim":0.34}`,V=`sk-waygate-4f9c2a7e1b83d04f391c7a2e`,H=u(),U=u().replace(/\/v1$/,``),W=[{k:`兼容协议`,v:`OpenAI v1 · Anthropic`},{k:`接入方式`,v:`HTTPS · ${E()}`},{k:`统计口径`,v:`本机日志聚合`},{k:`日志存储`,v:`本地`}];function G(){return(0,y.jsx)(`section`,{className:`section`,id:`clients`,"data-cam":B,children:(0,y.jsxs)(`div`,{className:`wrap`,children:[(0,y.jsxs)(`div`,{className:`sec-head reveal`,children:[(0,y.jsxs)(`div`,{children:[(0,y.jsxs)(`div`,{className:`eyebrow`,children:[(0,y.jsx)(`span`,{className:`idx`,children:`04`}),`客户端配置`]}),(0,y.jsx)(`h2`,{className:`display-2`,style:{marginTop:20},children:`四处填好,立刻能用`})]}),(0,y.jsx)(`p`,{className:`lead`,children:`所有客户端都只需要一个地址加一个密钥。下面是最常见的四种接法。`})]}),(0,y.jsxs)(`div`,{className:`grid-2`,children:[(0,y.jsxs)(`div`,{className:`glass glass-soft cfg reveal`,children:[(0,y.jsx)(`h3`,{children:`编程工具 / IDE`}),(0,y.jsx)(`div`,{className:`sub`,children:`Continue · Cline · 任意 OpenAI 兼容插件`}),(0,y.jsxs)(`div`,{className:`code`,children:[`OPENAI_BASE_URL=`,(0,y.jsx)(`span`,{className:`s`,children:H}),`
@@ -1 +1 @@
1
- import{B as e,N as t,_ as n,i as r,it as i,r as a,tt as o}from"./vendor-befEYGHM.js";import{O as s,_ as c,c as l,g as u,j as d,k as f,p,r as m,v as h,x as g}from"./index-C-pZwUmz.js";import{a as _,c as v,i as y,n as b,r as x,s as S,t as C}from"./Select-u39f55hZ.js";import{r as w}from"./PageSkeleton-DG-Dj4_9.js";import{n as T,t as E}from"./sharedWindow-FDfq75PI.js";import{t as D}from"./useFx-xcrp_LHB.js";import{n as O,t as k}from"./ClientMetricTip-Dr6gYRSd.js";var A=i(),j=o();function M({navigate:i}){let[o,M]=(0,A.useState)(()=>E()??`today`),N=d(()=>l.overview(o),[o],{cacheKey:`overview:${o}`});D(`overview-${o}-${N.data?`ready`:`pending`}`);let P=N.data,F={today:(()=>{let e=new Date;return e.getHours()*60+e.getMinutes()||1})(),"1h":60,"24h":1440,"7d":10080,"30d":43200},I=P?F[P.window]:null,L=P&&I?P.metrics.requests/I:null,R=P&&I?P.metrics.totalTokens/I:null,z=e=>{if(!P||P.byClient.length===0)return;let t=[...P.byClient].sort((t,n)=>(n[e]??0)-(t[e]??0));return(0,j.jsx)(k,{rows:t,metric:e,windowLabel:s[P.window],total:e===`credits`?P.metrics.creditsUsed:P.metrics.requests,totalCount:t.length})};return(0,j.jsxs)(`div`,{className:`view on`,children:[(0,j.jsx)(S,{title:`概览`,status:P?(0,j.jsxs)(v,{tone:`live`,dot:!0,children:[`本机日志聚合 · `,h(P.generatedAt)]}):null,actions:(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(C,{value:o,options:f,onChange:e=>{T(e),M(e)},width:148}),(0,j.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>i(`keys`),children:[(0,j.jsx)(n,{size:14}),`新建密钥`]})]})}),(0,j.jsx)(`div`,{className:`view-body stack`,children:N.initial&&N.loading?(0,j.jsx)(w,{}):N.error?(0,j.jsx)(_,{children:(0,j.jsx)(b,{kind:`error`,title:`概览数据没取到`,detail:x(N.error),onRetry:N.reload})}):P?(0,j.jsxs)(j.Fragment,{children:[P.alerts.channelReject.count>=P.alerts.channelReject.threshold?(0,j.jsxs)(m,{tone:`warn`,children:[(0,j.jsx)(a,{size:14,"aria-hidden":!0}),`最近 1 小时出现 `,(0,j.jsxs)(`b`,{children:[P.alerts.channelReject.count,` 次`]}),`上游渠道校验拒绝(11128): 请求的 system 提示命中了上游的第三方客户端指纹。本服务会自动改写模板行、被拒时降级重试, 连续失败说明出现了新的指纹变体。请到「请求日志」筛 note 含 upstream_11128 的行排查。`]}):null,P.alerts.credentialInvalid?.count!=null&&P.alerts.credentialInvalid.count>=P.alerts.credentialInvalid.threshold?(0,j.jsxs)(m,{tone:`error`,actions:(0,j.jsx)(`a`,{href:`#/clients`,className:`btn btn-quiet btn-sm`,children:`去提供商管理`}),children:[(0,j.jsx)(a,{size:14,"aria-hidden":!0}),`最近 1 小时出现 `,(0,j.jsxs)(`b`,{children:[P.alerts.credentialInvalid.count,` 次`]}),`上游访问密钥失效: 所有请求将以 502 失败,直到换新为止。`]}):null,(0,j.jsx)(O,{metrics:P.metrics}),(0,j.jsxs)(`div`,{className:`metric-row`,children:[(0,j.jsx)(y,{label:`已消耗积分`,value:p(P.metrics.creditsUsed),detail:P.metrics.creditsUnknownCount>0?`上游实扣 · 另 ${u(P.metrics.creditsUnknownCount)} 条上游未回报,未计入`:`上游实扣 · 全部请求均已回报`,tone:`amber`,icon:(0,j.jsx)(e,{size:14}),tip:z(`credits`),tipPos:`down`}),(0,j.jsx)(y,{label:`平均 RPM`,value:L===null?`—`:u(Math.round(L)),detail:`每分钟请求 · ${s[P.window]}`,tone:`violet`,icon:(0,j.jsx)(t,{size:14}),tip:z(`requests`),tipPos:`down`}),(0,j.jsx)(y,{label:`平均 TPM`,value:R===null?`—`:g(Math.round(R)),detail:`每分钟 tokens · ${s[P.window]}`,tone:`green`,icon:(0,j.jsx)(r,{size:14}),tip:z(`tokens`),tipPos:`down`})]}),(0,j.jsx)(_,{title:`提供商用量摘要`,aside:(0,j.jsxs)(v,{children:[P.byProvider.length,` 个提供商 · 按 Token 排序`]}),children:P.byProvider.length>0?(0,j.jsx)(`div`,{className:`tbl-scroll`,children:(0,j.jsxs)(`table`,{className:`tbl`,children:[(0,j.jsxs)(`caption`,{className:`sr-only`,children:[`提供商用量摘要,共 `,P.byProvider.length,` 个提供商`]}),(0,j.jsx)(`thead`,{children:(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`th`,{scope:`col`,children:`提供商`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 消耗`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 占比`}),(0,j.jsx)(`th`,{scope:`col`,children:`分布`})]})}),(0,j.jsx)(`tbody`,{children:[...P.byProvider].sort((e,t)=>(t.tokens??0)-(e.tokens??0)).slice(0,6).map(e=>{let t=P&&P.metrics.totalTokens>0?(e.tokens??0)/P.metrics.totalTokens:null;return(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`td`,{className:`td-strong`,children:(0,j.jsx)(`span`,{children:e.provider??`未归属`})}),(0,j.jsx)(`td`,{className:`r td-mono`,children:u(e.requests)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:g(e.tokens??0)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:p(e.credits)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:c(t)}),(0,j.jsx)(`td`,{children:(0,j.jsx)(`div`,{className:`ov-bar-track`,children:(0,j.jsx)(`div`,{className:`ov-bar-fill`,style:{width:`${Math.max(2,Math.round((t??0)*100))}%`}})})})]},e.providerId??`__unattributed__`)})})]})}):(0,j.jsxs)(`div`,{className:`empty-note`,style:{padding:`24px 0`},children:[s[P.window],`没有提供商消耗记录。`]})}),(0,j.jsxs)(_,{title:`模型用量摘要`,aside:(0,j.jsxs)(v,{children:[P.byModel.length,` 个模型 · 按 Token 排序`]}),children:[P.byModel.length>0?(0,j.jsx)(`div`,{className:`tbl-scroll`,children:(0,j.jsxs)(`table`,{className:`tbl`,children:[(0,j.jsxs)(`caption`,{className:`sr-only`,children:[`模型用量摘要,共 `,P.byModel.length,` 个模型`]}),(0,j.jsx)(`thead`,{children:(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`th`,{scope:`col`,children:`模型`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 消耗`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 占比`}),(0,j.jsx)(`th`,{scope:`col`,children:`分布`})]})}),(0,j.jsx)(`tbody`,{children:[...P.byModel].sort((e,t)=>(t.tokens??0)-(e.tokens??0)).slice(0,8).map(e=>{let t=e.displayName??e.model??`未知`;return(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`td`,{className:`td-strong`,children:t}),(0,j.jsx)(`td`,{className:`r td-mono`,children:u(e.requests)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:g(e.tokens??0)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:p(e.credits)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:c(e.share)}),(0,j.jsx)(`td`,{children:(0,j.jsx)(`div`,{className:`ov-bar-track`,children:(0,j.jsx)(`div`,{className:`ov-bar-fill`,style:{width:`${Math.max(2,Math.round((e.share??0)*100))}%`}})})})]},e.model??t)})})]})}):(0,j.jsxs)(`div`,{className:`empty-note`,style:{padding:`24px 0`},children:[s[P.window],`没有模型消耗记录。`]}),(0,j.jsx)(`div`,{className:`footer-note`,children:`这一页只留结论。详细构成去「积分与用量」,逐条请求去「请求日志」。`})]}),(0,j.jsx)(`div`,{className:`footer-note`,style:{marginTop:0},children:P.provenance.note})]}):null})]})}export{M as OverviewPage};
1
+ import{B as e,N as t,_ as n,i as r,it as i,r as a,tt as o}from"./vendor-befEYGHM.js";import{O as s,_ as c,c as l,g as u,j as d,k as f,p,r as m,v as h,x as g}from"./index-BMxTTq4E.js";import{a as _,c as v,i as y,n as b,r as x,s as S,t as C}from"./Select-CrLEZJdb.js";import{r as w}from"./PageSkeleton-DG-Dj4_9.js";import{n as T,t as E}from"./sharedWindow-FDfq75PI.js";import{t as D}from"./useFx-xcrp_LHB.js";import{n as O,t as k}from"./ClientMetricTip-DUB2wyWX.js";var A=i(),j=o();function M({navigate:i}){let[o,M]=(0,A.useState)(()=>E()??`today`),N=d(()=>l.overview(o),[o],{cacheKey:`overview:${o}`});D(`overview-${o}-${N.data?`ready`:`pending`}`);let P=N.data,F={today:(()=>{let e=new Date;return e.getHours()*60+e.getMinutes()||1})(),"1h":60,"24h":1440,"7d":10080,"30d":43200},I=P?F[P.window]:null,L=P&&I?P.metrics.requests/I:null,R=P&&I?P.metrics.totalTokens/I:null,z=e=>{if(!P||P.byClient.length===0)return;let t=[...P.byClient].sort((t,n)=>(n[e]??0)-(t[e]??0));return(0,j.jsx)(k,{rows:t,metric:e,windowLabel:s[P.window],total:e===`credits`?P.metrics.creditsUsed:P.metrics.requests,totalCount:t.length})};return(0,j.jsxs)(`div`,{className:`view on`,children:[(0,j.jsx)(S,{title:`概览`,status:P?(0,j.jsxs)(v,{tone:`live`,dot:!0,children:[`本机日志聚合 · `,h(P.generatedAt)]}):null,actions:(0,j.jsxs)(j.Fragment,{children:[(0,j.jsx)(C,{value:o,options:f,onChange:e=>{T(e),M(e)},width:148}),(0,j.jsxs)(`button`,{type:`button`,className:`btn btn-primary btn-sm`,onClick:()=>i(`keys`),children:[(0,j.jsx)(n,{size:14}),`新建密钥`]})]})}),(0,j.jsx)(`div`,{className:`view-body stack`,children:N.initial&&N.loading?(0,j.jsx)(w,{}):N.error?(0,j.jsx)(_,{children:(0,j.jsx)(b,{kind:`error`,title:`概览数据没取到`,detail:x(N.error),onRetry:N.reload})}):P?(0,j.jsxs)(j.Fragment,{children:[P.alerts.channelReject.count>=P.alerts.channelReject.threshold?(0,j.jsxs)(m,{tone:`warn`,children:[(0,j.jsx)(a,{size:14,"aria-hidden":!0}),`最近 1 小时出现 `,(0,j.jsxs)(`b`,{children:[P.alerts.channelReject.count,` 次`]}),`上游渠道校验拒绝(11128): 请求的 system 提示命中了上游的第三方客户端指纹。本服务会自动改写模板行、被拒时降级重试, 连续失败说明出现了新的指纹变体。请到「请求日志」筛 note 含 upstream_11128 的行排查。`]}):null,P.alerts.credentialInvalid?.count!=null&&P.alerts.credentialInvalid.count>=P.alerts.credentialInvalid.threshold?(0,j.jsxs)(m,{tone:`error`,actions:(0,j.jsx)(`a`,{href:`#/clients`,className:`btn btn-quiet btn-sm`,children:`去提供商管理`}),children:[(0,j.jsx)(a,{size:14,"aria-hidden":!0}),`最近 1 小时出现 `,(0,j.jsxs)(`b`,{children:[P.alerts.credentialInvalid.count,` 次`]}),`上游访问密钥失效: 所有请求将以 502 失败,直到换新为止。`]}):null,(0,j.jsx)(O,{metrics:P.metrics}),(0,j.jsxs)(`div`,{className:`metric-row`,children:[(0,j.jsx)(y,{label:`已消耗积分`,value:p(P.metrics.creditsUsed),detail:P.metrics.creditsUnknownCount>0?`上游实扣 · 另 ${u(P.metrics.creditsUnknownCount)} 条上游未回报,未计入`:`上游实扣 · 全部请求均已回报`,tone:`amber`,icon:(0,j.jsx)(e,{size:14}),tip:z(`credits`),tipPos:`down`}),(0,j.jsx)(y,{label:`平均 RPM`,value:L===null?`—`:u(Math.round(L)),detail:`每分钟请求 · ${s[P.window]}`,tone:`violet`,icon:(0,j.jsx)(t,{size:14}),tip:z(`requests`),tipPos:`down`}),(0,j.jsx)(y,{label:`平均 TPM`,value:R===null?`—`:g(Math.round(R)),detail:`每分钟 tokens · ${s[P.window]}`,tone:`green`,icon:(0,j.jsx)(r,{size:14}),tip:z(`tokens`),tipPos:`down`})]}),(0,j.jsx)(_,{title:`提供商用量摘要`,aside:(0,j.jsxs)(v,{children:[P.byProvider.length,` 个提供商 · 按 Token 排序`]}),children:P.byProvider.length>0?(0,j.jsx)(`div`,{className:`tbl-scroll`,children:(0,j.jsxs)(`table`,{className:`tbl`,children:[(0,j.jsxs)(`caption`,{className:`sr-only`,children:[`提供商用量摘要,共 `,P.byProvider.length,` 个提供商`]}),(0,j.jsx)(`thead`,{children:(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`th`,{scope:`col`,children:`提供商`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 消耗`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 占比`}),(0,j.jsx)(`th`,{scope:`col`,children:`分布`})]})}),(0,j.jsx)(`tbody`,{children:[...P.byProvider].sort((e,t)=>(t.tokens??0)-(e.tokens??0)).slice(0,6).map(e=>{let t=P&&P.metrics.totalTokens>0?(e.tokens??0)/P.metrics.totalTokens:null;return(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`td`,{className:`td-strong`,children:(0,j.jsx)(`span`,{children:e.provider??`未归属`})}),(0,j.jsx)(`td`,{className:`r td-mono`,children:u(e.requests)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:g(e.tokens??0)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:p(e.credits)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:c(t)}),(0,j.jsx)(`td`,{children:(0,j.jsx)(`div`,{className:`ov-bar-track`,children:(0,j.jsx)(`div`,{className:`ov-bar-fill`,style:{width:`${Math.max(2,Math.round((t??0)*100))}%`}})})})]},e.providerId??`__unattributed__`)})})]})}):(0,j.jsxs)(`div`,{className:`empty-note`,style:{padding:`24px 0`},children:[s[P.window],`没有提供商消耗记录。`]})}),(0,j.jsxs)(_,{title:`模型用量摘要`,aside:(0,j.jsxs)(v,{children:[P.byModel.length,` 个模型 · 按 Token 排序`]}),children:[P.byModel.length>0?(0,j.jsx)(`div`,{className:`tbl-scroll`,children:(0,j.jsxs)(`table`,{className:`tbl`,children:[(0,j.jsxs)(`caption`,{className:`sr-only`,children:[`模型用量摘要,共 `,P.byModel.length,` 个模型`]}),(0,j.jsx)(`thead`,{children:(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`th`,{scope:`col`,children:`模型`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`请求次数`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 消耗`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`消耗积分`}),(0,j.jsx)(`th`,{scope:`col`,className:`r`,children:`Token 占比`}),(0,j.jsx)(`th`,{scope:`col`,children:`分布`})]})}),(0,j.jsx)(`tbody`,{children:[...P.byModel].sort((e,t)=>(t.tokens??0)-(e.tokens??0)).slice(0,8).map(e=>{let t=e.displayName??e.model??`未知`;return(0,j.jsxs)(`tr`,{children:[(0,j.jsx)(`td`,{className:`td-strong`,children:t}),(0,j.jsx)(`td`,{className:`r td-mono`,children:u(e.requests)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:g(e.tokens??0)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:p(e.credits)}),(0,j.jsx)(`td`,{className:`r td-mono`,children:c(e.share)}),(0,j.jsx)(`td`,{children:(0,j.jsx)(`div`,{className:`ov-bar-track`,children:(0,j.jsx)(`div`,{className:`ov-bar-fill`,style:{width:`${Math.max(2,Math.round((e.share??0)*100))}%`}})})})]},e.model??t)})})]})}):(0,j.jsxs)(`div`,{className:`empty-note`,style:{padding:`24px 0`},children:[s[P.window],`没有模型消耗记录。`]}),(0,j.jsx)(`div`,{className:`footer-note`,children:`这一页只留结论。详细构成去「积分与用量」,逐条请求去「请求日志」。`})]}),(0,j.jsx)(`div`,{className:`footer-note`,style:{marginTop:0},children:P.provenance.note})]}):null})]})}export{M as OverviewPage};