diff --git a/.gitignore b/.gitignore index 8296128..22c8d90 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,6 @@ typings/ # Electron-Forge out/ + + +resources/* \ No newline at end of file diff --git a/forge.config.ts b/forge.config.ts index 88cbc37..391a356 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -1,18 +1,24 @@ -import type { ForgeConfig } from '@electron-forge/shared-types'; -import { MakerSquirrel } from '@electron-forge/maker-squirrel'; -import { MakerZIP } from '@electron-forge/maker-zip'; -import { MakerDeb } from '@electron-forge/maker-deb'; -import { MakerRpm } from '@electron-forge/maker-rpm'; -import { VitePlugin } from '@electron-forge/plugin-vite'; -import { FusesPlugin } from '@electron-forge/plugin-fuses'; -import { FuseV1Options, FuseVersion } from '@electron/fuses'; +import type { ForgeConfig } from "@electron-forge/shared-types"; +import { MakerSquirrel } from "@electron-forge/maker-squirrel"; +import { MakerZIP } from "@electron-forge/maker-zip"; +import { MakerDeb } from "@electron-forge/maker-deb"; +import { MakerRpm } from "@electron-forge/maker-rpm"; +import { VitePlugin } from "@electron-forge/plugin-vite"; +import { FusesPlugin } from "@electron-forge/plugin-fuses"; +import { FuseV1Options, FuseVersion } from "@electron/fuses"; const config: ForgeConfig = { packagerConfig: { asar: true, + icon: "src/assets/icon.png", }, rebuildConfig: {}, - makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], + makers: [ + new MakerSquirrel({}), + new MakerZIP({}, ["darwin"]), + new MakerRpm({}), + new MakerDeb({}), + ], plugins: [ new VitePlugin({ // `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc. @@ -20,20 +26,20 @@ const config: ForgeConfig = { build: [ { // `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`. - entry: 'src/main.ts', - config: 'vite.main.config.ts', - target: 'main', + entry: "src/main.ts", + config: "vite.main.config.ts", + target: "main", }, { - entry: 'src/preload.ts', - config: 'vite.preload.config.ts', - target: 'preload', + entry: "src/preload.ts", + config: "vite.preload.config.ts", + target: "preload", }, ], renderer: [ { - name: 'main_window', - config: 'vite.renderer.config.mts', + name: "main_window", + config: "vite.renderer.config.mts", }, ], }), diff --git a/package-lock.json b/package-lock.json index 7b528af..2d01ae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { - "name": "app", + "name": "open-webui", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "app", + "name": "open-webui", "version": "1.0.0", "license": "MIT", "dependencies": { + "electron-log": "^5.2.4", "electron-squirrel-startup": "^1.0.1", "tar": "^7.4.3", "update-electron-app": "^3.1.0" @@ -4463,6 +4464,15 @@ "node": ">=10" } }, + "node_modules/electron-log": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.2.4.tgz", + "integrity": "sha512-iX12WXc5XAaKeHg2QpiFjVwL+S1NVHPFd3V5RXtCmKhpAzXsVQnR3UEc0LovM6p6NkUQxDWnkdkaam9FNUVmCA==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/electron-squirrel-startup": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz", diff --git a/package.json b/package.json index 5c3ad72..4badc99 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "app", - "productName": "app", + "name": "open-webui", + "productName": "Open WebUI", "version": "1.0.0", - "description": "My Electron application description", + "description": "Open WebUI", "main": ".vite/build/main.js", "scripts": { "start": "electron-forge start", @@ -43,8 +43,12 @@ }, "license": "MIT", "dependencies": { + "electron-log": "^5.2.4", "electron-squirrel-startup": "^1.0.1", "tar": "^7.4.3", "update-electron-app": "^3.1.0" - } + }, + "extraResources": [ + "resources/py311.tar.gz" + ] } diff --git a/src/assets/icon.icns b/src/assets/icon.icns new file mode 100644 index 0000000..5fe63fb Binary files /dev/null and b/src/assets/icon.icns differ diff --git a/src/assets/icon.ico b/src/assets/icon.ico new file mode 100644 index 0000000..dac436a Binary files /dev/null and b/src/assets/icon.ico differ diff --git a/src/assets/icon.png b/src/assets/icon.png new file mode 100644 index 0000000..c27d9cc Binary files /dev/null and b/src/assets/icon.png differ diff --git a/src/main.ts b/src/main.ts index 87d1373..8bb3800 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,6 @@ -import { app, BrowserWindow } from 'electron'; -import path from 'path'; -import started from 'electron-squirrel-startup'; +import { app, BrowserWindow } from "electron"; +import path from "path"; +import started from "electron-squirrel-startup"; // Handle creating/removing shortcuts on Windows when installing/uninstalling. if (started) { @@ -12,8 +12,9 @@ const createWindow = () => { const mainWindow = new BrowserWindow({ width: 800, height: 600, + icon: "src/assets/icon.png", webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, "preload.js"), }, }); @@ -21,7 +22,9 @@ const createWindow = () => { 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`)); + mainWindow.loadFile( + path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`) + ); } // Open the DevTools. @@ -31,18 +34,18 @@ const createWindow = () => { // 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', createWindow); +app.on("ready", createWindow); // 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', () => { - if (process.platform !== 'darwin') { +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { app.quit(); } }); -app.on('activate', () => { +app.on("activate", () => { // On OS X it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) { diff --git a/src/render/assets/images/favicon.png b/src/render/assets/images/favicon.png new file mode 100644 index 0000000..2b20747 Binary files /dev/null and b/src/render/assets/images/favicon.png differ diff --git a/src/render/assets/images/splash-dark.png b/src/render/assets/images/splash-dark.png new file mode 100644 index 0000000..202c03f Binary files /dev/null and b/src/render/assets/images/splash-dark.png differ diff --git a/src/render/assets/images/splash.png b/src/render/assets/images/splash.png new file mode 100644 index 0000000..389196c Binary files /dev/null and b/src/render/assets/images/splash.png differ diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..e751393 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,94 @@ +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { + exec, + execFile, + ExecFileOptions, + execFileSync, + execSync, +} from "child_process"; + +import * as tar from "tar"; +import log from "electron-log"; + +import { app } from "electron"; + +//////////////////////////////////////////////// +// +// General Utils +// +//////////////////////////////////////////////// + +export function getAppDir(): string { + let appDir = app.getAppPath(); + if (!app.isPackaged) { + appDir = path.dirname(appDir); + } + return appDir; +} + +export function getUserHomeDir(): string { + return app.getPath("home"); +} + +export function getUserDataDir(): string { + const userDataDir = app.getPath("userData"); + + if (!fs.existsSync(userDataDir)) { + try { + fs.mkdirSync(userDataDir, { recursive: true }); + } catch (error) { + log.error(error); + } + } + + return userDataDir; +} + +//////////////////////////////////////////////// +// +// Python Utils +// +//////////////////////////////////////////////// + +export function getPackagedPythonTarPath(): string { + const appDir = getAppDir(); + return path.join(appDir, "resources", "python.tar.gz"); +} + +export function getBundledPythonEnvDir(): string { + const installDir = + process.platform === "darwin" + ? path.normalize(path.join(app.getPath("home"), "Library", app.getName())) + : app.getPath("userData"); + + if (!fs.existsSync(installDir)) { + try { + fs.mkdirSync(installDir, { recursive: true }); + } catch (error) { + log.error(error); + } + } + return installDir; +} + +export function getBundledPythonEnvPath(): string { + const userDataDir = getBundledPythonEnvDir(); + + return path.join(userDataDir, "python"); +} + +export function isCondaEnv(envPath: string): boolean { + return fs.existsSync(path.join(envPath, "conda-meta")); +} + +export function getPythonPath(envPath: string, isConda?: boolean) { + if (process.platform === "win32") { + return isConda ?? isCondaEnv(envPath) + ? path.join(envPath, "python.exe") + : path.join(envPath, "Scripts", "python.exe"); + } else { + return path.join(envPath, "bin", "python"); + } +}