fix: wav format audio, #2317

This commit is contained in:
jialin
2025-07-16 13:06:01 +08:00
parent f4d6abace7
commit 14d60264c7
9 changed files with 68 additions and 24 deletions
+22 -1
View File
@@ -1,7 +1,8 @@
import { UploadOutlined } from '@ant-design/icons'; import { UploadOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Tooltip, Upload } from 'antd'; import { Button, Tooltip, Upload, message } from 'antd';
import React from 'react'; import React from 'react';
import { convertFileSize } from '../../utils';
interface UploadAudioProps { interface UploadAudioProps {
accept?: string; accept?: string;
@@ -10,17 +11,36 @@ interface UploadAudioProps {
icon?: React.ReactNode; icon?: React.ReactNode;
size?: 'small' | 'middle' | 'large'; size?: 'small' | 'middle' | 'large';
shape?: 'circle' | 'round' | 'default'; shape?: 'circle' | 'round' | 'default';
maxFileSize?: number; // in bytes
onChange?: (data: { file: any; fileList: any[] }) => void; onChange?: (data: { file: any; fileList: any[] }) => void;
} }
const UploadAudio: React.FC<UploadAudioProps> = (props) => { const UploadAudio: React.FC<UploadAudioProps> = (props) => {
const [messageApi, contextHolder] = message.useMessage();
const { icon, accept, type, size = 'large', shape = 'circle' } = props; const { icon, accept, type, size = 'large', shape = 'circle' } = props;
const intl = useIntl(); const intl = useIntl();
const beforeUpload = (file: any) => { const beforeUpload = (file: any) => {
return false; 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 }) => { const handleOnChange = (data: { file: any; fileList: any }) => {
if (!isFileSizeValid(data.file)) {
return;
}
props.onChange?.(data); props.onChange?.(data);
}; };
@@ -57,6 +77,7 @@ const UploadAudio: React.FC<UploadAudioProps> = (props) => {
></Button> ></Button>
</div> </div>
</Upload> </Upload>
{contextHolder}
</Tooltip> </Tooltip>
); );
}; };
+3 -1
View File
@@ -154,5 +154,7 @@ export default {
'If the expected model isnt showing up, make sure its running and correctly categorized. If the category is incorrect, you can manually adjust it in the models settings.', 'If the expected model isnt showing up, make sure its running and correctly categorized. If the category is incorrect, you can manually adjust it in the models settings.',
'playground.rerank.query.validate': 'The query is required.', 'playground.rerank.query.validate': 'The query is required.',
'playground.image.generate.error': '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}.'
}; };
+5 -2
View File
@@ -157,10 +157,13 @@ export default {
'期待するモデルが表示されない場合は、モデルが実行中で正しく分類されていることを確認してください。カテゴリが間違っている場合は、モデルの設定で手動で調整できます。', '期待するモデルが表示されない場合は、モデルが実行中で正しく分類されていることを確認してください。カテゴリが間違っている場合は、モデルの設定で手動で調整できます。',
'playground.rerank.query.validate': 'The query is required.', 'playground.rerank.query.validate': 'The query is required.',
'playground.image.generate.error': '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) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'playground.rerank.query.validate': 'The query is required.' // 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 ========== // ========== End of To-Do List ==========
+5 -2
View File
@@ -151,10 +151,13 @@ export default {
'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.', 'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.',
'playground.rerank.query.validate': 'The query is required.', 'playground.rerank.query.validate': 'The query is required.',
'playground.image.generate.error': '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) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'playground.rerank.query.validate': 'The query is required.' // 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 ========== // ========== End of To-Do List ==========
+2 -1
View File
@@ -147,5 +147,6 @@ export default {
'playground.model.noavailable.tips2': 'playground.model.noavailable.tips2':
'若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。', '若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。',
'playground.rerank.query.validate': '查询内容不能为空', 'playground.rerank.query.validate': '查询内容不能为空',
'playground.image.generate.error': '出了一点问题,图片未能生成。' 'playground.image.generate.error': '出了一点问题,图片未能生成。',
'playground.uploadfile.sizeError': '上传的文件大小超过限制,最大允许 {size}'
}; };
@@ -2,7 +2,11 @@ import SimpleAudio from '@/components/audio-player/simple-audio';
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import UploadAudio from '@/components/upload-audio'; import UploadAudio from '@/components/upload-audio';
import HotKeys, { KeyMap } from '@/config/hotkeys'; import HotKeys, { KeyMap } from '@/config/hotkeys';
import { convertFileToBase64, readAudioFile } from '@/utils/load-audio-file'; import {
audioTypeMap,
convertFileToBase64,
readAudioFile
} from '@/utils/load-audio-file';
import { import {
ClearOutlined, ClearOutlined,
CustomerServiceOutlined, CustomerServiceOutlined,
@@ -28,12 +32,6 @@ import '../style/message-input.less';
import ThumbImg from './thumb-img'; import ThumbImg from './thumb-img';
import UploadImg from './upload-img'; import UploadImg from './upload-img';
const audioTypeMap: Record<string, string> = {
'audio/wav': 'wav',
'audio/mp3': 'mp3',
'audio/mpeg': 'mp3'
};
const AudioWrapper = styled.div` const AudioWrapper = styled.div`
padding-block: 10px; padding-block: 10px;
height: max-content; height: max-content;
@@ -303,7 +301,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
try { try {
const base64Audio = await convertFileToBase64(data.file); const base64Audio = await convertFileToBase64(data.file);
const audioData = await readAudioFile(data.file); const audioData = await readAudioFile(data.file);
console.log('audioData====', message.imgs); console.log('audioData====', audioData, message.imgs);
setMessage((pre) => { setMessage((pre) => {
return { return {
...pre, ...pre,
@@ -421,6 +419,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
)} )}
{actions.includes('upload') && message.role === Roles.User && ( {actions.includes('upload') && message.role === Roles.User && (
<UploadAudio <UploadAudio
maxFileSize={1024 * 1024}
type="text" type="text"
accept={'.mp3,.wav'} accept={'.mp3,.wav'}
size="middle" size="middle"
@@ -1,6 +1,10 @@
import CopyButton from '@/components/copy-button'; import CopyButton from '@/components/copy-button';
import UploadAudio from '@/components/upload-audio'; import UploadAudio from '@/components/upload-audio';
import { convertFileToBase64, readAudioFile } from '@/utils/load-audio-file'; import {
audioTypeMap,
convertFileToBase64,
readAudioFile
} from '@/utils/load-audio-file';
import { import {
CustomerServiceOutlined, CustomerServiceOutlined,
EditOutlined, EditOutlined,
@@ -18,12 +22,6 @@ import {
} from '../../config/types'; } from '../../config/types';
import UploadImg from '../upload-img'; import UploadImg from '../upload-img';
const audioTypeMap: Record<string, string> = {
'audio/wav': 'wav',
'audio/mp3': 'mp3',
'audio/mpeg': 'mp3'
};
interface MessageActionsProps { interface MessageActionsProps {
data: MessageItem; data: MessageItem;
actions: MessageItemAction[]; actions: MessageItemAction[];
@@ -88,6 +86,7 @@ const MessageActions: React.FC<MessageActionsProps> = ({
{actions.includes('upload') && data.role === Roles.User && ( {actions.includes('upload') && data.role === Roles.User && (
<UploadAudio <UploadAudio
type="text" type="text"
maxFileSize={1024 * 1024}
accept={'.mp3,.wav'} accept={'.mp3,.wav'}
size="small" size="small"
shape="default" shape="default"
+4 -2
View File
@@ -28,12 +28,14 @@ export const convertFileSize = (
): string | number => { ): string | number => {
if (!sizeInBytes) return allowEmpty ? '' : 0; if (!sizeInBytes) return allowEmpty ? '' : 0;
const fmt = 1024;
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
let size = sizeInBytes; let size = sizeInBytes;
let unitIndex = 0; let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) { while (size >= fmt && unitIndex < units.length - 1) {
size /= 1024; size /= fmt;
unitIndex++; unitIndex++;
} }
+14
View File
@@ -1,5 +1,13 @@
import { message } from 'antd';
import { convertFileSize } from './index'; import { convertFileSize } from './index';
export const audioTypeMap: Record<string, string> = {
'audio/wav': 'wav',
'audio/mp3': 'mp3',
'audio/mpeg': 'mp3',
'audio/x-wav': 'wav'
};
export const convertFileToBase64 = (file: File): Promise<string> => { export const convertFileToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
@@ -42,6 +50,12 @@ export const loadAudioData = async (
audio.addEventListener('ended', () => { audio.addEventListener('ended', () => {
URL.revokeObjectURL(audio.src); 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) { } catch (error) {
console.log('error====', error); console.log('error====', error);
reject(error); reject(error);