feat: Library 批量注册 + PeopleView 修复 + 国际化
Library: - 添加 checkbox 和鼠标框选功能 - Unregistered 卡片添加快速注册按钮 - 状态标签移至左下方,人脸图标移至左侧中间 - Deep Scan 改为 unregister -> register -> process - 完整国际化(日语、繁体中文、英语) PeopleView: - 修复卡片不显示问题(使用 get_file_traces API) - 移除对 identity_id 字段的依赖 其他: - 添加日语语言包 - 添加 useDragSelect composable - 新增文档:Face Bbox Null Bug Report
This commit is contained in:
@@ -1,9 +1,55 @@
|
||||
<template>
|
||||
<div class="build-version">{{ version }}</div>
|
||||
<div v-if="isAdvanced" class="build-version" @click="copyInfo" :title="tooltip">{{ displayText }}</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const version = __BUILD_VERSION__
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAdvancedMode } from '@/stores/advancedMode'
|
||||
import pkg from '../../package.json'
|
||||
|
||||
const router = useRouter()
|
||||
const { isAdvanced } = useAdvancedMode()
|
||||
const build = __BUILD_VERSION__
|
||||
const shortDate = build.slice(0, 8)
|
||||
const appVersion = pkg.version
|
||||
|
||||
const pageName = computed(() => {
|
||||
const name = router.currentRoute.value.name
|
||||
return typeof name === 'string' ? name : router.currentRoute.value.path
|
||||
})
|
||||
|
||||
const displayText = computed(() => `v${appVersion}-${shortDate} | ${pageName.value}`)
|
||||
|
||||
const tooltip = computed(() =>
|
||||
`App: ${appVersion}\nBuild: ${build}\nPage: ${pageName.value}`
|
||||
)
|
||||
|
||||
function copyInfo() {
|
||||
const lines = [
|
||||
'=== MOMENTRY STUDIO ===',
|
||||
`App Version: ${appVersion}`,
|
||||
`Build: ${build}`,
|
||||
`Page: ${pageName.value}`,
|
||||
`Route: ${router.currentRoute.value.path}`,
|
||||
`Timestamp: ${new Date().toISOString()}`
|
||||
]
|
||||
const text = lines.join('\n')
|
||||
const ta = document.createElement('textarea')
|
||||
ta.value = text
|
||||
ta.style.position = 'fixed'
|
||||
ta.style.left = '0'
|
||||
ta.style.top = '0'
|
||||
ta.style.width = '0'
|
||||
ta.style.height = '0'
|
||||
ta.style.opacity = '0'
|
||||
document.body.appendChild(ta)
|
||||
ta.focus()
|
||||
ta.setSelectionRange(0, text.length)
|
||||
const ok = document.execCommand('copy')
|
||||
document.body.removeChild(ta)
|
||||
if (!ok) window.prompt('Copy (Ctrl+C, Enter):', text)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -14,9 +60,11 @@ const version = __BUILD_VERSION__
|
||||
font-size: 10px;
|
||||
color: var(--text-secondary, #9aa0a6);
|
||||
background: var(--bg-secondary, rgba(0,0,0,0.1));
|
||||
padding: 2px 6px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
z-index: 9999;
|
||||
font-family: monospace;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -101,6 +101,79 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="section-title">Single Frame FS Settings</div>
|
||||
|
||||
<div class="filter-row checkbox-row">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="singleFrameSettings.single_frame_trace_vlm_enabled"
|
||||
@change="markModified"
|
||||
/>
|
||||
VLM Processing
|
||||
</label>
|
||||
<div class="hint">Enable VLM for single-frame traces</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row checkbox-row">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="singleFrameSettings.single_frame_trace_group_enabled"
|
||||
@change="markModified"
|
||||
/>
|
||||
Face Groups
|
||||
</label>
|
||||
<div class="hint">Include single-frame traces in groups</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row checkbox-row">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="singleFrameSettings.single_frame_trace_unassigned_enabled"
|
||||
@change="markModified"
|
||||
/>
|
||||
Unassigned List
|
||||
</label>
|
||||
<div class="hint">Show single-frame traces in unassigned</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-row checkbox-row">
|
||||
<label class="checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="singleFrameSettings.single_trace_group_filter_enabled"
|
||||
@change="markModified"
|
||||
/>
|
||||
Single Trace Groups
|
||||
</label>
|
||||
<div class="hint">Filter auto-generated Person_N groups</div>
|
||||
</div>
|
||||
|
||||
<div class="section-actions">
|
||||
<button
|
||||
class="action-btn action-btn-primary"
|
||||
@click="applySingleFrameSettings"
|
||||
:disabled="!singleFrameSettingsModified || singleFrameSettingsLoading"
|
||||
>
|
||||
{{ singleFrameSettingsLoading ? 'Applying...' : 'Apply Settings' }}
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="handleResetSingleFrame"
|
||||
:disabled="singleFrameSettingsLoading"
|
||||
>
|
||||
Reset to Default
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="singleFrameSettingsError" class="error-message">
|
||||
❌ {{ singleFrameSettingsError }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="filter-actions">
|
||||
<button
|
||||
@@ -150,13 +223,17 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useFaceFilter, type QuickFilterRules } from '../composables/useFaceFilter';
|
||||
|
||||
const props = defineProps<{
|
||||
fileUuid: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'settings-updated'): void;
|
||||
}>();
|
||||
|
||||
const {
|
||||
mode,
|
||||
rules,
|
||||
@@ -168,7 +245,15 @@ const {
|
||||
setMode,
|
||||
updateRules,
|
||||
applyBackendFilter,
|
||||
resetToDefault
|
||||
resetToDefault,
|
||||
singleFrameSettings,
|
||||
singleFrameSettingsLoading,
|
||||
singleFrameSettingsError,
|
||||
singleFrameSettingsModified,
|
||||
loadSingleFrameSettings,
|
||||
saveSingleFrameSettings,
|
||||
resetSingleFrameSettings,
|
||||
markSingleFrameSettingsModified
|
||||
} = useFaceFilter();
|
||||
|
||||
const showPanel = ref(false);
|
||||
@@ -190,6 +275,34 @@ function handleReset() {
|
||||
resetToDefault();
|
||||
}
|
||||
|
||||
function markModified() {
|
||||
markSingleFrameSettingsModified();
|
||||
}
|
||||
|
||||
async function applySingleFrameSettings() {
|
||||
if (!props.fileUuid) return;
|
||||
const success = await saveSingleFrameSettings(props.fileUuid);
|
||||
if (success) {
|
||||
emit('settings-updated');
|
||||
}
|
||||
}
|
||||
|
||||
function handleResetSingleFrame() {
|
||||
resetSingleFrameSettings();
|
||||
}
|
||||
|
||||
watch(() => props.fileUuid, (newUuid) => {
|
||||
if (newUuid && showPanel.value) {
|
||||
loadSingleFrameSettings(newUuid);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
watch(showPanel, (show) => {
|
||||
if (show && props.fileUuid) {
|
||||
loadSingleFrameSettings(props.fileUuid);
|
||||
}
|
||||
});
|
||||
|
||||
// Expose for parent
|
||||
defineExpose({
|
||||
showPanel,
|
||||
@@ -403,4 +516,51 @@ defineExpose({
|
||||
.stat-item span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border-color, #ddd);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-primary, #333);
|
||||
}
|
||||
|
||||
.checkbox-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user