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.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["createSnapshotStore","useState","useMemo"],"sources":["../src/namespace.ts","../src/client/controller.ts","../src/client/ModelVisibilityCard.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Shared settings namespace of the model-visibility plugin. Lives in its own\n * module so the browser half can name the namespace without value-importing\n * the Host half (which would drag the settings schema and its Node-side deps\n * into the client bundle).\n */\nexport const MODEL_VISIBILITY_NS = 'model-visibility'\n","/**\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-store'\nimport type { SnapshotStore } from '@deepseek-ai/dsh-client-store'\nimport type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type { Config, HiddenModel } from '../index.ts'\n\r\n/** One toggle row. */\r\nexport interface ModelRow {\r\n id: string\r\n name: string\r\n hidden: boolean\r\n}\r\n\r\n/** One provider group of rows. */\r\nexport interface ProviderGroupRow {\r\n id: string\r\n name: string\r\n models: ModelRow[]\r\n}\r\n\r\n/** What the card renders. */\nexport interface ModelVisibilityState {\n phase: 'loading' | 'ready' | 'error'\r\n error?: string\r\n /** Whether the Host settings document accepts writes. */\r\n writable: boolean\r\n groups: ProviderGroupRow[]\r\n /** Union count and how many of them remain visible. */\r\n total: number\r\n visible: number\n}\n\n/**\n * One catalog group as the Host's `remote.session.modelCatalog()` advertises\n * it. The 0.1.5 published Remote types mangle the catalog interface name away,\n * so this plugin restates the wire shape it actually consumes.\n */\nexport interface CatalogModelGroup {\n id: string\n name: string\n models: readonly { id: string; name: string }[]\n}\n\n/** Host model catalog outcome carried by the Remote result envelope. */\nexport type ModelCatalogOutcome =\n | { ok: true; value: { groups: readonly CatalogModelGroup[] } }\n | { ok: false; error: { code: string; message: string } }\n\n/** The assembled Client Remote face this plugin consumes. */\nexport interface ModelCatalogRemote {\n session: {\n modelCatalog(): Promise<ModelCatalogOutcome>\n }\n}\n\n/** Injected catalog reader: decouples the controller from the Remote wire shape. */\nexport interface ModelCatalogReader {\n read(): Promise<readonly CatalogModelGroup[]>\n}\n\n/** The face the slot registration injects into the card component. */\nexport interface ModelVisibilityFace {\n hooks: {\r\n /** Card snapshot bound by the renderer as useModelVisibility. */\r\n modelVisibility: SnapshotStore<ModelVisibilityState>\r\n }\r\n /** Flip one model's visibility. */\r\n toggle: (provider: string, model: string, name: string, hidden: boolean) => void\r\n /** Hide or show every model of one provider. */\r\n toggleProvider: (provider: string, hidden: boolean) => void\r\n /** Clear the hidden list entirely (everything visible again). */\r\n resetAll: () => void\r\n /** Re-read the catalog after a failure. */\r\n reload: () => void\r\n}\n\nconst EMPTY: ModelVisibilityState = { phase: 'loading', writable: false, groups: [], total: 0, visible: 0 }\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: CatalogModelGroup[] | undefined\n private loadError: string | undefined\n private generation = 0\n\n constructor(\n private readonly scope: SettingsScope<Config>,\n private readonly catalogReader: ModelCatalogReader,\n ) {\n scope.subscribe(() => { this.recompute() })\n void this.load()\n }\r\n\r\n /** The registration-side inject face. */\r\n face(): ModelVisibilityFace {\r\n return {\r\n hooks: { modelVisibility: this.store },\r\n toggle: (provider, model, name, hidden) => { void this.toggle(provider, model, name, hidden) },\r\n toggleProvider: (provider, hidden) => { void this.toggleProvider(provider, hidden) },\r\n resetAll: () => { void this.resetAll() },\r\n reload: () => { void this.load() },\r\n }\r\n }\r\n\r\n /** Re-read the catalog; an older response never overwrites a newer one. */\r\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 groups = await this.catalogReader.read()\n if (generation !== this.generation) return\n this.catalog = groups.map(group => ({\n id: group.id,\n name: group.name,\n models: group.models.map(model => ({ id: model.id, name: model.name })),\n }))\n this.loadError = undefined\n } catch (error: unknown) {\n if (generation !== this.generation) return\n this.loadError = error instanceof Error ? error.message : String(error)\n }\r\n this.recompute()\r\n }\r\n\r\n /** Compute the next hidden list from the current one plus one model's flip. */\r\n private nextHidden(current: HiddenModel[], provider: string, model: string, name: string, hidden: boolean): HiddenModel[] {\r\n const rest = current.filter(entry => !(entry.provider === provider && entry.model === model))\r\n return hidden ? [...rest, { provider, model, name }] : rest\r\n }\r\n\r\n /** Write the next hidden list, then re-pull the catalog the filter changed. */\r\n private async write(next: HiddenModel[]): Promise<void> {\r\n try {\r\n await this.scope.set('hidden', next)\r\n } catch {\r\n // The scope already reloads Host state after a failed latest write;\r\n // recompute below re-renders whatever the Host now reports.\r\n }\r\n await this.load()\r\n }\r\n\r\n /** Flip one model's visibility (public API — the section card calls it directly). */\r\n async toggle(provider: string, model: string, name: string, hidden: boolean): Promise<void> {\r\n const current = this.scope.getSnapshot().value?.hidden ?? []\r\n await this.write(this.nextHidden(current, provider, model, name, hidden))\r\n }\r\n\r\n /**\r\n * Batch flip for one provider. Models are taken from the union of the\r\n * (filtered) catalog rows and existing hidden entries so a provider with\r\n * every model already hidden still flips back fully.\r\n */\r\n /** Batch flip for one provider (public API). */\r\n async toggleProvider(provider: string, hidden: boolean): Promise<void> {\r\n const current = this.scope.getSnapshot().value?.hidden ?? []\r\n const providerRows = this.stateRows().find(group => group.id === provider)\r\n const ids = new Set<string>([\r\n ...providerRows ? providerRows.models.map(model => model.id) : [],\r\n ...current.filter(entry => entry.provider === provider).map(entry => entry.model),\r\n ])\r\n if (ids.size === 0) return\r\n let next = current.filter(entry => entry.provider !== provider)\r\n if (hidden) {\r\n const nameOf = (id: string): string => providerRows?.models.find(model => model.id === id)?.name ?? id\r\n next = [...next, ...[...ids].map(id => ({ provider, model: id, name: nameOf(id) }))]\r\n }\r\n await this.write(next)\r\n }\r\n\r\n /** Clear the hidden list entirely (public API). */\r\n async resetAll(): Promise<void> {\r\n await this.write([])\r\n }\r\n\r\n /** Current union rows (catalog ∪ hidden) without touching the store. */\r\n private stateRows(): ProviderGroupRow[] {\r\n const snapshot = this.scope.getSnapshot()\r\n const hidden = snapshot.value?.hidden ?? []\r\n if (this.catalog === undefined) return []\r\n const hiddenKeys = new Set(hidden.map(entry => `${entry.provider}\\u0000${entry.model}`))\r\n const groups: ProviderGroupRow[] = this.catalog.map(group => ({\r\n id: group.id,\r\n name: group.name,\r\n models: group.models.map(model => ({\r\n id: model.id,\r\n name: model.name,\r\n hidden: hiddenKeys.has(`${group.id}\\u0000${model.id}`),\r\n })),\r\n }))\r\n const groupsById = new Map(groups.map(group => [group.id, group]))\r\n for (const entry of hidden) {\r\n const group = groupsById.get(entry.provider)\r\n if (group?.models.some(model => model.id === entry.model)) continue\r\n const row: ModelRow = { id: entry.model, name: entry.name ?? entry.model, hidden: true }\r\n if (group !== undefined) {\r\n group.models.push(row)\r\n } else {\r\n const created: ProviderGroupRow = { id: entry.provider, name: entry.provider, models: [row] }\r\n groupsById.set(entry.provider, created)\r\n groups.push(created)\r\n }\r\n }\r\n return groups\r\n }\r\n\r\n /** Union the filtered catalog with the hidden entries into render rows. */\r\n private recompute(): void {\r\n const snapshot = this.scope.getSnapshot()\r\n if (this.catalog === undefined && this.loadError !== undefined) {\r\n this.store.set({ ...EMPTY, phase: 'error', error: this.loadError, writable: snapshot.writable })\r\n return\r\n }\r\n if (this.catalog === undefined || snapshot.status === 'loading') {\r\n this.store.set({ ...EMPTY, writable: snapshot.writable })\r\n return\r\n }\r\n const groups = this.stateRows()\r\n const total = groups.reduce((count, group) => count + group.models.length, 0)\r\n const visible = groups.reduce(\r\n (count, group) => count + group.models.filter(model => !model.hidden).length, 0)\r\n this.store.set({\r\n phase: 'ready',\r\n writable: snapshot.writable,\r\n groups,\r\n total,\r\n visible,\r\n })\r\n }\r\n}\r\n","/**\r\n * The model-visibility settings section, styled to mirror the official\r\n * settings pages: big page title + muted intro, a controls row, then one\r\n * bordered rounded card per provider (like the provider cards on the\r\n * 模型 page) with pill action buttons and token-style switches.\r\n *\r\n * Consumes the `settings.section` contribution contract exactly like the\r\n * official ModelsSection: props = spread inject face, where `hooks.snapshot`\r\n * arrives as the `useSnapshot` selector hook; all three seats are undefined\r\n * while the shell has not injected yet (render null then).\r\n */\r\nimport { useMemo, useState } from 'react'\nimport type { CSSProperties } from 'react'\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-store'\nimport type { ModelVisibilityCardController, ModelVisibilityState } from './controller.ts'\r\nimport type {} from './locales.ts'\r\n\r\n/** Composed props: spread face fields + the `useSnapshot` hook from hooks.snapshot. */\r\nexport interface ModelVisibilityCardProps {\r\n controller: ModelVisibilityCardController | undefined\r\n useSnapshot: SnapshotSelectorHook<ModelVisibilityState> | undefined\r\n t: TSeat | undefined\r\n}\r\n\r\n/** Localized text seat bound to this card's namespace. */\r\ntype TSeat = (key: string, params?: Record<string, unknown>) => string\r\n\r\nconst STYLE_ID = 'dsh-model-visibility-styles'\r\n\r\nconst STYLES = `\r\n.dsh-mvw { display:flex; flex-direction:column; gap:16px; }\r\n.dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }\r\n.dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }\r\n.dsh-mvw .mvw-controls { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:4px; }\r\n.dsh-mvw .mvw-search {\r\n flex:1 1 220px; min-width:160px; height:38px; padding:0 14px;\r\n font-size:13px; border-radius:10px;\r\n border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit; outline:none;\r\n}\r\n.dsh-mvw .mvw-search:focus { border-color: rgba(76,139,245,.85); }\r\n.dsh-mvw .mvw-pill {\r\n height:36px; padding:0 16px; font-size:13px; cursor:pointer;\r\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit;\r\n}\r\n.dsh-mvw .mvw-pill:hover:not(:disabled) { background:rgba(128,128,128,.08); }\r\n.dsh-mvw .mvw-pill:disabled { opacity:.45; cursor:not-allowed; }\r\n.dsh-mvw .mvw-count { font-size:12.5px; opacity:.62; margin-left:auto; }\r\n.dsh-mvw .mvw-card {\r\n border:1px solid rgba(128,128,128,.30); border-radius:16px;\r\n padding:6px 18px 2px;\r\n}\r\n.dsh-mvw .mvw-prov-head {\r\n display:flex; align-items:center; justify-content:space-between; gap:10px;\r\n padding:12px 0 10px;\r\n}\r\n.dsh-mvw .mvw-prov-name { font-size:15px; font-weight:650; }\r\n.dsh-mvw .mvw-prov-count { font-size:12px; opacity:.55; margin-left:10px; font-weight:400; }\r\n.dsh-mvw .mvw-prov-btns { display:flex; gap:8px; }\r\n.dsh-mvw .mvw-mini {\r\n height:30px; padding:0 13px; font-size:12.5px; cursor:pointer;\r\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit;\r\n}\r\n.dsh-mvw .mvw-mini:hover:not(:disabled) { background:rgba(128,128,128,.08); }\r\n.dsh-mvw .mvw-mini:disabled { opacity:.4; cursor:not-allowed; }\r\n.dsh-mvw .mvw-row {\r\n display:flex; align-items:center; justify-content:space-between; gap:12px;\r\n min-height:46px; border-top:1px solid rgba(128,128,128,.16);\r\n}\r\n.dsh-mvw .mvw-row:first-of-type { border-top:none; }\r\n.dsh-mvw .mvw-name {\r\n font-size:14px;\r\n min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\r\n}\r\n.dsh-mvw .mvw-id {\r\n opacity:.5; margin-left:8px; padding-right:6px; font-size:12px;\r\n}\r\n.dsh-mvw .mvw-note { font-size:12.5px; opacity:.66; }\r\n.dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }\r\n.dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }\r\n`\r\n\r\nfunction ensureStyles(): void {\r\n if (typeof document === 'undefined') return\r\n if (document.getElementById(STYLE_ID)) return\r\n const el = document.createElement('style')\r\n el.id = STYLE_ID\r\n el.textContent = STYLES\r\n document.head.appendChild(el)\r\n}\r\n\r\nexport function ModelVisibilityCard(props: ModelVisibilityCardProps) {\r\n const { controller, useSnapshot, t } = props\r\n\r\n // Hooks stay unconditional regardless of injection timing.\r\n const state = (useSnapshot ?? ((_selector: (s: ModelVisibilityState) => unknown) => undefined))(\r\n (snapshot: ModelVisibilityState) => snapshot,\r\n ) as ModelVisibilityState | undefined\r\n const [query, setQuery] = useState('')\r\n\r\n // Styles must exist before first paint of this subtree; guarded, so idempotent.\r\n useMemo(() => { ensureStyles() }, [])\r\n\r\n const filteredGroups = useMemo(() => {\r\n const q = query.trim().toLowerCase()\r\n if (q === '') return state?.groups ?? []\r\n return (state?.groups ?? [])\r\n .map(group => ({\r\n ...group,\r\n models: group.models.filter(model =>\r\n model.id.toLowerCase().includes(q) || model.name.toLowerCase().includes(q) || group.id.toLowerCase().includes(q)),\r\n }))\r\n .filter(group => group.models.length > 0)\r\n }, [state, query])\r\n\r\n if (controller === undefined || useSnapshot === undefined || t === undefined) return null\r\n if (state === undefined) {\r\n return (\r\n <section className=\"dsh-mvw\">\r\n <div className=\"mvw-note\">{t('loading')}</div>\r\n </section>\r\n )\r\n }\r\n\r\n return (\r\n <section className=\"dsh-mvw\">\r\n <div>\r\n <div className=\"mvw-title\">{t('title')}</div>\r\n <p className=\"mvw-sub\">{t('intro')}</p>\r\n </div>\r\n\r\n <div className=\"mvw-controls\">\r\n <input\r\n type=\"search\"\r\n className=\"mvw-search\"\r\n value={query}\r\n placeholder={t('searchPlaceholder')}\r\n onChange={event => { setQuery(event.target.value) }}\r\n />\r\n <button\r\n type=\"button\"\r\n className=\"mvw-pill\"\r\n disabled={!state.writable || state.total === state.visible}\r\n onClick={() => { controller.resetAll() }}\r\n >\r\n {t('showAllProviders')}\r\n </button>\r\n <span className=\"mvw-count\">{t('visibleCount', { visible: state.visible, total: state.total })}</span>\r\n </div>\r\n\r\n {state.phase === 'error' && (\r\n <div className=\"mvw-warn\">\r\n {t('loadFailed', { message: state.error ?? '' })}{' '}\r\n <button type=\"button\" className=\"mvw-mini\" onClick={() => { controller.load() }}>{t('retry')}</button>\r\n </div>\r\n )}\r\n\r\n {state.phase === 'ready' && !state.writable && <div className=\"mvw-note\">{t('readOnly')}</div>}\r\n {state.phase === 'ready' && state.groups.length === 0 && <div className=\"mvw-note\">{t('empty')}</div>}\r\n {state.phase === 'ready' && state.total > 0 && state.visible === 0 && (\r\n <div className=\"mvw-warn\">{t('allHiddenWarning')}</div>\r\n )}\r\n {state.phase === 'ready' && filteredGroups.length === 0 && state.groups.length > 0 && (\r\n <div className=\"mvw-note\">{t('noMatch')}</div>\r\n )}\r\n\r\n {state.phase === 'ready' && filteredGroups.map(group => {\r\n const groupVisible = group.models.filter(model => !model.hidden).length\r\n return (\r\n <div key={group.id} className=\"mvw-card\">\r\n <div className=\"mvw-prov-head\">\r\n <div>\r\n <span className=\"mvw-prov-name\">{group.name}</span>\r\n <span className=\"mvw-prov-count\">\r\n {t('providerRow', { visible: groupVisible, total: group.models.length })}\r\n </span>\r\n </div>\r\n <div className=\"mvw-prov-btns\">\r\n <button\r\n type=\"button\"\r\n className=\"mvw-mini\"\r\n disabled={!state.writable || groupVisible === 0}\r\n onClick={() => { controller.toggleProvider(group.id, true) }}\r\n >\r\n {t('hideAll')}\r\n </button>\r\n <button\r\n type=\"button\"\r\n className=\"mvw-mini\"\r\n disabled={!state.writable || groupVisible === group.models.length}\r\n onClick={() => { controller.toggleProvider(group.id, false) }}\r\n >\r\n {t('showAll')}\r\n </button>\r\n </div>\r\n </div>\r\n <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>\r\n {group.models.map(model => (\r\n <li key={model.id} className=\"mvw-row\">\r\n <span className=\"mvw-name\">\r\n {model.name}\r\n {model.name !== model.id && <span className=\"mvw-id\">{model.id}</span>}\r\n </span>\r\n <Switch\r\n on={!model.hidden}\r\n disabled={!state.writable}\r\n label={model.name}\r\n onChange={visible => { controller.toggle(group.id, model.id, model.name, !visible) }}\r\n />\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n )\r\n })}\r\n </section>\r\n )\r\n}\r\n\r\nfunction Switch(props: { on: boolean; disabled: boolean; label: string; onChange: (next: boolean) => void }) {\r\n return (\r\n <button\r\n type=\"button\"\r\n role=\"switch\"\r\n aria-checked={props.on}\r\n aria-label={props.label}\r\n disabled={props.disabled}\r\n onClick={() => { props.onChange(!props.on) }}\r\n style={{\r\n width: 40,\r\n height: 22,\r\n borderRadius: 11,\r\n border: 'none',\r\n padding: 2,\r\n cursor: props.disabled ? 'not-allowed' : 'pointer',\r\n background: props.on ? '#4c8bf5' : 'rgba(128,128,128,.45)',\r\n opacity: props.disabled ? 0.5 : 1,\r\n display: 'inline-flex',\r\n justifyContent: props.on ? 'flex-end' : 'flex-start',\r\n transition: 'background .15s',\r\n flex: 'none',\r\n }}\r\n >\r\n <span style={{ width: 18, height: 18, borderRadius: '50%', background: '#fff', display: 'block' }} />\r\n </button>\r\n )\r\n}\r\n","/**\r\n * Locale dictionaries for the model-visibility settings card.\r\n * The namespace is merged into the slot system's LocaleNamespaceMap so the\r\n * register site can declare `locale:` and receive the typed `t` seat.\r\n */\r\n\r\n/** Dictionary keys of this card's copy. */\r\nexport type ModelVisibilityLocaleKey =\r\n | 'nav'\r\n | 'title'\r\n | 'intro'\r\n | 'loading'\r\n | 'loadFailed'\r\n | 'retry'\r\n | 'empty'\r\n | 'allHiddenWarning'\r\n | 'readOnly'\r\n | 'visibleCount'\r\n | 'searchPlaceholder'\r\n | 'noMatch'\r\n | 'hideAll'\r\n | 'showAll'\r\n | 'showAllProviders'\r\n | 'providerRow'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** The model-visibility card's copy. */\r\n 'settings.modelVisibility': ModelVisibilityLocaleKey\r\n }\r\n}\r\n\r\nexport const LOCALE_NS = 'settings.modelVisibility' as const\r\n\r\nexport const zh: Record<ModelVisibilityLocaleKey, string> = {\r\n nav: '模型可见性',\r\n title: '模型可见性',\r\n intro: '控制各模型是否出现在对话界面的模型选择菜单中。隐藏一个正在使用的模型不会中断该会话,也不会改动 Provider 配置。',\r\n loading: '正在读取模型目录…',\r\n loadFailed: '模型目录读取失败:{message}',\r\n retry: '重试',\r\n empty: '没有任何 Provider 提供模型。',\r\n allHiddenWarning: '所有模型都已隐藏,模型选择菜单将是空的。',\r\n readOnly: '设置文档不可写,开关已禁用。',\r\n visibleCount: '{visible} / {total} 可见',\r\n searchPlaceholder: '搜索模型…',\r\n noMatch: '没有匹配的模型。',\r\n hideAll: '全部隐藏',\r\n showAll: '全部显示',\r\n showAllProviders: '恢复全部',\r\n providerRow: '该渠道 {visible} / {total} 可见',\r\n}\r\n\r\nexport const en: Record<ModelVisibilityLocaleKey, string> = {\r\n nav: 'Model visibility',\r\n title: 'Model visibility',\r\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.',\r\n loading: 'Loading the model catalog…',\r\n loadFailed: 'Failed to load the model catalog: {message}',\r\n retry: 'Retry',\r\n empty: 'No provider advertises any model.',\r\n allHiddenWarning: 'Every model is hidden; the model selector will be empty.',\r\n readOnly: 'The settings document is read-only; toggles are disabled.',\r\n visibleCount: '{visible} / {total} visible',\r\n searchPlaceholder: 'Search models…',\r\n noMatch: 'No model matches.',\r\n hideAll: 'Hide all',\r\n showAll: 'Show all',\r\n showAllProviders: 'Reset all',\r\n providerRow: '{visible} / {total} visible in this provider',\r\n}\r\n","/**\n * dsh-model-visibility — browser half.\n *\n * Registers the model-visibility card as a top-level settings section\n * (`settings.section`) 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 (settings), ctx.locale (locale), and the\n// `settings.section` slot declaration (ui-settings).\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport type { Config } from '../index.ts'\nimport { MODEL_VISIBILITY_NS } from '../namespace.ts'\nimport {\n ModelVisibilityCardController,\n type ModelCatalogReader,\n type ModelCatalogRemote,\n} from './controller.ts'\nimport { ModelVisibilityCard } from './ModelVisibilityCard.tsx'\nimport { LOCALE_NS, en, zh } from './locales.ts'\n\nexport const name = 'dsh-model-visibility'\n// `remote.session` carries the Host model catalog; `settingsScope` is the\n// durable hidden-list scope; `slots`/`locale` are the contribution surfaces.\nexport const inject = ['slots', 'locale', 'settingsScope', 'remote', 'remote.session'] 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_NS })\n // The assembled Remote service is typed through the api-remotes package; the\n // 0.1.5 build mangles its catalog interface name, so narrow to the wire shape\n // this plugin consumes instead of importing that declaration chain.\n const remote = (ctx as unknown as { remote: ModelCatalogRemote }).remote\n const catalog: ModelCatalogReader = {\n async read() {\n const response = await remote.session.modelCatalog()\n if (!response.ok) {\n throw new Error(`${response.error.code}: ${response.error.message}`)\n }\n return response.value.groups\n },\n }\n const controller = new ModelVisibilityCardController(scope, catalog)\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\r\n // Register as a top-level settings SECTION (nav entry 「模型可见性」, ordered\r\n // right after the official 模型 page at order=10) instead of a tab inside\r\n // the Plugins section — the card is a whole-page surface (search + batch\r\n // controls read better with room to breathe). The label thunk re-binds the\r\n // locale each call so a host language switch shows fresh text.\r\n //\r\n // Face contract for `settings.section` contributions (ui-slots): every\r\n // field of the injected object spreads onto the component as props, and\r\n // each `hooks` entry becomes a `use<Name>` snapshot-selector hook. The\r\n // component destructures { controller, useSnapshot, t } exactly like the\r\n // official ModelsSection does.\r\n const injected = () => ({\r\n controller,\r\n t,\r\n hooks: { snapshot: controller.store },\r\n })\r\n ctx.slots.inject('settings.section', () => ctx.slots.register({\r\n name: 'settings.section',\r\n id: 'model-visibility',\r\n order: 11,\r\n label: () => t('nav'),\r\n inject: injected,\r\n } as never, ModelVisibilityCard as never))\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["EMPTY: ModelVisibilityState","form: ConfigForm<VisibilitySection>","catalogReader: ModelCatalogReader","name","error: unknown","current: HiddenModel[]","provider: string","model: string","name: string","hidden: boolean","next: HiddenModel[]","id: string","groups: ProviderGroupRow[]","row: ModelRow","created: ProviderGroupRow","props: ModelVisibilityCardProps","_selector: (s: ModelVisibilityState) => unknown","snapshot: ModelVisibilityState","props: { on: boolean; disabled: boolean; label: string; onChange: (next: boolean) => void }","zh: Record<ModelVisibilityLocaleKey, string>","en: Record<ModelVisibilityLocaleKey, string>","svg: string","rowText: string | null | undefined","wantedLabel: string | null | undefined","maskUrl: string","ctx: NavIconContext","resolveLabel: () => string","ctx: Context","catalog: ModelCatalogReader"],"sources":["../src/namespace.ts","../src/client/controller.ts","../src/client/ModelVisibilityCard.tsx","../src/client/locales.ts","../src/client/settings-nav-icon.ts","../src/client/index.ts"],"sourcesContent":["/**\r\n * Shared settings namespace of the model-visibility plugin. Lives in its own\r\n * module so the browser half can name the namespace without value-importing\r\n * the Host half (which would drag the settings schema and its Node-side deps\r\n * into the client bundle).\r\n */\r\nimport type { HiddenModel } from './index.ts'\r\n\r\nexport const MODEL_VISIBILITY_NS = 'model-visibility'\r\n\r\n/**\r\n * The settings section the browser card edits: the JSON projection of the\r\n * Host `Config` section as the Settings describe mirror serves it (volatile\r\n * references are unwrapped into plain data at the form boundary).\r\n */\r\nexport interface VisibilitySection {\r\n /** Models excluded from the model-selector catalog; absent means all visible. */\r\n hidden?: HiddenModel[]\r\n}\r\n","/**\r\n * The model-visibility card's controller: React-free state over two sources —\r\n * the (already filtered) Host model catalog and the `model-visibility`\r\n * settings form. The rendered list is their union: catalog rows are visible\r\n * models; hidden entries restate the models the filter removed, labeled by\r\n * the name snapshot each entry carries. Beyond the reference shape, the\r\n * controller exposes provider-level batch writes and a whole-list write so\r\n * the card can offer search + bulk actions.\r\n */\r\nimport { createSnapshotStore } from '@deepseek-ai/dsh-client-store'\r\nimport type { SnapshotStore } from '@deepseek-ai/dsh-client-store'\r\nimport type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'\r\nimport type { ModelProviderGroup } from '@deepseek-ai/dsh-api-remotes/client'\r\nimport type { HiddenModel } from '../index.ts'\r\nimport type { VisibilitySection } from '../namespace.ts'\r\n\r\n/** One toggle row. */\r\nexport interface ModelRow {\r\n id: string\r\n name: string\r\n hidden: boolean\r\n}\r\n\r\n/** One provider group of rows. */\r\nexport interface ProviderGroupRow {\r\n id: string\r\n name: string\r\n models: ModelRow[]\r\n}\r\n\r\n/** What the card renders. */\r\nexport interface ModelVisibilityState {\r\n phase: 'loading' | 'ready' | 'error' | 'unavailable'\r\n error?: string\r\n /** Whether the Host settings document accepts writes. */\r\n writable: boolean\r\n groups: ProviderGroupRow[]\r\n /** Union count and how many of them remain visible. */\r\n total: number\r\n visible: number\r\n}\r\n\r\n/** Injected catalog reader: decouples the controller from the Remote wire shape. */\r\nexport interface ModelCatalogReader {\r\n read(): Promise<readonly ModelProviderGroup[]>\r\n}\r\n\r\n/** The face the slot registration injects into the card component. */\r\nexport interface ModelVisibilityFace {\r\n hooks: {\r\n /** Card snapshot bound by the renderer as useModelVisibility. */\r\n modelVisibility: SnapshotStore<ModelVisibilityState>\r\n }\r\n /** Flip one model's visibility. */\r\n toggle: (provider: string, model: string, name: string, hidden: boolean) => void\r\n /** Hide or show every model of one provider. */\r\n toggleProvider: (provider: string, hidden: boolean) => void\r\n /** Clear the hidden list entirely (everything visible again). */\r\n resetAll: () => void\r\n /** Re-read the catalog after a failure. */\r\n reload: () => void\r\n}\r\n\r\nconst EMPTY: ModelVisibilityState = { phase: 'loading', writable: false, groups: [], total: 0, visible: 0 }\r\n\r\nexport class ModelVisibilityCardController {\r\n /** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */\r\n readonly store = createSnapshotStore<ModelVisibilityState>(EMPTY)\r\n private catalog: ModelProviderGroup[] | undefined\r\n private loadError: string | undefined\r\n private generation = 0\r\n private readonly unsubscribe: () => void\r\n\r\n constructor(\r\n private readonly form: ConfigForm<VisibilitySection>,\r\n private readonly catalogReader: ModelCatalogReader,\r\n ) {\r\n this.unsubscribe = form.subscribe(() => { this.recompute() })\r\n void this.load()\r\n }\r\n\r\n /** The registration-side inject face. */\r\n face(): ModelVisibilityFace {\r\n return {\r\n hooks: { modelVisibility: this.store },\r\n toggle: (provider, model, name, hidden) => { void this.toggle(provider, model, name, hidden) },\r\n toggleProvider: (provider, hidden) => { void this.toggleProvider(provider, hidden) },\r\n resetAll: () => { void this.resetAll() },\r\n reload: () => { void this.load() },\r\n }\r\n }\r\n\r\n /** Re-read the catalog; an older response never overwrites a newer one. */\r\n async load(): Promise<void> {\r\n const generation = ++this.generation\r\n if (this.catalog === undefined) this.store.update((draft) => { draft.phase = 'loading' })\r\n try {\r\n const groups = await this.catalogReader.read()\r\n if (generation !== this.generation) return\r\n this.catalog = groups.map(group => ({\r\n id: group.id,\r\n name: group.name,\r\n models: group.models.map(model => ({ id: model.id, name: model.name })),\r\n }))\r\n this.loadError = undefined\r\n } catch (error: unknown) {\r\n if (generation !== this.generation) return\r\n this.loadError = error instanceof Error ? error.message : String(error)\r\n }\r\n this.recompute()\r\n }\r\n\r\n /** Stop following the settings form (plugin unload). */\r\n dispose(): void {\r\n this.unsubscribe()\r\n }\r\n\r\n /** Compute the next hidden list from the current one plus one model's flip. */\r\n private nextHidden(current: HiddenModel[], provider: string, model: string, name: string, hidden: boolean): HiddenModel[] {\r\n const rest = current.filter(entry => !(entry.provider === provider && entry.model === model))\r\n return hidden ? [...rest, { provider, model, name }] : rest\r\n }\r\n\r\n /** Write the next hidden list, then re-pull the catalog the filter changed. */\r\n private async write(next: HiddenModel[]): Promise<void> {\r\n try {\r\n await this.form.set('hidden', next)\r\n } catch {\r\n // A rejected latest write makes the form reload Host state; the\r\n // recompute below re-renders whatever the mirror now reports.\r\n }\r\n await this.load()\r\n }\r\n\r\n /** Flip one model's visibility (public API — the section card calls it directly). */\r\n async toggle(provider: string, model: string, name: string, hidden: boolean): Promise<void> {\r\n const current = this.form.getSnapshot().value?.hidden ?? []\r\n await this.write(this.nextHidden(current, provider, model, name, hidden))\r\n }\r\n\r\n /**\r\n * Batch flip for one provider. Models are taken from the union of the\r\n * (filtered) catalog rows and existing hidden entries so a provider with\r\n * every model already hidden still flips back fully.\r\n */\r\n /** Batch flip for one provider (public API). */\r\n async toggleProvider(provider: string, hidden: boolean): Promise<void> {\r\n const current = this.form.getSnapshot().value?.hidden ?? []\r\n const providerRows = this.stateRows().find(group => group.id === provider)\r\n const ids = new Set<string>([\r\n ...providerRows ? providerRows.models.map(model => model.id) : [],\r\n ...current.filter(entry => entry.provider === provider).map(entry => entry.model),\r\n ])\r\n if (ids.size === 0) return\r\n let next = current.filter(entry => entry.provider !== provider)\r\n if (hidden) {\r\n const nameOf = (id: string): string => providerRows?.models.find(model => model.id === id)?.name ?? id\r\n next = [...next, ...[...ids].map(id => ({ provider, model: id, name: nameOf(id) }))]\r\n }\r\n await this.write(next)\r\n }\r\n\r\n /** Clear the hidden list entirely (public API). */\r\n async resetAll(): Promise<void> {\r\n await this.write([])\r\n }\r\n\r\n /** Current union rows (catalog ∪ hidden) without touching the store. */\r\n private stateRows(): ProviderGroupRow[] {\r\n const snapshot = this.form.getSnapshot()\r\n const hidden = snapshot.value?.hidden ?? []\r\n if (this.catalog === undefined) return []\r\n const hiddenKeys = new Set(hidden.map(entry => `${entry.provider}\\u0000${entry.model}`))\r\n const groups: ProviderGroupRow[] = this.catalog.map(group => ({\r\n id: group.id,\r\n name: group.name,\r\n models: group.models.map(model => ({\r\n id: model.id,\r\n name: model.name,\r\n hidden: hiddenKeys.has(`${group.id}\\u0000${model.id}`),\r\n })),\r\n }))\r\n const groupsById = new Map(groups.map(group => [group.id, group]))\r\n for (const entry of hidden) {\r\n const group = groupsById.get(entry.provider)\r\n if (group?.models.some(model => model.id === entry.model)) continue\r\n const row: ModelRow = { id: entry.model, name: entry.name ?? entry.model, hidden: true }\r\n if (group !== undefined) {\r\n group.models.push(row)\r\n } else {\r\n const created: ProviderGroupRow = { id: entry.provider, name: entry.provider, models: [row] }\r\n groupsById.set(entry.provider, created)\r\n groups.push(created)\r\n }\r\n }\r\n return groups\r\n }\r\n\r\n /** Union the filtered catalog with the hidden entries into render rows. */\r\n private recompute(): void {\r\n const snapshot = this.form.getSnapshot()\r\n if (snapshot.status === 'unavailable') {\r\n // The namespace left the describe mirror (Host half unloading), or this\r\n // connection keeps settings process-local (memory mode never answers).\r\n this.store.set({ ...EMPTY, phase: 'unavailable', writable: snapshot.writable })\r\n return\r\n }\r\n if (this.catalog === undefined && this.loadError !== undefined) {\r\n this.store.set({ ...EMPTY, phase: 'error', error: this.loadError, writable: snapshot.writable })\r\n return\r\n }\r\n if (this.catalog === undefined || snapshot.status === 'loading') {\r\n this.store.set({ ...EMPTY, writable: snapshot.writable })\r\n return\r\n }\r\n const groups = this.stateRows()\r\n const total = groups.reduce((count, group) => count + group.models.length, 0)\r\n const visible = groups.reduce(\r\n (count, group) => count + group.models.filter(model => !model.hidden).length, 0)\r\n this.store.set({\r\n phase: 'ready',\r\n writable: snapshot.writable,\r\n groups,\r\n total,\r\n visible,\r\n })\r\n }\r\n}\r\n","/**\r\n * The model-visibility settings section, styled to mirror the official\r\n * settings pages: big page title + muted intro, a controls row, then one\r\n * bordered rounded card per provider (like the provider cards on the\r\n * 模型 page) with pill action buttons and token-style switches.\r\n *\r\n * Consumes the `settings.section` contribution contract exactly like the\r\n * official ModelsSection: props = spread inject face, where `hooks.snapshot`\r\n * arrives as the `useSnapshot` selector hook; all three seats are undefined\r\n * while the shell has not injected yet (render null then).\r\n */\r\nimport { useMemo, useState } from 'react'\r\nimport type { CSSProperties } from 'react'\r\nimport type { SnapshotSelectorHook } from '@deepseek-ai/dsh-client-store'\r\nimport type { ModelVisibilityCardController, ModelVisibilityState } from './controller.ts'\r\nimport type {} from './locales.ts'\r\n\r\n/** Composed props: spread face fields + the `useSnapshot` hook from hooks.snapshot. */\r\nexport interface ModelVisibilityCardProps {\r\n controller: ModelVisibilityCardController | undefined\r\n useSnapshot: SnapshotSelectorHook<ModelVisibilityState> | undefined\r\n t: TSeat | undefined\r\n}\r\n\r\n/** Localized text seat bound to this card's namespace. */\r\ntype TSeat = (key: string, params?: Record<string, unknown>) => string\r\n\r\nconst STYLE_ID = 'dsh-model-visibility-styles'\r\n\r\nconst STYLES = `\r\n.dsh-mvw { display:flex; flex-direction:column; gap:16px; }\r\n.dsh-mvw .mvw-title { font-size:20px; font-weight:700; letter-spacing:.01em; }\r\n.dsh-mvw .mvw-sub { font-size:13px; color:inherit; opacity:.62; margin-top:6px; max-width:64ch; line-height:1.55; }\r\n.dsh-mvw .mvw-controls { display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin-top:4px; }\r\n.dsh-mvw .mvw-search {\r\n flex:1 1 220px; min-width:160px; height:38px; padding:0 14px;\r\n font-size:13px; border-radius:10px;\r\n border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit; outline:none;\r\n}\r\n.dsh-mvw .mvw-search:focus { border-color: rgba(76,139,245,.85); }\r\n.dsh-mvw .mvw-pill {\r\n height:36px; padding:0 16px; font-size:13px; cursor:pointer;\r\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit;\r\n}\r\n.dsh-mvw .mvw-pill:hover:not(:disabled) { background:rgba(128,128,128,.08); }\r\n.dsh-mvw .mvw-pill:disabled { opacity:.45; cursor:not-allowed; }\r\n.dsh-mvw .mvw-count { font-size:12.5px; opacity:.62; margin-left:auto; }\r\n.dsh-mvw .mvw-card {\r\n border:1px solid rgba(128,128,128,.30); border-radius:16px;\r\n padding:6px 18px 2px;\r\n}\r\n.dsh-mvw .mvw-prov-head {\r\n display:flex; align-items:center; justify-content:space-between; gap:10px;\r\n padding:12px 0 10px;\r\n}\r\n.dsh-mvw .mvw-prov-name { font-size:15px; font-weight:650; }\r\n.dsh-mvw .mvw-prov-count { font-size:12px; opacity:.55; margin-left:10px; font-weight:400; }\r\n.dsh-mvw .mvw-prov-btns { display:flex; gap:8px; }\r\n.dsh-mvw .mvw-mini {\r\n height:30px; padding:0 13px; font-size:12.5px; cursor:pointer;\r\n border-radius:999px; border:1px solid rgba(128,128,128,.38);\r\n background:transparent; color:inherit;\r\n}\r\n.dsh-mvw .mvw-mini:hover:not(:disabled) { background:rgba(128,128,128,.08); }\r\n.dsh-mvw .mvw-mini:disabled { opacity:.4; cursor:not-allowed; }\r\n.dsh-mvw .mvw-row {\r\n display:flex; align-items:center; justify-content:space-between; gap:12px;\r\n min-height:46px; border-top:1px solid rgba(128,128,128,.16);\r\n}\r\n.dsh-mvw .mvw-row:first-of-type { border-top:none; }\r\n.dsh-mvw .mvw-name {\r\n font-size:14px;\r\n min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;\r\n}\r\n.dsh-mvw .mvw-id {\r\n opacity:.5; margin-left:8px; padding-right:6px; font-size:12px;\r\n}\r\n.dsh-mvw .mvw-note { font-size:12.5px; opacity:.66; }\r\n.dsh-mvw .mvw-warn { font-size:12.5px; color:#c77700; }\r\n.dsh-mvw .mvw-seats-off { font-size:12.5px; opacity:.66; }\r\n`\r\n\r\nfunction ensureStyles(): void {\r\n if (typeof document === 'undefined') return\r\n if (document.getElementById(STYLE_ID)) return\r\n const el = document.createElement('style')\r\n el.id = STYLE_ID\r\n el.textContent = STYLES\r\n document.head.appendChild(el)\r\n}\r\n\r\nexport function ModelVisibilityCard(props: ModelVisibilityCardProps) {\r\n const { controller, useSnapshot, t } = props\r\n\r\n // Hooks stay unconditional regardless of injection timing.\r\n const state = (useSnapshot ?? ((_selector: (s: ModelVisibilityState) => unknown) => undefined))(\r\n (snapshot: ModelVisibilityState) => snapshot,\r\n ) as ModelVisibilityState | undefined\r\n const [query, setQuery] = useState('')\r\n\r\n // Styles must exist before first paint of this subtree; guarded, so idempotent.\r\n useMemo(() => { ensureStyles() }, [])\r\n\r\n const filteredGroups = useMemo(() => {\r\n const q = query.trim().toLowerCase()\r\n if (q === '') return state?.groups ?? []\r\n return (state?.groups ?? [])\r\n .map(group => ({\r\n ...group,\r\n models: group.models.filter(model =>\r\n model.id.toLowerCase().includes(q) || model.name.toLowerCase().includes(q) || group.id.toLowerCase().includes(q)),\r\n }))\r\n .filter(group => group.models.length > 0)\r\n }, [state, query])\r\n\r\n if (controller === undefined || useSnapshot === undefined || t === undefined) return null\r\n if (state === undefined) {\r\n return (\r\n <section className=\"dsh-mvw\">\r\n <div className=\"mvw-note\">{t('loading')}</div>\r\n </section>\r\n )\r\n }\r\n\r\n return (\r\n <section className=\"dsh-mvw\">\r\n <div>\r\n <div className=\"mvw-title\">{t('title')}</div>\r\n <p className=\"mvw-sub\">{t('intro')}</p>\r\n </div>\r\n\r\n <div className=\"mvw-controls\">\r\n <input\r\n type=\"search\"\r\n className=\"mvw-search\"\r\n value={query}\r\n placeholder={t('searchPlaceholder')}\r\n onChange={event => { setQuery(event.target.value) }}\r\n />\r\n <button\r\n type=\"button\"\r\n className=\"mvw-pill\"\r\n disabled={!state.writable || state.total === state.visible}\r\n onClick={() => { controller.resetAll() }}\r\n >\r\n {t('showAllProviders')}\r\n </button>\r\n <span className=\"mvw-count\">{t('visibleCount', { visible: state.visible, total: state.total })}</span>\r\n </div>\r\n\r\n {state.phase === 'error' && (\r\n <div className=\"mvw-warn\">\r\n {t('loadFailed', { message: state.error ?? '' })}{' '}\r\n <button type=\"button\" className=\"mvw-mini\" onClick={() => { controller.load() }}>{t('retry')}</button>\r\n </div>\r\n )}\r\n\r\n {state.phase === 'unavailable' && <div className=\"mvw-note\">{t('unavailable')}</div>}\r\n {state.phase === 'ready' && !state.writable && <div className=\"mvw-note\">{t('readOnly')}</div>}\r\n {state.phase === 'ready' && state.groups.length === 0 && <div className=\"mvw-note\">{t('empty')}</div>}\r\n {state.phase === 'ready' && state.total > 0 && state.visible === 0 && (\r\n <div className=\"mvw-warn\">{t('allHiddenWarning')}</div>\r\n )}\r\n {state.phase === 'ready' && filteredGroups.length === 0 && state.groups.length > 0 && (\r\n <div className=\"mvw-note\">{t('noMatch')}</div>\r\n )}\r\n\r\n {state.phase === 'ready' && filteredGroups.map(group => {\r\n const groupVisible = group.models.filter(model => !model.hidden).length\r\n return (\r\n <div key={group.id} className=\"mvw-card\">\r\n <div className=\"mvw-prov-head\">\r\n <div>\r\n <span className=\"mvw-prov-name\">{group.name}</span>\r\n <span className=\"mvw-prov-count\">\r\n {t('providerRow', { visible: groupVisible, total: group.models.length })}\r\n </span>\r\n </div>\r\n <div className=\"mvw-prov-btns\">\r\n <button\r\n type=\"button\"\r\n className=\"mvw-mini\"\r\n disabled={!state.writable || groupVisible === 0}\r\n onClick={() => { controller.toggleProvider(group.id, true) }}\r\n >\r\n {t('hideAll')}\r\n </button>\r\n <button\r\n type=\"button\"\r\n className=\"mvw-mini\"\r\n disabled={!state.writable || groupVisible === group.models.length}\r\n onClick={() => { controller.toggleProvider(group.id, false) }}\r\n >\r\n {t('showAll')}\r\n </button>\r\n </div>\r\n </div>\r\n <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>\r\n {group.models.map(model => (\r\n <li key={model.id} className=\"mvw-row\">\r\n <span className=\"mvw-name\">\r\n {model.name}\r\n {model.name !== model.id && <span className=\"mvw-id\">{model.id}</span>}\r\n </span>\r\n <Switch\r\n on={!model.hidden}\r\n disabled={!state.writable}\r\n label={model.name}\r\n onChange={visible => { controller.toggle(group.id, model.id, model.name, !visible) }}\r\n />\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n )\r\n })}\r\n </section>\r\n )\r\n}\r\n\r\nfunction Switch(props: { on: boolean; disabled: boolean; label: string; onChange: (next: boolean) => void }) {\r\n return (\r\n <button\r\n type=\"button\"\r\n role=\"switch\"\r\n aria-checked={props.on}\r\n aria-label={props.label}\r\n disabled={props.disabled}\r\n onClick={() => { props.onChange(!props.on) }}\r\n style={{\r\n width: 40,\r\n height: 22,\r\n borderRadius: 11,\r\n border: 'none',\r\n padding: 2,\r\n cursor: props.disabled ? 'not-allowed' : 'pointer',\r\n background: props.on ? '#4c8bf5' : 'rgba(128,128,128,.45)',\r\n opacity: props.disabled ? 0.5 : 1,\r\n display: 'inline-flex',\r\n justifyContent: props.on ? 'flex-end' : 'flex-start',\r\n transition: 'background .15s',\r\n flex: 'none',\r\n }}\r\n >\r\n <span style={{ width: 18, height: 18, borderRadius: '50%', background: '#fff', display: 'block' }} />\r\n </button>\r\n )\r\n}\r\n","/**\r\n * Locale dictionaries for the model-visibility settings card.\r\n * The namespace is merged into the slot system's LocaleNamespaceMap so the\r\n * register site can declare `locale:` and receive the typed `t` seat.\r\n */\r\n\r\n/** Dictionary keys of this card's copy. */\r\nexport type ModelVisibilityLocaleKey =\r\n | 'nav'\r\n | 'title'\r\n | 'intro'\r\n | 'loading'\r\n | 'loadFailed'\r\n | 'retry'\r\n | 'unavailable'\r\n | 'empty'\r\n | 'allHiddenWarning'\r\n | 'readOnly'\r\n | 'visibleCount'\r\n | 'searchPlaceholder'\r\n | 'noMatch'\r\n | 'hideAll'\r\n | 'showAll'\r\n | 'showAllProviders'\r\n | 'providerRow'\r\n\r\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\r\n interface LocaleNamespaceMap {\r\n /** The model-visibility card's copy. */\r\n 'settings.modelVisibility': ModelVisibilityLocaleKey\r\n }\r\n}\r\n\r\nexport const LOCALE_NS = 'settings.modelVisibility' as const\r\n\r\nexport const zh: Record<ModelVisibilityLocaleKey, string> = {\r\n nav: '模型可见性',\r\n title: '模型可见性',\r\n intro: '控制各模型是否出现在对话界面的模型选择菜单中。隐藏一个正在使用的模型不会中断该会话,也不会改动 Provider 配置。',\r\n loading: '正在读取模型目录…',\r\n loadFailed: '模型目录读取失败:{message}',\r\n retry: '重试',\r\n unavailable: '此连接不共享宿主设置,模型可见性不可用。',\r\n empty: '没有任何 Provider 提供模型。',\r\n allHiddenWarning: '所有模型都已隐藏,模型选择菜单将是空的。',\r\n readOnly: '设置文档不可写,开关已禁用。',\r\n visibleCount: '{visible} / {total} 可见',\r\n searchPlaceholder: '搜索模型…',\r\n noMatch: '没有匹配的模型。',\r\n hideAll: '全部隐藏',\r\n showAll: '全部显示',\r\n showAllProviders: '恢复全部',\r\n providerRow: '该渠道 {visible} / {total} 可见',\r\n}\r\n\r\nexport const en: Record<ModelVisibilityLocaleKey, string> = {\r\n nav: 'Model visibility',\r\n title: 'Model visibility',\r\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.',\r\n loading: 'Loading the model catalog…',\r\n loadFailed: 'Failed to load the model catalog: {message}',\r\n retry: 'Retry',\r\n unavailable: 'Model visibility is unavailable: this connection does not share the Host settings.',\r\n empty: 'No provider advertises any model.',\r\n allHiddenWarning: 'Every model is hidden; the model selector will be empty.',\r\n readOnly: 'The settings document is read-only; toggles are disabled.',\r\n visibleCount: '{visible} / {total} visible',\r\n searchPlaceholder: 'Search models…',\r\n noMatch: 'No model matches.',\r\n hideAll: 'Hide all',\r\n showAll: 'Show all',\r\n showAllProviders: 'Reset all',\r\n providerRow: '{visible} / {total} visible in this provider',\r\n}\r\n","/**\n * The eye glyph in the settings navigation.\n *\n * The settings shell picks nav glyphs from a closed list of section ids\n * (`account`, `models`, `agent-presets`, `plugins`, `archived-sessions`) and\n * falls back to its own gear for every other id; `settings.section` projects\n * only `id` / `order` / `label`, so a registrant has no icon to pass — the\n * slot contract in `@deepseek-ai/dsh-client-ui-settings` and the runtime\n * slot inventory both list exactly those options. Every third-party section\n * therefore wears the gear, this one included.\n *\n * So this card claims its own row once the dialog is mounted and swaps the\n * fallback gear for an eye — the same glyph the feature is about. The\n * approach follows the precedent set by `dshmarket` (and credited there to\n * `dsh-better-sidebar` / `dsh-skill-mcp-panel`).\n *\n * Scope, deliberately narrow:\n *\n * - only the row whose visible text equals this plugin's own localized\n * section label is marked; no shell structure is touched;\n * - the marker and the injected stylesheet belong to a `ctx.effect`, so they\n * are removed with the fiber;\n * - a locale switch re-claims the row through the MutationObserver, so the\n * label and the glyph never disagree.\n *\n * Delete this module (and its call in index.ts) the day `settings.section`\n * grows an `icon` field.\n */\n\n/** Marks the one nav row this plugin owns. */\nexport const NAV_ICON_MARKER = 'data-dsh-model-visibility-nav-icon'\n\n/**\n * The nav rows of the settings dialog. The shell renders each\n * `settings.section` entry as a `<button>` inside the panel's `<nav>`\n * (SettingsPanel in dsh-client-ui-settings-general).\n */\nexport const NAV_ROW_SELECTOR = '[role=\"dialog\"] nav button'\n\n/**\n * Glyph box in px. The shell renders every nav icon at this size and ships no\n * media query at all, so there is deliberately no narrow variant here.\n */\nexport const NAV_ICON_SIZE = 16\n\n/**\n * The eye as standalone SVG for a CSS `mask-image`.\n *\n * Painted pure black on purpose: a mask reads alpha only, and the visible\n * colour comes from the element's `background-color: currentColor`, which\n * keeps the glyph in step with the shell's own icons on hover/active state.\n * The shape is the classic outline eye (almond contour + iris) on a 24 grid;\n * `mask-size` scales it down to the shell's 16px box.\n */\nexport function eyeMaskSvg(): string {\n return '<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\"'\n + ' stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">'\n + '<path d=\"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0'\n + ' 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0\"/>'\n + '<circle cx=\"12\" cy=\"12\" r=\"3\"/>'\n + '</svg>'\n}\n\n/** The mask URL for the glyph (encoded at runtime, never hand-escaped). */\nexport function eyeMaskUrl(svg: string = eyeMaskSvg()): string {\n return `data:image/svg+xml,${encodeURIComponent(svg)}`\n}\n\n/**\n * Whether a nav row is this plugin's own.\n *\n * Pure, and the only decision this feature makes: the row whose visible text\n * is the section label the shell is currently projecting. An empty label\n * matches nothing — a locale that has not resolved yet must not mark the\n * whole nav.\n */\nexport function isOwnNavRow(rowText: string | null | undefined, wantedLabel: string | null | undefined): boolean {\n const wanted = String(wantedLabel ?? '').trim()\n if (wanted.length === 0) return false\n return String(rowText ?? '').trim() === wanted\n}\n\n/** Stylesheet for the marked row: hide the shell's gear, draw the eye. */\nexport function navIconCss(maskUrl: string): string {\n return [\n `[${NAV_ICON_MARKER}] > svg { display: none; }`,\n `[${NAV_ICON_MARKER}]::before {`,\n ` content: '';`,\n ` flex: none;`,\n ` width: ${NAV_ICON_SIZE}px;`,\n ` height: ${NAV_ICON_SIZE}px;`,\n ` background-color: currentColor;`,\n ` -webkit-mask-image: url(\"${maskUrl}\");`,\n ` mask-image: url(\"${maskUrl}\");`,\n ` -webkit-mask-repeat: no-repeat;`,\n ` mask-repeat: no-repeat;`,\n ` -webkit-mask-position: center;`,\n ` mask-position: center;`,\n ` -webkit-mask-size: ${NAV_ICON_SIZE}px ${NAV_ICON_SIZE}px;`,\n ` mask-size: ${NAV_ICON_SIZE}px ${NAV_ICON_SIZE}px;`,\n `}`,\n ].join('\\n')\n}\n\n/** The slice of the client context this feature needs. */\nexport interface NavIconContext {\n effect(callback: () => unknown, label?: string): void\n}\n\n/**\n * Install the nav glyph.\n *\n * @param ctx - client context, for effect ownership.\n * @param resolveLabel - this plugin's current section label (the same thunk\n * the `settings.section` registration passes), re-read on every sync so a\n * locale switch is picked up without re-registering.\n */\nexport function installSettingsNavIcon(ctx: NavIconContext, resolveLabel: () => string): void {\n if (typeof document === 'undefined') return\n\n ctx.effect(() => {\n const tag = document.createElement('style')\n tag.dataset.plugin = 'dsh-model-visibility'\n tag.dataset.pluginCss = 'dsh-model-visibility/settings-nav-icon'\n tag.textContent = navIconCss(eyeMaskUrl())\n document.head.appendChild(tag)\n\n let disposed = false\n let scheduled = false\n\n const sync = () => {\n scheduled = false\n if (disposed) return\n const wanted = resolveLabel()\n for (const row of document.querySelectorAll(NAV_ROW_SELECTOR)) {\n if (isOwnNavRow(row.textContent, wanted)) row.setAttribute(NAV_ICON_MARKER, '')\n else row.removeAttribute(NAV_ICON_MARKER)\n }\n }\n\n // Coalesce a burst of DOM mutations into one sync, and land it before the\n // next paint so the row never shows the gear first.\n const schedule = () => {\n if (scheduled || disposed) return\n scheduled = true\n queueMicrotask(sync)\n }\n\n sync()\n const observer = new MutationObserver(schedule)\n observer.observe(document.body, { childList: true, subtree: true, characterData: true })\n\n return () => {\n disposed = true\n observer.disconnect()\n for (const row of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) row.removeAttribute(NAV_ICON_MARKER)\n tag.remove()\n }\n }, 'model-visibility: settings nav icon')\n}\n","/**\r\n * dsh-model-visibility — browser half.\r\n *\r\n * Registers the model-visibility card as a top-level settings section\r\n * (`settings.section`) and its locale dictionaries. Cross-plugin\r\n * collaboration goes through cordis services only: the slot key's\r\n * declaration, the settings scope, and the wire handle arrive as type-only\r\n * merges, never value imports (the loader bundle-purity rule).\r\n *\r\n * 0.2.0: the `settingsScope` service is gone; the card's durable state rides\r\n * `ctx.configForms` (the shared describe mirror over the Host form\r\n * projection), and the Remote namespace carries formal types, so the wire\r\n * shape is no longer restated locally.\r\n */\r\nimport type { Context } from '@deepseek-ai/cordis'\r\n// Type-only merges: ctx.configForms (ui-settings), ctx.locale (locale),\r\n// ctx.slots (ui-renderer), and the `settings.section` slot declaration.\r\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\r\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\r\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\r\n// Type-only: ctx.remote with the formal `session.modelCatalog()` signature.\r\nimport type {} from '@deepseek-ai/dsh-api-remotes/client'\r\nimport { MODEL_VISIBILITY_NS, type VisibilitySection } from '../namespace.ts'\r\nimport { ModelVisibilityCardController, type ModelCatalogReader } from './controller.ts'\nimport { ModelVisibilityCard } from './ModelVisibilityCard.tsx'\nimport { LOCALE_NS, en, zh } from './locales.ts'\nimport { installSettingsNavIcon } from './settings-nav-icon.ts'\n\r\nexport const name = 'dsh-model-visibility'\r\n// `remote.session` carries the Host model catalog; `configForms` is the\r\n// durable hidden-list form; `slots`/`locale` are the contribution surfaces.\r\nexport const inject = ['slots', 'locale', 'configForms', 'remote', 'remote.session'] as const\r\n\r\nexport function apply(ctx: Context): void {\r\n ctx.effect(() => ctx.locale.register(LOCALE_NS, { zh, en }), 'model-visibility: copy dictionaries')\r\n\r\n // The shared form over this plugin's own profile entry (id from\r\n // cordis.patch.yml); writes serialize through the Host settings document.\r\n const form = ctx.configForms.get<VisibilitySection>(MODEL_VISIBILITY_NS)\r\n const catalog: ModelCatalogReader = {\r\n async read() {\r\n const response = await ctx.remote.session.modelCatalog()\r\n if (!response.ok) {\r\n throw new Error(`${response.error.code}: ${response.error.message}`)\r\n }\r\n return response.value.groups\r\n },\r\n }\r\n const controller = new ModelVisibilityCardController(form, catalog)\r\n ctx.effect(() => () => { controller.dispose() }, 'model-visibility: card controller')\r\n\r\n // Keep the catalog fresh on pushed invalidations (the same channels the\r\n // official Models page listens to): provider-topology changes, and settings\r\n // writes — a provider's model-catalog edit (renames included) commits\r\n // volatile-only, which emits NO adapter event, so the settings document\r\n // channel is the only signal. Our own hidden-list write already re-reads\r\n // the catalog, so it is filtered out to avoid a redundant fetch.\r\n ctx.effect(() => {\r\n const offAdapters = ctx.remote.$on('llm/adapters-updated', () => { void controller.load() })\r\n const offSettings = ctx.remote.$on('settings/document-updated', (ns) => {\r\n if (ns !== MODEL_VISIBILITY_NS) void controller.load()\r\n })\r\n return () => {\r\n offAdapters()\r\n offSettings()\r\n }\r\n }, 'model-visibility: catalog invalidation')\r\n\r\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 // The section's nav glyph. The shell picks nav icons from its own built-in\n // list of section ids and falls back to the settings gear, and a\n // `settings.section` registration has no icon to pass — so the card claims\n // its own row and swaps the gear for an eye. Same label thunk as the\n // registration below, so the row is re-claimed when the locale changes.\n installSettingsNavIcon(ctx, () => t('nav'))\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\r\n // controls read better with room to breathe). The label thunk re-binds the\r\n // locale each call so a host language switch shows fresh text.\r\n //\r\n // Face contract for `settings.section` contributions (ui-slots): every\r\n // field of the injected object spreads onto the component as props, and\r\n // each `hooks` entry becomes a `use<Name>` snapshot-selector hook. The\r\n // component destructures { controller, useSnapshot, t } exactly like the\r\n // official ModelsSection does.\r\n const injected = () => ({\r\n controller,\r\n t,\r\n hooks: { snapshot: controller.store },\r\n })\r\n // Register the section only while the Host serves this plugin's namespace:\r\n // a deployment without the Host half shows no trace of the page instead of\r\n // a card stuck on its loading row.\r\n ctx.effect(\r\n () => ctx.configForms.whileServed([MODEL_VISIBILITY_NS], () =>\r\n ctx.slots.inject('settings.section', () => ctx.slots.register({\r\n name: 'settings.section',\r\n id: 'model-visibility',\r\n order: 11,\r\n label: () => t('nav'),\r\n inject: injected,\r\n } as never, ModelVisibilityCard as never))),\r\n 'model-visibility: settings section',\r\n )\r\n}\r\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
|
|
2
3
|
//#region src/namespace.ts
|
|
3
|
-
/**
|
|
4
|
-
* Shared settings namespace of the model-visibility plugin. Lives in its own
|
|
5
|
-
* module so the browser half can name the namespace without value-importing
|
|
6
|
-
* the Host half (which would drag the settings schema and its Node-side deps
|
|
7
|
-
* into the client bundle).
|
|
8
|
-
*/
|
|
9
4
|
const MODEL_VISIBILITY_NS = "model-visibility";
|
|
5
|
+
|
|
10
6
|
//#endregion
|
|
11
7
|
//#region src/index.ts
|
|
12
8
|
const name = "dsh-model-visibility";
|
|
13
|
-
const inject = ["llm"
|
|
14
|
-
const
|
|
9
|
+
const inject = ["llm"];
|
|
10
|
+
/** Field schemas, named so the const below can state its exact input/output pair. */
|
|
11
|
+
const fields = { hidden: z.array(z.object({
|
|
15
12
|
provider: z.string().required(),
|
|
16
13
|
model: z.string().required(),
|
|
17
14
|
name: z.string()
|
|
18
|
-
})).default([]) }
|
|
15
|
+
})).default([]).volatile() };
|
|
16
|
+
const Config = z.object(fields);
|
|
19
17
|
function apply(ctx, config) {
|
|
20
|
-
|
|
18
|
+
ctx.inject(["settings"], (child) => {
|
|
19
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
20
|
+
});
|
|
21
21
|
const llm = ctx.llm;
|
|
22
22
|
const original = llm.listModels.bind(llm);
|
|
23
23
|
ctx.effect(() => {
|
|
24
24
|
Object.defineProperty(llm, "listModels", {
|
|
25
25
|
value: async (provider) => {
|
|
26
26
|
const models = await original(provider);
|
|
27
|
-
const hidden = new Set(
|
|
27
|
+
const hidden = new Set(config.hidden.get().filter((entry) => entry.provider === provider).map((entry) => entry.model));
|
|
28
28
|
if (hidden.size === 0) return models;
|
|
29
29
|
return models.filter((model) => !hidden.has(model.id));
|
|
30
30
|
},
|
|
@@ -36,5 +36,6 @@ function apply(ctx, config) {
|
|
|
36
36
|
};
|
|
37
37
|
}, "model-visibility: listModels catalog filter");
|
|
38
38
|
}
|
|
39
|
+
|
|
39
40
|
//#endregion
|
|
40
|
-
export { Config, MODEL_VISIBILITY_NS, apply, inject, name };
|
|
41
|
+
export { Config, MODEL_VISIBILITY_NS, apply, inject, name };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
3
|
+
import type { ModelProviderGroup } from '@deepseek-ai/dsh-api-remotes/client';
|
|
4
|
+
import type { VisibilitySection } from '../namespace.ts';
|
|
4
5
|
/** One toggle row. */
|
|
5
6
|
export interface ModelRow {
|
|
6
7
|
id: string;
|
|
@@ -15,7 +16,7 @@ export interface ProviderGroupRow {
|
|
|
15
16
|
}
|
|
16
17
|
/** What the card renders. */
|
|
17
18
|
export interface ModelVisibilityState {
|
|
18
|
-
phase: 'loading' | 'ready' | 'error';
|
|
19
|
+
phase: 'loading' | 'ready' | 'error' | 'unavailable';
|
|
19
20
|
error?: string;
|
|
20
21
|
/** Whether the Host settings document accepts writes. */
|
|
21
22
|
writable: boolean;
|
|
@@ -24,41 +25,9 @@ export interface ModelVisibilityState {
|
|
|
24
25
|
total: number;
|
|
25
26
|
visible: number;
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
-
* One catalog group as the Host's `remote.session.modelCatalog()` advertises
|
|
29
|
-
* it. The 0.1.5 published Remote types mangle the catalog interface name away,
|
|
30
|
-
* so this plugin restates the wire shape it actually consumes.
|
|
31
|
-
*/
|
|
32
|
-
export interface CatalogModelGroup {
|
|
33
|
-
id: string;
|
|
34
|
-
name: string;
|
|
35
|
-
models: readonly {
|
|
36
|
-
id: string;
|
|
37
|
-
name: string;
|
|
38
|
-
}[];
|
|
39
|
-
}
|
|
40
|
-
/** Host model catalog outcome carried by the Remote result envelope. */
|
|
41
|
-
export type ModelCatalogOutcome = {
|
|
42
|
-
ok: true;
|
|
43
|
-
value: {
|
|
44
|
-
groups: readonly CatalogModelGroup[];
|
|
45
|
-
};
|
|
46
|
-
} | {
|
|
47
|
-
ok: false;
|
|
48
|
-
error: {
|
|
49
|
-
code: string;
|
|
50
|
-
message: string;
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
/** The assembled Client Remote face this plugin consumes. */
|
|
54
|
-
export interface ModelCatalogRemote {
|
|
55
|
-
session: {
|
|
56
|
-
modelCatalog(): Promise<ModelCatalogOutcome>;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
28
|
/** Injected catalog reader: decouples the controller from the Remote wire shape. */
|
|
60
29
|
export interface ModelCatalogReader {
|
|
61
|
-
read(): Promise<readonly
|
|
30
|
+
read(): Promise<readonly ModelProviderGroup[]>;
|
|
62
31
|
}
|
|
63
32
|
/** The face the slot registration injects into the card component. */
|
|
64
33
|
export interface ModelVisibilityFace {
|
|
@@ -76,18 +45,21 @@ export interface ModelVisibilityFace {
|
|
|
76
45
|
reload: () => void;
|
|
77
46
|
}
|
|
78
47
|
export declare class ModelVisibilityCardController {
|
|
79
|
-
private readonly
|
|
48
|
+
private readonly form;
|
|
80
49
|
private readonly catalogReader;
|
|
81
50
|
/** Exposed: the section contribution passes the store through its face as `hooks.snapshot`. */
|
|
82
51
|
readonly store: SnapshotStore<ModelVisibilityState>;
|
|
83
52
|
private catalog;
|
|
84
53
|
private loadError;
|
|
85
54
|
private generation;
|
|
86
|
-
|
|
55
|
+
private readonly unsubscribe;
|
|
56
|
+
constructor(form: ConfigForm<VisibilitySection>, catalogReader: ModelCatalogReader);
|
|
87
57
|
/** The registration-side inject face. */
|
|
88
58
|
face(): ModelVisibilityFace;
|
|
89
59
|
/** Re-read the catalog; an older response never overwrites a newer one. */
|
|
90
60
|
load(): Promise<void>;
|
|
61
|
+
/** Stop following the settings form (plugin unload). */
|
|
62
|
+
dispose(): void;
|
|
91
63
|
/** Compute the next hidden list from the current one plus one model's flip. */
|
|
92
64
|
private nextHidden;
|
|
93
65
|
/** Write the next hidden list, then re-pull the catalog the filter changed. */
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* collaboration goes through cordis services only: the slot key's
|
|
7
7
|
* declaration, the settings scope, and the wire handle arrive as type-only
|
|
8
8
|
* merges, never value imports (the loader bundle-purity rule).
|
|
9
|
+
*
|
|
10
|
+
* 0.2.0: the `settingsScope` service is gone; the card's durable state rides
|
|
11
|
+
* `ctx.configForms` (the shared describe mirror over the Host form
|
|
12
|
+
* projection), and the Remote namespace carries formal types, so the wire
|
|
13
|
+
* shape is no longer restated locally.
|
|
9
14
|
*/
|
|
10
15
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
16
|
export declare const name = "dsh-model-visibility";
|
|
12
|
-
export declare const inject: readonly ["slots", "locale", "
|
|
17
|
+
export declare const inject: readonly ["slots", "locale", "configForms", "remote", "remote.session"];
|
|
13
18
|
export declare function apply(ctx: Context): void;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* register site can declare `locale:` and receive the typed `t` seat.
|
|
5
5
|
*/
|
|
6
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';
|
|
7
|
+
export type ModelVisibilityLocaleKey = 'nav' | 'title' | 'intro' | 'loading' | 'loadFailed' | 'retry' | 'unavailable' | 'empty' | 'allHiddenWarning' | 'readOnly' | 'visibleCount' | 'searchPlaceholder' | 'noMatch' | 'hideAll' | 'showAll' | 'showAllProviders' | 'providerRow';
|
|
8
8
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
9
9
|
interface LocaleNamespaceMap {
|
|
10
10
|
/** The model-visibility card's copy. */
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The eye glyph in the settings navigation.
|
|
3
|
+
*
|
|
4
|
+
* The settings shell picks nav glyphs from a closed list of section ids
|
|
5
|
+
* (`account`, `models`, `agent-presets`, `plugins`, `archived-sessions`) and
|
|
6
|
+
* falls back to its own gear for every other id; `settings.section` projects
|
|
7
|
+
* only `id` / `order` / `label`, so a registrant has no icon to pass — the
|
|
8
|
+
* slot contract in `@deepseek-ai/dsh-client-ui-settings` and the runtime
|
|
9
|
+
* slot inventory both list exactly those options. Every third-party section
|
|
10
|
+
* therefore wears the gear, this one included.
|
|
11
|
+
*
|
|
12
|
+
* So this card claims its own row once the dialog is mounted and swaps the
|
|
13
|
+
* fallback gear for an eye — the same glyph the feature is about. The
|
|
14
|
+
* approach follows the precedent set by `dshmarket` (and credited there to
|
|
15
|
+
* `dsh-better-sidebar` / `dsh-skill-mcp-panel`).
|
|
16
|
+
*
|
|
17
|
+
* Scope, deliberately narrow:
|
|
18
|
+
*
|
|
19
|
+
* - only the row whose visible text equals this plugin's own localized
|
|
20
|
+
* section label is marked; no shell structure is touched;
|
|
21
|
+
* - the marker and the injected stylesheet belong to a `ctx.effect`, so they
|
|
22
|
+
* are removed with the fiber;
|
|
23
|
+
* - a locale switch re-claims the row through the MutationObserver, so the
|
|
24
|
+
* label and the glyph never disagree.
|
|
25
|
+
*
|
|
26
|
+
* Delete this module (and its call in index.ts) the day `settings.section`
|
|
27
|
+
* grows an `icon` field.
|
|
28
|
+
*/
|
|
29
|
+
/** Marks the one nav row this plugin owns. */
|
|
30
|
+
export declare const NAV_ICON_MARKER = "data-dsh-model-visibility-nav-icon";
|
|
31
|
+
/**
|
|
32
|
+
* The nav rows of the settings dialog. The shell renders each
|
|
33
|
+
* `settings.section` entry as a `<button>` inside the panel's `<nav>`
|
|
34
|
+
* (SettingsPanel in dsh-client-ui-settings-general).
|
|
35
|
+
*/
|
|
36
|
+
export declare const NAV_ROW_SELECTOR = "[role=\"dialog\"] nav button";
|
|
37
|
+
/**
|
|
38
|
+
* Glyph box in px. The shell renders every nav icon at this size and ships no
|
|
39
|
+
* media query at all, so there is deliberately no narrow variant here.
|
|
40
|
+
*/
|
|
41
|
+
export declare const NAV_ICON_SIZE = 16;
|
|
42
|
+
/**
|
|
43
|
+
* The eye as standalone SVG for a CSS `mask-image`.
|
|
44
|
+
*
|
|
45
|
+
* Painted pure black on purpose: a mask reads alpha only, and the visible
|
|
46
|
+
* colour comes from the element's `background-color: currentColor`, which
|
|
47
|
+
* keeps the glyph in step with the shell's own icons on hover/active state.
|
|
48
|
+
* The shape is the classic outline eye (almond contour + iris) on a 24 grid;
|
|
49
|
+
* `mask-size` scales it down to the shell's 16px box.
|
|
50
|
+
*/
|
|
51
|
+
export declare function eyeMaskSvg(): string;
|
|
52
|
+
/** The mask URL for the glyph (encoded at runtime, never hand-escaped). */
|
|
53
|
+
export declare function eyeMaskUrl(svg?: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether a nav row is this plugin's own.
|
|
56
|
+
*
|
|
57
|
+
* Pure, and the only decision this feature makes: the row whose visible text
|
|
58
|
+
* is the section label the shell is currently projecting. An empty label
|
|
59
|
+
* matches nothing — a locale that has not resolved yet must not mark the
|
|
60
|
+
* whole nav.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isOwnNavRow(rowText: string | null | undefined, wantedLabel: string | null | undefined): boolean;
|
|
63
|
+
/** Stylesheet for the marked row: hide the shell's gear, draw the eye. */
|
|
64
|
+
export declare function navIconCss(maskUrl: string): string;
|
|
65
|
+
/** The slice of the client context this feature needs. */
|
|
66
|
+
export interface NavIconContext {
|
|
67
|
+
effect(callback: () => unknown, label?: string): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Install the nav glyph.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx - client context, for effect ownership.
|
|
73
|
+
* @param resolveLabel - this plugin's current section label (the same thunk
|
|
74
|
+
* the `settings.section` registration passes), re-read on every sync so a
|
|
75
|
+
* locale switch is picked up without re-registering.
|
|
76
|
+
*/
|
|
77
|
+
export declare function installSettingsNavIcon(ctx: NavIconContext, resolveLabel: () => string): void;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-model-visibility — Host half.
|
|
3
3
|
*
|
|
4
|
-
* Owns the `model-visibility`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Owns the `model-visibility` profile entry (the hidden-model list) and filters
|
|
5
|
+
* the model catalog at its source: `ctx.llm.listModels` is wrapped so every
|
|
6
|
+
* catalog read (the conversation selector's `remote.session.modelCatalog()`
|
|
7
|
+
* and the settings page) stops advertising hidden models. Catalog membership
|
|
8
|
+
* is advisory in the harness, so hiding a model a session already uses never
|
|
9
|
+
* breaks that session's dispatch.
|
|
10
|
+
*
|
|
11
|
+
* 0.2.0 configuration model: the `hidden` field is declared `.volatile()`, so
|
|
12
|
+
* the Settings form projection exposes it to the browser card, and a form
|
|
13
|
+
* write commits the new value into the SAME stable reference (no fiber
|
|
14
|
+
* remount). The filter below reads `config.hidden.get()` on every listModels
|
|
15
|
+
* call, so a committed change takes effect on the next catalog read.
|
|
11
16
|
*/
|
|
12
17
|
import type { Context } from '@deepseek-ai/cordis';
|
|
18
|
+
import type { Volatile } from '@deepseek-ai/cosmokit';
|
|
13
19
|
import z from '@deepseek-ai/schemastery';
|
|
14
20
|
import { MODEL_VISIBILITY_NS } from './namespace.ts';
|
|
15
21
|
export declare const name = "dsh-model-visibility";
|
|
16
|
-
export declare const inject: readonly ["llm"
|
|
22
|
+
export declare const inject: readonly ["llm"];
|
|
17
23
|
export { MODEL_VISIBILITY_NS };
|
|
18
24
|
/** One hidden model: the provider/model route pair plus a display-name snapshot. */
|
|
19
25
|
export interface HiddenModel {
|
|
@@ -22,9 +28,27 @@ export interface HiddenModel {
|
|
|
22
28
|
/** Display name captured when hidden, so the card can label the row after the catalog stops advertising it. */
|
|
23
29
|
name?: string;
|
|
24
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Runtime plugin config as the Loader parses it: volatile fields arrive as
|
|
33
|
+
* STABLE references whose snapshots the Loader commits form writes into.
|
|
34
|
+
* The browser card edits the JSON projection of this same section, see
|
|
35
|
+
* `VisibilitySection` in namespace.ts.
|
|
36
|
+
*/
|
|
25
37
|
export interface Config {
|
|
26
38
|
/** Models excluded from the model-selector catalog; empty means all visible. */
|
|
27
|
-
hidden: HiddenModel[]
|
|
39
|
+
hidden: Volatile<HiddenModel[]>;
|
|
28
40
|
}
|
|
29
|
-
|
|
41
|
+
/** Field schemas, named so the const below can state its exact input/output pair. */
|
|
42
|
+
declare const fields: {
|
|
43
|
+
hidden: z<NoInfer<({
|
|
44
|
+
provider?: string | null | undefined;
|
|
45
|
+
model?: string | null | undefined;
|
|
46
|
+
name?: string | null | undefined;
|
|
47
|
+
} & import("@deepseek-ai/cosmokit").Dict)[]>, NoInfer<Schemastery.ObjectT<NoInfer<{
|
|
48
|
+
provider: z<string, string, "defined">;
|
|
49
|
+
model: z<string, string, "defined">;
|
|
50
|
+
name: z<string, string, "plain">;
|
|
51
|
+
}>>[]>, "volatile-defined">;
|
|
52
|
+
};
|
|
53
|
+
export declare const Config: z<Schemastery.ObjectS<typeof fields>, Schemastery.ObjectT<typeof fields>>;
|
|
30
54
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/types/namespace.d.ts
CHANGED
|
@@ -4,4 +4,14 @@
|
|
|
4
4
|
* the Host half (which would drag the settings schema and its Node-side deps
|
|
5
5
|
* into the client bundle).
|
|
6
6
|
*/
|
|
7
|
+
import type { HiddenModel } from './index.ts';
|
|
7
8
|
export declare const MODEL_VISIBILITY_NS = "model-visibility";
|
|
9
|
+
/**
|
|
10
|
+
* The settings section the browser card edits: the JSON projection of the
|
|
11
|
+
* Host `Config` section as the Settings describe mirror serves it (volatile
|
|
12
|
+
* references are unwrapped into plain data at the form boundary).
|
|
13
|
+
*/
|
|
14
|
+
export interface VisibilitySection {
|
|
15
|
+
/** Models excluded from the model-selector catalog; absent means all visible. */
|
|
16
|
+
hidden?: HiddenModel[];
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-visibility",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -26,44 +26,46 @@
|
|
|
26
26
|
"README.md",
|
|
27
27
|
"README.en.md"
|
|
28
28
|
],
|
|
29
|
-
"dsh": {
|
|
30
|
-
"bundle": {
|
|
31
|
-
"patch": "./cordis.patch.yml"
|
|
32
|
-
},
|
|
33
|
-
"client": {
|
|
34
|
-
"platform": "web",
|
|
35
|
-
"inject": [
|
|
36
|
-
"@deepseek-ai/dsh-api-remotes",
|
|
37
|
-
"@deepseek-ai/dsh-client-locale",
|
|
38
|
-
"@deepseek-ai/dsh-client-ui-settings"
|
|
39
|
-
]
|
|
40
|
-
}
|
|
41
|
-
},
|
|
29
|
+
"dsh": {
|
|
30
|
+
"bundle": {
|
|
31
|
+
"patch": "./cordis.patch.yml"
|
|
32
|
+
},
|
|
33
|
+
"client": {
|
|
34
|
+
"platform": "web",
|
|
35
|
+
"inject": [
|
|
36
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
37
|
+
"@deepseek-ai/dsh-client-locale",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc -p tsconfig.json && tsdown",
|
|
44
44
|
"prepare": "tsc -p tsconfig.json && tsdown",
|
|
45
45
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
46
46
|
},
|
|
47
|
-
"peerDependencies": {
|
|
48
|
-
"@deepseek-ai/cordis": "
|
|
49
|
-
},
|
|
50
|
-
"dependencies": {
|
|
51
|
-
"@deepseek-ai/schemastery": "
|
|
52
|
-
},
|
|
53
|
-
"devDependencies": {
|
|
54
|
-
"@deepseek-ai/cordis": "
|
|
55
|
-
"@deepseek-ai/
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-client-
|
|
60
|
-
"@deepseek-ai/dsh-client-
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@
|
|
65
|
-
"
|
|
66
|
-
"
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@deepseek-ai/cordis": "~4.0.4"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@deepseek-ai/schemastery": "~3.18.4"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@deepseek-ai/cordis": "~4.0.4",
|
|
55
|
+
"@deepseek-ai/cosmokit": "~1.8.5",
|
|
56
|
+
"@deepseek-ai/dsh-api-gateway": "0.2.0-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.0-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.2",
|
|
59
|
+
"@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
|
|
60
|
+
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
|
|
64
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
65
|
+
"@deepseek-ai/dsh-settings": "0.2.0-rc.2",
|
|
66
|
+
"@types/react": "^18.3.0",
|
|
67
|
+
"react": "^18.3.0",
|
|
68
|
+
"tsdown": "^0.12.0",
|
|
67
69
|
"typescript": "^5.7.0"
|
|
68
70
|
},
|
|
69
71
|
"publishConfig": {
|