应用开发- Ai Talk 应用开发流程记录文档

一、前言

Ai Talk 项目是根据个人使用习惯编写的一款款平台的 AI 大模型对话客户端应用。可利用该框架构建任何属于自己的跨平台客户端,因为本质上只是将 Web 应用封装成客户端。

项目地址:https://github.com/Funsiooo/Ai-Talk

二、程序介绍

2.1 简介

Ai Talk 是一款集合了多种大语言模型应用的开源桌面客户端,基于 Electron 构建。Electron 是一个由 GitHub 开发的开源框架,用于构建跨平台的桌面应用程序。它通过结合 Chromium 浏览器引擎和 Node.js 运行时环境,允许开发者使用 HTML、CSS 和 JavaScript 等前端技术开发桌面应用,同时支持调用底层系统功能(如文件系统、系统通知等)。当前支持以下大模型:OpenAI ChatGPT、Google Gemini、Quora Poe、月之暗面 Kimi、字节 豆包、阿里 通义千问、腾讯 元宝、百度 文心一言。

📑 Note: OpenAi ChatGPT、Google Gemini 、Quora Poe 需要设置网络代理才能正常访问(国外友人则无需代理)。

2.2 功能介绍

界面: 程序主要由两部分组成:左侧的侧边栏和右侧的显示区域。点击左侧的某个大模型,右侧将加载该大模型的官方页面,即可开始对话。

example1

example2

网络代理: 由于 OpenAI ChatGPTGoogle GeminiQuora Poe 模型需要访问国外网站,因此在使用之前,需要在左侧侧边栏下方的 “网络设置” 中配置代理网络。目前只支持 HTTPHTTPS 协议。配置步骤如下:

1
2
3
4
1)点击 “网络设置” ,在弹窗输入代理地址,如:本地开启了 7890 端口为代理网络端口,填入 http://127.0.0.1:7890 
2)点击 “设置代理” ,完成网络设置
3)如网络设置输入错误,点击 “清除代理” 即可恢复程序默认设置
4)设置完毕后点击 “关闭”

example3

2.3 大模型使用规则

1
2
3
4
5
6
7
8
9
10
| 模型名称        | 使用规则                                                     |
| -------------- | ------------------------------------------------------------ |
| 月之暗面 Kimi | 消息条数限制:免费使用,不限次数。 |
| 通义千问 | 消息条数限制:免费使用,不限次数。 |
| 字节 豆包 | 消息条数限制:免费使用,不限次数。 |
| 腾讯 元宝 | 消息条数限制:免费使用,不限次数。 |
| 百度 文心一言 | 消息条数限制:3.5 模型免费使用,其它模型存在次数限制。 |
| OpenAi ChatGPT | 消息条数限制:免费套餐用户在 5 小时内只能使用 GPT-4o 的有限次数,使用完毕回退其它模型,如:GPT-3.5 。【 官方文档链接:https://help.openai.com/en/articles/9275245-using-chatgpt-s-free-tier-faq#h_43513320b9】 |
| Google Gemini | 消息条数限制:1.5 flash 免费使用,存在次数限额,限额不详。【官方文档:https://gemini.google.com/faq】 |
| Quora Poe | 消息条数限制:存在次数限额,限额不详。 |

2.4 源码启动

  • 安装 nodejs
1
https://nodejs.org/zh-cn/download
  • 下载项目源码
1
git clone https://github.com/Funsiooo/Ai-Talk.git
  • 安装 electron
1
2
cd Ai-Talk
npm install electron --save-dev -d --registry=https://registry.npmmirror.com
  • Ai Talk 目录下执行
1
npm start

2.5 打包

  • 安装 node.js
1
https://nodejs.org/zh-cn/download
  • 下载项目源码
1
git clone https://github.com/Funsiooo/Ai-Talk.git
  • 安装 electron
1
2
cd Ai-Talk
npm install electron --save-dev -d --registry=https://registry.npmmirror.com
  • 安装 electron-forge/cli
