Update frontend/src/pages/Dashboard.jsx

#10
by munals - opened
Files changed (1) hide show
  1. frontend/src/pages/Dashboard.jsx +30 -40
frontend/src/pages/Dashboard.jsx CHANGED
@@ -41,9 +41,9 @@ const REFRESH_MS = Number(import.meta.env.VITE_DASH_REFRESH_MS || 1000);
41
  const SMOOTH_WINDOW = 3; // rolling average over last N frames
42
 
43
  // โ”€โ”€ Demo mode constants (PRESENTATION SAFETY โ€” DO NOT CHANGE) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
44
- const DEMO_FRAME_COUNT = 17; // hard limit: only frames 0โ€“16 are valid
45
- const DEMO_MAX_INDEX = 16; // = DEMO_FRAME_COUNT - 1
46
- const DEMO_INTERVAL_MS = 1500; // auto-play speed (ms per frame)
47
 
48
  // โ”€โ”€ Unique-ID window constant โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49
  const ID_WINDOW_SIZE = 6; // frames per unique-ID window (real YOLO track_ids)
@@ -148,7 +148,7 @@ function RiskGauge({ riskScore = 0, densityPct = 0 }) {
148
  const strokeOff = strokeLen - (strokeLen * percentage) / 100;
149
 
150
  // Derive level purely from score thresholds
151
- const levelKey = clamped >= 0.65 ? 'HIGH' : clamped >= 0.35 ? 'MEDIUM' : 'LOW';
152
  const meta = RISK_LEVEL_META[levelKey];
153
 
154
  return (
@@ -603,23 +603,26 @@ function Dashboard() {
603
  // โ”€โ”€ Derived values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
604
  // risk_score โ†’ level (used for "ู…ุณุชูˆู‰ ุงู„ู…ุฎุงุทุฑ" KPI + RiskGauge)
605
  const riskScore = Number(state.risk_score || 0);
606
- const riskLevelKey = riskScore >= 0.65 ? 'HIGH' : riskScore >= 0.35 ? 'MEDIUM' : 'LOW';
607
  const kpiRiskMeta = RISK_LEVEL_META[riskLevelKey] ?? RISK_LEVEL_META['LOW'];
608
 
609
  // Density % โ€” computed by backend (density_pct = peak_ema / 50 * 100)
610
  const densityPct = Math.round(state.density_pct ?? 0);
611
 
612
- // Current demo frame (clamped strictly to 0โ€“16)
613
- const safeIndex = Math.min(Math.max(demoIndex, 0), DEMO_MAX_INDEX);
614
- const currentFrame = frameBuffer[safeIndex] ?? null;
615
 
616
- // The frame image to display โ€” always from demo buffer, never live stream
 
 
 
617
  const displayedSrc = currentFrame
618
  ? `data:image/jpeg;base64,${currentFrame.annotated}`
619
- : null;
620
 
621
  // Per-frame person count (for the KPI card "ุนุฏุฏ ุงู„ุฃุดุฎุงุต")
622
- const demoPersonCount = currentFrame?.person_count ?? 0;
623
 
624
  const computedActions = useMemo(() => state.proposed_actions || [], [state.proposed_actions]);
625
 
@@ -911,38 +914,25 @@ function Dashboard() {
911
  )}
912
  </div>
913
 
914
- {/* โ”€โ”€ Frame scrubber โ€” demo mode, locked 0โ€“16 โ”€โ”€ */}
915
  <div
916
  className="shrink-0 px-3 py-2 flex flex-col gap-1"
917
  style={{ backgroundColor: COLORS.navDark, borderTop: `1px solid ${COLORS.borderMint}` }}
918
  >
919
- {frameBuffer.length > 0 ? (
920
- <>
921
- {/* Nav buttons + slider */}
922
- <div className="flex items-center gap-2">
923
- <button
924
- onClick={() => { setDemoPaused(true); setDemoIndex((i) => Math.max(i - 1, 0)); }}
925
- className="text-[11px] px-2 py-0.5 rounded border font-bold shrink-0"
926
- style={{ borderColor: COLORS.borderMint, color: COLORS.textMuted }}
927
- >โ†’</button>
928
- <input
929
- type="range"
930
- min={0}
931
- max={DEMO_MAX_INDEX}
932
- step={1}
933
- value={safeIndex}
934
- onChange={(e) => { setDemoPaused(true); setDemoIndex(Number(e.target.value)); }}
935
- className="flex-1 cursor-pointer"
936
- style={{ accentColor: COLORS.mint }}
937
- />
938
- <button
939
- onClick={() => { setDemoPaused(true); setDemoIndex((i) => Math.min(i + 1, DEMO_MAX_INDEX)); }}
940
- className="text-[11px] px-2 py-0.5 rounded border font-bold shrink-0"
941
- style={{ borderColor: COLORS.borderMint, color: COLORS.textMuted }}
942
- >โ†</button>
943
- </div>
944
- </>
945
- ) : null}
946
  </div>
947
  </main>
948
 
@@ -1024,4 +1014,4 @@ function Dashboard() {
1024
  );
1025
  }
1026
 
1027
- export default Dashboard;
 
41
  const SMOOTH_WINDOW = 3; // rolling average over last N frames
42
 
43
  // โ”€โ”€ Demo mode constants (PRESENTATION SAFETY โ€” DO NOT CHANGE) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
44
+ const DEMO_FRAME_COUNT = 691; // hard limit: frames 0โ€“690 are valid
45
+ const DEMO_MAX_INDEX = 690; // = DEMO_FRAME_COUNT - 1
46
+ const DEMO_INTERVAL_MS = 100; // auto-play speed (ms per frame)
47
 
48
  // โ”€โ”€ Unique-ID window constant โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
49
  const ID_WINDOW_SIZE = 6; // frames per unique-ID window (real YOLO track_ids)
 
148
  const strokeOff = strokeLen - (strokeLen * percentage) / 100;
149
 
150
  // Derive level purely from score thresholds
151
+ const levelKey = clamped > 0.80 ? 'HIGH' : clamped > 0.20 ? 'MEDIUM' : 'LOW';
152
  const meta = RISK_LEVEL_META[levelKey];
153
 
154
  return (
 
603
  // โ”€โ”€ Derived values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
604
  // risk_score โ†’ level (used for "ู…ุณุชูˆู‰ ุงู„ู…ุฎุงุทุฑ" KPI + RiskGauge)
605
  const riskScore = Number(state.risk_score || 0);
606
+ const riskLevelKey = state.risk_level || (riskScore > 0.80 ? 'HIGH' : riskScore > 0.20 ? 'MEDIUM' : 'LOW');
607
  const kpiRiskMeta = RISK_LEVEL_META[riskLevelKey] ?? RISK_LEVEL_META['LOW'];
608
 
609
  // Density % โ€” computed by backend (density_pct = peak_ema / 50 * 100)
610
  const densityPct = Math.round(state.density_pct ?? 0);
611
 
612
+ // Current frame progress from backend
613
+ const backendFrame = state.frame_id ?? 0;
614
+ const safeIndex = Math.min(Math.max(backendFrame, 0), DEMO_MAX_INDEX);
615
 
616
+ // Display the latest frame from buffer (last element = most recent)
617
+ const currentFrame = frameBuffer.length > 0 ? frameBuffer[frameBuffer.length - 1] : null;
618
+
619
+ // The frame image to display โ€” always the latest annotated frame
620
  const displayedSrc = currentFrame
621
  ? `data:image/jpeg;base64,${currentFrame.annotated}`
622
+ : (state.annotated ? `data:image/jpeg;base64,${state.annotated}` : null);
623
 
624
  // Per-frame person count (for the KPI card "ุนุฏุฏ ุงู„ุฃุดุฎุงุต")
625
+ const demoPersonCount = currentFrame?.person_count ?? (state.person_count ?? 0);
626
 
627
  const computedActions = useMemo(() => state.proposed_actions || [], [state.proposed_actions]);
628
 
 
914
  )}
915
  </div>
916
 
917
+ {/* โ”€โ”€ Frame progress bar โ”€โ”€ */}
918
  <div
919
  className="shrink-0 px-3 py-2 flex flex-col gap-1"
920
  style={{ backgroundColor: COLORS.navDark, borderTop: `1px solid ${COLORS.borderMint}` }}
921
  >
922
+ <div className="flex items-center gap-2">
923
+ <span className="text-[10px] shrink-0" style={{ color: COLORS.textMuted }}>
924
+ {backendFrame} / {DEMO_FRAME_COUNT}
925
+ </span>
926
+ <div className="flex-1 h-2 rounded-full overflow-hidden" style={{ backgroundColor: `${COLORS.borderMint}44` }}>
927
+ <div
928
+ className="h-full rounded-full transition-all duration-300"
929
+ style={{
930
+ width: `${Math.min((backendFrame / DEMO_FRAME_COUNT) * 100, 100)}%`,
931
+ backgroundColor: COLORS.mint,
932
+ }}
933
+ />
934
+ </div>
935
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
936
  </div>
937
  </main>
938
 
 
1014
  );
1015
  }
1016
 
1017
+ export default Dashboard;