@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 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: 075200f9f72702730eee6fefb3c8f0c78e920c13
6
- README.zh.md: a01d4af56fada30de8f044d4fde544378a7c9c2a
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.1 or later: the plugin is developed against the 0.1.7-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.
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.1 或更高:插件基于 0.1.7-rc.1 DSH cohort 开发(其设置页即宿主根据插件自身 Config schema 生成的页面),`@deepseek-ai/*` 运行时导入由宿主本体提供。
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("/api/dsh-usage/overview", "GET"),
2135
- refresh: () => usageFetch("/api/dsh-usage/refresh", "POST")
2223
+ overview: () => usageFetch("api/dsh-usage/overview", "GET"),
2224
+ refresh: () => usageFetch("api/dsh-usage/refresh", "POST")
2136
2225
  };
2137
- /** Settings namespace the section edits (dsh-web-settings maps it onto this row's profile entry id). */
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 }) : ctx.configForms.get(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 = () => {