← 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>
  `;
}