fixes for windows monitoring, fix comfyui to run on windows

This commit is contained in:
Egor 2025-09-14 00:57:26 -07:00
parent 329e191a49
commit de7ea100db
8 changed files with 527 additions and 419 deletions

View file

@ -72,6 +72,9 @@ export const GITHUB_API = {
get COMFYUI_DOWNLOAD_URL() {
return `https://github.com/${this.COMFYUI_REPO}/archive/refs/heads/master.zip`;
},
get COMFYUI_LATEST_COMMIT_URL() {
return `${this.BASE_URL}/repos/${this.COMFYUI_REPO}/commits/master`;
},
} as const;
export const ASSET_SUFFIXES = {

View file

@ -1,41 +1,23 @@
import { useState, useEffect } from 'react';
import { useEffect } from 'react';
import { useLaunchConfigStore } from '@/stores/launchConfig';
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
import {
getAvailableInterfaceOptions,
getDefaultInterfaceTab,
getServerInterfaceInfo,
} from '@/utils/interface';
import { safeExecute } from '@/utils/logger';
import type { FrontendPreference } from '@/types';
export function useFrontendPreference() {
const [frontendPreference, setFrontendPreference] =
useState<FrontendPreference>('koboldcpp');
const { frontendPreference, setFrontendPreference, loadFromConfig } =
useFrontendPreferenceStore();
useEffect(() => {
const loadPreference = async () => {
await safeExecute(async () => {
const preference = (await window.electronAPI.config.get(
'frontendPreference'
)) as FrontendPreference;
setFrontendPreference(preference || 'koboldcpp');
}, 'Error loading frontend preference:');
};
loadPreference();
}, []);
const updateFrontendPreference = async (preference: FrontendPreference) => {
setFrontendPreference(preference);
await safeExecute(async () => {
await window.electronAPI.config.set('frontendPreference', preference);
}, 'Error saving frontend preference:');
};
loadFromConfig();
}, [loadFromConfig]);
return {
frontendPreference,
setFrontendPreference: updateFrontendPreference,
setFrontendPreference,
};
}

View file

