diff --git a/src/constants/document.ts b/src/constants/document.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/hooks/chunk-hooks.ts b/src/hooks/chunk-hooks.ts new file mode 100644 index 0000000..41793f7 --- /dev/null +++ b/src/hooks/chunk-hooks.ts @@ -0,0 +1,107 @@ +import { useState, useEffect, useCallback } from 'react'; +import knowledgeService from '@/services/knowledge_service'; +import type { IChunk, IChunkListResult } from '@/interfaces/database/knowledge'; +import type { IFetchChunkListRequestBody } from '@/interfaces/request/knowledge'; + +// Chunk列表Hook状态接口 +export interface UseChunkListState { + chunks: IChunk[]; + total: number; + loading: boolean; + error: string | null; + currentPage: number; + pageSize: number; + keywords: string; +} + +// Chunk列表Hook返回值接口 +export interface UseChunkListReturn extends UseChunkListState { + fetchChunks: (params?: IFetchChunkListRequestBody) => Promise; + setKeywords: (keywords: string) => void; + setCurrentPage: (page: number) => void; + setPageSize: (size: number) => void; + refresh: () => Promise; +} + +/** + * Chunk列表数据管理Hook + * 支持关键词搜索、分页等功能 + */ +export const useChunkList = ( + docId: string, + initialParams?: IFetchChunkListRequestBody +): UseChunkListReturn => { + const [chunks, setChunks] = useState([]); + const [total, setTotal] = useState(0); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(initialParams?.page || 1); + const [pageSize, setPageSize] = useState(initialParams?.size || 10); + const [keywords, setKeywords] = useState(initialParams?.keywords || ''); + + /** + * 获取chunk列表 + */ + const fetchChunks = useCallback(async (params?: IFetchChunkListRequestBody) => { + if (!docId) return; + + try { + setLoading(true); + setError(null); + + // 合并参数 + const queryParams = { + doc_id: docId, + keywords: params?.keywords ?? keywords, + page: params?.page ?? currentPage, + size: params?.size ?? pageSize, + }; + + const response = await knowledgeService.getChunkList(queryParams); + + // 检查响应状态 + if (response.data.code === 0) { + const data: IChunkListResult = response.data.data; + setChunks(data.chunks || []); + setTotal(data.total || 0); + } else { + throw new Error(response.data.message || '获取chunk列表失败'); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message || '获取chunk列表失败'; + setError(errorMessage); + console.error('Failed to fetch chunks:', err); + } finally { + setLoading(false); + } + }, [docId, keywords, currentPage, pageSize]); + + /** + * 刷新数据 + */ + const refresh = useCallback(async () => { + await fetchChunks(); + }, [fetchChunks]); + + // 初始化加载数据 + useEffect(() => { + if (docId) { + fetchChunks(); + } + }, [docId, fetchChunks]); + + return { + chunks, + total, + loading, + error, + currentPage, + pageSize, + keywords, + fetchChunks, + setKeywords, + setCurrentPage, + setPageSize, + refresh, + }; +}; \ No newline at end of file diff --git a/src/hooks/document-hooks.ts b/src/hooks/document-hooks.ts index 0b183a5..a8cd0e5 100644 --- a/src/hooks/document-hooks.ts +++ b/src/hooks/document-hooks.ts @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import knowledgeService from '@/services/knowledge_service'; import type { IKnowledgeFile } from '@/interfaces/database/knowledge'; import type { IFetchKnowledgeListRequestParams, IFetchDocumentListRequestBody } from '@/interfaces/request/knowledge'; +import type { IDocumentInfoFilter } from '@/interfaces/database/document'; // 文档列表Hook状态接口 export interface UseDocumentListState { @@ -17,7 +18,7 @@ export interface UseDocumentListState { // 文档列表Hook返回值接口 export interface UseDocumentListReturn extends UseDocumentListState { fetchDocuments: (params?: IFetchKnowledgeListRequestParams) => Promise; - fetchDocumentsFilter: () => Promise; + fetchDocumentsFilter: () => Promise; setKeywords: (keywords: string) => void; setCurrentPage: (page: number) => void; setPageSize: (size: number) => void; @@ -107,7 +108,8 @@ export const useDocumentList = ( kb_id: kbId, }); if (response.data.code === 0) { - const data = response.data.data; + const data: IDocumentInfoFilter = response.data.data; + return data; } else { throw new Error(response.data.message || '获取文档过滤器失败'); } @@ -324,7 +326,7 @@ export const useDocumentOperations = () => { setLoading(true); setError(null); - const response = await knowledgeService.changeDocumentStatus({ doc_id: docIds, status }); + const response = await knowledgeService.changeDocumentStatus({ doc_ids: docIds, status }); if (response.data.code === 0) { return response.data.data; @@ -349,7 +351,7 @@ export const useDocumentOperations = () => { setLoading(true); setError(null); - const response = await knowledgeService.runDocument({ doc_id: docIds }); + const response = await knowledgeService.runDocument({ doc_ids: docIds, run: 1, delete: false }); if (response.data.code === 0) { return response.data.data; @@ -366,6 +368,31 @@ export const useDocumentOperations = () => { } }, []); + /** + * 取消文档处理 + */ + const cancelRunDocuments = useCallback(async (docIds: string[]) => { + try { + setLoading(true); + setError(null); + + const response = await knowledgeService.runDocument({ doc_ids: docIds, run: 2, delete: false }); + + if (response.data.code === 0) { + return response.data.data; + } else { + throw new Error(response.data.message || '取消文档处理失败'); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message || '取消文档处理失败'; + setError(errorMessage); + console.error('Failed to cancel documents:', err); + throw err; + } finally { + setLoading(false); + } + }, []); + /** * 清除错误状态 */ @@ -383,6 +410,7 @@ export const useDocumentOperations = () => { createDocument, changeDocumentStatus, runDocuments, + cancelRunDocuments, clearError, }; }; @@ -435,7 +463,7 @@ export const useDocumentBatchOperations = () => { setLoading(true); setError(null); - const response = await knowledgeService.changeDocumentStatus({ doc_id: docIds, status }); + const response = await knowledgeService.changeDocumentStatus({ doc_ids: docIds, status }); if (response.data.code === 0) { return response.data.data; @@ -460,7 +488,7 @@ export const useDocumentBatchOperations = () => { setLoading(true); setError(null); - const response = await knowledgeService.runDocument({ doc_id: docIds }); + const response = await knowledgeService.runDocument({ doc_ids: docIds, run: 1, delete: false }); if (response.data.code === 0) { return response.data.data; diff --git a/src/interfaces/database/knowledge.ts b/src/interfaces/database/knowledge.ts index ecaf539..7db96c0 100644 --- a/src/interfaces/database/knowledge.ts +++ b/src/interfaces/database/knowledge.ts @@ -1,4 +1,5 @@ import type { RunningStatus } from '@/constants/knowledge'; +import type { IDocumentInfo } from './document'; /** @@ -426,6 +427,15 @@ export interface INextTestingResult { isRuned?: boolean; } +export interface IChunkListResult { + /** 文档块列表 */ + chunks: IChunk[]; + /** 文档信息列表 */ + doc: IDocumentInfo[] + /** 总匹配数量 */ + total: number; +} + /** * 重命名标签类型 * 用于标签重命名操作 diff --git a/src/interfaces/request/document.ts b/src/interfaces/request/document.ts index 88bb449..29cac96 100644 --- a/src/interfaces/request/document.ts +++ b/src/interfaces/request/document.ts @@ -16,3 +16,19 @@ export interface IDocumentMetaRequestBody { documentId: string; meta: string; // json format string } + +// export const RUNNING_STATUS_KEYS = Object.freeze({ +// UNSTART: '0', // need to run +// RUNNING: '1', // need to cancel +// CANCEL: '2', // need to refresh +// DONE: '3', // need to refresh +// FAIL: '4', // need to refresh +// } as const) + +export interface IRunDocumentRequestBody { + doc_ids: Array | string | number; + // running status 1 run 2 cancel - operations + run: number; + delete: boolean; +} + diff --git a/src/interfaces/request/file-manager.ts b/src/interfaces/request/file-manager.ts index b355bf3..ead4df6 100644 --- a/src/interfaces/request/file-manager.ts +++ b/src/interfaces/request/file-manager.ts @@ -1,4 +1,4 @@ -import { IPaginationRequestBody } from './base'; +import { type IPaginationRequestBody } from './base'; export interface IFileListRequestBody extends IPaginationRequestBody { parent_id?: string; // folder id diff --git a/src/interfaces/request/knowledge.ts b/src/interfaces/request/knowledge.ts index 5e3d651..acfbd8c 100644 --- a/src/interfaces/request/knowledge.ts +++ b/src/interfaces/request/knowledge.ts @@ -49,3 +49,11 @@ export interface IFetchDocumentListRequestBody { suffix?: string[]; run_status?: string[]; } + + +export interface IFetchChunkListRequestBody { + doc_id?: string; + keywords?: string; + page?: number; + size?: number; +} diff --git a/src/pages/chunk/components/ChunkListResult.tsx b/src/pages/chunk/components/ChunkListResult.tsx new file mode 100644 index 0000000..f9261c4 --- /dev/null +++ b/src/pages/chunk/components/ChunkListResult.tsx @@ -0,0 +1,247 @@ +import React from 'react'; +import { + Box, + Paper, + Typography, + Grid, + Card, + CardContent, + Chip, + Stack, + Pagination, + CircularProgress, + Alert, +} from '@mui/material'; +import type { IChunk } from '@/interfaces/database/knowledge'; + +interface ChunkListResultProps { + chunks: IChunk[]; + total: number; + loading: boolean; + error: string | null; + page: number; + pageSize: number; + onPageChange: (page: number) => void; + docName?: string; +} + +function ChunkListResult(props: ChunkListResultProps) { + const { chunks, total, loading, error, page, pageSize, onPageChange, docName } = props; + + if (loading) { + return ( + + + + 正在加载chunk数据... + + + ); + } + + if (error) { + return ( + + + {error} + + + ); + } + + if (!chunks || chunks.length === 0) { + return ( + + + 暂无chunk数据 + + + 该文档还没有生成chunk数据,请检查文档是否已完成解析 + + + ); + } + + const totalPages = Math.ceil(total / pageSize); + + return ( + + {/* Chunk结果概览 */} + + + 文档Chunk详情 + + {docName && ( + + 文档名称: {docName} + + )} + + + + + + {total} + + + 总Chunk数量 + + + + + + + + + {chunks.filter(chunk => chunk.available_int === 1).length} + + + 已启用Chunk + + + + + + + + {/* Chunk列表 */} + + + + Chunk列表 (第 {page} 页,共 {totalPages} 页) + + + 共 {total} 个chunk + + + + + {chunks.map((chunk, index) => ( + + + + + + Chunk #{((page - 1) * pageSize) + index + 1} + + + + {chunk.image_id && ( + + )} + + + + + {chunk.content_with_weight || '无内容'} + + + {chunk.important_kwd && chunk.important_kwd.length > 0 && ( + + + 重要关键词: + + + {chunk.important_kwd.map((keyword, kwdIndex) => ( + + ))} + + + )} + + {chunk.question_kwd && chunk.question_kwd.length > 0 && ( + + + 问题关键词: + + + {chunk.question_kwd.map((keyword, kwdIndex) => ( + + ))} + + + )} + + {chunk.tag_kwd && chunk.tag_kwd.length > 0 && ( + + + 标签关键词: + + + {chunk.tag_kwd.map((keyword, kwdIndex) => ( + + ))} + + + )} + + {chunk.positions && chunk.positions.length > 0 && ( + + + 位置信息: {chunk.positions.length} 个位置点 + + + )} + + + + ))} + + + {/* 分页控件 */} + {totalPages > 1 && ( + + onPageChange(newPage)} + color="primary" + showFirstButton + showLastButton + /> + + )} + + + ); +} + +export default ChunkListResult; \ No newline at end of file diff --git a/src/pages/chunk/parsed-result.tsx b/src/pages/chunk/parsed-result.tsx new file mode 100644 index 0000000..8263157 --- /dev/null +++ b/src/pages/chunk/parsed-result.tsx @@ -0,0 +1,269 @@ +import React, { useState, useEffect } from 'react'; +import { useSearchParams, useNavigate } from "react-router-dom"; +import { + Box, + Typography, + Breadcrumbs, + Link, + TextField, + InputAdornment, + Paper, + Alert, + Card, + CardContent, + CardMedia +} from "@mui/material"; +import { Search as SearchIcon, ArrowBack as ArrowBackIcon } from '@mui/icons-material'; +import { useChunkList } from '@/hooks/chunk-hooks'; +import ChunkListResult from './components/ChunkListResult'; +import knowledgeService from '@/services/knowledge_service'; +import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge'; +import type { IDocumentInfo } from '@/interfaces/database/document'; + +function ChunkParsedResult() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const kb_id = searchParams.get('kb_id'); + const doc_id = searchParams.get('doc_id'); + + const [knowledgeBase, setKnowledgeBase] = useState(null); + const [document, setDocument] = useState(null); + const [documentFile, setDocumentFile] = useState(null); + const [fileUrl, setFileUrl] = useState(''); + const [searchKeyword, setSearchKeyword] = useState(''); + + // 使用chunk列表hook + const { + chunks, + total, + loading, + error, + currentPage, + pageSize, + setCurrentPage, + setKeywords, + refresh + } = useChunkList(doc_id || '', { + page: 1, + size: 10, + keywords: searchKeyword + }); + + // 获取知识库和文档信息 + useEffect(() => { + const fetchData = async () => { + if (!kb_id || !doc_id) return; + + try { + // 获取知识库信息 + const kbResponse = await knowledgeService.getKnowledgeDetail({ kb_id }); + if (kbResponse.data.code === 0) { + setKnowledgeBase(kbResponse.data.data); + } + + // 获取文档信息 + const docResponse = await knowledgeService.getDocumentInfos({ doc_ids: [doc_id] }); + if (docResponse.data.code === 0) { + const docArr: IKnowledgeFile[] = docResponse.data.data; + if (docArr.length > 0) { + setDocument(docArr[0]); + } + } + + // 获取文档文件 + const fileResponse = await knowledgeService.getDocumentFile({ doc_id }); + if (fileResponse.data) { + // 处理二进制文件数据 + setDocumentFile(fileResponse.data); + + // 创建文件URL用于预览 + const url = URL.createObjectURL(fileResponse.data); + setFileUrl(url); + } + } catch (error) { + console.error('Failed to fetch data:', error); + } + }; + + // 处理搜索 + const handleSearch = (keyword: string) => { + setSearchKeyword(keyword); + setKeywords(keyword); + setCurrentPage(1); + }; + + fetchData(); + + // 清理函数,释放URL对象 + return () => { + if (fileUrl) { + URL.revokeObjectURL(fileUrl); + } + }; + }, [kb_id, doc_id]); + + // 渲染文件预览组件 + const renderFilePreview = () => { + if (!document || !fileUrl) return null; + + const fileExtension = document.name?.split('.').pop()?.toLowerCase(); + + // 图片文件预览 + if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'].includes(fileExtension || '')) { + return ( + + + + 文件预览 + + + + + ); + } + + // PDF文件预览 + if (fileExtension === 'pdf') { + return ( + + + + PDF预览 + + +