Connections.svelte 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
  4. const dispatch = createEventDispatcher();
  5. import { getOllamaConfig, updateOllamaConfig } from '$lib/apis/ollama';
  6. import { getOpenAIConfig, updateOpenAIConfig, getOpenAIModels } from '$lib/apis/openai';
  7. import { getModels as _getModels } from '$lib/apis';
  8. import { models, user } from '$lib/stores';
  9. import Switch from '$lib/components/common/Switch.svelte';
  10. import Spinner from '$lib/components/common/Spinner.svelte';
  11. import Tooltip from '$lib/components/common/Tooltip.svelte';
  12. import Plus from '$lib/components/icons/Plus.svelte';
  13. import OpenAIConnection from './Connections/OpenAIConnection.svelte';
  14. import AddConnectionModal from './Connections/AddConnectionModal.svelte';
  15. import OllamaConnection from './Connections/OllamaConnection.svelte';
  16. const i18n = getContext('i18n');
  17. const getModels = async () => {
  18. const models = await _getModels(localStorage.token);
  19. return models;
  20. };
  21. // External
  22. let OLLAMA_BASE_URLS = [''];
  23. let OLLAMA_API_CONFIGS = {};
  24. let OPENAI_API_KEYS = [''];
  25. let OPENAI_API_BASE_URLS = [''];
  26. let OPENAI_API_CONFIGS = {};
  27. let ENABLE_OPENAI_API: null | boolean = null;
  28. let ENABLE_OLLAMA_API: null | boolean = null;
  29. let pipelineUrls = {};
  30. let showAddOpenAIConnectionModal = false;
  31. let showAddOllamaConnectionModal = false;
  32. const updateOpenAIHandler = async () => {
  33. if (ENABLE_OPENAI_API !== null) {
  34. // Remove trailing slashes
  35. OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.map((url) => url.replace(/\/$/, ''));
  36. // Check if API KEYS length is same than API URLS length
  37. if (OPENAI_API_KEYS.length !== OPENAI_API_BASE_URLS.length) {
  38. // if there are more keys than urls, remove the extra keys
  39. if (OPENAI_API_KEYS.length > OPENAI_API_BASE_URLS.length) {
  40. OPENAI_API_KEYS = OPENAI_API_KEYS.slice(0, OPENAI_API_BASE_URLS.length);
  41. }
  42. // if there are more urls than keys, add empty keys
  43. if (OPENAI_API_KEYS.length < OPENAI_API_BASE_URLS.length) {
  44. const diff = OPENAI_API_BASE_URLS.length - OPENAI_API_KEYS.length;
  45. for (let i = 0; i < diff; i++) {
  46. OPENAI_API_KEYS.push('');
  47. }
  48. }
  49. }
  50. const res = await updateOpenAIConfig(localStorage.token, {
  51. ENABLE_OPENAI_API: ENABLE_OPENAI_API,
  52. OPENAI_API_BASE_URLS: OPENAI_API_BASE_URLS,
  53. OPENAI_API_KEYS: OPENAI_API_KEYS,
  54. OPENAI_API_CONFIGS: OPENAI_API_CONFIGS
  55. }).catch((error) => {
  56. toast.error(`${error}`);
  57. });
  58. if (res) {
  59. toast.success($i18n.t('OpenAI API settings updated'));
  60. await models.set(await getModels());
  61. }
  62. }
  63. };
  64. const updateOllamaHandler = async () => {
  65. if (ENABLE_OLLAMA_API !== null) {
  66. // Remove trailing slashes
  67. OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.map((url) => url.replace(/\/$/, ''));
  68. const res = await updateOllamaConfig(localStorage.token, {
  69. ENABLE_OLLAMA_API: ENABLE_OLLAMA_API,
  70. OLLAMA_BASE_URLS: OLLAMA_BASE_URLS,
  71. OLLAMA_API_CONFIGS: OLLAMA_API_CONFIGS
  72. }).catch((error) => {
  73. toast.error(`${error}`);
  74. });
  75. if (res) {
  76. toast.success($i18n.t('Ollama API settings updated'));
  77. await models.set(await getModels());
  78. }
  79. }
  80. };
  81. const addOpenAIConnectionHandler = async (connection) => {
  82. OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, connection.url];
  83. OPENAI_API_KEYS = [...OPENAI_API_KEYS, connection.key];
  84. OPENAI_API_CONFIGS[OPENAI_API_BASE_URLS.length - 1] = connection.config;
  85. await updateOpenAIHandler();
  86. };
  87. const addOllamaConnectionHandler = async (connection) => {
  88. OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, connection.url];
  89. OLLAMA_API_CONFIGS[OLLAMA_BASE_URLS.length - 1] = {
  90. ...connection.config,
  91. key: connection.key
  92. };
  93. await updateOllamaHandler();
  94. };
  95. onMount(async () => {
  96. if ($user.role === 'admin') {
  97. let ollamaConfig = {};
  98. let openaiConfig = {};
  99. await Promise.all([
  100. (async () => {
  101. ollamaConfig = await getOllamaConfig(localStorage.token);
  102. })(),
  103. (async () => {
  104. openaiConfig = await getOpenAIConfig(localStorage.token);
  105. })()
  106. ]);
  107. ENABLE_OPENAI_API = openaiConfig.ENABLE_OPENAI_API;
  108. ENABLE_OLLAMA_API = ollamaConfig.ENABLE_OLLAMA_API;
  109. OPENAI_API_BASE_URLS = openaiConfig.OPENAI_API_BASE_URLS;
  110. OPENAI_API_KEYS = openaiConfig.OPENAI_API_KEYS;
  111. OPENAI_API_CONFIGS = openaiConfig.OPENAI_API_CONFIGS;
  112. OLLAMA_BASE_URLS = ollamaConfig.OLLAMA_BASE_URLS;
  113. OLLAMA_API_CONFIGS = ollamaConfig.OLLAMA_API_CONFIGS;
  114. if (ENABLE_OPENAI_API) {
  115. // get url and idx
  116. for (const [idx, url] of OPENAI_API_BASE_URLS.entries()) {
  117. if (!OPENAI_API_CONFIGS[idx]) {
  118. // Legacy support, url as key
  119. OPENAI_API_CONFIGS[idx] = OPENAI_API_CONFIGS[url] || {};
  120. }
  121. }
  122. OPENAI_API_BASE_URLS.forEach(async (url, idx) => {
  123. OPENAI_API_CONFIGS[idx] = OPENAI_API_CONFIGS[idx] || {};
  124. if (!(OPENAI_API_CONFIGS[idx]?.enable ?? true)) {
  125. return;
  126. }
  127. const res = await getOpenAIModels(localStorage.token, idx);
  128. if (res.pipelines) {
  129. pipelineUrls[url] = true;
  130. }
  131. });
  132. }
  133. if (ENABLE_OLLAMA_API) {
  134. for (const [idx, url] of OLLAMA_BASE_URLS.entries()) {
  135. if (!OLLAMA_API_CONFIGS[idx]) {
  136. OLLAMA_API_CONFIGS[idx] = OLLAMA_API_CONFIGS[url] || {};
  137. }
  138. }
  139. }
  140. }
  141. });
  142. </script>
  143. <AddConnectionModal
  144. bind:show={showAddOpenAIConnectionModal}
  145. onSubmit={addOpenAIConnectionHandler}
  146. />
  147. <AddConnectionModal
  148. ollama
  149. bind:show={showAddOllamaConnectionModal}
  150. onSubmit={addOllamaConnectionHandler}
  151. />
  152. <form
  153. class="flex flex-col h-full justify-between text-sm"
  154. on:submit|preventDefault={() => {
  155. updateOpenAIHandler();
  156. updateOllamaHandler();
  157. dispatch('save');
  158. }}
  159. >
  160. <div class=" overflow-y-scroll scrollbar-hidden h-full">
  161. {#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null}
  162. <div class="my-2">
  163. <div class="mt-2 space-y-2 pr-1.5">
  164. <div class="flex justify-between items-center text-sm">
  165. <div class=" font-medium">{$i18n.t('OpenAI API')}</div>
  166. <div class="flex items-center">
  167. <div class="">
  168. <Switch
  169. bind:state={ENABLE_OPENAI_API}
  170. on:change={async () => {
  171. updateOpenAIHandler();
  172. }}
  173. />
  174. </div>
  175. </div>
  176. </div>
  177. {#if ENABLE_OPENAI_API}
  178. <hr class=" border-gray-50 dark:border-gray-850" />
  179. <div class="">
  180. <div class="flex justify-between items-center">
  181. <div class="font-medium">{$i18n.t('Manage OpenAI API Connections')}</div>
  182. <Tooltip content={$i18n.t(`Add Connection`)}>
  183. <button
  184. class="px-1"
  185. on:click={() => {
  186. showAddOpenAIConnectionModal = true;
  187. }}
  188. type="button"
  189. >
  190. <Plus />
  191. </button>
  192. </Tooltip>
  193. </div>
  194. <div class="flex flex-col gap-1.5 mt-1.5">
  195. {#each OPENAI_API_BASE_URLS as url, idx}
  196. <OpenAIConnection
  197. pipeline={pipelineUrls[url] ? true : false}
  198. bind:url
  199. bind:key={OPENAI_API_KEYS[idx]}
  200. bind:config={OPENAI_API_CONFIGS[idx]}
  201. onSubmit={() => {
  202. updateOpenAIHandler();
  203. }}
  204. onDelete={() => {
  205. OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
  206. (url, urlIdx) => idx !== urlIdx
  207. );
  208. OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
  209. delete OPENAI_API_CONFIGS[idx];
  210. }}
  211. />
  212. {/each}
  213. </div>
  214. </div>
  215. {/if}
  216. </div>
  217. </div>
  218. <hr class=" border-gray-50 dark:border-gray-850" />
  219. <div class="pr-1.5 my-2">
  220. <div class="flex justify-between items-center text-sm mb-2">
  221. <div class=" font-medium">{$i18n.t('Ollama API')}</div>
  222. <div class="mt-1">
  223. <Switch
  224. bind:state={ENABLE_OLLAMA_API}
  225. on:change={async () => {
  226. updateOllamaHandler();
  227. }}
  228. />
  229. </div>
  230. </div>
  231. {#if ENABLE_OLLAMA_API}
  232. <hr class=" border-gray-50 dark:border-gray-850 my-2" />
  233. <div class="">
  234. <div class="flex justify-between items-center">
  235. <div class="font-medium">{$i18n.t('Manage Ollama API Connections')}</div>
  236. <Tooltip content={$i18n.t(`Add Connection`)}>
  237. <button
  238. class="px-1"
  239. on:click={() => {
  240. showAddOllamaConnectionModal = true;
  241. }}
  242. type="button"
  243. >
  244. <Plus />
  245. </button>
  246. </Tooltip>
  247. </div>
  248. <div class="flex w-full gap-1.5">
  249. <div class="flex-1 flex flex-col gap-1.5 mt-1.5">
  250. {#each OLLAMA_BASE_URLS as url, idx}
  251. <OllamaConnection
  252. bind:url
  253. bind:config={OLLAMA_API_CONFIGS[idx]}
  254. {idx}
  255. onSubmit={() => {
  256. updateOllamaHandler();
  257. }}
  258. onDelete={() => {
  259. OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
  260. delete OLLAMA_API_CONFIGS[idx];
  261. }}
  262. />
  263. {/each}
  264. </div>
  265. </div>
  266. <div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
  267. {$i18n.t('Trouble accessing Ollama?')}
  268. <a
  269. class=" text-gray-300 font-medium underline"
  270. href="https://github.com/open-webui/open-webui#troubleshooting"
  271. target="_blank"
  272. >
  273. {$i18n.t('Click here for help.')}
  274. </a>
  275. </div>
  276. </div>
  277. {/if}
  278. </div>
  279. {:else}
  280. <div class="flex h-full justify-center">
  281. <div class="my-auto">
  282. <Spinner className="size-6" />
  283. </div>
  284. </div>
  285. {/if}
  286. </div>
  287. <div class="flex justify-end pt-3 text-sm font-medium">
  288. <button
  289. class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
  290. type="submit"
  291. >
  292. {$i18n.t('Save')}
  293. </button>
  294. </div>
  295. </form>