news 2026/2/12 4:35:46

前端开发实战:Vue.js调用Baichuan-M2-32B医疗咨询接口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端开发实战:Vue.js调用Baichuan-M2-32B医疗咨询接口

前端开发实战:Vue.js调用Baichuan-M2-32B医疗咨询接口

1. 医疗AI应用场景与需求分析

在医疗健康领域,智能咨询系统正在改变传统的医患交互方式。通过集成先进的大语言模型,前端应用能够为用户提供即时、专业的医疗建议和健康指导。Baichuan-M2-32B作为专为医疗场景优化的开源模型,在HealthBench评测中表现优异,特别适合构建医疗咨询类应用。

这类应用的核心需求包括:友好的用户界面设计、实时流畅的对话体验、对话历史管理、以及安全可靠的信息展示。Vue.js作为现代前端框架,以其响应式特性和组件化架构,成为构建此类应用的理想选择。

2. 技术架构与环境准备

2.1 基础技术栈

构建医疗咨询前端应用需要以下技术组件:

  • Vue 3组合式API:提供更好的类型支持和代码组织
  • TypeScript:增强代码可靠性和开发体验
  • Axios:处理HTTP请求和API调用
  • Element Plus或Ant Design Vue:UI组件库
  • Vue Router:单页面应用路由管理

2.2 项目初始化

首先创建Vue项目并安装必要依赖:

npm create vue@latest medical-consultation-app cd medical-consultation-app npm install npm install axios element-plus @element-plus/icons-vue

配置TypeScript支持以确保类型安全:

// tsconfig.json { "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] }

3. API接口封装与配置

3.1 配置API基础设置

创建API配置文件,封装Baichuan-M2-32B的调用逻辑:

// src/api/config.ts export const API_CONFIG = { baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:8000', timeout: 30000, headers: { 'Content-Type': 'application/json', }, }; export interface MedicalConsultationRequest { messages: Array<{ role: 'user' | 'assistant' | 'system'; content: string; }>; max_tokens?: number; temperature?: number; } export interface MedicalConsultationResponse { id: string; choices: Array<{ message: { role: string; content: string; }; finish_reason: string; }>; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number; }; }

3.2 实现API服务层

创建专门的API服务类处理医疗咨询请求:

// src/api/medicalService.ts import axios from 'axios'; import { API_CONFIG, MedicalConsultationRequest, MedicalConsultationResponse } from './config'; class MedicalService { private axiosInstance; constructor() { this.axiosInstance = axios.create(API_CONFIG); // 请求拦截器 this.axiosInstance.interceptors.request.use( (config) => { console.log('发送医疗咨询请求:', config); return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 this.axiosInstance.interceptors.response.use( (response) => { console.log('收到医疗咨询响应:', response); return response; }, (error) => { console.error('医疗咨询请求错误:', error); return Promise.reject(error); } ); } async consult(request: MedicalConsultationRequest): Promise<MedicalConsultationResponse> { try { const response = await this.axiosInstance.post('/v1/chat/completions', request); return response.data; } catch (error) { throw new Error(`医疗咨询请求失败: ${error instanceof Error ? error.message : '未知错误'}`); } } // 流式响应处理 async consultStream( request: MedicalConsultationRequest, onMessage: (content: string) => void, onComplete: () => void, onError: (error: Error) => void ) { try { const response = await fetch(`${API_CONFIG.baseURL}/v1/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ...request, stream: true, }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) { throw new Error('无法读取响应流'); } while (true) { const { done, value } = await reader.read(); if (done) { onComplete(); 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 = line.slice(6); if (data === '[DONE]') { onComplete(); return; } try { const parsed = JSON.parse(data); const content = parsed.choices[0]?.delta?.content; if (content) { onMessage(content); } } catch (e) { console.warn('解析流式响应失败:', e); } } } } } catch (error) { onError(error instanceof Error ? error : new Error('流式请求失败')); } } } export const medicalService = new MedicalService();

4. Vue组件设计与实现

4.1 医疗咨询主界面组件

创建主要的医疗咨询界面组件:

