Timothy J. Baek 7 tháng trước cách đây
mục cha
commit
b862dff185

+ 2 - 5
backend/open_webui/apps/retrieval/main.py

@@ -785,10 +785,7 @@ def process_file(
                     "content": text_content,
                 }
         except Exception as e:
-            raise HTTPException(
-                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
-                detail=e,
-            )
+            raise e
     except Exception as e:
         log.exception(e)
         if "No pandoc was found" in str(e):
@@ -799,7 +796,7 @@ def process_file(
         else:
             raise HTTPException(
                 status_code=status.HTTP_400_BAD_REQUEST,
-                detail=ERROR_MESSAGES.DEFAULT(e),
+                detail=str(e),
             )
 
 

+ 24 - 7
src/lib/components/common/FileItem.svelte

@@ -9,8 +9,7 @@
 	const dispatch = createEventDispatcher();
 
 	export let className = 'w-60';
-	export let colorClassName =
-		'bg-white dark:bg-gray-850 border border-gray-50 dark:border-gray-850';
+	export let colorClassName = 'bg-white dark:bg-gray-850 border border-gray-50 dark:border-white/5';
 	export let url: string | null = null;
 
 	export let dismissible = false;
@@ -31,7 +30,7 @@
 {/if}
 
 <button
-	class="relative group p-1 {className} flex items-center {colorClassName} rounded-2xl text-left"
+	class="relative group p-1.5 {className} flex items-center {colorClassName} rounded-2xl text-left"
 	type="button"
 	on:click={async () => {
 		if (file?.file?.content) {
@@ -106,7 +105,7 @@
 		{/if}
 	</div>
 
-	<div class="flex flex-col justify-center -space-y-0.5 px-2 w-full">
+	<div class="flex flex-col justify-center -space-y-0.5 px-2.5 w-full">
 		<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1 mb-1">
 			{name}
 		</div>
@@ -128,16 +127,34 @@
 	</div>
 
 	{#if dismissible}
-		<div class=" pr-2">
+		<div class=" absolute -top-2 -right-2">
 			<button
-				class=" px-2 py-2 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-xl group-hover:visible invisible transition"
+				class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
 				type="button"
 				on:click={() => {
 					dispatch('dismiss');
 				}}
 			>
-				<GarbageBin />
+				<svg
+					xmlns="http://www.w3.org/2000/svg"
+					viewBox="0 0 20 20"
+					fill="currentColor"
+					class="w-4 h-4"
+				>
+					<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>
+
+			<!-- <button
+				class=" p-1 dark:text-gray-300 dark:hover:text-white hover:bg-black/5 dark:hover:bg-white/5 rounded-full group-hover:visible invisible transition"
+				type="button"
+				on:click={() => {
+				}}
+			>
+				<GarbageBin />
+			</button> -->
 		</div>
 	{/if}
 </button>

+ 19 - 0
src/lib/components/icons/BarsArrowUp.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="M3 4.5h14.25M3 9h9.75M3 13.5h5.25m5.25-.75L17.25 9m0 0L21 12.75M17.25 9v12"
+	/>
+</svg>

+ 126 - 136
src/lib/components/workspace/Knowledge/Item.svelte → src/lib/components/workspace/Knowledge/Collection.svelte

@@ -1,7 +1,7 @@
 <script lang="ts">
 	import { toast } from 'svelte-sonner';
 
-	import { onMount, getContext } from 'svelte';
+	import { onMount, getContext, onDestroy } from 'svelte';
 	const i18n = getContext('i18n');
 
 	import { goto } from '$app/navigation';
@@ -14,12 +14,13 @@
 	import Spinner from '$lib/components/common/Spinner.svelte';
 	import Tooltip from '$lib/components/common/Tooltip.svelte';
 	import Badge from '$lib/components/common/Badge.svelte';
-	import Files from './Files.svelte';
+	import Files from './Collection/Files.svelte';
 	import AddFilesPlaceholder from '$lib/components/AddFilesPlaceholder.svelte';
-	import AddContentModal from './AddContentModal.svelte';
+	import AddContentModal from './Collection/AddTextContentModal.svelte';
 	import { transcribeAudio } from '$lib/apis/audio';
 	import { blobToFile } from '$lib/utils';
 	import { processFile } from '$lib/apis/retrieval';
+	import AddContentMenu from './Collection/AddContentMenu.svelte';
 
 	let largeScreen = true;
 
@@ -40,30 +41,9 @@
 	let selectedFileId = null;
 
 	let debounceTimeout = null;
+	let mediaQuery;
 	let dragged = false;
 
-	let showAddContentModal = false;
-
-	const changeDebounceHandler = () => {
-		console.log('debounce');
-		if (debounceTimeout) {
-			clearTimeout(debounceTimeout);
-		}
-
-		debounceTimeout = setTimeout(async () => {
-			const res = await updateKnowledgeById(localStorage.token, id, {
-				name: knowledge.name,
-				description: knowledge.description
-			}).catch((e) => {
-				toast.error(e);
-			});
-
-			if (res) {
-				toast.success($i18n.t('Knowledge updated successfully'));
-			}
-		}, 1000);
-	};
-
 	const uploadFileHandler = async (file) => {
 		console.log(file);
 
@@ -87,28 +67,8 @@
 			});
 
 			if (uploadedFile) {
-				const processedFile = await processFile(localStorage.token, uploadedFile.id, id).catch(
-					(e) => {
-						toast.error(e);
-					}
-				);
-
-				if (processedFile.status) {
-					knowledge.data.file_ids = [...(knowledge.data.file_ids ?? []), uploadedFile.id];
-
-					const updatedKnowledge = await updateKnowledgeById(localStorage.token, id, {
-						data: knowledge.data
-					}).catch((e) => {
-						toast.error(e);
-					});
-
-					if (updatedKnowledge) {
-						knowledge = updatedKnowledge;
-						toast.success($i18n.t('File added successfully.'));
-					}
-				} else {
-					toast.error($i18n.t('Failed to process file.'));
-				}
+				console.log(uploadedFile);
+				processFileHandler(uploadedFile);
 			} else {
 				toast.error($i18n.t('Failed to upload file.'));
 			}
@@ -117,17 +77,95 @@
 		}
 	};
 
-	onMount(async () => {
-		// listen to resize 1024px
-		const mediaQuery = window.matchMedia('(min-width: 1024px)');
+	const processFileHandler = async (uploadedFile) => {
+		const processedFile = await processFile(localStorage.token, uploadedFile.id, id).catch((e) => {
+			toast.error(e);
+		});
+
+		if (processedFile.status) {
+			console.log(processedFile);
+
+			if (!knowledge.data) {
+				knowledge.data = {};
+			}
+
+			knowledge.data.file_ids = [...(knowledge?.data?.file_ids ?? []), uploadedFile.id];
+
+			console.log(knowledge);
+
+			const updatedKnowledge = await updateKnowledgeById(localStorage.token, id, {
+				data: knowledge?.data ?? {}
+			}).catch((e) => {
+				console.error(e);
+			});
+
+			if (updatedKnowledge) {
+				knowledge = updatedKnowledge;
+				toast.success($i18n.t('File added successfully.'));
+			}
+		} else {
+			toast.error($i18n.t('Failed to process file.'));
+		}
+	};
+
+	const changeDebounceHandler = () => {
+		console.log('debounce');
+		if (debounceTimeout) {
+			clearTimeout(debounceTimeout);
+		}
+
+		debounceTimeout = setTimeout(async () => {
+			const res = await updateKnowledgeById(localStorage.token, id, {
+				name: knowledge.name,
+				description: knowledge.description
+			}).catch((e) => {
+				toast.error(e);
+			});
 
-		const handleMediaQuery = async (e) => {
-			if (e.matches) {
-				largeScreen = true;
+			if (res) {
+				toast.success($i18n.t('Knowledge updated successfully'));
+			}
+		}, 1000);
+	};
+
+	const handleMediaQuery = async (e) => {
+		if (e.matches) {
+			largeScreen = true;
+		} else {
+			largeScreen = false;
+		}
+	};
+
+	const onDragOver = (e) => {
+		e.preventDefault();
+		dragged = true;
+	};
+
+	const onDragLeave = () => {
+		dragged = false;
+	};
+
+	const onDrop = async (e) => {
+		e.preventDefault();
+
+		if (e.dataTransfer?.files) {
+			const inputFiles = e.dataTransfer?.files;
+
+			if (inputFiles && inputFiles.length > 0) {
+				for (const file of inputFiles) {
+					await uploadFileHandler(file);
+				}
 			} else {
-				largeScreen = false;
+				toast.error($i18n.t(`File not found.`));
 			}
-		};
+		}
+
+		dragged = false;
+	};
+
+	onMount(async () => {
+		// listen to resize 1024px
+		mediaQuery = window.matchMedia('(min-width: 1024px)');
 
 		mediaQuery.addEventListener('change', handleMediaQuery);
 		handleMediaQuery(mediaQuery);
@@ -146,45 +184,17 @@
 		}
 
 		const dropZone = document.querySelector('body');
-
-		const onDragOver = (e) => {
-			e.preventDefault();
-			dragged = true;
-		};
-
-		const onDragLeave = () => {
-			dragged = false;
-		};
-
-		const onDrop = async (e) => {
-			e.preventDefault();
-
-			if (e.dataTransfer?.files) {
-				const inputFiles = e.dataTransfer?.files;
-
-				if (inputFiles && inputFiles.length > 0) {
-					for (const file of inputFiles) {
-						await uploadFileHandler(file);
-					}
-				} else {
-					toast.error($i18n.t(`File not found.`));
-				}
-			}
-
-			dragged = false;
-		};
-
 		dropZone?.addEventListener('dragover', onDragOver);
 		dropZone?.addEventListener('drop', onDrop);
 		dropZone?.addEventListener('dragleave', onDragLeave);
+	});
 
-		return () => {
-			mediaQuery.removeEventListener('change', handleMediaQuery);
-
-			dropZone?.removeEventListener('dragover', onDragOver);
-			dropZone?.removeEventListener('drop', onDrop);
-			dropZone?.removeEventListener('dragleave', onDragLeave);
-		};
+	onDestroy(() => {
+		mediaQuery?.removeEventListener('change', handleMediaQuery);
+		const dropZone = document.querySelector('body');
+		dropZone?.removeEventListener('dragover', onDragOver);
+		dropZone?.removeEventListener('drop', onDrop);
+		dropZone?.removeEventListener('dragleave', onDragLeave);
 	});
 </script>
 
@@ -211,13 +221,6 @@
 	</div>
 {/if}
 
-<AddContentModal
-	bind:show={showAddContentModal}
-	on:add={(e) => {
-		console.log(e);
-	}}
-/>
-
 <div class="flex flex-col w-full max-h-[100dvh] h-full">
 	<button
 		class="flex space-x-1"
@@ -282,57 +285,44 @@
 				<div
 					class=" {largeScreen
 						? 'flex-shrink-0'
-						: 'flex-1'} p-2.5 w-80 rounded-2xl border border-gray-50 dark:border-gray-850"
+						: 'flex-1'} flex py-2.5 w-80 rounded-2xl border border-gray-50 dark:border-gray-850"
 				>
 					<div class=" flex flex-col w-full space-x-2 rounded-lg h-full">
-						<div class="flex px-1">
-							<div class=" self-center ml-1 mr-3">
-								<svg
-									xmlns="http://www.w3.org/2000/svg"
-									viewBox="0 0 20 20"
-									fill="currentColor"
-									class="w-4 h-4"
-								>
-									<path
-										fill-rule="evenodd"
-										d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
-										clip-rule="evenodd"
-									/>
-								</svg>
-							</div>
-							<input
-								class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
-								bind:value={query}
-								placeholder={$i18n.t('Search Collection')}
-							/>
-
-							<div>
-								<Tooltip content={$i18n.t('Add Content')}>
-									<button
-										class=" px-2 py-2 rounded-xl border border-gray-100 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={() => {
-											showAddContentModal = true;
-										}}
-									>
+						<div class="w-full h-full flex flex-col">
+							<div class=" px-3">
+								<div class="flex">
+									<div class=" self-center ml-1 mr-3">
 										<svg
 											xmlns="http://www.w3.org/2000/svg"
