feat: Enhance initialization component with progress tracking and UI improvements

This commit is contained in:
Will Miao
2025-04-13 12:58:38 +08:00
parent a043b487bd
commit 0b11e6e6d0
5 changed files with 414 additions and 224 deletions

View File

@@ -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;
}
}

View File

@@ -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!';
}
}
/**