webapp1 commited on
Commit
fe8034e
·
verified ·
1 Parent(s): ae81151

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +228 -207
templates/index.html CHANGED
@@ -284,7 +284,7 @@
284
  <i class="fas fa-eye text-indigo-600 dark:text-purple-300 text-xl"></i>
285
  </div>
286
  <h4 class="text-sm font-black uppercase tracking-wider text-indigo-950 dark:text-purple-50 mb-2">Spatial Engine</h4>
287
- <p class="text-xs text-indigo-800/70 dark:text-purple-100 leading-relaxed font-bold">YOLOv8 Medium with XAI bounding boxes.</p>
288
  </div>
289
  <div class="ios-glass p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm cursor-default">
290
  <div class="w-12 h-12 rounded-2xl bg-fuchsia-100 dark:bg-white/10 flex items-center justify-center mb-5 border border-fuchsia-200/50 dark:border-white/10 shadow-sm">
@@ -317,20 +317,40 @@
317
 
318
  <!-- Interactive Media Player Area -->
319
  <div class="rounded-[32px] overflow-hidden shadow-2xl border border-white/50 dark:border-white/20 relative bg-black aspect-video flex items-center justify-center group">
320
- <!-- Display Image -->
321
  <img id="previewImg" src="" class="w-full h-full object-contain z-10 hidden" alt="Detection View">
322
- <!-- Standard Video Player -->
323
- <video id="previewVideo" controls class="w-full h-full object-contain hidden z-10 bg-black"></video>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
  <!-- Live Tag -->
326
- <div class="absolute top-6 left-6 z-20">
327
  <span id="liveBadge" class="bg-black/60 backdrop-blur-xl text-white text-[10px] px-3 py-1.5 rounded-xl font-black uppercase tracking-widest border border-white/20 flex items-center gap-2">
328
- <span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> XAI SNAPSHOT
329
  </span>
330
  </div>
331
  </div>
332
 
333
- <!-- Media Controls (Moved completely underneath the video) -->
334
  <div class="flex justify-center gap-4 mt-8" id="mediaControls">
335
  <button onclick="takeSnapshot()" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2">
336
  <i class="fas fa-camera"></i> Snapshot
@@ -339,7 +359,7 @@
339
  <i class="fas fa-brain"></i> AI Tracking
340
  </button>
341
  <button onclick="goOriginal()" id="oBtn" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2">
342
- <i class="fas fa-play"></i> Original
343
  </button>
344
  </div>
345
 
@@ -734,268 +754,269 @@
734
  if(document.getElementById('vLoc')) document.getElementById('vLoc').innerText = "Not Supported";
735
  }
736
  const now = new Date();
737
- if(document.getElementById('vTime')) document.getElementById('vTime').innerText = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'});
738
  }
739
 
740
- fetchRealLocationData();
741
-
742
- const fileInput = document.getElementById('fileInput');
743
- const uploadSection = document.getElementById('uploadSection');
744
- const resultsDiv = document.getElementById('results');
745
- const emergencyBanner = document.getElementById('emergencyBanner');
746
- const ipCamModal = document.getElementById('ipCamModal');
747
- const predictiveModal = document.getElementById('predictiveModal');
748
-
749
  let streamId = null;
750
  let poller = null;
751
 
752
- // Clean UI Start Flow
753
- fileInput.addEventListener('change', () => {
754
- if(fileInput.files.length > 0) {
755
- startStream('file', fileInput.files[0]);
756
- }
757
- });
 
 
 
 
 
 
 
 
 
758
 
759
  async function connectIpCam() {
760
- const url = document.getElementById('ipCamUrl').value.trim();
761
  if(!url) return;
762
  closeIpCamModal();
763
- startStream('live');
764
  }
765
 