-											viewBox="0 0 16 16"
+											viewBox="0 0 20 20"
 											fill="currentColor"
 											class="w-4 h-4"
 										>
 											<path
-												d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+												fill-rule="evenodd"
+												d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
+												clip-rule="evenodd"
 											/>
 										</svg>
-									</button>
-								</Tooltip>
+									</div>
+									<input
+										class=" w-full text-sm pr-4 py-1 rounded-r-xl outline-none bg-transparent"
+										bind:value={query}
+										placeholder={$i18n.t('Search Collection')}
+									/>
+
+									<div>
+										<AddContentMenu />
+									</div>
+								</div>
+
+								<hr class=" mt-2 mb-1 border-gray-50 dark:border-gray-850" />
 							</div>
-						</div>
-						<hr class="my-2 border-gray-50 dark:border-gray-850" />
 
-						<div class="w-full h-full flex">
-							{#if (knowledge?.data?.file_ids ?? []).length > 0}
-								<Files files={knowledge.files} />
+							{#if (knowledge?.files ?? []).length > 0}
+								<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
+									<Files files={knowledge.files} />
+								</div>
 							{:else}
 								<div class="m-auto text-gray-500 text-xs">No content found</div>
 							{/if}

+ 86 - 0
src/lib/components/workspace/Knowledge/Collection/AddContentMenu.svelte

@@ -0,0 +1,86 @@
+<script lang="ts">
+	import { DropdownMenu } from 'bits-ui';
+	import { flyAndScale } from '$lib/utils/transitions';
+	import { getContext, createEventDispatcher } from 'svelte';
+	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';
+
+	const i18n = getContext('i18n');
+
+	export let onClose: Function = () => {};
+
+	let show = false;
+</script>
+
+<Dropdown
+	bind:show
+	on:change={(e) => {
+		if (e.detail === false) {
+			onClose();
+		}
+	}}
+	align="end"
+>
+	<Tooltip content={$i18n.t('Add Content')}>
+		<button
+			class=" px-2 py-2 rounded-xl border border-gray-100 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={(e) => {
+				e.stopPropagation();
+				show = true;
+			}}
+		>
+			<svg
+				xmlns="http://www.w3.org/2000/svg"
+				viewBox="0 0 16 16"
+				fill="currentColor"
+				class="w-4 h-4"
+			>
+				<path
+					d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
+				/>
+			</svg>
+		</button>
+	</Tooltip>
+
+	<div slot="content">
+		<DropdownMenu.Content
+			class="w-full max-w-44 rounded-xl p-1 z-50 bg-white dark:bg-gray-850 dark:text-white shadow"
+			sideOffset={4}
+			side="bottom"
+			align="end"
+			transition={flyAndScale}
+		>
+			<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('files');
+				}}
+			>
+				<ArrowUpCircle strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Upload files')}</div>
+			</DropdownMenu.Item>
+
+			<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('text');
+				}}
+			>
+				<BarsArrowUp strokeWidth="2" />
+				<div class="flex items-center">{$i18n.t('Add text content')}</div>
+			</DropdownMenu.Item>
+		</DropdownMenu.Content>
+	</div>
+</Dropdown>

+ 0 - 0
src/lib/components/workspace/Knowledge/AddContentModal.svelte → src/lib/components/workspace/Knowledge/Collection/AddTextContentModal.svelte


+ 0 - 0
src/lib/components/workspace/Knowledge/CreateKnowledge.svelte → src/lib/components/workspace/Knowledge/CreateCollection.svelte


+ 2 - 2
src/routes/(app)/workspace/knowledge/[id]/+page.svelte

@@ -1,5 +1,5 @@
 <script>
-	import Knowledge from '$lib/components/workspace/Knowledge/Item.svelte';
+	import Collection from '$lib/components/workspace/Knowledge/Collection.svelte';
 </script>
 
-<Knowledge />
+<Collection />

+ 2 - 2
src/routes/(app)/workspace/knowledge/create/+page.svelte

@@ -1,5 +1,5 @@
 <script>
-	import CreateKnowledge from '$lib/components/workspace/Knowledge/CreateKnowledge.svelte';
+	import CreateCollection from '$lib/components/workspace/Knowledge/CreateCollection.svelte';
 </script>
 
-<CreateKnowledge />
+<CreateCollection />