+page.svelte 10.9 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
	import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
Timothy J. Baek's avatar
Timothy J. Baek committed
6
	import { WEBUI_NAME, config, user, socket } 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
29

			$socket.emit('user-join', { auth: { token: sessionUser.token } });
Timothy J. Baek's avatar
Timothy J. Baek committed
30
			await user.set(sessionUser);
31
32
33
34
			goto('/');
		}
	};

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

		await setSessionUser(sessionUser);
	};

44
	const signUpHandler = async () => {
Danny Liu's avatar
Danny Liu committed
45
46
47
48
49
50
		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
51
52
53
54
55
56
57
58
59

		await setSessionUser(sessionUser);
	};

	const submitHandler = async () => {
		if (mode === 'signin') {
			await signInHandler();
		} else {
			await signUpHandler();
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
85
86
	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
87
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
88
		if ($user !== undefined) {
Timothy J. Baek's avatar
Timothy J. Baek committed
89
90
			await goto('/');
		}
91
		await checkOauthCallback();
Timothy J. Baek's avatar
Timothy J. Baek committed
92
		loaded = true;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
93
		if (($config?.features.auth_trusted_header ?? false) || $config?.features.auth === false) {
94
95
			await signInHandler();
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
96
	});
97
98
</script>

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

Timothy J. Baek's avatar
Timothy J. Baek committed
105
{#if loaded}
106
107
108
	<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
109
110
111
112
113
114
				<img
					crossorigin="anonymous"
					src="{WEBUI_BASE_URL}/static/favicon.png"
					class=" w-8 rounded-full"
					alt="logo"
				/>
115
116
117
118
			</div>
		</div>
	</div>

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

					<div class="mt-2 text-yellow-600 text-xl">
SimonOriginal's avatar
SimonOriginal committed
128
						{$i18n.t('Run Llama 2, Code Llama, and other models. Customize and create your own.')}
129
130
131
					</div>
				</div>
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
132
		</div> -->
133

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

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

							{#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
175
176
177
178

						<div class="flex flex-col mt-4">
							{#if mode === 'signup'}
								<div>
Timothy J. Baek's avatar
Timothy J. Baek committed
179
									<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Name')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
180
181
182
									<input
										bind:value={name}
										type="text"
Timothy J. Baek's avatar
Timothy J. Baek committed
183
										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
184
185
186
187
188
189
										autocomplete="name"
										placeholder={$i18n.t('Enter Your Full Name')}
										required
									/>
								</div>

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

							<div class="mb-2">
Timothy J. Baek's avatar
Timothy J. Baek committed
194
								<div class=" text-sm font-medium text-left mb-1">{$i18n.t('Email')}</div>
195
								<input
Timothy J. Baek's avatar
Timothy J. Baek committed
196
197
									bind:value={email}
									type="email"
Timothy J. Baek's avatar
Timothy J. Baek committed
198
									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
199
200
									autocomplete="email"
									placeholder={$i18n.t('Enter Your Email')}
201
202
203
									required
								/>
							</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
204

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

Timothy J. Baek's avatar
Timothy J. Baek committed
208
209
210
								<input
									bind:value={password}
									type="password"
Timothy J. Baek's avatar
Timothy J. Baek committed
211
									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
212
213
214
215
216
									placeholder={$i18n.t('Enter Your Password')}
									autocomplete="current-password"
									required
								/>
							</div>
217
						</div>
218

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

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
227
							{#if $config?.features.enable_signup}
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
								<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}
248
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
249
					</form>
250

251
					{#if Object.keys($config?.oauth?.providers ?? {}).length > 0}
252
253
254
255
256
257
258
259
						<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">
260
							{#if $config?.oauth?.providers?.google}
261
262
263
								<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={() => {
264
										window.location.href = `${WEBUI_BASE_URL}/oauth/google/login`;
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
									}}
								>
									<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}
285
							{#if $config?.oauth?.providers?.microsoft}
286
287
288
								<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={() => {
289
										window.location.href = `${WEBUI_BASE_URL}/oauth/microsoft/login`;
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
									}}
								>
									<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}
310
							{#if $config?.oauth?.providers?.oidc}
311
312
313
								<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={() => {
314
										window.location.href = `${WEBUI_BASE_URL}/oauth/oidc/login`;
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
									}}
								>
									<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
341
				</div>
342
			{/if}
343
344
345
		</div>
	</div>
{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
346
347
348

<style>
	.font-mona {
349
		font-family: 'Mona Sans', -apple-system, 'Inter', ui-sans-serif, system-ui, 'Segoe UI', Roboto,
Timothy J. Baek's avatar
Timothy J. Baek committed
350
351
			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
352
353
	}
</style>