File size: 7,408 Bytes
5435413 4920069 5435413 4920069 5435413 5056db8 5435413 5056db8 414e700 c0fc1ad 414e700 5056db8 5435413 4920069 5435413 414e700 5435413 4920069 5435413 4920069 5435413 4920069 2aa7dab 5435413 4920069 5435413 2aa7dab 5435413 2aa7dab 5435413 2aa7dab 5435413 4920069 5435413 de7bb17 5435413 de7bb17 5435413 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
import type { GradioClient } from '$lib/types';
import {
initializeTrainerScanProgress,
getNextPendingImage,
markImageProcessingStarted,
markImageProcessingCompleted,
markImageProcessingFailed,
getScanningStats,
getCurrentProcessingImage
} from '$lib/db/trainerScanning';
export interface TrainerScanState {
isScanning: boolean;
currentImage: string | null;
currentTrainer: string | null;
progress: {
total: number;
completed: number;
failed: number;
pending: number;
};
error: string | null;
}
export class TrainerScanService {
private joyCaptionClient: GradioClient;
private zephyrClient: GradioClient;
private fluxClient: GradioClient;
private isScanning = false;
private shouldStop = false;
private stateCallbacks: ((state: TrainerScanState) => void)[] = [];
constructor(
joyCaptionClient: GradioClient,
zephyrClient: GradioClient,
fluxClient: GradioClient
) {
this.joyCaptionClient = joyCaptionClient;
this.zephyrClient = zephyrClient;
this.fluxClient = fluxClient;
}
// Subscribe to state changes
onStateChange(callback: (state: TrainerScanState) => void) {
this.stateCallbacks.push(callback);
}
// Notify all subscribers of state changes
private async notifyStateChange(state: Partial<TrainerScanState>) {
const currentState = await this.getCurrentState();
const fullState = { ...currentState, ...state };
this.stateCallbacks.forEach(callback => callback(fullState));
}
// Get current scanning state
private async getCurrentState(): Promise<TrainerScanState> {
try {
const stats = await getScanningStats();
return {
isScanning: this.isScanning,
currentImage: null,
currentTrainer: null,
progress: {
total: stats?.total || 0,
completed: stats?.completed || 0,
failed: stats?.failed || 0,
pending: stats?.pending || 0
},
error: null
};
} catch (error) {
console.error('Failed to get current state:', error);
return {
isScanning: this.isScanning,
currentImage: null,
currentTrainer: null,
progress: {
total: 0,
completed: 0,
failed: 0,
pending: 0
},
error: 'Failed to load progress stats'
};
}
}
// Initialize scanning database with image paths from file
async initializeFromFile(): Promise<void> {
try {
const response = await fetch('/trainer_image_paths.txt');
if (!response.ok) {
throw new Error(`Failed to fetch trainer_image_paths.txt: ${response.statusText}`);
}
const content = await response.text();
if (!content) {
throw new Error('trainer_image_paths.txt is empty');
}
const imagePaths = content.trim().split('\n')
.map(path => typeof path === 'string' ? path.trim() : '')
.filter(path => path.length > 0);
console.log(`Loaded ${imagePaths.length} trainer image paths`);
if (imagePaths.length === 0) {
throw new Error('No valid image paths found in trainer_image_paths.txt');
}
await initializeTrainerScanProgress(imagePaths);
await this.notifyStateChange({});
} catch (error) {
console.error('Failed to initialize trainer scan progress:', error);
throw new Error('Failed to load trainer image paths');
}
}
// Start automated scanning
async startScanning(): Promise<void> {
if (this.isScanning) {
throw new Error('Scanning is already in progress');
}
// Initialize database if needed
const stats = await getScanningStats();
if (stats.total === 0) {
await this.initializeFromFile();
}
// Check for interrupted processing
const currentProcessing = await getCurrentProcessingImage();
if (currentProcessing) {
// Reset interrupted processing back to pending
await markImageProcessingFailed(currentProcessing.imagePath, 'Process interrupted');
}
this.isScanning = true;
this.shouldStop = false;
await this.notifyStateChange({ isScanning: true, error: null });
try {
await this.processingLoop();
} catch (error) {
console.error('Scanning error:', error);
await this.notifyStateChange({ error: error instanceof Error ? error.message : 'Unknown error' });
} finally {
this.isScanning = false;
await this.notifyStateChange({ isScanning: false, currentImage: null, currentTrainer: null });
// Log final summary
const finalStats = await getScanningStats();
console.log(`π Scanning session complete:`, {
total: finalStats.total,
completed: finalStats.completed,
failed: finalStats.failed,
pending: finalStats.pending,
successRate: finalStats.total > 0 ? Math.round((finalStats.completed / finalStats.total) * 100) + '%' : '0%'
});
}
}
// Stop scanning
stopScanning(): void {
this.shouldStop = true;
}
// Main processing loop
private async processingLoop(): Promise<void> {
while (!this.shouldStop) {
const nextImage = await getNextPendingImage();
if (!nextImage) {
// No more pending images
break;
}
await this.notifyStateChange({
currentImage: nextImage.imagePath,
currentTrainer: typeof nextImage.trainerName === 'string' ? nextImage.trainerName : null
});
try {
await this.processImage(nextImage.imagePath, nextImage.remoteUrl);
console.log(`β
Successfully processed: ${nextImage.imagePath} (${nextImage.trainerName})`);
// Add small delay between images to prevent overwhelming the system
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error(`β Failed to process ${nextImage.imagePath} (${nextImage.trainerName}):`, {
imagePath: nextImage.imagePath,
trainerName: nextImage.trainerName,
remoteUrl: nextImage.remoteUrl,
error: errorMessage,
fullError: error
});
await markImageProcessingFailed(nextImage.imagePath, errorMessage);
// Continue to next image - don't let individual failures stop the whole process
console.log(`π Continuing to next image despite failure...`);
}
// Update progress
await this.notifyStateChange({});
}
}
// DEPRECATED: This service is no longer used
// The AutoTrainerScanner now directly uses PicletGenerator component
private async processImage(imagePath: string, remoteUrl: string): Promise<void> {
throw new Error('TrainerScanService is deprecated - use PicletGenerator directly');
}
// Fetch remote image and convert to File
private async fetchRemoteImage(remoteUrl: string, originalPath: string): Promise<File> {
const response = await fetch(remoteUrl);
if (!response.ok) {
throw new Error(`Failed to fetch ${remoteUrl}: ${response.statusText}`);
}
const blob = await response.blob();
const fileName = originalPath.split('/').pop() || 'trainer_image.jpg';
return new File([blob], fileName, { type: blob.type });
}
} |