"tools/vscode:/vscode.git/clone" did not exist on "f12064ee197112bb67c2d0a8bf02029c0c41edfc"
MessageInput.svelte 34.4 KB
Newer Older
1
<script lang="ts">
Jannik Streidl's avatar
Jannik Streidl committed
2
	import { toast } from 'svelte-sonner';
3
	import { onMount, tick, getContext } from 'svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
4
	import { modelfiles, settings, showSidebar } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
5
	import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
6

Timothy J. Baek's avatar
Timothy J. Baek committed
7
8
9
10
11
	import {
		uploadDocToVectorDB,
		uploadWebToVectorDB,
		uploadYoutubeTranscriptionToVectorDB
	} from '$lib/apis/rag';
Timothy J. Baek's avatar
Timothy J. Baek committed
12
13
14
15
16
17
	import { SUPPORTED_FILE_TYPE, SUPPORTED_FILE_EXTENSIONS, WEBUI_BASE_URL } from '$lib/constants';

	import { transcribeAudio } from '$lib/apis/audio';

	import Prompts from './MessageInput/PromptCommands.svelte';
	import Suggestions from './MessageInput/Suggestions.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
18
19
	import AddFilesPlaceholder from '../AddFilesPlaceholder.svelte';
	import Documents from './MessageInput/Documents.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
20
	import Models from './MessageInput/Models.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
21
	import Tooltip from '../common/Tooltip.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
22
	import XMark from '$lib/components/icons/XMark.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
23

24
25
	const i18n = getContext('i18n');

26
27
28
29
	export let submitPrompt: Function;
	export let stopResponse: Function;

	export let autoScroll = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
30
31
	export let selectedModel = '';

Ased Mammad's avatar
Ased Mammad committed
32
	let chatTextAreaElement: HTMLTextAreaElement;
33
	let filesInputElement;
Timothy J. Baek's avatar
Timothy J. Baek committed
34

Timothy J. Baek's avatar
Timothy J. Baek committed
35
	let promptsElement;
Timothy J. Baek's avatar
Timothy J. Baek committed
36
	let documentsElement;
Timothy J. Baek's avatar
Timothy J. Baek committed
37
	let modelsElement;
Timothy J. Baek's avatar
Timothy J. Baek committed
38

39
	let inputFiles;
40
	let dragged = false;
41

Timothy J. Baek's avatar
Timothy J. Baek committed
42
43
44
	let user = null;
	let chatInputPlaceholder = '';

45
46
	export let files = [];

Timothy J. Baek's avatar
Timothy J. Baek committed
47
	export let fileUploadEnabled = true;
48
49
	export let speechRecognitionEnabled = true;

50
51
	export let webSearchAvailable = true; // TODO: Default to false

52
53
54
55
56
	export let prompt = '';
	export let messages = [];

	let speechRecognition;

Timothy J. Baek's avatar
Timothy J. Baek committed
57
	$: if (prompt) {
58
59
60
		if (chatTextAreaElement) {
			chatTextAreaElement.style.height = '';
			chatTextAreaElement.style.height = Math.min(chatTextAreaElement.scrollHeight, 200) + 'px';
Timothy J. Baek's avatar
Timothy J. Baek committed
61
62
63
		}
	}

Timothy J. Baek's avatar
Timothy J. Baek committed
64
65
66
67
68
	let mediaRecorder;
	let audioChunks = [];
	let isRecording = false;
	const MIN_DECIBELS = -45;

69
70
	let useWebSearch = false;

