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-itnpm i @traptitech/markdown-it-katexnpm i -D @types/markdown-itnpm 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><divv-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)
};