feat(knowledge-testing): add pagination and document filtering to testing interface

This commit is contained in:
2025-10-16 11:48:00 +08:00
parent fd4309582d
commit 4f956e79ba
4 changed files with 537 additions and 281 deletions

View File

@@ -5,3 +5,11 @@ export interface IPaginationRequestBody {
orderby?: string;
desc?: string;
}
/**
* 分页请求参数
*/
export interface IPaginationBody {
page?: number;
size?: number;
}

View File

@@ -1,4 +1,18 @@
export interface ITestRetrievalRequestBody {
/**
{"similarity_threshold":0.2,
"vector_similarity_weight":0.3,
"top_k":1024,"use_kg":true,
"cross_languages":["English","Chinese","Spanish","French","German",
"Japanese","Korean","Vietnamese"],"question":"123","kb_id":"dcc2871aa4cd11f08d4116ac85b1de0a",
"page":1,"size":10,"doc_ids":["92b37a3aa7de11f084d336b0b158b556"]}
*/
import type { IPaginationBody } from "./base";
/**
* 检索测试请求体
*/
export interface ITestRetrievalRequestBody extends IPaginationBody {
question: string;
similarity_threshold: number;
vector_similarity_weight: number;
@@ -6,13 +20,21 @@ export interface ITestRetrievalRequestBody {
top_k?: number;
use_kg?: boolean;
highlight?: boolean;
kb_id?: string[];
kb_id?: string;
doc_ids?: string[];
cross_languages?: string[];
}
/**
* 获取知识库列表请求体
*/
export interface IFetchKnowledgeListRequestBody {
owner_ids?: string[];
}
/**
* 获取知识库列表请求参数
*/
export interface IFetchKnowledgeListRequestParams {
kb_id?: string;
keywords?: string;
@@ -20,6 +42,9 @@ export interface IFetchKnowledgeListRequestParams {
page_size?: number;
}
/**
* 获取文档列表请求体
*/
export interface IFetchDocumentListRequestBody {
suffix?: string[];
run_status?: string[];

View File

@@ -0,0 +1,249 @@
import React from 'react';
import {
Box,
Paper,
Typography,
FormControl,
InputLabel,
Select,
MenuItem,
Grid,
Card,
CardContent,
Chip,
Stack,
Checkbox,
ListItemText,
OutlinedInput,
} from '@mui/material';
import type { SelectChangeEvent } from '@mui/material/Select';
import { FilterList as FilterListIcon } from '@mui/icons-material';
import type { INextTestingResult, ITestingDocument } from '@/interfaces/database/knowledge';
interface TestChunkResultProps {
result: INextTestingResult | null;
loading: boolean;
page: number;
pageSize: number;
onDocumentFilter: (docIds: string[]) => void;
selectedDocIds: string[];
}
function TestChunkResult({ result, page, pageSize, onDocumentFilter, selectedDocIds }: TestChunkResultProps) {
if (!result) {
return (
<Paper sx={{ p: 3, textAlign: 'center' }}>
<Typography variant="h6" color="text.secondary">
"开始测试"
</Typography>
</Paper>
);
}
// 计算分页数据 - 使用服务端分页,直接显示当前页数据
const chunks = result.chunks || [];
const totalPages = Math.ceil(result.total / pageSize);
const handleDocumentFilterChange = (event: SelectChangeEvent<string[]>) => {
const value = event.target.value as string[];
onDocumentFilter(value);
};
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.doc_aggs?.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.doc_aggs && result.doc_aggs.length > 0 && (
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<FilterListIcon />
</Typography>
<FormControl fullWidth>
<InputLabel></InputLabel>
<Select
multiple
value={selectedDocIds}
onChange={handleDocumentFilterChange}
input={<OutlinedInput label="选择要显示的文档" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => {
const doc = result.doc_aggs?.find(d => d.doc_id === value);
return (
<Chip key={value} label={doc?.doc_name || value} size="small" />
);
})}
</Box>
)}
>
{result.doc_aggs.map((doc) => (
<MenuItem key={doc.doc_id} value={doc.doc_id}>
<Checkbox checked={selectedDocIds.indexOf(doc.doc_id) > -1} />
<ListItemText
primary={doc.doc_name}
secondary={`${doc.count} 个匹配块`}
/>
</MenuItem>
))}
</Select>
</FormControl>
</Paper>
)}
{/* 匹配的文档块 */}
{chunks && chunks.length > 0 && (
<Paper sx={{ p: 3, mb: 3 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
<Typography variant="h6">
( {page} {totalPages} )
</Typography>
<Typography variant="body2" color="text.secondary">
{result.total}
</Typography>
</Box>
<Grid container spacing={2}>
{chunks.map((chunk, index) => (
<Grid size={12} key={chunk.chunk_id}>
<Card variant="outlined">
<CardContent>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Typography variant="subtitle1" fontWeight="bold">
{chunk.doc_name}
</Typography>
<Stack direction="row" spacing={1}>
<Chip
label={`相似度: ${(chunk.similarity * 100).toFixed(1)}%`}
size="small"
color="primary"
/>
{chunk.vector_similarity !== undefined && (
<Chip
label={`向量: ${(chunk.vector_similarity * 100).toFixed(1)}%`}
size="small"
variant="outlined"
/>
)}
{chunk.term_similarity !== undefined && (
<Chip
label={`词项: ${(chunk.term_similarity * 100).toFixed(1)}%`}
size="small"
variant="outlined"
/>
)}
</Stack>
</Box>
<Typography
variant="body2"
sx={{
mb: 2,
'& mark': {
backgroundColor: 'yellow',
padding: '2px 4px',
borderRadius: '4px',
}
}}
dangerouslySetInnerHTML={{
__html: chunk.content_with_weight || chunk.content_ltks || '无内容'
}}
/>
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
:
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{chunk.important_kwd.map((keyword, kwdIndex) => (
<Chip
key={kwdIndex}
label={keyword}
size="small"
variant="outlined"
color="secondary"
/>
))}
</Box>
</Box>
)}
</CardContent>
</Card>
</Grid>
))}
</Grid>
</Paper>
)}
{/* 相关文档统计 */}
{result.doc_aggs && result.doc_aggs.length > 0 && (
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
</Typography>
<Stack spacing={1}>
{result.doc_aggs.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>
);
}
export default TestChunkResult;

View File

@@ -1,6 +1,6 @@
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useForm } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form';
import {
Box,
Container,
@@ -15,270 +15,206 @@ import {
MenuItem,
FormControlLabel,
Switch,
Fab,
Snackbar,
Alert,
Grid,
Card,
CardContent,
Pagination,
Checkbox,
ListItemText,
OutlinedInput,
ListSubheader,
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 { useRerankModelOptions } from '@/hooks/llm-hooks';
import knowledgeService from '@/services/knowledge_service';
import type { ITestRetrievalRequestBody } from '@/interfaces/request/knowledge';
import type { ITestingResult, ITestingChunk, ITestingDocument } from '@/interfaces/database/knowledge';
import type { INextTestingResult } from '@/interfaces/database/knowledge';
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
import TestChunkResult from './components/TestChunkResult';
import { useSnackbar } from '@/components/Provider/SnackbarProvider';
import { useTranslation } from 'react-i18next';
import { toLower } from 'lodash';
import { t } from 'i18next';
// 语言选项常量
const Languages = [
'English',
'Chinese',
'Spanish',
'French',
'German',
'Japanese',
'Korean',
'Vietnamese',
];
const options = Languages.map((x) => ({
label: t('language.' + toLower(x)),
value: x,
}));
// 表单数据接口
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>
);
top_k?: number;
use_kg?: boolean;
cross_languages?: string[];
doc_ids?: string[];
}
function KnowledgeBaseTesting() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [testResult, setTestResult] = useState<ITestingResult | null>(null);
const { t } = useTranslation();
// 状态管理
const [testResult, setTestResult] = useState<INextTestingResult | null>(null);
const [testing, setTesting] = useState(false);
const [snackbar, setSnackbar] = useState<{
open: boolean;
message: string;
severity: 'success' | 'error';
}>({
open: false,
message: '',
severity: 'success',
});
const [page, setPage] = useState(1);
const [pageSize] = useState(10);
const [selectedDocIds, setSelectedDocIds] = useState<string[]>([]);
const { showMessage } = useSnackbar();
// 获取知识库详情
const { knowledge, loading: detailLoading } = useKnowledgeDetail(id || '');
const { knowledge: knowledgeDetail, loading: detailLoading } = useKnowledgeDetail(id || '');
// 测试表单
const form = useForm<TestFormData>({
// 获取重排序模型选项
const { options: rerankOptions, loading: rerankLoading } = useRerankModelOptions();
// 表单配置
const { control, handleSubmit, watch, register, setValue, getValues, formState: { errors } } = useForm<TestFormData>({
defaultValues: {
question: '',
similarity_threshold: 0.2,
vector_similarity_weight: 0.3,
rerank_id: '',
top_k: 6,
top_k: 1024,
use_kg: false,
highlight: true,
cross_languages: [],
doc_ids: [],
},
});
const { register, handleSubmit, watch, setValue, formState: { errors } } = form;
// 处理测试提交
const handleTestSubmit = async (data: TestFormData) => {
if (!id) return;
setTesting(true);
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],
kb_id: id,
page: page,
size: pageSize,
};
if (data.rerank_id && data.rerank_id.trim()) {
// 只有当字段有值时才添加到请求体中
if (data.rerank_id) {
requestBody.rerank_id = data.rerank_id;
}
if (data.top_k) {
requestBody.top_k = data.top_k;
}
if (data.use_kg !== undefined) {
requestBody.use_kg = data.use_kg;
}
// 如果有选择的文档,添加到请求中
if (data.doc_ids && data.doc_ids.length > 0) {
requestBody.doc_ids = data.doc_ids;
} else {
if (selectedDocIds.length > 0) {
requestBody.doc_ids = selectedDocIds;
}
}
if (data.cross_languages && data.cross_languages.length > 0) {
requestBody.cross_languages = data.cross_languages;
}
const response = await knowledgeService.retrievalTest(requestBody);
if (response.data.code === 0) {
setTestResult(response.data.data);
setSnackbar({
open: true,
message: '检索测试完成',
severity: 'success',
});
setPage(1); // 重置到第一页
showMessage.success('检索测试完成');
} else {
throw new Error(response.data.message || '检索测试失败');
}
} catch (error: any) {
setSnackbar({
open: true,
message: error.message || '检索测试失败',
severity: 'error',
});
showMessage.error(error.message || '检索测试失败');
} finally {
setTesting(false);
}
};
const handleBackToDetail = () => {
navigate(`/knowledge/${id}`);
// 处理分页变化
const handlePageChange = async (event: React.ChangeEvent<unknown>, value: number) => {
if (!id) return;
setPage(value);
setTesting(true);
try {
const formData = getValues();
const requestBody: ITestRetrievalRequestBody = {
question: formData.question,
similarity_threshold: formData.similarity_threshold,
vector_similarity_weight: formData.vector_similarity_weight,
kb_id: id,
page: value,
size: pageSize,
highlight: true,
};
// 只有当字段有值时才添加到请求体中
if (formData.rerank_id) {
requestBody.rerank_id = formData.rerank_id;
}
if (formData.top_k) {
requestBody.top_k = formData.top_k;
}
if (formData.use_kg !== undefined) {
requestBody.use_kg = formData.use_kg;
}
if (selectedDocIds.length > 0) {
requestBody.doc_ids = selectedDocIds;
}
if (formData.cross_languages && formData.cross_languages.length > 0) {
requestBody.cross_languages = formData.cross_languages;
}
const response = await knowledgeService.retrievalTest(requestBody);
if (response.data.code === 0) {
setTestResult(response.data.data);
} else {
throw new Error(response.data.message || '分页请求失败');
}
} catch (error: any) {
showMessage.error(error.message || '分页请求失败');
} finally {
setTesting(false);
}
};
const handleCloseSnackbar = () => {
setSnackbar(prev => ({ ...prev, open: false }));
// 处理文档过滤
const handleDocumentFilter = (docIds: string[]) => {
setSelectedDocIds(docIds);
setValue('doc_ids', docIds);
handleTestSubmit(getValues());
};
// 返回详情页
const handleBackToDetail = () => {
navigate(`/knowledge/${id}`);
};
if (detailLoading) {
@@ -291,26 +227,27 @@ function KnowledgeBaseTesting() {
return (
<Container maxWidth="lg" sx={{ py: 4 }}>
{/* 面包屑导航 */}
<KnowledgeBreadcrumbs knowledge={knowledge} />
<KnowledgeBreadcrumbs knowledge={knowledgeDetail} />
<Box sx={{ mb: 3 }}>
<Typography variant="h4" gutterBottom>
</Typography>
<Typography variant="subtitle1" color="text.secondary">
{knowledge?.name}
{knowledgeDetail?.name}
</Typography>
</Box>
<Grid container spacing={3}>
<Grid container spacing={3} direction="row">
{/* 测试表单 */}
<Grid size={{xs: 12, sm: 4}}>
<Grid size={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: '请输入测试问题' })}
@@ -356,99 +293,136 @@ function KnowledgeBaseTesting() {
/>
</Box>
<TextField
{...register('top_k')}
label="返回结果数量"
type="number"
fullWidth
margin="normal"
inputProps={{ min: 1, max: 50 }}
/>
<FormControl fullWidth margin="normal">
<InputLabel> ()</InputLabel>
<Controller
name="rerank_id"
control={control}
render={({ field }) => (
<Select
{...field}
label="重排序模型 (可选)"
disabled={rerankLoading}
>
<MenuItem value="">
<em>使</em>
</MenuItem>
{rerankOptions.map((group) => [
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
...group.options.map((option) => (
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</MenuItem>
))
])}
</Select>
)}
/>
</FormControl>
<TextField
{...register('rerank_id')}
label="重排序模型ID (可选)"
fullWidth
margin="normal"
placeholder="留空使用默认设置"
/>
{/* Top-K 字段 - 只有选择了rerank_id时才显示 */}
{watch('rerank_id') && (
<TextField
{...register('top_k', {
required: '请输入返回结果数量',
min: { value: 1, message: '最小值为1' },
max: { value: 2048, message: '最大值为2048' }
})}
label="Top-K"
type="number"
fullWidth
margin="normal"
inputProps={{ min: 1, max: 2048 }}
error={!!errors.top_k}
helperText={errors.top_k?.message || '与Rerank模型配合使用'}
/>
)}
<FormControl fullWidth margin="normal">
<InputLabel></InputLabel>
<Controller
name="cross_languages"
control={control}
render={({ field }) => (
<Select
{...field}
multiple
label="跨语言搜索"
input={<OutlinedInput label="跨语言搜索" />}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => {
const option = options.find(opt => opt.value === value);
return (
<Chip key={value} label={option?.label || value} size="small" />
);
})}
</Box>
)}
>
{options.map((option) => (
<MenuItem key={option.value} value={option.value}>
<Checkbox checked={(watch('cross_languages') ?? []).indexOf(option.value) > -1} />
<ListItemText primary={option.label} />
</MenuItem>
))}
</Select>
)}
/>
</FormControl>
<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 }}
sx={{ mt: 2 }}
>
{testing ? '测试中...' : '开始测试'}
</Button>
</Box>
</Paper>
</Grid>
<Grid size={8}>
{testResult && (
<TestChunkResult
result={testResult}
loading={testing}
page={page}
pageSize={pageSize}
onDocumentFilter={handleDocumentFilter}
selectedDocIds={selectedDocIds}
/>
)}
{/* 测试结果 */}
<Grid size={{xs: 12, md: 8}}>
<ResultView result={testResult} loading={testing} />
{/* 分页组件 */}
{testResult && testResult.total > 10 && (
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 3 }}>
<Pagination
count={Math.ceil(testResult.total / 10)}
page={page}
onChange={handlePageChange}
color="primary"
size="large"
showFirstButton
showLastButton
/>
</Box>
)}
</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;