Interface.svelte 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <script lang="ts">
  2. import { getBackendConfig } from '$lib/apis';
  3. import { setDefaultPromptSuggestions } from '$lib/apis/configs';
  4. import { config, models, user, showWhatsChanged } from '$lib/stores';
  5. import { createEventDispatcher, onMount } from 'svelte';
  6. import toast from 'svelte-french-toast';
  7. const dispatch = createEventDispatcher();
  8. export let saveSettings: Function;
  9. // Addons
  10. let titleAutoGenerate = true;
  11. let responseAutoCopy = false;
  12. let titleAutoGenerateModel = '';
  13. let fullScreenMode = false;
  14. let titleGenerationPrompt = '';
  15. // Interface
  16. let promptSuggestions = [];
  17. let showUsername = false;
  18. let enableWhatsChanged = true;
  19. const toggleFullScreenMode = async () => {
  20. fullScreenMode = !fullScreenMode;
  21. saveSettings({ fullScreenMode: fullScreenMode });
  22. };
  23. const toggleShowUsername = async () => {
  24. showUsername = !showUsername;
  25. saveSettings({ showUsername: showUsername });
  26. };
  27. const toggleenableWhatsChanged = async () => {
  28. enableWhatsChanged = !enableWhatsChanged;
  29. if (enableWhatsChanged) {
  30. showWhatsChanged.update((value) => true);
  31. }
  32. saveSettings({ enableWhatsChanged: enableWhatsChanged });
  33. };
  34. const toggleTitleAutoGenerate = async () => {
  35. titleAutoGenerate = !titleAutoGenerate;
  36. saveSettings({ titleAutoGenerate: titleAutoGenerate });
  37. };
  38. const toggleResponseAutoCopy = async () => {
  39. const permission = await navigator.clipboard
  40. .readText()
  41. .then(() => {
  42. return 'granted';
  43. })
  44. .catch(() => {
  45. return '';
  46. });
  47. console.log(permission);
  48. if (permission === 'granted') {
  49. responseAutoCopy = !responseAutoCopy;
  50. saveSettings({ responseAutoCopy: responseAutoCopy });
  51. } else {
  52. toast.error(
  53. 'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
  54. );
  55. }
  56. };
  57. const updateInterfaceHandler = async () => {
  58. if ($user.role === 'admin') {
  59. promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
  60. await config.set(await getBackendConfig());
  61. }
  62. saveSettings({
  63. titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
  64. });
  65. };
  66. onMount(async () => {
  67. if ($user.role === 'admin') {
  68. promptSuggestions = $config?.default_prompt_suggestions;
  69. }
  70. let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
  71. titleAutoGenerate = settings.titleAutoGenerate ?? true;
  72. responseAutoCopy = settings.responseAutoCopy ?? false;
  73. showUsername = settings.showUsername ?? false;
  74. enableWhatsChanged = settings.enableWhatsChanged ?? true;
  75. fullScreenMode = settings.fullScreenMode ?? false;
  76. titleAutoGenerateModel = settings.titleAutoGenerateModel ?? '';
  77. titleGenerationPrompt =
  78. settings.titleGenerationPrompt ??
  79. `Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title': {{prompt}}`;
  80. });
  81. </script>
  82. <form
  83. class="flex flex-col h-full justify-between space-y-3 text-sm"
  84. on:submit|preventDefault={() => {
  85. updateInterfaceHandler();
  86. dispatch('save');
  87. }}
  88. >
  89. <div class=" space-y-3 pr-1.5 overflow-y-scroll h-80">
  90. <div>
  91. <div class=" mb-1 text-sm font-medium">WebUI Add-ons</div>
  92. <div>
  93. <div class=" py-0.5 flex w-full justify-between">
  94. <div class=" self-center text-xs font-medium">Title Auto-Generation</div>
  95. <button
  96. class="p-1 px-3 text-xs flex rounded transition"
  97. on:click={() => {
  98. toggleTitleAutoGenerate();
  99. }}
  100. type="button"
  101. >
  102. {#if titleAutoGenerate === true}
  103. <span class="ml-2 self-center">On</span>
  104. {:else}
  105. <span class="ml-2 self-center">Off</span>
  106. {/if}
  107. </button>
  108. </div>
  109. </div>
  110. <div>
  111. <div class=" py-0.5 flex w-full justify-between">
  112. <div class=" self-center text-xs font-medium">Response AutoCopy to Clipboard</div>
  113. <button
  114. class="p-1 px-3 text-xs flex rounded transition"
  115. on:click={() => {
  116. toggleResponseAutoCopy();
  117. }}
  118. type="button"
  119. >
  120. {#if responseAutoCopy === true}
  121. <span class="ml-2 self-center">On</span>
  122. {:else}
  123. <span class="ml-2 self-center">Off</span>
  124. {/if}
  125. </button>
  126. </div>
  127. </div>
  128. <div>
  129. <div class=" py-0.5 flex w-full justify-between">
  130. <div class=" self-center text-xs font-medium">Full Screen Mode</div>
  131. <button
  132. class="p-1 px-3 text-xs flex rounded transition"
  133. on:click={() => {
  134. toggleFullScreenMode();
  135. }}
  136. type="button"
  137. >
  138. {#if fullScreenMode === true}
  139. <span class="ml-2 self-center">On</span>
  140. {:else}
  141. <span class="ml-2 self-center">Off</span>
  142. {/if}
  143. </button>
  144. </div>
  145. </div>
  146. <div>
  147. <div class=" py-0.5 flex w-full justify-between">
  148. <div class=" self-center text-xs font-medium">
  149. Display the username instead of "You" in the Chat
  150. </div>
  151. <button
  152. class="p-1 px-3 text-xs flex rounded transition"
  153. on:click={() => {
  154. toggleShowUsername();
  155. }}
  156. type="button"
  157. >
  158. {#if showUsername === true}
  159. <span class="ml-2 self-center">On</span>
  160. {:else}
  161. <span class="ml-2 self-center">Off</span>
  162. {/if}
  163. </button>
  164. </div>
  165. </div>
  166. <div>
  167. <div class=" py-0.5 flex w-full justify-between">
  168. <div class=" self-center text-xs font-medium">Show "WhatsChanged" Modal on Startup</div>
  169. <button
  170. class="p-1 px-3 text-xs flex rounded transition"
  171. on:click={() => {
  172. toggleenableWhatsChanged();
  173. }}
  174. type="button"
  175. >
  176. {#if enableWhatsChanged === true}
  177. <span class="ml-2 self-center">On</span>
  178. {:else}
  179. <span class="ml-2 self-center">Off</span>
  180. {/if}
  181. </button>
  182. </div>
  183. </div>
  184. </div>
  185. <hr class=" dark:border-gray-700" />
  186. <div>
  187. <div class=" mb-2.5 text-sm font-medium">Set Title Auto-Generation Model</div>
  188. <div class="flex w-full">
  189. <div class="flex-1 mr-2">
  190. <select
  191. class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none"
  192. bind:value={titleAutoGenerateModel}
  193. placeholder="Select a model"
  194. >
  195. <option value="" selected>Current Model</option>
  196. {#each $models.filter((m) => m.size != null) as model}
  197. <option value={model.name} class="bg-gray-100 dark:bg-gray-700"
  198. >{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
  199. >
  200. {/each}
  201. </select>
  202. </div>
  203. <button
  204. class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
  205. on:click={() => {
  206. saveSettings({
  207. titleAutoGenerateModel:
  208. titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined
  209. });
  210. }}
  211. type="button"
  212. >
  213. <svg
  214. xmlns="http://www.w3.org/2000/svg"
  215. viewBox="0 0 16 16"
  216. fill="currentColor"
  217. class="w-3.5 h-3.5"
  218. >
  219. <path
  220. fill-rule="evenodd"
  221. d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
  222. clip-rule="evenodd"
  223. />
  224. </svg>
  225. </button>
  226. </div>
  227. <div class="mt-3">
  228. <div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div>
  229. <textarea
  230. bind:value={titleGenerationPrompt}
  231. class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
  232. rows="3"
  233. />
  234. </div>
  235. </div>
  236. {#if $user.role === 'admin'}
  237. <hr class=" dark:border-gray-700" />
  238. <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
  239. <div class="flex w-full justify-between mb-2">
  240. <div class=" self-center text-sm font-semibold">Default Prompt Suggestions</div>
  241. <button
  242. class="p-1 px-3 text-xs flex rounded transition"
  243. type="button"
  244. on:click={() => {
  245. if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
  246. promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
  247. }
  248. }}
  249. >
  250. <svg
  251. xmlns="http://www.w3.org/2000/svg"
  252. viewBox="0 0 20 20"
  253. fill="currentColor"
  254. class="w-4 h-4"
  255. >
  256. <path
  257. d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
  258. />
  259. </svg>
  260. </button>
  261. </div>
  262. <div class="flex flex-col space-y-1">
  263. {#each promptSuggestions as prompt, promptIdx}
  264. <div class=" flex border dark:border-gray-600 rounded-lg">
  265. <div class="flex flex-col flex-1">
  266. <div class="flex border-b dark:border-gray-600 w-full">
  267. <input
  268. class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
  269. placeholder="Title (e.g. Tell me a fun fact)"
  270. bind:value={prompt.title[0]}
  271. />
  272. <input
  273. class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
  274. placeholder="Subtitle (e.g. about the Roman Empire)"
  275. bind:value={prompt.title[1]}
  276. />
  277. </div>
  278. <input
  279. class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
  280. placeholder="Prompt (e.g. Tell me a fun fact about the Roman Empire)"
  281. bind:value={prompt.content}
  282. />
  283. </div>
  284. <button
  285. class="px-2"
  286. type="button"
  287. on:click={() => {
  288. promptSuggestions.splice(promptIdx, 1);
  289. promptSuggestions = promptSuggestions;
  290. }}
  291. >
  292. <svg
  293. xmlns="http://www.w3.org/2000/svg"
  294. viewBox="0 0 20 20"
  295. fill="currentColor"
  296. class="w-4 h-4"
  297. >
  298. <path
  299. 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"
  300. />
  301. </svg>
  302. </button>
  303. </div>
  304. {/each}
  305. </div>
  306. {#if promptSuggestions.length > 0}
  307. <div class="text-xs text-left w-full mt-2">
  308. Adjusting these settings will apply changes universally to all users.
  309. </div>
  310. {/if}
  311. </div>
  312. {/if}
  313. </div>
  314. <div class="flex justify-end pt-3 text-sm font-medium">
  315. <button
  316. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
  317. type="submit"
  318. >
  319. Save
  320. </button>
  321. </div>
  322. </form>