Connections.svelte 7.69 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
<script lang="ts">
	import { models, user } from '$lib/stores';
3
	import { createEventDispatcher, onMount, getContext } from 'svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
5
	const dispatch = createEventDispatcher();

6
	import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
Timothy J. Baek's avatar
Timothy J. Baek committed
7
	import {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
8
		getOpenAIConfig,
Timothy J. Baek's avatar
Timothy J. Baek committed
9
10
		getOpenAIKeys,
		getOpenAIUrls,
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
11
		updateOpenAIConfig,
Timothy J. Baek's avatar
Timothy J. Baek committed
12
13
14
		updateOpenAIKeys,
		updateOpenAIUrls
	} from '$lib/apis/openai';
Jannik Streidl's avatar
Jannik Streidl committed
15
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
16
	import Switch from '$lib/components/common/Switch.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
17

18
19
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
20
21
22
	export let getModels: Function;

	// External
23
	let OLLAMA_BASE_URLS = [''];
Timothy J. Baek's avatar
Timothy J. Baek committed
24

Timothy J. Baek's avatar
Timothy J. Baek committed
25
26
27
	let OPENAI_API_KEYS = [''];
	let OPENAI_API_BASE_URLS = [''];

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
28
	let ENABLE_OPENAI_API = false;
29

Timothy J. Baek's avatar
Timothy J. Baek committed
30
	const updateOpenAIHandler = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
31
32
		OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
		OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
Timothy J. Baek's avatar
Timothy J. Baek committed
33
34
35
36

		await models.set(await getModels());
	};

