feat: add face filter components and integration guide

- useFaceFilter composable: manages face filtering state and API calls
- FaceFilterPanel component: UI for filtering controls
- FaceCard component: text-only face display card
- Integration guide: step-by-step guide for integrating into PeopleView

Implements hybrid filtering mode with:
- Instant mode: client-side filtering (5-10ms)
- Backend mode: server-side complex filtering
- Hybrid mode: backend + instant filtering (AND)

Uses actual Core API endpoints:
- GET /file/{uuid}/faces
- POST /file/{uuid}/faces/filter
- GET /file/{uuid}/face/{faceId}/filter-report
- GET /file/{uuid}/filter-stats

Preferences stored in localStorage.
This commit is contained in:
2026-08-09 21:24:52 +08:00
parent 97a5fef0b2
commit 7b60ca3d2d
11 changed files with 1533 additions and 4892 deletions

View File

@@ -0,0 +1,543 @@
# Face Filter Integration Guide for PeopleView
**版本**: v1.0
**日期**: 2026-08-09
**狀態**: 可整合
---
## 概述
本指南說明如何將 Face Filter 功能整合到 PeopleView 中。
---
## 已創建的檔案
### 1. Composable
**位置**: `src/composables/useFaceFilter.ts`
**功能**:
- 管理臉部過濾狀態
- 提供三種過濾模式(instant, backend, hybrid)
- 處理 API 呼叫
- 儲存使用者偏好到 localStorage
**主要 exports**:
```typescript
export function useFaceFilter() {
return {
mode, // 'instant' | 'backend' | 'hybrid'
rules, // QuickFilterRules
allFaces, // FaceMetadata[]
filteredFaces, // FaceMetadata[]
filterResult, // FilterResult | null
filterStats, // FilterStats | null
loading, // boolean
error, // string | null
stats, // computed stats
// Actions
initialize,
updateRules,
setMode,
applyBackendFilter,
getFaceReportData,
refreshStats,
resetToDefault,
applyLocalFilter
};
}
```
---
### 2. Components
#### FaceFilterPanel.vue
**位置**: `src/components/FaceFilterPanel.vue`
**功能**:
- 過濾面板 UI
- 模式選擇(instant/backend/hybrid)
- 過濾規則控制(confidence, bbox_size, source, backend_status)
- 統計顯示
**Props**:
```typescript
{
fileUuid: string // 必需的檔案 UUID
}
```
**使用範例**:
```vue
<FaceFilterPanel :fileUuid="selectedFileUuid" />
```
---
#### FaceCard.vue
**位置**: `src/components/FaceCard.vue`
**功能**:
- 臉部卡片顯示(純文字,無縮圖)
- 狀態標示(accept/reject/review)
- 來源徽章(AV/MP)
- 選擇功能
**Props**:
```typescript
{
face: FaceMetadata,
selected?: boolean,
showCheckbox?: boolean
}
```
**Events**:
```typescript
{
click: [face: FaceMetadata],
select: [face: FaceMetadata]
}
```
**使用範例**:
```vue
<FaceCard
:face="face"
:selected="selectedFaces.includes(face)"
@click="openFaceDetail(face)"
@select="toggleFaceSelection(face)"
/>
```
---
## 整合步驟
### Step 1: 導入 Composable
在 PeopleView.vue 中導入 useFaceFilter:
```vue
<script setup lang="ts">
import { useFaceFilter } from '../composables/useFaceFilter';
const {
mode,
rules,
allFaces,
filteredFaces,
filterStats,
loading: filterLoading,
error: filterError,
stats: filterStats,
initialize,
updateRules,
setMode,
applyBackendFilter,
resetToDefault
} = useFaceFilter();
// ... existing code ...
</script>
```
---
### Step 2: 添加 FaceFilterPanel 組件
在 toolbar 區域添加 FaceFilterPanel:
```vue
<template>
<div class="people-view">
<div class="ms-ppl-control-area">
<!-- Existing toolbar -->
<div class="ms-ppl-toolbar">
<!-- ... existing controls ... -->
</div>
<!-- NEW: Face Filter Panel -->
<FaceFilterPanel
v-if="selectedFileUuid"
:fileUuid="selectedFileUuid"
ref="faceFilterPanel"
/>
</div>
<!-- ... rest of the view ... -->
</div>
</template>
<script setup lang="ts">
import FaceFilterPanel from '../components/FaceFilterPanel.vue';
const faceFilterPanel = ref();
// ... existing code ...
</script>
```
---
### Step 3: 初始化過濾
當選擇檔案時初始化過濾:
```typescript
// 在 onFileFilterChange 或 watch(selectedFileUuid) 中
watch(selectedFileUuid, async (newUuid) => {
if (newUuid) {
await initialize(newUuid);
}
});
```
---
### Step 4: 使用過濾後的臉部
使用 `filteredFaces` 替代原始的臉部列表:
```vue
<!-- 原本的臉部列表 -->
<div v-for="face in allFaces" :key="face.face_id">
<!-- ... -->
</div>
<!-- 改為使用過濾後的臉部 -->
<div v-for="face in filteredFaces" :key="face.face_id">
<FaceCard
:face="face"
@click="openFaceDetail(face)"
/>
</div>
```
---
### Step 5: 整合到現有 UI
#### 選項 A: 獨立面板(推薦)
FaceFilterPanel 作為獨立面板,放在 toolbar 下方:
```vue
<div class="ms-ppl-control-area">
<div class="ms-ppl-toolbar">
<!-- Existing controls -->
</div>
<!-- Face Filter Panel -->
<FaceFilterPanel :fileUuid="selectedFileUuid" />
<!-- Undo Banner -->
<div v-if="showUndoBanner" class="ms-undo-banner">
<!-- ... -->
</div>
</div>
```
#### 選項 B: 整合到現有面板
將過濾控制整合到現有的 Pending Faces 面板:
```vue
<div v-if="showUface" class="ms-ppl-section">
<div class="ms-ppl-section-toolbar">
<div class="ms-ppl-section-title">Pending Faces</div>
<!-- Add filter controls -->
<div class="filter-controls-inline">
<select v-model="rules.source">
<option value="all">All</option>
<option value="apple_vision">AV</option>
<option value="mediapipe">MP</option>
</select>
<input
type="number"
v-model.number="rules.min_confidence"
placeholder="Min confidence"
/>
<button @click="applyBackendFilter(selectedFileUuid)">
Apply Backend
</button>
</div>
</div>
<!-- Face cards -->
<div class="ms-ppl-face-grid">
<FaceCard
v-for="face in filteredFaces"
:key="face.face_id"
:face="face"
/>
</div>
</div>
```
---
## API 端點
所有 API 呼叫都通過 composable 處理,使用 proxy 到 `http://localhost:8888/api/v1/...`
**實際端點**:
1. `GET /api/v1/file/{uuid}/faces` - 獲取臉部元數據
2. `POST /api/v1/file/{uuid}/faces/filter` - 套用後端過濾
3. `GET /api/v1/file/{uuid}/face/{faceId}/filter-report` - 獲取單臉報告
4. `GET /api/v1/file/{uuid}/filter-stats` - 獲取統計
5. `GET/POST /api/v1/file/{uuid}/filter-rules` - 規則 CRUD
---
## 資料結構
### FaceMetadata
```typescript
interface FaceMetadata {
face_id: string;
frame: number;
primary_source: 'apple_vision' | 'mediapipe';
primary_confidence: number;
detection_sources: Array<{
source_type: string;
confidence: number;
}>;
filter_features: {
face_id: string;
frame: number;
bbox_size: number; // min(width, height)
apple_vision_confidence: number;
mediapipe_confidence?: number;
eye_ratio?: number;
nose_ratio?: number;
pose_score?: number;
mediapipe_detected?: boolean;
mediapipe_iou?: number;
embedding_similarity?: number;
consecutive_frames?: number;
total_frames_detected?: number;
backend_filter_status?: 'accept' | 'reject' | 'review';
};
}
```
---
## 過濾模式
### Instant Mode
- **效能**: 5-10ms
- **邏輯**: Client 端即時過濾
- **適用**: 快速調整規則
### Backend Mode
- **效能**: 100-200ms(API 呼叫)
- **邏輯**: 後端複雜過濾(landmark, embedding, temporal)
- **適用**: 高品質過濾
### Hybrid Mode
- **效能**: 5-10ms(前端)+ 後端預先計算
- **邏輯**: 後端標示 + 前端即時過濾(AND)
- **適用**: 最佳使用者體驗
---
## 使用者偏好
偏好儲存在 `localStorage`:
**Key**: `momentry_face_filter_preferences`
**結構**:
```json
{
"mode": "instant",
"rules": {
"min_confidence": 0.5,
"min_bbox_size": 80,
"source": "all",
"backend_status": "accept"
}
}
```
---
## 效能優化
### 分頁
- `page_size ≤ 50`(防 hang)
- 虛擬滾動(可選)
### 快取
- 臉部元數據快取在 composable
- 偏好儲存在 localStorage
### 懶載入
- JPG 縮圖懶載入(未來擴展)
- 當前使用純文字卡片
---
## 錯誤處理
Composable 提供 error 狀態:
```typescript
if (error.value) {
console.error('Face filter error:', error.value);
// 顯示錯誤訊息給使用者
}
```
---
## 測試
### 單元測試
```typescript
import { useFaceFilter } from '../composables/useFaceFilter';
describe('useFaceFilter', () => {
it('should filter faces by confidence', () => {
const { updateRules, filteredFaces } = useFaceFilter();
// Setup test data
// ...
updateRules({ min_confidence: 0.7 });
expect(filteredFaces.value.length).toBeLessThan(allFaces.value.length);
});
});
```
### 整合測試
1. 選擇檔案
2. 驗證臉部載入
3. 調整過濾規則
4. 驗證過濾結果
5. 切換模式
6. 驗證模式切換
---
## 未來擴展
### 1. 縮圖支援
當 Core 實作 `/face/{id}/thumbnail` 端點後:
```vue
<FaceCard :face="face" showThumbnail />
```
### 2. 規則管理 UI
當需要規則 CRUD 時:
```vue
<RuleManager
:fileUuid="selectedFileUuid"
@rule-change="handleRuleChange"
/>
```
### 3. 批量操作
```typescript
const selectedFaces = ref<FaceMetadata[]>([]);
const batchAction = async (action: 'accept' | 'reject') => {
await fetch('/api/v1/file/.../faces/batch', {
method: 'POST',
body: JSON.stringify({
face_ids: selectedFaces.value.map(f => f.face_id),
action
})
});
};
```
### 4. 匯出功能
```typescript
const exportFilteredFaces = () => {
const csv = filteredFaces.value.map(face =>
`${face.face_id},${face.frame},${face.primary_confidence},${face.filter_features.bbox_size}`
).join('\n');
// Download CSV
};
```
---
## 疑難排解
### 問題 1: 臉部未載入
**檢查**:
- `selectedFileUuid` 是否正確
- API 端點是否可達
- 網路請求是否成功
**解決**:
```typescript
console.log('File UUID:', selectedFileUuid);
console.log('API Response:', await fetchFaces(selectedFileUuid));
```
### 問題 2: 過濾未生效
**檢查**:
- `mode` 是否正確
- `rules` 是否正確
- `filteredFaces` 是否更新
**解決**:
```typescript
console.log('Mode:', mode.value);
console.log('Rules:', rules.value);
console.log('Filtered:', filteredFaces.value.length);
```
### 問題 3: 偏好未儲存
**檢查**:
- `localStorage` 是否可寫
- Key 是否正確
**解決**:
```typescript
console.log('LocalStorage:', localStorage.getItem('momentry_face_filter_preferences'));
```
---
## 總結
**整合步驟**:
1. ✅ 導入 useFaceFilter composable
2. ✅ 添加 FaceFilterPanel 組件
3. ✅ 初始化過濾(watch selectedFileUuid)
4. ✅ 使用 filteredFaces 顯示
5. ✅ 處理錯誤和載入狀態
**預期效果**:
- 即時過濾(5-10ms)
- 三種模式切換
- 使用者偏好儲存
- 純文字卡片(無縮圖)
**未來擴展**:
- 縮圖支援
- 規則管理 UI
- 批量操作
- 匯出功能
---
**文件版本**: v1.0
**最後更新**: 2026-08-09
**相關文件**:
- `/Users/accusys/momentry_core/docs_v1.0/STUDIO_TEAM/face_filter_usage_guide.md`
- `/Users/accusys/momentry_core/docs_v1.0/STUDIO_TEAM/face_filter_hybrid_mode_guide.md`