+page.svelte 18.1 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
<script lang="ts">
2
	import { v4 as uuidv4 } from 'uuid';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
4
	import toast from 'svelte-french-toast';

5
	import { OLLAMA_API_BASE_URL } from '$lib/constants';
6
	import { onMount, tick } from 'svelte';
7
8
	import { splitStream } from '$lib/utils';
	import { goto } from '$app/navigation';
Timothy J. Baek's avatar
Timothy J. Baek committed
9

10
	import { config, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
11
12
13
14

	import MessageInput from '$lib/components/chat/MessageInput.svelte';
	import Messages from '$lib/components/chat/Messages.svelte';
	import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
15
	import Navbar from '$lib/components/layout/Navbar.svelte';
16
	import { page } from '$app/stores';
17

18
19
	let stopResponseFlag = false;
	let autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
20

Timothy J. Baek's avatar
Timothy J. Baek committed
21
	let selectedModels = [''];
22
23
24
25
26
27
	let selectedModelfile = null;
	$: selectedModelfile =
		selectedModels.length === 1 &&
		$modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0]).length > 0
			? $modelfiles.filter((modelfile) => modelfile.tagName === selectedModels[0])[0]
			: null;
28

Timothy J. Baek's avatar
Timothy J. Baek committed
29
	let title = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
30
	let prompt = '';
31
	let files = [];
32

33
	let messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
	let history = {
		messages: {},
		currentId: null
	};

	$: if (history.currentId !== null) {
		let _messages = [];

		let currentMessage = history.messages[history.currentId];
		while (currentMessage !== null) {
			_messages.unshift({ ...currentMessage });
			currentMessage =
				currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
		}
		messages = _messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
49
50
	} else {
		messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
51
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
52

Timothy J. Baek's avatar
Timothy J. Baek committed
53
54
55
56
	$: if (files) {
		console.log(files);
	}

57
	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
58
59
60
61
62
		await chatId.set(uuidv4());

		chatId.subscribe(async () => {
			await initNewChat();
		});
63
64
65
66
67
68
	});

	//////////////////////////
	// Web functions
	//////////////////////////

