
1. 摘要
「AI聚合问答工具」是一款桌面客户端,核心目标是把多个主流大模型网页并排装进同一个窗口:用户在底部输入框写一次问题,点一下即可同步发送给所有面板,并排对比各家回答。它不调用任何大模型 API,而是复用各站点的网页登录态,以 webview 嵌入真实对话框;强调本地优先、无遥测、数据仅存本机。本文从问题定义、技术选型、系统架构、核心模块、工程化交付与关键问题六个维度,复盘这一项目的实践过程。
2. 问题定义与项目目标
大模型能力高度分化:有的擅长推理,有的擅长长文,有的联网检索更强。用户日常需要在多个站点之间反复复制粘贴、来回切换,既低效又难以横向对比。本项目据此设定四项目标:
- 一窗并排,一问多答:同一窗口内并排打开 2–12 个 AI 面板。
- 一次输入,全部发送:一个输入框,一键广播给所有面板。
- 复用网页、本地优先:复用各 AI 网页版登录态,不索取 API Key;登录态与配置存于本机。
- 隐私友好:无统计、无追踪、无云端上报。
3. 技术选型
| 维度 | 选择 | 理由 |
|---|---|---|
| 壳层框架 | **Electron 44** | 原生支持 `webview` 嵌入任意站点、分区持久化(`partition`)、`nativeTheme` 系统级色彩联动,最契合"聚合网页"的场景。 |
| 打包工具 | **electron-builder 26** | 统一产出 Windows(NSIS/便携)与 macOS(dmg),支持代码签名与 CI。 |
| 图标生成 | **sharp 0.35** | 用一条命令把矢量 `icon.svg` 栅格化为 1024×1024 `icon.png`,纳入构建流程。 |
| 聚合方式 | **网页注入**,非 API 聚合 | 调用 API 需各平台 Key、计费且接口频繁变动;直接嵌入官方网页可零成本、零授权地复用其完整能力。 |
关键判断:不重写对话 UI,而是"借用"各站点的对话 UI。这把工作量从"适配每个模型的 API"转移为"定位每个站点的输入框与发送按钮"。
4. 系统架构
4.1 进程与目录结构
src/
main/ 主进程:窗口、会话、主题联动、版本检测、本地存储
main.js
store.js
preload/ 安全桥接(contextIsolation)
preload.js
renderer/ 界面、布局、站点适配、注入、提示词库
index.html app.js styles.css
providers.js layouts.js inject.js prompts.js
scripts/ 图标生成、应用截图、自检脚本
build/ 图标与 README 截图(icon.png / screenshot.png)
assets/ 矢量图标(icon.svg)
`</pre>
### 4.2 进程模型与 IPC
采用 Electron 推荐的**主进程 / 预加载 / 渲染进程**三层隔离:
`</pre>
- 预加载(
preload.js) 通过contextBridge只暴露最小 API 集合,绝不开启nodeIntegration:`contextBridge.exposeInMainWorld('api', { storeGet: (k, f) => ipcRenderer.invoke('store:get', k, f), storeSet: (k, v) => ipcRenderer.invoke('store:set', k, v), openExternal: (url) => ipcRenderer.invoke('open-external', url), setNativeTheme: (mode) => ipcRenderer.invoke('set-native-theme', mode), onUpdateAvailable: (cb) => ipcRenderer.on('update-available', (_e, i) => cb(i)) }); ` - 主进程(
main.js) 用ipcMain.handle实现这些通道;其中open-external强制校验^https?://,避免任意协议跳转。* * *
5. 核心模块实现
5.1 多面板并排与 22 种布局
每个面板是一个webview,用partition=&quot;persist:&lt;providerId&gt;&quot;实现登录态隔离与重启存活:`function createWebview(provider) { var wv = document.createElement('webview'); wv.setAttribute('partition', 'persist:' + provider.id); wv.setAttribute('src', provider.url); wv.className = 'pane-web'; return wv; } `布局由layouts.js以 0–1 归一化坐标描述,共 22 种:2 屏(左右/上下)→ 3 屏(三栏/主从)→ … → 12 屏(3×4 / 4×3),涵盖网格、左右/上下、主从(一大带多小)等形态,渲染时映射为百分比定位。切换布局不刷新对话是整个交互的关键。实现要点:
paneEls缓存已创建的窗格实例;relayout()增删窗格后,用syncPaneProvider()判断——仅当某窗格的 provider 变化时才重建webview,否则原网页原样保留;- 切布局时统一复位
is-fullscreen / is-collapsed,避免与新的几何位置冲突。5.2 一次输入、全发送的站点适配
providers.js为每个站点维护inputs(输入框候选选择器)与send(发送按钮候选选择器)。真正执行在inject.js——一段注入到目标页面隔离世界的脚本,全程只读写该标签页 DOM、不触网:
- 定位输入框:先按站点专属选择器匹配,未命中则回退通用发现——在
textarea / contenteditable / input[type=text]中按可见性、位置(偏下方)、宽度、placeholder关键词打分取最优。 - 框架感知填充:
`* `textarea/input`:用原型 `value` setter + 派发 `input/change` 事件; `
* `contenteditable`:清空后用 `InputEvent(&#39;beforeinput&#39;, {inputType:&#39;insertText&#39;})` 兼容 **Slate**,失败回退 `execCommand(&#39;insertText&#39;)`;- 个别站点(如智谱清言)的提交绑定在
mousedown,脚本据此改走mousedown。
- 个别站点(如智谱清言)的提交绑定在
- 定位并触发发送:优先
aria-label/title含"发送/Send"的按钮,兜底按按钮文本匹配;触发时派发Pointer/Mouse/Click序列,或回退为在输入框pressEnter。渲染进程对每个面板并行
executeJavaScript,用Promise聚合结果,逐窗标记ok / fail并 toast 汇总。5.3 单窗全屏 / 收缩(不刷新)
全屏与收缩均为纯 CSS class 切换,不重建、不刷新webview:`pane.classList.toggle('is-fullscreen'); // 置顶覆盖,同一时刻仅一个 pane.classList.toggle('is-collapsed'); // 隐藏网页区,webview 仍驻留 DOM `这保证了"专注单个 AI"时既不丢失其他对话,也不产生额外的页面加载。5.4 白 / 夜模式联动
渲染进程applyTheme()经api.setNativeTheme()通知主进程,后者设置nativeTheme.themeSource = mode(light/dark/system)。由于webview内的 AI 站点普遍响应系统prefers-color-scheme,应用切主题即联动所有内嵌站点同步深/浅色。同时,主进程在
session-created时清洗 User-Agent,剥离Electron/*与应用名令牌,让各站点按正常桌面浏览器渲染、降低被限流风险:`.replace(/\s*Electron\/[0-9.]+/i, '').replace(/\s*AI聚合问答工具\/[0-9.]+/i, '')
5.5 提示词库
`prompts.js` 提供本地增删改查、分类与搜索;支持单 `{变量}` 与双 `{{变量}}` 占位符,使用时自动弹出变量表单(`buildVarFields` / `confirmVars`),把填好的内容回填到输入框。该模块与"发送注入"解耦,可独立维护。
### 5.6 版本检测与自动升级
主进程 `app ready` 后请求 `https://mutiai.0315e.com/curr_version`,用 `compareVersion()` 做语义化比较,发现远端更新即 `webContents.send(&#39;update-available&#39;, …)`,渲染进程弹窗并提供"去升级"按钮跳转官网;离线或接口不可达时静默忽略。
### 5.7 本地优先与隐私
每个 AI 独立
persist:分区,登录态互不串扰;应用配置(布局、provider、主题)以
store.json落盘于userData,由store.js用fs读写;全程无遥测接口;
requestSingleInstanceLock保证单实例运行。* * *6. 工程化与交付实践
6.1 国内镜像源加速
.npmrc将 npm、Electron 二进制、electron-builder 二进制、sharp 及其 libvips 全部指向 npmmirror,显著缩短依赖与二进制下载时间:`registry=https://registry.npmmirror.com
electron_mirror=https://registry.npmmirror.com/-/binary/electron/
electron_builder_binaries_mirror=https://registry.npmmirror.com/-/binary/electron-builder-binaries/
sharp_binary_host=https://registry.npmmirror.com/-/binary/sharp
sharp_libvips_binary_host=https://registry.npmmirror.com/-/binary/sharp-libvips
6.2 图标与截图自动化
- 图标:
scripts/make-icon.js用 sharp 把assets/icon.svg栅格化为build/icon.png(1024×1024),纳入npm run icon流程。 - 截图:
scripts/shot.js启动独立 Electron 实例加载真实index.html,注册最小 IPC 桩(store:get/set、open-external、set-native-theme)后capturePage()输出build/screenshot.png。实践中的关键坑:必须disableHardwareAcceleration()并加--disable-gpu,否则 GPU 进程崩溃;且必须提供最小 IPC 桩,否则渲染进程启动中断、窗格不渲染。
6.3 打包与分发
electron-builder 产出:
- Windows:
AI聚合问答工具-&lt;版本&gt;-win-x64.exe(NSIS 安装版)+-portable.exe(免安装便携版); - macOS:
*.dmg(x64 / arm64 通用)。
跨平台限制:Electron 不支持在单一系统交叉编译另一系统的安装包,macOS 包必须在 Mac 上执行
npm run dist:mac或通过 CI 构建。仓库已提供 GitHub Actions 工作流,推送v*tag 自动构建双平台。
6.4 版本管理与协作
代码托管于 Gitee(tyerp/ai-aggregated-qa-tool),HTTPS + 账号密码推送;.gitignore 排除 node_modules/、release/ 等体积较大的产物目录,仅纳入源码、配置与 build/ 资源。
7. 关键问题与解决方案
| 问题 | 现象 | 解决 |
|---|---|---|
| 运行态退化 | 环境 `ELECTRON_RUN_AS_NODE=1` 使 Electron 退化成 Node,`app is undefined` | `start` 脚本先 `delete process.env.ELECTRON_RUN_AS_NODE` 再拉起 |
| GPU/沙箱受限 | 虚拟桌面/服务器上 GPU 进程 FATAL,整体闪退 | `disableHardwareAcceleration()` + `--no-sandbox --disable-gpu --disable-software-rasterizer` |
| 后台进程被回收 | 用 `&` 后台启动随 shell 结束被回收,误判启动失败 | 改用守护方式常驻拉起 |
| 跨平台打包 | Windows 无法产出 macOS 包 | 明确在 Mac 或 CI 构建,文档标注限制 |
| 截图窗格空白 | 渲染进程缺 IPC 桩,中断启动 | `shot.js` 补齐最小 IPC 桩后三栏正常渲染 |
8. 安全与合规
- 进程隔离:
contextIsolation: true、nodeIntegration: false,预加载仅暴露必要 API; - 外部跳转白名单:
open-external仅放行http(s); - 最小权限:不索取任何 API Key、不读取用户隐私文件,所有数据存于本机
userData。
9. 总结与展望
「AI聚合问答工具」用"轻壳层 + 网页注入"的组合,以极低的维护成本实现了多 AI 并排对比这一高频诉求。其可复用经验有三:
- 复用网页优于调用 API——在能力快速迭代的大模型领域,嵌入官方网页比适配 API 更稳更省;
- 缓存与复用是流畅体验的底座——窗格实例与
webview的复用,让"切布局/切主题不刷新对话"成为现实; - 工程化前置——镜像源、图标/截图脚本、CI 打包、
.gitignore设计,决定了项目能否稳定交付。
后续可演进方向:补齐 macOS 构建与签名、提示词跨设备同步、可拖拽自定义布局、更多站点适配,以及把"变量表单"能力下沉到发送流程。