dsh-model-visibility 0.1.1 → 0.2.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/lib/client.js CHANGED
@@ -1,218 +1,237 @@
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
- });
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: () => {
48
71
  this.load();
49
72
  }
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
- }
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]
66
184
  };
185
+ groupsById.set(entry.provider, created);
186
+ groups.push(created);
67
187
  }
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 = `
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 = `
216
235
  .dsh-mvw { display:flex; flex-direction:column; gap:16px; }
217
236
  .dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }
218
237
  .dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }
@@ -266,279 +285,307 @@ window.__ModuleLoader__.load({
266
285
  .dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }
267
286
  .dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }
268
287
  `;
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",
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",
302
331
  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", {
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", {
323
412
  type: "button",
324
- className: "mvw-pill",
325
- disabled: !state.writable || state.total === state.visible,
413
+ className: "mvw-mini",
414
+ disabled: !state.writable || groupVisible === 0,
326
415
  onClick: () => {
327
- controller.resetAll();
416
+ controller.toggleProvider(group.id, true);
328
417
  },
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", {
418
+ children: t("hideAll")
419
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
346
420
  type: "button",
347
421
  className: "mvw-mini",
422
+ disabled: !state.writable || groupVisible === group.models.length,
348
423
  onClick: () => {
349
- controller.load();
424
+ controller.toggleProvider(group.id, false);
350
425
  },
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
- })]
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
405
442
  })]
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))
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
+ }
429
450
  })]
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"
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/index.ts
536
+ const name = "dsh-model-visibility";
537
+ const inject = [
538
+ "slots",
539
+ "locale",
540
+ "configForms",
541
+ "remote",
542
+ "remote.session"
543
+ ];
544
+ function apply(ctx) {
545
+ ctx.effect(() => ctx.locale.register(LOCALE_NS, {
546
+ zh,
547
+ en
548
+ }), "model-visibility: copy dictionaries");
549
+ const form = ctx.configForms.get(MODEL_VISIBILITY_NS);
550
+ const catalog = { async read() {
551
+ const response = await ctx.remote.session.modelCatalog();
552
+ if (!response.ok) throw new Error(`${response.error.code}: ${response.error.message}`);
553
+ return response.value.groups;
554
+ } };
555
+ const controller = new ModelVisibilityCardController(form, catalog);
556
+ ctx.effect(() => () => {
557
+ controller.dispose();
558
+ }, "model-visibility: card controller");
559
+ ctx.effect(() => {
560
+ const offAdapters = ctx.remote.$on("llm/adapters-updated", () => {
561
+ controller.load();
562
+ });
563
+ const offSettings = ctx.remote.$on("settings/document-updated", (ns) => {
564
+ if (ns !== MODEL_VISIBILITY_NS) controller.load();
565
+ });
566
+ return () => {
567
+ offAdapters();
568
+ offSettings();
506
569
  };
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
- });
570
+ }, "model-visibility: catalog invalidation");
571
+ const t = ctx.locale.bind(LOCALE_NS);
572
+ const injected = () => ({
573
+ controller,
574
+ t,
575
+ hooks: { snapshot: controller.store }
576
+ });
577
+ ctx.effect(() => ctx.configForms.whileServed([MODEL_VISIBILITY_NS], () => ctx.slots.inject("settings.section", () => ctx.slots.register({
578
+ name: "settings.section",
579
+ id: "model-visibility",
580
+ order: 11,
581
+ label: () => t("nav"),
582
+ inject: injected
583
+ }, ModelVisibilityCard))), "model-visibility: settings section");
584
+ }
543
585
 
586
+ //#endregion
587
+ exports.apply = apply;
588
+ exports.inject = inject;
589
+ exports.name = name;
590
+ return module.exports; } });
544
591
  //# sourceMappingURL=client.js.map