diff --git a/src/interfaces/request/base.ts b/src/interfaces/request/base.ts index 789be81..96f0911 100644 --- a/src/interfaces/request/base.ts +++ b/src/interfaces/request/base.ts @@ -5,3 +5,11 @@ export interface IPaginationRequestBody { orderby?: string; desc?: string; } + +/** + * 分页请求参数 + */ +export interface IPaginationBody { + page?: number; + size?: number; +} diff --git a/src/interfaces/request/knowledge.ts b/src/interfaces/request/knowledge.ts index de1b00b..5e3d651 100644 --- a/src/interfaces/request/knowledge.ts +++ b/src/interfaces/request/knowledge.ts @@ -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[]; diff --git a/src/pages/knowledge/components/TestChunkResult.tsx b/src/pages/knowledge/components/TestChunkResult.tsx new file mode 100644 index 0000000..1cde4e1 --- /dev/null +++ b/src/pages/knowledge/components/TestChunkResult.tsx @@ -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 ( + + + 请输入问题并点击"开始测试"来查看检索结果 + + + ); + } + + // 计算分页数据 - 使用服务端分页,直接显示当前页数据 + const chunks = result.chunks || []; + const totalPages = Math.ceil(result.total / pageSize); + + const handleDocumentFilterChange = (event: SelectChangeEvent) => { + const value = event.target.value as string[]; + onDocumentFilter(value); + }; + + return ( + + {/* 测试结果概览 */} + + + 测试结果概览 + + + + + + + {result.total} + + + 匹配的文档块 + + + + + + + + + {result.doc_aggs?.length || 0} + + + 相关文档 + + + + + + + + + {result.chunks?.length || 0} + + + 返回的块数 + + + + + + + + {/* 文档过滤器 */} + {result.doc_aggs && result.doc_aggs.length > 0 && ( + + + + 文档过滤 + + + 选择要显示的文档 + + + + )} + + {/* 匹配的文档块 */} + {chunks && chunks.length > 0 && ( + + + + 匹配的文档块 (第 {page} 页,共 {totalPages} 页) + + + 共找到 {result.total} 个匹配块 + + + + + {chunks.map((chunk, index) => ( + + + + + + {chunk.doc_name} + + + + {chunk.vector_similarity !== undefined && ( + + )} + {chunk.term_similarity !== undefined && ( + + )} + + + + + + {chunk.important_kwd && chunk.important_kwd.length > 0 && ( + + + 关键词: + + + {chunk.important_kwd.map((keyword, kwdIndex) => ( + + ))} + + + )} + + + + ))} + + + )} + + {/* 相关文档统计 */} + {result.doc_aggs && result.doc_aggs.length > 0 && ( + + + 相关文档统计 + + + {result.doc_aggs.map((doc: ITestingDocument, index: number) => ( + + + {doc.doc_name} + + + + ))} + + + )} + + ); +} + +export default TestChunkResult; \ No newline at end of file diff --git a/src/pages/knowledge/testing.tsx b/src/pages/knowledge/testing.tsx index 114e4d5..e36b7df 100644 --- a/src/pages/knowledge/testing.tsx +++ b/src/pages/knowledge/testing.tsx @@ -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 ( - - - - 正在检索测试... - - - ); - } - - if (!result) { - return ( - - - - 请输入测试问题并点击测试按钮 - - - ); - } - - return ( - - {/* 测试结果概览 */} - - - 测试结果概览 - - - - - - - {result.total} - - - 匹配的文档块 - - - - - - - - - {result.documents?.length || 0} - - - 相关文档 - - - - - - - - - {result.chunks?.length || 0} - - - 返回的块数 - - - - - - - - {/* 匹配的文档块 */} - {result.chunks && result.chunks.length > 0 && ( - - - 匹配的文档块 - - - {result.chunks.map((chunk: ITestingChunk, index: number) => ( - - - - - 文档: {chunk.document_name} - - - - {chunk.vector_similarity && ( - - )} - {chunk.term_similarity && ( - - )} - - - - {chunk.content || '内容不可用'} - - - - ))} - - - )} - - {/* 相关文档统计 */} - {result.documents && result.documents.length > 0 && ( - - - 相关文档统计 - - - {result.documents.map((doc: ITestingDocument, index: number) => ( - - - {doc.doc_name} - - - - ))} - - - )} - - ); + 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(null); + const { t } = useTranslation(); + + // 状态管理 + const [testResult, setTestResult] = useState(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([]); + + const { showMessage } = useSnackbar(); // 获取知识库详情 - const { knowledge, loading: detailLoading } = useKnowledgeDetail(id || ''); + const { knowledge: knowledgeDetail, loading: detailLoading } = useKnowledgeDetail(id || ''); - // 测试表单 - const form = useForm({ + // 获取重排序模型选项 + const { options: rerankOptions, loading: rerankLoading } = useRerankModelOptions(); + + // 表单配置 + const { control, handleSubmit, watch, register, setValue, getValues, formState: { errors } } = useForm({ 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, 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 ( + {/* 面包屑导航 */} - - + + 知识库测试 - {knowledge?.name} + {knowledgeDetail?.name} - + {/* 测试表单 */} - + 测试配置 - + - + + 重排序模型 (可选) + ( + + )} + /> + - + {/* Top-K 字段 - 只有选择了rerank_id时才显示 */} + {watch('rerank_id') && ( + + )} + + + 跨语言搜索 + ( + + )} + /> + setValue('use_kg', e.target.checked)} /> } label="使用知识图谱" - sx={{ mt: 2 }} - /> - - setValue('highlight', e.target.checked)} - /> - } - label="高亮显示" - sx={{ mt: 1 }} /> + + {testResult && ( + + )} - {/* 测试结果 */} - - + {/* 分页组件 */} + {testResult && testResult.total > 10 && ( + + + + )} - - {/* 返回按钮 */} - - - - - {/* 消息提示 */} - - - {snackbar.message} - - ); -} +}; export default KnowledgeBaseTesting; \ No newline at end of file