diff --git a/locales/de.json b/locales/de.json
index 860602f4..d2ffa18f 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "Verwendungsanzahl"
+ },
+ "footer": {
+ "versionCount": "{count} Versionen",
+ "viewAllVersions": "Alle lokalen Versionen anzeigen"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "Kleinste",
"usage": "Anzahl Nutzung",
"usageDesc": "Meiste",
- "usageAsc": "Wenigste"
+ "usageAsc": "Wenigste",
+ "versionsCount": "Lokale Versionen",
+ "versionsCountDesc": "Meiste Versionen zuerst",
+ "versionsCountAsc": "Wenigste Versionen zuerst"
},
"refresh": {
"title": "Modelliste aktualisieren",
diff --git a/locales/en.json b/locales/en.json
index 91d1f28f..36b5aacd 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "Times used"
+ },
+ "footer": {
+ "versionCount": "{count} versions",
+ "viewAllVersions": "View all local versions"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "Smallest",
"usage": "Use Count",
"usageDesc": "Most",
- "usageAsc": "Least"
+ "usageAsc": "Least",
+ "versionsCount": "Local Versions",
+ "versionsCountDesc": "Most versions first",
+ "versionsCountAsc": "Fewest versions first"
},
"refresh": {
"title": "Refresh model list",
diff --git a/locales/es.json b/locales/es.json
index 8ea639a7..7a6f9289 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "Veces usado"
+ },
+ "footer": {
+ "versionCount": "{count} versiones",
+ "viewAllVersions": "Ver todas las versiones locales"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "Menor",
"usage": "Número de usos",
"usageDesc": "Más",
- "usageAsc": "Menos"
+ "usageAsc": "Menos",
+ "versionsCount": "Versiones locales",
+ "versionsCountDesc": "Más versiones primero",
+ "versionsCountAsc": "Menos versiones primero"
},
"refresh": {
"title": "Actualizar lista de modelos",
diff --git a/locales/fr.json b/locales/fr.json
index a53052cd..8226802c 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "Nombre d'utilisations"
+ },
+ "footer": {
+ "versionCount": "{count} versions",
+ "viewAllVersions": "Voir toutes les versions locales"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "Plus petit",
"usage": "Nombre d'utilisations",
"usageDesc": "Plus",
- "usageAsc": "Moins"
+ "usageAsc": "Moins",
+ "versionsCount": "Versions locales",
+ "versionsCountDesc": "Plus de versions d'abord",
+ "versionsCountAsc": "Moins de versions d'abord"
},
"refresh": {
"title": "Actualiser la liste des modèles",
diff --git a/locales/he.json b/locales/he.json
index 701a6f28..cf4a4a86 100644
--- a/locales/he.json
+++ b/locales/he.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "מספר שימושים"
+ },
+ "footer": {
+ "versionCount": "{count} גרסאות",
+ "viewAllVersions": "הצג את כל הגרסאות המקומיות"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "הקטן ביותר",
"usage": "מספר שימושים",
"usageDesc": "הכי הרבה",
- "usageAsc": "הכי פחות"
+ "usageAsc": "הכי פחות",
+ "versionsCount": "גרסאות מקומיות",
+ "versionsCountDesc": "הכי הרבה גרסאות ראשונות",
+ "versionsCountAsc": "הכי מעט גרסאות ראשונות"
},
"refresh": {
"title": "רענן רשימת מודלים",
diff --git a/locales/ja.json b/locales/ja.json
index 7f7f6996..24f8c689 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "使用回数"
+ },
+ "footer": {
+ "versionCount": "{count} バージョン",
+ "viewAllVersions": "ローカルの全バージョンを表示"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "小さい順",
"usage": "使用回数",
"usageDesc": "多い",
- "usageAsc": "少ない"
+ "usageAsc": "少ない",
+ "versionsCount": "ローカルバージョン数",
+ "versionsCountDesc": "バージョン数の多い順",
+ "versionsCountAsc": "バージョン数の少ない順"
},
"refresh": {
"title": "モデルリストを更新",
diff --git a/locales/ko.json b/locales/ko.json
index 9701ed04..12eaa3c2 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "사용 횟수"
+ },
+ "footer": {
+ "versionCount": "{count}개 버전",
+ "viewAllVersions": "모든 로컬 버전 보기"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "작은 순서",
"usage": "사용 횟수",
"usageDesc": "많은 순",
- "usageAsc": "적은 순"
+ "usageAsc": "적은 순",
+ "versionsCount": "로컬 버전 수",
+ "versionsCountDesc": "버전 수 많은 순",
+ "versionsCountAsc": "버전 수 적은 순"
},
"refresh": {
"title": "모델 목록 새로고침",
diff --git a/locales/ru.json b/locales/ru.json
index 64907d45..15078501 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "Количество использований"
+ },
+ "footer": {
+ "versionCount": "{count} версий",
+ "viewAllVersions": "Показать все локальные версии"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "Наименьшим",
"usage": "Число использований",
"usageDesc": "Больше",
- "usageAsc": "Меньше"
+ "usageAsc": "Меньше",
+ "versionsCount": "Локальные версии",
+ "versionsCountDesc": "Сначала больше версий",
+ "versionsCountAsc": "Сначала меньше версий"
},
"refresh": {
"title": "Обновить список моделей",
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 66156f98..e7212976 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "使用次数"
+ },
+ "footer": {
+ "versionCount": "{count} 个版本",
+ "viewAllVersions": "查看所有本地版本"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "最小",
"usage": "使用次数",
"usageDesc": "最多",
- "usageAsc": "最少"
+ "usageAsc": "最少",
+ "versionsCount": "本地版本数",
+ "versionsCountDesc": "版本数从多到少",
+ "versionsCountAsc": "版本数从少到多"
},
"refresh": {
"title": "刷新模型列表",
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 5ff2028d..5760d932 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -145,6 +145,10 @@
},
"usage": {
"timesUsed": "使用次數"
+ },
+ "footer": {
+ "versionCount": "{count} 個版本",
+ "viewAllVersions": "檢視所有本地版本"
}
},
"globalContextMenu": {
@@ -675,7 +679,10 @@
"sizeAsc": "最小",
"usage": "使用次數",
"usageDesc": "最多",
- "usageAsc": "最少"
+ "usageAsc": "最少",
+ "versionsCount": "本地版本數",
+ "versionsCountDesc": "版本數從多到少",
+ "versionsCountAsc": "版本數從少到多"
},
"refresh": {
"title": "重新整理模型列表",
diff --git a/py/services/base_model_service.py b/py/services/base_model_service.py
index e7ba379b..016a9d76 100644
--- a/py/services/base_model_service.py
+++ b/py/services/base_model_service.py
@@ -115,19 +115,50 @@ class BaseModelService(ABC):
# Optionally group by civitai modelId, showing only the latest version per model
dedup_lost = 0
if kwargs.get("group_by_model") and civitai_model_id is None:
- dedup_map = {} # modelId -> (item, version_id)
+ # Determine whether to further sub-group by base model
+ # When update_flag_strategy is "same_base", versions with different
+ # base models are effectively different groups — the dedup key
+ # needs to include base_model so the version count and VLM flow
+ # stay consistent (card shows correct count for its base model).
+ ufs = self.settings.get("update_flag_strategy", "same_base")
+ group_by_base = ufs == "same_base"
+
+ dedup_map = {} # (modelId [,base_model]) -> (item, version_id)
+ version_counter = {} # same-key -> count
standalone = []
for item in sorted_data:
mid = self._extract_model_id(item)
if mid is None:
standalone.append(item)
continue
+ key = (mid, item.get("base_model") or "") if group_by_base else mid
+ # Count all versions per key
+ version_counter[key] = version_counter.get(key, 0) + 1
vid = self._extract_version_id(item) or 0
- if mid not in dedup_map or vid > dedup_map[mid][1]:
- dedup_map[mid] = (item, vid)
+ if key not in dedup_map or vid > dedup_map[key][1]:
+ dedup_map[key] = (item, vid)
+ # Attach version_count to each surviving grouped item (shallow copy
+ # to avoid mutating cached dicts — the cache is shared across requests)
+ for key, (item, vid) in dedup_map.items():
+ item = dict(item)
+ item["version_count"] = version_counter[key]
+ dedup_map[key] = (item, vid)
dedup_lost = len(sorted_data) - (len(dedup_map) + len(standalone))
sorted_data = [entry[0] for entry in dedup_map.values()] + standalone
+ # Re-sort by version_count after dedup (only makes sense in group_by_model mode)
+ is_group_by_active = kwargs.get("group_by_model") and civitai_model_id is None
+ if sort_params.key == "versions_count" and is_group_by_active:
+ reverse = sort_params.order == "desc"
+ sorted_data.sort(
+ key=lambda x: (
+ x.get("version_count", 0),
+ (x.get("model_name") or x.get("file_name") or "").lower(),
+ x.get("file_path", "").lower(),
+ ),
+ reverse=reverse,
+ )
+
t1 = time.perf_counter()
if hash_filters:
filtered_data = await self._apply_hash_filters(sorted_data, hash_filters)
diff --git a/py/services/checkpoint_service.py b/py/services/checkpoint_service.py
index 02befbda..82cf8aaa 100644
--- a/py/services/checkpoint_service.py
+++ b/py/services/checkpoint_service.py
@@ -48,6 +48,7 @@ class CheckpointService(BaseModelService):
"skip_metadata_refresh": bool(checkpoint_data.get("skip_metadata_refresh", False)),
"civitai": self.filter_civitai_data(checkpoint_data.get("civitai", {}), minimal=True),
"auto_tags": checkpoint_data.get("auto_tags") or extract_auto_tags(checkpoint_data),
+ "version_count": checkpoint_data.get("version_count"),
}
def find_duplicate_hashes(self) -> Dict:
diff --git a/py/services/embedding_service.py b/py/services/embedding_service.py
index ea187f85..779cbee3 100644
--- a/py/services/embedding_service.py
+++ b/py/services/embedding_service.py
@@ -48,6 +48,7 @@ class EmbeddingService(BaseModelService):
"skip_metadata_refresh": bool(embedding_data.get("skip_metadata_refresh", False)),
"civitai": self.filter_civitai_data(embedding_data.get("civitai", {}), minimal=True),
"auto_tags": embedding_data.get("auto_tags") or extract_auto_tags(embedding_data),
+ "version_count": embedding_data.get("version_count"),
}
def find_duplicate_hashes(self) -> Dict:
diff --git a/py/services/lora_service.py b/py/services/lora_service.py
index 6819e4fd..75c21bf7 100644
--- a/py/services/lora_service.py
+++ b/py/services/lora_service.py
@@ -59,6 +59,7 @@ class LoraService(BaseModelService):
lora_data.get("civitai", {}), minimal=True
),
"auto_tags": lora_data.get("auto_tags") or extract_auto_tags(lora_data),
+ "version_count": lora_data.get("version_count"),
}
async def _apply_specific_filters(self, data: List[Dict], **kwargs) -> List[Dict]:
diff --git a/py/services/model_cache.py b/py/services/model_cache.py
index 9b396634..ba515115 100644
--- a/py/services/model_cache.py
+++ b/py/services/model_cache.py
@@ -18,6 +18,8 @@ SUPPORTED_SORT_MODES = [
('size', 'desc'),
('usage', 'asc'),
('usage', 'desc'),
+ ('versions_count', 'asc'),
+ ('versions_count', 'desc'),
]
# Is this in use?
@@ -263,6 +265,17 @@ class ModelCache:
),
reverse=reverse
)
+ elif sort_key == 'versions_count':
+ # Pre-dedup sort: fall back to name sort.
+ # Actual re-sort by version_count happens in get_paginated_data after dedup.
+ result = natsorted(
+ data,
+ key=lambda x: (
+ self._get_display_name(x).lower(),
+ x.get('file_path', '').lower()
+ ),
+ reverse=reverse
+ )
else:
# Fallback: no sort
result = list(data)
diff --git a/static/css/components/card.css b/static/css/components/card.css
index 71bf2bb5..f3c83105 100644
--- a/static/css/components/card.css
+++ b/static/css/components/card.css
@@ -509,6 +509,50 @@
background: rgba(0,0,0,0.18); /* Optional: subtle background for contrast */
}
+/* Clickable version count link (shown in group-by-model mode) */
+.version-count-link {
+ display: inline-block;
+ color: var(--color-accent);
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
+ font-size: 0.85em;
+ line-height: 1.4;
+ margin-top: 2px;
+ border: 1px solid var(--color-accent-border);
+ border-radius: var(--border-radius-xs);
+ padding: 1px 6px;
+ background: var(--color-accent-subtle);
+ cursor: pointer;
+ transition: background 0.15s ease, border-color 0.15s ease;
+}
+.version-count-link:hover {
+ background: var(--color-accent-border);
+ border-color: var(--color-accent-transparent);
+}
+
+/* Medium density adjustments for version count link */
+.medium-density .version-count-link {
+ font-size: 0.8em;
+}
+
+.medium-density .badge-version-unit .version-count-link {
+ max-width: 90px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Compact density adjustments for version count link */
+.compact-density .version-count-link {
+ font-size: 0.75em;
+}
+
+.compact-density .badge-version-unit .version-count-link {
+ max-width: 70px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
/* Version row — flex container for badges + version names */
.version-row {
display: flex;
diff --git a/static/css/style.css b/static/css/style.css
index 9d88d6ff..dfc00782 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -59,3 +59,8 @@
.initialization-notice .loading-spinner {
margin-bottom: var(--space-2);
}
+
+/* Hide versions_count sort option when group-by-model is off */
+body:not(.group-by-model) .sort-option-versions-count {
+ display: none;
+}
diff --git a/static/js/components/ContextMenu/GlobalContextMenu.js b/static/js/components/ContextMenu/GlobalContextMenu.js
index c081a926..be834c7b 100644
--- a/static/js/components/ContextMenu/GlobalContextMenu.js
+++ b/static/js/components/ContextMenu/GlobalContextMenu.js
@@ -108,6 +108,11 @@ export class GlobalContextMenu extends BaseContextMenu {
const newValue = !state.global.settings.group_by_model;
state.global.settings.group_by_model = newValue;
+ // Save/restore sort preference when toggling group_by_model
+ if (window.pageControls?.onGroupByModelToggled) {
+ window.pageControls.onGroupByModelToggled(newValue);
+ }
+
sm.saveSetting('group_by_model', newValue).catch((error) => {
console.error('Failed to save group_by_model setting:', error);
// Revert state on failure
diff --git a/static/js/components/controls/CheckpointsControls.js b/static/js/components/controls/CheckpointsControls.js
index 57c72897..0d61644d 100644
--- a/static/js/components/controls/CheckpointsControls.js
+++ b/static/js/components/controls/CheckpointsControls.js
@@ -102,7 +102,12 @@ export class CheckpointsControls extends PageControls {
removeSessionItem('vlm_model_name');
removeSessionItem('vlm_base_model');
removeSessionItem('vlm_page_type');
- window.location.reload();
+ // Hide the indicator
+ const indicator = document.getElementById('customFilterIndicator');
+ if (indicator) {
+ indicator.classList.add('hidden');
+ }
+ await resetAndReload();
return;
}
diff --git a/static/js/components/controls/LorasControls.js b/static/js/components/controls/LorasControls.js
index b9e42473..d9733bcb 100644
--- a/static/js/components/controls/LorasControls.js
+++ b/static/js/components/controls/LorasControls.js
@@ -119,7 +119,11 @@ export class LorasControls extends PageControls {
removeSessionItem('vlm_model_name');
removeSessionItem('vlm_base_model');
removeSessionItem('vlm_page_type');
- window.location.reload();
+ const indicator = document.getElementById('customFilterIndicator');
+ if (indicator) {
+ indicator.classList.add('hidden');
+ }
+ await resetAndReload();
return;
}
diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js
index 3c6ce31c..d00441b0 100644
--- a/static/js/components/controls/PageControls.js
+++ b/static/js/components/controls/PageControls.js
@@ -1,6 +1,6 @@
// PageControls.js - Manages controls for both LoRAs and Checkpoints pages
import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js';
-import { getStorageItem, setStorageItem, getSessionItem, setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
+import { getStorageItem, setStorageItem, removeStorageItem, getSessionItem, setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
import { sidebarManager } from '../SidebarManager.js';
@@ -465,6 +465,68 @@ export class PageControls {
/**
* Clear custom filter
*/
+ /**
+ * Trigger View Local Versions without page reload
+ * Sets sessionStorage and reloads data via the API.
+ */
+ triggerVlmView(modelId, modelName, baseModel, pageType) {
+ const targetPageType = pageType || this.pageType;
+ setSessionItem('vlm_model_id', String(modelId));
+ setSessionItem('vlm_model_name', modelName || String(modelId));
+ setSessionItem('vlm_page_type', targetPageType);
+ if (baseModel) {
+ setSessionItem('vlm_base_model', baseModel);
+ } else {
+ removeSessionItem('vlm_base_model');
+ }
+ // Reload data via API (no page reload)
+ this.resetAndReload(true).then(() => {
+ // Show the VLM indicator after data loads
+ this.checkVlmFilter();
+ });
+ }
+
+ /**
+ * Called when group_by_model is toggled.
+ * Saves current sort when entering grouped mode, restores normal sort
+ * when leaving — prevents "Most versions first" persisting after exit.
+ */
+ onGroupByModelToggled(isEnabled) {
+ const normalKey = `${this.pageType}_sort_normal`;
+ const groupedKey = `${this.pageType}_sort_grouped`;
+
+ if (isEnabled) {
+ // Entering group mode: save current sort for later restoration
+ setStorageItem(normalKey, this.pageState.sortBy);
+ // Restore previously saved grouped sort, if any
+ const savedGroupedSort = getStorageItem(groupedKey);
+ if (savedGroupedSort) {
+ this.pageState.sortBy = savedGroupedSort;
+ this.saveSortPreference(savedGroupedSort);
+ const sortSelect = document.getElementById('sortSelect');
+ if (sortSelect) {
+ sortSelect.value = savedGroupedSort;
+ }
+ }
+ } else {
+ // Leaving group mode: save current grouped sort aside, restore normal
+ const currentSort = this.pageState.sortBy;
+ if (currentSort && currentSort.startsWith('versions_count')) {
+ setStorageItem(groupedKey, currentSort);
+ }
+ const savedNormalSort = getStorageItem(normalKey);
+ if (savedNormalSort) {
+ removeStorageItem(normalKey);
+ this.pageState.sortBy = savedNormalSort;
+ this.saveSortPreference(savedNormalSort);
+ const sortSelect = document.getElementById('sortSelect');
+ if (sortSelect) {
+ sortSelect.value = savedNormalSort;
+ }
+ }
+ }
+ }
+
/**
* Check for View Local Versions filter in sessionStorage (page-type-scoped)
*/
@@ -515,8 +577,14 @@ export class PageControls {
removeSessionItem('vlm_base_model');
removeSessionItem('vlm_page_type');
- // Full page reload to restore initial state (mirrors the "set" action)
- window.location.reload();
+ // Hide the indicator
+ const indicator = document.getElementById('customFilterIndicator');
+ if (indicator) {
+ indicator.classList.add('hidden');
+ }
+
+ // Reload data via API (no page reload)
+ await this.resetAndReload(true);
return;
}
diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js
index c8b1b9ee..9233400f 100644
--- a/static/js/components/shared/ModelCard.js
+++ b/static/js/components/shared/ModelCard.js
@@ -100,6 +100,12 @@ function handleModelCardEvent_internal(event, modelType) {
return true; // Stop propagation
}
+ if (event.target.closest('.version-count-link')) {
+ event.stopPropagation();
+ handleViewLocalVersionsFromCard(card, modelType);
+ return true;
+ }
+
// If no specific element was clicked, handle the card click (show modal or toggle selection)
handleCardClick(card, modelType);
return false; // Continue with other handlers (e.g., bulk selection)
@@ -265,6 +271,22 @@ async function handleExampleImagesAccess(card, modelType) {
}
}
+function handleViewLocalVersionsFromCard(card, modelType) {
+ const modelId = card.dataset.modelId;
+ const modelName = card.dataset.name;
+ if (!modelId) return;
+ // Respect update_flag_strategy: only filter by base model when the strategy says so
+ const strategy = state.global?.settings?.update_flag_strategy;
+ const shouldFilterByBase = strategy === 'same_base';
+ const baseModel = shouldFilterByBase && card.dataset.base_model !== 'Unknown'
+ ? card.dataset.base_model
+ : undefined;
+ // Use the no-reload VLM flow via PageControls
+ if (window.pageControls && typeof window.pageControls.triggerVlmView === 'function') {
+ window.pageControls.triggerVlmView(modelId, modelName, baseModel, modelType);
+ }
+}
+
function handleCardClick(card, modelType) {
const pageState = getCurrentPageState();
@@ -448,6 +470,10 @@ export function createModelCard(model, modelType) {
const hasUpdateAvailable = Boolean(model.update_available);
card.dataset.update_available = hasUpdateAvailable ? 'true' : 'false';
card.dataset.skip_metadata_refresh = model.skip_metadata_refresh ? 'true' : 'false';
+ // Store version_count for group-by-model display
+ if (model.version_count !== undefined) {
+ card.dataset.version_count = model.version_count;
+ }
// To only show usage_count when sorting by usage.
const pageState = getCurrentPageState();
@@ -659,16 +685,28 @@ export function createModelCard(model, modelType) {
const autoTags = model.auto_tags || [];
const hlTags = autoTags.filter(t => t === 'HIGH' || t === 'LOW');
const hasVersionName = model.civitai?.name;
- if (!hlTags.length && !hasVersionName) return '';
+ // When group_by_model is active and model has multiple versions,
+ // show clickable version count instead of version name (and hide badges)
+ const isGroupByModel = state.global.settings.group_by_model;
+ const versionCount = model.version_count;
+ const showVersionCount = isGroupByModel && versionCount > 1;
+ if (!hlTags.length && !hasVersionName && !showVersionCount) return '';
const density = state.global.settings.display_density || 'default';
const shortLabels = density === 'medium' || density === 'compact';
- const badges = hlTags.map(t => {
+ // Don't show HIGH/LOW badges when showing version count (confusing in grouped mode)
+ const badges = !showVersionCount ? hlTags.map(t => {
const cls = t === 'HIGH' ? 'hl-badge hl-badge--high' : 'hl-badge hl-badge--low';
const label = shortLabels ? (t === 'HIGH' ? 'H' : 'L') : t;
const titleAttr = shortLabels ? ` title="${t}"` : '';
return `${label}`;
- }).join('');
- const versionHtml = hasVersionName ? `${model.civitai.name}` : '';
+ }).join('') : '';
+ let versionHtml = '';
+ if (showVersionCount) {
+ const countLabel = translate('modelCard.footer.versionCount', { count: versionCount }, `${versionCount} versions`);
+ versionHtml = `${countLabel}`;
+ } else if (hasVersionName) {
+ versionHtml = `${model.civitai.name}`;
+ }
return `${badges}${versionHtml}`;
})()}
${hasUsageCount ? `${model.usage_count}×` : ''}
diff --git a/static/js/components/shared/ModelVersionsTab.js b/static/js/components/shared/ModelVersionsTab.js
index aebdb3cc..002045fb 100644
--- a/static/js/components/shared/ModelVersionsTab.js
+++ b/static/js/components/shared/ModelVersionsTab.js
@@ -1042,9 +1042,16 @@ export function initVersionsTab({
removeSessionItem('vlm_base_model');
}
- // Close the modal and reload the page to show filtered cards
+ // Close the modal and navigate via no-reload VLM flow
modalManager.closeModal(modalId);
- window.location.reload();
+ if (window.pageControls && typeof window.pageControls.triggerVlmView === 'function') {
+ window.pageControls.triggerVlmView(
+ modelId,
+ modelName || String(modelId),
+ isFilteringActive ? baseModelInfo.raw : undefined,
+ modelType
+ );
+ }
}
async function handleToggleVersionIgnore(button, versionId) {
diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js
index e7be6e23..6152080f 100644
--- a/static/js/managers/SettingsManager.js
+++ b/static/js/managers/SettingsManager.js
@@ -2018,6 +2018,10 @@ export class SettingsManager {
}
if (settingKey === 'show_only_sfw' || settingKey === 'blur_mature_content' || settingKey === 'group_by_model') {
+ // Save/restore sort preference when toggling group_by_model
+ if (settingKey === 'group_by_model' && window.pageControls?.onGroupByModelToggled) {
+ window.pageControls.onGroupByModelToggled(value);
+ }
this.reloadContent();
}
diff --git a/templates/components/controls.html b/templates/components/controls.html
index fb22f016..1c122b96 100644
--- a/templates/components/controls.html
+++ b/templates/components/controls.html
@@ -37,6 +37,12 @@
{% endif %}
+ {% if page_id != 'recipes' %}
+
+ {% endif %}
{% if page_id == 'recipes' %}