video_monitor/templates/app/smallmodel/test.html
2026-08-30 22:23:12 +08:00

629 lines
27 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.

{% extends "app/base.html" %}
{% load static %}
{% block title %}{{ T.sm_test_page|default:'小模型测试' }} - {{ settings.name|default:"Monitor" }}{% endblock %}
{% block nav_small_models %}active{% endblock %}
{% block page_title %}{{ T.sm_test_page|default:'小模型测试' }}{% endblock %}
{% block extra_head %}
<style>
.test-wrap{max-width:1200px;margin:0 auto;padding-bottom:20px;}
.test-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:14px;flex-wrap:wrap;}
.test-top h2{margin:0;font-size:16px;font-weight:700;color:#1e293b;display:flex;align-items:center;gap:8px;}
.test-top h2 .badge{font-size:10px;font-weight:600;padding:2px 8px;border-radius:12px;background:#ecfdf5;color:#065f46;border:1px solid #d1fae5;}
.test-back{display:inline-flex;align-items:center;gap:4px;padding:6px 12px;font-size:12px;border-radius:6px;border:1px solid #e2e8f0;background:#fff;color:#64748b;text-decoration:none;}
.test-top-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.btn-clear{padding:6px 12px;font-size:12px;border-radius:6px;border:1px solid #fecaca;background:#fff;color:#dc2626;cursor:pointer;}
.btn-clear:hover{background:#fef2f2;border-color:#f87171;}
.test-grid{display:grid;grid-template-columns:300px 1fr;gap:14px;align-items:start;}
@media(max-width:900px){.test-grid{grid-template-columns:1fr;}}
.panel{background:#fff;border-radius:10px;border:1px solid #eef2f6;box-shadow:0 1px 8px rgba(15,23,42,.04);overflow:hidden;}
.panel-head{padding:10px 14px;background:#fafbfc;border-bottom:1px solid #f1f5f9;}
.panel-head h3{margin:0;font-size:13px;font-weight:600;color:#475569;}
.panel-body{padding:12px 14px;}
.param-list{display:flex;flex-direction:column;gap:6px;}
.param-item{display:flex;justify-content:space-between;align-items:flex-start;gap:8px;padding:7px 10px;background:#f8fafc;border-radius:6px;font-size:11px;}
.param-item .k{color:#94a3b8;font-weight:600;flex-shrink:0;}
.param-item .v{color:#334155;text-align:right;word-break:break-all;font-family:Consolas,Monaco,monospace;font-size:10px;}
.param-item .v.tags{display:flex;flex-wrap:wrap;gap:3px;justify-content:flex-end;}
.param-tag{font-size:9px;padding:1px 6px;border-radius:8px;background:#e0f2fe;color:#0369a1;font-family:inherit;}
.work-zone{padding:14px;}
/* 紧凑上传栏 */
.upload-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 12px;background:#f8fafc;border:1px dashed #dbeafe;border-radius:8px;cursor:pointer;transition:.15s;}
.upload-bar:hover,.upload-bar.drag{border-color:#169F85;background:#f0fdf9;}
.upload-bar input{display:none;}
.btn-upload{flex-shrink:0;padding:6px 14px;font-size:12px;font-weight:600;border-radius:6px;border:1px solid #169F85;background:#fff;color:#169F85;cursor:pointer;pointer-events:none;}
.upload-bar:hover .btn-upload{background:#169F85;color:#fff;}
.upload-meta{flex:1;min-width:120px;}
.upload-meta .name{font-size:12px;color:#334155;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:320px;}
.upload-meta .hint{font-size:11px;color:#94a3b8;margin-top:2px;}
/* 原图 / 渲染对比 */
.media-compare{display:none;grid-template-columns:1fr 1fr;gap:10px;margin-top:12px;}
.media-compare.show{display:grid;}
@media(max-width:640px){.media-compare{grid-template-columns:1fr;}}
.media-pane{border:1px solid #eef2f6;border-radius:8px;overflow:hidden;background:#fff;}
.media-pane-head{padding:6px 10px;font-size:11px;font-weight:600;color:#64748b;background:#fafbfc;border-bottom:1px solid #f1f5f9;}
.media-pane-body{height:200px;display:flex;align-items:center;justify-content:center;background:#f1f5f9;overflow:hidden;position:relative;}
.media-pane-body img,.media-pane-body video{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;display:block;}
.media-pane-body img{cursor:zoom-in;}
.media-pane-body video{cursor:zoom-in;}
.media-pane-body .placeholder{font-size:11px;color:#94a3b8;text-align:center;padding:12px;}
.media-zoom-hint{position:absolute;right:8px;bottom:6px;font-size:10px;color:#94a3b8;background:rgba(255,255,255,.85);padding:2px 6px;border-radius:4px;pointer-events:none;opacity:0;transition:opacity .15s;}
.media-pane-body:hover .media-zoom-hint{opacity:1;}
.media-lightbox{display:none;position:fixed;inset:0;z-index:10050;background:rgba(15,23,42,.82);align-items:center;justify-content:center;padding:24px;}
.media-lightbox.show{display:flex;}
.media-lightbox-inner{position:relative;max-width:min(96vw,1400px);max-height:92vh;display:flex;flex-direction:column;align-items:center;}
.media-lightbox-close{position:absolute;top:-36px;right:0;width:32px;height:32px;border:none;border-radius:50%;background:rgba(255,255,255,.15);color:#fff;font-size:22px;line-height:1;cursor:pointer;}
.media-lightbox-close:hover{background:rgba(255,255,255,.28);}
.media-lightbox-title{margin-bottom:8px;font-size:12px;color:#e2e8f0;text-align:center;}
.media-lightbox-body{display:flex;align-items:center;justify-content:center;max-width:96vw;max-height:calc(92vh - 32px);overflow:auto;}
.media-lightbox-body img,.media-lightbox-body video{max-width:96vw;max-height:calc(92vh - 32px);width:auto;height:auto;object-fit:contain;display:block;border-radius:6px;box-shadow:0 8px 32px rgba(0,0,0,.35);}
.run-bar{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap;}
.btn-run{padding:7px 18px;font-size:12px;font-weight:600;border:none;border-radius:6px;cursor:pointer;background:#169F85;color:#fff;transition:.15s;}
.btn-run:hover:not(:disabled){background:#12836d;}
.btn-run:disabled{opacity:.5;cursor:not-allowed;}
.btn-ghost{padding:7px 14px;font-size:12px;border-radius:6px;border:1px solid #e2e8f0;background:#fff;color:#64748b;cursor:pointer;}
.btn-ghost:hover{border-color:#169F85;color:#169F85;}
.btn-ghost:disabled{opacity:.5;cursor:not-allowed;}
.progress-wrap{margin-top:10px;display:none;}
.progress-wrap.show{display:block;}
.progress-label{display:flex;justify-content:space-between;font-size:11px;color:#64748b;margin-bottom:4px;}
.progress-track{height:4px;background:#e2e8f0;border-radius:99px;overflow:hidden;}
.progress-fill{height:100%;width:0;border-radius:99px;background:#169F85;transition:width .25s ease;}
.progress-msg{font-size:10px;color:#94a3b8;margin-top:4px;}
.result-section{margin-top:14px;display:none;}
.result-section.show{display:block;}
.result-card{border:1px solid #eef2f6;border-radius:8px;overflow:hidden;background:#fff;}
.result-card-head{padding:8px 12px;background:#fafbfc;border-bottom:1px solid #f1f5f9;font-size:11px;font-weight:600;color:#475569;}
.result-card-body{padding:10px 12px;}
.report-table{width:100%;border-collapse:collapse;font-size:11px;}
.report-table th,.report-table td{padding:5px 8px;text-align:left;border-bottom:1px solid #f8fafc;}
.report-table th{color:#94a3b8;font-weight:600;width:38%;}
.report-table td{color:#334155;font-family:Consolas,Monaco,monospace;font-size:10px;}
.summary-chips{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;}
.summary-chip{font-size:10px;padding:2px 8px;border-radius:10px;background:#ecfdf5;color:#065f46;border:1px solid #d1fae5;}
.state-running .upload-bar{pointer-events:none;opacity:.65;}
</style>
{% endblock %}
{% block content %}
<div class="test-wrap state-idle" id="testWrap">
<div class="test-top">
<h2>
<span>{{ T.sm_test_page|default:'小模型测试' }}</span>
<span class="badge" id="algoBadge">#</span>
</h2>
<div class="test-top-actions">
<button type="button" class="btn-clear" onclick="clearTempFiles()">{{ T.sm_test_clear_temp|default:'清理测试临时文件' }}</button>
<a href="/smallmodel/index" class="test-back">← {{ T.sm_back_list|default:'返回小模型列表' }}</a>
</div>
</div>
<div class="test-grid">
<div class="panel">
<div class="panel-head"><h3>{{ T.sm_test_params|default:'小模型参数' }}</h3></div>
<div class="panel-body">
<div class="param-list" id="paramList">
<div style="text-align:center;color:#94a3b8;font-size:12px;padding:16px">{{ T.index_loading|default:'加载中...' }}</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-head"><h3>{{ T.sm_test_workspace|default:'小模型测试工作台' }}</h3></div>
<div class="work-zone">
<div id="reidDetectorPanel" style="display:none;margin-bottom:12px;padding:10px 12px;background:#f0f9ff;border:1px solid #bfdbfe;border-radius:8px;">
<label style="display:block;font-size:12px;font-weight:600;color:#1e40af;margin-bottom:6px;">{{ T.sm_reid_detector|default:'辅助检测小模型 (YOLO)' }} *</label>
<select id="detectorAlgoId" style="width:100%;padding:8px 10px;font-size:13px;border:1px solid #bfdbfe;border-radius:6px;" onchange="onDetectorChange()"></select>
<div style="font-size:11px;color:#64748b;margin-top:6px;">{{ T.sm_reid_detector_hint|default:'ReID 需先用检测模型定位行人,再提取特征并关联 track_id' }}</div>
</div>
<div class="upload-bar" id="dropArea">
<input type="file" id="fileInput" accept="image/*,video/*,.mp4,.avi,.mov,.mkv,.webm">
<span class="btn-upload">{{ T.sm_test_pick_file|default:'选择测试文件' }}</span>
<div class="upload-meta">
<div class="name" id="pickedName">{{ T.sm_test_upload_hint|default:'JPG / PNG / MP4最大 100MB' }}</div>
<div class="hint" id="pickedHint">{{ T.sm_test_upload_sub|default:'点击或拖拽到此处' }}</div>
</div>
</div>
<div class="media-compare" id="mediaCompare">
<div class="media-pane">
<div class="media-pane-head">{{ T.sm_test_input_preview|default:'原始样本' }}</div>
<div class="media-pane-body" id="inputPane">
<div class="placeholder" id="inputEmpty">{{ T.sm_test_preview_empty|default:'选择文件后可预览' }}</div>
<img id="previewImg" style="display:none" alt="preview">
<video id="previewVideo" style="display:none" controls muted playsinline></video>
<span class="media-zoom-hint">{{ T.sm_test_click_zoom|default:'点击放大' }}</span>
</div>
</div>
<div class="media-pane">
<div class="media-pane-head">{{ T.sm_test_output|default:'渲染结果' }}</div>
<div class="media-pane-body" id="outputPane">
<div class="placeholder" id="outputEmpty">{{ T.sm_test_output_wait|default:'小模型测试完成后显示' }}</div>
<span class="media-zoom-hint">{{ T.sm_test_click_zoom|default:'点击放大' }}</span>
</div>
</div>
</div>
<div class="run-bar">
<button type="button" class="btn-run" id="btnRun" disabled onclick="startTest()">{{ T.sm_test_run|default:'开始小模型测试' }}</button>
<button type="button" class="btn-ghost" id="btnReset" onclick="resetTest()">{{ T.sm_test_reset|default:'重置' }}</button>
</div>
<div class="progress-wrap" id="progressWrap">
<div class="progress-label">
<span id="progressLabel">{{ T.sm_test_running|default:'小模型测试中...' }}</span>
<span id="progressPct">0%</span>
</div>
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
<div class="progress-msg" id="progressMsg"></div>
</div>
<div class="result-section" id="resultSection">
<div class="result-card">
<div class="result-card-head">{{ T.sm_test_report|default:'小模型测试报告' }}</div>
<div class="result-card-body" id="reportBody"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="media-lightbox" id="mediaLightbox" onclick="closeMediaLightbox(event)">
<div class="media-lightbox-inner" onclick="event.stopPropagation()">
<button type="button" class="media-lightbox-close" onclick="closeMediaLightbox()" aria-label="close">&times;</button>
<div class="media-lightbox-title" id="lightboxTitle"></div>
<div class="media-lightbox-body" id="lightboxBody"></div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
var g_algoId = 0;
var g_algo = null;
var g_file = null;
var g_pollTimer = null;
var g_taskId = '';
var g_previewUrl = '';
var g_detectorId = 0;
var g_detectors = [];
function isReidAlgo(){
return g_algo && (g_algo.task_type || '').toLowerCase() === 'reid';
}
function updateRunButtonState(){
var needDetector = isReidAlgo();
var detectorOk = !needDetector || g_detectorId > 0;
document.getElementById('btnRun').disabled = !g_file || !g_algo || !g_algo.model_file || !detectorOk;
}
function typeLabel(t){
return ({yolo5:'YOLOv5',yolo8:'YOLOv8',yolo11:'YOLOv11',yolo26:'YOLO26',osnet:'OSNet ReID'})[t] || t || '-';
}
function qsId(){
var m = /[?&]id=(\d+)/.exec(window.location.search);
return m ? parseInt(m[1], 10) : 0;
}
function engineLabel(name){
if(name==='yolo_pytorch') return 'Yolo-PyTorch';
if(name==='onnxruntime') return 'OnnxRuntime';
if(name==='openvino') return 'OpenVINO';
return name || '-';
}
function renderParams(a){
var labels = a.labels || [];
var labelsHtml = '';
if(labels.length){
labelsHtml = '<div class="v tags">' + labels.slice(0,8).map(function(l){
return '<span class="param-tag">'+escapeHtml(l)+'</span>';
}).join('') + (labels.length>8?' <span class="param-tag">+'+(labels.length-8)+'</span>':'') + '</div>';
} else {
labelsHtml = '<span class="v">-</span>';
}
var rows = [
[_t('sm_name','小模型名称'), escapeHtml(a.name)],
[_t('sm_type','模型类型'), typeLabel(a.algorithm_type)],
[_t('sm_task_type','任务类型'), (a.task_type||'detect').toUpperCase()],
[_t('sm_engine','推理引擎'), engineLabel(a.inference_engine)],
[_t('sm_device','推理设备'), (a.device||'cpu').toUpperCase()],
[_t('sm_resolution','分辨率'), (a.input_width||640)+'×'+(a.input_height||640)],
];
if((a.task_type||'detect').toLowerCase() !== 'reid'){
rows.push([_t('sm_conf_threshold','置信度'), String(a.conf_threshold)]);
rows.push([_t('sm_labels','类别'), labelsHtml, true]);
}
rows.push([_t('sm_model_file','模型'), escapeHtml(a.model_file||'-')]);
var html = '';
rows.forEach(function(r){
if(r[2]) html += '<div class="param-item"><span class="k">'+r[0]+'</span>'+r[1]+'</div>';
else html += '<div class="param-item"><span class="k">'+r[0]+'</span><span class="v">'+r[1]+'</span></div>';
});
document.getElementById('paramList').innerHTML = html;
document.getElementById('algoBadge').textContent = '#'+a.id+' '+a.name;
}
function renderDetectorSelect(){
var panel = document.getElementById('reidDetectorPanel');
var sel = document.getElementById('detectorAlgoId');
if(!panel || !sel) return;
if(!isReidAlgo()){
panel.style.display = 'none';
g_detectorId = 0;
updateRunButtonState();
return;
}
panel.style.display = 'block';
sel.innerHTML = '';
if(!g_detectors.length){
var o = document.createElement('option');
o.value = '';
o.textContent = _t('sm_reid_no_detector','暂无可用检测小模型,请先在列表中添加 YOLO Detect 模型');
sel.appendChild(o);
g_detectorId = 0;
} else {
var picked = null;
g_detectors.forEach(function(d){
var o = document.createElement('option');
o.value = d.id;
var eng = engineLabel(d.inference_engine);
o.textContent = '#'+d.id+' '+d.name+' ('+eng+')';
sel.appendChild(o);
if(d.is_default === 1) picked = d.id;
});
g_detectorId = picked || g_detectors[0].id;
sel.value = String(g_detectorId);
}
updateRunButtonState();
}
function onDetectorChange(){
var sel = document.getElementById('detectorAlgoId');
g_detectorId = sel && sel.value ? parseInt(sel.value, 10) : 0;
updateRunButtonState();
}
function loadDetectors(cb){
Api.get('/smallmodel/openDetectors').then(function(res){
g_detectors = (res && res.code===1000 && res.data) ? res.data : [];
renderDetectorSelect();
if(cb) cb();
});
}
function loadAlgorithm(){
g_algoId = qsId();
if(!g_algoId){
showToast(_t('sm_test_no_id','缺少小模型 ID'), 'error');
setTimeout(function(){ window.location.href='/smallmodel/index'; }, 1200);
return;
}
Api.get('/smallmodel/openDetail', {id: g_algoId}).then(function(res){
if(!res || res.code!==1000 || !res.data || !res.data.id){
showToast(res?res.msg:_t('load_failed','加载失败'), 'error');
return;
}
g_algo = res.data;
renderParams(g_algo);
if(isReidAlgo()){
loadDetectors();
} else {
renderDetectorSelect();
}
if(!g_algo.model_file){
showToast(_t('sm_no_model_file','请先配置模型文件'), 'warning');
}
updateRunButtonState();
});
}
function isVideoFile(f){
return f && (/^video\//.test(f.type) || /\.(mp4|avi|mov|mkv|webm|m4v)$/i.test(f.name));
}
function isImageFile(f){
return f && (/^image\//.test(f.type) || /\.(jpg|jpeg|png|bmp|webp)$/i.test(f.name));
}
function revokePreviewUrl(){
if(g_previewUrl){ URL.revokeObjectURL(g_previewUrl); g_previewUrl = ''; }
}
function setFile(f){
if(!f) return;
if(!isVideoFile(f) && !isImageFile(f)){
showToast(_t('sm_test_bad_type','不支持的文件类型'), 'warning');
return;
}
g_file = f;
document.getElementById('pickedName').textContent = f.name;
document.getElementById('pickedHint').textContent = formatBytes(f.size);
updateRunButtonState();
var img = document.getElementById('previewImg');
var vid = document.getElementById('previewVideo');
var inputEmpty = document.getElementById('inputEmpty');
revokePreviewUrl();
g_previewUrl = URL.createObjectURL(f);
img.style.display = 'none';
vid.style.display = 'none';
inputEmpty.style.display = 'none';
if(isVideoFile(f)){
vid.src = g_previewUrl;
vid.style.display = 'block';
} else {
img.src = g_previewUrl;
img.style.display = 'block';
}
document.getElementById('mediaCompare').classList.add('show');
clearOutputPane(true);
document.getElementById('resultSection').classList.remove('show');
}
function clearOutputPane(showPlaceholder){
var pane = document.getElementById('outputPane');
var empty = document.getElementById('outputEmpty');
pane.querySelectorAll('img,video').forEach(function(el){ el.remove(); });
if(showPlaceholder){
empty.style.display = 'block';
empty.textContent = _t('sm_test_output_wait','小模型测试完成后显示');
empty.style.color = '';
if(!pane.contains(empty)) pane.appendChild(empty);
} else {
empty.style.display = 'none';
}
}
function openMediaLightbox(type, src, title){
if(!src) return;
var box = document.getElementById('mediaLightbox');
var body = document.getElementById('lightboxBody');
var titleEl = document.getElementById('lightboxTitle');
body.innerHTML = '';
titleEl.textContent = title || '';
if(type === 'video'){
var vid = document.createElement('video');
vid.src = src;
vid.controls = true;
vid.autoplay = true;
vid.playsInline = true;
body.appendChild(vid);
} else {
var img = document.createElement('img');
img.src = src;
img.alt = title || 'preview';
body.appendChild(img);
}
box.classList.add('show');
document.body.style.overflow = 'hidden';
}
function closeMediaLightbox(e){
if(e && e.target && e.target.id !== 'mediaLightbox' && !e.target.classList.contains('media-lightbox-close')) return;
var box = document.getElementById('mediaLightbox');
var body = document.getElementById('lightboxBody');
box.classList.remove('show');
body.innerHTML = '';
document.body.style.overflow = '';
}
function initMediaZoom(){
function onPaneClick(e){
var t = e.target;
if(t.tagName === 'IMG' && t.src){
e.preventDefault();
e.stopPropagation();
var title = t.closest('.media-pane') ? t.closest('.media-pane').querySelector('.media-pane-head').textContent : '';
openMediaLightbox('image', t.src, title);
} else if(t.tagName === 'VIDEO' && t.src){
e.preventDefault();
e.stopPropagation();
var titleV = t.closest('.media-pane') ? t.closest('.media-pane').querySelector('.media-pane-head').textContent : '';
openMediaLightbox('video', t.src, titleV);
}
}
document.getElementById('inputPane').addEventListener('click', onPaneClick);
document.getElementById('outputPane').addEventListener('click', onPaneClick);
document.addEventListener('keydown', function(e){
if(e.key === 'Escape') closeMediaLightbox({target: document.getElementById('mediaLightbox')});
});
}
function formatBytes(n){
if(!n) return '0 B';
if(n<1024*1024) return (n/1024).toFixed(1)+' KB';
return (n/1024/1024).toFixed(2)+' MB';
}
(function initDrop(){
var zone = document.getElementById('dropArea');
var input = document.getElementById('fileInput');
zone.addEventListener('click', function(e){
if(document.getElementById('testWrap').classList.contains('state-running')) return;
if(e.target.tagName==='VIDEO') return;
input.click();
});
input.addEventListener('change', function(e){ if(e.target.files[0]) setFile(e.target.files[0]); });
zone.addEventListener('dragover', function(e){ e.preventDefault(); zone.classList.add('drag'); });
zone.addEventListener('dragleave', function(){ zone.classList.remove('drag'); });
zone.addEventListener('drop', function(e){
e.preventDefault(); zone.classList.remove('drag');
if(e.dataTransfer.files[0]) setFile(e.dataTransfer.files[0]);
});
})();
function setRunning(running){
document.getElementById('testWrap').classList.toggle('state-running', running);
updateRunButtonState();
if(running) document.getElementById('btnRun').disabled = true;
document.getElementById('btnReset').disabled = running;
document.getElementById('progressWrap').classList.toggle('show', running);
}
function updateProgress(pct, msg){
document.getElementById('progressFill').style.width = Math.min(100, pct)+'%';
document.getElementById('progressPct').textContent = Math.round(pct)+'%';
document.getElementById('progressMsg').textContent = msg || '';
}
function startTest(){
if(!g_file || !g_algoId) return;
if(g_pollTimer){ clearInterval(g_pollTimer); g_pollTimer = null; }
document.getElementById('resultSection').classList.remove('show');
clearOutputPane(true);
setRunning(true);
updateProgress(2, _t('sm_test_uploading','上传样本...'));
var fd = new FormData();
fd.append('algorithm_id', g_algoId);
fd.append('file', g_file);
if(isReidAlgo() && g_detectorId){
fd.append('detector_algorithm_id', g_detectorId);
}
Api.postForm('/smallmodel/openTestStart', fd).then(function(res){
if(!res || res.code!==1000 || !res.data || !res.data.task_id){
setRunning(false);
showToast(res?res.msg:_t('msg_unknown_error','失败'), 'error');
return;
}
g_taskId = res.data.task_id;
updateProgress(8, _t('sm_test_queued','任务已排队...'));
g_pollTimer = setInterval(pollStatus, 800);
pollStatus();
});
}
function pollStatus(){
if(!g_taskId) return;
Api.get('/smallmodel/openTestStatus', {task_id: g_taskId}).then(function(res){
if(!res || res.code!==1000 || !res.data) return;
var d = res.data;
updateProgress(d.progress||0, d.message||'');
if(d.status==='done'){
clearInterval(g_pollTimer); g_pollTimer = null;
setRunning(false);
showResult(d);
showToast(_t('sm_test_done','小模型测试完成'), 'success');
} else if(d.status==='error'){
clearInterval(g_pollTimer); g_pollTimer = null;
setRunning(false);
var errMsg = d.error || d.message || _t('msg_unknown_error','失败');
showToast(errMsg, 'error');
clearOutputPane(true);
document.getElementById('outputEmpty').textContent = errMsg;
document.getElementById('outputEmpty').style.color = '#f87171';
document.getElementById('resultSection').classList.remove('show');
}
});
}
function showResult(d){
var rep = d.report || {};
var html = '<table class="report-table">';
html += row(_t('sm_test_media','媒体类型'), rep.media_type||'-');
html += row(_t('sm_test_input_size','输入尺寸'), (rep.input_size||[]).join('×')||'-');
if(rep.media_type==='video'){
html += row(_t('sm_test_frames','处理帧数'), (rep.processed_frames||0)+'/'+(rep.frame_count||'?'));
html += row('FPS', rep.fps||'-');
}
html += row(_t('sm_test_det_count','检测总数'), rep.detection_count||0);
if(rep.task_type === 'reid'){
html += row(_t('sm_test_embedding_count','特征提取数'), rep.embedding_count||0);
html += row(_t('sm_test_track_count','轨迹峰值'), rep.track_count||0);
if(rep.reid_sim_mean != null) html += row(_t('sm_test_reid_sim','特征时序相似度均值'), rep.reid_sim_mean);
if(rep.detector_name) html += row(_t('sm_reid_detector','辅助检测小模型'), escapeHtml(rep.detector_name));
}
html += row(_t('sm_test_infer_avg','平均推理'), (rep.inference_ms_avg||0)+' ms');
html += row(_t('sm_test_elapsed','总耗时'), (rep.elapsed_ms||0)+' ms');
html += '</table>';
var summary = rep.detections_summary || [];
if(summary.length){
html += '<div class="summary-chips">';
summary.slice(0,12).forEach(function(s){
html += '<span class="summary-chip">'+escapeHtml(s.label)+' ×'+s.count+'</span>';
});
html += '</div>';
}
document.getElementById('reportBody').innerHTML = html;
clearOutputPane(false);
var pane = document.getElementById('outputPane');
if(d.output_url){
var src = d.output_url + (d.output_url.indexOf('?') >= 0 ? '&' : '?') + 't=' + Date.now();
if(d.output_type === 'video'){
var vid = document.createElement('video');
vid.src = src;
vid.controls = true;
vid.muted = true;
vid.playsInline = true;
pane.appendChild(vid);
} else {
var img = document.createElement('img');
img.alt = 'result';
img.src = src;
img.onerror = function(){
clearOutputPane(true);
document.getElementById('outputEmpty').textContent = _t('sm_test_output_load_fail','渲染结果加载失败');
document.getElementById('outputEmpty').style.color = '#f87171';
};
pane.appendChild(img);
}
} else {
clearOutputPane(true);
}
document.getElementById('resultSection').classList.add('show');
}
function row(k,v){
return '<tr><th>'+k+'</th><td>'+v+'</td></tr>';
}
function resetTest(){
if(g_pollTimer){ clearInterval(g_pollTimer); g_pollTimer = null; }
g_taskId = '';
g_file = null;
revokePreviewUrl();
document.getElementById('fileInput').value = '';
document.getElementById('pickedName').textContent = _t('sm_test_upload_hint','JPG / PNG / MP4最大 100MB');
document.getElementById('pickedHint').textContent = _t('sm_test_upload_sub','点击或拖拽到此处');
document.getElementById('previewImg').style.display = 'none';
document.getElementById('previewVideo').style.display = 'none';
document.getElementById('inputEmpty').style.display = 'block';
document.getElementById('mediaCompare').classList.remove('show');
clearOutputPane(true);
document.getElementById('resultSection').classList.remove('show');
document.getElementById('progressWrap').classList.remove('show');
setRunning(false);
}
function clearTempFiles(){
if(!confirm(_t('sm_test_clear_confirm','确认清理所有小模型测试临时文件?'))) return;
Api.post('/smallmodel/openTestClearTemp', {}).then(function(res){
showToast(res.msg, res.code===1000?'success':'error');
if(res.code===1000){
resetTest();
}
});
}
loadAlgorithm();
initMediaZoom();
</script>
{% endblock %}