diff --git a/public/images/avatars/custom.png b/public/images/avatars/custom.png new file mode 100644 index 0000000..a08241b Binary files /dev/null and b/public/images/avatars/custom.png differ diff --git a/public/images/avatars/user.png b/public/images/avatars/user.png new file mode 100644 index 0000000..8328b07 Binary files /dev/null and b/public/images/avatars/user.png differ diff --git a/src/mocks/modules/chat.js b/src/mocks/modules/chat.js index 31541d1..8a369d7 100644 --- a/src/mocks/modules/chat.js +++ b/src/mocks/modules/chat.js @@ -36,7 +36,7 @@ const chatModes = { export default [ // 获取聊天模式配置 { - url: '/api/chat/modes', + url: 'api/chat/modes', method: 'POST', response: () => { return { diff --git a/src/store/chat.js b/src/store/chat.js index e1e2fa6..74ce7dc 100644 --- a/src/store/chat.js +++ b/src/store/chat.js @@ -1,53 +1,45 @@ import { getChatModes, sendMessage } from '@/api/chat' import { useSpeakStore } from '@/store/speak' +import { processTagContent } from '@/utils' import { speakText } from '@/utils/speak' import { defineStore } from 'pinia' - +const chatModes = { + training: { + name: "寿险代理人AI陪练", + customerAvatar: "/images/avatars/custom.png", + userAvatar: "/images/avatars/user.png", + token: "app-88ae2GN49aUyNO6qGg7tbTfX", + background: "# 客户信息\n### **基础背景与性格设定** \n**姓名**:刘勇 \n**年龄**:34 岁 \n**职业**:腾讯员工 \n**家庭**:妻子为全职太太,女儿 9 岁,双方父母健在 \n**性格**:冷静、理性、务实,偏好客观数据和逻辑分析,对保险持观望态度,防备心较强 \n**近期状态**: \n- 体检显示中度脂肪肝,担心投保问题 \n- 已为全家配置百万医疗险,妻子有重疾险,但认为保障不足 (保费 50W)\n- 对理财型保险收益不认可,但对补充重疾险有潜在需求 ", + chatBackground: "通过尚先生介绍,你和他的老同学在他的家里首次面谈。他的家中布置简洁,茶几上摆放着一套茶具。" + }, + ai_agent: { + name: "AI寿险代理人", + customerAvatar: "/images/avatars/user.png", + userAvatar: "/images/avatars/custom.png", + token: "app-eYoE51WXCaEKXvzb9ZCj6lBn", + background: "# AI寿险代理人\n### **角色设定** \n**身份**:AI寿险代理人 \n**特点**:专业、耐心、细致、富有同理心 \n**能力**:\n- 精通各类寿险产品知识\n- 擅长需求分析和方案定制\n- 具备优秀的沟通和谈判技巧\n- 能够处理各类客户异议\n- 熟悉保险行业最新动态", + chatBackground: "一位AI的保险代理人,通过聊天来确认用户的需求,收集用户的基本信息" + }, + quote_objection: { + name: "报价中异议", + customerAvatar: "/images/avatars/custom.png", + userAvatar: "/images/avatars/user.png", + token: "app-ur2Altw2LHR6niX8Q1S7Cn41", + background: "# 客户信息\n### **基础背景与性格设定** \n**姓名**: 王大牛\n**年龄**: 40岁\n**职业**: 私营企业主\n**车牌号**: 闽C12345\n**车辆信息**:拥有一辆行驶4年的大众途观L,购买价格约25万,主要用于商务出行及家庭使用。\n**投保信息**:在其他保司购买交强险及商业险(三者险、车损险),还有1个月到期。有过一次轻微追尾事故,已通过保司处理。王先生对成本控制非常敏感,但也能意识到保险的重要性。他希望得到保障全面、价格合理的产品和优质的服务。", + chatBackground: "现在你将扮演坐席专员,与系统扮演的客户针对报价中的各类异议开展对练,着重训练处理\"报价中异议\"的能力。按照\"保全保足\"原则给出险种推荐方案。" + }, + post_quote_objection: { + name: "报价后异议", + customerAvatar: "/images/avatars/custom.png", + userAvatar: "/images/avatars/user.png", + token: "app-Yiccl0JoXs2QF2lkHxO6f822", + background: "# 客户信息\n### **基础背景与性格设定** \n**姓名**: 张灵女士\n**年龄**: 32岁\n**职业**: 公司行政主管\n**车牌号**: 粤B56789\n**车辆信息**:拥有一辆行驶4年多的本田思域,1.5T,CVT燃动版,购买价格约15万,用于日常通勤及周末短途出行。\n**投保信息**:在其他保司购买交强险及商业险(三者险、车损险、不计免赔险、车上人员责任险),还有2个月到期。无事故记录,仅有两次违章停车记录。张女士注重性价比和服务质量,希望保险的保障全面、价格合理、服务贴心。", + chatBackground: "现在你将扮演坐席专员,与系统扮演的客户针对报价后的各类异议开展对练,着重训练处理\"报价后异议\"的能力。按照\"保全保足\"原则给出险种推荐方案。" + } +} const tagList = ['kehu', 'pingfen', 'zongjie', 'huaxiang', 'dafen'] -// 处理标签内容的辅助函数 -const processTagContent = (answerCache, tagName, cachedMessage) => { - // 检查是否包含完整的开始标签 - const startTag = `<${tagName}>` - const endTag = `` - // 如果正在处理该标签 - if (cachedMessage.respondingType === tagName) { - // 检查是否包含结束标签 - if (answerCache.includes(endTag)) { - // 提取结束标签前的内容 - const content = answerCache.split(endTag)[0] - cachedMessage[tagName] += content - cachedMessage.respondingType = '' - cachedMessage[`${tagName}Finished`] = true - return answerCache.split(endTag)[1] || '' - } else { - // 继续累积内容 - cachedMessage[tagName] += answerCache - return '' - } - } - - // 检查是否包含开始标签 - if (answerCache.includes(startTag)) { - cachedMessage.respondingType = tagName - const tagContent = answerCache.split(startTag)[1] || '' - - // 检查开始标签后是否立即包含结束标签 - if (tagContent.includes(endTag)) { - const content = tagContent.split(endTag)[0] - cachedMessage[tagName] = content - cachedMessage.respondingType = '' - return answerCache.split(endTag)[1] || '' - } else { - // 只有开始标签,累积内容 - cachedMessage[tagName] = tagContent - return '' - } - } - - return answerCache -} export const useChatStore = defineStore('chat', { state: () => ({ @@ -55,11 +47,11 @@ export const useChatStore = defineStore('chat', { currentMode: 'training', conversationId: null, messageCache: {}, - chatModes: {}, chatModesLoading: false, }), getters: { + chatModes: () => chatModes, currentToken: (state) => state.chatModes[state.currentMode]?.token, currentConversation: (state) => { return state.conversationId ? state.messages[state.conversationId] : null diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000..b7879bf --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,43 @@ +// 处理标签内容的辅助函数 +export const processTagContent = (answerCache, tagName, cachedMessage) => { + // 检查是否包含完整的开始标签 + const startTag = `<${tagName}>` + const endTag = `` + + // 如果正在处理该标签 + if (cachedMessage.respondingType === tagName) { + // 检查是否包含结束标签 + if (answerCache.includes(endTag)) { + // 提取结束标签前的内容 + const content = answerCache.split(endTag)[0] + cachedMessage[tagName] += content + cachedMessage.respondingType = '' + cachedMessage[`${tagName}Finished`] = true + return answerCache.split(endTag)[1] || '' + } else { + // 继续累积内容 + cachedMessage[tagName] += answerCache + return '' + } + } + + // 检查是否包含开始标签 + if (answerCache.includes(startTag)) { + cachedMessage.respondingType = tagName + const tagContent = answerCache.split(startTag)[1] || '' + + // 检查开始标签后是否立即包含结束标签 + if (tagContent.includes(endTag)) { + const content = tagContent.split(endTag)[0] + cachedMessage[tagName] = content + cachedMessage.respondingType = '' + return answerCache.split(endTag)[1] || '' + } else { + // 只有开始标签,累积内容 + cachedMessage[tagName] = tagContent + return '' + } + } + + return answerCache +} diff --git a/src/views/chat/ChatInterface.vue b/src/views/chat/ChatInterface.vue index 2b813f5..4e668b7 100644 --- a/src/views/chat/ChatInterface.vue +++ b/src/views/chat/ChatInterface.vue @@ -19,7 +19,7 @@
- +
@@ -53,7 +53,7 @@
- +
@@ -155,13 +155,20 @@ const lastMessageWithDafen = computed(() => { // 获取头像 const customerAvatar = computed(() => { - return chatStore.chatModes[chatStore.currentMode]?.customerAvatar + const avatar = chatStore.chatModes[chatStore.currentMode]?.customerAvatar + return avatar || '/logo.svg' }) const userAvatar = computed(() => { - return chatStore.chatModes[chatStore.currentMode]?.userAvatar + const avatar = chatStore.chatModes[chatStore.currentMode]?.userAvatar + return avatar || '/logo.svg' }) +// 处理头像加载错误 +const handleAvatarError = (event) => { + event.target.src = '/logo.svg' +} + // 配置marked选项 marked.setOptions({ breaks: true, @@ -293,13 +300,6 @@ const formatMarkdown = (text, tagType) => { return marked.parse(text) } -// 调整文本区域高度 -const adjustTextareaHeight = () => { - const textarea = messageInputRef.value - textarea.style.height = 'auto' - const newHeight = Math.min(textarea.scrollHeight, maxRows * 24) - textarea.style.height = newHeight + 'px' -} // 切换总结面板 const toggleSummary = () => { diff --git a/src/views/chat/ChatModeSelector.vue b/src/views/chat/ChatModeSelector.vue index 78f5456..0ec1d81 100644 --- a/src/views/chat/ChatModeSelector.vue +++ b/src/views/chat/ChatModeSelector.vue @@ -23,19 +23,17 @@ -
+ - + + + + + {{ mode.name }} + @@ -64,7 +62,7 @@ import { useChatStore } from '@/store/chat' import { useSettingsStore } from '@/store/settings' import ChatHistoryList from '@/views/chat/components/ChatHistoryList.vue' import { ChatDotRound, ChatLineRound, ChatRound, Plus, Service, Setting } from '@element-plus/icons-vue' -import { ElIcon, ElMenu, ElMenuItem, ElPopover, ElSkeleton } from 'element-plus' +import { ElIcon, ElMenu, ElMenuItem, ElPopover } from 'element-plus' import { computed, onMounted, onUnmounted, ref } from 'vue' const chatStore = useChatStore() @@ -80,14 +78,15 @@ const loadChatModes = async () => { } const chatModes = computed(() => { - console.log(chatStore, 'chatModes') - return Object.keys(chatStore.chatModes).map(key => ({ + const modes = Object.keys(chatStore.chatModes).map(key => ({ id: key, name: chatStore.chatModes[key].name, icon: key === 'general' ? ChatRound : key === 'assistant' ? Service : key === 'chat' ? ChatDotRound : ChatLineRound })) + console.log('%c chatModes:', 'color: #2196F3; font-weight: bold', modes) + return modes }) @@ -115,7 +114,7 @@ onMounted(() => { checkMobile() window.addEventListener('resize', checkMobile) // 加载聊天模式 - loadChatModes() + // loadChatModes() }) onUnmounted(() => { diff --git a/vite.config.js b/vite.config.js index f26dfc0..7cab18d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -29,7 +29,7 @@ export default defineConfig(({ command, mode }) => { viteMockServe({ mockPath: 'src/mocks', localEnabled: true, - prodEnabled: false, + prodEnabled: true, supportTs: true, watchFiles: true, logger: true,