mirror of
https://github.com/open-webui/desktop
synced 2025-06-26 18:15:59 +00:00
refac
This commit is contained in:
18
src/main.ts
18
src/main.ts
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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
0
src/utils/build.ts
Normal 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.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user