diff --git a/src/constants/index.ts b/src/constants/index.ts index aee71a9..e0300bb 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -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 = { diff --git a/src/hooks/useInterfaceSelection.ts b/src/hooks/useInterfaceSelection.ts index f3130cf..c5129f8 100644 --- a/src/hooks/useInterfaceSelection.ts +++ b/src/hooks/useInterfaceSelection.ts @@ -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('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, }; } diff --git a/src/main/modules/comfyui.ts b/src/main/modules/comfyui.ts index 0f6abc0..9ea843d 100644 --- a/src/main/modules/comfyui.ts +++ b/src/main/modules/comfyui.ts @@ -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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 => { + const { done, value } = await reader.read(); + if (done) { + stream.end(); + return; + } + stream.write(Buffer.from(value)); + return pump(); + }; + await pump(); + + await new Promise((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((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 +): Promise { + 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((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 { + 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((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 { + 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((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 => { - const { done, value } = await reader.read(); - if (done) { - stream.end(); - return; - } - stream.write(Buffer.from(value)); - return pump(); - }; - await pump(); - - await new Promise((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((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((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 = { - 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(); + } } diff --git a/src/main/modules/config.ts b/src/main/modules/config.ts index 8780839..f2c1d79 100644 --- a/src/main/modules/config.ts +++ b/src/main/modules/config.ts @@ -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; diff --git a/src/main/modules/hardware.ts b/src/main/modules/hardware.ts index c59bbc1..a3f2d68 100644 --- a/src/main/modules/hardware.ts +++ b/src/main/modules/hardware.ts @@ -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)); } } } diff --git a/src/main/modules/openwebui.ts b/src/main/modules/openwebui.ts index 63ba75e..43d3a55 100644 --- a/src/main/modules/openwebui.ts +++ b/src/main/modules/openwebui.ts @@ -35,8 +35,6 @@ async function createUvProcess(args: string[], env?: Record) { } async function waitForOpenWebUIToStart() { - sendKoboldOutput('Waiting for Open WebUI to start...'); - return new Promise((resolve, reject) => { const checkForOutput = (data: Buffer) => { const output = data.toString(); diff --git a/src/stores/frontendPreference.ts b/src/stores/frontendPreference.ts new file mode 100644 index 0000000..1318178 --- /dev/null +++ b/src/stores/frontendPreference.ts @@ -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; +} + +export const useFrontendPreferenceStore = create()( + (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:'); + }, + }) +); diff --git a/src/utils/node/gpu.ts b/src/utils/node/gpu.ts index b7df312..50dcf1f 100644 --- a/src/utils/node/gpu.ts +++ b/src/utils/node/gpu.ts @@ -182,7 +182,7 @@ foreach ($gpu in $gpus) { setTimeout(() => { powershell.kill('SIGTERM'); resolve([]); - }, 3000); + }, 5000); }); }