SettingsModal.svelte 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. <script lang="ts">
  2. import { getContext, tick } from 'svelte';
  3. import { toast } from 'svelte-sonner';
  4. import { config, 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. import Connections from './Settings/Connections.svelte';
  20. const i18n = getContext('i18n');
  21. export let show = false;
  22. interface SettingsTab {
  23. id: string;
  24. title: string;
  25. keywords: string[];
  26. }
  27. const searchData: SettingsTab[] = [
  28. {
  29. id: 'general',
  30. title: 'General',
  31. keywords: [
  32. 'general',
  33. 'theme',
  34. 'language',
  35. 'notifications',
  36. 'system',
  37. 'systemprompt',
  38. 'prompt',
  39. 'advanced',
  40. 'settings',
  41. 'defaultsettings',
  42. 'configuration',
  43. 'systemsettings',
  44. 'notificationsettings',
  45. 'systempromptconfig',
  46. 'languageoptions',
  47. 'defaultparameters',
  48. 'systemparameters'
  49. ]
  50. },
  51. {
  52. id: 'interface',
  53. title: 'Interface',
  54. keywords: [
  55. 'defaultmodel',
  56. 'selectmodel',
  57. 'ui',
  58. 'userinterface',
  59. 'display',
  60. 'layout',
  61. 'design',
  62. 'landingpage',
  63. 'landingpagemode',
  64. 'default',
  65. 'chat',
  66. 'chatbubble',
  67. 'chatui',
  68. 'username',
  69. 'showusername',
  70. 'displayusername',
  71. 'widescreen',
  72. 'widescreenmode',
  73. 'fullscreen',
  74. 'expandmode',
  75. 'chatdirection',
  76. 'lefttoright',
  77. 'ltr',
  78. 'righttoleft',
  79. 'rtl',
  80. 'notifications',
  81. 'toast',
  82. 'toastnotifications',
  83. 'largechunks',
  84. 'streamlargechunks',
  85. 'scroll',
  86. 'scrollonbranchchange',
  87. 'scrollbehavior',
  88. 'richtext',
  89. 'richtextinput',
  90. 'background',
  91. 'chatbackground',
  92. 'chatbackgroundimage',
  93. 'backgroundimage',
  94. 'uploadbackground',
  95. 'resetbackground',
  96. 'titleautogen',
  97. 'titleautogeneration',
  98. 'autotitle',
  99. 'chattags',
  100. 'autochattags',
  101. 'responseautocopy',
  102. 'clipboard',
  103. 'location',
  104. 'userlocation',
  105. 'userlocationaccess',
  106. 'haptic',
  107. 'hapticfeedback',
  108. 'vibration',
  109. 'voice',
  110. 'voicecontrol',
  111. 'voiceinterruption',
  112. 'call',
  113. 'emojis',
  114. 'displayemoji',
  115. 'save',
  116. 'interfaceoptions',
  117. 'interfacecustomization',
  118. 'alwaysonwebsearch'
  119. ]
  120. },
  121. {
  122. id: 'connections',
  123. title: 'Connections',
  124. keywords: []
  125. },
  126. {
  127. id: 'personalization',
  128. title: 'Personalization',
  129. keywords: [
  130. 'personalization',
  131. 'memory',
  132. 'personalize',
  133. 'preferences',
  134. 'profile',
  135. 'personalsettings',
  136. 'customsettings',
  137. 'userpreferences',
  138. 'accountpreferences'
  139. ]
  140. },
  141. {
  142. id: 'audio',
  143. title: 'Audio',
  144. keywords: [
  145. 'audio',
  146. 'sound',
  147. 'soundsettings',
  148. 'audiocontrol',
  149. 'volume',
  150. 'speech',
  151. 'speechrecognition',
  152. 'stt',
  153. 'speechtotext',
  154. 'tts',
  155. 'texttospeech',
  156. 'playback',
  157. 'playbackspeed',
  158. 'voiceplayback',
  159. 'speechplayback',
  160. 'audiooutput',
  161. 'speechengine',
  162. 'voicecontrol',
  163. 'audioplayback',
  164. 'transcription',
  165. 'autotranscribe',
  166. 'autosend',
  167. 'speechsettings',
  168. 'audiovoice',
  169. 'voiceoptions',
  170. 'setvoice',
  171. 'nonlocalvoices',
  172. 'savesettings',
  173. 'audioconfig',
  174. 'speechconfig',
  175. 'voicerecognition',
  176. 'speechsynthesis',
  177. 'speechmode',
  178. 'voicespeed',
  179. 'speechrate',
  180. 'speechspeed',
  181. 'audioinput',
  182. 'audiofeatures',
  183. 'voicemodes'
  184. ]
  185. },
  186. {
  187. id: 'chats',
  188. title: 'Chats',
  189. keywords: [
  190. 'chat',
  191. 'messages',
  192. 'conversations',
  193. 'chatsettings',
  194. 'history',
  195. 'chathistory',
  196. 'messagehistory',
  197. 'messagearchive',
  198. 'convo',
  199. 'chats',
  200. 'conversationhistory',
  201. 'exportmessages',
  202. 'chatactivity'
  203. ]
  204. },
  205. {
  206. id: 'account',
  207. title: 'Account',
  208. keywords: [
  209. 'account',
  210. 'profile',
  211. 'security',
  212. 'privacy',
  213. 'settings',
  214. 'login',
  215. 'useraccount',
  216. 'userdata',
  217. 'api',
  218. 'apikey',
  219. 'userprofile',
  220. 'profiledetails',
  221. 'accountsettings',
  222. 'accountpreferences',
  223. 'securitysettings',
  224. 'privacysettings'
  225. ]
  226. },
  227. {
  228. id: 'admin',
  229. title: 'Admin',
  230. keywords: [
  231. 'admin',
  232. 'administrator',
  233. 'adminsettings',
  234. 'adminpanel',
  235. 'systemadmin',
  236. 'administratoraccess',
  237. 'systemcontrol',
  238. 'manage',
  239. 'management',
  240. 'admincontrols',
  241. 'adminfeatures',
  242. 'usercontrol',
  243. 'arenamodel',
  244. 'evaluations',
  245. 'websearch',
  246. 'database',
  247. 'pipelines',
  248. 'images',
  249. 'audio',
  250. 'documents',
  251. 'rag',
  252. 'models',
  253. 'ollama',
  254. 'openai',
  255. 'users'
  256. ]
  257. },
  258. {
  259. id: 'about',
  260. title: 'About',
  261. keywords: [
  262. 'about',
  263. 'info',
  264. 'information',
  265. 'version',
  266. 'documentation',
  267. 'help',
  268. 'support',
  269. 'details',
  270. 'aboutus',
  271. 'softwareinfo',
  272. 'timothyjaeryangbaek',
  273. 'openwebui',
  274. 'release',
  275. 'updates',
  276. 'updateinfo',
  277. 'versioninfo',
  278. 'aboutapp',
  279. 'terms',
  280. 'termsandconditions',
  281. 'contact',
  282. 'aboutpage'
  283. ]
  284. }
  285. ];
  286. let search = '';
  287. let visibleTabs = searchData.map((tab) => tab.id);
  288. let searchDebounceTimeout;
  289. const searchSettings = (query: string): string[] => {
  290. const lowerCaseQuery = query.toLowerCase().trim();
  291. return searchData
  292. .filter(
  293. (tab) =>
  294. tab.title.toLowerCase().includes(lowerCaseQuery) ||
  295. tab.keywords.some((keyword) => keyword.includes(lowerCaseQuery))
  296. )
  297. .map((tab) => tab.id);
  298. };
  299. const searchDebounceHandler = () => {
  300. clearTimeout(searchDebounceTimeout);
  301. searchDebounceTimeout = setTimeout(() => {
  302. visibleTabs = searchSettings(search);
  303. if (visibleTabs.length > 0 && !visibleTabs.includes(selectedTab)) {
  304. selectedTab = visibleTabs[0];
  305. }
  306. }, 100);
  307. };
  308. const saveSettings = async (updated) => {
  309. console.log(updated);
  310. await settings.set({ ...$settings, ...updated });
  311. await models.set(await getModels());
  312. await updateUserSettings(localStorage.token, { ui: $settings });
  313. };
  314. const getModels = async () => {
  315. return await _getModels(
  316. localStorage.token,
  317. $config?.features?.enable_direct_connections && ($settings?.directConnections ?? null)
  318. );
  319. };
  320. let selectedTab = 'general';
  321. // Function to handle sideways scrolling
  322. const scrollHandler = (event) => {
  323. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  324. if (settingsTabsContainer) {
  325. event.preventDefault(); // Prevent default vertical scrolling
  326. settingsTabsContainer.scrollLeft += event.deltaY; // Scroll sideways
  327. }
  328. };
  329. const addScrollListener = async () => {
  330. await tick();
  331. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  332. if (settingsTabsContainer) {
  333. settingsTabsContainer.addEventListener('wheel', scrollHandler);
  334. }
  335. };
  336. const removeScrollListener = async () => {
  337. await tick();
  338. const settingsTabsContainer = document.getElementById('settings-tabs-container');
  339. if (settingsTabsContainer) {
  340. settingsTabsContainer.removeEventListener('wheel', scrollHandler);
  341. }
  342. };
  343. $: if (show) {
  344. addScrollListener();
  345. } else {
  346. removeScrollListener();
  347. }
  348. </script>
  349. <Modal size="xl" bind:show>
  350. <div class="text-gray-700 dark:text-gray-100">
  351. <div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
  352. <div class=" text-lg font-medium self-center">{$i18n.t('Settings')}</div>
  353. <button
  354. class="self-center"
  355. on:click={() => {
  356. show = false;
  357. }}
  358. >
  359. <svg
  360. xmlns="http://www.w3.org/2000/svg"
  361. viewBox="0 0 20 20"
  362. fill="currentColor"
  363. class="w-5 h-5"
  364. >
  365. <path
  366. 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"
  367. />
  368. </svg>
  369. </button>
  370. </div>
  371. <div class="flex flex-col md:flex-row w-full px-4 pt-1 pb-4 md:space-x-4">
  372. <div
  373. id="settings-tabs-container"
  374. 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"
  375. >
  376. <div class="hidden md:flex w-full rounded-xl -mb-1 px-0.5 gap-2" id="settings-search">
  377. <div class="self-center rounded-l-xl bg-transparent">
  378. <Search className="size-3.5" />
  379. </div>
  380. <input
  381. class="w-full py-1.5 text-sm bg-transparent dark:text-gray-300 outline-none"
  382. bind:value={search}
  383. on:input={searchDebounceHandler}
  384. placeholder={$i18n.t('Search')}
  385. />
  386. </div>
  387. {#if visibleTabs.length > 0}
  388. {#each visibleTabs as tabId (tabId)}
  389. {#if tabId === 'general'}
  390. <button
  391. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  392. 'general'
  393. ? ''
  394. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  395. on:click={() => {
  396. selectedTab = 'general';
  397. }}
  398. >
  399. <div class=" self-center mr-2">
  400. <svg
  401. xmlns="http://www.w3.org/2000/svg"
  402. viewBox="0 0 20 20"
  403. fill="currentColor"
  404. class="w-4 h-4"
  405. >
  406. <path
  407. fill-rule="evenodd"
  408. 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"
  409. clip-rule="evenodd"
  410. />
  411. </svg>
  412. </div>
  413. <div class=" self-center">{$i18n.t('General')}</div>
  414. </button>
  415. {:else if tabId === 'interface'}
  416. <button
  417. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  418. 'interface'
  419. ? ''
  420. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  421. on:click={() => {
  422. selectedTab = 'interface';
  423. }}
  424. >
  425. <div class=" self-center mr-2">
  426. <svg
  427. xmlns="http://www.w3.org/2000/svg"
  428. viewBox="0 0 16 16"
  429. fill="currentColor"
  430. class="w-4 h-4"
  431. >
  432. <path
  433. fill-rule="evenodd"
  434. 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"
  435. clip-rule="evenodd"
  436. />
  437. </svg>
  438. </div>
  439. <div class=" self-center">{$i18n.t('Interface')}</div>
  440. </button>
  441. {:else if tabId === 'connections'}
  442. {#if $user.role === 'admin' || ($user.role === 'user' && $config?.features?.enable_direct_connections)}
  443. <button
  444. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
  445. 'connections'
  446. ? ''
  447. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  448. on:click={() => {
  449. selectedTab = 'connections';
  450. }}
  451. >
  452. <div class=" self-center mr-2">
  453. <svg
  454. xmlns="http://www.w3.org/2000/svg"
  455. viewBox="0 0 16 16"
  456. fill="currentColor"
  457. class="w-4 h-4"
  458. >
  459. <path
  460. d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
  461. />
  462. </svg>
  463. </div>
  464. <div class=" self-center">{$i18n.t('Connections')}</div>
  465. </button>
  466. {/if}
  467. {:else if tabId === 'personalization'}
  468. <button
  469. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  470. 'personalization'
  471. ? ''
  472. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  473. on:click={() => {
  474. selectedTab = 'personalization';
  475. }}
  476. >
  477. <div class=" self-center mr-2">
  478. <User />
  479. </div>
  480. <div class=" self-center">{$i18n.t('Personalization')}</div>
  481. </button>
  482. {:else if tabId === 'audio'}
  483. <button
  484. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  485. 'audio'
  486. ? ''
  487. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  488. on:click={() => {
  489. selectedTab = 'audio';
  490. }}
  491. >
  492. <div class=" self-center mr-2">
  493. <svg
  494. xmlns="http://www.w3.org/2000/svg"
  495. viewBox="0 0 16 16"
  496. fill="currentColor"
  497. class="w-4 h-4"
  498. >
  499. <path
  500. 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"
  501. />
  502. <path
  503. 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"
  504. />
  505. </svg>
  506. </div>
  507. <div class=" self-center">{$i18n.t('Audio')}</div>
  508. </button>
  509. {:else if tabId === 'chats'}
  510. <button
  511. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  512. 'chats'
  513. ? ''
  514. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  515. on:click={() => {
  516. selectedTab = 'chats';
  517. }}
  518. >
  519. <div class=" self-center mr-2">
  520. <svg
  521. xmlns="http://www.w3.org/2000/svg"
  522. viewBox="0 0 16 16"
  523. fill="currentColor"
  524. class="w-4 h-4"
  525. >
  526. <path
  527. fill-rule="evenodd"
  528. 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"
  529. clip-rule="evenodd"
  530. />
  531. </svg>
  532. </div>
  533. <div class=" self-center">{$i18n.t('Chats')}</div>
  534. </button>
  535. {:else if tabId === 'account'}
  536. <button
  537. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  538. 'account'
  539. ? ''
  540. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  541. on:click={() => {
  542. selectedTab = 'account';
  543. }}
  544. >
  545. <div class=" self-center mr-2">
  546. <svg
  547. xmlns="http://www.w3.org/2000/svg"
  548. viewBox="0 0 16 16"
  549. fill="currentColor"
  550. class="w-4 h-4"
  551. >
  552. <path
  553. fill-rule="evenodd"
  554. 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"
  555. clip-rule="evenodd"
  556. />
  557. </svg>
  558. </div>
  559. <div class=" self-center">{$i18n.t('Account')}</div>
  560. </button>
  561. {:else if tabId === 'about'}
  562. <button
  563. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  564. 'about'
  565. ? ''
  566. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  567. on:click={() => {
  568. selectedTab = 'about';
  569. }}
  570. >
  571. <div class=" self-center mr-2">
  572. <svg
  573. xmlns="http://www.w3.org/2000/svg"
  574. viewBox="0 0 20 20"
  575. fill="currentColor"
  576. class="w-4 h-4"
  577. >
  578. <path
  579. fill-rule="evenodd"
  580. 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"
  581. clip-rule="evenodd"
  582. />
  583. </svg>
  584. </div>
  585. <div class=" self-center">{$i18n.t('About')}</div>
  586. </button>
  587. {:else if tabId === 'admin'}
  588. {#if $user.role === 'admin'}
  589. <button
  590. class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-left transition {selectedTab ===
  591. 'admin'
  592. ? ''
  593. : ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
  594. on:click={async () => {
  595. await goto('/admin/settings');
  596. show = false;
  597. }}
  598. >
  599. <div class=" self-center mr-2">
  600. <svg
  601. xmlns="http://www.w3.org/2000/svg"
  602. viewBox="0 0 24 24"
  603. fill="currentColor"
  604. class="size-4"
  605. >
  606. <path
  607. fill-rule="evenodd"
  608. 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"
  609. clip-rule="evenodd"
  610. />
  611. </svg>
  612. </div>
  613. <div class=" self-center">{$i18n.t('Admin Settings')}</div>
  614. </button>
  615. {/if}
  616. {/if}
  617. {/each}
  618. {:else}
  619. <div class="text-center text-gray-500 mt-4">
  620. {$i18n.t('No results found')}
  621. </div>
  622. {/if}
  623. </div>
  624. <div class="flex-1 md:min-h-[32rem] max-h-[32rem]">
  625. {#if selectedTab === 'general'}
  626. <General
  627. {getModels}
  628. {saveSettings}
  629. on:save={() => {
  630. toast.success($i18n.t('Settings saved successfully!'));
  631. }}
  632. />
  633. {:else if selectedTab === 'interface'}
  634. <Interface
  635. {saveSettings}
  636. on:save={() => {
  637. toast.success($i18n.t('Settings saved successfully!'));
  638. }}
  639. />
  640. {:else if selectedTab === 'connections'}
  641. <Connections
  642. saveSettings={async (updated) => {
  643. await saveSettings(updated);
  644. toast.success($i18n.t('Settings saved successfully!'));
  645. }}
  646. />
  647. {:else if selectedTab === 'personalization'}
  648. <Personalization
  649. {saveSettings}
  650. on:save={() => {
  651. toast.success($i18n.t('Settings saved successfully!'));
  652. }}
  653. />
  654. {:else if selectedTab === 'audio'}
  655. <Audio
  656. {saveSettings}
  657. on:save={() => {
  658. toast.success($i18n.t('Settings saved successfully!'));
  659. }}
  660. />
  661. {:else if selectedTab === 'chats'}
  662. <Chats {saveSettings} />
  663. {:else if selectedTab === 'account'}
  664. <Account
  665. {saveSettings}
  666. saveHandler={() => {
  667. toast.success($i18n.t('Settings saved successfully!'));
  668. }}
  669. />
  670. {:else if selectedTab === 'about'}
  671. <About />
  672. {/if}
  673. </div>
  674. </div>
  675. </div>
  676. </Modal>
  677. <style>
  678. input::-webkit-outer-spin-button,
  679. input::-webkit-inner-spin-button {
  680. /* display: none; <- Crashes Chrome on hover */
  681. -webkit-appearance: none;
  682. margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
  683. }
  684. .tabs::-webkit-scrollbar {
  685. display: none; /* for Chrome, Safari and Opera */
  686. }
  687. .tabs {
  688. -ms-overflow-style: none; /* IE and Edge */
  689. scrollbar-width: none; /* Firefox */
  690. }
  691. input[type='number'] {
  692. -moz-appearance: textfield; /* Firefox */
  693. }
  694. </style>