dsh-xdpool 2.4.5 → 2.4.6
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 +25 -0
- package/lib/client.js +39 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
本项目的所有重要变更都会记录在此文件。
|
|
3
3
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
|
+
## 2.4.6 — 扫码账号每次打开都空白:一份数据卡在两个细节里
|
|
5
|
+
|
|
6
|
+
> 2.4.4/2.4.5 之后侧栏能进、能滚了,但扫码过的渠道在账号池里经常整片空白,重开也一样。磁盘上账号是齐的(`state.json` 里 15 条),所以问题全在读取链。
|
|
7
|
+
|
|
8
|
+
### 16 秒的事实抓取把首屏挡掉了
|
|
9
|
+
|
|
10
|
+
`load()` 里先 `await fetchChannelModels(...)` 再 `setEntries(...)`。那次采样式采到了 **16.5 秒**——它要把每个模型逐个交给宿主适配器解析上下文窗口,冷启动时很慢。卡片区在这十几秒里是空的,看起来就像「没有账号」。
|
|
11
|
+
|
|
12
|
+
改成两阶段:`provider.status` 与 `account.list` 先回来(几十毫秒),**卡片立刻渲染**;事实(窗口/识图)随后用 `setEntries(previous => ...)` 合并进去。
|
|
13
|
+
|
|
14
|
+
### 失败被渲染成了「空」
|
|
15
|
+
|
|
16
|
+
`provider.status` 是「哪些渠道有账号」的唯一判据。它一旦整段返回 401,`scanProviders` 就是空的,`entries` 也是空的,组件走到 `if (entries.length === 0 && error === undefined) return <></>`——**静默渲染空白,一个字都不说**。读者无法区分「还没扫码」和「读取失败」。
|
|
17
|
+
|
|
18
|
+
现在把原始信封留下来判 `ok`:真的读失败就把错误显示出来。
|
|
19
|
+
|
|
20
|
+
### RPC 桥只捕获一次
|
|
21
|
+
|
|
22
|
+
`callPack` 必须走 `ctx.connection.rpc`(裸 `fetch('/api/channel-pack')` 被宿主 401,因为 `/api/*` 只允许经 admission 的客户端)。但 `installPackRpc(ctx)` 在 `apply()` 里只跑一次,而提供 `connection` 的是另一个 fiber——那一刻没就绪,桥就永久为空,此后所有 `callPack` 都回落裸 fetch 吃 401,正好触发上一条的静默空白。
|
|
23
|
+
|
|
24
|
+
改为保存 ctx、每次调用前惰性解析,就绪后自动恢复。每一步仍各自 try:宿主给客户端 ctx 套的 Proxy 对未声明服务的属性访问会**抛异常**(并上报运行失败),`?.` 挡不住抛异常的 getter,只有 `ctx.get()` 豁免。
|
|
25
|
+
|
|
26
|
+
### 其他
|
|
27
|
+
- 全套 79 文件 1023 用例。
|
|
28
|
+
- `tests/aggregate-probe-regression.test.ts` 全量并行时会因争抢跑到 60 秒超时,单独跑 26 秒通过——既有测试,与本次改动无关。
|
|
4
29
|
## 2.4.5 — 侧栏面板能滚动了
|
|
5
30
|
|
|
6
31
|
> 2.4.4 把账号池搬到侧栏那行之后,页面在折叠处被切掉、怎么都翻不到下半页。这不是新功能缺失,是我把设置页里的组件原样塞进了另一个容器,而**那个容器不滚动**。
|
package/lib/client.js
CHANGED
|
@@ -8947,6 +8947,25 @@ window.__ModuleLoader__.load({
|
|
|
8947
8947
|
let packRpcBridge;
|
|
8948
8948
|
/** Capture the client ctx's connection service for {@link callPack}. */
|
|
8949
8949
|
function installPackRpc(ctx) {
|
|
8950
|
+
packRpcContext = ctx;
|
|
8951
|
+
packRpcBridge = resolvePackRpcBridge();
|
|
8952
|
+
}
|
|
8953
|
+
/** The client ctx captured by {@link installPackRpc}, for late resolution. */
|
|
8954
|
+
let packRpcContext;
|
|
8955
|
+
/**
|
|
8956
|
+
* Read `connection.rpc.call` off the captured ctx, or `undefined` if the
|
|
8957
|
+
* service is still absent.
|
|
8958
|
+
*
|
|
8959
|
+
* Each step gets its own try. The host hands the client ctx a Proxy that
|
|
8960
|
+
* THROWS on direct property access to an undeclared service
|
|
8961
|
+
* (cordis-client-runner's dynamic ctx), and that throw also reports the run as
|
|
8962
|
+
* failed — so one shared try/catch would let a rejected `ctx.connection` skip
|
|
8963
|
+
* the exempt `ctx.get()` below and silently leave the bridge unset. `?.` does
|
|
8964
|
+
* not help: it guards null/undefined, not a throwing getter.
|
|
8965
|
+
*/
|
|
8966
|
+
function resolvePackRpcBridge() {
|
|
8967
|
+
const ctx = packRpcContext;
|
|
8968
|
+
if (ctx === void 0 || ctx === null) return void 0;
|
|
8950
8969
|
let connection;
|
|
8951
8970
|
try {
|
|
8952
8971
|
connection = ctx.connection;
|
|
@@ -8960,8 +8979,8 @@ window.__ModuleLoader__.load({
|
|
|
8960
8979
|
}
|
|
8961
8980
|
try {
|
|
8962
8981
|
const call = connection?.rpc?.call;
|
|
8963
|
-
if (typeof call !== "function") return;
|
|
8964
|
-
|
|
8982
|
+
if (typeof call !== "function") return void 0;
|
|
8983
|
+
return { call: async (method, payload) => {
|
|
8965
8984
|
const bound = call.bind(connection);
|
|
8966
8985
|
const controller = new AbortController();
|
|
8967
8986
|
const timer = setTimeout(() => controller.abort(), 9e4);
|
|
@@ -8975,7 +8994,7 @@ window.__ModuleLoader__.load({
|
|
|
8975
8994
|
}
|
|
8976
8995
|
} };
|
|
8977
8996
|
} catch {
|
|
8978
|
-
|
|
8997
|
+
return;
|
|
8979
8998
|
}
|
|
8980
8999
|
}
|
|
8981
9000
|
/**
|
|
@@ -11669,7 +11688,8 @@ window.__ModuleLoader__.load({
|
|
|
11669
11688
|
const load = async () => {
|
|
11670
11689
|
try {
|
|
11671
11690
|
const channels = (await fetchChannels()).filter((channel) => !REGION_OWNED_CHANNELS.has(channel.id));
|
|
11672
|
-
const
|
|
11691
|
+
const statusAnswer = await callPack("provider.status", { providers: channels.map((channel) => channel.id) });
|
|
11692
|
+
const statuses = providerStatusesFrom(statusAnswer);
|
|
11673
11693
|
const scanProviders = channels.map((channel) => channel.id).filter((id) => (statuses.get(id)?.accountsTotal ?? 0) > 0);
|
|
11674
11694
|
const perProvider = await Promise.all(scanProviders.map(async (provider) => {
|
|
11675
11695
|
const wantBalances = channels.find((channel) => channel.id === provider)?.credits !== void 0;
|
|
@@ -11698,18 +11718,28 @@ window.__ModuleLoader__.load({
|
|
|
11698
11718
|
}];
|
|
11699
11719
|
}));
|
|
11700
11720
|
const byProvider = new Map(perProvider);
|
|
11701
|
-
const idsByProvider = {};
|
|
11702
|
-
for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
|
|
11703
|
-
const facts = await fetchChannelModels(scanProviders, idsByProvider).catch(() => /* @__PURE__ */ new Map());
|
|
11704
11721
|
const next = channels.filter((channel) => byProvider.has(channel.id)).map((channel) => ({
|
|
11705
11722
|
channel,
|
|
11706
11723
|
...byProvider.get(channel.id),
|
|
11707
|
-
facts: new Map(
|
|
11724
|
+
facts: /* @__PURE__ */ new Map(),
|
|
11708
11725
|
status: statuses.get(channel.id)
|
|
11709
11726
|
}));
|
|
11710
11727
|
if (!mountedRef.current) return;
|
|
11711
11728
|
setEntries(next);
|
|
11712
11729
|
setError(void 0);
|
|
11730
|
+
if (next.length === 0) {
|
|
11731
|
+
if (!statusAnswer.ok) setError(statusAnswer.error?.message ?? "provider.status refused");
|
|
11732
|
+
return;
|
|
11733
|
+
}
|
|
11734
|
+
const idsByProvider = {};
|
|
11735
|
+
for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
|
|
11736
|
+
const facts = await fetchChannelModels(next.map((entry) => entry.channel.id), idsByProvider).catch(() => /* @__PURE__ */ new Map());
|
|
11737
|
+
if (!mountedRef.current) return;
|
|
11738
|
+
if (facts.size === 0) return;
|
|
11739
|
+
setEntries((previous) => previous.map((entry) => ({
|
|
11740
|
+
...entry,
|
|
11741
|
+
facts: new Map((facts.get(entry.channel.id) ?? []).map((model) => [model.id, model]))
|
|
11742
|
+
})));
|
|
11713
11743
|
} catch (cause) {
|
|
11714
11744
|
if (!mountedRef.current) return;
|
|
11715
11745
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -12338,7 +12368,7 @@ window.__ModuleLoader__.load({
|
|
|
12338
12368
|
*
|
|
12339
12369
|
* @module dsh-xdpool/version
|
|
12340
12370
|
*/
|
|
12341
|
-
const POOL_VERSION = "2.4.
|
|
12371
|
+
const POOL_VERSION = "2.4.6";
|
|
12342
12372
|
//#endregion
|
|
12343
12373
|
//#region src/client/PoolCard.tsx
|
|
12344
12374
|
/**
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-xdpool",
|
|
3
3
|
"displayName": "DSH XD Pool",
|
|
4
4
|
"description": "One DSH model pool for every free quota you have: merge all locally signed-in WorkBuddy accounts with auto-failover, sign in to 13 vendors by QR/web, aggregate 76 vendors' free tiers, plus a usage dashboard, model routing, a local OpenAI-compatible gateway and a points-automation suite.",
|
|
5
|
-
"version": "2.4.
|
|
5
|
+
"version": "2.4.6",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "XDTrees",
|
|
8
8
|
"homepage": "https://github.com/XDTrees/dsh-xdpool#readme",
|