diff --git a/locales/de.json b/locales/de.json index b6dc7098..3022cbbc 100644 --- a/locales/de.json +++ b/locales/de.json @@ -682,7 +682,8 @@ "usageAsc": "Wenigste", "versionsCount": "Lokale Versionen", "versionsCountDesc": "Meiste Versionen zuerst", - "versionsCountAsc": "Wenigste Versionen zuerst" + "versionsCountAsc": "Wenigste Versionen zuerst", + "versionIdDesc": "Neueste Version zuerst" }, "refresh": { "title": "Modelliste aktualisieren", diff --git a/locales/en.json b/locales/en.json index f556a2a4..9ce05217 100644 --- a/locales/en.json +++ b/locales/en.json @@ -682,7 +682,8 @@ "usageAsc": "Least", "versionsCount": "Local Versions", "versionsCountDesc": "Most versions first", - "versionsCountAsc": "Fewest versions first" + "versionsCountAsc": "Fewest versions first", + "versionIdDesc": "Newest version first" }, "refresh": { "title": "Refresh model list", diff --git a/locales/es.json b/locales/es.json index b9e8061d..d6b19286 100644 --- a/locales/es.json +++ b/locales/es.json @@ -682,7 +682,8 @@ "usageAsc": "Menos", "versionsCount": "Versiones locales", "versionsCountDesc": "Más versiones primero", - "versionsCountAsc": "Menos versiones primero" + "versionsCountAsc": "Menos versiones primero", + "versionIdDesc": "Versión más nueva primero" }, "refresh": { "title": "Actualizar lista de modelos", diff --git a/locales/fr.json b/locales/fr.json index eed801dc..b74b0e42 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -682,7 +682,8 @@ "usageAsc": "Moins", "versionsCount": "Versions locales", "versionsCountDesc": "Plus de versions d'abord", - "versionsCountAsc": "Moins de versions d'abord" + "versionsCountAsc": "Moins de versions d'abord", + "versionIdDesc": "Version la plus récente d'abord" }, "refresh": { "title": "Actualiser la liste des modèles", diff --git a/locales/he.json b/locales/he.json index 877a86f4..3fffe591 100644 --- a/locales/he.json +++ b/locales/he.json @@ -682,7 +682,8 @@ "usageAsc": "הכי פחות", "versionsCount": "גרסאות מקומיות", "versionsCountDesc": "הכי הרבה גרסאות ראשונות", - "versionsCountAsc": "הכי מעט גרסאות ראשונות" + "versionsCountAsc": "הכי מעט גרסאות ראשונות", + "versionIdDesc": "גרסה חדשה ביותר ראשונה" }, "refresh": { "title": "רענן רשימת מודלים", diff --git a/locales/ja.json b/locales/ja.json index dc8eb83e..28a98520 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -682,7 +682,8 @@ "usageAsc": "少ない", "versionsCount": "ローカルバージョン数", "versionsCountDesc": "バージョン数の多い順", - "versionsCountAsc": "バージョン数の少ない順" + "versionsCountAsc": "バージョン数の少ない順", + "versionIdDesc": "最新バージョン順" }, "refresh": { "title": "モデルリストを更新", diff --git a/locales/ko.json b/locales/ko.json index 76a78cb4..78536f45 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -682,7 +682,8 @@ "usageAsc": "적은 순", "versionsCount": "로컬 버전 수", "versionsCountDesc": "버전 수 많은 순", - "versionsCountAsc": "버전 수 적은 순" + "versionsCountAsc": "버전 수 적은 순", + "versionIdDesc": "최신 버전순" }, "refresh": { "title": "모델 목록 새로고침", diff --git a/locales/ru.json b/locales/ru.json index 30061fc4..f44a3147 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -682,7 +682,8 @@ "usageAsc": "Меньше", "versionsCount": "Локальные версии", "versionsCountDesc": "Сначала больше версий", - "versionsCountAsc": "Сначала меньше версий" + "versionsCountAsc": "Сначала меньше версий", + "versionIdDesc": "Сначала новые версии" }, "refresh": { "title": "Обновить список моделей", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 1ecc3ad6..b662a132 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -682,7 +682,8 @@ "usageAsc": "最少", "versionsCount": "本地版本数", "versionsCountDesc": "版本数从多到少", - "versionsCountAsc": "版本数从少到多" + "versionsCountAsc": "版本数从少到多", + "versionIdDesc": "最新版本优先" }, "refresh": { "title": "刷新模型列表", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index b321fffb..f475ce78 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -682,7 +682,8 @@ "usageAsc": "最少", "versionsCount": "本地版本數", "versionsCountDesc": "版本數從多到少", - "versionsCountAsc": "版本數從少到多" + "versionsCountAsc": "版本數從少到多", + "versionIdDesc": "最新版本優先" }, "refresh": { "title": "重新整理模型列表", diff --git a/py/services/base_model_service.py b/py/services/base_model_service.py index 5f757d75..5ecc0d7c 100644 --- a/py/services/base_model_service.py +++ b/py/services/base_model_service.py @@ -111,6 +111,12 @@ class BaseModelService(ABC): item for item in sorted_data if self._extract_model_id(item) == civitai_model_id ] + # VLM mode: always sort by version ID descending (newest version first), + # regardless of the current sort_by preference. + sorted_data.sort( + key=lambda x: self._extract_version_id(x) or 0, + reverse=True, + ) # Optionally group by civitai modelId, showing only the latest version per model dedup_lost = 0 diff --git a/static/css/layout.css b/static/css/layout.css index 1786a7cb..7122cc3b 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -264,6 +264,23 @@ box-shadow: 0 0 0 2px oklch(var(--lora-accent) / 0.15); } +/* Disabled sort dropdown — used when VLM custom filter is active */ +.control-group select:disabled { + opacity: 0.5; + cursor: not-allowed; + background-color: var(--bg-color); + border-color: var(--border-color); + box-shadow: none; + transform: none; +} + +.control-group select:disabled:hover { + border-color: var(--border-color); + background-color: var(--bg-color); + transform: none; + box-shadow: none; +} + /* Ensure hidden class works properly */ .hidden { display: none !important; diff --git a/static/js/components/controls/CheckpointsControls.js b/static/js/components/controls/CheckpointsControls.js index 0d61644d..51473c31 100644 --- a/static/js/components/controls/CheckpointsControls.js +++ b/static/js/components/controls/CheckpointsControls.js @@ -102,6 +102,7 @@ export class CheckpointsControls extends PageControls { removeSessionItem('vlm_model_name'); removeSessionItem('vlm_base_model'); removeSessionItem('vlm_page_type'); + this._restoreSortAfterVlm(); // Hide the indicator const indicator = document.getElementById('customFilterIndicator'); if (indicator) { diff --git a/static/js/components/controls/LorasControls.js b/static/js/components/controls/LorasControls.js index d9733bcb..b79139ac 100644 --- a/static/js/components/controls/LorasControls.js +++ b/static/js/components/controls/LorasControls.js @@ -119,6 +119,7 @@ export class LorasControls extends PageControls { removeSessionItem('vlm_model_name'); removeSessionItem('vlm_base_model'); removeSessionItem('vlm_page_type'); + this._restoreSortAfterVlm(); const indicator = document.getElementById('customFilterIndicator'); if (indicator) { indicator.classList.add('hidden'); diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js index d00441b0..e0d614bb 100644 --- a/static/js/components/controls/PageControls.js +++ b/static/js/components/controls/PageControls.js @@ -465,6 +465,60 @@ export class PageControls { /** * Clear custom filter */ + /** + * Dynamically add the VLM sort option (version_id:desc) to the sort dropdown. + * It is not a permanent option — only present while VLM is active. + */ + _addVlmSortOption() { + const sortSelect = document.getElementById('sortSelect'); + if (!sortSelect) return; + // Only add if not already present + if (sortSelect.querySelector('option[value="version_id:desc"]')) return; + const opt = document.createElement('option'); + opt.value = 'version_id:desc'; + opt.textContent = this._t('loras.controls.sort.versionIdDesc', 'Newest version first'); + sortSelect.appendChild(opt); + } + + /** + * Remove the VLM sort option from the sort dropdown. + */ + _removeVlmSortOption() { + const sortSelect = document.getElementById('sortSelect'); + if (!sortSelect) return; + const opt = sortSelect.querySelector('option[value="version_id:desc"]'); + if (opt) opt.remove(); + } + + /** + * Look up a translation key via the global i18n helper, falling back to + * a plain-text default when the key is missing or i18n is unavailable. + */ + _t(key, fallback) { + if (typeof window.i18n?.t === 'function') { + return window.i18n.t(key, { defaultValue: fallback }); + } + return fallback; + } + + /** + * Restore the sort dropdown state after VLM is cleared. + * Shared by PageControls.clearCustomFilter() and subclass overrides. + */ + _restoreSortAfterVlm() { + const prevSort = getSessionItem('vlm_prev_sort'); + removeSessionItem('vlm_prev_sort'); + const restoredSort = prevSort || 'name:asc'; + this.pageState.sortBy = restoredSort; + this.saveSortPreference(restoredSort); + this._removeVlmSortOption(); + const sortSelect = document.getElementById('sortSelect'); + if (sortSelect) { + sortSelect.value = restoredSort; + sortSelect.disabled = false; + } + } + /** * Trigger View Local Versions without page reload * Sets sessionStorage and reloads data via the API. @@ -479,6 +533,17 @@ export class PageControls { } else { removeSessionItem('vlm_base_model'); } + // Save current sort preference so it can be restored when VLM is cleared + setSessionItem('vlm_prev_sort', this.pageState.sortBy); + // Inject the temporary sort option and force version_id:desc + this._addVlmSortOption(); + this.pageState.sortBy = 'version_id:desc'; + this.saveSortPreference('version_id:desc'); + const sortSelect = document.getElementById('sortSelect'); + if (sortSelect) { + sortSelect.value = 'version_id:desc'; + sortSelect.disabled = true; + } // Reload data via API (no page reload) this.resetAndReload(true).then(() => { // Show the VLM indicator after data loads @@ -533,6 +598,7 @@ export class PageControls { checkVlmFilter() { const vlmModelId = getSessionItem('vlm_model_id'); const vlmPageType = getSessionItem('vlm_page_type'); + const sortSelect = document.getElementById('sortSelect'); // Only show VLM indicator when it belongs to the current page type if (vlmModelId && vlmPageType !== this.pageType) { @@ -541,6 +607,9 @@ export class PageControls { removeSessionItem('vlm_model_name'); removeSessionItem('vlm_base_model'); removeSessionItem('vlm_page_type'); + removeSessionItem('vlm_prev_sort'); + this._removeVlmSortOption(); + if (sortSelect) sortSelect.disabled = false; return; } @@ -548,6 +617,13 @@ export class PageControls { const vlmBaseModel = getSessionItem('vlm_base_model'); if (vlmModelId && vlmModelName) { + // VLM is active — inject sort option, disable dropdown, show indicator + this._addVlmSortOption(); + if (sortSelect) { + sortSelect.value = 'version_id:desc'; + sortSelect.disabled = true; + } + const indicator = document.getElementById('customFilterIndicator'); const filterText = indicator?.querySelector('.customFilterText'); @@ -562,6 +638,10 @@ export class PageControls { filterText.textContent = this._truncateText(displayText, 40); filterText.setAttribute('title', displayText); } + } else { + // No VLM — ensure sort option is removed and dropdown is enabled + this._removeVlmSortOption(); + if (sortSelect) sortSelect.disabled = false; } } @@ -577,6 +657,8 @@ export class PageControls { removeSessionItem('vlm_base_model'); removeSessionItem('vlm_page_type'); + this._restoreSortAfterVlm(); + // Hide the indicator const indicator = document.getElementById('customFilterIndicator'); if (indicator) {