Timothy J. Baek's avatar
Timothy J. Baek committed
71
72
73
74
75
	const scrollToBottom = () => {
		const element = document.getElementById('messages-container');
		element.scrollTop = element.scrollHeight;
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
	const startRecording = async () => {
		const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
		mediaRecorder = new MediaRecorder(stream);
		mediaRecorder.onstart = () => {
			isRecording = true;
			console.log('Recording started');
		};
		mediaRecorder.ondataavailable = (event) => audioChunks.push(event.data);
		mediaRecorder.onstop = async () => {
			isRecording = false;
			console.log('Recording stopped');

			// Create a blob from the audio chunks
			const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });

			const file = blobToFile(audioBlob, 'recording.wav');

			const res = await transcribeAudio(localStorage.token, file).catch((error) => {
				toast.error(error);
				return null;
			});

			if (res) {
				prompt = res.text;
				await tick();
101
				chatTextAreaElement?.focus();
Timothy J. Baek's avatar
Timothy J. Baek committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
151
152
153
154
155
156
157
158
159
160

				if (prompt !== '' && $settings?.speechAutoSend === true) {
					submitPrompt(prompt, user);
				}
			}

			// saveRecording(audioBlob);
			audioChunks = [];
		};

		// Start recording
		mediaRecorder.start();

		// Monitor silence
		monitorSilence(stream);
	};

	const monitorSilence = (stream) => {
		const audioContext = new AudioContext();
		const audioStreamSource = audioContext.createMediaStreamSource(stream);
		const analyser = audioContext.createAnalyser();
		analyser.minDecibels = MIN_DECIBELS;
		audioStreamSource.connect(analyser);

		const bufferLength = analyser.frequencyBinCount;
		const domainData = new Uint8Array(bufferLength);

		let lastSoundTime = Date.now();

		const detectSound = () => {
			analyser.getByteFrequencyData(domainData);

			if (domainData.some((value) => value > 0)) {
				lastSoundTime = Date.now();
			}

			if (isRecording && Date.now() - lastSoundTime > 3000) {
				mediaRecorder.stop();
				audioContext.close();
				return;
			}

			window.requestAnimationFrame(detectSound);
		};

		window.requestAnimationFrame(detectSound);
	};

	const saveRecording = (blob) => {
		const url = URL.createObjectURL(blob);
		const a = document.createElement('a');
		document.body.appendChild(a);
		a.style = 'display: none';
		a.href = url;
		a.download = 'recording.wav';
		a.click();
		window.URL.revokeObjectURL(url);
	};

161
162
163
	const speechRecognitionHandler = () => {
		// Check if SpeechRecognition is supported

Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
166
167
		if (isRecording) {
			if (speechRecognition) {
				speechRecognition.stop();
			}
168

Timothy J. Baek's avatar
Timothy J. Baek committed
169
170
171
172
173
			if (mediaRecorder) {
				mediaRecorder.stop();
			}
		} else {
			isRecording = true;
174

Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
175
			if ($settings?.audio?.STTEngine ?? '' !== '') {
Timothy J. Baek's avatar
Timothy J. Baek committed
176
				startRecording();
177
			} else {
Timothy J. Baek's avatar
Timothy J. Baek committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
				if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
					// Create a SpeechRecognition object
					speechRecognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();

					// Set continuous to true for continuous recognition
					speechRecognition.continuous = true;

					// Set the timeout for turning off the recognition after inactivity (in milliseconds)
					const inactivityTimeout = 3000; // 3 seconds

					let timeoutId;
					// Start recognition
					speechRecognition.start();

					// Event triggered when speech is recognized
					speechRecognition.onresult = async (event) => {
						// Clear the inactivity timeout
						clearTimeout(timeoutId);

						// Handle recognized speech
						console.log(event);
						const transcript = event.results[Object.keys(event.results).length - 1][0].transcript;

						prompt = `${prompt}${transcript}`;

						await tick();
204
						chatTextAreaElement?.focus();
Timothy J. Baek's avatar
Timothy J. Baek committed
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

						// Restart the inactivity timeout
						timeoutId = setTimeout(() => {
							console.log('Speech recognition turned off due to inactivity.');
							speechRecognition.stop();
						}, inactivityTimeout);
					};

					// Event triggered when recognition is ended
					speechRecognition.onend = function () {
						// Restart recognition after it ends
						console.log('recognition ended');
						isRecording = false;
						if (prompt !== '' && $settings?.speechAutoSend === true) {
							submitPrompt(prompt, user);
						}
					};

					// Event triggered when an error occurs
					speechRecognition.onerror = function (event) {
						console.log(event);
Ased Mammad's avatar
Ased Mammad committed
226
						toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
Timothy J. Baek's avatar
Timothy J. Baek committed
227
228
229
						isRecording = false;
					};
				} else {
230
					toast.error($i18n.t('SpeechRecognition API is not supported in this browser.'));
Timothy J. Baek's avatar
Timothy J. Baek committed
231
				}
232
233
234
			}
		}
	};
235

