"use client"; import { useCallback, useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { useAuth } from "@/features/agent/hooks/useAuth"; import { ResponsiveLayout } from "@/components/layout"; import { fetchKnowledgeBases, createKnowledgeBase, updateKnowledgeBase, updateKnowledgeBaseRAGEnabled, deleteKnowledgeBase, type KnowledgeBase, type CreateKnowledgeBaseRequest, type UpdateKnowledgeBaseRequest, } from "@/features/agent/services/knowledgeBaseApi"; import { fetchDocuments, createDocument, updateDocument, deleteDocument, publishDocument, unpublishDocument, type Document, type CreateDocumentRequest, type UpdateDocumentRequest, type DocumentListResult, } from "@/features/agent/services/documentApi"; import { importDocuments, importFromUrls, type ImportResult, } from "@/features/agent/services/importApi"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Switch } from "@/components/ui/switch"; import { Plus, Edit, Trash2, Search, FileText, Upload, Link as LinkIcon, BookOpen, CheckCircle2, XCircle, Loader2, ChevronLeft, ChevronRight, } from "lucide-react"; import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/hooks/useToast"; export default function KnowledgePage(props: any = {}) { const { embedded = false } = props; const router = useRouter(); const { agent } = useAuth(); // 知识库状态 const [knowledgeBases, setKnowledgeBases] = useState([]); const [selectedKnowledgeBase, setSelectedKnowledgeBase] = useState(null); const [loadingKBs, setLoadingKBs] = useState(true); // 文档状态 const [documents, setDocuments] = useState([]); const [documentResult, setDocumentResult] = useState(null); const [loadingDocs, setLoadingDocs] = useState(false); const [searchKeyword, setSearchKeyword] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [currentPage, setCurrentPage] = useState(1); const pageSize = 20; // 对话框状态 const [createKBDialogOpen, setCreateKBDialogOpen] = useState(false); const [editKBDialogOpen, setEditKBDialogOpen] = useState(false); const [deleteKBDialogOpen, setDeleteKBDialogOpen] = useState(false); const [createDocDialogOpen, setCreateDocDialogOpen] = useState(false); const [editDocDialogOpen, setEditDocDialogOpen] = useState(false); const [deleteDocDialogOpen, setDeleteDocDialogOpen] = useState(false); const [importDialogOpen, setImportDialogOpen] = useState(false); const [importTab, setImportTab] = useState<"file" | "url">("file"); const [selectedDocument, setSelectedDocument] = useState(null); // 表单状态 const [submitting, setSubmitting] = useState(false); const [createKBForm, setCreateKBForm] = useState({ name: "", description: "", }); const [editKBForm, setEditKBForm] = useState({}); const [createDocForm, setCreateDocForm] = useState({ knowledge_base_id: 0, title: "", content: "", summary: "", type: "document", status: "draft", }); const [editDocForm, setEditDocForm] = useState({}); const [importUrls, setImportUrls] = useState(""); const [importFiles, setImportFiles] = useState([]); // 加载知识库列表(不依赖 selectedKnowledgeBase,避免选中后反复触发 effect 导致疯狂刷新) const loadKnowledgeBases = useCallback(async () => { setLoadingKBs(true); try { const data = await fetchKnowledgeBases(); setKnowledgeBases(data); } catch (error) { console.error("加载知识库列表失败:", error); toast.error((error as Error).message || "加载知识库列表失败"); } finally { setLoadingKBs(false); } }, []); // 加载文档列表 const loadDocuments = useCallback(async () => { if (!selectedKnowledgeBase) { setDocuments([]); setDocumentResult(null); return; } setLoadingDocs(true); try { const status = statusFilter === "all" ? undefined : statusFilter; const result = await fetchDocuments( selectedKnowledgeBase.id, currentPage, pageSize, searchKeyword || undefined, status ); setDocumentResult(result); setDocuments(result.documents ?? []); } catch (error) { console.error("加载文档列表失败:", error); toast.error((error as Error).message || "加载文档列表失败"); setDocuments([]); setDocumentResult(null); } finally { setLoadingDocs(false); } }, [selectedKnowledgeBase, currentPage, searchKeyword, statusFilter]); // 初始加载 useEffect(() => { loadKnowledgeBases(); }, [loadKnowledgeBases]); // 当选择知识库或搜索条件变化时,重新加载文档 useEffect(() => { setCurrentPage(1); // 切换知识库或搜索时重置页码 loadDocuments(); }, [loadDocuments]); // 选择知识库 const handleSelectKnowledgeBase = (kb: KnowledgeBase) => { setSelectedKnowledgeBase(kb); setSearchKeyword(""); setStatusFilter("all"); setCurrentPage(1); }; // 创建知识库 const handleCreateKB = async () => { if (!createKBForm.name.trim()) { toast.error("知识库名称不能为空"); return; } setSubmitting(true); try { await createKnowledgeBase(createKBForm); setCreateKBDialogOpen(false); setCreateKBForm({ name: "", description: "" }); await loadKnowledgeBases(); toast.success("创建成功"); } catch (error) { toast.error((error as Error).message || "创建知识库失败"); } finally { setSubmitting(false); } }; // 打开编辑知识库对话框 const handleOpenEditKB = (kb: KnowledgeBase) => { setEditKBForm({ name: kb.name, description: kb.description, }); setSelectedKnowledgeBase(kb); setEditKBDialogOpen(true); }; // 更新知识库 const handleUpdateKB = async () => { if (!selectedKnowledgeBase) return; setSubmitting(true); try { await updateKnowledgeBase(selectedKnowledgeBase.id, editKBForm); setEditKBDialogOpen(false); await loadKnowledgeBases(); toast.success("更新成功"); } catch (error) { toast.error((error as Error).message || "更新知识库失败"); } finally { setSubmitting(false); } }; // 打开删除知识库对话框 const handleOpenDeleteKB = (kb: KnowledgeBase) => { setSelectedKnowledgeBase(kb); setDeleteKBDialogOpen(true); }; // 删除知识库 const handleDeleteKB = async () => { if (!selectedKnowledgeBase) return; setSubmitting(true); try { await deleteKnowledgeBase(selectedKnowledgeBase.id); setDeleteKBDialogOpen(false); setSelectedKnowledgeBase(null); await loadKnowledgeBases(); toast.success("删除成功"); } catch (error) { toast.error((error as Error).message || "删除知识库失败"); } finally { setSubmitting(false); } }; // 打开创建文档对话框 const handleOpenCreateDoc = () => { if (!selectedKnowledgeBase) { toast.error("请先选择知识库"); return; } setCreateDocForm({ knowledge_base_id: selectedKnowledgeBase.id, title: "", content: "", summary: "", type: "document", status: "draft", }); setCreateDocDialogOpen(true); }; // 创建文档 const handleCreateDoc = async () => { if (!createDocForm.title.trim() || !createDocForm.content.trim()) { toast.error("标题和内容不能为空"); return; } setSubmitting(true); try { await createDocument(createDocForm); setCreateDocDialogOpen(false); setCreateDocForm({ knowledge_base_id: selectedKnowledgeBase?.id || 0, title: "", content: "", summary: "", type: "document", status: "draft", }); await loadDocuments(); toast.success("创建成功"); } catch (error) { toast.error((error as Error).message || "创建文档失败"); } finally { setSubmitting(false); } }; // 打开编辑文档对话框 const handleOpenEditDoc = (doc: Document) => { setSelectedDocument(doc); setEditDocForm({ title: doc.title, content: doc.content, summary: doc.summary, type: doc.type, status: doc.status, }); setEditDocDialogOpen(true); }; // 更新文档 const handleUpdateDoc = async (docId: number) => { setSubmitting(true); try { await updateDocument(docId, editDocForm); setEditDocDialogOpen(false); await loadDocuments(); toast.success("更新成功"); } catch (error) { toast.error((error as Error).message || "更新文档失败"); } finally { setSubmitting(false); } }; // 打开删除文档对话框 const handleOpenDeleteDoc = (doc: Document) => { setSelectedDocument(doc); setDeleteDocDialogOpen(true); }; // 删除文档 const handleDeleteDoc = async (docId: number) => { setSubmitting(true); try { await deleteDocument(docId); setDeleteDocDialogOpen(false); await loadDocuments(); toast.success("删除成功"); } catch (error) { toast.error((error as Error).message || "删除文档失败"); } finally { setSubmitting(false); } }; // 发布文档 const handlePublishDoc = async (docId: number) => { try { await publishDocument(docId); await loadDocuments(); toast.success("发布成功"); } catch (error) { toast.error((error as Error).message || "发布文档失败"); } }; // 取消发布文档 const handleUnpublishDoc = async (docId: number) => { try { await unpublishDocument(docId); await loadDocuments(); toast.success("取消发布成功"); } catch (error) { toast.error((error as Error).message || "取消发布文档失败"); } }; // 导入文件 const handleImportFiles = async () => { if (!selectedKnowledgeBase) { toast.error("请先选择知识库"); return; } if (importFiles.length === 0) { toast.error("请选择要导入的文件"); return; } setSubmitting(true); try { const result: ImportResult = await importDocuments(selectedKnowledgeBase.id, importFiles); const errMsg = result.errors?.length ? result.errors[0] : ""; if (result.failed_count > 0 && result.success_count === 0) { toast.error(errMsg || `导入失败:${result.failed_count} 个文件未成功`); } else if (result.failed_count > 0) { toast.success(`导入完成:成功 ${result.success_count},失败 ${result.failed_count}${errMsg ? `(${errMsg})` : ""}`); } else { toast.success(`导入完成:成功 ${result.success_count} 个文件`); } setImportDialogOpen(false); setImportFiles([]); try { await loadDocuments(); await loadKnowledgeBases(); } catch { toast.error("导入成功,但刷新列表失败,请手动刷新页面"); } } catch (error) { toast.error((error as Error).message || "导入文档失败"); } finally { setSubmitting(false); } }; // 导入 URL const handleImportUrls = async () => { if (!selectedKnowledgeBase) { toast.error("请先选择知识库"); return; } const urls = importUrls .split("\n") .map((url) => url.trim()) .filter((url) => url.length > 0); if (urls.length === 0) { toast.error("请输入至少一个 URL"); return; } setSubmitting(true); try { const result: ImportResult = await importFromUrls({ knowledge_base_id: selectedKnowledgeBase.id, urls, }); const errMsg = result.errors?.length ? result.errors[0] : ""; if (result.failed_count > 0 && result.success_count === 0) { toast.error(errMsg || `导入失败:${result.failed_count} 个 URL 未成功`); } else if (result.failed_count > 0) { toast.success(`导入完成:成功 ${result.success_count},失败 ${result.failed_count}${errMsg ? `(${errMsg})` : ""}`); } else { toast.success(`导入完成:成功 ${result.success_count} 个 URL`); } setImportDialogOpen(false); setImportUrls(""); try { await loadDocuments(); await loadKnowledgeBases(); } catch { toast.error("导入成功,但刷新列表失败,请手动刷新页面"); } } catch (error) { toast.error((error as Error).message || "导入 URL 失败"); } finally { setSubmitting(false); } }; // 格式化时间 const formatTime = (dateStr: string) => { const date = new Date(dateStr); return date.toLocaleString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); }; // 获取状态标签 const getStatusBadge = (status: string) => { switch (status) { case "published": return ( 已发布 ); case "draft": return ( 草稿 ); default: return ( {status} ); } }; // 获取向量化状态标签 const getEmbeddingStatusBadge = (status: string) => { switch (status) { case "completed": return ( 已完成 ); case "processing": return ( 处理中 ); case "failed": return ( 失败 ); case "pending": default: return ( 待处理 ); } }; // 构建头部内容 const headerContent = (

