docs: People 頁面設計文件,基於實測 API 定義
This commit is contained in:
205
docs/PEOPLE_DESIGN.md
Normal file
205
docs/PEOPLE_DESIGN.md
Normal file
@@ -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<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 完整 UI(grid + modal + tabs + actions + 搜尋 + 合併 + 綁定)
|
||||||
|
5. **Phase 5**: 更新 AGENTS.md + 提交
|
||||||
Reference in New Issue
Block a user