From 9b02cc35b76c19e552f9ecccc353ee59fe6641c4 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 13 Nov 2024 19:15:19 -0500 Subject: [PATCH 01/13] Rename extra_model_config to extraModelConfig --- src/config/{extra_model_config.ts => extraModelConfig.ts} | 0 src/handlers/pathHandlers.ts | 2 +- src/install/index.ts | 0 src/install/resourcePaths.ts | 2 +- src/main.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename src/config/{extra_model_config.ts => extraModelConfig.ts} (100%) delete mode 100644 src/install/index.ts diff --git a/src/config/extra_model_config.ts b/src/config/extraModelConfig.ts similarity index 100% rename from src/config/extra_model_config.ts rename to src/config/extraModelConfig.ts diff --git a/src/handlers/pathHandlers.ts b/src/handlers/pathHandlers.ts index 4d374e0c..2de0d11c 100644 --- a/src/handlers/pathHandlers.ts +++ b/src/handlers/pathHandlers.ts @@ -1,7 +1,7 @@ import { app, dialog, ipcMain, shell } from 'electron'; import { IPC_CHANNELS } from '../constants'; import log from 'electron-log/main'; -import { getModelConfigPath } from '../config/extra_model_config'; +import { getModelConfigPath } from '../config/extraModelConfig'; import { getBasePath } from '../install/resourcePaths'; import type { SystemPaths } from '../preload'; import fs from 'fs'; diff --git a/src/install/index.ts b/src/install/index.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/install/resourcePaths.ts b/src/install/resourcePaths.ts index ea9ef07a..8e12aba1 100644 --- a/src/install/resourcePaths.ts +++ b/src/install/resourcePaths.ts @@ -1,5 +1,5 @@ import { app } from 'electron'; -import { getModelConfigPath, readBasePathFromConfig } from '../config/extra_model_config'; +import { getModelConfigPath, readBasePathFromConfig } from '../config/extraModelConfig'; import path from 'path'; export async function getBasePath(): Promise { diff --git a/src/main.ts b/src/main.ts index bf96e2d5..12951415 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import log from 'electron-log/main'; import * as Sentry from '@sentry/electron/main'; import * as net from 'net'; import { graphics } from 'systeminformation'; -import { createModelConfigFiles, getModelConfigPath } from './config/extra_model_config'; +import { createModelConfigFiles, getModelConfigPath } from './config/extraModelConfig'; import todesktop from '@todesktop/runtime'; import { DownloadManager } from './models/DownloadManager'; import { getModelsDirectory } from './utils'; From 9819af8d14e6e81f1bd66370d69465c2bc41311e Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 13 Nov 2024 19:16:38 -0500 Subject: [PATCH 02/13] Readonly constants --- src/config/extraModelConfig.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/extraModelConfig.ts b/src/config/extraModelConfig.ts index c88b5616..e434f67f 100644 --- a/src/config/extraModelConfig.ts +++ b/src/config/extraModelConfig.ts @@ -6,7 +6,7 @@ import { app } from 'electron'; export const EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; -interface ModelPaths { +export interface ModelPaths { comfyui: { base_path: string; is_default: boolean; @@ -51,7 +51,7 @@ const commonPaths = { instantid: 'models/instantid/', // End custom node model directories. custom_nodes: 'custom_nodes/', -}; +} as const; const configTemplates: Record = { win32: { @@ -72,7 +72,7 @@ const configTemplates: Record = { ...commonPaths, }, }, -}; +} as const; export function getModelConfigPath(): string { return path.join(app.getPath('userData'), EXTRA_MODEL_CONFIG_PATH); From 0cd47f37d7bd609f7b6ae34af4579fe8b396f123 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 13 Nov 2024 19:27:12 -0500 Subject: [PATCH 03/13] Refactor as class --- src/config/comfyServerConfig.ts | 135 ++++++++++++++++++++++++++++++++ src/config/extraModelConfig.ts | 131 ------------------------------- src/handlers/pathHandlers.ts | 4 +- src/install/resourcePaths.ts | 6 +- src/main.ts | 13 ++- 5 files changed, 146 insertions(+), 143 deletions(-) create mode 100644 src/config/comfyServerConfig.ts delete mode 100644 src/config/extraModelConfig.ts diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts new file mode 100644 index 00000000..8c3bb818 --- /dev/null +++ b/src/config/comfyServerConfig.ts @@ -0,0 +1,135 @@ +import * as fsPromises from 'node:fs/promises'; +import log from 'electron-log/main'; +import { stringify, parse } from 'yaml'; +import path from 'node:path'; +import { app } from 'electron'; + +interface ModelPaths { + comfyui: { + base_path: string; + is_default: boolean; + [key: string]: string | boolean; + }; +} + +export class ComfyServerConfig { + private static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; + + private static readonly commonPaths = { + is_default: true, + checkpoints: 'models/checkpoints/', + classifiers: 'models/classifiers/', + clip: 'models/clip/', + clip_vision: 'models/clip_vision/', + configs: 'models/configs/', + controlnet: 'models/controlnet/', + diffusers: 'models/diffusers/', + diffusion_models: 'models/diffusion_models/', + embeddings: 'models/embeddings/', + gligen: 'models/gligen/', + hypernetworks: 'models/hypernetworks/', + loras: 'models/loras/', + photomaker: 'models/photomaker/', + style_models: 'models/style_models/', + unet: 'models/unet/', + upscale_models: 'models/upscale_models/', + vae: 'models/vae/', + vae_approx: 'models/vae_approx/', + // TODO(robinhuang): Remove when we have a better way to specify base model paths. + animatediff_models: 'models/animatediff_models/', + animatediff_motion_lora: 'models/animatediff_motion_lora/', + animatediff_video_formats: 'models/animatediff_video_formats/', + ipadapter: 'models/ipadapter/', + liveportrait: 'models/liveportrait/', + insightface: 'models/insightface/', + layerstyle: 'models/layerstyle/', + LLM: 'models/LLM/', + Joy_caption: 'models/Joy_caption/', + sams: 'models/sams/', + blip: 'models/blip/', + CogVideo: 'models/CogVideo/', + xlabs: 'models/xlabs/', + instantid: 'models/instantid/', + // End custom node model directories. + custom_nodes: 'custom_nodes/', + } as const; + + private static readonly configTemplates: Record = { + win32: { + comfyui: { + base_path: '%USERPROFILE%/comfyui-electron', + ...this.commonPaths, + }, + }, + darwin: { + comfyui: { + base_path: '~/Library/Application Support/ComfyUI', + ...this.commonPaths, + }, + }, + linux: { + comfyui: { + base_path: '~/.config/ComfyUI', + ...this.commonPaths, + }, + }, + } as const; + + /** + * The path to the extra_models_config.yaml file. The config file is used for ComfyUI core to determine search paths + * for models and custom nodes. + */ + public static readonly configPath: string = path.join( + app.getPath('userData'), + ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH + ); + + public static async createModelConfigFiles(extraModelConfigPath: string, customBasePath?: string): Promise { + log.info(`Creating model config files in ${extraModelConfigPath} with base path ${customBasePath}`); + try { + for (const [platform, config] of Object.entries(this.configTemplates)) { + if (platform !== process.platform) { + continue; + } + + log.info(`Creating model config files for ${platform}`); + + if (customBasePath) { + config.comfyui.base_path = customBasePath; + } + + const yamlContent = stringify(config, { lineWidth: -1 }); + const fileContent = `# ComfyUI extra_model_paths.yaml for ${platform}\n${yamlContent}`; + await fsPromises.writeFile(extraModelConfigPath, fileContent, 'utf8'); + log.info(`Created extra_model_paths.yaml at ${extraModelConfigPath}`); + return true; + } + log.info(`No model config files created for platform ${process.platform}`); + return false; + } catch (error) { + log.error('Error creating model config files:', error); + return false; + } + } + + public static async readBasePathFromConfig(configPath: string): Promise { + try { + const fileContent = await fsPromises.readFile(configPath, 'utf8'); + const config = parse(fileContent); + + if (config?.comfyui?.base_path) { + return config.comfyui.base_path; + } + + log.warn(`No base_path found in ${configPath}`); + return null; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + log.info(`Config file not found at ${configPath}`); + } else { + log.error(`Error reading config file ${configPath}:`, error); + } + return null; + } + } +} diff --git a/src/config/extraModelConfig.ts b/src/config/extraModelConfig.ts deleted file mode 100644 index e434f67f..00000000 --- a/src/config/extraModelConfig.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as fsPromises from 'node:fs/promises'; -import log from 'electron-log/main'; -import { stringify, parse } from 'yaml'; -import path from 'node:path'; -import { app } from 'electron'; - -export const EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; - -export interface ModelPaths { - comfyui: { - base_path: string; - is_default: boolean; - [key: string]: string | boolean; - }; -} - -const commonPaths = { - is_default: true, - checkpoints: 'models/checkpoints/', - classifiers: 'models/classifiers/', - clip: 'models/clip/', - clip_vision: 'models/clip_vision/', - configs: 'models/configs/', - controlnet: 'models/controlnet/', - diffusers: 'models/diffusers/', - diffusion_models: 'models/diffusion_models/', - embeddings: 'models/embeddings/', - gligen: 'models/gligen/', - hypernetworks: 'models/hypernetworks/', - loras: 'models/loras/', - photomaker: 'models/photomaker/', - style_models: 'models/style_models/', - unet: 'models/unet/', - upscale_models: 'models/upscale_models/', - vae: 'models/vae/', - vae_approx: 'models/vae_approx/', - // TODO(robinhuang): Remove when we have a better way to specify base model paths. - animatediff_models: 'models/animatediff_models/', - animatediff_motion_lora: 'models/animatediff_motion_lora/', - animatediff_video_formats: 'models/animatediff_video_formats/', - ipadapter: 'models/ipadapter/', - liveportrait: 'models/liveportrait/', - insightface: 'models/insightface/', - layerstyle: 'models/layerstyle/', - LLM: 'models/LLM/', - Joy_caption: 'models/Joy_caption/', - sams: 'models/sams/', - blip: 'models/blip/', - CogVideo: 'models/CogVideo/', - xlabs: 'models/xlabs/', - instantid: 'models/instantid/', - // End custom node model directories. - custom_nodes: 'custom_nodes/', -} as const; - -const configTemplates: Record = { - win32: { - comfyui: { - base_path: '%USERPROFILE%/comfyui-electron', - ...commonPaths, - }, - }, - darwin: { - comfyui: { - base_path: '~/Library/Application Support/ComfyUI', - ...commonPaths, - }, - }, - linux: { - comfyui: { - base_path: '~/.config/ComfyUI', - ...commonPaths, - }, - }, -} as const; - -export function getModelConfigPath(): string { - return path.join(app.getPath('userData'), EXTRA_MODEL_CONFIG_PATH); -} - -export async function createModelConfigFiles(extraModelConfigPath: string, customBasePath?: string): Promise { - log.info(`Creating model config files in ${extraModelConfigPath} with base path ${customBasePath}`); - try { - for (const [platform, config] of Object.entries(configTemplates)) { - if (platform !== process.platform) { - continue; - } - - log.info(`Creating model config files for ${platform}`); - - // If a custom base path is provided, use it - if (customBasePath) { - config.comfyui.base_path = customBasePath; - } - - const yamlContent = stringify(config, { lineWidth: -1 }); - - // Add a comment at the top of the file - const fileContent = `# ComfyUI extra_model_paths.yaml for ${platform}\n${yamlContent}`; - await fsPromises.writeFile(extraModelConfigPath, fileContent, 'utf8'); - log.info(`Created extra_model_paths.yaml at ${extraModelConfigPath}`); - return true; - } - log.info(`No model config files created for platform ${process.platform}`); - return false; - } catch (error) { - log.error('Error creating model config files:', error); - return false; - } -} - -export async function readBasePathFromConfig(configPath: string): Promise { - try { - const fileContent = await fsPromises.readFile(configPath, 'utf8'); - const config = parse(fileContent); - - if (config && config.comfyui && config.comfyui.base_path) { - return config.comfyui.base_path; - } else { - log.warn(`No base_path found in ${configPath}`); - return null; - } - } catch (error) { - if ((error as NodeJS.ErrnoException).code === 'ENOENT') { - log.info(`Config file not found at ${configPath}`); - } else { - log.error(`Error reading config file ${configPath}:`, error); - } - return null; - } -} diff --git a/src/handlers/pathHandlers.ts b/src/handlers/pathHandlers.ts index 2de0d11c..57272812 100644 --- a/src/handlers/pathHandlers.ts +++ b/src/handlers/pathHandlers.ts @@ -1,7 +1,7 @@ import { app, dialog, ipcMain, shell } from 'electron'; import { IPC_CHANNELS } from '../constants'; import log from 'electron-log/main'; -import { getModelConfigPath } from '../config/extraModelConfig'; +import { ComfyServerConfig } from '../config/comfyServerConfig'; import { getBasePath } from '../install/resourcePaths'; import type { SystemPaths } from '../preload'; import fs from 'fs'; @@ -19,7 +19,7 @@ export class PathHandlers { }); ipcMain.handle(IPC_CHANNELS.GET_MODEL_CONFIG_PATH, (): string => { - return getModelConfigPath(); + return ComfyServerConfig.configPath; }); ipcMain.handle(IPC_CHANNELS.GET_BASE_PATH, async (): Promise => { diff --git a/src/install/resourcePaths.ts b/src/install/resourcePaths.ts index 8e12aba1..e063a178 100644 --- a/src/install/resourcePaths.ts +++ b/src/install/resourcePaths.ts @@ -1,10 +1,10 @@ import { app } from 'electron'; -import { getModelConfigPath, readBasePathFromConfig } from '../config/extraModelConfig'; +import { ComfyServerConfig } from '../config/comfyServerConfig'; import path from 'path'; export async function getBasePath(): Promise { - const modelConfigPath = getModelConfigPath(); - return readBasePathFromConfig(modelConfigPath); + const modelConfigPath = ComfyServerConfig.configPath; + return ComfyServerConfig.readBasePathFromConfig(modelConfigPath); } export async function getPythonInstallPath(): Promise { diff --git a/src/main.ts b/src/main.ts index 12951415..c9ecb08f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,7 +9,7 @@ import log from 'electron-log/main'; import * as Sentry from '@sentry/electron/main'; import * as net from 'net'; import { graphics } from 'systeminformation'; -import { createModelConfigFiles, getModelConfigPath } from './config/extraModelConfig'; +import { ComfyServerConfig } from './config/comfyServerConfig'; import todesktop from '@todesktop/runtime'; import { DownloadManager } from './models/DownloadManager'; import { getModelsDirectory } from './utils'; @@ -198,7 +198,7 @@ if (!gotTheLock) { ipcMain.handle(IPC_CHANNELS.REINSTALL, async () => { log.info('Reinstalling...'); - const modelConfigPath = getModelConfigPath(); + const modelConfigPath = ComfyServerConfig.configPath; fs.rmSync(modelConfigPath); restartApp(); }); @@ -465,15 +465,15 @@ function findAvailablePort(startPort: number, endPort: number): Promise * This means the extra_models_config.yaml file exists in the user's data directory. */ function isFirstTimeSetup(): boolean { - const extraModelsConfigPath = getModelConfigPath(); + const extraModelsConfigPath = ComfyServerConfig.configPath; log.info(`Checking if first time setup is complete. Extra models config path: ${extraModelsConfigPath}`); return !fs.existsSync(extraModelsConfigPath); } async function handleInstall(installOptions: InstallOptions) { const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); - const modelConfigPath = getModelConfigPath(); - await createModelConfigFiles(modelConfigPath, actualComfyDirectory); + const modelConfigPath = ComfyServerConfig.configPath; + await ComfyServerConfig.createModelConfigFiles(modelConfigPath, actualComfyDirectory); } async function serverStart() { @@ -510,9 +510,8 @@ async function serverStart() { appWindow.send(IPC_CHANNELS.LOG_MESSAGE, data); }, }); - const modelConfigPath = getModelConfigPath(); sendProgressUpdate(ProgressStatus.STARTING_SERVER); - await launchPythonServer(virtualEnvironment, appResourcesPath, modelConfigPath, basePath); + await launchPythonServer(virtualEnvironment, appResourcesPath, ComfyServerConfig.configPath, basePath); } else { sendProgressUpdate(ProgressStatus.READY); loadComfyIntoMainWindow(); From 1fb9d1ae598f0d101b4827d9cf32923fb22a3361 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 13 Nov 2024 19:48:13 -0500 Subject: [PATCH 04/13] Refactor create server config --- src/config/comfyServerConfig.ts | 74 ++++++++++++++++++++++++--------- src/main.ts | 7 +++- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 8c3bb818..80c94dbc 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -1,6 +1,6 @@ import * as fsPromises from 'node:fs/promises'; import log from 'electron-log/main'; -import { stringify, parse } from 'yaml'; +import yaml from 'yaml'; import path from 'node:path'; import { app } from 'electron'; @@ -12,6 +12,9 @@ interface ModelPaths { }; } +/** + * The ComfyServerConfig class is used to manage the configuration for the ComfyUI server. + */ export class ComfyServerConfig { private static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; @@ -84,28 +87,59 @@ export class ComfyServerConfig { ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH ); - public static async createModelConfigFiles(extraModelConfigPath: string, customBasePath?: string): Promise { - log.info(`Creating model config files in ${extraModelConfigPath} with base path ${customBasePath}`); - try { - for (const [platform, config] of Object.entries(this.configTemplates)) { - if (platform !== process.platform) { - continue; - } + /** + * Get the base config for the current operating system. + */ + public static getBaseConfig(): ModelPaths | null { + for (const [operatingSystem, modelPathConfig] of Object.entries(this.configTemplates)) { + if (operatingSystem === process.platform) { + return modelPathConfig; + } + } + return null; + } + /** + * Generate the content for the extra_model_paths.yaml file. + */ + private static generateConfigFileContent(modelPathConfig: ModelPaths): string { + const modelConfigYaml = yaml.stringify(modelPathConfig, { lineWidth: -1 }); + return `# ComfyUI extra_model_paths.yaml for ${process.platform}\n${modelConfigYaml}`; + } - log.info(`Creating model config files for ${platform}`); + private static mergeConfig(baseConfig: ModelPaths, customConfig: ModelPaths): ModelPaths { + return { + ...baseConfig, + comfyui: { + ...baseConfig.comfyui, + ...customConfig.comfyui, + }, + }; + } - if (customBasePath) { - config.comfyui.base_path = customBasePath; - } + private static async writeConfigFile(configFilePath: string, content: string): Promise { + try { + await fsPromises.writeFile(configFilePath, content, 'utf8'); + log.info(`Created extra_model_paths.yaml at ${configFilePath}`); + return true; + } catch (error) { + log.error('Error writing config file:', error); + return false; + } + } - const yamlContent = stringify(config, { lineWidth: -1 }); - const fileContent = `# ComfyUI extra_model_paths.yaml for ${platform}\n${yamlContent}`; - await fsPromises.writeFile(extraModelConfigPath, fileContent, 'utf8'); - log.info(`Created extra_model_paths.yaml at ${extraModelConfigPath}`); - return true; + /** + * Create the extra_model_paths.yaml file in the given destination path with the given custom config. + */ + public static async createConfigFile(destinationPath: string, customConfig: ModelPaths): Promise { + log.info(`Creating model config files in ${destinationPath}`); + try { + const baseConfig = this.getBaseConfig(); + if (!baseConfig) { + log.error('No base config found'); + return false; } - log.info(`No model config files created for platform ${process.platform}`); - return false; + const configContent = this.generateConfigFileContent(this.mergeConfig(baseConfig, customConfig)); + return await this.writeConfigFile(destinationPath, configContent); } catch (error) { log.error('Error creating model config files:', error); return false; @@ -115,7 +149,7 @@ export class ComfyServerConfig { public static async readBasePathFromConfig(configPath: string): Promise { try { const fileContent = await fsPromises.readFile(configPath, 'utf8'); - const config = parse(fileContent); + const config = yaml.parse(fileContent); if (config?.comfyui?.base_path) { return config.comfyui.base_path; diff --git a/src/main.ts b/src/main.ts index c9ecb08f..548f1057 100644 --- a/src/main.ts +++ b/src/main.ts @@ -473,7 +473,12 @@ function isFirstTimeSetup(): boolean { async function handleInstall(installOptions: InstallOptions) { const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); const modelConfigPath = ComfyServerConfig.configPath; - await ComfyServerConfig.createModelConfigFiles(modelConfigPath, actualComfyDirectory); + await ComfyServerConfig.createConfigFile(modelConfigPath, { + comfyui: { + base_path: actualComfyDirectory, + is_default: true, + }, + }); } async function serverStart() { From 5a12a0dfe54ee72bfdc3c9c21f20ee8b8aadb05b Mon Sep 17 00:00:00 2001 From: huchenlei Date: Wed, 13 Nov 2024 20:15:48 -0500 Subject: [PATCH 05/13] Add proper merge of yaml --- src/config/comfyServerConfig.ts | 144 ++++++++++++++++---------------- src/main.ts | 5 +- 2 files changed, 75 insertions(+), 74 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 80c94dbc..9f58cdfe 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -4,13 +4,55 @@ import yaml from 'yaml'; import path from 'node:path'; import { app } from 'electron'; -interface ModelPaths { - comfyui: { - base_path: string; - is_default: boolean; - [key: string]: string | boolean; - }; -} +const knownModelKeys = [ + 'checkpoints', + 'classifiers', + 'clip', + 'clip_vision', + 'configs', + 'controlnet', + 'diffusers', + 'diffusion_models', + 'embeddings', + 'gligen', + 'hypernetworks', + 'loras', + 'photomaker', + 'style_models', + 'unet', + 'upscale_models', + 'vae', + 'vae_approx', + // TODO(robinhuang): Remove when we have a better way to specify base model paths. + 'animatediff_models', + 'animatediff_motion_lora', + 'animatediff_video_formats', + 'ipadapter', + 'liveportrait', + 'insightface', + 'layerstyle', + 'LLM', + 'Joy_caption', + 'sams', + 'blip', + 'CogVideo', + 'xlabs', + 'instantid', + 'custom_nodes', +] as const; + +const commonPaths = knownModelKeys.reduce( + (acc, key) => { + acc[key] = `models/${key}/`; + return acc; + }, + {} as Record +); + +type ModelPaths = { + base_path: string; + [key: string]: string; +}; /** * The ComfyServerConfig class is used to manage the configuration for the ComfyUI server. @@ -18,63 +60,18 @@ interface ModelPaths { export class ComfyServerConfig { private static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; - private static readonly commonPaths = { - is_default: true, - checkpoints: 'models/checkpoints/', - classifiers: 'models/classifiers/', - clip: 'models/clip/', - clip_vision: 'models/clip_vision/', - configs: 'models/configs/', - controlnet: 'models/controlnet/', - diffusers: 'models/diffusers/', - diffusion_models: 'models/diffusion_models/', - embeddings: 'models/embeddings/', - gligen: 'models/gligen/', - hypernetworks: 'models/hypernetworks/', - loras: 'models/loras/', - photomaker: 'models/photomaker/', - style_models: 'models/style_models/', - unet: 'models/unet/', - upscale_models: 'models/upscale_models/', - vae: 'models/vae/', - vae_approx: 'models/vae_approx/', - // TODO(robinhuang): Remove when we have a better way to specify base model paths. - animatediff_models: 'models/animatediff_models/', - animatediff_motion_lora: 'models/animatediff_motion_lora/', - animatediff_video_formats: 'models/animatediff_video_formats/', - ipadapter: 'models/ipadapter/', - liveportrait: 'models/liveportrait/', - insightface: 'models/insightface/', - layerstyle: 'models/layerstyle/', - LLM: 'models/LLM/', - Joy_caption: 'models/Joy_caption/', - sams: 'models/sams/', - blip: 'models/blip/', - CogVideo: 'models/CogVideo/', - xlabs: 'models/xlabs/', - instantid: 'models/instantid/', - // End custom node model directories. - custom_nodes: 'custom_nodes/', - } as const; - private static readonly configTemplates: Record = { win32: { - comfyui: { - base_path: '%USERPROFILE%/comfyui-electron', - ...this.commonPaths, - }, + base_path: '%USERPROFILE%/comfyui-electron', + ...commonPaths, }, darwin: { - comfyui: { - base_path: '~/Library/Application Support/ComfyUI', - ...this.commonPaths, - }, + base_path: '~/Library/Application Support/ComfyUI', + ...commonPaths, }, linux: { - comfyui: { - base_path: '~/.config/ComfyUI', - ...this.commonPaths, - }, + base_path: '~/.config/ComfyUI', + ...commonPaths, }, } as const; @@ -90,7 +87,7 @@ export class ComfyServerConfig { /** * Get the base config for the current operating system. */ - public static getBaseConfig(): ModelPaths | null { + static getBaseConfig(): ModelPaths | null { for (const [operatingSystem, modelPathConfig] of Object.entries(this.configTemplates)) { if (operatingSystem === process.platform) { return modelPathConfig; @@ -101,22 +98,29 @@ export class ComfyServerConfig { /** * Generate the content for the extra_model_paths.yaml file. */ - private static generateConfigFileContent(modelPathConfig: ModelPaths): string { - const modelConfigYaml = yaml.stringify(modelPathConfig, { lineWidth: -1 }); + static generateConfigFileContent(modelPathConfig: ModelPaths): string { + const modelConfigYaml = yaml.stringify({ comfyui: modelPathConfig }, { lineWidth: -1 }); return `# ComfyUI extra_model_paths.yaml for ${process.platform}\n${modelConfigYaml}`; } - private static mergeConfig(baseConfig: ModelPaths, customConfig: ModelPaths): ModelPaths { - return { - ...baseConfig, - comfyui: { - ...baseConfig.comfyui, - ...customConfig.comfyui, - }, - }; + static mergeConfig(baseConfig: ModelPaths, customConfig: ModelPaths): ModelPaths { + const mergedConfig: ModelPaths = { ...baseConfig }; + + for (const [key, customPath] of Object.entries(customConfig)) { + if (key in baseConfig) { + // Concatenate paths if key exists in both configs + // Order here matters, as ComfyUI searches for models in the order they are listed. + mergedConfig[key] = baseConfig[key] + '\n' + customPath; + } else { + // Use custom path directly if key only exists in custom config + mergedConfig[key] = customPath; + } + } + + return mergedConfig; } - private static async writeConfigFile(configFilePath: string, content: string): Promise { + static async writeConfigFile(configFilePath: string, content: string): Promise { try { await fsPromises.writeFile(configFilePath, content, 'utf8'); log.info(`Created extra_model_paths.yaml at ${configFilePath}`); diff --git a/src/main.ts b/src/main.ts index 548f1057..81301463 100644 --- a/src/main.ts +++ b/src/main.ts @@ -474,10 +474,7 @@ async function handleInstall(installOptions: InstallOptions) { const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); const modelConfigPath = ComfyServerConfig.configPath; await ComfyServerConfig.createConfigFile(modelConfigPath, { - comfyui: { - base_path: actualComfyDirectory, - is_default: true, - }, + base_path: actualComfyDirectory, }); } From acac2cad0d553de69ce98080f9c7c60282e53461 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 15:00:28 -0500 Subject: [PATCH 06/13] Fix migration --- src/config/comfyServerConfig.ts | 40 +++++++++++++++++++++++++-------- src/main.ts | 25 +++++++++++++++++---- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 9f58cdfe..83a7606f 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -49,27 +49,27 @@ const commonPaths = knownModelKeys.reduce( {} as Record ); -type ModelPaths = { - base_path: string; - [key: string]: string; -}; +type ModelPaths = Record; /** * The ComfyServerConfig class is used to manage the configuration for the ComfyUI server. */ export class ComfyServerConfig { - private static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; + static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; private static readonly configTemplates: Record = { win32: { + is_default: 'true', base_path: '%USERPROFILE%/comfyui-electron', ...commonPaths, }, darwin: { + is_default: 'true', base_path: '~/Library/Application Support/ComfyUI', ...commonPaths, }, linux: { + is_default: 'true', base_path: '~/.config/ComfyUI', ...commonPaths, }, @@ -98,8 +98,8 @@ export class ComfyServerConfig { /** * Generate the content for the extra_model_paths.yaml file. */ - static generateConfigFileContent(modelPathConfig: ModelPaths): string { - const modelConfigYaml = yaml.stringify({ comfyui: modelPathConfig }, { lineWidth: -1 }); + static generateConfigFileContent(modelPathConfigs: Record): string { + const modelConfigYaml = yaml.stringify(modelPathConfigs, { lineWidth: -1 }); return `# ComfyUI extra_model_paths.yaml for ${process.platform}\n${modelConfigYaml}`; } @@ -131,10 +131,28 @@ export class ComfyServerConfig { } } + public static async readConfigFile(configPath: string): Promise | null> { + try { + const fileContent = await fsPromises.readFile(configPath, 'utf8'); + const config = yaml.parse(fileContent); + return config; + } catch (error) { + log.error(`Error reading config file ${configPath}:`, error); + return null; + } + } + /** * Create the extra_model_paths.yaml file in the given destination path with the given custom config. + * @param destinationPath - The path to the destination file. + * @param customConfig - The custom config to merge with the base config. + * @param extraConfigs - The extra configs such as paths from A1111. */ - public static async createConfigFile(destinationPath: string, customConfig: ModelPaths): Promise { + public static async createConfigFile( + destinationPath: string, + customConfig: ModelPaths, + extraConfigs: Record + ): Promise { log.info(`Creating model config files in ${destinationPath}`); try { const baseConfig = this.getBaseConfig(); @@ -142,7 +160,11 @@ export class ComfyServerConfig { log.error('No base config found'); return false; } - const configContent = this.generateConfigFileContent(this.mergeConfig(baseConfig, customConfig)); + const comfyuiConfig = this.mergeConfig(baseConfig, customConfig); + const configContent = this.generateConfigFileContent({ + ...extraConfigs, + comfyui: comfyuiConfig, + }); return await this.writeConfigFile(destinationPath, configContent); } catch (error) { log.error('Error creating model config files:', error); diff --git a/src/main.ts b/src/main.ts index 81301463..1d547219 100644 --- a/src/main.ts +++ b/src/main.ts @@ -471,11 +471,28 @@ function isFirstTimeSetup(): boolean { } async function handleInstall(installOptions: InstallOptions) { + const migrationSource = installOptions.migrationSourcePath; + const migrationItemIds = new Set(installOptions.migrationItemIds ?? []); + const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); - const modelConfigPath = ComfyServerConfig.configPath; - await ComfyServerConfig.createConfigFile(modelConfigPath, { - base_path: actualComfyDirectory, - }); + + const migrationServerConfig = + migrationSource && migrationItemIds.has('models') + ? ((await ComfyServerConfig.readConfigFile( + path.join(migrationSource, ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH) + )) ?? {}) + : {}; + migrationServerConfig['comfyui'] ??= {}; + migrationServerConfig['comfyui']['base_path'] = actualComfyDirectory; + // Do not migrate custom nodes as we currently don't have a way to install their dependencies. + if ('custom_nodes' in migrationServerConfig['comfyui']) { + delete migrationServerConfig['comfyui']['custom_nodes']; + } + await ComfyServerConfig.createConfigFile( + ComfyServerConfig.configPath, + migrationServerConfig['comfyui'], + migrationServerConfig + ); } async function serverStart() { From 17336d51e4d9ce07418125436673d39667f1a687 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 15:15:15 -0500 Subject: [PATCH 07/13] Migrate existing repo path --- src/config/comfyServerConfig.ts | 28 +++++++++++++++++----------- src/main.ts | 25 +++++++++++++------------ 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 83a7606f..3bf3d547 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -41,14 +41,6 @@ const knownModelKeys = [ 'custom_nodes', ] as const; -const commonPaths = knownModelKeys.reduce( - (acc, key) => { - acc[key] = `models/${key}/`; - return acc; - }, - {} as Record -); - type ModelPaths = Record; /** @@ -57,21 +49,22 @@ type ModelPaths = Record; export class ComfyServerConfig { static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; + private static readonly commonPaths = this.getBaseModelPathsFromRepoPath(''); private static readonly configTemplates: Record = { win32: { is_default: 'true', base_path: '%USERPROFILE%/comfyui-electron', - ...commonPaths, + ...this.commonPaths, }, darwin: { is_default: 'true', base_path: '~/Library/Application Support/ComfyUI', - ...commonPaths, + ...this.commonPaths, }, linux: { is_default: 'true', base_path: '~/.config/ComfyUI', - ...commonPaths, + ...this.commonPaths, }, } as const; @@ -142,6 +135,19 @@ export class ComfyServerConfig { } } + public static async getConfigFromRepoPath(repoPath: string): Promise> { + const configPath = path.join(repoPath, ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH); + const config = (await this.readConfigFile(configPath)) ?? {}; + return config; + } + + public static getBaseModelPathsFromRepoPath(repoPath: string): ModelPaths { + return knownModelKeys.reduce((acc, key) => { + acc[key] = path.join(repoPath, 'models', key) + path.sep; + return acc; + }, {} as ModelPaths); + } + /** * Create the extra_model_paths.yaml file in the given destination path with the given custom config. * @param destinationPath - The path to the destination file. diff --git a/src/main.ts b/src/main.ts index 1d547219..9f29fc0a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -476,23 +476,24 @@ async function handleInstall(installOptions: InstallOptions) { const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); + // The yaml file exited in migration source repo. const migrationServerConfig = migrationSource && migrationItemIds.has('models') - ? ((await ComfyServerConfig.readConfigFile( - path.join(migrationSource, ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH) - )) ?? {}) + ? await ComfyServerConfig.getConfigFromRepoPath(migrationSource) : {}; - migrationServerConfig['comfyui'] ??= {}; - migrationServerConfig['comfyui']['base_path'] = actualComfyDirectory; + + // The model paths in the migration source repo. + const migrationComfyConfig = migrationSource ? ComfyServerConfig.getBaseModelPathsFromRepoPath(migrationSource) : {}; + + // The overall paths to add to the config file. + const comfyuiConfig = ComfyServerConfig.mergeConfig(migrationServerConfig['comfyui'] ?? {}, migrationComfyConfig); + comfyuiConfig['base_path'] = actualComfyDirectory; // Do not migrate custom nodes as we currently don't have a way to install their dependencies. - if ('custom_nodes' in migrationServerConfig['comfyui']) { - delete migrationServerConfig['comfyui']['custom_nodes']; + if ('custom_nodes' in comfyuiConfig) { + delete comfyuiConfig['custom_nodes']; } - await ComfyServerConfig.createConfigFile( - ComfyServerConfig.configPath, - migrationServerConfig['comfyui'], - migrationServerConfig - ); + + await ComfyServerConfig.createConfigFile(ComfyServerConfig.configPath, comfyuiConfig, migrationServerConfig); } async function serverStart() { From 8978ea6e6aa29c540bd5c9f3a19cdb1aec39dda4 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 15:19:59 -0500 Subject: [PATCH 08/13] update tests --- tests/unit/comfyServerConfig.test.ts | 75 +++++++++++++++++++++++++++ tests/unit/extra_model_config.test.ts | 71 ------------------------- 2 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 tests/unit/comfyServerConfig.test.ts delete mode 100644 tests/unit/extra_model_config.test.ts diff --git a/tests/unit/comfyServerConfig.test.ts b/tests/unit/comfyServerConfig.test.ts new file mode 100644 index 00000000..c3ad9c3a --- /dev/null +++ b/tests/unit/comfyServerConfig.test.ts @@ -0,0 +1,75 @@ +// Mock electron +jest.mock('electron', () => ({ + app: { + getPath: jest.fn().mockReturnValue('/fake/user/data'), + }, +})); + +import { app } from 'electron'; +import path from 'path'; +import { ComfyServerConfig } from '../../src/config/comfyServerConfig'; +import * as fsPromises from 'node:fs/promises'; + +describe('ComfyServerConfig', () => { + describe('configPath', () => { + it('should return the correct path', () => { + // Mock the userData path + const mockUserDataPath = '/fake/user/data'; + (app.getPath as jest.Mock).mockImplementation((key: string) => { + if (key === 'userData') { + return mockUserDataPath; + } + throw new Error(`Unexpected getPath key: ${key}`); + }); + + // Access the static property + const result = ComfyServerConfig.configPath; + + // Verify the path is correctly joined + expect(result).toBe(path.join(mockUserDataPath, 'extra_models_config.yaml')); + + // Verify app.getPath was called with correct argument + expect(app.getPath).toHaveBeenCalledWith('userData'); + }); + }); + + describe('readBasePathFromConfig', () => { + const testConfigPath = path.join(__dirname, 'test_config.yaml'); + + beforeAll(async () => { + // Create a test YAML file + const testConfig = `# Test ComfyUI config +comfyui: + base_path: ~/test/comfyui + is_default: true + checkpoints: models/checkpoints/ + loras: models/loras/`; + + await fsPromises.writeFile(testConfigPath, testConfig, 'utf8'); + }); + + afterAll(async () => { + await fsPromises.rm(testConfigPath); + }); + + it('should read base_path from valid config file', async () => { + const result = await ComfyServerConfig.readBasePathFromConfig(testConfigPath); + expect(result).toBe('~/test/comfyui'); + }); + + it('should return null for non-existent file', async () => { + const result = await ComfyServerConfig.readBasePathFromConfig('non_existent_file.yaml'); + expect(result).toBeNull(); + }); + + it('should return null for invalid config file', async () => { + const invalidConfigPath = path.join(__dirname, 'invalid_config.yaml'); + await fsPromises.writeFile(invalidConfigPath, 'invalid: yaml: content:', 'utf8'); + + const result = await ComfyServerConfig.readBasePathFromConfig(invalidConfigPath); + expect(result).toBeNull(); + + await fsPromises.rm(invalidConfigPath); + }); + }); +}); diff --git a/tests/unit/extra_model_config.test.ts b/tests/unit/extra_model_config.test.ts deleted file mode 100644 index 4e12932f..00000000 --- a/tests/unit/extra_model_config.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { app } from 'electron'; -import path from 'path'; -import { getModelConfigPath, readBasePathFromConfig } from '../../src/config/extra_model_config'; -import * as fsPromises from 'node:fs/promises'; -// Mock electron -jest.mock('electron', () => ({ - app: { - getPath: jest.fn(), - }, -})); - -describe('getModelConfigPath', () => { - it('should return the correct path', async () => { - // Mock the userData path - const mockUserDataPath = '/fake/user/data'; - (app.getPath as jest.Mock).mockImplementation((key: string) => { - if (key === 'userData') { - return mockUserDataPath; - } - throw new Error(`Unexpected getPath key: ${key}`); - }); - - const result = getModelConfigPath(); - - // Verify the path is correctly joined - expect(result).toBe(path.join(mockUserDataPath, 'extra_models_config.yaml')); - - // Verify app.getPath was called with correct argument - expect(app.getPath).toHaveBeenCalledWith('userData'); - }); -}); - -describe('readBasePathFromConfig', () => { - const testConfigPath = path.join(__dirname, 'test_config.yaml'); - - beforeAll(async () => { - // Create a test YAML file - const testConfig = `# Test ComfyUI config -comfyui: - base_path: ~/test/comfyui - is_default: true - checkpoints: models/checkpoints/ - loras: models/loras/`; - - await fsPromises.writeFile(testConfigPath, testConfig, 'utf8'); - }); - - afterAll(async () => { - await fsPromises.rm(testConfigPath); - }); - - it('should read base_path from valid config file', async () => { - const result = await readBasePathFromConfig(testConfigPath); - expect(result).toBe('~/test/comfyui'); - }); - - it('should return null for non-existent file', async () => { - const result = await readBasePathFromConfig('non_existent_file.yaml'); - expect(result).toBeNull(); - }); - - it('should return null for invalid config file', async () => { - const invalidConfigPath = path.join(__dirname, 'invalid_config.yaml'); - await fsPromises.writeFile(invalidConfigPath, 'invalid: yaml: content:', 'utf8'); - - const result = await readBasePathFromConfig(invalidConfigPath); - expect(result).toBeNull(); - - await fsPromises.rm(invalidConfigPath); - }); -}); From dd4d283afb232128f615f42bff8df33bc98df3e3 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 15:24:10 -0500 Subject: [PATCH 09/13] Add merge config test --- tests/unit/comfyServerConfig.test.ts | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/unit/comfyServerConfig.test.ts b/tests/unit/comfyServerConfig.test.ts index c3ad9c3a..df1a3108 100644 --- a/tests/unit/comfyServerConfig.test.ts +++ b/tests/unit/comfyServerConfig.test.ts @@ -72,4 +72,69 @@ comfyui: await fsPromises.rm(invalidConfigPath); }); }); + + describe('mergeConfig', () => { + it('should merge configs with overlapping keys by concatenating paths', () => { + const baseConfig = { + checkpoints: '/base/path/checkpoints/', + loras: '/base/path/loras/', + }; + + const customConfig = { + checkpoints: '/custom/path/checkpoints/', + loras: '/custom/path/loras/', + }; + + const result = ComfyServerConfig.mergeConfig(baseConfig, customConfig); + + expect(result).toEqual({ + checkpoints: '/base/path/checkpoints/\n/custom/path/checkpoints/', + loras: '/base/path/loras/\n/custom/path/loras/', + }); + }); + + it('should preserve unique keys from custom config', () => { + const baseConfig = { + checkpoints: '/base/path/checkpoints/', + }; + + const customConfig = { + checkpoints: '/custom/path/checkpoints/', + newKey: '/custom/path/newKey/', + }; + + const result = ComfyServerConfig.mergeConfig(baseConfig, customConfig); + + expect(result).toEqual({ + checkpoints: '/base/path/checkpoints/\n/custom/path/checkpoints/', + newKey: '/custom/path/newKey/', + }); + }); + + it('should handle empty custom config', () => { + const baseConfig = { + checkpoints: '/base/path/checkpoints/', + loras: '/base/path/loras/', + }; + + const customConfig = {}; + + const result = ComfyServerConfig.mergeConfig(baseConfig, customConfig); + + expect(result).toEqual(baseConfig); + }); + + it('should handle empty base config', () => { + const baseConfig = {}; + + const customConfig = { + checkpoints: '/custom/path/checkpoints/', + loras: '/custom/path/loras/', + }; + + const result = ComfyServerConfig.mergeConfig(baseConfig, customConfig); + + expect(result).toEqual(customConfig); + }); + }); }); From 68fcdf42ff3babab9ab6c977583a713d799538ec Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 16:41:15 -0500 Subject: [PATCH 10/13] Extract method --- src/config/comfyServerConfig.ts | 32 +++++++++++++++++++++++++++ src/main.ts | 22 +++++-------------- tests/unit/comfyServerConfig.test.ts | 33 ++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 3bf3d547..3a205861 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -198,4 +198,36 @@ export class ComfyServerConfig { return null; } } + + /** + * Get the config for the migration source (Existing ComfyUI instance). + * @param migrationSource - The path to the migration source. + * @param migrationItemIds - The item ids to migrate. + */ + public static async getMigrationConfig( + migrationSource?: string, + migrationItemIds: Set = new Set() + ): Promise> { + if (!migrationSource || !migrationItemIds.has('models')) { + return {}; + } + // The yaml file exited in migration source repo. + const migrationServerConfig = await ComfyServerConfig.getConfigFromRepoPath(migrationSource); + + // The model paths in the migration source repo. + const migrationComfyConfig = migrationSource + ? ComfyServerConfig.getBaseModelPathsFromRepoPath(migrationSource) + : {}; + + // The overall paths to add to the config file. + const comfyuiConfig = ComfyServerConfig.mergeConfig(migrationServerConfig['comfyui'] ?? {}, migrationComfyConfig); + // Do not migrate custom nodes as we currently don't have a way to install their dependencies. + if ('custom_nodes' in comfyuiConfig) { + delete comfyuiConfig['custom_nodes']; + } + return { + ...migrationServerConfig, + comfyui: comfyuiConfig, + }; + } } diff --git a/src/main.ts b/src/main.ts index 9f29fc0a..ca7da7c6 100644 --- a/src/main.ts +++ b/src/main.ts @@ -476,24 +476,12 @@ async function handleInstall(installOptions: InstallOptions) { const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); - // The yaml file exited in migration source repo. - const migrationServerConfig = - migrationSource && migrationItemIds.has('models') - ? await ComfyServerConfig.getConfigFromRepoPath(migrationSource) - : {}; - - // The model paths in the migration source repo. - const migrationComfyConfig = migrationSource ? ComfyServerConfig.getBaseModelPathsFromRepoPath(migrationSource) : {}; - - // The overall paths to add to the config file. - const comfyuiConfig = ComfyServerConfig.mergeConfig(migrationServerConfig['comfyui'] ?? {}, migrationComfyConfig); + const { comfyui: comfyuiConfig, ...extraConfigs } = await ComfyServerConfig.getMigrationConfig( + migrationSource, + migrationItemIds + ); comfyuiConfig['base_path'] = actualComfyDirectory; - // Do not migrate custom nodes as we currently don't have a way to install their dependencies. - if ('custom_nodes' in comfyuiConfig) { - delete comfyuiConfig['custom_nodes']; - } - - await ComfyServerConfig.createConfigFile(ComfyServerConfig.configPath, comfyuiConfig, migrationServerConfig); + await ComfyServerConfig.createConfigFile(ComfyServerConfig.configPath, comfyuiConfig, extraConfigs); } async function serverStart() { diff --git a/tests/unit/comfyServerConfig.test.ts b/tests/unit/comfyServerConfig.test.ts index df1a3108..db5ad023 100644 --- a/tests/unit/comfyServerConfig.test.ts +++ b/tests/unit/comfyServerConfig.test.ts @@ -137,4 +137,37 @@ comfyui: expect(result).toEqual(customConfig); }); }); + + describe('getMigrationConfig', () => { + it('should return empty object when no migration source is provided', async () => { + const result = await ComfyServerConfig.getMigrationConfig(undefined); + expect(result).toEqual({}); + }); + + it('should merge configs and remove custom_nodes when migration source is provided', async () => { + // Mock the getConfigFromRepoPath and getBaseModelPathsFromRepoPath methods + const mockServerConfig = { + comfyui: { + checkpoints: '/server/path/checkpoints/', + custom_nodes: '/server/path/custom_nodes/', + }, + }; + + jest.spyOn(ComfyServerConfig, 'getConfigFromRepoPath').mockResolvedValue(mockServerConfig); + jest.spyOn(ComfyServerConfig, 'getBaseModelPathsFromRepoPath').mockReturnValue({ + checkpoints: '/base/path/checkpoints/', + custom_nodes: '/base/path/custom_nodes/', + }); + + const result = await ComfyServerConfig.getMigrationConfig('/fake/path', new Set(['models'])); + + expect(result.comfyui).toBeDefined(); + expect(result.comfyui.checkpoints).toBe('/server/path/checkpoints/\n/base/path/checkpoints/'); + expect(result.comfyui.custom_nodes).toBeUndefined(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + }); }); From 4a4710e66438beedf55a2c5e4b3e4fa610738214 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 17:39:39 -0500 Subject: [PATCH 11/13] Various fixes --- src/config/comfyServerConfig.ts | 14 ++++++++------ src/main.ts | 3 +-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 3a205861..7a9d55ff 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -38,7 +38,6 @@ const knownModelKeys = [ 'CogVideo', 'xlabs', 'instantid', - 'custom_nodes', ] as const; type ModelPaths = Record; @@ -47,23 +46,26 @@ type ModelPaths = Record; * The ComfyServerConfig class is used to manage the configuration for the ComfyUI server. */ export class ComfyServerConfig { + // The name of the default config file. + static readonly COMFYUI_DEFAULT_CONFIG_NAME = 'extra_model_paths.yaml'; + // The path to the extra_models_config.yaml file used by the Electron desktop app. static readonly EXTRA_MODEL_CONFIG_PATH = 'extra_models_config.yaml'; - private static readonly commonPaths = this.getBaseModelPathsFromRepoPath(''); + private static readonly commonPaths = { + ...this.getBaseModelPathsFromRepoPath(''), + custom_nodes: 'custom_nodes/', + }; private static readonly configTemplates: Record = { win32: { is_default: 'true', - base_path: '%USERPROFILE%/comfyui-electron', ...this.commonPaths, }, darwin: { is_default: 'true', - base_path: '~/Library/Application Support/ComfyUI', ...this.commonPaths, }, linux: { is_default: 'true', - base_path: '~/.config/ComfyUI', ...this.commonPaths, }, } as const; @@ -136,7 +138,7 @@ export class ComfyServerConfig { } public static async getConfigFromRepoPath(repoPath: string): Promise> { - const configPath = path.join(repoPath, ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH); + const configPath = path.join(repoPath, ComfyServerConfig.COMFYUI_DEFAULT_CONFIG_NAME); const config = (await this.readConfigFile(configPath)) ?? {}; return config; } diff --git a/src/main.ts b/src/main.ts index ca7da7c6..f5caf629 100644 --- a/src/main.ts +++ b/src/main.ts @@ -167,8 +167,7 @@ if (!gotTheLock) { }); ipcMain.on(IPC_CHANNELS.INSTALL_COMFYUI, async (event, installOptions: InstallOptions) => { // Non-blocking call. The renderer will navigate to /server-start and show install progress. - handleInstall(installOptions); - serverStart(); + handleInstall(installOptions).then(serverStart); }); // Loading renderer when all handlers are registered to ensure all event listeners are set up. From 0cbde662d242b5140137f9958b1450460c997043 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 17:52:40 -0500 Subject: [PATCH 12/13] Remove unnecessary tests --- tests/unit/install/resourcePath.test.ts | 86 ------------------------- 1 file changed, 86 deletions(-) delete mode 100644 tests/unit/install/resourcePath.test.ts diff --git a/tests/unit/install/resourcePath.test.ts b/tests/unit/install/resourcePath.test.ts deleted file mode 100644 index 50be268b..00000000 --- a/tests/unit/install/resourcePath.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { app } from 'electron'; -import path from 'path'; -import { getBasePath, getPythonInstallPath, getAppResourcesPath } from '../../../src/install/resourcePaths'; -import { getModelConfigPath, readBasePathFromConfig } from '../../../src/config/extra_model_config'; - -// Mock the external modules -jest.mock('electron', () => ({ - app: { - isPackaged: false, - getAppPath: jest.fn(), - }, -})); - -jest.mock('../../../src/config/extra_model_config', () => ({ - getModelConfigPath: jest.fn(), - readBasePathFromConfig: jest.fn(), -})); - -describe('resourcePaths', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('getBasePath', () => { - it('should return the base path from config', async () => { - const mockConfigPath = '/mock/config/path'; - const mockBasePath = '/mock/base/path'; - - (getModelConfigPath as jest.Mock).mockReturnValue(mockConfigPath); - (readBasePathFromConfig as jest.Mock).mockResolvedValue(mockBasePath); - - const result = await getBasePath(); - - expect(getModelConfigPath).toHaveBeenCalled(); - expect(readBasePathFromConfig).toHaveBeenCalledWith(mockConfigPath); - expect(result).toBe(mockBasePath); - }); - }); - - describe('getPythonInstallPath', () => { - it('should return assets path when app is not packaged', async () => { - const mockAppPath = '/mock/app/path'; - (app.getAppPath as jest.Mock).mockReturnValue(mockAppPath); - (app.isPackaged as boolean) = false; - - const result = await getPythonInstallPath(); - - expect(result).toBe(path.join(mockAppPath, 'assets')); - expect(app.getAppPath).toHaveBeenCalled(); - }); - - it('should return base path when app is packaged', async () => { - const mockBasePath = '/mock/base/path'; - (app.isPackaged as boolean) = true; - (getModelConfigPath as jest.Mock).mockReturnValue('/mock/config'); - (readBasePathFromConfig as jest.Mock).mockResolvedValue(mockBasePath); - - const result = await getPythonInstallPath(); - - expect(result).toBe(mockBasePath); - }); - }); - - describe('getAppResourcesPath', () => { - it('should return assets path when app is not packaged', async () => { - const mockAppPath = '/mock/app/path'; - (app.getAppPath as jest.Mock).mockReturnValue(mockAppPath); - (app.isPackaged as boolean) = false; - - const result = await getAppResourcesPath(); - - expect(result).toBe(path.join(mockAppPath, 'assets')); - expect(app.getAppPath).toHaveBeenCalled(); - }); - - it('should return resources path when app is packaged', async () => { - (app.isPackaged as boolean) = true; - const mockResourcesPath = '/mock/resources/path'; - (process as any).resourcesPath = mockResourcesPath; - - const result = await getAppResourcesPath(); - - expect(result).toBe(mockResourcesPath); - }); - }); -}); From dd050766b4d7aa3bd2fdc7848cb3fa3299017a73 Mon Sep 17 00:00:00 2001 From: huchenlei Date: Thu, 14 Nov 2024 17:55:56 -0500 Subject: [PATCH 13/13] nit --- src/config/comfyServerConfig.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/config/comfyServerConfig.ts b/src/config/comfyServerConfig.ts index 7a9d55ff..02cfc01b 100644 --- a/src/config/comfyServerConfig.ts +++ b/src/config/comfyServerConfig.ts @@ -74,10 +74,9 @@ export class ComfyServerConfig { * The path to the extra_models_config.yaml file. The config file is used for ComfyUI core to determine search paths * for models and custom nodes. */ - public static readonly configPath: string = path.join( - app.getPath('userData'), - ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH - ); + public static get configPath(): string { + return path.join(app.getPath('userData'), ComfyServerConfig.EXTRA_MODEL_CONFIG_PATH); + } /** * Get the base config for the current operating system.