Connections.svelte 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <script lang="ts">
  2. import { models, user } from '$lib/stores';
  3. import { createEventDispatcher, onMount } from 'svelte';
  4. const dispatch = createEventDispatcher();
  5. import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
  6. import {
  7. getOpenAIKeys,
  8. getOpenAIUrls,
  9. updateOpenAIKeys,
  10. updateOpenAIUrls
  11. } from '$lib/apis/openai';
  12. import { toast } from 'svelte-sonner';
  13. export let getModels: Function;
  14. // External
  15. let OLLAMA_BASE_URL = '';
  16. let OLLAMA_BASE_URLS = [''];
  17. let OPENAI_API_KEY = '';
  18. let OPENAI_API_BASE_URL = '';
  19. let OPENAI_API_KEYS = [''];
  20. let OPENAI_API_BASE_URLS = [''];
  21. let showOpenAI = false;
  22. const updateOpenAIHandler = async () => {
  23. OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
  24. OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
  25. await models.set(await getModels());
  26. };
  27. const updateOllamaUrlsHandler = async () => {
  28. OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
  29. const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
  30. toast.error(error);
  31. return null;
  32. });
  33. if (ollamaVersion) {
  34. toast.success('Server connection verified');
  35. await models.set(await getModels());
  36. }
  37. };
  38. onMount(async () => {
  39. if ($user.role === 'admin') {
  40. OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
  41. OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
  42. OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
  43. }
  44. });
  45. </script>
  46. <form
  47. class="flex flex-col h-full justify-between text-sm"
  48. on:submit|preventDefault={() => {
  49. updateOpenAIHandler();
  50. dispatch('save');
  51. }}
  52. >
  53. <div class=" pr-1.5 overflow-y-scroll max-h-[20.5rem] space-y-3">
  54. <div class=" space-y-3">
  55. <div class="mt-2 space-y-2 pr-1.5">
  56. <div class="flex justify-between items-center text-sm">
  57. <div class=" font-medium">OpenAI API</div>
  58. <button
  59. class=" text-xs font-medium text-gray-500"
  60. type="button"
  61. on:click={() => {
  62. showOpenAI = !showOpenAI;
  63. }}>{showOpenAI ? 'Hide' : 'Show'}</button
  64. >
  65. </div>
  66. {#if showOpenAI}
  67. <div class="flex flex-col gap-1">
  68. {#each OPENAI_API_BASE_URLS as url, idx}
  69. <div class="flex w-full gap-2">
  70. <div class="flex-1">
  71. <input
  72. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  73. placeholder="API Base URL"
  74. bind:value={url}
  75. autocomplete="off"
  76. />
  77. </div>
  78. <div class="flex-1">
  79. <input
  80. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  81. placeholder="API Key"
  82. bind:value={OPENAI_API_KEYS[idx]}
  83. autocomplete="off"
  84. />
  85. </div>
  86. <div class="self-center flex items-center">
  87. {#if idx === 0}
  88. <button
  89. class="px-1"
  90. on:click={() => {
  91. OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
  92. OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
  93. }}
  94. type="button"
  95. >
  96. <svg
  97. xmlns="http://www.w3.org/2000/svg"
  98. viewBox="0 0 16 16"
  99. fill="currentColor"
  100. class="w-4 h-4"
  101. >
  102. <path
  103. d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
  104. />
  105. </svg>
  106. </button>
  107. {:else}
  108. <button
  109. class="px-1"
  110. on:click={() => {
  111. OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
  112. (url, urlIdx) => idx !== urlIdx
  113. );
  114. OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
  115. }}
  116. type="button"
  117. >
  118. <svg
  119. xmlns="http://www.w3.org/2000/svg"
  120. viewBox="0 0 16 16"
  121. fill="currentColor"
  122. class="w-4 h-4"
  123. >
  124. <path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
  125. </svg>
  126. </button>
  127. {/if}
  128. </div>
  129. </div>
  130. <div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
  131. WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span>
  132. </div>
  133. {/each}
  134. </div>
  135. {/if}
  136. </div>
  137. </div>
  138. <hr class=" dark:border-gray-700" />
  139. <div>
  140. <div class=" mb-2.5 text-sm font-medium">Ollama Base URL</div>
  141. <div class="flex w-full gap-1.5">
  142. <div class="flex-1 flex flex-col gap-2">
  143. {#each OLLAMA_BASE_URLS as url, idx}
  144. <div class="flex gap-1.5">
  145. <input
  146. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  147. placeholder="Enter URL (e.g. http://localhost:11434)"
  148. bind:value={url}
  149. />
  150. <div class="self-center flex items-center">
  151. {#if idx === 0}
  152. <button
  153. class="px-1"
  154. on:click={() => {
  155. OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
  156. }}
  157. type="button"
  158. >
  159. <svg
  160. xmlns="http://www.w3.org/2000/svg"
  161. viewBox="0 0 16 16"
  162. fill="currentColor"
  163. class="w-4 h-4"
  164. >
  165. <path
  166. d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
  167. />
  168. </svg>
  169. </button>
  170. {:else}
  171. <button
  172. class="px-1"
  173. on:click={() => {
  174. OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
  175. }}
  176. type="button"
  177. >
  178. <svg
  179. xmlns="http://www.w3.org/2000/svg"
  180. viewBox="0 0 16 16"
  181. fill="currentColor"
  182. class="w-4 h-4"
  183. >
  184. <path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
  185. </svg>
  186. </button>
  187. {/if}
  188. </div>
  189. </div>
  190. {/each}
  191. </div>
  192. <div class="">
  193. <button
  194. class="p-2.5 bg-gray-200 hover:bg-gray-300 dark:bg-gray-850 dark:hover:bg-gray-800 rounded-lg transition"
  195. on:click={() => {
  196. updateOllamaUrlsHandler();
  197. }}
  198. type="button"
  199. >
  200. <svg
  201. xmlns="http://www.w3.org/2000/svg"
  202. viewBox="0 0 20 20"
  203. fill="currentColor"
  204. class="w-4 h-4"
  205. >
  206. <path
  207. fill-rule="evenodd"
  208. d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
  209. clip-rule="evenodd"
  210. />
  211. </svg>
  212. </button>
  213. </div>
  214. </div>
  215. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  216. Trouble accessing Ollama?
  217. <a
  218. class=" text-gray-300 font-medium"
  219. href="https://github.com/open-webui/open-webui#troubleshooting"
  220. target="_blank"
  221. >
  222. Click here for help.
  223. </a>
  224. </div>
  225. </div>
  226. </div>
  227. <div class="flex justify-end pt-3 text-sm font-medium">
  228. <button
  229. class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
  230. type="submit"
  231. >
  232. Save
  233. </button>
  234. </div>
  235. </form>