+page.svelte 18.6 KB
Newer Older
1
2
3
4
5
6
7
8
<script lang="ts">
	import { v4 as uuidv4 } from 'uuid';
	import toast from 'svelte-french-toast';

	import { OLLAMA_API_BASE_URL } from '$lib/constants';
	import { onMount, tick } from 'svelte';
	import { convertMessagesToHistory, splitStream } from '$lib/utils';
	import { goto } from '$app/navigation';
9
	import { config, models, modelfiles, user, settings, db, chats, chatId } from '$lib/stores';
10
11
12
13
14
15

	import MessageInput from '$lib/components/chat/MessageInput.svelte';
	import Messages from '$lib/components/chat/Messages.svelte';
	import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
	import Navbar from '$lib/components/layout/Navbar.svelte';
	import { page } from '$app/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
16
	import { createNewChat, getChatById, getChatList } from '$lib/apis/chats';
17
18
19
20
21
22
23

	let loaded = false;
	let stopResponseFlag = false;
	let autoScroll = true;

	// let chatId = $page.params.id;
	let selectedModels = [''];
24
25
26
27
28
29
	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;
30

31
32
	let chat = null;

33
34
	let title = '';
	let prompt = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
35
	let files = [];
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

	let messages = [];
	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
53
54
	} else {
		messages = [];
55
56
57
58
	}

	$: if ($page.params.id) {
		(async () => {
59
60
			if (await loadChat()) {
				await tick();
61
62
63
64
				loaded = true;
			} else {
				await goto('/');
			}
65
66
67
68
69
70
71
72
73
		})();
	}

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

	const loadChat = async () => {
		await chatId.set($page.params.id);
74
75
		chat = await getChatById(localStorage.token, $chatId).catch(async (error) => {
			await goto('/');
76
			return null;
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
		});

		if (chat) {
			const chatContent = chat.chat;

			if (chatContent) {
				console.log(chatContent);

				selectedModels =
					(chatContent?.models ?? undefined) !== undefined
						? chatContent.models
						: [chatContent.model ?? ''];
				history =
					(chatContent?.history ?? undefined) !== undefined
						? chatContent.history
						: convertMessagesToHistory(chatContent.messages);
				title = chatContent.title;

				let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
				await settings.set({
					..._settings,
					system: chatContent.system ?? _settings.system,
					options: chatContent.options ?? _settings.options
				});
				autoScroll = true;
				await tick();

				if (messages.length > 0) {
					history.messages[messages.at(-1).id].done = true;
				}
				await tick();

				return true;
			} else {
				return null;
			}
113
114
115
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
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
	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);
			}
		);
	};

151
152
153
154
	//////////////////////////
	// Ollama functions
	//////////////////////////

