- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
340 lines
10 KiB
PHP
340 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Template Name: API Demo - 查詢 (Query)
|
|
* Description: Demonstrates file and identity query 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 active">查詢</a>
|
|
<a href="/api-demo-display/" class="nav-item">展示</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. 檔案查詢 (GET /api/v1/files/:uuid)</h2>
|
|
<p>透過 file_uuid 查詢檔案的完整資訊,包含元數據、處理狀態、分類標籤等。</p>
|
|
|
|
<div class="api-tester">
|
|
<div class="input-group">
|
|
<label for="file_uuid">File UUID:</label>
|
|
<input type="text" id="file_uuid" placeholder="輸入 file_uuid 或從下方列表選擇">
|
|
<button class="btn btn-primary" onclick="queryFile()">查詢</button>
|
|
</div>
|
|
<div class="response-panel" id="file_query_result"></div>
|
|
</div>
|
|
|
|
<div class="file-list-panel">
|
|
<h3>最近註冊的檔案</h3>
|
|
<button class="btn btn-secondary" onclick="loadRecentFiles()">載入列表</button>
|
|
<div id="recent_files_list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查詢 2: 身份查詢 -->
|
|
<div class="api-section">
|
|
<h2>2. 身份查詢 (GET /api/v1/identities/:uuid)</h2>
|
|
<p>查詢跨檔案的全域身份資訊,包含關聯的檔案、臉部特徵、品質分數等。</p>
|
|
|
|
<div class="api-tester">
|
|
<div class="input-group">
|
|
<label for="identity_uuid">Identity UUID:</label>
|
|
<input type="text" id="identity_uuid" placeholder="輸入 identity_uuid">
|
|
<button class="btn btn-primary" onclick="queryIdentity()">查詢</button>
|
|
</div>
|
|
<div class="response-panel" id="identity_query_result"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查詢 3: 處理狀態 -->
|
|
<div class="api-section">
|
|
<h2>3. 處理狀態查詢 (GET /api/v1/jobs/:uuid/status)</h2>
|
|
<p>查詢檔案的處理進度與各處理器狀態 (ASR, YOLO, Face, OCR 等)。</p>
|
|
|
|
<div class="api-tester">
|
|
<div class="input-group">
|
|
<label for="job_uuid">File UUID (Job):</label>
|
|
<input type="text" id="job_uuid" placeholder="輸入 file_uuid 查詢處理狀態">
|
|
<button class="btn btn-primary" onclick="queryJobStatus()">查詢</button>
|
|
</div>
|
|
<div class="response-panel" id="job_status_result"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查詢 4: 遷移歷史 -->
|
|
<div class="api-section">
|
|
<h2>4. 檔案遷移歷史 (GET /api/v1/files/:uuid/history)</h2>
|
|
<p>查詢檔案因移動而產生的身份變更鏈,追蹤 parent_uuid 關聯。</p>
|
|
|
|
<div class="api-tester">
|
|
<div class="input-group">
|
|
<label for="history_uuid">File UUID:</label>
|
|
<input type="text" id="history_uuid" placeholder="輸入 file_uuid 查詢歷史">
|
|
<button class="btn btn-primary" onclick="queryFileHistory()">查詢</button>
|
|
</div>
|
|
<div class="response-panel" id="file_history_result"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 查詢 5: 語義搜尋 -->
|
|
<div class="api-section">
|
|
<h2>5. 語義搜尋 (POST /api/v1/search)</h2>
|
|
<p>使用自然語言搜尋相關的影片片段或身份。</p>
|
|
|
|
<div class="api-tester">
|
|
<div class="input-group">
|
|
<label for="search_query">搜尋查詢:</label>
|
|
<input type="text" id="search_query" placeholder="例如: 戴眼鏡的男性在說話">
|
|
<select id="search_type">
|
|
<option value="chunk">片段搜尋</option>
|
|
<option value="identity">身份搜尋</option>
|
|
<option value="face">臉部搜尋</option>
|
|
</select>
|
|
<button class="btn btn-primary" onclick="semanticSearch()">搜尋</button>
|
|
</div>
|
|
<div class="response-panel" id="search_result"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.api-nav {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
padding: 1rem;
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.api-nav .nav-item {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 4px;
|
|
text-decoration: none;
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
.api-nav .nav-item.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
}
|
|
.api-section {
|
|
background: var(--card-background);
|
|
border-radius: var(--border-radius);
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
box-shadow: var(--shadow);
|
|
}
|
|
.api-section h2 {
|
|
margin-top: 0;
|
|
color: var(--primary-color);
|
|
}
|
|
.api-tester {
|
|
margin: 1rem 0;
|
|
}
|
|
.input-group {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
.input-group label {
|
|
font-weight: 500;
|
|
min-width: 120px;
|
|
}
|
|
.input-group input,
|
|
.input-group select {
|
|
padding: 0.5rem;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
flex: 1;
|
|
min-width: 200px;
|
|
}
|
|
.btn-primary {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
padding: 0.5rem 1rem;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.btn-secondary {
|
|
background: var(--secondary-color);
|
|
color: white;
|
|
padding: 0.5rem 1rem;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.response-panel {
|
|
margin-top: 1rem;
|
|
padding: 1rem;
|
|
background: #f1f5f9;
|
|
border-radius: 4px;
|
|
font-family: monospace;
|
|
font-size: 0.75rem;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
display: none;
|
|
}
|
|
.response-panel.has-content {
|
|
display: block;
|
|
}
|
|
.file-list-panel {
|
|
margin-top: 1rem;
|
|
}
|
|
.file-item {
|
|
padding: 0.75rem;
|
|
background: #f8fafc;
|
|
border-radius: 4px;
|
|
margin-bottom: 0.5rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.file-item:hover {
|
|
background: #e2e8f0;
|
|
}
|
|
.file-item .uuid {
|
|
font-family: monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--primary-color);
|
|
}
|
|
.file-item .name {
|
|
font-weight: 500;
|
|
}
|
|
.badge-status {
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
.badge-status.pending { background: #fef3c7; color: #92400e; }
|
|
.badge-status.processing { background: #dbeafe; color: #1e40af; }
|
|
.badge-status.completed { background: #d1fae5; color: #065f46; }
|
|
.badge-status.error { background: #fee2e2; color: #991b1b; }
|
|
</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 queryFile() {
|
|
const uuid = document.getElementById('file_uuid').value.trim();
|
|
if (!uuid) return alert('請輸入 file_uuid');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/files/${uuid}`);
|
|
const data = await res.json();
|
|
showResult('file_query_result', data);
|
|
} catch (e) {
|
|
showError('file_query_result', e.message);
|
|
}
|
|
}
|
|
|
|
async function loadRecentFiles() {
|
|
const el = document.getElementById('recent_files_list');
|
|
el.innerHTML = '<p>載入中...</p>';
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/files?limit=10&sort=created_at&order=desc`);
|
|
const data = await res.json();
|
|
|
|
if (data.files && data.files.length > 0) {
|
|
el.innerHTML = data.files.map(f => `
|
|
<div class="file-item" onclick="document.getElementById('file_uuid').value='${f.file_uuid}'; queryFile();">
|
|
<span class="name">${f.file_name}</span>
|
|
<span class="uuid">${f.file_uuid}</span>
|
|
<span class="badge-status ${f.status}">${f.status || 'unknown'}</span>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
el.innerHTML = '<p>沒有找到檔案</p>';
|
|
}
|
|
} catch (e) {
|
|
el.innerHTML = '<p style="color:#ef4444">載入失敗: ' + e.message + '</p>';
|
|
}
|
|
}
|
|
|
|
async function queryIdentity() {
|
|
const uuid = document.getElementById('identity_uuid').value.trim();
|
|
if (!uuid) return alert('請輸入 identity_uuid');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/identities/${uuid}`);
|
|
const data = await res.json();
|
|
showResult('identity_query_result', data);
|
|
} catch (e) {
|
|
showError('identity_query_result', e.message);
|
|
}
|
|
}
|
|
|
|
async function queryJobStatus() {
|
|
const uuid = document.getElementById('job_uuid').value.trim();
|
|
if (!uuid) return alert('請輸入 file_uuid');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/jobs/${uuid}/status`);
|
|
const data = await res.json();
|
|
showResult('job_status_result', data);
|
|
} catch (e) {
|
|
showError('job_status_result', e.message);
|
|
}
|
|
}
|
|
|
|
async function queryFileHistory() {
|
|
const uuid = document.getElementById('history_uuid').value.trim();
|
|
if (!uuid) return alert('請輸入 file_uuid');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/files/${uuid}/history`);
|
|
const data = await res.json();
|
|
showResult('file_history_result', data);
|
|
} catch (e) {
|
|
showError('file_history_result', e.message);
|
|
}
|
|
}
|
|
|
|
async function semanticSearch() {
|
|
const query = document.getElementById('search_query').value.trim();
|
|
const type = document.getElementById('search_type').value;
|
|
if (!query) return alert('請輸入搜尋查詢');
|
|
|
|
try {
|
|
const res = await fetch(`${API_BASE}/search`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ query, type })
|
|
});
|
|
const data = await res.json();
|
|
showResult('search_result', data);
|
|
} catch (e) {
|
|
showError('search_result', e.message);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<?php get_footer(); ?>
|