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:
2026-10-01 00:42:13 +08:00
parent 7b60ca3d2d
commit f3d75ebce8
35 changed files with 8054 additions and 961 deletions

View File

@@ -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>

View File

@@ -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