import React, { useCallback, useEffect, useState } from 'react'; import { Drawer, Form, Input, Button, DatePicker, Select, Space, App, Upload, Card, Progress, Typography } from 'antd'; import { SaveOutlined, UploadOutlined, DeleteOutlined, AudioOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import apiClient from '../utils/apiClient'; import { buildApiUrl, API_ENDPOINTS } from '../config/api'; import configService from '../utils/configService'; import { AUDIO_UPLOAD_ACCEPT, uploadMeetingAudio, validateMeetingAudioFile } from '../services/meetingAudioService'; const { Text } = Typography; const MeetingFormDrawer = ({ open, onClose, onSuccess, meetingId = null }) => { const { message } = App.useApp(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [users, setUsers] = useState([]); const [prompts, setPrompts] = useState([]); const [selectedAudioFile, setSelectedAudioFile] = useState(null); const [audioUploading, setAudioUploading] = useState(false); const [audioUploadProgress, setAudioUploadProgress] = useState(0); const [audioUploadMessage, setAudioUploadMessage] = useState(''); const [maxAudioSize, setMaxAudioSize] = useState(100 * 1024 * 1024); const isEdit = Boolean(meetingId); const fetchOptions = useCallback(async () => { try { const [uRes, pRes] = await Promise.all([ apiClient.get(buildApiUrl(API_ENDPOINTS.USERS.LIST)), apiClient.get(buildApiUrl(API_ENDPOINTS.PROMPTS.ACTIVE('MEETING_TASK'))), ]); setUsers(uRes.data.users || []); setPrompts(pRes.data.prompts || []); } catch { message.error('加载会议表单选项失败'); } }, [message]); const loadAudioUploadConfig = useCallback(async () => { try { const nextMaxAudioSize = await configService.getMaxAudioSize(); setMaxAudioSize(nextMaxAudioSize || 100 * 1024 * 1024); } catch { setMaxAudioSize(100 * 1024 * 1024); } }, []); const fetchMeeting = useCallback(async () => { try { const res = await apiClient.get(buildApiUrl(API_ENDPOINTS.MEETINGS.DETAIL(meetingId))); const meeting = res.data; form.setFieldsValue({ title: meeting.title, meeting_time: dayjs(meeting.meeting_time), attendee_ids: meeting.attendee_ids || meeting.attendees?.map((a) => a.user_id).filter(Boolean) || [], prompt_id: meeting.prompt_id, tags: meeting.tags?.map((t) => t.name) || [], }); } catch { message.error('加载会议数据失败'); } }, [form, meetingId, message]); useEffect(() => { if (!open) return; fetchOptions(); loadAudioUploadConfig(); if (isEdit) { fetchMeeting(); } else { form.resetFields(); form.setFieldsValue({ meeting_time: dayjs() }); setSelectedAudioFile(null); setAudioUploading(false); setAudioUploadProgress(0); setAudioUploadMessage(''); } }, [fetchMeeting, fetchOptions, form, isEdit, loadAudioUploadConfig, open]); const handleAudioBeforeUpload = (file) => { const validationMessage = validateMeetingAudioFile(file, maxAudioSize, configService.formatFileSize.bind(configService)); if (validationMessage) { message.warning(validationMessage); return Upload.LIST_IGNORE; } setSelectedAudioFile(file); return false; }; const clearSelectedAudio = () => { setSelectedAudioFile(null); setAudioUploadProgress(0); setAudioUploadMessage(''); }; const handleSubmit = async () => { try { const values = await form.validateFields(); setLoading(true); const payload = { ...values, meeting_time: values.meeting_time.format('YYYY-MM-DD HH:mm:ss'), attendee_ids: values.attendee_ids || [], tags: values.tags?.join(',') || '', }; if (isEdit) { await apiClient.put(buildApiUrl(API_ENDPOINTS.MEETINGS.UPDATE(meetingId)), payload); message.success('会议更新成功'); } else { const res = await apiClient.post(buildApiUrl(API_ENDPOINTS.MEETINGS.CREATE), payload); if (res.code === '200') { const newMeetingId = res.data.meeting_id; if (selectedAudioFile) { setAudioUploading(true); setAudioUploadProgress(0); setAudioUploadMessage('正在上传音频文件...'); try { await uploadMeetingAudio({ meetingId: newMeetingId, file: selectedAudioFile, promptId: values.prompt_id, onUploadProgress: (progressEvent) => { if (progressEvent.total) { setAudioUploadProgress(Math.min(100, Math.round((progressEvent.loaded * 100) / progressEvent.total))); } setAudioUploadMessage('正在上传音频文件...'); }, }); setAudioUploadProgress(100); setAudioUploadMessage('上传完成,正在启动转录任务...'); message.success('会议创建成功,音频已开始上传处理'); } catch (uploadError) { message.warning(uploadError?.response?.data?.message || uploadError?.response?.data?.detail || '会议已创建,但音频上传失败,请在详情页重试'); } finally { setAudioUploading(false); } } else { message.success('会议创建成功'); } onSuccess?.(res.data.meeting_id); onClose(); return; } } onSuccess?.(); onClose(); } catch (error) { if (!error?.errorFields) { message.error(error?.response?.data?.message || error?.response?.data?.detail || '操作失败'); } } finally { setLoading(false); } }; return ( } >