diff --git a/frontend/composables/chat/useChatMessages.js b/frontend/composables/chat/useChatMessages.js index d1919b5..95ddc5a 100644 --- a/frontend/composables/chat/useChatMessages.js +++ b/frontend/composables/chat/useChatMessages.js @@ -5,6 +5,7 @@ import { getVoiceDurationInSeconds, getVoiceWidth } from '~/lib/chat/formatters' +import { createPerfTrace } from '~/lib/chat/perf-logger' import { createMessageNormalizer, dedupeMessagesById } from '~/lib/chat/message-normalizer' export const useChatMessages = ({ @@ -410,9 +411,15 @@ export const useChatMessages = ({ const loadMessages = async ({ username, reset }) => { if (!username || !selectedAccount.value) return - logMessagePhase('loadMessages:enter', { - username, - reset + const trace = createPerfTrace('chat-messages', { + account: String(selectedAccount.value || '').trim(), + selectedUsername: String(selectedContact.value?.username || '').trim(), + username: String(username || '').trim(), + reset: !!reset + }) + + trace.log('loadMessages:enter', { + activeMessagesFor: String(activeMessagesFor.value || '').trim() }) messagesError.value = '' isLoadingMessages.value = true @@ -438,46 +445,37 @@ export const useChatMessages = ({ if (realtimeEnabled.value) { params.source = 'realtime' } - logMessagePhase('loadMessages:request:start', { - username, - reset, + trace.log('loadMessages:request:start', { offset, existingCount: existing.length, renderTypeFilter: messageTypeFilter.value, realtime: !!realtimeEnabled.value }) const response = await api.listChatMessages(params) - logMessagePhase('loadMessages:request:end', { - username, - reset, + trace.log('loadMessages:request:end', { rawCount: Array.isArray(response?.messages) ? response.messages.length : 0, total: Number(response?.total || 0), hasMore: response?.hasMore }) const raw = response?.messages || [] - logMessagePhase('loadMessages:normalize:start', { - username, + trace.log('loadMessages:normalize:start', { rawCount: raw.length }) const mapped = dedupeMessagesById(raw.map(normalizeMessage)) - logMessagePhase('loadMessages:normalize:end', { - username, + trace.log('loadMessages:normalize:end', { mappedCount: mapped.length, renderTypeCounts: summarizeRenderTypes(mapped) }) if (activeMessagesFor.value !== username) { - logMessagePhase('loadMessages:abort-stale', { - username, + trace.log('loadMessages:abort-stale', { activeMessagesFor: activeMessagesFor.value }) return } - logMessagePhase('loadMessages:state-commit:start', { - username, - reset, + trace.log('loadMessages:state-commit:start', { mappedCount: mapped.length }) if (reset) { @@ -496,8 +494,7 @@ export const useChatMessages = ({ [username]: [...older, ...existing] } } - logMessagePhase('loadMessages:state-commit:end', { - username, + trace.log('loadMessages:state-commit:end', { storedCount: (allMessages.value[username] || []).length }) @@ -508,18 +505,14 @@ export const useChatMessages = ({ hasMore: response?.hasMore } } - logMessagePhase('loadMessages:meta-commit:end', { - username, + trace.log('loadMessages:meta-commit:end', { total: Number(response?.total || 0), hasMore: response?.hasMore }) - logMessagePhase('loadMessages:nextTick:start', { - username - }) + trace.log('loadMessages:nextTick:start') await nextTick() - logMessagePhase('loadMessages:nextTick:end', { - username, + trace.log('loadMessages:nextTick:end', { renderedCount: (allMessages.value[username] || []).length }) const nextContainer = messageContainerRef.value @@ -532,13 +525,16 @@ export const useChatMessages = ({ } } updateJumpToBottomState() - logMessagePhase('loadMessages:scroll:end', { - username, + trace.log('loadMessages:scroll:end', { hasContainer: !!nextContainer, scrollTop: nextContainer ? nextContainer.scrollTop : null, scrollHeight: nextContainer ? nextContainer.scrollHeight : null }) } catch (error) { + trace.log('loadMessages:error', { + message: String(error?.message || ''), + errorName: String(error?.name || '') + }) console.error('[chat-messages] loadMessages:error', { account: String(selectedAccount.value || '').trim(), username: String(username || '').trim(), @@ -548,9 +544,7 @@ export const useChatMessages = ({ messagesError.value = error?.message || '加载聊天记录失败' } finally { isLoadingMessages.value = false - logMessagePhase('loadMessages:exit', { - username, - reset, + trace.log('loadMessages:exit', { loading: isLoadingMessages.value, error: messagesError.value }) @@ -571,9 +565,24 @@ export const useChatMessages = ({ const refreshCurrentMessageMedia = async () => { if (!selectedContact.value?.username) return + const trace = createPerfTrace('chat-messages', { + account: String(selectedAccount.value || '').trim(), + username: String(selectedContact.value?.username || '').trim(), + action: 'refreshCurrentMessageMedia' + }) + trace.log('refreshCurrentMessageMedia:start', { + localMediaVersion: Number(localMediaVersion.value || 0) + }) bumpLocalMediaVersion() + trace.log('refreshCurrentMessageMedia:version-bumped', { + localMediaVersion: Number(localMediaVersion.value || 0) + }) renormalizeLoadedMessages(selectedContact.value.username) + trace.log('refreshCurrentMessageMedia:renormalized', { + renderedCount: (allMessages.value[selectedContact.value.username] || []).length + }) await nextTick() + trace.log('refreshCurrentMessageMedia:end') } const refreshRealtimeIncremental = async () => { diff --git a/frontend/composables/chat/useChatSessions.js b/frontend/composables/chat/useChatSessions.js index 5df2802..b838cff 100644 --- a/frontend/composables/chat/useChatSessions.js +++ b/frontend/composables/chat/useChatSessions.js @@ -1,5 +1,6 @@ import { computed, onMounted, ref } from 'vue' import { normalizeSessionPreview } from '~/lib/chat/formatters' +import { createPerfTrace } from '~/lib/chat/perf-logger' const SESSION_LIST_WIDTH_KEY = 'ui.chat.session_list_width_physical' const SESSION_LIST_WIDTH_KEY_LEGACY = 'ui.chat.session_list_width' @@ -170,6 +171,14 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled return [] } + const trace = createPerfTrace('chat-sessions', { + account: String(selectedAccount.value || '').trim(), + action: 'loadSessionsForSelectedAccount' + }) + trace.log('loadSessions:start', { + realtimeEnabled: !!realtimeEnabled?.value + }) + const fetchSessions = async (source) => { const params = { account: selectedAccount.value, @@ -184,18 +193,38 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled let sessionsResp = null if (realtimeEnabled?.value) { try { + trace.log('loadSessions:request:start', { + source: 'realtime' + }) sessionsResp = await fetchSessions('realtime') + trace.log('loadSessions:request:end', { + source: 'realtime', + rawCount: Array.isArray(sessionsResp?.sessions) ? sessionsResp.sessions.length : 0 + }) } catch { sessionsResp = null + trace.log('loadSessions:request:error', { + source: 'realtime' + }) } } if (!sessionsResp) { + trace.log('loadSessions:request:start', { + source: 'default' + }) sessionsResp = await fetchSessions('') + trace.log('loadSessions:request:end', { + source: 'default', + rawCount: Array.isArray(sessionsResp?.sessions) ? sessionsResp.sessions.length : 0 + }) } const sessions = Array.isArray(sessionsResp?.sessions) ? sessionsResp.sessions : [] contacts.value = mapSessions(sessions) contactsError.value = '' + trace.log('loadSessions:end', { + contactCount: contacts.value.length + }) return contacts.value } @@ -208,6 +237,14 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled const desiredSource = (sourceOverride != null) ? String(sourceOverride || '').trim() : (realtimeEnabled?.value ? 'realtime' : '') + const trace = createPerfTrace('chat-sessions', { + account: String(selectedAccount.value || '').trim(), + action: 'refreshSessionsForSelectedAccount', + desiredSource + }) + trace.log('refreshSessions:start', { + previousUsername + }) const params = { account: selectedAccount.value, @@ -219,15 +256,35 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled let sessionsResp = null if (desiredSource) { try { + trace.log('refreshSessions:request:start', { + source: desiredSource + }) sessionsResp = await api.listChatSessions({ ...params, source: desiredSource }) + trace.log('refreshSessions:request:end', { + source: desiredSource, + rawCount: Array.isArray(sessionsResp?.sessions) ? sessionsResp.sessions.length : 0 + }) } catch { sessionsResp = null + trace.log('refreshSessions:request:error', { + source: desiredSource + }) } } if (!sessionsResp) { try { + trace.log('refreshSessions:request:start', { + source: 'default' + }) sessionsResp = await api.listChatSessions(params) + trace.log('refreshSessions:request:end', { + source: 'default', + rawCount: Array.isArray(sessionsResp?.sessions) ? sessionsResp.sessions.length : 0 + }) } catch { + trace.log('refreshSessions:request:error', { + source: 'default' + }) return } } @@ -240,6 +297,10 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled const matched = nextContacts.find((contact) => contact.username === previousUsername) if (matched) selectedContact.value = matched } + trace.log('refreshSessions:end', { + contactCount: nextContacts.length, + selectedUsername: String(selectedContact.value?.username || '').trim() + }) } const loadContacts = async () => { @@ -249,25 +310,50 @@ export const useChatSessions = ({ chatAccounts, selectedAccount, realtimeEnabled isLoadingContacts.value = true contactsError.value = '' + const trace = createPerfTrace('chat-sessions', { + account: String(selectedAccount.value || '').trim(), + action: 'loadContacts' + }) + trace.log('loadContacts:start', { + cachedContacts: contacts.value.length + }) try { const hadLoadedAccountSnapshot = !!chatAccounts.loaded await chatAccounts.ensureLoaded() + trace.log('loadContacts:accounts-ready', { + hadLoadedAccountSnapshot, + availableAccounts: Array.isArray(chatAccounts?.accounts) ? chatAccounts.accounts.length : 0 + }) if (!selectedAccount.value && hadLoadedAccountSnapshot) { await chatAccounts.ensureLoaded({ force: true }) + trace.log('loadContacts:accounts-refreshed') } if (!selectedAccount.value) { clearContactsState(chatAccounts.error || '未检测到已解密账号,请先解密数据库。') + trace.log('loadContacts:no-account', { + error: contactsError.value + }) return { usedPrefetched: false } } await loadSessionsForSelectedAccount() + trace.log('loadContacts:end', { + contactCount: contacts.value.length + }) return { usedPrefetched: false } } catch (error) { clearContactsState(error?.message || '加载联系人失败') + trace.log('loadContacts:error', { + message: String(error?.message || '') + }) return { usedPrefetched: false } } finally { isLoadingContacts.value = false + trace.log('loadContacts:exit', { + loading: isLoadingContacts.value, + error: contactsError.value + }) } }