Merge branch 'feature/knowledgeBase_yjp' into develop

This commit is contained in:
yujiangping
2026-02-03 17:12:36 +08:00
22 changed files with 415 additions and 416 deletions

View File

@@ -24,7 +24,7 @@ const { TextArea } = Input;
const radioWrapperBaseStyle: React.CSSProperties = { const radioWrapperBaseStyle: React.CSSProperties = {
display: 'flex', display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
columnGap: 14, // 点与文字更宽的间距 columnGap: 14, // Wider gap between dot and text
width: '100%', width: '100%',
border: '1px solid #E5E5E5', border: '1px solid #E5E5E5',
borderRadius: 8, borderRadius: 8,
@@ -97,7 +97,7 @@ const CreateDataset = () => {
() => [ () => [
{ title: t('knowledgeBase.selectFile') }, { title: t('knowledgeBase.selectFile') },
{ title: t('knowledgeBase.parameterSettings') }, { title: t('knowledgeBase.parameterSettings') },
// { title: t('knowledgeBase.dataPreview') }, // 暂时隐藏第三步 // { title: t('knowledgeBase.dataPreview') }, // Temporarily hide step 3
{ title: t('knowledgeBase.confirmUpload') }, { title: t('knowledgeBase.confirmUpload') },
], ],
[t], [t],
@@ -105,27 +105,27 @@ const CreateDataset = () => {
// 存储每个文件的 AbortController用于取消上传 // 存储每个文件的 AbortController用于取消上传
const abortControllersRef = useRef<Map<string, AbortController>>(new Map()); const abortControllersRef = useRef<Map<string, AbortController>>(new Map());
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null); const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
console.log('上传文件',uploadRef.current?.fileList.length) console.log('Upload files', uploadRef.current?.fileList.length)
const handleNext = async () => { const handleNext = async () => {
// 暂时隐藏第三步调整步骤索引0->1->2 对应 选择文件->参数设置->确认上传) // Temporarily hide step 3: adjust step index (0->1->2 corresponds to select file->parameter settings->confirm upload)
let nextStep = current + 1; let nextStep = current + 1;
if(nextStep === 1 && source === 'local') { if(nextStep === 1 && source === 'local') {
// 检查是否有文件已上传 // Check if files have been uploaded
if (rechunkFileIds.length === 0) { if (rechunkFileIds.length === 0) {
// 如果没有文件,提示用户先上传文件 // If no files, prompt user to upload first
Modal.warning({ Modal.warning({
title: t('common.warning') || '提示', title: t('common.warning') || 'Warning',
content: t('knowledgeBase.pleaseUploadFileFirst') || '请先上传文件', content: t('knowledgeBase.pleaseUploadFileFirst') || 'Please upload files first',
}); });
return; // 不进入下一步 return; // Don't proceed to next step
} }
}else if(nextStep === 1 && source === 'text'){ }else if(nextStep === 1 && source === 'text'){
try { try {
const values = await form.validateFields(); const values = await form.validateFields();
// setLoading(true); // setLoading(true);
// TODO: 这里需要调用相应的API来保存内容 // TODO: Need to call corresponding API to save content here
const params = { const params = {
// ...values, // ...values,
kb_id: knowledgeBaseId, kb_id: knowledgeBaseId,
@@ -162,41 +162,41 @@ const CreateDataset = () => {
}) })
} }
// 立即执行一次,加载文档列表用于预览(不自动返回) // Execute once immediately to load document list for preview (don't auto-return)
pollDocumentStatus(false); pollDocumentStatus(false);
} }
// 限制最大步骤为 2确认上传 // Limit max step to 2 (confirm upload)
setCurrent(Math.min(nextStep, 2)); setCurrent(Math.min(nextStep, 2));
}; };
const handlePrev = () => setCurrent((c) => Math.max(c - 1, 0)); const handlePrev = () => setCurrent((c) => Math.max(c - 1, 0));
// 开始上传:触发文档解析并启动轮询 // Start upload: trigger document parsing and start polling
const handleStartUpload = () => { const handleStartUpload = () => {
if (rechunkFileIds.length === 0) { if (rechunkFileIds.length === 0) {
Modal.warning({ Modal.warning({
title: t('common.warning') || '提示', title: t('common.warning') || 'Warning',
content: t('knowledgeBase.pleaseUploadFileFirst') || '请先上传文件', content: t('knowledgeBase.pleaseUploadFileFirst') || 'Please upload files first',
}); });
return; return;
} }
// 显示确认弹框 // 显示确认弹框
confirm({ confirm({
title: t('knowledgeBase.startUploadConfirmTitle') || '开始处理文档', title: t('knowledgeBase.startUploadConfirmTitle') || 'Start processing documents',
content: t('knowledgeBase.startUploadConfirmContent') || '文档处理将在后台进行,您可以选择立即返回列表页或停留在此页面查看处理进度。', content: t('knowledgeBase.startUploadConfirmContent') || 'Document processing will proceed in the background. You can choose to return to the list page immediately or stay on this page to view processing progress.',
okText: t('knowledgeBase.returnToList') || '返回列表页', okText: t('knowledgeBase.returnToList') || 'Return to list',
cancelText: t('knowledgeBase.stayOnPage') || '停留在此页', cancelText: t('knowledgeBase.stayOnPage') || 'Stay on this page',
onOk: () => { onOk: () => {
// 用户选择返回列表页 - 不显示 loading直接跳转 // User chose to return to list - don't show loading, navigate directly
startProcessing(true); startProcessing(true);
}, },
onCancel: () => { onCancel: () => {
// 用户选择停留在当前页 - 显示 loading 并开始轮询 // User chose to stay on current page - show loading and start polling
console.log('用户选择停留,开始显示 loading'); console.log('User chose to stay, starting to show loading');
setPollingLoading(true); setPollingLoading(true);
// 延迟一点时间让用户看到 loading 效果,然后开始处理 // Delay a bit to let user see loading effect, then start processing
setTimeout(() => { setTimeout(() => {
startProcessing(false); startProcessing(false);
}, 100); }, 100);
@@ -204,25 +204,25 @@ const CreateDataset = () => {
}); });
}; };
// 实际开始处理的函数 // Function to actually start processing
const startProcessing = (autoReturnToList: boolean) => { const startProcessing = (autoReturnToList: boolean) => {
// 触发文档解析 // Trigger document parsing
rechunkFileIds.map((id) => { rechunkFileIds.map((id) => {
parseDocument(id, {}); parseDocument(id, {});
}); });
if (autoReturnToList) { if (autoReturnToList) {
// 用户选择立即返回,直接跳转(不显示 loading // User chose to return immediately, navigate directly (no loading shown)
console.log('用户选择立即返回列表页'); console.log('User chose to return to list page immediately');
handleBack(); handleBack();
} else { } else {
// 用户选择停留启动轮询查看进度loading 已在 onCancel 中设置) // User chose to stay, start polling to view progress (loading already set in onCancel)
console.log('用户选择停留查看进度'); console.log('User chose to stay and view progress');
// 立即执行一次轮询(启用自动返回) // Execute polling once immediately (enable auto-return)
pollDocumentStatus(true); pollDocumentStatus(true);
// 然后每3秒执行一次启用自动返回 // Then execute every 3 seconds (enable auto-return)
pollingTimerRef.current = setInterval(() => { pollingTimerRef.current = setInterval(() => {
pollDocumentStatus(true); pollDocumentStatus(true);
}, 3000); }, 3000);
@@ -244,11 +244,11 @@ const CreateDataset = () => {
}, },
onCancel: () => { onCancel: () => {
console.log('取消删除'); console.log('Delete cancelled');
}, },
}); });
} }
// 表格列配置 // Table column configuration
const columns: ColumnsType = [ const columns: ColumnsType = [
{ {
title: t('knowledgeBase.name'), title: t('knowledgeBase.name'),
@@ -261,7 +261,7 @@ const CreateDataset = () => {
dataIndex: 'progress', dataIndex: 'progress',
key: 'progress', key: 'progress',
render: (value: number, record: any) => { render: (value: number, record: any) => {
// value >= 1 时完成01 时显示进度条 // When value >= 1 it's complete, when 01 show progress bar
if (value >= 1) { if (value >= 1) {
return ( return (
<span className="rb:text-xs rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded rb:items-center rb:text-[#212332] rb:py-1 rb:px-2"> <span className="rb:text-xs rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded rb:items-center rb:text-[#212332] rb:py-1 rb:px-2">
@@ -270,7 +270,7 @@ const CreateDataset = () => {
</span> </span>
); );
} else if (value >= 0 && value < 1) { } else if (value >= 0 && value < 1) {
// 处理中,显示进度条 // Processing, show progress bar
return ( return (
<div className="rb:flex rb:items-center rb:gap-2"> <div className="rb:flex rb:items-center rb:gap-2">
<Progress <Progress
@@ -286,7 +286,7 @@ const CreateDataset = () => {
</div> </div>
); );
} else { } else {
// value = 0 或其他情况,显示待处理 // value = 0 or other cases, show pending
return ( return (
<span className="rb:text-xs rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded rb:items-center rb:text-[#212332] rb:py-1 rb:px-2"> <span className="rb:text-xs rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:rounded rb:items-center rb:text-[#212332] rb:py-1 rb:px-2">
<span className="rb:inline-block rb:w-[5px] rb:h-[5px] rb:mr-2 rb:rounded-full" style={{ backgroundColor: '#FF8A4C' }}></span> <span className="rb:inline-block rb:w-[5px] rb:h-[5px] rb:mr-2 rb:rounded-full" style={{ backgroundColor: '#FF8A4C' }}></span>
@@ -304,7 +304,7 @@ const CreateDataset = () => {
), ),
}, },
]; ];
// 检查媒体文件时长的辅助函数 // Helper function to check media file duration
const checkMediaDuration = (file: File): Promise<number> => { const checkMediaDuration = (file: File): Promise<number> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
@@ -324,36 +324,36 @@ const CreateDataset = () => {
}); });
}; };
// 上传文件 // Upload file
const handleUpload = async (options: UploadRequestOption) => { const handleUpload = async (options: UploadRequestOption) => {
const { file, onSuccess, onError, onProgress, filename = 'file' } = options; const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
// 创建 AbortController 用于取消上传 // Create AbortController for cancelling upload
const abortController = new AbortController(); const abortController = new AbortController();
const fileUid = (file as any).uid; const fileUid = (file as any).uid;
abortControllersRef.current.set(fileUid, abortController); abortControllersRef.current.set(fileUid, abortController);
// 获取文件扩展名 // Get file extension
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase(); const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav']; const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
// 如果是媒体文件,进行大小和时长检查 // If media file, check size and duration
if (fileExtension && mediaExtensions.includes(fileExtension)) { if (fileExtension && mediaExtensions.includes(fileExtension)) {
const fileSizeInMB = (file as File).size / (1024 * 1024); const fileSizeInMB = (file as File).size / (1024 * 1024);
// 检查文件大小50MB限制 // 检查文件大小50MB限制
if (fileSizeInMB > 100) { if (fileSizeInMB > 100) {
messageApi.error(`${t('knowledgeBase.sizeLimitError')}${fileSizeInMB.toFixed(2)}MB`); messageApi.error(`${t('knowledgeBase.sizeLimitError')}: ${fileSizeInMB.toFixed(2)}MB`);
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`)); onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
return; return;
} }
try { try {
// 检查媒体时长150秒限制 // Check media duration (150 second limit)
const duration = await checkMediaDuration(file as File); const duration = await checkMediaDuration(file as File);
if (duration > 150) { if (duration > 150) {
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}${Math.round(duration)}`); messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}: ${Math.round(duration)}s`);
onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`)); onError?.(new Error(`${t('knowledgeBase.fileDurationExceeds')}`));
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
return; return;
@@ -386,7 +386,7 @@ const CreateDataset = () => {
}, },
}) })
.then((res: UploadFileResponse) => { .then((res: UploadFileResponse) => {
// 上传成功,移除 AbortController // Upload successful, remove AbortController
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
onSuccess?.(res, new XMLHttpRequest()); onSuccess?.(res, new XMLHttpRequest());
@@ -399,12 +399,12 @@ const CreateDataset = () => {
} }
}) })
.catch((error) => { .catch((error) => {
// 移除 AbortController // Remove AbortController
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
// 如果是用户主动取消,不显示错误信息 // If user actively cancelled, don't show error message
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') { if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
console.log('上传已取消:', (file as File).name); console.log('Upload cancelled:', (file as File).name);
return; return;
} }
onError?.(error as Error); onError?.(error as Error);
@@ -413,12 +413,12 @@ const CreateDataset = () => {
// 轮询检查文档处理状态 // 轮询检查文档处理状态
// autoReturn: 是否在所有文档完成时自动返回列表页 // autoReturn: whether to automatically return to list page when all documents are completed
const pollDocumentStatus = (autoReturn: boolean = false) => { const pollDocumentStatus = (autoReturn: boolean = false) => {
console.log('开始轮询文档状态,当前 pollingLoading:', pollingLoading); console.log('Start polling document status, current pollingLoading:', pollingLoading);
if (!knowledgeBaseId || !parentId || rechunkFileIds.length === 0) { if (!knowledgeBaseId || !parentId || rechunkFileIds.length === 0) {
console.log('轮询条件不满足,退出'); console.log('Polling conditions not met, exiting');
return; return;
} }
@@ -436,10 +436,10 @@ const CreateDataset = () => {
} }
console.log('documents', documents); console.log('documents', documents);
// 检查是否所有文档的 progress 都为 1 // Check if all documents have progress of 1
const allCompleted = documents.every((doc: KnowledgeBaseDocumentData) => doc.progress === 1); const allCompleted = documents.every((doc: KnowledgeBaseDocumentData) => doc.progress === 1);
console.log('轮询状态:', allCompleted); console.log('Polling status:', allCompleted);
// 检查是否所有文档都完成了 // 检查是否所有文档都完成了
// debugger // debugger
@@ -455,23 +455,23 @@ const CreateDataset = () => {
setPollingLoading(false); setPollingLoading(false);
}, 1000); }, 1000);
// 只有在 autoReturn true 时才自动返回 // Only auto-return when autoReturn is true
if (autoReturn) { if (autoReturn) {
// 延迟 2 秒后跳转,让用户看到完成状态 // Delay 2 seconds before navigating to let user see completion status
console.log('所有文档处理完成2秒后返回列表页'); console.log('All documents processed, returning to list page in 2 seconds');
setTimeout(() => { setTimeout(() => {
handleBack(); handleBack();
}, 2000); }, 2000);
} else { } else {
console.log('所有文档处理完成,用户可手动操作'); console.log('All documents processed, user can operate manually');
} }
} else { } else {
// 如果还有文档在处理中,确保 loading 状态保持 // If documents are still processing, keep loading state
console.log('还有文档在处理中,保持 loading 状态'); console.log('Documents still processing, maintaining loading state');
} }
}) })
.catch((error) => { .catch((error) => {
console.error('轮询文档状态失败:', error); console.error('Failed to poll document status:', error);
setPollingLoading(false); setPollingLoading(false);
}); });
}; };
@@ -486,7 +486,7 @@ const CreateDataset = () => {
}, },
}); });
} else { } else {
console.warn('缺少路由参数,无法返回'); console.warn('Missing route parameters, unable to return');
} }
}; };
const handleChange = (value: number | null) =>{ const handleChange = (value: number | null) =>{
@@ -498,17 +498,17 @@ const CreateDataset = () => {
const handleDeleteFile = async (fileId: string) => { const handleDeleteFile = async (fileId: string) => {
try { try {
await deleteDocument(fileId); await deleteDocument(fileId);
// 删除成功,从 rechunkFileIds 中移除该 id // Delete successful, remove the id from rechunkFileIds
setRechunkFileIds((prev) => prev.filter((id) => id !== fileId)); setRechunkFileIds((prev) => prev.filter((id) => id !== fileId));
console.log(`${t('common.deleteSuccess')}`); console.log(`${t('common.deleteSuccess')}`);
} catch (error) { } catch (error) {
messageApi.error(`${t('common.deleteFailed')}`); messageApi.error(`${t('common.deleteFailed')}`);
} }
}; };
// 当从其他页面跳转过来且带有 fileIds 时,加载对应的文档数据 // When navigating from other pages with fileIds, load corresponding document data
// useEffect(() => { // useEffect(() => {
// if (initialFileIds.length > 0 && initialStepKey !== 'selectFile' && knowledgeBaseId && parentId) { // if (initialFileIds.length > 0 && initialStepKey !== 'selectFile' && knowledgeBaseId && parentId) {
// // 加载文档列表数据 // // Load document list data
// getDocumentList(knowledgeBaseId,{ // getDocumentList(knowledgeBaseId,{
// document_ids: initialFileIds.join(','), // document_ids: initialFileIds.join(','),
// }) // })
@@ -517,12 +517,12 @@ const CreateDataset = () => {
// setData(documents); // setData(documents);
// }) // })
// .catch((error) => { // .catch((error) => {
// console.error('加载文档列表失败:', error); // console.error('Failed to load document list:', error);
// }); // });
// } // }
// }, []); // }, []);
// 清理函数:组件卸载时清除定时器和 loading 状态 // Cleanup function: clear timer and loading state when component unmounts
useEffect(() => { useEffect(() => {
return () => { return () => {
if (pollingTimerRef.current) { if (pollingTimerRef.current) {
@@ -533,10 +533,10 @@ const CreateDataset = () => {
}; };
}, []); }, []);
// 监听路由变化,确保在页面切换时清理状态 // Watch for route changes, ensure state is cleaned up when page switches
useEffect(() => { useEffect(() => {
return () => { return () => {
// 页面卸载时清理状态 // Clean up state when page unmounts
if (pollingTimerRef.current) { if (pollingTimerRef.current) {
clearInterval(pollingTimerRef.current); clearInterval(pollingTimerRef.current);
pollingTimerRef.current = null; pollingTimerRef.current = null;
@@ -574,7 +574,7 @@ const CreateDataset = () => {
fileType={fileType} fileType={fileType}
customRequest={handleUpload} customRequest={handleUpload}
onChange={(fileList) => { onChange={(fileList) => {
console.log('文件列表变化:', fileList); console.log('File list changed:', fileList);
}} }}
onRemove={async (file) => { onRemove={async (file) => {
// 如果文件正在上传,取消上传 // 如果文件正在上传,取消上传
@@ -583,26 +583,26 @@ const CreateDataset = () => {
if (abortController) { if (abortController) {
abortController.abort(); abortController.abort();
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
console.log('已取消上传:', (file as any).name); console.log('Upload cancelled:', (file as any).name);
// 取消上传后直接返回 true允许移除文件 // 取消上传后直接返回 true允许移除文件
return true; return true;
} }
// 只有当文件已经上传成功有response.id才删除服务器上的文件 // Only delete server file when file upload was successful (has response.id)
if (file.response?.id) { if (file.response?.id) {
try { try {
await deleteDocument(file.response.id); await deleteDocument(file.response.id);
setRechunkFileIds(prev => prev.filter(id => id !== file.response.id)); setRechunkFileIds(prev => prev.filter(id => id !== file.response.id));
console.log('已删除服务器文件:', file.response.id); console.log('Server file deleted:', file.response.id);
return true; return true;
} catch (error) { } catch (error) {
console.error('删除文件失败:', error); console.error('Failed to delete file:', error);
messageApi.error(t('common.deleteFailed') || '删除文件失败'); messageApi.error(t('common.deleteFailed') || 'Failed to delete file');
return false; // 删除失败时不移除文件 return false; // Don't remove file when deletion fails
} }
} }
// 其他情况(如上传失败的文件)也允许移除 // Also allow removal in other cases (such as failed uploads)
return true; return true;
}} /> }} />
)} )}

View File

@@ -1,5 +1,5 @@
/* /**
* @Description: 文档详情 * @Description: Document Details
* @Version: 0.0.1 * @Version: 0.0.1
* @Author: yujiangping * @Author: yujiangping
* @Date: 2025-11-15 16:13:47 * @Date: 2025-11-15 16:13:47
@@ -57,28 +57,28 @@ const DocumentDetails: FC = () => {
} }
}, [documentId]); }, [documentId]);
// 更新面包屑 // Update breadcrumbs
useEffect(() => { useEffect(() => {
if (breadcrumbPath) { if (breadcrumbPath) {
updateBreadcrumbs(breadcrumbPath); updateBreadcrumbs(breadcrumbPath);
} }
}, [breadcrumbPath, updateBreadcrumbs]); }, [breadcrumbPath, updateBreadcrumbs]);
// 当文档加载完成且 progress === 1 时,加载分块列表 // Load chunk list when document is loaded and progress === 1
useEffect(() => { useEffect(() => {
if (document && document.progress === 1 && !isManualRefreshRef.current) { if (document && document.progress === 1 && !isManualRefreshRef.current) {
ChunkList(); ChunkList();
} }
// 重置标志 // Reset flag
isManualRefreshRef.current = false; isManualRefreshRef.current = false;
}, [document]); }, [document]);
// 监听 keywords 变化,重新搜索 // Listen to keywords changes and re-search
useEffect(() => { useEffect(() => {
if (documentId && keywords && document?.progress === 1) { if (documentId && keywords && document?.progress === 1) {
setPage(1); // 重置页码 setPage(1); // Reset page number
setChunkList([]); // 清空列表 setChunkList([]); // Clear list
ChunkList(1, false); // 重新加载第一页 ChunkList(1, false); // Reload first page
} }
}, [keywords]); }, [keywords]);
@@ -129,9 +129,9 @@ const DocumentDetails: FC = () => {
const url = `${imagePath}/api/files/${response.file_id}` const url = `${imagePath}/api/files/${response.file_id}`
setFileUrl(url); setFileUrl(url);
setParserMode(response?.parser_config?.auto_questions || 0) setParserMode(response?.parser_config?.auto_questions || 0)
// ChunkList 会在 useEffect 中根据 document.progress 自动调用 // ChunkList will be called automatically in useEffect based on document.progress
} catch (error) { } catch (error) {
console.error('获取文档详情失败:', error); console.error('Failed to fetch document details:', error);
message.error(t('common.loadFailed') || '加载失败'); message.error(t('common.loadFailed') || '加载失败');
} finally { } finally {
setLoading(false); setLoading(false);
@@ -140,12 +140,12 @@ const DocumentDetails: FC = () => {
const ChunkList = async (pageNum: number = 1, append: boolean = false, force: boolean = false) => { const ChunkList = async (pageNum: number = 1, append: boolean = false, force: boolean = false) => {
if (!documentId) return; if (!documentId) return;
// 如果不是强制刷新,且正在加载中,则跳过 // Skip if not force refresh and already loading
if (!force && chunkLoading) { if (!force && chunkLoading) {
return; return;
} }
// 只有当文档处理完成时才获取分块列表 // Only fetch chunk list when document processing is complete
if (document && document.progress !== 1) { if (document && document.progress !== 1) {
return; return;
} }
@@ -157,10 +157,10 @@ const DocumentDetails: FC = () => {
keywords: keywords || undefined, keywords: keywords || undefined,
page: pageNum, page: pageNum,
pagesize: 20, pagesize: 20,
_t: force ? Date.now() : undefined, // 强制刷新时添加时间戳破坏缓存 _t: force ? Date.now() : undefined, // Add timestamp to break cache when force refresh
}); });
// 转换数据格式以匹配 RecallTestData // Convert data format to match RecallTestData
const formattedChunks: RecallTestData[] = response.items.map((item: any) => ({ const formattedChunks: RecallTestData[] = response.items.map((item: any) => ({
page_content: item.page_content || item.content || '', page_content: item.page_content || item.content || '',
vector: null, vector: null,
@@ -172,7 +172,7 @@ const DocumentDetails: FC = () => {
document_id: item.metadata.document_id || documentId || '', document_id: item.metadata.document_id || documentId || '',
knowledge_id: item.metadata.knowledge_id || knowledgeBaseId || '', knowledge_id: item.metadata.knowledge_id || knowledgeBaseId || '',
sort_id: item.metadata.sort_id || item.id || 0, sort_id: item.metadata.sort_id || item.id || 0,
score: item.metadata.score || null, // chunk 列表没有相似度分数 score: item.metadata.score || null, // Chunk list has no similarity score
status: item.metadata.status, status: item.metadata.status,
}, },
children: null, children: null,
@@ -186,7 +186,7 @@ const DocumentDetails: FC = () => {
setHasMore(response.page?.has_next ?? false); setHasMore(response.page?.has_next ?? false);
} catch (error) { } catch (error) {
console.error('获取文档详情失败:', error); console.error('Failed to fetch document details:', error);
message.error(t('common.loadFailed') || '加载失败'); message.error(t('common.loadFailed') || '加载失败');
} finally { } finally {
setChunkLoading(false); setChunkLoading(false);
@@ -201,17 +201,17 @@ const DocumentDetails: FC = () => {
const handleBack = () => { const handleBack = () => {
if (knowledgeBaseId && breadcrumbPath) { if (knowledgeBaseId && breadcrumbPath) {
// 返回到知识库详情页,并传递面包屑信息以恢复状态 // Return to knowledge base detail page and pass breadcrumb info to restore state
const navigationState = { const navigationState = {
fromKnowledgeBaseList: true, fromKnowledgeBaseList: true,
knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath, knowledgeBaseFolderPath: breadcrumbPath.knowledgeBaseFolderPath,
navigateToDocumentFolder: locationParentId, navigateToDocumentFolder: locationParentId,
documentFolderPath: breadcrumbPath.documentFolderPath, documentFolderPath: breadcrumbPath.documentFolderPath,
timestamp: Date.now(), // 添加时间戳确保状态变化 timestamp: Date.now(), // Add timestamp to ensure state change
}; };
navigate(`/knowledge-base/${knowledgeBaseId}/private`, { state: navigationState }); navigate(`/knowledge-base/${knowledgeBaseId}/private`, { state: navigationState });
} else if (knowledgeBaseId) { } else if (knowledgeBaseId) {
// 降级处理:直接跳转到知识库详情页 // Fallback: Navigate directly to knowledge base detail page
navigate(`/knowledge-base/${knowledgeBaseId}/private`); navigate(`/knowledge-base/${knowledgeBaseId}/private`);
} }
}; };
@@ -226,61 +226,61 @@ const DocumentDetails: FC = () => {
insertModalRef.current?.handleOpen(documentId); insertModalRef.current?.handleOpen(documentId);
}; };
// 处理插入/编辑内容 // Handle insert/edit content
const handleInsertContent = async (_docId: string, content: string, chunkId?: string): Promise<boolean> => { const handleInsertContent = async (_docId: string, content: string, chunkId?: string): Promise<boolean> => {
try { try {
if (chunkId) { if (chunkId) {
// 编辑模式:更新现有块 // Edit mode: Update existing chunk
const response = await updateDocumentChunk(knowledgeBaseId || '', documentId, chunkId, { content }); const response = await updateDocumentChunk(knowledgeBaseId || '', documentId, chunkId, { content });
// 直接更新前端列表,不等待后端缓存刷新 // Update frontend list directly without waiting for backend cache refresh
setChunkList(prev => prev.map(item => setChunkList(prev => prev.map(item =>
item.metadata?.doc_id === chunkId item.metadata?.doc_id === chunkId
? { ...item, page_content: response.page_content || content } ? { ...item, page_content: response.page_content || content }
: item : item
)); ));
// 编辑模式返回特殊标记,告诉 InsertModal 不要调用 onSuccess // Edit mode returns special flag to tell InsertModal not to call onSuccess
return true; return true;
} else { } else {
// 插入模式:创建新块 // Insert mode: Create new chunk
await createDocumentChunk(knowledgeBaseId || '', documentId, { content }); await createDocumentChunk(knowledgeBaseId || '', documentId, { content });
return true; return true;
} }
} catch (error) { } catch (error) {
console.error('操作失败:', error); console.error('Operation failed:', error);
return false; return false;
} }
}; };
// 处理点击文本块 // Handle click on text chunk
const handleChunkClick = (item: RecallTestData, index: number) => { const handleChunkClick = (item: RecallTestData, index: number) => {
if (!documentId) return; if (!documentId) return;
const chunkId = String(item.metadata?.doc_id || index); const chunkId = String(item.metadata?.doc_id || index);
insertModalRef.current?.handleOpen(documentId, item.page_content, chunkId); insertModalRef.current?.handleOpen(documentId, item.page_content, chunkId);
}; };
// 插入成功后的回调(仅用于插入新块,编辑操作已在 handleInsertContent 中同步更新) // Callback after successful insert (only for inserting new chunks, edit operations are already updated synchronously in handleInsertContent)
const handleInsertSuccess = () => { const handleInsertSuccess = () => {
// 设置手动刷新标志,防止 useEffect 重复调用 // Set manual refresh flag to prevent useEffect from calling repeatedly
isManualRefreshRef.current = true; isManualRefreshRef.current = true;
// 重置页码 // Reset page number
setPage(1); setPage(1);
// 等待后端处理完成,然后重新加载数据(仅用于插入新块的情况) // Wait for backend processing to complete, then reload data (only for inserting new chunks)
setTimeout(() => { setTimeout(() => {
ChunkList(1, false, true).then(() => { ChunkList(1, false, true).then(() => {
return fetchDocumentDetail(); return fetchDocumentDetail();
}).catch(err => { }).catch(err => {
console.error('刷新失败:', err); console.error('Refresh failed:', err);
}); });
}, 1000); }, 1000);
}; };
const handleAdjustmentParameter = () =>{ const handleAdjustmentParameter = () =>{
if (!knowledgeBaseId || !document) return; if (!knowledgeBaseId || !document) return;
const targetFileId = document.id; const targetFileId = document.id;
// 优先使用从 location 传递的 parentId其次使用 document.parent_id,最后使用 knowledgeBaseId // Prioritize parentId from location, then document.parent_id, finally knowledgeBaseId
const parentId = locationParentId ?? document.parent_id ?? document.kb_id ?? knowledgeBaseId; const parentId = locationParentId ?? document.parent_id ?? document.kb_id ?? knowledgeBaseId;
navigate(`/knowledge-base/${knowledgeBaseId}/create-dataset`, { navigate(`/knowledge-base/${knowledgeBaseId}/create-dataset`, {
@@ -317,7 +317,7 @@ const DocumentDetails: FC = () => {
<img src={exitIcon} alt='exit' className='rb:w-4 rb:h-4' /> <img src={exitIcon} alt='exit' className='rb:w-4 rb:h-4' />
<span className='rb:text-gray-500 rb:text-sm'>{t('common.exit')}</span> <span className='rb:text-gray-500 rb:text-sm'>{t('common.exit')}</span>
</div> </div>
{/* 文档预览 */} {/* Document preview */}
{fileUrl && ( {fileUrl && (
<div className='rb:flex-1 rb:border rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4 rb:overflow-hidden'> <div className='rb:flex-1 rb:border rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4 rb:overflow-hidden'>
<h3 className="rb:text-sm rb:font-medium rb:mb-3"> <h3 className="rb:text-sm rb:font-medium rb:mb-3">
@@ -339,7 +339,7 @@ const DocumentDetails: FC = () => {
return (<> return (<>
<div className="rb:flex rb:flex-col rb:h-full rb:p-4"> <div className="rb:flex rb:flex-col rb:h-full rb:p-4">
{/* 头部 */} {/* Header */}
<div className="rb:flex rb:flex-col rb:text-left rb:mb-6"> <div className="rb:flex rb:flex-col rb:text-left rb:mb-6">
<div className='rb:flex rb:items-center rb:justify-between'> <div className='rb:flex rb:items-center rb:justify-between'>
<div className='rb:flex rb:items-center rb:gap-2 rb:mb-4 rb:cursor-pointer' onClick={handleBack}> <div className='rb:flex rb:items-center rb:gap-2 rb:mb-4 rb:cursor-pointer' onClick={handleBack}>
@@ -366,9 +366,9 @@ const DocumentDetails: FC = () => {
</div> </div>
</div> </div>
{/* 内容区域 */} {/* Content area */}
<div className="rb:flex rb:h-full rb:gap-4 rb:flex-1 rb:overflow-hidden"> <div className="rb:flex rb:h-full rb:gap-4 rb:flex-1 rb:overflow-hidden">
{/* 左侧:文档信息 */} {/* Left: Document info */}
<div className='rb:w-80 rb:h-full rb:flex rb:flex-col rb:gap-4 rb:overflow-hidden'> <div className='rb:w-80 rb:h-full rb:flex rb:flex-col rb:gap-4 rb:overflow-hidden'>
<div className='rb:border rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4'> <div className='rb:border rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4'>
<InfoPanel <InfoPanel
@@ -381,7 +381,7 @@ const DocumentDetails: FC = () => {
</div> </div>
</div> </div>
{/* 右侧:分块列表 */} {/* Right: Chunk list */}
<div <div
id="chunkScrollableDiv" id="chunkScrollableDiv"
className="rb:flex-1 rb:bg-white rb:rounded-lg rb:border rb:border-gray-200 rb:p-6 rb:overflow-y-auto" className="rb:flex-1 rb:bg-white rb:rounded-lg rb:border rb:border-gray-200 rb:p-6 rb:overflow-y-auto"
@@ -404,7 +404,7 @@ const DocumentDetails: FC = () => {
</div> </div>
</div> </div>
{/* 插入内容弹窗 */} {/* Insert content modal */}
<InsertModal <InsertModal
ref={insertModalRef} ref={insertModalRef}
onInsert={handleInsertContent} onInsert={handleInsertContent}

View File

@@ -40,7 +40,7 @@ import KnowledgeGraphCard from '../components/KnowledgeGraphCard';
import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager'; import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager';
import './Private.css' import './Private.css'
const { confirm } = Modal const { confirm } = Modal
// 树节点数据类型 // Tree node data type
const Private: FC = () => { const Private: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -73,9 +73,9 @@ const Private: FC = () => {
const [isGraph, setIsGraph] = useState(false); const [isGraph, setIsGraph] = useState(false);
const { updateBreadcrumbs } = useBreadcrumbManager({ const { updateBreadcrumbs } = useBreadcrumbManager({
breadcrumbType: 'detail', breadcrumbType: 'detail',
// 不提供 onKnowledgeBaseMenuClick,让它使用默认的导航行为(返回列表页面) // Don't provide onKnowledgeBaseMenuClick, let it use default navigation behavior (return to list page)
onKnowledgeBaseFolderClick: useCallback((folderId: string, folderPath: Array<{ id: string; name: string }>) => { onKnowledgeBaseFolderClick: useCallback((folderId: string, folderPath: Array<{ id: string; name: string }>) => {
// 点击文件夹面包屑时,导航到对应文件夹 // Navigate to corresponding folder when clicking folder breadcrumb
setParentId(folderId); setParentId(folderId);
setFolderPath(folderPath); setFolderPath(folderPath);
setSelectedKeys([folderId]); setSelectedKeys([folderId]);
@@ -84,7 +84,7 @@ const Private: FC = () => {
parent_id: folderId parent_id: folderId
}); });
// 确保query对象发生变化触发表格刷新 // Ensure query object changes to trigger table refresh
setQuery({ setQuery({
orderby: 'created_at', orderby: 'created_at',
desc: true, desc: true,
@@ -92,10 +92,10 @@ const Private: FC = () => {
_timestamp: Date.now() _timestamp: Date.now()
}); });
// 确保API URL正确设置 // Ensure API URL is set correctly
setTableApi(`/documents/${knowledgeBaseId}/documents`); setTableApi(`/documents/${knowledgeBaseId}/documents`);
// 手动触发表格刷新,确保数据更新 // Manually trigger table refresh to ensure data update
setTimeout(() => { setTimeout(() => {
tableRef.current?.loadData(); tableRef.current?.loadData();
}, 100); }, 100);
@@ -108,7 +108,7 @@ const Private: FC = () => {
setLoading(true); setLoading(true);
try { try {
const res = await getKnowledgeBaseDetail(id); const res = await getKnowledgeBaseDetail(id);
// KnowledgeBase 转换为 KnowledgeBaseListItem // Convert KnowledgeBase to KnowledgeBaseListItem
const listItem = res as unknown as KnowledgeBaseListItem; const listItem = res as unknown as KnowledgeBaseListItem;
setKnowledgeBase(listItem); setKnowledgeBase(listItem);
} finally { } finally {
@@ -122,7 +122,7 @@ const Private: FC = () => {
setTableApi(url); setTableApi(url);
fetchKnowledgeBaseDetail(knowledgeBaseId); fetchKnowledgeBaseDetail(knowledgeBaseId);
// 立即设置基础面包屑,确保不会显示其他页面的面包屑 // Immediately set base breadcrumbs to ensure other page breadcrumbs are not displayed
updateBreadcrumbs({ updateBreadcrumbs({
knowledgeBaseFolderPath, knowledgeBaseFolderPath,
knowledgeBase: { knowledgeBase: {
@@ -135,7 +135,7 @@ const Private: FC = () => {
} }
}, [knowledgeBaseId]); }, [knowledgeBaseId]);
// 更新面包屑 // Update breadcrumbs
useEffect(() => { useEffect(() => {
if (knowledgeBase) { if (knowledgeBase) {
updateBreadcrumbs({ updateBreadcrumbs({
@@ -150,22 +150,22 @@ const Private: FC = () => {
} }
}, [knowledgeBase, knowledgeBaseFolderPath, folderPath, updateBreadcrumbs]); }, [knowledgeBase, knowledgeBaseFolderPath, folderPath, updateBreadcrumbs]);
// 监听 tableApi 变化,自动刷新表格数据 // Listen to tableApi changes and auto refresh table data
useEffect(() => { useEffect(() => {
if (tableApi) { if (tableApi) {
tableRef.current?.loadData(); tableRef.current?.loadData();
} }
}, [tableApi]); }, [tableApi]);
// 监听 query 变化,确保表格数据更新 // Listen to query changes and ensure table data update
useEffect(() => { useEffect(() => {
if (tableApi && query._timestamp) { if (tableApi && query._timestamp) {
// query 中有 _timestamp 时,说明是通过面包屑或其他方式触发的更新 // When query has _timestamp, it means the update is triggered by breadcrumb or other means
tableRef.current?.loadData(); tableRef.current?.loadData();
} }
}, [query._timestamp, tableApi]); }, [query._timestamp, tableApi]);
// 监听 location state 变化 // Listen to location state changes
useEffect(() => { useEffect(() => {
const state = location.state as { const state = location.state as {
refresh?: boolean; refresh?: boolean;
@@ -180,18 +180,18 @@ const Private: FC = () => {
if (state?.refresh) { if (state?.refresh) {
tableRef.current?.loadData(); tableRef.current?.loadData();
// 清除 state,避免重复刷新 // Clear state to avoid repeated refresh
navigate(location.pathname, { replace: true, state: {} }); navigate(location.pathname, { replace: true, state: {} });
} }
// 如果是从知识库列表页跳转过来的,设置知识库文件夹路径 // If navigated from knowledge base list page, set knowledge base folder path
if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) { if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) {
setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath); setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath);
} }
// 如果需要重置到根目录(回到初始状态) // If need to reset to root directory (return to initial state)
if (state?.resetToRoot) { if (state?.resetToRoot) {
// 重置所有状态到初始状态,和页面初始化保持一致 // Reset all states to initial state, consistent with page initialization
setParentId(knowledgeBaseId); setParentId(knowledgeBaseId);
setFolderPath([]); setFolderPath([]);
setSelectedKeys([]); setSelectedKeys([]);
@@ -202,31 +202,31 @@ const Private: FC = () => {
setQuery({ setQuery({
orderby: 'created_at', orderby: 'created_at',
desc: true, desc: true,
_timestamp: Date.now() // 添加时间戳确保query对象发生变化触发API调用 _timestamp: Date.now() // Add timestamp to ensure query object changes and trigger API call
}); });
// 重新设置API URL // Reset API URL
const rootUrl = `/documents/${knowledgeBaseId}/documents`; const rootUrl = `/documents/${knowledgeBaseId}/documents`;
setTableApi(rootUrl); setTableApi(rootUrl);
// 清除自动展开路径 // Clear auto expand path
setAutoExpandPath([]); setAutoExpandPath([]);
// 刷新文件夹树 - 使用延迟确保状态重置完成后再刷新 // Refresh folder tree - use delay to ensure state reset is complete before refresh
setTimeout(() => { setTimeout(() => {
setFolderTreeRefreshKey((prev) => prev + 1); setFolderTreeRefreshKey((prev) => prev + 1);
}, 100); }, 100);
// 手动触发表格刷新,确保数据更新 // Manually trigger table refresh to ensure data update
setTimeout(() => { setTimeout(() => {
tableRef.current?.loadData(); tableRef.current?.loadData();
}, 200); }, 200);
// 清除 state,避免重复处理 // Clear state to avoid repeated processing
navigate(location.pathname, { replace: true, state: {} }); navigate(location.pathname, { replace: true, state: {} });
} }
// 如果是从文档详情页返回,恢复文档文件夹路径 // If returning from document details page, restore document folder path
if (state?.navigateToDocumentFolder && state?.documentFolderPath) { if (state?.navigateToDocumentFolder && state?.documentFolderPath) {
setFolderPath(state.documentFolderPath); setFolderPath(state.documentFolderPath);
setParentId(state.navigateToDocumentFolder); setParentId(state.navigateToDocumentFolder);
@@ -242,25 +242,25 @@ const Private: FC = () => {
setTableApi(`/documents/${knowledgeBaseId}/documents`); setTableApi(`/documents/${knowledgeBaseId}/documents`);
setSelectedKeys([state.navigateToDocumentFolder]); setSelectedKeys([state.navigateToDocumentFolder]);
// 设置自动展开路径让FolderTree自动展开到对应位置 // Set auto expand path to let FolderTree auto expand to corresponding position
setAutoExpandPath(state.documentFolderPath); setAutoExpandPath(state.documentFolderPath);
// 手动触发表格刷新 // Manually trigger table refresh
setTimeout(() => { setTimeout(() => {
tableRef.current?.loadData(); tableRef.current?.loadData();
}, 100); }, 100);
// 清除自动展开路径避免重复触发延迟清除确保FolderTree处理完成 // Clear auto expand path to avoid repeated trigger (delayed clear to ensure FolderTree processing is complete)
setTimeout(() => { setTimeout(() => {
setAutoExpandPath([]); setAutoExpandPath([]);
}, 2000); }, 2000);
} }
}, [location.state, knowledgeBaseId, navigate, location.pathname]); }, [location.state, knowledgeBaseId, navigate, location.pathname]);
// 处理树节点选择 // Handle tree node selection
const onSelect = (keys: React.Key[]) => { const onSelect = (keys: React.Key[]) => {
if (!keys.length) { if (!keys.length) {
// 如果没有选中任何节点,回到根目录(初始状态) // If no node is selected, return to root directory (initial state)
setParentId(knowledgeBaseId); setParentId(knowledgeBaseId);
setFolder({ setFolder({
kb_id: knowledgeBaseId ?? '', kb_id: knowledgeBaseId ?? '',
@@ -269,7 +269,7 @@ const Private: FC = () => {
setQuery({ setQuery({
orderby: 'created_at', orderby: 'created_at',
desc: true, desc: true,
_timestamp: Date.now() // 添加时间戳确保query对象发生变化 _timestamp: Date.now() // Add timestamp to ensure query object changes
}); });
setSelectedKeys([]); setSelectedKeys([]);
return; return;
@@ -284,7 +284,7 @@ const Private: FC = () => {
setQuery({ setQuery({
...query, ...query,
parent_id: String(keys[0]), parent_id: String(keys[0]),
_timestamp: Date.now() // 添加时间戳确保query对象发生变化 _timestamp: Date.now() // Add timestamp to ensure query object changes
}) })
let url = `/documents/${knowledgeBaseId}/documents`; let url = `/documents/${knowledgeBaseId}/documents`;
@@ -294,14 +294,14 @@ const Private: FC = () => {
setSelectedKeys(keys) setSelectedKeys(keys)
}; };
// 处理文件夹路径变化 // Handle folder path change
const handleFolderPathChange = (path: Array<{ id: string; name: string }>) => { const handleFolderPathChange = (path: Array<{ id: string; name: string }>) => {
setFolderPath(path); setFolderPath(path);
}; };
// 处理树节点展开 // Handle tree node expand
const onExpand = (_expandedKeys: React.Key[], _info: any) => { const onExpand = (_expandedKeys: React.Key[], _info: any) => {
// 展开节点时不需要特殊处理 // No special handling needed when expanding nodes
}; };
// create / import list // create / import list
const createItems: MenuProps['items'] = [ const createItems: MenuProps['items'] = [
@@ -344,13 +344,13 @@ const Private: FC = () => {
// createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '') // createImageDataset?.current?.handleOpen(knowledgeBaseId || '', parentId || '')
// }, // },
// }, // },
// 暂时未实现 // Not implemented yet
// { // {
// key: '4', // key: '4',
// icon: <img src={blankIcon} alt="blank" style={{ width: 16, height: 16 }} />, // icon: <img src={blankIcon} alt="blank" style={{ width: 16, height: 16 }} />,
// label: t('knowledgeBase.blankDataset'), // label: t('knowledgeBase.blankDataset'),
// onClick: () => { // onClick: () => {
// handleCreate('folder'); // 传入 type: 'folder' // handleCreate('folder'); // Pass type: 'folder'
// }, // },
// }, // },
// { // {
@@ -362,7 +362,7 @@ const Private: FC = () => {
// icon: <img src={templateIcon} alt="import" style={{ width: 16, height: 16 }} />, // icon: <img src={templateIcon} alt="import" style={{ width: 16, height: 16 }} />,
// label: t('knowledgeBase.importTemplate'), // label: t('knowledgeBase.importTemplate'),
// onClick: () => { // onClick: () => {
// handleCreate('folder'); // 传入 type: 'folder' // handleCreate('folder'); // Pass type: 'folder'
// }, // },
// }, // },
// { // {
@@ -370,17 +370,17 @@ const Private: FC = () => {
// icon: <img src={backupIcon} alt="import" style={{ width: 16, height: 16 }} />, // icon: <img src={backupIcon} alt="import" style={{ width: 16, height: 16 }} />,
// label: t('knowledgeBase.importBackup'), // label: t('knowledgeBase.importBackup'),
// onClick: () => { // onClick: () => {
// handleCreate('folder'); // 传入 type: 'folder' // handleCreate('folder'); // Pass type: 'folder'
// }, // },
// }, // },
]; ];
// 处理开关 // Handle switch
const onChange = (checked: boolean) => { const onChange = (checked: boolean) => {
if (!knowledgeBase) return; if (!knowledgeBase) return;
// 构造完整的更新数据,保留现有配置 // Construct complete update data, keeping existing configuration
const updateData: KnowledgeBaseFormData = { const updateData: KnowledgeBaseFormData = {
name: knowledgeBase.name, name: knowledgeBase.name,
description: knowledgeBase.description, description: knowledgeBase.description,
@@ -411,30 +411,30 @@ const Private: FC = () => {
updateKnowledgeBase(knowledgeBaseId || '', updateData); updateKnowledgeBase(knowledgeBaseId || '', updateData);
console.log(`switch to ${checked}`); console.log(`switch to ${checked}`);
}; };
// 处理搜索 // Handle search
const handleSearch = (value?: string) => { const handleSearch = (value?: string) => {
setQuery({ ...query, keywords: value }) setQuery({ ...query, keywords: value })
} }
// 处理分享 // Handle share
const handleShare = () => { const handleShare = () => {
shareModalRef?.current?.handleOpen(knowledgeBaseId,knowledgeBase); shareModalRef?.current?.handleOpen(knowledgeBaseId,knowledgeBase);
} }
// 处理分享回调,接收选中的数据 // Handle share callback, receive selected data
const handleShareCallback = (selectedData: { checkedItems: any[], selectedItem: any | null }) => { const handleShareCallback = (selectedData: { checkedItems: any[], selectedItem: any | null }) => {
console.log('选中的数据:', selectedData); console.log('Selected data:', selectedData);
// checkedItems: 所有 checked true 的数据 // checkedItems: All data with checked = true
// selectedItem: 当前选中的项(curIndex 对应的数据) // selectedItem: Currently selected item (corresponding to curIndex)
// 在这里处理分享逻辑 // Handle share logic here
} }
const handleCreateDatasetCallback = (payload: { value: number; title: string; description: string }) => { const handleCreateDatasetCallback = (payload: { value: number; title: string; description: string }) => {
console.log('创建数据集:', payload); console.log('Create dataset:', payload);
} }
// 处理设置 // Handle settings
const handleSetting = () => { const handleSetting = () => {
modalRef?.current?.handleOpen(knowledgeBase, ''); modalRef?.current?.handleOpen(knowledgeBase, '');
} }
// 处理召回测试 // Handle recall test
const handleRecallTest = () => { const handleRecallTest = () => {
recallTestDrawerRef?.current?.handleOpen(knowledgeBaseId); recallTestDrawerRef?.current?.handleOpen(knowledgeBaseId);
} }
@@ -443,7 +443,7 @@ const Private: FC = () => {
const handelCreateOrImport = () => { const handelCreateOrImport = () => {
} }
// 生成下拉菜单项(根据当前 row // Generate dropdown menu items (based on current row)
const getOptMenuItems = (row: KnowledgeBaseListItem): MenuProps['items'] => [ const getOptMenuItems = (row: KnowledgeBaseListItem): MenuProps['items'] => [
{ {
key: '1', key: '1',
@@ -495,19 +495,19 @@ const Private: FC = () => {
deleteDocument(item.id) deleteDocument(item.id)
.then(() => { .then(() => {
messageApi.success(t('common.deleteSuccess')); messageApi.success(t('common.deleteSuccess'));
// 刷新表格数据 // Refresh table data
tableRef.current?.loadData(); tableRef.current?.loadData();
}) })
.catch((err: any) => { .catch((err: any) => {
console.log('删除失败', err); console.log('Delete failed', err);
}); });
}, },
onCancel: () => { onCancel: () => {
console.log('取消删除'); console.log('Cancel delete');
}, },
}); });
} }
// 表格列配置 // Table column configuration
const columns: ColumnsType = [ const columns: ColumnsType = [
{ {
title: t('knowledgeBase.name'), title: t('knowledgeBase.name'),
@@ -524,7 +524,7 @@ const Private: FC = () => {
state: { state: {
documentId: document.id, documentId: document.id,
parentId: parentId ?? knowledgeBaseId, parentId: parentId ?? knowledgeBaseId,
// 传递面包屑信息 // Pass breadcrumb information
breadcrumbPath: { breadcrumbPath: {
knowledgeBaseFolderPath, knowledgeBaseFolderPath,
knowledgeBase: { knowledgeBase: {
@@ -572,7 +572,7 @@ const Private: FC = () => {
render: (value: string) => { render: (value: string) => {
if (!value) return '-'; if (!value) return '-';
// 解析日志格式,将 \n 转换为换行 // Parse log format, convert \n to newline
const formattedText = value.replace(/\\n/g, '\n'); const formattedText = value.replace(/\\n/g, '\n');
return ( return (
@@ -634,25 +634,25 @@ const Private: FC = () => {
), ),
}, },
]; ];
// 刷新列表数据 // Refresh list data
if (loading) { if (loading) {
return <div>...</div>; return <div>Loading...</div>;
} }
if (!knowledgeBase) { if (!knowledgeBase) {
return <div></div>; return <div></div>;
} }
const refreshDirectoryTree = async () => { const refreshDirectoryTree = async () => {
// 先刷新知识库详情,确保数据是最新的 // First refresh knowledge base details to ensure data is up-to-date
if (knowledgeBase?.id) { if (knowledgeBase?.id) {
await fetchKnowledgeBaseDetail(knowledgeBase.id); await fetchKnowledgeBaseDetail(knowledgeBase.id);
} }
// 添加短暂延迟,确保后端数据已经完全更新 // Add short delay to ensure backend data is fully updated
await new Promise(resolve => setTimeout(resolve, 300)); await new Promise(resolve => setTimeout(resolve, 300));
// 然后刷新文件夹树 // Then refresh folder tree
setFolderTreeRefreshKey((prev) => prev + 1); setFolderTreeRefreshKey((prev) => prev + 1);
// 确保 folder 状态正确设置 // Ensure folder state is set correctly
if (!folder) { if (!folder) {
setFolder({ setFolder({
kb_id: knowledgeBaseId ?? '', kb_id: knowledgeBaseId ?? '',
@@ -663,10 +663,10 @@ const Private: FC = () => {
} }
const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => { const handleRootTreeLoad = (nodes: TreeNodeData[] | null) => {
if (!nodes || nodes.length === 0) { if (!nodes || nodes.length === 0) {
// 如果没有节点设置folder为null这会隐藏FolderTree // If no nodes, set folder to null (this will hide FolderTree)
setFolder(null); setFolder(null);
} else { } else {
// 如果有节点且 folder null,重新设置 folder // If there are nodes and folder is null, reset folder
if (!folder) { if (!folder) {
setFolder({ setFolder({
kb_id: knowledgeBaseId ?? '', kb_id: knowledgeBaseId ?? '',
@@ -687,7 +687,7 @@ const Private: FC = () => {
} }
const handleRefreshTable = () => { const handleRefreshTable = () => {
// 刷新表格数据 // Refresh table data
fetchKnowledgeBaseDetail(knowledgeBase.id) fetchKnowledgeBaseDetail(knowledgeBase.id)
tableRef.current?.loadData(); tableRef.current?.loadData();
} }

View File

@@ -38,7 +38,7 @@ const Share: FC = () => {
if (knowledgeBaseId) { if (knowledgeBaseId) {
fetchKnowledgeBaseDetail(knowledgeBaseId); fetchKnowledgeBaseDetail(knowledgeBaseId);
// 打开召回测试组件 // Open recall test component
setTimeout(() => { setTimeout(() => {
console.log('Share.tsx - calling handleOpen with:', knowledgeBaseId); console.log('Share.tsx - calling handleOpen with:', knowledgeBaseId);
recallTestRef.current?.handleOpen(knowledgeBaseId); recallTestRef.current?.handleOpen(knowledgeBaseId);
@@ -48,7 +48,7 @@ const Share: FC = () => {
} }
}, [knowledgeBaseId]); }, [knowledgeBaseId]);
// 更新面包屑 // Update breadcrumbs
useEffect(() => { useEffect(() => {
if (knowledgeBase) { if (knowledgeBase) {
updateBreadcrumbs({ updateBreadcrumbs({
@@ -63,14 +63,14 @@ const Share: FC = () => {
} }
}, [knowledgeBase, knowledgeBaseFolderPath, updateBreadcrumbs]); }, [knowledgeBase, knowledgeBaseFolderPath, updateBreadcrumbs]);
// 监听 location state 变化 // Listen to location state changes
useEffect(() => { useEffect(() => {
const state = location.state as { const state = location.state as {
fromKnowledgeBaseList?: boolean; fromKnowledgeBaseList?: boolean;
knowledgeBaseFolderPath?: BreadcrumbItem[]; knowledgeBaseFolderPath?: BreadcrumbItem[];
} | null; } | null;
// 如果是从知识库列表页跳转过来的,设置知识库文件夹路径 // If navigated from knowledge base list page, set knowledge base folder path
if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) { if (state?.fromKnowledgeBaseList && state?.knowledgeBaseFolderPath) {
setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath); setKnowledgeBaseFolderPath(state.knowledgeBaseFolderPath);
} }

View File

@@ -41,7 +41,7 @@ const CreateContentModal = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
const values = await form.validateFields(); const values = await form.validateFields();
setLoading(true); setLoading(true);
// TODO: 这里需要调用相应的API来保存内容 // TODO: Call appropriate API to save content
const params = { const params = {
// ...values, // ...values,
kb_id: kbId, kb_id: kbId,
@@ -55,7 +55,7 @@ const CreateContentModal = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
} }
handleClose(); handleClose();
} catch (err) { } catch (err) {
console.error('创建内容失败:', err); console.error('Failed to create content:', err);
} finally { } finally {
setLoading(false); setLoading(false);
} }

View File

@@ -3,18 +3,18 @@ import { Button } from 'antd';
import CreateContentModal from './CreateContentModal'; import CreateContentModal from './CreateContentModal';
import type { CreateContentModalRef } from '../types'; import type { CreateContentModalRef } from '../types';
// 使用示例组件 // Example usage component
const CreateContentModalExample = () => { const CreateContentModalExample = () => {
const createContentModalRef = useRef<CreateContentModalRef>(null); const createContentModalRef = useRef<CreateContentModalRef>(null);
const handleOpenModal = () => { const handleOpenModal = () => {
// 打开弹窗传入知识库ID和父级ID // Open modal, pass knowledge base ID and parent ID
createContentModalRef.current?.handleOpen('kb_123', 'parent_456'); createContentModalRef.current?.handleOpen('kb_123', 'parent_456');
}; };
const handleRefreshTable = () => { const handleRefreshTable = () => {
console.log('刷新表格数据'); console.log('Refresh table data');
// 这里可以添加刷新表格的逻辑 // Add table refresh logic here
}; };
return ( return (

View File

@@ -1,5 +1,5 @@
/* /**
* @Description: * @Description: Create Dataset Modal
* @Version: 0.0.1 * @Version: 0.0.1
* @Author: yujiangping * @Author: yujiangping
* @Date: 2025-11-10 18:52:55 * @Date: 2025-11-10 18:52:55

View File

@@ -13,7 +13,7 @@ const CreateFolderModal = forwardRef<CreateFolderModalRef,CreateFolderModalRefPr
const [form] = Form.useForm<FolderFormData>(); const [form] = Form.useForm<FolderFormData>();
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
// 封装取消方法,添加关闭弹窗逻辑 // Close modal and reset state
const handleClose = () => { const handleClose = () => {
setFolder({} as FolderFormData); setFolder({} as FolderFormData);
form.resetFields(); form.resetFields();
@@ -22,17 +22,16 @@ const CreateFolderModal = forwardRef<CreateFolderModalRef,CreateFolderModalRefPr
}; };
const handleOpen = (folder?: FolderFormData | null) => { const handleOpen = (folder?: FolderFormData | null) => {
debugger
if (folder) { if (folder) {
setFolder(folder); setFolder(folder);
// 设置表单值 // Set form values
form.setFieldsValue({ form.setFieldsValue({
folder_name: folder.folder_name, folder_name: folder.folder_name,
parent_id: folder.parent_id ?? '', parent_id: folder.parent_id ?? '',
kb_id: folder.kb_id ?? '', kb_id: folder.kb_id ?? '',
}); });
} else { } else {
// 新建时,重置表单并设置默认值 // Reset form and set default values for new folder
form.resetFields(); form.resetFields();
form.setFieldsValue({ form.setFieldsValue({
parent_id: '', parent_id: '',
@@ -41,7 +40,7 @@ const CreateFolderModal = forwardRef<CreateFolderModalRef,CreateFolderModalRefPr
} }
setVisible(true); setVisible(true);
}; };
// 封装保存方法,添加提交逻辑 // Save form data and submit
const handleSave = () => { const handleSave = () => {
form form
.validateFields({ validateOnly: true }) .validateFields({ validateOnly: true })
@@ -74,13 +73,13 @@ const CreateFolderModal = forwardRef<CreateFolderModalRef,CreateFolderModalRefPr
}); });
} }
// 暴露给父组件的方法 // Expose methods to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
handleClose handleClose
})); }));
// 根据 type 获取标题 // Get modal title based on folder state
const getTitle = () => { const getTitle = () => {
if (folder.id) { if (folder.id) {
return t('common.edit') + ' ' + (folder.folder_name || ''); return t('common.edit') + ' ' + (folder.folder_name || '');

View File

@@ -28,12 +28,12 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
const [parentId, setParentId] = useState<string>(''); const [parentId, setParentId] = useState<string>('');
const [hasFiles, setHasFiles] = useState(false); const [hasFiles, setHasFiles] = useState(false);
const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null); const uploadRef = useRef<{ fileList: UploadFile[]; clearFiles: () => void }>(null);
// 存储每个文件的 AbortController,用于取消上传 // Store AbortController for each file to cancel upload
const abortControllersRef = useRef<Map<string, AbortController>>(new Map()); const abortControllersRef = useRef<Map<string, AbortController>>(new Map());
// const fileIds = []; // const fileIds = [];
const handleClose = () => { const handleClose = () => {
// 取消所有正在进行的上传 // Cancel all ongoing uploads
abortControllersRef.current.forEach((controller) => { abortControllersRef.current.forEach((controller) => {
controller.abort(); controller.abort();
}); });
@@ -69,7 +69,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
} }
const ids = fileList.map((file) => file.response?.id); const ids = fileList.map((file) => file.response?.id);
handleChunking(kbId, parentId, ids) handleChunking(kbId, parentId, ids)
// // 上传所有图片 // // Upload all images
// const uploadPromises = fileList.map(async (file) => { // const uploadPromises = fileList.map(async (file) => {
// if (file.originFileObj) { // if (file.originFileObj) {
// const formData = new FormData(); // const formData = new FormData();
@@ -91,7 +91,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
handleClose(); handleClose();
} catch (err) { } catch (err) {
console.error('创建图片数据集失败:', err); console.error('Failed to create image dataset:', err);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -112,7 +112,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
})); }));
// 检查媒体文件时长的辅助函数 // Helper function to check media file duration
const checkMediaDuration = (file: File): Promise<number> => { const checkMediaDuration = (file: File): Promise<number> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const url = URL.createObjectURL(file); const url = URL.createObjectURL(file);
@@ -131,7 +131,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
media.src = url; media.src = url;
}); });
}; };
// 删除已上传的文件 // Delete uploaded file
const handleDeleteFile = async (fileId: string) => { const handleDeleteFile = async (fileId: string) => {
try { try {
await deleteDocument(fileId); await deleteDocument(fileId);
@@ -141,24 +141,24 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
} }
}; };
// 上传文件 // Upload file
const handleUpload = async (options: UploadRequestOption) => { const handleUpload = async (options: UploadRequestOption) => {
const { file, onSuccess, onError, onProgress, filename = 'file' } = options; const { file, onSuccess, onError, onProgress, filename = 'file' } = options;
// 创建 AbortController 用于取消上传 // Create AbortController to cancel upload
const abortController = new AbortController(); const abortController = new AbortController();
const fileUid = (file as any).uid; const fileUid = (file as any).uid;
abortControllersRef.current.set(fileUid, abortController); abortControllersRef.current.set(fileUid, abortController);
// 获取文件扩展名 // Get file extension
const fileExtension = (file as File).name.split('.').pop()?.toLowerCase(); const fileExtension = (file as File).name.split('.').pop()?.toLowerCase();
const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav']; const mediaExtensions = ['mp3', 'mp4', 'mov', 'wav'];
// 如果是媒体文件,进行大小和时长检查 // If it's a media file, check size and duration
if (fileExtension && mediaExtensions.includes(fileExtension)) { if (fileExtension && mediaExtensions.includes(fileExtension)) {
const fileSizeInMB = (file as File).size / (50 * 1024); const fileSizeInMB = (file as File).size / (50 * 1024);
// 检查文件大小50MB限制 // Check file size (50MB limit)
if (fileSizeInMB > 50) { if (fileSizeInMB > 50) {
messageApi.error(`${t('knowledgeBase.sizeLimitError')}${fileSizeInMB.toFixed(2)}MB`); messageApi.error(`${t('knowledgeBase.sizeLimitError')}${fileSizeInMB.toFixed(2)}MB`);
onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`)); onError?.(new Error(`${t('knowledgeBase.fileSizeExceeds')}`));
@@ -167,7 +167,7 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
} }
try { try {
// 检查媒体时长150秒限制 // Check media duration (150 seconds limit)
const duration = await checkMediaDuration(file as File); const duration = await checkMediaDuration(file as File);
if (duration > 150) { if (duration > 150) {
messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}${Math.round(duration)}`); messageApi.error(`${t('knowledgeBase.fileDurationLimitError')}${Math.round(duration)}`);
@@ -204,21 +204,21 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
}, },
}); });
// 上传成功,移除 AbortController // Upload successful, remove AbortController
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
onSuccess?.(res, new XMLHttpRequest()); onSuccess?.(res, new XMLHttpRequest());
if (res?.id) { if (res?.id) {
// 上传成功 // Upload successful
// fileIds.push(res.id) // fileIds.push(res.id)
} }
} catch (error: any) { } catch (error: any) {
// 移除 AbortController // Remove AbortController
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
// 如果是用户主动取消,不显示错误信息 // If user actively cancelled, don't show error message
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') { if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
console.log('上传已取消:', (file as File).name); console.log('Upload cancelled:', (file as File).name);
return; return;
} }
@@ -259,11 +259,11 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp3', 'mp4', 'mov', 'wav']} fileType={['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'mp3', 'mp4', 'mov', 'wav']}
customRequest={handleUpload} customRequest={handleUpload}
onChange={(fileList) => { onChange={(fileList) => {
// 实时更新文件状态 // Update file status in real-time
setHasFiles(fileList.length > 0); setHasFiles(fileList.length > 0);
}} }}
onRemove={async (file) => { onRemove={async (file) => {
// 如果文件正在上传,取消上传 // If file is uploading, cancel upload
const fileUid = file.uid; const fileUid = file.uid;
const abortController = abortControllersRef.current.get(fileUid); const abortController = abortControllersRef.current.get(fileUid);
if (abortController) { if (abortController) {
@@ -271,12 +271,12 @@ const CreateImageDataset = forwardRef<CreateSetModalRef, CreateSetMoealRefProps>
abortControllersRef.current.delete(fileUid); abortControllersRef.current.delete(fileUid);
} }
// 如果文件已经上传成功,删除服务器上的文件 // If file is already uploaded successfully, delete file on server
if (file.response?.id) { if (file.response?.id) {
await handleDeleteFile(file.response.id); await handleDeleteFile(file.response.id);
} }
return true; // 允许移除文件 return true; // Allow file removal
}} }}
/> />
</Form.Item> </Form.Item>

View File

@@ -15,7 +15,7 @@ import RbModal from '@/components/RbModal'
const { TextArea } = Input; const { TextArea } = Input;
const { confirm } = Modal const { confirm } = Modal
// 全局模型数据常量 // Global model data constant
let models: any = null; let models: any = null;
const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
@@ -33,9 +33,9 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const [activeTab, setActiveTab] = useState('basic'); const [activeTab, setActiveTab] = useState('basic');
const [generatingEntityTypes, setGeneratingEntityTypes] = useState(false); const [generatingEntityTypes, setGeneratingEntityTypes] = useState(false);
const [isRebuildMode, setIsRebuildMode] = useState(false); const [isRebuildMode, setIsRebuildMode] = useState(false);
const [originalType, setOriginalType] = useState<string>(''); // 保存原始的 type 参数 const [originalType, setOriginalType] = useState<string>(''); // Save original type parameter
// 监听 parser_config.graphrag 相关字段的变化 // Watch for changes to parser_config.graphrag related fields
const parserConfig = Form.useWatch('parser_config', form); const parserConfig = Form.useWatch('parser_config', form);
const graphragConfig = parserConfig?.graphrag; const graphragConfig = parserConfig?.graphrag;
const enableKnowledgeGraph = graphragConfig?.use_graphrag || false; const enableKnowledgeGraph = graphragConfig?.use_graphrag || false;
@@ -43,30 +43,30 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const entityNormalization = graphragConfig?.resolution || false; const entityNormalization = graphragConfig?.resolution || false;
const communityReportGeneration = graphragConfig?.community || false; const communityReportGeneration = graphragConfig?.community || false;
// 封装取消方法,添加关闭弹窗逻辑 // Encapsulate cancel method, add close modal logic
const handleClose = () => { const handleClose = () => {
setDatasets(null); setDatasets(null);
form.resetFields(); form.resetFields();
setLoading(false); setLoading(false);
setActiveTab('basic'); setActiveTab('basic');
setIsRebuildMode(false); // 重置重建模式标识 setIsRebuildMode(false); // Reset rebuild mode flag
setOriginalType(''); // 重置原始 type setOriginalType(''); // Reset original type
setVisible(false); setVisible(false);
}; };
// 生成实体类型的函数 // Generate entity types function
const generateEntityTypes = async () => { const generateEntityTypes = async () => {
const sceneName = form.getFieldValue(['parser_config', 'graphrag', 'scene_name']); const sceneName = form.getFieldValue(['parser_config', 'graphrag', 'scene_name']);
if (!sceneName) { if (!sceneName) {
// 可以添加提示用户输入场景名称 // Can add prompt for user to enter scenario name
messageApi.error(t('knowledgeBase.enterScenarioName')); messageApi.error(t('knowledgeBase.enterScenarioName'));
return; return;
} }
// 检查是否选择了 LLM 模型 // Check if LLM model is selected
const llmId = form.getFieldValue('llm_id'); const llmId = form.getFieldValue('llm_id');
if (!llmId) { if (!llmId) {
// 跳转到基础配置页 // Navigate to basic configuration page
setActiveTab('basic'); setActiveTab('basic');
messageApi.error(t('knowledgeBase.pleaseSelectLLMModel')); messageApi.error(t('knowledgeBase.pleaseSelectLLMModel'));
return; return;
@@ -74,7 +74,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
setGeneratingEntityTypes(true); setGeneratingEntityTypes(true);
try { try {
// 这里应该调用实际的API接口 // Call the actual API interface here
// const user = JSON.parse(localStorage.getItem('user') as any); // const user = JSON.parse(localStorage.getItem('user') as any);
//datasets?.id || datasets?.parent_id || user?.current_workspace_id, //datasets?.id || datasets?.parent_id || user?.current_workspace_id,
const params = { const params = {
@@ -82,17 +82,17 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
llm_id: llmId llm_id: llmId
}; };
const response = await getKnowledgeGraphEntityTypes(params); const response = await getKnowledgeGraphEntityTypes(params);
// 模拟API调用 // Simulate API call
// await new Promise(resolve => setTimeout(resolve, 1000)); // await new Promise(resolve => setTimeout(resolve, 1000));
// 处理API响应数据 // Process API response data
console.log('API Response:', response); // 调试日志 console.log('API Response:', response); // Debug log
// 检查响应结构 - API直接返回字符串 // Check response structure - API returns string directly
if (response && typeof response === 'string' && response.trim()) { if (response && typeof response === 'string' && response.trim()) {
// 将逗号分隔的字符串转换为换行分隔的格式以便在TextArea中显示 // Convert comma-separated string to newline-separated format for TextArea display
const entityTypesString = response.replace(/,\s*/g, '\n'); const entityTypesString = response.replace(/,\s*/g, '\n');
console.log('Converted entity types:', entityTypesString); // 调试日志 console.log('Converted entity types:', entityTypesString); // Debug log
const currentGraphrag = form.getFieldValue(['parser_config', 'graphrag']) || {}; const currentGraphrag = form.getFieldValue(['parser_config', 'graphrag']) || {};
const updatedGraphrag = { const updatedGraphrag = {
@@ -100,22 +100,22 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
entity_types: entityTypesString entity_types: entityTypesString
}; };
console.log('Updating form with:', updatedGraphrag); // 调试日志 console.log('Updating form with:', updatedGraphrag); // Debug log
// 使用更直接的方式更新表单字段 // Use more direct way to update form field
form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString); form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString);
// 强制触发表单重新渲染 // Force trigger form re-render
form.validateFields([['parser_config', 'graphrag', 'entity_types']]); form.validateFields([['parser_config', 'graphrag', 'entity_types']]);
// 额外的强制更新机制 // Additional forced update mechanism
setTimeout(() => { setTimeout(() => {
form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString); form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString);
}, 100); }, 100);
messageApi.success(t('knowledgeBase.generateEntityTypesSuccess')); messageApi.success(t('knowledgeBase.generateEntityTypesSuccess'));
} else { } else {
messageApi.error(t('knowledgeBase.generateEntityTypesFailed') + '' + t('knowledgeBase.unknownError')); messageApi.error(t('knowledgeBase.generateEntityTypesFailed') + ': ' + t('knowledgeBase.unknownError'));
} }
} catch (error) { } catch (error) {
console.error(t('knowledgeBase.generateEntityTypesFailed') + ':', error); console.error(t('knowledgeBase.generateEntityTypesFailed') + ':', error);
@@ -143,7 +143,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
}; };
const fetchModelLists = async (types: string[]) => { const fetchModelLists = async (types: string[]) => {
// 如果还没有获取过全部模型数据,则获取一次 // If model data hasn't been fetched yet, fetch it once
if (!models) { if (!models) {
try { try {
models = await getModelList({ page: 1, pagesize: 100 }); models = await getModelList({ page: 1, pagesize: 100 });
@@ -153,7 +153,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
} }
} }
// 从全部模型数据中过滤出需要的类型 // Filter out the required types from all model data
const typesToFetch = types.includes('llm') ? [...types, 'chat'] : types; const typesToFetch = types.includes('llm') ? [...types, 'chat'] : types;
const next: Record<string, { label: string; value: string }[]> = {}; const next: Record<string, { label: string; value: string }[]> = {};
@@ -165,7 +165,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
setModelOptionsByType(next); setModelOptionsByType(next);
// 如果不是编辑模式,为每个类型的下拉框设置默认值为第一条数据 // If not in edit mode, set default value to first item for each type dropdown
if (!datasets?.id) { if (!datasets?.id) {
const defaultValues: Record<string, string> = {}; const defaultValues: Record<string, string> = {};
types.forEach((tp) => { types.forEach((tp) => {
@@ -174,7 +174,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
? [...(next['llm'] || []), ...(next['chat'] || [])] ? [...(next['llm'] || []), ...(next['chat'] || [])]
: next[tp] || []; : next[tp] || [];
// 如果有选项且当前字段没有值,设置第一个选项为默认值 // If there are options and current field has no value, set first option as default
if (options.length > 0 && !form.getFieldValue(fieldKey)) { if (options.length > 0 && !form.getFieldValue(fieldKey)) {
defaultValues[fieldKey] = options[0].value; defaultValues[fieldKey] = options[0].value;
} }
@@ -204,7 +204,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
status: record.status, status: record.status,
}; };
// 处理 parser_config 配置数据,如果没有则设置默认值 // Process parser_config data, set default values if not present
baseValues.parser_config = record.parser_config || { baseValues.parser_config = record.parser_config || {
graphrag: { graphrag: {
use_graphrag: false, use_graphrag: false,
@@ -216,13 +216,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
} }
}; };
// 如果存在 entity_types,转换为换行分隔格式用于 TextArea 显示 // If entity_types exists, convert to newline-separated format for TextArea display
if (baseValues.parser_config.graphrag.entity_types) { if (baseValues.parser_config.graphrag.entity_types) {
if (Array.isArray(baseValues.parser_config.graphrag.entity_types)) { if (Array.isArray(baseValues.parser_config.graphrag.entity_types)) {
// 如果是数组格式,转换为换行分隔字符串 // If array format, convert to newline-separated string
(baseValues.parser_config.graphrag as any).entity_types = baseValues.parser_config.graphrag.entity_types.join('\n'); (baseValues.parser_config.graphrag as any).entity_types = baseValues.parser_config.graphrag.entity_types.join('\n');
} else if (typeof baseValues.parser_config.graphrag.entity_types === 'string') { } else if (typeof baseValues.parser_config.graphrag.entity_types === 'string') {
// 如果是逗号分隔字符串格式,转换为换行分隔字符串(兼容旧数据) // If comma-separated string format, convert to newline-separated string (compatible with old data)
(baseValues.parser_config.graphrag as any).entity_types = (baseValues.parser_config.graphrag.entity_types as string).replace(/,\s*/g, '\n'); (baseValues.parser_config.graphrag as any).entity_types = (baseValues.parser_config.graphrag.entity_types as string).replace(/,\s*/g, '\n');
} }
} }
@@ -249,13 +249,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const handleOpen = (record?: KnowledgeBaseListItem | null, type?: string) => { const handleOpen = (record?: KnowledgeBaseListItem | null, type?: string) => {
setDatasets(record || null); setDatasets(record || null);
// 如果是重建模式,使用记录的实际类型,否则使用传入的类型 // If rebuild mode, use record's actual type, otherwise use passed type
const actualType = type === 'rebuild' ? (record?.type || 'General') : (type || currentType); const actualType = type === 'rebuild' ? (record?.type || 'General') : (type || currentType);
setCurrentType(actualType as any); setCurrentType(actualType as any);
setIsRebuildMode(type === 'rebuild'); // 设置重建模式标识 setIsRebuildMode(type === 'rebuild'); // Set rebuild mode flag
setOriginalType(type || ''); // 保存原始的 type 参数 setOriginalType(type || ''); // Save original type parameter
// 如果是重建模式,默认切换到知识图谱标签页 // If rebuild mode, default to knowledge graph tab
if (type === 'rebuild') { if (type === 'rebuild') {
setActiveTab('knowledgeGraph'); setActiveTab('knowledgeGraph');
} else { } else {
@@ -285,13 +285,13 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
setDynamicModelFields(datasets, modelTypeList); setDynamicModelFields(datasets, modelTypeList);
}, [visible, datasets, currentType, modelTypeList]); }, [visible, datasets, currentType, modelTypeList]);
// 封装保存方法,添加提交逻辑 // Encapsulate save method, add submit logic
const handleSave = () => { const handleSave = () => {
// 获取当前表单中的知识图谱开启状态 // Get current knowledge graph enabled status from form
const currentFormValues = form.getFieldsValue(); const currentFormValues = form.getFieldsValue();
const isGraphragEnabled = currentFormValues?.parser_config?.graphrag?.use_graphrag || false; const isGraphragEnabled = currentFormValues?.parser_config?.graphrag?.use_graphrag || false;
// 如果原始 type 'rebuild' 并且知识图谱开启为true显示确认弹框 // If original type is 'rebuild' and knowledge graph is enabled, show confirmation dialog
if (originalType === 'rebuild' && isGraphragEnabled) { if (originalType === 'rebuild' && isGraphragEnabled) {
confirm({ confirm({
title: t('knowledgeBase.rebuildConfirmTitle'), title: t('knowledgeBase.rebuildConfirmTitle'),
@@ -302,11 +302,11 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
await rebuildKnowledgeGraph(datasets?.id || '') await rebuildKnowledgeGraph(datasets?.id || '')
}, },
onCancel: () => { onCancel: () => {
// 用户取消,不执行任何操作 // User cancelled, no action taken
}, },
}); });
} else { } else {
// 非重建模式或知识图谱未开启,直接保存 // Non-rebuild mode or knowledge graph not enabled, save directly
performSave(); performSave();
} }
}; };
@@ -318,7 +318,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
messageApi.error(t('knowledgeBase.deleteGraphFailed')) messageApi.error(t('knowledgeBase.deleteGraphFailed'))
} }
}; };
// 实际的保存逻辑 // Actual save logic
const performSave = () => { const performSave = () => {
form form
.validateFields() .validateFields()
@@ -326,7 +326,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
setLoading(true) setLoading(true)
const formValues = form.getFieldsValue(); const formValues = form.getFieldsValue();
// 处理 entity_types 格式转换:从换行分隔字符串转换为字符串数组 // Process entity_types format conversion: from newline-separated string to string array
if (formValues.parser_config && formValues.parser_config.graphrag && formValues.parser_config.graphrag.entity_types) { if (formValues.parser_config && formValues.parser_config.graphrag && formValues.parser_config.graphrag.entity_types) {
const entityTypesString = formValues.parser_config.graphrag.entity_types as any as string; const entityTypesString = formValues.parser_config.graphrag.entity_types as any as string;
const entityTypesArray = entityTypesString const entityTypesArray = entityTypesString
@@ -336,7 +336,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
formValues.parser_config.graphrag.entity_types = entityTypesArray; formValues.parser_config.graphrag.entity_types = entityTypesArray;
} }
// 确保保存时使用正确的类型(不是 'rebuild' // Ensure correct type is used when saving (not 'rebuild')
const saveType = originalType === 'rebuild' ? currentType : (formValues.type || currentType); const saveType = originalType === 'rebuild' ? currentType : (formValues.type || currentType);
const payload: KnowledgeBaseFormData = { const payload: KnowledgeBaseFormData = {
@@ -346,7 +346,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
parent_id: datasets?.parent_id || undefined, parent_id: datasets?.parent_id || undefined,
}; };
console.log('Saving payload:', payload); // 调试日志 console.log('Saving payload:', payload); // Debug log
const submit = datasets?.id const submit = datasets?.id
? updateKnowledgeBase(datasets.id, payload) ? updateKnowledgeBase(datasets.id, payload)
@@ -367,32 +367,32 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
}); });
} }
const handleChange = (_value: string, tp: string) => { const handleChange = (_value: string, tp: string) => {
// 只在编辑模式且类型为 embedding 时触发提示 // Only trigger prompt in edit mode and when type is embedding
if (datasets?.id && tp.toLowerCase() === 'embedding') { if (datasets?.id && tp.toLowerCase() === 'embedding') {
const fieldKey = typeToFieldKey(tp); const fieldKey = typeToFieldKey(tp);
// 从原始 datasets 对象中获取之前的值 // Get previous value from original datasets object
const previousValue = (datasets as any)[fieldKey]; const previousValue = (datasets as any)[fieldKey];
confirm({ confirm({
title: t('common.updateWarning'), title: t('common.updateWarning'),
content: t('knowledgeBase.updateEmbeddingContent'), content: t('knowledgeBase.updateEmbeddingContent'),
onOk: () => { onOk: () => {
// 确定时什么也不做,保持新值 // Do nothing on confirm, keep new value
}, },
onCancel: () => { onCancel: () => {
// 取消时恢复之前的值 // Restore previous value on cancel
form.setFieldsValue({ [fieldKey]: previousValue } as any); form.setFieldsValue({ [fieldKey]: previousValue } as any);
}, },
}); });
} }
} }
// 暴露给父组件的方法 // Methods exposed to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
handleClose handleClose
})); }));
// 根据 type 获取标题 // Get title based on type
const getTitle = () => { const getTitle = () => {
if (isRebuildMode) { if (isRebuildMode) {
return t('knowledgeBase.rebuildGraph') + ' - ' + (datasets?.name || ''); return t('knowledgeBase.rebuildGraph') + ' - ' + (datasets?.name || '');
@@ -408,7 +408,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
const dynamicTypeList = useMemo(() => modelTypeList.filter((tp) => (modelOptionsByType[tp] || []).length), [modelTypeList, modelOptionsByType]); const dynamicTypeList = useMemo(() => modelTypeList.filter((tp) => (modelOptionsByType[tp] || []).length), [modelTypeList, modelOptionsByType]);
// 基础配置表单内容 // Basic configuration form content
const renderBasicConfig = () => ( const renderBasicConfig = () => (
<> <>
{!datasets?.id && ( {!datasets?.id && (
@@ -426,7 +426,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
{currentType !== 'Folder' && dynamicTypeList.map((tp) => { {currentType !== 'Folder' && dynamicTypeList.map((tp) => {
const fieldKey = typeToFieldKey(tp); const fieldKey = typeToFieldKey(tp);
// tp 'llm' 时,合并 llm chat 的选项 // When tp is 'llm', merge llm and chat options
const options = tp.toLowerCase() === 'llm' const options = tp.toLowerCase() === 'llm'
? [...(modelOptionsByType['llm'] || []), ...(modelOptionsByType['chat'] || [])] ? [...(modelOptionsByType['llm'] || []), ...(modelOptionsByType['chat'] || [])]
: modelOptionsByType[tp] || []; : modelOptionsByType[tp] || [];
@@ -451,7 +451,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
</> </>
); );
// 知识图谱配置表单内容 // Knowledge graph configuration form content
const renderKnowledgeGraphConfig = () => ( const renderKnowledgeGraphConfig = () => (
<> <>
<div className={`rb:flex rb:w-full rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${ <div className={`rb:flex rb:w-full rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${
@@ -482,7 +482,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
<div className='rb:text-[#212332] rb:text-base rb:font-medium rb:mb-4'> <div className='rb:text-[#212332] rb:text-base rb:font-medium rb:mb-4'>
{t('knowledgeBase.graphConfig')} {t('knowledgeBase.graphConfig')}
</div> </div>
{/* 场景名称 */} {/* Scene name */}
<div className='rb:flex rb:items-center rb:gap-2'> <div className='rb:flex rb:items-center rb:gap-2'>
<Form.Item <Form.Item
name={['parser_config', 'graphrag', 'scene_name']} name={['parser_config', 'graphrag', 'scene_name']}
@@ -506,7 +506,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
</div> </div>
{/* 实体类型 */} {/* Entity types */}
<Form.Item <Form.Item
name={['parser_config', 'graphrag', 'entity_types']} name={['parser_config', 'graphrag', 'entity_types']}
label={t('knowledgeBase.entityTypes')} label={t('knowledgeBase.entityTypes')}
@@ -517,7 +517,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
/> />
</Form.Item> </Form.Item>
{/* 实体归一化 */} {/* Entity normalization */}
<div className={`rb:flex rb:w-full rb:gap-2 rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${ <div className={`rb:flex rb:w-full rb:gap-2 rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${
entityNormalization entityNormalization
? 'rb:border-[#155EEF] rb:bg-[rgba(21,94,239,0.06)]' ? 'rb:border-[#155EEF] rb:bg-[rgba(21,94,239,0.06)]'
@@ -541,7 +541,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
</div> </div>
{/* 实体方法 */} {/* Entity method */}
<Form.Item <Form.Item
name={['parser_config', 'graphrag', 'method']} name={['parser_config', 'graphrag', 'method']}
label={t('knowledgeBase.entityMethod')} label={t('knowledgeBase.entityMethod')}
@@ -553,7 +553,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
{/* 社区报告生成 */} {/* Community report generation */}
<div className={`rb:flex rb:w-full rb:gap-2 rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${ <div className={`rb:flex rb:w-full rb:gap-2 rb:items-center rb:p-4 rb:border-1 rb:rounded-lg rb:mb-4 ${
communityReportGeneration communityReportGeneration
? 'rb:border-[#155EEF] rb:bg-[rgba(21,94,239,0.06)]' ? 'rb:border-[#155EEF] rb:bg-[rgba(21,94,239,0.06)]'
@@ -580,7 +580,7 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
</> </>
); );
// Tabs 配置 // Tabs configuration
const tabItems = [ const tabItems = [
{ {
key: 'basic', key: 'basic',
@@ -607,16 +607,16 @@ const CreateModal = forwardRef<CreateModalRef, CreateModalRefProps>(({
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={{ initialValues={{
permission_id: 'Private', // 设置 permission_id 的默认值 permission_id: 'Private', // Set default value for permission_id
type: currentType, type: currentType,
parser_config: { parser_config: {
graphrag: { graphrag: {
use_graphrag: false, // 默认不启用知识图谱 use_graphrag: false, // Default not to enable knowledge graph
scene_name: '', // 场景名称 scene_name: '', // Scene name
entity_types: '' as any, // 实体类型(界面上显示为字符串,保存时转为数组) entity_types: '' as any, // Entity types (displayed as string in UI, converted to array when saving)
method: 'general', // 默认使用通用方法 method: 'general', // Default to use general method
resolution: false, // 默认不启用实体归一化 resolution: false, // Default not to enable entity normalization
community: false, // 默认不生成社区报告 community: false, // Default not to generate community reports
} }
} }
}} }}

View File

@@ -17,13 +17,13 @@ const DelimiterSelector: FC<DelimiterSelectorProps> = ({
className = '', className = '',
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
// 默认值为空字符串(不设置) // Default value is empty string (not set)
const [selectedValue, setSelectedValue] = useState<string>(value || ''); const [selectedValue, setSelectedValue] = useState<string>(value || '');
const [customValue, setCustomValue] = useState<string>(''); const [customValue, setCustomValue] = useState<string>('');
const [showCustomInput, setShowCustomInput] = useState(false); const [showCustomInput, setShowCustomInput] = useState(false);
useEffect(() => { useEffect(() => {
// 检查当前值是否为自定义值 // Check if current value is a custom delimiter
if (value && isCustomDelimiter(value) && value !== 'custom') { if (value && isCustomDelimiter(value) && value !== 'custom') {
setSelectedValue('custom'); setSelectedValue('custom');
setCustomValue(value); setCustomValue(value);
@@ -39,15 +39,15 @@ const DelimiterSelector: FC<DelimiterSelectorProps> = ({
if (val === 'custom') { if (val === 'custom') {
setShowCustomInput(true); setShowCustomInput(true);
// 如果已有自定义值,使用它;否则等待用户输入 // If custom value exists, use it; otherwise wait for user input
if (customValue) { if (customValue) {
onChange?.(customValue); onChange?.(customValue);
} else { } else {
// 自定义但还没输入值,暂不触发 onChange // Custom selected but no value entered yet, don't trigger onChange
onChange?.(undefined); onChange?.(undefined);
} }
} else if (val === '') { } else if (val === '') {
// 选择"不设置"时,返回 undefined不传递该参数 // When "Not set" is selected, return undefined (don't pass this parameter)
setShowCustomInput(false); setShowCustomInput(false);
onChange?.(undefined); onChange?.(undefined);
} else { } else {
@@ -59,7 +59,7 @@ const DelimiterSelector: FC<DelimiterSelectorProps> = ({
const handleCustomInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleCustomInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const val = e.target.value; const val = e.target.value;
setCustomValue(val); setCustomValue(val);
// 只有当输入不为空时才触发 onChange // Only trigger onChange when input is not empty
onChange?.(val || undefined); onChange?.(val || undefined);
}; };

View File

@@ -60,7 +60,7 @@ interface FolderTreeProps {
onRootLoad?: (nodes: TreeNodeData[] | null) => void; onRootLoad?: (nodes: TreeNodeData[] | null) => void;
onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void; onFolderPathChange?: (path: Array<{ id: string; name: string }>) => void;
selectedKeys?: React.Key[]; selectedKeys?: React.Key[];
// 新增:自动展开到指定路径 // New: Auto expand to specified path
autoExpandPath?: Array<{ id: string; name: string }>; autoExpandPath?: Array<{ id: string; name: string }>;
} }
@@ -221,7 +221,7 @@ const extractItems = (resp: any): any[] => {
return []; return [];
}; };
// 只加载当前层级的节点,不递归加载子节点 // Only load nodes at current level, don't recursively load child nodes
const buildTreeNodes = async ( const buildTreeNodes = async (
kbId: string, kbId: string,
parentId: string, parentId: string,
@@ -229,7 +229,7 @@ const buildTreeNodes = async (
const currentParent = String(parentId ?? ''); const currentParent = String(parentId ?? '');
if (!currentParent) return []; if (!currentParent) return [];
// 只请求一次当前层级的数据,不分页 // Only request current level data once, no pagination
const response = await getFolderList({ const response = await getFolderList({
kb_id: kbId, kb_id: kbId,
parent_id: currentParent, parent_id: currentParent,
@@ -246,20 +246,20 @@ const buildTreeNodes = async (
const nodeKey = String(keySource); const nodeKey = String(keySource);
const isFolder = isFolderLike(raw); const isFolder = isFolderLike(raw);
// 只显示文件夹 // Only show folders
if (!isFolder) { if (!isFolder) {
continue; continue;
} }
// 文件夹节点初始不加载子节点isLeaf设为false表示可能有子节点 // Folder node initially doesn't load child nodes, isLeaf set to false indicates possible child nodes
nodes.push({ nodes.push({
key: nodeKey, key: nodeKey,
title: getNodeTitle(raw), title: getNodeTitle(raw),
icon: getNodeIcon(raw, isFolder), icon: getNodeIcon(raw, isFolder),
switcherIcon: isFolder ? switcherIcon : undefined, switcherIcon: isFolder ? switcherIcon : undefined,
type: isFolder ? 'folder' : (typeof raw?.type === 'string' ? raw.type : normalizeExt(raw?.file_ext) || 'file'), type: isFolder ? 'folder' : (typeof raw?.type === 'string' ? raw.type : normalizeExt(raw?.file_ext) || 'file'),
isLeaf: false, // 文件夹节点初始设为false表示可能有子节点需要展开时加载 isLeaf: false, // Folder node initially set to false, indicating possible child nodes, load when expanded
children: undefined, // 初始不加载子节点 children: undefined, // Initially don't load child nodes
}); });
} }
@@ -283,7 +283,7 @@ const FolderTree: FC<FolderTreeProps> = ({
const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]); const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
const [autoExpandInProgress, setAutoExpandInProgress] = useState(false); const [autoExpandInProgress, setAutoExpandInProgress] = useState(false);
// 更新树节点数据的辅助函数 // Helper function to update tree node data
const updateTreeData = (nodes: TreeNodeData[], key: Key, children: TreeNodeData[]): TreeNodeData[] => { const updateTreeData = (nodes: TreeNodeData[], key: Key, children: TreeNodeData[]): TreeNodeData[] => {
return nodes.map((node) => { return nodes.map((node) => {
if (node.key === key) { if (node.key === key) {
@@ -303,17 +303,17 @@ const FolderTree: FC<FolderTreeProps> = ({
}); });
}; };
// 加载根节点 // Load root nodes
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
const load = async () => { const load = async () => {
if (!knowledgeBaseId) { if (!knowledgeBaseId) {
setTreeData([]); setTreeData([]);
setExpandedKeys([]); // 重置展开状态 setExpandedKeys([]); // Reset expand state
return; return;
} }
try { try {
// 重置展开状态,确保从根目录开始 // Reset expand state, ensure starting from root directory
setExpandedKeys([]); setExpandedKeys([]);
const nodes = await buildTreeNodes(knowledgeBaseId, knowledgeBaseId); const nodes = await buildTreeNodes(knowledgeBaseId, knowledgeBaseId);
@@ -324,7 +324,7 @@ const FolderTree: FC<FolderTreeProps> = ({
} }
} }
} catch (e) { } catch (e) {
console.error('加载文件夹树失败:', e); console.error('Failed to load folder tree:', e);
if (!cancelled) { if (!cancelled) {
const fallback = buildMockTreeData(); const fallback = buildMockTreeData();
setTreeData(fallback); setTreeData(fallback);
@@ -340,27 +340,27 @@ const FolderTree: FC<FolderTreeProps> = ({
}; };
}, [knowledgeBaseId, refreshKey]); }, [knowledgeBaseId, refreshKey]);
// 懒加载子节点 - 只在展开时加载 // Lazy load child nodes - only load when expanded
const onLoadData = async (node: any) => { const onLoadData = async (node: any) => {
const { key } = node; const { key } = node;
// 如果已经加载过子节点,不再重复加载 // If child nodes already loaded, don't reload
if (node.children !== undefined) { if (node.children !== undefined) {
return Promise.resolve(); return Promise.resolve();
} }
try { try {
// 使用节点的 key 作为 parent_id 加载子文件夹 // Use node's key as parent_id to load child folders
const children = await buildTreeNodes(knowledgeBaseId, String(key)); const children = await buildTreeNodes(knowledgeBaseId, String(key));
setTreeData((prevData) => updateTreeData(prevData, key, children)); setTreeData((prevData) => updateTreeData(prevData, key, children));
} catch (e) { } catch (e) {
console.error('加载子节点失败:', e); console.error('Failed to load child nodes:', e);
// 加载失败时,将该节点标记为叶子节点(没有子节点) // On load failure, mark this node as leaf node (no child nodes)
setTreeData((prevData) => updateTreeData(prevData, key, [])); setTreeData((prevData) => updateTreeData(prevData, key, []));
} }
}; };
// 查找节点路径的辅助函数 // Helper function to find node path
const findNodePath = (nodes: TreeNodeData[], targetKey: Key, currentPath: Array<{ id: string; name: string }> = []): Array<{ id: string; name: string }> | null => { const findNodePath = (nodes: TreeNodeData[], targetKey: Key, currentPath: Array<{ id: string; name: string }> = []): Array<{ id: string; name: string }> | null => {
for (const node of nodes) { for (const node of nodes) {
const newPath = [...currentPath, { id: String(node.key), name: String(node.title) }]; const newPath = [...currentPath, { id: String(node.key), name: String(node.title) }];
@@ -379,7 +379,7 @@ const FolderTree: FC<FolderTreeProps> = ({
return null; return null;
}; };
// 查找节点的辅助函数 // Helper function to find node
const findNodeInTree = (nodes: TreeNodeData[], key: string): TreeNodeData | null => { const findNodeInTree = (nodes: TreeNodeData[], key: string): TreeNodeData | null => {
for (const node of nodes) { for (const node of nodes) {
if (String(node.key) === key) { if (String(node.key) === key) {
@@ -393,7 +393,7 @@ const FolderTree: FC<FolderTreeProps> = ({
return null; return null;
}; };
// 渐进式自动展开到指定路径 // Progressive auto expand to specified path
useEffect(() => { useEffect(() => {
if (!autoExpandPath || autoExpandPath.length === 0 || autoExpandInProgress || treeData.length === 0) { if (!autoExpandPath || autoExpandPath.length === 0 || autoExpandInProgress || treeData.length === 0) {
return; return;
@@ -406,46 +406,46 @@ const FolderTree: FC<FolderTreeProps> = ({
const keysToExpand: React.Key[] = []; const keysToExpand: React.Key[] = [];
let currentTreeData = treeData; let currentTreeData = treeData;
// 逐级展开,从第一级开始(跳过根节点,因为根节点已经加载) // Expand level by level, starting from first level (skip root node as it's already loaded)
for (let i = 0; i < autoExpandPath.length - 1; i++) { for (let i = 0; i < autoExpandPath.length - 1; i++) {
const nodeKey = autoExpandPath[i].id; const nodeKey = autoExpandPath[i].id;
keysToExpand.push(nodeKey); keysToExpand.push(nodeKey);
// 查找当前节点 // Find current node
const targetNode = findNodeInTree(currentTreeData, nodeKey); const targetNode = findNodeInTree(currentTreeData, nodeKey);
if (targetNode && targetNode.children === undefined) { if (targetNode && targetNode.children === undefined) {
// 如果子节点未加载,先加载 // If child nodes not loaded, load first
try { try {
console.log(`自动展开:加载节点 ${nodeKey} 的子节点`); console.log(`Auto expand: Loading child nodes of ${nodeKey}`);
const children = await buildTreeNodes(knowledgeBaseId, nodeKey); const children = await buildTreeNodes(knowledgeBaseId, nodeKey);
// 更新树数据 // Update tree data
setTreeData((prevData) => { setTreeData((prevData) => {
const newData = updateTreeData(prevData, nodeKey, children); const newData = updateTreeData(prevData, nodeKey, children);
currentTreeData = newData; // 更新当前引用 currentTreeData = newData; // Update current reference
return newData; return newData;
}); });
// 等待状态更新完成 // Wait for state update to complete
await new Promise(resolve => setTimeout(resolve, 150)); await new Promise(resolve => setTimeout(resolve, 150));
} catch (error) { } catch (error) {
console.error(`自动展开时加载节点 ${nodeKey} 失败:`, error); console.error(`Failed to load node ${nodeKey} during auto expand:`, error);
// 加载失败时停止展开 // Stop expanding on load failure
break; break;
} }
} }
} }
// 设置展开的节点 // Set expanded nodes
setExpandedKeys(keysToExpand); setExpandedKeys(keysToExpand);
// 选中最后一个节点(目标文件夹) // Select last node (target folder)
const targetKey = autoExpandPath[autoExpandPath.length - 1]?.id; const targetKey = autoExpandPath[autoExpandPath.length - 1]?.id;
if (targetKey) { if (targetKey) {
console.log(`自动展开:选中目标节点 ${targetKey}`); console.log(`Auto expand: Select target node ${targetKey}`);
// 延迟选中,确保展开动画完成 // Delay selection to ensure expand animation completes
setTimeout(() => { setTimeout(() => {
if (onSelect) { if (onSelect) {
onSelect([targetKey], { onSelect([targetKey], {
@@ -460,21 +460,21 @@ const FolderTree: FC<FolderTreeProps> = ({
} }
} catch (error) { } catch (error) {
console.error('自动展开路径失败:', error); console.error('Auto expand path failed:', error);
} finally { } finally {
// 延迟重置标志,确保展开过程完全完成 // Delay reset flag to ensure expand process is fully complete
setTimeout(() => { setTimeout(() => {
setAutoExpandInProgress(false); setAutoExpandInProgress(false);
}, 500); }, 500);
} }
}; };
// 延迟执行,确保树数据已经加载完成 // Delay execution to ensure tree data is loaded
const timer = setTimeout(expandToPath, 300); const timer = setTimeout(expandToPath, 300);
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [autoExpandPath, treeData.length, knowledgeBaseId, onSelect, autoExpandInProgress]); }, [autoExpandPath, treeData.length, knowledgeBaseId, onSelect, autoExpandInProgress]);
// 处理展开事件 // Handle expand event
const handleExpand: TreeProps['onExpand'] = (expandedKeys, info) => { const handleExpand: TreeProps['onExpand'] = (expandedKeys, info) => {
setExpandedKeys(expandedKeys); setExpandedKeys(expandedKeys);
if (onExpand) { if (onExpand) {
@@ -482,7 +482,7 @@ const FolderTree: FC<FolderTreeProps> = ({
} }
}; };
// 处理选择事件,计算并传递路径 // Handle select event, calculate and pass path
const handleSelect: TreeProps['onSelect'] = (selectedKeys, info) => { const handleSelect: TreeProps['onSelect'] = (selectedKeys, info) => {
if (selectedKeys.length > 0) { if (selectedKeys.length > 0) {
const path = findNodePath(treeData, selectedKeys[0]); const path = findNodePath(treeData, selectedKeys[0]);
@@ -493,7 +493,7 @@ const FolderTree: FC<FolderTreeProps> = ({
onFolderPathChange([]); onFolderPathChange([]);
} }
// 调用原始的 onSelect 回调 // Call original onSelect callback
if (onSelect) { if (onSelect) {
onSelect(selectedKeys, info); onSelect(selectedKeys, info);
} }
@@ -503,7 +503,7 @@ const FolderTree: FC<FolderTreeProps> = ({
return ( return (
<DirectoryTree <DirectoryTree
key={refreshKey} // 添加key确保refreshKey变化时重新渲染整个组件 key={refreshKey} // Add key to ensure component re-renders when refreshKey changes
multiple={multiple} multiple={multiple}
className={className} className={className}
style={style} style={style}

View File

@@ -130,7 +130,7 @@ const InsertModal = forwardRef<InsertModalRef, InsertModalProps>(({ onInsert, on
} }
} }
} catch (error) { } catch (error) {
console.error('操作失败:', error); console.error('Operation failed:', error);
const errorMsg = isEditMode const errorMsg = isEditMode
? (t('knowledgeBase.updateFailed') || '更新失败') ? (t('knowledgeBase.updateFailed') || '更新失败')
: (t('knowledgeBase.insertFailed') || '插入失败'); : (t('knowledgeBase.insertFailed') || '插入失败');

View File

@@ -9,7 +9,7 @@ import pointer from '@/assets/images/userMemory/pointer.svg'
import empty from '@/assets/images/userMemory/empty.svg' import empty from '@/assets/images/userMemory/empty.svg'
import Empty from '@/components/Empty' import Empty from '@/components/Empty'
// 知识图谱数据类型定义 // Knowledge graph data type definitions
export interface KnowledgeNode { export interface KnowledgeNode {
id: string id: string
entity_name: string entity_name: string
@@ -17,7 +17,7 @@ export interface KnowledgeNode {
description: string description: string
pagerank: number pagerank: number
source_id: string[] source_id: string[]
// ECharts 需要的属性 // Properties required by ECharts
name: string name: string
category: number category: number
symbolSize: number symbolSize: number
@@ -35,7 +35,7 @@ export interface KnowledgeEdge {
source_id: string[] source_id: string[]
source: string source: string
target: string target: string
// ECharts 需要的属性 // Properties required by ECharts
value: number value: number
} }
@@ -65,7 +65,7 @@ const operations = [
{ name: 'zoom', icon: zoom }, { name: 'zoom', icon: zoom },
] ]
// 预定义的颜色调色板 // Predefined color palette
const colorPalette = [ const colorPalette = [
'#155EEF', '#4DA8FF', '#9C6FFF', '#8BAEF7', '#369F21', '#155EEF', '#4DA8FF', '#9C6FFF', '#8BAEF7', '#369F21',
'#FF5D34', '#FF8A4C', '#FFB048', '#E74C3C', '#9B59B6', '#FF5D34', '#FF8A4C', '#FFB048', '#E74C3C', '#9B59B6',
@@ -73,7 +73,7 @@ const colorPalette = [
'#8E44AD', '#2980B9', '#16A085', '#F1C40F', '#E67E22' '#8E44AD', '#2980B9', '#16A085', '#F1C40F', '#E67E22'
] ]
// 动态生成实体类型颜色映射 // Dynamically generate entity type color mapping
const generateEntityTypeColors = (entityTypes: string[]): Record<string, string> => { const generateEntityTypeColors = (entityTypes: string[]): Record<string, string> => {
const colorMap: Record<string, string> = {} const colorMap: Record<string, string> = {}
entityTypes.forEach((type, index) => { entityTypes.forEach((type, index) => {
@@ -93,12 +93,12 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
const [selectedNode, setSelectedNode] = useState<KnowledgeNode | null>(null) const [selectedNode, setSelectedNode] = useState<KnowledgeNode | null>(null)
const [entityTypeColors, setEntityTypeColors] = useState<Record<string, string>>({}) const [entityTypeColors, setEntityTypeColors] = useState<Record<string, string>>({})
// 弹框拖动相关状态 // Modal drag-related state
const [modalPosition, setModalPosition] = useState({ x: 20, y: 20 }) const [modalPosition, setModalPosition] = useState({ x: 20, y: 20 })
const [isDragging, setIsDragging] = useState(false) const [isDragging, setIsDragging] = useState(false)
const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) const [dragStart, setDragStart] = useState({ x: 0, y: 0 })
// 拖动处理函数 // Drag handling functions
const handleMouseDown = useCallback((e: React.MouseEvent) => { const handleMouseDown = useCallback((e: React.MouseEvent) => {
setIsDragging(true) setIsDragging(true)
setDragStart({ setDragStart({
@@ -113,7 +113,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
const newX = e.clientX - dragStart.x const newX = e.clientX - dragStart.x
const newY = e.clientY - dragStart.y const newY = e.clientY - dragStart.y
// 限制拖动范围,确保弹框不会超出容器 // Limit drag range to ensure modal doesn't exceed container bounds
const container = chartRef.current?.getEchartsInstance().getDom().parentElement const container = chartRef.current?.getEchartsInstance().getDom().parentElement
if (container && modalRef.current) { if (container && modalRef.current) {
const containerRect = container.getBoundingClientRect() const containerRect = container.getBoundingClientRect()
@@ -133,7 +133,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
setIsDragging(false) setIsDragging(false)
}, []) }, [])
// 添加全局鼠标事件监听 // Add global mouse event listeners
useEffect(() => { useEffect(() => {
if (isDragging) { if (isDragging) {
document.addEventListener('mousemove', handleMouseMove) document.addEventListener('mousemove', handleMouseMove)
@@ -145,12 +145,12 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
} }
}, [isDragging, handleMouseMove, handleMouseUp]) }, [isDragging, handleMouseMove, handleMouseUp])
// 关闭弹框 // Close modal
const handleCloseModal = useCallback(() => { const handleCloseModal = useCallback(() => {
setSelectedNode(null) setSelectedNode(null)
}, []) }, [])
// 处理知识图谱数据 // Process knowledge graph data
const processGraphData = useCallback(() => { const processGraphData = useCallback(() => {
if (!data?.graph) { if (!data?.graph) {
setNodes([]) setNodes([])
@@ -164,31 +164,31 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
const processedNodes: KnowledgeNode[] = [] const processedNodes: KnowledgeNode[] = []
const processedEdges: KnowledgeEdge[] = [] const processedEdges: KnowledgeEdge[] = []
// 获取所有实体类型 // Get all entity types
const entityTypes = [...new Set(rawNodes.map(node => node.entity_type))] const entityTypes = [...new Set(rawNodes.map(node => node.entity_type))]
const categoryMap = entityTypes.reduce((acc, type, index) => { const categoryMap = entityTypes.reduce((acc, type, index) => {
acc[type] = index acc[type] = index
return acc return acc
}, {} as Record<string, number>) }, {} as Record<string, number>)
// 动态生成实体类型颜色映射 // Dynamically generate entity type color mapping
const dynamicEntityTypeColors = generateEntityTypeColors(entityTypes) const dynamicEntityTypeColors = generateEntityTypeColors(entityTypes)
setEntityTypeColors(dynamicEntityTypeColors) setEntityTypeColors(dynamicEntityTypeColors)
// 计算每个节点的连接数 // Calculate connection count for each node
const connectionCount: Record<string, number> = {} const connectionCount: Record<string, number> = {}
rawEdges.forEach(edge => { rawEdges.forEach(edge => {
// 使用 src_id tgt_id 计算连接数 // Use src_id and tgt_id to calculate connection count
connectionCount[edge.src_id] = (connectionCount[edge.src_id] || 0) + 1 connectionCount[edge.src_id] = (connectionCount[edge.src_id] || 0) + 1
connectionCount[edge.tgt_id] = (connectionCount[edge.tgt_id] || 0) + 1 connectionCount[edge.tgt_id] = (connectionCount[edge.tgt_id] || 0) + 1
}) })
// 处理节点数据 // Process node data
rawNodes.forEach(node => { rawNodes.forEach(node => {
const connections = connectionCount[node.id] || 0 const connections = connectionCount[node.id] || 0
const categoryIndex = categoryMap[node.entity_type] || 0 const categoryIndex = categoryMap[node.entity_type] || 0
// 根据 pagerank 和连接数计算节点大小 // Calculate node size based on pagerank and connection count
let symbolSize = Math.max(10, Math.min(50, node.pagerank * 200 + connections * 2)) let symbolSize = Math.max(10, Math.min(50, node.pagerank * 200 + connections * 2))
processedNodes.push({ processedNodes.push({
@@ -202,19 +202,19 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
}) })
}) })
// 处理边数据 // Process edge data
rawEdges.forEach(edge => { rawEdges.forEach(edge => {
// 注意:根据数据结构,source target 字段可能与 src_id tgt_id 相反 // Note: Based on data structure, source and target fields may be opposite to src_id and tgt_id
// 我们使用 src_id tgt_id 作为正确的连接关系 // We use src_id and tgt_id as the correct connection relationship
processedEdges.push({ processedEdges.push({
...edge, // 保留所有原始字段 ...edge, // Keep all original fields
source: edge.src_id, // 使用 src_id 作为源节点 source: edge.src_id, // Use src_id as source node
target: edge.tgt_id, // 使用 tgt_id 作为目标节点 target: edge.tgt_id, // Use tgt_id as target node
value: edge.weight || 1 value: edge.weight || 1
}) })
}) })
// 验证节点ID和边的连接 // Verify node IDs and edge connections
const nodeIds = new Set(processedNodes.map(n => n.id)) const nodeIds = new Set(processedNodes.map(n => n.id))
const validEdges = processedEdges.filter(edge => { const validEdges = processedEdges.filter(edge => {
const sourceExists = nodeIds.has(edge.source) const sourceExists = nodeIds.has(edge.source)
@@ -225,18 +225,18 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
return sourceExists && targetExists return sourceExists && targetExists
}) })
// 调试信息 // Debug information
console.log('Total nodes:', processedNodes.length) console.log('Total nodes:', processedNodes.length)
console.log('Total edges:', processedEdges.length) console.log('Total edges:', processedEdges.length)
console.log('Valid edges:', validEdges.length) console.log('Valid edges:', validEdges.length)
console.log('Node IDs:', Array.from(nodeIds).slice(0, 5)) console.log('Node IDs:', Array.from(nodeIds).slice(0, 5))
console.log('Edge sample:', validEdges.slice(0, 3)) console.log('Edge sample:', validEdges.slice(0, 3))
// 设置分类 // Set categories
const processedCategories = entityTypes.map(type => ({ name: type })) const processedCategories = entityTypes.map(type => ({ name: type }))
setNodes(processedNodes) setNodes(processedNodes)
setLinks(validEdges) // 只使用有效的边 setLinks(validEdges) // Only use valid edges
setCategories(processedCategories) setCategories(processedCategories)
}, [data]) }, [data])
@@ -334,7 +334,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
lineStyle: { lineStyle: {
color: '#5B6167', color: '#5B6167',
curveness: 0.3, curveness: 0.3,
width: 2, // 固定线宽,避免函数问题 width: 2, // Fixed line width to avoid function issues
opacity: 0.8 opacity: 0.8
}, },
force: { force: {
@@ -376,7 +376,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
}} }}
/> />
{/* 实体详情弹框 */} {/* Entity details modal */}
{selectedNode && ( {selectedNode && (
<div <div
ref={modalRef} ref={modalRef}
@@ -387,7 +387,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
cursor: isDragging ? 'grabbing' : 'grab' cursor: isDragging ? 'grabbing' : 'grab'
}} }}
> >
{/* 弹框头部 - 可拖动区域 */} {/* Modal header - draggable area */}
<div <div
className="rb:flex rb:items-center rb:justify-between rb:mb-3 rb:pb-2 rb:border-b rb:border-[#EBEBEB] rb:cursor-grab" className="rb:flex rb:items-center rb:justify-between rb:mb-3 rb:pb-2 rb:border-b rb:border-[#EBEBEB] rb:cursor-grab"
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
@@ -404,7 +404,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
</button> </button>
</div> </div>
{/* 弹框内容 */} {/* Modal content */}
<div> <div>
<div className="rb:font-medium rb:mb-4"> <div className="rb:font-medium rb:mb-4">
<div className="rb:text-[16px] rb:mb-2">{selectedNode.entity_name}</div> <div className="rb:text-[16px] rb:mb-2">{selectedNode.entity_name}</div>

View File

@@ -15,7 +15,7 @@ import { type KnowledgeBase } from '../types';
import Empty from '@/components/Empty'; import Empty from '@/components/Empty';
interface KnowledgeGraphCardProps { interface KnowledgeGraphCardProps {
knowledgeBase?: KnowledgeBase; knowledgeBase?: KnowledgeBase;
onRebuildGraph?: () => void; // 添加重建图谱的回调函数 onRebuildGraph?: () => void; // Callback function to rebuild graph
} }
const KnowledgeGraphCard: React.FC<KnowledgeGraphCardProps> = ({ knowledgeBase, onRebuildGraph }) => { const KnowledgeGraphCard: React.FC<KnowledgeGraphCardProps> = ({ knowledgeBase, onRebuildGraph }) => {
@@ -23,7 +23,7 @@ const KnowledgeGraphCard: React.FC<KnowledgeGraphCardProps> = ({ knowledgeBase,
const [data, setData] = useState<KnowledgeGraphResponse | undefined>() const [data, setData] = useState<KnowledgeGraphResponse | undefined>()
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const handleRebuildGraph = () => { const handleRebuildGraph = () => {
// 调用父组件传递的回调函数来打开CreateModal并传递重建标识 // Call parent component's callback to open CreateModal with rebuild flag
if (onRebuildGraph) { if (onRebuildGraph) {
onRebuildGraph(); onRebuildGraph();
} }
@@ -38,15 +38,15 @@ const KnowledgeGraphCard: React.FC<KnowledgeGraphCardProps> = ({ knowledgeBase,
setLoading(true) setLoading(true)
try { try {
const res = await getKnowledgeGraph(knowledgeBase?.id) const res = await getKnowledgeGraph(knowledgeBase?.id)
// 判断 res.graph 是否为空对象或不存在 // Check if res.graph is empty object or doesn't exist
const graphResponse = res as KnowledgeGraphResponse; const graphResponse = res as KnowledgeGraphResponse;
if (!graphResponse || !graphResponse.graph || Object.keys(graphResponse.graph).length === 0) { if (!graphResponse || !graphResponse.graph || Object.keys(graphResponse.graph).length === 0) {
setData(undefined) // 设置为 undefined 以显示 empty 状态 setData(undefined) // Set to undefined to show empty state
} else { } else {
setData(graphResponse) setData(graphResponse)
} }
} catch (error) { } catch (error) {
console.error('获取知识图谱数据失败:', error) console.error('Failed to fetch knowledge graph data:', error)
} finally { } finally {
setLoading(false) setLoading(false)
} }

View File

@@ -27,7 +27,7 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
{ label: t('knowledgeBase.vector'), value: false }, { label: t('knowledgeBase.vector'), value: false },
]); ]);
// 获取检索模式选项 // Get retrieval mode options
useEffect(() => { useEffect(() => {
fetchRetrievalModeOptions(); fetchRetrievalModeOptions();
}, []); }, []);
@@ -36,9 +36,9 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
try { try {
const response = await getRetrievalModeType(); const response = await getRetrievalModeType();
if (response && Array.isArray(response)) { if (response && Array.isArray(response)) {
// 将 API 返回的数据转换为选项格式 // Convert API response to option format
const options = response.map((item: any) => { const options = response.map((item: any) => {
// 支持多种数据格式 // Support multiple data formats
let label = t(`knowledgeBase.${item}`) + ' ' + t(`knowledgeBase.retrieve`); let label = t(`knowledgeBase.${item}`) + ' ' + t(`knowledgeBase.retrieve`);
let value = item; let value = item;
@@ -50,8 +50,8 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
} }
} }
} catch (error) { } catch (error) {
console.error('获取检索模式选项失败:', error); console.error('Failed to fetch retrieval mode options:', error);
// 保持默认选项 // Keep default options
} }
}; };
@@ -60,8 +60,8 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
setKnowledgeBaseId(kbId || ''); setKnowledgeBaseId(kbId || '');
form.resetFields(); form.resetFields();
setData([]); setData([]);
setRetrieveType('hybrid'); // 重置为默认值 setRetrieveType('hybrid'); // Reset to default value
// 确保表单字段也设置为默认值 // Ensure form field is also set to default value
form.setFieldsValue({ retrieve_type: 'hybrid' }); form.setFieldsValue({ retrieve_type: 'hybrid' });
} }
const fetchData = (params: RecallTestParams) => { const fetchData = (params: RecallTestParams) => {
@@ -91,10 +91,10 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
console.log('RecallTest - params:', params); console.log('RecallTest - params:', params);
fetchData(params); fetchData(params);
}).catch((error) => { }).catch((error) => {
console.error('表单验证失败:', error); console.error('Form validation failed:', error);
}); });
} }
// 暴露给父组件的方法 // Expose methods to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
})); }));
@@ -134,7 +134,7 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
/> />
</Form.Item> </Form.Item>
{/* retrieve_type = semantic hybrid 时显示 */} {/* Show when retrieve_type = semantic or hybrid */}
{(retrieveType === 'semantic' || retrieveType === 'hybrid') && ( {(retrieveType === 'semantic' || retrieveType === 'hybrid') && (
<Form.Item name="similarity_threshold" label={t('knowledgeBase.similarityThreshold')}> <Form.Item name="similarity_threshold" label={t('knowledgeBase.similarityThreshold')}>
<Select <Select
@@ -155,7 +155,7 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
</Form.Item> </Form.Item>
)} )}
{/* retrieve_type = participle hybrid 时显示 */} {/* Show when retrieve_type = participle or hybrid */}
{(retrieveType === 'participle' || retrieveType === 'hybrid') && ( {(retrieveType === 'participle' || retrieveType === 'hybrid') && (
<Form.Item name="vector_similarity_weight" label={t('knowledgeBase.semanticSimilarity')}> <Form.Item name="vector_similarity_weight" label={t('knowledgeBase.semanticSimilarity')}>
<Select <Select

View File

@@ -12,7 +12,7 @@ const RecallTestDrawer = forwardRef<RecallTestDrawerRef>(({},ref) => {
const pendingKbIdRef = useRef<string | undefined>(undefined); const pendingKbIdRef = useRef<string | undefined>(undefined);
const shouldCallHandleOpenRef = useRef(false); const shouldCallHandleOpenRef = useRef(false);
// 调用 RecallTest handleOpen 方法 // Call RecallTest's handleOpen method
const callRecallTestHandleOpen = useCallback(() => { const callRecallTestHandleOpen = useCallback(() => {
if (recallTestRef.current && shouldCallHandleOpenRef.current) { if (recallTestRef.current && shouldCallHandleOpenRef.current) {
recallTestRef.current.handleOpen(pendingKbIdRef.current); recallTestRef.current.handleOpen(pendingKbIdRef.current);
@@ -26,14 +26,14 @@ const RecallTestDrawer = forwardRef<RecallTestDrawerRef>(({},ref) => {
setOpen(true); setOpen(true);
} }
// Drawer 打开时,尝试调用 handleOpen // When Drawer opens, try to call handleOpen
useLayoutEffect(() => { useLayoutEffect(() => {
if (open) { if (open) {
callRecallTestHandleOpen(); callRecallTestHandleOpen();
} }
}, [open, callRecallTestHandleOpen]); }, [open, callRecallTestHandleOpen]);
// 使用回调 ref 确保在组件挂载后立即调用 // Use callback ref to ensure immediate call after component mount
const setRecallTestRef = useCallback((node: any) => { const setRecallTestRef = useCallback((node: any) => {
recallTestRef.current = node; recallTestRef.current = node;
if (open && shouldCallHandleOpenRef.current) { if (open && shouldCallHandleOpenRef.current) {
@@ -41,7 +41,7 @@ const RecallTestDrawer = forwardRef<RecallTestDrawerRef>(({},ref) => {
} }
}, [open, callRecallTestHandleOpen]); }, [open, callRecallTestHandleOpen]);
// 暴露给父组件的方法 // Expose methods to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
})); }));

View File

@@ -1,5 +1,5 @@
/* /**
* @Description: 滚动列表 * @Description: Scroll List
* @Version: 0.0.1 * @Version: 0.0.1
* @Author: yujiangping * @Author: yujiangping
* @Date: 2025-11-18 16:19:58 * @Date: 2025-11-18 16:19:58
@@ -22,9 +22,9 @@ interface RecallTestResultProps {
loadMore?: () => void; loadMore?: () => void;
loading?: boolean; loading?: boolean;
scrollableTarget?: string; scrollableTarget?: string;
editable?: boolean; // 是否可编辑 editable?: boolean; // Whether editable
onItemClick?: (item: RecallTestData, index: number) => void; // 点击项的回调 onItemClick?: (item: RecallTestData, index: number) => void; // Click item callback
parserMode?: number; // 解析模式1 表示 QA 格式 parserMode?: number; // Parser mode, 1 means QA format
} }
const RecallTestResult = ({ const RecallTestResult = ({
@@ -40,7 +40,7 @@ const RecallTestResult = ({
}: RecallTestResultProps) => { }: RecallTestResultProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
// 解析 QA 格式内容 // Parse QA format content
const parseQAContent = (content: string) => { const parseQAContent = (content: string) => {
if (!content || parserMode !== 1) return null; if (!content || parserMode !== 1) return null;
@@ -56,25 +56,25 @@ const RecallTestResult = ({
return null; return null;
}; };
// 格式化 QA 内容为显示格式 // Format QA content for display
const formatQAContent = (question: string, answer: string) => { const formatQAContent = (question: string, answer: string) => {
return `**${t('knowledgeBase.question')}:** ${question}\n**${t('knowledgeBase.answer')}:** ${answer}`; return `**${t('knowledgeBase.question')}:** ${question}\n**${t('knowledgeBase.answer')}:** ${answer}`;
}; };
const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => { const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => {
// 检查点击的是否是图片或图片相关元素 // Check if the click is on an image or image-related element
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
// 检查是否点击了图片本身、图片的容器、预览层、关闭按钮或 SVG 图标 // Check if clicked on image itself, image container, preview layer, close button or SVG icon
if ( if (
target.tagName === 'IMG' || target.tagName === 'IMG' ||
target.tagName === 'SVG' || // SVG 图标 target.tagName === 'SVG' || // SVG icon
target.tagName === 'PATH' || // SVG 路径 target.tagName === 'PATH' || // SVG path
target.closest('.ant-image') || target.closest('.ant-image') ||
target.closest('.ant-image-preview') || target.closest('.ant-image-preview') ||
target.closest('.ant-image-preview-wrap') || target.closest('.ant-image-preview-wrap') ||
target.closest('.ant-image-preview-operations') || target.closest('.ant-image-preview-operations') ||
target.closest('.anticon') || // Ant Design 图标 target.closest('.anticon') || // Ant Design icon
target.classList.contains('ant-image-img') || target.classList.contains('ant-image-img') ||
target.classList.contains('ant-image-mask') || target.classList.contains('ant-image-mask') ||
target.classList.contains('ant-image-preview-close') || target.classList.contains('ant-image-preview-close') ||
@@ -88,7 +88,7 @@ const RecallTestResult = ({
} }
}; };
// 根据分数获取颜色类名 // Get color class based on score
const getScoreColorClass = (score: number): string => { const getScoreColorClass = (score: number): string => {
const percentage = score * 100; const percentage = score * 100;
if (percentage >= 90) { if (percentage >= 90) {
@@ -177,7 +177,7 @@ const RecallTestResult = ({
</div> </div>
); );
// 如果提供了 loadMore hasMore,使用 InfiniteScroll // If loadMore and hasMore are provided, use InfiniteScroll
if (loadMore && hasMore !== undefined) { if (loadMore && hasMore !== undefined) {
return ( return (
<div className='rb:flex rb:h-full rb:flex-col'> <div className='rb:flex rb:h-full rb:flex-col'>
@@ -200,7 +200,7 @@ const RecallTestResult = ({
); );
} }
// 否则使用普通渲染 // Otherwise use normal rendering
return ( return (
<div className='rb:flex rb:flex-col'> <div className='rb:flex rb:flex-col'>
<div className='rb:flex rb:items-center rb:justify-start rb:gap-2'> <div className='rb:flex rb:items-center rb:justify-start rb:gap-2'>

View File

@@ -4,13 +4,13 @@
* @Author: yujiangping * @Author: yujiangping
* @Date: 2025-11-10 18:52:55 * @Date: 2025-11-10 18:52:55
* @LastEditors: yujiangping * @LastEditors: yujiangping
* @LastEditTime: 2025-11-29 12:29:31 * @LastEditTime: 2026-02-03 17:08:00
*/ */
import { forwardRef, useImperativeHandle, useState, useRef } from 'react'; import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
import { Switch } from 'antd'; import { Switch } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { message } from 'antd'; import { message } from 'antd';
import type { ShareModalRef, ShareModalRefProps, KnowledgeBase} from '@/views/KnowledgeBase/types'; import type { ShareModalRef, ShareModalRefProps, KnowledgeBase, SpaceItem} from '@/views/KnowledgeBase/types';
import RbModal from '@/components/RbModal' import RbModal from '@/components/RbModal'
// import betchControlIcon from '@/assets/images/knowledgeBase/betch-control.png'; // import betchControlIcon from '@/assets/images/knowledgeBase/betch-control.png';
import kbIcon from '@/assets/images/knowledgeBase/knowledge-management.png'; import kbIcon from '@/assets/images/knowledgeBase/knowledge-management.png';
@@ -33,7 +33,7 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBase | null>(null); const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBase | null>(null);
const [spaceList, setSpaceList] = useState<SpaceItem[]>([]); const [spaceList, setSpaceList] = useState<SpaceItem[]>([]);
// 封装取消方法,添加关闭弹窗逻辑 // Close modal and reset state
const handleClose = () => { const handleClose = () => {
setCurIndex(9999); setCurIndex(9999);
setLoading(false) setLoading(false)
@@ -66,11 +66,11 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
console.log('Workspace IDs:', workspaceIds); console.log('Workspace IDs:', workspaceIds);
shareSpaceModalRef?.current?.handleOpen(kbId,knowledgeBase,workspaceIds); shareSpaceModalRef?.current?.handleOpen(kbId,knowledgeBase,workspaceIds);
// 分享后关闭弹窗 // Close modal after sharing
handleClose(); handleClose();
} }
const handleChange = (checked: boolean, item: any) => { const handleChange = (checked: boolean, item: any) => {
// 打开/关闭分享出去的数据库 // Toggle shared knowledge base status
console.log('Switch changed:', checked, item); console.log('Switch changed:', checked, item);
updateKnowledgeBase(item.target_kb?.id, { updateKnowledgeBase(item.target_kb?.id, {
status: checked ? 1 : 2 status: checked ? 1 : 2
@@ -82,7 +82,7 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
}) })
} }
// 暴露给父组件的方法 // Expose methods to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
handleClose, handleClose,

View File

@@ -30,7 +30,7 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBase | null>(null); const [knowledgeBase, setKnowledgeBase] = useState<KnowledgeBase | null>(null);
const [spaceList, setSpaceList] = useState<SpaceItem[]>([]); const [spaceList, setSpaceList] = useState<SpaceItem[]>([]);
// 封装取消方法,添加关闭弹窗逻辑 // Close modal and reset state
const handleClose = () => { const handleClose = () => {
setCurIndex(-1); setCurIndex(-1);
setLoading(false) setLoading(false)
@@ -51,10 +51,10 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
} }
const handleShare = async() => { const handleShare = async() => {
// 获取所有 checked true 的数据 // Get all data with checked = true
const checkedItems = spaceList.filter(item => item.is_active); const checkedItems = spaceList.filter(item => item.is_active);
debugger debugger
// 获取当前选中的项(curIndex 对应的数据) // Get currently selected item (corresponding to curIndex)
const selectedItem = curIndex !== -1 ? spaceList[curIndex] : null; const selectedItem = curIndex !== -1 ? spaceList[curIndex] : null;
if(!selectedItem){ if(!selectedItem){
messageApi.error(t('knowledgeBase.selectSpace')); messageApi.error(t('knowledgeBase.selectSpace'));
@@ -70,13 +70,13 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
}else{ }else{
messageApi.error(t('knowledgeBase.shareFailed')); messageApi.error(t('knowledgeBase.shareFailed'));
} }
// 调用父组件传递的回调函数,传递选中的数据 // Call parent component's callback function with selected data
onShare?.({ onShare?.({
checkedItems, checkedItems,
selectedItem selectedItem
}); });
// 分享后关闭弹窗 // Close modal after sharing
handleClose(); handleClose();
} }
const handleClick = (index: number, checked: boolean) => { const handleClick = (index: number, checked: boolean) => {
@@ -84,7 +84,7 @@ const ShareModal = forwardRef<ShareModalRef,ShareModalRefProps>(({ handleShare:
setCurIndex(index); setCurIndex(index);
} }
// 暴露给父组件的方法 // Expose methods to parent component
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
handleClose, handleClose,

View File

@@ -36,11 +36,11 @@ const Datasets: FC = () => {
}; };
if (loading) { if (loading) {
return <div>...</div>; return <div>Loading...</div>;
} }
if (!knowledgeBase) { if (!knowledgeBase) {
return <div></div>; return <div>Knowledge base not found</div>;
} }
return ( return (
@@ -50,7 +50,7 @@ const Datasets: FC = () => {
icon={<ArrowLeftOutlined />} icon={<ArrowLeftOutlined />}
onClick={handleBack} onClick={handleBack}
> >
{t('common.back')}
</Button> </Button>
</div> </div>
@@ -61,7 +61,7 @@ const Datasets: FC = () => {
<div className="rb:bg-white rb:p-4 rb:rounded"> <div className="rb:bg-white rb:p-4 rb:rounded">
<h2 className="rb:text-lg rb:font-semibold rb:mb-4">{t('knowledgeBase.datasets')}</h2> <h2 className="rb:text-lg rb:font-semibold rb:mb-4">{t('knowledgeBase.datasets')}</h2>
{/* TODO: 添加数据集列表 */} {/* TODO: Add dataset list */}
<div>{t('knowledgeBase.noDataSets')}</div> <div>{t('knowledgeBase.noDataSets')}</div>
</div> </div>
</div> </div>

View File

@@ -388,7 +388,7 @@ const KnowledgeBaseManagement: FC = () => {
}); });
}, },
onCancel: () => { onCancel: () => {
console.log('取消删除'); console.log('Cancel delete');
}, },
}); });
}; };