@@ -227,52 +369,22 @@ export function DataSources() {
{t('clickTopRightToAddFirstDataSource')}
) : (
-
- {datasources.map((ds) => (
-
-
-
-
-
-
-
-
{ds.name}
-
{ds.type}
-
-
-
-
-
-
-
-
-
-
-
- Host
-
- {ds.config.host || parseConnectionString(ds.config.connection_string, 'host') || "Local / File"}
-
-
-
- Database
-
- {ds.config.database || parseConnectionString(ds.config.connection_string, 'database') || (ds.config.file_path ? ds.config.file_path.split('/').pop() : "-")}
-
-
-
+
+ ds.id!)}
+ strategy={rectSortingStrategy}
+ >
+
+ {datasources.map((ds) => (
+
+ ))}
- ))}
-
+
+
)}
diff --git a/frontend/src/pages/KnowledgeBases.tsx b/frontend/src/pages/KnowledgeBases.tsx
index bdbfcaf..f98ed16 100644
--- a/frontend/src/pages/KnowledgeBases.tsx
+++ b/frontend/src/pages/KnowledgeBases.tsx
@@ -4,11 +4,28 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
-import { Save, Loader2, RefreshCw, Pencil, Trash2, FileText, Plus, BookOpen } from "lucide-react";
+import { Save, Loader2, RefreshCw, Pencil, Trash2, FileText, Plus, BookOpen, GripVertical } from "lucide-react";
import { api } from "@/lib/api";
import { useProjectStore } from "@/store/projectStore";
import { Textarea } from "@/components/ui/textarea";
import { KnowledgeBaseForm, type KnowledgeBaseFormValues } from "@/components/KnowledgeBaseForm";
+import {
+ DndContext,
+ closestCenter,
+ KeyboardSensor,
+ PointerSensor,
+ useSensor,
+ useSensors
+} from '@dnd-kit/core';
+import type { DragEndEvent } from '@dnd-kit/core';
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ rectSortingStrategy,
+ useSortable
+} from '@dnd-kit/sortable';
+import { CSS } from '@dnd-kit/utilities';
interface KnowledgeBase {
id: string;
@@ -76,6 +93,25 @@ export function KnowledgeBases() {
setIsLoading(true);
try {
const data = await api.get
(`/api/v1/knowledge-bases?project_id=${currentProject.id}`);
+
+ // 从 localStorage 中恢复顺序
+ const savedOrderStr = localStorage.getItem(`knowledge_bases_order_${currentProject.id}`);
+ if (savedOrderStr) {
+ try {
+ const savedOrder = JSON.parse(savedOrderStr) as string[];
+ data.sort((a, b) => {
+ const indexA = savedOrder.indexOf(a.id);
+ const indexB = savedOrder.indexOf(b.id);
+ if (indexA === -1 && indexB === -1) return 0;
+ if (indexA === -1) return 1;
+ if (indexB === -1) return -1;
+ return indexA - indexB;
+ });
+ } catch (e) {
+ console.error("Failed to parse saved kb order", e);
+ }
+ }
+
setKnowledgeBases(data);
} catch (err: any) {
console.error(err);
@@ -247,6 +283,127 @@ export function KnowledgeBases() {
}
};
+ const sensors = useSensors(
+ useSensor(PointerSensor, {
+ activationConstraint: {
+ distance: 8,
+ },
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+
+ if (over && active.id !== over.id) {
+ setKnowledgeBases((items) => {
+ const oldIndex = items.findIndex((item) => item.id === active.id);
+ const newIndex = items.findIndex((item) => item.id === over.id);
+
+ const newItems = arrayMove(items, oldIndex, newIndex);
+
+ // 保存新的顺序到 localStorage
+ if (currentProject) {
+ localStorage.setItem(
+ `knowledge_bases_order_${currentProject.id}`,
+ JSON.stringify(newItems.map(i => i.id))
+ );
+ }
+
+ return newItems;
+ });
+ }
+ };
+
+ const SortableKbCard = ({ kb }: { kb: KnowledgeBase }) => {
+ const {
+ attributes,
+ listeners,
+ setNodeRef,
+ transform,
+ transition,
+ isDragging
+ } = useSortable({ id: kb.id });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {kb.name}
+ {!kb.is_active && (
+ Inactive
+ )}
+
+
+ {kb.documents?.length || 0} Documents
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Model
+
+ {kb.embedding_model ? kb.embedding_model.substring(0, 15) + (kb.embedding_model.length > 15 ? '...' : '') : 'Default'}
+
+
+
+ Chunking
+
+ {kb.chunk_size} / {kb.chunk_overlap}
+
+
+ {kb.description && (
+
+ {kb.description}
+
+ )}
+
+
+ );
+ };
+
return (
@@ -277,67 +434,22 @@ export function KnowledgeBases() {
{t('noKnowledgeBases')}
) : (
-
- {knowledgeBases.map((kb) => (
-
-
-
-
-
-
-
-
- {kb.name}
- {!kb.is_active && (
- Inactive
- )}
-
-
- {kb.documents?.length || 0} Documents
-
-
-
-
-
-
-
-
-
-
-
-
-
- Model
-
- {kb.embedding_model ? kb.embedding_model.substring(0, 15) + (kb.embedding_model.length > 15 ? '...' : '') : 'Default'}
-
-
-
- Chunking
-
- {kb.chunk_size} / {kb.chunk_overlap}
-
-
- {kb.description && (
-
- {kb.description}
-
- )}
-
+
+ kb.id)}
+ strategy={rectSortingStrategy}
+ >
+
+ {knowledgeBases.map((kb) => (
+
+ ))}
- ))}
-
+
+
)}