参考项目地址:https://gitee.com/mao-yongyao/chatroom
目前效果
实现了FreeChat界面,用户可与大模型自由对话
界面布局
布局分为左边侧边栏和右边实际界面
<template>
<div class="home">
<el-container height="100%">
<el-aside width="100px">
<Nav></Nav>
</el-aside>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</div>
</template>
FreeChat实际界面布局分为左边聊天列表与右边的聊天窗口
<template>
<div class="chatHome">
<div class="chatLeft">
<div class="title">
<br/>
<br/>
<h1>FileChat</h1>
</div>
<div class="online-person">
<input type="file" ref="fileInput" style="display: none" @change="handleFileChange">
<div class="add-butt" @click="add_file">添加文件</div>
<span class="onlin-text">聊天列表</span>
<div class="person-cards-wrapper">
<div
class="personList"
v-for="personInfo in personList"
:key="personInfo.id"
@click="clickPerson(personInfo)"
>
<PersonCard
@transmit="delete_one_chat"
:personInfo="personInfo"
:pcCurrent="pcCurrent"
></PersonCard>
</div>
</div>
</div>
</div>
<div class="chatRight">
<div v-if="showChatWindow">
<ChatWindow
:frinedInfo="chatWindowInfo"
></ChatWindow>
</div>
<div class="showIcon" v-else>
<span class="iconfont icon-snapchat"></span>
</div>
</div>
</div>
</template>
聊天窗口布局为对话框和输入框
<template>
<div class="chat-window">
<div class="botoom">
<div class="chat-content" ref="chatContent">
<div class="chat-wrapper" v-for="(item, index) in chatList" :key="item.id">
<div class="chat-friend" v-if="item.uid !== '1001'">
<div class="info-time">
<img :src="item.headImg" alt="" />
<span>{{ item.name }}</span>
<span>{{ item.time }}</span>
</div>
<div class="chat-text" v-if="item.chatType == 0">
<template v-if="isSend && index == chatList.length - 1">
<span class="flash_cursor"></span>
</template>
<template v-else><template><div v-hljs v-html="item.msg"></div></template></template>
</div>
</div>
<div class="chat-me" v-else>
<div class="info-time">
<span>{{ item.name }}</span>
<span>{{ item.time }}</span>
<img :src="item.headImg" alt="" />
</div>
<div class="chat-text" v-if="item.chatType == 0">
{{ item.msg }}
</div>
</div>
</div>
</div>
<div class="chatInputs">
<textarea class="inputs" v-model="inputMsg" @keydown="handleKeydown"></textarea>
<el-button class="send boxinput" :disabled = "isSend" @click="sendText">
<img v-if="!this.isSend" src="@/assets/img/emoji/rocket.png" alt="" />
<i class="el-icon-loading" v-if="this.isSend"></i>
</el-button>
</div>
</div>
</div>
</template>
代码高亮
使用highlight.js库,在main.js注册为组件,这样以v-hljs为属性的标签里被pre和code标签包裹的内容就会被渲染成代码高亮的样式
Vue.directive('hljs', {
inserted: el => {
let codes = el.querySelectorAll('pre code');
Array.prototype.forEach.call(codes, code => {
hljs.highlightElement(code);
const language = code.className.split(/\s+/).find(className => className.startsWith('language-'))?.replace('language-', '');
if (code.parentNode.tagName.toLowerCase() === 'pre') {
const languageSpan = document.createElement('span');
languageSpan.className = 'code-language';
languageSpan.textContent = language;
const copyButton = document.createElement('button');
copyButton.className = 'copy-button';
copyButton.textContent = 'Copy';
copyButton.addEventListener('click', () => {
// Copy code to clipboard
const codeText = code.textContent;
navigator.clipboard.writeText(codeText).then(() => {
alert('Code copied to clipboard!');
}).catch(err => {
console.error('Unable to copy code: ', err);
});
});
code.parentNode.appendChild(languageSpan)
code.parentNode.appendChild(copyButton);
}
});
}
});
由于hljs组件只能解析html格式的内容,我们需要将大模型传来的md格式转化为HTML格式,使用Showdown
import Showdown from "showdown";
let converter = new Showdown.Converter();
let htmlStr=res.msg;
let convertedHtml = converter.makeHtml(htmlStr);
this.chatList[this.chatList.length-1].msg = convertedHtml;
多会话与持久化储存
将聊天记录存储到localStorage以实现多会话与持久化储存
新建会话
add_chat(){
let new_chat = {
name: "new_chat",
id: generateUUID(),
time: new Date().toLocaleString(),
type : 0,
}
this.personList.push(new_chat)
localStorage.setItem("chats", JSON.stringify(this.personList));
},
删除会话
delete_one_chat(data){
let chats = JSON.parse(localStorage.getItem("chats"));
for(let i=0;i<chats.length;i++){
if(chats[i].id==data){
chats.splice(i,1);
}
}
localStorage.setItem("chats", JSON.stringify(chats));
let history = JSON.parse(localStorage.getItem("history"));
for(let i=0;i<history.length;i++){
if(history[i].uuid==data){
history.splice(i,1);
}
}
localStorage.setItem("history", JSON.stringify(history));
this.personList = chats;
this.showChatWindow = false;
},
获取单个会话的聊天记录
mounted() {
let history = JSON.parse(localStorage.getItem("history"));
let flag = false;
for(let i=0;i<history.length;i++){
if(history[i].uuid == this.frinedInfo.id){
this.chatList=history[i].data;
flag = true;
break;
}
}
if(!flag){
this.chatList=[]
}
this.$nextTick(() => {
const scrollDom = this.$refs.chatContent;
scrollDom.scrollTop = scrollDom.scrollHeight;
});
},