fix: trace debug — show Stranger_NNN for unnamed traces instead of unknown
This commit is contained in:
@@ -1,129 +1,50 @@
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-2xl font-bold">人物管理</h2>
|
||||
<button
|
||||
@click="loadPersons"
|
||||
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg transition"
|
||||
>
|
||||
重新整理
|
||||
</button>
|
||||
<h2 class="text-2xl font-bold">身分管理</h2>
|
||||
<button @click="loadPersons" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg transition">重新整理</button>
|
||||
</div>
|
||||
|
||||
<!-- Search Filter -->
|
||||
<div class="bg-gray-800 rounded-lg p-4 border border-gray-700">
|
||||
<input
|
||||
v-model="filterQuery"
|
||||
@keyup.enter="loadPersons"
|
||||
type="text"
|
||||
placeholder="搜尋人物..."
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:border-blue-500"
|
||||
/>
|
||||
<input v-model="filterQuery" @keyup.enter="loadPersons" placeholder="搜尋身分..."
|
||||
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
|
||||
<!-- Person List -->
|
||||
<div v-if="persons.length > 0" class="grid gap-4">
|
||||
<div
|
||||
v-for="person in persons"
|
||||
:key="person.id"
|
||||
class="bg-gray-800 rounded-lg p-6 border border-gray-700"
|
||||
>
|
||||
<div v-for="person in persons" :key="person.id" class="bg-gray-800 rounded-lg p-6 border border-gray-700">
|
||||
<div class="flex items-start gap-6">
|
||||
<!-- Thumbnail -->
|
||||
<PersonThumbnail :personId="person.person_id" :videoUuid="person.file_uuid" />
|
||||
|
||||
<div class="w-16 h-16 bg-gray-700 rounded-lg overflow-hidden border border-gray-600 flex-shrink-0 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-3 mb-2">
|
||||
<h3 class="text-xl font-semibold text-blue-400">
|
||||
{{ person.profile.name || '未命名' }}
|
||||
</h3>
|
||||
<span
|
||||
v-if="person.face_identity_id"
|
||||
class="bg-green-900 text-green-300 px-2 py-1 rounded text-xs"
|
||||
>
|
||||
已註冊
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="bg-yellow-900 text-yellow-300 px-2 py-1 rounded text-xs"
|
||||
>
|
||||
未註冊
|
||||
</span>
|
||||
<h3 class="text-xl font-semibold text-blue-400">{{ person.name || '未命名' }}</h3>
|
||||
<span class="bg-green-900 text-green-300 px-2 py-1 rounded text-xs">{{ person.source || 'system' }}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 text-sm text-gray-400 mb-3">
|
||||
<div>角色: {{ person.profile.character_name || '-' }}</div>
|
||||
<div>Speaker: {{ person.profile.speaker_id || '-' }}</div>
|
||||
<div>影片: {{ person.file_uuid }}</div>
|
||||
<div>出現次數: {{ person.stats.appearance_count }}</div>
|
||||
</div>
|
||||
<div v-if="person.profile.original_name" class="text-sm text-gray-500">
|
||||
原始名稱: {{ person.profile.original_name }}
|
||||
<div>ID: {{ person.id }}</div>
|
||||
<div v-if="person.metadata?.tmdb_movie_title">電影: {{ person.metadata.tmdb_movie_title }}</div>
|
||||
<div v-if="person.metadata?.tmdb_character">角色: {{ person.metadata.tmdb_character }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-col space-y-2">
|
||||
<button
|
||||
v-if="!person.face_identity_id"
|
||||
@click="registerPerson(person.person_id, person.file_uuid)"
|
||||
class="bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg text-sm transition"
|
||||
>
|
||||
註冊為全域身份
|
||||
</button>
|
||||
<button
|
||||
@click="viewDetails(person)"
|
||||
class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm transition"
|
||||
>
|
||||
{{ person.face_identity_id ? '查看全域詳情' : '查看影片' }}
|
||||
</button>
|
||||
<button @click="viewDetails(person)" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm transition">查看詳情</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-else-if="loading" class="text-center py-12 text-gray-500">
|
||||
載入中...
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else class="text-center py-12 text-gray-500">
|
||||
尚無人物資料
|
||||
</div>
|
||||
<div v-else-if="loading" class="text-center py-12 text-gray-500">載入中...</div>
|
||||
<div v-else class="text-center py-12 text-gray-500">尚無身分資料</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { listIdentities, httpFetch, getCurrentConfig } from '@/api/client'
|
||||
import { httpFetch, getCurrentConfig } from '@/api/client'
|
||||
import { useRouter } from 'vue-router'
|
||||
import PersonThumbnail from '@/components/PersonThumbnail.vue'
|
||||
|
||||
interface PersonProfile {
|
||||
name: string | null
|
||||
original_name: string | null
|
||||
character_name: string | null
|
||||
speaker_id: string | null
|
||||
}
|
||||
|
||||
interface PersonStats {
|
||||
appearance_count: number
|
||||
total_duration: number
|
||||
first_appearance: number | null
|
||||
last_appearance: number | null
|
||||
}
|
||||
|
||||
interface Person {
|
||||
id: number
|
||||
person_id: string
|
||||
face_identity_id: number | null
|
||||
file_uuid: string
|
||||
profile: PersonProfile
|
||||
stats: PersonStats
|
||||
is_confirmed: boolean
|
||||
}
|
||||
|
||||
const persons = ref<Person[]>([])
|
||||
const persons = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const filterQuery = ref('')
|
||||
const router = useRouter()
|
||||
@@ -131,46 +52,24 @@ const router = useRouter()
|
||||
const loadPersons = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await listIdentities()
|
||||
persons.value = result.identities
|
||||
const config = getCurrentConfig()
|
||||
const params = new URLSearchParams({ page: '1', page_size: '50' })
|
||||
if (filterQuery.value.trim()) params.set('query', filterQuery.value.trim())
|
||||
const result = await httpFetch<any>(`${config.api_base_url}/api/v1/identities?${params}`)
|
||||
persons.value = result.identities || []
|
||||
} catch (error) {
|
||||
console.error('Failed to load persons:', error)
|
||||
alert('載入失敗: ' + error)
|
||||
console.error('Failed to load identities:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const registerPerson = async (personId: string, _videoUuid: string) => {
|
||||
try {
|
||||
const config = getCurrentConfig()
|
||||
await httpFetch(`${config.api_base_url}/api/v1/identity`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ face_json_path: personId, identity_name: personId })
|
||||
})
|
||||
alert('註冊成功!')
|
||||
await loadPersons()
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error)
|
||||
alert('註冊失敗: ' + error)
|
||||
}
|
||||
const viewDetails = (person: any) => {
|
||||
router.push({
|
||||
name: 'identity-detail',
|
||||
params: { identity_uuid: person.id }
|
||||
})
|
||||
}
|
||||
|
||||
const viewDetails = (person: Person) => {
|
||||
if (person.face_identity_id) {
|
||||
router.push({
|
||||
name: 'identity-detail',
|
||||
params: { id: person.face_identity_id }
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
name: 'video-detail',
|
||||
params: { uuid: person.file_uuid }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadPersons()
|
||||
})
|
||||
</script>
|
||||
onMounted(() => { loadPersons() })
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user