dsh-neartoken 0.10.0 → 0.13.0

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
@@ -15,7 +15,7 @@
15
15
  |---|---|
16
16
  | **登录** | 设置页 → `POST /neartoken/authorize` → 站点 `POST /api/dsh/device/authorize` 拿到确认码 → 用户在自己浏览器里批准 → 插件按 `interval` 轮询 `POST /api/dsh/device/token`(`authorization_pending` / `slow_down` 退避 +2s / `access_denied` / `expired_token` / `granted`)→ 把站点铸造的 key 写进 credentials 的 `NEARTOKEN_API_KEY` |
17
17
  | **目录** | `GET /api/pricing`(TTL 缓存)→ 按 (分组, 模型) 展开成 `"<组>::<模型>"` → 可见性过滤 → `listModels` 交给 DSH |
18
- | **出字** | `stream()` → 按模型选协议 → 站点 `/v1/messages`、`/v1beta/models/*:streamGenerateContent`、`/v1/responses`、`/v1/chat/completions`。`body.model` 是裸模型名,`x-neartoken-group` 头带分组的 `#<id>` |
18
+ | **出字** | `stream()` → 按模型选协议 → 站点 `/v1/messages`、`/v1beta/models/*:streamGenerateContent`、`/v1/responses`、`/v1/chat/completions`。`body.model` 是裸模型名,`x-neartoken-group` 头带分组的 `#<id>`,每个请求另带**会话亲和键**(`prompt_cache_key` / `metadata.user_id`,见已知限制) |
19
19
  | **画图** | 工具 `generate_image` → 站点 `/v1/images/generations`(声明了 `image-generation` 的模型)或 `/v1/chat/completions`(gemini 系)→ 取回 bytes → 存进 attachment → 结果里带图 |
20
20
 
21
21
  一条实现约束:provider 只有在凭据存在时才挂载,且 `credentials` 服务可能晚于本插件就绪,所以启动时先同步一次、再用 `ctx.inject` 同步一次。
@@ -27,10 +27,11 @@
27
27
  | `src/host/routes.ts` | 设置页调用的同源路由(状态 / 授权 / 取消 / 断开 / 模型白名单) |
28
28
  | `src/host/whitelist.ts` | 白名单读写在插件自身 Config 的 `enabledModels`(volatile),存 profile patch |
29
29
  | `src/host/pricing.ts` | 拉 `/api/pricing`,展开成 `"<组>::<模型>"` 路由,过滤出能聊的模型 |
30
- | `src/host/model-caps.ts` | 一个模型能不能聊、上下文窗口多大、可选的推理等级 |
30
+ | `src/host/model-caps.ts` | 一个模型能不能聊、上下文窗口多大、可选的推理等级(只读站点声明,不存模型表) |
31
31
  | `src/host/adapter.ts` | 目录、元数据、按模型选协议并流式解析 |
32
32
  | `src/host/*-wire.ts` | 四条上游协议各自的请求体与 SSE 解析 |
33
33
  | `src/host/protocol-router.ts` | 按模型选上游协议(claude / gemini / gpt / 其余) |
34
+ | `src/host/session-key.ts` | 从 `GenerateOptions.sessionId` 取会话亲和键,四条 wire 用它让站点把会话粘在同一渠道 |
34
35
  | `src/host/provider-mount.ts` | 路由挂载、撤下、白名单变更时 `replace()` 重播 |
35
36
  | `src/host/images.ts` | 生图:挑通道、发请求、两种响应都读成 bytes |
36
37
  | `src/host/image-tool.ts` | `generate_image` 工具:选模型/分组、把图写进 attachment |
@@ -42,6 +43,8 @@
42
43
  1. 装插件、重启 `dsh web`、硬刷新。
43
44
  2. 打开 **设置 → NearToken**,点「连接 NearToken」。
44
45
  3. 设置页显示确认码;点「打开浏览器确认」,在站点自己的页面登录(密码、2FA、passkey 都走站点原生流程)并批准。
46
+ 这个按钮走 Host:页面自己 `window.open` 开出来的窗口会被困在承载页面的外壳里(Electron 桌面端会另开一个应用窗口、macOS 上甚至没有任何反应),所以由 Host 调平台启动器交给**系统默认浏览器**(macOS `open` / Windows `rundll32 url.dll,FileProtocolHandler` / Linux `xdg-open`)。地址固定取 Host 自己等待的那个,页面无法让 Host 打开任意 URL。
47
+ 卡片同时把确认地址当作可选文字列出来(带「复制链接」按钮)—— 没有桌面环境的机器(远程、容器)启动器可能不存在,把地址贴进自己的浏览器即可。
45
48
  4. 插件轮询到批准后自动完成:把站点为该插件铸造的 API key 写进 credentials 的 `NEARTOKEN_API_KEY`,并挂上 `neartoken` provider。
46
49
  5. 「断开连接」删掉本地凭据、撤掉 provider,并调用站点的撤销端点。
47
50
  6. 卡片下方的「模型白名单」勾选即自动保存 —— 未勾选的不再出现在模型选择器里,且当场生效(Host 用 `AdapterRegistrationHandle.replace()` 重新声明同一条路由,同步发布 `llm/adapters-updated`,浏览器重拉目录)。没设置过白名单时默认全部显示,「全部显示」清空白名单。
@@ -66,7 +69,7 @@
66
69
  | `imageTimeoutMs` | `300000` | 单次生图的预算(毫秒);工具调用截止时间 = 它 + 30 秒,见下文「超时」 |
67
70
  | `imageGeneration` | `{ enabled: false, models: {} }` | 生图开关与「模型 → 分组」映射;volatile,设置页写 |
68
71
 
69
- `modelOverrides` 是「站点没声明、目录还没收录」时的兜底手段,改完重启即生效,**不需要发新版本**:
72
+ `modelOverrides` 是「站点没声明清楚」时的逐条修正手段,改完重启即生效,**不需要发新版本**:
70
73
 
