Connections.svelte 7.5 KB

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