diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index 4f55ab14be..3409b88ed5 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1758,6 +1758,7 @@ export interface ModelConfig { export interface ModelMeta { toolIds: never[]; description?: string; + hidden?: boolean; capabilities?: object; profile_image_url?: string; } diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte index 4a0106d306..616969f0e1 100644 --- a/src/lib/components/admin/Settings/Models.svelte +++ b/src/lib/components/admin/Settings/Models.svelte @@ -7,7 +7,14 @@ import { onMount, onDestroy, getContext, tick } from 'svelte'; const i18n = getContext('i18n'); - import { config, models as _models, settings, showSettings, user } from '$lib/stores'; + import { + config, + models as _models, + pinnedModels, + settings, + showSettings, + user + } from '$lib/stores'; import { createNewModel, deleteAllModels, @@ -607,15 +614,12 @@ }; const pinModelHandler = async (modelId) => { - let pinnedModels = $settings?.pinnedModels ?? []; - - if (pinnedModels.includes(modelId)) { - pinnedModels = pinnedModels.filter((id) => id !== modelId); - } else { - pinnedModels = [...new Set([...pinnedModels, modelId])]; - } - - settings.set({ ...$settings, pinnedModels: pinnedModels }); + settings.set({ + ...$settings, + pinnedModels: $pinnedModels.includes(modelId) + ? $pinnedModels.filter((id) => id !== modelId) + : [...$pinnedModels, modelId] + }); await updateUserSettings(localStorage.token, { ui: $settings }); }; diff --git a/src/lib/components/admin/Settings/Models/ModelMenu.svelte b/src/lib/components/admin/Settings/Models/ModelMenu.svelte index 361a759bbe..81c7a9ca15 100644 --- a/src/lib/components/admin/Settings/Models/ModelMenu.svelte +++ b/src/lib/components/admin/Settings/Models/ModelMenu.svelte @@ -18,7 +18,7 @@ import GlobeAlt from '$lib/components/icons/GlobeAlt.svelte'; import LockClosed from '$lib/components/icons/LockClosed.svelte'; - import { config, settings } from '$lib/stores'; + import { config, pinnedModels, settings } from '$lib/stores'; import Link from '$lib/components/icons/Link.svelte'; const i18n = getContext('i18n'); @@ -173,14 +173,14 @@ class="select-none flex w-full gap-2 items-center h-[1.6875rem] px-2 text-[0.8125rem] font-normal cursor-pointer hover:bg-gray-50/40 dark:hover:bg-gray-800/40 rounded-xl" on:click={() => runAndClose(() => pinModelHandler(model?.id))} > - {#if ($settings?.pinnedModels ?? []).includes(model?.id)} + {#if $pinnedModels.includes(model?.id)} {:else} {/if}
- {#if ($settings?.pinnedModels ?? []).includes(model?.id)} + {#if $pinnedModels.includes(model?.id)} {$i18n.t('Hide from Sidebar')} {:else} {$i18n.t('Keep in Sidebar')} diff --git a/src/lib/components/chat/ModelSelector.svelte b/src/lib/components/chat/ModelSelector.svelte index ea65f12c13..23c54ed627 100644 --- a/src/lib/components/chat/ModelSelector.svelte +++ b/src/lib/components/chat/ModelSelector.svelte @@ -1,5 +1,5 @@
- {#each pinnedModels as modelId (modelId)} - {@const model = $models.find((model) => model.id === modelId)} - {#if model} - { - selectedChatId = null; - chatId.set(''); - if ($mobile) { - showSidebar.set(false); - } - }} - onUnpin={($settings?.pinnedModels ?? []).includes(modelId) - ? () => { - const pinnedModels = $settings.pinnedModels.filter((id) => id !== modelId); - settings.set({ ...$settings, pinnedModels }); - updateUserSettings(localStorage.token, { ui: $settings }); - } - : null} - /> - {/if} + {#each $visiblePinnedModels as modelId (modelId)} + model.id === modelId)} + {shiftKey} + onClick={() => { + selectedChatId = null; + chatId.set(''); + if ($mobile) { + showSidebar.set(false); + } + }} + onUnpin={() => { + settings.set({ + ...$settings, + pinnedModels: $pinnedModels.filter((id) => id !== modelId) + }); + updateUserSettings(localStorage.token, { ui: $settings }); + }} + /> {/each}
diff --git a/src/lib/components/workspace/Models.svelte b/src/lib/components/workspace/Models.svelte index 24888e7c7c..276bf275ef 100644 --- a/src/lib/components/workspace/Models.svelte +++ b/src/lib/components/workspace/Models.svelte @@ -17,6 +17,7 @@ config, mobile, models as _models, + pinnedModels, settings, user, workspaceActions @@ -292,15 +293,12 @@ }; const pinModelHandler = async (modelId) => { - let pinnedModels = $settings?.pinnedModels ?? []; - - if (pinnedModels.includes(modelId)) { - pinnedModels = pinnedModels.filter((id) => id !== modelId); - } else { - pinnedModels = [...new Set([...pinnedModels, modelId])]; - } - - settings.set({ ...$settings, pinnedModels: pinnedModels }); + settings.set({ + ...$settings, + pinnedModels: $pinnedModels.includes(modelId) + ? $pinnedModels.filter((id) => id !== modelId) + : [...$pinnedModels, modelId] + }); await updateUserSettings(localStorage.token, { ui: $settings }); }; diff --git a/src/lib/components/workspace/Models/ModelMenu.svelte b/src/lib/components/workspace/Models/ModelMenu.svelte index 0d99a31510..e44307776d 100644 --- a/src/lib/components/workspace/Models/ModelMenu.svelte +++ b/src/lib/components/workspace/Models/ModelMenu.svelte @@ -15,7 +15,7 @@ import Pin from '$lib/components/icons/Pin.svelte'; import PinSlash from '$lib/components/icons/PinSlash.svelte'; - import { config, user as currentUser, settings } from '$lib/stores'; + import { config, user as currentUser, pinnedModels, settings } from '$lib/stores'; import Link from '$lib/components/icons/Link.svelte'; const i18n = getContext('i18n'); @@ -131,14 +131,14 @@ class="select-none flex h-[1.6875rem] w-full cursor-pointer items-center gap-2 rounded-xl bg-transparent px-2 text-[0.8125rem] hover:text-gray-900 dark:hover:text-gray-100" on:click={() => runAndClose(() => pinModelHandler(model?.id))} > - {#if ($settings?.pinnedModels ?? []).includes(model?.id)} + {#if $pinnedModels.includes(model?.id)} {:else} {/if}
- {#if ($settings?.pinnedModels ?? []).includes(model?.id)} + {#if $pinnedModels.includes(model?.id)} {$i18n.t('Hide from Sidebar')} {:else} {$i18n.t('Keep in Sidebar')} diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts index 85f46a31d6..9277a30d0b 100644 --- a/src/lib/stores/index.ts +++ b/src/lib/stores/index.ts @@ -1,5 +1,5 @@ import { APP_NAME } from '$lib/constants'; -import { type Writable, writable } from 'svelte/store'; +import { type Writable, derived, writable } from 'svelte/store'; import type { ModelConfig } from '$lib/apis'; import type { Banner } from '$lib/types'; import type { Socket } from 'socket.io-client'; @@ -103,6 +103,20 @@ export const banners: Writable = writable([]); export const settings: Writable = writable({}); +// Users who never pinned a model follow the admin default, so changes to it keep reaching them +export const pinnedModels = derived([settings, config], ([$settings, $config]) => + $settings?.pinnedModels === undefined + ? ($config?.default_pinned_models ?? '').split(',').filter((id) => id) + : $settings.pinnedModels +); + +// Pins for models the user cannot see are kept in their settings but left out of the sidebar +export const visiblePinnedModels = derived([pinnedModels, models], ([$pinnedModels, $models]) => + $pinnedModels.filter((id) => + $models.some((model) => model.id === id && !model.info?.meta?.hidden) + ) +); + export const audioQueue = writable(null); export const chatRequestQueues: Writable< Record @@ -200,7 +214,7 @@ type OllamaModelDetails = { }; type Settings = { - pinnedModels?: never[]; + pinnedModels?: string[]; toolServers?: never[]; detectArtifacts?: boolean; showUpdateToast?: boolean; @@ -310,6 +324,7 @@ type Config = { version: string; default_locale: string; default_models: string; + default_pinned_models?: string | null; default_prompt_suggestions: PromptSuggestion[]; features: { auth: boolean;