From 8ceff847768e43300a9d8975f3cb004a97bc4793 Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Tue, 18 Nov 2025 15:53:54 +0800 Subject: [PATCH] feat(knowledge): enhance knowledge base detail page with embedded views --- src/components/ProfileFormDialog.tsx | 5 +- src/locales/en.ts | 3 +- src/locales/zh.ts | 3 +- .../components/DocumentListComponent.tsx | 41 +++- src/pages/knowledge/detail.tsx | 203 ++++++++---------- src/pages/knowledge/overview.tsx | 70 +++--- src/pages/knowledge/testing.tsx | 44 ++-- src/pages/setting/profile.tsx | 7 +- 8 files changed, 211 insertions(+), 165 deletions(-) diff --git a/src/components/ProfileFormDialog.tsx b/src/components/ProfileFormDialog.tsx index c92aac3..6b8bab8 100644 --- a/src/components/ProfileFormDialog.tsx +++ b/src/components/ProfileFormDialog.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import ProfileForm, { type ProfileFormHandle } from '@/pages/setting/components/ProfileForm'; import { useProfileSetting } from '@/hooks/setting-hooks'; import type { IUserInfo } from '@/interfaces/database/user-setting'; +import { useUserData } from '@/hooks/useUserData'; interface ProfileFormDialogProps { open: boolean; @@ -17,12 +18,14 @@ interface ProfileFormDialogProps { const ProfileFormDialog: React.FC = ({ open, onClose }) => { const { t } = useTranslation(); const { userInfo, updateUserInfo } = useProfileSetting(); + const { refreshUserData } = useUserData(); const formRef = useRef(null); const handleSubmit = useCallback(async (data: Partial) => { await updateUserInfo(data); + await refreshUserData(); onClose(); - }, [updateUserInfo, onClose]); + }, [updateUserInfo, onClose, refreshUserData]); return ( diff --git a/src/locales/en.ts b/src/locales/en.ts index bbe6b36..fc4fa0d 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -60,6 +60,7 @@ export default { description: 'Description', confirm: 'Confirm', enabled: 'Enabled', + disabled: 'Disabled', clearFilter: 'Clear Filter', confirmFilter: 'Confirm Filter', private: 'Private', @@ -463,7 +464,7 @@ export default { file: 'File', dataset: 'Dataset', testing: 'Retrieval testing', - files: 'files', + files: 'Files List', configuration: 'Configuration', knowledgeGraph: 'Knowledge Graph', name: 'Name', diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 8507dca..5ed8d76 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -59,6 +59,7 @@ export default { description: '描述', confirm: '确认', enabled: '已启用', + disabled: '已禁用', clearFilter: '清空筛选', confirmFilter: '确认筛选', private: '私有', @@ -459,7 +460,7 @@ export default { testing: '检索测试', configuration: '配置', knowledgeGraph: '知识图谱', - files: '个文件', + files: '文件列表', name: '名称', namePlaceholder: '请输入名称', doc: '文档', diff --git a/src/pages/knowledge/components/DocumentListComponent.tsx b/src/pages/knowledge/components/DocumentListComponent.tsx index 1751cc0..d9b1b46 100644 --- a/src/pages/knowledge/components/DocumentListComponent.tsx +++ b/src/pages/knowledge/components/DocumentListComponent.tsx @@ -47,6 +47,7 @@ import { InsertDriveFile as FileIcon, Upload as UploadIcon, BugReportOutlined as ProcessIcon, + Download as DownloadIcon, } from '@mui/icons-material'; import { DataGrid, type GridColDef, type GridRowSelectionModel } from '@mui/x-data-grid'; import { zhCN, enUS } from '@mui/x-data-grid/locales'; @@ -56,6 +57,7 @@ import { RunningStatus } from '@/constants/knowledge'; import dayjs from 'dayjs'; import logger from '@/utils/logger'; import { LanguageAbbreviation } from '@/constants/common'; +import knowledgeService from '@/services/knowledge_service'; interface DocumentListComponentProps { @@ -172,6 +174,9 @@ const getRunStatusChip = (run: RunningStatus, progress: number) => { return ; }; +/** + * 文档列表组件 + */ const DocumentListComponent: React.FC = ({ files, loading, @@ -305,6 +310,32 @@ const DocumentListComponent: React.FC = ({ handleMenuClose(); }; + // 下载文件 + const handleDownload = async () => { + try { + const docId = selectedFileIdRef.current || selectedFileId; + const fileName = selectedFileRef.current?.name || selectedFile?.name || 'document'; + if (!docId) return; + const fileResponse: { data: Blob } = await knowledgeService.getDocumentFile({ doc_id: docId }); + if (fileResponse?.data instanceof Blob) { + const url = URL.createObjectURL(fileResponse.data); + if (window?.document) { + const link = window.document.createElement('a'); + link.href = url; + link.download = fileName; + window.document.body.appendChild(link); + link.click(); + window.document.body.removeChild(link); + } + URL.revokeObjectURL(url); + } + } catch (err) { + logger.error('下载文件失败:', err); + } finally { + handleMenuClose(); + } + }; + const handleRunStatusChange = (event: SelectChangeEvent) => { const value = event.target.value; setSelectedRunStatus(typeof value === 'string' ? value.split(',') : value); @@ -630,7 +661,7 @@ const DocumentListComponent: React.FC = ({ /> - {/* 右键菜单 */} + {/* 菜单 */} = ({ {t('knowledge.viewDetails')} + + + {t('chunkPage.downloadFile')} + {t('common.rename')} @@ -674,7 +709,9 @@ const DocumentListComponent: React.FC = ({ {/* 重命名对话框 */} setRenameDialogOpen(false)}> {t('knowledge.renameFile')} - + (null); - // 标签页状态 - const [currentTab, setCurrentTab] = useState(0); + // 标签页状态(documents / testing / overview / graph) + const [currentTab, setCurrentTab] = useState<'documents' | 'testing' | 'overview' | 'graph'>('documents'); // 轮询相关状态 const pollingIntervalRef = useRef(null); @@ -296,117 +299,95 @@ function KnowledgeBaseDetail() { {/* 知识库信息卡片 */} - {/* 标签页组件 - 仅在showKnowledgeGraph为true时显示 */} - {showKnowledgeGraph ? ( - - setCurrentTab(newValue)} - sx={{ borderBottom: 1, borderColor: 'divider' }} - > - - - - - {/* Document List 标签页内容 */} - {currentTab === 0 && ( - - handleReparse(fileIds)} - onDelete={(fileIds) => { - setRowSelectionModel({ - type: 'include', - ids: new Set(fileIds) - }); - setDeleteDialogOpen(true); - }} - onUpload={() => setUploadDialogOpen(true)} - onRefresh={() => { - refreshFiles(); - fetchKnowledgeDetail(); - }} - rowSelectionModel={rowSelectionModel} - onRowSelectionModelChange={(newModel) => { - setRowSelectionModel(newModel); - }} - total={total} - page={currentPage} - pageSize={pageSize} - documentFilter={documentsFilter} - onSelectedFilterChange={(filterBody) => { - fetchDocuments({}, filterBody); - }} - onPageChange={setCurrentPage} - onPageSizeChange={setPageSize} - onRename={handleRename} - onChangeStatus={handleChangeStatus} - onCancelRun={handleCancelRun} - onViewDetails={handleViewDetails} - onViewProcessDetails={handleViewProcessDetails} - /> - + {/* 标签页组件 - 默认显示 documents/testing/overview;Graph 仅在有数据时显示 */} + + setCurrentTab(newValue)} + sx={{ borderBottom: 1, borderColor: 'divider' }} + > + + + + {showKnowledgeGraph && ( + )} + - {/* Graph 标签页内容 */} - {currentTab === 1 && ( - - - - )} - - ) : ( - /* 原有的文件列表组件 - 当showKnowledgeGraph为false时显示 */ - handleReparse(fileIds)} - onDelete={(fileIds) => { - console.log(t('knowledgeDetails.deleteFiles'), fileIds); - setRowSelectionModel({ - type: 'include', - ids: new Set(fileIds) - }); - setDeleteDialogOpen(true); - }} - onUpload={() => setUploadDialogOpen(true)} - onRefresh={() => { - refreshFiles(); - fetchKnowledgeDetail(); - }} - rowSelectionModel={rowSelectionModel} - onRowSelectionModelChange={(newModel) => { - console.log(t('knowledgeDetails.newSelectionModel'), newModel); - setRowSelectionModel(newModel); - }} - total={total} - page={currentPage} - pageSize={pageSize} - documentFilter={documentsFilter} - onSelectedFilterChange={(filterBody) => { - fetchDocuments({}, filterBody); - }} - onPageChange={setCurrentPage} - onPageSizeChange={setPageSize} - onRename={handleRename} - onChangeStatus={handleChangeStatus} - onCancelRun={handleCancelRun} - onViewDetails={handleViewDetails} - onViewProcessDetails={handleViewProcessDetails} - /> - )} + {/* Documents 标签页内容 */} + {currentTab === 'documents' && ( + + handleReparse(fileIds)} + onDelete={(fileIds) => { + setRowSelectionModel({ + type: 'include', + ids: new Set(fileIds) + }); + setDeleteDialogOpen(true); + }} + onUpload={() => setUploadDialogOpen(true)} + onRefresh={() => { + refreshFiles(); + fetchKnowledgeDetail(); + }} + rowSelectionModel={rowSelectionModel} + onRowSelectionModelChange={(newModel) => { + setRowSelectionModel(newModel); + }} + total={total} + page={currentPage} + pageSize={pageSize} + documentFilter={documentsFilter} + onSelectedFilterChange={(filterBody) => { + fetchDocuments({}, filterBody); + }} + onPageChange={setCurrentPage} + onPageSizeChange={setPageSize} + onRename={handleRename} + onChangeStatus={handleChangeStatus} + onCancelRun={handleCancelRun} + onViewDetails={handleViewDetails} + onViewProcessDetails={handleViewProcessDetails} + /> + + )} - {/* 浮动操作按钮 */} - navigate(`/knowledge/${id}/testing`)} - onConfigClick={() => navigate(`/knowledge/${id}/setting`)} - onOverviewClick={() => navigate(`/knowledge/${id}/overview`)} - /> + {/* Testing 标签页内容 */} + {currentTab === 'testing' && ( + + + + )} + + {/* Overview 标签页内容 */} + {currentTab === 'overview' && ( + + + + )} + + {/* Graph 标签页内容 */} + {currentTab === 'graph' && showKnowledgeGraph && ( + + + + )} + + + {/* 设置按钮(替代原浮动操作按钮) */} + navigate(`/knowledge/${id}/setting`)} + sx={{ position: 'fixed', bottom: 128, right: 64 }} + > + + {/* 上传文件对话框 */} + {/* 面包屑导航 */} - + {!embedded && ( + + )} {/* 顶部统计卡片占位 */} @@ -183,18 +191,20 @@ function KnowledgeLogsPage() { {/* 返回按钮 */} - - - + {!embedded && ( + + + + )} ); } diff --git a/src/pages/knowledge/testing.tsx b/src/pages/knowledge/testing.tsx index fe60a22..d24f0e3 100644 --- a/src/pages/knowledge/testing.tsx +++ b/src/pages/knowledge/testing.tsx @@ -61,9 +61,15 @@ interface TestFormData { doc_ids?: string[]; } -function KnowledgeBaseTesting() { +interface KnowledgeBaseTestingProps { + embedded?: boolean; + kbId?: string; +} + +function KnowledgeBaseTesting({ embedded = false, kbId }: KnowledgeBaseTestingProps) { const { t } = useTranslation(); - const { id } = useParams<{ id: string }>(); + const { id: idParam } = useParams<{ id: string }>(); + const id = kbId || idParam || ''; const navigate = useNavigate(); // 状态管理 @@ -230,24 +236,26 @@ function KnowledgeBaseTesting() { } return ( - + {/* 面包屑导航 */} - + {!embedded && ( + + )} diff --git a/src/pages/setting/profile.tsx b/src/pages/setting/profile.tsx index 9bd1836..7d1758c 100644 --- a/src/pages/setting/profile.tsx +++ b/src/pages/setting/profile.tsx @@ -6,10 +6,12 @@ import ProfileForm from "./components/ProfileForm"; import ChangePasswordDialog from "./components/ChangePasswordDialog"; import { useProfileSetting } from "@/hooks/setting-hooks"; import logger from "@/utils/logger"; +import { useUserData } from "@/hooks/useUserData"; function ProfileSetting() { const { t } = useTranslation(); const { userInfo, updateUserInfo: updateUserInfoFunc, changeUserPassword: changeUserPasswordFunc } = useProfileSetting(); + const { refreshUserData } = useUserData(); const [passwordDialogOpen, setPasswordDialogOpen] = useState(false); logger.debug('userInfo', userInfo); @@ -25,7 +27,10 @@ function ProfileSetting() { return ( {/* 个人资料表单 */} - + { + await updateUserInfoFunc(data); + await refreshUserData(); + }} /> {/* 分割线 */}