This commit is contained in:
Timothy Jaeryang Baek 2025-01-11 13:18:46 -08:00
parent 4e99cf651e
commit 9f09e26247
8 changed files with 531 additions and 32 deletions

View File

@ -1,7 +1,7 @@
{
"name": "open-webui",
"productName": "Open WebUI",
"version": "1.0.0",
"version": "0.0.1",
"description": "Open WebUI",
"main": ".vite/build/main.js",
"scripts": {

View File

@ -13,11 +13,11 @@
version: 1
metadata:
content_hash:
linux-64: 70e93bda3c8489b7c9a77267cf5f53637ac0986f8eb5e3c2a61b62c711ba28cc
linux-aarch64: ae6dd314eb6a45f1d31ad96cc52c06a1d7d4cd3bb9c37d49dd8faf2873366511
osx-64: d93fb8129cd157fb75eec5b95210a21f4277ab47e8c95f314d8cd36e820f1eaf
osx-arm64: 94bf97b6d0946324018d90ea0bb7203470a35c64ab1af0cb97b167670423fe12
win-64: 4a82a67e4012619c266c561d9ed123a9cb5b321bf0e216ed4446d36b38a1aa9a
linux-64: 32a65ee01f9bc14f0e200fcab21a7570646cd57905095aee6fdd821d282af160
linux-aarch64: 8211b35201c1b6b1be1e81f062a3cf49ba5ab3e243c4e36dfc7a427022ccf3da
osx-64: 2f9c668aa6e91bbe0f5972c8b491d399c6b9669268226e1d03ea4cf032e091d3
osx-arm64: df6864f98ad09f0d80fe854d9d9f09b54d38f64c3e7129f498f4be5abc8f30d8
win-64: 5109005b8bf33c4cb71d88488196a65b165f25e00a1363d1b7a5676dab11f366
channels:
- url: conda-forge
used_env_vars: []
@ -207,6 +207,30 @@ package:
sha256: 80ec7e8f006196808fac5bd4b3773a652847f97bbf08044cd87731424ac64f8b
category: main
optional: false
- name: libcxx
version: 19.1.6
manager: conda
platform: osx-64
dependencies:
__osx: '>=10.13'
url: https://conda.anaconda.org/conda-forge/osx-64/libcxx-19.1.6-hf95d169_1.conda
hash:
md5: 1bad6c181a0799298aad42fc5a7e98b7
sha256: c40661648c34c08e21b69e0eec021ccaf090ffff070d2a9cbcb1519e1b310568
category: main
optional: false
- name: libcxx
version: 19.1.6
manager: conda
platform: osx-arm64
dependencies:
__osx: '>=11.0'
url: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-19.1.6-ha82da77_1.conda
hash:
md5: ce5252d8db110cdb4ae4173d0a63c7c5
sha256: 2b2443404503cd862385fd2f2a2c73f9624686fd1e5a45050b4034cfc06904ec
category: main
optional: false
- name: libexpat
version: 2.6.4
manager: conda
@ -555,6 +579,30 @@ package:
sha256: ecfc0182c3b2e63c870581be1fa0e4dbdfec70d2011cb4f5bde416ece26c41df
category: main
optional: false
- name: libstdcxx
version: 14.2.0
manager: conda
platform: linux-64
dependencies:
libgcc: 14.2.0
url: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-14.2.0-hc0a3c3a_1.conda
hash:
md5: 234a5554c53625688d51062645337328
sha256: 4661af0eb9bdcbb5fb33e5d0023b001ad4be828fccdcc56500059d56f9869462
category: main
optional: false
- name: libstdcxx
version: 14.2.0
manager: conda
platform: linux-aarch64
dependencies:
libgcc: 14.2.0
url: https://conda.anaconda.org/conda-forge/linux-aarch64/libstdcxx-14.2.0-h3f4de04_1.conda
hash:
md5: 37f489acd39e22b623d2d1e5ac6d195c
sha256: 519556d2c93f1b487091ce046d62e762286177f4a670ec10e16005177d0bcab3
category: main
optional: false
- name: libuuid
version: 2.38.1
manager: conda
@ -783,6 +831,76 @@ package:
sha256: 519a06eaab7c878fbebb8cab98ea4a4465eafb1e9ed8c6ce67226068a80a92f0
category: main
optional: false
- name: pip
version: 24.3.1
manager: conda
platform: linux-64
dependencies:
python: '>=3.9,<3.13.0a0'
setuptools: ''
wheel: ''
url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda
hash:
md5: 04e691b9fadd93a8a9fad87a81d4fd8f
sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb
category: main
optional: false
- name: pip
version: 24.3.1
manager: conda
platform: linux-aarch64
dependencies:
setuptools: ''
wheel: ''
python: '>=3.9,<3.13.0a0'
url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda
hash:
md5: 04e691b9fadd93a8a9fad87a81d4fd8f
sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb
category: main
optional: false
- name: pip
version: 24.3.1
manager: conda
platform: osx-64
dependencies:
setuptools: ''
wheel: ''
python: '>=3.9,<3.13.0a0'
url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda
hash:
md5: 04e691b9fadd93a8a9fad87a81d4fd8f
sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb
category: main
optional: false
- name: pip
version: 24.3.1
manager: conda
platform: osx-arm64
dependencies:
setuptools: ''
wheel: ''
python: '>=3.9,<3.13.0a0'
url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda
hash:
md5: 04e691b9fadd93a8a9fad87a81d4fd8f
sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb
category: main
optional: false
- name: pip
version: 24.3.1
manager: conda
platform: win-64
dependencies:
setuptools: ''
wheel: ''
python: '>=3.9,<3.13.0a0'
url: https://conda.anaconda.org/conda-forge/noarch/pip-24.3.1-pyh8b19718_2.conda
hash:
md5: 04e691b9fadd93a8a9fad87a81d4fd8f
sha256: da8c8888de10c1e4234ebcaa1550ac2b4b5408ac20f093fe641e4bc8c9c9f3eb
category: main
optional: false
- name: python
version: 3.11.11
manager: conda
@ -957,6 +1075,66 @@ package:
sha256: a1dfa679ac3f6007362386576a704ad2d0d7a02e98f5d0b115f207a2da63e884
category: main
optional: false
- name: setuptools
version: 75.8.0
manager: conda
platform: linux-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda
hash:
md5: 8f28e299c11afdd79e0ec1e279dcdc52
sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9
category: main
optional: false
- name: setuptools
version: 75.8.0
manager: conda
platform: linux-aarch64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda
hash:
md5: 8f28e299c11afdd79e0ec1e279dcdc52
sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9
category: main
optional: false
- name: setuptools
version: 75.8.0
manager: conda
platform: osx-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda
hash:
md5: 8f28e299c11afdd79e0ec1e279dcdc52
sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9
category: main
optional: false
- name: setuptools
version: 75.8.0
manager: conda
platform: osx-arm64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda
hash:
md5: 8f28e299c11afdd79e0ec1e279dcdc52
sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9
category: main
optional: false
- name: setuptools
version: 75.8.0
manager: conda
platform: win-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/setuptools-75.8.0-pyhff2d567_0.conda
hash:
md5: 8f28e299c11afdd79e0ec1e279dcdc52
sha256: e0778e4f276e9a81b51c56f51ec22a27b4d8fc955abc0be77ad09ca9bea06bb9
category: main
optional: false
- name: tk
version: 8.6.13
manager: conda
@ -1087,6 +1265,73 @@ package:
sha256: db8dead3dd30fb1a032737554ce91e2819b43496a0db09927edf01c32b577450
category: main
optional: false
- name: uv
version: 0.5.17
manager: conda
platform: linux-64
dependencies:
__glibc: '>=2.17,<3.0.a0'
libgcc: '>=13'
libstdcxx: '>=13'
url: https://conda.anaconda.org/conda-forge/linux-64/uv-0.5.17-h0f3a69f_0.conda
hash:
md5: d5d97fb9313f4ff7ae3d7e5c98794b5b
sha256: 6c42b2eedd779b671f7cb7e74665a0dcf6a12404d546e2095a4aac5d76820cd2
category: main
optional: false
- name: uv
version: 0.5.17
manager: conda
platform: linux-aarch64
dependencies:
libgcc: '>=13'
libstdcxx: '>=13'
url: https://conda.anaconda.org/conda-forge/linux-aarch64/uv-0.5.17-h2016286_0.conda
hash:
md5: c4a46d144ce557015ffcc82c5153ca5b
sha256: 2708d296ad6f7928ea089d428c3c40190ecafc27ed6124f9979904e61f3adfd1
category: main
optional: false
- name: uv
version: 0.5.17
manager: conda
platform: osx-64
dependencies:
__osx: '>=10.13'
libcxx: '>=18'
url: https://conda.anaconda.org/conda-forge/osx-64/uv-0.5.17-h8de1528_0.conda
hash:
md5: 8fecdbea72fffbc52150943fdfe730a9
sha256: 86a0684c14e3d9ed54314c8a0d748c3d8378808137439ed8f6d7bfbd0a44520d
category: main
optional: false
- name: uv
version: 0.5.17
manager: conda
platform: osx-arm64
dependencies:
__osx: '>=11.0'
libcxx: '>=18'
url: https://conda.anaconda.org/conda-forge/osx-arm64/uv-0.5.17-h668ec48_0.conda
hash:
md5: e6aee4ba25b32e27eadf9a60315faa06
sha256: 27fb3cf5daa5cdbf9d16629d702290e342399b8217bb44031fe531200f9f9eeb
category: main
optional: false
- name: uv
version: 0.5.16
manager: conda
platform: win-64
dependencies:
ucrt: '>=10.0.20348.0'
vc: '>=14.2,<15'
vc14_runtime: '>=14.29.30139'
url: https://conda.anaconda.org/conda-forge/win-64/uv-0.5.16-ha08ef0e_0.conda
hash:
md5: 810e5ec595b71a697921245347b5c52b
sha256: e07951504d15fb299385ac20879007abf13f764e5a96b8abb9dff5275387f042
category: main
optional: false
- name: vc
version: '14.3'
manager: conda
@ -1123,3 +1368,63 @@ package:
sha256: 568ce8151eaae256f1cef752fc78651ad7a86ff05153cc7a4740b52ae6536118
category: main
optional: false
- name: wheel
version: 0.45.1
manager: conda
platform: linux-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
hash:
md5: 75cb7132eb58d97896e173ef12ac9986
sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce
category: main
optional: false
- name: wheel
version: 0.45.1
manager: conda
platform: linux-aarch64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
hash:
md5: 75cb7132eb58d97896e173ef12ac9986
sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce
category: main
optional: false
- name: wheel
version: 0.45.1
manager: conda
platform: osx-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
hash:
md5: 75cb7132eb58d97896e173ef12ac9986
sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce
category: main
optional: false
- name: wheel
version: 0.45.1
manager: conda
platform: osx-arm64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
hash:
md5: 75cb7132eb58d97896e173ef12ac9986
sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce
category: main
optional: false
- name: wheel
version: 0.45.1
manager: conda
platform: win-64
dependencies:
python: '>=3.9'
url: https://conda.anaconda.org/conda-forge/noarch/wheel-0.45.1-pyhd8ed1ab_1.conda
hash:
md5: 75cb7132eb58d97896e173ef12ac9986
sha256: 1b34021e815ff89a4d902d879c3bd2040bc1bd6169b32e9427497fa05c55f1ce
category: main
optional: false

View File

@ -3,6 +3,8 @@ channels:
- conda-forge
dependencies:
- python=3.11
- pip
- uv
platforms:
- linux-64
- linux-aarch64 # aka arm64, use for Docker on Apple Silicon

View File

@ -10,6 +10,8 @@ import {
import path from "path";
import started from "electron-squirrel-startup";
import { installPackage } from "./utils";
// Restrict app to a single instance
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
@ -68,6 +70,10 @@ if (!gotTheLock) {
);
}
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
// Create a system tray icon
const image = nativeImage.createFromPath(
path.join(__dirname, "assets/tray.png")
@ -102,7 +108,12 @@ if (!gotTheLock) {
});
};
ipcMain.on("load-webui", (event, arg) => {
ipcMain.handle("install-package", async (event) => {
console.log("Installing package...");
installPackage();
});
ipcMain.handle("load-webui", async (event, arg) => {
console.log(arg); // prints "ping"
mainWindow.loadURL("http://localhost:8080");
@ -118,9 +129,14 @@ if (!gotTheLock) {
});
});
app.on("before-quit", () => {
app.isQuiting = true; // Ensure quit flag is set
});
// Quit when all windows are closed, except on macOS
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.isQuitting = true;
app.quit();
}
});

