浏览代码

feat: document select

Timothy J. Baek 1 年之前
父节点
当前提交
63628a70a6
共有 2 个文件被更改,包括 176 次插入24 次删除
  1. 70 0
      src/lib/components/common/Checkbox.svelte
  2. 106 24
      src/routes/(app)/documents/+page.svelte

+ 70 - 0
src/lib/components/common/Checkbox.svelte

@@ -0,0 +1,70 @@
+<script lang="ts">
+	import { createEventDispatcher } from 'svelte';
+	const dispatch = createEventDispatcher();
+
+	export let state = 'unchecked';
+	export let indeterminate = false;
+
+	let _state = 'unchecked';
+
+	$: _state = state;
+</script>
+
+<button
+	class=" outline -outline-offset-1 outline-[1.5px] outline-gray-200 dark:outline-gray-600 {state !==
+	'unchecked'
+		? 'bg-black outline-black '
+		: 'hover:outline-gray-500 hover:bg-gray-50 dark:hover:bg-gray-800'} text-white transition-all rounded inline-block w-3.5 h-3.5 relative"
+	on:click={() => {
+		if (_state === 'unchecked') {
+			_state = 'checked';
+			dispatch('change', _state);
+		} else if (_state === 'checked') {
+			_state = 'unchecked';
+			if (!indeterminate) {
+				dispatch('change', _state);
+			}
+		} else if (indeterminate) {
+			_state = 'checked';
+			dispatch('change', _state);
+		}
+	}}
+>
+	<div class="top-0 left-0 absolute w-full flex justify-center">
+		{#if _state === 'checked'}
+			<svg
+				class="w-3.5 h-3.5"
+				aria-hidden="true"
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+			>
+				<path
+					stroke="currentColor"
+					stroke-linecap="round"
+					stroke-linejoin="round"
+					stroke-width="3"
+					d="m5 12 4.7 4.5 9.3-9"
+				/>
+			</svg>
+		{:else if indeterminate}
+			<svg
+				class="w-3 h-3.5 text-gray-800 dark:text-white"
+				aria-hidden="true"
+				xmlns="http://www.w3.org/2000/svg"
+				fill="none"
+				viewBox="0 0 24 24"
+			>
+				<path
+					stroke="currentColor"
+					stroke-linecap="round"
+					stroke-linejoin="round"
+					stroke-width="3"
+					d="M5 12h14"
+				/>
+			</svg>
+		{/if}
+	</div>
+
+	<!-- {checked} -->
+</button>

+ 106 - 24
src/routes/(app)/documents/+page.svelte

@@ -11,6 +11,8 @@
 	import { uploadDocToVectorDB } from '$lib/apis/rag';
 	import { uploadDocToVectorDB } from '$lib/apis/rag';
 	import { transformFileName } from '$lib/utils';
 	import { transformFileName } from '$lib/utils';
 
 
+	import Checkbox from '$lib/components/common/Checkbox.svelte';
+
 	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
 	import EditDocModal from '$lib/components/documents/EditDocModal.svelte';
 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
 	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
 	import SettingsModal from '$lib/components/documents/SettingsModal.svelte';
@@ -33,6 +35,16 @@
 		await documents.set(await getDocs(localStorage.token));
 		await documents.set(await getDocs(localStorage.token));
 	};
 	};
 
 
+	const deleteDocs = async (docs) => {
+		const res = await Promise.all(
+			docs.map(async (doc) => {
+				return await deleteDocByName(localStorage.token, doc.name);
+			})
+		);
+
+		await documents.set(await getDocs(localStorage.token));
+	};
+
 	const uploadDoc = async (file) => {
 	const uploadDoc = async (file) => {
 		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
 		const res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => {
 			toast.error(error);
 			toast.error(error);
@@ -123,6 +135,15 @@
 			dropZone?.removeEventListener('dragleave', onDragLeave);
 			dropZone?.removeEventListener('dragleave', onDragLeave);
 		};
 		};
 	});
 	});
