diff --git a/src/store.ts b/src/store.ts index eaa5c75..a6f9ff8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -267,6 +267,11 @@ function loadClusterStateFromStorage() { const since = localStorage.getItem('momentry_cluster_running_since') if (since) clusterRunningSince.value = JSON.parse(since) } 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() let changed = false 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[]) { console.log('[loadClusterNames] loading names for', clusters.length, 'clusters') 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] if (repTid != null) { try { @@ -350,7 +369,49 @@ export function getClusterResults(fileUuid: string): 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 = {} + 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) { @@ -628,7 +689,15 @@ export async function ensureFiles() { try { const result = await apiCall('get_files', { pageSize: 500 }) // 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) { console.error('Failed to load files:', e) } finally { @@ -733,12 +802,13 @@ export function loadTraceThumb(t: any) { loadingFaceThumbs.add(key) queueThumb(async () => { try { - // 改用自己的 robust face-thumbnail 端點(三層 fallback:.faces cache → - // key_face.jpg → live 重切格),避免 trace-thumb 缺檔時 404 破圖。 const base = getApiBase() 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 resp = await fetch(url) + const resp = await fetch(url, { + headers: { 'x-api-key': apiKey } + }) if (resp.ok) { const blob = await resp.blob() faceThumbsCache.value[key] = URL.createObjectURL(blob)