From 4d7f9c4dae3668508e8cff9c2c460fc2771e8880 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 29 Apr 2026 11:28:13 +0800 Subject: [PATCH 1/4] feat(web): show ids --- web/src/components/Tag/index.tsx | 5 ++-- .../[knowledgeBaseId]/DocumentDetails.tsx | 23 +++++++++++++-- .../[knowledgeBaseId]/Private.tsx | 25 ++++++++++++++--- .../KnowledgeBase/components/InfoPanel.tsx | 3 +- .../components/KnowledgeGraph.tsx | 2 ++ .../components/RecallTestResult.tsx | 28 +++++++++++++------ web/src/views/KnowledgeBase/index.tsx | 4 ++- 7 files changed, 72 insertions(+), 18 deletions(-) diff --git a/web/src/components/Tag/index.tsx b/web/src/components/Tag/index.tsx index e7307843..3601b984 100644 --- a/web/src/components/Tag/index.tsx +++ b/web/src/components/Tag/index.tsx @@ -24,6 +24,7 @@ export interface TagProps { /** Additional CSS classes */ className?: string; variant?: 'outline' | 'borderless' + onClick?: () => void; } /** Color theme mappings with text, border, and background colors */ @@ -38,9 +39,9 @@ const colors = { } /** Custom tag component with color themes */ -const Tag: FC = ({ color = 'processing', children, className, variant = 'outline' }) => { +const Tag: FC = ({ color = 'processing', children, className, variant = 'outline', onClick }) => { return ( - + {children} ) diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx index f3716cd3..3a720598 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx @@ -10,7 +10,7 @@ import { useEffect, useState, useRef, type FC } from 'react'; import { useNavigate, useParams, useLocation, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager'; -import { Button, Spin, message, Switch } from 'antd'; +import { Button, Spin, message, Switch, App } from 'antd'; import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk, getFileUrl } from '@/api/knowledgeBase'; import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types'; import { formatDateTime } from '@/utils/format'; @@ -21,9 +21,11 @@ import DocumentPreview from '@/components/DocumentPreview'; import InsertModal, { type InsertModalRef } from '../components/InsertModal'; import exitIcon from '@/assets/images/knowledgeBase/exit.png'; const imagePath = 'https://devapi.mem.redbearai.com' +import copy from 'copy-to-clipboard' const DocumentDetails: FC = () => { const { t } = useTranslation(); const navigate = useNavigate(); + const { message: messageApi } = App.useApp() const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>(); const location = useLocation(); const { updateBreadcrumbs } = useBreadcrumbManager({ @@ -100,9 +102,25 @@ const DocumentDetails: FC = () => { }, [keywords]); + const handleCopy = (value?: string) => { + if (!value) return + copy(value) + messageApi.success(t('common.copySuccess')) + } + const formatDocumentInfo = (doc: KnowledgeBaseDocumentData): InfoItem[] => { return [ + { + key: 'file_id', + label: 'ID', + value: handleCopy(doc.file_id)}> + {doc.file_id} + + , + }, { key: 'file_name', label: t('knowledgeBase.fileName') || '文件名', @@ -387,7 +405,7 @@ const DocumentDetails: FC = () => {
{/* Left: Document info */}
-
+
{ editable={true} onItemClick={handleChunkClick} parserMode={parserMode} + handleCopy={handleCopy} />
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx index 0198f363..b9dd3a41 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/Private.tsx @@ -39,6 +39,8 @@ import { formatDateTime } from '@/utils/format'; import KnowledgeGraphCard from '../components/KnowledgeGraphCard'; import { useBreadcrumbManager, type BreadcrumbItem } from '@/hooks/useBreadcrumbManager'; import './Private.css' +import Tag from '@/components/Tag' +import copy from 'copy-to-clipboard' // Tree node data type const Private: FC = () => { @@ -570,7 +572,7 @@ const Private: FC = () => { return ( {value === 1 ? t('knowledgeBase.completed') : value === 0 ? t('knowledgeBase.pending') : t('knowledgeBase.processing')} @@ -613,6 +615,7 @@ const Private: FC = () => { title: t('knowledgeBase.processingMode'), dataIndex: 'parser_id', key: 'parser_id', + width: 100, }, { title: t('knowledgeBase.dataSize'), @@ -629,6 +632,11 @@ const Private: FC = () => { ) } }, + { + title: 'ID', + dataIndex: 'id', + key: 'id', + }, { title: t('common.operation'), @@ -762,11 +770,16 @@ const Private: FC = () => { setIsSyncing(false); }; + const handleCopy = (value: string) => { + copy(value) + messageApi.success(t('common.copySuccess')) + } + return ( <>
{folder && ( -
+
{
- edit + edit {t('knowledgeBase.edit')} {t('knowledgeBase.name')}
-
+
+ handleCopy(knowledgeBase.id)}> + ID: {knowledgeBase.id} + + {t('knowledgeBase.created')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.created_at) || '-'} {t('knowledgeBase.updated')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.updated_at) || '-'} diff --git a/web/src/views/KnowledgeBase/components/InfoPanel.tsx b/web/src/views/KnowledgeBase/components/InfoPanel.tsx index a5d4ace2..a3688284 100644 --- a/web/src/views/KnowledgeBase/components/InfoPanel.tsx +++ b/web/src/views/KnowledgeBase/components/InfoPanel.tsx @@ -7,11 +7,12 @@ * @LastEditTime: 2025-11-19 19:59:36 */ import { Divider } from 'antd'; +import type { ReactElement } from 'react'; export interface InfoItem { key: string; label: string; - value: string | number | undefined; + value: string | number | undefined | ReactElement; icon?: string; } diff --git a/web/src/views/KnowledgeBase/components/KnowledgeGraph.tsx b/web/src/views/KnowledgeBase/components/KnowledgeGraph.tsx index 7096bbd4..6d2124a2 100644 --- a/web/src/views/KnowledgeBase/components/KnowledgeGraph.tsx +++ b/web/src/views/KnowledgeBase/components/KnowledgeGraph.tsx @@ -266,6 +266,8 @@ const KnowledgeGraph: FC = ({ data, loading = false }) => { } }, [nodes]) + console.log('selectedNode', selectedNode) + return ( void; // Click item callback parserMode?: number; // Parser mode, 1 means QA format + handleCopy?: (text?: string) => void; } const RecallTestResult = ({ @@ -38,8 +39,10 @@ const RecallTestResult = ({ editable = false, onItemClick, parserMode = 0, + handleCopy, }: RecallTestResultProps) => { const { t } = useTranslation(); + console.log('chunk data', data) // Parse QA format content const parseQAContent = (content: string) => { @@ -204,13 +207,21 @@ const RecallTestResult = ({ })()}
- {item.metadata?.file_created_at && ( -
- - {formatDateTime(item.metadata.file_created_at)} - -
- )} + + {item.metadata?.file_created_at && ( +
+ + {formatDateTime(item.metadata.file_created_at)} + +
+ )} + handleCopy?.(item.metadata?.doc_id)}> + ID: {item.metadata?.doc_id} + + +
); })} @@ -245,6 +256,7 @@ const RecallTestResult = ({ ); } + // Otherwise use normal rendering return (
diff --git a/web/src/views/KnowledgeBase/index.tsx b/web/src/views/KnowledgeBase/index.tsx index 8c16d7a7..3110ff63 100644 --- a/web/src/views/KnowledgeBase/index.tsx +++ b/web/src/views/KnowledgeBase/index.tsx @@ -574,6 +574,8 @@ const KnowledgeBaseManagement: FC = () => { title={item.name} headerType="borderless" headerClassName="rb:py-3!" + className="rb:cursor-pointer" + onClick={() => handleToDetail(item)} extra={
e.stopPropagation()}> {
} > -
handleToDetail(item)}> +
{/*
{t('knowledgeBase.description')}
*/} From 6197d698a2583bae886563d66dbc7d5f891cf132 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 29 Apr 2026 11:43:30 +0800 Subject: [PATCH 2/4] fix(web): workflow knowledge save --- web/src/views/Workflow/hooks/useWorkflowGraph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index a22ee6c0..7d299e91 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -1310,7 +1310,7 @@ export const useWorkflowGraph = ({ ...itemConfig, ...(data.config[key].defaultValue || {}), knowledge_bases: knowledge_bases?.map((vo: any) => { - const kb_config = vo.config || { similarity_threshold: vo.similarity_threshold, retrieve_type: vo.retrieve_type, top_k: vo.top_k, weight: vo.weight } + const kb_config = vo.config || vo return { kb_id: vo.kb_id || vo.id, ...kb_config, } }) } From b0a4f9fa189bb33a1232e52f6df62a104108a021 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 29 Apr 2026 12:27:04 +0800 Subject: [PATCH 3/4] fix(web): knowledge config --- web/src/i18n/en.ts | 12 ++--- web/src/i18n/zh.ts | 12 ++--- .../Knowledge/KnowledgeConfigModal.tsx | 46 +++++++++-------- .../Knowledge/KnowledgeConfigModal.tsx | 51 +++++++++---------- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 2a7534c4..e0247cd9 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -1281,13 +1281,13 @@ export const en = { hybrid: 'Hybrid Retrieval', graph: 'Graph Retrieval', - similarity_threshold: 'Semantic similarity threshold', - similarity_threshold_desc: 'Only return results with semantic similarity higher than this threshold', - similarity_threshold_desc1: 'The minimum similarity threshold for semantic retrieval', + vector_similarity_weight: 'Semantic similarity threshold', + vector_similarity_weight_desc: 'Only return results with semantic similarity higher than this threshold', + vector_similarity_weight_desc1: 'The minimum similarity threshold for semantic retrieval', - vector_similarity_weight: 'Vector Similarity Weight', - vector_similarity_weight_desc: 'Only return results with BM25 scores above this threshold', - vector_similarity_weight_desc1: 'The minimum BM25 score threshold for word segmentation retrieval', + similarity_threshold: 'Vector Similarity Weight', + similarity_threshold_desc: 'Only return results with BM25 scores above this threshold', + similarity_threshold_desc1: 'The minimum BM25 score threshold for word segmentation retrieval', description: 'Description', shareVersion: 'Share Version', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 6989cf3f..385a2ae7 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -663,13 +663,13 @@ export const zh = { hybrid: '混合检索', graph: '图谱检索', - similarity_threshold: '语义相似度阈值', - similarity_threshold_desc: '仅返回语义相似度高于此阈值的结果', - similarity_threshold_desc1: '语义检索的最小相似度阈值', + similarity_threshold: '向量相似度权重', + similarity_threshold_desc: '仅返回BM25分数高于此阈值的结果', + similarity_threshold_desc1: '分词检索的最小BM25分数阈值', - vector_similarity_weight: '向量相似度权重', - vector_similarity_weight_desc: '仅返回BM25分数高于此阈值的结果', - vector_similarity_weight_desc1: '分词检索的最小BM25分数阈值', + vector_similarity_weight: '语义相似度阈值', + vector_similarity_weight_desc: '仅返回语义相似度高于此阈值的结果', + vector_similarity_weight_desc1: '语义检索的最小相似度阈值', description: '描述', shareVersion: '分享版本', diff --git a/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeConfigModal.tsx b/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeConfigModal.tsx index ef8abe38..5da2bdef 100644 --- a/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeConfigModal.tsx +++ b/web/src/views/ApplicationConfig/components/Knowledge/KnowledgeConfigModal.tsx @@ -91,7 +91,7 @@ const KnowledgeConfigModal = forwardRef { if (values?.retrieve_type) { - const fieldsToReset = Object.keys(values).filter(key => + const fieldsToReset = Object.keys(values).filter(key => key !== 'kb_id' && key !== 'retrieve_type' && key !== 'top_k' ) as (keyof KnowledgeConfigForm)[]; form.resetFields(fieldsToReset); @@ -127,7 +127,7 @@ const KnowledgeConfigModal = forwardRef - + ({ label: t(`application.${key}`), value: key, }))} - // onChange={handleChange} /> {/* Top K */} @@ -124,34 +123,18 @@ const KnowledgeConfigModal = forwardRef form.setFieldValue('top_k', value)} + onChange={(value) => form.setFieldValue('top_k', value)} /> - {/* 语义相似度阈值 similarity_threshold */} + {/* Vector similarity weight */} {values?.retrieve_type === 'semantic' && ( - - - - )} - {/* 分词匹配度阈值 vector_similarity_weight */} - {values?.retrieve_type === 'participle' && ( - )} - {/* 混合检索权重 */} + {/* similarity threshold */} + {values?.retrieve_type === 'participle' && ( + + + + )} + {/* Hybrid retrieval weight */} {values?.retrieve_type === 'hybrid' && ( <> - - Date: Wed, 6 May 2026 14:13:13 +0800 Subject: [PATCH 4/4] feat(web): knowledge base --- web/src/api/knowledgeBase.ts | 18 ++- web/src/i18n/en.ts | 2 + web/src/i18n/zh.ts | 3 + .../[knowledgeBaseId]/CreateDataset.tsx | 145 +++++++++++------- .../[knowledgeBaseId]/DocumentDetails.tsx | 14 +- .../components/CreateDatasetModal.tsx | 12 +- .../components/RecallTestResult.tsx | 40 ++++- web/src/views/KnowledgeBase/index.tsx | 12 ++ web/src/views/KnowledgeBase/types.ts | 3 +- 9 files changed, 181 insertions(+), 68 deletions(-) diff --git a/web/src/api/knowledgeBase.ts b/web/src/api/knowledgeBase.ts index 52384d06..6816beb9 100644 --- a/web/src/api/knowledgeBase.ts +++ b/web/src/api/knowledgeBase.ts @@ -154,6 +154,19 @@ export const uploadFile = async (data: FormData, options?: UploadFileOptions) => }); return response as UploadFileResponse; }; +// 上传 QA 文件 +export const uploadQaFile = async (data: FormData, options?: UploadFileOptions) => { + const { kb_id, parent_id, onUploadProgress, signal } = options || {}; + const params: Record = {}; + if (kb_id) params.kb_id = kb_id; + if (parent_id) params.parent_id = parent_id; + const response = await request.uploadFile(`/chunks/${kb_id}/import_qa`, data, { + params, + onUploadProgress, + signal, + }); + return response as UploadFileResponse; +}; // 下载文件 export const downloadFile = async (fileId: string, fileName?: string) => { @@ -293,7 +306,10 @@ export const updateDocumentChunk = async (kb_id:string, document_id:string, doc_ const response = await request.put(`${apiPrefix}/chunks/${kb_id}/${document_id}/${doc_id}`, data); return response as any; }; - +export const deleteDocumentChunk = async (kb_id: string, document_id: string, doc_id: string) => { + const response = await request.delete(`${apiPrefix}/chunks/${kb_id}/${document_id}/${doc_id}`); + return response as any; +}; // 文档块儿创建 export const createDocumentChunk = async (kb_id:string, document_id:string, data: any) => { const response = await request.post(`${apiPrefix}/chunks/${kb_id}/${document_id}/chunk`, data); diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index e0247cd9..bbd431a3 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -709,6 +709,8 @@ export const en = { localFile: 'Local File', uploadFileTypes: 'Upload PDF, TXT, DOCX, IMAGE, MEDIA and other format files', webLink: 'Web Link', + csvFile: 'Tabular Dataset', + csvUploadFileTypes: 'Upload files in CSV format', webLinkPlaceholder:'Please enter', webLinkDesc: 'Only static links are supported. If the uploaded data shows as empty, the link may not be readable. One per line, with a maximum of {{count}} links at a time', selectorTutorial: 'Selector Usage Tutorial', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 385a2ae7..78d8db7e 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -194,6 +194,8 @@ export const zh = { localFile: '本地文件', uploadFileTypes: '上传 PDF、 TXT、 DOCX、 IMAGE、 MEDIA 等格式的文件', webLink: '网页链接', + csvFile: '表格数据集', + csvUploadFileTypes: '上传 CSV 格式的文件', webLinkPlaceholder: '请输入', webLinkDesc: '仅支持静态链接。如果上传的数据显示为空,则该链接可能无法读取。每行一个,一次最多{{count}}个链接', selectorTutorial: '选择器使用教程', @@ -283,6 +285,7 @@ export const zh = { qaExtract: '问答对提取', default: '默认', customize: '自定义', + qaPrompt: 'QA 拆分引导词', defaultSettings: '使用系统默认的参数和规则', customSettings: '自定义设置数据处理规则', fileName: '文件名称', diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx index 7d32796d..41375981 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/CreateDataset.tsx @@ -10,7 +10,7 @@ import type { ColumnsType } from 'antd/es/table'; import type { UploadFile } from 'antd'; import UploadFiles from '@/components/Upload/UploadFiles'; import type { UploadRequestOption } from 'rc-upload/lib/interface'; -import { uploadFile, getDocumentList, parseDocument, updateDocument, deleteDocument, createDocumentAndUpload } from '@/api/knowledgeBase'; +import { uploadFile, uploadQaFile, getDocumentList, parseDocument, updateDocument, deleteDocument, createDocumentAndUpload } from '@/api/knowledgeBase'; import exitIcon from '@/assets/images/knowledgeBase/exit.png'; import SliderInput from '@/components/SliderInput'; @@ -38,7 +38,7 @@ const { TextArea } = Input; }); -type SourceType = 'local' | 'link' | 'text'; +type SourceType = 'local' | 'link' | 'text' | 'csv'; type ProcessingMethod = 'directBlock' | 'qaExtract'; type ParameterSettings = 'defaultSettings' | 'customSettings'; const stepKeys = ['selectFile', 'parameterSettings', 'dataPreview', 'confirmUpload'] as const; @@ -63,6 +63,8 @@ interface ContentFormData { title: string; content: string; } +const fileType = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'md', 'htm', 'html', 'json', 'ppt', 'pptx', 'txt', 'png', 'jpg', 'mp3', 'mp4', 'mov', 'wav'] +const csvFileType = ['csv'] const CreateDataset = () => { const { t } = useTranslation(); const navigate = useNavigate(); @@ -91,11 +93,12 @@ const CreateDataset = () => { const pollingTimerRef = useRef | null>(null); const [delimiter, setDelimiter] = useState(undefined); const [blockSize, setBlockSize] = useState(130); + const [qaPrompt, setQaPrompt] = useState() + console.log('qaPrompt', qaPrompt) const [processingMethod, setProcessingMethod] = useState('directBlock'); const [parameterSettings, setParameterSettings] = useState('defaultSettings'); const [pdfEnhancementEnabled, setPdfEnhancementEnabled] = useState(true); const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState('mineru'); - const fileType = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv', 'md', 'htm', 'html', 'json', 'ppt', 'pptx', 'txt','png','jpg','mp3','mp4','mov','wav'] const steps = useMemo( () => [ { title: t('knowledgeBase.selectFile') }, @@ -112,8 +115,11 @@ const CreateDataset = () => { const handleNext = async () => { // Temporarily hide step 3: adjust step index (0->1->2 corresponds to select file->parameter settings->confirm upload) let nextStep = current + 1; + if (current === 0 && source === 'csv') { + return + } - if(nextStep === 1 && source === 'local') { + if((nextStep === 1 && source === 'local') || (nextStep === 2 && source === 'csv')) { // Check if files have been uploaded if (rechunkFileIds.length === 0) { // If no files, prompt user to upload first @@ -159,6 +165,7 @@ const CreateDataset = () => { delimiter: delimiter, chunk_token_num: blockSize, auto_questions: processingMethod === 'directBlock' ? 0 : 1, + qa_prompt: qaPrompt } } updateDocument(id, params) @@ -378,40 +385,67 @@ const CreateDataset = () => { formData.append('parent_id', parentId); } - uploadFile(formData, { - kb_id: knowledgeBaseId, - parent_id: parentId, - signal: abortController.signal, - onUploadProgress: (event) => { - if (!event.total) return; - const percent = Math.round((event.loaded / event.total) * 100); - onProgress?.({ percent }, file); - }, - }) - .then((res: UploadFileResponse) => { - // Upload successful, remove AbortController - abortControllersRef.current.delete(fileUid); - - onSuccess?.(res, new XMLHttpRequest()); - if (res?.id) { - setRechunkFileIds((prev) => { - if (prev.includes(res.id)) return prev; - const next = [...prev, res.id]; - return next; - }); - } + if (source === 'csv') { + uploadQaFile(formData, { + kb_id: knowledgeBaseId, + parent_id: parentId, + signal: abortController.signal, }) - .catch((error) => { - // Remove AbortController - abortControllersRef.current.delete(fileUid); - - // If user actively cancelled, don't show error message - if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') { - console.log('Upload cancelled:', (file as File).name); - return; - } - onError?.(error as Error); - }); + .then((res: UploadFileResponse) => { + // Upload successful, remove AbortController + abortControllersRef.current.delete(fileUid); + + onSuccess?.(res, new XMLHttpRequest()); + messageApi.success(t('knowledgeBase.uploadSuccess')) + handleBack() + }) + .catch((error) => { + // Remove AbortController + abortControllersRef.current.delete(fileUid); + + // If user actively cancelled, don't show error message + if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') { + console.log('Upload cancelled:', (file as File).name); + return; + } + onError?.(error as Error); + }); + } else { + uploadFile(formData, { + kb_id: knowledgeBaseId, + parent_id: parentId, + signal: abortController.signal, + onUploadProgress: (event) => { + if (!event.total) return; + const percent = Math.round((event.loaded / event.total) * 100); + onProgress?.({ percent }, file); + }, + }) + .then((res: UploadFileResponse) => { + // Upload successful, remove AbortController + abortControllersRef.current.delete(fileUid); + + onSuccess?.(res, new XMLHttpRequest()); + if (res?.id) { + setRechunkFileIds((prev) => { + if (prev.includes(res.id)) return prev; + const next = [...prev, res.id]; + return next; + }); + } + }) + .catch((error) => { + // Remove AbortController + abortControllersRef.current.delete(fileUid); + + // If user actively cancelled, don't show error message + if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') { + console.log('Upload cancelled:', (file as File).name); + return; + } + onError?.(error as Error); + }); + } }; @@ -557,21 +591,21 @@ const CreateDataset = () => { exit {t('common.exit')}
-
+ {source !== 'csv' &&
-
+
}
{current === 0 && (
- {source && source === 'local' && ( + {source && (source === 'local' || source === 'csv') && ( { console.log('File list changed:', fileList); @@ -765,18 +799,23 @@ const CreateDataset = () => { - {parameterSettings === 'customSettings' && ( + {parameterSettings === 'customSettings' && (<>
-
-
- {t('knowledgeBase.delimiter')} -
- +
+
+ {t('knowledgeBase.delimiter')} +
+
- - )} +
+
+ {t('knowledgeBase.qaPrompt')} +
+ setQaPrompt(e.target.value)} /> +
+ )}
)} @@ -853,7 +892,7 @@ const CreateDataset = () => { {t('common.previous') || 'Prev'} )} - + }
diff --git a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx index 3a720598..137efdd5 100644 --- a/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx +++ b/web/src/views/KnowledgeBase/[knowledgeBaseId]/DocumentDetails.tsx @@ -11,7 +11,7 @@ import { useNavigate, useParams, useLocation, useSearchParams } from 'react-rout import { useTranslation } from 'react-i18next'; import { useBreadcrumbManager, type BreadcrumbPath } from '@/hooks/useBreadcrumbManager'; import { Button, Spin, message, Switch, App } from 'antd'; -import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk, getFileUrl } from '@/api/knowledgeBase'; +import { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk } from '@/api/knowledgeBase'; import type { KnowledgeBaseDocumentData, RecallTestData } from '@/views/KnowledgeBase/types'; import { formatDateTime } from '@/utils/format'; import InfoPanel, { type InfoItem } from '../components/InfoPanel'; @@ -20,7 +20,6 @@ import SearchInput from '@/components/SearchInput'; import DocumentPreview from '@/components/DocumentPreview'; import InsertModal, { type InsertModalRef } from '../components/InsertModal'; import exitIcon from '@/assets/images/knowledgeBase/exit.png'; -const imagePath = 'https://devapi.mem.redbearai.com' import copy from 'copy-to-clipboard' const DocumentDetails: FC = () => { const { t } = useTranslation(); @@ -228,6 +227,11 @@ const DocumentDetails: FC = () => { } }; + const refreshChunks = () => { + let nextPage = 1; + setPage(nextPage); + ChunkList(nextPage); + } const loadMoreChunks = () => { const nextPage = page + 1; setPage(nextPage); @@ -363,8 +367,8 @@ const DocumentDetails: FC = () => { fileName={document?.file_name} fileExt={document?.file_ext} height="calc(100% - 40px)" - mode="google" - showModeSwitch={true} + // mode="google" + // showModeSwitch={true} />
)} @@ -425,7 +429,7 @@ const DocumentDetails: FC = () => { {t('knowledgeBase.chunkList') || '分块列表'} { @@ -86,7 +90,7 @@ const CreateDatasetModal = forwardRef{items[1].title} {items[1].description} + + + + {items[2].title} + {items[2].description} +
diff --git a/web/src/views/KnowledgeBase/components/RecallTestResult.tsx b/web/src/views/KnowledgeBase/components/RecallTestResult.tsx index 365356e2..f2a16368 100644 --- a/web/src/views/KnowledgeBase/components/RecallTestResult.tsx +++ b/web/src/views/KnowledgeBase/components/RecallTestResult.tsx @@ -7,20 +7,22 @@ * @LastEditTime: 2025-12-22 13:47:53 */ import { FileOutlined, FieldTimeOutlined, EditOutlined } from '@ant-design/icons'; -import { Skeleton, Flex, Space } from 'antd'; +import { Skeleton, Flex, Space, App } from 'antd'; import { useTranslation } from 'react-i18next'; import type { RecallTestData } from '@/views/KnowledgeBase/types'; import { NoData } from './noData'; import { formatDateTime } from '@/utils/format'; import InfiniteScroll from 'react-infinite-scroll-component'; import RbMarkdown from '@/components/Markdown'; -import { useMemo } from 'react'; +import { useMemo, type MouseEvent } from 'react'; +import { deleteDocumentChunk } from '@/api/knowledgeBase' interface RecallTestResultProps { data: RecallTestData[]; showEmpty?: boolean; hasMore?: boolean; loadMore?: () => void; + refresh?: () => void; loading?: boolean; scrollableTarget?: string; editable?: boolean; // Whether editable @@ -34,6 +36,7 @@ const RecallTestResult = ({ showEmpty = true, hasMore = false, loadMore, + refresh, loading = false, scrollableTarget, editable = false, @@ -42,6 +45,7 @@ const RecallTestResult = ({ handleCopy, }: RecallTestResultProps) => { const { t } = useTranslation(); + const { modal, message } = App.useApp() console.log('chunk data', data) // Parse QA format content @@ -133,6 +137,24 @@ const RecallTestResult = ({ return 'rb:text-[#FF5D34]'; } }; + const handleDelete = (e: MouseEvent, item: RecallTestData) => { + e.preventDefault(); + e.stopPropagation(); + modal.confirm({ + title: t('common.confirmDeleteDesc', { name: `chunk_${item.metadata?.sort_id}` }), + okText: t('common.delete'), + cancelText: t('common.cancel'), + okType: 'danger', + onOk: () => { + deleteDocumentChunk(item.metadata.knowledge_id, item.metadata.document_id, item.metadata.doc_id) + .then(() => { + message.success(t('common.deleteSuccess')); + refresh?.() + }) + } + }) + console.log('RecallTestData', item) + } // Show skeleton when initial loading if (loading && data.length === 0) { @@ -186,17 +208,21 @@ const RecallTestResult = ({ {scorePercentage.toFixed(1)}% {t('knowledgeBase.similarity')} )} -
+
{item.metadata?.file_name || '-'} - + chunk_{item.metadata?.sort_id || index} +
handleDelete(e, item)} + >
-
+
{(() => { const qaContent = parseQAContent(item.page_content); if (qaContent) { @@ -239,7 +265,7 @@ const RecallTestResult = ({
{t('knowledgeBase.recallResult')} - + ({data.length} results)
@@ -262,7 +288,7 @@ const RecallTestResult = ({
{t('knowledgeBase.recallResult')} - + ({data.length} results)
diff --git a/web/src/views/KnowledgeBase/index.tsx b/web/src/views/KnowledgeBase/index.tsx index 3110ff63..21d256fe 100644 --- a/web/src/views/KnowledgeBase/index.tsx +++ b/web/src/views/KnowledgeBase/index.tsx @@ -16,6 +16,7 @@ import RbCard from '@/components/RbCard/Card' import SearchInput from '@/components/SearchInput' import Empty from '@/components/Empty' import { getKnowledgeBaseList, getModelList, getModelTypeList, deleteKnowledgeBase, getKnowledgeBaseTypeList } from '@/api/knowledgeBase' +import copy from 'copy-to-clipboard' import InfiniteScroll from 'react-infinite-scroll-component'; @@ -527,6 +528,10 @@ const KnowledgeBaseManagement: FC = () => { fetchData(1, false); } }, [modelTypes, query.parent_id, query.keywords, query.orderby, query.desc]) + const handleCopy = (value: string) => { + copy(value) + messageApi.success(t('common.copySuccess')) + } return ( <> @@ -595,6 +600,13 @@ const KnowledgeBaseManagement: FC = () => {
+
handleCopy(item.id)}> +
ID:
+ + {item.id} + + +
{item.descriptionItems?.map((description: Record) => (
void;