mirror of
https://github.com/open-webui/desktop
synced 2025-06-26 18:15:59 +00:00
refac
This commit is contained in:
parent
e91a8ab4a7
commit
c5f5681f94
66
src/main.ts
66
src/main.ts
@ -1,6 +1,8 @@
|
||||
import {
|
||||
app,
|
||||
nativeImage,
|
||||
desktopCapturer,
|
||||
session,
|
||||
Tray,
|
||||
Menu,
|
||||
MenuItem,
|
||||
@ -52,14 +54,14 @@ if (!gotTheLock) {
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
let tray: Tray | null = null;
|
||||
|
||||
let SERVER_URL = null;
|
||||
|
||||
const loadDefaultView = () => {
|
||||
// Load index.html or dev server URL
|
||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
mainWindow.loadFile(
|
||||
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
|
||||
);
|
||||
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
|
||||
}
|
||||
};
|
||||
|
||||
@ -83,15 +85,54 @@ if (!gotTheLock) {
|
||||
});
|
||||
mainWindow.setIcon(path.join(__dirname, 'assets/icon.png'));
|
||||
|
||||
// Enables navigator.mediaDevices.getUserMedia API. See https://www.electronjs.org/docs/latest/api/desktop-capturer
|
||||
session.defaultSession.setDisplayMediaRequestHandler(
|
||||
(request, callback) => {
|
||||
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
||||
// Grant access to the first screen found.
|
||||
callback({ video: sources[0], audio: 'loopback' });
|
||||
});
|
||||
},
|
||||
{ useSystemPicker: true }
|
||||
);
|
||||
|
||||
loadDefaultView();
|
||||
if (!app.isPackaged) {
|
||||
mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (validateInstallation()) {
|
||||
const serverUrl = await startServer();
|
||||
mainWindow.loadURL(serverUrl);
|
||||
}
|
||||
// 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()) {
|
||||
try {
|
||||
SERVER_URL = await startServer();
|
||||
console.log('Server URL:', SERVER_URL);
|
||||
|
||||
// Send the server URL to the renderer
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:url',
|
||||
data: SERVER_URL
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to start server:', error);
|
||||
|
||||
// Send an error message if the server fails to start
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:error',
|
||||
data: 'Failed to start the server'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// No valid installation, send fallback info
|
||||
mainWindow.webContents.send('main:data', {
|
||||
type: 'server:url',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
globalShortcut.register('Alt+CommandOrControl+O', () => {
|
||||
mainWindow?.show();
|
||||
@ -101,12 +142,7 @@ if (!gotTheLock) {
|
||||
});
|
||||
|
||||
const defaultMenu = Menu.getApplicationMenu();
|
||||
|
||||
console.log(defaultMenu);
|
||||
// Convert the default menu to a template we can modify
|
||||
let menuTemplate = defaultMenu ? defaultMenu.items.map((item) => item) : [];
|
||||
|
||||
// Add your own custom menu items
|
||||
menuTemplate.push({
|
||||
label: 'Action',
|
||||
submenu: [
|
||||
@ -119,8 +155,6 @@ if (!gotTheLock) {
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Build the updated menu and set it as the application menu
|
||||
const updatedMenu = Menu.buildFromTemplate(menuTemplate);
|
||||
Menu.setApplicationMenu(updatedMenu);
|
||||
|
||||
@ -179,6 +213,10 @@ if (!gotTheLock) {
|
||||
stopAllServers();
|
||||
});
|
||||
|
||||
ipcMain.handle('server:url', async (event) => {
|
||||
return SERVER_URL;
|
||||
});
|
||||
|
||||
ipcMain.handle('load-webui', async (event, arg) => {
|
||||
console.log(arg); // prints "ping"
|
||||
mainWindow.loadURL('http://localhost:8080');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ipcRenderer, contextBridge } from 'electron';
|
||||
import { ipcRenderer, contextBridge, desktopCapturer } from 'electron';
|
||||
|
||||
const isLocalSource = () => {
|
||||
// Check if the execution environment is local
|
||||
@ -19,8 +19,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
// Forward the message to the renderer using window.postMessage
|
||||
window.postMessage(
|
||||
{
|
||||
type: `electron:${data.type}`,
|
||||
data: data
|
||||
...data,
|
||||
type: `electron:${data.type}`
|
||||
},
|
||||
window.location.origin
|
||||
);
|
||||
@ -35,9 +35,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
|
||||
installPackage: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error(
|
||||
'Access restricted: This operation is only allowed in a local environment.'
|
||||
);
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
await ipcRenderer.invoke('install');
|
||||
@ -45,9 +43,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
|
||||
removePackage: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error(
|
||||
'Access restricted: This operation is only allowed in a local environment.'
|
||||
);
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
await ipcRenderer.invoke('remove');
|
||||
@ -55,9 +51,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
|
||||
startServer: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error(
|
||||
'Access restricted: This operation is only allowed in a local environment.'
|
||||
);
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
await ipcRenderer.invoke('server:start');
|
||||
@ -65,11 +59,13 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
|
||||
stopServer: async () => {
|
||||
if (!isLocalSource()) {
|
||||
throw new Error(
|
||||
'Access restricted: This operation is only allowed in a local environment.'
|
||||
);
|
||||
throw new Error('Access restricted: This operation is only allowed in a local environment.');
|
||||
}
|
||||
|
||||
await ipcRenderer.invoke('server:stop');
|
||||
},
|
||||
|
||||
getServerUrl: async () => {
|
||||
return await ipcRenderer.invoke('server:url');
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,7 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { serverUrl } from './lib/stores.ts';
|
||||
|
||||
import Main from './lib/components/Main.svelte';
|
||||
onMount(() => {});
|
||||
|
||||
onMount(async () => {
|
||||
window.addEventListener('message', (event) => {
|
||||
// Ensure the message is coming from a trusted origin
|
||||
if (event.origin !== window.location.origin) {
|
||||
console.warn('Received message from untrusted origin:', event.origin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the type of the message
|
||||
if (event.data && event.data.type && event.data.type.startsWith('electron:')) {
|
||||
console.log('Received message:', event.data);
|
||||
|
||||
// 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);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('Unhandled message type:', event.data.type);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!$serverUrl) {
|
||||
const url = await window.electronAPI.getServerUrl();
|
||||
serverUrl.set(url);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<main class="w-screen h-screen bg-gray-900">
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
let selected = 'home';
|
||||
</script>
|
||||
|
||||
<div class="min-w-18 bg-gray-950 flex gap-2.5 flex-col pt-9.5">
|
||||
<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">
|
||||
@ -32,7 +32,7 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-gray-900 mx-3"></div>
|
||||
<!-- <div class="border-t border-gray-900 mx-3"></div> -->
|
||||
|
||||
<!-- <div class="flex justify-center relative group">
|
||||
{#if selected === ""}
|
||||
@ -56,9 +56,9 @@
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<div class="flex justify-center relative group text-gray-400">
|
||||
<!-- <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> -->
|
||||
</div>
|
||||
|
||||
@ -1,75 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { serverUrl } from '../stores.ts';
|
||||
|
||||
import Spinner from './common/Spinner.svelte';
|
||||
import ListView from './ListView.svelte';
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full h-full relative dark:text-gray-100">
|
||||
<div class="absolute top-0 left-0 w-full h-6 bg-transparent draggable"></div>
|
||||
<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">
|
||||
<div class="my-auto flex flex-col max-w-xs w-full">
|
||||
<div class=" flex justify-center mb-3">
|
||||
<!-- <img
|
||||
src="./assets/images/splash.png"
|
||||
class=" size-24 dark:invert"
|
||||
alt="hero"
|
||||
/> -->
|
||||
{#if !$serverUrl}
|
||||
<div class="m-auto">
|
||||
<Spinner className="size-5" />
|
||||
</div>
|
||||
|
||||
<!-- <div class=" text-2xl text-gray-50 font-secondary">Install Open WebUI</div> -->
|
||||
|
||||
<div class=" text-gray-500 hover:text-white transition">
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<div>Loading...</div>
|
||||
<div>
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
class=" hover:text-white transition font-medium cursor-pointer"
|
||||
onclick={() => {
|
||||
console.log("install clicked");
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.installPackage();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
<div>Install</div>
|
||||
<div>
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
</button> -->
|
||||
|
||||
<!--
|
||||
|
||||
<button
|
||||
class=" text-gray-100 hover:text-white transition font-medium cursor-pointer"
|
||||
onclick={() => {
|
||||
console.log("start clicked");
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.startServer();
|
||||
}
|
||||
}}>Start Open WebUI</button
|
||||
>
|
||||
|
||||
<button
|
||||
class=" text-gray-100 hover:text-white transition font-medium cursor-pointer"
|
||||
onclick={() => {
|
||||
console.log("stop clicked");
|
||||
if (window?.electronAPI) {
|
||||
window.electronAPI.stopServer();
|
||||
}
|
||||
}}>Stop Open WebUI</button
|
||||
> -->
|
||||
</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>
|
||||
|
||||
|
||||
3
src/render/lib/stores.ts
Normal file
3
src/render/lib/stores.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const serverUrl = writable(null);
|
||||
Loading…
Reference in New Issue
Block a user