69
70
	const initNewChat = async () => {
		console.log($chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
71

72
		autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
73

74
75
76
77
78
		title = '';
		messages = [];
		history = {
			messages: {},
			currentId: null
Timothy J. Baek's avatar
Timothy J. Baek committed
79
		};
80
81
82
		selectedModels = $page.url.searchParams.get('models')
			? $page.url.searchParams.get('models')?.split(',')
			: $settings.models ?? [''];
83
84
85
86
87
88

		let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
		console.log(_settings);
		settings.set({
			..._settings
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
89
90
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
	const copyToClipboard = (text) => {
		if (!navigator.clipboard) {
			var textArea = document.createElement('textarea');
			textArea.value = text;

			// Avoid scrolling to bottom
			textArea.style.top = '0';
			textArea.style.left = '0';
			textArea.style.position = 'fixed';

			document.body.appendChild(textArea);
			textArea.focus();
			textArea.select();

			try {
				var successful = document.execCommand('copy');
				var msg = successful ? 'successful' : 'unsuccessful';
				console.log('Fallback: Copying text command was ' + msg);
			} catch (err) {
				console.error('Fallback: Oops, unable to copy', err);
			}

			document.body.removeChild(textArea);
			return;
		}
		navigator.clipboard.writeText(text).then(
			function () {
				console.log('Async: Copying to clipboard was successful!');
			},
			function (err) {
				console.error('Async: Could not copy text: ', err);
			}
		);
	};

126
127
128
129
	//////////////////////////
	// Ollama functions
	//////////////////////////

Timothy J. Baek's avatar
Timothy J. Baek committed
130
	const sendPrompt = async (userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
131
132
133
		await Promise.all(
			selectedModels.map(async (model) => {
				if (model.includes('gpt-')) {
Timothy J. Baek's avatar
Timothy J. Baek committed
134
					await sendPromptOpenAI(model, userPrompt, parentId, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
135
				} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
136
					await sendPromptOllama(model, userPrompt, parentId, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
137
138
139
				}
			})
		);
140

141
		await chats.set(await $db.getChats());
142
143
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
144
	const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
145
		console.log('sendPromptOllama');
Timothy J. Baek's avatar
Timothy J. Baek committed
146
		let responseMessageId = uuidv4();
147
		let responseMessage = {
Timothy J. Baek's avatar
Timothy J. Baek committed
148
149
150
			parentId: parentId,
			id: responseMessageId,
			childrenIds: [],
151
			role: 'assistant',
Timothy J. Baek's avatar
Timothy J. Baek committed
152
153
			content: '',
			model: model
154
155
		};

Timothy J. Baek's avatar
Timothy J. Baek committed
156
157
158
159
160
161
162
163
164
		history.messages[responseMessageId] = responseMessage;
		history.currentId = responseMessageId;
		if (parentId !== null) {
			history.messages[parentId].childrenIds = [
				...history.messages[parentId].childrenIds,
				responseMessageId
			];
		}

Timothy J. Baek's avatar
Timothy J. Baek committed
165
166
		await tick();
		window.scrollTo({ top: document.body.scrollHeight });
167

Timothy J. Baek's avatar
Timothy J. Baek committed
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
		const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/chat`, {
			method: 'POST',
			headers: {
				'Content-Type': 'text/event-stream',
				...($settings.authHeader && { Authorization: $settings.authHeader }),
				...($user && { Authorization: `Bearer ${localStorage.token}` })
			},
			body: JSON.stringify({
				model: model,
				messages: [
					$settings.system
						? {
								role: 'system',
								content: $settings.system
						  }
						: undefined,
					...messages
				]
					.filter((message) => message)
Timothy J. Baek's avatar
Timothy J. Baek committed
187
188
189
190
191
192
193
194
195
					.map((message) => ({
						role: message.role,
						content: message.content,
						...(message.files && {
							images: message.files
								.filter((file) => file.type === 'image')
								.map((file) => file.url.slice(file.url.indexOf(',') + 1))
						})
					})),
Timothy J. Baek's avatar
Timothy J. Baek committed
196
197
198
199
200
201
202
203
204
205
206
				options: {
					seed: $settings.seed ?? undefined,
					temperature: $settings.temperature ?? undefined,
					repeat_penalty: $settings.repeat_penalty ?? undefined,
					top_k: $settings.top_k ?? undefined,
					top_p: $settings.top_p ?? undefined,
					num_ctx: $settings.num_ctx ?? undefined,
					...($settings.options ?? {})
				},
				format: $settings.requestFormat ?? undefined
			})
207
208
209
		}).catch((err) => {
			console.log(err);
			return null;
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
		});

212
		if (res && res.ok) {
Rohit Das's avatar
Rohit Das committed
213
214
215
216
217
218
219
220
221
222
223
224
			const reader = res.body
				.pipeThrough(new TextDecoderStream())
				.pipeThrough(splitStream('\n'))
				.getReader();

			while (true) {
				const { value, done } = await reader.read();
				if (done || stopResponseFlag || _chatId !== $chatId) {
					responseMessage.done = true;
					messages = messages;
					break;
				}
225

Rohit Das's avatar
Rohit Das committed
226
227
				try {
					let lines = value.split('\n');
228

Rohit Das's avatar
Rohit Das committed
229
230
231
232
					for (const line of lines) {
						if (line !== '') {
							console.log(line);
							let data = JSON.parse(line);
Timothy J. Baek's avatar
Timothy J. Baek committed
233

Rohit Das's avatar
Rohit Das committed
234
235
236
							if ('detail' in data) {
								throw data;
							}
Timothy J. Baek's avatar
Timothy J. Baek committed
237

Rohit Das's avatar
Rohit Das committed
238
239
240
241
242
243
244
							if (data.done == false) {
								if (responseMessage.content == '' && data.message.content == '\n') {
									continue;
								} else {
									responseMessage.content += data.message.content;
									messages = messages;
								}
245
							} else {
Rohit Das's avatar
Rohit Das committed
246
247
248
249
								responseMessage.done = true;
								responseMessage.context = data.context ?? null;
								responseMessage.info = {
									total_duration: data.total_duration,
Timothy J. Baek's avatar
Timothy J. Baek committed
250
251
252
									load_duration: data.load_duration,
									sample_count: data.sample_count,
									sample_duration: data.sample_duration,
Rohit Das's avatar
Rohit Das committed
253
254
255
256
257
									prompt_eval_count: data.prompt_eval_count,
									prompt_eval_duration: data.prompt_eval_duration,
									eval_count: data.eval_count,
									eval_duration: data.eval_duration
								};
258
								messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
259

Timothy J. Baek's avatar
Timothy J. Baek committed
260
								if ($settings.notificationEnabled && !document.hasFocus()) {
Timothy J. Baek's avatar
Timothy J. Baek committed
261
262
263
264
265
266
267
268
269
270
271
272
273
									const notification = new Notification(
										selectedModelfile
											? `${
													selectedModelfile.title.charAt(0).toUpperCase() +
													selectedModelfile.title.slice(1)
											  }`
											: `Ollama - ${model}`,
										{
											body: responseMessage.content,
											icon: selectedModelfile?.imageUrl ?? '/favicon.png'
										}
									);
								}
Timothy J. Baek's avatar
Timothy J. Baek committed
274
275
276
277

								if ($settings.responseAutoCopy) {
									copyToClipboard(responseMessage.content);
								}
278
279
280
							}
						}
					}
Rohit Das's avatar
Rohit Das committed
281
282
283
284
285
286
				} catch (error) {
					console.log(error);
					if ('detail' in error) {
						toast.error(error.detail);
					}
					break;
287
				}
Rohit Das's avatar
Rohit Das committed
288
289
290

				if (autoScroll) {
					window.scrollTo({ top: document.body.scrollHeight });
291
				}
292

Rohit Das's avatar
Rohit Das committed
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
				await $db.updateChatById(_chatId, {
					title: title === '' ? 'New Chat' : title,
					models: selectedModels,
					system: $settings.system ?? undefined,
					options: {
						seed: $settings.seed ?? undefined,
						temperature: $settings.temperature ?? undefined,
						repeat_penalty: $settings.repeat_penalty ?? undefined,
						top_k: $settings.top_k ?? undefined,
						top_p: $settings.top_p ?? undefined,
						num_ctx: $settings.num_ctx ?? undefined,
						...($settings.options ?? {})
					},
					messages: messages,
					history: history
				});
309
			}
310
311
312
		} else {
			if (res !== null) {
				const error = await res.json();
313
				console.log(error);
314
315
				if ('detail' in error) {
					toast.error(error.detail);
316
					responseMessage.content = error.detail;
317
318
				} else {
					toast.error(error.error);
319
					responseMessage.content = error.error;
320
				}
321
322
			} else {
				toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
323
				responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
324
325
			}

326
327
328
329
			responseMessage.error = true;
			responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
			responseMessage.done = true;
			messages = messages;
330
331
332
333
334
335
336
		}

		stopResponseFlag = false;
		await tick();
		if (autoScroll) {
			window.scrollTo({ top: document.body.scrollHeight });
		}
337

338
		if (messages.length == 2 && messages.at(1).content !== '') {
Timothy J. Baek's avatar
Timothy J. Baek committed
339
340
			window.history.replaceState(history.state, '', `/c/${_chatId}`);
			await generateChatTitle(_chatId, userPrompt);
341
342
343
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
344
	const sendPromptOpenAI = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
345
		if ($settings.OPENAI_API_KEY) {
346
			if (models) {
347
348
				let responseMessageId = uuidv4();

349
				let responseMessage = {
350
351
352
					parentId: parentId,
					id: responseMessageId,
					childrenIds: [],
353
					role: 'assistant',
Timothy J. Baek's avatar
Timothy J. Baek committed
354
355
					content: '',
					model: model
356
357
				};

358
359
360
361
362
363
364
365
366
				history.messages[responseMessageId] = responseMessage;
				history.currentId = responseMessageId;
				if (parentId !== null) {
					history.messages[parentId].childrenIds = [
						...history.messages[parentId].childrenIds,
						responseMessageId
					];
				}

Timothy J. Baek's avatar
Timothy J. Baek committed
367
368
				await tick();

369
370
371
372
373
374
				window.scrollTo({ top: document.body.scrollHeight });

				const res = await fetch(`https://api.openai.com/v1/chat/completions`, {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
Timothy J. Baek's avatar
Timothy J. Baek committed
375
						Authorization: `Bearer ${$settings.OPENAI_API_KEY}`
376
377
					},
					body: JSON.stringify({
Timothy J. Baek's avatar
Timothy J. Baek committed
378
						model: model,
379
						stream: true,
380
						messages: [
381
							$settings.system
382
383
								? {
										role: 'system',
Timothy J. Baek's avatar
Timothy J. Baek committed
384
										content: $settings.system
385
386
387
388
389
								  }
								: undefined,
							...messages
						]
							.filter((message) => message)
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
							.map((message) => ({
								role: message.role,
								...(message.files
									? {
											content: [
												{
													type: 'text',
													text: message.content
												},
												...message.files
													.filter((file) => file.type === 'image')
													.map((file) => ({
														type: 'image_url',
														image_url: {
															url: file.url
														}
													}))
											]
									  }
									: { content: message.content })
							})),
411
412
						temperature: $settings.temperature ?? undefined,
						top_p: $settings.top_p ?? undefined,
413
						num_ctx: $settings.num_ctx ?? undefined,
414
						frequency_penalty: $settings.repeat_penalty ?? undefined
415
416
417
418
419
420
421
422
423
424
					})
				});

				const reader = res.body
					.pipeThrough(new TextDecoderStream())
					.pipeThrough(splitStream('\n'))
					.getReader();

				while (true) {
					const { value, done } = await reader.read();
Timothy J. Baek's avatar
Timothy J. Baek committed
425
426
427
					if (done || stopResponseFlag || _chatId !== $chatId) {
						responseMessage.done = true;
						messages = messages;
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
						break;
					}

					try {
						let lines = value.split('\n');

						for (const line of lines) {
							if (line !== '') {
								console.log(line);
								if (line === 'data: [DONE]') {
									responseMessage.done = true;
									messages = messages;
								} else {
									let data = JSON.parse(line.replace(/^data: /, ''));
									console.log(data);

									if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
										continue;
									} else {
										responseMessage.content += data.choices[0].delta.content ?? '';
										messages = messages;
									}
								}
							}
						}
					} catch (error) {
						console.log(error);
					}

					if (autoScroll) {
						window.scrollTo({ top: document.body.scrollHeight });
					}

Timothy J. Baek's avatar
Timothy J. Baek committed
461
					await $db.updateChatById(_chatId, {
462
						title: title === '' ? 'New Chat' : title,
Timothy J. Baek's avatar
Timothy J. Baek committed
463
						models: selectedModels,
464
						system: $settings.system ?? undefined,
465
						options: {
466
467
468
469
							seed: $settings.seed ?? undefined,
							temperature: $settings.temperature ?? undefined,
							repeat_penalty: $settings.repeat_penalty ?? undefined,
							top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
470
							top_p: $settings.top_p ?? undefined,
471
472
							num_ctx: $settings.num_ctx ?? undefined,
							...($settings.options ?? {})
473
						},
Timothy J. Baek's avatar
Timothy J. Baek committed
474
						messages: messages,
475
						history: history
476
477
478
479
480
481
					});
				}

				stopResponseFlag = false;

				await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
482
483
484
485
486
487
488
489
490
491
492
493

				if ($settings.notificationEnabled && !document.hasFocus()) {
					const notification = new Notification(`OpenAI ${model}`, {
						body: responseMessage.content,
						icon: '/favicon.png'
					});
				}

				if ($settings.responseAutoCopy) {
					copyToClipboard(responseMessage.content);
				}

494
495
496
497
498
				if (autoScroll) {
					window.scrollTo({ top: document.body.scrollHeight });
				}

				if (messages.length == 2) {
Timothy J. Baek's avatar
Timothy J. Baek committed
499
500
					window.history.replaceState(history.state, '', `/c/${_chatId}`);
					await setChatTitle(_chatId, userPrompt);
501
502
503
				}
			}
		}
