11 Commits
v1 ... v1.5

7 changed files with 1254 additions and 306 deletions

View File

@@ -13,6 +13,8 @@ iFlow 设置编辑器是一个基于 Electron + Vue 3 的桌面应用程序,
| Vite | ^8.0.8 | 构建工具 |
| @icon-park/vue-next | ^1.4.2 | 图标库 |
| @vitejs/plugin-vue | ^6.0.6 | Vue 插件 |
| concurrently | ^8.2.2 | 并发执行工具 |
| electron-builder | ^24.13.3 | 应用打包工具 |
## 项目结构
@@ -26,6 +28,9 @@ iflow-settings-editor/
├── src/
│ ├── main.js # Vue 入口
│ └── App.vue # 主组件 (所有业务逻辑)
├── build/ # 构建资源 (图标等)
├── release/ # 打包输出目录
└── screenshots/ # 截图资源
```
## 核心架构
@@ -42,6 +47,12 @@ window.electronAPI = {
loadSettings: () => ipcRenderer.invoke('load-settings'),
saveSettings: (data) => ipcRenderer.invoke('save-settings', data),
showMessage: (options) => ipcRenderer.invoke('show-message', options),
listApiProfiles: () => ipcRenderer.invoke('list-api-profiles'),
switchApiProfile: (profileName) => ipcRenderer.invoke('switch-api-profile', profileName),
createApiProfile: (name) => ipcRenderer.invoke('create-api-profile', name),
deleteApiProfile: (name) => ipcRenderer.invoke('delete-api-profile', name),
renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName),
duplicateApiProfile: (name, newName) => ipcRenderer.invoke('duplicate-api-profile', name, newName),
isMaximized: () => ipcRenderer.invoke('is-maximized'),
minimize: () => ipcRenderer.send('window-minimize'),
maximize: () => ipcRenderer.send('window-maximize'),
@@ -56,8 +67,9 @@ window.electronAPI = {
### API 配置切换
- 支持多环境配置: 默认配置、开发环境、预发布环境、生产环境
- 切换前检查未保存的更改
- 配置文件管理: 支持创建、编辑、复制、删除、重命名
- 单独保存每个环境的 API 配置到 `apiProfiles` 对象
- 切换配置时直接应用新配置,无需确认
## 可用命令
@@ -68,6 +80,13 @@ npm run build # 构建 Vue 应用到 dist 目录
npm start # 运行 Electron (需先build)
npm run electron:dev # 同时运行 Vite + Electron (开发模式)
npm run electron:start # 构建 + 运行 Electron (生产模式)
npm run pack # 打包应用(不生成安装包)
npm run build:win # 构建 Windows 安装包
npm run build:win64 # 构建 Windows x64 安装包
npm run build:win32 # 构建 Windows x86 安装包
npm run build:win-portable # 构建可移植版本
npm run build:win-installer # 构建 NSIS 安装包
npm run dist # 完整构建和打包
```
## 功能模块
@@ -79,8 +98,13 @@ npm run electron:start # 构建 + 运行 Electron (生产模式)
- **检查点保存**: 启用 / 禁用
### 2. API 配置 (API)
- **配置切换**: 支持多环境 (默认/开发/预发布/生产)
- **认证方式**: iFlow / API Key
- **配置列表**: 显示所有可用的 API 配置文件
- **配置切换**: 点击配置卡片直接切换,无需确认
- **创建配置**: 新建 API 配置文件
- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等
- **复制配置**: 基于现有配置创建新配置
- **删除配置**: 删除非默认配置
- **认证方式**: iFlow / API Key / OpenAI 兼容
- **API Key**: 密码输入框
- **Base URL**: API 端点
- **模型名称**: AI 模型标识
@@ -118,6 +142,23 @@ if (fs.existsSync(SETTINGS_FILE)) {
- `modified` - 是否已修改 (computed/diff)
- `currentSection` - 当前显示的板块
- `currentServerName` - 当前选中的 MCP 服务器
- `currentApiProfile` - 当前使用的 API 配置名称
- `apiProfiles` - 可用的 API 配置列表
### 数据初始化
`loadSettings` 函数中确保所有字段都有默认值:
- `language`: 'zh-CN'
- `theme`: 'Xcode'
- `bootAnimationShown`: true
- `checkpointing`: { enabled: true }
- `selectedAuthType`: 'iflow'
- `apiKey`: ''
- `baseUrl`: ''
- `modelName`: ''
- `searchApiKey`: ''
- `cna`: ''
- `apiProfiles`: { default: {} }
- `currentApiProfile`: 'default'
## 开发注意事项
@@ -128,10 +169,28 @@ if (fs.existsSync(SETTINGS_FILE)) {
5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常
## 图标使用
使用 `@icon-park/vue-next` 图标库:
```javascript
import { Refresh, Save, Config, Key, Server, Globe, Setting, Robot, Search, Add, Edit, Delete } from '@icon-park/vue-next';
import { Refresh, Save, Config, Key, Server, Globe, Setting, Robot, Search, Add, Edit, Delete, Exchange, Copy } from '@icon-park/vue-next';
```
## 打包配置
### Windows 平台
- **NSIS 安装包**: 支持 x64 架构
- **可移植版本**: 无需安装的独立可执行文件
- **安装器特性**:
- 允许修改安装目录
- 允许提升权限
- 创建桌面和开始菜单快捷方式
- 支持中文和英文界面
- 卸载时保留用户数据
### 输出目录
- `release/` - 所有打包输出的根目录
- 安装包命名: `iFlow Settings Editor-${version}-${arch}-setup.${ext}`
- 可移植版本命名: `iFlow Settings Editor-${version}-portable.${ext}`

145
README.md
View File

@@ -1,26 +1,33 @@
# iFlow Settings Editor
一个用于编辑 `C:\Users\MSI\.iflow\settings.json` 配置文件的桌面应用程序
一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\<USER>\.iflow\settings.json` 配置文件。
## 技术栈
- **Electron** - 桌面应用框架
- **Vue 3** - 前端框架 (组合式 API)
- **Vite** - 构建工具
- **@icon-park/vue-next** - 图标库
| 技术 | 版本 | 用途 |
|------|------|------|
| Electron | ^28.0.0 | 桌面应用框架 |
| Vue | ^3.4.0 | 前端框架 (组合式 API) |
| Vite | ^8.0.8 | 构建工具 |
| @icon-park/vue-next | ^1.4.2 | 图标库 |
| concurrently | ^8.2.2 | 并发执行工具 |
| electron-builder | ^24.13.3 | 应用打包工具 |
## 项目结构
```
settings-editor/
├── main.js # Electron 主进程
iflow-settings-editor/
├── main.js # Electron 主进程 (窗口管理、IPC、文件操作)
├── preload.js # 预加载脚本 (IPC 通信)
├── index.html # HTML 入口
├── package.json # 项目配置
├── vite.config.js # Vite 配置
├── index.html # HTML 入口
└── src/
── main.js # Vue 入口
└── App.vue # 主组件
├── src/
│ ├── main.js # Vue 入口
── App.vue # 主组件 (所有业务逻辑)
├── build/ # 构建资源 (图标等)
├── release/ # 打包输出目录
└── screenshots/ # 截图资源
```
## 快速开始
@@ -35,7 +42,7 @@ npm install
```bash
npm run dev # 启动 Vite 开发服务器
npm run electron:dev # 同时运行 Electron (需先执行 npm run dev)
npm run electron:dev # 同时运行 Electron + Vite
```
### 构建与运行
@@ -43,24 +50,118 @@ npm run electron:dev # 同时运行 Electron (需先执行 npm run dev)
```bash
npm run build # 构建 Vue 应用到 dist 目录
npm start # 运行 Electron 应用
npm run electron:start # 构建 + 运行 Electron
```
## 功能
### 打包应用
- **常规设置**: 语言、主题、启动动画、检查点保存
- **API 配置**: 认证方式、API Key、Base URL、模型名称、搜索服务
- **MCP 服务器管理**: 添加、编辑、删除服务器配置
```bash
npm run pack # 打包应用(不生成安装包)
npm run build:win # 构建 Windows 安装包 (NSIS)
npm run build:win64 # 构建 Windows x64 安装包
npm run build:win32 # 构建 Windows x86 安装包
npm run build:win-portable # 构建可移植版本
npm run build:dist # 完整构建和打包
```
## 截图说明
## 功能模块
应用采用 Windows 11 设计风格,包含:
- 自定义标题栏 (支持最小化/最大化/关闭)
- 侧边导航栏
- 表单编辑区域
- 底部状态栏
### 1. 常规设置 (General)
![主界面](screenshots/main.png)
配置应用程序的常规选项:
- **语言**: zh-CN / en-US / ja-JP
- **主题**: Xcode / Dark / Light / Solarized Dark
- **启动动画**: 已显示 / 未显示
- **检查点保存**: 启用 / 禁用
### 2. API 配置 (API)
管理多个环境的 API 配置:
- **配置列表**: 显示所有可用的 API 配置文件
- **配置切换**: 点击配置卡片直接切换
- **创建配置**: 新建 API 配置文件
- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等
- **复制配置**: 基于现有配置创建新配置
- **删除配置**: 删除非默认配置
- **认证方式**: iFlow / API Key / OpenAI 兼容
- **API Key**: 密码输入框
- **Base URL**: API 端点
- **模型名称**: AI 模型标识
- **搜索 API Key**: 搜索服务认证
- **CNA**: CNA 标识
### 3. MCP 服务器管理 (MCP)
管理 Model Context Protocol 服务器配置:
- **服务器列表**: 显示所有已配置的服务器
- **添加服务器**: 创建新的 MCP 服务器配置
- **编辑服务器**: 修改现有服务器的配置
- **删除服务器**: 移除服务器配置
- **服务器配置项**:
- 名称
- 描述
- 命令
- 工作目录
- 参数 (每行一个)
- 环境变量 (JSON 格式)
## 核心架构
### 进程模型
- **Main Process (main.js)**: Electron 主进程处理窗口管理、IPC 通信、文件系统操作
- **Preload (preload.js)**: 通过 `contextBridge.exposeInMainWorld` 暴露安全 API
- **Renderer (Vue)**: 渲染进程,只通过 preload 暴露的 API 与主进程通信
### 窗口配置
- 窗口尺寸: 1100x750最小尺寸: 900x600
- 无边框窗口 (frame: false),自定义标题栏
- 开发模式加载 `http://localhost:5173`,生产模式加载 `dist/index.html`
### 安全配置
- `contextIsolation: true` - 隔离上下文
- `nodeIntegration: false` - 禁用 Node.js
- `webSecurity: false` - 仅开发环境解决 CSP 问题
## 打包配置
### Windows 平台
- **NSIS 安装包**: 支持 x64 架构
- **可移植版本**: 无需安装的独立可执行文件
- **安装器特性**:
- 允许修改安装目录
- 允许提升权限
- 创建桌面和开始菜单快捷方式
- 支持中文和英文界面界面
- 卸载时保留用户数据
### 输出目录
- `release/` - 所有打包输出的根目录
- 安装包命名: `iFlow Settings Editor-${version}-${arch}-setup.${ext}`
- 可移植版本命名: `iFlow Settings Editor-${version}-portable.${ext}`
## 注意事项
- `webSecurity: false` 仅用于开发环境解决 CSP 问题
- 保存设置时会自动创建备份 (`settings.json.bak`)
- MCP 服务器参数每行一个,环境变量支持 JSON 格式
- API 配置切换时会直接应用新配置,未保存的更改会被替换
## 开发注意事项
1. **修改检测**: 通过 `watch(settings, () => { modified.value = true }, { deep: true })` 深度监听
2. **服务器编辑**: 使用 DOM 操作收集表单数据
3. **MCP 参数**: 每行一个参数,通过换行分割
4. **环境变量**: 支持 JSON 格式输入
5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常
## 许可证
MIT License

90
main.js
View File

@@ -1,20 +1,14 @@
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const path = require('path')
const fs = require('fs')
console.log('main.js loaded')
console.log('app.getPath("home"):', app.getPath('home'))
const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json')
console.log('SETTINGS_FILE:', SETTINGS_FILE)
let mainWindow
const isDev = process.argv.includes('--dev')
function createWindow() {
console.log('Creating window...')
mainWindow = new BrowserWindow({
width: 1100,
height: 750,
@@ -32,7 +26,6 @@ function createWindow() {
webSecurity: false,
},
})
console.log('Loading index.html...')
if (isDev) {
mainWindow.loadURL('http://localhost:5173')
@@ -40,39 +33,31 @@ function createWindow() {
mainWindow.loadFile(path.join(__dirname, 'dist', 'index.html'))
}
console.log('index.html loading initiated')
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
console.error('Failed to load:', errorCode, errorDescription)
})
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
console.log('Console [' + level + ']:', message)
})
mainWindow.once('ready-to-show', () => {
console.log('Window ready to show')
mainWindow.show()
})
mainWindow.on('closed', () => {
mainWindow = null
})
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})
// Window controls
ipcMain.on('window-minimize', () => mainWindow.minimize())
ipcMain.on('window-maximize', () => {
@@ -83,12 +68,9 @@ ipcMain.on('window-maximize', () => {
}
})
ipcMain.on('window-close', () => mainWindow.close())
ipcMain.handle('is-maximized', () => mainWindow.isMaximized())
// API 配置相关的字段
const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna']
// 读取设置文件
function readSettings() {
if (!fs.existsSync(SETTINGS_FILE)) {
@@ -97,7 +79,6 @@ function readSettings() {
const data = fs.readFileSync(SETTINGS_FILE, 'utf-8')
return JSON.parse(data)
}
// 写入设置文件
function writeSettings(data) {
if (fs.existsSync(SETTINGS_FILE)) {
@@ -106,31 +87,31 @@ function writeSettings(data) {
}
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf-8')
}
// 获取 API 配置列表
ipcMain.handle('list-api-profiles', async () => {
try {
const settings = readSettings()
if (!settings) {
return { success: false, error: '配置文件不存在', profiles: [], currentProfile: '' }
return { success: true, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
}
const profiles = settings.apiProfiles || {}
// 确保至少有 default 配置
if (Object.keys(profiles).length === 0) {
profiles.default = {}
}
const profileList = Object.keys(profiles).map(name => ({
name,
isDefault: name === 'default'
isDefault: name === 'default',
}))
return {
success: true,
profiles: profileList,
currentProfile: settings.currentApiProfile || 'default'
currentProfile: settings.currentApiProfile || 'default',
}
} catch (error) {
return { success: false, error: error.message, profiles: [], currentProfile: '' }
return { success: false, error: error.message, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
}
})
// 切换 API 配置
ipcMain.handle('switch-api-profile', async (event, profileName) => {
try {
@@ -138,12 +119,10 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
if (!settings) {
return { success: false, error: '配置文件不存在' }
}
const profiles = settings.apiProfiles || {}
if (!profiles[profileName]) {
return { success: false, error: `配置 "${profileName}" 不存在` }
}
// 保存当前配置到 apiProfiles如果当前配置存在
const currentProfile = settings.currentApiProfile || 'default'
if (profiles[currentProfile]) {
@@ -155,7 +134,6 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
}
profiles[currentProfile] = currentConfig
}
// 从 apiProfiles 加载新配置到主字段
const newConfig = profiles[profileName]
for (const field of API_FIELDS) {
@@ -163,18 +141,14 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
settings[field] = newConfig[field]
}
}
settings.currentApiProfile = profileName
settings.apiProfiles = profiles
writeSettings(settings)
return { success: true, data: settings }
} catch (error) {
return { success: false, error: error.message }
}
})
// 创建新的 API 配置
ipcMain.handle('create-api-profile', async (event, name) => {
try {
@@ -182,7 +156,6 @@ ipcMain.handle('create-api-profile', async (event, name) => {
if (!settings) {
return { success: false, error: '配置文件不存在' }
}
if (!settings.apiProfiles) {
settings.apiProfiles = { default: {} }
// 初始化 default 配置
@@ -192,11 +165,9 @@ ipcMain.handle('create-api-profile', async (event, name) => {
}
}
}
if (settings.apiProfiles[name]) {
return { success: false, error: `配置 "${name}" 已存在` }
}
// 复制当前配置到新配置
const newConfig = {}
for (const field of API_FIELDS) {
@@ -205,15 +176,12 @@ ipcMain.handle('create-api-profile', async (event, name) => {
}
}
settings.apiProfiles[name] = newConfig
writeSettings(settings)
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 删除 API 配置
ipcMain.handle('delete-api-profile', async (event, name) => {
try {
@@ -221,19 +189,15 @@ ipcMain.handle('delete-api-profile', async (event, name) => {
if (!settings) {
return { success: false, error: '配置文件不存在' }
}
if (name === 'default') {
return { success: false, error: '不能删除默认配置' }
}
const profiles = settings.apiProfiles || {}
if (!profiles[name]) {
return { success: false, error: `配置 "${name}" 不存在` }
}
delete profiles[name]
settings.apiProfiles = profiles
// 如果删除的是当前配置,切换到 default
if (settings.currentApiProfile === name) {
settings.currentApiProfile = 'default'
@@ -245,15 +209,12 @@ ipcMain.handle('delete-api-profile', async (event, name) => {
}
}
}
writeSettings(settings)
return { success: true, data: settings }
} catch (error) {
return { success: false, error: error.message }
}
})
// 重命名 API 配置
ipcMain.handle('rename-api-profile', async (event, oldName, newName) => {
try {
@@ -261,36 +222,51 @@ ipcMain.handle('rename-api-profile', async (event, oldName, newName) => {
if (!settings) {
return { success: false, error: '配置文件不存在' }
}
if (oldName === 'default') {
return { success: false, error: '不能重命名默认配置' }
}
const profiles = settings.apiProfiles || {}
if (!profiles[oldName]) {
return { success: false, error: `配置 "${oldName}" 不存在` }
}
if (profiles[newName]) {
return { success: false, error: `配置 "${newName}" 已存在` }
}
profiles[newName] = profiles[oldName]
delete profiles[oldName]
settings.apiProfiles = profiles
if (settings.currentApiProfile === oldName) {
settings.currentApiProfile = newName
}
writeSettings(settings)
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// 复制 API 配置
ipcMain.handle('duplicate-api-profile', async (event, sourceName, newName) => {
try {
const settings = readSettings()
if (!settings) {
return { success: false, error: '配置文件不存在' }
}
const profiles = settings.apiProfiles || {}
if (!profiles[sourceName]) {
return { success: false, error: `配置 "${sourceName}" 不存在` }
}
if (profiles[newName]) {
return { success: false, error: `配置 "${newName}" 已存在` }
}
// 深拷贝配置
profiles[newName] = JSON.parse(JSON.stringify(profiles[sourceName]))
settings.apiProfiles = profiles
writeSettings(settings)
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
// IPC Handlers
ipcMain.handle('load-settings', async () => {
try {
@@ -304,7 +280,6 @@ ipcMain.handle('load-settings', async () => {
return { success: false, error: error.message, data: null }
}
})
ipcMain.handle('save-settings', async (event, data) => {
try {
// 保存时同步更新 apiProfiles 中的当前配置
@@ -312,7 +287,6 @@ ipcMain.handle('save-settings', async (event, data) => {
if (!data.apiProfiles) {
data.apiProfiles = {}
}
// 更新当前配置到 apiProfiles
const currentConfig = {}
for (const field of API_FIELDS) {
@@ -321,14 +295,12 @@ ipcMain.handle('save-settings', async (event, data) => {
}
}
data.apiProfiles[currentProfile] = currentConfig
writeSettings(data)
return { success: true }
} catch (error) {
return { success: false, error: error.message }
}
})
ipcMain.handle('show-message', async (event, { type, title, message }) => {
return dialog.showMessageBox(mainWindow, { type, title, message })
})

View File

@@ -1,9 +1,12 @@
{
"name": "iflow-settings-editor",
"version": "1.0.0",
"description": "iFlow Settings Editor - Vue 3 + Electron",
"version": "1.5.0",
"description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。",
"main": "main.js",
"author": "iFlow",
"author": "上海潘哆呐科技有限公司",
"repository": {
"url": "https://git.pandorastudio.cn/product/iFlow-Settings-Editor-GUI.git"
},
"license": "MIT",
"scripts": {
"dev": "vite",
@@ -22,7 +25,7 @@
"build": {
"appId": "com.iflow.settings-editor",
"productName": "iFlow Settings Editor",
"copyright": "Copyright © 2024 iFlow",
"copyright": "Copyright © 2025 上海潘哆呐科技有限公司",
"directories": {
"output": "release",
"buildResources": "build"

View File

@@ -17,5 +17,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
switchApiProfile: (profileName) => ipcRenderer.invoke('switch-api-profile', profileName),
createApiProfile: (name) => ipcRenderer.invoke('create-api-profile', name),
deleteApiProfile: (name) => ipcRenderer.invoke('delete-api-profile', name),
renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName)
renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName),
duplicateApiProfile: (sourceName, newName) => ipcRenderer.invoke('duplicate-api-profile', sourceName, newName)
})

BIN
screenshots/main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because it is too large Load Diff