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