Front end with upload and vision features working (open ai r1 and grok vision with void ai

This commit is contained in:
Jeen Koster 2025-08-05 21:46:06 +02:00
parent a829770c7e
commit d9226251e9
5 changed files with 283 additions and 50 deletions

213
script.js
View file

@ -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 = '<i class="fas fa-spinner fa-spin"></i> Enhancing...';
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);
// 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 = '<i class="fas fa-magic"></i> Enhance with AI';
enhanceBtn.innerHTML = '<i class="fas fa-magic"></i> 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;
}