From 9db68981d85ae65af2f23ca459b643171722999e Mon Sep 17 00:00:00 2001
From: Classic298 <27028174+Classic298@users.noreply.github.com>
Date: Thu, 24 Sep 2026 05:48:05 +0200
Subject: [PATCH] feat: allow reordering admin models while a search or filter
is active (#30390)
On Admin Settings > Models the drag handle was disabled as soon as a search, view or tag filter was active, so on a long list the only way to move a model was to clear everything and hunt for it by eye.
Dragging now works in any filtered list. The move is applied to the full order: the dragged model is placed directly after the visible model it was dropped below (or directly before the one it was dropped above), and every model hidden by the filter keeps its place. That anchoring is what makes reordering a subset safe, which is why the filters previously blocked it.
The tag filter now filters the loaded list client-side like search and view already do. Before, it reloaded the page data and rebuilt the order from only the tagged models, so saving under a tag would have dropped every other model from the order, and switching tags discarded unsaved moves. Export keeps its existing tag-filtered behaviour.
Verified in the browser with search, view (enabled/disabled) and tag filters, dragging up and down, multiple moves before one save and switching tags with unsaved moves; the saved order always contains every model.
Closes #29634
---
.../components/admin/Settings/Models.svelte | 76 ++++++++-----------
1 file changed, 32 insertions(+), 44 deletions(-)
diff --git a/src/lib/components/admin/Settings/Models.svelte b/src/lib/components/admin/Settings/Models.svelte
index 42bf61a370..9ea9629a59 100644
--- a/src/lib/components/admin/Settings/Models.svelte
+++ b/src/lib/components/admin/Settings/Models.svelte
@@ -153,6 +153,7 @@
const modelOrder = new Map(modelOrderList.map((id, idx) => [id, idx]));
filteredModels = models
+ .filter((m) => !selectedTag || modelTags(m).includes(selectedTag))
.filter((m) => searchValue === '' || m.name.toLowerCase().includes(searchValue.toLowerCase()))
.filter((m) => {
if (viewOption === 'base') return !isPresetModel(m);
@@ -180,9 +181,6 @@
}
let searchValue = '';
- let canReorderModels = false;
-
- $: canReorderModels = searchValue === '' && viewOption === '' && selectedTag === '';
const enableAllHandler = async () => {
const modelsToEnable = filteredModels.filter((m) => !(m.is_active ?? true));
@@ -292,26 +290,24 @@
const listedModelIds = new Set(allModels.map((model) => model.id));
allModels.push(...savedModels.filter((model) => !listedModelIds.has(model.id)));
- models = allModels
- .map((m: ModelListItem) => {
- const savedModel = savedModels.find((model: ModelListItem) => model.id === m.id);
+ models = allModels.map((m: ModelListItem) => {
+ const savedModel = savedModels.find((model: ModelListItem) => model.id === m.id);
- if (savedModel) {
- return {
- ...m,
- ...savedModel
- };
- } else {
- return {
- ...m,
- id: m.id,
- name: m.name,
+ if (savedModel) {
+ return {
+ ...m,
+ ...savedModel
+ };
+ } else {
+ return {
+ ...m,
+ id: m.id,
+ name: m.name,
- is_active: true
- };
- }
- })
- .filter((model) => !selectedTag || modelTags(model).includes(selectedTag));
+ is_active: true
+ };
+ }
+ });
modelOrderList = [
...modelOrderList.filter((id) => models.some((model) => model.id === id)),
@@ -445,15 +441,14 @@
const target = parent.children[oldIndex < newIndex ? oldIndex : oldIndex + 1];
parent.insertBefore(item, target);
- const updatedModels = [...filteredModels];
- const [movedModel] = updatedModels.splice(oldIndex, 1);
- updatedModels.splice(newIndex, 0, movedModel);
+ // Anchor on the visible neighbor so filtered-out models keep their place
+ const movedModelId = filteredModels[oldIndex].id;
+ const anchorModelId = filteredModels[newIndex].id;
+ const reorderedIds = modelOrderList.filter((id) => id !== movedModelId);
+ const anchorIndex = reorderedIds.indexOf(anchorModelId);
+ reorderedIds.splice(oldIndex < newIndex ? anchorIndex + 1 : anchorIndex, 0, movedModelId);
- const orderedIds = updatedModels.map((model) => model.id);
- const orderedSet = new Set(orderedIds);
-
- models = [...updatedModels, ...models.filter((model) => !orderedSet.has(model.id))];
- modelOrderList = models.map((model) => model.id);
+ modelOrderList = reorderedIds;
modelOrderDirty = true;
};
@@ -463,7 +458,7 @@
sortable = null;
}
- if (modelListElement && filteredModels.length > 0 && canReorderModels) {
+ if (modelListElement && filteredModels.length > 0) {
sortable = new Sortable(modelListElement, {
animation: 150,
handle: '.model-item-handle',
@@ -825,9 +820,6 @@
items={tags.map((tag) => {
return { value: tag, label: tag };
})}
- onChange={async () => {
- await init();
- }}
/>
{/if}
@@ -864,7 +856,11 @@
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-xl bg-transparent px-2 text-[0.8125rem] hover:text-gray-900 dark:hover:text-gray-100"
type="button"
on:click={() => {
- downloadModels(models ?? []);
+ downloadModels(
+ (models ?? []).filter(
+ (model) => !selectedTag || modelTags(model).includes(selectedTag)
+ )
+ );
}}
>