diff --git a/.gitignore b/.gitignore index a547bf3..62c62cc 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,17 @@ dist-ssr *.njsproj *.sln *.sw? + +# Electron build output +release/ +.webpack/ + +# Next.js +.next/ +out/ + +# Misc +*.pem +.env +.env.* +!.env.example diff --git a/README.md b/README.md index 85a680b..5d174d1 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ One-click installers — no Node.js or terminal required. | macOS Apple Silicon (M1/M2/M3/M4) | [Open Generative AI-1.0.0-arm64.dmg](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI-1.0.0-arm64.dmg) | | macOS Intel (x64) | [Open Generative AI-1.0.0.dmg](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI-1.0.0.dmg) | | Windows (x64 + ARM64) | [Open Generative AI Setup 1.0.0.exe](https://github.com/Anil-matcha/Open-Generative-AI/releases/download/v1.0.0/Open.Generative.AI.Setup.1.0.0.exe) | +| Linux (Ubuntu x64) | Build locally with `npm run electron:build:linux` | All releases: [github.com/Anil-matcha/Open-Generative-AI/releases](https://github.com/Anil-matcha/Open-Generative-AI/releases) @@ -56,6 +57,48 @@ Windows SmartScreen may show a warning because the installer is not code-signed: The app will install silently to `%LocalAppData%` with a Start Menu shortcut. +### Ubuntu / Linux Installation + +Linux artifacts are available when building with Electron Builder: + +```bash +# Build Linux installers (AppImage + .deb) +npm run electron:build:linux +``` + +Generated files are written to the `release/` folder: +- **AppImage** — portable, run directly after making executable: + ```bash + chmod +x "release/Open Generative AI-*.AppImage" + ./release/Open\ Generative\ AI-*.AppImage + ``` +- **.deb** — install on Debian/Ubuntu: + ```bash + sudo apt install ./release/open-generative-ai_*_amd64.deb + ``` + +If AppImage fails to start on older systems, install `libfuse2`: + +```bash +sudo apt install libfuse2 +``` + +#### Ubuntu 24.04+ / AppArmor sandbox restriction + +Ubuntu 24.04 and later enable a kernel security policy (`apparmor_restrict_unprivileged_userns`) that blocks Chromium's user-namespace sandbox. If the app fails to start silently or crashes immediately, you have two options: + +**Option A — Recommended: install the `.deb` instead.** +The `.deb` package ships an AppArmor profile that grants the required permission automatically on install with no system-wide changes. + +**Option B — Temporary system fix (AppImage users):** +```bash +sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 +``` +This lasts until next reboot. To make it permanent: +```bash +echo 'kernel.apparmor_restrict_unprivileged_userns=0' | sudo tee /etc/sysctl.d/99-userns.conf +``` + --- Open Generative AI is an open-source AI image, video, cinema, and lip sync studio that brings creative workflows to everyone. Powered by [Muapi.ai](https://muapi.ai), it supports text-to-image, image-to-image, text-to-video, image-to-video, and audio-driven lip sync generation across models like Flux, Nano Banana, Midjourney, Kling, Sora, Veo, Seedream, Infinite Talk, LTX Lipsync, Wan 2.2, and more — all from a sleek, modern interface you can self-host and customize. @@ -251,6 +294,9 @@ npm run electron:build # Windows (NSIS installer — x64 + ARM64) npm run electron:build:win +# Linux (AppImage + DEB — x64) +npm run electron:build:linux + # Both platforms in one pass npm run electron:build:all ``` diff --git a/build/linux/apparmor.profile b/build/linux/apparmor.profile new file mode 100644 index 0000000..2d8f991 --- /dev/null +++ b/build/linux/apparmor.profile @@ -0,0 +1,7 @@ +abi , +include + +profile open-generative-ai /opt/Open\ Generative\ AI/open-generative-ai flags=(unconfined) { + userns, + include if exists +} diff --git a/electron/main.js b/electron/main.js index ad3e8f1..c4fc5be 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,33 +1,46 @@ -import { app, BrowserWindow, shell } from 'electron'; -import { fileURLToPath } from 'url'; -import path from 'path'; +const { app, BrowserWindow, shell } = require('electron'); +const path = require('path'); -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); +// Ubuntu 24.04+ sets kernel.apparmor_restrict_unprivileged_userns=1 which +// blocks Chromium's user namespace sandbox. The .deb package ships an AppArmor +// profile that grants the permission cleanly. When running the AppImage on an +// affected system, run once: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 +// or pass --no-sandbox on the command line. +if (process.platform === 'linux') { + app.commandLine.appendSwitch('disable-dev-shm-usage'); +} let mainWindow; function createWindow() { + const isMac = process.platform === 'darwin'; + mainWindow = new BrowserWindow({ width: 1440, height: 900, minWidth: 1024, minHeight: 640, webPreferences: { - webSecurity: false, // Allow file:// origin to call external APIs + webSecurity: false, contextIsolation: true, nodeIntegration: false, }, - titleBarStyle: 'hiddenInset', + ...(isMac ? { titleBarStyle: 'hiddenInset' } : {}), backgroundColor: '#0d0d0d', show: false, title: 'Open Generative AI', }); const indexPath = path.join(__dirname, '../dist/index.html'); - mainWindow.loadFile(indexPath); + mainWindow.loadFile(indexPath).catch((err) => { + console.error('Failed to load index.html:', err); + mainWindow.show(); + }); + + mainWindow.webContents.on('did-fail-load', (event, code, desc) => { + console.error('did-fail-load:', code, desc); + }); - // Open external links in the system browser mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; diff --git a/package-lock.json b/package-lock.json index 4354cf2..3aa1715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "eslint": "^9", "eslint-config-next": "^15.0.0", "postcss": "^8.5.6", - "tailwindcss": "^3.4.0", + "tailwindcss": "^4.2.2", "vite": "^5.4.0" } }, @@ -36,6 +36,7 @@ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -10062,7 +10063,8 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -12778,82 +12780,11 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tailwindcss/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -13791,6 +13722,84 @@ "react": ">=18.0.0", "react-dom": ">=18.0.0" } + }, + "packages/studio/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "packages/studio/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "packages/studio/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "packages/studio/node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } } } } diff --git a/package.json b/package.json index f2a8f64..dde7e3a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "vite:build": "vite build", "electron:build": "vite build && electron-builder --mac", "electron:build:win": "vite build && electron-builder --win", - "electron:build:all": "vite build && electron-builder --mac --win" + "electron:build:linux": "vite build && electron-builder --linux", + "electron:build:all": "vite build && electron-builder --mac --win --linux" }, "build": { "appId": "ai.generative.open", @@ -56,27 +57,52 @@ ] } ] + }, + "linux": { + "icon": "public/banner.png", + "category": "Utility", + "maintainer": "Open Generative AI Team", + "extraFiles": [ + { + "from": "build/linux/apparmor.profile", + "to": "resources/apparmor.profile" + } + ], + "target": [ + { + "target": "AppImage", + "arch": [ + "x64" + ] + }, + { + "target": "deb", + "arch": [ + "x64" + ] + } + ] } }, "dependencies": { + "axios": "^1.7.0", "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "studio": "*", - "axios": "^1.7.0", - "react-hot-toast": "^2.4.1" + "react-hot-toast": "^2.4.1", + "studio": "*" }, "devDependencies": { "@eslint/eslintrc": "^3", + "@tailwindcss/vite": "^4.1.18", "autoprefixer": "^10.4.24", "electron": "^33.4.11", "electron-builder": "^25.1.8", "eslint": "^9", "eslint-config-next": "^15.0.0", "postcss": "^8.5.6", - "tailwindcss": "^3.4.0", - "vite": "^5.4.0", - "@tailwindcss/vite": "^4.1.18" + "tailwindcss": "^4.2.2", + "vite": "^5.4.0" }, "main": "electron/main.js" } diff --git a/src/components/CameraControls.js b/src/components/CameraControls.js index 84f54b6..70e947b 100644 --- a/src/components/CameraControls.js +++ b/src/components/CameraControls.js @@ -3,30 +3,30 @@ import { CAMERA_MAP, LENS_MAP, FOCAL_PERSPECTIVE, APERTURE_EFFECT } from '../lib const ASSET_URLS = { // CAMERA - "Modular 8K Digital": "/assets/cinema/modular_8k_digital.webp", - "Full-Frame Cine Digital": "/assets/cinema/full_frame_cine_digital.webp", - "Grand Format 70mm Film": "/assets/cinema/grand_format_70mm_film.webp", - "Studio Digital S35": "/assets/cinema/studio_digital_s35.webp", - "Classic 16mm Film": "/assets/cinema/classic_16mm_film.webp", - "Premium Large Format Digital": "/assets/cinema/premium_large_format_digital.webp", + "Modular 8K Digital": "./assets/cinema/modular_8k_digital.webp", + "Full-Frame Cine Digital": "./assets/cinema/full_frame_cine_digital.webp", + "Grand Format 70mm Film": "./assets/cinema/grand_format_70mm_film.webp", + "Studio Digital S35": "./assets/cinema/studio_digital_s35.webp", + "Classic 16mm Film": "./assets/cinema/classic_16mm_film.webp", + "Premium Large Format Digital": "./assets/cinema/premium_large_format_digital.webp", // LENS - "Creative Tilt Lens": "/assets/cinema/creative_tilt_lens.webp", - "Compact Anamorphic": "/assets/cinema/compact_anamorphic.webp", - "Extreme Macro": "/assets/cinema/extreme_macro.webp", - "70s Cinema Prime": "/assets/cinema/70s_cinema_prime.webp", - "Classic Anamorphic": "/assets/cinema/classic_anamorphic.webp", - "Premium Modern Prime": "/assets/cinema/premium_modern_prime.webp", - "Warm Cinema Prime": "/assets/cinema/warm_cinema_prime.webp", - "Swirl Bokeh Portrait": "/assets/cinema/swirl_bokeh_portrait.webp", - "Vintage Prime": "/assets/cinema/vintage_prime.webp", - "Halation Diffusion": "/assets/cinema/halation_diffusion.webp", - "Clinical Sharp Prime": "/assets/cinema/clinical_sharp_prime.webp", + "Creative Tilt Lens": "./assets/cinema/creative_tilt_lens.webp", + "Compact Anamorphic": "./assets/cinema/compact_anamorphic.webp", + "Extreme Macro": "./assets/cinema/extreme_macro.webp", + "70s Cinema Prime": "./assets/cinema/70s_cinema_prime.webp", + "Classic Anamorphic": "./assets/cinema/classic_anamorphic.webp", + "Premium Modern Prime": "./assets/cinema/premium_modern_prime.webp", + "Warm Cinema Prime": "./assets/cinema/warm_cinema_prime.webp", + "Swirl Bokeh Portrait": "./assets/cinema/swirl_bokeh_portrait.webp", + "Vintage Prime": "./assets/cinema/vintage_prime.webp", + "Halation Diffusion": "./assets/cinema/halation_diffusion.webp", + "Clinical Sharp Prime": "./assets/cinema/clinical_sharp_prime.webp", // APERTURE - "f/1.4": "/assets/cinema/f_1_4.webp", - "f/4": "/assets/cinema/f_4.webp", - "f/11": "/assets/cinema/f_11.webp" + "f/1.4": "./assets/cinema/f_1_4.webp", + "f/4": "./assets/cinema/f_4.webp", + "f/11": "./assets/cinema/f_11.webp" }; export function CameraControls(onChange) { diff --git a/src/components/ImageStudio.js b/src/components/ImageStudio.js index c66deec..6ce5f3f 100644 --- a/src/components/ImageStudio.js +++ b/src/components/ImageStudio.js @@ -9,6 +9,17 @@ import { AuthModal } from './AuthModal.js'; import { createUploadPicker } from './UploadPicker.js'; import { savePendingJob, removePendingJob, getPendingJobs } from '../lib/pendingJobs.js'; +function createInlineInstructions(type) { + const el = document.createElement('div'); + el.className = 'w-full text-center text-white/30 text-sm flex flex-col items-center gap-2 py-2'; + const icon = type === 'image' ? '🖼️' : '🎬'; + el.innerHTML = ` +

${icon} Enter a prompt above and click Generate to create your ${type}.

+

Tip: Be descriptive — include style, lighting, mood, and subject for best results.

+ `; + return el; +} + export function ImageStudio() { const container = document.createElement('div'); container.className = 'w-full h-full flex flex-col items-center justify-center bg-app-bg relative p-4 md:p-6 overflow-y-auto custom-scrollbar overflow-x-hidden';