feat: 增加音频上传超时和进度显示

为音频文件上传接口增加超时设置,防止大文件上传时请求超时。在 speaker 注册接口设置 2 分钟超时,在会议音频上传接口设置 5 分钟超时。同时为上传音频功能添加真实的进度回调,替换模拟进度,提升用户体验。

在首页点击创建会议按钮时,改为直接打开创建抽屉,而不是跳转到会议页面再打开,简化用户操作流程。
dev_na
alanpaine 2026-04-10 17:36:45 +08:00
parent 53ff2292a8
commit 5f895bfe26
5 changed files with 44 additions and 11 deletions

View File

@ -297,13 +297,17 @@ export const updateMeetingParticipants = (params: UpdateMeetingParticipantsComma
);
};
export const uploadAudio = (file: File) => {
export const uploadAudio = (file: File, onUploadProgress?: (progressEvent: any) => void) => {
const formData = new FormData();
formData.append("file", file);
return http.post<{ code: string; data: string; msg: string }>(
"/api/biz/meeting/upload",
formData,
{ headers: { "Content-Type": "multipart/form-data" } }
{
headers: { "Content-Type": "multipart/form-data" },
timeout: 300000, // 5 minutes timeout for large audio files
onUploadProgress
}
);
};

View File

@ -45,7 +45,8 @@ export const registerSpeaker = (params: SpeakerRegisterParams) => {
{
headers: {
"Content-Type": "multipart/form-data"
}
},
timeout: 120000 // 2 minutes timeout for audio files
}
);
};

View File

@ -149,13 +149,20 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({ open,
}, [type, form, open]);
const customUpload = async (options: any) => {
const { file, onSuccess: uploadSuccess, onError } = options;
const { file, onSuccess: uploadSuccess, onError, onProgress } = options;
setUploadProgress(0);
try {
const interval = setInterval(() => setUploadProgress(prev => (prev < 95 ? prev + 5 : prev)), 300);
const res = await uploadAudio(file);
clearInterval(interval);
const res = await uploadAudio(file, (progressEvent) => {
if (progressEvent.total) {
const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
// Only show up to 99% during upload, save 100% for actual completion
const displayPercent = percentCompleted > 99 ? 99 : percentCompleted;
setUploadProgress(displayPercent);
onProgress({ percent: displayPercent });
}
});
setUploadProgress(100);
onProgress({ percent: 100 });
setAudioUrl(res.data.data);
uploadSuccess(res.data.data);
message.success('录音上传成功');

View File

@ -10,7 +10,7 @@ import {
} from '@ant-design/icons';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { usePermission } from '../../hooks/usePermission';
import { getMeetingPage, deleteMeeting, MeetingVO, getMeetingProgress, MeetingProgress, createMeeting, uploadAudio, updateMeetingParticipants, getRealtimeMeetingSessionStatus, getRealtimeMeetingSessionStatuses, RealtimeMeetingSessionStatus } from '../../api/business/meeting';
import { getMeetingPage, deleteMeeting, MeetingVO, getMeetingProgress, MeetingProgress, createMeeting, updateMeetingParticipants, getRealtimeMeetingSessionStatus, getRealtimeMeetingSessionStatuses, RealtimeMeetingSessionStatus } from '../../api/business/meeting';
import { getAiModelPage, getAiModelDefault, AiModelVO } from '../../api/business/aimodel';
import { getPromptPage, PromptTemplateVO } from '../../api/business/prompt';
import { getHotWordPage, HotWordVO } from '../../api/business/hotword';

View File

@ -11,6 +11,7 @@ import { getRecentTasks } from "@/api/business/dashboard";
import type { MeetingVO } from "@/api/business/meeting";
import "./index.less";
import RightVisual from "./RightVisual";
import { MeetingCreateDrawer, MeetingCreateType } from "@/components/business/MeetingCreateDrawer";
const { Text, Title } = Typography;
@ -75,6 +76,8 @@ export default function HomePage() {
const navigate = useNavigate();
const [recentTasks, setRecentTasks] = useState<MeetingVO[]>([]);
const [loading, setLoading] = useState(true);
const [drawerOpen, setDrawerOpen] = useState(false);
const [drawerType, setDrawerType] = useState<MeetingCreateType>('realtime');
const [readCardIds, setReadCardIds] = useState<string[]>(() => {
if (typeof window === "undefined") {
return [];
@ -134,7 +137,10 @@ export default function HomePage() {
description: ["实时语音转文字", "同步翻译,智能总结要点"],
accent: "violet",
badge: "会议神器",
onClick: () => navigate("/meetings?action=create&type=realtime")
onClick: () => {
setDrawerType("realtime");
setDrawerOpen(true);
}
},
{
title: "上传音视频",
@ -142,10 +148,13 @@ export default function HomePage() {
description: ["音视频转文字", "区分发言人,一键导出"],
accent: "cyan",
badge: "iMeeting",
onClick: () => navigate("/meetings?action=create&type=upload")
onClick: () => {
setDrawerType("upload");
setDrawerOpen(true);
}
}
],
[navigate]
[]
);
const recentCards = useMemo(() => buildRecentCards(recentTasks), [recentTasks]);
@ -271,6 +280,18 @@ export default function HomePage() {
)}
</section>
</div>
<MeetingCreateDrawer
open={drawerOpen}
initialType={drawerType}
onCancel={() => setDrawerOpen(false)}
onSuccess={() => {
setDrawerOpen(false);
// onSuccess doesn't return meeting id in MeetingCreateDrawerProps right now
// the Drawer handles its own navigation or we can just redirect to meetings list
navigate('/meetings');
}}
/>
</main>
);
}