|
4 | 4 | import Dialog from "$lib/ui/dialog.svelte" |
5 | 5 | import TrashIcon from "$lib/ui/icons/trash_icon.svelte" |
6 | 6 | import UploadIcon from "$lib/ui/icons/upload_icon.svelte" |
| 7 | + import TagDropdown from "$lib/ui/tag_dropdown.svelte" |
7 | 8 | import { ragProgressStore } from "$lib/stores/rag_progress_store" |
| 9 | + import { load_document_tags } from "$lib/stores/document_tag_store" |
8 | 10 | import type { BulkCreateDocumentsResponse } from "$lib/types" |
9 | 11 | import posthog from "posthog-js" |
| 12 | + import { createKilnError, KilnError } from "$lib/utils/error_handlers" |
| 13 | + import FormElement from "$lib/utils/form_element.svelte" |
10 | 14 |
|
11 | 15 | export let onUploadCompleted: () => void |
12 | 16 |
|
| 17 | + let upload_error: KilnError | null = null |
13 | 18 | let selected_files: File[] = [] |
14 | 19 | let file_input: HTMLInputElement |
15 | 20 | let drag_over = false |
|
97 | 102 | let unsupported_files_count = 0 |
98 | 103 | let show_success_dialog = false |
99 | 104 |
|
| 105 | + // tags |
| 106 | + let selected_tags: Set<string> = new Set() |
| 107 | + let current_tag = "" |
| 108 | +
|
100 | 109 | async function handleUpload(): Promise<boolean> { |
| 110 | + upload_error = null |
101 | 111 | upload_in_progress = true |
102 | 112 | upload_progress = 0 |
103 | 113 | upload_total = selected_files.length |
|
112 | 122 | return false |
113 | 123 | } |
114 | 124 | return success |
| 125 | + } catch (e) { |
| 126 | + upload_error = createKilnError(e) |
| 127 | + return false |
115 | 128 | } finally { |
116 | 129 | upload_in_progress = false |
117 | 130 | upload_progress = 0 |
|
131 | 144 | formData.append(`names`, file.name) |
132 | 145 | }) |
133 | 146 |
|
| 147 | + if (selected_tags.size > 0) { |
| 148 | + Array.from(selected_tags).forEach((tag) => { |
| 149 | + formData.append("tags", tag) |
| 150 | + }) |
| 151 | + } |
| 152 | +
|
134 | 153 | const { data, error } = await client.POST( |
135 | 154 | "/api/projects/{project_id}/documents/bulk", |
136 | 155 | { |
|
158 | 177 | selected_files = [] |
159 | 178 | onUploadCompleted() |
160 | 179 |
|
| 180 | + // reload document tags for the project - because total counts have changed |
| 181 | + // and we cannot know which ones due to partial upload rejection possibly |
| 182 | + // happening on the backend |
| 183 | + await load_document_tags(project_id, { invalidate_cache: true }) |
| 184 | +
|
161 | 185 | ragProgressStore.run_all_rag_configs(project_id).catch((error) => { |
162 | 186 | console.error("Error running all rag configs", error) |
163 | 187 | }) |
|
244 | 268 | show_upload_result = false |
245 | 269 | show_success_dialog = false |
246 | 270 | unsupported_files_count = 0 |
| 271 | + selected_tags = new Set() |
| 272 | + current_tag = "" |
247 | 273 | } |
248 | 274 |
|
249 | 275 | export function close() { |
|
253 | 279 | show_upload_result = false |
254 | 280 | show_success_dialog = false |
255 | 281 | unsupported_files_count = 0 |
| 282 | + selected_tags = new Set() |
| 283 | + current_tag = "" |
256 | 284 | return true |
257 | 285 | } |
258 | 286 |
|
|
329 | 357 | {/if} |
330 | 358 |
|
331 | 359 | {#if !show_success_dialog} |
332 | | - <!-- Dropzone --> |
333 | | - <div |
334 | | - class="border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer {drag_over |
335 | | - ? 'border-primary bg-primary/5' |
336 | | - : 'border-gray-300 hover:border-gray-400'}" |
337 | | - on:dragover={handleDragOver} |
338 | | - on:dragleave={handleDragLeave} |
339 | | - on:drop={handleDrop} |
340 | | - on:click={openFileDialog} |
341 | | - role="button" |
342 | | - tabindex="0" |
343 | | - on:keydown={(e) => { |
344 | | - if (e.key === "Enter" || e.key === " ") { |
345 | | - e.preventDefault() |
346 | | - openFileDialog() |
347 | | - } |
348 | | - }} |
349 | | - > |
350 | | - <div class="space-y-2"> |
351 | | - <div class="w-10 h-10 mx-auto text-gray-500"> |
352 | | - <UploadIcon /> |
353 | | - </div> |
354 | | - <div> |
355 | | - <p class="text-gray-500">Drop files here or click to select</p> |
| 360 | + <div class="pb-2"> |
| 361 | + <!-- Dropzone --> |
| 362 | + <div |
| 363 | + class="border-2 border-dashed rounded-lg p-8 text-center transition-colors cursor-pointer {drag_over |
| 364 | + ? 'border-primary bg-primary/5' |
| 365 | + : 'border-gray-300 hover:border-gray-400'}" |
| 366 | + on:dragover={handleDragOver} |
| 367 | + on:dragleave={handleDragLeave} |
| 368 | + on:drop={handleDrop} |
| 369 | + on:click={openFileDialog} |
| 370 | + role="button" |
| 371 | + tabindex="0" |
| 372 | + on:keydown={(e) => { |
| 373 | + if (e.key === "Enter" || e.key === " ") { |
| 374 | + e.preventDefault() |
| 375 | + openFileDialog() |
| 376 | + } |
| 377 | + }} |
| 378 | + > |
| 379 | + <div class="space-y-2"> |
| 380 | + <div class="w-10 h-10 mx-auto text-gray-500"> |
| 381 | + <UploadIcon /> |
| 382 | + </div> |
| 383 | + <div> |
| 384 | + <p class="text-gray-500">Drop files here or click to select</p> |
| 385 | + </div> |
356 | 386 | </div> |
357 | 387 | </div> |
| 388 | + |
| 389 | + <!-- Hidden file input --> |
| 390 | + <input |
| 391 | + bind:this={file_input} |
| 392 | + type="file" |
| 393 | + multiple |
| 394 | + class="hidden" |
| 395 | + on:change={handleFileSelect} |
| 396 | + accept={supported_file_types.join(",")} |
| 397 | + /> |
| 398 | + |
| 399 | + {#if unsupported_files_count > 0} |
| 400 | + <div class="text-error text-sm"> |
| 401 | + {unsupported_files_count} file{unsupported_files_count === 1 |
| 402 | + ? "" |
| 403 | + : "s"} skipped due to unsupported format |
| 404 | + </div> |
| 405 | + {/if} |
358 | 406 | </div> |
359 | 407 |
|
360 | | - <!-- Hidden file input --> |
361 | | - <input |
362 | | - bind:this={file_input} |
363 | | - type="file" |
364 | | - multiple |
365 | | - class="hidden" |
366 | | - on:change={handleFileSelect} |
367 | | - accept={supported_file_types.join(",")} |
368 | | - /> |
369 | | - |
370 | | - {#if unsupported_files_count > 0} |
371 | | - <div class="text-error text-sm"> |
372 | | - {unsupported_files_count} file{unsupported_files_count === 1 |
373 | | - ? "" |
374 | | - : "s"} skipped due to unsupported format |
| 408 | + <!-- Tag selection --> |
| 409 | + <div> |
| 410 | + <FormElement |
| 411 | + inputType="header_only" |
| 412 | + label="Tags" |
| 413 | + id="tags_section" |
| 414 | + description="Add tags to organize your documents" |
| 415 | + info_description="Any tags set here will be added to each document you add. Tags can be used to filter your document set." |
| 416 | + optional={true} |
| 417 | + value="" |
| 418 | + /> |
| 419 | + {#if selected_tags.size > 0} |
| 420 | + <div class="flex flex-row flex-wrap gap-2 my-2"> |
| 421 | + {#each Array.from(selected_tags).sort() as tag} |
| 422 | + <div |
| 423 | + class="badge bg-gray-200 text-gray-500 py-3 px-3 max-w-full" |
| 424 | + > |
| 425 | + <span class="truncate">{tag}</span> |
| 426 | + <button |
| 427 | + class="pl-3 font-medium shrink-0" |
| 428 | + on:click={() => { |
| 429 | + selected_tags.delete(tag) |
| 430 | + selected_tags = selected_tags |
| 431 | + }}>✕</button |
| 432 | + > |
| 433 | + </div> |
| 434 | + {/each} |
| 435 | + </div> |
| 436 | + {/if} |
| 437 | + <div class="flex flex-row gap-2 items-center"> |
| 438 | + <TagDropdown |
| 439 | + bind:tag={current_tag} |
| 440 | + {project_id} |
| 441 | + example_tag_set="doc" |
| 442 | + on_select={(tag) => { |
| 443 | + selected_tags.add(tag) |
| 444 | + selected_tags = selected_tags |
| 445 | + current_tag = "" |
| 446 | + }} |
| 447 | + on_escape={() => {}} |
| 448 | + focus_on_mount={false} |
| 449 | + /> |
375 | 450 | </div> |
376 | | - {/if} |
| 451 | + </div> |
377 | 452 |
|
378 | 453 | {#if show_upload_result && upload_result} |
379 | 454 | {#if upload_result.created_documents.length > 0} |
|
447 | 522 | </div> |
448 | 523 | {/if} |
449 | 524 | {/if} |
| 525 | + |
| 526 | + {#if upload_error} |
| 527 | + <div class="text-error text-sm"> |
| 528 | + {upload_error.getMessage() || "An unknown error occurred"} |
| 529 | + </div> |
| 530 | + {/if} |
450 | 531 | </div> |
451 | 532 | </div> |
452 | 533 | </Dialog> |
0 commit comments