mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: Enhance initialization component with progress tracking and UI improvements
This commit is contained in:
@@ -62,11 +62,16 @@ class ModelScanner:
|
||||
# Set initializing flag to true
|
||||
self._is_initializing = True
|
||||
|
||||
# Determine the page type based on model type
|
||||
page_type = 'loras' if self.model_type == 'lora' else 'checkpoints'
|
||||
|
||||
# First, count all model files to track progress
|
||||
await ws_manager.broadcast_init_progress({
|
||||
'stage': 'scan_folders',
|
||||
'progress': 0,
|
||||
'details': f"Scanning {self.model_type} folders..."
|
||||
'details': f"Scanning {self.model_type} folders...",
|
||||
'scanner_type': self.model_type,
|
||||
'pageType': page_type
|
||||
})
|
||||
|
||||
# Count files in a separate thread to avoid blocking
|
||||
@@ -79,7 +84,9 @@ class ModelScanner:
|
||||
await ws_manager.broadcast_init_progress({
|
||||
'stage': 'count_models',
|
||||
'progress': 1, # Changed from 10 to 1
|
||||
'details': f"Found {total_files} {self.model_type} files"
|
||||
'details': f"Found {total_files} {self.model_type} files",
|
||||
'scanner_type': self.model_type,
|
||||
'pageType': page_type
|
||||
})
|
||||
|
||||
start_time = time.time()
|
||||
@@ -88,14 +95,17 @@ class ModelScanner:
|
||||
await loop.run_in_executor(
|
||||
None, # Use default thread pool
|
||||
self._initialize_cache_sync, # Run synchronous version in thread
|
||||
total_files # Pass the total file count for progress reporting
|
||||
total_files, # Pass the total file count for progress reporting
|
||||
page_type # Pass the page type for progress reporting
|
||||
)
|
||||
|
||||
# Send final progress update
|
||||
await ws_manager.broadcast_init_progress({
|
||||
'stage': 'finalizing',
|
||||
'progress': 99, # Changed from 95 to 99
|
||||
'details': f"Finalizing {self.model_type} cache..."
|
||||
'details': f"Finalizing {self.model_type} cache...",
|
||||
'scanner_type': self.model_type,
|
||||
'pageType': page_type
|
||||
})
|
||||
|
||||
logger.info(f"{self.model_type.capitalize()} cache initialized in {time.time() - start_time:.2f} seconds. Found {len(self._cache.raw_data)} models")
|
||||
@@ -106,7 +116,9 @@ class ModelScanner:
|
||||
'stage': 'finalizing',
|
||||
'progress': 100,
|
||||
'status': 'complete',
|
||||
'details': f"Completed! Found {len(self._cache.raw_data)} {self.model_type} files."
|
||||
'details': f"Completed! Found {len(self._cache.raw_data)} {self.model_type} files.",
|
||||
'scanner_type': self.model_type,
|
||||
'pageType': page_type
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@@ -154,7 +166,7 @@ class ModelScanner:
|
||||
|
||||
return total_files
|
||||
|
||||
def _initialize_cache_sync(self, total_files=0):
|
||||
def _initialize_cache_sync(self, total_files=0, page_type='loras'):
|
||||
"""Synchronous version of cache initialization for thread pool execution"""
|
||||
try:
|
||||
# Create a new event loop for this thread
|
||||
@@ -222,7 +234,9 @@ class ModelScanner:
|
||||
await ws_manager.broadcast_init_progress({
|
||||
'stage': 'process_models',
|
||||
'progress': progress_percent,
|
||||
'details': f"Processing {self.model_type} files: {processed_files}/{total_files}"
|
||||
'details': f"Processing {self.model_type} files: {processed_files}/{total_files}",
|
||||
'scanner_type': self.model_type,
|
||||
'pageType': page_type
|
||||
})
|
||||
|
||||
elif entry.is_dir(follow_symlinks=True):
|
||||
@@ -299,6 +313,9 @@ class ModelScanner:
|
||||
# Clear existing tags count
|
||||
self._tags_count = {}
|
||||
|
||||
# Determine the page type based on model type
|
||||
page_type = 'loras' if self.model_type == 'lora' else 'checkpoints'
|
||||
|
||||
# Scan for new data
|
||||
raw_data = await self.scan_all_models()
|
||||
|
||||
|
||||
@@ -55,6 +55,14 @@ class WebSocketManager:
|
||||
if not self._init_websockets:
|
||||
return
|
||||
|
||||
# Ensure data has all required fields
|
||||
if 'stage' not in data:
|
||||
data['stage'] = 'processing'
|
||||
if 'progress' not in data:
|
||||
data['progress'] = 0
|
||||
if 'details' not in data:
|
||||
data['details'] = 'Processing...'
|
||||
|
||||
for ws in self._init_websockets:
|
||||
try:
|
||||
await ws.send_json(data)
|
||||
|
||||
@@ -2,16 +2,28 @@
|
||||
|
||||
.initialization-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--space-3);
|
||||
background: var(--lora-surface);
|
||||
border-radius: var(--border-radius-base);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.initialization-content {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Override loading.css width for initialization component */
|
||||
.initialization-container .loading-content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
background: transparent;
|
||||
backdrop-filter: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.initialization-header {
|
||||
@@ -53,11 +65,20 @@
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
/* Progress Details */
|
||||
.progress-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-color);
|
||||
margin-top: var(--space-1);
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
#remainingTime {
|
||||
font-style: italic;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Stages Styles */
|
||||
@@ -143,49 +164,92 @@
|
||||
color: rgb(0, 150, 0);
|
||||
}
|
||||
|
||||
/* Tips Styles */
|
||||
.initialization-tips {
|
||||
/* Tips Container */
|
||||
.tips-container {
|
||||
margin-top: var(--space-3);
|
||||
background: rgba(var(--lora-accent), 0.05);
|
||||
border-radius: var(--border-radius-base);
|
||||
padding: var(--space-2);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tips-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-1);
|
||||
color: var(--text-color);
|
||||
margin-bottom: var(--space-2);
|
||||
padding-bottom: var(--space-1);
|
||||
border-bottom: 1px solid var(--lora-border);
|
||||
}
|
||||
|
||||
.tips-header i {
|
||||
margin-right: 8px;
|
||||
margin-right: 10px;
|
||||
color: var(--lora-accent);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.tips-header h3 {
|
||||
font-size: 1.1rem;
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Tip Carousel with Images */
|
||||
.tips-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tip-carousel {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
height: 160px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s ease;
|
||||
padding: 0 var(--space-1);
|
||||
padding: 0;
|
||||
border-radius: var(--border-radius-sm);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tip-item.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tip-item p {
|
||||
.tip-image {
|
||||
width: 40%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--lora-border);
|
||||
}
|
||||
|
||||
.tip-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
width: 60%;
|
||||
padding: var(--space-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tip-text h4 {
|
||||
margin: 0 0 var(--space-1) 0;
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.tip-text p {
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
font-size: 0.9rem;
|
||||
@@ -195,17 +259,21 @@
|
||||
.tip-navigation {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: var(--space-1);
|
||||
margin-top: var(--space-2);
|
||||
}
|
||||
|
||||
.tip-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--lora-border);
|
||||
margin: 0 4px;
|
||||
margin: 0 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.tip-dot:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.tip-dot.active {
|
||||
@@ -256,4 +324,30 @@
|
||||
height: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.tip-item {
|
||||
flex-direction: column;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.tip-image, .tip-text {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tip-image {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.tip-carousel {
|
||||
height: 220px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.initialization-container,
|
||||
.tip-item,
|
||||
.tip-dot {
|
||||
transition: none;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
@@ -2,26 +2,38 @@
|
||||
* Initialization Component
|
||||
* Manages the display of initialization progress and status
|
||||
*/
|
||||
import { appCore } from '../core.js';
|
||||
import { getSessionItem, setSessionItem } from '../utils/storageHelpers.js';
|
||||
import { state, getCurrentPageState } from '../state/index.js';
|
||||
|
||||
class InitializationManager {
|
||||
constructor() {
|
||||
this.currentTipIndex = 0;
|
||||
this.tipInterval = null;
|
||||
this.websocket = null;
|
||||
this.currentStage = null;
|
||||
this.progress = 0;
|
||||
this.stages = [
|
||||
{ id: 'stageScanFolders', name: 'scan_folders' },
|
||||
{ id: 'stageCountModels', name: 'count_models' },
|
||||
{ id: 'stageProcessModels', name: 'process_models' },
|
||||
{ id: 'stageFinalizing', name: 'finalizing' }
|
||||
];
|
||||
this.processingStartTime = null;
|
||||
this.processedFilesCount = 0;
|
||||
this.totalFilesCount = 0;
|
||||
this.averageProcessingTime = null;
|
||||
this.pageType = null; // Added page type property
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the component
|
||||
*/
|
||||
initialize() {
|
||||
// Initialize core application for theme and header functionality
|
||||
appCore.initialize().then(() => {
|
||||
console.log('Core application initialized for initialization component');
|
||||
});
|
||||
|
||||
// Detect the current page type
|
||||
this.detectPageType();
|
||||
|
||||
// Check session storage for saved progress
|
||||
this.restoreProgress();
|
||||
|
||||
// Setup the tip carousel
|
||||
this.setupTipCarousel();
|
||||
|
||||
@@ -33,9 +45,65 @@ class InitializationManager {
|
||||
|
||||
// Show first tip as active
|
||||
document.querySelector('.tip-item').classList.add('active');
|
||||
|
||||
// Set the first stage as active
|
||||
this.updateStage('scan_folders');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the current page type
|
||||
*/
|
||||
detectPageType() {
|
||||
// Get the current page type from URL or data attribute
|
||||
const path = window.location.pathname;
|
||||
if (path.includes('/checkpoints')) {
|
||||
this.pageType = 'checkpoints';
|
||||
} else if (path.includes('/loras')) {
|
||||
this.pageType = 'loras';
|
||||
} else {
|
||||
// Default to loras if can't determine
|
||||
this.pageType = 'loras';
|
||||
}
|
||||
console.log(`Initialization component detected page type: ${this.pageType}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the storage key with page type prefix
|
||||
*/
|
||||
getStorageKey(key) {
|
||||
return `${this.pageType}_${key}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore progress from session storage if available
|
||||
*/
|
||||
restoreProgress() {
|
||||
const savedProgress = getSessionItem(this.getStorageKey('initProgress'));
|
||||
if (savedProgress) {
|
||||
console.log(`Restoring ${this.pageType} progress from session storage:`, savedProgress);
|
||||
|
||||
// Restore progress percentage
|
||||
if (savedProgress.progress !== undefined) {
|
||||
this.updateProgress(savedProgress.progress);
|
||||
}
|
||||
|
||||
// Restore processed files count and total files
|
||||
if (savedProgress.processedFiles !== undefined) {
|
||||
this.processedFilesCount = savedProgress.processedFiles;
|
||||
}
|
||||
|
||||
if (savedProgress.totalFiles !== undefined) {
|
||||
this.totalFilesCount = savedProgress.totalFiles;
|
||||
}
|
||||
|
||||
// Restore processing time metrics if available
|
||||
if (savedProgress.averageProcessingTime !== undefined) {
|
||||
this.averageProcessingTime = savedProgress.averageProcessingTime;
|
||||
this.updateRemainingTime();
|
||||
}
|
||||
|
||||
// Restore progress status message
|
||||
if (savedProgress.details) {
|
||||
this.updateStatusMessage(savedProgress.details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,7 +150,7 @@ class InitializationManager {
|
||||
|
||||
// Set a simulated progress that moves forward slowly
|
||||
// This gives users feedback even if the backend isn't providing updates
|
||||
let simulatedProgress = 0;
|
||||
let simulatedProgress = this.progress || 0;
|
||||
const simulateInterval = setInterval(() => {
|
||||
simulatedProgress += 0.5;
|
||||
if (simulatedProgress > 95) {
|
||||
@@ -129,25 +197,97 @@ class InitializationManager {
|
||||
handleProgressUpdate(data) {
|
||||
if (!data) return;
|
||||
|
||||
// Check if this update is for our page type
|
||||
if (data.pageType && data.pageType !== this.pageType) {
|
||||
console.log(`Ignoring update for ${data.pageType}, we're on ${this.pageType}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no pageType is specified in the data but we have scanner_type, map it to pageType
|
||||
if (!data.pageType && data.scanner_type) {
|
||||
const scannerTypeToPageType = {
|
||||
'lora': 'loras',
|
||||
'checkpoint': 'checkpoints'
|
||||
};
|
||||
|
||||
if (scannerTypeToPageType[data.scanner_type] !== this.pageType) {
|
||||
console.log(`Ignoring update for ${data.scanner_type}, we're on ${this.pageType}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Save progress data to session storage
|
||||
setSessionItem(this.getStorageKey('initProgress'), {
|
||||
...data,
|
||||
averageProcessingTime: this.averageProcessingTime,
|
||||
processedFiles: this.processedFilesCount,
|
||||
totalFiles: this.totalFilesCount
|
||||
});
|
||||
|
||||
// Update progress percentage
|
||||
if (data.progress !== undefined) {
|
||||
this.updateProgress(data.progress);
|
||||
}
|
||||
|
||||
// Update current stage
|
||||
if (data.stage) {
|
||||
this.updateStage(data.stage);
|
||||
}
|
||||
|
||||
// Update stage-specific details
|
||||
if (data.details) {
|
||||
this.updateStageDetails(data.stage, data.details);
|
||||
this.updateStatusMessage(data.details);
|
||||
}
|
||||
|
||||
// Track files count for time estimation
|
||||
if (data.stage === 'count_models' && data.details) {
|
||||
const match = data.details.match(/Found (\d+)/);
|
||||
if (match && match[1]) {
|
||||
this.totalFilesCount = parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Track processed files for time estimation
|
||||
if (data.stage === 'process_models' && data.details) {
|
||||
const match = data.details.match(/Processing .* files: (\d+)\/(\d+)/);
|
||||
if (match && match[1] && match[2]) {
|
||||
const currentCount = parseInt(match[1]);
|
||||
const totalCount = parseInt(match[2]);
|
||||
|
||||
// Make sure we have valid total count
|
||||
if (totalCount > 0 && this.totalFilesCount === 0) {
|
||||
this.totalFilesCount = totalCount;
|
||||
}
|
||||
|
||||
// Start tracking processing time once we've processed some files
|
||||
if (currentCount > 0 && !this.processingStartTime && this.processedFilesCount === 0) {
|
||||
this.processingStartTime = Date.now();
|
||||
}
|
||||
|
||||
// Calculate average processing time based on elapsed time and files processed
|
||||
if (this.processingStartTime && currentCount > this.processedFilesCount) {
|
||||
const newFiles = currentCount - this.processedFilesCount;
|
||||
const elapsedTime = Date.now() - this.processingStartTime;
|
||||
const timePerFile = elapsedTime / currentCount; // ms per file
|
||||
|
||||
// Update moving average
|
||||
if (!this.averageProcessingTime) {
|
||||
this.averageProcessingTime = timePerFile;
|
||||
} else {
|
||||
// Simple exponential moving average
|
||||
this.averageProcessingTime = this.averageProcessingTime * 0.7 + timePerFile * 0.3;
|
||||
}
|
||||
|
||||
// Update remaining time estimate
|
||||
this.updateRemainingTime();
|
||||
}
|
||||
|
||||
this.processedFilesCount = currentCount;
|
||||
}
|
||||
}
|
||||
|
||||
// If initialization is complete, reload the page
|
||||
if (data.status === 'complete') {
|
||||
this.showCompletionMessage();
|
||||
|
||||
// Remove session storage data since we're done
|
||||
setSessionItem(this.getStorageKey('initProgress'), null);
|
||||
|
||||
// Give the user a moment to see the completion message
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
@@ -155,6 +295,52 @@ class InitializationManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the remaining time display based on current progress
|
||||
*/
|
||||
updateRemainingTime() {
|
||||
if (!this.averageProcessingTime || !this.totalFilesCount || this.totalFilesCount <= 0) {
|
||||
document.getElementById('remainingTime').textContent = 'Estimating...';
|
||||
return;
|
||||
}
|
||||
|
||||
const remainingFiles = this.totalFilesCount - this.processedFilesCount;
|
||||
const remainingTimeMs = remainingFiles * this.averageProcessingTime;
|
||||
|
||||
if (remainingTimeMs <= 0) {
|
||||
document.getElementById('remainingTime').textContent = 'Almost done...';
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the time for display
|
||||
let formattedTime;
|
||||
if (remainingTimeMs < 60000) {
|
||||
// Less than a minute
|
||||
formattedTime = 'Less than a minute';
|
||||
} else if (remainingTimeMs < 3600000) {
|
||||
// Less than an hour
|
||||
const minutes = Math.round(remainingTimeMs / 60000);
|
||||
formattedTime = `~${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
||||
} else {
|
||||
// Hours and minutes
|
||||
const hours = Math.floor(remainingTimeMs / 3600000);
|
||||
const minutes = Math.round((remainingTimeMs % 3600000) / 60000);
|
||||
formattedTime = `~${hours} hour${hours !== 1 ? 's' : ''} ${minutes} minute${minutes !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
document.getElementById('remainingTime').textContent = formattedTime + ' remaining';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status message
|
||||
*/
|
||||
updateStatusMessage(message) {
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
if (progressStatus) {
|
||||
progressStatus.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the progress bar and percentage
|
||||
*/
|
||||
@@ -169,89 +355,6 @@ class InitializationManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current stage
|
||||
*/
|
||||
updateStage(stageName) {
|
||||
// Mark the previous stage as completed if it exists
|
||||
if (this.currentStage) {
|
||||
const previousStageElement = document.getElementById(this.currentStage);
|
||||
if (previousStageElement) {
|
||||
previousStageElement.classList.remove('active');
|
||||
previousStageElement.classList.add('completed');
|
||||
|
||||
// Update the stage status icon to completed
|
||||
const statusElement = previousStageElement.querySelector('.stage-status');
|
||||
if (statusElement) {
|
||||
statusElement.className = 'stage-status completed';
|
||||
statusElement.innerHTML = '<i class="fas fa-check"></i>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find and activate the new current stage
|
||||
const stageInfo = this.stages.find(s => s.name === stageName);
|
||||
if (stageInfo) {
|
||||
this.currentStage = stageInfo.id;
|
||||
const currentStageElement = document.getElementById(stageInfo.id);
|
||||
|
||||
if (currentStageElement) {
|
||||
currentStageElement.classList.add('active');
|
||||
|
||||
// Update the stage status icon to in-progress
|
||||
const statusElement = currentStageElement.querySelector('.stage-status');
|
||||
if (statusElement) {
|
||||
statusElement.className = 'stage-status in-progress';
|
||||
statusElement.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
|
||||
}
|
||||
|
||||
// Update the progress status message
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
if (progressStatus) {
|
||||
progressStatus.textContent = `${this.stageNameToDisplay(stageName)}...`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert stage name to display text
|
||||
*/
|
||||
stageNameToDisplay(stageName) {
|
||||
switch (stageName) {
|
||||
case 'scan_folders':
|
||||
return 'Scanning folders';
|
||||
case 'count_models':
|
||||
return 'Counting models';
|
||||
case 'process_models':
|
||||
return 'Processing models';
|
||||
case 'finalizing':
|
||||
return 'Finalizing';
|
||||
default:
|
||||
return 'Initializing';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stage-specific details
|
||||
*/
|
||||
updateStageDetails(stageName, details) {
|
||||
const detailsMap = {
|
||||
'scan_folders': 'scanFoldersDetails',
|
||||
'count_models': 'countModelsDetails',
|
||||
'process_models': 'processModelsDetails',
|
||||
'finalizing': 'finalizingDetails'
|
||||
};
|
||||
|
||||
const detailsElementId = detailsMap[stageName];
|
||||
if (detailsElementId) {
|
||||
const detailsElement = document.getElementById(detailsElementId);
|
||||
if (detailsElement && details) {
|
||||
detailsElement.textContent = details;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the tip carousel to rotate through tips
|
||||
*/
|
||||
@@ -261,6 +364,7 @@ class InitializationManager {
|
||||
|
||||
// Show the first tip
|
||||
tipItems[0].classList.add('active');
|
||||
document.querySelector('.tip-dot').classList.add('active');
|
||||
|
||||
// Set up automatic rotation
|
||||
this.tipInterval = setInterval(() => {
|
||||
@@ -335,33 +439,16 @@ class InitializationManager {
|
||||
* Show completion message
|
||||
*/
|
||||
showCompletionMessage() {
|
||||
// Mark all stages as completed
|
||||
this.stages.forEach(stage => {
|
||||
const stageElement = document.getElementById(stage.id);
|
||||
if (stageElement) {
|
||||
stageElement.classList.remove('active');
|
||||
stageElement.classList.add('completed');
|
||||
|
||||
const statusElement = stageElement.querySelector('.stage-status');
|
||||
if (statusElement) {
|
||||
statusElement.className = 'stage-status completed';
|
||||
statusElement.innerHTML = '<i class="fas fa-check"></i>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update progress to 100%
|
||||
this.updateProgress(100);
|
||||
|
||||
// Update status message
|
||||
const progressStatus = document.getElementById('progressStatus');
|
||||
if (progressStatus) {
|
||||
progressStatus.textContent = 'Initialization complete!';
|
||||
}
|
||||
this.updateStatusMessage('Initialization complete!');
|
||||
|
||||
// Update title and subtitle
|
||||
const initTitle = document.getElementById('initTitle');
|
||||
const initSubtitle = document.getElementById('initSubtitle');
|
||||
const remainingTime = document.getElementById('remainingTime');
|
||||
|
||||
if (initTitle) {
|
||||
initTitle.textContent = 'Initialization Complete';
|
||||
@@ -370,6 +457,10 @@ class InitializationManager {
|
||||
if (initSubtitle) {
|
||||
initSubtitle.textContent = 'Reloading page...';
|
||||
}
|
||||
|
||||
if (remainingTime) {
|
||||
remainingTime.textContent = 'Done!';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,98 +6,78 @@
|
||||
<p class="init-subtitle" id="initSubtitle">{% block init_message %}Preparing your workspace...{% endblock %}</p>
|
||||
</div>
|
||||
|
||||
<div class="initialization-progress">
|
||||
<div class="progress-bar-container">
|
||||
<div class="loading-content">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-status" id="progressStatus">Initializing...</div>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" id="initProgressBar"></div>
|
||||
</div>
|
||||
<div class="progress-details">
|
||||
<span class="progress-percentage" id="progressPercentage">0%</span>
|
||||
<span class="progress-status" id="progressStatus">Starting initialization...</span>
|
||||
<span id="progressPercentage">0%</span>
|
||||
<span id="remainingTime">Estimating time...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="initialization-stages">
|
||||
<div class="stage-item" id="stageScanFolders">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</div>
|
||||
<div class="stage-content">
|
||||
<h4>Scanning Folders</h4>
|
||||
<div class="stage-details" id="scanFoldersDetails">Discovering model directories...</div>
|
||||
</div>
|
||||
<div class="stage-status pending">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stage-item" id="stageCountModels">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-calculator"></i>
|
||||
</div>
|
||||
<div class="stage-content">
|
||||
<h4>Counting Models</h4>
|
||||
<div class="stage-details" id="countModelsDetails">Analyzing files...</div>
|
||||
</div>
|
||||
<div class="stage-status pending">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stage-item" id="stageProcessModels">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="stage-content">
|
||||
<h4>Processing Models</h4>
|
||||
<div class="stage-details" id="processModelsDetails">Reading model metadata...</div>
|
||||
</div>
|
||||
<div class="stage-status pending">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stage-item" id="stageFinalizing">
|
||||
<div class="stage-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="stage-content">
|
||||
<h4>Finalizing</h4>
|
||||
<div class="stage-details" id="finalizingDetails">Building cache and optimizing...</div>
|
||||
</div>
|
||||
<div class="stage-status pending">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="initialization-tips">
|
||||
<div class="tips-container">
|
||||
<div class="tips-header">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
<h3>Tips</h3>
|
||||
<h3>Tips & Tricks</h3>
|
||||
</div>
|
||||
<div class="tip-carousel" id="tipCarousel">
|
||||
<div class="tip-item">
|
||||
<p>You can drag and drop LoRA files into your folders to automatically import them.</p>
|
||||
<div class="tips-content">
|
||||
<div class="tip-carousel" id="tipCarousel">
|
||||
<div class="tip-item active">
|
||||
<div class="tip-image">
|
||||
<img src="/static/images/tips/drag-drop.png" alt="Drag and Drop" onerror="this.src='/static/images/no-preview.png'">
|
||||
</div>
|
||||
<div class="tip-text">
|
||||
<h4>Quick Import</h4>
|
||||
<p>You can drag and drop LoRA files into your folders to automatically import them.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-image">
|
||||
<img src="/static/images/tips/civitai-download.png" alt="Civitai Download" onerror="this.src='/static/images/no-preview.png'">
|
||||
</div>
|
||||
<div class="tip-text">
|
||||
<h4>Easy Download</h4>
|
||||
<p>Use Civitai URLs to quickly download and install new models.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-image">
|
||||
<img src="/static/images/tips/recipes.png" alt="Recipes" onerror="this.src='/static/images/no-preview.png'">
|
||||
</div>
|
||||
<div class="tip-text">
|
||||
<h4>Save Recipes</h4>
|
||||
<p>Create recipes to save your favorite model combinations for future use.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-image">
|
||||
<img src="/static/images/tips/filter.png" alt="Filter Models" onerror="this.src='/static/images/no-preview.png'">
|
||||
</div>
|
||||
<div class="tip-text">
|
||||
<h4>Fast Filtering</h4>
|
||||
<p>Filter models by tags or base model type using the filter button in the header.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<div class="tip-image">
|
||||
<img src="/static/images/tips/search.png" alt="Quick Search" onerror="this.src='/static/images/no-preview.png'">
|
||||
</div>
|
||||
<div class="tip-text">
|
||||
<h4>Quick Search</h4>
|
||||
<p>Press Ctrl+F (Cmd+F on Mac) to quickly search within your current view.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<p>Use Civitai URLs to quickly download and install new models.</p>
|
||||
<div class="tip-navigation">
|
||||
<span class="tip-dot active" data-index="0"></span>
|
||||
<span class="tip-dot" data-index="1"></span>
|
||||
<span class="tip-dot" data-index="2"></span>
|
||||
<span class="tip-dot" data-index="3"></span>
|
||||
<span class="tip-dot" data-index="4"></span>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<p>Create recipes to save your favorite model combinations for future use.</p>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<p>Filter models by tags or base model type using the filter button in the header.</p>
|
||||
</div>
|
||||
<div class="tip-item">
|
||||
<p>Press Ctrl+F (Cmd+F on Mac) to quickly search within your current view.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tip-navigation">
|
||||
<span class="tip-dot active" data-index="0"></span>
|
||||
<span class="tip-dot" data-index="1"></span>
|
||||
<span class="tip-dot" data-index="2"></span>
|
||||
<span class="tip-dot" data-index="3"></span>
|
||||
<span class="tip-dot" data-index="4"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user