General.svelte 13.3 KB
Newer Older
1
2
<script lang="ts">
	import { getDocs } from '$lib/apis/documents';
3
	import {
Timothy J. Baek's avatar
Timothy J. Baek committed
4
5
		getRAGConfig,
		updateRAGConfig,
6
		getQuerySettings,
7
		scanDocs,
Timothy J. Baek's avatar
Timothy J. Baek committed
8
		updateQuerySettings,
9
10
11
		resetVectorDB,
		getEmbeddingModel,
		updateEmbeddingModel
12
	} from '$lib/apis/rag';
Timothy J. Baek's avatar
Timothy J. Baek committed
13

14
	import { documents } from '$lib/stores';
15
	import { onMount, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
16
	import { toast } from 'svelte-sonner';
17

18
19
	import Tooltip from '$lib/components/common/Tooltip.svelte';

20
21
	const i18n = getContext('i18n');

22
	export let saveHandler: Function;
Timothy J. Baek's avatar
Timothy J. Baek committed
23

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
24
25
	let scanDirLoading = false;
	let updateEmbeddingModelLoading = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
26

Timothy J. Baek's avatar
Timothy J. Baek committed
27
28
	let showResetConfirm = false;

29
	let embeddingEngine = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
30
31
	let chunkSize = 0;
	let chunkOverlap = 0;
Timothy J. Baek's avatar
Timothy J. Baek committed
32
	let pdfExtractImages = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
33

34
35
36
37
	let querySettings = {
		template: '',
		k: 4
	};
38

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
39
	let embeddingModel = '';
40

41
	const scanHandler = async () => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
42
		scanDirLoading = true;
43
		const res = await scanDocs(localStorage.token);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
44
		scanDirLoading = false;
45
46
47

		if (res) {
			await documents.set(await getDocs(localStorage.token));
48
			toast.success($i18n.t('Scan complete!'));
49
50
51
		}
	};

52
	const embeddingModelUpdateHandler = async () => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
53
		if (embeddingModel.split('/').length - 1 > 1) {
Self Denial's avatar
Self Denial committed
54
55
56
57
58
			toast.error(
				$i18n.t(
					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
				)
			);
59
60
61
			return;
		}

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
62
		console.log('Update embedding model attempt:', embeddingModel);
63

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
64
		updateEmbeddingModelLoading = true;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
65
66
		const res = await updateEmbeddingModel(localStorage.token, {
			embedding_model: embeddingModel
Timothy J. Baek's avatar
Timothy J. Baek committed
67
		}).catch(async (error) => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
68
			toast.error(error);
Timothy J. Baek's avatar
Timothy J. Baek committed
69
			embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
70
71
			return null;
		});
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
72
		updateEmbeddingModelLoading = false;
73
74
75

		if (res) {
			console.log('embeddingModelUpdateHandler:', res);
76
			if (res.status === true) {
Self Denial's avatar
Self Denial committed
77
78
79
				toast.success($i18n.t('Model {{embedding_model}} update complete!', res), {
					duration: 1000 * 10
				});
80
81
82
83
			}
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
84
	const submitHandler = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
85
86
87
88
89
90
91
		const res = await updateRAGConfig(localStorage.token, {
			pdf_extract_images: pdfExtractImages,
			chunk: {
				chunk_overlap: chunkOverlap,
				chunk_size: chunkSize
			}
		});
92
		querySettings = await updateQuerySettings(localStorage.token, querySettings);
Timothy J. Baek's avatar
Timothy J. Baek committed
93
94
95
	};

	onMount(async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
96
		const res = await getRAGConfig(localStorage.token);
Timothy J. Baek's avatar
Timothy J. Baek committed
97
98

		if (res) {
Timothy J. Baek's avatar
Timothy J. Baek committed
99
100
101
102
			pdfExtractImages = res.pdf_extract_images;

			chunkSize = res.chunk.chunk_size;
			chunkOverlap = res.chunk.chunk_overlap;
Timothy J. Baek's avatar
Timothy J. Baek committed
103
		}
104

Timothy J. Baek's avatar
Timothy J. Baek committed
105
		embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
106

107
		querySettings = await getQuerySettings(localStorage.token);
Timothy J. Baek's avatar
Timothy J. Baek committed
108
	});
