Files
momentry_studio/docs/PEOPLE_DESIGN.md

206 lines
7.3 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.
# People 頁面設計文件
## 1. 分層架構
| 層級 | 負責 |
|---|---|
| **Rust (Backend)** | API 代理、檔案系統存取、API key 管理 |
| **Vue (Frontend)** | UI 渲染、狀態管理、使用者交互 |
---
## 2. Core API 端點(已實測驗證)
詳見 `CORE_API_REFERENCE.md`。以下摘要 People 頁面使用的端點:
| 功能 | Method | 端點 | Response Key | 備註 |
|---|---|---|---|---|
| 列出人物 | GET | `/api/v1/identities` | `identities` | 含 metadata.starred |
| 人物詳情 | GET | `/api/v1/identity/{uuid}` | 頂層 | 含 tmdb_profile 路徑 |
| 搜尋人物 | GET | `/api/v1/identities/search?q=` | `results` | 欄位不同於 list |
| 大頭照 | — | 本地檔案讀取 | — | 非 Core API |
| 人臉列表 | GET | `/api/v1/identity/{uuid}/faces` | `data` | **不是** `faces` |
| 追蹤列表 | GET | `/api/v1/identity/{uuid}/traces` | `traces` | |
| 改名 | PATCH | `/api/v1/identity/{uuid}` | 頂層 | Body: `{"name": "..."}` |
| 刪除 | DELETE | `/api/v1/identity/{uuid}` | — | 204/404 |
| 建立 | POST | `/api/v1/identity` | — | 需要 face_json_path |
| 合併 | POST | `/api/v1/identity/{uuid}/mergeinto` | — | Body: `{"into_uuid": "..."}` |
| 綁定 | POST | `/api/v1/identity/{uuid}/bind` | — | Body: `{"face_id": "string", "file_uuid": "..."}` |
| 解綁 | POST | `/api/v1/identity/{uuid}/unbind` | — | Body: 同上 |
| 候選人臉 | GET | `/api/v1/faces/candidates` | `candidates` | |
| 縮圖 | GET | `/api/v1/file/{uuid}/thumbnail` | Binary | |
| 影片 | GET | `/api/v1/file/{uuid}/video` | Binary | |
---
## 3. Rust Structs 定義
```rust
struct PersonInfo {
identity_uuid: String,
name: String,
starred: bool, // 從 metadata.starred 提取
}
struct FaceInfo {
id: i64,
file_uuid: String,
frame_number: i64,
timestamp_secs: f64,
face_id: Option<String>,
confidence: f64,
}
struct TraceInfo {
trace_id: i64,
file_uuid: String,
frame_count: i64,
first_sec: f64,
last_sec: f64,
avg_confidence: f64,
}
struct FaceCandidate {
id: i64,
face_id: Option<String>,
file_uuid: String,
frame_number: i64,
confidence: f64,
}
struct SearchIdentityResult {
identity_id: i64,
name: String,
source: String,
tmdb_id: Option<i64>,
file_uuid: Option<String>,
start_time: f64,
end_time: f64,
text_content: Option<String>,
}
```
---
## 4. Rust Commands 清單
### 4.1 現有(需修改)
| Command | 修改內容 |
|---|---|
| `get_people` | PersonInfo 加 `starred: bool`,解析 metadata.starred |
| `get_faces` | 解析 `data` key 而非 `faces`FaceInfo 加 timestamp_secs |
| `get_video_stream` | **重構**:寫入 temp file → 回傳 `file://` URL |
### 4.2 現有(不變)
| Command | 行為 |
|---|---|
| `get_traces` | `GET /identity/{uuid}/traces` |
| `get_identity_profile` | 讀本地 profile.jpg → base64 |
| `update_identity_name` | `PATCH /identity/{uuid}` |
| `delete_identity` | `DELETE /identity/{uuid}` |
| `get_thumbnail` | 抓縮圖 → base64 |
### 4.3 新增
| Command | Core API | Body/Params | 回傳 |
|---|---|---|---|
| `search_identities` | `GET /identities/search?q=...` | `query: String, limit: usize` | `Vec<SearchIdentityResult>` |
| `merge_identities` | `POST /identity/{uuid}/mergeinto` | `uuid: String, into_uuid: String` | `()` |
| `bind_face` | `POST /identity/{uuid}/bind` | `uuid, face_id: String, file_uuid: String` | `()` |
| `unbind_face` | `POST /identity/{uuid}/unbind` | `uuid, face_id: String, file_uuid: String` | `()` |
| `get_face_candidates` | `GET /faces/candidates` | `page: usize, per_page: usize` | `Vec<FaceCandidate>` |
**注意create_identity 需要 face_json_path不在 People 頁面直接使用。**
---
## 5. Vue 前端元件樹
```
PeopleView.vue
├── Toolbar
│ ├── h1 "People"
│ ├── 搜尋框 → debounce 300ms → invoke('search_identities')
│ └── 建立按鈕暫留create 需要 face_json_path
├── PeopleGrid
│ └── PersonCard × N (v-for filteredPeople)
│ ├── 大頭照 → invoke('get_identity_profile') cache
│ ├── 名稱
│ ├── ⭐ 星號 (starred)
│ └── @click → selectPerson()
├── DetailModal
│ ├── Header: 大頭照 + 可編輯名稱 + UUID
│ ├── FacesTab (橫向捲軸)
│ │ └── face × N → confidence %
│ ├── TracesTab (縱向列表)
│ │ └── trace × N → 時間 + 信心% → @click → VideoPlayer
│ └── ActionsTab
│ ├── Delete → confirm + invoke('delete_identity')
│ ├── Merge → 搜尋目標 + invoke('merge_identities')
│ ├── Bind Face → 從 Candidates 選取 → invoke('bind_face')
│ └── Unbind Face → invoke('unbind_face')
├── CandidatesModal
│ └── FaceCandidate × N → @click → bind_face()
└── VideoPlayer (共用元件)
└── <video :src="videoSrc"> (file:// URL from temp file)
```
---
## 6. CSS 設計系統
### 色彩
| 用途 | 色碼 |
|---|---|
| Primary | `#2563eb` |
| Secondary | `#64748b` |
| Success | `#22c55e` |
| Warning | `#f59e0b` |
| Error | `#ef4444` |
| Card BG | `#ffffff` |
| Text Primary | `#1e293b` |
| Text Secondary | `#64748b` |
### 元件樣式
| 元件 | 樣式 |
|---|---|
| Grid | `repeat(auto-fill, minmax(100px, 1fr))` gap 16px |
| Card | bg #fff, border 1px #e5e7eb, radius 12px, padding 16px, hover border #4f46e5 |
| Avatar | 56px 圓形, bg #eef2ff, color #4f46e5 |
| Modal | fixed inset:0, bg rgba(0,0,0,.6), centered, max-w 700px, max-h 80vh |
| VideoPlayer | fixed inset:0, bg rgba(0,0,0,.85), video max-w 80vw max-h 60vh, radius 16px |
---
## 7. 資料流
```
事件 Vue 呼叫 Rust Command
───────────────── ────────────── ────────────
頁面載入 invoke('get_people') get_people()
點擊人物 invoke('get_faces') get_faces()
invoke('get_traces') get_traces()
invoke('get_identity_profile') get_identity_profile()
搜尋輸入(debounce) invoke('search_identities') search_identities()
編輯名稱 invoke('update_identity_name') update_identity_name()
刪除人物 invoke('delete_identity') delete_identity()
合併人物 invoke('merge_identities') merge_identities()
綁定人臉 invoke('bind_face') bind_face()
解綁人臉 invoke('unbind_face') unbind_face()
載入候選人臉 invoke('get_face_candidates') get_face_candidates()
播放影片 invoke('get_video_stream') get_video_stream() → temp file
```
---
## 8. 實作順序
1. **Phase 1**: 修改 `PersonInfo` + `FaceInfo` struct + 重構 `get_video_stream` (temp file)
2. **Phase 2**: 新增 `search_identities` + `get_face_candidates`
3. **Phase 3**: 新增 `merge_identities` + `bind_face` + `unbind_face`
4. **Phase 4**: Vue PeopleView 完整 UIgrid + modal + tabs + actions + 搜尋 + 合併 + 綁定)
5. **Phase 5**: 更新 AGENTS.md + 提交