SEO_iamge_renamer_starting_.../script.js
2025-08-05 00:15:36 +02:00

611 lines
No EOL
20 KiB
JavaScript

// Global variables
let uploadedImages = [];
let keywords = [];
let generatedNames = [];
// DOM elements
const dropArea = document.getElementById('drop-area');
const fileInput = document.getElementById('file-input');
const browseBtn = document.getElementById('browse-btn');
const workflowSection = document.getElementById('workflow-section');
const keywordsSection = document.getElementById('keywords-section');
const keywordInput = document.getElementById('keyword-input');
const enhanceBtn = document.getElementById('enhance-btn');
const keywordsDisplay = document.getElementById('keywords-display');
const imagesPreview = document.getElementById('images-preview');
const imagesContainer = document.getElementById('images-container');
const downloadBtn = document.getElementById('download-btn');
// AI Configuration
const AI_CONFIG = {
API_KEY: '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',
DEEPSEEK_MODEL: 'deepseek/deepseek-chat-v3-0324:free',
VISION_MODEL: 'meta-llama/llama-3.2-11b-vision-instruct:free',
API_URL: 'https://openrouter.ai/api/v1/chat/completions'
};
// Event listeners
document.addEventListener('DOMContentLoaded', () => {
// Upload area event listeners
dropArea.addEventListener('click', () => fileInput.click());
browseBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
// Drag and drop events
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
dropArea.addEventListener('drop', handleDrop, false);
// Keyword events
keywordInput.addEventListener('input', toggleEnhanceButton);
enhanceBtn.addEventListener('click', enhanceKeywords);
// Download button
downloadBtn.addEventListener('click', downloadImages);
});
// Prevent default drag behaviors
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Highlight drop area when item is dragged over it
function highlight() {
dropArea.classList.add('dragover');
}
// Remove highlight when item is dragged out of drop area
function unhighlight() {
dropArea.classList.remove('dragover');
}
// Handle dropped files
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
// Handle file selection
function handleFileSelect(e) {
const files = e.target.files;
handleFiles(files);
}
// Process uploaded files
function handleFiles(files) {
if (files.length === 0) return;
// Convert FileList to Array
const filesArray = Array.from(files);
// Filter only image files
const imageFiles = filesArray.filter(file => file.type.startsWith('image/'));
if (imageFiles.length === 0) {
alert('Please select image files only.');
return;
}
// Clear previous images
uploadedImages = [];
// Process each image file
let processedCount = 0;
imageFiles.forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
uploadedImages.push({
file: file,
name: file.name,
size: file.size,
type: file.type,
src: e.target.result,
newName: generateFileName(file.name),
visionKeywords: []
});
processedCount++;
// Show workflow section after all files are processed
if (processedCount === imageFiles.length) {
workflowSection.style.display = 'block';
keywordsSection.style.display = 'block';
imagesPreview.style.display = 'block';
updateImagesPreview();
// Smooth scroll to workflow section
workflowSection.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
};
reader.readAsDataURL(file);
});
}
// Generate a simple filename based on original name
function generateFileName(originalName) {
// Remove extension
const nameWithoutExt = originalName.substring(0, originalName.lastIndexOf('.'));
// Replace non-alphanumeric characters with spaces
const cleanName = nameWithoutExt.replace(/[^a-zA-Z0-9]/g, ' ');
// Capitalize first letter and make it SEO friendly
return cleanName.charAt(0).toUpperCase() + cleanName.slice(1);
}
// Toggle enhance button based on keyword input
function toggleEnhanceButton() {
enhanceBtn.disabled = keywordInput.value.trim() === '';
}
// Enhance keywords with AI
async function enhanceKeywords() {
const keywordText = keywordInput.value.trim();
if (keywordText === '') return;
// Show loading state
enhanceBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Enhancing...';
enhanceBtn.disabled = true;
try {
// Call AI API to enhance keywords
const enhancedKeywords = await callAIKeywordEnhancement(keywordText);
// Split keywords by comma or space
const newKeywords = enhancedKeywords.split(/[, ]+/).filter(k => k !== '');
// Add new keywords to the list
newKeywords.forEach(keyword => {
if (!keywords.includes(keyword)) {
keywords.push(keyword);
}
});
// Update keywords display
updateKeywordsDisplay();
// Clear input
keywordInput.value = '';
// Analyze images with vision AI and generate new filenames
await analyzeImagesAndGenerateNames();
} catch (error) {
console.error('Error enhancing keywords:', error);
alert('An error occurred while enhancing keywords. You may have hit rate limits. Please try again in a moment.');
// Fallback to simple keyword enhancement
fallbackToSimpleEnhancement(keywordText);
} finally {
// Reset button
enhanceBtn.innerHTML = '<i class="fas fa-magic"></i> Enhance with AI';
enhanceBtn.disabled = keywordInput.value.trim() === '';
}
}
// Fallback to simple keyword enhancement
function fallbackToSimpleEnhancement(keywordText) {
const newKeywords = keywordText.split(/[, ]+/).filter(k => k !== '');
newKeywords.forEach(keyword => {
if (!keywords.includes(keyword)) {
keywords.push(keyword);
}
});
// Add some simulated AI-enhanced keywords
const aiKeywords = [
'SEO optimized',
'high quality',
'professional',
'digital',
'modern'
];
aiKeywords.forEach(keyword => {
if (!keywords.includes(keyword)) {
keywords.push(keyword);
}
});
// Update keywords display
updateKeywordsDisplay();
// Generate new filenames for images
generateNewFileNames();
}
// Call AI API to enhance keywords
async function callAIKeywordEnhancement(keywords) {
const prompt = `Enhance these keywords for SEO image optimization. Provide 10 additional related keywords that would help images rank better in search engines. Return only the keywords separated by commas, nothing else. Keywords: ${keywords}`;
const response = await fetch(AI_CONFIG.API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AI_CONFIG.API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': window.location.href,
'X-Title': 'SEO Image Renamer'
},
body: JSON.stringify({
model: AI_CONFIG.DEEPSEEK_MODEL,
messages: [
{
role: "user",
content: prompt
}
]
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('API Error Response:', errorText);
throw new Error(`API request failed with status ${response.status}: ${errorText}`);
}
const data = await response.json();
return data.choices[0].message.content.trim();
}
// Analyze images with vision AI and generate new filenames
async function analyzeImagesAndGenerateNames() {
if (keywords.length === 0) return;
// Show loading state for each image
document.querySelectorAll('.new-name-input').forEach(input => {
input.disabled = true;
input.placeholder = 'Analyzing image and generating filename...';
});
try {
// Analyze each image with vision AI and generate unique filenames
const usedNames = new Set();
for (let i = 0; i < uploadedImages.length; i++) {
const image = uploadedImages[i];
// Analyze image with vision AI
const visionKeywords = await analyzeImageWithVisionAI(image.src);
image.visionKeywords = visionKeywords;
// Generate unique filename
let newName;
let attempts = 0;
do {
newName = await generateUniqueFilename(image, visionKeywords, usedNames);
attempts++;
} while (usedNames.has(newName.toLowerCase()) && attempts < 10);
// Add to used names
usedNames.add(newName.toLowerCase());
// Update the image with the new name
const extension = image.name.substring(image.name.lastIndexOf('.'));
image.newName = `${newName.substring(0, 50)}${extension}`;
}
// Update images preview
updateImagesPreview();
// Enable download button
downloadBtn.disabled = false;
} catch (error) {
console.error('Error analyzing images:', error);
alert('An error occurred while analyzing images. You may have hit rate limits or the AI service is temporarily unavailable. Please try again in a moment.');
// Revert to simple filename generation
fallbackToSimpleNaming();
} finally {
// Re-enable inputs
document.querySelectorAll('.new-name-input').forEach(input => {
input.disabled = false;
input.placeholder = '';
});
}
}
// Analyze image with vision AI
async function analyzeImageWithVisionAI(imageSrc) {
const prompt = "Analyze this image and provide exactly 2 keywords that describe the main subject or action in the image. Return only the two keywords separated by a space, nothing else.";
const response = await fetch(AI_CONFIG.API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AI_CONFIG.API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': window.location.href,
'X-Title': 'SEO Image Renamer'
},
body: JSON.stringify({
model: AI_CONFIG.VISION_MODEL,
messages: [
{
role: "user",
content: [
{
type: "text",
text: prompt
},
{
type: "image_url",
image_url: {
url: imageSrc
}
}
]
}
]
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('Vision API Error Response:', errorText);
throw new Error(`Vision API request failed with status ${response.status}: ${errorText}`);
}
const data = await response.json();
const visionResponse = data.choices[0].message.content.trim();
return visionResponse.split(' ').slice(0, 2); // Get up to 2 keywords
}
// Generate unique filename
async function generateUniqueFilename(image, visionKeywords, usedNames) {
const keywordString = keywords.slice(0, 5).join(', ');
const visionString = visionKeywords.join(' ');
const prompt = `Create a natural, descriptive filename for an image.
User keywords: ${keywordString}
Image content keywords: ${visionString}
Original filename: ${image.name}
Create a human-readable phrase that combines these elements naturally, like "burning car at a carshow in france".
Use 3-7 words, start with a capital letter, and use spaces between words.
Do not include the file extension.
Return only the filename, nothing else.`;
const response = await fetch(AI_CONFIG.API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AI_CONFIG.API_KEY}`,
'Content-Type': 'application/json',
'HTTP-Referer': window.location.href,
'X-Title': 'SEO Image Renamer'
},
body: JSON.stringify({
model: AI_CONFIG.DEEPSEEK_MODEL,
messages: [
{
role: "user",
content: prompt
}
]
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('Filename generation API Error Response:', errorText);
throw new Error(`Filename generation API request failed with status ${response.status}: ${errorText}`);
}
const data = await response.json();
return data.choices[0].message.content.trim();
}
// Fallback to simple naming
function fallbackToSimpleNaming() {
if (keywords.length === 0) return;
const usedNames = new Set();
uploadedImages.forEach((image, index) => {
// Combine keywords with the original filename
const keywordString = keywords.slice(0, 3).join(' ');
const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.'));
const extension = image.name.substring(image.name.lastIndexOf('.'));
// Create new name
let newName = `${keywordString} ${nameWithoutExt}`.substring(0, 50);
// Ensure uniqueness
let counter = 1;
let uniqueName = newName;
while (usedNames.has(uniqueName.toLowerCase())) {
uniqueName = `${newName} ${counter}`;
counter++;
}
usedNames.add(uniqueName.toLowerCase());
image.newName = uniqueName + extension;
});
// Update images preview
updateImagesPreview();
// Enable download button
downloadBtn.disabled = false;
}
// Fallback function to generate new filenames without AI
function generateNewFileNames() {
if (keywords.length === 0) return;
const usedNames = new Set();
uploadedImages.forEach((image, index) => {
// Combine keywords with the original filename
const keywordString = keywords.slice(0, 3).join(' ');
const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.'));
const extension = image.name.substring(image.name.lastIndexOf('.'));
// Create new name
let newName = `${keywordString} ${nameWithoutExt}`.substring(0, 50);
// Ensure uniqueness
let counter = 1;
let uniqueName = newName;
while (usedNames.has(uniqueName.toLowerCase())) {
uniqueName = `${newName} ${counter}`;
counter++;
}
usedNames.add(uniqueName.toLowerCase());
image.newName = uniqueName + extension;
});
// Update images preview
updateImagesPreview();
// Enable download button
downloadBtn.disabled = false;
}
// Highlight vision keywords in filename
function highlightVisionKeywords(filename, visionKeywords) {
if (!visionKeywords || visionKeywords.length === 0) {
return filename;
}
let highlightedName = filename;
visionKeywords.forEach(keyword => {
if (keyword && keyword.trim()) {
const regex = new RegExp(`\\b${keyword.trim()}\\b`, 'gi');
highlightedName = highlightedName.replace(regex, `<span class="vision-highlight">${keyword}</span>`);
}
});
return highlightedName;
}
// Update keywords display
function updateKeywordsDisplay() {
keywordsDisplay.innerHTML = '';
keywords.forEach((keyword, index) => {
const keywordChip = document.createElement('div');
keywordChip.className = 'keyword-chip';
keywordChip.innerHTML = `
<span>${keyword}</span>
<button class="remove-keyword" data-index="${index}">&times;</button>
`;
keywordsDisplay.appendChild(keywordChip);
});
// Add event listeners to remove buttons
document.querySelectorAll('.remove-keyword').forEach(button => {
button.addEventListener('click', (e) => {
const index = parseInt(e.target.getAttribute('data-index'));
keywords.splice(index, 1);
updateKeywordsDisplay();
generateNewFileNames();
});
});
}
// Update images preview
function updateImagesPreview() {
imagesContainer.innerHTML = '';
uploadedImages.forEach((image, index) => {
const imageCard = document.createElement('div');
imageCard.className = 'image-card';
// Get filename without extension for highlighting
const nameWithoutExt = image.newName.substring(0, image.newName.lastIndexOf('.'));
const extension = image.newName.substring(image.newName.lastIndexOf('.'));
const highlightedName = highlightVisionKeywords(nameWithoutExt, image.visionKeywords);
imageCard.innerHTML = `
<img src="${image.src}" alt="${image.name}" class="image-thumbnail">
<div class="image-info">
<div class="original-name">Original: ${image.name}</div>
${image.visionKeywords && image.visionKeywords.length > 0 ?
`<div class="vision-keywords">Vision AI: <span class="vision-tags">${image.visionKeywords.join(', ')}</span></div>` : ''}
<div class="new-name-container">
<label>New name:</label>
<div class="filename-display">${highlightedName}${extension}</div>
<input type="text" class="new-name-input" value="${image.newName}" data-index="${index}">
</div>
</div>
`;
imagesContainer.appendChild(imageCard);
});
// Add event listeners to name inputs
document.querySelectorAll('.new-name-input').forEach(input => {
input.addEventListener('input', (e) => {
const index = parseInt(e.target.getAttribute('data-index'));
uploadedImages[index].newName = e.target.value;
});
});
}
// Download images as ZIP
async function downloadImages() {
if (uploadedImages.length === 0) return;
// Show loading state
downloadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Preparing Download...';
downloadBtn.disabled = true;
try {
// Create a new ZIP file
const zip = new JSZip();
// Add each image to the ZIP with its new name
for (const image of uploadedImages) {
// Convert data URL to blob
const blob = await fetch(image.src).then(res => res.blob());
zip.file(image.newName, blob);
}
// Generate the ZIP file
const content = await zip.generateAsync({type: "blob"});
// Create download link
const url = URL.createObjectURL(content);
const a = document.createElement('a');
a.href = url;
a.download = 'renamed-images.zip';
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
// Reset button
downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download Renamed Images as ZIP';
downloadBtn.disabled = false;
} catch (error) {
console.error('Error creating ZIP file:', error);
alert('An error occurred while creating the ZIP file. Please try again.');
// Reset button
downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download Renamed Images as ZIP';
downloadBtn.disabled = false;
}
}
// Initialize the page
function init() {
// Set up any initial state
downloadBtn.disabled = true;
}
// Call init when page loads
init();