766
- async function startStream(type, file = null) {
767
- // Instantly transition out of the upload menu and show the dashboard!
768
- uploadSection.classList.add('hidden');
769
- resultsDiv.classList.remove('hidden');
770
-
771
  const fd = new FormData();
772
- let res;
773
- if(type === 'file') {
774
- fd.append('file', file);
775
- res = await fetch('/init_upload', {method: 'POST', body: fd});
776
- } else {
777
- res = await fetch('/init_stream', {
778
- method: 'POST',
779
- headers: {'Content-Type': 'application/json'},
780
- body: JSON.stringify({url: document.getElementById('ipCamUrl').value})
781
- });
782
- }
783
 
 
 
 
 
 
 
 
784
  const data = await res.json();
785
  streamId = data.stream_id;
786
 
787
- if(type === 'file') {
788
- if(document.getElementById('vLoc')) document.getElementById('vLoc').innerText = "--";
789
- if(document.getElementById('vWx')) document.getElementById('vWx').innerText = "--";
790
- if(document.getElementById('vTime')) document.getElementById('vTime').innerText = "--";
791
  } else {
792
- if(document.getElementById('vLoc')) document.getElementById('vLoc').innerText = data.location || "--";
793
- if(document.getElementById('vWx')) document.getElementById('vWx').innerText = data.weather || "--";
794
- if(document.getElementById('vTime')) document.getElementById('vTime').innerText = data.time || "--";
795
  }
796
-
797
- // Instantly unhide tracking box and begin stream fetching
 
 
 
798
  activateTrackingUI();
799
 
800
- // Set the source directly so the image connects right away
801
  const locText = document.getElementById('vLoc') ? document.getElementById('vLoc').innerText : "Unknown";
802
  document.getElementById('previewImg').src = `/video_feed/${streamId}?loc=${encodeURIComponent(locText)}&t=${Date.now()}`;
 
803
 
804
  if(poller) clearInterval(poller);
805
  poller = setInterval(update, 1000);
806
  }
807
 
808
  function activateTrackingUI() {
809
- const ai = document.getElementById('previewImg');
810
- ai.classList.remove('hidden');
811
- document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-fuchsia-500 animate-pulse shadow-[0_0_8px_fuchsia]"></span> LIVE AI TRACKING';
 
 
 
812
  if(document.getElementById('tBtn')) document.getElementById('tBtn').classList.add('opacity-50');
813
  if(document.getElementById('oBtn')) document.getElementById('oBtn').classList.remove('opacity-50');
814
  }
815
 
816
  function goTracking() { activateTrackingUI(); }
817
- function goOriginal() { alert("Raw video toggle requires caching. Please use XAI feed."); }
 
 
 
 
 
 
 
 
 
 
818
 
819
  function takeSnapshot() {
820
- let feed = document.getElementById('previewImg');
821
- if(feed && !feed.classList.contains('hidden')) {
 
 
 
 
 
 
822
  const c = document.createElement('canvas');
823
- c.width = feed.naturalWidth || feed.videoWidth; c.height = feed.naturalHeight || feed.videoHeight;
 
824
  c.getContext('2d').drawImage(feed, 0, 0, c.width, c.height);
825
- window.open().document.write(`<img src="${c.toDataURL()}">`);
 
 
 
 
 
 
826
  }
827
  }
828
 
829
- // --- Predictive Modal Functions ---
830
- function openIpCamModal() { ipCamModal.classList.remove('hidden'); }
831
- function closeIpCamModal() { ipCamModal.classList.add('hidden'); document.getElementById('ipCamUrl').value = ''; }
832
- function openPredictModal() { predictiveModal.classList.remove('hidden'); resetPredictModal(); }
833
- function closePredictModal() { predictiveModal.classList.add('hidden'); }
834
- function resetPredictModal() {
835
- document.getElementById('predictiveFormView').classList.remove('hidden');
836
- document.getElementById('predictiveResultView').classList.add('hidden');
837
  }
838
 
