feat(knowledge): restructure knowledge base pages and components

- Implement new setting and testing pages with breadcrumbs
This commit is contained in:
2025-10-14 18:06:12 +08:00
parent 7384ae36d0
commit 9f6785672f
12 changed files with 834 additions and 48 deletions

View File

@@ -224,24 +224,12 @@ export const useKnowledgeOperations = () => {
* 更新知识库基础信息
* 包括名称、描述、语言等基本信息
*/
const updateKnowledgeBasicInfo = useCallback(async (data: {
id: string;
name?: string;
description?: string;
language?: string;
avatar?: any;
permission?: string;
}) => {
const updateKnowledgeBasicInfo = useCallback(async (data: IKnowledge) => {
try {
setLoading(true);
setError(null);
const updateData = {
kb_id: data.id,
...data,
};
const response = await knowledgeService.updateKnowledge(updateData);
const response = await knowledgeService.updateKnowledge(data);
if (response.data.code === 0) {
return response.data.data;

View File

@@ -37,7 +37,7 @@ const parserOptions = [
{ value: DOCUMENT_PARSER_TYPES.KnowledgeGraph, label: '知识图谱解析器', description: '构建知识图谱结构' },
];
interface ConfigFormData {
export interface ConfigFormData {
parser_id: DocumentParserType;
chunk_token_count?: number;
layout_recognize?: boolean;

View File

@@ -46,6 +46,12 @@ interface FileListComponentProps {
onRefresh: () => void;
rowSelectionModel: GridRowSelectionModel;
onRowSelectionModelChange: (model: GridRowSelectionModel) => void;
// 分页相关props
total: number;
page: number;
pageSize: number;
onPageChange: (page: number) => void;
onPageSizeChange: (pageSize: number) => void;
}
const getFileIcon = (type: string) => {
@@ -104,6 +110,11 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
onRefresh,
rowSelectionModel,
onRowSelectionModelChange,
total,
page,
pageSize,
onPageChange,
onPageSizeChange,
}) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedFileId, setSelectedFileId] = useState<string>('');
@@ -141,10 +152,15 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
handleMenuClose();
};
// 过滤文件列表
const filteredFiles = files.filter(file =>
file.name.toLowerCase().includes(searchKeyword.toLowerCase())
);
// 处理分页变化
const handlePaginationModelChange = (model: { page: number; pageSize: number }) => {
if (model.page !== page - 1) { // DataGrid的page是0-based我们的是1-based
onPageChange(model.page + 1);
}
if (model.pageSize !== pageSize) {
onPageSizeChange(model.pageSize);
}
};
// DataGrid 列定义
const columns: GridColDef[] = [
@@ -278,7 +294,7 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
{/* 文件列表 */}
<Paper sx={{ height: 600, width: '100%' }}>
<DataGrid
rows={filteredFiles}
rows={files}
columns={columns}
loading={loading}
checkboxSelection
@@ -286,11 +302,13 @@ const FileListComponent: React.FC<FileListComponentProps> = ({
rowSelectionModel={rowSelectionModel}
onRowSelectionModelChange={onRowSelectionModelChange}
pageSizeOptions={[10, 25, 50, 100]}
initialState={{
pagination: {
paginationModel: { page: 0, pageSize: 25 },
},
paginationMode="server"
rowCount={total}
paginationModel={{
page: page - 1,
pageSize: pageSize,
}}
onPaginationModelChange={handlePaginationModelChange}
localeText={getDataGridLocale().components.MuiDataGrid.defaultProps.localeText}
sx={{
'& .MuiDataGrid-cell:focus': {

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { SpeedDial, SpeedDialAction } from '@mui/material';
import {
Settings as SettingsIcon,
Dashboard as DashboardIcon,
Search as TestIcon,
Settings as ConfigIcon,
} from '@mui/icons-material';
interface FloatingActionButtonsProps {
@@ -42,7 +43,7 @@ const FloatingActionButtons: React.FC<FloatingActionButtonsProps> = ({
},
},
}}
icon={<SettingsIcon />}
icon={<DashboardIcon />}
>
{actions.map((action) => (
<SpeedDialAction

View File

@@ -20,7 +20,7 @@ import {
Save as SaveIcon,
} from '@mui/icons-material';
interface BasicFormData {
export interface BasicFormData {
name: string;
description: string;
permission: string;

View 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;

View File

@@ -22,6 +22,7 @@ import FileUploadDialog from '@/components/FileUploadDialog';
import KnowledgeInfoCard from './components/KnowledgeInfoCard';
import FileListComponent from './components/FileListComponent';
import FloatingActionButtons from './components/FloatingActionButtons';
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
import { useDocumentList, useDocumentOperations } from '@/hooks/document-hooks';
function KnowledgeBaseDetail() {
@@ -45,10 +46,15 @@ function KnowledgeBaseDetail() {
// 使用新的document hooks
const {
documents: files,
total,
loading: filesLoading,
error: filesError,
currentPage,
pageSize,
refresh: refreshFiles,
setKeywords,
setCurrentPage,
setPageSize,
} = useDocumentList(id || '');
const {
@@ -89,6 +95,7 @@ function KnowledgeBaseDetail() {
ids: new Set()
});
refreshFiles();
fetchKnowledgeDetail();
} catch (err: any) {
setError(err.response?.data?.message || err.message || '删除文件失败');
}
@@ -101,6 +108,7 @@ function KnowledgeBaseDetail() {
try {
await uploadDocuments(kb_id, uploadFiles);
refreshFiles();
fetchKnowledgeDetail();
} catch (err: any) {
throw new Error(err.response?.data?.message || err.message || '上传文件失败');
}
@@ -161,17 +169,7 @@ function KnowledgeBaseDetail() {
return (
<Box sx={{ p: 3 }}>
{/* 面包屑导航 */}
<Breadcrumbs sx={{ mb: 2 }}>
<Link
component="button"
variant="body1"
onClick={() => navigate('/knowledge')}
sx={{ textDecoration: 'none' }}
>
</Link>
<Typography color="text.primary">{knowledgeBase.name}</Typography>
</Breadcrumbs>
<KnowledgeBreadcrumbs knowledge={knowledgeBase} />
{/* 知识库信息卡片 */}
<KnowledgeInfoCard knowledgeBase={knowledgeBase} />
@@ -202,12 +200,17 @@ function KnowledgeBaseDetail() {
console.log('新的选择模型:', newModel);
setRowSelectionModel(newModel);
}}
total={total}
page={currentPage}
pageSize={pageSize}
onPageChange={setCurrentPage}
onPageSizeChange={setPageSize}
/>
{/* 浮动操作按钮 */}
<FloatingActionButtons
onTestClick={() => setTestingDialogOpen(true)}
onConfigClick={() => setConfigDialogOpen(true)}
onTestClick={() => navigate(`/knowledge/${id}/testing`)}
onConfigClick={() => navigate(`/knowledge/${id}/setting`)}
/>
{/* 上传文件对话框 */}

View 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';

View 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;

View 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;

View File

@@ -2,26 +2,29 @@ import { Routes, Route, Navigate } from 'react-router-dom';
import MainLayout from '../components/Layout/MainLayout';
import Login from '../pages/login/Login';
import Home from '../pages/Home';
import KnowledgeBaseList from '../pages/knowledge/list';
import PipelineConfig from '../pages/PipelineConfig';
import Dashboard from '../pages/Dashboard';
import ModelsResources from '../pages/ModelsResources';
import KnowledgeBaseCreate from '../pages/knowledge/create';
import KnowledgeBaseDetail from '../pages/knowledge/detail';
import { KnowledgeBaseList, KnowledgeBaseCreate, KnowledgeBaseDetail, KnowledgeBaseSetting, KnowledgeBaseTesting } from '../pages/knowledge';
import MCP from '../pages/MCP';
const AppRoutes = () => {
return (
<Routes>
<Route path="/login" element={<Login />} />
{/* 使用MainLayout作为受保护路由的布局 */}
<Route path="/" element={<MainLayout />}>
{/* <Route index element={<Home />} /> */}
<Route path="knowledge">
<Route index element={<KnowledgeBaseList />} />
<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 index element={<KnowledgeBaseList />} />
<Route path="pipeline-config" element={<PipelineConfig />} />
@@ -29,7 +32,7 @@ const AppRoutes = () => {
<Route path="models-resources" element={<ModelsResources />} />
<Route path="mcp" element={<MCP />} />
</Route>
{/* 处理未匹配的路由 */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>

View File

@@ -132,10 +132,10 @@ request.interceptors.response.use(
if (data?.code === 100) {
snackbar.error(data?.message);
} else if (data?.code === 401) {
notification.error(data?.message);
notification.error(data?.message, i18n.t('message.401'));
redirectToLogin();
} else if (data?.code !== 0) {
notification.error(`${i18n.t('message.hint')} : ${data?.code}`, data?.message);
snackbar.error(data?.message);
}
return response;