瀏覽代碼

enh: sync directory

Timothy J. Baek 7 月之前
父節點
當前提交
a909aa1c20

+ 19 - 0
backend/open_webui/apps/webui/routers/knowledge.py

@@ -313,6 +313,25 @@ def remove_file_from_knowledge_by_id(
         )
 
 
+############################
+# ResetKnowledgeById
+############################
+
+
+@router.post("/{id}/reset", response_model=Optional[KnowledgeResponse])
+async def reset_knowledge_by_id(id: str, user=Depends(get_admin_user)):
+    try:
+        VECTOR_DB_CLIENT.delete_collection(collection_name=id)
+    except Exception as e:
+        log.debug(e)
+        pass
+
+    knowledge = Knowledges.update_knowledge_by_id(
+        id=id, form_data=KnowledgeUpdateForm(data={"file_ids": []})
+    )
+    return knowledge
+
+
 ############################
 # DeleteKnowledgeById
 ############################

+ 32 - 0
src/lib/apis/knowledge/index.ts

@@ -243,6 +243,38 @@ export const removeFileFromKnowledgeById = async (token: string, id: string, fil
 	return res;
 };
 
+export const resetKnowledgeById = async (token: string, id: string) => {
+	let error = null;
+
+	const res = await fetch(`${WEBUI_API_BASE_URL}/knowledge/${id}/reset`, {
+		method: 'POST',
+		headers: {
+			Accept: 'application/json',
+			'Content-Type': 'application/json',
+			authorization: `Bearer ${token}`
+		}
+	})
+		.then(async (res) => {
+			if (!res.ok) throw await res.json();
+			return res.json();
+		})
+		.then((json) => {
+			return json;
+		})
+		.catch((err) => {
+			error = err.detail;
+
+			console.log(err);
+			return null;
+		});
+
+	if (error) {
+		throw error;
+	}
+
+	return res;
+};
+
 export const deleteKnowledgeById = async (token: string, id: string) => {
 	let error = null;
 

+ 19 - 0
src/lib/components/icons/ArrowPath.svelte

@@ -0,0 +1,19 @@
+<script lang="ts">
+	export let className = 'size-4';
+	export let strokeWidth = '1.5';
+</script>
+
+<svg
+	xmlns="http://www.w3.org/2000/svg"
+	fill="none"
+	viewBox="0 0 24 24"
+	stroke-width={strokeWidth}
+	stroke="currentColor"
+	class={className}
+>
+	<path
+		stroke-linecap="round"
+		stroke-linejoin="round"
+		d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
+	/>
+</svg>

+ 22 - 3
src/lib/components/workspace/Knowledge/Collection.svelte

@@ -14,6 +14,7 @@
 		addFileToKnowledgeById,
 		getKnowledgeById,
 		removeFileFromKnowledgeById,
+		resetKnowledgeById,
 		updateFileFromKnowledgeById,
 		updateKnowledgeById
 	} from '$lib/apis/knowledge';
@@ -70,10 +71,12 @@
 	let selectedFileId = null;
 
 	$: if (selectedFileId) {
-		const file = knowledge.files.find((file) => file.id === selectedFileId);
+		const file = (knowledge?.files ?? []).find((file) => file.id === selectedFileId);
 		if (file) {
 			file.data = file.data ?? { content: '' };
 			selectedFile = file;
+		} else {
+			selectedFile = null;
 		}
 	} else {
 		selectedFile = null;
@@ -130,6 +133,9 @@
 			// Get directory handle through picker
 			const dirHandle = await window.showDirectoryPicker();
 
+			console.log(typeof dirHandle);
+			console.log(dirHandle);
+
 			let totalFiles = 0;
 			let uploadedFiles = 0;
 
@@ -196,8 +202,18 @@
 	};
 
 	// Helper function to maintain file paths within zip
-	const getRelativePath = (fullPath, basePath) => {
-		return fullPath.substring(basePath.length + 1);
+	const syncDirectoryHandler = async () => {
+		const res = await resetKnowledgeById(localStorage.token, id).catch((e) => {
+			toast.error(e);
+		});
+
+		if (res) {
+			knowledge = res;
+			toast.success($i18n.t('Knowledge reset successfully.'));
+
+			// Upload directory
+			uploadDirectoryHandler();
+		}
 	};
 
 	const addFileHandler = async (fileId) => {
@@ -501,6 +517,9 @@
 													document.getElementById('files-input').click();
 												}
 											}}
+											on:sync={(e) => {
+												syncDirectoryHandler();
+											}}
 										/>
 									</div>
 								</div>

+ 18 - 8
src/lib/components/workspace/Knowledge/Collection/AddContentMenu.svelte

@@ -5,18 +5,11 @@
 	const dispatch = createEventDispatcher();
 
 	import Dropdown from '$lib/components/common/Dropdown.svelte';
-	import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
-	import Pencil from '$lib/components/icons/Pencil.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
-	import Tags from '$lib/components/chat/Tags.svelte';
-	import Share from '$lib/components/icons/Share.svelte';
-	import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
-	import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
-	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
 	import ArrowUpCircle from '$lib/components/icons/ArrowUpCircle.svelte';
-	import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
 	import BarsArrowUp from '$lib/components/icons/BarsArrowUp.svelte';
 	import FolderOpen from '$lib/components/icons/FolderOpen.svelte';
+	import ArrowPath from '$lib/components/icons/ArrowPath.svelte';
 
 	const i18n = getContext('i18n');
 
@@ -83,6 +76,23 @@
 				<div class="flex items-center">{$i18n.t('Upload directory')}</div>
 			</DropdownMenu.Item>
 
+			<Tooltip
+				content={$i18n.t(
+					'This option will delete all existing files in the collection and replace them with newly uploaded files.'
+				)}
+				className="w-full"
+			>
+				<DropdownMenu.Item
+					class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
+					on:click={() => {
+						dispatch('sync', { type: 'directory' });
+					}}
+				>
+					<ArrowPath strokeWidth="2" />
+					<div class="flex items-center">{$i18n.t('Sync directory')}</div>
+				</DropdownMenu.Item>
+			</Tooltip>
+
 			<DropdownMenu.Item
 				class="flex  gap-2  items-center px-3 py-2 text-sm  cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
 				on:click={() => {