PC接入deepseek
<template>
<div class="btn">
<el-button type="primary" plain @click="openAIDrawer">AI问答</el-button>
</div>
<!-- deepSeek -->
<el-drawer
v-model="deepSeekData.drawerShow"
:title="deepSeekData.title"
size="40%"
>
<div class="deepSeekDrawer">
<!-- 首次进入时的引导界面,当历史记录为空时显示 -->
<div
class="deepSeekDrawer-guide"
v-if="!deepSeekData.history.length"
>
<div class="guide-box">
<div class="f-title">
<div class="icon">
<img src="" alt="" />
</div>
<div class="text">我是AI小安助理,很高兴认识你!</div>
</div>
<div class="s-title">
我能帮你搜索医疗文献,分级诊疗AI协作
</div>
<div class="input-box">
<div class="textarea-box">
<el-input
v-model="deepSeekData.query"
type="textarea"
:rows="3"
placeholder="给 小安助手 发送消息"
@keyup.enter="getDeepSeekContent"
></el-input>
</div>
<div class="menu-box">
<div
class="r1"
:class="{ r1active: deepSeekData.r1 }"
@click="toggleR1"
>
<div>
<i class="iconfont icon-shendusikao"></i>
</div>
<div>深度思考</div>
<div>(R1)</div>
</div>
<div class="r2">
<div class="link">
<i class="iconfont icon-attach"></i>
</div>
<div class="send" @click="getDeepSeekContent">
<i class="iconfont icon-up"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 历史记录存在时显示的界面 -->
<div class="deepSeekDrawer-tree" v-else>
<div class="tree-box" id="tree-box">
<div
class="qa-box"
v-for="(item, index) in deepSeekData.history"
:key="index"
>
<div class="q">
<div class="text">
{{ item.problem }}
</div>
</div>
<div class="a">
<div class="avatar"></div>
<div class="a-content">
<div class="tip-box">
<div class="tip-text">
<i
class="iconfont icon-shendusikao"
></i>
<div>已深度思考</div>
<div>
{{ `(用时${item.useTime}秒)` }}
</div>
<div
class="udIcon"
@click="toggleR1Show(index)"
>
<i
class="iconfont icon-xm-direction-down"
v-show="item.r1Show"
></i>
<i
class="iconfont icon-xm-direction-up"
v-show="!item.r1Show"
></i>
</div>
</div>
</div>
<div
class="thoughtContent"
v-html="item.thoughtContent"
v-show="item.r1Show"
></div>
<div
class="answerContent"
v-html="item.answer"
></div>
</div>
</div>
</div>
</div>
<div class="input-box">
<div class="textarea-box">
<el-input
v-model="deepSeekData.query"
type="textarea"
:rows="3"
placeholder="给 小安助手 发送消息"
@keyup.enter="getDeepSeekContent"
></el-input>
</div>
<div class="menu-box">
<div
class="r1"
:class="{ r1active: deepSeekData.r1 }"
@click="toggleR1"
>
<div>
<i class="iconfont icon-shendusikao"></i>
</div>
<div>深度思考</div>
<div>(R1)</div>
</div>
<div class="r2">
<div class="link">
<i class="iconfont icon-attach"></i>
</div>
<div class="send" @click="getDeepSeekContent">
<i class="iconfont icon-up">发送</i>
</div>
</div>
</div>
</div>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
import { reactive } from "vue";
// 定义存储AI问答相关数据的响应式对象
const deepSeekData = reactive<{
drawerShow: boolean; // 控制AI抽屉的显示状态
title: string; // AI抽屉的标题
query: string; // 用户输入的查询内容
content: string; // 未使用的变量,可以考虑移除
r1: boolean; // 控制是否开启深度思考模式
history: {
problem: string; // 用户提出的问题
answer: string; // AI的回答
useTime: number; // 回答用时
thoughtContent: string; // 深度思考的内容
r1Show: boolean; // 控制深度思考内容的显示状态
}[];
}>({
drawerShow: false,
title: "AI小安助理",
query: "",
content: "",
r1: false,
history: [],
});
// 打开AI抽屉的函数
function openAIDrawer() {
deepSeekData.drawerShow = true;
}
// 切换深度思考模式状态的函数
function toggleR1() {
deepSeekData.r1 = !deepSeekData.r1;
}
// 切换深度思考内容显示状态的函数
function toggleR1Show(index: number) {
deepSeekData.history[index].r1Show = !deepSeekData.history[index].r1Show;
}
// 获取deepSeek回复的异步函数
async function getDeepSeekContent() {
// 将用户的问题添加到历史记录中,并设置初始回答状态
deepSeekData.history.push({
problem: deepSeekData.query,
answer: "正在思考中......",
useTime: 0,
thoughtContent: "",
r1Show: false,
});
const treeBox = document.getElementById("tree-box");
scrollToBottom(treeBox);
try {
// 发送POST请求到deepSeek API获取回复
const response = await fetch(
"https://api.deepseek.com/v1/chat/completions",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer sk-5c58d7b4d7fa449fae******需要花钱",
},
body: JSON.stringify({
model: deepSeekData.r1
? "deepseek-reasoner"
: "deepseek-chat",
messages: [{ role: "user", content: deepSeekData.query }],
stream: true, // 启用流式传输
}),
}
);
// 检查响应状态,如果不是2xx则抛出错误
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 清空当前问题和初始回答,准备接收API的回复
deepSeekData.history[deepSeekData.history.length - 1].answer = "";
deepSeekData.query = "";
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const chunks = buffer.split("\n\n") as any;
buffer = chunks.pop();
for (const chunk of chunks) {
if (chunk.startsWith("data:")) {
try {
const data = JSON.parse(
chunk.replace("data:", "").trim()
);
deepSeekData.history[
deepSeekData.history.length - 1
].answer += data.choices[0].delta.content.replace(
/\n/g,
"<br>"
);
scrollToBottom(treeBox);
} catch (e) {
console.warn("解析错误:", chunk);
}
}
}
}
} catch (error) {
// 打印详细的错误信息,方便调试
console.error("调用 API 出错:", error);
// 可以在这里添加显示错误信息给用户的逻辑,例如弹出提示框
}
}
// 平滑滚动到指定元素底部的函数
function scrollToBottom(element: any) {
if (element) {
const start = element.scrollTop;
const end = element.scrollHeight;
const change = end - start;
const duration = 500;
let startTime: number | null = null;
function animateScroll(currentTime: number) {
if (!startTime) startTime = currentTime;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
element.scrollTop = start + change * progress;
if (progress < 1) {
window.requestAnimationFrame(animateScroll);
}
}
window.requestAnimationFrame(animateScroll);
}
}
</script>
<style scoped lang="scss">
.btn {
margin: 200px;
}
.el-button--primary.is-plain {
background-color: #fff;
border-color: #00abb6 !important;
color: #00abb6 !important;
}
.el-button--primary:hover {
background-color: #47cfd6;
color: #fff !important;
border: none !important;
}
.el-button--primary:focus {
outline: none; /* 去掉默认的外边框 */
box-shadow: none; /* 去掉可能的阴影 */
}
.el-drawer__body {
padding: 0 !important;
}
/* deepseek */
.deepSeekDrawer {
height: 100%;
overflow: hidden;
.deepSeekDrawer-guide {
height: 100%;
display: flex;
align-items: center;
.guide-box {
width: 100%;
}
.f-title {
width: 100%;
display: flex;
justify-content: center;
height: 50px;
color: black;
.icon {
width: 50px;
height: 50px;
margin-right: 20px;
}
.text {
font-weight: 600;
font-size: 24px;
line-height: 50px;
}
}
.s-title {
width: 100%;
text-align: center;
font-weight: 400;
font-size: 14px;
color: black;
margin-top: 24px;
}
}
.deepSeekDrawer-tree {
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.tree-box {
flex: 1;
overflow: auto;
.qa-box {
.q {
height: 44px;
position: relative;
.text {
background: #f3faf8;
border-radius: 8px;
position: absolute;
height: 44px;
padding: 0 20px;
top: 0;
right: 0;
display: flex;
align-items: center;
}
}
.a {
display: flex;
.avatar {
width: 34px;
height: 34px;
border-radius: 50%;
background: url("/xiaoanMeeting/AI.png") no-repeat;
background-size: 100% 100%;
margin-right: 20px;
}
.a-content {
flex: 1;
.tip-box {
height: 32px;
position: relative;
.tip-text {
position: absolute;
padding-left: 20px;
padding-right: 10px;
background: #f2f3f5;
border-radius: 8px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.udIcon {
cursor: pointer;
}
}
.thoughtContent {
margin-top: 10px;
padding: 10px 0;
border-bottom: 1px solid #c9cdd4;
font-size: 14px;
color: #1d2129;
line-height: 22px;
}
.answerContent {
padding: 16px 0;
font-size: 14px;
color: #1d2129;
line-height: 28px;
}
}
}
}
}
}
.input-box {
width: 100%;
background: #f9fafb;
border-radius: 16px 16px 16px 16px;
border: 1px solid #c9cdd4;
padding: 16px 14px;
margin-top: 24px;
.el-textarea__inner {
border: none !important;
box-shadow: none !important;
background: transparent;
}
.menu-box {
display: flex;
justify-content: space-between;
height: 25px;
align-items: center;
margin-top: 10px;
.r1 {
height: 100%;
background: #f3faf8;
border-radius: 12px 12px 12px 12px;
border: 2px solid #379583;
padding: 0 15px;
display: flex;
align-items: center;
font-weight: 400;
font-size: 12px;
color: #379583;
cursor: pointer;
}
.r1active {
background: #379583;
color: white;
}
.r2 {
display: flex;
cursor: pointer;
.link {
color: #4e5969;
font-size: 18px;
}
.send {
width: 25px;
height: 25px;
border-radius: 50%;
background: #c9cdd4;
font-size: 14px;
color: white;
text-align: center;
}
}
}
}
}
</style>