From 64630e1b4e80ac8d61b8f06a95b555e5ccc1373c Mon Sep 17 00:00:00 2001 From: zhuoqinglian <1035449612@qq.com> Date: Wed, 8 Apr 2026 16:26:48 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20mobile=20=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=B7=BB=E5=8A=A0=E5=B0=8F=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-mobile/src/api/agent/types.ts | 1 + .../src/api/chat-history/types.ts | 1 + main/manager-mobile/src/i18n/de.ts | 3 +- main/manager-mobile/src/i18n/en.ts | 3 +- main/manager-mobile/src/i18n/pt_BR.ts | 3 +- main/manager-mobile/src/i18n/vi.ts | 3 +- main/manager-mobile/src/i18n/zh_CN.ts | 3 +- main/manager-mobile/src/i18n/zh_TW.ts | 3 +- main/manager-mobile/src/pages/agent/edit.vue | 28 ++++++++++++++++++- 9 files changed, 41 insertions(+), 7 deletions(-) diff --git a/main/manager-mobile/src/api/agent/types.ts b/main/manager-mobile/src/api/agent/types.ts index 2c61c80f..f04ef3d1 100644 --- a/main/manager-mobile/src/api/agent/types.ts +++ b/main/manager-mobile/src/api/agent/types.ts @@ -28,6 +28,7 @@ export interface AgentDetail { asrModelId: string vadModelId: string llmModelId: string + slmModelId: string vllmModelId: string ttsModelId: string ttsVoiceId: string diff --git a/main/manager-mobile/src/api/chat-history/types.ts b/main/manager-mobile/src/api/chat-history/types.ts index b895ed47..32bcf8d7 100644 --- a/main/manager-mobile/src/api/chat-history/types.ts +++ b/main/manager-mobile/src/api/chat-history/types.ts @@ -3,6 +3,7 @@ export interface ChatSession { sessionId: string createdAt: string chatCount: number + title: string } // 聊天会话列表响应 diff --git a/main/manager-mobile/src/i18n/de.ts b/main/manager-mobile/src/i18n/de.ts index d76a4093..d2b24453 100644 --- a/main/manager-mobile/src/i18n/de.ts +++ b/main/manager-mobile/src/i18n/de.ts @@ -108,7 +108,8 @@ export default { 'agent.modelConfig': 'Modellkonfiguration', 'agent.vad': 'Sprachaktivitätserkennung', 'agent.asr': 'Spracherkennung', - 'agent.llm': 'Großes Sprachmodell', + 'agent.llm': 'Hauptsprachenmodell', + 'agent.slm': 'Kleine Parametermodelle', 'agent.vllm': 'Vision-Sprachmodell', 'agent.intent': 'Absichtserkennung', 'agent.memory': 'Speicher', diff --git a/main/manager-mobile/src/i18n/en.ts b/main/manager-mobile/src/i18n/en.ts index 0066b0fd..2a39b551 100644 --- a/main/manager-mobile/src/i18n/en.ts +++ b/main/manager-mobile/src/i18n/en.ts @@ -108,7 +108,8 @@ export default { 'agent.modelConfig': 'Model Configuration', 'agent.vad': 'Voice Activity Detection', 'agent.asr': 'Speech Recognition', - 'agent.llm': 'Large Language Model', + 'agent.llm': 'Main language model', + 'agent.slm': 'Small parameter model', 'agent.vllm': 'Vision Language Model', 'agent.intent': 'Intent Recognition', 'agent.memory': 'Memory', diff --git a/main/manager-mobile/src/i18n/pt_BR.ts b/main/manager-mobile/src/i18n/pt_BR.ts index 416b1f8f..79b6b700 100644 --- a/main/manager-mobile/src/i18n/pt_BR.ts +++ b/main/manager-mobile/src/i18n/pt_BR.ts @@ -108,7 +108,8 @@ export default { 'agent.modelConfig': 'Configuração do Modelo', 'agent.vad': 'Detecção de Atividade de Voz', 'agent.asr': 'Reconhecimento de Fala', - 'agent.llm': 'Modelo de Linguagem Grande', + 'agent.llm': 'Modelo de Linguagem Principal', + 'agent.slm': 'Modelo de pequenos parâmetros', 'agent.vllm': 'Modelo de Linguagem Visual', 'agent.intent': 'Reconhecimento de Intenção', 'agent.memory': 'Memória', diff --git a/main/manager-mobile/src/i18n/vi.ts b/main/manager-mobile/src/i18n/vi.ts index 8ee1a35e..15830036 100644 --- a/main/manager-mobile/src/i18n/vi.ts +++ b/main/manager-mobile/src/i18n/vi.ts @@ -108,7 +108,8 @@ export default { 'agent.modelConfig': 'Cấu hình mô hình', 'agent.vad': 'Phát hiện hoạt động giọng nói', 'agent.asr': 'Nhận dạng giọng nói', - 'agent.llm': 'Mô hình ngôn ngữ lớn', + 'agent.llm': 'Mô hình ngôn ngữ chính', + 'agent.slm': 'Mô hình tham số nhỏ', 'agent.vllm': 'Mô hình ngôn ngữ thị giác', 'agent.intent': 'Nhận dạng ý định', 'agent.memory': 'Bộ nhớ', diff --git a/main/manager-mobile/src/i18n/zh_CN.ts b/main/manager-mobile/src/i18n/zh_CN.ts index a5dec898..16366b20 100644 --- a/main/manager-mobile/src/i18n/zh_CN.ts +++ b/main/manager-mobile/src/i18n/zh_CN.ts @@ -108,7 +108,8 @@ export default { 'agent.modelConfig': '模型配置', 'agent.vad': '语音活动检测', 'agent.asr': '语音识别', - 'agent.llm': '大语言模型', + 'agent.llm': '主语言模型', + 'agent.slm': '小参数模型', 'agent.vllm': '视觉大模型', 'agent.intent': '意图识别', 'agent.memory': '记忆', diff --git a/main/manager-mobile/src/i18n/zh_TW.ts b/main/manager-mobile/src/i18n/zh_TW.ts index 9125e296..d2247c53 100644 --- a/main/manager-mobile/src/i18n/zh_TW.ts +++ b/main/manager-mobile/src/i18n/zh_TW.ts @@ -129,7 +129,8 @@ export default { 'agent.modelConfig': '模型配置', 'agent.vad': '語音活動檢測', 'agent.asr': '語音識別', - 'agent.llm': '大語言模型', + 'agent.llm': '主語言模型', + 'agent.slm': '小參數模型', 'agent.vllm': '視覺大模型', 'agent.intent': '意圖識別', 'agent.memory': '記憶', diff --git a/main/manager-mobile/src/pages/agent/edit.vue b/main/manager-mobile/src/pages/agent/edit.vue index ef61477f..cc1844d2 100644 --- a/main/manager-mobile/src/pages/agent/edit.vue +++ b/main/manager-mobile/src/pages/agent/edit.vue @@ -29,6 +29,7 @@ const formData = ref>({ vadModelId: '', asrModelId: '', llmModelId: '', + slmModelId: '', vllmModelId: '', intentModelId: '', memModelId: '', @@ -46,6 +47,7 @@ const displayNames = ref({ vad: t('agent.pleaseSelect'), asr: t('agent.pleaseSelect'), llm: t('agent.pleaseSelect'), + slm: t('agent.pleaseSelect'), vllm: t('agent.pleaseSelect'), intent: t('agent.pleaseSelect'), memory: t('agent.pleaseSelect'), @@ -94,6 +96,7 @@ const pickerShow = ref<{ vad: false, asr: false, llm: false, + slm: false, vllm: false, intent: false, memory: false, @@ -272,6 +275,7 @@ function updateDisplayNames() { displayNames.value.vad = getModelDisplayName('VAD', formData.value.vadModelId) displayNames.value.asr = getModelDisplayName('ASR', formData.value.asrModelId) displayNames.value.llm = getModelDisplayName('LLM', formData.value.llmModelId) + displayNames.value.slm = getModelDisplayName('LLM', formData.value.slmModelId) displayNames.value.vllm = getModelDisplayName('VLLM', formData.value.vllmModelId) displayNames.value.intent = getModelDisplayName('Intent', formData.value.intentModelId) displayNames.value.memory = getModelDisplayName('Memory', formData.value.memModelId) @@ -437,6 +441,7 @@ function selectRoleTemplate(templateId: string) { vadModelId: template.vadModelId || formData.value.vadModelId, asrModelId: template.asrModelId || formData.value.asrModelId, llmModelId: template.llmModelId || formData.value.llmModelId, + slmModelId: template.llmModelId || formData.value.slmModelId, vllmModelId: template.vllmModelId || formData.value.vllmModelId, intentModelId: template.intentModelId || formData.value.intentModelId, memModelId: template.memModelId || formData.value.memModelId, @@ -474,6 +479,9 @@ async function onPickerConfirm(type: string, value: any, name: string) { case 'llm': formData.value.llmModelId = value break + case 'slm': + formData.value.slmModelId = value + break case 'vllm': formData.value.vllmModelId = value break @@ -490,7 +498,8 @@ async function onPickerConfirm(type: string, value: any, name: string) { if (value === 'Memory_nomem' || value === 'Memory_mem_report_only') { tempSummaryMemory.value = formData.value.summaryMemory formData.value.summaryMemory = '' - } else if (tempSummaryMemory.value !== '' && formData.value.summaryMemory === '') { + } + else if (tempSummaryMemory.value !== '' && formData.value.summaryMemory === '') { formData.value.summaryMemory = tempSummaryMemory.value tempSummaryMemory.value = '' } @@ -839,6 +848,16 @@ onMounted(async () => { + + + {{ t('agent.slm') }} + + + {{ displayNames.slm }} + + + + {{ t('agent.vllm') }} @@ -995,6 +1014,13 @@ onMounted(async () => { @select="({ item }) => onPickerConfirm('llm', item.value, item.name)" /> + + Date: Thu, 9 Apr 2026 14:32:32 +0800 Subject: [PATCH 2/3] =?UTF-8?q?update:=20mobile=20=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=A0=87=E9=A2=98=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E8=AF=A6=E6=83=85=E5=8F=82=E6=95=B0=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=8A=98=E5=8F=A0=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/chat-history/types.ts | 2 +- .../src/pages/chat-history/detail.vue | 168 +++++++++++++++++- .../src/pages/chat-history/index.vue | 31 ++-- 3 files changed, 182 insertions(+), 19 deletions(-) diff --git a/main/manager-mobile/src/api/chat-history/types.ts b/main/manager-mobile/src/api/chat-history/types.ts index 32bcf8d7..9652db6d 100644 --- a/main/manager-mobile/src/api/chat-history/types.ts +++ b/main/manager-mobile/src/api/chat-history/types.ts @@ -15,7 +15,7 @@ export interface ChatSessionsResponse { // 聊天消息 export interface ChatMessage { createdAt: string - chatType: 1 | 2 // 1是用户,2是AI + chatType: 1 | 2 | 3 // 1是用户,2是AI,3是参数说明 content: string audioId: string | null macAddress: string diff --git a/main/manager-mobile/src/pages/chat-history/detail.vue b/main/manager-mobile/src/pages/chat-history/detail.vue index c3d399d5..e60bd93b 100644 --- a/main/manager-mobile/src/pages/chat-history/detail.vue +++ b/main/manager-mobile/src/pages/chat-history/detail.vue @@ -61,6 +61,7 @@ const loading = ref(false) // 音频播放相关 const audioContext = ref(null) const playingAudioId = ref(null) +const expandedToolResults = ref({}) // 返回上一页 function goBack() { @@ -124,8 +125,50 @@ function getSpeakerName(message: ChatMessage): string { // 格式化时间 function formatTime(timeStr: string) { - const date = new Date(timeStr) - return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` + if (!timeStr) + return t('chatHistory.unknownTime') + + // 处理时间字符串,确保格式正确 + const date = new Date(timeStr.replace(' ', 'T')) // 转换为ISO格式 + const now = new Date() + + // 检查日期是否有效 + if (Number.isNaN(date.getTime())) { + return timeStr // 如果解析失败,直接返回原字符串 + } + + const diff = now.getTime() - date.getTime() + + // 小于1分钟 + if (diff < 60000) + return t('chatHistory.justNow') + + // 小于1小时 + if (diff < 3600000) + return t('chatHistory.minutesAgo', { minutes: Math.floor(diff / 60000) }) + + // 小于1天(24小时) + if (diff < 86400000) + return t('chatHistory.hoursAgo', { hours: Math.floor(diff / 3600000) }) + + // 小于7天 + if (diff < 604800000) { + const days = Math.floor(diff / 86400000) + return t('chatHistory.daysAgo', { days }) + } + + // 超过7天,显示具体日期 + const year = date.getFullYear() + const month = String(date.getMonth() + 1).padStart(2, '0') + const day = String(date.getDate()).padStart(2, '0') + const currentYear = now.getFullYear() + + // 如果是当前年份,不显示年份 + if (year === currentYear) { + return `${month}-${day}` + } + + return `${year}-${month}-${day}` } // 播放音频 @@ -192,6 +235,52 @@ const playAudio = debounce(async (audioId: string) => { } }, 400) +function extractContentFromString(content: string) { + if (!content || content.trim() === '') { + return content + } + + // 尝试解析为 JSON + try { + const jsonObj = JSON.parse(content) + + // 如果是数组格式(包含 text 和 tool) + if (Array.isArray(jsonObj)) { + return jsonObj + } + + // 如果是对象且有 content 字段 + if (jsonObj && typeof jsonObj === 'object' && jsonObj.content) { + return jsonObj.content + } + } + catch (e) { + // 如果不是有效的 JSON,直接返回原内容 + } + + // 如果不是 JSON 格式或没有 content 字段,直接返回原内容 + return content +} + +function toggleToolResult(messageIndex, itemIndex) { + const key = `${messageIndex}-${itemIndex}` + expandedToolResults.value[key] = !expandedToolResults.value[key] +} + +function isToolResultCollapsed(messageIndex, itemIndex) { + const key = `${messageIndex}-${itemIndex}` + // 默认折叠(true表示折叠) + return !expandedToolResults.value[key] +} + +function getFirstLineText(text: string) { + if (!text) { + return '' + } + const firstLine = text.split('\n')[0] + return firstLine.length < text.length ? `${firstLine}...` : text +} + onLoad((options) => { if (options?.sessionId && options?.agentId) { sessionId.value = options.sessionId @@ -256,6 +345,7 @@ onUnload(() => { :class="{ 'items-end': message.chatType === 1, 'items-start': message.chatType === 2, + 'tool-message': message.chatType === 3, }" > @@ -263,11 +353,39 @@ onUnload(() => { class="shadow-message break-words rounded-[20rpx] p-[24rpx] leading-[1.4]" :class="{ 'bg-[#336cff] text-white': message.chatType === 1, - 'bg-white text-[#232338] border border-[#eeeeee]': message.chatType === 2, + 'bg-white text-[#232338] border border-[#eeeeee]': [2, 3].includes(message.chatType), }" > + - + { .animate-pulse-audio { animation: pulse-audio 1.5s infinite; } + +.text-content { + display: block; + margin-bottom: 8rpx; +} + +.tool-call-text { + color: #1890ff; + font-family: 'Courier New', monospace; + font-weight: 500; + font-size: 24rpx; + display: block; + margin-top: 8rpx; +} + +.user-message .tool-call-text { + color: #e6f7ff; +} + +.tool-message .message-content { + background-color: #f0f0f0; +} + +.tool-result-wrapper { + position: relative; + padding-right: 40rpx; +} + +.tool-result-collapsed { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.tool-toggle-btn { + position: absolute; + right: 0; + top: 0; + cursor: pointer; + color: #1890ff; + font-size: 24rpx; +} diff --git a/main/manager-mobile/src/pages/chat-history/index.vue b/main/manager-mobile/src/pages/chat-history/index.vue index aa5c8414..b922ccdd 100644 --- a/main/manager-mobile/src/pages/chat-history/index.vue +++ b/main/manager-mobile/src/pages/chat-history/index.vue @@ -8,15 +8,15 @@ defineOptions({ name: 'ChatHistory', }) +const props = withDefaults(defineProps(), { + agentId: 'default', +}) + // 接收props interface Props { agentId?: string } -const props = withDefaults(defineProps(), { - agentId: 'default' -}) - // 获取屏幕边界到安全区域距离 let safeAreaInsets: any let systemInfo: any @@ -187,8 +187,8 @@ defineExpose({ - {{ t('chatHistory.loading') }} - + {{ t('chatHistory.loading') }} + @@ -205,7 +205,7 @@ defineExpose({ - {{ t('chatHistory.conversationRecord') }} {{ session.sessionId.substring(0, 8) }}... + {{ session.title || `${t('chatHistory.conversationRecord')} ${session.sessionId.substring(0, 8)}...` }} {{ formatTime(session.createdAt) }} @@ -242,11 +242,11 @@ defineExpose({ - {{ t('chatHistory.noChatRecords') }} - - - {{ t('chatHistory.chatRecordsDescription') }} - + {{ t('chatHistory.noChatRecords') }} + + + {{ t('chatHistory.chatRecordsDescription') }} + @@ -333,7 +333,7 @@ defineExpose({ padding: 32rpx; .session-info { - flex: 1; + width: 94%; .session-header { display: flex; @@ -345,8 +345,11 @@ defineExpose({ font-size: 32rpx; font-weight: 600; color: #232338; - max-width: 70%; + width: 70%; word-break: break-all; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .session-time { From 2cd9484c54959fb01dc9bf493c57d819336e4434 Mon Sep 17 00:00:00 2001 From: zhuoqinglian <1035449612@qq.com> Date: Fri, 10 Apr 2026 16:11:04 +0800 Subject: [PATCH 3/3] =?UTF-8?q?update:=20mobile=20=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=8E=B7=E5=8F=96=E6=9C=80=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/chat-history/chat-history.ts | 3 ++ main/manager-mobile/src/pages/agent/edit.vue | 1 - .../src/pages/chat-history/detail.vue | 5 ++- .../src/pages/chat-history/index.vue | 19 +++++++---- main/manager-mobile/src/utils/index.ts | 33 +++++++++++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/main/manager-mobile/src/api/chat-history/chat-history.ts b/main/manager-mobile/src/api/chat-history/chat-history.ts index 89c3f1ef..dc7000ef 100644 --- a/main/manager-mobile/src/api/chat-history/chat-history.ts +++ b/main/manager-mobile/src/api/chat-history/chat-history.ts @@ -34,6 +34,9 @@ export function getChatHistory(agentId: string, sessionId: string) { ignoreAuth: false, toast: false, }, + cacheFor: { + expire: -1, + }, }) } diff --git a/main/manager-mobile/src/pages/agent/edit.vue b/main/manager-mobile/src/pages/agent/edit.vue index cc1844d2..964ad0b2 100644 --- a/main/manager-mobile/src/pages/agent/edit.vue +++ b/main/manager-mobile/src/pages/agent/edit.vue @@ -283,7 +283,6 @@ function updateDisplayNames() { // 角色音色特殊处理 displayNames.value.report = reportOptions.find(item => item.value === formData.value.chatHistoryConf)?.name - displayNames.value.language = formData.value.ttsLanguage isVisibleReport.value = formData.value.memModelId !== 'Memory_nomem' diff --git a/main/manager-mobile/src/pages/chat-history/detail.vue b/main/manager-mobile/src/pages/chat-history/detail.vue index e60bd93b..ae2d3d41 100644 --- a/main/manager-mobile/src/pages/chat-history/detail.vue +++ b/main/manager-mobile/src/pages/chat-history/detail.vue @@ -285,7 +285,6 @@ onLoad((options) => { if (options?.sessionId && options?.agentId) { sessionId.value = options.sessionId agentId.value = options.agentId - loadChatHistory() } else { console.error('缺少必要参数') @@ -293,6 +292,10 @@ onLoad((options) => { } }) +onShow(() => { + loadChatHistory() +}) + // 页面销毁时清理音频资源 onUnload(() => { if (audioContext.value) { diff --git a/main/manager-mobile/src/pages/chat-history/index.vue b/main/manager-mobile/src/pages/chat-history/index.vue index b922ccdd..b4cf6d97 100644 --- a/main/manager-mobile/src/pages/chat-history/index.vue +++ b/main/manager-mobile/src/pages/chat-history/index.vue @@ -3,6 +3,7 @@ import type { ChatSession } from '@/api/chat-history/types' import { computed, onMounted, ref } from 'vue' import { getChatSessions } from '@/api/chat-history/chat-history' import { t } from '@/i18n' +import { deepClone } from '@/utils' defineOptions({ name: 'ChatHistory', @@ -43,7 +44,7 @@ const sessionList = ref([]) const loading = ref(false) const loadingMore = ref(false) const hasMore = ref(true) -const currentPage = ref(1) +const currentPage = ref(0) const pageSize = 10 // 使用传入的智能体ID @@ -52,10 +53,8 @@ const currentAgentId = computed(() => { }) // 加载聊天会话列表 -async function loadChatSessions(page = 1, isRefresh = false) { +async function loadChatSessions(page = 1, isUpdate = false) { try { - console.log(t('chatHistory.getChatSessions'), { page, isRefresh }) - // 检查是否有当前选中的智能体 if (!currentAgentId.value) { console.warn(t('chatHistory.noSelectedAgent')) @@ -76,7 +75,10 @@ async function loadChatSessions(page = 1, isRefresh = false) { }) if (page === 1) { - sessionList.value = response.list || [] + const oldSessionList = deepClone(sessionList.value) + oldSessionList.splice(0, 10) + oldSessionList.unshift(...(response.list || [])) + sessionList.value = isUpdate ? oldSessionList : response.list || [] } else { sessionList.value.push(...(response.list || [])) @@ -170,10 +172,15 @@ function goToChatDetail(session: ChatSession) { onMounted(async () => { // 智能体已简化为默认 - loadChatSessions(1) }) +onShow(() => { + if (currentPage.value !== 0) { + loadChatSessions(1, true) + } +}) + // 暴露方法给父组件 defineExpose({ refresh, diff --git a/main/manager-mobile/src/utils/index.ts b/main/manager-mobile/src/utils/index.ts index 8ac842c8..4017494a 100644 --- a/main/manager-mobile/src/utils/index.ts +++ b/main/manager-mobile/src/utils/index.ts @@ -300,3 +300,36 @@ export function debounce( return debounced } + +type DeepCloneTarget = string | number | boolean | null | undefined | object + +/** + * 深拷贝方法 + * @param target 要拷贝的目标 + * @returns 拷贝后的新对象 + */ +export function deepClone(target: T): T { + if (target === null || typeof target !== 'object') { + return target + } + + if (target instanceof Date) { + return new Date(target.getTime()) as any + } + + if (Array.isArray(target)) { + return target.map(item => deepClone(item)) as any + } + + if (target instanceof Object) { + const clonedObj = {} as T + for (const key in target) { + if (Object.prototype.hasOwnProperty.call(target, key)) { + (clonedObj as any)[key] = deepClone((target as any)[key]) + } + } + return clonedObj + } + + return target +}