import { useCallback, useEffect, useMemo, useState } from "react"; import { App, Breadcrumb, Button, Card, Descriptions, Empty, Modal, Table, Tooltip, } from "antd"; import { DeleteOutlined, DownloadOutlined, EditOutlined, EyeOutlined, PlusOutlined } from "@ant-design/icons"; import { useNavigate, useParams } from "react-router"; import DetailHeader from "@/components/DetailHeader"; import { SearchControls } from "@/components/SearchControls"; import useFetchData from "@/hooks/useFetchData"; import { deleteKnowledgeItemByIdUsingDelete, deleteKnowledgeSetByIdUsingDelete, downloadKnowledgeItemFileUsingGet, exportKnowledgeItemsUsingGet, queryKnowledgeItemsUsingGet, queryKnowledgeSetByIdUsingGet, } from "../knowledge-management.api"; import { knowledgeContentTypeOptions, knowledgeSourceTypeOptions, knowledgeStatusMap, mapKnowledgeItem, KnowledgeItemView, } from "../knowledge-management.const"; import { KnowledgeItem, KnowledgeSet, KnowledgeContentType, KnowledgeSourceType, KnowledgeStatusType, } from "../knowledge-management.model"; import CreateKnowledgeSet from "../components/CreateKnowledgeSet"; import KnowledgeItemEditor from "../components/KnowledgeItemEditor"; import ImportKnowledgeItemsDialog from "../components/ImportKnowledgeItemsDialog"; import { formatDate } from "@/utils/unit"; import { PREVIEW_TEXT_MAX_LENGTH, resolvePreviewFileType, truncatePreviewText, type PreviewFileType, } from "@/utils/filePreview"; const PREVIEW_MAX_HEIGHT = 500; const PREVIEW_MODAL_WIDTH = { text: 800, media: 700, }; const PREVIEW_TEXT_FONT_SIZE = 12; const PREVIEW_TEXT_PADDING = 12; const PREVIEW_AUDIO_PADDING = 40; const KnowledgeSetDetail = () => { const navigate = useNavigate(); const { message } = App.useApp(); const { id } = useParams<{ id: string }>(); const [knowledgeSet, setKnowledgeSet] = useState(null); const [showEdit, setShowEdit] = useState(false); const [itemEditorOpen, setItemEditorOpen] = useState(false); const [currentItem, setCurrentItem] = useState(null); const [readItemId, setReadItemId] = useState(null); const [readModalOpen, setReadModalOpen] = useState(false); const [readContent, setReadContent] = useState(""); const [readTitle, setReadTitle] = useState(""); const [previewVisible, setPreviewVisible] = useState(false); const [previewContent, setPreviewContent] = useState(""); const [previewFileName, setPreviewFileName] = useState(""); const [previewFileType, setPreviewFileType] = useState("text"); const [previewMediaUrl, setPreviewMediaUrl] = useState(""); const [previewLoadingItemId, setPreviewLoadingItemId] = useState(null); const fetchKnowledgeSet = useCallback(async () => { if (!id) return; const { data } = await queryKnowledgeSetByIdUsingGet(id); setKnowledgeSet(data); }, [id]); useEffect(() => { fetchKnowledgeSet(); }, [fetchKnowledgeSet]); const { loading, tableData: items, searchParams, pagination, fetchData, setSearchParams, handleFiltersChange, handleKeywordChange, } = useFetchData( (params) => (id ? queryKnowledgeItemsUsingGet(id, params) : Promise.resolve({ data: [] })), mapKnowledgeItem, 30000, false, [], 0 ); const isReadOnly = knowledgeSet?.status === KnowledgeStatusType.ARCHIVED || knowledgeSet?.status === KnowledgeStatusType.DEPRECATED; const handleDeleteSet = async () => { if (!knowledgeSet) return; await deleteKnowledgeSetByIdUsingDelete(knowledgeSet.id); message.success("知识集已删除"); navigate("/data/knowledge-management"); }; const handleDeleteItem = async (item: KnowledgeItemView) => { if (!id) return; await deleteKnowledgeItemByIdUsingDelete(id, item.id); message.success("知识条目已删除"); fetchData(); }; const handleExportItems = async () => { if (!id) return; await exportKnowledgeItemsUsingGet(id); message.success("知识条目导出成功"); }; const isReadableItem = (record: KnowledgeItemView) => { if ( record.contentType === KnowledgeContentType.FILE || record.sourceType === KnowledgeSourceType.FILE_UPLOAD ) { return false; } return ( record.contentType === KnowledgeContentType.TEXT || record.contentType === KnowledgeContentType.MARKDOWN ); }; const resolvePreviewFileName = (record: KnowledgeItemView) => { if (record.sourceFileId) { return record.sourceFileId; } if (record.content) { const segments = record.content.split("/"); const lastSegment = segments[segments.length - 1]; if (lastSegment) { return lastSegment; } } return "文件"; }; const handlePreviewItemFile = async (record: KnowledgeItemView) => { if (!id) return; const fileName = resolvePreviewFileName(record); const fileType = resolvePreviewFileType(fileName); if (!fileType) { message.warning("不支持预览该文件类型"); return; } const previewUrl = `/api/data-management/knowledge-sets/${id}/items/${record.id}/preview`; setPreviewFileName(fileName); setPreviewFileType(fileType); setPreviewContent(""); setPreviewMediaUrl(""); if (fileType === "text") { setPreviewLoadingItemId(record.id); try { const response = await fetch(previewUrl); if (!response.ok) { throw new Error("下载失败"); } const text = await response.text(); setPreviewContent(truncatePreviewText(text, PREVIEW_TEXT_MAX_LENGTH)); setPreviewVisible(true); } catch (error) { console.error("预览知识条目文件失败", error); message.error("预览失败,请稍后重试"); } finally { setPreviewLoadingItemId(null); } return; } setPreviewMediaUrl(previewUrl); setPreviewVisible(true); }; const closePreview = () => { setPreviewVisible(false); setPreviewContent(""); setPreviewMediaUrl(""); setPreviewFileName(""); setPreviewFileType("text"); }; const handleReadItem = async (record: KnowledgeItemView) => { if ( record.contentType === KnowledgeContentType.FILE || record.sourceType === KnowledgeSourceType.FILE_UPLOAD ) { message.info("该条目为文件,请使用下载查看"); return; } setReadItemId(record.id); setReadTitle("知识条目"); if (!record.sourceDatasetId || !record.sourceFileId) { const content = record.content || ""; setReadContent(truncatePreviewText(content, PREVIEW_TEXT_MAX_LENGTH)); setReadModalOpen(true); setReadItemId(null); return; } const fileUrl = `/api/data-management/datasets/${record.sourceDatasetId}/files/${record.sourceFileId}/download`; try { const response = await fetch(fileUrl); if (!response.ok) { throw new Error("下载失败"); } const text = await response.text(); setReadContent(truncatePreviewText(text, PREVIEW_TEXT_MAX_LENGTH)); setReadModalOpen(true); } catch (error) { console.error("读取知识条目失败", error); message.error("读取失败,请稍后重试"); } finally { setReadItemId(null); } }; const handleDownloadItem = async (record: KnowledgeItemView) => { if (!id) return; try { await downloadKnowledgeItemFileUsingGet(id, record.id, record.sourceFileId); } catch (error) { console.error("下载知识条目文件失败", error); message.error("下载失败,请稍后重试"); } }; const statusMeta = knowledgeSet?.status ? knowledgeStatusMap[knowledgeSet.status] : undefined; const statistics = useMemo( () => [ { key: "items", icon: , label: "条目数", value: pagination.total || 0, }, { key: "updated", icon: , label: "更新时间", value: knowledgeSet?.updatedAt ? formatDate(knowledgeSet.updatedAt) : "-", }, ], [pagination.total, knowledgeSet?.updatedAt] ); const itemColumns = [ { title: "文件名", dataIndex: "sourceFileId", key: "sourceFileId", fixed: "left" as const, width: 260, ellipsis: true, render: (_: string, record: KnowledgeItemView) => { if ( record.contentType === KnowledgeContentType.FILE || record.sourceType === KnowledgeSourceType.FILE_UPLOAD ) { return resolvePreviewFileName(record); } return record.sourceFileId || "-"; }, }, { title: "类型", dataIndex: "contentType", key: "contentType", width: 120, render: (contentType: string) => knowledgeContentTypeOptions.find((opt) => opt.value === contentType)?.label || contentType, }, { title: "来源", dataIndex: "sourceType", key: "sourceType", width: 140, ellipsis: true, render: (sourceType: string) => knowledgeSourceTypeOptions.find((opt) => opt.value === sourceType)?.label || sourceType || "-", }, { title: "更新时间", dataIndex: "updatedAt", key: "updatedAt", width: 180, ellipsis: true, }, { title: "操作", key: "actions", width: 200, render: (_: unknown, record: KnowledgeItemView) => (
{isReadableItem(record) && (
), }, ]; return (
, onClick: () => setShowEdit(true), danger: false, }, { key: "export", label: "导出", icon: , onClick: handleExportItems, }, { key: "delete", label: "删除", icon: , danger: true, confirm: { title: "确认删除该知识集吗?", description: "删除后将无法恢复,请谨慎操作。", cancelText: "取消", okText: "删除", okType: "danger", onConfirm: () => handleDeleteSet(), }, }, ]} /> { setShowEdit(false); fetchKnowledgeSet(); }} onClose={() => setShowEdit(false)} /> {knowledgeSet?.domain || "-"} {knowledgeSet?.businessLine || "-"} {knowledgeSet?.owner || "-"} {knowledgeSet?.sensitivity || "-"} {knowledgeSet?.validFrom || "-"} ~ {knowledgeSet?.validTo || "-"} {knowledgeSet?.sourceType || "-"}
setSearchParams({ ...searchParams, filter: { type: [], status: [], tags: [] } })} showViewToggle={false} showReload={false} />
{items.length === 0 ? ( ) : ( )} { setItemEditorOpen(false); setCurrentItem(null); }} onSuccess={() => { setItemEditorOpen(false); setCurrentItem(null); fetchData(); }} /> 关闭 , ]} width={previewFileType === "text" ? PREVIEW_MODAL_WIDTH.text : PREVIEW_MODAL_WIDTH.media} > {previewFileType === "text" && (
            {previewContent}
          
)} {previewFileType === "image" && (
{previewFileName}
)} {previewFileType === "pdf" && (