839
- async function submitPredictiveData(e) {
840
- e.preventDefault();
841
- const btn = document.getElementById('predictBtn');
842
- const spinner = document.getElementById('predictSpinner');
843
- btn.disabled = true; spinner.classList.remove('hidden');
844
-
845
- const payload = {
846
- Weather: document.getElementById('tab_weather').value, Road_Type: document.getElementById('tab_road_type').value,
847
- Time_of_Day: document.getElementById('tab_time').value, Traffic_Density: document.getElementById('tab_density').value,
848
- Speed_Limit: document.getElementById('tab_speed').value, Number_of_Vehicles: document.getElementById('tab_vehicles').value,
849
- Driver_Alcohol: document.getElementById('tab_alcohol').value, Road_Condition: document.getElementById('tab_road_cond').value,
850
- Vehicle_Type: document.getElementById('tab_vehicle').value, Driver_Age: document.getElementById('tab_age').value,
851
- Driver_Experience: document.getElementById('tab_exp').value, Road_Light_Condition: document.getElementById('tab_light').value
852
- };
853
-
854
- try {
855
- const response = await fetch('/predict_traffic_risk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
856
- const data = await response.json();
857
- if (data.error) throw new Error(data.error);
858
-
859
- document.getElementById('predictiveFormView').classList.add('hidden');
860
- document.getElementById('predictiveResultView').classList.remove('hidden');
861
-
862
- const riskProb = data.risk_probability_percentage;
863
- const willHappen = data.will_accident_happen;
864
-
865
- const badge = document.getElementById('riskBadge');
866
- const icon = document.getElementById('riskIcon');
867
- document.getElementById('riskLabelText').innerText = data.status;
868
-
869
- if (willHappen || riskProb >= 50) {
870
- badge.className = "inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm bg-red-100 text-red-600 border-red-300 dark:bg-red-500/20 dark:text-red-400 dark:border-red-500/30";
871
- icon.className = "fas fa-exclamation-triangle";
872
- } else {
873
- badge.className = "inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm bg-emerald-100 text-emerald-600 border-emerald-300 dark:bg-emerald-500/20 dark:text-emerald-400 dark:border-emerald-500/30";
874
- icon.className = "fas fa-shield-check";
875
- }
876
- animateValue("riskProbabilityValue", 0, Math.floor(riskProb), 1000);
877
- } catch (err) { alert(`Error: ${err.message}`); resetPredictModal(); }
878
- finally { btn.disabled = false; spinner.classList.add('hidden'); }
879
  }
880
 
881
  async function update() {
882
  if(!streamId) return;
883
  try {
884
- const r = await fetch(`/stream_status/${streamId}`);
885
- const d = await r.json();
886
- if(!d.label) return;
887
-
888
- document.getElementById('confVal').innerText = d.confidence;
889
- document.getElementById('cnnVal').innerText = d.cnn + '%';
890
- document.getElementById('rcnnVal').innerText = d.rcnn + '%';
891
- document.getElementById('cnnBar').style.width = d.cnn + '%';
892
- document.getElementById('rcnnBar').style.width = d.rcnn + '%';
893
-
894
- document.getElementById('labelText').innerText = d.label;
895
- const statusBox = document.getElementById('statusCard');
896
- const statusBadge = document.getElementById('statusBadge');
897
  const statusIcon = document.getElementById('statusIcon');
898
- const severityLevel = document.getElementById('severityLevel');
899
-
900
- // Parse distinct backend logic states accurately
901
- const isError = d.label.includes("Failed") || d.label.includes("Error") || d.label.includes("Missing");
902
- const isFinished = d.label.includes("No accident detected") || d.label.includes("Finished");
903
- const isAnalyzing = d.label.includes("Analyzing") || d.label.includes("Scanning") || d.label.includes("Buffering");
904
-
905
- if(d.is_accident) {
906
- statusBox.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-red-500 bg-red-50/50 dark:bg-red-900/20 transition-colors duration-500";
907
- statusBadge.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] shadow-sm bg-red-100 text-red-600 border border-red-300 dark:bg-red-500/20 dark:text-red-400";
908
- statusIcon.className = "fas fa-skull-crossbones";
909
- if(severityLevel) {
910
- severityLevel.innerText = d.severity;
911
- severityLevel.className = "font-black text-sm uppercase text-red-600 dark:text-red-300";
912
- }
913
- if(d.alert_sent && document.getElementById('emergencyBanner')) document.getElementById('emergencyBanner').classList.remove('hidden');
914
- } else if(isError) {
915
- statusBox.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-orange-500 transition-colors duration-500";
916
- statusBadge.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] shadow-sm bg-orange-100 text-orange-600 border border-orange-300 dark:bg-orange-500/20 dark:text-orange-400";
917
- statusIcon.className = "fas fa-exclamation-triangle";
918
- if(severityLevel) {
919
- severityLevel.innerText = "Offline";
920
- severityLevel.className = "font-black text-[10px] uppercase text-orange-600 dark:text-orange-400";
921
- }
922
- } else if(isAnalyzing) {
923
- statusBox.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-indigo-500 transition-colors duration-500";
924
- statusBadge.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] border border-indigo-200 shadow-sm bg-white text-indigo-600 dark:bg-indigo-500/20 dark:text-indigo-300";
925
- statusIcon.className = "fas fa-spinner fa-spin";
926
- if(severityLevel) {
927
- severityLevel.innerText = "Calculating...";
928
- severityLevel.className = "font-black text-[10px] uppercase text-indigo-500 animate-pulse";
929
- }
930
- } else if(isFinished) {
931
- statusBox.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-emerald-500 bg-emerald-50/30 transition-colors duration-500";
932
- statusBadge.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] border border-emerald-200 shadow-sm bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400";
933
- statusIcon.className = "fas fa-check-double";
934
- if(severityLevel) {
935
- severityLevel.innerText = "Clear";
936
- severityLevel.className = "font-black text-[10px] uppercase text-emerald-600";
937
  }
