feat: SQLite persistence, non-blocking inference, modular frontend

Backend:
- Persist alert events and robot delivery counters in SQLite (data/app.db,
  YOLO_DB_PATH override); EventStore keeps its interface, DeliveryStatsStore
  uses read/write-through UPSERT; in-memory fallback remains for standalone
  AlertManager use
- Serve /api/detect from a sync endpoint and serialize YOLO inference with a
  lock, so concurrent requests no longer block the event loop
- Unify single-image and session detection: requests without session_id
  share the fixed single-image session and run full frame confirmation
- Store UTC Z-suffixed timestamps for sortable string comparison; close
  executor and database on shutdown via FastAPI lifespan

Frontend:
- Split app.js into ES modules (main.js + modules/{dom,api,ui,charts,store}
  + views/{dashboard,inspection,events,robots,robotCards}), no build step
- Escape all server data interpolated into innerHTML; guard missing
  event.classes; replace lazy element ID list with memoized qs()
- Remove hardcoded fake stats (trend badge, device donut segment)

Docs:
- Rewrite README: accurate weight policy (only trained best.pt committed,
  no Git LFS), SQLite persistence, single-worker note, data asset inventory
- Align models/pretrained/README.md with actual files; add YOLO_DB_PATH to
  .env.example; add persistence unit tests; ruff clean
This commit is contained in:
2026-08-13 16:50:23 +08:00
parent 4dee7f664d
commit 25fa0c5825
25 changed files with 1058 additions and 669 deletions

View File

@@ -68,7 +68,7 @@
<div class="dashboard-command-grid">
<article class="panel safety-score-panel">
<div class="panel-header"><div><p class="section-kicker">SAFETY INDEX</p><h3>综合安全指数</h3></div><span class="trend-badge">较昨日 +2.4%</span></div>
<div class="panel-header"><div><p class="section-kicker">SAFETY INDEX</p><h3>综合安全指数</h3></div></div>
<div class="safety-score-body"><div id="safetyGauge" class="safety-gauge" style="--score: 86"><div><strong id="safetyScore">86</strong><span>安全</span></div></div><div class="score-breakdown"><div><span>机器人可用率</span><strong id="deviceOnlineRate">--</strong></div><div><span>事件闭环率</span><strong id="eventClosureRate">--</strong></div><div><span>通知覆盖率</span><strong id="notificationCoverage">--</strong></div></div></div>
</article>
<article class="panel message-flow-panel">
@@ -84,7 +84,7 @@
</article>
<article class="panel risk-donut-panel">
<div class="panel-header"><div><p class="section-kicker">RISK MIX</p><h3>风险类型分布</h3></div><span class="micro-copy">近 7 日</span></div>
<div class="donut-layout"><div id="riskDonut" class="risk-donut"><div><strong id="riskTotal">0</strong><span>事件</span></div></div><div class="donut-legend"><div><span><i class="legend-fire"></i>火焰</span><strong id="fireRiskRatio">0%</strong></div><div><span><i class="legend-smoke"></i>烟雾</span><strong id="smokeRiskRatio">0%</strong></div><div><span><i class="legend-device"></i>设备</span><strong>12%</strong></div></div></div>
<div class="donut-layout"><div id="riskDonut" class="risk-donut"><div><strong id="riskTotal">0</strong><span>事件</span></div></div><div class="donut-legend"><div><span><i class="legend-fire"></i>火焰</span><strong id="fireRiskRatio">0%</strong></div><div><span><i class="legend-smoke"></i>烟雾</span><strong id="smokeRiskRatio">0%</strong></div></div></div>
</article>
</div>
@@ -165,7 +165,7 @@
<div class="fleet-stat-grid">
<article class="fleet-stat"><span>支持平台</span><strong id="robotTotalCount">0</strong><small>企业微信与飞书</small></article>
<article class="fleet-stat"><span>已配置</span><strong id="robotConfiguredCount">0</strong><small>可接收告警消息</small></article>
<article class="fleet-stat"><span>成功投递</span><strong id="robotDeliveredCount">0</strong><small>本次服务运行期间</small></article>
<article class="fleet-stat"><span>成功投递</span><strong id="robotDeliveredCount">0</strong><small>累计(已持久化)</small></article>
<article class="fleet-stat"><span>发送失败</span><strong id="robotFailedCount">0</strong><small>需要检查网络或凭据</small></article>
</div>
<div class="robot-management-grid message-robot-layout">
@@ -192,7 +192,7 @@
<div class="page-intro"><div><p class="section-kicker">SYSTEM SETTINGS</p><h2>系统设置</h2><p>查看当前生效的模型和告警参数。配置修改后需重启服务。</p></div></div>
<div class="settings-grid">
<article class="panel settings-card"><div class="panel-header"><div><p class="section-kicker">MODEL</p><h3>模型配置</h3></div></div><dl><div><dt>权重文件</dt><dd id="settingWeights">--</dd></div><div><dt>输入尺寸</dt><dd id="settingImageSize">--</dd></div><div><dt>置信度阈值</dt><dd id="settingConfidence">--</dd></div><div><dt>IOU 阈值</dt><dd id="settingIou">--</dd></div></dl></article>
<article class="panel settings-card"><div class="panel-header"><div><p class="section-kicker">ALERT POLICY</p><h3>告警策略</h3></div></div><dl><div><dt>连续确认</dt><dd id="settingConfirmFrames">--</dd></div><div><dt>分类冷却</dt><dd id="settingCooldown">--</dd></div><div><dt>事件存储</dt><dd>进程内最近 500 条</dd></div><div><dt>视频处理</dt><dd>浏览器本地抽帧</dd></div></dl></article>
<article class="panel settings-card"><div class="panel-header"><div><p class="section-kicker">ALERT POLICY</p><h3>告警策略</h3></div></div><dl><div><dt>连续确认</dt><dd id="settingConfirmFrames">--</dd></div><div><dt>分类冷却</dt><dd id="settingCooldown">--</dd></div><div><dt>事件存储</dt><dd>SQLite 持久化 · 最近 500 条</dd></div><div><dt>视频处理</dt><dd>浏览器本地抽帧</dd></div></dl></article>
</div>
<article class="panel env-panel"><div class="panel-header"><div><p class="section-kicker">ENVIRONMENT</p><h3>环境变量参考</h3></div></div><pre><code>YOLO_WEIGHTS=...
YOLO_DEVICE=0
@@ -206,6 +206,6 @@ ALERT_COOLDOWN_SECONDS=60</code></pre></article>
</main>
</div>
<div id="toastRegion" class="toast-region" aria-live="polite"></div>
<script src="./app.js" type="module"></script>
<script src="./main.js" type="module"></script>
</body>
</html>