From bb45ff45125a34208fdf73a461ab3f09aecc892c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=81=E6=B6=9B?= Date: Sat, 18 Apr 2026 22:32:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E6=A1=A3:=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=87=E6=A1=A3=EF=BC=8C=E6=8F=90=E5=8D=87?= =?UTF-8?q?=E5=8F=AF=E8=AF=BB=E6=80=A7=E5=92=8C=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E7=A8=8B=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 530 +++++++++++------------------- README.md | 340 ++++++++----------- screenshots/acrylic-slider-jp.jpg | Bin 0 -> 21593 bytes 3 files changed, 344 insertions(+), 526 deletions(-) create mode 100644 screenshots/acrylic-slider-jp.jpg diff --git a/AGENTS.md b/AGENTS.md index 2204088..b7f5363 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,363 +1,233 @@ -# iFlow Settings Editor - Agent 文档 +# iFlow Settings Editor - AI Context ## 项目概述 -iFlow 设置编辑器是一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\\.iflow\settings.json` 配置文件。 +**iFlow 设置编辑器** 是一个用于编辑 iFlow CLI 配置文件 (`~/.iflow/settings.json`) 的桌面应用程序,采用 Electron + Vue 3 技术栈构建。 -## 技术栈 +### 技术栈 -| 技术 | 版本 | 用途 | -|------|------|------| -| Electron | ^28.0.0 | 桌面应用框架 | -| Vue | ^3.4.0 | 前端框架 (组合式 API) | -| 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 | 应用打包工具 | -| vitest | ^4.1.4 | 单元测试框架 | -| @vue/test-utils | ^2.4.6 | Vue 组件测试工具 | -| happy-dom | ^20.9.0 | 浏览器环境模拟 | -| vue-i18n | ^9.14.5 | 国际化支持 | -| less | ^4.6.4 | CSS 预处理器 | +| 类别 | 技术 | +|------|------| +| 框架 | Electron 28 + Vue 3.4 | +| 构建工具 | Vite 8 | +| CSS 预处理器 | Less | +| 国际化 | vue-i18n 9 | +| 测试框架 | Vitest 4 + happy-dom | +| 打包工具 | electron-builder 24 | +| 图标库 | @icon-park/vue-next | + +### 核心架构 + +``` +┌─────────────────────────────────────────────────────┐ +│ Electron 主进程 │ +│ main.js - 窗口管理、系统托盘、IPC 通信、文件读写 │ +│ preload.js - 安全桥接 (contextBridge) │ +└─────────────────────────────────────────────────────┘ + ↕ IPC +┌─────────────────────────────────────────────────────┐ +│ Vue 3 渲染进程 │ +│ src/App.vue - 根组件 │ +│ ├── TitleBar.vue - 自定义标题栏 │ +│ ├── SideBar.vue - 侧边导航 │ +│ ├── Footer.vue - 底部栏 │ +│ └── views/ │ +│ ├── GeneralSettings.vue - 基础设置 │ +│ ├── ApiConfig.vue - API 配置管理 │ +│ └── McpServers.vue - MCP 服务器管理 │ +└─────────────────────────────────────────────────────┘ +``` + +### 配置文件 + +- **路径**: `~/.iflow/settings.json` +- **编码**: UTF-8 JSON +- **备份**: 自动生成 `.bak` 备份文件 + +## 开发命令 + +```bash +# 安装依赖 +npm install + +# 开发模式 (Vite Dev Server) +npm run dev + +# Electron 开发模式 (并行启动 Vite + Electron) +npm run electron:dev + +# 构建生产版本 +npm run build + +# 打包 Windows 安装包 (x64) +npm run build:win + +# 打包 Windows 便携版 +npm run build:win-portable + +# 打包 Windows 安装程序 (NSIS) +npm run build:win-installer + +# 运行测试 +npm run test + +# 测试 UI 模式 +npm run test:ui + +# 测试覆盖率 +npm run test:coverage + +# 单次运行测试 +npm run test:run +``` + +## 设计规范 + +### Windows 11 Fluent Design + +项目采用 Windows 11 Fluent Design 设计系统,核心规范: + +| 属性 | 规范 | +|------|------| +| 字体 | Segoe UI Variable, Segoe UI, system-ui | +| 等宽字体 | Cascadia Code, Consolas | +| 圆角 | 4px (sm) / 6px / 8px (lg) / 12px (xl) | +| 阴影 | 四级层次 (sm///lg/xl) | +| 过渡动画 | 0.1-0.2s ease, 0.15s cubic-bezier(0.4, 0, 0.2, 1) | + +### 主题系统 + +支持三种主题:`Light` (Xcode) / `Dark` / `Solarized Dark` + +CSS 变量定义在 `src/styles/global.less`,包括: +- `--bg-primary/secondary/elevated` - 背景层级 +- `--text-primary/secondary/tertiary` - 文本层级 +- `--accent` - 主题色 (Windows Blue) +- `--success/warning/danger/info` - 状态色 + +### 亚克力效果 + +支持可调节透明度的 Mica-inspired 亚克力效果: +- 背景透明度随 `acrylicIntensity` (0-100) 变化 +- 深色/浅色主题有独立的透明度计算逻辑 ## 项目结构 ``` -iflow-settings-editor/ -├── main.js # Electron 主进程 (窗口管理、IPC、文件操作、系统托盘) -├── preload.js # 预加载脚本 (contextBridge API) -├── index.html # HTML 入口 -├── package.json # 项目配置 -├── vite.config.js # Vite 配置 -├── vitest.config.js # Vitest 测试配置 -├── src/ -│ ├── main.js # Vue 入口 -│ ├── App.vue # 根组件 -│ ├── components/ # 可复用组件 -│ │ ├── ApiProfileDialog.vue -│ │ ├── Footer.vue -│ │ ├── InputDialog.vue -│ │ ├── MessageDialog.vue -│ │ ├── ServerPanel.vue -│ │ ├── SideBar.vue -│ │ └── TitleBar.vue -│ ├── views/ # 页面视图组件 -│ │ ├── ApiConfig.vue -│ │ ├── GeneralSettings.vue -│ │ └── McpServers.vue -│ ├── styles/ # 全局样式 -│ │ └── global.less -│ └── locales/ # 国际化资源 -│ ├── index.js -│ ├── en-US.js -│ └── ja-JP.js -├── build/ # 构建资源 (图标等) -├── dist/ # Vite 构建输出 -├── release/ # 打包输出目录 -└── screenshots/ # 截图资源 +src/ +├── main.js # Vue 应用入口 +├── App.vue # 根组件 +├── components/ +│ ├── TitleBar.vue # 标题栏 (窗口控制按钮) +│ ├── SideBar.vue # 侧边导航栏 +│ ├── Footer.vue # 底部栏 +│ ├── InputDialog.vue # 输入对话框 +│ ├── MessageDialog.vue # 消息对话框 +│ ├── ApiProfileDialog.vue # API 配置弹窗 +│ └── ServerPanel.vue # 服务器编辑面板 +├── views/ +│ ├── GeneralSettings.vue # 常规设置视图 +│ ├── ApiConfig.vue # API 配置视图 +│ └── McpServers.vue # MCP 服务器视图 +├── locales/ +│ ├── index.js # 中文 (zh-CN) +│ ├── en-US.js # 英文 +│ └── ja-JP.js # 日文 +└── styles/ + └── global.less # 全局样式 (Windows Fluent Design) ``` -## 核心架构 - -### 进程模型 -- **Main Process (main.js)**: Electron 主进程,处理窗口管理、IPC 通信、文件系统操作、系统托盘 -- **Preload (preload.js)**: 通过 `contextBridge.exposeInMainWorld` 暴露安全 API -- **Renderer (Vue)**: 渲染进程,只通过 preload 暴露的 API 与主进程通信 +## 关键模块 ### IPC 通信 + +**preload.js** 暴露的 API: + ```javascript -// preload.js 暴露的 API -window.electronAPI = { - // 基本设置操作 - loadSettings: () => ipcRenderer.invoke('load-settings'), - saveSettings: (data) => ipcRenderer.invoke('save-settings', data), - showMessage: (options) => ipcRenderer.invoke('show-message', options), +// 设置操作 +window.electronAPI.loadSettings() +window.electronAPI.saveSettings(data) - // 窗口控制 - isMaximized: () => ipcRenderer.invoke('is-maximized'), - minimize: () => ipcRenderer.send('window-minimize'), - maximize: () => ipcRenderer.send('window-maximize'), - close: () => ipcRenderer.send('window-close'), +// 窗口控制 +window.electronAPI.minimize() +window.electronAPI.maximize() +window.electronAPI.close() +window.electronAPI.isMaximized() - // API 配置管理(单文件内多配置) - 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: (sourceName, newName) => ipcRenderer.invoke('duplicate-api-profile', sourceName, newName), - - // 托盘事件监听 - onApiProfileSwitched: (callback) => { - ipcRenderer.on('api-profile-switched', (event, profileName) => callback(profileName)) - }, - - // 语言切换通知 - notifyLanguageChanged: () => ipcRenderer.send('language-changed') -} +// API 配置管理 +window.electronAPI.listApiProfiles() +window.electronAPI.switchApiProfile(name) +window.electronAPI.createApiProfile(name) +window.electronAPI.deleteApiProfile(name) +window.electronAPI.renameApiProfile(oldName, newName) +window.electronAPI.duplicateApiProfile(sourceName, newName) ``` -### 窗口配置 -- 窗口尺寸: 1100x750,最小尺寸: 900x600 -- 无边框窗口 (frame: false),自定义标题栏 -- 开发模式加载 `http://localhost:5173`,生产模式加载 `dist/index.html` -- **关闭窗口时隐藏到系统托盘**,双击托盘图标可重新显示 +### API 配置管理 + +配置文件内使用 `apiProfiles` 对象存储多个配置: + +```json +{ + "currentApiProfile": "default", + "apiProfiles": { + "default": { "apiKey": "...", "baseUrl": "..." }, + "production": { "apiKey": "...", "baseUrl": "..." } + } +} +``` ### 系统托盘 -- 托盘图标显示应用状态 -- 右键菜单支持: - - 显示主窗口 - - 切换 API 配置(显示所有配置列表,当前配置带勾选标记) - - 退出应用 + +- 窗口关闭时隐藏到托盘而非退出 +- 托盘菜单支持快速切换 API 配置 - 双击托盘图标显示主窗口 -### API 配置切换 -- 支持多环境配置: 默认配置,开发环境、预发布环境、生产环境 -- 配置文件管理: 支持创建、编辑、复制、删除、重命名 -- 单独保存每个环境的 API 配置到 `apiProfiles` 对象 -- 切换配置时直接应用新配置,无需确认 -- 支持从系统托盘快速切换配置 +## 代码风格 -## 可用命令 +### Vue 3 Composition API -```bash -npm install # 安装依赖 -npm run dev # 启动 Vite 开发服务器 -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 # 完整构建和打包 -npm run test # 运行所有测试(监听模式) -npm run test:run # 运行测试一次 -npm run test:ui # 运行测试 UI 界面 -npm run test:coverage # 生成测试覆盖率报告 +使用 ` ``` -## 功能模块 +### 样式规范 -### 1. 常规设置 (General) -- **语言**: zh-CN / en-US / ja-JP -- **主题**: Xcode / Dark / Solarized Dark -- **启动动画**: 已显示 / 未显示 -- **检查点保存**: 启用 / 禁用 +- 使用 Less 预处理器 +- 通过 CSS 变量 (`var(--xxx)`) 使用主题色 +- 组件样式使用 BEM-like 命名或直接使用功能类名 +- 动画使用 `@keyframes` 定义 -### 2. API 配置 (API) -- **配置列表**: 显示所有可用的 API 配置文件,带彩色图标和状态标记 -- **配置切换**: 点击配置卡片直接切换,无需确认 -- **创建配置**: 新建 API 配置文件(支持设置认证方式、API Key、Base URL 等) -- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等 -- **复制配置**: 基于现有配置创建新配置 -- **删除配置**: 删除非默认配置 -- **认证方式**: iFlow / API Key / OpenAI 兼容 -- **API Key**: 密码输入框 -- **Base URL**: API 端点 -- **模型名称**: AI 模型标识 -- **搜索 API Key**: 搜索服务认证 -- **CNA**: CNA 标识 +### 测试规范 -### 3. MCP 服务器管理 (MCP) -- 服务器列表展示(带描述信息) -- 添加/编辑/删除服务器 -- 侧边面板编辑界面 -- 服务器配置: 名称、描述、命令、工作目录、参数(每行一个)、环境变量(JSON) +- 测试文件命名: `*.test.js` 或 `*.spec.js` +- 使用 Vitest + @vue/test-utils +- DOM 测试环境: happy-dom +- 覆盖范围排除: node_modules, dist, release, build -## 关键实现细节 +## 快捷键与交互 -### 设置文件路径 -```javascript -const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json'); -``` +| 操作 | 说明 | +|------|------| +| 窗口关闭 | 隐藏到系统托盘 | +| 双击托盘 | 显示主窗口 | +| Ctrl+S | 自动保存设置 (通过 watch 监听) | -### 保存时自动备份 -```javascript -if (fs.existsSync(SETTINGS_FILE)) { - const backupPath = SETTINGS_FILE + '.bak'; - fs.copyFileSync(SETTINGS_FILE, backupPath); -} -``` +## 常见问题 -### 安全配置 -- `contextIsolation: true` - 隔离上下文 -- `nodeIntegration: false` - 禁用 Node.js -- `webSecurity: false` - 仅开发环境解决 CSP 问题 - -### Vue 组件状态管理 -- `settings` - 当前设置 (ref) -- `originalSettings` - 原始设置 (用于检测修改) -- `modified` - 是否已修改 (computed/diff) -- `currentSection` - 当前显示的板块 -- `currentServerName` - 当前选中的 MCP 服务器 -- `currentApiProfile` - 当前使用的 API 配置名称 -- `apiProfiles` - 可用的 API 配置列表 -- `isLoading` - 加载状态标志 - -### API 配置字段 -```javascript -const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna']; -``` - -### 数据初始化 -在 `loadSettings` 函数中确保所有字段都有默认值: -- `language`: 'zh-CN' -- `theme`: 'Xcode' -- `bootAnimationShown`: true -- `checkpointing`: { enabled: true } -- `selectedAuthType`: 'openai-compatible' -- `apiKey`: '' -- `baseUrl`: '' -- `modelName`: '' -- `searchApiKey`: '' -- `cna`: '' -- `apiProfiles`: { default: {} } -- `currentApiProfile`: 'default' -- `mcpServers`: {} - -## 设计系统 - -### Windows UI Kit - Fluent Design - -本项目采用 Windows 11 Fluent Design 设计规范,实现统一的视觉效果。 - -#### 主题变量 - -**Light 模式:** -```css -:root { - --bg-primary: rgba(243, 243, 243, 0.85); - --bg-secondary: rgba(255, 255, 255, 0.70); - --bg-tertiary: #ebebeb; - --bg-elevated: rgba(255, 255, 255, 0.95); - --text-primary: #1a1a1a; - --text-secondary: #5d5d5d; - --accent: #0078d4; - --accent-hover: #106ebe; - --border: #e0e0e0; - --control-fill: rgba(249, 249, 249, 0.85); -} -``` - -**Dark 模式:** -```css -.dark { - --bg-primary: #1f1f1f; - --bg-secondary: #2d2d2d; - --text-primary: #ffffff; - --accent: #60cdff; - --control-fill: #333333; -} -``` - -**Solarized Dark 模式:** -```css -.solarized-dark { - --bg-primary: #002b36; - --bg-secondary: #073642; - --text-primary: #839496; - --accent: #268bd2; -} -``` - -#### 设计原则 -- **Mica -inspired 层次感**: 使用半透明背景和分层深度 -- **圆角系统**: 4px / 6px / 8px / 12px 四级圆角 -- **阴影层次**: sm / md / lg / xl 四级阴影 -- **过渡动画**: 0.1s-0.2s 流畅曲线 -- **Segoe UI Variable 字体**: Windows 11 原生字体 - -## 测试框架 (Vitest) - -**测试配置**: -- 使用 Vitest 作为测试运行器 -- 使用 `@vue/test-utils` 进行 Vue 组件测试 -- 使用 `happy-dom` 作为浏览器环境模拟 -- 配置文件:`vitest.config.js` -- 全局变量启用:`globals: true` -- 覆盖率工具:`v8` -- 覆盖率报告格式:text、json、html - -**测试文件结构**: -``` -src/ -├── components/ -│ ├── Footer.test.js # Footer 组件测试 -│ ├── SideBar.test.js # 侧边栏测试 -│ └── TitleBar.test.js # 标题栏测试 -└── views/ - ├── ApiConfig.test.js # API 配置测试 - ├── GeneralSettings.test.js # 常规设置测试 - └── McpServers.test.js # MCP 服务器测试 -``` - -**测试命令**: -```bash -npm run test # 运行所有测试(监听模式) -npm run test:run # 运行测试一次 -npm run test:ui # 运行测试 UI 界面 (http://localhost:5174/__vitest__/) -npm run test:coverage # 生成测试覆盖率报告 -``` - -## 开发注意事项 - -1. **修改检测**: 通过 `watch(settings, () => { modified.value = true }, { deep: true })` 深度监听 -2. **服务器编辑**: 使用侧边面板 (Side Panel) 收集表单数据 -3. **MCP 参数**: 每行一个参数,通过换行分割 -4. **环境变量**: 支持 JSON 格式输入 -5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作 -6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中 -7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题 -8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常 -9. **托盘切换事件**: 监听 `onApiProfileSwitched` 处理托盘发起的配置切换 -10. **样式系统**: 使用 Windows UI Kit 设计系统,所有变量在 `global.less` 中定义 - -## 图标使用 - -使用 `@icon-park/vue-next` 图标库: -```javascript -import { Save, Config, Key, Server, Globe, Setting, Add, Edit, Delete, Exchange, Copy } from '@icon-park/vue-next'; -``` - -## 打包配置 - -### Windows 平台 -- **NSIS 安装包**: 支持 x64 架构 -- **可移植版本**: 无需安装的独立可执行文件 -- **安装器特性**: - - 允许修改安装目录 - - 允许提升权限 - - 创建桌面和开始菜单快捷方式 - - 支持中文和英文界面 (zh_CN, en_US) - - 卸载时保留用户数据 - -### 输出目录 -- `release/` - 所有打包输出的根目录 -- 安装包命名: `iFlow Settings Editor-${version}-${arch}-setup.${ext}` -- 可移植版本命名: `iFlow Settings Editor-${version}-portable.${ext}` - -## UI 组件 - -### 对话框类型 -- **输入对话框**: 用于重命名、复制等需要文本输入的场景 -- **确认对话框**: 用于删除等需要确认的操作 -- **消息对话框**: 显示 info/success/warning/error 四种类型,带图标 - -### 侧边面板 -- MCP 服务器编辑使用侧边面板 (从右侧滑入) -- API 配置编辑使用模态对话框 - -## 版本历史 - -| 版本 | 日期 | 主要变更 | -|------|------|----------| -| 1.6.0 | 2026-04-18 | 架构:重构样式系统,采用 Windows 11 Fluent Design 规范 | -| 1.5.1 | 2026-04-17 | 新增系统托盘功能,托盘快速切换 API 配置 | -| 1.5.0 | 2026-04-16 | 新增自定义消息对话框,API 配置重命名 | -| 1.4.0 | 2026-04-14 | 新增多环境配置文件管理 | -| 1.0.0 | 2026-04-14 | 项目初始化 | +1. **图标不显示**: 检查 `build/icon.ico` 是否存在 +2. **配置不保存**: 确认 `~/.iflow/settings.json` 目录可写 +3. **亚克力效果异常**: 检查 `acrylicIntensity` 值是否在 0-100 范围内 diff --git a/README.md b/README.md index 4c59077..75d5bfd 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,161 @@ # iFlow Settings Editor -一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\\.iflow\settings.json` 配置文件。 +一个用于编辑 iFlow CLI 配置文件的桌面应用程序。 + +![iFlow Settings Editor](./screenshots/main.png) + +## 功能特性 + +- 📝 **API 配置管理** - 支持多环境配置文件切换、创建、编辑、复制和删除 +- 🖥️ **MCP 服务器管理** - 便捷的 Model Context Protocol 服务器配置界面 +- 🎨 **Windows 11 设计风格** - 采用 Fluent Design 设计规范 +- 🌈 **多主题支持** - Light / Dark / Solarized Dark 三种主题 +- 🌍 **国际化** - 支持简体中文、English、日語 +- 💧 **亚克力效果** - 可调节透明度的现代视觉效果 +- 📦 **系统托盘** - 最小化到托盘,快速切换 API 配置 ## 技术栈 -| 技术 | 版本 | 用途 | -|------|------|------| -| 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 | 应用打包工具 | -| vue-i18n | ^9.14.5 | 国际化支持 | -| less | ^4.6.4 | CSS 预处理器 | +| 技术 | 版本 | +|------|------| +| Electron | 28.x | +| Vue | 3.4.x | +| Vite | 8.x | +| vue-i18n | 9.x | +| Less | 4.x | +| Vitest | 4.x | + +## 支持的系统 + +- Windows 10 / 11 (x64) + +## 安装 + +### 从源码运行 + +```bash +# 克隆项目 +git clone https://git.pandorastudio.cn/product/iFlow-Settings-Editor-GUI.git + +# 进入目录 +cd iFlow-Settings-Editor-GUI + +# 安装依赖 +npm install + +# 开发模式运行 +npm run electron:dev +``` + +### 构建安装包 + +```bash +# 构建 Windows 安装包 (x64) +npm run build:win + +# 构建便携版 +npm run build:win-portable + +# 构建 NSIS 安装程序 +npm run build:win-installer +``` + +构建完成后,安装包位于 `release/` 目录下。 + +## 使用说明 + +### 基础设置 + +在「常规」页面可以设置: + +- **语言** - 界面显示语言 +- **主题** - 视觉主题风格 +- **启动动画** - 控制应用启动时的动画显示 +- **检查点保存** - 开启/关闭自动保存功能 +- **亚克力效果** - 调节窗口背景透明度 + +### API 配置管理 + +在「API 配置」页面可以: + +- **切换配置** - 点击不同配置文件快速切换 +- **新建配置** - 创建新的 API 环境配置 +- **编辑配置** - 修改现有配置的认证方式、API Key、Base URL 等 +- **复制配置** - 复制现有配置创建新配置 +- **删除配置** - 删除不需要的配置(默认配置不可删除) + +支持的认证方式: +- iFlow +- API Key +- OpenAI 兼容 + +### MCP 服务器管理 + +在「MCP 服务器」页面可以: + +- **添加服务器** - 配置新的 MCP 服务器 +- **编辑服务器** - 修改服务器的命令、工作目录、参数等 +- **删除服务器** - 移除不需要的服务器 + +### 系统托盘 + +- 关闭窗口时,应用会最小化到系统托盘 +- 双击托盘图标可重新显示主窗口 +- 右键托盘菜单可快速切换 API 配置 + +## 配置文件 + +应用配置文件位于: + +``` +~/.iflow/settings.json +``` + +每次保存时会自动生成备份文件 `settings.json.bak`。 + +## 测试 + +```bash +# 运行测试 +npm run test + +# UI 模式测试 +npm run test:ui + +# 测试覆盖率 +npm run test:coverage + +# 单次运行测试 +npm run test:run +``` ## 项目结构 ``` -iflow-settings-editor/ -├── main.js # Electron 主进程 (窗口管理、IPC、文件操作、系统托盘) -├── preload.js # 预加载脚本 (contextBridge API) -├── index.html # HTML 入口 -├── package.json # 项目配置 +iFlow-Settings-Editor-GUI/ +├── main.js # Electron 主进程 +├── preload.js # 预加载脚本 +├── index.html # 入口 HTML ├── vite.config.js # Vite 配置 -├── vitest.config.js # Vitest 测试配置 -├── src/ -│ ├── main.js # Vue 入口 -│ ├── components/ # 可复用组件 -│ │ ├── ApiProfileDialog.vue -│ │ ├── Footer.vue -│ │ ├── InputDialog.vue -│ │ ├── MessageDialog.vue -│ │ ├── ServerPanel.vue -│ │ ├── SideBar.vue -│ │ └── TitleBar.vue -│ ├── views/ # 页面视图组件 -│ │ ├── ApiConfig.vue -│ │ ├── GeneralSettings.vue -│ │ └── McpServers.vue -│ └── App.vue # 主组件 (所有业务逻辑、UI 组件) -├── build/ # 构建资源 (图标等) +├── vitest.config.js # Vitest 测试配置 +├── build/ # 构建资源 ├── dist/ # Vite 构建输出 -├── release/ # 打包输出目录 -└── screenshots/ # 截图资源 +├── release/ # Electron Builder 输出 +├── screenshots/ # 应用截图 +└── src/ + ├── main.js # Vue 入口 + ├── App.vue # 根组件 + ├── components/ # 公共组件 + ├── views/ # 页面视图 + ├── locales/ # 国际化语言包 + └── styles/ # 全局样式 ``` -## 快速开始 - -### 安装依赖 - -```bash -npm install -``` - -### 开发模式 - -```bash -npm run dev # 启动 Vite 开发服务器 -npm run electron:dev # 同时运行 Electron + Vite -``` - -### 构建与运行 - -```bash -npm run build # 构建 Vue 应用到 dist 目录 -npm start # 运行 Electron 应用 -npm run electron:start # 构建 + 运行 Electron -``` - -### 打包应用 - -```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:win-installer # 构建 NSIS 安装包 -npm run dist # 完整构建和打包 -``` - -## 功能模块 - -### 1. 常规设置 (General) - -![主界面](screenshots/main.png) - -配置应用程序的常规选项: - -- **语言**: zh-CN / en-US / ja-JP -- **主题**: Xcode / Dark / 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` -- **关闭窗口时隐藏到系统托盘**,双击托盘图标可重新显示 - -### 系统托盘 -- 托盘图标显示应用状态 -- 右键菜单支持: - - 显示主窗口 - - 切换 API 配置(显示所有配置列表,当前配置带勾选标记) - - 退出应用 -- 双击托盘图标显示主窗口 - -### 安全配置 -- `contextIsolation: true` - 隔离上下文 -- `nodeIntegration: false` - 禁用 Node.js -- `webSecurity: false` - 仅开发环境解决 CSP 问题 - -## 设计系统 - -本项目采用 **Windows 11 Fluent Design** 设计规范,实现统一的视觉效果。 - -### 主题支持 -- **Xcode**: macOS 风格浅色主题 -- **Dark**: Windows 11 风格深色主题 -- **Solarized Dark**: Solarized 配色深色主题 - -### 设计特点 -- **Mica-inspired 层次感**: 使用半透明背景和分层深度 -- **圆角系统**: 4px / 6px / 8px / 12px 四级圆角 -- **阴影层次**: sm / md / lg / xl 四级阴影 -- **过渡动画**: 0.1s-0.2s 流畅曲线 -- **Segoe UI Variable 字体**: Windows 11 原生字体 - -## 打包配置 - -### Windows 平台 -- **NSIS 安装包**: 支持 x64 架构 -- **可移植版本**: 无需安装的独立可执行文件 -- **安装器特性**: - - 允许修改安装目录 - - 允许提升权限 - - 创建桌面和开始菜单快捷方式 - - 支持中文和英文界面 (zh_CN, en_US) - - 卸载时保留用户数据 - -### 输出目录 -- `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. **服务器编辑**: 使用侧边面板 (Side Panel) 收集表单数据 -3. **MCP 参数**: 每行一个参数,通过换行分割 -4. **环境变量**: 支持 JSON 格式输入 -5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作 -6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中 -7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题 -8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常 -9. **托盘切换事件**: 监听 `onApiProfileSwitched` 处理托盘发起的配置切换 - ## 许可证 MIT License -Copyright © 2026 上海潘哆呐科技有限公司 +## 联系方式 + +- 公司: 上海潘哆呐科技有限公司 +- 项目地址: https://git.pandorastudio.cn/product/iFlow-Settings-Editor-GUI diff --git a/screenshots/acrylic-slider-jp.jpg b/screenshots/acrylic-slider-jp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..86778fa726a673c813cf3972e0ff3e810f88ac86 GIT binary patch literal 21593 zcmb@tbzD?k+%Gzyh+u#qEg>L{lz_wlg3=)!LkdHOGz>#1-O>%xIdsP$N_QizbdSIg zL*DK4y!Sotz2}~L&-vWLA8TOG9`>5O*ZQq*tPqDF{Rel6(6`!y|ofkpQl7l7YH)xFNKBuZD%ddz>p!2}>iPdE1t~Q7wp9 zUlj02oyCK;O&zTF;o zP3y_~Ll=_g!I|lXKfh{pC=~i65>HcW4#Z7lSyr~WSUV@5-2u@Fm+3cEXCXw~4>TCw zW%e;diykead<2cJ#cx){Z;q1MpIz|b@u1;zM=J_Rw*%3%a#P}~;L`OUK8tdpbj#dF z{l@7fDOm=Nk`QZ_^d&mg4X!AKm9aZtPIcOOPMrdYXqnln3$;D$Ndj)}UP&dq$(d59 z4`SY-%G7CiU3TuY?&Hl_-jro}UPliCedVc+ayU3-%v@T}dUKx> ztq3=}L@948z}BJTlk@g1Lv`Efl=Qg`P>zwGI>^2A2ns6q9jq4?RZ9FCP?cMWR2gv* z*By%vl=!l{demi*p@iBKyH7Zt8)_SedC$NOLxbndz)$%v-1VVk6Pm>uzGy zfr0&6!M3=i@X4L5+^erY)r0$H_ezlG_j12}R=mb5aZejmF><90ea}9{wNOX@qlcfp zR9RlkdH?oMuFsz@k%Jr=fde8}$n-jB@~Ia)nN;sDgFCC-C61kqweZZqCR#izDUs zm>J?zcU>&Aan-flc5)akZfE6db{*G#QM$p9HdXO>vEDdC@Y!80*fey_+h~7sUQN1D zcaM#0`#4pqY%4&rV(4LQG|z66_P2}kAFFzlaS4S;LwC{n_v4CO<*fS7qI^jEPlt==iyxY2k*b@bPv-RNd>x>l?^Fqj^dy$V-*DR0qiExVKmf zrZzoKp-z1t?76bm-)=A(fAgLAq}tq{-R_z2*9_)_13A%0Djwzp1ojsngMCtUvX~7$ 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

WU|HQwVAg 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 XQO_<@TG+ru5Xk(Bl`+xi*28}Q={Hb= literal 0 HcmV?d00001