项目概述
项目目标:构建一个智能电影角色扮演对话大模型,它能够模拟电影角色的性格和语调,为用户提供与电影角色对话的能力。
技术基础:项目基于“mPlug-Owl”大模型,一个先进的对话生成模型,我们计划通过微调这一模型,使其适应电影角色的对话风格。数据源自开源中文电影数据集Movie101,包括各种电影中的对话和角色信息。
环境准备
设备名称 LAPTOP-6H4TQA0G
处理器 Intel(R) Core(TM) i5-10210U CPU @ 1.60GHz 2.11 GHz
机带 RAM 16.0 GB (15.8 GB 可用)
设备 ID F70DB001-6823-4C4E-B54C-E82681868062
产品 ID 00342-35695-74487-AAOEM
系统类型 64 位操作系统, 基于 x64 的处理器
笔和触控 为 10 触摸点提供触控支持
主要任务
模型和后端有其他组员提供,我就主要负责将参数提供给接口和接口提供的参数可视化
接口实现
1.测试接口:https://env-00jxgsdmxhfk.dev-hz.cloudbasefunction.cn/generate
传参为:
-
image_list:
(二进制)
-
prompt:
1111111
回参为:
{
"response": "text sequences....",
"prompt": "",
"image_list": [],
}
传参代码:建立一个表单将两个参数赋值传过去
const formData = new FormData(); formData.append('image_list', file); formData.append('prompt', chatMsg); fetch('https://env-00jxgsdmxhfk.dev-hz.cloudbasefunction.cn/generate', { //api,补充修改 method: 'POST', body: formData
返回参数处理:因为实现效果要类似chatgpt将回应msg一个个显示,用reader在响应对象中获取一个用于读取响应数据的 ReadableStreamDefaultReader 对象。以便逐步读取来自服务器的响应数据。这样我们可以通过读取数据块的方式逐步处理响应数据,而不是一次性将所有数据加载到内存中。这对于处理大量数据或长时间流式传输的情况非常有用。
}).then(response => { const reader = response.body.getReader(); let content = '';
定义一个递归函数来读取数据流:数据读取结束后, 设置最后一个会话的 loading 状态为 false,且 设置当前会话的 loading 状态为 false。如果这是第一次发送消息,创建一个新的会话对象,就像刚进入chat,直接发消息而不选择会话时会产生一个新的会话
function readData() { return reader.read().then(({ done, value }) => { if (done) { conv["loading"] = false; that.convLoading = false; if (first) { var newConv = { "id": that.cid, "title": "New chat" };
生成新的会话标题:
that.generateConvTitle(newConv);
generateConvTitle(conv) { //产生会话标题 var that = this; // 声明一个变量来引用当前的 Vue 实例 var tsource = this.tsource = new EventSource(`/api/chat/title/${this.cid}`); // 创建一个新的 EventSource 实例来监听会话标题的 API // 如果服务器响应报文中没有指明事件,默认触发message事件 conv.title = ""; // 设置会话标题为空字符串 tsource.addEventListener("message", function (e) { if (e.data == "[DONE]") { // 如果收到 "[DONE]" 消息,则关闭连接 tsource.close(); // 关闭 EventSource 连接 that.selectConversation(conv, false); // 选择会话 that.saveConversations(); // 保存会话列表 that.tsource = undefined; // 重置 tsource 变量为 undefined return; // 停止处理 } conv.title += e.data; // 将接收到的数据添加到会话标题 that.selectConversation(conv, false); // 选择会话 }); tsource.addEventListener("error", function (e) { console.log("error:" + e.data); // 当发生错误时打印日志 tsource.close(); // 关闭 EventSource 连接 that.tsource = undefined; // 重置 tsource 变量为 undefined }); },
将新的会话添加到会话列表的开头:
that.conversations.unshift(newConv);
选择新的会话:
that.selectConversation(newConv, false);
selectConversation(conv, loadConv) { //选择对话?????? var that = this; // 声明一个变量来引用当前的 Vue 实例 if (this.oldConv) { // 如果旧的会话存在 this.oldConv.selected = false; // 设置旧的会话的 selected 属性为 false } conv.selected = true; // 设置新选择的会话的 selected 属性为 true this.oldConv = conv; // 将新选择的会话设置为旧的会话 document.title = conv.title || "chatai"; // 设置网页标题为新选择的会话的标题 this.chatTitle = conv.title || "chatai"; // 设置聊天标题为新选择的会话的标题 if (!loadConv) { // 如果不需要加载会话内容 return; // 停止操作 }
保存会话列表:
that.saveConversations();
saveConversations() { //保存会话列表 var conversations = JSON.parse(JSON.stringify(this.conversations)); // 深度复制会话列表,避免直接修改原数据 for (let idx in conversations) { // 遍历会话列表 var conv = conversations[idx]; // 获取每个会话 delete conv.editable; // 删除会话的编辑属性 delete conv.selected; // 删除会话的选择属性 delete conv.delete; // 删除会话的删除属性 } let convs = JSON.stringify(conversations); // 将会话列表转换为 JSON 字符串 localStorage.setItem("conversations", convs); // 将 JSON 字符串保存到 localStorage 中 },
刷新会话内容:
that.refrechConversation();
refrechConversation() { this.conversation = JSON.parse(JSON.stringify(this.conversation)); // 深度复制会话内容,避免直接修改原数据 },
将每次读取的Uint8Array转换为字符串,然后添加到textString中:
content = new TextDecoder('utf-8').decode(value); if (content.includes("[ENTRY]")) { // 如果数据包含 "[ENTRY]",则替换为换行符 content = content.replaceAll("[ENTRY]", "\n"); }
滚动到最下面并用正则将response匹配内容显示:
that.handleScrollBottom(); const regex = /"response":\s*"([^"]+)"/g; let match; while ((match = regex.exec(content)) !== null) { const targetText = match[1]; // 获取匹配到的内容 conv["speeches"][0] += targetText; // 将内容赋值给 conv["speeches"][0] } window.console.log('content: ',conv["speeches"][0]) that.refrechConversation(); // 刷新会话内容
之后递归调用:
// 递归调用,继续读取下一块数据 return readData(); }); } window.console.log('res: ',res["speeches"][0]) // 开始读取数据 return readData(); }).catch(error => { console.log("error:" + error); // source.close(); that.source = undefined; }); },
2. 测试接口:this.axios.post(`/api/generate/id`, {})
请求成功的回调函数
打印结果
获取响应数据即请求的会话id
设置会话 ID
清空当前会话内容
.then((result) => { console.log(result); var resp = result.data; that.cid = resp.data; this.conversation = []; })
请求失败的回调函数
.catch((err) => { console.log("无法加载会话ID") }); },
3.点击函数:
检查本地存储中的主题
更改主题
加载 ID
加载对话
加载头像
获取聊天容器元素
为聊天容器添加滚动事件监听器
将复制方法添加到窗口
mounted: function () { var theme = localStorage.getItem("theme") || "light" this.changeTheme(theme); this.loadId(); this.loadConversations(); this.loadAvatar(); let chatDivEle = this.$refs.chatContainer; chatDivEle.addEventListener('scroll', this.isScrollAndNotBottom, true) window.copy = this.vueCopy }