1
npm install --save-dev @electron-forge/cli -d --registry=https://registry.npmmirror.com
  • macos 安装 @electron-forge/maker-dmg
1
npm install --save-dev  -d @electron-forge/maker-dmg --registry=https://registry.npmmirror.com
  • Ai Talk 目录下执行,打包文件存放在 out 目录下(打包过程中若出现报错可忽略)
1
npm run build

📑 Note: 自行打包需要根据自身设备替换项目中的 package.json 文件, 目前提供 Mac Apple siliconWindows 打包文件, 文件见 config 文件夹。

build

三、开发流程

3.1 Electron 安装

3.1.1 Node 安装

macOS 使用 brew 安装,win 直接官网下载安装即可

1
2
3
4
5
6
brew install node

funsiooo@macbook-pro ~ % node -v
v23.3.0
funsiooo@macbook-pro ~ % npm -v
10.9.0

3.1.2 初始化 Electron 项目

1)创建项目目录,使用 npm 初始化项目

1
2
3
mkdir Ai_Talk
cd Ai_Talk
npm init -y

2)项目中安装 Electron,可能会存在网络问题,建议 npm 设置国内源,有条件代理的直接全局代理

1
2
3
4
5
6
7
1)设置 npm 源
npm config set registry https://registry.npmmirror.com # 使用淘宝镜像
npm config set registry https://registry.npmjs.org # 恢复回默认的 npm 官方镜像

2)electron 安装,选当前目录下安装即可
npm install electron --save-dev -d # 在创建的项目文件夹内安装
npm install -g electron -d # 全局安装,即安装在本级上

build

3)构建第一个程序

官方教程:https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-first-app , 项目文件如下

1
2
3
4
5
├── index.html              # 主页面
├── main.js # 主进程入口文件
├── node_modules # Electron 项目依赖
├── package-lock.json # NPM 锁定文件
└── package.json # 项目依赖及元数据

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { app, BrowserWindow } = require('electron')

const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

app.whenReady().then(() => {
createWindow()
})

package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "23.1.3"
}
}

启动程序

1
npm run start

build

3.2 本项目开发流程

3.2.1 项目文件树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Ai-Talk/                     # 项目根目录
├── LICENSE # 开源许可证
├── README.md # 项目说明文档
├── assets/ # 静态资源目录
├── config/ # 打包文件目录
│ ├── macos/ # macOS 平台打包配置文件目录
│ │ └── package.json # macOS 平台打包配置文件
│ └── windows/ # Windows 平台打包配置文件目录
│ ├── css.zip # 样式压缩包,mac与windows样式差异,win打包可替换该css
│ └── package.json # Windows 平台打包配置文件
├── doc/ # 文档目录
│ └── README_EN.md # 英文项目说明文档
├── forge.config.js # Electron Forge 配置文件
├── main.js # 主进程入口文件
├── package-lock.json # NPM 锁定文件
├── package.json # 项目依赖及元数据
└── src/ # 源代码目录
├── css/ # 样式文件目录
│ ├── proxy.css # 代理设置相关样式
│ ├── second.css # 程序对话页面样式
│ ├── style.css # 主页面通用样式
│ └── version.css # 关于程序页面样式
├── js/ # 脚本文件目录
│ ├── preload.js # 预加载脚本
│ └── renderer.js # 渲染进程脚本
└── view/ # HTML 文件目录
├── about.html # 关于程序页面
├── index.html # 主页面
├── proxy.html # 代理弹窗页面
└── second.html # 程序对话页面

3.2.2 程序核心流程

