Spaces:
Running
Running
Update frontend/src/pages/Dashboard.jsx
Browse files- 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 =
|
| 45 |
-
const DEMO_MAX_INDEX =
|
| 46 |
-
const DEMO_INTERVAL_MS =
|
| 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 >
|
| 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 >
|
| 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
|
| 613 |
-
const
|
| 614 |
-
const
|
| 615 |
|
| 616 |
-
//
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 920 |
-
<>
|
| 921 |
-
{
|
| 922 |
-
|
| 923 |
-
|
| 924 |
-
|
| 925 |
-
|
| 926 |
-
|
| 927 |
-
|
| 928 |
-
|
| 929 |
-
|
| 930 |
-
|
| 931 |
-
|
| 932 |
-
|
| 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;
|