Connections.svelte 7.7 KB

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