fix: read custom cluster names directly from localStorage in setClusterResults
- Previously setClusterResults read from clusterResultsCache which gets deleted by refresh() - Now reads directly from localStorage to preserve custom names across refresh - Fixes Face Group rename not persisting after page refresh
This commit is contained in:
80
src/store.ts
80
src/store.ts
@@ -267,6 +267,11 @@ function loadClusterStateFromStorage() {
|
|||||||
const since = localStorage.getItem('momentry_cluster_running_since')
|
const since = localStorage.getItem('momentry_cluster_running_since')
|
||||||
if (since) clusterRunningSince.value = JSON.parse(since)
|
if (since) clusterRunningSince.value = JSON.parse(since)
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
// Restore cluster results cache from localStorage
|
||||||
|
try {
|
||||||
|
const savedResults = localStorage.getItem('momentry_cluster_results')
|
||||||
|
if (savedResults) clusterResultsCache.value = JSON.parse(savedResults)
|
||||||
|
} catch (e) {}
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
let changed = false
|
let changed = false
|
||||||
for (const uuid of Object.keys(clusterRunningState.value)) {
|
for (const uuid of Object.keys(clusterRunningState.value)) {
|
||||||
@@ -330,6 +335,20 @@ export async function pollClusterResults(fileUuid: string, onDone?: (results: an
|
|||||||
export async function loadClusterNames(fileUuid: string, clusters: any[]) {
|
export async function loadClusterNames(fileUuid: string, clusters: any[]) {
|
||||||
console.log('[loadClusterNames] loading names for', clusters.length, 'clusters')
|
console.log('[loadClusterNames] loading names for', clusters.length, 'clusters')
|
||||||
const promises = clusters.map(async (c) => {
|
const promises = clusters.map(async (c) => {
|
||||||
|
// Only load name from DB if cluster has no meaningful name
|
||||||
|
// "Person_X" names from clustering are considered meaningful
|
||||||
|
// Only replace "Unassigned", empty, or auto-generated trace names
|
||||||
|
const currentName = c.name || ''
|
||||||
|
const isAutoGenerated = currentName === 'Unassigned' ||
|
||||||
|
currentName === '' ||
|
||||||
|
currentName.startsWith('Face Trace ') ||
|
||||||
|
currentName.startsWith('Trace ')
|
||||||
|
|
||||||
|
if (!isAutoGenerated) {
|
||||||
|
// Cluster already has a meaningful name (e.g., Person_X from clustering or custom name), don't overwrite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const repTid = c.representative_trace !== undefined && c.representative_trace !== null ? c.representative_trace : c.trace_ids?.[0]
|
const repTid = c.representative_trace !== undefined && c.representative_trace !== null ? c.representative_trace : c.trace_ids?.[0]
|
||||||
if (repTid != null) {
|
if (repTid != null) {
|
||||||
try {
|
try {
|
||||||
@@ -350,7 +369,49 @@ export function getClusterResults(fileUuid: string): any[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setClusterResults(fileUuid: string, results: any[]) {
|
export function setClusterResults(fileUuid: string, results: any[]) {
|
||||||
clusterResultsCache.value[fileUuid] = results
|
console.log('[setClusterResults] Called with', results.length, 'groups')
|
||||||
|
if (results.length > 0) {
|
||||||
|
console.log('[setClusterResults] First group trace_ids:', results[0]?.trace_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接從 localStorage 讀取自定義名稱,不受 clusterResultsCache 刪除影響
|
||||||
|
let existingNames: Record<string, string> = {}
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('momentry_cluster_results')
|
||||||
|
if (saved) {
|
||||||
|
const savedCache = JSON.parse(saved)
|
||||||
|
const existing = savedCache[fileUuid] || []
|
||||||
|
for (const r of existing) {
|
||||||
|
if (r.cluster_id && r.name && !r.name.startsWith('Person_') && !r.name.startsWith('Trace ') && !r.name.startsWith('Face Trace ')) {
|
||||||
|
existingNames[r.cluster_id] = r.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('[setClusterResults] Loaded custom names from localStorage:', existingNames)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[setClusterResults] Failed to read from localStorage:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new objects to trigger Vue reactivity
|
||||||
|
const newResults = results.map(r => {
|
||||||
|
const newR = { ...r, trace_ids: [...(r.trace_ids || [])] }
|
||||||
|
// Apply custom names to new results
|
||||||
|
if (r.cluster_id && existingNames[r.cluster_id]) {
|
||||||
|
newR.name = existingNames[r.cluster_id]
|
||||||
|
console.log('[setClusterResults] Applied custom name:', existingNames[r.cluster_id], 'for cluster:', r.cluster_id)
|
||||||
|
}
|
||||||
|
return newR
|
||||||
|
})
|
||||||
|
|
||||||
|
clusterResultsCache.value = { ...clusterResultsCache.value, [fileUuid]: newResults }
|
||||||
|
console.log('[setClusterResults] Updated cache for', fileUuid, ':', newResults.length, 'groups')
|
||||||
|
if (newResults.length > 0) {
|
||||||
|
console.log('[setClusterResults] First group after update:', newResults[0])
|
||||||
|
}
|
||||||
|
// Save to localStorage for persistence across page refreshes
|
||||||
|
try {
|
||||||
|
localStorage.setItem('momentry_cluster_results', JSON.stringify(clusterResultsCache.value))
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clearClusterState(fileUuid: string) {
|
export function clearClusterState(fileUuid: string) {
|
||||||
@@ -628,7 +689,15 @@ export async function ensureFiles() {
|
|||||||
try {
|
try {
|
||||||
const result = await apiCall('get_files', { pageSize: 500 })
|
const result = await apiCall('get_files', { pageSize: 500 })
|
||||||
// API returns { success: true, total: number, data: File[] }
|
// API returns { success: true, total: number, data: File[] }
|
||||||
filesCache.value = Array.isArray(result?.data) ? result.data : (Array.isArray(result) ? result : [])
|
let files = Array.isArray(result?.data) ? result.data : (Array.isArray(result) ? result : [])
|
||||||
|
|
||||||
|
// Filter out files with invalid paths (e.g., /tmp, /private/tmp)
|
||||||
|
files = files.filter((f: any) => {
|
||||||
|
const path = f.file_path || ''
|
||||||
|
return !path.startsWith('/tmp') && !path.startsWith('/private/tmp')
|
||||||
|
})
|
||||||
|
|
||||||
|
filesCache.value = files
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load files:', e)
|
console.error('Failed to load files:', e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -733,12 +802,13 @@ export function loadTraceThumb(t: any) {
|
|||||||
loadingFaceThumbs.add(key)
|
loadingFaceThumbs.add(key)
|
||||||
queueThumb(async () => {
|
queueThumb(async () => {
|
||||||
try {
|
try {
|
||||||
// 改用自己的 robust face-thumbnail 端點(三層 fallback:.faces cache →
|
|
||||||
// key_face.jpg → live 重切格),避免 trace-thumb 缺檔時 404 破圖。
|
|
||||||
const base = getApiBase()
|
const base = getApiBase()
|
||||||
const ver = thumbCacheBust > 0 ? `&t=${thumbCacheBust}` : ''
|
const ver = thumbCacheBust > 0 ? `&t=${thumbCacheBust}` : ''
|
||||||
|
const apiKey = localStorage.getItem('momentry_api_key') || 'muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69'
|
||||||
const url = `${base}/api/v1/file/${t.file_uuid}/thumbnail?trace_id=${t.trace_id}${ver}`
|
const url = `${base}/api/v1/file/${t.file_uuid}/thumbnail?trace_id=${t.trace_id}${ver}`
|
||||||
const resp = await fetch(url)
|
const resp = await fetch(url, {
|
||||||
|
headers: { 'x-api-key': apiKey }
|
||||||
|
})
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
const blob = await resp.blob()
|
const blob = await resp.blob()
|
||||||
faceThumbsCache.value[key] = URL.createObjectURL(blob)
|
faceThumbsCache.value[key] = URL.createObjectURL(blob)
|
||||||
|
|||||||
Reference in New Issue
Block a user