flow

  • main.js: 主进程入口,负责创建窗口、管理应用生命周期和处理系统交互。需要展示如何创建BrowserWindow实例,加载页面,以及处理IPC事件。
  • Preload.js: 预加载脚本,作为主进程和渲染进程之间的桥梁,暴露有限的Node.js功能给渲染进程。需要说明contextBridge和ipcRenderer的使用。
  • index.html: 主页面模板,渲染进程加载的HTML结构。需要包含对渲染脚本的引用,并展示如何调用预加载的API。
  • Renderer.js: 渲染进程脚本,处理用户界面交互,通过预加载脚本与主进程通信。需要事件监听和更新UI的示例。

3.2.3 主要功能

官方Web服务集成

  • 采用 Electron WebView 组件无缝加载官方网页,实现跨平台封装(无需重复开发 UI/交互逻辑)

对话分栏布局

  • 左侧面板:对话大模型选择、代理设置等功能
  • 右侧主界面:显示左侧选择的大模型官方页面,开启对话模式

网络代理模块

  • Electron session.setProxy API 实现代理设置功能

3.2.4 代码分析

main.js 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
const { app, BrowserWindow, ipcMain, dialog, nativeImage } = require('electron')
const path = require('path')

let mainWindow;
let proxyWindow;
let versionWindow

function createWindow() {
// 创建主窗口
mainWindow = new BrowserWindow({
width: 1260,
height: 830,
icon: 'assets/logo.ico',
webPreferences: {
devTools: false,
contextIsolation: true,
webviewTag: true,
preload: path.join(__dirname, 'src', 'js', 'preload.js'),
enableRemoteModule: false,
nodeIntegration: false,
nativeWindowOpen: true,
webSecurity: true,
allowRunningInsecureContent: true,
},
});

// 隐藏electron menu
mainWindow.setMenu(null);


// 加载初始页面
mainWindow.loadFile(path.join(__dirname, 'src', 'view', 'index.html'));

mainWindow.on('closed', () => {
mainWindow = null;
});

}

// 创建用于输入代理的自定义窗口
function createProxyWindow() {
proxyWindow = new BrowserWindow({
width: 370,
height: 250,
icon: 'assets/logo.ico',
parent: mainWindow,
modal: true,
show: false, // 初始时不显示
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'src','js', 'preload.js')
}
});

proxyWindow.setMenu(null);

proxyWindow.loadFile(path.join(__dirname, 'src', 'view', 'proxy.html'));
proxyWindow.once('ready-to-show', () => {
proxyWindow.show();
});

// 监听关闭窗口事件
proxyWindow.on('closed', () => {
proxyWindow = null;
});
}

// 监听渲染进程发送的请求来打开代理输入窗口
ipcMain.handle('open-proxy-dialog', () => {
createProxyWindow();
});

// 设置代理
ipcMain.handle('set-proxy', (event, proxyAddress) => {
mainWindow.webContents.session.setProxy({
proxyRules: `http=${proxyAddress};https=${proxyAddress}`,
proxyBypassRules: '<local>' // 可选:不通过代理的地址
}).then(() => {
mainWindow.webContents.reload(); // 设置代理后重新加载页面
}).catch(err => {
console.error('设置代理失败:', err);
dialog.showErrorBox('设置代理失败', '无法设置代理: ' + err.message);
});
});

// 清除代理并恢复默认设置
ipcMain.handle('clear-proxy', () => {
mainWindow.webContents.session.clearCache() // 清除缓存
.then(() => {
// 恢复默认代理设置
return mainWindow.webContents.session.setProxy({});
})
.then(() => {
mainWindow.webContents.reload(); // 重新加载页面
console.log('代理已清除,恢复默认设置');
})
.catch(err => {
console.error('清除代理失败:', err);
});
});


// 创建用于输入显示“关于程序”的自定义窗口
function createVersionWindow() {
versionWindow = new BrowserWindow({
width: 500,
height: 685,
icon: 'assets/logo.ico',
parent: mainWindow,
modal: true,
show: false, // 初始时不显示
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'src','js', 'preload.js')
}
});

versionWindow.setMenu(null);

