'use client'; import React from 'react'; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography, CircularProgress, Box, TablePagination, Tooltip } from '@mui/material'; import { useTranslation } from 'react-i18next'; import { formatDistanceToNow } from 'date-fns'; import { zhCN, enUS } from 'date-fns/locale'; import TaskStatusChip from './TaskStatusChip'; import TaskProgress from './TaskProgress'; import TaskActions from './TaskActions'; export default function TasksTable({ tasks, loading, handleAbortTask, handleDeleteTask, page, rowsPerPage, handleChangePage, handleChangeRowsPerPage, totalCount }) { const { t, i18n } = useTranslation(); const formatDate = dateString => { if (!dateString) return '-'; const date = new Date(dateString); return formatDistanceToNow(date, { addSuffix: true, locale: i18n.language === 'zh-CN' ? zhCN : enUS }); }; const calculateDuration = (startTimeStr, endTimeStr) => { if (!startTimeStr || !endTimeStr) return '-'; try { const startTime = new Date(startTimeStr); const endTime = new Date(endTimeStr); const duration = endTime - startTime; const seconds = Math.floor(duration / 1000); if (seconds < 60) { return t('tasks.duration.seconds', { seconds }); } if (seconds < 3600) { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return t('tasks.duration.minutes', { minutes, seconds: remainingSeconds }); } const hours = Math.floor(seconds / 3600); const remainingMinutes = Math.floor((seconds % 3600) / 60); return t('tasks.duration.hours', { hours, minutes: remainingMinutes }); } catch (error) { console.error('Failed to calculate duration:', error); return '-'; } }; const parseModelInfo = modelInfoString => { let modelInfo = ''; try { const parsedModel = JSON.parse(modelInfoString); modelInfo = parsedModel.modelName || parsedModel.name || '-'; } catch { modelInfo = modelInfoString || '-'; } return modelInfo; }; const toTaskTypeLabel = taskType => { if (!taskType) return '-'; return String(taskType) .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); }; const getLocalizedTaskType = taskType => { return t(`tasks.types.${taskType}`, { defaultValue: toTaskTypeLabel(taskType) }); }; const parseJsonSafely = input => { if (!input || typeof input !== 'string') return null; try { return JSON.parse(input); } catch { return null; } }; const formatTaskNote = task => { const note = String(task?.note || '').trim(); if (!note) return '-'; const noteJson = parseJsonSafely(note); if (noteJson) { if (Array.isArray(noteJson.chunkIds)) { return t('tasks.notes.selectedChunks', { count: noteJson.chunkIds.length }); } if (Array.isArray(noteJson.fileList)) { return t('tasks.notes.fileBatch', { count: noteJson.fileList.length, strategy: noteJson.strategy || '-' }); } return t('tasks.notes.jsonParams'); } if (note === 'No chunks require question generation' || note.startsWith('No chunks require question gen')) { return t('tasks.notes.noChunksQuestion'); } if (note === 'No chunks require cleaning' || note.startsWith('No chunks require clean')) { return t('tasks.notes.noChunksCleaning'); } if (note.startsWith('Processing failed:')) { return t('tasks.notes.processingFailed', { error: note.replace('Processing failed:', '').trim() }); } const summaryMatch = note.match(/Processed:\s*(\d+)\/(\d+),\s*succeeded:\s*(\d+),\s*failed:\s*(\d+)/i); if (summaryMatch) { const [, processed, total, succeeded, failed] = summaryMatch; const questionMatch = note.match(/questions generated:\s*(\d+)/i); if (questionMatch) { return t('tasks.notes.questionSummary', { processed, total, succeeded, failed, generated: questionMatch[1] }); } const datasetMatch = note.match(/datasets generated:\s*(\d+)/i); if (datasetMatch) { return t('tasks.notes.datasetSummary', { processed, total, succeeded, failed, generated: datasetMatch[1] }); } const cleaningMatch = note.match(/total original length:\s*(\d+),\s*total cleaned length:\s*(\d+)/i); if (cleaningMatch) { return t('tasks.notes.cleaningSummary', { processed, total, succeeded, failed, original: cleaningMatch[1], cleaned: cleaningMatch[2] }); } return t('tasks.notes.genericSummary', { processed, total, succeeded, failed }); } return note; }; const truncateNote = (note, maxLength = 48) => { if (!note) return '-'; if (note.length <= maxLength) return note; return `${note.substring(0, maxLength)}...`; }; return ( {t('tasks.table.type')} {t('tasks.table.status')} {t('tasks.table.progress')} {t('tasks.table.createTime')} {t('tasks.table.duration')} {t('tasks.table.model')} {t('tasks.table.note')} {t('tasks.table.actions')} {loading && tasks.length === 0 ? ( {t('tasks.loading')} ) : tasks.length === 0 ? ( {t('tasks.empty')} ) : ( tasks.map(task => { const noteText = formatTaskNote(task); return ( {getLocalizedTaskType(task.taskType)} {formatDate(task.createAt)} {task.endTime ? calculateDuration(task.startTime, task.endTime) : '-'} {parseModelInfo(task.modelInfo)} {noteText !== '-' ? ( {truncateNote(noteText)} ) : ( '-' )} ); }) )}
{tasks.length > 0 && ( { const calculatedFrom = page * rowsPerPage + 1; const calculatedTo = Math.min((page + 1) * rowsPerPage, count); return t('datasets.pagination', { from: calculatedFrom, to: calculatedTo, count }); }} /> )}
); }