You've already forked iFlow-Settings-Editor-GUI
文档 完善项目文档结构和功能说明
This commit is contained in:
67
AGENTS.md
67
AGENTS.md
@@ -13,6 +13,8 @@ iFlow 设置编辑器是一个基于 Electron + Vue 3 的桌面应用程序,
|
|||||||
| Vite | ^8.0.8 | 构建工具 |
|
| Vite | ^8.0.8 | 构建工具 |
|
||||||
| @icon-park/vue-next | ^1.4.2 | 图标库 |
|
| @icon-park/vue-next | ^1.4.2 | 图标库 |
|
||||||
| @vitejs/plugin-vue | ^6.0.6 | Vue 插件 |
|
| @vitejs/plugin-vue | ^6.0.6 | Vue 插件 |
|
||||||
|
| concurrently | ^8.2.2 | 并发执行工具 |
|
||||||
|
| electron-builder | ^24.13.3 | 应用打包工具 |
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
@@ -26,6 +28,9 @@ iflow-settings-editor/
|
|||||||
├── src/
|
├── src/
|
||||||
│ ├── main.js # Vue 入口
|
│ ├── main.js # Vue 入口
|
||||||
│ └── App.vue # 主组件 (所有业务逻辑)
|
│ └── App.vue # 主组件 (所有业务逻辑)
|
||||||
|
├── build/ # 构建资源 (图标等)
|
||||||
|
├── release/ # 打包输出目录
|
||||||
|
└── screenshots/ # 截图资源
|
||||||
```
|
```
|
||||||
|
|
||||||
## 核心架构
|
## 核心架构
|
||||||
@@ -42,6 +47,12 @@ window.electronAPI = {
|
|||||||
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
||||||
saveSettings: (data) => ipcRenderer.invoke('save-settings', data),
|
saveSettings: (data) => ipcRenderer.invoke('save-settings', data),
|
||||||
showMessage: (options) => ipcRenderer.invoke('show-message', options),
|
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'),
|
isMaximized: () => ipcRenderer.invoke('is-maximized'),
|
||||||
minimize: () => ipcRenderer.send('window-minimize'),
|
minimize: () => ipcRenderer.send('window-minimize'),
|
||||||
maximize: () => ipcRenderer.send('window-maximize'),
|
maximize: () => ipcRenderer.send('window-maximize'),
|
||||||
@@ -56,8 +67,9 @@ window.electronAPI = {
|
|||||||
|
|
||||||
### API 配置切换
|
### API 配置切换
|
||||||
- 支持多环境配置: 默认配置、开发环境、预发布环境、生产环境
|
- 支持多环境配置: 默认配置、开发环境、预发布环境、生产环境
|
||||||
- 切换前检查未保存的更改
|
- 配置文件管理: 支持创建、编辑、复制、删除、重命名
|
||||||
- 单独保存每个环境的 API 配置到 `apiProfiles` 对象
|
- 单独保存每个环境的 API 配置到 `apiProfiles` 对象
|
||||||
|
- 切换配置时直接应用新配置,无需确认
|
||||||
|
|
||||||
## 可用命令
|
## 可用命令
|
||||||
|
|
||||||
@@ -68,6 +80,13 @@ npm run build # 构建 Vue 应用到 dist 目录
|
|||||||
npm start # 运行 Electron (需先build)
|
npm start # 运行 Electron (需先build)
|
||||||
npm run electron:dev # 同时运行 Vite + Electron (开发模式)
|
npm run electron:dev # 同时运行 Vite + Electron (开发模式)
|
||||||
npm run electron:start # 构建 + 运行 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)
|
### 2. API 配置 (API)
|
||||||
- **配置切换**: 支持多环境 (默认/开发/预发布/生产)
|
- **配置列表**: 显示所有可用的 API 配置文件
|
||||||
- **认证方式**: iFlow / API Key
|
- **配置切换**: 点击配置卡片直接切换,无需确认
|
||||||
|
- **创建配置**: 新建 API 配置文件
|
||||||
|
- **编辑配置**: 修改现有配置的认证方式、API Key、Base URL 等
|
||||||
|
- **复制配置**: 基于现有配置创建新配置
|
||||||
|
- **删除配置**: 删除非默认配置
|
||||||
|
- **认证方式**: iFlow / API Key / OpenAI 兼容
|
||||||
- **API Key**: 密码输入框
|
- **API Key**: 密码输入框
|
||||||
- **Base URL**: API 端点
|
- **Base URL**: API 端点
|
||||||
- **模型名称**: AI 模型标识
|
- **模型名称**: AI 模型标识
|
||||||
@@ -118,6 +142,23 @@ if (fs.existsSync(SETTINGS_FILE)) {
|
|||||||
- `modified` - 是否已修改 (computed/diff)
|
- `modified` - 是否已修改 (computed/diff)
|
||||||
- `currentSection` - 当前显示的板块
|
- `currentSection` - 当前显示的板块
|
||||||
- `currentServerName` - 当前选中的 MCP 服务器
|
- `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 发送指令,主进程处理实际窗口操作
|
5. **窗口控制**: 通过 IPC 发送指令,主进程处理实际窗口操作
|
||||||
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
|
6. **API 配置切换**: 多个环境配置存储在 `settings.apiProfiles` 对象中
|
||||||
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
|
7. **序列化问题**: IPC 通信使用 `JSON.parse(JSON.stringify())` 避免 Vue 响应式代理问题
|
||||||
|
8. **默认值处理**: 加载配置时检查 `undefined` 并应用默认值,防止界面显示异常
|
||||||
|
|
||||||
## 图标使用
|
## 图标使用
|
||||||
|
|
||||||
使用 `@icon-park/vue-next` 图标库:
|
使用 `@icon-park/vue-next` 图标库:
|
||||||
```javascript
|
```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}`
|
||||||
159
README.md
159
README.md
@@ -1,26 +1,33 @@
|
|||||||
# iFlow Settings Editor
|
# iFlow Settings Editor
|
||||||
|
|
||||||
一个用于编辑 `C:\Users\MSI\.iflow\settings.json` 配置文件的桌面应用程序。
|
一个基于 Electron + Vue 3 的桌面应用程序,用于编辑 `C:\Users\<USER>\.iflow\settings.json` 配置文件。
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
- **Electron** - 桌面应用框架
|
| 技术 | 版本 | 用途 |
|
||||||
- **Vue 3** - 前端框架 (组合式 API)
|
|------|------|------|
|
||||||
- **Vite** - 构建工具
|
| Electron | ^28.0.0 | 桌面应用框架 |
|
||||||
- **@icon-park/vue-next** - 图标库
|
| 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/
|
iflow-settings-editor/
|
||||||
├── main.js # Electron 主进程
|
├── main.js # Electron 主进程 (窗口管理、IPC、文件操作)
|
||||||
├── preload.js # 预加载脚本 (IPC 通信)
|
├── preload.js # 预加载脚本 (IPC 通信)
|
||||||
├── package.json # 项目配置
|
├── index.html # HTML 入口
|
||||||
├── vite.config.js # Vite 配置
|
├── package.json # 项目配置
|
||||||
├── index.html # HTML 入口
|
├── vite.config.js # Vite 配置
|
||||||
└── src/
|
├── src/
|
||||||
├── main.js # Vue 入口
|
│ ├── main.js # Vue 入口
|
||||||
└── App.vue # 主组件
|
│ └── App.vue # 主组件 (所有业务逻辑)
|
||||||
|
├── build/ # 构建资源 (图标等)
|
||||||
|
├── release/ # 打包输出目录
|
||||||
|
└── screenshots/ # 截图资源
|
||||||
```
|
```
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
@@ -34,33 +41,127 @@ npm install
|
|||||||
### 开发模式
|
### 开发模式
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev # 启动 Vite 开发服务器
|
npm run dev # 启动 Vite 开发服务器
|
||||||
npm run electron:dev # 同时运行 Electron (需先执行 npm run dev)
|
npm run electron:dev # 同时运行 Electron + Vite
|
||||||
```
|
```
|
||||||
|
|
||||||
### 构建与运行
|
### 构建与运行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build # 构建 Vue 应用到 dist 目录
|
npm run build # 构建 Vue 应用到 dist 目录
|
||||||
npm start # 运行 Electron 应用
|
npm start # 运行 Electron 应用
|
||||||
|
npm run electron:start # 构建 + 运行 Electron
|
||||||
```
|
```
|
||||||
|
|
||||||
## 功能
|
### 打包应用
|
||||||
|
|
||||||
- **常规设置**: 语言、主题、启动动画、检查点保存
|
```bash
|
||||||
- **API 配置**: 认证方式、API Key、Base URL、模型名称、搜索服务
|
npm run pack # 打包应用(不生成安装包)
|
||||||
- **MCP 服务器管理**: 添加、编辑、删除服务器配置
|
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)
|
||||||
- 自定义标题栏 (支持最小化/最大化/关闭)
|
|
||||||
- 侧边导航栏
|

|
||||||
- 表单编辑区域
|
|
||||||
- 底部状态栏
|
配置应用程序的常规选项:
|
||||||
|
|
||||||
|
- **语言**: 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 问题
|
- `webSecurity: false` 仅用于开发环境解决 CSP 问题
|
||||||
- 保存设置时会自动创建备份 (`settings.json.bak`)
|
- 保存设置时会自动创建备份 (`settings.json.bak`)
|
||||||
- MCP 服务器参数每行一个,环境变量支持 JSON 格式
|
- 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
|
||||||
114
main.js
114
main.js
@@ -1,20 +1,14 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
|
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
console.log('main.js loaded')
|
console.log('main.js loaded')
|
||||||
console.log('app.getPath("home"):', app.getPath('home'))
|
console.log('app.getPath("home"):', app.getPath('home'))
|
||||||
|
|
||||||
const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json')
|
const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json')
|
||||||
console.log('SETTINGS_FILE:', SETTINGS_FILE)
|
console.log('SETTINGS_FILE:', SETTINGS_FILE)
|
||||||
|
|
||||||
let mainWindow
|
let mainWindow
|
||||||
|
|
||||||
const isDev = process.argv.includes('--dev')
|
const isDev = process.argv.includes('--dev')
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
console.log('Creating window...')
|
console.log('Creating window...')
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1100,
|
width: 1100,
|
||||||
height: 750,
|
height: 750,
|
||||||
@@ -32,7 +26,6 @@ function createWindow() {
|
|||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('Loading index.html...')
|
console.log('Loading index.html...')
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
mainWindow.loadURL('http://localhost:5173')
|
mainWindow.loadURL('http://localhost:5173')
|
||||||
@@ -40,39 +33,31 @@ function createWindow() {
|
|||||||
mainWindow.loadFile(path.join(__dirname, 'dist', 'index.html'))
|
mainWindow.loadFile(path.join(__dirname, 'dist', 'index.html'))
|
||||||
}
|
}
|
||||||
console.log('index.html loading initiated')
|
console.log('index.html loading initiated')
|
||||||
|
|
||||||
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
|
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
|
||||||
console.error('Failed to load:', errorCode, errorDescription)
|
console.error('Failed to load:', errorCode, errorDescription)
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
|
mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
|
||||||
console.log('Console [' + level + ']:', message)
|
console.log('Console [' + level + ']:', message)
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.once('ready-to-show', () => {
|
mainWindow.once('ready-to-show', () => {
|
||||||
console.log('Window ready to show')
|
console.log('Window ready to show')
|
||||||
mainWindow.show()
|
mainWindow.show()
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
mainWindow = null
|
mainWindow = null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(createWindow)
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (mainWindow === null) {
|
if (mainWindow === null) {
|
||||||
createWindow()
|
createWindow()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Window controls
|
// Window controls
|
||||||
ipcMain.on('window-minimize', () => mainWindow.minimize())
|
ipcMain.on('window-minimize', () => mainWindow.minimize())
|
||||||
ipcMain.on('window-maximize', () => {
|
ipcMain.on('window-maximize', () => {
|
||||||
@@ -83,12 +68,9 @@ ipcMain.on('window-maximize', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
ipcMain.on('window-close', () => mainWindow.close())
|
ipcMain.on('window-close', () => mainWindow.close())
|
||||||
|
|
||||||
ipcMain.handle('is-maximized', () => mainWindow.isMaximized())
|
ipcMain.handle('is-maximized', () => mainWindow.isMaximized())
|
||||||
|
|
||||||
// API 配置相关的字段
|
// API 配置相关的字段
|
||||||
const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna']
|
const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna']
|
||||||
|
|
||||||
// 读取设置文件
|
// 读取设置文件
|
||||||
function readSettings() {
|
function readSettings() {
|
||||||
if (!fs.existsSync(SETTINGS_FILE)) {
|
if (!fs.existsSync(SETTINGS_FILE)) {
|
||||||
@@ -97,7 +79,6 @@ function readSettings() {
|
|||||||
const data = fs.readFileSync(SETTINGS_FILE, 'utf-8')
|
const data = fs.readFileSync(SETTINGS_FILE, 'utf-8')
|
||||||
return JSON.parse(data)
|
return JSON.parse(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入设置文件
|
// 写入设置文件
|
||||||
function writeSettings(data) {
|
function writeSettings(data) {
|
||||||
if (fs.existsSync(SETTINGS_FILE)) {
|
if (fs.existsSync(SETTINGS_FILE)) {
|
||||||
@@ -106,7 +87,6 @@ function writeSettings(data) {
|
|||||||
}
|
}
|
||||||
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf-8')
|
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 API 配置列表
|
// 获取 API 配置列表
|
||||||
ipcMain.handle('list-api-profiles', async () => {
|
ipcMain.handle('list-api-profiles', async () => {
|
||||||
try {
|
try {
|
||||||
@@ -114,28 +94,24 @@ ipcMain.handle('list-api-profiles', async () => {
|
|||||||
if (!settings) {
|
if (!settings) {
|
||||||
return { success: true, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
|
return { success: true, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = settings.apiProfiles || {}
|
const profiles = settings.apiProfiles || {}
|
||||||
// 确保至少有 default 配置
|
// 确保至少有 default 配置
|
||||||
if (Object.keys(profiles).length === 0) {
|
if (Object.keys(profiles).length === 0) {
|
||||||
profiles.default = {}
|
profiles.default = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileList = Object.keys(profiles).map(name => ({
|
const profileList = Object.keys(profiles).map(name => ({
|
||||||
name,
|
name,
|
||||||
isDefault: name === 'default'
|
isDefault: name === 'default',
|
||||||
}))
|
}))
|
||||||
|
return {
|
||||||
return {
|
success: true,
|
||||||
success: true,
|
profiles: profileList,
|
||||||
profiles: profileList,
|
currentProfile: settings.currentApiProfile || 'default',
|
||||||
currentProfile: settings.currentApiProfile || 'default'
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
|
return { success: false, error: error.message, profiles: [{ name: 'default', isDefault: true }], currentProfile: 'default' }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 切换 API 配置
|
// 切换 API 配置
|
||||||
ipcMain.handle('switch-api-profile', async (event, profileName) => {
|
ipcMain.handle('switch-api-profile', async (event, profileName) => {
|
||||||
try {
|
try {
|
||||||
@@ -143,12 +119,10 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
|
|||||||
if (!settings) {
|
if (!settings) {
|
||||||
return { success: false, error: '配置文件不存在' }
|
return { success: false, error: '配置文件不存在' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = settings.apiProfiles || {}
|
const profiles = settings.apiProfiles || {}
|
||||||
if (!profiles[profileName]) {
|
if (!profiles[profileName]) {
|
||||||
return { success: false, error: `配置 "${profileName}" 不存在` }
|
return { success: false, error: `配置 "${profileName}" 不存在` }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存当前配置到 apiProfiles(如果当前配置存在)
|
// 保存当前配置到 apiProfiles(如果当前配置存在)
|
||||||
const currentProfile = settings.currentApiProfile || 'default'
|
const currentProfile = settings.currentApiProfile || 'default'
|
||||||
if (profiles[currentProfile]) {
|
if (profiles[currentProfile]) {
|
||||||
@@ -160,7 +134,6 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
|
|||||||
}
|
}
|
||||||
profiles[currentProfile] = currentConfig
|
profiles[currentProfile] = currentConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 apiProfiles 加载新配置到主字段
|
// 从 apiProfiles 加载新配置到主字段
|
||||||
const newConfig = profiles[profileName]
|
const newConfig = profiles[profileName]
|
||||||
for (const field of API_FIELDS) {
|
for (const field of API_FIELDS) {
|
||||||
@@ -168,18 +141,14 @@ ipcMain.handle('switch-api-profile', async (event, profileName) => {
|
|||||||
settings[field] = newConfig[field]
|
settings[field] = newConfig[field]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.currentApiProfile = profileName
|
settings.currentApiProfile = profileName
|
||||||
settings.apiProfiles = profiles
|
settings.apiProfiles = profiles
|
||||||
|
|
||||||
writeSettings(settings)
|
writeSettings(settings)
|
||||||
|
|
||||||
return { success: true, data: settings }
|
return { success: true, data: settings }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建新的 API 配置
|
// 创建新的 API 配置
|
||||||
ipcMain.handle('create-api-profile', async (event, name) => {
|
ipcMain.handle('create-api-profile', async (event, name) => {
|
||||||
try {
|
try {
|
||||||
@@ -187,7 +156,6 @@ ipcMain.handle('create-api-profile', async (event, name) => {
|
|||||||
if (!settings) {
|
if (!settings) {
|
||||||
return { success: false, error: '配置文件不存在' }
|
return { success: false, error: '配置文件不存在' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settings.apiProfiles) {
|
if (!settings.apiProfiles) {
|
||||||
settings.apiProfiles = { default: {} }
|
settings.apiProfiles = { default: {} }
|
||||||
// 初始化 default 配置
|
// 初始化 default 配置
|
||||||
@@ -197,11 +165,9 @@ ipcMain.handle('create-api-profile', async (event, name) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.apiProfiles[name]) {
|
if (settings.apiProfiles[name]) {
|
||||||
return { success: false, error: `配置 "${name}" 已存在` }
|
return { success: false, error: `配置 "${name}" 已存在` }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制当前配置到新配置
|
// 复制当前配置到新配置
|
||||||
const newConfig = {}
|
const newConfig = {}
|
||||||
for (const field of API_FIELDS) {
|
for (const field of API_FIELDS) {
|
||||||
@@ -210,15 +176,12 @@ ipcMain.handle('create-api-profile', async (event, name) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
settings.apiProfiles[name] = newConfig
|
settings.apiProfiles[name] = newConfig
|
||||||
|
|
||||||
writeSettings(settings)
|
writeSettings(settings)
|
||||||
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 删除 API 配置
|
// 删除 API 配置
|
||||||
ipcMain.handle('delete-api-profile', async (event, name) => {
|
ipcMain.handle('delete-api-profile', async (event, name) => {
|
||||||
try {
|
try {
|
||||||
@@ -226,19 +189,15 @@ ipcMain.handle('delete-api-profile', async (event, name) => {
|
|||||||
if (!settings) {
|
if (!settings) {
|
||||||
return { success: false, error: '配置文件不存在' }
|
return { success: false, error: '配置文件不存在' }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'default') {
|
if (name === 'default') {
|
||||||
return { success: false, error: '不能删除默认配置' }
|
return { success: false, error: '不能删除默认配置' }
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = settings.apiProfiles || {}
|
const profiles = settings.apiProfiles || {}
|
||||||
if (!profiles[name]) {
|
if (!profiles[name]) {
|
||||||
return { success: false, error: `配置 "${name}" 不存在` }
|
return { success: false, error: `配置 "${name}" 不存在` }
|
||||||
}
|
}
|
||||||
|
|
||||||
delete profiles[name]
|
delete profiles[name]
|
||||||
settings.apiProfiles = profiles
|
settings.apiProfiles = profiles
|
||||||
|
|
||||||
// 如果删除的是当前配置,切换到 default
|
// 如果删除的是当前配置,切换到 default
|
||||||
if (settings.currentApiProfile === name) {
|
if (settings.currentApiProfile === name) {
|
||||||
settings.currentApiProfile = 'default'
|
settings.currentApiProfile = 'default'
|
||||||
@@ -250,121 +209,64 @@ ipcMain.handle('delete-api-profile', async (event, name) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSettings(settings)
|
writeSettings(settings)
|
||||||
|
|
||||||
return { success: true, data: settings }
|
return { success: true, data: settings }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 重命名 API 配置
|
// 重命名 API 配置
|
||||||
|
|
||||||
ipcMain.handle('rename-api-profile', async (event, oldName, newName) => {
|
ipcMain.handle('rename-api-profile', async (event, oldName, newName) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const settings = readSettings()
|
const settings = readSettings()
|
||||||
|
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
|
|
||||||
return { success: false, error: '配置文件不存在' }
|
return { success: false, error: '配置文件不存在' }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldName === 'default') {
|
if (oldName === 'default') {
|
||||||
|
|
||||||
return { success: false, error: '不能重命名默认配置' }
|
return { success: false, error: '不能重命名默认配置' }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = settings.apiProfiles || {}
|
const profiles = settings.apiProfiles || {}
|
||||||
|
|
||||||
if (!profiles[oldName]) {
|
if (!profiles[oldName]) {
|
||||||
|
|
||||||
return { success: false, error: `配置 "${oldName}" 不存在` }
|
return { success: false, error: `配置 "${oldName}" 不存在` }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profiles[newName]) {
|
if (profiles[newName]) {
|
||||||
|
|
||||||
return { success: false, error: `配置 "${newName}" 已存在` }
|
return { success: false, error: `配置 "${newName}" 已存在` }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
profiles[newName] = profiles[oldName]
|
profiles[newName] = profiles[oldName]
|
||||||
|
|
||||||
delete profiles[oldName]
|
delete profiles[oldName]
|
||||||
|
|
||||||
settings.apiProfiles = profiles
|
settings.apiProfiles = profiles
|
||||||
|
|
||||||
if (settings.currentApiProfile === oldName) {
|
if (settings.currentApiProfile === oldName) {
|
||||||
|
|
||||||
settings.currentApiProfile = newName
|
settings.currentApiProfile = newName
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSettings(settings)
|
writeSettings(settings)
|
||||||
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 复制 API 配置
|
// 复制 API 配置
|
||||||
|
|
||||||
ipcMain.handle('duplicate-api-profile', async (event, sourceName, newName) => {
|
ipcMain.handle('duplicate-api-profile', async (event, sourceName, newName) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const settings = readSettings()
|
const settings = readSettings()
|
||||||
|
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
|
|
||||||
return { success: false, error: '配置文件不存在' }
|
return { success: false, error: '配置文件不存在' }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const profiles = settings.apiProfiles || {}
|
const profiles = settings.apiProfiles || {}
|
||||||
|
|
||||||
if (!profiles[sourceName]) {
|
if (!profiles[sourceName]) {
|
||||||
|
|
||||||
return { success: false, error: `配置 "${sourceName}" 不存在` }
|
return { success: false, error: `配置 "${sourceName}" 不存在` }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profiles[newName]) {
|
if (profiles[newName]) {
|
||||||
|
|
||||||
return { success: false, error: `配置 "${newName}" 已存在` }
|
return { success: false, error: `配置 "${newName}" 已存在` }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 深拷贝配置
|
// 深拷贝配置
|
||||||
|
|
||||||
profiles[newName] = JSON.parse(JSON.stringify(profiles[sourceName]))
|
profiles[newName] = JSON.parse(JSON.stringify(profiles[sourceName]))
|
||||||
|
|
||||||
settings.apiProfiles = profiles
|
settings.apiProfiles = profiles
|
||||||
|
|
||||||
writeSettings(settings)
|
writeSettings(settings)
|
||||||
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// IPC Handlers
|
// IPC Handlers
|
||||||
ipcMain.handle('load-settings', async () => {
|
ipcMain.handle('load-settings', async () => {
|
||||||
try {
|
try {
|
||||||
@@ -378,7 +280,6 @@ ipcMain.handle('load-settings', async () => {
|
|||||||
return { success: false, error: error.message, data: null }
|
return { success: false, error: error.message, data: null }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('save-settings', async (event, data) => {
|
ipcMain.handle('save-settings', async (event, data) => {
|
||||||
try {
|
try {
|
||||||
// 保存时同步更新 apiProfiles 中的当前配置
|
// 保存时同步更新 apiProfiles 中的当前配置
|
||||||
@@ -386,7 +287,6 @@ ipcMain.handle('save-settings', async (event, data) => {
|
|||||||
if (!data.apiProfiles) {
|
if (!data.apiProfiles) {
|
||||||
data.apiProfiles = {}
|
data.apiProfiles = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新当前配置到 apiProfiles
|
// 更新当前配置到 apiProfiles
|
||||||
const currentConfig = {}
|
const currentConfig = {}
|
||||||
for (const field of API_FIELDS) {
|
for (const field of API_FIELDS) {
|
||||||
@@ -395,14 +295,12 @@ ipcMain.handle('save-settings', async (event, data) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.apiProfiles[currentProfile] = currentConfig
|
data.apiProfiles[currentProfile] = currentConfig
|
||||||
|
|
||||||
writeSettings(data)
|
writeSettings(data)
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: error.message }
|
return { success: false, error: error.message }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('show-message', async (event, { type, title, message }) => {
|
ipcMain.handle('show-message', async (event, { type, title, message }) => {
|
||||||
return dialog.showMessageBox(mainWindow, { type, title, message })
|
return dialog.showMessageBox(mainWindow, { type, title, message })
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "iflow-settings-editor",
|
"name": "iflow-settings-editor",
|
||||||
"version": "1.0.0",
|
"version": "1.5.0",
|
||||||
"description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。",
|
"description": "一个用于编辑 iFlow CLI 配置文件的桌面应用程序。",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"author": "上海潘哆呐科技有限公司",
|
"author": "上海潘哆呐科技有限公司",
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "com.iflow.settings-editor",
|
"appId": "com.iflow.settings-editor",
|
||||||
"productName": "iFlow Settings Editor",
|
"productName": "iFlow Settings Editor",
|
||||||
"copyright": "Copyright © 2024 iFlow",
|
"copyright": "Copyright © 2025 上海潘哆呐科技有限公司",
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "release",
|
"output": "release",
|
||||||
"buildResources": "build"
|
"buildResources": "build"
|
||||||
|
|||||||
BIN
screenshots/main.png
Normal file
BIN
screenshots/main.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
370
src/App.vue
370
src/App.vue
@@ -2,7 +2,7 @@
|
|||||||
<div class="app">
|
<div class="app">
|
||||||
<div class="titlebar">
|
<div class="titlebar">
|
||||||
<div class="titlebar-left">
|
<div class="titlebar-left">
|
||||||
<span class="titlebar-title">iFlow Settings Editor</span>
|
<span class="titlebar-title">iFlow 设置编辑器</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-controls">
|
<div class="titlebar-controls">
|
||||||
<button class="titlebar-btn" @click="minimize" title="最小化">
|
<button class="titlebar-btn" @click="minimize" title="最小化">
|
||||||
@@ -20,12 +20,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<header class="header">
|
|
||||||
<div class="header-left">
|
|
||||||
<span class="header-title">iFlow 设置编辑器</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-section">
|
||||||
@@ -113,19 +107,13 @@
|
|||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<Exchange size="16" />
|
<Exchange size="16" />
|
||||||
配置文件管理
|
配置文件管理
|
||||||
<button class="btn btn-primary btn-sm" @click="createNewApiProfile" style="margin-left: auto;">
|
<button class="btn btn-primary btn-sm" @click="createNewApiProfile" style="margin-left: auto">
|
||||||
<Add size="14" />
|
<Add size="14" />
|
||||||
新建配置
|
新建配置
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-list">
|
<div class="profile-list">
|
||||||
<div
|
<div v-for="profile in apiProfiles" :key="profile.name" class="profile-item" :class="{ active: currentApiProfile === profile.name }" @click="selectApiProfile(profile.name)">
|
||||||
v-for="profile in apiProfiles"
|
|
||||||
:key="profile.name"
|
|
||||||
class="profile-item"
|
|
||||||
:class="{ active: currentApiProfile === profile.name }"
|
|
||||||
@click="selectApiProfile(profile.name)"
|
|
||||||
>
|
|
||||||
<div class="profile-icon" :style="getProfileIconStyle(profile.name)">
|
<div class="profile-icon" :style="getProfileIconStyle(profile.name)">
|
||||||
<span class="profile-icon-text">{{ getProfileInitial(profile.name) }}</span>
|
<span class="profile-icon-text">{{ getProfileInitial(profile.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -147,15 +135,17 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="action-btn" @click.stop="duplicateApiProfile(profile.name)" title="复制">
|
<button class="action-btn" @click.stop="duplicateApiProfile(profile.name)" title="复制">
|
||||||
<Copy size="14" />
|
<Copy size="14" />
|
||||||
</button> <button class="action-btn action-btn-danger" @click.stop="deleteApiProfile(profile.name)" title="删除">
|
</button>
|
||||||
|
<button class="action-btn action-btn-danger" @click.stop="deleteApiProfile(profile.name)" title="删除">
|
||||||
<Delete size="14" />
|
<Delete size="14" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-if="currentSection === 'mcp'"> <div class="content-header">
|
<section v-if="currentSection === 'mcp'">
|
||||||
|
<div class="content-header">
|
||||||
<h1 class="content-title">MCP 服务器</h1>
|
<h1 class="content-title">MCP 服务器</h1>
|
||||||
<p class="content-desc">管理 Model Context Protocol 服务器配置</p>
|
<p class="content-desc">管理 Model Context Protocol 服务器配置</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -184,7 +174,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -214,22 +203,22 @@
|
|||||||
<div class="dialog message-dialog" @click.stop>
|
<div class="dialog message-dialog" @click.stop>
|
||||||
<div class="message-dialog-icon" :class="'message-dialog-icon-' + showMessageDialog.type">
|
<div class="message-dialog-icon" :class="'message-dialog-icon-' + showMessageDialog.type">
|
||||||
<svg v-if="showMessageDialog.type === 'info'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg v-if="showMessageDialog.type === 'info'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10"/>
|
<circle cx="12" cy="12" r="10" />
|
||||||
<path d="M12 16v-4M12 8h.01"/>
|
<path d="M12 16v-4M12 8h.01" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="showMessageDialog.type === 'success'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg v-else-if="showMessageDialog.type === 'success'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10"/>
|
<circle cx="12" cy="12" r="10" />
|
||||||
<path d="M9 12l2 2 4-4"/>
|
<path d="M9 12l2 2 4-4" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="showMessageDialog.type === 'warning'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg v-else-if="showMessageDialog.type === 'warning'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
|
<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" />
|
||||||
<line x1="12" y1="9" x2="12" y2="13"/>
|
<line x1="12" y1="9" x2="12" y2="13" />
|
||||||
<line x1="12" y1="17" x2="12.01" y2="17"/>
|
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg v-else-if="showMessageDialog.type === 'error'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg v-else-if="showMessageDialog.type === 'error'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<circle cx="12" cy="12" r="10"/>
|
<circle cx="12" cy="12" r="10" />
|
||||||
<line x1="15" y1="9" x2="9" y2="15"/>
|
<line x1="15" y1="9" x2="9" y2="15" />
|
||||||
<line x1="9" y1="9" x2="15" y2="15"/>
|
<line x1="9" y1="9" x2="15" y2="15" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-dialog-title">{{ showMessageDialog.title }}</div>
|
<div class="message-dialog-title">{{ showMessageDialog.title }}</div>
|
||||||
@@ -241,124 +230,121 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- API Profile Create Dialog -->
|
<!-- API Profile Create Dialog -->
|
||||||
<div v-if="showApiCreateDialog" class="dialog-overlay dialog-overlay-top" @keyup.esc="closeApiCreateDialog" tabindex="-1" ref="apiCreateDialogOverlay" style="z-index: 1200;">
|
<div v-if="showApiCreateDialog" class="dialog-overlay dialog-overlay-top" @keyup.esc="closeApiCreateDialog" tabindex="-1" ref="apiCreateDialogOverlay" style="z-index: 1200">
|
||||||
<div class="dialog api-edit-dialog" @click.stop>
|
<div class="dialog api-edit-dialog" @click.stop>
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<div class="dialog-title">
|
<div class="dialog-title">
|
||||||
<Add size="18" />
|
<Add size="18" />
|
||||||
新建 API 配置
|
新建 API 配置
|
||||||
|
</div>
|
||||||
|
<button class="side-panel-close" @click="closeApiCreateDialog">
|
||||||
|
<svg viewBox="0 0 10 10">
|
||||||
|
<line x1="0" y1="0" x2="10" y2="10" />
|
||||||
|
<line x1="10" y1="0" x2="0" y2="10" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="side-panel-close" @click="closeApiCreateDialog">
|
<div class="dialog-body">
|
||||||
<svg viewBox="0 0 10 10">
|
|
||||||
<line x1="0" y1="0" x2="10" y2="10" />
|
|
||||||
<line x1="10" y1="0" x2="0" y2="10" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-body">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">配置名称 <span class="form-required">*</span></label>
|
|
||||||
<input type="text" class="form-input" v-model="creatingApiData.name" placeholder="请输入配置名称" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">认证方式</label>
|
|
||||||
<select class="form-select" v-model="creatingApiData.selectedAuthType">
|
|
||||||
<option value="iflow">iFlow</option>
|
|
||||||
<option value="api">API Key</option>
|
|
||||||
<option value="openai-compatible">OpenAI 兼容</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">API Key</label>
|
|
||||||
<input type="password" class="form-input" v-model="creatingApiData.apiKey" placeholder="sk-cp-XXXXX..." />
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Base URL</label>
|
<label class="form-label">配置名称 <span class="form-required">*</span></label>
|
||||||
<input type="text" class="form-input" v-model="creatingApiData.baseUrl" placeholder="https://api.minimaxi.com/v1" />
|
<input type="text" class="form-input" v-model="creatingApiData.name" placeholder="请输入配置名称" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">模型名称</label>
|
<label class="form-label">认证方式</label>
|
||||||
<input type="text" class="form-input" v-model="creatingApiData.modelName" placeholder="MiniMax-M2.7" />
|
<select class="form-select" v-model="creatingApiData.selectedAuthType">
|
||||||
|
<option value="iflow">iFlow</option>
|
||||||
|
<option value="api">API Key</option>
|
||||||
|
<option value="openai-compatible">OpenAI 兼容</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">API Key</label>
|
||||||
|
<input type="password" class="form-input" v-model="creatingApiData.apiKey" placeholder="sk-cp-XXXXX..." />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">Base URL</label>
|
||||||
|
<input type="text" class="form-input" v-model="creatingApiData.baseUrl" placeholder="https://api.minimaxi.com/v1" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">模型名称</label>
|
||||||
|
<input type="text" class="form-input" v-model="creatingApiData.modelName" placeholder="MiniMax-M2.7" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">搜索 API Key</label>
|
||||||
|
<input type="password" class="form-input" v-model="creatingApiData.searchApiKey" placeholder="sk-XXXXX..." />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">CNA</label>
|
||||||
|
<input type="text" class="form-input" v-model="creatingApiData.cna" placeholder="CNA 标识" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="dialog-actions">
|
||||||
<label class="form-label">搜索 API Key</label>
|
<button class="btn btn-secondary" @click="closeApiCreateDialog">取消</button>
|
||||||
<input type="password" class="form-input" v-model="creatingApiData.searchApiKey" placeholder="sk-XXXXX..." />
|
<button class="btn btn-primary" @click="saveApiCreate"> <Save size="14" /> 创建 </button>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">CNA</label>
|
|
||||||
<input type="text" class="form-input" v-model="creatingApiData.cna" placeholder="CNA 标识" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-actions">
|
|
||||||
<button class="btn btn-secondary" @click="closeApiCreateDialog">取消</button>
|
|
||||||
<button class="btn btn-primary" @click="saveApiCreate">
|
|
||||||
<Save size="14" /> 创建
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- API Profile Edit Dialog -->
|
<!-- API Profile Edit Dialog -->
|
||||||
<div v-if="showApiEditDialog" class="dialog-overlay dialog-overlay-top" @keyup.esc="closeApiEditDialog" tabindex="-1" ref="apiEditDialogOverlay">
|
<div v-if="showApiEditDialog" class="dialog-overlay dialog-overlay-top" @keyup.esc="closeApiEditDialog" tabindex="-1" ref="apiEditDialogOverlay">
|
||||||
<div class="dialog api-edit-dialog" @click.stop>
|
<div class="dialog api-edit-dialog" @click.stop>
|
||||||
<div class="dialog-header">
|
<div class="dialog-header">
|
||||||
<div class="dialog-title">
|
<div class="dialog-title">
|
||||||
<Key size="18" />
|
<Key size="18" />
|
||||||
编辑 API 配置
|
编辑 API 配置
|
||||||
</div>
|
|
||||||
<button class="side-panel-close" @click="closeApiEditDialog">
|
|
||||||
<svg viewBox="0 0 10 10">
|
|
||||||
<line x1="0" y1="0" x2="10" y2="10" />
|
|
||||||
<line x1="10" y1="0" x2="0" y2="10" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<button class="side-panel-close" @click="closeApiEditDialog">
|
||||||
|
<svg viewBox="0 0 10 10">
|
||||||
|
<line x1="0" y1="0" x2="10" y2="10" />
|
||||||
|
<line x1="10" y1="0" x2="0" y2="10" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">认证方式</label>
|
||||||
|
<select class="form-select" v-model="editingApiData.selectedAuthType">
|
||||||
|
<option value="iflow">iFlow</option>
|
||||||
|
<option value="api">API Key</option>
|
||||||
|
<option value="openai-compatible">OpenAI 兼容</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">API Key</label>
|
||||||
|
<input type="password" class="form-input" v-model="editingApiData.apiKey" placeholder="sk-cp-XXXXX..." />
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">认证方式</label>
|
<label class="form-label">Base URL</label>
|
||||||
<select class="form-select" v-model="editingApiData.selectedAuthType">
|
<input type="text" class="form-input" v-model="editingApiData.baseUrl" placeholder="https://api.minimaxi.com/v1" />
|
||||||
<option value="iflow">iFlow</option>
|
|
||||||
<option value="api">API Key</option>
|
|
||||||
<option value="openai-compatible">OpenAI 兼容</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">API Key</label>
|
<label class="form-label">模型名称</label>
|
||||||
<input type="password" class="form-input" v-model="editingApiData.apiKey" placeholder="sk-cp-XXXXX..." />
|
<input type="text" class="form-input" v-model="editingApiData.modelName" placeholder="MiniMax-M2.7" />
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">Base URL</label>
|
|
||||||
<input type="text" class="form-input" v-model="editingApiData.baseUrl" placeholder="https://api.minimaxi.com/v1" />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">模型名称</label>
|
|
||||||
<input type="text" class="form-input" v-model="editingApiData.modelName" placeholder="MiniMax-M2.7" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">搜索 API Key</label>
|
|
||||||
<input type="password" class="form-input" v-model="editingApiData.searchApiKey" placeholder="sk-XXXXX..." />
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="form-label">CNA</label>
|
|
||||||
<input type="text" class="form-input" v-model="editingApiData.cna" placeholder="CNA 标识" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-actions">
|
<div class="form-group">
|
||||||
<button class="btn btn-secondary" @click="closeApiEditDialog">取消</button>
|
<label class="form-label">搜索 API Key</label>
|
||||||
<button class="btn btn-primary" @click="saveApiEdit">
|
<input type="password" class="form-input" v-model="editingApiData.searchApiKey" placeholder="sk-XXXXX..." />
|
||||||
<Save size="14" /> 保存
|
</div>
|
||||||
</button>
|
<div class="form-group">
|
||||||
|
<label class="form-label">CNA</label>
|
||||||
|
<input type="text" class="form-input" v-model="editingApiData.cna" placeholder="CNA 标识" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dialog-actions">
|
||||||
|
<button class="btn btn-secondary" @click="closeApiEditDialog">取消</button>
|
||||||
|
<button class="btn btn-primary" @click="saveApiEdit"> <Save size="14" /> 保存 </button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Server Side Panel -->
|
|
||||||
<div v-if="showServerPanel" class="side-panel-overlay" @keyup.esc="closeServerPanel" tabindex="-1" ref="serverPanelOverlay">
|
<!-- Server Side Panel -->
|
||||||
<div class="side-panel" @click.stop> <div class="side-panel-header">
|
<div v-if="showServerPanel" class="side-panel-overlay" @keyup.esc="closeServerPanel" tabindex="-1" ref="serverPanelOverlay">
|
||||||
|
<div class="side-panel" @click.stop>
|
||||||
|
<div class="side-panel-header">
|
||||||
<div class="side-panel-title">
|
<div class="side-panel-title">
|
||||||
<Server size="18" />
|
<Server size="18" />
|
||||||
{{ isEditingServer ? '编辑服务器' : '添加服务器' }}
|
{{ isEditingServer ? '编辑服务器' : '添加服务器' }}
|
||||||
@@ -452,7 +438,7 @@ const editingServerData = ref({
|
|||||||
command: 'npx',
|
command: 'npx',
|
||||||
cwd: '.',
|
cwd: '.',
|
||||||
args: '',
|
args: '',
|
||||||
env: ''
|
env: '',
|
||||||
})
|
})
|
||||||
const showApiEditDialog = ref(false)
|
const showApiEditDialog = ref(false)
|
||||||
const editingApiProfileName = ref('')
|
const editingApiProfileName = ref('')
|
||||||
@@ -462,7 +448,7 @@ const editingApiData = ref({
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
modelName: '',
|
modelName: '',
|
||||||
searchApiKey: '',
|
searchApiKey: '',
|
||||||
cna: ''
|
cna: '',
|
||||||
})
|
})
|
||||||
const showApiCreateDialog = ref(false)
|
const showApiCreateDialog = ref(false)
|
||||||
const creatingApiData = ref({
|
const creatingApiData = ref({
|
||||||
@@ -472,7 +458,7 @@ const creatingApiData = ref({
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
modelName: '',
|
modelName: '',
|
||||||
searchApiKey: '',
|
searchApiKey: '',
|
||||||
cna: ''
|
cna: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
// Load API profiles list
|
// Load API profiles list
|
||||||
@@ -507,9 +493,7 @@ const switchApiProfile = async () => {
|
|||||||
// Create new API profile
|
// Create new API profile
|
||||||
|
|
||||||
const createNewApiProfile = () => {
|
const createNewApiProfile = () => {
|
||||||
|
|
||||||
creatingApiData.value = {
|
creatingApiData.value = {
|
||||||
|
|
||||||
name: '',
|
name: '',
|
||||||
|
|
||||||
selectedAuthType: 'iflow',
|
selectedAuthType: 'iflow',
|
||||||
@@ -522,26 +506,18 @@ const createNewApiProfile = () => {
|
|||||||
|
|
||||||
searchApiKey: '',
|
searchApiKey: '',
|
||||||
|
|
||||||
cna: ''
|
cna: '',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showApiCreateDialog.value = true
|
showApiCreateDialog.value = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Close API create dialog
|
// Close API create dialog
|
||||||
|
|
||||||
const closeApiCreateDialog = () => {
|
const closeApiCreateDialog = () => {
|
||||||
|
|
||||||
showApiCreateDialog.value = false
|
showApiCreateDialog.value = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Save API create
|
// Save API create
|
||||||
const saveApiCreate = async () => {
|
const saveApiCreate = async () => {
|
||||||
const name = creatingApiData.value.name.trim()
|
const name = creatingApiData.value.name.trim()
|
||||||
@@ -559,7 +535,7 @@ const saveApiCreate = async () => {
|
|||||||
baseUrl: creatingApiData.value.baseUrl,
|
baseUrl: creatingApiData.value.baseUrl,
|
||||||
modelName: creatingApiData.value.modelName,
|
modelName: creatingApiData.value.modelName,
|
||||||
searchApiKey: creatingApiData.value.searchApiKey,
|
searchApiKey: creatingApiData.value.searchApiKey,
|
||||||
cna: creatingApiData.value.cna
|
cna: creatingApiData.value.cna,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存配置数据
|
// 保存配置数据
|
||||||
@@ -569,7 +545,7 @@ const saveApiCreate = async () => {
|
|||||||
if (!data.apiProfiles) data.apiProfiles = {}
|
if (!data.apiProfiles) data.apiProfiles = {}
|
||||||
data.apiProfiles[name] = profileData
|
data.apiProfiles[name] = profileData
|
||||||
await window.electronAPI.saveSettings(data)
|
await window.electronAPI.saveSettings(data)
|
||||||
|
|
||||||
showApiCreateDialog.value = false
|
showApiCreateDialog.value = false
|
||||||
await loadApiProfiles()
|
await loadApiProfiles()
|
||||||
await showMessage({ type: 'info', title: '创建成功', message: `配置 "${name}" 已创建` })
|
await showMessage({ type: 'info', title: '创建成功', message: `配置 "${name}" 已创建` })
|
||||||
@@ -580,7 +556,7 @@ const saveApiCreate = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete API profile
|
// Delete API profile
|
||||||
const deleteApiProfile = async (name) => {
|
const deleteApiProfile = async name => {
|
||||||
const profileName = name || currentApiProfile.value
|
const profileName = name || currentApiProfile.value
|
||||||
if (profileName === 'default') {
|
if (profileName === 'default') {
|
||||||
await showMessage({ type: 'warning', title: '无法删除', message: '不能删除默认配置' })
|
await showMessage({ type: 'warning', title: '无法删除', message: '不能删除默认配置' })
|
||||||
@@ -593,7 +569,7 @@ const deleteApiProfile = async (name) => {
|
|||||||
title: '删除配置',
|
title: '删除配置',
|
||||||
placeholder: `确定要删除配置 "${profileName}" 吗?`,
|
placeholder: `确定要删除配置 "${profileName}" 吗?`,
|
||||||
callback: resolve,
|
callback: resolve,
|
||||||
isConfirm: true
|
isConfirm: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!confirmed) return
|
if (!confirmed) return
|
||||||
@@ -634,7 +610,7 @@ const renameApiProfile = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Select API profile (click on card)
|
// Select API profile (click on card)
|
||||||
const selectApiProfile = async (name) => {
|
const selectApiProfile = async name => {
|
||||||
if (name === currentApiProfile.value) return
|
if (name === currentApiProfile.value) return
|
||||||
currentApiProfile.value = name
|
currentApiProfile.value = name
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
@@ -643,13 +619,13 @@ const selectApiProfile = async (name) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get profile initial letter for icon
|
// Get profile initial letter for icon
|
||||||
const getProfileInitial = (name) => {
|
const getProfileInitial = name => {
|
||||||
if (!name) return '?'
|
if (!name) return '?'
|
||||||
return name.charAt(0).toUpperCase()
|
return name.charAt(0).toUpperCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get profile URL for display
|
// Get profile URL for display
|
||||||
const getProfileUrl = (name) => {
|
const getProfileUrl = name => {
|
||||||
if (!settings.value.apiProfiles || !settings.value.apiProfiles[name]) {
|
if (!settings.value.apiProfiles || !settings.value.apiProfiles[name]) {
|
||||||
return '未配置'
|
return '未配置'
|
||||||
}
|
}
|
||||||
@@ -667,7 +643,7 @@ const profileColors = [
|
|||||||
'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)', // blue
|
'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)', // blue
|
||||||
]
|
]
|
||||||
|
|
||||||
const getProfileIconStyle = (name) => {
|
const getProfileIconStyle = name => {
|
||||||
if (name === 'default') {
|
if (name === 'default') {
|
||||||
return { background: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)' }
|
return { background: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)' }
|
||||||
}
|
}
|
||||||
@@ -681,48 +657,34 @@ const getProfileIconStyle = (name) => {
|
|||||||
|
|
||||||
// Duplicate API profile
|
// Duplicate API profile
|
||||||
|
|
||||||
const duplicateApiProfile = async (name) => {
|
const duplicateApiProfile = async name => {
|
||||||
|
|
||||||
const newName = await new Promise(resolve => {
|
const newName = await new Promise(resolve => {
|
||||||
|
|
||||||
showInputDialog.value = {
|
showInputDialog.value = {
|
||||||
|
|
||||||
show: true,
|
show: true,
|
||||||
|
|
||||||
title: '复制配置',
|
title: '复制配置',
|
||||||
|
|
||||||
placeholder: '请输入新配置的名称',
|
placeholder: '请输入新配置的名称',
|
||||||
|
|
||||||
callback: resolve
|
callback: resolve,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!newName) return
|
if (!newName) return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const result = await window.electronAPI.duplicateApiProfile(name, newName)
|
const result = await window.electronAPI.duplicateApiProfile(name, newName)
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|
||||||
await loadApiProfiles()
|
await loadApiProfiles()
|
||||||
|
|
||||||
await showMessage({ type: 'info', title: '复制成功', message: `配置已复制为 "${newName}"` })
|
await showMessage({ type: 'info', title: '复制成功', message: `配置已复制为 "${newName}"` })
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
await showMessage({ type: 'error', title: '复制失败', message: result.error })
|
await showMessage({ type: 'error', title: '复制失败', message: result.error })
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Open API edit dialog
|
// Open API edit dialog
|
||||||
const openApiEditDialog = (profileName) => {
|
const openApiEditDialog = profileName => {
|
||||||
// 保存正在编辑的配置名称
|
// 保存正在编辑的配置名称
|
||||||
editingApiProfileName.value = profileName
|
editingApiProfileName.value = profileName
|
||||||
// 从 apiProfiles 中加载指定配置的数据
|
// 从 apiProfiles 中加载指定配置的数据
|
||||||
@@ -733,34 +695,28 @@ const openApiEditDialog = (profileName) => {
|
|||||||
baseUrl: profile ? profile.baseUrl : settings.value.baseUrl || '',
|
baseUrl: profile ? profile.baseUrl : settings.value.baseUrl || '',
|
||||||
modelName: profile ? profile.modelName : settings.value.modelName || '',
|
modelName: profile ? profile.modelName : settings.value.modelName || '',
|
||||||
searchApiKey: profile ? profile.searchApiKey : settings.value.searchApiKey || '',
|
searchApiKey: profile ? profile.searchApiKey : settings.value.searchApiKey || '',
|
||||||
cna: profile ? profile.cna : settings.value.cna || ''
|
cna: profile ? profile.cna : settings.value.cna || '',
|
||||||
}
|
}
|
||||||
showApiEditDialog.value = true
|
showApiEditDialog.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Close API edit dialog
|
// Close API edit dialog
|
||||||
|
|
||||||
const closeApiEditDialog = () => {
|
const closeApiEditDialog = () => {
|
||||||
|
|
||||||
showApiEditDialog.value = false
|
showApiEditDialog.value = false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Save API edit
|
// Save API edit
|
||||||
const saveApiEdit = async () => {
|
const saveApiEdit = async () => {
|
||||||
if (!settings.value.apiProfiles) {
|
if (!settings.value.apiProfiles) {
|
||||||
settings.value.apiProfiles = {}
|
settings.value.apiProfiles = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保配置对象存在
|
// 确保配置对象存在
|
||||||
if (!settings.value.apiProfiles[editingApiProfileName.value]) {
|
if (!settings.value.apiProfiles[editingApiProfileName.value]) {
|
||||||
settings.value.apiProfiles[editingApiProfileName.value] = {}
|
settings.value.apiProfiles[editingApiProfileName.value] = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存到指定的配置
|
// 保存到指定的配置
|
||||||
settings.value.apiProfiles[editingApiProfileName.value].selectedAuthType = editingApiData.value.selectedAuthType
|
settings.value.apiProfiles[editingApiProfileName.value].selectedAuthType = editingApiData.value.selectedAuthType
|
||||||
settings.value.apiProfiles[editingApiProfileName.value].apiKey = editingApiData.value.apiKey
|
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].modelName = editingApiData.value.modelName
|
||||||
settings.value.apiProfiles[editingApiProfileName.value].searchApiKey = editingApiData.value.searchApiKey
|
settings.value.apiProfiles[editingApiProfileName.value].searchApiKey = editingApiData.value.searchApiKey
|
||||||
settings.value.apiProfiles[editingApiProfileName.value].cna = editingApiData.value.cna
|
settings.value.apiProfiles[editingApiProfileName.value].cna = editingApiData.value.cna
|
||||||
|
|
||||||
showApiEditDialog.value = false
|
showApiEditDialog.value = false
|
||||||
|
|
||||||
// 自动保存到文件
|
// 自动保存到文件
|
||||||
const result = await window.electronAPI.saveSettings(settings.value)
|
const result = await window.electronAPI.saveSettings(settings.value)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -779,8 +735,6 @@ const saveApiEdit = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
const result = await window.electronAPI.loadSettings()
|
const result = await window.electronAPI.loadSettings()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -861,7 +815,7 @@ const openAddServerPanel = () => {
|
|||||||
command: 'npx',
|
command: 'npx',
|
||||||
cwd: '.',
|
cwd: '.',
|
||||||
args: '-y\npackage-name',
|
args: '-y\npackage-name',
|
||||||
env: ''
|
env: '',
|
||||||
}
|
}
|
||||||
showServerPanel.value = true
|
showServerPanel.value = true
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -869,7 +823,7 @@ const openAddServerPanel = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const openEditServerPanel = (name) => {
|
const openEditServerPanel = name => {
|
||||||
const server = settings.value.mcpServers[name]
|
const server = settings.value.mcpServers[name]
|
||||||
if (!server) return
|
if (!server) return
|
||||||
isEditingServer.value = true
|
isEditingServer.value = true
|
||||||
@@ -879,7 +833,7 @@ const openEditServerPanel = (name) => {
|
|||||||
command: server.command || '',
|
command: server.command || '',
|
||||||
cwd: server.cwd || '.',
|
cwd: server.cwd || '.',
|
||||||
args: (server.args || []).join('\n'),
|
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
|
showServerPanel.value = true
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -911,7 +865,10 @@ const saveServerFromPanel = async () => {
|
|||||||
command: editingServerData.value.command.trim(),
|
command: editingServerData.value.command.trim(),
|
||||||
description: editingServerData.value.description.trim(),
|
description: editingServerData.value.description.trim(),
|
||||||
cwd: editingServerData.value.cwd.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()
|
const envText = editingServerData.value.env.trim()
|
||||||
@@ -985,11 +942,14 @@ const closeMessageDialog = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Watch for dialog open to set default value
|
// Watch for dialog open to set default value
|
||||||
watch(() => showInputDialog.value.show, (show) => {
|
watch(
|
||||||
if (show && showInputDialog.value.defaultValue) {
|
() => showInputDialog.value.show,
|
||||||
inputDialogValue.value = showInputDialog.value.defaultValue
|
show => {
|
||||||
}
|
if (show && showInputDialog.value.defaultValue) {
|
||||||
})
|
inputDialogValue.value = showInputDialog.value.defaultValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadApiProfiles()
|
await loadApiProfiles()
|
||||||
@@ -1813,11 +1773,21 @@ body {
|
|||||||
animation: fadeIn 0.3s ease backwards;
|
animation: fadeIn 0.3s ease backwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-item:nth-child(1) { animation-delay: 0.02s; }
|
.profile-item:nth-child(1) {
|
||||||
.profile-item:nth-child(2) { animation-delay: 0.04s; }
|
animation-delay: 0.02s;
|
||||||
.profile-item:nth-child(3) { animation-delay: 0.06s; }
|
}
|
||||||
.profile-item:nth-child(4) { animation-delay: 0.08s; }
|
.profile-item:nth-child(2) {
|
||||||
.profile-item:nth-child(5) { animation-delay: 0.1s; }
|
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 {
|
.profile-item:hover {
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
@@ -1828,7 +1798,9 @@ body {
|
|||||||
.profile-item.active {
|
.profile-item.active {
|
||||||
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(139, 92, 246, 0.05) 100%);
|
background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(139, 92, 246, 0.05) 100%);
|
||||||
border-color: var(--accent);
|
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 {
|
.profile-icon {
|
||||||
|
|||||||
Reference in New Issue
Block a user