dsh-model-visibility 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/lib/client.js +660 -487
- package/lib/client.js.map +1 -1
- package/lib/index.js +13 -12
- package/lib/types/client/controller.d.ts +10 -38
- package/lib/types/client/index.d.ts +6 -1
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/client/settings-nav-icon.d.ts +77 -0
- package/lib/types/index.d.ts +34 -10
- package/lib/types/namespace.d.ts +10 -0
- package/package.json +36 -34
package/lib/client.js
CHANGED
|
@@ -1,225 +1,237 @@
|
|
|
1
|
-
window.__ModuleLoader__.load({
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-model-visibility", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
const __deepseek_ai_dsh_client_store = __toESM(require("@deepseek-ai/dsh-client-store"));
|
|
27
|
+
const react = __toESM(require("react"));
|
|
28
|
+
const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
|
|
29
|
+
|
|
30
|
+
//#region src/namespace.ts
|
|
31
|
+
const MODEL_VISIBILITY_NS = "model-visibility";
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/client/controller.ts
|
|
35
|
+
const EMPTY = {
|
|
36
|
+
phase: "loading",
|
|
37
|
+
writable: false,
|
|
38
|
+
groups: [],
|
|
39
|
+
total: 0,
|
|
40
|
+
visible: 0
|
|
41
|
+
};
|
|
42
|
+
var ModelVisibilityCardController = class {
|
|
43
|
+
/** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */
|
|
44
|
+
store = (0, __deepseek_ai_dsh_client_store.createSnapshotStore)(EMPTY);
|
|
45
|
+
catalog;
|
|
46
|
+
loadError;
|
|
47
|
+
generation = 0;
|
|
48
|
+
unsubscribe;
|
|
49
|
+
constructor(form, catalogReader) {
|
|
50
|
+
this.form = form;
|
|
51
|
+
this.catalogReader = catalogReader;
|
|
52
|
+
this.unsubscribe = form.subscribe(() => {
|
|
53
|
+
this.recompute();
|
|
54
|
+
});
|
|
55
|
+
this.load();
|
|
56
|
+
}
|
|
57
|
+
/** The registration-side inject face. */
|
|
58
|
+
face() {
|
|
59
|
+
return {
|
|
60
|
+
hooks: { modelVisibility: this.store },
|
|
61
|
+
toggle: (provider, model, name$1, hidden) => {
|
|
62
|
+
this.toggle(provider, model, name$1, hidden);
|
|
63
|
+
},
|
|
64
|
+
toggleProvider: (provider, hidden) => {
|
|
65
|
+
this.toggleProvider(provider, hidden);
|
|
66
|
+
},
|
|
67
|
+
resetAll: () => {
|
|
68
|
+
this.resetAll();
|
|
69
|
+
},
|
|
70
|
+
reload: () => {
|
|
50
71
|
this.load();
|
|
51
72
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Re-read the catalog; an older response never overwrites a newer one. */
|
|
76
|
+
async load() {
|
|
77
|
+
const generation = ++this.generation;
|
|
78
|
+
if (this.catalog === void 0) this.store.update((draft) => {
|
|
79
|
+
draft.phase = "loading";
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
const groups = await this.catalogReader.read();
|
|
83
|
+
if (generation !== this.generation) return;
|
|
84
|
+
this.catalog = groups.map((group) => ({
|
|
85
|
+
id: group.id,
|
|
86
|
+
name: group.name,
|
|
87
|
+
models: group.models.map((model) => ({
|
|
88
|
+
id: model.id,
|
|
89
|
+
name: model.name
|
|
90
|
+
}))
|
|
91
|
+
}));
|
|
92
|
+
this.loadError = void 0;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (generation !== this.generation) return;
|
|
95
|
+
this.loadError = error instanceof Error ? error.message : String(error);
|
|
96
|
+
}
|
|
97
|
+
this.recompute();
|
|
98
|
+
}
|
|
99
|
+
/** Stop following the settings form (plugin unload). */
|
|
100
|
+
dispose() {
|
|
101
|
+
this.unsubscribe();
|
|
102
|
+
}
|
|
103
|
+
/** Compute the next hidden list from the current one plus one model's flip. */
|
|
104
|
+
nextHidden(current, provider, model, name$1, hidden) {
|
|
105
|
+
const rest = current.filter((entry) => !(entry.provider === provider && entry.model === model));
|
|
106
|
+
return hidden ? [...rest, {
|
|
107
|
+
provider,
|
|
108
|
+
model,
|
|
109
|
+
name: name$1
|
|
110
|
+
}] : rest;
|
|
111
|
+
}
|
|
112
|
+
/** Write the next hidden list, then re-pull the catalog the filter changed. */
|
|
113
|
+
async write(next) {
|
|
114
|
+
try {
|
|
115
|
+
await this.form.set("hidden", next);
|
|
116
|
+
} catch {}
|
|
117
|
+
await this.load();
|
|
118
|
+
}
|
|
119
|
+
/** Flip one model's visibility (public API — the section card calls it directly). */
|
|
120
|
+
async toggle(provider, model, name$1, hidden) {
|
|
121
|
+
const current = this.form.getSnapshot().value?.hidden ?? [];
|
|
122
|
+
await this.write(this.nextHidden(current, provider, model, name$1, hidden));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
|
|
126
|
+
* Batch flip for one provider. Models are taken from the union of the
|
|
127
|
+
|
|
128
|
+
* (filtered) catalog rows and existing hidden entries so a provider with
|
|
129
|
+
|
|
130
|
+
* every model already hidden still flips back fully.
|
|
131
|
+
|
|
132
|
+
*/
|
|
133
|
+
/** Batch flip for one provider (public API). */
|
|
134
|
+
async toggleProvider(provider, hidden) {
|
|
135
|
+
const current = this.form.getSnapshot().value?.hidden ?? [];
|
|
136
|
+
const providerRows = this.stateRows().find((group) => group.id === provider);
|
|
137
|
+
const ids = new Set([...providerRows ? providerRows.models.map((model) => model.id) : [], ...current.filter((entry) => entry.provider === provider).map((entry) => entry.model)]);
|
|
138
|
+
if (ids.size === 0) return;
|
|
139
|
+
let next = current.filter((entry) => entry.provider !== provider);
|
|
140
|
+
if (hidden) {
|
|
141
|
+
const nameOf = (id) => providerRows?.models.find((model) => model.id === id)?.name ?? id;
|
|
142
|
+
next = [...next, ...[...ids].map((id) => ({
|
|
143
|
+
provider,
|
|
144
|
+
model: id,
|
|
145
|
+
name: nameOf(id)
|
|
146
|
+
}))];
|
|
147
|
+
}
|
|
148
|
+
await this.write(next);
|
|
149
|
+
}
|
|
150
|
+
/** Clear the hidden list entirely (public API). */
|
|
151
|
+
async resetAll() {
|
|
152
|
+
await this.write([]);
|
|
153
|
+
}
|
|
154
|
+
/** Current union rows (catalog ∪ hidden) without touching the store. */
|
|
155
|
+
stateRows() {
|
|
156
|
+
const snapshot = this.form.getSnapshot();
|
|
157
|
+
const hidden = snapshot.value?.hidden ?? [];
|
|
158
|
+
if (this.catalog === void 0) return [];
|
|
159
|
+
const hiddenKeys = new Set(hidden.map((entry) => `${entry.provider}\u0000${entry.model}`));
|
|
160
|
+
const groups = this.catalog.map((group) => ({
|
|
161
|
+
id: group.id,
|
|
162
|
+
name: group.name,
|
|
163
|
+
models: group.models.map((model) => ({
|
|
164
|
+
id: model.id,
|
|
165
|
+
name: model.name,
|
|
166
|
+
hidden: hiddenKeys.has(`${group.id}\u0000${model.id}`)
|
|
167
|
+
}))
|
|
168
|
+
}));
|
|
169
|
+
const groupsById = new Map(groups.map((group) => [group.id, group]));
|
|
170
|
+
for (const entry of hidden) {
|
|
171
|
+
const group = groupsById.get(entry.provider);
|
|
172
|
+
if (group?.models.some((model) => model.id === entry.model)) continue;
|
|
173
|
+
const row = {
|
|
174
|
+
id: entry.model,
|
|
175
|
+
name: entry.name ?? entry.model,
|
|
176
|
+
hidden: true
|
|
177
|
+
};
|
|
178
|
+
if (group !== void 0) group.models.push(row);
|
|
179
|
+
else {
|
|
180
|
+
const created = {
|
|
181
|
+
id: entry.provider,
|
|
182
|
+
name: entry.provider,
|
|
183
|
+
models: [row]
|
|
68
184
|
};
|
|
185
|
+
groupsById.set(entry.provider, created);
|
|
186
|
+
groups.push(created);
|
|
69
187
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* (filtered) catalog rows and existing hidden entries so a provider with
|
|
118
|
-
* every model already hidden still flips back fully.
|
|
119
|
-
*/
|
|
120
|
-
/** Batch flip for one provider (public API). */
|
|
121
|
-
async toggleProvider(provider, hidden) {
|
|
122
|
-
const current = this.scope.getSnapshot().value?.hidden ?? [];
|
|
123
|
-
const providerRows = this.stateRows().find((group) => group.id === provider);
|
|
124
|
-
const ids = /* @__PURE__ */ new Set([...providerRows ? providerRows.models.map((model) => model.id) : [], ...current.filter((entry) => entry.provider === provider).map((entry) => entry.model)]);
|
|
125
|
-
if (ids.size === 0) return;
|
|
126
|
-
let next = current.filter((entry) => entry.provider !== provider);
|
|
127
|
-
if (hidden) {
|
|
128
|
-
const nameOf = (id) => providerRows?.models.find((model) => model.id === id)?.name ?? id;
|
|
129
|
-
next = [...next, ...[...ids].map((id) => ({
|
|
130
|
-
provider,
|
|
131
|
-
model: id,
|
|
132
|
-
name: nameOf(id)
|
|
133
|
-
}))];
|
|
134
|
-
}
|
|
135
|
-
await this.write(next);
|
|
136
|
-
}
|
|
137
|
-
/** Clear the hidden list entirely (public API). */
|
|
138
|
-
async resetAll() {
|
|
139
|
-
await this.write([]);
|
|
140
|
-
}
|
|
141
|
-
/** Current union rows (catalog ∪ hidden) without touching the store. */
|
|
142
|
-
stateRows() {
|
|
143
|
-
const hidden = this.scope.getSnapshot().value?.hidden ?? [];
|
|
144
|
-
if (this.catalog === void 0) return [];
|
|
145
|
-
const hiddenKeys = new Set(hidden.map((entry) => `${entry.provider}\u0000${entry.model}`));
|
|
146
|
-
const groups = this.catalog.map((group) => ({
|
|
147
|
-
id: group.id,
|
|
148
|
-
name: group.name,
|
|
149
|
-
models: group.models.map((model) => ({
|
|
150
|
-
id: model.id,
|
|
151
|
-
name: model.name,
|
|
152
|
-
hidden: hiddenKeys.has(`${group.id}\u0000${model.id}`)
|
|
153
|
-
}))
|
|
154
|
-
}));
|
|
155
|
-
const groupsById = new Map(groups.map((group) => [group.id, group]));
|
|
156
|
-
for (const entry of hidden) {
|
|
157
|
-
const group = groupsById.get(entry.provider);
|
|
158
|
-
if (group?.models.some((model) => model.id === entry.model)) continue;
|
|
159
|
-
const row = {
|
|
160
|
-
id: entry.model,
|
|
161
|
-
name: entry.name ?? entry.model,
|
|
162
|
-
hidden: true
|
|
163
|
-
};
|
|
164
|
-
if (group !== void 0) group.models.push(row);
|
|
165
|
-
else {
|
|
166
|
-
const created = {
|
|
167
|
-
id: entry.provider,
|
|
168
|
-
name: entry.provider,
|
|
169
|
-
models: [row]
|
|
170
|
-
};
|
|
171
|
-
groupsById.set(entry.provider, created);
|
|
172
|
-
groups.push(created);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return groups;
|
|
176
|
-
}
|
|
177
|
-
/** Union the filtered catalog with the hidden entries into render rows. */
|
|
178
|
-
recompute() {
|
|
179
|
-
const snapshot = this.scope.getSnapshot();
|
|
180
|
-
if (this.catalog === void 0 && this.loadError !== void 0) {
|
|
181
|
-
this.store.set({
|
|
182
|
-
...EMPTY,
|
|
183
|
-
phase: "error",
|
|
184
|
-
error: this.loadError,
|
|
185
|
-
writable: snapshot.writable
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (this.catalog === void 0 || snapshot.status === "loading") {
|
|
190
|
-
this.store.set({
|
|
191
|
-
...EMPTY,
|
|
192
|
-
writable: snapshot.writable
|
|
193
|
-
});
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const groups = this.stateRows();
|
|
197
|
-
const total = groups.reduce((count, group) => count + group.models.length, 0);
|
|
198
|
-
const visible = groups.reduce((count, group) => count + group.models.filter((model) => !model.hidden).length, 0);
|
|
199
|
-
this.store.set({
|
|
200
|
-
phase: "ready",
|
|
201
|
-
writable: snapshot.writable,
|
|
202
|
-
groups,
|
|
203
|
-
total,
|
|
204
|
-
visible
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
//#endregion
|
|
209
|
-
//#region src/client/ModelVisibilityCard.tsx
|
|
210
|
-
/**
|
|
211
|
-
* The model-visibility settings section, styled to mirror the official
|
|
212
|
-
* settings pages: big page title + muted intro, a controls row, then one
|
|
213
|
-
* bordered rounded card per provider (like the provider cards on the
|
|
214
|
-
* 模型 page) with pill action buttons and token-style switches.
|
|
215
|
-
*
|
|
216
|
-
* Consumes the `settings.section` contribution contract exactly like the
|
|
217
|
-
* official ModelsSection: props = spread inject face, where `hooks.snapshot`
|
|
218
|
-
* arrives as the `useSnapshot` selector hook; all three seats are undefined
|
|
219
|
-
* while the shell has not injected yet (render null then).
|
|
220
|
-
*/
|
|
221
|
-
const STYLE_ID = "dsh-model-visibility-styles";
|
|
222
|
-
const STYLES = `
|
|
188
|
+
}
|
|
189
|
+
return groups;
|
|
190
|
+
}
|
|
191
|
+
/** Union the filtered catalog with the hidden entries into render rows. */
|
|
192
|
+
recompute() {
|
|
193
|
+
const snapshot = this.form.getSnapshot();
|
|
194
|
+
if (snapshot.status === "unavailable") {
|
|
195
|
+
this.store.set({
|
|
196
|
+
...EMPTY,
|
|
197
|
+
phase: "unavailable",
|
|
198
|
+
writable: snapshot.writable
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (this.catalog === void 0 && this.loadError !== void 0) {
|
|
203
|
+
this.store.set({
|
|
204
|
+
...EMPTY,
|
|
205
|
+
phase: "error",
|
|
206
|
+
error: this.loadError,
|
|
207
|
+
writable: snapshot.writable
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (this.catalog === void 0 || snapshot.status === "loading") {
|
|
212
|
+
this.store.set({
|
|
213
|
+
...EMPTY,
|
|
214
|
+
writable: snapshot.writable
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const groups = this.stateRows();
|
|
219
|
+
const total = groups.reduce((count, group) => count + group.models.length, 0);
|
|
220
|
+
const visible = groups.reduce((count, group) => count + group.models.filter((model) => !model.hidden).length, 0);
|
|
221
|
+
this.store.set({
|
|
222
|
+
phase: "ready",
|
|
223
|
+
writable: snapshot.writable,
|
|
224
|
+
groups,
|
|
225
|
+
total,
|
|
226
|
+
visible
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region src/client/ModelVisibilityCard.tsx
|
|
233
|
+
const STYLE_ID = "dsh-model-visibility-styles";
|
|
234
|
+
const STYLES = `
|
|
223
235
|
.dsh-mvw { display:flex; flex-direction:column; gap:16px; }
|
|
224
236
|
.dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }
|
|
225
237
|
.dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }
|
|
@@ -273,286 +285,447 @@ window.__ModuleLoader__.load({
|
|
|
273
285
|
.dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }
|
|
274
286
|
.dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }
|
|
275
287
|
`;
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
288
|
+
function ensureStyles() {
|
|
289
|
+
if (typeof document === "undefined") return;
|
|
290
|
+
if (document.getElementById(STYLE_ID)) return;
|
|
291
|
+
const el = document.createElement("style");
|
|
292
|
+
el.id = STYLE_ID;
|
|
293
|
+
el.textContent = STYLES;
|
|
294
|
+
document.head.appendChild(el);
|
|
295
|
+
}
|
|
296
|
+
function ModelVisibilityCard(props) {
|
|
297
|
+
const { controller, useSnapshot, t } = props;
|
|
298
|
+
const state = (useSnapshot ?? ((_selector) => void 0))((snapshot) => snapshot);
|
|
299
|
+
const [query, setQuery] = (0, react.useState)("");
|
|
300
|
+
(0, react.useMemo)(() => {
|
|
301
|
+
ensureStyles();
|
|
302
|
+
}, []);
|
|
303
|
+
const filteredGroups = (0, react.useMemo)(() => {
|
|
304
|
+
const q = query.trim().toLowerCase();
|
|
305
|
+
if (q === "") return state?.groups ?? [];
|
|
306
|
+
return (state?.groups ?? []).map((group) => ({
|
|
307
|
+
...group,
|
|
308
|
+
models: group.models.filter((model) => model.id.toLowerCase().includes(q) || model.name.toLowerCase().includes(q) || group.id.toLowerCase().includes(q))
|
|
309
|
+
})).filter((group) => group.models.length > 0);
|
|
310
|
+
}, [state, query]);
|
|
311
|
+
if (controller === void 0 || useSnapshot === void 0 || t === void 0) return null;
|
|
312
|
+
if (state === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
313
|
+
className: "dsh-mvw",
|
|
314
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
315
|
+
className: "mvw-note",
|
|
316
|
+
children: t("loading")
|
|
317
|
+
})
|
|
318
|
+
});
|
|
319
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
320
|
+
className: "dsh-mvw",
|
|
321
|
+
children: [
|
|
322
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
323
|
+
className: "mvw-title",
|
|
324
|
+
children: t("title")
|
|
325
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
326
|
+
className: "mvw-sub",
|
|
327
|
+
children: t("intro")
|
|
328
|
+
})] }),
|
|
329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
330
|
+
className: "mvw-controls",
|
|
309
331
|
children: [
|
|
310
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
332
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
333
|
+
type: "search",
|
|
334
|
+
className: "mvw-search",
|
|
335
|
+
value: query,
|
|
336
|
+
placeholder: t("searchPlaceholder"),
|
|
337
|
+
onChange: (event) => {
|
|
338
|
+
setQuery(event.target.value);
|
|
339
|
+
}
|
|
340
|
+
}),
|
|
341
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
342
|
+
type: "button",
|
|
343
|
+
className: "mvw-pill",
|
|
344
|
+
disabled: !state.writable || state.total === state.visible,
|
|
345
|
+
onClick: () => {
|
|
346
|
+
controller.resetAll();
|
|
347
|
+
},
|
|
348
|
+
children: t("showAllProviders")
|
|
349
|
+
}),
|
|
350
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
351
|
+
className: "mvw-count",
|
|
352
|
+
children: t("visibleCount", {
|
|
353
|
+
visible: state.visible,
|
|
354
|
+
total: state.total
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
]
|
|
358
|
+
}),
|
|
359
|
+
state.phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
360
|
+
className: "mvw-warn",
|
|
361
|
+
children: [
|
|
362
|
+
t("loadFailed", { message: state.error ?? "" }),
|
|
363
|
+
" ",
|
|
364
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
365
|
+
type: "button",
|
|
366
|
+
className: "mvw-mini",
|
|
367
|
+
onClick: () => {
|
|
368
|
+
controller.load();
|
|
369
|
+
},
|
|
370
|
+
children: t("retry")
|
|
371
|
+
})
|
|
372
|
+
]
|
|
373
|
+
}),
|
|
374
|
+
state.phase === "unavailable" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
375
|
+
className: "mvw-note",
|
|
376
|
+
children: t("unavailable")
|
|
377
|
+
}),
|
|
378
|
+
state.phase === "ready" && !state.writable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
379
|
+
className: "mvw-note",
|
|
380
|
+
children: t("readOnly")
|
|
381
|
+
}),
|
|
382
|
+
state.phase === "ready" && state.groups.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
383
|
+
className: "mvw-note",
|
|
384
|
+
children: t("empty")
|
|
385
|
+
}),
|
|
386
|
+
state.phase === "ready" && state.total > 0 && state.visible === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
387
|
+
className: "mvw-warn",
|
|
388
|
+
children: t("allHiddenWarning")
|
|
389
|
+
}),
|
|
390
|
+
state.phase === "ready" && filteredGroups.length === 0 && state.groups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
391
|
+
className: "mvw-note",
|
|
392
|
+
children: t("noMatch")
|
|
393
|
+
}),
|
|
394
|
+
state.phase === "ready" && filteredGroups.map((group) => {
|
|
395
|
+
const groupVisible = group.models.filter((model) => !model.hidden).length;
|
|
396
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
397
|
+
className: "mvw-card",
|
|
398
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
399
|
+
className: "mvw-prov-head",
|
|
400
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
401
|
+
className: "mvw-prov-name",
|
|
402
|
+
children: group.name
|
|
403
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
404
|
+
className: "mvw-prov-count",
|
|
405
|
+
children: t("providerRow", {
|
|
406
|
+
visible: groupVisible,
|
|
407
|
+
total: group.models.length
|
|
408
|
+
})
|
|
409
|
+
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
410
|
+
className: "mvw-prov-btns",
|
|
411
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
330
412
|
type: "button",
|
|
331
|
-
className: "mvw-
|
|
332
|
-
disabled: !state.writable ||
|
|
413
|
+
className: "mvw-mini",
|
|
414
|
+
disabled: !state.writable || groupVisible === 0,
|
|
333
415
|
onClick: () => {
|
|
334
|
-
controller.
|
|
416
|
+
controller.toggleProvider(group.id, true);
|
|
335
417
|
},
|
|
336
|
-
children: t("
|
|
337
|
-
}),
|
|
338
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
339
|
-
className: "mvw-count",
|
|
340
|
-
children: t("visibleCount", {
|
|
341
|
-
visible: state.visible,
|
|
342
|
-
total: state.total
|
|
343
|
-
})
|
|
344
|
-
})
|
|
345
|
-
]
|
|
346
|
-
}),
|
|
347
|
-
state.phase === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
348
|
-
className: "mvw-warn",
|
|
349
|
-
children: [
|
|
350
|
-
t("loadFailed", { message: state.error ?? "" }),
|
|
351
|
-
" ",
|
|
352
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
418
|
+
children: t("hideAll")
|
|
419
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
353
420
|
type: "button",
|
|
354
421
|
className: "mvw-mini",
|
|
422
|
+
disabled: !state.writable || groupVisible === group.models.length,
|
|
355
423
|
onClick: () => {
|
|
356
|
-
controller.
|
|
424
|
+
controller.toggleProvider(group.id, false);
|
|
357
425
|
},
|
|
358
|
-
children: t("
|
|
359
|
-
})
|
|
360
|
-
]
|
|
361
|
-
}),
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
state.phase === "ready" && filteredGroups.length === 0 && state.groups.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
375
|
-
className: "mvw-note",
|
|
376
|
-
children: t("noMatch")
|
|
377
|
-
}),
|
|
378
|
-
state.phase === "ready" && filteredGroups.map((group) => {
|
|
379
|
-
const groupVisible = group.models.filter((model) => !model.hidden).length;
|
|
380
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
381
|
-
className: "mvw-card",
|
|
382
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
383
|
-
className: "mvw-prov-head",
|
|
384
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
385
|
-
className: "mvw-prov-name",
|
|
386
|
-
children: group.name
|
|
387
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
388
|
-
className: "mvw-prov-count",
|
|
389
|
-
children: t("providerRow", {
|
|
390
|
-
visible: groupVisible,
|
|
391
|
-
total: group.models.length
|
|
392
|
-
})
|
|
393
|
-
})] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
394
|
-
className: "mvw-prov-btns",
|
|
395
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
396
|
-
type: "button",
|
|
397
|
-
className: "mvw-mini",
|
|
398
|
-
disabled: !state.writable || groupVisible === 0,
|
|
399
|
-
onClick: () => {
|
|
400
|
-
controller.toggleProvider(group.id, true);
|
|
401
|
-
},
|
|
402
|
-
children: t("hideAll")
|
|
403
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
404
|
-
type: "button",
|
|
405
|
-
className: "mvw-mini",
|
|
406
|
-
disabled: !state.writable || groupVisible === group.models.length,
|
|
407
|
-
onClick: () => {
|
|
408
|
-
controller.toggleProvider(group.id, false);
|
|
409
|
-
},
|
|
410
|
-
children: t("showAll")
|
|
411
|
-
})]
|
|
426
|
+
children: t("showAll")
|
|
427
|
+
})]
|
|
428
|
+
})]
|
|
429
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
430
|
+
style: {
|
|
431
|
+
listStyle: "none",
|
|
432
|
+
margin: 0,
|
|
433
|
+
padding: 0
|
|
434
|
+
},
|
|
435
|
+
children: group.models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
436
|
+
className: "mvw-row",
|
|
437
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
438
|
+
className: "mvw-name",
|
|
439
|
+
children: [model.name, model.name !== model.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
440
|
+
className: "mvw-id",
|
|
441
|
+
children: model.id
|
|
412
442
|
})]
|
|
413
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
className: "mvw-row",
|
|
421
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
422
|
-
className: "mvw-name",
|
|
423
|
-
children: [model.name, model.name !== model.id && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
424
|
-
className: "mvw-id",
|
|
425
|
-
children: model.id
|
|
426
|
-
})]
|
|
427
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
428
|
-
on: !model.hidden,
|
|
429
|
-
disabled: !state.writable,
|
|
430
|
-
label: model.name,
|
|
431
|
-
onChange: (visible) => {
|
|
432
|
-
controller.toggle(group.id, model.id, model.name, !visible);
|
|
433
|
-
}
|
|
434
|
-
})]
|
|
435
|
-
}, model.id))
|
|
443
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Switch, {
|
|
444
|
+
on: !model.hidden,
|
|
445
|
+
disabled: !state.writable,
|
|
446
|
+
label: model.name,
|
|
447
|
+
onChange: (visible) => {
|
|
448
|
+
controller.toggle(group.id, model.id, model.name, !visible);
|
|
449
|
+
}
|
|
436
450
|
})]
|
|
437
|
-
},
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
451
|
+
}, model.id))
|
|
452
|
+
})]
|
|
453
|
+
}, group.id);
|
|
454
|
+
})
|
|
455
|
+
]
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
function Switch(props) {
|
|
459
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
460
|
+
type: "button",
|
|
461
|
+
role: "switch",
|
|
462
|
+
"aria-checked": props.on,
|
|
463
|
+
"aria-label": props.label,
|
|
464
|
+
disabled: props.disabled,
|
|
465
|
+
onClick: () => {
|
|
466
|
+
props.onChange(!props.on);
|
|
467
|
+
},
|
|
468
|
+
style: {
|
|
469
|
+
width: 40,
|
|
470
|
+
height: 22,
|
|
471
|
+
borderRadius: 11,
|
|
472
|
+
border: "none",
|
|
473
|
+
padding: 2,
|
|
474
|
+
cursor: props.disabled ? "not-allowed" : "pointer",
|
|
475
|
+
background: props.on ? "#4c8bf5" : "rgba(128,128,128,.45)",
|
|
476
|
+
opacity: props.disabled ? .5 : 1,
|
|
477
|
+
display: "inline-flex",
|
|
478
|
+
justifyContent: props.on ? "flex-end" : "flex-start",
|
|
479
|
+
transition: "background .15s",
|
|
480
|
+
flex: "none"
|
|
481
|
+
},
|
|
482
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
483
|
+
width: 18,
|
|
484
|
+
height: 18,
|
|
485
|
+
borderRadius: "50%",
|
|
486
|
+
background: "#fff",
|
|
487
|
+
display: "block"
|
|
488
|
+
} })
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
//#endregion
|
|
493
|
+
//#region src/client/locales.ts
|
|
494
|
+
const LOCALE_NS = "settings.modelVisibility";
|
|
495
|
+
const zh = {
|
|
496
|
+
nav: "模型可见性",
|
|
497
|
+
title: "模型可见性",
|
|
498
|
+
intro: "控制各模型是否出现在对话界面的模型选择菜单中。隐藏一个正在使用的模型不会中断该会话,也不会改动 Provider 配置。",
|
|
499
|
+
loading: "正在读取模型目录…",
|
|
500
|
+
loadFailed: "模型目录读取失败:{message}",
|
|
501
|
+
retry: "重试",
|
|
502
|
+
unavailable: "此连接不共享宿主设置,模型可见性不可用。",
|
|
503
|
+
empty: "没有任何 Provider 提供模型。",
|
|
504
|
+
allHiddenWarning: "所有模型都已隐藏,模型选择菜单将是空的。",
|
|
505
|
+
readOnly: "设置文档不可写,开关已禁用。",
|
|
506
|
+
visibleCount: "{visible} / {total} 可见",
|
|
507
|
+
searchPlaceholder: "搜索模型…",
|
|
508
|
+
noMatch: "没有匹配的模型。",
|
|
509
|
+
hideAll: "全部隐藏",
|
|
510
|
+
showAll: "全部显示",
|
|
511
|
+
showAllProviders: "恢复全部",
|
|
512
|
+
providerRow: "该渠道 {visible} / {total} 可见"
|
|
513
|
+
};
|
|
514
|
+
const en = {
|
|
515
|
+
nav: "Model visibility",
|
|
516
|
+
title: "Model visibility",
|
|
517
|
+
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.",
|
|
518
|
+
loading: "Loading the model catalog…",
|
|
519
|
+
loadFailed: "Failed to load the model catalog: {message}",
|
|
520
|
+
retry: "Retry",
|
|
521
|
+
unavailable: "Model visibility is unavailable: this connection does not share the Host settings.",
|
|
522
|
+
empty: "No provider advertises any model.",
|
|
523
|
+
allHiddenWarning: "Every model is hidden; the model selector will be empty.",
|
|
524
|
+
readOnly: "The settings document is read-only; toggles are disabled.",
|
|
525
|
+
visibleCount: "{visible} / {total} visible",
|
|
526
|
+
searchPlaceholder: "Search models…",
|
|
527
|
+
noMatch: "No model matches.",
|
|
528
|
+
hideAll: "Hide all",
|
|
529
|
+
showAll: "Show all",
|
|
530
|
+
showAllProviders: "Reset all",
|
|
531
|
+
providerRow: "{visible} / {total} visible in this provider"
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/client/settings-nav-icon.ts
|
|
536
|
+
/**
|
|
537
|
+
* The eye glyph in the settings navigation.
|
|
538
|
+
*
|
|
539
|
+
* The settings shell picks nav glyphs from a closed list of section ids
|
|
540
|
+
* (`account`, `models`, `agent-presets`, `plugins`, `archived-sessions`) and
|
|
541
|
+
* falls back to its own gear for every other id; `settings.section` projects
|
|
542
|
+
* only `id` / `order` / `label`, so a registrant has no icon to pass — the
|
|
543
|
+
* slot contract in `@deepseek-ai/dsh-client-ui-settings` and the runtime
|
|
544
|
+
* slot inventory both list exactly those options. Every third-party section
|
|
545
|
+
* therefore wears the gear, this one included.
|
|
546
|
+
*
|
|
547
|
+
* So this card claims its own row once the dialog is mounted and swaps the
|
|
548
|
+
* fallback gear for an eye — the same glyph the feature is about. The
|
|
549
|
+
* approach follows the precedent set by `dshmarket` (and credited there to
|
|
550
|
+
* `dsh-better-sidebar` / `dsh-skill-mcp-panel`).
|
|
551
|
+
*
|
|
552
|
+
* Scope, deliberately narrow:
|
|
553
|
+
*
|
|
554
|
+
* - only the row whose visible text equals this plugin's own localized
|
|
555
|
+
* section label is marked; no shell structure is touched;
|
|
556
|
+
* - the marker and the injected stylesheet belong to a `ctx.effect`, so they
|
|
557
|
+
* are removed with the fiber;
|
|
558
|
+
* - a locale switch re-claims the row through the MutationObserver, so the
|
|
559
|
+
* label and the glyph never disagree.
|
|
560
|
+
*
|
|
561
|
+
* Delete this module (and its call in index.ts) the day `settings.section`
|
|
562
|
+
* grows an `icon` field.
|
|
563
|
+
*/
|
|
564
|
+
/** Marks the one nav row this plugin owns. */
|
|
565
|
+
const NAV_ICON_MARKER = "data-dsh-model-visibility-nav-icon";
|
|
566
|
+
/**
|
|
567
|
+
* The nav rows of the settings dialog. The shell renders each
|
|
568
|
+
* `settings.section` entry as a `<button>` inside the panel's `<nav>`
|
|
569
|
+
* (SettingsPanel in dsh-client-ui-settings-general).
|
|
570
|
+
*/
|
|
571
|
+
const NAV_ROW_SELECTOR = "[role=\"dialog\"] nav button";
|
|
572
|
+
/**
|
|
573
|
+
* Glyph box in px. The shell renders every nav icon at this size and ships no
|
|
574
|
+
* media query at all, so there is deliberately no narrow variant here.
|
|
575
|
+
*/
|
|
576
|
+
const NAV_ICON_SIZE = 16;
|
|
577
|
+
/**
|
|
578
|
+
* The eye as standalone SVG for a CSS `mask-image`.
|
|
579
|
+
*
|
|
580
|
+
* Painted pure black on purpose: a mask reads alpha only, and the visible
|
|
581
|
+
* colour comes from the element's `background-color: currentColor`, which
|
|
582
|
+
* keeps the glyph in step with the shell's own icons on hover/active state.
|
|
583
|
+
* The shape is the classic outline eye (almond contour + iris) on a 24 grid;
|
|
584
|
+
* `mask-size` scales it down to the shell's 16px box.
|
|
585
|
+
*/
|
|
586
|
+
function eyeMaskSvg() {
|
|
587
|
+
return "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>";
|
|
588
|
+
}
|
|
589
|
+
/** The mask URL for the glyph (encoded at runtime, never hand-escaped). */
|
|
590
|
+
function eyeMaskUrl(svg = eyeMaskSvg()) {
|
|
591
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Whether a nav row is this plugin's own.
|
|
595
|
+
*
|
|
596
|
+
* Pure, and the only decision this feature makes: the row whose visible text
|
|
597
|
+
* is the section label the shell is currently projecting. An empty label
|
|
598
|
+
* matches nothing — a locale that has not resolved yet must not mark the
|
|
599
|
+
* whole nav.
|
|
600
|
+
*/
|
|
601
|
+
function isOwnNavRow(rowText, wantedLabel) {
|
|
602
|
+
const wanted = String(wantedLabel ?? "").trim();
|
|
603
|
+
if (wanted.length === 0) return false;
|
|
604
|
+
return String(rowText ?? "").trim() === wanted;
|
|
605
|
+
}
|
|
606
|
+
/** Stylesheet for the marked row: hide the shell's gear, draw the eye. */
|
|
607
|
+
function navIconCss(maskUrl) {
|
|
608
|
+
return [
|
|
609
|
+
`[${NAV_ICON_MARKER}] > svg { display: none; }`,
|
|
610
|
+
`[${NAV_ICON_MARKER}]::before {`,
|
|
611
|
+
` content: '';`,
|
|
612
|
+
` flex: none;`,
|
|
613
|
+
` width: ${NAV_ICON_SIZE}px;`,
|
|
614
|
+
` height: ${NAV_ICON_SIZE}px;`,
|
|
615
|
+
` background-color: currentColor;`,
|
|
616
|
+
` -webkit-mask-image: url("${maskUrl}");`,
|
|
617
|
+
` mask-image: url("${maskUrl}");`,
|
|
618
|
+
` -webkit-mask-repeat: no-repeat;`,
|
|
619
|
+
` mask-repeat: no-repeat;`,
|
|
620
|
+
` -webkit-mask-position: center;`,
|
|
621
|
+
` mask-position: center;`,
|
|
622
|
+
` -webkit-mask-size: ${NAV_ICON_SIZE}px ${NAV_ICON_SIZE}px;`,
|
|
623
|
+
` mask-size: ${NAV_ICON_SIZE}px ${NAV_ICON_SIZE}px;`,
|
|
624
|
+
`}`
|
|
625
|
+
].join("\n");
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Install the nav glyph.
|
|
629
|
+
*
|
|
630
|
+
* @param ctx - client context, for effect ownership.
|
|
631
|
+
* @param resolveLabel - this plugin's current section label (the same thunk
|
|
632
|
+
* the `settings.section` registration passes), re-read on every sync so a
|
|
633
|
+
* locale switch is picked up without re-registering.
|
|
634
|
+
*/
|
|
635
|
+
function installSettingsNavIcon(ctx, resolveLabel) {
|
|
636
|
+
if (typeof document === "undefined") return;
|
|
637
|
+
ctx.effect(() => {
|
|
638
|
+
const tag = document.createElement("style");
|
|
639
|
+
tag.dataset.plugin = "dsh-model-visibility";
|
|
640
|
+
tag.dataset.pluginCss = "dsh-model-visibility/settings-nav-icon";
|
|
641
|
+
tag.textContent = navIconCss(eyeMaskUrl());
|
|
642
|
+
document.head.appendChild(tag);
|
|
643
|
+
let disposed = false;
|
|
644
|
+
let scheduled = false;
|
|
645
|
+
const sync = () => {
|
|
646
|
+
scheduled = false;
|
|
647
|
+
if (disposed) return;
|
|
648
|
+
const wanted = resolveLabel();
|
|
649
|
+
for (const row of document.querySelectorAll(NAV_ROW_SELECTOR)) if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, "");
|
|
650
|
+
else row.removeAttribute(NAV_ICON_MARKER);
|
|
495
651
|
};
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
loading: "Loading the model catalog…",
|
|
501
|
-
loadFailed: "Failed to load the model catalog: {message}",
|
|
502
|
-
retry: "Retry",
|
|
503
|
-
empty: "No provider advertises any model.",
|
|
504
|
-
allHiddenWarning: "Every model is hidden; the model selector will be empty.",
|
|
505
|
-
readOnly: "The settings document is read-only; toggles are disabled.",
|
|
506
|
-
visibleCount: "{visible} / {total} visible",
|
|
507
|
-
searchPlaceholder: "Search models…",
|
|
508
|
-
noMatch: "No model matches.",
|
|
509
|
-
hideAll: "Hide all",
|
|
510
|
-
showAll: "Show all",
|
|
511
|
-
showAllProviders: "Reset all",
|
|
512
|
-
providerRow: "{visible} / {total} visible in this provider"
|
|
652
|
+
const schedule = () => {
|
|
653
|
+
if (scheduled || disposed) return;
|
|
654
|
+
scheduled = true;
|
|
655
|
+
queueMicrotask(sync);
|
|
513
656
|
};
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
657
|
+
sync();
|
|
658
|
+
const observer = new MutationObserver(schedule);
|
|
659
|
+
observer.observe(document.body, {
|
|
660
|
+
childList: true,
|
|
661
|
+
subtree: true,
|
|
662
|
+
characterData: true
|
|
663
|
+
});
|
|
664
|
+
return () => {
|
|
665
|
+
disposed = true;
|
|
666
|
+
observer.disconnect();
|
|
667
|
+
for (const row of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) row.removeAttribute(NAV_ICON_MARKER);
|
|
668
|
+
tag.remove();
|
|
669
|
+
};
|
|
670
|
+
}, "model-visibility: settings nav icon");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/client/index.ts
|
|
675
|
+
const name = "dsh-model-visibility";
|
|
676
|
+
const inject = [
|
|
677
|
+
"slots",
|
|
678
|
+
"locale",
|
|
679
|
+
"configForms",
|
|
680
|
+
"remote",
|
|
681
|
+
"remote.session"
|
|
682
|
+
];
|
|
683
|
+
function apply(ctx) {
|
|
684
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NS, {
|
|
685
|
+
zh,
|
|
686
|
+
en
|
|
687
|
+
}), "model-visibility: copy dictionaries");
|
|
688
|
+
const form = ctx.configForms.get(MODEL_VISIBILITY_NS);
|
|
689
|
+
const catalog = { async read() {
|
|
690
|
+
const response = await ctx.remote.session.modelCatalog();
|
|
691
|
+
if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
|
|
692
|
+
return response.value.groups;
|
|
693
|
+
} };
|
|
694
|
+
const controller = new ModelVisibilityCardController(form, catalog);
|
|
695
|
+
ctx.effect(() => () => {
|
|
696
|
+
controller.dispose();
|
|
697
|
+
}, "model-visibility: card controller");
|
|
698
|
+
ctx.effect(() => {
|
|
699
|
+
const offAdapters = ctx.remote.$on("llm/adapters-updated", () => {
|
|
700
|
+
controller.load();
|
|
701
|
+
});
|
|
702
|
+
const offSettings = ctx.remote.$on("settings/document-updated", (ns) => {
|
|
703
|
+
if (ns !== MODEL_VISIBILITY_NS) controller.load();
|
|
704
|
+
});
|
|
705
|
+
return () => {
|
|
706
|
+
offAdapters();
|
|
707
|
+
offSettings();
|
|
708
|
+
};
|
|
709
|
+
}, "model-visibility: catalog invalidation");
|
|
710
|
+
const t = ctx.locale.bind(LOCALE_NS);
|
|
711
|
+
installSettingsNavIcon(ctx, () => t("nav"));
|
|
712
|
+
const injected = () => ({
|
|
713
|
+
controller,
|
|
714
|
+
t,
|
|
715
|
+
hooks: { snapshot: controller.store }
|
|
716
|
+
});
|
|
717
|
+
ctx.effect(() => ctx.configForms.whileServed([MODEL_VISIBILITY_NS], () => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
718
|
+
name: "settings.section",
|
|
719
|
+
id: "model-visibility",
|
|
720
|
+
order: 11,
|
|
721
|
+
label: () => t("nav"),
|
|
722
|
+
inject: injected
|
|
723
|
+
}, ModelVisibilityCard))), "model-visibility: settings section");
|
|
724
|
+
}
|
|
557
725
|
|
|
726
|
+
//#endregion
|
|
727
|
+
exports.apply = apply;
|
|
728
|
+
exports.inject = inject;
|
|
729
|
+
exports.name = name;
|
|
730
|
+
return module.exports; } });
|
|
558
731
|
//# sourceMappingURL=client.js.map
|