diff --git a/docs/PEOPLE_DESIGN.md b/docs/PEOPLE_DESIGN.md new file mode 100644 index 0000000..6bab29c --- /dev/null +++ b/docs/PEOPLE_DESIGN.md @@ -0,0 +1,205 @@ +# 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, + 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, + file_uuid: String, + frame_number: i64, + confidence: f64, +} + +struct SearchIdentityResult { + identity_id: i64, + name: String, + source: String, + tmdb_id: Option, + file_uuid: Option, + start_time: f64, + end_time: f64, + text_content: Option, +} +``` + +--- + +## 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` | +| `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` | + +**注意: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 (共用元件) + └──