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.
This commit is contained in:
Classic298 authored and GitHub committed 2026-08-17 00:22:51 -06:00
1 parent 1a376ac17f
commit 1756c9d5d2
10 files changed
+99 -129

No files matched your search

+3 -11
View File
@@ -22,7 +22,7 @@
socket,
config,
isApp,
models,
visiblePinnedModels,
selectedFolder,
WEBUI_NAME,
sidebarWidth
@@ -120,9 +120,7 @@
let showCreateFolderModal = false;
let pinnedModels = [];
let showPinnedModels = false;
let showPinnedModels = true;
let showPinnedNotes = false;
let showChannels = false;
let showFolders = false;
@@ -683,12 +681,6 @@
navElement.style['-webkit-app-region'] = 'drag';
}
}
}),
settings.subscribe((value) => {
if (pinnedModels != value?.pinnedModels ?? []) {
pinnedModels = value?.pinnedModels ?? [];
showPinnedModels = pinnedModels.length > 0;
}
})
];
@@ -1285,7 +1277,7 @@
</div>
</div>
{#if ($models ?? []).length > 0 && (($settings?.pinnedModels ?? []).length > 0 || $config?.default_pinned_models)}
{#if $visiblePinnedModels.length > 0}
<SidebarSection
id="sidebar-models"
bind:open={showPinnedModels}
@@ -1,18 +1,23 @@
<script>
import Sortable from 'sortablejs';
import { onDestroy, onMount, tick } from 'svelte';
import { onMount, tick } from 'svelte';
import { chatId, config, mobile, models, settings, showSidebar } from '$lib/stores';
import { WEBUI_BASE_URL } from '$lib/constants';
import {
chatId,
mobile,
models,
pinnedModels,
settings,
showSidebar,
visiblePinnedModels
} from '$lib/stores';
import { updateUserSettings } from '$lib/apis/users';
import PinnedModelItem from './PinnedModelItem.svelte';
export let selectedChatId = null;
export let shiftKey = false;
let pinnedModels = [];
const initPinnedModelsSortable = () => {
const pinnedModelsList = document.getElementById('pinned-models-list');
if (pinnedModelsList && !$mobile) {
@@ -28,91 +33,49 @@
);
},
onUpdate: async (event) => {
const modelId = event.item.dataset.id;
const newIndex = event.newIndex;
const reorderedIds = [...$visiblePinnedModels];
const [movedId] = reorderedIds.splice(event.oldIndex, 1);
reorderedIds.splice(event.newIndex, 0, movedId);
const pinnedModels = $settings.pinnedModels;
const oldIndex = pinnedModels.indexOf(modelId);
pinnedModels.splice(oldIndex, 1);
pinnedModels.splice(newIndex, 0, modelId);
settings.set({ ...$settings, pinnedModels: pinnedModels });
// Keep pins for models the user cannot see
settings.set({
...$settings,
pinnedModels: [
...reorderedIds,
...$pinnedModels.filter((id) => !$visiblePinnedModels.includes(id))
]
});
await updateUserSettings(localStorage.token, { ui: $settings });
}
});
}
};
let unsubscribeSettings;
const cleanupStalePinnedModels = async (modelIds) => {
const validModels = modelIds.filter((id) => {
const model = $models.find((m) => m.id === id);
// Remove if model not found (deleted) or if hidden
return model && !(model?.info?.meta?.hidden ?? false);
});
if (validModels.length !== modelIds.length) {
pinnedModels = validModels;
settings.set({ ...$settings, pinnedModels: validModels });
await updateUserSettings(localStorage.token, { ui: $settings });
}
};
onMount(async () => {
pinnedModels = $settings?.pinnedModels ?? [];
if (pinnedModels.length === 0 && $config?.default_pinned_models) {
const defaultPinnedModels = ($config?.default_pinned_models).split(',').filter((id) => id);
pinnedModels = defaultPinnedModels.filter((id) => $models.find((model) => model.id === id));
settings.set({ ...$settings, pinnedModels });
await updateUserSettings(localStorage.token, { ui: $settings });
}
// Auto-unpin hidden or deleted models
if (pinnedModels.length > 0) {
await cleanupStalePinnedModels(pinnedModels);
}
unsubscribeSettings = settings.subscribe((value) => {
pinnedModels = value?.pinnedModels ?? [];
});
await tick();
initPinnedModelsSortable();
});
onDestroy(() => {
if (unsubscribeSettings) {
unsubscribeSettings();
}
});
</script>
<div class="mt-0.5 pb-1.5" id="pinned-models-list">
{#each pinnedModels as modelId (modelId)}
{@const model = $models.find((model) => model.id === modelId)}
{#if model}
<PinnedModelItem
{model}
{shiftKey}
onClick={() => {
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)}
<PinnedModelItem
model={$models.find((model) => 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}
</div>