"scripts/cache/run_wan_i2v_mag_calibration.sh" did not exist on "3b460075b2a7b3ead0e7aae55d8f8c7809d52e53"
+page.svelte 19.2 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';

Timothy J. Baek's avatar
Timothy J. Baek committed
5
	import { onMount, tick } from 'svelte';
6
	import { goto } from '$app/navigation';
7
	import { page } from '$app/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
8

Timothy J. Baek's avatar
Timothy J. Baek committed
9
	import { models, modelfiles, user, settings, chats, chatId, config } from '$lib/stores';
10
	import { copyToClipboard, splitStream } from '$lib/utils';
Timothy J. Baek's avatar
Timothy J. Baek committed
11
12

	import { generateChatCompletion, generateTitle } from '$lib/apis/ollama';
13
14
15
	import { createNewChat, getChatList, updateChatById } from '$lib/apis/chats';
	import { queryVectorDB } from '$lib/apis/rag';
	import { generateOpenAIChatCompletion } from '$lib/apis/openai';
16
17
18
19

	import MessageInput from '$lib/components/chat/MessageInput.svelte';
	import Messages from '$lib/components/chat/Messages.svelte';
	import ModelSelector from '$lib/components/chat/ModelSelector.svelte';
20
	import Navbar from '$lib/components/layout/Navbar.svelte';
21
	import { RAGTemplate } from '$lib/utils/rag';
22

23
24
	let stopResponseFlag = false;
	let autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
25
	let processing = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
26

Timothy J. Baek's avatar
Timothy J. Baek committed
27
	let selectedModels = [''];
Timothy J. Baek's avatar
Timothy J. Baek committed
28

29
30
31
32
33
34
	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;
35

36
37
38
39
40
41
42
43
44
45
46
	let selectedModelfiles = {};
	$: selectedModelfiles = selectedModels.reduce((a, tagName, i, arr) => {
		const modelfile =
			$modelfiles.filter((modelfile) => modelfile.tagName === tagName)?.at(0) ?? undefined;

		return {
			...a,
			...(modelfile && { [tagName]: modelfile })
		};
	}, {});

47
48
	let chat = null;

Timothy J. Baek's avatar
Timothy J. Baek committed
49
	let title = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
50
	let prompt = '';
51
	let files = [];
52
	let messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
	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
68
69
	} else {
		messages = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
70
	}
Timothy J. Baek's avatar
Timothy J. Baek committed
71

72
	onMount(async () => {
73
		await initNewChat();
74
75
76
77
78
79
	});

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

80
	const initNewChat = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
81
82
		window.history.replaceState(history.state, '', `/`);

83
84
85
		console.log('initNewChat');

		await chatId.set('');