155
156
	const sendPrompt = async (prompt, parentId) => {
		const _chatId = JSON.parse(JSON.stringify($chatId));
157
158
		await Promise.all(
			selectedModels.map(async (model) => {
159
160
				console.log(model);
				if ($models.filter((m) => m.name === model)[0].external) {
161
					await sendPromptOpenAI(model, prompt, parentId, _chatId);
162
				} else {
163
					await sendPromptOllama(model, prompt, parentId, _chatId);
164
165
166
167
				}
			})
		);

Timothy J. Baek's avatar
Timothy J. Baek committed
168
		await chats.set(await getChatList(localStorage.token));
169
170
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
171
	const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
172
		// Create response message
173
174
175
176
177
178
179
180
181
182
		let responseMessageId = uuidv4();
		let responseMessage = {
			parentId: parentId,
			id: responseMessageId,
			childrenIds: [],
			role: 'assistant',
			content: '',
			model: model
		};

Timothy J. Baek's avatar
Timothy J. Baek committed
183
		// Add message to history and Set currentId to messageId
184
185
		history.messages[responseMessageId] = responseMessage;
		history.currentId = responseMessageId;
Timothy J. Baek's avatar
Timothy J. Baek committed
186
187

		// Append messageId to childrenIds of parent message
188
189
190
191
192
193
194
		if (parentId !== null) {
			history.messages[parentId].childrenIds = [
				...history.messages[parentId].childrenIds,
				responseMessageId
			];
		}

Timothy J. Baek's avatar
Timothy J. Baek committed
195
		// Wait until history/message have been updated
Timothy J. Baek's avatar
Timothy J. Baek committed
196
		await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
197
198

		// Scroll down
Timothy J. Baek's avatar
Timothy J. Baek committed
199
		window.scrollTo({ top: document.body.scrollHeight });
200

Timothy J. Baek's avatar
Timothy J. Baek committed
201
202
203
204
		const res = await generateChatCompletion(
			$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
			localStorage.token,
			{
Timothy J. Baek's avatar
Timothy J. Baek committed
205
206
207
208
209
210
211
212
213
214
215
				model: model,
				messages: [
					$settings.system
						? {
								role: 'system',
								content: $settings.system
						  }
						: undefined,
					...messages
				]
					.filter((message) => message)
Timothy J. Baek's avatar
Timothy J. Baek committed
216
217
218
219
220
221
222
223
224
					.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
225
226
227
228
				options: {
					...($settings.options ?? {})
				},
				format: $settings.requestFormat ?? undefined
Timothy J. Baek's avatar
Timothy J. Baek committed
229
230
			}
		);
Timothy J. Baek's avatar
Timothy J. Baek committed
231

232
		if (res && res.ok) {
Rohit Das's avatar
Rohit Das committed
233
234
235
236
237
238
239
240
241
242
243
244
			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;
				}
245

Rohit Das's avatar
Rohit Das committed
246
247
				try {
					let lines = value.split('\n');
248

Rohit Das's avatar
Rohit Das committed
249
250
251
252
					for (const line of lines) {
						if (line !== '') {
							console.log(line);
							let data = JSON.parse(line);
Timothy J. Baek's avatar
Timothy J. Baek committed
253

Rohit Das's avatar
Rohit Das committed
254
255
256
							if ('detail' in data) {
								throw data;
							}
Timothy J. Baek's avatar
Timothy J. Baek committed
257

Rohit Das's avatar
Rohit Das committed
258
259
260
261
262
263
264
							if (data.done == false) {
								if (responseMessage.content == '' && data.message.content == '\n') {
									continue;
								} else {
									responseMessage.content += data.message.content;
									messages = messages;
								}
265
							} else {
Rohit Das's avatar
Rohit Das committed
266
								responseMessage.done = true;
267
268
269
270
271
272
273

								if (responseMessage.content == '') {
									responseMessage.error = true;
									responseMessage.content =
										'Oops! No text generated from Ollama, Please try again.';
								}

Rohit Das's avatar
Rohit Das committed
274
275
276
								responseMessage.context = data.context ?? null;
								responseMessage.info = {
									total_duration: data.total_duration,
Timothy J. Baek's avatar
Timothy J. Baek committed
277
278
279
									load_duration: data.load_duration,
									sample_count: data.sample_count,
									sample_duration: data.sample_duration,
Rohit Das's avatar
Rohit Das committed
280
281
282
283
284
									prompt_eval_count: data.prompt_eval_count,
									prompt_eval_duration: data.prompt_eval_duration,
									eval_count: data.eval_count,
									eval_duration: data.eval_duration
								};
285
								messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
286

Timothy J. Baek's avatar
Timothy J. Baek committed
287
								if ($settings.notificationEnabled && !document.hasFocus()) {
Timothy J. Baek's avatar
Timothy J. Baek committed
288
289
290
291
292
293
294
295
296
297
298
299
300
									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
301
302
303
304

								if ($settings.responseAutoCopy) {
									copyToClipboard(responseMessage.content);
								}
305
306
307
							}
						}
					}
Rohit Das's avatar
Rohit Das committed
308
309
310
311
312
313
				} catch (error) {
					console.log(error);
					if ('detail' in error) {
						toast.error(error.detail);
					}
					break;
314
				}
Rohit Das's avatar
Rohit Das committed
315
316
317

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

321
			if ($chatId == _chatId) {
Timothy J. Baek's avatar
Timothy J. Baek committed
322
				chat = await updateChatById(localStorage.token, _chatId, {
Rohit Das's avatar
Rohit Das committed
323
324
325
					messages: messages,
					history: history
				});
Timothy J. Baek's avatar
Timothy J. Baek committed
326
				await chats.set(await getChatList(localStorage.token));
327
			}
328
329
330
		} else {
			if (res !== null) {
				const error = await res.json();
331
332
333
				console.log(error);
				if ('detail' in error) {
					toast.error(error.detail);
334
					responseMessage.content = error.detail;
335
336
				} else {
					toast.error(error.error);
337
					responseMessage.content = error.error;
338
				}
339
340
			} else {
				toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
341
				responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
342
343
			}

344
345
346
347
			responseMessage.error = true;
			responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
			responseMessage.done = true;
			messages = messages;
348
349
350
351
		}

		stopResponseFlag = false;
		await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
352

353
354
355
356
357
		if (autoScroll) {
			window.scrollTo({ top: document.body.scrollHeight });
		}

		if (messages.length == 2 && messages.at(1).content !== '') {
Timothy J. Baek's avatar
Timothy J. Baek committed
358
359
			window.history.replaceState(history.state, '', `/c/${_chatId}`);
			await generateChatTitle(_chatId, userPrompt);
360
361
362
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
363
	const sendPromptOpenAI = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
364
		if ($settings.OPENAI_API_KEY) {
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
			if (models) {
				let responseMessageId = uuidv4();

				let responseMessage = {
					parentId: parentId,
					id: responseMessageId,
					childrenIds: [],
					role: 'assistant',
					content: '',
					model: model
				};

				history.messages[responseMessageId] = responseMessage;
				history.currentId = responseMessageId;
				if (parentId !== null) {
					history.messages[parentId].childrenIds = [
						...history.messages[parentId].childrenIds,
						responseMessageId
					];
				}

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

388
389
390
391
392
393
				const res = await fetch(
					`${$settings.OPENAI_API_BASE_URL ?? 'https://api.openai.com/v1'}/chat/completions`,
					{
						method: 'POST',
						headers: {
							Authorization: `Bearer ${$settings.OPENAI_API_KEY}`,
Timothy J. Baek's avatar
Timothy J. Baek committed
394
							'Content-Type': 'application/json'
395
396
397
398
399
400
						},
						body: JSON.stringify({
							model: model,
							stream: true,
							messages: [
								$settings.system
401
									? {
402
403
											role: 'system',
											content: $settings.system
404
									  }
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
									: undefined,
								...messages
							]
								.filter((message) => message)
								.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 })
								})),
							temperature: $settings.temperature ?? undefined,
							top_p: $settings.top_p ?? undefined,
							num_ctx: $settings.num_ctx ?? undefined,
							frequency_penalty: $settings.repeat_penalty ?? undefined
						})
435
					}
