Files
SupervisorAI/web_page_2/index.html
2026-04-01 17:16:41 +08:00

447 lines
12 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" />
<title>AI督察系统</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background: transparent;
color: #e5e7eb;
overflow: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
display: flex;
flex-direction: column;
}
.app-container {
width: 100vw;
height: 100vh;
background: #111827;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.log-panel {
height: 50%;
background: #020617;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.log-header {
padding: 8px 12px;
background: #0f172a;
border-bottom: 1px solid #1f2937;
font-size: 14px;
color: #9ca3af;
font-weight: 500;
flex-shrink: 0;
}
.log-content {
flex: 1;
overflow-y: auto;
padding: 8px;
font-family: 'Courier New', monospace;
font-size: 12px;
min-height: 0;
}
.log-entry {
margin-bottom: 6px;
padding: 4px 6px;
word-wrap: break-word;
}
.log-info {
color: #60a5fa;
background: rgba(96, 165, 250, 0.1);
}
.log-success {
color: #34d399;
background: rgba(52, 211, 153, 0.1);
}
.log-warning {
color: #fbbf24;
background: rgba(251, 191, 36, 0.1);
}
.log-error {
color: #f87171;
background: rgba(248, 113, 113, 0.1);
}
main {
flex: 1;
display: flex;
min-height: 0;
position: relative;
z-index: 1;
overflow: hidden;
}
.left-panel {
width: 320px;
border-right: 1px solid #1f2937;
background: #020617;
display: flex;
flex-direction: column;
height: 100vh;
max-height: 100vh;
overflow: hidden;
}
.sidebar {
height: 50%;
display: flex;
flex-direction: column;
border-bottom: 1px solid #1f2937;
flex-shrink: 0;
}
.sidebar-header {
padding: 8px 12px;
border-bottom: 1px solid #1f2937;
font-size: 14px;
color: #9ca3af;
flex-shrink: 0;
}
.message-list {
flex: 1;
overflow-y: auto;
min-height: 0;
}
.message-item {
padding: 8px 12px;
border-bottom: 1px solid #111827;
cursor: pointer;
}
.message-item:hover {
background: #111827;
}
.message-item.active {
background: #1f2937;
}
.message-title {
font-size: 14px;
margin-bottom: 4px;
}
.message-meta {
font-size: 12px;
color: #9ca3af;
}
.content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #000;
position: relative;
min-width: 0;
}
#liveImage {
width: 100%;
height: 100%;
object-fit: contain;
background: #000;
}
.status-bar {
position: absolute;
left: 12px;
bottom: 12px;
padding: 4px 8px;
background: rgba(15, 23, 42, 0.8);
font-size: 12px;
color: #e5e7eb;
}
.action-tag {
display: inline-block;
padding: 2px 6px;
font-size: 11px;
margin-right: 4px;
font-weight: 500;
}
.action-action {
background: rgba(239, 68, 68, 0.2);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.action-face {
background: rgba(59, 130, 246, 0.2);
color: #60a5fa;
border: 1px solid rgba(59, 130, 246, 0.3);
}
.enable-sound-btn {
padding: 4px 12px;
background: #3b82f6;
color: #fff;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: background 0.2s;
flex-shrink: 0;
margin-left: 8px;
}
.enable-sound-btn:hover {
background: #2563eb;
}
.enable-sound-btn:disabled {
background: #1f2937;
color: #6b7280;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="app-container">
<main>
<aside class="left-panel">
<div class="sidebar">
<div class="sidebar-header" style="display: flex; align-items: center; justify-content: space-between;">
异常消息
<button id="enableSoundBtn" class="enable-sound-btn">🔔 启用提示音</button>
</div>
<div id="messageList" class="message-list"></div>
</div>
<div class="log-panel">
<div class="log-header">系统日志</div>
<div id="logContent" class="log-content"></div>
</div>
</aside>
<section class="content">
<img id="liveImage" alt="live" style="display: none;" />
<div id="status" class="status-bar">连接中...</div>
<div id="waitingPlaceholder" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #6b7280; font-size: 16px; text-align: center;">
<div>正在连接 WebSocket...</div>
</div>
</section>
</main>
</div>
<script>
// 从URL参数获取配置默认使用 api=1 的配置
const urlParams = new URLSearchParams(window.location.search);
const apiParam = urlParams.get('api') || '1';
// 配置映射表
const configMap = {
'1': { port: 8765 },
'2': { port: 8767 },
'3': { port: 8769 },
'4': { port: 8771 },
'5': { port: 8767 },
'11': { port: 8501 },
'12': { port: 8502 },
'13': { port: 8503 },
'14': { port: 8504 },
'15': { port: 8505 },
'16': { port: 8506 },
'17': { port: 8511 },
'18': { port: 8512 }
};
// 获取当前配置,如果找不到则使用默认配置
const config = configMap[apiParam] || { port: 8765 };
const WS_PORT = config.port;
const WS_HOST = '29.1.70.11';
<!-- const WS_HOST = '127.0.0.1'; -->
const liveImage = document.getElementById('liveImage');
const statusBar = document.getElementById('status');
const messageListEl = document.getElementById('messageList');
const logContent = document.getElementById('logContent');
let alerts = [];
let ws = null;
let wsConnected = false;
let currentDetectedActions = [];
let audioEnabled = false;
let alertAudio = null;
function setMode(newMode) {
// 模式切换功能已禁用
}
function enableSound() {
alertAudio = new Audio('/sfx_alert.mp3');
alertAudio.volume = 0.5;
alertAudio.play().then(() => {
audioEnabled = true;
const btn = document.getElementById('enableSoundBtn');
btn.textContent = '🔔 提示音已启用';
btn.disabled = true;
addLog('提示音已启用', 'success');
alertAudio.currentTime = 0;
alertAudio.pause();
}).catch(err => {
console.error('音频播放失败:', err);
addLog('提示音启用失败,请检查音频文件', 'error');
});
}
function playAlertSound() {
if (audioEnabled && alertAudio) {
alertAudio.currentTime = 0;
alertAudio.play().catch(err => {
console.error('音频播放失败:', err);
});
}
}
function renderMessages() {
messageListEl.innerHTML = '';
for (const msg of alerts.slice().reverse()) {
const div = document.createElement('div');
div.className = 'message-item';
const title = document.createElement('div');
title.className = 'message-title';
title.textContent = `检测到异常: `;
msg.result_type.forEach(action => {
const tag = document.createElement('span');
tag.className = 'action-tag';
tag.classList.add('action-action');
const actionMap = {
'face': '检测到黑名单',
'Slap': '扇巴掌',
'LeavingPost': '离岗',
'Collision': '撞击',
'Push': '推搡',
'Lyingdown': '倒下',
'Hanging': '自缢',
'violation': '路线违规',
'prisoner': '检测到犯人',
'Only One': '单人单检',
'Nobody': '无人检查',
'Trunk Checked': '检查后备箱',
'Playing Phone': '玩手机',
'Unvaild Uniform!!': '违规着装',
'Unchecked Trunk': '未检查后备箱',
'Ignore': '漏检',
'Indoor Violation': '违规进入区域'
};
tag.textContent = actionMap[action] || action;
title.appendChild(tag);
});
const meta = document.createElement('div');
meta.className = 'message-meta';
const date = new Date(msg.timestamp * 1000);
meta.textContent = date.toLocaleString();
div.appendChild(title);
div.appendChild(meta);
messageListEl.appendChild(div);
}
}
function addLog(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${type}`;
logEntry.textContent = `[${timestamp}] ${message}`;
logContent.appendChild(logEntry);
logContent.scrollTop = logContent.scrollHeight;
}
function connectWebSocket() {
const wsUrl = `ws://${WS_HOST}:${WS_PORT}`;
statusBar.textContent = '连接中...';
addLog(`正在连接WebSocket: ${wsUrl}`, 'info');
addLog(`当前配置: api=${apiParam}, port=${WS_PORT}`, 'info');
ws = new WebSocket(wsUrl);
ws.onopen = () => {
wsConnected = true;
statusBar.textContent = '直播中';
addLog('WebSocket连接成功', 'success');
const placeholder = document.getElementById('waitingPlaceholder');
if (placeholder) {
placeholder.remove();
}
liveImage.style.display = 'block';
};
ws.onclose = () => {
wsConnected = false;
statusBar.textContent = '连接断开,重试中...';
addLog('WebSocket连接断开2秒后重试', 'warning');
setTimeout(() => {
connectWebSocket();
}, 2000);
};
ws.onerror = (err) => {
console.error('WebSocket error', err);
addLog('WebSocket连接错误', 'error');
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.msg_type === 'frame') {
if (msg.image_base64) {
liveImage.src = `data:image/jpeg;base64,${msg.image_base64}`;
}
currentDetectedActions = msg.result_type || [];
if (currentDetectedActions.length > 0) {
const actionText = currentDetectedActions.join(', ');
statusBar.textContent = `直播中 | 检测到: ${actionText}`;
} else {
statusBar.textContent = '直播中';
}
if (msg.result_type && msg.result_type.length > 0) {
const alertMsg = {
camera_id: msg.camera_id,
timestamp: msg.timestamp,
result_type: msg.result_type
};
<!-- const now = msg.timestamp;-->
<!-- const timeThreshold = now - 10;-->
<!-- const existingAlert = alerts.find(alert =>-->
<!-- JSON.stringify(alert.result_type) === JSON.stringify(alertMsg.result_type) &&-->
<!-- alert.timestamp > timeThreshold-->
<!-- );-->
<!-- if (!existingAlert) {-->
alerts.push(alertMsg);
playAlertSound();
renderMessages();
<!-- }-->
}
}
} catch (e) {
console.error('invalid ws message', e);
}
};
}
// 初始化
document.getElementById('enableSoundBtn').addEventListener('click', enableSound);
addLog('AI督察系统启动', 'info');
addLog('界面初始化完成', 'success');
addLog(`接口参数: api=${apiParam}`, 'info');
setTimeout(() => {
connectWebSocket();
}, 500);
</script>
</body>
</html>