+page.svelte 10.7 KB
Newer Older
1
2
<script>
	import { goto } from '$app/navigation';
3
	import { getSessionUser, userSignIn, userSignUp } from '$lib/apis/auths';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import Spinner from '$lib/components/common/Spinner.svelte';
5
6
	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
	import { WEBUI_NAME, config, user } from '$lib/stores';
7
	import { onMount, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
8
	import { toast } from 'svelte-sonner';
9
	import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
10
	import { page } from '$app/stores';
11

12
13
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
14
	let loaded = false;
15
16
17
18
19
20
	let mode = 'signin';

	let name = '';
	let email = '';
	let password = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
21
22
23
	const setSessionUser = async (sessionUser) => {
		if (sessionUser) {
			console.log(sessionUser);
24
			toast.success($i18n.t(`You're now logged in.`));
25
26
27
			if (sessionUser.token) {
				localStorage.token = sessionUser.token;
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
28
			await user.set(sessionUser);
29
30
31
32
			goto('/');
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
33
34
35
36
37
38
39
40
41
	const signInHandler = async () => {
		const sessionUser = await userSignIn(email, password).catch((error) => {
			toast.error(error);
			return null;
		});

		await setSessionUser(sessionUser);
	};

42
	const signUpHandler = async () => {
Danny Liu's avatar
Danny Liu committed
43
44
45
46
47
48
		const sessionUser = await userSignUp(name, email, password, generateInitialsImage(name)).catch(
			(error) => {
				toast.error(error);
				return null;
			}
		);
Timothy J. Baek's avatar
Timothy J. Baek committed
49
50
51
52
53
54
55
56
57

		await setSessionUser(sessionUser);
	};

	const submitHandler = async () => {
		if (mode === 'signin') {
			await signInHandler();
		} else {
			await signUpHandler();
58
59
60
		}
	};

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
	const checkOauthCallback = async () => {
		if (!$page.url.hash) {
			return;
		}
		const hash = $page.url.hash.substring(1);
		if (!hash) {
			return;
		}
		const params = new URLSearchParams(hash);
		const token = params.get('token');
		if (!token) {
			return;
		}
		const sessionUser = await getSessionUser(token).catch((error) => {
			toast.error(error);
			return null;
		});
		if (!sessionUser) {
			return;
		}
		localStorage.token = token;
		await setSessionUser(sessionUser);
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
85
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
86
		if ($user !== undefined) {
Timothy J. Baek's avatar
Timothy J. Baek committed
87
88
			await goto('/');
		}
89
		await checkOauthCallback();
Timothy J. Baek's avatar
Timothy J. Baek committed
90
		loaded = true;
91
		if (($config?.auth_trusted_header ?? false) || $config?.auth === false) {
92
93
			await signInHandler();
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
94
	});
95
96
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
97
98
99
100
101
102
<svelte:head>
	<title>
		{`${$WEBUI_NAME}`}
	</title>
</svelte:head>

Timothy J. Baek's avatar
Timothy J. Baek committed
103
{#if loaded}
104
105
106
	<div class="fixed m-10 z-50">
		<div class="flex space-x-2">
			<div class=" self-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
107
108
109
110
111
112
				<img
					crossorigin="anonymous"
					src="{WEBUI_BASE_URL}/static/favicon.png"
					class=" w-8 rounded-full"
					alt="logo"
				/>
113
114
115
116
			</div>
		</div>
	</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
117
	<div class=" bg-white dark:bg-gray-950 min-h-screen w-full flex justify-center font-mona">
Timothy J. Baek's avatar
Timothy J. Baek committed
118
		<!-- <div class="hidden lg:flex lg:flex-1 px-10 md:px-16 w-full bg-yellow-50 justify-center">
119
120
121
122
123
124
125
126
127
128
129
			<div class=" my-auto pb-16 text-left">
				<div>
					<div class=" font-bold text-yellow-600 text-4xl">
						Get up and running with <br />large language models, locally.
					</div>

					<div class="mt-2 text-yellow-600 text-xl">
						Run Llama 2, Code Llama, and other models. Customize and create your own.
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
130
		</div> -->
131

Timothy J. Baek's avatar
Timothy J. Baek committed
132
		<div class="w-full sm:max-w-md px-10 min-h-screen flex flex-col text-center">
133
			{#if ($config?.auth_trusted_header ?? false) || $config?.auth === false}
134
				<div class=" my-auto pb-10 w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
135
136
137
138
139
140
141
142
143
144
145
146
					<div
						class="flex items-center justify-center gap-3 text-xl sm:text-2xl text-center font-bold dark:text-gray-200"
					>
						<div>
							{$i18n.t('Signing in')}
							{$i18n.t('to')}
							{$WEBUI_NAME}
						</div>

						<div>
							<Spinner />
						</div>
147
148
149
					</div>
				</div>
			{:else}
Timothy J. Baek's avatar
Timothy J. Baek committed
150
				<div class="  my-auto pb-10 w-full dark:text-gray-100">
Timothy J. Baek's avatar
Timothy J. Baek committed
151
					<form
Timothy J. Baek's avatar
Timothy J. Baek committed
152
						class=" flex flex-col justify-center"
Timothy J. Baek's avatar
Timothy J. Baek committed
153
154
155
156
						on:submit|preventDefault={() => {
							submitHandler();
						}}
					>
Timothy J. Baek's avatar
Timothy J. Baek committed
157
158
159
160
161
						<div class="mb-1">
							<div class=" text-2xl font-bold">
								{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Sign up')}
								{$i18n.t('to')}
								{$WEBUI_NAME}
Timothy J. Baek's avatar
Timothy J. Baek committed
162
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
163
164
165
166
167
168
169
170
171
172

							{#if mode === 'signup'}
								<div class=" mt-1 text-xs font-medium text-gray-500">
									ⓘ {$WEBUI_NAME}
									{$i18n.t(
										'does not make any external connections, and your data stays securely on your locally hosted server.'
									)}
								</div>
							{/if}
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
173
174
175
176
177
178
179
180

						<div class="flex flex-col mt-4">
							{#if mode === 'signup'}
								<div>
									<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Name')}</div>
									<input
										bind:value={name}
										type="text"
Timothy J. Baek's avatar
Timothy J. Baek committed
181
										class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
Timothy J. Baek's avatar
Timothy J. Baek committed
182
183
184
185
186
187
										autocomplete="name"
										placeholder={$i18n.t('Enter Your Full Name')}
										required
									/>
								</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
188
								<hr class=" my-3 dark:border-gray-900" />
Timothy J. Baek's avatar
Timothy J. Baek committed
189
190
191
192
							{/if}

							<div class="mb-2">
								<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Email')}</div>
193
								<input
Timothy J. Baek's avatar
Timothy J. Baek committed
194
195
									bind:value={email}
									type="email"
Timothy J. Baek's avatar
Timothy J. Baek committed
196
									class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
Timothy J. Baek's avatar
Timothy J. Baek committed
197
198
									autocomplete="email"
									placeholder={$i18n.t('Enter Your Email')}
199
200
201
									required
								/>
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
202

Timothy J. Baek's avatar
Timothy J. Baek committed
203
204
							<div>
								<div class=" text-sm font-semibold text-left mb-1">{$i18n.t('Password')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
205

Timothy J. Baek's avatar
Timothy J. Baek committed
206
207
208
								<input
									bind:value={password}
									type="password"
Timothy J. Baek's avatar
Timothy J. Baek committed
209
									class=" px-5 py-3 rounded-2xl w-full text-sm outline-none border dark:border-none dark:bg-gray-900"
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
212
213
214
									placeholder={$i18n.t('Enter Your Password')}
									autocomplete="current-password"
									required
								/>
							</div>
215
						</div>
216

Timothy J. Baek's avatar
Timothy J. Baek committed
217
						<div class="mt-5">
218
							<button
Timothy J. Baek's avatar
Timothy J. Baek committed
219
								class=" bg-gray-900 hover:bg-gray-800 w-full rounded-2xl text-white font-semibold text-sm py-3 transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
220
								type="submit"
221
							>
Timothy J. Baek's avatar
Timothy J. Baek committed
222
								{mode === 'signin' ? $i18n.t('Sign in') : $i18n.t('Create Account')}
223
							</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
224

225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
							{#if $config.enable_signup}
								<div class=" mt-4 text-sm text-center">
									{mode === 'signin'
										? $i18n.t("Don't have an account?")
										: $i18n.t('Already have an account?')}

									<button
										class=" font-medium underline"
										type="button"
										on:click={() => {
											if (mode === 'signin') {
												mode = 'signup';
											} else {
												mode = 'signin';
											}
										}}
									>
										{mode === 'signin' ? $i18n.t('Sign up') : $i18n.t('Sign in')}
									</button>
								</div>
							{/if}
246
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
247
					</form>
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

					{#if Object.keys($config?.oauth?.providers ?? {}).length > 0 }
						<div class="inline-flex items-center justify-center w-full">
							<hr class="w-64 h-px my-8 bg-gray-200 border-0 dark:bg-gray-700" />
							<span
								class="absolute px-3 font-medium text-gray-900 -translate-x-1/2 bg-white left-1/2 dark:text-white dark:bg-gray-950"
								>{$i18n.t('or')}</span
							>
						</div>
						<div class="flex flex-col space-y-2">
							{#if $config?.oauth?.providers?.google }
								<button
									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
									on:click={() => {
										window.location.href = `${WEBUI_API_BASE_URL}/auths/oauth/google/login`;
									}}
								>
									<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" class="size-6 mr-3">
										<path
											fill="#EA4335"
											d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"
										/><path
											fill="#4285F4"
											d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"
										/><path
											fill="#FBBC05"
											d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"
										/><path
											fill="#34A853"
											d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"
										/><path fill="none" d="M0 0h48v48H0z" />
									</svg>
									<span>{$i18n.t('Continue with {{provider}}', { provider: 'Google' })}</span>
								</button>
							{/if}
							{#if $config?.oauth?.providers?.microsoft }
								<button
									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
									on:click={() => {
										window.location.href = `${WEBUI_API_BASE_URL}/auths/oauth/microsoft/login`;
									}}
								>
									<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21" class="size-6 mr-3">
										<rect x="1" y="1" width="9" height="9" fill="#f25022" /><rect
											x="1"
											y="11"
											width="9"
											height="9"
											fill="#00a4ef"
										/><rect x="11" y="1" width="9" height="9" fill="#7fba00" /><rect
											x="11"
											y="11"
											width="9"
											height="9"
											fill="#ffb900"
										/>
									</svg>
									<span>{$i18n.t('Continue with {{provider}}', { provider: 'Microsoft' })}</span>
								</button>
							{/if}
							{#if $config?.oauth?.providers?.oidc }
								<button
									class="flex items-center px-6 border-2 dark:border-gray-800 duration-300 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 w-full rounded-2xl dark:text-white text-sm py-3 transition"
									on:click={() => {
										window.location.href = `${WEBUI_API_BASE_URL}/auths/oauth/oidc/login`;
									}}
								>
									<svg
										xmlns="http://www.w3.org/2000/svg"
										fill="none"
										viewBox="0 0 24 24"
										stroke-width="1.5"
										stroke="currentColor"
										class="size-6 mr-3"
									>
										<path
											stroke-linecap="round"
											stroke-linejoin="round"
											d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
										/>
									</svg>

									<span
										>{$i18n.t('Continue with {{provider}}', {
											provider: $config?.oauth?.providers?.oidc ?? 'SSO'
										})}</span
									>
								</button>
							{/if}
						</div>
					{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
339
				</div>
340
			{/if}
341
342
343
		</div>
	</div>
{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
344
345
346

<style>
	.font-mona {
Timothy J. Baek's avatar
Timothy J. Baek committed
347
348
349
		font-family: 'Mona Sans', -apple-system, 'Arimo', ui-sans-serif, system-ui, 'Segoe UI', Roboto,
			Ubuntu, Cantarell, 'Noto Sans', sans-serif, 'Helvetica Neue', Arial, 'Apple Color Emoji',
			'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
Timothy J. Baek's avatar
Timothy J. Baek committed
350
351
	}
</style>