436
437
438
439
				).catch((err) => {
					console.log(err);
					return null;
				});
440

441
442
443
444
445
446
447
448
449
450
451
452
453
				if (res && res.ok) {
					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;
						}
454

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

458
459
460
461
462
							for (const line of lines) {
								if (line !== '') {
									console.log(line);
									if (line === 'data: [DONE]') {
										responseMessage.done = true;
463
										messages = messages;
464
465
466
467
468
469
470
471
472
473
									} 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;
										}
474
475
476
									}
								}
							}
477
478
						} catch (error) {
							console.log(error);
479
480
						}

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

488
489
490
						if ($settings.responseAutoCopy) {
							copyToClipboard(responseMessage.content);
						}
491

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

497
					if ($chatId == _chatId) {
Timothy J. Baek's avatar
Timothy J. Baek committed
498
						chat = await updateChatById(localStorage.token, _chatId, {
499
500
501
							messages: messages,
							history: history
						});
Timothy J. Baek's avatar
Timothy J. Baek committed
502
						await chats.set(await getChatList(localStorage.token));
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
					}
				} else {
					if (res !== null) {
						const error = await res.json();
						console.log(error);
						if ('detail' in error) {
							toast.error(error.detail);
							responseMessage.content = error.detail;
						} else {
							if ('message' in error.error) {
								toast.error(error.error.message);
								responseMessage.content = error.error.message;
							} else {
								toast.error(error.error);
								responseMessage.content = error.error;
							}
						}
					} else {
						toast.error(`Uh-oh! There was an issue connecting to ${model}.`);
						responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
					}
Timothy J. Baek's avatar
Timothy J. Baek committed
524

525
526
527
528
					responseMessage.error = true;
					responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
					responseMessage.done = true;
					messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
529
530
				}

531
532
				stopResponseFlag = false;
				await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
533

