fix: 移除 FaceFilterSettings 重复字段,与 Rust 端结构一致

问题修正:
- 移除 FaceFilterSettings 中的重复字段
- 简化为三段式结构(ui_filter_rules, processing_thresholds, trace_settings)
- 新增 version 字段用于版本控制
- 删除 useFaceFilter.ts 中的 rules 变量
- 重命名 faceFilterSettings 为 settings
- 更新所有访问方式为 settings.value.ui_filter_rules.*

变更内容:
1. src/types/faceFilter.ts
   - FaceFilterSettings 只保留三段式结构
   - fromLegacy() 和 defaults() 不再返回重复字段

2. src/composables/useFaceFilter.ts
   - 删除 rules 变量(不再需要)
   - 删除 defaultRules 常量
   - 重命名 faceFilterSettings 为 settings
   - 更新 localStorage 逻辑保存完整 settings
   - 所有访问改为 settings.value.ui_filter_rules.*

3. src/components/FaceFilterPanel.vue
   - 更新 v-model 绑定为 settings.trace_settings.*
   - 更新解构,删除 rules
   - 更新所有访问为 settings.ui_filter_rules.*

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

对应 Core API: 86c776ce
修正文档: docs/FACE_FILTER_TYPES_FIX_REQUIRED.md
This commit is contained in:
2026-10-09 15:55:30 +08:00
parent 6423eeed77
commit 8789770af2
3 changed files with 53 additions and 92 deletions

View File

