diff --git a/.github/workflows/publish_windows.yml b/.github/workflows/publish_windows.yml index 4f0eb1f7..e11ab95f 100644 --- a/.github/workflows/publish_windows.yml +++ b/.github/workflows/publish_windows.yml @@ -8,41 +8,80 @@ jobs: build-windows: runs-on: windows-latest steps: + - name: Enable Long Name + shell: pwsh + run: New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force + - run: git config --system core.longpaths true + - name: Set up certificate + run: | + echo "${{ secrets.DIGICERT_AUTHENTICATION_CERTIFICATE_BASE64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + shell: bash - name: Github checkout uses: actions/checkout@v4 - name: Use Node.js uses: actions/setup-node@v4 with: node-version: 20 - - run: npm i + - run: yarn install + - name: Mod + shell: powershell + run: | + (Get-Content node_modules\@electron\windows-sign\dist\cjs\sign-with-signtool.js) -replace [Regex]::Escape('await execute({ ...internalOptions, hash: "sha1'), [Regex]::Escape('//await execute({ ...internalOptions, hash: "sha1') | Out-File -encoding ASCII node_modules\@electron\windows-sign\dist\cjs\sign-with-signtool.js + (Get-Content node_modules\@electron\windows-sign\dist\esm\sign-with-signtool.js) -replace [Regex]::Escape('await execute({ ...internalOptions, hash: "sha1'), [Regex]::Escape('//await execute({ ...internalOptions, hash: "sha1') | Out-File -encoding ASCII node_modules\@electron\windows-sign\dist\esm\sign-with-signtool.js + gc node_modules\@electron\windows-sign\dist\esm\sign-with-signtool.js + - name: Set variables + id: variables + run: | + echo "{version}={${GITHUB_REF#refs/tags/v}}" >> $GITHUB_OUTPUT + echo "SM_HOST=${{ secrets.DIGICERT_HOST_ENVIRONMET }}" >> "$GITHUB_ENV" + echo "SM_API_KEY=${{ secrets.DIGICERT_API_KEY }}" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" + echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.DIGICERT_AUTHENTICATION_CERTIFICATE_PASSWORD }}" >> "$GITHUB_ENV" + echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH + echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH + echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH + shell: bash + - name: Download Keylocker Software + run: | + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o Keylockertools-windows-x64.msi + shell: cmd + - name: Install and Sync Cert Software + run: | + msiexec /i Keylockertools-windows-x64.msi /passive + smksp_registrar.exe list + smctl.exe keypair ls + C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user + smksp_cert_sync.exe + shell: cmd - name: Download and prepare ComfyUI run: | curl -L -o comfyui-win.7z https://github.com/Comfy-Org/python-dependencies/releases/download/embedded-windows-deps-cu11.8-py11.9-5/ComfyUI_windows_portable.7z - ls - 7z -x comfyui-win.7z -oAssets/UI/ComfyUI/ - rm -rf comfyui-win.7z + 7z x comfyui-win.7z -odist/ + move dist/ComfyUI_windows_portable/ComfyUI assets/UI/ + move dist/ComfyUI_windows_portable/python_embedded assets/UI/ + cd assets/UI/ComfyUI && ls - name: Make app + shell: powershell env: + DIGICERT_FINGERPRINT: ${{ secrets.DIGICERT_FINGERPRINT }} DEBUG: electron-forge:* GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run publish --dry-run - # Windows Code Sign: - #- name: Sign files with Trusted Signing - # uses: azure/trusted-signing-action@v0.4.0 - # with: - # azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} - # azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} - # azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }} - # endpoint: https://eus.codesigning.azure.net/ - # trusted-signing-account-name: vscx-codesigning - # certificate-profile-name: vscx-certificate-profile - # files-folder: ${{ github.workspace }}\out\ - # files-folder-filter: exe,dll - # file-digest: SHA256 - # timestamp-rfc3161: http://timestamp.acs.microsoft.com - # timestamp-digest: SHA256 + run: npm run publish + - name: Signing using Signtool + env: + SM_HOST: ${{ secrets.DIGICERT_HOST_ENVIRONMET }} + SM_CLIENT_CERT_FILE : D:\\Certificate_pkcs12.p12 + run: | + signtool.exe sign /sha1 ${{ secrets.DIGICERT_FINGERPRINT }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "out/ComfyUI-win32-x64/ComfyUI.exe" + - name: Print SignLogs + if: always() + shell: powershell + run: cd $HOME ; gc .signingmanager\logs\smksp.log + - name: verify signing + run: + signtool verify out/ComfyUI-win32-x64/ComfyUI.exe - name: publish app env: DEBUG: electron-forge:* GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: npm run publish --from-dry-run \ No newline at end of file + run: npm run publish -- --from-dry-run diff --git a/assets/UI/run_cpu.bat b/assets/UI/run_cpu.bat new file mode 100644 index 00000000..9e48a5e2 --- /dev/null +++ b/assets/UI/run_cpu.bat @@ -0,0 +1,2 @@ +.\resources\UI\python_embedded\python.exe -s .//resources//UI//ComfyUI//main.py --cpu --windows-standalone-build +pause diff --git a/forge.config.ts b/forge.config.ts index 075154eb..215d52c4 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -11,6 +11,13 @@ import { FuseV1Options, FuseVersion } from '@electron/fuses'; const config: ForgeConfig = { packagerConfig: { asar: true, + windowsSign: {debug:true, + hookFunction: (filePath) => { + if (filePath.endsWith(".dll")) return; + require("child_process").execSync(`signtool.exe sign /sha1 ${process.env.DIGICERT_FINGERPRINT} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 ${filePath}`) + }, + signWithParams: `/csp \"DigiCert Signing Manager KSP\" /kc key_889133389 /sha1 ${process.env.DIGICERT_FINGERPRINT}` + }, osxSign: { optionsForFile: (filepath) => { return { entitlements: './assets/entitlements.mac.plist' }; @@ -34,8 +41,7 @@ const config: ForgeConfig = { }, }, makers: [ - new MakerSquirrel({}), - new MakerZIP({}, ['darwin']), + new MakerZIP({}, ['darwin', 'win32']), new MakerRpm({}), new MakerDeb({}), ], @@ -76,7 +82,7 @@ const config: ForgeConfig = { publishers: [ { name: '@electron-forge/publisher-github', - platforms: ['darwin'], + platforms: ['darwin', 'win32'], config: { repository: { owner: 'comfy-org', diff --git a/index.html b/index.html index 68c0aee2..f297d336 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,12 @@ - Hello World! + Comfy UI -

