diff --git a/apps/desktop/src/app/shell/model-menu-panel.tsx b/apps/desktop/src/app/shell/model-menu-panel.tsx index d66761d0b82..538d2acf522 100644 --- a/apps/desktop/src/app/shell/model-menu-panel.tsx +++ b/apps/desktop/src/app/shell/model-menu-panel.tsx @@ -24,6 +24,7 @@ import { $visibleModels, collapseModelFamilies, DEFAULT_VISIBLE_PER_PROVIDER, + effectiveVisibleKeys, type ModelFamily, modelVisibilityKey, setModelVisibilityOpen @@ -86,13 +87,17 @@ export function ModelMenuPanel({ gateway, onSelectModel, requestGateway }: Model : null const providers = modelOptions.data?.providers + const effectiveVisibleModels = useMemo( + () => effectiveVisibleKeys(visibleModels, providers ?? []), + [visibleModels, providers] + ) const switchTo = (model: string, provider: string) => onSelectModel({ model, persistGlobal: !activeSessionId, provider }) const groups = useMemo( - () => groupModels(providers ?? [], search, { model: optionsModel, provider: optionsProvider }, visibleModels), - [providers, search, optionsModel, optionsProvider, visibleModels] + () => groupModels(providers ?? [], search, { model: optionsModel, provider: optionsProvider }, effectiveVisibleModels), + [providers, search, optionsModel, optionsProvider, effectiveVisibleModels] ) return ( diff --git a/apps/desktop/src/store/model-visibility.test.ts b/apps/desktop/src/store/model-visibility.test.ts new file mode 100644 index 00000000000..483578460ad --- /dev/null +++ b/apps/desktop/src/store/model-visibility.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' + +import type { ModelOptionProvider } from '@/types/hermes' + +import { effectiveVisibleKeys, modelVisibilityKey } from './model-visibility' + +const provider = (slug: string, models: string[]): ModelOptionProvider => ({ + models, + name: slug, + slug +}) + +describe('model visibility', () => { + it('keeps newly configured providers visible when stored choices are stale', () => { + const stored = new Set([modelVisibilityKey('copilot', 'claude-sonnet-4.6')]) + + const visible = effectiveVisibleKeys(stored, [ + provider('copilot', ['claude-sonnet-4.6']), + provider('local-ollama', ['qwen3:latest', 'llama3.2:latest']) + ]) + + expect(visible.has(modelVisibilityKey('copilot', 'claude-sonnet-4.6'))).toBe(true) + expect(visible.has(modelVisibilityKey('local-ollama', 'qwen3:latest'))).toBe(true) + expect(visible.has(modelVisibilityKey('local-ollama', 'llama3.2:latest'))).toBe(true) + }) + + it('does not re-add models from a provider that already has stored choices', () => { + const stored = new Set([modelVisibilityKey('local-ollama', 'qwen3:latest')]) + + const visible = effectiveVisibleKeys(stored, [ + provider('local-ollama', ['qwen3:latest', 'llama3.2:latest']) + ]) + + expect(visible.has(modelVisibilityKey('local-ollama', 'qwen3:latest'))).toBe(true) + expect(visible.has(modelVisibilityKey('local-ollama', 'llama3.2:latest'))).toBe(false) + }) +}) diff --git a/apps/desktop/src/store/model-visibility.ts b/apps/desktop/src/store/model-visibility.ts index 4f3ce744c08..9fb555a4e70 100644 --- a/apps/desktop/src/store/model-visibility.ts +++ b/apps/desktop/src/store/model-visibility.ts @@ -104,5 +104,30 @@ export function effectiveVisibleKeys( stored: Set | null, providers: readonly ModelOptionProvider[] ): Set { - return stored ?? defaultVisibleKeys(providers) + if (!stored) { + return defaultVisibleKeys(providers) + } + + if (stored.size === 0) { + return new Set() + } + + const next = new Set(stored) + + for (const provider of providers) { + const providerPrefix = `${provider.slug}::` + const hasStoredProvider = [...stored].some(key => key.startsWith(providerPrefix)) + + if (hasStoredProvider) { + continue + } + + const families = collapseModelFamilies(provider.models ?? []) + + for (const family of families.slice(0, DEFAULT_VISIBLE_PER_PROVIDER)) { + next.add(modelVisibilityKey(provider.slug, family.id)) + } + } + + return next }