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)
};