71
74
  ```yaml
72
75
  - id: dsh-neartoken
@@ -83,31 +86,28 @@
83
86
  站点 `/api/pricing` 会列出它知道的所有模型,插件只提供会响应聊天请求的那部分:
84
87
 
85
88
  1. **站点自己声明的 `supported_endpoint_types` 优先**:含 `openai` / `openai-response` / `anthropic` / `gemini` 任一即可见。这是站点上新增模型当天就能出现在选择器里的原因 —— 不需要等插件发版。
86
- 2. 无论站点声明什么,`-c` 后缀与媒体名(image / tts / transcribe / asr)都不提供 —— 定价接口把这些路由也挂在 `openai` 这个 surface 下,只看声明会把生图和语音模型塞进聊天选择器。
87
- 3. **站点没声明时**回退到名字规则:pi-ai 目录里没有的名字不提供。
89
+ 2. 无论站点声明什么,`-c` 后缀与媒体名都不提供 —— 名单是 `image` / `tts` / `transcribe` / `asr` / `embedding` / `lyria` / `midjourney` / `nano-banana` / `veo` / `imagen` / `dall-e` / `sd*`。**这是名字约定,不是能力元数据,也是当前唯一可用的判据**:定价接口把生图、embedding、音乐、视频模型同样挂在 `openai` / `gemini` 这个 surface 下(它们确实走那条路径),所以 `supported_endpoint_types` 分不出「聊天模型」和「共用该 surface 的媒体模型」。站点若将来能发布模型用途字段,这条约定就可以删。
90
+ 3. **站点没声明时**(老部署)按名字规则放行:插件不再持有模型白名单,所以宁可多列一个也不能让整份目录消失。
88
91
 
89
92
  ## 容量与推理等级
90
93
 
94
+ **模型 metadata 的单一真源是站点。** 插件不内置任何模型表:上下文窗口、最大输出、输入模态、推理档位全部读 `/api/pricing`,站点没声明就是没有(走配置兜底或干脆不提供),不再由插件猜。这样一台站点的容量对**所有客户端一致生效**,插件不需要跟着站点上新模型发版 —— 也因此不存在「插件里的表与站点打架」这种情况。
95
+
96
+ 站点侧在 New API 管理后台按模型维护这四项,`/api/pricing` 以 `context_window` / `max_tokens` / `input_modalities` / `reasoning_levels` 发布。维护步骤见 new-api 仓库 `AGENTS.md` 的「模型能力清单」一节。
97
+
91
98
  上下文窗口(`contextWindow`)决定两件事:对话里的占用率显示,以及自动压缩的阈值。**它缺失时自动压缩会直接放弃**(compaction 后端拒绝为无界模型计算摘要预算),所以插件永远会给出一个值。优先级:
92
99
 
93
100
  | 优先级 | 来源 | 维护方式 |
94
101
  |---|---|---|
95
- | 1 | `modelOverrides` | 改 profile 配置,重启 |
96
- | 2 | 站点 `/api/pricing` 的 `context_window` | 在 New API 管理后台维护,见下 |
97
- | 3 | pi-ai 生成表(`src/host/model-caps.generated.ts`) | `pnpm run gen:model-caps` |
98
- | 4 | `defaultContextWindow` | 改 profile 配置 |
99
-
100
- **推荐在站点侧维护**:New API 管理后台可以给每个模型填上下文窗口与最大输出,`/api/pricing` 会把它们作为 `context_window` / `max_tokens` 发出来。这样一台站点的容量对**所有客户端一致生效**,插件不需要跟着站点上新模型发版。维护步骤见 new-api 仓库 `AGENTS.md` 的「模型容量清单」一节。
101
-
102
- 最大输出(`max_tokens`)只在**站点或配置明确给出**时才写进请求;生成表里的 `maxTokens` 是厂商口径,站点可能把同一个名字路由到上限更低的渠道,照发会把「回答被截断」换成「请求被拒」,所以不用它。不给出该字段时由上游默认值决定。
103
-
104
- 输入模态(`text` / `image`)走同一条优先级链:`modelOverrides.*.input` > 站点 `/api/pricing` 的 `input_modalities` > 厂商文档核对表 > pi-ai 目录 > `['text']`。**未知模型一律声明为纯文本**,因为 harness 把「没有声明」当成「没有权限」:只有声明了 `image` 的模型才允许 `read_image` 和携带图片的请求,否则图片会在发出前被拦下。
102
+ | 1 | `modelOverrides.*.contextWindow` | 改 profile 配置,重启 |
103
+ | 2 | 站点 `/api/pricing` 的 `context_window` | 在 New API 管理后台维护 |
104
+ | 3 | `defaultContextWindow` | 改 profile 配置 |
105
105
 
106
- 站点侧同样能声明模态(New API 后台「Input modalities」两个复选框),所以一个刚上架的多模态模型只要后台勾一下就能用图,不必等插件发版。目录里有 729 个多模态模型作为开箱默认。
106
+ 最大输出(`max_tokens`)只在**站点或配置明确给出**时才写进请求。站点可能把同一个名字路由到上限更低的渠道,照抄厂商口径会把「回答被截断」换成「请求被拒」。不给出该字段时由上游默认值决定。
107
107
 
108
- 推理等级目前仍只来自插件内置表(站点不报),新模型带档位时需要发版或在 `modelOverrides.*.reasoning` 里写明。
108
+ 输入模态(`text` / `image`)优先级:`modelOverrides.*.input` > 站点 `input_modalities` > `['text']`。**站点没声明的模型一律当纯文本**,因为 harness 把「没有声明」当成「没有权限」:只有声明了 `image` 的模型才允许 `read_image` 和携带图片的请求,否则图片会在发出前被拦下。所以后台忘了勾 `image` 的模型,症状是「贴图被拦」而不是「报错」。
109
109
 
110
- 推理等级来自 `@earendil-works/pi-ai` 的模型目录(DSH 的 `llm-pi-ai` 用的同一份数据)。插件不在运行时依赖它:`scripts/gen-model-caps.mjs` 把目录生成成 `src/host/model-caps.generated.ts`(1101 个 chat 模型、486 个带推理档位、1101 个带上下文窗口),升级依赖后跑 `pnpm run gen:model-caps` 重建。同一模型 id 在不同 provider 下可能给出不同档位(网关常只列两档,厂商列六档),生成时取**最完整**的那份;上下文窗口取**最小**的那份。
110
+ 推理档位(`minimal` / `low` / `medium` / `high` / `xhigh` / `max`)优先级:`modelOverrides.*.reasoning` > 站点 `reasoning_levels` > 无档位。**站点没声明就没有档位选择器** —— 与其发一个上游不认的档位换来 400,不如不提供。
111
111
 
112
112
  ## 图像生成(`generate_image`)
113
113
 
@@ -187,13 +187,14 @@ Gemini 系走原生 surface 而不是 openai 兼容层:同模型同提示词
187
187
 
188
188
  - 四套 wire(chat / anthropic / genai / responses)各自解析 SSE;**用户消息里的图片按各协议原样发送** —— chat 用 `image_url` part、anthropic 用 `source.base64`、genai 用 `inlineData`、responses 用 `input_image`。字节由 `attachments.readImage` 在请求前内联;没有 attachments 服务或读不出来会**让请求失败**,不再静默降级成纯文本(曾经就是这样:贴的截图模型根本收不到)。文件块(非图片附件)仍不发;`/model` 弹窗仍是内置平铺列表;箭头键盘导航未接。
189
189
  - 四套 wire 的 SSE 解析都有「帧样本 → 期望 chunk 序列」回归测试(`src/host/wire-parse.spec.ts`),帧样本取自生产网关的真实响应。网关在内容帧上发 `"usage": null`,只有末帧带真值:任何一处把 `usage` 当对象直接读都会让整条流中途崩掉(曾导致 claude 模型全不可用)。
190
+ - **每个请求都带会话亲和键**:chat 与 responses 发 `prompt_cache_key`,anthropic 发 `metadata.user_id`,取值都是 DSH 的 `GenerateOptions.sessionId`。站点就是靠这个值挑上游渠道的(New API 的「codex cli trace」规则:`^gpt-.*$` + `/v1/responses`,键取自 body 的 `prompt_cache_key`,取不到就把整条规则跳过),而提示词缓存按**上游账号**存 —— 不带键时每个请求随机换账号,缓存永远不中。实测同一 6k 前缀:不带键连发 4 次 `cached_tokens` 全 0;带键后第 2 次起 5888/6011 ≈ 98%。`/v1/chat/completions` 上目前没有对应规则,chat 线即使带键也只是偶发命中,要彻底解决需要站点加一条匹配该路径的亲和规则;anthropic 线的 `metadata.user_id` 同理(规则只作用于 `/v1/messages`,这一条已经匹配)。
190
191
  - 推理等级按各协议自己的词汇发送:**anthropic 发 `thinking.type=adaptive` + `output_config.effort`**(不能再用 `thinking.type=enabled` + `budget_tokens`:Claude 5 代直接 400 `requires adaptive thinking`,而 adaptive 在老代际上同样可用,所以统一用它;`minimal` 收敛成 `low`);genai 发 `thinkingConfig` 且带 `includeThoughts`(不带你拿不到思维链);chat 与 responses 发 `reasoning_effort` / `reasoning.effort`。
191
192
  - **Gemini 4 换成 `thinkingLevel`**:这一代不再接受 token 预算(发 budget 会 400 `Use thinking level instead`),按 `gemini-<主版本>` ≥ 4 自动切换字段,等级名用 `low` / `medium` / `high` / `max`。
192
193
  - 思考历史**不回传**:harness 的 reasoning 块没有 signature 槽位,anthropic 的 `signature_delta` 与 gemini 的 `thoughtSignature` 都被丢弃。当前网关的转换层本身也会丢弃 thinking 块,所以多轮不会因此报错;接真正的 Anthropic / Gemini 直连渠道时这一条会变成硬限制。
193
194
  - 文本与思维块由**第一个非空 delta** 打开,思维块为空时不产生空块;工具调用块在 `content_block_start` 打开,因为 id 与函数名只在那里出现。
194
195
  - anthropic 线不硬编码 `max_tokens`:只在调用方给了上限、或开了 thinking 需要余量时才发。给死值会截断长回答。
195
196
  - `-c` 后缀是站点自己的约定(带该后缀的模型工具调用不可靠):站点声明了端点类型时插件按声明提供,声明里没有它就不会出现;没有声明时按名字隐藏它的无后缀孪生之外的变体。
196
- - 站点上新、pi-ai 目录还没收录的模型:只要站点声明了端点类型就会提供,容量走 `defaultContextWindow`,推理等级为空(除非在 `modelOverrides` 里写明)。
197
+ - 站点上新但还没在后台填能力的模型:只要声明了端点类型就会提供,容量走 `defaultContextWindow`,模态按纯文本、没有推理档位(除非在 `modelOverrides` 里写明)。这不是 bug,是「站点是唯一真源」的直接后果——补能力请去后台填,不必等插件发版。
197
198
  - pricing 加载失败时沿用上一次成功的结果,从未成功过则目录为空(失败不写缓存,下次调用会重试)。
198
199
  - 账号卡片不自动刷新:数字只来自授权那一刻 grant 里的 `user`(站点的 dashboard 接口认浏览器会话,不认本插件持有的 relay key),落盘到 profile 下的 `neartoken-account.json`,重启后从文件恢复。之后余额变化不会反映,需要重新连接一次。
199
200
  - 白名单按裸模型名过滤:勾掉某模型会把它的所有分组一起隐藏;某个正在使用的模型被勾掉后,会话里已选中的 id 仍在,但选择器里不再列出它。
@@ -209,6 +210,5 @@ pnpm install
209
210
  pnpm run typecheck
210
211
  pnpm run test # vitest:account / pricing / adapter / model-caps / groups / whitelist / routes / config / wire-parse / images / image-settings / image-tool
211
212
  pnpm run typecheck # 两个 face 分别检查(host 与 client 的 ctx.sessions 声明互相冲突,不能同 program)
212
- pnpm run gen:model-caps # 升级 @earendil-works/pi-ai 后重建能力表
213
213
  pnpm run build # lib/index.js (host) + lib/client.js (client) + lib/types
214
214
  ```