504
505
506
	};

	const submitPrompt = async (userPrompt) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
507
508
		const _chatId = JSON.parse(JSON.stringify($chatId));
		console.log('submitPrompt', _chatId);
509

Timothy J. Baek's avatar
Timothy J. Baek committed
510
		if (selectedModels.includes('')) {
Timothy J. Baek's avatar
Timothy J. Baek committed
511
			toast.error('Model not selected');
512
		} else if (messages.length != 0 && messages.at(-1).done != true) {
Timothy J. Baek's avatar
Timothy J. Baek committed
513
514
			console.log('wait');
		} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
515
516
			document.getElementById('chat-textarea').style.height = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
517
518
519
520
521
522
			let userMessageId = uuidv4();
			let userMessage = {
				id: userMessageId,
				parentId: messages.length !== 0 ? messages.at(-1).id : null,
				childrenIds: [],
				role: 'user',
523
524
				content: userPrompt,
				files: files.length > 0 ? files : undefined
Timothy J. Baek's avatar
Timothy J. Baek committed
525
526
527
528
529
530
531
532
			};

			if (messages.length !== 0) {
				history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
			}

			history.messages[userMessageId] = userMessage;
			history.currentId = userMessageId;
Timothy J. Baek's avatar
Timothy J. Baek committed
533

