diff --git a/.github/workflows/aur-release.yml b/.github/workflows/aur-release.yml index f6acb83..f93f752 100644 --- a/.github/workflows/aur-release.yml +++ b/.github/workflows/aur-release.yml @@ -161,7 +161,7 @@ jobs: license=('AGPL-3.0-or-later') depends=('gtk3' 'nss') optdepends=('nodejs: Required for SillyTavern integration' - 'uv: Required for OpenWebUI and ComfyUI integrations') + 'uv: Required for OpenWebUI integration') provides=('gerbil') conflicts=('gerbil-git') source=("gerbil-${pkgver}.AppImage::${{ steps.release_info.outputs.appimage_url }}" @@ -298,7 +298,7 @@ jobs: depends = gtk3 depends = nss optdepends = nodejs: Required for SillyTavern integration - optdepends = uv: Required for OpenWebUI and ComfyUI integrations + optdepends = uv: Required for OpenWebUI integration provides = gerbil conflicts = gerbil-git source = gerbil-${{ steps.release_info.outputs.version }}.AppImage::${{ steps.release_info.outputs.appimage_url }} diff --git a/package.json b/package.json index 2de6bb7..b0b139d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gerbil", "productName": "Gerbil", - "version": "1.8.5", + "version": "1.9.0", "description": "Run Large Language Models locally", "main": "out/main/index.js", "homepage": "./", diff --git a/src/components/screens/Interface/ServerTab.tsx b/src/components/screens/Interface/ServerTab.tsx index aee4a04..0f699dc 100644 --- a/src/components/screens/Interface/ServerTab.tsx +++ b/src/components/screens/Interface/ServerTab.tsx @@ -6,16 +6,11 @@ import { getServerInterfaceInfo } from '@/utils/interface'; import { TITLEBAR_HEIGHT, STATUSBAR_HEIGHT } from '@/constants'; interface ServerTabProps { - serverUrl?: string; isServerReady?: boolean; activeTab?: string; } -export const ServerTab = ({ - serverUrl, - isServerReady, - activeTab, -}: ServerTabProps) => { +export const ServerTab = ({ isServerReady, activeTab }: ServerTabProps) => { const { isImageGenerationMode } = useLaunchConfigStore(); const { frontendPreference, imageGenerationFrontendPreference } = usePreferencesStore(); @@ -33,17 +28,11 @@ export const ServerTab = ({ frontendPreference, imageGenerationFrontendPreference, isImageGenerationMode: effectiveImageMode, - serverUrl: serverUrl || '', }), - [ - frontendPreference, - imageGenerationFrontendPreference, - effectiveImageMode, - serverUrl, - ] + [frontendPreference, imageGenerationFrontendPreference, effectiveImageMode] ); - if (!isServerReady || !serverUrl) { + if (!isServerReady) { return ( The{' '} - {title.toLowerCase().includes('ui') || - title.toLowerCase().includes('comfy') - ? 'image generation' - : 'chat'}{' '} + {title.toLowerCase().includes('ui') ? 'image generation' : 'chat'}{' '} interface will load automatically when ready diff --git a/src/components/screens/Interface/TerminalTab.tsx b/src/components/screens/Interface/TerminalTab.tsx index cdca48d..5191abb 100644 --- a/src/components/screens/Interface/TerminalTab.tsx +++ b/src/components/screens/Interface/TerminalTab.tsx @@ -95,8 +95,6 @@ export const TerminalTab = forwardRef( signalToCheck = SERVER_READY_SIGNALS.SILLYTAVERN; } else if (effectiveFrontend === 'openwebui') { signalToCheck = SERVER_READY_SIGNALS.OPENWEBUI; - } else if (effectiveFrontend === 'comfyui') { - signalToCheck = SERVER_READY_SIGNALS.COMFYUI; } if (newData.includes(signalToCheck)) { diff --git a/src/components/screens/Interface/index.tsx b/src/components/screens/Interface/index.tsx index 80ea72b..792948c 100644 --- a/src/components/screens/Interface/index.tsx +++ b/src/components/screens/Interface/index.tsx @@ -18,7 +18,6 @@ export const InterfaceScreen = ({ activeTab, onTabChange, }: InterfaceScreenProps) => { - const [serverUrl, setServerUrl] = useState(''); const [isServerReady, setIsServerReady] = useState(false); const terminalTabRef = useRef(null); @@ -42,16 +41,13 @@ export const InterfaceScreen = ({ ] ); - const handleServerReady = useCallback( - (url: string) => { - setServerUrl(url); - setIsServerReady(true); - if (onTabChange) { - onTabChange(defaultInterfaceTab); - } - }, - [onTabChange, defaultInterfaceTab] - ); + const handleServerReady = useCallback(() => { + setIsServerReady(true); + + if (onTabChange) { + onTabChange(defaultInterfaceTab); + } + }, [onTabChange, defaultInterfaceTab]); useEffect(() => { if (activeTab === 'terminal' && terminalTabRef.current) { @@ -78,7 +74,6 @@ export const InterfaceScreen = ({ }} > diff --git a/src/components/settings/FrontendInterfaceSelector.tsx b/src/components/settings/FrontendInterfaceSelector.tsx index 7300024..19b9ba6 100644 --- a/src/components/settings/FrontendInterfaceSelector.tsx +++ b/src/components/settings/FrontendInterfaceSelector.tsx @@ -18,7 +18,6 @@ interface FrontendRequirement { interface FrontendConfig { value: string; label: string; - badges: string[]; requirements?: FrontendRequirement[]; requirementCheck?: () => Promise; } @@ -46,12 +45,10 @@ export const FrontendInterfaceSelector = ({ { value: 'koboldcpp', label: 'Built-in', - badges: ['Text', 'Image'], }, { value: 'sillytavern', label: FRONTENDS.SILLYTAVERN, - badges: ['Text', 'Image'], requirements: [ { id: 'nodejs', @@ -65,20 +62,6 @@ export const FrontendInterfaceSelector = ({ { value: 'openwebui', label: FRONTENDS.OPENWEBUI, - badges: ['Text', 'Image'], - requirements: [ - { - id: 'uv', - name: 'uv', - url: 'https://docs.astral.sh/uv/getting-started/installation/', - }, - ], - requirementCheck: () => window.electronAPI.dependencies.isUvAvailable(), - }, - { - value: 'comfyui', - label: FRONTENDS.COMFYUI, - badges: ['Image'], requirements: [ { id: 'uv', diff --git a/src/constants/index.ts b/src/constants/index.ts index d6c85fe..4ab9465 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -10,7 +10,6 @@ export const SERVER_READY_SIGNALS = { KOBOLDCPP: 'Please connect to custom endpoint at', SILLYTAVERN: 'SillyTavern is listening on', OPENWEBUI: 'Waiting for application startup.', - COMFYUI: 'Starting server', } as const; export const DEFAULT_CONTEXT_SIZE = 4096; @@ -24,24 +23,17 @@ export const SILLYTAVERN = { PORT: 3000, PROXY_PORT: 3001, get URL() { - return `http://localhost:${this.PORT}`; + return `http://127.0.0.1:${this.PORT}`; }, get PROXY_URL() { - return `http://localhost:${this.PROXY_PORT}`; + return `http://127.0.0.1:${this.PROXY_PORT}`; }, } as const; export const OPENWEBUI = { PORT: 8080, get URL() { - return `http://localhost:${this.PORT}`; - }, -} as const; - -export const COMFYUI = { - PORT: 8188, - get URL() { - return `http://localhost:${this.PORT}`; + return `http://127.0.0.1:${this.PORT}`; }, } as const; @@ -51,7 +43,6 @@ export const GITHUB_API = { KOBOLDCPP_REPO: 'LostRuins/koboldcpp', KOBOLDCPP_ROCM_REPO: 'YellowRoseCx/koboldcpp-rocm', GERBIL_REPO: 'lone-cloud/gerbil', - COMFYUI_REPO: 'comfyanonymous/ComfyUI', get LATEST_RELEASE_URL() { return `${this.BASE_URL}/repos/${this.KOBOLDCPP_REPO}/releases/latest` as const; }, @@ -61,12 +52,6 @@ export const GITHUB_API = { get ROCM_LATEST_RELEASE_URL() { return `${this.BASE_URL}/repos/${this.KOBOLDCPP_ROCM_REPO}/releases/latest` as const; }, - get COMFYUI_DOWNLOAD_URL() { - return `${this.GITHUB_BASE_URL}/${this.COMFYUI_REPO}/archive/refs/heads/master.zip` as const; - }, - get COMFYUI_LATEST_COMMIT_URL() { - return `${this.BASE_URL}/repos/${this.COMFYUI_REPO}/commits/master` as const; - }, get GERBIL_GITHUB_URL() { return `${this.GITHUB_BASE_URL}/${this.GERBIL_REPO}` as const; }, @@ -94,7 +79,6 @@ export const FRONTENDS = { STABLE_UI: 'Stable UI', SILLYTAVERN: 'SillyTavern', OPENWEBUI: 'Open WebUI', - COMFYUI: 'ComfyUI', } as const; export const ZOOM = { diff --git a/src/constants/patches.ts b/src/constants/patches.ts deleted file mode 100644 index 1927cdf..0000000 --- a/src/constants/patches.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const KLITE_CSS_OVERRIDE = ` -`; diff --git a/src/constants/proxy.ts b/src/constants/proxy.ts new file mode 100644 index 0000000..18bfffd --- /dev/null +++ b/src/constants/proxy.ts @@ -0,0 +1,7 @@ +export const PROXY = { + HOST: '127.0.0.1', + PORT: 5002, + get URL() { + return `http://${this.HOST}:${this.PORT}`; + }, +} as const; diff --git a/src/main/gui.ts b/src/main/gui.ts index 9257de7..b274a67 100644 --- a/src/main/gui.ts +++ b/src/main/gui.ts @@ -16,7 +16,6 @@ import { safeExecute } from '@/utils/node/logging'; import { stopKoboldCpp } from '@/main/modules/koboldcpp/launcher'; import { stopFrontend as stopSillyTavern } from '@/main/modules/sillytavern'; import { stopFrontend as stopOpenWebUI } from '@/main/modules/openwebui'; -import { stopFrontend as stopComfyUI } from '@/main/modules/comfyui'; import { setupIPCHandlers } from '@/main/ipc'; import { ensureDir } from '@/utils/node/fs'; import { PRODUCT_NAME } from '@/constants'; @@ -75,7 +74,6 @@ export async function initializeApp(options?: { startMinimized?: boolean }) { stopKoboldCpp(), stopSillyTavern(), stopOpenWebUI(), - stopComfyUI(), ]; const timeoutPromise = new Promise((resolve) => { diff --git a/src/main/ipc.ts b/src/main/ipc.ts index aa054db..107e7c7 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -35,7 +35,6 @@ import { getConfigDir, openPathHandler, openUrl } from '@/utils/node/path'; import { logError } from '@/utils/node/logging'; import { stopFrontend as stopSillyTavernFrontend } from '@/main/modules/sillytavern'; import { stopFrontend as stopOpenWebUIFrontend } from '@/main/modules/openwebui'; -import { stopFrontend as stopComfyUIFrontend } from '@/main/modules/comfyui'; import { isUvAvailable, isNpxAvailable, @@ -147,7 +146,6 @@ export function setupIPCHandlers() { stopKoboldCpp(); stopSillyTavernFrontend(); stopOpenWebUIFrontend(); - stopComfyUIFrontend(); }); ipcMain.handle('kobold:parseConfigFile', (_, filePath) => diff --git a/src/main/modules/comfyui.ts b/src/main/modules/comfyui.ts deleted file mode 100644 index a368ae1..0000000 --- a/src/main/modules/comfyui.ts +++ /dev/null @@ -1,599 +0,0 @@ -import { spawn } from 'child_process'; -import { dirname, join } from 'path'; -import { - access, - stat, - mkdir, - unlink, - copyFile, - readFile, - writeFile, - rm, -} from 'fs/promises'; -import { platform, on } from 'process'; -import type { ChildProcess } from 'child_process'; -import yauzl from 'yauzl'; -import { createWriteStream } from 'fs'; -import { app } from 'electron'; - -import { logError, safeExecute, tryExecute } from '@/utils/node/logging'; -import { sendKoboldOutput } from './window'; -import { getInstallDir } from './config'; -import { COMFYUI, SERVER_READY_SIGNALS, GITHUB_API } from '@/constants'; -import { terminateProcess } from '@/utils/node/process'; -import { parseKoboldConfig } from '@/utils/node/kobold'; -import { ensureDir } from '@/utils/node/fs'; -import { detectGPU } from './hardware'; -import { getUvEnvironment } from './dependencies'; - -interface ComfyUIVersionInfo { - sha: string; - date: string; -} - -async function getLatestComfyUIVersion() { - return safeExecute(async () => { - 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, - }; - }, 'Failed to fetch latest ComfyUI version'); -} - -async function getCurrentComfyUIVersion(workspaceDir: string) { - try { - const versionFile = join(workspaceDir, '.version.json'); - const content = await readFile(versionFile, 'utf-8'); - return JSON.parse(content); - } catch { - return null; - } -} - -async function saveComfyUIVersion( - workspaceDir: string, - version: ComfyUIVersionInfo -) { - await tryExecute(async () => { - const versionFile = join(workspaceDir, '.version.json'); - await writeFile(versionFile, JSON.stringify(version, null, 2)); - }, 'Failed to save ComfyUI version info'); -} - -async function shouldUpdateComfyUI(workspaceDir: string) { - 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) { - const isWindows = platform === 'win32'; - const pythonExecutable = isWindows ? 'python.exe' : 'python'; - const scriptsDir = isWindows ? 'Scripts' : 'bin'; - return join(workspaceDir, '.venv', scriptsDir, pythonExecutable); -} - -on('SIGINT', () => { - void stopFrontend(); -}); - -on('SIGTERM', () => { - void stopFrontend(); -}); - -async function shouldForceCPUMode() { - try { - const gpuInfo = await detectGPU(); - const { hasAMD, hasNVIDIA } = gpuInfo; - const isWindows = platform === 'win32'; - - return (hasAMD && isWindows) || (!hasAMD && !hasNVIDIA); - } catch { - return true; - } -} - -async function getPyTorchInstallArgs(pythonPath: string) { - const args = [ - 'pip', - 'install', - '--python', - pythonPath, - 'torch', - 'torchvision', - 'torchaudio', - ]; - - try { - const gpuInfo = await detectGPU(); - const { hasAMD, hasNVIDIA } = gpuInfo; - const isWindows = platform === 'win32'; - - if (hasAMD && !isWindows) { - sendKoboldOutput( - 'AMD GPU detected, installing PyTorch with ROCm support...' - ); - args.push('--index-url', 'https://download.pytorch.org/whl/rocm6.4'); - } else if (hasNVIDIA) { - sendKoboldOutput( - 'NVIDIA GPU detected, installing PyTorch with CUDA support...' - ); - args.push('--extra-index-url', 'https://download.pytorch.org/whl/cu121'); - } else { - 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 { - sendKoboldOutput('Could not detect GPU, installing default PyTorch...'); - } - - return args; -} - -async function downloadAndExtractComfyUI(workspaceDir: string) { - 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 zipPath = 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 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 = join(workspaceDir, entryPath.replace(/^[^/]+\//, '')); - - if (/\/$/.test(entryPath)) { - mkdir(destPath, { recursive: true }) - .then(() => zipfile!.readEntry()) - .catch(reject); - } else { - zipfile!.openReadStream(entry, (err, readStream) => { - if (err) { - reject(err); - return; - } - - mkdir(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', () => { - 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 -) { - sendKoboldOutput('Installing ComfyUI dependencies...'); - - const requirementsPath = join(workspaceDir, 'requirements.txt'); - const requirementsExists = await 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) { - 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) { - 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 copyFile(srcPath, destPath); - } catch (error) { - void error; - } - }); - - await Promise.all(backupPromises); - - try { - await downloadAndExtractComfyUI(workspaceDir); - sendKoboldOutput('ComfyUI updated successfully'); - - await 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 copyFile(srcPath, destPath); - } catch (error) { - void error; - } - }); - - await Promise.all(restorePromises); - await rm(backupDir, { recursive: true, force: true }); - - throw error; - } -} - -async function ensureComfyUIInstalled() { - const installDir = getInstallDir(); - const comfyUIWorkspace = join(installDir, 'comfyui-workspace'); - - await ensureDir(comfyUIWorkspace); - - const comfyUIMainPath = join(comfyUIWorkspace, 'main.py'); - const isComfyUIInstalled = await access(comfyUIMainPath) - .then(() => true) - .catch(() => false); - - const needsUpdate = - isComfyUIInstalled && (await shouldUpdateComfyUI(comfyUIWorkspace)); - - if (!isComfyUIInstalled) { - 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 and up to date'); - } -} - -async function waitForComfyUIToStart() { - return new Promise((resolve, reject) => { - const checkForOutput = (data: Buffer) => { - const output = data.toString(); - if (output.includes(SERVER_READY_SIGNALS.COMFYUI)) { - sendKoboldOutput('ComfyUI is now running!'); - resolve(); - - if (comfyUIProcess?.stdout) { - comfyUIProcess.stdout.removeListener('data', checkForOutput); - } - } - }; - - if (comfyUIProcess?.stdout) { - comfyUIProcess.stdout.on('data', checkForOutput); - } else { - reject(new Error('ComfyUI process stdout not available')); - } - }); -} - -export async function startFrontend(args: string[]) { - try { - const config = { - name: 'comfyui', - port: COMFYUI.PORT, - }; - const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args); - - sendKoboldOutput(`Starting ComfyUI frontend on port ${config.port}...`); - - await ensureComfyUIInstalled(); - - const installDir = getInstallDir(); - const comfyUIWorkspace = join(installDir, 'comfyui-workspace'); - const pythonPath = getPythonPath(comfyUIWorkspace); - - const comfyUIArgs = [ - join(comfyUIWorkspace, 'main.py'), - '--port', - config.port.toString(), - '--enable-cors-header', - ]; - - const forceCPU = await shouldForceCPUMode(); - if (forceCPU) { - comfyUIArgs.push('--cpu'); - sendKoboldOutput( - 'Forcing ComfyUI to use CPU mode (no compatible GPU acceleration available)' - ); - } - - if (koboldHost && koboldPort) { - sendKoboldOutput( - `Connecting to KoboldCpp at ${koboldHost}:${koboldPort}` - ); - } - - sendKoboldOutput(`Running ComfyUI with Python: ${pythonPath}`); - sendKoboldOutput(`ComfyUI args: ${comfyUIArgs.join(' ')}`); - - comfyUIProcess = spawn(pythonPath, comfyUIArgs, { - cwd: comfyUIWorkspace, - stdio: 'pipe', - }); - - const version = await app.getVersion(); - sendKoboldOutput(`ComfyUI started via Gerbil v${version}`); - - if (comfyUIProcess.stdout) { - comfyUIProcess.stdout.on('data', (data: Buffer) => { - sendKoboldOutput(data.toString('utf8'), true); - }); - } - - if (comfyUIProcess.stderr) { - comfyUIProcess.stderr.on('data', (data: Buffer) => { - sendKoboldOutput(data.toString('utf8'), true); - }); - } - - comfyUIProcess.on('exit', (code: number | null, signal: string | null) => { - 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: Error) => { - logError('ComfyUI process error', error); - comfyUIProcess = null; - }); - - await waitForComfyUIToStart(); - } catch (error) { - logError('Failed to start ComfyUI', error as Error); - throw error; - } -} - -export const stopFrontend = () => terminateProcess(comfyUIProcess); diff --git a/src/main/modules/koboldcpp/launcher.ts b/src/main/modules/koboldcpp/launcher/index.ts similarity index 61% rename from src/main/modules/koboldcpp/launcher.ts rename to src/main/modules/koboldcpp/launcher/index.ts index 6c3fb53..8c87631 100644 --- a/src/main/modules/koboldcpp/launcher.ts +++ b/src/main/modules/koboldcpp/launcher/index.ts @@ -1,20 +1,20 @@ import { spawn, ChildProcess } from 'child_process'; -import { readFile, writeFile, copyFile } from 'fs/promises'; -import { join } from 'path'; import { terminateProcess } from '@/utils/node/process'; -import { logError, tryExecute, safeExecute } from '@/utils/node/logging'; -import { sendKoboldOutput } from '../window'; +import { logError, safeExecute } from '@/utils/node/logging'; +import { sendKoboldOutput } from '@/main/modules/window'; import { SERVER_READY_SIGNALS } from '@/constants'; -import { KLITE_CSS_OVERRIDE } from '@/constants/patches'; import { pathExists } from '@/utils/node/fs'; import { parseKoboldConfig } from '@/utils/node/kobold'; -import { getAssetPath } from '@/utils/node/path'; -import { getCurrentVersion } from './version'; -import { getCurrentKoboldBinary, get as getConfig } from '../config'; +import { getCurrentVersion } from '../version'; +import { + getCurrentKoboldBinary, + get as getConfig, +} from '@/main/modules/config'; import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillytavern'; import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui'; -import { startFrontend as startComfyUIFrontend } from '@/main/modules/comfyui'; +import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches'; +import { startProxy, stopProxy } from '../proxy'; import type { FrontendPreference, ImageGenerationFrontendPreference, @@ -22,70 +22,6 @@ import type { let koboldProcess: ChildProcess | null = null; -const patchKliteEmbd = (unpackedDir: string) => - tryExecute(async () => { - const possiblePaths = [ - join(unpackedDir, '_internal', 'embd_res', 'klite.embd'), - join(unpackedDir, 'klite.embd'), - ]; - - let kliteEmbdPath: string | null = null; - for (const path of possiblePaths) { - if (await pathExists(path)) { - kliteEmbdPath = path; - break; - } - } - - if (!kliteEmbdPath) { - return; - } - - const content = await readFile(kliteEmbdPath, 'utf8'); - - if (content.includes('')) { - let patchedContent = content; - - if (content.includes('gerbil-css-override')) { - patchedContent = patchedContent.replace( - /`; + +export const patchKliteEmbd = (unpackedDir: string) => + tryExecute(async () => { + const possiblePaths = [ + join(unpackedDir, '_internal', 'embd_res', 'klite.embd'), + join(unpackedDir, 'klite.embd'), + ]; + + let kliteEmbdPath: string | null = null; + for (const path of possiblePaths) { + if (await pathExists(path)) { + kliteEmbdPath = path; + break; + } + } + + if (!kliteEmbdPath) { + return; + } + + const content = await readFile(kliteEmbdPath, 'utf8'); + + if (content.includes('')) { + let patchedContent = content; + + if (content.includes('gerbil-css-override')) { + patchedContent = patchedContent.replace( + /