打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手
DeepSeek AI 聊天助手集成指南
先看完整效果:
PixPin_2025-02-19_09-15-59
效果图:



目录
- 项目概述
- 功能特点
- 环境准备
- 项目结构
- 组件详解
- ChatContainer
- ChatInput
- MessageBubble
- TypeWriter
- 核心代码示例
- 使用指南
- 常见问题
项目概述
基于 Vue 3 + TypeScript + Element Plus 开发的 AI 聊天助手,集成了 DeepSeek 的 API 服务。提供打字机模式和流式输出两种对话模式,支持多种 AI 模型切换。
功能特点
| 基础功能 | 交互体验 | UI/UX | 其他特性 |
|---|---|---|---|
| 🚀 Vue 3 Composition API | ✨ 打字机效果 | 🎨 Element Plus UI | 📱 响应式设计 |
| 💪 TypeScript 支持 | 🌊 流式实时输出 | 🖼️ 自定义用户头像 | 🔄 模型切换 |
| 🔌 DeepSeek API 集成 | ⌚ 消息时间显示 | 🎭 思考状态动画 | 🗑️ 对话清空 |
环境准备
1. 安装依赖
# 创建项目
npm create vue@latest# 安装依赖
npm install element-plus @element-plus/icons-vue axios
2. 环境配置
创建 .env.local 文件:
VITE_DEEPSEEK_API_KEY=your_api_key_here
项目结构
src/
├── components/chat/ # 聊天相关组件
│ ├── ChatContainer.vue # 聊天容器组件
│ ├── ChatInput.vue # 输入组件
│ ├── MessageBubble.vue # 消息气泡组件
│ └── TypeWriter.vue # 打字机效果组件
├── services/
│ └── aiService.ts # API 服务封装
├── views/
│ ├── ChatView.vue # 打字机模式页面
│ └── StreamView.vue # 流式输出页面
└── App.vue # 根组件
组件详解
1. ChatContainer.vue
聊天界面的核心容器组件。
功能特性
- 📝 消息列表管理
- 🔄 自动滚动控制
- ⌨️ 打字机效果管理
- 🔀 模型切换
- 🗑️ 清空对话
- 💫 思考状态动画
- 📱 响应式适配
组件 API
// Props
interface Props {title?: string // 聊天标题messages: Message[] // 消息列表loading?: boolean // 加载状态streamMode?: boolean // 流式模式
}// Events
interface Events {send: (message: string) => voidclear: () => voidmodelChange: (model: ModelType) => void
}
完整代码如下:
<template><!-- 聊天容器主组件 --><div class="chat-container"><!-- 聊天头部:标题和清空按钮 --><div class="chat-header"><div class="header-left"><h3>{{ title }}</h3><el-select v-model="currentModel" size="small" class="model-select":disabled="loading"><el-optionv-for="(label, model) in modelOptions":key="model":label="label":value="model"/></el-select></div><el-button type="danger" plainclass="clear-button"@click="showClearConfirm"><template #icon><el-icon><Delete /></el-icon></template>清空全部对话</el-button></div><!-- 消息列表区域:包含所有对话内容 --><div class="chat-messages" ref="messagesContainer"><!-- 循环渲染消息气泡 --><MessageBubblev-for="(message, index) in messages":key="index":content="message.content":is-user="message.role === 'user'":use-typewriter="!initialLoad && !streamMode && message.role === 'assistant'"@complete="handleMessageComplete(index)"/><!-- AI思考中状态显示 --><div v-if="loading && (!messages.length || messages[messages.length - 1].role === 'user')" class="message message-ai thinking-message"><div class="message-content"><div class="avatar-wrapper"><el-avatar :size="40" class="ai-avatar"><el-icon><Service /></el-icon></el-avatar></div><div class="bubble-wrapper"><div class="bubble thinking-bubble"><div class="dots-container"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div></div></div></div></div></div><!-- 输入区域组件 --><ChatInput :disabled="loading || isTyping"@send="$emit('send', $event)"/><!-- 确认弹窗 --><el-dialogv-model="showConfirmDialog"title="确认清空"width="400px":show-close="false"class="clear-dialog"><div class="dialog-content"><el-icon class="warning-icon" color="#E6A23C"><Warning /></el-icon><p>确定要清空所有对话记录吗?</p><p class="warning-text">此操作不可恢复</p></div><template #footer><div class="dialog-footer"><el-button @click="showConfirmDialog = false">取消</el-button><el-button type="danger" @click="handleClear">确认清空</el-button></div></template></el-dialog></div>
</template><script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue'
import { Service, Loading, Delete, Warning } from '@element-plus/icons-vue'
import { ModelType } from '@/services/aiService'// 消息类型定义
interface Message {role: 'user' | 'assistant' | 'system'content: string
}// 组件属性定义
const props = defineProps<{title?: string // 聊天标题messages: Message[] // 消息列表loading?: boolean // 加载状态streamMode?: boolean // 新增流式模式属性
}>()// 定义组件事件
const emit = defineEmits<{send: [message: string]clear: []modelChange: [model: ModelType]
}>()// 组件状态
const messagesContainer = ref<HTMLElement | null>(null)
const isTyping = ref(false)
const initialLoad = ref(true) // 添加初始加载标记// 确认弹窗状态
const showConfirmDialog = ref(false)// 模型选项
const modelOptions = {[ModelType.Chat]: 'DeepSeek-V3 (通用对话)',[ModelType.Reasoner]: 'DeepSeek-R1 (推理增强)'
}// 当前选择的模型
const currentModel = ref<ModelType>(ModelType.Chat)// 处理消息打字完成事件
const handleMessageComplete = (index: number) => {if (index === props.messages.length - 1) {isTyping.value = false}
}// 监听新消息,控制打字机效果
watch(() => props.messages, (newMessages, oldMessages) => {// 跳过初始加载的消息if (initialLoad.value) {initialLoad.value = falsereturn}if (newMessages.length > oldMessages?.length) {const lastMessage = newMessages[newMessages.length - 1]// 只在非流式模式下启用打字机效果if (lastMessage.role === 'assistant' && !props.streamMode) {isTyping.value = true}}
}, { deep: true })// 监听模型变化
watch(currentModel, (newModel) => {emit('modelChange', newModel)
})// 滚动到底部方法
const scrollToBottom = async () => {await nextTick()if (messagesContainer.value) {messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight}
}// 监听消息变化,自动滚动
watch(() => props.messages, scrollToBottom, { deep: true })// 组件挂载时滚动到底部
onMounted(() => {scrollToBottom()// 初始加载完成后重置标记nextTick(() => {initialLoad.value = false})
})// 显示确认弹窗
const showClearConfirm = () => {showConfirmDialog.value = true
}// 处理清空操作
const handleClear = () => {showConfirmDialog.value = falseemit('clear')
}
</script><style scoped>
/* 容器基础样式 */
.chat-container {display: flex;flex-direction: column;height: 100%;width: 100%;max-width: 1200px;margin: 0 auto;background: #fff;border-radius: 16px;box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);overflow: hidden;
}/* 头部样式优化 */
.chat-header {display: flex;justify-content: space-between;align-items: center;box-sizing: border-box;padding: 16px 24px; /* 减小内边距 */background: #fff;border-bottom: 1px solid #eee;box-shadow: 0 1px 6px rgba(0, 0, 0, 0.02); /* 减小阴影 */z-index: 1;
}.chat-header h3 {font-size: 18px; /* 减小标题字号 */font-weight: 600;color: #303133;margin: 0;
}/* 在线状态指示器调整 */
.chat-header h3::before {width: 6px; /* 减小指示点大小 */height: 6px;margin-right: 8px;
}/* 消息列表区域样式 */
.chat-messages {flex: 1;overflow-y: auto;padding: 30px;display: flex;flex-direction: column;gap: 24px;background: #f9fafb;background-image: radial-gradient(circle at 25px 25px, rgba(0, 0, 0, 0.02) 2%, transparent 0%),radial-gradient(circle at 75px 75px, rgba(0, 0, 0, 0.02) 2%, transparent 0%);background-size: 100px 100px;scroll-behavior: smooth;scrollbar-width: thin;scrollbar-color: transparent transparent;
}/* 加载动画 */
.loading {animation: rotating 2s linear infinite;margin-right: 8px;
}@keyframes rotating {from { transform: rotate(0deg); }to { transform: rotate(360deg); }
}/* 自定义滚动条样式 */
.chat-messages::-webkit-scrollbar {width: 4px;
}.chat-messages::-webkit-scrollbar-track {background: transparent;
}.chat-messages::-webkit-scrollbar-thumb {background-color: rgba(0, 0, 0, 0.1);border-radius: 4px;transition: all 0.3s ease;
}/* 只在悬停时显示滚动条 */
.chat-messages:hover::-webkit-scrollbar-thumb {background-color: rgba(0, 0, 0, 0.2);
}/* 响应式布局 */
@media (max-width: 1400px) {.chat-container {max-width: 1000px;}
}@media (max-width: 1200px) {.chat-container {height: 100%;margin: 0;border-radius: 0;}.chat-view {padding: 0;}
}/* 思考中状态样式 */
.thinking-message {opacity: 0.8;animation: fadeInUp 0.3s ease-out;
}/* 思考中的气泡样式 */
.thinking-bubble {min-width: 60px;padding: 12px 16px !important;background: rgba(255, 255, 255, 0.9) !important;backdrop-filter: blur(8px);
}/* 跳动点动画 */
.dots-container {display: flex;align-items: center;gap: 6px;height: 20px;padding: 0 4px;
}.dot {width: 8px;height: 8px;background: #67c23a;border-radius: 50%;display: inline-block;opacity: 0.8;animation: bounce 1.4s infinite ease-in-out both;
}.dot:nth-child(1) {animation-delay: -0.32s;
}.dot:nth-child(2) {animation-delay: -0.16s;
}@keyframes bounce {0%, 80%, 100% { transform: scale(0);} 40% { transform: scale(1);}
}@keyframes fadeInUp {from {opacity: 0;transform: translateY(10px);}to {opacity: 0.8;transform: translateY(0);}
}/* 消息位置调整 */
.message-ai.thinking-message {margin: 0;padding-top: 12px;
}.message-ai.thinking-message .message-content {align-items: center;
}.ai-avatar {background: #67c23a;box-shadow: 0 2px 8px rgba(103, 194, 58, 0.2);
}.avatar-wrapper {width: 40px;height: 40px;flex-shrink: 0;
}.bubble-wrapper {display: flex;flex-direction: column;gap: 4px;
}.message {transition: transform 0.3s ease-out;
}.thinking-message {position: sticky;bottom: 30px;margin-top: 20px;z-index: 1;
}/* 清空按钮样式调整 */
.clear-button {padding: 8px 16px; /* 减小按钮内边距 */font-size: 13px; /* 减小字号 */height: 32px; /* 固定高度 */
}.clear-button :deep(.el-icon) {font-size: 14px; /* 减小图标大小 */margin-right: 4px;vertical-align: -1px;
}/* 头部布局间距调整 */
.header-left {gap: 12px; /* 减小间距 */
}/* 优化动画效果 */
@keyframes pulse {0% {transform: scale(0.95);box-shadow: 0 0 0 0 rgba(103, 194, 58, 0.3);}70% {transform: scale(1);box-shadow: 0 0 0 4px rgba(103, 194, 58, 0);}100% {transform: scale(0.95);box-shadow: 0 0 0 0 rgba(103, 194, 58, 0);}
}/* 弹窗样式 */
.clear-dialog :deep(.el-dialog__header) {padding: 20px 24px;margin: 0;border-bottom: 1px solid #eee;
}.clear-dialog :deep(.el-dialog__title) {font-size: 18px;font-weight: 600;
}.clear-dialog :deep(.el-dialog__body) {padding: 30px 24px;
}.clear-dialog :deep(.el-dialog__footer) {padding: 16px 24px;border-top: 1px solid #eee;
}.dialog-content {display: flex;flex-direction: column;align-items: center;text-align: center;
}.warning-icon {font-size: 48px;margin-bottom: 16px;
}.dialog-content p {margin: 0;font-size: 16px;color: #303133;
}.warning-text {margin-top: 8px !important;font-size: 14px !important;color: #909399 !important;
}.dialog-footer {display: flex;justify-content: flex-end;gap: 12px;
}/* 弹窗动画 */
.clear-dialog :deep(.el-overlay) {backdrop-filter: blur(4px);
}.clear-dialog :deep(.el-dialog) {border-radius: 12px;overflow: hidden;box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}/* 按钮样式优化 */
.dialog-footer :deep(.el-button) {padding: 10px 20px;border-radius: 6px;font-weight: 500;
}.dialog-footer :deep(.el-button--danger) {box-shadow: 0 2px 8px rgba(245, 108, 108, 0.2);
}.dialog-footer :deep(.el-button--danger:hover) {transform: translateY(-1px);box-shadow: 0 4px 12px rgba(245, 108, 108, 0.3);
}/* 模型选择器样式优化 */
.model-select {width: 200px;
}:deep(.el-select .el-input__wrapper) {padding: 0 12px; /* 减小内边距 */height: 32px; /* 减小高度 */
}:deep(.el-select .el-input__inner) {font-size: 13px; /* 减小字号 */
}
</style>
2. ChatInput.vue
输入组件特点:
- 自适应文本框高度
- 字数限制和显示
- Enter 快捷发送
- 优雅的加载状态
- 内联发送按钮
- 防重复提交
template 部分
.chat-input: 组件容器.input-wrapper: 输入框和按钮的包装容器el-input: 文本输入框,支持textarea自动伸缩el-button: 发送按钮,带Position图标
script setup 部分
组件属性 props
disabled:是否禁用输入框(可选)
组件事件 emit
send(message: string):发送消息事件
组件状态 ref
message:输入的消息内容
关键方法
handleSend():发送消息,去除前后空格,防止空消息发送
组件使用
<ChatInput :disabled="isLoading" @send="handleSendMessage" />
完整代码如下:
<template><!-- 聊天输入组件 --><div class="chat-input"><div class="input-wrapper"><el-inputv-model="message"type="textarea":maxlength="2000":autosize="{ minRows: 1, maxRows: 4 }"show-word-limitresize="none"placeholder="输入您的问题..."@keyup.enter.exact="handleSend":disabled="disabled"class="custom-input"/><el-button type="primary" :loading="disabled"@click="handleSend":disabled="!message.trim()"class="send-button"><template #icon><el-icon><Position /></el-icon></template>发送</el-button></div></div>
</template><script setup lang="ts">
import { ref } from 'vue'
import { Position } from '@element-plus/icons-vue'// 组件属性定义
const props = defineProps<{disabled?: boolean // 禁用状态
}>()// 定义事件
const emit = defineEmits<{send: [message: string] // 发送消息事件
}>()// 输入内容
const message = ref('')// 发送消息处理
const handleSend = () => {const trimmedMessage = message.value.trim()if (!trimmedMessage || props.disabled) returnemit('send', trimmedMessage)message.value = ''
}
</script><style scoped>
/* 输入区域容器样式 */
.chat-input {padding: 16px 24px;border-top: 1px solid #eee;background: #fff;border-radius: 0 0 16px 16px;position: relative;
}/* 输入框包装器 */
.input-wrapper {position: relative;display: flex;gap: 12px;align-items: flex-start;
}/* 输入框样式优化 */
.custom-input {flex: 1;transition: all 0.3s ease;
}.custom-input :deep(.el-textarea__inner) {padding: 12px 16px;padding-right: 120px; /* 为字数限制留出空间 */font-size: 14px;border-radius: 12px;border: 1px solid #e4e7ed;background: #f9fafb;box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);transition: all 0.3s ease;line-height: 1.6;min-height: 48px;resize: none;
}.custom-input :deep(.el-textarea__inner:hover) {background: #fff;border-color: #c0c4cc;
}.custom-input :deep(.el-textarea__inner:focus) {background: #fff;border-color: #409eff;box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}/* 字数限制样式 */
.custom-input :deep(.el-input__count) {position: absolute;right: 12px;bottom: 8px;background: transparent;font-size: 12px;color: #909399;padding: 0;height: auto;line-height: 1;margin: 0;
}/* 发送按钮样式 */
.send-button {padding: 0 24px;font-size: 14px;border-radius: 10px;height: 48px;transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);font-weight: 500;white-space: nowrap;flex-shrink: 0;
}.send-button:not(:disabled) {background: linear-gradient(135deg, #409eff, #3a8ee6);border: none;
}.send-button:not(:disabled):hover {transform: translateY(-1px);box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}.send-button:not(:disabled):active {transform: translateY(0);
}.send-button :deep(.el-icon) {font-size: 16px;margin-right: 4px;vertical-align: -2px;
}/* 禁用状态样式 */
.custom-input :deep(.el-textarea__inner:disabled) {background: #f5f7fa;border-color: #e4e7ed;cursor: not-allowed;opacity: 0.7;
}/* 响应式调整 */
@media (max-width: 768px) {.chat-input {padding: 12px 16px;}.input-wrapper {gap: 8px;}.send-button {padding: 0 16px;height: 48px;}.custom-input :deep(.el-textarea__inner) {padding-right: 90px;}
}/* 超小屏幕隐藏发送按钮文字 */
@media (max-width: 480px) {.send-button {padding: 0;width: 48px;}.send-button :deep(.el-icon) {margin: 0;}.send-button span:not(.el-icon) {display: none;}
}
</style>
3. MessageBubble.vue
消息气泡组件特点:
- 显示消息气泡
- 区分用户消息和 AI 消息
- 支持头像展示
- 支持打字机效果
- 自动格式化时间
template 部分
-
.message: 主要消息容器,包含用户或 AI 消息 -
.avatar-wrapper: 头像区域 -
- 消息内容区域
.bubble-wrapper.bubble: 消息文本,支持打字机效果.time: 消息时间
script setup 部分
组件属性 props
content:消息内容isUser:是否为用户消息useTypewriter(可选):是否启用打字机效果
组件事件 emit
complete:打字机动画完成事件
关键方法
formatTime():格式化消息时间(HH:mm格式)
组件使用
<MessageBubble :content="'你好!这是 AI 回复的消息。'" :isUser="false" :useTypewriter="true" @complete="handleTypingComplete"
/>
完整代码如下:
<template><!-- 消息气泡组件 --><div :class="['message', isUser ? 'message-user' : 'message-ai']"><div class="message-content"><!-- 头像区域 --><div class="avatar-wrapper"><el-avatar :size="40" :class="isUser ? 'user-avatar' : 'ai-avatar'":src="isUser ? userAvatar : undefined"><el-icon v-if="isUser && !userAvatar"><User /></el-icon><el-icon v-if="!isUser"><Service /></el-icon></el-avatar></div><!-- 消息内容区域 --><div class="bubble-wrapper"><div class="bubble"><TypeWriterv-if="!isUser && useTypewriter":text="content":speed="30"@complete="$emit('complete')"/><span v-else>{{ content }}</span></div><!-- 消息时间 --><div class="time">{{ formatTime() }}</div></div></div></div>
</template><script setup lang="ts">
import { User, Service } from '@element-plus/icons-vue'
import userAvatar from '@/assets/user.jpg'// 组件属性定义
defineProps<{content: string // 消息内容isUser: boolean // 是否为用户消息useTypewriter?: boolean // 是否使用打字机效果
}>()// 定义事件
defineEmits<{complete: [] // 打字完成事件
}>()// 格式化时间方法
const formatTime = () => {const now = new Date()return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
}
</script><style scoped>
/* 消息容器基础样式 */
.message {display: flex;margin-bottom: 20px;animation: fadeIn 0.3s ease-in-out;
}/* 头像样式 */
.message-content {display: flex;align-items: flex-start;gap: 12px;max-width: 70%;
}/* 用户头像样式 */
.avatar-wrapper {width: 40px;height: 40px;flex-shrink: 0;
}.user-avatar {box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}.user-avatar :deep(img) {object-fit: cover;width: 100%;height: 100%;
}.ai-avatar {background: #67c23a;box-shadow: 0 2px 8px rgba(103, 194, 58, 0.2);
}/* 气泡容器样式 */
.bubble-wrapper {display: flex;flex-direction: column;gap: 4px;
}.message-user {justify-content: flex-end;
}.message-user .message-content {flex-direction: row-reverse;
}/* 消息气泡样式 */
.bubble {padding: 16px 20px;border-radius: 12px;background: #fff;line-height: 1.6;font-size: 15px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);position: relative;transition: all 0.3s ease;
}.bubble:hover {box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}.message-user .bubble {background: #409eff;color: white;
}/* 时间显示样式 */
.time {font-size: 12px;color: #909399;margin: 0 8px;opacity: 0.8;
}.message-user .time {text-align: right;
}/* 动画效果 */
@keyframes fadeIn {from {opacity: 0;transform: translateY(10px);}to {opacity: 1;transform: translateY(0);}
}
</style>
4. TypeWriter.vue
组件描述
TypeWriter 组件用于实现文本逐字出现的打字机效果,适用于 AI 回复等场景。支持自定义打字速度、延迟时间,并提供事件监听打字过程。
打字机效果组件特点:
- 逐字显示文本
- 自定义打字速度
- 支持延迟启动
- 文本变化时重新播放
- 提供文本更新和完成事件
组件结构
template 部分
<slot>: 组件支持插槽,默认显示displayText
script setup 部分
组件属性 props
text:要显示的文本speed(可选):打字速度(默认30ms)delay(可选):延迟启动时间(默认0ms)
组件事件 emit
complete:打字完成事件textUpdate:每次文本更新时触发
组件状态
displayText:当前已显示的文本currentIndex:当前打字位置timer:定时器引用
关键方法
startTyping():递归执行逐字显示文本- 监听
props.text变化,重新播放打字动画 onUnmounted()清理定时器
组件使用
<TypeWriter :text="'你好,这是打字机效果演示。'" :speed="50" :delay="500"@complete="handleComplete"@textUpdate="handleTextUpdate"
><template #default="{ text }"><span class="custom-style">{{ text }}</span></template>
</TypeWriter>
完整代码如下:
<template><!-- 打字机效果组件 --><div><slot :text="displayText">{{ displayText }}</slot></div>
</template><script setup lang="ts">
import { ref, watch, onUnmounted } from 'vue'// 组件属性定义
const props = defineProps<{text: string // 要显示的文本speed?: number // 打字速度delay?: number // 开始延迟
}>()// 定义事件
const emit = defineEmits<{complete: [] // 打字完成事件textUpdate: [text: string] // 文本更新事件
}>()// 组件状态
const displayText = ref('')
let currentIndex = 0
let timer: number | null = null// 打字效果实现
const startTyping = () => {if (currentIndex < props.text.length) {displayText.value = props.text.slice(0, currentIndex + 1)emit('textUpdate', displayText.value)currentIndex++timer = window.setTimeout(startTyping, props.speed || 30)} else {emit('complete')}
}// 监听文本变化
watch(() => props.text, () => {if (timer) {clearTimeout(timer)}currentIndex = 0displayText.value = ''timer = window.setTimeout(startTyping, props.delay || 0)
}, { immediate: true })// 组件卸载时清理定时器
onUnmounted(() => {if (timer) {clearTimeout(timer)}
})
</script><style scoped>
/* 打字机容器样式 */
.typewriter {display: inline-block;
}
</style>
API 集成
aiService.ts
封装 DeepSeek API 的 AI 聊天服务,支持普通聊天、推理模式、流式响应等功能。提供灵活的模型切换和参数配置:
class AIChatService {// 普通对话请求async chat(messages: ChatCompletionRequestMessage[]) {// ... API 调用实现}// 流式对话请求async streamChat(messages: ChatCompletionRequestMessage[],onChunk: (chunk: string) => void) {// ... 流式 API 调用实现}// 模型配置更新updateConfig(newConfig: Partial<ChatRequestConfig>) {// ... 配置更新逻辑}
}
功能特点
- 支持普通聊天(同步请求)
- 支持推理模型(Reasoner)
- 支持流式响应
- 支持动态更新 API 配置
- 提供错误处理
枚举 ModelType
| 枚举项 | 说明 |
|---|---|
Chat | 普通聊天模型(deepseek-chat) |
Reasoner | 推理模型(deepseek-reasoner) |
类方法
| 方法 | 说明 |
|---|---|
chat(messages: ChatCompletionRequestMessage[]) | 发送聊天请求(同步) |
reason(prompt: string) | 使用 Reasoner 模型推理 |
updateConfig(newConfig: Partial<ChatRequestConfig>) | 更新配置 |
streamChat(messages: ChatCompletionRequestMessage[], onChunk: (chunk: string) => void) | 流式聊天 |
streamReason(prompt: string, onChunk: (chunk: string) => void) | 流式推理 |
使用示例
普通聊天
const response = await aiService.chat([{ role: 'user', content: '你好,AI!' }])
console.log(response) // 输出 AI 回复
流式聊天
await aiService.streamChat([{ role: 'user', content: '请介绍一下 Vue 3' }],(chunk) => {console.log('AI 回复片段:', chunk)}
)
推理模式
const result = await aiService.reason('如何优化前端性能?')
console.log(result)
更新配置
aiService.updateConfig({ temperature: 0.9, max_tokens: 1500 })
使用指南
打字机模式
<ChatContainertitle="DeepSeek 打字机模式":messages="messages":loading="loading"@send="handleSend"
/>
流式输出模式
<ChatContainertitle="DeepSeek 流式输出":messages="messages":loading="loading":stream-mode="true"@send="handleStreamSend"
/>
常见问题
API 相关
- ✅ 检查 API Key 配置
- 🌐 确认网络连接
- 🔍 查看控制台错误
- ⚙️ 验证请求参数
界面显示
- 📱 检查响应式布局
- 📏 确认容器高度
- 📜 验证滚动配置
- 🎨 检查样式冲突
性能优化
- 🔍 合理使用 v-show/v-if
- 🔄 避免深度监听
- 📊 优化滚动事件
- 💾 虚拟滚动处理
核心代码示例
1. 打字机效果 (TypeWriter.vue)
<template><span ref="textContainer"></span>
</template><script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";const props = defineProps<{text: string;speed?: number;
}>();const emit = defineEmits<{complete: [];
}>();const textContainer = ref<HTMLElement | null>(null);
let currentIndex = 0;
let timer: number | null = null;const startTyping = () => {if (currentIndex < props.text.length) {if (textContainer.value) {textContainer.value.textContent = props.text.slice(0, currentIndex + 1);}currentIndex++;timer = window.setTimeout(startTyping, props.speed || 30);} else {emit("complete");}
};onMounted(() => {startTyping();
});onUnmounted(() => {if (timer) clearTimeout(timer);
});
</script>
2. 流式输出 (StreamView.vue)
<script setup lang="ts">
const handleSend = async (message: string) => {messages.value.push({role: "user",content: message,});loading.value = true;try {const assistantMessage = {role: "assistant" as const,content: "",};messages.value.push(assistantMessage);// 流式回调处理const streamCallback = (chunk: string) => {assistantMessage.content += chunk;};await aiService.streamChat(messages.value.slice(0, -1), streamCallback);} catch (error) {ElMessage.error("发送消息失败,请重试");messages.value.pop();} finally {loading.value = false;}
};
</script>
3. API 服务 (aiService.ts)
class AIChatService {// 配置定义private config: ChatRequestConfig = {model: "deepseek-chat",temperature: 0.7,max_tokens: 2000,stream: false,system_message: "你是一个友好的中文助手。",};// 普通对话请求async chat(messages: ChatCompletionRequestMessage[]) {try {const response = await axios.post(`${API_CONFIG.baseURL}/v1/chat/completions`,{model: this.config.model,messages: [{ role: "system", content: this.config.system_message },...messages,],temperature: this.config.temperature,max_tokens: this.config.max_tokens,},{headers: {Authorization: `Bearer ${API_CONFIG.apiKey}`,},});return response.data.choices[0].message.content;} catch (error) {throw new Error("聊天服务出错了");}}// 流式对话请求async streamChat(messages: ChatCompletionRequestMessage[],onChunk: (chunk: string) => void) {try {const response = await fetch(`${API_CONFIG.baseURL}/v1/chat/completions`,{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Bearer ${API_CONFIG.apiKey}`,},body: JSON.stringify({model: this.config.model,messages: [{ role: "system", content: this.config.system_message },...messages,],stream: true,}),});const reader = response.body?.getReader();const decoder = new TextDecoder();while (reader) {const { done, value } = await reader.read();if (done) break;const chunk = decoder.decode(value);const lines = chunk.split("\n").filter((line) => line.trim());for (const line of lines) {if (line.startsWith("data: ")) {const data = JSON.parse(line.slice(6));const content = data.choices[0].delta.content;if (content) onChunk(content);}}}} catch (error) {throw new Error("流式聊天服务出错了");}}
}
4. 思考动画 (ChatContainer.vue)
<template><div v-if="loading" class="thinking-message"><div class="message-content"><el-avatar class="ai-avatar"><el-icon><Service /></el-icon></el-avatar><div class="bubble thinking-bubble"><div class="dots-container"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div></div></div></div>
</template><style scoped>
.thinking-message {opacity: 0.8;animation: fadeInUp 0.3s ease-out;
}.thinking-bubble {min-width: 60px;padding: 12px 16px;background: rgba(255, 255, 255, 0.9);backdrop-filter: blur(8px);
}.dots-container {display: flex;align-items: center;gap: 6px;height: 20px;
}.dot {width: 8px;height: 8px;background: #67c23a;border-radius: 50%;opacity: 0.8;animation: bounce 1.4s infinite ease-in-out both;
}@keyframes bounce {0%,80%,100% {transform: scale(0);}40% {transform: scale(1);}
}@keyframes fadeInUp {from {opacity: 0;transform: translateY(10px);}to {opacity: 0.8;transform: translateY(0);}
}
</style>
相关文章:
打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手
DeepSeek AI 聊天助手集成指南 先看完整效果: PixPin_2025-02-19_09-15-59 效果图: 目录 项目概述功能特点环境准备项目结构组件详解 ChatContainerChatInputMessageBubbleTypeWriter 核心代码示例使用指南常见问题 项目概述 基于 Vue 3 TypeScrip…...
C语言-语法
数据类型 字符串 C中字符串拼接不用+号,直接使用空格。 char* str = "hello" "world"; 换行链接,加上\就不会报错 char* longStr = "00000000000000000000000000000\ 00000000000000000000000000000"; typedef C 语言提供了 typedef …...
Unity组件TrailRenderer屏幕滑动拖尾
Unity组件TrailRenderer屏幕滑动拖尾 介绍制作总结 介绍 今天要做一个拖动效果,正好用到了TrailRenderer这个组件,正好分享一下 效果参考如下: 制作 1.创建空物体TrailObject添加组件TrailRenderer 下面的材质可以根据自己想要制作的效果去…...
基于昇腾MindIE与GPUStack的大模型容器化部署从入门到入土
引言 昇腾MindIE作为华为面向大模型推理的高性能引擎,结合GPUStack的集群管理能力,能够实现多机多卡的高效资源调度与模型服务化部署。本文将以DeepSeek R1-32B模型为例,详细解析从环境准备到服务验证的全流程实践,涵盖昇腾NPU驱…...
大模型信息整理
1. Benchmarks Reasoning, conversation, Q&A benchmarks HellaSwagBIG-Bench HardSQuADIFEvalMuSRMMLU-PROMT-BenchDomain-specific benchmarks GPQAMedQAPubMedQAMath benchmarks GSM8KMATHMathEvalSecurity-related benchmarks PyRITPurple Llama CyberSecEval2. 国内外…...
【Tools】Windows下Git 2.48安装教程详解
00. 目录 文章目录 00. 目录01. Git简介02. Git参考资料03. Git安装04. Git测试05. 附录 01. Git简介 Git(读音为/gɪt/。)是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核…...
【linux网络编程】套接字编程API详细介绍
在C语言中,套接字(Socket)编程主要用于网络通信,尤其是在基于TCP/IP协议的应用程序开发中。常用的套接字编程API主要基于Berkeley Sockets(伯克利套接字)接口,这些函数通常在<sys/socket.h&g…...
护网中shiro常问的问题
1. 漏洞原理 Apache Shiro 是一个强大的 Java 安全框架,提供身份验证、授权、加密及会话管理功能。Shiro 使用 rememberMe 机制来存储用户会话信息,该机制依赖于加密后的 Cookie。当攻击者能够控制 Cookie 并且服务器使用了不安全的反序列化机制时&…...
fastapi房产销售系统
说明: 我希望用fastapi写几个接口,查询房产交易系统的几条数据,然后在postman里面测试 查询客户所有预约记录(含房源信息)需要对应销售经理查询客户所有订单(含房源信息)统计销售经理名下所有房…...
swift -(5) 汇编分析结构体、类的内存布局
一、结构体 在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分 比如Bool、 Int、 Double、 String、 Array、 Dictionary等常见类型都是结构体 ① struct Date { ② var year: Int ③ var month: Int ④ …...
软件工程笔记下
从程序到软件☆ 章节 知识点 概论☆ 软件的定义,特点,生存周期。软件工程的概论。软件危机。 1.☆软件:软件程序数据文档 (1)软件:是指在计算机系统的支持下,能够完成特定功能与性能的包括…...
ElementUI 级联选择器el-cascader启用选择任意一级选项,选中后关闭下拉框
1、启用选择任意一级选项 在 el-cascader 标签上加上配置项: :props"{ checkStrictly: true }"例如: <el-cascaderref"selectedArrRef"v-model"selectedArr":options"optionsList":props"{ checkStri…...
【项目日记(九)】细节优化与对比测试
前言 上面我们对申请和释放的过程都已写完,并进行了单线程的联调。本期我们来对一些细节进行优化以及与malloc 进行对比测试。 目录 前言 一、大于256KB的内存申请问题 • 申请过程 • 释放过程 • 简单测试 二、使用定长内存池脱离使用new 三、优化释放对…...
PyTorch系列教程:编写高效模型训练流程
当使用PyTorch开发机器学习模型时,建立一个有效的训练循环是至关重要的。这个过程包括组织和执行对数据、参数和计算资源的操作序列。让我们深入了解关键组件,并演示如何构建一个精细的训练循环流程,有效地处理数据处理,向前和向后…...
10 【HarmonyOS NEXT】 仿uv-ui组件开发之Avatar头像组件开发教程(一)
温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦! 目录 第一篇:Avatar 组件基础概念与设计1. 组件概述2. 接口设计2.1 形状类型定义2.2 尺寸类型定义2.3 组件属性接口 3. 设计原则4. 使用…...
C++编程指南24 - 避免线程频繁的创建和销毁
一:概述 线程的创建和销毁是昂贵的操作,尤其在多线程程序中频繁创建和销毁线程时,可能会导致性能问题。 二:示例 这段代码中,dispatcher 每收到一个 Message 就创建一个新的线程来处理这个消息。这种方式虽然简单&…...
C语言——【全局变量和局部变量】
🚀个人主页:fasdfdaslsfadasdadf 📖收入专栏:C语言 🌍文章目入 1.🚀 全局变量2.🚀 局部变量3.🚀 局部和全局变量,名字相同呢? 1.🚀 全局变量 全局变量&…...
浅谈 DeepSeek 对 DBA 的影响
引言: 在人工智能技术飞速发展的背景下,DeepSeek 作为一款基于混合专家模型(MoE)和强化学习技术的大语言模型,正在重塑传统数据库管理(DBA)的工作模式。通过结合其强大的自然语言处理能力、推理…...
Web服务器配置
配置虚拟主机:启动XAMPP的Apache,在htdocs目录中创建www.php.test目录 创建index.html,内容为“Welcome www.php.test”,访问两个虚拟主机 访问权限控制 在Apache的主配置文件httpd.conf中,默认已经添加了一些目录的…...
DeepSeek-R1本地化部署(Mac)
一、下载 Ollama 本地化部署需要用到 Ollama,它能支持很多大模型。官方网站:https://ollama.com/ 点击 Download 即可,支持macOS,Linux 和 Windows;我下载的是 mac 版本,要求macOS 11 Big Sur or later,Ol…...
Java面试第九山!《SpringBoot框架》
引言 你是否经历过这样的场景?想快速开发一个Java Web应用,却被XML配置、依赖冲突、服务器部署搞得焦头烂额。Spring Boot的诞生,正是为了解决这些"配置地狱"问题。 对比项Spring Boot传统 Spring配置复杂度自动配置,…...
Java 中数据脱敏的实现
数据脱敏 首先,要思考一个问题,SpringBoot 查询到的一条数据是一个 Java 对象,为什么返回给前端时候,前端拿到的却是 JSON 格式的数据呢? 是因为 SpringBoot 默认采用了 Jackson 作为序列化器,而 Jackson…...
PyQt组件间的通信方式
PyQt组件间的通信方式 PyQt组件间的通信方式 1. 组件介绍 1.1 组件的定义1.2 组件的分类 2. 组件的通信方式 2.1 信号与槽(Signal & Slot) 1. 组件介绍 在 Qt 框架中,组件(Component)是构建图形用户界面&am…...
视频理解开山之作 “双流网络”
1 论文核心信息 1.1核心问题 任务:如何利用深度学习方法进行视频中的动作识别(Action Recognition)。挑战: 视频包含时空信息,既需要捕捉静态外观特征(Spatial Information),也需要…...
基于Matlab的人脸识别的二维PCA
一、基本原理 传统 PCA 在处理图像数据时,需将二维图像矩阵拉伸为一维向量,这使得数据维度剧增,引发高计算成本与存储压力。与之不同,2DPCA 直接基于二维图像矩阵展开运算。 它着眼于图像矩阵的列向量,构建协方差矩阵…...
Java直通车系列15【Spring MVC】(ModelAndView 使用)
目录 1. ModelAndView 概述 2. ModelAndView 的主要属性和方法 主要属性 主要方法 3. 场景示例 示例 1:简单的 ModelAndView 使用 示例 2:使用 ModelAndView 处理列表数据 示例 3:使用 ModelAndView 处理异常情况 1. ModelAndView 概…...
考研数一非数竞赛复习之Stolz定理求解数列极限
在非数类大学生数学竞赛中,Stolz定理作为一种强大的工具,经常被用来解决和式数列极限的问题,也被誉为离散版的’洛必达’方法,它提供了一种简洁而有效的方法,使得原本复杂繁琐的极限计算过程变得直观明了。本文&#x…...
Java在小米SU7 Ultra汽车中的技术赋能
目录 一、智能驾驶“大脑”与实时数据 场景一:海量数据的分布式计算 场景二:实时决策的毫秒级响应 场景三:弹性扩展与容错机制 技术隐喻: 二、车载信息系统(IVI)的交互 场景一:Android Automo…...
【简单的C++围棋游戏开发示例】
C围棋游戏开发简单示例(控制台版) 核心代码实现 #include <iostream> #include <vector> #include <queue> using namespace std;const int SIZE 9; // 简化棋盘为9x9:ml-citation{ref"1" data"citationList&…...
DeepSeek R1-7B 医疗大模型微调实战全流程分析(全码版)
DeepSeek R1-7B 医疗大模型微调实战全流程指南 目录 环境配置与硬件优化医疗数据工程微调策略详解训练监控与评估模型部署与安全持续优化与迭代多模态扩展伦理与合规体系故障排除与调试行业应用案例进阶调优技巧版本管理与迭代法律风险规避成本控制方案文档与知识传承1. 环境配…...
