Files
NaviGlassServer/templates/index.html
2025-12-31 15:42:30 +08:00

715 lines
17 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>NaviGlass 导盲系统可视化</title>
<link rel="icon" type="image/png" href="/static/favicon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0b0f14;
--card: #121821;
--text: #e6edf3;
--muted: #9fb0c3;
--ok: #7ee787;
--err: #ff8080;
--warn: #fbbf24;
--line: #1f2937;
--panel: rgba(18, 24, 33, .75);
--primary: #3b82f6;
--primary-glow: rgba(59, 130, 246, 0.4);
}
* {
box-sizing: border-box
}
body {
margin: 0;
background: linear-gradient(180deg, #0b0f14, #0b0f14 60%, #0e1621);
color: var(--text);
font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial;
}
/* 两栏布局 - Day 20 修复:防止滚动条 */
html,
body {
height: 100%;
overflow: hidden;
}
.app {
display: grid;
grid-template-columns: 1fr 700px;
gap: 16px;
height: 100vh;
padding: 16px;
box-sizing: border-box;
overflow: hidden;
}
.stage {
position: relative;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--card);
/* Day 20 修复: 统一使用 --card 背景色 */
box-shadow: 0 10px 30px rgba(0, 0, 0, .25);
overflow: hidden;
}
.canvas-wrap,
#canvas {
position: absolute;
inset: 1px;
/* Day 20 修复: 留出边框空间 */
width: calc(100% - 2px);
height: calc(100% - 2px);
display: block;
background: #0a1017;
/* 视频区域保持深色 */
border-radius: 13px;
/* 比外框小 1px */
z-index: 10
}
/* 右上角:参数 + 日志 */
.tri-panels {
position: absolute;
right: 12px;
top: 12px;
z-index: 30;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
max-width: min(720px, 60vw);
}
.box {
border: 2px dashed rgba(255, 255, 255, .35);
border-radius: 12px;
padding: 10px;
background: var(--panel);
backdrop-filter: blur(8px) saturate(130%);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06);
}
.box h4 {
margin: 0 0 6px 0;
font-size: 12px;
color: #ffd769
}
.kv {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px dashed rgba(255, 255, 255, .15);
padding-top: 6px;
margin-top: 6px;
color: var(--muted);
font-size: 12px
}
/* 左上角 IMU 浮窗 */
.imu-float {
position: absolute;
left: 12px;
top: 12px;
z-index: 35;
width: 600px;
/* 恢复合理尺寸 */
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
background: rgba(18, 24, 33, 0.9);
backdrop-filter: blur(12px) saturate(140%);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
padding: 12px;
transition: all 0.3s ease;
}
.imu-float:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
/* Day 20: 优化折叠状态 - 只显示标题和按钮 */
.imu-float.collapsed {
width: 180px;
height: 40px;
padding: 8px 12px;
overflow: hidden;
}
.imu-float.collapsed .imu-header {
margin: 0;
font-size: 12px;
}
.imu-float.collapsed .imu-row,
.imu-float.collapsed #imu_top_status {
display: none !important;
}
.imu-toggle {
position: absolute;
top: 4px;
right: 8px;
z-index: 10;
width: 32px;
height: 32px;
border-radius: 8px;
background: rgba(255, 255, 255, .1);
border: none;
cursor: pointer;
color: var(--muted);
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.imu-toggle:hover {
background: rgba(255, 255, 255, .2);
color: var(--text);
}
.imu-row {
display: grid;
grid-template-columns: 1.5fr 1fr;
/* 左侧 3D 模型稍大 */
gap: 12px;
align-items: stretch;
min-height: 200px;
}
#imu_view {
position: relative;
min-height: 200px;
background: #0a1017;
border-radius: 10px;
}
#imu_hud {
border-radius: 10px;
background: rgba(18, 24, 33, 0.8);
border: none;
padding: 8px;
overflow: visible;
/* Day 20: 允许内容自然显示 */
min-width: 150px;
/* Day 20: 缩小最小宽度 */
max-height: 300px;
}
#imu_hud::-webkit-scrollbar {
width: 8px;
/* 滚动条的宽度 */
}
#imu_hud::-webkit-scrollbar-thumb {
background-color: #2a6df4;
/* 滑块的颜色 */
border-radius: 4px;
/* 滑块的圆角 */
}
#imu_hud::-webkit-scrollbar-track {
background-color: #111a2e;
/* 滚动条轨道的颜色 */
border-radius: 4px;
/* 滚动条轨道的圆角 */
}
#imu_top_status {
position: absolute;
top: 10px;
right: 10px;
z-index: 2
}
#imu_top_status .badge {
background: rgba(0, 0, 0, .35);
border-color: #2d3b50
}
/* 角标 + 当前指令(角标在浮窗下) */
.badge-tag {
position: absolute;
left: 12px;
top: calc(12px + 300px + 28px);
z-index: 20;
background: linear-gradient(120deg, rgba(47, 134, 255, .9), rgba(26, 88, 255, .9));
color: #fff;
padding: 6px 10px;
font-weight: 700;
font-size: 12px;
border-radius: 999px;
box-shadow: 0 6px 16px rgba(47, 134, 255, .35)
}
.command {
position: absolute;
left: 50%;
bottom: 46px;
transform: translateX(-50%);
z-index: 25;
display: flex;
gap: 10px;
align-items: center;
padding: 12px 18px;
border-radius: 999px;
background: rgba(18, 24, 33, .75);
border: 1px solid rgba(255, 255, 255, .14);
backdrop-filter: blur(8px) saturate(140%);
box-shadow: 0 12px 28px rgba(0, 0, 0, .6)
}
.dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: #2f86ff;
box-shadow: 0 0 16px #2f86ff
}
/* 右侧聊天(左右气泡)- Day 20 修复:移除 height:100vh */
.chat {
display: flex;
flex-direction: column;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--card);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
overflow: hidden;
/* 不再使用 height:100vh让 grid 自动分配高度 */
}
.chat-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid var(--line)
}
.badges {
display: flex;
gap: 8px;
flex-wrap: wrap
}
/* Day 20: 美化状态 badge */
.badge {
font-size: 11px;
font-weight: 500;
padding: 5px 10px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, .1);
color: var(--muted);
background: rgba(255, 255, 255, .05);
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.badge::before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.badge.ok {
color: var(--ok);
border-color: rgba(126, 231, 135, .3);
background: rgba(126, 231, 135, .1);
}
.badge.ok::before {
animation: pulse 2s infinite;
}
.badge.err {
color: var(--err);
border-color: rgba(255, 128, 128, .3);
background: rgba(255, 128, 128, .1);
}
.badge.connecting {
color: var(--warn);
border-color: rgba(251, 191, 36, .3);
background: rgba(251, 191, 36, .1);
}
.badge.connecting::before {
animation: blink 1s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: .6;
transform: scale(1.2);
}
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: .2;
}
}
.chat-list {
flex: 1;
overflow: auto;
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
min-height: 0;
/* 确保 .chat-list 不会被外部内容撑大 */
}
.live {
padding: 12px;
border: 1px dashed #2d3b50;
border-radius: 12px;
background: #0c121a
}
.live h2 {
margin: 0 0 8px 0;
font-size: 14px;
color: var(--muted);
font-weight: 600
}
.partial {
font-size: 20px;
min-height: 2.2em;
letter-spacing: .2px
}
.finals {
padding: 12px;
border: 1px solid #1f2937;
border-radius: 12px;
background: #0c121a;
display: flex;
flex-direction: column;
flex: 1;
/* 使用 flex: 1 而不是 flex-grow: 1 */
min-height: 0;
/* 允许收缩到小于内容高度 */
}
.finals h2 {
margin: 0 0 8px 0;
font-size: 14px;
color: var(--muted);
font-weight: 600
}
.finals ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px
}
.bubble {
max-width: 82%;
padding: 10px 12px;
border-radius: 14px;
border: 1px solid #1f2937
}
.from-bot {
align-self: flex-start;
background: #0d1729
}
.from-me {
align-self: flex-end;
background: #12263a
}
.controls {
display: flex;
gap: 8px
}
button {
background: linear-gradient(135deg, #2563eb, #1d4ed8);
border: none;
color: #fff;
border-radius: 10px;
padding: 8px 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 2px 8px rgba(37, 99, 235, .3);
}
button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(37, 99, 235, .4);
}
button:active {
transform: translateY(0);
}
.ghost {
background: transparent;
border: 1px solid #304057;
color: #9fb0c3;
box-shadow: none;
}
.ghost:hover {
background: rgba(255, 255, 255, .05);
border-color: #405570;
}
/* 隐藏但保留:校准/滑杆(供 main.js 使用) */
.hidden-controls {
display: none
}
/* Day 20: 改善移动端适配 */
@media (max-width:1100px) {
.app {
grid-template-columns: 1fr;
height: auto;
padding: 8px;
overflow: visible;
}
/* 关键修复:确保视频区域有足够高度 */
.stage {
min-height: 50vh;
height: 50vh;
position: relative;
}
.chat {
height: 50vh;
min-height: 400px;
}
/* 移动端 IMU 浮窗缩小并移到右下角 */
.imu-float {
width: 160px;
left: auto;
right: 8px;
top: auto;
bottom: 8px;
padding: 8px;
}
/* 移动端默认折叠状态 */
.imu-float:not(.expanded) {
width: 160px;
height: 40px;
overflow: hidden;
}
.imu-float:not(.expanded) .imu-row,
.imu-float:not(.expanded) #imu_top_status {
display: none !important;
}
.imu-row {
grid-template-columns: 1fr;
}
#imu_view {
min-height: 200px;
}
}
@media (max-width:600px) {
.badges {
gap: 4px;
}
.badge {
font-size: 10px;
padding: 4px 8px;
}
.controls {
flex-wrap: wrap;
}
button {
padding: 6px 10px;
font-size: 12px;
}
/* 手机端视频区域更大 */
.stage {
min-height: 45vh;
height: 45vh;
}
.chat {
height: 45vh;
min-height: 300px;
}
/* 手机端 IMU 浮窗始终小巧 */
.imu-float {
width: 140px;
right: 6px;
bottom: 6px;
left: auto;
top: auto;
padding: 6px;
}
.imu-float.collapsed,
.imu-float:not(.expanded) {
width: 140px;
height: 36px;
}
}
</style>
</head>
<body>
<div class="app">
<!-- 左侧主舞台 -->
<section class="stage">
<div class="canvas-wrap"><canvas id="canvas"></canvas></div>
<!-- 左上角IMU 浮窗横向Day 20 优化 -->
<div class="imu-float" id="imuFloat">
<button class="imu-toggle" id="imuToggle" title="折叠/展开"></button>
<div class="imu-header" style="font-size:12px;color:#61dafb;margin-bottom:8px;font-weight:600;">📊 IMU 姿态可视化
</div>
<div class="imu-row">
<div id="imu_view"></div>
<div id="imu_hud"><!-- JS 会把“IMU 实时数据面板”插到这里 --></div>
</div>
<div id="imu_top_status" style="display: none;">
<span class="badge">UDP: <code>12345</code></span>
<span class="badge">Browser WS: <code>/ws</code></span>
<span class="badge" id="imu_ws_state">connecting…</span>
</div>
</div>
</section>
<!-- 右侧聊天 -->
<aside class="chat">
<div class="chat-head">
<div class="badges">
<span id="camStatus" class="badge">Camera: connecting…</span>
<span id="asrStatus" class="badge">ASR: connecting…</span>
<span id="fps" class="badge">FPS: --</span>
</div>
<div class="controls">
<button class="ghost" id="btnClear">清空 Final</button>
<button id="btnReconnect">重连</button>
</div>
</div>
<div class="chat-list">
<div class="live">
<h2>流式识别Partial</h2>
<div class="partial" id="partial">(等待音频…)</div>
</div>
<div class="finals">
<h2>最终文本Final</h2>
<ul id="finalList"></ul>
</div>
</div>
</aside>
</div>
<!-- 隐藏但保留:校准/滑杆(供 main.js 使用) -->
<div class="hidden-controls">
<button id="btn_zero"></button><button id="btn_reset"></button><button id="btn_bias_now"></button>
<input id="auto_rezero" type="checkbox" checked /><input id="auto_bias" type="checkbox" checked />
<input id="use_proj" type="checkbox" checked /><input id="freeze_still" type="checkbox" checked />
<select id="medn">
<option>3</option>
<option selected>5</option>
<option>7</option>
</select>
<select id="ang_ema">
<option value="0">0</option>
<option value="0.15" selected>0.15</option>
<option value="0.30">0.30</option>
<option value="0.5">0.5</option>
</select>
<select id="grav_beta">
<option value="0.95">0.95</option>
<option value="0.97">0.97</option>
<option value="0.98" selected>0.98</option>
<option value="0.99">0.99</option>
</select>
<select id="yaw_db">
<option value="0.05">0.05</option>
<option value="0.08" selected>0.08</option>
<option value="0.15">0.15</option>
<option value="0.30">0.30</option>
</select>
<select id="still_w">
<option value="0.4" selected>0.4</option>
<option value="0.6">0.6</option>
<option value="1.0">1.0</option>
</select>
<select id="yaw_leak">
<option value="0">0</option>
<option value="0.1">0.1</option>
<option value="0.2" selected>0.2</option>
<option value="0.5">0.5</option>
</select>
<input id="roll_sl" type="range"><span id="roll_val"></span>
<input id="pitch_sl" type="range"><span id="pitch_val"></span>
<input id="yaw_sl" type="range"><span id="yaw_val"></span>
<input id="gx_sl" type="range"><span id="gx_val"></span>
<input id="gy_sl" type="range"><span id="gy_val"></span>
<input id="gz_sl" type="range"><span id="gz_val"></span>
<input id="ax_sl" type="range"><span id="ax_val"></span>
<input id="ay_sl" type="range"><span id="ay_val"></span>
<input id="az_sl" type="range"><span id="az_val"></span>
</div>
<!-- three.js importmap -->
<script type="importmap">{
"imports": { "three": "https://unpkg.com/three@0.155.0/build/three.module.js" }
}</script>
<!-- 主脚本 -->
<script type="module" src="/static/main.js"></script>
</body>
</html>