Interface.svelte 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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. const dispatch = createEventDispatcher();
  9. const i18n = getContext('i18n');
  10. export let saveSettings: Function;
  11. // Addons
  12. let titleAutoGenerate = true;
  13. let responseAutoCopy = false;
  14. let widescreenMode = false;
  15. let splitLargeChunks = false;
  16. // Interface
  17. let defaultModelId = '';
  18. let showUsername = false;
  19. let chatBubble = true;
  20. let chatDirection: 'LTR' | 'RTL' = 'LTR';
  21. let showEmojiInCall = false;
  22. const toggleSplitLargeChunks = async () => {
  23. splitLargeChunks = !splitLargeChunks;
  24. saveSettings({ splitLargeChunks: splitLargeChunks });
  25. };
  26. const togglewidescreenMode = async () => {
  27. widescreenMode = !widescreenMode;
  28. saveSettings({ widescreenMode: widescreenMode });
  29. };
  30. const toggleChatBubble = async () => {
  31. chatBubble = !chatBubble;
  32. saveSettings({ chatBubble: chatBubble });
  33. };
  34. const toggleShowUsername = async () => {
  35. showUsername = !showUsername;
  36. saveSettings({ showUsername: showUsername });
  37. };
  38. const toggleEmojiInCall = async () => {
  39. showEmojiInCall = !showEmojiInCall;
  40. saveSettings({ showEmojiInCall: showEmojiInCall });
  41. };
  42. const toggleTitleAutoGenerate = async () => {
  43. titleAutoGenerate = !titleAutoGenerate;
  44. saveSettings({
  45. title: {
  46. ...$settings.title,
  47. auto: titleAutoGenerate
  48. }
  49. });
  50. };
  51. const toggleResponseAutoCopy = async () => {
  52. const permission = await navigator.clipboard
  53. .readText()
  54. .then(() => {
  55. return 'granted';
  56. })
  57. .catch(() => {
  58. return '';
  59. });
  60. console.log(permission);
  61. if (permission === 'granted') {
  62. responseAutoCopy = !responseAutoCopy;
  63. saveSettings({ responseAutoCopy: responseAutoCopy });
  64. } else {
  65. toast.error(
  66. 'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
  67. );
  68. }
  69. };
  70. const toggleChangeChatDirection = async () => {
  71. chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
  72. saveSettings({ chatDirection });
  73. };
  74. const updateInterfaceHandler = async () => {
  75. saveSettings({
  76. models: [defaultModelId]
  77. });
  78. };
  79. onMount(async () => {
  80. titleAutoGenerate = $settings?.title?.auto ?? true;
  81. responseAutoCopy = $settings.responseAutoCopy ?? false;
  82. showUsername = $settings.showUsername ?? false;
  83. showEmojiInCall = $settings.showEmojiInCall ?? false;
  84. chatBubble = $settings.chatBubble ?? true;
  85. widescreenMode = $settings.widescreenMode ?? false;
  86. splitLargeChunks = $settings.splitLargeChunks ?? false;
  87. chatDirection = $settings.chatDirection ?? 'LTR';
  88. defaultModelId = ($settings?.models ?? ['']).at(0);
  89. });
  90. </script>
  91. <form
  92. class="flex flex-col h-full justify-between space-y-3 text-sm"
  93. on:submit|preventDefault={() => {
  94. updateInterfaceHandler();
  95. dispatch('save');
  96. }}
  97. >
  98. <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
  99. <div>
  100. <div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
  101. <div>
  102. <div class=" py-0.5 flex w-full justify-between">
  103. <div class=" self-center text-xs font-medium">{$i18n.t('Chat Bubble UI')}</div>
  104. <button
  105. class="p-1 px-3 text-xs flex rounded transition"
  106. on:click={() => {
  107. toggleChatBubble();
  108. }}
  109. type="button"
  110. >
  111. {#if chatBubble === true}
  112. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  113. {:else}
  114. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  115. {/if}
  116. </button>
  117. </div>
  118. </div>
  119. <div>
  120. <div class=" py-0.5 flex w-full justify-between">
  121. <div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
  122. <button
  123. class="p-1 px-3 text-xs flex rounded transition"
  124. on:click={() => {
  125. toggleTitleAutoGenerate();
  126. }}
  127. type="button"
  128. >
  129. {#if titleAutoGenerate === true}
  130. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  131. {:else}
  132. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  133. {/if}
  134. </button>
  135. </div>
  136. </div>
  137. <div>
  138. <div class=" py-0.5 flex w-full justify-between">
  139. <div class=" self-center text-xs font-medium">
  140. {$i18n.t('Response AutoCopy to Clipboard')}
  141. </div>
  142. <button
  143. class="p-1 px-3 text-xs flex rounded transition"
  144. on:click={() => {
  145. toggleResponseAutoCopy();
  146. }}
  147. type="button"
  148. >
  149. {#if responseAutoCopy === true}
  150. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  151. {:else}
  152. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  153. {/if}
  154. </button>
  155. </div>
  156. </div>
  157. <div>
  158. <div class=" py-0.5 flex w-full justify-between">
  159. <div class=" self-center text-xs font-medium">{$i18n.t('Widescreen Mode')}</div>
  160. <button
  161. class="p-1 px-3 text-xs flex rounded transition"
  162. on:click={() => {
  163. togglewidescreenMode();
  164. }}
  165. type="button"
  166. >
  167. {#if widescreenMode === true}
  168. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  169. {:else}
  170. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  171. {/if}
  172. </button>
  173. </div>
  174. </div>
  175. <div>
  176. <div class=" py-0.5 flex w-full justify-between">
  177. <div class=" self-center text-xs font-medium">{$i18n.t('Display Emoji in Call')}</div>
  178. <button
  179. class="p-1 px-3 text-xs flex rounded transition"
  180. on:click={() => {
  181. toggleEmojiInCall();
  182. }}
  183. type="button"
  184. >
  185. {#if showEmojiInCall === true}
  186. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  187. {:else}
  188. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  189. {/if}
  190. </button>
  191. </div>
  192. </div>
  193. {#if !$settings.chatBubble}
  194. <div>
  195. <div class=" py-0.5 flex w-full justify-between">
  196. <div class=" self-center text-xs font-medium">
  197. {$i18n.t('Display the username instead of You in the Chat')}
  198. </div>
  199. <button
  200. class="p-1 px-3 text-xs flex rounded transition"
  201. on:click={() => {
  202. toggleShowUsername();
  203. }}
  204. type="button"
  205. >
  206. {#if showUsername === true}
  207. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  208. {:else}
  209. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  210. {/if}
  211. </button>
  212. </div>
  213. </div>
  214. {/if}
  215. <div>
  216. <div class=" py-0.5 flex w-full justify-between">
  217. <div class=" self-center text-xs font-medium">
  218. {$i18n.t('Fluidly stream large external response chunks')}
  219. </div>
  220. <button
  221. class="p-1 px-3 text-xs flex rounded transition"
  222. on:click={() => {
  223. toggleSplitLargeChunks();
  224. }}
  225. type="button"
  226. >
  227. {#if splitLargeChunks === true}
  228. <span class="ml-2 self-center">{$i18n.t('On')}</span>
  229. {:else}
  230. <span class="ml-2 self-center">{$i18n.t('Off')}</span>
  231. {/if}
  232. </button>
  233. </div>
  234. </div>
  235. </div>
  236. <div>
  237. <div class=" py-0.5 flex w-full justify-between">
  238. <div class=" self-center text-xs font-medium">{$i18n.t('Chat direction')}</div>
  239. <button
  240. class="p-1 px-3 text-xs flex rounded transition"
  241. on:click={toggleChangeChatDirection}
  242. type="button"
  243. >
  244. {#if chatDirection === 'LTR'}
  245. <span class="ml-2 self-center">{$i18n.t('LTR')}</span>
  246. {:else}
  247. <span class="ml-2 self-center">{$i18n.t('RTL')}</span>
  248. {/if}
  249. </button>
  250. </div>
  251. </div>
  252. <hr class=" dark:border-gray-850" />
  253. <div class=" space-y-1 mb-3">
  254. <div class="mb-2">
  255. <div class="flex justify-between items-center text-xs">
  256. <div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
  257. </div>
  258. </div>
  259. <div class="flex-1 mr-2">
  260. <select
  261. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  262. bind:value={defaultModelId}
  263. placeholder="Select a model"
  264. >
  265. <option value="" disabled selected>{$i18n.t('Select a model')}</option>
  266. {#each $models.filter((model) => model.id) as model}
  267. <option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
  268. {/each}
  269. </select>
  270. </div>
  271. </div>
  272. </div>
  273. <div class="flex justify-end text-sm font-medium">
  274. <button
  275. class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
  276. type="submit"
  277. >
  278. {$i18n.t('Save')}
  279. </button>
  280. </div>
  281. </form>