938
  } else {
939
- // Standard Monitoring
940
- statusBox.className = "ios-glass-inner p-8 rounded-[32px] mb-8 shadow-sm dark:shadow-lg relative overflow-hidden border-l-4 border-emerald-500 transition-colors duration-500";
941
- statusBadge.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-xl text-[10px] font-black mb-4 uppercase tracking-[0.2em] border border-emerald-200 dark:border-emerald-500/30 shadow-sm bg-emerald-100 text-emerald-600 dark:bg-emerald-500/20 dark:text-emerald-400";
942
- statusIcon.className = "fas fa-shield-check";
943
- if(severityLevel) {
944
- severityLevel.innerText = "Monitoring...";
945
- severityLevel.className = "font-black text-[10px] uppercase text-emerald-600 animate-pulse";
946
- }
947
  }
948
 
949
- const alprSection = document.getElementById('alprSection');
950
- const pBox = document.getElementById('alprContainer');
951
- pBox.innerHTML = '';
952
- let validPlates = false;
953
-
954
- if (d.plates && d.plates.length > 0) {
955
- d.plates.forEach(p => {
956
- if (p.text && p.text !== "NO CLEAR PLATES") {
957
- validPlates = true;
958
- const div = document.createElement('div');
959
- div.className = "bg-yellow-400 text-black p-2 rounded-xl shadow-sm border border-yellow-500 flex items-center gap-4";
960
- let img = p.image ? `<img src="data:image/jpeg;base64,${p.image}" class="h-10 rounded-lg border border-black/20 bg-white object-contain shadow-inner">` : "";
961
- div.innerHTML = `${img} <span class="font-black text-xl tracking-widest px-2">${p.text}</span>`;
962
- pBox.appendChild(div);
963
- }
964
- });
965
  }
966
- if(validPlates) alprSection.classList.remove('hidden');
967
- else alprSection.classList.add('hidden');
968
-
969
  } catch(e) {}
970
  }
971
 
972
  function resetApp() {
973
- if(fileInput) fileInput.value = '';
974
- if(emergencyBanner) emergencyBanner.classList.add('hidden');
975
- if(uploadSection) uploadSection.classList.remove('hidden');
976
- if(resultsDiv) resultsDiv.classList.add('hidden');
977
  if(poller) clearInterval(poller);
 
978
  document.getElementById('previewImg').src = "";
979
- fetchRealLocationData();
 
 
 
 
980
  }
981
 
982
- function animateValue(id, start, end, duration) {
983
- let current = start;
984
- const range = end - start;
985
- const obj = document.getElementById(id);
986
- if(!obj) return;
987
- if(range === 0) { obj.innerHTML = end; return; }
988
- const increment = end > start ? 1 : -1;
989
- const stepTime = Math.abs(Math.floor(duration / range));
990
- const timer = setInterval(function() {
991
- current += increment;
992
- obj.innerHTML = current;
993
- if ((increment > 0 && current >= end) || (increment < 0 && current <= end)) {
994
- clearInterval(timer);
995
- obj.innerHTML = end;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
996
  }
997
- }, stepTime);
 
 
 
 
 
 
998
  }
 
 
 
999
  </script>
1000
  </body>
1001
  </html>
 
284
  <i class="fas fa-eye text-indigo-600 dark:text-purple-300 text-xl"></i>
285
  </div>
286
  <h4 class="text-sm font-black uppercase tracking-wider text-indigo-950 dark:text-purple-50 mb-2">Spatial Engine</h4>
287
+ <p class="text-xs text-indigo-800/70 dark:text-purple-100 leading-relaxed font-bold">YOLOv8 XAI Tracking overlays.</p>
288
  </div>
289
  <div class="ios-glass p-6 rounded-[32px] hover:-translate-y-1 transition-transform duration-300 shadow-sm cursor-default">
