← back to Handbag Authentication
public/js/camera.js
219 lines
const API_BASE = window.location.origin;
let stream = null;
const video = document.getElementById('camera-view');
const canvas = document.getElementById('canvas');
const startBtn = document.getElementById('start-camera');
const captureBtn = document.getElementById('capture-photo');
const uploadBtn = document.getElementById('upload-photo');
const fileInput = document.getElementById('file-input');
const resultDiv = document.getElementById('analysis-result');
// Start camera
startBtn.addEventListener('click', async () => {
try {
// Try with basic constraints first
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: false
});
video.srcObject = stream;
video.style.display = 'block';
startBtn.style.display = 'none';
captureBtn.style.display = 'inline-block';
} catch (error) {
console.error('Camera error:', error);
// Show detailed error message
let errorMsg = '❌ Camera Access Issue\n\n';
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
errorMsg += '📸 Camera permission was blocked.\n\n';
errorMsg += '✅ To fix:\n';
errorMsg += '1. Click the camera icon in your browser address bar\n';
errorMsg += '2. Select "Allow" for camera access\n';
errorMsg += '3. Refresh the page and try again\n\n';
errorMsg += '📁 Or use "Upload Photo" button below instead!';
} else if (error.name === 'NotFoundError') {
errorMsg += '📷 No camera found on this device.\n\n';
errorMsg += '📁 Please use the "Upload Photo" button below instead!';
} else if (error.name === 'NotReadableError') {
errorMsg += '⚠️ Camera is already in use by another app.\n\n';
errorMsg += '✅ To fix:\n';
errorMsg += '1. Close other apps using the camera\n';
errorMsg += '2. Try again\n\n';
errorMsg += '📁 Or use "Upload Photo" button below instead!';
} else {
errorMsg += 'Error: ' + error.message + '\n\n';
errorMsg += '📁 Please use the "Upload Photo" button below instead!';
}
alert(errorMsg);
// Highlight upload button
uploadBtn.style.animation = 'pulse 1s ease-in-out 3';
uploadBtn.style.transform = 'scale(1.1)';
setTimeout(() => {
uploadBtn.style.transform = 'scale(1)';
}, 3000);
}
});
// Capture photo
captureBtn.addEventListener('click', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0);
canvas.toBlob(blob => {
analyzeImage(blob);
}, 'image/jpeg', 0.95);
});
// Upload photo
uploadBtn.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
if (files.length > 0) {
analyzeMultipleFiles(files);
}
});
async function analyzeMultipleFiles(files) {
const images = files.filter(f => f.type.startsWith('image/')).slice(0, 5);
const videos = files.filter(f => f.type.startsWith('video/')).slice(0, 1);
if (images.length === 0 && videos.length === 0) {
alert('Please select at least one image or video!');
return;
}
resultDiv.style.display = 'block';
resultDiv.innerHTML = `<div class="analyzing">🔍 Analyzing ${images.length} image(s) ${videos.length > 0 ? '+ 1 video' : ''} with Google Gemini AI Vision...<br>Detecting brand, model, condition, authenticity markers...</div>`;
try {
const fileDataPromises = [...images, ...videos].map(file => {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = () => resolve({
data: reader.result,
type: file.type
});
});
});
const filesData = await Promise.all(fileDataPromises);
// Send to API
const response = await fetch(`${API_BASE}/api/analyze-image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
files: filesData
})
});
const result = await response.json();
displayResults(result);
} catch (error) {
console.error('Analysis error:', error);
resultDiv.innerHTML = '<div style="color: red; padding: 2rem;">❌ Analysis failed. Please try again.</div>';
}
}
async function analyzeImage(imageBlob) {
resultDiv.style.display = 'block';
resultDiv.innerHTML = '<div class="analyzing">🔍 Analyzing image with AI Vision...<br>Detecting brand, model, condition, and value...</div>';
try {
// Convert blob to base64
const reader = new FileReader();
reader.readAsDataURL(imageBlob);
reader.onloadend = async () => {
const base64Image = reader.result;
// Send to API
const response = await fetch(`${API_BASE}/api/analyze-image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
image: base64Image
})
});
const result = await response.json();
displayResults(result);
};
} catch (error) {
console.error('Analysis error:', error);
resultDiv.innerHTML = '<div style="color: red; padding: 2rem;">❌ Analysis failed. Please try again.</div>';
}
}
function displayResults(data) {
if (!data.success) {
resultDiv.innerHTML = `<div style="color: red; padding: 2rem;">❌ ${data.error}</div>`;
return;
}
const analysis = data.analysis;
const filesAnalyzed = data.files_analyzed || 1;
resultDiv.innerHTML = `
<h2 style="margin-top: 0;">✅ Analysis Complete (${filesAnalyzed} file${filesAnalyzed > 1 ? 's' : ''} analyzed)</h2>
<div class="result-section">
<h3>👜 Identified Product</h3>
<p><strong>Brand:</strong> ${analysis.brand || 'Unknown'}</p>
<p><strong>Model:</strong> ${analysis.model || 'Unknown'}</p>
<p><strong>Style:</strong> ${analysis.style || 'N/A'}</p>
<p><strong>Material:</strong> ${analysis.material || 'Unknown'}</p>
<p><strong>Color:</strong> ${analysis.color || 'Unknown'}</p>
<p><strong>Hardware:</strong> ${analysis.hardware || 'N/A'}</p>
</div>
<div class="result-section" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
<h3 style="color: white;">🔍 Condition & Authentication</h3>
<p><strong>Overall Condition:</strong> ${analysis.condition || 'Unknown'}</p>
<p><strong>Hardware Condition:</strong> ${analysis.hardware_condition || 'Good'}</p>
<p><strong>Authenticity Confidence:</strong> ${analysis.authenticity_confidence || 'N/A'}</p>
<p><strong>Notable Issues:</strong> ${analysis.issues || 'None detected'}</p>
${analysis.authenticity_markers ? `<p><strong>✅ Authentication Markers:</strong> ${analysis.authenticity_markers}</p>` : ''}
${analysis.red_flags ? `<p style="background: rgba(231, 76, 60, 0.2); padding: 0.5rem; border-radius: 5px; margin-top: 0.5rem;"><strong>🚨 Red Flags:</strong> ${analysis.red_flags}</p>` : ''}
</div>
<div class="result-section" style="background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white;">
<h3 style="color: white;">💰 Value Estimate</h3>
<p style="font-size: 2rem; font-weight: 700; margin: 1rem 0;">
$${analysis.estimated_value_low || '?'} - $${analysis.estimated_value_high || '?'}
</p>
<p><strong>Retail MSRP:</strong> ${analysis.retail_msrp ? '$' + analysis.retail_msrp : 'N/A'}</p>
<p><strong>Current Market Value:</strong> ${analysis.current_market_value ? '$' + analysis.current_market_value : 'N/A'}</p>
<p><strong>Resale Platforms:</strong> ${analysis.best_platforms || 'eBay, Fashionphile, TheRealReal'}</p>
</div>
<div class="result-section">
<h3>📊 Market Analysis</h3>
<p>${analysis.market_notes || 'This item is in demand on secondary markets.'}</p>
<p><strong>Investment Rating:</strong> ${analysis.investment_rating || 'Moderate'}</p>
<p><strong>Demand Level:</strong> ${analysis.demand_level || 'Moderate'}</p>
</div>
<div class="result-section">
<h3>🔗 Similar Listings</h3>
<button class="camera-btn camera-btn-primary" onclick="window.location.href='/?brand=${encodeURIComponent(analysis.brand || '')}'">
🔍 Search Our Database
</button>
</div>
`;
}