versionWindow.loadFile(path.join(__dirname, 'src', 'view', 'about.html'));
versionWindow.once('ready-to-show', () => {
versionWindow.show();
});

// 监听关闭窗口事件
versionWindow.on('closed', () => {
versionWindow = null;
});
}

// 监听渲染进程发送的请求来打开窗口
ipcMain.handle('open-version-dialog', () => {
createVersionWindow();
});

app.whenReady().then(() => {
createWindow();

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

引入模块

1
2
const { app, BrowserWindow, ipcMain, dialog, nativeImage } = require('electron')
const path = require('path')
模块名称 用途 典型场景示例
app 管理应用生命周期(启动、退出) app.whenReady().then(createWindow)
BrowserWindow 创建和控制浏览器窗口 new BrowserWindow({ width: 800 })
ipcMain 主进程的进程间通信模块(与渲染进程 ipcRenderer 交互) ipcMain.handle('event', callback)
dialog 显示系统对话框(文件选择、消息提示等) dialog.showOpenDialog(options)
nativeImage 处理系统原生图片(如图标、截图) nativeImage.createFromPath('img.png')
path 处理文件路径 path.join(__dirname, 'src', 'file.js')

定义窗口函数,该程序主要有三个窗口,主窗口代理窗口关于程序窗口

1
2
3
let mainWindow;
let proxyWindow;
let versionWindow

主窗口 let mainWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 创建并配置应用程序主窗口
*/
function createWindow() {
// 创建主浏览器窗口实例
mainWindow = new BrowserWindow({
// 基础窗口配置
width: 1260, // 初始窗口宽度(像素)
height: 830, // 初始窗口高度(像素)
icon: 'assets/logo.ico', // 窗口图标(Windows/Linux 生效,macOS 需单独设置 Dock 图标)

// 网页内容安全配置(关键安全设置)
webPreferences: {
devTools: false, // 禁用开发者工具(生产环境建议关闭,开发时可动态开启)
contextIsolation: true, // 启用上下文隔离,隔离主进程与渲染进程(安全必需)
webviewTag: true, // 启用 <webview> 标签加载外部内容
preload: path.join(__dirname, 'src', 'js', 'preload.js'), // 预加载脚本路径
enableRemoteModule: false,// 禁用 remote 模块,防止渲染进程直接访问主进程 API(安全必需)
nodeIntegration: false, // 禁止渲染进程直接访问 Node.js(安全必需)
nativeWindowOpen: true, // 使用原生 window.open() 行为
webSecurity: true, // 启用同源策略(CORS 保护)
allowRunningInsecureContent: true, // 允许 HTTPS 页面加载 HTTP 资源
},
});

// 隐藏默认菜单栏,若不隐藏,Windows 上会出现 electron 菜单栏,影响程序美观
mainWindow.setMenu(null);

// 加载应用主界面
// 使用 path.join 确保跨平台路径兼容性(Windows/macOS/Linux)
mainWindow.loadFile(
path.join(__dirname, 'src', 'view', 'index.html')
);

// 窗口关闭事件处理
// 释放窗口引用避免内存泄漏(Electron 多窗口应用必需)
mainWindow.on('closed', () => {
mainWindow = null; // 垃圾回收时释放内存
});
}

代理端口 let proxyWindow;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
* 创建代理配置专用窗口(模态对话框)
* 用于用户输入代理服务器地址
*/
function createProxyWindow() {
// 创建代理配置窗口实例
proxyWindow = new BrowserWindow({
width: 370, // 窗口宽度(适配常见代理地址输入长度)
height: 250, // 窗口高度(容纳输入框和操作按钮)
icon: 'assets/logo.ico', // 窗口图标(Windows/Linux)
parent: mainWindow, // 设置父窗口实现模态效果
modal: true, // 模态窗口(阻止父窗口操作)
show: false, // 初始隐藏避免闪烁
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'src', 'js', 'preload.js') // 预加载脚本
}
});

// 移除默认菜单栏
proxyWindow.setMenu(null);

// 加载代理配置页面(建议路径:src/view/proxy.html)
proxyWindow.loadFile(
path.join(__dirname, 'src', 'view', 'proxy.html')
);

// 页面加载完成后显示窗口(避免白屏)
proxyWindow.once('ready-to-show', () => {
proxyWindow.show();
});

// 窗口关闭事件处理
proxyWindow.on('closed', () => {
proxyWindow = null; // 释放内存引用
});
}

