diff --git a/src/store/emotion.ts b/src/store/emotion.ts index 8435fee..0b7e9b0 100644 --- a/src/store/emotion.ts +++ b/src/store/emotion.ts @@ -7,11 +7,13 @@ export const useEmotionStore = defineStore('emotion', { stockList: [] as StockData[], // 当前显示的股票列表 activeStockIndex: 0, // 当前激活的股票索引 maxStocks: 10, // 最大股票数量限制 + conversations: [] as ConversationMessage[], // 对话消息数组 + maxConversations: 100, // 最大对话数量限制 }), persist: { key: 'emotion-store', storage: sessionStorage, - paths: ['history', 'stockList', 'activeStockIndex'] + paths: ['history', 'stockList', 'activeStockIndex', 'conversations'] }, getters: { // 获取当前激活的股票 @@ -22,6 +24,14 @@ export const useEmotionStore = defineStore('emotion', { stockCount: (state) => state.stockList.length, // 是否达到最大股票数量 isMaxStocks: (state) => state.stockList.length >= state.maxStocks, + // 获取对话数量 + conversationCount: (state) => state.conversations.length, + // 是否达到最大对话数量 + isMaxConversations: (state) => state.conversations.length >= state.maxConversations, + // 获取最近的对话消息 + recentConversations: (state) => { + return state.conversations.slice(-20); // 返回最近20条对话 + }, }, actions: { // 添加历史记录 @@ -118,6 +128,48 @@ export const useEmotionStore = defineStore('emotion', { updateActiveStockConclusion(conclusionData: string) { this.updateStockConclusion(this.activeStockIndex, conclusionData); }, + // 添加对话消息 + addConversation(message: ConversationMessage) { + // 如果达到最大数量,移除最旧的消息 + if (this.conversations.length >= this.maxConversations) { + this.conversations.shift(); + } + // 添加新消息到数组末尾 + this.conversations.push({ + ...message, + timestamp: message.timestamp || new Date().toISOString() + }); + }, + // 批量添加对话消息 + addConversations(messages: ConversationMessage[]) { + messages.forEach(message => { + this.addConversation(message); + }); + }, + // 清空对话记录 + clearConversations() { + this.conversations = []; + }, + // 获取对话记录 + getConversations() { + return this.conversations; + }, + // 删除指定索引的对话消息 + removeConversation(index: number) { + if (index >= 0 && index < this.conversations.length) { + this.conversations.splice(index, 1); + } + }, + // 更新对话消息 + updateConversation(index: number, updatedMessage: Partial) { + if (index >= 0 && index < this.conversations.length) { + this.conversations[index] = { + ...this.conversations[index], + ...updatedMessage, + timestamp: new Date().toISOString() + }; + } + }, }, }); @@ -145,4 +197,13 @@ interface StockData { apiData: any; // API返回的完整数据 conclusionData?: string; // 第二个工作流接口返回的结论数据 timestamp: string; // 数据获取时间 +} + +// 定义对话消息的类型 +interface ConversationMessage { + sender: 'user' | 'ai'; // 消息发送者类型 + text: string; // 消息内容 + timestamp?: string; // 消息时间戳 + id?: string; // 消息唯一标识(可选) + type?: string; // 消息类型(可选,如 'text', 'error', 'system' 等) } \ No newline at end of file diff --git a/src/views/AIchat.vue b/src/views/AIchat.vue index fa075a2..d5f50fe 100644 --- a/src/views/AIchat.vue +++ b/src/views/AIchat.vue @@ -32,9 +32,34 @@ import logo1 from "@/assets/img/AIchat/夺宝奇兵logo.png"; import logo2 from "@/assets/img/AIchat/开启无限财富.png"; import bgc from "@/assets/img/AIchat/圈.png"; import bgc1 from "@/assets/img/AIchat/圈1.png"; +import getCountAll from "../assets/img/homePage/get-count-all.png"; +import voice from "../assets/img/homePage/tail/voice.png"; +import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png"; const chatStore = useChatStore(); const audioStore = useAudioStore(); const dataStore = useDataStore(); + +// 语音播放控制函数 +const toggleVoiceForUser = () => { + if (!audioStore.isVoiceEnabled) { + // 如果语音功能关闭,先开启语音功能 + audioStore.toggleVoice(); + } else { + // 如果语音功能开启,则切换播放/暂停状态 + if (audioStore.currentAudioUrl || audioStore.ttsUrl) { + // 有音频时切换播放/暂停 + audioStore.togglePlayPause(); + } else { + // 没有音频时关闭语音功能 + audioStore.toggleVoice(); + } + } +}; + +// 计算属性:判断语音是否启用 +const isVoice = computed(() => { + return audioStore.isVoiceEnabled; +}); // 随机GIF const currentGif = ref(""); @@ -4023,15 +4048,38 @@ onUnmounted(() => { 夺宝奇兵大模型logo -
+
+ +
+ 喇叭 +
+
+
+
+ + +
@@ -4096,7 +4144,8 @@ onUnmounted(() => {
-
+
+
@@ -4292,13 +4341,50 @@ p { position: relative; } +/* 用户消息容器样式 */ +.user-message-container { + display: flex; + align-items: flex-start; + margin: 10px 0; + justify-content: flex-end; + gap: 10px; +} + +.user-message-speaker { + width: 32px; + height: 32px; + object-fit: contain; + margin-top: 5px; + cursor: pointer; + transition: all 0.3s ease; +} + +.user-message-speaker:hover { + transform: scale(1.1); +} + +.user-message-speaker.speaker-active { + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } + 100% { + transform: scale(1); + } +} + .message-bubble.user { color: #6d22f8; background: white; font-weight: bold; - margin-left: auto; border-radius: 10px; - margin-right: 20px; + margin: 0; /* border-bottom-right-radius: 5px; */ } diff --git a/src/views/AiEmotion.vue b/src/views/AiEmotion.vue index 005387b..3123a91 100644 --- a/src/views/AiEmotion.vue +++ b/src/views/AiEmotion.vue @@ -12,8 +12,19 @@
-
- {{ message.text }} +
+ 喇叭 +
+ {{ message.text }} +
@@ -192,6 +203,9 @@ import { ElMessage } from 'element-plus'; // 接口失败提示已改为对话 import { useEmotionStore } from '@/store/emotion'; // 导入Pinia store import { useEmotionAudioStore } from '@/store/emotionAudio.js'; // 导入音频store import { useChatStore } from '@/store/chat.js'; // 导入聊天store +import getCountAll from "../assets/img/homePage/get-count-all.png"; +import voice from "../assets/img/homePage/tail/voice.png"; +import voiceNoActive from "../assets/img/homePage/tail/voice-no-active.png"; import { Howl, Howler } from 'howler'; // 导入音频播放库 import { reactive } from 'vue'; import { marked } from 'marked'; // 引入marked库 @@ -200,6 +214,28 @@ import { useUserStore } from "../store/userPessionCode"; // 使用Pinia store const emotionStore = useEmotionStore(); const emotionAudioStore = useEmotionAudioStore(); + +// 语音播放控制函数 +const toggleVoiceForUser = () => { + if (!emotionAudioStore.isVoiceEnabled) { + // 如果语音功能关闭,先开启语音功能 + emotionAudioStore.toggleVoice(); + } else { + // 如果语音功能开启,则切换播放/暂停状态 + if (emotionAudioStore.currentAudioUrl || emotionAudioStore.ttsUrl) { + // 有音频时切换播放/暂停 + emotionAudioStore.togglePlayPause(); + } else { + // 没有音频时关闭语音功能 + emotionAudioStore.toggleVoice(); + } + } +}; + +// 计算属性:判断语音是否启用 +const isVoice = computed(() => { + return emotionAudioStore.isVoiceEnabled; +}); const chatStore = useChatStore(); // 获取权限 const userStore = useUserStore(); @@ -241,6 +277,27 @@ const userInputDisplayRef = ref(null);//消息区域的引用 // 响应式数据 const messages = ref([]); + +// 从emotion store中恢复对话记录 +const loadConversationsFromStore = () => { + const storedConversations = emotionStore.getConversations(); + messages.value = storedConversations.map(conv => ({ + sender: conv.sender, + text: conv.text + })); +}; + +// 清空对话记录 +const clearConversations = () => { + messages.value = []; + emotionStore.clearConversations(); +}; + +// 暴露清空对话记录的方法给父组件 +defineExpose({ + handleSendMessage, + clearConversations +}); const isPageLoaded = ref(false); // 控制页面是否显示 const isLoading = ref(false); // 控制加载状态 const isRotating = ref(false);//控制旋转 @@ -943,6 +1000,13 @@ async function handleSendMessage(input, onComplete) { messages.value.push(userMessage); const aiMessage = reactive({ sender: 'ai', text: '您的剩余次数为0,无法使用情绪大模型,请联系客服或购买服务包。' }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '您的剩余次数为0,无法使用情绪大模型,请联系客服或购买服务包。', + timestamp: new Date().toISOString() + }); // 停止图片旋转,恢复历史数据 isRotating.value = false; messages.value = [...previousMessages, ...messages.value]; @@ -980,6 +1044,13 @@ async function handleSendMessage(input, onComplete) { const userMessage = reactive({ sender: 'user', text: input }); messages.value.push(userMessage); + + // 将用户消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'user', + text: input, + timestamp: new Date().toISOString() + }); try { // 第一步:调用第一个接口验证用户输入内容是否合法 @@ -1018,6 +1089,13 @@ async function handleSendMessage(input, onComplete) { isPageLoaded.value = false; const aiMessage = reactive({ sender: 'ai', text: processRefuseMessage(parsedData.refuse) }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: processRefuseMessage(parsedData.refuse), + timestamp: new Date().toISOString() + }); isRotating.value = false; messages.value = [...previousMessages, ...messages.value]; // 调用完成回调,重新启用输入框 @@ -1152,6 +1230,13 @@ async function handleSendMessage(input, onComplete) { const aiMessage = reactive({ sender: 'ai', text: '请求工作流接口失败,请检查网络连接' }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '请求工作流接口失败,请检查网络连接', + timestamp: new Date().toISOString() + }); // 请求失败时停止图片旋转,恢复历史数据 isRotating.value = false; messages.value = [...previousMessages, ...messages.value]; @@ -1233,6 +1318,13 @@ async function fetchData(code, market, stockName, queryText) { text: `数据丢失了,请稍后重试。` }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '数据丢失了,请稍后重试。', + timestamp: new Date().toISOString() + }); return false; // 返回失败标识,不添加股票到标签 } @@ -1269,8 +1361,15 @@ async function fetchData(code, market, stockName, queryText) { } const aiMessage = reactive({ sender: 'ai', text: '图表数据请求失败,请检查网络连接' }); - messages.value.push(aiMessage); - return false; // 返回失败标识 + messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '图表数据请求失败,请检查网络连接', + timestamp: new Date().toISOString() + }); + return false; // 返回失败标识 } } catch (error) { // 关闭加载状态 @@ -1291,6 +1390,13 @@ async function fetchData(code, market, stockName, queryText) { const aiMessage = reactive({ sender: 'ai', text: '图表数据请求失败,请检查网络连接' }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '图表数据请求失败,请检查网络连接', + timestamp: new Date().toISOString() + }); return false; // 返回失败标识 } } @@ -1380,6 +1486,13 @@ function renderCharts(data) { text: `数据不完整,缺少以下关键数据:${validation.missingFields.join('、')}。请稍后重试或联系客服。` }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: `数据不完整,缺少以下关键数据:${validation.missingFields.join('、')}。请稍后重试或联系客服。`, + timestamp: new Date().toISOString() + }); // 隐藏页面内容 isPageLoaded.value = false; @@ -1516,6 +1629,13 @@ function renderCharts(data) { console.error('图表渲染错误:', error); const aiMessage = reactive({ sender: 'ai', text: '图表渲染失败,请重试' }); messages.value.push(aiMessage); + + // 将AI消息添加到emotion store中 + emotionStore.addConversation({ + sender: 'ai', + text: '图表渲染失败,请重试', + timestamp: new Date().toISOString() + }); } }, 500); // 增加延迟到500ms确保DOM和组件完全稳定 }); @@ -1707,13 +1827,16 @@ const handleContainerScroll = () => { // 页面挂载完成后触发图片旋转和设置滚动监听 onMounted(async () => { + // 恢复对话记录 + loadConversationsFromStore(); + // 确保获取用户次数 - try { - await chatStore.getUserCount(); - console.log('情绪大模型页面:用户次数获取成功'); - } catch (error) { - console.error('情绪大模型页面:获取用户次数失败', error); - } + // try { + // await chatStore.getUserCount(); + // console.log('情绪大模型页面:用户次数获取成功'); + // } catch (error) { + // console.error('情绪大模型页面:获取用户次数失败', error); + // } // 添加全局resize监听器,确保所有图表和容器响应页面宽度变化 const globalResizeHandler = debounce(() => { @@ -1885,10 +2008,7 @@ onUnmounted(() => { // 声明组件可以触发的事件 const emit = defineEmits(['updateMessage', 'sendMessage', 'ensureAIchat']); -// 导出方法供外部使用 -defineExpose({ - handleSendMessage -}); +// 导出方法供外部使用(已在上方定义)