feat: 增加音频上传超时和进度显示
为音频文件上传接口增加超时设置,防止大文件上传时请求超时。在 speaker 注册接口设置 2 分钟超时,在会议音频上传接口设置 5 分钟超时。同时为上传音频功能添加真实的进度回调,替换模拟进度,提升用户体验。 在首页点击创建会议按钮时,改为直接打开创建抽屉,而不是跳转到会议页面再打开,简化用户操作流程。dev_na
parent
53ff2292a8
commit
5f895bfe26
|
|
@ -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
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ export const registerSpeaker = (params: SpeakerRegisterParams) => {
|
|||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data"
|
||||
}
|
||||
},
|
||||
timeout: 120000 // 2 minutes timeout for audio files
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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('录音上传成功');
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue