Files
momentry_studio/docs/proposals/core-api-face-groups-endpoint.md
Momentry Studio 5951aca086 feat: frame-based positioning and mark system foundation
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
2026-07-24 20:19:47 +08:00

451 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_trace` node 的 `label` 欄位
- 名稱可被用戶覆蓋overridable
**技術實現**
```json
{
"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
```
**響應格式**
```json
{
"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
```
**響應格式**
```json
{
"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 實現邏輯(建議)
```python
# 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-profile` endpoint 命名一致
- 未來可以擴展(如 `/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
**修改前**
```rust
// 讀取本地文件
let path = format!("{}/{}.{}.json", output_dir, file_hash, processor);
let content = std::fs::read_to_string(&path)?;
```
**修改後**
```rust
// 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
**修改前**
```rust
// 讀取本地文件
let path = format!("{}/{}/{}.cluster_result.json", base, file_hash, file_hash);
let content = std::fs::read_to_string(&path)?;
```
**修改後**
```rust
// 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 共享邏輯
**建議抽取共享函數**
```rust
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 團隊確認
1. **Endpoint 選擇**
- `/cluster-results``/face-groups`
2. **未命名 traces**
- 是否同意返回 `unassigned_traces` 列表?
3. **Group ID**
- 是否同意使用動態生成的順序編號?
- 或需要固定的 UUID
4. **分頁**
- 是否需要支持?
5. **性能**
- 是否需要內部緩存?
---
### 7.2 Studio 團隊負責
1. 修改 Proxy handlers
- `get_processor_json_handler` → Forward 到 Core API
- `get_cluster_results_handler` → Forward 到 Core API
2. 抽取共享邏輯:
- 統一 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 團隊討論與反饋。**