0. 引言
最近在学习ai大模型相关的东西,就想着自己做一个类似于chatGPT的网站,做到最后的时候代码块始终是不能高亮显示,以前一直搞Java没太了解过前端vue相关的东西,经过自己查资料,自己慢慢也是研究出来了。
这个项目整体前端是vue3,后端是python,ollama,chatOpenAi,langchain,第三方的大模型API。
ollama部署模型在本地,chatOpenAi调用模型,langchain用来做提示词和聊天记录保存支持上下文对话。
题外:项目已经部署了,想体验的可以私信我。
1.导入配置
其他无关的依赖省略
"dependencies": { "@traptitech/markdown-it-katex": "^3.6.0", "github-markdown-css": "^5.5.1", "highlight.js": "^9.18.5", "markdown-it": "^14.1.0" }, "devDependencies": { "@types/markdown-it": "^14.1.1", }
复制
2.依赖的下载命令
npm i markdown-it npm i @traptitech/markdown-it-katex npm i -D @types/markdown-it npm i highlight.js
复制
3.项目中引入
import MarkdownIt from 'markdown-it' import mdKatex from '@traptitech/markdown-it-katex' import hljs from 'highlight.js'; import 'highlight.js/styles/atom-one-dark.css';
复制
在引入import hljs from 'highlight.js';可能会报一个提示,在项目srm中新建一个.d.ts结尾的文件,写入以下内容
declare module '*.vue'{
import Vue from 'vue'
export default Vue
}
declare module 'highlight.js'
4.我的vue
4.1页面显示
<div> <div v-html="changeItem(item)" class="message" style="margin: 1rem" ></div> </div>
复制
4.2定义一个数据来接受用户输入和大模型响应的数据,用户输入的时候就往数组里push就可以了。
const contentdata = reactive([]);
复制
4.3发请求获取数据
用的是SSE(Server-Sent Events)来实时获取响应流中的数据。
fetch(url, { method: "POST", signal: controller.signal, body: formData, }) .then((response) => { loadFlg.value = false; if (response.status === 200) { const reader = response.body.getReader(); let accumulatedText = ""; // 用于累积未完成的数据 function read() { reader .read() .then(({ done, value }) => { if (done) { icType.value = Top; return; } // 解码接收到的数据 const text = new TextDecoder().decode(value); accumulatedText += text; // 按换行符分割数据块 let lines = accumulatedText.split("\n"); // 保留未完成的一行 accumulatedText = lines.pop(); lines.forEach((line) => { if (line.trim()) { // 确保非空行 if (line === "[END]") { icType.value = Top; return; } // 移除前缀 'data: ' if (line.startsWith("data: ")) { line = line.substring(6); } try { const data = JSON.parse(line); const content = data.content; // 直接使用 content,因为 content 已经是一个字符串 contentdata[ contentdata.length - 1 ].ai += content; div.scrollTop = div.scrollHeight; } catch (error) { console.error( "JSON parse error:", error ); } } }); // 继续读取下一个数据块 read(); }) .catch((error) => { icType.value = Top; console.error("Read error:", error); }); } // 开始读取数据 read(); } else { contentdata[contentdata.length - 1].ai = "非法请求,请刷新页面或关闭页面再次打开。"; icType.value = Top; alIsShow.value = true; } }) .catch((error) => { icType.value = Top; console.error("Fetch error:", error); });
复制
主要的代码其实就是下面这个,将获取到的数据解析出来显示在页面上。
contentdata[contentdata.length - 1].ai += content;
复制
5.使用MarkdownIt
const mdi = new MarkdownIt({ linkify: true, highlight(code, language) { const validLang = !!(language && hljs.getLanguage(language)) if (validLang) { const lang = language ?? '' return highlightBlock(hljs.highlight(lang, code, true).value, lang) } return highlightBlock(hljs.highlightAuto(code).value, '') } }) mdi.use(mdKatex, { blockClass: 'katexmath-block rounded-md p-[10px]', errorColor: ' #cc0000' }) function highlightBlock(str, lang) { lang = lang || "text"; return `<pre class="pre-code-box"> <div class="pre-code-header" style = " background-color: #50505a; padding: 0.2rem; padding-left: 1rem; color: white; font-size: 1rem; border-top-left-radius: 10px; border-top-right-radius: 10px; "><span class="code-block-header__lang">${lang}</span></div><div class="pre-code"><code class="hljs code-block-body ${lang}" style="padding:1.5rem; font-size: 1.05rem;border-bottom-left-radius: 10px;border-bottom-right-radius: 10px;" >${str}</code></div></pre>` } // 把数组最后一项的ai属性的值转换成html const changeItem = (item) => { return mdi.render(item.ai) };
复制