@iasiv5/dsh-quota-watch 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/client.js +106 -69
- package/package.json +1 -1
- package/src/client.mjs +123 -78
- package/src/host.mjs +42 -11
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Web 与 Desktop profile 均已实测适配。按 DSH 插件管理器提示完成
|
|
|
58
58
|
|
|
59
59
|
> **重置卡是可消耗、不可撤销的订阅权益。** 使用前请确认适用范围、卡种与额度窗口。
|
|
60
60
|
|
|
61
|
-
仅支持配置为 `zai-coding-cn` 的**国内站个人版**。国际站 `zai` / `zai-coding`
|
|
61
|
+
仅支持配置为 `zai-coding-cn` 的**国内站个人版**。国际站 `zai` / `zai-coding` 不提供此入口;团队版未验证、不在支持范围内。宿主启动时查询重置卡,此后最多每 10 分钟周期查询一次;卡种、可用张数与最早到期日会随结果更新。两种卡都无可用且自动用卡均关闭、无待展示事件时,卡区收成保留分隔线的一行,仍可展开自动用卡设置。概览底部或详情标题栏的「立即刷新」会同时更新额度和重置卡,不会消费卡;手动使用前宿主仍会重新查询当前卡列表,面板也会先显示二次确认。5 小时卡重置 5 小时窗口;每周卡同时重置每周与 5 小时窗口。
|
|
62
62
|
|
|
63
63
|

