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;