Sync frontend project from dev-yjp branch
- Updated web folder with latest frontend code - Added new components: CreateContentModal, CreateContentModalExample - Added new hook: useBreadcrumbManager - Updated knowledge base components and views - Updated i18n translations - Various bug fixes and improvements
This commit is contained in:
@@ -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
|
||||
@@ -545,6 +626,7 @@ const Private: FC = () => {
|
||||
onRootLoad={handleRootTreeLoad}
|
||||
onFolderPathChange={handleFolderPathChange}
|
||||
selectedKeys={selectedKeys}
|
||||
autoExpandPath={autoExpandPath}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -601,6 +683,10 @@ const Private: FC = () => {
|
||||
ref={createFolderModalRef}
|
||||
refreshTable={refreshDirectoryTree}
|
||||
/>
|
||||
<CreateContentModal
|
||||
ref={createContentModalRef}
|
||||
refreshTable={handleRefreshTable}
|
||||
/>
|
||||
<CreateModal
|
||||
ref={modalRef}
|
||||
refreshTable={handleRefreshTable}
|
||||
|
||||
Reference in New Issue
Block a user