diff --git a/AGENTS.md b/AGENTS.md index 234a5dd..a6e5aae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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}` \ No newline at end of file diff --git a/README.md b/README.md index 5a59535..1b24934 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,33 @@ # iFlow Settings Editor -一个用于编辑 `C:\Users\MSI\.iflow\settings.json` 配置文件的桌面应用程序。 +一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\\.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 主进程 -├── preload.js # 预加载脚本 (IPC 通信) -├── package.json # 项目配置 -├── vite.config.js # Vite 配置 -├── index.html # HTML 入口 -└── src/ - ├── main.js # Vue 入口 - └── App.vue # 主组件 +iflow-settings-editor/ +├── main.js # Electron 主进程 (窗口管理、IPC、文件操作) +├── preload.js # 预加载脚本 (IPC 通信) +├── index.html # HTML 入口 +├── package.json # 项目配置 +├── vite.config.js # Vite 配置 +├── src/ +│ ├── main.js # Vue 入口 +│ └── App.vue # 主组件 (所有业务逻辑) +├── build/ # 构建资源 (图标等) +├── release/ # 打包输出目录 +└── screenshots/ # 截图资源 ``` ## 快速开始 @@ -34,33 +41,127 @@ npm install ### 开发模式 ```bash -npm run dev # 启动 Vite 开发服务器 -npm run electron:dev # 同时运行 Electron (需先执行 npm run dev) +npm run dev # 启动 Vite 开发服务器 +npm run electron:dev # 同时运行 Electron + Vite ``` ### 构建与运行 ```bash -npm run build # 构建 Vue 应用到 dist 目录 -npm start # 运行 Electron 应用 +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 格式 \ No newline at end of file +- 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 \ No newline at end of file diff --git a/main.js b/main.js index 734cb3b..0109e96 100644 --- a/main.js +++ b/main.js @@ -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,7 +87,6 @@ function writeSettings(data) { } fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf-8') } - // 获取 API 配置列表 ipcMain.handle('list-api-profiles', async () => { try { @@ -114,28 +94,24 @@ ipcMain.handle('list-api-profiles', async () => { if (!settings) { 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' + return { + success: true, + profiles: profileList, + currentProfile: settings.currentApiProfile || 'default', } } catch (error) { return { success: false, error: error.message, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' } } }) - // 切换 API 配置 ipcMain.handle('switch-api-profile', async (event, profileName) => { try { @@ -143,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]) { @@ -160,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) { @@ -168,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 { @@ -187,7 +156,6 @@ ipcMain.handle('create-api-profile', async (event, name) => { if (!settings) { return { success: false, error: '配置文件不存在' } } - if (!settings.apiProfiles) { settings.apiProfiles = { default: {} } // 初始化 default 配置 @@ -197,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) { @@ -210,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 { @@ -226,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' @@ -250,121 +209,64 @@ 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 { - const settings = readSettings() - 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 { @@ -378,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 中的当前配置 @@ -386,7 +287,6 @@ ipcMain.handle('save-settings', async (event, data) => { if (!data.apiProfiles) { data.apiProfiles = {} } - // 更新当前配置到 apiProfiles const currentConfig = {} for (const field of API_FIELDS) { @@ -395,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 }) -}) \ No newline at end of file +}) diff --git a/package.json b/package.json index 6728f4d..18dcad5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iflow-settings-editor", - "version": "1.0.0", + "version": "1.5.0", "description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。", "main": "main.js", "author": "上海潘哆呐科技有限公司", @@ -25,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" diff --git a/screenshots/main.png b/screenshots/main.png new file mode 100644 index 0000000..a28e3f2 Binary files /dev/null and b/screenshots/main.png differ diff --git a/src/App.vue b/src/App.vue index e832430..62362c8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,7 +2,7 @@
- iFlow Settings Editor + iFlow 设置编辑器
-
-
- iFlow 设置编辑器 -
-
-
+ + + +
+

MCP 服务器

管理 Model Context Protocol 服务器配置

