
很多开发者都有这样一个直觉网页版聊天工具已经很成熟了为什么还要自己动手做一个桌面版 AI 聊天应用真实的答案是场景。当你需要让聊天窗口常驻桌面、希望对话记录保存在本地、想在系统托盘里快速唤起、或者想把 AI 能力嵌入到自己正在开发的工具中时网页版的边界就会立刻显现出来。而反过来的问题也很现实从零开始写一个跨平台桌面应用对绝大多数前端开发者来说门槛并不低。但这篇文章要讲的是一条已经被很多人验证过的路线用 Cursor 这类 AI 编程助手结合 Vue3.5 和 Electron再接入一个大模型 API从零到一开发属于自己的跨平台 AI 桌面聊天应用。我会把选择这条技术路线的理由、每一步的关键代码、以及最容易踩的坑一次性讲完。读完你不仅能跑通一个最小可用的桌面聊天程序还能搞明白 Electron 主进程与渲染进程的关系、大模型接口的接入方式、以及打包分发时需要注意的工程细节。先给出一个明确判断这条路线真正降低的不是写代码的难度而是“桌面应用工程化”的门槛。前端开发者最熟悉的是 Vue 组件和页面交互最陌生的是窗口管理、进程通信、打包配置这一套桌面应用概念。Cursor 负责补齐代码生成的效率Electron 负责把前端能力装进桌面壳子大模型 API 负责提供 AI 对话能力Vue3.5 负责把界面体验做好。四个技术各司其职组合起来就是一个低成本、可落地、可扩展的跨平台 AI 应用方案。1. 这篇文章真正要解决的问题如果你准备开发一个桌面版 AI 聊天工具通常会遇到下面几个问题我们逐个拆开看。第一技术选型不确定。打开搜索引擎能看到 Electron、Tauri、Flutter Desktop、Qt 等各种方案。每种方案都有自己的语言生态和打包方式对前端开发者来说Electron 的学习曲线最友好因为它直接复用你已经具备的 HTML、CSS、JavaScript 技能。Vue3.5 负责界面层Electron 负责桌面壳子两者用进程通信机制连接整体心智负担远比学一套 Rust 或 C 小得多。第二AI 能力接入方式不清楚。大模型不是只有一个“API 调用”这么简单它涉及接口地址、鉴权方式、模型名称、消息历史格式、流式响应解析、错误处理等细节。很多新手把 API Key 写死在页面代码里一旦打包发布密钥就跟着应用包泄露了这是非常危险的做法。这篇文章会演示如何把大模型调用放在 Electron 主进程中让前端只通过 preload 暴露的接口发起请求从架构层面规避密钥泄露问题。第三从项目骨架到完整应用之间有一条“工程鸿沟”。很多人用脚手架创建了一个 Vue 项目也安装了 Electron但不知道这两个东西怎么跑起来更不知道开发环境和生产环境分别应该怎么加载页面。Vite Dev Server 和 Electron 窗口如何联动、打包后静态资源为何加载失败、IPC 通信为什么在渲染进程里不可用这些都是实际项目中百分之百会遇到的问题。第四跨平台分发没有经验。开发时一切正常打包后到了别的电脑上白屏、闪退、缺少依赖这是桌面应用开发的经典问题。尤其是国内开发者还要面对国产操作系统的适配诉求如果不在一开始就把构建和分发思路想清楚后面返工的代价会很高。这篇文章的目标就是把这四个问题一次讲透。你会得到一个完整的项目结构、可复制的代码、运行验证方式、常见故障排查清单以及生产环境的最佳实践。2. 基础概念与核心原理在动手写代码之前有必要把几个核心概念先讲清楚。这些概念决定了你后面的调试思路提前理解能省下大量排查时间。2.1 Electron 的进程模型Electron 应用由两类进程构成主进程和渲染进程。主进程负责创建窗口、管理系统事件、访问操作系统能力它运行在 Node.js 环境中可以使用 Node 的所有 API。渲染进程负责页面展示和交互本质上是 Chromium 浏览器环境默认情况下不能直接使用 Node API。两者之间的通信依赖 IPCInter-Process Communication。渲染进程通过 preload 脚本中暴露的接口发送消息主进程通过监听对应的事件名来处理消息并返回结果。理解这个模型非常重要因为它决定了你的代码应该写在哪个文件里。举个实际例子大模型 API Key 不应该暴露给渲染进程因为渲染进程加载的页面如果被注入了恶意脚本密钥就会被窃取。正确的做法是把密钥保存在主进程的环境变量中渲染进程通过 IPC 请求主进程代为调用大模型接口。2.2 Vue3.5 在其中的角色Vue3.5 是本文界面层的核心技术。它负责的功能包括消息列表的渲染、输入框的双向绑定、发送按钮的交互、滚动到底部的逻辑、以及聊天数据的响应式维护。用 Vue 做桌面应用的界面比做网页多了一层特殊关系Vue 应用运行在 Electron 的渲染进程中它不需要关心自己是在浏览器还是桌面窗口里只需要通过 window 对象上暴露的桥接接口来调用主进程能力。这种“界面与逻辑分离”的设计让前端开发和桌面能力开发可以并行进行。2.3 Cursor 在开发流程中的位置Cursor 是一个 AI 编程助手它可以理解项目上下文、生成代码片段、解释报错信息、辅助完成重构。在本文的实践流程中Cursor 的角色是“结对编程搭档”而不是代码的最终审查者。用 Cursor 生成 Electron 的主进程模板、Vue 组件代码、甚至帮你排查一段编译报错都能明显提高效率。但你要清楚它的边界AI 生成的代码不一定符合你的项目约束尤其是涉及安全配置、环境变量、权限边界时必须人工 review。这里强烈建议不要使用任何第三方“汉化包”或来路不明的修改脚本Cursor 的界面语言设置请直接查阅官方文档因为你无法确认第三方改动是否会影响配置文件或引入额外风险。2.4 大模型 API 的统一抽象市面上大模型服务很多接口风格略有差异但目前主流服务基本都兼容 OpenAI 格式的接口。这意味着你可以用同一套请求结构只是切换 baseUrl、apiKey、model 三个参数就能在不同服务之间切换。一个标准的 non-stream 请求是这样组织的向接口地址发送一个 POST 请求Headers 里带上鉴权信息Body 里指定模型名称和消息列表。消息列表是数组结构每一条包含 roleuser 或 assistant和 content 两个字段。服务端返回的内容在 choices 数组里取 choices[0].message.content 就是模型生成的文本。如果你希望做流式输出也就是模型边生成边显示文字需要把请求参数中的 stream 设置为 true然后用流式解析方式逐段读取响应。本文先以非流式为例讲清架构流式优化放在最佳实践中说明。3. 环境准备与前置条件在开始写代码之前先把开发环境准备好。这里列出的工具和版本要求尽量以当前最新稳定版为准不要刻意追求老版本因为 Electron 和 Vue 的生态更新较快老版本可能会遇到兼容性问题。3.1 基础环境清单类别推荐工具说明操作系统Windows 10/11、macOS、主流 Linux 发行版本文代码为跨平台设计各系统命令略有差异Node.js18 或更高版本Electron 构建和高版本 Vite 都依赖较新的 Node 环境包管理工具npm 或 pnpm本文示例使用 npmpnpm 在依赖安装严格模式下可能有坑代码编辑器Cursor 或 VS CodeCursor 提供 AI 辅助普通 VS Code 安装 AI 插件也可大模型服务任意 OpenAI 兼容接口或本地 Ollama需要准备 baseUrl、apiKey、model 三个信息3.2 大模型服务的两种准备方案方案一是使用在线大模型 API。你需要去服务商平台注册账号创建一个 API Key并确认你选择的模型名称和接口地址。API Key 属于敏感信息后续要存储在环境变量中不能提交到 Git 仓库也不能写进前端代码。方案二是使用 Ollama 在本地部署模型。这种方式的好处是数据不出本机、离线可用、不需要为每一次调用付费缺点是对电脑配置有要求模型越大需要的内存和显存越多。Ollama 安装完成后拉取一个小模型跑通流程是最稳妥的起步方式。3.3 创建项目目录打开终端创建一个新的工作目录并初始化 npm 项目。mkdir ai-chat-desktop cd ai-chat-desktop npm init -y后续所有代码和配置都放在这个目录下面。建议先不要着急安装 Electron等整个目录结构规划清楚后一次性安装避免依赖关系混乱。4. 创建 Vue3.5 工程并接入 Electron这一步是整个项目的骨架搭建。核心思路是Vite 负责前端开发和构建Electron 负责桌面壳子两者通过约定好的目录结构协作。4.1 使用 Vite 创建 Vue 项目在项目目录下使用 Vite 官方脚手架创建 Vue 应用。选择 Vue 模板后脚手架会自动生成最新版本的 Vue 3 项目。npm create vitelatest . -- --template vue npm install执行完成后目录下会出现 src 目录、index.html、vite.config.js 等文件。Vite 默认会在 5173 端口启动开发服务器这个地址在开发 Electron 时要用到。4.2 安装 Electron 和构建工具安装 Electron 本身以及开发时需要用到的一些辅助依赖。Electron 体积较大国内网络环境下下载可能较慢请耐心等待或配置镜像源。npm install --save-dev electron electron-builder concurrently wait-on cross-env这里说明一下这几个包的用途electron桌面应用运行时框架electron-builder打包和分发工具生成安装包concurrently同时运行多个 npm 脚本wait-on等待某个端口可用后再执行下一个命令cross-env跨平台设置环境变量解决 Windows 和 macOS/Linux 命令差异4.3 创建 Electron 主进程文件在项目根目录创建 electron 目录并在其中创建 main.js。这个文件是 Electron 应用的入口负责创建桌面窗口。// electron/main.js const { app, BrowserWindow } require(electron); const path require(path); function createWindow() { const win new BrowserWindow({ width: 1080, height: 760, minWidth: 800, minHeight: 600, title: AI Desktop Chat, webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true, nodeIntegration: false, sandbox: false } }); const devServerUrl process.env.VITE_DEV_SERVER_URL; if (devServerUrl) { win.loadURL(devServerUrl); } else { win.loadFile(path.join(__dirname, ../dist/index.html)); } return win; } app.whenReady().then(() { createWindow(); app.on(activate, () { if (BrowserWindow.getAllWindows().length 0) { createWindow(); } }); }); app.on(window-all-closed, () { if (process.platform ! darwin) { app.quit(); } });这段代码的关键逻辑有三处。第一webPreferences 中开启了 contextIsolation 并关闭了 nodeIntegration这是 Electron 官方推荐的安全配置能有效防止渲染进程直接访问 Node 系统能力。第二通过环境变量 VITE_DEV_SERVER_URL 区分开发和生产环境。开发时加载 Vite 提供的地址方便热更新生产时加载构建后的静态文件。第三macOS 下点击 Dock 图标时如果没有窗口则重新创建这符合 mac 应用的常规交互习惯。4.4 创建 preload 桥接脚本preload 脚本是主进程和渲染进程之间的桥梁。它通过 contextBridge 把需要暴露给页面的接口挂载到 window 对象上同时确保页面只能访问被明确暴露的 API。// electron/preload.js const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(electronAPI, { sendChat: (payload) ipcRenderer.invoke(chat:send, payload) });这里只暴露了一个 sendChat 方法渲染进程调用 window.electronAPI.sendChat 时实际上是向主进程发送了一个名为 chat:send 的 IPC 请求并等待结果返回。后续添加新功能时只需要在这里增加对应的方法核心逻辑保留在主进程中。4.5 配置 package.json修改 package.json 中的 main 字段和 scripts 脚本让 Electron 能够找到主进程文件并且通过脚本一键启动开发环境。{ name: ai-chat-desktop, version: 1.0.0, description: 基于 Electron 和 Vue3.5 的跨平台 AI 桌面聊天应用, main: electron/main.js, scripts: { dev:web: vite, dev:electron: wait-on tcp:127.0.0.1:5173 cross-env VITE_DEV_SERVER_URLhttp://127.0.0.1:5173 electron ., dev: concurrently \npm run dev:web\ \npm run dev:electron\, build: vite build, dist: npm run build electron-builder } }启动流程是npm run dev 同时启动 Vite 和 Electronwait-on 会监听 5173 端口直到 Vite 服务可用后才启动 Electron 窗口。这样避免了 Electron 先启动时可能加载空白页的问题。5. 用 Vue3.5 实现聊天界面工程骨架搭建完成后下一步是写聊天界面。这个界面包含消息列表、输入框、发送按钮三部分是聊天应用的最小交互闭环。5.1 在 App.vue 中引入聊天组件为了让项目结构更清晰建议把聊天界面单独拆成组件。这里直接在 App.vue 中引入一个 ChatWindow 组件后续扩展设置页面或历史记录页面时可以继续按组件方式组织。!-- src/App.vue -- script setup import ChatWindow from ./components/ChatWindow.vue; /script template ChatWindow / /template5.2 实现 ChatWindow 组件聊天组件的核心逻辑包含三个方面消息列表的响应式数据、发送消息的处理函数、发送后自动滚动到底部。!-- src/components/ChatWindow.vue -- script setup import { ref, nextTick } from vue; const messages ref([ { role: assistant, content: 你好我是你的 AI 桌面助手有什么可以帮你 } ]); const inputText ref(); const loading ref(false); async function sendMessage() { const text inputText.value.trim(); if (!text || loading.value) return; messages.value.push({ role: user, content: text }); inputText.value ; loading.value true; try { const history messages.value.map(({ role, content }) ({ role, content })); const reply await window.electronAPI.sendChat({ history }); messages.value.push({ role: assistant, content: reply }); } catch (error) { messages.value.push({ role: assistant, content: 请求失败 error.message }); } finally { loading.value false; scrollToBottom(); } } function scrollToBottom() { nextTick(() { const list document.getElementById(message-list); if (list) { list.scrollTop list.scrollHeight; } }); } /script template div classchat-container div idmessage-list classmessage-list div v-for(msg, index) in messages :keyindex :class[message-item, msg.role] div classmessage-content{{ msg.content }}/div /div div v-ifloading classmessage-item assistant div classmessage-content正在思考.../div /div /div div classinput-area input v-modelinputText typetext placeholder输入你的问题回车发送 keyup.entersendMessage / button :disabledloading clicksendMessage {{ loading ? 回复中 : 发送 }} /button /div /div /template style scoped .chat-container { display: flex; flex-direction: column; height: 100vh; background: #f5f6f8; } .message-list { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 12px; } .message-item { max-width: 70%; padding: 10px 14px; border-radius: 10px; line-height: 1.6; word-break: break-word; } .message-item.user { align-self: flex-end; background: #1677ff; color: #fff; border-bottom-right-radius: 2px; } .message-item.assistant { align-self: flex-start; background: #fff; color: #333; border-bottom-left-radius: 2px; box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); } .input-area { display: flex; gap: 10px; padding: 14px 16px; background: #fff; border-top: 1px solid #e8e8e8; } .input-area input { flex: 1; height: 40px; border: 1px solid #d9d9d9; border-radius: 8px; padding: 0 14px; font-size: 14px; outline: none; } .input-area input:focus { border-color: #1677ff; } .input-area button { height: 40px; padding: 0 24px; background: #1677ff; color: #fff; border: none; border-radius: 8px; font-size: 14px; cursor: pointer; } .input-area button:disabled { opacity: 0.6; cursor: not-allowed; } /style这里真正容易踩坑的地方是 messages 数组的 role 字段。大模型要求角色只能是 user、assistant、system 三种之一如果你把 loading 状态或者错误提示也塞进 messages再直接作为历史记录发送给大模型可能会导致接口报错。所以 sendMessage 中构造 history 时先对 messages 做了 role 和 content 的过滤映射这是一个很细节但很重要的处理。5.3 为什么要把大模型调用放在主进程刚才的组件代码中前端发起的调用是 window.electronAPI.sendChat真正的大模型请求不在这里而是在 Electron 主进程中。这么设计有三个原因。第一是安全。API Key 如果放在渲染进程的代码里打包后的 JS 文件可以被轻易解包读取。放到主进程后前端只知道“调用一个方法”不知道密钥是什么。第二是网络环境。主进程运行在 Node 环境中不受渲染进程同源策略限制可以在网络请求上做更灵活的处理。第三是扩展性。后续如果增加语音输入、文件上传、截图识别等能力这些都需要主进程访问本地资源提前把调用入口集中到主进程架构上更合理。6. 大模型接入与 IPC 通信完整实现前面已经把界面和壳子搭好了接下来是核心环节让聊天窗口真正说话也就是接通大模型。6.1 在主进程中封装大模型请求在 electron 目录下新建 llm.js专门负责大模型接口的调用。这里以 OpenAI 兼容接口为例如果你使用的是本地 Ollama 或国内服务商只需要修改 baseUrl、apiKey、model 三个参数。// electron/llm.js const { ipcMain } require(electron); const APP_CONFIG { baseUrl: process.env.LLM_BASE_URL || http://127.0.0.1:11434, apiKey: process.env.LLM_API_KEY || , model: process.env.LLM_MODEL || qwen2.5:7b }; async function requestChatCompletion(history) { const response await fetch(${APP_CONFIG.baseUrl}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${APP_CONFIG.apiKey} }, body: JSON.stringify({ model: APP_CONFIG.model, messages: history, temperature: 0.7 }) }); if (!response.ok) { const errorText await response.text(); throw new Error(LLM API error ${response.status}: ${errorText}); } const data await response.json(); return data.choices[0].message.content; } function registerLLMHandlers() { ipcMain.handle(chat:send, async (_event, payload) { try { return await requestChatCompletion(payload.history); } catch (error) { return 调用大模型失败${error.message}; } }); } module.exports { registerLLMHandlers };这段代码有几点需要注意。baseUrl 指向的是大模型服务的基础地址如果使用 Ollama 本地部署默认端口是 11434服务路径是 /v1/chat/completions。如果使用在线服务商把地址换成服务商提供的基础地址即可。model 参数决定了请求哪个模型不同模型的能力、速度、价格差异很大起步阶段建议先用中等参数量的模型跑通流程。6.2 环境变量配置API Key 不写死在代码里而是通过环境变量注入。在项目根目录创建 .env 文件并把它加入 .gitignore。# .env LLM_BASE_URLhttp://127.0.0.1:11434 LLM_API_KEYsk-your-key-here LLM_MODELqwen2.5:7b注意.env 文件中的变量是给主进程读取的。如果使用在线大模型服务把 LLM_BASE_URL 换成服务商地址LLM_API_KEY 换成你申请的密钥LLM_MODEL 换成你在服务商开通的模型名称。不要把这些信息提交到代码仓库。6.3 修改主进程入口在 main.js 中引入 llm.js并在应用初始化时注册 IPC 处理器。// electron/main.js const { app, BrowserWindow } require(electron); const path require(path); const { registerLLMHandlers } require(./llm); function createWindow() { // 窗口创建代码同上 } app.whenReady().then(() { registerLLMHandlers(); createWindow(); app.on(activate, () { if (BrowserWindow.getAllWindows().length 0) { createWindow(); } }); }); app.on(window-all-closed, () { if (process.platform ! darwin) { app.quit(); } });registerLLMHandlers 的作用是把 chat:send 这个 IPC 事件与具体的处理函数绑定。这样渲染进程通过 preload 暴露的 sendChat 方法发送消息时主进程就能收到并代为请求大模型。6.4 完整请求流程梳理当你在界面中输入一个问题并按下回车完整的数据流是这样的Vue 组件的 sendMessage 函数把消息列表转换为 history 数组。调用 window.electronAPI.sendChat({ history })。preload 脚本中 contextBridge 暴露的方法把请求转发给主进程。主进程中 ipcMain.handle 注册的处理器收到请求。处理器调用 requestChatCompletion 向大模型服务发送 HTTP 请求。大模型返回结果后主进程通过 IPC 把内容返回给渲染进程。Vue 组件把返回内容推入 messages 数组界面自动更新。理解这条链路后排查问题时你就能快速定位是哪个环节出了故障。界面不响应先看渲染进程IPC 没有反应看 preload 和主进程的处理器返回报错看大模型服务返回的状态码和错误信息。7. 运行验证与效果测试代码写完后先不要着急打包按照下面的步骤在本地把应用跑起来验证每一个环节是否正常。7.1 启动开发环境npm run dev这个命令会同时启动 Vite 开发服务器和 Electron 窗口。正常情况下一个桌面窗口会自动弹出窗口中显示之前写的聊天界面。如果窗口白屏优先级最高的排查方式是查看终端输出。Vite 是否有报错、Electron 是否有加载失败的信息终端里会直接呈现。常见原因是 5173 端口被占用或 wait-on 检测失败这时候关掉占用端口的程序重新执行 npm run dev 即可。7.2 验证聊天功能在输入框中输入“你好请介绍一下你自己”按回车。预期行为是用户消息显示在右侧。按钮变为“回复中”状态。稍等片刻大模型返回的内容显示在左侧。消息列表自动滚动到底部。如果 Ollama 本地部署第一次请求时模型可能需要加载响应时间会长一些这是正常现象。如果使用在线 API首次请求时间一般在几秒内。7.3 验证错误处理为了确认错误处理逻辑有效可以故意把 .env 中的模型名称改成一个不存在的名字重启应用后再发送消息。此时界面应该显示“调用大模型失败”的提示而不是整个应用崩溃。这说明异常处理链路是通的后续接入更复杂的业务逻辑时你不用担心单个接口失败导致应用白屏。8. 常见问题与排查思路开发过程中容易遇到的问题很多这里整理一份高频故障对照表遇到问题时可以按表格查。问题现象可能原因排查方式解决方案Electron 窗口白屏Vite Dev Server 未启动或端口错误查看终端输出确认 5173 端口是否监听重新执行 npm run dev确认 wait-on 等待端口正确页面中 window.electronAPI 为 undefinedpreload 脚本路径错误或 contextBridge 未生效在渲染进程 console 中打印 window 对象检查 main.js 中 preload 路径确认 contextIsolation 已开启大模型返回 401/403API Key 错误或权限不足用 curl 或 Postman 直接测试接口检查 .env 中的 key确认服务商平台已开通对应模型大模型返回 404baseUrl 或模型名错误查看服务商文档确认接口路径修改 baseUrl确认模型名与平台记录一致中文显示乱码页面编码或字体问题检查 index.html 的 charset 设置确保页面声明 UTF-8 编码字体使用系统中文字体打包后白屏开发环境与生产环境加载路径不同导致打包前先执行 vite build确认 dist 目录存在检查 main.js 中 loadFile 路径使用 path.join 拼绝对路径发送消息后长时间无响应本地模型加载慢或 API 超时查看主进程终端日志检查模型是否在加载首次请求耐心等待或改用原子化模型减少加载时间关于“Electron 能不能把 URL 打包进去”这个高频问题做一个明确回答可以而且有两种方案。方案一是把前端代码构建成静态文件用 loadFile 加载本地 dist 目录这种方式适合需要离线使用、对加载速度有要求的场景是本文推荐的方案。方案二是直接用 loadURL 加载远程地址这种方式适合内容需要频繁更新的场景但要注意远程页面可能引入不受你控制的脚本安全风险更高同时还要应对 CSP 和跨域问题。多数桌面聊天应用建议采用方案一把能力打包进本地把内容通过接口远程获取。9. 最佳实践与生产环境建议代码跑通只是第一步真要作为产品发布还需要关注以下工程层面的问题。9.1 安全配置要守住底线Electron 应用最常见的漏洞是启用了 nodeIntegration导致渲染进程可以直接执行 Node 代码。一旦页面存在 XSS 漏洞攻击者就能直接操作你的文件系统。守住三条底线即可避免大部分问题contextIsolation 保持开启nodeIntegration 保持关闭preload 中只暴露你明确需要的方法。大模型的 API Key 永远不要出现在前端代码中这是发布前必须自查的红线。9.2 环境变量与配置分层开发环境和生产环境使用不同的大模型配置是很常见的需求。建议不要改代码而是通过环境变量区分。开发时读取本地 .env 文件生产打包时在打包服务器或系统环境变量中配置。这样同一个代码包在不同环境里就能使用不同的模型和密钥。9.3 流式响应是体验分水岭非流式请求在内容较长时会出现数秒的空白等待用户体感很差。真正的聊天应用都应该使用流式响应让文字像打字机一样逐字显示。实现思路是请求参数加 stream: true主进程通过流式解析读取数据块然后通过 IPC 把中间结果逐步推送给渲染进程。这个优化建议在跑通最小应用后第一时间完成它带来的体验提升非常明显。使用 IPC 推送流式数据时要注意ipcRenderer.invoke 是“请求-响应”模式不适合推送多次结果。更合适的方案是主进程用 webContents.send 向渲染进程发事件渲染进程在 preload 中注册 on 监听来接收连续的中间结果。这样架构调整需要提前设计建议在接入流式能力时顺带重构。9.4 打包与分发策略打包使用 electron-builder在 package.json 中补充 build 配置即可。不同平台的安装包格式不同Windows 使用 NSIS 生成 exemacOS 使用 dmgLinux 生成 AppImage 或 deb。国内开发者如果需要在国产 Linux 发行版上分发要注意 Electron 版本对系统的 glibc 等基础库有要求建议在目标系统上准备虚拟机做真机验证而不是只依赖打包机。同时也推荐提供免安装的绿色版本作为备选方案降低分发门槛。9.5 关于 Cursor 的使用建议用 Cursor 辅助开发这个项目时最有效的方式是让它做三件事生成基础模板代码、解释报错信息、实现重复性较高的组件。但在涉及安全配置、密钥管理、进程通信的部分必须人工 review 生成的代码。这些边界一旦出错影响的是整个应用的安全基线AI 助手不应该成为最终的安全审查者。10. 总结与后续学习方向这篇文章讲清楚了从零到一实现跨平台 AI 桌面聊天应用的完整路径用 Vite 和 Vue3.5 搭建界面层用 Electron 提供跨平台桌面壳子用 preload 和 IPC 打通两层通信用大模型 API 提供 AI 对话能力最后用环境变量和主进程封装守住安全边界。建议你按这个顺序继续实践先保持非流式版本把项目跑通并理解 IPC 数据流然后将接口切换到流式响应体验完整的打字机效果再往后可以接入本地 Ollama 模型摆脱对在线服务的依赖最后尝试用 electron-builder 打包并在另一台干净电脑上安装验证。这个项目的价值在于它把前端、桌面、AI 三条技术线拧在了一起。你学到的 IPC 通信、安全配置、跨平台打包、大模型接口对接都能复用到其他桌面工具开发中。真正容易出问题的不是某一个功能而是工程化细节。只要把 API 地址、模型名、环境变量、安全边界这些基础配置管理好后续迭代理所当然会顺畅很多。