dsh-connect-trae 4.4.2 → 4.4.3
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/CHANGELOG.md +120 -0
- package/README.md +1 -1
- package/lib/client.js +559 -133
- package/lib/index.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,125 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.4.3 (2026-10-23)
|
|
4
|
+
|
|
5
|
+
> **模型选择器里也看得见活动优惠;账号池信息架构对齐 workbuddy;顺带修一个会让已保存设置
|
|
6
|
+
> 读回空值的区域映射缺陷,和三处「写了等于没写」的样式。**
|
|
7
|
+
>
|
|
8
|
+
> 合并自 PR #51(issue #50 的落实)。两条从姊妹插件 `dsh-connect-workbuddy` 搬来的账:
|
|
9
|
+
> **模型的活动优惠该在「挑模型的地方」就看得见**,以及一个会让**已保存的设置读回空值**的
|
|
10
|
+
> 区域映射缺陷。搬的是结论与陷阱,不是代码 —— 两边的促销协议不同(Trae 发 `activity_discount`
|
|
11
|
+
> 规则,WorkBuddy 发 `badge` + `displayMode`),所以只有「怎么判、什么时候判、显示几个数字」
|
|
12
|
+
> 这三件事是共通的。
|
|
13
|
+
|
|
14
|
+
### 新增
|
|
15
|
+
|
|
16
|
+
- **模型选择器里也画活动优惠胶囊**(`feat(client)`,移植自 workbuddy 的 #120)。
|
|
17
|
+
DSH 的模型选择器由宿主编排,插件能塞进去的只有一个字符 —— 而字符的颜色由 emoji 字体决定,
|
|
18
|
+
不是我们能定的。宿主为此留的唯一缝隙是 `conversation.input.model.label` 插槽(契约原文:
|
|
19
|
+
「渲染非交互的内联内容,例如带颜色的名称或徽标」,官方自己就用它挂 DeepSeek 的发布徽标)。
|
|
20
|
+
插件在该插槽注册 `TraeModelLabel`:
|
|
21
|
+
- **只显示一个倍率**。`traeModelDisplayName` 已经把目录倍率拼进名字(`Kimi-K2 · x0.55`),
|
|
22
|
+
宿主原样渲染该字符串,所以插槽若再追加一个倍率就会印成 `· x0.55 · x0.55` —— 这正是
|
|
23
|
+
workbuddy 的 #120 回归。现在后缀被**按形状**拆回名字之外(`/ · x(\d+(?:\.\d+)?)$/`),
|
|
24
|
+
再画在那个能承载折后价的 span 里:有折扣时显示折后价,没有时显示目录价,**永远只有一个数字**,
|
|
25
|
+
且是此刻成立的那个。
|
|
26
|
+
- **在渲染时刻判定,不在解析时刻**。判据是钟点(闲时窗口开合、活动到期),所以快照只取一次、
|
|
27
|
+
胶囊每次渲染现算,并挂一只**共享的分钟定时器**(20 行标签只花一个 interval);夜间折扣不会
|
|
28
|
+
在第二天早上继续挂着。
|
|
29
|
+
- **取数走 `detail=fast`**(issue #77)。插槽在卡片之外,读不到卡片的 React state,只能自己问
|
|
30
|
+
宿主;用的是卡片轮询的同一个 `/usage` 路由,但取**首屏文档**:模型名册、`creditMultiplier`
|
|
31
|
+
与未解析的 `activityDiscount` 都是本地已知的,所以**打开选择器不会触发一次上游读**。
|
|
32
|
+
结果缓存 60 秒。
|
|
33
|
+
- **归属按 provider 判**:`panelOfProvider`(`src/region.ts`)把 `trae` / `trae-enterprise` /
|
|
34
|
+
`trae-global` 映射到各自的 panel,**企业版读自己的名册**(同一凭据、不同计费池、不同阵容);
|
|
35
|
+
别人的 provider 一个徽标都不画、**连请求都不发**。
|
|
36
|
+
- **语言不靠注入的 `t`**:插槽拿不到卡片的 `t`,所以直接读浏览器语言并查同一张
|
|
37
|
+
`locales.ts` 表(中文「5 折」/ 英文「50% off」,两个占位符都传,各自取用的那个)。
|
|
38
|
+
- 关键样式内联兜底:插槽渲染在卡片之外,卡片样式表不一定在位(从未打开过卡片的页面),
|
|
39
|
+
所以胶囊的颜色/边框/圆角由组件自带,与 `.dsm-trae-model-promo-*` 同源同值。
|
|
40
|
+
- **测试**:`tests/model-label-slot.spec.tsx`(13 条)覆盖「拆后缀」「折扣优先于名字里的倍率」
|
|
41
|
+
「按形状而非数值拆」「`detail=fast` 与 panel 路由」「无价格就不画」「别人的 provider 不 fetch」
|
|
42
|
+
与「中文文案」;`tests/client-activation.spec.tsx` 的注册计数 4 → 5。
|
|
43
|
+
|
|
44
|
+
### 修复
|
|
45
|
+
|
|
46
|
+
- **三处新样式被后定义的基础类吃掉**(`fix(styles)`,真机截图比对时发现)。
|
|
47
|
+
这三个元素都同时挂两个类,而修饰类的规则写在基础类**之前** —— 特异性相同时后定义者胜,于是
|
|
48
|
+
「写了的样式」在页面上等于「没写」:
|
|
49
|
+
- `.dsm-trae-pool-modebar`(462 行)被 `.dsm-trae-pool-settings`(518 行)的
|
|
50
|
+
`padding:0` / `border` / `background` 抹平,蓝色描边与淡蓝渐变全部消失,模式条塌成一条普通分隔行;
|
|
51
|
+
- 免费模型缺失的警示语 `.dsm-trae-pool-serving-note`(471 行)被 `.dsm-trae-pool-note`(501 行)
|
|
52
|
+
的 tertiary 灰覆盖,该显示橙色的地方显示成灰;
|
|
53
|
+
- 账号下拉 `.dsm-trae-pool-account-select`(511 行)的 `max-width:min(320px,50%)`
|
|
54
|
+
被 `.dsm-trae-select`(600 行)的 `max-width:220px` 覆盖,框比设计值窄。
|
|
55
|
+
|
|
56
|
+
三处都改成双类选择器(`.a.b`,特异性 0-2-0),与规则顺序解耦。姊妹插件 `promo` 没有这个问题
|
|
57
|
+
—— 是搬样式时把新规则插错位置的连带伤。
|
|
58
|
+
**验证**:写脚本扫两端 JSX 里全部 33 组多类组合,按「交集属性 + 定义行号」判定谁覆盖谁,
|
|
59
|
+
Trae 修完 0 处、promo 本来 0 处;`tsc` 两端静默,`vitest run` 81 文件 / 1014 条全过。
|
|
60
|
+
|
|
61
|
+
- **`regionsMapOf` 不再跳到过期的同名区域**(`fix(region)`,已单独提交,移植自 workbuddy 的 5.5.x)。
|
|
62
|
+
旧规则是「有 `regions` 键就往下跳」,但 **`regions` map 自己也可能带 `regions` 键** —— 早期构建
|
|
63
|
+
往 map 里写过旧兄弟字段(真实 profile 里 `accounts` / `regions` / `cliBridge` 就并排躺在
|
|
64
|
+
`cn` 旁边)。跳下去读到的是**同一 panel 的另一份更旧的拷贝**,于是已保存的开关/手动排序读回空值,
|
|
65
|
+
表现为「设置没保存」,而文件里其实还在。
|
|
66
|
+
- 新规则按**形状**判定:外层只要含 panel id(`cn` / `enterprise` / `ai`)就是 map,不跳;
|
|
67
|
+
否则才视为整段设置文档。
|
|
68
|
+
- **变异验证**:把判定改回旧行为,新增用例必红(实测 1 条失败);恢复后 22 条全绿。
|
|
69
|
+
|
|
70
|
+
### 重构
|
|
71
|
+
|
|
72
|
+
- **「更多设置」下面三段说明收进折叠块**(`refactor(pool)`,与 workbuddy 的「本池如何挑选账号」同形)。
|
|
73
|
+
副本路径、保存规则、删除行为三段原本平铺,在标题下占掉八行,把两个真正的控件(目标测速模型、
|
|
74
|
+
测试请求体积)顶出首屏。改用 `<details>/<summary>`:键盘可操作、展开状态对屏幕阅读器原生可见,
|
|
75
|
+
不需要拿按钮和 state 去追 DOM;`open` 受控,因为这张卡片每次轮询都会重渲染,非受控会在用户
|
|
76
|
+
刚点开时把它收回去。文案一字未删,副本路径那句照样在(「插件留了真副本」是主张,「这两个 id
|
|
77
|
+
此刻就在这个文件里」是用户能打开来核对的事实)。顺带删掉从未写过样式的死类名
|
|
78
|
+
`dsm-trae-pool-copyfile`。新增双语 key `row.poolMoreRulesTitle`。
|
|
79
|
+
|
|
80
|
+
- **账号池的信息架构重排**(`refactor(pool)`,结论移植自 workbuddy 的 5.4.0,代码按 Trae 的结构重写)。
|
|
81
|
+
这一批不是改功能,是改**同一件事说几次、在哪里说**。十条原则里 Trae 欠九条,逐条落地:
|
|
82
|
+
1. **总开关移到标题下方**。它原本是「账号池设置」框的最后一行,被它控制一切的成员表在它上方
|
|
83
|
+
一百多行;池开/池关意味着「未入池」这一列**有没有意义**,所以选模式的控件必须排在
|
|
84
|
+
依赖模式的控件之前。开关那一行同时给出**运行中的模式**(自动挑号 / 手动选号)——
|
|
85
|
+
读的是已保存的值而不是草稿,因为开关本身已经显示待保存的编辑,旁边的「未保存」胶囊
|
|
86
|
+
说明这件事,模式胶囊因此可以回答另一个问题:池此刻在做什么。
|
|
87
|
+
2. **手动选号搬进标题行**。原来它自己占一整块,重复写着账号名。现在它与「当前计费:X」
|
|
88
|
+
徽标**共用一个槽位**:池关闭且有绑定路由时是选择器(控件自带名字),否则是徽标。
|
|
89
|
+
一个控件回答一个问题,不并存。
|
|
90
|
+
3. **「派单中」不再与「未入池」同框**。这两件事正交:Host 在池里没有可用成员时会回退到
|
|
91
|
+
已保存的选择,于是「正在计费的账号」完全可能不在池中。同一行还顶着「已选 0 / 1」,
|
|
92
|
+
正确状态因此读起来像 bug。现在行内只留成员关系(且跟随**草稿**而非已保存列表),
|
|
93
|
+
谁在计费由标题回答;两者真的冲突时,模式条下方给**一句话**解释。整行着色保留,
|
|
94
|
+
计费行仍然一眼可辨。
|
|
95
|
+
4. **批量按钮搬进成员区**。顺序改为「先声明范围(已选 N / M · 全部 / 清空)→ 再给动作
|
|
96
|
+
(一键测试 / 重新检测)→ 再列账号」。它原先是整块页头工具条,按钮在问「对哪些账号下手」
|
|
97
|
+
时,回答这个问题的控件还在一整块之外。「为什么按钮是灰的」紧贴动作行,不再隔一屏。
|
|
98
|
+
5. **保存条 sticky**。两个生效按钮原本在整页最底部:改完一行偏好,按钮在视口之外。
|
|
99
|
+
6. **「账号池设置」降级为「更多设置」**,它是「剩下那些」而不是页面主标题,不该与顶部的
|
|
100
|
+
「账号池」争主次。
|
|
101
|
+
7. **账号导入/导出搬到「更多设置」之上**。它们点一下立即生效,而它们下方那句提示写的是
|
|
102
|
+
「点保存才生效」——立即生效的动作不能住在只承诺保存的区块里。
|
|
103
|
+
8. **测试模型只说一遍**。设置区「目标测速模型」、表头「目标模型」、行尾各说一次,
|
|
104
|
+
同一个名字在一屏里出现三遍。表头改为「**状态**」(它描述的是结果),行尾仅在
|
|
105
|
+
**实际测的模型 ≠ 本池保存的模型**时才点名——那才是看着像 bug 而其实不是的情形。
|
|
106
|
+
来源(实时请求 / 批量测试)仍然保留,它不在这两个地方说过。
|
|
107
|
+
9. 顺带删掉因此变死的两条规则(`.dsm-trae-pool-bind`、`.dsm-trae-pool-tag-current`)
|
|
108
|
+
与一个死键(`row.poolCurrentBadge`);`row.poolCurrentHeader` 原本已定义却从未使用,
|
|
109
|
+
这次终于用上。
|
|
110
|
+
- **测试相应重写**:`「派单中」 names only the pool's own answer` 那组改为断言**信息搬到了哪里**
|
|
111
|
+
——标题行的计费账号、池外旁注、以及池关闭时槽位里是选择器而不是徽标。新增三组共 10 条
|
|
112
|
+
结构断言:开关在成员表之前且标签读运行态;批量按钮在「已选」之后、表格之前;保存条
|
|
113
|
+
`position:sticky`;表头是「状态」、相同模型不复述而不同模型必点名。
|
|
114
|
+
- **变异验证**(通过 ≠ 有效):把 `modelDiffers` 改回「有模型就显示」→ **2 条必红**;
|
|
115
|
+
把动作条移出成员区 → **1 条必红**;恢复后 50 条全绿。
|
|
116
|
+
|
|
117
|
+
### 验证
|
|
118
|
+
|
|
119
|
+
- `tsc -p tsconfig.json` 与 `tsc -p tsconfig.client.json` 均无输出。
|
|
120
|
+
- `vitest run` **81 文件 / 1014 条全过**(合并后 main 上复核实测;PR 写作过程中为 1007,末两个提交又加了 7 条)。
|
|
121
|
+
- `tsdown` 两个 build 通过。
|
|
122
|
+
|
|
3
123
|
## 4.4.2 (2026-10-23)
|
|
4
124
|
|
|
5
125
|
> **带工具的对话在 deepseek-v4.1-flash 上整轮 400/4027 拒绝。这个版本在两个工具出口
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
> 池是**显式选择**:默认关闭。它不会替你在没打开池的情况下换账号——「换账号等于换人计费」,所以这件事只能由你点头。池打开后卡片会明确写出**当前是谁在付费**(Host 报告的是「下一个真正发送请求的账号」,不是保存的那个绑定)。
|
|
43
43
|
|
|
44
44
|
- **倍率内嵌模型名** —— 模型名称按 Trae 自身菜单的格式显示积分倍率(如 `GLM-5.2 · x0.79`),倍率随目录刷新更新。
|
|
45
|
-
- **活动优惠徽标** —— Trae 把促销规则发在目录里(`activity_discount`:折扣比例、原价、闲时时段与时区),插件按**当前时钟**判定后,在模型名旁边画一枚胶囊:`5 折` / `闲时 5 折` / `限时 5 折`,悬停写明原价、现价与时段。判定放在**渲染时刻**而不是解析时刻,所以夜间折扣不会在第二天早上继续挂着;上游说「此刻没打折」就不画,**不从倍率反推**,也不给没有结束时间的活动编「限时」。这是纯提示:不改请求、不改计费,国际区(订阅制)没有这项数据因此不显示。详见 [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md)
|
|
45
|
+
- **活动优惠徽标** —— Trae 把促销规则发在目录里(`activity_discount`:折扣比例、原价、闲时时段与时区),插件按**当前时钟**判定后,在模型名旁边画一枚胶囊:`5 折` / `闲时 5 折` / `限时 5 折`,悬停写明原价、现价与时段。判定放在**渲染时刻**而不是解析时刻,所以夜间折扣不会在第二天早上继续挂着;上游说「此刻没打折」就不画,**不从倍率反推**,也不给没有结束时间的活动编「限时」。这是纯提示:不改请求、不改计费,国际区(订阅制)没有这项数据因此不显示。详见 [docs/PROMO_BADGE.md](docs/PROMO_BADGE.md)。**同一枚胶囊也画在 DSH 的模型选择器里**(宿主 `conversation.input.model.label` 插槽),挑模型时就能看见——毕竟徽标画在设置卡片里时,你已经选完了。选择器里**倍率只出现一次**:名字里已有的目录倍率会被拆出来,有折扣时换成**折后价**(与卡片同一套时钟判定,同一份数据),打开选择器不会额外触发上游读。
|
|
46
46
|
- **DSH 本地工具循环** —— 通过 Trae `llm_utils_chat` 获取待执行的结构化 `tool_calls`,交由 DSH 自带的本地工具执行,再将工具结果回传模型。
|
|
47
47
|
- **国内国际双区域自动识别** —— 自动发现 Trae CN / TRAE SOLO CN / Trae / TRAE SOLO 四个本地安装的登录账号;区域由凭证自带的 `userRegion` 声明自动判定(host 后缀与 edition 标签兜底),无需手动指定。
|
|
48
48
|
- **目录与勾选按面板隔离** —— 三个面板各一套模型目录、勾选、图片开关与上下文预算,各自读各自的槽位。名录从**该面板所钉的那条网关**读取——原本钉在企业池的客户端去读消费池名录,会永远发现不了自己池的独占模型。
|
package/lib/client.js
CHANGED
|
@@ -23,6 +23,10 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
if (panel === "enterprise") return "trae-enterprise";
|
|
24
24
|
return panel === "ai" ? "trae-global" : "trae";
|
|
25
25
|
}
|
|
26
|
+
/** The panel that owns a provider id, or undefined for an unknown id. */
|
|
27
|
+
function panelOfProvider(provider) {
|
|
28
|
+
return TRAE_PANELS.find((panel) => providerOfPanel(panel) === provider);
|
|
29
|
+
}
|
|
26
30
|
//#endregion
|
|
27
31
|
//#region src/status-paths.ts
|
|
28
32
|
/** Plugin-owned usage endpoint consumed by its browser half. */
|
|
@@ -243,8 +247,9 @@ window.__ModuleLoader__.load({
|
|
|
243
247
|
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
244
248
|
const record = unwrapped;
|
|
245
249
|
const nested = record["regions"];
|
|
246
|
-
if (typeof nested
|
|
247
|
-
|
|
250
|
+
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return record;
|
|
251
|
+
const panelIds = TRAE_PANELS;
|
|
252
|
+
return Object.keys(record).some((key) => panelIds.includes(key)) ? record : nested;
|
|
248
253
|
}
|
|
249
254
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
250
255
|
function regionSlotOf(value, panel) {
|
|
@@ -990,13 +995,38 @@ window.__ModuleLoader__.load({
|
|
|
990
995
|
* leave a hole on the right. *
|
|
991
996
|
* ------------------------------------------------------------------ */
|
|
992
997
|
.dsm-trae-pool{--dsm-trae-pool-pad-x:14px;--dsm-trae-pool-pad-y:10px;--dsm-trae-pool-radius:14px;display:flex;flex-direction:column;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding:14px 0 0}
|
|
993
|
-
.dsm-trae-pool-head{display:flex;align-items:
|
|
998
|
+
.dsm-trae-pool-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}
|
|
994
999
|
.dsm-trae-pool-title{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
995
1000
|
.dsm-trae-pool-region-note{margin:3px 0 0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
996
1001
|
.dsm-trae-pool-current{flex:none;padding:2px 8px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary,#c6c9d0);white-space:nowrap}
|
|
1002
|
+
/* Two classes on the element (settings + modebar): a single-class rule here loses
|
|
1003
|
+
to .dsm-trae-pool-settings further down, which resets padding to 0 and repaints
|
|
1004
|
+
the background. Match both classes so the strip keeps its own frame. */
|
|
1005
|
+
.dsm-trae-pool-settings.dsm-trae-pool-modebar{padding:12px 14px;gap:8px;border-color:rgba(86,134,254,.36);background:linear-gradient(180deg,rgba(86,134,254,.10),rgba(86,134,254,.02)),var(--dsw-alias-bg-layer-2,#24262c)}
|
|
1006
|
+
.dsm-trae-pool-modebar-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
1007
|
+
.dsm-trae-pool-modebar-label{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
1008
|
+
.dsm-trae-pool-modebar-state{flex:none;padding:1px 9px;border-radius:999px;font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-brand-primary,#5686fe);background:rgba(86,134,254,.14);border:1px solid rgba(86,134,254,.3)}
|
|
1009
|
+
.dsm-trae-pool-modebar .dsm-trae-pool-dirty{margin-left:auto}
|
|
1010
|
+
.dsm-trae-pool-modebar .dsm-trae-pool-note{line-height:18px}
|
|
1011
|
+
/* Modifier rules must out-specify their base class wherever the base is defined
|
|
1012
|
+
further down (.dsm-trae-pool-note, .dsm-trae-select): matching both classes keeps
|
|
1013
|
+
the warn tone and the wider select from being reset by the later base rule. */
|
|
1014
|
+
.dsm-trae-pool-note.dsm-trae-pool-serving-note{color:var(--dsw-alias-state-warn-primary,#b45309)}
|
|
1015
|
+
.dsm-trae-pool-card .dsm-trae-pool-actions{padding:0 var(--dsm-trae-pool-pad-x) 12px}
|
|
1016
|
+
.dsm-trae-pool-card .dsm-trae-pool-action-note{padding:0 var(--dsm-trae-pool-pad-x) 12px}
|
|
997
1017
|
.dsm-trae-pool-actions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
|
|
998
1018
|
.dsm-trae-pool-action-checkin{flex:none;white-space:nowrap}
|
|
999
1019
|
.dsm-trae-pool-action-test{flex:none;white-space:nowrap}
|
|
1020
|
+
/* Reference text, folded away. Same <details>/<summary> shape as the WorkBuddy
|
|
1021
|
+
sibling's "how the pool picks an account": keyboard-operable and
|
|
1022
|
+
screen-reader-expanded natively, and nothing to keep in sync in state. The
|
|
1023
|
+
paragraphs inside are plain .dsm-trae-pool-note - the container only supplies
|
|
1024
|
+
the frame, so their own type scale stays authoritative. */
|
|
1025
|
+
.dsm-trae-pool-more{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));border-radius:10px;background:var(--dsw-alias-bg-layer-2,#24262c);overflow:hidden}
|
|
1026
|
+
.dsm-trae-pool-more-summary{padding:8px 12px;cursor:pointer;user-select:none;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;font-weight:600;line-height:18px}
|
|
1027
|
+
.dsm-trae-pool-more-summary:hover{color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
1028
|
+
.dsm-trae-pool-more-summary:focus-visible{outline:2px solid rgba(86,134,254,.55);outline-offset:-2px}
|
|
1029
|
+
.dsm-trae-pool-more-body{display:flex;flex-direction:column;gap:8px;padding:10px 12px 12px;border-top:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18))}
|
|
1000
1030
|
/* Removing an account from the pool is destructive (it drops THIS panel's saved
|
|
1001
1031
|
membership), so the hover state takes the error tone rather than the neutral
|
|
1002
1032
|
one every other outline button gets. */
|
|
@@ -1031,8 +1061,7 @@ window.__ModuleLoader__.load({
|
|
|
1031
1061
|
.dsm-trae-pool-set-copy span{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
1032
1062
|
.dsm-trae-pool-set-ctl{display:flex;align-items:center;gap:8px;flex:none}
|
|
1033
1063
|
.dsm-trae-pool-set-ctl select{min-width:180px}
|
|
1034
|
-
.dsm-trae-
|
|
1035
|
-
.dsm-trae-pool-account-select{min-width:180px}
|
|
1064
|
+
.dsm-trae-select.dsm-trae-pool-account-select{min-width:180px;max-width:min(320px,50%)}
|
|
1036
1065
|
.dsm-trae-pool-tools{display:flex;flex-direction:column;gap:8px}
|
|
1037
1066
|
.dsm-trae-pool-tool-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
1038
1067
|
.dsm-trae-pool-meta{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary,#9aa0a8);white-space:nowrap}
|
|
@@ -1076,7 +1105,6 @@ window.__ModuleLoader__.load({
|
|
|
1076
1105
|
.dsm-trae-pool-tag{padding:0 6px;border-radius:999px;font-size:10px;line-height:16px;white-space:nowrap}
|
|
1077
1106
|
.dsm-trae-pool-member{font-size:11px;line-height:16px;color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
1078
1107
|
.dsm-trae-pool-not-member{font-size:11px;line-height:16px;color:var(--dsw-alias-label-dimmed,#9aa0a6)}
|
|
1079
|
-
.dsm-trae-pool-tag-current{padding:1px 6px;border-radius:999px;background:rgba(86,134,254,.16);color:var(--dsw-alias-brand-primary,#5686fe);font-size:11px;font-weight:600;line-height:16px}
|
|
1080
1108
|
.dsm-trae-pool-tag-member{border:1px solid var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
1081
1109
|
.dsm-trae-pool-tag-excluded{background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
|
|
1082
1110
|
.dsm-trae-pool-tag-ghost{border:1px dashed var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8)}
|
|
@@ -1115,7 +1143,7 @@ window.__ModuleLoader__.load({
|
|
|
1115
1143
|
/* "Every member is unusable" is the one case that IS the pool's fault, so it
|
|
1116
1144
|
keeps the warn tone the base class sets. */
|
|
1117
1145
|
.dsm-trae-pool-warning-unusable{border-color:var(--dsw-alias-state-warn-secondary,#fbbf24);background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309)}
|
|
1118
|
-
.dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px}
|
|
1146
|
+
.dsm-trae-pool-save-row{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px;position:sticky;bottom:0;z-index:2;padding-bottom:8px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
1119
1147
|
.dsm-trae-pool-save-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
|
|
1120
1148
|
.dsm-trae-pool-save-row .dsm-btn{padding:5px 14px;font-size:13px;line-height:1.5}
|
|
1121
1149
|
.dsm-trae-pool-dirty{padding:0 6px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary,#fef3c7);color:var(--dsw-alias-state-warn-primary,#b45309);font-size:10px;line-height:16px}
|
|
@@ -1591,6 +1619,7 @@ window.__ModuleLoader__.load({
|
|
|
1591
1619
|
const [selected, setSelected] = (0, react.useState)(() => new Set(saved.memberAccountIds ?? []));
|
|
1592
1620
|
const [busy, setBusy] = (0, react.useState)(void 0);
|
|
1593
1621
|
const [saveError, setSaveError] = (0, react.useState)(void 0);
|
|
1622
|
+
const [moreRulesOpen, setMoreRulesOpen] = (0, react.useState)(false);
|
|
1594
1623
|
const [testing, setTesting] = (0, react.useState)(void 0);
|
|
1595
1624
|
const [rows, setRows] = (0, react.useState)({});
|
|
1596
1625
|
const [deletingIds, setDeletingIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
|
|
@@ -1943,60 +1972,79 @@ window.__ModuleLoader__.load({
|
|
|
1943
1972
|
className: "dsm-trae-pool",
|
|
1944
1973
|
"aria-label": t("row.poolTitle"),
|
|
1945
1974
|
children: [
|
|
1946
|
-
pool.enabled !== true && onBind !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1947
|
-
className: "dsm-trae-pool-set dsm-trae-pool-bind",
|
|
1948
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1949
|
-
className: "dsm-trae-pool-set-copy",
|
|
1950
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolManualAccountLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolManualAccountHint") })]
|
|
1951
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1952
|
-
className: "dsm-trae-pool-set-ctl",
|
|
1953
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1954
|
-
className: "dsm-trae-select dsm-trae-pool-account-select",
|
|
1955
|
-
value: serving?.accountId ?? "",
|
|
1956
|
-
disabled: bindingBusy === true || !writable,
|
|
1957
|
-
onChange: (event) => {
|
|
1958
|
-
const next = event.target.value;
|
|
1959
|
-
if (next !== "") onBind(next);
|
|
1960
|
-
},
|
|
1961
|
-
children: [serving === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1962
|
-
value: "",
|
|
1963
|
-
disabled: true,
|
|
1964
|
-
children: t("row.poolManualAccountNone")
|
|
1965
|
-
}) : null, pool.accounts.filter((account) => account.reachable !== false).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1966
|
-
value: account.accountId,
|
|
1967
|
-
children: account.accountName
|
|
1968
|
-
}, account.accountId))]
|
|
1969
|
-
})
|
|
1970
|
-
})]
|
|
1971
|
-
}) : null,
|
|
1972
1975
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1973
|
-
className: "dsm-trae-pool-
|
|
1974
|
-
children: [
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
children:
|
|
1993
|
-
})
|
|
1994
|
-
|
|
1976
|
+
className: "dsm-trae-pool-head",
|
|
1977
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1978
|
+
className: "dsm-trae-pool-title",
|
|
1979
|
+
children: t("row.poolTitle")
|
|
1980
|
+
}), pool.enabled !== true && onBind !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1981
|
+
className: "dsm-trae-select dsm-trae-pool-account-select",
|
|
1982
|
+
"aria-label": t("row.poolManualAccountLabel"),
|
|
1983
|
+
value: serving?.accountId ?? "",
|
|
1984
|
+
disabled: bindingBusy === true || !writable,
|
|
1985
|
+
onChange: (event) => {
|
|
1986
|
+
const next = event.target.value;
|
|
1987
|
+
if (next !== "") onBind(next);
|
|
1988
|
+
},
|
|
1989
|
+
children: [serving === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1990
|
+
value: "",
|
|
1991
|
+
disabled: true,
|
|
1992
|
+
children: t("row.poolManualAccountNone")
|
|
1993
|
+
}) : null, pool.accounts.filter((account) => account.reachable !== false).map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1994
|
+
value: account.accountId,
|
|
1995
|
+
children: account.accountName
|
|
1996
|
+
}, account.accountId))]
|
|
1997
|
+
}) : serving === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1998
|
+
className: "dsm-trae-pool-current",
|
|
1999
|
+
children: t("row.poolCurrentHeader", { account: serving.accountName })
|
|
2000
|
+
})]
|
|
1995
2001
|
}),
|
|
1996
|
-
|
|
1997
|
-
className: "dsm-trae-pool-
|
|
1998
|
-
children:
|
|
2002
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2003
|
+
className: "dsm-trae-pool-settings dsm-trae-pool-modebar",
|
|
2004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2005
|
+
className: "dsm-trae-pool-modebar-row",
|
|
2006
|
+
children: [
|
|
2007
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
2008
|
+
className: "dsm-trae-switch dsm-trae-switch-pill",
|
|
2009
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2010
|
+
type: "checkbox",
|
|
2011
|
+
checked: enabled,
|
|
2012
|
+
disabled: busy !== void 0 || !writable,
|
|
2013
|
+
"aria-label": t("row.poolEnabled"),
|
|
2014
|
+
onChange: (e) => {
|
|
2015
|
+
setEnabled(e.target.checked);
|
|
2016
|
+
}
|
|
2017
|
+
})
|
|
2018
|
+
}),
|
|
2019
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
|
|
2020
|
+
className: "dsm-trae-pool-modebar-label",
|
|
2021
|
+
children: t("row.poolEnabled")
|
|
2022
|
+
}),
|
|
2023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2024
|
+
className: "dsm-trae-pool-modebar-state",
|
|
2025
|
+
children: pool.enabled === true ? t("row.poolModeAuto") : t("row.poolModeManual")
|
|
2026
|
+
}),
|
|
2027
|
+
dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2028
|
+
className: "dsm-trae-pool-dirty",
|
|
2029
|
+
role: "status",
|
|
2030
|
+
children: t("row.poolDirty")
|
|
2031
|
+
}) : null
|
|
2032
|
+
]
|
|
2033
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2034
|
+
className: "dsm-trae-pool-note",
|
|
2035
|
+
children: t("row.poolEnabledHint")
|
|
2036
|
+
})]
|
|
1999
2037
|
}),
|
|
2038
|
+
pool.enabled === true && serving !== void 0 && serving.member !== true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2039
|
+
className: "dsm-trae-pool-note dsm-trae-pool-serving-note",
|
|
2040
|
+
role: "status",
|
|
2041
|
+
children: t("row.poolServingOutside", { account: serving.accountName })
|
|
2042
|
+
}) : null,
|
|
2043
|
+
saved.enabled === true && enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2044
|
+
className: "dsm-trae-pool-off-notice",
|
|
2045
|
+
role: "status",
|
|
2046
|
+
children: t("row.poolOffNotice")
|
|
2047
|
+
}) : null,
|
|
2000
2048
|
showNoCandidate ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NoCandidateNotice, {
|
|
2001
2049
|
noCandidate,
|
|
2002
2050
|
t
|
|
@@ -2036,6 +2084,34 @@ window.__ModuleLoader__.load({
|
|
|
2036
2084
|
]
|
|
2037
2085
|
})]
|
|
2038
2086
|
}),
|
|
2087
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2088
|
+
className: "dsm-trae-pool-actions",
|
|
2089
|
+
children: [
|
|
2090
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2091
|
+
type: "button",
|
|
2092
|
+
className: "dsm-btn dsm-btn-outline dsm-trae-pool-action-test",
|
|
2093
|
+
disabled: busy !== void 0 || selected.size === 0 || pool.targetModelSource === "none",
|
|
2094
|
+
title: pool.targetModelSource === "none" ? t("row.poolTargetNoneHint") : void 0,
|
|
2095
|
+
onClick: () => {
|
|
2096
|
+
runTest();
|
|
2097
|
+
},
|
|
2098
|
+
children: busy === "test" ? t("row.poolTesting") : t("row.poolTestAll")
|
|
2099
|
+
}),
|
|
2100
|
+
busy === "test" || testing !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2101
|
+
className: "dsm-trae-pool-busy",
|
|
2102
|
+
role: "status",
|
|
2103
|
+
children: t("row.poolBusyHint")
|
|
2104
|
+
}) : null,
|
|
2105
|
+
topActions === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2106
|
+
className: "dsm-trae-pool-actions-end",
|
|
2107
|
+
children: topActions
|
|
2108
|
+
})
|
|
2109
|
+
]
|
|
2110
|
+
}),
|
|
2111
|
+
actionNotes.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2112
|
+
className: "dsm-trae-pool-action-note",
|
|
2113
|
+
children: actionNotes.join(" · ")
|
|
2114
|
+
}),
|
|
2039
2115
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2040
2116
|
className: pool.checkinSupported ? "dsm-trae-pool-table" : "dsm-trae-pool-table dsm-trae-pool-table-no-checkin",
|
|
2041
2117
|
role: "table",
|
|
@@ -2053,6 +2129,7 @@ window.__ModuleLoader__.load({
|
|
|
2053
2129
|
rows,
|
|
2054
2130
|
checked: selected.has(account.accountId),
|
|
2055
2131
|
poolEnabled: pool.enabled === true,
|
|
2132
|
+
poolModelId: pool.targetModelId ?? "",
|
|
2056
2133
|
detail: details?.[account.accountId],
|
|
2057
2134
|
claim: claims?.[account.accountId],
|
|
2058
2135
|
onClaim,
|
|
@@ -2120,66 +2197,49 @@ window.__ModuleLoader__.load({
|
|
|
2120
2197
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2121
2198
|
className: "dsm-trae-pool-settings-wrap",
|
|
2122
2199
|
children: [
|
|
2123
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2124
|
-
className: "dsm-trae-pool-head",
|
|
2125
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2126
|
-
className: "dsm-trae-pool-title",
|
|
2127
|
-
children: t("row.poolSettingsTitle")
|
|
2128
|
-
}), dirty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2129
|
-
className: "dsm-trae-pool-dirty",
|
|
2130
|
-
role: "status",
|
|
2131
|
-
children: t("row.poolDirty")
|
|
2132
|
-
}) : null]
|
|
2133
|
-
}),
|
|
2134
2200
|
tools === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2135
2201
|
className: "dsm-trae-pool-tools dsm-trae-pool-tools-dashed",
|
|
2136
2202
|
children: tools
|
|
2137
2203
|
}),
|
|
2138
|
-
|
|
2139
|
-
className: "dsm-trae-pool-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
count: pool.copies.count
|
|
2204
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2205
|
+
className: "dsm-trae-pool-head",
|
|
2206
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
2207
|
+
className: "dsm-trae-pool-title",
|
|
2208
|
+
children: t("row.poolSettingsTitle")
|
|
2144
2209
|
})
|
|
2145
2210
|
}),
|
|
2146
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2147
|
-
className: "dsm-trae-pool-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2211
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
2212
|
+
className: "dsm-trae-pool-more",
|
|
2213
|
+
open: moreRulesOpen,
|
|
2214
|
+
onToggle: (event) => setMoreRulesOpen(event.currentTarget.open),
|
|
2215
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", {
|
|
2216
|
+
className: "dsm-trae-pool-more-summary",
|
|
2217
|
+
children: t("row.poolMoreRulesTitle")
|
|
2218
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2219
|
+
className: "dsm-trae-pool-more-body",
|
|
2220
|
+
children: [
|
|
2221
|
+
pool.copies === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2222
|
+
className: "dsm-trae-pool-note",
|
|
2223
|
+
role: "note",
|
|
2224
|
+
children: t("row.poolCopyFile", {
|
|
2225
|
+
path: pool.copies.path,
|
|
2226
|
+
count: pool.copies.count
|
|
2227
|
+
})
|
|
2228
|
+
}),
|
|
2229
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2230
|
+
className: "dsm-trae-pool-note",
|
|
2231
|
+
children: t("row.poolSettingsNote")
|
|
2232
|
+
}),
|
|
2233
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2234
|
+
className: "dsm-trae-pool-note",
|
|
2235
|
+
children: t("row.poolDeleteHint")
|
|
2236
|
+
})
|
|
2237
|
+
]
|
|
2238
|
+
})]
|
|
2153
2239
|
}),
|
|
2154
2240
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2155
2241
|
className: "dsm-trae-pool-settings",
|
|
2156
2242
|
children: [
|
|
2157
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2158
|
-
className: "dsm-trae-pool-set",
|
|
2159
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2160
|
-
className: "dsm-trae-pool-set-copy",
|
|
2161
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: t("row.poolEnabled") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.poolEnabledHint") })]
|
|
2162
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2163
|
-
className: "dsm-trae-pool-set-ctl",
|
|
2164
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
2165
|
-
className: "dsm-trae-switch dsm-trae-switch-pill",
|
|
2166
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2167
|
-
type: "checkbox",
|
|
2168
|
-
checked: enabled,
|
|
2169
|
-
disabled: busy !== void 0 || !writable,
|
|
2170
|
-
"aria-label": t("row.poolEnabled"),
|
|
2171
|
-
onChange: (e) => {
|
|
2172
|
-
setEnabled(e.target.checked);
|
|
2173
|
-
}
|
|
2174
|
-
})
|
|
2175
|
-
})
|
|
2176
|
-
})]
|
|
2177
|
-
}),
|
|
2178
|
-
saved.enabled === true && enabled === false ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2179
|
-
className: "dsm-trae-pool-off-notice",
|
|
2180
|
-
role: "status",
|
|
2181
|
-
children: t("row.poolOffNotice")
|
|
2182
|
-
}) : null,
|
|
2183
2243
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2184
2244
|
className: "dsm-trae-pool-set",
|
|
2185
2245
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -2351,7 +2411,7 @@ window.__ModuleLoader__.load({
|
|
|
2351
2411
|
]
|
|
2352
2412
|
});
|
|
2353
2413
|
}
|
|
2354
|
-
function MemberRow({ account, models, rows, checked, onToggle, onTest, t, disabled, deleting, onDelete, canDelete, formatNumber, relativeAt, detail, claim, onClaim, showCheckin, deviceClaimed, poolEnabled }) {
|
|
2414
|
+
function MemberRow({ account, models, rows, checked, onToggle, onTest, t, disabled, deleting, onDelete, canDelete, formatNumber, relativeAt, detail, claim, onClaim, showCheckin, deviceClaimed, poolEnabled, poolModelId }) {
|
|
2355
2415
|
/**
|
|
2356
2416
|
* Whether this row is the one billing the next request — and, with the pool on,
|
|
2357
2417
|
* whether that claim is the pool's own.
|
|
@@ -2417,10 +2477,6 @@ window.__ModuleLoader__.load({
|
|
|
2417
2477
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2418
2478
|
className: "dsm-trae-pool-account-tags",
|
|
2419
2479
|
children: [
|
|
2420
|
-
isCurrent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2421
|
-
className: "dsm-trae-pool-tag dsm-trae-pool-tag-current",
|
|
2422
|
-
children: t("row.poolCurrentBadge")
|
|
2423
|
-
}) : null,
|
|
2424
2480
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2425
2481
|
className: checked ? "dsm-trae-pool-member" : "dsm-trae-pool-not-member",
|
|
2426
2482
|
children: checked ? t("row.poolMember") : t("row.poolNotMember")
|
|
@@ -2467,7 +2523,8 @@ window.__ModuleLoader__.load({
|
|
|
2467
2523
|
t,
|
|
2468
2524
|
relativeAt,
|
|
2469
2525
|
now: Date.now(),
|
|
2470
|
-
detail: testRow.detail
|
|
2526
|
+
detail: testRow.detail,
|
|
2527
|
+
poolModelId
|
|
2471
2528
|
}) : account.probe !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProbeResult, {
|
|
2472
2529
|
outcome: account.probe.outcome,
|
|
2473
2530
|
modelId: account.probe.modelId,
|
|
@@ -2476,7 +2533,8 @@ window.__ModuleLoader__.load({
|
|
|
2476
2533
|
t,
|
|
2477
2534
|
relativeAt,
|
|
2478
2535
|
now: Date.now(),
|
|
2479
|
-
detail: account.probe.message
|
|
2536
|
+
detail: account.probe.message,
|
|
2537
|
+
poolModelId
|
|
2480
2538
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2481
2539
|
className: "dsm-trae-pool-probe-none",
|
|
2482
2540
|
children: t("row.poolNeverTested")
|
|
@@ -2592,12 +2650,15 @@ window.__ModuleLoader__.load({
|
|
|
2592
2650
|
]
|
|
2593
2651
|
});
|
|
2594
2652
|
}
|
|
2595
|
-
function ProbeResult({ outcome, modelId, atMs, source, detail, t, relativeAt, now }) {
|
|
2653
|
+
function ProbeResult({ outcome, modelId, atMs, source, detail, poolModelId, t, relativeAt, now }) {
|
|
2654
|
+
const tone = outcome === "ok" ? "ok" : "warn";
|
|
2655
|
+
const modelDiffers = modelId !== void 0 && modelId !== poolModelId;
|
|
2656
|
+
const sourceLabel = source === void 0 ? "" : probeSourceLabel(source, t);
|
|
2596
2657
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2597
2658
|
className: "dsm-trae-pool-probe-cell",
|
|
2598
2659
|
children: [
|
|
2599
2660
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2600
|
-
className: `dsm-trae-pool-probe-outcome dsm-trae-pool-probe-${
|
|
2661
|
+
className: `dsm-trae-pool-probe-outcome dsm-trae-pool-probe-${tone}`,
|
|
2601
2662
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2602
2663
|
className: "dsm-trae-pool-dot dsm-trae-pool-dot-ok",
|
|
2603
2664
|
"aria-hidden": "true"
|
|
@@ -2607,9 +2668,12 @@ window.__ModuleLoader__.load({
|
|
|
2607
2668
|
className: "dsm-trae-pool-probe-meta",
|
|
2608
2669
|
children: t("row.poolProbeMeasured", { when: relativeAt(atMs, now, t) })
|
|
2609
2670
|
}),
|
|
2610
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2671
|
+
modelDiffers ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2672
|
+
className: "dsm-trae-pool-probe-meta",
|
|
2673
|
+
children: [t("row.poolProbeModel", { model: modelId }), sourceLabel === "" ? "" : ` · ${sourceLabel}`]
|
|
2674
|
+
}) : sourceLabel === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2611
2675
|
className: "dsm-trae-pool-probe-meta",
|
|
2612
|
-
children:
|
|
2676
|
+
children: sourceLabel
|
|
2613
2677
|
}),
|
|
2614
2678
|
detail !== void 0 && detail !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2615
2679
|
className: "dsm-trae-pool-probe-detail",
|
|
@@ -3559,13 +3623,13 @@ window.__ModuleLoader__.load({
|
|
|
3559
3623
|
* - the number shown is upstream's own pay ratio (`discount: 50` ⇒ 五折 /
|
|
3560
3624
|
* "50% off"), never a re-derived price.
|
|
3561
3625
|
*/
|
|
3562
|
-
const PROMO_LABELS = {
|
|
3626
|
+
const PROMO_LABELS$1 = {
|
|
3563
3627
|
limited: "row.promoLimited",
|
|
3564
3628
|
subsidy: "row.promoSubsidy",
|
|
3565
3629
|
offPeak: "row.promoOffPeak"
|
|
3566
3630
|
};
|
|
3567
3631
|
/** Pay ratio as the "折" figure a Chinese price tag uses: 50 → "5", 25 → "2.5". */
|
|
3568
|
-
function promoZhe(discountPercent) {
|
|
3632
|
+
function promoZhe$1(discountPercent) {
|
|
3569
3633
|
const zhe = Math.round(discountPercent * 10) / 100;
|
|
3570
3634
|
return Number.isInteger(zhe) ? String(zhe) : zhe.toFixed(1);
|
|
3571
3635
|
}
|
|
@@ -3585,7 +3649,7 @@ window.__ModuleLoader__.load({
|
|
|
3585
3649
|
function promoBadge(model, t, now) {
|
|
3586
3650
|
const badge = selectTraePromoBadge(model.activityDiscount, now);
|
|
3587
3651
|
if (badge === void 0) return null;
|
|
3588
|
-
const zhe = promoZhe(badge.discountPercent);
|
|
3652
|
+
const zhe = promoZhe$1(badge.discountPercent);
|
|
3589
3653
|
const hints = [];
|
|
3590
3654
|
if (badge.beforeRate !== void 0 && badge.rate !== void 0) hints.push(t("row.promoHintRate", {
|
|
3591
3655
|
before: String(badge.beforeRate),
|
|
@@ -3597,16 +3661,16 @@ window.__ModuleLoader__.load({
|
|
|
3597
3661
|
zone: badge.timeZone
|
|
3598
3662
|
}));
|
|
3599
3663
|
if (badge.memberDiscountPercent !== void 0) hints.push(t("row.promoHintMember", {
|
|
3600
|
-
zhe: promoZhe(badge.memberDiscountPercent),
|
|
3664
|
+
zhe: promoZhe$1(badge.memberDiscountPercent),
|
|
3601
3665
|
percent: String(100 - badge.memberDiscountPercent)
|
|
3602
3666
|
}));
|
|
3603
3667
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3604
3668
|
className: `dsm-trae-model-promo dsm-trae-model-promo-${badge.kind}`,
|
|
3605
|
-
title: hints.length === 0 ? t(PROMO_LABELS[badge.kind], {
|
|
3669
|
+
title: hints.length === 0 ? t(PROMO_LABELS$1[badge.kind], {
|
|
3606
3670
|
zhe,
|
|
3607
3671
|
percent: String(badge.percentOff)
|
|
3608
3672
|
}) : hints.join(" · "),
|
|
3609
|
-
children: t(PROMO_LABELS[badge.kind], {
|
|
3673
|
+
children: t(PROMO_LABELS$1[badge.kind], {
|
|
3610
3674
|
zhe,
|
|
3611
3675
|
percent: String(badge.percentOff)
|
|
3612
3676
|
})
|
|
@@ -6229,7 +6293,8 @@ window.__ModuleLoader__.load({
|
|
|
6229
6293
|
"row.poolEnabled": "Enable the account pool",
|
|
6230
6294
|
"row.poolEnabledHint": "The pool picks the account to bill for this panel's requests, ranking by credits and availability. Off means the manually bound account serves.",
|
|
6231
6295
|
"row.poolCurrentHeader": "Billing now: {account}",
|
|
6232
|
-
"row.
|
|
6296
|
+
"row.poolModeAuto": "pool picks",
|
|
6297
|
+
"row.poolModeManual": "you pick",
|
|
6233
6298
|
"row.poolMember": "in pool",
|
|
6234
6299
|
"row.poolNotMember": "not in pool",
|
|
6235
6300
|
"row.poolBusyHint": "Working through accounts one at a time…",
|
|
@@ -6241,6 +6306,7 @@ window.__ModuleLoader__.load({
|
|
|
6241
6306
|
"row.poolNoCandidateEmptyHint": "Tick accounts in the membership list to bring them into the pool.",
|
|
6242
6307
|
"row.poolNoCandidateUnusable": "Every selected member is unavailable right now.",
|
|
6243
6308
|
"row.poolNoCandidateHint": "Traffic keeps landing on the saved selection until one recovers.",
|
|
6309
|
+
"row.poolServingOutside": "{account} is billing right now but is not in the pool — until a member becomes available, requests fall back to the saved selection.",
|
|
6244
6310
|
"row.poolMembersTitle": "Accounts in this pool",
|
|
6245
6311
|
"row.poolRescan": "Re-detect accounts",
|
|
6246
6312
|
"row.poolToolsLabel": "Export / import accounts",
|
|
@@ -6256,7 +6322,7 @@ window.__ModuleLoader__.load({
|
|
|
6256
6322
|
"row.poolSelectNone": "None",
|
|
6257
6323
|
"row.poolColumnAccount": "Account",
|
|
6258
6324
|
"row.poolColumnCredits": "Credits",
|
|
6259
|
-
"row.poolColumnProbe": "
|
|
6325
|
+
"row.poolColumnProbe": "Status",
|
|
6260
6326
|
"row.poolColumnCheckin": "Check-in",
|
|
6261
6327
|
"row.poolGeneralLabel": "General",
|
|
6262
6328
|
"row.poolWorkHint": "Work credits are not spendable by DSH — only General powers chat.",
|
|
@@ -6281,9 +6347,9 @@ window.__ModuleLoader__.load({
|
|
|
6281
6347
|
"row.poolProbeSizeHint": "How much text a test sends. A one-word probe only proves the account can answer a one-word request — if the upstream refuses long turns, this is where you find out. Bigger is closer to a real conversation, and costs more credit on every account in the batch.",
|
|
6282
6348
|
"row.poolProbeSizeDefault": "Minimal (default)",
|
|
6283
6349
|
"row.poolProbeSizeOption": "{wan}0k tokens",
|
|
6284
|
-
"row.poolSettingsTitle": "
|
|
6350
|
+
"row.poolSettingsTitle": "More settings",
|
|
6351
|
+
"row.poolMoreRulesTitle": "Account copies & saving rules",
|
|
6285
6352
|
"row.poolManualAccountLabel": "Manually billed account",
|
|
6286
|
-
"row.poolManualAccountHint": "Used while the pool is off. With it on, the Host picks by credits.",
|
|
6287
6353
|
"row.poolManualAccountNone": "No account in effect",
|
|
6288
6354
|
"row.poolDirty": "unsaved",
|
|
6289
6355
|
"row.poolSaved": "Save",
|
|
@@ -6578,7 +6644,8 @@ window.__ModuleLoader__.load({
|
|
|
6578
6644
|
"row.poolEnabled": "启用账号池",
|
|
6579
6645
|
"row.poolEnabledHint": "账号池按积分与可用性挑选本面板请求的计费账号;关闭时由手动绑定的账号计费。",
|
|
6580
6646
|
"row.poolCurrentHeader": "当前计费:{account}",
|
|
6581
|
-
"row.
|
|
6647
|
+
"row.poolModeAuto": "自动挑号",
|
|
6648
|
+
"row.poolModeManual": "手动选号",
|
|
6582
6649
|
"row.poolMember": "已在池中",
|
|
6583
6650
|
"row.poolNotMember": "未入池",
|
|
6584
6651
|
"row.poolBusyHint": "逐个处理账号中…",
|
|
@@ -6590,6 +6657,7 @@ window.__ModuleLoader__.load({
|
|
|
6590
6657
|
"row.poolNoCandidateEmptyHint": "在成员列表中勾选账号即可加入账号池。",
|
|
6591
6658
|
"row.poolNoCandidateUnusable": "所有已选成员当前均不可用。",
|
|
6592
6659
|
"row.poolNoCandidateHint": "请求会继续落在已保存的账号上,直到有成员恢复可用。",
|
|
6660
|
+
"row.poolServingOutside": "{account} 当前在计费,但它不在池中——在池里有可用成员之前,请求会落回你保存的选择。",
|
|
6593
6661
|
"row.poolMembersTitle": "本池包含的账号",
|
|
6594
6662
|
"row.poolRescan": "重新检测账号",
|
|
6595
6663
|
"row.poolToolsLabel": "导出 / 导入账号",
|
|
@@ -6605,7 +6673,7 @@ window.__ModuleLoader__.load({
|
|
|
6605
6673
|
"row.poolSelectNone": "清空",
|
|
6606
6674
|
"row.poolColumnAccount": "账号",
|
|
6607
6675
|
"row.poolColumnCredits": "积分",
|
|
6608
|
-
"row.poolColumnProbe": "
|
|
6676
|
+
"row.poolColumnProbe": "状态",
|
|
6609
6677
|
"row.poolColumnCheckin": "签到",
|
|
6610
6678
|
"row.poolGeneralLabel": "通用",
|
|
6611
6679
|
"row.poolWorkHint": "工作积分不能由 DSH 消费——只有通用积分用于对话。",
|
|
@@ -6630,9 +6698,9 @@ window.__ModuleLoader__.load({
|
|
|
6630
6698
|
"row.poolProbeSizeHint": "测试时发送多少文本。只发一个词的探针,只能证明「这个账号能回答一个词」——如果上游拒绝长请求,问题只会在你真实对话时才出现。填得越多越接近真实对话,每个账号每次测试消耗的积分也越多。",
|
|
6631
6699
|
"row.poolProbeSizeDefault": "最小请求(默认)",
|
|
6632
6700
|
"row.poolProbeSizeOption": "{wan} 万 token",
|
|
6633
|
-
"row.poolSettingsTitle": "
|
|
6701
|
+
"row.poolSettingsTitle": "更多设置",
|
|
6702
|
+
"row.poolMoreRulesTitle": "账号副本与保存规则",
|
|
6634
6703
|
"row.poolManualAccountLabel": "手动计费账号",
|
|
6635
|
-
"row.poolManualAccountHint": "账号池关闭时由它计费;开启后由 Host 按积分挑选。",
|
|
6636
6704
|
"row.poolManualAccountNone": "当前无生效账号",
|
|
6637
6705
|
"row.poolDirty": "未保存",
|
|
6638
6706
|
"row.poolSaved": "保存",
|
|
@@ -6724,6 +6792,340 @@ window.__ModuleLoader__.load({
|
|
|
6724
6792
|
"row.cheer": "鼓励一下"
|
|
6725
6793
|
};
|
|
6726
6794
|
//#endregion
|
|
6795
|
+
//#region src/client/TraeModelLabel.tsx
|
|
6796
|
+
/**
|
|
6797
|
+
* The HOST's model picker label: the catalog name, the upstream's activity
|
|
6798
|
+
* badge, and EXACTLY ONE multiplier.
|
|
6799
|
+
*
|
|
6800
|
+
* WHY A SLOT. The picker renders the model name as a plain string, so the only
|
|
6801
|
+
* thing a provider can put there is a character — and a character's colour is
|
|
6802
|
+
* the emoji font's business, not ours. The host therefore offers exactly one
|
|
6803
|
+
* seam for this: the `conversation.input.model.label` slot, whose contract is
|
|
6804
|
+
* "render non-interactive inline content such as a coloured name or badge"
|
|
6805
|
+
* while the picker keeps owning clicks, focus, check marks and the
|
|
6806
|
+
* accessibility text. The shipped example is the official DeepSeek "1017"
|
|
6807
|
+
* release badge — a coloured marker next to a model name — which is the same
|
|
6808
|
+
* job. (Ported from the sibling `dsh-connect-workbuddy` plugin, which solved
|
|
6809
|
+
* the same problem for the same host; the two must stay one presentation.)
|
|
6810
|
+
*
|
|
6811
|
+
* WHY THE BADGE BELONGS HERE AT ALL. The picker is where the choice is made,
|
|
6812
|
+
* and a time-boxed promotion is a choice criterion: "half price at night"
|
|
6813
|
+
* changes which row you click. The card already drew these badges, but the card
|
|
6814
|
+
* is the settings surface — by the time you read them there you have already
|
|
6815
|
+
* picked. This is the same upstream fact, moved to where it can still change a
|
|
6816
|
+
* decision.
|
|
6817
|
+
*
|
|
6818
|
+
* WHY ONE MULTIPLIER AND NOT TWO. `traeModelDisplayName` ALREADY ends the name
|
|
6819
|
+
* in the catalog multiplier (`Kimi-K2 · x0.55` — see `src/catalog.ts`), and the
|
|
6820
|
+
* host renders that string verbatim. Appending another rate here would print
|
|
6821
|
+
* `· x0.55 · x0.55`, which is precisely the defect the sibling plugin shipped
|
|
6822
|
+
* and had to fix (its #120 regression). The suffix is therefore pulled back OUT
|
|
6823
|
+
* of the name and re-drawn in the span that can carry the DISCOUNTED value, so
|
|
6824
|
+
* the row always shows exactly one number and it is the one that holds now.
|
|
6825
|
+
*
|
|
6826
|
+
* WHY A SEPARATE FETCH. The slot is rendered by the host, outside the plugin's
|
|
6827
|
+
* card, so it cannot read the card's React state (nor its injected `t`). It
|
|
6828
|
+
* asks the Host itself, over the SAME route the card polls
|
|
6829
|
+
* (`TRAE_USAGE_PATH`) — but with `detail=fast` (issue #77), the first-paint
|
|
6830
|
+
* document built from LOCAL state: the model roster, its `creditMultiplier` and
|
|
6831
|
+
* its unresolved `activityDiscount` are all known locally, so opening the
|
|
6832
|
+
* picker must not trigger an upstream read. The answer is cached briefly: a
|
|
6833
|
+
* picker that re-fetched on every re-render would be wasteful, and a level a few
|
|
6834
|
+
* seconds old is still true.
|
|
6835
|
+
*
|
|
6836
|
+
* WHY THE CLOCK IS RE-READ ON EVERY RENDER. A promotion's badge is a function
|
|
6837
|
+
* of the clock (an off-peak window opens and closes; a campaign expires).
|
|
6838
|
+
* Resolving it once at fetch time and holding it would leave "闲时 5 折" on the
|
|
6839
|
+
* picker the next morning — the exact overstatement `src/promo.ts` refuses to
|
|
6840
|
+
* make. The snapshot is fetched once and the BADGE is resolved at render time;
|
|
6841
|
+
* a shared one-minute ticker re-renders the mounted labels, so a window edge is
|
|
6842
|
+
* picked up without one timer per model row.
|
|
6843
|
+
*
|
|
6844
|
+
* WHY THE HOST STILL OWNS THE NAME. The contract says to keep `model.name` as
|
|
6845
|
+
* the text and render the addition beside it, so this component returns the name
|
|
6846
|
+
* first — it never replaces it — and it renders nothing extra for a provider
|
|
6847
|
+
* that is not ours.
|
|
6848
|
+
*
|
|
6849
|
+
* @module dsh-connect-trae/client/TraeModelLabel
|
|
6850
|
+
*/
|
|
6851
|
+
/** How long a fetched snapshot is trusted, in milliseconds. */
|
|
6852
|
+
const CACHE_MS = 6e4;
|
|
6853
|
+
/** How often a mounted label re-reads the clock, in milliseconds. */
|
|
6854
|
+
const TICK_MS = 6e4;
|
|
6855
|
+
/** The snapshot per panel, plus when it was read. */
|
|
6856
|
+
const caches = /* @__PURE__ */ new Map();
|
|
6857
|
+
/**
|
|
6858
|
+
* The snapshot, read from the Host once and reused briefly.
|
|
6859
|
+
*
|
|
6860
|
+
* `undefined` means "not known" — the caller renders the plain name rather than
|
|
6861
|
+
* guessing, because a badge that claims a promotion the upstream never stated
|
|
6862
|
+
* is worse than no badge.
|
|
6863
|
+
*/
|
|
6864
|
+
async function statusNow(panel) {
|
|
6865
|
+
const cached = caches.get(panel);
|
|
6866
|
+
if (cached !== void 0 && Date.now() - cached.at < CACHE_MS) return cached.snapshot;
|
|
6867
|
+
try {
|
|
6868
|
+
const response = await fetch(withTraeRegionFastDetail(TRAE_USAGE_PATH, panel), {
|
|
6869
|
+
headers: { accept: "application/json" },
|
|
6870
|
+
credentials: "same-origin"
|
|
6871
|
+
});
|
|
6872
|
+
if (!response.ok) return void 0;
|
|
6873
|
+
const body = await response.json();
|
|
6874
|
+
const models = /* @__PURE__ */ new Map();
|
|
6875
|
+
for (const model of body.models ?? []) models.set(model.id, model);
|
|
6876
|
+
const snapshot = { models };
|
|
6877
|
+
caches.set(panel, {
|
|
6878
|
+
snapshot,
|
|
6879
|
+
at: Date.now()
|
|
6880
|
+
});
|
|
6881
|
+
return snapshot;
|
|
6882
|
+
} catch {
|
|
6883
|
+
return;
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
/** One shared minute ticker, so N mounted labels cost one timer, not N. */
|
|
6887
|
+
const minuteTicker = {
|
|
6888
|
+
timer: void 0,
|
|
6889
|
+
listeners: /* @__PURE__ */ new Set()
|
|
6890
|
+
};
|
|
6891
|
+
/**
|
|
6892
|
+
* Re-render the caller once a minute while it is mounted.
|
|
6893
|
+
*
|
|
6894
|
+
* @param listener - The callback to run at each tick.
|
|
6895
|
+
* @returns The unsubscribe function, which stops the shared timer when the last
|
|
6896
|
+
* listener leaves.
|
|
6897
|
+
*/
|
|
6898
|
+
function subscribeMinute(listener) {
|
|
6899
|
+
minuteTicker.listeners.add(listener);
|
|
6900
|
+
if (minuteTicker.timer === void 0) {
|
|
6901
|
+
minuteTicker.timer = setInterval(() => {
|
|
6902
|
+
for (const each of [...minuteTicker.listeners]) each();
|
|
6903
|
+
}, TICK_MS);
|
|
6904
|
+
minuteTicker.timer.unref?.();
|
|
6905
|
+
}
|
|
6906
|
+
return () => {
|
|
6907
|
+
minuteTicker.listeners.delete(listener);
|
|
6908
|
+
if (minuteTicker.listeners.size === 0 && minuteTicker.timer !== void 0) {
|
|
6909
|
+
clearInterval(minuteTicker.timer);
|
|
6910
|
+
minuteTicker.timer = void 0;
|
|
6911
|
+
}
|
|
6912
|
+
};
|
|
6913
|
+
}
|
|
6914
|
+
/**
|
|
6915
|
+
* The UI's language, as the browser reports it.
|
|
6916
|
+
*
|
|
6917
|
+
* The card formats dates and numbers with the browser's locale, so reading the
|
|
6918
|
+
* same signal here keeps the badge in the language the user reads. Read
|
|
6919
|
+
* defensively: this module is imported by tests that run in a plain Node
|
|
6920
|
+
* environment, where neither global exists.
|
|
6921
|
+
*/
|
|
6922
|
+
function uiLanguage() {
|
|
6923
|
+
if (typeof navigator !== "undefined" && typeof navigator.language === "string" && navigator.language !== "") return navigator.language;
|
|
6924
|
+
if (typeof document !== "undefined" && typeof document.documentElement?.lang === "string") return document.documentElement.lang;
|
|
6925
|
+
return "";
|
|
6926
|
+
}
|
|
6927
|
+
/** Whether the given language is an English one. */
|
|
6928
|
+
function isEnglish(language) {
|
|
6929
|
+
return language.toLowerCase().startsWith("en");
|
|
6930
|
+
}
|
|
6931
|
+
/** The copy table for one language, so the slot needs no injected `t`. */
|
|
6932
|
+
function copyFor(language) {
|
|
6933
|
+
return isEnglish(language) ? en : zh;
|
|
6934
|
+
}
|
|
6935
|
+
/**
|
|
6936
|
+
* Fill `{name}` placeholders in one locale string.
|
|
6937
|
+
*
|
|
6938
|
+
* The card's own `t` does this; the slot cannot use `t` (it is rendered by the
|
|
6939
|
+
* host, outside the card's injection), so the same substitution is done here on
|
|
6940
|
+
* the same table. Both phrasings are always passed: the zh locale spells a pay
|
|
6941
|
+
* ratio as "5 折" and needs `{zhe}`, the en one says "50% off" and needs
|
|
6942
|
+
* `{percent}` — each uses the placeholder it names, the other is ignored.
|
|
6943
|
+
*/
|
|
6944
|
+
function fillPlaceholders(template, vars) {
|
|
6945
|
+
return template.replace(/\{(\w+)\}/g, (whole, key) => vars[key] ?? whole);
|
|
6946
|
+
}
|
|
6947
|
+
/** Pay ratio as the "折" figure a Chinese price tag uses: 50 → "5", 25 → "2.5". */
|
|
6948
|
+
function promoZhe(discountPercent) {
|
|
6949
|
+
const zhe = Math.round(discountPercent * 10) / 100;
|
|
6950
|
+
return Number.isInteger(zhe) ? String(zhe) : zhe.toFixed(1);
|
|
6951
|
+
}
|
|
6952
|
+
/** The locale key that names each promotion kind. */
|
|
6953
|
+
const PROMO_LABELS = {
|
|
6954
|
+
limited: "row.promoLimited",
|
|
6955
|
+
subsidy: "row.promoSubsidy",
|
|
6956
|
+
offPeak: "row.promoOffPeak"
|
|
6957
|
+
};
|
|
6958
|
+
/**
|
|
6959
|
+
* The chip's colour per kind, mirroring `.dsm-trae-model-promo-*` in
|
|
6960
|
+
* `styles.ts`.
|
|
6961
|
+
*
|
|
6962
|
+
* Written inline because a slot component is rendered OUTSIDE the card: the
|
|
6963
|
+
* card's stylesheet is only mounted while the card is, so a picker opened on a
|
|
6964
|
+
* page where the card was never opened would otherwise get an uncoloured chip.
|
|
6965
|
+
* The tokens are the same ones the stylesheet uses, with the same fallbacks.
|
|
6966
|
+
*/
|
|
6967
|
+
const PROMO_TONES = {
|
|
6968
|
+
subsidy: {
|
|
6969
|
+
color: "var(--dsw-alias-state-error-primary,#d92d20)",
|
|
6970
|
+
border: "color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 40%,transparent)",
|
|
6971
|
+
background: "color-mix(in srgb,var(--dsw-alias-state-error-primary,#d92d20) 10%,transparent)"
|
|
6972
|
+
},
|
|
6973
|
+
limited: {
|
|
6974
|
+
color: "var(--dsw-alias-state-warn-primary,#f59e0b)",
|
|
6975
|
+
border: "color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 45%,transparent)",
|
|
6976
|
+
background: "color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 12%,transparent)"
|
|
6977
|
+
},
|
|
6978
|
+
offPeak: {
|
|
6979
|
+
color: "var(--dsw-alias-brand-primary,#5686fe)",
|
|
6980
|
+
border: "color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 40%,transparent)",
|
|
6981
|
+
background: "color-mix(in srgb,var(--dsw-alias-brand-primary,#5686fe) 10%,transparent)"
|
|
6982
|
+
}
|
|
6983
|
+
};
|
|
6984
|
+
/**
|
|
6985
|
+
* Split the host's display name into "name" and "the multiplier to show".
|
|
6986
|
+
*
|
|
6987
|
+
* `traeModelDisplayName` builds `"<name> · x<multiplier>"` and the host renders
|
|
6988
|
+
* that string verbatim, so appending another rate next to it would print the
|
|
6989
|
+
* figure twice in the picker (`Kimi-K2 · x0.55 · x0.55`). The suffix is
|
|
6990
|
+
* therefore pulled back OUT of the string and re-drawn in the span that can
|
|
6991
|
+
* carry the discounted value — exactly one number, and it is the one that holds
|
|
6992
|
+
* right now.
|
|
6993
|
+
*
|
|
6994
|
+
* The suffix is recognised by SHAPE, not by comparing against the snapshot's
|
|
6995
|
+
* `creditMultiplier`: the two can disagree (a promotion can change the catalog
|
|
6996
|
+
* figure between the snapshot and this render), and leaving the suffix in place
|
|
6997
|
+
* then prints two numbers again — the very defect this function removes.
|
|
6998
|
+
*
|
|
6999
|
+
* @param name - The host's rendered name, possibly ending in ` · x<rate>`.
|
|
7000
|
+
* @param catalogRate - The catalog multiplier, as the snapshot reports it.
|
|
7001
|
+
* @param discounted - The rate to show instead, when a discount is running.
|
|
7002
|
+
* @returns The name without its rate suffix, and the single rate to draw.
|
|
7003
|
+
* `undefined` means "draw no number" (the name had none and no discount
|
|
7004
|
+
* is running).
|
|
7005
|
+
*/
|
|
7006
|
+
function splitCatalogRate(name, catalogRate, discounted) {
|
|
7007
|
+
const match = / · x(\d+(?:\.\d+)?)$/.exec(name);
|
|
7008
|
+
const fromName = match === null ? void 0 : Number(match[1]);
|
|
7009
|
+
return {
|
|
7010
|
+
base: match === null ? name : name.slice(0, match.index),
|
|
7011
|
+
rate: discounted ?? fromName ?? catalogRate
|
|
7012
|
+
};
|
|
7013
|
+
}
|
|
7014
|
+
/** The rate a live badge says the user pays, when it names one. */
|
|
7015
|
+
function discountedRateOf(badge) {
|
|
7016
|
+
return badge?.rate;
|
|
7017
|
+
}
|
|
7018
|
+
/**
|
|
7019
|
+
* The upstream's promotion chip, as the picker draws it.
|
|
7020
|
+
*
|
|
7021
|
+
* Same classes as the card's capsule so the two surfaces cannot drift apart,
|
|
7022
|
+
* plus the inline tone described in {@link PROMO_TONES}.
|
|
7023
|
+
*
|
|
7024
|
+
* @param props - The resolved badge and the copy table for the UI's language.
|
|
7025
|
+
* @returns The chip.
|
|
7026
|
+
*/
|
|
7027
|
+
function PromoChip(props) {
|
|
7028
|
+
const { badge, copy } = props;
|
|
7029
|
+
const zhe = promoZhe(badge.discountPercent);
|
|
7030
|
+
const label = fillPlaceholders(copy[PROMO_LABELS[badge.kind]], {
|
|
7031
|
+
zhe,
|
|
7032
|
+
percent: String(badge.percentOff)
|
|
7033
|
+
});
|
|
7034
|
+
const hints = [];
|
|
7035
|
+
if (badge.beforeRate !== void 0 && badge.rate !== void 0) hints.push(fillPlaceholders(copy["row.promoHintRate"], {
|
|
7036
|
+
before: String(badge.beforeRate),
|
|
7037
|
+
rate: String(badge.rate)
|
|
7038
|
+
}));
|
|
7039
|
+
if (badge.fromClock !== void 0 && badge.untilClock !== void 0 && badge.timeZone !== void 0) hints.push(fillPlaceholders(copy["row.promoHintWindow"], {
|
|
7040
|
+
from: badge.fromClock,
|
|
7041
|
+
until: badge.untilClock,
|
|
7042
|
+
zone: badge.timeZone
|
|
7043
|
+
}));
|
|
7044
|
+
if (badge.memberDiscountPercent !== void 0) hints.push(fillPlaceholders(copy["row.promoHintMember"], {
|
|
7045
|
+
zhe: promoZhe(badge.memberDiscountPercent),
|
|
7046
|
+
percent: String(100 - badge.memberDiscountPercent)
|
|
7047
|
+
}));
|
|
7048
|
+
const tone = PROMO_TONES[badge.kind];
|
|
7049
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7050
|
+
className: `dsm-trae-model-promo dsm-trae-model-promo-${badge.kind} dsm-trae-model-label-promo`,
|
|
7051
|
+
title: hints.length === 0 ? label : hints.join(" · "),
|
|
7052
|
+
style: {
|
|
7053
|
+
flex: "none",
|
|
7054
|
+
borderRadius: "999px",
|
|
7055
|
+
padding: "1px 7px",
|
|
7056
|
+
fontSize: "10px",
|
|
7057
|
+
fontWeight: 500,
|
|
7058
|
+
lineHeight: "15px",
|
|
7059
|
+
whiteSpace: "nowrap",
|
|
7060
|
+
border: `1px solid ${tone.border}`,
|
|
7061
|
+
color: tone.color,
|
|
7062
|
+
background: tone.background,
|
|
7063
|
+
maxWidth: "6em",
|
|
7064
|
+
overflow: "hidden",
|
|
7065
|
+
textOverflow: "ellipsis"
|
|
7066
|
+
},
|
|
7067
|
+
children: label
|
|
7068
|
+
});
|
|
7069
|
+
}
|
|
7070
|
+
/**
|
|
7071
|
+
* The label for one model row in the host's picker.
|
|
7072
|
+
*
|
|
7073
|
+
* @param props - The host's model identity and the placement of this label.
|
|
7074
|
+
* @returns The catalog name, followed by the promotion chip when one holds.
|
|
7075
|
+
*/
|
|
7076
|
+
function TraeModelLabel(props) {
|
|
7077
|
+
const { provider, model } = props;
|
|
7078
|
+
const panel = panelOfProvider(provider.id);
|
|
7079
|
+
if (panel === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name });
|
|
7080
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TraeModelLabelInner, {
|
|
7081
|
+
modelId: model.id,
|
|
7082
|
+
name: model.name,
|
|
7083
|
+
panel
|
|
7084
|
+
});
|
|
7085
|
+
}
|
|
7086
|
+
/** The stateful half: fetches the snapshot, then renders what holds right now. */
|
|
7087
|
+
function TraeModelLabelInner(props) {
|
|
7088
|
+
const { modelId, name, panel } = props;
|
|
7089
|
+
const [snapshot, setSnapshot] = (0, react.useState)(void 0);
|
|
7090
|
+
const [, setTick] = (0, react.useState)(0);
|
|
7091
|
+
(0, react.useEffect)(() => {
|
|
7092
|
+
let live = true;
|
|
7093
|
+
statusNow(panel).then((next) => {
|
|
7094
|
+
if (!live || next === void 0) return;
|
|
7095
|
+
setSnapshot(next);
|
|
7096
|
+
});
|
|
7097
|
+
return () => {
|
|
7098
|
+
live = false;
|
|
7099
|
+
};
|
|
7100
|
+
}, [modelId, panel]);
|
|
7101
|
+
(0, react.useEffect)(() => subscribeMinute(() => setTick((count) => count + 1)), []);
|
|
7102
|
+
const model = snapshot?.models.get(modelId);
|
|
7103
|
+
const badge = model === void 0 ? void 0 : selectTraePromoBadge(model.activityDiscount, /* @__PURE__ */ new Date());
|
|
7104
|
+
const { base, rate } = splitCatalogRate(name, model?.creditMultiplier, discountedRateOf(badge));
|
|
7105
|
+
const copy = copyFor(uiLanguage());
|
|
7106
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7107
|
+
className: "dsm-trae-model-label",
|
|
7108
|
+
style: {
|
|
7109
|
+
display: "inline-flex",
|
|
7110
|
+
alignItems: "center",
|
|
7111
|
+
gap: "5px",
|
|
7112
|
+
whiteSpace: "nowrap",
|
|
7113
|
+
verticalAlign: "baseline"
|
|
7114
|
+
},
|
|
7115
|
+
children: [
|
|
7116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: base }),
|
|
7117
|
+
rate === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7118
|
+
className: "dsm-trae-model-label-rate",
|
|
7119
|
+
children: [" · x", rate.toFixed(2)]
|
|
7120
|
+
}),
|
|
7121
|
+
badge === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PromoChip, {
|
|
7122
|
+
badge,
|
|
7123
|
+
copy
|
|
7124
|
+
})
|
|
7125
|
+
]
|
|
7126
|
+
});
|
|
7127
|
+
}
|
|
7128
|
+
//#endregion
|
|
6727
7129
|
//#region src/client/index.tsx
|
|
6728
7130
|
/** Stable browser-plugin name. */
|
|
6729
7131
|
const name = "dsh-connect-trae-client";
|
|
@@ -6892,6 +7294,30 @@ window.__ModuleLoader__.load({
|
|
|
6892
7294
|
};
|
|
6893
7295
|
registerPointsSlot("conversation.input.left", "dsh-connect-trae-composer-points", 4, "composer");
|
|
6894
7296
|
registerPointsSlot("conversation.composer.dock", "dsh-connect-trae-stats-points", 10, "stats");
|
|
7297
|
+
/**
|
|
7298
|
+
* The upstream's activity badge in the HOST's model picker (ported from the
|
|
7299
|
+
* sibling `dsh-connect-workbuddy` plugin, whose #120 shipped the same fix).
|
|
7300
|
+
*
|
|
7301
|
+
* The picker renders the model name as a plain string, so a provider can
|
|
7302
|
+
* only put a CHARACTER there — and a character's colour is the emoji font's
|
|
7303
|
+
* business, not ours. The host therefore offers exactly one seam for this:
|
|
7304
|
+
* the `conversation.input.model.label` slot, whose contract is "render
|
|
7305
|
+
* non-interactive inline content such as a coloured name or badge" while the
|
|
7306
|
+
* picker keeps owning clicks, focus and check marks.
|
|
7307
|
+
*
|
|
7308
|
+
* Registered unconditionally: the component renders the plain name for a
|
|
7309
|
+
* provider that is not ours, so there is nothing to gate on. Its own
|
|
7310
|
+
* try/catch keeps a host that does not serve this slot from taking the card
|
|
7311
|
+
* down with it.
|
|
7312
|
+
*/
|
|
7313
|
+
const registerModelLabel = () => {
|
|
7314
|
+
try {
|
|
7315
|
+
return ctx.slots.inject("conversation.input.model.label", () => ctx.slots.register({ name: "conversation.input.model.label" }, TraeModelLabel));
|
|
7316
|
+
} catch (error) {
|
|
7317
|
+
console.error("[dsh-connect-trae] model label slot failed to register (host provider unaffected):", error);
|
|
7318
|
+
}
|
|
7319
|
+
};
|
|
7320
|
+
registerModelLabel();
|
|
6895
7321
|
} catch (error) {
|
|
6896
7322
|
console.error("[dsh-connect-trae] client card failed to load (host provider unaffected):", error);
|
|
6897
7323
|
}
|
package/lib/index.js
CHANGED
|
@@ -1364,8 +1364,9 @@ function regionsMapOf(value) {
|
|
|
1364
1364
|
if (typeof unwrapped !== "object" || unwrapped === null || Array.isArray(unwrapped)) return {};
|
|
1365
1365
|
const record = unwrapped;
|
|
1366
1366
|
const nested = record["regions"];
|
|
1367
|
-
if (typeof nested
|
|
1368
|
-
|
|
1367
|
+
if (typeof nested !== "object" || nested === null || Array.isArray(nested)) return record;
|
|
1368
|
+
const panelIds = TRAE_PANELS;
|
|
1369
|
+
return Object.keys(record).some((key) => panelIds.includes(key)) ? record : nested;
|
|
1369
1370
|
}
|
|
1370
1371
|
/** One region's stored slot as a plain object; any other shape reads as empty. */
|
|
1371
1372
|
function regionSlotOf(value, panel) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-trae",
|
|
3
3
|
"displayName": "DSH Connect Trae",
|
|
4
4
|
"description": "把本机登录的 Trae 模型接入 DeepSeek Harness:国内版与国际版双供应商并行,提供用量/积分概览与每日签到领取。",
|
|
5
|
-
"version": "4.4.
|
|
5
|
+
"version": "4.4.3",
|
|
6
6
|
"author": "dingminhua",
|
|
7
7
|
"copyright": "Copyright (c) 2026 LaoDing",
|
|
8
8
|
"license": "MIT",
|