534
535
536
537
538
				if (autoScroll) {
					window.scrollTo({ top: document.body.scrollHeight });
				}

				if (messages.length == 2) {
Timothy J. Baek's avatar
Timothy J. Baek committed
539
540
					window.history.replaceState(history.state, '', `/c/${_chatId}`);
					await setChatTitle(_chatId, userPrompt);
541
542
543
544
545
546
				}
			}
		}
	};

	const submitPrompt = async (userPrompt) => {
547
		console.log('submitPrompt', $chatId);
548
549
550
551

		if (selectedModels.includes('')) {
			toast.error('Model not selected');
		} else if (messages.length != 0 && messages.at(-1).done != true) {
Timothy J. Baek's avatar
Timothy J. Baek committed
552
			// Response not done
553
554
			console.log('wait');
		} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
555
			// Reset chat message textarea height
556
557
			document.getElementById('chat-textarea').style.height = '';

Timothy J. Baek's avatar
Timothy J. Baek committed
558
			// Create user message
559
560
561
562
563
564
			let userMessageId = uuidv4();
			let userMessage = {
				id: userMessageId,
				parentId: messages.length !== 0 ? messages.at(-1).id : null,
				childrenIds: [],
				role: 'user',
Timothy J. Baek's avatar
Timothy J. Baek committed
565
566
				content: userPrompt,
				files: files.length > 0 ? files : undefined
567
568
			};

Timothy J. Baek's avatar
Timothy J. Baek committed
569
570
571
572
573
			// Add message to history and Set currentId to messageId
			history.messages[userMessageId] = userMessage;
			history.currentId = userMessageId;

			// Append messageId to childrenIds of parent message
574
575
576
577
			if (messages.length !== 0) {
				history.messages[messages.at(-1).id].childrenIds.push(userMessageId);
			}

Timothy J. Baek's avatar
Timothy J. Baek committed
578
			// Wait until history/message have been updated
Timothy J. Baek's avatar
Timothy J. Baek committed
579
			await tick();
580

Timothy J. Baek's avatar
Timothy J. Baek committed
581
			// Create new chat if only one message in messages
Timothy J. Baek's avatar
Timothy J. Baek committed
582
			if (messages.length == 1) {
Timothy J. Baek's avatar
Timothy J. Baek committed
583
				chat = await createNewChat(localStorage.token, {
584
					id: $chatId,
585
586
587
588
					title: 'New Chat',
					models: selectedModels,
					system: $settings.system ?? undefined,
					options: {
589
						...($settings.options ?? {})
590
591
					},
					messages: messages,
Timothy J. Baek's avatar
Timothy J. Baek committed
592
593
					history: history,
					timestamp: Date.now()
594
				});
Timothy J. Baek's avatar
Timothy J. Baek committed
595
				await chats.set(await getChatList(localStorage.token));
596
597
				await chatId.set(chat.id);
				await tick();
598
599
			}

Timothy J. Baek's avatar
Timothy J. Baek committed
600
			// Reset chat input textarea
Timothy J. Baek's avatar
Timothy J. Baek committed
601
602
603
			prompt = '';
			files = [];

Timothy J. Baek's avatar
Timothy J. Baek committed
604
			// Send prompt
605
			await sendPrompt(userPrompt, userMessageId);
606
607
608
609
610
611
612
613
614
		}
	};

	const stopResponse = () => {
		stopResponseFlag = true;
		console.log('stopResponse');
	};

	const regenerateResponse = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
615
		console.log('regenerateResponse');
616
617
618
619
620
621
622
		if (messages.length != 0 && messages.at(-1).done == true) {
			messages.splice(messages.length - 1, 1);
			messages = messages;

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

Timothy J. Baek's avatar
Timothy J. Baek committed
623
			await sendPrompt(userPrompt, userMessage.id);
624
625
626
627
		}
	};

	const generateChatTitle = async (_chatId, userPrompt) => {
628
		if ($settings.titleAutoGenerate ?? true) {
Timothy J. Baek's avatar
Timothy J. Baek committed
629
630
631
632
633
634
635
636
637
			const title = await generateTitle(
				$settings?.API_BASE_URL ?? OLLAMA_API_BASE_URL,
				localStorage.token,
				selectedModels[0],
				userPrompt
			);

			if (title) {
				await setChatTitle(_chatId, title);
638
639
640
			}
		} else {
			await setChatTitle(_chatId, `${userPrompt}`);
641
642
643
644
		}
	};

	const setChatTitle = async (_chatId, _title) => {
645
		if (_chatId === $chatId) {
646
647
			title = _title;
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
648
649
650

		chat = await updateChatById(localStorage.token, _chatId, { title: _title });
		await chats.set(await getChatList(localStorage.token));
651
652
653
654
655
656
657
658
659
	};
</script>

<svelte:window
	on:scroll={(e) => {
		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
	}}
/>

Timothy J. Baek's avatar
Timothy J. Baek committed
660
{#if loaded}
661
662
663
664
665
666
667
	<Navbar
		{title}
		shareEnabled={messages.length > 0}
		initNewChat={() => {
			goto('/');
		}}
	/>
Timothy J. Baek's avatar
Timothy J. Baek committed
668
669
670
671
672
673
674
	<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} />
			</div>

			<div class=" h-full mt-10 mb-32 w-full flex flex-col">
675
				<Messages
676
					chatId={$chatId}
677
678
679
680
681
					{selectedModels}
					{selectedModelfile}
					bind:history
					bind:messages
					bind:autoScroll
Timothy J. Baek's avatar
Timothy J. Baek committed
682
					bottomPadding={files.length > 0}
683
684
685
					{sendPrompt}
					{regenerateResponse}
				/>
Timothy J. Baek's avatar
Timothy J. Baek committed
686
			</div>
687
688
		</div>

689
		<MessageInput
Timothy J. Baek's avatar
Timothy J. Baek committed
690
			bind:files
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
			bind:prompt
			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}
		/>
715
	</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
716
{/if}