86
		console.log($chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
87

88
		autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
89

90
91
92
93
94
		title = '';
		messages = [];
		history = {
			messages: {},
			currentId: null
Timothy J. Baek's avatar
Timothy J. Baek committed
95
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
96
97
98
99
100
101
102
103
104
105
106
107

		console.log($config);

		if ($page.url.searchParams.get('models')) {
			selectedModels = $page.url.searchParams.get('models')?.split(',');
		} else if ($settings?.models) {
			selectedModels = $settings?.models;
		} else if ($config?.default_models) {
			selectedModels = $config?.default_models.split(',');
		} else {
			selectedModels = [''];
		}
108
109
110
111
112

		let _settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
		settings.set({
			..._settings
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
113
114
	};

115
116
117
118
	//////////////////////////
	// Ollama functions
	//////////////////////////

Timothy J. Baek's avatar
Timothy J. Baek committed
119
	const submitPrompt = async (userPrompt, _user = null) => {
120
121
122
123
124
125
126
		console.log('submitPrompt', $chatId);

		if (selectedModels.includes('')) {
			toast.error('Model not selected');
		} else if (messages.length != 0 && messages.at(-1).done != true) {
			// Response not done
			console.log('wait');
127
128
129
130
131
132
133
134
		} else if (
			files.length > 0 &&
			files.filter((file) => file.upload_status === false).length > 0
		) {
			// Upload not done
			toast.error(
				`Oops! Hold tight! Your files are still in the processing oven. We're cooking them up to perfection. Please be patient and we'll let you know once they're ready.`
			);
135
136
137
138
139
140
141
142
143
144
145
		} else {
			// Reset chat message textarea height
			document.getElementById('chat-textarea').style.height = '';

			// Create user message
			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
146
				user: _user ?? undefined,
147
				content: userPrompt,
Timothy J. Baek's avatar
Timothy J. Baek committed
148
				files: files.length > 0 ? files : undefined,
Timothy J. Baek's avatar
Timothy J. Baek committed
149
				timestamp: Math.floor(Date.now() / 1000) // Unix epoch
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
			};

			// Add message to history and Set currentId to messageId
			history.messages[userMessageId] = userMessage;
			history.currentId = userMessageId;

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

			// Wait until history/message have been updated
			await tick();

			// Create new chat if only one message in messages
			if (messages.length == 1) {
				if ($settings.saveChatHistory ?? true) {
					chat = await createNewChat(localStorage.token, {
						id: $chatId,
						title: 'New Chat',
						models: selectedModels,
						system: $settings.system ?? undefined,
						options: {
							...($settings.options ?? {})
						},
						messages: messages,
						history: history,
						timestamp: Date.now()
					});
					await chats.set(await getChatList(localStorage.token));
					await chatId.set(chat.id);
				} else {
					await chatId.set('local');
				}
				await tick();
			}

			// Reset chat input textarea
			prompt = '';
			files = [];

			// Send prompt
			await sendPrompt(userPrompt, userMessageId);
		}
	};

196
197
	const sendPrompt = async (prompt, parentId) => {
		const _chatId = JSON.parse(JSON.stringify($chatId));
198

199
200
201
202
		const docs = messages
			.filter((message) => message?.files ?? null)
			.map((message) => message.files.filter((item) => item.type === 'doc'))
			.flat(1);
Timothy J. Baek's avatar
Timothy J. Baek committed
203
204

		console.log(docs);
205
		if (docs.length > 0) {
Timothy J. Baek's avatar
Timothy J. Baek committed
206
			processing = 'Reading';
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
			const query = history.messages[parentId].content;

			let relevantContexts = await Promise.all(
				docs.map(async (doc) => {
					return await queryVectorDB(localStorage.token, doc.collection_name, query, 4).catch(
						(error) => {
							console.log(error);
							return null;
						}
					);
				})
			);
			relevantContexts = relevantContexts.filter((context) => context);

			const contextString = relevantContexts.reduce((a, context, i, arr) => {
				return `${a}${context.documents.join(' ')}\n`;
			}, '');

Timothy J. Baek's avatar
Timothy J. Baek committed
225
226
			console.log(contextString);

227
228
229
			history.messages[parentId].raContent = RAGTemplate(contextString, query);
			history.messages[parentId].contexts = relevantContexts;
			await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
230
			processing = '';
231
232
		}

Timothy J. Baek's avatar
Timothy J. Baek committed
233
234
		await Promise.all(
			selectedModels.map(async (model) => {
235
				console.log(model);
Timothy J. Baek's avatar
Timothy J. Baek committed
236
237
238
				const modelTag = $models.filter((m) => m.name === model).at(0);

				if (modelTag?.external) {
239
					await sendPromptOpenAI(model, prompt, parentId, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
240
				} else if (modelTag) {
241
					await sendPromptOllama(model, prompt, parentId, _chatId);
Timothy J. Baek's avatar
Timothy J. Baek committed
242
243
				} else {
					toast.error(`Model ${model} not found`);
Timothy J. Baek's avatar
Timothy J. Baek committed
244
245
246
				}
			})
		);
247

Timothy J. Baek's avatar
Timothy J. Baek committed
248
		await chats.set(await getChatList(localStorage.token));
249
250
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
251
	const sendPromptOllama = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
252
		// Create response message
Timothy J. Baek's avatar
Timothy J. Baek committed
253
		let responseMessageId = uuidv4();
254
		let responseMessage = {
Timothy J. Baek's avatar
Timothy J. Baek committed
255
256
257
			parentId: parentId,
			id: responseMessageId,
			childrenIds: [],
258
			role: 'assistant',
Timothy J. Baek's avatar
Timothy J. Baek committed
259
			content: '',
Timothy J. Baek's avatar
Timothy J. Baek committed
260
			model: model,
Timothy J. Baek's avatar
Timothy J. Baek committed
261
			timestamp: Math.floor(Date.now() / 1000) // Unix epoch
262
263
		};

Timothy J. Baek's avatar
Timothy J. Baek committed
264
		// Add message to history and Set currentId to messageId
Timothy J. Baek's avatar
Timothy J. Baek committed
265
266
		history.messages[responseMessageId] = responseMessage;
		history.currentId = responseMessageId;
Timothy J. Baek's avatar
Timothy J. Baek committed
267
268

		// Append messageId to childrenIds of parent message
Timothy J. Baek's avatar
Timothy J. Baek committed
269
270
271
272
273
274
275
		if (parentId !== null) {
			history.messages[parentId].childrenIds = [
				...history.messages[parentId].childrenIds,
				responseMessageId
			];
		}

Timothy J. Baek's avatar
Timothy J. Baek committed
276
		// Wait until history/message have been updated
Timothy J. Baek's avatar
Timothy J. Baek committed
277
		await tick();
Timothy J. Baek's avatar
Timothy J. Baek committed
278
279

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

282
283
284
285
286
287
288
289
290
291
292
293
		const res = await generateChatCompletion(localStorage.token, {
			model: model,
			messages: [
				$settings.system
					? {
							role: 'system',
							content: $settings.system
					  }
					: undefined,
				...messages
			]
				.filter((message) => message)
294
				.map((message, idx, arr) => ({
295
					role: message.role,
296
					content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content,
297
298
299
300
301
302
303
304
305
306
307
					...(message.files && {
						images: message.files
							.filter((file) => file.type === 'image')
							.map((file) => file.url.slice(file.url.indexOf(',') + 1))
					})
				})),
			options: {
				...($settings.options ?? {})
			},
			format: $settings.requestFormat ?? undefined
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
308

309
		if (res && res.ok) {
Rohit Das's avatar
Rohit Das committed
310
311
312
313
314
315
316
317
318
319
320
321
			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;
				}
322

Rohit Das's avatar
Rohit Das committed
323
324
				try {
					let lines = value.split('\n');
325

Rohit Das's avatar
Rohit Das committed
326
327
328
329
					for (const line of lines) {
						if (line !== '') {
							console.log(line);
							let data = JSON.parse(line);
Timothy J. Baek's avatar
Timothy J. Baek committed
330

Rohit Das's avatar
Rohit Das committed
331
332
333
							if ('detail' in data) {
								throw data;
							}
Timothy J. Baek's avatar
Timothy J. Baek committed
334

Rohit Das's avatar
Rohit Das committed
335
336
337
338
339
340
341
							if (data.done == false) {
								if (responseMessage.content == '' && data.message.content == '\n') {
									continue;
								} else {
									responseMessage.content += data.message.content;
									messages = messages;
								}
342
							} else {
Rohit Das's avatar
Rohit Das committed
343
								responseMessage.done = true;
344
345
346
347
348
349
350

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

Rohit Das's avatar
Rohit Das committed
351
352
353
								responseMessage.context = data.context ?? null;
								responseMessage.info = {
									total_duration: data.total_duration,
Timothy J. Baek's avatar
Timothy J. Baek committed
354
355
356
									load_duration: data.load_duration,
									sample_count: data.sample_count,
									sample_duration: data.sample_duration,
Rohit Das's avatar
Rohit Das committed
357
358
359
360
361
									prompt_eval_count: data.prompt_eval_count,
									prompt_eval_duration: data.prompt_eval_duration,
									eval_count: data.eval_count,
									eval_duration: data.eval_duration
								};
362
								messages = messages;
Timothy J. Baek's avatar
Timothy J. Baek committed
363

Timothy J. Baek's avatar
Timothy J. Baek committed
364
								if ($settings.notificationEnabled && !document.hasFocus()) {
Timothy J. Baek's avatar
Timothy J. Baek committed
365
366
367
368
369
370
371
372
373
374
375
376
377
									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
378
379
380
381

								if ($settings.responseAutoCopy) {
									copyToClipboard(responseMessage.content);
								}
382
383
384
							}
						}
					}
Rohit Das's avatar
Rohit Das committed
385
386
387
388
389
390
				} catch (error) {
					console.log(error);
					if ('detail' in error) {
						toast.error(error.detail);
					}
					break;
391
				}
Rohit Das's avatar
Rohit Das committed
392
393
394

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

398
			if ($chatId == _chatId) {
399
400
401
402
403
404
405
				if ($settings.saveChatHistory ?? true) {
					chat = await updateChatById(localStorage.token, _chatId, {
						messages: messages,
						history: history
					});
					await chats.set(await getChatList(localStorage.token));
				}
406
			}
407
408
409
		} else {
			if (res !== null) {
				const error = await res.json();
410
				console.log(error);
411
412
				if ('detail' in error) {
					toast.error(error.detail);
413
					responseMessage.content = error.detail;
414
415
				} else {
					toast.error(error.error);
416
					responseMessage.content = error.error;
417
				}
418
419
			} else {
				toast.error(`Uh-oh! There was an issue connecting to Ollama.`);
420
				responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
421
422
			}

423
424
425
426
			responseMessage.error = true;
			responseMessage.content = `Uh-oh! There was an issue connecting to Ollama.`;
			responseMessage.done = true;
			messages = messages;
427
428
429
430
		}

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

432
433
434
		if (autoScroll) {
			window.scrollTo({ top: document.body.scrollHeight });
		}
435

436
		if (messages.length == 2 && messages.at(1).content !== '') {
Timothy J. Baek's avatar
Timothy J. Baek committed
437
438
			window.history.replaceState(history.state, '', `/c/${_chatId}`);
			await generateChatTitle(_chatId, userPrompt);
439
440
441
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
442
	const sendPromptOpenAI = async (model, userPrompt, parentId, _chatId) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
443
		let responseMessageId = uuidv4();
444

Timothy J. Baek's avatar
Timothy J. Baek committed
445
446
447
448
449
450
		let responseMessage = {
			parentId: parentId,
			id: responseMessageId,
			childrenIds: [],
			role: 'assistant',
			content: '',
Timothy J. Baek's avatar
Timothy J. Baek committed
451
			model: model,
Timothy J. Baek's avatar
Timothy J. Baek committed
452
			timestamp: Math.floor(Date.now() / 1000) // Unix epoch
Timothy J. Baek's avatar
Timothy J. Baek committed
453
		};
454

Timothy J. Baek's avatar
Timothy J. Baek committed
455
456
457
458
459
460
461
462
		history.messages[responseMessageId] = responseMessage;
		history.currentId = responseMessageId;
		if (parentId !== null) {
			history.messages[parentId].childrenIds = [
				...history.messages[parentId].childrenIds,
				responseMessageId
			];
		}
463

Timothy J. Baek's avatar
Timothy J. Baek committed
464
		window.scrollTo({ top: document.body.scrollHeight });
465

Timothy J. Baek's avatar
Timothy J. Baek committed
466
467
468
469
470
471
472
473
474
475
476
477
478
		const res = await generateOpenAIChatCompletion(localStorage.token, {
			model: model,
			stream: true,
			messages: [
				$settings.system
					? {
							role: 'system',
							content: $settings.system
					  }
					: undefined,
				...messages
			]
				.filter((message) => message)
479
				.map((message, idx, arr) => ({
Timothy J. Baek's avatar
Timothy J. Baek committed
480
481
482
483
484
485
					role: message.role,
					...(message.files
						? {
								content: [
									{
										type: 'text',
486
487
488
489
										text:
											arr.length - 1 !== idx
												? message.content
												: message?.raContent ?? message.content
Timothy J. Baek's avatar
Timothy J. Baek committed
490
491
492
493
494
495
496
497
498
499
500
									},
									...message.files
										.filter((file) => file.type === 'image')
										.map((file) => ({
											type: 'image_url',
											image_url: {
												url: file.url
											}
										}))
								]
						  }
501
502
503
504
						: {
								content:
									arr.length - 1 !== idx ? message.content : message?.raContent ?? message.content
						  })
Timothy J. Baek's avatar
Timothy J. Baek committed
505
506
507
508
509
510
511
512
513
				})),
			seed: $settings?.options?.seed ?? undefined,
			stop: $settings?.options?.stop ?? undefined,
			temperature: $settings?.options?.temperature ?? undefined,
			top_p: $settings?.options?.top_p ?? undefined,
			num_ctx: $settings?.options?.num_ctx ?? undefined,
			frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
			max_tokens: $settings?.options?.num_predict ?? undefined
		});
514

Timothy J. Baek's avatar
Timothy J. Baek committed
515
516
517
518
519
		if (res && res.ok) {
			const reader = res.body
				.pipeThrough(new TextDecoderStream())
				.pipeThrough(splitStream('\n'))
				.getReader();
520

Timothy J. Baek's avatar
Timothy J. Baek committed
521
522
523
524
525
526
527
			while (true) {
				const { value, done } = await reader.read();
				if (done || stopResponseFlag || _chatId !== $chatId) {
					responseMessage.done = true;
					messages = messages;
					break;
				}
528

Timothy J. Baek's avatar
Timothy J. Baek committed
529
530
531
532
533
534
535
536
537
				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;
538
							} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
539
540
541
542
543
544
545
546
547
								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;
								}
548
549
550
							}
						}
					}
Timothy J. Baek's avatar
Timothy J. Baek committed
551
552
553
				} catch (error) {
					console.log(error);
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
554

Timothy J. Baek's avatar
Timothy J. Baek committed
555
556
557
558
559
				if ($settings.notificationEnabled && !document.hasFocus()) {
					const notification = new Notification(`OpenAI ${model}`, {
						body: responseMessage.content,
						icon: '/favicon.png'
					});
Timothy J. Baek's avatar
Timothy J. Baek committed
560
561
				}

Timothy J. Baek's avatar
Timothy J. Baek committed
562
563
564
				if ($settings.responseAutoCopy) {
					copyToClipboard(responseMessage.content);
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
565

566
567
568
				if (autoScroll) {
					window.scrollTo({ top: document.body.scrollHeight });
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
569
			}
570

Timothy J. Baek's avatar
Timothy J. Baek committed
571
			if ($chatId == _chatId) {
572
573
574
575
576
577
578
				if ($settings.saveChatHistory ?? true) {
					chat = await updateChatById(localStorage.token, _chatId, {
						messages: messages,
						history: history
					});
					await chats.set(await getChatList(localStorage.token));
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
			}
		} 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;
					}
595
				}
Timothy J. Baek's avatar
Timothy J. Baek committed
596
597
598
			} else {
				toast.error(`Uh-oh! There was an issue connecting to ${model}.`);
				responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
599
			}
Timothy J. Baek's avatar
Timothy J. Baek committed
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616

			responseMessage.error = true;
			responseMessage.content = `Uh-oh! There was an issue connecting to ${model}.`;
			responseMessage.done = true;
			messages = messages;
		}

		stopResponseFlag = false;
		await tick();

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

		if (messages.length == 2) {
			window.history.replaceState(history.state, '', `/c/${_chatId}`);
			await setChatTitle(_chatId, userPrompt);
617
		}
618
619
	};

