@iasiv5/dsh-quota-watch 0.1.26 → 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 +69 -53
- package/package.json +1 -1
- package/src/client.mjs +87 -65
- 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,7 +249,6 @@ 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
253
|
autoActive: "\u81EA\u52A8\u7528\u5361 \xB7 \u5DF2\u542F\u7528",
|
|
255
254
|
expiry: "{date} \u524D\u6709\u6548",
|
|
@@ -321,7 +320,6 @@ var COPY = {
|
|
|
321
320
|
count: "{count}",
|
|
322
321
|
none: "none",
|
|
323
322
|
use: "Reset",
|
|
324
|
-
reloadLink: "( Refresh )",
|
|
325
323
|
autoToggle: "Auto-use",
|
|
326
324
|
autoActive: "Auto-use \xB7 on",
|
|
327
325
|
expiry: "expires {date}",
|
|
@@ -410,6 +408,8 @@ var FLOAT_STYLE_TEXT = `
|
|
|
410
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; }
|
|
411
409
|
.dqw-panel-back svg { display: block; width: 16px; height: 16px; }
|
|
412
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; }
|
|
413
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; }
|
|
414
414
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
415
415
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -449,18 +449,11 @@ var FLOAT_STYLE_TEXT = `
|
|
|
449
449
|
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
450
450
|
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
451
451
|
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
452
|
-
/* Reset cards:
|
|
453
|
-
|
|
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. */
|
|
454
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; }
|
|
455
|
-
.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; }
|
|
456
456
|
.dqw-reset-title { opacity: .65; }
|
|
457
|
-
/* No underline: the full-width \u300C\uFF08 \u5237\u65B0 \uFF09\u300D brackets carry the affordance, the
|
|
458
|
-
hover opacity bump is the only hover signal. */
|
|
459
|
-
.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; }
|
|
460
|
-
.dqw-reset-reload:hover { opacity: 1; }
|
|
461
|
-
.dqw-reset-reload:disabled { cursor: default; opacity: .35; }
|
|
462
|
-
.dqw-reset-head-left { display: flex; align-items: baseline; min-width: 0; }
|
|
463
|
-
.dqw-reset-head-left.is-en { gap: 4px; }
|
|
464
457
|
.dqw-auto-master { display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 10px; line-height: 14px; }
|
|
465
458
|
.dqw-auto-master .dqw-auto-box { accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
466
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; }
|
|
@@ -871,6 +864,7 @@ function mountQuotaCard({
|
|
|
871
864
|
let queuedPlace = false;
|
|
872
865
|
let openKey;
|
|
873
866
|
let panelNav = false;
|
|
867
|
+
let knownResetCards;
|
|
874
868
|
let resetInfo;
|
|
875
869
|
let resetLoading = false;
|
|
876
870
|
let resetPending;
|
|
@@ -932,7 +926,7 @@ function mountQuotaCard({
|
|
|
932
926
|
row.append(text(doc, "span", "dqw-chev", openKey === key ? "\u25BE" : "\u25B8"));
|
|
933
927
|
return [row];
|
|
934
928
|
};
|
|
935
|
-
const panelHeader = ({ title, showBack = false }) => {
|
|
929
|
+
const panelHeader = ({ title, showBack = false, showRefresh = false }) => {
|
|
936
930
|
const header = doc.createElement("div");
|
|
937
931
|
header.className = "dqw-panel-header";
|
|
938
932
|
if (showBack) {
|
|
@@ -946,6 +940,15 @@ function mountQuotaCard({
|
|
|
946
940
|
header.append(back);
|
|
947
941
|
}
|
|
948
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
|
+
}
|
|
949
952
|
const close = doc.createElement("button");
|
|
950
953
|
close.type = "button";
|
|
951
954
|
close.className = "dqw-panel-close";
|
|
@@ -1007,6 +1010,7 @@ function mountQuotaCard({
|
|
|
1007
1010
|
try {
|
|
1008
1011
|
const payload = await fetchResetEnvelope(fetchImpl, CLIENT_ROUTES.resetCards);
|
|
1009
1012
|
resetInfo = payload;
|
|
1013
|
+
if (payload.ok === true && payload.cards !== void 0) knownResetCards = payload.cards;
|
|
1010
1014
|
} catch {
|
|
1011
1015
|
resetInfo = { ok: false, error: "request-failed" };
|
|
1012
1016
|
} finally {
|
|
@@ -1025,6 +1029,7 @@ function mountQuotaCard({
|
|
|
1025
1029
|
if (payload.ok === true) {
|
|
1026
1030
|
resetNotice = { kind: "ok", message: copy.resetCards.succeeded };
|
|
1027
1031
|
resetInfo = payload.cards !== void 0 ? { ok: true, cards: payload.cards } : resetInfo;
|
|
1032
|
+
if (payload.cards !== void 0) knownResetCards = payload.cards;
|
|
1028
1033
|
} else {
|
|
1029
1034
|
resetNotice = { kind: "error", message: payload.message ?? copy.resetCards.failed };
|
|
1030
1035
|
}
|
|
@@ -1136,51 +1141,60 @@ function mountQuotaCard({
|
|
|
1136
1141
|
}
|
|
1137
1142
|
return wrap;
|
|
1138
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
|
+
}
|
|
1139
1177
|
function renderResetSection() {
|
|
1140
1178
|
const section = doc.createElement("div");
|
|
1141
1179
|
section.className = "dqw-reset";
|
|
1142
|
-
const head = doc.createElement("div");
|
|
1143
|
-
head.className = "dqw-reset-head";
|
|
1144
|
-
const headLeft = doc.createElement("div");
|
|
1145
|
-
headLeft.className = `dqw-reset-head-left${locale === "en" ? " is-en" : ""}`;
|
|
1146
|
-
headLeft.append(text(doc, "span", "dqw-reset-title", copy.resetCards.section));
|
|
1147
|
-
const reload = doc.createElement("button");
|
|
1148
|
-
reload.type = "button";
|
|
1149
|
-
reload.className = "dqw-reset-reload";
|
|
1150
|
-
reload.textContent = resetLoading ? copy.loading : copy.resetCards.reloadLink;
|
|
1151
|
-
reload.disabled = resetLoading || resetBusy !== void 0;
|
|
1152
|
-
reload.addEventListener("click", () => {
|
|
1153
|
-
loadResetCards();
|
|
1154
|
-
});
|
|
1155
|
-
headLeft.append(reload);
|
|
1156
|
-
head.append(headLeft);
|
|
1157
1180
|
const autoHeader = snapshot?.autoReset;
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
masterBox.addEventListener("change", () => {
|
|
1167
|
-
autoOpen = masterBox.checked;
|
|
1168
|
-
saveAutoOpen(win.localStorage, autoOpen);
|
|
1169
|
-
syncPop();
|
|
1170
|
-
});
|
|
1171
|
-
const anyEnabled = autoHeader.fiveHour?.enabled === true || autoHeader.week?.enabled === true;
|
|
1172
|
-
master.append(text(
|
|
1173
|
-
doc,
|
|
1174
|
-
"span",
|
|
1175
|
-
"dqw-reset-title",
|
|
1176
|
-
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle
|
|
1177
|
-
), masterBox);
|
|
1178
|
-
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;
|
|
1179
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);
|
|
1180
1194
|
section.append(head);
|
|
1181
|
-
const cards =
|
|
1195
|
+
const cards = knownResetCards;
|
|
1182
1196
|
if (cards === void 0) {
|
|
1183
|
-
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);
|
|
1184
1198
|
section.append(text(doc, "p", "dqw-muted", message));
|
|
1185
1199
|
return section;
|
|
1186
1200
|
}
|
|
@@ -1255,7 +1269,7 @@ function mountQuotaCard({
|
|
|
1255
1269
|
}
|
|
1256
1270
|
function renderDetail(detail, key, provider, fromPanelNav = false) {
|
|
1257
1271
|
detail.replaceChildren();
|
|
1258
|
-
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav }));
|
|
1272
|
+
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav, showRefresh: key === "glm" }));
|
|
1259
1273
|
if (provider?.status === "stale") {
|
|
1260
1274
|
const bannerPrefix = provider.errorKind === "auth" ? copy.authRejected : copy.stale;
|
|
1261
1275
|
detail.append(text(doc, "p", "dqw-error", `${bannerPrefix}${provider.error ? `: ${provider.error}` : ""}`));
|
|
@@ -1331,7 +1345,7 @@ function mountQuotaCard({
|
|
|
1331
1345
|
}
|
|
1332
1346
|
if (provider?.credential === "configured" && provider?.providerId === PROVIDER_IDS.glmDomestic) {
|
|
1333
1347
|
detail.append(renderResetSection());
|
|
1334
|
-
if (resetInfo === void 0 && !resetLoading) {
|
|
1348
|
+
if (resetInfo === void 0 && knownResetCards === void 0 && !resetLoading) {
|
|
1335
1349
|
void loadResetCards();
|
|
1336
1350
|
}
|
|
1337
1351
|
}
|
|
@@ -1512,6 +1526,8 @@ function mountQuotaCard({
|
|
|
1512
1526
|
};
|
|
1513
1527
|
const render = (next) => {
|
|
1514
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;
|
|
1515
1531
|
renderAll();
|
|
1516
1532
|
};
|
|
1517
1533
|
const showTransportError = () => {
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -66,7 +66,6 @@ const COPY = {
|
|
|
66
66
|
count: '{count} 张',
|
|
67
67
|
none: '无可用',
|
|
68
68
|
use: '重置',
|
|
69
|
-
reloadLink: '( 刷新 )',
|
|
70
69
|
autoToggle: '自动用卡',
|
|
71
70
|
autoActive: '自动用卡 · 已启用',
|
|
72
71
|
expiry: '{date} 前有效',
|
|
@@ -138,7 +137,6 @@ const COPY = {
|
|
|
138
137
|
count: '{count}',
|
|
139
138
|
none: 'none',
|
|
140
139
|
use: 'Reset',
|
|
141
|
-
reloadLink: '( Refresh )',
|
|
142
140
|
autoToggle: 'Auto-use',
|
|
143
141
|
autoActive: 'Auto-use · on',
|
|
144
142
|
expiry: 'expires {date}',
|
|
@@ -234,6 +232,8 @@ const FLOAT_STYLE_TEXT = `
|
|
|
234
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; }
|
|
235
233
|
.dqw-panel-back svg { display: block; width: 16px; height: 16px; }
|
|
236
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; }
|
|
237
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; }
|
|
238
238
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
239
239
|
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -273,18 +273,11 @@ const FLOAT_STYLE_TEXT = `
|
|
|
273
273
|
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
274
274
|
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
275
275
|
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
276
|
-
/* Reset cards:
|
|
277
|
-
|
|
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. */
|
|
278
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; }
|
|
279
|
-
.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; }
|
|
280
280
|
.dqw-reset-title { opacity: .65; }
|
|
281
|
-
/* No underline: the full-width 「( 刷新 )」 brackets carry the affordance, the
|
|
282
|
-
hover opacity bump is the only hover signal. */
|
|
283
|
-
.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; }
|
|
284
|
-
.dqw-reset-reload:hover { opacity: 1; }
|
|
285
|
-
.dqw-reset-reload:disabled { cursor: default; opacity: .35; }
|
|
286
|
-
.dqw-reset-head-left { display: flex; align-items: baseline; min-width: 0; }
|
|
287
|
-
.dqw-reset-head-left.is-en { gap: 4px; }
|
|
288
281
|
.dqw-auto-master { display: flex; align-items: center; gap: 4px; cursor: pointer; font-size: 10px; line-height: 14px; }
|
|
289
282
|
.dqw-auto-master .dqw-auto-box { accent-color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
290
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; }
|
|
@@ -817,9 +810,11 @@ export function mountQuotaCard({
|
|
|
817
810
|
let queuedPlace = false
|
|
818
811
|
let openKey
|
|
819
812
|
let panelNav = false
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
|
|
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)
|
|
823
818
|
let resetLoading = false
|
|
824
819
|
let resetPending // reset type awaiting confirmation, or undefined
|
|
825
820
|
let resetBusy // reset type currently being spent, or undefined
|
|
@@ -890,8 +885,8 @@ export function mountQuotaCard({
|
|
|
890
885
|
return [row]
|
|
891
886
|
}
|
|
892
887
|
|
|
893
|
-
/** Panel chrome: optional back, title
|
|
894
|
-
const panelHeader = ({ title, showBack = false }) => {
|
|
888
|
+
/** Panel chrome: optional back/refresh, title and close affordance. */
|
|
889
|
+
const panelHeader = ({ title, showBack = false, showRefresh = false }) => {
|
|
895
890
|
const header = doc.createElement('div')
|
|
896
891
|
header.className = 'dqw-panel-header'
|
|
897
892
|
if (showBack) {
|
|
@@ -905,6 +900,15 @@ export function mountQuotaCard({
|
|
|
905
900
|
header.append(back)
|
|
906
901
|
}
|
|
907
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
|
+
}
|
|
908
912
|
const close = doc.createElement('button')
|
|
909
913
|
close.type = 'button'
|
|
910
914
|
close.className = 'dqw-panel-close'
|
|
@@ -971,11 +975,8 @@ export function mountQuotaCard({
|
|
|
971
975
|
panel.append(overviewActions())
|
|
972
976
|
}
|
|
973
977
|
|
|
974
|
-
/**
|
|
975
|
-
*
|
|
976
|
-
* Called when the GLM detail opens and after a spend; never on the poller, so a
|
|
977
|
-
* closed panel costs nothing upstream.
|
|
978
|
-
*/
|
|
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. */
|
|
979
980
|
async function loadResetCards() {
|
|
980
981
|
if (resetLoading || disposed) return
|
|
981
982
|
resetLoading = true
|
|
@@ -983,6 +984,7 @@ export function mountQuotaCard({
|
|
|
983
984
|
try {
|
|
984
985
|
const payload = await fetchResetEnvelope(fetchImpl, CLIENT_ROUTES.resetCards)
|
|
985
986
|
resetInfo = payload
|
|
987
|
+
if (payload.ok === true && payload.cards !== undefined) knownResetCards = payload.cards
|
|
986
988
|
} catch {
|
|
987
989
|
resetInfo = { ok: false, error: 'request-failed' }
|
|
988
990
|
} finally {
|
|
@@ -1010,6 +1012,7 @@ export function mountQuotaCard({
|
|
|
1010
1012
|
// The host returns the post-reset card list; fall back to a re-list when it
|
|
1011
1013
|
// could not refresh, so the panel never offers a card that is already gone.
|
|
1012
1014
|
resetInfo = payload.cards !== undefined ? { ok: true, cards: payload.cards } : resetInfo
|
|
1015
|
+
if (payload.cards !== undefined) knownResetCards = payload.cards
|
|
1013
1016
|
} else {
|
|
1014
1017
|
resetNotice = { kind: 'error', message: payload.message ?? copy.resetCards.failed }
|
|
1015
1018
|
}
|
|
@@ -1135,57 +1138,74 @@ export function mountQuotaCard({
|
|
|
1135
1138
|
return wrap
|
|
1136
1139
|
}
|
|
1137
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
|
+
|
|
1138
1182
|
/** The reset block: counts, per-type reset buttons, inline confirm and last result. */
|
|
1139
1183
|
function renderResetSection() {
|
|
1140
1184
|
const section = doc.createElement('div')
|
|
1141
1185
|
section.className = 'dqw-reset'
|
|
1142
|
-
const head = doc.createElement('div')
|
|
1143
|
-
head.className = 'dqw-reset-head'
|
|
1144
|
-
// Left: the title carries the manual reload affordance —「额度重置卡( 刷新 )」.
|
|
1145
|
-
const headLeft = doc.createElement('div')
|
|
1146
|
-
headLeft.className = `dqw-reset-head-left${locale === 'en' ? ' is-en' : ''}`
|
|
1147
|
-
headLeft.append(text(doc, 'span', 'dqw-reset-title', copy.resetCards.section))
|
|
1148
|
-
const reload = doc.createElement('button')
|
|
1149
|
-
reload.type = 'button'
|
|
1150
|
-
reload.className = 'dqw-reset-reload'
|
|
1151
|
-
reload.textContent = resetLoading ? copy.loading : copy.resetCards.reloadLink
|
|
1152
|
-
reload.disabled = resetLoading || resetBusy !== undefined
|
|
1153
|
-
reload.addEventListener('click', () => { loadResetCards() })
|
|
1154
|
-
headLeft.append(reload)
|
|
1155
|
-
head.append(headLeft)
|
|
1156
|
-
// Right: a pure visibility toggle. Checking it only reveals the two auto-use
|
|
1157
|
-
// rows below; actually enabling a window stays with its own per-window switch,
|
|
1158
|
-
// so the default panel is three lines and no single click can arm auto-spend.
|
|
1159
1186
|
const autoHeader = snapshot?.autoReset
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
masterBox.addEventListener('change', () => {
|
|
1169
|
-
autoOpen = masterBox.checked
|
|
1170
|
-
saveAutoOpen(win.localStorage, autoOpen)
|
|
1171
|
-
syncPop()
|
|
1172
|
-
})
|
|
1173
|
-
// Expansion is not authorization: disclose the persisted host switch state
|
|
1174
|
-
// even when these rows are collapsed, so an unchecked box cannot imply off.
|
|
1175
|
-
const anyEnabled = autoHeader.fiveHour?.enabled === true || autoHeader.week?.enabled === true
|
|
1176
|
-
master.append(text(doc, 'span', 'dqw-reset-title',
|
|
1177
|
-
anyEnabled ? copy.resetCards.autoActive : copy.resetCards.autoToggle), masterBox)
|
|
1178
|
-
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
|
|
1179
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)
|
|
1180
1200
|
section.append(head)
|
|
1181
1201
|
|
|
1182
|
-
const cards =
|
|
1202
|
+
const cards = knownResetCards
|
|
1183
1203
|
if (cards === undefined) {
|
|
1184
1204
|
// Nothing fetched yet, a failed fetch, or no GLM credential on the host: say so
|
|
1185
1205
|
// instead of drawing two rows of zeroes that look like "no cards left".
|
|
1186
1206
|
const message = resetLoading
|
|
1187
1207
|
? copy.loading
|
|
1188
|
-
: resetInfo?.message ?? (resetInfo === undefined ? copy.loading : copy.resetCards.loadFailed)
|
|
1208
|
+
: resetInfo?.message ?? snapshot?.reset?.error ?? (resetInfo === undefined ? copy.loading : copy.resetCards.loadFailed)
|
|
1189
1209
|
section.append(text(doc, 'p', 'dqw-muted', message))
|
|
1190
1210
|
return section
|
|
1191
1211
|
}
|
|
@@ -1260,7 +1280,7 @@ export function mountQuotaCard({
|
|
|
1260
1280
|
|
|
1261
1281
|
function renderDetail(detail, key, provider, fromPanelNav = false) {
|
|
1262
1282
|
detail.replaceChildren()
|
|
1263
|
-
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav }))
|
|
1283
|
+
detail.append(panelHeader({ title: copy.providerNames[key], showBack: fromPanelNav, showRefresh: key === 'glm' }))
|
|
1264
1284
|
if (provider?.status === 'stale') {
|
|
1265
1285
|
const bannerPrefix = provider.errorKind === 'auth' ? copy.authRejected : copy.stale
|
|
1266
1286
|
detail.append(text(doc, 'p', 'dqw-error', `${bannerPrefix}${provider.error ? `: ${provider.error}` : ''}`))
|
|
@@ -1343,10 +1363,9 @@ export function mountQuotaCard({
|
|
|
1343
1363
|
// international (z.ai) provider would only ever render a permanent failure.
|
|
1344
1364
|
if (provider?.credential === 'configured' && provider?.providerId === PROVIDER_IDS.glmDomestic) {
|
|
1345
1365
|
detail.append(renderResetSection())
|
|
1346
|
-
//
|
|
1347
|
-
//
|
|
1348
|
-
|
|
1349
|
-
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) {
|
|
1350
1369
|
void loadResetCards()
|
|
1351
1370
|
}
|
|
1352
1371
|
}
|
|
@@ -1563,6 +1582,8 @@ export function mountQuotaCard({
|
|
|
1563
1582
|
|
|
1564
1583
|
const render = (next) => {
|
|
1565
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
|
|
1566
1587
|
renderAll()
|
|
1567
1588
|
}
|
|
1568
1589
|
|
|
@@ -1680,6 +1701,7 @@ export function mountQuotaCard({
|
|
|
1680
1701
|
// sit inside this shadow tree, so a panel-level listener sees the real
|
|
1681
1702
|
// target with no retargeting ambiguity.
|
|
1682
1703
|
if (event.target?.closest?.('[data-action="panel-refresh"]')) {
|
|
1704
|
+
// The host force-refresh re-lists cards too; do not issue a duplicate GET.
|
|
1683
1705
|
void poll(true)
|
|
1684
1706
|
return
|
|
1685
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' })
|