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:
339
themes/momentry/page-api-demo-query.php
Normal file
339
themes/momentry/page-api-demo-query.php
Normal file
@@ -0,0 +1,339 @@
|
||||
<?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(); ?>
|
||||
Reference in New Issue
Block a user