@linxin666/dsh-usage 0.4.2 → 0.4.4
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 +104 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +252 -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/host/usage-service.d.ts +4 -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/body-mutations.ts +4 -4
- package/src/client/index.ts +17 -7
- package/src/client/settings-entry-form.ts +142 -0
- package/src/host/usage-service.ts +129 -8
- package/src/index.ts +138 -43
- package/src/mount-once.ts +107 -13
- package/lib/types/host/run-guarded.d.ts +0 -39
- package/src/host/run-guarded.ts +0 -76
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: ce35e9cda06a1c0d34c1c37170eece47ffa2a80c
|
|
6
|
+
README.zh.md: 4199ceccb73a627ba4bef72c4857d4a80c144020
|
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.
|
|
27
|
+
Requires DSH 0.2.0-rc.1 or later: the plugin is developed against the 0.2.0-rc.1 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.
|
|
27
|
+
要求 DSH 0.2.0-rc.1 或更高:插件基于 0.2.0-rc.1 DSH cohort 开发(其设置页即宿主根据插件自身 Config schema 生成的页面),`@deepseek-ai/*` 运行时导入由宿主本体提供。
|
|
28
28
|
|
|
29
29
|
在 profile(如 `~/.dsh/profiles/web`)中:
|
|
30
30
|
|
package/lib/client.js
CHANGED
|
@@ -2118,6 +2118,96 @@ window.__ModuleLoader__.load({
|
|
|
2118
2118
|
};
|
|
2119
2119
|
}
|
|
2120
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
|
|
2121
2211
|
//#region src/client/index.ts
|
|
2122
2212
|
/** Hard ceiling for one usage API call; a stalled host must not pile up requests. */
|
|
2123
2213
|
const USAGE_FETCH_TIMEOUT_MS = 2e4;
|
|
@@ -2133,8 +2223,17 @@ window.__ModuleLoader__.load({
|
|
|
2133
2223
|
overview: () => usageFetch("api/dsh-usage/overview", "GET"),
|
|
2134
2224
|
refresh: () => usageFetch("api/dsh-usage/refresh", "POST")
|
|
2135
2225
|
};
|
|
2136
|
-
/**
|
|
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
|
+
*/
|
|
2137
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
|
+
];
|
|
2138
2237
|
/** First-level nav position: directly below the Workshop section (order 150). */
|
|
2139
2238
|
const SECTION_ORDER = 151;
|
|
2140
2239
|
/** Required services. */
|
|
@@ -2163,7 +2262,10 @@ window.__ModuleLoader__.load({
|
|
|
2163
2262
|
}
|
|
2164
2263
|
}, "dsh-usage: dictionaries");
|
|
2165
2264
|
const binder = ctx.get("webUiSettings");
|
|
2166
|
-
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
|
+
});
|
|
2167
2269
|
const store = createUsageStore().create();
|
|
2168
2270
|
let pollSeq = 0;
|
|
2169
2271
|
const poll = () => {
|