package/lib/client.js CHANGED
@@ -10,7 +10,7 @@ window.__ModuleLoader__.load({
10
10
  let react_jsx_runtime = require("react/jsx-runtime");
11
11
  //#region src/protocol.ts
12
12
  /**
13
- * The wire vocabulary shared by the Host routes and the browser section: four
13
+ * The wire vocabulary shared by the Host routes and the browser section: the
14
14
  * route paths and the JSON each answers. Both build faces import this file, so
15
15
  * a route rename cannot leave the browser calling a path the Host stopped
16
16
  * serving.
@@ -27,6 +27,11 @@ window.__ModuleLoader__.load({
27
27
  const AUTHORIZE_PATH = "/neartoken/authorize";
28
28
  /** Abandon the authorization in progress. */
29
29
  const CANCEL_PATH = "/neartoken/cancel";
30
+ /**
31
+ * Ask the Host to open the pending confirmation page in the user's own browser.
32
+ * The request carries no address: the Host opens the one it is waiting on.
33
+ */
34
+ const OPEN_PATH = "/neartoken/open";
30
35
  /** Forget the stored key and revoke it on the site. */
31
36
  const LOGOUT_PATH = "/neartoken/logout";
32
37
  /** Read the catalog beside the stored whitelist; POST a new whitelist. */
@@ -36,8 +41,8 @@ window.__ModuleLoader__.load({
36
41
  //#endregion
37
42
  //#region src/client/api.ts
38
43
  /**
39
- * The browser half of the settings-page carrier: four same-origin calls to the
40
- * Host routes declared in ../protocol.ts.
44
+ * The browser half of the settings-page carrier: same-origin calls to the Host
45
+ * routes declared in ../protocol.ts.
41
46
  *
42
47
  * @module
43
48
  */
@@ -73,6 +78,13 @@ window.__ModuleLoader__.load({
73
78
  async cancel() {
74
79
  await post(CANCEL_PATH);
75
80
  },
81
+ async openBrowser() {
82
+ const response = await fetcher(OPEN_PATH, { method: "POST" });
83
+ if (!response.ok) {
84
+ const refusal = await response.json().catch(() => ({ message: `HTTP ${String(response.status)}` }));
85
+ throw new Error(refusal.message ?? `HTTP ${String(response.status)}`);
86
+ }
87
+ },
76
88
  async signOut() {
77
89
  await post(LOGOUT_PATH);
78
90
  },
@@ -321,23 +333,23 @@ window.__ModuleLoader__.load({
321
333
  document.head.appendChild(tag);
322
334
  }
323
335
  var group_tags_module_css_default = {
336
+ "toneViolet": "FotuUq_toneViolet",
337
+ "toneEmerald": "FotuUq_toneEmerald",
338
+ "toneOrange": "FotuUq_toneOrange",
339
+ "toneRose": "FotuUq_toneRose",
340
+ "toneTeal": "FotuUq_toneTeal",
324
341
  "ratioBase": "FotuUq_ratioBase",
325
342
  "toneCyan": "FotuUq_toneCyan",
326
- "toneLime": "FotuUq_toneLime",
327
- "toneRose": "FotuUq_toneRose",
328
- "tonePink": "FotuUq_tonePink",
329
- "toneIndigo": "FotuUq_toneIndigo",
330
- "toneOrange": "FotuUq_toneOrange",
331
343
  "ratioCheap": "FotuUq_ratioCheap",
332
- "ratioChip": "FotuUq_ratioChip",
333
- "toneTeal": "FotuUq_toneTeal",
334
- "toneViolet": "FotuUq_toneViolet",
335
344
  "toneSky": "FotuUq_toneSky",
336
- "toneAmber": "FotuUq_toneAmber",
337
- "toneFuchsia": "FotuUq_toneFuchsia",
345
+ "toneIndigo": "FotuUq_toneIndigo",
338
346
  "tag": "FotuUq_tag",
347
+ "toneLime": "FotuUq_toneLime",
339
348
  "ratioPricey": "FotuUq_ratioPricey",
340
- "toneEmerald": "FotuUq_toneEmerald"
349
+ "tonePink": "FotuUq_tonePink",
350
+ "toneAmber": "FotuUq_toneAmber",
351
+ "ratioChip": "FotuUq_ratioChip",
352
+ "toneFuchsia": "FotuUq_toneFuchsia"
341
353
  };
342
354
  //#endregion
343
355
  //#region src/client/group-tags.tsx
@@ -392,39 +404,39 @@ window.__ModuleLoader__.load({
392
404
  document.head.appendChild(tag);
393
405
  }
394
406
  var ModelGroupSelect_module_css_default = {
407
+ "triggerEffort": "_2pt-bW_triggerEffort",
408
+ "check": "_2pt-bW_check",
409
+ "trigger": "_2pt-bW_trigger",
410
+ "searchClear": "_2pt-bW_searchClear",
411
+ "search": "_2pt-bW_search",
412
+ "cell": "_2pt-bW_cell",
413
+ "triggerLabel": "_2pt-bW_triggerLabel",
414
+ "modelOption": "_2pt-bW_modelOption",
415
+ "optionActive": "_2pt-bW_optionActive",
416
+ "groups": "_2pt-bW_groups",
417
+ "groupOptionLabel": "_2pt-bW_groupOptionLabel",
418
+ "status": "_2pt-bW_status",
419
+ "empty": "_2pt-bW_empty",
395
420
  "searchRow": "_2pt-bW_searchRow",
396
- "cellChevron": "_2pt-bW_cellChevron",
421
+ "menu": "_2pt-bW_menu",
422
+ "root": "_2pt-bW_root",
397
423
  "toast": "_2pt-bW_toast",
398
- "chevron": "_2pt-bW_chevron",
399
- "retry": "_2pt-bW_retry",
424
+ "cellChevron": "_2pt-bW_cellChevron",
400
425
  "option": "_2pt-bW_option",
401
- "groups": "_2pt-bW_groups",
402
- "modelName": "_2pt-bW_modelName",
403
- "groupOptionLabel": "_2pt-bW_groupOptionLabel",
404
- "groupInline": "_2pt-bW_groupInline",
405
- "triggerIcon": "_2pt-bW_triggerIcon",
426
+ "groupName": "_2pt-bW_groupName",
406
427
  "optionTrailing": "_2pt-bW_optionTrailing",
428
+ "retry": "_2pt-bW_retry",
429
+ "chevron": "_2pt-bW_chevron",
430
+ "selected": "_2pt-bW_selected",
431
+ "optionCopy": "_2pt-bW_optionCopy",
407
432
  "cellLabel": "_2pt-bW_cellLabel",
408
433
  "error": "_2pt-bW_error",
409
- "modelOption": "_2pt-bW_modelOption",
410
- "groupName": "_2pt-bW_groupName",
411
- "search": "_2pt-bW_search",
412
- "triggerEffort": "_2pt-bW_triggerEffort",
434
+ "modelName": "_2pt-bW_modelName",
413
435
  "variantCount": "_2pt-bW_variantCount",
414
- "check": "_2pt-bW_check",
436
+ "triggerIcon": "_2pt-bW_triggerIcon",
415
437
  "cellValue": "_2pt-bW_cellValue",
416
- "status": "_2pt-bW_status",
417
438
  "warning": "_2pt-bW_warning",
418
- "optionCopy": "_2pt-bW_optionCopy",
419
- "root": "_2pt-bW_root",
420
- "trigger": "_2pt-bW_trigger",
421
- "menu": "_2pt-bW_menu",
422
- "triggerLabel": "_2pt-bW_triggerLabel",
423
- "empty": "_2pt-bW_empty",
424
- "searchClear": "_2pt-bW_searchClear",
425
- "optionActive": "_2pt-bW_optionActive",
426
- "selected": "_2pt-bW_selected",
427
- "cell": "_2pt-bW_cell"
439
+ "groupInline": "_2pt-bW_groupInline"
428
440
  };
429
441
  //#endregion
430
442
  //#region src/client/ModelGroupSelect.tsx
@@ -980,7 +992,7 @@ window.__ModuleLoader__.load({
980
992
  }
981
993
  //#endregion
982
994
  //#region \0dsh-css:D:\Projects\deepseek-harness\dsh-neartoken\src\client\NearTokenSection.module.css.mjs
983
- const css = "._02HI2q_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}._02HI2q_title{margin:0;font-size:16px;font-weight:500;line-height:24px}._02HI2q_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}._02HI2q_card{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;padding:14px 16px;display:flex}._02HI2q_cardHead{align-items:baseline;gap:10px;display:flex}._02HI2q_name{font-size:15px;font-weight:500;line-height:22px}._02HI2q_handle{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._02HI2q_badge{background:var(--dsw-alias-button-elevated-fill);color:var(--dsw-alias-label-secondary);border-radius:999px;margin-left:auto;padding:2px 8px;font-size:12px;line-height:18px}._02HI2q_badgeOn{color:var(--dsw-alias-state-success-primary)}._02HI2q_facts{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover);grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:14px 18px;margin:0;padding:12px 14px;display:grid}._02HI2q_fact{flex-direction:column;gap:2px;display:flex}._02HI2q_factLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._02HI2q_factValue{font-variant-numeric:tabular-nums;letter-spacing:-.01em;color:var(--dsw-alias-label-primary);font-size:15px;font-weight:500;line-height:22px}._02HI2q_form{flex-direction:column;gap:10px;display:flex}._02HI2q_label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}._02HI2q_codeRow{align-items:center;gap:10px;display:flex}._02HI2q_code{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);letter-spacing:.08em;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-button-elevated-fill);color:var(--dsw-alias-label-primary);user-select:all;padding:4px 12px;font-size:18px;line-height:26px}._02HI2q_actions{align-items:center;gap:10px;display:flex}._02HI2q_button{border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-button-elevated-fill);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;padding:0 14px;font-size:14px}._02HI2q_buttonPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-inverted);border-color:#0000}._02HI2q_button:disabled{opacity:.55;cursor:default}._02HI2q_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}._02HI2q_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}._02HI2q_search{border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-button-elevated-fill);height:32px;color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px}._02HI2q_search::placeholder{color:var(--dsw-alias-label-dimmed)}._02HI2q_toolbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._02HI2q_autosave{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;line-height:18px}._02HI2q_list{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-md);flex-direction:column;max-height:320px;padding:4px;display:flex;overflow-y:auto}._02HI2q_row{border-radius:var(--dsw-radius-sm);cursor:pointer;align-items:baseline;gap:8px;padding:4px 6px;font-size:13px;line-height:20px;display:flex}._02HI2q_row:hover{background:var(--dsw-alias-interactive-bg-hover)}._02HI2q_row input{accent-color:var(--dsw-alias-button-primary-fill);align-self:center}._02HI2q_rowName{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:auto;overflow:hidden}._02HI2q_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;flex:none;align-items:baseline;gap:8px;font-size:12px;display:flex}._02HI2q_rowRatio{color:var(--dsw-alias-label-secondary)}._02HI2q_tip{z-index:40;border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-settings-card-fill);pointer-events:none;flex-direction:column;gap:2px;width:280px;max-height:240px;padding:8px 10px;font-size:12px;line-height:18px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000024}._02HI2q_tipTitle{color:var(--dsw-alias-label-tertiary)}._02HI2q_tipRow{justify-content:space-between;align-items:baseline;gap:10px;display:flex}._02HI2q_tipGroup{min-width:0;color:var(--dsw-alias-label-primary);align-items:center;gap:4px;display:flex;overflow:hidden}._02HI2q_tipGroupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._02HI2q_select{border:1px solid var(--dsw-color-border-subtle,#0000001f);max-width:60%;color:inherit;font:inherit;background:0 0;border-radius:6px;margin-left:auto;padding:2px 6px;font-size:12px}._02HI2q_select:disabled{opacity:.5}._02HI2q_cardToggle{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:8px;padding:0;display:flex}._02HI2q_cardToggle:hover ._02HI2q_name{color:var(--dsw-alias-label-primary)}._02HI2q_count{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;margin-left:auto;font-size:12px;line-height:18px;display:inline-flex}._02HI2q_countDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;width:3px;height:3px}._02HI2q_countPicked{color:var(--dsw-alias-label-secondary)}._02HI2q_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .12s}._02HI2q_chevronOpen{transform:rotate(180deg)}";
995
+ const css = "._02HI2q_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}._02HI2q_title{margin:0;font-size:16px;font-weight:500;line-height:24px}._02HI2q_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}._02HI2q_card{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;padding:14px 16px;display:flex}._02HI2q_cardHead{align-items:baseline;gap:10px;display:flex}._02HI2q_name{font-size:15px;font-weight:500;line-height:22px}._02HI2q_handle{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._02HI2q_badge{background:var(--dsw-alias-button-elevated-fill);color:var(--dsw-alias-label-secondary);border-radius:999px;margin-left:auto;padding:2px 8px;font-size:12px;line-height:18px}._02HI2q_badgeOn{color:var(--dsw-alias-state-success-primary)}._02HI2q_facts{border-radius:var(--dsw-radius-md);background:var(--dsw-alias-interactive-bg-hover);grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:14px 18px;margin:0;padding:12px 14px;display:grid}._02HI2q_fact{flex-direction:column;gap:2px;display:flex}._02HI2q_factLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}._02HI2q_factValue{font-variant-numeric:tabular-nums;letter-spacing:-.01em;color:var(--dsw-alias-label-primary);font-size:15px;font-weight:500;line-height:22px}._02HI2q_voucherFact{grid-column:1/-1}._02HI2q_voucherList{flex-direction:column;gap:2px;margin:0;display:flex}._02HI2q_voucherRow{justify-content:space-between;align-items:baseline;gap:10px;font-size:13px;line-height:20px;display:flex}._02HI2q_voucherGroup{color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._02HI2q_voucherAmount{color:var(--dsw-alias-state-success-primary);font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:500}._02HI2q_form{flex-direction:column;gap:10px;display:flex}._02HI2q_label{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}._02HI2q_codeRow{align-items:center;gap:10px;display:flex}._02HI2q_code{font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);letter-spacing:.08em;border-radius:var(--dsw-radius-md);background:var(--dsw-alias-button-elevated-fill);color:var(--dsw-alias-label-primary);user-select:all;padding:4px 12px;font-size:18px;line-height:26px}._02HI2q_linkRow{flex-direction:column;gap:4px;display:flex}._02HI2q_linkLine{align-items:flex-start;gap:8px;display:flex}._02HI2q_link{min-width:0;font-family:var(--dsw-font-mono,ui-monospace, SFMono-Regular, Menlo, monospace);word-break:break-all;color:var(--dsw-alias-label-secondary);user-select:all;flex:auto;font-size:12px;line-height:18px}._02HI2q_actions{align-items:center;gap:10px;display:flex}._02HI2q_button{border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-button-elevated-fill);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;padding:0 14px;font-size:14px}._02HI2q_buttonPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-inverted);border-color:#0000}._02HI2q_button:disabled{opacity:.55;cursor:default}._02HI2q_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}._02HI2q_hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}._02HI2q_search{border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-button-elevated-fill);height:32px;color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px}._02HI2q_search::placeholder{color:var(--dsw-alias-label-dimmed)}._02HI2q_toolbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._02HI2q_autosave{color:var(--dsw-alias-label-tertiary);margin-left:auto;font-size:12px;line-height:18px}._02HI2q_list{border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-md);flex-direction:column;max-height:320px;padding:4px;display:flex;overflow-y:auto}._02HI2q_row{border-radius:var(--dsw-radius-sm);cursor:pointer;align-items:baseline;gap:8px;padding:4px 6px;font-size:13px;line-height:20px;display:flex}._02HI2q_row:hover{background:var(--dsw-alias-interactive-bg-hover)}._02HI2q_row input{accent-color:var(--dsw-alias-button-primary-fill);align-self:center}._02HI2q_rowName{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:auto;overflow:hidden}._02HI2q_rowMeta{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;flex:none;align-items:baseline;gap:8px;font-size:12px;display:flex}._02HI2q_rowRatio{color:var(--dsw-alias-label-secondary)}._02HI2q_tip{z-index:40;border:.5px solid var(--dsw-alias-settings-card-stroke);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-settings-card-fill);pointer-events:none;flex-direction:column;gap:2px;width:280px;max-height:240px;padding:8px 10px;font-size:12px;line-height:18px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000024}._02HI2q_tipTitle{color:var(--dsw-alias-label-tertiary)}._02HI2q_tipRow{justify-content:space-between;align-items:baseline;gap:10px;display:flex}._02HI2q_tipGroup{min-width:0;color:var(--dsw-alias-label-primary);align-items:center;gap:4px;display:flex;overflow:hidden}._02HI2q_tipGroupName{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._02HI2q_select{border:1px solid var(--dsw-color-border-subtle,#0000001f);max-width:60%;color:inherit;font:inherit;background:0 0;border-radius:6px;margin-left:auto;padding:2px 6px;font-size:12px}._02HI2q_select:disabled{opacity:.5}._02HI2q_cardToggle{width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;align-items:center;gap:8px;padding:0;display:flex}._02HI2q_cardToggle:hover ._02HI2q_name{color:var(--dsw-alias-label-primary)}._02HI2q_count{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary);align-items:center;gap:8px;margin-left:auto;font-size:12px;line-height:18px;display:inline-flex}._02HI2q_countDot{background:var(--dsw-alias-label-dimmed);border-radius:50%;width:3px;height:3px}._02HI2q_countPicked{color:var(--dsw-alias-label-secondary)}._02HI2q_chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .12s}._02HI2q_chevronOpen{transform:rotate(180deg)}";
984
996
  const tagId = "dsh-neartoken/NearTokenSection.module.css";
985
997
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
986
998
  const tag = document.createElement("style");
@@ -990,48 +1002,56 @@ window.__ModuleLoader__.load({
990
1002
  document.head.appendChild(tag);
991
1003
  }
992
1004
  var NearTokenSection_module_css_default = {
993
- "facts": "_02HI2q_facts",
994
- "cardToggle": "_02HI2q_cardToggle",
995
- "cardHead": "_02HI2q_cardHead",
996
- "fact": "_02HI2q_fact",
997
- "actions": "_02HI2q_actions",
998
- "codeRow": "_02HI2q_codeRow",
999
- "handle": "_02HI2q_handle",
1000
- "autosave": "_02HI2q_autosave",
1001
- "tipGroupName": "_02HI2q_tipGroupName",
1005
+ "factValue": "_02HI2q_factValue",
1006
+ "code": "_02HI2q_code",
1002
1007
  "button": "_02HI2q_button",
1008
+ "voucherRow": "_02HI2q_voucherRow",
1009
+ "row": "_02HI2q_row",
1010
+ "form": "_02HI2q_form",
1011
+ "count": "_02HI2q_count",
1003
1012
  "label": "_02HI2q_label",
1004
- "rowName": "_02HI2q_rowName",
1013
+ "linkLine": "_02HI2q_linkLine",
1014
+ "fact": "_02HI2q_fact",
1015
+ "voucherAmount": "_02HI2q_voucherAmount",
1016
+ "autosave": "_02HI2q_autosave",
1017
+ "rowRatio": "_02HI2q_rowRatio",
1018
+ "title": "_02HI2q_title",
1019
+ "tipTitle": "_02HI2q_tipTitle",
1020
+ "select": "_02HI2q_select",
1021
+ "factLabel": "_02HI2q_factLabel",
1022
+ "cardToggle": "_02HI2q_cardToggle",
1005
1023
  "card": "_02HI2q_card",
1024
+ "hint": "_02HI2q_hint",
1025
+ "link": "_02HI2q_link",
1026
+ "list": "_02HI2q_list",
1027
+ "cardHead": "_02HI2q_cardHead",
1028
+ "handle": "_02HI2q_handle",
1029
+ "linkRow": "_02HI2q_linkRow",
1030
+ "buttonPrimary": "_02HI2q_buttonPrimary",
1031
+ "notice": "_02HI2q_notice",
1006
1032
  "intro": "_02HI2q_intro",
1007
- "code": "_02HI2q_code",
1008
- "rowRatio": "_02HI2q_rowRatio",
1033
+ "rowMeta": "_02HI2q_rowMeta",
1009
1034
  "tipRow": "_02HI2q_tipRow",
1010
- "select": "_02HI2q_select",
1011
- "countDot": "_02HI2q_countDot",
1035
+ "tipGroupName": "_02HI2q_tipGroupName",
1036
+ "facts": "_02HI2q_facts",
1037
+ "countPicked": "_02HI2q_countPicked",
1038
+ "voucherFact": "_02HI2q_voucherFact",
1039
+ "section": "_02HI2q_section",
1040
+ "voucherGroup": "_02HI2q_voucherGroup",
1012
1041
  "tipGroup": "_02HI2q_tipGroup",
1013
- "name": "_02HI2q_name",
1014
- "notice": "_02HI2q_notice",
1015
- "list": "_02HI2q_list",
1016
- "factLabel": "_02HI2q_factLabel",
1017
- "form": "_02HI2q_form",
1018
1042
  "tip": "_02HI2q_tip",
1019
- "badge": "_02HI2q_badge",
1020
- "rowMeta": "_02HI2q_rowMeta",
1021
1043
  "chevron": "_02HI2q_chevron",
1022
1044
  "chevronOpen": "_02HI2q_chevronOpen",
1023
1045
  "toolbar": "_02HI2q_toolbar",
1024
- "hint": "_02HI2q_hint",
1046
+ "actions": "_02HI2q_actions",
1047
+ "rowName": "_02HI2q_rowName",
1048
+ "badge": "_02HI2q_badge",
1049
+ "voucherList": "_02HI2q_voucherList",
1025
1050
  "search": "_02HI2q_search",
1026
- "tipTitle": "_02HI2q_tipTitle",
1027
- "count": "_02HI2q_count",
1028
- "countPicked": "_02HI2q_countPicked",
1029
- "factValue": "_02HI2q_factValue",
1030
- "buttonPrimary": "_02HI2q_buttonPrimary",
1031
- "row": "_02HI2q_row",
1051
+ "countDot": "_02HI2q_countDot",
1052
+ "codeRow": "_02HI2q_codeRow",
1032
1053
  "badgeOn": "_02HI2q_badgeOn",
1033
- "title": "_02HI2q_title",
1034
- "section": "_02HI2q_section"
1054
+ "name": "_02HI2q_name"
1035
1055
  };
1036
1056
  //#endregion
1037
1057
  //#region src/client/NearTokenSection.tsx
@@ -1046,8 +1066,15 @@ window.__ModuleLoader__.load({
1046
1066
  */
1047
1067
  /** How often the page re-reads the Host while an authorization is pending. */
1048
1068
  const PENDING_REFRESH_MS = 1500;
1069
+ /**
1070
+ * How often the page re-reads the Host while connected, so the card shows the
1071
+ * wallet numbers the Host re-reads at the same cadence.
1072
+ */
1073
+ const CONNECTED_REFRESH_MS = 3e5;
1049
1074
  /** How long a burst of checkbox clicks waits before one save is written. */
1050
1075
  const AUTOSAVE_DELAY_MS = 400;
1076
+ /** How long the copy button acknowledges a copied address before resetting. */
1077
+ const COPIED_RESET_MS = 2e3;
1051
1078
  /**
1052
1079
  * Render the NearToken settings page.
1053
1080
  * @param props - the injected route carrier and translate, plus slot owner props.
@@ -1057,6 +1084,8 @@ window.__ModuleLoader__.load({
1057
1084
  const [status, setStatus] = (0, react.useState)(void 0);
1058
1085
  const [busy, setBusy] = (0, react.useState)(false);
1059
1086
  const [error, setError] = (0, react.useState)(void 0);
1087
+ /** Whether the confirmation address was just copied to the clipboard. */
1088
+ const [copied, setCopied] = (0, react.useState)(false);
1060
1089
  /** Re-read the Host state and publish it. */
1061
1090
  const refresh = (0, react.useCallback)(async () => {
1062
1091
  try {
@@ -1070,6 +1099,18 @@ window.__ModuleLoader__.load({
1070
1099
  }, [refresh]);
1071
1100
  const pendingCode = status?.pending?.userCode;
1072
1101
  const waiting = pendingCode !== void 0;
1102
+ (0, react.useEffect)(() => {
1103
+ setCopied(false);
1104
+ }, [pendingCode]);
1105
+ (0, react.useEffect)(() => {
1106
+ if (!copied) return;
1107
+ const timer = setTimeout(() => {
1108
+ setCopied(false);
1109
+ }, COPIED_RESET_MS);
1110
+ return () => {
1111
+ clearTimeout(timer);
1112
+ };
1113
+ }, [copied]);
1073
1114
  (0, react.useEffect)(() => {
1074
1115
  if (!waiting) return;
1075
1116
  const timer = setInterval(() => {
@@ -1083,6 +1124,16 @@ window.__ModuleLoader__.load({
1083
1124
  pendingCode,
1084
1125
  refresh
1085
1126
  ]);
1127
+ const connected = status?.configured === true;
1128
+ (0, react.useEffect)(() => {
1129
+ if (!connected) return;
1130
+ const timer = setInterval(() => {
1131
+ refresh();
1132
+ }, CONNECTED_REFRESH_MS);
1133
+ return () => {
1134
+ clearInterval(timer);
1135
+ };
1136
+ }, [connected, refresh]);
1086
1137
  /** Run one Host call with the busy flag and the error channel wired. */
1087
1138
  const run = (0, react.useCallback)(async (action) => {
1088
1139
  setBusy(true);
@@ -1108,6 +1159,37 @@ window.__ModuleLoader__.load({
1108
1159
  await refresh();
1109
1160
  });
1110
1161
  };
1162
+ /**
1163
+ * Hand the confirmation page to the user's own browser, through the Host: a
1164
+ * window the page opens stays inside the shell hosting it — a desktop build
1165
+ * shows another application window, and macOS may refuse outright. The
1166
+ * address is printed below as well, for a machine with no launcher at all.
1167
+ */
1168
+ const openAddress = () => {
1169
+ (async () => {
1170
+ setError(void 0);
1171
+ try {
1172
+ await api.openBrowser();
1173
+ } catch (failure) {
1174
+ setError(t("account.openFailed", { message: messageOf(failure) }));
1175
+ }
1176
+ })();
1177
+ };
1178
+ /**
1179
+ * Copy the confirmation address, for a machine where nothing can be launched.
1180
+ */
1181
+ const copyAddress = () => {
1182
+ const address = status?.pending?.verificationUriComplete;
1183
+ if (address === void 0) return;
1184
+ (async () => {
1185
+ try {
1186
+ await navigator.clipboard.writeText(address);
1187
+ setCopied(true);
1188
+ } catch {
1189
+ setError(t("account.copyFailed"));
1190
+ }
1191
+ })();
1192
+ };
1111
1193
  const signOut = () => {
1112
1194
  run(async () => {
1113
1195
  await api.signOut();
@@ -1189,7 +1271,26 @@ window.__ModuleLoader__.load({
1189
1271
  className: NearTokenSection_module_css_default.factValue,
1190
1272
  children: status.tokenName
1191
1273
  })]
1192
- })
1274
+ }),
1275
+ account !== void 0 && (account.vouchers?.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1276
+ className: `${NearTokenSection_module_css_default.fact} ${NearTokenSection_module_css_default.voucherFact}`,
1277
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
1278
+ className: NearTokenSection_module_css_default.factLabel,
1279
+ children: t("account.vouchers")
1280
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
1281
+ className: NearTokenSection_module_css_default.voucherList,
1282
+ children: account.vouchers?.map((voucher) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1283
+ className: NearTokenSection_module_css_default.voucherRow,
1284
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1285
+ className: NearTokenSection_module_css_default.voucherGroup,
1286
+ children: voucher.group
1287
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1288
+ className: NearTokenSection_module_css_default.voucherAmount,
1289
+ children: voucher.amount
1290
+ })]
1291
+ }, `${voucher.groupId}:${voucher.group}`))
1292
+ })]
1293
+ }) : null
1193
1294
  ]
1194
1295
  }),
1195
1296
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1255,9 +1356,7 @@ window.__ModuleLoader__.load({
1255
1356
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1256
1357
  type: "button",
1257
1358
  className: `${NearTokenSection_module_css_default.button} ${NearTokenSection_module_css_default.buttonPrimary}`,
1258
- onClick: () => {
1259
- window.open(pending.verificationUriComplete, "_blank", "noopener,noreferrer");
1260
- },
1359
+ onClick: openAddress,
1261
1360
  children: t("account.openBrowser")
1262
1361
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1263
1362
  type: "button",
@@ -1267,6 +1366,27 @@ window.__ModuleLoader__.load({
1267
1366
  children: busy ? t("account.cancelling") : t("account.cancel")
1268
1367
  })]
1269
1368
  }),
1369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1370
+ className: NearTokenSection_module_css_default.linkRow,
1371
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1372
+ className: NearTokenSection_module_css_default.label,
1373
+ children: t("account.manualLink")
1374
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1375
+ className: NearTokenSection_module_css_default.linkLine,
1376
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1377
+ className: NearTokenSection_module_css_default.link,
1378
+ href: pending.verificationUriComplete,
1379
+ target: "_blank",
1380
+ rel: "noopener noreferrer",
1381
+ children: pending.verificationUriComplete
1382
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1383
+ type: "button",
1384
+ className: NearTokenSection_module_css_default.button,
1385
+ onClick: copyAddress,
1386
+ children: copied ? t("account.copied") : t("account.copyLink")
1387
+ })]
1388
+ })]
1389
+ }),
1270
1390
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1271
1391
  className: NearTokenSection_module_css_default.hint,
