| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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', |
| | ]; |
| |
|
| | |
| | const renderTimestamp = (timestampInSeconds) => { |
| | const date = new Date(timestampInSeconds * 1000); |
| |
|
| | const year = date.getFullYear(); |
| | const month = ('0' + (date.getMonth() + 1)).slice(-2); |
| | 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) => { |
| | |
| | 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> |
| | ); |
| | }, |
| | }, |
| | ]; |
| | }; |
| |
|