Timothy J. Baek's avatar
Timothy J. Baek committed
534
535
			await tick();
			if (messages.length == 1) {
536
				await $db.createNewChat({
Timothy J. Baek's avatar
Timothy J. Baek committed
537
					id: _chatId,
538
					title: 'New Chat',
Timothy J. Baek's avatar
Timothy J. Baek committed
539
					models: selectedModels,
540
					system: $settings.system ?? undefined,
541
					options: {
542
543
544
545
						seed: $settings.seed ?? undefined,
						temperature: $settings.temperature ?? undefined,
						repeat_penalty: $settings.repeat_penalty ?? undefined,
						top_k: $settings.top_k ?? undefined,
Anthony Cucci's avatar
Anthony Cucci committed
546
						top_p: $settings.top_p ?? undefined,
547
548
						num_ctx: $settings.num_ctx ?? undefined,
						...($settings.options ?? {})
549
					},
Timothy J. Baek's avatar
Timothy J. Baek committed
550
					messages: messages,
551
					history: history
552
553
				});
			}
554

Timothy J. Baek's avatar
Timothy J. Baek committed
555
556
557
			prompt = '';
			files = [];

558
559
560
561
			setTimeout(() => {
				window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
			}, 50);

Timothy J. Baek's avatar
Timothy J. Baek committed
562
			await sendPrompt(userPrompt, userMessageId, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
563
564
565
		}
	};

