diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java index feb33b0..b6652f6 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java @@ -84,7 +84,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme } catch (Exception e) { log.error("Meeting {} AI Task Flow failed", meetingId, e); updateMeetingStatus(meetingId, 4); // Overall Failed - updateProgress(meetingId, -1, "分析失败: " + e.getMessage()); + updateProgress(meetingId, -1, "分析失败: " + e.getMessage(), 0); } finally { redisTemplate.delete(lockKey); } @@ -115,7 +115,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme private String processAsrTask(Meeting meeting) throws Exception { updateMeetingStatus(meeting.getId(), 1); // 识别中 - updateProgress(meeting.getId(), 5, "已提交识别请求..."); + updateProgress(meeting.getId(), 5, "已提交识别请求...", 0); AiModel asrModel = aiModelService.getById(meeting.getAsrModelId()); if (asrModel == null) throw new RuntimeException("ASR Model config not found"); @@ -177,7 +177,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme if ("completed".equalsIgnoreCase(status)) { resultNode = data.path("result"); updateAiTaskSuccess(taskRecord, statusNode); - updateProgress(meeting.getId(), 85, "语音转录完成,准备进行总结..."); + updateProgress(meeting.getId(), 85, "语音转录完成,准备进行总结...", 0); break; } else if ("failed".equalsIgnoreCase(status)) { updateAiTaskFail(taskRecord, "ASR reported failure: " + queryResp); @@ -186,10 +186,11 @@ public class AiTaskServiceImpl extends ServiceImpl impleme // 处理中:同步进度到 Redis int currentPercent = data.path("percentage").asInt(); String message = data.path("message").asText(); + int eta = data.path("eta").asInt(0); // 缩放到 0-85% 范围 int scaledPercent = (int)(currentPercent * 0.85); - updateProgress(meeting.getId(), Math.max(5, scaledPercent), message); + updateProgress(meeting.getId(), Math.max(5, scaledPercent), message, eta); // 防死循环逻辑:如果进度长时间不动且不是 0 if (currentPercent > 0 && currentPercent == lastPercent) { @@ -241,7 +242,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme private void processSummaryTask(Meeting meeting, String asrText) throws Exception { updateMeetingStatus(meeting.getId(), 2); // 总结中 - updateProgress(meeting.getId(), 90, "正在进行 AI 智能总结..."); + updateProgress(meeting.getId(), 90, "正在进行 AI 智能总结...", 0); AiModel llmModel = aiModelService.getById(meeting.getSummaryModelId()); if (llmModel == null) return; @@ -273,18 +274,19 @@ public class AiTaskServiceImpl extends ServiceImpl impleme meeting.setStatus(3); // Finished meetingMapper.updateById(meeting); updateAiTaskSuccess(taskRecord, respNode); - updateProgress(meeting.getId(), 100, "分析已完成"); + updateProgress(meeting.getId(), 100, "分析已完成", 0); } else { updateAiTaskFail(taskRecord, "LLM failed: " + response.body()); throw new RuntimeException("AI总结生成失败"); } } - private void updateProgress(Long meetingId, int percent, String msg) { + private void updateProgress(Long meetingId, int percent, String msg, int eta) { try { Map progress = new HashMap<>(); progress.put("percent", percent); progress.put("message", msg); + progress.put("eta", eta); progress.put("updateAt", System.currentTimeMillis()); redisTemplate.opsForValue().set(RedisKeys.meetingProgressKey(meetingId), objectMapper.writeValueAsString(progress), 1, TimeUnit.HOURS); diff --git a/frontend/src/pages/business/MeetingDetail.tsx b/frontend/src/pages/business/MeetingDetail.tsx index 468dc15..2e5e905 100644 --- a/frontend/src/pages/business/MeetingDetail.tsx +++ b/frontend/src/pages/business/MeetingDetail.tsx @@ -39,6 +39,15 @@ const MeetingProgressDisplay: React.FC<{ meetingId: number; onComplete: () => vo const percent = progress?.percent || 0; const isError = percent < 0; + // 格式化剩余时间 (ETA) + const formatETA = (seconds?: number) => { + if (!seconds || seconds <= 0) return '即将完成'; + if (seconds < 60) return `${seconds}秒`; + const m = Math.floor(seconds / 60); + const s = seconds % 60; + return s > 0 ? `${m}分${s}秒` : `${m}分钟`; + }; + return (
vo 预计剩余 - {isError ? '--' : (percent > 90 ? '即将完成' : '计算中')} + {isError ? '--' : formatETA(progress?.eta)} diff --git a/frontend/src/pages/business/Meetings.tsx b/frontend/src/pages/business/Meetings.tsx index fd9a58c..68aef7d 100644 --- a/frontend/src/pages/business/Meetings.tsx +++ b/frontend/src/pages/business/Meetings.tsx @@ -293,22 +293,25 @@ const MeetingCardItem: React.FC<{ item: MeetingVO, config: any, fetchData: () => display: 'flex', alignItems: 'center', background: item.status === 1 ? '#e6f7ff' : '#fff7e6', - padding: '6px 12px', // 增加内边距,更聚拢 + padding: '6px 10px', borderRadius: 6, marginTop: 4, - width: 'calc(100% - 12px)', // 留出右侧与卡片边缘的距离 + width: '100%', // 占满 Space 容器 overflow: 'hidden', - boxSizing: 'border-box' + boxSizing: 'border-box', + minWidth: 0 // 关键:允许 flex 子项收缩 }}> - + {progress?.message || '等待引擎调度...'}