Files
ComfyUI-Lora-Manager/static/js/managers/import/ImageProcessor.js
T

271 lines
9.6 KiB
JavaScript

import { showToast } from '../../utils/uiHelpers.js';
import { translate } from '../../utils/i18nHelpers.js';
export class ImageProcessor {
constructor(importManager) {
this.importManager = importManager;
}
handleFileUpload(event) {
const file = event.target.files[0];
if (file) {
this.handleDroppedFile(file);
}
}
/**
* Shared entry for files coming from the file picker, drag & drop,
* or clipboard paste.
*/
handleDroppedFile(file) {
const errorElement = document.getElementById('uploadError');
// Validate file type
if (!file.type.match('image.*')) {
errorElement.textContent = translate('recipes.controls.import.errors.selectImageFile', {}, 'Please select an image file');
return;
}
// Reset error
errorElement.textContent = '';
this.importManager.recipeImage = file;
this.importManager.importMode = 'upload';
// Show the selected file name in the drop zone
this.importManager.updateSelectedFileName(file.name);
// Auto-proceed to next step if file is selected
this.importManager.uploadAndAnalyzeImage();
}
async handleUrlInput() {
const urlInput = document.getElementById('imageUrlInput');
const errorElement = document.getElementById('importUrlError');
const input = urlInput.value.trim();
// Validate input
if (!input) {
errorElement.textContent = translate('recipes.controls.import.errors.enterUrlOrPath', {}, 'Please enter a URL or file path');
return;
}
// Front-end format validation before hitting the backend
if (input.startsWith('http://') || input.startsWith('https://')) {
try {
new URL(input);
} catch {
errorElement.textContent = translate('recipes.controls.import.errors.invalidUrl', {}, 'Please enter a valid URL');
return;
}
} else if (!/\.(png|jpe?g|webp|gif|bmp|avif|jxl|mp4|webm)$/i.test(input)) {
errorElement.textContent = translate('recipes.controls.import.errors.invalidInputFormat', {}, 'Please enter an image URL or a local image file path');
return;
}
// Reset error
errorElement.textContent = '';
this.importManager.importMode = 'url';
// Put the fetch button into a loading state to prevent duplicate submits
const fetchBtn = document.getElementById('fetchImageBtn');
this._setFetchButtonLoading(fetchBtn, true);
// Show loading indicator
this.importManager.loadingManager.showSimpleLoading(translate('recipes.controls.import.processingInput', {}, 'Processing input...'));
try {
// Check if it's a URL or a local file path
if (input.startsWith('http://') || input.startsWith('https://')) {
// Handle as URL
await this.analyzeImageFromUrl(input);
} else {
// Handle as local file path
await this.analyzeImageFromLocalPath(input);
}
} catch (error) {
errorElement.textContent = error.message || 'Failed to process input';
} finally {
this._setFetchButtonLoading(fetchBtn, false);
this.importManager.loadingManager.hide();
}
}
_setFetchButtonLoading(button, isLoading) {
if (!button) return;
button.disabled = isLoading;
button.classList.toggle('loading', isLoading);
const icon = button.querySelector('i');
if (icon) {
icon.className = isLoading ? 'fas fa-spinner fa-spin' : 'fas fa-download';
}
}
async analyzeImageFromUrl(url) {
try {
// Call the API with URL data
const response = await fetch('/api/lm/recipes/analyze-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: url })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to analyze image from URL');
}
// Get recipe data from response
const recipeData = await response.json();
if (!recipeData) {
throw new Error('No recipe data returned from image analysis');
}
this.importManager.recipeData = recipeData;
this._ensureCheckpointMetadata();
// Check if we have an error message
if (this.importManager.recipeData.error) {
throw new Error(this.importManager.recipeData.error);
}
this.importManager.recipeData.loras = Array.isArray(this.importManager.recipeData.loras)
? this.importManager.recipeData.loras
: [];
// Find missing LoRAs
this.importManager.missingLoras = this.importManager.recipeData.loras.filter(
lora => !lora.existsLocally
);
// Reset import as new flag
this.importManager.importAsNew = false;
// Proceed to recipe details step
this.importManager.showRecipeDetailsStep();
} catch (error) {
console.error('Error analyzing URL:', error);
throw error;
}
}
async analyzeImageFromLocalPath(path) {
try {
// Call the API with local path data
const response = await fetch('/api/lm/recipes/analyze-local-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ path: path })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to load image from local path');
}
// Get recipe data from response
const recipeData = await response.json();
if (!recipeData) {
throw new Error('No recipe data returned from image analysis');
}
this.importManager.recipeData = recipeData;
this._ensureCheckpointMetadata();
// Check if we have an error message
if (this.importManager.recipeData.error) {
throw new Error(this.importManager.recipeData.error);
}
this.importManager.recipeData.loras = Array.isArray(this.importManager.recipeData.loras)
? this.importManager.recipeData.loras
: [];
// Find missing LoRAs
this.importManager.missingLoras = this.importManager.recipeData.loras.filter(
lora => !lora.existsLocally
);
// Reset import as new flag
this.importManager.importAsNew = false;
// Proceed to recipe details step
this.importManager.showRecipeDetailsStep();
} catch (error) {
console.error('Error analyzing local path:', error);
throw error;
}
}
async uploadAndAnalyzeImage() {
if (!this.importManager.recipeImage) {
showToast('toast.recipes.selectImageFirst', {}, 'error');
return;
}
try {
this.importManager.loadingManager.showSimpleLoading(translate('recipes.controls.import.analyzingMetadata', {}, 'Analyzing image metadata...'));
// Create form data for upload
const formData = new FormData();
formData.append('image', this.importManager.recipeImage);
// Upload image for analysis
const response = await fetch('/api/lm/recipes/analyze-image', {
method: 'POST',
body: formData
});
// Get recipe data from response
const recipeData = await response.json();
if (!recipeData) {
throw new Error('No recipe data returned from image analysis');
}
this.importManager.recipeData = recipeData;
this._ensureCheckpointMetadata();
// Check if we have an error message
if (this.importManager.recipeData.error) {
throw new Error(this.importManager.recipeData.error);
}
this.importManager.recipeData.loras = Array.isArray(this.importManager.recipeData.loras)
? this.importManager.recipeData.loras
: [];
// Find missing LoRAs
this.importManager.missingLoras = this.importManager.recipeData.loras.filter(
lora => !lora.existsLocally
);
// Reset import as new flag
this.importManager.importAsNew = false;
// Proceed to recipe details step
this.importManager.showRecipeDetailsStep();
} catch (error) {
document.getElementById('uploadError').textContent = error.message;
} finally {
this.importManager.loadingManager.hide();
}
}
_ensureCheckpointMetadata() {
if (!this.importManager.recipeData) return;
if (this.importManager.recipeData.model && !this.importManager.recipeData.checkpoint) {
this.importManager.recipeData.checkpoint = this.importManager.recipeData.model;
}
}
}