SEO_iamge_renamer_starting_.../script.js

736 lines
No EOL
26 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 - loaded from config.js
let AI_CONFIG = {};
// Initialize configuration on page load
function initializeConfig() {
// Load configuration from window.APP_CONFIG (set by config.js)
if (window.APP_CONFIG) {
AI_CONFIG = {
// OpenRouter API for text generation (DeepSeek)
API_KEY: window.APP_CONFIG.OPENROUTER_API_KEY,
DEEPSEEK_MODEL: window.APP_CONFIG.DEEPSEEK_MODEL,
API_URL: window.APP_CONFIG.OPENROUTER_API_URL,
// Grok-2-Vision API for image analysis
GROK_API_KEY: window.APP_CONFIG.GROK_API_KEY,
GROK_VISION_MODEL: window.APP_CONFIG.GROK_VISION_MODEL,
GROK_API_URL: window.APP_CONFIG.GROK_API_URL
};
console.log('✅ Configuration loaded successfully from config.js');
console.log('🔑 Grok API Key configured:', AI_CONFIG.GROK_API_KEY !== 'your_grok_api_key_here' ? '✅ Yes' : '❌ No - Please add your API key to config.js');
if (AI_CONFIG.GROK_API_KEY === 'your_grok_api_key_here') {
console.warn('⚠️ SETUP REQUIRED: Please add your Grok API key to config.js file');
console.log('📖 Instructions: Open config.js and replace "your_grok_api_key_here" with your actual Grok API key from https://console.x.ai/');
}
} else {
console.error('❌ Configuration file (config.js) not loaded properly');
// Fallback configuration
AI_CONFIG = {
API_KEY: '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',
DEEPSEEK_MODEL: 'deepseek/deepseek-chat-v3-0324:free',
API_URL: 'https://openrouter.ai/api/v1/chat/completions',
GROK_API_KEY: 'your_grok_api_key_here',
GROK_VISION_MODEL: 'grok-2-vision-1212',
GROK_API_URL: 'https://api.x.ai/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> Generating SEO Keywords...';
enhanceBtn.disabled = true;
try {
// Call AI API to enhance keywords
const enhancedKeywords = await callAIKeywordEnhancement(keywordText);
// Better handling for keyphrases - split by comma primarily, preserve phrases
const newKeywords = enhancedKeywords.split(',').map(k => k.trim()).filter(k => k !== '');
// Add new keywords/keyphrases to the list
newKeywords.forEach(keyword => {
// Avoid duplicates by checking both exact match and similarity
const lowercaseKeyword = keyword.toLowerCase();
const isDuplicate = keywords.some(existing =>
existing.toLowerCase() === lowercaseKeyword ||
existing.toLowerCase().includes(lowercaseKeyword) ||
lowercaseKeyword.includes(existing.toLowerCase())
);
if (!isDuplicate) {
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> Generate SEO Keywords';
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();
}
// Detect language from provided keywords
function detectLanguage(keywords) {
const lowerKeywords = keywords.toLowerCase();
// Common patterns for different languages
const languagePatterns = {
dutch: /\b(de|het|en|in|van|met|bij|naar|voor|amsterdam|nederland|barbier|kapper|winkel|kapperszaak|amsterdam|rotterdam|den haag|utrecht)\b/,
german: /\b(der|die|das|und|in|von|mit|bei|nach|für|berlin|münchen|deutschland|friseur|laden|geschäft|hamburg|köln)\b/,
french: /\b(le|la|les|de|du|des|et|dans|avec|chez|vers|pour|paris|france|coiffeur|magasin|boutique|lyon|marseille)\b/,
spanish: /\b(el|la|los|las|de|del|y|en|con|para|hacia|madrid|españa|peluquero|tienda|negocio|barcelona|valencia)\b/,
italian: /\b(il|la|lo|gli|le|di|del|e|in|con|per|verso|roma|italia|parrucchiere|negozio|bottega|milano|napoli)\b/,
};
for (const [lang, pattern] of Object.entries(languagePatterns)) {
if (pattern.test(lowerKeywords)) {
return lang;
}
}
return 'english'; // Default to English
}
// Call AI API to enhance keywords
async function callAIKeywordEnhancement(keywords) {
const detectedLanguage = detectLanguage(keywords);
const languageInstruction = detectedLanguage === 'english' ? '' : `Respond in ${detectedLanguage} language. `;
const prompt = `You are an expert SEO consultant specializing in high-performing search keywords and keyphrases that people actually search for on Google.
${languageInstruction}Based on these seed keywords: "${keywords}"
Generate 8-12 SEO-optimized keywords and keyphrases that:
1. Are commonly searched terms people actually use on Google
2. Include both short keywords (1-2 words) and longer keyphrases (3-5 words)
3. Focus on commercial intent and local SEO when relevant
4. Include location-specific terms if a location is mentioned
5. Consider trending and high-volume search terms
6. Think like a customer searching for these services/products
Examples of good SEO keyphrases:
- "african barbershop amsterdam"
- "professional hair cutting services"
- "luxury wedding photography"
- "modern interior design ideas"
- "organic food delivery service"
Return only the keywords and keyphrases separated by commas, no explanations or extra text.
Seed 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 Grok-2-Vision AI
async function analyzeImageWithVisionAI(imageSrc) {
const prompt = "Analyze this image and provide exactly 2 descriptive keywords that describe the main subject, object, or action in the image. Focus on what would be useful for SEO image naming. Return only the two keywords separated by a space, nothing else.";
const response = await fetch(AI_CONFIG.GROK_API_URL, {
method: 'POST',
headers: {
'Authorization': `Bearer ${AI_CONFIG.GROK_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: AI_CONFIG.GROK_VISION_MODEL,
messages: [
{
role: "user",
content: [
{
type: "text",
text: prompt
},
{
type: "image_url",
image_url: {
url: imageSrc
}
}
]
}
],
max_tokens: 50,
temperature: 0.3
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('Grok Vision API Error Response:', errorText);
throw new Error(`Grok Vision API request failed with status ${response.status}: ${errorText}`);
}
const data = await response.json();
const visionResponse = data.choices[0].message.content.trim();
// Clean up response and extract keywords
const keywords = visionResponse.split(' ').filter(word =>
word.length > 2 &&
!word.includes('.') &&
!word.includes(',') &&
/^[a-zA-Z]+$/.test(word)
).slice(0, 2);
return keywords.length >= 2 ? keywords : ['image', 'content']; // Fallback if extraction fails
}
// Generate unique filename
async function generateUniqueFilename(image, visionKeywords, usedNames) {
const detectedLanguage = detectLanguage(keywords.join(' '));
const languageInstruction = detectedLanguage === 'english' ? '' : `Generate the filename in ${detectedLanguage} language. `;
// Ensure we use ALL keywords, not just first 5
const keywordString = keywords.join(', ');
const visionString = visionKeywords.join(' ');
const prompt = `You are an SEO filename expert. Create an SEO-optimized filename for this image that will rank well in search engines.
${languageInstruction}REQUIREMENTS:
1. MUST include at least 2-3 of these user keywords: ${keywordString}
2. MUST incorporate image content: ${visionString}
3. Create natural, searchable phrases people actually type into Google
4. Use 4-8 words maximum
5. Focus on commercial intent and local SEO when applicable
6. Think like someone searching for this image online
EXAMPLES of good SEO filenames:
- "professional african barbershop amsterdam"
- "luxury wedding photography netherlands"
- "modern restaurant interior design"
- "organic food delivery service menu"
- "vintage car classic automobile show"
Original filename reference: ${image.name}
Create ONE natural filename that combines the keywords strategically for maximum SEO impact.
Return only the filename with spaces between words, no punctuation, no file extension.`;
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) => {
// Use more keywords for better SEO - cycle through all keywords
const keywordCount = Math.min(keywords.length, 4); // Use up to 4 keywords
const startIndex = index % Math.max(1, keywords.length - 2); // Rotate starting point
const selectedKeywords = keywords.slice(startIndex, startIndex + keywordCount);
const keywordString = selectedKeywords.join(' ');
const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.'));
const extension = image.name.substring(image.name.lastIndexOf('.'));
// Create SEO-focused name - prioritize keywords over original filename
let newName = keywordString.length > 0 ? keywordString : nameWithoutExt;
newName = newName.substring(0, 45); // Leave room for counter
// 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) => {
// Use more keywords for better SEO - cycle through all keywords
const keywordCount = Math.min(keywords.length, 4); // Use up to 4 keywords
const startIndex = index % Math.max(1, keywords.length - 2); // Rotate starting point
const selectedKeywords = keywords.slice(startIndex, startIndex + keywordCount);
const keywordString = selectedKeywords.join(' ');
const nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.'));
const extension = image.name.substring(image.name.lastIndexOf('.'));
// Create SEO-focused name - prioritize keywords over original filename
let newName = keywordString.length > 0 ? keywordString : nameWithoutExt;
newName = newName.substring(0, 45); // Leave room for counter
// 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() {
// Initialize configuration first
initializeConfig();
// Set up any initial state
downloadBtn.disabled = true;
}
// Call init when page loads
init();