共计 3503 个字符,预计需要花费 9 分钟才能阅读完成。
背景与需求分析
作为一名长期使用 ChatGPT 网页版的开发者,我发现自己经常需要反复打开浏览器、登录账号,甚至要处理多标签页的混乱。于是我开始思考:为什么不打造一个像豆包那样简洁高效的 Windows 桌面应用呢?桌面应用相比网页版有几个显著优势:

- 快捷访问:无需每次打开浏览器,直接通过桌面图标或系统托盘快速启动
- 系统集成:支持全局快捷键唤醒、通知提醒等操作系统级功能
- 性能优化:独立进程运行,避免浏览器内存占用过高的问题
- 隐私保护:本地存储敏感数据比浏览器更安全可控
技术选型对比
在决定开发桌面应用后,我调研了几种主流技术方案:
- Electron
- 成熟稳定,社区资源丰富
- 基于 Chromium 和 Node.js,可直接使用 Web 技术栈
-
缺点:打包体积较大(约 120MB 基础包)
-
Tauri
- 使用 Rust 编写,性能更好
- 打包体积小(约 5MB)
-
缺点:生态相对年轻,某些 Node.js 模块需要额外适配
-
NW.js
- 更接近原生浏览器环境
- 适合需要深度浏览器特性的场景
- 缺点:社区活跃度不如 Electron
考虑到开发效率和生态支持,我最终选择了 Electron + React + TypeScript 的组合。
核心实现
项目初始化
首先创建基础项目结构:
mkdir chatgpt-desktop && cd chatgpt-desktop
npm init -y
npm install electron react react-dom @types/react @types/react-dom --save-dev
主进程配置 (main.ts)
import {app, BrowserWindow} from 'electron';
import path from 'path';
let mainWindow: BrowserWindow | null = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, 'assets/icon.ico') // Windows 图标
});
// 加载 React 应用
mainWindow.loadURL(
process.env.NODE_ENV === 'development'
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../build/index.html')}`
);
// 开发者工具(仅开发环境)if (process.env.NODE_ENV === 'development') {mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => {mainWindow = null;});
}
app.whenReady().then(createWindow);
// 实现豆包式的系统托盘功能
import {Tray, Menu} from 'electron';
let tray: Tray | null = null;
app.whenReady().then(() => {tray = new Tray(path.join(__dirname, 'assets/icon.ico'));
const contextMenu = Menu.buildFromTemplate([{ label: '打开', click: () => mainWindow?.show()},
{label: '退出', click: () => app.quit()}
]);
tray.setToolTip('ChatGPT 桌面版');
tray.setContextMenu(contextMenu);
});
渲染进程与 API 通信
在 React 组件中调用 ChatGPT API 的关键实现:
// src/api/chatgpt.ts
interface ChatMessage {
role: 'user' | 'assistant';
content: string;
}
export async function sendToChatGPT(messages: ChatMessage[],
apiKey: string
): Promise<string> {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: 'gpt-3.5-turbo',
messages,
temperature: 0.7,
}),
});
if (!response.ok) {throw new Error(`API 请求失败: ${response.statusText}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
主进程与渲染进程通信
通过预加载脚本安全地暴露 API:
// preload.js
const {contextBridge, ipcRenderer} = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {minimize: () => ipcRenderer.send('window-minimize'),
close: () => ipcRenderer.send('window-close'),
getApiKey: () => ipcRenderer.invoke('get-api-key'),
setApiKey: (key) => ipcRenderer.invoke('set-api-key', key)
});
性能优化
打包体积优化
- 使用 electron-builder 配置排除不必要的文件:
{
"build": {"files": ["build/**/*", "!build/node_modules"],
"asar": true,
"compression": "maximum"
}
}
- 启用 Electron 的 ASAR 归档减少文件数量
- 使用 electron-packager 的 prune 选项移除 devDependencies
内存管理
- 限制聊天历史记录条数(如最多保留 50 条)
- 实现懒加载对话历史
- 使用虚拟滚动长列表
安全考量
API 密钥存储
// 使用 electron-store 安全存储 API 密钥
import Store from 'electron-store';
const schema = {
apiKey: {
type: 'string',
default: '',
encrypt: true // 启用加密存储
}
};
const store = new Store({schema});
// 在主进程中处理密钥存取
ipcMain.handle('get-api-key', () => store.get('apiKey'));
ipcMain.handle('set-api-key', (_, key) => store.set('apiKey', key));
通信安全
- 所有 API 请求必须使用 HTTPS
- 实现请求签名防止中间人攻击
- Content Security Policy 配置:
<meta http-equiv="Content-Security-Policy" content="default-src'self'; connect-src https://api.openai.com;">
生产环境避坑指南
- 白屏问题 :确保正确配置了
__dirname路径 - 跨域问题 :在 webPreferences 中正确设置
nodeIntegration和contextIsolation - 打包失败:检查 electron-builder 的 target 配置是否符合 Windows 要求
- 内存泄漏:定期检查并清理事件监听器
开放性问题
如何实现离线缓存对话记录功能?我有几个思考方向:
- 使用 IndexedDB 在客户端存储加密的对话历史
- 实现本地 SQLite 数据库同步
- 采用 PouchDB 实现离线优先策略
你更倾向于哪种方案?或者有其他更好的实现思路吗?欢迎在评论区分享你的见解。
正文完
