打造智能聊天体验:前端集成 DeepSeek AI 助你快速上手

DeepSeek AI 聊天助手集成指南

先看完整效果:

PixPin_2025-02-19_09-15-59

效果图:
请添加图片描述

请添加图片描述

请添加图片描述

目录

  1. 项目概述
  2. 功能特点
  3. 环境准备
  4. 项目结构
  5. 组件详解
    • ChatContainer
    • ChatInput
    • MessageBubble
    • TypeWriter
  6. 核心代码示例
  7. 使用指南
  8. 常见问题

项目概述

基于 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>) {// ... 配置更新逻辑}
}
功能特点
  1. 支持普通聊天(同步请求)
  2. 支持推理模型(Reasoner)
  3. 支持流式响应
  4. 支持动态更新 API 配置
  5. 提供错误处理
枚举 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/30454.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JPA编程,去重查询ES索引中的字段,对已有数据的去重过滤,而非全部字典数据

一、背景 课程管理界面&#xff0c;查询前&#xff0c;需要把查询元数据给出。 学科列表、学段列表和分类列表&#xff0c;我们把它定义为查询元数据。 一般的业务需求是&#xff1a; 系统维护好多个字典&#xff0c;比如学科、学段等等&#xff0c;相当于属性库。 但是&…

MySQL语法总结

本篇博客说明&#xff1a; &#xff01;&#xff01;&#xff01;.注意此系列都用的是MySQL语句&#xff0c;和SQLServer&#xff0c;PostgreSQL有些细节上的差别&#xff01;&#xff01;&#xff01; 1.每个操作都是先展示出语法格式 2.然后是具体例子 3.本篇注脚与文本顺讯息…

C++ Primer 交换操作

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…

go切片定义和初始化

1.简介 切片是数组的一个引用&#xff0c;因此切片是引用类型&#xff0c;在进行传递时&#xff0c;遵守引用传递的机制。切片的使用和数组类似&#xff0c;遍历切片、访问切片的元素和切片的长度都一样。。切片的长度是可以变化的&#xff0c;因此切片是一个可以动态变化的数…

huggingface镜像站hf-mirror的各大AI模型文件下载

一、说明 huggingface地址&#xff1a;https://huggingface.co 但是由于需要国外网络&#xff0c;国内网络延迟较大或无法下载&#xff0c;所以使用国内镜像站&#xff1a; hf-mirror地址&#xff1a;https://hf-mirror.com/ 二、下载方法 1.本机安装了GIT 2.打开HF-Mirro…

Unity Shader 学习15:可交互式雪地流程

本质是 利用顶点变换实现的&#xff1a; 通过一个俯视整个场地的正交摄像机&#xff0c;根据绑定在移动物体身上的粒子系统&#xff0c;来获取物体移动过的位置&#xff0c;记录到一张RenderTexture上作为轨迹图&#xff0c;再通过这张图来对雪地做顶点变换。 1. 由于顶点变换需…

自由学习记录(42)

可能会出现到后面没有教程可以看&#xff0c;走不动&#xff0c;&#xff0c;但还是尝试吧 过程远比想象的要多 那连Live2d的这些脚本怎么控制的都要了解一下 ------------ 文件类型和扩展名 | 编辑手册 | Live2D Manuals & Tutorials 全部导入之后 在这下载SDK Live2D…

LeetCode - 28 找出字符串中第一个匹配项的下标

题目来源 28. 找出字符串中第一个匹配项的下标 - 力扣&#xff08;LeetCode&#xff09; 题目解析 暴力解法 本题如果采用暴力解法的话&#xff0c;可以定义两个指针 i&#xff0c;j&#xff0c;其中 i 指针用于扫描 S&#xff08;haystack&#xff09;串&#xff0c;j 指针…

windows下使用msys2编译ffmpeg

三种方法&#xff1a; 1、在msys2中使用gcc编译 2、在msys2中使用visual studio编译&#xff08;有环境变量&#xff09; 3、在msys2中使用visual studio编译&#xff08;无环境变量&#xff09; 我的环境&#xff1a; 1、msys2-x86_64-20250221 2、vs2015 3、ffmpeg-7.1…