@@ -184,7 +174,6 @@ -
@@ -214,22 +203,22 @@
- - + + - - + + - - - + + + - - - + + +
{{ showMessageDialog.title }}
@@ -241,124 +230,121 @@
-
-
-
-
- - 新建 API 配置 +
+
+
+
+ + 新建 API 配置 +
+
- -
-
-
- - -
-
- - -
-
- - -
-
+
- - + +
- - + + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
-
- - +
+ +
-
- - -
-
-
- -
-
- -
-
-
-
- - 编辑 API 配置 -
- + +
+
+
+
+ + 编辑 API 配置
-
+ +
+
+
+ + +
+
+ + +
+
- - + +
- - -
-
-
- - -
-
- - -
-
-
- - -
-
- - + +
-
- - +
+ + +
+
+ +
+
+ + +
- - -
-
+
+ + +
+
+
{{ isEditingServer ? '编辑服务器' : '添加服务器' }} @@ -452,7 +438,7 @@ const editingServerData = ref({ command: 'npx', cwd: '.', args: '', - env: '' + env: '', }) const showApiEditDialog = ref(false) const editingApiProfileName = ref('') @@ -462,7 +448,7 @@ const editingApiData = ref({ baseUrl: '', modelName: '', searchApiKey: '', - cna: '' + cna: '', }) const showApiCreateDialog = ref(false) const creatingApiData = ref({ @@ -472,7 +458,7 @@ const creatingApiData = ref({ baseUrl: '', modelName: '', searchApiKey: '', - cna: '' + cna: '', }) // Load API profiles list @@ -507,9 +493,7 @@ const switchApiProfile = async () => { // Create new API profile const createNewApiProfile = () => { - creatingApiData.value = { - name: '', selectedAuthType: 'iflow', @@ -522,26 +506,18 @@ const createNewApiProfile = () => { searchApiKey: '', - cna: '' - + cna: '', } showApiCreateDialog.value = true - } - - // Close API create dialog const closeApiCreateDialog = () => { - showApiCreateDialog.value = false - } - - // Save API create const saveApiCreate = async () => { const name = creatingApiData.value.name.trim() @@ -559,7 +535,7 @@ const saveApiCreate = async () => { baseUrl: creatingApiData.value.baseUrl, modelName: creatingApiData.value.modelName, searchApiKey: creatingApiData.value.searchApiKey, - cna: creatingApiData.value.cna + cna: creatingApiData.value.cna, } // 保存配置数据 @@ -569,7 +545,7 @@ const saveApiCreate = async () => { if (!data.apiProfiles) data.apiProfiles = {} data.apiProfiles[name] = profileData await window.electronAPI.saveSettings(data) - + showApiCreateDialog.value = false await loadApiProfiles() await showMessage({ type: 'info', title: '创建成功', message: `配置 "${name}" 已创建` }) @@ -580,7 +556,7 @@ const saveApiCreate = async () => { } // Delete API profile -const deleteApiProfile = async (name) => { +const deleteApiProfile = async name => { const profileName = name || currentApiProfile.value if (profileName === 'default') { await showMessage({ type: 'warning', title: '无法删除', message: '不能删除默认配置' }) @@ -593,7 +569,7 @@ const deleteApiProfile = async (name) => { title: '删除配置', placeholder: `确定要删除配置 "${profileName}" 吗?`, callback: resolve, - isConfirm: true + isConfirm: true, } }) if (!confirmed) return @@ -634,7 +610,7 @@ const renameApiProfile = async () => { } // Select API profile (click on card) -const selectApiProfile = async (name) => { +const selectApiProfile = async name => { if (name === currentApiProfile.value) return currentApiProfile.value = name isLoading.value = true @@ -643,13 +619,13 @@ const selectApiProfile = async (name) => { } // Get profile initial letter for icon -const getProfileInitial = (name) => { +const getProfileInitial = name => { if (!name) return '?' return name.charAt(0).toUpperCase() } // Get profile URL for display -const getProfileUrl = (name) => { +const getProfileUrl = name => { if (!settings.value.apiProfiles || !settings.value.apiProfiles[name]) { return '未配置' } @@ -667,7 +643,7 @@ const profileColors = [ 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)', // blue ] -const getProfileIconStyle = (name) => { +const getProfileIconStyle = name => { if (name === 'default') { return { background: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)' } } @@ -681,48 +657,34 @@ const getProfileIconStyle = (name) => { // Duplicate API profile -const duplicateApiProfile = async (name) => { - +const duplicateApiProfile = async name => { const newName = await new Promise(resolve => { - showInputDialog.value = { - show: true, title: '复制配置', placeholder: '请输入新配置的名称', - callback: resolve - + callback: resolve, } - }) if (!newName) return - - const result = await window.electronAPI.duplicateApiProfile(name, newName) if (result.success) { - await loadApiProfiles() await showMessage({ type: 'info', title: '复制成功', message: `配置已复制为 "${newName}"` }) - } else { - await showMessage({ type: 'error', title: '复制失败', message: result.error }) - } - } - - // Open API edit dialog -const openApiEditDialog = (profileName) => { +const openApiEditDialog = profileName => { // 保存正在编辑的配置名称 editingApiProfileName.value = profileName // 从 apiProfiles 中加载指定配置的数据 @@ -733,34 +695,28 @@ const openApiEditDialog = (profileName) => { baseUrl: profile ? profile.baseUrl : settings.value.baseUrl || '', modelName: profile ? profile.modelName : settings.value.modelName || '', searchApiKey: profile ? profile.searchApiKey : settings.value.searchApiKey || '', - cna: profile ? profile.cna : settings.value.cna || '' + cna: profile ? profile.cna : settings.value.cna || '', } showApiEditDialog.value = true } - - // Close API edit dialog const closeApiEditDialog = () => { - showApiEditDialog.value = false - } - - // Save API edit const saveApiEdit = async () => { if (!settings.value.apiProfiles) { settings.value.apiProfiles = {} } - + // 确保配置对象存在 if (!settings.value.apiProfiles[editingApiProfileName.value]) { settings.value.apiProfiles[editingApiProfileName.value] = {} } - + // 保存到指定的配置 settings.value.apiProfiles[editingApiProfileName.value].selectedAuthType = editingApiData.value.selectedAuthType settings.value.apiProfiles[editingApiProfileName.value].apiKey = editingApiData.value.apiKey @@ -768,9 +724,9 @@ const saveApiEdit = async () => { settings.value.apiProfiles[editingApiProfileName.value].modelName = editingApiData.value.modelName settings.value.apiProfiles[editingApiProfileName.value].searchApiKey = editingApiData.value.searchApiKey settings.value.apiProfiles[editingApiProfileName.value].cna = editingApiData.value.cna - + showApiEditDialog.value = false - + // 自动保存到文件 const result = await window.electronAPI.saveSettings(settings.value) if (result.success) { @@ -779,8 +735,6 @@ const saveApiEdit = async () => { } } - - const loadSettings = async () => { const result = await window.electronAPI.loadSettings() if (result.success) { @@ -861,7 +815,7 @@ const openAddServerPanel = () => { command: 'npx', cwd: '.', args: '-y\npackage-name', - env: '' + env: '', } showServerPanel.value = true nextTick(() => { @@ -869,7 +823,7 @@ const openAddServerPanel = () => { }) } -const openEditServerPanel = (name) => { +const openEditServerPanel = name => { const server = settings.value.mcpServers[name] if (!server) return isEditingServer.value = true @@ -879,7 +833,7 @@ const openEditServerPanel = (name) => { command: server.command || '', cwd: server.cwd || '.', args: (server.args || []).join('\n'), - env: server.env ? JSON.stringify(server.env, null, 2) : '' + env: server.env ? JSON.stringify(server.env, null, 2) : '', } showServerPanel.value = true nextTick(() => { @@ -911,7 +865,10 @@ const saveServerFromPanel = async () => { command: editingServerData.value.command.trim(), description: editingServerData.value.description.trim(), cwd: editingServerData.value.cwd.trim() || '.', - args: editingServerData.value.args.split('\n').map(s => s.trim()).filter(s => s) + args: editingServerData.value.args + .split('\n') + .map(s => s.trim()) + .filter(s => s), } const envText = editingServerData.value.env.trim() @@ -985,11 +942,14 @@ const closeMessageDialog = () => { } // Watch for dialog open to set default value -watch(() => showInputDialog.value.show, (show) => { - if (show && showInputDialog.value.defaultValue) { - inputDialogValue.value = showInputDialog.value.defaultValue - } -}) +watch( + () => showInputDialog.value.show, + show => { + if (show && showInputDialog.value.defaultValue) { + inputDialogValue.value = showInputDialog.value.defaultValue + } + }, +) onMounted(async () => { await loadApiProfiles() @@ -1813,11 +1773,21 @@ body { animation: fadeIn 0.3s ease backwards; } -.profile-item:nth-child(1) { animation-delay: 0.02s; } -.profile-item:nth-child(2) { animation-delay: 0.04s; } -.profile-item:nth-child(3) { animation-delay: 0.06s; } -.profile-item:nth-child(4) { animation-delay: 0.08s; } -.profile-item:nth-child(5) { animation-delay: 0.1s; } +.profile-item:nth-child(1) { + animation-delay: 0.02s; +} +.profile-item:nth-child(2) { + animation-delay: 0.04s; +} +.profile-item:nth-child(3) { + animation-delay: 0.06s; +} +.profile-item:nth-child(4) { + animation-delay: 0.08s; +} +.profile-item:nth-child(5) { + animation-delay: 0.1s; +} .profile-item:hover { background: var(--bg-tertiary); @@ -1828,7 +1798,9 @@ body { .profile-item.active { background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(139, 92, 246, 0.05) 100%); border-color: var(--accent); - box-shadow: 0 0 0 1px var(--accent), 0 4px 12px rgba(59, 130, 246, 0.15); + box-shadow: + 0 0 0 1px var(--accent), + 0 4px 12px rgba(59, 130, 246, 0.15); } .profile-icon {