Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
333
themes/momentry/page-api-demo-display.php
Normal file
333
themes/momentry/page-api-demo-display.php
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: API Demo - 展示 (Display)
|
||||
* Description: Demonstrates file detail display, identity visualization, and classification results
|
||||
*/
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<div class="site-content">
|
||||
<h1>Momentry API Demo - 展示</h1>
|
||||
<p class="page-description">示範檔案詳情展示、身份視覺化、分類結果呈現等展示類 API 的操作</p>
|
||||
|
||||
<div class="api-nav">
|
||||
<a href="/api-demo-query/" class="nav-item">查詢</a>
|
||||
<a href="/api-demo-display/" class="nav-item active">展示</a>
|
||||
<a href="/api-demo-operation/" class="nav-item">操作</a>
|
||||
<a href="/api-demo-application/" class="nav-item">應用</a>
|
||||
</div>
|
||||
|
||||
<!-- 展示 1: 檔案詳情儀表板 -->
|
||||
<div class="api-section">
|
||||
<h2>1. 檔案詳情儀表板</h2>
|
||||
<p>整合展示檔案的元數據、處理進度、分類標籤等完整資訊。</p>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="dashboard_uuid">File UUID:</label>
|
||||
<input type="text" id="dashboard_uuid" placeholder="輸入 file_uuid">
|
||||
<button class="btn btn-primary" onclick="loadDashboard()">載入</button>
|
||||
</div>
|
||||
|
||||
<div id="dashboard_container" class="dashboard-grid" style="display:none;">
|
||||
<div class="card card-metadata">
|
||||
<h3>基本資訊</h3>
|
||||
<div id="metadata_content"></div>
|
||||
</div>
|
||||
<div class="card card-status">
|
||||
<h3>處理狀態</h3>
|
||||
<div id="status_content"></div>
|
||||
</div>
|
||||
<div class="card class="card-classification">
|
||||
<h3>分類標籤</h3>
|
||||
<div id="classification_content"></div>
|
||||
</div>
|
||||
<div class="card card-identity">
|
||||
<h3>關聯身份</h3>
|
||||
<div id="identity_content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展示 2: 身份視覺化 -->
|
||||
<div class="api-section">
|
||||
<h2>2. 身份視覺化</h2>
|
||||
<p>展示身份的跨檔案關聯、臉部檢測統計、品質分數等。</p>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="identity_viz_uuid">Identity UUID:</label>
|
||||
<input type="text" id="identity_viz_uuid" placeholder="輸入 identity_uuid">
|
||||
<button class="btn btn-primary" onclick="visualizeIdentity()">視覺化</button>
|
||||
</div>
|
||||
|
||||
<div id="identity_viz_container" class="identity-visualization" style="display:none;">
|
||||
<div class="viz-header">
|
||||
<h3 id="identity_name"></h3>
|
||||
<div class="quality-score" id="quality_score"></div>
|
||||
</div>
|
||||
<div class="viz-grid">
|
||||
<div class="viz-card">
|
||||
<h4>關聯檔案</h4>
|
||||
<ul id="linked_files"></ul>
|
||||
</div>
|
||||
<div class="viz-card">
|
||||
<h4>臉部統計</h4>
|
||||
<div id="face_stats"></div>
|
||||
</div>
|
||||
<div class="viz-card">
|
||||
<h4>角度覆蓋</h4>
|
||||
<div id="angle_coverage"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展示 3: 片段展示 -->
|
||||
<div class="api-section">
|
||||
<h2>3. 影片片段展示</h2>
|
||||
<p>展示影片的語義片段、說話者分段、鏡頭切換等分類結果。</p>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="chunks_uuid">File UUID:</label>
|
||||
<input type="text" id="chunks_uuid" placeholder="輸入 file_uuid">
|
||||
<select id="chunk_type">
|
||||
<option value="sentence">語義片段</option>
|
||||
<option value="cut">鏡頭切換</option>
|
||||
<option value="time">時間片段</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadChunks()">載入片段</button>
|
||||
</div>
|
||||
|
||||
<div id="chunks_container" class="chunks-timeline" style="display:none;">
|
||||
<div id="chunks_list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 展示 4: 分類結果 -->
|
||||
<div class="api-section">
|
||||
<h2>4. 分類結果展示</h2>
|
||||
<p>展示 YOLO 檢測、姿勢估計、動作識別等視覺分類結果。</p>
|
||||
|
||||
<div class="input-group">
|
||||
<label for="classify_uuid">File UUID:</label>
|
||||
<input type="text" id="classify_uuid" placeholder="輸入 file_uuid">
|
||||
<select id="processor_type">
|
||||
<option value="yolo">YOLO 物件檢測</option>
|
||||
<option value="pose">姿勢估計</option>
|
||||
<option value="face">臉部檢測</option>
|
||||
<option value="ocr">OCR 文字識別</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="loadClassification()">載入結果</button>
|
||||
</div>
|
||||
|
||||
<div id="classification_container" class="classification-results" style="display:none;">
|
||||
<div id="classification_content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dashboard-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-background);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.card h3 {
|
||||
margin-top: 0;
|
||||
color: var(--primary-color);
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
.identity-visualization {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.viz-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.viz-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
.viz-card {
|
||||
background: #f8fafc;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
}
|
||||
.viz-card h4 {
|
||||
margin-top: 0;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
.chunks-timeline {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.chunk-item {
|
||||
padding: 0.75rem;
|
||||
background: #f1f5f9;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
border-left: 3px solid var(--primary-color);
|
||||
}
|
||||
.chunk-time {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-secondary);
|
||||
font-family: monospace;
|
||||
}
|
||||
.classification-results {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.classification-item {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem;
|
||||
background: #f8fafc;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.confidence-bar {
|
||||
width: 100px;
|
||||
height: 8px;
|
||||
background: #e2e8f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.confidence-fill {
|
||||
height: 100%;
|
||||
background: var(--success-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const API_BASE = 'http://localhost:3003/api/v1';
|
||||
|
||||
async function loadDashboard() {
|
||||
const uuid = document.getElementById('dashboard_uuid').value.trim();
|
||||
if (!uuid) return alert('請輸入 file_uuid');
|
||||
|
||||
try {
|
||||
const [fileRes, jobRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/files/${uuid}`),
|
||||
fetch(`${API_BASE}/jobs/${uuid}/status`)
|
||||
]);
|
||||
const fileData = await fileRes.json();
|
||||
const jobData = await jobRes.json();
|
||||
|
||||
document.getElementById('dashboard_container').style.display = 'grid';
|
||||
|
||||
document.getElementById('metadata_content').innerHTML = `
|
||||
<p><strong>檔案名稱:</strong> ${fileData.file_name || '-'}</p>
|
||||
<p><strong>檔案類型:</strong> ${fileData.file_type || '-'}</p>
|
||||
<p><strong>時長:</strong> ${fileData.duration ? fileData.duration.toFixed(1) + 's' : '-'}</p>
|
||||
<p><strong>解析度:</strong> ${fileData.width}x${fileData.height}</p>
|
||||
<p><strong>幀率:</strong> ${fileData.fps ? fileData.fps.toFixed(1) : '-'} fps</p>
|
||||
`;
|
||||
|
||||
document.getElementById('status_content').innerHTML = `
|
||||
<p><strong>狀態:</strong> <span class="badge-status ${fileData.status}">${fileData.status || 'unknown'}</span></p>
|
||||
<p><strong>處理進度:</strong> ${jobData.progress || 0}%</p>
|
||||
<p><strong>已完成處理器:</strong> ${(jobData.completed_processors || []).join(', ') || '無'}</p>
|
||||
`;
|
||||
|
||||
document.getElementById('classification_content').innerHTML = `
|
||||
<p><strong>分類標籤:</strong> ${(fileData.tags || []).join(', ') || '無'}</p>
|
||||
<p><strong>語義標籤:</strong> ${(fileData.semantic_tags || []).join(', ') || '無'}</p>
|
||||
`;
|
||||
|
||||
document.getElementById('identity_content').innerHTML = `
|
||||
<p><strong>檢測到身份:</strong> ${fileData.identities_count || 0}</p>
|
||||
<p><strong>主要身份:</strong> ${fileData.primary_identity || '無'}</p>
|
||||
`;
|
||||
} catch (e) {
|
||||
alert('載入失敗: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function visualizeIdentity() {
|
||||
const uuid = document.getElementById('identity_viz_uuid').value.trim();
|
||||
if (!uuid) return alert('請輸入 identity_uuid');
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/identities/${uuid}`);
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('identity_viz_container').style.display = 'block';
|
||||
document.getElementById('identity_name').textContent = data.name || '未命名身份';
|
||||
document.getElementById('quality_score').innerHTML = `
|
||||
品質分數: <strong>${(data.quality_score || 0).toFixed(1)}</strong>/100
|
||||
`;
|
||||
|
||||
document.getElementById('linked_files').innerHTML = (data.linked_files || [])
|
||||
.map(f => `<li>${f.file_name} <span class="uuid">${f.file_uuid}</span></li>`)
|
||||
.join('') || '<li>無關聯檔案</li>';
|
||||
|
||||
document.getElementById('face_stats').innerHTML = `
|
||||
<p>檢測次數: ${data.detection_count || 0}</p>
|
||||
<p>平均品質: ${(data.avg_quality || 0).toFixed(1)}</p>
|
||||
`;
|
||||
|
||||
document.getElementById('angle_coverage').innerHTML = (data.angle_coverage || {})
|
||||
.map(a => `<span class="badge ${a.status}">${a.angle}</span>`)
|
||||
.join('') || '無資料';
|
||||
} catch (e) {
|
||||
alert('載入失敗: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadChunks() {
|
||||
const uuid = document.getElementById('chunks_uuid').value.trim();
|
||||
const type = document.getElementById('chunk_type').value;
|
||||
if (!uuid) return alert('請輸入 file_uuid');
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/files/${uuid}/chunks?type=${type}`);
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('chunks_container').style.display = 'block';
|
||||
document.getElementById('chunks_list').innerHTML = (data.chunks || [])
|
||||
.map(c => `
|
||||
<div class="chunk-item">
|
||||
<div class="chunk-time">${c.start_time?.toFixed(1) || 0}s - ${c.end_time?.toFixed(1) || 0}s</div>
|
||||
<div>${c.text_content || c.description || '無內容'}</div>
|
||||
</div>
|
||||
`).join('') || '<p>沒有片段</p>';
|
||||
} catch (e) {
|
||||
alert('載入失敗: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadClassification() {
|
||||
const uuid = document.getElementById('classify_uuid').value.trim();
|
||||
const processor = document.getElementById('processor_type').value;
|
||||
if (!uuid) return alert('請輸入 file_uuid');
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/files/${uuid}/processors/${processor}`);
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('classification_container').style.display = 'block';
|
||||
document.getElementById('classification_content').innerHTML = (data.results || [])
|
||||
.map(r => `
|
||||
<div class="classification-item">
|
||||
<div>
|
||||
<strong>${r.label || r.class}</strong>
|
||||
<div class="confidence-bar"><div class="confidence-fill" style="width:${(r.confidence || 0) * 100}%"></div></div>
|
||||
</div>
|
||||
<div>${r.bbox ? `[${r.bbox.join(',')}]` : ''}</div>
|
||||
</div>
|
||||
`).join('') || '<p>沒有檢測結果</p>';
|
||||
} catch (e) {
|
||||
alert('載入失敗: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
Reference in New Issue
Block a user