【Python 数据结构 9.树】

我装作漠视一切&#xff0c;其实我在乎的太多&#xff0c;但我知道抓得越紧越容易失去 —— 25.3.6 一、树的基本概念 1.树的定义 树是n个结点的有限集合&#xff0c;n0时为空树。当n大于0的时候&#xff0c;满足如下两个条件&#xff1a; ① 有且仅有一个特定的结点&#xff…

数据结构--AVL树

一、二叉搜索树&#xff08;Binary Search Tree, BST&#xff09; 基本性质 对于树中的每个节点&#xff0c;其左子树中的所有节点值均小于该节点值。其右子树中的所有节点值均大于该节点值。左右子树也分别是二叉搜索树。 极端场景 在极端情况下&#xff0c;如插入节点顺序…

C语言——链表

大神文献&#xff1a;https://blog.csdn.net/weixin_73588765/article/details/128356985 目录 一、链表概念 1. 什么是链表&#xff1f; 1.1 链表的构成 2. 链表和数组的区别 数组的特点&#xff1a; 链表的特点&#xff1a; 二者对比&#xff1a; 二…

Qt:多线程

目录 初识Qt多线程 QThread常用API QThread的使用 Qt中的锁 条件变量和信号量 初识Qt多线程 Qt 多线程 和 Linux 中的线程本质是一个东西 Linux 中学过的 多线程 APl&#xff0c;Linux 系统提供的 pthread 库 Qt 中针对系统提供的线程 API 重新封装了 C11 中&#xff0c;…

如何用Kimi生成PPT?秒出PPT更高效!

做PPT是不是总是让你头疼&#xff1f;&#x1f629; 快速制作出专业的PPT&#xff0c;今天我们要推荐两款超级好用的AI工具——Kimi 和 秒出PPT&#xff01;我们来看看哪一款更适合你吧&#xff01;&#x1f680; &#x1f947; Kimi&#xff1a;让PPT制作更轻松 Kimi的生成效…

计算机毕业设计SpringBoot+Vue.js车辆管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

机器学习4-PCA降维

1 降维 在数据处理过程中&#xff0c;会碰到维度爆炸&#xff0c;维度灾难的情况&#xff0c;为了得到更精简更有价值的信息&#xff0c;我们需要进一步处理&#xff0c;用的方法就是降维。 降维有两种方式&#xff1a;特征抽取、特征选择 特征抽取&#xff1a;就是特征映射…

探秘沃尔什-哈达玛变换(WHT)原理

沃尔什-哈达玛变换&#xff08;WHT&#xff09;起源 起源与命名&#xff08;20世纪早期&#xff09; 数学基础&#xff1a;该变换的理论基础由法国数学家雅克哈达玛&#xff08;Jacques Hadamard&#xff09;在1893年提出&#xff0c;其核心是哈达玛矩阵的构造。扩展与命名&…

使用 vxe-table 导出 excel,支持带数值、货币、图片等带格式导出

使用 vxe-table 导出 excel&#xff0c;支持带数值、货币、图片等带格式导出&#xff0c;通过官方自动的导出插件 plugin-export-xlsx 实现导出功能 查看官网&#xff1a;https://vxetable.cn gitbub&#xff1a;https://github.com/x-extends/vxe-table gitee&#xff1a;htt…

uniapp实现的个人中心页面(仿小红书)

采用 uniapp 实现的一款仿小红书个人中心页面模板&#xff0c;支持vue2、vue3, 同时适配H5、小程序等多端多应用。 简约美观大方 可到插件市场下载尝试&#xff1a; https://ext.dcloud.net.cn/plugin?id22516 示例

步进电机软件细分算法解析与实践指南

1. 步进电机细分技术概述 步进电机是一种将电脉冲信号转换为角位移的执行机构&#xff0c;其基本运动单位为步距角。传统步进电机的步距角通常为 1.8&#xff08;对应 200 步 / 转&#xff09;&#xff0c;但在高精度定位场景下&#xff0c;这种分辨率已无法满足需求。细分技术…