Final front-end version for day 1 by Jeen.
This commit is contained in:
parent
4b82d495b2
commit
a829770c7e
3 changed files with 1173 additions and 412 deletions
140
index.html
140
index.html
|
@ -11,7 +11,7 @@
|
|||
<header>
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<h1>SEO Image Renamer</h1>
|
||||
<h1><i class="fas fa-image"></i> SEO Image Renamer</h1>
|
||||
</div>
|
||||
<nav>
|
||||
<ul>
|
||||
|
@ -21,47 +21,87 @@
|
|||
<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">
|
||||
<h1>Boost Your Website's SEO with AI-Powered Image Naming</h1>
|
||||
<p>Stop manually renaming images for SEO. Our AI tool automatically generates SEO-friendly filenames based on your keywords and image content.</p>
|
||||
<a href="#upload-section" class="btn btn-large btn-primary">Try It Free</a>
|
||||
<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 class="hero-image">
|
||||
<img src="https://images.unsplash.com/photo-1558655146-d09347e92766?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=600&h=400&q=80" alt="SEO Image Renamer Dashboard">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="upload-section" class="upload-section">
|
||||
<section id="workflow-section" class="workflow-section" style="display: none;">
|
||||
<div class="container">
|
||||
<h2>Rename Your Images with AI</h2>
|
||||
<p class="section-description">Upload your images, provide keywords, and let our AI generate SEO-optimized filenames.</p>
|
||||
|
||||
<div class="upload-container">
|
||||
<div id="drop-area" class="drop-area">
|
||||
<div class="drop-area-content">
|
||||
<i class="fas fa-cloud-upload-alt fa-3x"></i>
|
||||
<h3>Drag & Drop your images here</h3>
|
||||
<p>or</p>
|
||||
<button id="browse-btn" class="btn btn-secondary">Browse Files</button>
|
||||
<input type="file" id="file-input" accept="image/*" multiple style="display: none;">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div id="keywords-section" class="keywords-section" style="display: none;">
|
||||
<h3>Enter Keywords for Your Images</h3>
|
||||
<p>Provide keywords that describe your images to help our AI generate better filenames.</p>
|
||||
|
||||
<div class="keywords-input">
|
||||
<input type="text" id="keyword-input" placeholder="Enter keywords (e.g., beach vacation, summer party)">
|
||||
<button id="enhance-btn" class="btn btn-secondary" disabled>
|
||||
<button id="enhance-btn" class="btn btn-primary" disabled>
|
||||
<i class="fas fa-magic"></i> Enhance with AI
|
||||
</button>
|
||||
</div>
|
||||
|
@ -70,48 +110,66 @@
|
|||
<!-- 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-preview" class="images-preview" style="display: none;">
|
||||
<h3>Your Images</h3>
|
||||
<div id="images-container" class="images-container">
|
||||
<!-- Images will be displayed here -->
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="download-btn" class="btn btn-primary btn-large" disabled>
|
||||
<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 class="section-description">Our AI-powered tool helps you optimize your images for search engines without the manual work.</p>
|
||||
<p>Everything you need to optimize your images for search engines</p>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-robot fa-2x"></i>
|
||||
<div class="feature-icon">
|
||||
<i class="fas fa-robot"></i>
|
||||
</div>
|
||||
<h3>AI-Powered Naming</h3>
|
||||
<p>Our advanced AI generates SEO-friendly filenames that help your images rank higher in search results.</p>
|
||||
<p>Advanced AI generates SEO-friendly filenames that help your images rank higher in search results.</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<i class="fas fa-eye fa-2x"></i>
|
||||
<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">
|
||||
<i class="fas fa-key fa-2x"></i>
|
||||
<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">
|
||||
<i class="fas fa-file-archive fa-2x"></i>
|
||||
<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>
|
||||
|
@ -121,8 +179,10 @@
|
|||
|
||||
<section id="how-it-works" class="how-it-works">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>How It Works</h2>
|
||||
<p class="section-description">Get better SEO for your images in just three simple steps.</p>
|
||||
<p>Get better SEO for your images in just three simple steps</p>
|
||||
</div>
|
||||
|
||||
<div class="steps">
|
||||
<div class="step">
|
||||
|
@ -148,8 +208,10 @@
|
|||
|
||||
<section id="pricing" class="pricing">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<h2>Simple, Transparent Pricing</h2>
|
||||
<p class="section-description">Choose the plan that works best for you.</p>
|
||||
<p>Choose the plan that works best for you</p>
|
||||
</div>
|
||||
|
||||
<div class="pricing-grid">
|
||||
<div class="pricing-card">
|
||||
|
@ -161,7 +223,7 @@
|
|||
<li>Keyword enhancement</li>
|
||||
<li>ZIP download</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary">Get Started</button>
|
||||
<button class="btn btn-outline">Get Started</button>
|
||||
</div>
|
||||
|
||||
<div class="pricing-card featured">
|
||||
|
@ -189,7 +251,7 @@
|
|||
<li>Priority support</li>
|
||||
<li>Advanced analytics</li>
|
||||
</ul>
|
||||
<button class="btn btn-secondary">Get Started</button>
|
||||
<button class="btn btn-outline">Get Started</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -200,7 +262,7 @@
|
|||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-logo">
|
||||
<h2>SEO Image Renamer</h2>
|
||||
<h2><i class="fas fa-image"></i> SEO Image Renamer</h2>
|
||||
<p>AI-powered image SEO optimization</p>
|
||||
</div>
|
||||
|
||||
|
|
259
script.js
259
script.js
|
@ -7,6 +7,7 @@ let generatedNames = [];
|
|||
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');
|
||||
|
@ -15,10 +16,11 @@ const imagesPreview = document.getElementById('images-preview');
|
|||
const imagesContainer = document.getElementById('images-container');
|
||||
const downloadBtn = document.getElementById('download-btn');
|
||||
|
||||
// AI Configuration (in a real app, this would be loaded from .env)
|
||||
// AI Configuration
|
||||
const AI_CONFIG = {
|
||||
API_KEY: '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',
|
||||
MODEL_NAME: 'deepseek/deepseek-chat-v3-0324:free',
|
||||
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'
|
||||
};
|
||||
|
||||
|
@ -110,15 +112,23 @@ function handleFiles(files) {
|
|||
size: file.size,
|
||||
type: file.type,
|
||||
src: e.target.result,
|
||||
newName: generateFileName(file.name)
|
||||
newName: generateFileName(file.name),
|
||||
visionKeywords: []
|
||||
});
|
||||
|
||||
processedCount++;
|
||||
// Show keywords section after all files are processed
|
||||
// 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);
|
||||
|
@ -169,11 +179,14 @@ async function enhanceKeywords() {
|
|||
// Clear input
|
||||
keywordInput.value = '';
|
||||
|
||||
// Generate new filenames for images
|
||||
await generateNewFileNamesWithAI();
|
||||
// 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. Please try again.');
|
||||
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';
|
||||
|
@ -181,6 +194,37 @@ async function enhanceKeywords() {
|
|||
}
|
||||
}
|
||||
|
||||
// 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}`;
|
||||
|
@ -189,10 +233,12 @@ async function callAIKeywordEnhancement(keywords) {
|
|||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${AI_CONFIG.API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'HTTP-Referer': window.location.href,
|
||||
'X-Title': 'SEO Image Renamer'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: AI_CONFIG.MODEL_NAME,
|
||||
model: AI_CONFIG.DEEPSEEK_MODEL,
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
|
@ -203,37 +249,50 @@ async function callAIKeywordEnhancement(keywords) {
|
|||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed with status ${response.status}`);
|
||||
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();
|
||||
}
|
||||
|
||||
// Generate new filenames for images based on keywords using AI
|
||||
async function generateNewFileNamesWithAI() {
|
||||
// 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 = 'Generating AI filename...';
|
||||
input.placeholder = 'Analyzing image and generating filename...';
|
||||
});
|
||||
|
||||
try {
|
||||
// Generate new filename for each image
|
||||
// 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];
|
||||
const keywordString = keywords.slice(0, 5).join(', ');
|
||||
|
||||
// Call AI to generate a descriptive filename
|
||||
const aiGeneratedName = await callAIFilenameGeneration(image.name, keywordString);
|
||||
// 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 nameWithoutExt = image.name.substring(0, image.name.lastIndexOf('.'));
|
||||
const extension = image.name.substring(image.name.lastIndexOf('.'));
|
||||
const newName = `${aiGeneratedName.substring(0, 50)}${extension}`;
|
||||
image.newName = newName;
|
||||
image.newName = `${newName.substring(0, 50)}${extension}`;
|
||||
}
|
||||
|
||||
// Update images preview
|
||||
|
@ -242,11 +301,11 @@ async function generateNewFileNamesWithAI() {
|
|||
// Enable download button
|
||||
downloadBtn.disabled = false;
|
||||
} catch (error) {
|
||||
console.error('Error generating filenames:', error);
|
||||
alert('An error occurred while generating filenames. Please try again.');
|
||||
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
|
||||
generateNewFileNames();
|
||||
fallbackToSimpleNaming();
|
||||
} finally {
|
||||
// Re-enable inputs
|
||||
document.querySelectorAll('.new-name-input').forEach(input => {
|
||||
|
@ -256,18 +315,76 @@ async function generateNewFileNamesWithAI() {
|
|||
}
|
||||
}
|
||||
|
||||
// Call AI API to generate filename
|
||||
async function callAIFilenameGeneration(originalName, keywords) {
|
||||
const prompt = `Generate an SEO-optimized filename for an image. The original filename is "${originalName}" and the keywords are: ${keywords}. Create a descriptive, SEO-friendly filename that is 3-6 words long. Use only letters, numbers, and hyphens. Do not include the file extension. Return only the filename, nothing else.`;
|
||||
// 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'
|
||||
'Content-Type': 'application/json',
|
||||
'HTTP-Referer': window.location.href,
|
||||
'X-Title': 'SEO Image Renamer'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: AI_CONFIG.MODEL_NAME,
|
||||
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",
|
||||
|
@ -278,17 +395,21 @@ async function callAIFilenameGeneration(originalName, keywords) {
|
|||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed with status ${response.status}`);
|
||||
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().replace(/[^a-zA-Z0-9\- ]/g, '').replace(/\s+/g, '-');
|
||||
return data.choices[0].message.content.trim();
|
||||
}
|
||||
|
||||
// Fallback function to generate new filenames without AI
|
||||
function generateNewFileNames() {
|
||||
// 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(' ');
|
||||
|
@ -296,8 +417,18 @@ function generateNewFileNames() {
|
|||
const extension = image.name.substring(image.name.lastIndexOf('.'));
|
||||
|
||||
// Create new name
|
||||
const newName = `${keywordString} ${nameWithoutExt}`.substring(0, 50) + extension;
|
||||
image.newName = newName;
|
||||
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
|
||||
|
@ -307,6 +438,57 @@ function generateNewFileNames() {
|
|||
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 = '';
|
||||
|
@ -339,12 +521,21 @@ function updateImagesPreview() {
|
|||
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>
|
||||
|
|
1144
styles.css
1144
styles.css
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue