206 lines
7.3 KiB
Markdown
206 lines
7.3 KiB
Markdown
# 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 + 提交
|