diff --git a/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx b/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx
index 088a4bd..c0d7ec8 100644
--- a/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx
+++ b/web/app/dashboard/knowledge/_components/knowledge-directory-panel.tsx
@@ -6,6 +6,7 @@ import {
MouseSensor,
TouchSensor,
closestCenter,
+ useDroppable,
useSensor,
useSensors,
type DragEndEvent,
@@ -61,7 +62,11 @@ import {
} from "@/lib/api/admin";
import { useI18n } from "@/i18n/provider";
import { cn } from "@/lib/utils";
-import { findDirectoryParentId, moveDirectoryWithinParent } from "./knowledge-directory-sort";
+import {
+ findDirectoryParentId,
+ moveDirectoryToParent,
+ moveDirectoryWithinParent,
+} from "./knowledge-directory-sort";
type KnowledgeDirectoryPanelProps = {
knowledgeBaseId: number;
@@ -84,6 +89,7 @@ const DIRECTORY_PANEL_WIDTH_STORAGE_KEY = "knowledge-directory-panel-width";
const DIRECTORY_PANEL_MIN_WIDTH = 180;
const DIRECTORY_PANEL_MAX_WIDTH = 360;
const DIRECTORY_PANEL_DEFAULT_WIDTH = 224;
+const ROOT_DIRECTORY_DROP_ID = "knowledge-directory-root";
function rootDirectoryOptions(items: KnowledgeDirectory[]): DirectoryOption[] {
return items.map((item) => ({ value: String(item.id), label: item.name }));
@@ -305,14 +311,29 @@ export function KnowledgeDirectoryPanel({
return;
}
const activeId = Number(active.id);
+ if (!Number.isFinite(activeId)) {
+ return;
+ }
+ if (over.id === ROOT_DIRECTORY_DROP_ID) {
+ await handleDirectoryMoveToParent(activeId, 0);
+ return;
+ }
const overId = Number(over.id);
- if (!Number.isFinite(activeId) || !Number.isFinite(overId)) {
+ if (!Number.isFinite(overId)) {
return;
}
const activeParentId = findDirectoryParentId(directories, activeId);
const overParentId = findDirectoryParentId(directories, overId);
- if (activeParentId === null || overParentId === null || activeParentId !== overParentId) {
+ if (activeParentId === null || overParentId === null) {
+ return;
+ }
+ if (activeParentId !== overParentId) {
+ await handleDirectoryMoveToParent(activeId, overId);
+ return;
+ }
+ if (activeParentId === 0 && overParentId === 0 && event.delta.x > 24) {
+ await handleDirectoryMoveToParent(activeId, overId);
return;
}
@@ -341,6 +362,42 @@ export function KnowledgeDirectoryPanel({
}
}
+ async function handleDirectoryMoveToParent(activeId: number, targetParentId: number) {
+ const previousDirectories = directories;
+ const moved = moveDirectoryToParent(previousDirectories, activeId, targetParentId);
+ if (!moved.changed || !moved.item) {
+ return;
+ }
+
+ setDirectories(moved.items);
+ if (moved.parentId > 0) {
+ setExpandedIds((current) => new Set(current).add(moved.parentId));
+ }
+ setSorting(true);
+ try {
+ await updateKnowledgeDirectory({
+ id: moved.item.id,
+ knowledgeBaseId,
+ parentId: moved.parentId,
+ name: moved.item.name,
+ remark: moved.item.remark || "",
+ });
+ await updateKnowledgeDirectorySort({
+ knowledgeBaseId,
+ parentId: moved.parentId,
+ ids: moved.orderedIds,
+ });
+ toast.success(t("knowledge.directoryMoved"));
+ await loadDirectories();
+ onChanged?.();
+ } catch (error) {
+ setDirectories(previousDirectories);
+ toast.error(error instanceof Error ? error.message : t("knowledge.directoryMoveFailed"));
+ } finally {
+ setSorting(false);
+ }
+ }
+
return (
<>
onSelectDirectory(null)}
/>
-
}
- label={t("knowledge.rootContent")}
- selected={selectedDirectoryId === 0}
- onClick={() => onSelectDirectory(0)}
- />
void handleDirectoryDragEnd(event)}
>
+ onSelectDirectory(0)}
+ t={t}
+ />
item.id)}
strategy={verticalListSortingStrategy}
@@ -490,16 +546,18 @@ type DirectoryStaticRowProps = {
icon: ReactNode;
label: string;
selected: boolean;
+ draggingOver?: boolean;
onClick: () => void;
};
-function DirectoryStaticRow({ icon, label, selected, onClick }: DirectoryStaticRowProps) {
+function DirectoryStaticRow({ icon, label, selected, draggingOver, onClick }: DirectoryStaticRowProps) {
return (