forked from open-webui/open-webui
		
	feat: document select
This commit is contained in:
		
							parent
							
								
									f8cf43c0f7
								
							
						
					
					
						commit
						63628a70a6
					
				
					 2 changed files with 176 additions and 24 deletions
				
			
		
							
								
								
									
										70
									
								
								src/lib/components/common/Checkbox.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/lib/components/common/Checkbox.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -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> | ||||
|  | @ -11,6 +11,8 @@ | |||
| 	import { uploadDocToVectorDB } from '$lib/apis/rag'; | ||||
| 	import { transformFileName } from '$lib/utils'; | ||||
| 
 | ||||
| 	import Checkbox from '$lib/components/common/Checkbox.svelte'; | ||||
| 
 | ||||
| 	import EditDocModal from '$lib/components/documents/EditDocModal.svelte'; | ||||
| 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte'; | ||||
| 	import SettingsModal from '$lib/components/documents/SettingsModal.svelte'; | ||||
|  | @ -33,6 +35,16 @@ | |||
| 		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 res = await uploadDocToVectorDB(localStorage.token, '', file).catch((error) => { | ||||
| 			toast.error(error); | ||||
|  | @ -123,6 +135,15 @@ | |||
| 			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> | ||||
| 
 | ||||
| {#if dragged} | ||||
|  | @ -287,6 +308,26 @@ | |||
| 
 | ||||
| 			{#if tags.length > 0} | ||||
| 				<div class="px-2.5 pt-1 flex gap-1 flex-wrap"> | ||||
| 					<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 | ||||
| 							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 () => { | ||||
|  | @ -296,6 +337,7 @@ | |||
| 						> | ||||
| 							<div class=" text-xs font-medium self-center line-clamp-1">all</div> | ||||
| 						</button> | ||||
| 
 | ||||
| 						{#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" | ||||
|  | @ -309,16 +351,53 @@ | |||
| 								</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> | ||||
| 			{/if} | ||||
| 
 | ||||
| 			<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 items-center space-x-3"> | ||||
| 								<div class="p-2.5 bg-red-400 text-white rounded-lg"> | ||||
|  | @ -387,9 +466,10 @@ | |||
| 						</div> | ||||
| 						<div class="flex flex-row space-x-1 self-center"> | ||||
| 							<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" | ||||
| 								on:click={async () => { | ||||
| 								on:click={async (e) => { | ||||
| 									e.stopPropagation(); | ||||
| 									showEditDocModal = !showEditDocModal; | ||||
| 									selectedDoc = doc; | ||||
| 								}} | ||||
|  | @ -435,7 +515,9 @@ | |||
| 							<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" | ||||
| 								type="button" | ||||
| 								on:click={() => { | ||||
| 								on:click={(e) => { | ||||
| 									e.stopPropagation(); | ||||
| 
 | ||||
| 									deleteDoc(doc.name); | ||||
| 								}} | ||||
| 							> | ||||
|  | @ -455,7 +537,7 @@ | |||
| 								</svg> | ||||
| 							</button> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					</button> | ||||
| 				{/each} | ||||
| 			</div> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy J. Baek
						Timothy J. Baek