first-update
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button,
|
||||
Typography,
|
||||
Box,
|
||||
Alert
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function DeleteConfirmDialog({ open, fileName, onClose, onConfirm }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby="delete-dialog-title"
|
||||
aria-describedby="delete-dialog-description"
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle id="delete-dialog-title">
|
||||
{t('common.confirmDelete')}「{fileName}」?
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="delete-dialog-description">{t('common.confirmDeleteDescription')}</DialogContentText>
|
||||
|
||||
<Alert severity="warning" sx={{ my: 2 }}>
|
||||
<Typography variant="body2" component="div" fontWeight="medium">
|
||||
{t('textSplit.deleteFileWarning')}
|
||||
</Typography>
|
||||
<Box sx={{ mt: 1 }}>
|
||||
<Typography variant="body2" component="div">
|
||||
• {t('textSplit.deleteFileWarningChunks')}
|
||||
</Typography>
|
||||
<Typography variant="body2" component="div">
|
||||
• {t('textSplit.deleteFileWarningQuestions')}
|
||||
</Typography>
|
||||
<Typography variant="body2" component="div">
|
||||
• {t('textSplit.deleteFileWarningDatasets')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Alert>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose} color="primary">
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} color="error" variant="contained">
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import { Box, List, ListItem, ListItemIcon, ListItemText, Collapse, IconButton } from '@mui/material';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
import ExpandLess from '@mui/icons-material/ExpandLess';
|
||||
import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
/**
|
||||
* 目录结构组件
|
||||
* @param {Object} props
|
||||
* @param {Array} props.items - 目录项数组
|
||||
* @param {Object} props.expandedItems - 展开状态对象
|
||||
* @param {Function} props.onToggleItem - 展开/折叠回调
|
||||
* @param {number} props.level - 当前层级
|
||||
* @param {string} props.parentId - 父级ID
|
||||
*/
|
||||
export default function DirectoryView({ items, expandedItems, onToggleItem, level = 0, parentId = '' }) {
|
||||
const theme = useTheme();
|
||||
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<List sx={{ pl: level > 0 ? 2 : 0 }}>
|
||||
{items.map((item, index) => {
|
||||
const itemId = `${parentId}-${index}`;
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const isExpanded = expandedItems[itemId] || false;
|
||||
|
||||
return (
|
||||
<Box key={itemId}>
|
||||
<ListItem
|
||||
sx={{
|
||||
pl: level * 2,
|
||||
borderLeft: level > 0 ? `1px solid ${theme.palette.divider}` : 'none',
|
||||
ml: level > 0 ? 1 : 0
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 36 }}>
|
||||
{hasChildren ? <FolderIcon color="primary" /> : <ArticleIcon color="info" />}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={item.text}
|
||||
primaryTypographyProps={{
|
||||
fontWeight: level === 0 ? 'bold' : 'normal',
|
||||
variant: level === 0 ? 'subtitle1' : 'body2'
|
||||
}}
|
||||
/>
|
||||
{hasChildren && (
|
||||
<IconButton size="small" onClick={() => onToggleItem(itemId)}>
|
||||
{isExpanded ? <ExpandLess /> : <ExpandMore />}
|
||||
</IconButton>
|
||||
)}
|
||||
</ListItem>
|
||||
|
||||
{hasChildren && (
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<DirectoryView
|
||||
items={item.children}
|
||||
expandedItems={expandedItems}
|
||||
onToggleItem={onToggleItem}
|
||||
level={level + 1}
|
||||
parentId={itemId}
|
||||
/>
|
||||
</Collapse>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
FormControlLabel,
|
||||
FormControl,
|
||||
Typography
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
/**
|
||||
* 领域树操作选择对话框
|
||||
* 提供三种选项:修订领域树、重建领域树、不更改领域树
|
||||
*/
|
||||
export default function DomainTreeActionDialog({ open, onClose, onConfirm, isFirstUpload, action }) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(isFirstUpload ? 'rebuild' : 'revise');
|
||||
|
||||
// 处理选项变更
|
||||
const handleChange = event => {
|
||||
setValue(event.target.value);
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
onConfirm(value);
|
||||
};
|
||||
|
||||
// 获取对话框标题
|
||||
const getDialogTitle = () => {
|
||||
if (isFirstUpload) {
|
||||
return t('textSplit.domainTree.firstUploadTitle');
|
||||
}
|
||||
return action === 'upload' ? t('textSplit.domainTree.uploadTitle') : t('textSplit.domainTree.deleteTitle');
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>{getDialogTitle()}</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControl component="fieldset">
|
||||
<RadioGroup value={value} onChange={handleChange}>
|
||||
{!isFirstUpload && (
|
||||
<FormControlLabel
|
||||
value="revise"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="subtitle1">{t('textSplit.domainTree.reviseOption')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('textSplit.domainTree.reviseDesc')}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value="rebuild"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="subtitle1">{t('textSplit.domainTree.rebuildOption')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('textSplit.domainTree.rebuildDesc')}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{!isFirstUpload && (
|
||||
<FormControlLabel
|
||||
value="keep"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<>
|
||||
<Typography variant="subtitle1">{t('textSplit.domainTree.keepOption')}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('textSplit.domainTree.keepDesc')}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>{t('common.cancel')}</Button>
|
||||
<Button onClick={handleConfirm} variant="contained" color="primary">
|
||||
{t('common.confirm')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
import { TreeView, TreeItem } from '@mui/lab';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
|
||||
/**
|
||||
* 领域知识树组件
|
||||
* @param {Object} props
|
||||
* @param {Array} props.nodes - 树节点数组
|
||||
*/
|
||||
export default function DomainTreeView({ nodes = [] }) {
|
||||
if (!nodes || nodes.length === 0) return null;
|
||||
|
||||
const renderTreeItems = nodes => {
|
||||
return nodes.map((node, index) => (
|
||||
<TreeItem key={`node-${index}`} nodeId={`node-${index}`} label={node.text} sx={{ mb: 1 }}>
|
||||
{node.children && node.children.length > 0 && renderTreeItems(node.children)}
|
||||
</TreeItem>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<TreeView
|
||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||
defaultExpandIcon={<ChevronRightIcon />}
|
||||
sx={{ flexGrow: 1, overflowY: 'auto' }}
|
||||
>
|
||||
{renderTreeItems(nodes)}
|
||||
</TreeView>
|
||||
);
|
||||
}
|
||||
1068
easy-dataset-main/components/text-split/components/FileList.js
Normal file
1068
easy-dataset-main/components/text-split/components/FileList.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,208 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Typography, keyframes, Paper } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { handleLongFileName } from '@/lib/file/file-process';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
// 定义动画效果
|
||||
const pulse = keyframes`
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(32, 76, 255, 0.2);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 15px rgba(32, 76, 255, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(32, 76, 255, 0);
|
||||
}
|
||||
`;
|
||||
|
||||
const rotateAnimation = keyframes`
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
`;
|
||||
|
||||
const shimmer = keyframes`
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
`;
|
||||
|
||||
/**
|
||||
* 文件处理进度展示组件 - 美化版
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} props.fileTask - 文件处理任务信息
|
||||
*/
|
||||
export default function FileLoadingProgress({ fileTask }) {
|
||||
const { t } = useTranslation();
|
||||
const [animationStep, setAnimationStep] = useState(0);
|
||||
|
||||
// 创建动态效果
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setAnimationStep(prev => (prev + 1) % 4);
|
||||
}, 600);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (!fileTask) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const pageProgress = (fileTask.current.processedPage / fileTask.current.totalPage) * 100;
|
||||
const filesProgress = (fileTask.processedFiles / fileTask.totalFiles) * 100;
|
||||
|
||||
// 生成进度指示器文本
|
||||
const getProgressIndicator = () => {
|
||||
const dots = '.';
|
||||
return dots.repeat(animationStep + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: 'auto',
|
||||
minHeight: '25vh',
|
||||
width: '80%',
|
||||
maxWidth: '600px',
|
||||
margin: '0 auto',
|
||||
padding: 4,
|
||||
borderRadius: 3,
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
background: 'linear-gradient(45deg, #f9f9f9 0%, #ffffff 100%)',
|
||||
animation: `${pulse} 2s infinite`
|
||||
}}
|
||||
>
|
||||
{/* 背景动画元素 */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '-50%',
|
||||
left: '-50%',
|
||||
width: '200%',
|
||||
height: '200%',
|
||||
background: 'radial-gradient(circle, rgba(32,76,255,0.05) 0%, rgba(255,255,255,0) 70%)',
|
||||
animation: `${rotateAnimation} 15s linear infinite`,
|
||||
zIndex: 0
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 主标题 */}
|
||||
<Typography
|
||||
variant="h5"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
mb: 3,
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
background: 'linear-gradient(90deg, #3a7bd5 0%, #00d2ff 100%)',
|
||||
backgroundClip: 'text',
|
||||
textFillColor: 'transparent',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
{t('textSplit.pdfProcessingLoading')}
|
||||
{getProgressIndicator()}
|
||||
</Typography>
|
||||
|
||||
{/* 处理进度显示区域 */}
|
||||
<Box sx={{ width: '90%', mt: 2, mb: 3, position: 'relative', zIndex: 1 }}>
|
||||
{/* 当前文件进度 */}
|
||||
<ProgressSection
|
||||
label={t('textSplit.pdfPageProcessStatus', {
|
||||
fileName: handleLongFileName(fileTask.current.fileName),
|
||||
total: fileTask.current.totalPage,
|
||||
completed: fileTask.current.processedPage
|
||||
})}
|
||||
progress={pageProgress}
|
||||
color="#3a7bd5"
|
||||
/>
|
||||
|
||||
{/* 总文件进度 */}
|
||||
<ProgressSection
|
||||
label={t('textSplit.pdfProcessStatus', {
|
||||
total: fileTask.totalFiles,
|
||||
completed: fileTask.processedFiles
|
||||
})}
|
||||
progress={filesProgress}
|
||||
color="#00d2ff"
|
||||
mt={3}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度条区域组件
|
||||
*/
|
||||
function ProgressSection({ label, progress, color, mt = 0 }) {
|
||||
return (
|
||||
<Box sx={{ mt }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
mb: 1,
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="medium"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
fontSize: '0.9rem'
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
color,
|
||||
fontSize: '1.1rem'
|
||||
}}
|
||||
>
|
||||
{Math.round(progress)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* 自定义进度条 */}
|
||||
<Box
|
||||
sx={{
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
background: '#f0f0f0',
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100%',
|
||||
width: `${progress}%`,
|
||||
borderRadius: 5,
|
||||
background: `linear-gradient(90deg, ${color} 0%, ${color}80 100%)`,
|
||||
transition: 'width 0.5s ease',
|
||||
backgroundSize: '200% 100%',
|
||||
animation: `${shimmer} 2s infinite linear`
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
Box,
|
||||
Stack,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem
|
||||
} from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined';
|
||||
import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined';
|
||||
import LaunchOutlinedIcon from '@mui/icons-material/LaunchOutlined';
|
||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||
import ChangeCircleOutlinedIcon from '@mui/icons-material/ChangeCircleOutlined';
|
||||
|
||||
const StyledCard = styled(Card)(({ theme, disabled }) => ({
|
||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||
opacity: disabled ? 0.6 : 1,
|
||||
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
|
||||
'&:hover': disabled
|
||||
? {}
|
||||
: {
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: theme.shadows[4]
|
||||
}
|
||||
}));
|
||||
|
||||
const OptionCard = ({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
disabled,
|
||||
onClick,
|
||||
selected,
|
||||
isVisionEnabled,
|
||||
visionModels,
|
||||
selectorName,
|
||||
handleSettingChange,
|
||||
selectedViosnModel
|
||||
}) => (
|
||||
<StyledCard
|
||||
disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
sx={{
|
||||
height: '100%',
|
||||
border: selected ? '2px solid primary.main' : '1px solid divider',
|
||||
backgroundColor: selected ? 'action.selected' : 'background.paper'
|
||||
}}
|
||||
>
|
||||
<CardContent>
|
||||
<Stack spacing={1}>
|
||||
<Box sx={{ color: 'primary.main', mb: 1 }}>{icon}</Box>
|
||||
<Typography variant="h6" component="div">
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{description}
|
||||
</Typography>
|
||||
{isVisionEnabled && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>{selectorName}</InputLabel>
|
||||
<Select
|
||||
label={selectorName}
|
||||
value={selectedViosnModel}
|
||||
onChange={e => handleSettingChange(e)}
|
||||
name="vision"
|
||||
>
|
||||
{visionModels.map(item => (
|
||||
<MenuItem key={item.id} value={item.id}>
|
||||
{item.modelName} ({item.providerName})
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</StyledCard>
|
||||
);
|
||||
|
||||
export default function PdfProcessingDialog({
|
||||
open,
|
||||
onClose,
|
||||
onRadioChange,
|
||||
value,
|
||||
taskSettings,
|
||||
visionModels,
|
||||
selectedViosnModel,
|
||||
setSelectedViosnModel
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
//检查配置中是否启用MinerU
|
||||
const isMinerUEnabled = taskSettings && taskSettings.minerUToken ? true : false;
|
||||
|
||||
const isMinerULocalEnabled = taskSettings && taskSettings.minerULocalUrl ? true : false;
|
||||
|
||||
//检查配置中是否启用Vision策略
|
||||
const isVisionEnabled = visionModels.length > 0 ? true : false;
|
||||
|
||||
//用于传递到父组件,显示当前选中的模型
|
||||
let selectedModel = selectedViosnModel;
|
||||
|
||||
const handleOptionClick = optionValue => {
|
||||
if (optionValue === 'mineru-web') {
|
||||
window.open('https://mineru.net/OpenSourceTools/Extractor', '_blank');
|
||||
} else {
|
||||
onRadioChange({ target: { value: optionValue, selectedVision: selectedModel } });
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
// 处理设置变更
|
||||
const handleSettingChange = e => {
|
||||
const { value } = e.target;
|
||||
selectedModel = value;
|
||||
setSelectedViosnModel(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} fullWidth maxWidth="md">
|
||||
<DialogTitle>{t('textSplit.pdfProcess')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
|
||||
gap: 2,
|
||||
p: 1
|
||||
}}
|
||||
>
|
||||
<OptionCard
|
||||
icon={<ArticleOutlinedIcon fontSize="large" />}
|
||||
title={t('textSplit.basicPdfParsing')}
|
||||
description={t('textSplit.basicPdfParsingDesc')}
|
||||
onClick={() => handleOptionClick('default')}
|
||||
selected={value === 'default'}
|
||||
/>
|
||||
<OptionCard
|
||||
icon={<ScienceOutlinedIcon fontSize="large" />}
|
||||
title="MinerU API"
|
||||
description={isMinerUEnabled ? t('textSplit.mineruApiDesc') : t('textSplit.mineruApiDescDisabled')}
|
||||
disabled={!isMinerUEnabled}
|
||||
onClick={() => handleOptionClick('mineru')}
|
||||
selected={value === 'mineru'}
|
||||
/>
|
||||
<OptionCard
|
||||
icon={<ChangeCircleOutlinedIcon fontSize="large" />}
|
||||
title="MinerU Local"
|
||||
description={isMinerULocalEnabled ? t('textSplit.mineruLocalDesc') : t('textSplit.mineruLocalDisabled')}
|
||||
disabled={!isMinerULocalEnabled}
|
||||
onClick={() => handleOptionClick('mineru-local')}
|
||||
selected={value === 'mineru-local'}
|
||||
/>
|
||||
<OptionCard
|
||||
icon={<LaunchOutlinedIcon fontSize="large" />}
|
||||
title={t('textSplit.mineruWebPlatform')}
|
||||
description={t('textSplit.mineruWebPlatformDesc')}
|
||||
onClick={() => handleOptionClick('mineru-web')}
|
||||
/>
|
||||
<OptionCard
|
||||
icon={<SmartToyOutlinedIcon fontSize="large" />}
|
||||
title={t('textSplit.customVisionModel')}
|
||||
description={t('textSplit.customVisionModelDesc')}
|
||||
disabled={!isVisionEnabled}
|
||||
onClick={() => handleOptionClick('vision')}
|
||||
selected={value === 'vision'}
|
||||
isVisionEnabled={isVisionEnabled}
|
||||
visionModels={visionModels}
|
||||
selectorName={t('settings.vision')}
|
||||
selectedViosnModel={selectedViosnModel}
|
||||
handleSettingChange={handleSettingChange}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
/**
|
||||
* 标签页面板组件
|
||||
* @param {Object} props
|
||||
* @param {number} props.value - 当前激活的标签索引
|
||||
* @param {number} props.index - 当前面板对应的索引
|
||||
* @param {ReactNode} props.children - 子组件
|
||||
*/
|
||||
export default function TabPanel({ value, index, children }) {
|
||||
return (
|
||||
<Box
|
||||
role="tabpanel"
|
||||
hidden={value !== index}
|
||||
id={`domain-tabpanel-${index}`}
|
||||
aria-labelledby={`domain-tab-${index}`}
|
||||
sx={{ height: '100%' }}
|
||||
>
|
||||
{value === index && <Box sx={{ height: '100%' }}>{children}</Box>}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
207
easy-dataset-main/components/text-split/components/UploadArea.js
Normal file
207
easy-dataset-main/components/text-split/components/UploadArea.js
Normal file
@@ -0,0 +1,207 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Typography,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Divider,
|
||||
CircularProgress,
|
||||
Tooltip
|
||||
} from '@mui/material';
|
||||
import UploadFileIcon from '@mui/icons-material/UploadFile';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
export default function UploadArea({
|
||||
theme,
|
||||
files,
|
||||
uploading,
|
||||
uploadedFiles,
|
||||
onFileSelect,
|
||||
onRemoveFile,
|
||||
onUpload,
|
||||
selectedModel
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
const inputRef = useRef(null);
|
||||
|
||||
// 拖拽进入
|
||||
const handleDragOver = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!dragActive) setDragActive(true);
|
||||
};
|
||||
// 拖拽离开
|
||||
const handleDragLeave = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(false);
|
||||
};
|
||||
// 拖拽释放
|
||||
const handleDrop = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(false);
|
||||
if (!selectedModel?.id || uploading) return;
|
||||
const files = e.dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
// 构造一个模拟的 event 以复用 onFileSelect
|
||||
const event = { target: { files } };
|
||||
onFileSelect(event);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
p: 3,
|
||||
height: '100%',
|
||||
border: `2px dashed ${dragActive ? theme.palette.primary.main : alpha(theme.palette.primary.main, 0.2)}`,
|
||||
borderRadius: 2,
|
||||
bgcolor: dragActive ? alpha(theme.palette.primary.main, 0.12) : alpha(theme.palette.primary.main, 0.05),
|
||||
transition: 'all 0.3s ease',
|
||||
'&:hover': {
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.08),
|
||||
borderColor: alpha(theme.palette.primary.main, 0.3)
|
||||
},
|
||||
cursor: uploading || !selectedModel?.id ? 'not-allowed' : 'pointer',
|
||||
position: 'relative'
|
||||
}}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
{dragActive && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
bgcolor: alpha(theme.palette.primary.main, 0.3),
|
||||
zIndex: 10,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
borderRadius: 2,
|
||||
border: `3px solid ${theme.palette.primary.main}`,
|
||||
backdropFilter: 'blur(2px)'
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'rgba(255, 255, 255, 0.9)',
|
||||
p: 3,
|
||||
borderRadius: 1,
|
||||
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
|
||||
textAlign: 'center',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
border: `1px solid ${theme.palette.primary.main}`
|
||||
}}
|
||||
>
|
||||
<UploadFileIcon color="primary" sx={{ fontSize: 40, mb: 1 }} />
|
||||
<Typography variant="h6" color="primary" sx={{ fontWeight: 'bold' }}>
|
||||
{t('textSplit.dragToUpload', { defaultValue: '拖拽文件到此处上传' })}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
{t('textSplit.uploadNewDocument')}
|
||||
</Typography>
|
||||
|
||||
<Tooltip
|
||||
title={!selectedModel?.id ? t('textSplit.selectModelFirst', { defaultValue: '请先在右上角选择模型' }) : ''}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
component="label"
|
||||
variant="contained"
|
||||
startIcon={<UploadFileIcon />}
|
||||
sx={{ mb: 2, mt: 2 }}
|
||||
disabled={!selectedModel?.id || uploading}
|
||||
>
|
||||
{t('textSplit.selectFile')}
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
accept=".md,.txt,.docx,.pdf,.epub"
|
||||
multiple
|
||||
onChange={onFileSelect}
|
||||
disabled={!selectedModel?.id || uploading}
|
||||
/>
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
{uploadedFiles.total > 0 ? t('textSplit.mutilFileMessage') : t('textSplit.supportedFormats')}
|
||||
</Typography>
|
||||
|
||||
{files.length > 0 && (
|
||||
<Box sx={{ mt: 3, width: '100%' }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
{t('textSplit.selectedFiles', { count: files.length })}
|
||||
</Typography>
|
||||
|
||||
<List sx={{ bgcolor: theme.palette.background.paper, borderRadius: 1, maxHeight: '200px', overflow: 'auto' }}>
|
||||
{files.map((file, index) => (
|
||||
<Box key={index}>
|
||||
<ListItem
|
||||
secondaryAction={
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
startIcon={<DeleteIcon />}
|
||||
onClick={() => onRemoveFile(index)}
|
||||
disabled={uploading}
|
||||
>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<ListItemText primary={file.name} secondary={`${(file.size / 1024).toFixed(2)} KB`} />
|
||||
</ListItem>
|
||||
{index < files.length - 1 && <Divider />}
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'center' }}>
|
||||
<Tooltip
|
||||
title={
|
||||
!selectedModel?.id ? t('textSplit.selectModelFirst', { defaultValue: '请先在右上角选择模型' }) : ''
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onUpload}
|
||||
disabled={uploading || !selectedModel?.id}
|
||||
sx={{ minWidth: 120 }}
|
||||
>
|
||||
{uploading ? <CircularProgress size={24} /> : t('textSplit.uploadAndProcess')}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user