Connections.svelte 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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] = 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] = connection.config;
  90. await updateOllamaHandler();
  91. };
  92. onMount(async () => {
  93. if ($user.role === 'admin') {
  94. let ollamaConfig = {};
  95. let openaiConfig = {};
  96. await Promise.all([
  97. (async () => {
  98. ollamaConfig = await getOllamaConfig(localStorage.token);
  99. })(),
  100. (async () => {
  101. openaiConfig = await getOpenAIConfig(localStorage.token);
  102. })()
  103. ]);
  104. ENABLE_OPENAI_API = openaiConfig.ENABLE_OPENAI_API;
  105. ENABLE_OLLAMA_API = ollamaConfig.ENABLE_OLLAMA_API;
  106. OPENAI_API_BASE_URLS = openaiConfig.OPENAI_API_BASE_URLS;
  107. OPENAI_API_KEYS = openaiConfig.OPENAI_API_KEYS;
  108. OPENAI_API_CONFIGS = openaiConfig.OPENAI_API_CONFIGS;
  109. OLLAMA_BASE_URLS = ollamaConfig.OLLAMA_BASE_URLS;
  110. OLLAMA_API_CONFIGS = ollamaConfig.OLLAMA_API_CONFIGS;
  111. if (ENABLE_OPENAI_API) {
  112. // get url and idx
  113. for (const [idx, url] of OPENAI_API_BASE_URLS.entries()) {
  114. if (!OPENAI_API_CONFIGS[idx]) {
  115. // Legacy support, url as key
  116. OPENAI_API_CONFIGS[idx] = OPENAI_API_CONFIGS[url] || {};
  117. }
  118. }
  119. OPENAI_API_BASE_URLS.forEach(async (url, idx) => {
  120. OPENAI_API_CONFIGS[idx] = OPENAI_API_CONFIGS[idx] || {};
  121. if (!(OPENAI_API_CONFIGS[idx]?.enable ?? true)) {
  122. return;
  123. }
  124. const res = await getOpenAIModels(localStorage.token, idx);
  125. if (res.pipelines) {
  126. pipelineUrls[url] = true;
  127. }
  128. });
  129. }
  130. if (ENABLE_OLLAMA_API) {
  131. for (const [idx, url] of OLLAMA_BASE_URLS.entries()) {
  132. if (!OLLAMA_API_CONFIGS[idx]) {
  133. OLLAMA_API_CONFIGS[idx] = OLLAMA_API_CONFIGS[url] || {};
  134. }
  135. }
  136. }
  137. }
  138. });
  139. </script>
  140. <AddConnectionModal
  141. bind:show={showAddOpenAIConnectionModal}
  142. onSubmit={addOpenAIConnectionHandler}
  143. />
  144. <AddConnectionModal
  145. ollama
  146. bind:show={showAddOllamaConnectionModal}
  147. onSubmit={addOllamaConnectionHandler}
  148. />
  149. <form
  150. class="flex flex-col h-full justify-between text-sm"
  151. on:submit|preventDefault={() => {
  152. updateOpenAIHandler();
  153. updateOllamaHandler();
  154. dispatch('save');
  155. }}
  156. >
  157. <div class=" overflow-y-scroll scrollbar-hidden h-full">
  158. {#if ENABLE_OPENAI_API !== null && ENABLE_OLLAMA_API !== null}
  159. <div class="my-2">
  160. <div class="mt-2 space-y-2 pr-1.5">
  161. <div class="flex justify-between items-center text-sm">
  162. <div class=" font-medium">{$i18n.t('OpenAI API')}</div>
  163. <div class="flex items-center">
  164. <div class="">
  165. <Switch
  166. bind:state={ENABLE_OPENAI_API}
  167. on:change={async () => {
  168. updateOpenAIHandler();
  169. }}
  170. />
  171. </div>
  172. </div>
  173. </div>
  174. {#if ENABLE_OPENAI_API}
  175. <hr class=" border-gray-50 dark:border-gray-850" />
  176. <div class="">
  177. <div class="flex justify-between items-center">
  178. <div class="font-medium">{$i18n.t('Manage OpenAI API Connections')}</div>
  179. <Tooltip content={$i18n.t(`Add Connection`)}>
  180. <button
  181. class="px-1"
  182. on:click={() => {
  183. showAddOpenAIConnectionModal = true;
  184. }}
  185. type="button"
  186. >
  187. <Plus />
  188. </button>
  189. </Tooltip>
  190. </div>
  191. <div class="flex flex-col gap-1.5 mt-1.5">
  192. {#each OPENAI_API_BASE_URLS as url, idx}
  193. <OpenAIConnection
  194. pipeline={pipelineUrls[url] ? true : false}
  195. bind:url
  196. bind:key={OPENAI_API_KEYS[idx]}
  197. bind:config={OPENAI_API_CONFIGS[idx]}
  198. onSubmit={() => {
  199. updateOpenAIHandler();
  200. }}
  201. onDelete={() => {
  202. OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
  203. (url, urlIdx) => idx !== urlIdx
  204. );
  205. OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
  206. delete OPENAI_API_CONFIGS[idx];
  207. }}
  208. />
  209. {/each}
  210. </div>
  211. </div>
  212. {/if}
  213. </div>
  214. </div>
  215. <hr class=" border-gray-50 dark:border-gray-850" />
  216. <div class="pr-1.5 my-2">
  217. <div class="flex justify-between items-center text-sm mb-2">
  218. <div class=" font-medium">{$i18n.t('Ollama API')}</div>
  219. <div class="mt-1">
  220. <Switch
  221. bind:state={ENABLE_OLLAMA_API}
  222. on:change={async () => {
  223. updateOllamaHandler();
  224. }}
  225. />
  226. </div>
  227. </div>
  228. {#if ENABLE_OLLAMA_API}
  229. <hr class=" border-gray-50 dark:border-gray-850 my-2" />
  230. <div class="">
  231. <div class="flex justify-between items-center">
  232. <div class="font-medium">{$i18n.t('Manage Ollama API Connections')}</div>
  233. <Tooltip content={$i18n.t(`Add Connection`)}>
  234. <button
  235. class="px-1"
  236. on:click={() => {
  237. showAddOllamaConnectionModal = true;
  238. }}
  239. type="button"
  240. >
  241. <Plus />
  242. </button>
  243. </Tooltip>
  244. </div>
  245. <div class="flex w-full gap-1.5">
  246. <div class="flex-1 flex flex-col gap-1.5 mt-1.5">
  247. {#each OLLAMA_BASE_URLS as url, idx}
  248. <OllamaConnection
  249. bind:url
  250. bind:config={OLLAMA_API_CONFIGS[idx]}
  251. {idx}
  252. onSubmit={() => {
  253. updateOllamaHandler();
  254. }}
  255. onDelete={() => {
  256. OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
  257. delete OLLAMA_API_CONFIGS[idx];
  258. }}
  259. />
  260. {/each}
  261. </div>
  262. </div>
  263. <div class="mt-1 text-xs text-gray-400 dark:text-gray-500">
  264. {$i18n.t('Trouble accessing Ollama?')}
  265. <a
  266. class=" text-gray-300 font-medium underline"
  267. href="https://github.com/open-webui/open-webui#troubleshooting"
  268. target="_blank"
  269. >
  270. {$i18n.t('Click here for help.')}
  271. </a>
  272. </div>
  273. </div>
  274. {/if}
  275. </div>
  276. {:else}
  277. <div class="flex h-full justify-center">
  278. <div class="my-auto">
  279. <Spinner className="size-6" />
  280. </div>
  281. </div>
  282. {/if}
  283. </div>
  284. <div class="flex justify-end pt-3 text-sm font-medium">
  285. <button
  286. 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"
  287. type="submit"
  288. >
  289. {$i18n.t('Save')}
  290. </button>
  291. </div>
  292. </form>