scrollContainerRefs.current[index] = el} className={clsx(`rb:relative rb:overflow-y-auto rb:overflow-x-hidden`, {
'rb:h-[calc(100vh-186px)]': isCluster,
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx
index 92059e7e..7139230d 100644
--- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx
+++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx
@@ -8,15 +8,14 @@ import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/Knowl
import type { ColumnsType } from 'antd/es/table';
import UploadFiles from '@/components/Upload/UploadFiles';
import type { UploadRequestOption } from 'rc-upload/lib/interface';
-import { uploadFile, getDocumentList, previewDocumentChunk, parseDocument, updateDocument, deleteDocument } from '@/api/knowledgeBase';
+import { uploadFile, getDocumentList, parseDocument, updateDocument, deleteDocument } from '@/api/knowledgeBase';
import exitIcon from '@/assets/images/knowledgeBase/exit.png';
-import { NoData } from '../components/noData';
-import noDataIcon from '@/assets/images/knowledgeBase/noData.png';
+
import SliderInput from '@/components/SliderInput';
import DelimiterSelector from '../components/DelimiterSelector';
const { confirm } = Modal
const { TextArea } = Input;
-import styles from '../index.module.css';
+
const style: React.CSSProperties = {
display: 'flex',
gap: 16,
@@ -71,12 +70,11 @@ const CreateDataset = () => {
const initialFileIds = locationState.fileIds ?? (locationState.fileId ? [locationState.fileId] : []);
const [current, setCurrent] = useState
(stepIndexMap[initialStepKey]);
const tableRef = useRef(null);
+
+
const [data, setData] = useState([]);
- const [chunkData, setChunkData] = useState([]);
- const [total, setTotal] = useState(0);
const [rechunkFileIds, setRechunkFileIds] = useState(initialFileIds);
- const [curSelectedFileId, setCurSelectedFileId] = useState(-1);
- const [previewLoading, setPreviewLoading] = useState(false);
+
const [pollingLoading, setPollingLoading] = useState(false);
const pollingTimerRef = useRef | null>(null);
const [delimiter, setDelimiter] = useState(undefined);
@@ -121,6 +119,7 @@ const CreateDataset = () => {
layout_recognize:'DeepDOC',
delimiter: delimiter,
chunk_token_num: blockSize,
+ auto_question: processingMethod === 'directBlock' ? 0 : 1,
}
}
updateDocument(id, params)
@@ -145,7 +144,7 @@ const CreateDataset = () => {
});
return;
}
- debugger
+
// 显示确认弹框
confirm({
@@ -168,7 +167,7 @@ const CreateDataset = () => {
const startProcessing = (autoReturnToList: boolean) => {
// 触发文档解析
rechunkFileIds.map((id) => {
- parseDocument(id);
+ parseDocument(id, {});
});
// 开启 loading
@@ -276,21 +275,7 @@ const CreateDataset = () => {
onError?.(error as Error);
});
};
- // 点击文件 预览分块
- const handlePreview = async(item: KnowledgeBaseDocumentData, index: number) => {
- setCurSelectedFileId(index);
- setPreviewLoading(true);
- try{
- const res = await previewDocumentChunk(knowledgeBaseId ?? '', item.id ?? '');
- setChunkData(res.items || []);
- setTotal(res.page.total || 0);
- console.log('res', res);
- }catch(error) {
- console.log('error', error);
- } finally {
- setPreviewLoading(false);
- }
- }
+
// 轮询检查文档处理状态
// autoReturn: 是否在所有文档完成时自动返回列表页
@@ -346,6 +331,8 @@ const CreateDataset = () => {
state: {
refresh: true,
timestamp: Date.now(), // 添加时间戳确保每次都是新的 state
+ // 保持返回到原来的文档文件夹位置
+ navigateToDocumentFolder: parentId !== knowledgeBaseId ? parentId : undefined,
},
});
} else {
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
index 1994c456..b96dff75 100644
--- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
+++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx
@@ -4,11 +4,12 @@
* @Author: yujiangping
* @Date: 2025-11-15 16:13:47
* @LastEditors: yujiangping
- * @LastEditTime: 2025-11-29 19:46:46
+ * @LastEditTime: 2025-12-12 20:02:05
*/
import { useEffect, useState, useRef, type FC } from 'react';
import { useNavigate, useParams, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager';
import { Button, Spin, message, Switch } from 'antd';
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
@@ -25,7 +26,18 @@ const DocumentDetails: FC = () => {
const navigate = useNavigate();
const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>();
const location = useLocation();
- const { documentId, parentId: locationParentId } = location.state as { documentId: string; parentId?: string };
+ const { updateBreadcrumbs } = useBreadcrumbManager({
+ breadcrumbType: 'detail'
+ });
+ const {
+ documentId,
+ parentId: locationParentId,
+ breadcrumbPath
+ } = location.state as {
+ documentId: string;
+ parentId?: string;
+ breadcrumbPath?: BreadcrumbPath;
+ };
const [loading, setLoading] = useState(false);
const [document, setDocument] = useState(null);
const [chunkList, setChunkList] = useState([]);
@@ -44,6 +56,13 @@ const DocumentDetails: FC = () => {
}
}, [documentId]);
+ // 更新面包屑
+ useEffect(() => {
+ if (breadcrumbPath) {
+ updateBreadcrumbs(breadcrumbPath);
+ }
+ }, [breadcrumbPath, updateBreadcrumbs]);
+
// 当文档加载完成且 progress === 1 时,加载分块列表
useEffect(() => {
if (document && document.progress === 1 && !isManualRefreshRef.current) {
@@ -179,7 +198,18 @@ const DocumentDetails: FC = () => {
};
const handleBack = () => {
- if (knowledgeBaseId) {
+ if (knowledgeBaseId && breadcrumbPath) {
+ // 返回到知识库详情页,并传递面包屑信息以恢复状态
+ const navigationState = {
+ fromKnowledgeBaseList: true,
+ knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
+ navigateToDocumentFolder: locationParentId,
+ documentFolderPath: breadcrumbPath.documentFolderPath,
+ timestamp: Date.now(), // 添加时间戳确保状态变化
+ };
+ navigate(`/knowledge-base/${knowledgeBaseId}/private`, { state: navigationState });
+ } else if (knowledgeBaseId) {
+ // 降级处理:直接跳转到知识库详情页
navigate(`/knowledge-base/${knowledgeBaseId}/private`);
}
};
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx
index 7c43b79f..96b1af9d 100644
--- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx
+++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx
@@ -1,5 +1,5 @@
-import { useEffect, useState, useRef, type FC } from 'react';
+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 } from 'antd';
@@ -12,26 +12,29 @@ import { MoreOutlined } 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 { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
-import type {
- CreateModalRef,
- KnowledgeBaseListItem,
- RecallTestDrawerRef,
- CreateFolderModalRef,
- CreateImageModalRef,
- ShareModalRef,
- CreateDatasetModalRef,FolderFormData,
- KnowledgeBaseDocumentData
+import {
+ type CreateModalRef,
+ type KnowledgeBaseListItem,
+ type RecallTestDrawerRef,
+ type CreateFolderModalRef,
+ type CreateSetModalRef,
+ type ShareModalRef,
+ type CreateDatasetModalRef,type FolderFormData,
+ type KnowledgeBaseDocumentData,
} 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 { useMenu } from '@/store/menu';
+
+import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
import './Private.css'
const { confirm } = Modal
// 树节点数据类型
@@ -48,7 +51,8 @@ const Private: FC = () => {
const [tableApi, setTableApi] = useState(undefined);
const recallTestDrawerRef = useRef(null);
const createFolderModalRef = useRef(null);
- const createImageDataset = useRef(null)
+ const createImageDataset = useRef(null)
+ const createContentModalRef = useRef(null);
const [knowledgeBase, setKnowledgeBase] = useState(null);
const [folder, setFolder] = useState({
kb_id:knowledgeBaseId ?? '',
@@ -56,47 +60,47 @@ const Private: FC = () => {
});
const [query, setQuery] = useState>({
orderby: 'created_at',
- desc: true,
+ desc: true
});
const modalRef = useRef(null)
const shareModalRef = useRef(null);
const datasetModalRef = useRef(null);
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
- const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
- const [folderPath, setFolderPath] = useState>([]);
+ const [autoExpandPath, setAutoExpandPath] = useState>([]);
+
+ 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([]);
- useEffect(() => {
- if (knowledgeBaseId) {
- let url = `/documents/${knowledgeBaseId}/${parentId}/documents`;
- setTableApi(url);
- fetchKnowledgeBaseDetail(knowledgeBaseId);
- }
- }, [knowledgeBaseId]);
-
- // 更新面包屑
- useEffect(() => {
- if (knowledgeBase) {
- updateBreadcrumbs();
- }
- }, [knowledgeBase, folderPath]);
-
- // 监听 tableApi 变化,自动刷新表格数据
- useEffect(() => {
- if (tableApi) {
- tableRef.current?.loadData();
- }
- }, [tableApi]);
-
- // 监听 location state 变化,如果有 refresh 标志则刷新列表
- useEffect(() => {
- const state = location.state as { refresh?: boolean; timestamp?: number } | null;
- if (state?.refresh) {
- tableRef.current?.loadData();
- // 清除 state,避免重复刷新
- navigate(location.pathname, { replace: true, state: {} });
- }
- }, [location.state]);
-
+ const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState([]);
const fetchKnowledgeBaseDetail = async (id: string) => {
setLoading(true);
try {
@@ -109,110 +113,160 @@ const Private: FC = () => {
}
};
- // 更新面包屑,包含知识库名称和文件夹路径
- const updateBreadcrumbs = () => {
- if (!knowledgeBase) return;
-
- const baseBreadcrumbs = allBreadcrumbs['space'] || [];
- // 只保留知识库菜单项之前的面包屑
- const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
- const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
- ? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
- : baseBreadcrumbs;
-
- const customBreadcrumbs = [
- ...filteredBaseBreadcrumbs,
- {
- id: 0,
- parent: 0,
- code: null,
- label: knowledgeBase.name,
- i18nKey: null,
- path: null,
- enable: true,
- display: true,
- level: 0,
- sort: 0,
- icon: null,
- iconActive: null,
- menuDesc: null,
- deleted: null,
- updateTime: 0,
- new_: null,
- keepAlive: false,
- master: null,
- disposable: false,
- appSystem: null,
- subs: [],
- onClick: (e?: React.MouseEvent) => {
- // 阻止默认行为和事件冒泡
- e?.preventDefault();
- e?.stopPropagation();
- // 点击知识库名称,回到根目录
- setParentId(knowledgeBaseId);
- setFolder({
- kb_id: knowledgeBaseId ?? '',
- parent_id: knowledgeBaseId ?? ''
- });
- setTableApi(`/documents/${knowledgeBaseId}/${knowledgeBaseId}/documents`);
- setFolderPath([]);
- setSelectedKeys([knowledgeBaseId ?? '']);
- return false;
- },
- },
- ...folderPath.map((folder, index) => ({
- id: 0,
- parent: 0,
- code: null,
- label: folder.name,
- i18nKey: null,
- path: null,
- enable: true,
- display: true,
- level: 0,
- sort: 0,
- icon: null,
- iconActive: null,
- menuDesc: null,
- deleted: null,
- updateTime: 0,
- new_: null,
- keepAlive: false,
- master: null,
- disposable: false,
- appSystem: null,
- subs: [],
- onClick: (e?: React.MouseEvent) => {
- // 阻止默认行为和事件冒泡
- e?.preventDefault();
- e?.stopPropagation();
- // 点击文件夹,回到该文件夹层级
- setParentId(folder.id);
- setFolder({
- kb_id: knowledgeBaseId ?? '',
- parent_id: folder.id
- });
- setTableApi(`/documents/${knowledgeBaseId}/${folder.id}/documents`);
- // 更新文件夹路径,只保留到当前点击的文件夹
- setFolderPath(folderPath.slice(0, index + 1));
- setSelectedKeys([folder.id]);
- return false;
- },
- })),
- ];
+ useEffect(() => {
+ if (knowledgeBaseId) {
+ let url = `/documents/${knowledgeBaseId}/documents`;
+ setTableApi(url);
+ fetchKnowledgeBaseDetail(knowledgeBaseId);
+ }
+ }, [knowledgeBaseId]);
- setCustomBreadcrumbs(customBreadcrumbs, 'space');
- };
+ // 更新面包屑
+ 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([]);
+
+ // 刷新文件夹树(简单的刷新,不需要复杂的重置逻辑)
+ setFolderTreeRefreshKey((prev) => prev + 1);
+
+ // 清除 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) return;
+ 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]),
}
- let url = `/documents/${knowledgeBaseId}/${String(keys[0])}/documents`;
+ setQuery({
+ ...query,
+ parent_id: String(keys[0]),
+ _timestamp: Date.now() // 添加时间戳确保query对象发生变化
+ })
+ let url = `/documents/${knowledgeBaseId}/documents`;
+
setTableApi(url);
setParentId(String(keys[0]))
setFolder(f)
@@ -253,6 +307,15 @@ const Private: FC = () => {
datasetModalRef?.current?.handleOpen(knowledgeBase?.id,folder?.parent_id ?? knowledgeBase?.id ?? '');
},
},
+ {
+ key: '8',
+ icon:
,
+ label: t('knowledgeBase.customTextDataset'),
+ onClick: () => {
+ createContentModalRef?.current?.handleOpen(knowledgeBase?.id ?? '', folder?.parent_id ?? knowledgeBase?.id ?? '');
+ // handleCreate('folder'); // 传入 type: 'folder'
+ },
+ },
// 暂时未实现
// {
// key: '3',
@@ -413,6 +476,21 @@ const Private: FC = () => {
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'
+ }
+ }
},
});
}
@@ -486,7 +564,9 @@ const Private: FC = () => {
}
const refreshDirectoryTree = async () => {
// 先刷新知识库详情,确保数据是最新的
- await fetchKnowledgeBaseDetail(knowledgeBase.id);
+ if (knowledgeBase?.id) {
+ await fetchKnowledgeBaseDetail(knowledgeBase.id);
+ }
// 添加短暂延迟,确保后端数据已经完全更新
await new Promise(resolve => setTimeout(resolve, 300));
// 然后刷新文件夹树
@@ -501,6 +581,7 @@ const Private: FC = () => {
}
const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => {
if (!nodes || nodes.length === 0) {
+ // 如果没有节点,设置folder为null(这会隐藏FolderTree)
setFolder(null);
} else {
// 如果有节点且 folder 为 null,重新设置 folder
@@ -545,6 +626,7 @@ const Private: FC = () => {
onRootLoad={handleRootTreeLoad}
onFolderPathChange={handleFolderPathChange}
selectedKeys={selectedKeys}
+ autoExpandPath={autoExpandPath}
/>
)}
@@ -601,6 +683,10 @@ const Private: FC = () => {
ref={createFolderModalRef}
refreshTable={refreshDirectoryTree}
/>
+