diff --git a/src/constants/knowledge.ts b/src/constants/knowledge.ts index 78e3a7b..eb607a5 100644 --- a/src/constants/knowledge.ts +++ b/src/constants/knowledge.ts @@ -25,6 +25,7 @@ export const RunningStatusMap = { [RUNNING_STATUS_KEYS.DONE]: 'Success', [RUNNING_STATUS_KEYS.FAIL]: 'Failed', } as const; + export const MODEL_VARIABLE_TYPES = Object.freeze({ Improvise: 'Improvise', Precise: 'Precise', diff --git a/src/hooks/knowledge-hooks.ts b/src/hooks/knowledge-hooks.ts index 324b231..c3303ec 100644 --- a/src/hooks/knowledge-hooks.ts +++ b/src/hooks/knowledge-hooks.ts @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import knowledgeService from '@/services/knowledge_service'; -import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult } from '@/interfaces/database/knowledge'; +import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult, IOverviewTital, IFileLogList } from '@/interfaces/database/knowledge'; import type { IFetchKnowledgeListRequestBody, IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge'; import logger from '@/utils/logger'; @@ -477,3 +477,161 @@ export const useKnowledgeBatchOperations = () => { }; }; + + +/* ============================================================================ + * useKnowledgeOverview + * ============================================================================ */ + +const LOGS_TABS = { + FILE_LOGS: 'fileLogs', + DATASET_LOGS: 'datasetLogs', +} as const + +type LogTabs = typeof LOGS_TABS[keyof typeof LOGS_TABS] + +/** + * 知识库详情页 - 概览数据获取Hook + */ +export interface UseKnowledgeOverviewReturn { + overview: IOverviewTital | null; + fileLogs: IFileLogList | null; + datasetLogs: IFileLogList | null; + loading: boolean; + error: string | null; + currentPage: number; + pageSize: number; + activeTab: LogTabs; + keywords: string; + setCurrentPage: (page: number) => void; + setPageSize: (size: number) => void; + setActiveTab: (tab: LogTabs) => void; + setKeywords: (keywords: string) => void; + refresh: () => Promise; +} + +export const useKnowledgeOverview = (kb_id: string): UseKnowledgeOverviewReturn => { + const { t } = useTranslation(); + const [overview, setOverview] = useState(null); + const [fileLogs, setFileLogs] = useState(null); + const [datasetLogs, setDatasetLogs] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + const [activeTab, setActiveTab] = useState(LOGS_TABS.FILE_LOGS); + const [keywords, setKeywordsState] = useState(''); + + const fetchOverviewTital = useCallback(async () => { + if (!kb_id) return; + try { + setLoading(true); + setError(null); + const response = await knowledgeService.getKnowledgeBasicInfo({ kb_id }); + if (response.data.code === 0) { + setOverview(response.data.data); + } else { + throw new Error(response.data.message || t('knowledgeHooks.fetchOverviewFailed')); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchOverviewFailed'); + setError(errorMessage); + console.error('Failed to fetch overview tital:', err); + } finally { + setLoading(false); + } + }, [kb_id]); + + const fetchPipelineLogs = useCallback(async () => { + if (!kb_id) return; + try { + setLoading(true); + setError(null); + const response = await knowledgeService.getDataPipelineLogs({ + kb_id, + page: currentPage, + page_size: pageSize, + keywords, + }); + if (response.data.code === 0) { + setFileLogs(response.data.data as IFileLogList); + } else { + throw new Error(response.data.message); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message; + setError(errorMessage); + console.error('Failed to fetch data pipeline logs:', err); + } finally { + setLoading(false); + } + }, [kb_id, currentPage, pageSize, keywords]); + + const fetchPipelineDatasetLogs = useCallback(async () => { + if (!kb_id) return; + try { + setLoading(true); + setError(null); + const response = await knowledgeService.getPipelineDatasetLogs({ + kb_id, + page: currentPage, + page_size: pageSize, + keywords, + }); + if (response.data.code === 0) { + setDatasetLogs(response.data.data as IFileLogList); + } else { + throw new Error(response.data.message); + } + } catch (err: any) { + const errorMessage = err.response?.data?.message || err.message; + setError(errorMessage); + console.error('Failed to fetch pipeline dataset logs:', err); + } finally { + setLoading(false); + } + }, [kb_id, currentPage, pageSize, keywords]); + + useEffect(() => { + fetchOverviewTital(); + }, [fetchOverviewTital]); + +useEffect(() => { + if (activeTab === LOGS_TABS.FILE_LOGS) { + fetchPipelineLogs(); + } else { + fetchPipelineDatasetLogs(); + } +}, [activeTab, fetchPipelineLogs, fetchPipelineDatasetLogs]); + +// 设置关键词并重置到第一页 +const setKeywords = useCallback((kw: string) => { + setKeywordsState(kw); + setCurrentPage(1); +}, []); + + const refresh = useCallback(async () => { + await Promise.all([ + fetchOverviewTital(), + fetchPipelineLogs(), + fetchPipelineDatasetLogs(), + ]); + }, [fetchOverviewTital, fetchPipelineLogs, fetchPipelineDatasetLogs]); + + return { + overview, + fileLogs, + datasetLogs, + loading, + error, + currentPage, + pageSize, + activeTab, + keywords, + setCurrentPage, + setPageSize, + setActiveTab, + setKeywords, + refresh, + }; +}; \ No newline at end of file diff --git a/src/interfaces/database/knowledge.ts b/src/interfaces/database/knowledge.ts index f8a6022..a9f3043 100644 --- a/src/interfaces/database/knowledge.ts +++ b/src/interfaces/database/knowledge.ts @@ -1,4 +1,4 @@ -import type { RunningStatus } from '@/constants/knowledge'; +import { RunningStatusMap, type RunningStatus } from '@/constants/knowledge'; import type { IDocumentInfo } from './document'; @@ -491,3 +491,55 @@ export interface IKnowledgeGraph { /** 思维导图数据(已注释) */ // mind_map: TreeData; } + +/** 文档日志项接口 */ +export interface IDocumentLog { + fileName: string; + status: RunningStatus; + statusName: typeof RunningStatusMap[keyof typeof RunningStatusMap]; +} + +/** 日志统计项接口 */ +export interface IOverviewTital { + cancelled: number; + failed: number; + finished: number; + processing: number; +} + +/** 日志项接口 */ +export interface IFileLogItem { + create_date: string; + create_time: number; + document_id: string; + document_name: string; + document_suffix: string; + document_type: string; + dsl: any; + path: string[]; + task_id: string; + id: string; + name: string; + kb_id: string; + operation_status: string; + parser_id: string; + pipeline_id: string; + pipeline_title: string; + avatar: string; + process_begin_at: null | string; + process_duration: number; + progress: number; + progress_msg: string; + source_from: string; + status: string; + task_type: string; + tenant_id: string; + update_date: string; + update_time: number; +} + +/** 日志列表接口 */ +export interface IFileLogList { + logs: Array; + total: number; +} diff --git a/src/locales/en.ts b/src/locales/en.ts index 2fd394a..25879f4 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -427,6 +427,7 @@ export default { raptor: 'Raptor', processingType: 'Processing Type', dataPipeline: 'Data Pipeline', + ingestionPipeline: 'Ingestion Pipeline', operations: 'Operations', taskId: 'Task ID', duration: 'Duration', @@ -453,6 +454,7 @@ export default { retrievalTestingDescription: 'Conduct a retrieval test to check if RAGFlow can recover the intended content for the LLM.', Parse: 'Parse', + file: 'File', dataset: 'Dataset', testing: 'Retrieval testing', files: 'files', diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 7a1dc8e..c8c406f 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -421,6 +421,7 @@ export default { raptor: 'Raptor', processingType: '处理类型', dataPipeline: '数据管道', + ingestionPipeline: '摄取管道', operations: '操作', taskId: '任务ID', duration: '耗时', @@ -447,6 +448,7 @@ export default { retrievalTestingDescription: '进行检索测试,检查 RAGFlow 是否能够为大语言模型(LLM)恢复预期的内容。', Parse: '解析', + file: '文件', dataset: '知识库', testing: '检索测试', configuration: '配置', diff --git a/src/pages/knowledge/components/FloatingActionButtons.tsx b/src/pages/knowledge/components/FloatingActionButtons.tsx index 96134e5..fda7b73 100644 --- a/src/pages/knowledge/components/FloatingActionButtons.tsx +++ b/src/pages/knowledge/components/FloatingActionButtons.tsx @@ -4,18 +4,20 @@ import { Dashboard as DashboardIcon, Search as TestIcon, Settings as ConfigIcon, - + ListAlt as OverviewIcon, } from '@mui/icons-material'; import { useTranslation } from 'react-i18next'; interface FloatingActionButtonsProps { onTestClick: () => void; onConfigClick: () => void; + onOverviewClick: () => void; } const FloatingActionButtons: React.FC = ({ onTestClick, onConfigClick, + onOverviewClick, }) => { const { t } = useTranslation(); @@ -30,6 +32,11 @@ const FloatingActionButtons: React.FC = ({ name: t('knowledge.configSettings'), onClick: onConfigClick, }, + { + icon: , + name: t('knowledgeDetails.overview'), + onClick: onOverviewClick, + }, ]; return ( diff --git a/src/pages/knowledge/detail.tsx b/src/pages/knowledge/detail.tsx index 7d989be..d93ce94 100644 --- a/src/pages/knowledge/detail.tsx +++ b/src/pages/knowledge/detail.tsx @@ -405,6 +405,7 @@ function KnowledgeBaseDetail() { navigate(`/knowledge/${id}/testing`)} onConfigClick={() => navigate(`/knowledge/${id}/setting`)} + onOverviewClick={() => navigate(`/knowledge/${id}/overview`)} /> {/* 上传文件对话框 */} diff --git a/src/pages/knowledge/index.ts b/src/pages/knowledge/index.ts index eb84c9c..51d5f16 100644 --- a/src/pages/knowledge/index.ts +++ b/src/pages/knowledge/index.ts @@ -2,4 +2,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'; \ No newline at end of file +export { default as KnowledgeBaseTesting } from './testing'; +export { default as KnowledgeLogsPage } from './overview'; \ No newline at end of file diff --git a/src/pages/knowledge/overview.tsx b/src/pages/knowledge/overview.tsx new file mode 100644 index 0000000..375b921 --- /dev/null +++ b/src/pages/knowledge/overview.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import { Box, Grid, Paper, Typography, IconButton, TextField, Tabs, Tab } from '@mui/material'; +import { DataGrid, type GridColDef } from '@mui/x-data-grid'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import SearchIcon from '@mui/icons-material/Search'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'react-router-dom'; +import { useKnowledgeOverview } from '@/hooks/knowledge-hooks'; +import type { IDocumentLog, IFileLogItem } from '@/interfaces/database/knowledge'; +import logger from '@/utils/logger'; +import i18n, { LanguageAbbreviation } from '@/locales'; +import { enUS, zhCN } from '@mui/x-data-grid/locales'; + + +const PROCESSING_TYPES = { + knowledgeGraph: 'GraphRAG', + raptor: 'RAPTOR', +} as const + +type ProcessingType = typeof PROCESSING_TYPES[keyof typeof PROCESSING_TYPES] + +const ProcessingTypeMap = { + [PROCESSING_TYPES.knowledgeGraph]: 'Knowledge Graph', + [PROCESSING_TYPES.raptor]: 'RAPTOR', +} as const + +function KnowledgeLogsPage() { + const { t } = useTranslation(); + const [paginationModel, setPaginationModel] = React.useState({ page: 0, pageSize: 50 }); + + // 根据当前语言获取DataGrid的localeText + const getDataGridLocale = () => { + const currentLanguage = i18n.language; + return currentLanguage === LanguageAbbreviation.Zh ? zhCN : enUS; + }; + + + // 路由参数与数据Hook + const { id: kbId = '' } = useParams(); + const { + overview, + fileLogs, + datasetLogs, + loading, + currentPage, + pageSize, + setCurrentPage, + setPageSize, + activeTab, + keywords, + setActiveTab, + setKeywords, + } = useKnowledgeOverview(kbId); + + // 同步分页模型到Hook + React.useEffect(() => { + setPaginationModel({ page: Math.max(currentPage - 1, 0), pageSize }); + }, [currentPage, pageSize]); + + const columnsFile: GridColDef[] = [ + { field: 'id', headerName: 'ID', width: 100 }, + { + field: 'document_name', headerName: t('knowledgeDetails.fileName'), flex: 1, minWidth: 160, valueGetter: (params) => { + logger.info('params', params) + return '' + } + }, + { field: 'source_from', headerName: t('knowledgeDetails.source'), flex: 1, minWidth: 140 }, + { field: 'pipeline_title', headerName: t('knowledgeDetails.ingestionPipeline'), flex: 1, minWidth: 180 }, + { field: 'create_date', headerName: t('knowledgeDetails.startDate') || 'Start Date', flex: 0.8, minWidth: 160, sortable: true }, + { field: 'task_type', headerName: t('knowledgeDetails.task') || 'Task', flex: 0.8, minWidth: 140 }, + { field: 'status', headerName: t('knowledgeDetails.status'), flex: 0.7, minWidth: 120 }, + { field: 'operations', headerName: t('knowledgeDetails.operations') || 'Operations', flex: 0.8, minWidth: 160, sortable: false, filterable: false, align: 'center', headerAlign: 'center' }, + ]; + + const columnsDataset: GridColDef[] = [ + { field: 'id', headerName: 'ID', width: 100 }, + { field: 'create_date', headerName: t('knowledgeDetails.startDate') || 'Start Date', flex: 1, minWidth: 160, sortable: true }, + { field: 'task_type', headerName: 'Processing Type', flex: 1, minWidth: 180 }, + { field: 'status', headerName: t('knowledgeDetails.status'), flex: 0.8, minWidth: 120 }, + { field: 'operations', headerName: t('knowledgeDetails.operations') || 'Operations', flex: 0.8, minWidth: 160, sortable: false, filterable: false, align: 'center', headerAlign: 'center' }, + ]; + + const columns = React.useMemo(() => (activeTab === 'fileLogs' ? + columnsFile : + columnsDataset), + [activeTab]); + const rows = React.useMemo(() => (activeTab === 'fileLogs' ? + (fileLogs?.logs || []) : + (datasetLogs?.logs || [])), + [activeTab, fileLogs, datasetLogs]); + const rowCount = React.useMemo(() => (activeTab === 'fileLogs' ? + (fileLogs?.total || 0) : + (datasetLogs?.total || 0)), + [activeTab, fileLogs, datasetLogs]); + + return ( + + {/* 顶部统计卡片占位 */} + + + + {t('datasetOverview.totalFiles')} + {fileLogs?.total ?? 0} + 0% from last week + + + + + {t('datasetOverview.downloading')} + 0 + {t('knowledgeDetails.success')} {overview?.finished ?? 0} · {t('knowledgeDetails.failed')} {overview?.failed ?? 0} + + + + + {t('datasetOverview.processing')} + {overview?.processing ?? 0} + {t('knowledgeDetails.success')} {overview?.finished ?? 0} · {t('knowledgeDetails.failed')} {overview?.failed ?? 0} + + + + + {/* Tabs & Filter/Search */} + + setActiveTab(v)}> + + + + {/* */} + + + setKeywords(e.target.value)} /> + + + + {/* DataGrid 表格占位 */} + + row.id} + density="comfortable" + pageSizeOptions={[20, 50, 100]} + paginationMode="server" + rowCount={rowCount} + paginationModel={paginationModel} + onPaginationModelChange={(model) => { + setPaginationModel(model); + setCurrentPage(model.page + 1); + setPageSize(model.pageSize); + }} + loading={loading} + disableColumnMenu + localeText={getDataGridLocale().components.MuiDataGrid.defaultProps.localeText} + /> + + + ); +} + +export default KnowledgeLogsPage; \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 680e49f..139fba7 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -10,7 +10,8 @@ import { KnowledgeBaseCreate, KnowledgeBaseDetail, KnowledgeBaseSetting, - KnowledgeBaseTesting + KnowledgeBaseTesting, + KnowledgeLogsPage } from '../pages/knowledge'; import { ModelsSetting, @@ -40,6 +41,7 @@ const AppRoutes = () => { } /> } /> } /> + } /> } /> diff --git a/src/services/api.ts b/src/services/api.ts index 273501a..80497d9 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -47,9 +47,10 @@ export default { getMeta: `${api_host}/kb/get_meta`, getKnowledgeBasicInfo: `${api_host}/kb/basic_info`, // data pipeline log - fetchDataPipelineLog: `${api_host}/kb/list_pipeline_logs`, + fetchDataPipelineLogs: `${api_host}/kb/list_pipeline_logs`, get_pipeline_detail: `${api_host}/kb/pipeline_log_detail`, fetchPipelineDatasetLogs: `${api_host}/kb/list_pipeline_dataset_logs`, + // run pipeline runGraphRag: `${api_host}/kb/run_graphrag`, traceGraphRag: `${api_host}/kb/trace_graphrag`, runRaptor: `${api_host}/kb/run_raptor`, diff --git a/src/services/knowledge_service.ts b/src/services/knowledge_service.ts index c210ea3..83f999d 100644 --- a/src/services/knowledge_service.ts +++ b/src/services/knowledge_service.ts @@ -251,11 +251,11 @@ const knowledgeService = { // ===== 数据管道 ===== // 获取数据管道日志 - getDataPipelineLog: ( + getDataPipelineLogs: ( params?: IFetchKnowledgeListRequestParams, body?: IFetchDocumentListRequestBody ) => { - return request.post(api.fetchDataPipelineLog, body || {}, { params }); + return request.post(api.fetchDataPipelineLogs, body || {}, { params }); }, // 获取管道详情