290
  <div class="w-12 h-12 rounded-2xl bg-fuchsia-100 dark:bg-white/10 flex items-center justify-center mb-5 border border-fuchsia-200/50 dark:border-white/10 shadow-sm">
 
317
 
318
  <!-- Interactive Media Player Area -->
319
  <div class="rounded-[32px] overflow-hidden shadow-2xl border border-white/50 dark:border-white/20 relative bg-black aspect-video flex items-center justify-center group">
320
+ <!-- Display Image (AI Overlays) -->
321
  <img id="previewImg" src="" class="w-full h-full object-contain z-10 hidden" alt="Detection View">
322
+ <!-- Raw Video Feed Image -->
323
+ <img id="rawFeedImg" src="" class="w-full h-full object-contain z-10 hidden" alt="Raw View">
324
+
325
+ <!-- Snapshot Container Overlay -->
326
+ <div id="snapshotContainer" class="absolute inset-0 z-30 hidden bg-black flex items-center justify-center">
327
+ <img id="snapshotImg" src="" class="w-full h-full object-contain">
328
+ <!-- Fullscreen / Close Controls -->
329
+ <div class="absolute top-6 right-6 flex gap-3">
330
+ <button onclick="fullscreenSnapshot()" class="w-8 h-8 rounded-full bg-white/20 hover:bg-white/40 border border-white/40 text-white flex items-center justify-center transition-all shadow-lg backdrop-blur-md">
331
+ <i class="fas fa-expand"></i>
332
+ </button>
333
+ <button onclick="closeSnapshot()" class="w-8 h-8 rounded-full bg-red-500/80 hover:bg-red-500 border border-red-400 text-white flex items-center justify-center transition-all shadow-lg backdrop-blur-md">
334
+ <i class="fas fa-times"></i>
335
+ </button>
336
+ </div>
337
+ <!-- Snapshot Tag -->
338
+ <div class="absolute top-6 left-6 z-20">
339
+ <span class="bg-black/60 backdrop-blur-xl text-white text-[10px] px-3 py-1.5 rounded-xl font-black uppercase tracking-widest border border-white/20 flex items-center gap-2">
340
+ <i class="fas fa-camera text-blue-400"></i> CAPTURED SNAPSHOT
341
+ </span>
342
+ </div>
343
+ </div>
344
 
345
  <!-- Live Tag -->
346
+ <div id="liveBadgeContainer" class="absolute top-6 left-6 z-20">
347
  <span id="liveBadge" class="bg-black/60 backdrop-blur-xl text-white text-[10px] px-3 py-1.5 rounded-xl font-black uppercase tracking-widest border border-white/20 flex items-center gap-2">
348
+ <span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> LIVE AI FEED
349
  </span>
350
  </div>
351
  </div>
352
 
353
+ <!-- Media Controls -->
354
  <div class="flex justify-center gap-4 mt-8" id="mediaControls">
355
  <button onclick="takeSnapshot()" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2">
356
  <i class="fas fa-camera"></i> Snapshot
 
359
  <i class="fas fa-brain"></i> AI Tracking
360
  </button>
361
  <button onclick="goOriginal()" id="oBtn" class="px-5 py-3 rounded-xl text-[10px] font-black uppercase tracking-widest text-indigo-900 dark:text-white bg-white/50 dark:bg-white/10 border border-indigo-200/50 dark:border-white/20 hover:scale-105 transition-transform shadow-sm flex items-center gap-2">
362
+ <i class="fas fa-play"></i> Raw Feed
363
  </button>
364
  </div>
365
 
 
754
  if(document.getElementById('vLoc')) document.getElementById('vLoc').innerText = "Not Supported";
755
  }
756
  const now = new Date();
757
+ if(document.getElementById('vTime')) document.getElementById('vTime').innerText = now.toLocaleTimeString([], {hour: '2-digit', minute: '2-digit'});
758
  }
759
 
 
 
 
 
 
 
 
 
 
760
  let streamId = null;
761
  let poller = null;
762
 
763
+ function openIpCamModal() { document.getElementById('ipCamModal').classList.remove('hidden'); }
764
+ function closeIpCamModal() { document.getElementById('ipCamModal').classList.add('hidden'); }
765
+
766
+ function openPredictModal() {
767
+ document.getElementById('predictiveModal').classList.remove('hidden');
768
+ document.body.style.overflow = 'hidden';
769
+ }
770
+ function closePredictModal() {
771
+ document.getElementById('predictiveModal').classList.add('hidden');
772
+ document.body.style.overflow = '';
773
+ }
774
+ function resetPredictModal() {
775
+ document.getElementById('predictiveResultView').classList.add('hidden');
776
+ document.getElementById('predictiveFormView').classList.remove('hidden');
777
+ }
778
 
