mirror of
https://github.com/open-webui/desktop.git
synced 2026-07-15 04:35:40 -04:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db1b59a813 | |||
| 6d4ffea851 | |||
| 238cce6dc8 | |||
| b255f22fe7 | |||
| d205c176ef | |||
| 2a223e62b6 | |||
| eb2ebd2c1d | |||
| a6a944c847 | |||
| 03fea4a7cc | |||
| 6b159246d1 | |||
| 398dc3128e | |||
| d991908205 | |||
| 2528096e07 | |||
| aeba0e7c22 |
+46
-9
@@ -16,12 +16,9 @@ import {
|
||||
} from "electron";
|
||||
import path, { join } from "path";
|
||||
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
|
||||
import log from "electron-log";
|
||||
|
||||
import icon from "../../resources/icon.png?asset";
|
||||
import trayIconImage from "../../resources/assets/tray.png?asset";
|
||||
|
||||
import {
|
||||
getLogFilePath,
|
||||
checkUrlAndOpen,
|
||||
getConfig,
|
||||
getServerLog,
|
||||
@@ -38,6 +35,12 @@ import {
|
||||
uninstallPython,
|
||||
} from "./utils";
|
||||
|
||||
import log from "electron-log";
|
||||
log.transports.file.resolvePathFn = () => getLogFilePath("main");
|
||||
|
||||
import icon from "../../resources/icon.png?asset";
|
||||
import trayIconImage from "../../resources/assets/tray.png?asset";
|
||||
|
||||
// Main application logic
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let tray: Tray | null = null;
|
||||
@@ -122,6 +125,13 @@ function createWindow(show = true): void {
|
||||
uninstallHandler();
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
label: "Reset",
|
||||
click: async () => {
|
||||
await resetAppHandler();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
const updatedMenu = Menu.buildFromTemplate(menuTemplate);
|
||||
@@ -241,7 +251,7 @@ const uninstallHandler = async () => {
|
||||
});
|
||||
notification.show();
|
||||
} catch (error) {
|
||||
console.error("Uninstallation failed:", error);
|
||||
log.error("Uninstallation failed:", error);
|
||||
// Show error notification
|
||||
const notification = new Notification({
|
||||
title: "Open WebUI",
|
||||
@@ -307,7 +317,7 @@ const startServerHandler = async () => {
|
||||
|
||||
return true; // Indicate success
|
||||
} catch (error) {
|
||||
console.error("Failed to start server:", error);
|
||||
log.error("Failed to start server:", error);
|
||||
SERVER_STATUS = "failed";
|
||||
mainWindow?.webContents.send("main:data", {
|
||||
type: "status:server",
|
||||
@@ -343,11 +353,38 @@ const stopServerHandler = async () => {
|
||||
|
||||
return true; // Indicate success
|
||||
} catch (error) {
|
||||
console.error("Failed to stop server:", error);
|
||||
log.error("Failed to stop server:", error);
|
||||
return false; // Indicate failure
|
||||
}
|
||||
};
|
||||
|
||||
const resetAppHandler = async () => {
|
||||
try {
|
||||
await stopServerHandler(); // Stop the server if running
|
||||
SERVER_STATUS = null;
|
||||
|
||||
// wait a moment to ensure all processes are stopped
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
await resetApp(); // Reset the application state
|
||||
|
||||
// Show success notification
|
||||
const notification = new Notification({
|
||||
title: "Open WebUI",
|
||||
body: "Application has been reset successfully.",
|
||||
});
|
||||
notification.show();
|
||||
} catch (error) {
|
||||
log.error("Failed to reset application:", error);
|
||||
// Show error notification
|
||||
const notification = new Notification({
|
||||
title: "Open WebUI",
|
||||
body: `Failed to reset application: ${error.message}`,
|
||||
});
|
||||
notification.show();
|
||||
}
|
||||
};
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit(); // Quit if another instance is already running
|
||||
@@ -485,7 +522,7 @@ if (!gotTheLock) {
|
||||
});
|
||||
|
||||
ipcMain.handle("app:reset", async (event) => {
|
||||
return await resetApp();
|
||||
return await resetAppHandler();
|
||||
});
|
||||
|
||||
ipcMain.handle("get:config", async (event) => {
|
||||
@@ -528,7 +565,7 @@ if (!gotTheLock) {
|
||||
);
|
||||
await installPackage("open-webui");
|
||||
} catch (error) {
|
||||
console.error("Failed to update package:", error);
|
||||
log.error("Failed to update package:", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+203
-77
@@ -12,6 +12,18 @@ import { app, shell, Notification } from "electron";
|
||||
import { execFileSync, exec, spawn, execSync, execFile } from "child_process";
|
||||
|
||||
import log from "electron-log";
|
||||
log.transports.file.resolvePathFn = () => getLogFilePath("main");
|
||||
|
||||
const serverLogger = log.create({ logId: "server" });
|
||||
serverLogger.transports.file.resolvePath = () => getLogFilePath(`server`);
|
||||
|
||||
export const getLogFilePath = (name: string = "main"): string => {
|
||||
const logDir = path.join(getUserDataPath(), "logs");
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
return path.join(logDir, `${name}.log`);
|
||||
};
|
||||
|
||||
export const getAppPath = (): string => {
|
||||
let appPath = app.getAppPath();
|
||||
@@ -219,7 +231,7 @@ export const downloadFileWithProgress = async (
|
||||
return downloadPath;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Download failed:", error);
|
||||
log.error("Download failed:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -237,7 +249,7 @@ export const getPythonDownloadPath = (): string => {
|
||||
return downloadPath;
|
||||
};
|
||||
|
||||
export const getPythonInstallationPath = (): string => {
|
||||
export const getPythonInstallationDir = (): string => {
|
||||
const installDir = path.join(app.getPath("userData"), "python");
|
||||
|
||||
if (!fs.existsSync(installDir)) {
|
||||
@@ -276,7 +288,7 @@ const downloadPython = async (onProgress = null) => {
|
||||
log.info(`✅ Python downloaded successfully to: ${result}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(`❌ Download failed: ${error?.message}`);
|
||||
log.error(`❌ Download failed: ${error?.message}`);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -299,7 +311,7 @@ const checkInternet = async () => {
|
||||
};
|
||||
|
||||
export const installPython = async (
|
||||
installationPath?: string
|
||||
installationDir?: string
|
||||
): Promise<boolean> => {
|
||||
let pythonDownloadPath = getPythonDownloadPath();
|
||||
if (!isPythonDownloaded()) {
|
||||
@@ -314,9 +326,6 @@ export const installPython = async (
|
||||
log.info(
|
||||
`Downloading Python: ${progress.toFixed(2)}% (${downloaded} of ${total} bytes)`
|
||||
);
|
||||
log.info(
|
||||
`Downloading Python: ${progress.toFixed(2)}% (${downloaded} of ${total} bytes)`
|
||||
);
|
||||
});
|
||||
}
|
||||
if (!fs.existsSync(pythonDownloadPath)) {
|
||||
@@ -324,8 +333,8 @@ export const installPython = async (
|
||||
return false;
|
||||
}
|
||||
|
||||
installationPath = installationPath || getPythonInstallationPath();
|
||||
log.info(installationPath, pythonDownloadPath);
|
||||
installationDir = installationDir || getPythonInstallationDir();
|
||||
log.info(installationDir, pythonDownloadPath);
|
||||
|
||||
try {
|
||||
const userDataPath = getUserDataPath();
|
||||
@@ -339,13 +348,16 @@ export const installPython = async (
|
||||
}
|
||||
|
||||
// Get the path to the installed Python binary
|
||||
if (isPythonInstalled(installationPath)) {
|
||||
const pythonPath = getPythonPath(installationPath);
|
||||
if (isPythonInstalled(installationDir)) {
|
||||
const pythonPath = getPythonPath(installationDir);
|
||||
|
||||
execFileSync(pythonPath, ["-m", "pip", "install", "uv"], {
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
log.info("Successfully installed uv package");
|
||||
@@ -367,14 +379,14 @@ export const getPythonExecutablePath = (envPath: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getPythonPath = (installationPath?: string) => {
|
||||
export const getPythonPath = (installationDir?: string) => {
|
||||
return path.normalize(
|
||||
getPythonExecutablePath(installationPath || getPythonInstallationPath())
|
||||
getPythonExecutablePath(installationDir || getPythonInstallationDir())
|
||||
);
|
||||
};
|
||||
|
||||
export const isPythonInstalled = (installationPath?: string) => {
|
||||
const pythonPath = getPythonPath(installationPath);
|
||||
export const isPythonInstalled = (installationDir?: string) => {
|
||||
const pythonPath = getPythonPath(installationDir);
|
||||
|
||||
if (!fs.existsSync(pythonPath)) {
|
||||
log.error("Python binary not found in install path");
|
||||
@@ -387,6 +399,9 @@ export const isPythonInstalled = (installationPath?: string) => {
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
log.info("Installed Python Version:", pythonVersion.trim());
|
||||
@@ -398,14 +413,17 @@ export const isPythonInstalled = (installationPath?: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const isUvInstalled = (installationPath?: string) => {
|
||||
const pythonPath = getPythonPath(installationPath);
|
||||
export const isUvInstalled = (installationDir?: string) => {
|
||||
const pythonPath = getPythonPath(installationDir);
|
||||
try {
|
||||
// Check if uv is installed by running the command
|
||||
const result = execFileSync(pythonPath, ["-m", "uv", "--version"], {
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -420,16 +438,17 @@ export const isUvInstalled = (installationPath?: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const uninstallPython = (installationPath?: string): boolean => {
|
||||
installationPath = installationPath || getPythonInstallationPath();
|
||||
export const uninstallPython = (installationDir?: string): boolean => {
|
||||
installationDir = installationDir || getPythonInstallationDir();
|
||||
|
||||
if (!fs.existsSync(installationPath)) {
|
||||
if (!fs.existsSync(installationDir)) {
|
||||
log.error("Python installation not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
fs.rmSync(installationPath, { recursive: true });
|
||||
fs.rmSync(installationDir, { recursive: true, force: true });
|
||||
log.info("Python installation removed successfully:", installationDir);
|
||||
} catch (error) {
|
||||
log.error("Failed to remove Python installation", error);
|
||||
return false;
|
||||
@@ -450,17 +469,6 @@ export const resetApp = async (): Promise<void> => {
|
||||
await uninstallPython();
|
||||
log.info("Uninstalled Python environment");
|
||||
|
||||
// remove /data folder
|
||||
const dataPath = getOpenWebUIDataPath();
|
||||
if (fs.existsSync(dataPath)) {
|
||||
try {
|
||||
fs.rmSync(dataPath, { recursive: true });
|
||||
log.info("Removed data directory:", dataPath);
|
||||
} catch (error) {
|
||||
log.error("Failed to remove data directory:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// remove config file
|
||||
const configPath = path.join(getUserDataPath(), "config.json");
|
||||
if (fs.existsSync(configPath)) {
|
||||
@@ -482,6 +490,17 @@ export const resetApp = async (): Promise<void> => {
|
||||
log.error("Failed to remove secret key file:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// remove /data folder
|
||||
const dataPath = getOpenWebUIDataPath();
|
||||
if (fs.existsSync(dataPath)) {
|
||||
try {
|
||||
fs.rmSync(dataPath, { recursive: true, force: true });
|
||||
log.info("Removed data directory:", dataPath);
|
||||
} catch (error) {
|
||||
log.error("Failed to remove data directory:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////
|
||||
@@ -551,6 +570,9 @@ export const installPackage = (
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -601,6 +623,9 @@ export const isPackageInstalled = (packageName: string): boolean => {
|
||||
encoding: "utf-8",
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -643,16 +668,15 @@ export const startServer = async (
|
||||
const pythonPath = getPythonPath();
|
||||
log.info(`Using Python at: ${pythonPath}`);
|
||||
|
||||
const openWebUIPath = path.join(path.dirname(pythonPath), "open-webui");
|
||||
|
||||
let commandArgs: string[];
|
||||
commandArgs = ["-m", "uv", "run", "open-webui", "serve", "--host", host];
|
||||
|
||||
const dataDir = path.join(app.getPath("userData"), "data");
|
||||
const dataDir = getOpenWebUIDataPath();
|
||||
const secretKey = getSecretKey();
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
process.env.DATA_DIR = dataDir;
|
||||
process.env.WEBUI_SECRET_KEY = secretKey;
|
||||
|
||||
@@ -678,7 +702,12 @@ export const startServer = async (
|
||||
const childProcess = spawn(pythonPath, commandArgs, {
|
||||
detached: process.platform !== "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: { ...process.env },
|
||||
env: {
|
||||
...process.env,
|
||||
...(process.platform === "win32"
|
||||
? { PYTHONIOENCODING: "utf-8" }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
if (!childProcess.pid) {
|
||||
@@ -691,24 +720,23 @@ export const startServer = async (
|
||||
|
||||
const appendLog = (source: string) => (data: Buffer) => {
|
||||
const logLine = data.toString().trim();
|
||||
const tag = `[${source}][PID:${childProcess.pid}]:`;
|
||||
logLines.push(`${tag} ${logLine}`);
|
||||
// (Optional) also log to main process console
|
||||
// log.info(`${tag} ${logLine}`);
|
||||
const line = `[${source}][PID:${childProcess.pid}]: ${logLine}`;
|
||||
logLines.push(line);
|
||||
serverLogger.info(line); // Log to console
|
||||
};
|
||||
childProcess.stdout?.on("data", appendLog("stdout"));
|
||||
childProcess.stderr?.on("data", appendLog("stderr"));
|
||||
childProcess.on("close", (code, signal) => {
|
||||
logLines.push(
|
||||
`[process][PID:${childProcess.pid}] Exited with code ${code} signal ${signal}`
|
||||
);
|
||||
const line = `[process][PID:${childProcess.pid}] Exited with code ${code} signal ${signal}`;
|
||||
serverLogger.info(line);
|
||||
logLines.push(line);
|
||||
serverPIDs.delete(childProcess.pid);
|
||||
// Note: we keep the logs available until manually cleared
|
||||
});
|
||||
childProcess.on("error", (err) => {
|
||||
logLines.push(
|
||||
`[process][PID:${childProcess.pid}] Error: ${err.message}`
|
||||
);
|
||||
const line = `[process][PID:${childProcess.pid}] Error: ${err.message}`;
|
||||
serverLogger.error(line);
|
||||
logLines.push(line);
|
||||
});
|
||||
|
||||
// Compute URL directly, do not try to parse logs
|
||||
@@ -721,50 +749,148 @@ export const startServer = async (
|
||||
};
|
||||
|
||||
/**
|
||||
* Terminates all server processes.
|
||||
* Terminates all server processes with maximum reliability.
|
||||
*/
|
||||
export async function stopAllServers(): Promise<void> {
|
||||
log.info("Stopping all servers...");
|
||||
for (const pid of Array.from(serverPIDs)) {
|
||||
try {
|
||||
terminateProcessTree(pid);
|
||||
serverPIDs.delete(pid); // Remove from tracking set after termination
|
||||
|
||||
const pidsToStop = Array.from(serverPIDs);
|
||||
if (pidsToStop.length === 0) {
|
||||
log.info("No servers to stop.");
|
||||
return;
|
||||
}
|
||||
|
||||
// First pass: attempt graceful termination
|
||||
for (const pid of pidsToStop) {
|
||||
await terminateProcessTree(pid, false);
|
||||
}
|
||||
|
||||
// Wait a moment for graceful shutdown
|
||||
await sleep(2000);
|
||||
|
||||
// Second pass: force kill any remaining processes
|
||||
for (const pid of pidsToStop) {
|
||||
await terminateProcessTree(pid, true);
|
||||
}
|
||||
|
||||
// Final verification and cleanup
|
||||
for (const pid of pidsToStop) {
|
||||
if (!isProcessRunning(pid)) {
|
||||
serverPIDs.delete(pid);
|
||||
serverLogs.delete(pid);
|
||||
} catch (error) {
|
||||
console.error(`Error stopping server with PID ${pid}:`, error);
|
||||
} else {
|
||||
log.warn(
|
||||
`Process ${pid} may still be running after termination attempts`
|
||||
);
|
||||
}
|
||||
}
|
||||
log.info("All servers stopped successfully.");
|
||||
|
||||
log.info(
|
||||
`Stopped ${pidsToStop.length - serverPIDs.size}/${pidsToStop.length} servers successfully.`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kills a process tree by PID.
|
||||
* Kills a process tree by PID with retry logic.
|
||||
*/
|
||||
function terminateProcessTree(pid: number): void {
|
||||
if (process.platform === "win32") {
|
||||
async function terminateProcessTree(
|
||||
pid: number,
|
||||
forceKill: boolean = false
|
||||
): Promise<void> {
|
||||
const maxRetries = 3;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
execSync(`taskkill /PID ${pid} /T /F`);
|
||||
log.info(
|
||||
`Terminated server process tree (PID: ${pid}) on Windows.`
|
||||
);
|
||||
if (process.platform === "win32") {
|
||||
await terminateWindows(pid, forceKill);
|
||||
} else {
|
||||
await terminateUnix(pid, forceKill);
|
||||
}
|
||||
|
||||
// Verify termination
|
||||
if (!isProcessRunning(pid)) {
|
||||
log.info(`Successfully terminated process tree (PID: ${pid})`);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to terminate process tree (PID: ${pid}):`,
|
||||
log.warn(
|
||||
`Attempt ${attempt}/${maxRetries} failed for PID ${pid}:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
process.kill(-pid, "SIGKILL");
|
||||
log.info(
|
||||
`Terminated server process tree (PID: ${pid}) on Unix-like OS.`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to terminate process tree (PID: ${pid}):`,
|
||||
error
|
||||
);
|
||||
|
||||
if (attempt < maxRetries) {
|
||||
await sleep(1000); // Wait before retry
|
||||
}
|
||||
}
|
||||
|
||||
log.error(
|
||||
`Failed to terminate process tree (PID: ${pid}) after ${maxRetries} attempts`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate process on Windows.
|
||||
*/
|
||||
async function terminateWindows(
|
||||
pid: number,
|
||||
forceKill: boolean
|
||||
): Promise<void> {
|
||||
const commands = forceKill
|
||||
? [`taskkill /PID ${pid} /T /F`]
|
||||
: [`taskkill /PID ${pid} /T`, `taskkill /PID ${pid} /T /F`];
|
||||
|
||||
for (const cmd of commands) {
|
||||
try {
|
||||
execSync(cmd, { timeout: 5000, stdio: "ignore" });
|
||||
await sleep(500); // Brief pause between commands
|
||||
} catch (error) {
|
||||
log.error(`Failed to terminate process (PID: ${pid}):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate process on Unix-like systems.
|
||||
*/
|
||||
async function terminateUnix(pid: number, forceKill: boolean): Promise<void> {
|
||||
const signals = forceKill ? ["SIGKILL"] : ["SIGTERM", "SIGKILL"];
|
||||
|
||||
for (const signal of signals) {
|
||||
try {
|
||||
// Kill process group (negative PID)
|
||||
process.kill(-pid, signal);
|
||||
await sleep(500);
|
||||
|
||||
// Also try individual process if group kill fails
|
||||
if (isProcessRunning(pid)) {
|
||||
process.kill(pid, signal);
|
||||
await sleep(500);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to terminate process (PID: ${pid}):`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a process is still running.
|
||||
*/
|
||||
function isProcessRunning(pid: number): boolean {
|
||||
try {
|
||||
// Sending signal 0 checks if process exists without killing it
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple sleep utility.
|
||||
*/
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -826,7 +952,7 @@ export const checkUrlAndOpen = async (
|
||||
|
||||
// Start polling in the background (don't await)
|
||||
pollUrl().catch((error) => {
|
||||
console.error("Error in URL polling:", error);
|
||||
log.error("Error in URL polling:", error);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -839,7 +965,7 @@ export const getConfig = async (): Promise<Record<string, any>> => {
|
||||
}
|
||||
return {};
|
||||
} catch (error) {
|
||||
console.error("Error reading config:", error);
|
||||
log.error("Error reading config:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -852,7 +978,7 @@ export const setConfig = async (config: Record<string, any>): Promise<void> => {
|
||||
JSON.stringify(config, null, 2)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error writing config:", error);
|
||||
log.error("Error writing config:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,7 +154,6 @@ html {
|
||||
.scrollbar-hidden:hover::-webkit-scrollbar-thumb {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.scrollbar-hidden::-webkit-scrollbar-thumb {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -162,3 +161,40 @@ html {
|
||||
.scrollbar-hidden::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-none::-webkit-scrollbar {
|
||||
display: none; /* for Chrome, Safari and Opera */
|
||||
}
|
||||
|
||||
.scrollbar-none::-webkit-scrollbar-corner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-none {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
--tw-border-opacity: 1;
|
||||
background-color: rgba(215, 215, 215, 0.8);
|
||||
border-color: rgba(255, 255, 255, var(--tw-border-opacity));
|
||||
border-radius: 9999px;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
/* Dark theme scrollbar styles */
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(67, 67, 67, 0.8); /* Darker color for dark theme */
|
||||
border-color: rgba(0, 0, 0, var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
height: 0.6rem;
|
||||
width: 0.4rem;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
@@ -59,8 +59,9 @@
|
||||
await window.electronAPI.resetApp();
|
||||
toast.success("App has been reset successfully.");
|
||||
|
||||
// refresh the page to apply changes
|
||||
window.location.reload();
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
toast.error("Failed to reset the app");
|
||||
}
|
||||
|
||||
@@ -11,20 +11,12 @@
|
||||
import galaxyImage from "../assets/images/galaxy.jpg";
|
||||
import greenImage from "../assets/images/green.jpg";
|
||||
import adamImage from "../assets/images/adam.jpg";
|
||||
import earthImage from "../assets/images/earth.jpg";
|
||||
import nasaImage from "../assets/images/nasa.jpg";
|
||||
import neomImage from "../assets/images/neom.jpg";
|
||||
|
||||
let { installed = $bindable() } = $props();
|
||||
|
||||
let images = [
|
||||
galaxyImage,
|
||||
greenImage,
|
||||
adamImage,
|
||||
earthImage,
|
||||
nasaImage,
|
||||
neomImage,
|
||||
];
|
||||
let images = [galaxyImage, greenImage, adamImage, nasaImage, neomImage];
|
||||
|
||||
let mounted = $state(false);
|
||||
let currentTime = Date.now();
|
||||
@@ -57,10 +49,12 @@
|
||||
(await window.electronAPI.getPythonStatus()) &&
|
||||
(await window.electronAPI.getPackageStatus())
|
||||
) {
|
||||
// Notify the user that the installation is complete
|
||||
// Start the server if it's not already running
|
||||
if (!(await window.electronAPI.getServerStatus())) {
|
||||
await window.electronAPI.startServer();
|
||||
}
|
||||
|
||||
// Notify the user that the installation is complete
|
||||
await window.electronAPI.notification(
|
||||
"Installation Complete",
|
||||
"Open WebUI is now ready to use."
|
||||
|
||||
@@ -7,19 +7,11 @@
|
||||
import galaxyImage from "../assets/images/galaxy.jpg";
|
||||
import greenImage from "../assets/images/green.jpg";
|
||||
import adamImage from "../assets/images/adam.jpg";
|
||||
import earthImage from "../assets/images/earth.jpg";
|
||||
import nasaImage from "../assets/images/nasa.jpg";
|
||||
import neomImage from "../assets/images/neom.jpg";
|
||||
import { fly } from "svelte/transition";
|
||||
|
||||
let images = [
|
||||
galaxyImage,
|
||||
greenImage,
|
||||
adamImage,
|
||||
earthImage,
|
||||
nasaImage,
|
||||
neomImage,
|
||||
];
|
||||
let images = [galaxyImage, greenImage, adamImage, nasaImage, neomImage];
|
||||
|
||||
let startTime = $state(null);
|
||||
let currentTime = $state(null);
|
||||
|
||||
Reference in New Issue
Block a user