feat: Refactor channel management and UI components
- Removed hardcoded channel data from `channel.ts` and replaced it with a dynamic channel dictionary. - Introduced a new Pinia store `channel.ts` to manage selected and available channels. - Reworked `AddChannelDialog.vue` to allow users to search and select channels dynamically. - Updated `TaskCenter.vue` to utilize the new channel store and handle empty channel selections gracefully. - Enhanced IPC communication for loading and saving selected channels in the configuration. - Adjusted `runTaskOperationService.ts` to ensure proper handling of channel data. - Improved styling and structure of UI components for better user experience.
This commit is contained in:
@@ -152,7 +152,9 @@ if (started) {
|
||||
// logManager.error('unhandledRejection', reason, promise);
|
||||
// });
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
await configManager.init();
|
||||
|
||||
gatewayManager.init();
|
||||
|
||||
onProviderChange(() => {
|
||||
|
||||
@@ -205,14 +205,20 @@ export function runTaskOperationService() {
|
||||
|
||||
openedTabIndexByChannelName.clear()
|
||||
|
||||
if (Array.isArray(channels)) {
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
const name = channels[i]?.channelName
|
||||
if (name) openedTabIndexByChannelName.set(String(name), i)
|
||||
}
|
||||
const validChannels = Array.isArray(channels)
|
||||
? channels.filter((c) => c && typeof c === 'object' && c.channelUrl)
|
||||
: []
|
||||
|
||||
if (validChannels.length === 0) {
|
||||
return { success: false, error: '没有可用的渠道配置' }
|
||||
}
|
||||
|
||||
const result = await executeScriptServiceInstance.executeScript(scriptPath, { channels })
|
||||
for (let i = 0; i < validChannels.length; i++) {
|
||||
const name = validChannels[i]?.channelName
|
||||
if (name) openedTabIndexByChannelName.set(String(name), i)
|
||||
}
|
||||
|
||||
const result = await executeScriptServiceInstance.executeScript(scriptPath, { channels: validChannels })
|
||||
return { success: true, result }
|
||||
} catch (error) {
|
||||
return { success: false, error: (error as any)?.message || 'open channel failed' }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { app, BrowserWindow, ipcMain } from 'electron'
|
||||
import { BrowserWindow, ipcMain } from 'electron'
|
||||
import type { ConfigKeys, IConfig } from '@lib/types'
|
||||
import { CONFIG_KEYS, IPC_EVENTS } from '@lib/constants'
|
||||
import { debounce, simpleCloneDeep } from '@lib/utils'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { debounce } from '@lib/utils'
|
||||
|
||||
import logManager from '@electron/service/logger'
|
||||
|
||||
@@ -15,33 +13,47 @@ const DEFAULT_CONFIG: IConfig = {
|
||||
[CONFIG_KEYS.MINIMIZE_TO_TRAY]: false,
|
||||
[CONFIG_KEYS.PROVIDER]: '',
|
||||
[CONFIG_KEYS.DEFAULT_MODEL]: null,
|
||||
[CONFIG_KEYS.SELECTED_CHANNELS]: [],
|
||||
}
|
||||
|
||||
export class ConfigService {
|
||||
private static _instance: ConfigService;
|
||||
private _config: IConfig;
|
||||
private _configPath: string;
|
||||
private _store: any;
|
||||
private _defaultConfig: IConfig = DEFAULT_CONFIG;
|
||||
|
||||
private _listeners: Array<(config: IConfig) => void> = [];
|
||||
|
||||
|
||||
private constructor() {
|
||||
// 获取配置文件路径
|
||||
this._configPath = path.join(app.getPath('userData'), 'config.json');
|
||||
// 加载配置
|
||||
this._config = this._loadConfig();
|
||||
// 设置 IPC 事件
|
||||
// store 通过 init() 异步初始化,避免 ESM 模块在 CJS 打包环境中同步导入的问题
|
||||
}
|
||||
|
||||
public async init() {
|
||||
if (this._store) return;
|
||||
const { default: Store } = await import('electron-store');
|
||||
this._store = new Store<IConfig>({
|
||||
name: 'config',
|
||||
defaults: DEFAULT_CONFIG,
|
||||
});
|
||||
this._setupIpcEvents();
|
||||
logManager.info('ConfigService initialized successfully.')
|
||||
}
|
||||
|
||||
private _ensureStore() {
|
||||
if (!this._store) {
|
||||
throw new Error('ConfigService has not been initialized. Call init() first.');
|
||||
}
|
||||
}
|
||||
|
||||
private _setupIpcEvents() {
|
||||
const duration = 200;
|
||||
const handelUpdate = debounce((val) => this.update(val), duration);
|
||||
|
||||
ipcMain.handle(IPC_EVENTS.GET_CONFIG, (_, key) => this.get(key));
|
||||
ipcMain.on(IPC_EVENTS.SET_CONFIG, (_, key, val) => this.set(key, val));
|
||||
ipcMain.handle(IPC_EVENTS.SET_CONFIG, (_, key, val) => {
|
||||
this.set(key, val);
|
||||
return { success: true };
|
||||
});
|
||||
ipcMain.on(IPC_EVENTS.UPDATE_CONFIG, (_, updates) => handelUpdate(updates));
|
||||
}
|
||||
|
||||
@@ -52,66 +64,51 @@ export class ConfigService {
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
private _loadConfig(): IConfig {
|
||||
try {
|
||||
if (fs.existsSync(this._configPath)) {
|
||||
const configContent = fs.readFileSync(this._configPath, 'utf-8');
|
||||
const config = { ...this._defaultConfig, ...JSON.parse(configContent) };
|
||||
logManager.info('Config loaded successfully from:', this._configPath);
|
||||
return config;
|
||||
}
|
||||
} catch (error) {
|
||||
logManager.error('Failed to load config:', error);
|
||||
}
|
||||
return { ...this._defaultConfig };
|
||||
}
|
||||
|
||||
private _saveConfig(): void {
|
||||
try {
|
||||
// 确保目录存在
|
||||
fs.mkdirSync(path.dirname(this._configPath), { recursive: true });
|
||||
// 写入
|
||||
fs.writeFileSync(this._configPath, JSON.stringify(this._config, null, 2), 'utf-8');
|
||||
// 通知监听者
|
||||
this._notifyListeners();
|
||||
|
||||
logManager.info('Config saved successfully to:', this._configPath);
|
||||
} catch (error) {
|
||||
logManager.error('Failed to save config:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private _notifyListeners(): void {
|
||||
BrowserWindow.getAllWindows().forEach(win => win.webContents.send(IPC_EVENTS.CONFIG_UPDATED, this._config));
|
||||
this._listeners.forEach(listener => listener({ ...this._config }));
|
||||
this._ensureStore();
|
||||
BrowserWindow.getAllWindows().forEach(win => win.webContents.send(IPC_EVENTS.CONFIG_UPDATED, this._store.store));
|
||||
this._listeners.forEach(listener => listener({ ...this._store.store }));
|
||||
}
|
||||
|
||||
public getConfig(): IConfig {
|
||||
return simpleCloneDeep(this._config);
|
||||
if (!this._store) {
|
||||
return { ...this._defaultConfig };
|
||||
}
|
||||
return this._store.store;
|
||||
}
|
||||
|
||||
public get<T = any>(key: ConfigKeys): T {
|
||||
return this._config[key] as T
|
||||
if (!this._store) {
|
||||
return this._defaultConfig[key] as T;
|
||||
}
|
||||
return this._store.get(key) as T
|
||||
}
|
||||
|
||||
public set(key: ConfigKeys, value: unknown, autoSave: boolean = true): void {
|
||||
if (!(key in this._config)) return;
|
||||
const oldValue = this._config[key];
|
||||
this._ensureStore();
|
||||
const oldValue = this._store.get(key);
|
||||
if (oldValue === value) return;
|
||||
this._config[key] = value as never;
|
||||
logManager.debug(`Config set: ${key} = ${value}`);
|
||||
autoSave && this._saveConfig();
|
||||
this._store.set(key, value);
|
||||
logManager.info(`Config set: ${key} = ${JSON.stringify(value)}`);
|
||||
logManager.info(`Config file path: ${this._store.path}`);
|
||||
autoSave && this._notifyListeners();
|
||||
}
|
||||
|
||||
public update(updates: Partial<IConfig>, autoSave: boolean = true): void {
|
||||
this._config = { ...this._config, ...updates };
|
||||
autoSave && this._saveConfig();
|
||||
this._ensureStore();
|
||||
(Object.keys(updates) as ConfigKeys[]).forEach((key) => {
|
||||
this._store.set(key, updates[key]);
|
||||
});
|
||||
autoSave && this._notifyListeners();
|
||||
}
|
||||
|
||||
public resetToDefault(): void {
|
||||
this._config = { ...this._defaultConfig };
|
||||
this._ensureStore();
|
||||
(Object.keys(this._defaultConfig) as ConfigKeys[]).forEach((key) => {
|
||||
this._store.set(key, this._defaultConfig[key]);
|
||||
});
|
||||
logManager.info('Config reset to default.');
|
||||
this._saveConfig();
|
||||
this._notifyListeners();
|
||||
}
|
||||
|
||||
public onConfigChange(listener: ((config: IConfig) => void)): () => void {
|
||||
|
||||
Reference in New Issue
Block a user