mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
feat(frontend): add Other Models page with subtype filter and badges
This commit is contained in:
@@ -9,7 +9,8 @@ import { state } from '../state/index.js';
|
||||
export const MODEL_TYPES = {
|
||||
LORA: 'loras',
|
||||
CHECKPOINT: 'checkpoints',
|
||||
EMBEDDING: 'embeddings' // Future model type
|
||||
EMBEDDING: 'embeddings',
|
||||
OTHER: 'other'
|
||||
};
|
||||
|
||||
// Base API configuration for each model type
|
||||
@@ -40,6 +41,15 @@ export const MODEL_CONFIG = {
|
||||
supportsBulkOperations: true,
|
||||
supportsMove: true,
|
||||
templateName: 'embeddings.html'
|
||||
},
|
||||
[MODEL_TYPES.OTHER]: {
|
||||
displayName: 'Other Model',
|
||||
singularName: 'other',
|
||||
defaultPageSize: 100,
|
||||
supportsLetterFilter: false,
|
||||
supportsBulkOperations: true,
|
||||
supportsMove: true,
|
||||
templateName: 'other.html'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -133,6 +143,9 @@ export const MODEL_SPECIFIC_ENDPOINTS = {
|
||||
},
|
||||
[MODEL_TYPES.EMBEDDING]: {
|
||||
metadata: `/api/lm/${MODEL_TYPES.EMBEDDING}/metadata`,
|
||||
},
|
||||
[MODEL_TYPES.OTHER]: {
|
||||
metadata: `/api/lm/${MODEL_TYPES.OTHER}/metadata`,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LoraApiClient } from './loraApi.js';
|
||||
import { CheckpointApiClient } from './checkpointApi.js';
|
||||
import { EmbeddingApiClient } from './embeddingApi.js';
|
||||
import { OtherApiClient } from './otherApi.js';
|
||||
import { MODEL_TYPES, isValidModelType } from './apiConfig.js';
|
||||
import { state } from '../state/index.js';
|
||||
|
||||
@@ -12,6 +13,8 @@ export function createModelApiClient(modelType) {
|
||||
return new CheckpointApiClient(MODEL_TYPES.CHECKPOINT);
|
||||
case MODEL_TYPES.EMBEDDING:
|
||||
return new EmbeddingApiClient(MODEL_TYPES.EMBEDDING);
|
||||
case MODEL_TYPES.OTHER:
|
||||
return new OtherApiClient(MODEL_TYPES.OTHER);
|
||||
default:
|
||||
throw new Error(`Unsupported model type: ${modelType}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { BaseModelApiClient } from './baseModelApi.js';
|
||||
|
||||
/**
|
||||
* Other-models-specific API client (VAE, upscalers, text encoders, etc.)
|
||||
*/
|
||||
export class OtherApiClient extends BaseModelApiClient {
|
||||
}
|
||||
@@ -139,8 +139,8 @@ export class BulkContextMenu extends BaseContextMenu {
|
||||
|
||||
const downloadExampleImagesSubmenu = this.menu.querySelector('[data-has-submenu="download-example-images"]');
|
||||
if (downloadExampleImagesSubmenu) {
|
||||
// Show on model pages (loras, checkpoints, embeddings), hide on recipes
|
||||
downloadExampleImagesSubmenu.style.display = ['loras', 'checkpoints', 'embeddings'].includes(currentModelType) ? 'flex' : 'none';
|
||||
// Show on model pages (loras, checkpoints, embeddings, other), hide on recipes
|
||||
downloadExampleImagesSubmenu.style.display = ['loras', 'checkpoints', 'embeddings', 'other'].includes(currentModelType) ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
const skipMetadataRefreshItem = this.menu.querySelector('[data-action="skip-metadata-refresh"]');
|
||||
|
||||
@@ -112,7 +112,8 @@ export const ModelContextMenuMixin = {
|
||||
const prefixMap = {
|
||||
lora: 'loras',
|
||||
checkpoint: 'checkpoints',
|
||||
embedding: 'embeddings'
|
||||
embedding: 'embeddings',
|
||||
other: 'other'
|
||||
};
|
||||
return prefixMap[this.modelType] || 'loras';
|
||||
},
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
||||
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
||||
import { moveManager } from '../../managers/MoveManager.js';
|
||||
import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js';
|
||||
|
||||
export class OtherContextMenu extends BaseContextMenu {
|
||||
constructor() {
|
||||
super('otherContextMenu', '.model-card');
|
||||
this.nsfwSelector = document.getElementById('nsfwLevelSelector');
|
||||
this.modelType = 'other';
|
||||
this.resetAndReload = resetAndReload;
|
||||
|
||||
this.initNSFWSelector();
|
||||
}
|
||||
|
||||
// Implementation needed by the mixin
|
||||
async saveModelMetadata(filePath, data) {
|
||||
return getModelApiClient().saveModelMetadata(filePath, data);
|
||||
}
|
||||
|
||||
showMenu(x, y, card) {
|
||||
super.showMenu(x, y, card);
|
||||
this.updateExcludeMenuItem();
|
||||
}
|
||||
|
||||
handleMenuAction(action) {
|
||||
// First try to handle with common actions
|
||||
if (ModelContextMenuMixin.handleCommonMenuActions.call(this, action)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiClient = getModelApiClient();
|
||||
|
||||
// Otherwise handle other-models-specific actions
|
||||
switch(action) {
|
||||
case 'details':
|
||||
// Show model details
|
||||
this.currentCard.click();
|
||||
break;
|
||||
case 'replace-preview':
|
||||
// Add new action for replacing preview images
|
||||
apiClient.replaceModelPreview(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'delete':
|
||||
showDeleteModal(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'copyname':
|
||||
// Copy model name
|
||||
if (this.currentCard.querySelector('.fa-copy')) {
|
||||
this.currentCard.querySelector('.fa-copy').click();
|
||||
}
|
||||
break;
|
||||
case 'refresh-metadata':
|
||||
// Refresh metadata from CivitAI
|
||||
apiClient.refreshSingleModelMetadata(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'move':
|
||||
moveManager.showMoveModal(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'exclude':
|
||||
showExcludeModal(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'restore':
|
||||
this.restoreExcludedModel(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mix in shared methods
|
||||
Object.assign(OtherContextMenu.prototype, ModelContextMenuMixin);
|
||||
@@ -2,6 +2,7 @@ export { LoraContextMenu } from './LoraContextMenu.js';
|
||||
export { RecipeContextMenu } from './RecipeContextMenu.js';
|
||||
export { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
||||
export { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
||||
export { OtherContextMenu } from './OtherContextMenu.js';
|
||||
export { GlobalContextMenu } from './GlobalContextMenu.js';
|
||||
export { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
||||
|
||||
@@ -9,6 +10,7 @@ import { LoraContextMenu } from './LoraContextMenu.js';
|
||||
import { RecipeContextMenu } from './RecipeContextMenu.js';
|
||||
import { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
||||
import { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
||||
import { OtherContextMenu } from './OtherContextMenu.js';
|
||||
import { GlobalContextMenu } from './GlobalContextMenu.js';
|
||||
|
||||
// Factory method to create page-specific context menu instances
|
||||
@@ -22,6 +24,8 @@ export function createPageContextMenu(pageType) {
|
||||
return new CheckpointContextMenu();
|
||||
case 'embeddings':
|
||||
return new EmbeddingContextMenu();
|
||||
case 'other':
|
||||
return new OtherContextMenu();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export class HeaderManager {
|
||||
if (path.includes('/loras/recipes')) return 'recipes';
|
||||
if (path.includes('/checkpoints')) return 'checkpoints';
|
||||
if (path.includes('/embeddings')) return 'embeddings';
|
||||
if (path.includes('/other')) return 'other';
|
||||
if (path.includes('/statistics')) return 'statistics';
|
||||
if (path.includes('/loras')) return 'loras';
|
||||
return 'unknown';
|
||||
|
||||
@@ -1126,6 +1126,7 @@ export class SidebarManager {
|
||||
recipes: 'Recipes',
|
||||
checkpoints: 'Checkpoints',
|
||||
embeddings: 'Embeddings',
|
||||
other: 'Other Models',
|
||||
};
|
||||
return names[this.pageType] || this.pageType;
|
||||
}
|
||||
@@ -1804,7 +1805,7 @@ export class SidebarManager {
|
||||
_migrateOldSettings() {
|
||||
if (getStorageItem('_sidebar_migration_done')) return;
|
||||
|
||||
const PAGES = ['loras', 'recipes', 'checkpoints', 'embeddings'];
|
||||
const PAGES = ['loras', 'recipes', 'checkpoints', 'embeddings', 'other'];
|
||||
|
||||
// 1. Migrate global hide setting to per-page
|
||||
if (state?.global?.settings?.show_folder_sidebar === false) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// OtherControls.js - Specific implementation for the Other Models page
|
||||
import { PageControls } from './PageControls.js';
|
||||
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
|
||||
/**
|
||||
* OtherControls class - Extends PageControls for the Other Models page
|
||||
* (VAE, upscalers, text encoders, CLIP vision, ControlNet, ...)
|
||||
*/
|
||||
export class OtherControls extends PageControls {
|
||||
constructor() {
|
||||
// Initialize with 'other' page type
|
||||
super('other');
|
||||
|
||||
// Register API methods specific to the Other Models page
|
||||
this.registerOtherAPI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Other-models-specific API methods
|
||||
*/
|
||||
registerOtherAPI() {
|
||||
const otherAPI = {
|
||||
// Core API functions
|
||||
loadMoreModels: async (resetPage = false, updateFolders = false) => {
|
||||
return await getModelApiClient().loadMoreWithVirtualScroll(resetPage, updateFolders);
|
||||
},
|
||||
|
||||
resetAndReload: async (updateFolders = false) => {
|
||||
return await resetAndReload(updateFolders);
|
||||
},
|
||||
|
||||
refreshModels: async (fullRebuild = false) => {
|
||||
return await getModelApiClient().refreshModels(fullRebuild);
|
||||
},
|
||||
|
||||
// Add fetch from Civitai functionality for other models
|
||||
fetchFromCivitai: async () => {
|
||||
return await getModelApiClient().fetchCivitaiMetadata();
|
||||
},
|
||||
|
||||
toggleBulkMode: () => {
|
||||
if (window.bulkManager) {
|
||||
window.bulkManager.toggleBulkMode();
|
||||
} else {
|
||||
console.error('Bulk manager not available');
|
||||
}
|
||||
},
|
||||
|
||||
// No clearCustomFilter implementation is needed for other models
|
||||
// as custom filters are currently only used for LoRAs
|
||||
clearCustomFilter: async () => {
|
||||
showToast('toast.filters.noCustomFilterToClear', {}, 'info');
|
||||
}
|
||||
};
|
||||
|
||||
// Register the API
|
||||
this.registerAPI(otherAPI);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,14 @@ import { PageControls } from './PageControls.js';
|
||||
import { LorasControls } from './LorasControls.js';
|
||||
import { CheckpointsControls } from './CheckpointsControls.js';
|
||||
import { EmbeddingsControls } from './EmbeddingsControls.js';
|
||||
import { OtherControls } from './OtherControls.js';
|
||||
|
||||
// Export the classes
|
||||
export { PageControls, LorasControls, CheckpointsControls, EmbeddingsControls };
|
||||
export { PageControls, LorasControls, CheckpointsControls, EmbeddingsControls, OtherControls };
|
||||
|
||||
/**
|
||||
* Factory function to create the appropriate controls based on page type
|
||||
* @param {string} pageType - The type of page ('loras', 'checkpoints', or 'embeddings')
|
||||
* @param {string} pageType - The type of page ('loras', 'checkpoints', 'embeddings', or 'other')
|
||||
* @returns {PageControls} - The appropriate controls instance
|
||||
*/
|
||||
export function createPageControls(pageType) {
|
||||
@@ -19,8 +20,10 @@ export function createPageControls(pageType) {
|
||||
return new CheckpointsControls();
|
||||
} else if (pageType === 'embeddings') {
|
||||
return new EmbeddingsControls();
|
||||
} else if (pageType === 'other') {
|
||||
return new OtherControls();
|
||||
} else {
|
||||
console.error(`Unknown page type: ${pageType}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class InitializationManager {
|
||||
this.pageType = 'recipes';
|
||||
} else if (path.includes('/checkpoints')) {
|
||||
this.pageType = 'checkpoints';
|
||||
} else if (path.includes('/other')) {
|
||||
this.pageType = 'other';
|
||||
} else if (path.includes('/loras')) {
|
||||
this.pageType = 'loras';
|
||||
} else if (path.includes('/embeddings')) {
|
||||
@@ -221,6 +223,7 @@ class InitializationManager {
|
||||
'lora': 'loras',
|
||||
'checkpoint': 'checkpoints',
|
||||
'embedding': 'embeddings',
|
||||
'other': 'other',
|
||||
'recipe': 'recipes'
|
||||
};
|
||||
|
||||
|
||||
@@ -250,6 +250,11 @@ function handleCopyAction(card, modelType) {
|
||||
const embeddingCode = folder ? `embedding:${folder}/${name}` : `embedding:${name}`;
|
||||
const message = translate('modelCard.actions.embeddingNameCopied', {}, 'Embedding syntax copied');
|
||||
copyToClipboard(embeddingCode, message);
|
||||
} else {
|
||||
// Other model types (VAE, upscalers, ...) - copy the file name
|
||||
const fileName = card.dataset.file_name;
|
||||
const message = translate('modelCard.actions.modelNameCopied', {}, 'Model name copied');
|
||||
copyToClipboard(fileName, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ export class AppCore {
|
||||
initializePageFeatures() {
|
||||
const pageType = this.getPageType();
|
||||
|
||||
if (['loras', 'recipes', 'checkpoints', 'embeddings'].includes(pageType)) {
|
||||
if (['loras', 'recipes', 'checkpoints', 'embeddings', 'other'].includes(pageType)) {
|
||||
this.initializeContextMenus(pageType);
|
||||
initializeInfiniteScroll(pageType);
|
||||
}
|
||||
|
||||
@@ -424,12 +424,13 @@ class BannerService {
|
||||
|
||||
/**
|
||||
* Get the current page type from the URL
|
||||
* @returns {string} Page type (loras, checkpoints, embeddings, recipes)
|
||||
* @returns {string} Page type (loras, checkpoints, embeddings, other, recipes)
|
||||
*/
|
||||
getCurrentPageType() {
|
||||
const path = window.location.pathname;
|
||||
if (path.includes('/checkpoints')) return 'checkpoints';
|
||||
if (path.includes('/embeddings')) return 'embeddings';
|
||||
if (path.includes('/other')) return 'other';
|
||||
if (path.includes('/recipes')) return 'recipes';
|
||||
return 'loras';
|
||||
}
|
||||
@@ -443,7 +444,8 @@ class BannerService {
|
||||
const endpoints = {
|
||||
'loras': '/api/lm/loras/reload?rebuild=true',
|
||||
'checkpoints': '/api/lm/checkpoints/reload?rebuild=true',
|
||||
'embeddings': '/api/lm/embeddings/reload?rebuild=true'
|
||||
'embeddings': '/api/lm/embeddings/reload?rebuild=true',
|
||||
'other': '/api/lm/other/reload?rebuild=true'
|
||||
};
|
||||
return endpoints[pageType] || endpoints['loras'];
|
||||
}
|
||||
|
||||
@@ -93,6 +93,20 @@ export class BulkManager {
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
},
|
||||
[MODEL_TYPES.OTHER]: {
|
||||
addTags: true,
|
||||
sendToWorkflow: false,
|
||||
copyAll: false,
|
||||
refreshAll: true,
|
||||
checkUpdates: true,
|
||||
moveAll: true,
|
||||
autoOrganize: true,
|
||||
deleteAll: true,
|
||||
setContentRating: true,
|
||||
skipMetadataRefresh: true,
|
||||
setFavorite: true,
|
||||
unfavorite: true
|
||||
},
|
||||
recipes: {
|
||||
addTags: true,
|
||||
sendToWorkflow: false,
|
||||
|
||||
@@ -805,7 +805,7 @@ export class FilterManager {
|
||||
// Call the appropriate manager's load method based on page type
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
await window.recipeManager.loadRecipes(true);
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints' || this.currentPage === 'other') {
|
||||
// For models page, reset the page and reload
|
||||
await getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||
}
|
||||
@@ -904,7 +904,7 @@ export class FilterManager {
|
||||
// Reload data using the appropriate method for the current page
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
await window.recipeManager.loadRecipes(true);
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings' || this.currentPage === 'other') {
|
||||
await getModelApiClient().loadMoreWithVirtualScroll(true, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ class MoveManager {
|
||||
this.bulkFilePaths = null;
|
||||
|
||||
const apiClient = this._getApiClient(modelType);
|
||||
const currentPageType = state.currentPageType;
|
||||
const modelConfig = apiClient.apiConfig.config;
|
||||
|
||||
// Handle bulk mode
|
||||
@@ -113,7 +112,7 @@ class MoveManager {
|
||||
).join('');
|
||||
|
||||
// Set default root if available
|
||||
const settingsKey = `default_${currentPageType.slice(0, -1)}_root`;
|
||||
const settingsKey = `default_${modelConfig.singularName}_root`;
|
||||
const defaultRoot = state.global.settings[settingsKey];
|
||||
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
||||
modelRootSelect.value = defaultRoot;
|
||||
@@ -228,13 +227,12 @@ class MoveManager {
|
||||
if (modelRoot) {
|
||||
if (this.useDefaultPath) {
|
||||
// Show actual template path
|
||||
try {
|
||||
const singularType = apiClient.modelType.replace(/s$/, '');
|
||||
const templates = state.global.settings.download_path_templates;
|
||||
const template = templates[singularType];
|
||||
const singularType = config.singularName || apiClient.modelType.replace(/s$/, '');
|
||||
const templates = state.global.settings.download_path_templates;
|
||||
const template = templates[singularType];
|
||||
if (template) {
|
||||
fullPath += `/${template}`;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch template:', error);
|
||||
} else {
|
||||
fullPath += '/' + translate('modals.download.autoOrganizedPath');
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -298,7 +298,7 @@ export class SearchManager {
|
||||
pageState.searchOptions.loraName = options.loraName || false;
|
||||
pageState.searchOptions.loraModel = options.loraModel || false;
|
||||
pageState.searchOptions.prompt = options.prompt || false;
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings') {
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'checkpoints' || this.currentPage === 'embeddings' || this.currentPage === 'other') {
|
||||
// Update only the relevant fields in searchOptions instead of replacing the whole object
|
||||
pageState.searchOptions.filename = options.filename || false;
|
||||
pageState.searchOptions.modelname = options.modelname || false;
|
||||
@@ -311,7 +311,7 @@ export class SearchManager {
|
||||
// Call the appropriate manager's load method based on page type
|
||||
if (this.currentPage === 'recipes' && window.recipeManager) {
|
||||
window.recipeManager.loadRecipes(true);
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints') {
|
||||
} else if (this.currentPage === 'loras' || this.currentPage === 'embeddings' || this.currentPage === 'checkpoints' || this.currentPage === 'other') {
|
||||
// For models page, reset the page and reload
|
||||
getModelApiClient().loadMoreWithVirtualScroll(true, false);
|
||||
}
|
||||
|
||||
@@ -3360,6 +3360,9 @@ export class SettingsManager {
|
||||
} else if (this.currentPage === 'embeddings') {
|
||||
// Reload the embeddings without updating folders
|
||||
await resetAndReload(false);
|
||||
} else if (this.currentPage === 'other') {
|
||||
// Reload the other models without updating folders
|
||||
await resetAndReload(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { appCore } from './core.js';
|
||||
import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } from './utils/modalUtils.js';
|
||||
import { createPageControls } from './components/controls/index.js';
|
||||
import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js';
|
||||
import { MODEL_TYPES } from './api/apiConfig.js';
|
||||
import { initActiveFiltersSync } from './utils/activeFiltersSync.js';
|
||||
|
||||
// Initialize the Other Models page
|
||||
class OtherPageManager {
|
||||
constructor() {
|
||||
// Initialize page controls
|
||||
this.pageControls = createPageControls(MODEL_TYPES.OTHER);
|
||||
|
||||
// Initialize the ModelDuplicatesManager
|
||||
this.duplicatesManager = new ModelDuplicatesManager(this, MODEL_TYPES.OTHER);
|
||||
|
||||
// Expose only necessary functions to global scope
|
||||
this._exposeRequiredGlobalFunctions();
|
||||
}
|
||||
|
||||
_exposeRequiredGlobalFunctions() {
|
||||
// Minimal set of functions that need to remain global
|
||||
window.confirmDelete = confirmDelete;
|
||||
window.closeDeleteModal = closeDeleteModal;
|
||||
window.confirmExclude = confirmExclude;
|
||||
window.closeExcludeModal = closeExcludeModal;
|
||||
|
||||
// Expose duplicates manager
|
||||
window.modelDuplicatesManager = this.duplicatesManager;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
// Initialize common page features (including context menus)
|
||||
appCore.initializePageFeatures();
|
||||
|
||||
// Mirror active filters to the backend for the ComfyUI-side autocomplete
|
||||
initActiveFiltersSync(MODEL_TYPES.OTHER);
|
||||
|
||||
console.log('Other Models Manager initialized');
|
||||
}
|
||||
}
|
||||
|
||||
async function initializeOtherPage() {
|
||||
// Initialize core application
|
||||
await appCore.initialize();
|
||||
|
||||
// Initialize other models page
|
||||
const otherPage = new OtherPageManager();
|
||||
await otherPage.initialize();
|
||||
|
||||
return otherPage;
|
||||
}
|
||||
|
||||
// Initialize everything when DOM is ready
|
||||
document.addEventListener('DOMContentLoaded', initializeOtherPage);
|
||||
|
||||
export { OtherPageManager, initializeOtherPage };
|
||||
@@ -79,6 +79,7 @@ export function createDefaultSettings() {
|
||||
const loraPreviewVersions = getMapFromStorage('loras_preview_versions');
|
||||
const checkpointPreviewVersions = getMapFromStorage('checkpoints_preview_versions');
|
||||
const embeddingPreviewVersions = getMapFromStorage('embeddings_preview_versions');
|
||||
const otherPreviewVersions = getMapFromStorage('other_preview_versions');
|
||||
|
||||
export const state = {
|
||||
// Global state
|
||||
@@ -234,6 +235,44 @@ export const state = {
|
||||
search: '',
|
||||
},
|
||||
activeViewSnapshot: null,
|
||||
},
|
||||
|
||||
[MODEL_TYPES.OTHER]: {
|
||||
currentPage: 1,
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
sortBy: 'name',
|
||||
activeFolder: getStorageItem(`${MODEL_TYPES.OTHER}_activeFolder`),
|
||||
previewVersions: otherPreviewVersions,
|
||||
searchManager: null,
|
||||
searchOptions: {
|
||||
filename: true,
|
||||
modelname: true,
|
||||
tags: false,
|
||||
creator: false,
|
||||
hash: false,
|
||||
recursive: getStorageItem(`${MODEL_TYPES.OTHER}_recursiveSearch`, true),
|
||||
},
|
||||
filters: {
|
||||
baseModel: [],
|
||||
tags: {},
|
||||
license: {},
|
||||
modelTypes: [],
|
||||
search: '',
|
||||
tagLogic: 'any',
|
||||
},
|
||||
bulkMode: false,
|
||||
selectedModels: new Set(),
|
||||
metadataCache: new Map(),
|
||||
showFavoritesOnly: false,
|
||||
showUpdateAvailableOnly: false,
|
||||
duplicatesMode: false,
|
||||
viewMode: 'active',
|
||||
excludedViewState: {
|
||||
sortBy: 'name:asc',
|
||||
search: '',
|
||||
},
|
||||
activeViewSnapshot: null,
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export function syncActiveFilters(pageType) {
|
||||
* Register the storage listener and push the current (restored) state once.
|
||||
* The initial push covers server restarts, where the backend store is empty
|
||||
* until the manager page re-publishes its localStorage-restored filters.
|
||||
* @param {string} pageType - 'loras' | 'checkpoints' | 'embeddings'
|
||||
* @param {string} pageType - 'loras' | 'checkpoints' | 'embeddings' | 'other'
|
||||
*/
|
||||
export function initActiveFiltersSync(pageType) {
|
||||
setActiveFiltersListener((changedPageType) => syncActiveFilters(changedPageType));
|
||||
|
||||
@@ -106,6 +106,12 @@ export const MODEL_SUBTYPE_DISPLAY_NAMES = {
|
||||
diffusion_model: "Diffusion Model",
|
||||
// Embedding sub-types
|
||||
embedding: "Embedding",
|
||||
// Other model sub-types
|
||||
vae: "VAE",
|
||||
upscaler: "Upscaler",
|
||||
text_encoder: "Text Encoder",
|
||||
clip_vision: "CLIP Vision",
|
||||
controlnet: "ControlNet",
|
||||
};
|
||||
|
||||
// Backward compatibility alias
|
||||
@@ -119,6 +125,11 @@ export const MODEL_SUBTYPE_ABBREVIATIONS = {
|
||||
checkpoint: "CKPT",
|
||||
diffusion_model: "DM",
|
||||
embedding: "EMB",
|
||||
vae: "VAE",
|
||||
upscaler: "UPS",
|
||||
text_encoder: "TE",
|
||||
clip_vision: "CV",
|
||||
controlnet: "CN",
|
||||
};
|
||||
|
||||
export function getSubTypeAbbreviation(subType) {
|
||||
|
||||
@@ -66,7 +66,7 @@ async function getCardCreator(pageType) {
|
||||
|
||||
// Function to get the appropriate data fetcher based on page type
|
||||
async function getDataFetcher(pageType) {
|
||||
if (pageType === 'loras' || pageType === 'embeddings' || pageType === 'checkpoints') {
|
||||
if (pageType === 'loras' || pageType === 'embeddings' || pageType === 'checkpoints' || pageType === 'other') {
|
||||
return (page = 1, pageSize = 100) => getModelApiClient().fetchModelsPage(page, pageSize);
|
||||
} else if (pageType === 'recipes') {
|
||||
// Import the recipeApi module and use the fetchRecipesPage function
|
||||
|
||||
@@ -8,7 +8,7 @@ const STORAGE_PREFIX = 'lora_manager_';
|
||||
|
||||
// Matches keys that carry the manager page's active filter state
|
||||
// (e.g. 'loras_activeFolder', 'checkpoints_filters').
|
||||
const ACTIVE_FILTER_KEY_PATTERN = /^(loras|checkpoints|embeddings)_(activeFolder|recursiveSearch|filters)$/;
|
||||
const ACTIVE_FILTER_KEY_PATTERN = /^(loras|checkpoints|embeddings|other)_(activeFolder|recursiveSearch|filters)$/;
|
||||
|
||||
let activeFiltersListener = null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user