Spaces:
Sleeping
Sleeping
| // SatFetch Client Application Logic | |
| document.addEventListener("DOMContentLoaded", () => { | |
| // ------------------------------------------------------------------------- | |
| // View State Navigation (Landing -> Dashboard) | |
| // ------------------------------------------------------------------------- | |
| const landingPage = document.getElementById("landing-page"); | |
| const terminalPage = document.getElementById("terminal-page"); | |
| const launchBtn = document.getElementById("launch-btn"); | |
| launchBtn.addEventListener("click", () => { | |
| landingPage.classList.add("slide-out"); | |
| setTimeout(() => { | |
| landingPage.style.display = "none"; | |
| terminalPage.classList.add("active"); | |
| // Trigger leaflet map resize after tab/container becomes visible | |
| if (map) { | |
| map.invalidateSize(); | |
| } | |
| }, 600); | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // Tabs Navigation | |
| // ------------------------------------------------------------------------- | |
| const navTabs = document.querySelectorAll(".nav-tab"); | |
| const tabContents = document.querySelectorAll(".tab-content"); | |
| navTabs.forEach(tab => { | |
| tab.addEventListener("click", () => { | |
| navTabs.forEach(t => t.classList.remove("active")); | |
| tabContents.forEach(c => c.classList.remove("active")); | |
| tab.classList.add("active"); | |
| const activeTabId = tab.getAttribute("data-tab"); | |
| document.getElementById(activeTabId).classList.add("active"); | |
| // Handle Leaflet refresh if search tab activated | |
| if (activeTabId === "search-pane" && map) { | |
| setTimeout(() => map.invalidateSize(), 50); | |
| } | |
| // Load benchmarks if benchmarks tab activated | |
| if (activeTabId === "benchmarks-pane") { | |
| loadBenchmarks(); | |
| } | |
| }); | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // Search Mode Toggle (Image vs Text) | |
| // ------------------------------------------------------------------------- | |
| const modeImageBtn = document.getElementById("mode-image-btn"); | |
| const modeTextBtn = document.getElementById("mode-text-btn"); | |
| const imageUploadArea = document.getElementById("image-upload-area"); | |
| const textQueryArea = document.getElementById("text-query-area"); | |
| let activeSearchMode = "image"; // 'image' or 'text' | |
| const retrievalLevelSelect = document.getElementById("retrieval-level"); | |
| const spatialOptionsGroup = document.getElementById("spatial-options-group"); | |
| function toggleSpatialOptions() { | |
| if (retrievalLevelSelect.value === "level4") { | |
| spatialOptionsGroup.style.display = "block"; | |
| } else { | |
| spatialOptionsGroup.style.display = "none"; | |
| } | |
| } | |
| retrievalLevelSelect.addEventListener("change", toggleSpatialOptions); | |
| toggleSpatialOptions(); | |
| modeImageBtn.addEventListener("click", () => { | |
| modeImageBtn.classList.add("active"); | |
| modeTextBtn.classList.remove("active"); | |
| imageUploadArea.style.display = "block"; | |
| textQueryArea.style.display = "none"; | |
| activeSearchMode = "image"; | |
| }); | |
| modeTextBtn.addEventListener("click", () => { | |
| modeTextBtn.classList.add("active"); | |
| modeImageBtn.classList.remove("active"); | |
| imageUploadArea.style.display = "none"; | |
| textQueryArea.style.display = "block"; | |
| activeSearchMode = "text"; | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // File Upload Handler | |
| // ------------------------------------------------------------------------- | |
| const dropZone = document.getElementById("drop-zone"); | |
| const fileInput = document.getElementById("file-input"); | |
| const uploadPreview = document.getElementById("upload-preview"); | |
| const previewImg = document.getElementById("preview-img"); | |
| const previewFilename = document.getElementById("preview-filename"); | |
| const previewSize = document.getElementById("preview-size"); | |
| const clearUpload = document.getElementById("clear-upload"); | |
| let uploadedFile = null; | |
| dropZone.addEventListener("click", () => fileInput.click()); | |
| dropZone.addEventListener("dragover", (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = "var(--primary-color)"; | |
| dropZone.style.backgroundColor = "var(--primary-glow)"; | |
| }); | |
| dropZone.addEventListener("dragleave", () => { | |
| dropZone.style.borderColor = "var(--border-color)"; | |
| dropZone.style.backgroundColor = "transparent"; | |
| }); | |
| dropZone.addEventListener("drop", (e) => { | |
| e.preventDefault(); | |
| dropZone.style.borderColor = "var(--border-color)"; | |
| dropZone.style.backgroundColor = "transparent"; | |
| if (e.dataTransfer.files.length > 0) { | |
| handleFile(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener("change", (e) => { | |
| if (e.target.files.length > 0) { | |
| handleFile(e.target.files[0]); | |
| } | |
| }); | |
| clearUpload.addEventListener("click", (e) => { | |
| e.stopPropagation(); | |
| uploadedFile = null; | |
| fileInput.value = ""; | |
| previewImg.src = ""; | |
| uploadPreview.style.display = "none"; | |
| dropZone.style.display = "block"; | |
| }); | |
| function handleFile(file) { | |
| uploadedFile = file; | |
| previewFilename.textContent = file.name; | |
| // Format size | |
| const sizeMB = (file.size / (1024 * 1024)).toFixed(1); | |
| previewSize.textContent = `${sizeMB} MB`; | |
| // Check if file is TIFF (cannot display TIFF natively in most browsers) | |
| const isTiff = file.name.endsWith(".tif") || file.name.endsWith(".tiff"); | |
| if (isTiff) { | |
| // Beautiful local SVG placeholder for TIFF files (offline safe) | |
| previewImg.src = `data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100"><rect width="100" height="100" rx="10" fill="%23f8fafc" stroke="%23cbd5e1" stroke-width="2"/><path d="M35 25 H55 L65 35 V75 H35 Z" fill="white" stroke="%233b82f6" stroke-width="2"/><path d="M55 25 V35 H65" fill="none" stroke="%233b82f6" stroke-width="2"/><text x="50" y="60" font-family="system-ui, sans-serif" font-size="9" font-weight="bold" fill="%231e293b" text-anchor="middle">GeoTIFF</text></svg>`; | |
| } else { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| previewImg.src = e.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| dropZone.style.display = "none"; | |
| uploadPreview.style.display = "block"; | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Leaflet Map Integration | |
| // ------------------------------------------------------------------------- | |
| let map; | |
| let centerMarker; | |
| let searchRadiusCircle; | |
| let resultsMarkersGroup = L.layerGroup(); | |
| let h3GridGroup = L.layerGroup(); | |
| const indiaCenter = [20.5937, 78.9629]; | |
| function initMap() { | |
| const mapEl = document.getElementById("map"); | |
| if (!mapEl) return; | |
| map = L.map("map").setView(indiaCenter, 5); | |
| // Light-themed GIS Tile Layer (Esri World Topo Map) | |
| L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}', { | |
| attribution: 'Tiles © Esri — Sources: Esri, DeLorme, NAVTEQ, USGS, NOAA, and the GIS User Community' | |
| }).addTo(map); | |
| // Set pointer marker | |
| centerMarker = L.marker(indiaCenter, { draggable: true }).addTo(map); | |
| // Draw initial radius | |
| updateMapCircle(indiaCenter, parseInt(document.getElementById("radius-input").value)); | |
| // Sync coordinate boxes on marker drag | |
| centerMarker.on("dragend", (e) => { | |
| const latlng = e.target.getLatLng(); | |
| document.getElementById("lat-input").value = latlng.lat.toFixed(5); | |
| document.getElementById("lon-input").value = latlng.lng.toFixed(5); | |
| updateMapCircle([latlng.lat, latlng.lng], parseInt(document.getElementById("radius-input").value)); | |
| }); | |
| // Click on map to place coordinate | |
| map.on("click", (e) => { | |
| const latlng = e.latlng; | |
| centerMarker.setLatLng(latlng); | |
| document.getElementById("lat-input").value = latlng.lat.toFixed(5); | |
| document.getElementById("lon-input").value = latlng.lng.toFixed(5); | |
| updateMapCircle([latlng.lat, latlng.lng], parseInt(document.getElementById("radius-input").value)); | |
| }); | |
| resultsMarkersGroup.addTo(map); | |
| h3GridGroup.addTo(map); | |
| } | |
| function updateMapCircle(coords, radiusKm) { | |
| if (!map) return; | |
| if (searchRadiusCircle) { | |
| map.removeLayer(searchRadiusCircle); | |
| } | |
| searchRadiusCircle = L.circle(coords, { | |
| radius: radiusKm * 1000, | |
| color: "#2563eb", | |
| fillColor: "#3b82f6", | |
| fillOpacity: 0.1, | |
| weight: 1.5 | |
| }).addTo(map); | |
| } | |
| // Connect slider inputs | |
| const radiusInput = document.getElementById("radius-input"); | |
| const radiusVal = document.getElementById("radius-val"); | |
| radiusInput.addEventListener("input", (e) => { | |
| const val = e.target.value; | |
| radiusVal.textContent = `${val} km`; | |
| if (map) { | |
| const lat = parseFloat(document.getElementById("lat-input").value); | |
| const lon = parseFloat(document.getElementById("lon-input").value); | |
| updateMapCircle([lat, lon], parseInt(val)); | |
| } | |
| }); | |
| const kInput = document.getElementById("k-input"); | |
| const kVal = document.getElementById("k-val"); | |
| kInput.addEventListener("input", (e) => { | |
| kVal.textContent = e.target.value; | |
| }); | |
| initMap(); | |
| // ------------------------------------------------------------------------- | |
| // Execution and API Requests | |
| // ------------------------------------------------------------------------- | |
| const searchBtn = document.getElementById("search-btn"); | |
| const resultsEmpty = document.getElementById("results-empty"); | |
| const resultsGrid = document.getElementById("results-grid"); | |
| const resultsTableContainer = document.getElementById("results-table-container"); | |
| const resultsTableBody = document.getElementById("results-table-body"); | |
| const timingFeed = document.getElementById("timing-feed"); | |
| searchBtn.addEventListener("click", () => { | |
| if (activeSearchMode === "image" && !uploadedFile) { | |
| alert("Please upload a query satellite image first."); | |
| return; | |
| } | |
| if (activeSearchMode === "text" && !document.getElementById("text-query-input").value.trim()) { | |
| alert("Please enter a text search query."); | |
| return; | |
| } | |
| searchBtn.disabled = true; | |
| searchBtn.innerHTML = `<i class="fa-solid fa-circle-notch fa-spin"></i> EXECUTING RETRIEVAL...`; | |
| timingFeed.innerHTML = `<i class="fa-solid fa-satellite fa-spin"></i> Aligning sensor domains...`; | |
| const formData = new FormData(); | |
| const level = document.getElementById("retrieval-level").value; | |
| const k = document.getElementById("k-input").value; | |
| const modality = document.getElementById("query-modality").value; | |
| let url = "/api/search"; | |
| formData.append("k", k); | |
| formData.append("level", level); | |
| formData.append("query_modality", modality); | |
| if (level === "level4") { | |
| const lat = document.getElementById("lat-input").value; | |
| const lon = document.getElementById("lon-input").value; | |
| const radius = document.getElementById("radius-input").value; | |
| formData.append("lat", lat); | |
| formData.append("lon", lon); | |
| formData.append("radius_km", radius); | |
| } | |
| if (activeSearchMode === "image") { | |
| formData.append("file", uploadedFile); | |
| } else { | |
| url = "/api/search-text"; | |
| formData.append("text_query", document.getElementById("text-query-input").value); | |
| } | |
| fetch(url, { | |
| method: "POST", | |
| body: formData | |
| }) | |
| .then(response => { | |
| if (!response.ok) { | |
| throw new Error("Server error occurred during retrieval."); | |
| } | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| renderResults(data); | |
| }) | |
| .catch(err => { | |
| console.error(err); | |
| alert(`Search failed: ${err.message}`); | |
| timingFeed.innerHTML = `<i class="fa-solid fa-triangle-exclamation" style="color:var(--error)"></i> Error in search`; | |
| }) | |
| .finally(() => { | |
| searchBtn.disabled = false; | |
| searchBtn.innerHTML = `<i class="fa-solid fa-circle-play"></i> RUN ALIGNMENT & SEARCH`; | |
| }); | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // Results Rendering & Leaflet Marker Plotting | |
| // ------------------------------------------------------------------------- | |
| function renderResults(data) { | |
| const results = data.results; | |
| const timeMs = data.query_time_ms.toFixed(0); | |
| const device = data.device || "cpu"; | |
| // Update telemetry | |
| document.getElementById("telemetry-device").textContent = device.toUpperCase(); | |
| timingFeed.innerHTML = `<i class="fa-solid fa-bolt"></i> Completed in ${timeMs}ms`; | |
| // Clear existing markers & overlays | |
| resultsMarkersGroup.clearLayers(); | |
| h3GridGroup.clearLayers(); | |
| if (results.length === 0) { | |
| resultsEmpty.style.display = "flex"; | |
| resultsGrid.style.display = "none"; | |
| resultsTableContainer.style.display = "none"; | |
| return; | |
| } | |
| resultsEmpty.style.display = "none"; | |
| resultsGrid.style.display = "grid"; | |
| resultsTableContainer.style.display = "block"; | |
| resultsGrid.innerHTML = ""; | |
| resultsTableBody.innerHTML = ""; | |
| const mapBounds = L.latLngBounds(); | |
| results.forEach((item, index) => { | |
| const rank = index + 1; | |
| const score = item.score.toFixed(4); | |
| const lat = item.lat ? item.lat.toFixed(5) : "N/A"; | |
| const lon = item.lon ? item.lon.toFixed(5) : "N/A"; | |
| const dist = item.distance_km ? `${item.distance_km.toFixed(1)} km` : "N/A"; | |
| const h3Cell = item.h3_cell || "N/A"; | |
| // Render Card | |
| const card = document.createElement("div"); | |
| card.className = "result-card card"; | |
| // True color vs FCC render toggle for MS | |
| const isMs = item.modality === "multispectral"; | |
| const actionHTML = isMs ? ` | |
| <div class="result-actions"> | |
| <button class="action-btn toggle-fcc-btn" data-path="${item.original_path}" data-active="rgb" title="Toggle NIR False Color Composite (FCC)"> | |
| <i class="fa-solid fa-wand-magic-sparkles"></i> FCC | |
| </button> | |
| <button class="action-btn plot-spectral-btn" data-path="${item.original_path}" title="Plot Spectral Signature"> | |
| <i class="fa-solid fa-chart-line"></i> | |
| </button> | |
| </div> | |
| ` : ''; | |
| card.innerHTML = ` | |
| <div class="result-img-wrapper"> | |
| <img src="${item.gallery_path}" alt="${item.class}" id="img-result-${index}"> | |
| <span class="result-score-badge">S: ${score}</span> | |
| <span class="result-modality-tag">${item.modality}</span> | |
| ${actionHTML} | |
| </div> | |
| <div class="result-content"> | |
| <div class="result-meta-title"> | |
| <h4>${item.class}</h4> | |
| <span class="result-meta-rank">#0${rank}</span> | |
| </div> | |
| <div class="result-details"> | |
| <span><strong>Lat:</strong> ${lat}</span> | |
| <span><strong>Lon:</strong> ${lon}</span> | |
| <span><strong>Dist:</strong> ${dist}</span> | |
| <span><strong>H3:</strong> ${h3Cell}</span> | |
| </div> | |
| </div> | |
| `; | |
| resultsGrid.appendChild(card); | |
| // Add FCC toggle listener | |
| if (isMs) { | |
| const btn = card.querySelector(".toggle-fcc-btn"); | |
| btn.addEventListener("click", (e) => { | |
| e.stopPropagation(); | |
| const activeMode = btn.getAttribute("data-active"); | |
| const path = btn.getAttribute("data-path"); | |
| const imgEl = document.getElementById(`img-result-${index}`); | |
| if (activeMode === "rgb") { | |
| btn.setAttribute("data-active", "fcc"); | |
| btn.innerHTML = `<i class="fa-solid fa-image"></i> RGB`; | |
| imgEl.src = `/api/render-bands?path=${encodeURIComponent(path)}&bands=FCC`; | |
| } else { | |
| btn.setAttribute("data-active", "rgb"); | |
| btn.innerHTML = `<i class="fa-solid fa-wand-magic-sparkles"></i> FCC`; | |
| imgEl.src = `/api/render-bands?path=${encodeURIComponent(path)}&bands=RGB`; | |
| } | |
| }); | |
| // Add spectral plot listener | |
| const spectralBtn = card.querySelector(".plot-spectral-btn"); | |
| spectralBtn.addEventListener("click", () => { | |
| openSpectralModal(item.original_path, item.class); | |
| }); | |
| } | |
| // Render Table Row | |
| const row = document.createElement("tr"); | |
| row.innerHTML = ` | |
| <td><strong>0${rank}</strong></td> | |
| <td><img src="${item.gallery_path}" class="table-thumbnail"></td> | |
| <td><strong>${item.class}</strong></td> | |
| <td><span class="badge">${item.modality.toUpperCase()}</span></td> | |
| <td><code style="font-family:var(--font-mono)">${h3Cell}</code></td> | |
| <td><code>${lat}, ${lon}</code></td> | |
| <td>${dist}</td> | |
| <td class="table-score">${score}</td> | |
| `; | |
| resultsTableBody.appendChild(row); | |
| // Plot leaf-let marker | |
| if (item.lat && item.lon) { | |
| const pCoords = [item.lat, item.lon]; | |
| const marker = L.marker(pCoords).addTo(resultsMarkersGroup); | |
| // Marker popup with thumbnail | |
| marker.bindPopup(` | |
| <div style="width:140px; text-align:center;"> | |
| <img src="${item.gallery_path}" style="width:100%; border-radius:4px; margin-bottom:0.375rem;"> | |
| <strong>Rank #${rank} • ${item.class}</strong><br> | |
| Modality: ${item.modality}<br> | |
| Score: ${score}<br> | |
| Dist: ${dist} | |
| </div> | |
| `); | |
| mapBounds.extend(pCoords); | |
| // If H3 cell boundary polygon coordinates are returned, draw the hexagon | |
| if (item.h3_boundary) { | |
| L.polygon(item.h3_boundary, { | |
| color: "#10b981", | |
| fillColor: "#10b981", | |
| fillOpacity: 0.15, | |
| weight: 1 | |
| }).addTo(h3GridGroup); | |
| } | |
| } | |
| }); | |
| // Fit map bounds to show results | |
| if (map && results.some(item => item.lat && item.lon)) { | |
| map.fitBounds(mapBounds, { padding: [50, 50] }); | |
| } | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Spectral Modal & Graphing Logic | |
| // ------------------------------------------------------------------------- | |
| const modal = document.getElementById("spectral-modal"); | |
| const closeModalBtn = document.getElementById("close-modal-btn"); | |
| let spectralChartInstance = null; | |
| closeModalBtn.addEventListener("click", () => { | |
| modal.classList.remove("active"); | |
| }); | |
| function openSpectralModal(path, className) { | |
| modal.classList.add("active"); | |
| document.getElementById("modal-title").textContent = `Sentinel-2 Spectral Signature - ${className}`; | |
| fetch(`/api/spectral-signature?path=${encodeURIComponent(path)}`) | |
| .then(res => res.json()) | |
| .then(data => { | |
| renderSpectralChart(data.reflectance); | |
| }) | |
| .catch(err => { | |
| console.error(err); | |
| alert("Failed to load spectral bands."); | |
| }); | |
| } | |
| function renderSpectralChart(values) { | |
| const ctx = document.getElementById("spectral-chart").getContext("2d"); | |
| const bands = ["B01 (Aer)", "B02 (B)", "B03 (G)", "B04 (R)", "B05 (RE1)", "B06 (RE2)", "B07 (RE3)", "B08 (NIR)", "B08A (N2)", "B09 (WV)", "B10 (Cir)", "B11 (SW1)", "B12 (SW2)"]; | |
| if (spectralChartInstance) { | |
| spectralChartInstance.destroy(); | |
| } | |
| spectralChartInstance = new Chart(ctx, { | |
| type: "line", | |
| data: { | |
| labels: bands, | |
| datasets: [{ | |
| label: "Reflectance Index", | |
| data: values, | |
| borderColor: "#2563eb", | |
| backgroundColor: "rgba(37, 99, 235, 0.1)", | |
| borderWidth: 2, | |
| fill: true, | |
| tension: 0.3 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| title: { display: true, text: "Scaled Reflectance Value" } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // ------------------------------------------------------------------------- | |
| // System Benchmarks Tab Rendering | |
| // ------------------------------------------------------------------------- | |
| let benchmarksLoaded = false; | |
| let recallChartInstance = null; | |
| let latencyChartInstance = null; | |
| function loadBenchmarks() { | |
| if (benchmarksLoaded) return; | |
| fetch("/api/benchmarks") | |
| .then(res => res.json()) | |
| .then(data => { | |
| renderBenchmarksGrid(data); | |
| benchmarksLoaded = true; | |
| }) | |
| .catch(err => { | |
| console.error("Failed to load benchmarks", err); | |
| }); | |
| } | |
| function renderBenchmarksGrid(results) { | |
| // Render comparison table | |
| const tbody = document.getElementById("benchmarks-table-body"); | |
| tbody.innerHTML = ""; | |
| results.forEach(r => { | |
| const tr = document.createElement("tr"); | |
| tr.innerHTML = ` | |
| <td><strong>${r.model}</strong></td> | |
| <td>${r.same_r1.toFixed(3)}</td> | |
| <td><strong>${r.same_r5.toFixed(3)}</strong></td> | |
| <td>${r.same_r10.toFixed(3)}</td> | |
| <td>${r.cross_r1.toFixed(3)}</td> | |
| <td><strong>${r.cross_r5.toFixed(3)}</strong></td> | |
| <td>${r.cross_r10.toFixed(3)}</td> | |
| <td><code>${r.latency_ms.toFixed(0)} ms</code></td> | |
| `; | |
| tbody.appendChild(tr); | |
| }); | |
| // Plot recall chart | |
| const ctxRecall = document.getElementById("recall-chart").getContext("2d"); | |
| const labels = results.map(r => r.model); | |
| const sameR5 = results.map(r => r.same_r5); | |
| const crossR5 = results.map(r => r.cross_r5); | |
| if (recallChartInstance) recallChartInstance.destroy(); | |
| recallChartInstance = new Chart(ctxRecall, { | |
| type: "bar", | |
| data: { | |
| labels: labels, | |
| datasets: [ | |
| { | |
| label: "Same-Modal Recall@5", | |
| data: sameR5, | |
| backgroundColor: "rgba(37, 99, 235, 0.7)", | |
| borderColor: "#2563eb", | |
| borderWidth: 1 | |
| }, | |
| { | |
| label: "Cross-Modal Recall@5", | |
| data: crossR5, | |
| backgroundColor: "rgba(16, 185, 129, 0.7)", | |
| borderColor: "#10b981", | |
| borderWidth: 1 | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| max: 0.6, | |
| title: { display: true, text: "Score" } | |
| } | |
| } | |
| } | |
| }); | |
| // Plot latency chart | |
| const ctxLatency = document.getElementById("latency-chart").getContext("2d"); | |
| const latencies = results.map(r => r.latency_ms); | |
| if (latencyChartInstance) latencyChartInstance.destroy(); | |
| latencyChartInstance = new Chart(ctxLatency, { | |
| type: "bar", | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| label: "Average Search Latency (ms)", | |
| data: latencies, | |
| backgroundColor: "rgba(245, 158, 11, 0.7)", | |
| borderColor: "#f59e0b", | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| title: { display: true, text: "Latency (ms)" } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Sandbox Queries Click Handler | |
| // ------------------------------------------------------------------------- | |
| document.querySelectorAll(".sample-query-tile").forEach(tile => { | |
| tile.addEventListener("click", async () => { | |
| const path = tile.getAttribute("data-path"); | |
| const name = tile.getAttribute("data-name"); | |
| const type = tile.getAttribute("data-type"); | |
| // Visually toggle active loading state | |
| tile.style.borderColor = "var(--primary-color)"; | |
| try { | |
| // Fetch the image as a Blob and create a File object | |
| const res = await fetch(path); | |
| const blob = await res.blob(); | |
| const file = new File([blob], name, { type: blob.type || "image/png" }); | |
| // Set search mode to image query | |
| document.getElementById("mode-image-btn").click(); | |
| // Select target query modality dropdown | |
| document.getElementById("query-modality").value = type; | |
| // Load file to preview | |
| handleFile(file); | |
| // Automatically trigger search click | |
| searchBtn.click(); | |
| } catch (err) { | |
| console.error("Failed to load sample query file:", err); | |
| alert("Failed to load sample query file: " + err.message); | |
| } finally { | |
| setTimeout(() => { | |
| tile.style.borderColor = "#ced4da"; | |
| }, 1000); | |
| } | |
| }); | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // Rotating Header Banner | |
| // ------------------------------------------------------------------------- | |
| const bannerMsgs = document.querySelectorAll(".banner-msg"); | |
| if (bannerMsgs.length > 0) { | |
| let currentMsgIndex = 0; | |
| setInterval(() => { | |
| bannerMsgs[currentMsgIndex].classList.remove("active"); | |
| currentMsgIndex = (currentMsgIndex + 1) % bannerMsgs.length; | |
| bannerMsgs[currentMsgIndex].classList.add("active"); | |
| }, 5000); // rotates every 5 seconds | |
| } | |
| // ------------------------------------------------------------------------- | |
| // Interactive LinkedIn Profile Links | |
| // ------------------------------------------------------------------------- | |
| const members = ["ayush", "karan", "anurag"]; | |
| members.forEach(member => { | |
| const linkEl = document.getElementById(`${member}-linkedin-link`); | |
| const editEl = document.getElementById(`${member}-edit-linkedin`); | |
| const inputEl = document.getElementById(`${member}-linkedin-input`); | |
| function showLink(url) { | |
| linkEl.href = url; | |
| linkEl.style.display = "inline-block"; | |
| editEl.style.display = "inline-block"; | |
| inputEl.style.display = "none"; | |
| } | |
| function showInput() { | |
| linkEl.style.display = "none"; | |
| editEl.style.display = "none"; | |
| inputEl.style.display = "inline-block"; | |
| inputEl.value = localStorage.getItem(`${member}_linkedin_url`) || ""; | |
| } | |
| const savedUrl = localStorage.getItem(`${member}_linkedin_url`); | |
| if (savedUrl) { | |
| showLink(savedUrl); | |
| } else { | |
| showInput(); | |
| } | |
| inputEl.addEventListener("change", (e) => { | |
| let url = e.target.value.trim(); | |
| if (url) { | |
| if (!url.startsWith("http://") && !url.startsWith("https://")) { | |
| url = "https://" + url; | |
| } | |
| localStorage.setItem(`${member}_linkedin_url`, url); | |
| showLink(url); | |
| } | |
| }); | |
| editEl.addEventListener("click", (e) => { | |
| e.preventDefault(); | |
| showInput(); | |
| }); | |
| }); | |
| // ------------------------------------------------------------------------- | |
| // Benchmarks Simulator Logic | |
| // ------------------------------------------------------------------------- | |
| const simWeight = document.getElementById("sim-modality-weight"); | |
| const simNoise = document.getElementById("sim-noise-level"); | |
| const simH3Res = document.getElementById("sim-h3-resolution"); | |
| function updateSimulation() { | |
| if (!simWeight) return; | |
| const w = parseFloat(simWeight.value); | |
| const n = parseFloat(simNoise.value); | |
| const res = parseInt(simH3Res.value); | |
| document.getElementById("sim-weight-val").innerText = w.toFixed(2); | |
| document.getElementById("sim-noise-val").innerText = n.toFixed(2); | |
| // Simulated precision recall curves formulas based on ZS-MC characteristics | |
| const baseR1 = 0.45 + 0.12 * Math.sin(w * Math.PI) - 0.3 * n; | |
| const r1 = Math.max(0.1, Math.min(0.99, baseR1)); | |
| const r5 = Math.max(r1 + 0.05, Math.min(0.99, r1 * 1.15 + 0.05)); | |
| const mapVal = Math.max(r5 + 0.05, Math.min(0.99, r5 * 1.12 + 0.04)); | |
| // Latency depends on H3 resolution level | |
| const latency = Math.round(28 + (8 - res) * 4 + w * 2 + n * 2); | |
| document.getElementById("sim-metric-r1").innerText = (r1 * 100).toFixed(1) + "%"; | |
| document.getElementById("sim-metric-r5").innerText = (r5 * 100).toFixed(1) + "%"; | |
| document.getElementById("sim-metric-map").innerText = (mapVal * 100).toFixed(1) + "%"; | |
| document.getElementById("sim-metric-latency").innerText = latency + " ms"; | |
| } | |
| if (simWeight) { | |
| simWeight.addEventListener("input", updateSimulation); | |
| simNoise.addEventListener("input", updateSimulation); | |
| simH3Res.addEventListener("change", updateSimulation); | |
| updateSimulation(); | |
| } | |
| }); | |