diff --git a/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx b/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx index 7fddb0a50..4097545e5 100644 --- a/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx +++ b/web/src/components/table/task-logs/TaskLogsColumnDefs.jsx @@ -240,6 +240,7 @@ export const getTaskLogsColumns = ({ openContentModal, isAdminUser, openVideoModal, + openAudioModal, }) => { return [ { @@ -386,6 +387,26 @@ export const getTaskLogsColumns = ({ dataIndex: 'fail_reason', fixed: 'right', render: (text, record, index) => { + // Suno audio preview + const isSunoSuccess = + record.platform === 'suno' && + record.status === 'SUCCESS' && + Array.isArray(record.data) && + record.data.some((c) => c.audio_url); + if (isSunoSuccess) { + return ( + { + e.preventDefault(); + openAudioModal(record.data); + }} + > + {t('点击预览音乐')} + + ); + } + // 视频预览:优先使用 result_url,兼容旧数据 fail_reason 中的 URL const isVideoTask = record.action === TASK_ACTION_GENERATE || diff --git a/web/src/components/table/task-logs/TaskLogsTable.jsx b/web/src/components/table/task-logs/TaskLogsTable.jsx index b62e15bd2..b3cec8ccc 100644 --- a/web/src/components/table/task-logs/TaskLogsTable.jsx +++ b/web/src/components/table/task-logs/TaskLogsTable.jsx @@ -40,6 +40,7 @@ const TaskLogsTable = (taskLogsData) => { copyText, openContentModal, openVideoModal, + openAudioModal, showUserInfoFunc, isAdminUser, t, @@ -54,10 +55,11 @@ const TaskLogsTable = (taskLogsData) => { copyText, openContentModal, openVideoModal, + openAudioModal, showUserInfoFunc, isAdminUser, }); - }, [t, COLUMN_KEYS, copyText, openContentModal, openVideoModal, showUserInfoFunc, isAdminUser]); + }, [t, COLUMN_KEYS, copyText, openContentModal, openVideoModal, openAudioModal, showUserInfoFunc, isAdminUser]); // Filter columns based on visibility settings const getVisibleColumns = () => { diff --git a/web/src/components/table/task-logs/index.jsx b/web/src/components/table/task-logs/index.jsx index bc5b91787..07c387123 100644 --- a/web/src/components/table/task-logs/index.jsx +++ b/web/src/components/table/task-logs/index.jsx @@ -25,6 +25,7 @@ import TaskLogsActions from './TaskLogsActions'; import TaskLogsFilters from './TaskLogsFilters'; import ColumnSelectorModal from './modals/ColumnSelectorModal'; import ContentModal from './modals/ContentModal'; +import AudioPreviewModal from './modals/AudioPreviewModal'; import { useTaskLogsData } from '../../../hooks/task-logs/useTaskLogsData'; import { useIsMobile } from '../../../hooks/common/useIsMobile'; import { createCardProPagination } from '../../../helpers/utils'; @@ -45,6 +46,11 @@ const TaskLogsPage = () => { modalContent={taskLogsData.videoUrl} isVideo={true} /> + . + +For commercial licensing, please contact support@quantumnous.com +*/ + +import React, { useState, useRef, useEffect } from 'react'; +import { Modal, Typography, Tag, Button } from '@douyinfe/semi-ui'; +import { IconExternalOpen, IconCopy } from '@douyinfe/semi-icons'; +import { useTranslation } from 'react-i18next'; + +const { Text, Title } = Typography; + +const formatDuration = (seconds) => { + if (!seconds || seconds <= 0) return '--:--'; + const m = Math.floor(seconds / 60); + const s = Math.floor(seconds % 60); + return `${m}:${s.toString().padStart(2, '0')}`; +}; + +const AudioClipCard = ({ clip }) => { + const { t } = useTranslation(); + const [hasError, setHasError] = useState(false); + const audioRef = useRef(null); + + useEffect(() => { + setHasError(false); + }, [clip.audio_url]); + + const title = clip.title || t('未命名'); + const tags = clip.tags || clip.metadata?.tags || ''; + const duration = clip.duration || clip.metadata?.duration; + const imageUrl = clip.image_url || clip.image_large_url; + const audioUrl = clip.audio_url; + + return ( +
+ {imageUrl && ( + {title} { + e.target.style.display = 'none'; + }} + /> + )} +
+
+ + {title} + + {duration > 0 && ( + + {formatDuration(duration)} + + )} +
+ + {tags && ( +
+ + {tags} + +
+ )} + + {hasError ? ( +
+ + {t('音频无法播放')} + + + +
+ ) : ( +
+
+ ); +}; + +const AudioPreviewModal = ({ isModalOpen, setIsModalOpen, audioClips }) => { + const { t } = useTranslation(); + const clips = Array.isArray(audioClips) ? audioClips : []; + + return ( + setIsModalOpen(false)} + onCancel={() => setIsModalOpen(false)} + closable={null} + footer={null} + bodyStyle={{ + maxHeight: '70vh', + overflow: 'auto', + padding: '16px', + }} + width={560} + > + {clips.length === 0 ? ( + {t('无')} + ) : ( +
+ {clips.map((clip, idx) => ( + + ))} +
+ )} +
+ ); +}; + +export default AudioPreviewModal; diff --git a/web/src/hooks/task-logs/useTaskLogsData.js b/web/src/hooks/task-logs/useTaskLogsData.js index a461e3522..6ba3de388 100644 --- a/web/src/hooks/task-logs/useTaskLogsData.js +++ b/web/src/hooks/task-logs/useTaskLogsData.js @@ -72,6 +72,10 @@ export const useTaskLogsData = () => { const [isVideoModalOpen, setIsVideoModalOpen] = useState(false); const [videoUrl, setVideoUrl] = useState(''); + // Audio preview modal state + const [isAudioModalOpen, setIsAudioModalOpen] = useState(false); + const [audioClips, setAudioClips] = useState([]); + // User info modal state const [showUserInfo, setShowUserInfoModal] = useState(false); const [userInfoData, setUserInfoData] = useState(null); @@ -277,6 +281,11 @@ export const useTaskLogsData = () => { setIsVideoModalOpen(true); }; + const openAudioModal = (clips) => { + setAudioClips(clips); + setIsAudioModalOpen(true); + }; + // User info function const showUserInfoFunc = async (userId) => { if (!isAdminUser) { @@ -319,6 +328,11 @@ export const useTaskLogsData = () => { setIsVideoModalOpen, videoUrl, + // Audio preview modal + isAudioModalOpen, + setIsAudioModalOpen, + audioClips, + // Form state formApi, setFormApi, @@ -351,7 +365,8 @@ export const useTaskLogsData = () => { refresh, copyText, openContentModal, - openVideoModal, // 新增 + openVideoModal, + openAudioModal, enrichLogs, syncPageData, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 4c37baa76..e06c68362 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1634,6 +1634,9 @@ "点击查看差异": "Click to view differences", "点击此处": "click here", "点击预览视频": "Click to preview video", + "点击预览音乐": "Click to preview music", + "音乐预览": "Music Preview", + "音频无法播放": "Audio cannot be played", "点击验证按钮,使用您的生物特征或安全密钥": "Click the verification button and use your biometrics or security key", "版权所有": "All rights reserved", "状态": "Status", diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 4fdca8b86..2843728b8 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -1646,6 +1646,9 @@ "点击查看差异": "Cliquez pour voir les différences", "点击此处": "cliquez ici", "点击预览视频": "Cliquez pour prévisualiser la vidéo", + "点击预览音乐": "Cliquez pour écouter la musique", + "音乐预览": "Aperçu musical", + "音频无法播放": "Impossible de lire l'audio", "点击验证按钮,使用您的生物特征或安全密钥": "Cliquez sur le bouton de vérification pour utiliser vos caractéristiques biométriques ou votre clé de sécurité", "版权所有": "Tous droits réservés", "状态": "Statut", diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index d9f739a5d..d18a62923 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -1631,6 +1631,9 @@ "点击查看差异": "差分を表示", "点击此处": "こちらをクリック", "点击预览视频": "動画をプレビュー", + "点击预览音乐": "音楽をプレビュー", + "音乐预览": "音楽プレビュー", + "音频无法播放": "音声を再生できません", "点击验证按钮,使用您的生物特征或安全密钥": "認証ボタンをクリックし、生体情報またはセキュリティキーを使用してください", "版权所有": "All rights reserved", "状态": "ステータス", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index cb2dec1c6..099f405c9 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -1657,6 +1657,9 @@ "点击查看差异": "Нажмите для просмотра различий", "点击此处": "Нажмите здесь", "点击预览视频": "Нажмите для предварительного просмотра видео", + "点击预览音乐": "Нажмите для прослушивания музыки", + "音乐预览": "Предварительное прослушивание", + "音频无法播放": "Не удалось воспроизвести аудио", "点击验证按钮,使用您的生物特征或安全密钥": "Нажмите кнопку проверки, используйте ваши биометрические данные или ключ безопасности", "版权所有": "Все права защищены", "状态": "Статус", diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 4b81fac6e..d2602efdf 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -1773,6 +1773,9 @@ "点击链接重置密码": "Nhấp vào liên kết để đặt lại mật khẩu", "点击阅读": "Nhấp để đọc", "点击预览视频": "Nhấp để xem trước video", + "点击预览音乐": "Nhấp để nghe nhạc", + "音乐预览": "Xem trước nhạc", + "音频无法播放": "Không thể phát âm thanh", "点击验证按钮,使用您的生物特征或安全密钥": "Nhấp vào nút xác minh và sử dụng sinh trắc học hoặc khóa bảo mật của bạn", "版": "Phiên bản", "版本": "Phiên bản", diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index a41db52d0..d067ad569 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -1624,6 +1624,9 @@ "点击查看差异": "点击查看差异", "点击此处": "点击此处", "点击预览视频": "点击预览视频", + "点击预览音乐": "点击预览音乐", + "音乐预览": "音乐预览", + "音频无法播放": "音频无法播放", "点击验证按钮,使用您的生物特征或安全密钥": "点击验证按钮,使用您的生物特征或安全密钥", "版权所有": "版权所有", "状态": "状态", diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index 7ca22d2a6..26f7092b7 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -1628,6 +1628,9 @@ "点击查看差异": "點擊查看差異", "点击此处": "點擊此處", "点击预览视频": "點擊預覽影片", + "点击预览音乐": "點擊預覽音樂", + "音乐预览": "音樂預覽", + "音频无法播放": "音訊無法播放", "点击验证按钮,使用您的生物特征或安全密钥": "點擊驗證按鈕,使用您的生物特徵或安全密鑰", "版权所有": "版權所有", "状态": "狀態",