mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-21 09:52:03 -03:00
feat(versions): add View all local versions button to model versions tab
Clicking the button closes the modal, writes filter params to sessionStorage, and reloads the page to show all local versions of the model as individual cards (bypassing group-by-model dedup). The filter respects the update flag strategy and the versions-filter-toggle state (same-base vs all versions). Supporting changes: - sessionStorage keys vlm_model_id / vlm_model_name / vlm_base_model - BaseModelApiClient._addModelSpecificParams adds civitai_model_id param - LoraApiClient calls super._addModelSpecificParams for VLM detection - LorasControls / CheckpointsControls clearCustomFilter checks VLM first - PageControls.checkVlmFilter shows customFilterIndicator with label - Backend parses civitai_model_id, filters before group_by_model dedup
This commit is contained in:
@@ -1465,7 +1465,7 @@
|
|||||||
"resumeModelUpdates": "Resume updates for this model",
|
"resumeModelUpdates": "Resume updates for this model",
|
||||||
"ignoreModelUpdates": "Ignore updates for this model",
|
"ignoreModelUpdates": "Ignore updates for this model",
|
||||||
"viewLocalVersions": "View all local versions",
|
"viewLocalVersions": "View all local versions",
|
||||||
"viewLocalTooltip": "Coming soon"
|
"viewLocalTooltip": "Show all local versions of this model on the main page"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"label": "Base filter",
|
"label": "Base filter",
|
||||||
|
|||||||
@@ -373,6 +373,14 @@ class ModelListingHandler:
|
|||||||
request.query.get("group_by_model", "false").lower() == "true"
|
request.query.get("group_by_model", "false").lower() == "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# View-local-versions filter: show all local versions of a specific model
|
||||||
|
civitai_model_id = request.query.get("civitai_model_id")
|
||||||
|
if civitai_model_id is not None:
|
||||||
|
try:
|
||||||
|
civitai_model_id = int(civitai_model_id)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
civitai_model_id = None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"page": page,
|
"page": page,
|
||||||
"page_size": page_size,
|
"page_size": page_size,
|
||||||
@@ -397,6 +405,7 @@ class ModelListingHandler:
|
|||||||
"name_pattern_exclude": name_pattern_exclude,
|
"name_pattern_exclude": name_pattern_exclude,
|
||||||
"name_pattern_use_regex": name_pattern_use_regex,
|
"name_pattern_use_regex": name_pattern_use_regex,
|
||||||
"group_by_model": group_by_model,
|
"group_by_model": group_by_model,
|
||||||
|
"civitai_model_id": civitai_model_id,
|
||||||
**self._parse_specific_params(request),
|
**self._parse_specific_params(request),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,9 +104,17 @@ class BaseModelService(ABC):
|
|||||||
fetch_duration = time.perf_counter() - t0
|
fetch_duration = time.perf_counter() - t0
|
||||||
initial_count = len(sorted_data)
|
initial_count = len(sorted_data)
|
||||||
|
|
||||||
|
# Optionally filter by civitai model ID (shows all local versions of a specific model)
|
||||||
|
civitai_model_id = kwargs.get("civitai_model_id")
|
||||||
|
if civitai_model_id is not None:
|
||||||
|
sorted_data = [
|
||||||
|
item for item in sorted_data
|
||||||
|
if self._extract_model_id(item) == civitai_model_id
|
||||||
|
]
|
||||||
|
|
||||||
# Optionally group by civitai modelId, showing only the latest version per model
|
# Optionally group by civitai modelId, showing only the latest version per model
|
||||||
dedup_lost = 0
|
dedup_lost = 0
|
||||||
if kwargs.get("group_by_model"):
|
if kwargs.get("group_by_model") and civitai_model_id is None:
|
||||||
dedup_map = {} # modelId -> (item, version_id)
|
dedup_map = {} # modelId -> (item, version_id)
|
||||||
standalone = []
|
standalone = []
|
||||||
for item in sorted_data:
|
for item in sorted_data:
|
||||||
|
|||||||
@@ -1271,8 +1271,9 @@ export class BaseModelApiClient {
|
|||||||
|
|
||||||
params.append('recursive', pageState.searchOptions.recursive ? 'true' : 'false');
|
params.append('recursive', pageState.searchOptions.recursive ? 'true' : 'false');
|
||||||
|
|
||||||
// Pass group-by-model mode to backend
|
// Pass group-by-model mode to backend (skip when showing all versions of a specific model)
|
||||||
if (state.global.settings.group_by_model) {
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
if (state.global.settings.group_by_model && !vlmModelId) {
|
||||||
params.append('group_by_model', 'true');
|
params.append('group_by_model', 'true');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1357,6 +1358,17 @@ export class BaseModelApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_addModelSpecificParams(params, pageState) {
|
_addModelSpecificParams(params, pageState) {
|
||||||
|
// Check for View Local Versions filter (takes priority over recipe filters)
|
||||||
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
if (vlmModelId) {
|
||||||
|
params.append('civitai_model_id', vlmModelId);
|
||||||
|
const vlmBaseModel = getSessionItem('vlm_base_model');
|
||||||
|
if (vlmBaseModel) {
|
||||||
|
params.append('base_model', vlmBaseModel);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.modelType === 'loras') {
|
if (this.modelType === 'loras') {
|
||||||
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ export class LoraApiClient extends BaseModelApiClient {
|
|||||||
* Add LoRA-specific parameters to query
|
* Add LoRA-specific parameters to query
|
||||||
*/
|
*/
|
||||||
_addModelSpecificParams(params, pageState) {
|
_addModelSpecificParams(params, pageState) {
|
||||||
|
// Let parent handle View Local Versions filter first
|
||||||
|
super._addModelSpecificParams(params, pageState);
|
||||||
|
// If VLM filter was applied, skip recipe-specific filters
|
||||||
|
if (params.has('civitai_model_id')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,16 @@ export class CheckpointsControls extends PageControls {
|
|||||||
* Clear checkpoint custom filter and reload
|
* Clear checkpoint custom filter and reload
|
||||||
*/
|
*/
|
||||||
async clearCustomFilter() {
|
async clearCustomFilter() {
|
||||||
|
// Check for View Local Versions filter first
|
||||||
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
if (vlmModelId) {
|
||||||
|
removeSessionItem('vlm_model_id');
|
||||||
|
removeSessionItem('vlm_model_name');
|
||||||
|
removeSessionItem('vlm_base_model');
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
removeSessionItem('recipe_to_checkpoint_filterHash');
|
removeSessionItem('recipe_to_checkpoint_filterHash');
|
||||||
removeSessionItem('recipe_to_checkpoint_filterHashes');
|
removeSessionItem('recipe_to_checkpoint_filterHashes');
|
||||||
removeSessionItem('filterCheckpointRecipeName');
|
removeSessionItem('filterCheckpointRecipeName');
|
||||||
|
|||||||
@@ -112,6 +112,16 @@ export class LorasControls extends PageControls {
|
|||||||
* Clear the custom filter and reload the page
|
* Clear the custom filter and reload the page
|
||||||
*/
|
*/
|
||||||
async clearCustomFilter() {
|
async clearCustomFilter() {
|
||||||
|
// Check for View Local Versions filter first (handles VLM and reloads)
|
||||||
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
if (vlmModelId) {
|
||||||
|
removeSessionItem('vlm_model_id');
|
||||||
|
removeSessionItem('vlm_model_name');
|
||||||
|
removeSessionItem('vlm_base_model');
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Clearing custom filter...");
|
console.log("Clearing custom filter...");
|
||||||
// Remove filter parameters from session storage
|
// Remove filter parameters from session storage
|
||||||
removeSessionItem('recipe_to_lora_filterLoraHash');
|
removeSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// PageControls.js - Manages controls for both LoRAs and Checkpoints pages
|
// PageControls.js - Manages controls for both LoRAs and Checkpoints pages
|
||||||
import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js';
|
import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js';
|
||||||
import { getStorageItem, setStorageItem, getSessionItem, setSessionItem } from '../../utils/storageHelpers.js';
|
import { getStorageItem, setStorageItem, getSessionItem, setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
|
||||||
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
|
import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js';
|
||||||
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js';
|
||||||
import { sidebarManager } from '../SidebarManager.js';
|
import { sidebarManager } from '../SidebarManager.js';
|
||||||
@@ -129,6 +129,9 @@ export class PageControls {
|
|||||||
clearFilterBtn.addEventListener('click', () => this.clearCustomFilter());
|
clearFilterBtn.addEventListener('click', () => this.clearCustomFilter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for View Local Versions filter
|
||||||
|
this.checkVlmFilter();
|
||||||
|
|
||||||
// Page-specific event listeners
|
// Page-specific event listeners
|
||||||
this.initPageSpecificListeners();
|
this.initPageSpecificListeners();
|
||||||
}
|
}
|
||||||
@@ -459,15 +462,57 @@ export class PageControls {
|
|||||||
this.api.toggleBulkMode();
|
this.api.toggleBulkMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear custom filter
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Check for View Local Versions filter in sessionStorage
|
||||||
|
*/
|
||||||
|
checkVlmFilter() {
|
||||||
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
const vlmModelName = getSessionItem('vlm_model_name');
|
||||||
|
const vlmBaseModel = getSessionItem('vlm_base_model');
|
||||||
|
|
||||||
|
if (vlmModelId && vlmModelName) {
|
||||||
|
const indicator = document.getElementById('customFilterIndicator');
|
||||||
|
const filterText = indicator?.querySelector('.customFilterText');
|
||||||
|
|
||||||
|
if (indicator && filterText) {
|
||||||
|
indicator.classList.remove('hidden');
|
||||||
|
|
||||||
|
const prefix = vlmBaseModel
|
||||||
|
? 'Showing same-base versions from'
|
||||||
|
: 'Showing all versions from';
|
||||||
|
const displayText = `${prefix}: ${vlmModelName}`;
|
||||||
|
|
||||||
|
filterText.textContent = this._truncateText(displayText, 40);
|
||||||
|
filterText.setAttribute('title', displayText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear custom filter
|
* Clear custom filter
|
||||||
*/
|
*/
|
||||||
async clearCustomFilter() {
|
async clearCustomFilter() {
|
||||||
|
// Check for View Local Versions filter first
|
||||||
|
const vlmModelId = getSessionItem('vlm_model_id');
|
||||||
|
if (vlmModelId) {
|
||||||
|
removeSessionItem('vlm_model_id');
|
||||||
|
removeSessionItem('vlm_model_name');
|
||||||
|
removeSessionItem('vlm_base_model');
|
||||||
|
|
||||||
|
// Full page reload to restore initial state (mirrors the "set" action)
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise delegate to subclass for recipe filters
|
||||||
if (!this.api) {
|
if (!this.api) {
|
||||||
console.error('API methods not registered');
|
console.error('API methods not registered');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.clearCustomFilter();
|
await this.api.clearCustomFilter();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -475,6 +520,14 @@ export class PageControls {
|
|||||||
showToast('toast.controls.clearFilterFailed', { message: error.message }, 'error');
|
showToast('toast.controls.clearFilterFailed', { message: error.message }, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate text with ellipsis
|
||||||
|
*/
|
||||||
|
_truncateText(text, maxLength) {
|
||||||
|
if (!text) return '';
|
||||||
|
return text.length > maxLength ? `${text.substring(0, maxLength - 3)}...` : text;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the favorites filter button state
|
* Initialize the favorites filter button state
|
||||||
|
|||||||
@@ -752,6 +752,7 @@ export async function showModelModal(model, modelType) {
|
|||||||
modelId: civitaiModelId,
|
modelId: civitaiModelId,
|
||||||
currentVersionId: civitaiVersionId,
|
currentVersionId: civitaiVersionId,
|
||||||
currentBaseModel: modelWithFullData.base_model,
|
currentBaseModel: modelWithFullData.base_model,
|
||||||
|
modelName: model.model_name,
|
||||||
onUpdateStatusChange: handleUpdateStatusChange,
|
onUpdateStatusChange: handleUpdateStatusChange,
|
||||||
});
|
});
|
||||||
setupEditableFields(modelWithFullData.file_path, modelType);
|
setupEditableFields(modelWithFullData.file_path, modelType);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { translate } from '../../utils/i18nHelpers.js';
|
|||||||
import { state } from '../../state/index.js';
|
import { state } from '../../state/index.js';
|
||||||
import { buildCivitaiModelUrl } from '../../utils/civitaiUtils.js';
|
import { buildCivitaiModelUrl } from '../../utils/civitaiUtils.js';
|
||||||
import { formatFileSize } from './utils.js';
|
import { formatFileSize } from './utils.js';
|
||||||
|
import { setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
|
||||||
|
|
||||||
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.mov', '.mkv'];
|
const VIDEO_EXTENSIONS = ['.mp4', '.webm', '.mov', '.mkv'];
|
||||||
const PREVIEW_PLACEHOLDER_URL = '/loras_static/images/no-preview.png';
|
const PREVIEW_PLACEHOLDER_URL = '/loras_static/images/no-preview.png';
|
||||||
@@ -744,7 +745,7 @@ function renderToolbar(record, toolbarState = {}) {
|
|||||||
<button class="versions-toolbar-btn versions-toolbar-btn-primary" data-versions-action="toggle-model-ignore">
|
<button class="versions-toolbar-btn versions-toolbar-btn-primary" data-versions-action="toggle-model-ignore">
|
||||||
${escapeHtml(ignoreText)}
|
${escapeHtml(ignoreText)}
|
||||||
</button>
|
</button>
|
||||||
<button class="versions-toolbar-btn versions-toolbar-btn-secondary" data-versions-action="view-local" title="${escapeHtml(translate('modals.model.versions.actions.viewLocalTooltip', {}, 'Coming soon'))}" disabled>
|
<button class="versions-toolbar-btn versions-toolbar-btn-secondary" data-versions-action="view-local" title="${escapeHtml(translate('modals.model.versions.actions.viewLocalTooltip', {}, 'Show all local versions of this model on the main page'))}">
|
||||||
${escapeHtml(viewLocalText)}
|
${escapeHtml(viewLocalText)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -792,6 +793,7 @@ export function initVersionsTab({
|
|||||||
modelId,
|
modelId,
|
||||||
currentVersionId,
|
currentVersionId,
|
||||||
currentBaseModel,
|
currentBaseModel,
|
||||||
|
modelName,
|
||||||
onUpdateStatusChange,
|
onUpdateStatusChange,
|
||||||
}) {
|
}) {
|
||||||
const pane = document.querySelector(`#${modalId} #versions-tab`);
|
const pane = document.querySelector(`#${modalId} #versions-tab`);
|
||||||
@@ -1019,6 +1021,31 @@ export function initVersionsTab({
|
|||||||
render(controller.record);
|
render(controller.record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleViewLocalVersions() {
|
||||||
|
if (!controller.record || !modelId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Determine base model filter based on current display mode
|
||||||
|
const baseModelInfo = getCurrentVersionBaseModel(controller.record, normalizedCurrentVersionId);
|
||||||
|
const isFilteringActive =
|
||||||
|
displayMode === DISPLAY_FILTER_MODES.SAME_BASE &&
|
||||||
|
Boolean(baseModelInfo.normalized);
|
||||||
|
|
||||||
|
// Write filter params to sessionStorage
|
||||||
|
setSessionItem('vlm_model_id', String(modelId));
|
||||||
|
setSessionItem('vlm_model_name', modelName || String(modelId));
|
||||||
|
if (isFilteringActive) {
|
||||||
|
// Use raw (non-normalized) base model for exact backend matching
|
||||||
|
setSessionItem('vlm_base_model', baseModelInfo.raw);
|
||||||
|
} else {
|
||||||
|
removeSessionItem('vlm_base_model');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the modal and reload the page to show filtered cards
|
||||||
|
modalManager.closeModal(modalId);
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
async function handleToggleVersionIgnore(button, versionId) {
|
async function handleToggleVersionIgnore(button, versionId) {
|
||||||
if (!controller.record) {
|
if (!controller.record) {
|
||||||
return;
|
return;
|
||||||
@@ -1348,6 +1375,10 @@ export function initVersionsTab({
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleToggleVersionDisplayMode();
|
handleToggleVersionDisplayMode();
|
||||||
break;
|
break;
|
||||||
|
case 'view-local':
|
||||||
|
event.preventDefault();
|
||||||
|
handleViewLocalVersions();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user