+
+	let filteredDocs;
+
+	$: filteredDocs = $documents.filter(
+		(doc) =>
+			(selectedTag === '' ||
+				(doc?.content?.tags ?? []).map((tag) => tag.name).includes(selectedTag)) &&
+			(query === '' || doc.name.includes(query))
+	);
 </script>
 </script>
 
 
 {#if dragged}
 {#if dragged}
@@ -287,38 +308,96 @@
 
 
 			{#if tags.length > 0}
 			{#if tags.length > 0}
 				<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
 				<div class="px-2.5 pt-1 flex gap-1 flex-wrap">
-					<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">all</div>
-					</button>
-					{#each tags as tag}
+					<div class="ml-0.5 pr-3 my-auto flex items-center">
+						<Checkbox
+							state={filteredDocs.filter((doc) => doc?.selected === 'checked').length ===
+							filteredDocs.length
+								? 'checked'
+								: 'unchecked'}
+							indeterminate={filteredDocs.filter((doc) => doc?.selected === 'checked').length > 0 &&
+								filteredDocs.filter((doc) => doc?.selected === 'checked').length !==
+									filteredDocs.length}
+							on:change={(e) => {
+								if (e.detail === 'checked') {
+									filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'checked' }));
+								} else if (e.detail === 'unchecked') {
+									filteredDocs = filteredDocs.map((doc) => ({ ...doc, selected: 'unchecked' }));
+								}
+							}}
+						/>
+					</div>
+
+					{#if filteredDocs.filter((doc) => doc?.selected === 'checked').length === 0}
 						<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"
 							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 () => {
 							on:click={async () => {
-								selectedTag = tag;
+								selectedTag = '';
 								// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 								// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
 							}}
 							}}
 						>
 						>
-							<div class=" text-xs font-medium self-center line-clamp-1">
-								#{tag}
-							</div>
+							<div class=" text-xs font-medium self-center line-clamp-1">all</div>
 						</button>
 						</button>
-					{/each}
+
+						{#each tags as tag}
+							<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 = tag;
+									// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+								}}
+							>
+								<div class=" text-xs font-medium self-center line-clamp-1">
+									#{tag}
+								</div>
+							</button>
+						{/each}
+					{:else}
+						<div class="flex-1 flex w-full justify-between items-center">
+							<div class="text-xs font-medium py-0.5 self-center mr-1">
+								{filteredDocs.filter((doc) => doc?.selected === 'checked').length} Selected
+							</div>
+
+							<div class="flex gap-1">
+								<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>
+
+								<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 () => {
+										deleteDocs(filteredDocs.filter((doc) => doc.selected === 'checked'));
+										// await chats.set(await getChatListByTagName(localStorage.token, tag.name));
+									}}
+								>
+									<div class=" text-xs font-medium self-center line-clamp-1">delete</div>
+								</button>
+							</div>
+						</div>
+					{/if}
 				</div>
 				</div>
 			{/if}
 			{/if}
 
 
 			<div class="my-3 mb-5">
 			<div class="my-3 mb-5">
-				{#each $documents.filter((doc) => (selectedTag === '' || (doc?.content?.tags ?? [])
-								.map((tag) => tag.name)
-								.includes(selectedTag)) && (query === '' || doc.name.includes(query))) as doc}
-					<div
-						class=" flex space-x-4 cursor-pointer w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+				{#each filteredDocs as doc}
+					<button
+						class=" flex space-x-4 cursor-pointer text-left w-full px-3 py-2 dark:hover:bg-white/5 hover:bg-black/5 rounded-xl"
+						on:click={() => {
+							if (doc?.selected === 'checked') {
+								doc.selected = 'unchecked';
+							} else {
+								doc.selected = 'checked';
+							}
+						}}
 					>
 					>
+						<div class="my-auto flex items-center">
+							<Checkbox state={doc?.selected ?? 'unchecked'} />
+						</div>
 						<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
 						<div class=" flex flex-1 space-x-4 cursor-pointer w-full">
 							<div class=" flex items-center space-x-3">
 							<div class=" flex items-center space-x-3">
 								<div class="p-2.5 bg-red-400 text-white rounded-lg">
 								<div class="p-2.5 bg-red-400 text-white rounded-lg">
@@ -387,9 +466,10 @@
 						</div>
 						</div>
 						<div class="flex flex-row space-x-1 self-center">
 						<div class="flex flex-row space-x-1 self-center">
 							<button
 							<button
-								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
+								class="self-center w-fit text-sm z-20 px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 								type="button"
 								type="button"
-								on:click={async () => {
+								on:click={async (e) => {
+									e.stopPropagation();
 									showEditDocModal = !showEditDocModal;
 									showEditDocModal = !showEditDocModal;
 									selectedDoc = doc;
 									selectedDoc = doc;
 								}}
 								}}
@@ -435,7 +515,9 @@
 							<button
 							<button
 								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 								class="self-center w-fit text-sm px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
 								type="button"
 								type="button"
-								on:click={() => {
+								on:click={(e) => {
+									e.stopPropagation();
+
 									deleteDoc(doc.name);
 									deleteDoc(doc.name);
 								}}
 								}}
 							>
 							>
@@ -455,7 +537,7 @@
 								</svg>
 								</svg>
 							</button>
 							</button>
 						</div>
 						</div>
-					</div>
+					</button>
 				{/each}
 				{/each}
 			</div>
 			</div>