37
38
	const updateOllamaUrlsHandler = async () => {
		OLLAMA_BASE_URLS = await updateOllamaUrls(localStorage.token, OLLAMA_BASE_URLS);
Timothy J. Baek's avatar
Timothy J. Baek committed
39

Timothy J. Baek's avatar
Timothy J. Baek committed
40
		const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
41
			toast.error(error);
Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
45
			return null;
		});

		if (ollamaVersion) {
46
			toast.success($i18n.t('Server connection verified'));
Timothy J. Baek's avatar
Timothy J. Baek committed
47
			await models.set(await getModels());
Timothy J. Baek's avatar
Timothy J. Baek committed
48
49
50
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
51
52
	onMount(async () => {
		if ($user.role === 'admin') {
53
			OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
54
55
56
57

			const config = await getOpenAIConfig(localStorage.token);
			ENABLE_OPENAI_API = config.ENABLE_OPENAI_API;

Timothy J. Baek's avatar
Timothy J. Baek committed
58
59
			OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
			OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
Timothy J. Baek's avatar
Timothy J. Baek committed
60
61
62
63
64
		}
	});
</script>

<form
65
	class="flex flex-col h-full justify-between text-sm"
Timothy J. Baek's avatar
Timothy J. Baek committed
66
67
68
69
70
	on:submit|preventDefault={() => {
		updateOpenAIHandler();
		dispatch('save');
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
71
	<div class="  pr-1.5 overflow-y-scroll max-h-[25rem] space-y-3">
72
73
74
		<div class=" space-y-3">
			<div class="mt-2 space-y-2 pr-1.5">
				<div class="flex justify-between items-center text-sm">
75
					<div class="  font-medium">{$i18n.t('OpenAI API')}</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
76
77
78
79
80
81
82
83
84

					<div class="mt-1">
						<Switch
							bind:state={ENABLE_OPENAI_API}
							on:change={async () => {
								updateOpenAIConfig(localStorage.token, ENABLE_OPENAI_API);
							}}
						/>
					</div>
85
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
86

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
87
				{#if ENABLE_OPENAI_API}
Timothy J. Baek's avatar
Timothy J. Baek committed
88
89
90
91
92
93
					<div class="flex flex-col gap-1">
						{#each OPENAI_API_BASE_URLS as url, idx}
							<div class="flex w-full gap-2">
								<div class="flex-1">
									<input
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
94
										placeholder={$i18n.t('API Base URL')}
Timothy J. Baek's avatar
Timothy J. Baek committed
95
96
97
98
										bind:value={url}
										autocomplete="off"
									/>
								</div>
99

Timothy J. Baek's avatar
Timothy J. Baek committed
100
101
102
								<div class="flex-1">
									<input
										class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
103
										placeholder={$i18n.t('API Key')}
Timothy J. Baek's avatar
Timothy J. Baek committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
										bind:value={OPENAI_API_KEYS[idx]}
										autocomplete="off"
									/>
								</div>
								<div class="self-center flex items-center">
									{#if idx === 0}
										<button
											class="px-1"
											on:click={() => {
												OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
												OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
											}}
											type="button"
										>
											<svg
												xmlns="http://www.w3.org/2000/svg"
												viewBox="0 0 16 16"
												fill="currentColor"
												class="w-4 h-4"
											>
												<path
													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"
												/>
											</svg>
										</button>
									{:else}
										<button
											class="px-1"
											on:click={() => {
												OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
													(url, urlIdx) => idx !== urlIdx
												);
												OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
											}}
											type="button"
										>
											<svg
												xmlns="http://www.w3.org/2000/svg"
												viewBox="0 0 16 16"
												fill="currentColor"
												class="w-4 h-4"
											>
												<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" />
											</svg>
										</button>
									{/if}
								</div>
151
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
152
							<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
153
154
								{$i18n.t('WebUI will make requests to')}
								<span class=" text-gray-200">'{url}/models'</span>
155
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
156
						{/each}
157
158
159
					</div>
				{/if}
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
160
161
		</div>

162
		<hr class=" dark:border-gray-700" />
Timothy J. Baek's avatar
Timothy J. Baek committed
163

Timothy J. Baek's avatar
Timothy J. Baek committed
164
		<div>
Ased Mammad's avatar
Ased Mammad committed
165
			<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Base URL')}</div>
166
167
168
169
170
171
			<div class="flex w-full gap-1.5">
				<div class="flex-1 flex flex-col gap-2">
					{#each OLLAMA_BASE_URLS as url, idx}
						<div class="flex gap-1.5">
							<input
								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
172
								placeholder={$i18n.t('Enter URL (e.g. http://localhost:11434)')}
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
								bind:value={url}
							/>

							<div class="self-center flex items-center">
								{#if idx === 0}
									<button
										class="px-1"
										on:click={() => {
											OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
										}}
										type="button"
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 16 16"
											fill="currentColor"
											class="w-4 h-4"
										>
											<path
												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"
											/>
										</svg>
									</button>
								{:else}
									<button
										class="px-1"
										on:click={() => {
											OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
										}}
										type="button"
									>
										<svg
											xmlns="http://www.w3.org/2000/svg"
											viewBox="0 0 16 16"
											fill="currentColor"
											class="w-4 h-4"
										>
											<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" />
										</svg>
									</button>
								{/if}
							</div>
						</div>
					{/each}
Timothy J. Baek's avatar
Timothy J. Baek committed
217
				</div>
218
219
220
221
222
223
224
225

				<div class="">
					<button
						class="p-2.5 bg-gray-200 hover:bg-gray-300 dark:bg-gray-850 dark:hover:bg-gray-800 rounded-lg transition"
						on:click={() => {
							updateOllamaUrlsHandler();
						}}
						type="button"
226
					>
227
228
229
230
231
232
233
234
235
236
237
238
239
240
						<svg
							xmlns="http://www.w3.org/2000/svg"
							viewBox="0 0 20 20"
							fill="currentColor"
							class="w-4 h-4"
						>
							<path
								fill-rule="evenodd"
								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"
								clip-rule="evenodd"
							/>
						</svg>
					</button>
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
241
242
243
			</div>

			<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Jannik Streidl's avatar
Jannik Streidl committed
244
				{$i18n.t('Trouble accessing Ollama?')}
245
				<a
Jannik Streidl's avatar
Jannik Streidl committed
246
					class=" text-gray-300 font-medium underline"
247
248
249
					href="https://github.com/open-webui/open-webui#troubleshooting"
					target="_blank"
				>
Ased Mammad's avatar
Ased Mammad committed
250
					{$i18n.t('Click here for help.')}
251
				</a>
Timothy J. Baek's avatar
Timothy J. Baek committed
252
253
254
255
256
257
			</div>
		</div>
	</div>

	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
258
			class="  px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
Timothy J. Baek's avatar
Timothy J. Baek committed
259
260
			type="submit"
		>
Jannik Streidl's avatar
Jannik Streidl committed
261
			{$i18n.t('Save')}
Timothy J. Baek's avatar
Timothy J. Baek committed
262
263
264
		</button>
	</div>
</form>