109
110
111
112
113
</script>

<form
	class="flex flex-col h-full justify-between space-y-3 text-sm"
	on:submit|preventDefault={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
114
		submitHandler();
115
116
117
		saveHandler();
	}}
>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
118
	<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
119
		<div>
120
			<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
121

122
123
124
125
126
127
128
129
130
131
132
			<div class=" flex w-full justify-between">
				<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Engine')}</div>
				<div class="flex items-center relative">
					<select
						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
						bind:value={embeddingEngine}
						placeholder="Select an embedding engine"
					>
						<option value="">{$i18n.t('Default (SentenceTransformer)')}</option>
						<option value="ollama">{$i18n.t('Ollama')}</option>
					</select>
133
				</div>
134
			</div>
135
		</div>
136

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
137
138
		<div class="space-y-2">
			<div>
139
140
141
142
143
144
145
146
147
148
149
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
196
197
198
199
200
201
				{#if embeddingEngine === 'ollama'}
					<div>da</div>
				{:else}
					<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
					<div class="flex w-full">
						<div class="flex-1 mr-2">
							<input
								class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
								placeholder={$i18n.t('Update embedding model (e.g. {{model}})', {
									model: embeddingModel.slice(-40)
								})}
								bind:value={embeddingModel}
							/>
						</div>
						<button
							class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
							on:click={() => {
								embeddingModelUpdateHandler();
							}}
							disabled={updateEmbeddingModelLoading}
						>
							{#if updateEmbeddingModelLoading}
								<div class="self-center">
									<svg
										class=" w-4 h-4"
										viewBox="0 0 24 24"
										fill="currentColor"
										xmlns="http://www.w3.org/2000/svg"
										><style>
											.spinner_ajPY {
												transform-origin: center;
												animation: spinner_AtaB 0.75s infinite linear;
											}
											@keyframes spinner_AtaB {
												100% {
													transform: rotate(360deg);
												}
											}
										</style><path
											d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
											opacity=".25"
										/><path
											d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
											class="spinner_ajPY"
										/></svg
									>
								</div>
							{:else}
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 16 16"
									fill="currentColor"
									class="w-4 h-4"
								>
									<path
										d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
									/>
									<path
										d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
									/>
								</svg>
							{/if}
						</button>
202
					</div>
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217

					<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
						{$i18n.t(
							'Warning: If you update or change your embedding model, you will need to re-import all documents.'
						)}
					</div>
				{/if}

				<hr class=" dark:border-gray-700 my-3" />

				<div class="  flex w-full justify-between">
					<div class=" self-center text-xs font-medium">
						{$i18n.t('Scan for documents from {{path}}', { path: '/data/docs' })}
					</div>

218
					<button
219
220
221
						class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded-lg flex flex-row space-x-1 items-center {scanDirLoading
							? ' cursor-not-allowed'
							: ''}"
222
						on:click={() => {
223
224
							scanHandler();
							console.log('check');
225
						}}
226
227
						type="button"
						disabled={scanDirLoading}
228
					>
229
230
231
232
						<div class="self-center font-medium">{$i18n.t('Scan')}</div>

						{#if scanDirLoading}
							<div class="ml-3 self-center">
233
								<svg
234
									class=" w-3 h-3"
235
236
237
238
239
240
241
									viewBox="0 0 24 24"
									fill="currentColor"
									xmlns="http://www.w3.org/2000/svg"
									><style>
										.spinner_ajPY {
											transform-origin: center;
											animation: spinner_AtaB 0.75s infinite linear;
242
										}
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
										@keyframes spinner_AtaB {
											100% {
												transform: rotate(360deg);
											}
										}
									</style><path
										d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
										opacity=".25"
									/><path
										d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
										class="spinner_ajPY"
									/></svg
								>
							</div>
						{/if}
					</button>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
259
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
260

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
261
				<hr class=" dark:border-gray-700 my-3" />
Timothy J. Baek's avatar
Timothy J. Baek committed
262

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
263
264
				<div class=" ">
					<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
265

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
266
267
268
					<div class=" flex">
						<div class="  flex w-full justify-between">
							<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
269

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
270
271
							<div class="self-center p-3">
								<input
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
272
									class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
273
274
275
276
277
278
279
280
									type="number"
									placeholder={$i18n.t('Enter Chunk Size')}
									bind:value={chunkSize}
									autocomplete="off"
									min="0"
								/>
							</div>
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
281

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
282
283
284
285
						<div class="flex w-full">
							<div class=" self-center text-xs font-medium min-w-fit">
								{$i18n.t('Chunk Overlap')}
							</div>
286

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
287
288
							<div class="self-center p-3">
								<input
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
289
									class="w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
290
291
292
293
294
295
296
297
298
									type="number"
									placeholder={$i18n.t('Enter Chunk Overlap')}
									bind:value={chunkOverlap}
									autocomplete="off"
									min="0"
								/>
							</div>
						</div>
					</div>
299

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
300
					<div class="pr-2">
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
301
302
303
304
305
306
307
308
309
310
311
						<div class="flex justify-between items-center text-xs">
							<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>

							<button
								class=" text-xs font-medium text-gray-500"
								type="button"
								on:click={() => {
									pdfExtractImages = !pdfExtractImages;
								}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
							>
						</div>
312
313
314
					</div>
				</div>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
315
316
				<hr class=" dark:border-gray-700 my-3" />

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
317
318
319
320
321
322
323
324
325
				<div>
					<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>

					<div class=" flex">
						<div class="  flex w-full justify-between">
							<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>

							<div class="self-center p-3">
								<input
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
326
									class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
327
328
329
330
331
332
333
334
335
									type="number"
									placeholder={$i18n.t('Enter Top K')}
									bind:value={querySettings.k}
									autocomplete="off"
									min="0"
								/>
							</div>
						</div>
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
336

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
337
338
339
340
					<div>
						<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
						<textarea
							bind:value={querySettings.template}
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
341
							class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
342
							rows="4"
Timothy J. Baek's avatar
Timothy J. Baek committed
343
						/>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
344
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
345
346
				</div>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
347
				<hr class=" dark:border-gray-700 my-3" />
Timothy J. Baek's avatar
Timothy J. Baek committed
348

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
				{#if showResetConfirm}
					<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
						<div class="flex items-center space-x-3">
							<svg
								xmlns="http://www.w3.org/2000/svg"
								viewBox="0 0 16 16"
								fill="currentColor"
								class="w-4 h-4"
							>
								<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
								<path
									fill-rule="evenodd"
									d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
									clip-rule="evenodd"
								/>
							</svg>
							<span>{$i18n.t('Are you sure?')}</span>
						</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
367

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
						<div class="flex space-x-1.5 items-center">
							<button
								class="hover:text-white transition"
								on:click={() => {
									const res = resetVectorDB(localStorage.token).catch((error) => {
										toast.error(error);
										return null;
									});

									if (res) {
										toast.success($i18n.t('Success'));
									}

									showResetConfirm = false;
								}}
							>
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 20 20"
									fill="currentColor"
									class="w-4 h-4"
								>
									<path
										fill-rule="evenodd"
										d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
										clip-rule="evenodd"
									/>
								</svg>
							</button>
							<button
								class="hover:text-white transition"
								on:click={() => {
									showResetConfirm = false;
								}}
							>
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 20 20"
									fill="currentColor"
									class="w-4 h-4"
								>
									<path
										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"
									/>
								</svg>
							</button>
						</div>
					</div>
				{:else}
Timothy J. Baek's avatar
Timothy J. Baek committed
417
					<button
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
418
						class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
Timothy J. Baek's avatar
Timothy J. Baek committed
419
						on:click={() => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
420
							showResetConfirm = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
421
422
						}}
					>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
						<div class=" self-center mr-3">
							<svg
								xmlns="http://www.w3.org/2000/svg"
								viewBox="0 0 16 16"
								fill="currentColor"
								class="w-4 h-4"
							>
								<path
									fill-rule="evenodd"
									d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
									clip-rule="evenodd"
								/>
							</svg>
						</div>
						<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
438
					</button>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
439
				{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
440
			</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
441
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
442
	</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
443
444
445
446
447
448
449
450
	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
			class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
			type="submit"
		>
			{$i18n.t('Save')}
		</button>
	</div>
451
</form>