Files
m5wp.momentry.ddns.net/themes/momentry/page-api-demo-operation.php
OpenCode 39b9fd7da8 Unify API port to 3002 and add video playback to search page
- Change localhost:3003 to localhost:3002 in demo pages
- Add video playback modal to page-api-demo-query.php
- Enhance search results with clickable video chunks
- Update search functions to support identity and universal search
2026-05-30 19:32:46 +08:00

285 lines
10 KiB
PHP

<?php
/**
* Template Name: API Demo - 操作 (Operation)
* Description: Demonstrates file registration, identity binding, processing triggers and other operational APIs
*/
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">展示</a>
<a href="/api-demo-operation/" class="nav-item active">操作</a>
<a href="/api-demo-application/" class="nav-item">應用</a>
</div>
<!-- 操作 1: 檔案註冊 -->
<div class="api-section">
<h2>1. 檔案註冊 (POST /api/v1/register)</h2>
<p>將新影片或音訊檔案註冊到系統,觸發後續處理流程。</p>
<div class="api-tester">
<div class="input-group">
<label for="register_path">檔案路徑:</label>
<input type="text" id="register_path" placeholder="/path/to/video.mp4">
<button class="btn btn-primary" onclick="registerFile()">註冊</button>
</div>
<div class="response-panel" id="register_result"></div>
</div>
<div class="quick-actions">
<h4>快速測試:</h4>
<button class="btn btn-secondary" onclick="document.getElementById('register_path').value='/Users/accusys/Videos/test1.mp4'">測試影片 1</button>
<button class="btn btn-secondary" onclick="document.getElementById('register_path').value='/Users/accusys/Videos/test2.mp4'">測試影片 2</button>
</div>
</div>
<!-- 操作 2: 身份綁定 -->
<div class="api-section">
<h2>2. 身份綁定 (POST /api/v1/identities/bind)</h2>
<p>將臉部檢測綁定到特定身份,建立臉部與身份的關聯。</p>
<div class="api-tester">
<div class="input-group">
<label for="bind_face_id">Face ID:</label>
<input type="text" id="bind_face_id" placeholder="face_100">
<label for="bind_identity_uuid">Identity UUID:</label>
<input type="text" id="bind_identity_uuid" placeholder="a9a90105-...">
<button class="btn btn-primary" onclick="bindFace()">綁定</button>
</div>
<div class="response-panel" id="bind_result"></div>
</div>
</div>
<!-- 操作 3: 處理觸發 -->
<div class="api-section">
<h2>3. 處理觸發 (POST /api/v1/files/:uuid/process)</h2>
<p>手動觸發檔案的處理流程,指定要執行的處理器。</p>
<div class="api-tester">
<div class="input-group">
<label for="process_uuid">File UUID:</label>
<input type="text" id="process_uuid" placeholder="輸入 file_uuid">
<div class="checkbox-group">
<label><input type="checkbox" value="asr" checked> ASR</label>
<label><input type="checkbox" value="yolo" checked> YOLO</label>
<label><input type="checkbox" value="face" checked> Face</label>
<label><input type="checkbox" value="ocr"> OCR</label>
<label><input type="checkbox" value="pose"> Pose</label>
<label><input type="checkbox" value="cut" checked> CUT</label>
</div>
<button class="btn btn-primary" onclick="triggerProcess()">觸發處理</button>
</div>
<div class="response-panel" id="process_result"></div>
</div>
</div>
<!-- 操作 4: 身份合併 -->
<div class="api-section">
<h2>4. 身份合併 (POST /api/v1/identities/merge)</h2>
<p>將多個身份合併為單一身份,用於處理重複的身份記錄。</p>
<div class="api-tester">
<div class="input-group">
<label for="merge_target">目標 Identity UUID:</label>
<input type="text" id="merge_target" placeholder="保留的身份 UUID">
<label for="merge_sources">來源 Identity UUIDs:</label>
<input type="text" id="merge_sources" placeholder="uuid1,uuid2,uuid3 (逗號分隔)">
<button class="btn btn-primary" onclick="mergeIdentities()">合併</button>
</div>
<div class="response-panel" id="merge_result"></div>
</div>
</div>
<!-- 操作 5: 處理器重試 -->
<div class="api-section">
<h2>5. 處理器重試 (POST /api/v1/jobs/:uuid/retry)</h2>
<p>重試失敗的處理器,可選擇重試全部或特定處理器。</p>
<div class="api-tester">
<div class="input-group">
<label for="retry_uuid">File UUID:</label>
<input type="text" id="retry_uuid" placeholder="輸入 file_uuid">
<select id="retry_processor">
<option value="all">全部</option>
<option value="asr">ASR</option>
<option value="yolo">YOLO</option>
<option value="face">Face</option>
<option value="ocr">OCR</option>
</select>
<button class="btn btn-primary" onclick="retryProcessor()">重試</button>
</div>
<div class="response-panel" id="retry_result"></div>
</div>
</div>
</div>
<style>
.checkbox-group {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.875rem;
}
.quick-actions {
margin-top: 1rem;
padding: 0.75rem;
background: #f8fafc;
border-radius: 4px;
}
.quick-actions h4 {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
color: var(--text-secondary);
}
.quick-actions .btn-secondary {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.operation-log {
margin-top: 1rem;
padding: 1rem;
background: #1e293b;
color: #e2e8f0;
border-radius: 4px;
font-family: monospace;
font-size: 0.75rem;
max-height: 300px;
overflow-y: auto;
}
.operation-log .log-entry {
margin-bottom: 0.25rem;
}
.operation-log .log-time {
color: #64748b;
}
.operation-log .log-success {
color: #22c55e;
}
.operation-log .log-error {
color: #ef4444;
}
</style>
<script>
const API_BASE = 'http://localhost:3002/api/v1';
function showResult(elementId, data) {
const el = document.getElementById(elementId);
el.textContent = JSON.stringify(data, null, 2);
el.classList.add('has-content');
}
function showError(elementId, message) {
const el = document.getElementById(elementId);
el.textContent = 'Error: ' + message;
el.classList.add('has-content');
el.style.color = '#ef4444';
}
async function registerFile() {
const path = document.getElementById('register_path').value.trim();
if (!path) return alert('請輸入檔案路徑');
try {
const res = await fetch(`${API_BASE}/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_path: path })
});
const data = await res.json();
showResult('register_result', data);
} catch (e) {
showError('register_result', e.message);
}
}
async function bindFace() {
const faceId = document.getElementById('bind_face_id').value.trim();
const identityUuid = document.getElementById('bind_identity_uuid').value.trim();
if (!faceId || !identityUuid) return alert('請輸入 Face ID 和 Identity UUID');
try {
const res = await fetch(`${API_BASE}/identities/bind`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ face_id: faceId, identity_uuid: identityUuid })
});
const data = await res.json();
showResult('bind_result', data);
} catch (e) {
showError('bind_result', e.message);
}
}
async function triggerProcess() {
const uuid = document.getElementById('process_uuid').value.trim();
if (!uuid) return alert('請輸入 file_uuid');
const processors = Array.from(document.querySelectorAll('.checkbox-group input:checked'))
.map(cb => cb.value);
try {
const res = await fetch(`${API_BASE}/files/${uuid}/process`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ processors })
});
const data = await res.json();
showResult('process_result', data);
} catch (e) {
showError('process_result', e.message);
}
}
async function mergeIdentities() {
const target = document.getElementById('merge_target').value.trim();
const sources = document.getElementById('merge_sources').value.trim();
if (!target || !sources) return alert('請輸入目標和來源 Identity UUIDs');
try {
const res = await fetch(`${API_BASE}/identities/merge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
target_uuid: target,
source_uuids: sources.split(',').map(s => s.trim())
})
});
const data = await res.json();
showResult('merge_result', data);
} catch (e) {
showError('merge_result', e.message);
}
}
async function retryProcessor() {
const uuid = document.getElementById('retry_uuid').value.trim();
const processor = document.getElementById('retry_processor').value;
if (!uuid) return alert('請輸入 file_uuid');
try {
const res = await fetch(`${API_BASE}/jobs/${uuid}/retry`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ processor: processor === 'all' ? null : processor })
});
const data = await res.json();
showResult('retry_result', data);
} catch (e) {
showError('retry_result', e.message);
}
}
</script>
<?php get_footer(); ?>