当前位置: 首页 > news >正文

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>

                                &nbsp;

                                <div>深度思考</div>

                                &nbsp;

                                <div>(R1)</div>

                            </div>

                            <div class="r2">

                                <div class="link">

                                    <i class="iconfont icon-attach"></i>

                                </div>

                                &nbsp;&nbsp;&nbsp;&nbsp;

                                <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>

                                        &nbsp;

                                        <div>已深度思考</div>

                                        &nbsp;

                                        <div>

                                            {{ `(用时${item.useTime}秒)` }}

                                        </div>

                                        &nbsp;&nbsp;&nbsp;&nbsp;

                                        <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>

                            &nbsp;

                            <div>深度思考</div>

                            &nbsp;

                            <div>(R1)</div>

                        </div>

                        <div class="r2">

                            <div class="link">

                                <i class="iconfont icon-attach"></i>

                            </div>

                            &nbsp;&nbsp;&nbsp;&nbsp;

                            <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>

相关文章:

  • 【数据可视化-29】食物营养成分数据可视化分析
  • Qt C++/Go/Python 面试题(持续更新)
  • MySQL的图形管理工具-MySQL Workbench的下载安装及使用【保姆级】
  • [Redis] Redis最佳实践
  • 【Ubuntu】提升 docker ps -a 输出的可读性:让 Docker 容器状态更清晰
  • K8S学习路线图:从入门到精通的技术成长指南
  • 图像可视化
  • PCB常见封装类型
  • RSUniVLM论文精读
  • 【深度】如何通过MCP实现多智能体之间的协同
  • IDEA导入并启动若依项目步骤(SpringBoot+Vue3)
  • 【华为HCIP | 华为数通工程师】821—多选解析—第十五页
  • m365是什么,和o365的区别
  • 自动化标注软件解析
  • 多回路电表如何革新电力监控?安科瑞技术深度解析
  • linux:启动后,ubuntu屏幕变成红色了
  • 从平台工程视角出发,重塑云原生后端的工程体系
  • 电商热点数据哈希槽分片案例:双11秒杀场景设计
  • 陪诊陪检系统源码,陪诊小程序,陪诊APP,陪诊服务,家政上门系统,居家护理陪护源码,医护小程序
  • 学员答题pk知识竞赛小程序怎么做
  • 新东方:2025财年前三季度净利增29%,第四财季海外业务将承压
  • 蚌埠一动物园用染色犬扮熊猫引争议,园方回应:被投诉已撤走
  • 白宫称中美贸易协议谈判取得进展,外交部回应
  • 魔都眼·上海车展①|开幕首日:首发首秀近百款新车
  • 直播中抢镜“甲亢哥”的翁东华卸任!此前任文和友小龙虾公司董事
  • 电商平台全面取消“仅退款”:电商反内卷一大步,行业回归良性竞争