@linxin666/dsh-usage 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +112 -11
- package/lib/client.js.map +1 -1
- package/lib/index.js +222 -45
- package/lib/types/client/body-mutations.d.ts +4 -4
- package/lib/types/client/settings-entry-form.d.ts +49 -0
- package/lib/types/core/adapters.d.ts +45 -0
- package/lib/types/index.d.ts +61 -10
- package/lib/types/mount-once.d.ts +38 -7
- package/package.json +15 -15
- package/src/client/UsageSectionCard.tsx +8 -1
- package/src/client/body-mutations.ts +4 -4
- package/src/client/index.ts +22 -9
- package/src/client/settings-entry-form.ts +142 -0
- package/src/core/adapters.ts +56 -0
- package/src/host/usage-service.ts +19 -2
- package/src/index.ts +138 -43
- package/src/mount-once.ts +107 -13
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 2397df7cc658e2f6f5f447312723de5bee7cb490
|
|
6
|
+
README.zh.md: 34535e95511e0d66198e801d37ef5ba6b5d1a288
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ It covers the official DeepSeek routes only — relay traffic billed elsewhere (
|
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
|
-
Requires DSH 0.1.7-rc.
|
|
27
|
+
Requires DSH 0.1.7-rc.2 or later: the plugin is developed against the 0.1.7-rc.2 DSH cohort (its settings page is the Host-generated page of the plugin's own Config schema) and its `@deepseek-ai/*` runtime imports are provided by the host itself.
|
|
28
28
|
|
|
29
29
|
In your profile (e.g. `~/.dsh/profiles/web`):
|
|
30
30
|
|
package/README.zh.md
CHANGED
|
@@ -24,7 +24,7 @@ dsh Web GUI 的使用统计插件:多 provider 余额与编程套餐用量检
|
|
|
24
24
|
|
|
25
25
|
## 安装
|
|
26
26
|
|
|
27
|
-
要求 DSH 0.1.7-rc.
|
|
27
|
+
要求 DSH 0.1.7-rc.2 或更高:插件基于 0.1.7-rc.2 DSH cohort 开发(其设置页即宿主根据插件自身 Config schema 生成的页面),`@deepseek-ai/*` 运行时导入由宿主本体提供。
|
|
28
28
|
|
|
29
29
|
在 profile(如 `~/.dsh/profiles/web`)中:
|
|
30
30
|
|
package/lib/client.js
CHANGED
|
@@ -303,16 +303,11 @@ window.__ModuleLoader__.load({
|
|
|
303
303
|
function bearer(apiKey) {
|
|
304
304
|
return { authorization: `Bearer ${apiKey}` };
|
|
305
305
|
}
|
|
306
|
-
/**
|
|
307
|
-
* The official pay-as-you-go balance. `deepseek` is the configurable-catalog
|
|
308
|
-
* route key; `deepseek-official` is the live provider route the llm-deepseek
|
|
309
|
-
* adapter registers (sessions and agent-default-model carry it), so both ids
|
|
310
|
-
* must resolve here or the current provider would never be probed.
|
|
311
|
-
*/
|
|
312
306
|
const DEEPSEEK = {
|
|
313
307
|
ids: ["deepseek", "deepseek-official"],
|
|
314
308
|
displayName: "DeepSeek",
|
|
315
309
|
balance: {
|
|
310
|
+
origin: "https://api.deepseek.com",
|
|
316
311
|
build: ({ apiKey }) => ({
|
|
317
312
|
url: "https://api.deepseek.com/user/balance",
|
|
318
313
|
headers: bearer(apiKey)
|
|
@@ -1019,6 +1014,10 @@ window.__ModuleLoader__.load({
|
|
|
1019
1014
|
className: usage_module_css_default.muted,
|
|
1020
1015
|
children: t("usage.balance.noCredential")
|
|
1021
1016
|
});
|
|
1017
|
+
if (provider.balanceSupported === false) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1018
|
+
className: usage_module_css_default.muted,
|
|
1019
|
+
children: t("usage.balance.unsupported")
|
|
1020
|
+
});
|
|
1022
1021
|
if (!provider.supported) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1023
1022
|
className: usage_module_css_default.muted,
|
|
1024
1023
|
children: t("usage.balance.unsupported")
|
|
@@ -1224,7 +1223,7 @@ window.__ModuleLoader__.load({
|
|
|
1224
1223
|
}),
|
|
1225
1224
|
(() => {
|
|
1226
1225
|
const configured = snapshot.providers.filter(isConfigured);
|
|
1227
|
-
const rows = configured.filter((provider) => provider.balanceSupported === true || provider.balanceSupported === void 0 && provider.supported);
|
|
1226
|
+
const rows = configured.filter((provider) => provider.balanceSupported === true || provider.balanceSupported === false || provider.balanceSupported === void 0 && provider.supported);
|
|
1228
1227
|
if (rows.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1229
1228
|
className: usage_module_css_default.muted,
|
|
1230
1229
|
children: configured.length === 0 ? t("usage.balance.noneConfigured") : t("usage.balance.unsupported")
|
|
@@ -2119,6 +2118,96 @@ window.__ModuleLoader__.load({
|
|
|
2119
2118
|
};
|
|
2120
2119
|
}
|
|
2121
2120
|
//#endregion
|
|
2121
|
+
//#region src/client/settings-entry-form.ts
|
|
2122
|
+
/** The snapshot a form reports before the Host has answered with this entry. */
|
|
2123
|
+
function pendingSnapshot() {
|
|
2124
|
+
return {
|
|
2125
|
+
status: "loading",
|
|
2126
|
+
value: void 0,
|
|
2127
|
+
base: void 0,
|
|
2128
|
+
user: void 0,
|
|
2129
|
+
revision: void 0,
|
|
2130
|
+
writable: false,
|
|
2131
|
+
mode: "host"
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
/**
|
|
2135
|
+
* Create a settings form bound to the profile entry id this Host serves, and
|
|
2136
|
+
* rebound whenever the shared describe mirror names a different one of this
|
|
2137
|
+
* package's rows.
|
|
2138
|
+
* @param options - the shared forms service and this package's candidate row ids.
|
|
2139
|
+
* @returns a form delegating to the currently resolved entry's own form.
|
|
2140
|
+
*/
|
|
2141
|
+
function createServedEntryForm(options) {
|
|
2142
|
+
const { forms, entryIds } = options;
|
|
2143
|
+
const fallbackId = entryIds[0];
|
|
2144
|
+
const namespaceId = entryIds[entryIds.length - 1];
|
|
2145
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
2146
|
+
let boundId;
|
|
2147
|
+
let bound;
|
|
2148
|
+
let offBound;
|
|
2149
|
+
let snapshot = pendingSnapshot();
|
|
2150
|
+
/** Republish: the delegated snapshot when one is bound, the pending one otherwise. */
|
|
2151
|
+
const publish = () => {
|
|
2152
|
+
if (bound !== void 0) snapshot = bound.getSnapshot();
|
|
2153
|
+
for (const listener of [...listeners]) listener();
|
|
2154
|
+
};
|
|
2155
|
+
/**
|
|
2156
|
+
* The entry id the mirror currently justifies. An unanswered or empty mirror
|
|
2157
|
+
* is not evidence of absence, so it keeps the first candidate; a mirror that
|
|
2158
|
+
* answers without any of this package's rows leaves the namespace itself.
|
|
2159
|
+
* @returns the entry id to bind.
|
|
2160
|
+
*/
|
|
2161
|
+
const resolve = () => {
|
|
2162
|
+
let served;
|
|
2163
|
+
try {
|
|
2164
|
+
served = forms.describe().getSnapshot().view?.namespaces.map((row) => row.ns);
|
|
2165
|
+
} catch {
|
|
2166
|
+
served = void 0;
|
|
2167
|
+
}
|
|
2168
|
+
if (served === void 0 || served.length === 0) return fallbackId;
|
|
2169
|
+
return entryIds.find((id) => served.includes(id)) ?? namespaceId;
|
|
2170
|
+
};
|
|
2171
|
+
/** Bind (or rebind) the resolved entry's form; a no-op while it is unchanged. */
|
|
2172
|
+
const bind = () => {
|
|
2173
|
+
const target = resolve();
|
|
2174
|
+
if (target === boundId) return;
|
|
2175
|
+
offBound?.();
|
|
2176
|
+
offBound = void 0;
|
|
2177
|
+
let form;
|
|
2178
|
+
try {
|
|
2179
|
+
form = forms.get(target);
|
|
2180
|
+
} catch {
|
|
2181
|
+
boundId = void 0;
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
boundId = target;
|
|
2185
|
+
bound = form;
|
|
2186
|
+
offBound = form.subscribe(() => {
|
|
2187
|
+
publish();
|
|
2188
|
+
});
|
|
2189
|
+
publish();
|
|
2190
|
+
};
|
|
2191
|
+
try {
|
|
2192
|
+
forms.describe().subscribe(() => {
|
|
2193
|
+
bind();
|
|
2194
|
+
});
|
|
2195
|
+
} catch {}
|
|
2196
|
+
bind();
|
|
2197
|
+
return {
|
|
2198
|
+
getSnapshot: () => snapshot,
|
|
2199
|
+
subscribe: (listener) => {
|
|
2200
|
+
listeners.add(listener);
|
|
2201
|
+
return () => {
|
|
2202
|
+
listeners.delete(listener);
|
|
2203
|
+
};
|
|
2204
|
+
},
|
|
2205
|
+
set: (field, value) => bound?.set(field, value) ?? Promise.resolve(false),
|
|
2206
|
+
unset: (field) => bound?.unset(field) ?? Promise.resolve(false),
|
|
2207
|
+
mutate: (ops, expectedRevision) => bound?.mutate(ops, expectedRevision) ?? Promise.resolve(false)
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
//#endregion
|
|
2122
2211
|
//#region src/client/index.ts
|
|
2123
2212
|
/** Hard ceiling for one usage API call; a stalled host must not pile up requests. */
|
|
2124
2213
|
const USAGE_FETCH_TIMEOUT_MS = 2e4;
|
|
@@ -2131,11 +2220,20 @@ window.__ModuleLoader__.load({
|
|
|
2131
2220
|
return await response.json();
|
|
2132
2221
|
}
|
|
2133
2222
|
const usageApi = {
|
|
2134
|
-
overview: () => usageFetch("
|
|
2135
|
-
refresh: () => usageFetch("
|
|
2223
|
+
overview: () => usageFetch("api/dsh-usage/overview", "GET"),
|
|
2224
|
+
refresh: () => usageFetch("api/dsh-usage/refresh", "POST")
|
|
2136
2225
|
};
|
|
2137
|
-
/**
|
|
2226
|
+
/**
|
|
2227
|
+
* Settings namespace the section edits: the family identity of this plugin's
|
|
2228
|
+
* own settings form, and the row id a standalone bundle install carries.
|
|
2229
|
+
*/
|
|
2138
2230
|
const USAGE_SETTINGS_NS = "dsh-usage";
|
|
2231
|
+
/** Profile entry ids this package's patch rows carry, most likely first. */
|
|
2232
|
+
const USAGE_ENTRY_IDS = [
|
|
2233
|
+
"web-ui-usage",
|
|
2234
|
+
"usage",
|
|
2235
|
+
USAGE_SETTINGS_NS
|
|
2236
|
+
];
|
|
2139
2237
|
/** First-level nav position: directly below the Workshop section (order 150). */
|
|
2140
2238
|
const SECTION_ORDER = 151;
|
|
2141
2239
|
/** Required services. */
|
|
@@ -2164,7 +2262,10 @@ window.__ModuleLoader__.load({
|
|
|
2164
2262
|
}
|
|
2165
2263
|
}, "dsh-usage: dictionaries");
|
|
2166
2264
|
const binder = ctx.get("webUiSettings");
|
|
2167
|
-
const settingsForm = binder !== void 0 ? binder.bind({ namespace: USAGE_SETTINGS_NS }) :
|
|
2265
|
+
const settingsForm = binder !== void 0 ? binder.bind({ namespace: USAGE_SETTINGS_NS }) : createServedEntryForm({
|
|
2266
|
+
forms: ctx.configForms,
|
|
2267
|
+
entryIds: USAGE_ENTRY_IDS
|
|
2268
|
+
});
|
|
2168
2269
|
const store = createUsageStore().create();
|
|
2169
2270
|
let pollSeq = 0;
|
|
2170
2271
|
const poll = () => {
|