import { resolveResourceUrl } from '@/utils/resourceUrl' /** 与后端 ConsultSessionRules 一致 */ export const SENDER_ROLE_USER = 1 export const SENDER_ROLE_VET = 2 export const SENDER_ROLE_AI = 3 export const MSG_TYPE_TEXT = 1 export const MSG_TYPE_IMAGE = 2 export const MSG_TYPE_VIDEO = 3 export const MSG_TYPE_VOICE = 4 export const DEFAULT_SESSION_TITLE = '新会话' export const MESSAGE_PAGE_SIZE = 50 /** 移动端快捷分类:问诊 / 咨询 */ export const AI_CATEGORY = { advise: 1, consult: 2, medicine: 3, device: 4 } function resolveConsultIdFromIdFields(session) { if (!session) return null if (session.id != null && session.id !== '') { return String(session.id) } if (session.realSessionId != null && session.realSessionId !== '') { return String(session.realSessionId) } return null } /** * 若依问诊会话主键(listAiConsultMessages / sendAiConsultMessage 路径参数)。 * 与改造前一致:id → 列表 sessionId → realSessionId。 */ export function resolveConsultSessionId(session) { if (!session) return null if (typeof session === 'string' || typeof session === 'number') { return String(session) } const fromId = resolveConsultIdFromIdFields(session) if (fromId) { return fromId } if (session.sessionId != null && session.sessionId !== '') { return String(session.sessionId) } return null } /** @deprecated 使用 resolveConsultSessionId */ export function resolveSessionId(session) { return resolveConsultSessionId(session) } /** 列表 rows[].sessionId:与若依主键不同时,为大模型网关 sessionId */ export function resolveLlmSessionIdFromRow(session) { if (!session) return null if (session.llmSessionId != null && session.llmSessionId !== '') { return String(session.llmSessionId) } const consultId = resolveConsultIdFromIdFields(session) if (session.sessionId != null && session.sessionId !== '') { const sid = String(session.sessionId) if (!consultId || sid !== consultId) { return sid } } return null } /** 解析当前会话的大模型 sessionId(行内字段 + 可选本地缓存) */ export function resolveLlmSessionId(session, loadFromStorage) { if (!session) return null const consultId = resolveConsultSessionId(session) const fromRow = resolveLlmSessionIdFromRow(session) || (session.llmSessionId != null && session.llmSessionId !== '' ? String(session.llmSessionId) : null) if (fromRow) return fromRow if (consultId && typeof loadFromStorage === 'function') { const stored = loadFromStorage(consultId) if (stored) return String(stored) } return null } /** 列表行标准化:realSessionId → id;sessionId(列表) → llmSessionId */ export function normalizeSessionRow(row, llmByConsultId) { const consultId = resolveConsultSessionId(row) if (!consultId) { return row } const map = llmByConsultId && typeof llmByConsultId === 'object' ? llmByConsultId : {} const llm = resolveLlmSessionIdFromRow(row) || (row.llmSessionId != null && row.llmSessionId !== '' ? String(row.llmSessionId) : null) || map[consultId] || null return { ...row, id: consultId, realSessionId: row.realSessionId != null && row.realSessionId !== '' ? String(row.realSessionId) : consultId, llmSessionId: llm } } export function mergeSessionsPreserveLlmId(rows, prevSessions, loadFromStorage) { const prevMap = {} ;(prevSessions || []).forEach((s) => { const id = resolveSessionId(s) const llm = resolveLlmSessionIdFromRow(s) || s.llmSessionId if (id && llm) { prevMap[id] = String(llm) } }) return (rows || []).map((row) => { const consultId = resolveConsultSessionId(row) if (!consultId) { return row } const stored = typeof loadFromStorage === 'function' ? loadFromStorage(consultId) : null const llm = resolveLlmSessionIdFromRow(row) || prevMap[consultId] || stored || null return normalizeSessionRow(row, llm ? { [consultId]: llm } : prevMap) }) } export function isUserMessage(m) { return m && m.senderRole === SENDER_ROLE_USER } export function isAiMessage(m) { return m && m.senderRole === SENDER_ROLE_AI } export function isVetMessage(m) { return m && m.senderRole === SENDER_ROLE_VET } export function mediaUrl(url) { return resolveResourceUrl(url) } export function normalizeMessage(m) { return { id: m.id || m.messageId, sessionId: m.sessionId, senderRole: m.senderRole, senderName: m.senderName, msgType: m.msgType, content: m.content, mediaDuration: m.mediaDuration, sendTime: m.sendTime } } export function sessionTitle(s, fallback) { if (!s) return fallback || '' const t = s.sessionTitle if (!t || t === DEFAULT_SESSION_TITLE) { return fallback || t || '' } return t } export function formatListDate(time) { if (!time) return '' const d = new Date(time) if (isNaN(d.getTime())) return '' const now = new Date() const isToday = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate() if (isToday) { const h = String(d.getHours()).padStart(2, '0') const min = String(d.getMinutes()).padStart(2, '0') return `${h}:${min}` } return `${d.getMonth() + 1}/${d.getDate()}` } export function formatMsgTime(time) { if (!time) return '' const d = new Date(time) if (isNaN(d.getTime())) return '' const h = String(d.getHours()).padStart(2, '0') const min = String(d.getMinutes()).padStart(2, '0') return `${h}:${min}` } export function dateKey(time) { if (!time) return '' const d = new Date(time) if (isNaN(d.getTime())) return '' return `${d.getFullYear()}-${d.getMonth() + 1}-${d.getDate()}` } export function formatDateDivider(key, tFn) { if (!key) return '' const parts = key.split('-') if (parts.length >= 3) { const month = Number(parts[1]) const day = Number(parts[2]) if (tFn) { return tFn('aiOnlineConsult.dateDivider', { month, day }) } return `${month}月${day}日` } return key } export function formatVoiceDuration(sec) { const s = Math.max(1, Math.round(Number(sec) || 1)) return s + '"' } export function buildMessageGroups(messages, tFn) { const groups = [] let currentKey = null let currentItems = [] for (const m of messages) { const key = dateKey(m.sendTime) if (key !== currentKey) { if (currentItems.length) { groups.push({ dateLabel: formatDateDivider(currentKey, tFn), items: currentItems }) } currentKey = key currentItems = [m] } else { currentItems.push(m) } } if (currentItems.length) { groups.push({ dateLabel: formatDateDivider(currentKey, tFn), items: currentItems }) } return groups } export function previewText(msgType, content, tFn) { if (msgType === MSG_TYPE_IMAGE) return tFn ? tFn('aiOnlineConsult.msgImage') : '[图片]' if (msgType === MSG_TYPE_VIDEO) return tFn ? tFn('aiOnlineConsult.msgVideo') : '[视频]' if (msgType === MSG_TYPE_VOICE) return tFn ? tFn('aiOnlineConsult.msgVoice') : '[语音]' return content || '' }