| <script> |
| import { io } from 'socket.io-client'; |
| import { spring } from 'svelte/motion'; |
| |
| let loadingProgress = spring(0, { |
| stiffness: 0.05 |
| }); |
| |
| import { onMount, tick, setContext } from 'svelte'; |
| import { |
| config, |
| user, |
| theme, |
| WEBUI_NAME, |
| mobile, |
| socket, |
| activeUserCount, |
| USAGE_POOL |
| } from '$lib/stores'; |
| import { goto } from '$app/navigation'; |
| import { page } from '$app/stores'; |
| import { Toaster, toast } from 'svelte-sonner'; |
| |
| import { getBackendConfig } from '$lib/apis'; |
| import { getSessionUser } from '$lib/apis/auths'; |
| |
| import '../tailwind.css'; |
| import '../app.css'; |
| |
| import 'tippy.js/dist/tippy.css'; |
| |
| import { WEBUI_BASE_URL, WEBUI_HOSTNAME } from '$lib/constants'; |
| import i18n, { initI18n, getLanguages } from '$lib/i18n'; |
| import { bestMatchingLanguage } from '$lib/utils'; |
| |
| setContext('i18n', i18n); |
| |
| let loaded = false; |
| const BREAKPOINT = 768; |
| |
| const setupSocket = () => { |
| const _socket = io(`${WEBUI_BASE_URL}` || undefined, { |
| reconnection: true, |
| reconnectionDelay: 1000, |
| reconnectionDelayMax: 5000, |
| randomizationFactor: 0.5, |
| path: '/ws/socket.io', |
| auth: { token: localStorage.token } |
| }); |
| |
| socket.set(_socket); |
| |
| _socket.on('connect_error', (err) => { |
| console.log('connect_error', err); |
| }); |
| |
| _socket.on('connect', () => { |
| console.log('connected', _socket.id); |
| }); |
| |
| _socket.on('reconnect_attempt', (attempt) => { |
| console.log('reconnect_attempt', attempt); |
| }); |
| |
| _socket.on('reconnect_failed', () => { |
| console.log('reconnect_failed'); |
| }); |
| |
| _socket.on('disconnect', (reason, details) => { |
| console.log(`Socket ${_socket.id} disconnected due to ${reason}`); |
| if (details) { |
| console.log('Additional details:', details); |
| } |
| }); |
| |
| _socket.on('user-count', (data) => { |
| console.log('user-count', data); |
| activeUserCount.set(data.count); |
| }); |
| |
| _socket.on('usage', (data) => { |
| console.log('usage', data); |
| USAGE_POOL.set(data['models']); |
| }); |
| }; |
| |
| onMount(async () => { |
| theme.set(localStorage.theme); |
| |
| mobile.set(window.innerWidth < BREAKPOINT); |
| const onResize = () => { |
| if (window.innerWidth < BREAKPOINT) { |
| mobile.set(true); |
| } else { |
| mobile.set(false); |
| } |
| }; |
| |
| window.addEventListener('resize', onResize); |
| |
| let backendConfig = null; |
| try { |
| backendConfig = await getBackendConfig(); |
| console.log('Backend config:', backendConfig); |
| } catch (error) { |
| console.error('Error loading backend config:', error); |
| } |
| |
| |
| |
| initI18n(); |
| if (!localStorage.locale) { |
| const languages = await getLanguages(); |
| const browserLanguages = navigator.languages |
| ? navigator.languages |
| : [navigator.language || navigator.userLanguage]; |
| const lang = backendConfig.default_locale |
| ? backendConfig.default_locale |
| : bestMatchingLanguage(languages, browserLanguages, 'en-US'); |
| $i18n.changeLanguage(lang); |
| } |
| |
| if (backendConfig) { |
| // Save Backend Status to Store |
| await config.set(backendConfig); |
| await WEBUI_NAME.set(backendConfig.name); |
| |
| if ($config) { |
| setupSocket(); |
| |
| if (localStorage.token) { |
| // Get Session User Info |
| const sessionUser = await getSessionUser(localStorage.token).catch((error) => { |
| toast.error(error); |
| return null; |
| }); |
| |
| if (sessionUser) { |
| // Save Session User to Store |
| await user.set(sessionUser); |
| await config.set(await getBackendConfig()); |
| } else { |
| // Redirect Invalid Session User to /auth Page |
| localStorage.removeItem('token'); |
| await goto('/auth'); |
| } |
| } else { |
| // 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'); |
| } |
| } |
| } |
| } else { |
| // Redirect to /error when Backend Not Detected |
| await goto(`/error`); |
| } |
| |
| await tick(); |
| |
| if ( |
| document.documentElement.classList.contains('her') && |
| document.getElementById('progress-bar') |
| ) { |
| loadingProgress.subscribe((value) => { |
| const progressBar = document.getElementById('progress-bar'); |
| |
| if (progressBar) { |
| progressBar.style.width = `${value}%`; |
| } |
| }); |
| |
| 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; |
| } |
| |
| return () => { |
| window.removeEventListener('resize', onResize); |
| }; |
| }); |
| </script> |
|
|
| <svelte:head> |
| <title>{$WEBUI_NAME}</title> |
| <link crossorigin="anonymous" rel="icon" href="{WEBUI_BASE_URL}/static/favicon.png" /> |
| |
| |
| |
| |
| |
| </svelte:head> |
|
|
| {#if loaded} |
| <slot /> |
| {/if} |
|
|
| <Toaster |
| theme={$theme.includes('dark') |
| ? 'dark' |
| : $theme === 'system' |
| ? window.matchMedia('(prefers-color-scheme: dark)').matches |
| ? 'dark' |
| : 'light' |
| : 'light'} |
| richColors |
| position="top-center" |
| /> |
|
|