Core Changes: - Fix SearchView to use start_frame/end_frame directly (no time*fps conversion) - Add hard_delete support to delete_trace API - VideoPlayer: Main timeline + Mark system foundation - Proxy: Add local routes for auth, media, identity-matches, cluster-results - Add .gitignore to exclude build artifacts and dependencies Design Documents: - Multi-track Mark system design (.opencode/plans/) - Video editing positioning standards research Files Modified: - src/views/SearchView.vue: Frame positioning, ensureMinDuration (240 frames) - src/views/PeopleView.vue: batchDeleteGroups with hard_delete - src/api/index.ts: delete_trace with hard_delete body - src/components/VideoPlayer.vue: Timeline + Mark UI - src-tauri/src/proxy.rs: New local routes - AGENTS.md: Update documentation
10 KiB
10 KiB
Core API Face Groups Endpoint - 技術建議書
日期: 2026-07-19
提出團隊: Momentry Studio
狀態: 建議中
一、背景
1.1 問題描述
Momentry Studio Proxy 當前實現以下 endpoints:
| Endpoint | 當前行為 | 用途 |
|---|---|---|
/api/v1/processor-json |
讀取本地 {file_hash}.{processor}.json |
返回 processor 輸出(asrx, ocr, face 等) |
/api/v1/cluster-results |
讀取本地 {file_hash}.cluster_result.json |
返回 face 分組結果 |
問題:
- 遠端瀏覽器訪問
studio.momentry.ddns.net時,無法獲取本地文件數據 - Proxy 應該作為 forward 層,將請求轉發到 Core API
1.2 現有 Core API Endpoints
| Endpoint | 狀態 | 說明 |
|---|---|---|
POST /api/v1/file/:file_uuid/json/:processor |
✅ 已存在 | 返回 processor JSON 數據 |
POST /api/v1/file/:file_uuid/tkg/nodes |
✅ 已存在 | 查詢 TKG nodes |
GET /api/v1/trace-profile |
✅ 已存在 | 查詢單一 trace profile |
PUT /api/v1/trace-profile |
✅ 已存在 | 更新 trace profile(含名稱) |
PUT /api/v1/trace-profile/group |
✅ 已存在 | 批量更新 trace 名稱 |
GET /api/v1/file/:file_uuid/cluster-results |
❌ 不存在 | 需要新增 |
二、架構理解
2.1 Face Group 概念
定義:
- Face Group = 多個 face traces 共享同一個名稱(label)
- 名稱來自 TKG
face_tracenode 的label欄位 - 名稱可被用戶覆蓋(overridable)
技術實現:
{
"node_type": "face_trace",
"label": "Cary Grant", // ← 可被用戶覆蓋
"properties": {
"trace_id": 9,
"face_count": 142,
"avg_confidence": 0.87
}
}
2.2 Cluster 與 Face Group 的關係
核心觀點:
- Cluster 是技術手段:用於將多個 face traces 分組
- Face Group 是語義概念:多個 traces 共享同一個名稱
- Cluster ID 是技術編號:無業務語義,僅用於識別分組
命名流程:
用戶修改名稱
→ Studio Frontend
→ Studio Proxy
→ Core API: PUT /api/v1/trace-profile
→ 更新 TKG node 的 label
三、建議方案
3.1 Processor JSON Endpoint
建議:使用 Core API 現有 endpoint
Studio Proxy 實現:
GET /api/v1/processor-json?file_hash={uuid}&processor={name}
→ Forward to Core API:
POST /api/v1/file/{uuid}/json/{name}?api_key={API_KEY}
Core API 需要做的:無需修改
3.2 Face Groups Endpoint(建議新增)
選項 A:以 Cluster 格式返回
Endpoint:
GET /api/v1/file/:file_uuid/cluster-results
響應格式:
{
"success": true,
"file_uuid": "d3f9ae8e471a1fc4d47022c66091b920",
"clusters": [
{
"cluster_id": 1,
"name": "Cary Grant",
"trace_ids": [9, 6, 8],
"trace_count": 3,
"representative_trace": 9
},
{
"cluster_id": 2,
"name": "Audrey Hepburn",
"trace_ids": [1, 11, 4, 10],
"trace_count": 4,
"representative_trace": 1
}
],
"total_clusters": 2
}
選項 B:以 Face Groups 格式返回(推薦)
Endpoint:
GET /api/v1/file/:file_uuid/face-groups
響應格式:
{
"success": true,
"file_uuid": "d3f9ae8e471a1fc4d47022c66091b920",
"face_groups": [
{
"group_id": 1,
"name": "Cary Grant",
"trace_ids": [9, 6, 8],
"trace_count": 3,
"representative_trace": 9,
"editable": true,
"total_face_count": 425
},
{
"group_id": 2,
"name": "Audrey Hepburn",
"trace_ids": [1, 11, 4, 10],
"trace_count": 4,
"representative_trace": 1,
"editable": true,
"total_face_count": 380
}
],
"total_groups": 2,
"unassigned_traces": [5, 7]
}
推薦理由:
- 語義更清晰(Face Group vs Cluster)
- 與命名機制對應
- 擴展性更好(可加入
editable,total_face_count等屬性) - 可區分已命名和未命名的 traces
3.3 實現邏輯(建議)
# Pseudocode
def get_face_groups(file_uuid: str):
# 1. 查詢 TKG nodes
response = post(
f"{CORE_API}/api/v1/file/{file_uuid}/tkg/nodes",
json={"node_type": "face_trace", "page_size": 500}
)
nodes = response.json()["nodes"]
# 2. 按 label 分組
groups = {}
unassigned = []
for node in nodes:
trace_id = node["properties"]["trace_id"]
label = node["label"]
face_count = node["properties"].get("face_count", 0)
# 判斷是否為預設名稱(未命名)
if label.startswith("Face Trace ") or label.startswith("Trace "):
unassigned.append(trace_id)
continue
if label not in groups:
groups[label] = {
"trace_ids": [],
"total_face_count": 0
}
groups[label]["trace_ids"].append(trace_id)
groups[label]["total_face_count"] += face_count
# 3. 轉換為輸出格式
result = []
for idx, (name, data) in enumerate(sorted(groups.items()), 1):
result.append({
"group_id": idx,
"name": name,
"trace_ids": data["trace_ids"],
"trace_count": len(data["trace_ids"]),
"representative_trace": data["trace_ids"][0],
"editable": True,
"total_face_count": data["total_face_count"]
})
return {
"success": True,
"file_uuid": file_uuid,
"face_groups": result,
"total_groups": len(result),
"unassigned_traces": unassigned
}
四、設計決策問題
4.1 Endpoint 命名
問題:
- 使用
/cluster-results(現有概念)? - 使用
/face-groups(語義更清晰)?
建議:使用 /face-groups,理由:
- 語義更準確(名稱可被覆蓋,不是固定的 cluster)
- 與
trace-profileendpoint 命名一致 - 未來可以擴展(如
/face-groups/:name查詢單一 group)
4.2 未命名 Traces 處理
場景:
- 新創建的 face_trace,
label為預設值(如"Face Trace 9") - 用戶尚未命名
建議選項:
| 選項 | 處理方式 | 優點 | 缺點 |
|---|---|---|---|
| A | 歸類為 unassigned_traces 列表 |
清晰區分已命名/未命名 | 需要額外欄位 |
| B | 每個未命名 trace 作為獨立 group | 格式統一 | 可能產生大量 group |
| C | 不返回未命名 traces | 響應簡潔 | 遺失數據 |
建議:選項 A,返回 unassigned_traces 列表
4.3 Cluster ID / Group ID 的必要性
問題:
cluster_id或group_id是否需要?- 如果只是順序編號,每次查詢可能不同
建議選項:
| 選項 | 實現方式 | 優點 | 缺點 |
|---|---|---|---|
| A | 使用 name 作為唯一標識 |
語義清晰,無需額外 ID | 名稱變更時引用會失效 |
| B | 生成固定的 group_id(如 UUID) |
ID 固定,不受名稱變更影響 | 需要額外存儲 |
| C | 每次查詢時動態生成順序編號 | 實現簡單 | 編號可能變化 |
建議:選項 C,動態生成順序編號(因為 Cluster ID 只是技術手段)
4.4 分頁支持
問題:
- 如果一個文件有大量 face traces(如超過 500),如何處理?
建議:
- 支持分頁參數
page和page_size - 默認
page_size=100,最大500
4.5 性能考慮
問題:
- TKG nodes 查詢是否需要緩存?
建議:
- Core API 可以在內部緩存 TKG nodes 查詢結果
- 緩存時效:5-10 分鐘(或直到有 trace-profile 更新)
五、Studio Proxy 職責
5.1 Processor JSON Handler
修改前:
// 讀取本地文件
let path = format!("{}/{}.{}.json", output_dir, file_hash, processor);
let content = std::fs::read_to_string(&path)?;
修改後:
// Forward 到 Core API
let url = format!("{}/api/v1/file/{}/json/{}?api_key={}",
CORE_API, file_hash, processor, API_KEY);
let response = client.post(&url).send().await?;
5.2 Cluster Results / Face Groups Handler
修改前:
// 讀取本地文件
let path = format!("{}/{}/{}.cluster_result.json", base, file_hash, file_hash);
let content = std::fs::read_to_string(&path)?;
修改後:
// Forward 到 Core API
let url = format!("{}/api/v1/file/{}/face-groups?api_key={}",
CORE_API, file_hash, API_KEY);
let response = client.get(&url).send().await?;
5.3 共享邏輯
建議抽取共享函數:
async fn call_core_api(url: &str, method: Method, body: Option<Value>) -> Response {
let client = reqwest::Client::new();
// 統一的錯誤處理
// 統一的響應轉換
}
六、數據流向
6.1 當前流程(本地文件)
Studio Frontend
↓
Studio Proxy
↓
本地文件系統 → {file_hash}.{processor}.json
↓
返回數據
問題:遠端瀏覽器無法訪問本地文件
6.2 建議流程(Forward 到 Core API)
Studio Frontend
↓
Studio Proxy (forward)
↓
Core API
├── POST /file/{uuid}/json/{processor} (已有)
└── GET /file/{uuid}/face-groups (新增)
↓
返回數據
優點:遠端瀏覽器可正常訪問
七、請求反饋
7.1 請 Core API 團隊確認
-
Endpoint 選擇:
/cluster-results或/face-groups?
-
未命名 traces:
- 是否同意返回
unassigned_traces列表?
- 是否同意返回
-
Group ID:
- 是否同意使用動態生成的順序編號?
- 或需要固定的 UUID?
-
分頁:
- 是否需要支持?
-
性能:
- 是否需要內部緩存?
7.2 Studio 團隊負責
-
修改 Proxy handlers:
get_processor_json_handler→ Forward 到 Core APIget_cluster_results_handler→ Forward 到 Core API
-
抽取共享邏輯:
- 統一 Core API 客戶端
- 統一錯誤處理
八、附錄
A. 相關 Core API Endpoints
| Endpoint | 文檔位置 |
|---|---|
| Processor JSON | docs_v1.0/doc_wasm/modules/04_lookup.md |
| TKG Nodes | docs_v1.0/doc_wasm/modules/15_tkg.md |
| Trace Profile | docs_v1.0/doc_wasm/modules/18_profile.md |
| Progress | docs_v1.0/doc_wasm/modules/17_progress.md |
B. Studio 相關代碼
| 文件 | 說明 |
|---|---|
src-tauri/src/proxy.rs |
Proxy handlers |
src/views/PeopleView.vue |
Face 頁面 |
src/store.ts |
數據加載邏輯 |
以上為 Studio 團隊提出的技術建議,歡迎 Core API 團隊討論與反饋。