前言
本项目主要采用 Vue3 + TypeScript + Vite 构建
项目主要依赖:Element-Plus + mitt + Scss + base-64 + crypto-js + markdown-it
介绍
本项目主要用来学习对接DeepSeek,进行简单的 Web 端页面搭建及基本使用,该 demo 项目具备基本的聊天对话功能,可通过左侧选择不同的助手在右侧窗口进行对话,可供实际开发参考使用。
本项目是基于原有项目[Vue3+TS]讯飞星火认知大模型搭建demo的基础上进行的新增,调整了文件结构,除了一些样式和微小改动以外,讯飞星火大模型相关的部分代码基本没有变化,可以只专注于DeepSeek相关部分的代码进行查看。
项目链接
Github仓库地址:GitHub - xkYZI/BigModel-demo
主要代码
1、大模型KEY配置
/**
* 讯飞星火大模型的Key信息
*/
export const SparkAPPID = "你的SparkAPPID";
export const SparkAPIKey = "你的SparkAPIKey ";
export const SparkAPISecret = "你的SparkAPISecret ";
/**
* DeepSeek的Key信息
*/
export const DSKey = '你的DeepSeekKey';
2、对接DeepSeek,开启通信
import { AIType, HistoryType } from '@/types';
import OpenAI from 'openai'
import { ChatCompletionMessageParam } from 'openai/resources/chat/completions.mjs';
import { ref } from 'vue';
import { emitter } from '../utils/emitter';
import { decodeUnicode } from "@/utils/markdownUtils"
import { DSKey } from "@/config/authKey"
const historyList = ref([] as HistoryType[]);
const messageList = ref([] as ChatCompletionMessageParam[]);
const resoning_content = ref("");
const content = ref("");
const inputValue = ref("");
const AI = ref({} as AIType);
const isLoading = ref(false);
// const ModelVersion = ref("deepseek-reasoner");
const ModelVersion = ref("deepseek-chat");
const constant = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: DSKey,
dangerouslyAllowBrowser: true
});
//初始化
const init = () => {
historyList.value = [];
messageList.value = [];
messageList.value.push({
role: "system",
content: AI.value.roleDefinitionStatement,
});
}
//根据请求地址和Key发送请求
//流式返回响应
const doSend = async (message: ChatCompletionMessageParam) => {
messageList.value.push(message);
historyList.value.push({
role: "user",
content: message.content,
resoning_content: resoning_content.value,
time: new Date().toLocaleString()
})
isLoading.value = true;
return await constant.chat.completions.create({
messages: messageList.value,
model: ModelVersion.value,
// model: "deepseek-chat",
stream: true,
});
}
//重新刷新发送
const doReSend = async () => {
if (messageList.value[messageList.value.length - 1].role == "user")
return;
messageList.value.pop();
historyList.value.pop();
isLoading.value = true;
emitter.emit("ing");
emitter.emit("scrollToBottom");
await constant.chat.completions.create({
messages: messageList.value,
model: ModelVersion.value,
stream: true,
}).then(async (response: any) => {
resoning_content.value = "";
content.value = "";
for await (const chunk of response) {
emitter.emit("scrollToBottom");
if (chunk.choices[0].delta.reasoning_content) {
resoning_content.value += decodeUnicode(chunk.choices[0].delta.reasoning_content);
emitter.emit("scrollToBottom")
}
else if (chunk.choices[0].delta.content) {
content.value += decodeUnicode(chunk.choices[0].delta.content);
emitter.emit("scrollToBottom")
}
}
afterDo();
});
}
const afterDo = () => {
if (content.value == "") {
content.value = "当前API接口提供方服务器繁忙,请稍后再试。"
}
messageList.value.push({
role: "assistant",
content: content.value,
});
historyList.value.push({
role: "assistant",
content: content.value,
resoning_content: resoning_content.value,
time: new Date().toLocaleString()
});
content.value = "";
resoning_content.value = "";
isLoading.value = false;
emitter.emit("ing");
}
//处理页面发送操作
const send = () => {
let msg = {
role: "user",
content: inputValue.value,
} as ChatCompletionMessageParam;
emitter.emit("ing");
emitter.emit("scrollToBottom");
inputValue.value = "";
doSend(msg).then(async (response: any) => {
resoning_content.value = "";
content.value = "";
console.log(response);
for await (const chunk of response) {
emitter.emit("scrollToBottom");
if (chunk.choices[0].delta.reasoning_content) {
resoning_content.value += decodeUnicode(chunk.choices[0].delta.reasoning_content);
emitter.emit("scrollToBottom")
}
else if (chunk.choices[0].delta.content) {
content.value += decodeUnicode(chunk.choices[0].delta.content);
emitter.emit("scrollToBottom")
}
}
afterDo();
})
}
function resetHistory() {
historyList.value = [];
messageList.value = [];
ElMessage.success("历史记录已清空");
}
export {
messageList,
historyList,
AI,
send,
init,
inputValue,
content,
resoning_content,
resetHistory,
isLoading,
ModelVersion,
doReSend
}
3、完善markDown文本展示
import MarkdownIt from "markdown-it";
// 解码包含Unicode转义序列的字符串
export function decodeUnicode(str: any) {
return str.replace(/\\u[\dA-Fa-f]{4}/g, function (match: any) {
return String.fromCharCode(parseInt(match.substr(2), 16));
});
}
// script标签中
// 引入代码高亮
import hljs from "highlight.js";
// 这个是高亮的样式,有很多,我选了这个
import "highlight.js/styles/ir-black.css";
export const md = new MarkdownIt({
highlight: function (str: string, lang: string): string {
const language = hljs.getLanguage(lang);
if (language) {
try {
return `<div class="hljs" style="position:relative;background-color:rgba(40, 44, 52,.85);
border-radius: .375rem;"><small style="background:rgba(0,0,0,.3);
position:absolute;top:0;right:0;border-bottom-left-radius: .375rem;
padding-left: .5rem;padding-right: .5rem;padding-top: .25rem;padding-bottom: .25rem;
font-size: .75rem;text-transform: uppercase;
line-height: 1rem;">${lang}</small><div style="padding:0.6rem"><code class="language-html">${hljs.highlight(lang, str, true).value
}</code></div></div>`;
} catch (error) {
console.error(error);
}
}
// 如果未指定语言或无法识别语言,则使用默认的逃逸 HTML 处理
return `<div class="hljs" style="position:relative;background-color:rgba(40, 44, 52,.85);
border-radius: .375rem;"><small style="background:rgba(0,0,0,.3);
position:absolute;top:0;right:0;border-bottom-left-radius: .375rem;
padding-left: .5rem;padding-right: .5rem;padding-top: .25rem;padding-bottom: .25rem;
font-size: .75rem;text-transform: uppercase;
line-height: 1rem;">${lang}</small><div style="padding:0.6rem"><code class="language-html">${md.utils.escapeHtml(str)}</code></div></div>`;
// return `<div class="hl-code"><div class="hljs"><code>${md.utils.escapeHtml(
// str
// )}</code></div></div>`;
},
});
4、页面样例
运行方式
1、从GitHub项目链接下载 BigModelDemo v1.0。
2、在vscode或其他编译器中打开本项目。
3、新建/打开终端。
4、首次启动 需加载项目所需依赖。
npm install
5、启动项目。
npm run dev
注意
本项目仅供学习交流使用,不得擅自售卖谋利!
结束
本人还只是一个编程的小萌新,好多地方做的也不是很好,在毕业设计里加入AI对话也是临时起意边学边写的,可能这边看上个方法就直接去改动尝试了,所以导致讯飞星火大模型相关部分的编码很乱。这次DeepSeek的部分我尽量将代码编写方式调整了一些,但介于项目的前端底子还是原有项目,所以可能还是有些乱,请大家见谅!