Spaces:
Sleeping
Sleeping
Commit ·
b308285
1
Parent(s): ebd8fd8
fix: add HTML routes for all pages
Browse files- TRAFFIC_ROAD_APP/TRAFFIC_ROAD_APP/Dockerfile +0 -28
- TRAFFIC_ROAD_APP/TRAFFIC_ROAD_APP/requirements.txt +0 -8
- TRAFFIC_ROAD_APP/requirements_hf.txt +0 -13
- TRAFFIC_ROAD_APP/templates/dashboard.html +0 -418
- TRAFFIC_ROAD_APP/templates/history.html +0 -295
- TRAFFIC_ROAD_APP/templates/home.html +0 -684
- TRAFFIC_ROAD_APP/templates/index.html +0 -486
- TRAFFIC_ROAD_APP/templates/logs.html +0 -415
- app.py +1 -1
- models/yolo11n.pt +2 -2
- templates/home.html +128 -20
TRAFFIC_ROAD_APP/TRAFFIC_ROAD_APP/Dockerfile
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
FROM python:3.10-slim-bullseye
|
| 2 |
-
|
| 3 |
-
RUN apt-get update && apt-get install -y \
|
| 4 |
-
libgl1 \
|
| 5 |
-
libglib2.0-0 \
|
| 6 |
-
libsm6 \
|
| 7 |
-
libxext6 \
|
| 8 |
-
libxrender-dev \
|
| 9 |
-
ffmpeg \
|
| 10 |
-
libopencv-dev \
|
| 11 |
-
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
-
|
| 13 |
-
WORKDIR /app
|
| 14 |
-
|
| 15 |
-
COPY requirements.txt requirements.txt
|
| 16 |
-
|
| 17 |
-
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
-
|
| 19 |
-
COPY app.py .
|
| 20 |
-
COPY static/ ./static/
|
| 21 |
-
COPY templates/ ./templates/
|
| 22 |
-
|
| 23 |
-
RUN mkdir -p models uploads outputs logs
|
| 24 |
-
|
| 25 |
-
ENV PORT=7860
|
| 26 |
-
EXPOSE 7860
|
| 27 |
-
|
| 28 |
-
CMD ["python", "app.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/TRAFFIC_ROAD_APP/requirements.txt
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
flask==3.1.3
|
| 2 |
-
flask-cors==6.0.2
|
| 3 |
-
opencv-python-headless==4.10.0.84
|
| 4 |
-
ultralytics==8.4.39
|
| 5 |
-
torch==2.11.0
|
| 6 |
-
torchvision==0.26.0
|
| 7 |
-
numpy==1.26.4
|
| 8 |
-
pillow==10.4.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/requirements_hf.txt
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
flask==3.1.3
|
| 2 |
-
flask-cors==6.0.2
|
| 3 |
-
opencv-python-headless==4.13.0.92
|
| 4 |
-
ultralytics==8.4.39
|
| 5 |
-
torch==2.11.0
|
| 6 |
-
torchvision==0.26.0
|
| 7 |
-
numpy==2.3.4
|
| 8 |
-
pillow==12.0.0
|
| 9 |
-
pandas==2.3.3
|
| 10 |
-
python-dotenv==1.2.1
|
| 11 |
-
requests==2.32.5
|
| 12 |
-
pyyaml==6.0.3
|
| 13 |
-
tqdm==4.67.3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/templates/dashboard.html
DELETED
|
@@ -1,418 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" data-theme="light">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>TrackIQ Dashboard</title>
|
| 7 |
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 9 |
-
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 10 |
-
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.2.0/dist/chartjs-plugin-datalabels.min.js"></script>
|
| 11 |
-
<style>
|
| 12 |
-
:root{--bg:#0c1117;--s:#151c25;--s2:#1c2531;--bd:rgba(255,255,255,0.07);--sh:0 2px 16px rgba(0,0,0,0.45);--shlg:0 8px 36px rgba(0,0,0,0.55);--t:#e8edf2;--t2:#7a8fa0;--t3:#3a4a5a;--ac:#880000;--ac2:#660000;--acl:rgba(136,0,0,0.12);--gr:#16a34a;--grb:#052e16;--bl:#3b82f6;--blb:#0f2040;--yw:#f59e0b;--ywb:#1a0f00;--re:#ef4444;--reb:#2d0000;--sw:60px;--r:14px;--rs:9px;}
|
| 13 |
-
[data-theme="light"]{--bg:#f0f4f8;--s:#fff;--s2:#f6f8fb;--bd:rgba(0,0,0,0.07);--sh:0 2px 16px rgba(0,0,0,0.09);--shlg:0 8px 36px rgba(0,0,0,0.13);--t:#0f1923;--t2:#5a6a7a;--t3:#9aaab8;--grb:#dcfce7;--blb:#dbeafe;--ywb:#fef9c3;--reb:#fee2e2;}
|
| 14 |
-
*{box-sizing:border-box;margin:0;padding:0}::-webkit-scrollbar{width:0;height:0}
|
| 15 |
-
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--t);font-size:13px;display:flex;height:100vh;overflow:hidden;transition:background .3s,color .3s}
|
| 16 |
-
.sb{width:var(--sw);background:var(--s);border-right:1px solid var(--bd);display:flex;flex-direction:column;align-items:center;padding:14px 0;gap:4px;flex-shrink:0}
|
| 17 |
-
.sbl{width:34px;height:34px;background:var(--ac);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;margin-bottom:10px;text-decoration:none}
|
| 18 |
-
.sbsep{width:26px;height:1px;background:var(--bd);margin:3px 0}
|
| 19 |
-
.sbb{width:38px;height:38px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--t3);font-size:14px;cursor:pointer;transition:all .2s;text-decoration:none;border:none;background:none;position:relative}
|
| 20 |
-
.sbb:hover{background:var(--s2);color:var(--t)}.sbb.on{background:var(--acl);color:var(--ac)}
|
| 21 |
-
.sbb .tip{position:absolute;left:44px;top:50%;transform:translateY(-50%);background:var(--t);color:var(--bg);font-size:10px;padding:3px 7px;border-radius:4px;white-space:nowrap;opacity:0;pointer-events:none;z-index:300}.sbb:hover .tip{opacity:1}
|
| 22 |
-
.sbsp{flex:1}.pw{flex:1;display:flex;flex-direction:column;overflow:hidden}
|
| 23 |
-
.topb{height:50px;background:var(--s);border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;padding:0 18px;flex-shrink:0}
|
| 24 |
-
.topl,.topr{display:flex;align-items:center;gap:9px}.bn{font-size:14px;font-weight:600}.bn b{color:var(--ac)}
|
| 25 |
-
.pill{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--gr);background:var(--grb);padding:2px 8px;border-radius:20px}
|
| 26 |
-
.ld{width:6px;height:6px;border-radius:50%;background:var(--gr);animation:blink 2s infinite}
|
| 27 |
-
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
|
| 28 |
-
.clk{font-size:10px;font-family:'DM Mono',monospace;color:var(--t2)}
|
| 29 |
-
.lg{display:flex;background:var(--s2);border-radius:5px;padding:2px;border:1px solid var(--bd)}
|
| 30 |
-
.lb{font-size:10px;font-weight:500;padding:2px 8px;border:none;background:none;cursor:pointer;border-radius:3px;color:var(--t2);font-family:'DM Sans',sans-serif;transition:all .2s}.lb.on{background:var(--ac);color:#fff}
|
| 31 |
-
.ib{width:28px;height:28px;border-radius:5px;border:1px solid var(--bd);background:var(--s);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--t2);font-size:11px;position:relative;transition:all .2s}.ib:hover{background:var(--s2)}
|
| 32 |
-
.nd{position:absolute;top:3px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--ac)}
|
| 33 |
-
main{flex:1;padding:12px 14px;display:grid;grid-template-columns:minmax(0,1.4fr) 360px;gap:12px;overflow:auto;min-height:0}
|
| 34 |
-
.left-col{display:grid;grid-template-rows:auto minmax(190px,1fr) minmax(190px,1fr);gap:10px;min-height:0}
|
| 35 |
-
.right-col{display:grid;grid-template-rows:minmax(300px,1.15fr) auto auto;gap:10px;min-height:0}
|
| 36 |
-
.mr{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}
|
| 37 |
-
.mc,.cd{background:var(--s);border:1px solid var(--bd);box-shadow:var(--sh);overflow:hidden}
|
| 38 |
-
.mc{border-radius:var(--rs);padding:11px 13px;transition:transform .2s}.mc:hover{transform:translateY(-2px)}
|
| 39 |
-
.cd{border-radius:var(--r);padding:12px 14px;display:flex;flex-direction:column}.cd:hover{box-shadow:var(--shlg)}
|
| 40 |
-
.mct{display:flex;align-items:center;justify-content:space-between;margin-bottom:5px}
|
| 41 |
-
.mcl{font-size:10px;color:var(--t2)}.bx{font-size:9px;font-weight:500;padding:1px 5px;border-radius:4px}
|
| 42 |
-
.mv{font-size:20px;font-weight:300;letter-spacing:-.5px;line-height:1}.mu{font-size:10px;color:var(--t2)}.ms{font-size:9px;color:var(--t3);margin-top:2px}
|
| 43 |
-
.ct{font-size:11px;font-weight:500;margin-bottom:8px;display:flex;align-items:center;gap:5px;flex-shrink:0}.ct i{color:var(--ac);font-size:10px}
|
| 44 |
-
.chart-wrap{flex:1;min-height:0}
|
| 45 |
-
.stack{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
| 46 |
-
.hmcd{padding:0}
|
| 47 |
-
.hmhd{padding:10px 12px 6px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0}
|
| 48 |
-
.hmhd .ct{margin-bottom:0}
|
| 49 |
-
.hmbd{flex:1;position:relative;min-height:220px}
|
| 50 |
-
#hmc2{position:absolute;inset:0;width:100%!important;height:100%!important;display:block}
|
| 51 |
-
.stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:9px}
|
| 52 |
-
.statbox{background:var(--s2);border:1px solid var(--bd);border-radius:8px;padding:10px 11px}
|
| 53 |
-
.statbox .k{font-size:9px;color:var(--t2);text-transform:uppercase;letter-spacing:.04em}
|
| 54 |
-
.statbox .v{font-size:18px;font-weight:300;margin-top:4px}
|
| 55 |
-
.subline{font-size:10px;color:var(--t2)}
|
| 56 |
-
.listbox{display:flex;flex-direction:column;gap:6px}
|
| 57 |
-
.vr{display:flex;align-items:center;justify-content:space-between;background:var(--s2);border:1px solid var(--bd);border-radius:8px;padding:8px 10px}
|
| 58 |
-
.vrl{display:flex;align-items:center;gap:7px;font-size:11px}.vic{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px}.vp{font-size:13px;font-weight:500}
|
| 59 |
-
.al{display:flex;align-items:flex-start;gap:8px;padding:7px 0;border-bottom:1px solid var(--bd)}.al:last-child{border-bottom:none}
|
| 60 |
-
.ai{width:22px;height:22px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:9px;flex-shrink:0;margin-top:1px}
|
| 61 |
-
.at2{font-size:10px;font-weight:500;margin-bottom:1px}.as2{font-size:9px;color:var(--t2)}
|
| 62 |
-
.pill-row{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}.mini-pill{font-size:9px;padding:3px 7px;border-radius:999px;background:var(--s2);border:1px solid var(--bd);color:var(--t2)}
|
| 63 |
-
.empty-note{font-size:10px;color:var(--t3);padding-top:4px}
|
| 64 |
-
@media(max-width:1180px){main{grid-template-columns:1fr}.right-col{grid-template-rows:minmax(260px,auto) auto auto}.stack{grid-template-columns:1fr}.mr{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
| 65 |
-
</style>
|
| 66 |
-
</head>
|
| 67 |
-
<body>
|
| 68 |
-
<nav class="sb">
|
| 69 |
-
<a href="/" class="sbl"><i class="fas fa-satellite-dish"></i></a>
|
| 70 |
-
<div class="sbsep"></div>
|
| 71 |
-
<a href="/home" class="sbb"><i class="fas fa-play-circle"></i><span class="tip">Analyze</span></a>
|
| 72 |
-
<a href="/dashboard" class="sbb on"><i class="fas fa-chart-line"></i><span class="tip">Dashboard</span></a>
|
| 73 |
-
<a href="/logs" class="sbb"><i class="fas fa-list-ul"></i><span class="tip">Logs</span></a>
|
| 74 |
-
<a href="/history" class="sbb"><i class="fas fa-film"></i><span class="tip">History</span></a>
|
| 75 |
-
<div class="sbsp"></div>
|
| 76 |
-
<button class="sbb" onclick="toggleTheme()"><i class="fas fa-moon" id="ti"></i><span class="tip">Theme</span></button>
|
| 77 |
-
</nav>
|
| 78 |
-
|
| 79 |
-
<div class="pw">
|
| 80 |
-
<header class="topb">
|
| 81 |
-
<div class="topl">
|
| 82 |
-
<span class="bn">Track<b>IQ</b></span>
|
| 83 |
-
<span style="color:var(--bd);font-size:16px;margin:0 1px;">|</span>
|
| 84 |
-
<span style="font-size:12px;font-weight:500;" id="t-dash">Dashboard</span>
|
| 85 |
-
<div class="pill"><span class="ld"></span> <span id="liveBadge">Live</span></div>
|
| 86 |
-
</div>
|
| 87 |
-
<div class="topr">
|
| 88 |
-
<span class="clk" id="clk">--:--:--</span>
|
| 89 |
-
<div class="lg">
|
| 90 |
-
<button class="lb on" onclick="setLang('en',this)">EN</button>
|
| 91 |
-
<button class="lb" onclick="setLang('fr',this)">FR</button>
|
| 92 |
-
<button class="lb" onclick="setLang('wo',this)">WO</button>
|
| 93 |
-
</div>
|
| 94 |
-
<button class="ib" onclick="toggleNotifs()"><i class="fas fa-bell"></i><span class="nd" id="nd"></span></button>
|
| 95 |
-
<button class="ib" onclick="toggleTheme()"><i class="fas fa-moon" id="ti2"></i></button>
|
| 96 |
-
</div>
|
| 97 |
-
</header>
|
| 98 |
-
|
| 99 |
-
<div id="notifP" style="display:none;position:fixed;top:57px;right:65px;z-index:500;width:255px;background:var(--s);border:1px solid var(--bd);border-radius:var(--r);box-shadow:var(--shlg);padding:12px 14px;">
|
| 100 |
-
<div style="font-size:11px;font-weight:500;margin-bottom:8px;display:flex;justify-content:space-between;"><span id="t-notif">Notifications</span><button onclick="clearN()" style="font-size:10px;color:var(--ac);background:none;border:none;cursor:pointer;">Clear all</button></div>
|
| 101 |
-
<div id="nl">
|
| 102 |
-
<div style="padding:6px 0;border-bottom:1px solid var(--bd);"><div style="font-size:10px;font-weight:500;color:var(--re);" id="t-a1">Congestion Zone A</div><div style="font-size:9px;color:var(--t2);">4 min ago</div></div>
|
| 103 |
-
<div style="padding:6px 0;"><div style="font-size:10px;font-weight:500;" id="t-source-loaded">Source ready</div><div style="font-size:9px;color:var(--t2);">Just now</div></div>
|
| 104 |
-
</div>
|
| 105 |
-
</div>
|
| 106 |
-
|
| 107 |
-
<main>
|
| 108 |
-
<section class="left-col">
|
| 109 |
-
<div class="mr">
|
| 110 |
-
<div class="mc"><div class="mct"><span class="mcl" id="t-m1">Objects Today</span><span class="bx" style="background:var(--grb);color:var(--gr);" id="m1Trend">+0%</span></div><div><span class="mv" id="mvToday">0</span> <span class="mu" id="t-veh">obj.</span></div><div class="ms" id="m1Sub">Latest analyzed scenes</div></div>
|
| 111 |
-
<div class="mc"><div class="mct"><span class="mcl" id="t-m2">Current Flow</span><span class="bx" style="background:var(--grb);color:var(--gr);" id="flowBadge">Smooth</span></div><div><span class="mv" id="mvFlow">0</span> <span class="mu">obj/min</span></div><div class="ms" id="m2Sub">Based on recent activity</div></div>
|
| 112 |
-
<div class="mc"><div class="mct"><span class="mcl" id="t-m3">Avg FPS</span><span class="bx" style="background:var(--blb);color:var(--bl);" id="fpsBadge">Stable</span></div><div><span class="mv" id="mvFps">0</span> <span class="mu">fps</span></div><div class="ms" id="m3Sub">Last completed analysis</div></div>
|
| 113 |
-
<div class="mc"><div class="mct"><span class="mcl" id="t-m4">Latest clip</span><span class="bx" style="background:var(--ywb);color:var(--yw);" id="accBadge">Current</span></div><div><span class="mv" id="mvAcc">0</span> <span class="mu">s</span></div><div class="ms" id="m4Sub">Duration of latest processed video</div></div>
|
| 114 |
-
</div>
|
| 115 |
-
|
| 116 |
-
<div class="stack">
|
| 117 |
-
<div class="cd">
|
| 118 |
-
<div class="ct"><i class="fas fa-chart-area"></i><span id="t-c1">Flow & speed</span></div>
|
| 119 |
-
<div class="chart-wrap"><canvas id="fc"></canvas></div>
|
| 120 |
-
</div>
|
| 121 |
-
<div class="cd">
|
| 122 |
-
<div class="ct"><i class="fas fa-chart-bar"></i><span id="t-c2">Classification</span></div>
|
| 123 |
-
<div class="chart-wrap"><canvas id="tc"></canvas></div>
|
| 124 |
-
</div>
|
| 125 |
-
</div>
|
| 126 |
-
|
| 127 |
-
<div class="cd">
|
| 128 |
-
<div class="ct"><i class="fas fa-layer-group"></i><span id="t-scenes">Scene summary</span></div>
|
| 129 |
-
<div class="stats-grid">
|
| 130 |
-
<div class="statbox"><div class="k" id="t-live-source">Source</div><div class="v" id="liveSource">No source</div><div class="subline" id="liveSourceMeta">Ready after analysis</div></div>
|
| 131 |
-
<div class="statbox"><div class="k" id="t-live-objects">Objects</div><div class="v" id="liveObjects" id="liveObjects">0</div><div class="subline" id="liveObjectsMeta">Peak visible count</div></div>
|
| 132 |
-
<div class="statbox"><div class="k" id="t-live-fps">FPS</div><div class="v" id="liveFps" id="liveFps">0</div><div class="subline" id="liveFpsMeta">Captured stream rate</div></div>
|
| 133 |
-
<div class="statbox"><div class="k" id="t-live-duration">Duration</div><div class="v" id="liveDuration" id="liveDuration">0s</div><div class="subline" id="liveDurationMeta">Latest processed clip</div></div>
|
| 134 |
-
</div>
|
| 135 |
-
<div class="pill-row" id="liveClassPills"></div>
|
| 136 |
-
<div class="empty-note" id="liveNote">Current summary follows the latest processed analysis instead of the old floating video preview.</div>
|
| 137 |
-
<div id="sceneSummaryGrid" style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:10px;"></div>
|
| 138 |
-
</div>
|
| 139 |
-
</section>
|
| 140 |
-
|
| 141 |
-
<aside class="right-col">
|
| 142 |
-
<div class="cd hmcd">
|
| 143 |
-
<div class="hmhd"><div class="ct"><i class="fas fa-map-marker-alt"></i><span id="t-hm">Position heatmap</span></div><span style="font-size:9px;color:var(--t3);" id="t-hms">Object density</span></div>
|
| 144 |
-
<div class="hmbd"><canvas id="hmc2"></canvas></div>
|
| 145 |
-
</div>
|
| 146 |
-
|
| 147 |
-
<div class="stack">
|
| 148 |
-
<div class="cd" style="padding:11px 12px;">
|
| 149 |
-
<div class="ct">
|
| 150 |
-
<i class="fas fa-chart-pie"></i>
|
| 151 |
-
<span>Object Type Distribution</span>
|
| 152 |
-
</div>
|
| 153 |
-
<!-- Pie chart + légende -->
|
| 154 |
-
<div style="position:relative;display:flex;align-items:center;gap:10px;min-height:140px;">
|
| 155 |
-
<div style="width:145px;height:145px;flex-shrink:0;">
|
| 156 |
-
<canvas id="pieChart"></canvas>
|
| 157 |
-
|
| 158 |
-
</div>
|
| 159 |
-
<!-- Légende dynamique -->
|
| 160 |
-
<div id="pieLegend" style="flex:1;display:flex;flex-direction:column;gap:5px;">
|
| 161 |
-
<div style="font-size:10px;color:var(--t3);text-align:center;">No data yet</div>
|
| 162 |
-
</div>
|
| 163 |
-
</div>
|
| 164 |
-
</div>
|
| 165 |
-
<div class="cd" style="padding:11px 12px;">
|
| 166 |
-
<div class="ct"><i class="fas fa-wave-square"></i><span id="t-live-panel">Live stats</span></div>
|
| 167 |
-
<div class="stats-grid">
|
| 168 |
-
<div class="statbox"><div class="k" id="t-det">Detected</div><div class="v" id="sbdet">0</div></div>
|
| 169 |
-
<div class="statbox"><div class="k" id="t-acc">Latest FPS</div><div class="v" id="sbacc">0</div></div>
|
| 170 |
-
</div>
|
| 171 |
-
<div class="empty-note" id="livePanelNote">Heatmap and key stats stay visible at all times.</div>
|
| 172 |
-
</div>
|
| 173 |
-
</div>
|
| 174 |
-
</aside>
|
| 175 |
-
</main>
|
| 176 |
-
</div>
|
| 177 |
-
|
| 178 |
-
<script>
|
| 179 |
-
setInterval(()=>{document.getElementById('clk').textContent=new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',second:'2-digit'});},1000);
|
| 180 |
-
|
| 181 |
-
function toggleTheme(){
|
| 182 |
-
const d=document.documentElement,dk=d.getAttribute('data-theme')==='dark';
|
| 183 |
-
d.setAttribute('data-theme',dk?'light':'dark');
|
| 184 |
-
['ti','ti2'].forEach(x=>{const e=document.getElementById(x);if(e)e.className=dk?'fas fa-moon':'fas fa-sun';});
|
| 185 |
-
rebuildCharts();
|
| 186 |
-
setTimeout(drawHm,50);
|
| 187 |
-
rebuildPie();
|
| 188 |
-
try{localStorage.setItem('tiq_theme',dk?'light':'dark');}catch(e){}
|
| 189 |
-
}
|
| 190 |
-
|
| 191 |
-
const TL={
|
| 192 |
-
en:{dash:'Dashboard',m1:'Objects Today',veh:'obj.',m2:'Current Flow',m3:'Avg FPS',m4:'Latest clip',c1:'Flow from video',c2:'Classification',hm:'Position heatmap',hms:'Detected positions',bk:'Breakdown',ca:'Cars',mo:'Moto',tr:'Trucks',al:'Alerts',notif:'Notifications',scenes:'Scene summary',livesource:'Source',liveobjects:'Objects',livefps:'FPS',liveduration:'Duration',livepanel:'Live stats',det:'Detected',acc:'Latest FPS'},
|
| 193 |
-
fr:{dash:'Tableau de bord',m1:'Objets du jour',veh:'obj.',m2:'Flux actuel',m3:'FPS moy.',m4:'Dernier clip',c1:'Flux vidéo',c2:'Classification',hm:'Carte de densité',hms:'Positions détectées',bk:'Répartition',ca:'Voitures',mo:'Motos',tr:'Camions',al:'Alertes',notif:'Notifications',scenes:'Résumé scène',livesource:'Source',liveobjects:'Objets',livefps:'FPS',liveduration:'Durée',livepanel:'Stats live',det:'Détectés',acc:'FPS récent'},
|
| 194 |
-
wo:{dash:'Dashboard',m1:'Objets tey',veh:'obj.',m2:'Yóbbu',m3:'FPS moy.',m4:'Clip mujj',c1:'Flux video',c2:'Wërsëg',hm:'Kàrt',hms:'Posision yi',bk:'Wërsëg',ca:'Cars',mo:'Moto',tr:'Trucks',al:'Yëgël',notif:'Yëgël yi',scenes:'Melo scène',livesource:'Source',liveobjects:'Objets',livefps:'FPS',liveduration:'Yoon',livepanel:'Stats live',det:'Yi réy',acc:'FPS mujj'}
|
| 195 |
-
};
|
| 196 |
-
|
| 197 |
-
function setLang(l,btn){
|
| 198 |
-
document.querySelectorAll('.lb').forEach(b=>b.classList.remove('on'));
|
| 199 |
-
btn.classList.add('on');
|
| 200 |
-
const t=TL[l];
|
| 201 |
-
const mp={dash:'t-dash',m1:'t-m1',veh:'t-veh',m2:'t-m2',m3:'t-m3',m4:'t-m4',c1:'t-c1',c2:'t-c2',hm:'t-hm',hms:'t-hms',bk:'t-bk',ca:'t-ca',mo:'t-mo',tr:'t-tr',al:'t-al',notif:'t-notif',scenes:'t-scenes',livesource:'t-live-source',liveobjects:'t-live-objects',livefps:'t-live-fps',liveduration:'t-live-duration',livepanel:'t-live-panel',det:'t-det',acc:'t-acc'};
|
| 202 |
-
Object.entries(mp).forEach(([k,id])=>{const el=document.getElementById(id);if(el&&t[k])el.textContent=t[k];});
|
| 203 |
-
try{localStorage.setItem('tiq_lang',l);}catch(e){}
|
| 204 |
-
}
|
| 205 |
-
|
| 206 |
-
let no=false;
|
| 207 |
-
function toggleNotifs(){no=!no;document.getElementById('notifP').style.display=no?'block':'none';if(no)document.getElementById('nd').style.display='none';}
|
| 208 |
-
function clearN(){document.getElementById('nl').innerHTML='<div style="font-size:10px;color:var(--t3);text-align:center;padding:10px;">No notifications</div>';no=false;document.getElementById('notifP').style.display='none';}
|
| 209 |
-
document.addEventListener('click',e=>{if(no&&!e.target.closest('#notifP')&&!e.target.closest('.ib'))toggleNotifs();});
|
| 210 |
-
|
| 211 |
-
window.fch=null;
|
| 212 |
-
window.tch=null;
|
| 213 |
-
window._tiqHeatPoints=[];
|
| 214 |
-
|
| 215 |
-
function gc(){
|
| 216 |
-
const dk=document.documentElement.getAttribute('data-theme')==='dark';
|
| 217 |
-
return{grid:dk?'rgba(255,255,255,0.05)':'rgba(0,0,0,0.06)',tick:dk?'#4a5a6a':'#94a3b8'};
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
/* ── Pie chart — Object Type Distribution ── */
|
| 221 |
-
window.pch = null;
|
| 222 |
-
|
| 223 |
-
function rebuildPie(labels=[], data=[], colors=[]){
|
| 224 |
-
// Enregistre datalabels seulement si dispo
|
| 225 |
-
if(window.ChartDataLabels) Chart.register(ChartDataLabels);
|
| 226 |
-
if(window.pch) window.pch.destroy();
|
| 227 |
-
const canvas = document.getElementById('pieChart');
|
| 228 |
-
if(!canvas) return;
|
| 229 |
-
const dk = document.documentElement.getAttribute('data-theme')==='dark';
|
| 230 |
-
|
| 231 |
-
const defaultColors = ['#3b82f6','#10b981','#f59e0b','#8b5cf6','#ec4899','#06b6d4','#ef4444','#f97316'];
|
| 232 |
-
|
| 233 |
-
window.pch = new Chart(canvas, {
|
| 234 |
-
type: 'pie',
|
| 235 |
-
data: {
|
| 236 |
-
labels: labels.length ? labels : ['No data'],
|
| 237 |
-
datasets: [{
|
| 238 |
-
data: data.length ? data : [1],
|
| 239 |
-
backgroundColor: colors.length ? colors : [dk?'#1c2531':'#e2e8f0'],
|
| 240 |
-
borderColor: dk ? '#151c25' : '#ffffff',
|
| 241 |
-
borderWidth: 2,
|
| 242 |
-
hoverOffset: 6,
|
| 243 |
-
}]
|
| 244 |
-
},
|
| 245 |
-
options: {
|
| 246 |
-
responsive: true,
|
| 247 |
-
maintainAspectRatio: false,
|
| 248 |
-
plugins: {
|
| 249 |
-
legend: { display: false },
|
| 250 |
-
tooltip: {
|
| 251 |
-
callbacks: {
|
| 252 |
-
label: ctx => {
|
| 253 |
-
const tot = ctx.dataset.data.reduce((a,b)=>a+b,0);
|
| 254 |
-
return ` ${ctx.label}: ${ctx.raw} (${Math.round(ctx.raw/tot*100)}%)`;
|
| 255 |
-
}
|
| 256 |
-
}
|
| 257 |
-
},
|
| 258 |
-
/* Affiche les % à l'intérieur de chaque part */
|
| 259 |
-
datalabels: {
|
| 260 |
-
color: '#fff',
|
| 261 |
-
font: { size: 11, weight: 'bold' },
|
| 262 |
-
formatter: (value, ctx) => {
|
| 263 |
-
const tot = ctx.dataset.data.reduce((a,b)=>a+b,0);
|
| 264 |
-
const pct = Math.round(value/tot*100);
|
| 265 |
-
return pct >= 5 ? pct+'%' : ''; // cache si trop petit
|
| 266 |
-
}
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
}
|
| 270 |
-
});
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
function updatePie(uc){
|
| 274 |
-
const COLORS = {
|
| 275 |
-
'Vehicle':'#3b82f6','Motorcycle':'#10b981','Truck':'#f59e0b',
|
| 276 |
-
'Bus':'#8b5cf6','Human':'#ec4899','Bicycle':'#06b6d4',
|
| 277 |
-
'Traffic light':'#ef4444','Road sign':'#f97316'
|
| 278 |
-
};
|
| 279 |
-
const EMO = {
|
| 280 |
-
'Vehicle':'🚗','Motorcycle':'🏍','Truck':'🚚','Bus':'🚌',
|
| 281 |
-
'Human':'🚶','Bicycle':'🚲','Traffic light':'🚦','Road sign':'🛑'
|
| 282 |
-
};
|
| 283 |
-
|
| 284 |
-
const labels = Object.keys(uc);
|
| 285 |
-
const data = Object.values(uc);
|
| 286 |
-
const colors = labels.map(l => COLORS[l] || '#888');
|
| 287 |
-
const total = data.reduce((a,b)=>a+b,0);
|
| 288 |
-
|
| 289 |
-
// Recrée le chart
|
| 290 |
-
rebuildPie(labels, data, colors);
|
| 291 |
-
|
| 292 |
-
// Met à jour le label central
|
| 293 |
-
const ptn = document.getElementById('pieTotalNum');
|
| 294 |
-
if(ptn) ptn.textContent = total;
|
| 295 |
-
|
| 296 |
-
// Légende dynamique
|
| 297 |
-
const leg = document.getElementById('pieLegend');
|
| 298 |
-
if(!leg) return;
|
| 299 |
-
leg.innerHTML = '';
|
| 300 |
-
labels.forEach((lbl, i) => {
|
| 301 |
-
const pct = Math.round(data[i]/Math.max(total,1)*100);
|
| 302 |
-
const row = document.createElement('div');
|
| 303 |
-
row.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:6px;';
|
| 304 |
-
row.innerHTML = `
|
| 305 |
-
<div style="display:flex;align-items:center;gap:5px;font-size:11px;overflow:hidden;">
|
| 306 |
-
<span style="width:9px;height:9px;border-radius:2px;background:${colors[i]};flex-shrink:0;display:inline-block;"></span>
|
| 307 |
-
<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${EMO[lbl]||'📦'} ${lbl}</span>
|
| 308 |
-
</div>
|
| 309 |
-
<span style="font-size:10px;color:var(--t2);flex-shrink:0;">${data[i]}</span>
|
| 310 |
-
`;
|
| 311 |
-
leg.appendChild(row);
|
| 312 |
-
});
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
function rebuildCharts(){
|
| 316 |
-
if(window.fch)window.fch.destroy();
|
| 317 |
-
if(window.tch)window.tch.destroy();
|
| 318 |
-
const {grid,tick}=gc();
|
| 319 |
-
/* Flow chart : 2 datasets (Objects + FPS) — compatible avec trackiq-api.js */
|
| 320 |
-
window.fch=new Chart(document.getElementById('fc'),{
|
| 321 |
-
data:{labels:['—'],datasets:[
|
| 322 |
-
{type:'line',label:'Objects',data:[0],borderColor:'#3b82f6',backgroundColor:'rgba(59,130,246,0.08)',borderWidth:1.5,tension:0.45,pointRadius:0,fill:true,yAxisID:'y'},
|
| 323 |
-
{type:'line',label:'FPS',data:[0],borderColor:'#10b981',backgroundColor:'rgba(16,185,129,0.05)',borderWidth:1.5,tension:0.45,pointRadius:0,yAxisID:'y2'}
|
| 324 |
-
]},
|
| 325 |
-
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},plugins:{legend:{labels:{boxWidth:8,font:{size:9},color:tick}}},scales:{x:{ticks:{font:{size:9},color:tick,maxTicksLimit:8},grid:{color:grid},border:{display:false}},y:{position:'left',beginAtZero:true,ticks:{font:{size:9},color:tick},grid:{color:grid},border:{display:false}},y2:{position:'right',beginAtZero:true,ticks:{font:{size:9},color:tick},grid:{drawOnChartArea:false},border:{display:false}}}}
|
| 326 |
-
});
|
| 327 |
-
/* Classification chart : NON stacké, 1 dataset — compatible avec trackiq-api.js */
|
| 328 |
-
window.tch=new Chart(document.getElementById('tc'),{
|
| 329 |
-
type:'bar',
|
| 330 |
-
data:{labels:['—'],datasets:[
|
| 331 |
-
{label:'Count',data:[0],backgroundColor:'#3b82f6',borderRadius:3,borderSkipped:false}
|
| 332 |
-
]},
|
| 333 |
-
options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{ticks:{font:{size:9},color:tick},grid:{color:grid},border:{display:false}},y:{beginAtZero:true,ticks:{font:{size:9},color:tick},grid:{color:grid},border:{display:false}}}}
|
| 334 |
-
});
|
| 335 |
-
}
|
| 336 |
-
|
| 337 |
-
function drawHm(){
|
| 338 |
-
const c=document.getElementById('hmc2'),b=c.parentElement;
|
| 339 |
-
const w=b.offsetWidth,h=b.offsetHeight;
|
| 340 |
-
if(!w||!h)return;
|
| 341 |
-
c.width=w;c.height=h;
|
| 342 |
-
const ctx=c.getContext('2d');
|
| 343 |
-
const dk=document.documentElement.getAttribute('data-theme')==='dark';
|
| 344 |
-
ctx.fillStyle=dk?'#0c1219':'#e8eef5';
|
| 345 |
-
ctx.fillRect(0,0,w,h);
|
| 346 |
-
ctx.strokeStyle=dk?'rgba(255,255,255,0.07)':'rgba(0,0,0,0.07)';
|
| 347 |
-
ctx.lineWidth=1;
|
| 348 |
-
[0.25,0.5,0.75].forEach(r=>{ctx.beginPath();ctx.moveTo(0,h*r);ctx.lineTo(w,h*r);ctx.stroke();ctx.beginPath();ctx.moveTo(w*r,0);ctx.lineTo(w*r,h);ctx.stroke();});
|
| 349 |
-
const pts=Array.isArray(window._tiqHeatPoints)?window._tiqHeatPoints:[];
|
| 350 |
-
if(pts.length){
|
| 351 |
-
let mxR=0,mxH=0;
|
| 352 |
-
pts.forEach(p=>{if(p.r>mxR)mxR=p.r;if(p.h>mxH)mxH=p.h;});
|
| 353 |
-
mxR=Math.max(mxR,0.01);mxH=Math.max(mxH,0.01);
|
| 354 |
-
pts.forEach(p=>{
|
| 355 |
-
const px=(p.x/mxR)*w,py=(p.y/mxH)*h;
|
| 356 |
-
const rad=Math.max(10,Math.min(w,h)*0.015);
|
| 357 |
-
const grd=ctx.createRadialGradient(px,py,0,px,py,rad);
|
| 358 |
-
grd.addColorStop(0,'rgba(220,38,38,0.5)');
|
| 359 |
-
grd.addColorStop(0.4,'rgba(180,0,0,0.18)');
|
| 360 |
-
grd.addColorStop(1,'rgba(0,0,0,0)');
|
| 361 |
-
ctx.fillStyle=grd;ctx.fillRect(0,0,w,h);
|
| 362 |
-
});
|
| 363 |
-
ctx.font="9px 'DM Mono',monospace";
|
| 364 |
-
ctx.fillStyle=dk?'rgba(255,255,255,0.3)':'rgba(0,0,0,0.25)';
|
| 365 |
-
ctx.textAlign='left';ctx.fillText(pts.length+' points',8,13);
|
| 366 |
-
}else{
|
| 367 |
-
ctx.fillStyle=dk?'rgba(255,255,255,0.18)':'rgba(0,0,0,0.2)';
|
| 368 |
-
ctx.font="12px 'DM Sans',sans-serif";ctx.textAlign='center';
|
| 369 |
-
ctx.fillText('Run an analysis to build the heatmap',w/2,h/2);
|
| 370 |
-
ctx.textAlign='left';
|
| 371 |
-
}
|
| 372 |
-
ctx.font="10px 'DM Mono',monospace";
|
| 373 |
-
ctx.fillStyle=dk?'rgba(255,255,255,0.24)':'rgba(0,0,0,0.2)';
|
| 374 |
-
ctx.fillText('N',w/2-3,12);ctx.fillText('S',w/2-3,h-5);ctx.fillText('W',5,h/2+3);ctx.fillText('E',w-10,h/2+3);
|
| 375 |
-
}
|
| 376 |
-
|
| 377 |
-
async function loadHeatmap(){
|
| 378 |
-
try{
|
| 379 |
-
const r=await fetch('/api/logs/rows');
|
| 380 |
-
if(!r.ok)return;
|
| 381 |
-
const rows=await r.json();
|
| 382 |
-
if(!rows.length)return;
|
| 383 |
-
const pts=[];let mxX=0,mxY=0;
|
| 384 |
-
rows.slice(0,4000).forEach(r=>{
|
| 385 |
-
if(r.cx>0&&r.cy>0){pts.push({x:r.cx,y:r.cy,r:0,h:0});if(r.cx>mxX)mxX=r.cx;if(r.cy>mxY)mxY=r.cy;}
|
| 386 |
-
});
|
| 387 |
-
if(!pts.length)return;
|
| 388 |
-
pts.forEach(p=>{p.r=mxX;p.h=mxY;});
|
| 389 |
-
window._tiqHeatPoints=pts;
|
| 390 |
-
drawHm();
|
| 391 |
-
}catch(e){}
|
| 392 |
-
}
|
| 393 |
-
|
| 394 |
-
setTimeout(rebuildCharts,120);
|
| 395 |
-
setTimeout(rebuildPie,150);
|
| 396 |
-
setTimeout(drawHm,180);
|
| 397 |
-
setTimeout(loadHeatmap,600);
|
| 398 |
-
setInterval(loadHeatmap,20000);
|
| 399 |
-
window.addEventListener('resize',()=>setTimeout(drawHm,50));
|
| 400 |
-
|
| 401 |
-
(function(){
|
| 402 |
-
try{
|
| 403 |
-
const th=localStorage.getItem('tiq_theme');
|
| 404 |
-
if(th){
|
| 405 |
-
document.documentElement.setAttribute('data-theme',th);
|
| 406 |
-
if(th==='dark'){['ti','ti2'].forEach(x=>{const e=document.getElementById(x);if(e)e.className='fas fa-sun';});}
|
| 407 |
-
}
|
| 408 |
-
const lg=localStorage.getItem('tiq_lang');
|
| 409 |
-
if(lg&&lg!=='en'){
|
| 410 |
-
const btn=document.querySelector('.lb[onclick*="\''+lg+'\'"]');
|
| 411 |
-
if(btn)setLang(lg,btn);
|
| 412 |
-
}
|
| 413 |
-
}catch(e){}
|
| 414 |
-
})();
|
| 415 |
-
</script>
|
| 416 |
-
<script src="/static/trackiq-api.js"></script>
|
| 417 |
-
</body>
|
| 418 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/templates/history.html
DELETED
|
@@ -1,295 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" data-theme="light">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>TrackIQ — History</title>
|
| 7 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 9 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 10 |
-
<style>
|
| 11 |
-
:root{--bg:#f0f4f8;--surface:#fff;--surface2:#f6f8fb;--border:rgba(0,0,0,0.07);--shadow:0 2px 16px rgba(0,0,0,0.09);--shadow-lg:0 8px 36px rgba(0,0,0,0.13);--text:#0f1923;--text2:#5a6a7a;--text3:#9aaab8;--accent:#880000;--accent2:#660000;--accent-light:rgba(136,0,0,0.07);--green:#16a34a;--green-bg:#dcfce7;--sidebar-w:60px;--radius:14px;--radius-sm:9px;}
|
| 12 |
-
[data-theme="dark"]{--bg:#0c1117;--surface:#151c25;--surface2:#1c2531;--border:rgba(255,255,255,0.07);--shadow:0 2px 16px rgba(0,0,0,0.45);--text:#e8edf2;--text2:#7a8fa0;--text3:#3a4a5a;--green-bg:#052e16;}
|
| 13 |
-
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
| 14 |
-
::-webkit-scrollbar{width:0;}
|
| 15 |
-
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);font-size:13px;display:flex;min-height:100vh;transition:background 0.3s,color 0.3s;}
|
| 16 |
-
|
| 17 |
-
.sidebar{width:var(--sidebar-w);background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:16px 0;gap:4px;position:fixed;top:0;left:0;bottom:0;z-index:200;box-shadow:2px 0 12px rgba(0,0,0,0.04);}
|
| 18 |
-
.sb-logo{width:34px;height:34px;background:var(--accent);border-radius:8px;display:flex;align-items:center;justify-content:center;color:white;font-size:14px;margin-bottom:10px;text-decoration:none;}
|
| 19 |
-
.sb-sep{width:28px;height:1px;background:var(--border);margin:4px 0;}
|
| 20 |
-
.sb-btn{width:38px;height:38px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:15px;cursor:pointer;transition:all 0.2s;text-decoration:none;border:none;background:none;position:relative;}
|
| 21 |
-
.sb-btn:hover{background:var(--surface2);color:var(--text);}
|
| 22 |
-
.sb-btn.active{background:var(--accent-light);color:var(--accent);}
|
| 23 |
-
.sb-btn .tip{position:absolute;left:46px;top:50%;transform:translateY(-50%);background:var(--text);color:var(--surface);font-size:11px;padding:3px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity 0.15s;z-index:300;}
|
| 24 |
-
.sb-btn:hover .tip{opacity:1;}
|
| 25 |
-
.sb-spacer{flex:1;}
|
| 26 |
-
|
| 27 |
-
.page-wrap{margin-left:var(--sidebar-w);flex:1;display:flex;flex-direction:column;}
|
| 28 |
-
.topbar{height:52px;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 22px;position:sticky;top:0;z-index:100;}
|
| 29 |
-
.topbar-l{display:flex;align-items:center;gap:10px;}
|
| 30 |
-
.brand-name{font-size:15px;font-weight:600;} .brand-name b{color:var(--accent);}
|
| 31 |
-
.sep{color:var(--border);font-size:18px;margin:0 2px;}
|
| 32 |
-
.topbar-r{display:flex;align-items:center;gap:7px;}
|
| 33 |
-
.lang-group{display:flex;background:var(--surface2);border-radius:6px;padding:2px;border:1px solid var(--border);}
|
| 34 |
-
.lang-btn{font-size:11px;font-weight:500;padding:3px 8px;border:none;background:none;cursor:pointer;border-radius:4px;color:var(--text2);font-family:'DM Sans',sans-serif;transition:all 0.2s;}
|
| 35 |
-
.lang-btn.active{background:var(--accent);color:white;}
|
| 36 |
-
.icon-btn{width:30px;height:30px;border-radius:6px;border:1px solid var(--border);background:var(--surface);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text2);font-size:12px;}
|
| 37 |
-
.icon-btn:hover{background:var(--surface2);}
|
| 38 |
-
|
| 39 |
-
main{flex:1;padding:18px 20px;max-width:1380px;width:100%;margin:0 auto;display:flex;flex-direction:column;gap:16px;}
|
| 40 |
-
|
| 41 |
-
/* PLAYER CARD — full width */
|
| 42 |
-
.player-card{grid-column:1/3;background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);box-shadow:var(--shadow-lg);overflow:hidden;}
|
| 43 |
-
.player-head{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;}
|
| 44 |
-
.player-title{font-size:13px;font-weight:500;display:flex;align-items:center;gap:7px;}
|
| 45 |
-
.player-title i{color:var(--accent);}
|
| 46 |
-
.player-body{position:relative;background:#000;}
|
| 47 |
-
.player-body video{width:100%;display:block;max-height:420px;object-fit:contain;}
|
| 48 |
-
.player-hud{position:absolute;top:11px;left:13px;color:white;font-family:'DM Mono',monospace;font-size:12px;line-height:1.8;text-shadow:1px 1px 4px rgba(0,0,0,0.98);pointer-events:none;}
|
| 49 |
-
.player-empty{padding:80px 20px;text-align:center;color:var(--text3);}
|
| 50 |
-
.player-empty i{font-size:44px;display:block;margin-bottom:12px;}
|
| 51 |
-
.player-empty p{font-size:13px;}
|
| 52 |
-
|
| 53 |
-
/* VIDEO GRID */
|
| 54 |
-
.vid-card{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);box-shadow:var(--shadow);overflow:hidden;cursor:pointer;transition:all 0.2s;}
|
| 55 |
-
.vid-card:hover{box-shadow:var(--shadow-lg);transform:translateY(-3px);}
|
| 56 |
-
.vid-card.selected{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent);}
|
| 57 |
-
.vid-thumb{position:relative;background:#0a0a0a;aspect-ratio:16/9;display:flex;align-items:center;justify-content:center;overflow:hidden;}
|
| 58 |
-
.vid-thumb video{width:100%;height:100%;object-fit:cover;display:block;}
|
| 59 |
-
.vid-thumb-ph{color:rgba(255,255,255,0.3);font-size:32px;}
|
| 60 |
-
.play-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.3);opacity:0;transition:opacity 0.2s;}
|
| 61 |
-
.play-overlay i{font-size:32px;color:white;}
|
| 62 |
-
.vid-card:hover .play-overlay{opacity:1;}
|
| 63 |
-
.vid-info{padding:11px 13px;}
|
| 64 |
-
.vid-name{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:3px;}
|
| 65 |
-
.vid-meta{font-size:10px;color:var(--text2);display:flex;align-items:center;justify-content:space-between;}
|
| 66 |
-
.vid-meta span{display:flex;align-items:center;gap:4px;}
|
| 67 |
-
.vid-actions{display:flex;gap:6px;margin-top:8px;}
|
| 68 |
-
.btn-vi{flex:1;padding:5px 8px;border-radius:6px;border:1px solid var(--border);background:var(--surface2);color:var(--text2);font-size:11px;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;gap:4px;transition:all 0.2s;}
|
| 69 |
-
.btn-vi:hover{background:var(--accent);color:white;border-color:var(--accent);}
|
| 70 |
-
.btn-vi.danger:hover{background:#dc2626;border-color:#dc2626;}
|
| 71 |
-
|
| 72 |
-
/* EMPTY HISTORY */
|
| 73 |
-
.empty-hist{text-align:center;padding:60px 20px;color:var(--text3);}
|
| 74 |
-
.empty-hist i{font-size:48px;display:block;margin-bottom:14px;}
|
| 75 |
-
.empty-hist p{font-size:13px;margin-bottom:16px;}
|
| 76 |
-
.empty-hist a{display:inline-flex;align-items:center;gap:6px;background:var(--accent);color:white;padding:9px 18px;border-radius:9px;text-decoration:none;font-size:12px;}
|
| 77 |
-
|
| 78 |
-
/* BADGE */
|
| 79 |
-
.badge{font-size:10px;font-weight:500;padding:1px 6px;border-radius:4px;}
|
| 80 |
-
.bg{background:var(--green-bg);color:var(--green);}
|
| 81 |
-
</style>
|
| 82 |
-
</head>
|
| 83 |
-
<body>
|
| 84 |
-
|
| 85 |
-
<nav class="sidebar">
|
| 86 |
-
<a href="/" class="sb-logo"><i class="fas fa-satellite-dish"></i></a>
|
| 87 |
-
<div class="sb-sep"></div>
|
| 88 |
-
<a href="/home" class="sb-btn"><i class="fas fa-play-circle"></i><span class="tip" id="tip-analyze">Analyze</span></a>
|
| 89 |
-
<a href="/dashboard" class="sb-btn"><i class="fas fa-chart-line"></i><span class="tip" id="tip-dash">Dashboard</span></a>
|
| 90 |
-
<a href="/logs" class="sb-btn"><i class="fas fa-list-ul"></i><span class="tip" id="tip-logs">Logs</span></a>
|
| 91 |
-
<a href="/history" class="sb-btn active"><i class="fas fa-film"></i><span class="tip" id="tip-hist">History</span></a>
|
| 92 |
-
<div class="sb-spacer"></div>
|
| 93 |
-
<button class="sb-btn" onclick="toggleTheme()"><i class="fas fa-moon" id="themeIcon"></i><span class="tip">Theme</span></button>
|
| 94 |
-
</nav>
|
| 95 |
-
|
| 96 |
-
<div class="page-wrap">
|
| 97 |
-
<header class="topbar">
|
| 98 |
-
<div class="topbar-l">
|
| 99 |
-
<span class="brand-name">Track<b>IQ</b></span>
|
| 100 |
-
<span class="sep">|</span>
|
| 101 |
-
<span style="font-size:13px;font-weight:500;" id="t-title">Annotated History</span>
|
| 102 |
-
</div>
|
| 103 |
-
<div class="topbar-r">
|
| 104 |
-
<div class="lang-group">
|
| 105 |
-
<button class="lang-btn active" onclick="setLang('en',this)">EN</button>
|
| 106 |
-
<button class="lang-btn" onclick="setLang('fr',this)">FR</button>
|
| 107 |
-
<button class="lang-btn" onclick="setLang('wo',this)">WO</button>
|
| 108 |
-
</div>
|
| 109 |
-
<button class="icon-btn" onclick="toggleTheme()"><i class="fas fa-moon" id="themeIcon2"></i></button>
|
| 110 |
-
</div>
|
| 111 |
-
</header>
|
| 112 |
-
|
| 113 |
-
<main id="mainGrid">
|
| 114 |
-
|
| 115 |
-
<!-- Videos grid -->
|
| 116 |
-
<div id="videoGrid" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;"></div>
|
| 117 |
-
|
| 118 |
-
</main>
|
| 119 |
-
</div>
|
| 120 |
-
|
| 121 |
-
<script>
|
| 122 |
-
/* THEME */
|
| 123 |
-
function toggleTheme(){
|
| 124 |
-
const d=document.documentElement,dark=d.getAttribute('data-theme')==='dark';
|
| 125 |
-
d.setAttribute('data-theme',dark?'light':'dark');
|
| 126 |
-
['themeIcon','themeIcon2'].forEach(id=>{const el=document.getElementById(id);if(el)el.className=dark?'fas fa-moon':'fas fa-sun';});
|
| 127 |
-
try{localStorage.setItem('tiq_theme',dark?'light':'dark');}catch(e){}
|
| 128 |
-
}
|
| 129 |
-
|
| 130 |
-
/* LANG */
|
| 131 |
-
const T={
|
| 132 |
-
en:{title:'Annotated History',player:'Video player',dl:'Download','select-vid':'Select a video below to play it',tipAnalyze:'Analyze',tipDash:'Dashboard',tipLogs:'Logs',tipHist:'History',reanalyze:'Re-analyze',download:'Download',delete:'Delete',empty:'No annotated videos yet.',goAnalyze:'Go to Analyze'},
|
| 133 |
-
fr:{title:'Historique annoté',player:'Lecteur vidéo',dl:'Télécharger','select-vid':'Sélectionnez une vidéo ci-dessous',tipAnalyze:'Analyser',tipDash:'Tableau de bord',tipLogs:'Logs',tipHist:'Historique',reanalyze:'Ré-analyser',download:'Télécharger',delete:'Supprimer',empty:'Aucune vidéo annotée.',goAnalyze:'Aller sur Analyser'},
|
| 134 |
-
wo:{title:'Màgg yi',player:'Video bi',dl:'Télécharger','select-vid':'Tànn video ci suuf',tipAnalyze:'Jàng',tipDash:'Dashboard',tipLogs:'Logs',tipHist:'Màgg',reanalyze:'Wërtëk',download:'Télécharger',delete:'Bañ',empty:'Amul video.',goAnalyze:'Dem Analyser'}
|
| 135 |
-
};
|
| 136 |
-
let curLang='en';
|
| 137 |
-
function setLang(l,btn){
|
| 138 |
-
curLang=l;
|
| 139 |
-
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.remove('active'));btn.classList.add('active');
|
| 140 |
-
const t=T[l];
|
| 141 |
-
Object.entries({title:'t-title',player:'t-player',dl:'t-dl','select-vid':'t-select-vid',tipAnalyze:'tip-analyze',tipDash:'tip-dash',tipLogs:'tip-logs',tipHist:'tip-hist'}).forEach(([k,id])=>{const el=document.getElementById(id);if(el&&t[k])el.textContent=t[k];});
|
| 142 |
-
renderGrid();
|
| 143 |
-
try{localStorage.setItem('tiq_lang',l);}catch(e){}
|
| 144 |
-
}
|
| 145 |
-
|
| 146 |
-
/* HISTORY DATA */
|
| 147 |
-
function getHistory(){try{return JSON.parse(localStorage.getItem('tiq_history')||'[]');}catch(e){return[];}}
|
| 148 |
-
function saveHistory(h){try{localStorage.setItem('tiq_history',JSON.stringify(h));}catch(e){}}
|
| 149 |
-
|
| 150 |
-
/* FORMAT DATE */
|
| 151 |
-
function fmtDate(iso){
|
| 152 |
-
try{const d=new Date(iso);return d.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'})+' '+d.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit'});}catch(e){return 'Unknown date';}
|
| 153 |
-
}
|
| 154 |
-
|
| 155 |
-
let activeUrl=null,activeIdx=null;
|
| 156 |
-
|
| 157 |
-
function dlVideo(url,name){
|
| 158 |
-
const a=document.createElement('a');a.href=url;
|
| 159 |
-
a.download=name||'trackiq_video.mp4';a.click();
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
function playVideo(url,name,idx){
|
| 163 |
-
activeUrl=url;activeIdx=idx;
|
| 164 |
-
const op=document.getElementById('overlayPlayer');
|
| 165 |
-
op.src=url;op.play().catch(()=>{});
|
| 166 |
-
document.getElementById('overlayName').textContent=name;
|
| 167 |
-
document.getElementById('vidOverlay').style.display='flex';
|
| 168 |
-
}
|
| 169 |
-
function closeOverlay(){
|
| 170 |
-
const op=document.getElementById('overlayPlayer');op.pause();op.src='';
|
| 171 |
-
document.getElementById('vidOverlay').style.display='none';
|
| 172 |
-
activeUrl=null;activeIdx=null;
|
| 173 |
-
}
|
| 174 |
-
function dlOverlay(){if(activeUrl){const a=document.createElement('a');a.href=activeUrl;a.download='trackiq.mp4';a.click();}}
|
| 175 |
-
|
| 176 |
-
function dlActive(){
|
| 177 |
-
if(!activeUrl)return;
|
| 178 |
-
const a=document.createElement('a');a.href=activeUrl;a.download='trackiq_annotated.mp4';a.click();
|
| 179 |
-
}
|
| 180 |
-
|
| 181 |
-
function deleteVideo(idx){
|
| 182 |
-
const h=getHistory();h.splice(idx,1);saveHistory(h);
|
| 183 |
-
if(activeIdx===idx){
|
| 184 |
-
closeModal();
|
| 185 |
-
activeUrl=null;activeIdx=null;
|
| 186 |
-
}
|
| 187 |
-
renderGrid();
|
| 188 |
-
}
|
| 189 |
-
|
| 190 |
-
function renderGrid(){
|
| 191 |
-
const hist=getHistory();
|
| 192 |
-
const t=T[curLang];
|
| 193 |
-
const grid=document.getElementById('videoGrid');grid.innerHTML='';
|
| 194 |
-
|
| 195 |
-
if(!hist.length){
|
| 196 |
-
const empty=document.createElement('div');empty.className='empty-hist';
|
| 197 |
-
empty.innerHTML=`<i class="fas fa-film"></i><p>${t.empty}</p><a href="/home">${t.goAnalyze} <i class="fas fa-arrow-right"></i></a>`;
|
| 198 |
-
grid.appendChild(empty);return;
|
| 199 |
-
}
|
| 200 |
-
|
| 201 |
-
hist.forEach((item,i)=>{
|
| 202 |
-
const card=document.createElement('div');card.className='vid-card'+(activeIdx===i?' selected':'');
|
| 203 |
-
const isBlob=item.url&&item.url.startsWith('blob:');
|
| 204 |
-
const isBackend=item.url&&(item.url.startsWith('/api/')||item.url.startsWith('http'));
|
| 205 |
-
const canPlay=isBlob||isBackend;
|
| 206 |
-
card.innerHTML=`
|
| 207 |
-
<div class="vid-thumb">
|
| 208 |
-
${isBlob
|
| 209 |
-
? `<video src="${item.url}" style="width:100%;height:100%;object-fit:cover;" muted preload="metadata" onloadeddata="this.currentTime=1"></video>`
|
| 210 |
-
: `<div class="vid-thumb-ph" style="background:#0a0e15;display:flex;align-items:center;justify-content:center;height:100%;">
|
| 211 |
-
<i class="fas fa-film" style="font-size:32px;color:rgba(255,255,255,0.2);"></i>
|
| 212 |
-
</div>`
|
| 213 |
-
}
|
| 214 |
-
<div class="play-overlay"><i class="fas fa-play"></i></div>
|
| 215 |
-
</div>
|
| 216 |
-
<div class="vid-info">
|
| 217 |
-
<div class="vid-name" title="${item.name}">${item.name||'Unnamed'}</div>
|
| 218 |
-
<div class="vid-meta">
|
| 219 |
-
<span><i class="fas fa-calendar" style="font-size:9px;"></i> ${fmtDate(item.date)}</span>
|
| 220 |
-
<span class="badge bg">${isBlob?'Local':'Server'}</span>
|
| 221 |
-
</div>
|
| 222 |
-
<div class="vid-actions">
|
| 223 |
-
<button class="btn-vi" onclick="event.stopPropagation();playVideo('${item.url}','${item.name}',${i})">
|
| 224 |
-
<i class="fas fa-play"></i> Play
|
| 225 |
-
</button>
|
| 226 |
-
<button class="btn-vi" onclick="event.stopPropagation();dlVideo('${item.url}','${item.name}')">
|
| 227 |
-
<i class="fas fa-download"></i>
|
| 228 |
-
</button>
|
| 229 |
-
<button class="btn-vi danger" onclick="event.stopPropagation();deleteVideo(${i})">
|
| 230 |
-
<i class="fas fa-trash"></i>
|
| 231 |
-
</button>
|
| 232 |
-
</div>
|
| 233 |
-
</div>
|
| 234 |
-
`;
|
| 235 |
-
card.onclick=(ev)=>{if(!ev.target.closest('.btn-vi'))playVideo(item.url,item.name,i);};
|
| 236 |
-
grid.appendChild(card);
|
| 237 |
-
});
|
| 238 |
-
}
|
| 239 |
-
|
| 240 |
-
/* HUD ticker on player */
|
| 241 |
-
setInterval(()=>{
|
| 242 |
-
if(!activeUrl)return;
|
| 243 |
-
const f=570+Math.floor(Math.random()*80),s=295+Math.floor(Math.random()*40);
|
| 244 |
-
const hf=document.getElementById('oHudF');if(hf)hf.textContent=`F: = ${f} ▶ ${s}`;
|
| 245 |
-
const car=2+Math.floor(Math.random()*6),van=Math.floor(Math.random()*3),bike=Math.floor(Math.random()*6),p=Math.floor(Math.random()*7);
|
| 246 |
-
const hc=document.getElementById('oHudC');if(hc)hc.innerHTML=`Car = ${car}<br>Van = ${van}<br>bike = ${bike}<br>person = ${p}`;
|
| 247 |
-
},2200);
|
| 248 |
-
|
| 249 |
-
document.addEventListener('keydown',e=>{if(e.key==='Escape')closeOverlay();});
|
| 250 |
-
/* INIT */
|
| 251 |
-
(function(){
|
| 252 |
-
try{
|
| 253 |
-
const th=localStorage.getItem('tiq_theme');if(th){document.documentElement.setAttribute('data-theme',th);if(th==='dark'){['themeIcon','themeIcon2'].forEach(id=>{const el=document.getElementById(id);if(el)el.className='fas fa-sun';});}}
|
| 254 |
-
const lg=localStorage.getItem('tiq_lang');if(lg&&lg!=='en'){const btn=document.querySelector(`.lang-btn[onclick*="'${lg}'"]`);if(btn)setLang(lg,btn);}
|
| 255 |
-
}catch(e){}
|
| 256 |
-
renderGrid();
|
| 257 |
-
})();
|
| 258 |
-
</script>
|
| 259 |
-
<script src="/static/trackiq-api.js"></script>
|
| 260 |
-
|
| 261 |
-
<!-- ── Modal lecteur vidéo ── -->
|
| 262 |
-
<div id="vidOverlay" style="display:none;position:fixed;inset:0;z-index:1000;
|
| 263 |
-
background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);
|
| 264 |
-
align-items:center;justify-content:center;flex-direction:column;gap:12px;">
|
| 265 |
-
|
| 266 |
-
<!-- Header -->
|
| 267 |
-
<div style="display:flex;align-items:center;justify-content:space-between;
|
| 268 |
-
width:min(90vw,860px);padding:0 4px;">
|
| 269 |
-
<span id="overlayName" style="color:#fff;font-size:13px;font-weight:500;
|
| 270 |
-
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:70%;"></span>
|
| 271 |
-
<div style="display:flex;gap:8px;">
|
| 272 |
-
<button onclick="dlOverlay()" style="background:rgba(255,255,255,0.12);color:#fff;
|
| 273 |
-
border:none;border-radius:7px;padding:6px 13px;font-size:12px;cursor:pointer;
|
| 274 |
-
font-family:'DM Sans',sans-serif;display:flex;align-items:center;gap:5px;">
|
| 275 |
-
<i class="fas fa-download"></i> Download
|
| 276 |
-
</button>
|
| 277 |
-
<button onclick="closeOverlay()" style="background:rgba(220,38,38,0.7);color:#fff;
|
| 278 |
-
border:none;border-radius:7px;padding:6px 13px;font-size:12px;cursor:pointer;
|
| 279 |
-
font-family:'DM Sans',sans-serif;display:flex;align-items:center;gap:5px;">
|
| 280 |
-
<i class="fas fa-times"></i> Close
|
| 281 |
-
</button>
|
| 282 |
-
</div>
|
| 283 |
-
</div>
|
| 284 |
-
|
| 285 |
-
<!-- Vidéo -->
|
| 286 |
-
<video id="overlayPlayer" controls autoplay
|
| 287 |
-
style="width:min(90vw,860px);max-height:75vh;border-radius:10px;
|
| 288 |
-
background:#000;object-fit:contain;box-shadow:0 20px 60px rgba(0,0,0,0.6);">
|
| 289 |
-
</video>
|
| 290 |
-
|
| 291 |
-
<!-- Hint ESC -->
|
| 292 |
-
<span style="color:rgba(255,255,255,0.3);font-size:10px;">Press ESC to close</span>
|
| 293 |
-
</div>
|
| 294 |
-
</body>
|
| 295 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/templates/home.html
DELETED
|
@@ -1,684 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" data-theme="light">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>TrackIQ — Analyze</title>
|
| 7 |
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 9 |
-
<style>
|
| 10 |
-
:root{--bg:#f0f4f8;--sur:#fff;--sur2:#f6f8fb;--bd:rgba(0,0,0,0.07);--sh:0 2px 16px rgba(0,0,0,0.09);--shlg:0 8px 36px rgba(0,0,0,0.13);--t:#0f1923;--t2:#5a6a7a;--t3:#9aaab8;--ac:#880000;--ac2:#660000;--acl:rgba(136,0,0,0.07);--gr:#16a34a;--sw:60px;--r:14px;--rs:9px;}
|
| 11 |
-
[data-theme="dark"]{--bg:#0c1117;--sur:#151c25;--sur2:#1c2531;--bd:rgba(255,255,255,0.07);--sh:0 2px 16px rgba(0,0,0,0.45);--t:#e8edf2;--t2:#7a8fa0;--t3:#3a4a5a;}
|
| 12 |
-
*{box-sizing:border-box;margin:0;padding:0;}::-webkit-scrollbar{width:0;}
|
| 13 |
-
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--t);font-size:13px;display:flex;min-height:100vh;}
|
| 14 |
-
|
| 15 |
-
/* Sidebar */
|
| 16 |
-
.sb{width:var(--sw);background:var(--sur);border-right:1px solid var(--bd);display:flex;flex-direction:column;align-items:center;padding:14px 0;gap:4px;position:fixed;top:0;left:0;bottom:0;z-index:200;}
|
| 17 |
-
.sbl{width:34px;height:34px;background:var(--ac);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;margin-bottom:10px;text-decoration:none;}
|
| 18 |
-
.sbsep{width:26px;height:1px;background:var(--bd);margin:3px 0;}
|
| 19 |
-
.sbb{width:38px;height:38px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--t3);font-size:14px;cursor:pointer;transition:all 0.2s;text-decoration:none;border:none;background:none;position:relative;}
|
| 20 |
-
.sbb:hover{background:var(--sur2);color:var(--t);}.sbb.on{background:var(--acl);color:var(--ac);}
|
| 21 |
-
.sbb .tip{position:absolute;left:44px;top:50%;transform:translateY(-50%);background:var(--t);color:var(--sur);font-size:10px;padding:3px 7px;border-radius:4px;white-space:nowrap;opacity:0;pointer-events:none;z-index:300;}.sbb:hover .tip{opacity:1;}
|
| 22 |
-
.sbsp{flex:1;}
|
| 23 |
-
|
| 24 |
-
/* Layout */
|
| 25 |
-
.pw{margin-left:var(--sw);flex:1;display:flex;flex-direction:column;}
|
| 26 |
-
.topb{height:50px;background:var(--sur);border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;padding:0 20px;position:sticky;top:0;z-index:100;}
|
| 27 |
-
.topl{display:flex;align-items:center;gap:9px;}
|
| 28 |
-
.bn{font-size:14px;font-weight:600;}.bn b{color:var(--ac);}
|
| 29 |
-
.topr{display:flex;align-items:center;gap:6px;}
|
| 30 |
-
.lg{display:flex;background:var(--sur2);border-radius:5px;padding:2px;border:1px solid var(--bd);}
|
| 31 |
-
.lb{font-size:10px;font-weight:500;padding:2px 8px;border:none;background:none;cursor:pointer;border-radius:3px;color:var(--t2);font-family:'DM Sans',sans-serif;transition:all 0.2s;}.lb.on{background:var(--ac);color:#fff;}
|
| 32 |
-
.ib{width:28px;height:28px;border-radius:5px;border:1px solid var(--bd);background:var(--sur);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--t2);font-size:11px;}
|
| 33 |
-
.ib:hover{background:var(--sur2);}
|
| 34 |
-
|
| 35 |
-
main{flex:1;padding:14px 16px;display:grid;grid-template-columns:340px 1fr;gap:12px;max-width:1400px;width:100%;margin:0 auto;}
|
| 36 |
-
|
| 37 |
-
/* Card */
|
| 38 |
-
.card{background:var(--sur);border-radius:var(--r);border:1px solid var(--bd);box-shadow:var(--sh);padding:14px 16px;}
|
| 39 |
-
.ct{font-size:12px;font-weight:500;margin-bottom:11px;display:flex;align-items:center;gap:6px;}
|
| 40 |
-
.ct i{color:var(--ac);}
|
| 41 |
-
|
| 42 |
-
/* Source tabs */
|
| 43 |
-
.stabs{display:flex;gap:3px;background:var(--sur2);border-radius:7px;padding:3px;border:1px solid var(--bd);margin-bottom:11px;}
|
| 44 |
-
.stab{flex:1;padding:6px 4px;border-radius:5px;font-size:11px;cursor:pointer;color:var(--t2);border:none;background:none;font-family:'DM Sans',sans-serif;transition:all 0.2s;display:flex;align-items:center;justify-content:center;gap:4px;}
|
| 45 |
-
.stab.on{background:var(--sur);color:var(--t);box-shadow:0 1px 4px rgba(0,0,0,0.1);font-weight:500;}
|
| 46 |
-
|
| 47 |
-
/* Upload zone */
|
| 48 |
-
.uzone{border:2px dashed var(--bd);border-radius:var(--rs);min-height:110px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;cursor:pointer;transition:all 0.2s;padding:14px;position:relative;overflow:hidden;}
|
| 49 |
-
.uzone:hover,.uzone.drag{border-color:var(--ac);background:var(--acl);}
|
| 50 |
-
.uzone.has{border-style:solid;border-color:var(--ac);}
|
| 51 |
-
.uzone i{font-size:22px;color:var(--t3);}
|
| 52 |
-
.uzone.has i{color:var(--ac);}
|
| 53 |
-
.uzone p{font-size:11px;color:var(--t2);text-align:center;}
|
| 54 |
-
.uzone input{position:absolute;inset:0;opacity:0;cursor:pointer;}
|
| 55 |
-
.fname{font-size:11px;font-weight:500;color:var(--ac);display:none;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
| 56 |
-
.prog{display:none;background:var(--sur2);border-radius:3px;height:4px;margin-top:7px;overflow:hidden;}
|
| 57 |
-
.progb{height:100%;background:var(--ac);width:0%;transition:width 0.3s;}
|
| 58 |
-
.progt{font-size:10px;color:var(--t2);margin-top:4px;display:none;}
|
| 59 |
-
|
| 60 |
-
/* URL */
|
| 61 |
-
.urow{display:flex;gap:6px;}
|
| 62 |
-
.uin{flex:1;padding:7px 9px;border:1px solid var(--bd);border-radius:7px;font-size:12px;background:var(--sur2);color:var(--t);font-family:'DM Sans',sans-serif;outline:none;}
|
| 63 |
-
.uin:focus{border-color:var(--ac);}
|
| 64 |
-
.uin::placeholder{color:var(--t3);}
|
| 65 |
-
.bsm{padding:7px 12px;background:var(--ac);color:#fff;border:none;border-radius:7px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;}
|
| 66 |
-
|
| 67 |
-
/* Webcam */
|
| 68 |
-
.wcbox{background:#000;border-radius:8px;overflow:hidden;aspect-ratio:16/9;position:relative;margin-bottom:10px;}
|
| 69 |
-
.wcbox video,.wcbox img{width:100%;height:100%;object-fit:cover;display:block;}
|
| 70 |
-
.wcph{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;color:rgba(255,255,255,0.3);font-size:11px;}
|
| 71 |
-
.wcph i{font-size:22px;}
|
| 72 |
-
.recbdg{position:absolute;top:7px;left:8px;display:none;align-items:center;gap:4px;background:rgba(220,38,38,0.85);color:#fff;font-size:10px;padding:2px 8px;border-radius:10px;}
|
| 73 |
-
@keyframes blink{0%,100%{opacity:1}50%{opacity:0.3}}
|
| 74 |
-
.recbdg i{font-size:8px;animation:blink 1s infinite;}
|
| 75 |
-
|
| 76 |
-
/* Classes */
|
| 77 |
-
.clshd{display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;}
|
| 78 |
-
.clshd span{font-size:11px;color:var(--t2);}
|
| 79 |
-
.clshd button{font-size:11px;color:var(--ac);background:none;border:none;cursor:pointer;font-family:'DM Sans',sans-serif;}
|
| 80 |
-
.clsg{display:grid;grid-template-columns:1fr 1fr;gap:5px;}
|
| 81 |
-
.clsi{display:flex;align-items:center;gap:5px;padding:5px 8px;border-radius:6px;border:1px solid var(--bd);cursor:pointer;transition:all 0.2s;font-size:11px;background:var(--sur2);}
|
| 82 |
-
.clsi:hover{border-color:var(--ac);}.clsi.on{border-color:var(--ac);background:var(--acl);}
|
| 83 |
-
.cldot{width:8px;height:8px;border-radius:3px;flex-shrink:0;}
|
| 84 |
-
|
| 85 |
-
/* Buttons */
|
| 86 |
-
.runbtn{width:100%;padding:10px;background:var(--ac);color:#fff;border:none;border-radius:9px;font-size:13px;font-weight:500;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;gap:7px;transition:all 0.2s;margin-top:4px;}
|
| 87 |
-
.runbtn:hover{background:var(--ac2);}.runbtn:disabled{opacity:0.4;cursor:not-allowed;}
|
| 88 |
-
.rstbtn{width:100%;padding:8px;background:var(--sur2);color:var(--t2);border:1px solid var(--bd);border-radius:9px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;gap:6px;transition:all 0.2s;}
|
| 89 |
-
.rstbtn:hover{border-color:var(--ac);color:var(--ac);}
|
| 90 |
-
|
| 91 |
-
/* Right col */
|
| 92 |
-
.rc{display:flex;flex-direction:column;gap:11px;}
|
| 93 |
-
|
| 94 |
-
/* Big panel */
|
| 95 |
-
.bp{background:var(--sur);border-radius:var(--r);border:1px solid var(--bd);box-shadow:var(--shlg);overflow:hidden;}
|
| 96 |
-
.bph{padding:10px 14px;border-bottom:1px solid var(--bd);display:flex;align-items:center;justify-content:space-between;}
|
| 97 |
-
.bpht{font-size:12px;font-weight:500;display:flex;align-items:center;gap:6px;}
|
| 98 |
-
.bpht i{color:var(--ac);}
|
| 99 |
-
.bpb{position:relative;background:#080e15;height:420px;}
|
| 100 |
-
|
| 101 |
-
/* Video dans le panel */
|
| 102 |
-
.bpb video,.bpb img#wcYoloImg{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:none;}
|
| 103 |
-
|
| 104 |
-
/* HUD top-left */
|
| 105 |
-
.hud{position:absolute;top:10px;left:11px;color:#fff;font-family:'DM Mono',monospace;font-size:11px;line-height:1.85;text-shadow:1px 1px 4px rgba(0,0,0,0.98);pointer-events:none;display:none;z-index:4;}
|
| 106 |
-
.hudf{font-size:9px;color:rgba(255,255,255,0.4);margin-bottom:2px;}
|
| 107 |
-
|
| 108 |
-
/* Count overlay top-right */
|
| 109 |
-
.cov{position:absolute;top:10px;right:11px;display:none;flex-direction:column;gap:4px;pointer-events:none;z-index:4;}
|
| 110 |
-
.cbdg{display:flex;flex-direction:column;background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);border-left:3px solid transparent;border-radius:5px;padding:4px 9px;min-width:115px;}
|
| 111 |
-
.cbdg .cn{font-size:13px;font-weight:600;line-height:1.2;}
|
| 112 |
-
.cbdg .cu{font-size:9px;color:#ccc;line-height:1.2;}
|
| 113 |
-
|
| 114 |
-
/* No-object banner */
|
| 115 |
-
.noobj{display:none;position:absolute;bottom:14px;left:50%;transform:translateX(-50%);z-index:5;
|
| 116 |
-
background:rgba(220,38,38,0.9);color:#fff;font-size:12px;font-weight:500;
|
| 117 |
-
padding:6px 16px;border-radius:20px;align-items:center;gap:7px;white-space:nowrap;}
|
| 118 |
-
|
| 119 |
-
/* Empty state */
|
| 120 |
-
.es{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:9px;color:rgba(255,255,255,0.18);}
|
| 121 |
-
.es i{font-size:38px;}.es p{font-size:12px;}
|
| 122 |
-
|
| 123 |
-
/* Stats row */
|
| 124 |
-
.sr{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;}
|
| 125 |
-
.sc{background:var(--sur);border-radius:var(--rs);border:1px solid var(--bd);box-shadow:var(--sh);padding:10px 12px;}
|
| 126 |
-
.sc .sl{font-size:10px;color:var(--t2);margin-bottom:3px;}
|
| 127 |
-
.sc .sv{font-size:18px;font-weight:300;letter-spacing:-0.5px;}
|
| 128 |
-
.sc .su{font-size:10px;color:var(--t2);}
|
| 129 |
-
|
| 130 |
-
/* Download bar */
|
| 131 |
-
.dlb{display:flex;align-items:center;justify-content:space-between;background:var(--sur);border-radius:var(--rs);border:1px solid var(--bd);box-shadow:var(--sh);padding:9px 13px;}
|
| 132 |
-
.dlb span{font-size:11px;color:var(--t2);}
|
| 133 |
-
.btndl{display:flex;align-items:center;gap:5px;background:var(--ac);color:#fff;border:none;border-radius:7px;padding:7px 13px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif;}
|
| 134 |
-
.btndl:hover{background:var(--ac2);}.btndl:disabled{opacity:0.4;cursor:not-allowed;background:var(--t3);}
|
| 135 |
-
</style>
|
| 136 |
-
</head>
|
| 137 |
-
<body>
|
| 138 |
-
|
| 139 |
-
<nav class="sb">
|
| 140 |
-
<a href="/" class="sbl"><i class="fas fa-satellite-dish"></i></a>
|
| 141 |
-
<div class="sbsep"></div>
|
| 142 |
-
<a href="/home" class="sbb on"><i class="fas fa-play-circle"></i><span class="tip">Analyze</span></a>
|
| 143 |
-
<a href="/dashboard" class="sbb"><i class="fas fa-chart-line"></i><span class="tip">Dashboard</span></a>
|
| 144 |
-
<a href="/logs" class="sbb"><i class="fas fa-list-ul"></i><span class="tip">Logs</span></a>
|
| 145 |
-
<a href="/history" class="sbb"><i class="fas fa-film"></i><span class="tip">History</span></a>
|
| 146 |
-
<div class="sbsp"></div>
|
| 147 |
-
<button class="sbb" onclick="toggleTheme()"><i class="fas fa-moon" id="ti"></i><span class="tip">Theme</span></button>
|
| 148 |
-
</nav>
|
| 149 |
-
|
| 150 |
-
<div class="pw">
|
| 151 |
-
<header class="topb">
|
| 152 |
-
<div class="topl">
|
| 153 |
-
<span class="bn">Track<b>IQ</b></span>
|
| 154 |
-
<span style="color:var(--bd);font-size:16px;">|</span>
|
| 155 |
-
<span style="font-size:12px;font-weight:500;color:var(--t2);">Video Analysis</span>
|
| 156 |
-
</div>
|
| 157 |
-
<div class="topr">
|
| 158 |
-
<div class="lg">
|
| 159 |
-
<button class="lb on" onclick="setLang('en',this)">EN</button>
|
| 160 |
-
<button class="lb" onclick="setLang('fr',this)">FR</button>
|
| 161 |
-
<button class="lb" onclick="setLang('wo',this)">WO</button>
|
| 162 |
-
</div>
|
| 163 |
-
<button class="ib" onclick="toggleTheme()"><i class="fas fa-moon" id="ti2"></i></button>
|
| 164 |
-
</div>
|
| 165 |
-
</header>
|
| 166 |
-
|
| 167 |
-
<main>
|
| 168 |
-
<!-- Colonne gauche -->
|
| 169 |
-
<div style="display:flex;flex-direction:column;gap:11px;">
|
| 170 |
-
|
| 171 |
-
<!-- Input source -->
|
| 172 |
-
<div class="card">
|
| 173 |
-
<div class="ct"><i class="fas fa-video"></i> Input source</div>
|
| 174 |
-
<div class="stabs">
|
| 175 |
-
<button class="stab on" onclick="switchSrc('upload',this)"><i class="fas fa-upload"></i> Video</button>
|
| 176 |
-
<button class="stab" onclick="switchSrc('url',this)"><i class="fas fa-link"></i> URL</button>
|
| 177 |
-
<button class="stab" onclick="switchSrc('webcam',this)"><i class="fas fa-camera"></i> Webcam</button>
|
| 178 |
-
</div>
|
| 179 |
-
|
| 180 |
-
<!-- Upload -->
|
| 181 |
-
<div id="src-upload">
|
| 182 |
-
<label class="uzone" id="uzone">
|
| 183 |
-
<i class="fas fa-cloud-upload-alt"></i>
|
| 184 |
-
<p>Drop video here or click to browse</p>
|
| 185 |
-
<p style="font-size:10px;color:var(--t3);">MP4, AVI, MOV — max 500 MB</p>
|
| 186 |
-
<span class="fname" id="fname"></span>
|
| 187 |
-
<input type="file" id="fileInput" accept="video/*" onchange="onFile(event)">
|
| 188 |
-
</label>
|
| 189 |
-
<div class="prog" id="progWrap"><div class="progb" id="progBar"></div></div>
|
| 190 |
-
<div class="progt" id="progTxt"></div>
|
| 191 |
-
</div>
|
| 192 |
-
|
| 193 |
-
<!-- URL -->
|
| 194 |
-
<div id="src-url" style="display:none;">
|
| 195 |
-
<div class="urow">
|
| 196 |
-
<input type="text" class="uin" id="urlIn" placeholder="https://… or rtsp://…">
|
| 197 |
-
<button class="bsm" onclick="loadUrl()">Load</button>
|
| 198 |
-
</div>
|
| 199 |
-
</div>
|
| 200 |
-
|
| 201 |
-
<!-- Webcam -->
|
| 202 |
-
<div id="src-webcam" style="display:none;">
|
| 203 |
-
<!-- Mini preview : flux YOLO annoté -->
|
| 204 |
-
<div class="wcbox" id="wcbox">
|
| 205 |
-
<img id="wcMiniYolo" alt="YOLO stream" style="display:none;">
|
| 206 |
-
<div class="wcph" id="wcPh"><i class="fas fa-camera-slash"></i><span>Click Start</span></div>
|
| 207 |
-
<div class="recbdg" id="recBadge"><i class="fas fa-circle"></i> LIVE</div>
|
| 208 |
-
</div>
|
| 209 |
-
<!-- Bouton stop webcam seulement (plus de bouton Enregistrer) -->
|
| 210 |
-
<button class="runbtn" id="wcBtn" onclick="startWc()">
|
| 211 |
-
<i class="fas fa-circle" style="color:#f87171;font-size:8px;"></i> Start webcam
|
| 212 |
-
</button>
|
| 213 |
-
</div>
|
| 214 |
-
</div>
|
| 215 |
-
|
| 216 |
-
<!-- Detection classes -->
|
| 217 |
-
<div class="card">
|
| 218 |
-
<div class="ct"><i class="fas fa-tags"></i> Detection classes</div>
|
| 219 |
-
<div class="clshd">
|
| 220 |
-
<span id="clsCnt">0 selected</span>
|
| 221 |
-
<button onclick="toggleAll()">Select all</button>
|
| 222 |
-
</div>
|
| 223 |
-
<div class="clsg" id="clsGrid"></div>
|
| 224 |
-
</div>
|
| 225 |
-
|
| 226 |
-
<button class="runbtn" id="runBtn" onclick="runAnalysis()" disabled>
|
| 227 |
-
<i class="fas fa-play"></i> Run Analysis
|
| 228 |
-
</button>
|
| 229 |
-
<button class="rstbtn" onclick="resetAll()">
|
| 230 |
-
<i class="fas fa-rotate-right"></i> Reset
|
| 231 |
-
</button>
|
| 232 |
-
</div>
|
| 233 |
-
|
| 234 |
-
<!-- Colonne droite -->
|
| 235 |
-
<div class="rc">
|
| 236 |
-
|
| 237 |
-
<!-- Big result panel -->
|
| 238 |
-
<div class="bp">
|
| 239 |
-
<div class="bph">
|
| 240 |
-
<div class="bpht"><i class="fas fa-film"></i> Result</div>
|
| 241 |
-
<span style="font-size:11px;color:var(--t2);" id="srcLabel"></span>
|
| 242 |
-
</div>
|
| 243 |
-
<div class="bpb" id="bigBody">
|
| 244 |
-
<!-- Vidéo annotée (upload/url) -->
|
| 245 |
-
<video id="resultVid" controls></video>
|
| 246 |
-
<!-- Flux YOLO webcam (grand) -->
|
| 247 |
-
<img id="wcYoloImg" alt="YOLO stream">
|
| 248 |
-
<!-- HUD top-left -->
|
| 249 |
-
<div class="hud" id="hudEl">
|
| 250 |
-
<div class="hudf" id="hudFr">—</div>
|
| 251 |
-
<div id="hudCo">—</div>
|
| 252 |
-
</div>
|
| 253 |
-
<!-- Compteurs top-right -->
|
| 254 |
-
<div class="cov" id="cOverlay"></div>
|
| 255 |
-
<!-- Banner no-object -->
|
| 256 |
-
<div class="noobj" id="noObjBanner">
|
| 257 |
-
<i class="fas fa-eye-slash"></i> No object detected
|
| 258 |
-
</div>
|
| 259 |
-
<!-- Canvas caché pour enregistrement webcam -->
|
| 260 |
-
<canvas id="wcCanvas" style="display:none;"></canvas>
|
| 261 |
-
<!-- Empty state -->
|
| 262 |
-
<div class="es" id="emptyState">
|
| 263 |
-
<i class="fas fa-film"></i>
|
| 264 |
-
<p>Result will appear here</p>
|
| 265 |
-
</div>
|
| 266 |
-
</div>
|
| 267 |
-
</div>
|
| 268 |
-
|
| 269 |
-
<!-- Stats -->
|
| 270 |
-
<div class="sr">
|
| 271 |
-
<div class="sc"><div class="sl">Frames</div><div class="sv" id="stF">—</div></div>
|
| 272 |
-
<div class="sc"><div class="sl">Objects</div><div class="sv" id="stO">—</div></div>
|
| 273 |
-
<div class="sc"><div class="sl">FPS</div><div class="sv" id="stFps">—</div></div>
|
| 274 |
-
<div class="sc"><div class="sl">Duration</div><div class="sv" id="stD">—</div></div>
|
| 275 |
-
</div>
|
| 276 |
-
|
| 277 |
-
<!-- Download -->
|
| 278 |
-
<div class="dlb">
|
| 279 |
-
<span id="dlName">No result yet</span>
|
| 280 |
-
<button class="btndl" id="dlBtn" disabled onclick="doDownload()">
|
| 281 |
-
<i class="fas fa-download"></i> Download
|
| 282 |
-
</button>
|
| 283 |
-
</div>
|
| 284 |
-
|
| 285 |
-
</div>
|
| 286 |
-
</main>
|
| 287 |
-
</div>
|
| 288 |
-
|
| 289 |
-
<script>
|
| 290 |
-
/* ── Theme ── */
|
| 291 |
-
function toggleTheme(){
|
| 292 |
-
const d=document.documentElement,dk=d.getAttribute('data-theme')==='dark';
|
| 293 |
-
d.setAttribute('data-theme',dk?'light':'dark');
|
| 294 |
-
['ti','ti2'].forEach(x=>{const e=document.getElementById(x);if(e)e.className=dk?'fas fa-moon':'fas fa-sun';});
|
| 295 |
-
try{localStorage.setItem('tiq_theme',dk?'light':'dark');}catch(e){}
|
| 296 |
-
}
|
| 297 |
-
function setLang(l,btn){
|
| 298 |
-
document.querySelectorAll('.lb').forEach(b=>b.classList.remove('on'));btn.classList.add('on');
|
| 299 |
-
try{localStorage.setItem('tiq_lang',l);}catch(e){}
|
| 300 |
-
}
|
| 301 |
-
|
| 302 |
-
/* ── Classes ── */
|
| 303 |
-
const CLASSES=[
|
| 304 |
-
{name:'Vehicle',color:'#3b82f6'},{name:'Motorcycle',color:'#10b981'},
|
| 305 |
-
{name:'Truck',color:'#f59e0b'},{name:'Bus',color:'#8b5cf6'},
|
| 306 |
-
{name:'Human',color:'#ec4899'},{name:'Bicycle',color:'#06b6d4'},
|
| 307 |
-
{name:'Traffic light',color:'#ef4444'},{name:'Road sign',color:'#f97316'},
|
| 308 |
-
];
|
| 309 |
-
let sel=new Set(['Human']);
|
| 310 |
-
|
| 311 |
-
function buildCls(){
|
| 312 |
-
const g=document.getElementById('clsGrid'); g.innerHTML='';
|
| 313 |
-
CLASSES.forEach(c=>{
|
| 314 |
-
const d=document.createElement('div');
|
| 315 |
-
d.className='clsi'+(sel.has(c.name)?' on':'');
|
| 316 |
-
d.innerHTML=`<span class="cldot" style="background:${c.color}"></span>${c.name}`;
|
| 317 |
-
d.onclick=()=>{sel.has(c.name)?sel.delete(c.name):sel.add(c.name);d.classList.toggle('on');upCls();};
|
| 318 |
-
g.appendChild(d);
|
| 319 |
-
});
|
| 320 |
-
upCls();
|
| 321 |
-
}
|
| 322 |
-
function toggleAll(){sel.size===CLASSES.length?sel.clear():CLASSES.forEach(c=>sel.add(c.name));buildCls();}
|
| 323 |
-
function upCls(){document.getElementById('clsCnt').textContent=sel.size+' selected';}
|
| 324 |
-
buildCls();
|
| 325 |
-
|
| 326 |
-
/* ── State ── */
|
| 327 |
-
let curSrc='upload', vidFile=null, vidUrl=null, wcStream=null;
|
| 328 |
-
let _jobId=null, _sse=null, _wcPollIv=null, _annotatedUrl=null;
|
| 329 |
-
|
| 330 |
-
/* ── Source switch ── */
|
| 331 |
-
function switchSrc(s,btn){
|
| 332 |
-
curSrc=s;
|
| 333 |
-
document.querySelectorAll('.stab').forEach(b=>b.classList.remove('on'));btn.classList.add('on');
|
| 334 |
-
['upload','url','webcam'].forEach(id=>{
|
| 335 |
-
const el=document.getElementById('src-'+id);if(el)el.style.display='none';
|
| 336 |
-
});
|
| 337 |
-
document.getElementById('src-'+s).style.display='block';
|
| 338 |
-
checkReady();
|
| 339 |
-
}
|
| 340 |
-
|
| 341 |
-
/* ── File upload ── */
|
| 342 |
-
function onFile(e){
|
| 343 |
-
const f=e.target.files[0];if(!f)return;
|
| 344 |
-
vidFile=f;
|
| 345 |
-
const fn=document.getElementById('fname');fn.textContent=f.name;fn.style.display='block';
|
| 346 |
-
document.getElementById('uzone').classList.add('has');
|
| 347 |
-
checkReady();
|
| 348 |
-
}
|
| 349 |
-
function loadUrl(){
|
| 350 |
-
const u=document.getElementById('urlIn').value.trim();if(!u)return;
|
| 351 |
-
vidUrl=u;checkReady();
|
| 352 |
-
}
|
| 353 |
-
|
| 354 |
-
/* ── Run Analysis ── */
|
| 355 |
-
window.runAnalysis=async function(){
|
| 356 |
-
const btn=document.getElementById('runBtn');
|
| 357 |
-
btn.disabled=true;
|
| 358 |
-
btn.innerHTML='<i class="fas fa-spinner fa-spin"></i> Processing…';
|
| 359 |
-
document.getElementById('progWrap').style.display='block';
|
| 360 |
-
document.getElementById('progTxt').style.display='block';
|
| 361 |
-
document.getElementById('progTxt').textContent='Uploading…';
|
| 362 |
-
document.getElementById('progBar').style.width='5%';
|
| 363 |
-
document.getElementById('emptyState').style.display='none';
|
| 364 |
-
|
| 365 |
-
/* Upload si pas encore fait */
|
| 366 |
-
if(!_jobId){
|
| 367 |
-
if(!vidFile&&!vidUrl){
|
| 368 |
-
btn.disabled=false;btn.innerHTML='<i class="fas fa-play"></i> Run Analysis';return;
|
| 369 |
-
}
|
| 370 |
-
const fd=new FormData();
|
| 371 |
-
if(vidFile) fd.append('video',vidFile);
|
| 372 |
-
try{
|
| 373 |
-
const r=await fetch('/api/upload',{method:'POST',body:fd});
|
| 374 |
-
const d=await r.json();
|
| 375 |
-
_jobId=d.job_id;
|
| 376 |
-
}catch(err){
|
| 377 |
-
console.error('[TrackIQ] Upload:',err);
|
| 378 |
-
btn.disabled=false;btn.innerHTML='<i class="fas fa-play"></i> Run Analysis';return;
|
| 379 |
-
}
|
| 380 |
-
}
|
| 381 |
-
|
| 382 |
-
document.getElementById('progTxt').textContent='Starting detection…';
|
| 383 |
-
document.getElementById('progBar').style.width='10%';
|
| 384 |
-
|
| 385 |
-
try{
|
| 386 |
-
await fetch('/api/run',{method:'POST',headers:{'Content-Type':'application/json'},
|
| 387 |
-
body:JSON.stringify({job_id:_jobId,classes:[...sel],model:'yolo11n'})});
|
| 388 |
-
}catch(err){console.error('[TrackIQ] Run:',err);return;}
|
| 389 |
-
|
| 390 |
-
/* SSE */
|
| 391 |
-
if(_sse)_sse.close();
|
| 392 |
-
_sse=new EventSource(`/api/stream/${_jobId}`);
|
| 393 |
-
_sse.onmessage=(e)=>{
|
| 394 |
-
let d;try{d=JSON.parse(e.data);}catch{return;}
|
| 395 |
-
|
| 396 |
-
if(d.event==='done'){
|
| 397 |
-
_sse.close();
|
| 398 |
-
_showBanner(false);
|
| 399 |
-
document.getElementById('progBar').style.width='100%';
|
| 400 |
-
document.getElementById('progTxt').textContent='Done!';
|
| 401 |
-
btn.disabled=false;btn.innerHTML='<i class="fas fa-play"></i> Run Analysis';
|
| 402 |
-
|
| 403 |
-
/* ✅ Affiche la vidéo annotée */
|
| 404 |
-
const rv=document.getElementById('resultVid');
|
| 405 |
-
rv.src=`/api/video/${_jobId}`;
|
| 406 |
-
rv.style.display='block';
|
| 407 |
-
rv.load();
|
| 408 |
-
document.getElementById('srcLabel').textContent=vidFile?vidFile.name:'video';
|
| 409 |
-
document.getElementById('hudEl').style.display='block';
|
| 410 |
-
document.getElementById('cOverlay').style.display='flex';
|
| 411 |
-
_annotatedUrl=`/api/video/${_jobId}`;
|
| 412 |
-
|
| 413 |
-
/* Stats */
|
| 414 |
-
const s=d.stats||{};
|
| 415 |
-
_set('stF',s.total_frames??'—');_set('stO',s.total_unique??'—');
|
| 416 |
-
_set('stFps',s.fps?s.fps.toFixed(1):'—');_set('stD',s.duration_s?s.duration_s+'s':'—');
|
| 417 |
-
_showCounts(s.unique_counts||{});
|
| 418 |
-
_set('dlName',vidFile?vidFile.name:'annotated.mp4');
|
| 419 |
-
document.getElementById('dlBtn').removeAttribute('disabled');
|
| 420 |
-
|
| 421 |
-
/* Sauvegarde localStorage */
|
| 422 |
-
try{
|
| 423 |
-
localStorage.setItem('tiq_job_id',_jobId);
|
| 424 |
-
localStorage.setItem('tiq_video_url',`/api/video/${_jobId}`);
|
| 425 |
-
localStorage.setItem('tiq_video_name',s.video_name||'');
|
| 426 |
-
localStorage.setItem('tiq_stats',JSON.stringify(s));
|
| 427 |
-
}catch(ex){}
|
| 428 |
-
return;
|
| 429 |
-
}
|
| 430 |
-
|
| 431 |
-
/* Progression */
|
| 432 |
-
if(d.pct!=null){
|
| 433 |
-
document.getElementById('progBar').style.width=d.pct+'%';
|
| 434 |
-
document.getElementById('progTxt').textContent=`Processing… ${d.pct.toFixed(0)}%`;
|
| 435 |
-
}
|
| 436 |
-
_showBanner(d.no_objects===true);
|
| 437 |
-
if(d.hud){
|
| 438 |
-
_set('hudFr',d.hud.frame_str);
|
| 439 |
-
const hco=document.getElementById('hudCo');
|
| 440 |
-
if(hco) hco.innerHTML=Object.entries(d.hud.counts||{})
|
| 441 |
-
.map(([k,v])=>`${k} = ${v}`).join('<br>')||'No object';
|
| 442 |
-
}
|
| 443 |
-
if(d.unique_counts) _showCounts(d.unique_counts);
|
| 444 |
-
};
|
| 445 |
-
_sse.onerror=()=>_sse.close();
|
| 446 |
-
};
|
| 447 |
-
|
| 448 |
-
/* ── Webcam ── */
|
| 449 |
-
let _wcFrameIv=null, _wcHiddenVideo=null, _wcCanvas2=null;
|
| 450 |
-
|
| 451 |
-
async function startWc(){
|
| 452 |
-
try{
|
| 453 |
-
wcStream=await navigator.mediaDevices.getUserMedia({video:{width:640,height:480},audio:false});
|
| 454 |
-
}catch(e){alert('Webcam unavailable: '+e.message);return;}
|
| 455 |
-
|
| 456 |
-
document.getElementById('wcPh').style.display='none';
|
| 457 |
-
document.getElementById('recBadge').style.display='flex';
|
| 458 |
-
document.getElementById('runBtn').style.display='none';
|
| 459 |
-
|
| 460 |
-
const btn=document.getElementById('wcBtn');
|
| 461 |
-
btn.innerHTML='<i class="fas fa-stop" style="color:#f87171;font-size:9px;"></i> Stop webcam';
|
| 462 |
-
btn.onclick=stopWc;
|
| 463 |
-
|
| 464 |
-
/* Lance YOLO backend */
|
| 465 |
-
await fetch('/api/webcam/start',{method:'POST',headers:{'Content-Type':'application/json'},
|
| 466 |
-
body:JSON.stringify({classes:[...sel],model:'yolo11n'})}).catch(()=>{});
|
| 467 |
-
|
| 468 |
-
/* Setup hidden video + canvas pour capturer frames */
|
| 469 |
-
_wcHiddenVideo=document.createElement('video');
|
| 470 |
-
_wcHiddenVideo.srcObject=wcStream;
|
| 471 |
-
_wcHiddenVideo.setAttribute('playsinline','');
|
| 472 |
-
_wcHiddenVideo.muted=true;
|
| 473 |
-
_wcHiddenVideo.play();
|
| 474 |
-
_wcCanvas2=document.createElement('canvas');
|
| 475 |
-
_wcCanvas2.width=640;_wcCanvas2.height=480;
|
| 476 |
-
const ctx2=_wcCanvas2.getContext('2d');
|
| 477 |
-
|
| 478 |
-
/* Envoie une frame toutes les 200ms → reçoit JPEG annoté → l'affiche */
|
| 479 |
-
const miniImg=document.getElementById('wcMiniYolo');
|
| 480 |
-
const bigImg=document.getElementById('wcYoloImg');
|
| 481 |
-
miniImg.style.display='block';
|
| 482 |
-
bigImg.style.display='block';
|
| 483 |
-
|
| 484 |
-
_wcFrameIv=setInterval(async()=>{
|
| 485 |
-
if(!wcStream||!_wcHiddenVideo)return;
|
| 486 |
-
ctx2.drawImage(_wcHiddenVideo,0,0,640,480);
|
| 487 |
-
_wcCanvas2.toBlob(async(blob)=>{
|
| 488 |
-
if(!blob)return;
|
| 489 |
-
try{
|
| 490 |
-
const resp=await fetch('/api/webcam/frame',{method:'POST',
|
| 491 |
-
headers:{'Content-Type':'image/jpeg'},body:blob});
|
| 492 |
-
if(!resp.ok)return;
|
| 493 |
-
const url=URL.createObjectURL(await resp.blob());
|
| 494 |
-
miniImg.src=url;bigImg.src=url;
|
| 495 |
-
}catch(e){}
|
| 496 |
-
},'image/jpeg',0.75);
|
| 497 |
-
},200);
|
| 498 |
-
|
| 499 |
-
/* Grand panel droite */
|
| 500 |
-
document.getElementById('emptyState').style.display='none';
|
| 501 |
-
document.getElementById('resultVid').style.display='none';
|
| 502 |
-
document.getElementById('hudEl').style.display='block';
|
| 503 |
-
document.getElementById('cOverlay').style.display='flex';
|
| 504 |
-
document.getElementById('srcLabel').textContent='Webcam — LIVE';
|
| 505 |
-
|
| 506 |
-
/* Démarre l'enregistrement automatiquement */
|
| 507 |
-
setTimeout(_startRec, 800);
|
| 508 |
-
/* Polling stats toutes les 800ms */
|
| 509 |
-
clearInterval(_wcPollIv);
|
| 510 |
-
_wcPollIv=setInterval(_pollWc,800);
|
| 511 |
-
checkReady();
|
| 512 |
-
}
|
| 513 |
-
|
| 514 |
-
/* ── Recording webcam → grand Result ── */
|
| 515 |
-
let _recIv=null, _recChunks=[], _mediaRec=null;
|
| 516 |
-
|
| 517 |
-
function _startRec(){
|
| 518 |
-
const canvas=document.getElementById('wcCanvas');
|
| 519 |
-
const img=document.getElementById('wcYoloImg');
|
| 520 |
-
if(!canvas||!img)return;
|
| 521 |
-
canvas.width=640;canvas.height=480;
|
| 522 |
-
const ctx=canvas.getContext('2d');
|
| 523 |
-
_recIv=setInterval(()=>{
|
| 524 |
-
if(img.naturalWidth>0){canvas.width=img.naturalWidth;canvas.height=img.naturalHeight;ctx.drawImage(img,0,0);}
|
| 525 |
-
},40);
|
| 526 |
-
const mime=MediaRecorder.isTypeSupported('video/webm;codecs=vp9')?'video/webm;codecs=vp9':'video/webm';
|
| 527 |
-
_mediaRec=new MediaRecorder(canvas.captureStream(25),{mimeType:mime,videoBitsPerSecond:2000000});
|
| 528 |
-
_recChunks=[];
|
| 529 |
-
_mediaRec.ondataavailable=e=>{if(e.data.size>0)_recChunks.push(e.data);};
|
| 530 |
-
_mediaRec.onstop=_saveRec;
|
| 531 |
-
_mediaRec.start(500);
|
| 532 |
-
}
|
| 533 |
-
|
| 534 |
-
function _saveRec(){
|
| 535 |
-
clearInterval(_recIv);_recIv=null;
|
| 536 |
-
if(!_recChunks.length)return;
|
| 537 |
-
const blob=new Blob(_recChunks,{type:'video/webm'});
|
| 538 |
-
const url=URL.createObjectURL(blob);
|
| 539 |
-
_annotatedUrl=url;
|
| 540 |
-
/* Affiche dans le grand Result */
|
| 541 |
-
const rv=document.getElementById('resultVid');
|
| 542 |
-
rv.src=url;rv.style.display='block';rv.controls=true;
|
| 543 |
-
rv.onloadeddata=()=>rv.play().catch(()=>{});rv.load();
|
| 544 |
-
document.getElementById('srcLabel').textContent='Webcam — recorded';
|
| 545 |
-
document.getElementById('emptyState').style.display='none';
|
| 546 |
-
document.getElementById('hudEl').style.display='block';
|
| 547 |
-
/* Active download */
|
| 548 |
-
_set('dlName','webcam_trackiq.webm');
|
| 549 |
-
document.getElementById('dlBtn').removeAttribute('disabled');
|
| 550 |
-
document.getElementById('dlBtn').onclick=()=>{
|
| 551 |
-
const a=document.createElement('a');a.href=url;a.download='webcam_trackiq.webm';a.click();
|
| 552 |
-
};
|
| 553 |
-
}
|
| 554 |
-
|
| 555 |
-
function stopWc(){
|
| 556 |
-
/* 1. Arrête enregistrement → déclenche _saveRec → vidéo dans Result */
|
| 557 |
-
if(_mediaRec&&_mediaRec.state==='recording') _mediaRec.stop();
|
| 558 |
-
clearInterval(_wcFrameIv);_wcFrameIv=null;
|
| 559 |
-
if(_wcHiddenVideo){_wcHiddenVideo.srcObject=null;_wcHiddenVideo=null;}
|
| 560 |
-
_wcCanvas2=null;
|
| 561 |
-
if(wcStream){wcStream.getTracks().forEach(t=>t.stop());wcStream=null;}
|
| 562 |
-
fetch('/api/webcam/stop',{method:'POST'}).catch(()=>{});
|
| 563 |
-
clearInterval(_wcPollIv);_wcPollIv=null;
|
| 564 |
-
|
| 565 |
-
/* 2. Cache flux live */
|
| 566 |
-
const mi=document.getElementById('wcMiniYolo');mi.src='';mi.style.display='none';
|
| 567 |
-
const bi=document.getElementById('wcYoloImg');bi.src='';bi.style.display='none';
|
| 568 |
-
|
| 569 |
-
document.getElementById('wcPh').style.display='flex';
|
| 570 |
-
document.getElementById('recBadge').style.display='none';
|
| 571 |
-
document.getElementById('runBtn').style.display='flex';
|
| 572 |
-
document.getElementById('cOverlay').style.display='none';
|
| 573 |
-
document.getElementById('noObjBanner').style.display='none';
|
| 574 |
-
|
| 575 |
-
const btn=document.getElementById('wcBtn');
|
| 576 |
-
btn.innerHTML='<i class="fas fa-circle" style="color:#f87171;font-size:8px;"></i> Start webcam';
|
| 577 |
-
btn.onclick=startWc;
|
| 578 |
-
checkReady();
|
| 579 |
-
}
|
| 580 |
-
|
| 581 |
-
async function _pollWc(){
|
| 582 |
-
try{
|
| 583 |
-
const r=await fetch('/api/webcam/stats');
|
| 584 |
-
if(!r.ok)return;
|
| 585 |
-
const d=await r.json();
|
| 586 |
-
const fc=d.frame_counts||{};
|
| 587 |
-
const uc=d.unique_counts||{};
|
| 588 |
-
_set('hudFr',`F:${d.frame||0} ▶ LIVE`);
|
| 589 |
-
const hco=document.getElementById('hudCo');
|
| 590 |
-
if(hco) hco.innerHTML=Object.entries(fc).map(([k,v])=>`${k} = ${v}`).join('<br>')||
|
| 591 |
-
'<span style="color:#ef4444">No object</span>';
|
| 592 |
-
_showCountsWc(fc,uc);
|
| 593 |
-
_showBanner(!Object.keys(fc).length);
|
| 594 |
-
}catch(e){}
|
| 595 |
-
}
|
| 596 |
-
|
| 597 |
-
/* ── Affichage compteurs ── */
|
| 598 |
-
function _showCounts(uc){
|
| 599 |
-
const ov=document.getElementById('cOverlay');
|
| 600 |
-
ov.innerHTML='';
|
| 601 |
-
if(!Object.keys(uc).length){
|
| 602 |
-
_showBanner(true);return;
|
| 603 |
-
}
|
| 604 |
-
Object.entries(uc).forEach(([cls,cnt])=>{
|
| 605 |
-
const cl=CLASSES.find(c=>c.name===cls);
|
| 606 |
-
const color=cl?cl.color:'#a78bfa';
|
| 607 |
-
const div=document.createElement('div');div.className='cbdg';
|
| 608 |
-
div.style.borderLeftColor=color;
|
| 609 |
-
div.innerHTML=`<span class="cn" style="color:${color}">${cls} = ${cnt}</span>`;
|
| 610 |
-
ov.appendChild(div);
|
| 611 |
-
});
|
| 612 |
-
_showBanner(false);
|
| 613 |
-
}
|
| 614 |
-
|
| 615 |
-
function _showCountsWc(fc,uc){
|
| 616 |
-
const ov=document.getElementById('cOverlay');
|
| 617 |
-
ov.innerHTML='';
|
| 618 |
-
const all=new Set([...Object.keys(fc),...Object.keys(uc)]);
|
| 619 |
-
if(!all.size){_showBanner(true);return;}
|
| 620 |
-
all.forEach(cls=>{
|
| 621 |
-
const cl=CLASSES.find(c=>c.name===cls);
|
| 622 |
-
const color=cl?cl.color:'#a78bfa';
|
| 623 |
-
const div=document.createElement('div');div.className='cbdg';
|
| 624 |
-
div.style.borderLeftColor=color;
|
| 625 |
-
div.innerHTML=`<span class="cn" style="color:${color}">${cls} = ${fc[cls]||0}</span>
|
| 626 |
-
<span class="cu">Total unique : ${uc[cls]||0}</span>`;
|
| 627 |
-
ov.appendChild(div);
|
| 628 |
-
});
|
| 629 |
-
_showBanner(false);
|
| 630 |
-
}
|
| 631 |
-
|
| 632 |
-
function _showBanner(show){
|
| 633 |
-
const b=document.getElementById('noObjBanner');
|
| 634 |
-
b.style.display=show?'flex':'none';
|
| 635 |
-
}
|
| 636 |
-
|
| 637 |
-
/* ── Helpers ── */
|
| 638 |
-
function _set(id,val){const el=document.getElementById(id);if(el)el.textContent=val;}
|
| 639 |
-
function checkReady(){
|
| 640 |
-
const ok=(curSrc==='upload'&&vidFile)||(curSrc==='url'&&vidUrl)||(curSrc==='webcam'&&wcStream);
|
| 641 |
-
document.getElementById('runBtn').disabled=!ok;
|
| 642 |
-
}
|
| 643 |
-
function doDownload(){
|
| 644 |
-
const url=_annotatedUrl;if(!url)return;
|
| 645 |
-
const a=document.createElement('a');a.href=url;a.download='trackiq_annotated.mp4';a.click();
|
| 646 |
-
}
|
| 647 |
-
function resetAll(){
|
| 648 |
-
if(wcStream)stopWc();
|
| 649 |
-
if(_sse)_sse.close();
|
| 650 |
-
_jobId=null;_annotatedUrl=null;vidFile=null;vidUrl=null;
|
| 651 |
-
const rv=document.getElementById('resultVid');rv.src='';rv.style.display='none';
|
| 652 |
-
document.getElementById('fname').style.display='none';
|
| 653 |
-
document.getElementById('fname').textContent='';
|
| 654 |
-
document.getElementById('uzone').classList.remove('has');
|
| 655 |
-
document.getElementById('urlIn').value='';
|
| 656 |
-
document.getElementById('progWrap').style.display='none';
|
| 657 |
-
document.getElementById('progBar').style.width='0%';
|
| 658 |
-
document.getElementById('progTxt').style.display='none';
|
| 659 |
-
document.getElementById('emptyState').style.display='flex';
|
| 660 |
-
document.getElementById('cOverlay').style.display='none';
|
| 661 |
-
document.getElementById('hudEl').style.display='none';
|
| 662 |
-
document.getElementById('noObjBanner').style.display='none';
|
| 663 |
-
document.getElementById('dlBtn').disabled=true;
|
| 664 |
-
document.getElementById('dlName').textContent='No result yet';
|
| 665 |
-
document.getElementById('srcLabel').textContent='';
|
| 666 |
-
['stF','stO','stFps','stD'].forEach(id=>_set(id,'—'));
|
| 667 |
-
const fi=document.getElementById('fileInput');if(fi)fi.value='';
|
| 668 |
-
checkReady();
|
| 669 |
-
}
|
| 670 |
-
|
| 671 |
-
/* ── Init ── */
|
| 672 |
-
(function(){
|
| 673 |
-
try{
|
| 674 |
-
const th=localStorage.getItem('tiq_theme');
|
| 675 |
-
if(th){document.documentElement.setAttribute('data-theme',th);
|
| 676 |
-
if(th==='dark'){['ti','ti2'].forEach(x=>{const e=document.getElementById(x);if(e)e.className='fas fa-sun';});}}
|
| 677 |
-
const lg=localStorage.getItem('tiq_lang');
|
| 678 |
-
if(lg&&lg!=='en'){const btn=document.querySelector('.lb[onclick*="\''+lg+'\'"]');if(btn)setLang(lg,btn);}
|
| 679 |
-
}catch(e){}
|
| 680 |
-
})();
|
| 681 |
-
</script>
|
| 682 |
-
<script src="/static/trackiq-api.js"></script>
|
| 683 |
-
</body>
|
| 684 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/templates/index.html
DELETED
|
@@ -1,486 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" data-theme="light">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>TrackIQ — Urban Traffic Detection</title>
|
| 7 |
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300&family=DM+Mono:wght@400&display=swap" rel="stylesheet">
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 9 |
-
<style>
|
| 10 |
-
:root {
|
| 11 |
-
--ac: #880000;
|
| 12 |
-
--ac2: #660000;
|
| 13 |
-
--bg: #f9fafb;
|
| 14 |
-
--s: #fff;
|
| 15 |
-
--bd: rgba(0,0,0,0.07);
|
| 16 |
-
--t: #111827;
|
| 17 |
-
--t2: #6b7280;
|
| 18 |
-
--t3: #d1d5db;
|
| 19 |
-
--sh: 0 4px 24px rgba(0,0,0,0.09);
|
| 20 |
-
--shlg: 0 20px 25px -5px rgba(0,0,0,0.10), 0 10px 10px -5px rgba(0,0,0,0.04);
|
| 21 |
-
--r: 16px;
|
| 22 |
-
}
|
| 23 |
-
[data-theme="dark"] {
|
| 24 |
-
--bg: #0c1117;
|
| 25 |
-
--s: #151c25;
|
| 26 |
-
--t: #e8edf2;
|
| 27 |
-
--t2: #7a8fa0;
|
| 28 |
-
--t3: #3a4a5a;
|
| 29 |
-
--bd: rgba(255,255,255,0.07);
|
| 30 |
-
--sh: 0 4px 24px rgba(0,0,0,0.5);
|
| 31 |
-
--shlg: 0 20px 40px rgba(0,0,0,0.5);
|
| 32 |
-
}
|
| 33 |
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
| 34 |
-
html, body { min-height: 100%; }
|
| 35 |
-
body {
|
| 36 |
-
font-family: "DM Sans", sans-serif;
|
| 37 |
-
background: var(--bg);
|
| 38 |
-
color: var(--t);
|
| 39 |
-
transition: background 0.3s, color 0.3s;
|
| 40 |
-
}
|
| 41 |
-
::-webkit-scrollbar { width: 6px; }
|
| 42 |
-
::-webkit-scrollbar-track { background: transparent; }
|
| 43 |
-
::-webkit-scrollbar-thumb { background: var(--t3); border-radius: 3px; }
|
| 44 |
-
|
| 45 |
-
/* ── FLOATING NAV ── */
|
| 46 |
-
.floatnav {
|
| 47 |
-
position: absolute;
|
| 48 |
-
top: 0; left: 0; right: 0;
|
| 49 |
-
z-index: 50;
|
| 50 |
-
pointer-events: none;
|
| 51 |
-
}
|
| 52 |
-
.floatnav-inner {
|
| 53 |
-
max-width: 1280px;
|
| 54 |
-
margin: 0 auto;
|
| 55 |
-
padding: 16px 24px;
|
| 56 |
-
display: flex;
|
| 57 |
-
justify-content: space-between;
|
| 58 |
-
align-items: flex-start;
|
| 59 |
-
}
|
| 60 |
-
.nav-brand {
|
| 61 |
-
pointer-events: auto;
|
| 62 |
-
background: rgba(255,255,255,0.92);
|
| 63 |
-
backdrop-filter: blur(12px);
|
| 64 |
-
border-radius: 10px;
|
| 65 |
-
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
| 66 |
-
padding: 8px 16px;
|
| 67 |
-
display: flex;
|
| 68 |
-
align-items: center;
|
| 69 |
-
gap: 8px;
|
| 70 |
-
text-decoration: none;
|
| 71 |
-
transition: all 0.2s;
|
| 72 |
-
}
|
| 73 |
-
.nav-brand:hover { background: #fff; box-shadow: 0 8px 24px rgba(0,0,0,0.16); }
|
| 74 |
-
.nav-brand-icon {
|
| 75 |
-
width: 28px; height: 28px;
|
| 76 |
-
background: var(--ac);
|
| 77 |
-
border-radius: 7px;
|
| 78 |
-
display: flex; align-items: center; justify-content: center;
|
| 79 |
-
color: #fff; font-size: 12px;
|
| 80 |
-
}
|
| 81 |
-
.nav-brand-name {
|
| 82 |
-
font-size: 14px; font-weight: 600;
|
| 83 |
-
color: #111827; letter-spacing: -0.3px;
|
| 84 |
-
}
|
| 85 |
-
.nav-brand-name b { color: var(--ac); }
|
| 86 |
-
.nav-right {
|
| 87 |
-
pointer-events: auto;
|
| 88 |
-
display: flex; gap: 8px; align-items: center;
|
| 89 |
-
}
|
| 90 |
-
.nav-lang {
|
| 91 |
-
background: #fff;
|
| 92 |
-
border-radius: 10px;
|
| 93 |
-
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
| 94 |
-
padding: 4px;
|
| 95 |
-
display: flex; gap: 4px;
|
| 96 |
-
}
|
| 97 |
-
.lb {
|
| 98 |
-
font-size: 12px; font-weight: 500;
|
| 99 |
-
padding: 4px 10px;
|
| 100 |
-
border: none; background: none;
|
| 101 |
-
cursor: pointer; border-radius: 6px;
|
| 102 |
-
color: #6b7280;
|
| 103 |
-
font-family: "DM Sans", sans-serif;
|
| 104 |
-
transition: all 0.2s;
|
| 105 |
-
}
|
| 106 |
-
.lb.on { background: var(--ac); color: #fff; }
|
| 107 |
-
.lb:not(.on):hover { background: #f3f4f6; color: #111827; }
|
| 108 |
-
.nav-theme {
|
| 109 |
-
background: #fff;
|
| 110 |
-
border-radius: 10px;
|
| 111 |
-
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
|
| 112 |
-
width: 38px; height: 38px;
|
| 113 |
-
border: none; cursor: pointer;
|
| 114 |
-
display: flex; align-items: center; justify-content: center;
|
| 115 |
-
color: #6b7280; font-size: 13px;
|
| 116 |
-
transition: all 0.2s;
|
| 117 |
-
}
|
| 118 |
-
.nav-theme:hover { color: var(--ac); }
|
| 119 |
-
|
| 120 |
-
/* ── HERO ── */
|
| 121 |
-
.hero {
|
| 122 |
-
background-color: var(--ac);
|
| 123 |
-
background-image: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23991111' fill-opacity='0.4'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
|
| 124 |
-
color: #fff;
|
| 125 |
-
padding-top: 90px;
|
| 126 |
-
padding-bottom: 90px;
|
| 127 |
-
padding-left: 24px;
|
| 128 |
-
padding-right: 24px;
|
| 129 |
-
text-align: center;
|
| 130 |
-
position: relative;
|
| 131 |
-
overflow: hidden;
|
| 132 |
-
}
|
| 133 |
-
.hero-inner {
|
| 134 |
-
max-width: 860px;
|
| 135 |
-
margin: 0 auto;
|
| 136 |
-
position: relative;
|
| 137 |
-
z-index: 10;
|
| 138 |
-
animation: fadeUp 0.6s ease both;
|
| 139 |
-
}
|
| 140 |
-
@keyframes fadeUp {
|
| 141 |
-
from { opacity: 0; transform: translateY(24px); }
|
| 142 |
-
to { opacity: 1; transform: translateY(0); }
|
| 143 |
-
}
|
| 144 |
-
.hero-logo-wrap {
|
| 145 |
-
display: flex;
|
| 146 |
-
justify-content: center;
|
| 147 |
-
margin-bottom: 16px;
|
| 148 |
-
}
|
| 149 |
-
.hero-logo-box {
|
| 150 |
-
background: #fff;
|
| 151 |
-
padding: 14px;
|
| 152 |
-
border-radius: 20px;
|
| 153 |
-
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
| 154 |
-
display: flex; align-items: center; justify-content: center;
|
| 155 |
-
width: 80px; height: 80px;
|
| 156 |
-
}
|
| 157 |
-
.hero-logo-box i {
|
| 158 |
-
font-size: 36px;
|
| 159 |
-
color: var(--ac);
|
| 160 |
-
}
|
| 161 |
-
.hero h1 {
|
| 162 |
-
font-size: clamp(32px, 5vw, 60px);
|
| 163 |
-
font-weight: 800;
|
| 164 |
-
letter-spacing: -2px;
|
| 165 |
-
line-height: 1.1;
|
| 166 |
-
margin-bottom: 14px;
|
| 167 |
-
}
|
| 168 |
-
.hero h1 .dim { opacity: 0.75; font-weight: 300; font-style: italic; }
|
| 169 |
-
.hero-sub {
|
| 170 |
-
font-size: clamp(14px, 1.8vw, 18px);
|
| 171 |
-
color: rgba(255,255,255,0.88);
|
| 172 |
-
font-weight: 400;
|
| 173 |
-
line-height: 1.65;
|
| 174 |
-
margin-bottom: 24px;
|
| 175 |
-
max-width: 560px;
|
| 176 |
-
margin-left: auto;
|
| 177 |
-
margin-right: auto;
|
| 178 |
-
}
|
| 179 |
-
.hero-cta {
|
| 180 |
-
display: inline-flex; align-items: center; gap: 10px;
|
| 181 |
-
background: #fff;
|
| 182 |
-
color: var(--ac);
|
| 183 |
-
font-weight: 700; font-size: 16px;
|
| 184 |
-
padding: 14px 32px;
|
| 185 |
-
border-radius: 50px;
|
| 186 |
-
text-decoration: none;
|
| 187 |
-
box-shadow: 0 8px 24px rgba(0,0,0,0.18);
|
| 188 |
-
transition: all 0.3s;
|
| 189 |
-
}
|
| 190 |
-
.hero-cta:hover {
|
| 191 |
-
background: #f3f4f6;
|
| 192 |
-
transform: scale(1.05);
|
| 193 |
-
box-shadow: 0 16px 40px rgba(0,0,0,0.22);
|
| 194 |
-
}
|
| 195 |
-
/* Wave bottom */
|
| 196 |
-
.hero-wave {
|
| 197 |
-
position: absolute;
|
| 198 |
-
bottom: 0; left: 0; right: 0;
|
| 199 |
-
pointer-events: none;
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
/* ── CARDS GRID ── */
|
| 203 |
-
.cards-section {
|
| 204 |
-
max-width: 1280px;
|
| 205 |
-
margin: 0 auto;
|
| 206 |
-
padding: 0 24px 64px;
|
| 207 |
-
margin-top: -64px;
|
| 208 |
-
position: relative;
|
| 209 |
-
z-index: 20;
|
| 210 |
-
}
|
| 211 |
-
.cards-grid {
|
| 212 |
-
display: grid;
|
| 213 |
-
grid-template-columns: repeat(1, 1fr);
|
| 214 |
-
gap: 24px;
|
| 215 |
-
}
|
| 216 |
-
@media (min-width: 640px) { .cards-grid { grid-template-columns: repeat(2, 1fr); } }
|
| 217 |
-
@media (min-width: 1024px) { .cards-grid { grid-template-columns: repeat(4, 1fr); } }
|
| 218 |
-
|
| 219 |
-
.card {
|
| 220 |
-
background: var(--s);
|
| 221 |
-
border-radius: 20px;
|
| 222 |
-
padding: 32px 24px;
|
| 223 |
-
box-shadow: var(--shlg);
|
| 224 |
-
border: 1px solid rgba(0,0,0,0.05);
|
| 225 |
-
text-decoration: none;
|
| 226 |
-
display: block;
|
| 227 |
-
transition: all 0.3s ease;
|
| 228 |
-
animation: fadeUp 0.6s ease both;
|
| 229 |
-
}
|
| 230 |
-
.card:nth-child(1) { animation-delay: 0.05s; }
|
| 231 |
-
.card:nth-child(2) { animation-delay: 0.12s; }
|
| 232 |
-
.card:nth-child(3) { animation-delay: 0.18s; }
|
| 233 |
-
.card:nth-child(4) { animation-delay: 0.24s; }
|
| 234 |
-
.card:hover {
|
| 235 |
-
transform: translateY(-8px);
|
| 236 |
-
box-shadow: 0 20px 25px -5px rgba(0,0,0,0.13), 0 10px 10px -5px rgba(0,0,0,0.05);
|
| 237 |
-
border-color: var(--ac);
|
| 238 |
-
}
|
| 239 |
-
.card-icon-wrap {
|
| 240 |
-
width: 80px; height: 80px;
|
| 241 |
-
background: #fff1f1;
|
| 242 |
-
color: var(--ac);
|
| 243 |
-
border-radius: 50%;
|
| 244 |
-
display: flex; align-items: center; justify-content: center;
|
| 245 |
-
font-size: 28px;
|
| 246 |
-
margin: 0 auto 24px;
|
| 247 |
-
transition: all 0.3s;
|
| 248 |
-
box-shadow: 0 2px 8px rgba(136,0,0,0.08);
|
| 249 |
-
}
|
| 250 |
-
[data-theme="dark"] .card-icon-wrap {
|
| 251 |
-
background: rgba(136,0,0,0.18);
|
| 252 |
-
}
|
| 253 |
-
.card:hover .card-icon-wrap {
|
| 254 |
-
background: var(--ac);
|
| 255 |
-
color: #fff;
|
| 256 |
-
}
|
| 257 |
-
.card-title {
|
| 258 |
-
text-align: center;
|
| 259 |
-
font-size: 18px; font-weight: 700;
|
| 260 |
-
color: var(--t);
|
| 261 |
-
margin-bottom: 10px;
|
| 262 |
-
transition: color 0.2s;
|
| 263 |
-
}
|
| 264 |
-
.card:hover .card-title { color: var(--ac); }
|
| 265 |
-
.card-desc {
|
| 266 |
-
text-align: center;
|
| 267 |
-
font-size: 13px;
|
| 268 |
-
color: var(--t2);
|
| 269 |
-
line-height: 1.65;
|
| 270 |
-
}
|
| 271 |
-
.card-launch {
|
| 272 |
-
margin-top: 24px;
|
| 273 |
-
display: flex; justify-content: center;
|
| 274 |
-
}
|
| 275 |
-
.card-launch span {
|
| 276 |
-
font-size: 13px; font-weight: 600;
|
| 277 |
-
color: var(--ac);
|
| 278 |
-
display: flex; align-items: center; gap: 6px;
|
| 279 |
-
opacity: 0;
|
| 280 |
-
transition: opacity 0.2s;
|
| 281 |
-
}
|
| 282 |
-
.card:hover .card-launch span { opacity: 1; }
|
| 283 |
-
|
| 284 |
-
/* ── FOOTER ── */
|
| 285 |
-
footer {
|
| 286 |
-
background: var(--s);
|
| 287 |
-
border-top: 1px solid var(--bd);
|
| 288 |
-
padding: 28px 24px;
|
| 289 |
-
text-align: center;
|
| 290 |
-
transition: background 0.3s;
|
| 291 |
-
}
|
| 292 |
-
footer p {
|
| 293 |
-
font-size: 13px;
|
| 294 |
-
color: var(--t2);
|
| 295 |
-
line-height: 1.7;
|
| 296 |
-
}
|
| 297 |
-
footer p span {
|
| 298 |
-
font-size: 11px;
|
| 299 |
-
opacity: 0.6;
|
| 300 |
-
display: block;
|
| 301 |
-
margin-top: 4px;
|
| 302 |
-
}
|
| 303 |
-
</style>
|
| 304 |
-
</head>
|
| 305 |
-
<body>
|
| 306 |
-
|
| 307 |
-
<!-- FLOATING NAV -->
|
| 308 |
-
<div class="floatnav">
|
| 309 |
-
<div class="floatnav-inner">
|
| 310 |
-
<a href="#" class="nav-brand">
|
| 311 |
-
<div class="nav-brand-icon"><i class="fas fa-satellite-dish"></i></div>
|
| 312 |
-
<span class="nav-brand-name">Track<b>IQ</b></span>
|
| 313 |
-
</a>
|
| 314 |
-
<div class="nav-right">
|
| 315 |
-
<div class="nav-lang">
|
| 316 |
-
<button class="lb on" onclick="setLang('en',this)">EN</button>
|
| 317 |
-
<button class="lb" onclick="setLang('fr',this)">FR</button>
|
| 318 |
-
<button class="lb" onclick="setLang('wo',this)">WO</button>
|
| 319 |
-
</div>
|
| 320 |
-
<button class="nav-theme" onclick="toggleTheme()"><i class="fas fa-moon" id="ti"></i></button>
|
| 321 |
-
</div>
|
| 322 |
-
</div>
|
| 323 |
-
</div>
|
| 324 |
-
|
| 325 |
-
<!-- HERO -->
|
| 326 |
-
<div class="hero">
|
| 327 |
-
<div class="hero-inner">
|
| 328 |
-
<div class="hero-logo-wrap">
|
| 329 |
-
<div class="hero-logo-box">
|
| 330 |
-
<i class="fas fa-satellite-dish"></i>
|
| 331 |
-
</div>
|
| 332 |
-
</div>
|
| 333 |
-
<h1 id="t-h1">
|
| 334 |
-
TrackIQ Urban Traffic<br>
|
| 335 |
-
<span class="dim">Surveillance & Detection</span>
|
| 336 |
-
</h1>
|
| 337 |
-
<p class="hero-sub" id="t-sub">
|
| 338 |
-
Based on Computer Vision — real-time vehicle detection and classification, speed estimation, congestion analysis and traffic sign identification.
|
| 339 |
-
</p>
|
| 340 |
-
<a href="/home" class="hero-cta" id="t-launch">
|
| 341 |
-
<i class="fas fa-play"></i>
|
| 342 |
-
<span>Launch</span>
|
| 343 |
-
</a>
|
| 344 |
-
</div>
|
| 345 |
-
<div class="hero-wave">
|
| 346 |
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 120" style="display:block;width:100%;">
|
| 347 |
-
<path fill="var(--bg)" fill-opacity="1"
|
| 348 |
-
d="M0,64L80,69.3C160,75,320,85,480,80C640,75,800,53,960,48C1120,43,1280,53,1360,58.7L1440,64L1440,120L1360,120C1280,120,1120,120,960,120C800,120,640,120,480,120C320,120,160,120,80,120L0,120Z"/>
|
| 349 |
-
</svg>
|
| 350 |
-
</div>
|
| 351 |
-
</div>
|
| 352 |
-
|
| 353 |
-
<!-- CARDS -->
|
| 354 |
-
<div class="cards-section">
|
| 355 |
-
<div class="cards-grid">
|
| 356 |
-
|
| 357 |
-
<!-- Card 1 -->
|
| 358 |
-
<a href="/home" class="card">
|
| 359 |
-
<div class="card-icon-wrap"><i class="fas fa-car-side"></i></div>
|
| 360 |
-
<div class="card-title" id="t-c1t">Vehicle Detection</div>
|
| 361 |
-
<p class="card-desc" id="t-c1d">Real-time detection and classification of vehicles: cars, trucks, motorcycles, buses and more.</p>
|
| 362 |
-
<div class="card-launch"><span id="t-launch2">Launch <i class="fas fa-arrow-right"></i></span></div>
|
| 363 |
-
</a>
|
| 364 |
-
|
| 365 |
-
<!-- Card 2 -->
|
| 366 |
-
<a href="/home" class="card">
|
| 367 |
-
<div class="card-icon-wrap"><i class="fas fa-tachometer-alt"></i></div>
|
| 368 |
-
<div class="card-title" id="t-c2t">Speed Estimation</div>
|
| 369 |
-
<p class="card-desc" id="t-c2d">Automatic estimation of vehicle speed from video stream without additional sensors.</p>
|
| 370 |
-
<div class="card-launch"><span id="t-launch3">Launch <i class="fas fa-arrow-right"></i></span></div>
|
| 371 |
-
</a>
|
| 372 |
-
|
| 373 |
-
<!-- Card 3 -->
|
| 374 |
-
<a href="/home" class="card">
|
| 375 |
-
<div class="card-icon-wrap"><i class="fas fa-traffic-light"></i></div>
|
| 376 |
-
<div class="card-title" id="t-c3t">Signs & Lights</div>
|
| 377 |
-
<p class="card-desc" id="t-c3d">Identification of traffic signs and lights, red light detection, stop signs and more.</p>
|
| 378 |
-
<div class="card-launch"><span id="t-launch4">Launch <i class="fas fa-arrow-right"></i></span></div>
|
| 379 |
-
</a>
|
| 380 |
-
|
| 381 |
-
<!-- Card 4 -->
|
| 382 |
-
<a href="/home" class="card">
|
| 383 |
-
<div class="card-icon-wrap"><i class="fas fa-chart-bar"></i></div>
|
| 384 |
-
<div class="card-title" id="t-c4t">Flow & Congestion</div>
|
| 385 |
-
<p class="card-desc" id="t-c4d">Congestion analysis, flow measurement and annotated video export for reporting.</p>
|
| 386 |
-
<div class="card-launch"><span id="t-launch5">Launch <i class="fas fa-arrow-right"></i></span></div>
|
| 387 |
-
</a>
|
| 388 |
-
|
| 389 |
-
</div>
|
| 390 |
-
</div>
|
| 391 |
-
|
| 392 |
-
<!-- FOOTER -->
|
| 393 |
-
<footer>
|
| 394 |
-
<p>
|
| 395 |
-
© 2026 <strong>TrackIQ</strong> — Computer Vision Project. All rights reserved.
|
| 396 |
-
<span id="t-foot">Academic Project — Version 1.0</span>
|
| 397 |
-
</p>
|
| 398 |
-
</footer>
|
| 399 |
-
|
| 400 |
-
<script>
|
| 401 |
-
const T = {
|
| 402 |
-
en: {
|
| 403 |
-
h1: 'TrackIQ Urban Traffic<br><span class="dim">Surveillance & Detection</span>',
|
| 404 |
-
sub: "Based on Computer Vision — real-time vehicle detection and classification, speed estimation, congestion analysis and traffic sign identification.",
|
| 405 |
-
launch: '<i class="fas fa-play"></i><span>Launch</span>',
|
| 406 |
-
c1t: "Vehicle Detection", c1d: "Real-time detection and classification of vehicles: cars, trucks, motorcycles, buses and more.",
|
| 407 |
-
c2t: "Speed Estimation", c2d: "Automatic estimation of vehicle speed from video stream without additional sensors.",
|
| 408 |
-
c3t: "Signs & Lights", c3d: "Identification of traffic signs and lights, red light detection, stop signs and more.",
|
| 409 |
-
c4t: "Flow & Congestion", c4d: "Congestion analysis, flow measurement and annotated video export for reporting.",
|
| 410 |
-
launchbtn: 'Launch <i class="fas fa-arrow-right"></i>',
|
| 411 |
-
foot: "Academic Project — Version 1.0"
|
| 412 |
-
},
|
| 413 |
-
fr: {
|
| 414 |
-
h1: 'TrackIQ Trafic Urbain<br><span class="dim">Surveillance & Détection</span>',
|
| 415 |
-
sub: "Basé sur Computer Vision — détection et classification en temps réel des véhicules, estimation de vitesse, analyse de congestion et identification des feux et panneaux.",
|
| 416 |
-
launch: '<i class="fas fa-play"></i><span>Lancer</span>',
|
| 417 |
-
c1t: "Détection Véhicules", c1d: "Détection et classification en temps réel des véhicules : voitures, camions, motos, bus et plus.",
|
| 418 |
-
c2t: "Estimation Vitesse", c2d: "Estimation automatique de la vitesse des véhicules depuis un flux vidéo, sans capteur additionnel.",
|
| 419 |
-
c3t: "Feux & Panneaux", c3d: "Identification des panneaux et feux de signalisation, détection des feux rouges, stops et plus.",
|
| 420 |
-
c4t: "Flux & Congestion",c4d: "Analyse de congestion, mesure de flux et export vidéo annoté pour rapport.",
|
| 421 |
-
launchbtn: 'Lancer <i class="fas fa-arrow-right"></i>',
|
| 422 |
-
foot: "Projet Académique — Version 1.0"
|
| 423 |
-
},
|
| 424 |
-
wo: {
|
| 425 |
-
h1: 'TrackIQ Trafik Kanam<br><span class="dim">Xam ak Réy</span>',
|
| 426 |
-
sub: "Moom Computer Vision la tax — léegi-léegi di réy wërsëg yi, ak xam njariñ yoom yi, ci kanam.",
|
| 427 |
-
launch: '<i class="fas fa-play"></i><span>Tambali</span>',
|
| 428 |
-
c1t: "Réy Wërsëg", c1d: "Réy ak xam njariñ wërsëg yi ci kanam: yëgël, camion, moto, bus ak yeneen.",
|
| 429 |
-
c2t: "Yóbbu Yoom", c2d: "Xam yóbbu yoom wërsëg yi ci video, benn jëriñ mujj dafa soxor.",
|
| 430 |
-
c3t: "Siñali", c3d: "Xam siñali yi ak dëkkandiku, réy feux rouge, stop ak yeneen.",
|
| 431 |
-
c4t: "Rafet & Xonq", c4d: "Xam xonq, jëf rafet ci kanam ak dëkkandiku video bu annotéwoon.",
|
| 432 |
-
launchbtn: 'Tambali <i class="fas fa-arrow-right"></i>',
|
| 433 |
-
foot: "Liggéey Académique — Version 1.0"
|
| 434 |
-
}
|
| 435 |
-
};
|
| 436 |
-
|
| 437 |
-
function setLang(l, btn) {
|
| 438 |
-
document.querySelectorAll(".lb").forEach(b => b.classList.remove("on"));
|
| 439 |
-
btn.classList.add("on");
|
| 440 |
-
const t = T[l];
|
| 441 |
-
if (!t) return;
|
| 442 |
-
document.getElementById("t-h1").innerHTML = t.h1;
|
| 443 |
-
document.getElementById("t-sub").textContent = t.sub;
|
| 444 |
-
document.getElementById("t-launch").innerHTML = t.launch;
|
| 445 |
-
["c1t","c2t","c3t","c4t"].forEach(k => {
|
| 446 |
-
const el = document.getElementById("t-"+k);
|
| 447 |
-
if (el) el.innerHTML = t[k];
|
| 448 |
-
});
|
| 449 |
-
["c1d","c2d","c3d","c4d"].forEach(k => {
|
| 450 |
-
const el = document.getElementById("t-"+k);
|
| 451 |
-
if (el) el.textContent = t[k];
|
| 452 |
-
});
|
| 453 |
-
["t-launch2","t-launch3","t-launch4","t-launch5"].forEach(id => {
|
| 454 |
-
const el = document.getElementById(id);
|
| 455 |
-
if (el) el.innerHTML = t.launchbtn;
|
| 456 |
-
});
|
| 457 |
-
document.getElementById("t-foot").textContent = t.foot;
|
| 458 |
-
try { localStorage.setItem("tiq_lang", l); } catch(e) {}
|
| 459 |
-
}
|
| 460 |
-
|
| 461 |
-
function toggleTheme() {
|
| 462 |
-
const d = document.documentElement;
|
| 463 |
-
const dk = d.getAttribute("data-theme") === "dark";
|
| 464 |
-
d.setAttribute("data-theme", dk ? "light" : "dark");
|
| 465 |
-
document.getElementById("ti").className = dk ? "fas fa-moon" : "fas fa-sun";
|
| 466 |
-
try { localStorage.setItem("tiq_theme", dk ? "light" : "dark"); } catch(e) {}
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
(function() {
|
| 470 |
-
try {
|
| 471 |
-
const th = localStorage.getItem("tiq_theme");
|
| 472 |
-
if (th) {
|
| 473 |
-
document.documentElement.setAttribute("data-theme", th);
|
| 474 |
-
if (th === "dark") document.getElementById("ti").className = "fas fa-sun";
|
| 475 |
-
}
|
| 476 |
-
const lg = localStorage.getItem("tiq_lang");
|
| 477 |
-
if (lg && lg !== "en") {
|
| 478 |
-
const btn = document.querySelector('.lb[onclick*="\'' + lg + '\'"]');
|
| 479 |
-
if (btn) setLang(lg, btn);
|
| 480 |
-
}
|
| 481 |
-
} catch(e) {}
|
| 482 |
-
})();
|
| 483 |
-
</script>
|
| 484 |
-
<script src="/static/trackiq-api.js"></script>
|
| 485 |
-
</body>
|
| 486 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TRAFFIC_ROAD_APP/templates/logs.html
DELETED
|
@@ -1,415 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="en" data-theme="light">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>TrackIQ Logs</title>
|
| 7 |
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
| 8 |
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
| 9 |
-
<style>
|
| 10 |
-
:root{--bg:#f0f4f8;--surface:#fff;--surface2:#f6f8fb;--border:rgba(0,0,0,0.07);--shadow:0 2px 16px rgba(0,0,0,0.09);--shadow-lg:0 8px 36px rgba(0,0,0,0.13);--text:#0f1923;--text2:#5a6a7a;--text3:#9aaab8;--accent:#880000;--accent2:#660000;--accent-light:rgba(136,0,0,0.07);--sidebar-w:60px;--radius:14px;--radius-sm:9px;}
|
| 11 |
-
[data-theme="dark"]{--bg:#0c1117;--surface:#151c25;--surface2:#1c2531;--border:rgba(255,255,255,0.07);--shadow:0 2px 16px rgba(0,0,0,0.45);--shadow-lg:0 8px 36px rgba(0,0,0,0.55);--text:#e8edf2;--text2:#7a8fa0;--text3:#3a4a5a;}
|
| 12 |
-
*{box-sizing:border-box;margin:0;padding:0}::-webkit-scrollbar{width:0;height:0}
|
| 13 |
-
body{font-family:'DM Sans',sans-serif;background:var(--bg);color:var(--text);font-size:13px;display:flex;min-height:100vh}
|
| 14 |
-
.sidebar{width:var(--sidebar-w);background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;align-items:center;padding:16px 0;gap:4px;position:fixed;top:0;left:0;bottom:0;z-index:200}
|
| 15 |
-
.sb-logo{width:34px;height:34px;background:var(--accent);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;margin-bottom:10px;text-decoration:none}
|
| 16 |
-
.sb-sep{width:28px;height:1px;background:var(--border);margin:4px 0}
|
| 17 |
-
.sb-btn{width:38px;height:38px;border-radius:9px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:15px;cursor:pointer;transition:all .2s;text-decoration:none;border:none;background:none;position:relative}
|
| 18 |
-
.sb-btn:hover,.sb-btn.active{background:var(--accent-light);color:var(--accent)}
|
| 19 |
-
.sb-btn .tip{position:absolute;left:46px;top:50%;transform:translateY(-50%);background:var(--text);color:var(--surface);font-size:11px;padding:3px 8px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none}.sb-btn:hover .tip{opacity:1}
|
| 20 |
-
.sb-spacer{flex:1}.page-wrap{margin-left:var(--sidebar-w);flex:1;display:flex;flex-direction:column}
|
| 21 |
-
.topbar{height:52px;background:var(--surface);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 22px}
|
| 22 |
-
.topbar-l,.topbar-r{display:flex;align-items:center;gap:10px}
|
| 23 |
-
.brand-name{font-size:15px;font-weight:600}.brand-name b{color:var(--accent)}.sep{color:var(--border);font-size:18px;margin:0 2px}
|
| 24 |
-
.lang-group{display:flex;background:var(--surface2);border-radius:6px;padding:2px;border:1px solid var(--border)}
|
| 25 |
-
.lang-btn{font-size:11px;font-weight:500;padding:3px 8px;border:none;background:none;cursor:pointer;border-radius:4px;color:var(--text2);font-family:'DM Sans',sans-serif}.lang-btn.active{background:var(--accent);color:white}
|
| 26 |
-
.icon-btn{width:30px;height:30px;border-radius:6px;border:1px solid var(--border);background:var(--surface);cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--text2);font-size:12px}
|
| 27 |
-
main{flex:1;padding:16px 18px;max-width:1440px;width:100%;margin:0 auto;display:flex;flex-direction:column;gap:12px}
|
| 28 |
-
.sum-row{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
|
| 29 |
-
.sc,.card,.tcard{background:var(--surface);border-radius:var(--radius);border:1px solid var(--border);box-shadow:var(--shadow)}
|
| 30 |
-
.sc{padding:13px 15px}.sc-l{font-size:11px;color:var(--text2);margin-bottom:5px}.sc-v{font-size:22px;font-weight:300}.sc-s{font-size:10px;color:var(--text3);margin-top:3px}
|
| 31 |
-
.card{padding:14px 16px}.filter-row{display:flex;align-items:flex-end;gap:9px;flex-wrap:wrap}.fg{display:flex;flex-direction:column;gap:4px}.fg label{font-size:11px;color:var(--text2)}
|
| 32 |
-
.fin{padding:8px 10px;border:1px solid var(--border);border-radius:7px;font-size:12px;background:var(--surface2);color:var(--text);font-family:'DM Sans',sans-serif;outline:none;min-width:160px}.fin:focus{border-color:var(--accent)}
|
| 33 |
-
.f-sp{flex:1}
|
| 34 |
-
.btn{display:flex;align-items:center;gap:5px;border:none;border-radius:7px;padding:8px 13px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif}
|
| 35 |
-
.btn-exp{background:var(--accent);color:white}.btn-exp:hover{background:var(--accent2)}
|
| 36 |
-
.save-opt{display:flex;align-items:center;gap:10px;width:100%;padding:9px 14px;border:none;background:none;cursor:pointer;font-size:12px;color:var(--text);font-family:'DM Sans',sans-serif;text-align:left}.save-opt:hover{background:var(--surface2)}
|
| 37 |
-
.btn-clr{background:var(--surface2);color:var(--text2);border:1px solid var(--border)}
|
| 38 |
-
.tcard{overflow:hidden}.twrap{overflow:auto}
|
| 39 |
-
table{width:100%;border-collapse:collapse}
|
| 40 |
-
thead th{padding:10px 12px;text-align:left;font-size:11px;font-weight:500;color:var(--text2);background:var(--surface2);border-bottom:1px solid var(--border);white-space:nowrap}
|
| 41 |
-
tbody tr{border-bottom:1px solid var(--border)} tbody tr:last-child{border-bottom:none}
|
| 42 |
-
tbody td{padding:10px 12px;font-size:12px;vertical-align:middle}
|
| 43 |
-
.mono{font-family:'DM Mono',monospace}
|
| 44 |
-
.pill{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border);background:var(--surface2);border-radius:999px;padding:3px 8px}
|
| 45 |
-
.dot{width:8px;height:8px;border-radius:50%}
|
| 46 |
-
.empty{padding:38px;text-align:center;color:var(--text3)}
|
| 47 |
-
@media(max-width:1100px){.sum-row{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
| 48 |
-
@media(max-width:760px){.sum-row{grid-template-columns:1fr}}
|
| 49 |
-
</style>
|
| 50 |
-
</head>
|
| 51 |
-
<body>
|
| 52 |
-
<nav class="sidebar">
|
| 53 |
-
<a href="/" class="sb-logo"><i class="fas fa-satellite-dish"></i></a>
|
| 54 |
-
<div class="sb-sep"></div>
|
| 55 |
-
<a href="/home" class="sb-btn"><i class="fas fa-play-circle"></i><span class="tip">Analyze</span></a>
|
| 56 |
-
<a href="/dashboard" class="sb-btn"><i class="fas fa-chart-line"></i><span class="tip">Dashboard</span></a>
|
| 57 |
-
<a href="/logs" class="sb-btn active"><i class="fas fa-list-ul"></i><span class="tip">Logs</span></a>
|
| 58 |
-
<a href="/history" class="sb-btn"><i class="fas fa-film"></i><span class="tip">History</span></a>
|
| 59 |
-
<div class="sb-spacer"></div>
|
| 60 |
-
<button class="sb-btn" onclick="toggleTheme()"><i class="fas fa-moon" id="themeIcon"></i><span class="tip">Theme</span></button>
|
| 61 |
-
</nav>
|
| 62 |
-
|
| 63 |
-
<div class="page-wrap">
|
| 64 |
-
<header class="topbar">
|
| 65 |
-
<div class="topbar-l">
|
| 66 |
-
<span class="brand-name">Track<b>IQ</b></span>
|
| 67 |
-
<span class="sep">|</span>
|
| 68 |
-
<span style="font-size:13px;font-weight:500;" id="t-title">Detection Logs</span>
|
| 69 |
-
</div>
|
| 70 |
-
<div class="topbar-r">
|
| 71 |
-
<div class="lang-group">
|
| 72 |
-
<button class="lang-btn active" onclick="setLang('en',this)">EN</button>
|
| 73 |
-
<button class="lang-btn" onclick="setLang('fr',this)">FR</button>
|
| 74 |
-
<button class="lang-btn" onclick="setLang('wo',this)">WO</button>
|
| 75 |
-
</div>
|
| 76 |
-
<button class="icon-btn" onclick="toggleTheme()"><i class="fas fa-moon" id="themeIcon2"></i></button>
|
| 77 |
-
</div>
|
| 78 |
-
</header>
|
| 79 |
-
|
| 80 |
-
<main>
|
| 81 |
-
<div class="sum-row">
|
| 82 |
-
<div class="sc"><div class="sc-l" id="t-s1">Detections</div><div class="sc-v" id="sumDetections">0</div><div class="sc-s" id="sumDetectionsSub">Real rows from generated videos</div></div>
|
| 83 |
-
<div class="sc"><div class="sc-l" id="t-s2">Scenes</div><div class="sc-v" id="sumScenes">0</div><div class="sc-s" id="sumScenesSub">Processed analyses</div></div>
|
| 84 |
-
<div class="sc"><div class="sc-l" id="t-s3">Classes</div><div class="sc-v" id="sumClasses">0</div><div class="sc-s" id="sumClassesSub">Distinct detected labels</div></div>
|
| 85 |
-
<div class="sc"><div class="sc-l" id="t-s4">Latest video</div><div class="sc-v" id="sumLatest">-</div><div class="sc-s" id="sumLatestSub">Most recent analyzed clip</div></div>
|
| 86 |
-
</div>
|
| 87 |
-
|
| 88 |
-
<div class="card">
|
| 89 |
-
<div class="filter-row">
|
| 90 |
-
<div class="fg"><label id="t-search">Search video or scene</label><input id="searchIn" class="fin" oninput="applyFilters()"></div>
|
| 91 |
-
<div class="fg"><label id="t-scene">Scene</label><select id="sceneF" class="fin" onchange="applyFilters()"><option value="">All</option></select></div>
|
| 92 |
-
<div class="fg"><label id="t-type">Class</label><select id="classF" class="fin" onchange="applyFilters()"><option value="">All</option></select></div>
|
| 93 |
-
<div class="fg"><label id="t-direction">Direction</label><select id="dirF" class="fin" onchange="applyFilters()"><option value="">All</option><option>up</option><option>down</option><option>left</option><option>right</option><option>unknown</option></select></div>
|
| 94 |
-
<div class="f-sp"></div>
|
| 95 |
-
<button class="btn btn-clr" onclick="clearFilters()" ondblclick="clearAllLogs()" title="Click=clear filters | Double-click=delete all logs"><i class="fas fa-rotate-left"></i> <span id="t-clear">Clear</span></button>
|
| 96 |
-
<div style="position:relative">
|
| 97 |
-
<button class="btn btn-exp" onclick="toggleSaveMenu(event)"><i class="fas fa-cloud-arrow-up"></i> <span id="t-export">Save Logs</span> <i class="fas fa-chevron-down" style="font-size:9px;margin-left:2px"></i></button>
|
| 98 |
-
<div id="saveMenu" style="display:none;position:absolute;right:0;top:calc(100% + 6px);background:var(--surface);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-lg);min-width:240px;z-index:999;overflow:hidden">
|
| 99 |
-
<div style="padding:10px 14px 6px;font-size:11px;color:var(--text2);font-weight:500;border-bottom:1px solid var(--border)">
|
| 100 |
-
<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px">
|
| 101 |
-
<label style="font-size:11px;color:var(--text2)">Group name:</label>
|
| 102 |
-
<input id="groupNameIn" value="trackiq" style="flex:1;padding:4px 7px;border:1px solid var(--border);border-radius:5px;font-size:11px;background:var(--surface2);color:var(--text);outline:none" placeholder="e.g. session1">
|
| 103 |
-
</div>
|
| 104 |
-
<span style="color:var(--text3);font-size:10px">File: <span id="csvPreview">trackiq_all.csv</span></span>
|
| 105 |
-
</div>
|
| 106 |
-
<button class="save-opt" onclick="saveLocal()"><i class="fas fa-download" style="color:#3b82f6"></i> Download locally</button>
|
| 107 |
-
<button class="save-opt" onclick="showGithubPanel()"><i class="fab fa-github" style="color:#e2e8f0"></i> Push to GitHub</button>
|
| 108 |
-
<button class="save-opt" onclick="saveDrive()"><i class="fab fa-google-drive" style="color:#34a853"></i> Save to Google Drive</button>
|
| 109 |
-
</div>
|
| 110 |
-
</div>
|
| 111 |
-
<!-- GitHub panel -->
|
| 112 |
-
<div id="githubPanel" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:1000;display:none;align-items:center;justify-content:center">
|
| 113 |
-
<div style="background:var(--surface);border-radius:14px;padding:22px 24px;width:420px;box-shadow:var(--shadow-lg)">
|
| 114 |
-
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
| 115 |
-
<span style="font-weight:600;font-size:14px"><i class="fab fa-github"></i> Push to GitHub</span>
|
| 116 |
-
<button onclick="closeGithubPanel()" style="background:none;border:none;color:var(--text2);cursor:pointer;font-size:16px"><i class="fas fa-times"></i></button>
|
| 117 |
-
</div>
|
| 118 |
-
<div style="display:flex;flex-direction:column;gap:9px">
|
| 119 |
-
<div class="fg"><label>Personal Access Token (PAT)</label><input id="ghToken" class="fin" type="password" placeholder="ghp_xxxxxxxxxxxx"></div>
|
| 120 |
-
<div class="fg"><label>Repository (owner/repo)</label><input id="ghRepo" class="fin" placeholder="username/trackiq-logs"></div>
|
| 121 |
-
<div class="fg"><label>Branch</label><input id="ghBranch" class="fin" value="main" placeholder="main"></div>
|
| 122 |
-
<div class="fg"><label>Path in repo</label><input id="ghPath" class="fin" placeholder="logs/" value="logs/"></div>
|
| 123 |
-
</div>
|
| 124 |
-
<div id="ghStatus" style="margin-top:10px;font-size:12px;color:var(--text2);min-height:18px"></div>
|
| 125 |
-
<div style="display:flex;gap:8px;margin-top:14px;justify-content:flex-end">
|
| 126 |
-
<button class="btn btn-clr" onclick="closeGithubPanel()">Cancel</button>
|
| 127 |
-
<button class="btn btn-exp" onclick="pushToGithub()"><i class="fab fa-github"></i> Push CSV</button>
|
| 128 |
-
</div>
|
| 129 |
-
</div>
|
| 130 |
-
</div>
|
| 131 |
-
</div>
|
| 132 |
-
</div>
|
| 133 |
-
|
| 134 |
-
<div class="tcard">
|
| 135 |
-
<div class="twrap">
|
| 136 |
-
<table>
|
| 137 |
-
<thead>
|
| 138 |
-
<tr>
|
| 139 |
-
<th id="th-video">Video</th>
|
| 140 |
-
<th id="th-scene">Scene</th>
|
| 141 |
-
<th id="th-time">Time</th>
|
| 142 |
-
<th id="th-frame">Frame</th>
|
| 143 |
-
<th id="th-track">Track</th>
|
| 144 |
-
<th id="th-class">Class</th>
|
| 145 |
-
<th id="th-confidence">Confidence</th>
|
| 146 |
-
<th id="th-direction">Direction</th>
|
| 147 |
-
<th id="th-bbox">Bounding Box</th>
|
| 148 |
-
<th id="th-position">Center</th>
|
| 149 |
-
</tr>
|
| 150 |
-
</thead>
|
| 151 |
-
<tbody id="tbody">
|
| 152 |
-
<tr><td colspan="9" class="empty">Loading real logs...</td></tr>
|
| 153 |
-
</tbody>
|
| 154 |
-
</table>
|
| 155 |
-
</div>
|
| 156 |
-
</div>
|
| 157 |
-
</main>
|
| 158 |
-
</div>
|
| 159 |
-
|
| 160 |
-
<script>
|
| 161 |
-
let allRows = [];
|
| 162 |
-
let filteredRows = [];
|
| 163 |
-
|
| 164 |
-
function toggleTheme(){
|
| 165 |
-
const d=document.documentElement;
|
| 166 |
-
const dark=d.getAttribute('data-theme')==='dark';
|
| 167 |
-
d.setAttribute('data-theme',dark?'light':'dark');
|
| 168 |
-
['themeIcon','themeIcon2'].forEach(id=>{const el=document.getElementById(id);if(el)el.className=dark?'fas fa-moon':'fas fa-sun';});
|
| 169 |
-
try{localStorage.setItem('tiq_theme',dark?'light':'dark');}catch(e){}
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
const TL = {
|
| 173 |
-
en:{title:'Detection Logs',s1:'Detections',s2:'Scenes',s3:'Classes',s4:'Latest video',search:'Search video or scene',scene:'Scene',type:'Class',direction:'Direction',clear:'Clear',export:'Save Logs',video:'Video',time:'Time',frame:'Frame',track:'Track',class:'Class',confidence:'Confidence',bbox:'Bounding Box',position:'Center'},
|
| 174 |
-
fr:{title:'Logs de détection',s1:'Détections',s2:'Scènes',s3:'Classes',s4:'Dernière vidéo',search:'Recherche vidéo ou scène',scene:'Scène',type:'Classe',direction:'Direction',clear:'Effacer',export:'Sauvegarder',video:'Vidéo',time:'Temps',frame:'Frame',track:'Track',class:'Classe',confidence:'Confiance',bbox:'Boîte englobante',position:'Centre'},
|
| 175 |
-
wo:{title:'Logs yi',s1:'Réy',s2:'Scène yi',s3:'Classe yi',s4:'Video mujj',search:'Seet video walla scène',scene:'Scène',type:'Classe',direction:'Direction',clear:'Baň',export:'Sauvegarder',video:'Video',time:'Waxtu',frame:'Frame',track:'Track',class:'Classe',confidence:'Confiance',bbox:'Bounding Box',position:'Centre'}
|
| 176 |
-
};
|
| 177 |
-
|
| 178 |
-
function setLang(l, btn){
|
| 179 |
-
document.querySelectorAll('.lang-btn').forEach(b=>b.classList.remove('active'));
|
| 180 |
-
btn.classList.add('active');
|
| 181 |
-
const t = TL[l];
|
| 182 |
-
const ids = {title:'t-title',s1:'t-s1',s2:'t-s2',s3:'t-s3',s4:'t-s4',search:'t-search',scene:'t-scene',type:'t-type',direction:'t-direction',clear:'t-clear',export:'t-export',video:'th-video',time:'th-time',frame:'th-frame',track:'th-track',class:'th-class',confidence:'th-confidence',bbox:'th-bbox',position:'th-position'};
|
| 183 |
-
Object.entries(ids).forEach(([k,id])=>{const el=document.getElementById(id);if(el)el.textContent=t[k];});
|
| 184 |
-
document.getElementById('th-scene').textContent = t.scene;
|
| 185 |
-
document.getElementById('th-direction').textContent = t.direction;
|
| 186 |
-
try{localStorage.setItem('tiq_lang',l);}catch(e){}
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
async function loadLogs(){
|
| 190 |
-
try{
|
| 191 |
-
const [rowsRes, scenesRes] = await Promise.all([fetch('/api/logs/rows'), fetch('/api/logs')]);
|
| 192 |
-
allRows = await rowsRes.json();
|
| 193 |
-
const scenes = await scenesRes.json();
|
| 194 |
-
fillFilters(scenes);
|
| 195 |
-
fillSummary(scenes, allRows);
|
| 196 |
-
applyFilters();
|
| 197 |
-
}catch(err){
|
| 198 |
-
const tb=document.getElementById('tbody');
|
| 199 |
-
tb.innerHTML = '<tr><td colspan="9" class="empty">Unable to load real logs.</td></tr>';
|
| 200 |
-
console.warn('[TrackIQ] real logs:', err);
|
| 201 |
-
}
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
function fillFilters(scenes){
|
| 205 |
-
const sceneF = document.getElementById('sceneF');
|
| 206 |
-
const classF = document.getElementById('classF');
|
| 207 |
-
sceneF.innerHTML = '<option value="">All</option>' + scenes.map(s=>`<option value="${s.scene_id}">${s.video_name || s.scene_id}</option>`).join('');
|
| 208 |
-
const classes = [...new Set(allRows.map(r=>r.class_name).filter(Boolean))].sort();
|
| 209 |
-
classF.innerHTML = '<option value="">All</option>' + classes.map(c=>`<option value="${c}">${c}</option>`).join('');
|
| 210 |
-
}
|
| 211 |
-
|
| 212 |
-
function fillSummary(scenes, rows){
|
| 213 |
-
document.getElementById('sumDetections').textContent = rows.length.toLocaleString();
|
| 214 |
-
document.getElementById('sumScenes').textContent = scenes.length.toLocaleString();
|
| 215 |
-
document.getElementById('sumClasses').textContent = new Set(rows.map(r=>r.class_name).filter(Boolean)).size.toLocaleString();
|
| 216 |
-
document.getElementById('sumLatest').textContent = scenes[0]?.video_name || '-';
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
function applyFilters(){
|
| 220 |
-
const search = document.getElementById('searchIn').value.toLowerCase();
|
| 221 |
-
const scene = document.getElementById('sceneF').value;
|
| 222 |
-
const cls = document.getElementById('classF').value;
|
| 223 |
-
const dir = document.getElementById('dirF').value;
|
| 224 |
-
filteredRows = allRows.filter(r =>
|
| 225 |
-
(!search || (r.video_name || '').toLowerCase().includes(search) || (r.scene_id || '').toLowerCase().includes(search)) &&
|
| 226 |
-
(!scene || r.scene_id === scene) &&
|
| 227 |
-
(!cls || r.class_name === cls) &&
|
| 228 |
-
(!dir || r.direction === dir)
|
| 229 |
-
);
|
| 230 |
-
renderRows();
|
| 231 |
-
}
|
| 232 |
-
|
| 233 |
-
async function clearAllLogs(){
|
| 234 |
-
if(!confirm('Delete all logs? This cannot be undone.')) return;
|
| 235 |
-
try{
|
| 236 |
-
const scenes = await fetch('/api/logs').then(r=>r.json());
|
| 237 |
-
await Promise.all(scenes.map(s=>fetch(`/api/logs/${s.scene_id}`,{method:'DELETE'})));
|
| 238 |
-
location.reload();
|
| 239 |
-
}catch(e){ alert('Error: '+e.message); }
|
| 240 |
-
}
|
| 241 |
-
|
| 242 |
-
function clearFilters(){
|
| 243 |
-
['searchIn','sceneF','classF','dirF'].forEach(id=>{document.getElementById(id).value='';});
|
| 244 |
-
applyFilters();
|
| 245 |
-
}
|
| 246 |
-
|
| 247 |
-
function renderRows(){
|
| 248 |
-
const tb = document.getElementById('tbody');
|
| 249 |
-
if(!filteredRows.length){
|
| 250 |
-
tb.innerHTML = '<tr><td colspan="10" class="empty">No real detections found for this filter.</td></tr>';
|
| 251 |
-
return;
|
| 252 |
-
}
|
| 253 |
-
const colors = {Vehicle:'#3b82f6',Motorcycle:'#10b981',Truck:'#f59e0b',Bus:'#8b5cf6',Human:'#ec4899',Bicycle:'#06b6d4'};
|
| 254 |
-
tb.innerHTML = '';
|
| 255 |
-
filteredRows.forEach(r=>{
|
| 256 |
-
const tr = document.createElement('tr');
|
| 257 |
-
tr.innerHTML = `
|
| 258 |
-
<td title="${r.video_name || ''}">${(r.video_name || '-').slice(0,28)}</td>
|
| 259 |
-
<td class="mono">${r.scene_id}</td>
|
| 260 |
-
<td class="mono">${Number(r.timestamp_s || 0).toFixed(2)}s</td>
|
| 261 |
-
<td class="mono">${r.frame_id}</td>
|
| 262 |
-
<td class="mono">#${r.track_id}</td>
|
| 263 |
-
<td><span class="pill"><span class="dot" style="background:${colors[r.class_name] || '#888'}"></span>${r.class_name || '-'}</span></td>
|
| 264 |
-
<td class="mono">${Math.round((r.confidence || 0) * 100)}%</td>
|
| 265 |
-
<td>${r.direction || 'unknown'}</td>
|
| 266 |
-
<td class="mono" style="font-size:11px">(${r.x1},${r.y1})→(${r.x2},${r.y2})</td>
|
| 267 |
-
<td class="mono">(${r.cx}, ${r.cy})</td>
|
| 268 |
-
`;
|
| 269 |
-
tb.appendChild(tr);
|
| 270 |
-
});
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
-
/* ── Helpers CSV ─────────────────────────────────────────────── */
|
| 274 |
-
function getGroupName(){ return (document.getElementById('groupNameIn')?.value||'trackiq').trim().replace(/\s+/g,'_')||'trackiq'; }
|
| 275 |
-
function getCsvFilename(){
|
| 276 |
-
const g=getGroupName();
|
| 277 |
-
const scenes=[...new Set(filteredRows.map(r=>r.scene_id))];
|
| 278 |
-
return `${g}_${scenes.length===1?scenes[0]:'all'}.csv`;
|
| 279 |
-
}
|
| 280 |
-
function buildCsvContent(){
|
| 281 |
-
const hdr = 'group,scene_id,video_name,timestamp_s,frame_id,track_id,class_name,confidence,x1,y1,x2,y2,cx,cy,direction\n';
|
| 282 |
-
const g = getGroupName();
|
| 283 |
-
const rows = filteredRows.map(r => [g,r.scene_id,`"${(r.video_name||'').replace(/"/g,'""')}"`,r.timestamp_s,r.frame_id,r.track_id,r.class_name,r.confidence,r.x1,r.y1,r.x2,r.y2,r.cx,r.cy,r.direction].join(',')).join('\n');
|
| 284 |
-
return hdr + rows;
|
| 285 |
-
}
|
| 286 |
-
function updateCsvPreview(){
|
| 287 |
-
const el = document.getElementById('csvPreview');
|
| 288 |
-
if(el) el.textContent = getCsvFilename();
|
| 289 |
-
}
|
| 290 |
-
|
| 291 |
-
/* ── Save menu ───────────────────────────────────���───────────── */
|
| 292 |
-
function toggleSaveMenu(e){
|
| 293 |
-
e.stopPropagation();
|
| 294 |
-
const m = document.getElementById('saveMenu');
|
| 295 |
-
m.style.display = m.style.display==='none'?'block':'none';
|
| 296 |
-
updateCsvPreview();
|
| 297 |
-
}
|
| 298 |
-
document.addEventListener('click', ()=>{ const m=document.getElementById('saveMenu'); if(m) m.style.display='none'; });
|
| 299 |
-
|
| 300 |
-
/* ── 1. Download local ───────────────────────────────────────── */
|
| 301 |
-
function saveLocal(){
|
| 302 |
-
const a = document.createElement('a');
|
| 303 |
-
a.href = URL.createObjectURL(new Blob([buildCsvContent()], {type:'text/csv'}));
|
| 304 |
-
a.download = getCsvFilename();
|
| 305 |
-
a.click();
|
| 306 |
-
document.getElementById('saveMenu').style.display='none';
|
| 307 |
-
}
|
| 308 |
-
|
| 309 |
-
/* ── 2. GitHub push ──────────────────────────────────────────── */
|
| 310 |
-
function showGithubPanel(){
|
| 311 |
-
document.getElementById('saveMenu').style.display='none';
|
| 312 |
-
const p = document.getElementById('githubPanel');
|
| 313 |
-
p.style.display='flex';
|
| 314 |
-
updateCsvPreview();
|
| 315 |
-
}
|
| 316 |
-
function closeGithubPanel(){ document.getElementById('githubPanel').style.display='none'; }
|
| 317 |
-
|
| 318 |
-
async function pushToGithub(){
|
| 319 |
-
const token = document.getElementById('ghToken').value.trim();
|
| 320 |
-
const repo = document.getElementById('ghRepo').value.trim();
|
| 321 |
-
const branch = document.getElementById('ghBranch').value.trim() || 'main';
|
| 322 |
-
const path = (document.getElementById('ghPath').value.trim().replace(/\/+$/,'')||'logs') + '/' + getCsvFilename();
|
| 323 |
-
const status = document.getElementById('ghStatus');
|
| 324 |
-
|
| 325 |
-
if(!token||!repo){ status.textContent='⚠ Token and repo are required.'; return; }
|
| 326 |
-
status.textContent='Pushing…';
|
| 327 |
-
|
| 328 |
-
const content = btoa(unescape(encodeURIComponent(buildCsvContent())));
|
| 329 |
-
// Check if file exists (to get SHA for update)
|
| 330 |
-
let sha = null;
|
| 331 |
-
try{
|
| 332 |
-
const check = await fetch(`https://api.github.com/repos/${repo}/contents/${path}`,{headers:{Authorization:`token ${token}`}});
|
| 333 |
-
if(check.ok){ const d=await check.json(); sha=d.sha; }
|
| 334 |
-
}catch(e){}
|
| 335 |
-
|
| 336 |
-
try{
|
| 337 |
-
const body = {message:`TrackIQ logs export — ${getCsvFilename()}`, content, branch};
|
| 338 |
-
if(sha) body.sha = sha;
|
| 339 |
-
const res = await fetch(`https://api.github.com/repos/${repo}/contents/${path}`,{
|
| 340 |
-
method:'PUT', headers:{Authorization:`token ${token}`,'Content-Type':'application/json'},
|
| 341 |
-
body: JSON.stringify(body)
|
| 342 |
-
});
|
| 343 |
-
if(res.ok){ status.innerHTML='<span style="color:#10b981">✓ Pushed successfully!</span>'; }
|
| 344 |
-
else{ const e=await res.json(); status.innerHTML=`<span style="color:#ef4444">Error: ${e.message}</span>`; }
|
| 345 |
-
}catch(err){ status.innerHTML=`<span style="color:#ef4444">Network error: ${err.message}</span>`; }
|
| 346 |
-
}
|
| 347 |
-
|
| 348 |
-
/* ── 3. Google Drive ─────────────────────────────────────────── */
|
| 349 |
-
async function saveDrive(){
|
| 350 |
-
document.getElementById('saveMenu').style.display='none';
|
| 351 |
-
// Uses Google Drive File Picker API via OAuth implicit flow
|
| 352 |
-
const CLIENT_ID = ''; // User must fill their own GCP OAuth client ID
|
| 353 |
-
if(!CLIENT_ID){
|
| 354 |
-
const ok = confirm('To save to Google Drive you need a Google OAuth Client ID.\n\nWould you like instructions on how to set it up?\n\n(For now the file will be downloaded locally as a fallback)');
|
| 355 |
-
if(ok){ window.open('https://developers.google.com/drive/api/guides/about-auth','_blank'); }
|
| 356 |
-
saveLocal();
|
| 357 |
-
return;
|
| 358 |
-
}
|
| 359 |
-
// Full Drive upload with OAuth token
|
| 360 |
-
const scope = 'https://www.googleapis.com/auth/drive.file';
|
| 361 |
-
const redirect = location.origin + location.pathname;
|
| 362 |
-
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(redirect)}&response_type=token&scope=${encodeURIComponent(scope)}`;
|
| 363 |
-
const popup = window.open(authUrl,'_blank','width=500,height=600');
|
| 364 |
-
// Listen for token in URL hash after redirect
|
| 365 |
-
const interval = setInterval(()=>{
|
| 366 |
-
try{
|
| 367 |
-
if(popup.closed){ clearInterval(interval); return; }
|
| 368 |
-
const hash = popup.location.hash;
|
| 369 |
-
if(hash && hash.includes('access_token')){
|
| 370 |
-
clearInterval(interval); popup.close();
|
| 371 |
-
const token = new URLSearchParams(hash.replace('#','')).get('access_token');
|
| 372 |
-
uploadToDrive(token);
|
| 373 |
-
}
|
| 374 |
-
}catch(e){}
|
| 375 |
-
},500);
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
async function uploadToDrive(token){
|
| 379 |
-
const csv = buildCsvContent();
|
| 380 |
-
const fname = getCsvFilename();
|
| 381 |
-
const meta = JSON.stringify({name:fname,mimeType:'text/csv'});
|
| 382 |
-
const form = new FormData();
|
| 383 |
-
form.append('metadata', new Blob([meta],{type:'application/json'}));
|
| 384 |
-
form.append('file', new Blob([csv], {type:'text/csv'}));
|
| 385 |
-
try{
|
| 386 |
-
const res = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart',{
|
| 387 |
-
method:'POST', headers:{Authorization:`Bearer ${token}`}, body:form
|
| 388 |
-
});
|
| 389 |
-
if(res.ok){ alert(`✓ "${fname}" saved to Google Drive!`); }
|
| 390 |
-
else{ const e=await res.json(); alert('Drive error: '+e.error?.message); }
|
| 391 |
-
}catch(err){ alert('Drive upload failed: '+err.message); }
|
| 392 |
-
}
|
| 393 |
-
|
| 394 |
-
function exportFiltered(){ saveLocal(); } // legacy alias
|
| 395 |
-
|
| 396 |
-
(function(){
|
| 397 |
-
try{
|
| 398 |
-
const th=localStorage.getItem('tiq_theme');
|
| 399 |
-
if(th){
|
| 400 |
-
document.documentElement.setAttribute('data-theme',th);
|
| 401 |
-
if(th==='dark'){['themeIcon','themeIcon2'].forEach(id=>{const el=document.getElementById(id);if(el)el.className='fas fa-sun';});}
|
| 402 |
-
}
|
| 403 |
-
const lg=localStorage.getItem('tiq_lang');
|
| 404 |
-
if(lg&&lg!=='en'){
|
| 405 |
-
const btn=document.querySelector(`.lang-btn[onclick*="'${lg}'"]`);
|
| 406 |
-
if(btn)setLang(lg,btn);
|
| 407 |
-
}
|
| 408 |
-
}catch(e){}
|
| 409 |
-
loadLogs();
|
| 410 |
-
// Update CSV filename preview when group name changes
|
| 411 |
-
document.addEventListener('input', e=>{ if(e.target.id==='groupNameIn') updateCsvPreview(); });
|
| 412 |
-
})();
|
| 413 |
-
</script>
|
| 414 |
-
</body>
|
| 415 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -49,7 +49,7 @@ def _load_model_background():
|
|
| 49 |
print("⏳ Chargement du modèle YOLO...")
|
| 50 |
_model = _get_model()
|
| 51 |
_model_ready = True
|
| 52 |
-
print(" Modèle YOLO chargé !")
|
| 53 |
|
| 54 |
def _get_model(key="yolo11n"):
|
| 55 |
p = MODELS_DIR / f"{key}.pt"
|
|
|
|
| 49 |
print("⏳ Chargement du modèle YOLO...")
|
| 50 |
_model = _get_model()
|
| 51 |
_model_ready = True
|
| 52 |
+
print("✅ Modèle YOLO chargé !")
|
| 53 |
|
| 54 |
def _get_model(key="yolo11n"):
|
| 55 |
p = MODELS_DIR / f"{key}.pt"
|
models/yolo11n.pt
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0ebbc80d4a7680d14987a577cd21342b65ecfd94632bd9a8da63ae6417644ee1
|
| 3 |
+
size 5613764
|
templates/home.html
CHANGED
|
@@ -306,7 +306,8 @@ const CLASSES=[
|
|
| 306 |
{name:'Human',color:'#ec4899'},{name:'Bicycle',color:'#06b6d4'},
|
| 307 |
{name:'Traffic light',color:'#ef4444'},{name:'Road sign',color:'#f97316'},
|
| 308 |
];
|
| 309 |
-
|
|
|
|
| 310 |
|
| 311 |
function buildCls(){
|
| 312 |
const g=document.getElementById('clsGrid'); g.innerHTML='';
|
|
@@ -450,8 +451,43 @@ let _wcFrameIv=null, _wcHiddenVideo=null, _wcCanvas2=null;
|
|
| 450 |
|
| 451 |
async function startWc(){
|
| 452 |
try{
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
|
| 456 |
document.getElementById('wcPh').style.display='none';
|
| 457 |
document.getElementById('recBadge').style.display='flex';
|
|
@@ -461,16 +497,48 @@ async function startWc(){
|
|
| 461 |
btn.innerHTML='<i class="fas fa-stop" style="color:#f87171;font-size:9px;"></i> Stop webcam';
|
| 462 |
btn.onclick=stopWc;
|
| 463 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
/* Lance YOLO backend */
|
| 465 |
-
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
/* Setup hidden video + canvas pour capturer frames */
|
| 469 |
_wcHiddenVideo=document.createElement('video');
|
| 470 |
_wcHiddenVideo.srcObject=wcStream;
|
| 471 |
-
_wcHiddenVideo.setAttribute('playsinline','');
|
| 472 |
_wcHiddenVideo.muted=true;
|
| 473 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
_wcCanvas2=document.createElement('canvas');
|
| 475 |
_wcCanvas2.width=640;_wcCanvas2.height=480;
|
| 476 |
const ctx2=_wcCanvas2.getContext('2d');
|
|
@@ -481,19 +549,59 @@ async function startWc(){
|
|
| 481 |
miniImg.style.display='block';
|
| 482 |
bigImg.style.display='block';
|
| 483 |
|
|
|
|
| 484 |
_wcFrameIv=setInterval(async()=>{
|
| 485 |
-
if(!wcStream||!_wcHiddenVideo)
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
},200);
|
| 498 |
|
| 499 |
/* Grand panel droite */
|
|
@@ -681,4 +789,4 @@ function resetAll(){
|
|
| 681 |
</script>
|
| 682 |
<script src="/static/trackiq-api.js"></script>
|
| 683 |
</body>
|
| 684 |
-
</html>
|
|
|
|
| 306 |
{name:'Human',color:'#ec4899'},{name:'Bicycle',color:'#06b6d4'},
|
| 307 |
{name:'Traffic light',color:'#ef4444'},{name:'Road sign',color:'#f97316'},
|
| 308 |
];
|
| 309 |
+
// ✅ CORRIGÉ: Sélectionne TOUTES les classes par défaut (pas juste Human!)
|
| 310 |
+
let sel=new Set(CLASSES.map(c=>c.name));
|
| 311 |
|
| 312 |
function buildCls(){
|
| 313 |
const g=document.getElementById('clsGrid'); g.innerHTML='';
|
|
|
|
| 451 |
|
| 452 |
async function startWc(){
|
| 453 |
try{
|
| 454 |
+
// ✅ CORRECTION 1: Vérifier que le modèle est prêt
|
| 455 |
+
const healthRes=await fetch('/health');
|
| 456 |
+
const health=await healthRes.json();
|
| 457 |
+
if(!health.model_ready){
|
| 458 |
+
alert('⏳ Model is still loading... Please wait 30 seconds and try again');
|
| 459 |
+
return;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
// ✅ CORRECTION 2: Vérifier que getUserMedia est disponible
|
| 463 |
+
if(!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia){
|
| 464 |
+
alert('⚠️ Webcam not available on this device or browser. Try uploading a video instead.');
|
| 465 |
+
return;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
// ✅ CORRECTION 3: Ajouter les flags pour Hugging Face Spaces
|
| 469 |
+
wcStream=await navigator.mediaDevices.getUserMedia({
|
| 470 |
+
video:{
|
| 471 |
+
width:640,
|
| 472 |
+
height:480,
|
| 473 |
+
facingMode:'user' // Force la caméra frontale
|
| 474 |
+
},
|
| 475 |
+
audio:false
|
| 476 |
+
});
|
| 477 |
+
}catch(e){
|
| 478 |
+
// ✅ CORRECTION 4: Messages d'erreur détaillés
|
| 479 |
+
console.error('Webcam error:',e);
|
| 480 |
+
if(e.name==='NotAllowedError'){
|
| 481 |
+
alert('❌ Camera permission denied. Please allow camera access in your browser settings.');
|
| 482 |
+
}else if(e.name==='NotFoundError'){
|
| 483 |
+
alert('❌ No camera found. Please connect a webcam or upload a video instead.');
|
| 484 |
+
}else if(e.name==='NotReadableError'){
|
| 485 |
+
alert('❌ Camera is in use by another application. Please close it and try again.');
|
| 486 |
+
}else{
|
| 487 |
+
alert('❌ Webcam unavailable: '+e.message);
|
| 488 |
+
}
|
| 489 |
+
return;
|
| 490 |
+
}
|
| 491 |
|
| 492 |
document.getElementById('wcPh').style.display='none';
|
| 493 |
document.getElementById('recBadge').style.display='flex';
|
|
|
|
| 497 |
btn.innerHTML='<i class="fas fa-stop" style="color:#f87171;font-size:9px;"></i> Stop webcam';
|
| 498 |
btn.onclick=stopWc;
|
| 499 |
|
| 500 |
+
/* ✅ CORRECTION 5: Ajouter un timeout pour éviter que ça reste en attente */
|
| 501 |
+
const timeoutId=setTimeout(()=>{
|
| 502 |
+
console.warn('Webcam frame sending timeout - checking if stream is alive');
|
| 503 |
+
if(!wcStream || !wcStream.active){
|
| 504 |
+
stopWc();
|
| 505 |
+
alert('⚠️ Webcam stream lost. Stopping...');
|
| 506 |
+
}
|
| 507 |
+
}, 30000); // 30 secondes max
|
| 508 |
+
|
| 509 |
/* Lance YOLO backend */
|
| 510 |
+
try{
|
| 511 |
+
const startRes=await fetch('/api/webcam/start',{
|
| 512 |
+
method:'POST',
|
| 513 |
+
headers:{'Content-Type':'application/json'},
|
| 514 |
+
body:JSON.stringify({classes:[...sel],model:'yolo11n'})
|
| 515 |
+
});
|
| 516 |
+
if(!startRes.ok){
|
| 517 |
+
throw new Error('Backend webcam start failed');
|
| 518 |
+
}
|
| 519 |
+
}catch(startErr){
|
| 520 |
+
console.error('Failed to start backend webcam:',startErr);
|
| 521 |
+
alert('⚠️ Could not start backend processing. Try uploading a video instead.');
|
| 522 |
+
clearTimeout(timeoutId);
|
| 523 |
+
return;
|
| 524 |
+
}
|
| 525 |
|
| 526 |
/* Setup hidden video + canvas pour capturer frames */
|
| 527 |
_wcHiddenVideo=document.createElement('video');
|
| 528 |
_wcHiddenVideo.srcObject=wcStream;
|
| 529 |
+
_wcHiddenVideo.setAttribute('playsinline',''); // ✅ Important pour iOS/mobile
|
| 530 |
_wcHiddenVideo.muted=true;
|
| 531 |
+
|
| 532 |
+
// ✅ CORRECTION 6: Attendre que la vidéo soit prête avant de jouer
|
| 533 |
+
_wcHiddenVideo.onloadedmetadata=()=>{
|
| 534 |
+
_wcHiddenVideo.play().catch(err=>console.warn('Video play error:',err));
|
| 535 |
+
};
|
| 536 |
+
|
| 537 |
+
// ✅ Si déjà chargée (rare mais possible)
|
| 538 |
+
if(_wcHiddenVideo.readyState>=2){
|
| 539 |
+
_wcHiddenVideo.play().catch(err=>console.warn('Video play error:',err));
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
_wcCanvas2=document.createElement('canvas');
|
| 543 |
_wcCanvas2.width=640;_wcCanvas2.height=480;
|
| 544 |
const ctx2=_wcCanvas2.getContext('2d');
|
|
|
|
| 549 |
miniImg.style.display='block';
|
| 550 |
bigImg.style.display='block';
|
| 551 |
|
| 552 |
+
let frameErrors=0;
|
| 553 |
_wcFrameIv=setInterval(async()=>{
|
| 554 |
+
if(!wcStream||!wcStream.active||!_wcHiddenVideo) {
|
| 555 |
+
console.warn('Webcam stream lost - stopping');
|
| 556 |
+
clearInterval(_wcFrameIv);
|
| 557 |
+
return;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
try{
|
| 561 |
+
// ✅ Vérifier que la vidéo a des frames
|
| 562 |
+
if(_wcHiddenVideo.readyState<2){
|
| 563 |
+
console.warn('Video not ready yet, frame:', _wcHiddenVideo.readyState);
|
| 564 |
+
return;
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
ctx2.drawImage(_wcHiddenVideo,0,0,640,480);
|
| 568 |
+
_wcCanvas2.toBlob(async(blob)=>{
|
| 569 |
+
if(!blob)return;
|
| 570 |
+
try{
|
| 571 |
+
const resp=await fetch('/api/webcam/frame',{
|
| 572 |
+
method:'POST',
|
| 573 |
+
headers:{'Content-Type':'image/jpeg'},
|
| 574 |
+
body:blob,
|
| 575 |
+
timeout:5000 // ✅ Timeout 5s par frame
|
| 576 |
+
});
|
| 577 |
+
|
| 578 |
+
if(!resp.ok){
|
| 579 |
+
frameErrors++;
|
| 580 |
+
if(frameErrors>5){
|
| 581 |
+
console.error('Too many frame errors - stopping webcam');
|
| 582 |
+
clearInterval(_wcFrameIv);
|
| 583 |
+
alert('⚠️ Backend is not responding. Stopping webcam.');
|
| 584 |
+
stopWc();
|
| 585 |
+
}
|
| 586 |
+
return;
|
| 587 |
+
}
|
| 588 |
+
frameErrors=0; // Reset counter on success
|
| 589 |
+
const url=URL.createObjectURL(await resp.blob());
|
| 590 |
+
miniImg.src=url;
|
| 591 |
+
bigImg.src=url;
|
| 592 |
+
}catch(e){
|
| 593 |
+
frameErrors++;
|
| 594 |
+
console.warn('Frame send error:',e);
|
| 595 |
+
if(frameErrors>5){
|
| 596 |
+
console.error('Too many frame errors - stopping');
|
| 597 |
+
clearInterval(_wcFrameIv);
|
| 598 |
+
stopWc();
|
| 599 |
+
}
|
| 600 |
+
}
|
| 601 |
+
},'image/jpeg',0.75);
|
| 602 |
+
}catch(e){
|
| 603 |
+
console.error('Frame interval error:',e);
|
| 604 |
+
}
|
| 605 |
},200);
|
| 606 |
|
| 607 |
/* Grand panel droite */
|
|
|
|
| 789 |
</script>
|
| 790 |
<script src="/static/trackiq-api.js"></script>
|
| 791 |
</body>
|
| 792 |
+
</html>
|