236
237
238
239
240
241
242
243
244
245
246
	const uploadDoc = async (file) => {
		console.log(file);

		const doc = {
			type: 'doc',
			name: file.name,
			collection_name: '',
			upload_status: false,
			error: ''
		};

Dave Bauman's avatar
Dave Bauman committed
247
248
		try {
			files = [...files, doc];
Timothy J. Baek's avatar
Timothy J. Baek committed
249
250
251
252
253
254
255
256
257
258
259
260
261
262

			if (['audio/mpeg', 'audio/wav'].includes(file['type'])) {
				const res = await transcribeAudio(localStorage.token, file).catch((error) => {
					toast.error(error);
					return null;
				});

				if (res) {
					console.log(res);
					const blob = new Blob([res.text], { type: 'text/plain' });
					file = blobToFile(blob, `${file.name}.txt`);
				}
			}

Dave Bauman's avatar
Dave Bauman committed
263
264
265
266
267
268
269
270
271
272
273
			const res = await uploadDocToVectorDB(localStorage.token, '', file);

			if (res) {
				doc.upload_status = true;
				doc.collection_name = res.collection_name;
				files = files;
			}
		} catch (e) {
			// Remove the failed doc from the files array
			files = files.filter((f) => f.name !== file.name);
			toast.error(e);
274
275
276
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
277
278
279
280
281
282
283
284
	const uploadWeb = async (url) => {
		console.log(url);

		const doc = {
			type: 'doc',
			name: url,
			collection_name: '',
			upload_status: false,
Timothy J. Baek's avatar
Timothy J. Baek committed
285
			url: url,
Timothy J. Baek's avatar
Timothy J. Baek committed
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
			error: ''
		};

		try {
			files = [...files, doc];
			const res = await uploadWebToVectorDB(localStorage.token, '', url);

			if (res) {
				doc.upload_status = true;
				doc.collection_name = res.collection_name;
				files = files;
			}
		} catch (e) {
			// Remove the failed doc from the files array
			files = files.filter((f) => f.name !== url);
			toast.error(e);
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
	const uploadYoutubeTranscription = async (url) => {
		console.log(url);

		const doc = {
			type: 'doc',
			name: url,
			collection_name: '',
			upload_status: false,
			url: url,
			error: ''
		};

		try {
			files = [...files, doc];
			const res = await uploadYoutubeTranscriptionToVectorDB(localStorage.token, url);

			if (res) {
				doc.upload_status = true;
				doc.collection_name = res.collection_name;
				files = files;
			}
		} catch (e) {
			// Remove the failed doc from the files array
			files = files.filter((f) => f.name !== url);
			toast.error(e);
		}
	};

333
	onMount(() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
334
		console.log(document.getElementById('sidebar'));
335
		window.setTimeout(() => chatTextAreaElement?.focus(), 0);
336

337
338
		const dropZone = document.querySelector('body');

339
340
341
342
343
344
345
		const handleKeyDown = (event: KeyboardEvent) => {
			if (event.key === 'Escape') {
				console.log('Escape');
				dragged = false;
			}
		};

346
		const onDragOver = (e) => {
347
348
			e.preventDefault();
			dragged = true;
349
350
351
352
353
		};

		const onDragLeave = () => {
			dragged = false;
		};
354

355
		const onDrop = async (e) => {
356
357
358
359
			e.preventDefault();
			console.log(e);

			if (e.dataTransfer?.files) {
Timothy J. Baek's avatar
Timothy J. Baek committed
360
				const inputFiles = Array.from(e.dataTransfer?.files);
Timothy J. Baek's avatar
Timothy J. Baek committed
361
362

				if (inputFiles && inputFiles.length > 0) {
Timothy J. Baek's avatar
Timothy J. Baek committed
363
364
365
					inputFiles.forEach((file) => {
						console.log(file, file.name.split('.').at(-1));
						if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
366
367
368
369
370
371
372
373
374
375
							let reader = new FileReader();
							reader.onload = (event) => {
								files = [
									...files,
									{
										type: 'image',
										url: `${event.target.result}`
									}
								];
							};
Timothy J. Baek's avatar
Timothy J. Baek committed
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
							reader.readAsDataURL(file);
						} else if (
							SUPPORTED_FILE_TYPE.includes(file['type']) ||
							SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
						) {
							uploadDoc(file);
						} else {
							toast.error(
								$i18n.t(
									`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
									{ file_type: file['type'] }
								)
							);
							uploadDoc(file);
						}
					});
392
				} else {
393
					toast.error($i18n.t(`File not found.`));
394
395
396
397
				}
			}

			dragged = false;
398
		};
Timothy J. Baek's avatar
Timothy J. Baek committed
399

400
401
		window.addEventListener('keydown', handleKeyDown);

402
403
404
405
406
		dropZone?.addEventListener('dragover', onDragOver);
		dropZone?.addEventListener('drop', onDrop);
		dropZone?.addEventListener('dragleave', onDragLeave);

		return () => {
407
408
			window.removeEventListener('keydown', handleKeyDown);

409
410
411
412
			dropZone?.removeEventListener('dragover', onDragOver);
			dropZone?.removeEventListener('drop', onDrop);
			dropZone?.removeEventListener('dragleave', onDragLeave);
		};
413
	});
414
415
</script>

416
417
{#if dragged}
	<div
418
419
420
		class="fixed {$showSidebar
			? 'left-0 lg:left-[260px] lg:w-[calc(100%-260px)]'
			: 'left-0'}  w-full h-full flex z-50 touch-none pointer-events-none"
421
422
423
424
		id="dropzone"
		role="region"
		aria-label="Drag and Drop Container"
	>
Timothy J. Baek's avatar
Timothy J. Baek committed
425
		<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
426
			<div class="m-auto pt-64 flex flex-col justify-center">
427
				<div class="max-w-md">
Timothy J. Baek's avatar
Timothy J. Baek committed
428
					<AddFilesPlaceholder />
429
430
431
432
433
434
				</div>
			</div>
		</div>
	</div>
{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
435
436
<div class="fixed bottom-0 {$showSidebar ? 'left-0 lg:left-[260px]' : 'left-0'} right-0">
	<div class="w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
437
		<div class="px-2.5 lg:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
438
			<div class="flex flex-col max-w-5xl w-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
439
440
				<div class="relative">
					{#if autoScroll === false && messages.length > 0}
Timothy J. Baek's avatar
Timothy J. Baek committed
441
						<div class=" absolute -top-12 left-0 right-0 flex justify-center z-30">
Timothy J. Baek's avatar
Timothy J. Baek committed
442
443
444
445
446
447
							<button
								class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
								on:click={() => {
									autoScroll = true;
									scrollToBottom();
								}}
Timothy J. Baek's avatar
Timothy J. Baek committed
448
							>
Timothy J. Baek's avatar
Timothy J. Baek committed
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 20 20"
									fill="currentColor"
									class="w-5 h-5"
								>
									<path
										fill-rule="evenodd"
										d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
										clip-rule="evenodd"
									/>
								</svg>
							</button>
						</div>
					{/if}
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
465

Timothy J. Baek's avatar
Timothy J. Baek committed
466
467
468
469
470
471
472
				<div class="w-full relative">
					{#if prompt.charAt(0) === '/'}
						<Prompts bind:this={promptsElement} bind:prompt />
					{:else if prompt.charAt(0) === '#'}
						<Documents
							bind:this={documentsElement}
							bind:prompt
Timothy J. Baek's avatar
Timothy J. Baek committed
473
474
475
476
							on:youtube={(e) => {
								console.log(e);
								uploadYoutubeTranscription(e.detail);
							}}
Timothy J. Baek's avatar
Timothy J. Baek committed
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
							on:url={(e) => {
								console.log(e);
								uploadWeb(e.detail);
							}}
							on:select={(e) => {
								console.log(e);
								files = [
									...files,
									{
										type: e?.detail?.type ?? 'doc',
										...e.detail,
										upload_status: true
									}
								];
							}}
						/>
					{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
494

Timothy J. Baek's avatar
Timothy J. Baek committed
495
496
497
498
499
500
					<Models
						bind:this={modelsElement}
						bind:prompt
						bind:user
						bind:chatInputPlaceholder
						{messages}
Timothy J. Baek's avatar
Timothy J. Baek committed
501
502
503
504
						on:select={(e) => {
							selectedModel = e.detail;
							chatTextAreaElement?.focus();
						}}
Timothy J. Baek's avatar
Timothy J. Baek committed
505
506
					/>

Timothy J. Baek's avatar
Timothy J. Baek committed
507
508
					{#if selectedModel !== ''}
						<div
Timothy J. Baek's avatar
Timothy J. Baek committed
509
							class="px-3 py-2.5 text-left w-full flex justify-between items-center absolute bottom-0 left-0 right-0 bg-gradient-to-t from-50% from-white dark:from-gray-900"
Timothy J. Baek's avatar
Timothy J. Baek committed
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
						>
							<div class="flex items-center gap-2 text-sm dark:text-gray-500">
								<img
									alt="model profile"
									class="size-5 max-w-[28px] object-cover rounded-full"
									src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
										?.imageUrl ??
										($i18n.language === 'dg-DG'
											? `/doge.png`
											: `${WEBUI_BASE_URL}/static/favicon.png`)}
								/>
								<div>
									Talking to <span class=" font-medium">{selectedModel.name} </span>
								</div>
							</div>
							<div>
								<button
									class="flex items-center"
									on:click={() => {
										selectedModel = '';
									}}
								>
									<XMark />
								</button>
							</div>
						</div>
					{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
537
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
538
			</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
539
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
540

Timothy J. Baek's avatar
Timothy J. Baek committed
541
		<div class="bg-white dark:bg-gray-900">
Timothy J. Baek's avatar
Timothy J. Baek committed
542
			<div class="max-w-6xl px-2.5 lg:px-16 mx-auto inset-x-0">
Timothy J. Baek's avatar
Timothy J. Baek committed
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
				<div class=" pb-2">
					<input
						bind:this={filesInputElement}
						bind:files={inputFiles}
						type="file"
						hidden
						multiple
						on:change={async () => {
							if (inputFiles && inputFiles.length > 0) {
								const _inputFiles = Array.from(inputFiles);
								_inputFiles.forEach((file) => {
									if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
										let reader = new FileReader();
										reader.onload = (event) => {
											files = [
												...files,
												{
													type: 'image',
													url: `${event.target.result}`
												}
											];
											inputFiles = null;
											filesInputElement.value = '';
										};
										reader.readAsDataURL(file);
									} else if (
										SUPPORTED_FILE_TYPE.includes(file['type']) ||
										SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
									) {
										uploadDoc(file);
573
										filesInputElement.value = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
									} else {
										toast.error(
											$i18n.t(
												`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
												{ file_type: file['type'] }
											)
										);
										uploadDoc(file);
										filesInputElement.value = '';
									}
								});
							} else {
								toast.error($i18n.t(`File not found.`));
							}
						}}
					/>
					<form
						class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
						on:submit|preventDefault={() => {
							submitPrompt(prompt, user);
						}}
					>
						{#if files.length > 0}
							<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
								{#each files as file, fileIdx}
									<div class=" relative group">
										{#if file.type === 'image'}
											<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
										{:else if file.type === 'doc'}
											<div
												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
											>
												<div class="p-2.5 bg-red-400 text-white rounded-lg">
													{#if file.upload_status}
														<svg
															xmlns="http://www.w3.org/2000/svg"
															viewBox="0 0 24 24"
															fill="currentColor"
															class="w-6 h-6"
														>
															<path
																fill-rule="evenodd"
																d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
																clip-rule="evenodd"
															/>
															<path
																d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
															/>
														</svg>
													{:else}
														<svg
															class=" w-6 h-6 translate-y-[0.5px]"
															fill="currentColor"
															viewBox="0 0 24 24"
															xmlns="http://www.w3.org/2000/svg"
															><style>
																.spinner_qM83 {
																	animation: spinner_8HQG 1.05s infinite;
																}
																.spinner_oXPr {
																	animation-delay: 0.1s;
																}
																.spinner_ZTLf {
																	animation-delay: 0.2s;
																}
																@keyframes spinner_8HQG {
																	0%,
																	57.14% {
																		animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
																		transform: translate(0);
																	}
																	28.57% {
																		animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
																		transform: translateY(-6px);
																	}
																	100% {
																		transform: translate(0);
																	}
																}
															</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
																class="spinner_qM83 spinner_oXPr"
																cx="12"
																cy="12"
																r="2.5"
															/><circle
																class="spinner_qM83 spinner_ZTLf"
																cx="20"
																cy="12"
																r="2.5"
															/></svg
														>
													{/if}
												</div>

												<div class="flex flex-col justify-center -space-y-0.5">
													<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
														{file.name}
													</div>

													<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
												</div>
											</div>
										{:else if file.type === 'collection'}
											<div
												class="h-16 w-[15rem] flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none"
											>
												<div class="p-2.5 bg-red-400 text-white rounded-lg">
681
682
683
684
685
686
687
													<svg
														xmlns="http://www.w3.org/2000/svg"
														viewBox="0 0 24 24"
														fill="currentColor"
														class="w-6 h-6"
													>
														<path
Timothy J. Baek's avatar
Timothy J. Baek committed
688
															d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
689
690
														/>
														<path
Timothy J. Baek's avatar
Timothy J. Baek committed
691
															d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
692
693
														/>
													</svg>
694
695
												</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
696
697
698
699
700
701
702
												<div class="flex flex-col justify-center -space-y-0.5">
													<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
														{file?.title ?? `#${file.name}`}
													</div>

													<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
												</div>
703
											</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
704
705
706
707
708
709
710
711
712
713
714
										{/if}

										<div class=" absolute -top-1 -right-1">
											<button
												class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
												type="button"
												on:click={() => {
													files.splice(fileIdx, 1);
													files = files;
												}}
											>
715
716
												<svg
													xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
717
													viewBox="0 0 20 20"
718
													fill="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
719
													class="w-4 h-4"
720
721
												>
													<path
Timothy J. Baek's avatar
Timothy J. Baek committed
722
														d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
723
724
													/>
												</svg>
Timothy J. Baek's avatar
Timothy J. Baek committed
725
											</button>
726
										</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
727
728
729
730
									</div>
								{/each}
							</div>
						{/if}
731

Timothy J. Baek's avatar
Timothy J. Baek committed
732
733
734
735
						<div class=" flex">
							{#if fileUploadEnabled}
								<div class=" self-end mb-2 ml-1">
									<Tooltip content={$i18n.t('Upload files')}>
736
										<button
Timothy J. Baek's avatar
Timothy J. Baek committed
737
											class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
738
739
											type="button"
											on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
740
												filesInputElement.click();
741
742
743
744
											}}
										>
											<svg
												xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
745
												viewBox="0 0 16 16"
746
												fill="currentColor"
Timothy J. Baek's avatar
Timothy J. Baek committed
747
												class="w-[1.2rem] h-[1.2rem]"
748
749
											>
												<path
Timothy J. Baek's avatar
Timothy J. Baek committed
750
													d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
751
752
753
												/>
											</svg>
										</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
754
									</Tooltip>
755
								</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
756
							{/if}
757

Timothy J. Baek's avatar
Timothy J. Baek committed
758
759
760
							<textarea
								id="chat-textarea"
								bind:this={chatTextAreaElement}
Timothy J. Baek's avatar
Timothy J. Baek committed
761
								class="scrollbar-none dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
Timothy J. Baek's avatar
Timothy J. Baek committed
762
763
764
765
766
767
768
769
770
									? ''
									: ' pl-4'} rounded-xl resize-none h-[48px]"
								placeholder={chatInputPlaceholder !== ''
									? chatInputPlaceholder
									: isRecording
									? $i18n.t('Listening...')
									: $i18n.t('Send a Message')}
								bind:value={prompt}
								on:keypress={(e) => {
771
									if (
Timothy J. Baek's avatar
fix  
Timothy J. Baek committed
772
										window.innerWidth > 1024 ||
773
774
775
776
777
778
										!(
											'ontouchstart' in window ||
											navigator.maxTouchPoints > 0 ||
											navigator.msMaxTouchPoints > 0
										)
									) {
Timothy J. Baek's avatar
Timothy J. Baek committed
779
780
781
782
783
784
										if (e.keyCode == 13 && !e.shiftKey) {
											e.preventDefault();
										}
										if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
											submitPrompt(prompt, user);
										}
785
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
786
787
788
								}}
								on:keydown={async (e) => {
									const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
789

Timothy J. Baek's avatar
Timothy J. Baek committed
790
791
792
793
									// Check if Ctrl + R is pressed
									if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
										e.preventDefault();
										console.log('regenerate');
794

Timothy J. Baek's avatar
Timothy J. Baek committed
795
796
797
										const regenerateButton = [
											...document.getElementsByClassName('regenerate-response-button')
										]?.at(-1);
798

Timothy J. Baek's avatar
Timothy J. Baek committed
799
800
										regenerateButton?.click();
									}
801

Timothy J. Baek's avatar
Timothy J. Baek committed
802
803
									if (prompt === '' && e.key == 'ArrowUp') {
										e.preventDefault();
804

Timothy J. Baek's avatar
Timothy J. Baek committed
805
806
807
										const userMessageElement = [
											...document.getElementsByClassName('user-message')
										]?.at(-1);
808

Timothy J. Baek's avatar
Timothy J. Baek committed
809
810
811
										const editButton = [
											...document.getElementsByClassName('edit-user-message-button')
										]?.at(-1);
812

Timothy J. Baek's avatar
Timothy J. Baek committed
813
										console.log(userMessageElement);
814

Timothy J. Baek's avatar
Timothy J. Baek committed
815
816
817
										userMessageElement.scrollIntoView({ block: 'center' });
										editButton?.click();
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
818

Timothy J. Baek's avatar
Timothy J. Baek committed
819
820
									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowUp') {
										e.preventDefault();
Timothy J. Baek's avatar
Timothy J. Baek committed
821

Timothy J. Baek's avatar
Timothy J. Baek committed
822
										(promptsElement || documentsElement || modelsElement).selectUp();
Timothy J. Baek's avatar
Timothy J. Baek committed
823

Timothy J. Baek's avatar
Timothy J. Baek committed
824
825
826
827
828
										const commandOptionButton = [
											...document.getElementsByClassName('selected-command-option-button')
										]?.at(-1);
										commandOptionButton.scrollIntoView({ block: 'center' });
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
829

Timothy J. Baek's avatar
Timothy J. Baek committed
830
831
									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowDown') {
										e.preventDefault();
Timothy J. Baek's avatar
Timothy J. Baek committed
832

Timothy J. Baek's avatar
Timothy J. Baek committed
833
										(promptsElement || documentsElement || modelsElement).selectDown();
Timothy J. Baek's avatar
Timothy J. Baek committed
834

Timothy J. Baek's avatar
Timothy J. Baek committed
835
836
837
838
839
										const commandOptionButton = [
											...document.getElementsByClassName('selected-command-option-button')
										]?.at(-1);
										commandOptionButton.scrollIntoView({ block: 'center' });
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
840

Timothy J. Baek's avatar
Timothy J. Baek committed
841
842
									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Enter') {
										e.preventDefault();
Timothy J. Baek's avatar
Timothy J. Baek committed
843

Timothy J. Baek's avatar
Timothy J. Baek committed
844
845
846
										const commandOptionButton = [
											...document.getElementsByClassName('selected-command-option-button')
										]?.at(-1);
Timothy J. Baek's avatar
Timothy J. Baek committed
847

Timothy J. Baek's avatar
Timothy J. Baek committed
848
849
850
851
852
										if (commandOptionButton) {
											commandOptionButton?.click();
										} else {
											document.getElementById('send-message-button')?.click();
										}
853
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
854

Timothy J. Baek's avatar
Timothy J. Baek committed
855
856
									if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
										e.preventDefault();
857

Timothy J. Baek's avatar
Timothy J. Baek committed
858
859
860
										const commandOptionButton = [
											...document.getElementsByClassName('selected-command-option-button')
										]?.at(-1);
861

Timothy J. Baek's avatar
Timothy J. Baek committed
862
863
864
										commandOptionButton?.click();
									} else if (e.key === 'Tab') {
										const words = findWordIndices(prompt);
865

Timothy J. Baek's avatar
Timothy J. Baek committed
866
867
868
										if (words.length > 0) {
											const word = words.at(0);
											const fullPrompt = prompt;
869

Timothy J. Baek's avatar
Timothy J. Baek committed
870
871
											prompt = prompt.substring(0, word?.endIndex + 1);
											await tick();
872

Timothy J. Baek's avatar
Timothy J. Baek committed
873
874
875
											e.target.scrollTop = e.target.scrollHeight;
											prompt = fullPrompt;
											await tick();
876

Timothy J. Baek's avatar
Timothy J. Baek committed
877
878
											e.preventDefault();
											e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
879
										}
880
881
882

										e.target.style.height = '';
										e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
883
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
884
885
886
887
888

									if (e.key === 'Escape') {
										console.log('Escape');
										selectedModel = '';
									}
Timothy J. Baek's avatar
Timothy J. Baek committed
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
								}}
								rows="1"
								on:input={(e) => {
									e.target.style.height = '';
									e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
									user = null;
								}}
								on:focus={(e) => {
									e.target.style.height = '';
									e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
								}}
								on:paste={(e) => {
									const clipboardData = e.clipboardData || window.clipboardData;

									if (clipboardData && clipboardData.items) {
										for (const item of clipboardData.items) {
											if (item.type.indexOf('image') !== -1) {
												const blob = item.getAsFile();
												const reader = new FileReader();

												reader.onload = function (e) {
													files = [
														...files,
														{
															type: 'image',
															url: `${e.target.result}`
915
														}
Timothy J. Baek's avatar
Timothy J. Baek committed
916
917
918
919
920
921
922
923
924
925
926
927
													];
												};

												reader.readAsDataURL(blob);
											}
										}
									}
								}}
							/>

							<div class="self-end mb-2 flex space-x-1 mr-1">
								{#if messages.length == 0 || messages.at(-1).done == true}
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
									{#if webSearchAvailable}
										<Tooltip
											content={useWebSearch
												? $i18n.t('Web Search Enabled')
												: $i18n.t('Web Search Disabled')}
										>
											<button
												id="toggle-websearch-button"
												class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
												type="button"
												on:click={() => {
													useWebSearch = !useWebSearch;
												}}
											>
												{#if useWebSearch}
													<svg
														xmlns="http://www.w3.org/2000/svg"
														viewBox="0 0 24 24"
														fill="currentColor"
														class="w-5 h-5 translate-y-[0.5px]"
													>
														<path
															d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
														/>
													</svg>
												{:else}
													<svg
														xmlns="http://www.w3.org/2000/svg"
														fill="none"
														viewBox="0 0 24 24"
														stroke-width="1.5"
														stroke="currentColor"
														class="w-5 h-5 translate-y-[0.5px]"
													>
														<path
															stroke-linecap="round"
															stroke-linejoin="round"
															d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
														/>
													</svg>
												{/if}
											</button>
										</Tooltip>
									{/if}

Timothy J. Baek's avatar
Timothy J. Baek committed
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
									<Tooltip content={$i18n.t('Record voice')}>
										{#if speechRecognitionEnabled}
											<button
												id="voice-input-button"
												class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
												type="button"
												on:click={() => {
													speechRecognitionHandler();
												}}
											>
												{#if isRecording}
													<svg
														class=" w-5 h-5 translate-y-[0.5px]"
														fill="currentColor"
														viewBox="0 0 24 24"
														xmlns="http://www.w3.org/2000/svg"
														><style>
															.spinner_qM83 {
																animation: spinner_8HQG 1.05s infinite;
Timothy J. Baek's avatar
Timothy J. Baek committed
992
															}
Timothy J. Baek's avatar
Timothy J. Baek committed
993
994
															.spinner_oXPr {
																animation-delay: 0.1s;
Timothy J. Baek's avatar
Timothy J. Baek committed
995
															}
Timothy J. Baek's avatar
Timothy J. Baek committed
996
997
															.spinner_ZTLf {
																animation-delay: 0.2s;
Timothy J. Baek's avatar
Timothy J. Baek committed
998
															}
Timothy J. Baek's avatar
Timothy J. Baek committed
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
															@keyframes spinner_8HQG {
																0%,
																57.14% {
																	animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
																	transform: translate(0);
																}
																28.57% {
																	animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
																	transform: translateY(-6px);
																}
																100% {
																	transform: translate(0);
																}
															}
														</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
															class="spinner_qM83 spinner_oXPr"
															cx="12"
															cy="12"
															r="2.5"
														/><circle
															class="spinner_qM83 spinner_ZTLf"
															cx="20"
															cy="12"
															r="2.5"
														/></svg
													>
												{:else}
													<svg
														xmlns="http://www.w3.org/2000/svg"
														viewBox="0 0 20 20"
														fill="currentColor"
														class="w-5 h-5 translate-y-[0.5px]"
													>
														<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
														<path
															d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
														/>
													</svg>
												{/if}
											</button>
										{/if}
									</Tooltip>
Timothy J. Baek's avatar
Timothy J. Baek committed
1041

Timothy J. Baek's avatar
Timothy J. Baek committed
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
									<Tooltip content={$i18n.t('Send message')}>
										<button
											id="send-message-button"
											class="{prompt !== ''
												? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
												: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
											type="submit"
											disabled={prompt === ''}
										>
											<svg
												xmlns="http://www.w3.org/2000/svg"
												viewBox="0 0 16 16"
												fill="currentColor"
												class="w-5 h-5"
											>
												<path
													fill-rule="evenodd"
													d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
													clip-rule="evenodd"
												/>
											</svg>
										</button>
									</Tooltip>
								{:else}
Timothy J. Baek's avatar
Timothy J. Baek committed
1066
									<button
Timothy J. Baek's avatar
Timothy J. Baek committed
1067
1068
										class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
										on:click={stopResponse}
1069
									>
Timothy J. Baek's avatar
Timothy J. Baek committed
1070
1071
										<svg
											xmlns="http://www.w3.org/2000/svg"
Timothy J. Baek's avatar
Timothy J. Baek committed
1072
											viewBox="0 0 24 24"
Timothy J. Baek's avatar
Timothy J. Baek committed
1073
1074
1075
1076
1077
											fill="currentColor"
											class="w-5 h-5"
										>
											<path
												fill-rule="evenodd"
Timothy J. Baek's avatar
Timothy J. Baek committed
1078
												d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c.725 0 1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z"
Timothy J. Baek's avatar
Timothy J. Baek committed
1079
1080
1081
1082
												clip-rule="evenodd"
											/>
										</svg>
									</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
1083
1084
								{/if}
							</div>
1085
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
1086
					</form>
1087

Timothy J. Baek's avatar
Timothy J. Baek committed
1088
1089
1090
					<div class="mt-1.5 text-xs text-gray-500 text-center">
						{$i18n.t('LLMs can make mistakes. Verify important information.')}
					</div>
1091
1092
1093
1094
1095
				</div>
			</div>
		</div>
	</div>
</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106

<style>
	.scrollbar-none:active::-webkit-scrollbar-thumb,
	.scrollbar-none:focus::-webkit-scrollbar-thumb,
	.scrollbar-none:hover::-webkit-scrollbar-thumb {
		visibility: visible;
	}
	.scrollbar-none::-webkit-scrollbar-thumb {
		visibility: hidden;
	}
</style>