|
|
64
64
|
|
package/lib/client.js
CHANGED
|
@@ -249,9 +249,8 @@ var COPY = {
|
|
|
249
249
|
count: "{count} \u5F20",
|
|
250
250
|
none: "\u65E0\u53EF\u7528",
|
|
251
251
|
use: "\u91CD\u7F6E",
|
|
252
|
-
reloadLink: "\uFF08\u5237\u65B0\uFF09",
|
|
253
252
|
autoToggle: "\u81EA\u52A8\u7528\u5361",
|
|
254
|
-
autoActive: "\u81EA\u52A8\u7528\u5361\xB7\u5DF2\u542F\u7528",
|
|
253
|
+
autoActive: "\u81EA\u52A8\u7528\u5361 \xB7 \u5DF2\u542F\u7528",
|
|
255
254
|
expiry: "{date} \u524D\u6709\u6548",
|
|
256
255
|
confirmTitle: "\u786E\u8BA4\u4F7F\u7528\u91CD\u7F6E\u5361\uFF1F",
|
|
257
256
|
confirmWeek: "\u5C06\u6D88\u8017 1 \u5F20\u6BCF\u5468\u91CD\u7F6E\u5361\uFF0C\u7ACB\u5373\u91CD\u7F6E\u6BCF\u5468\u989D\u5EA6\u4E0E 5 \u5C0F\u65F6\u989D\u5EA6\u3002",
|
|
@@ -263,8 +262,11 @@ var COPY = {
|
|
|
263
262
|
succeeded: "\u5DF2\u91CD\u7F6E",
|
|
264
263
|
failed: "\u91CD\u7F6E\u5931\u8D25",
|
|
265
264
|
loadFailed: "\u8BFB\u53D6\u91CD\u7F6E\u5361\u5931\u8D25",
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
// Auto rows render as three grid cells — window label, a centered ·
|
|
266
|
+
// separator, and the used note — so the · column lines up across rows.
|
|
267
|
+
autoFiveHour: "\u81EA\u52A8\u7528\u5361\uFF085 \u5C0F\u65F6\uFF09",
|
|
268
|
+
autoWeek: "\u81EA\u52A8\u7528\u5361\uFF08\u6BCF\u5468\uFF09",
|
|
269
|
+
autoUsed: "\u5DF2\u7528 {threshold}% \u89E6\u53D1",
|
|
268
270
|
autoImmediate: "\u5F53\u524D\u5DF2\u7528 {used}\uFF0C\u5DF2\u8D85\u9608\u503C\uFF1A\u5F00\u542F\u540E\u4F1A\u7ACB\u5373\u6D88\u8017\u4E00\u5F20\u5361",
|
|
269
271
|
autoSaveFailed: "\u5F00\u5173\u672A\u80FD\u4FDD\u5B58\uFF0C\u8BF7\u91CD\u8BD5",
|
|
270
272
|
autoPersistFailed: "\u5F00\u5173\u5DF2\u751F\u6548\uFF0C\u4F46\u672A\u80FD\u5199\u5165\u78C1\u76D8\uFF08\u91CD\u542F\u540E\u4F1A\u4E22\u5931\uFF09",
|
|
@@ -318,7 +320,6 @@ var COPY = {
|
|
|
318
320
|
count: "{count}",
|
|
319
321
|
none: "none",
|
|
320
322
|
use: "Reset",
|
|
321
|
-
reloadLink: "(Refresh)",
|
|
322
323
|
autoToggle: "Auto-use",
|
|
323
324
|
autoActive: "Auto-use \xB7 on",
|
|
324
325
|
expiry: "expires {date}",
|
|
@@ -332,8 +333,10 @@ var COPY = {
|
|
|
332
333
|
succeeded: "Reset done",
|
|
333
334
|
failed: "Reset failed",
|
|
334
335
|
loadFailed: "Could not read reset cards",
|
|
335
|
-
|
|
336
|
-
|
|
336
|
+
// Same three-cell grid as zh: label · used note.
|
|
337
|
+
autoFiveHour: "Auto-use (5h)",
|
|
338
|
+
autoWeek: "Auto-use (weekly)",
|
|
339
|
+
autoUsed: "trigger at {threshold}% used",
|
|
337
340
|
autoImmediate: "Currently {used} used \u2014 already past the threshold: turning this on spends a card immediately",
|
|
338
341
|
autoSaveFailed: "Could not save the switch \u2014 retry",
|
|
339
342
|
autoPersistFailed: "Switch applied, but it could not be written to disk (lost on restart)",
|
|
@@ -405,6 +408,8 @@ var FLOAT_STYLE_TEXT = `
|
|
|
405
408
|
.dqw-panel-back { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; }
|
|
406
409
|
.dqw-panel-back svg { display: block; width: 16px; height: 16px; }
|
|
407
410
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
411
|
+
.dqw-panel-refresh { flex: none; margin: 0; padding: 4px 6px; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 10px; line-height: 14px; opacity: .72; }
|
|
412
|
+
.dqw-panel-refresh:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); opacity: 1; }
|
|
408
413
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
409
414
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
410
415
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -444,16 +449,11 @@ var FLOAT_STYLE_TEXT = `
|
|
|
444
449
|
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
445
450
|
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
446
451
|
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
447
|
-
/* Reset cards:
|
|
448
|
-
|
|
452
|
+
/* Reset cards: keep the dashed divider even when an empty wallet collapses to
|
|
453
|
+
one row. The panel's \u7ACB\u5373\u5237\u65B0 is the only manual re-check control. */
|
|
449
454
|
.dqw-reset { border-top: 1px dashed var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); padding-top: 6px; display: flex; flex-direction: column; gap: 4px; }
|
|
450
|
-
.dqw-reset-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 10px; line-height: 14px; }
|
|
455
|
+
.dqw-reset-head, .dqw-reset-collapsed { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 10px; line-height: 14px; }
|
|
451
456
|
.dqw-reset-title { opacity: .65; }
|
|
452
|
-
.dqw-reset-reload { border: 0; background: none; padding: 0; font: inherit; font-size: 10px; color: var(--dsw-alias-label-secondary, inherit); opacity: .65; cursor: pointer; text-decoration: underline; }
|
|
453
|
-
.dqw-reset-reload:hover { opacity: 1; }
|
|
454
|
-
.dqw-reset-reload:disabled { cursor: default; opacity: .35; text-decoration: none; }
|
|
455
|
-
.dqw-reset-head-left { display: flex; align-items: baseline; min-width: 0; }
|
|
456
|
-
.dqw-reset-head-left.is-en { gap: 4px; }
|
|
457
457
|
.dqw-auto-master { display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 10px; line-height: 14px; }
|
|
458
458
|
.dqw-auto-master .dqw-auto-box { accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
459
459
|
.dqw-reset-lines { display: grid; grid-template-columns: max-content max-content minmax(0, 1fr) max-content max-content; column-gap: 6px; row-gap: 4px; align-items: center; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
@@ -482,13 +482,21 @@ var FLOAT_STYLE_TEXT = `
|
|
|
482
482
|
.dqw-reset-notice.is-error { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
483
483
|
.dqw-reset-auto { font-size: 10px; line-height: 14px; opacity: .6; }
|
|
484
484
|
/* Auto-use switches: a real checkbox per window, backed by a host-side file so
|
|
485
|
-
automatic spending keeps working while no browser is open.
|
|
486
|
-
|
|
487
|
-
|
|
485
|
+
automatic spending keeps working while no browser is open. Both rows share one
|
|
486
|
+
grid (same recipe as .dqw-reset-lines): the window label stays left-aligned,
|
|
487
|
+
the \xB7 sits in its own narrow centered column so the two rows line up, and the
|
|
488
|
+
used note follows after the column gap. display:contents keeps the <label>
|
|
489
|
+
wrapping its checkbox for click-to-toggle while the cells join the grid. */
|
|
490
|
+
.dqw-auto-grid { display: grid; grid-template-columns: max-content max-content 12px minmax(0, 1fr); column-gap: 6px; row-gap: 2px; align-items: center; font-size: 10.5px; line-height: 16px; }
|
|
491
|
+
.dqw-auto { display: contents; }
|
|
492
|
+
.dqw-auto-row { display: contents; }
|
|
493
|
+
.dqw-auto-grid label > :is(input, span) { cursor: pointer; }
|
|
488
494
|
.dqw-auto-box { width: 12px; height: 12px; margin: 0; flex: none; cursor: pointer; accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
489
495
|
.dqw-auto-box:disabled { cursor: default; }
|
|
490
|
-
.dqw-auto-label { opacity: .75; }
|
|
491
|
-
.dqw-auto-
|
|
496
|
+
.dqw-auto-label { opacity: .75; white-space: nowrap; }
|
|
497
|
+
.dqw-auto-dot { justify-self: center; opacity: .75; }
|
|
498
|
+
.dqw-auto-used { opacity: .75; min-width: 0; }
|
|
499
|
+
.dqw-auto-hint { grid-column: 1 / -1; font-size: 10px; line-height: 14px; padding-left: 18px; color: var(--dsw-alias-label-warning, #d29922); }
|
|
492
500
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
493
501
|
`;
|
|
494
502
|
function localeFor(win) {
|
|
@@ -856,6 +864,7 @@ function mountQuotaCard({
|
|
|
856
864
|
let queuedPlace = false;
|
|
857
865
|
let openKey;
|
|
858
866
|
let panelNav = false;
|
|
867
|
+
let knownResetCards;
|
|
859
868
|
let resetInfo;
|
|
860
869
|
let resetLoading = false;
|
|
861
870
|
let resetPending;
|
|
@@ -917,7 +926,7 @@ function mountQuotaCard({
|
|
|
917
926
|
row.append(text(doc, "span", "dqw-chev", openKey === key ? "\u25BE" : "\u25B8"));
|
|
918
927
|
return [row];
|
|
919
928
|
};
|
|
920
|
-
const panelHeader = ({ title, showBack = false }) => {
|
|
929
|
+
const panelHeader = ({ title, showBack = false, showRefresh = false }) => {
|
|
921
930
|
const header = doc.createElement("div");
|
|
922
931
|
header.className = "dqw-panel-header";
|
|
923
932
|
if (showBack) {
|
|
@@ -931,6 +940,15 @@ function mountQuotaCard({
|
|
|
931
940
|
header.append(back);
|
|
932
941
|
}
|
|
933
942
|
header.append(text(doc, "p", "dqw-panel-title", title));
|
|
943
|
+
if (showRefresh) {
|
|
944
|
+
const refresh = doc.createElement("button");
|
|
945
|
+
refresh.type = "button";
|
|
946
|
+
refresh.className = "dqw-panel-refresh";
|
|
947
|
+
refresh.dataset.action = "panel-refresh";
|
|
948
|
+
refresh.textContent = copy.actions.refreshNow;
|
|
949
|
+
refresh.setAttribute("aria-label", copy.actions.refreshNow);
|
|
950
|
+
header.append(refresh);
|
|
951
|
+
}
|
|
934
952
|
const close = doc.createElement("button");
|
|
935
953
|
close.type = "button";
|
|
936
954
|
close.className = "dqw-panel-close";
|
|
@@ -992,6 +1010,7 @@ function mountQuotaCard({
|
|
|
992
1010
|
try {
|
|
993
1011
|
const payload = await fetchResetEnvelope(fetchImpl, CLIENT_ROUTES.resetCards);
|
|
994
1012
|
resetInfo = payload;
|
|
1013
|
+
if (payload.ok === true && payload.cards !== void 0) knownResetCards = payload.cards;
|
|
995
1014
|
} catch {
|
|
996
1015
|
resetInfo = { ok: false, error: "request-failed" };
|
|
997
1016
|
} finally {
|
|
@@ -1010,6 +1029,7 @@ function mountQuotaCard({
|
|
|
1010
1029
|
if (payload.ok === true) {
|
|
1011
1030
|
resetNotice = { kind: "ok", message: copy.resetCards.succeeded };
|
|
1012
1031
|
resetInfo = payload.cards !== void 0 ? { ok: true, cards: payload.cards } : resetInfo;
|
|
1032
|
+
if (payload.cards !== void 0) knownResetCards = payload.cards;
|
|
1013
1033
|
} else {
|
|
1014
1034
|
resetNotice = { kind: "error", message: payload.message ?? copy.resetCards.failed };
|
|
1015
1035
|
}
|
|
@@ -1101,12 +1121,13 @@ function mountQuotaCard({
|
|
|
1101
1121
|
void saveAutoReset(configKey, box.checked);
|
|
1102
1122
|
});
|
|
1103
1123
|
const threshold = typeof entry?.threshold === "number" ? entry.threshold : AUTO_RESET_THRESHOLD_FALLBACK;
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
"dqw-auto-label",
|
|
1108
|
-
(
|
|
1109
|
-
|
|
1124
|
+
const cards = copy.resetCards;
|
|
1125
|
+
row.append(
|
|
1126
|
+
box,
|
|
1127
|
+
text(doc, "span", "dqw-auto-label", windowKey === "5h" ? cards.autoFiveHour : cards.autoWeek),
|
|
1128
|
+
text(doc, "span", "dqw-auto-dot", "\xB7"),
|
|
1129
|
+
text(doc, "span", "dqw-auto-used", cards.autoUsed.replace("{threshold}", formatNumber(threshold, locale)))
|
|
1130
|
+
);
|
|
1110
1131
|
wrap.append(row);
|
|
1111
1132
|
const provider = (snapshot?.providers ?? []).find((item) => item?.key === "glm");
|
|
1112
1133
|
const used = (provider?.plan?.windows ?? []).find((window) => window?.key === windowKey)?.percent;
|
|
@@ -1120,51 +1141,60 @@ function mountQuotaCard({
|
|
|
1120
1141
|
}
|
|
1121
1142
|
return wrap;
|
|
1122
1143
|
}
|
|
1144
|
+
function resetCardsKnownEmpty() {
|
|
1145
|
+
if (knownResetCards === void 0) return false;
|
|
1146
|
+
return Array.isArray(knownResetCards.fiveHour) && Array.isArray(knownResetCards.week) && knownResetCards.fiveHour.length === 0 && knownResetCards.week.length === 0;
|
|
1147
|
+
}
|
|
1148
|
+
function resetCollapsed() {
|
|
1149
|
+
if (!resetCardsKnownEmpty() || autoOpen || resetPending !== void 0 || resetBusy !== void 0 || resetNotice !== void 0 || autoSaveError !== void 0) return false;
|
|
1150
|
+
const auto = snapshot?.autoReset;
|
|
1151
|
+
if (auto?.fiveHour?.enabled === true || auto?.week?.enabled === true || auto?.persistError !== void 0 || auto?.lastEvent !== void 0) return false;
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
function buildAutoMaster(auto) {
|
|
1155
|
+
if (auto === void 0 || typeof auto !== "object") return void 0;
|
|
1156
|
+
const master = doc.createElement("label");
|
|
1157
|
+
master.className = "dqw-auto-master";
|
|
1158
|
+
const masterBox = doc.createElement("input");
|
|
1159
|
+
masterBox.type = "checkbox";
|
|
1160
|
+
masterBox.className = "dqw-auto-box";
|
|
1161
|
+
masterBox.checked = autoOpen === true;
|
|
1162
|
+
masterBox.disabled = autoSaving === true;
|
|
1163
|
+
masterBox.addEventListener("change", () => {
|
|
1164
|
+
autoOpen = masterBox.checked;
|
|
1165
|
+
saveAutoOpen(win.localStorage, autoOpen);
|
|
1166
|
+
syncPop();
|
|
1167
|
+
});
|
|
1168
|
+
const anyEnabled = auto.fiveHour?.enabled === true || auto.week?.enabled === true;
|
|
1169
|
+
master.append(text(
|
|
1170
|
+
doc,
|
|
1171
|
+
"span",
|
|
1172
|
+
"dqw-reset-title",
|
|
1173
|
+
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle
|
|
1174
|
+
), masterBox);
|
|
1175
|
+
return master;
|
|
1176
|
+
}
|
|
1123
1177
|
function renderResetSection() {
|
|
1124
1178
|
const section = doc.createElement("div");
|
|
1125
1179
|
section.className = "dqw-reset";
|
|
1126
|
-
const head = doc.createElement("div");
|
|
1127
|
-
head.className = "dqw-reset-head";
|
|
1128
|
-
const headLeft = doc.createElement("div");
|
|
1129
|
-
headLeft.className = `dqw-reset-head-left${locale === "en" ? " is-en" : ""}`;
|
|
1130
|
-
headLeft.append(text(doc, "span", "dqw-reset-title", copy.resetCards.section));
|
|
1131
|
-
const reload = doc.createElement("button");
|
|
1132
|
-
reload.type = "button";
|
|
1133
|
-
reload.className = "dqw-reset-reload";
|
|
1134
|
-
reload.textContent = resetLoading ? copy.loading : copy.resetCards.reloadLink;
|
|
1135
|
-
reload.disabled = resetLoading || resetBusy !== void 0;
|
|
1136
|
-
reload.addEventListener("click", () => {
|
|
1137
|
-
loadResetCards();
|
|
1138
|
-
});
|
|
1139
|
-
headLeft.append(reload);
|
|
1140
|
-
head.append(headLeft);
|
|
1141
1180
|
const autoHeader = snapshot?.autoReset;
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
masterBox.addEventListener("change", () => {
|
|
1151
|
-
autoOpen = masterBox.checked;
|
|
1152
|
-
saveAutoOpen(win.localStorage, autoOpen);
|
|
1153
|
-
syncPop();
|
|
1154
|
-
});
|
|
1155
|
-
const anyEnabled = autoHeader.fiveHour?.enabled === true || autoHeader.week?.enabled === true;
|
|
1156
|
-
master.append(text(
|
|
1157
|
-
doc,
|
|
1158
|
-
"span",
|
|
1159
|
-
"dqw-reset-title",
|
|
1160
|
-
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle
|
|
1161
|
-
), masterBox);
|
|
1162
|
-
head.append(master);
|
|
1181
|
+
const master = buildAutoMaster(autoHeader);
|
|
1182
|
+
if (resetCollapsed()) {
|
|
1183
|
+
const row = doc.createElement("div");
|
|
1184
|
+
row.className = "dqw-reset-collapsed";
|
|
1185
|
+
row.append(text(doc, "span", "dqw-reset-title", `${copy.resetCards.section} \xB7 ${copy.resetCards.none}`));
|
|
1186
|
+
if (master !== void 0) row.append(master);
|
|
1187
|
+
section.append(row);
|
|
1188
|
+
return section;
|
|
1163
1189
|
}
|
|
1190
|
+
const head = doc.createElement("div");
|
|
1191
|
+
head.className = "dqw-reset-head";
|
|
1192
|
+
head.append(text(doc, "span", "dqw-reset-title", copy.resetCards.section));
|
|
1193
|
+
if (master !== void 0) head.append(master);
|
|
1164
1194
|
section.append(head);
|
|
1165
|
-
const cards =
|
|
1195
|
+
const cards = knownResetCards;
|
|
1166
1196
|
if (cards === void 0) {
|
|
1167
|
-
const message = resetLoading ? copy.loading : resetInfo?.message ?? (resetInfo === void 0 ? copy.loading : copy.resetCards.loadFailed);
|
|
1197
|
+
const message = resetLoading ? copy.loading : resetInfo?.message ?? snapshot?.reset?.error ?? (resetInfo === void 0 ? copy.loading : copy.resetCards.loadFailed);
|
|
1168
1198
|
section.append(text(doc, "p", "dqw-muted", message));
|
|
1169
1199
|
return section;
|
|
1170
1200
|
}
|
|
@@ -1177,8 +1207,13 @@ function mountQuotaCard({
|
|
|
1177
1207
|
const auto = snapshot?.autoReset;
|
|
1178
1208
|
if (auto !== void 0 && typeof auto === "object") {
|
|
1179
1209
|
if (autoOpen === true) {
|
|
1180
|
-
|
|
1181
|
-
|
|
1210
|
+
const grid = doc.createElement("div");
|
|
1211
|
+
grid.className = "dqw-auto-grid";
|
|
1212
|
+
grid.append(
|
|
1213
|
+
renderAutoResetRow("5h", "fiveHour", auto.fiveHour, auto),
|
|
1214
|
+
renderAutoResetRow("week", "week", auto.week, auto)
|
|
1215
|
+
);
|
|
1216
|
+
section.append(grid);
|
|
1182
1217
|
}
|
|
1183
1218
|
const last = auto.lastEvent;
|
|
1184
1219
|
if (last?.kind === "auto") {
|
|
@@ -1234,7 +1269,7 @@ function mountQuotaCard({
|
|
|
1234
1269
|
}
|
|
1235
1270
|
function renderDetail(detail, key, provider, fromPanelNav = false) {
|
|
1236
1271
|
detail.replaceChildren();
|
|
1237
|
-
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav }));
|
|
1272
|
+
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav, showRefresh: key === "glm" }));
|
|
1238
1273
|
if (provider?.status === "stale") {
|
|
1239
1274
|
const bannerPrefix = provider.errorKind === "auth" ? copy.authRejected : copy.stale;
|
|
1240
1275
|
detail.append(text(doc, "p", "dqw-error", `${bannerPrefix}${provider.error ? `: ${provider.error}` : ""}`));
|
|
@@ -1310,7 +1345,7 @@ function mountQuotaCard({
|
|
|
1310
1345
|
}
|
|
1311
1346
|
if (provider?.credential === "configured" && provider?.providerId === PROVIDER_IDS.glmDomestic) {
|
|
1312
1347
|
detail.append(renderResetSection());
|
|
1313
|
-
if (resetInfo === void 0 && !resetLoading) {
|
|
1348
|
+
if (resetInfo === void 0 && knownResetCards === void 0 && !resetLoading) {
|
|
1314
1349
|
void loadResetCards();
|
|
1315
1350
|
}
|
|
1316
1351
|
}
|
|
@@ -1491,6 +1526,8 @@ function mountQuotaCard({
|
|
|
1491
1526
|
};
|
|
1492
1527
|
const render = (next) => {
|
|
1493
1528
|
snapshot = next;
|
|
1529
|
+
const cards = next?.reset?.status === "ready" ? next.reset.cards : void 0;
|
|
1530
|
+
if (Array.isArray(cards?.fiveHour) && Array.isArray(cards?.week)) knownResetCards = cards;
|
|
1494
1531
|
renderAll();
|
|
1495
1532
|
};
|
|
1496
1533
|
const showTransportError = () => {
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -66,9 +66,8 @@ const COPY = {
|
|
|
66
66
|
count: '{count} 张',
|
|
67
67
|
none: '无可用',
|
|
68
68
|
use: '重置',
|
|
69
|
-
reloadLink: '(刷新)',
|
|
70
69
|
autoToggle: '自动用卡',
|
|
71
|
-
autoActive: '
|
|
70
|
+
autoActive: '自动用卡 · 已启用',
|
|
72
71
|
expiry: '{date} 前有效',
|
|
73
72
|
confirmTitle: '确认使用重置卡?',
|
|
74
73
|
confirmWeek: '将消耗 1 张每周重置卡,立即重置每周额度与 5 小时额度。',
|
|
@@ -80,8 +79,11 @@ const COPY = {
|
|
|
80
79
|
succeeded: '已重置',
|
|
81
80
|
failed: '重置失败',
|
|
82
81
|
loadFailed: '读取重置卡失败',
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Auto rows render as three grid cells — window label, a centered ·
|
|
83
|
+
// separator, and the used note — so the · column lines up across rows.
|
|
84
|
+
autoFiveHour: '自动用卡(5 小时)',
|
|
85
|
+
autoWeek: '自动用卡(每周)',
|
|
86
|
+
autoUsed: '已用 {threshold}% 触发',
|
|
85
87
|
autoImmediate: '当前已用 {used},已超阈值:开启后会立即消耗一张卡',
|
|
86
88
|
autoSaveFailed: '开关未能保存,请重试',
|
|
87
89
|
autoPersistFailed: '开关已生效,但未能写入磁盘(重启后会丢失)',
|
|
@@ -135,7 +137,6 @@ const COPY = {
|
|
|
135
137
|
count: '{count}',
|
|
136
138
|
none: 'none',
|
|
137
139
|
use: 'Reset',
|
|
138
|
-
reloadLink: '(Refresh)',
|
|
139
140
|
autoToggle: 'Auto-use',
|
|
140
141
|
autoActive: 'Auto-use · on',
|
|
141
142
|
expiry: 'expires {date}',
|
|
@@ -149,8 +150,10 @@ const COPY = {
|
|
|
149
150
|
succeeded: 'Reset done',
|
|
150
151
|
failed: 'Reset failed',
|
|
151
152
|
loadFailed: 'Could not read reset cards',
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
// Same three-cell grid as zh: label · used note.
|
|
154
|
+
autoFiveHour: 'Auto-use (5h)',
|
|
155
|
+
autoWeek: 'Auto-use (weekly)',
|
|
156
|
+
autoUsed: 'trigger at {threshold}% used',
|
|
154
157
|
autoImmediate: 'Currently {used} used — already past the threshold: turning this on spends a card immediately',
|
|
155
158
|
autoSaveFailed: 'Could not save the switch — retry',
|
|
156
159
|
autoPersistFailed: 'Switch applied, but it could not be written to disk (lost on restart)',
|
|
@@ -229,6 +232,8 @@ const FLOAT_STYLE_TEXT = `
|
|
|
229
232
|
.dqw-panel-back { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; }
|
|
230
233
|
.dqw-panel-back svg { display: block; width: 16px; height: 16px; }
|
|
231
234
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
235
|
+
.dqw-panel-refresh { flex: none; margin: 0; padding: 4px 6px; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 10px; line-height: 14px; opacity: .72; }
|
|
236
|
+
.dqw-panel-refresh:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); opacity: 1; }
|
|
232
237
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
233
238
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
234
239
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -268,16 +273,11 @@ const FLOAT_STYLE_TEXT = `
|
|
|
268
273
|
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
269
274
|
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
270
275
|
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
271
|
-
/* Reset cards:
|
|
272
|
-
|
|
276
|
+
/* Reset cards: keep the dashed divider even when an empty wallet collapses to
|
|
277
|
+
one row. The panel's 立即刷新 is the only manual re-check control. */
|
|
273
278
|
.dqw-reset { border-top: 1px dashed var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); padding-top: 6px; display: flex; flex-direction: column; gap: 4px; }
|
|
274
|
-
.dqw-reset-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 10px; line-height: 14px; }
|
|
279
|
+
.dqw-reset-head, .dqw-reset-collapsed { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 10px; line-height: 14px; }
|
|
275
280
|
.dqw-reset-title { opacity: .65; }
|
|
276
|
-
.dqw-reset-reload { border: 0; background: none; padding: 0; font: inherit; font-size: 10px; color: var(--dsw-alias-label-secondary, inherit); opacity: .65; cursor: pointer; text-decoration: underline; }
|
|
277
|
-
.dqw-reset-reload:hover { opacity: 1; }
|
|
278
|
-
.dqw-reset-reload:disabled { cursor: default; opacity: .35; text-decoration: none; }
|
|
279
|
-
.dqw-reset-head-left { display: flex; align-items: baseline; min-width: 0; }
|
|
280
|
-
.dqw-reset-head-left.is-en { gap: 4px; }
|
|
281
281
|
.dqw-auto-master { display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 10px; line-height: 14px; }
|
|
282
282
|
.dqw-auto-master .dqw-auto-box { accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
283
283
|
.dqw-reset-lines { display: grid; grid-template-columns: max-content max-content minmax(0, 1fr) max-content max-content; column-gap: 6px; row-gap: 4px; align-items: center; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
@@ -306,13 +306,21 @@ const FLOAT_STYLE_TEXT = `
|
|
|
306
306
|
.dqw-reset-notice.is-error { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
307
307
|
.dqw-reset-auto { font-size: 10px; line-height: 14px; opacity: .6; }
|
|
308
308
|
/* Auto-use switches: a real checkbox per window, backed by a host-side file so
|
|
309
|
-
automatic spending keeps working while no browser is open.
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
automatic spending keeps working while no browser is open. Both rows share one
|
|
310
|
+
grid (same recipe as .dqw-reset-lines): the window label stays left-aligned,
|
|
311
|
+
the · sits in its own narrow centered column so the two rows line up, and the
|
|
312
|
+
used note follows after the column gap. display:contents keeps the <label>
|
|
313
|
+
wrapping its checkbox for click-to-toggle while the cells join the grid. */
|
|
314
|
+
.dqw-auto-grid { display: grid; grid-template-columns: max-content max-content 12px minmax(0, 1fr); column-gap: 6px; row-gap: 2px; align-items: center; font-size: 10.5px; line-height: 16px; }
|
|
315
|
+
.dqw-auto { display: contents; }
|
|
316
|
+
.dqw-auto-row { display: contents; }
|
|
317
|
+
.dqw-auto-grid label > :is(input, span) { cursor: pointer; }
|
|
312
318
|
.dqw-auto-box { width: 12px; height: 12px; margin: 0; flex: none; cursor: pointer; accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
313
319
|
.dqw-auto-box:disabled { cursor: default; }
|
|
314
|
-
.dqw-auto-label { opacity: .75; }
|
|
315
|
-
.dqw-auto-
|
|
320
|
+
.dqw-auto-label { opacity: .75; white-space: nowrap; }
|
|
321
|
+
.dqw-auto-dot { justify-self: center; opacity: .75; }
|
|
322
|
+
.dqw-auto-used { opacity: .75; min-width: 0; }
|
|
323
|
+
.dqw-auto-hint { grid-column: 1 / -1; font-size: 10px; line-height: 14px; padding-left: 18px; color: var(--dsw-alias-label-warning, #d29922); }
|
|
316
324
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
317
325
|
`
|
|
318
326
|
|
|
@@ -802,9 +810,11 @@ export function mountQuotaCard({
|
|
|
802
810
|
let queuedPlace = false
|
|
803
811
|
let openKey
|
|
804
812
|
let panelNav = false
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
|
|
813
|
+
// The host now refreshes the wallet on a slow independent clock; the detail
|
|
814
|
+
// still fetches on first open. Keep the last authoritative list through a
|
|
815
|
+
// transient list failure so the section never vanishes or invents zero cards.
|
|
816
|
+
let knownResetCards
|
|
817
|
+
let resetInfo // last on-demand listResetCards payload (or an error)
|
|
808
818
|
let resetLoading = false
|
|
809
819
|
let resetPending // reset type awaiting confirmation, or undefined
|
|
810
820
|
let resetBusy // reset type currently being spent, or undefined
|
|
@@ -875,8 +885,8 @@ export function mountQuotaCard({
|
|
|
875
885
|
return [row]
|
|
876
886
|
}
|
|
877
887
|
|
|
878
|
-
/** Panel chrome: optional back, title
|
|
879
|
-
const panelHeader = ({ title, showBack = false }) => {
|
|
888
|
+
/** Panel chrome: optional back/refresh, title and close affordance. */
|
|
889
|
+
const panelHeader = ({ title, showBack = false, showRefresh = false }) => {
|
|
880
890
|
const header = doc.createElement('div')
|
|
881
891
|
header.className = 'dqw-panel-header'
|
|
882
892
|
if (showBack) {
|
|
@@ -890,6 +900,15 @@ export function mountQuotaCard({
|
|
|
890
900
|
header.append(back)
|
|
891
901
|
}
|
|
892
902
|
header.append(text(doc, 'p', 'dqw-panel-title', title))
|
|
903
|
+
if (showRefresh) {
|
|
904
|
+
const refresh = doc.createElement('button')
|
|
905
|
+
refresh.type = 'button'
|
|
906
|
+
refresh.className = 'dqw-panel-refresh'
|
|
907
|
+
refresh.dataset.action = 'panel-refresh'
|
|
908
|
+
refresh.textContent = copy.actions.refreshNow
|
|
909
|
+
refresh.setAttribute('aria-label', copy.actions.refreshNow)
|
|
910
|
+
header.append(refresh)
|
|
911
|
+
}
|
|
893
912
|
const close = doc.createElement('button')
|
|
894
913
|
close.type = 'button'
|
|
895
914
|
close.className = 'dqw-panel-close'
|
|
@@ -956,11 +975,8 @@ export function mountQuotaCard({
|
|
|
956
975
|
panel.append(overviewActions())
|
|
957
976
|
}
|
|
958
977
|
|
|
959
|
-
/**
|
|
960
|
-
*
|
|
961
|
-
* Called when the GLM detail opens and after a spend; never on the poller, so a
|
|
962
|
-
* closed panel costs nothing upstream.
|
|
963
|
-
*/
|
|
978
|
+
/** Fetch a live card list for the detail or after spending, independently of
|
|
979
|
+
* the host's ten-minute background snapshot and its force-refresh route. */
|
|
964
980
|
async function loadResetCards() {
|
|
965
981
|
if (resetLoading || disposed) return
|
|
966
982
|
resetLoading = true
|
|
@@ -968,6 +984,7 @@ export function mountQuotaCard({
|
|
|
968
984
|
try {
|
|
969
985
|
const payload = await fetchResetEnvelope(fetchImpl, CLIENT_ROUTES.resetCards)
|
|
970
986
|
resetInfo = payload
|
|
987
|
+
if (payload.ok === true && payload.cards !== undefined) knownResetCards = payload.cards
|
|
971
988
|
} catch {
|
|
972
989
|
resetInfo = { ok: false, error: 'request-failed' }
|
|
973
990
|
} finally {
|
|
@@ -995,6 +1012,7 @@ export function mountQuotaCard({
|
|
|
995
1012
|
// The host returns the post-reset card list; fall back to a re-list when it
|
|
996
1013
|
// could not refresh, so the panel never offers a card that is already gone.
|
|
997
1014
|
resetInfo = payload.cards !== undefined ? { ok: true, cards: payload.cards } : resetInfo
|
|
1015
|
+
if (payload.cards !== undefined) knownResetCards = payload.cards
|
|
998
1016
|
} else {
|
|
999
1017
|
resetNotice = { kind: 'error', message: payload.message ?? copy.resetCards.failed }
|
|
1000
1018
|
}
|
|
@@ -1100,9 +1118,13 @@ export function mountQuotaCard({
|
|
|
1100
1118
|
box.disabled = autoSaving === true
|
|
1101
1119
|
box.addEventListener('change', () => { void saveAutoReset(configKey, box.checked) })
|
|
1102
1120
|
const threshold = typeof entry?.threshold === 'number' ? entry.threshold : AUTO_RESET_THRESHOLD_FALLBACK
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1121
|
+
// Structured cells — window label (left), · (centered column, aligned across
|
|
1122
|
+
// rows), used note — joined by the shared .dqw-auto-grid column gap.
|
|
1123
|
+
const cards = copy.resetCards
|
|
1124
|
+
row.append(box,
|
|
1125
|
+
text(doc, 'span', 'dqw-auto-label', windowKey === '5h' ? cards.autoFiveHour : cards.autoWeek),
|
|
1126
|
+
text(doc, 'span', 'dqw-auto-dot', '·'),
|
|
1127
|
+
text(doc, 'span', 'dqw-auto-used', cards.autoUsed.replace('{threshold}', formatNumber(threshold, locale))))
|
|
1106
1128
|
wrap.append(row)
|
|
1107
1129
|
|
|
1108
1130
|
// Usage is already past the threshold: turning this on spends a card at the
|
|
@@ -1116,57 +1138,74 @@ export function mountQuotaCard({
|
|
|
1116
1138
|
return wrap
|
|
1117
1139
|
}
|
|
1118
1140
|
|
|
1141
|
+
/** Only an authoritative successful list can mean "no cards". Errors retain
|
|
1142
|
+
* the last good inventory, preventing a transient upstream failure from hiding
|
|
1143
|
+
* controls or claiming that a still-available card is gone. */
|
|
1144
|
+
function resetCardsKnownEmpty() {
|
|
1145
|
+
if (knownResetCards === undefined) return false
|
|
1146
|
+
return Array.isArray(knownResetCards.fiveHour) && Array.isArray(knownResetCards.week)
|
|
1147
|
+
&& knownResetCards.fiveHour.length === 0 && knownResetCards.week.length === 0
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
function resetCollapsed() {
|
|
1151
|
+
if (!resetCardsKnownEmpty() || autoOpen || resetPending !== undefined
|
|
1152
|
+
|| resetBusy !== undefined || resetNotice !== undefined || autoSaveError !== undefined) return false
|
|
1153
|
+
const auto = snapshot?.autoReset
|
|
1154
|
+
if (auto?.fiveHour?.enabled === true || auto?.week?.enabled === true
|
|
1155
|
+
|| auto?.persistError !== undefined || auto?.lastEvent !== undefined) return false
|
|
1156
|
+
return true
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/** This checkbox only reveals the real per-window switches; keep it reachable
|
|
1160
|
+
* on the single-line empty state so an account without cards can still pre-arm. */
|
|
1161
|
+
function buildAutoMaster(auto) {
|
|
1162
|
+
if (auto === undefined || typeof auto !== 'object') return undefined
|
|
1163
|
+
const master = doc.createElement('label')
|
|
1164
|
+
master.className = 'dqw-auto-master'
|
|
1165
|
+
const masterBox = doc.createElement('input')
|
|
1166
|
+
masterBox.type = 'checkbox'
|
|
1167
|
+
masterBox.className = 'dqw-auto-box'
|
|
1168
|
+
masterBox.checked = autoOpen === true
|
|
1169
|
+
masterBox.disabled = autoSaving === true
|
|
1170
|
+
masterBox.addEventListener('change', () => {
|
|
1171
|
+
autoOpen = masterBox.checked
|
|
1172
|
+
saveAutoOpen(win.localStorage, autoOpen)
|
|
1173
|
+
syncPop()
|
|
1174
|
+
})
|
|
1175
|
+
// An unchecked disclosure must never imply the host's auto-use switch is off.
|
|
1176
|
+
const anyEnabled = auto.fiveHour?.enabled === true || auto.week?.enabled === true
|
|
1177
|
+
master.append(text(doc, 'span', 'dqw-reset-title',
|
|
1178
|
+
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle), masterBox)
|
|
1179
|
+
return master
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1119
1182
|
/** The reset block: counts, per-type reset buttons, inline confirm and last result. */
|
|
1120
1183
|
function renderResetSection() {
|
|
1121
1184
|
const section = doc.createElement('div')
|
|
1122
1185
|
section.className = 'dqw-reset'
|
|
1123
|
-
const head = doc.createElement('div')
|
|
1124
|
-
head.className = 'dqw-reset-head'
|
|
1125
|
-
// Left: the title carries the manual reload affordance —「额度重置卡(刷新)」.
|
|
1126
|
-
const headLeft = doc.createElement('div')
|
|
1127
|
-
headLeft.className = `dqw-reset-head-left${locale === 'en' ? ' is-en' : ''}`
|
|
1128
|
-
headLeft.append(text(doc, 'span', 'dqw-reset-title', copy.resetCards.section))
|
|
1129
|
-
const reload = doc.createElement('button')
|
|
1130
|
-
reload.type = 'button'
|
|
1131
|
-
reload.className = 'dqw-reset-reload'
|
|
1132
|
-
reload.textContent = resetLoading ? copy.loading : copy.resetCards.reloadLink
|
|
1133
|
-
reload.disabled = resetLoading || resetBusy !== undefined
|
|
1134
|
-
reload.addEventListener('click', () => { loadResetCards() })
|
|
1135
|
-
headLeft.append(reload)
|
|
1136
|
-
head.append(headLeft)
|
|
1137
|
-
// Right: a pure visibility toggle. Checking it only reveals the two auto-use
|
|
1138
|
-
// rows below; actually enabling a window stays with its own per-window switch,
|
|
1139
|
-
// so the default panel is three lines and no single click can arm auto-spend.
|
|
1140
1186
|
const autoHeader = snapshot?.autoReset
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
masterBox.addEventListener('change', () => {
|
|
1150
|
-
autoOpen = masterBox.checked
|
|
1151
|
-
saveAutoOpen(win.localStorage, autoOpen)
|
|
1152
|
-
syncPop()
|
|
1153
|
-
})
|
|
1154
|
-
// Expansion is not authorization: disclose the persisted host switch state
|
|
1155
|
-
// even when these rows are collapsed, so an unchecked box cannot imply off.
|
|
1156
|
-
const anyEnabled = autoHeader.fiveHour?.enabled === true || autoHeader.week?.enabled === true
|
|
1157
|
-
master.append(text(doc, 'span', 'dqw-reset-title',
|
|
1158
|
-
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle), masterBox)
|
|
1159
|
-
head.append(master)
|
|
1187
|
+
const master = buildAutoMaster(autoHeader)
|
|
1188
|
+
if (resetCollapsed()) {
|
|
1189
|
+
const row = doc.createElement('div')
|
|
1190
|
+
row.className = 'dqw-reset-collapsed'
|
|
1191
|
+
row.append(text(doc, 'span', 'dqw-reset-title', `${copy.resetCards.section} · ${copy.resetCards.none}`))
|
|
1192
|
+
if (master !== undefined) row.append(master)
|
|
1193
|
+
section.append(row)
|
|
1194
|
+
return section
|
|
1160
1195
|
}
|
|
1196
|
+
const head = doc.createElement('div')
|
|
1197
|
+
head.className = 'dqw-reset-head'
|
|
1198
|
+
head.append(text(doc, 'span', 'dqw-reset-title', copy.resetCards.section))
|
|
1199
|
+
if (master !== undefined) head.append(master)
|
|
1161
1200
|
section.append(head)
|
|
1162
1201
|
|
|
1163
|
-
const cards =
|
|
1202
|
+
const cards = knownResetCards
|
|
1164
1203
|
if (cards === undefined) {
|
|
1165
1204
|
// Nothing fetched yet, a failed fetch, or no GLM credential on the host: say so
|
|
1166
1205
|
// instead of drawing two rows of zeroes that look like "no cards left".
|
|
1167
1206
|
const message = resetLoading
|
|
1168
1207
|
? copy.loading
|
|
1169
|
-
: resetInfo?.message ?? (resetInfo === undefined ? copy.loading : copy.resetCards.loadFailed)
|
|
1208
|
+
: resetInfo?.message ?? snapshot?.reset?.error ?? (resetInfo === undefined ? copy.loading : copy.resetCards.loadFailed)
|
|
1170
1209
|
section.append(text(doc, 'p', 'dqw-muted', message))
|
|
1171
1210
|
return section
|
|
1172
1211
|
}
|
|
@@ -1185,9 +1224,13 @@ export function mountQuotaCard({
|
|
|
1185
1224
|
if (auto !== undefined && typeof auto === 'object') {
|
|
1186
1225
|
// The two auto-use rows stay collapsed behind the header toggle; the event
|
|
1187
1226
|
// notices below still render while collapsed so auto activity stays visible.
|
|
1227
|
+
// One shared grid is what keeps the · column aligned across both rows.
|
|
1188
1228
|
if (autoOpen === true) {
|
|
1189
|
-
|
|
1190
|
-
|
|
1229
|
+
const grid = doc.createElement('div')
|
|
1230
|
+
grid.className = 'dqw-auto-grid'
|
|
1231
|
+
grid.append(renderAutoResetRow('5h', 'fiveHour', auto.fiveHour, auto),
|
|
1232
|
+
renderAutoResetRow('week', 'week', auto.week, auto))
|
|
1233
|
+
section.append(grid)
|
|
1191
1234
|
}
|
|
1192
1235
|
const last = auto.lastEvent
|
|
1193
1236
|
if (last?.kind === 'auto') {
|
|
@@ -1237,7 +1280,7 @@ export function mountQuotaCard({
|
|
|
1237
1280
|
|
|
1238
1281
|
function renderDetail(detail, key, provider, fromPanelNav = false) {
|
|
1239
1282
|
detail.replaceChildren()
|
|
1240
|
-
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav }))
|
|
1283
|
+
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav, showRefresh: key === 'glm' }))
|
|
1241
1284
|
if (provider?.status === 'stale') {
|
|
1242
1285
|
const bannerPrefix = provider.errorKind === 'auth' ? copy.authRejected : copy.stale
|
|
1243
1286
|
detail.append(text(doc, 'p', 'dqw-error', `${bannerPrefix}${provider.error ? `: ${provider.error}` : ''}`))
|
|
@@ -1320,10 +1363,9 @@ export function mountQuotaCard({
|
|
|
1320
1363
|
// international (z.ai) provider would only ever render a permanent failure.
|
|
1321
1364
|
if (provider?.credential === 'configured' && provider?.providerId === PROVIDER_IDS.glmDomestic) {
|
|
1322
1365
|
detail.append(renderResetSection())
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1326
|
-
if (resetInfo === undefined && !resetLoading) {
|
|
1366
|
+
// The host snapshot carries the full recent wallet. Fetch lazily only
|
|
1367
|
+
// when neither it nor a prior on-demand read has a usable card list.
|
|
1368
|
+
if (resetInfo === undefined && knownResetCards === undefined && !resetLoading) {
|
|
1327
1369
|
void loadResetCards()
|
|
1328
1370
|
}
|
|
1329
1371
|
}
|
|
@@ -1540,6 +1582,8 @@ export function mountQuotaCard({
|
|
|
1540
1582
|
|
|
1541
1583
|
const render = (next) => {
|
|
1542
1584
|
snapshot = next
|
|
1585
|
+
const cards = next?.reset?.status === 'ready' ? next.reset.cards : undefined
|
|
1586
|
+
if (Array.isArray(cards?.fiveHour) && Array.isArray(cards?.week)) knownResetCards = cards
|
|
1543
1587
|
renderAll()
|
|
1544
1588
|
}
|
|
1545
1589
|
|
|
@@ -1657,6 +1701,7 @@ export function mountQuotaCard({
|
|
|
1657
1701
|
// sit inside this shadow tree, so a panel-level listener sees the real
|
|
1658
1702
|
// target with no retargeting ambiguity.
|
|
1659
1703
|
if (event.target?.closest?.('[data-action="panel-refresh"]')) {
|
|
1704
|
+
// The host force-refresh re-lists cards too; do not issue a duplicate GET.
|
|
1660
1705
|
void poll(true)
|
|
1661
1706
|
return
|
|
1662
1707
|
}
|
package/src/host.mjs
CHANGED
|
@@ -516,13 +516,24 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
516
516
|
// same window is retried, so a persistent error cannot hammer
|
|
517
517
|
// the upstream every poll.
|
|
518
518
|
|
|
519
|
-
function refresh() {
|
|
520
|
-
if (pollPromise !== undefined)
|
|
519
|
+
function refresh(forceCards = false) {
|
|
520
|
+
if (pollPromise !== undefined) {
|
|
521
|
+
// A manual refresh must still re-list cards when it overlaps a scheduled
|
|
522
|
+
// round whose ten-minute gate skipped the list.
|
|
523
|
+
if (forceCards !== true) return pollPromise
|
|
524
|
+
return pollPromise.then(async () => {
|
|
525
|
+
if (!disposed) await probeResetCards(true)
|
|
526
|
+
return view()
|
|
527
|
+
})
|
|
528
|
+
}
|
|
521
529
|
if (disposed) return Promise.resolve(view())
|
|
522
530
|
pollPromise = Promise.all([probeGlm(), probeCopilot()])
|
|
523
531
|
.then(async () => {
|
|
524
532
|
updatedAt = now()
|
|
533
|
+
// Spending gets priority over the read-only background list: otherwise
|
|
534
|
+
// two wallet reads could race and a manual spend could slip between them.
|
|
525
535
|
await maybeAutoReset()
|
|
536
|
+
await probeResetCards(forceCards === true)
|
|
526
537
|
return view()
|
|
527
538
|
})
|
|
528
539
|
.finally(() => { pollPromise = undefined })
|
|
@@ -530,11 +541,31 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
530
541
|
}
|
|
531
542
|
|
|
532
543
|
// --- Reset cards -----------------------------------------------------------
|
|
533
|
-
// Card state is
|
|
534
|
-
//
|
|
535
|
-
//
|
|
544
|
+
// Card state is independent of the quota probes: poll it on its own slow
|
|
545
|
+
// clock, as well as on demand and before spending. A failed list never blanks
|
|
546
|
+
// the provider rows or turns a known card count into an authoritative zero.
|
|
536
547
|
let resetState
|
|
537
548
|
let resetSpendInFlight = false
|
|
549
|
+
let resetProbePromise
|
|
550
|
+
const RESET_CARDS_POLL_INTERVAL_MS = 10 * 60_000
|
|
551
|
+
|
|
552
|
+
async function probeResetCards(force = false) {
|
|
553
|
+
if (configuredGlmProvider(ctx) !== PROVIDER_IDS.glmDomestic || resetSpendInFlight) return
|
|
554
|
+
if (resetProbePromise !== undefined) return resetProbePromise
|
|
555
|
+
if (!force && resetState?.checkedAt !== undefined
|
|
556
|
+
&& now() - resetState.checkedAt < RESET_CARDS_POLL_INTERVAL_MS) return
|
|
557
|
+
const probe = (async () => {
|
|
558
|
+
try {
|
|
559
|
+
await listResetCards()
|
|
560
|
+
} catch (error) {
|
|
561
|
+
// Even unexpected credential failures cannot break the quota poll round.
|
|
562
|
+
const { message, kind } = classifyProbeError(error)
|
|
563
|
+
resetState = { status: 'error', error: message, errorKind: kind, checkedAt: now() }
|
|
564
|
+
}
|
|
565
|
+
})()
|
|
566
|
+
resetProbePromise = probe
|
|
567
|
+
try { await probe } finally { resetProbePromise = undefined }
|
|
568
|
+
}
|
|
538
569
|
|
|
539
570
|
/** Resolve the GLM provider + key, or report why no reset can be attempted. */
|
|
540
571
|
async function resetCredential() {
|
|
@@ -548,11 +579,8 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
548
579
|
return { provider, apiKey }
|
|
549
580
|
}
|
|
550
581
|
|
|
551
|
-
/**
|
|
552
|
-
*
|
|
553
|
-
* `force` is advisory only — the endpoint is cheap and the panel opens rarely, so
|
|
554
|
-
* every call re-reads upstream rather than serving a stale count before a spend.
|
|
555
|
-
*/
|
|
582
|
+
/** Fetch the live available cards. The poll clock gates only the background
|
|
583
|
+
* probe; an explicit list or spend always re-reads upstream. */
|
|
556
584
|
async function listResetCards() {
|
|
557
585
|
const credential = await resetCredential()
|
|
558
586
|
if (credential.error !== undefined) {
|
|
@@ -584,6 +612,9 @@ export function createQuotaService(ctx, options = {}) {
|
|
|
584
612
|
if (resetSpendInFlight) return { ok: false, error: 'busy', message: '重置操作进行中,请稍后重试' }
|
|
585
613
|
resetSpendInFlight = true
|
|
586
614
|
try {
|
|
615
|
+
// A spend re-lists against live state; wait for any cosmetic background
|
|
616
|
+
// read first so its older result cannot overwrite the post-spend wallet.
|
|
617
|
+
if (resetProbePromise !== undefined) await resetProbePromise
|
|
587
618
|
return await performResetCard(args, autoWindowKey)
|
|
588
619
|
} finally {
|
|
589
620
|
resetSpendInFlight = false
|
|
@@ -842,7 +873,7 @@ export function makeRefreshRoute(ctx, service) {
|
|
|
842
873
|
handler: async (req, res) => {
|
|
843
874
|
if (!requestAllowed(ctx, req, res) || !methodAllowed(req, res, 'POST')) return
|
|
844
875
|
try {
|
|
845
|
-
await service.refresh()
|
|
876
|
+
await service.refresh(true)
|
|
846
877
|
responseJson(res, 200, service.overview())
|
|
847
878
|
} catch {
|
|
848
879
|
responseJson(res, 500, { ok: false, error: 'refresh failed' })
|