首页 / 资讯中心 / 文章详情

从网页到桌面:Electron 打造 DeepSeek 客户端实战

从网页到桌面:Electron 打造 DeepSeek 客户端实战 ★ FEATURED ARTICLE
1. 从浏览器标签页到桌面图标我为什么彻底换掉了 WebUI用了大半年网页版对话工具之后我发现自己每天在做一件很蠢的事打开浏览器、在一堆标签页里翻找那个正确的窗口、重新登录、等待页面加载、然后才想起来自己要问什么。更别提有时候浏览器插件冲突导致输入框卡死或者不小心刷新页面把一整段没保存的对话弄丢了。这种体验在偶尔用用的时候还能忍但当我每天要处理几十次问答、需要频繁切换不同对话上下文的时候网页端的短板就彻底暴露了。后来我换成了 DeepSeek 的桌面版客户端用了一周左右直接把浏览器里的书签删了。这不是说网页版不好而是桌面版在几个关键维度上的体验提升太明显了启动速度、会话管理、系统级快捷键调用、离线缓存、以及和本地文件的交互能力。这些东西加在一起构成了一个完全不同的使用感受。这篇文章适合两类人看一类是还在用网页版、犹豫要不要换桌面端的普通用户我会把实际体验差异讲清楚另一类是对 Electron 技术栈感兴趣、想自己动手做一个类似桌面客户端的开发者我会把架构思路、关键实现细节、踩过的坑都拆开来讲。不管你是哪种看完应该都能拿到可以直接用的东西。2. 桌面版到底解决了什么问题核心需求拆解2.1 网页端的三个硬伤先说清楚网页版的根本问题在哪里这样才能理解桌面版的设计逻辑。第一个硬伤是状态易失。浏览器本质上是一个无状态的渲染容器你刷新一下、误触一下、系统休眠一下当前对话的上下文就可能丢失。虽然很多网页端做了本地存储但存储策略参差不齐有些只存最近几条有些在隐私模式下直接不工作。我遇到过好几次写了一大段提示词结果手滑按了 CtrlW整个页面关掉再打开什么都没有了。第二个硬伤是调用链路太长。从想用到真正开始输入中间要经过找到浏览器图标、打开、找到标签页或输入网址、等待加载、确认登录状态、点击新建对话。这一套下来少说十几秒多的时候半分钟。对于高频使用者来说这个摩擦成本累积起来非常可观。第三个硬伤是系统集成能力弱。网页端拿不到系统级快捷键、没法在文件管理器里右键调用、不能方便地读取本地文件内容、也没法在系统托盘常驻。这些能力在桌面端都是原生支持的而网页端受限于浏览器沙箱基本做不到。2.2 桌面版的核心能力清单换成桌面版之后我实际用到的核心能力大概有这些全局快捷键唤起设置一个组合键在任何界面下按一下就能弹出输入框问完即走不需要切换窗口。这个功能看起来简单但实际使用频率极高。多会话标签管理可以同时开多个对话每个对话独立维护上下文左侧栏切换类似 IDE 的多文件编辑体验。本地文件拖拽读取直接把本地的代码文件、文档、图片拖进输入框客户端会读取内容并作为上下文发送不需要手动复制粘贴。对话历史本地持久化所有对话记录存在本地数据库里支持全文搜索不依赖服务端的历史记录功能。系统托盘常驻关掉主窗口后程序缩到托盘随时可以唤出不用重新启动。Markdown 渲染与代码高亮对话中的代码块、表格、数学公式都能正确渲染阅读体验比网页端更稳定。这些能力加在一起本质上把对话工具从一个网页应用变成了一个系统级工具。这个定位转变才是桌面版真正的价值所在。2.3 为什么选 Electron 而不是其他方案说到桌面客户端的技术选型市面上主流方案大概有这么几种Electron、Tauri、Qt、原生开发Swift/C#、以及 Flutter Desktop。DeepSeek 桌面版选择的是 Electron这个选择背后有很实际的考量。Electron 的核心优势是复用 Web 技术栈。如果服务端已经有一套成熟的 Web 前端用 Electron 可以几乎零成本地把这套界面搬到桌面端只需要额外处理系统集成部分。对于一个以对话交互为核心的产品来说界面复杂度不高Electron 的性能开销完全可以接受。对比一下其他方案Tauri 用 Rust 做后端、系统 WebView 做前端打包体积小很多通常几 MB vs Electron 的几十 MB但生态成熟度和调试便利性不如 ElectronQt 性能好但开发效率低界面风格和 Web 差异大原生开发体验最好但需要维护多套代码。对于一个需要快速迭代、跨平台覆盖的产品Electron 是性价比最高的选择。注意Electron 的打包体积和内存占用确实比原生方案高这是它的固有代价。如果你的应用对资源占用极度敏感或者只需要覆盖单一平台可以考虑 Tauri 或原生方案。但对于对话类工具这种交互为主、计算为辅的场景Electron 完全够用。3. Electron 桌面客户端的架构拆解3.1 主进程与渲染进程的职责划分Electron 的架构核心是主进程Main Process和渲染进程Renderer Process的分离。理解这个分离是理解整个客户端工作原理的关键。主进程是 Node.js 环境负责所有系统级操作创建窗口、管理托盘、注册全局快捷键、读写本地文件、访问数据库、处理进程间通信。整个应用只有一个主进程它是所有系统资源的入口。渲染进程是 Chromium 环境负责界面渲染和用户交互。每个窗口对应一个渲染进程它运行的是标准的 Web 技术HTML/CSS/JS但默认没有 Node.js 的 API 访问权限。这是出于安全考虑的设计——渲染进程里的代码可能来自不可信的来源不能让它直接操作文件系统。两个进程之间通过IPCInter-Process Communication通信。渲染进程想读文件不能直接读要通过 IPC 发消息给主进程主进程读完再把结果传回来。这个机制看起来麻烦但它是 Electron 安全模型的基础。// 主进程注册 IPC 处理器 const { ipcMain, dialog } require(electron); const fs require(fs).promises; ipcMain.handle(read-local-file, async (event, filePath) { try { const content await fs.readFile(filePath, utf-8); return { success: true, data: content }; } catch (error) { return { success: false, error: error.message }; } }); // 渲染进程调用主进程方法 const { ipcRenderer } require(electron); async function loadFile(path) { const result await ipcRenderer.invoke(read-local-file, path); if (result.success) { return result.data; } throw new Error(result.error); }上面这段代码展示了最基本的 IPC 模式。ipcMain.handle在主进程注册一个异步处理器ipcRenderer.invoke在渲染进程调用它并等待结果。这种请求-响应模式适合大多数场景。3.2 安全配置contextIsolation 与 sandboxElectron 的安全配置是新手最容易踩坑的地方。默认情况下如果你在创建窗口时不加任何配置渲染进程会拥有完整的 Node.js 访问权限这意味着任何注入到页面里的脚本都能读写你的文件系统。这是极其危险的。正确的做法是开启contextIsolation和sandboxconst mainWindow new BrowserWindow({ width: 1200, height: 800, webPreferences: { contextIsolation: true, // 隔离渲染进程和预加载脚本的上下文 sandbox: true, // 启用 Chromium 沙箱 nodeIntegration: false, // 禁止渲染进程直接访问 Node.js preload: path.join(__dirname, preload.js) // 预加载脚本 } });contextIsolation: true意味着渲染进程的 JavaScript 环境和预加载脚本的环境是隔离的页面脚本无法直接访问预加载脚本里定义的变量。sandbox: true则进一步限制渲染进程的能力即使有漏洞也难以逃逸。预加载脚本preload.js是主进程和渲染进程之间的桥梁。它运行在渲染进程的上下文里但拥有有限的 Node.js API 访问权限。通过contextBridge它可以把特定的 API 安全地暴露给页面脚本// preload.js const { contextBridge, ipcRenderer } require(electron); contextBridge.exposeInMainWorld(desktopAPI, { readFile: (path) ipcRenderer.invoke(read-local-file, path), saveConversation: (data) ipcRenderer.invoke(save-conversation, data), getGlobalShortcut: () ipcRenderer.invoke(get-shortcut) });这样页面脚本只能访问window.desktopAPI上明确暴露的方法无法直接调用require或访问文件系统。这个设计模式是 Electron 安全开发的核心。提示如果你在开发过程中遇到require is not defined的错误大概率是因为开了contextIsolation但没配preload或者页面脚本试图直接访问 Node.js API。这是正常的说明安全配置生效了你需要通过 IPC 来间接访问系统能力。3.3 本地数据存储方案选型桌面版相比网页版的一个核心优势是本地数据持久化。对话历史、用户配置、缓存数据都需要存在本地。Electron 环境下有几种常见方案方案适用场景优点缺点better-sqlite3结构化对话历史、全文搜索同步 API、性能好、支持 FTS需要编译原生模块lowdb简单配置存储纯 JS、零依赖、易上手大数据量性能差electron-store用户偏好设置开箱即用、支持加密不适合大量数据IndexedDB渲染进程内数据浏览器原生、无需 IPC容量受限、查询能力弱对于对话历史这种需要全文搜索、可能积累几万条记录的场景better-sqlite3是最合适的选择。它是同步 API在 Electron 主进程里用起来很顺手而且支持 SQLite 的 FTS5 全文搜索扩展搜索速度非常快。const Database require(better-sqlite3); const db new Database(path.join(app.getPath(userData), conversations.db)); // 建表 db.exec( CREATE TABLE IF NOT EXISTS conversations ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, created_at INTEGER, updated_at INTEGER ); CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, conversation_id INTEGER, role TEXT, content TEXT, created_at INTEGER, FOREIGN KEY (conversation_id) REFERENCES conversations(id) ); CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(content, contentmessages, content_rowidid); ); // 全文搜索 const searchStmt db.prepare( SELECT m.* FROM messages m JOIN messages_fts fts ON m.id fts.rowid WHERE messages_fts MATCH ? ORDER BY m.created_at DESC LIMIT 50 );app.getPath(userData)会返回当前系统的用户数据目录Windows 下是%APPDATA%/应用名macOS 下是~/Library/Application Support/应用名Linux 下是~/.config/应用名。把数据库放在这里可以保证跨平台兼容而且不会被误删。4. 从零搭建一个对话桌面客户端的实操流程4.1 环境准备与项目初始化先把基础环境搭起来。你需要 Node.js 18 以上版本推荐用 nvm 或 fnm 管理版本。然后初始化项目mkdir deepseek-desktop cd deepseek-desktop npm init -y npm install electron --save-dev npm install better-sqlite3 electron-store安装better-sqlite3的时候可能会遇到编译问题因为它包含原生模块。Windows 上需要安装 Visual Studio Build ToolsmacOS 上需要 Xcode Command Line Tools。如果编译失败可以试试electron-rebuildnpm install --save-dev electron/rebuild npx electron-rebuild项目结构建议这样组织deepseek-desktop/ ├── src/ │ ├── main/ │ │ ├── index.js # 主进程入口 │ │ ├── ipc-handlers.js # IPC 处理器 │ │ ├── database.js # 数据库操作 │ │ └── shortcuts.js # 全局快捷键 │ ├── preload/ │ │ └── index.js # 预加载脚本 │ └── renderer/ │ ├── index.html │ ├── app.js │ └── styles.css ├── package.json └── electron-builder.yml这个结构把主进程、预加载、渲染进程清晰分开后续维护起来不会乱。4.2 主进程核心逻辑实现主进程入口文件负责创建窗口、注册快捷键、初始化数据库、设置托盘。下面是一个精简但完整的实现const { app, BrowserWindow, globalShortcut, Tray, Menu, nativeImage } require(electron); const path require(path); const { initDatabase } require(./database); const { registerIpcHandlers } require(./ipc-handlers); let mainWindow null; let tray null; function createWindow() { mainWindow new BrowserWindow({ width: 1100, height: 750, minWidth: 800, minHeight: 600, show: false, webPreferences: { contextIsolation: true, sandbox: true, nodeIntegration: false, preload: path.join(__dirname, ../preload/index.js) } }); mainWindow.loadFile(path.join(__dirname, ../renderer/index.html)); mainWindow.once(ready-to-show, () { mainWindow.show(); }); // 关闭时最小化到托盘而不是退出 mainWindow.on(close, (event) { if (!app.isQuitting) { event.preventDefault(); mainWindow.hide(); } }); } function registerGlobalShortcut() { const ret globalShortcut.register(CommandOrControlShiftD, () { if (mainWindow.isVisible()) { mainWindow.hide(); } else { mainWindow.show(); mainWindow.focus(); } }); if (!ret) { console.error(全局快捷键注册失败可能被其他应用占用); } } function createTray() { const icon nativeImage.createFromPath(path.join(__dirname, ../../assets/tray.png)); tray new Tray(icon.resize({ width: 16, height: 16 })); const contextMenu Menu.buildFromTemplate([ { label: 显示主窗口, click: () mainWindow.show() }, { label: 新建对话, click: () mainWindow.webContents.send(new-conversation) }, { type: separator }, { label: 退出, click: () { app.isQuitting true; app.quit(); } } ]); tray.setToolTip(DeepSeek Desktop); tray.setContextMenu(contextMenu); tray.on(click, () mainWindow.show()); } app.whenReady().then(() { initDatabase(); registerIpcHandlers(); createWindow(); registerGlobalShortcut(); createTray(); }); app.on(will-quit, () { globalShortcut.unregisterAll(); }); app.on(window-all-closed, () { if (process.platform ! darwin) { app.quit(); } });这段代码里有几个关键点值得展开说。show: false配合ready-to-show事件是为了避免窗口白屏闪烁——先创建但不显示等页面渲染好了再显示。close事件里拦截关闭操作是为了实现关闭即最小化到托盘的行为这是桌面客户端的常见交互模式。全局快捷键注册失败时要给出提示因为快捷键冲突是很常见的情况。4.3 对话界面与流式响应处理对话客户端的核心交互是流式响应——用户发送消息后AI 的回复是逐字返回的而不是等全部生成完再一次性显示。这个体验在网页端和桌面端都需要实现但桌面端可以用更灵活的方式处理。DeepSeek 的 API 支持 SSEServer-Sent Events流式返回。在渲染进程里可以用fetch配合ReadableStream来读取async function sendMessage(messages, onChunk) { const response await fetch(https://api.deepseek.com/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify({ model: deepseek-chat, messages: messages, stream: true }) }); const reader response.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop(); // 保留不完整的最后一行 for (const line of lines) { if (line.startsWith(data: )) { const data line.slice(6); if (data [DONE]) return; try { const parsed JSON.parse(data); const content parsed.choices[0]?.delta?.content; if (content) onChunk(content); } catch (e) { // 忽略解析错误继续处理下一行 } } } } }这里有个容易踩的坑SSE 的数据是按行分割的但网络传输不保证每次read()返回的都是完整的行。所以必须用一个buffer来累积数据按\n分割后保留最后一段不完整的部分等下次读取时再拼接。如果不做这个处理偶尔会出现 JSON 解析失败的情况。注意API Key 不要硬编码在前端代码里。在桌面客户端中建议把 Key 存在主进程的加密存储里比如用safeStorageAPI渲染进程通过 IPC 请求主进程代为发起 API 调用。这样即使渲染进程被注入恶意脚本也拿不到 Key。4.4 本地文件拖拽与上下文注入桌面版相比网页版的一个杀手级功能是本地文件拖拽。用户可以直接把代码文件、Markdown 文档、甚至图片拖进输入框客户端读取内容后作为上下文一起发送。实现这个功能需要处理两个部分渲染进程的拖拽事件和主进程的文件读取。// 渲染进程处理拖拽 const dropZone document.getElementById(input-area); dropZone.addEventListener(dragover, (e) { e.preventDefault(); dropZone.classList.add(drag-over); }); dropZone.addEventListener(dragleave, () { dropZone.classList.remove(drag-over); }); dropZone.addEventListener(drop, async (e) { e.preventDefault(); dropZone.classList.remove(drag-over); const files Array.from(e.dataTransfer.files); for (const file of files) { // 通过 preload 暴露的 API 读取文件 const result await window.desktopAPI.readFile(file.path); if (result.success) { // 根据文件类型决定处理方式 const ext file.name.split(.).pop().toLowerCase(); if ([png, jpg, jpeg, gif, webp].includes(ext)) { // 图片走 base64 编码 insertImageContext(file.name, result.data); } else { // 文本文件直接插入内容 insertTextContext(file.name, result.data); } } } });主进程侧的读取逻辑需要处理大文件的情况。如果用户拖进来一个几 MB 的日志文件直接全部塞进上下文会超出模型的 token 限制。合理的做法是设置一个大小阈值超过阈值的文件只读取前 N 行或前 N 个字符并给出提示。ipcMain.handle(read-local-file, async (event, filePath) { const stats await fs.stat(filePath); const MAX_SIZE 1024 * 1024; // 1MB if (stats.size MAX_SIZE) { // 大文件只读前 5000 行 const content await readFirstLines(filePath, 5000); return { success: true, data: content, truncated: true, originalSize: stats.size }; } const content await fs.readFile(filePath, utf-8); return { success: true, data: content, truncated: false }; });4.5 打包与分发开发完成后需要打包成可执行文件。推荐用electron-builder它支持 Windowsnsis、portable、macOSdmg、zip、LinuxAppImage、deb多种格式。# electron-builder.yml appId: com.example.deepseek-desktop productName: DeepSeek Desktop directories: output: dist buildResources: assets files: - src/**/* - assets/**/* - node_modules/**/* win: target: - target: nsis arch: [x64] icon: assets/icon.ico mac: target: - target: dmg arch: [x64, arm64] icon: assets/icon.icns linux: target: - AppImage - deb icon: assets/icon.png打包命令npx electron-builder --win --x64 npx electron-builder --mac npx electron-builder --linux打包过程中最常见的坑是原生模块如 better-sqlite3没有正确编译到目标平台。如果是在 Windows 上打包 macOS 版本原生模块需要交叉编译比较麻烦。实际项目中通常用 CI/CD 在各自平台上分别打包或者用 Docker 容器模拟目标环境。5. 实际使用中遇到的问题与排查记录5.1 常见问题速查表问题现象可能原因排查方法解决方案启动后白屏渲染进程加载失败打开 DevTools 看 Console检查 loadFile 路径、preload 路径全局快捷键无效被其他应用占用换一个组合键测试注册失败时提示用户重新设置数据库写入报错原生模块未编译检查 node_modules 下是否有 .node 文件运行 electron-rebuild打包后体积过大包含了 devDependencies检查 files 配置用 electron-builder 的 files 字段排除流式响应卡顿渲染进程频繁重绘用 Performance 面板分析用 requestAnimationFrame 节流更新托盘图标不显示图标尺寸或格式不对检查图标文件Windows 用 16x16 icomacOS 用 16x16 png关闭窗口后进程残留未处理 close 事件任务管理器查看设置 app.isQuitting 标志API 请求跨域失败渲染进程直接请求看 Network 面板通过主进程代理请求5.2 三个我踩过的坑第一个坑contextIsolation 开启后 preload 里的 require 失效。这个问题困扰了我半天。开了contextIsolation: true之后preload 脚本里用require(fs)会报错。原因是 contextIsolation 隔离了 preload 和页面的上下文但 preload 本身还是可以访问 Node.js API 的——前提是sandbox没有开启。如果同时开了sandbox: truepreload 里就只能用有限的 API 了。解决方案是把文件操作全部放到主进程preload 只负责通过ipcRenderer.invoke转发请求。第二个坑better-sqlite3 在打包后找不到数据库文件。开发时数据库路径用的是相对路径./data.db打包后工作目录变了导致找不到文件。正确做法是用app.getPath(userData)获取绝对路径。这个坑的教训是所有涉及文件路径的地方开发阶段就要用绝对路径不要图省事用相对路径。第三个坑全局快捷键在 macOS 上需要辅助功能权限。在 macOS 上注册全局快捷键系统会要求用户授予辅助功能权限。如果用户拒绝快捷键就静默失效了。解决方案是在注册失败时弹出引导告诉用户去系统偏好设置里开启权限。Windows 上一般没有这个问题但要注意某些安全软件可能会拦截全局快捷键注册。5.3 性能优化的几个实用技巧桌面客户端的性能优化主要集中在启动速度和内存占用两个方面。启动速度方面最关键的是延迟加载非核心模块。数据库初始化、托盘创建这些操作可以放在窗口显示之后异步执行不要阻塞首屏渲染。另外ready-to-show事件触发后再显示窗口比直接show: true体验好很多因为用户看到的是已经渲染好的界面而不是白屏。内存占用方面Electron 的每个窗口都是一个独立的 Chromium 实例内存开销不小。如果应用只需要一个主窗口就不要创建多余的 BrowserWindow。另外长时间运行后渲染进程可能会有内存泄漏可以在托盘菜单里加一个重新加载选项让用户手动刷新渲染进程。// 托盘菜单里加重新加载选项 { label: 重新加载界面, click: () mainWindow.webContents.reload() }还有一个容易被忽略的点是对话历史的懒加载。如果用户积累了几千条对话启动时全部加载到内存里会很慢。正确的做法是只加载最近 50 条滚动到顶部时再加载更多。这个分页逻辑在数据库层面用LIMIT和OFFSET实现即可。6. 桌面客户端的扩展方向与插件思路6.1 插件系统的设计考量桌面客户端相比网页版的另一个优势是可以做插件系统。网页端的插件受限于浏览器沙箱能做的事情很有限桌面端的插件可以访问文件系统、调用系统命令、和其他应用交互想象空间大很多。一个简单的插件系统设计思路是每个插件是一个独立的 JS 模块导出一个标准接口主进程在启动时扫描插件目录并加载。插件通过 IPC 和主进程通信主进程负责权限控制。// 插件接口定义 class Plugin { constructor(context) { this.context context; // 提供 logger、storage、ipc 等能力 } // 插件元信息 get metadata() { return { name: , version: , description: }; } // 生命周期钩子 async onLoad() {} async onUnload() {} // 注册自定义命令 get commands() { return []; } }插件能做什么举几个实际有用的例子代码格式化插件可以在发送代码前自动格式化翻译插件可以把选中的文本翻译成目标语言导出插件可以把对话导出为 Markdown、PDF 或图片快捷指令插件可以预设常用的提示词模板一键填充。6.2 几个值得做的扩展功能基于我自己的使用习惯有几个扩展功能是值得投入时间做的。对话导出与备份。把对话导出为 Markdown 文件按日期和主题分类存储。这个功能实现起来不复杂但实用性极高。导出时可以选择是否包含代码块、是否保留时间戳、是否合并连续的同角色消息。提示词模板管理。把常用的提示词存成模板支持变量替换。比如一个代码审查模板里面用{{language}}和{{code}}作为占位符使用时填充即可。这个功能可以大幅减少重复输入。多模型切换。如果同时使用多个对话服务可以在客户端里统一管理根据任务类型切换不同的模型。这个功能的实现关键是抽象出一层统一的 API 适配器把不同服务的请求格式统一转换。本地知识库检索。把本地的文档、笔记建立索引提问时自动检索相关内容作为上下文。这个功能需要用到向量检索可以用本地的 embedding 模型或者调用 API 生成向量然后存在 SQLite 的向量扩展里。6.3 关于生态和社区的观察桌面客户端的生态目前还在早期阶段。官方客户端提供了基础功能但扩展能力有限。社区里有一些第三方客户端在做差异化比如支持多模型、支持插件、支持本地知识库等。这个方向的机会在于官方客户端通常追求通用性和稳定性不会做太多个性化功能这就给第三方留下了空间。如果你打算自己做一个桌面客户端我的建议是不要试图做一个全能选手。找一个具体的场景做深做透比如程序员的代码助手、研究人员的文献阅读助手、写作者的素材管理助手围绕这个场景把体验做到极致。通用型产品很难和官方竞争但垂直场景的深度优化是官方不会去做的。提示做第三方客户端要注意 API 的使用条款和速率限制。不要做违反服务条款的事情比如绕过限制、批量刷量等。合规使用是长期运营的前提。7. 一些实际使用中的体会用桌面版这段时间最大的感受是工具形态会影响使用习惯。网页版的时候我把对话工具当成一个需要专门去打开的东西用的频率虽然高但每次都是有明确目的才去用。换成桌面版之后因为全局快捷键太方便了我发现自己开始把它当成一个随时可以问一句的助手——写代码卡住的时候按一下快捷键问一句看文档遇到不懂的概念随手查一下甚至写邮件的时候让它帮忙润色一下措辞。这种随手可用的体验是网页版给不了的。另一个体会是本地数据的所有权很重要。网页版的对话历史存在服务端你没法方便地导出、搜索、备份。桌面版把数据存在本地你可以用任何工具去分析它、迁移它、备份它。这种数据掌控感对于长期使用来说很关键。如果你还在犹豫要不要换桌面版我的建议是先花十分钟装一个试试把全局快捷键设好用一天。如果一天之后你觉得没什么区别那就继续用网页版如果你像我一样回不去了那就说明这个形态确实解决了你的真实痛点。工具这东西适合自己的才是最好的别人的体验只能作为参考。最后分享一个小技巧桌面版的配置文件通常存在用户数据目录里定期备份这个目录可以保留所有对话历史和设置。换电脑的时候直接拷贝过去无缝迁移。这个目录的位置在 Windows 上是%APPDATA%/应用名macOS 上是~/Library/Application Support/应用名Linux 上是~/.config/应用名。养成定期备份的习惯比什么云同步都靠谱。
阅读完成 · 觉得有帮助?
咨询建站