/**
* IPC 通信:监听打开代理配置窗口请求
* 渲染进程调用方式:window.electron.ipcRenderer.invoke('open-proxy-dialog')
*/
ipcMain.handle('open-proxy-dialog', () => {
createProxyWindow(); // 创建并显示代理窗口,调用的是上方 function createProxyWindow()窗口
});

/**
* IPC 通信:设置代理服务器
* @param {string} proxyAddress - 代理地址(格式:host:port)
*/
ipcMain.handle('set-proxy', (event, proxyAddress) => {
// 通过 session 对象设置全局代理
mainWindow.webContents.session.setProxy({
proxyRules: `http=${proxyAddress};https=${proxyAddress}`, // 同时代理 HTTP/HTTPS
proxyBypassRules: '<local>' // 绕过本地地址(localhost, 127.0.0.1 等)
}).then(() => {
mainWindow.webContents.reload(); // 重载页面使代理生效
}).catch(err => {
console.error('设置代理失败:', err);
// 显示系统级错误对话框
dialog.showErrorBox('设置代理失败', '无法设置代理: ' + err.message);
});
});

/**
* IPC 通信:清除代理配置并恢复默认
*/
ipcMain.handle('clear-proxy', () => {
// 先清除网络缓存
mainWindow.webContents.session.clearCache()
.then(() => {
// 重置代理设置为空(使用系统默认)
return mainWindow.webContents.session.setProxy({});
})
.then(() => {
mainWindow.webContents.reload(); // 重载页面应用更改
console.log('代理已清除,恢复默认设置');
})
.catch(err => {
console.error('清除代理失败:', err);
// 可在此添加错误反馈到渲染进程
});
});

关于程序窗口 let versionWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* 创建显示"关于程序"信息的模态窗口
* 用于展示版本信息、版权声明、许可证等
*/
function createVersionWindow() {
// 创建关于窗口实例
versionWindow = new BrowserWindow({
width: 500, // 窗口宽度(适配常见说明文档布局)
height: 685, // 窗口高度(容纳长文本和图片)
icon: 'assets/logo.ico', // 窗口图标(Windows/Linux 生效)
parent: mainWindow,// 父窗口设置(实现模态对话框效果)
modal: true, // 模态窗口(阻止父窗口交互)
show: false, // 初始隐藏避免页面加载时闪烁
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'src', 'js', 'preload.js') // 预加载脚本路径
}
});

// 移除默认菜单栏(增强原生应用体验)
versionWindow.setMenu(null);

// 加载关于页面
versionWindow.loadFile(
path.join(__dirname, 'src', 'view', 'about.html')
);

// 页面加载完成后显示窗口(确保内容渲染完成)
versionWindow.once('ready-to-show', () => {
versionWindow.show();
});

// 窗口关闭事件处理(内存管理)
versionWindow.on('closed', () => {
versionWindow = null; // 释放内存引用防止内存泄漏
});
}

/**
* IPC 通信:监听打开"关于程序"窗口请求
* 渲染进程调用方式:window.electron.ipcRenderer.invoke('open-version-dialog')
*/
// 监听渲染进程发送的请求来打开窗口
ipcMain.handle('open-version-dialog', () => {
createVersionWindow();
});

Index.html 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ai Talk</title>
<!-- 加载 index.html CSS 格式-->
<link rel="stylesheet" href="../css/style.css">
</head>
<body>
<div id="container">
<!-- 测边栏 -->
<div id="sidebar">
<!-- logo -->
<div class="logo">
<img src="../../assets/logo.png" alt="Logo">
<h1 class="app_name">Ai Talk</h1>
</div>

