+layout.svelte 5.06 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script>
Timothy J. Baek's avatar
Timothy J. Baek committed
2
	import { io } from 'socket.io-client';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
4
	import { spring } from 'svelte/motion';

Timothy J. Baek's avatar
Timothy J. Baek committed
5
6
7
	let loadingProgress = spring(0, {
		stiffness: 0.05
	});
Timothy J. Baek's avatar
Timothy J. Baek committed
8

9
	import { onMount, tick, setContext } from 'svelte';
10
11
12
13
14
15
16
17
18
19
	import {
		config,
		user,
		theme,
		WEBUI_NAME,
		mobile,
		socket,
		activeUserCount,
		USAGE_POOL
	} from '$lib/stores';
20
	import { goto } from '$app/navigation';
21
	import { page } from '$app/stores';
Jannik Streidl's avatar
Jannik Streidl committed
22
	import { Toaster, toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
23

Timothy J. Baek's avatar
Timothy J. Baek committed
24
25
26
	import { getBackendConfig } from '$lib/apis';
	import { getSessionUser } from '$lib/apis/auths';

Timothy J. Baek's avatar
Timothy J. Baek committed
27
	import '../tailwind.css';
Timothy J. Baek's avatar
Timothy J. Baek committed
28
29
	import '../app.css';

Timothy J. Baek's avatar
Timothy J. Baek committed
30
	import 'tippy.js/dist/tippy.css';
Timothy J. Baek's avatar
Timothy J. Baek committed
31

Timothy J. Baek's avatar
Timothy J. Baek committed
32
	import { WEBUI_BASE_URL, WEBUI_HOSTNAME } from '$lib/constants';
33
	import i18n, { initI18n, getLanguages } from '$lib/i18n';
34
35

	setContext('i18n', i18n);
Timothy J. Baek's avatar
Timothy J. Baek committed
36

37
	let loaded = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
38
	const BREAKPOINT = 768;
39

Timothy J. Baek's avatar
Timothy J. Baek committed
40
41
	let wakeLock = null;

42
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
43
		theme.set(localStorage.theme);
44
45
46
47
48
49
50
51
52
53
54
55

		mobile.set(window.innerWidth < BREAKPOINT);
		const onResize = () => {
			if (window.innerWidth < BREAKPOINT) {
				mobile.set(true);
			} else {
				mobile.set(false);
			}
		};

		window.addEventListener('resize', onResize);

Timothy J. Baek's avatar
Timothy J. Baek committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
		const setWakeLock = async () => {
			try {
				wakeLock = await navigator.wakeLock.request('screen');
			} catch (err) {
				// The Wake Lock request has failed - usually system related, such as battery.
				console.log(err);
			}

			wakeLock.addEventListener('release', () => {
				// the wake lock has been released
				console.log('Wake Lock released');
			});
		};

		if ('wakeLock' in navigator) {
			await setWakeLock();

			document.addEventListener('visibilitychange', async () => {
				// Re-request the wake lock if the document becomes visible
				if (wakeLock !== null && document.visibilityState === 'visible') {
					await setWakeLock();
				}
			});
		}

Aarni Koskela's avatar
Aarni Koskela committed
81
82
83
		let backendConfig = null;
		try {
			backendConfig = await getBackendConfig();
84
			console.log('Backend config:', backendConfig);
Aarni Koskela's avatar
Aarni Koskela committed
85
		} catch (error) {
86
			console.error('Error loading backend config:', error);
Aarni Koskela's avatar
Aarni Koskela committed
87
88
89
		}
		// Initialize i18n even if we didn't get a backend config,
		// so `/error` can show something that's not `undefined`.
90
91
92
93
94
95
96
97

		const languages = await getLanguages();

		const browserLanguage = navigator.languages
			? navigator.languages[0]
			: navigator.language || navigator.userLanguage;

		initI18n(languages.includes(browserLanguage) ? browserLanguage : backendConfig?.default_locale);
98

Timothy J. Baek's avatar
Timothy J. Baek committed
99
		if (backendConfig) {
Timothy J. Baek's avatar
Timothy J. Baek committed
100
			// Save Backend Status to Store
Timothy J. Baek's avatar
Timothy J. Baek committed
101
			await config.set(backendConfig);
102
			await WEBUI_NAME.set(backendConfig.name);
103

104
			if ($config) {
Timothy J. Baek's avatar
Timothy J. Baek committed
105
106
107
108
109
110
111
112
113
				const _socket = io(`${WEBUI_BASE_URL}`, {
					path: '/ws/socket.io',
					auth: { token: localStorage.token }
				});

				_socket.on('connect', () => {
					console.log('connected');
				});

Timothy J. Baek's avatar
Timothy J. Baek committed
114
115
116
117
118
119
				await socket.set(_socket);

				_socket.on('user-count', (data) => {
					console.log('user-count', data);
					activeUserCount.set(data.count);
				});
Timothy J. Baek's avatar
Timothy J. Baek committed
120

121
122
123
124
125
				_socket.on('usage', (data) => {
					console.log('usage', data);
					USAGE_POOL.set(data['models']);
				});

126
				if (localStorage.token) {
127
					// Get Session User Info
Timothy J. Baek's avatar
Timothy J. Baek committed
128
129
130
131
					const sessionUser = await getSessionUser(localStorage.token).catch((error) => {
						toast.error(error);
						return null;
					});
132

133
					if (sessionUser) {
Timothy J. Baek's avatar
Timothy J. Baek committed
134
						// Save Session User to Store
135
						await user.set(sessionUser);
Timothy J. Baek's avatar
Timothy J. Baek committed
136
					} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
137
						// Redirect Invalid Session User to /auth Page
Timothy J. Baek's avatar
Timothy J. Baek committed
138
139
140
						localStorage.removeItem('token');
						await goto('/auth');
					}
141
				} else {
142
143
144
145
146
					// Don't redirect if we're already on the auth page
					// Needed because we pass in tokens from OAuth logins via URL fragments
					if ($page.url.pathname !== '/auth') {
						await goto('/auth');
					}
147
148
				}
			}
149
		} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