<template> <div class="medical-consultation"> <div class="header"> <h1>智能医疗咨询</h1> <p>基于Baichuan-M2-32B的专业医疗建议</p> </div> <div class="chat-container"> <div class="messages" ref="messagesRef"> <div v-for="(message, index) in messages" :key="index" :class="['message', message.role]" > <div class="avatar"> <el-icon v-if="message.role === 'user'"><User /></el-icon> <el-icon v-else><FirstAidKit /></el-icon> </div> <div class="content"> <div class="text" v-html="formatMessage(message.content)"></div> <div class="timestamp">{{ formatTime(message.timestamp) }}</div> </div> </div> <div v-if="isLoading" class="message assistant loading"> <div class="avatar"> <el-icon><FirstAidKit /></el-icon> </div> <div class="content"> <div class="thinking">思考中<span class="dots">...</span></div> </div> </div> </div> <div class="input-area"> <el-input v-model="userInput" type="textarea" :rows="3" placeholder="请输入您的医疗咨询问题..." @keydown.enter="handleSend" :disabled="isLoading" /> <el-button type="primary" :loading="isLoading" @click="handleSend" class="send-button" > 发送 </el-button> </div> </div> <div class="sidebar"> <div class="history-section"> <h3>咨询历史</h3> <el-scrollbar> <div v-for="(session, index) in historySessions" :key="index" class="history-item" @click="loadSession(session)" > <div class="session-title">{{ session.title }}</div> <div class="session-time">{{ formatSessionTime(session.timestamp) }}</div> </div> </el-scrollbar> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, nextTick } from 'vue' import { ElMessage } from 'element-plus' import { User, FirstAidKit } from '@element-plus/icons-vue' import { medicalService } from '@/api/medicalService' interface Message { role: 'user' | 'assistant' content: string timestamp: Date } interface ChatSession { id: string title: string messages: Message[] timestamp: Date } const userInput = ref('') const messages = ref<Message[]>([]) const isLoading = ref(false) const messagesRef = ref<HTMLElement>() // 初始化示例对话 onMounted(() => { messages.value = [ { role: 'assistant', content: '您好!我是您的智能医疗助手。我可以为您提供健康咨询、症状分析、用药建议等医疗服务。请注意,我的建议不能替代专业医生的诊断,如有紧急情况请及时就医。', timestamp: new Date() } ] }) const scrollToBottom = () => { nextTick(() => { if (messagesRef.value) { messagesRef.value.scrollTop = messagesRef.value.scrollHeight } }) } const formatMessage = (content: string) => { // 简单的格式化处理 return content.replace(/\n/g, '<br>') } const formatTime = (date: Date) => { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }) } const formatSessionTime = (date: Date) => { return date.toLocaleDateString('zh-CN') } const handleSend = async () => { if (!userInput.value.trim() || isLoading.value) return const userMessage: Message = { role: 'user', content: userInput.value.trim(), timestamp: new Date() } messages.value.push(userMessage) userInput.value = '' isLoading.value = true scrollToBottom() try { let assistantResponse = '' await medicalService.consultStream( { messages: [ { role: 'system', content: '你是一个专业的医疗助手,提供准确、谨慎的医疗建议。强调需要专业医生诊断的重要性。' }, ...messages.value.map(msg => ({ role: msg.role, content: msg.content })) ], max_tokens: 1000, temperature: 0.7 }, (content) => { assistantResponse += content // 更新最后一条消息内容 if (messages.value[messages.value.length - 1]?.role === 'assistant') { messages.value[messages.value.length - 1].content = assistantResponse } else { messages.value.push({ role: 'assistant', content: assistantResponse, timestamp: new Date() }) } scrollToBottom() }, () => { isLoading.value = false scrollToBottom() }, (error) => { ElMessage.error(`咨询失败: ${error.message}`) isLoading.value = false } ) } catch (error) { ElMessage.error('咨询请求失败,请稍后重试') isLoading.value = false } } // 历史会话相关功能 const historySessions = ref<ChatSession[]>([]) const loadSession = (session: ChatSession) => { messages.value = session.messages scrollToBottom() } </script> <style scoped> .medical-consultation { display: grid; grid-template-columns: 1fr 300px; grid-template-rows: auto 1fr; gap: 20px; height: 100vh; padding: 20px; background: #f5f7fa; } .header { grid-column: 1 / -1; text-align: center; padding: 20px; } .chat-container { display: flex; flex-direction: column; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); overflow: hidden; } .messages { flex: 1; padding: 20px; overflow-y: auto; max-height: 60vh; } .message { display: flex; margin-bottom: 20px; gap: 12px; } .message.user { flex-direction: row-reverse; } .avatar { width: 40px; height: 40px; border-radius: 50%; background: #409eff; color: white; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .message.user .avatar { background: #67c23a; } .content { max-width: 70%; background: #f5f7fa; padding: 12px 16px; border-radius: 12px; position: relative; } .message.user .content { background: #409eff; color: white; } .timestamp { font-size: 12px; color: #909399; margin-top: 4px; } .message.user .timestamp { color: rgba(255, 255, 255, 0.8); } .input-area { padding: 20px; border-top: 1px solid #ebeef5; display: flex; gap: 12px; align-items: flex-end; } .send-button { height: 88px; } .sidebar { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); padding: 20px; } .history-section h3 { margin-bottom: 16px; color: #303133; } .history-item { padding: 12px; border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: background-color 0.2s; } .history-item:hover { background: #f5f7fa; } .session-title { font-weight: 500; margin-bottom: 4px; } .session-time { font-size: 12px; color: #909399; } .thinking { color: #909399; font-style: italic; } .dots { animation: blink 1.4s infinite; } @keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } </style>

