dsh-workbuddy-xdpool 0.2.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 +43 -0
- package/LICENSE +21 -0
- package/README.en.md +152 -0
- package/README.md +170 -0
- package/assets/model-picker.png +0 -0
- package/assets/settings-card.png +0 -0
- package/cordis.patch.yml +6 -0
- package/lib/bin.js +1294 -0
- package/lib/client.js +858 -0
- package/lib/index.d.ts +629 -0
- package/lib/index.js +1920 -0
- package/package.json +109 -0
- package/screenshots.json +4 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-workbuddy-xdpool",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react = require("react");
|
|
8
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/status-paths.ts
|
|
11
|
+
/**
|
|
12
|
+
* Node-free constants and types shared by the Host and browser halves of the
|
|
13
|
+
* WorkBuddy XD Pool settings card.
|
|
14
|
+
*
|
|
15
|
+
* Pool's runtime state already lives in `src/status.ts` (`buildStatus` /
|
|
16
|
+
* `WorkBuddyStatus`); this module only carves the cross-domain (Host→browser)
|
|
17
|
+
* JSON document into a shape that stays token-free and matches what the
|
|
18
|
+
* browser card renders. Route paths are plugin-owned and mounted on the Host's
|
|
19
|
+
* same-origin web server (see `src/web-status.ts`).
|
|
20
|
+
*
|
|
21
|
+
* @module dsh-workbuddy-xdpool/status-paths
|
|
22
|
+
*/
|
|
23
|
+
/** Plugin-owned read-only pool status endpoint (account rows + models + shim). */
|
|
24
|
+
const POOL_STATUS_PATH = "/plugins/dsh-workbuddy-xdpool/status";
|
|
25
|
+
/** Plugin-owned local account rescan endpoint (re-read desktop snapshots). */
|
|
26
|
+
const POOL_RESCAN_PATH = "/plugins/dsh-workbuddy-xdpool/accounts/rescan";
|
|
27
|
+
/** Plugin-owned cooldown reset endpoint (clear all 429 cooldowns). */
|
|
28
|
+
const POOL_RESET_COOLDOWN_PATH = "/plugins/dsh-workbuddy-xdpool/cooldowns/reset";
|
|
29
|
+
/** Plugin-owned daily check-in action endpoint (claim today's reward). */
|
|
30
|
+
const POOL_CHECKIN_PATH = "/plugins/dsh-workbuddy-xdpool/checkin";
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/client/icon.ts
|
|
33
|
+
/**
|
|
34
|
+
* Plugin card icon (data URI) for the WorkBuddy XD Pool card.
|
|
35
|
+
*
|
|
36
|
+
* A neutral, dependency-free 24px “pool / droplet stack” glyph kept as an SVG
|
|
37
|
+
* data URI so the browser half never needs an external asset. Three stacked
|
|
38
|
+
* droplet outlines + an encompassing orbit mark read as “rotating accounts";
|
|
39
|
+
* the line and fill colors stay inside the host’s accent family so the icon
|
|
40
|
+
* sits naturally on the dark Plugin configuration surface.
|
|
41
|
+
*
|
|
42
|
+
* @module dsh-workbuddy-xdpool/client/icon
|
|
43
|
+
*/
|
|
44
|
+
const POOL_PLUGIN_ICON = "data:image/svg+xml;utf8," + encodeURIComponent([
|
|
45
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\">",
|
|
46
|
+
"<g fill=\"none\" stroke=\"#5686fe\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">",
|
|
47
|
+
"<path d=\"M7 5.5C7 3.6 8.4 2.5 8.4 2.5S9.8 3.6 9.8 5.5A1.4 1.4 0 0 1 7 5.5Z\" fill=\"#5686fe\" fill-opacity=\".28\"/>",
|
|
48
|
+
"<path d=\"M15 10.5C15 8.6 16.4 7.5 16.4 7.5S17.8 8.6 17.8 10.5a1.4 1.4 0 0 1-2.8 0Z\" fill=\"#5686fe\" fill-opacity=\".28\"/>",
|
|
49
|
+
"<ellipse cx=\"12\" cy=\"14.5\" rx=\"5.6\" ry=\"4.4\" stroke-dasharray=\"2 2\" stroke-opacity=\".55\"/>",
|
|
50
|
+
"</g>",
|
|
51
|
+
"</svg>"
|
|
52
|
+
].join(""));
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/client/styles.ts
|
|
55
|
+
/**
|
|
56
|
+
* Client styles for the WorkBuddy XD Pool card.
|
|
57
|
+
*
|
|
58
|
+
* The card uses the same dark-theme token vocabulary as the built-in plugin
|
|
59
|
+
* cards (`--dsw-alias-*`), so the pooled account and model directory sit
|
|
60
|
+
* naturally next to the other configuration rows instead of looking like a
|
|
61
|
+
* bright Google-Material block on top of DSH's dark surface.
|
|
62
|
+
*
|
|
63
|
+
* The collapsible shell mirrors dingminhua/dsh-connect-trae (which itself
|
|
64
|
+
* borrows from the LaoDing plugin family) so the row header behaves exactly
|
|
65
|
+
* like the built-in cards next to it; the inner workbuddy-specific classes
|
|
66
|
+
* are renamed to `dsm-workbuddy-xdpool-*` to stay namespaced.
|
|
67
|
+
*
|
|
68
|
+
* @module dsh-workbuddy-xdpool/client/styles
|
|
69
|
+
*/
|
|
70
|
+
const POOL_CARD_CSS = `
|
|
71
|
+
/* Shell: same collapse affordance as every other plugin config row. */
|
|
72
|
+
.dsm-plugin-card{border:1px solid var(--dsw-alias-border-l2,#36373b);background:var(--dsw-alias-bg-module-platform,#202126);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}
|
|
73
|
+
.dsm-plugin-card:hover{border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
74
|
+
.dsm-plugin-card-open{background:var(--dsw-alias-bg-layer-2,#25262b);border-color:var(--dsw-alias-label-dimmed,#777)}
|
|
75
|
+
.dsm-plugin-card-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:transparent;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}
|
|
76
|
+
.dsm-plugin-card-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:-2px}
|
|
77
|
+
.dsm-plugin-card-head{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}
|
|
78
|
+
.dsm-plugin-card-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;font-weight:600;line-height:1.4}
|
|
79
|
+
.dsm-plugin-card-description{color:var(--dsw-alias-label-tertiary,#999);font-size:13px;line-height:1.5}
|
|
80
|
+
.dsm-plugin-card-chevron{color:var(--dsw-alias-label-tertiary,#999);flex:none;display:inline-flex;transition:transform .16s}
|
|
81
|
+
.dsm-plugin-card-chevron-open{transform:rotate(180deg)}
|
|
82
|
+
.dsm-plugin-card-body{border-top:1px solid var(--dsw-alias-border-l2,#36373b);margin:0 16px;padding:0 0 8px}
|
|
83
|
+
.dsm-plugin-card-icon{width:32px;height:32px;flex:none;border-radius:7px}
|
|
84
|
+
|
|
85
|
+
/* Reusable button primitives shared with the rest of the card body. */
|
|
86
|
+
.dsm-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}
|
|
87
|
+
.dsm-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
88
|
+
.dsm-btn:disabled{opacity:.4;cursor:default}
|
|
89
|
+
.dsm-btn-outline{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:transparent;font-weight:500}
|
|
90
|
+
.dsm-btn-outline:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed);background:rgba(255,255,255,.04)}
|
|
91
|
+
.dsm-btn-primary{background:var(--dsw-alias-label-primary,#e6e6e6);color:var(--dsw-alias-bg-layer-3,#202126)}
|
|
92
|
+
.dsm-btn-primary:hover:not(:disabled){opacity:.9}
|
|
93
|
+
|
|
94
|
+
/* Body layout: status row + accounts list + models list. */
|
|
95
|
+
.dsm-workbuddy-xdpool-usage{display:flex;flex-direction:column;gap:14px;margin:0;padding:14px 0 4px}
|
|
96
|
+
.dsm-workbuddy-xdpool-usage-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
|
97
|
+
.dsm-workbuddy-xdpool-usage-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
98
|
+
.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
|
+
.dsm-workbuddy-xdpool-usage-dot{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
|
|
100
|
+
.dsm-workbuddy-xdpool-usage-hint{padding-left:19px;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px}
|
|
101
|
+
.dsm-workbuddy-xdpool-usage-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
102
|
+
|
|
103
|
+
/* Account list (each account = a labeled subpanel, same as dingminhua). */
|
|
104
|
+
.dsm-workbuddy-xdpool-accounts{display:flex;flex-direction:column;gap:14px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
|
|
105
|
+
.dsm-workbuddy-xdpool-accounts-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
106
|
+
.dsm-workbuddy-xdpool-accounts-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
|
|
107
|
+
.dsm-workbuddy-xdpool-accounts-summary{margin:2px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
108
|
+
.dsm-workbuddy-xdpool-account{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:14px;background:var(--dsw-alias-bg-layer-2,#24262c)}
|
|
109
|
+
.dsm-workbuddy-xdpool-account-copy{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
110
|
+
.dsm-workbuddy-xdpool-account-label{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
111
|
+
.dsm-workbuddy-xdpool-account-tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
112
|
+
.dsm-workbuddy-xdpool-account-tag{padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:var(--dsw-alias-state-success-subtle,rgba(34,160,107,.12));color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
113
|
+
.dsm-workbuddy-xdpool-account-tag-cooling{background:var(--dsw-alias-state-warning-subtle,rgba(217,119,6,.15));color:var(--dsw-alias-state-warning-primary,#d97706)}
|
|
114
|
+
.dsm-workbuddy-xdpool-account-tag-error{background:var(--dsw-alias-state-error-subtle,rgba(239,68,68,.12));color:var(--dsw-alias-state-error-primary,#ef4444)}
|
|
115
|
+
.dsm-workbuddy-xdpool-account-meta{color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:12px;line-height:18px;display:flex;flex-wrap:wrap;gap:10px}
|
|
116
|
+
.dsm-workbuddy-xdpool-account-modelcool{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
|
|
117
|
+
.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
|
+
.dsm-workbuddy-xdpool-account-error{margin:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:13px;line-height:20px}
|
|
119
|
+
|
|
120
|
+
/* Credit packages under each account: package list with remain/size. */
|
|
121
|
+
.dsm-workbuddy-xdpool-credits-panels{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(150px,.8fr);gap:10px;margin-top:10px}
|
|
122
|
+
.dsm-workbuddy-xdpool-credit-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)}
|
|
123
|
+
.dsm-workbuddy-xdpool-credit-panel-title{color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
124
|
+
.dsm-workbuddy-xdpool-credit-panel-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:15px;line-height:21px;font-variant-numeric:tabular-nums}
|
|
125
|
+
.dsm-workbuddy-xdpool-credit-packages{display:flex;flex-direction:column;gap:5px;margin:0;padding:0;list-style:none}
|
|
126
|
+
.dsm-workbuddy-xdpool-credit-packages li{display:flex;align-items:baseline;justify-content:space-between;gap:10px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:12px;line-height:18px}
|
|
127
|
+
.dsm-workbuddy-xdpool-credit-packages li span:first-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
128
|
+
.dsm-workbuddy-xdpool-credit-packages li span:last-child{flex:none;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-variant-numeric:tabular-nums}
|
|
129
|
+
.dsm-workbuddy-xdpool-credit-panel-total{position:relative;align-items:center;text-align:center;overflow:hidden}
|
|
130
|
+
.dsm-workbuddy-xdpool-credit-panel-total::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;opacity:.9;background:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
131
|
+
.dsm-workbuddy-xdpool-credit-total-body{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;width:100%}
|
|
132
|
+
.dsm-workbuddy-xdpool-credit-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
|
+
|
|
134
|
+
/* Model directory list. */
|
|
135
|
+
.dsm-workbuddy-xdpool-models{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:14px}
|
|
136
|
+
.dsm-workbuddy-xdpool-models-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
|
|
137
|
+
.dsm-workbuddy-xdpool-models-title{margin:0;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:14px;font-weight:600;line-height:20px}
|
|
138
|
+
.dsm-workbuddy-xdpool-models-summary{margin:2px 0 0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
|
|
139
|
+
.dsm-workbuddy-xdpool-model-list{display:flex;flex-direction:column;border:1px solid var(--dsw-alias-border-l2,#36373b);border-radius:10px;overflow:hidden}
|
|
140
|
+
.dsm-workbuddy-xdpool-model{display:grid;grid-template-columns:minmax(0,1fr);gap:7px;padding:10px 12px;background:var(--dsw-alias-bg-layer-2,#232529);transition:opacity .16s}
|
|
141
|
+
.dsm-workbuddy-xdpool-model+.dsm-workbuddy-xdpool-model{border-top:1px solid var(--dsw-alias-border-l2,#36373b)}
|
|
142
|
+
.dsm-workbuddy-xdpool-model-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-width:0}
|
|
143
|
+
.dsm-workbuddy-xdpool-model-copy{display:flex;align-items:baseline;gap:8px;min-width:0;flex-wrap:wrap}
|
|
144
|
+
.dsm-workbuddy-xdpool-model-name{display:inline-flex;align-items:baseline;gap:7px;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:500;line-height:19px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
145
|
+
.dsm-workbuddy-xdpool-model-name-rate{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:400;line-height:16px;flex:none}
|
|
146
|
+
.dsm-workbuddy-xdpool-model-id{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
147
|
+
.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
|
+
.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
|
+
.dsm-workbuddy-xdpool-model-cap{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px;font-variant-numeric:tabular-nums}
|
|
150
|
+
|
|
151
|
+
/* Daily check-in block: streak chips on the left, one claim button per account. */
|
|
152
|
+
.dsm-workbuddy-xdpool-checkin{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:10px;padding:12px 14px;border:1px solid var(--dsw-alias-border-l2,#3a3d45);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#24262c);flex-wrap:wrap}
|
|
153
|
+
.dsm-workbuddy-xdpool-checkin-copy{display:flex;flex-direction:column;gap:6px;min-width:0}
|
|
154
|
+
.dsm-workbuddy-xdpool-checkin-title{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:600;line-height:19px}
|
|
155
|
+
.dsm-workbuddy-xdpool-checkin-meta{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
|
|
156
|
+
.dsm-workbuddy-xdpool-checkin-chip{padding:1px 8px;border-radius:999px;font-size:11px;line-height:18px;background:rgba(174,179,187,.11);color:var(--dsw-alias-label-secondary,#c6c9d0);font-variant-numeric:tabular-nums}
|
|
157
|
+
.dsm-workbuddy-xdpool-checkin-chip-bonus{background:var(--dsw-alias-state-success-subtle,rgba(51,160,107,.14));color:var(--dsw-alias-state-success-primary,#22a06b)}
|
|
158
|
+
.dsm-workbuddy-xdpool-checkin-btn{flex:none;padding:6px 16px;border-radius:9px;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
|
+
.dsm-workbuddy-xdpool-checkin-btn:hover:not(:disabled){opacity:.88}
|
|
160
|
+
.dsm-workbuddy-xdpool-checkin-btn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5686fe);outline-offset:1px}
|
|
161
|
+
.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}
|
|
162
|
+
|
|
163
|
+
/* Inline notes + error messages. */
|
|
164
|
+
.dsm-workbuddy-xdpool-note{margin:0;color:var(--dsw-alias-label-tertiary,#9aa0a8);font-size:13px;line-height:20px}
|
|
165
|
+
.dsm-workbuddy-xdpool-error{margin:0;color:var(--dsw-alias-state-error-primary,#ef4444);font-size:13px;line-height:20px}
|
|
166
|
+
|
|
167
|
+
/* Responsive: collapse the two-column credit panels on narrow screens. */
|
|
168
|
+
@media (max-width:760px){
|
|
169
|
+
.dsm-workbuddy-xdpool-credits-panels{grid-template-columns:1fr}
|
|
170
|
+
.dsm-workbuddy-xdpool-credit-panel-total{align-items:flex-start;text-align:left}
|
|
171
|
+
.dsm-workbuddy-xdpool-credit-total-body{align-items:flex-start}
|
|
172
|
+
.dsm-workbuddy-xdpool-checkin{align-items:stretch;flex-direction:column}
|
|
173
|
+
.dsm-workbuddy-xdpool-checkin-btn{width:100%}
|
|
174
|
+
}
|
|
175
|
+
`.trim();
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/client/PoolCard.tsx
|
|
178
|
+
/**
|
|
179
|
+
* WorkBuddy XD Pool card contributed to DSH Plugin configuration.
|
|
180
|
+
*
|
|
181
|
+
* The card body mirrors the LaoDing plugin family used by dingminhua's
|
|
182
|
+
* `dsh-connect-workbuddy`: a small status row (dot + count + Rescan / Clear
|
|
183
|
+
* cooldowns buttons), then a per-account panel showing label / status tag /
|
|
184
|
+
* token expiry / cooldown info / credit packages, then the model directory
|
|
185
|
+
* with per-model free/limited/night/image badges and context size.
|
|
186
|
+
*
|
|
187
|
+
* The outer shell reuses the host's `dsm-plugin-card*` classes so the
|
|
188
|
+
* collapse affordance is identical to every other plugin configuration row.
|
|
189
|
+
*
|
|
190
|
+
* @module dsh-workbuddy-xdpool/client/PoolCard
|
|
191
|
+
*/
|
|
192
|
+
const POLL_INTERVAL_MS = 3e4;
|
|
193
|
+
/** Inject or refresh the shared card CSS for the current client bundle. */
|
|
194
|
+
if (typeof document !== "undefined") {
|
|
195
|
+
const cssId = "dsh-workbuddy-xdpool/client.css";
|
|
196
|
+
const existing = document.querySelector(`style[data-plugin-css="${cssId}"]`);
|
|
197
|
+
if (existing !== null) existing.textContent = POOL_CARD_CSS;
|
|
198
|
+
else {
|
|
199
|
+
const styleTag = document.createElement("style");
|
|
200
|
+
styleTag.dataset.plugin = "dsh-workbuddy-xdpool";
|
|
201
|
+
styleTag.dataset.pluginCss = cssId;
|
|
202
|
+
styleTag.textContent = POOL_CARD_CSS;
|
|
203
|
+
document.head.appendChild(styleTag);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function formatNumber(value) {
|
|
207
|
+
if (value === void 0) return "–";
|
|
208
|
+
return new Intl.NumberFormat(void 0, { maximumFractionDigits: 0 }).format(value);
|
|
209
|
+
}
|
|
210
|
+
function formatTime(value) {
|
|
211
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
212
|
+
month: "2-digit",
|
|
213
|
+
day: "2-digit",
|
|
214
|
+
hour: "2-digit",
|
|
215
|
+
minute: "2-digit"
|
|
216
|
+
}).format(new Date(value));
|
|
217
|
+
}
|
|
218
|
+
function formatDateTime(value) {
|
|
219
|
+
if (value === void 0) return "";
|
|
220
|
+
const ms = Date.parse(value);
|
|
221
|
+
if (Number.isNaN(ms)) return value;
|
|
222
|
+
return new Intl.DateTimeFormat(void 0, {
|
|
223
|
+
month: "2-digit",
|
|
224
|
+
day: "2-digit",
|
|
225
|
+
hour: "2-digit",
|
|
226
|
+
minute: "2-digit"
|
|
227
|
+
}).format(new Date(ms));
|
|
228
|
+
}
|
|
229
|
+
function dotColor(status) {
|
|
230
|
+
return status === "ok" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #ef4444)" : "var(--dsw-alias-label-dimmed, #9aa0a6)";
|
|
231
|
+
}
|
|
232
|
+
function formatCapacity(value) {
|
|
233
|
+
if (value === void 0) return "";
|
|
234
|
+
if (value >= 1e6 && value % 1e6 === 0) return `${value / 1e6}M`;
|
|
235
|
+
if (value >= 1e3 && value % 1e3 === 0) return `${value / 1e3}K`;
|
|
236
|
+
return String(value);
|
|
237
|
+
}
|
|
238
|
+
/** Pick the right promotion chip for a model. */
|
|
239
|
+
function tagFor(model) {
|
|
240
|
+
const tags = model.tags ?? [];
|
|
241
|
+
if (tags.includes("free")) return "free";
|
|
242
|
+
if (tags.includes("limited-free")) return "limited";
|
|
243
|
+
if (tags.includes("night-discount")) return "night";
|
|
244
|
+
}
|
|
245
|
+
/** Render pool health, per-account credits/cooldown, and the model directory. */
|
|
246
|
+
function PoolCard({ t }) {
|
|
247
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
248
|
+
const [status, setStatus] = (0, react.useState)(void 0);
|
|
249
|
+
const [error, setError] = (0, react.useState)(void 0);
|
|
250
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
251
|
+
const [cooldownBusy, setCooldownBusy] = (0, react.useState)(false);
|
|
252
|
+
const [flash, setFlash] = (0, react.useState)(void 0);
|
|
253
|
+
/** Account id whose daily claim is currently in flight. */
|
|
254
|
+
const [checkinBusyId, setCheckinBusyId] = (0, react.useState)(void 0);
|
|
255
|
+
const mounted = (0, react.useRef)(true);
|
|
256
|
+
(0, react.useEffect)(() => {
|
|
257
|
+
mounted.current = true;
|
|
258
|
+
return () => {
|
|
259
|
+
mounted.current = false;
|
|
260
|
+
};
|
|
261
|
+
}, []);
|
|
262
|
+
const refresh = (0, react.useCallback)(async (signal) => {
|
|
263
|
+
try {
|
|
264
|
+
const response = await fetch(`${POOL_STATUS_PATH}`, {
|
|
265
|
+
headers: { accept: "application/json" },
|
|
266
|
+
credentials: "same-origin",
|
|
267
|
+
...signal === void 0 ? {} : { signal }
|
|
268
|
+
});
|
|
269
|
+
const value = await response.json().catch(() => void 0);
|
|
270
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
271
|
+
if (mounted.current && signal?.aborted !== true) {
|
|
272
|
+
setStatus(value);
|
|
273
|
+
setError(void 0);
|
|
274
|
+
}
|
|
275
|
+
} catch (cause) {
|
|
276
|
+
if (mounted.current && signal?.aborted !== true) setError(cause instanceof Error ? cause.message : String(cause));
|
|
277
|
+
}
|
|
278
|
+
}, []);
|
|
279
|
+
(0, react.useEffect)(() => {
|
|
280
|
+
if (!open) return;
|
|
281
|
+
const controller = new AbortController();
|
|
282
|
+
refresh(controller.signal);
|
|
283
|
+
const timer = window.setInterval(() => {
|
|
284
|
+
refresh(controller.signal);
|
|
285
|
+
}, POLL_INTERVAL_MS);
|
|
286
|
+
return () => {
|
|
287
|
+
window.clearInterval(timer);
|
|
288
|
+
controller.abort();
|
|
289
|
+
};
|
|
290
|
+
}, [open, refresh]);
|
|
291
|
+
const rescan = async () => {
|
|
292
|
+
setBusy(true);
|
|
293
|
+
setFlash(void 0);
|
|
294
|
+
try {
|
|
295
|
+
const response = await fetch(POOL_RESCAN_PATH, {
|
|
296
|
+
method: "POST",
|
|
297
|
+
headers: { accept: "application/json" },
|
|
298
|
+
credentials: "same-origin"
|
|
299
|
+
});
|
|
300
|
+
const body = await response.json();
|
|
301
|
+
if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`);
|
|
302
|
+
await refresh();
|
|
303
|
+
if (mounted.current) setFlash(t?.("row.accountsRescanned", { count: body.accounts ?? 0 }) ?? "");
|
|
304
|
+
} catch (cause) {
|
|
305
|
+
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
306
|
+
} finally {
|
|
307
|
+
if (mounted.current) setBusy(false);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
const resetCooldowns = async () => {
|
|
311
|
+
setCooldownBusy(true);
|
|
312
|
+
setFlash(void 0);
|
|
313
|
+
try {
|
|
314
|
+
const response = await fetch(POOL_RESET_COOLDOWN_PATH, {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { accept: "application/json" },
|
|
317
|
+
credentials: "same-origin"
|
|
318
|
+
});
|
|
319
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
320
|
+
await refresh();
|
|
321
|
+
if (mounted.current) setFlash(t?.("row.resetCooldownsDone") ?? "");
|
|
322
|
+
} catch (cause) {
|
|
323
|
+
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
324
|
+
} finally {
|
|
325
|
+
if (mounted.current) setCooldownBusy(false);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Claim one account's daily check-in. The account id travels in the body so
|
|
330
|
+
* the Host can never guess: a click on account B's button can only ever
|
|
331
|
+
* collect account B's reward. The status is re-read afterwards so the card
|
|
332
|
+
* reflects the new streak / total without waiting for the next poll.
|
|
333
|
+
*/
|
|
334
|
+
const claimCheckin = async (accountId) => {
|
|
335
|
+
setCheckinBusyId(accountId);
|
|
336
|
+
setFlash(void 0);
|
|
337
|
+
try {
|
|
338
|
+
const response = await fetch(POOL_CHECKIN_PATH, {
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: {
|
|
341
|
+
accept: "application/json",
|
|
342
|
+
"content-type": "application/json"
|
|
343
|
+
},
|
|
344
|
+
credentials: "same-origin",
|
|
345
|
+
body: JSON.stringify({ accountId })
|
|
346
|
+
});
|
|
347
|
+
const body = await response.json().catch(() => void 0);
|
|
348
|
+
if (!response.ok) throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
349
|
+
await refresh();
|
|
350
|
+
const credit = body?.claim?.credit ?? 0;
|
|
351
|
+
if (mounted.current) setFlash(t?.("row.checkinClaimedReward", { credit: formatNumber(credit) }) ?? `Claimed +${formatNumber(credit)} credits`);
|
|
352
|
+
} catch (cause) {
|
|
353
|
+
if (mounted.current) setError(cause instanceof Error ? cause.message : String(cause));
|
|
354
|
+
} finally {
|
|
355
|
+
if (mounted.current) setCheckinBusyId(void 0);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const title = t?.("row.title") ?? "WorkBuddy XD Pool";
|
|
359
|
+
const description = t?.("row.desc") ?? "";
|
|
360
|
+
const accountCount = status?.accounts.length ?? 0;
|
|
361
|
+
const cooling = status?.cooling ?? 0;
|
|
362
|
+
const state = error !== void 0 ? "error" : status === void 0 && error === void 0 ? "idle" : accountCount > 0 && cooling < accountCount ? "ok" : "idle";
|
|
363
|
+
const stateLabel = error !== void 0 ? t?.("row.requestFailed") ?? "Request failed" : accountCount === 0 ? t?.("row.poolEmpty") ?? "No account yet" : state === "ok" ? t?.("row.ok") ?? "Healthy" : t?.("row.allCooling") ?? "All cooling";
|
|
364
|
+
const shimRunning = status?.shim.running === true;
|
|
365
|
+
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
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
367
|
+
className: `dsm-plugin-card${open ? " dsm-plugin-card-open" : ""}`,
|
|
368
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
369
|
+
type: "button",
|
|
370
|
+
className: "dsm-plugin-card-header",
|
|
371
|
+
"aria-expanded": open,
|
|
372
|
+
"aria-label": `${t?.(open ? "row.collapse" : "row.expand") ?? ""}: ${title}`,
|
|
373
|
+
onClick: () => {
|
|
374
|
+
setOpen(!open);
|
|
375
|
+
},
|
|
376
|
+
children: [
|
|
377
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
378
|
+
className: "dsm-plugin-card-icon",
|
|
379
|
+
src: POOL_PLUGIN_ICON,
|
|
380
|
+
alt: ""
|
|
381
|
+
}),
|
|
382
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
383
|
+
className: "dsm-plugin-card-head",
|
|
384
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
385
|
+
className: "dsm-plugin-card-title",
|
|
386
|
+
children: title
|
|
387
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
388
|
+
className: "dsm-plugin-card-description",
|
|
389
|
+
children: description
|
|
390
|
+
})]
|
|
391
|
+
}),
|
|
392
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
393
|
+
"aria-hidden": "true",
|
|
394
|
+
className: `dsm-plugin-card-chevron${open ? " dsm-plugin-card-chevron-open" : ""}`,
|
|
395
|
+
children: (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 })
|
|
396
|
+
})
|
|
397
|
+
]
|
|
398
|
+
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
399
|
+
className: "dsm-plugin-card-body",
|
|
400
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
401
|
+
className: "dsm-workbuddy-xdpool-usage",
|
|
402
|
+
children: [
|
|
403
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
404
|
+
className: "dsm-workbuddy-xdpool-usage-head",
|
|
405
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
406
|
+
className: "dsm-workbuddy-xdpool-usage-copy",
|
|
407
|
+
role: "status",
|
|
408
|
+
children: [
|
|
409
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
410
|
+
className: "dsm-workbuddy-xdpool-usage-status",
|
|
411
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
412
|
+
"aria-hidden": "true",
|
|
413
|
+
className: "dsm-workbuddy-xdpool-usage-dot",
|
|
414
|
+
style: { background: dotColor(state) }
|
|
415
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: stateLabel })]
|
|
416
|
+
}),
|
|
417
|
+
accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
418
|
+
className: "dsm-workbuddy-xdpool-usage-hint",
|
|
419
|
+
children: t?.("row.accountsSummary", {
|
|
420
|
+
count: accountCount,
|
|
421
|
+
cooling
|
|
422
|
+
}) ?? `${accountCount} account(s) · ${cooling} cooling`
|
|
423
|
+
}) : null,
|
|
424
|
+
shimHint === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
425
|
+
className: "dsm-workbuddy-xdpool-usage-hint",
|
|
426
|
+
children: shimHint
|
|
427
|
+
})
|
|
428
|
+
]
|
|
429
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
430
|
+
className: "dsm-workbuddy-xdpool-usage-actions",
|
|
431
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
432
|
+
type: "button",
|
|
433
|
+
className: "dsm-btn dsm-btn-outline",
|
|
434
|
+
disabled: busy,
|
|
435
|
+
onClick: () => {
|
|
436
|
+
rescan();
|
|
437
|
+
},
|
|
438
|
+
children: busy ? t?.("row.accountsScanning") ?? "Detecting…" : t?.("row.accountsRescan") ?? "Detect accounts again"
|
|
439
|
+
}), cooling > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
440
|
+
type: "button",
|
|
441
|
+
className: "dsm-btn dsm-btn-outline",
|
|
442
|
+
disabled: cooldownBusy,
|
|
443
|
+
onClick: () => {
|
|
444
|
+
resetCooldowns();
|
|
445
|
+
},
|
|
446
|
+
children: cooldownBusy ? t?.("row.resetCooldownsBusy") ?? "Clearing…" : t?.("row.resetCooldowns") ?? "Clear all cooldowns"
|
|
447
|
+
}) : null]
|
|
448
|
+
})]
|
|
449
|
+
}),
|
|
450
|
+
flash === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
451
|
+
className: "dsm-workbuddy-xdpool-note",
|
|
452
|
+
children: flash
|
|
453
|
+
}),
|
|
454
|
+
error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
455
|
+
className: "dsm-workbuddy-xdpool-error",
|
|
456
|
+
children: t?.("row.error", { message: error }) ?? `Pool status unavailable: ${error}`
|
|
457
|
+
}),
|
|
458
|
+
accountCount === 0 && error === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
459
|
+
className: "dsm-workbuddy-xdpool-note",
|
|
460
|
+
children: t?.("row.poolEmptyHint") ?? ""
|
|
461
|
+
}) : null,
|
|
462
|
+
accountCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
463
|
+
className: "dsm-workbuddy-xdpool-accounts",
|
|
464
|
+
"aria-label": t?.("row.accountsTitle") ?? "Accounts",
|
|
465
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
466
|
+
className: "dsm-workbuddy-xdpool-accounts-head",
|
|
467
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
468
|
+
className: "dsm-workbuddy-xdpool-accounts-title",
|
|
469
|
+
children: t?.("row.accountsTitle") ?? "Accounts in the pool"
|
|
470
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
471
|
+
className: "dsm-workbuddy-xdpool-accounts-summary",
|
|
472
|
+
children: t?.("row.accountsSummary", {
|
|
473
|
+
count: accountCount,
|
|
474
|
+
cooling
|
|
475
|
+
}) ?? `${accountCount} account(s) · ${cooling} cooling`
|
|
476
|
+
})]
|
|
477
|
+
}), status?.accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountBlock, {
|
|
478
|
+
account,
|
|
479
|
+
...status.activeAccountId === void 0 ? {} : { activeAccountId: status.activeAccountId },
|
|
480
|
+
...checkinBusyId === void 0 ? {} : { checkinBusyId },
|
|
481
|
+
onClaimCheckin: (accountId) => {
|
|
482
|
+
claimCheckin(accountId);
|
|
483
|
+
},
|
|
484
|
+
t
|
|
485
|
+
}, account.id))]
|
|
486
|
+
}) : null,
|
|
487
|
+
(status?.models.length ?? 0) > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
488
|
+
className: "dsm-workbuddy-xdpool-models",
|
|
489
|
+
"aria-label": t?.("row.modelsTitle") ?? "Models",
|
|
490
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
491
|
+
className: "dsm-workbuddy-xdpool-models-head",
|
|
492
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
493
|
+
className: "dsm-workbuddy-xdpool-models-title",
|
|
494
|
+
children: t?.("row.modelsTitle") ?? "Models"
|
|
495
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
496
|
+
className: "dsm-workbuddy-xdpool-models-summary",
|
|
497
|
+
children: t?.("row.modelsSummary", { count: status?.models.length ?? 0 }) ?? `${status?.models.length ?? 0} model(s) in the live catalog`
|
|
498
|
+
})]
|
|
499
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
500
|
+
className: "dsm-workbuddy-xdpool-model-list",
|
|
501
|
+
children: status?.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelRow, {
|
|
502
|
+
model,
|
|
503
|
+
t
|
|
504
|
+
}, model.id))
|
|
505
|
+
})]
|
|
506
|
+
}) : null
|
|
507
|
+
]
|
|
508
|
+
})
|
|
509
|
+
}) : null]
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
/** One account block: label + status tag + meta + optional credit panels. */
|
|
513
|
+
function AccountBlock({ account, activeAccountId, t, checkinBusyId, onClaimCheckin }) {
|
|
514
|
+
const isActive = account.id === activeAccountId;
|
|
515
|
+
const isCooling = account.cooling === true;
|
|
516
|
+
const cooldownUntil = account.cooldownUntil !== void 0 ? Date.parse(account.cooldownUntil) : void 0;
|
|
517
|
+
const modelCooldowns = account.modelCooldowns ?? [];
|
|
518
|
+
const tag = isActive ? {
|
|
519
|
+
text: t?.("row.accountNext") ?? "Next up",
|
|
520
|
+
cls: "dsm-workbuddy-xdpool-account-tag"
|
|
521
|
+
} : isCooling ? {
|
|
522
|
+
text: t?.("row.cooling") ?? "Cooling",
|
|
523
|
+
cls: "dsm-workbuddy-xdpool-account-tag dsm-workbuddy-xdpool-account-tag-cooling"
|
|
524
|
+
} : null;
|
|
525
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
526
|
+
className: "dsm-workbuddy-xdpool-account",
|
|
527
|
+
children: [
|
|
528
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
529
|
+
className: "dsm-workbuddy-xdpool-account-copy",
|
|
530
|
+
children: [
|
|
531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
532
|
+
className: "dsm-workbuddy-xdpool-account-label",
|
|
533
|
+
children: account.label
|
|
534
|
+
}),
|
|
535
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
536
|
+
className: "dsm-workbuddy-xdpool-account-tags",
|
|
537
|
+
children: tag === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
538
|
+
className: tag.cls,
|
|
539
|
+
children: tag.text
|
|
540
|
+
})
|
|
541
|
+
}),
|
|
542
|
+
account.domain !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
543
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
544
|
+
children: account.domain
|
|
545
|
+
}),
|
|
546
|
+
account.expiresAt !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
547
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
548
|
+
children: t?.("row.tokenExpiry", { time: formatDateTime(account.expiresAt) }) ?? `token ${formatDateTime(account.expiresAt)}`
|
|
549
|
+
}) : null,
|
|
550
|
+
isCooling && cooldownUntil !== void 0 && !Number.isNaN(cooldownUntil) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
551
|
+
className: "dsm-workbuddy-xdpool-account-meta",
|
|
552
|
+
children: [
|
|
553
|
+
t?.("row.cooldownUntil", { time: formatTime(cooldownUntil) }) ?? `until ${formatTime(cooldownUntil)}`,
|
|
554
|
+
" · ",
|
|
555
|
+
t?.("row.cooldownHits", { hits: account.rateLimitHits ?? 0 }) ?? `${account.rateLimitHits ?? 0} hit(s)`
|
|
556
|
+
]
|
|
557
|
+
}) : null,
|
|
558
|
+
modelCooldowns.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
559
|
+
className: "dsm-workbuddy-xdpool-account-modelcool",
|
|
560
|
+
children: modelCooldowns.map((mc) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
561
|
+
className: "dsm-workbuddy-xdpool-account-modelcool-chip",
|
|
562
|
+
children: t?.("row.modelCooling", {
|
|
563
|
+
model: mc.modelId,
|
|
564
|
+
time: formatDateTime(mc.until)
|
|
565
|
+
}) ?? `${mc.modelId} cooling to ${formatDateTime(mc.until)}`
|
|
566
|
+
}, mc.modelId))
|
|
567
|
+
}) : null
|
|
568
|
+
]
|
|
569
|
+
}),
|
|
570
|
+
account.credits === void 0 && account.creditsError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountCredits, {
|
|
571
|
+
account,
|
|
572
|
+
t
|
|
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
|
+
]
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Daily check-in block: streak summary plus one claim button for this account.
|
|
585
|
+
* Every account in the pool gets its own button, so a multi-account user can
|
|
586
|
+
* collect each reward without switching the pool's preferred account first.
|
|
587
|
+
*/
|
|
588
|
+
function AccountCheckin({ account, t, busy, onClaim }) {
|
|
589
|
+
if (account.checkinError !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
590
|
+
className: "dsm-workbuddy-xdpool-account-error",
|
|
591
|
+
children: t?.("row.checkinError", { message: account.checkinError }) ?? account.checkinError
|
|
592
|
+
});
|
|
593
|
+
const checkin = account.checkin;
|
|
594
|
+
if (checkin === void 0) return null;
|
|
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
|
+
});
|
|
641
|
+
const credits = account.credits;
|
|
642
|
+
if (credits === void 0) return null;
|
|
643
|
+
const packages = credits.packages.filter((p) => (p.size ?? 0) > 0).slice(0, 5);
|
|
644
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
645
|
+
className: "dsm-workbuddy-xdpool-credits-panels",
|
|
646
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
647
|
+
className: "dsm-workbuddy-xdpool-credit-panel",
|
|
648
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
649
|
+
className: "dsm-workbuddy-xdpool-credit-panel-title",
|
|
650
|
+
children: t?.("row.creditsPackages") ?? "Credit packages"
|
|
651
|
+
}), packages.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
652
|
+
className: "dsm-workbuddy-xdpool-credit-panel-value",
|
|
653
|
+
children: "–"
|
|
654
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
655
|
+
className: "dsm-workbuddy-xdpool-credit-packages",
|
|
656
|
+
children: packages.map((pack, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: pack.packageName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t?.("row.creditsPackage", {
|
|
657
|
+
remain: formatNumber(pack.remain),
|
|
658
|
+
size: formatNumber(pack.size)
|
|
659
|
+
}) ?? `${formatNumber(pack.remain)} / ${formatNumber(pack.size)}` })] }, `${pack.packageName}-${String(index)}`))
|
|
660
|
+
})]
|
|
661
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
662
|
+
className: "dsm-workbuddy-xdpool-credit-panel dsm-workbuddy-xdpool-credit-panel-total",
|
|
663
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
664
|
+
className: "dsm-workbuddy-xdpool-credit-total-body",
|
|
665
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
666
|
+
className: "dsm-workbuddy-xdpool-credit-panel-title",
|
|
667
|
+
children: t?.("row.creditsTotal") ?? "Total"
|
|
668
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
669
|
+
className: "dsm-workbuddy-xdpool-credit-total-value",
|
|
670
|
+
children: formatNumber(credits.total)
|
|
671
|
+
})]
|
|
672
|
+
})
|
|
673
|
+
})]
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
/** One model row: name + rate + tag chip + image badge + context size. */
|
|
677
|
+
function ModelRow({ model, t }) {
|
|
678
|
+
const tag = tagFor(model);
|
|
679
|
+
const tagText = tag === "free" ? t?.("row.free") ?? "free" : tag === "limited" ? t?.("row.limitedFree") ?? "limited free" : tag === "night" ? t?.("row.nightDiscount") ?? "night" : null;
|
|
680
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
681
|
+
className: "dsm-workbuddy-xdpool-model",
|
|
682
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
683
|
+
className: "dsm-workbuddy-xdpool-model-head",
|
|
684
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
685
|
+
className: "dsm-workbuddy-xdpool-model-copy",
|
|
686
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
687
|
+
className: "dsm-workbuddy-xdpool-model-name",
|
|
688
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.multiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
689
|
+
className: "dsm-workbuddy-xdpool-model-name-rate",
|
|
690
|
+
children: t?.("row.rate", { rate: model.multiplier.toFixed(2) }) ?? `${model.multiplier.toFixed(2)}x`
|
|
691
|
+
})]
|
|
692
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
693
|
+
className: "dsm-workbuddy-xdpool-model-id",
|
|
694
|
+
children: model.id
|
|
695
|
+
})]
|
|
696
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
697
|
+
className: "dsm-workbuddy-xdpool-model-meta",
|
|
698
|
+
children: [
|
|
699
|
+
tagText === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
700
|
+
className: "dsm-workbuddy-xdpool-model-meta-tag",
|
|
701
|
+
children: tagText
|
|
702
|
+
}),
|
|
703
|
+
model.supportsImages === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
704
|
+
className: "dsm-workbuddy-xdpool-model-meta-tag",
|
|
705
|
+
children: t?.("row.imageCapable") ?? "image"
|
|
706
|
+
}) : null,
|
|
707
|
+
model.contextWindow === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
708
|
+
className: "dsm-workbuddy-xdpool-model-cap",
|
|
709
|
+
children: formatCapacity(model.contextWindow)
|
|
710
|
+
})
|
|
711
|
+
]
|
|
712
|
+
})]
|
|
713
|
+
})
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
//#endregion
|
|
717
|
+
//#region src/client/locales.ts
|
|
718
|
+
/**
|
|
719
|
+
* Plugin-card copy registered under the `settings.workbuddy-xdpool` locale
|
|
720
|
+
* namespace. Key lists in `en` and `zh` are kept 1:1 by typing `zh` against
|
|
721
|
+
* the key set of `en`.
|
|
722
|
+
*
|
|
723
|
+
* @module dsh-workbuddy-xdpool/client/locales
|
|
724
|
+
*/
|
|
725
|
+
const en = {
|
|
726
|
+
"row.title": "WorkBuddy XD Pool (dsh-workbuddy-xdpool)",
|
|
727
|
+
"row.desc": "Route every WorkBuddy sign-in on this machine into DSH as one auto-failing-over model pool.",
|
|
728
|
+
"row.expand": "Expand",
|
|
729
|
+
"row.collapse": "Collapse",
|
|
730
|
+
"row.requestFailed": "Request failed",
|
|
731
|
+
"row.poolEmpty": "No WorkBuddy account discovered yet.",
|
|
732
|
+
"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.",
|
|
733
|
+
"row.shimStopped": "Provider loopback is not running.",
|
|
734
|
+
"row.shimRunning": "Provider listening on loopback",
|
|
735
|
+
"row.accountsTitle": "Accounts in the pool",
|
|
736
|
+
"row.accountsSummary": "{count} account(s) · {cooling} cooling",
|
|
737
|
+
"row.ok": "Healthy — requests auto-rotate across accounts",
|
|
738
|
+
"row.allCooling": "Every account is rate-limited right now; requests pause until a cooldown lifts.",
|
|
739
|
+
"row.accountNext": "Next up",
|
|
740
|
+
"row.cooling": "Cooling (rate-limited)",
|
|
741
|
+
"row.cooldownHits": "{hits} hit(s)",
|
|
742
|
+
"row.cooldownUntil": "until {time}",
|
|
743
|
+
"row.modelCooling": "{model} cooling until {time}",
|
|
744
|
+
"row.tokenExpiry": "token {time}",
|
|
745
|
+
"row.creditsTotal": "Total",
|
|
746
|
+
"row.creditsPackages": "Credit packages",
|
|
747
|
+
"row.creditsPackage": "{remain} / {size}",
|
|
748
|
+
"row.creditsError": "credits unavailable",
|
|
749
|
+
"row.checkinTitle": "Daily check-in",
|
|
750
|
+
"row.checkinClaim": "Check in",
|
|
751
|
+
"row.checkinClaiming": "Checking in…",
|
|
752
|
+
"row.checkinClaimed": "Checked in today",
|
|
753
|
+
"row.checkinInactive": "Check-in not available for this account",
|
|
754
|
+
"row.checkinStreak": "{days}-day streak",
|
|
755
|
+
"row.checkinDaily": "+{credit} credits/day",
|
|
756
|
+
"row.checkinStreakBonus": "day {days} bonus +{credit}",
|
|
757
|
+
"row.checkinClaimedReward": "Claimed +{credit} credits",
|
|
758
|
+
"row.checkinError": "Check-in failed: {message}",
|
|
759
|
+
"row.checkinAllHint": "Collect every account’s daily reward here — no need to switch accounts first.",
|
|
760
|
+
"row.modelsTitle": "Models",
|
|
761
|
+
"row.modelsSummary": "{count} model(s) in the live catalog",
|
|
762
|
+
"row.modelsHint": "Read from the live WorkBuddy catalog. Free tiers are marked.",
|
|
763
|
+
"row.free": "free",
|
|
764
|
+
"row.limitedFree": "limited free",
|
|
765
|
+
"row.nightDiscount": "night",
|
|
766
|
+
"row.imageCapable": "image input",
|
|
767
|
+
"row.rate": "{rate}x credits",
|
|
768
|
+
"row.accountsRescan": "Detect accounts again",
|
|
769
|
+
"row.accountsScanning": "Detecting…",
|
|
770
|
+
"row.resetCooldowns": "Clear all cooldowns",
|
|
771
|
+
"row.resetCooldownsBusy": "Clearing…",
|
|
772
|
+
"row.resetCooldownsDone": "Cooldowns cleared",
|
|
773
|
+
"row.accountsRescanned": "Detected {count} account(s)",
|
|
774
|
+
"row.error": "Pool status unavailable: {message}"
|
|
775
|
+
};
|
|
776
|
+
const zh = {
|
|
777
|
+
"row.title": "WorkBuddy 池(dsh-workbuddy-xdpool)",
|
|
778
|
+
"row.desc": "把本机所有已登录的 WorkBuddy 账号并入 DSH,作为一个自动容错的模型池使用。",
|
|
779
|
+
"row.expand": "展开",
|
|
780
|
+
"row.collapse": "收起",
|
|
781
|
+
"row.requestFailed": "请求失败",
|
|
782
|
+
"row.poolEmpty": "还没有发现任何 WorkBuddy 账号。",
|
|
783
|
+
"row.poolEmptyHint": "先在 WorkBuddy 桌面 App 里登录一个或多个 WorkBuddy 账号,再点“重新检测账号”。每次登录都会被自动纳入池中。",
|
|
784
|
+
"row.shimStopped": "回环提供端未运行。",
|
|
785
|
+
"row.shimRunning": "提供端正在回环地址监听",
|
|
786
|
+
"row.accountsTitle": "池中账号",
|
|
787
|
+
"row.accountsSummary": "{count} 个账号 · {cooling} 个冷却中",
|
|
788
|
+
"row.ok": "运行健康 —— 请求会在各账号间自动轮换",
|
|
789
|
+
"row.allCooling": "当前所有账号都处于限流冷却,请求会暂停直到某个冷却结束。",
|
|
790
|
+
"row.accountNext": "下一个",
|
|
791
|
+
"row.cooling": "冷却中(被限流)",
|
|
792
|
+
"row.cooldownHits": "触发 {hits} 次",
|
|
793
|
+
"row.cooldownUntil": "至 {time}",
|
|
794
|
+
"row.modelCooling": "{model} 冷却至 {time}",
|
|
795
|
+
"row.tokenExpiry": "令牌 {time}",
|
|
796
|
+
"row.creditsTotal": "合计",
|
|
797
|
+
"row.creditsPackages": "积分包",
|
|
798
|
+
"row.creditsPackage": "{remain} / {size}",
|
|
799
|
+
"row.creditsError": "积分不可用",
|
|
800
|
+
"row.checkinTitle": "每日签到",
|
|
801
|
+
"row.checkinClaim": "签到",
|
|
802
|
+
"row.checkinClaiming": "签到中…",
|
|
803
|
+
"row.checkinClaimed": "今日已签到",
|
|
804
|
+
"row.checkinInactive": "该账号当前无签到活动",
|
|
805
|
+
"row.checkinStreak": "连签 {days} 天",
|
|
806
|
+
"row.checkinDaily": "每日 +{credit} 积分",
|
|
807
|
+
"row.checkinStreakBonus": "第 {days} 天额外 +{credit}",
|
|
808
|
+
"row.checkinClaimedReward": "已领取 +{credit} 积分",
|
|
809
|
+
"row.checkinError": "签到失败:{message}",
|
|
810
|
+
"row.checkinAllHint": "这里可以为每个账号分别领取每日签到奖励,无需先切换账号。",
|
|
811
|
+
"row.modelsTitle": "模型",
|
|
812
|
+
"row.modelsSummary": "实时目录中 {count} 个模型",
|
|
813
|
+
"row.modelsHint": "读取自 WorkBuddy 实时目录;免费档位已标注。",
|
|
814
|
+
"row.free": "免费",
|
|
815
|
+
"row.limitedFree": "限量免费",
|
|
816
|
+
"row.nightDiscount": "夜间",
|
|
817
|
+
"row.imageCapable": "图片输入",
|
|
818
|
+
"row.rate": "{rate}x 积分",
|
|
819
|
+
"row.accountsRescan": "重新检测账号",
|
|
820
|
+
"row.accountsScanning": "正在检测…",
|
|
821
|
+
"row.resetCooldowns": "清除所有冷却",
|
|
822
|
+
"row.resetCooldownsBusy": "正在清除…",
|
|
823
|
+
"row.resetCooldownsDone": "冷却已清除",
|
|
824
|
+
"row.accountsRescanned": "检测到 {count} 个账号",
|
|
825
|
+
"row.error": "池状态不可用:{message}"
|
|
826
|
+
};
|
|
827
|
+
//#endregion
|
|
828
|
+
//#region src/client/index.tsx
|
|
829
|
+
/** Stable browser-plugin name. */
|
|
830
|
+
const name = "dsh-workbuddy-xdpool-client";
|
|
831
|
+
/** Client services required by the Plugin configuration contribution. */
|
|
832
|
+
const inject = ["slots", "locale"];
|
|
833
|
+
/** Register card copy and the pool card under Plugin configuration. */
|
|
834
|
+
function apply(ctx) {
|
|
835
|
+
try {
|
|
836
|
+
const namespace = "settings.workbuddy-xdpool";
|
|
837
|
+
ctx.effect(() => ctx.locale.register(namespace, {
|
|
838
|
+
zh,
|
|
839
|
+
en
|
|
840
|
+
}), "dsh-workbuddy-xdpool: settings copy");
|
|
841
|
+
const t = ctx.locale.bind(namespace);
|
|
842
|
+
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
843
|
+
name: "settings.plugin.item",
|
|
844
|
+
key: "workbuddy-xdpool",
|
|
845
|
+
priority: 30,
|
|
846
|
+
inject: () => ({ t })
|
|
847
|
+
}, PoolCard));
|
|
848
|
+
} catch (error) {
|
|
849
|
+
console.error("[dsh-workbuddy-xdpool] client card failed to load (host provider unaffected):", error);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
853
|
+
exports.apply = apply;
|
|
854
|
+
exports.inject = inject;
|
|
855
|
+
exports.name = name;
|
|
856
|
+
return module.exports;
|
|
857
|
+
}
|
|
858
|
+
});
|