在使用 DeepSeek 时,你是否总被 “服务器繁忙,请稍后再试” 这句话困扰?明明你的需求并不复杂,只是想借助它梳理思路,却总是碰壁。其实,本地化部署是个很好的解决方案。无需高昂成本,就能实现高效的思路梳理,以最低的代价创造高附加值。
DeepSeek本地部署在Mac M系列芯片,步骤实际就两个:
1、安装 Ollama,从官网下载符合芯片的安装包,按步骤安装;
2、下载DeepSeek模型,根据自己的配置和需求,不建议使用过大的,7b基本上能满足80%的场景, 如果配置杠杠的,那就32B吧。
接下来就是具体展示VUE代码,在这个基础上可以完全使用一个全新的VUE脚手架工程,这里包括css样式也一并纳入页面中,轻轻松松就能自产自用:
<template>
<!-- 新增的外层容器,用于添加渐变背景 -->
<div class="background-container">
<!-- 整体页面容器,添加边框 -->
<div class="chat-container">
<!-- 新增的 el-switch 组件 -->
<div class="robot-title" slot="title">
<div class="robot-title-right">
<!-- 用两张图片和一个“x”替换原来的文字 -->
<img src="../../assets/picLeft.png" alt="图片1" class="robot-title-image1">
<span class="robot-title-x blink">X</span>
<img src="../../assets/picRight.png" alt="图片2" class="robot-title-image2">
<div class="robot-title-right-bottom"></div>
</div>
</div>
<!-- 对话页面容器,仿微信聊天对话框 -->
<div class="chat-dialog" ref="chatDialog">
<!-- 显示对话消息,按时间顺序穿插展示 -->
<div v-for="message in allMessages" :key="message.id" :class="message.type === 'user' ? 'user-message' : 'ai-message'">
{{ message.text }}
<!-- 如果有思考过程,显示查看思考过程的图标 -->
<span v-if="message.type === 'ai' && message.thinkContent" class="think-icon" @click="showThinkContent = message.thinkContent">
查看思考过程
</span>
</div>
<!-- 遮罩层,当 AI 还未回答返回时显示 -->
<div v-if="isLoading" class="loading-mask">
<div class="loading-spinner"></div>
</div>
<!-- 思考过程弹框 -->
<div v-if="showThinkContent" class="think-popup">
{{ showThinkContent }}
<button @click="showThinkContent = null">关闭</button>
</div>
</div>
<!-- 用户输入区域,多行文本输入框和发送按钮 -->
<div class="input-container">
<input v-model="userInput" placeholder="输入你的问题" class="input-textarea" @keydown.enter="sendMessageBasedOnApi"></input>
<button @click="sendMessageBasedOnApi" class="send-button">发送</button>
<div class="switch-container">
<el-switch v-model="isLocalApi" @change="toggleApiUrl"
active-text="私有化API"
inactive-text="远程API"
inactive-color = "#C0CC1A">
{{ isLocalApi ? '私有化 API' : '远程 API' }}
</el-switch>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "index",
data() {
return {
// 用户输入的内容
userInput: "",
// AI 回复的内容
aiResponse: "",
// 用户发送的消息列表
userMessages: [],
// AI 回复的消息列表
aiMessages: [],
// 所有消息列表,用于穿插展示
allMessages: [],
// 鼠标悬停时显示的思考过程内容
showThinkContent: null,
// 是否正在加载 AI 回复
isLoading: false,
// 是否使用本地 API
isLocalApi: false,
// API 接口地址
apiUrl: "https://api.deepseek.com/chat/completions"
};
},
methods: {
/**
* 发送用户输入的消息给 API 并获取回复
*/
sendMessage() {
if (this.userInput.trim() === "") {
return;
}
// 构造请求数据
const requestData = {
messages: [
{
content: this.userInput,
role: "user"
}
],
model: "deepseek-chat",
frequency_penalty: 0,
max_tokens: 2048,
presence_penalty: 0,
response_format: {
type: "text"
},
stop: null,
stream: false,
stream_options: null,
temperature: 1,
top_p: 1,
tools: null,
tool_choice: "none",
logprobs: false,
top_logprobs: null
};
// 将用户输入的消息添加到用户消息列表中
const userMessage = {
id: Date.now(),
text: this.userInput,
type: 'user'
};
this.userMessages.push(userMessage);
this.allMessages.push(userMessage);
// 显示加载遮罩层
this.isLoading = true;
// 发送请求
fetch(this.apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer 这里填自己的ApiKey",
"Accept": "application/json"
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
// 将 AI 回复赋值给 aiResponse 以便显示
this.aiResponse = data.choices[0].message.content
let thinkContent = null;
// 将 AI 回复的消息添加到 AI 消息列表中
const aiMessage = {
id: Date.now(),
text: this.aiResponse,
thinkContent: thinkContent,
type: 'ai'
};
this.aiMessages.push(aiMessage);
this.allMessages.push(aiMessage);
// 清空用户输入框
this.userInput = "";
// 隐藏加载遮罩层
this.isLoading = false;
// 滚动到最新消息
this.$nextTick(() => {
const chatDialog = this.$refs.chatDialog;
chatDialog.scrollTop = chatDialog.scrollHeight;
});
})
.catch(error => {
console.error("请求出错:", error);
// 隐藏加载遮罩层
this.isLoading = false;
});
},
sendMessageLocal() {
if (this.userInput.trim() === "") {
return;
}
// 这是本地部署Ollama提供的接口地址,配置采用的模型,可以是ds,也可以是其他的模型,如Qwen等
const apiUrl = "http://localhost:11434/api/generate";
// 构造请求数据
const requestData = {
model: "deepseek-r1:7b",
prompt: this.userInput,
stream: false
};
// 将用户输入的消息添加到用户消息列表中
const userMessage = {
id: Date.now(),
text: this.userInput,
type: 'user'
};
this.userMessages.push(userMessage);
this.allMessages.push(userMessage);
// 显示加载遮罩层
this.isLoading = true;
// 发送请求
fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
// 将 AI 回复赋值给 aiResponse 以便显示
this.aiResponse = data.response;
let thinkContent = null;
// 检查 AI 回复中是否包含 <think> 节点
if (data.response.includes("<think>")) {
// 提取 <think> 节点的内容
const startIndex = data.response.indexOf("<think>") + 7;
const endIndex = data.response.indexOf("</think>");
thinkContent = data.response.substring(startIndex, endIndex);
}
let aiContent = String(this.aiResponse).replace(thinkContent, '').replace("<think>", '').replace("</think>", '');
// 将 AI 回复的消息添加到 AI 消息列表中
const aiMessage = {
id: Date.now(),
text: aiContent,
thinkContent: thinkContent,
type: 'ai'
};
this.aiMessages.push(aiMessage);
this.allMessages.push(aiMessage);
// 清空用户输入框
this.userInput = "";
// 隐藏加载遮罩层
this.isLoading = false;
// 滚动到最新消息
this.$nextTick(() => {
const chatDialog = this.$refs.chatDialog;
chatDialog.scrollTop = chatDialog.scrollHeight;
});
})
.catch(error => {
console.error("请求出错:", error);
// 隐藏加载遮罩层
this.isLoading = false;
});
},
/**
* 根据 el-switch 的切换结果调用相应的发送消息方法
*/
sendMessageBasedOnApi() {
if (this.isLocalApi) {
this.sendMessageLocal();
} else {
this.sendMessage();
}
},
/**
* 切换 API 地址
*/
toggleApiUrl() {
this.apiUrl = this.isLocalApi ? "http://localhost:11434/api/generate" : "https://api.deepseek.com/chat/completions";
},
/**
* 处理内网取数点击事件,方法预留
*/
handleInternalDataFetch() {
// 这里添加内网取数的具体逻辑
},
/**
* 处理组装格式点击事件,方法预留
*/
handleFormatAssembly() {
// 这里添加组装格式的具体逻辑
},
/**
* 处理构建图表点击事件,方法预留
*/
handleChartBuilding() {
// 这里添加构建图表的具体逻辑
}
}
};
</script>
<style scoped>
/* 新增的外层容器样式,设置浅蓝色渐变背景 */
.background-container {
background: linear-gradient(to bottom, #c0ebdd, #e0e6c8);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* 整体页面容器样式 */
.chat-container {
border: 0px solid #ccc;
padding: 10px;
width: 80%;
margin: 0 auto;
box-sizing: border-box;
position: relative;
}
/* 新增的 switch 容器样式 */
.switch-container {
position: absolute;
/*top: 10px;*/
left: 10px;
}
/* 对话页面容器样式 */
.chat-dialog {
height: 560px;
overflow-y: auto;
padding: 10px;
}
/* 用户发送的消息样式 */
.user-message {
background-color: #4CAF50;
color: white;
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
float: right;
clear: both;
max-width: 70%;
}
/* AI 回复的消息样式 */
.ai-message {
background-color: #f1f1f1;
color: black;
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
float: left;
clear: both;
max-width: 70%;
position: relative;
}
/* 查看思考过程的图标样式 */
.think-icon {
cursor: pointer;
color: blue;
margin-left: 10px;
}
/* 思考过程的详细内容样式(弹框) */
.think-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid #ccc;
padding: 20px;
z-index: 2;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.think-popup button {
margin-top: 10px;
padding: 5px 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* 加载遮罩层样式 */
.loading-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: transparent; /* 修改为透明 */
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
}
.loading-spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 用户输入区域容器样式 */
.input-container {
display: flex;
justify-content: center;
align-items: center;
width: 50%;
margin: 0 auto;
}
/* 多行文本输入框样式 */
.input-textarea {
flex-grow: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
resize: none;
}
/* 发送按钮样式 */
.send-button {
padding: 10px 20px;
margin-left: 10px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* 构建图表样式 */
.build-button {
padding: 10px 20px;
margin-left: 10px;
background-color: #285eaf;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
/* 新增的图片样式 */
.robot-title-image1 {
width: 30%;
/*height: 80px;*/
object-fit: cover;
}
.robot-title-image2 {
/*width: 140px;*/
/*height: 80px;*/
/*object-fit: cover;*/
width: 30%; /* 让图片宽度自适应 */
height: auto; /* 高度自动调整,保持图片宽高比 */
object-fit: cover;
}
/* 新增的“x”样式 */
.robot-title-x {
margin: 0 30px;
font-size: 20px;
}
.robot-title-right{
width: 800px;
display: flex;
align-items: center; /* 垂直居中 */
}
/* 闪烁高亮效果 */
.blink {
animation: blink 1s infinite;
color: #2b6aff; /* 高亮颜色 */
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* 步骤条容器样式 */
.step-container {
display: flex;
flex-direction: column;
align-items: center;
margin-right: 50px;
height: 500px;
width: 160px;
}
/* 步骤条样式 */
.step {
padding: 10px 20px;
border: 1px solid #ccc;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
}
/* 步骤条字体颜色变为白色 */
.el-steps .el-step__title {
color: white;
}
</style>
没有繁杂冗余的元素堆砌,左下角可以切换调用远程API,也可以本地化的接口进行切换,总体页面简洁,反正总比在终端看着舒服,要是你还有其他修改意见,欢迎指导互相学习~