From bf9b4259b9c074dda29b0c0afb2c14e82b45bf3f Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Wed, 13 Nov 2024 16:48:40 -0500 Subject: [PATCH] Implement install APIs (#235) * Implement getSystemPaths * nit * Remove SHOW_SELECT_DIRECTORY * Remove FIRST_TIME_SETUP_COMPLETE * Validate install path * Implement migration items * Implement VALIDATE_COMFYUI_SOURCE * Implement SHOW_DIRECTORY_PICKER * Implement handle install * Move python install path * extract handle install * Remove renderer content * Remove OPEN_FORUM * Remove GET_DEFAULT_INSTALL_LOCATION * Re-wire dev server * Use systeminformation to replace check-disk-space --- .env_example | 6 + scripts/launchdev.js | 6 +- src/constants.ts | 36 +++- src/handlers/appInfoHandlers.ts | 5 - src/handlers/pathHandlers.ts | 77 +++++++- src/install/index.ts | 0 src/main-process/appWindow.ts | 16 +- src/main.ts | 89 +++++----- src/preload.ts | 75 ++------ src/renderer/index.tsx | 82 +-------- src/renderer/screens/AnimatedLogDisplay.tsx | 49 ----- src/renderer/screens/FirstTimeSetup.tsx | 187 -------------------- src/renderer/screens/ProgressOverlay.tsx | 92 ---------- tests/unit/handlers/appinfoHandlers.test.ts | 7 +- 14 files changed, 190 insertions(+), 537 deletions(-) create mode 100644 src/install/index.ts delete mode 100644 src/renderer/screens/AnimatedLogDisplay.tsx delete mode 100644 src/renderer/screens/FirstTimeSetup.tsx delete mode 100644 src/renderer/screens/ProgressOverlay.tsx diff --git a/.env_example b/.env_example index 907fead8..50f61380 100644 --- a/.env_example +++ b/.env_example @@ -6,3 +6,9 @@ COMFY_PORT=8188 # Whether to use an external server instead of starting one locally. USE_EXTERNAL_SERVER=false + +# The URL of the development server to use. +# This is for the install/server-startup screen. +# Run `npm run dev` in the frontend repo(https://github.com/Comfy-Org/ComfyUI_frontend) +# to start a development server. +DEV_SERVER_URL=http://192.168.2.20:5173 diff --git a/scripts/launchdev.js b/scripts/launchdev.js index 90336ea5..96d3a525 100644 --- a/scripts/launchdev.js +++ b/scripts/launchdev.js @@ -11,12 +11,8 @@ const logLevel = 'warn' /** * Setup watcher for `main` package * On file changed it totally re-launch electron app. - * @param {import('vite').ViteDevServer} watchServer Renderer watch server instance. - * Needs to set up `VITE_DEV_SERVER_URL` environment variable from {@link import('vite').ViteDevServer.resolvedUrls} */ -function setupMainPackageWatcher({ resolvedUrls }) { - process.env.VITE_DEV_SERVER_URL = resolvedUrls.local[0]; - +function setupMainPackageWatcher() { /** @type {ChildProcess | null} */ let electronApp = null; diff --git a/src/constants.ts b/src/constants.ts index bdc0a7a8..b3618918 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,11 +5,7 @@ export const IPC_CHANNELS = { RESTART_APP: 'restart-app', REINSTALL: 'reinstall', LOG_MESSAGE: 'log-message', - SHOW_SELECT_DIRECTORY: 'show-select-directory', - SELECTED_DIRECTORY: 'selected-directory', OPEN_DIALOG: 'open-dialog', - FIRST_TIME_SETUP_COMPLETE: 'first-time-setup-complete', - DEFAULT_INSTALL_LOCATION: 'default-install-location', DOWNLOAD_PROGRESS: 'download-progress', START_DOWNLOAD: 'start-download', PAUSE_DOWNLOAD: 'pause-download', @@ -24,8 +20,12 @@ export const IPC_CHANNELS = { OPEN_PATH: 'open-path', OPEN_LOGS_PATH: 'open-logs-path', OPEN_DEV_TOOLS: 'open-dev-tools', - OPEN_FORUM: 'open-forum', IS_FIRST_TIME_SETUP: 'is-first-time-setup', + GET_SYSTEM_PATHS: 'get-system-paths', + VALIDATE_INSTALL_PATH: 'validate-install-path', + VALIDATE_COMFYUI_SOURCE: 'validate-comfyui-source', + SHOW_DIRECTORY_PICKER: 'show-directory-picker', + INSTALL_COMFYUI: 'install-comfyui', } as const; export enum ProgressStatus { @@ -72,3 +72,29 @@ export const ELECTRON_BRIDGE_API = 'electronAPI'; export const SENTRY_URL_ENDPOINT = 'https://942cadba58d247c9cab96f45221aa813@o4507954455314432.ingest.us.sentry.io/4508007940685824'; + +export interface MigrationItem { + id: string; + label: string; + description: string; +} + +export const MigrationItems: MigrationItem[] = [ + { + id: 'user_files', + label: 'User Files', + description: 'Settings and user-created workflows', + }, + { + id: 'models', + label: 'Models', + description: 'Reference model files from existing ComfyUI installations. (No copy)', + }, + // TODO: Decide whether we want to auto-migrate custom nodes, and install their dependencies. + // huchenlei: This is a very essential thing for migration experience. + // { + // id: 'custom_nodes', + // label: 'Custom Nodes', + // description: 'Reference custom node files from existing ComfyUI installations. (No copy)', + // }, +] as const; diff --git a/src/handlers/appInfoHandlers.ts b/src/handlers/appInfoHandlers.ts index 7ceb5de4..bfb8d17e 100644 --- a/src/handlers/appInfoHandlers.ts +++ b/src/handlers/appInfoHandlers.ts @@ -14,10 +14,5 @@ export class AppInfoHandlers { ipcMain.handle(IPC_CHANNELS.GET_ELECTRON_VERSION, () => { return app.getVersion(); }); - - ipcMain.handle(IPC_CHANNELS.OPEN_FORUM, () => { - shell.openExternal('https://forum.comfy.org'); - }); - ipcMain.handle(IPC_CHANNELS.DEFAULT_INSTALL_LOCATION, () => app.getPath('documents')); } } diff --git a/src/handlers/pathHandlers.ts b/src/handlers/pathHandlers.ts index 19bd1307..4d374e0c 100644 --- a/src/handlers/pathHandlers.ts +++ b/src/handlers/pathHandlers.ts @@ -1,10 +1,16 @@ -import { app, ipcMain, shell } from 'electron'; +import { app, dialog, ipcMain, shell } from 'electron'; import { IPC_CHANNELS } from '../constants'; import log from 'electron-log/main'; import { getModelConfigPath } from '../config/extra_model_config'; import { getBasePath } from '../install/resourcePaths'; +import type { SystemPaths } from '../preload'; +import fs from 'fs'; +import si from 'systeminformation'; +import { ComfyConfigManager } from '../config/comfyConfigManager'; export class PathHandlers { + static readonly REQUIRED_SPACE = 10 * 1024 * 1024 * 1024; // 10GB in bytes + constructor() {} registerHandlers() { @@ -24,5 +30,74 @@ export class PathHandlers { log.info(`Opening path: ${folderPath}`); shell.openPath(folderPath); }); + + ipcMain.handle(IPC_CHANNELS.GET_SYSTEM_PATHS, (): SystemPaths => { + return { + appData: app.getPath('appData'), + appPath: app.getAppPath(), + defaultInstallPath: app.getPath('documents'), + }; + }); + + /** + * Validate the install path for the application. Check whether the path is valid + * and writable. The disk should have enough free space to install the application. + */ + ipcMain.handle( + IPC_CHANNELS.VALIDATE_INSTALL_PATH, + async (event, path: string): Promise<{ isValid: boolean; error?: string }> => { + try { + // Check if path exists + if (!fs.existsSync(path)) { + return { isValid: false, error: 'Path does not exist' }; + } + + // Check if path is writable + try { + fs.accessSync(path, fs.constants.W_OK); + } catch (err) { + return { isValid: false, error: 'Path is not writable' }; + } + + // Check available disk space (require at least 10GB free) + const disks = await si.fsSize(); + const disk = disks.find((disk) => path.startsWith(disk.mount)); + if (disk && disk.available < PathHandlers.REQUIRED_SPACE) { + return { + isValid: false, + error: 'Insufficient disk space. At least 10GB of free space is required.', + }; + } + + return { isValid: true }; + } catch (error) { + log.error('Error validating install path:', error); + return { + isValid: false, + error: 'Failed to validate install path: ' + error, + }; + } + } + ); + /** + * Validate whether the given path is a valid ComfyUI source path. + */ + ipcMain.handle( + IPC_CHANNELS.VALIDATE_COMFYUI_SOURCE, + async (event, path: string): Promise<{ isValid: boolean; error?: string }> => { + const isValid = ComfyConfigManager.isComfyUIDirectory(path); + return { + isValid, + error: isValid ? undefined : 'Invalid ComfyUI source path', + }; + } + ); + + ipcMain.handle(IPC_CHANNELS.SHOW_DIRECTORY_PICKER, async (): Promise => { + const result = await dialog.showOpenDialog({ + properties: ['openDirectory'], + }); + return result.filePaths[0]; + }); } } diff --git a/src/install/index.ts b/src/install/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/main-process/appWindow.ts b/src/main-process/appWindow.ts index 40494fc7..e17c4b44 100644 --- a/src/main-process/appWindow.ts +++ b/src/main-process/appWindow.ts @@ -4,6 +4,7 @@ import Store from 'electron-store'; import { StoreType } from '../store'; import log from 'electron-log/main'; import { IPC_CHANNELS } from '../constants'; +import { getAppResourcesPath } from '../install/resourcePaths'; /** * Creates a single application window that displays the renderer and encapsulates all the logic for sending messages to the renderer. @@ -44,7 +45,6 @@ export class AppWindow { this.setupWindowEvents(); this.sendQueuedEventsOnReady(); - this.loadRenderer(); } public isReady(): boolean { @@ -105,13 +105,17 @@ export class AppWindow { this.window.focus(); } - private async loadRenderer(): Promise { - if (process.env.VITE_DEV_SERVER_URL) { - log.info('Loading Vite Dev Server'); - await this.window.loadURL(process.env.VITE_DEV_SERVER_URL); + public async loadRenderer(urlPath: string = ''): Promise { + if (process.env.DEV_SERVER_URL) { + const url = `${process.env.DEV_SERVER_URL}/${urlPath}`; + + log.info(`Loading development server ${url}`); + await this.window.loadURL(url); this.window.webContents.openDevTools(); } else { - this.window.loadFile(path.join(__dirname, `../renderer/index.html`)); + const appResourcesPath = await getAppResourcesPath(); + const frontendPath = path.join(appResourcesPath, 'ComfyUI', 'web_custom_versions', 'desktop_app'); + this.window.loadFile(path.join(frontendPath, 'index.html'), { hash: urlPath }); } } diff --git a/src/main.ts b/src/main.ts index 5b42d664..e7518833 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ import { AppWindow } from './main-process/appWindow'; import { getAppResourcesPath, getBasePath, getPythonInstallPath } from './install/resourcePaths'; import { PathHandlers } from './handlers/pathHandlers'; import { AppInfoHandlers } from './handlers/appInfoHandlers'; +import { InstallOptions } from './preload'; dotenv.config(); @@ -164,35 +165,19 @@ if (!gotTheLock) { ipcMain.handle(IPC_CHANNELS.IS_FIRST_TIME_SETUP, () => { return isFirstTimeSetup(); }); - await handleFirstTimeSetup(); - const basePath = await getBasePath(); - const pythonInstallPath = await getPythonInstallPath(); - if (!basePath || !pythonInstallPath) { - log.error('ERROR: Base path not found!'); - sendProgressUpdate(ProgressStatus.ERROR_INSTALL_PATH); - return; - } - downloadManager = DownloadManager.getInstance(appWindow!, getModelsDirectory(basePath)); - - port = - port !== -1 - ? port - : await findAvailablePort(8000, 9999).catch((err) => { - log.error(`ERROR: Failed to find available port: ${err}`); - throw err; - }); - - if (!useExternalServer) { - sendProgressUpdate(ProgressStatus.PYTHON_SETUP); - const appResourcesPath = await getAppResourcesPath(); - const pythonEnvironment = new PythonEnvironment(pythonInstallPath, appResourcesPath, spawnPythonAsync); - await pythonEnvironment.setup(); - const modelConfigPath = getModelConfigPath(); - sendProgressUpdate(ProgressStatus.STARTING_SERVER); - await launchPythonServer(pythonEnvironment.pythonInterpreterPath, appResourcesPath, modelConfigPath, basePath); - } else { - sendProgressUpdate(ProgressStatus.READY); - loadComfyIntoMainWindow(); + ipcMain.on(IPC_CHANNELS.INSTALL_COMFYUI, async (event, installOptions: InstallOptions) => { + // Non-blocking call. The renderer will navigate to /server-start and show install progress. + handleInstall(installOptions); + serverStart(); + }); + + // Loading renderer when all handlers are registered to ensure all event listeners are set up. + const firstTimeSetup = isFirstTimeSetup(); + const urlPath = firstTimeSetup ? 'welcome' : 'server-start'; + await appWindow.loadRenderer(urlPath); + + if (!firstTimeSetup) { + await serverStart(); } } catch (error) { log.error(error); @@ -558,27 +543,41 @@ function isFirstTimeSetup(): boolean { return !fs.existsSync(extraModelsConfigPath); } -async function selectedInstallDirectory(): Promise { - return new Promise((resolve, reject) => { - ipcMain.on(IPC_CHANNELS.SELECTED_DIRECTORY, (_event, value) => { - log.info('User selected to install ComfyUI in:', value); - resolve(value); - }); - }); +async function handleInstall(installOptions: InstallOptions) { + const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(installOptions.installPath); + const modelConfigPath = getModelConfigPath(); + await createModelConfigFiles(modelConfigPath, actualComfyDirectory); } -async function handleFirstTimeSetup() { - const firstTimeSetup = isFirstTimeSetup(); - log.info('First time setup:', firstTimeSetup); - if (firstTimeSetup) { - appWindow.send(IPC_CHANNELS.SHOW_SELECT_DIRECTORY, null); - const selectedDirectory = await selectedInstallDirectory(); - const actualComfyDirectory = ComfyConfigManager.setUpComfyUI(selectedDirectory); +async function serverStart() { + const basePath = await getBasePath(); + const pythonInstallPath = await getPythonInstallPath(); + if (!basePath || !pythonInstallPath) { + log.error('ERROR: Base path not found!'); + sendProgressUpdate(ProgressStatus.ERROR_INSTALL_PATH); + return; + } + downloadManager = DownloadManager.getInstance(appWindow!, getModelsDirectory(basePath)); + + port = + port !== -1 + ? port + : await findAvailablePort(8000, 9999).catch((err) => { + log.error(`ERROR: Failed to find available port: ${err}`); + throw err; + }); + if (!useExternalServer) { + sendProgressUpdate(ProgressStatus.PYTHON_SETUP); + const appResourcesPath = await getAppResourcesPath(); + const pythonEnvironment = new PythonEnvironment(pythonInstallPath, appResourcesPath, spawnPythonAsync); + await pythonEnvironment.setup(); const modelConfigPath = getModelConfigPath(); - await createModelConfigFiles(modelConfigPath, actualComfyDirectory); + sendProgressUpdate(ProgressStatus.STARTING_SERVER); + await launchPythonServer(pythonEnvironment.pythonInterpreterPath, appResourcesPath, modelConfigPath, basePath); } else { - appWindow.send(IPC_CHANNELS.FIRST_TIME_SETUP_COMPLETE, null); + sendProgressUpdate(ProgressStatus.READY); + loadComfyIntoMainWindow(); } } diff --git a/src/preload.ts b/src/preload.ts index d1da4ea4..ea8aa67f 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -12,12 +12,6 @@ const openFolder = async (folderPath: string) => { ipcRenderer.send(IPC_CHANNELS.OPEN_PATH, path.join(basePath, folderPath)); }; -export interface MigrationItem { - id: string; - label: string; - description: string; -} - export interface InstallOptions { installPath: string; autoUpdate: boolean; @@ -26,6 +20,12 @@ export interface InstallOptions { migrationItemIds?: string[]; } +export interface SystemPaths { + appData: string; + appPath: string; + defaultInstallPath: string; +} + const electronAPI = { /** * Callback for progress updates from the main process for starting ComfyUI. @@ -57,25 +57,9 @@ const electronAPI = { reinstall: () => { return ipcRenderer.invoke(IPC_CHANNELS.REINSTALL); }, - onShowSelectDirectory: (callback: () => void) => { - ipcRenderer.on(IPC_CHANNELS.SHOW_SELECT_DIRECTORY, () => callback()); - }, - /** - * Callback for when the user clicks the "Select Directory" button in the setup wizard. - * @param callback - */ - selectSetupDirectory: (directory: string) => { - ipcRenderer.send(IPC_CHANNELS.SELECTED_DIRECTORY, directory); - }, openDialog: (options: Electron.OpenDialogOptions) => { return ipcRenderer.invoke(IPC_CHANNELS.OPEN_DIALOG, options); }, - onFirstTimeSetupComplete: (callback: () => void) => { - ipcRenderer.on(IPC_CHANNELS.FIRST_TIME_SETUP_COMPLETE, () => callback()); - }, - getDefaultInstallLocation: (): Promise => { - return ipcRenderer.invoke(IPC_CHANNELS.DEFAULT_INSTALL_LOCATION); - }, /** * Various paths that are useful to the renderer. * - Base path: The base path of the application. @@ -101,9 +85,6 @@ const electronAPI = { const modelConfigPath = await electronAPI.getModelConfigPath(); ipcRenderer.send(IPC_CHANNELS.OPEN_PATH, modelConfigPath); }, - openForum: () => { - ipcRenderer.invoke(IPC_CHANNELS.OPEN_FORUM); - }, /** * Open the developer tools window. */ @@ -164,55 +145,37 @@ const electronAPI = { isFirstTimeSetup: (): Promise => { return ipcRenderer.invoke(IPC_CHANNELS.IS_FIRST_TIME_SETUP); }, - // TODO(robinjhuang): Implement these methods. - // Currently, they are mocked. /** * Get the system paths for the application. */ - getSystemPaths: () => - Promise.resolve({ - appData: 'C:/Users/username/AppData/Roaming', - appPath: 'C:/Program Files/comfyui-electron/resources/app', - defaultInstallPath: 'C:/Users/username/comfyui-electron', - }), + getSystemPaths: (): Promise => { + return ipcRenderer.invoke(IPC_CHANNELS.GET_SYSTEM_PATHS); + }, /** * Validate the install path for the application. Check whether the path is valid * and writable. The disk should have enough free space to install the application. */ - validateInstallPath: (path: string) => { - if (path === 'bad') { - return { isValid: false, error: 'Bad path!' }; - } - return { isValid: true }; + validateInstallPath: (path: string): Promise<{ isValid: boolean; error?: string }> => { + return ipcRenderer.invoke(IPC_CHANNELS.VALIDATE_INSTALL_PATH, path); }, - /** - * Get the migration items for the application. - */ - migrationItems: (): Promise => - Promise.resolve([ - { - id: 'user_files', - label: 'User Files', - description: 'Settings and user-created workflows', - }, - ]), /** * Validate whether the given path is a valid ComfyUI source path. */ - validateComfyUISource: (path: string) => { - if (path === 'bad') { - return { isValid: false, error: 'Bad path!' }; - } - return { isValid: true }; + validateComfyUISource: (path: string): Promise<{ isValid: boolean; error?: string }> => { + return ipcRenderer.invoke(IPC_CHANNELS.VALIDATE_COMFYUI_SOURCE, path); }, /** * Show a directory picker dialog and return the selected path. */ - showDirectoryPicker: () => Promise.resolve('C:/Users/username/comfyui-electron/1'), + showDirectoryPicker: (): Promise => { + return ipcRenderer.invoke(IPC_CHANNELS.SHOW_DIRECTORY_PICKER); + }, /** * Install ComfyUI with given options. */ - installComfyUI: (installOptions: InstallOptions) => Promise.resolve(), + installComfyUI: (installOptions: InstallOptions) => { + ipcRenderer.send(IPC_CHANNELS.INSTALL_COMFYUI, installOptions); + }, } as const; export type ElectronAPI = typeof electronAPI; diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index d91acc7c..3a5951dc 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -1,85 +1,7 @@ -import React, { useEffect, useState, useCallback, useRef } from 'react'; -import ProgressOverlay from './screens/ProgressOverlay'; -import log from 'electron-log/renderer'; -import FirstTimeSetup from './screens/FirstTimeSetup'; -import { ElectronAPI } from 'src/preload'; -import { ELECTRON_BRIDGE_API, ProgressStatus } from 'src/constants'; +import React from 'react'; -const bodyStyle: React.CSSProperties = { - fontFamily: 'Arial, sans-serif', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - height: '100vh', - margin: '0', - color: '#d4d4d4', - backgroundColor: '#1e1e1e', -}; - -// Main entry point for the front end renderer. -// Currently this serves as the overlay to show progress as the comfy backend is coming online. -// after coming online the main.ts will replace the renderer with comfy's internal index.html const Home: React.FC = () => { - const [showSetup, setShowSetup] = useState(null); - const [status, setStatus] = useState(ProgressStatus.INITIAL_STATE); - const [logs, setLogs] = useState([]); - const [defaultInstallLocation, setDefaultInstallLocation] = useState(''); - const electronAPI: ElectronAPI = (window as any)[ELECTRON_BRIDGE_API]; - - const updateProgress = useCallback(({ status: newStatus }: { status: ProgressStatus }) => { - setStatus(newStatus); - setLogs([]); // Clear logs when status changes - }, []); - - const addLogMessage = useCallback((message: string) => { - setLogs((prevLogs) => [...prevLogs, message]); - }, []); - - useEffect(() => { - const electronAPI: ElectronAPI = (window as any)[ELECTRON_BRIDGE_API]; - - log.info(`Sending ready event from renderer`); - electronAPI.sendReady(); - - electronAPI.onShowSelectDirectory(() => { - log.info('Showing select directory'); - setShowSetup(true); - }); - - electronAPI.onFirstTimeSetupComplete(() => { - log.info('First time setup complete'); - setShowSetup(false); - }); - }, []); - - useEffect(() => { - electronAPI.onProgressUpdate(updateProgress); - electronAPI.onLogMessage((message: string) => { - log.info(`Received log message: ${message}`); - addLogMessage(message); - }); - }, [updateProgress, addLogMessage]); - - useEffect(() => { - electronAPI.getDefaultInstallLocation().then((location: string) => { - setDefaultInstallLocation(location); - }); - }, []); - - if (showSetup) { - return ( -
- setShowSetup(false)} initialPath={defaultInstallLocation} /> -
- ); - } - - return ( -
- electronAPI.openForum()} /> -
- ); + return
Home
; }; export default Home; diff --git a/src/renderer/screens/AnimatedLogDisplay.tsx b/src/renderer/screens/AnimatedLogDisplay.tsx deleted file mode 100644 index 7ac6b09b..00000000 --- a/src/renderer/screens/AnimatedLogDisplay.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useEffect, useRef } from 'react'; - -interface AnimatedLogDisplayProps { - logs: string[]; -} - -const AnimatedLogDisplay: React.FC = ({ logs }) => { - const logContainerRef = useRef(null); - const shouldScrollRef = useRef(true); - - useEffect(() => { - const scrollContainer = logContainerRef.current; - if (scrollContainer) { - if (shouldScrollRef.current) { - scrollContainer.scrollTop = scrollContainer.scrollHeight; - } - } - }, [logs]); - - const handleScroll = () => { - const scrollContainer = logContainerRef.current; - if (scrollContainer) { - const { scrollTop, scrollHeight, clientHeight } = scrollContainer; - const isScrolledToBottom = scrollHeight - clientHeight <= scrollTop + 1; - shouldScrollRef.current = isScrolledToBottom; - } - }; - - const containerStyle: React.CSSProperties = { - height: '200px', - padding: '10px', - fontFamily: 'monospace', - fontSize: '14px', - overflowY: 'auto', - scrollbarWidth: 'thin', - scrollbarColor: '#888 #f1f1f1', - }; - - return ( -
- {logs.length === 0 &&
Streaming logs...
} - {logs.map((logMessage, index) => ( -
{logMessage}
- ))} -
- ); -}; - -export default AnimatedLogDisplay; diff --git a/src/renderer/screens/FirstTimeSetup.tsx b/src/renderer/screens/FirstTimeSetup.tsx deleted file mode 100644 index f5c8d56c..00000000 --- a/src/renderer/screens/FirstTimeSetup.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import React, { useState } from 'react'; -import { ElectronAPI } from '/src/preload'; -import log from 'electron-log/renderer'; - -interface FirstTimeSetupProps { - onComplete: (selectedDirectory: string) => void; - initialPath: string; -} - -const FirstTimeSetup: React.FC = ({ onComplete, initialPath }) => { - const [selectedPath, setSelectedPath] = useState(initialPath); - const electronAPI: ElectronAPI = (window as any).electronAPI; - - const handleDirectoryChange = async () => { - const options: Electron.OpenDialogOptions = { - title: 'Select a directory', - properties: ['openDirectory', 'createDirectory'], - defaultPath: selectedPath, - }; - const directory = await electronAPI.openDialog(options); - if (directory && directory.length > 0) { - log.info('Selected directory', directory[0]); - setSelectedPath(directory[0]); - } else { - log.error('No directory selected'); - } - }; - - const handleInstall = () => { - if (selectedPath) { - log.info('Installing to directory', selectedPath); - electronAPI.selectSetupDirectory(selectedPath); - onComplete(selectedPath); - } else { - log.error('No directory selected for installation'); - alert('Please select a directory before installing.'); - } - }; - - return ( -
-

Install ComfyUI

- <> -

- Select a directory for where ComfyUI will store models, outputs, and custom nodes. If you already have a - ComfyUI setup, you can select that to reuse your existing model files eg. 'C:/Users/comfyanonymous/ComfyUI'. - Custom nodes will need to be re-installed. -

-

Otherwise, we will create a ComfyUI directory for you.

- -
-
- - - -

{selectedPath}

-
- -
- -
- -
-
- ); -}; - -const styles = { - container: { - display: 'flex', - flexDirection: 'column' as const, - alignItems: 'center', - padding: '20px', - maxWidth: '600px', - margin: '0 auto', - justifyContent: 'center', - }, - title: { - fontSize: '24px', - marginBottom: '20px', - color: '#ffffff', - }, - description: { - textAlign: 'center' as const, - marginBottom: '20px', - lineHeight: '1.5', - }, - button: { - padding: '10px 20px', - fontSize: '14px', - cursor: 'pointer', - marginBottom: '10px', - borderRadius: '3px', - border: 'none', - fontWeight: 'bold', - }, - selectButton: { - padding: '10px 20px', - fontSize: '14px', - cursor: 'pointer', - marginBottom: '10px', - borderRadius: '3px', - border: 'none', - fontWeight: 'bold', - backgroundColor: '#60a5fa', - color: '#ffffff', - }, - directoryContainer: { - display: 'flex', - flexDirection: 'row' as const, - alignItems: 'center', - justifyContent: 'center', - gap: '10px', - marginBottom: '20px', - marginTop: '10px', - }, - pathDisplay: { - padding: '10px', - backgroundColor: '#2d2d2d', - borderRadius: '3px', - width: '100%', - color: '#d4d4d4', - textAlign: 'center' as const, - display: 'flex', - alignItems: 'center' as const, - }, - folderIcon: { - width: '24px', - height: '24px', - marginRight: '10px', - flexShrink: 0, - }, - pathText: { - margin: 0, - whiteSpace: 'nowrap' as const, - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - installButton: { - padding: '10px 20px', - fontSize: '14px', - cursor: 'pointer', - borderRadius: '3px', - border: 'none', - fontWeight: 'bold', - backgroundColor: '#4CAF50', - color: '#ffffff', - }, - disabledButton: { - backgroundColor: '#4d4d4d', - color: '#a0a0a0', - cursor: 'not-allowed', - }, - buttonContainer: { - display: 'flex', - justifyContent: 'center', - gap: '10px', - }, - changePathButton: { - backgroundColor: '#4d4d4d', - color: '#ffffff', - cursor: 'pointer', - padding: '10px 20px', - fontSize: '14px', - borderRadius: '3px', - border: 'none', - fontWeight: 'bold', - }, -}; - -export default FirstTimeSetup; diff --git a/src/renderer/screens/ProgressOverlay.tsx b/src/renderer/screens/ProgressOverlay.tsx deleted file mode 100644 index 12bf2cc4..00000000 --- a/src/renderer/screens/ProgressOverlay.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import { ProgressMessages, ProgressStatus } from '/src/constants'; -import AnimatedLogDisplay from './AnimatedLogDisplay'; -import Linkify from 'linkify-react'; - -const loadingTextStyle: React.CSSProperties = { - marginBottom: '20px', - textAlign: 'center', - fontSize: '20px', - fontFamily: 'sans-serif, monospace', - fontWeight: 'bold', -}; - -const outerContainerStyle: React.CSSProperties = { - width: '100%', - height: '100vh', - overflow: 'hidden', -}; - -const containerStyle: React.CSSProperties = { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', // Center vertically - width: '100%', - height: '100%', - overflow: 'scroll', - padding: '20px', -}; - -const logContainerStyle: React.CSSProperties = { - width: '50%', - height: '120px', - overflowY: 'hidden', - marginTop: '20px', - padding: '10px', - backgroundColor: '#1e1e1e', - borderRadius: '5px', - fontFamily: "'Roboto Mono', monospace", - fontSize: '14px', - lineHeight: '1.5', - color: '#9198a1', - boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', -}; - -interface ProgressOverlayProps { - status: ProgressStatus; - logs: string[]; - openForum: () => void; -} - -const linkStyle: React.CSSProperties = { - color: '#3391ff', // Bright blue that works well on dark background - textDecoration: 'underline', - cursor: 'pointer', -}; - -const ProgressOverlay: React.FC = ({ status, logs, openForum }) => { - const linkOptions = { - render: ({ attributes, content }: { attributes: any; content: string }) => { - const { href, ...props } = attributes; - return ( - { - e.preventDefault(); - openForum(); - }} - > - {content} - - ); - }, - }; - - return ( -
-
-
- {ProgressMessages[status]} -
-
- {status !== ProgressStatus.READY && status !== ProgressStatus.ERROR && } -
-
-
- ); -}; - -export default ProgressOverlay; diff --git a/tests/unit/handlers/appinfoHandlers.test.ts b/tests/unit/handlers/appinfoHandlers.test.ts index e162e56a..508c4701 100644 --- a/tests/unit/handlers/appinfoHandlers.test.ts +++ b/tests/unit/handlers/appinfoHandlers.test.ts @@ -16,12 +16,7 @@ describe('AppInfoHandlers', () => { }); it('should register all expected handle channels', () => { - const expectedChannels = [ - IPC_CHANNELS.IS_PACKAGED, - IPC_CHANNELS.GET_ELECTRON_VERSION, - IPC_CHANNELS.OPEN_FORUM, - IPC_CHANNELS.DEFAULT_INSTALL_LOCATION, - ]; + const expectedChannels = [IPC_CHANNELS.IS_PACKAGED, IPC_CHANNELS.GET_ELECTRON_VERSION]; expectedChannels.forEach((channel) => { expect(ipcMain.handle).toHaveBeenCalledWith(channel, expect.any(Function));