@ -14,8 +14,83 @@ import { getAppVersion, ensureDir } from '@/utils/node/fs';
import { getGPUData } from '@/utils/node/gpu';
import { getUvEnvironment } from './dependencies';
interface ComfyUIVersionInfo {
sha: string;
date: string;
}
async function getLatestComfyUIVersion(): Promise<ComfyUIVersionInfo | null> {
try {
const response = await fetch(GITHUB_API.COMFYUI_LATEST_COMMIT_URL);
if (!response.ok) {
throw new Error(
`Failed to fetch ComfyUI version: ${response.statusText}`
);
}
const data = await response.json();
return {
sha: data.sha,
date: data.commit.committer.date,
};
} catch (error) {
logError(
'Failed to fetch latest ComfyUI version',
error instanceof Error ? error : undefined
);
return null;
}
}
async function getCurrentComfyUIVersion(
workspaceDir: string
): Promise<ComfyUIVersionInfo | null> {
try {
const fs = await import('fs/promises');
const versionFile = join(workspaceDir, '.version.json');
const content = await fs.readFile(versionFile, 'utf-8');
return JSON.parse(content);
} catch {
return null;
}
}
async function saveComfyUIVersion(
workspaceDir: string,
version: ComfyUIVersionInfo
): Promise<void> {
try {
const fs = await import('fs/promises');
const versionFile = join(workspaceDir, '.version.json');
await fs.writeFile(versionFile, JSON.stringify(version, null, 2));
} catch (error) {
logError(
'Failed to save ComfyUI version info',
error instanceof Error ? error : undefined
);
}
}
async function shouldUpdateComfyUI(workspaceDir: string): Promise<boolean> {
const current = await getCurrentComfyUIVersion(workspaceDir);
const latest = await getLatestComfyUIVersion();
if (!current || !latest) {
return false;
}
return current.sha !== latest.sha;
}
let comfyUIProcess: ChildProcess | null = null;
function getPythonPath(workspaceDir: string): string {
const isWindows = process.platform === 'win32';
const pythonExecutable = isWindows ? 'python.exe' : 'python';
const scriptsDir = isWindows ? 'Scripts' : 'bin';
return join(workspaceDir, '.venv', scriptsDir, pythonExecutable);
}
process.on('SIGINT', () => {
void cleanup();
});
@ -24,6 +99,19 @@ process.on('SIGTERM', () => {
void cleanup();
});
async function shouldForceCPUMode(): Promise<boolean> {
try {
const gpus = await getGPUData();
const hasAMD = gpus.some((gpu) => gpu.deviceName.includes('AMD'));
const hasNVIDIA = gpus.some((gpu) => gpu.deviceName.includes('NVIDIA'));
const isWindows = process.platform === 'win32';
return (hasAMD && isWindows) || (!hasAMD && !hasNVIDIA);
} catch {
return true;
}
}
async function getPyTorchInstallArgs(pythonPath: string) {
const args = [
'pip',
@ -39,8 +127,9 @@ async function getPyTorchInstallArgs(pythonPath: string) {
const gpus = await getGPUData();
const hasAMD = gpus.some((gpu) => gpu.deviceName.includes('AMD'));
const hasNVIDIA = gpus.some((gpu) => gpu.deviceName.includes('NVIDIA'));
const isWindows = process.platform === 'win32';
if (hasAMD) {
if (hasAMD && !isWindows) {
sendKoboldOutput(
'AMD GPU detected, installing PyTorch with ROCm support...'
);
@ -51,9 +140,15 @@ async function getPyTorchInstallArgs(pythonPath: string) {
);
args.push('--extra-index-url', 'https://download.pytorch.org/whl/cu121');
} else {
sendKoboldOutput(
'No dedicated GPU detected, installing CPU-only PyTorch...'
);
if (hasAMD && isWindows) {
sendKoboldOutput(
'AMD GPU detected on Windows, installing CPU PyTorch (ROCm not available on Windows)...'
);
} else {
sendKoboldOutput(
'No dedicated GPU detected, installing CPU-only PyTorch...'
);
}
args.push('--index-url', 'https://download.pytorch.org/whl/cpu');
}
} catch {
@ -63,6 +158,332 @@ async function getPyTorchInstallArgs(pythonPath: string) {
return args;
}
async function downloadAndExtractComfyUI(workspaceDir: string): Promise<void> {
sendKoboldOutput('Downloading ComfyUI...');
const response = await fetch(GITHUB_API.COMFYUI_DOWNLOAD_URL);
if (!response.ok) {
throw new Error(`Failed to download ComfyUI: ${response.statusText}`);
}
const fs = await import('fs/promises');
const path = await import('path');
const { createWriteStream } = await import('fs');
const zipPath = path.join(workspaceDir, 'comfyui.zip');
const stream = createWriteStream(zipPath);
if (response.body) {
const reader = response.body.getReader();
const pump = async (): Promise<void> => {
const { done, value } = await reader.read();
if (done) {
stream.end();
return;
}
stream.write(Buffer.from(value));
return pump();
};
await pump();
await new Promise<void>((resolve, reject) => {
stream.on('finish', resolve);
stream.on('error', reject);
});
}
const zipStats = await fs.stat(zipPath);
sendKoboldOutput(
`ComfyUI downloaded (${Math.round(zipStats.size / 1024 / 1024)}MB), extracting...`
);
await new Promise<void>((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
if (err) {
reject(err);
return;
}
zipfile!.readEntry();
zipfile!.on('entry', (entry) => {
const entryPath = entry.fileName;
const destPath = path.join(
workspaceDir,
entryPath.replace(/^[^/]+\//, '')
);
if (/\/$/.test(entryPath)) {
fs.mkdir(destPath, { recursive: true })
.then(() => zipfile!.readEntry())
.catch(reject);
} else {
zipfile!.openReadStream(entry, (err, readStream) => {
if (err) {
reject(err);
return;
}
fs.mkdir(path.dirname(destPath), { recursive: true })
.then(() => {
const writeStream = createWriteStream(destPath);
readStream!.pipe(writeStream);
writeStream.on('close', () => zipfile!.readEntry());
writeStream.on('error', reject);
})
.catch(reject);
});
}
});
zipfile!.on('end', () => {
fs.unlink(zipPath)
.then(() => resolve())
.catch(reject);
});
});
});
sendKoboldOutput('ComfyUI extracted successfully');
const latestVersion = await getLatestComfyUIVersion();
if (latestVersion) {
await saveComfyUIVersion(workspaceDir, latestVersion);
}
}
async function installDependencies(
workspaceDir: string,
env: Record<string, string | undefined>
): Promise<void> {
const fs = await import('fs/promises');
sendKoboldOutput('Installing ComfyUI dependencies...');
const requirementsPath = join(workspaceDir, 'requirements.txt');
const requirementsExists = await fs
.access(requirementsPath)
.then(() => true)
.catch(() => false);
if (!requirementsExists) {
throw new Error('requirements.txt not found in ComfyUI directory');
}
const pythonPath = getPythonPath(workspaceDir);
const torchInstallArgs = await getPyTorchInstallArgs(pythonPath);
const torchInstallProcess = spawn('uv', torchInstallArgs, {
stdio: 'pipe',
env,
});
return new Promise<void>((resolve, reject) => {
torchInstallProcess.on('exit', (torchCode: number | null) => {
if (torchCode === 0) {
sendKoboldOutput(
'PyTorch installed successfully, installing remaining dependencies...'
);
const pipInstallProcess = spawn(
'uv',
[
'pip',
'install',
'--python',
getPythonPath(workspaceDir),
'-r',
requirementsPath,
],
{
stdio: 'pipe',
env,
}
);
pipInstallProcess.on('exit', (pipCode: number | null) => {
if (pipCode === 0) {
sendKoboldOutput('ComfyUI dependencies installed successfully');
const verifyProcess = spawn(
getPythonPath(workspaceDir),
[
'-c',
'import torch; print("PyTorch version:", torch.__version__)',
],
{
stdio: 'pipe',
env,
}
);
verifyProcess.on('exit', (verifyCode: number | null) => {
if (verifyCode === 0) {
sendKoboldOutput('ComfyUI installation verified successfully');
resolve();
} else {
reject(
new Error(
`ComfyUI verification failed with code ${verifyCode}`
)
);
}
});
verifyProcess.on('error', (error) => {
reject(error);
});
if (verifyProcess.stdout) {
verifyProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (verifyProcess.stderr) {
verifyProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(
`Verify Error: ${data.toString('utf8')}`,
true
);
});
}
} else {
reject(
new Error(`Dependency installation failed with code ${pipCode}`)
);
}
});
pipInstallProcess.on('error', (error) => {
reject(error);
});
if (pipInstallProcess.stdout) {
pipInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (pipInstallProcess.stderr) {
pipInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
} else {
reject(new Error(`PyTorch installation failed with code ${torchCode}`));
}
});
torchInstallProcess.on('error', (error) => {
reject(error);
});
if (torchInstallProcess.stdout) {
torchInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (torchInstallProcess.stderr) {
torchInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
});
}
async function installComfyUI(workspaceDir: string): Promise<void> {
const env = await getUvEnvironment();
sendKoboldOutput('Creating virtual environment...');
const installProcess = spawn(
'uv',
['venv', '--python', '3.11', join(workspaceDir, '.venv')],
{
stdio: 'pipe',
env,
}
);
return new Promise<void>((resolve, reject) => {
installProcess.on('exit', async (code) => {
if (code === 0) {
try {
await downloadAndExtractComfyUI(workspaceDir);
await installDependencies(workspaceDir, env);
resolve();
} catch (error) {
reject(error instanceof Error ? error : new Error(String(error)));
}
} else {
reject(
new Error(`Virtual environment creation failed with code ${code}`)
);
}
});
installProcess.on('error', (error) => {
reject(error);
});
if (installProcess.stdout) {
installProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (installProcess.stderr) {
installProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
});
}
async function updateComfyUI(workspaceDir: string): Promise<void> {
const fs = await import('fs/promises');
const backupDir = join(workspaceDir, 'backup');
await ensureDir(backupDir);
const filesToBackup = ['main.py', 'requirements.txt'];
const backupPromises = filesToBackup.map(async (file) => {
const srcPath = join(workspaceDir, file);
const destPath = join(backupDir, file);
try {
await fs.copyFile(srcPath, destPath);
} catch (error) {
void error;
}
});
await Promise.all(backupPromises);
try {
await downloadAndExtractComfyUI(workspaceDir);
sendKoboldOutput('ComfyUI updated successfully');
await fs.rm(backupDir, { recursive: true, force: true });
} catch (error) {
sendKoboldOutput('Update failed, restoring backup...');
const restorePromises = filesToBackup.map(async (file) => {
const srcPath = join(backupDir, file);
const destPath = join(workspaceDir, file);
try {
await fs.copyFile(srcPath, destPath);
} catch (error) {
void error;
}
});
await Promise.all(restorePromises);
await fs.rm(backupDir, { recursive: true, force: true });
throw error;
}
}
async function ensureComfyUIInstalled() {
const installDir = getInstallDir();
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
@ -74,325 +495,21 @@ async function ensureComfyUIInstalled() {
.then(() => true)
.catch(() => false);
const needsUpdate =
isComfyUIInstalled && (await shouldUpdateComfyUI(comfyUIWorkspace));
if (!isComfyUIInstalled) {
sendKoboldOutput('ComfyUI not found, installing via uv...');
const env = await getUvEnvironment();
sendKoboldOutput(
'Creating virtual environment and installing ComfyUI dependencies...'
);
const installProcess = spawn(
'uv',
['venv', '--python', '3.11', join(comfyUIWorkspace, '.venv')],
{
stdio: 'pipe',
env,
}
);
return new Promise<void>((resolve, reject) => {
installProcess.on('exit', async (code) => {
if (code === 0) {
try {
sendKoboldOutput(
'Virtual environment created, downloading ComfyUI...'
);
const response = await fetch(GITHUB_API.COMFYUI_DOWNLOAD_URL);
if (!response.ok) {
throw new Error(
`Failed to download ComfyUI: ${response.statusText}`
);
}
const fs = await import('fs/promises');
const path = await import('path');
const { createWriteStream } = await import('fs');
const zipPath = path.join(comfyUIWorkspace, 'comfyui.zip');
const stream = createWriteStream(zipPath);
if (response.body) {
const reader = response.body.getReader();
const pump = async (): Promise<void> => {
const { done, value } = await reader.read();
if (done) {
stream.end();
return;
}
stream.write(Buffer.from(value));
return pump();
};
await pump();
await new Promise<void>((resolve, reject) => {
stream.on('finish', resolve);
stream.on('error', reject);
});
}
const zipStats = await fs.stat(zipPath);
sendKoboldOutput(
`ComfyUI downloaded (${Math.round(zipStats.size / 1024 / 1024)}MB), extracting...`
);
try {
await new Promise<void>((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (err, zipfile) => {
if (err) {
reject(err);
return;
}
if (!zipfile) {
reject(new Error('Failed to open zip file'));
return;
}
zipfile.readEntry();
zipfile.on('entry', (entry) => {
if (/\/$/.test(entry.fileName)) {
zipfile.readEntry();
} else {
zipfile.openReadStream(entry, (err, readStream) => {
if (err) {
reject(err);
return;
}
if (!readStream) {
reject(new Error('Failed to create read stream'));
return;
}
const fileName = entry.fileName.replace(/^[^/]+\//, '');
const outputPath = path.join(
comfyUIWorkspace,
fileName
);
fs.mkdir(path.dirname(outputPath), { recursive: true })
.then(() => {
const writeStream = createWriteStream(outputPath);
readStream.pipe(writeStream);
writeStream.on('close', () => {
zipfile.readEntry();
});
writeStream.on('error', reject);
})
.catch(reject);
});
}
});
zipfile.on('end', () => {
resolve();
});
zipfile.on('error', reject);
});
});
// eslint-disable-next-line no-restricted-syntax
await fs.unlink(zipPath);
sendKoboldOutput('ComfyUI extracted, installing dependencies...');
const requirementsPath = join(
comfyUIWorkspace,
'requirements.txt'
);
const requirementsExists = await fs
.access(requirementsPath)
.then(() => true)
.catch(() => false);
if (!requirementsExists) {
throw new Error(
'requirements.txt not found in ComfyUI directory'
);
}
const pythonPath = join(
comfyUIWorkspace,
'.venv',
'bin',
'python'
);
const torchInstallArgs = await getPyTorchInstallArgs(pythonPath);
const torchInstallProcess = spawn('uv', torchInstallArgs, {
stdio: 'pipe',
env,
});
torchInstallProcess.on('exit', (torchCode: number | null) => {
if (torchCode === 0) {
sendKoboldOutput(
'PyTorch with ROCm installed, installing remaining dependencies...'
);
const pipInstallProcess = spawn(
'uv',
[
'pip',
'install',
'--python',
join(comfyUIWorkspace, '.venv', 'bin', 'python'),
'-r',
requirementsPath,
],
{
stdio: 'pipe',
env,
}
);
pipInstallProcess.on('exit', async (pipCode) => {
if (pipCode === 0) {
sendKoboldOutput(
'Dependencies installation completed, verifying...'
);
const verifyProcess = spawn(
join(comfyUIWorkspace, '.venv', 'bin', 'python'),
[
'-c',
'import einops; print("einops found:", einops.__version__)',
],
{
stdio: 'pipe',
env,
}
);
verifyProcess.on('exit', (verifyCode) => {
if (verifyCode === 0) {
sendKoboldOutput('ComfyUI installed successfully!');
resolve();
} else {
sendKoboldOutput(
'Warning: einops verification failed, but continuing...'
);
resolve();
}
});
if (verifyProcess.stdout) {
verifyProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(
`Verify: ${data.toString('utf8')}`,
true
);
});
}
if (verifyProcess.stderr) {
verifyProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(
`Verify Error: ${data.toString('utf8')}`,
true
);
});
}
} else {
reject(
new Error(
`Dependency installation failed with code ${pipCode}`
)
);
}
});
pipInstallProcess.on('error', (error) => {
reject(error);
});
if (pipInstallProcess.stdout) {
pipInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (pipInstallProcess.stderr) {
pipInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
} else {
reject(
new Error(
`PyTorch installation failed with code ${torchCode}`
)
);
}
});
torchInstallProcess.on('error', (error) => {
reject(error);
});
if (torchInstallProcess.stdout) {
torchInstallProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (torchInstallProcess.stderr) {
torchInstallProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
} catch (error) {
reject(
new Error(
`Failed to extract ComfyUI: ${error instanceof Error ? error.message : String(error)}`
)
);
}
} catch (error) {
reject(
new Error(
`Failed to download ComfyUI: ${error instanceof Error ? error.message : String(error)}`
)
);
}
} else {
reject(
new Error(`Virtual environment creation failed with code ${code}`)
);
}
});
installProcess.on('error', (error) => {
reject(error);
});
if (installProcess.stdout) {
installProcess.stdout.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (installProcess.stderr) {
installProcess.stderr.on('data', (data: Buffer) => {
sendKoboldOutput(data.toString('utf8'), true);
});
}
});
sendKoboldOutput('ComfyUI not found, installing...');
await installComfyUI(comfyUIWorkspace);
} else if (needsUpdate) {
sendKoboldOutput('ComfyUI update available, updating...');
await updateComfyUI(comfyUIWorkspace);
} else {
sendKoboldOutput('ComfyUI found in workspace');
sendKoboldOutput('ComfyUI found in workspace and up to date');
}
}
async function waitForComfyUIToStart() {
sendKoboldOutput('Waiting for ComfyUI to start...');
return new Promise<void>((resolve, reject) => {
const checkForOutput = (data: Buffer) => {
const output = data.toString();
@ -420,106 +537,80 @@ export async function startFrontend(args: string[]) {
name: 'comfyui',
port: COMFYUI.PORT,
};
const {
host: koboldHost,
port: koboldPort,
isImageMode,
} = parseKoboldConfig(args);
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
if (!isImageMode) {
throw new Error('ComfyUI requires image generation mode to be enabled');
}
const [, appVersion] = await Promise.all([stopFrontend(), getAppVersion()]);
sendKoboldOutput(
`Preparing ComfyUI to connect at ${koboldHost}:${koboldPort} for image generation...`
);
const koboldUrl = `http://${koboldHost}:${koboldPort}`;
sendKoboldOutput(`Starting ComfyUI frontend on port ${config.port}...`);
await ensureComfyUIInstalled();
const installDir = getInstallDir();
const comfyUIDataDir = join(installDir, 'comfyui-data');
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
const comfyUIMainPath = join(comfyUIWorkspace, 'main.py');
await Promise.all([ensureDir(comfyUIDataDir), ensureDir(comfyUIWorkspace)]);
const pythonPath = getPythonPath(comfyUIWorkspace);
const comfyUIArgs = [
comfyUIMainPath,
join(comfyUIWorkspace, 'main.py'),
'--port',
config.port.toString(),
'--disable-auto-launch',
'--listen',
'0.0.0.0',
'--enable-cors-header',
];
sendKoboldOutput('Starting ComfyUI directly...');
const forceCPU = await shouldForceCPUMode();
if (forceCPU) {
comfyUIArgs.push('--cpu');
sendKoboldOutput(
'Forcing ComfyUI to use CPU mode (no compatible GPU acceleration available)'
);
}
const envConfig: Record<string, string> = {
KOBOLDCPP_API_URL: `${koboldUrl}/comfyapi/v1`,
USER_AGENT: `Gerbil/${appVersion}`,
};
if (koboldHost && koboldPort) {
sendKoboldOutput(
`Connecting to KoboldCpp at ${koboldHost}:${koboldPort}`
);
}
const env = await getUvEnvironment();
Object.assign(env, envConfig);
sendKoboldOutput(`Running ComfyUI with Python: ${pythonPath}`);
sendKoboldOutput(`ComfyUI args: ${comfyUIArgs.join(' ')}`);
const pythonPath = join(comfyUIWorkspace, '.venv', 'bin', 'python');
comfyUIProcess = spawn(pythonPath, comfyUIArgs, {
stdio: 'pipe',
env,
cwd: comfyUIWorkspace,
stdio: 'pipe',
});
const version = await getAppVersion();
sendKoboldOutput(`ComfyUI started via Gerbil v${version}`);
if (comfyUIProcess.stdout) {
comfyUIProcess.stdout.on('data', (data: Buffer) => {
try {
const output = data.toString('utf8');
sendKoboldOutput(output, true);
} catch (error) {
logError('Error processing stdout data:', error as Error);
}
sendKoboldOutput(data.toString('utf8'), true);
});
}
if (comfyUIProcess.stderr) {
comfyUIProcess.stderr.on('data', (data: Buffer) => {
try {
const output = data.toString('utf8');
sendKoboldOutput(output, true);
} catch (error) {
logError('Error processing stderr data:', error as Error);
}
sendKoboldOutput(data.toString('utf8'), true);
});
}
comfyUIProcess.on('exit', (code: number | null, signal: string | null) => {
const message = signal
? `ComfyUI terminated with signal ${signal}`
: `ComfyUI exited with code ${code}`;
sendKoboldOutput(message);
if (code === null && signal) {
sendKoboldOutput(`ComfyUI process terminated by signal: ${signal}`);
} else {
sendKoboldOutput(`ComfyUI process exited with code: ${code}`);
}
comfyUIProcess = null;
});
comfyUIProcess.on('error', (error) => {
logError('ComfyUI process error:', error);
sendKoboldOutput(`ComfyUI error: ${error.message}`);
comfyUIProcess.on('error', (error: Error) => {
logError('ComfyUI process error', error);
comfyUIProcess = null;
});
await waitForComfyUIToStart();
sendKoboldOutput(`ComfyUI is ready and auto-configured!`);
sendKoboldOutput(`Access ComfyUI at: http://localhost:${config.port}`);
sendKoboldOutput(
`Image Generation API: ${koboldUrl}/comfyapi/v1 (auto-configured)`
);
} catch (error) {
logError('Failed to start ComfyUI frontend:', error as Error);
sendKoboldOutput(`Failed to start ComfyUI: ${(error as Error).message}`);
comfyUIProcess = null;
logError(
'Failed to start ComfyUI',
error instanceof Error ? error : undefined
);
throw error;
}
}
@ -527,20 +618,22 @@ export async function startFrontend(args: string[]) {
export async function stopFrontend() {
if (comfyUIProcess) {
sendKoboldOutput('Stopping ComfyUI...');
try {
await terminateProcess(comfyUIProcess, {
logError: (message, error) => logError(message, error),
});
await terminateProcess(comfyUIProcess);
comfyUIProcess = null;
sendKoboldOutput('ComfyUI stopped');
} catch (error) {
logError('Error stopping ComfyUI:', error as Error);
logError(
'Error stopping ComfyUI',
error instanceof Error ? error : undefined
);
comfyUIProcess = null;
}
comfyUIProcess = null;
}
}
export async function cleanup() {
await stopFrontend();
if (comfyUIProcess) {
await stopFrontend();
}
}

View file

@ -1,12 +1,12 @@
import { logError } from '@/main/modules/logging';
import { readJsonFile, writeJsonFile } from '@/utils/node/fs';
import { getConfigDir } from '@/utils/node/path';
import type { FrontendPreference } from '@/types';
import type { MantineColorScheme } from '@mantine/core';
import { homedir } from 'os';
import { join } from 'path';
import { nativeTheme } from 'electron';
import { PRODUCT_NAME } from '@/constants';
import type { FrontendPreference } from '@/types';
import type { MantineColorScheme } from '@mantine/core';
type ConfigValue = string | number | boolean | unknown[] | undefined;

View file

@ -353,8 +353,7 @@ function parseClInfoOutput(output: string) {
const deviceName = findDeviceNameInClInfo(lines, i);
if (deviceName && currentPlatform) {
const deviceLabel = `${formatDeviceName(deviceName)} (${currentPlatform})`;
devices.push(deviceLabel);
devices.push(formatDeviceName(deviceName));
}
}
}

View file

@ -35,8 +35,6 @@ async function createUvProcess(args: string[], env?: Record<string, string>) {
}
async function waitForOpenWebUIToStart() {
sendKoboldOutput('Waiting for Open WebUI to start...');
return new Promise<void>((resolve, reject) => {
const checkForOutput = (data: Buffer) => {
const output = data.toString();

View file

@ -0,0 +1,33 @@
import { create } from 'zustand';
import { safeExecute } from '@/utils/logger';
import type { FrontendPreference } from '@/types';
interface FrontendPreferenceState {
frontendPreference: FrontendPreference;
setFrontendPreference: (preference: FrontendPreference) => void;
loadFromConfig: () => Promise<void>;
}
export const useFrontendPreferenceStore = create<FrontendPreferenceState>()(
(set) => ({
frontendPreference: 'koboldcpp',
setFrontendPreference: (preference: FrontendPreference) => {
set({ frontendPreference: preference });
safeExecute(async () => {
await window.electronAPI.config.set('frontendPreference', preference);
}, 'Error saving frontend preference:');
},
loadFromConfig: async () => {
await safeExecute(async () => {
const preference = (await window.electronAPI.config.get(
'frontendPreference'
)) as FrontendPreference;
set({ frontendPreference: preference || 'koboldcpp' });
}, 'Error loading frontend preference:');
},
})
);

View file

@ -182,7 +182,7 @@ foreach ($gpu in $gpus) {
setTimeout(() => {
powershell.kill('SIGTERM');
resolve([]);
}, 3000);
}, 5000);
});
}