dsh-llm-adapters 1.2.0 → 1.2.1
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/DEVELOPMENT.md +26 -9
- package/FAQ.md +8 -2
- package/README.md +30 -17
- package/client.js +103 -14
- package/lib/index.js +553 -98
- package/lib/types/account-store.d.ts +7 -5
- package/lib/types/adapter.d.ts +8 -2
- package/lib/types/auth.d.ts +17 -11
- package/lib/types/index.d.ts +4 -2
- package/lib/types/trae-client.d.ts +52 -0
- package/lib/types/trae-endpoints.d.ts +2 -0
- package/lib/types/trae-login.d.ts +5 -3
- package/lib/types/trae.d.ts +29 -3
- package/lib/types/types.d.ts +19 -1
- package/package.json +3 -2
- package/src/account-store.ts +9 -5
- package/src/adapter.ts +31 -3
- package/src/api.ts +15 -3
- package/src/auth.ts +47 -16
- package/src/index.ts +5 -3
- package/src/trae-client.ts +312 -0
- package/src/trae-endpoints.ts +4 -0
- package/src/trae-login.ts +5 -3
- package/src/trae.ts +191 -3
- package/src/types.ts +20 -1
package/DEVELOPMENT.md
CHANGED
|
@@ -16,11 +16,12 @@ src/
|
|
|
16
16
|
loomy-login.ts Loomy 微信扫码:uuid、长轮询、讯飞账号 HMAC 接口
|
|
17
17
|
raccoon-login.ts 小浣熊扫码:服务端签发二维码、轮询、业务会话确认
|
|
18
18
|
trae-login.ts Trae 网页登录:独立浏览器配置、CDP 读 cookie 与设备指纹
|
|
19
|
+
trae-client.ts Trae 桌面客户端登录态:只读 storage.json、本机解密、设备指纹
|
|
19
20
|
trae-endpoints.ts 两个区域的站点 / 网关 / 计费基址
|
|
20
21
|
account-profile.ts 小浣熊账号展示字段
|
|
21
22
|
loomy.ts Loomy 目录 / 积分 / 流式调用
|
|
22
23
|
raccoon.ts 小浣熊目录 / 余额 / 流式调用
|
|
23
|
-
trae.ts Trae 目录合并 / SOLO 流式调用 / 额度
|
|
24
|
+
trae.ts Trae 目录合并 / SOLO 流式调用 / 额度 / 客户端签到
|
|
24
25
|
codearts-login.ts CodeArts 浏览器授权:本地回调、PKCE/DPoP、ticket 回退
|
|
25
26
|
codearts-oauth.ts STS 令牌换取与续期(PKCE + DPoP,零依赖 JWS)
|
|
26
27
|
codearts-sign.ts 华为 SDK-HMAC-SHA256 请求签名
|
|
@@ -188,8 +189,20 @@ metadata”)。远端目录在多个分组里用不同大小写/显示名重
|
|
|
188
189
|
`end_time` 重置时间)。国际版的 `ide_user_pay_status` 只有套餐名、没有任何金额,官网的
|
|
189
190
|
“Dollar Usage”面板读的也是 v1 这份 roster。两条路线的选择由区域档案的 `usageV2` 决定。
|
|
190
191
|
|
|
191
|
-
Trae
|
|
192
|
-
|
|
192
|
+
### Trae 客户端登录与签到(`trae-client.ts`)
|
|
193
|
+
|
|
194
|
+
Trae 的每日签到只在“复用本机客户端登录态”时开放。网页登录派生的设备号在 `claim` 上会被上游
|
|
195
|
+
回 `9074`,因此网页登录不显示签到入口。`trae-client.ts` 只读客户端
|
|
196
|
+
`User/globalStorage/storage.json` 里的 `iCubeAuthInfo://icube.cloudide`:密文头部是 6 字节
|
|
197
|
+
magic + 32 字节密钥材料,AES-128-CBC 的 key/IV 由两级 SHA-512 与固定 salt 派生,明文是
|
|
198
|
+
SHA-512 摘要 + JSON。插件从解密结果取 `Cloud-IDE-JWT`,从
|
|
199
|
+
`iCubeAuthInfo://icube-dc:<16 位设备号>` 取设备号,从 `telemetry.machineId` 取机器码;
|
|
200
|
+
只读、不回写,客户端退出或令牌过期后重新复用。`checkinStatus` / `claimCheckin` 只在该来源下
|
|
201
|
+
调用 `/trae/api/v2/ug/checkin_credits/{status,claim}`,body 为 `{ req_source: 1 }`,并直接
|
|
202
|
+
使用客户端设备号(不做派生)。数据目录按区域默认取 `%APPDATA%\Trae CN` / `%APPDATA%\Trae`
|
|
203
|
+
(macOS 为 `Application Support`,Linux 为 XDG 配置目录);`DSH_TRAE_CLIENT_DIR`、
|
|
204
|
+
`DSH_TRAE_CN_CLIENT_DIR` 与 `DSH_TRAE_GLOBAL_CLIENT_DIR` 可覆盖它,测试与便携安装都走这组
|
|
205
|
+
环境变量。
|
|
193
206
|
|
|
194
207
|
### 小浣熊的每日积分(`raccoon.ts`)
|
|
195
208
|
|
|
@@ -385,12 +398,15 @@ Raccoon 的思考控制有两条通道,都要发:`extra_body.thinking.type =
|
|
|
385
398
|
| 路由 | 用途 |
|
|
386
399
|
| --- | --- |
|
|
387
400
|
| `GET /api/llm-adapters/models?brand=` | 实时目录 + 开关 / 窗口,`fresh=1` 清缓存 |
|
|
388
|
-
| `GET /api/llm-adapters/status?brand=` |
|
|
401
|
+
| `GET /api/llm-adapters/status?brand=` | 登录状态、登录来源、本机客户端检测、到期时间、额度 |
|
|
389
402
|
| `GET /api/llm-adapters/account/browser?brand=` | 登录进度(本地登录页 URL、Trae 的浏览器状态或 CodeArts 的授权页地址) |
|
|
390
|
-
| `POST /api/llm-adapters/account` | `browser` / `browser-cancel` / `checkin` / `logout` |
|
|
403
|
+
| `POST /api/llm-adapters/account` | `browser` / `browser-cancel` / `client-login` / `checkin` / `logout` |
|
|
391
404
|
|
|
392
|
-
`checkin` 由 Trae
|
|
393
|
-
|
|
405
|
+
`checkin` 由 Trae(仅国内版且客户端登录态)与 CodeArts 实现,其余供应商会明确拒绝;`browser`
|
|
406
|
+
对 CodeArts 返回华为云授权页地址,页面在新标签页打开它,之后仍由宿主轮询 `/account/browser`
|
|
407
|
+
接回。`client-login` 只对 Trae 生效:服务端读取本机客户端登录态后返回普通账户读数,响应里
|
|
408
|
+
没有令牌。`GET /status` 的 `clientLogin` 字段只暴露客户端是否安装、是否已登录与账号名,
|
|
409
|
+
同样不含令牌。
|
|
394
410
|
|
|
395
411
|
写操作在 body 里带 `brand`,URL 不承载写目标。`browser` 还可以带 `region`(`cn` / `global`),
|
|
396
412
|
只有 Trae 使用它。配置写入仍走宿主 `configForms` 的修订栅栏,不经过插件自己的写接口。设置页
|
|
@@ -433,8 +449,9 @@ npm run build
|
|
|
433
449
|
没有 `lib/index.js` 的包会在启用时报 `ERR_MODULE_NOT_FOUND`。改完 `src/` 必须重新构建并提交产物。
|
|
434
450
|
|
|
435
451
|
`vitest` 覆盖目录解析、登录页状态机、Loomy 长轮询与扫码绑定、小浣熊扫码轮询、Trae 两份目录
|
|
436
|
-
的合并与档位映射、
|
|
437
|
-
|
|
452
|
+
的合并与档位映射、Trae 客户端登录态的解密 / 设备号 / 过期与区域判定、Trae 客户端签到、
|
|
453
|
+
CodeArts 的签名 / PKCE-DPoP / 双目录 / 倍率时段 / 额度 / 签到、SSE 与工具调用、凭据存储与并发、
|
|
454
|
+
过期判定、设置 API。登录相关的测试全部注入假 `fetch`,不访问真实上游,
|
|
438
455
|
也不启动浏览器。需要真机验证时,手动点一次「打开扫码登录」或「打开浏览器登录」,确认接回、
|
|
439
456
|
模型列表与一次对话。
|
|
440
457
|
|
package/FAQ.md
CHANGED
|
@@ -261,8 +261,14 @@ STS 令牌本来就只有两小时,插件在到期前 5 分钟用续期令牌
|
|
|
261
261
|
|
|
262
262
|
有,但只对**积分计费账户**开放:插件先读账户类型,再查活动列表里的 `USER_LOGIN`,可领取时才
|
|
263
263
|
`claim`(服务端要求确认时再补 `confirm`)。Token 计费账户会显示「非积分计费账户,没有签到活动」,
|
|
264
|
-
而不是报领取失败。
|
|
265
|
-
|
|
264
|
+
而不是报领取失败。
|
|
265
|
+
|
|
266
|
+
**Trae 有签到吗?**
|
|
267
|
+
|
|
268
|
+
有,但只在本机 Trae 客户端已登录、并在设置页选择「复用客户端登录态」时显示。签到接口要求
|
|
269
|
+
客户端自己的 16 位设备号;网页登录派生的设备号在 claim 上会被上游回 `9074`,所以网页登录
|
|
270
|
+
不显示按钮。插件只读客户端的登录态,不修改也不退出客户端;客户端退出或令牌过期后,先在客户端
|
|
271
|
+
重新登录,再点一次「重新读取客户端登录态」。国际版没有签到活动。
|
|
266
272
|
|
|
267
273
|
**CodeArts 报 `TM.00001041 并发会话数已达上限(3个)`?**
|
|
268
274
|
|
package/README.md
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
把 **Loomy(讯飞)**、**商汤小浣熊**、**Trae** 与 **华为云 CodeArts** 接进
|
|
4
4
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)。
|
|
5
5
|
|
|
6
|
-
-
|
|
6
|
+
- **默认在网页里登录,不用装客户端**:Loomy、小浣熊点「打开扫码登录」用微信扫码;
|
|
7
7
|
Trae 点「打开浏览器登录」在独立浏览器窗口里登录官网;CodeArts 点「打开浏览器登录」在华为云
|
|
8
8
|
完成一次 OAuth 授权。插件自己接回结果,不读日常浏览器的 cookie,也不接收密码。
|
|
9
|
+
- **Trae 可选复用本机客户端**:本机安装了 Trae 且已登录时,Trae 卡片会多出「复用客户端登录态」;
|
|
10
|
+
插件只读客户端的登录态,不修改也不退出客户端。签到只在用这种方式登录时显示。
|
|
9
11
|
- **Trae 两种区域**:国内版(trae.cn)与国际版(trae.ai)各自保存账号,同一张卡片切换。
|
|
10
12
|
- **额度可见**:Loomy 显示可用 / 永久 / 每日积分;小浣熊显示剩余积分;Trae 国内版与 CodeArts
|
|
11
13
|
显示「已用 / 总额」进度条与资源包,国际版显示套餐与美元用量(如 `$0.00 / $1.00`)及重置时间。
|
|
@@ -17,8 +19,8 @@
|
|
|
17
19
|
|
|
18
20
|
## 为什么需要它
|
|
19
21
|
|
|
20
|
-
四家都没有面向个人用户的开放 API
|
|
21
|
-
|
|
22
|
+
四家都没有面向个人用户的开放 API。网页版登录后的会话不能直接当模型网关凭据;桌面客户端的
|
|
23
|
+
登录态通常锁在客户端自己的文件里,Trae 是唯一提供显式、只读复用的例外(见下文)。
|
|
22
24
|
|
|
23
25
|
插件因此在宿主侧完成登录:Loomy 与小浣熊起一个本地登录页,页面上是真正的微信登录二维码;
|
|
24
26
|
Trae 则启动一个**独立配置目录**的浏览器窗口,让用户在官网正常登录,再通过调试协议读回
|
|
@@ -26,9 +28,10 @@ cookie 与 `localStorage` 里的设备指纹。全程由宿主轮询,不依赖
|
|
|
26
28
|
拿到凭据后,模型调用沿用各家的网页网关协议;插件负责把 DSH 的消息、图片、工具调用与思考流
|
|
27
29
|
翻译成对应格式。
|
|
28
30
|
|
|
29
|
-
Trae
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
Trae 的网页登录把设备指纹(`x-device-id` / `x-machine-id`)和短期 JWT 一起保存在插件
|
|
32
|
+
自己的账号文件里(仅当前用户可读),用于在 JWT 到期前自动续期。若本机 Trae 客户端已登录,
|
|
33
|
+
插件也可以只读解析客户端自己的加密登录态,复用它的 JWT、16 位设备号与 machine id;这条路径
|
|
34
|
+
不会回写客户端,也不会退出客户端。两种来源的令牌都只存在插件自己的账号文件里。
|
|
32
35
|
|
|
33
36
|
CodeArts 走华为云 OAuth:浏览器里授权后,宿主拿到 AK/SK 与 STS 安全令牌,之后每个请求都用
|
|
34
37
|
`SDK-HMAC-SHA256` 签名调用网关。它同样不读本机 CodeArts IDE 的任何文件,也不需要安装 IDE。
|
|
@@ -43,10 +46,14 @@ CodeArts 走华为云 OAuth:浏览器里授权后,宿主拿到 AK/SK 与 STS
|
|
|
43
46
|
**设置 → Loomy** 或 **设置 → 商汤小浣熊** → 点「打开扫码登录」。浏览器会打开一个本地
|
|
44
47
|
登录页,用微信扫码并在手机上确认即可;成功后账号、额度与模型列表随即刷新。
|
|
45
48
|
|
|
46
|
-
**设置 → Trae** → 先选区域(国内版 /
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
**设置 → Trae** → 先选区域(国内版 / 国际版),然后二选一:
|
|
50
|
+
|
|
51
|
+
- 点「打开浏览器登录」:插件会启动一个独立且持久的浏览器配置目录(不影响你自己的浏览器),
|
|
52
|
+
在其中登录 Trae 官网;登录成功后窗口自动关闭,账号、额度与模型列表随即刷新。之后约 8 小时
|
|
53
|
+
到期的 JWT 会用这次登录的网页会话自动续期。
|
|
54
|
+
- 若本机已安装并登录 Trae 客户端,点「复用客户端登录态」:插件只读客户端自己的登录态,直接
|
|
55
|
+
复用其 JWT、16 位设备号与 machine id。客户端退出登录或令牌过期后,先在客户端重新登录,再点
|
|
56
|
+
一次「重新读取客户端登录态」即可。国内版的签到按钮只在这个方式下出现。
|
|
50
57
|
|
|
51
58
|
**设置 → 华为云 CodeArts** → 点「打开浏览器登录」,在华为云页面完成授权。授权后浏览器回到
|
|
52
59
|
本机,宿主接回凭据,页面无需复制任何内容。
|
|
@@ -55,10 +62,11 @@ CodeArts 走华为云 OAuth:浏览器里授权后,宿主拿到 AK/SK 与 STS
|
|
|
55
62
|
账号侧的要求;绑定完成后才会下发 `session`。
|
|
56
63
|
- **小浣熊**:二维码由官方服务端一次性签发,扫码确认后自动接回,没有额外表单;过期或已用过
|
|
57
64
|
的码会明确提示,重新点一次即可换新码。
|
|
58
|
-
- **Trae
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
- **Trae**:两种登录方式。网页登录使用插件自己的持久配置目录,再次登录会复用同一设备指纹;
|
|
66
|
+
网页会话约 14 天,期间 8 小时的 JWT 会自动续期,只有网页会话失效才需要重新登录。复用本机
|
|
67
|
+
客户端登录态则直接读取客户端已保存的 JWT、16 位设备号与 machine id,不回写、不退出客户端;
|
|
68
|
+
客户端自己续期,插件里的令牌过期后重新复用即可。区域在卡片上切换,两个区域的账号互不影响,
|
|
69
|
+
切换回来仍是原账号。国际版若提示 `account is at risk`,请用 Log in 登录已有账号(与你在 Edge 里能正常登录的
|
|
62
70
|
Google/GitHub 账号一致),不要用新邮箱注册;独立配置首次登录会被 Trae 视为新设备。
|
|
63
71
|
- **CodeArts**:授权页是华为云自己的 OAuth portal,回调地址是 `127.0.0.1` 上的临时端口;
|
|
64
72
|
凭据只在宿主侧保存,浏览器拿不到。STS 令牌两小时有效,到期自动续期;续期令牌失效(登录
|
|
@@ -70,12 +78,17 @@ CodeArts 走华为云 OAuth:浏览器里授权后,宿主拿到 AK/SK 与 STS
|
|
|
70
78
|
|
|
71
79
|
## 设置页
|
|
72
80
|
|
|
73
|
-
-
|
|
74
|
-
Trae 的卡片顶部是区域切换(国内版 /
|
|
75
|
-
|
|
81
|
+
- **登录卡**:显示账号、到期时间、登录方式与额度;未登录时可发起登录,登录中可取消,也可退出登录。
|
|
82
|
+
Trae 的卡片顶部是区域切换(国内版 / 国际版),并提供网页登录与复用本机客户端两种方式;
|
|
83
|
+
Trae 国内版(仅客户端登录态)、CodeArts 与小浣熊另有「签到领积分」按钮;签到状态按
|
|
84
|
+
「今日已签到」(账号)与「本机今日已签到」(设备)分别显示。小浣熊没有独立签到接口:插件会
|
|
76
85
|
核对积分流水里的 `daily_grant`(只认今天的记录),并在到账时显示具体到账时间;未到账时调用
|
|
77
86
|
网页当日首次加载用的 `setting_info` 尝试触发。「打开网页签到」链接无论是否已到账都始终保留。
|
|
78
87
|
插件页还可设置自动监测间隔(关闭 / 15 分钟 / 30 分钟 / 1 小时 / 2 小时),关闭后只保留手动入口。
|
|
88
|
+
- **Trae 本机客户端登录**:检测到本机安装了 Trae CN / 国际版客户端时,登录卡会显示客户端账号与
|
|
89
|
+
「复用客户端登录态」。插件只读 `User/globalStorage/storage.json` 里的加密登录态并在本机解密,
|
|
90
|
+
不修改客户端,也不把令牌返回给页面;网页登录与客户端登录可以随时切换,退出插件登录不会退出
|
|
91
|
+
客户端。签到按钮只出现在客户端登录态下。
|
|
79
92
|
Trae 国内版与 CodeArts 的资源包默认只显示一行
|
|
80
93
|
摘要(包数、合计额度、最近到期),点「详细信息」才展开每个包的已用 / 总额与到期时间,展开区限高可滚动。
|
|
81
94
|
这两家的额度区顶部是「已用 / 总额」进度条;CodeArts 的账号名取自登录令牌里的用户信息,老账号
|
package/client.js
CHANGED
|
@@ -117,9 +117,22 @@ window.__ModuleLoader__.load({
|
|
|
117
117
|
raccoonIntro: '点“打开扫码登录”,用微信扫码登录小浣熊即可使用,无需安装客户端。改动即时生效,无需重启。',
|
|
118
118
|
traeNav: 'Trae',
|
|
119
119
|
traeTitle: 'Trae',
|
|
120
|
-
traeIntro: '
|
|
121
|
-
traeHint: '
|
|
122
|
-
traeLoginMissing: 'Trae
|
|
120
|
+
traeIntro: '先选国内版或国际版,再任选一种登录方式:在 Trae 官网网页登录,或复用本机 Trae 客户端的登录态。网页登录会用网页会话自动续期;只有复用客户端登录态时,国内版才显示签到按钮。改动即时生效,无需重启。',
|
|
121
|
+
traeHint: '网页登录的令牌约 8 小时,插件会用登录时拿到的网页会话自动续期;复用本机客户端登录态则直接使用客户端令牌,客户端退出登录或令牌过期后重新复用即可。签到仅在国内版、且使用客户端登录态时可用。',
|
|
122
|
+
traeLoginMissing: 'Trae 尚未登录。可以点“打开浏览器登录”,或复用本机已登录的 Trae 客户端。',
|
|
123
|
+
traeClientTitle: '复用本机客户端登录',
|
|
124
|
+
traeClientHint: '检测到本机已安装 Trae 客户端。这里只读取客户端当前的登录态,不会修改或退出客户端;复用后会替换插件里的当前登录态。用这种方式登录后才显示签到按钮。',
|
|
125
|
+
traeClientReady: '本机客户端已登录,可直接复用。',
|
|
126
|
+
traeClientNotLoggedIn: '检测到本机 Trae 客户端,但尚未登录。请先打开客户端完成登录,再点“重新读取登录态”。',
|
|
127
|
+
traeClientAccount: '客户端账号',
|
|
128
|
+
traeClientUse: '复用客户端登录态',
|
|
129
|
+
traeClientReuse: '重新读取客户端登录态',
|
|
130
|
+
traeClientBusy: '正在读取…',
|
|
131
|
+
traeClientFailed: '读取客户端登录态失败',
|
|
132
|
+
traeClientSource: '登录方式',
|
|
133
|
+
traeSourceWeb: '网页登录',
|
|
134
|
+
traeSourceClient: '本机客户端',
|
|
135
|
+
traeClientLogoutNote: '退出登录只会清除插件里的登录态,不会退出本机 Trae 客户端。',
|
|
123
136
|
regionLabel: '区域',
|
|
124
137
|
regionCn: '国内版 · trae.cn',
|
|
125
138
|
regionGlobal: '国际版 · trae.ai',
|
|
@@ -281,9 +294,22 @@ window.__ModuleLoader__.load({
|
|
|
281
294
|
raccoonIntro: 'Use “Open QR sign-in” and scan the code in WeChat; no client install needed. Changes apply immediately.',
|
|
282
295
|
traeNav: 'Trae',
|
|
283
296
|
traeTitle: 'Trae',
|
|
284
|
-
traeIntro: 'Pick the mainland or international edition, then use
|
|
285
|
-
traeHint: '
|
|
286
|
-
traeLoginMissing: 'Trae is not signed in. Use
|
|
297
|
+
traeIntro: 'Pick the mainland or international edition, then use either method: sign in on Trae’s website, or reuse the local Trae client’s sign-in state. A browser login renews itself; the mainland check-in button appears only with the client sign-in. Changes apply immediately.',
|
|
298
|
+
traeHint: 'A browser login uses the website session captured at sign-in; a client login uses the desktop client’s token and is imported again after the client refreshes or signs out. Check-in is mainland-only and client-login-only.',
|
|
299
|
+
traeLoginMissing: 'Trae is not signed in. Use browser sign-in, or reuse a signed-in local Trae client.',
|
|
300
|
+
traeClientTitle: 'Reuse the local client sign-in',
|
|
301
|
+
traeClientHint: 'A Trae desktop client was found. This only reads its current sign-in state and never modifies or signs out the client; reusing it replaces the plugin’s current login. The check-in button appears only after signing in this way.',
|
|
302
|
+
traeClientReady: 'The local client is signed in and ready to reuse.',
|
|
303
|
+
traeClientNotLoggedIn: 'A Trae desktop client was found but is not signed in. Sign in there, then re-read the login state.',
|
|
304
|
+
traeClientAccount: 'Client account',
|
|
305
|
+
traeClientUse: 'Reuse client sign-in',
|
|
306
|
+
traeClientReuse: 'Re-read client sign-in',
|
|
307
|
+
traeClientBusy: 'Reading…',
|
|
308
|
+
traeClientFailed: 'Could not read the client sign-in',
|
|
309
|
+
traeClientSource: 'Sign-in method',
|
|
310
|
+
traeSourceWeb: 'Browser',
|
|
311
|
+
traeSourceClient: 'Local client',
|
|
312
|
+
traeClientLogoutNote: 'Signing out only clears the plugin’s login; the local Trae client stays signed in.',
|
|
287
313
|
regionLabel: 'Region',
|
|
288
314
|
regionCn: 'Mainland · trae.cn',
|
|
289
315
|
regionGlobal: 'International · trae.ai',
|
|
@@ -627,6 +653,7 @@ window.__ModuleLoader__.load({
|
|
|
627
653
|
const [actionBusy, setActionBusy] = useState(false)
|
|
628
654
|
const [actionError, setActionError] = useState(undefined)
|
|
629
655
|
const [checkinBusy, setCheckinBusy] = useState(false)
|
|
656
|
+
const [clientBusy, setClientBusy] = useState(false)
|
|
630
657
|
const [packsExpanded, setPacksExpanded] = useState(false)
|
|
631
658
|
const ticket = useRef(0)
|
|
632
659
|
|
|
@@ -745,6 +772,23 @@ window.__ModuleLoader__.load({
|
|
|
745
772
|
}
|
|
746
773
|
}, [accountRequest, t])
|
|
747
774
|
|
|
775
|
+
const importClientLogin = useCallback(async () => {
|
|
776
|
+
setClientBusy(true)
|
|
777
|
+
setActionError(undefined)
|
|
778
|
+
try {
|
|
779
|
+
const payload = await accountRequest({ action: 'client-login' })
|
|
780
|
+
setStatus(payload)
|
|
781
|
+
setBrowser({ running: false, phase: 'idle' })
|
|
782
|
+
writeCache(cacheId, 'status', { ...payload, cachedAt: Date.now() })
|
|
783
|
+
onStatus?.(payload)
|
|
784
|
+
onAfterRefresh?.()
|
|
785
|
+
} catch (err) {
|
|
786
|
+
setActionError(`${t('traeClientFailed')}:${String(err?.message ?? err)}`)
|
|
787
|
+
} finally {
|
|
788
|
+
setClientBusy(false)
|
|
789
|
+
}
|
|
790
|
+
}, [accountRequest, cacheId, onStatus, onAfterRefresh, t])
|
|
791
|
+
|
|
748
792
|
// Claiming is the only state-changing account action. The reward changes
|
|
749
793
|
// the quota, so the reading is refreshed afterwards instead of guessed.
|
|
750
794
|
const claimCheckin = useCallback(async () => {
|
|
@@ -887,12 +931,14 @@ window.__ModuleLoader__.load({
|
|
|
887
931
|
)
|
|
888
932
|
})()
|
|
889
933
|
|
|
890
|
-
//
|
|
891
|
-
//
|
|
892
|
-
//
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
934
|
+
// Trae's check-in needs the device id only a client-login import has, so
|
|
935
|
+
// a web login must not show the panel even though the provider exposes
|
|
936
|
+
// the method. CodeArts grants one reward per account per day, but its
|
|
937
|
+
// device flag can also block the claim; the two reasons are shown apart
|
|
938
|
+
// so "本机已签" never reads as "this account already claimed".
|
|
939
|
+
const checkin = brand.id === 'trae'
|
|
940
|
+
? (status?.loginSource === 'client' ? status?.checkin : undefined)
|
|
941
|
+
: (brand.id === 'codearts' || brand.id === 'raccoon' ? status?.checkin : undefined)
|
|
896
942
|
const checkinBlocked = checkin === undefined
|
|
897
943
|
|| checkin.available === false
|
|
898
944
|
|| checkin.claimed === true
|
|
@@ -923,7 +969,7 @@ window.__ModuleLoader__.load({
|
|
|
923
969
|
? h('div', { style: css.bar },
|
|
924
970
|
h('button', {
|
|
925
971
|
style: { ...css.btn, ...css.btnPrimary },
|
|
926
|
-
disabled: checkinBlocked || checkinBusy || actionBusy,
|
|
972
|
+
disabled: checkinBlocked || checkinBusy || clientBusy || actionBusy,
|
|
927
973
|
onClick: () => void claimCheckin(),
|
|
928
974
|
}, checkinBusy ? t('checkinBusy') : checkinLabel),
|
|
929
975
|
checkinNote === undefined ? null : h('span', { style: css.note }, checkinNote),
|
|
@@ -986,9 +1032,42 @@ window.__ModuleLoader__.load({
|
|
|
986
1032
|
browser !== undefined && browser.running === false && browser.error !== undefined
|
|
987
1033
|
? h('div', { style: css.err }, browser.error)
|
|
988
1034
|
: null,
|
|
989
|
-
actionError !== undefined ? h('div', { style: css.err }, actionError) : null,
|
|
990
1035
|
)
|
|
991
1036
|
|
|
1037
|
+
const clientLogin = brand.id === 'trae' ? status?.clientLogin : undefined
|
|
1038
|
+
const clientAccount = clientLogin?.signedIn === true
|
|
1039
|
+
? ([clientLogin.name, clientLogin.expiresAt === undefined
|
|
1040
|
+
? undefined
|
|
1041
|
+
: `${t('expires')} ${dateLabel(clientLogin.expiresAt)}`]
|
|
1042
|
+
.filter(part => typeof part === 'string' && part.length > 0)
|
|
1043
|
+
.join(' · ') || t('accountUnknown'))
|
|
1044
|
+
: undefined
|
|
1045
|
+
const clientPanel = brand.id === 'trae' && clientLogin?.available === true
|
|
1046
|
+
? h('div', { style: css.loginBox },
|
|
1047
|
+
h('h4', { style: css.h4 }, t('traeClientTitle')),
|
|
1048
|
+
h('div', { style: css.note }, t('traeClientHint')),
|
|
1049
|
+
clientLogin.signedIn === true
|
|
1050
|
+
? h('div', { style: css.line },
|
|
1051
|
+
h('span', { style: css.label }, t('traeClientAccount')),
|
|
1052
|
+
h('span', { style: css.value }, clientAccount),
|
|
1053
|
+
)
|
|
1054
|
+
: h('div', { style: css.note }, clientLogin.error ?? t('traeClientNotLoggedIn')),
|
|
1055
|
+
h('div', { style: css.bar },
|
|
1056
|
+
h('button', {
|
|
1057
|
+
style: { ...css.btn, ...(clientLogin.signedIn === true ? css.btnPrimary : {}) },
|
|
1058
|
+
disabled: clientBusy || checkinBusy || actionBusy || browserRunning || clientLogin.signedIn !== true,
|
|
1059
|
+
onClick: () => void importClientLogin(),
|
|
1060
|
+
}, clientBusy
|
|
1061
|
+
? t('traeClientBusy')
|
|
1062
|
+
: status?.loginSource === 'client' ? t('traeClientReuse') : t('traeClientUse')),
|
|
1063
|
+
clientLogin.signedIn === true
|
|
1064
|
+
? h('span', { style: css.note }, t('traeClientReady'))
|
|
1065
|
+
: null,
|
|
1066
|
+
),
|
|
1067
|
+
h('div', { style: css.note }, t('traeClientLogoutNote')),
|
|
1068
|
+
)
|
|
1069
|
+
: null
|
|
1070
|
+
|
|
992
1071
|
// Trae's region is a saved setting, not a tab: the selector writes the
|
|
993
1072
|
// same `traeRegion` the provider reads, and the page then re-reads both
|
|
994
1073
|
// the account and the catalog for the newly selected region.
|
|
@@ -1041,6 +1120,14 @@ window.__ModuleLoader__.load({
|
|
|
1041
1120
|
h('span', { style: css.value }, dateLabel(status?.expiresAt)),
|
|
1042
1121
|
)
|
|
1043
1122
|
: null,
|
|
1123
|
+
signedIn && brand.id === 'trae'
|
|
1124
|
+
? h('div', { style: css.line },
|
|
1125
|
+
h('span', { style: css.label }, t('traeClientSource')),
|
|
1126
|
+
h('span', { style: css.value }, status?.loginSource === 'client'
|
|
1127
|
+
? t('traeSourceClient')
|
|
1128
|
+
: t('traeSourceWeb')),
|
|
1129
|
+
)
|
|
1130
|
+
: null,
|
|
1044
1131
|
signedIn && brand.id === 'raccoon'
|
|
1045
1132
|
? h('div', { style: css.line },
|
|
1046
1133
|
h('span', { style: css.label }, t('raccoonOwner')),
|
|
@@ -1068,6 +1155,8 @@ window.__ModuleLoader__.load({
|
|
|
1068
1155
|
)
|
|
1069
1156
|
: null,
|
|
1070
1157
|
!signedIn || status?.possiblyExpired === true ? browserPanel : null,
|
|
1158
|
+
clientPanel,
|
|
1159
|
+
actionError !== undefined ? h('div', { style: css.err }, actionError) : null,
|
|
1071
1160
|
error !== undefined ? h('div', { style: css.err }, `${t('loadFailed')}:${error}`) : null,
|
|
1072
1161
|
)
|
|
1073
1162
|
}
|