Files
SupervisorAI/web_page/index.html

400 lines
11 KiB
HTML
Raw 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>指监智能场景识别系统</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 {
max-width: 100%;
max-height: 100%;
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);
}
.action-takeout {
background: rgba(139, 92, 246, 0.2);
color: #a78bfa;
border: 1px solid rgba(139, 92, 246, 0.3);
}
</style>
</head>
<body>
<div class="app-container">
<main>
<aside class="left-panel">
<div class="sidebar">
<div class="sidebar-header">异常消息</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>
const WS_PORT = 8765;
const WS_HOST = 'localhost';
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 = [];
function setMode(newMode) {
// 模式切换功能已禁用
}
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';
if (msg.msg_type === 'take_out') {
// 处理take_out类型的消息
title.textContent = `人员带出: `;
const tag = document.createElement('span');
tag.className = 'action-tag action-takeout';
tag.textContent = msg.person_name;
title.appendChild(tag);
const countTag = document.createElement('span');
countTag.className = 'action-tag action-face';
countTag.textContent = `${msg.historical_alert_count}`;
title.appendChild(countTag);
} else {
// 处理原有的异常检测消息
title.textContent = `检测到异常: `;
msg.result_type.forEach(action => {
const tag = document.createElement('span');
tag.className = 'action-tag';
if (action === 'face') {
tag.classList.add('action-face');
tag.textContent = '检测到黑名单';
} else {
tag.classList.add('action-action');
const actionMap = {
'Slap': '扇巴掌',
'LeavingPost': '离岗',
'Collision': '撞击',
'Push': '推搡',
'Lyingdown': '倒下',
'Hanging': '自缢'
};
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');
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);
renderMessages();
const alertTypes = alertMsg.result_type.join(', ');
addLog(`检测到异常: ${alertTypes}`, 'warning');
}
} else if (msg.msg_type === 'take_out') {
// 处理take_out类型的消息
const takeOutMsg = {
msg_type: 'take_out',
person_name: msg.person_name,
historical_alert_count: msg.historical_alert_count || 0,
timestamp: Math.floor(Date.now() / 1000)
};
alerts.push(takeOutMsg);
renderMessages();
addLog(`人员带出: ${takeOutMsg.person_name} (历史告警: ${takeOutMsg.historical_alert_count})`, 'warning');
}
}
} catch (e) {
console.error('invalid ws message', e);
}
};
}
// 初始化
addLog('AI Monitor 系统启动', 'info');
addLog('界面初始化完成', 'success');
setTimeout(() => {
connectWebSocket();
}, 500);
</script>
</body>
</html>