Merge #4 into develop_web from web
合并 feature/20251219_yjp 分支到 web 分支 * web: (11 commits) update web update web update web update web update web update web Sync frontend project from dev-yjp branch feat(web): Update auto-imports with new React hooks and components feat:create cusotm text dataset feat(web): update auto-imports with new React hooks and components 合并 feature/20251219_yjp 分支到 web 分支 Signed-off-by: zhaoying <zhaoying@redbearai.com> Merged-by: zhaoying <zhaoying@redbearai.com> CR-link: https://codeup.aliyun.com/redbearai/python/redbear-mem-open/change/4
This commit is contained in:
10
web/public/auto-imports.d.ts
vendored
10
web/public/auto-imports.d.ts
vendored
@@ -6,22 +6,31 @@
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
declare global {
|
||||
const Activity: typeof import('react').Activity
|
||||
const Fragment: typeof import('react').Fragment
|
||||
const Link: typeof import('react-router-dom').Link
|
||||
const NavLink: typeof import('react-router-dom').NavLink
|
||||
const Navigate: typeof import('react-router-dom').Navigate
|
||||
const Outlet: typeof import('react-router-dom').Outlet
|
||||
const Route: typeof import('react-router-dom').Route
|
||||
const Routes: typeof import('react-router-dom').Routes
|
||||
const Suspense: typeof import('react').Suspense
|
||||
const cache: typeof import('react').cache
|
||||
const cacheSignal: typeof import('react').cacheSignal
|
||||
const createContext: typeof import('react').createContext
|
||||
const createRef: typeof import('react').createRef
|
||||
const forwardRef: typeof import('react').forwardRef
|
||||
const lazy: typeof import('react').lazy
|
||||
const memo: typeof import('react').memo
|
||||
const startTransition: typeof import('react').startTransition
|
||||
const use: typeof import('react').use
|
||||
const useActionState: typeof import('react').useActionState
|
||||
const useCallback: typeof import('react').useCallback
|
||||
const useContext: typeof import('react').useContext
|
||||
const useDebugValue: typeof import('react').useDebugValue
|
||||
const useDeferredValue: typeof import('react').useDeferredValue
|
||||
const useEffect: typeof import('react').useEffect
|
||||
const useEffectEvent: typeof import('react').useEffectEvent
|
||||
const useHref: typeof import('react-router-dom').useHref
|
||||
const useId: typeof import('react').useId
|
||||
const useImperativeHandle: typeof import('react').useImperativeHandle
|
||||
@@ -33,6 +42,7 @@ declare global {
|
||||
const useMemo: typeof import('react').useMemo
|
||||
const useNavigate: typeof import('react-router-dom').useNavigate
|
||||
const useNavigationType: typeof import('react-router-dom').useNavigationType
|
||||
const useOptimistic: typeof import('react').useOptimistic
|
||||
const useOutlet: typeof import('react-router-dom').useOutlet
|
||||
const useOutletContext: typeof import('react-router-dom').useOutletContext
|
||||
const useParams: typeof import('react-router-dom').useParams
|
||||
|
||||
@@ -27,12 +27,21 @@ import 'dayjs/locale/en'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import 'dayjs/plugin/timezone'
|
||||
import 'dayjs/plugin/utc'
|
||||
import { cookieUtils } from './utils/request';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function App() {
|
||||
const { t } = useTranslation();
|
||||
const { locale, language, timeZone } = useI18n()
|
||||
useEffect(() => {
|
||||
const authToken = cookieUtils.get('authToken')
|
||||
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||
window.location.href = `/#/login`;
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t('memoryBear')
|
||||
|
||||
@@ -200,7 +200,7 @@ export const deleteFile = async (id: string) => {
|
||||
|
||||
// 获取文档列表
|
||||
export const getDocumentList = async (query: PathQuery) => {
|
||||
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/${query.parent_id}/documents`, query);
|
||||
const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/documents`, query);
|
||||
return response as KnowledgeBaseDocumentData[];
|
||||
};
|
||||
// 文档详情
|
||||
@@ -213,6 +213,11 @@ export const createDocument = async (data: KnowledgeBaseDocumentData) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/document`, data);
|
||||
return response as KnowledgeBaseDocumentData;
|
||||
};
|
||||
// 自定义文档上传并创建
|
||||
export const createDocumentAndUpload = async ( data: any, params: PathQuery) => {
|
||||
const response = await request.post(`${apiPrefix}/files/customtext`, data, { params } );
|
||||
return response as any;
|
||||
};
|
||||
// 更新文档
|
||||
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
|
||||
const response = await request.put(`${apiPrefix}/documents/${id}`, data);
|
||||
@@ -223,9 +228,9 @@ export const deleteDocument = async (id: string) => {
|
||||
const response = await request.delete(`${apiPrefix}/documents/${id}`);
|
||||
return response;
|
||||
};
|
||||
// 文档解析
|
||||
export const parseDocument = async (id: string) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`);
|
||||
// 文档解析 / 分块
|
||||
export const parseDocument = async (id: string, data: any) => {
|
||||
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`, data);
|
||||
return response as any;
|
||||
};
|
||||
// 文档分块预览
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Layout, Dropdown, Space, Breadcrumb } from 'antd';
|
||||
import type { MenuProps, BreadcrumbProps } from 'antd';
|
||||
import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useUser } from '@/store/user';
|
||||
import { useMenu } from '@/store/menu';
|
||||
import styles from './index.module.css'
|
||||
@@ -12,12 +13,35 @@ const { Header } = Layout;
|
||||
|
||||
const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const settingModalRef = useRef<SettingModalRef>(null)
|
||||
const userInfoModalRef = useRef<UserInfoModalRef>(null)
|
||||
|
||||
const { user, logout } = useUser();
|
||||
const { allBreadcrumbs } = useMenu();
|
||||
const breadcrumbs = allBreadcrumbs[source] || [];
|
||||
|
||||
// 根据当前路由动态选择面包屑源
|
||||
const getBreadcrumbSource = () => {
|
||||
const pathname = location.pathname;
|
||||
|
||||
// 知识库列表页面使用默认的 space 面包屑
|
||||
if (pathname === '/knowledge-base') {
|
||||
return 'space';
|
||||
}
|
||||
|
||||
// 知识库详情相关页面使用独立的面包屑
|
||||
if (pathname.includes('/knowledge-base/') && pathname !== '/knowledge-base') {
|
||||
return 'space-detail';
|
||||
}
|
||||
|
||||
// 其他页面使用传入的 source
|
||||
return source;
|
||||
};
|
||||
|
||||
const breadcrumbSource = getBreadcrumbSource();
|
||||
const breadcrumbs = allBreadcrumbs[breadcrumbSource] || [];
|
||||
|
||||
|
||||
|
||||
// 处理退出登录
|
||||
const handleLogout = () => {
|
||||
|
||||
248
web/src/hooks/useBreadcrumbManager.ts
Normal file
248
web/src/hooks/useBreadcrumbManager.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useMenu } from '@/store/menu';
|
||||
import type { MenuItem } from '@/store/menu';
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
id: string;
|
||||
name: string;
|
||||
type?: 'knowledgeBase' | 'folder' | 'document';
|
||||
}
|
||||
|
||||
export interface BreadcrumbPath {
|
||||
knowledgeBaseFolderPath: BreadcrumbItem[]; // 知识库文件夹路径
|
||||
knowledgeBase?: BreadcrumbItem; // 知识库信息
|
||||
documentFolderPath: BreadcrumbItem[]; // 文档文件夹路径
|
||||
document?: BreadcrumbItem; // 文档信息
|
||||
}
|
||||
|
||||
export interface BreadcrumbOptions {
|
||||
onKnowledgeBaseMenuClick?: () => void;
|
||||
onKnowledgeBaseFolderClick?: (folderId: string, folderPath: BreadcrumbItem[]) => void;
|
||||
// 新增:区分面包屑类型
|
||||
breadcrumbType?: 'list' | 'detail';
|
||||
}
|
||||
|
||||
export const useBreadcrumbManager = (options?: BreadcrumbOptions) => {
|
||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const updateBreadcrumbs = useCallback((breadcrumbPath: BreadcrumbPath) => {
|
||||
const breadcrumbType = options?.breadcrumbType || 'list';
|
||||
|
||||
// 获取基础面包屑,对于详情页面,使用列表页面的基础面包屑作为起点
|
||||
const baseBreadcrumbs = breadcrumbType === 'list'
|
||||
? (allBreadcrumbs['space'] || [])
|
||||
: (allBreadcrumbs['space'] || []); // 详情页面也从 space 获取基础面包屑
|
||||
|
||||
// 只保留知识库菜单项之前的面包屑
|
||||
const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
|
||||
const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
|
||||
? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
|
||||
: baseBreadcrumbs;
|
||||
|
||||
// 给"知识库管理"添加点击事件
|
||||
const breadcrumbsWithClick = filteredBaseBreadcrumbs.map((item) => {
|
||||
if (item.path === '/knowledge-base') {
|
||||
return {
|
||||
...item,
|
||||
onClick: (e?: React.MouseEvent) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
|
||||
if (options?.onKnowledgeBaseMenuClick) {
|
||||
// 如果提供了回调函数,执行回调
|
||||
options.onKnowledgeBaseMenuClick();
|
||||
} else if (breadcrumbType === 'detail') {
|
||||
// 知识库详情页面:没有回调函数时,返回到知识库列表页面
|
||||
navigate('/knowledge-base', {
|
||||
state: {
|
||||
resetToRoot: true,
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
let customBreadcrumbs: MenuItem[] = [...breadcrumbsWithClick];
|
||||
|
||||
if (breadcrumbType === 'list') {
|
||||
// 知识库列表页面:只显示知识库文件夹路径
|
||||
customBreadcrumbs = [
|
||||
...breadcrumbsWithClick,
|
||||
...breadcrumbPath.knowledgeBaseFolderPath.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();
|
||||
|
||||
// 如果有回调函数,直接调用回调函数来更新状态
|
||||
if (options?.onKnowledgeBaseFolderClick) {
|
||||
options.onKnowledgeBaseFolderClick(folder.id, breadcrumbPath.knowledgeBaseFolderPath.slice(0, index + 1));
|
||||
} else {
|
||||
// 否则使用导航(兜底逻辑)
|
||||
navigate('/knowledge-base', {
|
||||
state: {
|
||||
navigateToFolder: folder.id,
|
||||
folderPath: breadcrumbPath.knowledgeBaseFolderPath.slice(0, index + 1)
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
},
|
||||
})),
|
||||
];
|
||||
} else {
|
||||
// 知识库详情页面:显示知识库名称 + 文档文件夹路径 + 文档名称
|
||||
customBreadcrumbs = [
|
||||
...breadcrumbsWithClick,
|
||||
|
||||
// 添加知识库名称
|
||||
...(breadcrumbPath.knowledgeBase ? [{
|
||||
id: 0,
|
||||
parent: 0,
|
||||
code: null,
|
||||
label: breadcrumbPath.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();
|
||||
// 返回到知识库详情页的根目录
|
||||
const navigationState = {
|
||||
fromKnowledgeBaseList: true,
|
||||
knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
|
||||
resetToRoot: true, // 添加重置到根目录的标志
|
||||
refresh: true, // 添加刷新标志
|
||||
timestamp: Date.now(), // 添加时间戳确保状态变化
|
||||
};
|
||||
navigate(`/knowledge-base/${breadcrumbPath.knowledgeBase!.id}/private`, {
|
||||
state: navigationState,
|
||||
replace: true // 使用 replace 避免历史记录堆积
|
||||
});
|
||||
return false;
|
||||
},
|
||||
}] : []),
|
||||
|
||||
// 添加文档文件夹路径
|
||||
...breadcrumbPath.documentFolderPath.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();
|
||||
// 返回到知识库详情页的对应文件夹
|
||||
const navigationState = {
|
||||
fromKnowledgeBaseList: true,
|
||||
knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
|
||||
navigateToDocumentFolder: folder.id,
|
||||
documentFolderPath: breadcrumbPath.documentFolderPath.slice(0, index + 1),
|
||||
refresh: true, // 添加刷新标志
|
||||
timestamp: Date.now(), // 添加时间戳确保状态变化
|
||||
};
|
||||
navigate(`/knowledge-base/${breadcrumbPath.knowledgeBase!.id}/private`, {
|
||||
state: navigationState,
|
||||
replace: true // 使用 replace 避免历史记录堆积
|
||||
});
|
||||
return false;
|
||||
},
|
||||
})),
|
||||
|
||||
// 添加文档名称(如果存在)
|
||||
...(breadcrumbPath.document ? [{
|
||||
id: 0,
|
||||
parent: 0,
|
||||
code: null,
|
||||
label: breadcrumbPath.document.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: [],
|
||||
// 文档名称不可点击
|
||||
}] : []),
|
||||
];
|
||||
}
|
||||
|
||||
// 根据面包屑类型使用不同的键,实现独立的面包屑路径
|
||||
const breadcrumbKey = breadcrumbType === 'list' ? 'space' : 'space-detail';
|
||||
|
||||
|
||||
|
||||
setCustomBreadcrumbs(customBreadcrumbs, breadcrumbKey);
|
||||
}, [setCustomBreadcrumbs, navigate, options?.breadcrumbType, options?.onKnowledgeBaseMenuClick, options?.onKnowledgeBaseFolderClick]);
|
||||
|
||||
return {
|
||||
updateBreadcrumbs,
|
||||
};
|
||||
};
|
||||
@@ -11,8 +11,10 @@ export const checkAuthStatus = (): boolean => {
|
||||
|
||||
// 递归检查路由是否存在于菜单数据中
|
||||
export const checkRoutePermission = (menus: MenuItem[], currentPath: string): boolean => {
|
||||
// 首页默认有权限
|
||||
if (currentPath === '/' || currentPath.includes('knowledge-detail')) return true;
|
||||
// 首页和知识库相关页面默认有权限
|
||||
if (currentPath === '/' || currentPath.includes('knowledge-detail') || currentPath.includes('knowledge-base')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const menu of menus) {
|
||||
// 检查当前菜单的path是否匹配
|
||||
@@ -26,6 +28,7 @@ export const checkRoutePermission = (menus: MenuItem[], currentPath: string): bo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -52,7 +55,7 @@ export const useRouteGuard = (source: 'space' | 'manage') => {
|
||||
const hasPermission = checkRoutePermission(menus, location.pathname);
|
||||
if (!hasPermission) {
|
||||
// 无权限访问该路由,重定向到无权限页面
|
||||
// navigate('/not-found', { replace: true });
|
||||
// navigate('/no-permission', { replace: true });
|
||||
}
|
||||
}
|
||||
}, [navigate, location.pathname, location.search, location.hash, menus]);
|
||||
|
||||
@@ -479,11 +479,18 @@ export const en = {
|
||||
noDataSets: 'No datasets yet, click the button below or drag files to create.',
|
||||
createEmptyDataSet: '+ Empty Dataset',
|
||||
createImageDataSet: '+ Image Dataset',
|
||||
createContent: 'Create Content',
|
||||
title: 'Title',
|
||||
content: 'Content',
|
||||
pleaseEnterTitle: 'Please enter title',
|
||||
pleaseEnterContent: 'Please enter content',
|
||||
// createImageDataSet: '+ Image Dataset',
|
||||
dragFilesHere: 'Drag files here to upload',
|
||||
createImport: 'Create/Import',
|
||||
textDataSet: 'Text Dataset',
|
||||
imageDataSet: 'Image Dataset',
|
||||
blankDataset: 'Blank Dataset',
|
||||
customTextDataset: 'Custom Text Dataset',
|
||||
text: 'Text',
|
||||
search: 'Search',
|
||||
image: 'Image',
|
||||
|
||||
@@ -110,6 +110,11 @@ export const zh = {
|
||||
noDataSets: '暂无数据集,点击下方按钮或拖拽文件创建。',
|
||||
createEmptyDataSet: '+ 空白数据集',
|
||||
createImageDataSet: '+ 图片数据集',
|
||||
createContent: '创建内容',
|
||||
title: '标题',
|
||||
content: '内容',
|
||||
pleaseEnterTitle: '请输入标题',
|
||||
pleaseEnterContent: '请输入内容',
|
||||
dragFilesHere: '拖拽文件到此处上传',
|
||||
downloadOriginal: '下载原始内容',
|
||||
createImport: '新建/导入',
|
||||
@@ -117,6 +122,7 @@ export const zh = {
|
||||
imageDataSet: '图片数据集',
|
||||
blankDataset: '空白数据集',
|
||||
emptyDataSet: '空白数据集',
|
||||
customTextDataset: '自定义文本数据集',
|
||||
text: '文本',
|
||||
search: '搜索',
|
||||
image: '图片',
|
||||
|
||||
@@ -32,7 +32,7 @@ interface MenuState {
|
||||
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
||||
loadMenus: (source: 'space' | 'manage') => void;
|
||||
updateBreadcrumbs: (keyPath: string[], source: 'space' | 'manage') => void;
|
||||
setCustomBreadcrumbs: (breadcrumbs: MenuItem[], source: 'space' | 'manage') => void;
|
||||
setCustomBreadcrumbs: (breadcrumbs: MenuItem[], source: string) => void;
|
||||
}
|
||||
|
||||
const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]'
|
||||
|
||||
@@ -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<number>(stepIndexMap[initialStepKey]);
|
||||
const tableRef = useRef<TableRef>(null);
|
||||
|
||||
|
||||
const [data, setData] = useState<KnowledgeBaseDocumentData[]>([]);
|
||||
const [chunkData, setChunkData] = useState<any[]>([]);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
const [rechunkFileIds, setRechunkFileIds] = useState<string[]>(initialFileIds);
|
||||
const [curSelectedFileId, setCurSelectedFileId] = useState<number>(-1);
|
||||
const [previewLoading, setPreviewLoading] = useState<boolean>(false);
|
||||
|
||||
const [pollingLoading, setPollingLoading] = useState<boolean>(false);
|
||||
const pollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const [delimiter, setDelimiter] = useState<string | undefined>(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 {
|
||||
@@ -565,8 +552,8 @@ const CreateDataset = () => {
|
||||
{rechunkFileIds.length > 0 ? (
|
||||
<Table
|
||||
ref={tableRef}
|
||||
apiUrl={`/documents/${knowledgeBaseId}/${parentId}/documents`}
|
||||
apiParams={{
|
||||
apiUrl={`/documents/${knowledgeBaseId}/documents`}
|
||||
apiParams={{
|
||||
document_ids: rechunkFileIds.join(','),
|
||||
}}
|
||||
columns={columns}
|
||||
|
||||
@@ -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<KnowledgeBaseDocumentData | null>(null);
|
||||
const [chunkList, setChunkList] = useState<RecallTestData[]>([]);
|
||||
@@ -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`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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<string | undefined>(undefined);
|
||||
const recallTestDrawerRef = useRef<RecallTestDrawerRef>(null);
|
||||
const createFolderModalRef = useRef<CreateFolderModalRef>(null);
|
||||
const createImageDataset = useRef<CreateImageModalRef>(null)
|
||||
const createImageDataset = useRef<CreateSetModalRef>(null)
|
||||
const createContentModalRef = useRef<CreateSetModalRef>(null);
|
||||
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
|
||||
const [folder, setFolder] = useState<FolderFormData | null>({
|
||||
kb_id:knowledgeBaseId ?? '',
|
||||
@@ -56,47 +60,47 @@ const Private: FC = () => {
|
||||
});
|
||||
const [query, setQuery] = useState<Record<string, unknown>>({
|
||||
orderby: 'created_at',
|
||||
desc: true,
|
||||
desc: true
|
||||
});
|
||||
const modalRef = useRef<CreateModalRef>(null)
|
||||
const shareModalRef = useRef<ShareModalRef>(null);
|
||||
const datasetModalRef = useRef<CreateDatasetModalRef>(null);
|
||||
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
|
||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
||||
const [folderPath, setFolderPath] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [autoExpandPath, setAutoExpandPath] = useState<Array<{ id: string; name: string }>>([]);
|
||||
|
||||
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<BreadcrumbItem[]>([]);
|
||||
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||
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<BreadcrumbItem[]>([]);
|
||||
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: <img src={blankIcon} alt="Custome Text" style={{ width: 16, height: 16 }} />,
|
||||
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
|
||||
@@ -524,6 +605,7 @@ const Private: FC = () => {
|
||||
}
|
||||
|
||||
const handleRefreshTable = () => {
|
||||
debugger
|
||||
// 刷新表格数据
|
||||
tableRef.current?.loadData();
|
||||
}
|
||||
@@ -545,6 +627,7 @@ const Private: FC = () => {
|
||||
onRootLoad={handleRootTreeLoad}
|
||||
onFolderPathChange={handleFolderPathChange}
|
||||
selectedKeys={selectedKeys}
|
||||
autoExpandPath={autoExpandPath}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -601,6 +684,10 @@ const Private: FC = () => {
|
||||
ref={createFolderModalRef}
|
||||
refreshTable={refreshDirectoryTree}
|
||||
/>
|
||||
<CreateContentModal
|
||||
ref={createContentModalRef}
|
||||
refreshTable={handleRefreshTable}
|
||||
/>
|
||||
<CreateModal
|
||||
ref={modalRef}
|
||||
refreshTable={handleRefreshTable}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState, useRef, type FC } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useParams, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
|
||||
import RecallTest from '../components/RecallTest';
|
||||
@@ -15,17 +15,22 @@ import kbModelIcon from '@/assets/images/knowledgeBase/kb-model.png';
|
||||
import kbHistoryIcon from '@/assets/images/knowledgeBase/kb-history.png';
|
||||
import { getKnowledgeBaseDetail } from '@/api/knowledgeBase';
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
import { useMenu } from '@/store/menu';
|
||||
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
|
||||
|
||||
const Share: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams<{ knowledgeBaseId: string }>();
|
||||
const location = useLocation();
|
||||
const knowledgeBaseId = params.knowledgeBaseId;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
|
||||
const recallTestRef = useRef<RecallTestDrawerRef>(null);
|
||||
const [infoItems, setInfoItems] = useState<InfoItem[]>([]);
|
||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
||||
const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState<BreadcrumbItem[]>([]);
|
||||
|
||||
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||
breadcrumbType: 'detail'
|
||||
});
|
||||
useEffect(() => {
|
||||
console.log('Share.tsx - useParams result:', params);
|
||||
console.log('Share.tsx - knowledgeBaseId:', knowledgeBaseId);
|
||||
@@ -46,9 +51,30 @@ const Share: FC = () => {
|
||||
// 更新面包屑
|
||||
useEffect(() => {
|
||||
if (knowledgeBase) {
|
||||
updateBreadcrumbs();
|
||||
updateBreadcrumbs({
|
||||
knowledgeBaseFolderPath,
|
||||
knowledgeBase: {
|
||||
id: knowledgeBase.id,
|
||||
name: knowledgeBase.name,
|
||||
type: 'knowledgeBase'
|
||||
},
|
||||
documentFolderPath: [],
|
||||
});
|
||||
}
|
||||
}, [knowledgeBase]);
|
||||
}, [knowledgeBase, knowledgeBaseFolderPath, updateBreadcrumbs]);
|
||||
|
||||
// 监听 location state 变化
|
||||
useEffect(() => {
|
||||
const state = location.state as {
|
||||
fromKnowledgeBaseList?: boolean;
|
||||
knowledgeBaseFolderPath?: BreadcrumbItem[];
|
||||
} | null;
|
||||
|
||||
// 如果是从知识库列表页跳转过来的,设置知识库文件夹路径
|
||||
if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) {
|
||||
setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath);
|
||||
}
|
||||
}, [location.state]);
|
||||
const formatInfoItems = (data: KnowledgeBaseListItem): InfoItem[] => {
|
||||
const items: InfoItem[] = [
|
||||
{
|
||||
@@ -112,46 +138,7 @@ const Share: 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: [],
|
||||
},
|
||||
];
|
||||
|
||||
setCustomBreadcrumbs(customBreadcrumbs, 'space');
|
||||
};
|
||||
|
||||
// const handleBack = () => {
|
||||
// navigate('/knowledge-base');
|
||||
|
||||
117
web/src/views/KnowledgeBase/components/CreateContentModal.tsx
Normal file
117
web/src/views/KnowledgeBase/components/CreateContentModal.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import RbModal from '@/components/RbModal';
|
||||
import { createDocumentAndUpload } from '@/api/knowledgeBase'
|
||||
import type { CreateSetModalRef,CreateSetMoealRefProps } from '../types'
|
||||
interface ContentFormData {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const CreateContentModal = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
|
||||
({ refreshTable }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<ContentFormData>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [kbId, setKbId] = useState<string>('');
|
||||
const [parentId, setParentId] = useState<string>('');
|
||||
|
||||
const handleClose = () => {
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
setVisible(false);
|
||||
setKbId('');
|
||||
setParentId('');
|
||||
};
|
||||
|
||||
const handleOpen = (kb_id: string, parent_id: string) => {
|
||||
setKbId(kb_id);
|
||||
setParentId(parent_id);
|
||||
form.resetFields();
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setLoading(true);
|
||||
|
||||
// TODO: 这里需要调用相应的API来保存内容
|
||||
const params = {
|
||||
// ...values,
|
||||
kb_id: kbId,
|
||||
parent_id: parentId,
|
||||
};
|
||||
|
||||
|
||||
const response = await createDocumentAndUpload(values, params)
|
||||
if(response){
|
||||
handleChunking(response.kb_id,parentId,response.id)
|
||||
}
|
||||
handleClose();
|
||||
} catch (err) {
|
||||
console.error('创建内容失败:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const handleChunking = (kb_id: string, parent_id: string, file_id: string) => {
|
||||
if (!kb_id) return;
|
||||
const targetFileId = file_id
|
||||
navigate(`/knowledge-base/${kb_id}/create-dataset`, {
|
||||
state: {
|
||||
source: 'local',
|
||||
knowledgeBaseId: kb_id,
|
||||
parentId: parent_id ?? kb_id,
|
||||
startStep: 'parameterSettings',
|
||||
fileId: targetFileId,
|
||||
},
|
||||
});
|
||||
}
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('knowledgeBase.createContent')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.create')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
width={600}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
name="title"
|
||||
label={t('knowledgeBase.title')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterTitle') }]}
|
||||
>
|
||||
<Input placeholder={t('knowledgeBase.pleaseEnterTitle')} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="content"
|
||||
label={t('knowledgeBase.content')}
|
||||
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterContent') }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder={t('knowledgeBase.pleaseEnterContent')}
|
||||
rows={8}
|
||||
showCount
|
||||
maxLength={5000}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CreateContentModal;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { useRef } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import CreateContentModal from './CreateContentModal';
|
||||
import type { CreateContentModalRef } from '../types';
|
||||
|
||||
// 使用示例组件
|
||||
const CreateContentModalExample = () => {
|
||||
const createContentModalRef = useRef<CreateContentModalRef>(null);
|
||||
|
||||
const handleOpenModal = () => {
|
||||
// 打开弹窗,传入知识库ID和父级ID
|
||||
createContentModalRef.current?.handleOpen('kb_123', 'parent_456');
|
||||
};
|
||||
|
||||
const handleRefreshTable = () => {
|
||||
console.log('刷新表格数据');
|
||||
// 这里可以添加刷新表格的逻辑
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button type="primary" onClick={handleOpenModal}>
|
||||
创建内容
|
||||
</Button>
|
||||
|
||||
<CreateContentModal
|
||||
ref={createContentModalRef}
|
||||
refreshTable={handleRefreshTable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateContentModalExample;
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { UploadFile } from 'antd';
|
||||
import type { CreateImageModalRef, CreateImageMoealRefProps,UploadFileResponse } from '@/views/KnowledgeBase/types';
|
||||
import type { CreateSetModalRef, CreateSetMoealRefProps, UploadFileResponse } from '@/views/KnowledgeBase/types';
|
||||
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||
import RbModal from '@/components/RbModal';
|
||||
import UploadFiles from '@/components/Upload/UploadFiles';
|
||||
@@ -13,7 +13,7 @@ interface ImageDatasetFormData {
|
||||
images: UploadFile[];
|
||||
}
|
||||
|
||||
const CreateImageDataset = forwardRef<CreateImageModalRef, CreateImageMoealRefProps>(
|
||||
const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
|
||||
({ refreshTable }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
@@ -60,6 +60,8 @@ interface FolderTreeProps {
|
||||
onRootLoad?: (nodes: TreeNodeData[] | null) => void;
|
||||
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
|
||||
selectedKeys?: React.Key[];
|
||||
// 新增:自动展开到指定路径
|
||||
autoExpandPath?: Array<{ id: string; name: string }>;
|
||||
}
|
||||
|
||||
const renderIcon = (icon?: string) => {
|
||||
@@ -275,8 +277,11 @@ const FolderTree: FC<FolderTreeProps> = ({
|
||||
onRootLoad,
|
||||
onFolderPathChange,
|
||||
selectedKeys,
|
||||
autoExpandPath,
|
||||
}) => {
|
||||
const [treeData, setTreeData] = useState<TreeNodeData[]>([]);
|
||||
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
|
||||
const [autoExpandInProgress, setAutoExpandInProgress] = useState(false);
|
||||
|
||||
// 更新树节点数据的辅助函数
|
||||
const updateTreeData = (nodes: TreeNodeData[], key: Key, children: TreeNodeData[]): TreeNodeData[] => {
|
||||
@@ -370,6 +375,109 @@ const FolderTree: FC<FolderTreeProps> = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
// 查找节点的辅助函数
|
||||
const findNodeInTree = (nodes: TreeNodeData[], key: string): TreeNodeData | null => {
|
||||
for (const node of nodes) {
|
||||
if (String(node.key) === key) {
|
||||
return node;
|
||||
}
|
||||
if (node.children) {
|
||||
const found = findNodeInTree(node.children, key);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 渐进式自动展开到指定路径
|
||||
useEffect(() => {
|
||||
if (!autoExpandPath || autoExpandPath.length === 0 || autoExpandInProgress || treeData.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expandToPath = async () => {
|
||||
setAutoExpandInProgress(true);
|
||||
|
||||
try {
|
||||
const keysToExpand: React.Key[] = [];
|
||||
let currentTreeData = treeData;
|
||||
|
||||
// 逐级展开,从第一级开始(跳过根节点,因为根节点已经加载)
|
||||
for (let i = 0; i < autoExpandPath.length - 1; i++) {
|
||||
const nodeKey = autoExpandPath[i].id;
|
||||
keysToExpand.push(nodeKey);
|
||||
|
||||
// 查找当前节点
|
||||
const targetNode = findNodeInTree(currentTreeData, nodeKey);
|
||||
|
||||
if (targetNode && targetNode.children === undefined) {
|
||||
// 如果子节点未加载,先加载
|
||||
try {
|
||||
console.log(`自动展开:加载节点 ${nodeKey} 的子节点`);
|
||||
const children = await buildTreeNodes(knowledgeBaseId, nodeKey);
|
||||
|
||||
// 更新树数据
|
||||
setTreeData((prevData) => {
|
||||
const newData = updateTreeData(prevData, nodeKey, children);
|
||||
currentTreeData = newData; // 更新当前引用
|
||||
return newData;
|
||||
});
|
||||
|
||||
// 等待状态更新完成
|
||||
await new Promise(resolve => setTimeout(resolve, 150));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`自动展开时加载节点 ${nodeKey} 失败:`, error);
|
||||
// 加载失败时停止展开
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置展开的节点
|
||||
setExpandedKeys(keysToExpand);
|
||||
|
||||
// 选中最后一个节点(目标文件夹)
|
||||
const targetKey = autoExpandPath[autoExpandPath.length - 1]?.id;
|
||||
if (targetKey) {
|
||||
console.log(`自动展开:选中目标节点 ${targetKey}`);
|
||||
// 延迟选中,确保展开动画完成
|
||||
setTimeout(() => {
|
||||
if (onSelect) {
|
||||
onSelect([targetKey], {
|
||||
selected: true,
|
||||
selectedNodes: [],
|
||||
node: {} as any,
|
||||
event: 'select',
|
||||
nativeEvent: new MouseEvent('click')
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('自动展开路径失败:', error);
|
||||
} finally {
|
||||
// 延迟重置标志,确保展开过程完全完成
|
||||
setTimeout(() => {
|
||||
setAutoExpandInProgress(false);
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
// 延迟执行,确保树数据已经加载完成
|
||||
const timer = setTimeout(expandToPath, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [autoExpandPath, treeData.length, knowledgeBaseId, onSelect, autoExpandInProgress]);
|
||||
|
||||
// 处理展开事件
|
||||
const handleExpand: TreeProps['onExpand'] = (expandedKeys, info) => {
|
||||
setExpandedKeys(expandedKeys);
|
||||
if (onExpand) {
|
||||
onExpand(expandedKeys, info);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理选择事件,计算并传递路径
|
||||
const handleSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
|
||||
if (selectedKeys.length > 0) {
|
||||
@@ -391,11 +499,13 @@ const FolderTree: FC<FolderTreeProps> = ({
|
||||
|
||||
return (
|
||||
<DirectoryTree
|
||||
key={refreshKey} // 添加key确保refreshKey变化时重新渲染整个组件
|
||||
multiple={multiple}
|
||||
className={className}
|
||||
style={style}
|
||||
onSelect={handleSelect}
|
||||
onExpand={onExpand}
|
||||
onExpand={handleExpand}
|
||||
expandedKeys={expandedKeys}
|
||||
loadData={onLoadData}
|
||||
treeData={treeNodes}
|
||||
selectedKeys={selectedKeys}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect, useState, useRef, useMemo, type FC } from 'react';
|
||||
import { useEffect, useState, useRef, useMemo, useCallback, type FC } from 'react';
|
||||
import { Row, Col, Button, Dropdown, Modal, message, Tooltip } from 'antd'
|
||||
import type { MenuProps } from 'antd';
|
||||
import { EllipsisOutlined } from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
@@ -18,7 +18,8 @@ import Empty from '@/components/Empty'
|
||||
import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from '@/api/knowledgeBase'
|
||||
const { confirm } = Modal;
|
||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||
import { useMenu } from '@/store/menu';
|
||||
|
||||
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
|
||||
|
||||
type ModelMenuInfo = {
|
||||
menu: NonNullable<MenuProps['items']>;
|
||||
@@ -28,6 +29,7 @@ type ModelMenuInfo = {
|
||||
const KnowledgeBaseManagement: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState<KnowledgeBaseListItem[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
@@ -42,10 +44,29 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
const modelListCache = useRef<Record<string, string>>({});
|
||||
const modalRef = useRef<CreateModalRef>(null)
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const processedStateRef = useRef<any>(null);
|
||||
|
||||
// 使用 menu store 管理面包屑
|
||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
||||
const [folderPath, setFolderPath] = useState<Array<{ id: string; name: string }>>([]);
|
||||
// 使用面包屑管理 Hook
|
||||
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||
breadcrumbType: 'list',
|
||||
onKnowledgeBaseMenuClick: useCallback(() => {
|
||||
// 返回根目录
|
||||
setFolderPath([]);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: undefined,
|
||||
}));
|
||||
}, []),
|
||||
onKnowledgeBaseFolderClick: useCallback((folderId: string, folderPath: Array<{ id: string; name: string }>) => {
|
||||
// 直接更新文件夹路径和查询状态
|
||||
setFolderPath(folderPath);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: folderId,
|
||||
}));
|
||||
}, [])
|
||||
});
|
||||
const [folderPath, setFolderPath] = useState<BreadcrumbItem[]>([]);
|
||||
|
||||
|
||||
// 生成下拉菜单项(根据当前 item)
|
||||
@@ -134,7 +155,7 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
handleCreate(type);
|
||||
},
|
||||
}));
|
||||
}, [knowledgeBaseTypes, t, folderPath, query]);
|
||||
}, [knowledgeBaseTypes, t]);
|
||||
const typeToFieldKey = (type: string) => {
|
||||
const normalized = (type || '').toLowerCase();
|
||||
switch (normalized) {
|
||||
@@ -371,90 +392,72 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据权限类型跳转到不同的详情页
|
||||
if (knowledgeBase.permission_id === 'Private' || knowledgeBase.permission_id === 'private') {
|
||||
navigate(`/knowledge-base/${knowledgeBase.id}/private`)
|
||||
// 跳转时传递当前的文件夹路径信息
|
||||
const navigationState = {
|
||||
fromKnowledgeBaseList: true,
|
||||
knowledgeBaseFolderPath: folderPath,
|
||||
parentId: query.parent_id,
|
||||
timestamp: Date.now(), // 添加时间戳确保每次跳转状态都不同
|
||||
};
|
||||
const targetPath = knowledgeBase.permission_id === 'Private' || knowledgeBase.permission_id === 'private'
|
||||
? `/knowledge-base/${knowledgeBase.id}/private`
|
||||
: `/knowledge-base/${knowledgeBase.id}/share`;
|
||||
|
||||
// 检查是否是相同路径跳转
|
||||
const currentPath = location.pathname;
|
||||
|
||||
if (currentPath === targetPath) {
|
||||
// 如果是相同路径,使用replace并强制刷新状态
|
||||
navigate(targetPath, {
|
||||
state: navigationState,
|
||||
replace: true
|
||||
});
|
||||
} else {
|
||||
navigate(`/knowledge-base/${knowledgeBase.id}/share`)
|
||||
// 不同路径,正常跳转
|
||||
navigate(targetPath, { state: navigationState });
|
||||
}
|
||||
}
|
||||
// 更新面包屑的函数
|
||||
const updateBreadcrumbs = () => {
|
||||
const baseBreadcrumbs = allBreadcrumbs['space'] || [];
|
||||
// 只保留知识库菜单项之前的面包屑
|
||||
const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
|
||||
const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
|
||||
? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
|
||||
: baseBreadcrumbs;
|
||||
|
||||
// 给"知识库管理"添加点击事件,返回根目录
|
||||
const breadcrumbsWithClick = filteredBaseBreadcrumbs.map((item) => {
|
||||
if (item.path === '/knowledge-base') {
|
||||
return {
|
||||
...item,
|
||||
onClick: (e?: React.MouseEvent) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
// 返回根目录
|
||||
setFolderPath([]);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: undefined,
|
||||
}));
|
||||
return false;
|
||||
},
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
const customBreadcrumbs = [
|
||||
...breadcrumbsWithClick,
|
||||
...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();
|
||||
// 点击文件夹,回到该文件夹层级
|
||||
const newFolderPath = folderPath.slice(0, index + 1);
|
||||
setFolderPath(newFolderPath);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: folder.id,
|
||||
}));
|
||||
return false;
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
setCustomBreadcrumbs(customBreadcrumbs, 'space');
|
||||
};
|
||||
|
||||
// 更新面包屑
|
||||
useEffect(() => {
|
||||
updateBreadcrumbs();
|
||||
}, [folderPath]);
|
||||
updateBreadcrumbs({
|
||||
knowledgeBaseFolderPath: folderPath,
|
||||
documentFolderPath: [],
|
||||
});
|
||||
}, [folderPath, updateBreadcrumbs]);
|
||||
|
||||
// 处理从详情页返回的导航
|
||||
useEffect(() => {
|
||||
const state = location.state as {
|
||||
navigateToFolder?: string;
|
||||
folderPath?: Array<{ id: string; name: string }>;
|
||||
resetToRoot?: boolean;
|
||||
} | null;
|
||||
|
||||
// 避免重复处理相同的状态
|
||||
if (state && state !== processedStateRef.current) {
|
||||
processedStateRef.current = state;
|
||||
|
||||
if (state.resetToRoot) {
|
||||
// 重置到根目录
|
||||
setFolderPath([]);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: undefined,
|
||||
}));
|
||||
} else if (state?.navigateToFolder && state?.folderPath) {
|
||||
// 恢复文件夹路径和查询状态
|
||||
setFolderPath(state.folderPath);
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
parent_id: state.navigateToFolder,
|
||||
}));
|
||||
}
|
||||
|
||||
// 不清除 state,避免干扰后续导航
|
||||
// 使用 processedStateRef 来避免重复处理相同的 state
|
||||
}
|
||||
}, [location.state, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchModelTypes();
|
||||
@@ -465,7 +468,7 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
if (modelTypes.length) {
|
||||
fetchData(1, false);
|
||||
}
|
||||
}, [modelTypes, query])
|
||||
}, [modelTypes, query.parent_id, query.keywords, query.orderby, query.desc])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -146,11 +146,19 @@ export interface CreateFolderModalRefProps{
|
||||
refreshTable?: () => void;
|
||||
}
|
||||
|
||||
//他建图片数据集
|
||||
export interface CreateImageModalRef{
|
||||
handleOpen: (kb_id:string,parent_id:string) => void;
|
||||
//创建图片数据集 / 创建自定义文本数据集
|
||||
export interface CreateSetModalRef{
|
||||
handleOpen: (kb_id:string, parent_id:string) => void;
|
||||
}
|
||||
export interface CreateImageMoealRefProps{
|
||||
export interface CreateSetMoealRefProps{
|
||||
refreshTable?: () => void;
|
||||
}
|
||||
|
||||
// 创建内容
|
||||
export interface CreateContentModalRef {
|
||||
handleOpen: (kb_id: string, parent_id: string) => void;
|
||||
}
|
||||
export interface CreateContentModalRefProps {
|
||||
refreshTable?: () => void;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user