779
  async function connectIpCam() {
780
+ const url = document.getElementById('ipCamUrl').value;
781
  if(!url) return;
782
  closeIpCamModal();
783
+ startSession({url: url, location: currentLocationStr}, '/init_stream');
784
  }
785
 
786
+ document.getElementById('fileInput').addEventListener('change', async (e) => {
787
+ if(!e.target.files.length) return;
 
 
 
788
  const fd = new FormData();
789
+ fd.append('file', e.target.files[0]);
790
+ startSession(fd, '/init_upload', true);
791
+ });
792
+
793
+ async function startSession(payload, endpoint, isFormData=false) {
794
+ document.getElementById('uploadSection').classList.add('hidden');
795
+ document.getElementById('results').classList.remove('hidden');
 
 
 
 
796
 
797
+ document.getElementById('labelText').innerText = "Initializing Stream...";
798
+
799
+ const opts = { method: 'POST' };
800
+ if(isFormData) { opts.body = payload; }
801
+ else { opts.headers = {'Content-Type': 'application/json'}; opts.body = JSON.stringify(payload); }
802
+
803
+ const res = await fetch(endpoint, opts);
804
  const data = await res.json();
805
  streamId = data.stream_id;
806
 
807
+ if(data.location && data.location !== "Nil" && document.getElementById('vLoc')) {
808
+ document.getElementById('vLoc').innerText = data.location;
 
 
809
  } else {
810
+ fetchRealLocationData(); // fallback
 
 
811
  }
812
+
813
+ if(data.weather && data.weather !== "Nil" && document.getElementById('vWx')) {
814
+ document.getElementById('vWx').innerText = data.weather;
815
+ }
816
+
817
  activateTrackingUI();
818
 
 
819
  const locText = document.getElementById('vLoc') ? document.getElementById('vLoc').innerText : "Unknown";
820
  document.getElementById('previewImg').src = `/video_feed/${streamId}?loc=${encodeURIComponent(locText)}&t=${Date.now()}`;
821
+ document.getElementById('rawFeedImg').src = `/raw_feed/${streamId}?loc=${encodeURIComponent(locText)}&t=${Date.now()}`;
822
 
823
  if(poller) clearInterval(poller);
824
  poller = setInterval(update, 1000);
825
  }
826
 
827
  function activateTrackingUI() {
828
+ document.getElementById('previewImg').classList.remove('hidden');
829
+ document.getElementById('rawFeedImg').classList.add('hidden');
830
+ document.getElementById('snapshotContainer').classList.add('hidden');
831
+ document.getElementById('liveBadgeContainer').classList.remove('hidden');
832
+
833
+ document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-red-500 animate-pulse shadow-[0_0_8px_red]"></span> LIVE AI FEED';
834
  if(document.getElementById('tBtn')) document.getElementById('tBtn').classList.add('opacity-50');
835
  if(document.getElementById('oBtn')) document.getElementById('oBtn').classList.remove('opacity-50');
836
  }
837
 
838
  function goTracking() { activateTrackingUI(); }
839
+
840
+ function goOriginal() {
841
+ document.getElementById('previewImg').classList.add('hidden');
842
+ document.getElementById('rawFeedImg').classList.remove('hidden');
843
+ document.getElementById('snapshotContainer').classList.add('hidden');
844
+ document.getElementById('liveBadgeContainer').classList.remove('hidden');
845
+
846
+ document.getElementById('liveBadge').innerHTML = '<span class="w-2 h-2 rounded-full bg-blue-500 shadow-[0_0_8px_blue]"></span> RAW FEED';
847
+ if(document.getElementById('oBtn')) document.getElementById('oBtn').classList.add('opacity-50');
848
+ if(document.getElementById('tBtn')) document.getElementById('tBtn').classList.remove('opacity-50');
849
+ }
850
 