566
567
568
569
570
	const stopResponse = () => {
		stopResponseFlag = true;
		console.log('stopResponse');
	};

571
	const regenerateResponse = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
572
573
574
		const _chatId = JSON.parse(JSON.stringify($chatId));
		console.log('regenerateResponse', _chatId);

575
576
577
		if (messages.length != 0 && messages.at(-1).done == true) {
			messages.splice(messages.length - 1, 1);
			messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
578

579
580
			let userMessage = messages.at(-1);
			let userPrompt = userMessage.content;
581

Timothy J. Baek's avatar
Timothy J. Baek committed
582
			await sendPrompt(userPrompt, userMessage.id, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
583
		}
584
	};
585

586
	const generateChatTitle = async (_chatId, userPrompt) => {
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
		if ($settings.titleAutoGenerate ?? true) {
			console.log('generateChatTitle');

			const res = await fetch(`${$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL}/generate`, {
				method: 'POST',
				headers: {
					'Content-Type': 'text/event-stream',
					...($settings.authHeader && { Authorization: $settings.authHeader }),
					...($user && { Authorization: `Bearer ${localStorage.token}` })
				},
				body: JSON.stringify({
					model: selectedModels[0],
					prompt: `Generate a brief 3-5 word title for this question, excluding the term 'title.' Then, please reply with only the title: ${userPrompt}`,
					stream: false
				})
602
			})
603
604
605
606
607
608
609
610
611
612
613
				.then(async (res) => {
					if (!res.ok) throw await res.json();
					return res.json();
				})
				.catch((error) => {
					if ('detail' in error) {
						toast.error(error.detail);
					}
					console.log(error);
					return null;
				});
614

615
616
617
618
619
			if (res) {
				await setChatTitle(_chatId, res.response === '' ? 'New Chat' : res.response);
			}
		} else {
			await setChatTitle(_chatId, `${userPrompt}`);
620
621
622
623
		}
	};

	const setChatTitle = async (_chatId, _title) => {
624
625
		await $db.updateChatById(_chatId, { title: _title });
		if (_chatId === $chatId) {
626
			title = _title;
627
628
		}
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
629
630
</script>

631
632
<svelte:window
	on:scroll={(e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
633
		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
634
635
636
	}}
/>

Timothy J. Baek's avatar
Timothy J. Baek committed
637
<Navbar {title} shareEnabled={messages.length > 0} />
638
639
640
641
<div class="min-h-screen w-full flex justify-center">
	<div class=" py-2.5 flex flex-col justify-between w-full">
		<div class="max-w-2xl mx-auto w-full px-3 md:px-0 mt-10">
			<ModelSelector bind:selectedModels disabled={messages.length > 0} />
Timothy J. Baek's avatar
Timothy J. Baek committed
642
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
643

644
		<div class=" h-full mt-10 mb-32 w-full flex flex-col">
645
646
647
648
649
650
			<Messages
				{selectedModels}
				{selectedModelfile}
				bind:history
				bind:messages
				bind:autoScroll
Timothy J. Baek's avatar
Timothy J. Baek committed
651
				bottomPadding={files.length > 0}
652
653
654
				{sendPrompt}
				{regenerateResponse}
			/>
655
656
		</div>
	</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
657

658
659
	<MessageInput
		bind:files
Timothy J. Baek's avatar
Timothy J. Baek committed
660
		bind:prompt
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
		bind:autoScroll
		suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
			{
				title: ['Help me study', 'vocabulary for a college entrance exam'],
				content: `Help me study vocabulary: write a sentence for me to fill in the blank, and I'll try to pick the correct option.`
			},
			{
				title: ['Give me ideas', `for what to do with my kids' art`],
				content: `What are 5 creative things I could do with my kids' art? I don't want to throw them away, but it's also so much clutter.`
			},
			{
				title: ['Tell me a fun fact', 'about the Roman Empire'],
				content: 'Tell me a random fun fact about the Roman Empire'
			},
			{
				title: ['Show me a code snippet', `of a website's sticky header`],
				content: `Show me a code snippet of a website's sticky header in CSS and JavaScript.`
			}
		]}
		{messages}
		{submitPrompt}
		{stopResponse}
	/>
684
</div>