diff --git a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Application/Services/AiChatService.cs b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Application/Services/AiChatService.cs index f16d371e..cdac53ee 100644 --- a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Application/Services/AiChatService.cs +++ b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Application/Services/AiChatService.cs @@ -41,7 +41,7 @@ namespace Yi.Framework.ChatHub.Application.Services var str = stringQueue.Dequeue(); currentStr.Append(str); } - await _userMessageManager.SendMessageAsync(MessageContext.CreateAi(currentStr.ToString(), CurrentUser.Id!.Value, contextId)); + await _userMessageManager.SendMessageAsync(MessageContext.CreateAi(currentStr.ToString(), CurrentUser.Id!.Value, contextId),model); } } @@ -51,7 +51,7 @@ namespace Yi.Framework.ChatHub.Application.Services var str = stringQueue.Dequeue(); currentEndStr.Append(str); } - await _userMessageManager.SendMessageAsync(MessageContext.CreateAi(currentEndStr.ToString(), CurrentUser.Id!.Value, contextId)); + await _userMessageManager.SendMessageAsync(MessageContext.CreateAi(currentEndStr.ToString(), CurrentUser.Id!.Value, contextId),model); } } } diff --git a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Managers/UserMessageManager.cs b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Managers/UserMessageManager.cs index 7bfc47dd..6810e9f4 100644 --- a/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Managers/UserMessageManager.cs +++ b/Yi.Abp.Net8/module/chat-hub/Yi.Framework.ChatHub.Domain/Managers/UserMessageManager.cs @@ -31,7 +31,14 @@ namespace Yi.Framework.ChatHub.Domain.Managers private IRedisClient RedisClient => LazyServiceProvider.LazyGetRequiredService(); private string CacheKeyPrefix => LazyServiceProvider.LazyGetRequiredService>().Value.KeyPrefix; - public async Task SendMessageAsync(MessageContext context) + + /// + /// 发送消息 + /// + /// 消息内容 + /// 关联字符 + /// + public async Task SendMessageAsync(MessageContext context,string relStr=null) { switch (context.MessageType) { @@ -39,20 +46,20 @@ namespace Yi.Framework.ChatHub.Domain.Managers var userModel = await GetUserAsync(context.ReceiveId.Value); if (userModel is not null) { - await _hubContext.Clients.Client(userModel.ClientId).SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType, context); + await _hubContext.Clients.Client(userModel.ClientId).SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType,relStr, context); } break; case MessageTypeEnum.Group: throw new NotImplementedException(); break; case MessageTypeEnum.All: - await _hubContext.Clients.All.SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType, context); + await _hubContext.Clients.All.SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType,relStr, context); break; case MessageTypeEnum.Ai: var userModel2 = await GetUserAsync(context.ReceiveId.Value); if (userModel2 is not null) { - await _hubContext.Clients.Client(userModel2.ClientId).SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType, context); + await _hubContext.Clients.Client(userModel2.ClientId).SendAsync(ChatConst.ClientActionReceiveMsg, context.MessageType,relStr, context); } break; diff --git a/Yi.Bbs.Vue3/src/apis/chatMessageApi.js b/Yi.Bbs.Vue3/src/apis/chatMessageApi.js index 3ed0d791..5a262d3c 100644 --- a/Yi.Bbs.Vue3/src/apis/chatMessageApi.js +++ b/Yi.Bbs.Vue3/src/apis/chatMessageApi.js @@ -22,9 +22,9 @@ export function sendGroupMessage(data) { }); } - export function sendAiChat(data) { + export function sendAiChat(data,model) { return request({ - url: "/ai-chat/chat", + url: `/ai-chat/chat/${model}`, method: "post", data }); diff --git a/Yi.Bbs.Vue3/src/hubs/chatHub.js b/Yi.Bbs.Vue3/src/hubs/chatHub.js index c2aaa819..3310a243 100644 --- a/Yi.Bbs.Vue3/src/hubs/chatHub.js +++ b/Yi.Bbs.Vue3/src/hubs/chatHub.js @@ -12,18 +12,16 @@ const receiveMsg = (connection) => { chatStore.delUser(userId); }); //接受其他用户消息 - connection.on("receiveMsg", (type, content) => { + connection.on("receiveMsg", (type,relStr, content) => { const letChatStore = useChatStore(); //如果是ai消息,还要进行流式显示 - // alert(type) - if (type == 3) { + if (type === 3) {//(ai消息,还需要支持动态类型) + content.messageType ='ai@'+ relStr; letChatStore.addOrUpdateMsg(content); } else { letChatStore.addMsg(content); } - - }); //用户状态-正在输入中,无 connection.on("userStatus", (type) => { diff --git a/Yi.Bbs.Vue3/src/stores/chat.js b/Yi.Bbs.Vue3/src/stores/chat.js index aa7682ed..ec6754f1 100644 --- a/Yi.Bbs.Vue3/src/stores/chat.js +++ b/Yi.Bbs.Vue3/src/stores/chat.js @@ -5,16 +5,20 @@ const chatStore = defineStore("chat", { msgList: [] }), getters: { - allMsgContext: (state) => state.msgList.filter(x => x.messageType == "All"), - personalMsgContext: (state) => state.msgList.filter(x => x.messageType == "Personal"), - aiMsgContext: (state) => state.msgList.filter(x => x.messageType == "Ai") + allMsgContext: (state) => state.msgList.filter(x => x.messageType === "All"), + personalMsgContext: (state) => state.msgList.filter(x => x.messageType === "Personal"), + // aiMsgContext: (state) => state.msgList.filter(x => x.messageType === "Ai"), + //获取msg,通过类型 + getMsgContextFunc: (state) => (messageType) => { + return state.msgList.filter(item => item.messageType === messageType); + }, }, actions: { addOrUpdateMsg(msg) { - var currentMsg = this.msgList.filter(x => x.id == msg.id)[0]; + var currentMsg = this.msgList.filter(x => x.id === msg.id)[0]; //当前没有包含,如果有相同的上下文id,只需要改变content即可 - if (currentMsg == undefined) { + if (currentMsg === undefined) { this.addMsg(msg); } else { @@ -22,9 +26,9 @@ const chatStore = defineStore("chat", { } }, - clearAiMsg() + clearTypeMsg(messageType) { - this.msgList=this.msgList.filter(x => x.messageType != "Ai") + this.msgList=this.msgList.filter(x => x.messageType !==messageType) }, setMsgList(value) { this.msgList = value; @@ -39,7 +43,7 @@ const chatStore = defineStore("chat", { this.userList.push(user); }, delUser(userId) { - this.userList = this.userList.filter(obj => obj.userId != userId); + this.userList = this.userList.filter(obj => obj.userId !== userId); } }, }); diff --git a/Yi.Bbs.Vue3/src/views/chathub/Index.vue b/Yi.Bbs.Vue3/src/views/chathub/Index.vue index e2b422e2..1db223c6 100644 --- a/Yi.Bbs.Vue3/src/views/chathub/Index.vue +++ b/Yi.Bbs.Vue3/src/views/chathub/Index.vue @@ -38,10 +38,16 @@ const currentSelectUser = ref('all'); const currentInputValue = ref(""); //临时存储的输入框,根据用户id及组name、all组为key,data为value const inputListDataStore = ref([ - {key: "all", name:"官方学习交流群",titleName:"官方学习交流群",logo:"yilogo.png", value: ""}, - {key: "ai@deepseek-chat",name:"DeepSeek聊天", titleName:"DeepSeek-聊天模式",logo:"deepSeekAi.png",value: ""}, - {key: "ai@deepseek-reasoner",name:"DeepSeek思索",titleName:"DeepSeek-思索模式",logo:"deepSeekAi.png", value: ""}, - {key: "ai@gpt-4o-mini",name:"ChatGpt聊天",titleName:"ChatGpt-聊天模式",logo:"openAi.png", value: ""}, + {key: "all", name: "官方学习交流群", titleName: "官方学习交流群", logo: "yilogo.png", value: ""}, + {key: "ai@deepseek-chat", name: "DeepSeek聊天", titleName: "DeepSeek-聊天模式", logo: "deepSeekAi.png", value: ""}, + { + key: "ai@DeepSeek-R1", + name: "DeepSeek思索", + titleName: "DeepSeek-思索模式", + logo: "deepSeekAi.png", + value: "" + }, + {key: "ai@gpt-4o-mini", name: "ChatGpt聊天", titleName: "ChatGpt-聊天模式", logo: "openAi.png", value: ""}, ]); //AI聊天临时存储 const sendAiChatContext = ref([]); @@ -60,7 +66,9 @@ onMounted(async () => { onclickClose(); }, 3000); } + //all的聊天消息 chatStore.setMsgList((await getChatAccountMessageList()).data); + //在线用户列表 chatStore.setUserList((await getChatUserList()).data); startCountTip(); }) @@ -83,11 +91,9 @@ const currentMsgContext = computed(() => { //选择ai else if (selectIsAi()) { //如果是ai的值,还行经过markdown处理 - // console.log(chatStore.aiMsgContext, "chatStore.aiMsgContext"); - // return chatStore.aiMsgContext; let tempHtml = []; codeCopyDic = []; - chatStore.aiMsgContext.forEach(element => { + chatStore.getMsgContextFunc(currentSelectUser.value).forEach(element => { tempHtml.push({ content: toMarkDownHtml(element.content), messageType: 'Ai', @@ -109,19 +115,17 @@ const currentMsgContext = computed(() => { }); } }); -//图片 +//获取聊天内容的头像 const getChatUrl = (url, position) => { - if (position === "left" && selectIsAi()) { - return "/openAi.png" + if (position === "left" && (selectIsAi()||selectIsAll())) { + return imageSrc(inputListDataStore.value.find(x=>x.key===currentSelectUser.value).logo) } return getUrl(url); } //当前聊天框显示的名称 const currentHeaderName = computed(() => { - if (selectIsAll()) { - return "官方学习交流群"; - } else if (selectIsAi()) { - return "Ai-ChatGpt4.0(你的私人ai小助手)" + if (selectIsAll()||selectIsAi()) { + return inputListDataStore.value.find(x=>x.key===currentSelectUser.value).name; } else { return currentSelectUser.value.userName; } @@ -138,8 +142,13 @@ const selectIsAll = () => { }; //当前选择的是否为Ai const selectIsAi = () => { - return currentSelectUser.value === 'ai'; + //以ai@开头 + return /^ai@/.test(currentSelectUser.value); }; +//是否为公共的类型 +const isPublicType=(itemType)=>{ + return itemType==='all'||/^ai@/.test(itemType); +} //输入框的值被更改(同时保存到store中) const changeInputValue = (inputValue) => { @@ -149,7 +158,7 @@ const changeInputValue = (inputValue) => { if (selectIsAll()) { findKey = 'all'; } else if (selectIsAi()) { - findKey = 'ai'; + findKey = currentSelectUser.value; } index = inputListDataStore.value.findIndex(obj => obj.key === findKey); inputListDataStore.value[index].value = currentInputValue.value; @@ -160,11 +169,11 @@ const getCurrentInputValue = () => { if (selectIsAll()) { return inputListDataStore.value.filter(x => x.key === "all")[0].value; } else if (selectIsAi()) { - return inputListDataStore.value.filter(x => x.key === "ai")[0].value; + return inputListDataStore.value.filter(x => x.key === currentSelectUser.value)[0].value; } else { //如果不存在初始存储值 if (!inputListDataStore.value.some(x => x.key === currentSelectUser.value.userId)) { - inputListDataStore.value.push({key: currentSelectUser.value.userId, value: ""}); + inputListDataStore.value.push({key: currentSelectUser.value.userId, value: "",type:'user'}); return ""; } return inputListDataStore.value.filter(x => x.key === currentSelectUser.value.userId)[0].value; @@ -173,11 +182,12 @@ const getCurrentInputValue = () => { //点击用户列表, const onclickUserItem = (userInfo, itemType) => { - if (itemType === "all") { - currentSelectUser.value = 'all'; - } else if (itemType === "ai") { - currentSelectUser.value = 'ai'; - } else { + //公共 + if (isPublicType(itemType)) { + currentSelectUser.value = itemType; + } + //个人 + else { currentSelectUser.value = userInfo; } //填充临时存储的输入框 @@ -188,6 +198,7 @@ const onclickUserItem = (userInfo, itemType) => { //点击发送按钮 const onclickSendMsg = () => { + //发送空消息 if (currentInputValue.value === "") { msgIsNullShow.value = true; setTimeout(() => { @@ -204,18 +215,24 @@ const onclickSendMsg = () => { sendAiChatContext.value.push({ answererType: 'User', message: currentInputValue.value, - number: sendAiChatContext.value.length + number: sendAiChatContext.value.length, + messageType: currentSelectUser.value }) //离线前端存储 chatStore.addMsg({ - messageType: "Ai", + messageType: currentSelectUser.value, content: currentInputValue.value, sendUserId: userStore.id, sendUserInfo: {user: {icon: userStore.icon}} }) + //ai模型,去掉key的ai@开头的字符串 + const model=currentSelectUser.value.replace(/^ai@/, ''); + //上下文内容,当前ai进行隔离 + const content= sendAiChatContext.value.filter(x=>x.messageType===currentSelectUser.value) + //发送ai消息 - sendAiChat(sendAiChatContext.value); + sendAiChat(content,model); } else { onclickSendPersonalMsg(currentSelectUser.value.userId, currentInputValue.value); } @@ -247,11 +264,11 @@ const onclickSendGroupMsg = (groupName, msg) => { //获取当前最后一条信息(显示在左侧菜单) const getLastMessage = ((receiveId, itemType) => { - if (itemType === "all") { - return chatStore.allMsgContext[chatStore.allMsgContext.length - 1]?.content.substring(0, 15); - } else if (itemType === "ai") { - return chatStore.aiMsgContext[chatStore.aiMsgContext.length - 1]?.content.substring(0, 15); - } else { + if (isPublicType(itemType)) { + const message = chatStore.getMsgContextFunc(itemType); + return message[message.length - 1]?.content.substring(0, 15); + } + else { const messageContext = chatStore.personalMsgContext.filter(x => { //两个条件 //接收用户者id为对面id(我发给他) @@ -266,151 +283,151 @@ const getLastMessage = ((receiveId, itemType) => { /*----------以下方法是对内容通用处理,没有业务逻辑,无需变化----------*/ - const imageSrc=(imageName)=> { - // 动态拼接路径 - return new URL(`../../assets/chat_images/${imageName}`, import.meta.url).href; - } - - //绑定的input改变事件 - const updateInputValue = (event) => { - changeInputValue(event.target.value); - } - - //输入框按键事件 - const handleKeydownInput = () => { - // 检查是否按下 Shift + Enter - if (event.key === 'Enter' && event.shiftKey) { - // 允许输入换行 - return; // 让默认行为继续 - } - // 如果只按下 Enter,则阻止默认的提交行为,比如在表单中 - if (event.key === 'Enter') { - // 阻止默认行为 - event.preventDefault(); - onclickSendMsg(); - return; - } - } - - //关闭聊天框 - const onclickClose = () => { - router.push({path: "/index"}) - .then(() => { - // 重新刷新页面 - location.reload() - }) - } - -//清除ai对话 - const clearAiMsg = () => { - sendAiChatContext.value = []; - chatStore.clearAiMsg(); - ElMessage({ - message: "当前会话清除成功", - type: "success", - duration: 2000, - }); +const imageSrc = (imageName) => { + // 动态拼接路径 + return new URL(`../../assets/chat_images/${imageName}`, import.meta.url).href; +} +//绑定的input改变事件 +const updateInputValue = (event) => { + changeInputValue(event.target.value); +} + +//输入框按键事件 +const handleKeydownInput = () => { + // 检查是否按下 Shift + Enter + if (event.key === 'Enter' && event.shiftKey) { + // 允许输入换行 + return; // 让默认行为继续 } + // 如果只按下 Enter,则阻止默认的提交行为,比如在表单中 + if (event.key === 'Enter') { + // 阻止默认行为 + event.preventDefault(); + onclickSendMsg(); + return; + } +} + +//关闭聊天框 +const onclickClose = () => { + router.push({path: "/index"}) + .then(() => { + // 重新刷新页面 + location.reload() + }) +} + +//清除ai对话 +const clearAiMsg = () => { + sendAiChatContext.value = []; + chatStore.clearTypeMsg(currentSelectUser.value); + ElMessage({ + message: "当前Ai会话清除成功", + type: "success", + duration: 2000, + }); + +} //转换markdown - const toMarkDownHtml = (text) => { - marked.setOptions({ - renderer: new marked.Renderer(), - highlight: function (code, language) { - return codeHandler(code, language); - }, - pedantic: false, - gfm: true,//允许 Git Hub标准的markdown - tables: true,//支持表格 - breaks: true, - sanitize: false, - smartypants: false, - xhtml: false, - smartLists: true, - } - ); - //需要注意代码块样式 - const soureHtml = marked(text); - nextTick(() => { - addCopyEvent(); - }) - return soureHtml; - } +const toMarkDownHtml = (text) => { + marked.setOptions({ + renderer: new marked.Renderer(), + highlight: function (code, language) { + return codeHandler(code, language); + }, + pedantic: false, + gfm: true,//允许 Git Hub标准的markdown + tables: true,//支持表格 + breaks: true, + sanitize: false, + smartypants: false, + xhtml: false, + smartLists: true, + } + ); + //需要注意代码块样式 + const soureHtml = marked(text); + nextTick(() => { + addCopyEvent(); + }) + return soureHtml; +} //code部分处理、高亮 - const codeHandler = (code, language) => { - const codeIndex = parseInt(Date.now() + "") + Math.floor(Math.random() * 10000000); - //console.log(codeIndex,"codeIndex"); - // 格式化第一行是右侧language和 “复制” 按钮; - if (code) { - const navCode = navHandler(code) - try { - // 使用 highlight.js 对代码进行高亮显示 - const preCode = hljs.highlightAuto(code).value; - // 将代码包裹在 textarea 中,由于防止textarea渲染出现问题,这里将 "<" 用 "<" 代替,不影响复制功能 - let html = `
${language}复制代码
${preCode}
`; - codeCopyDic.push({id: codeIndex, code: code}); - // console.log(codeCopyDic.length); - return html; - // - } catch (error) { - console.log(error); - } +const codeHandler = (code, language) => { + const codeIndex = parseInt(Date.now() + "") + Math.floor(Math.random() * 10000000); + //console.log(codeIndex,"codeIndex"); + // 格式化第一行是右侧language和 “复制” 按钮; + if (code) { + const navCode = navHandler(code) + try { + // 使用 highlight.js 对代码进行高亮显示 + const preCode = hljs.highlightAuto(code).value; + // 将代码包裹在 textarea 中,由于防止textarea渲染出现问题,这里将 "<" 用 "<" 代替,不影响复制功能 + let html = `
${language}复制代码
${preCode}
`; + codeCopyDic.push({id: codeIndex, code: code}); + // console.log(codeCopyDic.length); + return html; + // + } catch (error) { + console.log(error); } - } + +} //左侧导航栏处理 - const navHandler = (code) => { - //获取行数 - var linesCount = getLinesCount(code); - var currentLine = 1; - var liHtml = ``; - while (linesCount + 1 >= currentLine) { - liHtml += `` - currentLine++ - } - - let html = `` - return html; - } - const BREAK_LINE_REGEXP = /\r\n|\r|\n/g; - const getLinesCount = (text) => { - return (text.trim().match(BREAK_LINE_REGEXP) || []).length; +const navHandler = (code) => { + //获取行数 + var linesCount = getLinesCount(code); + var currentLine = 1; + var liHtml = ``; + while (linesCount + 1 >= currentLine) { + liHtml += `` + currentLine++ } - let timerTip = null; + let html = `` + return html; +} +const BREAK_LINE_REGEXP = /\r\n|\r|\n/g; +const getLinesCount = (text) => { + return (text.trim().match(BREAK_LINE_REGEXP) || []).length; +} + +let timerTip = null; //倒计时显示tip - const startCountTip = () => { - timerTip = setInterval(() => { - if (isShowTipNumber.value > 0) { - isShowTipNumber.value--; - } else { - clearInterval(timerTip); // 倒计时结束 - } - }, 1000); - }; +const startCountTip = () => { + timerTip = setInterval(() => { + if (isShowTipNumber.value > 0) { + isShowTipNumber.value--; + } else { + clearInterval(timerTip); // 倒计时结束 + } + }, 1000); +}; //代码copy事件 - const addCopyEvent = () => { - const copySpans = document.querySelectorAll('.copy'); +const addCopyEvent = () => { + const copySpans = document.querySelectorAll('.copy'); // 为每个 copy span 元素添加点击事件 - copySpans.forEach(span => { - //先移除,再新增 - span.removeEventListener('click', clickCopyEvent); - span.addEventListener('click', clickCopyEvent); - }); - } - const clickCopyEvent = async function (event) { - const spanId = event.target.id; - console.log(codeCopyDic, "codeCopyDic") - console.log(spanId, "spanId") - await navigator.clipboard.writeText(codeCopyDic.filter(x => x.id === spanId)[0].code); - ElMessage({ - message: "代码块复制成功", - type: "success", - duration: 2000, - }); - } + copySpans.forEach(span => { + //先移除,再新增 + span.removeEventListener('click', clickCopyEvent); + span.addEventListener('click', clickCopyEvent); + }); +} +const clickCopyEvent = async function (event) { + const spanId = event.target.id; + console.log(codeCopyDic, "codeCopyDic") + console.log(spanId, "spanId") + await navigator.clipboard.writeText(codeCopyDic.filter(x => x.id === spanId)[0].code); + ElMessage({ + message: "代码块复制成功", + type: "success", + duration: 2000, + }); +}