Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Override model, custom nodes, input, output, user directories + install manager core. #11

Merged
merged 13 commits into from
Sep 12, 2024
2 changes: 1 addition & 1 deletion .github/workflows/publish_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
with:
sign-and-publish: true
DIGICERT_FINGERPRINT: ${{secrets.DIGICERT_FINGERPRINT}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
22 changes: 22 additions & 0 deletions config/model_paths_linux.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ComfyUI extra_model_paths.yaml for Linux
comfyui:
base_path: ~/.config/ComfyUI
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/
custom_nodes: custom_nodes/
22 changes: 22 additions & 0 deletions config/model_paths_mac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ComfyUI extra_model_paths.yaml for macOS
comfyui:
base_path: ~/Library/Application Support/ComfyUI
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/
custom_nodes: custom_nodes/
22 changes: 22 additions & 0 deletions config/model_paths_windows.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# ComfyUI extra_model_paths.yaml for Windows
comfyui:
base_path: "%APPDATA%/ComfyUI"
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/
custom_nodes: custom_nodes/
40 changes: 34 additions & 6 deletions forge.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { ForgeConfig } from '@electron-forge/shared-types';
import { MakerSquirrel } from '@electron-forge/maker-squirrel';
import { MakerZIP } from '@electron-forge/maker-zip';
import { MakerDMG } from '@electron-forge/maker-dmg';
import { MakerDeb } from '@electron-forge/maker-deb';
import { MakerRpm } from '@electron-forge/maker-rpm';
import { VitePlugin } from '@electron-forge/plugin-vite';
import { FusesPlugin } from '@electron-forge/plugin-fuses';
import { FuseV1Options, FuseVersion } from '@electron/fuses';

import path from 'path';
import fs from 'fs';

const config: ForgeConfig = {
packagerConfig: {
Expand All @@ -21,15 +21,15 @@ const config: ForgeConfig = {
},
},
osxSign: {
identity: process.env.SIGN_ID,
identity: process.env.SIGN_ID as string,
optionsForFile: (filepath) => {
return { entitlements: './scripts/entitlements.mac.plist' };
}
},
osxNotarize: {
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_PASSWORD,
teamId: process.env.APPLE_TEAM_ID
appleId: process.env.APPLE_ID as string,
appleIdPassword: process.env.APPLE_PASSWORD as string,
teamId: process.env.APPLE_TEAM_ID as string
},
},
extraResource: ['./assets/ComfyUI', './assets/python.tgz', './assets/UI'],
Expand All @@ -38,6 +38,34 @@ const config: ForgeConfig = {
},
rebuildConfig: {},
hooks: {
prePackage: async () => {
const configDir = path.join(__dirname, 'config');
const assetDir = path.join(__dirname, 'assets', 'ComfyUI');

// Ensure the asset directory exists
if (!fs.existsSync(assetDir)) {
fs.mkdirSync(assetDir, { recursive: true });
}

let sourceFile;
if (process.platform === 'darwin') {
sourceFile = path.join(configDir, 'model_paths_mac.yaml');
} else if (process.platform === 'win32') {
sourceFile = path.join(configDir, 'model_paths_windows.yaml');
} else {
sourceFile = path.join(configDir, 'model_paths_linux.yaml');
}

const destFile = path.join(assetDir, 'extra_model_paths.yaml');

try {
fs.copyFileSync(sourceFile, destFile);
console.log(`Copied ${sourceFile} to ${destFile}`);
} catch (err) {
console.error(`Failed to copy config file: ${err}`);
throw err; // This will stop the packaging process if the copy fails
}
},
postPackage: async (forgeConfig, packageResult) => {
console.log('Post-package hook started');
console.log('Package result:', JSON.stringify(packageResult, null, 2));
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"lint": "eslint --ext .ts,.tsx .",
"lint:fix": "eslint --fix --ext .ts,.tsx .",
"make": "electron-forge make",
"make:assets:amd": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --amd && comfy-cli --here standalone",
"make:assets:cpu": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --cpu && comfy-cli --here standalone && node ../scripts/env.mjs",
"make:assets:nvidia": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --nvidia && comfy-cli --here standalone",
"make:assets:amd": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --amd --manager-url https://github.com/Comfy-Org/manager-core && comfy-cli --here standalone",
"make:assets:cpu": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --cpu --manager-url https://github.com/Comfy-Org/manager-core && cd ComfyUI && git checkout origin/rh-userdirectory && cd ../ && comfy-cli --here standalone && node ../scripts/env.mjs",
"make:assets:nvidia": "cd assets && comfy-cli --skip-prompt --here install --fast-deps --nvidia --manager-url https://github.com/Comfy-Org/manager-core && comfy-cli --here standalone",
"notarize": "node debug/notarize.js",
"package": "electron-forge package",
"publish": "electron-forge publish",
Expand Down
84 changes: 73 additions & 11 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { spawn, ChildProcess } from 'node:child_process';
import * as fs from 'node:fs/promises';
import * as fsPromises from 'node:fs/promises';
import fs from 'fs'
import net from 'node:net';
import path from 'node:path';
import { SetupTray } from './tray';
Expand All @@ -14,13 +15,11 @@ import('electron-squirrel-startup').then(ess => {
}
});
import tar from 'tar';
import { create } from 'node:domain';

let pythonProcess: ChildProcess | null = null;
const host = '127.0.0.1'; // Replace with the desired IP address
const port = 8188; // Replace with the port number your server is running on
const scriptPath = path.join(process.resourcesPath, 'ComfyUI', 'main.py');

const packagedComfyUIExecutable = process.platform == 'win32' ? 'run_cpu.bat' : process.platform == 'darwin' ? 'ComfyUI' : 'ComfyUI';

const createWindow = () => {
// Create the browser window.
Expand Down Expand Up @@ -127,7 +126,14 @@ const launchPythonServer = async (args: {userResourcesPath: string, appResources
const pythonInterpreterPath = process.platform==='win32' ? path.join(pythonRootPath, 'python.exe') : path.join(pythonRootPath, 'bin', 'python');
const pythonRecordPath = path.join(pythonRootPath, "INSTALLER");
const scriptPath = path.join(appResourcesPath, 'ComfyUI', 'main.py');
const comfyMainCmd = [scriptPath, ...(process.env.COMFYUI_CPU_ONLY === "true" ? ["--cpu"] : [])];
const userDirectoryPath = path.join(app.getPath('userData'), 'user');
const inputDirectoryPath = path.join(app.getPath('userData'), 'input');
const outputDirectoryPath = path.join(app.getPath('userData'), 'output');
const comfyMainCmd = [scriptPath,
'--user-directory', userDirectoryPath,
'--input-directory', inputDirectoryPath,
'--output-directory', outputDirectoryPath,
...(process.env.COMFYUI_CPU_ONLY === "true" ? ["--cpu"] : [])];

const spawnPython = async () => {
pythonProcess = spawn(pythonInterpreterPath, comfyMainCmd, {
Expand All @@ -144,29 +150,29 @@ const launchPythonServer = async (args: {userResourcesPath: string, appResources

try {
// check for existence of both interpreter and INSTALLER record to ensure a correctly installed python env
await Promise.all([fs.access(pythonInterpreterPath), fs.access(pythonRecordPath)]);
await Promise.all([fsPromises.access(pythonInterpreterPath), fsPromises.access(pythonRecordPath)]);
spawnPython();
} catch {
console.log('Running one-time python installation on first startup...');
// clean up any possible existing non-functional python env
try {
await fs.rm(pythonRootPath, {recursive: true});
await fsPromises.rm(pythonRootPath, {recursive: true});
} catch {null;}

const pythonTarPath = path.join(appResourcesPath, 'python.tgz');
await tar.extract({file: pythonTarPath, cwd: userResourcesPath, strict: true});

const wheelsPath = path.join(pythonRootPath, 'wheels');
const rehydrateCmd = ['-m', 'uv', 'pip', 'install', '--no-index', '--no-deps', ...(await fs.readdir(wheelsPath)).map(x => path.join(wheelsPath, x))];
const rehydrateCmd = ['-m', 'uv', 'pip', 'install', '--no-index', '--no-deps', ...(await fsPromises.readdir(wheelsPath)).map(x => path.join(wheelsPath, x))];
const rehydrateProc = spawn(pythonInterpreterPath, rehydrateCmd, {cwd: wheelsPath});

rehydrateProc.on("exit", code => {
// write an INSTALLER record on sucessful completion of rehydration
fs.writeFile(pythonRecordPath, "ComfyUI");
fsPromises.writeFile(pythonRecordPath, "ComfyUI");

if (code===0) {
// remove the now installed wheels
fs.rm(wheelsPath, {recursive: true});
fsPromises.rm(wheelsPath, {recursive: true});
console.log(`Python successfully installed to ${pythonRootPath}`);

spawnPython();
Expand Down Expand Up @@ -229,12 +235,13 @@ app.on('ready', async () => {
}

try {
await fs.mkdir(userResourcesPath);
await fsPromises.mkdir(userResourcesPath);
} catch {
// if user-specific resources dir already exists, that is fine
}
try {
createWindow();
createComfyDirectories();
await launchPythonServer({userResourcesPath, appResourcesPath});
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -262,6 +269,61 @@ const killPythonServer = () => {
})
};

type DirectoryStructure = (string | [string, string[]])[];

function createComfyDirectories(): void {
const userDataPath: string = app.getPath('userData');
const directories: DirectoryStructure = [
'custom_nodes',
'input',
'output',
'user',
['models', [
'checkpoints',
'clip',
'clip_vision',
'configs',
'controlnet',
'diffusers',
'diffusion_models',
'embeddings',
'gligen',
'hypernetworks',
'loras',
'photomaker',
'style_models',
'unet',
'upscale_models',
'vae',
'vae_approx',
]]
];

function createDir(dirPath: string): void {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
console.log(`Created directory: ${dirPath}`);
} else {
console.log(`Directory already exists: ${dirPath}`);
}
}

directories.forEach((dir: string | [string, string[]]) => {
if (Array.isArray(dir)) {
const [mainDir, subDirs] = dir;
const mainDirPath: string = path.join(userDataPath, mainDir);
createDir(mainDirPath);
subDirs.forEach((subDir: string) => {
const subDirPath: string = path.join(mainDirPath, subDir);
createDir(subDirPath);
});
} else {
const dirPath: string = path.join(userDataPath, dir);
createDir(dirPath);
}
});
}

app.on('before-quit', async () => {
try {
await killPythonServer();
Expand Down
Loading