【实战】 Vue 3、Anything LLM + DeepSeek本地化项目(二)

本次目标在上一篇登录的基础上,搭建一个基本的模型对话UI界面,可以与deepseek完成简单的交流。

  • 要完成本次案例需要学习Ollama本地化部署相关的知识
  • 项目使用的是deepseek-r1:32b模型完成,大家结合自己电脑配置自行下载合适的模型

预期效果

DeepSeek+Ollama本地化部署

安装 Ollama

下载 Ollama

安装 Ollama

  1. Windows
  • 下载安装包后,双击安装文件并按照提示完成安装。
  • 如果需要指定安装路径,可以通过命令行安装:
bash 复制代码
OllamaSetup.exe /DIR="D:\Net_Program\Net_Ollama"
  • 安装完成后,退出 Ollama(右键点击任务栏图标并选择退出)。
  1. macOS
bash 复制代码
curl https://ollama.ai/install.sh | sh
  1. Linux
bash 复制代码
curl -fsSL https://ollama.com/install.sh | sh
systemctl start ollama

验证 Ollama 安装

打开命令行工具,运行以下命令验证 Ollama 是否安装成功:

bash 复制代码
ollama -v

如果输出版本号,表示安装成功。

下载并运行 DeepSeek R1 模型

选择模型大小根据你的硬件配置选择合适的模型大小:

  • 1.5B 模型:适合低配置设备(如 8GB 显存)。
  • 7B 模型:适合中等配置设备(如 16GB 显存)。
  • 32B 模型:适合高性能设备(如 32GB 显存)。

下载并运行模型

在命令行中运行以下命令下载并运行 DeepSeek R1 模型:

bash 复制代码
ollama run deepseek-r1:7b

如果需要下载其他大小的模型,可以指定版本号,例如:

bash 复制代码
ollama run deepseek-r1:1.5b

配置环境变量(可选)

修改模型存储路径

为了避免占用 C 盘空间,可以将模型存储路径修改到其他磁盘:

bash 复制代码
OLLAMA_MODELS=D:\Net_Program\Net_Ollama\models

多 GPU 支持

如果你有多个 GPU,可以通过以下命令指定使用的 GPU 索引:

bash 复制代码
CUDA_VISIBLE_DEVICES=0,1

使用 DeepSeek R1 模型

通过命令行交互

运行模型后,Ollama 会进入交互模式,你可以直接输入问题并获取回答。输入/bye退出交互模式。

通过 HTTP API

调用你可以通过 HTTP API 与模型交互。以下是一个示例:

bash 复制代码
curl -X POST http://localhost:11434/api/generate -d '{
  "model": "deepseek-r1:7b",
  "prompt": "请解释量子计算的基本原理"
}'

性能优化

内存管理

根据你的硬件配置,可以调整内存限制:

bash 复制代码
ollama run deepseek-r1:7b --memory-limit 16GB

上下文长度调整

根据需求调整上下文长度:

bash 复制代码
ollama run deepseek-r1:7b --context-length 8192

常见问题解决

内存分配错误

如果遇到内存分配错误,可以尝试以下命令:

bash 复制代码
ollama run deepseek-r1:7b --memory-limit 8GB

模型加载问题

如果模型加载失败,可以尝试重新拉取模型:

bash 复制代码
ollama pull deepseek-r1:7b

总结

通过以上步骤,你可以在本地成功部署并运行 DeepSeek R1 模型。确保根据你的硬件配置选择合适的模型大小,并根据需要调整性能参数。

DeepSeek-R1模型在Vue3中的使用

小助手界面实现

新建views/ChatMsgStream/index.vue组件文件来实现智能小助手的UI界面

