dsh-workbuddy-xdpool 0.2.0 → 0.4.0
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 +77 -0
- package/README.en.md +14 -3
- package/README.md +14 -3
- package/assets/model-picker.png +0 -0
- package/assets/region-tabs.png +0 -0
- package/assets/settings-card.png +0 -0
- package/lib/bin.js +1483 -1294
- package/lib/client.js +719 -219
- package/lib/index.d.ts +767 -629
- package/lib/index.js +2289 -1920
- package/package.json +109 -109
- package/screenshots.json +1 -0
package/lib/client.js
CHANGED
|
@@ -93,11 +93,36 @@ window.__ModuleLoader__.load({
|
|
|
93
93
|
|
|
94
94
|
/* Body layout: status row + accounts list + models list. */
|
|
95
95
|
.dsm-workbuddy-xdpool-usage{display:flex;flex-direction:column;gap:14px;margin:0;padding:14px 0 4px}
|
|
96
|
+
/* Region tabs: two independent suppliers, one shown at a time. */
|
|
97
|
+
.dsm-workbuddy-xdpool-tabs{display:flex;gap:6px;padding:4px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:10px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
98
|
+
/* Empty-region guide: steps for signing in on the other gateway. */
|
|
99
|
+
.dsm-workbuddy-xdpool-empty{display:flex;flex-direction:column;gap:10px;padding:14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
100
|
+
.dsm-workbuddy-xdpool-empty-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
|
|
101
|
+
.dsm-workbuddy-xdpool-empty-steps{display:flex;flex-direction:column;gap:6px;padding:12px;border-radius:10px;background:var(--dsw-alias-bg-layer-3,#2a2c33)}
|
|
102
|
+
.dsm-workbuddy-xdpool-empty-steps-title{margin:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;font-weight:600;line-height:18px}
|
|
103
|
+
.dsm-workbuddy-xdpool-empty-list{margin:0;padding-left:20px;display:flex;flex-direction:column;gap:5px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
104
|
+
.dsm-workbuddy-xdpool-empty-list li{min-width:0}
|
|
105
|
+
.dsm-workbuddy-xdpool-empty-note{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:17px}
|
|
106
|
+
.dsm-workbuddy-xdpool-tab{appearance:none;font:inherit;cursor:pointer;flex:1;border:0;border-radius:7px;padding:7px 10px;color:var(--dsw-alias-label-tertiary,#999);font-size:13px;font-weight:500;line-height:18px;background:transparent;transition:color .16s,background .16s,box-shadow .16s}
|
|
107
|
+
.dsm-workbuddy-xdpool-tab:hover:not(.dsm-workbuddy-xdpool-tab-active){color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
108
|
+
.dsm-workbuddy-xdpool-tab:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
109
|
+
.dsm-workbuddy-xdpool-tab-active{color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,#232529);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2,#3a3d45)}
|
|
110
|
+
.dsm-workbuddy-xdpool-tab-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:baseline;background:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
111
|
+
.dsm-workbuddy-xdpool-tab-dot[data-state="error"]{background:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
112
|
+
.dsm-workbuddy-xdpool-tab-dot[data-state="idle"]{background:var(--dsw-alias-label-dimmed,#9aa0a6)}
|
|
96
113
|
.dsm-workbuddy-xdpool-usage-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
|
97
114
|
.dsm-workbuddy-xdpool-usage-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
98
115
|
.dsm-workbuddy-xdpool-usage-status{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:500;color:var(--dsw-alias-label-primary,#e6e6e6)}
|
|
99
116
|
.dsm-workbuddy-xdpool-usage-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
|
|
100
117
|
.dsm-workbuddy-xdpool-usage-hint{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
118
|
+
/* Distribution switch: priority (drain one) vs round-robin (spread). */
|
|
119
|
+
.dsm-workbuddy-xdpool-dist{display:flex;align-items:center;gap:8px;padding-left:19px;flex-wrap:wrap}
|
|
120
|
+
.dsm-workbuddy-xdpool-dist-title{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
121
|
+
.dsm-workbuddy-xdpool-dist-option{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:999px;padding:2px 10px;font-size:11px;line-height:18px;background:transparent;color:var(--dsw-alias-label-tertiary,#9aa0a8);transition:color .16s,border-color .16s,background .16s}
|
|
122
|
+
.dsm-workbuddy-xdpool-dist-option:hover:not(:disabled):not(.dsm-workbuddy-xdpool-dist-option-active){color:var(--dsw-alias-label-primary,#e6e6e6);border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
123
|
+
.dsm-workbuddy-xdpool-dist-option:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
124
|
+
.dsm-workbuddy-xdpool-dist-option-active{background:var(--dsw-alias-state-success-subtle,rgba(34,160,107,.14));border-color:var(--dsw-alias-state-success-primary,#22a06b);color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
125
|
+
.dsm-workbuddy-xdpool-dist-option:disabled{cursor:default;opacity:.6}
|
|
101
126
|
.dsm-workbuddy-xdpool-usage-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
102
127
|
|
|
103
128
|
/* Account list (each account = a labeled subpanel, same as dingminhua). */
|
|
@@ -117,19 +142,30 @@ window.__ModuleLoader__.load({
|
|
|
117
142
|
.dsm-workbuddy-xdpool-account-modelcool-chip{display:inline-flex;align-items:center;gap:4px;padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.12));color:var(--dsw-alias-state-warning-primary,#d97706)}
|
|
118
143
|
.dsm-workbuddy-xdpool-account-error{margin:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:13px;line-height:20px}
|
|
119
144
|
|
|
120
|
-
/*
|
|
121
|
-
.dsm-workbuddy-xdpool-
|
|
122
|
-
.dsm-workbuddy-xdpool-
|
|
123
|
-
.dsm-workbuddy-xdpool-
|
|
124
|
-
.dsm-workbuddy-xdpool-
|
|
125
|
-
.dsm-workbuddy-xdpool-
|
|
126
|
-
.dsm-workbuddy-xdpool-
|
|
127
|
-
.dsm-workbuddy-xdpool-
|
|
128
|
-
.dsm-workbuddy-xdpool-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.
|
|
145
|
+
/* Two-column stats: packages on the left, total + check-in on the right. */
|
|
146
|
+
.dsm-workbuddy-xdpool-stats{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(170px,.85fr);gap:10px;margin-top:10px}
|
|
147
|
+
.dsm-workbuddy-xdpool-panel{display:flex;flex-direction:column;min-width:0;gap:7px;padding:14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
148
|
+
.dsm-workbuddy-xdpool-panel-title{color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
149
|
+
.dsm-workbuddy-xdpool-panel-empty{color:var(--dsw-alias-label-tertiary,#999);font-size:14px;line-height:20px}
|
|
150
|
+
.dsm-workbuddy-xdpool-panel-error{color:var(--dsw-alias-state-error-primary,#ef4444);font-size:12px;line-height:18px;word-break:break-word}
|
|
151
|
+
.dsm-workbuddy-xdpool-panel-foot{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-top:9px;padding-top:9px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px}
|
|
152
|
+
.dsm-workbuddy-xdpool-panel-foot strong{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-variant-numeric:tabular-nums}
|
|
153
|
+
.dsm-workbuddy-xdpool-packages{display:flex;flex-direction:column;gap:5px;margin:0;padding:0;list-style:none}
|
|
154
|
+
/* One credit package: name + amount on the first line, its deadline beneath.
|
|
155
|
+
A two-row grid keeps the columns aligned across rows; a wrapping flex row
|
|
156
|
+
dropped the deadline onto a second line that started at the container edge,
|
|
157
|
+
so the list read as ragged text rather than a table. */
|
|
158
|
+
.dsm-workbuddy-xdpool-packages li{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:10px;row-gap:1px;align-items:baseline;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px}
|
|
159
|
+
.dsm-workbuddy-xdpool-packages-name{grid-column:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
160
|
+
.dsm-workbuddy-xdpool-packages-value{grid-column:2;justify-self:end;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-variant-numeric:tabular-nums}
|
|
161
|
+
/* Per-package deadline: the upstream grants one-off packs at arbitrary clock
|
|
162
|
+
times, so each row carries its own timestamp; the soon ones are tinted and
|
|
163
|
+
fold onto their own line so a long package name cannot squeeze them out. */
|
|
164
|
+
.dsm-workbuddy-xdpool-packages-when{grid-column:1/-1;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:11px;line-height:15px;font-variant-numeric:tabular-nums}
|
|
165
|
+
.dsm-workbuddy-xdpool-packages-when-soon{color:var(--dsw-alias-state-warning-primary,#d97706)}
|
|
166
|
+
.dsm-workbuddy-xdpool-panel-total{position:relative;align-items:center;text-align:center;overflow:hidden}
|
|
167
|
+
.dsm-workbuddy-xdpool-panel-total::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;opacity:.9;background:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
168
|
+
.dsm-workbuddy-xdpool-total-value{color:var(--dsw-alias-state-success-primary,#22a06b);font-size:30px;line-height:34px;font-weight:700;letter-spacing:-.5px;white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
133
169
|
|
|
134
170
|
/* Model directory list. */
|
|
135
171
|
.dsm-workbuddy-xdpool-models{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
|
|
@@ -147,30 +183,44 @@ window.__ModuleLoader__.load({
|
|
|
147
183
|
.dsm-workbuddy-xdpool-model-meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
|
|
148
184
|
.dsm-workbuddy-xdpool-model-meta-tag{padding:1px 8px;border-radius:999px;font-size:11px;line-height:16px;background:rgba(174,179,187,.11);color:var(--dsw-alias-label-secondary,#c6c9d0)}
|
|
149
185
|
.dsm-workbuddy-xdpool-model-cap{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
|
|
186
|
+
/* Model row: checkbox + image toggle + context-budget radios. */
|
|
187
|
+
.dsm-workbuddy-xdpool-model-off{opacity:.55}
|
|
188
|
+
.dsm-workbuddy-xdpool-model-check{display:flex;align-items:center;gap:8px;min-width:0;flex:1;cursor:pointer}
|
|
189
|
+
.dsm-workbuddy-xdpool-model-check input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe);flex:none}
|
|
190
|
+
.dsm-workbuddy-xdpool-model-controls{display:flex;align-items:center;gap:10px;flex:none;flex-wrap:wrap;justify-content:flex-end}
|
|
191
|
+
.dsm-workbuddy-xdpool-model-image{display:inline-flex;align-items:center;gap:5px;flex:none;cursor:pointer;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
|
|
192
|
+
.dsm-workbuddy-xdpool-model-image input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
193
|
+
.dsm-workbuddy-xdpool-model-budget{display:flex;align-items:center;gap:9px;flex:none;margin:0;padding:0;border:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
|
|
194
|
+
.dsm-workbuddy-xdpool-model-budget label{display:inline-flex;align-items:center;gap:4px;cursor:pointer}
|
|
195
|
+
.dsm-workbuddy-xdpool-model-budget input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
|
|
196
|
+
.dsm-workbuddy-xdpool-models-heading{display:flex;flex-direction:column;gap:2px;min-width:0}
|
|
197
|
+
.dsm-workbuddy-xdpool-models-actions{display:flex;align-items:center;gap:8px;flex:none}
|
|
150
198
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
.dsm-workbuddy-xdpool-checkin
|
|
154
|
-
.dsm-workbuddy-xdpool-checkin-
|
|
155
|
-
.dsm-workbuddy-xdpool-checkin-
|
|
156
|
-
.dsm-workbuddy-xdpool-checkin-
|
|
157
|
-
.dsm-workbuddy-xdpool-checkin-
|
|
158
|
-
.dsm-workbuddy-xdpool-checkin-btn{
|
|
199
|
+
|
|
200
|
+
/* Check-in docked under the total, inside the right-hand panel. */
|
|
201
|
+
.dsm-workbuddy-xdpool-checkin{display:flex;flex-direction:column;align-items:center;gap:7px;width:100%;margin-top:10px;padding-top:11px;border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
|
|
202
|
+
.dsm-workbuddy-xdpool-checkin-meta{display:flex;flex-direction:column;align-items:center;gap:2px;width:100%}
|
|
203
|
+
.dsm-workbuddy-xdpool-checkin-streak{color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:17px;font-variant-numeric:tabular-nums}
|
|
204
|
+
.dsm-workbuddy-xdpool-checkin-daily{color:var(--dsw-alias-state-success-primary,#22a06b);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
|
|
205
|
+
.dsm-workbuddy-xdpool-checkin-bonus{padding:1px 8px;border-radius:999px;font-size:11px;line-height:16px;background:var(--dsw-alias-state-success-subtle,rgba(51,160,107,.14));color:var(--dsw-alias-state-success-primary,#22a06b);text-align:center}
|
|
206
|
+
.dsm-workbuddy-xdpool-checkin-btn{width:100%;padding:5px 10px;border-radius:8px;border:1px solid transparent;font-size:12px;font-weight:600;line-height:18px;cursor:pointer;background:var(--dsw-alias-state-success-primary,#22a06b);color:#fff;transition:opacity .16s,border-color .16s,background .16s}
|
|
159
207
|
.dsm-workbuddy-xdpool-checkin-btn:hover:not(:disabled){opacity:.88}
|
|
160
208
|
.dsm-workbuddy-xdpool-checkin-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
161
209
|
.dsm-workbuddy-xdpool-checkin-btn:disabled{cursor:default;background:transparent;border-color:var(--dsw-alias-border-l2,#3a3d45);color:var(--dsw-alias-label-tertiary,#9aa0a8);opacity:1}
|
|
210
|
+
.dsm-workbuddy-xdpool-checkin-error{color:var(--dsw-alias-state-error-primary,#ef4444);font-size:11px;line-height:16px;text-align:center;word-break:break-word}
|
|
162
211
|
|
|
163
212
|
/* Inline notes + error messages. */
|
|
164
213
|
.dsm-workbuddy-xdpool-note{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:13px;line-height:20px}
|
|
165
214
|
.dsm-workbuddy-xdpool-error{margin:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:13px;line-height:20px}
|
|
166
215
|
|
|
167
|
-
/* Responsive:
|
|
216
|
+
/* Responsive: stack the stats columns on narrow screens. */
|
|
168
217
|
@media (max-width:760px){
|
|
169
|
-
.dsm-workbuddy-xdpool-
|
|
170
|
-
.dsm-workbuddy-xdpool-
|
|
171
|
-
.dsm-workbuddy-xdpool-
|
|
172
|
-
.dsm-workbuddy-xdpool-checkin{align-items:stretch
|
|
173
|
-
.dsm-workbuddy-xdpool-checkin-
|
|
218
|
+
.dsm-workbuddy-xdpool-stats{grid-template-columns:1fr}
|
|
219
|
+
.dsm-workbuddy-xdpool-panel-total{align-items:stretch;text-align:left}
|
|
220
|
+
.dsm-workbuddy-xdpool-total-value{text-align:left}
|
|
221
|
+
.dsm-workbuddy-xdpool-checkin{align-items:stretch}
|
|
222
|
+
.dsm-workbuddy-xdpool-checkin-meta{align-items:flex-start}
|
|
223
|
+
.dsm-workbuddy-xdpool-checkin-bonus{text-align:left}
|
|
174
224
|
}
|
|
175
225
|
`.trim();
|
|
176
226
|
//#endregion
|
|
@@ -189,6 +239,12 @@ window.__ModuleLoader__.load({
|
|
|
189
239
|
*
|
|
190
240
|
* @module dsh-workbuddy-xdpool/client/PoolCard
|
|
191
241
|
*/
|
|
242
|
+
/**
|
|
243
|
+
* Default context window the card offers as the "capped" choice, in tokens.
|
|
244
|
+
* Mirrors the host-side DEFAULT_CONTEXT_BUDGET; declared here rather than
|
|
245
|
+
* imported, because the browser bundle must not pull in the host entry.
|
|
246
|
+
*/
|
|
247
|
+
const DEFAULT_CONTEXT_BUDGET = 2e5;
|
|
192
248
|
const POLL_INTERVAL_MS = 3e4;
|
|
193
249
|
/** Inject or refresh the shared card CSS for the current client bundle. */
|
|
194
250
|
if (typeof document !== "undefined") {
|
|
@@ -235,7 +291,63 @@ window.__ModuleLoader__.load({
|
|
|
235
291
|
if (value >= 1e3 && value % 1e3 === 0) return `${value / 1e3}K`;
|
|
236
292
|
return String(value);
|
|
237
293
|
}
|
|
238
|
-
/**
|
|
294
|
+
/** Build the draft from the server's selection + catalog flags. */
|
|
295
|
+
function draftFromStatus(status) {
|
|
296
|
+
const selection = status.selection;
|
|
297
|
+
const enabled = selection.enabledModelIds;
|
|
298
|
+
const images = selection.imageModelIds;
|
|
299
|
+
const budgets = selection.contextBudgets;
|
|
300
|
+
const out = {};
|
|
301
|
+
for (const model of status.models) {
|
|
302
|
+
const entry = {
|
|
303
|
+
enabled: enabled === void 0 || enabled.includes(model.id),
|
|
304
|
+
images: images === void 0 ? model.supportsImages : images.includes(model.id)
|
|
305
|
+
};
|
|
306
|
+
const budget = budgets?.[model.id];
|
|
307
|
+
if (budget !== void 0) entry.budget = budget;
|
|
308
|
+
out[model.id] = entry;
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
/** True when the draft differs from what the server last reported. */
|
|
313
|
+
function draftIsDirty(status, draft) {
|
|
314
|
+
const selection = status.selection;
|
|
315
|
+
const enabled = new Set(selection.enabledModelIds ?? status.models.filter((m) => m.enabled).map((m) => m.id));
|
|
316
|
+
const images = new Set(selection.imageModelIds ?? status.models.filter((m) => m.supportsImages).map((m) => m.id));
|
|
317
|
+
const budgets = selection.contextBudgets ?? {};
|
|
318
|
+
for (const model of status.models) {
|
|
319
|
+
const entry = draft[model.id];
|
|
320
|
+
if (entry === void 0) continue;
|
|
321
|
+
if (entry.enabled !== enabled.has(model.id)) return true;
|
|
322
|
+
if (entry.images !== images.has(model.id)) return true;
|
|
323
|
+
if ((budgets[model.id] ?? model.nativeContextWindow) !== (entry.budget ?? model.nativeContextWindow)) return true;
|
|
324
|
+
}
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
/** Absolute expiry with the time of day: the upstream grants one-off packages at
|
|
328
|
+
* arbitrary clock times, so "expires 09/19 15:36" is what the user needs — a
|
|
329
|
+
* date alone would read as if it lapsed at midnight. */
|
|
330
|
+
function formatExpiry(ms) {
|
|
331
|
+
if (ms === void 0 || !Number.isFinite(ms)) return "";
|
|
332
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
333
|
+
month: "2-digit",
|
|
334
|
+
day: "2-digit",
|
|
335
|
+
hour: "2-digit",
|
|
336
|
+
minute: "2-digit",
|
|
337
|
+
hour12: false
|
|
338
|
+
}).format(new Date(ms));
|
|
339
|
+
}
|
|
340
|
+
/** Whole days until `ms`, floored at 0; undefined when there is no deadline. */
|
|
341
|
+
function daysUntil(ms) {
|
|
342
|
+
if (ms === void 0 || !Number.isFinite(ms)) return void 0;
|
|
343
|
+
return Math.max(0, Math.floor((ms - Date.now()) / 864e5));
|
|
344
|
+
}
|
|
345
|
+
/** True when a one-off package lapses inside the "expiring soon" window. */
|
|
346
|
+
function isExpiringSoon(pack) {
|
|
347
|
+
if (pack.monthly === true) return false;
|
|
348
|
+
const days = daysUntil(pack.expiresAtMs);
|
|
349
|
+
return days !== void 0 && days <= 3;
|
|
350
|
+
}
|
|
239
351
|
function tagFor(model) {
|
|
240
352
|
const tags = model.tags ?? [];
|
|
241
353
|
if (tags.includes("free")) return "free";
|
|
@@ -243,15 +355,32 @@ window.__ModuleLoader__.load({
|
|
|
243
355
|
if (tags.includes("night-discount")) return "night";
|
|
244
356
|
}
|
|
245
357
|
/** Render pool health, per-account credits/cooldown, and the model directory. */
|
|
246
|
-
function PoolCard({ t }) {
|
|
358
|
+
function PoolCard({ t, settingsScope }) {
|
|
359
|
+
const settingsWritable = settingsScope?.getSnapshot().writable === true;
|
|
360
|
+
/** Which region tab is showing. A CN-only install never leaves this. */
|
|
361
|
+
const [activeRegion, setActiveRegion] = (0, react.useState)("cn");
|
|
247
362
|
const [open, setOpen] = (0, react.useState)(false);
|
|
248
|
-
|
|
363
|
+
/**
|
|
364
|
+
* Last-known status per region. Kept per region (not a single slot) so
|
|
365
|
+
* switching tabs shows the other side's last answer immediately instead of
|
|
366
|
+
* a blank frame, and the tab dots stay meaningful while a tab is hidden.
|
|
367
|
+
*/
|
|
368
|
+
const [statusByRegion, setStatusByRegion] = (0, react.useState)({});
|
|
369
|
+
/** The document for the tab on screen; undefined until its first answer. */
|
|
370
|
+
const status = statusByRegion[activeRegion];
|
|
249
371
|
const [error, setError] = (0, react.useState)(void 0);
|
|
250
372
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
251
373
|
const [cooldownBusy, setCooldownBusy] = (0, react.useState)(false);
|
|
252
374
|
const [flash, setFlash] = (0, react.useState)(void 0);
|
|
253
375
|
/** Account id whose daily claim is currently in flight. */
|
|
254
376
|
const [checkinBusyId, setCheckinBusyId] = (0, react.useState)(void 0);
|
|
377
|
+
/**
|
|
378
|
+
* Draft model selection. `undefined` means "no local edits"; once a checkbox
|
|
379
|
+
* is touched the draft takes over and is what the Save button posts. Discard
|
|
380
|
+
* drops it back to the copy the server last reported.
|
|
381
|
+
*/
|
|
382
|
+
const [draft, setDraft] = (0, react.useState)(void 0);
|
|
383
|
+
const [savingModels, setSavingModels] = (0, react.useState)(false);
|
|
255
384
|
const mounted = (0, react.useRef)(true);
|
|
256
385
|
(0, react.useEffect)(() => {
|
|
257
386
|
mounted.current = true;
|
|
@@ -259,9 +388,15 @@ window.__ModuleLoader__.load({
|
|
|
259
388
|
mounted.current = false;
|
|
260
389
|
};
|
|
261
390
|
}, []);
|
|
262
|
-
|
|
391
|
+
/**
|
|
392
|
+
* Fetch one region's status. `region` is a parameter rather than a closure
|
|
393
|
+
* read so the callback identity does not change with the tab: the polling
|
|
394
|
+
* effect can key off it without restarting on every switch, and each region's
|
|
395
|
+
* last answer stays in its own slot (see `statusByRegion`).
|
|
396
|
+
*/
|
|
397
|
+
const refresh = (0, react.useCallback)(async (region, signal) => {
|
|
263
398
|
try {
|
|
264
|
-
const response = await fetch(`${POOL_STATUS_PATH}`, {
|
|
399
|
+
const response = await fetch(`${POOL_STATUS_PATH}?region=${region}`, {
|
|
265
400
|
headers: { accept: "application/json" },
|
|
266
401
|
credentials: "same-origin",
|
|
267
402
|
...signal === void 0 ? {} : { signal }
|
|
@@ -269,7 +404,10 @@ window.__ModuleLoader__.load({
|
|
|
269
404
|
const value = await response.json().catch(() => void 0);
|
|
270
405
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
271
406
|
if (mounted.current && signal?.aborted !== true) {
|
|
272
|
-
|
|
407
|
+
setStatusByRegion((prev) => ({
|
|
408
|
+
...prev,
|
|
409
|
+
[region]: value
|
|
410
|
+
}));
|
|
273
411
|
setError(void 0);
|
|
274
412
|
}
|
|
275
413
|
} catch (cause) {
|
|
@@ -279,15 +417,19 @@ window.__ModuleLoader__.load({
|
|
|
279
417
|
(0, react.useEffect)(() => {
|
|
280
418
|
if (!open) return;
|
|
281
419
|
const controller = new AbortController();
|
|
282
|
-
refresh(controller.signal);
|
|
420
|
+
refresh(activeRegion, controller.signal);
|
|
283
421
|
const timer = window.setInterval(() => {
|
|
284
|
-
refresh(controller.signal);
|
|
422
|
+
refresh(activeRegion, controller.signal);
|
|
285
423
|
}, POLL_INTERVAL_MS);
|
|
286
424
|
return () => {
|
|
287
425
|
window.clearInterval(timer);
|
|
288
426
|
controller.abort();
|
|
289
427
|
};
|
|
290
|
-
}, [
|
|
428
|
+
}, [
|
|
429
|
+
open,
|
|
430
|
+
refresh,
|
|
431
|
+
activeRegion
|
|
432
|
+
]);
|
|
291
433
|
const rescan = async () => {
|
|
292
434
|
setBusy(true);
|
|
293
435
|
setFlash(void 0);
|
|
@@ -299,7 +441,7 @@ window.__ModuleLoader__.load({
|
|
|
299
441
|
});
|
|
300
442
|
const body = await response.json();
|
|
301
443
|
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
302
|
-
await refresh();
|
|
444
|
+
await refresh(activeRegion);
|
|
303
445
|
if (mounted.current) setFlash(t?.("row.accountsRescanned", { count: body.accounts ?? 0 }) ?? "");
|
|
304
446
|
} catch (cause) {
|
|
305
447
|
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -317,7 +459,7 @@ window.__ModuleLoader__.load({
|
|
|
317
459
|
credentials: "same-origin"
|
|
318
460
|
});
|
|
319
461
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
320
|
-
await refresh();
|
|
462
|
+
await refresh(activeRegion);
|
|
321
463
|
if (mounted.current) setFlash(t?.("row.resetCooldownsDone") ?? "");
|
|
322
464
|
} catch (cause) {
|
|
323
465
|
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -346,7 +488,7 @@ window.__ModuleLoader__.load({
|
|
|
346
488
|
});
|
|
347
489
|
const body = await response.json().catch(() => void 0);
|
|
348
490
|
if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
349
|
-
await refresh();
|
|
491
|
+
await refresh(activeRegion);
|
|
350
492
|
const credit = body?.claim?.credit ?? 0;
|
|
351
493
|
if (mounted.current) setFlash(t?.("row.checkinClaimedReward", { credit: formatNumber(credit) }) ?? `Claimed +${formatNumber(credit)} credits`);
|
|
352
494
|
} catch (cause) {
|
|
@@ -355,12 +497,128 @@ window.__ModuleLoader__.load({
|
|
|
355
497
|
if (mounted.current) setCheckinBusyId(void 0);
|
|
356
498
|
}
|
|
357
499
|
};
|
|
500
|
+
/** Keep the draft in step with the server copy while nothing is dirty. */
|
|
501
|
+
const modelDraft = draft ?? (status === void 0 ? {} : draftFromStatus(status));
|
|
502
|
+
/** Model edits need a writable settings scope; otherwise the rows are read-only. */
|
|
503
|
+
const modelsEditable = settingsWritable;
|
|
504
|
+
const modelsDirty = draft !== void 0 && status !== void 0 && draftIsDirty(status, draft);
|
|
505
|
+
const enabledCount = Object.values(modelDraft).filter((entry) => entry.enabled).length;
|
|
506
|
+
const toggleModel = (id) => {
|
|
507
|
+
if (status === void 0) return;
|
|
508
|
+
const base = draft ?? draftFromStatus(status);
|
|
509
|
+
const entry = base[id];
|
|
510
|
+
if (entry === void 0) return;
|
|
511
|
+
setDraft({
|
|
512
|
+
...base,
|
|
513
|
+
[id]: {
|
|
514
|
+
...entry,
|
|
515
|
+
enabled: !entry.enabled
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
};
|
|
519
|
+
const toggleModelImage = (id) => {
|
|
520
|
+
if (status === void 0) return;
|
|
521
|
+
const base = draft ?? draftFromStatus(status);
|
|
522
|
+
const entry = base[id];
|
|
523
|
+
if (entry === void 0) return;
|
|
524
|
+
setDraft({
|
|
525
|
+
...base,
|
|
526
|
+
[id]: {
|
|
527
|
+
...entry,
|
|
528
|
+
images: !entry.images
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
const setModelBudget = (id, budget) => {
|
|
533
|
+
if (status === void 0) return;
|
|
534
|
+
const base = draft ?? draftFromStatus(status);
|
|
535
|
+
const entry = base[id];
|
|
536
|
+
if (entry === void 0) return;
|
|
537
|
+
setDraft({
|
|
538
|
+
...base,
|
|
539
|
+
[id]: {
|
|
540
|
+
...entry,
|
|
541
|
+
budget
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
const discardModels = () => {
|
|
546
|
+
setDraft(void 0);
|
|
547
|
+
setFlash(void 0);
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* Persist the draft. The route validates the payload again on the host side,
|
|
551
|
+
* so a malformed draft is rejected there rather than silently stored. The
|
|
552
|
+
* card refuses to save an empty enable-list: that would leave the picker
|
|
553
|
+
* with nothing to offer and no obvious way back.
|
|
554
|
+
*/
|
|
555
|
+
/**
|
|
556
|
+
* Persist the draft into the plugin settings section.
|
|
557
|
+
*
|
|
558
|
+
* The write goes through `settingsScope` rather than a bespoke route: that is
|
|
559
|
+
* the same document the model picker reads, so one save covers every account
|
|
560
|
+
* and survives account rotation — the selection is a property of the pool,
|
|
561
|
+
* not of whichever account happens to be serving right now.
|
|
562
|
+
*
|
|
563
|
+
* The card refuses an empty enable-list: saving one would leave the picker
|
|
564
|
+
* with nothing to offer and no obvious way back.
|
|
565
|
+
*/
|
|
566
|
+
/**
|
|
567
|
+
* Switch how the pool spreads requests. Written straight through the
|
|
568
|
+
* settings scope (that is where the host keeps the pool options), so the
|
|
569
|
+
* change lands without a restart and survives the next card refresh.
|
|
570
|
+
*/
|
|
571
|
+
const setDistribution = async (next) => {
|
|
572
|
+
const write = settingsScope?.set;
|
|
573
|
+
if (write === void 0) {
|
|
574
|
+
setError(t?.("row.modelsSaveError", { message: "settings scope is read-only" }) ?? "settings scope is read-only");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
setFlash(void 0);
|
|
578
|
+
try {
|
|
579
|
+
await write.call(settingsScope, "distribution", next);
|
|
580
|
+
await refresh(activeRegion);
|
|
581
|
+
} catch (cause) {
|
|
582
|
+
if (mounted.current) setError(String(cause));
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
const saveModels = async () => {
|
|
586
|
+
if (draft === void 0 || status === void 0) return;
|
|
587
|
+
if (enabledCount === 0) {
|
|
588
|
+
setError(t?.("row.modelsEmpty") ?? "No model enabled");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const write = settingsScope?.set;
|
|
592
|
+
if (write === void 0) {
|
|
593
|
+
setError(t?.("row.modelsSaveError", { message: "settings scope is read-only" }) ?? "settings scope is read-only");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
setSavingModels(true);
|
|
597
|
+
setFlash(void 0);
|
|
598
|
+
try {
|
|
599
|
+
const enabledModelIds = Object.entries(draft).filter(([, e]) => e.enabled).map(([id]) => id);
|
|
600
|
+
const imageModelIds = Object.entries(draft).filter(([, e]) => e.images).map(([id]) => id);
|
|
601
|
+
const contextBudgets = {};
|
|
602
|
+
for (const [id, entry] of Object.entries(draft)) if (entry.budget !== void 0) contextBudgets[id] = entry.budget;
|
|
603
|
+
await write.call(settingsScope, "enabledModelIds", enabledModelIds);
|
|
604
|
+
await write.call(settingsScope, "imageModelIds", imageModelIds);
|
|
605
|
+
await write.call(settingsScope, "contextBudgets", contextBudgets);
|
|
606
|
+
setDraft(void 0);
|
|
607
|
+
if (mounted.current) setFlash(t?.("row.modelsSaved") ?? "Saved");
|
|
608
|
+
} catch (cause) {
|
|
609
|
+
if (mounted.current) setError(t?.("row.modelsSaveError", { message: cause instanceof Error ? cause.message : String(cause) }) ?? String(cause));
|
|
610
|
+
} finally {
|
|
611
|
+
if (mounted.current) setSavingModels(false);
|
|
612
|
+
}
|
|
613
|
+
};
|
|
358
614
|
const title = t?.("row.title") ?? "WorkBuddy XD Pool";
|
|
359
615
|
const description = t?.("row.desc") ?? "";
|
|
360
616
|
const accountCount = status?.accounts.length ?? 0;
|
|
361
617
|
const cooling = status?.cooling ?? 0;
|
|
362
618
|
const state = error !== void 0 ? "error" : status === void 0 && error === void 0 ? "idle" : accountCount > 0 && cooling < accountCount ? "ok" : "idle";
|
|
363
|
-
|
|
619
|
+
/** Human label for the active tab, used inside the empty-state copy. */
|
|
620
|
+
const regionLabel = activeRegion === "cn" ? t?.("row.tabCn") ?? "CN" : t?.("row.tabGlobal") ?? "Global";
|
|
621
|
+
const stateLabel = error !== void 0 ? t?.("row.requestFailed") ?? "Request failed" : accountCount === 0 ? t?.("row.regionEmpty") ?? t?.("row.poolEmpty") ?? "No account yet" : state === "ok" ? t?.("row.ok") ?? "Healthy" : t?.("row.allCooling") ?? "All cooling";
|
|
364
622
|
const shimRunning = status?.shim.running === true;
|
|
365
623
|
const shimHint = status === void 0 ? null : shimRunning ? `${t?.("row.shimRunning") ?? "Provider listening"}${status.shim.baseUrl === void 0 ? "" : ` · ${status.shim.baseUrl}`}` : t?.("row.shimStopped") ?? "Provider loopback not running";
|
|
366
624
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
@@ -400,6 +658,23 @@ window.__ModuleLoader__.load({
|
|
|
400
658
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
401
659
|
className: "dsm-workbuddy-xdpool-usage",
|
|
402
660
|
children: [
|
|
661
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
662
|
+
className: "dsm-workbuddy-xdpool-tabs",
|
|
663
|
+
role: "tablist",
|
|
664
|
+
children: status?.regions.map((region) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
665
|
+
type: "button",
|
|
666
|
+
role: "tab",
|
|
667
|
+
"aria-selected": region === activeRegion,
|
|
668
|
+
className: `dsm-workbuddy-xdpool-tab${region === activeRegion ? " dsm-workbuddy-xdpool-tab-active" : ""}`,
|
|
669
|
+
onClick: () => {
|
|
670
|
+
setActiveRegion(region);
|
|
671
|
+
},
|
|
672
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
673
|
+
className: "dsm-workbuddy-xdpool-tab-dot",
|
|
674
|
+
"data-state": state
|
|
675
|
+
}), region === "cn" ? t?.("row.tabCn") ?? "CN" : t?.("row.tabGlobal") ?? "Global"]
|
|
676
|
+
}, region))
|
|
677
|
+
}),
|
|
403
678
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
404
679
|
className: "dsm-workbuddy-xdpool-usage-head",
|
|
405
680
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -424,6 +699,31 @@ window.__ModuleLoader__.load({
|
|
|
424
699
|
shimHint === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
425
700
|
className: "dsm-workbuddy-xdpool-usage-hint",
|
|
426
701
|
children: shimHint
|
|
702
|
+
}),
|
|
703
|
+
status === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
704
|
+
className: "dsm-workbuddy-xdpool-dist",
|
|
705
|
+
role: "radiogroup",
|
|
706
|
+
"aria-label": t?.("row.distTitle") ?? "Account usage",
|
|
707
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
708
|
+
className: "dsm-workbuddy-xdpool-dist-title",
|
|
709
|
+
children: t?.("row.distTitle") ?? "Account usage"
|
|
710
|
+
}), ["priority", "round-robin"].map((option) => {
|
|
711
|
+
const active = (status.distribution ?? "priority") === option;
|
|
712
|
+
const label = option === "priority" ? t?.("row.distPriority") ?? "Priority" : t?.("row.distRoundRobin") ?? "Round-robin";
|
|
713
|
+
const hint = option === "priority" ? t?.("row.distPriorityHint") ?? "" : t?.("row.distRoundRobinHint") ?? "";
|
|
714
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
715
|
+
type: "button",
|
|
716
|
+
role: "radio",
|
|
717
|
+
"aria-checked": active,
|
|
718
|
+
title: hint,
|
|
719
|
+
disabled: !modelsEditable,
|
|
720
|
+
className: `dsm-workbuddy-xdpool-dist-option${active ? " dsm-workbuddy-xdpool-dist-option-active" : ""}`,
|
|
721
|
+
onClick: () => {
|
|
722
|
+
setDistribution(option);
|
|
723
|
+
},
|
|
724
|
+
children: label
|
|
725
|
+
}, option);
|
|
726
|
+
})]
|
|
427
727
|
})
|
|
428
728
|
]
|
|
429
729
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -455,9 +755,33 @@ window.__ModuleLoader__.load({
|
|
|
455
755
|
className: "dsm-workbuddy-xdpool-error",
|
|
456
756
|
children: t?.("row.error", { message: error }) ?? `Pool status unavailable: ${error}`
|
|
457
757
|
}),
|
|
458
|
-
accountCount === 0 && error === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
459
|
-
className: "dsm-workbuddy-xdpool-
|
|
460
|
-
children:
|
|
758
|
+
accountCount === 0 && error === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
759
|
+
className: "dsm-workbuddy-xdpool-empty",
|
|
760
|
+
children: [
|
|
761
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
762
|
+
className: "dsm-workbuddy-xdpool-empty-title",
|
|
763
|
+
children: t?.("row.regionEmptyTitle", { region: regionLabel }) ?? t?.("row.regionEmpty") ?? "No account yet"
|
|
764
|
+
}),
|
|
765
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
766
|
+
className: "dsm-workbuddy-xdpool-empty-steps",
|
|
767
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
768
|
+
className: "dsm-workbuddy-xdpool-empty-steps-title",
|
|
769
|
+
children: t?.("row.regionHowToTitle", { region: regionLabel }) ?? `How to sign in to the ${regionLabel} version`
|
|
770
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ol", {
|
|
771
|
+
className: "dsm-workbuddy-xdpool-empty-list",
|
|
772
|
+
children: [
|
|
773
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo1") ?? "" }),
|
|
774
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo2") ?? "" }),
|
|
775
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo3") ?? "" }),
|
|
776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: t?.("row.regionHowTo4") ?? "" })
|
|
777
|
+
]
|
|
778
|
+
})]
|
|
779
|
+
}),
|
|
780
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
781
|
+
className: "dsm-workbuddy-xdpool-empty-note",
|
|
782
|
+
children: t?.("row.regionHowToNote") ?? ""
|
|
783
|
+
})
|
|
784
|
+
]
|
|
461
785
|
}) : null,
|
|
462
786
|
accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
463
787
|
className: "dsm-workbuddy-xdpool-accounts",
|
|
@@ -489,18 +813,49 @@ window.__ModuleLoader__.load({
|
|
|
489
813
|
"aria-label": t?.("row.modelsTitle") ?? "Models",
|
|
490
814
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
491
815
|
className: "dsm-workbuddy-xdpool-models-head",
|
|
492
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
493
|
-
className: "dsm-workbuddy-xdpool-models-
|
|
494
|
-
children:
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
817
|
+
className: "dsm-workbuddy-xdpool-models-heading",
|
|
818
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
819
|
+
className: "dsm-workbuddy-xdpool-models-title",
|
|
820
|
+
children: t?.("row.modelsTitle") ?? "Models"
|
|
821
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
822
|
+
className: "dsm-workbuddy-xdpool-models-summary",
|
|
823
|
+
children: t?.("row.modelsEnabledCount", {
|
|
824
|
+
enabled: enabledCount,
|
|
825
|
+
total: status?.models.length ?? 0
|
|
826
|
+
}) ?? `${enabledCount} / ${status?.models.length ?? 0} enabled`
|
|
827
|
+
})]
|
|
828
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
829
|
+
className: "dsm-workbuddy-xdpool-models-actions",
|
|
830
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
831
|
+
type: "button",
|
|
832
|
+
className: "dsm-btn dsm-btn-outline",
|
|
833
|
+
disabled: !modelsDirty || savingModels,
|
|
834
|
+
onClick: discardModels,
|
|
835
|
+
children: t?.("row.modelsDiscard") ?? "Discard"
|
|
836
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
837
|
+
type: "button",
|
|
838
|
+
className: "dsm-btn dsm-btn-primary",
|
|
839
|
+
disabled: !modelsDirty || savingModels || enabledCount === 0,
|
|
840
|
+
onClick: () => {
|
|
841
|
+
saveModels();
|
|
842
|
+
},
|
|
843
|
+
children: savingModels ? t?.("row.modelsSaving") ?? "Saving…" : t?.("row.modelsSave") ?? "Save"
|
|
844
|
+
})]
|
|
498
845
|
})]
|
|
499
846
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
500
847
|
className: "dsm-workbuddy-xdpool-model-list",
|
|
501
848
|
children: status?.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
|
|
502
849
|
model,
|
|
503
|
-
t
|
|
850
|
+
t,
|
|
851
|
+
draft: modelDraft[model.id] ?? {
|
|
852
|
+
enabled: model.enabled,
|
|
853
|
+
images: model.supportsImages
|
|
854
|
+
},
|
|
855
|
+
editable: modelsEditable,
|
|
856
|
+
onToggle: toggleModel,
|
|
857
|
+
onToggleImage: toggleModelImage,
|
|
858
|
+
onBudget: setModelBudget
|
|
504
859
|
}, model.id))
|
|
505
860
|
})]
|
|
506
861
|
}) : null
|
|
@@ -524,60 +879,53 @@ window.__ModuleLoader__.load({
|
|
|
524
879
|
} : null;
|
|
525
880
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
526
881
|
className: "dsm-workbuddy-xdpool-account",
|
|
527
|
-
children: [
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
account.checkin === void 0 && account.checkinError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCheckin, {
|
|
575
|
-
account,
|
|
576
|
-
t,
|
|
577
|
-
busy: checkinBusyId === account.id,
|
|
578
|
-
onClaim: onClaimCheckin
|
|
579
|
-
})
|
|
580
|
-
]
|
|
882
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
883
|
+
className: "dsm-workbuddy-xdpool-account-copy",
|
|
884
|
+
children: [
|
|
885
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
886
|
+
className: "dsm-workbuddy-xdpool-account-label",
|
|
887
|
+
children: account.label
|
|
888
|
+
}),
|
|
889
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
890
|
+
className: "dsm-workbuddy-xdpool-account-tags",
|
|
891
|
+
children: tag === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
892
|
+
className: tag.cls,
|
|
893
|
+
children: tag.text
|
|
894
|
+
})
|
|
895
|
+
}),
|
|
896
|
+
account.domain !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
897
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
898
|
+
children: account.domain
|
|
899
|
+
}),
|
|
900
|
+
account.expiresAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
901
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
902
|
+
children: t?.("row.tokenExpiry", { time: formatDateTime(account.expiresAt) }) ?? `token ${formatDateTime(account.expiresAt)}`
|
|
903
|
+
}) : null,
|
|
904
|
+
isCooling && cooldownUntil !== void 0 && !Number.isNaN(cooldownUntil) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
905
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
906
|
+
children: [
|
|
907
|
+
t?.("row.cooldownUntil", { time: formatTime(cooldownUntil) }) ?? `until ${formatTime(cooldownUntil)}`,
|
|
908
|
+
" · ",
|
|
909
|
+
t?.("row.cooldownHits", { hits: account.rateLimitHits ?? 0 }) ?? `${account.rateLimitHits ?? 0} hit(s)`
|
|
910
|
+
]
|
|
911
|
+
}) : null,
|
|
912
|
+
modelCooldowns.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
913
|
+
className: "dsm-workbuddy-xdpool-account-modelcool",
|
|
914
|
+
children: modelCooldowns.map((mc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
915
|
+
className: "dsm-workbuddy-xdpool-account-modelcool-chip",
|
|
916
|
+
children: t?.("row.modelCooling", {
|
|
917
|
+
model: mc.modelId,
|
|
918
|
+
time: formatDateTime(mc.until)
|
|
919
|
+
}) ?? `${mc.modelId} cooling to ${formatDateTime(mc.until)}`
|
|
920
|
+
}, mc.modelId))
|
|
921
|
+
}) : null
|
|
922
|
+
]
|
|
923
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountStats, {
|
|
924
|
+
account,
|
|
925
|
+
t,
|
|
926
|
+
checkinBusy: checkinBusyId === account.id,
|
|
927
|
+
onClaim: onClaimCheckin
|
|
928
|
+
})]
|
|
581
929
|
});
|
|
582
930
|
}
|
|
583
931
|
/**
|
|
@@ -585,132 +933,206 @@ window.__ModuleLoader__.load({
|
|
|
585
933
|
* Every account in the pool gets its own button, so a multi-account user can
|
|
586
934
|
* collect each reward without switching the pool's preferred account first.
|
|
587
935
|
*/
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const claimable = checkin.active && !checkin.todayCheckedIn;
|
|
596
|
-
const label = !checkin.active ? t?.("row.checkinInactive") ?? "Check-in not available" : checkin.todayCheckedIn ? t?.("row.checkinClaimed") ?? "Checked in today" : busy ? t?.("row.checkinClaiming") ?? "Checking in…" : t?.("row.checkinClaim") ?? "Check in";
|
|
597
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
598
|
-
className: "dsm-workbuddy-xdpool-checkin",
|
|
599
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
600
|
-
className: "dsm-workbuddy-xdpool-checkin-copy",
|
|
601
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
602
|
-
className: "dsm-workbuddy-xdpool-checkin-title",
|
|
603
|
-
children: t?.("row.checkinTitle") ?? "Daily check-in"
|
|
604
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
605
|
-
className: "dsm-workbuddy-xdpool-checkin-meta",
|
|
606
|
-
children: [
|
|
607
|
-
checkin.streakDays > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
608
|
-
className: "dsm-workbuddy-xdpool-checkin-chip",
|
|
609
|
-
children: t?.("row.checkinStreak", { days: checkin.streakDays }) ?? `${checkin.streakDays}-day streak`
|
|
610
|
-
}) : null,
|
|
611
|
-
checkin.dailyCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
612
|
-
className: "dsm-workbuddy-xdpool-checkin-chip",
|
|
613
|
-
children: t?.("row.checkinDaily", { credit: formatNumber(checkin.dailyCredit) }) ?? `+${formatNumber(checkin.dailyCredit)}/day`
|
|
614
|
-
}) : null,
|
|
615
|
-
checkin.isStreakDay ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
616
|
-
className: "dsm-workbuddy-xdpool-checkin-chip dsm-workbuddy-xdpool-checkin-chip-bonus",
|
|
617
|
-
children: t?.("row.checkinStreakBonus", {
|
|
618
|
-
days: formatNumber(checkin.nextStreakDay),
|
|
619
|
-
credit: formatNumber(checkin.streakBonusCredit)
|
|
620
|
-
}) ?? `bonus +${formatNumber(checkin.streakBonusCredit)}`
|
|
621
|
-
}) : null
|
|
622
|
-
]
|
|
623
|
-
})]
|
|
624
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
625
|
-
type: "button",
|
|
626
|
-
className: "dsm-workbuddy-xdpool-checkin-btn",
|
|
627
|
-
disabled: !claimable || busy,
|
|
628
|
-
onClick: () => {
|
|
629
|
-
onClaim(account.id);
|
|
630
|
-
},
|
|
631
|
-
children: label
|
|
632
|
-
})]
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
/** Two-panel credit layout: package list on the left, big total on the right. */
|
|
636
|
-
function AccountCredits({ account, t }) {
|
|
637
|
-
if (account.creditsError !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
638
|
-
className: "dsm-workbuddy-xdpool-account-error",
|
|
639
|
-
children: t?.("row.creditsError", { message: account.creditsError }) ?? account.creditsError
|
|
640
|
-
});
|
|
936
|
+
/**
|
|
937
|
+
* Credit panels: package breakdown on the left, the big total on the right with
|
|
938
|
+
* the daily check-in action docked beneath it. Mirrors the two-column credit
|
|
939
|
+
* layout the LaoDing plugin family uses, so the numbers stay scannable and the
|
|
940
|
+
* claim button sits where the eye already is.
|
|
941
|
+
*/
|
|
942
|
+
function AccountStats({ account, t, checkinBusy, onClaim }) {
|
|
641
943
|
const credits = account.credits;
|
|
642
|
-
|
|
643
|
-
const
|
|
944
|
+
const checkin = account.checkin;
|
|
945
|
+
const hasCredits = credits !== void 0 || account.creditsError !== void 0;
|
|
946
|
+
const hasCheckin = checkin !== void 0 || account.checkinError !== void 0;
|
|
947
|
+
if (!hasCredits && !hasCheckin) return null;
|
|
948
|
+
const packages = (credits?.packages ?? []).filter((p) => (p.size ?? 0) > 0).slice(0, 6);
|
|
644
949
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
645
|
-
className: "dsm-workbuddy-xdpool-
|
|
646
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
647
|
-
className: "dsm-workbuddy-xdpool-
|
|
648
|
-
children: [
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
950
|
+
className: "dsm-workbuddy-xdpool-stats",
|
|
951
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
952
|
+
className: "dsm-workbuddy-xdpool-panel dsm-workbuddy-xdpool-panel-packages",
|
|
953
|
+
children: [
|
|
954
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
955
|
+
className: "dsm-workbuddy-xdpool-panel-title",
|
|
956
|
+
children: t?.("row.creditsPackages") ?? "Credit packages"
|
|
957
|
+
}),
|
|
958
|
+
account.creditsError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
959
|
+
className: "dsm-workbuddy-xdpool-panel-error",
|
|
960
|
+
children: account.creditsError
|
|
961
|
+
}) : packages.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
962
|
+
className: "dsm-workbuddy-xdpool-panel-empty",
|
|
963
|
+
children: "–"
|
|
964
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
965
|
+
className: "dsm-workbuddy-xdpool-packages",
|
|
966
|
+
children: packages.map((pack, index) => {
|
|
967
|
+
const expiry = formatExpiry(pack.expiresAtMs);
|
|
968
|
+
const refresh = formatExpiry(pack.cycleRefreshMs);
|
|
969
|
+
const soon = isExpiringSoon(pack);
|
|
970
|
+
const when = pack.monthly === true ? refresh === "" ? null : t?.("row.creditsRefreshAt", { time: refresh }) ?? `Refreshes ${refresh}` : expiry === "" ? null : t?.("row.creditsExpiresAt", { time: expiry }) ?? `Expires ${expiry}`;
|
|
971
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
|
|
972
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
973
|
+
className: "dsm-workbuddy-xdpool-packages-name",
|
|
974
|
+
children: pack.packageName
|
|
975
|
+
}),
|
|
976
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
977
|
+
className: "dsm-workbuddy-xdpool-packages-value",
|
|
978
|
+
children: t?.("row.creditsPackage", {
|
|
979
|
+
remain: formatNumber(pack.remain),
|
|
980
|
+
size: formatNumber(pack.size)
|
|
981
|
+
}) ?? `${formatNumber(pack.remain)} / ${formatNumber(pack.size)}`
|
|
982
|
+
}),
|
|
983
|
+
when === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
984
|
+
className: `dsm-workbuddy-xdpool-packages-when${soon ? " dsm-workbuddy-xdpool-packages-when-soon" : ""}`,
|
|
985
|
+
title: t?.("row.creditsExpiresSoonTitle") ?? "Expiring within 3 days",
|
|
986
|
+
children: when
|
|
987
|
+
})
|
|
988
|
+
] }, `${pack.packageName}-${String(index)}`);
|
|
989
|
+
})
|
|
990
|
+
}),
|
|
991
|
+
credits?.expiringSoon !== void 0 && credits.expiringSoon > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
992
|
+
className: "dsm-workbuddy-xdpool-panel-foot",
|
|
993
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("row.creditsSoon") ?? "Expiring in 3 days" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatNumber(credits.expiringSoon) })]
|
|
994
|
+
}) : null
|
|
995
|
+
]
|
|
996
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
997
|
+
className: "dsm-workbuddy-xdpool-panel dsm-workbuddy-xdpool-panel-total",
|
|
998
|
+
children: [
|
|
999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1000
|
+
className: "dsm-workbuddy-xdpool-panel-title",
|
|
667
1001
|
children: t?.("row.creditsTotal") ?? "Total"
|
|
668
|
-
}),
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1002
|
+
}),
|
|
1003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1004
|
+
className: "dsm-workbuddy-xdpool-total-value",
|
|
1005
|
+
children: formatNumber(credits?.total)
|
|
1006
|
+
}),
|
|
1007
|
+
hasCheckin ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1008
|
+
className: "dsm-workbuddy-xdpool-checkin",
|
|
1009
|
+
children: account.checkinError !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1010
|
+
className: "dsm-workbuddy-xdpool-checkin-error",
|
|
1011
|
+
children: account.checkinError
|
|
1012
|
+
}) : checkin === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1014
|
+
className: "dsm-workbuddy-xdpool-checkin-meta",
|
|
1015
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1016
|
+
className: "dsm-workbuddy-xdpool-checkin-streak",
|
|
1017
|
+
children: t?.("row.checkinStreak", { days: checkin.streakDays }) ?? `${checkin.streakDays}-day streak`
|
|
1018
|
+
}), checkin.dailyCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1019
|
+
className: "dsm-workbuddy-xdpool-checkin-daily",
|
|
1020
|
+
children: t?.("row.checkinDaily", { credit: formatNumber(checkin.dailyCredit) }) ?? `+${formatNumber(checkin.dailyCredit)}/day`
|
|
1021
|
+
}) : null]
|
|
1022
|
+
}),
|
|
1023
|
+
checkin.isStreakDay && checkin.streakBonusCredit > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1024
|
+
className: "dsm-workbuddy-xdpool-checkin-bonus",
|
|
1025
|
+
children: t?.("row.checkinStreakBonus", {
|
|
1026
|
+
days: formatNumber(checkin.nextStreakDay),
|
|
1027
|
+
credit: formatNumber(checkin.streakBonusCredit)
|
|
1028
|
+
}) ?? `bonus +${formatNumber(checkin.streakBonusCredit)}`
|
|
1029
|
+
}) : null,
|
|
1030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1031
|
+
type: "button",
|
|
1032
|
+
className: "dsm-workbuddy-xdpool-checkin-btn",
|
|
1033
|
+
disabled: !checkin.active || checkin.todayCheckedIn || checkinBusy,
|
|
1034
|
+
onClick: () => {
|
|
1035
|
+
onClaim(account.id);
|
|
1036
|
+
},
|
|
1037
|
+
children: !checkin.active ? t?.("row.checkinInactive") ?? "Unavailable" : checkin.todayCheckedIn ? t?.("row.checkinClaimed") ?? "Checked in" : checkinBusy ? t?.("row.checkinClaiming") ?? "Checking in…" : t?.("row.checkinClaim") ?? "Check in"
|
|
1038
|
+
})
|
|
1039
|
+
] })
|
|
1040
|
+
}) : null
|
|
1041
|
+
]
|
|
673
1042
|
})]
|
|
674
1043
|
});
|
|
675
1044
|
}
|
|
676
|
-
/**
|
|
677
|
-
|
|
1045
|
+
/**
|
|
1046
|
+
* One model row.
|
|
1047
|
+
*
|
|
1048
|
+
* Read-only when the card has no writable settings scope: the checkbox and the
|
|
1049
|
+
* context radios stay disabled rather than pretending an edit took hold. The
|
|
1050
|
+
* draft lives in the parent, so this component only ever reports intent.
|
|
1051
|
+
*/
|
|
1052
|
+
function ModelRow({ model, t, draft, editable, onToggle, onToggleImage, onBudget }) {
|
|
678
1053
|
const tag = tagFor(model);
|
|
679
1054
|
const tagText = tag === "free" ? t?.("row.free") ?? "free" : tag === "limited" ? t?.("row.limitedFree") ?? "limited free" : tag === "night" ? t?.("row.nightDiscount") ?? "night" : null;
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1055
|
+
const native = model.nativeContextWindow;
|
|
1056
|
+
const capped = native > DEFAULT_CONTEXT_BUDGET;
|
|
1057
|
+
const currentBudget = draft.budget ?? native;
|
|
1058
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1059
|
+
className: `dsm-workbuddy-xdpool-model${draft.enabled ? "" : " dsm-workbuddy-xdpool-model-off"}`,
|
|
1060
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
683
1061
|
className: "dsm-workbuddy-xdpool-model-head",
|
|
684
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("
|
|
685
|
-
className: "dsm-workbuddy-xdpool-model-
|
|
686
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1062
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1063
|
+
className: "dsm-workbuddy-xdpool-model-check",
|
|
1064
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1065
|
+
type: "checkbox",
|
|
1066
|
+
checked: draft.enabled,
|
|
1067
|
+
disabled: !editable,
|
|
1068
|
+
onChange: () => {
|
|
1069
|
+
onToggle(model.id);
|
|
1070
|
+
}
|
|
1071
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1072
|
+
className: "dsm-workbuddy-xdpool-model-copy",
|
|
1073
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1074
|
+
className: "dsm-workbuddy-xdpool-model-name",
|
|
1075
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1076
|
+
className: "dsm-workbuddy-xdpool-model-name-rate",
|
|
1077
|
+
children: t?.("row.rate", { rate: model.multiplier.toFixed(2) }) ?? `${model.multiplier.toFixed(2)}x`
|
|
1078
|
+
})]
|
|
1079
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1080
|
+
className: "dsm-workbuddy-xdpool-model-id",
|
|
1081
|
+
children: model.id
|
|
691
1082
|
})]
|
|
692
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
693
|
-
className: "dsm-workbuddy-xdpool-model-id",
|
|
694
|
-
children: model.id
|
|
695
1083
|
})]
|
|
696
1084
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
697
|
-
className: "dsm-workbuddy-xdpool-model-
|
|
698
|
-
children: [
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1085
|
+
className: "dsm-workbuddy-xdpool-model-controls",
|
|
1086
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1087
|
+
className: "dsm-workbuddy-xdpool-model-image",
|
|
1088
|
+
title: t?.("row.modelImage") ?? "Image input",
|
|
1089
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1090
|
+
type: "checkbox",
|
|
1091
|
+
checked: draft.images,
|
|
1092
|
+
disabled: !editable,
|
|
1093
|
+
onChange: () => {
|
|
1094
|
+
onToggleImage(model.id);
|
|
1095
|
+
}
|
|
1096
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("row.modelImage") ?? "Image" })]
|
|
1097
|
+
}), capped ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
|
|
1098
|
+
className: "dsm-workbuddy-xdpool-model-budget",
|
|
1099
|
+
"aria-label": t?.("row.modelContextBudget") ?? "Context",
|
|
1100
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1101
|
+
type: "radio",
|
|
1102
|
+
name: `budget-${model.id}`,
|
|
1103
|
+
checked: currentBudget === DEFAULT_CONTEXT_BUDGET,
|
|
1104
|
+
disabled: !editable,
|
|
1105
|
+
onChange: () => {
|
|
1106
|
+
onBudget(model.id, DEFAULT_CONTEXT_BUDGET);
|
|
1107
|
+
}
|
|
1108
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(DEFAULT_CONTEXT_BUDGET) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1109
|
+
type: "radio",
|
|
1110
|
+
name: `budget-${model.id}`,
|
|
1111
|
+
checked: currentBudget === native,
|
|
1112
|
+
disabled: !editable,
|
|
1113
|
+
onChange: () => {
|
|
1114
|
+
onBudget(model.id, native);
|
|
1115
|
+
}
|
|
1116
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(native) })] })]
|
|
1117
|
+
}) : null]
|
|
712
1118
|
})]
|
|
713
|
-
})
|
|
1119
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1120
|
+
className: "dsm-workbuddy-xdpool-model-meta",
|
|
1121
|
+
children: [
|
|
1122
|
+
tagText === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1123
|
+
className: "dsm-workbuddy-xdpool-model-meta-tag",
|
|
1124
|
+
children: tagText
|
|
1125
|
+
}),
|
|
1126
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1127
|
+
className: "dsm-workbuddy-xdpool-model-cap",
|
|
1128
|
+
children: t?.("row.modelOutput", { size: formatCapacity(model.maxOutputTokens) }) ?? `out ${formatCapacity(model.maxOutputTokens)}`
|
|
1129
|
+
}),
|
|
1130
|
+
model.supportedEfforts === void 0 || model.supportedEfforts.length === 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1131
|
+
className: "dsm-workbuddy-xdpool-model-cap",
|
|
1132
|
+
children: t?.("row.modelReasoning", { efforts: model.supportedEfforts.join(" / ") }) ?? model.supportedEfforts.join(" / ")
|
|
1133
|
+
})
|
|
1134
|
+
]
|
|
1135
|
+
})]
|
|
714
1136
|
});
|
|
715
1137
|
}
|
|
716
1138
|
//#endregion
|
|
@@ -730,9 +1152,21 @@ window.__ModuleLoader__.load({
|
|
|
730
1152
|
"row.requestFailed": "Request failed",
|
|
731
1153
|
"row.poolEmpty": "No WorkBuddy account discovered yet.",
|
|
732
1154
|
"row.poolEmptyHint": "Sign in to one or more WorkBuddy accounts in the WorkBuddy desktop app, then click “Detect accounts again”. Each sign-in is picked up automatically as a pool member.",
|
|
1155
|
+
"row.regionEmpty": "No account signed in for this region yet.",
|
|
1156
|
+
"row.regionEmptyHint": "Sign in to a WorkBuddy account for this region in the desktop app, then choose “Detect accounts again”. Domestic and international accounts can be signed in side by side.",
|
|
1157
|
+
"row.regionEmptyTitle": "No {region} account is signed in yet.",
|
|
1158
|
+
"row.regionHowToTitle": "How to sign in to the {region} version",
|
|
1159
|
+
"row.regionHowTo1": "Download and install the {region} client: the international build is named WorkBuddy AI while the domestic one is WorkBuddy, and they are different apps. The web version at https://www.workbuddy.ai/ also works.",
|
|
1160
|
+
"row.regionHowTo2": "Pick a sign-in method: email sign-up / sign-in (most common), OAuth with Google, GitHub or X, or WeChat QR scan on some builds.",
|
|
1161
|
+
"row.regionHowTo3": "Make sure this machine can reach overseas sites when signing in: the international version targets users outside mainland China and may fail to load behind a restricted network.",
|
|
1162
|
+
"row.regionHowTo4": "Back here, press the re-detect button. Every sign-in the client has left on this machine is absorbed into its own region - the two sides stay separate and can run at the same time.",
|
|
1163
|
+
"row.regionHowToNote": "The two versions keep separate accounts, credits and data: a domestic account cannot sign in to the international one, and vice versa, so each needs its own registration. This plugin only reads the sign-ins the client has already performed.",
|
|
733
1164
|
"row.shimStopped": "Provider loopback is not running.",
|
|
734
1165
|
"row.shimRunning": "Provider listening on loopback",
|
|
735
1166
|
"row.accountsTitle": "Accounts in the pool",
|
|
1167
|
+
"row.tabCn": "国内版",
|
|
1168
|
+
"row.tabGlobal": "国际版",
|
|
1169
|
+
"row.tabHint": "每个 tab 是一个独立供应商,各有自己的账号、积分与模型;两边同时生效,一侧的改动不影响另一侧。",
|
|
736
1170
|
"row.accountsSummary": "{count} account(s) · {cooling} cooling",
|
|
737
1171
|
"row.ok": "Healthy — requests auto-rotate across accounts",
|
|
738
1172
|
"row.allCooling": "Every account is rate-limited right now; requests pause until a cooldown lifts.",
|
|
@@ -746,6 +1180,10 @@ window.__ModuleLoader__.load({
|
|
|
746
1180
|
"row.creditsPackages": "Credit packages",
|
|
747
1181
|
"row.creditsPackage": "{remain} / {size}",
|
|
748
1182
|
"row.creditsError": "credits unavailable",
|
|
1183
|
+
"row.creditsSoon": "Expiring in 3 days",
|
|
1184
|
+
"row.creditsExpiresAt": "Expires {time}",
|
|
1185
|
+
"row.creditsExpiresSoonTitle": "Expiring within 3 days",
|
|
1186
|
+
"row.creditsRefreshAt": "Refreshes {time}",
|
|
749
1187
|
"row.checkinTitle": "Daily check-in",
|
|
750
1188
|
"row.checkinClaim": "Check in",
|
|
751
1189
|
"row.checkinClaiming": "Checking in…",
|
|
@@ -760,12 +1198,31 @@ window.__ModuleLoader__.load({
|
|
|
760
1198
|
"row.modelsTitle": "Models",
|
|
761
1199
|
"row.modelsSummary": "{count} model(s) in the live catalog",
|
|
762
1200
|
"row.modelsHint": "Read from the live WorkBuddy catalog. Free tiers are marked.",
|
|
1201
|
+
"row.modelEnabled": "Enabled",
|
|
1202
|
+
"row.modelImage": "Image input",
|
|
1203
|
+
"row.modelContextBudget": "Context window",
|
|
1204
|
+
"row.modelContextNative": "{size} (max)",
|
|
1205
|
+
"row.modelContextCapped": "{size}",
|
|
1206
|
+
"row.modelOutput": "Output {size}",
|
|
1207
|
+
"row.modelReasoning": "Thinking: {efforts}",
|
|
1208
|
+
"row.modelsEnabledCount": "{enabled} / {total} enabled",
|
|
1209
|
+
"row.modelsSave": "Save",
|
|
1210
|
+
"row.modelsSaving": "Saving…",
|
|
1211
|
+
"row.modelsDiscard": "Discard",
|
|
1212
|
+
"row.modelsSaved": "Model selection saved",
|
|
1213
|
+
"row.modelsSaveError": "Could not save: {message}",
|
|
1214
|
+
"row.modelsEmpty": "No model enabled — enable at least one before saving.",
|
|
763
1215
|
"row.free": "free",
|
|
764
1216
|
"row.limitedFree": "limited free",
|
|
765
1217
|
"row.nightDiscount": "night",
|
|
766
1218
|
"row.imageCapable": "image input",
|
|
767
1219
|
"row.rate": "{rate}x credits",
|
|
768
1220
|
"row.accountsRescan": "Detect accounts again",
|
|
1221
|
+
"row.distTitle": "Account usage",
|
|
1222
|
+
"row.distPriority": "Priority",
|
|
1223
|
+
"row.distPriorityHint": "Drain one account before moving to the next",
|
|
1224
|
+
"row.distRoundRobin": "Round-robin",
|
|
1225
|
+
"row.distRoundRobinHint": "Spread the spend evenly across accounts",
|
|
769
1226
|
"row.accountsScanning": "Detecting…",
|
|
770
1227
|
"row.resetCooldowns": "Clear all cooldowns",
|
|
771
1228
|
"row.resetCooldownsBusy": "Clearing…",
|
|
@@ -781,9 +1238,21 @@ window.__ModuleLoader__.load({
|
|
|
781
1238
|
"row.requestFailed": "请求失败",
|
|
782
1239
|
"row.poolEmpty": "还没有发现任何 WorkBuddy 账号。",
|
|
783
1240
|
"row.poolEmptyHint": "先在 WorkBuddy 桌面 App 里登录一个或多个 WorkBuddy 账号,再点“重新检测账号”。每次登录都会被自动纳入池中。",
|
|
1241
|
+
"row.regionEmpty": "这边还没有登录账号。",
|
|
1242
|
+
"row.regionEmptyHint": "在 WorkBuddy 桌面 App 里登录一个该区域的账号,再点「重新检测账号」。国内版与国际版可以同时登录,两边各自独立。",
|
|
1243
|
+
"row.regionEmptyTitle": "这边还没有登录{region}账号。",
|
|
1244
|
+
"row.regionHowToTitle": "{region}怎么登录",
|
|
1245
|
+
"row.regionHowTo1": "下载并安装{region}客户端:国际版安装包名称是「WorkBuddy AI」,国内版是「WorkBuddy」,两者是不同的应用。也可以直接用网页版 https://www.workbuddy.ai/ 登录。",
|
|
1246
|
+
"row.regionHowTo2": "登录方式(任选其一):① 邮箱注册/登录(最常用);② 用 Google、GitHub、X 等海外账号授权登录;③ 部分版本支持微信扫码。",
|
|
1247
|
+
"row.regionHowTo3": "登录时请确保能正常访问海外站点(国际版面向海外用户,网络受限时可能打不开或登录失败)。",
|
|
1248
|
+
"row.regionHowTo4": "回到这里点「重新检测账号」。App 在本机留下的每次登录都会被自动吸收到各自区域 —— 两边互相独立,可以同时使用。",
|
|
1249
|
+
"row.regionHowToNote": "国内版与国际版的账号、积分、数据体系完全隔离,互不相通:国内版账号无法登录国际版,反之亦然,需要各自单独注册。本插件只读取客户端已完成的登录,不会代替你登录。",
|
|
784
1250
|
"row.shimStopped": "回环提供端未运行。",
|
|
785
1251
|
"row.shimRunning": "提供端正在回环地址监听",
|
|
786
1252
|
"row.accountsTitle": "池中账号",
|
|
1253
|
+
"row.tabCn": "国内版",
|
|
1254
|
+
"row.tabGlobal": "国际版",
|
|
1255
|
+
"row.tabHint": "每个 tab 是一个独立供应商,各有自己的账号、积分与模型;两边同时生效,一侧的改动不影响另一侧。",
|
|
787
1256
|
"row.accountsSummary": "{count} 个账号 · {cooling} 个冷却中",
|
|
788
1257
|
"row.ok": "运行健康 —— 请求会在各账号间自动轮换",
|
|
789
1258
|
"row.allCooling": "当前所有账号都处于限流冷却,请求会暂停直到某个冷却结束。",
|
|
@@ -797,6 +1266,10 @@ window.__ModuleLoader__.load({
|
|
|
797
1266
|
"row.creditsPackages": "积分包",
|
|
798
1267
|
"row.creditsPackage": "{remain} / {size}",
|
|
799
1268
|
"row.creditsError": "积分不可用",
|
|
1269
|
+
"row.creditsSoon": "3 天内到期",
|
|
1270
|
+
"row.creditsExpiresAt": "到期 {time}",
|
|
1271
|
+
"row.creditsExpiresSoonTitle": "3 天内到期",
|
|
1272
|
+
"row.creditsRefreshAt": "刷新 {time}",
|
|
800
1273
|
"row.checkinTitle": "每日签到",
|
|
801
1274
|
"row.checkinClaim": "签到",
|
|
802
1275
|
"row.checkinClaiming": "签到中…",
|
|
@@ -811,12 +1284,31 @@ window.__ModuleLoader__.load({
|
|
|
811
1284
|
"row.modelsTitle": "模型",
|
|
812
1285
|
"row.modelsSummary": "实时目录中 {count} 个模型",
|
|
813
1286
|
"row.modelsHint": "读取自 WorkBuddy 实时目录;免费档位已标注。",
|
|
1287
|
+
"row.modelEnabled": "启用",
|
|
1288
|
+
"row.modelImage": "图片输入",
|
|
1289
|
+
"row.modelContextBudget": "上下文窗口",
|
|
1290
|
+
"row.modelContextNative": "{size}(最大)",
|
|
1291
|
+
"row.modelContextCapped": "{size}",
|
|
1292
|
+
"row.modelOutput": "输出 {size}",
|
|
1293
|
+
"row.modelReasoning": "思考档位:{efforts}",
|
|
1294
|
+
"row.modelsEnabledCount": "已启用 {enabled} / {total}",
|
|
1295
|
+
"row.modelsSave": "保存",
|
|
1296
|
+
"row.modelsSaving": "保存中…",
|
|
1297
|
+
"row.modelsDiscard": "放弃修改",
|
|
1298
|
+
"row.modelsSaved": "模型选择已保存",
|
|
1299
|
+
"row.modelsSaveError": "保存失败:{message}",
|
|
1300
|
+
"row.modelsEmpty": "至少要启用一个模型才能保存。",
|
|
814
1301
|
"row.free": "免费",
|
|
815
1302
|
"row.limitedFree": "限量免费",
|
|
816
1303
|
"row.nightDiscount": "夜间",
|
|
817
1304
|
"row.imageCapable": "图片输入",
|
|
818
1305
|
"row.rate": "{rate}x 积分",
|
|
819
1306
|
"row.accountsRescan": "重新检测账号",
|
|
1307
|
+
"row.distTitle": "账号使用方式",
|
|
1308
|
+
"row.distPriority": "优先模式",
|
|
1309
|
+
"row.distPriorityHint": "优先用完一个账号再换下一个",
|
|
1310
|
+
"row.distRoundRobin": "轮换模式",
|
|
1311
|
+
"row.distRoundRobinHint": "积分平均分摊到各账号",
|
|
820
1312
|
"row.accountsScanning": "正在检测…",
|
|
821
1313
|
"row.resetCooldowns": "清除所有冷却",
|
|
822
1314
|
"row.resetCooldownsBusy": "正在清除…",
|
|
@@ -829,7 +1321,11 @@ window.__ModuleLoader__.load({
|
|
|
829
1321
|
/** Stable browser-plugin name. */
|
|
830
1322
|
const name = "dsh-workbuddy-xdpool-client";
|
|
831
1323
|
/** Client services required by the Plugin configuration contribution. */
|
|
832
|
-
const inject = [
|
|
1324
|
+
const inject = [
|
|
1325
|
+
"slots",
|
|
1326
|
+
"locale",
|
|
1327
|
+
"settingsScope"
|
|
1328
|
+
];
|
|
833
1329
|
/** Register card copy and the pool card under Plugin configuration. */
|
|
834
1330
|
function apply(ctx) {
|
|
835
1331
|
try {
|
|
@@ -839,11 +1335,15 @@ window.__ModuleLoader__.load({
|
|
|
839
1335
|
en
|
|
840
1336
|
}), "dsh-workbuddy-xdpool: settings copy");
|
|
841
1337
|
const t = ctx.locale.bind(namespace);
|
|
1338
|
+
const settingsScope = ctx.settingsScope.bind({ namespace: "workbuddy-xdpool" });
|
|
842
1339
|
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
843
1340
|
name: "settings.plugin.item",
|
|
844
1341
|
key: "workbuddy-xdpool",
|
|
845
1342
|
priority: 30,
|
|
846
|
-
inject: () => ({
|
|
1343
|
+
inject: () => ({
|
|
1344
|
+
t,
|
|
1345
|
+
settingsScope
|
|
1346
|
+
})
|
|
847
1347
|
}, PoolCard));
|
|
848
1348
|
} catch (error) {
|
|
849
1349
|
console.error("[dsh-workbuddy-xdpool] client card failed to load (host provider unaffected):", error);
|