Demo-2025 / demo.html
zye0616's picture
update: added new yolo model for UAV detections
5995290
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Mission Console</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css">
<style>
body {
margin: 0;
padding: 0;
font-family: "Inter", Arial, sans-serif;
background-color: #121212;
color: #e0e0e0;
}
h1 {
text-align: center;
padding: 20px;
font-size: 26px;
color: #f5f5f5;
margin-bottom: 10px;
}
.container {
width: 90%;
max-width: 1100px;
margin: auto;
background: #1e1e1e;
padding: 25px;
border-radius: 12px;
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.25);
}
.form-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-top: 10px;
}
.form-control {
flex: 1;
min-width: 260px;
}
.label {
font-size: 15px;
font-weight: 600;
display: block;
color: #bdbdbd;
}
input[type="text"] {
padding: 12px;
border: 1px solid #2c2c2c;
background-color: #161616;
color: #fff;
font-size: 15px;
border-radius: 8px;
width: 100%;
margin-top: 5px;
}
input[type="file"],
select {
margin-top: 5px;
padding: 10px;
background-color: #161616;
border: 1px solid #2c2c2c;
border-radius: 8px;
color: #f5f5f5;
width: 100%;
}
button {
margin-top: 25px;
width: 100%;
padding: 14px;
background-color: #333333;
border: none;
color: #f5f5f5;
font-size: 16px;
font-weight: 600;
border-radius: 8px;
cursor: pointer;
transition: 0.2s;
}
button:hover {
background-color: #4f4f4f;
}
button:disabled {
background-color: #1f1f1f;
color: #6e6e6e;
cursor: not-allowed;
}
.secondary-button {
width: auto;
padding: 10px 18px;
margin-top: 10px;
background-color: #262626;
}
.secondary-button:hover {
background-color: #3a3a3a;
}
.secondary-button.small {
padding: 8px 14px;
font-size: 14px;
}
.prompt-actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 8px;
}
.location-section {
margin-top: 20px;
padding: 16px;
border: 1px solid #2c2c2c;
border-radius: 10px;
background: #141414;
}
.location-hint {
font-size: 13px;
color: #bdbdbd;
margin-top: 6px;
}
.location-map-container {
margin-top: 12px;
height: 260px;
border: 1px solid #2c2c2c;
border-radius: 8px;
overflow: hidden;
}
#missionMap {
height: 100%;
width: 100%;
}
.location-readout {
display: flex;
gap: 20px;
margin-top: 12px;
font-size: 14px;
}
.output-section {
margin-top: 35px;
padding: 20px;
border-radius: 12px;
background-color: #171717;
border: 1px solid #2a2a2a;
}
#summary {
margin-top: 10px;
padding: 14px;
border: 1px solid #2a2a2a;
background-color: #111;
color: #d1d1d1;
font-size: 15px;
min-height: 70px;
white-space: pre-wrap;
border-radius: 8px;
}
.video-section {
margin-top: 20px;
display: flex;
flex-wrap: wrap;
gap: 25px;
}
.video-panel {
flex: 1;
min-width: 320px;
}
.video-panel h2 {
color: #00ffea;
margin-bottom: 10px;
font-size: 18px;
}
.mission-video {
width: 100%;
max-height: 420px;
object-fit: contain;
border: 1px solid #2a2a2a;
border-radius: 8px;
background-color: #000;
}
.status-message {
margin-top: 15px;
color: #ffa95c;
font-size: 13px;
min-height: 18px;
}
.status-message.small {
font-size: 12px;
margin-top: 10px;
}
</style>
</head>
<body>
<h1>Mission Console</h1>
<div class="container">
<div class="form-grid">
<div class="form-control">
<label class="label">MISSION PROMPT</label>
<input id="missionPrompt" type="text" placeholder="e.g., Track hostile drone movement...">
<div class="prompt-actions">
<button type="button" id="setMissionButton" class="secondary-button" onclick="stageMissionPrompt()">Set Mission Prompt</button>
</div>
</div>
<div class="form-control">
<label class="label">UPLOAD VIDEO (.mp4)</label>
<input id="videoInput" type="file" accept="video/mp4" disabled>
</div>
</div>
<div class="location-section">
<label class="label">MISSION LOCATION</label>
<div class="location-hint">We will auto-detect your GPS (if allowed). Otherwise, click anywhere on the map to select the mission location.</div>
<div id="missionMapContainer" class="location-map-container">
<div id="missionMap"></div>
</div>
<div class="location-readout">
<div>Latitude: <span id="latitudeDisplay">--</span></div>
<div>Longitude: <span id="longitudeDisplay">--</span></div>
</div>
<div id="locationStatus" class="status-message small"></div>
<div id="locationIntel" class="status-message small" style="margin-top:6px;"></div>
</div>
<label class="label">OBJECT DETECTOR</label>
<select id="detectorSelect">
<option value="owlv2" selected>OWL-V2</option>
<option value="hf_yolov8">YOLOv8</option>
<option value="hf_yolov8_defence">YOLOv8m Defence</option>
<option value="hf_yolov12_bot_sort">YOLOv12 BoT-SORT + ReID</option>
</select>
<button type="button" id="executeButton" onclick="executeMission()" disabled>EXECUTE MISSION</button>
<div class="output-section">
<div class="video-section">
<div class="video-panel">
<h2>ORIGINAL VIDEO</h2>
<video id="originalVideo" class="mission-video" controls></video>
</div>
<div class="video-panel">
<h2>PROCESSED VIDEO FEED</h2>
<video id="processedVideo" class="mission-video" controls></video>
</div>
</div>
<h2 style="color:#00ffea; margin:25px 0 10px;">MISSION SUMMARY</h2>
<div id="summary">(Awaiting mission results...)</div>
<div id="status" class="status-message"></div>
</div>
</div>
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
<script>
const API_BASE_URL = "https://biaslab2025-demo-2025.hf.space";
const PROCESS_VIDEO_URL = `${API_BASE_URL}/process_video`;
const SUMMARY_URL = `${API_BASE_URL}/mission_summary`;
const MISSION_PLAN_URL = `${API_BASE_URL}/mission_plan`;
const LOCATION_INTEL_URL = `${API_BASE_URL}/location_context`;
const missionInputEl = document.getElementById("missionPrompt");
const detectorSelectEl = document.getElementById("detectorSelect");
const originalVideoEl = document.getElementById("originalVideo");
const videoInputEl = document.getElementById("videoInput");
const summaryEl = document.getElementById("summary");
const statusEl = document.getElementById("status");
const setMissionButton = document.getElementById("setMissionButton");
const executeButton = document.getElementById("executeButton");
const latitudeDisplayEl = document.getElementById("latitudeDisplay");
const longitudeDisplayEl = document.getElementById("longitudeDisplay");
const locationStatusEl = document.getElementById("locationStatus");
const locationIntelEl = document.getElementById("locationIntel");
let originalVideoUrl = null;
let currentMissionId = null;
let missionReady = false;
let missionRequestPending = false;
let videoProcessing = false;
let locationReady = false;
let selectedLat = null;
let selectedLon = null;
let missionMap = null;
let missionMapMarker = null;
missionInputEl.addEventListener("input", handleMissionPromptEdit);
videoInputEl.addEventListener("change", () => {
const uploaded = videoInputEl.files[0];
if (uploaded) {
setOriginalPreview(uploaded);
} else if (originalVideoUrl) {
URL.revokeObjectURL(originalVideoUrl);
originalVideoEl.removeAttribute("src");
originalVideoEl.load();
originalVideoUrl = null;
}
});
initializeMissionMap();
attemptAutoGps();
function initializeMissionMap() {
if (missionMap) {
return;
}
missionMap = L.map("missionMap").setView([20, 0], 2);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; OpenStreetMap contributors",
}).addTo(missionMap);
missionMap.on("click", (event) => {
setSelectedLocation(event.latlng.lat, event.latlng.lng, "map");
});
}
function attemptAutoGps() {
if (!navigator.geolocation) {
setLocationStatus("GPS unavailable. Click on the map to choose a location.", "error");
return;
}
setLocationStatus("Detecting GPS location...");
navigator.geolocation.getCurrentPosition(
(position) => {
setSelectedLocation(position.coords.latitude, position.coords.longitude, "gps");
},
(error) => {
setLocationStatus(`GPS failed (${error.message}). Click on the map to select a location.`, "error");
},
{ enableHighAccuracy: true, timeout: 12000, maximumAge: 0 }
);
}
function setSelectedLocation(lat, lon, source) {
selectedLat = lat;
selectedLon = lon;
locationReady = true;
placeMapMarker(lat, lon);
updateLocationDisplay();
setLocationStatus(
`Location set via ${source.toUpperCase()}: ${lat.toFixed(4)}, ${lon.toFixed(4)}`,
"success"
);
if (currentMissionId) {
missionReady = false;
currentMissionId = null;
setStatus("Mission location changed. Set mission prompt again.");
}
updateControlState();
sendLocationIntel(lat, lon);
}
function updateLocationDisplay() {
latitudeDisplayEl.textContent = selectedLat !== null ? selectedLat.toFixed(4) : "--";
longitudeDisplayEl.textContent = selectedLon !== null ? selectedLon.toFixed(4) : "--";
}
function placeMapMarker(lat, lon) {
if (!missionMap) {
return;
}
const point = [lat, lon];
if (!missionMapMarker) {
missionMapMarker = L.circleMarker(point, {
radius: 8,
color: "#ff4d4d",
fillColor: "#ff4d4d",
fillOpacity: 0.95,
weight: 2,
}).addTo(missionMap);
} else {
missionMapMarker.setLatLng(point);
}
const targetZoom = missionMap.getZoom() < 8 ? 8 : missionMap.getZoom();
missionMap.setView(point, targetZoom);
}
function handleMissionPromptEdit() {
if (!missionReady && !currentMissionId) {
return;
}
missionReady = false;
currentMissionId = null;
setStatus("Mission prompt changed. Set it again to process videos.");
updateControlState();
}
function updateControlState() {
const disableVideoActions = !missionReady || missionRequestPending || videoProcessing;
videoInputEl.disabled = disableVideoActions;
executeButton.disabled = disableVideoActions;
setMissionButton.disabled = missionRequestPending || !locationReady;
}
function setStatus(message, tone = "info") {
statusEl.textContent = message || "";
if (tone === "error") {
statusEl.style.color = "#ff7b7b";
} else if (tone === "success") {
statusEl.style.color = "#7bffb3";
} else {
statusEl.style.color = "#ffa95c";
}
}
function setLocationStatus(message, tone = "info") {
locationStatusEl.textContent = message || "";
if (tone === "error") {
locationStatusEl.style.color = "#ff7b7b";
} else if (tone === "success") {
locationStatusEl.style.color = "#7bffb3";
} else {
locationStatusEl.style.color = "#bdbdbd";
}
}
let locationIntelAbortController = null;
async function sendLocationIntel(lat, lon) {
if (locationIntelAbortController) {
locationIntelAbortController.abort();
}
locationIntelAbortController = new AbortController();
locationIntelEl.textContent = "Analyzing location-based threats...";
locationIntelEl.style.color = "#ffa95c";
try {
const form = new FormData();
form.append("latitude", lat.toString());
form.append("longitude", lon.toString());
const response = await fetch(LOCATION_INTEL_URL, {
method: "POST",
body: form,
signal: locationIntelAbortController.signal,
});
if (!response.ok) {
throw new Error(`Location intelligence failed (${response.status})`);
}
const payload = await response.json();
const classes = (payload.mission_plan?.classes || []).slice(0, 4);
if (!classes.length) {
locationIntelEl.textContent = "Location intel: no threat classes suggested.";
locationIntelEl.style.color = "#bdbdbd";
return;
}
const classSummary = classes
.map((entry) => entry.name)
.join(", ");
locationIntelEl.textContent = `Likely threats near this location: ${classSummary}`;
locationIntelEl.style.color = "#7bffb3";
} catch (error) {
if (error.name === "AbortError") {
return;
}
console.error("[location_intel] error", error);
locationIntelEl.textContent = error.message || "Failed to analyze location.";
locationIntelEl.style.color = "#ff7b7b";
}
}
async function stageMissionPrompt() {
const mission = missionInputEl.value.trim();
if (!mission) {
alert("Enter a mission prompt before setting it.");
return;
}
if (!locationReady || selectedLat === null || selectedLon === null) {
alert("Set mission location via GPS or the map before proceeding.");
return;
}
missionRequestPending = true;
missionReady = false;
currentMissionId = null;
updateControlState();
setStatus("Preparing mission prompt...");
try {
const form = new FormData();
form.append("prompt", mission);
form.append("detector", detectorSelectEl.value);
form.append("latitude", selectedLat.toString());
form.append("longitude", selectedLon.toString());
const response = await fetch(MISSION_PLAN_URL, {
method: "POST",
body: form,
});
if (!response.ok) {
let errorDetail = `Failed to set mission prompt (${response.status})`;
try {
const errJson = await response.json();
errorDetail = errJson.detail || errJson.error || errorDetail;
} catch (_) {}
throw new Error(errorDetail);
}
const payload = await response.json();
currentMissionId = payload.mission_id;
missionReady = true;
setStatus("Mission prompt ready. Upload videos to process.", "success");
} catch (error) {
console.error("[mission_plan] error", error);
setStatus(error.message || "Failed to set mission prompt.", "error");
} finally {
missionRequestPending = false;
updateControlState();
}
}
async function executeMission() {
const videoFile = videoInputEl.files[0];
const mission = missionInputEl.value.trim();
const detector = detectorSelectEl.value;
if (!currentMissionId || !missionReady) {
alert("Set the mission prompt before processing videos.");
return;
}
if (!videoFile) {
alert("Mission invalid: Upload a video file.");
return;
}
setOriginalPreview(videoFile);
summaryEl.textContent = "(Awaiting summary...)";
videoProcessing = true;
updateControlState();
setStatus("Processing video...");
try {
const videoForm = new FormData();
videoForm.append("video", videoFile);
videoForm.append("prompt", mission);
videoForm.append("mission_id", currentMissionId);
videoForm.append("latitude", selectedLat?.toString() || "");
videoForm.append("longitude", selectedLon?.toString() || "");
videoForm.append("detector", detector);
console.log("[process_video] submitting request", { detector, missionLength: mission.length, fileSize: videoFile.size });
const response = await fetch(PROCESS_VIDEO_URL, {
method: "POST",
body: videoForm
});
console.log(
"[process_video] response meta",
response.status,
response.statusText,
{ contentLength: response.headers.get("content-length"), contentType: response.headers.get("content-type") }
);
if (!response.ok) {
let errorDetail = `Request failed (${response.status})`;
try {
const errJson = await response.json();
errorDetail = errJson.error || errorDetail;
} catch (_) {
// ignore
}
throw new Error(errorDetail);
}
const videoBlob = await response.blob();
console.log("[process_video] blob size", videoBlob.size, videoBlob.type);
const videoUrl = URL.createObjectURL(videoBlob);
const videoEl = document.getElementById("processedVideo");
videoEl.addEventListener("loadeddata", () => {
console.log("[process_video] video loadeddata event", videoEl.readyState);
}, { once: true });
videoEl.addEventListener("error", (event) => {
console.error("[process_video] video error", videoEl.error, event);
}, { once: true });
videoEl.src = videoUrl;
videoEl.load();
console.log("[process_video] video element readyState after load()", videoEl.readyState);
if (videoEl.error) {
console.error("[process_video] immediate video error", videoEl.error);
}
setStatus("Generating summary...");
const summaryForm = new FormData();
summaryForm.append("video", videoFile);
summaryForm.append("prompt", mission);
summaryForm.append("mission_id", currentMissionId);
summaryForm.append("latitude", selectedLat?.toString() || "");
summaryForm.append("longitude", selectedLon?.toString() || "");
summaryForm.append("detector", detector);
console.log("[mission_summary] submitting request", { detector, missionLength: mission.length, fileSize: videoFile.size });
const summaryResponse = await fetch(SUMMARY_URL, {
method: "POST",
body: summaryForm
});
if (!summaryResponse.ok) {
let errorDetail = `Summary failed (${summaryResponse.status})`;
try {
const errJson = await summaryResponse.json();
errorDetail = errJson.error || errorDetail;
} catch (_) {}
throw new Error(errorDetail);
}
console.log(
"[mission_summary] response meta",
summaryResponse.status,
summaryResponse.statusText,
{ contentLength: summaryResponse.headers.get("content-length"), contentType: summaryResponse.headers.get("content-type") }
);
const summaryJson = await summaryResponse.json();
const summaryText = summaryJson.mission_summary || "No summary returned.";
summaryEl.textContent = summaryText;
setStatus("Mission complete.", "success");
} catch (err) {
console.error(err);
summaryEl.textContent = "Mission failed.";
setStatus(`Error: ${err.message}`, "error");
} finally {
videoProcessing = false;
updateControlState();
}
}
function setOriginalPreview(file) {
if (originalVideoUrl) {
URL.revokeObjectURL(originalVideoUrl);
}
originalVideoUrl = URL.createObjectURL(file);
originalVideoEl.src = originalVideoUrl;
originalVideoEl.load();
}
setLocationStatus("Awaiting GPS permission or map selection...");
updateControlState();
</script>
</body>
</html>