Merge branch 'feature/ui_yjp' into feature/ui_upgrade_zy

This commit is contained in:
yujiangping
2026-03-17 15:41:58 +08:00
14 changed files with 398 additions and 333 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="48px" height="48px" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 12</title>
<g id="空间里层页面优化" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="工作台-知识库-创建数据集-本地文件" transform="translate(-816, -184)">
<g id="上传" transform="translate(252, 148)">
<g id="编组-13" transform="translate(16, 16)">
<g id="编组-12" transform="translate(548, 20)">
<g id="编组-55" transform="translate(5, 2)">
<path d="M4,0 L18.9462071,0 L18.9462071,0 L29,10 L29,34 C29,36.209139 27.209139,38 25,38 L4,38 C1.790861,38 0,36.209139 0,34 L0,4 C0,1.790861 1.790861,4.4408921e-16 4,0 Z" id="矩形" fill="#EBEBEB"></path>
<path d="M19,0 L29,10 L21,10 C19.8954305,10 19,9.1045695 19,8 L19,0 L19,0 Z" id="矩形" fill="#A8A9AA"></path>
<g id="编组-56" transform="translate(18, 23)">
<circle id="椭圆形" fill="#171719" cx="11" cy="11" r="11"></circle>
<g id="编组-54" transform="translate(6, 6)" stroke="#FFFFFF" stroke-linecap="round">
<path d="M10,6 L10,7.5 C10,8.88071187 8.88071187,10 7.5,10 L2.5,10 C1.11928813,10 0,8.88071187 0,7.5 L0,6 L0,6" id="路径"></path>
<line x1="5" y1="0.08499952" x2="5" y2="6.99635859" id="路径-24"></line>
<polyline id="路径-25" stroke-linejoin="round" points="2 3 4.98005548 6.08298138e-18 8 3"></polyline>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -240,10 +240,10 @@ const UploadFiles = forwardRef<UploadFilesRef, UploadFilesProps>(({
if (isCanDrag) {
return (
<div className="rb:mb-6 rb:w-full">
<div className="rb:mb-6 rb:w-full rb:border rb:border-dashed rb:border-gray-900 rb:rounded-xl">
<Dragger {...uploadProps} style={{ height: '270px' }}>
<div className="rb:flex rb:justify-center rb:flex-col rb:items-center">
<div className="rb:size-12 rb:bg-cover rb:bg-[url('@/assets/images/CloudUploadOutlined.png')]"></div>
<div className="rb:size-12 rb:bg-cover rb:bg-[url('@/assets/images/CloudUploadOutlined.svg')]"></div>
{(!isAutoUpload || !hasProgress && (!fileList || !fileList.length)) &&
<>
<div className="rb:text-base rb:text-[14px] rb:font-medium rb:flex rb:items-center rb:mt-2 rb:leading-5">

View File

@@ -633,6 +633,7 @@ export const en = {
documentInfo: 'Document Information',
documentPreview:'Document Preview',
type: 'Type',
viewBasicInfo: 'View Basic Information',
permission_id: 'Permission',
status: 'Status',
created_at: 'Created At',

View File

@@ -135,6 +135,7 @@ export const zh = {
documentInfo: '文档信息',
documentPreview: '文档预览',
type: '类型',
viewBasicInfo:'查看基本信息',
permission_id: '权限',
status: '状态',
created_at: '创建时间',

View File

@@ -2,6 +2,7 @@ import { useMemo,useRef, useState, useEffect } from 'react';
import { Button, Flex, Radio, Steps, Modal, Input, Spin, message, Checkbox, Select, Form, Progress} from 'antd';
import { useTranslation } from 'react-i18next';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import './Private.css';
import Table, { type TableRef } from '@/components/Table'
import type { AnyObject } from 'antd/es/_util/type';
import type { UploadFileResponse,KnowledgeBaseDocumentData } from '@/views/KnowledgeBase/types';
@@ -32,7 +33,7 @@ const { TextArea } = Input;
};
const getActiveRadioStyle = (active: boolean): React.CSSProperties => ({
...radioWrapperBaseStyle,
border: active ? '1px solid #1677ff' : radioWrapperBaseStyle.border,
border: active ? '1px solid #171719' : radioWrapperBaseStyle.border,
});
@@ -549,7 +550,7 @@ const CreateDataset = () => {
<>
{contextHolder}
<div className='rb:p-6 rb:pt-2 rb:h-full'>
<div className='rb:p-3 rb:pt-2 rb:h-full rb:flex rb:flex-col'>
{/* <Typography.Title level={4} className='rb:!m-0 rb:!mb-4'>
{t('knowledgeBase.createA') + ' ' + t('knowledgeBase.dataset')}
</Typography.Title> */}
@@ -557,298 +558,299 @@ 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-[#FBFDFF] rb:rounded-lg rb:border rb:border-[#DFE4ED]'>
<Steps current={current} items={steps} />
<div className='rb:px-24 rb:py-5 rb:bg-white rb:rounded-lg'>
<Steps current={current} items={steps} className="custom-steps" />
</div>
<div className='rb:bg-white rb:rounded-lg rb:flex-1 rb:mt-3'>
{current === 0 && (
<div className='rb:flex rb:w-full rb:mt-10'>
{source && source === 'local' && (
<UploadFiles
ref={uploadRef}
isCanDrag={true}
fileSize={100}
multiple={true}
maxCount={99}
fileType={fileType}
customRequest={handleUpload}
onChange={(fileList) => {
console.log('File list changed:', fileList);
}}
onRemove={async (file) => {
// 如果文件正在上传,取消上传
const fileUid = file.uid;
const abortController = abortControllersRef.current.get(fileUid);
if (abortController) {
abortController.abort();
abortControllersRef.current.delete(fileUid);
console.log('Upload cancelled:', (file as any).name);
// 取消上传后直接返回 true允许移除文件
return true;
}
// Only delete server file when file upload was successful (has response.id)
if (file.response?.id) {
try {
await deleteDocument(file.response.id);
setRechunkFileIds(prev => prev.filter(id => id !== file.response.id));
console.log('Server file deleted:', file.response.id);
{current === 0 && (
<div className='rb:flex rb:w-full rb:p-6'>
{source && source === 'local' && (
<UploadFiles
ref={uploadRef}
isCanDrag={true}
fileSize={100}
multiple={true}
maxCount={99}
fileType={fileType}
customRequest={handleUpload}
onChange={(fileList) => {
console.log('File list changed:', fileList);
}}
onRemove={async (file) => {
// 如果文件正在上传,取消上传
const fileUid = file.uid;
const abortController = abortControllersRef.current.get(fileUid);
if (abortController) {
abortController.abort();
abortControllersRef.current.delete(fileUid);
console.log('Upload cancelled:', (file as any).name);
// 取消上传后直接返回 true允许移除文件
return true;
} catch (error) {
console.error('Failed to delete file:', error);
messageApi.error(t('common.deleteFailed') || 'Failed to delete file');
return false; // Don't remove file when deletion fails
}
}
// Also allow removal in other cases (such as failed uploads)
return true;
}} />
)}
{source && source === 'link' && (
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
// Only delete server file when file upload was successful (has response.id)
if (file.response?.id) {
try {
await deleteDocument(file.response.id);
setRechunkFileIds(prev => prev.filter(id => id !== file.response.id));
console.log('Server file deleted:', file.response.id);
return true;
} catch (error) {
console.error('Failed to delete file:', error);
messageApi.error(t('common.deleteFailed') || 'Failed to delete file');
return false; // Don't remove file when deletion fails
}
}
// Also allow removal in other cases (such as failed uploads)
return true;
}} />
)}
{source && source === 'link' && (
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.webLink')}
</div>
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} />
<div className='rb:text-sm rb:text-gray-500 rb:mt-3'>
{t('knowledgeBase.webLinkDesc',{count: 5})}
</div>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
{t('knowledgeBase.selectorTutorial')}
</div>
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
</div>
)}
{source && source === 'text' && (
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
<Form form={form} layout="vertical">
<Form.Item
name="title"
label={t('knowledgeBase.title')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterTitle') }]}
>
<Input placeholder={t('knowledgeBase.pleaseEnterTitle')} />
</Form.Item>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.webLink')}
</div>
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} />
<div className='rb:text-sm rb:text-gray-500 rb:mt-3'>
{t('knowledgeBase.webLinkDesc',{count: 5})}
</div>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
{t('knowledgeBase.selectorTutorial')}
</div>
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
</div>
)}
{source && source === 'text' && (
<div className='rb:flex rb:w-full rb:flex-col rb:mt-10 rb:px-40'>
<Form form={form} layout="vertical">
<Form.Item
name="title"
label={t('knowledgeBase.title')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterTitle') }]}
>
<Input placeholder={t('knowledgeBase.pleaseEnterTitle')} />
</Form.Item>
<Form.Item
name="content"
label={t('knowledgeBase.customContent')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterContent') }]}
>
<Input.TextArea
placeholder={t('knowledgeBase.pleaseEnterContent')}
rows={8}
showCount
maxLength={5000}
/>
</Form.Item>
</Form>
{/* <div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.customText')}
</div>
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
{t('knowledgeBase.customContent')}
</div>
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} /> */}
</div>
)}
</div>
)}
<Form.Item
name="content"
label={t('knowledgeBase.customContent')}
rules={[{ required: true, message: t('knowledgeBase.pleaseEnterContent') }]}
>
<Input.TextArea
placeholder={t('knowledgeBase.pleaseEnterContent')}
rows={8}
showCount
maxLength={5000}
/>
</Form.Item>
</Form>
{/* <div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.customText')}
</div>
<Input className='rb:w-full' placeholder={t('knowledgeBase.webLinkPlaceholder')}/>
<div className='rb:text-sm rb:font-medium rb:text-gray-800 rb:mt-10 rb:mb-3'>
{t('knowledgeBase.customContent')}
</div>
<TextArea rows={6} placeholder={t('knowledgeBase.webLinkPlaceholder')} /> */}
</div>
)}
</div>
)}
{current === 1 && (
<div className='rb:flex rb:flex-col rb:mt-10 rb:px-40'>
{rechunkFileIds.length > 0 && (
<div className='rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded rb:px-3 rb:py-2 rb:mb-4 rb:text-xs rb:text-gray-600 rb:flex rb:flex-wrap rb:gap-2'>
<span className='rb:text-gray-700 rb:font-medium'>{t('knowledgeBase.rechunking')}:</span>
{rechunkFileIds.map((id) => (
<span key={id} className='rb:px-2 rb:py-0.5 rb:bg-white rb:border rb:border-[#DFE4ED] rb:rounded'>{id}</span>
))}
</div>
)}
<div className='rb:text-base rb:font-medium rb:text-gray-800 rb:mt-4'>
{t('knowledgeBase.fileParsingSettings')}
</div>
<div className='rb:mt-4'>
<div
className={`rb:flex rb:items-center rb:w-full rb:border rb:rounded-lg rb:p-4 rb:cursor-pointer ${
pdfEnhancementEnabled ? 'rb:border-blue-500' : 'rb:border-gray-300'
}`}
// onClick={() => setPdfEnhancementEnabled(!pdfEnhancementEnabled)}
>
<Checkbox
checked={pdfEnhancementEnabled}
onChange={(e) => setPdfEnhancementEnabled(e.target.checked)}
className='rb:mr-3'
/>
<span className='rb:text-base rb:font-medium rb:text-gray-800 rb:pl-[22px]'>
{t('knowledgeBase.pdfEnhancementAnalysis')}
</span>
{pdfEnhancementEnabled && (
<div className='rb:ml-10'>
<Select
value={pdfEnhancementMethod}
onChange={(value) => setPdfEnhancementMethod(value)}
className='rb:w-48'
options={[
{ value: 'deepdoc', label: 'DeepDoc' },
{ value: 'mineru', label: 'MinerU' },
{ value: 'textln', label: 'TextLN' }
]}
/>
{current === 1 && (
<div className='rb:flex rb:flex-col rb:py-6 rb:px-40'>
{rechunkFileIds.length > 0 && (
<div className='rb:bg-[#F0F3F8] rb:border rb:border-[#DFE4ED] rb:rounded rb:px-3 rb:py-2 rb:mb-4 rb:text-xs rb:text-gray-600 rb:flex rb:flex-wrap rb:gap-2'>
<span className='rb:text-gray-700 rb:font-medium'>{t('knowledgeBase.rechunking')}:</span>
{rechunkFileIds.map((id) => (
<span key={id} className='rb:px-2 rb:py-0.5 rb:bg-white rb:border rb:border-[#DFE4ED] rb:rounded'>{id}</span>
))}
</div>
)}
<div className='rb:text-base rb:font-medium rb:text-gray-800 rb:mt-4'>
{t('knowledgeBase.fileParsingSettings')}
</div>
</div>
<div className='rb:text-base rb:font-medium rb:text-gray-800 rb:mt-6'>
{t('knowledgeBase.dataProcessingSettings')}
</div>
<div className='rb:font-medium rb:text-gray-500 rb:mt-4 rb:mb-3'>
{t('knowledgeBase.processingMethod')}
</div>
<Radio.Group
value={processingMethod}
onChange={(e) => setProcessingMethod(e.target.value)}
style={style}
>
<Radio value='directBlock' style={getActiveRadioStyle(processingMethod === 'directBlock')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.directBlock')}
</span>
</Flex>
</Radio>
<Radio value='qaExtract' style={getActiveRadioStyle(processingMethod === 'qaExtract')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.qaExtract')}
</span>
</Flex>
</Radio>
</Radio.Group>
<div className='rb:font-medium rb:text-gray-500 rb:mt-4 rb:mb-3'>
{t('knowledgeBase.parameterSettings')}
</div>
<Radio.Group
value={parameterSettings}
onChange={(e) => setParameterSettings(e.target.value)}
style={style}
>
<Radio value='defaultSettings' style={getActiveRadioStyle(parameterSettings === 'defaultSettings')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.default')}
</span>
<span className='rb:text-3 rb:text-gray-500'>{t('knowledgeBase.defaultSettings')}</span>
</Flex>
</Radio>
<Radio value='customSettings' style={getActiveRadioStyle(parameterSettings === 'customSettings')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.customize')}
</span>
<span className='rb:text-3 rb:text-gray-500'>{t('knowledgeBase.customSettings')}</span>
</Flex>
</Radio>
</Radio.Group>
{parameterSettings === 'customSettings' && (
<div className='rb:flex rb:flex-col rb:mt-5'>
<div className='rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.delimiter')}
<div className='rb:mt-4'>
<div
className={`rb:flex rb:items-center rb:w-full rb:border rb:rounded-lg rb:p-4 rb:cursor-pointer ${
pdfEnhancementEnabled ? 'rb:border-gray-900' : 'rb:border-gray-300'
}`}
// onClick={() => setPdfEnhancementEnabled(!pdfEnhancementEnabled)}
>
<Checkbox
checked={pdfEnhancementEnabled}
onChange={(e) => setPdfEnhancementEnabled(e.target.checked)}
className='rb:mr-3'
/>
<span className='rb:text-base rb:font-medium rb:text-gray-800 rb:pl-[22px]'>
{t('knowledgeBase.pdfEnhancementAnalysis')}
</span>
{pdfEnhancementEnabled && (
<div className='rb:ml-10'>
<Select
value={pdfEnhancementMethod}
onChange={(value) => setPdfEnhancementMethod(value)}
className='rb:w-48'
options={[
{ value: 'deepdoc', label: 'DeepDoc' },
{ value: 'mineru', label: 'MinerU' },
{ value: 'textln', label: 'TextLN' }
]}
/>
</div>
<DelimiterSelector value={delimiter} onChange={setDelimiter} className='rb:mb-5'/>
<SliderInput label={t('knowledgeBase.suggestedBlockSize')} max={1024} min={1} step={1} value={blockSize} onChange={handleChange} />
)}
</div>
</div>
)}
</div>
)}
{/* 暂时隐藏第三步:数据预览 */}
{/* {current === stepIndexMap.dataPreview && (
<div className='rb:grid rb:grid-cols-2 rb:rounded-xl rb:border rb:border-[#DFE4ED] rb:h-[calc(100%-160px)] rb:bg-[#FBFDFF] rb:mt-4'>
<div className='rb:border-r rb:h-full rb:overflow-hidden rb:border-[#DFE4ED]'>
<div className='rb:h-11 rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:px-4 rb:py-3 rb:border-b rb:border-[#DFE4ED]'>
{t('knowledgeBase.fileList')}
</div>
<div className='rb:flex rb:flex-col rb:h-[calc(100%-44px)] rb:overflow-y-auto'>
{data.map((item, index) => (
<div key={index} className={`rb:h-11 rb:w-full rb:text-sm rb:text-gray-800 rb:px-4 rb:py-3 rb:hover:text-[#155EEF] rb:cursor-pointer ${curSelectedFileId === index ? styles.textBg + ' ' + styles.active : ''}`}
onClick={() => handlePreview(item, index)}>
{item.file_name}
</div>
))
}
</div>
</div>
<div className='rb:h-full rb:overflow-hidden'>
<div className='rb:flex rb:items-center rb:justify-between rb:h-11 rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:px-4 rb:py-3 rb:border-b rb:border-[#DFE4ED]'>
{t('knowledgeBase.dataPreview')}
<span className='rb:text-sm rb:text-gray-500'>{t('knowledgeBase.maxPreviewChunks', {count: total, max: chunkData.length})}</span>
</div>
<Spin spinning={previewLoading}>
<div className='rb:flex rb:flex-col rb:h-[calc(100%-44px)] rb:overflow-y-auto'>
{chunkData.length > 0 ? (
chunkData.map((item, index) => (
<div key={index} className='rb:text-sm rb:text-gray-800 rb:px-4 rb:py-3'
dangerouslySetInnerHTML={{ __html: item.page_content }}
/>
))
) : (
<NoData title={t('knowledgeBase.noChunksToPreview')}
subTitle={t('knowledgeBase.clickToPreview')}
image={noDataIcon}
/>
)}
<div className='rb:text-base rb:font-medium rb:text-gray-800 rb:mt-6'>
{t('knowledgeBase.dataProcessingSettings')}
</div>
<div className='rb:font-medium rb:text-gray-500 rb:mt-4 rb:mb-3'>
{t('knowledgeBase.processingMethod')}
</div>
<Radio.Group
value={processingMethod}
onChange={(e) => setProcessingMethod(e.target.value)}
style={style}
>
<Radio value='directBlock' style={getActiveRadioStyle(processingMethod === 'directBlock')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.directBlock')}
</span>
</Flex>
</Radio>
<Radio value='qaExtract' style={getActiveRadioStyle(processingMethod === 'qaExtract')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.qaExtract')}
</span>
</Flex>
</Radio>
</Radio.Group>
<div className='rb:font-medium rb:text-gray-500 rb:mt-4 rb:mb-3'>
{t('knowledgeBase.parameterSettings')}
</div>
<Radio.Group
value={parameterSettings}
onChange={(e) => setParameterSettings(e.target.value)}
style={style}
>
<Radio value='defaultSettings' style={getActiveRadioStyle(parameterSettings === 'defaultSettings')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.default')}
</span>
<span className='rb:text-3 rb:text-gray-500'>{t('knowledgeBase.defaultSettings')}</span>
</Flex>
</Radio>
<Radio value='customSettings' style={getActiveRadioStyle(parameterSettings === 'customSettings')}>
<Flex gap='small' vertical>
<span className='rb:text-base rb:font-medium rb:text-gray-800'>
{t('knowledgeBase.customize')}
</span>
<span className='rb:text-3 rb:text-gray-500'>{t('knowledgeBase.customSettings')}</span>
</Flex>
</Radio>
</Radio.Group>
{parameterSettings === 'customSettings' && (
<div className='rb:flex rb:flex-col rb:mt-5'>
<div className='rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:mb-3'>
{t('knowledgeBase.delimiter')}
</div>
</Spin>
</div>
</div>
)} */}
{current === 2 && (
// <Spin spinning={pollingLoading} tip={t('knowledgeBase.processingDocuments') || '正在处理文档...'}>
<div className='rb:text-sm rb:text-gray-500 rb:mt-4 rb:h-[calc(100%-160px)] rb:overflow-y-auto'>
{rechunkFileIds.length > 0 ? (
<Table
ref={tableRef}
apiUrl={`/documents/${knowledgeBaseId}/documents`}
apiParams={{
document_ids: rechunkFileIds.join(','),
}}
columns={columns}
rowKey="id"
/>
) : (
<Table
ref={tableRef}
columns={columns}
rowKey="id"
initialData={[]}
/>
)}
<DelimiterSelector value={delimiter} onChange={setDelimiter} className='rb:mb-5'/>
<SliderInput label={t('knowledgeBase.suggestedBlockSize')} max={1024} min={1} step={1} value={blockSize} onChange={handleChange} />
</div>
)}
</div>
// </Spin>
)}
<div className={`rb:flex rb:gap-3 rb:mt-6 ${current === 1 || (source == 'link' && current === 0) || (source == 'text' && current === 0) ? 'rb:pl-40 rb:mt-10' : ''}`}>
{current !== 0 && (
<Button onClick={handlePrev} disabled={current === 0 || pollingLoading}>
{t('common.previous') || 'Prev'}
</Button>
)}
<Button
type='primary'
onClick={current === 2 ? handleStartUpload : handleNext}
disabled={pollingLoading || (current === 0 && rechunkFileIds.length === 0)}
>
{current === 2 ? t('knowledgeBase.startUploading') || 'Start Upload' : t('common.next') || 'Next'}
</Button>
{/* 暂时隐藏第三步:数据预览 */}
{/* {current === stepIndexMap.dataPreview && (
<div className='rb:grid rb:grid-cols-2 rb:rounded-xl rb:border rb:border-[#DFE4ED] rb:h-[calc(100%-160px)] rb:bg-[#FBFDFF] rb:mt-4'>
<div className='rb:border-r rb:h-full rb:overflow-hidden rb:border-[#DFE4ED]'>
<div className='rb:h-11 rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:px-4 rb:py-3 rb:border-b rb:border-[#DFE4ED]'>
{t('knowledgeBase.fileList')}
</div>
<div className='rb:flex rb:flex-col rb:h-[calc(100%-44px)] rb:overflow-y-auto'>
{data.map((item, index) => (
<div key={index} className={`rb:h-11 rb:w-full rb:text-sm rb:text-gray-800 rb:px-4 rb:py-3 rb:hover:text-[#155EEF] rb:cursor-pointer ${curSelectedFileId === index ? styles.textBg + ' ' + styles.active : ''}`}
onClick={() => handlePreview(item, index)}>
{item.file_name}
</div>
))
}
</div>
</div>
<div className='rb:h-full rb:overflow-hidden'>
<div className='rb:flex rb:items-center rb:justify-between rb:h-11 rb:w-full rb:text-sm rb:font-medium rb:text-gray-800 rb:px-4 rb:py-3 rb:border-b rb:border-[#DFE4ED]'>
{t('knowledgeBase.dataPreview')}
<span className='rb:text-sm rb:text-gray-500'>{t('knowledgeBase.maxPreviewChunks', {count: total, max: chunkData.length})}</span>
</div>
<Spin spinning={previewLoading}>
<div className='rb:flex rb:flex-col rb:h-[calc(100%-44px)] rb:overflow-y-auto'>
{chunkData.length > 0 ? (
chunkData.map((item, index) => (
<div key={index} className='rb:text-sm rb:text-gray-800 rb:px-4 rb:py-3'
dangerouslySetInnerHTML={{ __html: item.page_content }}
/>
))
) : (
<NoData title={t('knowledgeBase.noChunksToPreview')}
subTitle={t('knowledgeBase.clickToPreview')}
image={noDataIcon}
/>
)}
</div>
</Spin>
</div>
</div>
)} */}
{current === 2 && (
// <Spin spinning={pollingLoading} tip={t('knowledgeBase.processingDocuments') || '正在处理文档...'}>
<div className='rb:text-sm rb:text-gray-500 rb:mt-4 rb:h-[calc(100%-160px)] rb:overflow-y-auto rb:px-6 rb:py-6'>
{rechunkFileIds.length > 0 ? (
<Table
ref={tableRef}
apiUrl={`/documents/${knowledgeBaseId}/documents`}
apiParams={{
document_ids: rechunkFileIds.join(','),
}}
columns={columns}
rowKey="id"
/>
) : (
<Table
ref={tableRef}
columns={columns}
rowKey="id"
initialData={[]}
/>
)}
</div>
// </Spin>
)}
<div className={`rb:flex rb:p-6 rb:gap-3 rb:mt-6 ${current === 1 || (source == 'link' && current === 0) || (source == 'text' && current === 0) ? 'rb:pl-40 rb:mt-10' : ''}`}>
{current !== 0 && (
<Button onClick={handlePrev} disabled={current === 0 || pollingLoading}>
{t('common.previous') || 'Prev'}
</Button>
)}
<Button
type='primary'
onClick={current === 2 ? handleStartUpload : handleNext}
disabled={pollingLoading || (current === 0 && rechunkFileIds.length === 0)}
>
{current === 2 ? t('knowledgeBase.startUploading') || 'Start Upload' : t('common.next') || 'Next'}
</Button>
</div>
</div>
</div>
</>);

View File

@@ -16,6 +16,7 @@
.customTree .ant-tree-node-content-wrapper {
background: transparent !important;
background-color: transparent !important;
color: #171719 !important;
height: 40px !important;
display: flex !important;
align-items: center !important;
@@ -28,15 +29,18 @@
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper:before,
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper:hover:before {
background: #FFFFFF !important;
background: #171719 !important;
}
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper,
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-node-content-wrapper:hover{
color: #000 !important;
color: #FFFFFF !important;
}
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher,
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-draggable-icon{
color: #000;
color: #FFFFFF;
}
.customTree.ant-tree.ant-tree-directory .ant-tree-treenode-selected .ant-tree-switcher img {
filter: brightness(0) invert(1) !important;
}
.customTree .ant-tree-switcher {
display: flex !important;
@@ -111,3 +115,8 @@
/* Steps 已完成步骤圆内字体颜色 */
.custom-steps .ant-steps-item-finish .ant-steps-item-icon .ant-steps-icon {
color: #FFFFFF !important;
}

View File

@@ -530,7 +530,7 @@ const Private: FC = () => {
const document = record as KnowledgeBaseDocumentData;
return (
<span
className="rb:text-blue-600 rb:cursor-pointer rb:hover:underline"
className="rb:text-gray-900 rb:font-medium rb:cursor-pointer rb:hover:underline"
onClick={() => {
if (knowledgeBaseId && document.id) {
navigate(`/knowledge-base/${knowledgeBaseId}/DocumentDetails`,{
@@ -765,9 +765,9 @@ const Private: FC = () => {
return (
<>
{contextHolder}
<div className="rb:flex rb:h-full rb:gap-4">
<div className="rb:flex rb:h-full rb:bg-white rb:rounded-xl">
{folder && (
<div className="rb:w-64 rb:flex-shrink-0 rb:h-[calc(100%+40px)] rb:mt-[-16px] rb:border-r rb:border-[#EAECEE] rb:p-4 rb:bg-transparent">
<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">
<FolderTree
multiple
className="customTree"
@@ -783,7 +783,7 @@ const Private: FC = () => {
/>
</div>
)}
<div className='rb:flex-1 rb:min-w-0'>
<div className='rb:flex-1 rb:min-w-0 rb:p-4'>
<div className="rb:flex rb:items-center rb:justify-between rb:mb-4">
<div className="rb:flex-col">

View File

@@ -1,6 +1,7 @@
import { useEffect, useState, useRef, type FC } from 'react';
import { useParams, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Popover } from 'antd';
import type { KnowledgeBaseListItem, RecallTestDrawerRef } from '@/views/KnowledgeBase/types';
import RecallTest from '../components/RecallTest';
import InfoPanel, { type InfoItem } from '../components/InfoPanel';
@@ -27,7 +28,7 @@ const Share: FC = () => {
const recallTestRef = useRef<RecallTestDrawerRef>(null);
const [infoItems, setInfoItems] = useState<InfoItem[]>([]);
const [knowledgeBaseFolderPath, setKnowledgeBaseFolderPath] = useState<BreadcrumbItem[]>([]);
const [viewInfo,setViewInfo] = useState<boolean>(false);
const { updateBreadcrumbs } = useBreadcrumbManager({
breadcrumbType: 'detail'
});
@@ -160,7 +161,17 @@ const Share: FC = () => {
<span className='rb:text-gray-500 rb:text-sm rb:ml-2 rb:font-normal'>(ID: {knowledgeBase.id})</span></h1>
{/* <p className="rb:text-gray-600 rb:mt-2">{knowledgeBase.description || t('knowledgeBase.noDescription')}</p> */}
<span className='rb:text-gray-800 rb:text-xs rb:border rb:border-[#369F21] rb:bg-[rgba(54,159,33,0.2)] rb:px-1 rb:py-[2px] rb:rounded'>{knowledgeBase.permission_id}</span>
<span className='rb:text-white rb:text-xs rb:bg-blue-500 rb:px-1 rb:py-[2px] rb:rounded'>{knowledgeBase.permission_id}</span>
<Popover
content={<InfoPanel title={t('knowledgeBase.knowledgeBaseInfo')} items={infoItems} />}
trigger="hover"
placement="bottomLeft"
arrow={false}
>
<span
className='rb:border rb:border-[#171719] rb:px-1 rb:py-[2px] rb:rounded rb:text-gray-900 rb:text-xs rb:cursor-pointer'
>{t('knowledgeBase.viewBasicInfo')}</span>
</Popover>
</div>
<div className="rb:flex rb:w-full rb:items-center rb:mb-5 rb:gap-2">
<img src={shareUserIcon} className='rb:size-4 rb:ml-2' />
@@ -178,15 +189,11 @@ const Share: FC = () => {
<RecallTest ref={recallTestRef} />
</div>
</div>
<div className='rb:w-80 rb:border rb:overflow-y-auto rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4'>
<InfoPanel
title={t('knowledgeBase.knowledgeBaseInfo')}
items={infoItems}
/>
</div>
{/* <div className='rb:w-80 rb:border rb:overflow-y-auto rb:border-[#DFE4ED] rb:bg-white rb:rounded-xl rb:p-4'>
</div> */}
</div>
</div>
);
};

View File

@@ -270,8 +270,8 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
<Col span={24}>
<RbCard
title={t('knowledgeBase.knowledgeGraph')}
headerType="borderless"
headerClassName="rb:text-[18px]! rb:leading-[24px]"
variant="outlined"
headerClassName="rb:text-sm! rb:leading-11 rb:bg-[#FAFAFA]! rb:w-full rb:ml-0! rb:px-3!"
>
<div className="rb:h-124 rb:relative">
{loading ? (
@@ -435,7 +435,7 @@ const KnowledgeGraph: FC<KnowledgeGraphProps> = ({ data, loading = false }) => {
</>
)}
</div>
<div className="rb:bg-[#F0F3F8] rb:flex rb:items-center rb:gap-6 rb:rounded-[0px_0px_12px_12px] rb:p-[14px_40px] rb:m-[0_-20px_-16px_-16px]">
<div className="rb:bg-[#FAFAFA] rb:border-box rb:border-t rb:border-gray-200 rb:flex rb:items-center rb:justify-between rb:gap-6 rb:rounded-[0px_0px_12px_12px] rb:p-[14px_40px] rb:m-[0_-16px_-20px_-16px]">
{operations.map((item) => (
<div key={item.name} className="rb:flex rb:items-center rb:text-[#5B6167] rb:leading-5">
<img src={item.icon} className="rb:w-5 rb:h-5 rb:mr-1" />

View File

@@ -108,11 +108,11 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
<span className='rb:text-[#155eef]'>{ t('knowledgeBase.loadSampleQuestions')}</span>
</div> */}
</div>
<Form form={form} >
<Form form={form} layout="vertical">
<Form.Item name="query">
<TextArea rows={4} placeholder={t('knowledgeBase.testQuestionPlaceholder')}/>
</Form.Item>
<div className='rb:grid rb:grid-cols-2 rb:gap-x-4'>
<div className='rb:grid rb:grid-cols-5 rb:gap-x-4'>
<Form.Item
name="retrieve_type"
label={t('knowledgeBase.retrieveMode')}
@@ -179,7 +179,7 @@ const RecallTest = forwardRef<RecallTestDrawerRef>(({},ref) => {
{/* <Form.Item name="hybrid" valuePropName="checked" initialValue={true} label={t('knowledgeBase.hybrid') || 'Hybrid'}>
<Switch checkedChildren={t('common.yes') || 'Yes'} unCheckedChildren={t('common.no') || 'No'} />
</Form.Item> */}
<Form.Item>
<Form.Item className="rb:flex rb:items-end rb:justify-end">
<Button type="primary" onClick={handleStartTest} loading={loading}>{ t('knowledgeBase.startTesting')}</Button>
</Form.Item>
</div>

View File

@@ -1,4 +1,3 @@
import { forwardRef, useImperativeHandle, useState, useRef, useLayoutEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import RbDrawer from '@/components/RbDrawer';
@@ -51,6 +50,7 @@ const RecallTestDrawer = forwardRef<RecallTestDrawerRef>(({},ref) => {
title={t('knowledgeBase.recallTest')}
open={open}
onClose={() => setOpen(false)}
width={1000}
>
<RecallTest ref={setRecallTestRef} />
</RbDrawer>

View File

@@ -169,7 +169,7 @@ const RecallTestResult = ({
return (
<div
key={`${item.metadata?.sort_id || index}-${index}`}
className={`rb:flex rb:flex-col rb:mb-4 rb:rounded-lg rb:border rb:border-[#DFE4ED] rb:bg-[#FBFDFF] rb:p-4 rb:pt-2 rb:pb-3 rb:relative rb:group ${editable ? 'rb:cursor-pointer rb:transition-all hover:rb:border-[#155EEF] hover:rb:shadow-md' : ''}`}
className={`rb:flex rb:flex-col rb:mb-4 rb:rounded-xl rb:bg-[#F6F6F6] rb:p-4 rb:pt-2 rb:pb-3 rb:relative rb:group ${editable ? 'rb:cursor-pointer rb:transition-all hover:rb:border-[#155EEF] hover:rb:shadow-md' : ''}`}
onClick={(e) => handleItemClick(e, item, index)}
>
{editable && (
@@ -183,16 +183,16 @@ const RecallTestResult = ({
{scorePercentage.toFixed(1)}% {t('knowledgeBase.similarity')}
</span>
)}
<div className={`rb:flex rb:mt-2 rb:flex-col rb:items-end rb:justify-end rb:gap-1 ${!showScore ? 'rb:w-full' : ''}`}>
<div className={`rb:flex rb:mt-2 rb:flex 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-[#F0F3F8] 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-[2px] rb:rounded'>
chunk_{item.metadata?.sort_id || index}
</span>
</div>
</div>
<div className='rb:flex rb:text-left rb:px-4 rb:py-3 rb:bg-[#F0F3F8] rb:rounded-lg rb:mt-2'>
<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'>
{(() => {
const qaContent = parseQAContent(item.page_content);

View File

@@ -1,7 +1,7 @@
import { useEffect, useState, useRef, useMemo, useCallback, type FC } from 'react';
import { Row, Col, Button, Dropdown, Modal, message, Tooltip } from 'antd'
import type { MenuProps } from 'antd';
import { EllipsisOutlined } from '@ant-design/icons';
import { EllipsisOutlined, RightOutlined, DownOutlined } from '@ant-design/icons';
import { useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -185,7 +185,7 @@ const KnowledgeBaseManagement: FC = () => {
}
};
const formatData = (data: KnowledgeBaseListItem) => {
const keys: (keyof KnowledgeBaseListItem)[] = ['type', 'permission_id']
const keys: (keyof KnowledgeBaseListItem)[] = ['permission_id','type']
return keys.map(key => ({
key,
label: t(`knowledgeBase.${key}`),
@@ -569,7 +569,9 @@ const KnowledgeBaseManagement: FC = () => {
<Col xs={12} sm={12} md={12} lg={8} xl={8} key={item.id} >
<RbCard
title={item.name}
className='rb:min-h-[198px]'
headerType="borderless"
headerClassName="rb:py-3!"
className='rb:min-h-[200px]'
extra={
<div onClick={(e) => e.stopPropagation()}>
<Dropdown menu={{ items: getOptMenuItems(item) }} >
@@ -578,41 +580,57 @@ const KnowledgeBaseManagement: FC = () => {
</div>
}
>
<div className='rb:min-h-[158px]' onClick={() => handleToDetail(item)}>
<div className='rb:min-h-[124px]'>
<div className='' onClick={() => handleToDetail(item)}>
<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}>
<div className='rb:flex-1 rb:text-left rb:leading-5 rb:text-gray-800 rb:break-words rb:line-clamp-2'>{(item.description && item.description != '') ? item.description : t('knowledgeBase.noDescription')}</div>
</Tooltip>
</div>
<div className='rb:min-h-[60px] rb:py-2.5 rb:px-3 rb:bg-[#F6F6F6] rb:rounded-lg rb:mb-3'>
{item.descriptionItems?.map((description: Record<string, unknown>) => (
<div
key={description.key as string}
className="rb:flex rb:gap-4 rb:justify-start rb:text-[#5B6167] rb:text-[14px] rb:leading-[20px] rb:mb-[12px]"
className="rb:flex rb:gap-4 rb:justify-start rb:text-[#5B6167] rb:text-[14px] rb:leading-[20px]"
>
<div className="rb:whitespace-nowrap rb:w-20">{(description.label as string)}</div>
<div className={clsx('rb:whitespace-nowrap rb:w-20',{"rb:text-gray-800 rb:font-medium" : (description.key as string) === 'permission_id'})}>{(description.label as string)}</div>
<div className={clsx('rb:flex-inline rb:text-left rb:py-[1px] rb:rounded rb:font-medium',{
"rb:text-[#155eef] rb:bg-[rgba(21,94,239,0.06)] rb:px-2 rb:border rb:border-[rgba(21,94,239,0.25)] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.private'),
"rb:text-[#369F21] rb:bg-[rgba(54,159,33,0.06)] rb:px-2 rb:border rb:border-[rgba(54,159,33,0.25);] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.share'),
"rb:text-[#155eef] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.private'),
"rb:text-[#FF8A4C] rb:font-medium": (description.key as string) === 'permission_id' && (description.children as string) === t('knowledgeBase.share'),
})}>{(description.children as string)}</div>
</div>
))}
{item.description && (
<div className="rb:flex rb:text-[#5B6167] rb:h-10 rb:line-clamp-2 rb:text-sm rb:leading-5 rb:mb-3 rb:gap-4">
<div className="rb:font-medium rb:w-20">{t('knowledgeBase.description')} </div>
<Tooltip title={item.description}>
<div className='rb:flex-1 rb:text-left rb:leading-5 rb:text-gray-800 rb:break-words rb:line-clamp-2'>{item.description || t('knowledgeBase.noDescription')}</div>
</Tooltip>
</div>
)}
</div>
{hasModelInfo && (
<Dropdown menu={{ items: modelInfo.menu }}>
<div onClick={(e) => e.stopPropagation()}>
<div
className="rb:flex rb:text-gray-500 rb:px-3 rb:py-2 rb:text-[12px] rb:leading-4 rb:mb-2 rb:bg-[#F0F3F8] rb:rounded"
onClick={(e) => e.stopPropagation()}
className="rb:flex rb:items-center rb:pt-2 rb:px-2 rb:text-[12px] rb:leading-5 rb:cursor-pointer rb:rounded rb:transition-colors"
onClick={() => {
setData(prev => prev.map(d => d.id === item.id ? { ...d, _expanded: !d._expanded } : d));
}}
>
<span>{t('knowledgeBase.models')}:</span>
<span className="rb:ml-1 rb:truncate rb:max-w-[200px]">
{modelInfo.summary.join('')}
</span>
{/* <span className='rb:text-gray-500'>{t('knowledgeBase.models')}:</span> */}
<span className="rb:ml-1 rb:truncate rb:flex-1 rb:text-gray-500">
{modelInfo.summary[0].split(':')[0]}:<span className="rb:text-gray-900">{modelInfo.summary[0].split(':').slice(1).join(':')}</span>
</span>
<span className="rb:ml-auto rb:text-gray-400 rb:text-[10px]">
{item._expanded ? <DownOutlined /> : <RightOutlined />}
</span>
</div>
</Dropdown>
{item._expanded && (
<div className="rb:py-1 rb:px-2 rb:text-[12px]">
{modelInfo.summary.slice(1).map((text, idx) => {
const [label, value] = text.split(':');
return (
<div key={idx} className="rb:py-1 rb:text-gray-500">
{label}:<span className="rb:text-gray-900">{value}</span>
</div>
);
})}
</div>
)}
</div>
)}
</div>
</RbCard>

View File

@@ -283,6 +283,7 @@ export interface KnowledgeBaseListItem extends KnowledgeBase {
reranker?: Model;
llm?: Model;
image2text?: Model;
_expanded?: boolean;
}
// 知识库列表响应