From 23f4d7508bdd4e84c13a8c006cbbea6fe9491b05 Mon Sep 17 00:00:00 2001 From: yuantao Date: Thu, 16 Apr 2026 10:34:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9E=B6=E6=9E=84=20API=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E9=80=BB=E8=BE=91=E4=BB=8E=E5=A4=9A=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E6=94=B9=E4=B8=BA=E5=8D=95=E6=96=87=E4=BB=B6=E5=86=85?= =?UTF-8?q?apiProfiles=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.js | 261 ++++++++++++++++++++++++++++++++++++++-------------- preload.js | 18 ++-- src/App.vue | 106 +++++++++++---------- 3 files changed, 260 insertions(+), 125 deletions(-) diff --git a/main.js b/main.js index 8f364a8..2bf1ad9 100644 --- a/main.js +++ b/main.js @@ -3,14 +3,11 @@ const path = require('path') const fs = require('fs') console.log('main.js loaded') -console.log('app.getPath(\"home\"):', app.getPath('home')) +console.log('app.getPath("home"):', app.getPath('home')) -const DEFAULT_SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json') -const CONFIG_DIR = path.join(app.getPath('home'), '.iflow', 'configs') -console.log('DEFAULT_SETTINGS_FILE:', DEFAULT_SETTINGS_FILE) -console.log('CONFIG_DIR:', CONFIG_DIR) +const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json') +console.log('SETTINGS_FILE:', SETTINGS_FILE) -let currentSettingsFile = DEFAULT_SETTINGS_FILE let mainWindow const isDev = process.argv.includes('--dev') @@ -88,91 +85,206 @@ ipcMain.on('window-close', () => mainWindow.close()) ipcMain.handle('is-maximized', () => mainWindow.isMaximized()) -// Get current config file path -ipcMain.handle('get-current-config', () => { - return { filePath: currentSettingsFile, fileName: path.basename(currentSettingsFile) } -}) +// API 配置相关的字段 +const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna'] -// List all config files -ipcMain.handle('list-configs', async () => { +// 读取设置文件 +function readSettings() { + if (!fs.existsSync(SETTINGS_FILE)) { + return null + } + const data = fs.readFileSync(SETTINGS_FILE, 'utf-8') + return JSON.parse(data) +} + +// 写入设置文件 +function writeSettings(data) { + if (fs.existsSync(SETTINGS_FILE)) { + const backupPath = SETTINGS_FILE + '.bak' + fs.copyFileSync(SETTINGS_FILE, backupPath) + } + fs.writeFileSync(SETTINGS_FILE, JSON.stringify(data, null, 2), 'utf-8') +} + +// 获取 API 配置列表 +ipcMain.handle('list-api-profiles', async () => { try { - const configs = [] - // Always include default settings - if (fs.existsSync(DEFAULT_SETTINGS_FILE)) { - const stats = fs.statSync(DEFAULT_SETTINGS_FILE) - configs.push({ - name: '默认配置', - filePath: DEFAULT_SETTINGS_FILE, - modified: stats.mtime - }) + const settings = readSettings() + if (!settings) { + return { success: false, error: '配置文件不存在', profiles: [], currentProfile: '' } } - // Add configs from CONFIG_DIR - if (fs.existsSync(CONFIG_DIR)) { - const files = fs.readdirSync(CONFIG_DIR).filter(f => f.endsWith('.json')) - for (const file of files) { - const filePath = path.join(CONFIG_DIR, file) - const stats = fs.statSync(filePath) - configs.push({ - name: file.replace('.json', ''), - filePath: filePath, - modified: stats.mtime - }) - } - } else { - fs.mkdirSync(CONFIG_DIR, { recursive: true }) + + const profiles = settings.apiProfiles || {} + const profileList = Object.keys(profiles).map(name => ({ + name, + isDefault: name === 'default' + })) + + return { + success: true, + profiles: profileList, + currentProfile: settings.currentApiProfile || 'default' } - return { success: true, configs: configs } } catch (error) { - return { success: false, error: error.message, configs: [] } + return { success: false, error: error.message, profiles: [], currentProfile: '' } } }) -// Create new config -ipcMain.handle('create-config', async (event, name) => { +// 切换 API 配置 +ipcMain.handle('switch-api-profile', async (event, profileName) => { try { - if (!fs.existsSync(CONFIG_DIR)) { - fs.mkdirSync(CONFIG_DIR, { recursive: true }) + const settings = readSettings() + if (!settings) { + return { success: false, error: '配置文件不存在' } } - const fileName = name + '.json' - const filePath = path.join(CONFIG_DIR, fileName) - if (fs.existsSync(filePath)) { - return { success: false, error: '配置文件已存在' } + + const profiles = settings.apiProfiles || {} + if (!profiles[profileName]) { + return { success: false, error: `配置 "${profileName}" 不存在` } } - let data = {} - if (fs.existsSync(currentSettingsFile)) { - const content = fs.readFileSync(currentSettingsFile, 'utf-8') - data = JSON.parse(content) + + // 保存当前配置到 apiProfiles(如果当前配置存在) + const currentProfile = settings.currentApiProfile || 'default' + if (profiles[currentProfile]) { + const currentConfig = {} + for (const field of API_FIELDS) { + if (settings[field] !== undefined) { + currentConfig[field] = settings[field] + } + } + profiles[currentProfile] = currentConfig } - fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8') - return { success: true, filePath: filePath } + + // 从 apiProfiles 加载新配置到主字段 + const newConfig = profiles[profileName] + for (const field of API_FIELDS) { + if (newConfig[field] !== undefined) { + 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 } } }) -// Delete config -ipcMain.handle('delete-config', async (event, filePath) => { +// 创建新的 API 配置 +ipcMain.handle('create-api-profile', async (event, name) => { try { - if (filePath === DEFAULT_SETTINGS_FILE) { - return { success: false, error: '不能删除默认配置' } + const settings = readSettings() + if (!settings) { + return { success: false, error: '配置文件不存在' } } - if (fs.existsSync(filePath)) { - fs.unlinkSync(filePath) + + if (!settings.apiProfiles) { + settings.apiProfiles = { default: {} } + // 初始化 default 配置 + for (const field of API_FIELDS) { + if (settings[field] !== undefined) { + settings.apiProfiles.default[field] = settings[field] + } + } } + + if (settings.apiProfiles[name]) { + return { success: false, error: `配置 "${name}" 已存在` } + } + + // 复制当前配置到新配置 + const newConfig = {} + for (const field of API_FIELDS) { + if (settings[field] !== undefined) { + newConfig[field] = settings[field] + } + } + settings.apiProfiles[name] = newConfig + + writeSettings(settings) + return { success: true } } catch (error) { return { success: false, error: error.message } } }) -// Switch to config -ipcMain.handle('switch-config', async (event, filePath) => { +// 删除 API 配置 +ipcMain.handle('delete-api-profile', async (event, name) => { try { - if (!fs.existsSync(filePath)) { + const settings = readSettings() + if (!settings) { return { success: false, error: '配置文件不存在' } } - currentSettingsFile = filePath - return { success: true, filePath: currentSettingsFile } + + 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' + if (profiles.default) { + for (const field of API_FIELDS) { + if (profiles.default[field] !== undefined) { + settings[field] = profiles.default[field] + } + } + } + } + + 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 } } @@ -181,10 +293,10 @@ ipcMain.handle('switch-config', async (event, filePath) => { // IPC Handlers ipcMain.handle('load-settings', async () => { try { - if (!fs.existsSync(currentSettingsFile)) { + if (!fs.existsSync(SETTINGS_FILE)) { return { success: false, error: 'File not found', data: null } } - const data = fs.readFileSync(currentSettingsFile, 'utf-8') + const data = fs.readFileSync(SETTINGS_FILE, 'utf-8') const json = JSON.parse(data) return { success: true, data: json } } catch (error) { @@ -194,11 +306,22 @@ ipcMain.handle('load-settings', async () => { ipcMain.handle('save-settings', async (event, data) => { try { - if (fs.existsSync(currentSettingsFile)) { - const backupPath = currentSettingsFile + '.bak' - fs.copyFileSync(currentSettingsFile, backupPath) + // 保存时同步更新 apiProfiles 中的当前配置 + const currentProfile = data.currentApiProfile || 'default' + if (!data.apiProfiles) { + data.apiProfiles = {} } - fs.writeFileSync(currentSettingsFile, JSON.stringify(data, null, 2), 'utf-8') + + // 更新当前配置到 apiProfiles + const currentConfig = {} + for (const field of API_FIELDS) { + if (data[field] !== undefined) { + currentConfig[field] = data[field] + } + } + data.apiProfiles[currentProfile] = currentConfig + + writeSettings(data) return { success: true } } catch (error) { return { success: false, error: error.message } @@ -207,4 +330,4 @@ ipcMain.handle('save-settings', async (event, data) => { 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/preload.js b/preload.js index e983d1d..7e6cdc4 100644 --- a/preload.js +++ b/preload.js @@ -1,17 +1,21 @@ const { contextBridge, ipcRenderer } = require('electron') contextBridge.exposeInMainWorld('electronAPI', { + // 基本设置操作 loadSettings: () => ipcRenderer.invoke('load-settings'), saveSettings: (data) => ipcRenderer.invoke('save-settings', data), showMessage: (options) => ipcRenderer.invoke('show-message', options), + + // 窗口控制 isMaximized: () => ipcRenderer.invoke('is-maximized'), minimize: () => ipcRenderer.send('window-minimize'), maximize: () => ipcRenderer.send('window-maximize'), close: () => ipcRenderer.send('window-close'), - getCurrentConfig: () => ipcRenderer.invoke('get-current-config'), - listConfigs: () => ipcRenderer.invoke('list-configs'), - createConfig: (name) => ipcRenderer.invoke('create-config', name), - deleteConfig: (filePath) => ipcRenderer.invoke('delete-config', filePath), - switchConfig: (filePath) => ipcRenderer.invoke('switch-config', filePath) -}) - + + // API 配置管理(单文件内多配置) + listApiProfiles: () => ipcRenderer.invoke('list-api-profiles'), + switchApiProfile: (profileName) => ipcRenderer.invoke('switch-api-profile', profileName), + createApiProfile: (name) => ipcRenderer.invoke('create-api-profile', name), + deleteApiProfile: (name) => ipcRenderer.invoke('delete-api-profile', name), + renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName) +}) \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 07e83f0..7e3ae2a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -126,17 +126,17 @@
- - +
- - @@ -263,7 +263,7 @@ @@ -306,7 +306,9 @@ const settings = ref({ baseUrl: '', modelName: '', searchApiKey: '', - cna: '' + cna: '', + currentApiProfile: 'default', + apiProfiles: { default: {} } }); const originalSettings = ref({}); @@ -314,73 +316,83 @@ const modified = ref(false); const currentSection = ref('general'); const currentServerName = ref(null); const isLoading = ref(true); -const configList = ref([]); -const currentConfigFilePath = ref(''); +const apiProfiles = ref([]); +const currentApiProfile = ref('default'); const showInputDialog = ref({ show: false, title: '', placeholder: '', callback: null }); const inputDialogValue = ref(''); -// Load config list -const loadConfigList = async () => { - const result = await window.electronAPI.listConfigs(); +// Load API profiles list +const loadApiProfiles = async () => { + const result = await window.electronAPI.listApiProfiles(); if (result.success) { - configList.value = result.configs; + apiProfiles.value = result.profiles; + currentApiProfile.value = result.currentProfile; } }; -// Switch config -const switchConfig = async () => { +// Switch API profile +const switchApiProfile = async () => { if (modified.value) { const confirmed = await new Promise((resolve) => { showInputDialog.value = { show: true, title: '切换配置', placeholder: '当前有未保存的更改,切换配置将丢失这些更改,确定要切换吗?', callback: resolve, isConfirm: true }; }); - if (!confirmed) return; - } - if (currentConfigFilePath.value) { - const result = await window.electronAPI.switchConfig(currentConfigFilePath.value); - if (result.success) { - await loadSettings(); + if (!confirmed) { + // 恢复到之前的值 + const result = await window.electronAPI.listApiProfiles(); + if (result.success) { + currentApiProfile.value = result.currentProfile; + } + return; } } + const result = await window.electronAPI.switchApiProfile(currentApiProfile.value); + if (result.success) { + const data = JSON.parse(JSON.stringify(result.data)); + if (!data.checkpointing) data.checkpointing = { enabled: true }; + if (!data.mcpServers) data.mcpServers = {}; + settings.value = data; + originalSettings.value = JSON.parse(JSON.stringify(data)); + modified.value = false; + } else { + await window.electronAPI.showMessage({ type: 'error', title: '切换失败', message: result.error }); + } }; -// Create new config -const createNewConfig = async () => { +// Create new API profile +const createNewApiProfile = async () => { const name = await new Promise((resolve) => { - showInputDialog.value = { show: true, title: '新建配置文件', placeholder: '请输入配置名称', callback: resolve }; + showInputDialog.value = { show: true, title: '新建配置', placeholder: '请输入配置名称', callback: resolve }; }); if (!name) return; - const result = await window.electronAPI.createConfig(name); + const result = await window.electronAPI.createApiProfile(name); if (result.success) { - await loadConfigList(); - currentConfigFilePath.value = result.filePath; - await loadSettings(); - await window.electronAPI.showMessage({ type: 'info', title: '创建成功', message: `配置文件 "${name}" 已创建` }); + await loadApiProfiles(); + await window.electronAPI.showMessage({ type: 'info', title: '创建成功', message: `配置 "${name}" 已创建` }); } else { await window.electronAPI.showMessage({ type: 'error', title: '创建失败', message: result.error }); } }; -// Delete config -const deleteConfig = async () => { - if (configList.value.length <= 1) { - await window.electronAPI.showMessage({ type: 'warning', title: '无法删除', message: '至少需要保留一个配置文件' }); +// Delete API profile +const deleteApiProfile = async () => { + if (currentApiProfile.value === 'default') { + await window.electronAPI.showMessage({ type: 'warning', title: '无法删除', message: '不能删除默认配置' }); return; } - const cfg = configList.value.find(c => c.filePath === currentConfigFilePath.value); - if (!cfg) return; const confirmed = await new Promise((resolve) => { - showInputDialog.value = { show: true, title: '删除配置文件', placeholder: `确定要删除配置文件 "${cfg.name}" 吗?`, callback: resolve, isConfirm: true }; + showInputDialog.value = { show: true, title: '删除配置', placeholder: `确定要删除配置 "${currentApiProfile.value}" 吗?`, callback: resolve, isConfirm: true }; }); if (!confirmed) return; - const result = await window.electronAPI.deleteConfig(currentConfigFilePath.value); + const result = await window.electronAPI.deleteApiProfile(currentApiProfile.value); if (result.success) { - await loadConfigList(); - if (configList.value.length > 0) { - currentConfigFilePath.value = configList.value[0].filePath; - await window.electronAPI.switchConfig(currentConfigFilePath.value); - await loadSettings(); - } - await window.electronAPI.showMessage({ type: 'info', title: '删除成功', message: `配置文件 "${cfg.name}" 已删除` }); + const data = JSON.parse(JSON.stringify(result.data)); + if (!data.checkpointing) data.checkpointing = { enabled: true }; + if (!data.mcpServers) data.mcpServers = {}; + settings.value = data; + originalSettings.value = JSON.parse(JSON.stringify(data)); + modified.value = false; + await loadApiProfiles(); + await window.electronAPI.showMessage({ type: 'info', title: '删除成功', message: `配置已删除` }); } else { await window.electronAPI.showMessage({ type: 'error', title: '删除失败', message: result.error }); } @@ -507,11 +519,7 @@ const closeInputDialog = (result) => { }; onMounted(async () => { - await loadConfigList(); - const current = await window.electronAPI.getCurrentConfig(); - if (current.filePath) { - currentConfigFilePath.value = current.filePath; - } + await loadApiProfiles(); await loadSettings(); });