document.addEventListener('DOMContentLoaded', () => { // DOM Elements const uploadForm = document.getElementById('upload-form'); const promptInput = document.getElementById('prompt-input'); const beforeInput = document.getElementById('video-before'); const afterInput = document.getElementById('video-after'); const beforeZone = document.getElementById('upload-before-zone'); const afterZone = document.getElementById('upload-after-zone'); const beforeFileName = document.getElementById('before-file-name'); const afterFileName = document.getElementById('after-file-name'); const speedModeSelect = document.getElementById('speed-mode-select'); const fpsSelect = document.getElementById('fps-select'); const geminiKeyInput = document.getElementById('gemini-key-input'); const uploadBtn = document.getElementById('upload-btn'); const startBtn = document.getElementById('start-btn'); const resetBtn = document.getElementById('reset-btn'); const processingView = document.getElementById('processing-view'); const progressStatus = document.getElementById('progress-status'); const progressFill = document.getElementById('progress-fill'); const progressPct = document.getElementById('progress-pct'); const backendStatusText = document.getElementById('backend-status-text'); const resultsPlaceholder = document.getElementById('results-placeholder'); const resultsView = document.getElementById('results-view'); const outVideoBefore = document.getElementById('out-video-before'); const outVideoAfter = document.getElementById('out-video-after'); const assetCardsContainer = document.getElementById('asset-cards-container'); let pollInterval = null; // --- Load saved Gemini API Key --- const savedKey = localStorage.getItem('gemini_api_key'); if (savedKey) { geminiKeyInput.value = savedKey; } // --- File Drag & Drop Listeners --- setupDragAndDrop(beforeZone, beforeInput, beforeFileName, "Before Renting Video Selected"); setupDragAndDrop(afterZone, afterInput, afterFileName, "After Renting Video Selected"); function setupDragAndDrop(zone, input, display, successMsg) { zone.addEventListener('click', () => input.click()); input.addEventListener('change', (e) => { if (input.files.length > 0) { display.textContent = `${input.files[0].name} (${(input.files[0].size / 1e6).toFixed(1)} MB)`; zone.style.borderColor = 'var(--success)'; } }); zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.classList.add('drag-over'); }); zone.addEventListener('dragleave', () => { zone.classList.remove('drag-over'); }); zone.addEventListener('drop', (e) => { e.preventDefault(); zone.classList.remove('drag-over'); if (e.dataTransfer.files.length > 0) { input.files = e.dataTransfer.files; display.textContent = `${input.files[0].name} (${(input.files[0].size / 1e6).toFixed(1)} MB)`; zone.style.borderColor = 'var(--success)'; } }); } // --- Upload Videos --- uploadForm.addEventListener('submit', async (e) => { e.preventDefault(); if (beforeInput.files.length === 0 || afterInput.files.length === 0) { alert('Please select both "Before" and "After" renting videos first.'); return; } const prompt = promptInput.value.trim(); if (!prompt) { alert('Please define target inspection items prompt.'); return; } const formData = new FormData(); formData.append('video_before', beforeInput.files[0]); formData.append('video_after', afterInput.files[0]); formData.append('prompt', prompt); try { uploadBtn.disabled = true; uploadBtn.innerHTML = ' Uploading...'; backendStatusText.textContent = "Uploading videos..."; const response = await fetch('/upload', { method: 'POST', body: formData }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Upload failed.'); } uploadBtn.innerHTML = ' Uploaded!'; uploadBtn.style.background = 'var(--success)'; // Enable Start button startBtn.removeAttribute('disabled'); startBtn.classList.remove('btn-disabled'); backendStatusText.textContent = "Ready to analyze"; alert('Videos uploaded successfully. You can now start the evaluation.'); } catch (error) { console.error(error); alert(`Error: ${error.message}`); uploadBtn.disabled = false; uploadBtn.innerHTML = ' Upload Videos'; backendStatusText.textContent = "Upload failed"; } }); // --- Start Tracking / Evaluation --- startBtn.addEventListener('click', async () => { const prompt = promptInput.value.trim(); const geminiKey = geminiKeyInput.value.trim(); const speedMode = speedModeSelect.value; const fps = fpsSelect.value; // Securely cache Gemini API key in browser if (geminiKey) { localStorage.setItem('gemini_api_key', geminiKey); } else { localStorage.removeItem('gemini_api_key'); } try { startBtn.disabled = true; startBtn.classList.add('btn-disabled'); processingView.classList.remove('hidden'); backendStatusText.textContent = "Running evaluation..."; const response = await fetch('/process', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: prompt, gemini_api_key: geminiKey, speed_mode: speedMode, fps: parseFloat(fps) }) }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Failed to start process.'); } // Start polling status startStatusPolling(); } catch (error) { console.error(error); alert(`Error: ${error.message}`); processingView.classList.add('hidden'); startBtn.disabled = false; startBtn.classList.remove('btn-disabled'); backendStatusText.textContent = "Error occurred"; } }); // --- Reset --- resetBtn.addEventListener('click', async () => { if (confirm('Are you sure you want to reset and clear all data?')) { try { await fetch('/reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: true }) }); // Clear intervals clearInterval(pollInterval); pollInterval = null; // Reset inputs and displays uploadForm.reset(); beforeInput.value = ''; afterInput.value = ''; beforeZone.style.borderColor = 'var(--border-color)'; afterZone.style.borderColor = 'var(--border-color)'; beforeFileName.textContent = 'Drag & drop or click to upload (.mp4)'; afterFileName.textContent = 'Drag & drop or click to upload (.mp4)'; // Retain Gemini Key const savedKey = localStorage.getItem('gemini_api_key'); if (savedKey) { geminiKeyInput.value = savedKey; } uploadBtn.disabled = false; uploadBtn.innerHTML = ' Upload Videos'; uploadBtn.style.background = 'var(--primary)'; startBtn.disabled = true; startBtn.classList.add('btn-disabled'); processingView.classList.add('hidden'); resultsView.classList.add('hidden'); resultsPlaceholder.classList.remove('hidden'); outVideoBefore.src = ''; outVideoAfter.src = ''; assetCardsContainer.innerHTML = ''; backendStatusText.textContent = "System Ready"; } catch (error) { console.error(error); alert('Reset failed.'); } } }); // --- Status Polling --- function startStatusPolling() { if (pollInterval) clearInterval(pollInterval); pollInterval = setInterval(async () => { try { const response = await fetch('/status'); const data = await response.json(); if (data.error) { clearInterval(pollInterval); alert(`Evaluation Error: ${data.error}`); processingView.classList.add('hidden'); startBtn.disabled = false; startBtn.classList.remove('btn-disabled'); backendStatusText.textContent = "Analysis Failed"; return; } // Update progress overlay progressStatus.textContent = data.status; progressFill.style.width = `${data.percent}%`; progressPct.textContent = `${data.percent}%`; if (!data.running && data.percent === 100 && data.result) { clearInterval(pollInterval); pollInterval = null; displayResults(data.result); } } catch (error) { console.error('Polling error:', error); } }, 1500); } // --- Display Results --- function displayResults(result) { // Hide processing and placeholder processingView.classList.add('hidden'); resultsPlaceholder.classList.add('hidden'); resultsView.classList.remove('hidden'); backendStatusText.textContent = "Evaluation Complete"; // Render Summary Banner const summaryBanner = document.getElementById('summary-banner'); if (summaryBanner && result.summary) { const s = result.summary; // Format elapsed time (e.g. "45.2s" or "1m 15s") let timeStr = ""; if (s.elapsed_time < 60) { timeStr = `${s.elapsed_time}s`; } else { const mins = Math.floor(s.elapsed_time / 60); const secs = Math.round(s.elapsed_time % 60); timeStr = `${mins}m ${secs}s`; } // Compensation judgment logic: if any item is Missing or Damaged, tenant must pay compensation const compensationRequired = (s.statuses.Missing || 0) > 0 || (s.statuses["Potential Damage / Altered"] || 0) > 0; const verdictTitle = compensationRequired ? "Tenant Must Pay Compensation" : "No Compensation Required"; const verdictColor = compensationRequired ? "var(--danger)" : "var(--success)"; const verdictIcon = compensationRequired ? "fa-solid fa-circle-exclamation" : "fa-solid fa-circle-check"; const verdictDesc = compensationRequired ? "Damage or missing assets detected. Tenant is liable for compensation." : "All tracked assets are intact. No compensation required."; // Create unique list of all discovered item labels const uniqueLabels = Array.from(new Set([...s.before_items, ...s.after_items])); const labelsHtml = uniqueLabels.length > 0 ? uniqueLabels.map(lbl => `${lbl}`).join('') : 'None'; summaryBanner.innerHTML = `