diff --git a/frontend/src/api/business/meeting.ts b/frontend/src/api/business/meeting.ts index d24c584..c2ac16c 100644 --- a/frontend/src/api/business/meeting.ts +++ b/frontend/src/api/business/meeting.ts @@ -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 + } ); }; diff --git a/frontend/src/api/business/speaker.ts b/frontend/src/api/business/speaker.ts index 16f4c3e..23d370a 100644 --- a/frontend/src/api/business/speaker.ts +++ b/frontend/src/api/business/speaker.ts @@ -45,7 +45,8 @@ export const registerSpeaker = (params: SpeakerRegisterParams) => { { headers: { "Content-Type": "multipart/form-data" - } + }, + timeout: 120000 // 2 minutes timeout for audio files } ); }; diff --git a/frontend/src/components/business/MeetingCreateDrawer.tsx b/frontend/src/components/business/MeetingCreateDrawer.tsx index 35ceb51..3b00dae 100644 --- a/frontend/src/components/business/MeetingCreateDrawer.tsx +++ b/frontend/src/components/business/MeetingCreateDrawer.tsx @@ -149,13 +149,20 @@ export const MeetingCreateDrawer: React.FC = ({ 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('录音上传成功'); diff --git a/frontend/src/pages/business/Meetings.tsx b/frontend/src/pages/business/Meetings.tsx index e877822..a61c912 100644 --- a/frontend/src/pages/business/Meetings.tsx +++ b/frontend/src/pages/business/Meetings.tsx @@ -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'; diff --git a/frontend/src/pages/home/index.tsx b/frontend/src/pages/home/index.tsx index 9d504b2..58b776d 100644 --- a/frontend/src/pages/home/index.tsx +++ b/frontend/src/pages/home/index.tsx @@ -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([]); const [loading, setLoading] = useState(true); + const [drawerOpen, setDrawerOpen] = useState(false); + const [drawerType, setDrawerType] = useState('realtime'); const [readCardIds, setReadCardIds] = useState(() => { 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() { )} + + 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'); + }} + /> ); }