dsh-xdpool 2.4.4 → 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 +37 -0
- package/lib/client.js +144 -184
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
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 秒通过——既有测试,与本次改动无关。
|
|
29
|
+
## 2.4.5 — 侧栏面板能滚动了
|
|
30
|
+
|
|
31
|
+
> 2.4.4 把账号池搬到侧栏那行之后,页面在折叠处被切掉、怎么都翻不到下半页。这不是新功能缺失,是我把设置页里的组件原样塞进了另一个容器,而**那个容器不滚动**。
|
|
32
|
+
|
|
33
|
+
### 面板被裁切
|
|
34
|
+
|
|
35
|
+
`@deepseek-ai/dsh-client-ui-layout` 把中央列样式成 `display:flex; flex-direction:column; overflow:hidden`——设置对话框自己带滚动容器,所以同一个组件放在设置里能滚动,放进主区域就只能被裁掉。现在页面外面包一层 `.dsm-xdpool-page`(`height:100%; min-height:0; overflow:auto`),由它当滚动容器;顺带补上设置对话框原本提供的内边距,否则卡片会贴着窗口边缘。设置页那条路径**不受影响**:那里宿主已经在滚,再加一层会双份。
|
|
36
|
+
|
|
37
|
+
### 其他
|
|
38
|
+
|
|
39
|
+
- 侧栏那行标签保持 `XD Pool`(中英文都不译)。
|
|
40
|
+
|
|
4
41
|
## 2.4.4 — 扫码登录后仍显示「登录已被拒绝」,以及侧栏入口
|
|
5
42
|
|
|
6
43
|
> 两个都不算新功能:一个是**我上一版自己的代码**把刚扫的凭据判成「旧的」;另一个是入口位置——账号池一直只能从「设置」里进。
|
package/lib/client.js
CHANGED
|
@@ -1670,39 +1670,19 @@ window.__ModuleLoader__.load({
|
|
|
1670
1670
|
):active:not(:disabled){transform:none}
|
|
1671
1671
|
}
|
|
1672
1672
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
.dsm-xdpool-
|
|
1684
|
-
.dsm-xdpool-
|
|
1685
|
-
|
|
1686
|
-
.dsm-xdpool-docklabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1687
|
-
/* No dark backdrop: the panel is a companion to the conversation, not a modal,
|
|
1688
|
-
and dimming the whole host for a settings card reads as a page navigation.
|
|
1689
|
-
Clicking outside still closes it — the scrim covers the viewport and is
|
|
1690
|
-
transparent, so only the click is caught. */
|
|
1691
|
-
.dsm-xdpool-dockscrim{position:fixed;inset:0;z-index:60;display:flex;align-items:stretch;justify-content:flex-end;background:transparent}
|
|
1692
|
-
.dsm-xdpool-dockpanel{--ease-instrument:cubic-bezier(.16,1,.3,1);display:flex;flex-direction:column;width:min(760px, 92vw);margin:14px;border:1px solid color-mix(in srgb, var(--dsw-alias-border-l2,#ffffff1f) 82%, transparent);border-radius:16px;background:color-mix(in srgb, var(--dsw-alias-bg-layer-1,#141517) 96%, transparent);box-shadow:0 20px 60px rgb(0 0 0 / 45%);overflow:hidden;animation:dsm-xdpool-dock-in .3s var(--ease-instrument)}
|
|
1693
|
-
@keyframes dsm-xdpool-dock-in{from{opacity:0;transform:translateX(18px)}to{opacity:1;transform:none}}
|
|
1694
|
-
.dsm-xdpool-dockhead{display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid color-mix(in srgb, var(--dsw-alias-border-l1,#ffffff14) 80%, transparent)}
|
|
1695
|
-
.dsm-xdpool-docktitle{flex:1 1 auto;font-size:12px;line-height:17px;font-weight:640;color:var(--dsw-alias-label-primary,#e8e8ea)}
|
|
1696
|
-
.dsm-xdpool-dockclose{appearance:none;flex:none;width:22px;height:22px;padding:0;border:1px solid transparent;border-radius:7px;background:transparent;color:var(--dsw-alias-label-tertiary,#8a90a0);font:inherit;font-size:15px;line-height:1;cursor:pointer;transition:color .16s ease,background .16s ease}
|
|
1697
|
-
.dsm-xdpool-dockclose:hover{color:var(--dsw-alias-label-primary,#e8e8ea);background:color-mix(in srgb, var(--dsw-alias-label-primary,#e8e8ea) 9%, transparent)}
|
|
1698
|
-
/* The card inside the panel was built for the settings dialog's width; scroll
|
|
1699
|
-
the panel body, never the card, so its own sticky header keeps working. */
|
|
1700
|
-
.dsm-xdpool-dockbody{flex:1 1 auto;min-height:0;overflow:auto;padding:14px}
|
|
1701
|
-
@media (prefers-reduced-motion:reduce){
|
|
1702
|
-
.dsm-xdpool-dock::after{display:none}
|
|
1703
|
-
.dsm-xdpool-dockpanel{animation:none}
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1673
|
+
`.trim();
|
|
1674
|
+
/**
|
|
1675
|
+
* Frame for hosting the pool in the main column instead of the settings dialog.
|
|
1676
|
+
*
|
|
1677
|
+
* `.centerCol` is `overflow:hidden`, so this element is the scrollport; without
|
|
1678
|
+
* it the page clips at the fold. The horizontal inset is asymmetric on purpose —
|
|
1679
|
+
* the sidebar sits on the left of this column, so the left edge needs less air
|
|
1680
|
+
* than the right one to look centred.
|
|
1681
|
+
*/
|
|
1682
|
+
const PAGE_CSS = `
|
|
1683
|
+
.dsm-xdpool-page{flex:1 1 auto;height:100%;min-height:0;overflow:auto;box-sizing:border-box;padding:20px 28px 34px}
|
|
1684
|
+
.dsm-xdpool-page > .dsm-xdpool-shell{margin:0 auto}
|
|
1685
|
+
@media (max-width:720px){.dsm-xdpool-page{padding:14px 16px 26px}}
|
|
1706
1686
|
`.trim();
|
|
1707
1687
|
//#endregion
|
|
1708
1688
|
//#region src/client/avatar.ts
|
|
@@ -8967,6 +8947,25 @@ window.__ModuleLoader__.load({
|
|
|
8967
8947
|
let packRpcBridge;
|
|
8968
8948
|
/** Capture the client ctx's connection service for {@link callPack}. */
|
|
8969
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;
|
|
8970
8969
|
let connection;
|
|
8971
8970
|
try {
|
|
8972
8971
|
connection = ctx.connection;
|
|
@@ -8980,8 +8979,8 @@ window.__ModuleLoader__.load({
|
|
|
8980
8979
|
}
|
|
8981
8980
|
try {
|
|
8982
8981
|
const call = connection?.rpc?.call;
|
|
8983
|
-
if (typeof call !== "function") return;
|
|
8984
|
-
|
|
8982
|
+
if (typeof call !== "function") return void 0;
|
|
8983
|
+
return { call: async (method, payload) => {
|
|
8985
8984
|
const bound = call.bind(connection);
|
|
8986
8985
|
const controller = new AbortController();
|
|
8987
8986
|
const timer = setTimeout(() => controller.abort(), 9e4);
|
|
@@ -8995,7 +8994,7 @@ window.__ModuleLoader__.load({
|
|
|
8995
8994
|
}
|
|
8996
8995
|
} };
|
|
8997
8996
|
} catch {
|
|
8998
|
-
|
|
8997
|
+
return;
|
|
8999
8998
|
}
|
|
9000
8999
|
}
|
|
9001
9000
|
/**
|
|
@@ -11689,7 +11688,8 @@ window.__ModuleLoader__.load({
|
|
|
11689
11688
|
const load = async () => {
|
|
11690
11689
|
try {
|
|
11691
11690
|
const channels = (await fetchChannels()).filter((channel) => !REGION_OWNED_CHANNELS.has(channel.id));
|
|
11692
|
-
const
|
|
11691
|
+
const statusAnswer = await callPack("provider.status", { providers: channels.map((channel) => channel.id) });
|
|
11692
|
+
const statuses = providerStatusesFrom(statusAnswer);
|
|
11693
11693
|
const scanProviders = channels.map((channel) => channel.id).filter((id) => (statuses.get(id)?.accountsTotal ?? 0) > 0);
|
|
11694
11694
|
const perProvider = await Promise.all(scanProviders.map(async (provider) => {
|
|
11695
11695
|
const wantBalances = channels.find((channel) => channel.id === provider)?.credits !== void 0;
|
|
@@ -11718,18 +11718,28 @@ window.__ModuleLoader__.load({
|
|
|
11718
11718
|
}];
|
|
11719
11719
|
}));
|
|
11720
11720
|
const byProvider = new Map(perProvider);
|
|
11721
|
-
const idsByProvider = {};
|
|
11722
|
-
for (const [provider, entry] of perProvider) if (entry.models.length > 0) idsByProvider[provider] = entry.models.map((model) => model.id);
|
|
11723
|
-
const facts = await fetchChannelModels(scanProviders, idsByProvider).catch(() => /* @__PURE__ */ new Map());
|
|
11724
11721
|
const next = channels.filter((channel) => byProvider.has(channel.id)).map((channel) => ({
|
|
11725
11722
|
channel,
|
|
11726
11723
|
...byProvider.get(channel.id),
|
|
11727
|
-
facts: new Map(
|
|
11724
|
+
facts: /* @__PURE__ */ new Map(),
|
|
11728
11725
|
status: statuses.get(channel.id)
|
|
11729
11726
|
}));
|
|
11730
11727
|
if (!mountedRef.current) return;
|
|
11731
11728
|
setEntries(next);
|
|
11732
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
|
+
})));
|
|
11733
11743
|
} catch (cause) {
|
|
11734
11744
|
if (!mountedRef.current) return;
|
|
11735
11745
|
setError(cause instanceof Error ? cause.message : String(cause));
|
|
@@ -12358,7 +12368,7 @@ window.__ModuleLoader__.load({
|
|
|
12358
12368
|
*
|
|
12359
12369
|
* @module dsh-xdpool/version
|
|
12360
12370
|
*/
|
|
12361
|
-
const POOL_VERSION = "2.4.
|
|
12371
|
+
const POOL_VERSION = "2.4.6";
|
|
12362
12372
|
//#endregion
|
|
12363
12373
|
//#region src/client/PoolCard.tsx
|
|
12364
12374
|
/**
|
|
@@ -14732,15 +14742,33 @@ window.__ModuleLoader__.load({
|
|
|
14732
14742
|
* `fill-rule="evenodd"` cuts the interior notches out of the silhouette so the
|
|
14733
14743
|
* three layers stay distinguishable at that size.
|
|
14734
14744
|
*/
|
|
14745
|
+
/**
|
|
14746
|
+
* The mark's geometry, independent of any wrapper element.
|
|
14747
|
+
*
|
|
14748
|
+
* Kept separate from {@link POOL_NAV_ICON_SVG} because the two consumers need
|
|
14749
|
+
* different frames: the settings nav row masks a standalone document, while the
|
|
14750
|
+
* sidebar panel row renders paths into an `svg` the shell sizes for it (16px
|
|
14751
|
+
* wide, 18px in the collapsed rail). Sharing the paths — rather than the whole
|
|
14752
|
+
* document, parsed back out with a regex — is what keeps the two placements from
|
|
14753
|
+
* drifting apart when the artwork changes.
|
|
14754
|
+
*/
|
|
14755
|
+
const POOL_NAV_ICON_PATHS = [{
|
|
14756
|
+
fill: "currentColor",
|
|
14757
|
+
fillRule: "evenodd",
|
|
14758
|
+
d: "M12 2.6a3.1 3.1 0 1 1 0 6.2 3.1 3.1 0 0 1 0-6.2Zm0 1.7a1.4 1.4 0 1 0 0 2.8 1.4 1.4 0 0 0 0-2.8ZM6.6 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2ZM17.4 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"
|
|
14759
|
+
}, {
|
|
14760
|
+
fill: "none",
|
|
14761
|
+
stroke: "currentColor",
|
|
14762
|
+
strokeWidth: 1.7,
|
|
14763
|
+
strokeLinecap: "round",
|
|
14764
|
+
strokeDasharray: "2.6 2.2",
|
|
14765
|
+
d: "M4.4 17.2a8.6 8.6 0 0 0 15.2 0"
|
|
14766
|
+
}];
|
|
14767
|
+
/** The same mark as a standalone SVG document, for `mask-image`. */
|
|
14735
14768
|
const POOL_NAV_ICON_SVG = [
|
|
14736
14769
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"16\" height=\"16\">",
|
|
14737
|
-
|
|
14738
|
-
"
|
|
14739
|
-
"M6.6 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z",
|
|
14740
|
-
"M17.4 8.9a2.6 2.6 0 1 1 0 5.2 2.6 2.6 0 0 1 0-5.2Zm0 1.6a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z",
|
|
14741
|
-
"\"/>",
|
|
14742
|
-
"<path fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\"",
|
|
14743
|
-
" d=\"M4.4 17.2a8.6 8.6 0 0 0 15.2 0\" stroke-dasharray=\"2.6 2.2\"/>",
|
|
14770
|
+
`<path fill="currentColor" fill-rule="evenodd" d="${POOL_NAV_ICON_PATHS[0].d}"/>`,
|
|
14771
|
+
`<path fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" d="${POOL_NAV_ICON_PATHS[1].d}" stroke-dasharray="2.6 2.2"/>`,
|
|
14744
14772
|
"</svg>"
|
|
14745
14773
|
].join("");
|
|
14746
14774
|
/**
|
|
@@ -14752,135 +14780,67 @@ window.__ModuleLoader__.load({
|
|
|
14752
14780
|
*/
|
|
14753
14781
|
const POOL_NAV_ICON_MASK_URL = `data:image/svg+xml;utf8,${encodeURIComponent(POOL_NAV_ICON_SVG)}`;
|
|
14754
14782
|
//#endregion
|
|
14755
|
-
//#region src/client/
|
|
14783
|
+
//#region src/client/panel-icon.tsx
|
|
14756
14784
|
/**
|
|
14757
|
-
*
|
|
14758
|
-
*
|
|
14759
|
-
*
|
|
14760
|
-
*
|
|
14761
|
-
*
|
|
14762
|
-
*
|
|
14763
|
-
*
|
|
14764
|
-
* own. Re-implementing the card for the overlay would mean two copies to keep in
|
|
14765
|
-
* step, so the overlay renders the very same `PoolCard` the settings section
|
|
14766
|
-
* renders, page state and all.
|
|
14767
|
-
*
|
|
14768
|
-
* The open flag lives at module scope rather than in React state because the
|
|
14769
|
-
* button and the panel are two separate slot registrations with no common
|
|
14770
|
-
* ancestor; module state plus a subscription is the only channel between them.
|
|
14785
|
+
* Render the pool's mark.
|
|
14786
|
+
*
|
|
14787
|
+
* Inlined rather than masked from a data URI because the shell gives the glyph an
|
|
14788
|
+
* 18px box in the collapsed rail: a `mask-size: 16px` silhouette would read as
|
|
14789
|
+
* smaller than the first-party icons beside it, and scaling the mask would blur
|
|
14790
|
+
* its 1.7px arc. Both placements share {@link POOL_NAV_ICON_PATHS}, so the artwork
|
|
14791
|
+
* cannot drift between the settings nav row and this one.
|
|
14771
14792
|
*/
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
for (const listener of [...listeners]) listener();
|
|
14784
|
-
}
|
|
14785
|
-
/** Subscribe to {@link setDockedOpen}. Returns the unsubscribe. */
|
|
14786
|
-
function subscribeDockedOpen(listener) {
|
|
14787
|
-
listeners.add(listener);
|
|
14788
|
-
return () => {
|
|
14789
|
-
listeners.delete(listener);
|
|
14790
|
-
};
|
|
14791
|
-
}
|
|
14792
|
-
/** Subscribe a component to the open flag. */
|
|
14793
|
-
function useDockedOpen() {
|
|
14794
|
-
const [open, setOpen] = (0, react.useState)(isDockedOpen);
|
|
14795
|
-
(0, react.useEffect)(() => {
|
|
14796
|
-
setOpen(isDockedOpen());
|
|
14797
|
-
return subscribeDockedOpen(() => {
|
|
14798
|
-
setOpen(isDockedOpen());
|
|
14799
|
-
});
|
|
14800
|
-
}, []);
|
|
14801
|
-
return open;
|
|
14793
|
+
function PanelIcon({ size }) {
|
|
14794
|
+
const edge = typeof size === "number" && size > 0 ? size : 16;
|
|
14795
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
14796
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
14797
|
+
viewBox: "0 0 24 24",
|
|
14798
|
+
width: edge,
|
|
14799
|
+
height: edge,
|
|
14800
|
+
"aria-hidden": "true",
|
|
14801
|
+
focusable: "false",
|
|
14802
|
+
children: POOL_NAV_ICON_PATHS.map((shape, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { ...shape }, index))
|
|
14803
|
+
});
|
|
14802
14804
|
}
|
|
14805
|
+
//#endregion
|
|
14806
|
+
//#region src/client/page.tsx
|
|
14803
14807
|
/**
|
|
14804
|
-
* The sidebar
|
|
14808
|
+
* The wrapper that makes the pool usable as a *sidebar panel*.
|
|
14805
14809
|
*
|
|
14806
|
-
*
|
|
14807
|
-
*
|
|
14810
|
+
* Two problems it solves, both invisible from inside `PoolShell`:
|
|
14811
|
+
*
|
|
14812
|
+
* 1. Scrolling. The settings dialog owns a scroll container, so a page dropped
|
|
14813
|
+
* into `settings.plugin.item` can be as tall as it likes. The main column
|
|
14814
|
+
* cannot: `@deepseek-ai/dsh-client-ui-layout` styles `.centerCol` as
|
|
14815
|
+
* `display:flex; flex-direction:column; overflow:hidden`, and the slot is
|
|
14816
|
+
* rendered into that box with no scrolling of its own. Without a scrollport
|
|
14817
|
+
* here the page simply clips — which is what happens if you register
|
|
14818
|
+
* `PoolCard` into the `main` slot directly. The reference community plugin
|
|
14819
|
+
* solves it the same way, with `height:100%; min-height:0; overflow:auto` on
|
|
14820
|
+
* its page root.
|
|
14821
|
+
*
|
|
14822
|
+
* 2. Insets. The settings dialog supplies padding around the card; the main
|
|
14823
|
+
* column supplies none, so the same markup arrives flush against the window
|
|
14824
|
+
* edge. The padding below restores the frame the dialog used to provide.
|
|
14825
|
+
*
|
|
14826
|
+
* `PAGE_CSS` is injected here rather than living in `SHARED_CARD_CSS` because
|
|
14827
|
+
* the stylesheets in `PoolShell` are rendered *inside* the shell — this wrapper
|
|
14828
|
+
* is its parent, so nothing from that subtree could style it.
|
|
14829
|
+
*
|
|
14830
|
+
* @module dsh-xdpool/client/page
|
|
14808
14831
|
*/
|
|
14809
|
-
function SidebarEntry({ wide }) {
|
|
14810
|
-
const open = useDockedOpen();
|
|
14811
|
-
const label = "XD Pool";
|
|
14812
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
14813
|
-
type: "button",
|
|
14814
|
-
className: `dsm-xdpool-dock${open ? " dsm-xdpool-dock-on" : ""}`,
|
|
14815
|
-
title: open ? "收起 XD Pool" : "打开 XD Pool",
|
|
14816
|
-
"aria-expanded": open,
|
|
14817
|
-
"aria-label": label,
|
|
14818
|
-
style: { "--dsm-xdpool-dock-glyph": `url("${POOL_NAV_ICON_MASK_URL}")` },
|
|
14819
|
-
onClick: () => {
|
|
14820
|
-
setDockedOpen(!isDockedOpen());
|
|
14821
|
-
},
|
|
14822
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14823
|
-
className: "dsm-xdpool-dockicon",
|
|
14824
|
-
"aria-hidden": "true"
|
|
14825
|
-
}), wide === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14826
|
-
className: "dsm-xdpool-docklabel",
|
|
14827
|
-
children: label
|
|
14828
|
-
}) : null]
|
|
14829
|
-
});
|
|
14830
|
-
}
|
|
14831
14832
|
/**
|
|
14832
|
-
*
|
|
14833
|
+
* `PoolCard`, sized and scrolled for the main column.
|
|
14833
14834
|
*
|
|
14834
|
-
*
|
|
14835
|
-
*
|
|
14836
|
-
*
|
|
14835
|
+
* Kept as a separate component instead of an option on `PoolCard` so the
|
|
14836
|
+
* settings registration stays exactly what it was: the dialog already provides
|
|
14837
|
+
* both the scrollport and the insets, and adding either again would double
|
|
14838
|
+
* them.
|
|
14837
14839
|
*/
|
|
14838
|
-
function
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
const onKeyDown = (event) => {
|
|
14843
|
-
if (event.key === "Escape") setDockedOpen(false);
|
|
14844
|
-
};
|
|
14845
|
-
window.addEventListener("keydown", onKeyDown);
|
|
14846
|
-
return () => {
|
|
14847
|
-
window.removeEventListener("keydown", onKeyDown);
|
|
14848
|
-
};
|
|
14849
|
-
}, [open]);
|
|
14850
|
-
if (!open) return null;
|
|
14851
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14852
|
-
className: "dsm-xdpool-dockscrim",
|
|
14853
|
-
role: "presentation",
|
|
14854
|
-
onClick: () => {
|
|
14855
|
-
setDockedOpen(false);
|
|
14856
|
-
},
|
|
14857
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14858
|
-
className: "dsm-xdpool-dockpanel",
|
|
14859
|
-
role: "dialog",
|
|
14860
|
-
"aria-modal": "false",
|
|
14861
|
-
"aria-label": "XD Pool",
|
|
14862
|
-
onClick: (event) => {
|
|
14863
|
-
event.stopPropagation();
|
|
14864
|
-
},
|
|
14865
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14866
|
-
className: "dsm-xdpool-dockhead",
|
|
14867
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
14868
|
-
className: "dsm-xdpool-docktitle",
|
|
14869
|
-
children: "XD Pool"
|
|
14870
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
14871
|
-
type: "button",
|
|
14872
|
-
className: "dsm-xdpool-dockclose",
|
|
14873
|
-
"aria-label": "关闭",
|
|
14874
|
-
onClick: () => {
|
|
14875
|
-
setDockedOpen(false);
|
|
14876
|
-
},
|
|
14877
|
-
children: "×"
|
|
14878
|
-
})]
|
|
14879
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
14880
|
-
className: "dsm-xdpool-dockbody",
|
|
14881
|
-
children
|
|
14882
|
-
})]
|
|
14883
|
-
})
|
|
14840
|
+
function PoolPage(props) {
|
|
14841
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
14842
|
+
className: "dsm-xdpool-page",
|
|
14843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: PAGE_CSS }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PoolCard, { ...props })]
|
|
14884
14844
|
});
|
|
14885
14845
|
}
|
|
14886
14846
|
//#endregion
|
|
@@ -15961,22 +15921,22 @@ window.__ModuleLoader__.load({
|
|
|
15961
15921
|
}, PoolCard));
|
|
15962
15922
|
installNavIcon(ctx, () => t("row.navLabel"));
|
|
15963
15923
|
try {
|
|
15964
|
-
ctx.slots.inject("sidebar.
|
|
15965
|
-
name: "sidebar.
|
|
15966
|
-
id: "xdpool
|
|
15967
|
-
order:
|
|
15968
|
-
label:
|
|
15969
|
-
},
|
|
15970
|
-
ctx.slots.inject("
|
|
15971
|
-
name: "
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
}
|
|
15924
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
15925
|
+
name: "sidebar.panellist",
|
|
15926
|
+
id: "xdpool",
|
|
15927
|
+
order: 20,
|
|
15928
|
+
label: () => t("row.navLabel")
|
|
15929
|
+
}, PanelIcon));
|
|
15930
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
15931
|
+
name: "main",
|
|
15932
|
+
key: "xdpool",
|
|
15933
|
+
inject: () => settingsScope === void 0 ? { t } : {
|
|
15934
|
+
t,
|
|
15935
|
+
settingsScope
|
|
15936
|
+
}
|
|
15937
|
+
}, PoolPage));
|
|
15978
15938
|
} catch (error) {
|
|
15979
|
-
console.error("[dsh-xdpool]
|
|
15939
|
+
console.error("[dsh-xdpool] sidebar entry unavailable (settings page unaffected):", error);
|
|
15980
15940
|
}
|
|
15981
15941
|
} catch (error) {
|
|
15982
15942
|
console.error("[dsh-xdpool] client page failed to load (host provider unaffected):", error);
|
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",
|