851
  function takeSnapshot() {
852
+ let feed = null;
853
+ if(!document.getElementById('previewImg').classList.contains('hidden')) {
854
+ feed = document.getElementById('previewImg');
855
+ } else if (!document.getElementById('rawFeedImg').classList.contains('hidden')) {
856
+ feed = document.getElementById('rawFeedImg');
857
+ }
858
+
859
+ if(feed && feed.src) {
860
  const c = document.createElement('canvas');
861
+ c.width = feed.naturalWidth || feed.width || 640;
862
+ c.height = feed.naturalHeight || feed.height || 480;
863
  c.getContext('2d').drawImage(feed, 0, 0, c.width, c.height);
864
+
865
+ document.getElementById('snapshotImg').src = c.toDataURL('image/jpeg');
866
+
867
+ document.getElementById('snapshotContainer').classList.remove('hidden');
868
+ document.getElementById('previewImg').classList.add('hidden');
869
+ document.getElementById('rawFeedImg').classList.add('hidden');
870
+ document.getElementById('liveBadgeContainer').classList.add('hidden');
871
  }
872
  }
873
 
874
+ function closeSnapshot() {
875
+ if (document.getElementById('tBtn').classList.contains('opacity-50')) {
876
+ goTracking();
877
+ } else {
878
+ goOriginal();
879
+ }
 
 
880
  }
881
 
