diff --git a/static/js/api/loraApi.js b/static/js/api/loraApi.js
index 86278c21..781739ea 100644
--- a/static/js/api/loraApi.js
+++ b/static/js/api/loraApi.js
@@ -1,4 +1,4 @@
-import { state } from '../state/index.js';
+import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
import { createLoraCard } from '../components/LoraCard.js';
import { initializeInfiniteScroll } from '../utils/infiniteScroll.js';
@@ -6,21 +6,23 @@ import { showDeleteModal } from '../utils/modalUtils.js';
import { toggleFolder } from '../utils/uiHelpers.js';
export async function loadMoreLoras(boolUpdateFolders = false) {
- if (state.isLoading || !state.hasMore) return;
+ const pageState = getCurrentPageState();
- state.isLoading = true;
+ if (pageState.isLoading || !pageState.hasMore) return;
+
+ pageState.isLoading = true;
try {
const params = new URLSearchParams({
- page: state.currentPage,
+ page: pageState.currentPage,
page_size: 20,
- sort_by: state.sortBy
+ sort_by: pageState.sortBy
});
- // 使用 state 中的 searchManager 获取递归搜索状态
- const isRecursiveSearch = state.searchManager?.isRecursiveSearch ?? false;
+ // Use pageState instead of state
+ const isRecursiveSearch = pageState.searchManager?.isRecursiveSearch ?? false;
- if (state.activeFolder !== null) {
- params.append('folder', state.activeFolder);
+ if (pageState.activeFolder !== null) {
+ params.append('folder', pageState.activeFolder);
params.append('recursive', isRecursiveSearch.toString());
}
@@ -32,14 +34,14 @@ export async function loadMoreLoras(boolUpdateFolders = false) {
}
// Add filter parameters if active
- if (state.filters) {
- if (state.filters.tags && state.filters.tags.length > 0) {
+ if (pageState.filters) {
+ if (pageState.filters.tags && pageState.filters.tags.length > 0) {
// Convert the array of tags to a comma-separated string
- params.append('tags', state.filters.tags.join(','));
+ params.append('tags', pageState.filters.tags.join(','));
}
- if (state.filters.baseModel && state.filters.baseModel.length > 0) {
+ if (pageState.filters.baseModel && pageState.filters.baseModel.length > 0) {
// Convert the array of base models to a comma-separated string
- params.append('base_models', state.filters.baseModel.join(','));
+ params.append('base_models', pageState.filters.baseModel.join(','));
}
}
@@ -53,13 +55,13 @@ export async function loadMoreLoras(boolUpdateFolders = false) {
const data = await response.json();
console.log('Received data:', data);
- if (data.items.length === 0 && state.currentPage === 1) {
+ if (data.items.length === 0 && pageState.currentPage === 1) {
const grid = document.getElementById('loraGrid');
grid.innerHTML = '
No loras found in this folder
';
- state.hasMore = false;
+ pageState.hasMore = false;
} else if (data.items.length > 0) {
- state.hasMore = state.currentPage < data.total_pages;
- state.currentPage++;
+ pageState.hasMore = pageState.currentPage < data.total_pages;
+ pageState.currentPage++;
appendLoraCards(data.items);
const sentinel = document.getElementById('scroll-sentinel');
@@ -67,7 +69,7 @@ export async function loadMoreLoras(boolUpdateFolders = false) {
state.observer.observe(sentinel);
}
} else {
- state.hasMore = false;
+ pageState.hasMore = false;
}
if (boolUpdateFolders && data.folders) {
@@ -78,7 +80,7 @@ export async function loadMoreLoras(boolUpdateFolders = false) {
console.error('Error loading loras:', error);
showToast('Failed to load loras: ' + error.message, 'error');
} finally {
- state.isLoading = false;
+ pageState.isLoading = false;
}
}
@@ -87,7 +89,8 @@ function updateFolderTags(folders) {
if (!folderTagsContainer) return;
// Keep track of currently selected folder
- const currentFolder = state.activeFolder;
+ const pageState = getCurrentPageState();
+ const currentFolder = pageState.activeFolder;
// Create HTML for folder tags
const tagsHTML = folders.map(folder => {
@@ -269,11 +272,12 @@ export function appendLoraCards(loras) {
}
export async function resetAndReload(boolUpdateFolders = false) {
- console.log('Resetting with state:', { ...state });
+ const pageState = getCurrentPageState();
+ console.log('Resetting with state:', { ...pageState });
- state.currentPage = 1;
- state.hasMore = true;
- state.isLoading = false;
+ pageState.currentPage = 1;
+ pageState.hasMore = true;
+ pageState.isLoading = false;
const grid = document.getElementById('loraGrid');
grid.innerHTML = '';
diff --git a/static/js/core.js b/static/js/core.js
index a81ef9de..78abffb9 100644
--- a/static/js/core.js
+++ b/static/js/core.js
@@ -1,5 +1,5 @@
// Core application functionality
-import { state, initSettings } from './state/index.js';
+import { state } from './state/index.js';
import { LoadingManager } from './managers/LoadingManager.js';
import { modalManager } from './managers/ModalManager.js';
import { updateService } from './managers/UpdateService.js';
@@ -18,9 +18,6 @@ export class AppCore {
async initialize() {
if (this.initialized) return;
- // Initialize settings
- initSettings();
-
// Initialize managers
state.loadingManager = new LoadingManager();
modalManager.initialize();
@@ -80,4 +77,4 @@ export class AppCore {
export const appCore = new AppCore();
// Export common utilities for global use
-export { showToast, modalManager, state, lazyLoadImages, initializeInfiniteScroll };
\ No newline at end of file
+export { showToast, lazyLoadImages, initializeInfiniteScroll };
\ No newline at end of file
diff --git a/static/js/loras.js b/static/js/loras.js
index 1a7380db..b4c0bc74 100644
--- a/static/js/loras.js
+++ b/static/js/loras.js
@@ -1,23 +1,21 @@
-import { appCore, state } from './core.js';
+import { appCore } from './core.js';
+import { state, initPageState } from './state/index.js';
import { showLoraModal, toggleShowcase, scrollToTop } from './components/LoraModal.js';
import { loadMoreLoras, fetchCivitai, deleteModel, replacePreview, resetAndReload, refreshLoras } from './api/loraApi.js';
import {
- lazyLoadImages,
restoreFolderFilter,
toggleFolder,
copyTriggerWord,
openCivitai,
toggleFolderTags,
initFolderTagsVisibility,
- updatePanelPositions
} from './utils/uiHelpers.js';
-import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
-import { showDeleteModal, confirmDelete, closeDeleteModal } from './utils/modalUtils.js';
+import { confirmDelete, closeDeleteModal } from './utils/modalUtils.js';
import { DownloadManager } from './managers/DownloadManager.js';
import { toggleApiKeyVisibility } from './managers/SettingsManager.js';
import { LoraContextMenu } from './components/ContextMenu.js';
import { moveManager } from './managers/MoveManager.js';
-import { createLoraCard, updateCardsForBulkMode } from './components/LoraCard.js';
+import { updateCardsForBulkMode } from './components/LoraCard.js';
import { bulkManager } from './managers/BulkManager.js';
// Initialize the LoRA page
@@ -99,6 +97,9 @@ function initializeEventListeners() {
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', async () => {
+ // Initialize page state
+ initPageState('loras');
+
// Initialize core application
await appCore.initialize();
diff --git a/static/js/managers/BulkManager.js b/static/js/managers/BulkManager.js
index b510efe2..76fa5043 100644
--- a/static/js/managers/BulkManager.js
+++ b/static/js/managers/BulkManager.js
@@ -91,16 +91,17 @@ export class BulkManager {
// Set text content without the icon
countElement.textContent = `${state.selectedLoras.size} selected `;
- // Re-add the caret icon with proper direction
- const caretIcon = document.createElement('i');
- // Use down arrow if strip is visible, up arrow if not
- caretIcon.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
- caretIcon.style.visibility = state.selectedLoras.size > 0 ? 'visible' : 'hidden';
- countElement.appendChild(caretIcon);
-
- // If there are no selections, hide the thumbnail strip
- if (state.selectedLoras.size === 0) {
- this.hideThumbnailStrip();
+ // Update caret icon if it exists
+ const existingCaret = countElement.querySelector('.dropdown-caret');
+ if (existingCaret) {
+ existingCaret.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
+ existingCaret.style.visibility = state.selectedLoras.size > 0 ? 'visible' : 'hidden';
+ } else {
+ // Create new caret icon if it doesn't exist
+ const caretIcon = document.createElement('i');
+ caretIcon.className = `fas fa-caret-${this.isStripVisible ? 'down' : 'up'} dropdown-caret`;
+ caretIcon.style.visibility = state.selectedLoras.size > 0 ? 'visible' : 'hidden';
+ countElement.appendChild(caretIcon);
}
}
}
@@ -252,12 +253,20 @@ export class BulkManager {
hideThumbnailStrip() {
const strip = document.querySelector('.selected-thumbnails-strip');
- if (strip) {
+ if (strip && this.isStripVisible) { // Only hide if actually visible
strip.classList.remove('visible');
- // Update strip visibility state and caret direction
+ // Update strip visibility state
this.isStripVisible = false;
- this.updateSelectedCount(); // Update caret
+
+ // Update caret without triggering another hide
+ const countElement = document.getElementById('selectedCount');
+ if (countElement) {
+ const caret = countElement.querySelector('.dropdown-caret');
+ if (caret) {
+ caret.className = 'fas fa-caret-up dropdown-caret';
+ }
+ }
// Wait for animation to complete before removing
setTimeout(() => {
@@ -340,4 +349,4 @@ export class BulkManager {
}
// Create a singleton instance
-export const bulkManager = new BulkManager();
\ No newline at end of file
+export const bulkManager = new BulkManager();
diff --git a/static/js/managers/FilterManager.js b/static/js/managers/FilterManager.js
index 36239927..e691da24 100644
--- a/static/js/managers/FilterManager.js
+++ b/static/js/managers/FilterManager.js
@@ -1,11 +1,12 @@
import { BASE_MODELS, BASE_MODEL_CLASSES } from '../utils/constants.js';
-import { state } from '../state/index.js';
+import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
import { resetAndReload } from '../api/loraApi.js';
export class FilterManager {
constructor() {
- this.filters = {
+ const pageState = getCurrentPageState();
+ this.filters = pageState.filters || {
baseModel: [],
tags: []
};
@@ -219,7 +220,8 @@ export class FilterManager {
localStorage.setItem('loraFilters', JSON.stringify(this.filters));
// Update state with current filters
- state.filters = { ...this.filters };
+ const pageState = getCurrentPageState();
+ pageState.filters = { ...this.filters };
// Reload loras with filters applied
await resetAndReload();
@@ -258,7 +260,8 @@ export class FilterManager {
};
// Update state
- state.filters = { ...this.filters };
+ const pageState = getCurrentPageState();
+ pageState.filters = { ...this.filters };
// Update UI
this.updateTagSelections();
diff --git a/static/js/managers/LoraSearchManager.js b/static/js/managers/LoraSearchManager.js
index c350416f..bae212b3 100644
--- a/static/js/managers/LoraSearchManager.js
+++ b/static/js/managers/LoraSearchManager.js
@@ -5,7 +5,7 @@
import { SearchManager } from './SearchManager.js';
import { appendLoraCards } from '../api/loraApi.js';
import { resetAndReload } from '../api/loraApi.js';
-import { state } from '../state/index.js';
+import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
export class LoraSearchManager extends SearchManager {
@@ -19,12 +19,14 @@ export class LoraSearchManager extends SearchManager {
// Store this instance in the state
if (state) {
- state.searchManager = this;
+ const pageState = getCurrentPageState();
+ pageState.searchManager = this;
}
}
async performSearch() {
const searchTerm = this.searchInput.value.trim().toLowerCase();
+ const pageState = getCurrentPageState();
// Log the search attempt for debugging
console.log('LoraSearchManager performSearch called with:', searchTerm);
@@ -42,8 +44,8 @@ export class LoraSearchManager extends SearchManager {
}
if (!searchTerm) {
- if (state) {
- state.currentPage = 1;
+ if (pageState) {
+ pageState.currentPage = 1;
}
await resetAndReload();
return;
@@ -58,15 +60,15 @@ export class LoraSearchManager extends SearchManager {
// Store current scroll position
const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;
- if (state) {
- state.currentPage = 1;
- state.hasMore = true;
+ if (pageState) {
+ pageState.currentPage = 1;
+ pageState.hasMore = true;
}
const url = new URL('/api/loras', window.location.origin);
url.searchParams.set('page', '1');
url.searchParams.set('page_size', '20');
- url.searchParams.set('sort_by', state ? state.sortBy : 'name');
+ url.searchParams.set('sort_by', pageState ? pageState.sortBy : 'name');
url.searchParams.set('search', searchTerm);
url.searchParams.set('fuzzy', 'true');
@@ -80,8 +82,8 @@ export class LoraSearchManager extends SearchManager {
url.searchParams.set('search_tags', searchOptions.tags ? 'true' : 'false');
// Always send folder parameter if there is an active folder
- if (state && state.activeFolder) {
- url.searchParams.set('folder', state.activeFolder);
+ if (pageState && pageState.activeFolder) {
+ url.searchParams.set('folder', pageState.activeFolder);
// Add recursive parameter when recursive search is enabled
const recursive = this.recursiveSearchToggle ? this.recursiveSearchToggle.checked : false;
url.searchParams.set('recursive', recursive.toString());
@@ -102,14 +104,14 @@ export class LoraSearchManager extends SearchManager {
if (data.items.length === 0) {
grid.innerHTML = 'No matching loras found
';
- if (state) {
- state.hasMore = false;
+ if (pageState) {
+ pageState.hasMore = false;
}
} else {
appendLoraCards(data.items);
- if (state) {
- state.hasMore = state.currentPage < data.total_pages;
- state.currentPage++;
+ if (pageState) {
+ pageState.hasMore = pageState.currentPage < data.total_pages;
+ pageState.currentPage++;
}
}
diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js
index 2afea5a0..da485760 100644
--- a/static/js/managers/SettingsManager.js
+++ b/static/js/managers/SettingsManager.js
@@ -1,6 +1,6 @@
import { modalManager } from './ModalManager.js';
import { showToast } from '../utils/uiHelpers.js';
-import { state, saveSettings } from '../state/index.js';
+import { state } from '../state/index.js';
import { resetAndReload } from '../api/loraApi.js';
export class SettingsManager {
@@ -41,13 +41,13 @@ export class SettingsManager {
// Set frontend settings from state
const blurMatureContentCheckbox = document.getElementById('blurMatureContent');
if (blurMatureContentCheckbox) {
- blurMatureContentCheckbox.checked = state.settings.blurMatureContent;
+ blurMatureContentCheckbox.checked = state.global.settings.blurMatureContent;
}
const showOnlySFWCheckbox = document.getElementById('showOnlySFW');
if (showOnlySFWCheckbox) {
// Sync with state (backend will set this via template)
- state.settings.show_only_sfw = showOnlySFWCheckbox.checked;
+ state.global.settings.show_only_sfw = showOnlySFWCheckbox.checked;
}
// Backend settings are loaded from the template directly
@@ -71,9 +71,11 @@ export class SettingsManager {
const showOnlySFW = document.getElementById('showOnlySFW').checked;
// Update frontend state and save to localStorage
- state.settings.blurMatureContent = blurMatureContent;
- state.settings.show_only_sfw = showOnlySFW;
- saveSettings();
+ state.global.settings.blurMatureContent = blurMatureContent;
+ state.global.settings.show_only_sfw = showOnlySFW;
+
+ // Save settings to localStorage
+ localStorage.setItem('settings', JSON.stringify(state.global.settings));
try {
// Save backend settings via API
@@ -107,7 +109,7 @@ export class SettingsManager {
applyFrontendSettings() {
// Apply blur setting to existing content
- const blurSetting = state.settings.blurMatureContent;
+ const blurSetting = state.global.settings.blurMatureContent;
document.querySelectorAll('.lora-card[data-nsfw="true"] .card-image').forEach(img => {
if (blurSetting) {
img.classList.add('nsfw-blur');
diff --git a/static/js/state/index.js b/static/js/state/index.js
index cde287b1..1a54a7d0 100644
--- a/static/js/state/index.js
+++ b/static/js/state/index.js
@@ -1,53 +1,148 @@
+// Create the new hierarchical state structure
export const state = {
- currentPage: 1,
- isLoading: false,
- hasMore: true,
- sortBy: 'name',
- activeFolder: null,
- loadingManager: null,
- observer: null,
- previewVersions: new Map(),
- searchManager: null,
- searchOptions: {
- filename: true,
- modelname: true,
- tags: false,
- recursive: false
+ // Global state
+ global: {
+ settings: {
+ blurMatureContent: true,
+ show_only_sfw: false
+ },
+ loadingManager: null,
+ observer: null,
},
- filters: {
- baseModel: [],
- tags: []
+
+ // Page-specific states
+ pages: {
+ loras: {
+ currentPage: 1,
+ isLoading: false,
+ hasMore: true,
+ sortBy: 'name',
+ activeFolder: null,
+ previewVersions: new Map(),
+ searchManager: null,
+ searchOptions: {
+ filename: true,
+ modelname: true,
+ tags: false,
+ recursive: false
+ },
+ filters: {
+ baseModel: [],
+ tags: []
+ },
+ bulkMode: false,
+ selectedLoras: new Set(),
+ loraMetadataCache: new Map(),
+ },
+
+ recipes: {
+ currentPage: 1,
+ isLoading: false,
+ hasMore: true,
+ sortBy: 'date',
+ searchManager: null,
+ searchOptions: {
+ filename: true,
+ modelname: true,
+ tags: true,
+ loras: true,
+ recursive: false
+ },
+ filters: {
+ baseModel: [],
+ tags: []
+ }
+ },
+
+ checkpoints: {
+ currentPage: 1,
+ isLoading: false,
+ hasMore: true,
+ sortBy: 'name',
+ activeFolder: null,
+ searchManager: null,
+ searchOptions: {
+ filename: true,
+ modelname: true,
+ recursive: false
+ },
+ filters: {
+ baseModel: [],
+ tags: []
+ }
+ }
},
- bulkMode: false,
- selectedLoras: new Set(),
- loraMetadataCache: new Map(),
- settings: {
- blurMatureContent: true,
- show_only_sfw: false
- }
+
+ // Current active page
+ currentPageType: 'loras',
+
+ // Backward compatibility - proxy properties
+ get currentPage() { return this.pages[this.currentPageType].currentPage; },
+ set currentPage(value) { this.pages[this.currentPageType].currentPage = value; },
+
+ get isLoading() { return this.pages[this.currentPageType].isLoading; },
+ set isLoading(value) { this.pages[this.currentPageType].isLoading = value; },
+
+ get hasMore() { return this.pages[this.currentPageType].hasMore; },
+ set hasMore(value) { this.pages[this.currentPageType].hasMore = value; },
+
+ get sortBy() { return this.pages[this.currentPageType].sortBy; },
+ set sortBy(value) { this.pages[this.currentPageType].sortBy = value; },
+
+ get activeFolder() { return this.pages[this.currentPageType].activeFolder; },
+ set activeFolder(value) { this.pages[this.currentPageType].activeFolder = value; },
+
+ get loadingManager() { return this.global.loadingManager; },
+ set loadingManager(value) { this.global.loadingManager = value; },
+
+ get observer() { return this.global.observer; },
+ set observer(value) { this.global.observer = value; },
+
+ get previewVersions() { return this.pages.loras.previewVersions; },
+ set previewVersions(value) { this.pages.loras.previewVersions = value; },
+
+ get searchManager() { return this.pages[this.currentPageType].searchManager; },
+ set searchManager(value) { this.pages[this.currentPageType].searchManager = value; },
+
+ get searchOptions() { return this.pages[this.currentPageType].searchOptions; },
+ set searchOptions(value) { this.pages[this.currentPageType].searchOptions = value; },
+
+ get filters() { return this.pages[this.currentPageType].filters; },
+ set filters(value) { this.pages[this.currentPageType].filters = value; },
+
+ get bulkMode() { return this.pages.loras.bulkMode; },
+ set bulkMode(value) { this.pages.loras.bulkMode = value; },
+
+ get selectedLoras() { return this.pages.loras.selectedLoras; },
+ set selectedLoras(value) { this.pages.loras.selectedLoras = value; },
+
+ get loraMetadataCache() { return this.pages.loras.loraMetadataCache; },
+ set loraMetadataCache(value) { this.pages.loras.loraMetadataCache = value; },
+
+ get settings() { return this.global.settings; },
+ set settings(value) { this.global.settings = value; }
};
-// Initialize settings from localStorage if available
-export function initSettings() {
- try {
- const savedSettings = localStorage.getItem('loraManagerSettings');
- if (savedSettings) {
- const parsedSettings = JSON.parse(savedSettings);
- state.settings = { ...state.settings, ...parsedSettings };
- }
- } catch (error) {
- console.error('Error loading settings from localStorage:', error);
- }
+// Get the current page state
+export function getCurrentPageState() {
+ return state.pages[state.currentPageType];
}
-// Save settings to localStorage
-export function saveSettings() {
- try {
- localStorage.setItem('loraManagerSettings', JSON.stringify(state.settings));
- } catch (error) {
- console.error('Error saving settings to localStorage:', error);
+// Set the current page type
+export function setCurrentPageType(pageType) {
+ if (state.pages[pageType]) {
+ state.currentPageType = pageType;
+ return true;
}
+ console.warn(`Unknown page type: ${pageType}`);
+ return false;
}
-// Initialize settings on load
-initSettings();
\ No newline at end of file
+// Initialize page state when a page loads
+export function initPageState(pageType) {
+ if (setCurrentPageType(pageType)) {
+ console.log(`Initialized state for page: ${pageType}`);
+ return getCurrentPageState();
+ }
+ return null;
+}
\ No newline at end of file
diff --git a/static/js/utils/infiniteScroll.js b/static/js/utils/infiniteScroll.js
index a9360b96..5e95e5a8 100644
--- a/static/js/utils/infiniteScroll.js
+++ b/static/js/utils/infiniteScroll.js
@@ -1,4 +1,4 @@
-import { state } from '../state/index.js';
+import { state, getCurrentPageState } from '../state/index.js';
import { loadMoreLoras } from '../api/loraApi.js';
import { debounce } from './debounce.js';
@@ -7,6 +7,12 @@ export function initializeInfiniteScroll(pageType = 'loras') {
state.observer.disconnect();
}
+ // Set the current page type
+ state.currentPageType = pageType;
+
+ // Get the current page state
+ const pageState = getCurrentPageState();
+
// Determine the load more function and grid ID based on page type
let loadMoreFunction;
let gridId;
@@ -32,7 +38,7 @@ export function initializeInfiniteScroll(pageType = 'loras') {
state.observer = new IntersectionObserver(
(entries) => {
const target = entries[0];
- if (target.isIntersecting && !state.isLoading && state.hasMore) {
+ if (target.isIntersecting && !pageState.isLoading && pageState.hasMore) {
debouncedLoadMore();
}
},