+layout.svelte 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <script>
  2. import { io } from 'socket.io-client';
  3. import { spring } from 'svelte/motion';
  4. let loadingProgress = spring(0, {
  5. stiffness: 0.05
  6. });
  7. import { onMount, tick, setContext } from 'svelte';
  8. import {
  9. config,
  10. user,
  11. theme,
  12. WEBUI_NAME,
  13. mobile,
  14. socket,
  15. activeUserCount,
  16. USAGE_POOL
  17. } from '$lib/stores';
  18. import { goto } from '$app/navigation';
  19. import { page } from '$app/stores';
  20. import { Toaster, toast } from 'svelte-sonner';
  21. import { getBackendConfig } from '$lib/apis';
  22. import { getSessionUser } from '$lib/apis/auths';
  23. import '../tailwind.css';
  24. import '../app.css';
  25. import 'tippy.js/dist/tippy.css';
  26. import { WEBUI_BASE_URL, WEBUI_HOSTNAME } from '$lib/constants';
  27. import i18n, { initI18n, getLanguages } from '$lib/i18n';
  28. import { bestMatchingLanguage } from '$lib/utils';
  29. setContext('i18n', i18n);
  30. let loaded = false;
  31. const BREAKPOINT = 768;
  32. let wakeLock = null;
  33. onMount(async () => {
  34. theme.set(localStorage.theme);
  35. mobile.set(window.innerWidth < BREAKPOINT);
  36. const onResize = () => {
  37. if (window.innerWidth < BREAKPOINT) {
  38. mobile.set(true);
  39. } else {
  40. mobile.set(false);
  41. }
  42. };
  43. window.addEventListener('resize', onResize);
  44. const setWakeLock = async () => {
  45. try {
  46. wakeLock = await navigator.wakeLock.request('screen');
  47. } catch (err) {
  48. // The Wake Lock request has failed - usually system related, such as battery.
  49. console.log(err);
  50. }
  51. if (wakeLock) {
  52. // Add a listener to release the wake lock when the page is unloaded
  53. wakeLock.addEventListener('release', () => {
  54. // the wake lock has been released
  55. console.log('Wake Lock released');
  56. });
  57. }
  58. };
  59. if ('wakeLock' in navigator) {
  60. await setWakeLock();
  61. document.addEventListener('visibilitychange', async () => {
  62. // Re-request the wake lock if the document becomes visible
  63. if (wakeLock !== null && document.visibilityState === 'visible') {
  64. await setWakeLock();
  65. }
  66. });
  67. }
  68. let backendConfig = null;
  69. try {
  70. backendConfig = await getBackendConfig();
  71. console.log('Backend config:', backendConfig);
  72. } catch (error) {
  73. console.error('Error loading backend config:', error);
  74. }
  75. // Initialize i18n even if we didn't get a backend config,
  76. // so `/error` can show something that's not `undefined`.
  77. initI18n();
  78. if (!localStorage.locale) {
  79. const languages = await getLanguages();
  80. const browserLanguages = navigator.languages
  81. ? navigator.languages
  82. : [navigator.language || navigator.userLanguage];
  83. const lang = backendConfig.default_locale
  84. ? backendConfig.default_locale
  85. : bestMatchingLanguage(languages, browserLanguages, 'en-US');
  86. $i18n.changeLanguage(lang);
  87. }
  88. if (backendConfig) {
  89. // Save Backend Status to Store
  90. await config.set(backendConfig);
  91. await WEBUI_NAME.set(backendConfig.name);
  92. if ($config) {
  93. const _socket = io(`${WEBUI_BASE_URL}` || undefined, {
  94. reconnection: true,
  95. reconnectionDelay: 1000,
  96. reconnectionDelayMax: 5000,
  97. randomizationFactor: 0.5,
  98. path: '/ws/socket.io',
  99. auth: { token: localStorage.token }
  100. });
  101. _socket.on('connect', () => {
  102. console.log('connected');
  103. });
  104. _socket.on('reconnect_attempt', (attempt) => {
  105. console.log('reconnect_attempt', attempt);
  106. });
  107. _socket.on('reconnect_failed', () => {
  108. console.log('reconnect_failed');
  109. });
  110. _socket.on('disconnect', (reason, details) => {
  111. console.log(`Socket ${socket.id} disconnected due to ${reason}`);
  112. if (details) {
  113. console.log('Additional details:', details);
  114. }
  115. });
  116. await socket.set(_socket);
  117. _socket.on('user-count', (data) => {
  118. console.log('user-count', data);
  119. activeUserCount.set(data.count);
  120. });
  121. _socket.on('usage', (data) => {
  122. console.log('usage', data);
  123. USAGE_POOL.set(data['models']);
  124. });
  125. if (localStorage.token) {
  126. // Get Session User Info
  127. const sessionUser = await getSessionUser(localStorage.token).catch((error) => {
  128. toast.error(error);
  129. return null;
  130. });
  131. if (sessionUser) {
  132. // Save Session User to Store
  133. await user.set(sessionUser);
  134. } else {
  135. // Redirect Invalid Session User to /auth Page
  136. localStorage.removeItem('token');
  137. await goto('/auth');
  138. }
  139. } else {
  140. // Don't redirect if we're already on the auth page
  141. // Needed because we pass in tokens from OAuth logins via URL fragments
  142. if ($page.url.pathname !== '/auth') {
  143. await goto('/auth');
  144. }
  145. }
  146. }
  147. } else {
  148. // Redirect to /error when Backend Not Detected
  149. await goto(`/error`);
  150. }
  151. await tick();
  152. if (
  153. document.documentElement.classList.contains('her') &&
  154. document.getElementById('progress-bar')
  155. ) {
  156. loadingProgress.subscribe((value) => {
  157. const progressBar = document.getElementById('progress-bar');
  158. if (progressBar) {
  159. progressBar.style.width = `${value}%`;
  160. }
  161. });
  162. await loadingProgress.set(100);
  163. document.getElementById('splash-screen')?.remove();
  164. const audio = new Audio(`/audio/greeting.mp3`);
  165. const playAudio = () => {
  166. audio.play();
  167. document.removeEventListener('click', playAudio);
  168. };
  169. document.addEventListener('click', playAudio);
  170. loaded = true;
  171. } else {
  172. document.getElementById('splash-screen')?.remove();
  173. loaded = true;
  174. }
  175. return () => {
  176. window.removeEventListener('resize', onResize);
  177. };
  178. });
  179. </script>
  180. <svelte:head>
  181. <title>{$WEBUI_NAME}</title>
  182. <link crossorigin="anonymous" rel="icon" href="{WEBUI_BASE_URL}/static/favicon.png" />
  183. <!-- rosepine themes have been disabled as it's not up to date with our latest version. -->
  184. <!-- feel free to make a PR to fix if anyone wants to see it return -->
  185. <!-- <link rel="stylesheet" type="text/css" href="/themes/rosepine.css" />
  186. <link rel="stylesheet" type="text/css" href="/themes/rosepine-dawn.css" /> -->
  187. </svelte:head>
  188. {#if loaded}
  189. <slot />
  190. {/if}
  191. <Toaster richColors position="top-center" />