1272
1392
  children: t("account.expiresIn", { minutes: String(minutes) })
@@ -1865,6 +1985,11 @@ window.__ModuleLoader__.load({
1865
1985
  "account.waiting": "已发起授权,请在浏览器中确认。",
1866
1986
  "account.userCode": "确认码",
1867
1987
  "account.openBrowser": "打开浏览器确认",
1988
+ "account.manualLink": "打不开浏览器时,手动打开下面的地址:",
1989
+ "account.copyLink": "复制链接",
1990
+ "account.copied": "已复制",
1991
+ "account.copyFailed": "复制失败,请手动选中上面的链接复制。",
1992
+ "account.openFailed": "未能打开浏览器({message});请复制下面的地址,在自己的浏览器里访问。",
1868
1993
  "account.expiresIn": "本次确认 {minutes} 分钟内有效",
1869
1994
  "account.cancel": "取消",
1870
1995
  "account.cancelling": "正在取消…",
@@ -1873,6 +1998,7 @@ window.__ModuleLoader__.load({
1873
1998
  "account.group": "用户分组",
1874
1999
  "account.balance": "剩余额度",
1875
2000
  "account.used": "已用额度",
2001
+ "account.vouchers": "代金券",
1876
2002
  "account.token": "API 令牌",
1877
2003
  "account.signOut": "断开连接",
1878
2004
  "account.signingOut": "正在断开…",
@@ -1950,6 +2076,11 @@ window.__ModuleLoader__.load({
1950
2076
  "account.waiting": "Authorization started. Confirm it in your browser.",
1951
2077
  "account.userCode": "Code",
1952
2078
  "account.openBrowser": "Open browser to confirm",
2079
+ "account.manualLink": "If the browser does not open, open this address yourself:",
2080
+ "account.copyLink": "Copy link",
2081
+ "account.copied": "Copied",
2082
+ "account.copyFailed": "Could not copy; select the link above and copy it yourself.",
2083
+ "account.openFailed": "Could not open your browser ({message}); copy the address below and open it yourself.",
1953
2084
  "account.expiresIn": "This confirmation is valid for {minutes} minutes",
1954
2085
  "account.cancel": "Cancel",
1955
2086
  "account.cancelling": "Cancelling…",
@@ -1958,6 +2089,7 @@ window.__ModuleLoader__.load({
1958
2089
  "account.group": "Group",
1959
2090
  "account.balance": "Balance",
1960
2091
  "account.used": "Used",
2092
+ "account.vouchers": "Vouchers",
1961
2093
  "account.token": "API token",
1962
2094
  "account.signOut": "Disconnect",
1963
2095
  "account.signingOut": "Disconnecting…",