@@ -57,7 +57,7 @@
<label>Confidence:</label>
<input
type="number"
v-model.number="rules.min_confidence"
v-model.number="settings.ui_filter_rules.min_confidence"
@change="onRuleChange"
min="0"
max="1"
@@ -72,7 +72,7 @@
<label>Bbox Size:</label>
<input
type="number"
v-model.number="rules.min_bbox_size"
v-model.number="settings.ui_filter_rules.min_bbox_size"
@change="onRuleChange"
min="0"
step="10"
@@ -84,7 +84,7 @@
<div class="filter-row">
<label>Source:</label>
<select v-model="rules.source" @change="onRuleChange" class="filter-select">
<select v-model="settings.ui_filter_rules.source" @change="onRuleChange" class="filter-select">
<option value="all">All</option>
<option value="apple_vision">Apple Vision</option>
<option value="mediapipe">MediaPipe</option>
@@ -93,7 +93,7 @@
<div class="filter-row">
<label>Backend Status:</label>
<select v-model="rules.backend_status" @change="onRuleChange" class="filter-select">
<select v-model="settings.ui_filter_rules.backend_status" @change="onRuleChange" class="filter-select">
<option value="all">All</option>
<option value="accept">Accept</option>
<option value="review">Review</option>
@@ -108,7 +108,7 @@
<label class="checkbox-label">
<input
type="checkbox"
v-model="faceFilterSettings.single_frame_trace_vlm_enabled"
v-model="settings.trace_settings.single_frame_trace_vlm_enabled"
@change="markModified"
/>
VLM Processing
@@ -120,7 +120,7 @@
<label class="checkbox-label">
<input
type="checkbox"
v-model="faceFilterSettings.single_frame_trace_group_enabled"
v-model="settings.trace_settings.single_frame_trace_group_enabled"
@change="markModified"
/>
Face Groups
@@ -132,7 +132,7 @@
<label class="checkbox-label">
<input
type="checkbox"
v-model="faceFilterSettings.single_frame_trace_unassigned_enabled"
v-model="settings.trace_settings.single_frame_trace_unassigned_enabled"
@change="markModified"
/>
Unassigned List
@@ -144,7 +144,7 @@
<label class="checkbox-label">
<input
type="checkbox"
v-model="faceFilterSettings.single_trace_group_filter_enabled"
v-model="settings.trace_settings.single_trace_group_filter_enabled"
@change="markModified"
/>
Single Trace Groups
@@ -154,19 +154,19 @@
<div class="section-actions">
<div class="auto-save-hint">
{{ faceFilterSettingsLoading ? '⏳ Saving...' : '✓ Auto-saved' }}
{{ settingsLoading ? '⏳ Saving...' : '✓ Auto-saved' }}
</div>
<button
class="action-btn"
@click="handleResetSingleFrame"
:disabled="faceFilterSettingsLoading"
:disabled="settingsLoading"
>
Reset to Default
</button>
</div>
<div v-if="faceFilterSettingsError" class="error-message">
❌ {{ faceFilterSettingsError }}
<div v-if="settingsError" class="error-message">
❌ {{ settingsError }}
</div>
</div>
@@ -232,7 +232,6 @@ const emit = defineEmits<{
const {
mode,
rules,
filterResult,
filterStats,
loading,
@@ -242,14 +241,14 @@ const {
updateRules,
applyBackendFilter,
resetToDefault,
faceFilterSettings,
faceFilterSettingsLoading,
faceFilterSettingsError,
faceFilterSettingsModified,
settings,
settingsLoading,
settingsError,
settingsModified,
loadSingleFrameSettings,
resetSingleFrameSettings,
markSingleFrameSettingsModified,
cleanupAutoSave
cleanupAutoSave,
} = useFaceFilter();
const showPanel = ref(false);
@@ -259,7 +258,7 @@ function togglePanel() {
}
function onRuleChange() {
updateRules(rules.value);
updateRules(settings.value.ui_filter_rules);
}
async function handleApplyBackend() {
@@ -291,7 +290,7 @@ watch(showPanel, (show) => {
}
});
watch(faceFilterSettingsModified, (newVal, oldVal) => {
watch(settingsModified, (newVal, oldVal) => {
if (oldVal === true && newVal === false) {
emit('settings-updated');
}

View File

@@ -97,13 +97,6 @@ export interface FileProfileResponse {
face_filter: FaceFilterSettings;
}
const defaultRules: UiFilterRules = {
min_confidence: 0.5,
min_bbox_size: 80,
source: 'all',
backend_status: 'accept'
};
const STORAGE_KEY = 'momentry_face_filter_preferences';
// API helpers
@@ -196,7 +189,7 @@ function hybridFilter(faces: FaceMetadata[], rules: UiFilterRules): FaceMetadata
export function useFaceFilter() {
const mode = ref<'instant' | 'backend' | 'hybrid'>('instant');
const rules = ref<UiFilterRules>({ ...defaultRules });
const settings = ref<FaceFilterSettings>(FaceFilterSettingsMigration.defaults());
const allFaces = ref<FaceMetadata[]>([]);
const filteredFaces = ref<FaceMetadata[]>([]);
const filterResult = ref<FilterResult | null>(null);
@@ -204,10 +197,9 @@ export function useFaceFilter() {
const loading = ref(false);
const error = ref<string | null>(null);
const faceFilterSettings = ref<FaceFilterSettings>(FaceFilterSettingsMigration.defaults());
const faceFilterSettingsLoading = ref(false);
const faceFilterSettingsError = ref<string | null>(null);
const faceFilterSettingsModified = ref(false);
const settingsLoading = ref(false);
const settingsError = ref<string | null>(null);
const settingsModified = ref(false);
const currentFileUuid = ref<string | null>(null);
let autoSaveTimer: ReturnType<typeof setTimeout> | null = null;
@@ -219,26 +211,24 @@ export function useFaceFilter() {
removed: allFaces.value.length - filteredFaces.value.length
}));
// Load preferences from localStorage
function loadPreferences() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
const prefs = JSON.parse(stored);
mode.value = prefs.mode || 'instant';
rules.value = prefs.rules || { ...defaultRules };
settings.value = prefs.settings || FaceFilterSettingsMigration.defaults();
}
} catch (err) {
console.error('Failed to load preferences:', err);
}
}
// Save preferences to localStorage
function savePreferences() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
mode: mode.value,
rules: rules.value
settings: settings.value
}));
} catch (err) {
console.error('Failed to save preferences:', err);
@@ -297,11 +287,10 @@ export function useFaceFilter() {
});
}
// Apply local filter based on mode
function applyLocalFilter() {
switch (mode.value) {
case 'instant':
filteredFaces.value = quickFilter(allFaces.value, rules.value);
filteredFaces.value = quickFilter(allFaces.value, settings.value.ui_filter_rules);
break;
case 'backend':
filteredFaces.value = allFaces.value.filter(face =>
@@ -309,13 +298,13 @@ export function useFaceFilter() {
);
break;
case 'hybrid':
filteredFaces.value = hybridFilter(allFaces.value, rules.value);
filteredFaces.value = hybridFilter(allFaces.value, settings.value.ui_filter_rules);
break;
}
}
function updateRules(newRules: Partial<UiFilterRules>) {
rules.value = { ...rules.value, ...newRules };
settings.value.ui_filter_rules = { ...settings.value.ui_filter_rules, ...newRules };
applyLocalFilter();
savePreferences();
}
@@ -371,52 +360,51 @@ function updateRules(newRules: Partial<UiFilterRules>) {
}
}
// Reset to default
function resetToDefault() {
rules.value = { ...defaultRules };
settings.value = FaceFilterSettingsMigration.defaults();
applyLocalFilter();
savePreferences();
}
async function loadSingleFrameSettings(fileUuid: string) {
faceFilterSettingsLoading.value = true;
faceFilterSettingsError.value = null;
settingsLoading.value = true;
settingsError.value = null;
try {
currentFileUuid.value = fileUuid;
const profile = await getFileProfile(fileUuid);
if (profile.face_filter) {
faceFilterSettings.value = profile.face_filter;
settings.value = profile.face_filter;
} else {
faceFilterSettings.value = FaceFilterSettingsMigration.defaults();
settings.value = FaceFilterSettingsMigration.defaults();
}
faceFilterSettingsModified.value = false;
settingsModified.value = false;
} catch (err: any) {
faceFilterSettingsError.value = err.message || 'Failed to load settings';
settingsError.value = err.message || 'Failed to load settings';
console.error('Failed to load single frame settings:', err);
} finally {
faceFilterSettingsLoading.value = false;
settingsLoading.value = false;
}
}
async function saveSingleFrameSettings(fileUuid: string): Promise<boolean> {
faceFilterSettingsLoading.value = true;
faceFilterSettingsError.value = null;
settingsLoading.value = true;
settingsError.value = null;
try {
const success = await updateFileProfile(fileUuid, faceFilterSettings.value);
const success = await updateFileProfile(fileUuid, settings.value);
if (success) {
faceFilterSettingsModified.value = false;
settingsModified.value = false;
}
return success;
} catch (err: any) {
faceFilterSettingsError.value = err.message || 'Failed to save settings';
settingsError.value = err.message || 'Failed to save settings';
console.error('Failed to save single frame settings:', err);
return false;
} finally {
faceFilterSettingsLoading.value = false;
settingsLoading.value = false;
}
}
@@ -426,7 +414,7 @@ function autoSaveSettings() {
}
autoSaveTimer = setTimeout(async () => {
if (currentFileUuid.value && faceFilterSettingsModified.value) {
if (currentFileUuid.value && settingsModified.value) {
const success = await saveSingleFrameSettings(currentFileUuid.value);
if (!success) {
console.error('Auto-save failed for single frame settings');
@@ -444,19 +432,18 @@ function cleanupAutoSave() {
}
function resetSingleFrameSettings() {
faceFilterSettings.value = FaceFilterSettingsMigration.defaults();
faceFilterSettingsModified.value = true;
settings.value = FaceFilterSettingsMigration.defaults();
settingsModified.value = true;
autoSaveSettings();
}
function markSingleFrameSettingsModified() {
faceFilterSettingsModified.value = true;
settingsModified.value = true;
autoSaveSettings();
}
return {
mode,
rules,
allFaces,
filteredFaces,
filterResult,
@@ -464,10 +451,10 @@ return {
loading,
error,
stats,
faceFilterSettings,
faceFilterSettingsLoading,
faceFilterSettingsError,
faceFilterSettingsModified,
settings,
settingsLoading,
settingsError,
settingsModified,
currentFileUuid,
initialize,

View File

@@ -63,21 +63,10 @@ export interface TraceSettings {
* - 新增字段: max_confidence, max_bbox_size, source, backend_status
*/
export interface FaceFilterSettings {
version?: string;
ui_filter_rules: UiFilterRules;
processing_thresholds: ProcessingThresholds;
trace_settings: TraceSettings;
min_confidence: number;
max_confidence?: number;
min_bbox_size: number;
max_bbox_size?: number;
source?: string;
backend_status?: string;
single_frame_trace_vlm_enabled: boolean;
single_frame_trace_group_enabled: boolean;
single_frame_trace_unassigned_enabled: boolean;
single_trace_group_filter_enabled: boolean;
}
/**
@@ -142,24 +131,16 @@ export class FaceFilterSettingsMigration {
};
return {
version: "1.0",
ui_filter_rules: uiFilterRules,
processing_thresholds: processingThresholds,
trace_settings: traceSettings,
min_confidence: minConf,
max_confidence: maxConf,
min_bbox_size: legacy.min_bbox_size ?? 80,
max_bbox_size: legacy.max_bbox_size,
source: legacy.source,
backend_status: legacy.backend_status,
single_frame_trace_vlm_enabled: traceSettings.single_frame_trace_vlm_enabled,
single_frame_trace_group_enabled: traceSettings.single_frame_trace_group_enabled,
single_frame_trace_unassigned_enabled: traceSettings.single_frame_trace_unassigned_enabled,
single_trace_group_filter_enabled: traceSettings.single_trace_group_filter_enabled,
};
}
static defaults(): FaceFilterSettings {
return {
version: "1.0",
ui_filter_rules: {
min_confidence: 0.5,
min_bbox_size: 80,
@@ -174,12 +155,6 @@ export class FaceFilterSettingsMigration {
single_frame_trace_unassigned_enabled: true,
single_trace_group_filter_enabled: true,
},
min_confidence: 0.5,
min_bbox_size: 80,
single_frame_trace_vlm_enabled: true,
single_frame_trace_group_enabled: true,
single_frame_trace_unassigned_enabled: true,
single_trace_group_filter_enabled: true,
};
}
}