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
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
|
const Activity: typeof import('react').Activity
|
||||||
|
const Fragment: typeof import('react').Fragment
|
||||||
const Link: typeof import('react-router-dom').Link
|
const Link: typeof import('react-router-dom').Link
|
||||||
const NavLink: typeof import('react-router-dom').NavLink
|
const NavLink: typeof import('react-router-dom').NavLink
|
||||||
const Navigate: typeof import('react-router-dom').Navigate
|
const Navigate: typeof import('react-router-dom').Navigate
|
||||||
const Outlet: typeof import('react-router-dom').Outlet
|
const Outlet: typeof import('react-router-dom').Outlet
|
||||||
const Route: typeof import('react-router-dom').Route
|
const Route: typeof import('react-router-dom').Route
|
||||||
const Routes: typeof import('react-router-dom').Routes
|
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 createRef: typeof import('react').createRef
|
||||||
const forwardRef: typeof import('react').forwardRef
|
const forwardRef: typeof import('react').forwardRef
|
||||||
const lazy: typeof import('react').lazy
|
const lazy: typeof import('react').lazy
|
||||||
const memo: typeof import('react').memo
|
const memo: typeof import('react').memo
|
||||||
const startTransition: typeof import('react').startTransition
|
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 useCallback: typeof import('react').useCallback
|
||||||
const useContext: typeof import('react').useContext
|
const useContext: typeof import('react').useContext
|
||||||
const useDebugValue: typeof import('react').useDebugValue
|
const useDebugValue: typeof import('react').useDebugValue
|
||||||
const useDeferredValue: typeof import('react').useDeferredValue
|
const useDeferredValue: typeof import('react').useDeferredValue
|
||||||
const useEffect: typeof import('react').useEffect
|
const useEffect: typeof import('react').useEffect
|
||||||
|
const useEffectEvent: typeof import('react').useEffectEvent
|
||||||
const useHref: typeof import('react-router-dom').useHref
|
const useHref: typeof import('react-router-dom').useHref
|
||||||
const useId: typeof import('react').useId
|
const useId: typeof import('react').useId
|
||||||
const useImperativeHandle: typeof import('react').useImperativeHandle
|
const useImperativeHandle: typeof import('react').useImperativeHandle
|
||||||
@@ -33,6 +42,7 @@ declare global {
|
|||||||
const useMemo: typeof import('react').useMemo
|
const useMemo: typeof import('react').useMemo
|
||||||
const useNavigate: typeof import('react-router-dom').useNavigate
|
const useNavigate: typeof import('react-router-dom').useNavigate
|
||||||
const useNavigationType: typeof import('react-router-dom').useNavigationType
|
const useNavigationType: typeof import('react-router-dom').useNavigationType
|
||||||
|
const useOptimistic: typeof import('react').useOptimistic
|
||||||
const useOutlet: typeof import('react-router-dom').useOutlet
|
const useOutlet: typeof import('react-router-dom').useOutlet
|
||||||
const useOutletContext: typeof import('react-router-dom').useOutletContext
|
const useOutletContext: typeof import('react-router-dom').useOutletContext
|
||||||
const useParams: typeof import('react-router-dom').useParams
|
const useParams: typeof import('react-router-dom').useParams
|
||||||
|
|||||||
@@ -27,12 +27,21 @@ import 'dayjs/locale/en'
|
|||||||
import 'dayjs/locale/zh-cn'
|
import 'dayjs/locale/zh-cn'
|
||||||
import 'dayjs/plugin/timezone'
|
import 'dayjs/plugin/timezone'
|
||||||
import 'dayjs/plugin/utc'
|
import 'dayjs/plugin/utc'
|
||||||
|
import { cookieUtils } from './utils/request';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { locale, language, timeZone } = useI18n()
|
const { locale, language, timeZone } = useI18n()
|
||||||
|
useEffect(() => {
|
||||||
|
const authToken = cookieUtils.get('authToken')
|
||||||
|
if (!authToken && !window.location.hash.includes('#/login')) {
|
||||||
|
window.location.href = `/#/login`;
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = t('memoryBear')
|
document.title = t('memoryBear')
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export const deleteFile = async (id: string) => {
|
|||||||
|
|
||||||
// 获取文档列表
|
// 获取文档列表
|
||||||
export const getDocumentList = async (query: PathQuery) => {
|
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[];
|
return response as KnowledgeBaseDocumentData[];
|
||||||
};
|
};
|
||||||
// 文档详情
|
// 文档详情
|
||||||
@@ -213,6 +213,11 @@ export const createDocument = async (data: KnowledgeBaseDocumentData) => {
|
|||||||
const response = await request.post(`${apiPrefix}/documents/document`, data);
|
const response = await request.post(`${apiPrefix}/documents/document`, data);
|
||||||
return response as KnowledgeBaseDocumentData;
|
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) => {
|
export const updateDocument = async (id: string, data: KnowledgeBaseDocumentData) => {
|
||||||
const response = await request.put(`${apiPrefix}/documents/${id}`, data);
|
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}`);
|
const response = await request.delete(`${apiPrefix}/documents/${id}`);
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
// 文档解析
|
// 文档解析 / 分块
|
||||||
export const parseDocument = async (id: string) => {
|
export const parseDocument = async (id: string, data: any) => {
|
||||||
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`);
|
const response = await request.post(`${apiPrefix}/documents/${id}/chunks`, data);
|
||||||
return response as any;
|
return response as any;
|
||||||
};
|
};
|
||||||
// 文档分块预览
|
// 文档分块预览
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Layout, Dropdown, Space, Breadcrumb } from 'antd';
|
|||||||
import type { MenuProps, BreadcrumbProps } from 'antd';
|
import type { MenuProps, BreadcrumbProps } from 'antd';
|
||||||
import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
|
import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import { useUser } from '@/store/user';
|
import { useUser } from '@/store/user';
|
||||||
import { useMenu } from '@/store/menu';
|
import { useMenu } from '@/store/menu';
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
@@ -12,12 +13,35 @@ const { Header } = Layout;
|
|||||||
|
|
||||||
const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
|
const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const location = useLocation();
|
||||||
const settingModalRef = useRef<SettingModalRef>(null)
|
const settingModalRef = useRef<SettingModalRef>(null)
|
||||||
const userInfoModalRef = useRef<UserInfoModalRef>(null)
|
const userInfoModalRef = useRef<UserInfoModalRef>(null)
|
||||||
|
|
||||||
const { user, logout } = useUser();
|
const { user, logout } = useUser();
|
||||||
const { allBreadcrumbs } = useMenu();
|
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 = () => {
|
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 => {
|
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) {
|
for (const menu of menus) {
|
||||||
// 检查当前菜单的path是否匹配
|
// 检查当前菜单的path是否匹配
|
||||||
@@ -26,6 +28,7 @@ export const checkRoutePermission = (menus: MenuItem[], currentPath: string): bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ export const useRouteGuard = (source: 'space' | 'manage') => {
|
|||||||
const hasPermission = checkRoutePermission(menus, location.pathname);
|
const hasPermission = checkRoutePermission(menus, location.pathname);
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
// 无权限访问该路由,重定向到无权限页面
|
// 无权限访问该路由,重定向到无权限页面
|
||||||
// navigate('/not-found', { replace: true });
|
// navigate('/no-permission', { replace: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [navigate, location.pathname, location.search, location.hash, menus]);
|
}, [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.',
|
noDataSets: 'No datasets yet, click the button below or drag files to create.',
|
||||||
createEmptyDataSet: '+ Empty Dataset',
|
createEmptyDataSet: '+ Empty Dataset',
|
||||||
createImageDataSet: '+ Image 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',
|
dragFilesHere: 'Drag files here to upload',
|
||||||
createImport: 'Create/Import',
|
createImport: 'Create/Import',
|
||||||
textDataSet: 'Text Dataset',
|
textDataSet: 'Text Dataset',
|
||||||
imageDataSet: 'Image Dataset',
|
imageDataSet: 'Image Dataset',
|
||||||
blankDataset: 'Blank Dataset',
|
blankDataset: 'Blank Dataset',
|
||||||
|
customTextDataset: 'Custom Text Dataset',
|
||||||
text: 'Text',
|
text: 'Text',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
image: 'Image',
|
image: 'Image',
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ export const zh = {
|
|||||||
noDataSets: '暂无数据集,点击下方按钮或拖拽文件创建。',
|
noDataSets: '暂无数据集,点击下方按钮或拖拽文件创建。',
|
||||||
createEmptyDataSet: '+ 空白数据集',
|
createEmptyDataSet: '+ 空白数据集',
|
||||||
createImageDataSet: '+ 图片数据集',
|
createImageDataSet: '+ 图片数据集',
|
||||||
|
createContent: '创建内容',
|
||||||
|
title: '标题',
|
||||||
|
content: '内容',
|
||||||
|
pleaseEnterTitle: '请输入标题',
|
||||||
|
pleaseEnterContent: '请输入内容',
|
||||||
dragFilesHere: '拖拽文件到此处上传',
|
dragFilesHere: '拖拽文件到此处上传',
|
||||||
downloadOriginal: '下载原始内容',
|
downloadOriginal: '下载原始内容',
|
||||||
createImport: '新建/导入',
|
createImport: '新建/导入',
|
||||||
@@ -117,6 +122,7 @@ export const zh = {
|
|||||||
imageDataSet: '图片数据集',
|
imageDataSet: '图片数据集',
|
||||||
blankDataset: '空白数据集',
|
blankDataset: '空白数据集',
|
||||||
emptyDataSet: '空白数据集',
|
emptyDataSet: '空白数据集',
|
||||||
|
customTextDataset: '自定义文本数据集',
|
||||||
text: '文本',
|
text: '文本',
|
||||||
search: '搜索',
|
search: '搜索',
|
||||||
image: '图片',
|
image: '图片',
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ interface MenuState {
|
|||||||
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
allBreadcrumbs: Record<'space' | 'manage' | string, MenuItem[]>;
|
||||||
loadMenus: (source: 'space' | 'manage') => void;
|
loadMenus: (source: 'space' | 'manage') => void;
|
||||||
updateBreadcrumbs: (keyPath: string[], 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') || '[]'
|
const initBreadcrumbs = localStorage.getItem('breadcrumbs') || '[]'
|
||||||
|
|||||||
@@ -8,15 +8,14 @@ import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/Knowl
|
|||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import UploadFiles from '@/components/Upload/UploadFiles';
|
import UploadFiles from '@/components/Upload/UploadFiles';
|
||||||
import type { UploadRequestOption } from 'rc-upload/lib/interface';
|
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 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 SliderInput from '@/components/SliderInput';
|
||||||
import DelimiterSelector from '../components/DelimiterSelector';
|
import DelimiterSelector from '../components/DelimiterSelector';
|
||||||
const { confirm } = Modal
|
const { confirm } = Modal
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
import styles from '../index.module.css';
|
|
||||||
const style: React.CSSProperties = {
|
const style: React.CSSProperties = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: 16,
|
gap: 16,
|
||||||
@@ -71,12 +70,11 @@ const CreateDataset = () => {
|
|||||||
const initialFileIds = locationState.fileIds ?? (locationState.fileId ? [locationState.fileId] : []);
|
const initialFileIds = locationState.fileIds ?? (locationState.fileId ? [locationState.fileId] : []);
|
||||||
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
const [current, setCurrent] = useState<number>(stepIndexMap[initialStepKey]);
|
||||||
const tableRef = useRef<TableRef>(null);
|
const tableRef = useRef<TableRef>(null);
|
||||||
|
|
||||||
|
|
||||||
const [data, setData] = useState<KnowledgeBaseDocumentData[]>([]);
|
const [data, setData] = useState<KnowledgeBaseDocumentData[]>([]);
|
||||||
const [chunkData, setChunkData] = useState<any[]>([]);
|
|
||||||
const [total, setTotal] = useState<number>(0);
|
|
||||||
const [rechunkFileIds, setRechunkFileIds] = useState<string[]>(initialFileIds);
|
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 [pollingLoading, setPollingLoading] = useState<boolean>(false);
|
||||||
const pollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
const pollingTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||||
const [delimiter, setDelimiter] = useState<string | undefined>(undefined);
|
const [delimiter, setDelimiter] = useState<string | undefined>(undefined);
|
||||||
@@ -121,6 +119,7 @@ const CreateDataset = () => {
|
|||||||
layout_recognize:'DeepDOC',
|
layout_recognize:'DeepDOC',
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
chunk_token_num: blockSize,
|
chunk_token_num: blockSize,
|
||||||
|
auto_question: processingMethod === 'directBlock' ? 0 : 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateDocument(id, params)
|
updateDocument(id, params)
|
||||||
@@ -145,7 +144,7 @@ const CreateDataset = () => {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugger
|
|
||||||
|
|
||||||
// 显示确认弹框
|
// 显示确认弹框
|
||||||
confirm({
|
confirm({
|
||||||
@@ -168,7 +167,7 @@ const CreateDataset = () => {
|
|||||||
const startProcessing = (autoReturnToList: boolean) => {
|
const startProcessing = (autoReturnToList: boolean) => {
|
||||||
// 触发文档解析
|
// 触发文档解析
|
||||||
rechunkFileIds.map((id) => {
|
rechunkFileIds.map((id) => {
|
||||||
parseDocument(id);
|
parseDocument(id, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 开启 loading
|
// 开启 loading
|
||||||
@@ -276,21 +275,7 @@ const CreateDataset = () => {
|
|||||||
onError?.(error as Error);
|
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: 是否在所有文档完成时自动返回列表页
|
// autoReturn: 是否在所有文档完成时自动返回列表页
|
||||||
@@ -346,6 +331,8 @@ const CreateDataset = () => {
|
|||||||
state: {
|
state: {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
timestamp: Date.now(), // 添加时间戳确保每次都是新的 state
|
timestamp: Date.now(), // 添加时间戳确保每次都是新的 state
|
||||||
|
// 保持返回到原来的文档文件夹位置
|
||||||
|
navigateToDocumentFolder: parentId !== knowledgeBaseId ? parentId : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -565,7 +552,7 @@ const CreateDataset = () => {
|
|||||||
{rechunkFileIds.length > 0 ? (
|
{rechunkFileIds.length > 0 ? (
|
||||||
<Table
|
<Table
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
apiUrl={`/documents/${knowledgeBaseId}/${parentId}/documents`}
|
apiUrl={`/documents/${knowledgeBaseId}/documents`}
|
||||||
apiParams={{
|
apiParams={{
|
||||||
document_ids: rechunkFileIds.join(','),
|
document_ids: rechunkFileIds.join(','),
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
* @Author: yujiangping
|
* @Author: yujiangping
|
||||||
* @Date: 2025-11-15 16:13:47
|
* @Date: 2025-11-15 16:13:47
|
||||||
* @LastEditors: yujiangping
|
* @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 { useEffect, useState, useRef, type FC } from 'react';
|
||||||
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
import { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager';
|
||||||
import { Button, Spin, message, Switch } from 'antd';
|
import { Button, Spin, message, Switch } from 'antd';
|
||||||
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
|
import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase';
|
||||||
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
|
import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types';
|
||||||
@@ -25,7 +26,18 @@ const DocumentDetails: FC = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>();
|
const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>();
|
||||||
const location = useLocation();
|
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 [loading, setLoading] = useState(false);
|
||||||
const [document, setDocument] = useState<KnowledgeBaseDocumentData | null>(null);
|
const [document, setDocument] = useState<KnowledgeBaseDocumentData | null>(null);
|
||||||
const [chunkList, setChunkList] = useState<RecallTestData[]>([]);
|
const [chunkList, setChunkList] = useState<RecallTestData[]>([]);
|
||||||
@@ -44,6 +56,13 @@ const DocumentDetails: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [documentId]);
|
}, [documentId]);
|
||||||
|
|
||||||
|
// 更新面包屑
|
||||||
|
useEffect(() => {
|
||||||
|
if (breadcrumbPath) {
|
||||||
|
updateBreadcrumbs(breadcrumbPath);
|
||||||
|
}
|
||||||
|
}, [breadcrumbPath, updateBreadcrumbs]);
|
||||||
|
|
||||||
// 当文档加载完成且 progress === 1 时,加载分块列表
|
// 当文档加载完成且 progress === 1 时,加载分块列表
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (document && document.progress === 1 && !isManualRefreshRef.current) {
|
if (document && document.progress === 1 && !isManualRefreshRef.current) {
|
||||||
@@ -179,7 +198,18 @@ const DocumentDetails: FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
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`);
|
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 { useNavigate, useParams, useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Switch, Button, Dropdown, Space, Modal, message } from 'antd';
|
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 folderIcon from '@/assets/images/knowledgeBase/folder.png';
|
||||||
import textIcon from '@/assets/images/knowledgeBase/text.png';
|
import textIcon from '@/assets/images/knowledgeBase/text.png';
|
||||||
import editIcon from '@/assets/images/knowledgeBase/edit.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 { getKnowledgeBaseDetail, deleteDocument, downloadFile, updateKnowledgeBase } from '@/api/knowledgeBase';
|
||||||
import type {
|
import {
|
||||||
CreateModalRef,
|
type CreateModalRef,
|
||||||
KnowledgeBaseListItem,
|
type KnowledgeBaseListItem,
|
||||||
RecallTestDrawerRef,
|
type RecallTestDrawerRef,
|
||||||
CreateFolderModalRef,
|
type CreateFolderModalRef,
|
||||||
CreateImageModalRef,
|
type CreateSetModalRef,
|
||||||
ShareModalRef,
|
type ShareModalRef,
|
||||||
CreateDatasetModalRef,FolderFormData,
|
type CreateDatasetModalRef,type FolderFormData,
|
||||||
KnowledgeBaseDocumentData
|
type KnowledgeBaseDocumentData,
|
||||||
} from '@/views/KnowledgeBase/types';
|
} from '@/views/KnowledgeBase/types';
|
||||||
import RecallTestDrawer from '../components/RecallTestDrawer';
|
import RecallTestDrawer from '../components/RecallTestDrawer';
|
||||||
import CreateFolderModal from '../components/CreateFolderModal';
|
import CreateFolderModal from '../components/CreateFolderModal';
|
||||||
|
import CreateContentModal from '../components/CreateContentModal';
|
||||||
import CreateModal from '../components/CreateModal';
|
import CreateModal from '../components/CreateModal';
|
||||||
import ShareModal from '../components/ShareModal';
|
import ShareModal from '../components/ShareModal';
|
||||||
import CreateDatasetModal from '../components/CreateDatasetModal';
|
import CreateDatasetModal from '../components/CreateDatasetModal';
|
||||||
import CreateImageDataset from '../components/CreateImageDataset';
|
import CreateImageDataset from '../components/CreateImageDataset';
|
||||||
import FolderTree, { type TreeNodeData } from '../components/FolderTree';
|
import FolderTree, { type TreeNodeData } from '../components/FolderTree';
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import { useMenu } from '@/store/menu';
|
|
||||||
|
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
|
||||||
import './Private.css'
|
import './Private.css'
|
||||||
const { confirm } = Modal
|
const { confirm } = Modal
|
||||||
// 树节点数据类型
|
// 树节点数据类型
|
||||||
@@ -48,7 +51,8 @@ const Private: FC = () => {
|
|||||||
const [tableApi, setTableApi] = useState<string | undefined>(undefined);
|
const [tableApi, setTableApi] = useState<string | undefined>(undefined);
|
||||||
const recallTestDrawerRef = useRef<RecallTestDrawerRef>(null);
|
const recallTestDrawerRef = useRef<RecallTestDrawerRef>(null);
|
||||||
const createFolderModalRef = useRef<CreateFolderModalRef>(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 [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
|
||||||
const [folder, setFolder] = useState<FolderFormData | null>({
|
const [folder, setFolder] = useState<FolderFormData | null>({
|
||||||
kb_id:knowledgeBaseId ?? '',
|
kb_id:knowledgeBaseId ?? '',
|
||||||
@@ -56,47 +60,47 @@ const Private: FC = () => {
|
|||||||
});
|
});
|
||||||
const [query, setQuery] = useState<Record<string, unknown>>({
|
const [query, setQuery] = useState<Record<string, unknown>>({
|
||||||
orderby: 'created_at',
|
orderby: 'created_at',
|
||||||
desc: true,
|
desc: true
|
||||||
});
|
});
|
||||||
const modalRef = useRef<CreateModalRef>(null)
|
const modalRef = useRef<CreateModalRef>(null)
|
||||||
const shareModalRef = useRef<ShareModalRef>(null);
|
const shareModalRef = useRef<ShareModalRef>(null);
|
||||||
const datasetModalRef = useRef<CreateDatasetModalRef>(null);
|
const datasetModalRef = useRef<CreateDatasetModalRef>(null);
|
||||||
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
|
const [folderTreeRefreshKey, setFolderTreeRefreshKey] = useState(0);
|
||||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
const [autoExpandPath, setAutoExpandPath] = useState<Array<{ id: string; name: string }>>([]);
|
||||||
const [folderPath, setFolderPath] = 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[]>([]);
|
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
||||||
useEffect(() => {
|
const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState<BreadcrumbItem[]>([]);
|
||||||
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 fetchKnowledgeBaseDetail = async (id: string) => {
|
const fetchKnowledgeBaseDetail = async (id: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -109,110 +113,160 @@ const Private: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新面包屑,包含知识库名称和文件夹路径
|
useEffect(() => {
|
||||||
const updateBreadcrumbs = () => {
|
if (knowledgeBaseId) {
|
||||||
if (!knowledgeBase) return;
|
let url = `/documents/${knowledgeBaseId}/documents`;
|
||||||
|
setTableApi(url);
|
||||||
|
fetchKnowledgeBaseDetail(knowledgeBaseId);
|
||||||
|
}
|
||||||
|
}, [knowledgeBaseId]);
|
||||||
|
|
||||||
const baseBreadcrumbs = allBreadcrumbs['space'] || [];
|
// 更新面包屑
|
||||||
// 只保留知识库菜单项之前的面包屑
|
useEffect(() => {
|
||||||
const knowledgeBaseMenuIndex = baseBreadcrumbs.findIndex(item => item.path === '/knowledge-base');
|
if (knowledgeBase) {
|
||||||
const filteredBaseBreadcrumbs = knowledgeBaseMenuIndex >= 0
|
updateBreadcrumbs({
|
||||||
? baseBreadcrumbs.slice(0, knowledgeBaseMenuIndex + 1)
|
knowledgeBaseFolderPath,
|
||||||
: baseBreadcrumbs;
|
knowledgeBase: {
|
||||||
|
id: knowledgeBase.id,
|
||||||
const customBreadcrumbs = [
|
name: knowledgeBase.name,
|
||||||
...filteredBaseBreadcrumbs,
|
type: 'knowledgeBase'
|
||||||
{
|
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
},
|
documentFolderPath: folderPath,
|
||||||
...folderPath.map((folder, index) => ({
|
});
|
||||||
id: 0,
|
}
|
||||||
parent: 0,
|
}, [knowledgeBase, knowledgeBaseFolderPath, folderPath, updateBreadcrumbs]);
|
||||||
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;
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
];
|
|
||||||
|
|
||||||
setCustomBreadcrumbs(customBreadcrumbs, 'space');
|
// 监听 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[]) => {
|
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;
|
if (!folder) return;
|
||||||
|
|
||||||
const f = {
|
const f = {
|
||||||
...folder,
|
...folder,
|
||||||
parent_id: String(keys[0]),
|
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);
|
setTableApi(url);
|
||||||
setParentId(String(keys[0]))
|
setParentId(String(keys[0]))
|
||||||
setFolder(f)
|
setFolder(f)
|
||||||
@@ -253,6 +307,15 @@ const Private: FC = () => {
|
|||||||
datasetModalRef?.current?.handleOpen(knowledgeBase?.id,folder?.parent_id ?? knowledgeBase?.id ?? '');
|
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',
|
// key: '3',
|
||||||
@@ -413,6 +476,21 @@ const Private: FC = () => {
|
|||||||
state: {
|
state: {
|
||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
parentId: parentId ?? knowledgeBaseId,
|
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 () => {
|
const refreshDirectoryTree = async () => {
|
||||||
// 先刷新知识库详情,确保数据是最新的
|
// 先刷新知识库详情,确保数据是最新的
|
||||||
await fetchKnowledgeBaseDetail(knowledgeBase.id);
|
if (knowledgeBase?.id) {
|
||||||
|
await fetchKnowledgeBaseDetail(knowledgeBase.id);
|
||||||
|
}
|
||||||
// 添加短暂延迟,确保后端数据已经完全更新
|
// 添加短暂延迟,确保后端数据已经完全更新
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
await new Promise(resolve => setTimeout(resolve, 300));
|
||||||
// 然后刷新文件夹树
|
// 然后刷新文件夹树
|
||||||
@@ -501,6 +581,7 @@ const Private: FC = () => {
|
|||||||
}
|
}
|
||||||
const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => {
|
const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => {
|
||||||
if (!nodes || nodes.length === 0) {
|
if (!nodes || nodes.length === 0) {
|
||||||
|
// 如果没有节点,设置folder为null(这会隐藏FolderTree)
|
||||||
setFolder(null);
|
setFolder(null);
|
||||||
} else {
|
} else {
|
||||||
// 如果有节点且 folder 为 null,重新设置 folder
|
// 如果有节点且 folder 为 null,重新设置 folder
|
||||||
@@ -524,6 +605,7 @@ const Private: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleRefreshTable = () => {
|
const handleRefreshTable = () => {
|
||||||
|
debugger
|
||||||
// 刷新表格数据
|
// 刷新表格数据
|
||||||
tableRef.current?.loadData();
|
tableRef.current?.loadData();
|
||||||
}
|
}
|
||||||
@@ -545,6 +627,7 @@ const Private: FC = () => {
|
|||||||
onRootLoad={handleRootTreeLoad}
|
onRootLoad={handleRootTreeLoad}
|
||||||
onFolderPathChange={handleFolderPathChange}
|
onFolderPathChange={handleFolderPathChange}
|
||||||
selectedKeys={selectedKeys}
|
selectedKeys={selectedKeys}
|
||||||
|
autoExpandPath={autoExpandPath}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -601,6 +684,10 @@ const Private: FC = () => {
|
|||||||
ref={createFolderModalRef}
|
ref={createFolderModalRef}
|
||||||
refreshTable={refreshDirectoryTree}
|
refreshTable={refreshDirectoryTree}
|
||||||
/>
|
/>
|
||||||
|
<CreateContentModal
|
||||||
|
ref={createContentModalRef}
|
||||||
|
refreshTable={handleRefreshTable}
|
||||||
|
/>
|
||||||
<CreateModal
|
<CreateModal
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
refreshTable={handleRefreshTable}
|
refreshTable={handleRefreshTable}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useRef, type FC } from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
|
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
|
||||||
import RecallTest from '../components/RecallTest';
|
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 kbHistoryIcon from '@/assets/images/knowledgeBase/kb-history.png';
|
||||||
import { getKnowledgeBaseDetail } from '@/api/knowledgeBase';
|
import { getKnowledgeBaseDetail } from '@/api/knowledgeBase';
|
||||||
import { formatDateTime } from '@/utils/format';
|
import { formatDateTime } from '@/utils/format';
|
||||||
import { useMenu } from '@/store/menu';
|
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
|
||||||
|
|
||||||
const Share: FC = () => {
|
const Share: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const params = useParams<{ knowledgeBaseId: string }>();
|
const params = useParams<{ knowledgeBaseId: string }>();
|
||||||
|
const location = useLocation();
|
||||||
const knowledgeBaseId = params.knowledgeBaseId;
|
const knowledgeBaseId = params.knowledgeBaseId;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
|
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBaseListItem | null>(null);
|
||||||
const recallTestRef = useRef<RecallTestDrawerRef>(null);
|
const recallTestRef = useRef<RecallTestDrawerRef>(null);
|
||||||
const [infoItems, setInfoItems] = useState<InfoItem[]>([]);
|
const [infoItems, setInfoItems] = useState<InfoItem[]>([]);
|
||||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState<BreadcrumbItem[]>([]);
|
||||||
|
|
||||||
|
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||||
|
breadcrumbType: 'detail'
|
||||||
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Share.tsx - useParams result:', params);
|
console.log('Share.tsx - useParams result:', params);
|
||||||
console.log('Share.tsx - knowledgeBaseId:', knowledgeBaseId);
|
console.log('Share.tsx - knowledgeBaseId:', knowledgeBaseId);
|
||||||
@@ -46,9 +51,30 @@ const Share: FC = () => {
|
|||||||
// 更新面包屑
|
// 更新面包屑
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (knowledgeBase) {
|
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 formatInfoItems = (data: KnowledgeBaseListItem): InfoItem[] => {
|
||||||
const items: 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 = () => {
|
// const handleBack = () => {
|
||||||
// navigate('/knowledge-base');
|
// 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 { Form, Input } from 'antd';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { UploadFile } from 'antd';
|
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 type { UploadRequestOption } from 'rc-upload/lib/interface';
|
||||||
import RbModal from '@/components/RbModal';
|
import RbModal from '@/components/RbModal';
|
||||||
import UploadFiles from '@/components/Upload/UploadFiles';
|
import UploadFiles from '@/components/Upload/UploadFiles';
|
||||||
@@ -13,7 +13,7 @@ interface ImageDatasetFormData {
|
|||||||
images: UploadFile[];
|
images: UploadFile[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateImageDataset = forwardRef<CreateImageModalRef, CreateImageMoealRefProps>(
|
const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>(
|
||||||
({ refreshTable }, ref) => {
|
({ refreshTable }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ interface FolderTreeProps {
|
|||||||
onRootLoad?: (nodes: TreeNodeData[] | null) => void;
|
onRootLoad?: (nodes: TreeNodeData[] | null) => void;
|
||||||
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
|
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
|
||||||
selectedKeys?: React.Key[];
|
selectedKeys?: React.Key[];
|
||||||
|
// 新增:自动展开到指定路径
|
||||||
|
autoExpandPath?: Array<{ id: string; name: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderIcon = (icon?: string) => {
|
const renderIcon = (icon?: string) => {
|
||||||
@@ -275,8 +277,11 @@ const FolderTree: FC<FolderTreeProps> = ({
|
|||||||
onRootLoad,
|
onRootLoad,
|
||||||
onFolderPathChange,
|
onFolderPathChange,
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
|
autoExpandPath,
|
||||||
}) => {
|
}) => {
|
||||||
const [treeData, setTreeData] = useState<TreeNodeData[]>([]);
|
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[] => {
|
const updateTreeData = (nodes: TreeNodeData[], key: Key, children: TreeNodeData[]): TreeNodeData[] => {
|
||||||
@@ -370,6 +375,109 @@ const FolderTree: FC<FolderTreeProps> = ({
|
|||||||
return null;
|
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) => {
|
const handleSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
|
||||||
if (selectedKeys.length > 0) {
|
if (selectedKeys.length > 0) {
|
||||||
@@ -391,11 +499,13 @@ const FolderTree: FC<FolderTreeProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DirectoryTree
|
<DirectoryTree
|
||||||
|
key={refreshKey} // 添加key确保refreshKey变化时重新渲染整个组件
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onExpand={onExpand}
|
onExpand={handleExpand}
|
||||||
|
expandedKeys={expandedKeys}
|
||||||
loadData={onLoadData}
|
loadData={onLoadData}
|
||||||
treeData={treeNodes}
|
treeData={treeNodes}
|
||||||
selectedKeys={selectedKeys}
|
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 { Row, Col, Button, Dropdown, Modal, message, Tooltip } from 'antd'
|
||||||
import type { MenuProps } from 'antd';
|
import type { MenuProps } from 'antd';
|
||||||
import { EllipsisOutlined } from '@ant-design/icons';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -18,7 +18,8 @@ import Empty from '@/components/Empty'
|
|||||||
import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from '@/api/knowledgeBase'
|
import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from '@/api/knowledgeBase'
|
||||||
const { confirm } = Modal;
|
const { confirm } = Modal;
|
||||||
import InfiniteScroll from 'react-infinite-scroll-component';
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
||||||
import { useMenu } from '@/store/menu';
|
|
||||||
|
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
|
||||||
|
|
||||||
type ModelMenuInfo = {
|
type ModelMenuInfo = {
|
||||||
menu: NonNullable<MenuProps['items']>;
|
menu: NonNullable<MenuProps['items']>;
|
||||||
@@ -28,6 +29,7 @@ type ModelMenuInfo = {
|
|||||||
const KnowledgeBaseManagement: FC = () => {
|
const KnowledgeBaseManagement: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState<KnowledgeBaseListItem[]>([])
|
const [data, setData] = useState<KnowledgeBaseListItem[]>([])
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
@@ -42,10 +44,29 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
const modelListCache = useRef<Record<string, string>>({});
|
const modelListCache = useRef<Record<string, string>>({});
|
||||||
const modalRef = useRef<CreateModalRef>(null)
|
const modalRef = useRef<CreateModalRef>(null)
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
|
const processedStateRef = useRef<any>(null);
|
||||||
|
|
||||||
// 使用 menu store 管理面包屑
|
// 使用面包屑管理 Hook
|
||||||
const { allBreadcrumbs, setCustomBreadcrumbs } = useMenu();
|
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||||
const [folderPath, setFolderPath] = useState<Array<{ id: string; name: string }>>([]);
|
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)
|
// 生成下拉菜单项(根据当前 item)
|
||||||
@@ -134,7 +155,7 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
handleCreate(type);
|
handleCreate(type);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}, [knowledgeBaseTypes, t, folderPath, query]);
|
}, [knowledgeBaseTypes, t]);
|
||||||
const typeToFieldKey = (type: string) => {
|
const typeToFieldKey = (type: string) => {
|
||||||
const normalized = (type || '').toLowerCase();
|
const normalized = (type || '').toLowerCase();
|
||||||
switch (normalized) {
|
switch (normalized) {
|
||||||
@@ -371,90 +392,72 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
}));
|
}));
|
||||||
return;
|
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 {
|
} 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(() => {
|
useEffect(() => {
|
||||||
updateBreadcrumbs();
|
updateBreadcrumbs({
|
||||||
}, [folderPath]);
|
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(() => {
|
useEffect(() => {
|
||||||
fetchModelTypes();
|
fetchModelTypes();
|
||||||
@@ -465,7 +468,7 @@ const KnowledgeBaseManagement: FC = () => {
|
|||||||
if (modelTypes.length) {
|
if (modelTypes.length) {
|
||||||
fetchData(1, false);
|
fetchData(1, false);
|
||||||
}
|
}
|
||||||
}, [modelTypes, query])
|
}, [modelTypes, query.parent_id, query.keywords, query.orderby, query.desc])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -146,11 +146,19 @@ export interface CreateFolderModalRefProps{
|
|||||||
refreshTable?: () => void;
|
refreshTable?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//他建图片数据集
|
//创建图片数据集 / 创建自定义文本数据集
|
||||||
export interface CreateImageModalRef{
|
export interface CreateSetModalRef{
|
||||||
handleOpen: (kb_id:string,parent_id:string) => void;
|
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;
|
refreshTable?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user