Documents.svelte 5.89 KB
Newer Older
Timothy J. Baek's avatar
Timothy J. Baek committed
1
2
3
4
<script lang="ts">
	import { createEventDispatcher } from 'svelte';

	import { documents } from '$lib/stores';
Timothy J. Baek's avatar
Timothy J. Baek committed
5
	import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
6
	import { tick, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
7
	import { toast } from 'svelte-sonner';
Timothy J. Baek's avatar
Timothy J. Baek committed
8

9
10
	const i18n = getContext('i18n');

Timothy J. Baek's avatar
Timothy J. Baek committed
11
12
13
14
	export let prompt = '';

	const dispatch = createEventDispatcher();
	let selectedIdx = 0;
15
16

	let filteredItems = [];
Timothy J. Baek's avatar
Timothy J. Baek committed
17
	let filteredDocs = [];
18
19
20

	let collections = [];

Timothy J. Baek's avatar
Timothy J. Baek committed
21
	$: collections = [
22
23
24
25
26
		...($documents.length > 0
			? [
					{
						name: 'All Documents',
						type: 'collection',
Jannik Streidl's avatar
Jannik Streidl committed
27
						title: $i18n.t('All Documents'),
28
29
30
31
						collection_names: $documents.map((doc) => doc.collection_name)
					}
			  ]
			: []),
Timothy J. Baek's avatar
Timothy J. Baek committed
32
33
34
35
36
37
38
39
40
41
42
43
		...$documents
			.reduce((a, e, i, arr) => {
				return [...new Set([...a, ...(e?.content?.tags ?? []).map((tag) => tag.name)])];
			}, [])
			.map((tag) => ({
				name: tag,
				type: 'collection',
				collection_names: $documents
					.filter((doc) => (doc?.content?.tags ?? []).map((tag) => tag.name).includes(tag))
					.map((doc) => doc.collection_name)
			}))
	];
44
45

	$: filteredCollections = collections
46
		.filter((collection) => findByName(collection, prompt))
47
		.sort((a, b) => a.name.localeCompare(b.name));
Timothy J. Baek's avatar
Timothy J. Baek committed
48
49

	$: filteredDocs = $documents
50
		.filter((doc) => findByName(doc, prompt))
Timothy J. Baek's avatar
Timothy J. Baek committed
51
52
		.sort((a, b) => a.title.localeCompare(b.title));

53
54
	$: filteredItems = [...filteredCollections, ...filteredDocs];

Timothy J. Baek's avatar
Timothy J. Baek committed
55
56
	$: if (prompt) {
		selectedIdx = 0;
Timothy J. Baek's avatar
Timothy J. Baek committed
57
58

		console.log(filteredCollections);
Timothy J. Baek's avatar
Timothy J. Baek committed
59
60
	}

61
62
63
64
65
66
67
68
69
	type ObjectWithName = {
		name: string;
	};

	const findByName = (obj: ObjectWithName, prompt: string) => {
		const name = obj.name.toLowerCase();
		return name.includes(prompt.toLowerCase().split(' ')?.at(0)?.substring(1) ?? '');
	}

Timothy J. Baek's avatar
Timothy J. Baek committed
70
71
72
73
74
	export const selectUp = () => {
		selectedIdx = Math.max(0, selectedIdx - 1);
	};

	export const selectDown = () => {
75
		selectedIdx = Math.min(selectedIdx + 1, filteredItems.length - 1);
Timothy J. Baek's avatar
Timothy J. Baek committed
76
77
78
79
80
81
82
83
84
85
86
87
	};

	const confirmSelect = async (doc) => {
		dispatch('select', doc);

		prompt = removeFirstHashWord(prompt);
		const chatInputElement = document.getElementById('chat-textarea');

		await tick();
		chatInputElement?.focus();
		await tick();
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
88
89
90
91
92
93
94
95
96
97
98

	const confirmSelectWeb = async (url) => {
		dispatch('url', url);

		prompt = removeFirstHashWord(prompt);
		const chatInputElement = document.getElementById('chat-textarea');

		await tick();
		chatInputElement?.focus();
		await tick();
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
101
102
103
104
105
106
107
108
109

	const confirmSelectYoutube = async (url) => {
		dispatch('youtube', url);

		prompt = removeFirstHashWord(prompt);
		const chatInputElement = document.getElementById('chat-textarea');

		await tick();
		chatInputElement?.focus();
		await tick();
	};
Timothy J. Baek's avatar
Timothy J. Baek committed
110
111
</script>

Timothy J. Baek's avatar
Timothy J. Baek committed
112
{#if filteredItems.length > 0 || prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
Timothy J. Baek's avatar
Timothy J. Baek committed
113
	<div class="pl-1 pr-12 mb-3 text-left w-full absolute bottom-0 left-0 right-0 z-10">
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
114
115
		<div class="flex w-full dark:border dark:border-gray-850 rounded-lg">
			<div class=" bg-gray-50 dark:bg-gray-850 w-10 rounded-l-lg text-center">
Timothy J. Baek's avatar
Timothy J. Baek committed
116
117
118
				<div class=" text-lg font-semibold mt-2">#</div>
			</div>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
119
120
121
122
			<div
				class="max-h-60 flex flex-col w-full rounded-r-xl bg-white dark:bg-gray-900 dark:text-gray-100"
			>
				<div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5 scrollbar-hidden">
123
					{#each filteredItems as doc, docIdx}
Timothy J. Baek's avatar
Timothy J. Baek committed
124
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
125
							class=" px-3 py-1.5 rounded-xl w-full text-left {docIdx === selectedIdx
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
126
								? ' bg-gray-50 dark:bg-gray-850 dark:text-gray-100 selected-command-option-button'
Timothy J. Baek's avatar
Timothy J. Baek committed
127
128
129
								: ''}"
							type="button"
							on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
130
								console.log(doc);
131

Timothy J. Baek's avatar
Timothy J. Baek committed
132
133
134
135
136
137
138
								confirmSelect(doc);
							}}
							on:mousemove={() => {
								selectedIdx = docIdx;
							}}
							on:focus={() => {}}
						>
139
							{#if doc.type === 'collection'}
140
								<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
Timothy J. Baek's avatar
Timothy J. Baek committed
141
									{doc?.title ?? `#${doc.name}`}
142
143
								</div>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
144
145
146
								<div class=" text-xs text-gray-600 dark:text-gray-100 line-clamp-1">
									{$i18n.t('Collection')}
								</div>
147
							{:else}
148
								<div class=" font-medium text-black dark:text-gray-100 line-clamp-1">
149
150
151
									#{doc.name} ({doc.filename})
								</div>

152
								<div class=" text-xs text-gray-600 dark:text-gray-100 line-clamp-1">
153
154
155
									{doc.title}
								</div>
							{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
156
157
						</button>
					{/each}
Timothy J. Baek's avatar
Timothy J. Baek committed
158

159
160
161
162
163
					{#if prompt
						.split(' ')
						.some((s) => s.substring(1).startsWith('https://www.youtube.com') || s
									.substring(1)
									.startsWith('https://youtu.be'))}
Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
						<button
							class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-100 selected-command-option-button"
							type="button"
							on:click={() => {
								const url = prompt.split(' ')?.at(0)?.substring(1);
								if (isValidHttpUrl(url)) {
									confirmSelectYoutube(url);
								} else {
									toast.error(
										$i18n.t(
											'Oops! Looks like the URL is invalid. Please double-check and try again.'
										)
									);
								}
							}}
						>
							<div class=" font-medium text-black line-clamp-1">
								{prompt.split(' ')?.at(0)?.substring(1)}
							</div>

							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Youtube')}</div>
						</button>
					{:else if prompt.split(' ')?.at(0)?.substring(1).startsWith('http')}
Timothy J. Baek's avatar
Timothy J. Baek committed
187
						<button
Timothy J. Baek's avatar
Timothy J. Baek committed
188
							class="px-3 py-1.5 rounded-xl w-full text-left bg-gray-100 selected-command-option-button"
Timothy J. Baek's avatar
Timothy J. Baek committed
189
190
191
192
193
							type="button"
							on:click={() => {
								const url = prompt.split(' ')?.at(0)?.substring(1);
								if (isValidHttpUrl(url)) {
									confirmSelectWeb(url);
194
195
								} else {
									toast.error(
196
197
198
										$i18n.t(
											'Oops! Looks like the URL is invalid. Please double-check and try again.'
										)
199
									);
Timothy J. Baek's avatar
Timothy J. Baek committed
200
201
202
203
204
205
206
								}
							}}
						>
							<div class=" font-medium text-black line-clamp-1">
								{prompt.split(' ')?.at(0)?.substring(1)}
							</div>

207
							<div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
208
209
						</button>
					{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
210
211
212
213
214
				</div>
			</div>
		</div>
	</div>
{/if}