feat(knowledge): enhance knowledge base detail page with embedded views
This commit is contained in:
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import ProfileForm, { type ProfileFormHandle } from '@/pages/setting/components/ProfileForm';
|
import ProfileForm, { type ProfileFormHandle } from '@/pages/setting/components/ProfileForm';
|
||||||
import { useProfileSetting } from '@/hooks/setting-hooks';
|
import { useProfileSetting } from '@/hooks/setting-hooks';
|
||||||
import type { IUserInfo } from '@/interfaces/database/user-setting';
|
import type { IUserInfo } from '@/interfaces/database/user-setting';
|
||||||
|
import { useUserData } from '@/hooks/useUserData';
|
||||||
|
|
||||||
interface ProfileFormDialogProps {
|
interface ProfileFormDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -17,12 +18,14 @@ interface ProfileFormDialogProps {
|
|||||||
const ProfileFormDialog: React.FC<ProfileFormDialogProps> = ({ open, onClose }) => {
|
const ProfileFormDialog: React.FC<ProfileFormDialogProps> = ({ open, onClose }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo, updateUserInfo } = useProfileSetting();
|
const { userInfo, updateUserInfo } = useProfileSetting();
|
||||||
|
const { refreshUserData } = useUserData();
|
||||||
const formRef = useRef<ProfileFormHandle>(null);
|
const formRef = useRef<ProfileFormHandle>(null);
|
||||||
|
|
||||||
const handleSubmit = useCallback(async (data: Partial<IUserInfo>) => {
|
const handleSubmit = useCallback(async (data: Partial<IUserInfo>) => {
|
||||||
await updateUserInfo(data);
|
await updateUserInfo(data);
|
||||||
|
await refreshUserData();
|
||||||
onClose();
|
onClose();
|
||||||
}, [updateUserInfo, onClose]);
|
}, [updateUserInfo, onClose, refreshUserData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export default {
|
|||||||
description: 'Description',
|
description: 'Description',
|
||||||
confirm: 'Confirm',
|
confirm: 'Confirm',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
|
disabled: 'Disabled',
|
||||||
clearFilter: 'Clear Filter',
|
clearFilter: 'Clear Filter',
|
||||||
confirmFilter: 'Confirm Filter',
|
confirmFilter: 'Confirm Filter',
|
||||||
private: 'Private',
|
private: 'Private',
|
||||||
@@ -463,7 +464,7 @@ export default {
|
|||||||
file: 'File',
|
file: 'File',
|
||||||
dataset: 'Dataset',
|
dataset: 'Dataset',
|
||||||
testing: 'Retrieval testing',
|
testing: 'Retrieval testing',
|
||||||
files: 'files',
|
files: 'Files List',
|
||||||
configuration: 'Configuration',
|
configuration: 'Configuration',
|
||||||
knowledgeGraph: 'Knowledge Graph',
|
knowledgeGraph: 'Knowledge Graph',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export default {
|
|||||||
description: '描述',
|
description: '描述',
|
||||||
confirm: '确认',
|
confirm: '确认',
|
||||||
enabled: '已启用',
|
enabled: '已启用',
|
||||||
|
disabled: '已禁用',
|
||||||
clearFilter: '清空筛选',
|
clearFilter: '清空筛选',
|
||||||
confirmFilter: '确认筛选',
|
confirmFilter: '确认筛选',
|
||||||
private: '私有',
|
private: '私有',
|
||||||
@@ -459,7 +460,7 @@ export default {
|
|||||||
testing: '检索测试',
|
testing: '检索测试',
|
||||||
configuration: '配置',
|
configuration: '配置',
|
||||||
knowledgeGraph: '知识图谱',
|
knowledgeGraph: '知识图谱',
|
||||||
files: '个文件',
|
files: '文件列表',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
namePlaceholder: '请输入名称',
|
namePlaceholder: '请输入名称',
|
||||||
doc: '文档',
|
doc: '文档',
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import {
|
|||||||
InsertDriveFile as FileIcon,
|
InsertDriveFile as FileIcon,
|
||||||
Upload as UploadIcon,
|
Upload as UploadIcon,
|
||||||
BugReportOutlined as ProcessIcon,
|
BugReportOutlined as ProcessIcon,
|
||||||
|
Download as DownloadIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { DataGrid, type GridColDef, type GridRowSelectionModel } from '@mui/x-data-grid';
|
import { DataGrid, type GridColDef, type GridRowSelectionModel } from '@mui/x-data-grid';
|
||||||
import { zhCN, enUS } from '@mui/x-data-grid/locales';
|
import { zhCN, enUS } from '@mui/x-data-grid/locales';
|
||||||
@@ -56,6 +57,7 @@ import { RunningStatus } from '@/constants/knowledge';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { LanguageAbbreviation } from '@/constants/common';
|
import { LanguageAbbreviation } from '@/constants/common';
|
||||||
|
import knowledgeService from '@/services/knowledge_service';
|
||||||
|
|
||||||
|
|
||||||
interface DocumentListComponentProps {
|
interface DocumentListComponentProps {
|
||||||
@@ -172,6 +174,9 @@ const getRunStatusChip = (run: RunningStatus, progress: number) => {
|
|||||||
return <Chip label={config.label} color={config.color} size="small" />;
|
return <Chip label={config.label} color={config.color} size="small" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档列表组件
|
||||||
|
*/
|
||||||
const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
||||||
files,
|
files,
|
||||||
loading,
|
loading,
|
||||||
@@ -305,6 +310,32 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const docId = selectedFileIdRef.current || selectedFileId;
|
||||||
|
const fileName = selectedFileRef.current?.name || selectedFile?.name || 'document';
|
||||||
|
if (!docId) return;
|
||||||
|
const fileResponse: { data: Blob } = await knowledgeService.getDocumentFile({ doc_id: docId });
|
||||||
|
if (fileResponse?.data instanceof Blob) {
|
||||||
|
const url = URL.createObjectURL(fileResponse.data);
|
||||||
|
if (window?.document) {
|
||||||
|
const link = window.document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileName;
|
||||||
|
window.document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
window.document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('下载文件失败:', err);
|
||||||
|
} finally {
|
||||||
|
handleMenuClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleRunStatusChange = (event: SelectChangeEvent<string[]>) => {
|
const handleRunStatusChange = (event: SelectChangeEvent<string[]>) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
setSelectedRunStatus(typeof value === 'string' ? value.split(',') : value);
|
setSelectedRunStatus(typeof value === 'string' ? value.split(',') : value);
|
||||||
@@ -630,7 +661,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* 右键菜单 */}
|
{/* 菜单 */}
|
||||||
<Menu
|
<Menu
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
open={Boolean(anchorEl)}
|
open={Boolean(anchorEl)}
|
||||||
@@ -640,6 +671,10 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
<ViewIcon sx={{ mr: 1 }} />
|
<ViewIcon sx={{ mr: 1 }} />
|
||||||
<Typography>{t('knowledge.viewDetails')}</Typography>
|
<Typography>{t('knowledge.viewDetails')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem onClick={handleDownload}>
|
||||||
|
<DownloadIcon sx={{ mr: 1 }} />
|
||||||
|
<Typography>{t('chunkPage.downloadFile')}</Typography>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem onClick={handleRename}>
|
<MenuItem onClick={handleRename}>
|
||||||
<EditIcon sx={{ mr: 1 }} />
|
<EditIcon sx={{ mr: 1 }} />
|
||||||
<Typography>{t('common.rename')}</Typography>
|
<Typography>{t('common.rename')}</Typography>
|
||||||
@@ -674,7 +709,9 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
{/* 重命名对话框 */}
|
{/* 重命名对话框 */}
|
||||||
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
||||||
<DialogTitle>{t('knowledge.renameFile')}</DialogTitle>
|
<DialogTitle>{t('knowledge.renameFile')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent sx={{
|
||||||
|
minWidth: 200
|
||||||
|
}}>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="dense"
|
margin="dense"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
Chip,
|
Chip,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
|
Fab,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { type GridRowSelectionModel } from '@mui/x-data-grid';
|
import { type GridRowSelectionModel } from '@mui/x-data-grid';
|
||||||
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||||
@@ -28,9 +29,11 @@ import type { IDocumentInfoFilter } from '@/interfaces/database/document';
|
|||||||
import FileUploadDialog from '@/components/FileUploadDialog';
|
import FileUploadDialog from '@/components/FileUploadDialog';
|
||||||
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
||||||
import DocumentListComponent from './components/DocumentListComponent';
|
import DocumentListComponent from './components/DocumentListComponent';
|
||||||
import FloatingActionButtons from './components/FloatingActionButtons';
|
import KnowledgeBaseTesting from './testing';
|
||||||
|
import KnowledgeLogsPage from './overview';
|
||||||
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||||
import KnowledgeGraphView from './components/KnowledgeGraphView';
|
import KnowledgeGraphView from './components/KnowledgeGraphView';
|
||||||
|
import { Settings as SettingsIcon } from '@mui/icons-material';
|
||||||
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
||||||
import { RunningStatus } from '@/constants/knowledge';
|
import { RunningStatus } from '@/constants/knowledge';
|
||||||
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
||||||
@@ -54,8 +57,8 @@ function KnowledgeBaseDetail() {
|
|||||||
const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false);
|
const [processDetailsDialogOpen, setProcessDetailsDialogOpen] = useState(false);
|
||||||
const [selectedFileDetails, setSelectedFileDetails] = useState<IKnowledgeFile | null>(null);
|
const [selectedFileDetails, setSelectedFileDetails] = useState<IKnowledgeFile | null>(null);
|
||||||
|
|
||||||
// 标签页状态
|
// 标签页状态(documents / testing / overview / graph)
|
||||||
const [currentTab, setCurrentTab] = useState(0);
|
const [currentTab, setCurrentTab] = useState<'documents' | 'testing' | 'overview' | 'graph'>('documents');
|
||||||
|
|
||||||
// 轮询相关状态
|
// 轮询相关状态
|
||||||
const pollingIntervalRef = useRef<any>(null);
|
const pollingIntervalRef = useRef<any>(null);
|
||||||
@@ -296,117 +299,95 @@ function KnowledgeBaseDetail() {
|
|||||||
{/* 知识库信息卡片 */}
|
{/* 知识库信息卡片 */}
|
||||||
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
||||||
|
|
||||||
{/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */}
|
{/* 标签页组件 - 默认显示 documents/testing/overview;Graph 仅在有数据时显示 */}
|
||||||
{showKnowledgeGraph ? (
|
<Box sx={{ mt: 3 }}>
|
||||||
<Box sx={{ mt: 3 }}>
|
<Tabs
|
||||||
<Tabs
|
value={currentTab}
|
||||||
value={currentTab}
|
onChange={(event, newValue) => setCurrentTab(newValue)}
|
||||||
onChange={(event, newValue) => setCurrentTab(newValue)}
|
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
||||||
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
>
|
||||||
>
|
<Tab value="documents" label={t('knowledgeDetails.files')} />
|
||||||
<Tab label={t('knowledgeDetails.documents')} />
|
<Tab value="testing" label={t('knowledgeDetails.testing')} />
|
||||||
<Tab label={t('knowledgeDetails.graph')} />
|
<Tab value="overview" label={t('knowledgeDetails.datasetLogs')} />
|
||||||
</Tabs>
|
{showKnowledgeGraph && (
|
||||||
|
<Tab value="graph" label={t('knowledgeDetails.graph')} />
|
||||||
{/* Document List 标签页内容 */}
|
|
||||||
{currentTab === 0 && (
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<DocumentListComponent
|
|
||||||
files={files}
|
|
||||||
loading={filesLoading}
|
|
||||||
searchKeyword={searchKeyword}
|
|
||||||
onSearchChange={setSearchKeyword}
|
|
||||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
|
||||||
onDelete={(fileIds) => {
|
|
||||||
setRowSelectionModel({
|
|
||||||
type: 'include',
|
|
||||||
ids: new Set(fileIds)
|
|
||||||
});
|
|
||||||
setDeleteDialogOpen(true);
|
|
||||||
}}
|
|
||||||
onUpload={() => setUploadDialogOpen(true)}
|
|
||||||
onRefresh={() => {
|
|
||||||
refreshFiles();
|
|
||||||
fetchKnowledgeDetail();
|
|
||||||
}}
|
|
||||||
rowSelectionModel={rowSelectionModel}
|
|
||||||
onRowSelectionModelChange={(newModel) => {
|
|
||||||
setRowSelectionModel(newModel);
|
|
||||||
}}
|
|
||||||
total={total}
|
|
||||||
page={currentPage}
|
|
||||||
pageSize={pageSize}
|
|
||||||
documentFilter={documentsFilter}
|
|
||||||
onSelectedFilterChange={(filterBody) => {
|
|
||||||
fetchDocuments({}, filterBody);
|
|
||||||
}}
|
|
||||||
onPageChange={setCurrentPage}
|
|
||||||
onPageSizeChange={setPageSize}
|
|
||||||
onRename={handleRename}
|
|
||||||
onChangeStatus={handleChangeStatus}
|
|
||||||
onCancelRun={handleCancelRun}
|
|
||||||
onViewDetails={handleViewDetails}
|
|
||||||
onViewProcessDetails={handleViewProcessDetails}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
{/* Graph 标签页内容 */}
|
{/* Documents 标签页内容 */}
|
||||||
{currentTab === 1 && (
|
{currentTab === 'documents' && (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<KnowledgeGraphView knowledgeGraph={knowledgeGraph} />
|
<DocumentListComponent
|
||||||
</Box>
|
files={files}
|
||||||
)}
|
loading={filesLoading}
|
||||||
</Box>
|
searchKeyword={searchKeyword}
|
||||||
) : (
|
onSearchChange={setSearchKeyword}
|
||||||
/* 原有的文件列表组件 - 当showKnowledgeGraph为false时显示 */
|
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||||
<DocumentListComponent
|
onDelete={(fileIds) => {
|
||||||
files={files}
|
setRowSelectionModel({
|
||||||
loading={filesLoading}
|
type: 'include',
|
||||||
searchKeyword={searchKeyword}
|
ids: new Set(fileIds)
|
||||||
onSearchChange={setSearchKeyword}
|
});
|
||||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
setDeleteDialogOpen(true);
|
||||||
onDelete={(fileIds) => {
|
}}
|
||||||
console.log(t('knowledgeDetails.deleteFiles'), fileIds);
|
onUpload={() => setUploadDialogOpen(true)}
|
||||||
setRowSelectionModel({
|
onRefresh={() => {
|
||||||
type: 'include',
|
refreshFiles();
|
||||||
ids: new Set(fileIds)
|
fetchKnowledgeDetail();
|
||||||
});
|
}}
|
||||||
setDeleteDialogOpen(true);
|
rowSelectionModel={rowSelectionModel}
|
||||||
}}
|
onRowSelectionModelChange={(newModel) => {
|
||||||
onUpload={() => setUploadDialogOpen(true)}
|
setRowSelectionModel(newModel);
|
||||||
onRefresh={() => {
|
}}
|
||||||
refreshFiles();
|
total={total}
|
||||||
fetchKnowledgeDetail();
|
page={currentPage}
|
||||||
}}
|
pageSize={pageSize}
|
||||||
rowSelectionModel={rowSelectionModel}
|
documentFilter={documentsFilter}
|
||||||
onRowSelectionModelChange={(newModel) => {
|
onSelectedFilterChange={(filterBody) => {
|
||||||
console.log(t('knowledgeDetails.newSelectionModel'), newModel);
|
fetchDocuments({}, filterBody);
|
||||||
setRowSelectionModel(newModel);
|
}}
|
||||||
}}
|
onPageChange={setCurrentPage}
|
||||||
total={total}
|
onPageSizeChange={setPageSize}
|
||||||
page={currentPage}
|
onRename={handleRename}
|
||||||
pageSize={pageSize}
|
onChangeStatus={handleChangeStatus}
|
||||||
documentFilter={documentsFilter}
|
onCancelRun={handleCancelRun}
|
||||||
onSelectedFilterChange={(filterBody) => {
|
onViewDetails={handleViewDetails}
|
||||||
fetchDocuments({}, filterBody);
|
onViewProcessDetails={handleViewProcessDetails}
|
||||||
}}
|
/>
|
||||||
onPageChange={setCurrentPage}
|
</Box>
|
||||||
onPageSizeChange={setPageSize}
|
)}
|
||||||
onRename={handleRename}
|
|
||||||
onChangeStatus={handleChangeStatus}
|
|
||||||
onCancelRun={handleCancelRun}
|
|
||||||
onViewDetails={handleViewDetails}
|
|
||||||
onViewProcessDetails={handleViewProcessDetails}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 浮动操作按钮 */}
|
{/* Testing 标签页内容 */}
|
||||||
<FloatingActionButtons
|
{currentTab === 'testing' && (
|
||||||
onTestClick={() => navigate(`/knowledge/${id}/testing`)}
|
<Box sx={{ mt: 2 }}>
|
||||||
onConfigClick={() => navigate(`/knowledge/${id}/setting`)}
|
<KnowledgeBaseTesting embedded kbId={id || ''} />
|
||||||
onOverviewClick={() => navigate(`/knowledge/${id}/overview`)}
|
</Box>
|
||||||
/>
|
)}
|
||||||
|
|
||||||
|
{/* Overview 标签页内容 */}
|
||||||
|
{currentTab === 'overview' && (
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<KnowledgeLogsPage embedded kbId={id || ''} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Graph 标签页内容 */}
|
||||||
|
{currentTab === 'graph' && showKnowledgeGraph && (
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<KnowledgeGraphView knowledgeGraph={knowledgeGraph} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 设置按钮(替代原浮动操作按钮) */}
|
||||||
|
<Fab
|
||||||
|
color="primary"
|
||||||
|
aria-label={t('knowledgeSettings.knowledgeBaseSettings')}
|
||||||
|
onClick={() => navigate(`/knowledge/${id}/setting`)}
|
||||||
|
sx={{ position: 'fixed', bottom: 128, right: 64 }}
|
||||||
|
>
|
||||||
|
<SettingsIcon />
|
||||||
|
</Fab>
|
||||||
|
|
||||||
{/* 上传文件对话框 */}
|
{/* 上传文件对话框 */}
|
||||||
<FileUploadDialog
|
<FileUploadDialog
|
||||||
|
|||||||
@@ -27,7 +27,12 @@ const ProcessingTypeMap = {
|
|||||||
[PROCESSING_TYPES.raptor]: 'RAPTOR',
|
[PROCESSING_TYPES.raptor]: 'RAPTOR',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
function KnowledgeLogsPage() {
|
interface KnowledgeLogsPageProps {
|
||||||
|
embedded?: boolean;
|
||||||
|
kbId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function KnowledgeLogsPage({ embedded = false, kbId: kbIdProp }: KnowledgeLogsPageProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [paginationModel, setPaginationModel] = React.useState({ page: 0, pageSize: 50 });
|
const [paginationModel, setPaginationModel] = React.useState({ page: 0, pageSize: 50 });
|
||||||
|
|
||||||
@@ -38,7 +43,8 @@ function KnowledgeLogsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 路由参数与数据Hook
|
// 路由参数与数据Hook
|
||||||
const { id: kbId = '' } = useParams();
|
const { id: kbIdParam = '' } = useParams();
|
||||||
|
const kbId = kbIdProp || kbIdParam;
|
||||||
const {
|
const {
|
||||||
overview,
|
overview,
|
||||||
fileLogs,
|
fileLogs,
|
||||||
@@ -105,23 +111,25 @@ function KnowledgeLogsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: embedded ? 0 : 3 }}>
|
||||||
{/* 面包屑导航 */}
|
{/* 面包屑导航 */}
|
||||||
<KnowledgeBreadcrumbs
|
{!embedded && (
|
||||||
kbItems={[
|
<KnowledgeBreadcrumbs
|
||||||
{
|
kbItems={[
|
||||||
label: t('knowledgeSettings.knowledgeBase'),
|
{
|
||||||
path: '/knowledge'
|
label: t('knowledgeSettings.knowledgeBase'),
|
||||||
},
|
path: '/knowledge'
|
||||||
{
|
},
|
||||||
label: knowledge?.name || t('knowledgeSettings.knowledgeBaseDetail'),
|
{
|
||||||
path: `/knowledge/${kbId}`
|
label: knowledge?.name || t('knowledgeSettings.knowledgeBaseDetail'),
|
||||||
},
|
path: `/knowledge/${kbId}`
|
||||||
{
|
},
|
||||||
label: t('knowledgeSettings.fileLogs')
|
{
|
||||||
}
|
label: t('knowledgeSettings.fileLogs')
|
||||||
]}
|
}
|
||||||
/>
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{/* 顶部统计卡片占位 */}
|
{/* 顶部统计卡片占位 */}
|
||||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
<Grid container spacing={2} sx={{ mb: 2 }}>
|
||||||
<Grid size={{ xs: 12, md: 4 }}>
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
@@ -183,18 +191,20 @@ function KnowledgeLogsPage() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* 返回按钮 */}
|
{/* 返回按钮 */}
|
||||||
<Fab
|
{!embedded && (
|
||||||
color="primary"
|
<Fab
|
||||||
aria-label={t('knowledgeSettings.backToKnowledgeDetail')}
|
color="primary"
|
||||||
onClick={handleNavigateBack}
|
aria-label={t('knowledgeSettings.backToKnowledgeDetail')}
|
||||||
sx={{
|
onClick={handleNavigateBack}
|
||||||
position: 'fixed',
|
sx={{
|
||||||
bottom: 128,
|
position: 'fixed',
|
||||||
right: 64,
|
bottom: 128,
|
||||||
}}
|
right: 64,
|
||||||
>
|
}}
|
||||||
<ArrowBackIcon />
|
>
|
||||||
</Fab>
|
<ArrowBackIcon />
|
||||||
|
</Fab>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,15 @@ interface TestFormData {
|
|||||||
doc_ids?: string[];
|
doc_ids?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function KnowledgeBaseTesting() {
|
interface KnowledgeBaseTestingProps {
|
||||||
|
embedded?: boolean;
|
||||||
|
kbId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function KnowledgeBaseTesting({ embedded = false, kbId }: KnowledgeBaseTestingProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id: idParam } = useParams<{ id: string }>();
|
||||||
|
const id = kbId || idParam || '';
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
@@ -230,24 +236,26 @@ function KnowledgeBaseTesting() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
<Container maxWidth="lg" sx={{ py: embedded ? 0 : 4 }}>
|
||||||
|
|
||||||
{/* 面包屑导航 */}
|
{/* 面包屑导航 */}
|
||||||
<KnowledgeBreadcrumbs
|
{!embedded && (
|
||||||
kbItems={[
|
<KnowledgeBreadcrumbs
|
||||||
{
|
kbItems={[
|
||||||
label: t('knowledgeTesting.knowledgeBase'),
|
{
|
||||||
path: '/knowledge'
|
label: t('knowledgeTesting.knowledgeBase'),
|
||||||
},
|
path: '/knowledge'
|
||||||
{
|
},
|
||||||
label: knowledgeDetail?.name || t('knowledgeTesting.knowledgeBaseDetail'),
|
{
|
||||||
path: `/knowledge/${id}`
|
label: knowledgeDetail?.name || t('knowledgeTesting.knowledgeBaseDetail'),
|
||||||
},
|
path: `/knowledge/${id}`
|
||||||
{
|
},
|
||||||
label: t('knowledgeTesting.testing')
|
{
|
||||||
}
|
label: t('knowledgeTesting.testing')
|
||||||
]}
|
}
|
||||||
/>
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import ProfileForm from "./components/ProfileForm";
|
|||||||
import ChangePasswordDialog from "./components/ChangePasswordDialog";
|
import ChangePasswordDialog from "./components/ChangePasswordDialog";
|
||||||
import { useProfileSetting } from "@/hooks/setting-hooks";
|
import { useProfileSetting } from "@/hooks/setting-hooks";
|
||||||
import logger from "@/utils/logger";
|
import logger from "@/utils/logger";
|
||||||
|
import { useUserData } from "@/hooks/useUserData";
|
||||||
|
|
||||||
function ProfileSetting() {
|
function ProfileSetting() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo, updateUserInfo: updateUserInfoFunc, changeUserPassword: changeUserPasswordFunc } = useProfileSetting();
|
const { userInfo, updateUserInfo: updateUserInfoFunc, changeUserPassword: changeUserPasswordFunc } = useProfileSetting();
|
||||||
|
const { refreshUserData } = useUserData();
|
||||||
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
|
const [passwordDialogOpen, setPasswordDialogOpen] = useState(false);
|
||||||
|
|
||||||
logger.debug('userInfo', userInfo);
|
logger.debug('userInfo', userInfo);
|
||||||
@@ -25,7 +27,10 @@ function ProfileSetting() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ maxWidth: 800, mx: 'auto', p: 3 }}>
|
<Box sx={{ maxWidth: 800, mx: 'auto', p: 3 }}>
|
||||||
{/* 个人资料表单 */}
|
{/* 个人资料表单 */}
|
||||||
<ProfileForm userInfo={userInfo} onSubmit={updateUserInfoFunc} />
|
<ProfileForm userInfo={userInfo} onSubmit={async (data) => {
|
||||||
|
await updateUserInfoFunc(data);
|
||||||
|
await refreshUserData();
|
||||||
|
}} />
|
||||||
|
|
||||||
{/* 分割线 */}
|
{/* 分割线 */}
|
||||||
<Divider sx={{ my: 4 }} />
|
<Divider sx={{ my: 4 }} />
|
||||||
|
|||||||
Reference in New Issue
Block a user