4.2 医疗咨询功能增强组件

创建专门的医疗咨询功能组件,增强用户体验:

<template> <div class="medical-features"> <div class="quick-questions"> <h4>常见问题</h4> <el-space wrap> <el-tag v-for="(question, index) in quickQuestions" :key="index" type="info" effect="dark" @click="selectQuestion(question)" class="question-tag" > {{ question }} </el-tag> </el-space> </div> <div class="safety-notice"> <el-alert title="重要提示" type="warning" :closable="false" description="本咨询服务仅供参考,不能替代专业医疗诊断。如有紧急情况,请立即就医或拨打急救电话。" /> </div> <div class="consultation-tips"> <h4>咨询建议</h4> <ul> <li>详细描述您的症状和持续时间</li> <li>提供相关的医疗历史和用药情况</li> <li>如有图片资料(如皮疹、伤口),可准备描述</li> <li>保持网络通畅以获得最佳咨询体验</li> </ul> </div> </div> </template> <script setup lang="ts"> const emit = defineEmits(['question-selected']) const quickQuestions = [ '感冒了怎么办?', '头痛应该吃什么药?', '如何预防高血压?', '糖尿病饮食要注意什么?', '运动损伤如何处理?' ] const selectQuestion = (question: string) => { emit('question-selected', question) } </script> <style scoped> .medical-features { padding: 20px; background: white; border-radius: 12px; margin-top: 20px; } .quick-questions { margin-bottom: 20px; } .quick-questions h4 { margin-bottom: 12px; color: #303133; } .question-tag { cursor: pointer; transition: transform 0.2s; } .question-tag:hover { transform: translateY(-2px); } .safety-notice { margin-bottom: 20px; } .consultation-tips h4 { margin-bottom: 12px; color: #303133; } .consultation-tips ul { color: #606266; line-height: 1.6; } .consultation-tips li { margin-bottom: 8px; } </style>

5. 状态管理与数据持久化

5.1 Pinia状态管理

使用Pinia管理应用状态:

// src/stores/medicalStore.ts import { defineStore } from 'pinia' import { ref } from 'vue' export interface ChatMessage { id: string role: 'user' | 'assistant' content: string timestamp: Date } export interface ChatSession { id: string title: string messages: ChatMessage[] createdAt: Date updatedAt: Date } export const useMedicalStore = defineStore('medical', () => { const currentSession = ref<ChatSession | null>(null) const sessions = ref<ChatSession[]>([]) const isLoading = ref(false) // 创建新会话 const createNewSession = () => { const newSession: ChatSession = { id: Date.now().toString(), title: '新会话', messages: [], createdAt: new Date(), updatedAt: new Date() } currentSession.value = newSession sessions.value.unshift(newSession) saveToLocalStorage() } // 添加消息到当前会话 const addMessage = (message: Omit<ChatMessage, 'id'>) => { if (!currentSession.value) { createNewSession() } const newMessage: ChatMessage = { ...message, id: Date.now().toString() } currentSession.value!.messages.push(newMessage) currentSession.value!.updatedAt = new Date() // 更新会话标题(使用第一条用户消息) if (message.role === 'user' && currentSession.value.messages.length === 1) { currentSession.value.title = message.content.slice(0, 30) + (message.content.length > 30 ? '...' : '') } saveToLocalStorage() } // 加载会话 const loadSession = (sessionId: string) => { const session = sessions.value.find(s => s.id === sessionId) if (session) { currentSession.value = session } } // 删除会话 const deleteSession = (sessionId: string) => { sessions.value = sessions.value.filter(s => s.id !== sessionId) if (currentSession.value?.id === sessionId) { currentSession.value = sessions.value[0] || null } saveToLocalStorage() } // 本地存储 const saveToLocalStorage = () => { localStorage.setItem('medicalSessions', JSON.stringify({ sessions: sessions.value, currentSessionId: currentSession.value?.id })) } const loadFromLocalStorage = () => { const data = localStorage.getItem('medicalSessions') if (data) { try { const parsed = JSON.parse(data) sessions.value = parsed.sessions.map((s: any) => ({ ...s, createdAt: new Date(s.createdAt), updatedAt: new Date(s.updatedAt), messages: s.messages.map((m: any) => ({ ...m, timestamp: new Date(m.timestamp) })) })) if (parsed.currentSessionId) { currentSession.value = sessions.value.find(s => s.id === parsed.currentSessionId) || null } } catch (error) { console.error('加载本地存储失败:', error) } } } return { currentSession, sessions, isLoading, createNewSession, addMessage, loadSession, deleteSession, loadFromLocalStorage, saveToLocalStorage } })

6. 错误处理与用户体验优化

6.1 全局错误处理

实现全局错误处理机制:

// src/utils/errorHandler.ts import { ElMessage } from 'element-plus' export class MedicalError extends Error { constructor( message: string, public code?: string, public details?: any ) { super(message) this.name = 'MedicalError' } } export const handleError = (error: unknown) => { if (error instanceof MedicalError) { switch (error.code) { case 'NETWORK_ERROR': ElMessage.error('网络连接失败,请检查网络设置') break case 'API_ERROR': ElMessage.error('服务暂时不可用,请稍后重试') break case 'TIMEOUT_ERROR': ElMessage.error('请求超时,请检查网络状况') break default: ElMessage.error(error.message || '发生未知错误') } } else if (error instanceof Error) { ElMessage.error(error.message) } else { ElMessage.error('发生未知错误') } console.error('Medical error:', error) } // 在main.ts中设置全局错误处理 export const setupErrorHandling = (app: any) => { app.config.errorHandler = (err: unknown) => { handleError(err) } window.addEventListener('unhandledrejection', (event) => { handleError(event.reason) event.preventDefault() }) }

6.2 用户体验优化措施

实现加载状态、重试机制等用户体验优化:

<template> <div class="loading-overlay" v-if="loading"> <div class="loading-content"> <el-icon class="loading-icon"><Loading /></el-icon> <p>正在处理您的咨询请求...</p> <p class="loading-tip">这通常需要几秒钟时间</p> </div> </div> <div class="error-retry" v-if="error"> <el-alert :title="error.title" :description="error.message" type="error" show-icon /> <el-button @click="handleRetry" type="primary" class="retry-button"> 重试 </el-button> </div> </template> <script setup lang="ts"> import { ref } from 'vue' import { Loading } from '@element-plus/icons-vue' interface ErrorInfo { title: string message: string } const loading = ref(false) const error = ref<ErrorInfo | null>(null) const handleRetry = () => { error.value = null // 重试逻辑 } </script> <style scoped> .loading-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.9); display: flex; align-items: center; justify-content: center; z-index: 9999; } .loading-content { text-align: center; } .loading-icon { font-size: 48px; color: #409eff; animation: spin 1s linear infinite; } .loading-tip { color: #909399; font-size: 14px; margin-top: 8px; } .error-retry { padding: 20px; text-align: center; } .retry-button { margin-top: 16px; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } </style>

7. 总结

通过Vue.js集成Baichuan-M2-32B医疗大模型,我们成功构建了一个功能完善、用户体验良好的医疗咨询前端应用。这个方案展示了现代前端框架与AI大模型结合的实际应用价值。

在实际开发过程中,有几个关键点值得特别注意:首先是流式响应的处理,这对于保持对话的自然流畅至关重要;其次是错误处理和用户反馈,医疗类应用需要格外注重信息的准确性和可靠性;最后是状态管理和数据持久化,确保用户咨询历史的完整保存。

这个实现方案不仅适用于医疗咨询场景,其架构设计和技术选型也可以扩展到其他领域的AI对话应用。随着大模型技术的不断发展,前端与AI的结合将会创造出更多有价值的应用场景。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/12 6:30:12

Xinference入门指南:5步搞定开源LLM模型部署

Xinference入门指南&#xff1a;5步搞定开源LLM模型部署 1. 引言&#xff1a;为什么选择Xinference&#xff1f; 你是否曾经想要在自己的服务器上运行类似ChatGPT的大语言模型&#xff0c;但又觉得部署过程太复杂&#xff1f;或者担心商业API的费用太高、数据隐私问题&#x…

作者头像 李华
网站建设 2026/2/11 1:02:39

告别压枪难题:雷蛇鼠标宏的3大核心优化方案

告别压枪难题&#xff1a;雷蛇鼠标宏的3大核心优化方案 【免费下载链接】logitech-pubg PUBG no recoil script for Logitech gaming mouse / 绝地求生 罗技 鼠标宏 项目地址: https://gitcode.com/gh_mirrors/lo/logitech-pubg 雷蛇鼠标宏作为提升《绝地求生》射击精度…

作者头像 李华
网站建设 2026/2/11 1:01:52

5个维度解析thief-book-idea:重新定义开发者的碎片化时间管理

5个维度解析thief-book-idea&#xff1a;重新定义开发者的碎片化时间管理 【免费下载链接】thief-book-idea IDEA插件版上班摸鱼看书神器 项目地址: https://gitcode.com/gh_mirrors/th/thief-book-idea 作为开发者效率工具的创新实践&#xff0c;thief-book-idea IDE阅…

作者头像 李华
网站建设 2026/2/11 1:01:41

Windows驱动存储深度管理:从异常诊断到长效优化

Windows驱动存储深度管理&#xff1a;从异常诊断到长效优化 【免费下载链接】DriverStoreExplorer Driver Store Explorer [RAPR] 项目地址: https://gitcode.com/gh_mirrors/dr/DriverStoreExplorer 在Windows系统运维中&#xff0c;驱动存储区&#xff08;DriverStore…

作者头像 李华
网站建设 2026/2/11 1:01:25

TranslateGemma双显卡部署详解:26GB显存优化配置指南

TranslateGemma双显卡部署详解&#xff1a;26GB显存优化配置指南1. 为什么需要双显卡部署TranslateGemma&#xff1f; 你是否试过在单张RTX 4090上加载TranslateGemma-12B-IT&#xff1f;大概率会遇到这样的报错&#xff1a;CUDA out of memory&#xff0c;或者更隐蔽的device-…

作者头像 李华
网站建设 2026/2/12 9:20:36

基于Starry Night Art Gallery的网络安全应用:威胁检测实战

基于Starry Night Art Gallery的网络安全应用&#xff1a;威胁检测实战 最近和几个做安全运维的朋友聊天&#xff0c;他们都在抱怨同一个问题&#xff1a;每天面对海量的网络流量日志&#xff0c;眼睛都快看花了&#xff0c;但那些真正危险的攻击行为&#xff0c;往往就藏在看…

作者头像 李华