From 14d60264c7191d42c3868ef97f1c5981683c49db Mon Sep 17 00:00:00 2001 From: jialin Date: Wed, 16 Jul 2025 12:27:54 +0800 Subject: [PATCH] fix: wav format audio, #2317 --- src/components/upload-audio/index.tsx | 23 ++++++++++++++++++- src/locales/en-US/playground.ts | 4 +++- src/locales/ja-JP/playground.ts | 7 ++++-- src/locales/ru-RU/playground.ts | 7 ++++-- src/locales/zh-CN/playground.ts | 3 ++- .../playground/components/message-input.tsx | 15 ++++++------ .../multiple-chat/message-actions.tsx | 13 +++++------ src/utils/index.ts | 6 +++-- src/utils/load-audio-file.ts | 14 +++++++++++ 9 files changed, 68 insertions(+), 24 deletions(-) diff --git a/src/components/upload-audio/index.tsx b/src/components/upload-audio/index.tsx index 0319475a..b5316394 100644 --- a/src/components/upload-audio/index.tsx +++ b/src/components/upload-audio/index.tsx @@ -1,7 +1,8 @@ import { UploadOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Tooltip, Upload } from 'antd'; +import { Button, Tooltip, Upload, message } from 'antd'; import React from 'react'; +import { convertFileSize } from '../../utils'; interface UploadAudioProps { accept?: string; @@ -10,17 +11,36 @@ interface UploadAudioProps { icon?: React.ReactNode; size?: 'small' | 'middle' | 'large'; shape?: 'circle' | 'round' | 'default'; + maxFileSize?: number; // in bytes onChange?: (data: { file: any; fileList: any[] }) => void; } const UploadAudio: React.FC = (props) => { + const [messageApi, contextHolder] = message.useMessage(); const { icon, accept, type, size = 'large', shape = 'circle' } = props; const intl = useIntl(); const beforeUpload = (file: any) => { return false; }; + const isFileSizeValid = (file: File) => { + if (props.maxFileSize && file.size > props.maxFileSize) { + messageApi.open({ + type: 'warning', + content: intl.formatMessage( + { id: 'playground.uploadfile.sizeError' }, + { size: `${convertFileSize(props.maxFileSize)}` } // Convert bytes to MB + ) + }); + return false; + } + return true; + }; + const handleOnChange = (data: { file: any; fileList: any }) => { + if (!isFileSizeValid(data.file)) { + return; + } props.onChange?.(data); }; @@ -57,6 +77,7 @@ const UploadAudio: React.FC = (props) => { > + {contextHolder} ); }; diff --git a/src/locales/en-US/playground.ts b/src/locales/en-US/playground.ts index 0169d575..71b1ccd7 100644 --- a/src/locales/en-US/playground.ts +++ b/src/locales/en-US/playground.ts @@ -154,5 +154,7 @@ export default { 'If the expected model isn’t showing up, make sure it’s running and correctly categorized. If the category is incorrect, you can manually adjust it in the model’s settings.', 'playground.rerank.query.validate': 'The query is required.', 'playground.image.generate.error': - 'Something went wrong. The image could not be generated.' + 'Something went wrong. The image could not be generated.', + 'playground.uploadfile.sizeError': + 'File size exceeds the limit. Maximum allowed: {size}.' }; diff --git a/src/locales/ja-JP/playground.ts b/src/locales/ja-JP/playground.ts index a72ffa50..736e00b5 100644 --- a/src/locales/ja-JP/playground.ts +++ b/src/locales/ja-JP/playground.ts @@ -157,10 +157,13 @@ export default { '期待するモデルが表示されない場合は、モデルが実行中で正しく分類されていることを確認してください。カテゴリが間違っている場合は、モデルの設定で手動で調整できます。', 'playground.rerank.query.validate': 'The query is required.', 'playground.image.generate.error': - 'Something went wrong. The image could not be generated.' + 'Something went wrong. The image could not be generated.', + 'playground.uploadfile.sizeError': + 'File size exceeds the limit. Maximum allowed: {size}.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'playground.rerank.query.validate': 'The query is required.' -// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.' +// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.', +// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/playground.ts b/src/locales/ru-RU/playground.ts index a8cecd09..65ac32d6 100644 --- a/src/locales/ru-RU/playground.ts +++ b/src/locales/ru-RU/playground.ts @@ -151,10 +151,13 @@ export default { 'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.', 'playground.rerank.query.validate': 'The query is required.', 'playground.image.generate.error': - 'Something went wrong. The image could not be generated.' + 'Something went wrong. The image could not be generated.', + 'playground.uploadfile.sizeError': + 'File size exceeds the limit. Maximum allowed: {size}.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'playground.rerank.query.validate': 'The query is required.' -// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.' +// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.', +// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/playground.ts b/src/locales/zh-CN/playground.ts index 57ae4785..02598f71 100644 --- a/src/locales/zh-CN/playground.ts +++ b/src/locales/zh-CN/playground.ts @@ -147,5 +147,6 @@ export default { 'playground.model.noavailable.tips2': '若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。', 'playground.rerank.query.validate': '查询内容不能为空', - 'playground.image.generate.error': '出了一点问题,图片未能生成。' + 'playground.image.generate.error': '出了一点问题,图片未能生成。', + 'playground.uploadfile.sizeError': '上传的文件大小超过限制,最大允许 {size}' }; diff --git a/src/pages/playground/components/message-input.tsx b/src/pages/playground/components/message-input.tsx index dae1104c..f7511e52 100644 --- a/src/pages/playground/components/message-input.tsx +++ b/src/pages/playground/components/message-input.tsx @@ -2,7 +2,11 @@ import SimpleAudio from '@/components/audio-player/simple-audio'; import IconFont from '@/components/icon-font'; import UploadAudio from '@/components/upload-audio'; import HotKeys, { KeyMap } from '@/config/hotkeys'; -import { convertFileToBase64, readAudioFile } from '@/utils/load-audio-file'; +import { + audioTypeMap, + convertFileToBase64, + readAudioFile +} from '@/utils/load-audio-file'; import { ClearOutlined, CustomerServiceOutlined, @@ -28,12 +32,6 @@ import '../style/message-input.less'; import ThumbImg from './thumb-img'; import UploadImg from './upload-img'; -const audioTypeMap: Record = { - 'audio/wav': 'wav', - 'audio/mp3': 'mp3', - 'audio/mpeg': 'mp3' -}; - const AudioWrapper = styled.div` padding-block: 10px; height: max-content; @@ -303,7 +301,7 @@ const MessageInput: React.FC = forwardRef( try { const base64Audio = await convertFileToBase64(data.file); const audioData = await readAudioFile(data.file); - console.log('audioData====', message.imgs); + console.log('audioData====', audioData, message.imgs); setMessage((pre) => { return { ...pre, @@ -421,6 +419,7 @@ const MessageInput: React.FC = forwardRef( )} {actions.includes('upload') && message.role === Roles.User && ( = { - 'audio/wav': 'wav', - 'audio/mp3': 'mp3', - 'audio/mpeg': 'mp3' -}; - interface MessageActionsProps { data: MessageItem; actions: MessageItemAction[]; @@ -88,6 +86,7 @@ const MessageActions: React.FC = ({ {actions.includes('upload') && data.role === Roles.User && ( { if (!sizeInBytes) return allowEmpty ? '' : 0; + const fmt = 1024; + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; let size = sizeInBytes; let unitIndex = 0; - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; + while (size >= fmt && unitIndex < units.length - 1) { + size /= fmt; unitIndex++; } diff --git a/src/utils/load-audio-file.ts b/src/utils/load-audio-file.ts index d8c3c176..a720f012 100644 --- a/src/utils/load-audio-file.ts +++ b/src/utils/load-audio-file.ts @@ -1,5 +1,13 @@ +import { message } from 'antd'; import { convertFileSize } from './index'; +export const audioTypeMap: Record = { + 'audio/wav': 'wav', + 'audio/mp3': 'mp3', + 'audio/mpeg': 'mp3', + 'audio/x-wav': 'wav' +}; + export const convertFileToBase64 = (file: File): Promise => { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -42,6 +50,12 @@ export const loadAudioData = async ( audio.addEventListener('ended', () => { URL.revokeObjectURL(audio.src); }); + + audio.addEventListener('error', () => { + URL.revokeObjectURL(url); + message.error('Failed to load audio metadata invalid file'); + reject(new Error('Failed to load audio metadata invalid file')); + }); } catch (error) { console.log('error====', error); reject(error);