AionUi:与AI终端代理协作的跨平台桌面界面
AionUi 是一个基于 Electron 框架构建的跨平台桌面应用,旨在为用户提供一个统一、可视化的图形界面,用于与多种 AI 终端代理(例如 Gemini CLI、Claude Code、Qwen Code、Goose CLI、Auggie 等)进行协作。它遵循模块化架构原则,优先考虑用户体验、本地数据安全和开发者可维护性。


✨ 项目概述
AionUi 是一个基于 Electron 框架构建的跨平台桌面应用,旨在为用户提供一个统一、可视化的图形界面,用于与多种 AI 终端代理(例如 Gemini CLI、Claude Code、Qwen Code、Goose CLI、Auggie 等)进行协作。它遵循模块化架构原则,优先考虑用户体验、本地数据安全和开发者可维护性。
✨ 核心特性
- 多AI代理集成:支持通过标准化适配器连接多种AI终端代理,每个代理可独立管理和配置。
- 跨平台兼容:支持 macOS、Windows 和 Linux 操作系统。
- 模块化架构:采用桥接模式处理进程间通信,核心功能(如对话框、文件系统、会话管理)均实现为独立的、可测试的模块。
- 可视化交互界面:提供直观的聊天式界面,支持文件拖放、多会话管理和工作区集成。
- 本地数据安全:对话历史和设置默认存储在本地,API密钥经加密管理,未经用户明确同意不传输数据。
- WebUI模式:可通过嵌入式Web服务器启动,允许通过浏览器远程访问应用。
- 开发者友好:采用 TypeScript 保证类型安全,配置了 ESLint、Prettier、Jest 等工具确保代码质量和一致性,并集成了 Git Hooks 和规范化的提交信息格式。
- 丰富的技能库(Skills):内置支持 PDF、PPTX、DOCX、XLSX 等文档的创建、编辑与分析,并提供诸如“Manus风格文件规划”、“HUMAN 3.0 发展教练”等多种工作流技能。
🚀 安装指南
环境要求
- Node.js: (版本建议参考
package.json中的engines字段) - npm 或 yarn
- 系统: macOS / Windows / Linux
安装步骤
-
克隆仓库
git clone https://github.com/iOfficeAI/AionUi.git cd AionUi -
安装依赖
npm install安装过程会自动运行
postinstall脚本,处理原生模块的安装。 -
开发环境运行
npm start这会启动 Electron 应用,并运行在开发模式下(支持热重载)。
-
构建与打包
项目使用 Electron Forge 和 electron-builder 进行构建。- 开发构建:
npm run build - 生产环境打包:
特定平台或架构的打包脚本可在npm run distpackage.json的scripts部分找到,如dist:win、dist:mac、dist:linux。
- 开发构建:
原生模块重建
项目依赖一些原生模块(如 better-sqlite3、bcrypt 等)。跨架构构建或特定平台下,脚本会自动处理重建。如需手动强制重建,可设置环境变量:
FORCE_NATIVE_REBUILD=true npm run dist
📖 使用说明
基础使用
-
启动应用:
- 桌面模式:直接运行可执行文件或使用
npm start。 - WebUI 模式:
然后通过浏览器访问# Windows "C:\Program Files\AionUi\AionUi.exe" --webui # macOS /Applications/AionUi.app/Contents/MacOS/AionUi --webuihttp://localhost:3000(端口可能根据配置变化)。
- 桌面模式:直接运行可执行文件或使用
-
连接AI代理:
应用启动时会自动检测系统中已安装的、支持的AI CLI工具(如Gemini CLI, Claude Code等)。你可以在设置界面查看和管理已连接的代理。 -
开始协作:
在主界面的聊天窗口中输入指令,选择你想要协作的AI代理,即可开始工作。支持文件拖拽上传、多轮对话和上下文管理。
技能(Skills)使用
AionUi 集成了强大的 Cowork 模式,支持通过预定义的“技能”执行复杂任务。当你的请求中包含特定关键词时,对应的技能会被自动激活。
例如:
- 处理 PDF 文件时,可使用
pdf技能进行合并、拆分、填写表单或转换图片。 - 需要创建 PPT 时,可使用
pptx技能,遵循html2pptx.js或pptxgenjs的工作流生成幻灯片。 - 需要进行复杂项目规划时,可激活 “Planning with Files - Manus风格文件规划” 技能,它将引导你创建
task_plan.md、findings.md和progress.md文件来系统化管理任务。
开发与配置
-
代码风格:项目使用 ESLint 和 Prettier。在提交代码前,Git Hooks 会自动运行检查和格式化。
# 手动运行代码检查 npm run lint # 自动修复可修复的问题 npm run lint:fix # 格式化代码 npm run format -
环境变量:可以通过环境变量覆盖开发服务器端口,例如:
AIONUI_DEV_PORT=4000 npm start
💻 核心代码
1. 主进程入口 (main.ts)
此文件是 Electron 应用的主进程入口,负责初始化应用、创建浏览器窗口、设置全局错误处理、启动 Web 服务器和初始化与 AI 代理的通信桥梁。
/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import './utils/configureChromium';
import { app, BrowserWindow, screen } from 'electron';
import fixPath from 'fix-path';
import * as fs from 'fs';
import * as path from 'path';
import { initMainAdapterWithWindow } from './adapter/main'; // 初始化主进程与渲染进程的通信桥梁
import { ipcBridge } from './common';
import { initializeProcess } from './process';
import { initializeAcpDetector } from './process/bridge'; // 初始化 ACP (AI 代理协议) 检测器
import { registerWindowMaximizeListeners } from './process/bridge/windowControlsBridge';
import WorkerManage from './process/WorkerManage';
import { startWebServer } from './webserver'; // 启动 WebUI 模式的服务器
import { SERVER_CONFIG } from './webserver/config/constants';
import { applyZoomToWindow } from './process/utils/zoom';
// 处理 Windows 安装器事件
import electronSquirrelStartup from 'electron-squirrel-startup';
// 修复 macOS/Linux 下 GUI 应用的 PATH 环境变量
if (process.platform === 'darwin' || process.platform === 'linux') {
fixPath();
}
// 处理 Squirrel 启动事件 (Windows)
if (electronSquirrelStartup) {
app.quit();
}
// 全局未捕获异常处理器
process.on('uncaughtException', (_error) => {
// 生产环境可记录日志或上报
if (process.env.NODE_ENV !== 'development') {
// TODO: 添加错误处理逻辑
}
});
// 应用准备就绪后创建窗口
app.whenReady().then(() => {
createWindow();
initializeAcpDetector(); // 检测可用的 AI 代理
// ... 其他初始化逻辑
});
// 创建浏览器窗口函数
function createWindow(): void {
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
const mainWindow = new BrowserWindow({
width: Math.min(1440, width),
height: Math.min(900, height),
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // 预加载脚本
contextIsolation: true,
nodeIntegration: false,
},
});
// 加载 Webpack 打包的入口
mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
// 初始化与该窗口的通信桥梁
initMainAdapterWithWindow(mainWindow);
}
2. 预加载脚本 (preload.ts)
预加载脚本在渲染进程加载网页之前运行,负责将安全的、受控的 Electron API 暴露给渲染进程,是实现进程间通信(IPC)的关键。
/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import { contextBridge, ipcRenderer, webUtils } from 'electron';
import { ADAPTER_BRIDGE_EVENT_KEY } from './adapter/constant';
// 将 `electronAPI` 对象安全地暴露给渲染进程的 window 对象
contextBridge.exposeInMainWorld('electronAPI', {
// 发送消息到主进程
emit: (name: string, data: any) => {
return ipcRenderer
.invoke(
ADAPTER_BRIDGE_EVENT_KEY,
JSON.stringify({
name: name,
data: data,
})
)
.catch((error) => {
console.error('IPC invoke error:', error);
throw error;
});
},
// 监听来自主进程的消息
on: (callback: any) => {
const handler = (event: any, value: any) => {
callback({ event, value });
};
ipcRenderer.on(ADAPTER_BRIDGE_EVENT_KEY, handler);
return () => {
ipcRenderer.off(ADAPTER_BRIDGE_EVENT_KEY, handler);
};
},
// 获取拖拽文件的绝对路径(用于 WebUI 模式下的文件选择)
getPathForFile: (file: File) => webUtils.getPathForFile(file),
});
3. AI代理连接与适配器 (AcpConnection.ts)
此类负责与支持 ACP (AI 代理协议) 的外部 CLI 工具建立连接、管理 JSON-RPC 通信和处理权限请求,是集成多种 AI 代理的核心。
/**
* @license
* Copyright 2025 AionUi (aionui.com)
* SPDX-License-Identifier: Apache-2.0
*/
import type { AcpBackend, AcpIncomingMessage, AcpMessage, AcpNotification, AcpPermissionRequest, AcpRequest, AcpResponse, AcpSessionUpdate } from '@/types/acpTypes';
import { ACP_METHODS, JSONRPC_VERSION } from '@/types/acpTypes';
import type { ChildProcess, SpawnOptions } from 'child_process';
import { spawn } from 'child_process';
import { promises as fs } from 'fs';
import path from 'path';
interface PendingRequest<T = unknown> {
resolve: (value: T) => void;
reject: (error: Error) => void;
timeoutId?: NodeJS.Timeout;
method: string;
isPaused: boolean;
startTime: number;
timeoutDuration: number;
}
/**
* 创建用于生成 ACP CLI 进程的配置。
*/
export function createGenericSpawnConfig(cliPath: string, workingDir: string, acpArgs?: string[], customEnv?: Record<string, string>) {
const isWindows = process.platform === 'win32';
const env = { ...process.env, ...customEnv };
const effectiveAcpArgs = acpArgs && acpArgs.length > 0 ? acpArgs : ['--experimental-acp'];
let spawnCommand: string;
let spawnArgs: string[];
if (cliPath.startsWith('npx ')) {
// 处理 npm 包命令
const parts = cliPath.split(' ');
spawnCommand = isWindows ? 'npx.cmd' : 'npx';
spawnArgs = [...parts.slice(1), ...effectiveAcpArgs];
} else {
// 处理直接路径或简单命令
spawnCommand = cliPath;
spawnArgs = effectiveAcpArgs;
}
const options: SpawnOptions = {
cwd: workingDir,
env,
stdio: ['pipe', 'pipe', 'pipe'], // 标准输入、输出、错误流
};
return { spawnCommand, spawnArgs, options };
}
/**
* ACP 连接管理类。
*/
export class AcpConnection {
private child: ChildProcess | null = null;
private nextId = 1;
private pendingRequests = new Map<number | string, PendingRequest>();
// ... 其他私有属性和方法
/**
* 启动 ACP 会话。
*/
async startSession(cliPath: string, workingDir: string, acpArgs?: string[], customEnv?: Record<string, string>): Promise<void> {
const { spawnCommand, spawnArgs, options } = createGenericSpawnConfig(cliPath, workingDir, acpArgs, customEnv);
return new Promise((resolve, reject) => {
this.child = spawn(spawnCommand, spawnArgs, options);
this.child.stdout?.on('data', (data) => {
this.handleStdout(data.toString());
});
this.child.stderr?.on('data', (data) => {
console.error(`[ACP STDERR] ${data.toString()}`);
});
this.child.on('error', (error) => {
reject(new Error(`Failed to spawn ACP process: ${error.message}`));
});
this.child.on('close', (code) => {
const message = `ACP process exited with code ${code}`;
this.rejectAllPendingRequests(new Error(message));
});
// 发送初始化请求以验证连接
this.sendRequest('initialize', {
protocolVersion: '0.1.0',
capabilities: { tools: [] },
})
.then(resolve)
.catch(reject);
});
}
/**
* 发送 JSON-RPC 请求。
*/
private sendRequest(method: string, params?: unknown, timeoutMs = 30000): Promise<unknown> {
return new Promise((resolve, reject) => {
const id = this.nextId++;
const request: AcpRequest = {
jsonrpc: JSONRPC_VERSION,
id,
method,
params,
};
const timeoutId = setTimeout(() => {
this.pendingRequests.delete(id);
reject(new Error(`Request ${method} timed out after ${timeoutMs}ms`));
}, timeoutMs);
this.pendingRequests.set(id, {
resolve,
reject,
timeoutId,
method,
isPaused: false,
startTime: Date.now(),
timeoutDuration: timeoutMs,
});
if (this.child && this.child.stdin && !this.child.stdin.destroyed) {
this.child.stdin.write(JSON.stringify(request) + '\n');
} else {
reject(new Error('Process stdin is not available'));
}
});
}
/**
* 处理来自子进程标准输出的数据(解析 JSON-RPC 消息)。
*/
private handleStdout(data: string): void {
try {
const message = JSON.parse(data) as AcpIncomingMessage;
if ('id' in message && message.id !== undefined) {
// 处理响应
this.handleResponse(message as AcpResponse);
} else if ('method' in message) {
// 处理通知或请求(如权限请求)
this.handleNotification(message as AcpNotification);
}
} catch (error) {
console.warn('[ACP] Failed to parse stdout:', data, error);
}
}
// ... 处理响应、通知、权限请求等其他方法
}
XfkHivgPackSeBYb8h6edGRK+FQI9gdrv8FBRI2tlQs=
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)
更多推荐



所有评论(0)