SettingsModal.svelte 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. <script lang="ts">
  2. import { getContext, tick } from 'svelte';
  3. import { toast } from 'svelte-sonner';
  4. import { models, settings, user } from '$lib/stores';
  5. import { updateUserSettings } from '$lib/apis/users';
  6. import { getModels as _getModels } from '$lib/apis';
  7. import { goto } from '$app/navigation';
  8. import Modal from '../common/Modal.svelte';
  9. import Account from './Settings/Account.svelte';
  10. import About from './Settings/About.svelte';
  11. import General from './Settings/General.svelte';
  12. import Interface from './Settings/Interface.svelte';
  13. import Audio from './Settings/Audio.svelte';
  14. import Chats from './Settings/Chats.svelte';
  15. import User from '../icons/User.svelte';
  16. import Personalization from './Settings/Personalization.svelte';
  17. import SearchInput from '../layout/Sidebar/SearchInput.svelte';
  18. import Search from '../icons/Search.svelte';
  19. const i18n = getContext('i18n');
  20. export let show = false;
  21. interface SettingsTab {
  22. id: string;
  23. title: string;
  24. keywords: string[];
  25. }
  26. const searchData: SettingsTab[] = [
  27. {
  28. id: 'general',
  29. title: 'General',
  30. keywords: [
  31. 'general',
  32. 'theme',
  33. 'language',
  34. 'notifications',
  35. 'system',
  36. 'systemprompt',
  37. 'prompt',
  38. 'advanced',
  39. 'settings',
  40. 'defaultsettings',
  41. 'configuration',
  42. 'systemsettings',
  43. 'notificationsettings',
  44. 'systempromptconfig',
  45. 'languageoptions',
  46. 'defaultparameters',
  47. 'systemparameters'
  48. ]
  49. },
  50. {
  51. id: 'interface',
  52. title: 'Interface',
  53. keywords: [
  54. 'defaultmodel',
  55. 'selectmodel',
  56. 'ui',
  57. 'userinterface',
  58. 'display',
  59. 'layout',
  60. 'design',
  61. 'landingpage',
  62. 'landingpagemode',
  63. 'default',
  64. 'chat',
  65. 'chatbubble',
  66. 'chatui',
  67. 'username',
  68. 'showusername',
  69. 'displayusername',
  70. 'widescreen',
  71. 'widescreenmode',
  72. 'fullscreen',
  73. 'expandmode',
  74. 'chatdirection',
  75. 'lefttoright',
  76. 'ltr',
  77. 'righttoleft',
  78. 'rtl',
  79. 'notifications',
  80. 'toast',
  81. 'toastnotifications',
  82. 'largechunks',
  83. 'streamlargechunks',
  84. 'scroll',
  85. 'scrollonbranchchange',
  86. 'scrollbehavior',
  87. 'richtext',
  88. 'richtextinput',
  89. 'background',
  90. 'chatbackground',
  91. 'chatbackgroundimage',
  92. 'backgroundimage',
  93. 'uploadbackground',
  94. 'resetbackground',
  95. 'titleautogen',
  96. 'titleautogeneration',
  97. 'autotitle',
  98. 'chattags',
  99. 'autochattags',
  100. 'responseautocopy',
  101. 'clipboard',
  102. 'location',
  103. 'userlocation',
  104. 'userlocationaccess',
  105. 'haptic',
  106. 'hapticfeedback',
  107. 'vibration',
  108. 'voice',
  109. 'voicecontrol',
  110. 'voiceinterruption',
  111. 'call',
  112. 'emojis',
  113. 'displayemoji',
  114. 'save',
  115. 'interfaceoptions',
  116. 'interfacecustomization',
  117. 'alwaysonwebsearch'
  118. ]
  119. },
  120. {
  121. id: 'personalization',
  122. title: 'Personalization',
  123. keywords: [
  124. 'personalization',
  125. 'memory',
  126. 'personalize',
  127. 'preferences',
  128. 'profile',
  129. 'personalsettings',
  130. 'customsettings',
  131. 'userpreferences',
  132. 'accountpreferences'
  133. ]
  134. },
  135. {
  136. id: 'audio',
  137. title: 'Audio',
  138. keywords: [
  139. 'audio',
  140. 'sound',
  141. 'soundsettings',
  142. 'audiocontrol',
  143. 'volume',
  144. 'speech',
  145. 'speechrecognition',
  146. 'stt',
  147. 'speechtotext',
  148. 'tts',
  149. 'texttospeech',
  150. 'playback',
  151. 'playbackspeed',
  152. 'voiceplayback',
  153. 'speechplayback',
  154. 'audiooutput',
  155. 'speechengine',
  156. 'voicecontrol',
  157. 'audioplayback',
  158. 'transcription',
  159. 'autotranscribe',
  160. 'autosend',
  161. 'speechsettings',
  162. 'audiovoice',
  163. 'voiceoptions',
  164. 'setvoice',
  165. 'nonlocalvoices',
  166. 'savesettings',
  167. 'audioconfig',
  168. 'speechconfig',
  169. 'voicerecognition',
  170. 'speechsynthesis',
  171. 'speechmode',
  172. 'voicespeed',
  173. 'speechrate',
  174. 'speechspeed',
  175. 'audioinput',
  176. 'audiofeatures',
  177. 'voicemodes'
  178. ]
  179. },
  180. {
  181. id: 'chats',
  182. title: 'Chats',
  183. keywords: [
  184. 'chat',
  185. 'messages',
  186. 'conversations',
  187. 'chatsettings',
  188. 'history',
  189. 'chathistory',
  190. 'messagehistory',
  191. 'messagearchive',
  192. 'convo',
  193. 'chats',
  194. 'conversationhistory',
  195. 'exportmessages',
  196. 'chatactivity'
  197. ]
  198. },
  199. {
  200. id: 'account',
  201. title: 'Account',
  202. keywords: [
  203. 'account',
  204. 'profile',
  205. 'security',
  206. 'privacy',
  207. 'settings',
  208. 'login',
  209. 'useraccount',
  210. 'userdata',
  211. 'api',
  212. 'apikey',
  213. 'userprofile',
  214. 'profiledetails',
  215. 'accountsettings',
  216. 'accountpreferences',
  217. 'securitysettings',
  218. 'privacysettings'
  219. ]
  220. },
  221. {
  222. id: 'admin',
  223. title: 'Admin',
  224. keywords: [
  225. 'admin',
  226. 'administrator',
  227. 'adminsettings',
  228. 'adminpanel',
  229. 'systemadmin',
  230. 'administratoraccess',
  231. 'systemcontrol',
  232. 'manage',
  233. 'management',
  234. 'admincontrols',
  235. 'adminfeatures',
  236. 'usercontrol',
  237. 'arenamodel',
  238. 'evaluations',
  239. 'websearch',
  240. 'database',
  241. 'pipelines',
  242. 'images',
  243. 'audio',
  244. 'documents',
  245. 'rag',
  246. 'models',
  247. 'ollama',
  248. 'openai',
  249. 'users'
  250. ]
  251. },
  252. {
  253. id: 'about',
  254. title: 'About',
  255. keywords: [
  256. 'about',
  257. 'info',
  258. 'information',
  259. 'version',
  260. 'documentation',
  261. 'help',
  262. 'support',
  263. 'details',
  264. 'aboutus',
  265. 'softwareinfo',
  266. 'timothyjaeryangbaek',
  267. 'openwebui',
  268. 'release',
  269. 'updates',
  270. 'updateinfo',
  271. 'versioninfo',
  272. 'aboutapp',
  273. 'terms',
  274. 'termsandconditions',
  275. 'contact',
  276. 'aboutpage'
  277. ]
  278. }
  279. ];
  280. let search = '';
  281. let visibleTabs = searchData.map((tab) => tab.id);
  282. let searchDebounceTimeout;
  283. const searchSettings = (query: string): string[] => {
  284. const lowerCaseQuery = query.toLowerCase().trim();
  285. return searchData
  286. .filter(
  287. (tab) =>
  288. tab.title.toLowerCase().includes(lowerCaseQuery) ||
  289. tab.keywords.some((keyword) => keyword.includes(lowerCaseQuery))
  290. )
  291. .map((tab) => tab.id);
  292. };
  293. const searchDebounceHandler = () => {
  294. clearTimeout(searchDebounceTimeout);
  295. searchDebounceTimeout = setTimeout(() => {
  296. visibleTabs = searchSettings(search);
  297. if (visibleTabs.length > 0 && !visibleTabs.includes(selectedTab)) {
  298. selectedTab = visibleTabs[0];
  299. }
  300. }, 100);
  301. };
  302. const saveSettings = async (updated) => {
  303. console.log(updated);
  304. await settings.set({ ...$settings, ...updated });
  305. await models.set(await getModels());
  306. await updateUserSettings(localStorage.token, { ui: $settings });
  307. };
  308. const getModels = async () => {
  309. return await _getModels(localStorage.token);
  310. };
  311. let selectedTab = 'general';
  312. // Function to handle sideways scrolling
  313. const scrollHandler = (event) => {
  314. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  315. if (settingsTabsContainer) {
  316. event.preventDefault(); // Prevent default vertical scrolling
  317. settingsTabsContainer.scrollLeft += event.deltaY; // Scroll sideways
  318. }
  319. };
  320. const addScrollListener = async () => {
  321. await tick();
  322. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  323. if (settingsTabsContainer) {
  324. settingsTabsContainer.addEventListener('wheel', scrollHandler);
  325. }
  326. };
  327. const removeScrollListener = async () => {
  328. await tick();
  329. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  330. if (settingsTabsContainer) {
  331. settingsTabsContainer.removeEventListener('wheel', scrollHandler);
  332. }
  333. };
  334. $: if (show) {
  335. addScrollListener();
  336. } else {
  337. removeScrollListener();
  338. }
  339. </script>
  340. <Modal size="xl" bind:show>
  341. <div class="text-gray-700 dark:text-gray-100">
  342. <div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
  343. <div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
  344. <button
  345. class="self-center"
  346. on:click={() => {
  347. show = false;
  348. }}
  349. >
  350. <svg
  351. xmlns="http://www.w3.org/2000/svg"
  352. viewBox="0 0 20 20"
  353. fill="currentColor"
  354. class="w-5 h-5"
  355. >
  356. <path
  357. d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
  358. />
  359. </svg>
  360. </button>
  361. </div>
  362. <div class="flex flex-col md:flex-row w-full px-4 pt-1 pb-4 md:space-x-4">
  363. <div
  364. id="settings-tabs-container"
  365. class="tabs flex flex-row overflow-x-auto gap-2.5 md:gap-1 md:flex-col flex-1 md:flex-none md:w-40 dark:text-gray-200 text-sm font-medium text-left mb-1 md:mb-0 -translate-y-1"
  366. >
  367. <div class="hidden md:flex w-full rounded-xl -mb-1 px-0.5 gap-2" id="settings-search">
  368. <div class="self-center rounded-l-xl bg-transparent">
  369. <Search className="size-3.5" />
  370. </div>
  371. <input
  372. class="w-full py-1.5 text-sm bg-transparent dark:text-gray-300 outline-none"
  373. bind:value={search}
  374. on:input={searchDebounceHandler}
  375. placeholder={$i18n.t('Search')}
  376. />
  377. </div>
  378. {#if visibleTabs.length > 0}
  379. {#each visibleTabs as tabId (tabId)}
  380. {#if tabId === 'general'}
  381. <button
  382. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  383. 'general'
  384. ? ''
  385. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  386. on:click={() => {
  387. selectedTab = 'general';
  388. }}
  389. >
  390. <div class=" self-center mr-2">
  391. <svg
  392. xmlns="http://www.w3.org/2000/svg"
  393. viewBox="0 0 20 20"
  394. fill="currentColor"
  395. class="w-4 h-4"
  396. >
  397. <path
  398. fill-rule="evenodd"
  399. d="M8.34 1.804A1 1 0 019.32 1h1.36a1 1 0 01.98.804l.295 1.473c.497.144.971.342 1.416.587l1.25-.834a1 1 0 011.262.125l.962.962a1 1 0 01.125 1.262l-.834 1.25c.245.445.443.919.587 1.416l1.473.294a1 1 0 01.804.98v1.361a1 1 0 01-.804.98l-1.473.295a6.95 6.95 0 01-.587 1.416l.834 1.25a1 1 0 01-.125 1.262l-.962.962a1 1 0 01-1.262.125l-1.25-.834a6.953 6.953 0 01-1.416.587l-.294 1.473a1 1 0 01-.98.804H9.32a1 1 0 01-.98-.804l-.295-1.473a6.957 6.957 0 01-1.416-.587l-1.25.834a1 1 0 01-1.262-.125l-.962-.962a1 1 0 01-.125-1.262l.834-1.25a6.957 6.957 0 01-.587-1.416l-1.473-.294A1 1 0 011 10.68V9.32a1 1 0 01.804-.98l1.473-.295c.144-.497.342-.971.587-1.416l-.834-1.25a1 1 0 01.125-1.262l.962-.962A1 1 0 015.38 3.03l1.25.834a6.957 6.957 0 011.416-.587l.294-1.473zM13 10a3 3 0 11-6 0 3 3 0 016 0z"
  400. clip-rule="evenodd"
  401. />
  402. </svg>
  403. </div>
  404. <div class=" self-center">{$i18n.t('General')}</div>
  405. </button>
  406. {:else if tabId === 'interface'}
  407. <button
  408. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  409. 'interface'
  410. ? ''
  411. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  412. on:click={() => {
  413. selectedTab = 'interface';
  414. }}
  415. >
  416. <div class=" self-center mr-2">
  417. <svg
  418. xmlns="http://www.w3.org/2000/svg"
  419. viewBox="0 0 16 16"
  420. fill="currentColor"
  421. class="w-4 h-4"
  422. >
  423. <path
  424. fill-rule="evenodd"
  425. d="M2 4.25A2.25 2.25 0 0 1 4.25 2h7.5A2.25 2.25 0 0 1 14 4.25v5.5A2.25 2.25 0 0 1 11.75 12h-1.312c.1.128.21.248.328.36a.75.75 0 0 1 .234.545v.345a.75.75 0 0 1-.75.75h-4.5a.75.75 0 0 1-.75-.75v-.345a.75.75 0 0 1 .234-.545c.118-.111.228-.232.328-.36H4.25A2.25 2.25 0 0 1 2 9.75v-5.5Zm2.25-.75a.75.75 0 0 0-.75.75v4.5c0 .414.336.75.75.75h7.5a.75.75 0 0 0 .75-.75v-4.5a.75.75 0 0 0-.75-.75h-7.5Z"
  426. clip-rule="evenodd"
  427. />
  428. </svg>
  429. </div>
  430. <div class=" self-center">{$i18n.t('Interface')}</div>
  431. </button>
  432. {:else if tabId === 'personalization'}
  433. <button
  434. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  435. 'personalization'
  436. ? ''
  437. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  438. on:click={() => {
  439. selectedTab = 'personalization';
  440. }}
  441. >
  442. <div class=" self-center mr-2">
  443. <User />
  444. </div>
  445. <div class=" self-center">{$i18n.t('Personalization')}</div>
  446. </button>
  447. {:else if tabId === 'audio'}
  448. <button
  449. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  450. 'audio'
  451. ? ''
  452. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  453. on:click={() => {
  454. selectedTab = 'audio';
  455. }}
  456. >
  457. <div class=" self-center mr-2">
  458. <svg
  459. xmlns="http://www.w3.org/2000/svg"
  460. viewBox="0 0 16 16"
  461. fill="currentColor"
  462. class="w-4 h-4"
  463. >
  464. <path
  465. d="M7.557 2.066A.75.75 0 0 1 8 2.75v10.5a.75.75 0 0 1-1.248.56L3.59 11H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.59l3.162-2.81a.75.75 0 0 1 .805-.124ZM12.95 3.05a.75.75 0 1 0-1.06 1.06 5.5 5.5 0 0 1 0 7.78.75.75 0 1 0 1.06 1.06 7 7 0 0 0 0-9.9Z"
  466. />
  467. <path
  468. d="M10.828 5.172a.75.75 0 1 0-1.06 1.06 2.5 2.5 0 0 1 0 3.536.75.75 0 1 0 1.06 1.06 4 4 0 0 0 0-5.656Z"
  469. />
  470. </svg>
  471. </div>
  472. <div class=" self-center">{$i18n.t('Audio')}</div>
  473. </button>
  474. {:else if tabId === 'chats'}
  475. <button
  476. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  477. 'chats'
  478. ? ''
  479. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  480. on:click={() => {
  481. selectedTab = 'chats';
  482. }}
  483. >
  484. <div class=" self-center mr-2">
  485. <svg
  486. xmlns="http://www.w3.org/2000/svg"
  487. viewBox="0 0 16 16"
  488. fill="currentColor"
  489. class="w-4 h-4"
  490. >
  491. <path
  492. fill-rule="evenodd"
  493. d="M8 2C4.262 2 1 4.57 1 8c0 1.86.98 3.486 2.455 4.566a3.472 3.472 0 0 1-.469 1.26.75.75 0 0 0 .713 1.14 6.961 6.961 0 0 0 3.06-1.06c.403.062.818.094 1.241.094 3.738 0 7-2.57 7-6s-3.262-6-7-6ZM5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Zm7-1a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM8 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
  494. clip-rule="evenodd"
  495. />
  496. </svg>
  497. </div>
  498. <div class=" self-center">{$i18n.t('Chats')}</div>
  499. </button>
  500. {:else if tabId === 'account'}
  501. <button
  502. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  503. 'account'
  504. ? ''
  505. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  506. on:click={() => {
  507. selectedTab = 'account';
  508. }}
  509. >
  510. <div class=" self-center mr-2">
  511. <svg
  512. xmlns="http://www.w3.org/2000/svg"
  513. viewBox="0 0 16 16"
  514. fill="currentColor"
  515. class="w-4 h-4"
  516. >
  517. <path
  518. fill-rule="evenodd"
  519. d="M15 8A7 7 0 1 1 1 8a7 7 0 0 1 14 0Zm-5-2a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM8 9c-1.825 0-3.422.977-4.295 2.437A5.49 5.49 0 0 0 8 13.5a5.49 5.49 0 0 0 4.294-2.063A4.997 4.997 0 0 0 8 9Z"
  520. clip-rule="evenodd"
  521. />
  522. </svg>
  523. </div>
  524. <div class=" self-center">{$i18n.t('Account')}</div>
  525. </button>
  526. {:else if tabId === 'about'}
  527. <button
  528. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  529. 'about'
  530. ? ''
  531. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  532. on:click={() => {
  533. selectedTab = 'about';
  534. }}
  535. >
  536. <div class=" self-center mr-2">
  537. <svg
  538. xmlns="http://www.w3.org/2000/svg"
  539. viewBox="0 0 20 20"
  540. fill="currentColor"
  541. class="w-4 h-4"
  542. >
  543. <path
  544. fill-rule="evenodd"
  545. d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
  546. clip-rule="evenodd"
  547. />
  548. </svg>
  549. </div>
  550. <div class=" self-center">{$i18n.t('About')}</div>
  551. </button>
  552. {:else if tabId === 'admin'}
  553. {#if $user.role === 'admin'}
  554. <button
  555. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  556. 'admin'
  557. ? ''
  558. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  559. on:click={async () => {
  560. await goto('/admin/settings');
  561. show = false;
  562. }}
  563. >
  564. <div class=" self-center mr-2">
  565. <svg
  566. xmlns="http://www.w3.org/2000/svg"
  567. viewBox="0 0 24 24"
  568. fill="currentColor"
  569. class="size-4"
  570. >
  571. <path
  572. fill-rule="evenodd"
  573. d="M4.5 3.75a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-15Zm4.125 3a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Zm-3.873 8.703a4.126 4.126 0 0 1 7.746 0 .75.75 0 0 1-.351.92 7.47 7.47 0 0 1-3.522.877 7.47 7.47 0 0 1-3.522-.877.75.75 0 0 1-.351-.92ZM15 8.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15ZM14.25 12a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H15a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15Z"
  574. clip-rule="evenodd"
  575. />
  576. </svg>
  577. </div>
  578. <div class=" self-center">{$i18n.t('Admin Settings')}</div>
  579. </button>
  580. {/if}
  581. {/if}
  582. {/each}
  583. {:else}
  584. <div class="text-center text-gray-500 mt-4">
  585. {$i18n.t('No results found')}
  586. </div>
  587. {/if}
  588. </div>
  589. <div class="flex-1 md:min-h-[32rem] max-h-[32rem]">
  590. {#if selectedTab === 'general'}
  591. <General
  592. {getModels}
  593. {saveSettings}
  594. on:save={() => {
  595. toast.success($i18n.t('Settings saved successfully!'));
  596. }}
  597. />
  598. {:else if selectedTab === 'interface'}
  599. <Interface
  600. {saveSettings}
  601. on:save={() => {
  602. toast.success($i18n.t('Settings saved successfully!'));
  603. }}
  604. />
  605. {:else if selectedTab === 'personalization'}
  606. <Personalization
  607. {saveSettings}
  608. on:save={() => {
  609. toast.success($i18n.t('Settings saved successfully!'));
  610. }}
  611. />
  612. {:else if selectedTab === 'audio'}
  613. <Audio
  614. {saveSettings}
  615. on:save={() => {
  616. toast.success($i18n.t('Settings saved successfully!'));
  617. }}
  618. />
  619. {:else if selectedTab === 'chats'}
  620. <Chats {saveSettings} />
  621. {:else if selectedTab === 'account'}
  622. <Account
  623. {saveSettings}
  624. saveHandler={() => {
  625. toast.success($i18n.t('Settings saved successfully!'));
  626. }}
  627. />
  628. {:else if selectedTab === 'about'}
  629. <About />
  630. {/if}
  631. </div>
  632. </div>
  633. </div>
  634. </Modal>
  635. <style>
  636. input::-webkit-outer-spin-button,
  637. input::-webkit-inner-spin-button {
  638. /* display: none; <- Crashes Chrome on hover */
  639. -webkit-appearance: none;
  640. margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
  641. }
  642. .tabs::-webkit-scrollbar {
  643. display: none; /* for Chrome, Safari and Opera */
  644. }
  645. .tabs {
  646. -ms-overflow-style: none; /* IE and Edge */
  647. scrollbar-width: none; /* Firefox */
  648. }
  649. input[type='number'] {
  650. -moz-appearance: textfield; /* Firefox */
  651. }
  652. </style>