feat: 更新 FaceFilterSettings TypeScript 类型定义为三段式统一结构

变更内容:
- 创建 src/types/faceFilter.ts 类型定义文件
- 新增 UiFilterRules, ProcessingThresholds, TraceSettings 三个子结构
- 更新 FaceFilterSettings 为三段式统一结构
- min_confidence 范围从 0-100 改为 0.0-1.0
- min_size 重命名为 min_bbox_size
- 新增字段: max_confidence, max_bbox_size, source, backend_status

向后兼容性:
- FaceFilterSettingsMigration.fromLegacy() 自动转换旧格式
- FaceFilterSettingsMigration.defaults() 提供默认值
- 保留向后兼容字段(deprecated 标记)

更新文件:
- src/composables/useFaceFilter.ts: 使用新类型,更新变量名
- src/components/FaceFilterPanel.vue: 更新导入和变量引用
- docs/FACE_FILTER_SETTINGS_MIGRATION_GUIDE.md: 完整迁移指南

测试:
- ✅ TypeScript 编译通过
- ✅ Vite build 成功

对应 Core API commit: 86c776ce
This commit is contained in:
2026-10-09 15:13:50 +08:00
parent bb374dd374
commit 6423eeed77
4 changed files with 702 additions and 170 deletions

View File

@@ -1,6 +1,11 @@
import { ref, computed } from 'vue';
import type {
FaceFilterSettings,
UiFilterRules,
FileProfileResponse as NewFileProfileResponse
} from '@/types/faceFilter';
import { FaceFilterSettingsMigration } from '@/types/faceFilter';
// Types
export interface FaceMetadata {
face_id: string;
frame: number;
@@ -28,15 +33,6 @@ export interface FaceMetadata {
};
}
export interface QuickFilterRules {
min_confidence?: number;
max_confidence?: number;
min_bbox_size?: number;
max_bbox_size?: number;
source?: 'apple_vision' | 'mediapipe' | 'all';
backend_status?: 'accept' | 'reject' | 'review' | 'all';
}
export interface FilterResult {
total_faces: number;
passed: number;
@@ -86,12 +82,7 @@ export interface FilterStats {
};
}
export interface SingleFrameFSSettings {
single_frame_trace_vlm_enabled: boolean;
single_frame_trace_group_enabled: boolean;
single_frame_trace_unassigned_enabled: boolean;
single_trace_group_filter_enabled: boolean;
}
export type { UiFilterRules, FaceFilterSettings };
export interface FileProfileResponse {
file_uuid: string;
@@ -103,11 +94,10 @@ export interface FileProfileResponse {
height: number;
fps: number;
total_frames: number;
face_filter: SingleFrameFSSettings;
face_filter: FaceFilterSettings;
}
// Default rules
const defaultRules: QuickFilterRules = {
const defaultRules: UiFilterRules = {
min_confidence: 0.5,
min_bbox_size: 80,
source: 'all',
@@ -158,7 +148,7 @@ async function getFileProfile(fileUuid: string): Promise<FileProfileResponse> {
return response.json();
}
async function updateFileProfile(fileUuid: string, faceFilter: Partial<SingleFrameFSSettings>): Promise<boolean> {
async function updateFileProfile(fileUuid: string, faceFilter: Partial<FaceFilterSettings>): Promise<boolean> {
const response = await fetch(`${apiBase}/file-profile`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
@@ -167,10 +157,8 @@ async function updateFileProfile(fileUuid: string, faceFilter: Partial<SingleFra
return response.ok;
}
// Quick filter function (client-side)
function quickFilter(faces: FaceMetadata[], rules: QuickFilterRules): FaceMetadata[] {
function quickFilter(faces: FaceMetadata[], rules: UiFilterRules): FaceMetadata[] {
return faces.filter(face => {
// 1. Confidence 過濾
if (rules.min_confidence && face.primary_confidence < rules.min_confidence) {
return false;
}
@@ -178,7 +166,6 @@ function quickFilter(faces: FaceMetadata[], rules: QuickFilterRules): FaceMetada
return false;
}
// 2. Bbox size 過濾
if (rules.min_bbox_size && face.filter_features.bbox_size < rules.min_bbox_size) {
return false;
}
@@ -186,12 +173,10 @@ function quickFilter(faces: FaceMetadata[], rules: QuickFilterRules): FaceMetada
return false;
}
// 3. Source 過濾
if (rules.source && rules.source !== 'all' && face.primary_source !== rules.source) {
return false;
}
// 4. Backend status 過濾
if (rules.backend_status && rules.backend_status !== 'all' &&
face.filter_features.backend_filter_status !== rules.backend_status) {
return false;
@@ -201,22 +186,17 @@ function quickFilter(faces: FaceMetadata[], rules: QuickFilterRules): FaceMetada
});
}
// Hybrid filter function (backend + frontend)
function hybridFilter(faces: FaceMetadata[], rules: QuickFilterRules): FaceMetadata[] {
// 1. 前端過濾
function hybridFilter(faces: FaceMetadata[], rules: UiFilterRules): FaceMetadata[] {
const frontendFiltered = quickFilter(faces, rules);
// 2. 後端已標示 backend_filter_status
// 3. 兩條件 AND
return frontendFiltered.filter(face =>
face.filter_features.backend_filter_status === 'accept'
);
}
// Main composable
export function useFaceFilter() {
const mode = ref<'instant' | 'backend' | 'hybrid'>('instant');
const rules = ref<QuickFilterRules>({ ...defaultRules });
const rules = ref<UiFilterRules>({ ...defaultRules });
const allFaces = ref<FaceMetadata[]>([]);
const filteredFaces = ref<FaceMetadata[]>([]);
const filterResult = ref<FilterResult | null>(null);
@@ -224,15 +204,10 @@ export function useFaceFilter() {
const loading = ref(false);
const error = ref<string | null>(null);
const singleFrameSettings = ref<SingleFrameFSSettings>({
single_frame_trace_vlm_enabled: true,
single_frame_trace_group_enabled: true,
single_frame_trace_unassigned_enabled: true,
single_trace_group_filter_enabled: true,
});
const singleFrameSettingsLoading = ref(false);
const singleFrameSettingsError = ref<string | null>(null);
const singleFrameSettingsModified = ref(false);
const faceFilterSettings = ref<FaceFilterSettings>(FaceFilterSettingsMigration.defaults());
const faceFilterSettingsLoading = ref(false);
const faceFilterSettingsError = ref<string | null>(null);
const faceFilterSettingsModified = ref(false);
const currentFileUuid = ref<string | null>(null);
let autoSaveTimer: ReturnType<typeof setTimeout> | null = null;
@@ -339,12 +314,11 @@ export function useFaceFilter() {
}
}
// Update rules and re-filter
function updateRules(newRules: Partial<QuickFilterRules>) {
rules.value = { ...rules.value, ...newRules };
applyLocalFilter();
savePreferences();
}
function updateRules(newRules: Partial<UiFilterRules>) {
rules.value = { ...rules.value, ...newRules };
applyLocalFilter();
savePreferences();
}
// Change mode and re-filter
function setMode(newMode: 'instant' | 'backend' | 'hybrid') {
@@ -404,124 +378,117 @@ export function useFaceFilter() {
savePreferences();
}
async function loadSingleFrameSettings(fileUuid: string) {
singleFrameSettingsLoading.value = true;
singleFrameSettingsError.value = null;
try {
currentFileUuid.value = fileUuid;
const profile = await getFileProfile(fileUuid);
singleFrameSettings.value = {
single_frame_trace_vlm_enabled: profile.face_filter.single_frame_trace_vlm_enabled ?? true,
single_frame_trace_group_enabled: profile.face_filter.single_frame_trace_group_enabled ?? true,
single_frame_trace_unassigned_enabled: profile.face_filter.single_frame_trace_unassigned_enabled ?? true,
single_trace_group_filter_enabled: profile.face_filter.single_trace_group_filter_enabled ?? true,
};
singleFrameSettingsModified.value = false;
} catch (err: any) {
singleFrameSettingsError.value = err.message || 'Failed to load settings';
console.error('Failed to load single frame settings:', err);
} finally {
singleFrameSettingsLoading.value = false;
}
}
async function loadSingleFrameSettings(fileUuid: string) {
faceFilterSettingsLoading.value = true;
faceFilterSettingsError.value = null;
async function saveSingleFrameSettings(fileUuid: string): Promise<boolean> {
singleFrameSettingsLoading.value = true;
singleFrameSettingsError.value = null;
try {
currentFileUuid.value = fileUuid;
const profile = await getFileProfile(fileUuid);
try {
const success = await updateFileProfile(fileUuid, singleFrameSettings.value);
if (success) {
singleFrameSettingsModified.value = false;
}
return success;
} catch (err: any) {
singleFrameSettingsError.value = err.message || 'Failed to save settings';
console.error('Failed to save single frame settings:', err);
return false;
} finally {
singleFrameSettingsLoading.value = false;
}
}
function autoSaveSettings() {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer);
if (profile.face_filter) {
faceFilterSettings.value = profile.face_filter;
} else {
faceFilterSettings.value = FaceFilterSettingsMigration.defaults();
}
autoSaveTimer = setTimeout(async () => {
if (currentFileUuid.value && singleFrameSettingsModified.value) {
const success = await saveSingleFrameSettings(currentFileUuid.value);
if (!success) {
console.error('Auto-save failed for single frame settings');
}
}
autoSaveTimer = null;
}, 500);
faceFilterSettingsModified.value = false;
} catch (err: any) {
faceFilterSettingsError.value = err.message || 'Failed to load settings';
console.error('Failed to load single frame settings:', err);
} finally {
faceFilterSettingsLoading.value = false;
}
}
async function saveSingleFrameSettings(fileUuid: string): Promise<boolean> {
faceFilterSettingsLoading.value = true;
faceFilterSettingsError.value = null;
function cleanupAutoSave() {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer);
autoSaveTimer = null;
try {
const success = await updateFileProfile(fileUuid, faceFilterSettings.value);
if (success) {
faceFilterSettingsModified.value = false;
}
return success;
} catch (err: any) {
faceFilterSettingsError.value = err.message || 'Failed to save settings';
console.error('Failed to save single frame settings:', err);
return false;
} finally {
faceFilterSettingsLoading.value = false;
}
}
function autoSaveSettings() {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer);
}
function resetSingleFrameSettings() {
singleFrameSettings.value = {
single_frame_trace_vlm_enabled: true,
single_frame_trace_group_enabled: true,
single_frame_trace_unassigned_enabled: true,
single_trace_group_filter_enabled: true,
};
singleFrameSettingsModified.value = true;
autoSaveSettings();
autoSaveTimer = setTimeout(async () => {
if (currentFileUuid.value && faceFilterSettingsModified.value) {
const success = await saveSingleFrameSettings(currentFileUuid.value);
if (!success) {
console.error('Auto-save failed for single frame settings');
}
}
autoSaveTimer = null;
}, 500);
}
function cleanupAutoSave() {
if (autoSaveTimer) {
clearTimeout(autoSaveTimer);
autoSaveTimer = null;
}
}
function resetSingleFrameSettings() {
faceFilterSettings.value = FaceFilterSettingsMigration.defaults();
faceFilterSettingsModified.value = true;
autoSaveSettings();
}
function markSingleFrameSettingsModified() {
faceFilterSettingsModified.value = true;
autoSaveSettings();
}
function markSingleFrameSettingsModified() {
singleFrameSettingsModified.value = true;
autoSaveSettings();
}
return {
mode,
rules,
allFaces,
filteredFaces,
filterResult,
filterStats,
loading,
error,
stats,
faceFilterSettings,
faceFilterSettingsLoading,
faceFilterSettingsError,
faceFilterSettingsModified,
currentFileUuid,
initialize,
updateRules,
setMode,
applyBackendFilter,
getFaceReportData,
refreshStats,
resetToDefault,
applyLocalFilter,
loadSingleFrameSettings,
saveSingleFrameSettings,
resetSingleFrameSettings,
markSingleFrameSettingsModified,
cleanupAutoSave,
return {
// State
mode,
rules,
allFaces,
filteredFaces,
filterResult,
filterStats,
loading,
error,
stats,
singleFrameSettings,
singleFrameSettingsLoading,
singleFrameSettingsError,
singleFrameSettingsModified,
currentFileUuid,
// Actions
initialize,
updateRules,
setMode,
applyBackendFilter,
getFaceReportData,
refreshStats,
resetToDefault,
applyLocalFilter,
loadSingleFrameSettings,
saveSingleFrameSettings,
resetSingleFrameSettings,
markSingleFrameSettingsModified,
cleanupAutoSave,
// API helpers (exposed for direct use)
fetchFaces,
applyFilter,
getFilterStats,
getFaceReport,
quickFilter,
hybridFilter
};
fetchFaces,
applyFilter,
getFilterStats,
getFaceReport,
quickFilter,
hybridFilter
};
}