620
621
622
623
624
	const stopResponse = () => {
		stopResponseFlag = true;
		console.log('stopResponse');
	};

625
	const regenerateResponse = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
626
		console.log('regenerateResponse');
627
628
629
		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
630

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

Timothy J. Baek's avatar
Timothy J. Baek committed
634
			await sendPrompt(userPrompt, userMessage.id);
Timothy J. Baek's avatar
Timothy J. Baek committed
635
		}
636
	};
637

638
	const generateChatTitle = async (_chatId, userPrompt) => {
639
		if ($settings.titleAutoGenerate ?? true) {
Timothy J. Baek's avatar
Timothy J. Baek committed
640
641
			const title = await generateTitle(
				localStorage.token,
642
				$settings?.titleAutoGenerateModel ?? selectedModels[0],
Timothy J. Baek's avatar
Timothy J. Baek committed
643
644
645
646
647
				userPrompt
			);

			if (title) {
				await setChatTitle(_chatId, title);
648
649
650
			}
		} else {
			await setChatTitle(_chatId, `${userPrompt}`);
651
652
653
654
		}
	};

	const setChatTitle = async (_chatId, _title) => {
655
		if (_chatId === $chatId) {
656
			title = _title;
657
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
658

659
660
661
662
		if ($settings.saveChatHistory ?? true) {
			chat = await updateChatById(localStorage.token, _chatId, { title: _title });
			await chats.set(await getChatList(localStorage.token));
		}
663
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
664
665
</script>

666
667
<svelte:window
	on:scroll={(e) => {
Timothy J. Baek's avatar
Timothy J. Baek committed
668
		autoScroll = window.innerHeight + window.scrollY >= document.body.offsetHeight - 40;
669
670
671
	}}
/>

672
<Navbar {title} shareEnabled={messages.length > 0} {initNewChat} />
673
674
675
676
<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
677
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
678

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

695
696
	<MessageInput
		bind:files
Timothy J. Baek's avatar
Timothy J. Baek committed
697
		bind:prompt
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
		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}
	/>
721
</div>