dsh-model-visibility 0.1.0
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/LICENSE +21 -0
- package/README.en.md +44 -0
- package/README.md +44 -0
- package/cordis.patch.yml +6 -0
- package/lib/client.js +544 -0
- package/lib/client.js.map +1 -0
- package/lib/index.js +40 -0
- package/lib/types/client/ModelVisibilityCard.d.ts +12 -0
- package/lib/types/client/controller.d.ts +74 -0
- package/lib/types/client/index.d.ts +19 -0
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/index.d.ts +29 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lzh3070
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# dsh-model-visibility
|
|
2
|
+
|
|
3
|
+
**English · [中文](README.md)**
|
|
4
|
+
|
|
5
|
+
Model visibility for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): control which models appear in the conversation model selector.
|
|
6
|
+
|
|
7
|
+
Installs a dedicated **Model visibility** settings section (right after 模型 / Models in the left nav):
|
|
8
|
+
|
|
9
|
+
- 🔍 **Search** — live filter by model name / id / provider
|
|
10
|
+
- 🎚️ **Per-model switches** — hide a model and it disappears from the selector instantly; flip back to restore
|
|
11
|
+
- 📦 **Provider-level batch** — hide or show every model of one provider in one click
|
|
12
|
+
- ♻️ **Reset all** — clear the hidden list
|
|
13
|
+
- 🛡️ **Zero intrusion** — writes only the plugin's own hidden list, never your provider config; hiding a model a session already uses never interrupts that session
|
|
14
|
+
- 🆕 **New models visible by default** — the plugin remembers what to hide, not what to show
|
|
15
|
+
|
|
16
|
+
## Requirements
|
|
17
|
+
|
|
18
|
+
- DeepSeek Harness ≥ 0.1.1-rc.2 (earlier versions untested)
|
|
19
|
+
- Node.js 22+
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh plugin --profile web add dsh-model-visibility
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Restart `dsh web` to activate.
|
|
28
|
+
|
|
29
|
+
## How it works
|
|
30
|
+
|
|
31
|
+
- **Host half** (`lib/index.js`): owns the `model-visibility` settings section (the hidden list) and wraps `ctx.llm.listModels` at its source, so both `session.models` (the conversation selector) and `llm.models` (the settings page) receive the filtered catalog. Catalog membership is advisory in the harness, so hiding a model a session already uses never breaks that session's dispatch.
|
|
32
|
+
- **Browser half** (`lib/client.js`): registers a `settings.section` page; page state is the union of the filtered catalog and the hidden entries, each hidden entry labeled by the display-name snapshot captured when it was hidden.
|
|
33
|
+
|
|
34
|
+
## Uninstall
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
dsh plugin --profile web remove dsh-model-visibility
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Uninstalling restores everything: the plugin never writes provider config, and the hidden list dies with the package.
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# dsh-model-visibility
|
|
2
|
+
|
|
3
|
+
**[English](README.en.md) · 中文**
|
|
4
|
+
|
|
5
|
+
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的模型可见性插件:控制哪些模型出现在对话界面的模型选择菜单里。
|
|
6
|
+
|
|
7
|
+
安装后在设置左侧导航提供一个独立的「**模型可见性**」分区页(紧跟「模型」):
|
|
8
|
+
|
|
9
|
+
- 🔍 **搜索** —— 按模型名/ID/渠道名实时过滤
|
|
10
|
+
- 🎚️ **逐模型开关** —— 隐藏的模型立即从选择器消失,显示即恢复
|
|
11
|
+
- 📦 **渠道级批量** —— 一键隐藏/显示某个 Provider 的全部模型
|
|
12
|
+
- ♻️ **一键恢复全部** —— 清空隐藏列表
|
|
13
|
+
- 🛡️ **零侵入** —— 只写插件自己的隐藏列表,不改你的 Provider 配置;隐藏正在使用的模型不会中断该会话
|
|
14
|
+
- 🆕 **新模型自动可见** —— 插件只记录"要隐藏什么",Provider 新广告的模型默认出现
|
|
15
|
+
|
|
16
|
+
## 要求
|
|
17
|
+
|
|
18
|
+
- DeepSeek Harness ≥ 0.1.1-rc.2(更早版本未测试)
|
|
19
|
+
- Node.js 22+(构建与运行)
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh plugin --profile web add dsh-model-visibility
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
重启 `dsh web` 后生效。
|
|
28
|
+
|
|
29
|
+
## 工作原理
|
|
30
|
+
|
|
31
|
+
- **宿主半**(`lib/index.js`):持有 `model-visibility` 设置段(隐藏列表),并在源头包装 `ctx.llm.listModels`——会话选择器(`session.models`)与设置页(`llm.models`)拿到的目录都是过滤后的。目录成员资格在 harness 中是"建议性"的,因此隐藏一个正在使用的模型不影响其调度。
|
|
32
|
+
- **浏览器半**(`lib/client.js`):向设置壳注册一个 `settings.section` 分区页;页面状态来自"过滤后目录 ∪ 隐藏条目"的并集,隐藏条目携带隐藏时抓取的显示名快照。
|
|
33
|
+
|
|
34
|
+
## 卸载
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
dsh plugin --profile web remove dsh-model-visibility
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
卸载即恢复原状:插件不写入 Provider 配置,隐藏列表随包删除自动失效。
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
package/cordis.patch.yml
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-model-visibility",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let _deepseek_ai_dsh_client_runtime_client = require("@deepseek-ai/dsh-client-runtime/client");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
|
+
//#region src/client/controller.ts
|
|
11
|
+
/**
|
|
12
|
+
* The model-visibility card's controller: React-free state over two sources —
|
|
13
|
+
* the (already filtered) Host model catalog and the `model-visibility`
|
|
14
|
+
* settings scope. The rendered list is their union: catalog rows are visible
|
|
15
|
+
* models; hidden entries restate the models the filter removed, labeled by
|
|
16
|
+
* the name snapshot each entry carries. Beyond the reference shape, the
|
|
17
|
+
* controller exposes provider-level batch writes and a whole-list write so
|
|
18
|
+
* the card can offer search + bulk actions.
|
|
19
|
+
*/
|
|
20
|
+
const EMPTY = {
|
|
21
|
+
phase: "loading",
|
|
22
|
+
writable: false,
|
|
23
|
+
groups: [],
|
|
24
|
+
total: 0,
|
|
25
|
+
visible: 0
|
|
26
|
+
};
|
|
27
|
+
/** Mint one wire id (the brand is client-side nominal typing only). */
|
|
28
|
+
function rpcRequest(payload) {
|
|
29
|
+
return {
|
|
30
|
+
rpcId: crypto.randomUUID(),
|
|
31
|
+
payload
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
var ModelVisibilityCardController = class {
|
|
35
|
+
scope;
|
|
36
|
+
api;
|
|
37
|
+
/** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */
|
|
38
|
+
store = (0, _deepseek_ai_dsh_client_runtime_client.createSnapshotStore)(EMPTY);
|
|
39
|
+
catalog;
|
|
40
|
+
loadError;
|
|
41
|
+
generation = 0;
|
|
42
|
+
constructor(scope, api) {
|
|
43
|
+
this.scope = scope;
|
|
44
|
+
this.api = api;
|
|
45
|
+
scope.subscribe(() => {
|
|
46
|
+
this.recompute();
|
|
47
|
+
});
|
|
48
|
+
this.load();
|
|
49
|
+
}
|
|
50
|
+
/** The registration-side inject face. */
|
|
51
|
+
face() {
|
|
52
|
+
return {
|
|
53
|
+
hooks: { modelVisibility: this.store },
|
|
54
|
+
toggle: (provider, model, name, hidden) => {
|
|
55
|
+
this.toggle(provider, model, name, hidden);
|
|
56
|
+
},
|
|
57
|
+
toggleProvider: (provider, hidden) => {
|
|
58
|
+
this.toggleProvider(provider, hidden);
|
|
59
|
+
},
|
|
60
|
+
resetAll: () => {
|
|
61
|
+
this.resetAll();
|
|
62
|
+
},
|
|
63
|
+
reload: () => {
|
|
64
|
+
this.load();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Re-read the catalog; an older response never overwrites a newer one. */
|
|
69
|
+
async load() {
|
|
70
|
+
const generation = ++this.generation;
|
|
71
|
+
if (this.catalog === void 0) this.store.update((draft) => {
|
|
72
|
+
draft.phase = "loading";
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const response = await this.api.llm.models(rpcRequest({}));
|
|
76
|
+
if (generation !== this.generation) return;
|
|
77
|
+
if (response.result.ok) {
|
|
78
|
+
this.catalog = response.result.value.groups;
|
|
79
|
+
this.loadError = void 0;
|
|
80
|
+
} else this.loadError = response.result.error.message;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (generation !== this.generation) return;
|
|
83
|
+
this.loadError = error instanceof Error ? error.message : String(error);
|
|
84
|
+
}
|
|
85
|
+
this.recompute();
|
|
86
|
+
}
|
|
87
|
+
/** Compute the next hidden list from the current one plus one model's flip. */
|
|
88
|
+
nextHidden(current, provider, model, name, hidden) {
|
|
89
|
+
const rest = current.filter((entry) => !(entry.provider === provider && entry.model === model));
|
|
90
|
+
return hidden ? [...rest, {
|
|
91
|
+
provider,
|
|
92
|
+
model,
|
|
93
|
+
name
|
|
94
|
+
}] : rest;
|
|
95
|
+
}
|
|
96
|
+
/** Write the next hidden list, then re-pull the catalog the filter changed. */
|
|
97
|
+
async write(next) {
|
|
98
|
+
try {
|
|
99
|
+
await this.scope.set("hidden", next);
|
|
100
|
+
} catch {}
|
|
101
|
+
await this.load();
|
|
102
|
+
}
|
|
103
|
+
/** Flip one model's visibility (public API — the section card calls it directly). */
|
|
104
|
+
async toggle(provider, model, name, hidden) {
|
|
105
|
+
const current = this.scope.getSnapshot().value?.hidden ?? [];
|
|
106
|
+
await this.write(this.nextHidden(current, provider, model, name, hidden));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Batch flip for one provider. Models are taken from the union of the
|
|
110
|
+
* (filtered) catalog rows and existing hidden entries so a provider with
|
|
111
|
+
* every model already hidden still flips back fully.
|
|
112
|
+
*/
|
|
113
|
+
/** Batch flip for one provider (public API). */
|
|
114
|
+
async toggleProvider(provider, hidden) {
|
|
115
|
+
const current = this.scope.getSnapshot().value?.hidden ?? [];
|
|
116
|
+
const providerRows = this.stateRows().find((group) => group.id === provider);
|
|
117
|
+
const ids = /* @__PURE__ */ new Set([...providerRows ? providerRows.models.map((model) => model.id) : [], ...current.filter((entry) => entry.provider === provider).map((entry) => entry.model)]);
|
|
118
|
+
if (ids.size === 0) return;
|
|
119
|
+
let next = current.filter((entry) => entry.provider !== provider);
|
|
120
|
+
if (hidden) {
|
|
121
|
+
const nameOf = (id) => providerRows?.models.find((model) => model.id === id)?.name ?? id;
|
|
122
|
+
next = [...next, ...[...ids].map((id) => ({
|
|
123
|
+
provider,
|
|
124
|
+
model: id,
|
|
125
|
+
name: nameOf(id)
|
|
126
|
+
}))];
|
|
127
|
+
}
|
|
128
|
+
await this.write(next);
|
|
129
|
+
}
|
|
130
|
+
/** Clear the hidden list entirely (public API). */
|
|
131
|
+
async resetAll() {
|
|
132
|
+
await this.write([]);
|
|
133
|
+
}
|
|
134
|
+
/** Current union rows (catalog ∪ hidden) without touching the store. */
|
|
135
|
+
stateRows() {
|
|
136
|
+
const hidden = this.scope.getSnapshot().value?.hidden ?? [];
|
|
137
|
+
if (this.catalog === void 0) return [];
|
|
138
|
+
const hiddenKeys = new Set(hidden.map((entry) => `${entry.provider}\u0000${entry.model}`));
|
|
139
|
+
const groups = this.catalog.map((group) => ({
|
|
140
|
+
id: group.id,
|
|
141
|
+
name: group.name,
|
|
142
|
+
models: group.models.map((model) => ({
|
|
143
|
+
id: model.id,
|
|
144
|
+
name: model.name,
|
|
145
|
+
hidden: hiddenKeys.has(`${group.id}\u0000${model.id}`)
|
|
146
|
+
}))
|
|
147
|
+
}));
|
|
148
|
+
const groupsById = new Map(groups.map((group) => [group.id, group]));
|
|
149
|
+
for (const entry of hidden) {
|
|
150
|
+
const group = groupsById.get(entry.provider);
|
|
151
|
+
if (group?.models.some((model) => model.id === entry.model)) continue;
|
|
152
|
+
const row = {
|
|
153
|
+
id: entry.model,
|
|
154
|
+
name: entry.name ?? entry.model,
|
|
155
|
+
hidden: true
|
|
156
|
+
};
|
|
157
|
+
if (group !== void 0) group.models.push(row);
|
|
158
|
+
else {
|
|
159
|
+
const created = {
|
|
160
|
+
id: entry.provider,
|
|
161
|
+
name: entry.provider,
|
|
162
|
+
models: [row]
|
|
163
|
+
};
|
|
164
|
+
groupsById.set(entry.provider, created);
|
|
165
|
+
groups.push(created);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return groups;
|
|
169
|
+
}
|
|
170
|
+
/** Union the filtered catalog with the hidden entries into render rows. */
|
|
171
|
+
recompute() {
|
|
172
|
+
const snapshot = this.scope.getSnapshot();
|
|
173
|
+
if (this.catalog === void 0 && this.loadError !== void 0) {
|
|
174
|
+
this.store.set({
|
|
175
|
+
...EMPTY,
|
|
176
|
+
phase: "error",
|
|
177
|
+
error: this.loadError,
|
|
178
|
+
writable: snapshot.writable
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (this.catalog === void 0 || snapshot.status === "loading") {
|
|
183
|
+
this.store.set({
|
|
184
|
+
...EMPTY,
|
|
185
|
+
writable: snapshot.writable
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const groups = this.stateRows();
|
|
190
|
+
const total = groups.reduce((count, group) => count + group.models.length, 0);
|
|
191
|
+
const visible = groups.reduce((count, group) => count + group.models.filter((model) => !model.hidden).length, 0);
|
|
192
|
+
this.store.set({
|
|
193
|
+
phase: "ready",
|
|
194
|
+
writable: snapshot.writable,
|
|
195
|
+
groups,
|
|
196
|
+
total,
|
|
197
|
+
visible
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/client/ModelVisibilityCard.tsx
|
|
203
|
+
/**
|
|
204
|
+
* The model-visibility settings section, styled to mirror the official
|
|
205
|
+
* settings pages: big page title + muted intro, a controls row, then one
|
|
206
|
+
* bordered rounded card per provider (like the provider cards on the
|
|
207
|
+
* 模型 page) with pill action buttons and token-style switches.
|
|
208
|
+
*
|
|
209
|
+
* Consumes the `settings.section` contribution contract exactly like the
|
|
210
|
+
* official ModelsSection: props = spread inject face, where `hooks.snapshot`
|
|
211
|
+
* arrives as the `useSnapshot` selector hook; all three seats are undefined
|
|
212
|
+
* while the shell has not injected yet (render null then).
|
|
213
|
+
*/
|
|
214
|
+
const STYLE_ID = "dsh-model-visibility-styles";
|
|
215
|
+
const STYLES = `
|
|
216
|
+
.dsh-mvw { display:flex; flex-direction:column; gap:16px; }
|
|
217
|
+
.dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }
|
|
218
|
+
.dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }
|
|
219
|
+
.dsh-mvw .mvw-controls { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:4px; }
|
|
220
|
+
.dsh-mvw .mvw-search {
|
|
221
|
+
flex:1 1 220px; min-width:160px; height:38px; padding:0 14px;
|
|
222
|
+
font-size:13px; border-radius:10px;
|
|
223
|
+
border:1px solid rgba(128,128,128,.38);
|
|
224
|
+
background:transparent; color:inherit; outline:none;
|
|
225
|
+
}
|
|
226
|
+
.dsh-mvw .mvw-search:focus { border-color: rgba(76,139,245,.85); }
|
|
227
|
+
.dsh-mvw .mvw-pill {
|
|
228
|
+
height:36px; padding:0 16px; font-size:13px; cursor:pointer;
|
|
229
|
+
border-radius:999px; border:1px solid rgba(128,128,128,.38);
|
|
230
|
+
background:transparent; color:inherit;
|
|
231
|
+
}
|
|
232
|
+
.dsh-mvw .mvw-pill:hover:not(:disabled) { background:rgba(128,128,128,.08); }
|
|
233
|
+
.dsh-mvw .mvw-pill:disabled { opacity:.45; cursor:not-allowed; }
|
|
234
|
+
.dsh-mvw .mvw-count { font-size:12.5px; opacity:.62; margin-left:auto; }
|
|
235
|
+
.dsh-mvw .mvw-card {
|
|
236
|
+
border:1px solid rgba(128,128,128,.30); border-radius:16px;
|
|
237
|
+
padding:6px 18px 2px;
|
|
238
|
+
}
|
|
239
|
+
.dsh-mvw .mvw-prov-head {
|
|
240
|
+
display:flex; align-items:center; justify-content:space-between; gap:10px;
|
|
241
|
+
padding:12px 0 10px;
|
|
242
|
+
}
|
|
243
|
+
.dsh-mvw .mvw-prov-name { font-size:15px; font-weight:650; }
|
|
244
|
+
.dsh-mvw .mvw-prov-count { font-size:12px; opacity:.55; margin-left:10px; font-weight:400; }
|
|
245
|
+
.dsh-mvw .mvw-prov-btns { display:flex; gap:8px; }
|
|
246
|
+
.dsh-mvw .mvw-mini {
|
|
247
|
+
height:30px; padding:0 13px; font-size:12.5px; cursor:pointer;
|
|
248
|
+
border-radius:999px; border:1px solid rgba(128,128,128,.38);
|
|
249
|
+
background:transparent; color:inherit;
|
|
250
|
+
}
|
|
251
|
+
.dsh-mvw .mvw-mini:hover:not(:disabled) { background:rgba(128,128,128,.08); }
|
|
252
|
+
.dsh-mvw .mvw-mini:disabled { opacity:.4; cursor:not-allowed; }
|
|
253
|
+
.dsh-mvw .mvw-row {
|
|
254
|
+
display:flex; align-items:center; justify-content:space-between; gap:12px;
|
|
255
|
+
min-height:46px; border-top:1px solid rgba(128,128,128,.16);
|
|
256
|
+
}
|
|
257
|
+
.dsh-mvw .mvw-row:first-of-type { border-top:none; }
|
|
258
|
+
.dsh-mvw .mvw-name {
|
|
259
|
+
font-size:14px;
|
|
260
|
+
min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
|
|
261
|
+
}
|
|
262
|
+
.dsh-mvw .mvw-id {
|
|
263
|
+
opacity:.5; margin-left:8px; padding-right:6px; font-size:12px;
|
|
264
|
+
}
|
|
265
|
+
.dsh-mvw .mvw-note { font-size:12.5px; opacity:.66; }
|
|
266
|
+
.dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }
|
|
267
|
+
.dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }
|
|
268
|
+
`;
|
|
269
|
+
function ensureStyles() {
|
|
270
|
+
if (typeof document === "undefined") return;
|
|
271
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
272
|
+
const el = document.createElement("style");
|
|
273
|
+
el.id = STYLE_ID;
|
|
274
|
+
el.textContent = STYLES;
|
|
275
|
+
document.head.appendChild(el);
|
|
276
|
+
}
|
|
277
|
+
function ModelVisibilityCard(props) {
|
|
278
|
+
const { controller, useSnapshot, t } = props;
|
|
279
|
+
const state = (useSnapshot ?? ((_selector) => void 0))((snapshot) => snapshot);
|
|
280
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
281
|
+
(0, react.useMemo)(() => {
|
|
282
|
+
ensureStyles();
|
|
283
|
+
}, []);
|
|
284
|
+
const filteredGroups = (0, react.useMemo)(() => {
|
|
285
|
+
const q = query.trim().toLowerCase();
|
|
286
|
+
if (q === "") return state?.groups ?? [];
|
|
287
|
+
return (state?.groups ?? []).map((group) => ({
|
|
288
|
+
...group,
|
|
289
|
+
models: group.models.filter((model) => model.id.toLowerCase().includes(q) || model.name.toLowerCase().includes(q) || group.id.toLowerCase().includes(q))
|
|
290
|
+
})).filter((group) => group.models.length > 0);
|
|
291
|
+
}, [state, query]);
|
|
292
|
+
if (controller === void 0 || useSnapshot === void 0 || t === void 0) return null;
|
|
293
|
+
if (state === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
294
|
+
className: "dsh-mvw",
|
|
295
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
296
|
+
className: "mvw-note",
|
|
297
|
+
children: t("loading")
|
|
298
|
+
})
|
|
299
|
+
});
|
|
300
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
301
|
+
className: "dsh-mvw",
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
304
|
+
className: "mvw-title",
|
|
305
|
+
children: t("title")
|
|
306
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
307
|
+
className: "mvw-sub",
|
|
308
|
+
children: t("intro")
|
|
309
|
+
})] }),
|
|
310
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
311
|
+
className: "mvw-controls",
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
314
|
+
type: "search",
|
|
315
|
+
className: "mvw-search",
|
|
316
|
+
value: query,
|
|
317
|
+
placeholder: t("searchPlaceholder"),
|
|
318
|
+
onChange: (event) => {
|
|
319
|
+
setQuery(event.target.value);
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
323
|
+
type: "button",
|
|
324
|
+
className: "mvw-pill",
|
|
325
|
+
disabled: !state.writable || state.total === state.visible,
|
|
326
|
+
onClick: () => {
|
|
327
|
+
controller.resetAll();
|
|
328
|
+
},
|
|
329
|
+
children: t("showAllProviders")
|
|
330
|
+
}),
|
|
331
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
332
|
+
className: "mvw-count",
|
|
333
|
+
children: t("visibleCount", {
|
|
334
|
+
visible: state.visible,
|
|
335
|
+
total: state.total
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
]
|
|
339
|
+
}),
|
|
340
|
+
state.phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
341
|
+
className: "mvw-warn",
|
|
342
|
+
children: [
|
|
343
|
+
t("loadFailed", { message: state.error ?? "" }),
|
|
344
|
+
" ",
|
|
345
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
346
|
+
type: "button",
|
|
347
|
+
className: "mvw-mini",
|
|
348
|
+
onClick: () => {
|
|
349
|
+
controller.load();
|
|
350
|
+
},
|
|
351
|
+
children: t("retry")
|
|
352
|
+
})
|
|
353
|
+
]
|
|
354
|
+
}),
|
|
355
|
+
state.phase === "ready" && !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
356
|
+
className: "mvw-note",
|
|
357
|
+
children: t("readOnly")
|
|
358
|
+
}),
|
|
359
|
+
state.phase === "ready" && state.groups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
360
|
+
className: "mvw-note",
|
|
361
|
+
children: t("empty")
|
|
362
|
+
}),
|
|
363
|
+
state.phase === "ready" && state.total > 0 && state.visible === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
364
|
+
className: "mvw-warn",
|
|
365
|
+
children: t("allHiddenWarning")
|
|
366
|
+
}),
|
|
367
|
+
state.phase === "ready" && filteredGroups.length === 0 && state.groups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
368
|
+
className: "mvw-note",
|
|
369
|
+
children: t("noMatch")
|
|
370
|
+
}),
|
|
371
|
+
state.phase === "ready" && filteredGroups.map((group) => {
|
|
372
|
+
const groupVisible = group.models.filter((model) => !model.hidden).length;
|
|
373
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
374
|
+
className: "mvw-card",
|
|
375
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
376
|
+
className: "mvw-prov-head",
|
|
377
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
378
|
+
className: "mvw-prov-name",
|
|
379
|
+
children: group.name
|
|
380
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
381
|
+
className: "mvw-prov-count",
|
|
382
|
+
children: t("providerRow", {
|
|
383
|
+
visible: groupVisible,
|
|
384
|
+
total: group.models.length
|
|
385
|
+
})
|
|
386
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
387
|
+
className: "mvw-prov-btns",
|
|
388
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
389
|
+
type: "button",
|
|
390
|
+
className: "mvw-mini",
|
|
391
|
+
disabled: !state.writable || groupVisible === 0,
|
|
392
|
+
onClick: () => {
|
|
393
|
+
controller.toggleProvider(group.id, true);
|
|
394
|
+
},
|
|
395
|
+
children: t("hideAll")
|
|
396
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
397
|
+
type: "button",
|
|
398
|
+
className: "mvw-mini",
|
|
399
|
+
disabled: !state.writable || groupVisible === group.models.length,
|
|
400
|
+
onClick: () => {
|
|
401
|
+
controller.toggleProvider(group.id, false);
|
|
402
|
+
},
|
|
403
|
+
children: t("showAll")
|
|
404
|
+
})]
|
|
405
|
+
})]
|
|
406
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
407
|
+
style: {
|
|
408
|
+
listStyle: "none",
|
|
409
|
+
margin: 0,
|
|
410
|
+
padding: 0
|
|
411
|
+
},
|
|
412
|
+
children: group.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
413
|
+
className: "mvw-row",
|
|
414
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
415
|
+
className: "mvw-name",
|
|
416
|
+
children: [model.name, model.name !== model.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
417
|
+
className: "mvw-id",
|
|
418
|
+
children: model.id
|
|
419
|
+
})]
|
|
420
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
421
|
+
on: !model.hidden,
|
|
422
|
+
disabled: !state.writable,
|
|
423
|
+
label: model.name,
|
|
424
|
+
onChange: (visible) => {
|
|
425
|
+
controller.toggle(group.id, model.id, model.name, !visible);
|
|
426
|
+
}
|
|
427
|
+
})]
|
|
428
|
+
}, model.id))
|
|
429
|
+
})]
|
|
430
|
+
}, group.id);
|
|
431
|
+
})
|
|
432
|
+
]
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
function Switch(props) {
|
|
436
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
437
|
+
type: "button",
|
|
438
|
+
role: "switch",
|
|
439
|
+
"aria-checked": props.on,
|
|
440
|
+
"aria-label": props.label,
|
|
441
|
+
disabled: props.disabled,
|
|
442
|
+
onClick: () => {
|
|
443
|
+
props.onChange(!props.on);
|
|
444
|
+
},
|
|
445
|
+
style: {
|
|
446
|
+
width: 40,
|
|
447
|
+
height: 22,
|
|
448
|
+
borderRadius: 11,
|
|
449
|
+
border: "none",
|
|
450
|
+
padding: 2,
|
|
451
|
+
cursor: props.disabled ? "not-allowed" : "pointer",
|
|
452
|
+
background: props.on ? "#4c8bf5" : "rgba(128,128,128,.45)",
|
|
453
|
+
opacity: props.disabled ? .5 : 1,
|
|
454
|
+
display: "inline-flex",
|
|
455
|
+
justifyContent: props.on ? "flex-end" : "flex-start",
|
|
456
|
+
transition: "background .15s",
|
|
457
|
+
flex: "none"
|
|
458
|
+
},
|
|
459
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
460
|
+
width: 18,
|
|
461
|
+
height: 18,
|
|
462
|
+
borderRadius: "50%",
|
|
463
|
+
background: "#fff",
|
|
464
|
+
display: "block"
|
|
465
|
+
} })
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/client/locales.ts
|
|
470
|
+
const LOCALE_NS = "settings.modelVisibility";
|
|
471
|
+
const zh = {
|
|
472
|
+
nav: "模型可见性",
|
|
473
|
+
title: "模型可见性",
|
|
474
|
+
intro: "控制各模型是否出现在对话界面的模型选择菜单中。隐藏一个正在使用的模型不会中断该会话,也不会改动 Provider 配置。",
|
|
475
|
+
loading: "正在读取模型目录…",
|
|
476
|
+
loadFailed: "模型目录读取失败:{message}",
|
|
477
|
+
retry: "重试",
|
|
478
|
+
empty: "没有任何 Provider 提供模型。",
|
|
479
|
+
allHiddenWarning: "所有模型都已隐藏,模型选择菜单将是空的。",
|
|
480
|
+
readOnly: "设置文档不可写,开关已禁用。",
|
|
481
|
+
visibleCount: "{visible} / {total} 可见",
|
|
482
|
+
searchPlaceholder: "搜索模型…",
|
|
483
|
+
noMatch: "没有匹配的模型。",
|
|
484
|
+
hideAll: "全部隐藏",
|
|
485
|
+
showAll: "全部显示",
|
|
486
|
+
showAllProviders: "恢复全部",
|
|
487
|
+
providerRow: "该渠道 {visible} / {total} 可见"
|
|
488
|
+
};
|
|
489
|
+
const en = {
|
|
490
|
+
nav: "Model visibility",
|
|
491
|
+
title: "Model visibility",
|
|
492
|
+
intro: "Control which models appear in the conversation model selector. Hiding a model a session already uses never interrupts that session or touches your provider config.",
|
|
493
|
+
loading: "Loading the model catalog…",
|
|
494
|
+
loadFailed: "Failed to load the model catalog: {message}",
|
|
495
|
+
retry: "Retry",
|
|
496
|
+
empty: "No provider advertises any model.",
|
|
497
|
+
allHiddenWarning: "Every model is hidden; the model selector will be empty.",
|
|
498
|
+
readOnly: "The settings document is read-only; toggles are disabled.",
|
|
499
|
+
visibleCount: "{visible} / {total} visible",
|
|
500
|
+
searchPlaceholder: "Search models…",
|
|
501
|
+
noMatch: "No model matches.",
|
|
502
|
+
hideAll: "Hide all",
|
|
503
|
+
showAll: "Show all",
|
|
504
|
+
showAllProviders: "Reset all",
|
|
505
|
+
providerRow: "{visible} / {total} visible in this provider"
|
|
506
|
+
};
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region src/client/index.ts
|
|
509
|
+
const name = "dsh-model-visibility";
|
|
510
|
+
const inject = [
|
|
511
|
+
"slots",
|
|
512
|
+
"locale",
|
|
513
|
+
"connection",
|
|
514
|
+
"settingsScope"
|
|
515
|
+
];
|
|
516
|
+
function apply(ctx) {
|
|
517
|
+
ctx.locale.register(LOCALE_NS, {
|
|
518
|
+
zh,
|
|
519
|
+
en
|
|
520
|
+
});
|
|
521
|
+
const controller = new ModelVisibilityCardController(ctx.settingsScope.bind({ namespace: "model-visibility" }), ctx.connection.api);
|
|
522
|
+
const t = ctx.locale.bind(LOCALE_NS);
|
|
523
|
+
const injected = () => ({
|
|
524
|
+
controller,
|
|
525
|
+
t,
|
|
526
|
+
hooks: { snapshot: controller.store }
|
|
527
|
+
});
|
|
528
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
529
|
+
name: "settings.section",
|
|
530
|
+
id: "model-visibility",
|
|
531
|
+
order: 11,
|
|
532
|
+
label: () => t("nav"),
|
|
533
|
+
inject: injected
|
|
534
|
+
}, ModelVisibilityCard));
|
|
535
|
+
}
|
|
536
|
+
//#endregion
|
|
537
|
+
exports.apply = apply;
|
|
538
|
+
exports.inject = inject;
|
|
539
|
+
exports.name = name;
|
|
540
|
+
return module.exports;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["createSnapshotStore","useState","useMemo"],"sources":["../src/client/controller.ts","../src/client/ModelVisibilityCard.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * The model-visibility card's controller: React-free state over two sources —\n * the (already filtered) Host model catalog and the `model-visibility`\n * settings scope. The rendered list is their union: catalog rows are visible\n * models; hidden entries restate the models the filter removed, labeled by\n * the name snapshot each entry carries. Beyond the reference shape, the\n * controller exposes provider-level batch writes and a whole-list write so\n * the card can offer search + bulk actions.\n */\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { IApiClient, ModelProviderGroup, RpcRequest } from '@deepseek-ai/dsh-client-connection/client'\nimport type { Config, HiddenModel } from '../index.ts'\n\n/** One toggle row. */\nexport interface ModelRow {\n id: string\n name: string\n hidden: boolean\n}\n\n/** One provider group of rows. */\nexport interface ProviderGroupRow {\n id: string\n name: string\n models: ModelRow[]\n}\n\n/** What the card renders. */\nexport interface ModelVisibilityState {\n phase: 'loading' | 'ready' | 'error'\n error?: string\n /** Whether the Host settings document accepts writes. */\n writable: boolean\n groups: ProviderGroupRow[]\n /** Union count and how many of them remain visible. */\n total: number\n visible: number\n}\n\n/** The face the slot registration injects into the card component. */\nexport interface ModelVisibilityFace {\n hooks: {\n /** Card snapshot bound by the renderer as useModelVisibility. */\n modelVisibility: SnapshotStore<ModelVisibilityState>\n }\n /** Flip one model's visibility. */\n toggle: (provider: string, model: string, name: string, hidden: boolean) => void\n /** Hide or show every model of one provider. */\n toggleProvider: (provider: string, hidden: boolean) => void\n /** Clear the hidden list entirely (everything visible again). */\n resetAll: () => void\n /** Re-read the catalog after a failure. */\n reload: () => void\n}\n\nconst EMPTY: ModelVisibilityState = { phase: 'loading', writable: false, groups: [], total: 0, visible: 0 }\n\n/** Mint one wire id (the brand is client-side nominal typing only). */\nfunction rpcRequest<P>(payload: P): RpcRequest<P> {\n return { rpcId: crypto.randomUUID(), payload } as RpcRequest<P>\n}\n\nexport class ModelVisibilityCardController {\n /** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */\n readonly store = createSnapshotStore<ModelVisibilityState>(EMPTY)\n private catalog: ModelProviderGroup[] | undefined\n private loadError: string | undefined\n private generation = 0\n\n constructor(\n private readonly scope: SettingsScope<Config>,\n private readonly api: Pick<IApiClient, 'llm'>,\n ) {\n scope.subscribe(() => { this.recompute() })\n void this.load()\n }\n\n /** The registration-side inject face. */\n face(): ModelVisibilityFace {\n return {\n hooks: { modelVisibility: this.store },\n toggle: (provider, model, name, hidden) => { void this.toggle(provider, model, name, hidden) },\n toggleProvider: (provider, hidden) => { void this.toggleProvider(provider, hidden) },\n resetAll: () => { void this.resetAll() },\n reload: () => { void this.load() },\n }\n }\n\n /** Re-read the catalog; an older response never overwrites a newer one. */\n async load(): Promise<void> {\n const generation = ++this.generation\n if (this.catalog === undefined) this.store.update((draft) => { draft.phase = 'loading' })\n try {\n const response = await this.api.llm.models(rpcRequest({}))\n if (generation !== this.generation) return\n if (response.result.ok) {\n this.catalog = response.result.value.groups\n this.loadError = undefined\n } else {\n this.loadError = response.result.error.message\n }\n } catch (error: unknown) {\n if (generation !== this.generation) return\n this.loadError = error instanceof Error ? error.message : String(error)\n }\n this.recompute()\n }\n\n /** Compute the next hidden list from the current one plus one model's flip. */\n private nextHidden(current: HiddenModel[], provider: string, model: string, name: string, hidden: boolean): HiddenModel[] {\n const rest = current.filter(entry => !(entry.provider === provider && entry.model === model))\n return hidden ? [...rest, { provider, model, name }] : rest\n }\n\n /** Write the next hidden list, then re-pull the catalog the filter changed. */\n private async write(next: HiddenModel[]): Promise<void> {\n try {\n await this.scope.set('hidden', next)\n } catch {\n // The scope already reloads Host state after a failed latest write;\n // recompute below re-renders whatever the Host now reports.\n }\n await this.load()\n }\n\n /** Flip one model's visibility (public API — the section card calls it directly). */\n async toggle(provider: string, model: string, name: string, hidden: boolean): Promise<void> {\n const current = this.scope.getSnapshot().value?.hidden ?? []\n await this.write(this.nextHidden(current, provider, model, name, hidden))\n }\n\n /**\n * Batch flip for one provider. Models are taken from the union of the\n * (filtered) catalog rows and existing hidden entries so a provider with\n * every model already hidden still flips back fully.\n */\n /** Batch flip for one provider (public API). */\n async toggleProvider(provider: string, hidden: boolean): Promise<void> {\n const current = this.scope.getSnapshot().value?.hidden ?? []\n const providerRows = this.stateRows().find(group => group.id === provider)\n const ids = new Set<string>([\n ...providerRows ? providerRows.models.map(model => model.id) : [],\n ...current.filter(entry => entry.provider === provider).map(entry => entry.model),\n ])\n if (ids.size === 0) return\n let next = current.filter(entry => entry.provider !== provider)\n if (hidden) {\n const nameOf = (id: string): string => providerRows?.models.find(model => model.id === id)?.name ?? id\n next = [...next, ...[...ids].map(id => ({ provider, model: id, name: nameOf(id) }))]\n }\n await this.write(next)\n }\n\n /** Clear the hidden list entirely (public API). */\n async resetAll(): Promise<void> {\n await this.write([])\n }\n\n /** Current union rows (catalog ∪ hidden) without touching the store. */\n private stateRows(): ProviderGroupRow[] {\n const snapshot = this.scope.getSnapshot()\n const hidden = snapshot.value?.hidden ?? []\n if (this.catalog === undefined) return []\n const hiddenKeys = new Set(hidden.map(entry => `${entry.provider}\\u0000${entry.model}`))\n const groups: ProviderGroupRow[] = this.catalog.map(group => ({\n id: group.id,\n name: group.name,\n models: group.models.map(model => ({\n id: model.id,\n name: model.name,\n hidden: hiddenKeys.has(`${group.id}\\u0000${model.id}`),\n })),\n }))\n const groupsById = new Map(groups.map(group => [group.id, group]))\n for (const entry of hidden) {\n const group = groupsById.get(entry.provider)\n if (group?.models.some(model => model.id === entry.model)) continue\n const row: ModelRow = { id: entry.model, name: entry.name ?? entry.model, hidden: true }\n if (group !== undefined) {\n group.models.push(row)\n } else {\n const created: ProviderGroupRow = { id: entry.provider, name: entry.provider, models: [row] }\n groupsById.set(entry.provider, created)\n groups.push(created)\n }\n }\n return groups\n }\n\n /** Union the filtered catalog with the hidden entries into render rows. */\n private recompute(): void {\n const snapshot = this.scope.getSnapshot()\n if (this.catalog === undefined && this.loadError !== undefined) {\n this.store.set({ ...EMPTY, phase: 'error', error: this.loadError, writable: snapshot.writable })\n return\n }\n if (this.catalog === undefined || snapshot.status === 'loading') {\n this.store.set({ ...EMPTY, writable: snapshot.writable })\n return\n }\n const groups = this.stateRows()\n const total = groups.reduce((count, group) => count + group.models.length, 0)\n const visible = groups.reduce(\n (count, group) => count + group.models.filter(model => !model.hidden).length, 0)\n this.store.set({\n phase: 'ready',\n writable: snapshot.writable,\n groups,\n total,\n visible,\n })\n }\n}\n","/**\n * The model-visibility settings section, styled to mirror the official\n * settings pages: big page title + muted intro, a controls row, then one\n * bordered rounded card per provider (like the provider cards on the\n * 模型 page) with pill action buttons and token-style switches.\n *\n * Consumes the `settings.section` contribution contract exactly like the\n * official ModelsSection: props = spread inject face, where `hooks.snapshot`\n * arrives as the `useSnapshot` selector hook; all three seats are undefined\n * while the shell has not injected yet (render null then).\n */\nimport { useMemo, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { ModelVisibilityCardController, ModelVisibilityState } from './controller.ts'\nimport type {} from './locales.ts'\n\n/** Composed props: spread face fields + the `useSnapshot` hook from hooks.snapshot. */\nexport interface ModelVisibilityCardProps {\n controller: ModelVisibilityCardController | undefined\n useSnapshot: SnapshotSelectorHook<ModelVisibilityState> | undefined\n t: TSeat | undefined\n}\n\n/** Localized text seat bound to this card's namespace. */\ntype TSeat = (key: string, params?: Record<string, unknown>) => string\n\nconst STYLE_ID = 'dsh-model-visibility-styles'\n\nconst STYLES = `\n.dsh-mvw { display:flex; flex-direction:column; gap:16px; }\n.dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }\n.dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }\n.dsh-mvw .mvw-controls { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:4px; }\n.dsh-mvw .mvw-search {\n flex:1 1 220px; min-width:160px; height:38px; padding:0 14px;\n font-size:13px; border-radius:10px;\n border:1px solid rgba(128,128,128,.38);\n background:transparent; color:inherit; outline:none;\n}\n.dsh-mvw .mvw-search:focus { border-color: rgba(76,139,245,.85); }\n.dsh-mvw .mvw-pill {\n height:36px; padding:0 16px; font-size:13px; cursor:pointer;\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\n background:transparent; color:inherit;\n}\n.dsh-mvw .mvw-pill:hover:not(:disabled) { background:rgba(128,128,128,.08); }\n.dsh-mvw .mvw-pill:disabled { opacity:.45; cursor:not-allowed; }\n.dsh-mvw .mvw-count { font-size:12.5px; opacity:.62; margin-left:auto; }\n.dsh-mvw .mvw-card {\n border:1px solid rgba(128,128,128,.30); border-radius:16px;\n padding:6px 18px 2px;\n}\n.dsh-mvw .mvw-prov-head {\n display:flex; align-items:center; justify-content:space-between; gap:10px;\n padding:12px 0 10px;\n}\n.dsh-mvw .mvw-prov-name { font-size:15px; font-weight:650; }\n.dsh-mvw .mvw-prov-count { font-size:12px; opacity:.55; margin-left:10px; font-weight:400; }\n.dsh-mvw .mvw-prov-btns { display:flex; gap:8px; }\n.dsh-mvw .mvw-mini {\n height:30px; padding:0 13px; font-size:12.5px; cursor:pointer;\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\n background:transparent; color:inherit;\n}\n.dsh-mvw .mvw-mini:hover:not(:disabled) { background:rgba(128,128,128,.08); }\n.dsh-mvw .mvw-mini:disabled { opacity:.4; cursor:not-allowed; }\n.dsh-mvw .mvw-row {\n display:flex; align-items:center; justify-content:space-between; gap:12px;\n min-height:46px; border-top:1px solid rgba(128,128,128,.16);\n}\n.dsh-mvw .mvw-row:first-of-type { border-top:none; }\n.dsh-mvw .mvw-name {\n font-size:14px;\n min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\n}\n.dsh-mvw .mvw-id {\n opacity:.5; margin-left:8px; padding-right:6px; font-size:12px;\n}\n.dsh-mvw .mvw-note { font-size:12.5px; opacity:.66; }\n.dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }\n.dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }\n`\n\nfunction ensureStyles(): void {\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID)) return\n const el = document.createElement('style')\n el.id = STYLE_ID\n el.textContent = STYLES\n document.head.appendChild(el)\n}\n\nexport function ModelVisibilityCard(props: ModelVisibilityCardProps) {\n const { controller, useSnapshot, t } = props\n\n // Hooks stay unconditional regardless of injection timing.\n const state = (useSnapshot ?? ((_selector: (s: ModelVisibilityState) => unknown) => undefined))(\n (snapshot: ModelVisibilityState) => snapshot,\n ) as ModelVisibilityState | undefined\n const [query, setQuery] = useState('')\n\n // Styles must exist before first paint of this subtree; guarded, so idempotent.\n useMemo(() => { ensureStyles() }, [])\n\n const filteredGroups = useMemo(() => {\n const q = query.trim().toLowerCase()\n if (q === '') return state?.groups ?? []\n return (state?.groups ?? [])\n .map(group => ({\n ...group,\n models: group.models.filter(model =>\n model.id.toLowerCase().includes(q) || model.name.toLowerCase().includes(q) || group.id.toLowerCase().includes(q)),\n }))\n .filter(group => group.models.length > 0)\n }, [state, query])\n\n if (controller === undefined || useSnapshot === undefined || t === undefined) return null\n if (state === undefined) {\n return (\n <section className=\"dsh-mvw\">\n <div className=\"mvw-note\">{t('loading')}</div>\n </section>\n )\n }\n\n return (\n <section className=\"dsh-mvw\">\n <div>\n <div className=\"mvw-title\">{t('title')}</div>\n <p className=\"mvw-sub\">{t('intro')}</p>\n </div>\n\n <div className=\"mvw-controls\">\n <input\n type=\"search\"\n className=\"mvw-search\"\n value={query}\n placeholder={t('searchPlaceholder')}\n onChange={event => { setQuery(event.target.value) }}\n />\n <button\n type=\"button\"\n className=\"mvw-pill\"\n disabled={!state.writable || state.total === state.visible}\n onClick={() => { controller.resetAll() }}\n >\n {t('showAllProviders')}\n </button>\n <span className=\"mvw-count\">{t('visibleCount', { visible: state.visible, total: state.total })}</span>\n </div>\n\n {state.phase === 'error' && (\n <div className=\"mvw-warn\">\n {t('loadFailed', { message: state.error ?? '' })}{' '}\n <button type=\"button\" className=\"mvw-mini\" onClick={() => { controller.load() }}>{t('retry')}</button>\n </div>\n )}\n\n {state.phase === 'ready' && !state.writable && <div className=\"mvw-note\">{t('readOnly')}</div>}\n {state.phase === 'ready' && state.groups.length === 0 && <div className=\"mvw-note\">{t('empty')}</div>}\n {state.phase === 'ready' && state.total > 0 && state.visible === 0 && (\n <div className=\"mvw-warn\">{t('allHiddenWarning')}</div>\n )}\n {state.phase === 'ready' && filteredGroups.length === 0 && state.groups.length > 0 && (\n <div className=\"mvw-note\">{t('noMatch')}</div>\n )}\n\n {state.phase === 'ready' && filteredGroups.map(group => {\n const groupVisible = group.models.filter(model => !model.hidden).length\n return (\n <div key={group.id} className=\"mvw-card\">\n <div className=\"mvw-prov-head\">\n <div>\n <span className=\"mvw-prov-name\">{group.name}</span>\n <span className=\"mvw-prov-count\">\n {t('providerRow', { visible: groupVisible, total: group.models.length })}\n </span>\n </div>\n <div className=\"mvw-prov-btns\">\n <button\n type=\"button\"\n className=\"mvw-mini\"\n disabled={!state.writable || groupVisible === 0}\n onClick={() => { controller.toggleProvider(group.id, true) }}\n >\n {t('hideAll')}\n </button>\n <button\n type=\"button\"\n className=\"mvw-mini\"\n disabled={!state.writable || groupVisible === group.models.length}\n onClick={() => { controller.toggleProvider(group.id, false) }}\n >\n {t('showAll')}\n </button>\n </div>\n </div>\n <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>\n {group.models.map(model => (\n <li key={model.id} className=\"mvw-row\">\n <span className=\"mvw-name\">\n {model.name}\n {model.name !== model.id && <span className=\"mvw-id\">{model.id}</span>}\n </span>\n <Switch\n on={!model.hidden}\n disabled={!state.writable}\n label={model.name}\n onChange={visible => { controller.toggle(group.id, model.id, model.name, !visible) }}\n />\n </li>\n ))}\n </ul>\n </div>\n )\n })}\n </section>\n )\n}\n\nfunction Switch(props: { on: boolean; disabled: boolean; label: string; onChange: (next: boolean) => void }) {\n return (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={props.on}\n aria-label={props.label}\n disabled={props.disabled}\n onClick={() => { props.onChange(!props.on) }}\n style={{\n width: 40,\n height: 22,\n borderRadius: 11,\n border: 'none',\n padding: 2,\n cursor: props.disabled ? 'not-allowed' : 'pointer',\n background: props.on ? '#4c8bf5' : 'rgba(128,128,128,.45)',\n opacity: props.disabled ? 0.5 : 1,\n display: 'inline-flex',\n justifyContent: props.on ? 'flex-end' : 'flex-start',\n transition: 'background .15s',\n flex: 'none',\n }}\n >\n <span style={{ width: 18, height: 18, borderRadius: '50%', background: '#fff', display: 'block' }} />\n </button>\n )\n}\n","/**\n * Locale dictionaries for the model-visibility settings card.\n * The namespace is merged into the slot system's LocaleNamespaceMap so the\n * register site can declare `locale:` and receive the typed `t` seat.\n */\n\n/** Dictionary keys of this card's copy. */\nexport type ModelVisibilityLocaleKey =\n | 'nav'\n | 'title'\n | 'intro'\n | 'loading'\n | 'loadFailed'\n | 'retry'\n | 'empty'\n | 'allHiddenWarning'\n | 'readOnly'\n | 'visibleCount'\n | 'searchPlaceholder'\n | 'noMatch'\n | 'hideAll'\n | 'showAll'\n | 'showAllProviders'\n | 'providerRow'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The model-visibility card's copy. */\n 'settings.modelVisibility': ModelVisibilityLocaleKey\n }\n}\n\nexport const LOCALE_NS = 'settings.modelVisibility' as const\n\nexport const zh: Record<ModelVisibilityLocaleKey, string> = {\n nav: '模型可见性',\n title: '模型可见性',\n intro: '控制各模型是否出现在对话界面的模型选择菜单中。隐藏一个正在使用的模型不会中断该会话,也不会改动 Provider 配置。',\n loading: '正在读取模型目录…',\n loadFailed: '模型目录读取失败:{message}',\n retry: '重试',\n empty: '没有任何 Provider 提供模型。',\n allHiddenWarning: '所有模型都已隐藏,模型选择菜单将是空的。',\n readOnly: '设置文档不可写,开关已禁用。',\n visibleCount: '{visible} / {total} 可见',\n searchPlaceholder: '搜索模型…',\n noMatch: '没有匹配的模型。',\n hideAll: '全部隐藏',\n showAll: '全部显示',\n showAllProviders: '恢复全部',\n providerRow: '该渠道 {visible} / {total} 可见',\n}\n\nexport const en: Record<ModelVisibilityLocaleKey, string> = {\n nav: 'Model visibility',\n title: 'Model visibility',\n intro: 'Control which models appear in the conversation model selector. Hiding a model a session already uses never interrupts that session or touches your provider config.',\n loading: 'Loading the model catalog…',\n loadFailed: 'Failed to load the model catalog: {message}',\n retry: 'Retry',\n empty: 'No provider advertises any model.',\n allHiddenWarning: 'Every model is hidden; the model selector will be empty.',\n readOnly: 'The settings document is read-only; toggles are disabled.',\n visibleCount: '{visible} / {total} visible',\n searchPlaceholder: 'Search models…',\n noMatch: 'No model matches.',\n hideAll: 'Hide all',\n showAll: 'Show all',\n showAllProviders: 'Reset all',\n providerRow: '{visible} / {total} visible in this provider',\n}\n","/**\n * dsh-model-visibility — browser half.\n *\n * Registers the model-visibility card into the plugin settings section\n * (`settings.plugin.item`) and its locale dictionaries. Cross-plugin\n * collaboration goes through cordis services only: the slot key's\n * declaration, the settings scope, and the wire handle arrive as type-only\n * merges, never value imports (the loader bundle-purity rule).\n */\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only merges: ctx.settingsScope, ctx.locale, ctx.slots, and the\n// `settings.plugin.item` slot declaration.\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-runtime/client'\nimport type { Config } from '../index.ts'\nimport { ModelVisibilityCardController } from './controller.ts'\nimport { ModelVisibilityCard } from './ModelVisibilityCard.tsx'\nimport { LOCALE_NS, en, zh } from './locales.ts'\n\n// The published /client types omit the browser-side merge for\n// `ctx.connection` (the ConnectionHandle the connection plugin provides);\n// restate it locally until the package ships it.\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n connection: ConnectionHandle\n }\n}\n\nexport const name = 'dsh-model-visibility'\nexport const inject = ['slots', 'locale', 'connection', 'settingsScope'] as const\n\nexport function apply(ctx: Context): void {\n ctx.locale.register(LOCALE_NS, { zh, en })\n\n const scope = ctx.settingsScope.bind<Config>({ namespace: 'model-visibility' })\n const controller = new ModelVisibilityCardController(scope, ctx.connection.api)\n // The section card consumes the locale seat through the inject face\n // (mirroring the official Models page, whose face carries `t`).\n const t = ctx.locale.bind(LOCALE_NS)\n\n // Register as a top-level settings SECTION (nav entry 「模型可见性」, ordered\n // right after the official 模型 page at order=10) instead of a tab inside\n // the Plugins section — the card is a whole-page surface (search + batch\n // controls read better with room to breathe). The label thunk re-binds the\n // locale each call so a host language switch shows fresh text.\n //\n // Face contract for `settings.section` contributions (ui-slots): every\n // field of the injected object spreads onto the component as props, and\n // each `hooks` entry becomes a `use<Name>` snapshot-selector hook. The\n // component destructures { controller, useSnapshot, t } exactly like the\n // official ModelsSection does.\n const injected = () => ({\n controller,\n t,\n hooks: { snapshot: controller.store },\n })\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'model-visibility',\n order: 11,\n label: () => t('nav'),\n inject: injected,\n } as never, ModelVisibilityCard as never))\n}\n"],"mappings":"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"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
//#region src/index.ts
|
|
4
|
+
const name = "dsh-model-visibility";
|
|
5
|
+
const inject = ["llm"];
|
|
6
|
+
/** The settings namespace both halves address (the card keys on it too). */
|
|
7
|
+
const MODEL_VISIBILITY_NS = settingsNamespace("model-visibility");
|
|
8
|
+
const Config = z.object({ hidden: z.array(z.object({
|
|
9
|
+
provider: z.string().required(),
|
|
10
|
+
model: z.string().required(),
|
|
11
|
+
name: z.string()
|
|
12
|
+
})).default([]) });
|
|
13
|
+
function apply(ctx, config) {
|
|
14
|
+
let source = () => config;
|
|
15
|
+
installSettingsSection(ctx, MODEL_VISIBILITY_NS, Config, config, {
|
|
16
|
+
setSource: (current) => {
|
|
17
|
+
source = current;
|
|
18
|
+
},
|
|
19
|
+
onChange: () => {}
|
|
20
|
+
});
|
|
21
|
+
const llm = ctx.llm;
|
|
22
|
+
const original = llm.listModels.bind(llm);
|
|
23
|
+
ctx.effect(() => {
|
|
24
|
+
Object.defineProperty(llm, "listModels", {
|
|
25
|
+
value: async (provider) => {
|
|
26
|
+
const models = await original(provider);
|
|
27
|
+
const hidden = new Set(source().hidden.filter((entry) => entry.provider === provider).map((entry) => entry.model));
|
|
28
|
+
if (hidden.size === 0) return models;
|
|
29
|
+
return models.filter((model) => !hidden.has(model.id));
|
|
30
|
+
},
|
|
31
|
+
configurable: true,
|
|
32
|
+
writable: true
|
|
33
|
+
});
|
|
34
|
+
return () => {
|
|
35
|
+
delete llm.listModels;
|
|
36
|
+
};
|
|
37
|
+
}, "model-visibility: listModels catalog filter");
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { Config, MODEL_VISIBILITY_NS, apply, inject, name };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import type { ModelVisibilityCardController, ModelVisibilityState } from './controller.ts';
|
|
3
|
+
/** Composed props: spread face fields + the `useSnapshot` hook from hooks.snapshot. */
|
|
4
|
+
export interface ModelVisibilityCardProps {
|
|
5
|
+
controller: ModelVisibilityCardController | undefined;
|
|
6
|
+
useSnapshot: SnapshotSelectorHook<ModelVisibilityState> | undefined;
|
|
7
|
+
t: TSeat | undefined;
|
|
8
|
+
}
|
|
9
|
+
/** Localized text seat bound to this card's namespace. */
|
|
10
|
+
type TSeat = (key: string, params?: Record<string, unknown>) => string;
|
|
11
|
+
export declare function ModelVisibilityCard(props: ModelVisibilityCardProps): import("react").JSX.Element | null;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { SettingsScope, SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
import type { IApiClient } from '@deepseek-ai/dsh-client-connection/client';
|
|
3
|
+
import type { Config } from '../index.ts';
|
|
4
|
+
/** One toggle row. */
|
|
5
|
+
export interface ModelRow {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
hidden: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** One provider group of rows. */
|
|
11
|
+
export interface ProviderGroupRow {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
models: ModelRow[];
|
|
15
|
+
}
|
|
16
|
+
/** What the card renders. */
|
|
17
|
+
export interface ModelVisibilityState {
|
|
18
|
+
phase: 'loading' | 'ready' | 'error';
|
|
19
|
+
error?: string;
|
|
20
|
+
/** Whether the Host settings document accepts writes. */
|
|
21
|
+
writable: boolean;
|
|
22
|
+
groups: ProviderGroupRow[];
|
|
23
|
+
/** Union count and how many of them remain visible. */
|
|
24
|
+
total: number;
|
|
25
|
+
visible: number;
|
|
26
|
+
}
|
|
27
|
+
/** The face the slot registration injects into the card component. */
|
|
28
|
+
export interface ModelVisibilityFace {
|
|
29
|
+
hooks: {
|
|
30
|
+
/** Card snapshot bound by the renderer as useModelVisibility. */
|
|
31
|
+
modelVisibility: SnapshotStore<ModelVisibilityState>;
|
|
32
|
+
};
|
|
33
|
+
/** Flip one model's visibility. */
|
|
34
|
+
toggle: (provider: string, model: string, name: string, hidden: boolean) => void;
|
|
35
|
+
/** Hide or show every model of one provider. */
|
|
36
|
+
toggleProvider: (provider: string, hidden: boolean) => void;
|
|
37
|
+
/** Clear the hidden list entirely (everything visible again). */
|
|
38
|
+
resetAll: () => void;
|
|
39
|
+
/** Re-read the catalog after a failure. */
|
|
40
|
+
reload: () => void;
|
|
41
|
+
}
|
|
42
|
+
export declare class ModelVisibilityCardController {
|
|
43
|
+
private readonly scope;
|
|
44
|
+
private readonly api;
|
|
45
|
+
/** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */
|
|
46
|
+
readonly store: SnapshotStore<ModelVisibilityState>;
|
|
47
|
+
private catalog;
|
|
48
|
+
private loadError;
|
|
49
|
+
private generation;
|
|
50
|
+
constructor(scope: SettingsScope<Config>, api: Pick<IApiClient, 'llm'>);
|
|
51
|
+
/** The registration-side inject face. */
|
|
52
|
+
face(): ModelVisibilityFace;
|
|
53
|
+
/** Re-read the catalog; an older response never overwrites a newer one. */
|
|
54
|
+
load(): Promise<void>;
|
|
55
|
+
/** Compute the next hidden list from the current one plus one model's flip. */
|
|
56
|
+
private nextHidden;
|
|
57
|
+
/** Write the next hidden list, then re-pull the catalog the filter changed. */
|
|
58
|
+
private write;
|
|
59
|
+
/** Flip one model's visibility (public API — the section card calls it directly). */
|
|
60
|
+
toggle(provider: string, model: string, name: string, hidden: boolean): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* Batch flip for one provider. Models are taken from the union of the
|
|
63
|
+
* (filtered) catalog rows and existing hidden entries so a provider with
|
|
64
|
+
* every model already hidden still flips back fully.
|
|
65
|
+
*/
|
|
66
|
+
/** Batch flip for one provider (public API). */
|
|
67
|
+
toggleProvider(provider: string, hidden: boolean): Promise<void>;
|
|
68
|
+
/** Clear the hidden list entirely (public API). */
|
|
69
|
+
resetAll(): Promise<void>;
|
|
70
|
+
/** Current union rows (catalog ∪ hidden) without touching the store. */
|
|
71
|
+
private stateRows;
|
|
72
|
+
/** Union the filtered catalog with the hidden entries into render rows. */
|
|
73
|
+
private recompute;
|
|
74
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-model-visibility — browser half.
|
|
3
|
+
*
|
|
4
|
+
* Registers the model-visibility card into the plugin settings section
|
|
5
|
+
* (`settings.plugin.item`) and its locale dictionaries. Cross-plugin
|
|
6
|
+
* collaboration goes through cordis services only: the slot key's
|
|
7
|
+
* declaration, the settings scope, and the wire handle arrive as type-only
|
|
8
|
+
* merges, never value imports (the loader bundle-purity rule).
|
|
9
|
+
*/
|
|
10
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
+
import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client';
|
|
12
|
+
declare module '@deepseek-ai/cordis' {
|
|
13
|
+
interface Context {
|
|
14
|
+
connection: ConnectionHandle;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export declare const name = "dsh-model-visibility";
|
|
18
|
+
export declare const inject: readonly ["slots", "locale", "connection", "settingsScope"];
|
|
19
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale dictionaries for the model-visibility settings card.
|
|
3
|
+
* The namespace is merged into the slot system's LocaleNamespaceMap so the
|
|
4
|
+
* register site can declare `locale:` and receive the typed `t` seat.
|
|
5
|
+
*/
|
|
6
|
+
/** Dictionary keys of this card's copy. */
|
|
7
|
+
export type ModelVisibilityLocaleKey = 'nav' | 'title' | 'intro' | 'loading' | 'loadFailed' | 'retry' | 'empty' | 'allHiddenWarning' | 'readOnly' | 'visibleCount' | 'searchPlaceholder' | 'noMatch' | 'hideAll' | 'showAll' | 'showAllProviders' | 'providerRow';
|
|
8
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
9
|
+
interface LocaleNamespaceMap {
|
|
10
|
+
/** The model-visibility card's copy. */
|
|
11
|
+
'settings.modelVisibility': ModelVisibilityLocaleKey;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export declare const LOCALE_NS: "settings.modelVisibility";
|
|
15
|
+
export declare const zh: Record<ModelVisibilityLocaleKey, string>;
|
|
16
|
+
export declare const en: Record<ModelVisibilityLocaleKey, string>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-model-visibility — Host half.
|
|
3
|
+
*
|
|
4
|
+
* Owns the `model-visibility` settings namespace (the hidden-model list) and
|
|
5
|
+
* filters the model catalog at its source: `ctx.llm.listModels` is wrapped
|
|
6
|
+
* so both `session.models` (the conversation selector) and `llm.models`
|
|
7
|
+
* (the settings page) stop advertising hidden models. Catalog membership
|
|
8
|
+
* is advisory in the harness, so hiding a model a session already uses
|
|
9
|
+
* never breaks that session's dispatch.
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
import z from '@deepseek-ai/schemastery';
|
|
13
|
+
export declare const name = "dsh-model-visibility";
|
|
14
|
+
export declare const inject: readonly ["llm"];
|
|
15
|
+
/** The settings namespace both halves address (the card keys on it too). */
|
|
16
|
+
export declare const MODEL_VISIBILITY_NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
|
|
17
|
+
/** One hidden model: the provider/model route pair plus a display-name snapshot. */
|
|
18
|
+
export interface HiddenModel {
|
|
19
|
+
provider: string;
|
|
20
|
+
model: string;
|
|
21
|
+
/** Display name captured when hidden, so the card can label the row after the catalog stops advertising it. */
|
|
22
|
+
name?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface Config {
|
|
25
|
+
/** Models excluded from the model-selector catalog; empty means all visible. */
|
|
26
|
+
hidden: HiddenModel[];
|
|
27
|
+
}
|
|
28
|
+
export declare const Config: z<Config>;
|
|
29
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-model-visibility",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DeepSeek Harness plugin: control which models appear in the model selector — per-model switches, provider-level batch toggles, and search. Hiding a model never touches your provider config or running sessions.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/types/client/index.d.ts",
|
|
15
|
+
"default": "./lib/client.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib/index.js",
|
|
21
|
+
"lib/client.js",
|
|
22
|
+
"lib/client.js.map",
|
|
23
|
+
"lib/types/**/*.d.ts",
|
|
24
|
+
"cordis.patch.yml",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md",
|
|
27
|
+
"README.en.md"
|
|
28
|
+
],
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"platform": "web",
|
|
35
|
+
"inject": [
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.json && tsdown",
|
|
42
|
+
"prepare": "tsc -p tsconfig.json && tsdown",
|
|
43
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@deepseek-ai/dsh-settings": "0.0.1-rc.5",
|
|
50
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
54
|
+
"@deepseek-ai/dsh-client-connection": "0.0.1-rc.5",
|
|
55
|
+
"@deepseek-ai/dsh-client-locale": "0.0.1-rc.5",
|
|
56
|
+
"@deepseek-ai/dsh-client-runtime": "0.0.1-rc.5",
|
|
57
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.5",
|
|
58
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.0.1-rc.5",
|
|
59
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "0.0.1-rc.3",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.5",
|
|
61
|
+
"@deepseek-ai/dsh-llm": "0.0.1-rc.5",
|
|
62
|
+
"@types/react": "^18.3.0",
|
|
63
|
+
"react": "^18.3.0",
|
|
64
|
+
"tsdown": "^0.12.0",
|
|
65
|
+
"typescript": "^5.7.0"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public"
|
|
69
|
+
},
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "git+https://github.com/Lzh3070/dsh-model-visibility.git"
|
|
73
|
+
},
|
|
74
|
+
"keywords": [
|
|
75
|
+
"dsh",
|
|
76
|
+
"dsh-plugin",
|
|
77
|
+
"deepseek-harness",
|
|
78
|
+
"model",
|
|
79
|
+
"visibility",
|
|
80
|
+
"selector"
|
|
81
|
+
],
|
|
82
|
+
"license": "MIT"
|
|
83
|
+
}
|