Merge pull request #1043 from SuanmoSuanyangTechnology/feature/KnowledgeBase_zy
Feature/knowledge base zy
This commit is contained in:
@@ -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<string, string> = {};
|
||||
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);
|
||||
|
||||
@@ -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<TagProps> = ({ color = 'processing', children, className, variant = 'outline' }) => {
|
||||
const Tag: FC<TagProps> = ({ color = 'processing', children, className, variant = 'outline', onClick }) => {
|
||||
return (
|
||||
<span className={`rb:inline-block rb:px-1 rb:py-0.5 rb:rounded-sm rb:text-[12px] rb:font-regular! rb:leading-4 rb:border ${colors[color]} ${className || ''} ${variant === 'borderless' ? 'rb:border-none!' : ''}`}>
|
||||
<span onClick={onClick} className={`rb:inline-block rb:px-1 rb:py-0.5 rb:rounded-sm rb:text-[12px] rb:font-regular! rb:leading-4 rb:border ${colors[color]} ${className || ''} ${variant === 'borderless' ? 'rb:border-none!' : ''}`}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -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',
|
||||
@@ -1281,13 +1283,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',
|
||||
|
||||
@@ -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: '文件名称',
|
||||
@@ -663,13 +666,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: '分享版本',
|
||||
|
||||
@@ -131,7 +131,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.retrieve_type_desc')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
|
||||
|
||||
<Select
|
||||
options={retrieveTypes.map(key => ({
|
||||
label: t(`application.${key}`),
|
||||
@@ -154,33 +154,35 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
onChange={(value) => form.setFieldValue('top_k', value)}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* Semantic similarity threshold */}
|
||||
{/* Vector similarity weight */}
|
||||
{values?.retrieve_type === 'semantic' && (
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* Word segmentation matching threshold */}
|
||||
{values?.retrieve_type === 'participle' && (
|
||||
<FormItem
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* Semantic similarity threshold */}
|
||||
{values?.retrieve_type === 'participle' && (
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -193,10 +195,11 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.similarity_threshold_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem
|
||||
@@ -205,10 +208,11 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.vector_similarity_weight_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
</>
|
||||
|
||||
@@ -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<ReturnType<typeof setInterval> | null>(null);
|
||||
const [delimiter, setDelimiter] = useState<string | undefined>(undefined);
|
||||
const [blockSize, setBlockSize] = useState<number>(130);
|
||||
const [qaPrompt, setQaPrompt] = useState<string | undefined>()
|
||||
console.log('qaPrompt', qaPrompt)
|
||||
const [processingMethod, setProcessingMethod] = useState<ProcessingMethod>('directBlock');
|
||||
const [parameterSettings, setParameterSettings] = useState<ParameterSettings>('defaultSettings');
|
||||
const [pdfEnhancementEnabled, setPdfEnhancementEnabled] = useState<boolean>(true);
|
||||
const [pdfEnhancementMethod, setPdfEnhancementMethod] = useState<string>('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 = () => {
|
||||
<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>
|
||||
</div>
|
||||
<div className='rb:px-24 rb:py-5 rb:bg-white rb:rounded-xl'>
|
||||
{source !== 'csv' && <div className='rb:px-24 rb:py-5 rb:bg-white rb:rounded-xl'>
|
||||
<Steps current={current} items={steps} className="custom-steps" />
|
||||
</div>
|
||||
</div> }
|
||||
<div className='rb:bg-white rb:rounded-xl rb:flex-1 rb:mt-3'>
|
||||
|
||||
{current === 0 && (
|
||||
<div className='rb:flex rb:w-full rb:p-6'>
|
||||
{source && source === 'local' && (
|
||||
{source && (source === 'local' || source === 'csv') && (
|
||||
<UploadFiles
|
||||
ref={uploadRef}
|
||||
isCanDrag={true}
|
||||
fileSize={100}
|
||||
multiple={true}
|
||||
maxCount={99}
|
||||
fileType={fileType}
|
||||
multiple={source !== 'csv'}
|
||||
maxCount={source === 'csv' ? 1 : 99}
|
||||
fileType={source === 'csv' ? csvFileType : fileType}
|
||||
customRequest={handleUpload}
|
||||
onChange={(fileList) => {
|
||||
console.log('File list changed:', fileList);
|
||||
@@ -765,18 +799,23 @@ const CreateDataset = () => {
|
||||
</Flex>
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
{parameterSettings === 'customSettings' && (
|
||||
{parameterSettings === 'customSettings' && (<>
|
||||
<div className='rb:grid rb:grid-cols-2 rb:mt-5 rb-border rb:rounded-xl rb:px-6 rb:py-4 rb:gap-10'>
|
||||
<div>
|
||||
<div className='rb:w-full rb:text-[#5B6167] rb:leading-5 rb:mb-2'>
|
||||
{t('knowledgeBase.delimiter')}
|
||||
</div>
|
||||
<DelimiterSelector value={delimiter} onChange={setDelimiter} />
|
||||
<div>
|
||||
<div className='rb:w-full rb:text-[#5B6167] rb:leading-5 rb:mb-2'>
|
||||
{t('knowledgeBase.delimiter')}
|
||||
</div>
|
||||
<DelimiterSelector value={delimiter} onChange={setDelimiter} />
|
||||
</div>
|
||||
<SliderInput label={t('knowledgeBase.suggestedBlockSize')} max={1024} min={1} step={1} value={blockSize} onChange={handleChange} />
|
||||
</div>
|
||||
|
||||
)}
|
||||
<div>
|
||||
<div className='rb:w-full rb:text-[#5B6167] rb:leading-5 rb:mb-2 rb:mt-4'>
|
||||
{t('knowledgeBase.qaPrompt')}
|
||||
</div>
|
||||
<Input.TextArea value={qaPrompt} rows={6} onChange={(e) => setQaPrompt(e.target.value)} />
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -853,7 +892,7 @@ const CreateDataset = () => {
|
||||
{t('common.previous') || 'Prev'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
{source !== 'csv' && <Button
|
||||
type='primary'
|
||||
onClick={current === 2 ? handleStartUpload : handleNext}
|
||||
disabled={
|
||||
@@ -863,7 +902,7 @@ const CreateDataset = () => {
|
||||
}
|
||||
>
|
||||
{current === 2 ? t('knowledgeBase.startUploading') || 'Start Upload' : t('common.next') || 'Next'}
|
||||
</Button>
|
||||
</Button>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,8 @@ 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 { getDocumentDetail, getDocumentChunkList, downloadFile, updateDocument, updateDocumentChunk, createDocumentChunk, getFileUrl } from '@/api/knowledgeBase';
|
||||
import { Button, Spin, message, Switch, App } from 'antd';
|
||||
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,10 +20,11 @@ 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();
|
||||
const navigate = useNavigate();
|
||||
const { message: messageApi } = App.useApp()
|
||||
const { knowledgeBaseId } = useParams<{ knowledgeBaseId: string }>();
|
||||
const location = useLocation();
|
||||
const { updateBreadcrumbs } = useBreadcrumbManager({
|
||||
@@ -100,9 +101,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: <span onClick={() => handleCopy(doc.file_id)}>
|
||||
{doc.file_id}
|
||||
<span
|
||||
className="rb:cursor-pointer rb:-mb-0.5 rb:ml-1 rb:inline-block rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"
|
||||
></span>
|
||||
</span>,
|
||||
},
|
||||
{
|
||||
key: 'file_name',
|
||||
label: t('knowledgeBase.fileName') || '文件名',
|
||||
@@ -210,6 +227,11 @@ const DocumentDetails: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const refreshChunks = () => {
|
||||
let nextPage = 1;
|
||||
setPage(nextPage);
|
||||
ChunkList(nextPage);
|
||||
}
|
||||
const loadMoreChunks = () => {
|
||||
const nextPage = page + 1;
|
||||
setPage(nextPage);
|
||||
@@ -345,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}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -387,7 +409,7 @@ const DocumentDetails: FC = () => {
|
||||
<div className="rb:flex rb:h-full rb:flex-1 rb:overflow-hidden rb:bg-white rb:rounded-xl rb:border rb:border-[#DFE4ED]">
|
||||
{/* 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:h-full rb:border-r rb:border-[#DFE4ED] rb:p-4'>
|
||||
<div className='rb:h-full rb:border-r rb:border-[#DFE4ED] rb:p-4 rb:overflow-y-auto'>
|
||||
<InfoPanel
|
||||
title={t('knowledgeBase.documentInfo') || '文档信息'}
|
||||
items={infoItems}
|
||||
@@ -407,7 +429,7 @@ const DocumentDetails: FC = () => {
|
||||
{t('knowledgeBase.chunkList') || '分块列表'}
|
||||
</h2>
|
||||
<RecallTestResult
|
||||
|
||||
refresh={refreshChunks}
|
||||
data={chunkList}
|
||||
showEmpty={false}
|
||||
hasMore={hasMore}
|
||||
@@ -417,6 +439,7 @@ const DocumentDetails: FC = () => {
|
||||
editable={true}
|
||||
onItemClick={handleChunkClick}
|
||||
parserMode={parserMode}
|
||||
handleCopy={handleCopy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<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"
|
||||
className="rb:inline-block rb:w-1.25 rb:h-1.25 rb:mr-2 rb:rounded-full"
|
||||
style={{ backgroundColor: value === 1 ? '#369F21' : value === 0 ? '#FF0000' : '#FF8A4C' }}
|
||||
></span>
|
||||
<span>{value === 1 ? t('knowledgeBase.completed') : value === 0 ? t('knowledgeBase.pending') : t('knowledgeBase.processing')}</span>
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className="rb:flex rb:h-full rb:bg-white rb:rounded-xl">
|
||||
{folder && (
|
||||
<div className="rb:w-64 rb:py-4 rb:flex-shrink-0 rb:h-[calc(100%+40px)] rb:border-r rb:border-[#EAECEE] rb:p-4 rb:bg-transparent">
|
||||
<div className="rb:w-64 rb:py-4 rb:shrink-0 rb:h-[calc(100%+40px)] rb:border-r rb:border-[#EAECEE] rb:p-4 rb:bg-transparent">
|
||||
<FolderTree
|
||||
multiple
|
||||
className="customTree"
|
||||
@@ -791,11 +804,15 @@ const Private: FC = () => {
|
||||
<div className="rb:flex rb:items-center rb:border rb:border-[rgba(33, 35, 50, 0.17)] rb:text-gray-500 rb:cursor-pointer rb:px-1 rb:py-0.5 rb:rounded"
|
||||
onClick={handleEditFolder}
|
||||
>
|
||||
<img src={editIcon} alt="edit" className="rb:w-[14px] rb:h-[14px" />
|
||||
<img src={editIcon} alt="edit" className="rb:w-3.5 rb:h-[14px" />
|
||||
<span className='rb:text-[12px]'>{t('knowledgeBase.edit')} {t('knowledgeBase.name')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rb:flex rb:items-center rb:gap-6 rb:text-gray-500 rb:mt-2 rb:text-xs'>
|
||||
<div className='rb:flex rb:items-center rb:gap-6 rb:text-gray-500 rb:mt-2 rb:text-xs'>
|
||||
<Tag variant="borderless" color="default" className="rb:cursor-pointer" onClick={() => handleCopy(knowledgeBase.id)}>
|
||||
ID: {knowledgeBase.id}
|
||||
<span className="rb:-mb-0.5 rb:ml-1 rb:inline-block rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></span>
|
||||
</Tag>
|
||||
<span className='rb:text-[12px]'>{t('knowledgeBase.created')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.created_at) || '-'}</span>
|
||||
<span className='rb:text-[12px]'>{t('knowledgeBase.updated')} {t('knowledgeBase.time')}: {formatDateTime(knowledgeBase.updated_at) || '-'}</span>
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
||||
title: t('knowledgeBase.customText'),
|
||||
description: t('knowledgeBase.manuallyInputText')
|
||||
},
|
||||
{
|
||||
title: t('knowledgeBase.csvFile'),
|
||||
description: t('knowledgeBase.csvUploadFileTypes')
|
||||
},
|
||||
]
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
@@ -86,7 +90,7 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
||||
// description: selected.description,
|
||||
// });
|
||||
// 跳转到创建数据集页面并携带来源参数
|
||||
const source = value === 0 ? 'local' : value === 1 ? 'link' : 'text';
|
||||
const source = value === 3 ? 'csv' : value === 0 ? 'local' : value === 1 ? 'link' : 'text';
|
||||
if (knowledgeBaseId) {
|
||||
navigate(`/knowledge-base/${knowledgeBaseId}/create-dataset`,{
|
||||
state: {
|
||||
@@ -139,6 +143,12 @@ const CreateDatasetModal = forwardRef<CreateDatasetModalRef,CreateDatasetModalRe
|
||||
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[1].title}</span>
|
||||
<span className='rb:text-xs rb:text-gray-500'>{items[1].description}</span>
|
||||
</Flex>
|
||||
</Radio>
|
||||
<Radio value={3} style={getActiveRadioStyle(value === 3)} className='rb:w-full'>
|
||||
<Flex gap="small" align='start' justify='start' vertical>
|
||||
<span className='rb:text-base rb:font-medium rb:text-gray-800'>{items[2].title}</span>
|
||||
<span className='rb:text-xs rb:text-gray-500'>{items[2].description}</span>
|
||||
</Flex>
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -266,6 +266,8 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
|
||||
}
|
||||
}, [nodes])
|
||||
|
||||
console.log('selectedNode', selectedNode)
|
||||
|
||||
return (
|
||||
<Col span={24}>
|
||||
<RbCard
|
||||
|
||||
@@ -7,25 +7,28 @@
|
||||
* @LastEditTime: 2025-12-22 13:47:53
|
||||
*/
|
||||
import { FileOutlined, FieldTimeOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { Skeleton } 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
|
||||
onItemClick?: (item: RecallTestData, index: number) => void; // Click item callback
|
||||
parserMode?: number; // Parser mode, 1 means QA format
|
||||
handleCopy?: (text?: string) => void;
|
||||
}
|
||||
|
||||
const RecallTestResult = ({
|
||||
@@ -33,13 +36,17 @@ const RecallTestResult = ({
|
||||
showEmpty = true,
|
||||
hasMore = false,
|
||||
loadMore,
|
||||
refresh,
|
||||
loading = false,
|
||||
scrollableTarget,
|
||||
editable = false,
|
||||
onItemClick,
|
||||
parserMode = 0,
|
||||
handleCopy,
|
||||
}: RecallTestResultProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { modal, message } = App.useApp()
|
||||
console.log('chunk data', data)
|
||||
|
||||
// Parse QA format content
|
||||
const parseQAContent = (content: string) => {
|
||||
@@ -130,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) {
|
||||
@@ -183,17 +208,21 @@ const RecallTestResult = ({
|
||||
{scorePercentage.toFixed(1)}% {t('knowledgeBase.similarity')}
|
||||
</span>
|
||||
)}
|
||||
<div className={`rb:flex rb:mt-2 rb:flex rb:items-end rb:justify-end rb:gap-4 ${!showScore ? 'rb:w-full' : ''}`}>
|
||||
<div className={`rb:flex rb:mt-2 rb:items-end rb:justify-end rb:gap-4 ${!showScore ? 'rb:w-full' : ''}`}>
|
||||
<span className='rb:text-gray-800'>
|
||||
<FileOutlined /> {item.metadata?.file_name || '-'}
|
||||
</span>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:bg-[#DFDFDF] rb:px-1 rb:py-[2px] rb:rounded'>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:bg-[#DFDFDF] rb:px-1 rb:py-0.5 rb:rounded'>
|
||||
chunk_{item.metadata?.sort_id || index}
|
||||
</span>
|
||||
<div
|
||||
className="rb:size-5 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/common/delete.svg')] rb:hover:bg-[url('@/assets/images/common/delete_hover.svg')]"
|
||||
onClick={(e) => handleDelete(e, item)}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rb:flex rb:text-left rb:px-4 rb:py-3 rb:bg-white rb:rounded-lg rb:mt-2'>
|
||||
<div className='rb:text-gray-800 rb:text-sm rb:whitespace-pre-wrap rb:break-words rb:w-full'>
|
||||
<div className='rb:text-gray-800 rb:text-sm rb:whitespace-pre-wrap rb:wrap-break-word rb:w-full'>
|
||||
{(() => {
|
||||
const qaContent = parseQAContent(item.page_content);
|
||||
if (qaContent) {
|
||||
@@ -204,13 +233,21 @@ const RecallTestResult = ({
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
{item.metadata?.file_created_at && (
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:mt-3'>
|
||||
<span className='rb:text-gray-500 rb:text-xs'>
|
||||
<FieldTimeOutlined /> {formatDateTime(item.metadata.file_created_at)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Flex align="center" justify={item.metadata?.file_created_at ? 'space-between' : 'end'} className="rb:mt-3!">
|
||||
{item.metadata?.file_created_at && (
|
||||
<div className='rb:flex rb:items-center rb:justify-start'>
|
||||
<span className='rb:text-gray-500 rb:text-xs'>
|
||||
<FieldTimeOutlined /> {formatDateTime(item.metadata.file_created_at)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<Space align="center" className='rb:text-gray-500 rb:text-xs' onClick={() => handleCopy?.(item.metadata?.doc_id)}>
|
||||
ID: {item.metadata?.doc_id}
|
||||
<span
|
||||
className="rb:cursor-pointer rb:inline-block rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"
|
||||
></span>
|
||||
</Space>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -228,7 +265,7 @@ const RecallTestResult = ({
|
||||
<div className='rb:flex rb:h-full rb:flex-col'>
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:gap-2'>
|
||||
<span className='rb:text-lg rb:font-medium'>{t('knowledgeBase.recallResult')}</span>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:pt-[2px]'>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:pt-0.5'>
|
||||
(<span className='rb:text-[#155EEF]'>{data.length}</span> results)
|
||||
</span>
|
||||
</div>
|
||||
@@ -245,12 +282,13 @@ const RecallTestResult = ({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Otherwise use normal rendering
|
||||
return (
|
||||
<div className='rb:flex rb:flex-col'>
|
||||
<div className='rb:flex rb:items-center rb:justify-start rb:gap-2'>
|
||||
<span className='rb:text-lg rb:font-medium'>{t('knowledgeBase.recallResult')}</span>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:pt-[2px]'>
|
||||
<span className='rb:text-gray-500 rb:text-xs rb:pt-0.5'>
|
||||
(<span className='rb:text-[#155EEF]'>{data.length}</span> results)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
@@ -574,6 +579,8 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
title={item.name}
|
||||
headerType="borderless"
|
||||
headerClassName="rb:py-3!"
|
||||
className="rb:cursor-pointer"
|
||||
onClick={() => handleToDetail(item)}
|
||||
extra={
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Dropdown
|
||||
@@ -585,7 +592,7 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className='' onClick={() => handleToDetail(item)}>
|
||||
<div className=''>
|
||||
<div className="rb:flex rb:text-[#5B6167] rb:h-5 rb:line-clamp-1 rb:text-sm rb:leading-5 rb:mb-3">
|
||||
{/* <div className="rb:font-medium rb:w-20">{t('knowledgeBase.description')} </div> */}
|
||||
<Tooltip title={item.description}>
|
||||
@@ -593,6 +600,13 @@ const KnowledgeBaseManagement: FC = () => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Flex vertical gap={4} className='rb:min-h-15 rb:py-2.5! rb:px-3! rb:bg-[#F6F6F6] rb:rounded-lg rb:mb-3'>
|
||||
<div className="rb:cursor-pointer rb:mb-3 rb:w-full" onClick={() => handleCopy(item.id)}>
|
||||
<div className="rb:text-gray-800 rb:font-medium">ID:</div>
|
||||
<Flex align="center" className="rb:text-[#5B6167]">
|
||||
{item.id}
|
||||
<span className="rb:ml-1 rb:inline-block rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></span>
|
||||
</Flex>
|
||||
</div>
|
||||
{item.descriptionItems?.map((description: Record<string, unknown>) => (
|
||||
<div
|
||||
key={description.key as string}
|
||||
|
||||
@@ -95,7 +95,7 @@ export interface ParserConfig {
|
||||
auto_keywords?: number; // 自动关键词
|
||||
auto_questions?: number; // 自动问题
|
||||
html4excel?: boolean; // 是否为Excel文件
|
||||
graphrag: GraphragConfig; // 知识图谱生成
|
||||
graphrag?: GraphragConfig; // 知识图谱生成
|
||||
|
||||
// Web 类型特有字段
|
||||
entry_url?: string; // 入口网址
|
||||
@@ -135,6 +135,7 @@ export interface KnowledgeBaseDocumentData { // 知识库文档数据
|
||||
status?: number; // 状态 1 可检索 0 不可检索
|
||||
created_at?: string; // 创建时间
|
||||
updated_at?: string; // 更新时间
|
||||
qa_prompt?: string; // 提示词
|
||||
}
|
||||
export interface DocumentModalRef {
|
||||
handleOpen: (file?: KnowledgeBaseDocumentData | null) => void;
|
||||
|
||||
@@ -95,7 +95,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
<Flex align="center" justify="space-between" className="rb:mb-6! rb-border rb:rounded-lg rb:p-[17px_16px]! rb:cursor-pointer rb:bg-[#F0F3F8] rb:text-[#212332]">
|
||||
<div className="rb:text-[16px] rb:leading-5.5">
|
||||
{data.name}
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167] rb:mt-2">{t('application.contains', {include_count: data.doc_num})}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167] rb:mt-2">{t('application.contains', { include_count: data.doc_num })}</div>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167]">{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
</Flex>
|
||||
@@ -108,13 +108,12 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.retrieve_type_desc')}
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
|
||||
|
||||
<Select
|
||||
options={retrieveTypes.map(key => ({
|
||||
label: t(`application.${key}`),
|
||||
value: key,
|
||||
}))}
|
||||
// onChange={handleChange}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* Top K */}
|
||||
@@ -128,34 +127,18 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
style={{ width: '100%' }}
|
||||
min={1}
|
||||
max={20}
|
||||
// onChange={(value) => form.setFieldValue('top_k', value)}
|
||||
onChange={(value) => form.setFieldValue('top_k', value)}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* 语义相似度阈值 similarity_threshold */}
|
||||
{/* Vector similarity weight */}
|
||||
{values?.retrieve_type === 'semantic' && (
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 分词匹配度阈值 vector_similarity_weight */}
|
||||
{values?.retrieve_type === 'participle' && (
|
||||
<FormItem
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
@@ -163,7 +146,23 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* 混合检索权重 */}
|
||||
{/* similarity threshold */}
|
||||
{values?.retrieve_type === 'participle' && (
|
||||
<FormItem
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
isInput={true}
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
{/* Hybrid retrieval weight */}
|
||||
{values?.retrieve_type === 'hybrid' && (
|
||||
<>
|
||||
<FormItem
|
||||
@@ -172,7 +171,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.similarity_threshold_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
@@ -185,7 +184,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
extra={t('application.vector_similarity_weight_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
step={0.1}
|
||||
min={0.0}
|
||||
|
||||
@@ -1642,7 +1642,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, }
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user