Documents.svelte 22.7 KB
Newer Older
1
2
<script lang="ts">
	import { getDocs } from '$lib/apis/documents';
Timothy J. Baek's avatar
Timothy J. Baek committed
3
	import { deleteAllFiles, deleteFileById } from '$lib/apis/files';
4
	import {
5
		getQuerySettings,
6
		scanDocs,
Timothy J. Baek's avatar
Timothy J. Baek committed
7
		updateQuerySettings,
8
		resetVectorDB,
Timothy J. Baek's avatar
Timothy J. Baek committed
9
		getEmbeddingConfig,
Steven Kreitzer's avatar
Steven Kreitzer committed
10
11
		updateEmbeddingConfig,
		getRerankingConfig,
Timothy J. Baek's avatar
Timothy J. Baek committed
12
		updateRerankingConfig,
13
14
15
		resetUploadDir,
		getRAGConfig,
		updateRAGConfig
16
	} from '$lib/apis/rag';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
17
18
	import ResetUploadDirConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
	import ResetVectorDBConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
Timothy J. Baek's avatar
Timothy J. Baek committed
19

Timothy J. Baek's avatar
Timothy J. Baek committed
20
	import { documents, models } from '$lib/stores';
21
	import { onMount, getContext } from 'svelte';
Jannik Streidl's avatar
Jannik Streidl committed
22
	import { toast } from 'svelte-sonner';
23
	import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
24

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

27
	export let saveHandler: Function;
Timothy J. Baek's avatar
Timothy J. Baek committed
28

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
29
30
	let scanDirLoading = false;
	let updateEmbeddingModelLoading = false;
Steven Kreitzer's avatar
Steven Kreitzer committed
31
	let updateRerankingModelLoading = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
32

Timothy J. Baek's avatar
Timothy J. Baek committed
33
	let showResetConfirm = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
34
	let showResetUploadDirConfirm = false;
Timothy J. Baek's avatar
Timothy J. Baek committed
35

36
	let embeddingEngine = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
37
	let embeddingModel = '';
Steven Kreitzer's avatar
Steven Kreitzer committed
38
	let rerankingModel = '';
Timothy J. Baek's avatar
Timothy J. Baek committed
39

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
40
	let contentExtractionEngine = 'default';
41
42
43
	let tikaServerUrl = '';
	let showTikaServerUrl = false;

44
45
46
47
	let chunkSize = 0;
	let chunkOverlap = 0;
	let pdfExtractImages = true;

48
49
	let OpenAIKey = '';
	let OpenAIUrl = '';
50
	let OpenAIBatchSize = 1;
51

52
53
	let querySettings = {
		template: '',
54
		r: 0.0,
Steven Kreitzer's avatar
Steven Kreitzer committed
55
56
		k: 4,
		hybrid: false
57
	};
58

59
	const scanHandler = async () => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
60
		scanDirLoading = true;
61
		const res = await scanDocs(localStorage.token);
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
62
		scanDirLoading = false;
63
64
65

		if (res) {
			await documents.set(await getDocs(localStorage.token));
66
			toast.success($i18n.t('Scan complete!'));
67
68
69
		}
	};

70
	const embeddingModelUpdateHandler = async () => {
71
72
73
74
75
76
77
78
79
		if (embeddingEngine === '' && embeddingModel.split('/').length - 1 > 1) {
			toast.error(
				$i18n.t(
					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
				)
			);
			return;
		}
		if (embeddingEngine === 'ollama' && embeddingModel === '') {
Timothy J. Baek's avatar
Timothy J. Baek committed
80
81
82
83
84
85
86
87
			toast.error(
				$i18n.t(
					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
				)
			);
			return;
		}

88
		if (embeddingEngine === 'openai' && embeddingModel === '') {
Self Denial's avatar
Self Denial committed
89
90
91
92
93
			toast.error(
				$i18n.t(
					'Model filesystem path detected. Model shortname is required for update, cannot continue.'
				)
			);
94
95
96
			return;
		}

97
		if ((embeddingEngine === 'openai' && OpenAIKey === '') || OpenAIUrl === '') {
98
99
100
101
			toast.error($i18n.t('OpenAI URL/Key required.'));
			return;
		}

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

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
104
		updateEmbeddingModelLoading = true;
Timothy J. Baek's avatar
Timothy J. Baek committed
105
106
		const res = await updateEmbeddingConfig(localStorage.token, {
			embedding_engine: embeddingEngine,
107
108
109
110
			embedding_model: embeddingModel,
			...(embeddingEngine === 'openai'
				? {
						openai_config: {
111
							key: OpenAIKey,
112
113
							url: OpenAIUrl,
							batch_size: OpenAIBatchSize
114
115
116
						}
				  }
				: {})
Timothy J. Baek's avatar
Timothy J. Baek committed
117
		}).catch(async (error) => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
118
			toast.error(error);
119
			await setEmbeddingConfig();
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
120
121
			return null;
		});
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
122
		updateEmbeddingModelLoading = false;
