From 1756c9d5d2c8bcacc1bcd94cd5466054a290b454 Mon Sep 17 00:00:00 2001
From: Classic298 <27028174+Classic298@users.noreply.github.com>
Date: Mon, 17 Aug 2026 08:22:51 +0200
Subject: [PATCH] fix: default pinned models stop applying after a user's first
page load (#28069)
Changing "Default Pinned Models" in admin settings had no effect for anyone who had already opened Open WebUI once. The sidebar copied the admin default into that user's own settings the first time it rendered and saved it to the server, which marked them as having customized their pins, so every later change to the default was ignored for them. Merely loading the page was enough, the user never had to touch a pin.
The default is now resolved for display only, through a shared store that falls back to the admin list while the user has no pins of their own, the same way default models already work. Nothing is written to the user's settings until they actually pin, unpin or reorder something, at which point their choice takes over for good. Unpinning everything still persists an empty list rather than snapping back to the default.
Users whose settings were already overwritten by the old behaviour keep that copy, since a stored pin list cannot be told apart from a deliberate one.
Fixes a drag-reorder path that mixed sidebar positions with stored ones, and stops the sidebar section reopening itself after any unrelated settings change.
---
src/lib/apis/index.ts | 1 +
.../components/admin/Settings/Models.svelte | 24 ++--
.../admin/Settings/Models/ModelMenu.svelte | 6 +-
src/lib/components/chat/ModelSelector.svelte | 17 ++-
.../chat/ModelSelector/ModelItemMenu.svelte | 8 +-
src/lib/components/layout/Sidebar.svelte | 14 +--
.../layout/Sidebar/PinnedModelList.svelte | 117 ++++++------------
src/lib/components/workspace/Models.svelte | 16 ++-
.../workspace/Models/ModelMenu.svelte | 6 +-
src/lib/stores/index.ts | 19 ++-
10 files changed, 99 insertions(+), 129 deletions(-)
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;