150
			// Redirect to /error when Backend Not Detected
151
			await goto(`/error`);
152
153
154
		}

		await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
155

Timothy J. Baek's avatar
Timothy J. Baek committed
156
157
158
159
160
		if (
			document.documentElement.classList.contains('her') &&
			document.getElementById('progress-bar')
		) {
			loadingProgress.subscribe((value) => {
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
161
162
163
				const progressBar = document.getElementById('progress-bar');

				if (progressBar) {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
164
					progressBar.style.width = `${value}%`;
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
165
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
			});

			await loadingProgress.set(100);

			document.getElementById('splash-screen')?.remove();

			const audio = new Audio(`/audio/greeting.mp3`);
			const playAudio = () => {
				audio.play();
				document.removeEventListener('click', playAudio);
			};

			document.addEventListener('click', playAudio);

			loaded = true;
		} else {
			document.getElementById('splash-screen')?.remove();
			loaded = true;
		}
185
186
187
188

		return () => {
			window.removeEventListener('resize', onResize);
		};
189
	});
Timothy J. Baek's avatar
Timothy J. Baek committed
190
191
192
</script>

<svelte:head>
193
	<title>{$WEBUI_NAME}</title>
Timothy J. Baek's avatar
Timothy J. Baek committed
194
	<link crossorigin="anonymous" rel="icon" href="{WEBUI_BASE_URL}/static/favicon.png" />
Timothy J. Baek's avatar
Timothy J. Baek committed
195

Timothy J. Baek's avatar
Timothy J. Baek committed
196
197
	<!-- rosepine themes have been disabled as it's not up to date with our latest version. -->
	<!-- feel free to make a PR to fix if anyone wants to see it return -->
198
199
	<!-- <link rel="stylesheet" type="text/css" href="/themes/rosepine.css" />
	<link rel="stylesheet" type="text/css" href="/themes/rosepine-dawn.css" /> -->
Timothy J. Baek's avatar
Timothy J. Baek committed
200
</svelte:head>
201

202
{#if loaded}
203
204
	<slot />
{/if}
205

Timothy J. Baek's avatar
Timothy J. Baek committed
206
<Toaster richColors position="top-center" />