| |
| |
| |
| |
| |
| |
|
|
| import { Client, handle_file } from |
| "https://cdn.jsdelivr.net/npm/@gradio/client@1.5.0/dist/index.min.js"; |
|
|
| |
| let selectedFile = null; |
| let selectedLang = "en"; |
| let gradioClient = null; |
|
|
| const SEVERITY_COLORS = { |
| 1: "#22c55e", |
| 2: "#84cc16", |
| 3: "#f59e0b", |
| 4: "#ef4444", |
| 5: "#991b1b", |
| }; |
|
|
| |
| async function getClient() { |
| if (!gradioClient) { |
| gradioClient = await Client.connect(window.location.origin); |
| } |
| return gradioClient; |
| } |
|
|
| |
| window.setLang = function(lang) { |
| selectedLang = lang; |
| document.querySelectorAll(".lang-btn").forEach(btn => { |
| btn.classList.toggle("active", btn.dataset.lang === lang); |
| }); |
| }; |
|
|
| |
| function bindInputs() { |
| document.getElementById("cameraInput").addEventListener("change", handleFileChange); |
| document.getElementById("uploadInput").addEventListener("change", handleFileChange); |
|
|
| |
| const dropZone = document.getElementById("dropZone"); |
| dropZone.addEventListener("dragover", e => { e.preventDefault(); dropZone.style.borderColor = "#3b82f6"; }); |
| dropZone.addEventListener("dragleave", () => { dropZone.style.borderColor = ""; }); |
| dropZone.addEventListener("drop", e => { |
| e.preventDefault(); |
| dropZone.style.borderColor = ""; |
| const file = e.dataTransfer.files[0]; |
| if (file && file.type.startsWith("image/")) setSelectedFile(file); |
| }); |
| } |
|
|
| function handleFileChange(e) { |
| const file = e.target.files[0]; |
| if (file) setSelectedFile(file); |
| } |
|
|
| function setSelectedFile(file) { |
| selectedFile = file; |
|
|
| |
| const reader = new FileReader(); |
| reader.onload = ev => { |
| const img = document.getElementById("previewImg"); |
| img.src = ev.target.result; |
| img.classList.remove("hidden"); |
| document.getElementById("capturePlaceholder").classList.add("hidden"); |
| }; |
| reader.readAsDataURL(file); |
|
|
| |
| document.getElementById("analyzeBtn").disabled = false; |
| } |
|
|
| |
| window.runAnalysis = async function() { |
| if (!selectedFile) return; |
|
|
| showLoading(); |
|
|
| try { |
| const client = await getClient(); |
|
|
| |
| progressLoadingSteps(); |
|
|
| const result = await client.predict("/analyze_defect", { |
| image_path: handle_file(selectedFile), |
| language: selectedLang, |
| }); |
|
|
| const report = result.data[0]; |
|
|
| if (report.analysis_ok === false) { |
| showError(report.description || "Analysis failed. Please retake the photo."); |
| } else { |
| renderReport(report); |
| } |
|
|
| } catch (err) { |
| console.error("GharScan API error:", err); |
| |
| const msg = err.message?.includes("timeout") |
| ? "The model is warming up (first request takes ~30s). Please try again in a moment." |
| : `Analysis failed: ${err.message}`; |
| showError(msg); |
| } |
| }; |
|
|
| |
| function renderReport(r) { |
| |
| const structBanner = document.getElementById("structuralBanner"); |
| const safeBanner = document.getElementById("safeBanner"); |
| if (r.is_structural) { |
| structBanner.classList.remove("hidden"); |
| safeBanner.classList.add("hidden"); |
| document.getElementById("structuralReasoning").textContent = r.structural_reasoning || ""; |
| } else { |
| safeBanner.classList.remove("hidden"); |
| structBanner.classList.add("hidden"); |
| } |
|
|
| |
| document.getElementById("defectLabel").textContent = r.defect_display || r.defect_type; |
| document.getElementById("defectTypeSub").textContent = r.defect_type?.replace(/_/g, " ").toUpperCase(); |
|
|
| |
| const chip = document.getElementById("severityChip"); |
| const color = SEVERITY_COLORS[r.severity] || "#6b7280"; |
| chip.style.borderColor = color; |
| document.getElementById("severityNum").textContent = r.severity; |
| document.getElementById("severityNum").style.color = color; |
| document.getElementById("severityWord").textContent = r.severity_label || ""; |
|
|
| |
| const fill = document.getElementById("severityFill"); |
| const pct = ((r.severity / 5) * 100).toFixed(1); |
| fill.style.width = pct + "%"; |
| fill.style.background = color; |
|
|
| |
| document.getElementById("defectDescription").textContent = r.description || ""; |
| document.getElementById("primaryCause").textContent = r.primary_cause || ""; |
| document.getElementById("immediateAction").textContent = r.immediate_action || ""; |
| document.getElementById("urgencyDisplay").textContent = r.urgency_display || ""; |
|
|
| |
| const costRange = document.getElementById("costRange"); |
| |
| costRange.textContent = (r.cost_range_inr || "").replace(/^βΉ\s*/, ""); |
| document.getElementById("professionalDisplay").textContent = r.professional_display || ""; |
|
|
| |
| document.getElementById("monsoonWarning").classList.toggle("hidden", !r.monsoon_risk); |
|
|
| |
| const liabBanner = document.getElementById("liabilityBanner"); |
| if (r.show_liability_banner && r.liability_text) { |
| document.getElementById("liabilityText").textContent = r.liability_text; |
| liabBanner.classList.remove("hidden"); |
| } else { |
| liabBanner.classList.add("hidden"); |
| } |
|
|
| |
| const discCard = document.getElementById("disclaimerCard"); |
| if (r.disclaimer) { |
| document.getElementById("disclaimerText").textContent = r.disclaimer; |
| discCard.classList.remove("hidden"); |
| } else { |
| discCard.classList.add("hidden"); |
| } |
|
|
| showSection("reportSection"); |
| } |
|
|
| |
| function showLoading() { |
| showSection("loadingSection"); |
| |
| ["step1","step2","step3"].forEach(id => { |
| const el = document.getElementById(id); |
| el.classList.remove("active","done"); |
| }); |
| document.getElementById("step1").classList.add("active"); |
| } |
|
|
| function progressLoadingSteps() { |
| const steps = ["step1","step2","step3"]; |
| let i = 0; |
| const interval = setInterval(() => { |
| if (i > 0) document.getElementById(steps[i-1]).classList.replace("active","done"); |
| if (i < steps.length) { |
| document.getElementById(steps[i]).classList.add("active"); |
| const labels = [ |
| "Step 1 of 3: Classifying defect type", |
| "Step 2 of 3: Assessing severity", |
| "Step 3 of 3: Calculating cost estimate", |
| ]; |
| document.getElementById("loadingSub").textContent = labels[i]; |
| } |
| i++; |
| if (i >= steps.length + 1) clearInterval(interval); |
| }, 4000); |
| } |
|
|
| function showError(msg) { |
| document.getElementById("errorMsg").textContent = msg; |
| showSection("errorSection"); |
| } |
|
|
| function showSection(id) { |
| ["captureSection","loadingSection","reportSection","errorSection"].forEach(s => { |
| document.getElementById(s)?.classList.toggle("hidden", s !== id); |
| }); |
| } |
|
|
| window.resetToCapture = function() { |
| selectedFile = null; |
| document.getElementById("previewImg").src = ""; |
| document.getElementById("previewImg").classList.add("hidden"); |
| document.getElementById("capturePlaceholder").classList.remove("hidden"); |
| document.getElementById("analyzeBtn").disabled = true; |
| document.getElementById("cameraInput").value = ""; |
| document.getElementById("uploadInput").value = ""; |
| showSection("captureSection"); |
| }; |
|
|
| |
| document.addEventListener("DOMContentLoaded", () => { |
| bindInputs(); |
|
|
| |
| getClient().catch(() => {}); |
| }); |