Connections.svelte 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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-[22rem] 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. {$i18n.t('WebUI will make requests to')}
  133. <span class=" text-gray-200">'{url}/models'</span>
  134. </div>
  135. {/each}
  136. </div>
  137. {/if}
  138. </div>
  139. </div>
  140. <hr class=" dark:border-gray-700" />
  141. <div>
  142. <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Base URL')}</div>
  143. <div class="flex w-full gap-1.5">
  144. <div class="flex-1 flex flex-col gap-2">
  145. {#each OLLAMA_BASE_URLS as url, idx}
  146. <div class="flex gap-1.5">
  147. <input
  148. class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
  149. placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
  150. bind:value={url}
  151. />
  152. <div class="self-center flex items-center">
  153. {#if idx === 0}
  154. <button
  155. class="px-1"
  156. on:click={() => {
  157. OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
  158. }}
  159. type="button"
  160. >
  161. <svg
  162. xmlns="http://www.w3.org/2000/svg"
  163. viewBox="0 0 16 16"
  164. fill="currentColor"
  165. class="w-4 h-4"
  166. >
  167. <path
  168. 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"
  169. />
  170. </svg>
  171. </button>
  172. {:else}
  173. <button
  174. class="px-1"
  175. on:click={() => {
  176. OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
  177. }}
  178. type="button"
  179. >
  180. <svg
  181. xmlns="http://www.w3.org/2000/svg"
  182. viewBox="0 0 16 16"
  183. fill="currentColor"
  184. class="w-4 h-4"
  185. >
  186. <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" />
  187. </svg>
  188. </button>
  189. {/if}
  190. </div>
  191. </div>
  192. {/each}
  193. </div>
  194. <div class="">
  195. <button
  196. class="p-2.5 bg-gray-200 hover:bg-gray-300 dark:bg-gray-850 dark:hover:bg-gray-800 rounded-lg transition"
  197. on:click={() => {
  198. updateOllamaUrlsHandler();
  199. }}
  200. type="button"
  201. >
  202. <svg
  203. xmlns="http://www.w3.org/2000/svg"
  204. viewBox="0 0 20 20"
  205. fill="currentColor"
  206. class="w-4 h-4"
  207. >
  208. <path
  209. fill-rule="evenodd"
  210. 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"
  211. clip-rule="evenodd"
  212. />
  213. </svg>
  214. </button>
  215. </div>
  216. </div>
  217. <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
  218. {$i18n.t('Trouble accessing Ollama?')}
  219. <a
  220. class=" text-gray-300 font-medium underline"
  221. href="https://github.com/open-webui/open-webui#troubleshooting"
  222. target="_blank"
  223. >
  224. {$i18n.t('Click here for help.')}
  225. </a>
  226. </div>
  227. </div>
  228. </div>
  229. <div class="flex justify-end pt-3 text-sm font-medium">
  230. <button
  231. class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
  232. type="submit"
  233. >
  234. {$i18n.t('Save')}
  235. </button>
  236. </div>
  237. </form>