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:
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user