Files
YG-Datasets/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnnotationDialog.js

269 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Box,
Typography,
Chip,
CircularProgress
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import Image from 'next/image';
import QuestionSelector from './QuestionSelector';
import AnswerInput from './AnswerInput';
export default function AnnotationDialog({
open,
onClose,
image,
templates,
selectedTemplate,
onTemplateChange,
answer,
onAnswerChange,
onSave,
onSaveAndContinue,
saving,
loading,
onOpenCreateQuestion,
onOpenCreateTemplate
}) {
const { t } = useTranslation();
if (!image) return null;
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="xl"
fullWidth
PaperProps={{
sx: {
borderRadius: 2,
maxHeight: '90vh'
}
}}
>
<DialogTitle sx={{ pb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="h5" fontWeight="600">
{t('images.annotateImage', { defaultValue: '标注图片' })}
</Typography>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
{image && (
<Chip
label={`${image.answeredQuestions?.length || 0} / ${(image.answeredQuestions?.length || 0) + (image.unansweredQuestions?.length || 0)} 已完成`}
color="primary"
variant="outlined"
size="small"
/>
)}
</Box>
</Box>
</DialogTitle>
<DialogContent sx={{ p: 3 }}>
{/* 图片预览区域 */}
<Box
sx={{
display: 'flex',
gap: 4,
mb: 4,
minHeight: 450
}}
>
{/* 图片预览 */}
<Box
sx={{
flex: '0 0 450px',
display: 'flex',
flexDirection: 'column',
gap: 2
}}
>
{image && (
<>
<Box
sx={{
position: 'relative',
width: '100%',
height: 400,
border: '2px solid',
borderColor: 'divider',
borderRadius: 2,
overflow: 'hidden',
bgcolor: 'grey.50'
}}
>
{image.base64 ? (
<Image src={image.base64} alt={image.imageName} fill style={{ objectFit: 'contain' }} priority />
) : (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'text.secondary'
}}
>
<Typography variant="body2">
{t('images.imageLoadError', { defaultValue: '图片加载失败' })}
</Typography>
</Box>
)}
</Box>
{/* 图片信息卡片 */}
<Box
sx={{
p: 2,
bgcolor: 'grey.50',
borderRadius: 2,
border: '1px solid',
borderColor: 'divider'
}}
>
<Typography variant="subtitle1" fontWeight="600" gutterBottom>
{image.imageName}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 1 }}>
{image.width && image.height && (
<Chip label={`${image.width} × ${image.height}`} size="small" variant="outlined" />
)}
{image.size && (
<Chip label={`${(image.size / 1024).toFixed(2)} KB`} size="small" variant="outlined" />
)}
{image.format && <Chip label={image.format?.toUpperCase()} size="small" variant="outlined" />}
</Box>
<Typography variant="body2" color="text.secondary">
<strong>{t('images.annotatedCount', { defaultValue: '已标注' })}:</strong> {image.datasetCount || 0}{' '}
{t('images.questions', { defaultValue: '个问题' })}
</Typography>
</Box>
</>
)}
</Box>
{/* 标注区域 */}
<Box
sx={{
flex: 1,
display: 'flex',
flexDirection: 'column',
gap: 3,
minWidth: 0
}}
>
{/* 问题选择器 */}
<Box
sx={{
p: 3,
bgcolor: 'background.paper',
borderRadius: 2,
border: '1px solid',
borderColor: 'divider'
}}
>
{loading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</Box>
) : (
<QuestionSelector
templates={templates}
selectedTemplate={selectedTemplate}
onTemplateChange={onTemplateChange}
answeredQuestions={image?.answeredQuestions || []}
unansweredQuestions={image?.unansweredQuestions || []}
onOpenCreateQuestion={onOpenCreateQuestion}
onOpenCreateTemplate={onOpenCreateTemplate}
/>
)}
</Box>
{/* 答案输入区域 */}
{selectedTemplate && (
<Box
sx={{
p: 3,
bgcolor: 'background.paper',
borderRadius: 2,
border: '1px solid',
borderColor: 'divider'
}}
>
<AnswerInput
answerType={selectedTemplate.answerType}
answer={answer}
onAnswerChange={onAnswerChange}
labels={selectedTemplate.labels}
customFormat={selectedTemplate.customFormat}
projectId={image?.projectId}
imageName={image?.imageName}
question={selectedTemplate}
/>
</Box>
)}
</Box>
</Box>
</DialogContent>
<DialogActions
sx={{
p: 3,
pt: 0,
gap: 1,
justifyContent: 'space-between',
display: 'flex',
flexWrap: 'wrap'
}}
>
{/* 左侧:创建按钮 */}
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
onClick={onOpenCreateQuestion}
variant="outlined"
size="small"
sx={{ borderRadius: 2, textTransform: 'none' }}
>
{t('images.createQuestion', { defaultValue: '创建问题' })}
</Button>
<Button
onClick={onOpenCreateTemplate}
variant="outlined"
size="small"
sx={{ borderRadius: 2, textTransform: 'none' }}
>
{t('images.createTemplate', { defaultValue: '创建问题模板' })}
</Button>
</Box>
{/* 右侧:操作按钮 */}
<Box sx={{ display: 'flex', gap: 1 }}>
<Button onClick={onClose} disabled={saving} variant="outlined" sx={{ borderRadius: 2 }}>
{t('common.cancel')}
</Button>
<Button
onClick={onSaveAndContinue}
disabled={saving || !selectedTemplate}
variant="outlined"
sx={{ borderRadius: 2 }}
>
{saving ? <CircularProgress size={20} /> : t('images.saveAndContinue', { defaultValue: '保存并继续' })}
</Button>
<Button onClick={onSave} disabled={saving || !selectedTemplate} variant="contained" sx={{ borderRadius: 2 }}>
{saving ? <CircularProgress size={20} /> : t('common.save')}
</Button>
</Box>
</DialogActions>
</Dialog>
);
}