💖 Hello World!

-

Welcome to your Electron application.

- +
+ + diff --git a/package.json b/package.json index 0fb0a7b3..816810a0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "comfyui-electron", "productName": "ComfyUI", - "version": "1.0.0", + "version": "0.0.1", "description": "The best modular GUI to run AI diffusion models.", "main": ".vite/build/main.js", "scripts": { @@ -24,7 +24,11 @@ "@electron-forge/plugin-vite": "^7.4.0", "@electron-forge/publisher-github": "^7.4.0", "@electron/fuses": "^1.8.0", + "@electron/notarize": "^2.4.0", + "@electron/windows-sign": "^1.1.3", "@types/adm-zip": "^0.5.5", + "@types/react": "^18.3.4", + "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "electron": "31.3.1", @@ -41,9 +45,10 @@ }, "license": "GPL", "dependencies": { - "@electron/notarize": "^2.4.0", "adm-zip": "^0.5.15", "axios": "^1.7.3", - "electron-squirrel-startup": "^1.0.1" + "electron-squirrel-startup": "^1.0.1", + "react": "^18.3.1", + "react-dom": "^18.3.1" } } diff --git a/src/index.css b/src/index.css index 8856f90b..04a008cc 100644 --- a/src/index.css +++ b/src/index.css @@ -4,4 +4,5 @@ body { margin: auto; max-width: 38rem; padding: 2rem; + background-color: #474747; } diff --git a/src/main.ts b/src/main.ts index bb95c353..3b9e2dd8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -14,112 +14,134 @@ const port = 8188; // Replace with the port number your server is running on const packagedComfyUIExecutable = process.platform == 'win32' ? 'run_cpu.bat' : process.platform == 'darwin' ? 'ComfyUI' : 'ComfyUI'; const createWindow = () => { - // Create the browser window. - const mainWindow = new BrowserWindow({ - title: 'ComfyUI', - width: 800, - height: 600, - webPreferences: { - preload: path.join(__dirname, 'preload.js'), - nodeIntegration: true, // Enable Node.js integration - contextIsolation: false, - }, - - }); - - // Load the UI from the Python server's URL - mainWindow.loadURL('http://localhost:8188/'); - - // Open the DevTools. - mainWindow.webContents.openDevTools(); - }; + // Create the browser window. + const mainWindow = new BrowserWindow({ + title: 'ComfyUI', + width: 800, + height: 600, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + nodeIntegration: true, // Enable Node.js integration + contextIsolation: false, + webviewTag: true, + }, + + }); + + // and load the index.html of the app. + if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { + mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); + } else { + mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); + } + // Open the DevTools. + mainWindow.webContents.openDevTools(); + + return; + + // Load the UI from the Python server's URL + mainWindow.loadURL('http://localhost:8188/'); + +}; const isPortInUse = (host: string, port: number): Promise => { - return new Promise((resolve) => { - const server = net.createServer(); - - server.once('error', (err: NodeJS.ErrnoException) => { - if (err.code === 'EADDRINUSE') { - resolve(true); - } else { - resolve(false); - } - }); - - server.once('listening', () => { - server.close(); + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', (err: NodeJS.ErrnoException) => { + if (err.code === 'EADDRINUSE') { + resolve(true); + } else { resolve(false); - }); - - server.listen(port, host); + } }); - }; - + + server.once('listening', () => { + server.close(); + resolve(false); + }); + + server.listen(port, host); + }); +}; + +const maxFailWait: number = 10 * 1000; // 10seconds +let currentWaitTime: number = 0; const launchPythonServer = async () => { - const isServerRunning = await isPortInUse(host, port); - if (isServerRunning) { - console.log('Python server is already running'); - return Promise.resolve(); - } + const isServerRunning = await isPortInUse(host, port); + if (isServerRunning) { + console.log('Python server is already running'); + return Promise.resolve(); + } + + console.log('Launching Python server...'); + + return new Promise((resolve, reject) => { + let executablePath: string; - console.log('Launching Python server...'); - return new Promise((resolve, reject) => { - let executablePath: string; - - if (app.isPackaged) { - //Production: use the bundled Python package - executablePath = path.join(process.resourcesPath,'UI', packagedComfyUIExecutable); - pythonProcess = spawn(executablePath, {shell:true}); - } else { - // Development: use the fake Python server - executablePath = path.join(app.getAppPath(), 'ComfyUI', 'ComfyUI.sh'); - pythonProcess = spawn(executablePath, { - stdio: 'pipe', - }); - } - - pythonProcess.stdout.pipe(process.stdout); - pythonProcess.stderr.pipe(process.stderr); - - const checkInterval = 1000; // Check every 1 second - - const checkServerReady = async () => { - const isReady = await isPortInUse(host, port); - if (isReady) { - console.log('Python server is ready'); - resolve(); - } else { - setTimeout(checkServerReady, checkInterval); - } - }; - - checkServerReady(); + if (app.isPackaged) { + //Production: use the bundled Python package + executablePath = path.join(process.resourcesPath, 'UI', packagedComfyUIExecutable); + pythonProcess = spawn(executablePath, { shell: true }); + } else { + // Development: use the fake Python server + executablePath = path.join(app.getAppPath(), 'ComfyUI', 'ComfyUI.sh'); + pythonProcess = spawn(executablePath, { + stdio: 'pipe', }); + } + + pythonProcess.stdout.pipe(process.stdout); + pythonProcess.stderr.pipe(process.stderr); + + const checkInterval = 1000; // Check every 1 second + + const checkServerReady = async () => { + currentWaitTime += 1000; + if (currentWaitTime > maxFailWait) { + reject("Python Server Failed To Start"); + } + const isReady = await isPortInUse(host, port); + if (isReady) { + console.log('Python server is ready'); + resolve(); + } else { + setTimeout(checkServerReady, checkInterval); + } + }; + + checkServerReady(); + }); }; - + // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', async () => { - await launchPythonServer(); createWindow(); + try { + await launchPythonServer(); + + } catch (error) { + + } }); const killPythonServer = () => { - if (pythonProcess) { - pythonProcess.kill(); - pythonProcess = null; - } - }; + if (pythonProcess) { + pythonProcess.kill(); + pythonProcess = null; + } +}; - app.on('will-quit', () => { - killPythonServer(); - }); +app.on('will-quit', () => { + killPythonServer(); +}); - // Quit when all windows are closed, except on macOS. There, it's common +// Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. app.on('window-all-closed', () => { diff --git a/src/renderer.ts b/src/renderer.tsx similarity index 78% rename from src/renderer.ts rename to src/renderer.tsx index d75993cd..16e9ffbc 100644 --- a/src/renderer.ts +++ b/src/renderer.tsx @@ -27,5 +27,12 @@ */ import './index.css'; +import React from 'react'; +import Home from './renderer/home/index'; +import ReactDOM from 'react-dom/client' -console.log('👋 This message is being logged by "renderer.ts", included via Vite'); +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + , + ) \ No newline at end of file diff --git a/src/renderer/home/comfyTab.tsx b/src/renderer/home/comfyTab.tsx new file mode 100644 index 00000000..27ca69be --- /dev/null +++ b/src/renderer/home/comfyTab.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; + + +function ComfyUI(): React.ReactElement { + return +} + +export default ComfyUI; \ No newline at end of file diff --git a/src/renderer/home/index.tsx b/src/renderer/home/index.tsx new file mode 100644 index 00000000..0ab41eae --- /dev/null +++ b/src/renderer/home/index.tsx @@ -0,0 +1,43 @@ +import React, { useState } from 'react'; + +import TabOne from './tab1'; +import TabTwo from './comfyTab'; +import TabThree from './tab3'; + + +function tabStyle(active: boolean): React.CSSProperties { + return ({ + backgroundColor: active ? '#878787' : '#474747' + }); +} + +function getTab(tabOn: number): any { + switch (tabOn) { + case 0: + return ; + case 1: + return ; + case 2: + return ; + }; +} + +function Home(): React.ReactElement { + + const [tabOn, setTab] = useState(0); + + return (

Tab Test

+
+ + + +
+
+ { + getTab(tabOn) + } +
+
); +} + +export default Home; \ No newline at end of file diff --git a/src/renderer/home/tab1.tsx b/src/renderer/home/tab1.tsx new file mode 100644 index 00000000..07705688 --- /dev/null +++ b/src/renderer/home/tab1.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +function TabOne(): React.JSX.Element { + return
This is Tab One
+} + +export default TabOne; \ No newline at end of file diff --git a/src/renderer/home/tab3.tsx b/src/renderer/home/tab3.tsx new file mode 100644 index 00000000..ea3877fb --- /dev/null +++ b/src/renderer/home/tab3.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; + +function TabThree(): React.ReactElement { + return +} + +export default TabThree; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 74434b2a..e924623c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "jsx": "react", "target": "ESNext", "module": "commonjs", "allowJs": true,