'use client'; import { useState, useEffect } from 'react'; import { useParams, useRouter } from 'next/navigation'; import { useTranslation } from 'react-i18next'; import { Container, Box, Typography, Button, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, TextField } from '@mui/material'; import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; import DeleteIcon from '@mui/icons-material/Delete'; import { imageStyles } from './styles/imageStyles'; import { toast } from 'sonner'; import axios from 'axios'; import { useAtomValue } from 'jotai'; import { selectedModelInfoAtom } from '@/lib/store'; import ImageFilters from './components/ImageFilters'; import ImageGrid from './components/ImageGrid'; import ImageList from './components/ImageList'; import ImportDialog from './components/ImportDialog'; import QuestionDialog from './components/QuestionDialog'; import DatasetDialog from './components/DatasetDialog'; import AnnotationDialog from './components/annotation/AnnotationDialog'; import { useQuestionTemplates } from '../questions/hooks/useQuestionTemplates'; import { useAnnotation } from './hooks/useAnnotation'; import { useQuestionEdit } from '../questions/hooks/useQuestionEdit'; import QuestionEditDialog from '../questions/components/QuestionEditDialog'; import TemplateFormDialog from '../questions/components/template/TemplateFormDialog'; export default function ImagesPage() { const { projectId } = useParams(); const router = useRouter(); const { t, i18n } = useTranslation(); const selectedModel = useAtomValue(selectedModelInfoAtom); const [loading, setLoading] = useState(false); const [images, setImages] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize] = useState(8); // 筛选条件 const [imageName, setImageName] = useState(''); const [hasQuestions, setHasQuestions] = useState('all'); const [hasDatasets, setHasDatasets] = useState('all'); // 视图模式 const [viewMode, setViewMode] = useState('grid'); // 选中状态(仅列表视图使用) const [selectedIds, setSelectedIds] = useState([]); // 对话框状态 const [importDialogOpen, setImportDialogOpen] = useState(false); const [questionDialogOpen, setQuestionDialogOpen] = useState(false); const [datasetDialogOpen, setDatasetDialogOpen] = useState(false); const [selectedImage, setSelectedImage] = useState(null); const [autoGenerateDialogOpen, setAutoGenerateDialogOpen] = useState(false); const [questionCount, setQuestionCount] = useState(3); // 问题模板和标注功能 (只获取图像类型的模板) const { templates, createTemplate } = useQuestionTemplates(projectId, 'image'); // 问题编辑 Hook const { editDialogOpen, editMode, editingQuestion, handleOpenCreateDialog, handleCloseDialog, handleSubmitQuestion } = useQuestionEdit(projectId, async () => { fetchImages(); if (annotationOpen && currentImage) { await refreshCurrentImage(); } toast.success(t('questions.operationSuccess')); }); // 模板管理状态 const [templateDialogOpen, setTemplateDialogOpen] = useState(false); // 获取图片列表 const fetchImages = async () => { try { setLoading(true); const params = new URLSearchParams({ page: page.toString(), pageSize: pageSize.toString() }); if (imageName) params.append('imageName', imageName); if (hasQuestions !== 'all') params.append('hasQuestions', hasQuestions); if (hasDatasets !== 'all') params.append('hasDatasets', hasDatasets); const response = await axios.get(`/api/projects/${projectId}/images?${params.toString()}`); setImages(response.data.data); setTotal(response.data.total); } catch (error) { console.error('Failed to fetch images:', error); toast.error(t('common.fetchError')); } finally { setLoading(false); } }; // 查找下一个有未标注问题的图片 const handleFindNextImage = async () => { try { const response = await axios.get(`/api/projects/${projectId}/images/next-unanswered`); return response.data.data || null; } catch (error) { console.error('查找下一个图片失败:', error); return null; } }; const { open: annotationOpen, saving: annotationSaving, loading: annotationLoading, currentImage, selectedTemplate, answer, setAnswer, handleTemplateChange, openAnnotation, closeAnnotation, saveAnnotation, refreshCurrentImage } = useAnnotation(projectId, fetchImages, handleFindNextImage); useEffect(() => { fetchImages(); }, [projectId, page, imageName, hasQuestions, hasDatasets]); useEffect(() => { setSelectedIds([]); }, [viewMode]); // 处理导入成功 const handleImportSuccess = () => { setImportDialogOpen(false); setPage(1); fetchImages(); }; // 处理生成问题 const handleGenerateQuestions = image => { setSelectedImage(image); setQuestionDialogOpen(true); }; // 处理生成数据集 const handleGenerateDataset = image => { setSelectedImage(image); setDatasetDialogOpen(true); }; // 删除图片 const handleDeleteImage = async imageId => { if (!confirm(t('images.deleteConfirm', { defaultValue: '确定要删除这张图片吗?' }))) { return; } try { await axios.delete(`/api/projects/${projectId}/images?imageId=${imageId}`); toast.success(t('images.deleteSuccess', { defaultValue: '删除成功' })); fetchImages(); } catch (error) { console.error('Failed to delete image:', error); toast.error(t('images.deleteFailed', { defaultValue: '删除失败' })); } }; // 批量删除图片 const handleBatchDelete = async () => { if (selectedIds.length === 0) { toast.error(t('images.selectImagesToDelete', { defaultValue: '请选择要删除的图片' })); return; } if ( !confirm( t('images.batchDeleteConfirm', { defaultValue: `确定要删除选中的 ${selectedIds.length} 张图片吗?`, count: selectedIds.length }) ) ) { return; } try { setLoading(true); let successCount = 0; let failCount = 0; // 逐个调用删除接口 for (const imageId of selectedIds) { try { await axios.delete(`/api/projects/${projectId}/images?imageId=${imageId}`); successCount++; } catch (error) { console.error(`Failed to delete image ${imageId}:`, error); failCount++; } } // 显示结果 if (failCount === 0) { toast.success( t('images.batchDeleteSuccess', { defaultValue: `成功删除 ${successCount} 张图片`, count: successCount }) ); } else { toast.warning( t('images.batchDeletePartialSuccess', { defaultValue: `成功删除 ${successCount} 张,失败 ${failCount} 张`, success: successCount, fail: failCount }) ); } // 清空选中状态并刷新列表 setSelectedIds([]); fetchImages(); } catch (error) { console.error('Batch delete failed:', error); toast.error(t('images.batchDeleteFailed', { defaultValue: '批量删除失败' })); } finally { setLoading(false); } }; // 处理自动提取问题 const handleAutoGenerateQuestions = () => { if (!selectedModel) { toast.error(t('images.selectModelFirst')); return; } if (selectedModel.type !== 'vision') { toast.error(t('images.visionModelRequired')); return; } setAutoGenerateDialogOpen(true); }; // 确认创建自动提取任务 const handleConfirmAutoGenerate = async () => { // 验证问题数量 if (questionCount < 1 || questionCount > 10) { toast.error(t('images.countRange')); return; } try { setAutoGenerateDialogOpen(false); const response = await axios.post(`/api/projects/${projectId}/tasks`, { taskType: 'image-question-generation', modelInfo: selectedModel, language: i18n.language, note: { questionCount } }); if (response.data.code === 0) { toast.success(t('images.taskCreated')); // 跳转到任务管理页面 router.push(`/projects/${projectId}/tasks`); } else { toast.error(response.data.error || t('images.taskCreateFailed')); } } catch (error) { console.error('Failed to create auto-generate task:', error); toast.error(t('images.taskCreateFailed')); } }; // 模板管理函数 const handleOpenCreateTemplateDialog = () => { setTemplateDialogOpen(true); }; const handleCloseTemplateDialog = () => { setTemplateDialogOpen(false); }; const handleSubmitTemplate = async data => { try { await createTemplate(data); handleCloseTemplateDialog(); fetchImages(); if (annotationOpen && currentImage) { await refreshCurrentImage(); } toast.success(t('questions.operationSuccess')); } catch (error) { console.error('Failed to save template:', error); } }; return ( {/* 页面头部 */} {t('images.title', { defaultValue: '图片管理' })} {viewMode === 'list' && selectedIds.length > 0 && ( )} {/* 筛选区域 */} {/* 图片列表 */} {loading ? ( ) : viewMode === 'grid' ? ( ) : ( )} setImportDialogOpen(false)} onSuccess={handleImportSuccess} /> setQuestionDialogOpen(false)} onSuccess={fetchImages} /> setDatasetDialogOpen(false)} onSuccess={fetchImages} /> setAutoGenerateDialogOpen(false)} maxWidth="sm" fullWidth> {t('images.autoGenerateQuestions')} {t('images.autoGenerateConfirm')} setQuestionCount(parseInt(e.target.value) || 1)} inputProps={{ min: 1, max: 10 }} helperText={t('images.questionCountHelp')} sx={{ mb: 2 }} /> {t('images.currentModel')}: {selectedModel?.modelName || t('common.none')} saveAnnotation(false)} onSaveAndContinue={() => saveAnnotation(true)} saving={annotationSaving} loading={annotationLoading} onOpenCreateQuestion={handleOpenCreateDialog} onOpenCreateTemplate={handleOpenCreateTemplateDialog} /> {/* 问题编辑对话框 */} {/* 问题模板对话框 */} ); }