<!-- 对话 -->
<div class="dialogue">
<p class="dialogue_text">
选择对话
<button class="action_button">
<img src="../../assets/dialogue.svg" alt="Logo">
</button>
</p>
</div>

<!-- AI 模块 -->
<div class="ai_module">
<div class="icon-item">
<button id="kimi">
<img src="../../assets/kimi.ico" alt="kimi" class="icon-img"> 月之暗面 Kimi
</button>
</div>

<div class="icon-item">
<button id="deepseek">
<img src="../../assets/deepseek.svg" alt="DeepSeek" class="icon-img"> 深度求索 DeepSeek
</button>
</div>

<div class="icon-item">
<button id="doubao">
<img src="../../assets/doubao.png" alt="doubao" class="icon-img"> 字节 豆包
</button>
</div>

<div class="icon-item">
<button id="tyqw">
<img src="../../assets/tyqw.png" alt="tyqw" class="icon-img"> 啊里 通义千问
</button>
</div>

<div class="icon-item">
<button id="openai">
<img src="../../assets/chatgpt.ico" alt="OpenAI" class="icon-img"> OpenAI ChatGPT
</button>
</div>

<div class="icon-item">
<button id="google">
<img src="../../assets/google.png" alt="google" class="icon-img"> Google Gemini
</button>
</div>

<div class="icon-item">
<button id="poe">
<img src="../../assets/poe.svg" alt="poe" class="icon-img"> Quora Poe
</button>
</div>

<div class="icon-item">
<button id="tenxun">
<img src="../../assets/tenxun.png" alt="tenxun" class="icon-img"> 腾讯 元宝
</button>
</div>

<div class="icon-item">
<button id="wxyy">
<img src="../../assets/wxyy.ico" alt="wxyy" class="icon-img"> 百度 文心一言
</button>
</div>
</div>

<!-- 设置选项列表 -->
<div class="settings-list">
<div class="settings-item">
<button id="home-btn">
<img src="../../assets/home.svg" alt="Night Mode Icon"> 回到主页
</button>
</div>

<div class="settings-item">
<button id="set-proxy-btn">
<img src="../../assets/network.svg" alt="Language Icon"> 网络设置
</button>
</div>

<div class="settings-item">
<button id="manual-btn">
<img src="../../assets/manual.svg" alt="Settings Icon"> 用户手册
</button>
</div>

<div class="settings-item">
<button id="version-btn">
<img src="../../assets/version.svg" alt="Version Icon"> 关于程序
</button>
</div>
</div>
</div>

<!-- 右边显示区域 -->
<div id="content">
<!-- 使用webview加载,默认加载second.html页面 -->
<webview id="webview" src="second.html" style="width: 100%; height: 100vh;"></webview>
</div>
</div>

<!--- 引入渲染进程文件 renderer.js-->
<script src="../js/renderer.js"></script>
</body>
</html>

renderer.js 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/*********************** 渲染进程主逻辑 (renderer.js) ***********************/
/* 本文件功能:控制界面交互与 WebView 内容加载,通过预加载脚本与主进程通信 */

/*--------------------- WebView 元素获取 ---------------------*/
// 获取用于显示网页内容的 WebView 容器,在index.html中已引入该文件(需主进程启用 webviewTag)
const webview = document.getElementById('webview');


/*--------------------- 侧边栏模型切换功能 -------------------*/
// 功能说明:点击不同 AI 模型按钮时,切换 WebView 加载的目标 URL

/* 获取所有 AI 模型按钮元素(每个按钮对应一个 AI 服务) */
// 月之暗面 Kimi 按钮,元素 kimi 对应,index.html 中的 <button id="kimi"> 元素
const kimiButton = document.getElementById('kimi');
// ...其他按钮定义(结构相同)

