LFM2.5-1.2B-Thinking开发秘籍Vue3前端交互全攻略你是不是已经用Ollama把LFM2.5-1.2B-Thinking模型跑起来了看着命令行里一行行输出的推理轨迹心里琢磨着“这玩意儿要是能有个漂亮的网页界面就好了”或者你正在开发一个需要AI推理能力的应用想给用户一个更直观、更流畅的交互体验今天咱们就来聊聊怎么用Vue3给这个强大的端侧推理模型做个像样的前端界面。我会带你从零开始一步步搭建一个完整的聊天应用不仅能调用模型API还能实时展示它的“思考过程”最后再封装成可复用的组件库让你以后的项目都能直接拿来用。1. 项目准备与环境搭建在开始写代码之前得先把基础环境准备好。咱们这个项目用Vue3因为它现在确实是前端开发的主流选择组合式API用起来特别顺手。1.1 创建Vue3项目打开你的终端用Vite来创建项目这比老旧的Vue CLI快多了npm create vuelatest lfm2-frontend创建过程中它会问你几个问题我是这么选的TypeScript选上现在写前端不用TS总觉得少了点什么JSX不选咱们用模板语法就够了Vue Router选上虽然现在用不到但项目大了肯定需要Pinia选上状态管理必备ESLint选上代码规范很重要Prettier选上格式化工具创建完项目进入目录安装依赖cd lfm2-frontend npm install1.2 安装必要的依赖除了Vue3的基础包咱们还需要几个关键的库npm install axios # HTTP请求库 npm install vueuse/core # Vue组合式工具集特别好用 npm install highlight.js # 代码高亮 npm install marked # Markdown渲染 npm install nprogress # 加载进度条 npm install types/node --save-dev # Node.js类型定义1.3 配置Ollama API代理默认情况下Ollama的API跑在http://localhost:11434但浏览器有跨域限制。咱们需要在Vite里配置个代理// vite.config.ts import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://localhost:11434, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })这样配置后前端访问/api/chat就会被代理到http://localhost:11434/api/chat跨域问题就解决了。2. 核心API调用与流式响应处理LFM2.5-1.2B-Thinking最酷的地方就是它能生成推理轨迹咱们得把这个特性在前端完美展示出来。2.1 封装Ollama API服务先创建一个专门处理API调用的服务文件// src/services/ollama.ts import axios from axios export interface Message { role: user | assistant | system content: string } export interface ChatRequest { model: string messages: Message[] stream?: boolean options?: { temperature?: number top_k?: number } } export interface ChatResponse { model: string created_at: string message: Message done: boolean total_duration?: number load_duration?: number prompt_eval_count?: number eval_count?: number } class OllamaService { private baseURL /api // 使用代理 async chat(request: ChatRequest): PromiseChatResponse { const response await axios.postChatResponse(${this.baseURL}/chat, request) return response.data } async *chatStream(request: ChatRequest): AsyncGeneratorstring, void, unknown { const response await fetch(${this.baseURL}/chat, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({ ...request, stream: true }) }) if (!response.body) { throw new Error(No response body) } const reader response.body.getReader() const decoder new TextDecoder() try { while (true) { 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 line.slice(6) if (data [DONE]) return try { const parsed JSON.parse(data) if (parsed.message?.content) { yield parsed.message.content } } catch (e) { console.error(Failed to parse stream data:, e) } } } } } finally { reader.releaseLock() } } async listModels() { const response await axios.get(${this.baseURL}/tags) return response.data.models || [] } async generateEmbedding(model: string, prompt: string) { const response await axios.post(${this.baseURL}/embeddings, { model, prompt }) return response.data.embedding } } export const ollamaService new OllamaService()这个服务类做了几件事封装了普通的聊天接口实现了流式响应处理能实时获取模型生成的内容提供了模型列表和嵌入向量生成的接口2.2 实现流式响应的组合式函数Vue3的组合式API特别适合封装这种有状态的逻辑// src/composables/useChatStream.ts import { ref, computed } from vue import { ollamaService, type Message } from /services/ollama export function useChatStream() { const messages refMessage[]([]) const isLoading ref(false) const currentResponse ref() const error refstring | null(null) const addMessage (content: string, role: Message[role] user) { messages.value.push({ role, content }) } const sendMessage async (content: string, model lfm2.5-thinking:1.2b) { isLoading.value true error.value null currentResponse.value // 添加用户消息 addMessage(content, user) try { // 添加一个空的助手消息用来填充流式内容 const assistantMessageIndex messages.value.length addMessage(, assistant) // 调用流式接口 const stream ollamaService.chatStream({ model, messages: messages.value.slice(0, -1) // 不包含刚添加的空消息 }) // 实时更新响应内容 for await (const chunk of stream) { currentResponse.value chunk messages.value[assistantMessageIndex].content currentResponse.value } } catch (err) { error.value err instanceof Error ? err.message : 请求失败 // 移除空的助手消息 messages.value.pop() } finally { isLoading.value false } } const clearChat () { messages.value [] currentResponse.value error.value null } return { messages: computed(() messages.value), isLoading: computed(() isLoading.value), currentResponse: computed(() currentResponse.value), error: computed(() error.value), sendMessage, clearChat, addMessage } }这个组合式函数把聊天的状态和逻辑都封装在一起了在任何组件里都能直接用。3. 构建聊天界面组件有了底层的API封装现在可以开始做界面了。咱们先做个基础的聊天界面。3.1 聊天消息组件!-- src/components/ChatMessage.vue -- template div :class[message, message.role] div classavatar div v-ifmessage.role user classuser-avatar span/span /div div v-else classassistant-avatar span/span /div /div div classcontent div classrole-label {{ message.role user ? 你 : LFM2.5 }} /div div classtext v-htmlformattedContent/div !-- 显示推理轨迹 -- div v-ifshowThinking message.role assistant classthinking-trail div classthinking-header span classthinking-icon/span span classthinking-title推理轨迹/span /div div classthinking-content {{ extractThinking(message.content) }} /div /div /div /div /template script setup langts import { computed } from vue import { marked } from marked import hljs from highlight.js import highlight.js/styles/github-dark.css interface Props { message: { role: user | assistant content: string } showThinking?: boolean } const props withDefaults(definePropsProps(), { showThinking: true }) // 配置marked marked.setOptions({ highlight: function(code, lang) { if (lang hljs.getLanguage(lang)) { return hljs.highlight(code, { language: lang }).value } return hljs.highlightAuto(code).value }, breaks: true, gfm: true }) const formattedContent computed(() { const content props.message.content // 如果是助手消息提取最终答案部分 if (props.message.role assistant) { const finalAnswer extractFinalAnswer(content) return marked.parse(finalAnswer) } return marked.parse(content) }) // 提取推理轨迹假设模型输出中包含思考前缀 function extractThinking(content: string): string { const thinkingMatch content.match(/思考([\s\S]*?)答案/) return thinkingMatch ? thinkingMatch[1].trim() : 未显示推理过程 } // 提取最终答案 function extractFinalAnswer(content: string): string { const answerMatch content.match(/答案([\s\S]*)/) return answerMatch ? answerMatch[1].trim() : content } /script style scoped .message { display: flex; margin-bottom: 24px; animation: fadeIn 0.3s ease; } .message.user { flex-direction: row-reverse; } .avatar { flex-shrink: 0; margin: 0 12px; } .user-avatar, .assistant-avatar { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px; } .user-avatar { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .assistant-avatar { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); color: white; } .content { max-width: 70%; padding: 16px; border-radius: 12px; position: relative; } .message.user .content { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-bottom-right-radius: 4px; } .message.assistant .content { background: #f5f5f5; color: #333; border-bottom-left-radius: 4px; } .role-label { font-size: 12px; opacity: 0.7; margin-bottom: 8px; font-weight: 500; } .text { line-height: 1.6; } .text :deep(pre) { background: #1a1a1a; color: #f8f8f2; padding: 16px; border-radius: 8px; overflow-x: auto; margin: 12px 0; } .text :deep(code) { background: #f0f0f0; padding: 2px 6px; border-radius: 4px; font-family: Monaco, Menlo, Ubuntu Mono, monospace; } .text :deep(a) { color: #667eea; text-decoration: none; } .text :deep(a:hover) { text-decoration: underline; } .thinking-trail { margin-top: 16px; padding: 12px; background: rgba(0, 0, 0, 0.05); border-radius: 8px; border-left: 4px solid #f5576c; } .thinking-header { display: flex; align-items: center; margin-bottom: 8px; font-size: 14px; font-weight: 500; color: #666; } .thinking-icon { margin-right: 8px; } .thinking-content { font-size: 13px; line-height: 1.5; color: #666; white-space: pre-wrap; font-family: Monaco, Menlo, Ubuntu Mono, monospace; } keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /style这个组件做了几件重要的事区分用户和助手消息的样式支持Markdown渲染和代码高亮特别处理了LFM2.5的推理轨迹展示3.2 主聊天界面!-- src/components/ChatInterface.vue -- template div classchat-container div classchat-header h1LFM2.5-1.2B-Thinking 聊天界面/h1 div classmodel-info span classmodel-tag端侧推理模型/span span classmemory-info~900MB 内存/span /div /div div classchat-messages refmessagesContainer div v-ifmessages.length 0 classempty-state div classempty-icon/div h3开始与 LFM2.5 对话/h3 p这是一个专为推理任务优化的模型试试问它一些需要思考的问题/p div classexample-prompts button v-forprompt in examplePrompts :keyprompt clicksendExamplePrompt(prompt) classexample-prompt {{ prompt }} /button /div /div ChatMessage v-for(message, index) in messages :keyindex :messagemessage :show-thinkingshowThinkingTrail / div v-ifisLoading classtyping-indicator div classtyping-dots span/span span/span span/span /div span classtyping-textLFM2.5 正在思考.../span /div /div div classchat-input-area div classinput-controls label classtoggle-thinking input typecheckbox v-modelshowThinkingTrail / span显示推理轨迹/span /label button clickclearChat classclear-button :disabledmessages.length 0 清空对话 /button /div div classinput-wrapper textarea v-modelinputText keydown.enter.exact.preventsendMessage placeholder输入你的问题... (按 Enter 发送ShiftEnter 换行) :disabledisLoading rows3 classmessage-input /textarea button clicksendMessage :disabledisLoading || !inputText.trim() classsend-button span v-if!isLoading发送/span span v-else classsending发送中.../span /button /div div classinput-footer span classmodel-selector 模型 select v-modelselectedModel :disabledisLoading option valuelfm2.5-thinking:1.2bLFM2.5-1.2B-Thinking/option option valuelfm2.5-1.2b-instructLFM2.5-1.2B-Instruct/option /select /span div v-iferror classerror-message {{ error }} /div /div /div /div /template script setup langts import { ref, watch, nextTick, onMounted } from vue import ChatMessage from ./ChatMessage.vue import { useChatStream } from /composables/useChatStream const { messages, isLoading, error, sendMessage: sendChatMessage, clearChat: clearChatHistory } useChatStream() const inputText ref() const showThinkingTrail ref(true) const selectedModel ref(lfm2.5-thinking:1.2b) const messagesContainer refHTMLElement() const examplePrompts [ 解释什么是液态神经网络, 帮我解这个方程2x 5 15, 用Python写一个快速排序算法, 比较Transformer和LFM架构的优缺点 ] const sendMessage async () { if (!inputText.value.trim() || isLoading.value) return const text inputText.value.trim() inputText.value await sendChatMessage(text, selectedModel.value) scrollToBottom() } const sendExamplePrompt (prompt: string) { inputText.value prompt sendMessage() } const clearChat () { clearChatHistory() inputText.value } const scrollToBottom () { nextTick(() { if (messagesContainer.value) { messagesContainer.value.scrollTop messagesContainer.value.scrollHeight } }) } // 监听消息变化自动滚动到底部 watch(messages, scrollToBottom, { deep: true }) // 组件挂载时滚动到底部 onMounted(scrollToBottom) /script style scoped .chat-container { display: flex; flex-direction: column; height: 100vh; max-width: 900px; margin: 0 auto; background: white; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); } .chat-header { padding: 20px 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .chat-header h1 { margin: 0; font-size: 24px; font-weight: 600; } .model-info { display: flex; gap: 12px; margin-top: 8px; font-size: 14px; } .model-tag { background: rgba(255, 255, 255, 0.2); padding: 4px 12px; border-radius: 20px; backdrop-filter: blur(10px); } .memory-info { opacity: 0.9; } .chat-messages { flex: 1; overflow-y: auto; padding: 24px; background: #fafafa; } .empty-state { text-align: center; padding: 60px 20px; color: #666; } .empty-icon { font-size: 64px; margin-bottom: 20px; opacity: 0.5; } .empty-state h3 { margin: 0 0 12px 0; color: #333; font-size: 20px; } .empty-state p { margin: 0 0 32px 0; font-size: 15px; max-width: 500px; margin-left: auto; margin-right: auto; } .example-prompts { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; max-width: 600px; margin: 0 auto; } .example-prompt { padding: 12px 20px; background: white; border: 1px solid #e0e0e0; border-radius: 8px; font-size: 14px; color: #555; cursor: pointer; transition: all 0.2s; text-align: left; } .example-prompt:hover { background: #f0f0f0; border-color: #667eea; color: #667eea; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.1); } .typing-indicator { display: flex; align-items: center; gap: 12px; padding: 16px; background: white; border-radius: 12px; margin-top: 16px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); } .typing-dots { display: flex; gap: 4px; } .typing-dots span { width: 8px; height: 8px; background: #667eea; border-radius: 50%; animation: typing 1.4s infinite ease-in-out; } .typing-dots span:nth-child(1) { animation-delay: -0.32s; } .typing-dots span:nth-child(2) { animation-delay: -0.16s; } .typing-text { color: #666; font-size: 14px; } keyframes typing { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } } .chat-input-area { padding: 20px 24px; border-top: 1px solid #e0e0e0; background: white; } .input-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; } .toggle-thinking { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #666; cursor: pointer; } .toggle-thinking input { width: 18px; height: 18px; cursor: pointer; } .clear-button { padding: 8px 16px; background: #f5f5f5; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; color: #666; cursor: pointer; transition: all 0.2s; } .clear-button:hover:not(:disabled) { background: #eee; color: #333; } .clear-button:disabled { opacity: 0.5; cursor: not-allowed; } .input-wrapper { display: flex; gap: 12px; margin-bottom: 12px; } .message-input { flex: 1; padding: 16px; border: 2px solid #e0e0e0; border-radius: 12px; font-size: 15px; line-height: 1.5; resize: none; transition: border-color 0.2s; font-family: inherit; } .message-input:focus { outline: none; border-color: #667eea; } .message-input:disabled { background: #fafafa; cursor: not-allowed; } .send-button { padding: 0 32px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 12px; font-size: 16px; font-weight: 500; cursor: pointer; transition: all 0.2s; align-self: flex-end; height: 56px; } .send-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); } .send-button:disabled { opacity: 0.5; cursor: not-allowed; } .sending { opacity: 0.8; } .input-footer { display: flex; justify-content: space-between; align-items: center; font-size: 14px; color: #666; } .model-selector select { padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px; background: white; font-size: 14px; margin-left: 8px; } .error-message { color: #f5576c; font-size: 13px; padding: 8px 12px; background: rgba(245, 87, 108, 0.1); border-radius: 6px; } /style4. 高级功能模型管理与设置一个完整的前端应用还需要一些管理功能比如切换模型、调整参数等。4.1 模型设置面板!-- src/components/ModelSettings.vue -- template div classsettings-panel h3模型设置/h3 div classsettings-group label classsetting-label span温度 (Temperature)/span input typerange v-modeltemperature min0 max1 step0.1 classslider / span classvalue{{ temperature }}/span /label div classslider-description 控制随机性较低值更确定较高值更有创意 /div /div div classsettings-group label classsetting-label spanTop-K/span input typerange v-modeltopK min1 max100 step1 classslider / span classvalue{{ topK }}/span /label div classslider-description 限制候选词数量较低值更集中较高值更多样 /div /div div classsettings-group label classsetting-label span最大生成长度/span input typenumber v-modelmaxTokens min100 max32768 step100 classnumber-input / span classunittokens/span /label /div div classsettings-group label classsetting-label checkbox input typecheckbox v-modelstreamEnabled / span启用流式响应/span /label label classsetting-label checkbox input typecheckbox v-modelshowProbabilities / span显示生成概率/span /label /div div classsettings-actions button clicksaveSettings classsave-button 保存设置 /button button clickresetSettings classreset-button 恢复默认 /button /div div classmodel-info h4当前模型信息/h4 div classinfo-grid div classinfo-item span classinfo-label参数量/span span classinfo-value1.17B/span /div div classinfo-item span classinfo-label上下文长度/span span classinfo-value32,768 tokens/span /div div classinfo-item span classinfo-label内存占用/span span classinfo-value~900MB/span /div div classinfo-item span classinfo-label支持语言/span span classinfo-value中/英/日/韩等8种/span /div /div /div /div /template script setup langts import { ref, watch } from vue interface Settings { temperature: number topK: number maxTokens: number streamEnabled: boolean showProbabilities: boolean } const emit defineEmits{ update:settings: [settings: Settings] }() const temperature ref(0.7) const topK ref(50) const maxTokens ref(2048) const streamEnabled ref(true) const showProbabilities ref(false) const defaultSettings: Settings { temperature: 0.7, topK: 50, maxTokens: 2048, streamEnabled: true, showProbabilities: false } // 监听设置变化 watch([temperature, topK, maxTokens, streamEnabled, showProbabilities], () { emit(update:settings, { temperature: temperature.value, topK: topK.value, maxTokens: maxTokens.value, streamEnabled: streamEnabled.value, showProbabilities: showProbabilities.value }) }, { deep: true }) const saveSettings () { localStorage.setItem(lfm2-settings, JSON.stringify({ temperature: temperature.value, topK: topK.value, maxTokens: maxTokens.value, streamEnabled: streamEnabled.value, showProbabilities: showProbabilities.value })) // 这里可以添加保存成功的提示 console.log(设置已保存) } const resetSettings () { temperature.value defaultSettings.temperature topK.value defaultSettings.topK maxTokens.value defaultSettings.maxTokens streamEnabled.value defaultSettings.streamEnabled showProbabilities.value defaultSettings.showProbabilities } // 加载保存的设置 const loadSettings () { const saved localStorage.getItem(lfm2-settings) if (saved) { try { const settings JSON.parse(saved) temperature.value settings.temperature || defaultSettings.temperature topK.value settings.topK || defaultSettings.topK maxTokens.value settings.maxTokens || defaultSettings.maxTokens streamEnabled.value settings.streamEnabled ?? defaultSettings.streamEnabled showProbabilities.value settings.showProbabilities ?? defaultSettings.showProbabilities } catch (e) { console.error(加载设置失败:, e) } } } // 组件挂载时加载设置 loadSettings() /script style scoped .settings-panel { padding: 24px; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); } .settings-panel h3 { margin: 0 0 24px 0; color: #333; font-size: 18px; font-weight: 600; } .settings-group { margin-bottom: 24px; } .setting-label { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; font-size: 14px; color: #555; } .setting-label.checkbox { justify-content: flex-start; gap: 8px; margin-bottom: 12px; } .setting-label.checkbox input { width: 18px; height: 18px; } .slider { flex: 1; margin: 0 16px; height: 6px; border-radius: 3px; background: #e0e0e0; outline: none; -webkit-appearance: none; } .slider::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #667eea; cursor: pointer; border: 2px solid white; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } .slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: #667eea; cursor: pointer; border: 2px solid white; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2); } .value { min-width: 40px; text-align: right; font-weight: 500; color: #667eea; } .slider-description { font-size: 12px; color: #888; margin-top: 4px; line-height: 1.4; } .number-input { width: 100px; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; text-align: center; } .unit { margin-left: 8px; color: #666; font-size: 14px; } .settings-actions { display: flex; gap: 12px; margin-top: 32px; } .save-button, .reset-button { flex: 1; padding: 12px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s; } .save-button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; } .save-button:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); } .reset-button { background: white; border: 1px solid #ddd; color: #666; } .reset-button:hover { background: #f5f5f5; border-color: #ccc; } .model-info { margin-top: 32px; padding-top: 24px; border-top: 1px solid #eee; } .model-info h4 { margin: 0 0 16px 0; color: #333; font-size: 16px; font-weight: 500; } .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; } .info-item { display: flex; flex-direction: column; gap: 4px; } .info-label { font-size: 12px; color: #888; } .info-value { font-size: 14px; color: #333; font-weight: 500; } /style5. 可复用组件库封装最后咱们把这些组件封装成一个可复用的库方便在其他项目中使用。5.1 创建组件库入口// src/components/index.ts export { default as ChatInterface } from ./ChatInterface.vue export { default as ChatMessage } from ./ChatMessage.vue export { default as ModelSettings } from ./ModelSettings.vue export { useChatStream } from /composables/useChatStream export { ollamaService } from /services/ollama export type { Message, ChatRequest, ChatResponse } from /services/ollama5.2 创建Vue插件// src/plugins/lfm2-chat.ts import type { App } from vue import ChatInterface from /components/ChatInterface.vue import ChatMessage from /components/ChatMessage.vue import ModelSettings from /components/ModelSettings.vue import { useChatStream } from /composables/useChatStream import { ollamaService } from /services/ollama export interface LFM2ChatOptions { apiBaseURL?: string defaultModel?: string } const LFM2ChatPlugin { install(app: App, options: LFM2ChatOptions {}) { // 全局注册组件 app.component(LFM2ChatInterface, ChatInterface) app.component(LFM2ChatMessage, ChatMessage) app.component(LFM2ModelSettings, ModelSettings) // 提供全局配置 app.provide(lfm2-config, { apiBaseURL: options.apiBaseURL || /api, defaultModel: options.defaultModel || lfm2.5-thinking:1.2b }) // 挂载工具到全局属性 app.config.globalProperties.$lfm2 { useChatStream, ollamaService } } } export default LFM2ChatPlugin5.3 使用示例!-- 在其他项目中使用 -- template div classapp div classsidebar LFM2ModelSettings update:settingshandleSettingsUpdate / /div div classmain LFM2ChatInterface / /div /div /template script setup langts import { LFM2ChatInterface, LFM2ModelSettings } from lfm2-chat-vue const handleSettingsUpdate (settings: any) { console.log(设置已更新:, settings) // 这里可以保存设置或应用到聊天中 } /script style scoped .app { display: flex; height: 100vh; } .sidebar { width: 300px; border-right: 1px solid #e0e0e0; overflow-y: auto; } .main { flex: 1; } /style6. 总结走完这一趟你应该已经掌握了用Vue3构建LFM2.5-1.2B-Thinking前端界面的全套技能。从最基础的API调用到复杂的流式响应处理再到完整的组件封装每一步我都尽量用最直白的方式讲清楚。实际用下来这套方案在响应速度和用户体验上表现都不错。特别是流式响应的实现让用户能实时看到模型的“思考过程”这比等半天才出完整结果体验好太多了。组件库的封装也考虑到了复用性你完全可以根据自己的需求调整样式或添加新功能。如果你刚开始接触Vue3可能会觉得有些地方有点绕特别是组合式API和TypeScript的类型定义。但多写几次就习惯了这种写法其实更灵活逻辑复用也更方便。遇到问题别怕多看看Vue3的官方文档里面的例子都很实用。最后提醒一下虽然LFM2.5-1.2B-Thinking在端侧跑得很流畅但实际部署时还是要考虑网络环境和硬件限制。建议先在小范围测试没问题了再逐步扩大使用。后面如果还想优化可以考虑加个消息持久化或者做个更精细的错误处理这些就留给你自己探索了。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。