Przeglądaj źródła

feat: batch doc tagging

Timothy J. Baek 1 rok temu
rodzic
commit
92c922b061

+ 4 - 2
src/lib/apis/documents/index.ts

@@ -5,7 +5,8 @@ export const createNewDoc = async (
 	collection_name: string,
 	filename: string,
 	name: string,
-	title: string
+	title: string,
+	content: object | null = null
 ) => {
 	let error = null;
 
@@ -20,7 +21,8 @@ export const createNewDoc = async (
 			collection_name: collection_name,
 			filename: filename,
 			name: name,
-			title: title
+			title: title,
+			...(content ? { content: JSON.stringify(content) } : {})
 		})
 	})
 		.then(async (res) => {

+ 188 - 0
src/lib/components/documents/AddDocModal.svelte

@@ -0,0 +1,188 @@
+<script lang="ts">
+	import toast from 'svelte-french-toast';
+	import dayjs from 'dayjs';
+	import { onMount } from 'svelte';
+
+	import { createNewDoc, getDocs, tagDocByName, updateDocByName } from '$lib/apis/documents';
+	import Modal from '../common/Modal.svelte';
+	import { documents } from '$lib/stores';
+	import TagInput from '../common/Tags/TagInput.svelte';
+	import Tags from '../common/Tags.svelte';
+	import { addTagById } from '$lib/apis/chats';
+	import { uploadDocToVectorDB } from '$lib/apis/rag';
+	import { transformFileName } from '$lib/utils';
+	import { SUPPORTED_FILE_EXTENSIONS, SUPPORTED_FILE_TYPE } from '$lib/constants';
+
+	export let show = false;
+	export let selectedDoc;
+
+	let inputFiles;
+	let tags = [];
+
+	let doc = {
+		name: '',
+		title: '',
+		content: null
+	};
+
+	const uploadDoc = async (file) => {
+		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
+			toast.error(error);
+			return null;
+		});
+
+		if (res) {
+			await createNewDoc(
+				localStorage.token,
+				res.collection_name,
+				res.filename,
+				transformFileName(res.filename),
+				res.filename,
+				tags.length > 0
+					? {
+							tags: tags
+					  }
+					: null
+			).catch((error) => {
+				toast.error(error);
+				return null;
+			});
+			await documents.set(await getDocs(localStorage.token));
+		}
+	};
+
+	const submitHandler = async () => {
+		if (inputFiles && inputFiles.length > 0) {
+			for (const file of inputFiles) {
+				console.log(file, file.name.split('.').at(-1));
+				if (
+					SUPPORTED_FILE_TYPE.includes(file['type']) ||
+					SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
+				) {
+					uploadDoc(file);
+				} else {
+					toast.error(
+						`Unknown File Type '${file['type']}', but accepting and treating as plain text`
+					);
+					uploadDoc(file);
+				}
+			}
+
+			inputFiles = null;
+			document.getElementById('upload-doc-input').value = '';
+		} else {
+			toast.error(`File not found.`);
+		}
+
+		show = false;
+		documents.set(await getDocs(localStorage.token));
+	};
+
+	const addTagHandler = async (tagName) => {
+		if (!tags.find((tag) => tag.name === tagName) && tagName !== '') {
+			tags = [...tags, { name: tagName }];
+		} else {
+			console.log('tag already exists');
+		}
+	};
+
+	const deleteTagHandler = async (tagName) => {
+		tags = tags.filter((tag) => tag.name !== tagName);
+	};
+
+	onMount(() => {});
+</script>
+
+<Modal size="sm" bind:show>
+	<div>
+		<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
+			<div class=" text-lg font-medium self-center">Add Docs</div>
+			<button
+				class="self-center"
+				on:click={() => {
+					show = false;
+				}}
+			>
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-5 h-5"
+				>
+					<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>
+		<hr class=" dark:border-gray-800" />
+
+		<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
+			<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
+				<form
+					class="flex flex-col w-full"
+					on:submit|preventDefault={() => {
+						submitHandler();
+					}}
+				>
+					<div class="mb-3 w-full">
+						<input id="upload-doc-input" hidden bind:files={inputFiles} type="file" multiple />
+
+						<button
+							class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
+							type="button"
+							on:click={() => {
+								document.getElementById('upload-doc-input')?.click();
+							}}
+						>
+							{#if inputFiles}
+								{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
+							{:else}
+								Click here to select documents.
+							{/if}
+						</button>
+					</div>
+
+					<div class=" flex flex-col space-y-1.5">
+						<div class="flex flex-col w-full">
+							<div class=" mb-1.5 text-xs text-gray-500">Tags</div>
+
+							<Tags {tags} addTag={addTagHandler} deleteTag={deleteTagHandler} />
+						</div>
+					</div>
+
+					<div class="flex justify-end pt-5 text-sm font-medium">
+						<button
+							class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
+							type="submit"
+						>
+							Save
+						</button>
+					</div>
+				</form>
+			</div>
+		</div>
+	</div>
+</Modal>
+
+<style>
+	input::-webkit-outer-spin-button,
+	input::-webkit-inner-spin-button {
+		/* display: none; <- Crashes Chrome on hover */
+		-webkit-appearance: none;
+		margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
+	}
+
+	.tabs::-webkit-scrollbar {
+		display: none; /* for Chrome, Safari and Opera */
+	}
+
+	.tabs {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
+
+	input[type='number'] {
+		-moz-appearance: textfield; /* Firefox */
+	}
+</style>

+ 7 - 34
src/routes/(app)/documents/+page.svelte

@@ -16,6 +16,7 @@
 	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
 	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
+	import AddDocModal from '$lib/components/documents/AddDocModal.svelte';
 	let importFiles = '';
 
 	let inputFiles = '';
@@ -24,6 +25,7 @@
 	let tags = [];
 
 	let showSettingsModal = false;
+	let showAddDocModal = false;
 	let showEditDocModal = false;
 	let selectedDoc;
 	let selectedTag = '';
@@ -171,36 +173,7 @@
 	<EditDocModal bind:show={showEditDocModal} {selectedDoc} />
 {/key}
 
-<input
-	id="upload-doc-input"
-	bind:files={inputFiles}
-	type="file"
-	multiple
-	hidden
-	on:change={async (e) => {
-		if (inputFiles && inputFiles.length > 0) {
-			for (const file of inputFiles) {
-				console.log(file, file.name.split('.').at(-1));
-				if (
-					SUPPORTED_FILE_TYPE.includes(file['type']) ||
-					SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
-				) {
-					uploadDoc(file);
-				} else {
-					toast.error(
-						`Unknown File Type '${file['type']}', but accepting and treating as plain text`
-					);
-					uploadDoc(file);
-				}
-			}
-
-			inputFiles = null;
-			e.target.value = '';
-		} else {
-			toast.error(`File not found.`);
-		}
-	}}
-/>
+<AddDocModal bind:show={showAddDocModal} />
 
 <SettingsModal bind:show={showSettingsModal} />
 
@@ -268,7 +241,7 @@
 					<button
 						class=" px-2 py-2 rounded-xl border border-gray-200 dark:border-gray-600 dark:border-0 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 transition font-medium text-sm flex items-center space-x-1"
 						on:click={() => {
-							document.getElementById('upload-doc-input')?.click();
+							showAddDocModal = true;
 						}}
 					>
 						<svg
@@ -358,15 +331,15 @@
 							</div>
 
 							<div class="flex gap-1">
-								<button
+								<!-- <button
 									class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"
 									on:click={async () => {
 										selectedTag = '';
 										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 									}}
 								>
-									<div class=" text-xs font-medium self-center line-clamp-1">tag</div>
-								</button>
+									<div class=" text-xs font-medium self-center line-clamp-1">add tags</div>
+								</button> -->
 
 								<button
 									class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:text-white"