diff --git a/web/src/api/knowledgeBase.ts b/web/src/api/knowledgeBase.ts index 60f374a2..3c1433a1 100644 --- a/web/src/api/knowledgeBase.ts +++ b/web/src/api/knowledgeBase.ts @@ -199,8 +199,8 @@ export const deleteFile = async (id: string) => { }; // 获取文档列表 -export const getDocumentList = async (query: PathQuery) => { - const response = await request.get(`${apiPrefix}/documents/${query.kb_id}/documents`, query); +export const getDocumentList = async (kb_id:string, query: PathQuery) => { + const response = await request.get(`${apiPrefix}/documents/${kb_id}/documents`, query); return response as KnowledgeBaseDocumentData[]; }; // 文档详情 diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx index d4a9084f..c1e42fc8 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx @@ -111,15 +111,17 @@ const CreateDataset = () => { // 从参数设置进入确认上传时的处理 if(current === 1 && nextStep === 2) { + // debugger // handlePreview(data[0],0) - if(parameterSettings === 'customSettings'){ + if(parameterSettings === 'customSettings' || processingMethod === 'qaExtract'){ rechunkFileIds.map((id) => { const params = { + progress: 0, parser_config: { layout_recognize:'DeepDOC', delimiter: delimiter, chunk_token_num: blockSize, - auto_question: processingMethod === 'directBlock' ? 0 : 1, + auto_questions: processingMethod === 'directBlock' ? 0 : 1, } } updateDocument(id, params) @@ -144,7 +146,6 @@ const CreateDataset = () => { }); return; } - // 显示确认弹框 confirm({ @@ -153,12 +154,18 @@ const CreateDataset = () => { okText: t('knowledgeBase.returnToList') || '返回列表页', cancelText: t('knowledgeBase.stayOnPage') || '停留在此页', onOk: () => { - // 用户选择返回列表页 + // 用户选择返回列表页 - 不显示 loading,直接跳转 startProcessing(true); }, onCancel: () => { - // 用户选择停留在当前页 - startProcessing(false); + // 用户选择停留在当前页 - 显示 loading 并开始轮询 + console.log('用户选择停留,开始显示 loading'); + setPollingLoading(true); + + // 延迟一点时间让用户看到 loading 效果,然后开始处理 + setTimeout(() => { + startProcessing(false); + }, 100); }, }); }; @@ -170,15 +177,12 @@ const CreateDataset = () => { parseDocument(id, {}); }); - // 开启 loading - setPollingLoading(true); - if (autoReturnToList) { - // 用户选择立即返回,直接跳转 + // 用户选择立即返回,直接跳转(不显示 loading) console.log('用户选择立即返回列表页'); handleBack(); } else { - // 用户选择停留,启动轮询查看进度 + // 用户选择停留,启动轮询查看进度(loading 已在 onCancel 中设置) console.log('用户选择停留查看进度'); // 立即执行一次轮询(启用自动返回) @@ -187,7 +191,7 @@ const CreateDataset = () => { // 然后每3秒执行一次(启用自动返回) pollingTimerRef.current = setInterval(() => { pollDocumentStatus(true); - }, 5000); + }, 3000); } }; const handleDelete = (record: AnyObject) => { @@ -222,7 +226,7 @@ const CreateDataset = () => { title: t('knowledgeBase.status'), dataIndex: 'progress', key: 'progress', - render: (value: number) => { + render: (value: number, record: any) => { return ( @@ -280,44 +284,59 @@ const CreateDataset = () => { // 轮询检查文档处理状态 // autoReturn: 是否在所有文档完成时自动返回列表页 const pollDocumentStatus = (autoReturn: boolean = false) => { + console.log('开始轮询文档状态,当前 pollingLoading:', pollingLoading); + if (!knowledgeBaseId || !parentId || rechunkFileIds.length === 0) { + console.log('轮询条件不满足,退出'); return; } - // 刷新 Table 组件的数据(仅在 confirmUpload 步骤) - if (current === 2) { - tableRef.current?.loadData(); - } - - // 同时获取文档列表检查是否全部完成 - getDocumentList({ - kb_id: knowledgeBaseId, - parent_id: parentId, + // 获取文档列表检查是否全部完成,并刷新表格数据 + getDocumentList(knowledgeBaseId, { document_ids: rechunkFileIds.join(','), }) .then((res: any) => { const documents = res.items || []; setData(documents); + // 只在 confirmUpload 步骤刷新表格数据 + if (current === 2) { + tableRef.current?.loadData(); + } + + console.log('documents', documents); // 检查是否所有文档的 progress 都为 1 const allCompleted = documents.every((doc: KnowledgeBaseDocumentData) => doc.progress === 1); - console.log('轮询状态:', documents.map((d: KnowledgeBaseDocumentData) => ({ name: d.file_name, progress: d.progress }))); + console.log('轮询状态:', allCompleted); - // 只有在 autoReturn 为 true 且所有文档完成时才自动返回 - if (allCompleted && autoReturn) { - // 所有文档处理完成,清除定时器和 loading + // 检查是否所有文档都完成了 + // debugger + if (allCompleted) { + // 清除定时器和 loading 状态 if (pollingTimerRef.current) { clearInterval(pollingTimerRef.current); pollingTimerRef.current = null; } - setPollingLoading(false); - // 延迟 2 秒后跳转,让用户看到完成状态 - console.log('所有文档处理完成,2秒后返回列表页'); + // 延迟清除 loading,让用户看到完成状态 setTimeout(() => { - handleBack(); - }, 2000); + setPollingLoading(false); + }, 1000); + + // 只有在 autoReturn 为 true 时才自动返回 + if (autoReturn) { + // 延迟 2 秒后跳转,让用户看到完成状态 + console.log('所有文档处理完成,2秒后返回列表页'); + setTimeout(() => { + handleBack(); + }, 2000); + } else { + console.log('所有文档处理完成,用户可手动操作'); + } + } else { + // 如果还有文档在处理中,确保 loading 状态保持 + console.log('还有文档在处理中,保持 loading 状态'); } }) .catch((error) => { @@ -349,9 +368,7 @@ const CreateDataset = () => { useEffect(() => { if (initialFileIds.length > 0 && initialStepKey !== 'selectFile' && knowledgeBaseId && parentId) { // 加载文档列表数据 - getDocumentList({ - kb_id: knowledgeBaseId, - parent_id: parentId, + getDocumentList(knowledgeBaseId,{ document_ids: initialFileIds.join(','), }) .then((res: any) => { @@ -364,7 +381,7 @@ const CreateDataset = () => { } }, []); - // 清理函数:组件卸载时清除定时器 + // 清理函数:组件卸载时清除定时器和 loading 状态 useEffect(() => { return () => { if (pollingTimerRef.current) { @@ -375,6 +392,18 @@ const CreateDataset = () => { }; }, []); + // 监听路由变化,确保在页面切换时清理状态 + useEffect(() => { + return () => { + // 页面卸载时清理状态 + if (pollingTimerRef.current) { + clearInterval(pollingTimerRef.current); + pollingTimerRef.current = null; + } + setPollingLoading(false); + }; + }, [location.pathname]); + return ( <> {contextHolder} @@ -553,7 +582,7 @@ const CreateDataset = () => { { const [chunkLoading, setChunkLoading] = useState(false); const [keywords, setKeywords] = useState(''); const [fileUrl, setFileUrl] = useState(''); + const [parserMode, setParserMode] = useState(0); const insertModalRef = useRef(null); const isManualRefreshRef = useRef(false); @@ -127,6 +128,7 @@ const DocumentDetails: FC = () => { setInfoItems(formatDocumentInfo(response)); const url = `${imagePath}/api/files/${response.file_id}` setFileUrl(url); + setParserMode(response?.parser_config?.auto_questions || 0) // ChunkList 会在 useEffect 中根据 document.progress 自动调用 } catch (error) { console.error('获取文档详情失败:', error); @@ -388,6 +390,7 @@ const DocumentDetails: FC = () => { {t('knowledgeBase.chunkList') || '分块列表'} { scrollableTarget="chunkScrollableDiv" editable={true} onItemClick={handleChunkClick} + parserMode={parserMode} /> diff --git a/web/src/views/KnowledgeBase/components/CreateFolderModal.tsx b/web/src/views/KnowledgeBase/components/CreateFolderModal.tsx index 906c1f77..578288d9 100644 --- a/web/src/views/KnowledgeBase/components/CreateFolderModal.tsx +++ b/web/src/views/KnowledgeBase/components/CreateFolderModal.tsx @@ -22,6 +22,7 @@ const CreateFolderModal = forwardRef { + debugger if (folder) { setFolder(folder); // 设置表单值 diff --git a/web/src/views/KnowledgeBase/components/RecallTestResult.tsx b/web/src/views/KnowledgeBase/components/RecallTestResult.tsx index 1475d790..70583249 100644 --- a/web/src/views/KnowledgeBase/components/RecallTestResult.tsx +++ b/web/src/views/KnowledgeBase/components/RecallTestResult.tsx @@ -24,6 +24,7 @@ interface RecallTestResultProps { scrollableTarget?: string; editable?: boolean; // 是否可编辑 onItemClick?: (item: RecallTestData, index: number) => void; // 点击项的回调 + parserMode?: number; // 解析模式,1 表示 QA 格式 } const RecallTestResult = ({ @@ -35,9 +36,31 @@ const RecallTestResult = ({ scrollableTarget, editable = false, onItemClick, + parserMode = 0, }: RecallTestResultProps) => { const { t } = useTranslation(); + // 解析 QA 格式内容 + const parseQAContent = (content: string) => { + if (!content || parserMode !== 1) return null; + + const qaRegex = /question:\s*(.*?)\s*answer:\s*(.*?)$/s; + const match = content.match(qaRegex); + + if (match) { + const question = match[1]?.trim() || ''; + const answer = match[2]?.trim() || ''; + return { question, answer }; + } + + return null; + }; + + // 格式化 QA 内容为显示格式 + const formatQAContent = (question: string, answer: string) => { + return `**问题:** ${question}\n\n**答案:** ${answer}`; + }; + const handleItemClick = (e: React.MouseEvent, item: RecallTestData, index: number) => { // 检查点击的是否是图片或图片相关元素 const target = e.target as HTMLElement; @@ -126,7 +149,14 @@ const RecallTestResult = ({
- + {(() => { + const qaContent = parseQAContent(item.page_content); + if (qaContent) { + const formattedContent = formatQAContent(qaContent.question, qaContent.answer); + return ; + } + return ; + })()}
{item.metadata?.file_created_at && ( diff --git a/web/src/views/KnowledgeBase/index.tsx b/web/src/views/KnowledgeBase/index.tsx index ca438f66..caed669c 100644 --- a/web/src/views/KnowledgeBase/index.tsx +++ b/web/src/views/KnowledgeBase/index.tsx @@ -74,7 +74,8 @@ const KnowledgeBaseManagement: FC = () => { const items: NonNullable = []; // 当权限为 share 时,不显示编辑按钮 - if (item.permission_id !== 'share') { + const permissionId = (item.permission_id || '').toLowerCase(); + if (permissionId !== 'share') { items.push({ key: '1', label: t('knowledgeBase.edit'), @@ -131,7 +132,7 @@ const KnowledgeBaseManagement: FC = () => { }; // 处理创建 - const handleCreate = (type?: string) => { + const handleCreate = useCallback((type?: string) => { // 如果在文件夹内,使用 folderPath 的最后一项作为 parent_id // 这样更可靠,因为 folderPath 是直接管理的状态 const currentParentId = folderPath.length > 0 @@ -142,8 +143,17 @@ const KnowledgeBaseManagement: FC = () => { parent_id: currentParentId as string, } as KnowledgeBaseListItem : null; + console.log('handleCreate called:', { + type, + folderPath, + folderPathLength: folderPath.length, + queryParentId: query.parent_id, + currentParentId, + record + }); + modalRef?.current?.handleOpen(record, type) - } + }, [folderPath, query.parent_id]) // 动态生成 createItems const createItems: MenuProps['items'] = useMemo(() => { @@ -155,7 +165,7 @@ const KnowledgeBaseManagement: FC = () => { handleCreate(type); }, })); - }, [knowledgeBaseTypes, t]); + }, [knowledgeBaseTypes, t, handleCreate]); const typeToFieldKey = (type: string) => { const normalized = (type || '').toLowerCase(); switch (normalized) { @@ -180,7 +190,7 @@ const KnowledgeBaseManagement: FC = () => { key, label: t(`knowledgeBase.${key}`), children: key === 'permission_id' - ? (data[key] === 'Private' || data[key] === 'private' ? t('knowledgeBase.private') : t('knowledgeBase.share')) + ? ((data[key] || '').toLowerCase() === 'private' ? t('knowledgeBase.private') : t('knowledgeBase.share')) : String(data[key] || '-'), })) } @@ -283,7 +293,15 @@ const KnowledgeBaseManagement: FC = () => { const fetchData = async (pageNum: number = 1, isLoadMore: boolean = false) => { if (!modelTypes.length) return; if (loading) return; - console.log('fetchData called, pageNum:', pageNum, 'isLoadMore:', isLoadMore); + + console.log('fetchData called:', { + pageNum, + isLoadMore, + currentQuery: query, + currentFolderPath: folderPath, + folderPathLastId: folderPath.length > 0 ? folderPath[folderPath.length - 1].id : 'none' + }); + setLoading(true); try { const params = { @@ -293,6 +311,8 @@ const KnowledgeBaseManagement: FC = () => { orderby:'created_at', desc:true, } + + console.log('API params:', params); const res = await getKnowledgeBaseList(undefined, params); const response = res as KnowledgeBaseListResponse & { items?: KnowledgeBaseListItem[] }; console.log('API response:', response); @@ -373,10 +393,21 @@ const KnowledgeBaseManagement: FC = () => { }); }; // 处理跳转详情 - const handleToDetail = (knowledgeBase: KnowledgeBaseListItem) => { + const handleToDetail = useCallback((knowledgeBase: KnowledgeBaseListItem) => { + // 统一处理类型判断,忽略大小写 + const itemType = (knowledgeBase.type || '').toLowerCase(); + + console.log('handleToDetail called with:', { + id: knowledgeBase.id, + name: knowledgeBase.name, + type: itemType, + currentFolderPath: folderPath, + currentQuery: query + }); + // 如果是 Folder 类型,刷新当前页面,显示该文件夹下的知识库列表 - if (knowledgeBase.type === 'Folder' || knowledgeBase.type === 'folder') { - // 添加到文件夹路径 + if (itemType === 'folder') { + // 计算新的文件夹路径 const newFolderPath = [ ...folderPath, { @@ -384,15 +415,33 @@ const KnowledgeBaseManagement: FC = () => { name: knowledgeBase.name, }, ]; - setFolderPath(newFolderPath); + console.log('Folder clicked:', { + folderId: knowledgeBase.id, + folderName: knowledgeBase.name, + currentFolderPath: folderPath, + newFolderPath: newFolderPath + }); + + // 同步更新状态,保持与面包屑逻辑一致 + setFolderPath(newFolderPath); setQuery((prev) => ({ ...prev, parent_id: knowledgeBase.id, })); + return; } + + // 统一处理权限判断,忽略大小写 + const permissionId = (knowledgeBase.permission_id || '').toLowerCase(); + const isPrivate = permissionId === 'private'; + // 根据权限类型跳转到不同的详情页 + const targetPath = isPrivate + ? `/knowledge-base/${knowledgeBase.id}/private` + : `/knowledge-base/${knowledgeBase.id}/share`; + // 跳转时传递当前的文件夹路径信息 const navigationState = { fromKnowledgeBaseList: true, @@ -400,9 +449,6 @@ const KnowledgeBaseManagement: FC = () => { parentId: query.parent_id, timestamp: Date.now(), // 添加时间戳确保每次跳转状态都不同 }; - const targetPath = knowledgeBase.permission_id === 'Private' || knowledgeBase.permission_id === 'private' - ? `/knowledge-base/${knowledgeBase.id}/private` - : `/knowledge-base/${knowledgeBase.id}/share`; // 检查是否是相同路径跳转 const currentPath = location.pathname; @@ -417,7 +463,7 @@ const KnowledgeBaseManagement: FC = () => { // 不同路径,正常跳转 navigate(targetPath, { state: navigationState }); } - } + }, [folderPath, query, location.pathname, navigate]) // 更新面包屑 useEffect(() => { updateBreadcrumbs({