Compare commits

...
Sign in to create a new pull request.

3 commits

6 changed files with 2370 additions and 0 deletions

33
.env Normal file
View file

@ -0,0 +1,33 @@
# === AI CONFIGURATION ===
# OpenRouter API key for text generation (DeepSeek)
AI_API_KEY=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
# DeepSeek model for text/keyword generation
AI_MODEL_NAME=deepseek/deepseek-chat-v3-0324:free
# OpenRouter API URL
AI_API_URL=https://openrouter.ai/api/v1/chat/completions
# === GROK VISION API CONFIGURATION ===
# Grok API key for vision analysis (add your Grok API key here)
GROK_API_KEY=sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow
# Grok-2-Vision model
GROK_VISION_MODEL=grok-2-vision-1212
# Grok API URL
GROK_API_URL=https://api.voidai.app/v1/
# Optional: AI task-specific configuration
AI_TASK=keyword_generation
AI_RENAME_STRATEGY=descriptive # Options: 'timestamped', 'uuid', 'descriptive'
# === GENERAL SETTINGS ===
# Environment type
NODE_ENV=development
# Optional: Logging or debugging
ENABLE_LOGGING=true

75
GROK_SETUP.md Normal file
View file

@ -0,0 +1,75 @@
# Grok-2-Vision Integration Setup
## How to Configure Your Grok API Key
The SEO Image Renamer now supports Grok-2-Vision for enhanced image analysis. Follow these steps to set up your API key:
### Step 1: Get Your Grok API Key
1. Visit [https://console.x.ai/](https://console.x.ai/)
2. Sign up or log in to your account
3. Navigate to API Keys section
4. Create a new API key for your project
5. Copy the generated API key
### Step 2: Update the .env File
1. Open the `.env` file in your project directory
2. Find the line that says: `GROK_API_KEY=your_grok_api_key_here`
3. Replace `your_grok_api_key_here` with your actual Grok API key
4. Save the file
Example:
```env
GROK_API_KEY=gsk-proj-abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567
```
### Step 3: Test the Integration
1. Open your SEO Image Renamer website
2. Upload some images
3. Add keywords and click "Generate SEO Keywords"
4. Check the browser console for the message: "Grok API Key configured: Yes"
## What's New with Grok-2-Vision
### Enhanced Image Analysis
- **Better Keyword Extraction**: Grok-2-Vision provides more accurate and contextual keywords from your images
- **SEO-Focused Analysis**: The AI is specifically prompted to identify keywords useful for SEO image naming
- **Improved Accuracy**: Better object and action recognition compared to previous vision models
### Technical Improvements
- **Dedicated Vision API**: Separate API endpoint optimized for vision tasks
- **Enhanced Prompts**: Specialized prompts for extracting SEO-relevant keywords
- **Better Error Handling**: Improved fallback mechanisms if vision analysis fails
- **Cleaner Responses**: Better parsing and filtering of AI responses
## Troubleshooting
### Common Issues
**Issue**: "Grok API Key configured: No" in console
**Solution**: Make sure you've replaced `sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow` with your actual API key in the .env file
**Issue**: Vision analysis fails or returns generic keywords
**Solution**:
1. Check that your API key is valid and has sufficient credits
2. Ensure your API key has access to Grok-2-Vision model
3. Check browser console for specific error messages
**Issue**: .env file not loading
**Solution**:
1. Make sure the .env file is in the same directory as index.html
2. Ensure your web server allows access to .env files (may need to serve from a local server)
3. For production, you may need to hardcode the API key in the JavaScript file
### API Limits
- Grok-2-Vision has rate limits and usage quotas
- If you hit limits, the system will fallback to generic keywords
- Monitor your usage in the X.AI console
## Security Note
Keep your API key secure and never commit it to public repositories. The .env file should be added to your .gitignore file.
## Support
If you encounter issues with the Grok-2-Vision integration, check:
1. X.AI API documentation: [https://docs.x.ai/](https://docs.x.ai/)
2. Your API key permissions and quotas
3. Browser console for detailed error messages

20
config.js Normal file
View file

@ -0,0 +1,20 @@
// Configuration file for SEO Image Renamer
// Update your API keys here
window.APP_CONFIG = {
// OpenRouter API for text generation (DeepSeek) - already configured
OPENROUTER_API_KEY: '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',
DEEPSEEK_MODEL: 'deepseek/deepseek-chat-v3-0324:free',
OPENROUTER_API_URL: 'https://openrouter.ai/api/v1/chat/completions',
// Grok-2-Vision API - ADD YOUR GROK API KEY HERE
GROK_API_KEY: 'sk-voidai-vVU2HHiq1txTNmXdZOP98LzARmi4HsptTixMqFSX4yBbw8ogvmKlJEPeKrH1hwEd6j6AnED9LsR6ztPtRMT7UzeLOyxQkasbwKow'
GROK_VISION_MODEL: 'grok-2-vision-1212',
GROK_API_URL: 'https://api.x.ai/v1/chat/completions'
};
// Instructions:
// 1. Get your Grok API key from https://console.x.ai/
// 2. Replace 'your_grok_api_key_here' above with your actual API key
// 3. Save this file
// 4. The website will automatically use your new API key

310
index.html Normal file
View file

@ -0,0 +1,310 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEO Image Renamer - AI-Powered Image SEO Tool</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<header>
<div class="container">
<div class="logo">
<h1><i class="fas fa-image"></i> SEO Image Renamer</h1>
</div>
<nav>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#how-it-works">How It Works</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#" class="btn btn-primary">Sign In</a></li>
</ul>
</nav>
<div class="mobile-menu">
<i class="fas fa-bars"></i>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<div class="hero-grid">
<div class="hero-content">
<div class="hero-badge">
<i class="fas fa-bolt"></i>
<span>AI-Powered</span>
</div>
<h1>Save time! Bulk rename your images individually for better SEO performance</h1>
<p>Transform your image SEO workflow with AI that analyzes content and generates perfect filenames automatically. No more manual renaming - just upload, enhance, and download.</p>
<div class="hero-features">
<div class="mini-feature">
<i class="fas fa-eye"></i>
<span>AI Vision Analysis</span>
</div>
<div class="mini-feature">
<i class="fas fa-magic"></i>
<span>Smart Keyword Enhancement</span>
</div>
<div class="mini-feature">
<i class="fas fa-download"></i>
<span>Instant ZIP Download</span>
</div>
</div>
<div class="hero-stats">
<div class="stat">
<span class="stat-number">10k+</span>
<span class="stat-label">Images Processed</span>
</div>
<div class="stat">
<span class="stat-number">95%</span>
<span class="stat-label">Time Saved</span>
</div>
</div>
</div>
<div class="hero-upload">
<div id="drop-area" class="drop-area">
<div class="drop-area-content">
<div class="upload-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<h3>Drop your images here</h3>
<p>or click to browse files</p>
<button id="browse-btn" class="upload-btn">
<i class="fas fa-folder-open"></i>
<span>Choose Files</span>
</button>
<input type="file" id="file-input" accept="image/*" multiple style="display: none;">
<div class="supported-formats">
<span>Supports: JPG, PNG, WEBP, GIF</span>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="workflow-section" class="workflow-section" style="display: none;">
<div class="container">
<div id="keywords-section" class="keywords-section">
<div class="workflow-step">
<div class="step-header">
<i class="fas fa-tags"></i>
<h3>Step 1: Add Your Keywords</h3>
<p>Help our AI understand your content better</p>
</div>
<div class="keywords-input">
<input type="text" id="keyword-input" placeholder="Enter keywords or phrases (e.g., african barbershop amsterdam, professional wedding photography)">
<button id="enhance-btn" class="btn btn-primary" disabled>
<i class="fas fa-magic"></i> Generate SEO Keywords
</button>
</div>
<div id="keywords-display" class="keywords-display">
<!-- Keywords will be displayed here -->
</div>
</div>
</div>
<div id="images-preview" class="images-preview">
<div class="workflow-step">
<div class="step-header">
<i class="fas fa-images"></i>
<h3>Step 2: Review & Download</h3>
<p>Your AI-generated filenames are ready</p>
</div>
<div id="images-container" class="images-container">
<!-- Images will be displayed here -->
</div>
<div class="actions">
<button id="download-btn" class="btn btn-success btn-large" disabled>
<i class="fas fa-download"></i> Download Renamed Images as ZIP
</button>
</div>
</div>
</div>
</div>
</section>
<section id="features" class="features">
<div class="container">
<div class="section-header">
<h2>Powerful Features for Better SEO</h2>
<p>Everything you need to optimize your images for search engines</p>
</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-robot"></i>
</div>
<h3>AI-Powered Naming</h3>
<p>Advanced AI generates SEO-friendly filenames that help your images rank higher in search results.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-eye"></i>
</div>
<h3>Image Recognition</h3>
<p>AI analyzes your images to understand content and context for more accurate naming.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-key"></i>
</div>
<h3>Keyword Enhancement</h3>
<p>Enhance your keywords with AI-suggested synonyms for better SEO performance.</p>
</div>
<div class="feature-card">
<div class="feature-icon">
<i class="fas fa-file-archive"></i>
</div>
<h3>Easy Download</h3>
<p>Download all your renamed images in a single ZIP file for easy implementation.</p>
</div>
</div>
</div>
</section>
<section id="how-it-works" class="how-it-works">
<div class="container">
<div class="section-header">
<h2>How It Works</h2>
<p>Get better SEO for your images in just three simple steps</p>
</div>
<div class="steps">
<div class="step">
<div class="step-number">1</div>
<h3>Upload Images</h3>
<p>Drag and drop your images or browse your files to upload them to our platform.</p>
</div>
<div class="step">
<div class="step-number">2</div>
<h3>Add Keywords</h3>
<p>Provide keywords that describe your images, or let our AI enhance them for better SEO.</p>
</div>
<div class="step">
<div class="step-number">3</div>
<h3>Download & Implement</h3>
<p>Download your renamed images as a ZIP file and use them on your website.</p>
</div>
</div>
</div>
</section>
<section id="pricing" class="pricing">
<div class="container">
<div class="section-header">
<h2>Simple, Transparent Pricing</h2>
<p>Choose the plan that works best for you</p>
</div>
<div class="pricing-grid">
<div class="pricing-card">
<h3>Basic</h3>
<div class="price">$0<span>/month</span></div>
<ul>
<li>50 images per month</li>
<li>AI-powered naming</li>
<li>Keyword enhancement</li>
<li>ZIP download</li>
</ul>
<button class="btn btn-outline">Get Started</button>
</div>
<div class="pricing-card featured">
<div class="featured-badge">Most Popular</div>
<h3>Pro</h3>
<div class="price">$9<span>/month</span></div>
<ul>
<li>500 images per month</li>
<li>AI-powered naming</li>
<li>Keyword enhancement</li>
<li>ZIP download</li>
<li>Priority support</li>
</ul>
<button class="btn btn-primary">Get Started</button>
</div>
<div class="pricing-card">
<h3>Max</h3>
<div class="price">$19<span>/month</span></div>
<ul>
<li>1000 images per month</li>
<li>AI-powered naming</li>
<li>Keyword enhancement</li>
<li>ZIP download</li>
<li>Priority support</li>
<li>Advanced analytics</li>
</ul>
<button class="btn btn-outline">Get Started</button>
</div>
</div>
</div>
</section>
</main>
<footer>
<div class="container">
<div class="footer-content">
<div class="footer-logo">
<h2><i class="fas fa-image"></i> SEO Image Renamer</h2>
<p>AI-powered image SEO optimization</p>
</div>
<div class="footer-links">
<div class="footer-column">
<h4>Product</h4>
<ul>
<li><a href="#features">Features</a></li>
<li><a href="#how-it-works">How It Works</a></li>
<li><a href="#pricing">Pricing</a></li>
</ul>
</div>
<div class="footer-column">
<h4>Company</h4>
<ul>
<li><a href="#">About Us</a></li>
<li><a href="#">Blog</a></li>
<li><a href="#">Contact</a></li>
</ul>
</div>
<div class="footer-column">
<h4>Legal</h4>
<ul>
<li><a href="#">Privacy Policy</a></li>
<li><a href="#">Terms of Service</a></li>
</ul>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; 2025 SEO Image Renamer. All rights reserved.</p>
</div>
</div>
</footer>
<!-- Include JSZip library for ZIP functionality -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<!-- Include configuration file -->
<script src="config.js"></script>
<script src="script.js"></script>
</body>
</html>

736
script.js Normal file
View file

@ -0,0 +1,736 @@
// 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();

1196
styles.css Normal file

File diff suppressed because it is too large Load diff