diff --git a/frontend/composables/useApi.js b/frontend/composables/useApi.js
index fab4e7a..470ee02 100644
--- a/frontend/composables/useApi.js
+++ b/frontend/composables/useApi.js
@@ -357,30 +357,6 @@ export const useApi = () => {
return await request(url)
}
- // 朋友圈图片本地缓存候选(用于错图时手动选择)
- const listSnsMediaCandidates = async (params = {}) => {
- const query = new URLSearchParams()
- if (params && params.account) query.set('account', params.account)
- if (params && params.create_time != null) query.set('create_time', String(params.create_time))
- if (params && params.width != null) query.set('width', String(params.width))
- if (params && params.height != null) query.set('height', String(params.height))
- if (params && params.limit != null) query.set('limit', String(params.limit))
- if (params && params.offset != null) query.set('offset', String(params.offset))
- const url = '/sns/media_candidates' + (query.toString() ? `?${query.toString()}` : '')
- return await request(url)
- }
-
- // 保存朋友圈图片手动匹配结果(本机)
- const saveSnsMediaPicks = async (data = {}) => {
- return await request('/sns/media_picks', {
- method: 'POST',
- body: {
- account: data.account || null,
- picks: (data && data.picks && typeof data.picks === 'object' && !Array.isArray(data.picks)) ? data.picks : {}
- }
- })
- }
-
const openChatMediaFolder = async (params = {}) => {
const query = new URLSearchParams()
if (params && params.account) query.set('account', params.account)
@@ -667,8 +643,6 @@ export const useApi = () => {
resolveAppMsg,
listSnsTimeline,
listSnsUsers,
- listSnsMediaCandidates,
- saveSnsMediaPicks,
openChatMediaFolder,
downloadChatEmoji,
saveMediaKeys,
diff --git a/frontend/pages/sns.vue b/frontend/pages/sns.vue
index c257211..da1136a 100644
--- a/frontend/pages/sns.vue
+++ b/frontend/pages/sns.vue
@@ -114,20 +114,7 @@
:src="getSnsMediaUrl(activeCover, activeCover.media[0], 0, activeCover.media[0].url)"
class="w-full h-full object-cover"
alt="朋友圈封面"
- @load="onCoverMediaLoaded(activeCover, $event)"
/>
-
-
- {{ snsMediaStageLabel(snsCoverStageKey(activeCover)) || '识别中' }}
-
-
@@ -361,7 +348,6 @@
loop
:muted="livePhotoHoverMuted"
playsinline
- @loadeddata="onSnsMediaLoaded(post, post.media[0], 0)"
@error="onLivePhotoVideoError(post.id, 0)"
>
@@ -372,22 +358,8 @@
alt=""
loading="lazy"
referrerpolicy="no-referrer"
- @load="onSnsMediaLoaded(post, post.media[0], 0, $event)"
@error="onMediaError(post.id, 0)"
/>
-
-
-
- {{ snsMediaStageLabel(snsMediaStageKey(post.id, 0, 'thumb')) || '识别中' }}
-
-
![]()
-
-
-
- {{ snsMediaStageLabel(snsMediaStageKey(post.id, idx, 'thumb')) || '识别中' }}
-
-
图片失败
@@ -630,7 +587,7 @@
-
+
` requests may not produce
-// entries, making Server-Timing based stage detection always fall back to "unknown".
-if (process.client) {
- try {
- if (typeof performance !== 'undefined' && performance?.setResourceTimingBufferSize) {
- performance.setResourceTimingBufferSize(5000)
- }
- } catch {}
-}
-
const api = useApi()
const chatAccounts = useChatAccountsStore()
@@ -937,186 +883,6 @@ const onMediaError = (postId, idx) => {
mediaErrors.value[mediaErrorKey(postId, idx)] = true
}
-// Hover badge: show which SNS media pipeline stage produced the image.
-// Backend provides `X-SNS-Source` (and optional `X-SNS-Hit-Type`, `X-SNS-X-Enc`) on `/api/sns/media` responses.
-const snsMediaStage = ref({}) // stageKey -> { source, hitType, xEnc }
-const snsMediaStageLoading = ref({}) // stageKey -> boolean
-const snsMediaStageInFlight = new Set()
-
-const isSnsMediaApiUrl = (url) => {
- const u = String(url || '').trim()
- return !!u && u.includes('/api/sns/media')
-}
-
-const snsMediaStageKey = (postId, idx, kind = 'thumb') => {
- const acc = String(selectedAccount.value || '').trim()
- const pid = String(postId || '').trim()
- return `sns:${acc}:${pid}:${String(Number(idx) || 0)}:${String(kind || 'thumb')}`
-}
-
-const snsCoverStageKey = (cover) => {
- const acc = String(selectedAccount.value || '').trim()
- const cid = String(cover?.id || cover?.tid || cover?.createTime || '').trim()
- return `sns:${acc}:cover:${cid || '0'}`
-}
-
-const snsMediaStageLabel = (key) => {
- const k = String(key || '').trim()
- if (!k) return ''
- const info = snsMediaStage.value[k]
- if (!info || typeof info !== 'object') return ''
-
- const source = String(info?.source || '').trim()
- const hitType = String(info?.hitType || '').trim()
-
- if (source === 'remote-cache') return '远程缓存'
- if (source === 'remote-decrypt') return '远程解密'
- if (source === 'remote') return '远程直出'
- if (source === 'deterministic-hash') return hitType ? `本地命中(${hitType})` : '本地命中'
- if (source === 'manual-pick') return '手动匹配'
- if (source === 'local-heuristic') return '本地兜底'
- if (source === 'local-heuristic-next') return '本地兜底(跳过)'
- if (source === 'browser-cache') return '浏览器缓存'
- if (source === 'bkg-cover') return '封面缓存'
- if (source === 'proxy') return '远程代理'
- if (source === 'unknown') return '未知'
- if (source === 'error') return '获取失败'
- return source || '未知'
-}
-
-const snsMediaStageBadgeColorClass = (key) => {
- const k = String(key || '').trim()
- const source = String(snsMediaStage.value?.[k]?.source || '').trim()
-
- if (source.startsWith('remote')) return 'bg-emerald-600/85 text-white'
- if (source === 'deterministic-hash') return 'bg-sky-600/85 text-white'
- if (source.startsWith('local')) return 'bg-blue-600/85 text-white'
- if (source === 'manual-pick') return 'bg-amber-600/90 text-white'
- if (source === 'browser-cache') return 'bg-slate-600/85 text-white'
- if (source === 'proxy') return 'bg-fuchsia-600/85 text-white'
- if (source === 'bkg-cover') return 'bg-indigo-600/85 text-white'
- if (source === 'error') return 'bg-red-600/85 text-white'
- return 'bg-black/50 text-white'
-}
-
-const snsMediaStageBadgeTitle = (key) => {
- const k = String(key || '').trim()
- const info = snsMediaStage.value?.[k]
- if (!info || typeof info !== 'object') return ''
- const source = String(info?.source || '').trim()
- const hitType = String(info?.hitType || '').trim()
- const xEnc = String(info?.xEnc || '').trim()
-
- const parts = []
- if (source) parts.push(`source=${source}`)
- if (hitType) parts.push(`hit=${hitType}`)
- if (xEnc) parts.push(`x-enc=${xEnc}`)
- return parts.join(' · ')
-}
-
-const readSnsStageFromResourceTiming = (url) => {
- try {
- if (!process.client) return null
- if (typeof performance === 'undefined' || !performance?.getEntriesByName) return null
- const u = String(url || '').trim()
- if (!u) return null
- const entries = performance.getEntriesByName(u) || []
- const latest = [...entries].reverse().find((e) => String(e?.entryType || '') === 'resource')
- if (!latest) return null
-
- // Prefer backend-injected stage info from `Server-Timing`.
- const st = latest?.serverTiming
- if (Array.isArray(st) && st.length > 0) {
- let source = ''
- let hitType = ''
- let xEnc = ''
- for (const item of st) {
- const name = String(item?.name || '').trim()
- const desc = String(item?.description || '').trim()
- if (name === 'sns_source' && desc) source = desc
- else if (name.startsWith('sns_source_')) source = name.slice('sns_source_'.length) || desc
- else if (name === 'sns_hit' && desc) hitType = desc
- else if (name.startsWith('sns_hit_')) hitType = name.slice('sns_hit_'.length) || desc
- else if (name === 'sns_xenc' && desc) xEnc = desc
- else if (name.startsWith('sns_xenc_')) xEnc = name.slice('sns_xenc_'.length) || desc
- }
- if (source) return { source, hitType, xEnc }
- }
-
- // When DevTools shows "(from disk cache)", browsers may not expose `serverTiming` at all.
- // Best-effort: infer a browser cache hit from ResourceTiming sizes.
- const transferSize = Number(latest?.transferSize)
- if (Number.isFinite(transferSize) && transferSize === 0) {
- return { source: 'browser-cache', hitType: 'transfer=0', xEnc: '' }
- }
-
- return null
- } catch {
- return null
- }
-}
-
-const ensureSnsMediaStage = async (key, url) => {
- if (!process.client) return
- const k = String(key || '').trim()
- const u = String(url || '').trim()
- if (!k || !u) return
- if (!isSnsMediaApiUrl(u)) return
-
- const existingSource = String(snsMediaStage.value?.[k]?.source || '').trim()
- if (existingSource && existingSource !== 'unknown') return
- if (snsMediaStageLoading.value[k]) return
- if (snsMediaStageInFlight.has(k)) return
-
- snsMediaStageInFlight.add(k)
- snsMediaStageLoading.value[k] = true
-
- try {
- // Prefer stage info from the *same* request that loaded the
![]()
/