diff --git a/README.md b/README.md index 9e5b72dd..5f742878 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,28 @@ You can also build the package and/or distributables using the `make` command: yarn make ``` +If you get an error similar to: + +``` +The module '/electron/node_modules/node-pty/build/Release/pty.node' was compiled against a different Node.js version using NODE_MODULE_VERSION 115. This version of Node.js requires NODE_MODULE_VERSION 125. Please try re-compiling or re-installing the module (for instance, using `npm rebuild` or `npm install`). +``` + +You will need to rebuild the node-pty using [electron-rebuild](https://www.electronjs.org/docs/latest/tutorial/using-native-node-modules), for example: + +``` +npx electron-rebuild +``` + +or if that fails + +``` +yarn install -D @electron/rebuild +rm -rf node_modules +rm yarn.lock +yarn install +electron-rebuild +``` + ### Debugger There are helpful debug launch scripts for VSCode / Cursor under `.vscode/launch.json`. It will start the dev server as defined in `.vscode/tasks.json`. Then attach the debugger. diff --git a/builder-debug.config.ts b/builder-debug.config.ts index 94ab81e9..777d31c8 100644 --- a/builder-debug.config.ts +++ b/builder-debug.config.ts @@ -23,6 +23,7 @@ const debugConfig: Configuration = { icon: './assets/UI/Comfy_Logo_x256.png', target: 'appimage', }, + asarUnpack: ['**/node_modules/node-pty/**/*'], }; export default debugConfig; diff --git a/package.json b/package.json index b14d9330..ad881bfc 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "main": ".vite/build/main.js", "packageManager": "yarn@4.5.0", "config": { - "frontendVersion": "1.4.0", + "frontendVersion": "1.4.2", "comfyVersion": "0.2.7", "managerCommit": "e629215c100c89a9a9d33fc03be3248069ff67ef", "uvVersion": "0.5.1" @@ -55,6 +55,7 @@ "devDependencies": { "@electron/fuses": "^1.8.0", "@electron/notarize": "^2.4.0", + "@electron/rebuild": "^3.7.1", "@electron/windows-sign": "^1.1.3", "@playwright/test": "^1.47.2", "@sentry/wizard": "^3.30.0", @@ -97,6 +98,7 @@ "electron-log": "^5.2.0", "electron-store": "8.2.0", "jest": "^29.7.0", + "node-pty": "^1.0.0", "systeminformation": "^5.23.5", "tar": "^7.4.3", "wait-on": "^8.0.1", diff --git a/src/constants.ts b/src/constants.ts index 74074138..5e973bef 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -20,6 +20,10 @@ export const IPC_CHANNELS = { OPEN_PATH: 'open-path', OPEN_LOGS_PATH: 'open-logs-path', OPEN_DEV_TOOLS: 'open-dev-tools', + TERMINAL_WRITE: 'execute-terminal-command', + TERMINAL_RESIZE: 'resize-terminal', + TERMINAL_RESTORE: 'restore-terminal', + TERMINAL_ON_OUTPUT: 'terminal-output', IS_FIRST_TIME_SETUP: 'is-first-time-setup', GET_SYSTEM_PATHS: 'get-system-paths', VALIDATE_INSTALL_PATH: 'validate-install-path', diff --git a/src/main-process/comfyDesktopApp.ts b/src/main-process/comfyDesktopApp.ts index c8220486..62fe1090 100644 --- a/src/main-process/comfyDesktopApp.ts +++ b/src/main-process/comfyDesktopApp.ts @@ -15,15 +15,20 @@ import { getModelsDirectory } from '../utils'; import { DownloadManager } from '../models/DownloadManager'; import { VirtualEnvironment } from '../virtualEnvironment'; import { InstallWizard } from '../install/installWizard'; +import { Terminal } from '../terminal'; +import { getAppResourcesPath } from '../install/resourcePaths'; export class ComfyDesktopApp { public comfyServer: ComfyServer | null = null; + private terminal: Terminal; constructor( public basePath: string, public comfySettings: ComfySettings, public appWindow: AppWindow - ) {} + ) { + this.terminal = new Terminal(this.appWindow, getAppResourcesPath()); + } get pythonInstallPath() { return app.isPackaged ? this.basePath : path.join(app.getAppPath(), 'assets'); @@ -104,6 +109,18 @@ export class ComfyDesktopApp { return null; } }); + + ipcMain.handle(IPC_CHANNELS.TERMINAL_WRITE, (_event, command: string) => { + this.terminal.write(command); + }); + + ipcMain.handle(IPC_CHANNELS.TERMINAL_RESIZE, (_event, cols: number, rows: number) => { + this.terminal.resize(cols, rows); + }); + + ipcMain.handle(IPC_CHANNELS.TERMINAL_RESTORE, (_event) => { + return this.terminal.restore(); + }); } /** diff --git a/src/main.ts b/src/main.ts index 47e34618..5eb0a5de 100644 --- a/src/main.ts +++ b/src/main.ts @@ -75,7 +75,6 @@ if (!gotTheLock) { ...options, }); }); - try { const comfyDesktopApp = await ComfyDesktopApp.create(appWindow); await comfyDesktopApp.initialize(); @@ -90,7 +89,6 @@ if (!gotTheLock) { if (!useExternalServer) { await comfyDesktopApp.startComfyServer({ host, port, extraServerArgs }); } - appWindow.sendServerStartProgress(ProgressStatus.READY); appWindow.loadComfyUI({ host, port, extraServerArgs }); } catch (error) { diff --git a/src/preload.ts b/src/preload.ts index 65905251..f117070d 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -141,6 +141,40 @@ const electronAPI = { extras, }); }, + Terminal: { + /** + * Writes the data to the terminal + * @param data The command to execute + */ + write: (data: string): Promise => { + return ipcRenderer.invoke(IPC_CHANNELS.TERMINAL_WRITE, data); + }, + /** + * Resizes the terminal + * @param data The command to execute + */ + resize: (cols: number, rows: number): Promise => { + return ipcRenderer.invoke(IPC_CHANNELS.TERMINAL_RESIZE, cols, rows); + }, + /** + * Gets the data required to restore the terminal + * @param data The command to execute + */ + restore: (): Promise<{ buffer: string[]; pos: { x: number; y: number } }> => { + return ipcRenderer.invoke(IPC_CHANNELS.TERMINAL_RESTORE); + }, + /** + * Callback for terminal output messages + * @param callback The output handler + */ + onOutput: (callback: (message: string) => void) => { + const handler = (_event: Electron.IpcRendererEvent, value: string) => { + callback(value); + }; + ipcRenderer.on(IPC_CHANNELS.TERMINAL_ON_OUTPUT, handler); + return () => ipcRenderer.off(IPC_CHANNELS.TERMINAL_ON_OUTPUT, handler); + }, + }, /** * Check if the user has completed the first time setup wizard. */ diff --git a/src/terminal.ts b/src/terminal.ts new file mode 100644 index 00000000..80887fe0 --- /dev/null +++ b/src/terminal.ts @@ -0,0 +1,67 @@ +import * as os from 'node:os'; +import * as pty from 'node-pty'; +import { AppWindow } from './main-process/appWindow'; +import { IPC_CHANNELS } from './constants'; + +export class Terminal { + #pty: pty.IPty | undefined; + #window: AppWindow | undefined; + #cwd: string | undefined; + + readonly sessionBuffer: string[] = []; + readonly size = { cols: 80, rows: 30 }; + + get pty() { + this.#pty ??= this.#createPty(); + return this.#pty; + } + + get window() { + if (!this.#window) throw new Error('AppWindow not initialized.'); + return this.#window; + } + + constructor(window: AppWindow, cwd: string) { + this.#window = window; + this.#cwd = cwd; + } + + write(data: string) { + this.pty.write(data); + } + + resize(cols: number, rows: number) { + this.pty.resize(cols, rows); + this.size.cols = cols; + this.size.rows = rows; + } + + restore() { + return { + buffer: this.sessionBuffer, + size: this.size, + }; + } + + #createPty() { + const window = this.window; + // TODO: does this want to be a setting? + const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'; + const instance = pty.spawn(shell, [], { + handleFlowControl: false, + conptyInheritCursor: false, + name: 'comfyui-terminal', + cols: this.size.cols, + rows: this.size.rows, + cwd: this.#cwd, + }); + + instance.onData((data) => { + this.sessionBuffer.push(data); + window.send(IPC_CHANNELS.TERMINAL_ON_OUTPUT, data); + if (this.sessionBuffer.length > 1000) this.sessionBuffer.shift(); + }); + + return instance; + } +} diff --git a/todesktop.json b/todesktop.json index 3d608629..37640b65 100644 --- a/todesktop.json +++ b/todesktop.json @@ -1,6 +1,7 @@ { "id": "241012ess7yxs0e", "icon": "./assets/UI/Comfy_Logo_x128.png", + "appBuilderLibVersion": "25.1.8", "schemaVersion": 1, "uploadSizeLimit": 300, "appPath": ".", diff --git a/yarn.lock b/yarn.lock index bf0c2ae1..cb7f9f60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -470,6 +470,7 @@ __metadata: dependencies: "@electron/fuses": "npm:^1.8.0" "@electron/notarize": "npm:^2.4.0" + "@electron/rebuild": "npm:^3.7.1" "@electron/windows-sign": "npm:^1.1.3" "@playwright/test": "npm:^1.47.2" "@sentry/electron": "npm:^5.4.0" @@ -497,6 +498,7 @@ __metadata: husky: "npm:^9.1.6" jest: "npm:^29.7.0" lint-staged: "npm:^15.2.10" + node-pty: "npm:^1.0.0" prettier: "npm:^3.3.3" rimraf: "npm:^6.0.1" systeminformation: "npm:^5.23.5" @@ -576,6 +578,26 @@ __metadata: languageName: node linkType: hard +"@electron/node-gyp@git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2": + version: 10.2.0-electron.1 + resolution: "@electron/node-gyp@https://github.com/electron/node-gyp.git#commit=06b29aafb7708acef8b3669835c8a7857ebc92d2" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^8.1.0" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^10.2.1" + nopt: "npm:^6.0.0" + proc-log: "npm:^2.0.1" + semver: "npm:^7.3.5" + tar: "npm:^6.2.1" + which: "npm:^2.0.2" + bin: + node-gyp: ./bin/node-gyp.js + checksum: 10c0/e8c97bb5347bf0871312860010b70379069359bf05a6beb9e4d898d0831f9f8447f35b887a86d5241989e804813cf72054327928da38714a6102f791e802c8d9 + languageName: node + linkType: hard + "@electron/notarize@npm:2.5.0, @electron/notarize@npm:^2.4.0": version: 2.5.0 resolution: "@electron/notarize@npm:2.5.0" @@ -628,6 +650,31 @@ __metadata: languageName: node linkType: hard +"@electron/rebuild@npm:^3.7.1": + version: 3.7.1 + resolution: "@electron/rebuild@npm:3.7.1" + dependencies: + "@electron/node-gyp": "git+https://github.com/electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2" + "@malept/cross-spawn-promise": "npm:^2.0.0" + chalk: "npm:^4.0.0" + debug: "npm:^4.1.1" + detect-libc: "npm:^2.0.1" + fs-extra: "npm:^10.0.0" + got: "npm:^11.7.0" + node-abi: "npm:^3.45.0" + node-api-version: "npm:^0.2.0" + node-gyp: "npm:latest" + ora: "npm:^5.1.0" + read-binary-file-arch: "npm:^1.0.6" + semver: "npm:^7.3.5" + tar: "npm:^6.0.5" + yargs: "npm:^17.0.1" + bin: + electron-rebuild: lib/cli.js + checksum: 10c0/ef498ec1eb6d2870f4331dd53d84132a4fb7d2ef6a0058854731d3261fc0af3e2c8549c1c268801b7c2c9e93519b22d67b2a4fe7ed0136214100980801e2a372 + languageName: node + linkType: hard + "@electron/universal@npm:2.0.1": version: 2.0.1 resolution: "@electron/universal@npm:2.0.1" @@ -9364,7 +9411,7 @@ __metadata: languageName: node linkType: hard -"make-fetch-happen@npm:^10.0.3": +"make-fetch-happen@npm:^10.0.3, make-fetch-happen@npm:^10.2.1": version: 10.2.1 resolution: "make-fetch-happen@npm:10.2.1" dependencies: @@ -9822,7 +9869,7 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.14.0": +"nan@npm:^2.14.0, nan@npm:^2.17.0": version: 2.22.0 resolution: "nan@npm:2.22.0" dependencies: @@ -9966,6 +10013,16 @@ __metadata: languageName: node linkType: hard +"node-pty@npm:^1.0.0": + version: 1.0.0 + resolution: "node-pty@npm:1.0.0" + dependencies: + nan: "npm:^2.17.0" + node-gyp: "npm:latest" + checksum: 10c0/c308686826eb2f7616735f4c71e6e41ff630346b14319972dbdf9711640bc6975ce21eab66a47b7ce9a88c70308fe81bfd9840127388d5fa26c52afbad255871 + languageName: node + linkType: hard + "node-releases@npm:^2.0.18": version: 2.0.18 resolution: "node-releases@npm:2.0.18" @@ -10727,6 +10784,13 @@ __metadata: languageName: node linkType: hard +"proc-log@npm:^2.0.1": + version: 2.0.1 + resolution: "proc-log@npm:2.0.1" + checksum: 10c0/701c501429775ce34cec28ef6a1c976537274b42917212fb8a5975ebcecb0a85612907fd7f99ff28ff4c2112bb84a0f4322fc9b9e1e52a8562fcbb1d5b3ce608 + languageName: node + linkType: hard + "proc-log@npm:^4.1.0, proc-log@npm:^4.2.0": version: 4.2.0 resolution: "proc-log@npm:4.2.0"