View File

@ -15,12 +15,16 @@ window.addEventListener("DOMContentLoaded", () => {
});
contextBridge.exposeInMainWorld("electronAPI", {
sendPing: () => {
sendPing: async () => {
console.log("Sending PING to main process...");
ipcRenderer.send("send-ping"); // Send the ping back to the main process
await ipcRenderer.invoke("send-ping"); // Send the ping back to the main process
},
loadWebUI: (arg) => {
ipcRenderer.send("load-webui", arg);
installPackage: async () => {
await ipcRenderer.invoke("install-package");
},
loadWebUI: async (arg) => {
await ipcRenderer.invoke("load-webui", arg);
},
});

View File

@ -19,7 +19,7 @@
onclick={() => {
console.log("clicked");
if (window?.electronAPI) {
window.electronAPI.loadWebUI();
window.electronAPI.installPackage();
}
}}>Install Open WebUI</button
>

0
src/utils/build.ts Normal file
View File

View File

@ -7,6 +7,8 @@ import {
ExecFileOptions,
execFileSync,
execSync,
spawn,
ChildProcess,
} from "child_process";
import * as tar from "tar";
@ -22,9 +24,6 @@ import { app } from "electron";
export function getAppPath(): string {
let appDir = app.getAppPath();
if (!app.isPackaged) {
appDir = path.dirname(appDir);
}
return appDir;
}
@ -57,7 +56,7 @@ export function getBundledPythonTarPath(): string {
return path.join(appPath, "resources", "python.tar.gz");
}
export function getBundledPythonInstallPath(): string {
export function getBundledPythonInstallationPath(): string {
const installDir = path.join(app.getPath("userData"), "python");
if (!fs.existsSync(installDir)) {
@ -85,7 +84,7 @@ export function getPythonPath(envPath: string, isConda?: boolean) {
}
export function getBundledPythonPath() {
return getPythonPath(getBundledPythonInstallPath());
return getPythonPath(getBundledPythonInstallationPath());
}
export function isBundledPythonInstalled() {
@ -128,37 +127,210 @@ export function createAdHocSignCommand(envPath: string): string {
)} && cd -`;
}
export async function installBundledPython(installPath?: string) {
const platform = process.platform;
const isWin = platform === "win32";
installPath = installPath || getBundledPythonInstallPath();
export async function installOpenWebUI(installationPath: string) {
console.log(installationPath);
let unpackCommand =
process.platform === "win32"
? `${installationPath}\\Scripts\\activate.bat && pip install open-webui -U`
: `source "${installationPath}/bin/activate" && pip install open-webui -U`;
// only unsign when installing from bundled installer
// if (platform === "darwin") {
// unpackCommand = `${createAdHocSignCommand(installationPath)}\n${unpackCommand}`;
// }
console.log(unpackCommand);
const commandProcess = exec(unpackCommand, {
shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash",
});
// once the environment is activated, print the python version
commandProcess.stdout?.on("data", (data) => {
console.log(data);
});
commandProcess.stderr?.on("data", (data) => {
console.error(data);
});
commandProcess.on("exit", (code) => {
console.log(`Child exited with code ${code}`);
});
}
export async function installBundledPython(installationPath?: string) {
installationPath = installationPath || getBundledPythonInstallationPath();
const pythonTarPath = getBundledPythonTarPath();
console.log(installationPath, pythonTarPath);
if (!fs.existsSync(pythonTarPath)) {
log.error("Python tarball not found");
return;
}
try {
fs.mkdirSync(installPath, { recursive: true });
fs.mkdirSync(installationPath, { recursive: true });
await tar.x({
cwd: installPath,
cwd: installationPath,
file: pythonTarPath,
});
} catch (error) {
log.error(error);
}
let unpackCommand = isWin
? `${installPath}\\Scripts\\activate.bat && conda-unpack`
: `source "${installPath}/bin/activate" && conda-unpack`;
// Get the path to the installed Python binary
const bundledPythonPath = getBundledPythonPath();
// only unsign when installing from bundled installer
if (platform === "darwin") {
unpackCommand = `${createAdHocSignCommand(installPath)}\n${unpackCommand}`;
if (!fs.existsSync(bundledPythonPath)) {
log.error("Python binary not found in install path");
return;
}
const commandProcess = exec(unpackCommand, {
shell: isWin ? "cmd.exe" : "/bin/bash",
});
try {
// Execute the Python binary to print the version
const pythonVersion = execFileSync(bundledPythonPath, ["--version"], {
encoding: "utf-8",
});
console.log("Installed Python Version:", pythonVersion.trim());
} catch (error) {
log.error("Failed to execute Python binary", error);
}
}
export async function installPackage(installationPath?: string) {
installationPath = installationPath || getBundledPythonInstallationPath();
if (!isBundledPythonInstalled()) {
try {
await installBundledPython(installationPath);
} catch (error) {
log.error("Failed to install bundled Python", error);
return Promise.reject("Failed to install bundled Python");
}
}
try {
await installOpenWebUI(installationPath);
} catch (error) {
log.error("Failed to install open-webui", error);
return Promise.reject("Failed to install open-webui");
}
}
/**
* Validates that Python is installed and the `open-webui` package is present
* within the specified virtual environment.
*
* @param installationPath - The path to the virtual environment installation
* @returns Promise<void> - Resolves if all prerequisites are valid; rejects otherwise
*/
export async function validateInstallation(
installationPath: string
): Promise<void> {
const pythonPath = getPythonPath(installationPath);
// Check if Python binary exists
if (!fs.existsSync(pythonPath)) {
return Promise.reject(
`Python binary not found in environment: ${pythonPath}`
);
}
try {
// Check if `open-webui` is installed
const checkCommand =
process.platform === "win32"
? `${installationPath}\\Scripts\\activate.bat && pip show open-webui`
: `source "${installationPath}/bin/activate" && pip show open-webui`;
execSync(checkCommand, { stdio: "ignore", shell: true });
} catch (error) {
return Promise.reject(
`The 'open-webui' package is not installed in the virtual environment at ${installationPath}. Install it first.`
);
}
// All validation passed
return Promise.resolve();
}
// Map to track running processes by installation path
const activeProcesses: Map<string, ChildProcess> = new Map();
/**
* Starts the Open-WebUI server.
*
* @param installationPath - The path to the virtual environment installation
* @param port - The port on which the server will run
*/
export async function startOpenWebUIServer(
installationPath: string,
port: number
): Promise<void> {
try {
await validateInstallation(installationPath);
} catch (validationError) {
console.error(validationError);
return Promise.reject(validationError); // Abort if validation fails
}
// Construct the command based on the platform
let startCommand =
process.platform === "win32"
? `${installationPath}\\Scripts\\activate.bat && open-webui serve`
: `source "${installationPath}/bin/activate" && open-webui serve`;
if (port) {
startCommand += ` --port ${port}`;
}
// Spawn the process
console.log("Starting Open-WebUI server...");
const childProcess = spawn(startCommand, [], { shell: true });
// Log process output
childProcess.stdout?.on("data", (data) => {
console.log(`[Open-WebUI]: ${data.toString().trim()}`);
});
childProcess.stderr?.on("data", (data) => {
console.error(`[Open-WebUI Error]: ${data.toString().trim()}`);
});
childProcess.on("exit", (exitCode) => {
console.log(`Open-WebUI server exited with code ${exitCode}`);
});
// Keep track of the process for later termination
activeProcesses.set(installationPath, childProcess);
}
/**
* Stops the running Open-WebUI server.
*
* @param installationPath - The path to the virtual environment installation
*/
export async function stopOpenWebUIServer(
installationPath: string
): Promise<void> {
const processToStop = activeProcesses.get(installationPath);
if (!processToStop) {
console.error(
"No active server found for the specified installation path."
);
return;
}
console.log("Stopping Open-WebUI server...");
// Terminate the process
processToStop.kill();
// Remove from the active processes map
activeProcesses.delete(installationPath);
console.log("Open-WebUI server stopped successfully.");
}