/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; import { Progress, Tag, Typography } from '@douyinfe/semi-ui'; import { Music, FileText, HelpCircle, CheckCircle, Pause, Clock, Play, XCircle, Loader, List, Hash, Video, Sparkles, } from 'lucide-react'; import { TASK_ACTION_FIRST_TAIL_GENERATE, TASK_ACTION_GENERATE, TASK_ACTION_REFERENCE_GENERATE, TASK_ACTION_TEXT_GENERATE, } from '../../../constants/common.constant'; import { CHANNEL_OPTIONS } from '../../../constants/channel.constants'; const colors = [ 'amber', 'blue', 'cyan', 'green', 'grey', 'indigo', 'light-blue', 'lime', 'orange', 'pink', 'purple', 'red', 'teal', 'violet', 'yellow', ]; // Render functions const renderTimestamp = (timestampInSeconds) => { const date = new Date(timestampInSeconds * 1000); // 从秒转换为毫秒 const year = date.getFullYear(); // 获取年份 const month = ('0' + (date.getMonth() + 1)).slice(-2); // 获取月份,从0开始需要+1,并保证两位数 const day = ('0' + date.getDate()).slice(-2); // 获取日期,并保证两位数 const hours = ('0' + date.getHours()).slice(-2); // 获取小时,并保证两位数 const minutes = ('0' + date.getMinutes()).slice(-2); // 获取分钟,并保证两位数 const seconds = ('0' + date.getSeconds()).slice(-2); // 获取秒钟,并保证两位数 return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; // 格式化输出 }; function renderDuration(submit_time, finishTime) { if (!submit_time || !finishTime) return 'N/A'; const durationSec = finishTime - submit_time; const color = durationSec > 60 ? 'red' : 'green'; // 返回带有样式的颜色标签 return ( }> {durationSec} 秒 ); } const renderType = (type, t) => { switch (type) { case 'MUSIC': return ( }> {t('生成音乐')} ); case 'LYRICS': return ( }> {t('生成歌词')} ); case TASK_ACTION_GENERATE: return ( }> {t('图生视频')} ); case TASK_ACTION_TEXT_GENERATE: return ( }> {t('文生视频')} ); case TASK_ACTION_FIRST_TAIL_GENERATE: return ( }> {t('首尾生视频')} ); case TASK_ACTION_REFERENCE_GENERATE: return ( }> {t('参照生视频')} ); default: return ( }> {t('未知')} ); } }; const renderPlatform = (platform, t) => { let option = CHANNEL_OPTIONS.find( (opt) => String(opt.value) === String(platform), ); if (option) { return ( }> {option.label} ); } switch (platform) { case 'suno': return ( }> Suno ); default: return ( }> {t('未知')} ); } }; const renderStatus = (type, t) => { switch (type) { case 'SUCCESS': return ( } > {t('成功')} ); case 'NOT_START': return ( }> {t('未启动')} ); case 'SUBMITTED': return ( }> {t('队列中')} ); case 'IN_PROGRESS': return ( }> {t('执行中')} ); case 'FAILURE': return ( }> {t('失败')} ); case 'QUEUED': return ( }> {t('排队中')} ); case 'UNKNOWN': return ( }> {t('未知')} ); case '': return ( }> {t('正在提交')} ); default: return ( }> {t('未知')} ); } }; export const getTaskLogsColumns = ({ t, COLUMN_KEYS, copyText, openContentModal, isAdminUser, openVideoModal, }) => { return [ { key: COLUMN_KEYS.SUBMIT_TIME, title: t('提交时间'), dataIndex: 'submit_time', render: (text, record, index) => { return
{text ? renderTimestamp(text) : '-'}
; }, }, { key: COLUMN_KEYS.FINISH_TIME, title: t('结束时间'), dataIndex: 'finish_time', render: (text, record, index) => { return
{text ? renderTimestamp(text) : '-'}
; }, }, { key: COLUMN_KEYS.DURATION, title: t('花费时间'), dataIndex: 'finish_time', render: (finish, record) => { return <>{finish ? renderDuration(record.submit_time, finish) : '-'}; }, }, { key: COLUMN_KEYS.CHANNEL, title: t('渠道'), dataIndex: 'channel_id', render: (text, record, index) => { return isAdminUser ? (
} onClick={() => { copyText(text); }} > {text}
) : ( <> ); }, }, { key: COLUMN_KEYS.PLATFORM, title: t('平台'), dataIndex: 'platform', render: (text, record, index) => { return
{renderPlatform(text, t)}
; }, }, { key: COLUMN_KEYS.TYPE, title: t('类型'), dataIndex: 'action', render: (text, record, index) => { return
{renderType(text, t)}
; }, }, { key: COLUMN_KEYS.TASK_ID, title: t('任务ID'), dataIndex: 'task_id', render: (text, record, index) => { return ( { openContentModal(JSON.stringify(record, null, 2)); }} >
{text}
); }, }, { key: COLUMN_KEYS.TASK_STATUS, title: t('任务状态'), dataIndex: 'status', render: (text, record, index) => { return
{renderStatus(text, t)}
; }, }, { key: COLUMN_KEYS.PROGRESS, title: t('进度'), dataIndex: 'progress', render: (text, record, index) => { return (
{isNaN(text?.replace('%', '')) ? ( text || '-' ) : ( )}
); }, }, { key: COLUMN_KEYS.FAIL_REASON, title: t('详情'), dataIndex: 'fail_reason', fixed: 'right', render: (text, record, index) => { // 仅当为视频生成任务且成功,且 fail_reason 是 URL 时显示可点击链接 const isVideoTask = record.action === TASK_ACTION_GENERATE || record.action === TASK_ACTION_TEXT_GENERATE || record.action === TASK_ACTION_FIRST_TAIL_GENERATE || record.action === TASK_ACTION_REFERENCE_GENERATE; const isSuccess = record.status === 'SUCCESS'; const isUrl = typeof text === 'string' && /^https?:\/\//.test(text); if (isSuccess && isVideoTask && isUrl) { return ( { e.preventDefault(); openVideoModal(text); }} > {t('点击预览视频')} ); } if (!text) { return t('无'); } return ( { openContentModal(text); }} > {text} ); }, }, ]; };