mirror of
https://github.com/open-webui/desktop
synced 2025-06-26 18:15:59 +00:00
refac
This commit is contained in:
parent
8d97c049e6
commit
73740d7d73
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Open WebUI</title>
|
||||
<link rel="preload" href="/assets/fonts/InstrumentSerif-Regular.ttf" as="font" crossorigin="anonymous" />
|
||||
<link rel="preload" href="/assets/fonts/InstrumentSerif-Regular.ttf" as="font" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
57
src/main.ts
57
src/main.ts
@ -9,7 +9,8 @@ import {
|
||||
BrowserWindow,
|
||||
globalShortcut,
|
||||
Notification,
|
||||
ipcMain
|
||||
ipcMain,
|
||||
ipcRenderer
|
||||
} from 'electron';
|
||||
import path from 'path';
|
||||
import started from 'electron-squirrel-startup';
|
||||
@ -17,6 +18,7 @@ import started from 'electron-squirrel-startup';
|
||||
import {
|
||||
installPackage,
|
||||
removePackage,
|
||||
logEmitter,
|
||||
startServer,
|
||||
stopAllServers,
|
||||
validateInstallation
|
||||
@ -56,6 +58,11 @@ if (!gotTheLock) {
|
||||
let tray: Tray | null = null;
|
||||
|
||||
let SERVER_URL = null;
|
||||
let SERVER_STATUS = 'stopped';
|
||||
|
||||
logEmitter.on('log', (message) => {
|
||||
mainWindow?.webContents.send('main:log', message);
|
||||
});
|
||||
|
||||
const loadDefaultView = () => {
|
||||
// Load index.html or dev server URL
|
||||
@ -66,6 +73,34 @@ if (!gotTheLock) {
|
||||
}
|
||||
};
|
||||
|
||||
const startServerHandler = async () => {
|
||||
SERVER_STATUS = 'starting';
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:status',
|
||||
data: SERVER_STATUS
|
||||
});
|
||||
|
||||
try {
|
||||
SERVER_URL = await startServer();
|
||||
SERVER_STATUS = 'started';
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:status',
|
||||
data: SERVER_STATUS
|
||||
});
|
||||
|
||||
mainWindow.loadURL(SERVER_URL);
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
SERVER_STATUS = 'failed';
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:status',
|
||||
data: SERVER_STATUS
|
||||
});
|
||||
|
||||
mainWindow.webContents.send('main:log', `Failed to start server: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const onReady = async () => {
|
||||
console.log(process.resourcesPath);
|
||||
console.log(app.getName());
|
||||
@ -113,12 +148,7 @@ if (!gotTheLock) {
|
||||
data: true
|
||||
});
|
||||
|
||||
try {
|
||||
SERVER_URL = await startServer();
|
||||
mainWindow.loadURL(SERVER_URL);
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
}
|
||||
await startServerHandler();
|
||||
} else {
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'install:status',
|
||||
@ -204,16 +234,25 @@ if (!gotTheLock) {
|
||||
removePackage();
|
||||
});
|
||||
|
||||
ipcMain.handle('server:status', async (event) => {
|
||||
return SERVER_STATUS;
|
||||
});
|
||||
|
||||
ipcMain.handle('server:start', async (event) => {
|
||||
console.log('Starting server...');
|
||||
|
||||
startServer();
|
||||
await startServerHandler();
|
||||
});
|
||||
|
||||
ipcMain.handle('server:stop', async (event) => {
|
||||
console.log('Stopping server...');
|
||||
|
||||
stopAllServers();
|
||||
await stopAllServers();
|
||||
SERVER_STATUS = 'stopped';
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:status',
|
||||
data: SERVER_STATUS
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.handle('server:url', async (event) => {
|
||||
|
@ -28,9 +28,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
sendPing: async () => {
|
||||
console.log('Sending PING to main process...');
|
||||
await ipcRenderer.invoke('send-ping'); // Send the ping back to the main process
|
||||
onLog: (callback: (message: string) => void) => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
ipcRenderer.on('main:log', (_, message: string) => callback(message));
|
||||
},
|
||||
|
||||
installPackage: async () => {
|
||||
@ -53,6 +56,14 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
await ipcRenderer.invoke('remove');
|
||||
},
|
||||
|
||||
getServerStatus: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
return await ipcRenderer.invoke('server:status');
|
||||
},
|
||||
|
||||
startServer: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { installStatus } from './lib/stores';
|
||||
import { installStatus, serverStatus } from './lib/stores';
|
||||
|
||||
import Main from './lib/components/Main.svelte';
|
||||
|
||||
@ -24,6 +24,12 @@
|
||||
|
||||
break;
|
||||
|
||||
case 'electron:server:status':
|
||||
console.log('Server status:', event.data.data);
|
||||
serverStatus.set(event.data.data);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Unhandled message type:', event.data.type);
|
||||
}
|
||||
@ -32,6 +38,11 @@
|
||||
|
||||
if (window.electronAPI) {
|
||||
installStatus.set(await window.electronAPI.getInstallStatus());
|
||||
serverStatus.set(await window.electronAPI.getServerStatus());
|
||||
|
||||
window.electronAPI.onLog((log) => {
|
||||
console.log('Electron log:', log);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -1,13 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { installStatus } from '../stores';
|
||||
import { installStatus, serverStatus } from '../stores';
|
||||
|
||||
import Spinner from './common/Spinner.svelte';
|
||||
import ArrowRightCircle from './icons/ArrowRightCircle.svelte';
|
||||
|
||||
let installing = false;
|
||||
|
||||
const continueHandler = async () => {
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.installPackage();
|
||||
installing = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const installStatus = writable(null);
|
||||
export const serverStatus = writable(null);
|
||||
|
@ -1,6 +1,9 @@
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import net from 'net';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import {
|
||||
exec,
|
||||
execFile,
|
||||
@ -10,13 +13,16 @@ import {
|
||||
spawn,
|
||||
ChildProcess
|
||||
} from 'child_process';
|
||||
import net from 'net';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
import * as tar from 'tar';
|
||||
import log from 'electron-log';
|
||||
|
||||
import { app } from 'electron';
|
||||
|
||||
// Create and export a global event emitter specifically for logs
|
||||
export const logEmitter = new EventEmitter();
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//
|
||||
// General Utils
|
||||
@ -60,6 +66,18 @@ export function getOpenWebUIDataPath(): string {
|
||||
return openWebUIDataDir;
|
||||
}
|
||||
|
||||
export function getSecretKey(keyPath?: string, key?: string): string {
|
||||
keyPath = keyPath || path.join(getOpenWebUIDataPath(), '.key');
|
||||
|
||||
if (fs.existsSync(keyPath)) {
|
||||
return fs.readFileSync(keyPath, 'utf-8');
|
||||
}
|
||||
|
||||
key = key || crypto.randomBytes(64).toString('hex');
|
||||
fs.writeFileSync(keyPath, key);
|
||||
return key;
|
||||
}
|
||||
|
||||
export async function portInUse(port: number, host: string = '127.0.0.1'): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const client = new net.Socket();
|
||||
@ -182,22 +200,21 @@ export async function installOpenWebUI(installationPath: string) {
|
||||
// unpackCommand = `${createAdHocSignCommand(installationPath)}\n${unpackCommand}`;
|
||||
// }
|
||||
|
||||
console.log(unpackCommand);
|
||||
|
||||
const commandProcess = exec(unpackCommand, {
|
||||
shell: process.platform === 'win32' ? 'cmd.exe' : '/bin/bash'
|
||||
});
|
||||
|
||||
commandProcess.stdout?.on('data', (data) => {
|
||||
const onLog = (data) => {
|
||||
console.log(data);
|
||||
});
|
||||
logEmitter.emit('log', data);
|
||||
};
|
||||
|
||||
commandProcess.stderr?.on('data', (data) => {
|
||||
console.error(data);
|
||||
});
|
||||
commandProcess.stdout?.on('data', onLog);
|
||||
commandProcess.stderr?.on('data', onLog);
|
||||
|
||||
commandProcess.on('exit', (code) => {
|
||||
console.log(`Child exited with code ${code}`);
|
||||
logEmitter.emit('log', `Child exited with code ${code}`);
|
||||
});
|
||||
}
|
||||
|
||||
@ -209,6 +226,7 @@ export async function installBundledPython(installationPath?: string) {
|
||||
console.log(installationPath, pythonTarPath);
|
||||
if (!fs.existsSync(pythonTarPath)) {
|
||||
log.error('Python tarball not found');
|
||||
logEmitter.emit('log', 'Python tarball not found'); // Emit log
|
||||
return;
|
||||
}
|
||||
|
||||
@ -220,6 +238,7 @@ export async function installBundledPython(installationPath?: string) {
|
||||
});
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
logEmitter.emit('log', error); // Emit log
|
||||
}
|
||||
|
||||
// Get the path to the installed Python binary
|
||||
@ -227,6 +246,7 @@ export async function installBundledPython(installationPath?: string) {
|
||||
|
||||
if (!fs.existsSync(bundledPythonPath)) {
|
||||
log.error('Python binary not found in install path');
|
||||
logEmitter.emit('log', 'Python binary not found in install path'); // Emit log
|
||||
return;
|
||||
}
|
||||
|
||||
@ -236,6 +256,7 @@ export async function installBundledPython(installationPath?: string) {
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
console.log('Installed Python Version:', pythonVersion.trim());
|
||||
logEmitter.emit('log', `Installed Python Version: ${pythonVersion.trim()}`); // Emit log
|
||||
} catch (error) {
|
||||
log.error('Failed to execute Python binary', error);
|
||||
}
|
||||
@ -319,6 +340,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
|
||||
if (!(await validateInstallation(installationPath))) {
|
||||
console.error('Failed to validate installation');
|
||||
logEmitter.emit('log', 'Failed to validate installation'); // Emit log
|
||||
return;
|
||||
}
|
||||
|
||||
@ -327,11 +349,11 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
? `${installationPath}\\Scripts\\activate.bat && set DATA_DIR="${path.join(
|
||||
app.getPath('userData'),
|
||||
'data'
|
||||
)}" && open-webui serve`
|
||||
)}" && set WEBUI_SECRET_KEY=${getSecretKey()} && open-webui serve`
|
||||
: `source "${installationPath}/bin/activate" && export DATA_DIR="${path.join(
|
||||
app.getPath('userData'),
|
||||
'data'
|
||||
)}" && open-webui serve`;
|
||||
)}" && export WEBUI_SECRET_KEY=${getSecretKey()} && open-webui serve`;
|
||||
|
||||
port = port || 8080;
|
||||
while (await portInUse(port)) {
|
||||
@ -341,6 +363,8 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
startCommand += ` --port ${port}`;
|
||||
|
||||
console.log('Starting Open-WebUI server...');
|
||||
logEmitter.emit('log', 'Starting Open-WebUI server...'); // Emit log
|
||||
|
||||
const childProcess = spawn(startCommand, {
|
||||
shell: true,
|
||||
detached: true,
|
||||
@ -356,6 +380,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
const handleLog = (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
console.log(`[Open-WebUI Log]: ${logLine}`);
|
||||
logEmitter.emit('log', logLine);
|
||||
|
||||
// Look for "Uvicorn running on http://<hostname>:<port>"
|
||||
const match = logLine.match(
|
||||
@ -386,6 +411,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
if (childProcess.pid) {
|
||||
serverPIDs.add(childProcess.pid);
|
||||
console.log(`Server started with PID: ${childProcess.pid}`);
|
||||
logEmitter.emit('log', `Server started with PID: ${childProcess.pid}`); // Emit PID log
|
||||
} else {
|
||||
throw new Error('Failed to start server: No PID available');
|
||||
}
|
||||
@ -405,6 +431,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
}
|
||||
|
||||
console.log(`Server is now running at ${detectedURL}`);
|
||||
logEmitter.emit('log', `Server is now running at ${detectedURL}`); // Emit server URL log
|
||||
return detectedURL; // Return the detected URL
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user