882
+ function fullscreenSnapshot() {
883
+ const imgSrc = document.getElementById('snapshotImg').src;
884
+ if(imgSrc) {
885
+ const w = window.open('');
886
+ w.document.write(`<html><body style="margin:0;background:#000;display:flex;justify-content:center;align-items:center;height:100vh;"><img src="${imgSrc}" style="max-width:100%;max-height:100%;object-fit:contain;"></body></html>`);
887
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
888
  }
889
 
890
  async function update() {
891
  if(!streamId) return;
892
  try {
893
+ const res = await fetch(`/stream_status/${streamId}`);
894
+ const data = await res.json();
895
+ if(!data || Object.keys(data).length === 0) return;
896
+
897
+ document.getElementById('labelText').innerText = data.label || "Monitoring...";
898
+ document.getElementById('confVal').innerText = data.confidence || 0;
899
+ document.getElementById('cnnBar').style.width = `${data.cnn || 0}%`;
900
+ document.getElementById('cnnVal').innerText = `${data.cnn || 0}%`;
901
+ document.getElementById('rcnnBar').style.width = `${data.rcnn || 0}%`;
902
+ document.getElementById('rcnnVal').innerText = `${data.rcnn || 0}%`;
903
+ document.getElementById('severityLevel').innerText = data.severity || "--";
904
+
905
+ const statusCard = document.getElementById('statusCard');
906
  const statusIcon = document.getElementById('statusIcon');
907
+ const emergencyBanner = document.getElementById('emergencyBanner');
908
+
909
+ if(data.is_accident) {
910
+ statusCard.classList.remove('border-indigo-500');
911
+ statusCard.classList.add('border-rose-500', 'bg-rose-50/50', 'dark:bg-rose-900/20');
912
+ statusIcon.className = "fas fa-exclamation-triangle text-rose-600";
913
+
914
+ if(data.severity && (data.severity.toLowerCase() === 'major' || data.severity.toLowerCase() === 'moderate')) {
915
+ emergencyBanner.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
916
  }
917
  } else {
918
+ statusCard.classList.add('border-indigo-500');
919
+ statusCard.classList.remove('border-rose-500', 'bg-rose-50/50', 'dark:bg-rose-900/20');
920
+ statusIcon.className = "fas fa-shield-check text-indigo-600";
921
+ emergencyBanner.classList.add('hidden');
 
 
 
 
922
  }
923
 
924
+ const alprSec = document.getElementById('alprSection');
925
+ const alprCont = document.getElementById('alprContainer');
926
+ if(data.plates && data.plates.length > 0) {
927
+ alprSec.classList.remove('hidden');
928
+ alprCont.innerHTML = data.plates.map(p => `
929
+ <div class="flex items-center gap-2 bg-white/80 dark:bg-black/40 px-3 py-1.5 rounded-lg border border-indigo-100 dark:border-white/10 shadow-sm">
930
+ <img src="data:image/jpeg;base64,${p.image}" class="h-6 rounded object-cover">
931
+ <span class="font-mono font-bold text-xs text-indigo-900 dark:text-white">${p.text}</span>
932
+ </div>
933
+ `).join('');
 
 
 
 
 
 
934
  }
 
 
 
935
  } catch(e) {}
936
  }
937
 
938
  function resetApp() {
 
 
 
 
939
  if(poller) clearInterval(poller);
940
+ streamId = null;
941
  document.getElementById('previewImg').src = "";
942
+ document.getElementById('rawFeedImg').src = "";
943
+ document.getElementById('uploadSection').classList.remove('hidden');
944
+ document.getElementById('results').classList.add('hidden');
945
+ document.getElementById('emergencyBanner').classList.add('hidden');
946
+ document.getElementById('fileInput').value = "";
947
  }
948
 
949
+ async function submitPredictiveData(e) {
950
+ e.preventDefault();
951
+ const btn = document.getElementById('predictBtn');
952
+ const spinner = document.getElementById('predictSpinner');
953
+ btn.disabled = true;
954
+ spinner.classList.remove('hidden');
955
+
956
+ const payload = {
957
+ Weather_Conditions: document.getElementById('tab_weather').value,
958
+ Light_Conditions: document.getElementById('tab_light').value,
959
+ Time_of_Day: document.getElementById('tab_time').value,
960
+ Road_Type: document.getElementById('tab_road_type').value,
961
+ Road_Surface_Conditions: document.getElementById('tab_road_cond').value,
962
+ Speed_Limit: document.getElementById('tab_speed').value,
963
+ Traffic_Density: document.getElementById('tab_density').value,
964
+ Number_of_Vehicles: document.getElementById('tab_vehicles').value,
965
+ Vehicle_Type: document.getElementById('tab_vehicle').value,
966
+ Driver_Age: document.getElementById('tab_age').value,
967
+ Driver_Experience: document.getElementById('tab_exp').value,
968
+ Driver_Alcohol: document.getElementById('tab_alcohol').value
969
+ };
970
+
971
+ try {
972
+ const res = await fetch('/predict_traffic_risk', {
973
+ method: 'POST',
974
+ headers: {'Content-Type': 'application/json'},
975
+ body: JSON.stringify(payload)
976
+ });
977
+ const data = await res.json();
978
+
979
+ document.getElementById('predictiveFormView').classList.add('hidden');
980
+ document.getElementById('predictiveResultView').classList.remove('hidden');
981
+
982
+ const prob = data.risk_probability_percentage || 0;
983
+ document.getElementById('riskProbabilityValue').innerText = prob;
984
+
985
+ const riskCard = document.getElementById('riskStatusCard');
986
+ const riskBadge = document.getElementById('riskBadge');
987
+ const riskIcon = document.getElementById('riskIcon');
988
+ const riskLabel = document.getElementById('riskLabelText');
989
+
990
+ riskCard.className = 'ios-glass-inner p-10 rounded-[32px] shadow-sm dark:shadow-lg relative overflow-hidden w-full max-w-sm border';
991
+ riskBadge.className = 'inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-xs font-black mb-6 uppercase tracking-[0.2em] border shadow-sm text-white';
992
+
993
+ if(prob >= 70) {
994
+ riskCard.classList.add('border-red-500', 'bg-red-50/10', 'dark:bg-red-900/20');
995
+ riskBadge.classList.add('bg-red-500', 'border-red-400');
996
+ riskIcon.className = 'fas fa-exclamation-triangle';
997
+ riskLabel.innerText = 'Critical Risk';
998
+ } else if(prob >= 40) {
999
+ riskCard.classList.add('border-amber-500', 'bg-amber-50/10', 'dark:bg-amber-900/20');
1000
+ riskBadge.classList.add('bg-amber-500', 'border-amber-400');
1001
+ riskIcon.className = 'fas fa-exclamation-circle';
1002
+ riskLabel.innerText = 'Elevated Risk';
1003
+ } else {
1004
+ riskCard.classList.add('border-green-500', 'bg-green-50/10', 'dark:bg-green-900/20');
1005
+ riskBadge.classList.add('bg-green-500', 'border-green-400');
1006
+ riskIcon.className = 'fas fa-shield-check';
1007
+ riskLabel.innerText = 'Low Risk';
1008
  }
1009
+
1010
+ } catch (e) {
1011
+ alert("Error running prediction.");
1012
+ } finally {
1013
+ btn.disabled = false;
1014
+ spinner.classList.add('hidden');
1015
+ }
1016
  }
1017
+
1018
+ // Setup script initialization call
1019
+ fetchRealLocationData();
1020
  </script>
1021
  </body>
1022
  </html>