DamageLensAI / index.html
junaid17's picture
Upload 15 files
1ae016f verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Car Damage AI</title>
<script src="https://cdn.plot.ly/plotly-2.27.0.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
:root {
--bg-dark: #09090b;
--bg-card: #18181b;
--text-primary: #e2e8f0;
--text-secondary: #a1a1aa;
--accent: #00c6ff;
--accent-hover: #0072ff;
--glass: rgba(255, 255, 255, 0.03);
--card-border: #27272a;
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', sans-serif; }
body {
background-color: var(--bg-dark);
color: var(--text-primary);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 20px;
background-image: radial-gradient(circle at top right, rgba(0, 198, 255, 0.05) 0%, transparent 40%);
}
.container {
width: 100%;
max-width: 850px;
background: var(--bg-card);
border-radius: 20px;
padding: 35px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
animation: slideUpFade 0.6s ease-out forwards;
border: 1px solid var(--card-border);
}
@keyframes slideUpFade { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
/* Shimmering Main Title */
.shimmer-text {
text-align: center;
font-size: 2.5rem;
font-weight: 800;
background: linear-gradient(90deg, #e2e8f0 0%, #ffffff 25%, #00c6ff 50%, #e2e8f0 75%, #e2e8f0 100%);
background-size: 200% auto;
color: transparent;
-webkit-background-clip: text;
background-clip: text;
animation: shimmer 4s linear infinite;
margin-bottom: 0.2rem;
}
@keyframes shimmer { 0% { background-position: -200% center; } 100% { background-position: 200% center; } }
.subtitle { text-align: center; color: var(--text-secondary); font-size: 1rem; margin-bottom: 25px; }
/* Warning Box */
.warning-box {
background: rgba(0, 198, 255, 0.1);
border-left: 4px solid var(--accent);
color: var(--text-primary);
padding: 12px 15px;
border-radius: 8px;
margin-bottom: 25px;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 12px;
}
/* Controls Section */
.controls-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 25px;
}
.file-wrapper {
position: relative; height: 160px; border: 2px dashed #444; border-radius: 16px;
display: flex; justify-content: center; align-items: center; cursor: pointer;
transition: all 0.3s ease; background: var(--glass); overflow: hidden;
}
.file-wrapper:hover { border-color: var(--accent); background: rgba(0, 198, 255, 0.05); }
.file-wrapper input { position: absolute; width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 2; }
.settings-card {
background: rgba(0,0,0,0.2);
border-radius: 16px;
padding: 20px;
border: 1px solid var(--card-border);
display: flex;
flex-direction: column;
justify-content: center;
}
select {
width: 100%; background: #27272a; border: 1px solid #3f3f46; padding: 14px;
border-radius: 12px; color: white; outline: none; margin-top: 10px; font-size: 1rem;
}
select:focus { border-color: var(--accent); }
/* Preview Area & Animations */
.image-area {
width: 100%; height: 350px; background: #09090b; border-radius: 16px;
margin-bottom: 25px; display: none; justify-content: center; align-items: center;
overflow: hidden; position: relative; border: 1px solid var(--card-border);
}
.image-area img { max-width: 100%; max-height: 100%; object-fit: contain; z-index: 1;}
/* Scanner Animation */
.scan-line {
position: absolute; top: -10%; left: 0; width: 100%; height: 5px;
background: var(--accent); box-shadow: 0 0 15px var(--accent), 0 0 30px var(--accent);
z-index: 5; opacity: 0.8; display: none; animation: scanMove 2s ease-in-out infinite; filter: blur(1px);
}
@keyframes scanMove { 0% { top: -10%; opacity: 0.5; } 50% { opacity: 1; } 100% { top: 110%; opacity: 0.5; } }
/* Loader Overlay */
.loader-overlay {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.65); backdrop-filter: blur(4px);
display: none; flex-direction: column; justify-content: center; align-items: center; z-index: 10;
}
.spinner {
width: 50px; height: 50px; border: 4px solid rgba(0, 198, 255, 0.2);
border-top: 4px solid var(--accent); border-radius: 50%;
animation: spin 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) infinite; margin-bottom: 15px;
}
@keyframes spin { 100% { transform: rotate(360deg); } }
/* Buttons */
.btn {
width: 100%; padding: 16px; background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
color: white; border: none; border-radius: 12px; cursor: pointer; font-weight: 700; font-size: 1rem;
transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0, 114, 255, 0.3);
}
.btn:hover:not(:disabled) { transform: scale(1.02); box-shadow: 0 8px 25px rgba(0, 198, 255, 0.5); }
.btn:disabled { background: #444; color: #888; box-shadow: none; transform: none; cursor: not-allowed;}
/* Results Tabs */
.results-section { display: none; margin-top: 30px; animation: slideUpFade 0.5s ease-out; }
.tabs { display: flex; gap: 10px; margin-bottom: 20px; border-bottom: 1px solid var(--card-border); padding-bottom: 10px; overflow-x: auto; }
.tab {
padding: 10px 20px; cursor: pointer; border-radius: 8px; color: var(--text-secondary);
font-weight: 600; transition: all 0.3s ease; white-space: nowrap;
}
.tab.active { background: rgba(0, 198, 255, 0.1); color: var(--accent); }
.tab-content { display: none; }
.tab-content.active { display: block; animation: slideUpFade 0.4s ease-out; }
/* Progress Bar */
.progress-wrapper { background: #27272a; border-radius: 20px; overflow: hidden; height: 12px; margin: 10px 0 20px 0; box-shadow: inset 0 2px 4px rgba(0,0,0,0.5); }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-hover)); border-radius: 20px; width: 0%; transition: width 1.5s cubic-bezier(0.22, 1, 0.36, 1); }
/* Final Prediction Text */
.big-text { font-size: 2.5rem; font-weight: 800; background: -webkit-linear-gradient(45deg, #00c6ff, #0072ff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 5px; }
/* Images Grid (Attention Maps) */
.img-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; }
.img-card { background: rgba(0,0,0,0.3); border: 1px solid var(--card-border); border-radius: 12px; padding: 10px; text-align: center; }
.img-card img { width: 100%; border-radius: 8px; margin-top: 10px; }
/* YOLO Grid */
.yolo-grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 20px; }
.log-box { background: rgba(0,0,0,0.3); border: 1px solid var(--card-border); border-radius: 12px; padding: 20px; height: 100%; }
.detection-item { background: #27272a; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid var(--accent); box-shadow: 0 2px 4px rgba(0,0,0,0.2); }
@media (max-width: 768px) {
.controls-grid, .img-grid, .yolo-grid { grid-template-columns: 1fr; }
.shimmer-text { font-size: 2rem; }
}
</style>
</head>
<body>
<div class="container">
<div class="shimmer-text">🚗 Car Damage AI</div>
<div class="subtitle">Fusion Intelligence: ResNet + YOLO</div>
<div class="warning-box">
<span style="font-size: 1.2rem;">⏱️</span>
<span><b>Note:</b> The first analysis may take up to 3-4 mins while models warm up. Subsequent requests are faster!</span>
</div>
<div class="controls-grid">
<div class="file-wrapper">
<input type="file" id="fileInput" accept="image/jpeg, image/png, image/jpg">
<div style="text-align: center;">
<p style="font-size: 2.5rem; margin-bottom: 5px;">📷</p>
<p style="color:#a1a1aa; font-weight: 500;">Tap or Drag & Drop Vehicle Image</p>
</div>
</div>
<div class="settings-card">
<h3 style="font-size: 1.1rem; margin-bottom: 5px;">⚙️ Analysis Settings</h3>
<p style="font-size: 0.85rem; color: var(--text-secondary);">Select the neural network pipeline.</p>
<select id="engineMode">
<option value="fusion">Fusion</option>
<option value="resnet">ResNet</option>
</select>
</div>
</div>
<div class="image-area" id="previewBox">
<img id="displayImage" src="" alt="Car Image">
<div class="scan-line" id="scanLine"></div>
<div class="loader-overlay" id="loader">
<div class="spinner"></div>
<p style="color:white; font-weight:600; letter-spacing: 1px; margin-bottom: 5px;">🧠 ANALYZING...</p>
<p id="loaderStatusText" style="color:#00c6ff; font-size:0.9rem;">Extracting features...</p>
</div>
</div>
<button class="btn" id="analyzeBtn" onclick="analyze()">🚀 Run AI Analysis</button>
<div class="results-section" id="resultsSection">
<div class="tabs">
<div class="tab active" onclick="switchResultTab('tab-pred')">📊 Prediction</div>
<div class="tab" onclick="switchResultTab('tab-attention')">👀 Attention Maps</div>
<div class="tab" onclick="switchResultTab('tab-yolo')">🎯 Localization</div>
</div>
<div id="tab-pred" class="tab-content active">
<div class="settings-card">
<div id="finalPredText" class="big-text">--</div>
<div style="font-weight: 600; margin-top: 5px;" id="confText">Confidence Score: 0%</div>
<div class="progress-wrapper">
<div class="progress-fill" id="confBar"></div>
</div>
<h3 style="margin: 15px 0 5px 0; font-size: 1.1rem;">Probability Distribution</h3>
<div id="plotlyChart" style="width:100%; height:300px;"></div>
</div>
</div>
<div id="tab-attention" class="tab-content">
<div class="img-grid">
<div class="img-card">
<div style="font-weight:600; color:#e2e8f0;">Original Image</div>
<img id="camOriginal" src="" alt="Original Image">
</div>
<div class="img-card">
<div id="camSelectedLabel" style="font-weight:600; color:#e2e8f0;">Selected Grad-CAM</div>
<img id="camSelected" src="" alt="Selected Grad-CAM">
</div>
</div>
</div>
<div id="tab-yolo" class="tab-content">
<div class="yolo-grid">
<div class="settings-card">
<h3 style="margin-bottom: 10px;">Bounding Boxes</h3>
<img id="yoloImage" src="" alt="YOLO Output" style="width: 100%; border-radius: 8px;">
</div>
<div class="log-box">
<h3 style="margin-bottom: 15px;">Detection Log</h3>
<div id="yoloLogContainer">
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const API_URL = "http://127.0.0.1:8000";
let currentFile = null;
// DOM Elements
const fileInput = document.getElementById('fileInput');
const displayImage = document.getElementById('displayImage');
const previewBox = document.getElementById('previewBox');
const resultsSection = document.getElementById('resultsSection');
const loader = document.getElementById('loader');
const loaderStatusText = document.getElementById('loaderStatusText');
const scanLine = document.getElementById('scanLine');
const analyzeBtn = document.getElementById('analyzeBtn');
fileInput.addEventListener('change', e => {
if(e.target.files[0]) {
currentFile = e.target.files[0];
const reader = new FileReader();
reader.onload = x => {
displayImage.src = x.target.result;
previewBox.style.display = 'flex';
resultsSection.style.display = 'none'; // Hide old results
};
reader.readAsDataURL(currentFile);
}
});
// --- BUG FIX IS HERE ---
function switchResultTab(tabId) {
// 1. Remove active state from all tabs and panels
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
// 2. Find the tab button that corresponds to this panel and make it active
const tabButton = document.querySelector(`.tab[onclick*="${tabId}"]`);
if(tabButton) {
tabButton.classList.add('active');
}
// 3. Make the specific panel active
document.getElementById(tabId).classList.add('active');
// 4. Resize Plotly chart if switching back to its tab to prevent layout squash
if(tabId === 'tab-pred') {
window.dispatchEvent(new Event('resize'));
}
}
// Plotly Chart Helper
function drawChart(dataObj, title) {
const labels = Object.keys(dataObj);
const values = Object.values(dataObj);
const trace = {
x: labels,
y: values,
type: 'bar',
marker: { color: '#00c6ff', line: { color: '#0072ff', width: 1.5 } },
opacity: 0.85
};
const layout = {
title: title || '',
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)',
font: { family: 'Inter', color: '#a1a1aa' },
margin: { l: 40, r: 10, t: 30, b: 40 },
xaxis: { title: 'Classes' },
yaxis: { title: 'Probability', range: [0, 1] }
};
Plotly.newPlot('plotlyChart', [trace], layout, {displayModeBar: false, responsive: true});
}
async function analyze() {
if(!currentFile) return alert("Please upload an image first.");
const engineMode = document.getElementById('engineMode').value; // fusion or resnet
// UI Prep
loader.style.display = 'flex';
scanLine.style.display = 'block';
analyzeBtn.disabled = true;
analyzeBtn.innerText = "Processing...";
resultsSection.style.display = 'none';
const formData = new FormData();
formData.append('image', currentFile);
try {
loaderStatusText.innerText = "Extracting features...";
const predRes = await fetch(`${API_URL}/predict/${engineMode}`, { method: 'POST', body: formData });
if (!predRes.ok) throw new Error("Prediction API failed");
const predData = await predRes.json();
loaderStatusText.innerText = "Generating Grad-CAM...";
const camForm = new FormData();
camForm.append('file', currentFile);
const camRes = await fetch(`${API_URL}/predict?mode=${engineMode}`, { method: 'POST', body: camForm });
if (!camRes.ok) throw new Error("Grad-CAM API failed");
const camData = await camRes.json();
loaderStatusText.innerText = "Running YOLO detection...";
const yoloRes = await fetch(`${API_URL}/predict/yolo`, { method: 'POST', body: camForm });
if (!yoloRes.ok) throw new Error("YOLO API failed");
const yoloData = await yoloRes.json();
const highestClass = Object.keys(predData).reduce((a, b) => predData[a] > predData[b] ? a : b);
const highestScore = predData[highestClass] || 0;
document.getElementById('finalPredText').innerText = highestClass;
document.getElementById('confText').innerText = `Confidence Score: ${(highestScore * 100).toFixed(2)}%`;
drawChart(predData, `${engineMode.toUpperCase()} Output`);
setTimeout(() => { document.getElementById('confBar').style.width = `${(highestScore * 100).toFixed(2)}%`; }, 100);
document.getElementById('camOriginal').src = `${API_URL}${camData.original_image}`;
document.getElementById('camSelected').src = `${API_URL}${camData.selected_viz}`;
document.getElementById('camSelectedLabel').innerText = engineMode === 'fusion' ? 'Fusion Grad-CAM' : 'ResNet Grad-CAM';
document.getElementById('yoloImage').src = `${API_URL}${yoloData.yolo_image}`;
const logContainer = document.getElementById('yoloLogContainer');
if (!yoloData.detections || yoloData.detections.length === 0) {
logContainer.innerHTML = '<div style="color: #a1a1aa; padding: 10px;">🟢 No damage boxes detected.</div>';
} else {
let logHTML = `<div style="color: #ffcc00; margin-bottom: 10px; font-weight:600;">🔴 Found ${yoloData.total_detections} damage region(s).</div>`;
yoloData.detections.forEach((det, idx) => {
logHTML += `<div class="detection-item"><b style="color: #e2e8f0;">Region ${idx + 1}</b><br><span style="color: #a1a1aa; font-size: 0.9em;">${det.label} · ${(det.confidence * 100).toFixed(1)}%</span></div>`;
});
logContainer.innerHTML = logHTML;
}
resultsSection.style.display = 'block';
switchResultTab('tab-pred');
} catch (error) {
alert(`Error connecting to AI server. Details: ${error.message}`);
console.error(error);
} finally {
loader.style.display = 'none';
scanLine.style.display = 'none';
analyzeBtn.disabled = false;
analyzeBtn.innerText = "🚀 Run AI Analysis";
}
}
</script>
</body>
</html>