123
124
125

		if (res) {
			console.log('embeddingModelUpdateHandler:', res);
126
			if (res.status === true) {
127
				toast.success($i18n.t('Embedding model set to "{{embedding_model}}"', res), {
Self Denial's avatar
Self Denial committed
128
129
					duration: 1000 * 10
				});
130
131
132
133
			}
		}
	};

Steven Kreitzer's avatar
Steven Kreitzer committed
134
135
136
137
138
	const rerankingModelUpdateHandler = async () => {
		console.log('Update reranking model attempt:', rerankingModel);

		updateRerankingModelLoading = true;
		const res = await updateRerankingConfig(localStorage.token, {
139
			reranking_model: rerankingModel
Steven Kreitzer's avatar
Steven Kreitzer committed
140
141
142
143
144
145
146
147
148
149
		}).catch(async (error) => {
			toast.error(error);
			await setRerankingConfig();
			return null;
		});
		updateRerankingModelLoading = false;

		if (res) {
			console.log('rerankingModelUpdateHandler:', res);
			if (res.status === true) {
Steven Kreitzer's avatar
Steven Kreitzer committed
150
151
152
153
154
155
156
157
158
				if (rerankingModel === '') {
					toast.success($i18n.t('Reranking model disabled', res), {
						duration: 1000 * 10
					});
				} else {
					toast.success($i18n.t('Reranking model set to "{{reranking_model}}"', res), {
						duration: 1000 * 10
					});
				}
Steven Kreitzer's avatar
Steven Kreitzer committed
159
160
161
162
			}
		}
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
163
	const submitHandler = async () => {
Timothy J. Baek's avatar
Timothy J. Baek committed
164
165
166
167
168
		embeddingModelUpdateHandler();

		if (querySettings.hybrid) {
			rerankingModelUpdateHandler();
		}
169

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
170
		if (contentExtractionEngine === 'tika' && tikaServerUrl === '') {
171
172
173
174
			toast.error($i18n.t('Tika Server URL required.'));
			return;
		}

175
176
177
178
179
		const res = await updateRAGConfig(localStorage.token, {
			pdf_extract_images: pdfExtractImages,
			chunk: {
				chunk_overlap: chunkOverlap,
				chunk_size: chunkSize
180
			},
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
181
182
			content_extraction: {
				engine: contentExtractionEngine,
183
				tika_server_url: tikaServerUrl
184
185
			}
		});
Timothy J. Baek's avatar
Timothy J. Baek committed
186
187

		await updateQuerySettings(localStorage.token, querySettings);
Timothy J. Baek's avatar
Timothy J. Baek committed
188
189
	};

190
191
192
193
194
195
196
	const setEmbeddingConfig = async () => {
		const embeddingConfig = await getEmbeddingConfig(localStorage.token);

		if (embeddingConfig) {
			embeddingEngine = embeddingConfig.embedding_engine;
			embeddingModel = embeddingConfig.embedding_model;

197
198
			OpenAIKey = embeddingConfig.openai_config.key;
			OpenAIUrl = embeddingConfig.openai_config.url;
199
			OpenAIBatchSize = embeddingConfig.openai_config.batch_size ?? 1;
200
201
202
		}
	};

Steven Kreitzer's avatar
Steven Kreitzer committed
203
204
205
206
207
208
209
210
	const setRerankingConfig = async () => {
		const rerankingConfig = await getRerankingConfig(localStorage.token);

		if (rerankingConfig) {
			rerankingModel = rerankingConfig.reranking_model;
		}
	};

Steven Kreitzer's avatar
Steven Kreitzer committed
211
212
213
214
215
	const toggleHybridSearch = async () => {
		querySettings.hybrid = !querySettings.hybrid;
		querySettings = await updateQuerySettings(localStorage.token, querySettings);
	};

Timothy J. Baek's avatar
Timothy J. Baek committed
216
	onMount(async () => {
217
		await setEmbeddingConfig();
Steven Kreitzer's avatar
Steven Kreitzer committed
218
		await setRerankingConfig();
219

220
		querySettings = await getQuerySettings(localStorage.token);
221
222
223
224
225
226
227
228

		const res = await getRAGConfig(localStorage.token);

		if (res) {
			pdfExtractImages = res.pdf_extract_images;

			chunkSize = res.chunk.chunk_size;
			chunkOverlap = res.chunk.chunk_overlap;
229

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
230
231
232
			contentExtractionEngine = res.content_extraction.engine;
			tikaServerUrl = res.content_extraction.tika_server_url;
			showTikaServerUrl = contentExtractionEngine === 'tika';
233
		}
Timothy J. Baek's avatar
Timothy J. Baek committed
234
	});
235
236
</script>

Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
237
238
<ResetUploadDirConfirmDialog
	bind:show={showResetUploadDirConfirm}
Timothy J. Baek's avatar
Timothy J. Baek committed
239
240
	on:confirm={async () => {
		const res = await deleteAllFiles(localStorage.token).catch((error) => {
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
			toast.error(error);
			return null;
		});

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

<ResetVectorDBConfirmDialog
	bind:show={showResetConfirm}
	on:confirm={() => {
		const res = resetVectorDB(localStorage.token).catch((error) => {
			toast.error(error);
			return null;
		});

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

265
266
267
<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
268
		submitHandler();
269
270
271
		saveHandler();
	}}
>
Timothy J. Baek's avatar
Timothy J. Baek committed
272
	<div class=" space-y-2.5 overflow-y-scroll scrollbar-hidden h-full">
Timothy J. Baek's avatar
Timothy J. Baek committed
273
274
		<div class="flex flex-col gap-0.5">
			<div class=" mb-0.5 text-sm font-medium">{$i18n.t('General Settings')}</div>
275

Timothy J. Baek's avatar
Timothy J. Baek committed
276
277
			<div class="  flex w-full justify-between">
				<div class=" self-center text-xs font-medium">
Timothy J. Baek's avatar
Timothy J. Baek committed
278
					{$i18n.t('Scan for documents from {{path}}', { path: 'DOCS_DIR (/data/docs)' })}
Timothy J. Baek's avatar
Timothy J. Baek committed
279
				</div>
Steven Kreitzer's avatar
Steven Kreitzer committed
280
281

				<button
Timothy J. Baek's avatar
Timothy J. Baek committed
282
283
284
					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'
						: ''}"
Steven Kreitzer's avatar
Steven Kreitzer committed
285
					on:click={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
286
287
						scanHandler();
						console.log('check');
Steven Kreitzer's avatar
Steven Kreitzer committed
288
289
					}}
					type="button"
Timothy J. Baek's avatar
Timothy J. Baek committed
290
					disabled={scanDirLoading}
Steven Kreitzer's avatar
Steven Kreitzer committed
291
				>
Timothy J. Baek's avatar
Timothy J. Baek committed
292
293
294
295
296
297
298
299
300
					<div class="self-center font-medium">{$i18n.t('Scan')}</div>

					{#if scanDirLoading}
						<div class="ml-3 self-center">
							<svg
								class=" w-3 h-3"
								viewBox="0 0 24 24"
								fill="currentColor"
								xmlns="http://www.w3.org/2000/svg"
301
302
							>
								<style>
Timothy J. Baek's avatar
Timothy J. Baek committed
303
304
305
306
									.spinner_ajPY {
										transform-origin: center;
										animation: spinner_AtaB 0.75s infinite linear;
									}
307

Timothy J. Baek's avatar
Timothy J. Baek committed
308
309
310
311
312
									@keyframes spinner_AtaB {
										100% {
											transform: rotate(360deg);
										}
									}
313
314
								</style>
								<path
Timothy J. Baek's avatar
Timothy J. Baek committed
315
316
									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"
317
318
								/>
								<path
Timothy J. Baek's avatar
Timothy J. Baek committed
319
320
									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"
321
322
								/>
							</svg>
Timothy J. Baek's avatar
Timothy J. Baek committed
323
						</div>
Steven Kreitzer's avatar
Steven Kreitzer committed
324
325
326
327
					{/if}
				</button>
			</div>

328
			<div class=" flex w-full justify-between">
Timothy J. Baek's avatar
Timothy J. Baek committed
329
				<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Model Engine')}</div>
330
331
332
333
				<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}
Timothy J. Baek's avatar
Timothy J. Baek committed
334
						placeholder="Select an embedding model engine"
335
336
337
338
339
						on:change={(e) => {
							if (e.target.value === 'ollama') {
								embeddingModel = '';
							} else if (e.target.value === 'openai') {
								embeddingModel = 'text-embedding-3-small';
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
340
341
							} else if (e.target.value === '') {
								embeddingModel = 'sentence-transformers/all-MiniLM-L6-v2';
342
							}
Timothy J. Baek's avatar
Timothy J. Baek committed
343
						}}
344
					>
345
						<option value="">{$i18n.t('Default (SentenceTransformers)')}</option>
346
						<option value="ollama">{$i18n.t('Ollama')}</option>
347
						<option value="openai">{$i18n.t('OpenAI')}</option>
348
					</select>
349
				</div>
350
			</div>
351
352

			{#if embeddingEngine === 'openai'}
Timothy J. Baek's avatar
Timothy J. Baek committed
353
				<div class="my-0.5 flex gap-2">
354
					<input
355
						class="flex-1 w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
356
						placeholder={$i18n.t('API Base URL')}
357
						bind:value={OpenAIUrl}
358
359
360
						required
					/>

361
					<SensitiveInput placeholder={$i18n.t('API Key')} bind:value={OpenAIKey} />
362
				</div>
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
				<div class="flex mt-0.5 space-x-2">
					<div class=" self-center text-xs font-medium">{$i18n.t('Embedding Batch Size')}</div>
					<div class=" flex-1">
						<input
							id="steps-range"
							type="range"
							min="1"
							max="2048"
							step="1"
							bind:value={OpenAIBatchSize}
							class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
						/>
					</div>
					<div class="">
						<input
							bind:value={OpenAIBatchSize}
							type="number"
							class=" bg-transparent text-center w-14"
							min="-2"
							max="16000"
							step="1"
						/>
					</div>
				</div>
387
			{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405

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

				<button
					class="p-1 px-3 text-xs flex rounded transition"
					on:click={() => {
						toggleHybridSearch();
					}}
					type="button"
				>
					{#if querySettings.hybrid === true}
						<span class="ml-2 self-center">{$i18n.t('On')}</span>
					{:else}
						<span class="ml-2 self-center">{$i18n.t('Off')}</span>
					{/if}
				</button>
			</div>
406
		</div>
407

408
409
410
411
412
413
414
415
416
		<hr class="dark:border-gray-850" />

		<div class="">
			<div class="text-sm font-medium">{$i18n.t('Text Extraction')}</div>

			<div class="flex w-full justify-between mt-2">
				<div class="self-center text-xs font-medium">{$i18n.t('Engine')}</div>
				<div class="flex items-center relative">
					<select
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
417
418
419
420
421
						class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
						bind:value={contentExtractionEngine}
						on:change={(e) => {
							showTikaServerUrl = e.target.value === 'tika';
						}}
422
423
424
425
426
427
428
429
430
431
432
					>
						<option value="default">{$i18n.t('Default')}</option>
						<option value="tika">{$i18n.t('Tika')}</option>
					</select>
				</div>
			</div>

			{#if showTikaServerUrl}
				<div class="flex w-full mt-2">
					<div class="flex-1 mr-2">
						<input
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
433
434
435
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
							placeholder={$i18n.t('Enter Tika Server URL')}
							bind:value={tikaServerUrl}
436
437
438
439
440
						/>
					</div>
				</div>
			{/if}
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
441
		<hr class=" dark:border-gray-850 my-1" />
Timothy J. Baek's avatar
Timothy J. Baek committed
442

Timothy J. Baek's avatar
Timothy J. Baek committed
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
		<div class="space-y-2" />
		<div>
			<div class=" mb-2 text-sm font-medium">{$i18n.t('Embedding Model')}</div>

			{#if embeddingEngine === 'ollama'}
				<div class="flex w-full">
					<div class="flex-1 mr-2">
						<select
							class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
							bind:value={embeddingModel}
							placeholder={$i18n.t('Select a model')}
							required
						>
							{#if !embeddingModel}
								<option value="" disabled selected>{$i18n.t('Select a model')}</option>
							{/if}
Timothy J. Baek's avatar
Timothy J. Baek committed
459
460
							{#each $models.filter((m) => m.id && m.ollama && !(m?.preset ?? false)) as model}
								<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
Timothy J. Baek's avatar
Timothy J. Baek committed
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
							{/each}
						</select>
					</div>
				</div>
			{:else}
				<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('Set embedding model (e.g. {{model}})', {
								model: embeddingModel.slice(-40)
							})}
							bind:value={embeddingModel}
						/>
					</div>

					{#if embeddingEngine === ''}
Timothy J. Baek's avatar
Timothy J. Baek committed
478
479
480
481
482
483
484
485
486
487
488
489
490
491
						<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"
492
493
									>
										<style>
Timothy J. Baek's avatar
Timothy J. Baek committed
494
495
496
497
											.spinner_ajPY {
												transform-origin: center;
												animation: spinner_AtaB 0.75s infinite linear;
											}
498

Timothy J. Baek's avatar
Timothy J. Baek committed
499
500
501
502
503
											@keyframes spinner_AtaB {
												100% {
													transform: rotate(360deg);
												}
											}
504
505
										</style>
										<path
Timothy J. Baek's avatar
Timothy J. Baek committed
506
507
											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"
508
509
										/>
										<path
Timothy J. Baek's avatar
Timothy J. Baek committed
510
511
											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"
512
513
										/>
									</svg>
Timothy J. Baek's avatar
Timothy J. Baek committed
514
515
516
517
518
519
520
521
522
								</div>
							{:else}
								<svg
									xmlns="http://www.w3.org/2000/svg"
									viewBox="0 0 16 16"
									fill="currentColor"
									class="w-4 h-4"
								>
									<path
Timothy J. Baek's avatar
Timothy J. Baek committed
523
524
525
526
										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"
Timothy J. Baek's avatar
Timothy J. Baek committed
527
528
529
530
									/>
								</svg>
							{/if}
						</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
531
532
533
534
535
536
537
538
539
540
541
542
543
544
					{/if}
				</div>
			{/if}

			<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 querySettings.hybrid === true}
				<div class=" ">
					<div class=" mb-2 text-sm font-medium">{$i18n.t('Reranking Model')}</div>

545
546
547
548
					<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"
Timothy J. Baek's avatar
Timothy J. Baek committed
549
550
								placeholder={$i18n.t('Set reranking model (e.g. {{model}})', {
									model: 'BAAI/bge-reranker-v2-m3'
551
								})}
Timothy J. Baek's avatar
Timothy J. Baek committed
552
								bind:value={rerankingModel}
553
554
555
556
557
							/>
						</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={() => {
Timothy J. Baek's avatar
Timothy J. Baek committed
558
								rerankingModelUpdateHandler();
559
							}}
Timothy J. Baek's avatar
Timothy J. Baek committed
560
							disabled={updateRerankingModelLoading}
561
						>
Timothy J. Baek's avatar
Timothy J. Baek committed
562
							{#if updateRerankingModelLoading}
563
564
565
566
567
568
								<div class="self-center">
									<svg
										class=" w-4 h-4"
										viewBox="0 0 24 24"
										fill="currentColor"
										xmlns="http://www.w3.org/2000/svg"
569
570
									>
										<style>
571
572
573
574
											.spinner_ajPY {
												transform-origin: center;
												animation: spinner_AtaB 0.75s infinite linear;
											}
575

576
577
578
579
580
											@keyframes spinner_AtaB {
												100% {
													transform: rotate(360deg);
												}
											}
581
582
										</style>
										<path
583
584
											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"
585
586
										/>
										<path
587
588
											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"
589
590
										/>
									</svg>
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
								</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>
608
					</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
609
				</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
610
611
			{/if}
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
612

Timothy J. Baek's avatar
Timothy J. Baek committed
613
		<hr class=" dark:border-gray-850" />
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
		<div class=" ">
			<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 min-w-fit">{$i18n.t('Top K')}</div>

					<div class="self-center p-3">
						<input
							class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
							type="number"
							placeholder={$i18n.t('Enter Top K')}
							bind:value={querySettings.k}
							autocomplete="off"
							min="0"
						/>
					</div>
				</div>

				{#if querySettings.hybrid === true}
					<div class="flex w-full">
						<div class=" self-center text-xs font-medium min-w-fit">
							{$i18n.t('Minimum Score')}
						</div>

						<div class="self-center p-3">
							<input
								class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
								type="number"
								step="0.01"
								placeholder={$i18n.t('Enter Score')}
								bind:value={querySettings.r}
								autocomplete="off"
								min="0.0"
								title={$i18n.t('The score should be a value between 0.0 (0%) and 1.0 (100%).')}
							/>
						</div>
					</div>
				{/if}
			</div>

			{#if querySettings.hybrid === true}
				<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
					{$i18n.t(
						'Note: If you set a minimum score, the search will only return documents with a score greater than or equal to the minimum score.'
					)}
				</div>

				<hr class=" dark:border-gray-850 my-3" />
			{/if}

			<div>
				<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
				<textarea
					bind:value={querySettings.template}
					class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
					rows="4"
				/>
			</div>
		</div>

		<hr class=" dark:border-gray-850" />

		<div class=" ">
			<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
681
682
683
684
			<div class=" my-2 flex gap-1.5">
				<div class="  w-full justify-between">
					<div class="self-center text-xs font-medium min-w-fit mb-1">{$i18n.t('Chunk Size')}</div>
					<div class="self-center">
685
686
687
688
689
690
691
692
693
694
695
						<input
							class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
							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
696
697
				<div class="w-full">
					<div class=" self-center text-xs font-medium min-w-fit mb-1">
698
699
700
						{$i18n.t('Chunk Overlap')}
					</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
701
					<div class="self-center">
702
703
704
705
706
707
708
709
710
711
712
713
						<input
							class="w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
							type="number"
							placeholder={$i18n.t('Enter Chunk Overlap')}
							bind:value={chunkOverlap}
							autocomplete="off"
							min="0"
						/>
					</div>
				</div>
			</div>

Timothy J. Baek's avatar
Timothy J. Baek committed
714
			<div class="my-3">
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
				<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>
			</div>
		</div>

		<hr class=" dark:border-gray-850" />

Timothy J. Baek's avatar
Timothy J. Baek committed
731
		<div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
			<button
				class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
				on:click={() => {
					showResetUploadDirConfirm = true;
				}}
				type="button"
			>
				<div class=" self-center mr-3">
					<svg
						xmlns="http://www.w3.org/2000/svg"
						viewBox="0 0 24 24"
						fill="currentColor"
						class="size-4"
					>
						<path
							fill-rule="evenodd"
							d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875ZM9.75 14.25a.75.75 0 0 0 0 1.5H15a.75.75 0 0 0 0-1.5H9.75Z"
							clip-rule="evenodd"
						/>
						<path
							d="M14.25 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 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
						/>
					</svg>
Timothy J. Baek's avatar
Timothy J. Baek committed
755
				</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
				<div class=" self-center text-sm font-medium">{$i18n.t('Reset Upload Directory')}</div>
			</button>

			<button
				class=" flex rounded-xl py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
				on:click={() => {
					showResetConfirm = true;
				}}
				type="button"
			>
				<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>
Timothy J. Baek's avatar
Timothy J. Baek committed
779
				</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
780
781
				<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
			</button>
Timothy J. Baek's avatar
Timothy J. Baek committed
782
		</div>
Timothy J. Baek's avatar
Timothy J. Baek committed
783
	</div>
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
784
785
	<div class="flex justify-end pt-3 text-sm font-medium">
		<button
Timothy J. Baek's avatar
Timothy J. Baek committed
786
			class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
Timothy J. Baek's avatar
refac  
Timothy J. Baek committed
787
788
789
790
791
			type="submit"
		>
			{$i18n.t('Save')}
		</button>
	</div>
792
</form>