Interface.svelte 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. <script lang="ts">
  2. import { getBackendConfig } from '$lib/apis';
  3. import { setDefaultPromptSuggestions } from '$lib/apis/configs';
  4. import { config, models, settings, user } from '$lib/stores';
  5. import { createEventDispatcher, onMount, getContext } from 'svelte';
  6. import { toast } from 'svelte-sonner';
  7. import Tooltip from '$lib/components/common/Tooltip.svelte';
  8. import { updateUserInfo } from '$lib/apis/users';
  9. import { getUserPosition } from '$lib/utils';
  10. const dispatch = createEventDispatcher();
  11. const i18n = getContext('i18n');
  12. export let saveSettings: Function;
  13. let backgroundImageUrl = null;
  14. let inputFiles = null;
  15. let filesInputElement;
  16. // Addons
  17. let titleAutoGenerate = true;
  18. let autoTags = true;
  19. let responseAutoCopy = false;
  20. let widescreenMode = false;
  21. let splitLargeChunks = false;
  22. let scrollOnBranchChange = true;
  23. let userLocation = false;
  24. // Interface
  25. let defaultModelId = '';
  26. let showUsername = false;
  27. let richTextInput = true;
  28. let largeTextAsFile = false;
  29. let notificationSound = true;
  30. let landingPageMode = '';
  31. let chatBubble = true;
  32. let chatDirection: 'LTR' | 'RTL' = 'LTR';
  33. let ctrlEnterToSend = false;
  34. let imageCompression = false;
  35. let imageCompressionSize = {
  36. width: '',
  37. height: ''
  38. };
  39. // Admin - Show Update Available Toast
  40. let showUpdateToast = true;
  41. let showChangelog = true;
  42. let showEmojiInCall = false;
  43. let voiceInterruption = false;
  44. let hapticFeedback = false;
  45. let webSearch = null;
  46. const toggleSplitLargeChunks = async () => {
  47. splitLargeChunks = !splitLargeChunks;
  48. saveSettings({ splitLargeChunks: splitLargeChunks });
  49. };
  50. const togglesScrollOnBranchChange = async () => {
  51. scrollOnBranchChange = !scrollOnBranchChange;
  52. saveSettings({ scrollOnBranchChange: scrollOnBranchChange });
  53. };
  54. const toggleWidescreenMode = async () => {
  55. widescreenMode = !widescreenMode;
  56. saveSettings({ widescreenMode: widescreenMode });
  57. };
  58. const toggleChatBubble = async () => {
  59. chatBubble = !chatBubble;
  60. saveSettings({ chatBubble: chatBubble });
  61. };
  62. const toggleLandingPageMode = async () => {
  63. landingPageMode = landingPageMode === '' ? 'chat' : '';
  64. saveSettings({ landingPageMode: landingPageMode });
  65. };
  66. const toggleShowUpdateToast = async () => {
  67. showUpdateToast = !showUpdateToast;
  68. saveSettings({ showUpdateToast: showUpdateToast });
  69. };
  70. const toggleNotificationSound = async () => {
  71. notificationSound = !notificationSound;
  72. saveSettings({ notificationSound: notificationSound });
  73. };
  74. const toggleShowChangelog = async () => {
  75. showChangelog = !showChangelog;
  76. saveSettings({ showChangelog: showChangelog });
  77. };
  78. const toggleShowUsername = async () => {
  79. showUsername = !showUsername;
  80. saveSettings({ showUsername: showUsername });
  81. };
  82. const toggleEmojiInCall = async () => {
  83. showEmojiInCall = !showEmojiInCall;
  84. saveSettings({ showEmojiInCall: showEmojiInCall });
  85. };
  86. const toggleVoiceInterruption = async () => {
  87. voiceInterruption = !voiceInterruption;
  88. saveSettings({ voiceInterruption: voiceInterruption });
  89. };
  90. const toggleImageCompression = async () => {
  91. imageCompression = !imageCompression;
  92. saveSettings({ imageCompression });
  93. };
  94. const toggleHapticFeedback = async () => {
  95. hapticFeedback = !hapticFeedback;
  96. saveSettings({ hapticFeedback: hapticFeedback });
  97. };
  98. const toggleUserLocation = async () => {
  99. userLocation = !userLocation;
  100. if (userLocation) {
  101. const position = await getUserPosition().catch((error) => {
  102. toast.error(error.message);
  103. return null;
  104. });
  105. if (position) {
  106. await updateUserInfo(localStorage.token, { location: position });
  107. toast.success($i18n.t('User location successfully retrieved.'));
  108. } else {
  109. userLocation = false;
  110. }
  111. }
  112. saveSettings({ userLocation });
  113. };
  114. const toggleTitleAutoGenerate = async () => {
  115. titleAutoGenerate = !titleAutoGenerate;
  116. saveSettings({
  117. title: {
  118. ...$settings.title,
  119. auto: titleAutoGenerate
  120. }
  121. });
  122. };
  123. const toggleAutoTags = async () => {
  124. autoTags = !autoTags;
  125. saveSettings({ autoTags });
  126. };
  127. const toggleRichTextInput = async () => {
  128. richTextInput = !richTextInput;
  129. saveSettings({ richTextInput });
  130. };
  131. const toggleLargeTextAsFile = async () => {
  132. largeTextAsFile = !largeTextAsFile;
  133. saveSettings({ largeTextAsFile });
  134. };
  135. const toggleResponseAutoCopy = async () => {
  136. const permission = await navigator.clipboard
  137. .readText()
  138. .then(() => {
  139. return 'granted';
  140. })
  141. .catch(() => {
  142. return '';
  143. });
  144. console.log(permission);
  145. if (permission === 'granted') {
  146. responseAutoCopy = !responseAutoCopy;
  147. saveSettings({ responseAutoCopy: responseAutoCopy });
  148. } else {
  149. toast.error(
  150. $i18n.t(
  151. 'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
  152. )
  153. );
  154. }
  155. };
  156. const toggleChangeChatDirection = async () => {
  157. chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
  158. saveSettings({ chatDirection });
  159. };
  160. const togglectrlEnterToSend = async () => {
  161. ctrlEnterToSend = !ctrlEnterToSend;
  162. saveSettings({ ctrlEnterToSend });
  163. };
  164. const updateInterfaceHandler = async () => {
  165. saveSettings({
  166. models: [defaultModelId],
  167. imageCompressionSize: imageCompressionSize
  168. });
  169. };
  170. const toggleWebSearch = async () => {
  171. webSearch = webSearch === null ? 'always' : null;
  172. saveSettings({ webSearch: webSearch });
  173. };
  174. onMount(async () => {
  175. titleAutoGenerate = $settings?.title?.auto ?? true;
  176. autoTags = $settings.autoTags ?? true;
  177. responseAutoCopy = $settings.responseAutoCopy ?? false;
  178. showUsername = $settings.showUsername ?? false;
  179. showUpdateToast = $settings.showUpdateToast ?? true;
  180. showChangelog = $settings.showChangelog ?? true;
  181. showEmojiInCall = $settings.showEmojiInCall ?? false;
  182. voiceInterruption = $settings.voiceInterruption ?? false;
  183. richTextInput = $settings.richTextInput ?? true;
  184. largeTextAsFile = $settings.largeTextAsFile ?? false;
  185. landingPageMode = $settings.landingPageMode ?? '';
  186. chatBubble = $settings.chatBubble ?? true;
  187. widescreenMode = $settings.widescreenMode ?? false;
  188. splitLargeChunks = $settings.splitLargeChunks ?? false;
  189. scrollOnBranchChange = $settings.scrollOnBranchChange ?? true;
  190. chatDirection = $settings.chatDirection ?? 'LTR';
  191. userLocation = $settings.userLocation ?? false;
  192. notificationSound = $settings.notificationSound ?? true;
  193. hapticFeedback = $settings.hapticFeedback ?? false;
  194. ctrlEnterToSend = $settings.ctrlEnterToSend ?? false;
  195. imageCompression = $settings.imageCompression ?? false;
  196. imageCompressionSize = $settings.imageCompressionSize ?? { width: '', height: '' };
  197. defaultModelId = $settings?.models?.at(0) ?? '';
  198. if ($config?.default_models) {
  199. defaultModelId = $config.default_models.split(',')[0];
  200. }
  201. backgroundImageUrl = $settings.backgroundImageUrl ?? null;
  202. webSearch = $settings.webSearch ?? null;
  203. });
  204. </script>
  205. <form
  206. class="flex flex-col h-full justify-between space-y-3 text-sm"
  207. on:submit|preventDefault={() => {
  208. updateInterfaceHandler();
  209. dispatch('save');
  210. }}
  211. >
  212. <input
  213. bind:this={filesInputElement}
  214. bind:files={inputFiles}
  215. type="file"
  216. hidden
  217. accept="image/*"
  218. on:change={() => {
  219. let reader = new FileReader();
  220. reader.onload = (event) => {
  221. let originalImageUrl = `${event.target.result}`;
  222. backgroundImageUrl = originalImageUrl;
  223. saveSettings({ backgroundImageUrl });
  224. };
  225. if (
  226. inputFiles &&
  227. inputFiles.length > 0 &&
  228. ['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
  229. ) {
  230. reader.readAsDataURL(inputFiles[0]);
  231. } else {
  232. console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
  233. inputFiles = null;
  234. }
  235. }}
  236. />
  237. <div class=" space-y-3 overflow-y-scroll max-h-[28rem] lg:max-h-full">
  238. <div>
  239. <div class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</div>
  240. <div>
  241. <div class=" py-0.5 flex w-full justify-between">
  242. <div class=" self-center text-xs">{$i18n.t('Landing Page Mode')}</div>
  243. <button
  244. class="p-1 px-3 text-xs flex rounded-sm transition"
  245. on:click={() => {
  246. toggleLandingPageMode();
  247. }}
  248. type="button"
  249. >
  250. {#if landingPageMode === ''}
  251. <span class="ml-2 self-center">{$i18n.t('Default')}</span>
  252. {:else}
  253. <span class="ml-2 self-center">{$i18n.t('Chat')}</span>
  254. {/if}
  255. </button>
  256. </div>
  257. </div>
  258. <div>
  259. <div class=" py-0.5 flex w-full justify-between">
  260. <div class=" self-center text-xs">{$i18n.t('Chat Bubble UI')}</div>
  261. <button
  262. class="p-1 px-3 text-xs flex rounded-sm transition"
  263. on:click={() => {
  264. toggleChatBubble();
  265. }}
  266. type="button"
  267. >
  268. {#if chatBubble === true}
  269. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  270. {:else}
  271. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  272. {/if}
  273. </button>
  274. </div>
  275. </div>
  276. {#if !$settings.chatBubble}
  277. <div>
  278. <div class=" py-0.5 flex w-full justify-between">
  279. <div class=" self-center text-xs">
  280. {$i18n.t('Display the username instead of You in the Chat')}
  281. </div>
  282. <button
  283. class="p-1 px-3 text-xs flex rounded-sm transition"
  284. on:click={() => {
  285. toggleShowUsername();
  286. }}
  287. type="button"
  288. >
  289. {#if showUsername === true}
  290. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  291. {:else}
  292. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  293. {/if}
  294. </button>
  295. </div>
  296. </div>
  297. {/if}
  298. <div>
  299. <div class=" py-0.5 flex w-full justify-between">
  300. <div class=" self-center text-xs">{$i18n.t('Widescreen Mode')}</div>
  301. <button
  302. class="p-1 px-3 text-xs flex rounded-sm transition"
  303. on:click={() => {
  304. toggleWidescreenMode();
  305. }}
  306. type="button"
  307. >
  308. {#if widescreenMode === true}
  309. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  310. {:else}
  311. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  312. {/if}
  313. </button>
  314. </div>
  315. </div>
  316. <div>
  317. <div class=" py-0.5 flex w-full justify-between">
  318. <div class=" self-center text-xs">{$i18n.t('Chat direction')}</div>
  319. <button
  320. class="p-1 px-3 text-xs flex rounded-sm transition"
  321. on:click={toggleChangeChatDirection}
  322. type="button"
  323. >
  324. {#if chatDirection === 'LTR'}
  325. <span class="ml-2 self-center">{$i18n.t('LTR')}</span>
  326. {:else}
  327. <span class="ml-2 self-center">{$i18n.t('RTL')}</span>
  328. {/if}
  329. </button>
  330. </div>
  331. </div>
  332. <div>
  333. <div class=" py-0.5 flex w-full justify-between">
  334. <div class=" self-center text-xs">
  335. {$i18n.t('Notification Sound')}
  336. </div>
  337. <button
  338. class="p-1 px-3 text-xs flex rounded-sm transition"
  339. on:click={() => {
  340. toggleNotificationSound();
  341. }}
  342. type="button"
  343. >
  344. {#if notificationSound === true}
  345. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  346. {:else}
  347. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  348. {/if}
  349. </button>
  350. </div>
  351. </div>
  352. {#if $user.role === 'admin'}
  353. <div>
  354. <div class=" py-0.5 flex w-full justify-between">
  355. <div class=" self-center text-xs">
  356. {$i18n.t('Toast notifications for new updates')}
  357. </div>
  358. <button
  359. class="p-1 px-3 text-xs flex rounded-sm transition"
  360. on:click={() => {
  361. toggleShowUpdateToast();
  362. }}
  363. type="button"
  364. >
  365. {#if showUpdateToast === true}
  366. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  367. {:else}
  368. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  369. {/if}
  370. </button>
  371. </div>
  372. </div>
  373. <div>
  374. <div class=" py-0.5 flex w-full justify-between">
  375. <div class=" self-center text-xs">
  376. {$i18n.t(`Show "What's New" modal on login`)}
  377. </div>
  378. <button
  379. class="p-1 px-3 text-xs flex rounded-sm transition"
  380. on:click={() => {
  381. toggleShowChangelog();
  382. }}
  383. type="button"
  384. >
  385. {#if showChangelog === true}
  386. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  387. {:else}
  388. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  389. {/if}
  390. </button>
  391. </div>
  392. </div>
  393. {/if}
  394. <div class=" my-1.5 text-sm font-medium">{$i18n.t('Chat')}</div>
  395. <div>
  396. <div class=" py-0.5 flex w-full justify-between">
  397. <div class=" self-center text-xs">{$i18n.t('Title Auto-Generation')}</div>
  398. <button
  399. class="p-1 px-3 text-xs flex rounded-sm transition"
  400. on:click={() => {
  401. toggleTitleAutoGenerate();
  402. }}
  403. type="button"
  404. >
  405. {#if titleAutoGenerate === true}
  406. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  407. {:else}
  408. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  409. {/if}
  410. </button>
  411. </div>
  412. </div>
  413. <div>
  414. <div class=" py-0.5 flex w-full justify-between">
  415. <div class=" self-center text-xs">{$i18n.t('Chat Tags Auto-Generation')}</div>
  416. <button
  417. class="p-1 px-3 text-xs flex rounded-sm transition"
  418. on:click={() => {
  419. toggleAutoTags();
  420. }}
  421. type="button"
  422. >
  423. {#if autoTags === true}
  424. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  425. {:else}
  426. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  427. {/if}
  428. </button>
  429. </div>
  430. </div>
  431. <div>
  432. <div class=" py-0.5 flex w-full justify-between">
  433. <div class=" self-center text-xs">
  434. {$i18n.t('Auto-Copy Response to Clipboard')}
  435. </div>
  436. <button
  437. class="p-1 px-3 text-xs flex rounded-sm transition"
  438. on:click={() => {
  439. toggleResponseAutoCopy();
  440. }}
  441. type="button"
  442. >
  443. {#if responseAutoCopy === true}
  444. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  445. {:else}
  446. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  447. {/if}
  448. </button>
  449. </div>
  450. </div>
  451. <div>
  452. <div class=" py-0.5 flex w-full justify-between">
  453. <div class=" self-center text-xs">
  454. {$i18n.t('Rich Text Input for Chat')}
  455. </div>
  456. <button
  457. class="p-1 px-3 text-xs flex rounded-sm transition"
  458. on:click={() => {
  459. toggleRichTextInput();
  460. }}
  461. type="button"
  462. >
  463. {#if richTextInput === true}
  464. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  465. {:else}
  466. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  467. {/if}
  468. </button>
  469. </div>
  470. </div>
  471. <div>
  472. <div class=" py-0.5 flex w-full justify-between">
  473. <div class=" self-center text-xs">
  474. {$i18n.t('Paste Large Text as File')}
  475. </div>
  476. <button
  477. class="p-1 px-3 text-xs flex rounded-sm transition"
  478. on:click={() => {
  479. toggleLargeTextAsFile();
  480. }}
  481. type="button"
  482. >
  483. {#if largeTextAsFile === true}
  484. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  485. {:else}
  486. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  487. {/if}
  488. </button>
  489. </div>
  490. </div>
  491. <div>
  492. <div class=" py-0.5 flex w-full justify-between">
  493. <div class=" self-center text-xs">
  494. {$i18n.t('Chat Background Image')}
  495. </div>
  496. <button
  497. class="p-1 px-3 text-xs flex rounded-sm transition"
  498. on:click={() => {
  499. if (backgroundImageUrl !== null) {
  500. backgroundImageUrl = null;
  501. saveSettings({ backgroundImageUrl });
  502. } else {
  503. filesInputElement.click();
  504. }
  505. }}
  506. type="button"
  507. >
  508. {#if backgroundImageUrl !== null}
  509. <span class="ml-2 self-center">{$i18n.t('Reset')}</span>
  510. {:else}
  511. <span class="ml-2 self-center">{$i18n.t('Upload')}</span>
  512. {/if}
  513. </button>
  514. </div>
  515. </div>
  516. <div>
  517. <div class=" py-0.5 flex w-full justify-between">
  518. <div class=" self-center text-xs">{$i18n.t('Allow User Location')}</div>
  519. <button
  520. class="p-1 px-3 text-xs flex rounded-sm transition"
  521. on:click={() => {
  522. toggleUserLocation();
  523. }}
  524. type="button"
  525. >
  526. {#if userLocation === true}
  527. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  528. {:else}
  529. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  530. {/if}
  531. </button>
  532. </div>
  533. </div>
  534. <div>
  535. <div class=" py-0.5 flex w-full justify-between">
  536. <div class=" self-center text-xs">{$i18n.t('Haptic Feedback')}</div>
  537. <button
  538. class="p-1 px-3 text-xs flex rounded-sm transition"
  539. on:click={() => {
  540. toggleHapticFeedback();
  541. }}
  542. type="button"
  543. >
  544. {#if hapticFeedback === true}
  545. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  546. {:else}
  547. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  548. {/if}
  549. </button>
  550. </div>
  551. </div>
  552. <!-- <div>
  553. <div class=" py-0.5 flex w-full justify-between">
  554. <div class=" self-center text-xs">
  555. {$i18n.t('Fluidly stream large external response chunks')}
  556. </div>
  557. <button
  558. class="p-1 px-3 text-xs flex rounded-sm transition"
  559. on:click={() => {
  560. toggleSplitLargeChunks();
  561. }}
  562. type="button"
  563. >
  564. {#if splitLargeChunks === true}
  565. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  566. {:else}
  567. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  568. {/if}
  569. </button>
  570. </div>
  571. </div> -->
  572. <div>
  573. <div class=" py-0.5 flex w-full justify-between">
  574. <div class=" self-center text-xs">
  575. {$i18n.t('Enter Key Behavior')}
  576. </div>
  577. <button
  578. class="p-1 px-3 text-xs flex rounded transition"
  579. on:click={() => {
  580. togglectrlEnterToSend();
  581. }}
  582. type="button"
  583. >
  584. {#if ctrlEnterToSend === true}
  585. <span class="ml-2 self-center">{$i18n.t('Ctrl+Enter to Send')}</span>
  586. {:else}
  587. <span class="ml-2 self-center">{$i18n.t('Enter to Send')}</span>
  588. {/if}
  589. </button>
  590. </div>
  591. </div>
  592. <div>
  593. <div class=" py-0.5 flex w-full justify-between">
  594. <div class=" self-center text-xs">
  595. {$i18n.t('Scroll to bottom when switching between branches')}
  596. </div>
  597. <button
  598. class="p-1 px-3 text-xs flex rounded-sm transition"
  599. on:click={() => {
  600. togglesScrollOnBranchChange();
  601. }}
  602. type="button"
  603. >
  604. {#if scrollOnBranchChange === true}
  605. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  606. {:else}
  607. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  608. {/if}
  609. </button>
  610. </div>
  611. </div>
  612. <div>
  613. <div class=" py-0.5 flex w-full justify-between">
  614. <div class=" self-center text-xs">{$i18n.t('Web Search in Chat')}</div>
  615. <button
  616. class="p-1 px-3 text-xs flex rounded-sm transition"
  617. on:click={() => {
  618. toggleWebSearch();
  619. }}
  620. type="button"
  621. >
  622. {#if webSearch === 'always'}
  623. <span class="ml-2 self-center">{$i18n.t('Always')}</span>
  624. {:else}
  625. <span class="ml-2 self-center">{$i18n.t('Default')}</span>
  626. {/if}
  627. </button>
  628. </div>
  629. </div>
  630. <div class=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
  631. <div>
  632. <div class=" py-0.5 flex w-full justify-between">
  633. <div class=" self-center text-xs">{$i18n.t('Allow Voice Interruption in Call')}</div>
  634. <button
  635. class="p-1 px-3 text-xs flex rounded-sm transition"
  636. on:click={() => {
  637. toggleVoiceInterruption();
  638. }}
  639. type="button"
  640. >
  641. {#if voiceInterruption === true}
  642. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  643. {:else}
  644. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  645. {/if}
  646. </button>
  647. </div>
  648. </div>
  649. <div>
  650. <div class=" py-0.5 flex w-full justify-between">
  651. <div class=" self-center text-xs">{$i18n.t('Display Emoji in Call')}</div>
  652. <button
  653. class="p-1 px-3 text-xs flex rounded-sm transition"
  654. on:click={() => {
  655. toggleEmojiInCall();
  656. }}
  657. type="button"
  658. >
  659. {#if showEmojiInCall === true}
  660. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  661. {:else}
  662. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  663. {/if}
  664. </button>
  665. </div>
  666. </div>
  667. <div class=" my-1.5 text-sm font-medium">{$i18n.t('File')}</div>
  668. <div>
  669. <div class=" py-0.5 flex w-full justify-between">
  670. <div class=" self-center text-xs">{$i18n.t('Image Compression')}</div>
  671. <button
  672. class="p-1 px-3 text-xs flex rounded-sm transition"
  673. on:click={() => {
  674. toggleImageCompression();
  675. }}
  676. type="button"
  677. >
  678. {#if imageCompression === true}
  679. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  680. {:else}
  681. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  682. {/if}
  683. </button>
  684. </div>
  685. </div>
  686. {#if imageCompression}
  687. <div>
  688. <div class=" py-0.5 flex w-full justify-between text-xs">
  689. <div class=" self-center text-xs">{$i18n.t('Image Max Compression Size')}</div>
  690. <div>
  691. <input
  692. bind:value={imageCompressionSize.width}
  693. type="number"
  694. class="w-20 bg-transparent outline-hidden text-center"
  695. min="0"
  696. placeholder="Width"
  697. />x
  698. <input
  699. bind:value={imageCompressionSize.height}
  700. type="number"
  701. class="w-20 bg-transparent outline-hidden text-center"
  702. min="0"
  703. placeholder="Height"
  704. />
  705. </div>
  706. </div>
  707. </div>
  708. {/if}
  709. </div>
  710. </div>
  711. <div class="flex justify-end text-sm font-medium">
  712. <button
  713. class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
  714. type="submit"
  715. >
  716. {$i18n.t('Save')}
  717. </button>
  718. </div>
  719. </form>