From d9226251e93846e933c767addb49c74376650697 Mon Sep 17 00:00:00 2001 From: Jeen Koster Date: Tue, 5 Aug 2025 21:46:06 +0200 Subject: [PATCH] Front end with upload and vision features working (open ai r1 and grok vision with void ai --- .env | 19 ++++- GROK_SETUP.md | 75 ++++++++++++++++++ config.js | 20 +++++ index.html | 6 +- script.js | 213 +++++++++++++++++++++++++++++++++++++++----------- 5 files changed, 283 insertions(+), 50 deletions(-) create mode 100644 GROK_SETUP.md create mode 100644 config.js diff --git a/.env b/.env index fb31641..5390d16 100644 --- a/.env +++ b/.env @@ -1,13 +1,24 @@ # === AI CONFIGURATION === -# Your API key or access token +# OpenRouter API key for text generation (DeepSeek) AI_API_KEY=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 -# Name or ID of the AI model +# DeepSeek model for text/keyword generation AI_MODEL_NAME=deepseek/deepseek-chat-v3-0324:free -# Endpoint or base URL for DeepSeek API (update if different) -AI_API_URL=https://api.deepseek.com/v1 +# 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 diff --git a/GROK_SETUP.md b/GROK_SETUP.md new file mode 100644 index 0000000..0653e14 --- /dev/null +++ b/GROK_SETUP.md @@ -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 \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..00ed80b --- /dev/null +++ b/config.js @@ -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 \ No newline at end of file diff --git a/index.html b/index.html index 5e2c585..98d91cd 100644 --- a/index.html +++ b/index.html @@ -100,9 +100,9 @@
- +
@@ -303,6 +303,8 @@ + + \ No newline at end of file diff --git a/script.js b/script.js index 5a9b9ef..54f1a17 100644 --- a/script.js +++ b/script.js @@ -16,13 +16,46 @@ 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' -}; +// 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', () => { @@ -156,19 +189,27 @@ async function enhanceKeywords() { if (keywordText === '') return; // Show loading state - enhanceBtn.innerHTML = ' Enhancing...'; + enhanceBtn.innerHTML = ' Generating SEO Keywords...'; 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 !== ''); + // Better handling for keyphrases - split by comma primarily, preserve phrases + const newKeywords = enhancedKeywords.split(',').map(k => k.trim()).filter(k => k !== ''); - // Add new keywords to the list + // Add new keywords/keyphrases to the list newKeywords.forEach(keyword => { - if (!keywords.includes(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); } }); @@ -189,7 +230,7 @@ async function enhanceKeywords() { fallbackToSimpleEnhancement(keywordText); } finally { // Reset button - enhanceBtn.innerHTML = ' Enhance with AI'; + enhanceBtn.innerHTML = ' Generate SEO Keywords'; enhanceBtn.disabled = keywordInput.value.trim() === ''; } } @@ -225,9 +266,55 @@ function fallbackToSimpleEnhancement(keywordText) { 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 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 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', @@ -315,20 +402,18 @@ async function analyzeImagesAndGenerateNames() { } } -// Analyze image with vision AI +// Analyze image with Grok-2-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 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.API_URL, { + const response = await fetch(AI_CONFIG.GROK_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' + 'Authorization': `Bearer ${AI_CONFIG.GROK_API_KEY}`, + 'Content-Type': 'application/json' }, body: JSON.stringify({ - model: AI_CONFIG.VISION_MODEL, + model: AI_CONFIG.GROK_VISION_MODEL, messages: [ { role: "user", @@ -345,35 +430,62 @@ async function analyzeImageWithVisionAI(imageSrc) { } ] } - ] + ], + max_tokens: 50, + temperature: 0.3 }) }); 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}`); + 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(); - return visionResponse.split(' ').slice(0, 2); // Get up to 2 keywords + + // 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 keywordString = keywords.slice(0, 5).join(', '); + 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 = `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 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', @@ -411,13 +523,18 @@ function fallbackToSimpleNaming() { const usedNames = new Set(); uploadedImages.forEach((image, index) => { - // Combine keywords with the original filename - const keywordString = keywords.slice(0, 3).join(' '); + // 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 new name - let newName = `${keywordString} ${nameWithoutExt}`.substring(0, 50); + // 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; @@ -445,13 +562,18 @@ function generateNewFileNames() { const usedNames = new Set(); uploadedImages.forEach((image, index) => { - // Combine keywords with the original filename - const keywordString = keywords.slice(0, 3).join(' '); + // 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 new name - let newName = `${keywordString} ${nameWithoutExt}`.substring(0, 50); + // 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; @@ -603,6 +725,9 @@ async function downloadImages() { // Initialize the page function init() { + // Initialize configuration first + initializeConfig(); + // Set up any initial state downloadBtn.disabled = true; }