mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 19:54:44 -07:00
fixes for windows monitoring, fix comfyui to run on windows
This commit is contained in:
parent
329e191a49
commit
de7ea100db
8 changed files with 527 additions and 419 deletions
|
|
@ -72,6 +72,9 @@ export const GITHUB_API = {
|
||||||
get COMFYUI_DOWNLOAD_URL() {
|
get COMFYUI_DOWNLOAD_URL() {
|
||||||
return `https://github.com/${this.COMFYUI_REPO}/archive/refs/heads/master.zip`;
|
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;
|
} as const;
|
||||||
|
|
||||||
export const ASSET_SUFFIXES = {
|
export const ASSET_SUFFIXES = {
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,23 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
import { useLaunchConfigStore } from '@/stores/launchConfig';
|
||||||
|
import { useFrontendPreferenceStore } from '@/stores/frontendPreference';
|
||||||
import {
|
import {
|
||||||
getAvailableInterfaceOptions,
|
getAvailableInterfaceOptions,
|
||||||
getDefaultInterfaceTab,
|
getDefaultInterfaceTab,
|
||||||
getServerInterfaceInfo,
|
getServerInterfaceInfo,
|
||||||
} from '@/utils/interface';
|
} from '@/utils/interface';
|
||||||
import { safeExecute } from '@/utils/logger';
|
|
||||||
import type { FrontendPreference } from '@/types';
|
|
||||||
|
|
||||||
export function useFrontendPreference() {
|
export function useFrontendPreference() {
|
||||||
const [frontendPreference, setFrontendPreference] =
|
const { frontendPreference, setFrontendPreference, loadFromConfig } =
|
||||||
useState<FrontendPreference>('koboldcpp');
|
useFrontendPreferenceStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPreference = async () => {
|
loadFromConfig();
|
||||||
await safeExecute(async () => {
|
}, [loadFromConfig]);
|
||||||
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:');
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frontendPreference,
|
frontendPreference,
|
||||||
setFrontendPreference: updateFrontendPreference,
|
setFrontendPreference,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,83 @@ import { getAppVersion, ensureDir } from '@/utils/node/fs';
|
||||||
import { getGPUData } from '@/utils/node/gpu';
|
import { getGPUData } from '@/utils/node/gpu';
|
||||||
import { getUvEnvironment } from './dependencies';
|
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;
|
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', () => {
|
process.on('SIGINT', () => {
|
||||||
void cleanup();
|
void cleanup();
|
||||||
});
|
});
|
||||||
|
|
@ -24,6 +99,19 @@ process.on('SIGTERM', () => {
|
||||||
void cleanup();
|
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) {
|
async function getPyTorchInstallArgs(pythonPath: string) {
|
||||||
const args = [
|
const args = [
|
||||||
'pip',
|
'pip',
|
||||||
|
|
@ -39,8 +127,9 @@ async function getPyTorchInstallArgs(pythonPath: string) {
|
||||||
const gpus = await getGPUData();
|
const gpus = await getGPUData();
|
||||||
const hasAMD = gpus.some((gpu) => gpu.deviceName.includes('AMD'));
|
const hasAMD = gpus.some((gpu) => gpu.deviceName.includes('AMD'));
|
||||||
const hasNVIDIA = gpus.some((gpu) => gpu.deviceName.includes('NVIDIA'));
|
const hasNVIDIA = gpus.some((gpu) => gpu.deviceName.includes('NVIDIA'));
|
||||||
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
if (hasAMD) {
|
if (hasAMD && !isWindows) {
|
||||||
sendKoboldOutput(
|
sendKoboldOutput(
|
||||||
'AMD GPU detected, installing PyTorch with ROCm support...'
|
'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');
|
args.push('--extra-index-url', 'https://download.pytorch.org/whl/cu121');
|
||||||
} else {
|
} else {
|
||||||
sendKoboldOutput(
|
if (hasAMD && isWindows) {
|
||||||
'No dedicated GPU detected, installing CPU-only PyTorch...'
|
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');
|
args.push('--index-url', 'https://download.pytorch.org/whl/cpu');
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -63,6 +158,332 @@ async function getPyTorchInstallArgs(pythonPath: string) {
|
||||||
return args;
|
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() {
|
async function ensureComfyUIInstalled() {
|
||||||
const installDir = getInstallDir();
|
const installDir = getInstallDir();
|
||||||
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
|
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
|
||||||
|
|
@ -74,325 +495,21 @@ async function ensureComfyUIInstalled() {
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
|
|
||||||
|
const needsUpdate =
|
||||||
|
isComfyUIInstalled && (await shouldUpdateComfyUI(comfyUIWorkspace));
|
||||||
|
|
||||||
if (!isComfyUIInstalled) {
|
if (!isComfyUIInstalled) {
|
||||||
sendKoboldOutput('ComfyUI not found, installing via uv...');
|
sendKoboldOutput('ComfyUI not found, installing...');
|
||||||
|
await installComfyUI(comfyUIWorkspace);
|
||||||
const env = await getUvEnvironment();
|
} else if (needsUpdate) {
|
||||||
|
sendKoboldOutput('ComfyUI update available, updating...');
|
||||||
sendKoboldOutput(
|
await updateComfyUI(comfyUIWorkspace);
|
||||||
'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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
sendKoboldOutput('ComfyUI found in workspace');
|
sendKoboldOutput('ComfyUI found in workspace and up to date');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForComfyUIToStart() {
|
async function waitForComfyUIToStart() {
|
||||||
sendKoboldOutput('Waiting for ComfyUI to start...');
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const checkForOutput = (data: Buffer) => {
|
const checkForOutput = (data: Buffer) => {
|
||||||
const output = data.toString();
|
const output = data.toString();
|
||||||
|
|
@ -420,106 +537,80 @@ export async function startFrontend(args: string[]) {
|
||||||
name: 'comfyui',
|
name: 'comfyui',
|
||||||
port: COMFYUI.PORT,
|
port: COMFYUI.PORT,
|
||||||
};
|
};
|
||||||
const {
|
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
|
||||||
host: koboldHost,
|
|
||||||
port: koboldPort,
|
|
||||||
isImageMode,
|
|
||||||
} = parseKoboldConfig(args);
|
|
||||||
|
|
||||||
if (!isImageMode) {
|
sendKoboldOutput(`Starting ComfyUI frontend on port ${config.port}...`);
|
||||||
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}`;
|
|
||||||
|
|
||||||
await ensureComfyUIInstalled();
|
await ensureComfyUIInstalled();
|
||||||
|
|
||||||
const installDir = getInstallDir();
|
const installDir = getInstallDir();
|
||||||
const comfyUIDataDir = join(installDir, 'comfyui-data');
|
|
||||||
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
|
const comfyUIWorkspace = join(installDir, 'comfyui-workspace');
|
||||||
const comfyUIMainPath = join(comfyUIWorkspace, 'main.py');
|
const pythonPath = getPythonPath(comfyUIWorkspace);
|
||||||
|
|
||||||
await Promise.all([ensureDir(comfyUIDataDir), ensureDir(comfyUIWorkspace)]);
|
|
||||||
|
|
||||||
const comfyUIArgs = [
|
const comfyUIArgs = [
|
||||||
comfyUIMainPath,
|
join(comfyUIWorkspace, 'main.py'),
|
||||||
'--port',
|
'--port',
|
||||||
config.port.toString(),
|
config.port.toString(),
|
||||||
'--disable-auto-launch',
|
'--enable-cors-header',
|
||||||
'--listen',
|
|
||||||
'0.0.0.0',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
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> = {
|
if (koboldHost && koboldPort) {
|
||||||
KOBOLDCPP_API_URL: `${koboldUrl}/comfyapi/v1`,
|
sendKoboldOutput(
|
||||||
USER_AGENT: `Gerbil/${appVersion}`,
|
`Connecting to KoboldCpp at ${koboldHost}:${koboldPort}`
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const env = await getUvEnvironment();
|
sendKoboldOutput(`Running ComfyUI with Python: ${pythonPath}`);
|
||||||
Object.assign(env, envConfig);
|
sendKoboldOutput(`ComfyUI args: ${comfyUIArgs.join(' ')}`);
|
||||||
|
|
||||||
const pythonPath = join(comfyUIWorkspace, '.venv', 'bin', 'python');
|
|
||||||
comfyUIProcess = spawn(pythonPath, comfyUIArgs, {
|
comfyUIProcess = spawn(pythonPath, comfyUIArgs, {
|
||||||
stdio: 'pipe',
|
|
||||||
env,
|
|
||||||
cwd: comfyUIWorkspace,
|
cwd: comfyUIWorkspace,
|
||||||
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const version = await getAppVersion();
|
||||||
|
sendKoboldOutput(`ComfyUI started via Gerbil v${version}`);
|
||||||
|
|
||||||
if (comfyUIProcess.stdout) {
|
if (comfyUIProcess.stdout) {
|
||||||
comfyUIProcess.stdout.on('data', (data: Buffer) => {
|
comfyUIProcess.stdout.on('data', (data: Buffer) => {
|
||||||
try {
|
sendKoboldOutput(data.toString('utf8'), true);
|
||||||
const output = data.toString('utf8');
|
|
||||||
sendKoboldOutput(output, true);
|
|
||||||
} catch (error) {
|
|
||||||
logError('Error processing stdout data:', error as Error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comfyUIProcess.stderr) {
|
if (comfyUIProcess.stderr) {
|
||||||
comfyUIProcess.stderr.on('data', (data: Buffer) => {
|
comfyUIProcess.stderr.on('data', (data: Buffer) => {
|
||||||
try {
|
sendKoboldOutput(data.toString('utf8'), true);
|
||||||
const output = data.toString('utf8');
|
|
||||||
sendKoboldOutput(output, true);
|
|
||||||
} catch (error) {
|
|
||||||
logError('Error processing stderr data:', error as Error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
comfyUIProcess.on('exit', (code: number | null, signal: string | null) => {
|
comfyUIProcess.on('exit', (code: number | null, signal: string | null) => {
|
||||||
const message = signal
|
if (code === null && signal) {
|
||||||
? `ComfyUI terminated with signal ${signal}`
|
sendKoboldOutput(`ComfyUI process terminated by signal: ${signal}`);
|
||||||
: `ComfyUI exited with code ${code}`;
|
} else {
|
||||||
sendKoboldOutput(message);
|
sendKoboldOutput(`ComfyUI process exited with code: ${code}`);
|
||||||
|
}
|
||||||
comfyUIProcess = null;
|
comfyUIProcess = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
comfyUIProcess.on('error', (error) => {
|
comfyUIProcess.on('error', (error: Error) => {
|
||||||
logError('ComfyUI process error:', error);
|
logError('ComfyUI process error', error);
|
||||||
sendKoboldOutput(`ComfyUI error: ${error.message}`);
|
|
||||||
comfyUIProcess = null;
|
comfyUIProcess = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitForComfyUIToStart();
|
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) {
|
} catch (error) {
|
||||||
logError('Failed to start ComfyUI frontend:', error as Error);
|
logError(
|
||||||
sendKoboldOutput(`Failed to start ComfyUI: ${(error as Error).message}`);
|
'Failed to start ComfyUI',
|
||||||
comfyUIProcess = null;
|
error instanceof Error ? error : undefined
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -527,20 +618,22 @@ export async function startFrontend(args: string[]) {
|
||||||
export async function stopFrontend() {
|
export async function stopFrontend() {
|
||||||
if (comfyUIProcess) {
|
if (comfyUIProcess) {
|
||||||
sendKoboldOutput('Stopping ComfyUI...');
|
sendKoboldOutput('Stopping ComfyUI...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await terminateProcess(comfyUIProcess, {
|
await terminateProcess(comfyUIProcess);
|
||||||
logError: (message, error) => logError(message, error),
|
comfyUIProcess = null;
|
||||||
});
|
|
||||||
sendKoboldOutput('ComfyUI stopped');
|
sendKoboldOutput('ComfyUI stopped');
|
||||||
} catch (error) {
|
} 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() {
|
export async function cleanup() {
|
||||||
await stopFrontend();
|
if (comfyUIProcess) {
|
||||||
|
await stopFrontend();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { logError } from '@/main/modules/logging';
|
import { logError } from '@/main/modules/logging';
|
||||||
import { readJsonFile, writeJsonFile } from '@/utils/node/fs';
|
import { readJsonFile, writeJsonFile } from '@/utils/node/fs';
|
||||||
import { getConfigDir } from '@/utils/node/path';
|
import { getConfigDir } from '@/utils/node/path';
|
||||||
import type { FrontendPreference } from '@/types';
|
|
||||||
import type { MantineColorScheme } from '@mantine/core';
|
|
||||||
import { homedir } from 'os';
|
import { homedir } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { nativeTheme } from 'electron';
|
import { nativeTheme } from 'electron';
|
||||||
import { PRODUCT_NAME } from '@/constants';
|
import { PRODUCT_NAME } from '@/constants';
|
||||||
|
import type { FrontendPreference } from '@/types';
|
||||||
|
import type { MantineColorScheme } from '@mantine/core';
|
||||||
|
|
||||||
type ConfigValue = string | number | boolean | unknown[] | undefined;
|
type ConfigValue = string | number | boolean | unknown[] | undefined;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -353,8 +353,7 @@ function parseClInfoOutput(output: string) {
|
||||||
const deviceName = findDeviceNameInClInfo(lines, i);
|
const deviceName = findDeviceNameInClInfo(lines, i);
|
||||||
|
|
||||||
if (deviceName && currentPlatform) {
|
if (deviceName && currentPlatform) {
|
||||||
const deviceLabel = `${formatDeviceName(deviceName)} (${currentPlatform})`;
|
devices.push(formatDeviceName(deviceName));
|
||||||
devices.push(deviceLabel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ async function createUvProcess(args: string[], env?: Record<string, string>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForOpenWebUIToStart() {
|
async function waitForOpenWebUIToStart() {
|
||||||
sendKoboldOutput('Waiting for Open WebUI to start...');
|
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const checkForOutput = (data: Buffer) => {
|
const checkForOutput = (data: Buffer) => {
|
||||||
const output = data.toString();
|
const output = data.toString();
|
||||||
|
|
|
||||||
33
src/stores/frontendPreference.ts
Normal file
33
src/stores/frontendPreference.ts
Normal 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:');
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
@ -182,7 +182,7 @@ foreach ($gpu in $gpus) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
powershell.kill('SIGTERM');
|
powershell.kill('SIGTERM');
|
||||||
resolve([]);
|
resolve([]);
|
||||||
}, 3000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue