import { useEffect, useState, useRef, useCallback, type FC } from 'react'; import { useNavigate, useParams, useLocation } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Switch, Button, Dropdown, Space, Modal, message, Radio } from 'antd'; import type { MenuProps } from 'antd'; import SearchInput from '@/components/SearchInput' import Table, { type TableRef } from '@/components/Table' import type { ColumnsType } from 'antd/es/table'; import type { AnyObject } from 'antd/es/_util/type'; import { MoreOutlined, DeploymentUnitOutlined, BarsOutlined } from '@ant-design/icons'; import folderIcon from '@/assets/images/knowledgeBase/folder.png'; import textIcon from '@/assets/images/knowledgeBase/text.png'; import editIcon from '@/assets/images/knowledgeBase/edit.png'; // import blankIcon from '@/assets/images/knowledgeBase/blankDocument.png'; // import imageIcon from '@/assets/images/knowledgeBase/image.png' import { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase'; import { type CreateModalRef, type KnowledgeBaseListItem, type RecallTestDrawerRef, type CreateFolderModalRef, type CreateSetModalRef, type ShareModalRef, type CreateDatasetModalRef, type FolderFormData, type KnowledgeBaseDocumentData, type KnowledgeBaseFormData, } from '@/views/KnowledgeBase/types'; import RecallTestDrawer from '../components/RecallTestDrawer'; import CreateFolderModal from '../components/CreateFolderModal'; import CreateContentModal from '../components/CreateContentModal'; import CreateModal from '../components/CreateModal'; import ShareModal from '../components/ShareModal'; import CreateDatasetModal from '../components/CreateDatasetModal'; import CreateImageDataset from '../components/CreateImageDataset'; import FolderTree, { type TreeNodeData } from '../components/FolderTree'; import { formatDateTime } from '@/utils/format'; import KnowledgeGraphCard from '../components/KnowledgeGraphCard'; import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager'; import './Private.css' const { confirm } = Modal // 树节点数据类型 const Private: FC = () => { const { t } = useTranslation(); const [messageApi, contextHolder] = message.useMessage(); const navigate = useNavigate(); const location = useLocation(); const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>(); const [parentId, setParentId] = useState(knowledgeBaseId); const [loading, setLoading] = useState(false); const tableRef = useRef(null); const [tableApi, setTableApi] = useState(undefined); const recallTestDrawerRef = useRef(null); const createFolderModalRef = useRef(null); const createImageDataset = useRef(null) const createContentModalRef = useRef(null); const [knowledgeBase, setKnowledgeBase] = useState(null); const [folder, setFolder] = useState({ kb_id:knowledgeBaseId ?? '', parent_id:parentId ?? '' }); const [query, setQuery] = useState>({ orderby: 'created_at', desc: true }); const modalRef = useRef(null) const shareModalRef = useRef(null); const datasetModalRef = useRef(null); const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0); const [autoExpandPath, setAutoExpandPath] = useState>([]); const [isGraph, setIsGraph] = useState(false); const { updateBreadcrumbs } = useBreadcrumbManager({ breadcrumbType: 'detail', // 不提供 onKnowledgeBaseMenuClick,让它使用默认的导航行为(返回列表页面) onKnowledgeBaseFolderClick: useCallback((folderId: string, folderPath: Array<{ id: string; name: string }>) => { // 点击文件夹面包屑时,导航到对应文件夹 setParentId(folderId); setFolderPath(folderPath); setSelectedKeys([folderId]); setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: folderId }); // 确保query对象发生变化,触发表格刷新 setQuery({ orderby: 'created_at', desc: true, parent_id: folderId, _timestamp: Date.now() }); // 确保API URL正确设置 setTableApi(`/documents/${knowledgeBaseId}/documents`); // 手动触发表格刷新,确保数据更新 setTimeout(() => { tableRef.current?.loadData(); }, 100); }, [knowledgeBaseId]) }); const [folderPath, setFolderPath] = useState([]); const [selectedKeys, setSelectedKeys] = useState([]); const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState([]); const fetchKnowledgeBaseDetail = async (id: string) => { setLoading(true); try { const res = await getKnowledgeBaseDetail(id); // 将 KnowledgeBase 转换为 KnowledgeBaseListItem const listItem = res as unknown as KnowledgeBaseListItem; setKnowledgeBase(listItem); } finally { setLoading(false); } }; useEffect(() => { if (knowledgeBaseId) { let url = `/documents/${knowledgeBaseId}/documents`; setTableApi(url); fetchKnowledgeBaseDetail(knowledgeBaseId); // 立即设置基础面包屑,确保不会显示其他页面的面包屑 updateBreadcrumbs({ knowledgeBaseFolderPath, knowledgeBase: { id: knowledgeBaseId, name: '加载中...', type: 'knowledgeBase' }, documentFolderPath: folderPath, }); } }, [knowledgeBaseId]); // 更新面包屑 useEffect(() => { if (knowledgeBase) { updateBreadcrumbs({ knowledgeBaseFolderPath, knowledgeBase: { id: knowledgeBase.id, name: knowledgeBase.name, type: 'knowledgeBase' }, documentFolderPath: folderPath, }); } }, [knowledgeBase, knowledgeBaseFolderPath, folderPath, updateBreadcrumbs]); // 监听 tableApi 变化,自动刷新表格数据 useEffect(() => { if (tableApi) { tableRef.current?.loadData(); } }, [tableApi]); // 监听 query 变化,确保表格数据更新 useEffect(() => { if (tableApi && query._timestamp) { // 当 query 中有 _timestamp 时,说明是通过面包屑或其他方式触发的更新 tableRef.current?.loadData(); } }, [query._timestamp, tableApi]); // 监听 location state 变化 useEffect(() => { const state = location.state as { refresh?: boolean; timestamp?: number; fromKnowledgeBaseList?: boolean; knowledgeBaseFolderPath?: BreadcrumbItem[]; parentId?: string; navigateToDocumentFolder?: string; documentFolderPath?: BreadcrumbItem[]; resetToRoot?: boolean; } | null; if (state?.refresh) { tableRef.current?.loadData(); // 清除 state,避免重复刷新 navigate(location.pathname, { replace: true, state: {} }); } // 如果是从知识库列表页跳转过来的,设置知识库文件夹路径 if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) { setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath); } // 如果需要重置到根目录(回到初始状态) if (state?.resetToRoot) { // 重置所有状态到初始状态,和页面初始化保持一致 setParentId(knowledgeBaseId); setFolderPath([]); setSelectedKeys([]); setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: knowledgeBaseId ?? '' }); setQuery({ orderby: 'created_at', desc: true, _timestamp: Date.now() // 添加时间戳确保query对象发生变化,触发API调用 }); // 重新设置API URL const rootUrl = `/documents/${knowledgeBaseId}/documents`; setTableApi(rootUrl); // 清除自动展开路径 setAutoExpandPath([]); // 刷新文件夹树 - 使用延迟确保状态重置完成后再刷新 setTimeout(() => { setFolderTreeRefreshKey((prev) => prev + 1); }, 100); // 手动触发表格刷新,确保数据更新 setTimeout(() => { tableRef.current?.loadData(); }, 200); // 清除 state,避免重复处理 navigate(location.pathname, { replace: true, state: {} }); } // 如果是从文档详情页返回,恢复文档文件夹路径 if (state?.navigateToDocumentFolder && state?.documentFolderPath) { setFolderPath(state.documentFolderPath); setParentId(state.navigateToDocumentFolder); setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: state.navigateToDocumentFolder }); setQuery(prevQuery => ({ ...prevQuery, parent_id: state.navigateToDocumentFolder, _timestamp: Date.now() })); setTableApi(`/documents/${knowledgeBaseId}/documents`); setSelectedKeys([state.navigateToDocumentFolder]); // 设置自动展开路径,让FolderTree自动展开到对应位置 setAutoExpandPath(state.documentFolderPath); // 手动触发表格刷新 setTimeout(() => { tableRef.current?.loadData(); }, 100); // 清除自动展开路径,避免重复触发(延迟清除,确保FolderTree处理完成) setTimeout(() => { setAutoExpandPath([]); }, 2000); } }, [location.state, knowledgeBaseId, navigate, location.pathname]); // 处理树节点选择 const onSelect = (keys: React.Key[]) => { if (!keys.length) { // 如果没有选中任何节点,回到根目录(初始状态) setParentId(knowledgeBaseId); setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: knowledgeBaseId ?? '' }); setQuery({ orderby: 'created_at', desc: true, _timestamp: Date.now() // 添加时间戳确保query对象发生变化 }); setSelectedKeys([]); return; } if (!folder) return; const f = { ...folder, parent_id: String(keys[0]), } setQuery({ ...query, parent_id: String(keys[0]), _timestamp: Date.now() // 添加时间戳确保query对象发生变化 }) let url = `/documents/${knowledgeBaseId}/documents`; setTableApi(url); setParentId(String(keys[0])) setFolder(f) setSelectedKeys(keys) }; // 处理文件夹路径变化 const handleFolderPathChange = (path: Array<{ id: string; name: string }>) => { setFolderPath(path); }; // 处理树节点展开 const onExpand = (_expandedKeys: React.Key[], _info: any) => { // 展开节点时不需要特殊处理 }; // create / import list const createItems: MenuProps['items'] = [ { key: '1', icon: dataset, label: t('knowledgeBase.folder'), onClick: () => { let f: FolderFormData | null = null; f = { kb_id: knowledgeBase?.id ?? '', parent_id:folder?.parent_id ?? knowledgeBase?.id ?? '', } // setFolder(f); createFolderModalRef?.current?.handleOpen(f as FolderFormData); }, }, { key: '2', icon: text, label: ({t('knowledgeBase.createA')} {t('knowledgeBase.dataset')}), onClick: () => { datasetModalRef?.current?.handleOpen(knowledgeBase?.id,folder?.parent_id ?? knowledgeBase?.id ?? ''); }, }, // { // key: '8', // icon: Custome Text, // label: t('knowledgeBase.mediaDataSet'), // onClick: () => { // createContentModalRef?.current?.handleOpen(knowledgeBase?.id ?? '', folder?.parent_id ?? knowledgeBase?.id ?? ''); // }, // }, // { // key: '3', // icon: image, // label: t('knowledgeBase.imageDataSet'), // onClick: () => { // createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '') // }, // }, // 暂时未实现 // { // key: '4', // icon: blank, // label: t('knowledgeBase.blankDataset'), // onClick: () => { // handleCreate('folder'); // 传入 type: 'folder' // }, // }, // { // key: '5', // type: 'divider', // }, // { // key: '6', // icon: import, // label: t('knowledgeBase.importTemplate'), // onClick: () => { // handleCreate('folder'); // 传入 type: 'folder' // }, // }, // { // key: '7', // icon: import, // label: t('knowledgeBase.importBackup'), // onClick: () => { // handleCreate('folder'); // 传入 type: 'folder' // }, // }, ]; // 处理开关 const onChange = (checked: boolean) => { if (!knowledgeBase) return; // 构造完整的更新数据,保留现有配置 const updateData: KnowledgeBaseFormData = { name: knowledgeBase.name, description: knowledgeBase.description, embedding_id: knowledgeBase.embedding_id, llm_id: knowledgeBase.llm_id, image2text_id: knowledgeBase.image2text_id, reranker_id: knowledgeBase.reranker_id, permission_id: knowledgeBase.permission_id, type: knowledgeBase.type, status: checked ? 1 : 0, parser_config: knowledgeBase.parser_config || { chunk_token_num: 512, delimiter: '\n', auto_keywords: 0, auto_questions: 0, html4excel: false, graphrag: { use_graphrag: false, scene_name: '', entity_types: [], method: '', resolution: false, community: false } } }; updateKnowledgeBase(knowledgeBaseId || '', updateData); console.log(`switch to ${checked}`); }; // 处理搜索 const handleSearch = (value?: string) => { setQuery({ ...query, keywords: value }) } // 处理分享 const handleShare = () => { shareModalRef?.current?.handleOpen(knowledgeBaseId,knowledgeBase); } // 处理分享回调,接收选中的数据 const handleShareCallback = (selectedData: { checkedItems: any[], selectedItem: any | null }) => { console.log('选中的数据:', selectedData); // checkedItems: 所有 checked 为 true 的数据 // selectedItem: 当前选中的项(curIndex 对应的数据) // 在这里处理分享逻辑 } const handleCreateDatasetCallback = (payload: { value: number; title: string; description: string }) => { console.log('创建数据集:', payload); } // 处理设置 const handleSetting = () => { modalRef?.current?.handleOpen(knowledgeBase, ''); } // 处理召回测试 const handleRecallTest = () => { recallTestDrawerRef?.current?.handleOpen(knowledgeBaseId); } // new / import const handelCreateOrImport = () => { } // 生成下拉菜单项(根据当前 row) const getOptMenuItems = (row: KnowledgeBaseListItem): MenuProps['items'] => [ { key: '1', label: t('knowledgeBase.rechunking'), onClick: () => { handleRechunking(row); }, }, { key: '2', label: t('knowledgeBase.download'), onClick: () => { handleDownload(row); }, }, { key: '3', label: t('knowledgeBase.delete'), onClick: () => { handleDelete(row); }, } ]; const handleRechunking = (item: KnowledgeBaseListItem) => { if (!knowledgeBaseId) return; const document = item as unknown as KnowledgeBaseDocumentData; const targetFileId = document?.id || document?.file_id; navigate(`/knowledge-base/${knowledgeBaseId}/create-dataset`, { state: { source: 'local', knowledgeBaseId, parentId: parentId ?? knowledgeBaseId, startStep: 'parameterSettings', fileId: targetFileId, }, }); } const handleDownload = (item: KnowledgeBaseListItem) => { const document = item as unknown as KnowledgeBaseDocumentData; const targetFileId = document?.file_id ?? ''; const fileName = document?.file_name ?? ''; downloadFile(targetFileId, fileName); } const handleDelete = (item: any) => { confirm({ title: t('common.deleteWarning'), content: t('common.deleteWarningContent', { content: item.file_name }), onOk: () => { deleteDocument(item.id) .then(() => { messageApi.success(t('common.deleteSuccess')); // 刷新表格数据 tableRef.current?.loadData(); }) .catch((err: any) => { console.log('删除失败', err); }); }, onCancel: () => { console.log('取消删除'); }, }); } // 表格列配置 const columns: ColumnsType = [ { title: t('knowledgeBase.name'), dataIndex: 'file_name', key: 'file_name', render: (text: string, record: AnyObject) => { const document = record as KnowledgeBaseDocumentData; return ( { if (knowledgeBaseId && document.id) { navigate(`/knowledge-base/${knowledgeBaseId}/DocumentDetails`,{ state: { documentId: document.id, parentId: parentId ?? knowledgeBaseId, // 传递面包屑信息 breadcrumbPath: { knowledgeBaseFolderPath, knowledgeBase: { id: knowledgeBase?.id || knowledgeBaseId, name: knowledgeBase?.name || '', type: 'knowledgeBase' }, documentFolderPath: folderPath, document: { id: document.id, name: document.file_name || '', type: 'document' } } }, }); } }} > {text} ); }, }, { title: t('knowledgeBase.status'), dataIndex: 'progress', key: 'progress', render: (value: string | number) => { return ( {value === 1 ? t('knowledgeBase.completed') : value === 0 ? t('knowledgeBase.pending') : t('knowledgeBase.processing')} ); } }, { title: t('knowledgeBase.processingMode'), dataIndex: 'parser_id', key: 'parser_id', }, { title: t('knowledgeBase.dataSize'), dataIndex: 'file_size', key: 'file_size', }, { title: t('knowledgeBase.createUpdateTime'), dataIndex: 'created_at', key: 'created_at', render:(value:string) => { return( {formatDateTime(value,'YYYY-MM-DD HH:mm:ss')} ) } }, { title: t('common.operation'), key: 'action', fixed: 'right', width: 100, render: (_, record) => ( ), }, ]; // 刷新列表数据 if (loading) { return
加载中...
; } if (!knowledgeBase) { return
知识库不存在
; } const refreshDirectoryTree = async () => { // 先刷新知识库详情,确保数据是最新的 if (knowledgeBase?.id) { await fetchKnowledgeBaseDetail(knowledgeBase.id); } // 添加短暂延迟,确保后端数据已经完全更新 await new Promise(resolve => setTimeout(resolve, 300)); // 然后刷新文件夹树 setFolderTreeRefreshKey((prev) => prev + 1); // 确保 folder 状态正确设置 if (!folder) { setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: parentId ?? knowledgeBaseId ?? '' }); } } const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => { if (!nodes || nodes.length === 0) { // 如果没有节点,设置folder为null(这会隐藏FolderTree) setFolder(null); } else { // 如果有节点且 folder 为 null,重新设置 folder if (!folder) { setFolder({ kb_id: knowledgeBaseId ?? '', parent_id: parentId ?? knowledgeBaseId ?? '' }); } } }; const handleEditFolder = () => { const f = { id:knowledgeBase.id, parent_id:knowledgeBase.parent_id, kb_id:knowledgeBase.id, folder_name:knowledgeBase.name } // setFolder(f) createFolderModalRef?.current?.handleOpen(f,'edit'); } const handleRefreshTable = () => { // 刷新表格数据 tableRef.current?.loadData(); } return ( <> {contextHolder}
{folder && (
)}

{knowledgeBase.name}

edit {t('knowledgeBase.edit')} {t('knowledgeBase.name')}
{t('knowledgeBase.created')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.created_at) || '-'} {t('knowledgeBase.updated')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.updated_at) || '-'}
{/*
*/} {/*
*/}
setIsGraph(e.target.value)}>
{isGraph ? ( modalRef.current?.handleOpen(knowledgeBase, 'rebuild')} /> ) : ( } columns={columns} rowKey="id" scrollX={1500} /> )} ); }; export default Private;