mirror of
https://github.com/open-webui/desktop
synced 2025-06-26 18:15:59 +00:00
refac: styling
This commit is contained in:
parent
37cdf72959
commit
37e1b6bd1d
BIN
public/assets/images/green.jpg
Normal file
BIN
public/assets/images/green.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 MiB |
34
src/main.ts
34
src/main.ts
@ -8,6 +8,7 @@ import {
|
||||
MenuItem,
|
||||
BrowserWindow,
|
||||
globalShortcut,
|
||||
Notification,
|
||||
ipcMain
|
||||
} from 'electron';
|
||||
import path from 'path';
|
||||
@ -104,17 +105,27 @@ if (!gotTheLock) {
|
||||
// Wait for the renderer to finish loading
|
||||
mainWindow.webContents.once('did-finish-load', async () => {
|
||||
console.log('Renderer finished loading');
|
||||
});
|
||||
|
||||
// Check installation and start the server
|
||||
if (validateInstallation()) {
|
||||
if (await validateInstallation()) {
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'install:status',
|
||||
data: true
|
||||
});
|
||||
|
||||
try {
|
||||
SERVER_URL = await startServer();
|
||||
mainWindow.loadURL(SERVER_URL);
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
}
|
||||
} else {
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'install:status',
|
||||
data: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
globalShortcut.register('Alt+CommandOrControl+O', () => {
|
||||
mainWindow?.show();
|
||||
@ -134,6 +145,12 @@ if (!gotTheLock) {
|
||||
click: () => {
|
||||
loadDefaultView();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Reset',
|
||||
click: () => {
|
||||
removePackage();
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
@ -178,6 +195,10 @@ if (!gotTheLock) {
|
||||
installPackage();
|
||||
});
|
||||
|
||||
ipcMain.handle('install:status', async (event) => {
|
||||
return await validateInstallation();
|
||||
});
|
||||
|
||||
ipcMain.handle('remove', async (event) => {
|
||||
console.log('Resetting package...');
|
||||
removePackage();
|
||||
@ -199,6 +220,15 @@ if (!gotTheLock) {
|
||||
return SERVER_URL;
|
||||
});
|
||||
|
||||
ipcMain.handle('notification', async (event, { title, body }) => {
|
||||
console.log('Received notification:', title, body);
|
||||
const notification = new Notification({
|
||||
title: title,
|
||||
body: body
|
||||
});
|
||||
notification.show();
|
||||
});
|
||||
|
||||
ipcMain.handle('load-webui', async (event, arg) => {
|
||||
console.log(arg); // prints "ping"
|
||||
mainWindow.loadURL('http://localhost:8080');
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ipcRenderer, contextBridge, desktopCapturer } from 'electron';
|
||||
import { ipcRenderer, contextBridge } from 'electron';
|
||||
|
||||
const isLocalSource = () => {
|
||||
// Check if the execution environment is local
|
||||
@ -41,6 +41,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
await ipcRenderer.invoke('install');
|
||||
},
|
||||
|
||||
getInstallStatus: async () => {
|
||||
return await ipcRenderer.invoke('install:status');
|
||||
},
|
||||
|
||||
removePackage: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
@ -67,5 +71,9 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
|
||||
getServerUrl: async () => {
|
||||
return await ipcRenderer.invoke('server:url');
|
||||
},
|
||||
|
||||
notification: async (title: string, body: string) => {
|
||||
await ipcRenderer.invoke('notification', { title, body });
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { serverUrl } from './lib/stores.ts';
|
||||
import { installStatus } from './lib/stores';
|
||||
|
||||
import Main from './lib/components/Main.svelte';
|
||||
|
||||
@ -18,10 +18,10 @@
|
||||
|
||||
// Perform actions based on the `type` or the `data`
|
||||
switch (event.data.type) {
|
||||
case 'electron:server:url':
|
||||
console.log('Setting server URL:', event.data.data);
|
||||
// Set the server URL
|
||||
serverUrl.set(event.data.data);
|
||||
case 'electron:install:status':
|
||||
console.log('Install status:', event.data.data);
|
||||
installStatus.set(event.data.data);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -30,9 +30,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (!$serverUrl) {
|
||||
const url = await window.electronAPI.getServerUrl();
|
||||
serverUrl.set(url);
|
||||
if (window.electronAPI) {
|
||||
installStatus.set(await window.electronAPI.getInstallStatus());
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -16,6 +16,23 @@
|
||||
font-family: 'InstrumentSerif', sans-serif;
|
||||
}
|
||||
|
||||
.font-system {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
'Noto Sans',
|
||||
sans-serif,
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: 'Archivo';
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Tooltip from './common/Tooltip.svelte';
|
||||
import Plus from './icons/Plus.svelte';
|
||||
|
||||
let selected = 'home';
|
||||
</script>
|
||||
|
||||
<div class="min-w-18 bg-black flex gap-2.5 flex-col pt-9.5">
|
||||
<div class="flex justify-center relative">
|
||||
{#if selected === 'home'}
|
||||
<div class="absolute top-0 left-0 flex h-full">
|
||||
<div class="my-auto rounded-r-lg w-1 h-8 bg-white"></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Tooltip content="Home" placement="right">
|
||||
<button
|
||||
class=" cursor-pointer bg-gray-850 {selected === 'home'
|
||||
? 'rounded-2xl'
|
||||
: 'rounded-full'}"
|
||||
onclick={() => {
|
||||
selected = 'home';
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="./assets/images/splash.png"
|
||||
class="size-11 dark:invert p-1"
|
||||
alt="logo"
|
||||
draggable="false"
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<!-- <div class="border-t border-gray-900 mx-3"></div> -->
|
||||
|
||||
<!-- <div class="flex justify-center relative group">
|
||||
{#if selected === ""}
|
||||
<div class="absolute top-0 left-0 flex h-full">
|
||||
<div class="my-auto rounded-r-lg w-1 h-8 bg-white"></div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class=" cursor-pointer bg-transparent"
|
||||
onclick={() => {
|
||||
selected = "";
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="./assets/images/adam.jpg"
|
||||
class="size-11 {selected === '' ? 'rounded-2xl' : 'rounded-full'}"
|
||||
alt="logo"
|
||||
draggable="false"
|
||||
/>
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="flex justify-center relative group text-gray-400">
|
||||
<button class=" cursor-pointer p-2" onclick={() => {}}>
|
||||
<Plus className="size-5" strokeWidth="2" />
|
||||
</button>
|
||||
</div> -->
|
||||
</div>
|
@ -1,31 +1,82 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { serverUrl } from '../stores.ts';
|
||||
import { installStatus } from '../stores';
|
||||
|
||||
import Spinner from './common/Spinner.svelte';
|
||||
import ListView from './ListView.svelte';
|
||||
import ArrowRightCircle from './icons/ArrowRightCircle.svelte';
|
||||
|
||||
const continueHandler = async () => {
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.installPackage();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full h-full relative dark:text-gray-100">
|
||||
{#if $installStatus === null}
|
||||
<div class="flex flex-row w-full h-full relative dark:text-gray-100">
|
||||
<div class="absolute top-0 left-0 w-full h-7 bg-transparent draggable"></div>
|
||||
|
||||
<ListView />
|
||||
|
||||
<div class="flex-1 w-full flex justify-center">
|
||||
{#if !$serverUrl}
|
||||
<div class="flex-1 w-full flex justify-center relative">
|
||||
<div class="m-auto">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
{:else}
|
||||
<iframe
|
||||
src={$serverUrl}
|
||||
class="w-full h-full"
|
||||
allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; display-capture; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; navigation-override; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-script; sync-xhr; usb; screen-wake-lock; web-share; unoptimized-images; unsized-media; xr-spatial-tracking"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if $installStatus === false}
|
||||
<div class="flex flex-row w-full h-full relative dark:text-gray-100">
|
||||
<div class="absolute top-0 left-0 w-full h-7 bg-transparent draggable"></div>
|
||||
|
||||
<div class="fixed right-0 m-10 z-50">
|
||||
<div class="flex space-x-2">
|
||||
<div class=" self-center">
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src="./assets/images/splash.png"
|
||||
class=" w-6 rounded-full dark:invert"
|
||||
alt="logo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="image w-full h-full absolute top-0 left-0 bg-cover bg-center transition-opacity duration-1000"
|
||||
style="opacity: 1; background-image: url('./assets/images/green.jpg')"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="w-full h-full absolute top-0 left-0 bg-gradient-to-t from-20% from-black to-transparent"
|
||||
></div>
|
||||
|
||||
<div class="w-full h-full absolute top-0 left-0 backdrop-blur-sm bg-black/50"></div>
|
||||
|
||||
<div class="flex-1 w-full flex justify-center relative">
|
||||
<div class="m-auto flex flex-col justify-center text-center max-w-md">
|
||||
<div class=" font-medium text-5xl text-center mb-4 font-secondary">Open WebUI</div>
|
||||
|
||||
<div class=" text-sm text-center mb-3">To install Open WebUI, click Continue.</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 pb-10">
|
||||
<div class="flex justify-center mt-8">
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<button
|
||||
class="relative z-20 flex p-1 rounded-full bg-white/5 hover:bg-white/10 transition font-medium text-sm cursor-pointer"
|
||||
on:click={() => {
|
||||
continueHandler();
|
||||
}}
|
||||
>
|
||||
<ArrowRightCircle className="size-6" />
|
||||
</button>
|
||||
<div class="mt-1.5 font-primary text-base font-medium">
|
||||
{`Continue`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.draggable {
|
||||
|
39
src/render/lib/components/common/SlideShow.svelte
Normal file
39
src/render/lib/components/common/SlideShow.svelte
Normal file
@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let imageUrls = [
|
||||
'./assets/images/adam.jpg',
|
||||
'./assets/images/galaxy.jpg',
|
||||
'./assets/images/earth.jpg',
|
||||
'./assets/images/space.jpg'
|
||||
];
|
||||
export let duration = 5000;
|
||||
let selectedImageIdx = 0;
|
||||
|
||||
onMount(() => {
|
||||
setInterval(() => {
|
||||
selectedImageIdx = (selectedImageIdx + 1) % (imageUrls.length - 1);
|
||||
}, duration);
|
||||
});
|
||||
</script>
|
||||
|
||||
{#each imageUrls as imageUrl, idx (idx)}
|
||||
<div
|
||||
class="image w-full h-full absolute top-0 left-0 bg-cover bg-center transition-opacity duration-1000"
|
||||
style="opacity: {selectedImageIdx === idx ? 1 : 0}; background-image: url('{imageUrl}')"
|
||||
></div>
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
.image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-position: center; /* Center the background images */
|
||||
transition: opacity 1s ease-in-out; /* Smooth fade effect */
|
||||
opacity: 0; /* Make images initially not visible */
|
||||
}
|
||||
</style>
|
19
src/render/lib/components/icons/ArrowRightCircle.svelte
Normal file
19
src/render/lib/components/icons/ArrowRightCircle.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let className = 'size-4';
|
||||
export let strokeWidth = '1.5';
|
||||
</script>
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width={strokeWidth}
|
||||
stroke="currentColor"
|
||||
class={className}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m12.75 15 3-3m0 0-3-3m3 3h-7.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
@ -1,3 +1,3 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const serverUrl = writable(null);
|
||||
export const installStatus = writable(null);
|
||||
|
@ -269,11 +269,12 @@ export async function installPackage(installationPath?: string) {
|
||||
}
|
||||
|
||||
export async function removePackage(installationPath?: string) {
|
||||
await stopAllServers();
|
||||
installationPath = installationPath || getBundledPythonInstallationPath();
|
||||
|
||||
// remove the python env entirely
|
||||
if (fs.existsSync(installationPath)) {
|
||||
fs.rmdirSync(installationPath, { recursive: true });
|
||||
fs.rmSync(installationPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,6 +294,7 @@ export async function validateInstallation(installationPath?: string): Promise<b
|
||||
if (!fs.existsSync(pythonPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const checkCommand =
|
||||
process.platform === 'win32'
|
||||
@ -315,7 +317,7 @@ const serverPIDs: Set<number> = new Set();
|
||||
export async function startServer(installationPath?: string, port?: number): Promise<string> {
|
||||
installationPath = path.normalize(installationPath || getBundledPythonInstallationPath());
|
||||
|
||||
if (!validateInstallation(installationPath)) {
|
||||
if (!(await validateInstallation(installationPath))) {
|
||||
console.error('Failed to validate installation');
|
||||
return;
|
||||
}
|
||||
@ -373,9 +375,7 @@ export async function startServer(installationPath?: string, port?: number): Pro
|
||||
serverCrashed = true;
|
||||
if (!detectedURL) {
|
||||
reject(
|
||||
new Error(
|
||||
`Process exited unexpectedly with code ${code}. No server URL detected.`
|
||||
)
|
||||
new Error(`Process exited unexpectedly with code ${code}. No server URL detected.`)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user