知识库管理

{!embedded && ( )}
); // 构建主内容区 const mainContent = (
{/* 左侧:知识库列表 */}
{loadingKBs ? (
加载中...
) : knowledgeBases.length === 0 ? (
暂无知识库
) : (
{knowledgeBases.map((kb) => ( handleSelectKnowledgeBase(kb)} >

{kb.name}

{kb.description && (

{kb.description}

)}
{kb.document_count} 篇文档
))}
)}
{/* 右侧:文档列表 */}
{selectedKnowledgeBase ? ( <> {/* 文档列表头部 */}

{selectedKnowledgeBase.name}

{ try { const updated = await updateKnowledgeBaseRAGEnabled(selectedKnowledgeBase.id, checked); setSelectedKnowledgeBase((prev) => (prev?.id === updated.id ? { ...prev, rag_enabled: updated.rag_enabled } : prev)); setKnowledgeBases((prev) => prev.map((kb) => (kb.id === updated.id ? { ...kb, rag_enabled: updated.rag_enabled } : kb))); } catch (e) { toast.error((e as Error).message || "更新失败"); } }} />
{/* 搜索和筛选 */}
setSearchKeyword(e.target.value)} className="pl-10" />
{/* 文档列表 */}
{loadingDocs ? (
加载中...
) : (documents?.length ?? 0) === 0 ? (
{searchKeyword || statusFilter !== "all" ? "没有找到匹配的文档" : "暂无文档"}
) : (
{(documents ?? []).map((doc) => (

{doc.title}

{getStatusBadge(doc.status)} {getEmbeddingStatusBadge(doc.embedding_status)}
{doc.summary && (

{doc.summary}

)}
类型: {doc.type} 创建时间: {formatTime(doc.created_at)}
{doc.status === "published" ? ( ) : ( )}
))}
)} {/* 分页 */} {documentResult && documentResult.total_page > 1 && (
第 {currentPage} / {documentResult.total_page} 页,共 {documentResult.total} 条
)}
) : (
请选择一个知识库
)}
); // 如果是嵌入模式,只返回内容,不包含 ResponsiveLayout if (embedded) { return ( <>
{headerContent} {mainContent}
{/* 对话框 */} {/* 创建知识库对话框 */} 创建知识库 填写知识库名称和描述
setCreateKBForm({ ...createKBForm, name: e.target.value }) } placeholder="请输入知识库名称" />