waygate-open 1.6.16 → 1.6.18

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,7 +8,7 @@
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.16-4A38C0?style=flat-square)](https://github.com/zhanghaojie513/waygate-open/releases)
11
+ [![version](https://img.shields.io/badge/version-1.6.18-4A38C0?style=flat-square)](https://github.com/zhanghaojie513/waygate-open/releases)
12
12
  [![tests](https://img.shields.io/badge/tests-741%20passed-0A7247?style=flat-square)](./server)
13
13
 
14
14
  ![途关 · Waygate](docs/img/intro.png)
@@ -391,4 +391,4 @@ tools/ 仓库级工具(lint 门禁、token 合并、截图
391
391
 
392
392
  ---
393
393
 
394
- 版本约定:`1.0.0` 起 API 契约稳定(当前 `1.6.16`,见 `server/src/version.ts`;历史版本见 [Releases](https://github.com/zhanghaojie513/waygate-open/releases))。
394
+ 版本约定:`1.0.0` 起 API 契约稳定(当前 `1.6.18`,见 `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.16",
3
+ "version": "1.6.18",
4
4
  "description": "途关 Waygate · 本地启动 —— 把 WorkBuddy / MiniMax Code / Qoder CN 的客户端订阅转成 OpenAI / Anthropic 兼容 base_url(自带控制台,一条命令起服务并自动打开浏览器)",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -112,6 +112,11 @@ let OverviewService = OverviewService_1 = class OverviewService {
112
112
  MAX(ts) AS lastTs,
113
113
  MAX(key_hint) AS keyHint
114
114
  FROM requests WHERE ts >= ? AND ts < ?
115
+ /* [2026-10-17] 排除 model 为空的行:实测 36 条全是 429 并发拒绝 ——
116
+ 请求在模型解析之前就被闸挡下,model 列为空串;它们不是任何
117
+ 「模型」的用量,进 byModel 只会多出一行 0 token 的「未知模型」。
118
+ metrics.requests 仍计入(被拒也是一次请求),仅摘要剔除。 */
119
+ AND model <> ''
115
120
  GROUP BY client_id, model
116
121
  )
117
122
  SELECT g.client_id AS clientId, g.model, g.requests, g.errors, g.creditsRaw, g.knownCount,
@@ -451,7 +456,7 @@ let OverviewService = OverviewService_1 = class OverviewService {
451
456
  升格为概览横幅(与 11128 告警同机制)。按 note 计数(upstream_credential_invalid
452
457
  是 note 标记而非独立业务码列);阈值 1 —— 单次即报,停摆级不等累积。
453
458
  窗口与 channelReject 一致:固定最近 1 小时,与 window 参数无关。 */
454
- const credentialInvalidCount = this.stmt(`SELECT COUNT(*) AS n FROM requests
459
+ const credentialInvalidCount = this.stmt(`SELECT COUNT(*) AS n FROM requests
455
460
  WHERE ts >= ? AND ts < ? AND note LIKE '%' || ? || '%' AND status >= 400`)
456
461
  .get(now - exports.CHANNEL_REJECT_WINDOW_MS, now, 'upstream_credential_invalid').n;
457
462
  const channelRejectCount = this.stmt(`SELECT COUNT(*) AS n FROM requests
@@ -150,7 +150,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
150
150
  return;
151
151
  const baseUrl = this.config.get().upstream.baseUrl;
152
152
  this.repo.create({ id: exports.DEFAULT_PROVIDER_ID, name: exports.DEFAULT_PROVIDER_NAME, baseUrl, active: true });
153
- /* [v1.6.11] 不再把 config.upstreamKey 迁到 .key —— WorkBuddy 的凭证是 OAuth 槽
153
+ /* [v1.6.13] 不再把 config.upstreamKey 迁到 .key —— WorkBuddy 的凭证是 OAuth 槽
154
154
  (ck_ 已退役)。升级后该行显示「未配置」,用户在控制台点「重新导入授权」即可。
155
155
  旧 .key 文件如存在也不读(不再是凭证来源),保留不删以免误伤回滚。 */
156
156
  if (this.catalog) {
@@ -242,7 +242,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
242
242
  if (row.protocol === 'anthropic')
243
243
  this.minimaxTokens?.invalidate(id);
244
244
  }
245
- /* [v1.6.11] openai 走上面的 parseCredential 报错(ck_ 已退役)—— 这里到不了。 */
245
+ /* [v1.6.13] openai 走上面的 parseCredential 报错(ck_ 已退役)—— 这里到不了。 */
246
246
  }
247
247
  /* 活提供商的地址改了 → 立即写进设置(转发/实测/目录拉取都读它) */
248
248
  const updated = this.repo.findById(id);
@@ -267,7 +267,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
267
267
  其余都不放:
268
268
  · [v0.5.0] anthropic+oauth_minimax:文件里是 refresh token,出站时换 access token;
269
269
  · [v0.6.0] qoder:凭证是身份 JSON,出站由 QoderCredentialStore 直读文件;
270
- · [v1.6.11] openai(WorkBuddy):凭证是 OAuth 槽,出站由 WbOauthService 取(ck_ 已退役)。 */
270
+ · [v1.6.13] openai(WorkBuddy):凭证是 OAuth 槽,出站由 WbOauthService 取(ck_ 已退役)。 */
271
271
  if (row.protocol === 'anthropic' && row.cred_kind !== 'oauth_minimax') {
272
272
  this.config.setUpstreamKey(this.readCredentialFile(id));
273
273
  }
@@ -294,15 +294,19 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
294
294
  this.repo.delete(id);
295
295
  /* 目录分区一并清(多生效下其他家的清单不动)。 */
296
296
  this.catalog?.clearProvider(id);
297
- /* 偏好与凭证都是"这家提供商的",一起清(provider_model_prefs 无 FK,不连带清理会留孤儿行)。 */
297
+ /* 偏好与凭证都是"这家提供商的",一起清(provider_model_prefs 无 FK,不连带清理会留孤儿行)。
298
+ [2026-10-17] 凭证清理从「只删 .key」扩到「该 id 的全部凭证类文件」——
299
+ 实测删 p_mm0001 后 .access 残留(token 服务的缓存文件不在原清理范围)。 */
298
300
  this.prefs.deleteForProvider(id);
299
- try {
300
- const file = this.credentialPath(id);
301
- if ((0, node_fs_1.existsSync)(file))
302
- (0, node_fs_1.unlinkSync)(file);
303
- }
304
- catch (err) {
305
- this.logger.warn(`删除凭证文件失败(提供商已删,文件遗留无危害):${err.message}`);
301
+ for (const suffix of ['.key', '.access', '.oauth']) {
302
+ try {
303
+ const file = this.credentialPath(id).replace(/\.key$/, suffix);
304
+ if ((0, node_fs_1.existsSync)(file))
305
+ (0, node_fs_1.unlinkSync)(file);
306
+ }
307
+ catch (err) {
308
+ this.logger.warn(`删除凭证文件 ${suffix} 失败(提供商已删,文件遗留无危害):${err.message}`);
309
+ }
306
310
  }
307
311
  this.logger.log(`删除提供商 ${row.name}(${row.id})`);
308
312
  }
@@ -342,7 +346,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
342
346
  const row = this.mustFind(id);
343
347
  let key;
344
348
  if (row.protocol === 'openai') {
345
- /* [v1.6.11] WorkBuddy 的凭证是 OAuth 槽(不与行内文件同址)—— 从服务取 access token。
349
+ /* [v1.6.13] WorkBuddy 的凭证是 OAuth 槽(不与行内文件同址)—— 从服务取 access token。
346
350
  不接草稿 credential(ck_ 已退役,界面也不再提供输入框)。 */
347
351
  if (rawKey !== undefined && rawKey !== null && rawKey !== '') {
348
352
  throw this.bad('WorkBuddy 已不再使用 ck_ 密钥 —— 请用「重新导入授权」完成官方授权。');
@@ -397,7 +401,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
397
401
  // ---- 内部 ----
398
402
  toItem(row) {
399
403
  const key = this.readCredentialFile(row.id);
400
- /* [v1.6.11] openai(WorkBuddy)的「已配置」改看 **OAuth 槽** —— ck_ 已退役。
404
+ /* [v1.6.13] openai(WorkBuddy)的「已配置」改看 **OAuth 槽** —— ck_ 已退役。
401
405
  其余两家仍看凭证文件(anthropic = mmort_ refresh token / M Plan key;
402
406
  qoder = 身份 JSON)。 */
403
407
  const isOpenai = row.protocol !== 'anthropic' && row.protocol !== 'qoder';
@@ -452,7 +456,7 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
452
456
  }
453
457
  /** 写进 settings.upstream.baseUrl —— 持久化 + 热生效(resolveUpstreamBase 每请求都读它)。 */
454
458
  applyBaseUrlToSettings(baseUrl) {
455
- this.config.applyUpdates([{ key: 'upstream.baseUrl', value: baseUrl }]);
459
+ this.config.applyInternal([{ key: 'upstream.baseUrl', value: baseUrl }]);
456
460
  }
457
461
  readCredentialFile(id) {
458
462
  try {
@@ -532,11 +536,11 @@ let ProvidersService = ProvidersService_1 = class ProvidersService {
532
536
  * 这两家的凭证是客户端登录态里的东西(MiniMax = `mmort_` refresh token;
533
537
  * Qoder = 一整段身份 JSON),用户手上没有、也手敲不出来 —— 「配置」抽屉为此在
534
538
  * v1.6.4 已改为导入指引。但**新增**弹窗当时没跟着改,仍强制必填,
535
- * 于是这两家在界面上**根本建不出来**:导入脚本 `qoder-import-credential.mjs <id>`
539
+ * 于是这两家在界面上**根本建不出来**:导入又要 provider id
536
540
  * 又要 provider id,而 id 建完才有 —— 死锁。
537
541
  *
538
542
  * 放行留空后:行先建出来(`configured=false`),再用「自动导入」把凭证灌进去。
539
- * [v1.6.11] openai(WorkBuddy)也放行留空 —— **ck_ 已退役**,三家统一走
543
+ * [v1.6.13] openai(WorkBuddy)也放行留空 —— **ck_ 已退役**,三家统一走
540
544
  * 「导入客户端 → 官方授权」;凭证分别落 .oauth(WorkBuddy)/ .key(另两家)。
541
545
  */
542
546
  parseCredential(raw, protocol = 'openai') {
@@ -3,8 +3,10 @@
3
3
  * 目录同步编排(task#18;task 倍率接口化并入 /v3/config):
4
4
  * [v1.5.0] 纯接口口径(用户拍板):目录 = 上游清单接口本轮返回的列表,**替换式**落库
5
5
  * (replaceSync,manual 行保留);拉取失败保留旧目录(不倒退)。observed 机制与
6
- * Qoder 内置种子回落已废除 ——「客户端没给就是没有」。唯一例外:MiniMax(anthropic)
7
- * 上游无清单端点(实测 /models 恒 503),静态种子是它的唯一目录来源。
6
+ * Qoder 内置种子回落已废除 ——「客户端没给就是没有」。
7
+ * [v1.6.25] MiniMax(anthropic)改为**真拉取**(/mavis/api/v1/models,2026-10-17
8
+ * 实测发现——此前"无清单端点"的结论是测错了路径);拉不到时回落静态种子
9
+ * (与 Qoder 不同:MiniMax 已有种子用户在用,保留回落更安全)。
8
10
  * 倍率仍以 /v3/config 接口为权威(随清单一起合成)。逐项实测仅手动刷新。
9
11
  *
10
12
  * 从 models.service 抽出,原因:models.service 已越过 300 行门禁,
@@ -43,6 +45,7 @@ const pull_1 = require("./pull");
43
45
  const v3_config_1 = require("./v3-config");
44
46
  const upstream_url_1 = require("../proxy/upstream-url");
45
47
  const wb_oauth_service_1 = require("../proxy/openai-upstream/wb-oauth.service");
48
+ const minimax_token_service_1 = require("../proxy/anthropic-upstream/minimax-token.service");
46
49
  const qoder_models_1 = require("./qoder-models");
47
50
  const credential_1 = require("../proxy/qoder-upstream/credential");
48
51
  /**
@@ -105,6 +108,7 @@ let CatalogSyncService = CatalogSyncService_1 = class CatalogSyncService {
105
108
  probeIntervalMs;
106
109
  providers;
107
110
  wbOauth;
111
+ minimaxTokens;
108
112
  logger = new common_1.Logger(CatalogSyncService_1.name);
109
113
  /** 上次**成功**刷新时刻;null = 从未刷新。 */
110
114
  lastRefreshAt = null;
@@ -112,25 +116,26 @@ let CatalogSyncService = CatalogSyncService_1 = class CatalogSyncService {
112
116
  refreshing = false;
113
117
  timer = null;
114
118
  constructor(
115
- /* [v1.6.11] openai 凭证改走 OAuth 后,本类不再读 config(原先只用于回落
119
+ /* [v1.6.13] openai 凭证改走 OAuth 后,本类不再读 config(原先只用于回落
116
120
  `config.upstreamKey`)。参数保留(不加访问修饰符,仅作 DI 位占位):
117
121
  位置与 `design:paramtypes` 元数据不变,既有 spec / harness 按位置构造不受影响。 */
118
122
  _config, catalog, probe,
119
123
  /** 按提供商记住启停偏好(2026-10-09)—— 切换提供商机用。 */
120
- prefs, probeIntervalMs = probe_1.PROBE_INTERVAL_MS, providers, wbOauth) {
124
+ prefs, probeIntervalMs = probe_1.PROBE_INTERVAL_MS, providers, wbOauth, minimaxTokens) {
121
125
  this.catalog = catalog;
122
126
  this.probe = probe;
123
127
  this.prefs = prefs;
124
128
  this.probeIntervalMs = probeIntervalMs;
125
129
  this.providers = providers;
126
130
  this.wbOauth = wbOauth;
131
+ this.minimaxTokens = minimaxTokens;
127
132
  }
128
133
  /** 活提供商集合([v0.5.1] 多生效:列表而非单行)。未装配 providers 时返回空。 */
129
134
  activeProviders() {
130
135
  return this.providers?.listActive() ?? [];
131
136
  }
132
137
  /**
133
- * [v1.6.11] openai 提供商的出站凭证:改从 **OAuth 槽**取 access token(ck_ 已退役)。
138
+ * [v1.6.13] openai 提供商的出站凭证:改从 **OAuth 槽**取 access token(ck_ 已退役)。
134
139
  * 取不到返回空串 —— 调用方据此判「未授权」(不回落任何全局密钥,用户口径)。
135
140
  */
136
141
  async openAiCredential(provider) {
@@ -143,6 +148,48 @@ let CatalogSyncService = CatalogSyncService_1 = class CatalogSyncService {
143
148
  return '';
144
149
  }
145
150
  }
151
+ /**
152
+ * [v1.6.25] anthropic(MiniMax)目录:**真拉取** `/mavis/api/v1/models` + 种子回落。
153
+ *
154
+ * 端点 2026-10-17 发现(此前"无清单端点"的结论是测了 `/models` 而非 `/mavis/api/v1/models`)。
155
+ * 响应形状:`{ providers: [{ providerId: 'minimax', config: { models: { [id]: { name, reasoning, tool_call, limit: {context, output}, ... } } } }] }`。
156
+ * 用 access token(MinimaxTokenService)鉴权,与 chat 端点同凭证链。
157
+ * 拉取失败回落静态种子(替换式失败时保留旧目录 + 种子兜底首启场景)。
158
+ */
159
+ async pullMinimaxCatalog(provider) {
160
+ /* 种子兜底(首启 / 拉取失败时用)。 */
161
+ const seedFallback = () => this.seedMinimaxCatalog(provider.id);
162
+ if (!this.minimaxTokens)
163
+ return seedFallback();
164
+ try {
165
+ const token = await this.minimaxTokens.accessToken(provider);
166
+ const url = `${(0, upstream_url_1.stripTrailingSlash)(provider.base_url)}/mavis/api/v1/models`;
167
+ const res = await fetch(url, {
168
+ headers: { authorization: `Bearer ${token}`, accept: 'application/json' },
169
+ signal: AbortSignal.timeout(PULL_TIMEOUT_MS),
170
+ });
171
+ if (!res.ok)
172
+ throw new Error(`HTTP ${res.status}`);
173
+ const body = (await res.json());
174
+ const models = body.providers?.find((p) => p.providerId === 'minimax')?.config?.models;
175
+ if (!models || typeof models !== 'object' || Object.keys(models).length === 0) {
176
+ throw new Error('响应无可解析的 models 段');
177
+ }
178
+ const entries = Object.entries(models).map(([id, m]) => ({
179
+ id,
180
+ displayName: typeof m?.name === 'string' && m.name ? m.name : id,
181
+ source: 'upstream',
182
+ multiplier: null,
183
+ tag: null,
184
+ }));
185
+ const { inserted } = this.catalog.replaceSync(provider.id, entries);
186
+ return { pullOk: true, configOk: false, added: inserted, multipliersUpdated: 0 };
187
+ }
188
+ catch (err) {
189
+ this.logger.warn(`MiniMax 目录拉取失败(回落静态种子):${err.message}`);
190
+ return seedFallback();
191
+ }
192
+ }
146
193
  /** [v0.5.1] anthropic 提供商的目录:静态种子(无上游清单端点、不做方言实测)。 */
147
194
  seedMinimaxCatalog(providerId) {
148
195
  const { inserted } = this.catalog.mergeSync(providerId, MINIMAX_SEED_MODELS.map((m) => ({
@@ -334,9 +381,9 @@ let CatalogSyncService = CatalogSyncService_1 = class CatalogSyncService {
334
381
  * observed/种子合并全部废除;MiniMax 例外(上游无清单端点,种子是唯一来源)。
335
382
  */
336
383
  async pullAndMergeFor(provider) {
337
- /* anthropic 协议:不拉上游(无清单端点)—— 静态种子(唯一例外,见方法注释)。 */
384
+ /* anthropic 协议:/mavis/api/v1/models 真拉取,失败回落静态种子(见 pullMinimaxCatalog)。 */
338
385
  if (provider.protocol === 'anthropic')
339
- return this.seedMinimaxCatalog(provider.id);
386
+ return this.pullMinimaxCatalog(provider);
340
387
  /* qoder 协议:网关有 /model/list —— 真拉取,替换式。 */
341
388
  if (provider.protocol === 'qoder')
342
389
  return await this.pullQoderCatalog(provider);
@@ -381,7 +428,7 @@ let CatalogSyncService = CatalogSyncService_1 = class CatalogSyncService {
381
428
  const actives = this.activeProviders();
382
429
  if (actives.length === 0)
383
430
  return;
384
- /* [v1.6.11] openai 家需已导入 OAuth 授权才同步(ck_ 已退役,不再看全局位)。 */
431
+ /* [v1.6.13] openai 家需已导入 OAuth 授权才同步(ck_ 已退役,不再看全局位)。 */
385
432
  void (async () => {
386
433
  const openAiActives = actives.filter((p) => p.protocol === 'openai');
387
434
  const creds = await Promise.all(openAiActives.map((p) => this.openAiCredential(p)));
@@ -442,9 +489,11 @@ exports.CatalogSyncService = CatalogSyncService = CatalogSyncService_1 = __decor
442
489
  __param(4, (0, common_1.Inject)(exports.CATALOG_PROBE_INTERVAL_MS)),
443
490
  __param(5, (0, common_1.Optional)()),
444
491
  __param(6, (0, common_1.Optional)()),
492
+ __param(7, (0, common_1.Optional)()),
445
493
  __metadata("design:paramtypes", [config_service_1.RelayConfigService,
446
494
  model_catalog_repo_1.ModelCatalogRepository,
447
495
  probe_1.ModelProbeService,
448
496
  provider_model_prefs_repo_1.ProviderModelPrefsRepository, Number, providers_repo_1.ProvidersRepository,
449
- wb_oauth_service_1.WbOauthService])
497
+ wb_oauth_service_1.WbOauthService,
498
+ minimax_token_service_1.MinimaxTokenService])
450
499
  ], CatalogSyncService);
@@ -217,6 +217,31 @@ let RelayConfigService = class RelayConfigService {
217
217
  (0, node_fs_1.writeFileSync)(this.settingsPath(), JSON.stringify({ version: this.version, settings: this.settings }, null, 2), { mode: 0o600 });
218
218
  return { applied: entries.length, requiresRestart, version: this.version };
219
219
  }
220
+ /**
221
+ * 内部系统写入:**不走 `EDITABLE_KEYS` 闸**(那是用户设置 PUT 的防线)。
222
+ *
223
+ * 用途:提供商激活 / 改址时同步 `upstream.baseUrl` —— 该键 v0.6.5 起对用户只读
224
+ * (出站都读 `provider.base_url`),但仍是「空库种子」与「无 active 提供商时的
225
+ * 额度回落」的数据源,系统侧需要保持它指向当前活提供商。
226
+ *
227
+ * ⚠️ [2026-10-17 事故] 此前激活路径误走 `applyUpdates` —— 撞只读闸抛
228
+ * 「upstream.baseUrl 不是可修改的设置项」,而 `repo.activate()` 已先落库:
229
+ * 用户看到报错、刷新后却已启用(启用按钮自 v0.6.5 起一直带着这个静默故障,
230
+ * 首次 UI 触发激活是本次删除重导才暴露)。
231
+ */
232
+ applyInternal(entries) {
233
+ for (const entry of entries) {
234
+ const check = (0, settings_validate_1.validateSetting)(entry.key, entry.value);
235
+ if (!check.ok)
236
+ throw new errors_1.RelayHttpError({
237
+ httpStatus: 400, code: 'invalid_request_body', apiCode: errors_1.ApiCode.BAD_PARAM,
238
+ message: check.message ?? `${entry.key} 校验失败`,
239
+ });
240
+ this.assign(entry.key, entry.value);
241
+ }
242
+ this.version += 1;
243
+ (0, node_fs_1.writeFileSync)(this.settingsPath(), JSON.stringify({ version: this.version, settings: this.settings }, null, 2), { mode: 0o600 });
244
+ }
220
245
  assign(key, value) {
221
246
  const [group, field] = key.split('.');
222
247
  const target = this.settings;
@@ -12,4 +12,4 @@ exports.VERSION = void 0;
12
12
  * `version` 一致,不一致直接 fail build —— 两处版本永远不会悄悄分叉。
13
13
  * 升版本 = 同时改 package.json 与这里(构建会逼你对齐)。
14
14
  */
15
- exports.VERSION = '1.6.16';
15
+ exports.VERSION = '1.6.18';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygate-server",
3
- "version": "1.6.16",
3
+ "version": "1.6.18",
4
4
  "private": true,
5
5
  "description": "驿站 · Relay — OpenAI 兼容反向代理与控制台(后端)",
6
6
  "engines": {
@@ -1,4 +1,4 @@
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-nQeRK3kW.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-G6AhT0TA.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,pendingId:n,onToggle:r,onEdit:i,onDelete:c}){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 u=p(e.enabled,e.expiresAt),d=n===e.id,f=t?.get(e.id),h=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:h.length>0?(0,F.jsx)(g,{content:`绑定提供商(路由优先级从高到低):\n${h.join(`
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-BcWmrhMD.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-Bfquh0d_.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,pendingId:n,onToggle:r,onEdit:i,onDelete:c}){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 u=p(e.enabled,e.expiresAt),d=n===e.id,f=t?.get(e.id),h=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:h.length>0?(0,F.jsx)(g,{content:`绑定提供商(路由优先级从高到低):\n${h.join(`
2
2
  `)}`,children:(0,F.jsx)(`span`,{className:`key-bindings`,children:(()=>{let e=h[0],t=f?.name===e?f:void 0;return(0,F.jsxs)(F.Fragment,{children:[t?(0,F.jsx)(`a`,{href:`#/clients?clientId=${encodeURIComponent(t.clientId)}`,children:e}):e,h.length>1?(0,F.jsx)(`i`,{className:`kb-rank`,children:`优先`}):null]})})()})}):f?(0,F.jsx)(`a`,{href:`#/clients?clientId=${encodeURIComponent(f.clientId)}`,children:f.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[u],children:m[u]}),(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:d?(0,F.jsx)(`span`,{tabIndex:0,children:(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:d,onClick:()=>i(e),children:[(0,F.jsx)(l,{size:12}),`修改`]})}):(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,onClick:()=>i(e),children:[(0,F.jsx)(l,{size:12}),`修改`]})}),(0,F.jsx)(g,{content:u===`exp`?`已过期,需重新创建`:e.enabled?`停用后立即失效`:`重新启用`,children:d||u===`exp`?(0,F.jsx)(`span`,{tabIndex:0,children:(0,F.jsxs)(`button`,{type:`button`,className:`mini-pill`,disabled:!0,onClick:()=>r(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:()=>r(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:d,onClick:()=>c(e),children:[(0,F.jsx)(a,{size:12}),`删除`]})]})})]},e.id)})})]})})}var R={ipText:``,maxIps:``,tokenQuota:``};function z(e){return{ipText:e.ipWhitelist.join(`
3
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
4
  203.0.113.7
@@ -0,0 +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-BcWmrhMD.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-nQeRK3kW.js";import{a as C,c as w,n as T,r as E,s as D,t as O}from"./Select-G6AhT0TA.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-BcWmrhMD.js";import{a as C,c as w,n as T,r as E,s as D,t as O}from"./Select-Bfquh0d_.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-nQeRK3kW.js";import{t as u}from"./accessUrl-DbL5DLDz.js";var d=t(),f=`1.6.16`,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-BcWmrhMD.js";import{t as u}from"./accessUrl-DbL5DLDz.js";var d=t(),f=`1.6.18`,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}),`