/* 为每个模型按钮绑定点击事件 */
// Kimi 按钮点击事件:用户点击index.html中的 kimi 元素,renderer.js 接收到信号,加载官方页面
kimiButton.addEventListener('click', function() {
webview.setAttribute('src', "https://kimi.moonshot.cn/");
});

// ...其他按钮事件监听逻辑(模式相同,加载对应服务 URL)


/*--------------------- 侧边栏设置功能 -----------------------*/
// 功能说明:处理设置相关按钮的点击事件(主页/手册/代理/版本)

/* 获取设置按钮元素 */
const homeBtn = document.getElementById('home-btn'); // 主页按钮
const setProxyBtn = document.getElementById('set-proxy-btn'); // 代理按钮
const manualBtn = document.getElementById('manual-btn'); // 手册按钮
const versionBtn = document.getElementById('version-btn'); // 版本按钮

/* 主页按钮点击事件:加载本地 second.html 页面 */
homeBtn.addEventListener('click', () => {
webview.src = 'second.html';
});

/* 用户手册按钮点击事件:加载本地 PDF 文件 */
manualBtn.addEventListener('click', () => {
webview.src = '../../assets/Readme.pdf';
});

/* 代理设置按钮点击事件:通过预加载脚本与主进程通信 */
setProxyBtn.addEventListener('click', () => {
// 调用预加载脚本暴露的 openProxyDialog 方法
// 通信流程:渲染进程 -> preload.js -> 主进程 ipcMain -> 创建代理窗口
window.electron.openProxyDialog();
});

/* 版本信息按钮点击事件:通过预加载脚本与主进程通信 */
versionBtn.addEventListener('click', () => {
// 调用预加载脚本暴露的 openVersionDialog 方法
window.electron.openVersionDialog();
});

preload.js 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* 
* 预加载脚本 (preload.js)
* 作用:在渲染进程和主进程之间建立安全可控的通信桥梁
*/

// 导入 Electron 模块
const { contextBridge, ipcRenderer } = require("electron");

/*
* 使用上下文桥接技术安全暴露 IPC 方法到渲染进程
*/
contextBridge.exposeInMainWorld("electron", {
/**
* 打开代理设置窗口
* 通信流程:
* 渲染进程 -> preload.js -> 主进程 (ipcMain.handle('open-proxy-dialog'))
*/
openProxyDialog: () => ipcRenderer.invoke('open-proxy-dialog'),

/**
* 设置网络代理
* @param {string} proxyAddress - 代理地址 (格式:host:port)
* 通信流程:
* 渲染进程 -> 主进程 (ipcMain.handle('set-proxy'))
* 主进程通过 session.setProxy 应用代理设置
*/
setProxy: (proxyAddress) => ipcRenderer.invoke('set-proxy', proxyAddress),

/**
* 清除代理设置
* 通信流程:
* 渲染进程 -> 主进程 (ipcMain.handle('clear-proxy'))
* 主进程通过 session.setProxy({}) 重置代理
*/
clearProxy: () => ipcRenderer.invoke('clear-proxy'),

/**
* 打开版本信息窗口
* 通信流程:
* 渲染进程 -> 主进程 (ipcMain.handle('open-version-dialog'))
* 主进程创建 about 窗口
*/
openVersionDialog: () => ipcRenderer.invoke('open-version-dialog')
});

3.2.5 自定义

若需要加入个人习惯的程序, 只需修改两部分代码即可

1)index.html 中的

中添加一个

2

2)renderer.js 中添加 index 的按钮元素以及需要加载的链接即可

3

例子:添加 deepseek 大模型

1)修改 index.html 添加 deepseek 按钮元素

4

2)renderer.js 中添加index.html 按钮元素,并指定跳转的 url

5

四、总结

本项目基于个人使用习惯驱动,Ai 时代,简单的重复性和文字性的工作不应该浪费我们的时间。