import { joinLlmUrl, LLM_API_KEY } from '@/config/llm' import { mediaUrl, SENDER_ROLE_USER, SENDER_ROLE_AI } from '@/utils/aiConsult' export { SENDER_ROLE_USER, SENDER_ROLE_AI } export const MODEL_OPTION_DEFS = [ { value: 'auto', labelKey: 'modelAuto', shortKey: 'modelAutoShort', descKey: 'modelAutoDesc', icon: 'grid-fill' }, { value: 'yak-disease', labelKey: 'modelDisease', shortKey: 'modelDiseaseShort', descKey: 'modelDiseaseDesc', icon: 'order' }, { value: 'yak-general', labelKey: 'modelGeneral', shortKey: 'modelGeneralShort', descKey: 'modelGeneralDesc', icon: 'chat' }, { value: 'yak-feeding', labelKey: 'modelFeeding', shortKey: 'modelFeedingShort', descKey: 'modelFeedingDesc', icon: 'shopping-cart' }, { value: 'yak-growth', labelKey: 'modelGrowth', shortKey: 'modelGrowthShort', descKey: 'modelGrowthDesc', icon: 'chat' } ] export const MEDIA_RULES = { image: { exts: ['jpg', 'jpeg', 'png', 'gif'], maxMb: 10, errFmt: 'errImageFmt', errMb: 'errImageMb' }, video: { exts: ['mp4', 'mov'], maxMb: 50, errFmt: 'errVideoFmt', errMb: 'errVideoMb' }, voice: { exts: ['mp3', 'm4a', 'wav'], maxMb: 10, errFmt: 'errVoiceFmt', errMb: 'errVoiceMb' } } export function genLocalId() { return 'm_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 9) } export function extOf(fileName) { if (!fileName || fileName.lastIndexOf('.') < 0) { return '' } return fileName.slice(fileName.lastIndexOf('.') + 1).toLowerCase() } /** 与大模型 SSE/JSON 一致:id 即为网关 sessionId */ export function extractLlmSessionId(data) { if (!data || typeof data !== 'object') { return null } if (data.id != null && data.id !== '') { return String(data.id) } if (data.session_id != null && data.session_id !== '') { return String(data.session_id) } if (data.sessionId != null && data.sessionId !== '') { return String(data.sessionId) } return null } export function extractAssistantText(data) { try { const choice = data.choices && data.choices[0] const msg = choice && choice.message return (msg && msg.content) || '' } catch (e) { return '' } } export function contentForLlm(content) { if (typeof content === 'string') { return content } if (!Array.isArray(content)) { return '' } return content } export function buildUserContentForLlm(text, pendingAttachments) { const parts = [] const imgs = (pendingAttachments || []).filter((a) => a.kind === 'image') for (const im of imgs) { parts.push({ type: 'image_url', image_url: { url: im.url } }) } const body = (text || '').trim() if (body) { parts.push({ type: 'text', text: body }) } if (!parts.length) { return '' } if (parts.length === 1 && parts[0].type === 'text') { return parts[0].text } return parts } export function buildMessagesForLlm(messages, llmSessionId) { if (llmSessionId) { for (let i = (messages || []).length - 1; i >= 0; i--) { const m = messages[i] if (m.senderRole === SENDER_ROLE_USER) { let content = m.content if (m.msgType === 2 && content) { content = [{ type: 'image_url', image_url: { url: mediaUrl(content) } }] } return [{ role: 'user', content: contentForLlm(content) }] } } return [] } const out = [] for (const m of messages || []) { const role = m.senderRole === SENDER_ROLE_AI ? 'assistant' : 'user' let content = m.content if (m.msgType === 2 && content) { content = [{ type: 'image_url', image_url: { url: mediaUrl(content) } }] } out.push({ role, content: contentForLlm(content) }) } return out } export function resolvePayloadUserContent(payload, draft) { if (payload.msgType === 1) { return payload.content } if (payload.msgType === 2) { const parts = [{ type: 'image_url', image_url: { url: mediaUrl(payload.content) } }] const text = (draft || '').trim() if (text) { parts.push({ type: 'text', text }) } return parts.length === 1 && parts[0].type === 'text' ? parts[0].text : parts } return String(payload.content || '') } export function buildPersistSendBody(msgType, payload, draftText, pendingAttachments) { const body = { msgType: msgType || 1 } if (body.msgType === 1) { let text = (draftText || '').trim() || (payload && payload.content ? String(payload.content).trim() : '') if (!text && pendingAttachments && pendingAttachments.length) { const img = pendingAttachments.find((a) => a.kind === 'image') if (img && img.url) { body.msgType = 2 body.content = img.url return body } } body.content = text } else { body.content = (payload && payload.content) || '' if (payload && payload.mediaDuration != null) { body.mediaDuration = payload.mediaDuration } } return body } export function findLastLocalExchangeIndexes(messages) { let userIdx = -1 let aiIdx = -1 for (let i = (messages || []).length - 1; i >= 0; i--) { const m = messages[i] if (!m || !String(m.id).startsWith('m_')) { continue } if (aiIdx < 0 && m.senderRole === SENDER_ROLE_AI) { aiIdx = i } else if (userIdx < 0 && m.senderRole === SENDER_ROLE_USER) { userIdx = i } if (userIdx >= 0 && aiIdx >= 0) { break } } return { userIdx, aiIdx } } /** * 调用大模型 /v1/chat/completions */ export function requestLlmChat(body) { const baseUrl = joinLlmUrl('') if (!baseUrl) { return Promise.reject(new Error('configLlmBase')) } if (!LLM_API_KEY) { return Promise.reject(new Error('configLlmKey')) } const header = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + LLM_API_KEY } return new Promise((resolve, reject) => { uni.request({ url: joinLlmUrl('/v1/chat/completions'), method: 'POST', header, data: body, timeout: 120000, success: (res) => { const httpStatus = res.statusCode || 200 if (httpStatus >= 200 && httpStatus < 300) { const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data resolve(data) return } let msg = 'requestFailed' try { const errBody = typeof res.data === 'string' ? JSON.parse(res.data) : res.data msg = (errBody && (errBody.message || errBody.msg)) || msg } catch (e) { /* ignore */ } reject(new Error(msg)) }, fail: (err) => reject(err || new Error('requestFailed')) }) }) }