| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- /*
- 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 <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact [email protected]
- */
- 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 (
- <Tag color={color} shape='circle' prefixIcon={<Clock size={14} />}>
- {durationSec} 秒
- </Tag>
- );
- }
- const renderType = (type, t) => {
- switch (type) {
- case 'MUSIC':
- return (
- <Tag color='grey' shape='circle' prefixIcon={<Music size={14} />}>
- {t('生成音乐')}
- </Tag>
- );
- case 'LYRICS':
- return (
- <Tag color='pink' shape='circle' prefixIcon={<FileText size={14} />}>
- {t('生成歌词')}
- </Tag>
- );
- case TASK_ACTION_GENERATE:
- return (
- <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
- {t('图生视频')}
- </Tag>
- );
- case TASK_ACTION_TEXT_GENERATE:
- return (
- <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
- {t('文生视频')}
- </Tag>
- );
- case TASK_ACTION_FIRST_TAIL_GENERATE:
- return (
- <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
- {t('首尾生视频')}
- </Tag>
- );
- case TASK_ACTION_REFERENCE_GENERATE:
- return (
- <Tag color='blue' shape='circle' prefixIcon={<Sparkles size={14} />}>
- {t('参照生视频')}
- </Tag>
- );
- default:
- return (
- <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
- {t('未知')}
- </Tag>
- );
- }
- };
- const renderPlatform = (platform, t) => {
- let option = CHANNEL_OPTIONS.find(
- (opt) => String(opt.value) === String(platform),
- );
- if (option) {
- return (
- <Tag color={option.color} shape='circle' prefixIcon={<Video size={14} />}>
- {option.label}
- </Tag>
- );
- }
- switch (platform) {
- case 'suno':
- return (
- <Tag color='green' shape='circle' prefixIcon={<Music size={14} />}>
- Suno
- </Tag>
- );
- default:
- return (
- <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
- {t('未知')}
- </Tag>
- );
- }
- };
- const renderStatus = (type, t) => {
- switch (type) {
- case 'SUCCESS':
- return (
- <Tag
- color='green'
- shape='circle'
- prefixIcon={<CheckCircle size={14} />}
- >
- {t('成功')}
- </Tag>
- );
- case 'NOT_START':
- return (
- <Tag color='grey' shape='circle' prefixIcon={<Pause size={14} />}>
- {t('未启动')}
- </Tag>
- );
- case 'SUBMITTED':
- return (
- <Tag color='yellow' shape='circle' prefixIcon={<Clock size={14} />}>
- {t('队列中')}
- </Tag>
- );
- case 'IN_PROGRESS':
- return (
- <Tag color='blue' shape='circle' prefixIcon={<Play size={14} />}>
- {t('执行中')}
- </Tag>
- );
- case 'FAILURE':
- return (
- <Tag color='red' shape='circle' prefixIcon={<XCircle size={14} />}>
- {t('失败')}
- </Tag>
- );
- case 'QUEUED':
- return (
- <Tag color='orange' shape='circle' prefixIcon={<List size={14} />}>
- {t('排队中')}
- </Tag>
- );
- case 'UNKNOWN':
- return (
- <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
- {t('未知')}
- </Tag>
- );
- case '':
- return (
- <Tag color='grey' shape='circle' prefixIcon={<Loader size={14} />}>
- {t('正在提交')}
- </Tag>
- );
- default:
- return (
- <Tag color='white' shape='circle' prefixIcon={<HelpCircle size={14} />}>
- {t('未知')}
- </Tag>
- );
- }
- };
- 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 <div>{text ? renderTimestamp(text) : '-'}</div>;
- },
- },
- {
- key: COLUMN_KEYS.FINISH_TIME,
- title: t('结束时间'),
- dataIndex: 'finish_time',
- render: (text, record, index) => {
- return <div>{text ? renderTimestamp(text) : '-'}</div>;
- },
- },
- {
- 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 ? (
- <div>
- <Tag
- color={colors[parseInt(text) % colors.length]}
- size='large'
- shape='circle'
- prefixIcon={<Hash size={14} />}
- onClick={() => {
- copyText(text);
- }}
- >
- {text}
- </Tag>
- </div>
- ) : (
- <></>
- );
- },
- },
- {
- key: COLUMN_KEYS.PLATFORM,
- title: t('平台'),
- dataIndex: 'platform',
- render: (text, record, index) => {
- return <div>{renderPlatform(text, t)}</div>;
- },
- },
- {
- key: COLUMN_KEYS.TYPE,
- title: t('类型'),
- dataIndex: 'action',
- render: (text, record, index) => {
- return <div>{renderType(text, t)}</div>;
- },
- },
- {
- key: COLUMN_KEYS.TASK_ID,
- title: t('任务ID'),
- dataIndex: 'task_id',
- render: (text, record, index) => {
- return (
- <Typography.Text
- ellipsis={{ showTooltip: true }}
- onClick={() => {
- openContentModal(JSON.stringify(record, null, 2));
- }}
- >
- <div>{text}</div>
- </Typography.Text>
- );
- },
- },
- {
- key: COLUMN_KEYS.TASK_STATUS,
- title: t('任务状态'),
- dataIndex: 'status',
- render: (text, record, index) => {
- return <div>{renderStatus(text, t)}</div>;
- },
- },
- {
- key: COLUMN_KEYS.PROGRESS,
- title: t('进度'),
- dataIndex: 'progress',
- render: (text, record, index) => {
- return (
- <div>
- {isNaN(text?.replace('%', '')) ? (
- text || '-'
- ) : (
- <Progress
- stroke={
- record.status === 'FAILURE'
- ? 'var(--semi-color-warning)'
- : null
- }
- percent={text ? parseInt(text.replace('%', '')) : 0}
- showInfo={true}
- aria-label='task progress'
- style={{ minWidth: '160px' }}
- />
- )}
- </div>
- );
- },
- },
- {
- 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 (
- <a
- href='#'
- onClick={(e) => {
- e.preventDefault();
- openVideoModal(text);
- }}
- >
- {t('点击预览视频')}
- </a>
- );
- }
- if (!text) {
- return t('无');
- }
- return (
- <Typography.Text
- ellipsis={{ showTooltip: true }}
- style={{ width: 100 }}
- onClick={() => {
- openContentModal(text);
- }}
- >
- {text}
- </Typography.Text>
- );
- },
- },
- ];
- };
|