feat: knowledge directory

This commit is contained in:
Timothy Jaeryang Baek
2026-05-13 22:37:53 +09:00
parent 8ace73da84
commit c2cbc47ca7
10 changed files with 1244 additions and 13 deletions
@@ -32,7 +32,11 @@
updateFileFromKnowledgeById,
updateKnowledgeById,
updateKnowledgeAccessGrants,
searchKnowledgeFilesById
searchKnowledgeFilesById,
createKnowledgeDirectory,
updateKnowledgeDirectory,
deleteKnowledgeDirectory,
moveFileInKnowledge
} from '$lib/apis/knowledge';
import { processWeb, processYoutubeVideo } from '$lib/apis/retrieval';
@@ -44,8 +48,11 @@
import AddContentMenu from './KnowledgeBase/AddContentMenu.svelte';
import AddTextContentModal from './KnowledgeBase/AddTextContentModal.svelte';
import NewDirectoryModal from './KnowledgeBase/NewDirectoryModal.svelte';
import KnowledgeBreadcrumbs from './KnowledgeBase/KnowledgeBreadcrumbs.svelte';
import SyncConfirmDialog from '../../common/ConfirmDialog.svelte';
import ConfirmDialog from '../../common/ConfirmDialog.svelte';
import Drawer from '$lib/components/common/Drawer.svelte';
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
import LockClosed from '$lib/components/icons/LockClosed.svelte';
@@ -63,6 +70,7 @@
let showAddWebpageModal = false;
let showAddTextContentModal = false;
let showNewDirectoryModal = false;
let showSyncConfirmModal = false;
let showAccessControlModal = false;
@@ -101,6 +109,15 @@
let fileItems = null;
let fileItemsTotal = null;
// Directory state
let currentDirectoryId: string | null = null;
let directoryItems = [];
let breadcrumbs = [];
let showDeleteDirectoryConfirm = false;
let pendingDeleteDirectoryId: string | null = null;
let deleteDirectoryContents = true;
const reset = () => {
currentPage = 1;
};
@@ -156,7 +173,8 @@
viewOption,
sortKey,
direction,
currentPage
currentPage,
currentDirectoryId
).catch(() => {
return null;
});
@@ -164,6 +182,8 @@
if (res) {
fileItems = res.items;
fileItemsTotal = res.total;
directoryItems = res.directories ?? [];
breadcrumbs = res.breadcrumbs ?? [];
}
return res;
};
@@ -531,7 +551,12 @@
};
const addFileHandler = async (fileId) => {
const res = await addFileToKnowledgeById(localStorage.token, id, fileId).catch((e) => {
const res = await addFileToKnowledgeById(
localStorage.token,
id,
fileId,
currentDirectoryId
).catch((e) => {
toast.error(`${e}`);
return null;
});
@@ -545,6 +570,88 @@
}
};
// Directory handlers
const navigateToDirectory = (directoryId: string | null) => {
currentDirectoryId = directoryId;
currentPage = 1;
selectedFileId = null;
selectedFile = null;
getItemsPage();
};
const createDirectoryHandler = async (name: string) => {
const res = await createKnowledgeDirectory(
localStorage.token,
knowledge.id,
name,
currentDirectoryId
).catch((e) => {
toast.error(`${e}`);
return null;
});
if (res) {
toast.success($i18n.t('Directory created.'));
getItemsPage();
}
};
const renameDirectoryHandler = async (dirId: string, name: string) => {
const res = await updateKnowledgeDirectory(localStorage.token, knowledge.id, dirId, {
name
}).catch((e) => {
toast.error(`${e}`);
return null;
});
if (res) {
toast.success($i18n.t('Directory renamed.'));
getItemsPage();
}
};
const confirmDeleteDirectory = (dirId: string) => {
pendingDeleteDirectoryId = dirId;
showDeleteDirectoryConfirm = true;
};
const deleteDirectoryHandler = async (moveFiles: boolean) => {
if (!pendingDeleteDirectoryId) return;
const res = await deleteKnowledgeDirectory(
localStorage.token,
knowledge.id,
pendingDeleteDirectoryId,
moveFiles
).catch((e) => {
toast.error(`${e}`);
return null;
});
if (res) {
toast.success($i18n.t('Directory deleted.'));
getItemsPage();
}
pendingDeleteDirectoryId = null;
};
const moveFileToDirectoryHandler = async (fileId: string, directoryId: string | null) => {
const res = await moveFileInKnowledge(
localStorage.token,
knowledge.id,
fileId,
directoryId
).catch((e) => {
toast.error(`${e}`);
return null;
});
if (res) {
toast.success($i18n.t('File moved.'));
getItemsPage();
}
};
const deleteFileHandler = async (fileId) => {
try {
console.log('Starting file deletion process for:', fileId);
@@ -806,6 +913,13 @@
}}
/>
<NewDirectoryModal
bind:show={showNewDirectoryModal}
on:submit={(e) => {
createDirectoryHandler(e.detail.name);
}}
/>
<input
id="files-input"
bind:files={inputFiles}
@@ -942,6 +1056,8 @@
onUpload={(data) => {
if (data.type === 'directory') {
uploadDirectoryHandler();
} else if (data.type === 'new_directory') {
showNewDirectoryModal = true;
} else if (data.type === 'web') {
showAddWebpageModal = true;
} else if (data.type === 'text') {
@@ -959,6 +1075,12 @@
</div>
</div>
{#if currentDirectoryId !== null}
<div class="px-5 -mt-1 pb-2">
<KnowledgeBreadcrumbs rootLabel={knowledge.name} {breadcrumbs} onNavigate={(dirId) => navigateToDirectory(dirId)} />
</div>
{/if}
<div class="px-3 flex justify-between">
<div
class="flex w-full bg-transparent overflow-x-auto scrollbar-none"
@@ -1020,10 +1142,11 @@
<div class="flex-1 flex">
<div class=" flex flex-col w-full space-x-2 rounded-lg h-full">
<div class="w-full h-full flex flex-col min-h-full">
{#if fileItems.length > 0}
{#if fileItems.length > 0 || directoryItems.length > 0}
<div class=" flex overflow-y-auto h-full w-full scrollbar-hidden text-xs">
<Files
files={fileItems}
directories={directoryItems}
{knowledge}
{selectedFileId}
onClick={(fileId) => {
@@ -1044,6 +1167,11 @@
deleteFileHandler(fileId);
}}
onNavigateDirectory={(dirId) => navigateToDirectory(dirId)}
onRenameDirectory={(id, name) => renameDirectoryHandler(id, name)}
onDeleteDirectory={(id) => confirmDeleteDirectory(id)}
onMoveFileToDirectory={(fileId, dirId) =>
moveFileToDirectoryHandler(fileId, dirId)}
/>
</div>
@@ -1133,3 +1261,26 @@
<Spinner className="size-5" />
{/if}
</div>
<ConfirmDialog
bind:show={showDeleteDirectoryConfirm}
title={$i18n.t('Delete directory?')}
on:confirm={() => {
deleteDirectoryHandler(!deleteDirectoryContents);
}}
on:cancel={() => {
pendingDeleteDirectoryId = null;
}}
>
<div class="text-sm text-gray-700 dark:text-gray-300 flex-1 line-clamp-3 mb-2">
{$i18n.t(`Are you sure you want to delete this directory?`)}
</div>
<div class="flex items-center gap-1.5">
<input type="checkbox" bind:checked={deleteDirectoryContents} />
<div class="text-xs text-gray-500">
{$i18n.t('Delete all contents inside this directory')}
</div>
</div>
</ConfirmDialog>