dsh-connect-trae 4.4.1 → 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 +170 -0
- package/README.md +1 -1
- package/lib/client.js +559 -133
- package/lib/index.d.ts +30 -1
- package/lib/index.js +146 -87
- package/package.json +1 -1
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
|
}
|