vue 复制代码
<template>
  <el-row class="chat-window">
    <el-col class="chat-title" :span="20">
      智能小助手<span v-if="isThinking"><i class="el-icon-loading"></i>(思考中......)</span>
    </el-col>
    <el-col class="chat-content" :span="24">
      <template v-for="(message, index) in messages" :key="index">
        <el-collapse v-if="message.role === 'assistant'">
          <el-collapse-item
            :title="`思考信息${
              (message.thinkTime && '-' + message.thinkTime + 's') || ''
            }`"
          >
            {{ message.thinkInfo }}
          </el-collapse-item>
        </el-collapse>
        <div class="chat-message">
          <div
            v-if="message.content"
            class="chat-picture"
            :class="{ 'chat-picture-user': message.role === 'user' }"
          >
            {{ message.role === "user" ? "用户" : "助手" }}
          </div>
          <v-md-preview :text="message.content"></v-md-preview>
        </div>
      </template>
    </el-col>
    <el-col class="chat-input" :span="20">
      <el-input
        v-model="userInput"
        type="textarea"
        :rows="4"
        placeholder="请输入消息"
      ></el-input>
    </el-col>
    <el-col class="chat-btn" :span="4">
      <el-button type="primary" plain @click="sendMessage">发送</el-button>
    </el-col>
  </el-row>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { callDeepSeekAPI } from "@/apis/chatAPIStream";
const messages = ref<
  { role: string; content: string; thinkTime?: number; thinkInfo?: string }[]
>([]);
const userInput = ref<string>("");
let thinkStartTime = 0; // 思考开始时间
let thinkEndTime = 0; // 思考结束时间
let isThinking = ref<boolean>(false); // 是否处于思考状态
const formatDuring = (millisecond: number): number => {
  let seconds: number = (millisecond % (1000 * 60)) / 1000;
  return seconds;
};
const sendMessage = async () => {
  if (!userInput.value.trim()) return;

  // 添加用户消息
  messages.value.push({ role: "user", content: userInput.value, thinkTime: 0 });

  try {
    // 调用 DeepSeek API
    const stream = await callDeepSeekAPI(messages.value);
    const decoder = new TextDecoder("utf-8");
    let assistantContent = ""; // 初始化助手内容
    const reader = stream.getReader();
    messages.value.push({ role: "assistant", content: "", thinkTime: 0, thinkInfo: "" });
    // 读取流式数据
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      // console.log(value, "value");
      const chunk = decoder.decode(value, { stream: true });
      // console.log(chunk, "chunk");
      let _chunkArr = chunk.split("\n").filter(Boolean);
      _chunkArr.forEach((item: string) => {
      let _content = "";
      let {
        message: { content },
      } = JSON.parse(item);
      _content = content;
        assistantContent += _content; // 拼接流式数据
      });
      // 处理 <think> 标签
      if (
        /<think>(.*?)/gs.test(assistantContent) &&
        !/<\/think>/gs.test(assistantContent)
      ) {
        let _thinkInfo = assistantContent.replace(/<think>/gs, "");
        if (!thinkStartTime) {
          thinkStartTime = Date.now();
        }
        messages.value[messages.value.length - 1].thinkInfo = _thinkInfo;

        isThinking.value = true;
        continue;
      } else if (/<\/think>/gs.test(assistantContent)) {
        assistantContent = assistantContent.replace(/<think>(.*?)<\/think>/gs, "");
        isThinking.value = false;
        if (!thinkEndTime) {
          thinkEndTime = Date.now();
        }
        messages.value[messages.value.length - 1].thinkTime = formatDuring(
          thinkEndTime - thinkStartTime
        );
      }
      // 逐字输出动画
      let currentContent = "";
      const chars = assistantContent.split("");
      chars.forEach((char, i) => {
        currentContent += char;
        messages.value[messages.value.length - 1].content = currentContent;
      });
    }
    thinkStartTime = 0;
    thinkEndTime = 0;
  } catch (error) {
    console.error("API 调用失败:", error);
  } finally {
    userInput.value = ""; // 清空输入框
  }
};
</script>

<style scoped lang="scss">
.user {
  color: blue;
}

.assistant {
  color: green;
}

.chat-window {
  width: 60%;
  padding: 10px;
  height: 600px;
  margin: 100px auto;
  box-shadow: 0 0 10px #6cb4ffcf;
  overflow: hidden;

  .chat-title {
    text-align: center;
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 10px;
    height: 30px;
  }

  .chat-content {
    overflow-y: auto;
    border: 1px solid #ccc;
    padding: 10px;
    margin-bottom: 10px;
    width: 100%;
    height: 436px;
    .chat-message {
      position: relative;
    }
    .chat-picture {
      width: 35px;
      height: 35px;
      background: #d44512;
      color: #fff;
      overflow: hidden;
      border-radius: 25px;
      font-size: 20px;
      line-height: 35px;
      text-align: center;
      position: absolute;
      top: 12px;
      left: -6px;
      &.chat-picture-user {
        background: #0079ff;
      }
    }
  }
  .chat-input,
  .chat-btn {
    height: 94px;
  }
  .chat-input {
  }

  .chat-btn {
    text-align: center;
    button {
      width: 100%;
      height: 100%;
    }
  }
}
</style>

小助手接口调用

新建apis/chatAPIStream.ts来实现与deepseek-r1接口交互

typescript 复制代码
export const callDeepSeekAPI = async (messages: any[]): Promise<ReadableStream> => {
   const data = {
    model: 'deepseek-r1:32b',
    messages,
    stream: true, // 启用流式响应
  };

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data),
  });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  return response.body as ReadableStream; // 返回流式数据
};

小助手配置文件处理

在vite的配置文件vite.config.ts中增加deepSeek API代理

typescript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src', // 确保这里的路径是正确的
    },
  },
  server: {
    port: 8080,
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, ''),
      },
      '/deepseek-server': {
        target: 'http://localhost:11434',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/deepseek-server/, ''),
      },
    },
  },
})

至此,成功完成了智能小助手的本地化搭建,如有什么问题欢迎留言讨论~

相关推荐
鱼樱前端1 小时前
Vue3 + TypeScript 整合 MeScroll.js 组件
前端·vue.js
拉不动的猪2 小时前
刷刷题29
前端·vue.js·面试
武昌库里写JAVA2 小时前
原生iOS集成react-native (react-native 0.65+)
vue.js·spring boot·毕业设计·layui·课程设计
野生的程序媛2 小时前
重生之我在学Vue--第5天 Vue 3 路由管理(Vue Router)
前端·javascript·vue.js
鱼樱前端2 小时前
Vue 2 与 Vue 3 响应式原理详细对比
javascript·vue.js
阿丽塔~3 小时前
面试题之vue和react的异同
前端·vue.js·react.js·面试
鱼樱前端4 小时前
Vue 2 与 Vue 3 语法区别完整对比
前端·javascript·vue.js
程序饲养员5 小时前
2025年最流行的 JavaScript 动效库有哪些?前端动效必备指南!
前端·javascript·vue.js
刺客_Andy7 小时前
vue3 第二十四节(JSX用法)使用及注意事项详解
前端·vue.js
刺客_Andy7 小时前
vue3 第二十二节(defineOptions用途)
前端·vue.js