feat(knowledge): restructure knowledge base pages and components
- Implement new setting and testing pages with breadcrumbs
This commit is contained in:
@@ -224,24 +224,12 @@ export const useKnowledgeOperations = () => {
|
|||||||
* 更新知识库基础信息
|
* 更新知识库基础信息
|
||||||
* 包括名称、描述、语言等基本信息
|
* 包括名称、描述、语言等基本信息
|
||||||
*/
|
*/
|
||||||
const updateKnowledgeBasicInfo = useCallback(async (data: {
|
const updateKnowledgeBasicInfo = useCallback(async (data: IKnowledge) => {
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
language?: string;
|
|
||||||
avatar?: any;
|
|
||||||
permission?: string;
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const updateData = {
|
const response = await knowledgeService.updateKnowledge(data);
|
||||||
kb_id: data.id,
|
|
||||||
...data,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await knowledgeService.updateKnowledge(updateData);
|
|
||||||
|
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const parserOptions = [
|
|||||||
{ value: DOCUMENT_PARSER_TYPES.KnowledgeGraph, label: '知识图谱解析器', description: '构建知识图谱结构' },
|
{ value: DOCUMENT_PARSER_TYPES.KnowledgeGraph, label: '知识图谱解析器', description: '构建知识图谱结构' },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface ConfigFormData {
|
export interface ConfigFormData {
|
||||||
parser_id: DocumentParserType;
|
parser_id: DocumentParserType;
|
||||||
chunk_token_count?: number;
|
chunk_token_count?: number;
|
||||||
layout_recognize?: boolean;
|
layout_recognize?: boolean;
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ interface FileListComponentProps {
|
|||||||
onRefresh: () => void;
|
onRefresh: () => void;
|
||||||
rowSelectionModel: GridRowSelectionModel;
|
rowSelectionModel: GridRowSelectionModel;
|
||||||
onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
|
onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
|
||||||
|
// 分页相关props
|
||||||
|
total: number;
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
onPageChange: (page: number) => void;
|
||||||
|
onPageSizeChange: (pageSize: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getFileIcon = (type: string) => {
|
const getFileIcon = (type: string) => {
|
||||||
@@ -104,6 +110,11 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
|
|||||||
onRefresh,
|
onRefresh,
|
||||||
rowSelectionModel,
|
rowSelectionModel,
|
||||||
onRowSelectionModelChange,
|
onRowSelectionModelChange,
|
||||||
|
total,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
onPageChange,
|
||||||
|
onPageSizeChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [selectedFileId, setSelectedFileId] = useState<string>('');
|
const [selectedFileId, setSelectedFileId] = useState<string>('');
|
||||||
@@ -141,10 +152,15 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 过滤文件列表
|
// 处理分页变化
|
||||||
const filteredFiles = files.filter(file =>
|
const handlePaginationModelChange = (model: { page: number; pageSize: number }) => {
|
||||||
file.name.toLowerCase().includes(searchKeyword.toLowerCase())
|
if (model.page !== page - 1) { // DataGrid的page是0-based,我们的是1-based
|
||||||
);
|
onPageChange(model.page + 1);
|
||||||
|
}
|
||||||
|
if (model.pageSize !== pageSize) {
|
||||||
|
onPageSizeChange(model.pageSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// DataGrid 列定义
|
// DataGrid 列定义
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
@@ -278,7 +294,7 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
|
|||||||
{/* 文件列表 */}
|
{/* 文件列表 */}
|
||||||
<Paper sx={{ height: 600, width: '100%' }}>
|
<Paper sx={{ height: 600, width: '100%' }}>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={filteredFiles}
|
rows={files}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
checkboxSelection
|
checkboxSelection
|
||||||
@@ -286,11 +302,13 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
|
|||||||
rowSelectionModel={rowSelectionModel}
|
rowSelectionModel={rowSelectionModel}
|
||||||
onRowSelectionModelChange={onRowSelectionModelChange}
|
onRowSelectionModelChange={onRowSelectionModelChange}
|
||||||
pageSizeOptions={[10, 25, 50, 100]}
|
pageSizeOptions={[10, 25, 50, 100]}
|
||||||
initialState={{
|
paginationMode="server"
|
||||||
pagination: {
|
rowCount={total}
|
||||||
paginationModel: { page: 0, pageSize: 25 },
|
paginationModel={{
|
||||||
},
|
page: page - 1,
|
||||||
|
pageSize: pageSize,
|
||||||
}}
|
}}
|
||||||
|
onPaginationModelChange={handlePaginationModelChange}
|
||||||
localeText={getDataGridLocale().components.MuiDataGrid.defaultProps.localeText}
|
localeText={getDataGridLocale().components.MuiDataGrid.defaultProps.localeText}
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiDataGrid-cell:focus': {
|
'& .MuiDataGrid-cell:focus': {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SpeedDial, SpeedDialAction } from '@mui/material';
|
import { SpeedDial, SpeedDialAction } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
Settings as SettingsIcon,
|
Dashboard as DashboardIcon,
|
||||||
Search as TestIcon,
|
Search as TestIcon,
|
||||||
Settings as ConfigIcon,
|
Settings as ConfigIcon,
|
||||||
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
interface FloatingActionButtonsProps {
|
interface FloatingActionButtonsProps {
|
||||||
@@ -42,7 +43,7 @@ const FloatingActionButtons: React.FC<FloatingActionButtonsProps> = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
icon={<SettingsIcon />}
|
icon={<DashboardIcon />}
|
||||||
>
|
>
|
||||||
{actions.map((action) => (
|
{actions.map((action) => (
|
||||||
<SpeedDialAction
|
<SpeedDialAction
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
Save as SaveIcon,
|
Save as SaveIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
|
||||||
interface BasicFormData {
|
export interface BasicFormData {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
permission: string;
|
permission: string;
|
||||||
|
|||||||
89
src/pages/knowledge/components/KnowledgeBreadcrumbs.tsx
Normal file
89
src/pages/knowledge/components/KnowledgeBreadcrumbs.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { Breadcrumbs, Link, Typography } from '@mui/material';
|
||||||
|
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
||||||
|
|
||||||
|
interface KnowledgeBreadcrumbsProps {
|
||||||
|
knowledge?: IKnowledge | null;
|
||||||
|
sx?: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
const KnowledgeBreadcrumbs: React.FC<KnowledgeBreadcrumbsProps> = ({ knowledge, sx }) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
// 解析当前路径
|
||||||
|
const pathSegments = location.pathname.split('/').filter(Boolean);
|
||||||
|
|
||||||
|
// 生成面包屑项
|
||||||
|
const breadcrumbItems = [];
|
||||||
|
|
||||||
|
// 第一层:知识库列表
|
||||||
|
breadcrumbItems.push({
|
||||||
|
label: '知识库',
|
||||||
|
path: '/knowledge',
|
||||||
|
isLast: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第二层:知识库详情(如果有id)
|
||||||
|
if (id && knowledge) {
|
||||||
|
const isDetailPage = pathSegments.length === 2; // /knowledge/:id
|
||||||
|
breadcrumbItems.push({
|
||||||
|
label: knowledge.name,
|
||||||
|
path: `/knowledge/${id}`,
|
||||||
|
isLast: isDetailPage
|
||||||
|
});
|
||||||
|
|
||||||
|
// 第三层:设置或测试页面
|
||||||
|
if (pathSegments.length === 3) {
|
||||||
|
const lastSegment = pathSegments[2];
|
||||||
|
let label = '';
|
||||||
|
|
||||||
|
switch (lastSegment) {
|
||||||
|
case 'setting':
|
||||||
|
label = '设置';
|
||||||
|
break;
|
||||||
|
case 'testing':
|
||||||
|
label = '测试';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
label = lastSegment;
|
||||||
|
}
|
||||||
|
|
||||||
|
breadcrumbItems.push({
|
||||||
|
label,
|
||||||
|
path: location.pathname,
|
||||||
|
isLast: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Breadcrumbs sx={{ mb: 2, ...sx }}>
|
||||||
|
{breadcrumbItems.map((item, index) => {
|
||||||
|
if (item.isLast) {
|
||||||
|
return (
|
||||||
|
<Typography key={index} color="text.primary">
|
||||||
|
{item.label}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={index}
|
||||||
|
component="button"
|
||||||
|
variant="body1"
|
||||||
|
onClick={() => navigate(item.path)}
|
||||||
|
sx={{ textDecoration: 'none' }}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Breadcrumbs>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KnowledgeBreadcrumbs;
|
||||||
@@ -22,6 +22,7 @@ import FileUploadDialog from '@/components/FileUploadDialog';
|
|||||||
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
|
||||||
import FileListComponent from './components/FileListComponent';
|
import FileListComponent from './components/FileListComponent';
|
||||||
import FloatingActionButtons from './components/FloatingActionButtons';
|
import FloatingActionButtons from './components/FloatingActionButtons';
|
||||||
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||||
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
|
||||||
|
|
||||||
function KnowledgeBaseDetail() {
|
function KnowledgeBaseDetail() {
|
||||||
@@ -45,10 +46,15 @@ function KnowledgeBaseDetail() {
|
|||||||
// 使用新的document hooks
|
// 使用新的document hooks
|
||||||
const {
|
const {
|
||||||
documents: files,
|
documents: files,
|
||||||
|
total,
|
||||||
loading: filesLoading,
|
loading: filesLoading,
|
||||||
error: filesError,
|
error: filesError,
|
||||||
|
currentPage,
|
||||||
|
pageSize,
|
||||||
refresh: refreshFiles,
|
refresh: refreshFiles,
|
||||||
setKeywords,
|
setKeywords,
|
||||||
|
setCurrentPage,
|
||||||
|
setPageSize,
|
||||||
} = useDocumentList(id || '');
|
} = useDocumentList(id || '');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -89,6 +95,7 @@ function KnowledgeBaseDetail() {
|
|||||||
ids: new Set()
|
ids: new Set()
|
||||||
});
|
});
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
|
fetchKnowledgeDetail();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || err.message || '删除文件失败');
|
setError(err.response?.data?.message || err.message || '删除文件失败');
|
||||||
}
|
}
|
||||||
@@ -101,6 +108,7 @@ function KnowledgeBaseDetail() {
|
|||||||
try {
|
try {
|
||||||
await uploadDocuments(kb_id, uploadFiles);
|
await uploadDocuments(kb_id, uploadFiles);
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
|
fetchKnowledgeDetail();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
throw new Error(err.response?.data?.message || err.message || '上传文件失败');
|
throw new Error(err.response?.data?.message || err.message || '上传文件失败');
|
||||||
}
|
}
|
||||||
@@ -161,17 +169,7 @@ function KnowledgeBaseDetail() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
{/* 面包屑导航 */}
|
{/* 面包屑导航 */}
|
||||||
<Breadcrumbs sx={{ mb: 2 }}>
|
<KnowledgeBreadcrumbs knowledge={knowledgeBase} />
|
||||||
<Link
|
|
||||||
component="button"
|
|
||||||
variant="body1"
|
|
||||||
onClick={() => navigate('/knowledge')}
|
|
||||||
sx={{ textDecoration: 'none' }}
|
|
||||||
>
|
|
||||||
知识库
|
|
||||||
</Link>
|
|
||||||
<Typography color="text.primary">{knowledgeBase.name}</Typography>
|
|
||||||
</Breadcrumbs>
|
|
||||||
|
|
||||||
{/* 知识库信息卡片 */}
|
{/* 知识库信息卡片 */}
|
||||||
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
|
||||||
@@ -202,12 +200,17 @@ function KnowledgeBaseDetail() {
|
|||||||
console.log('新的选择模型:', newModel);
|
console.log('新的选择模型:', newModel);
|
||||||
setRowSelectionModel(newModel);
|
setRowSelectionModel(newModel);
|
||||||
}}
|
}}
|
||||||
|
total={total}
|
||||||
|
page={currentPage}
|
||||||
|
pageSize={pageSize}
|
||||||
|
onPageChange={setCurrentPage}
|
||||||
|
onPageSizeChange={setPageSize}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 浮动操作按钮 */}
|
{/* 浮动操作按钮 */}
|
||||||
<FloatingActionButtons
|
<FloatingActionButtons
|
||||||
onTestClick={() => setTestingDialogOpen(true)}
|
onTestClick={() => navigate(`/knowledge/${id}/testing`)}
|
||||||
onConfigClick={() => setConfigDialogOpen(true)}
|
onConfigClick={() => navigate(`/knowledge/${id}/setting`)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 上传文件对话框 */}
|
{/* 上传文件对话框 */}
|
||||||
|
|||||||
5
src/pages/knowledge/index.ts
Normal file
5
src/pages/knowledge/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export { default as KnowledgeBaseList } from './list';
|
||||||
|
export { default as KnowledgeBaseCreate } from './create';
|
||||||
|
export { default as KnowledgeBaseDetail } from './detail'
|
||||||
|
export { default as KnowledgeBaseSetting } from './setting';
|
||||||
|
export { default as KnowledgeBaseTesting } from './testing';
|
||||||
225
src/pages/knowledge/setting.tsx
Normal file
225
src/pages/knowledge/setting.tsx
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { useForm, type UseFormReturn } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
Fab,
|
||||||
|
Snackbar,
|
||||||
|
Alert,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
ArrowBack as ArrowBackIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { useKnowledgeDetail, useKnowledgeOperations } from '@/hooks/knowledge-hooks';
|
||||||
|
import GeneralForm, { type BasicFormData } from './components/GeneralForm';
|
||||||
|
import ChunkMethodForm, { type ConfigFormData } from './components/ChunkMethodForm';
|
||||||
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||||
|
import { useSnackbar } from '@/components/Provider/SnackbarProvider';
|
||||||
|
|
||||||
|
interface TabPanelProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
index: number;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabPanel(props: TabPanelProps) {
|
||||||
|
const { children, value, index, ...other } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tabpanel"
|
||||||
|
hidden={value !== index}
|
||||||
|
id={`setting-tabpanel-${index}`}
|
||||||
|
aria-labelledby={`setting-tab-${index}`}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
{value === index && (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function a11yProps(index: number) {
|
||||||
|
return {
|
||||||
|
id: `setting-tab-${index}`,
|
||||||
|
'aria-controls': `setting-tabpanel-${index}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function KnowledgeBaseSetting() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [tabValue, setTabValue] = useState(0);
|
||||||
|
|
||||||
|
// 获取知识库详情
|
||||||
|
const { knowledge, loading: detailLoading, refresh } = useKnowledgeDetail(id || '');
|
||||||
|
const { showMessage } = useSnackbar();
|
||||||
|
|
||||||
|
// 知识库操作hooks
|
||||||
|
const {
|
||||||
|
updateKnowledgeBasicInfo,
|
||||||
|
updateKnowledgeModelConfig,
|
||||||
|
loading: operationLoading
|
||||||
|
} = useKnowledgeOperations();
|
||||||
|
|
||||||
|
// 基础信息表单
|
||||||
|
const basicForm = useForm<BasicFormData>({
|
||||||
|
defaultValues: {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
permission: 'me',
|
||||||
|
avatar: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析配置表单
|
||||||
|
const configForm = useForm<ConfigFormData>({
|
||||||
|
defaultValues: {
|
||||||
|
parser_id: 'naive',
|
||||||
|
chunk_token_count: 512,
|
||||||
|
layout_recognize: false,
|
||||||
|
task_page_size: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当知识库数据加载完成时,更新表单默认值
|
||||||
|
useEffect(() => {
|
||||||
|
if (knowledge) {
|
||||||
|
basicForm.reset({
|
||||||
|
name: knowledge.name || '',
|
||||||
|
description: knowledge.description || '',
|
||||||
|
permission: knowledge.permission || 'me',
|
||||||
|
avatar: knowledge.avatar,
|
||||||
|
});
|
||||||
|
|
||||||
|
configForm.reset({
|
||||||
|
// parser_id: knowledge.parser_id || 'naive',
|
||||||
|
// chunk_token_count: knowledge.chunk_token_count || 512,
|
||||||
|
// layout_recognize: knowledge.layout_recognize || false,
|
||||||
|
// task_page_size: knowledge.task_page_size || 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [knowledge, basicForm, configForm]);
|
||||||
|
|
||||||
|
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
|
setTabValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBasicInfoSubmit = async (data: BasicFormData) => {
|
||||||
|
if (!knowledge) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const kb = {
|
||||||
|
...data,
|
||||||
|
// parser_id: knowledge.parser_id,
|
||||||
|
kb_id: knowledge.id,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
await updateKnowledgeBasicInfo(kb);
|
||||||
|
showMessage.success('基础信息更新成功');
|
||||||
|
// 刷新知识库详情
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
// showMessage.error('基础信息更新失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfigSubmit = async (data: ConfigFormData) => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateKnowledgeModelConfig({
|
||||||
|
id,
|
||||||
|
parser_id: data.parser_id,
|
||||||
|
// 可以根据需要添加更多配置字段
|
||||||
|
});
|
||||||
|
showMessage.success('解析配置更新成功');
|
||||||
|
// 刷新知识库详情
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
// showMessage.error('解析配置更新失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackToDetail = () => {
|
||||||
|
navigate(`/knowledge/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detailLoading) {
|
||||||
|
return (
|
||||||
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
|
<Typography>加载中...</Typography>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
|
{/* 面包屑导航 */}
|
||||||
|
<KnowledgeBreadcrumbs knowledge={knowledge} />
|
||||||
|
|
||||||
|
<Box sx={{ mb: 3 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
知识库设置
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
|
{knowledge?.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper sx={{ width: '100%' }}>
|
||||||
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
|
<Tabs value={tabValue} onChange={handleTabChange} aria-label="设置选项卡">
|
||||||
|
<Tab label="基础信息" {...a11yProps(0)} />
|
||||||
|
<Tab label="解析配置" {...a11yProps(1)} />
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={0}>
|
||||||
|
<GeneralForm
|
||||||
|
form={basicForm}
|
||||||
|
onSubmit={handleBasicInfoSubmit}
|
||||||
|
isSubmitting={operationLoading}
|
||||||
|
submitButtonText="保存基础信息"
|
||||||
|
disabled={detailLoading}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
|
||||||
|
<TabPanel value={tabValue} index={1}>
|
||||||
|
<ChunkMethodForm
|
||||||
|
form={configForm as any}
|
||||||
|
onSubmit={handleConfigSubmit}
|
||||||
|
isSubmitting={operationLoading}
|
||||||
|
submitButtonText="保存解析配置"
|
||||||
|
disabled={detailLoading}
|
||||||
|
/>
|
||||||
|
</TabPanel>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* 返回按钮 */}
|
||||||
|
<Fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="返回知识库详情"
|
||||||
|
onClick={handleBackToDetail}
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 128,
|
||||||
|
right: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</Fab>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KnowledgeBaseSetting;
|
||||||
454
src/pages/knowledge/testing.tsx
Normal file
454
src/pages/knowledge/testing.tsx
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Typography,
|
||||||
|
Paper,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Slider,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
FormControlLabel,
|
||||||
|
Switch,
|
||||||
|
Fab,
|
||||||
|
Snackbar,
|
||||||
|
Alert,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
Chip,
|
||||||
|
Divider,
|
||||||
|
Stack,
|
||||||
|
CircularProgress,
|
||||||
|
} from '@mui/material';
|
||||||
|
import {
|
||||||
|
ArrowBack as ArrowBackIcon,
|
||||||
|
Search as SearchIcon,
|
||||||
|
Psychology as PsychologyIcon,
|
||||||
|
} from '@mui/icons-material';
|
||||||
|
import { useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
||||||
|
import knowledgeService from '@/services/knowledge_service';
|
||||||
|
import type { ITestRetrievalRequestBody } from '@/interfaces/request/knowledge';
|
||||||
|
import type { ITestingResult, ITestingChunk, ITestingDocument } from '@/interfaces/database/knowledge';
|
||||||
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||||
|
|
||||||
|
interface TestFormData {
|
||||||
|
question: string;
|
||||||
|
similarity_threshold: number;
|
||||||
|
vector_similarity_weight: number;
|
||||||
|
rerank_id?: string;
|
||||||
|
top_k: number;
|
||||||
|
use_kg: boolean;
|
||||||
|
highlight: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ResultViewProps {
|
||||||
|
result: ITestingResult | null;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResultView({ result, loading }: ResultViewProps) {
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
<Typography variant="body2" sx={{ mt: 2 }}>
|
||||||
|
正在检索测试...
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return (
|
||||||
|
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
||||||
|
<PsychologyIcon sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
||||||
|
<Typography variant="h6" color="text.secondary">
|
||||||
|
请输入测试问题并点击测试按钮
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
{/* 测试结果概览 */}
|
||||||
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
测试结果概览
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid size={{xs: 12, sm: 4}}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h4" color="primary">
|
||||||
|
{result.total}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
匹配的文档块
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs: 12, sm: 4}}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h4" color="secondary">
|
||||||
|
{result.documents?.length || 0}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
相关文档
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid size={{xs: 12, sm: 4}}>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h4" color="success.main">
|
||||||
|
{result.chunks?.length || 0}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
返回的块数
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
{/* 匹配的文档块 */}
|
||||||
|
{result.chunks && result.chunks.length > 0 && (
|
||||||
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
匹配的文档块
|
||||||
|
</Typography>
|
||||||
|
<Stack spacing={2}>
|
||||||
|
{result.chunks.map((chunk: ITestingChunk, index: number) => (
|
||||||
|
<Card key={chunk.id || index} variant="outlined">
|
||||||
|
<CardContent>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
||||||
|
<Typography variant="subtitle2" color="primary">
|
||||||
|
文档: {chunk.document_name}
|
||||||
|
</Typography>
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
<Chip
|
||||||
|
label={`相似度: ${(chunk.similarity * 100).toFixed(1)}%`}
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
{chunk.vector_similarity && (
|
||||||
|
<Chip
|
||||||
|
label={`向量: ${(chunk.vector_similarity * 100).toFixed(1)}%`}
|
||||||
|
size="small"
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{chunk.term_similarity && (
|
||||||
|
<Chip
|
||||||
|
label={`词项: ${(chunk.term_similarity * 100).toFixed(1)}%`}
|
||||||
|
size="small"
|
||||||
|
color="info"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||||
|
{chunk.content || '内容不可用'}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 相关文档统计 */}
|
||||||
|
{result.documents && result.documents.length > 0 && (
|
||||||
|
<Paper sx={{ p: 3 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
相关文档统计
|
||||||
|
</Typography>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
{result.documents.map((doc: ITestingDocument, index: number) => (
|
||||||
|
<Box key={doc.doc_id || index} sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
{doc.doc_name}
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label={`${doc.count} 个匹配块`}
|
||||||
|
size="small"
|
||||||
|
color="default"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function KnowledgeBaseTesting() {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [testResult, setTestResult] = useState<ITestingResult | null>(null);
|
||||||
|
const [testing, setTesting] = useState(false);
|
||||||
|
const [snackbar, setSnackbar] = useState<{
|
||||||
|
open: boolean;
|
||||||
|
message: string;
|
||||||
|
severity: 'success' | 'error';
|
||||||
|
}>({
|
||||||
|
open: false,
|
||||||
|
message: '',
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取知识库详情
|
||||||
|
const { knowledge, loading: detailLoading } = useKnowledgeDetail(id || '');
|
||||||
|
|
||||||
|
// 测试表单
|
||||||
|
const form = useForm<TestFormData>({
|
||||||
|
defaultValues: {
|
||||||
|
question: '',
|
||||||
|
similarity_threshold: 0.2,
|
||||||
|
vector_similarity_weight: 0.3,
|
||||||
|
rerank_id: '',
|
||||||
|
top_k: 6,
|
||||||
|
use_kg: false,
|
||||||
|
highlight: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { register, handleSubmit, watch, setValue, formState: { errors } } = form;
|
||||||
|
|
||||||
|
const handleTestSubmit = async (data: TestFormData) => {
|
||||||
|
if (!id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setTesting(true);
|
||||||
|
|
||||||
|
const requestBody: ITestRetrievalRequestBody = {
|
||||||
|
question: data.question,
|
||||||
|
similarity_threshold: data.similarity_threshold,
|
||||||
|
vector_similarity_weight: data.vector_similarity_weight,
|
||||||
|
top_k: data.top_k,
|
||||||
|
use_kg: data.use_kg,
|
||||||
|
highlight: data.highlight,
|
||||||
|
kb_id: [id],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.rerank_id && data.rerank_id.trim()) {
|
||||||
|
requestBody.rerank_id = data.rerank_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await knowledgeService.retrievalTest(requestBody);
|
||||||
|
|
||||||
|
if (response.data.code === 0) {
|
||||||
|
setTestResult(response.data.data);
|
||||||
|
setSnackbar({
|
||||||
|
open: true,
|
||||||
|
message: '检索测试完成',
|
||||||
|
severity: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(response.data.message || '检索测试失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
setSnackbar({
|
||||||
|
open: true,
|
||||||
|
message: error.message || '检索测试失败',
|
||||||
|
severity: 'error',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setTesting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackToDetail = () => {
|
||||||
|
navigate(`/knowledge/${id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseSnackbar = () => {
|
||||||
|
setSnackbar(prev => ({ ...prev, open: false }));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (detailLoading) {
|
||||||
|
return (
|
||||||
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
|
<Typography>加载中...</Typography>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
|
{/* 面包屑导航 */}
|
||||||
|
<KnowledgeBreadcrumbs knowledge={knowledge} />
|
||||||
|
|
||||||
|
<Box sx={{ mb: 3 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
知识库测试
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
|
{knowledge?.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{/* 测试表单 */}
|
||||||
|
<Grid size={{xs: 12, sm: 4}}>
|
||||||
|
<Paper sx={{ p: 3, position: 'sticky', top: 20 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
测试配置
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box component="form" onSubmit={handleSubmit(handleTestSubmit)} sx={{ mt: 2 }}>
|
||||||
|
<TextField
|
||||||
|
{...register('question', { required: '请输入测试问题' })}
|
||||||
|
label="测试问题"
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
error={!!errors.question}
|
||||||
|
helperText={errors.question?.message}
|
||||||
|
placeholder="请输入您想要测试的问题..."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
相似度阈值: {watch('similarity_threshold')}
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
{...register('similarity_threshold')}
|
||||||
|
value={watch('similarity_threshold')}
|
||||||
|
onChange={(_, value) => setValue('similarity_threshold', value as number)}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.1}
|
||||||
|
marks
|
||||||
|
valueLabelDisplay="auto"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<Typography gutterBottom>
|
||||||
|
向量相似度权重: {watch('vector_similarity_weight')}
|
||||||
|
</Typography>
|
||||||
|
<Slider
|
||||||
|
{...register('vector_similarity_weight')}
|
||||||
|
value={watch('vector_similarity_weight')}
|
||||||
|
onChange={(_, value) => setValue('vector_similarity_weight', value as number)}
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.1}
|
||||||
|
marks
|
||||||
|
valueLabelDisplay="auto"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
{...register('top_k')}
|
||||||
|
label="返回结果数量"
|
||||||
|
type="number"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
inputProps={{ min: 1, max: 50 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
{...register('rerank_id')}
|
||||||
|
label="重排序模型ID (可选)"
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
placeholder="留空使用默认设置"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
{...register('use_kg')}
|
||||||
|
checked={watch('use_kg')}
|
||||||
|
onChange={(e) => setValue('use_kg', e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="使用知识图谱"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch
|
||||||
|
{...register('highlight')}
|
||||||
|
checked={watch('highlight')}
|
||||||
|
onChange={(e) => setValue('highlight', e.target.checked)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="高亮显示"
|
||||||
|
sx={{ mt: 1 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
fullWidth
|
||||||
|
size="large"
|
||||||
|
disabled={testing}
|
||||||
|
startIcon={testing ? <CircularProgress size={20} /> : <SearchIcon />}
|
||||||
|
sx={{ mt: 3 }}
|
||||||
|
>
|
||||||
|
{testing ? '测试中...' : '开始测试'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* 测试结果 */}
|
||||||
|
<Grid size={{xs: 12, md: 8}}>
|
||||||
|
<ResultView result={testResult} loading={testing} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* 返回按钮 */}
|
||||||
|
<Fab
|
||||||
|
color="primary"
|
||||||
|
aria-label="返回知识库详情"
|
||||||
|
onClick={handleBackToDetail}
|
||||||
|
sx={{
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: 16,
|
||||||
|
right: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</Fab>
|
||||||
|
|
||||||
|
{/* 消息提示 */}
|
||||||
|
<Snackbar
|
||||||
|
open={snackbar.open}
|
||||||
|
autoHideDuration={6000}
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
|
||||||
|
>
|
||||||
|
<Alert
|
||||||
|
onClose={handleCloseSnackbar}
|
||||||
|
severity={snackbar.severity}
|
||||||
|
sx={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
{snackbar.message}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default KnowledgeBaseTesting;
|
||||||
@@ -2,12 +2,10 @@ import { Routes, Route, Navigate } from 'react-router-dom';
|
|||||||
import MainLayout from '../components/Layout/MainLayout';
|
import MainLayout from '../components/Layout/MainLayout';
|
||||||
import Login from '../pages/login/Login';
|
import Login from '../pages/login/Login';
|
||||||
import Home from '../pages/Home';
|
import Home from '../pages/Home';
|
||||||
import KnowledgeBaseList from '../pages/knowledge/list';
|
|
||||||
import PipelineConfig from '../pages/PipelineConfig';
|
import PipelineConfig from '../pages/PipelineConfig';
|
||||||
import Dashboard from '../pages/Dashboard';
|
import Dashboard from '../pages/Dashboard';
|
||||||
import ModelsResources from '../pages/ModelsResources';
|
import ModelsResources from '../pages/ModelsResources';
|
||||||
import KnowledgeBaseCreate from '../pages/knowledge/create';
|
import { KnowledgeBaseList, KnowledgeBaseCreate, KnowledgeBaseDetail, KnowledgeBaseSetting, KnowledgeBaseTesting } from '../pages/knowledge';
|
||||||
import KnowledgeBaseDetail from '../pages/knowledge/detail';
|
|
||||||
import MCP from '../pages/MCP';
|
import MCP from '../pages/MCP';
|
||||||
|
|
||||||
const AppRoutes = () => {
|
const AppRoutes = () => {
|
||||||
@@ -21,7 +19,12 @@ const AppRoutes = () => {
|
|||||||
<Route path="knowledge">
|
<Route path="knowledge">
|
||||||
<Route index element={<KnowledgeBaseList />} />
|
<Route index element={<KnowledgeBaseList />} />
|
||||||
<Route path="create" element={<KnowledgeBaseCreate />} />
|
<Route path="create" element={<KnowledgeBaseCreate />} />
|
||||||
<Route path=":id" element={<KnowledgeBaseDetail />} />
|
{/* 详情通用一个Layout */}
|
||||||
|
<Route path=':id'>
|
||||||
|
<Route path="testing" element={<KnowledgeBaseTesting />} />
|
||||||
|
<Route index element={<KnowledgeBaseDetail />} />
|
||||||
|
<Route path="setting" element={<KnowledgeBaseSetting />} />
|
||||||
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
<Route index element={<KnowledgeBaseList />} />
|
<Route index element={<KnowledgeBaseList />} />
|
||||||
<Route path="pipeline-config" element={<PipelineConfig />} />
|
<Route path="pipeline-config" element={<PipelineConfig />} />
|
||||||
|
|||||||
@@ -132,10 +132,10 @@ request.interceptors.response.use(
|
|||||||
if (data?.code === 100) {
|
if (data?.code === 100) {
|
||||||
snackbar.error(data?.message);
|
snackbar.error(data?.message);
|
||||||
} else if (data?.code === 401) {
|
} else if (data?.code === 401) {
|
||||||
notification.error(data?.message);
|
notification.error(data?.message, i18n.t('message.401'));
|
||||||
redirectToLogin();
|
redirectToLogin();
|
||||||
} else if (data?.code !== 0) {
|
} else if (data?.code !== 0) {
|
||||||
notification.error(`${i18n.t('message.hint')} : ${data?.code}`, data?.message);
|
snackbar.error(data?.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
Reference in New Issue
Block a user