feat: 添加总结和章节任务重试功能并优化会议处理逻辑
- 在 `MeetingCommandServiceImpl` 中添加 `retrySummary` 和 `retryChapter` 方法,支持总结和章节任务的重试 - 更新 `MeetingCommandService` 接口以包含新的重试方法 - 优化 `executeSummaryFlow` 和 `doDispatchChapterTask` 方法,简化任务执行逻辑 - 更新 `finalizeSummary` 方法,移除不必要的章节版本检查 - 调整 `updateMeetingProgress` 的进度值和消息,更准确地反映任务状态 - 在前端 `MeetingDetail.tsx` 中添加重试按钮和相关逻辑,支持用户手动重试失败的任务dev_na
parent
892275bc65
commit
384494d9ff
|
|
@ -9,4 +9,7 @@ public final class SysParamKeys {
|
||||||
public static final String MEETING_CREATE_OFFLINE_ENABLED = "meeting.create.offline_enabled";
|
public static final String MEETING_CREATE_OFFLINE_ENABLED = "meeting.create.offline_enabled";
|
||||||
public static final String MEETING_CREATE_REALTIME_ENABLED = "meeting.create.realtime_enabled";
|
public static final String MEETING_CREATE_REALTIME_ENABLED = "meeting.create.realtime_enabled";
|
||||||
public static final String MEETING_ASR_MAX_CONCURRENT = "meeting.asr.max_concurrent";
|
public static final String MEETING_ASR_MAX_CONCURRENT = "meeting.asr.max_concurrent";
|
||||||
|
public static final String MEETING_MAX_PAUSE_DURATION = "meeting.max_pause_duration";
|
||||||
|
public static final String MEETING_MAX_MEETING_DURATION = "meeting.max_meeting_duration";
|
||||||
|
public static final String MEETING_PACKET_LOSS_RATE = "meeting.packet_loss_rate";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/android/clients")
|
@RequestMapping("/api/android/clients")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class AndroidClientController {
|
public class AndroidClientController {
|
||||||
|
|
||||||
private final AndroidAuthService androidAuthService;
|
private final AndroidAuthService androidAuthService;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.imeeting.common.RedisKeys;
|
import com.imeeting.common.RedisKeys;
|
||||||
|
import com.imeeting.common.SysParamKeys;
|
||||||
import com.imeeting.dto.android.AndroidAuthContext;
|
import com.imeeting.dto.android.AndroidAuthContext;
|
||||||
|
import com.imeeting.dto.android.AndroidMeetingConfigVo;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
||||||
|
|
@ -13,19 +15,15 @@ import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyUploadAudioResponse;
|
import com.imeeting.dto.android.legacy.LegacyUploadAudioResponse;
|
||||||
|
import com.imeeting.dto.biz.AiModelVO;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
import com.imeeting.dto.biz.MeetingVO;
|
||||||
|
import com.imeeting.dto.biz.PromptTemplateVO;
|
||||||
import com.imeeting.entity.biz.AiTask;
|
import com.imeeting.entity.biz.AiTask;
|
||||||
import com.imeeting.entity.biz.Meeting;
|
import com.imeeting.entity.biz.Meeting;
|
||||||
import com.imeeting.entity.biz.PromptTemplate;
|
import com.imeeting.entity.biz.PromptTemplate;
|
||||||
import com.imeeting.service.android.AndroidAuthService;
|
import com.imeeting.service.android.AndroidAuthService;
|
||||||
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
|
import com.imeeting.service.android.legacy.LegacyMeetingAdapterService;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.*;
|
||||||
import com.imeeting.service.biz.MeetingAccessService;
|
|
||||||
import com.imeeting.service.biz.MeetingCommandService;
|
|
||||||
import com.imeeting.service.biz.MeetingProgressService;
|
|
||||||
import com.imeeting.service.biz.MeetingQueryService;
|
|
||||||
import com.imeeting.service.biz.MeetingService;
|
|
||||||
import com.imeeting.service.biz.PromptTemplateService;
|
|
||||||
import com.imeeting.service.biz.impl.RedisOnlyMeetingProgressServiceAdapter;
|
import com.imeeting.service.biz.impl.RedisOnlyMeetingProgressServiceAdapter;
|
||||||
import com.unisbase.common.ApiResponse;
|
import com.unisbase.common.ApiResponse;
|
||||||
import com.unisbase.common.annotation.Log;
|
import com.unisbase.common.annotation.Log;
|
||||||
|
|
@ -33,6 +31,9 @@ import com.unisbase.dto.PageResult;
|
||||||
import com.unisbase.entity.SysUser;
|
import com.unisbase.entity.SysUser;
|
||||||
import com.unisbase.mapper.SysUserMapper;
|
import com.unisbase.mapper.SysUserMapper;
|
||||||
import com.unisbase.security.LoginUser;
|
import com.unisbase.security.LoginUser;
|
||||||
|
import com.unisbase.service.SysDictItemService;
|
||||||
|
import com.unisbase.service.SysParamService;
|
||||||
|
import com.unisbase.service.impl.SysDictItemServiceImpl;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
@ -54,6 +55,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
@ -81,6 +83,9 @@ public class AndroidMeetingController {
|
||||||
private final AiTaskService aiTaskService;
|
private final AiTaskService aiTaskService;
|
||||||
private final PromptTemplateService promptTemplateService;
|
private final PromptTemplateService promptTemplateService;
|
||||||
private final SysUserMapper sysUserMapper;
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final AiModelService aiModelService;
|
||||||
|
private final SysDictItemService dictItemService;
|
||||||
|
private final SysParamService paramService;
|
||||||
private final MeetingProgressService meetingProgressService;
|
private final MeetingProgressService meetingProgressService;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
|
@ -94,6 +99,9 @@ public class AndroidMeetingController {
|
||||||
AiTaskService aiTaskService,
|
AiTaskService aiTaskService,
|
||||||
PromptTemplateService promptTemplateService,
|
PromptTemplateService promptTemplateService,
|
||||||
SysUserMapper sysUserMapper,
|
SysUserMapper sysUserMapper,
|
||||||
|
AiModelService aiModelService,
|
||||||
|
SysDictItemService dictItemService,
|
||||||
|
SysParamService paramService,
|
||||||
MeetingProgressService meetingProgressService,
|
MeetingProgressService meetingProgressService,
|
||||||
ObjectMapper objectMapper) {
|
ObjectMapper objectMapper) {
|
||||||
this.androidAuthService = androidAuthService;
|
this.androidAuthService = androidAuthService;
|
||||||
|
|
@ -107,6 +115,9 @@ public class AndroidMeetingController {
|
||||||
this.sysUserMapper = sysUserMapper;
|
this.sysUserMapper = sysUserMapper;
|
||||||
this.meetingProgressService = meetingProgressService;
|
this.meetingProgressService = meetingProgressService;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
|
this.aiModelService = aiModelService;
|
||||||
|
this.paramService = paramService;
|
||||||
|
this.dictItemService = dictItemService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AndroidMeetingController(AndroidAuthService androidAuthService,
|
public AndroidMeetingController(AndroidAuthService androidAuthService,
|
||||||
|
|
@ -118,6 +129,9 @@ public class AndroidMeetingController {
|
||||||
AiTaskService aiTaskService,
|
AiTaskService aiTaskService,
|
||||||
PromptTemplateService promptTemplateService,
|
PromptTemplateService promptTemplateService,
|
||||||
SysUserMapper sysUserMapper,
|
SysUserMapper sysUserMapper,
|
||||||
|
AiModelService aiModelService,
|
||||||
|
SysDictItemService dictItemService,
|
||||||
|
SysParamService paramService,
|
||||||
StringRedisTemplate redisTemplate,
|
StringRedisTemplate redisTemplate,
|
||||||
ObjectMapper objectMapper) {
|
ObjectMapper objectMapper) {
|
||||||
this(
|
this(
|
||||||
|
|
@ -130,6 +144,9 @@ public class AndroidMeetingController {
|
||||||
aiTaskService,
|
aiTaskService,
|
||||||
promptTemplateService,
|
promptTemplateService,
|
||||||
sysUserMapper,
|
sysUserMapper,
|
||||||
|
aiModelService,
|
||||||
|
dictItemService,
|
||||||
|
paramService,
|
||||||
new RedisOnlyMeetingProgressServiceAdapter(redisTemplate, objectMapper),
|
new RedisOnlyMeetingProgressServiceAdapter(redisTemplate, objectMapper),
|
||||||
objectMapper
|
objectMapper
|
||||||
);
|
);
|
||||||
|
|
@ -266,6 +283,43 @@ public class AndroidMeetingController {
|
||||||
meetingCommandService.deleteMeeting(meetingId);
|
meetingCommandService.deleteMeeting(meetingId);
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
@GetMapping("/config")
|
||||||
|
@Log(value = "获取会议配置", type = "Android会议管理")
|
||||||
|
@Operation(summary = "获取会议配置")
|
||||||
|
public ApiResponse<AndroidMeetingConfigVo> config(HttpServletRequest request) {
|
||||||
|
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||||||
|
LoginUser loginUser = AndroidLoginUserSupport.requireLoginUser(authContext);
|
||||||
|
AndroidMeetingConfigVo resultVo = new AndroidMeetingConfigVo();
|
||||||
|
PageResult<List<PromptTemplateVO>> promptTemplateList = promptTemplateService.pageTemplates(
|
||||||
|
1,
|
||||||
|
1000,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
loginUser.getTenantId(),
|
||||||
|
loginUser.getUserId(),
|
||||||
|
loginUser.getIsPlatformAdmin(),
|
||||||
|
loginUser.getIsTenantAdmin()
|
||||||
|
);
|
||||||
|
List<PromptTemplateVO> enabledTemplates = promptTemplateList.getRecords() == null
|
||||||
|
? List.of()
|
||||||
|
: promptTemplateList.getRecords().stream()
|
||||||
|
.filter(item -> Integer.valueOf(1).equals(item.getStatus()))
|
||||||
|
.toList();
|
||||||
|
resultVo.setTemplateList(enabledTemplates);
|
||||||
|
PageResult<List<AiModelVO>> modelList = aiModelService.pageModels(1, 1000, null, "LLM", loginUser.getTenantId());
|
||||||
|
List<AiModelVO> enabledModels = modelList.getRecords() == null
|
||||||
|
? List.of()
|
||||||
|
: modelList.getRecords().stream()
|
||||||
|
.filter(item -> Integer.valueOf(1).equals(item.getStatus()))
|
||||||
|
.toList();
|
||||||
|
resultVo.setModelsList(enabledModels);
|
||||||
|
resultVo.setSummaryDegreeOfDetail(dictItemService.getItemsByTypeCode("summary_degree_detail"));
|
||||||
|
resultVo.setMaxMeetingDuration(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_MAX_MEETING_DURATION,"30")));
|
||||||
|
resultVo.setMaxPauseDuration(Integer.valueOf(paramService.getParamValue(SysParamKeys.MEETING_MAX_PAUSE_DURATION,String.valueOf(60*4))));
|
||||||
|
resultVo.setPacketLossRate(new BigDecimal(paramService.getParamValue(SysParamKeys.MEETING_MAX_PAUSE_DURATION,"99")));
|
||||||
|
|
||||||
|
return ApiResponse.ok(resultVo);
|
||||||
|
}
|
||||||
|
|
||||||
private LegacyMeetingPreviewResult buildPreviewResult(Long meetingId) {
|
private LegacyMeetingPreviewResult buildPreviewResult(Long meetingId) {
|
||||||
Meeting meeting = meetingService.getById(meetingId);
|
Meeting meeting = meetingService.getById(meetingId);
|
||||||
|
|
@ -291,7 +345,7 @@ public class AndroidMeetingController {
|
||||||
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可查看详情", 100, STAGE_COMPLETED))
|
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可查看详情", 100, STAGE_COMPLETED))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
if (isFailed(asrTask)) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"503",
|
"503",
|
||||||
buildFailureMessage(asrTask, "转写"),
|
buildFailureMessage(asrTask, "转写"),
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ public class LegacyMeetingController {
|
||||||
// buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
|
// buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
if (isFailed(asrTask)) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"503",
|
"503",
|
||||||
buildFailureMessage(asrTask, "转译"),
|
buildFailureMessage(asrTask, "转译"),
|
||||||
|
|
@ -440,7 +440,7 @@ public class LegacyMeetingController {
|
||||||
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
||||||
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
||||||
}
|
}
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
if (isFailed(asrTask)) {
|
||||||
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||||
}
|
}
|
||||||
if (isFailed(summaryTask)) {
|
if (isFailed(summaryTask)) {
|
||||||
|
|
|
||||||
|
|
@ -531,6 +531,28 @@ public class MeetingController {
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重试 AI 目录")
|
||||||
|
@PostMapping("/{id}/chapters/retry")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public ApiResponse<Boolean> retryChapter(@PathVariable Long id) {
|
||||||
|
LoginUser loginUser = currentLoginUser();
|
||||||
|
Meeting meeting = meetingAccessService.requireMeeting(id);
|
||||||
|
meetingAccessService.assertCanEditMeeting(meeting, loginUser);
|
||||||
|
meetingCommandService.retryChapter(id);
|
||||||
|
return ApiResponse.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "重试会议总结")
|
||||||
|
@PostMapping("/{id}/summary/retry")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public ApiResponse<Boolean> retrySummary(@PathVariable Long id) {
|
||||||
|
LoginUser loginUser = currentLoginUser();
|
||||||
|
Meeting meeting = meetingAccessService.requireMeeting(id);
|
||||||
|
meetingAccessService.assertCanEditMeeting(meeting, loginUser);
|
||||||
|
meetingCommandService.retrySummary(id);
|
||||||
|
return ApiResponse.ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "更新会议基础信息")
|
@Operation(summary = "更新会议基础信息")
|
||||||
@PutMapping("/{id}/basic")
|
@PutMapping("/{id}/basic")
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.imeeting.dto.android;
|
||||||
|
|
||||||
|
|
||||||
|
import com.imeeting.dto.biz.AiModelVO;
|
||||||
|
import com.imeeting.dto.biz.PromptTemplateVO;
|
||||||
|
import com.unisbase.dto.SysDictItemDTO;
|
||||||
|
import com.unisbase.entity.SysDictItem;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author : ch
|
||||||
|
* @version : 1.0
|
||||||
|
* @ClassName : AndroidMeetingConfigVo
|
||||||
|
* @Description :
|
||||||
|
* @DATE : Created in 17:02 2026/5/27
|
||||||
|
* <pre> Copyright: Copyright(c) 2026 </pre>
|
||||||
|
* <pre> Company : 紫光汇智信息技术有限公司 </pre>
|
||||||
|
* Modification History:
|
||||||
|
* Date Author Version Discription
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* 2026/05/27 ch 1.0 Why & What is modified: <修改原因描述> *
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class AndroidMeetingConfigVo {
|
||||||
|
private List<AiModelVO> modelsList;
|
||||||
|
private List<PromptTemplateVO> templateList;
|
||||||
|
private List<SysDictItemDTO> summaryDegreeOfDetail;
|
||||||
|
private Integer maxPauseDuration;
|
||||||
|
private Integer maxMeetingDuration;
|
||||||
|
private BigDecimal packetLossRate;
|
||||||
|
}
|
||||||
|
|
@ -21,8 +21,7 @@ public class MeetingSummaryFinalizeDTO {
|
||||||
@Schema(description = "转录指纹")
|
@Schema(description = "转录指纹")
|
||||||
private String sourceFingerprint;
|
private String sourceFingerprint;
|
||||||
|
|
||||||
@NotNull(message = "chapterVersionId must not be null")
|
@Schema(description = "章节版本ID,可选,仅用于审计记录")
|
||||||
@Schema(description = "章节版本ID")
|
|
||||||
private Long chapterVersionId;
|
private Long chapterVersionId;
|
||||||
|
|
||||||
@NotBlank(message = "summaryContent must not be blank")
|
@NotBlank(message = "summaryContent must not be blank")
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.unisbase.security.LoginUser;
|
||||||
import com.unisbase.service.TokenValidationService;
|
import com.unisbase.service.TokenValidationService;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -18,6 +19,7 @@ import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class AndroidAuthServiceImpl implements AndroidAuthService {
|
public class AndroidAuthServiceImpl implements AndroidAuthService {
|
||||||
|
|
||||||
private static final String HEADER_DEVICE_ID = "X-Android-Device-Id";
|
private static final String HEADER_DEVICE_ID = "X-Android-Device-Id";
|
||||||
|
|
@ -50,8 +52,8 @@ public class AndroidAuthServiceImpl implements AndroidAuthService {
|
||||||
String platform = request.getHeader(HEADER_PLATFORM);
|
String platform = request.getHeader(HEADER_PLATFORM);
|
||||||
|
|
||||||
requireAndroidHttpHeaders(deviceId, appVersion, platform);
|
requireAndroidHttpHeaders(deviceId, appVersion, platform);
|
||||||
|
log.info("[安卓接口访问]X-Android-Device-Id={},X-Android-App-Version={},X-Android-Platform={}",deviceId,appVersion,platform);
|
||||||
assertDeviceEnabled(deviceId);
|
assertDeviceEnabled(deviceId);
|
||||||
|
|
||||||
if (loginUser != null) {
|
if (loginUser != null) {
|
||||||
return buildContext("USER_JWT", false,
|
return buildContext("USER_JWT", false,
|
||||||
deviceId,
|
deviceId,
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,7 @@ import com.imeeting.entity.biz.AiTask;
|
||||||
|
|
||||||
public interface AiTaskService extends IService<AiTask> {
|
public interface AiTaskService extends IService<AiTask> {
|
||||||
void dispatchTasks(Long meetingId, Long tenantId, Long userId);
|
void dispatchTasks(Long meetingId, Long tenantId, Long userId);
|
||||||
|
void dispatchChapterTask(Long meetingId, Long tenantId, Long userId);
|
||||||
void dispatchSummaryTask(Long meetingId, Long tenantId, Long userId);
|
void dispatchSummaryTask(Long meetingId, Long tenantId, Long userId);
|
||||||
|
void reconcileMeetingStatus(Long meetingId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ public interface MeetingCommandService {
|
||||||
|
|
||||||
void retryTranscription(Long meetingId);
|
void retryTranscription(Long meetingId);
|
||||||
|
|
||||||
|
void retrySummary(Long meetingId);
|
||||||
|
|
||||||
|
void retryChapter(Long meetingId);
|
||||||
|
|
||||||
MeetingTranscriptChapterImportResultVO importTranscriptChapters(MeetingTranscriptChapterImportDTO command);
|
MeetingTranscriptChapterImportResultVO importTranscriptChapters(MeetingTranscriptChapterImportDTO command);
|
||||||
|
|
||||||
void finalizeSummary(MeetingSummaryFinalizeDTO command);
|
void finalizeSummary(MeetingSummaryFinalizeDTO command);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.imeeting.common.MeetingProgressStage;
|
||||||
import com.imeeting.common.RedisKeys;
|
import com.imeeting.common.RedisKeys;
|
||||||
import com.imeeting.dto.biz.AiModelVO;
|
import com.imeeting.dto.biz.AiModelVO;
|
||||||
import com.imeeting.dto.biz.MeetingSummarySource;
|
import com.imeeting.dto.biz.MeetingSummarySource;
|
||||||
|
import com.imeeting.dto.biz.MeetingTranscriptSourceVO;
|
||||||
import com.imeeting.entity.biz.AiTask;
|
import com.imeeting.entity.biz.AiTask;
|
||||||
import com.imeeting.entity.biz.HotWord;
|
import com.imeeting.entity.biz.HotWord;
|
||||||
import com.imeeting.entity.biz.Meeting;
|
import com.imeeting.entity.biz.Meeting;
|
||||||
|
|
@ -247,30 +248,14 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
if (!asrText.isBlank()) {
|
if (!asrText.isBlank()) {
|
||||||
meetingProgressService.markStage(meetingId, asrTask, 1, MeetingProgressStage.ASR_COMPLETED, 80, "转写完成,准备生成总结", 0);
|
meetingProgressService.markStage(meetingId, asrTask, 1, MeetingProgressStage.ASR_COMPLETED, 80, "转写完成,准备生成总结", 0);
|
||||||
scheduleQueuedAsrTasks();
|
scheduleQueuedAsrTasks();
|
||||||
|
self.dispatchChapterTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
self.dispatchSummaryTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
self.dispatchSummaryTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (chapterTask != null && canExecuteTask(chapterTask)) {
|
|
||||||
executeChapterFlow(meeting, chapterTask);
|
|
||||||
}
|
|
||||||
if (chapterTask != null && Integer.valueOf(3).equals(chapterTask.getStatus()) && hasDetailedChapterFailureMessage(chapterTask)) {
|
|
||||||
String chapterFailureMessage = buildChapterFailurePropagationMessage(chapterTask);
|
|
||||||
failPendingSummaryTask(sumTask, chapterFailureMessage);
|
|
||||||
updateMeetingStatus(meetingId, 4);
|
|
||||||
updateProgress(meetingId, -1, chapterFailureMessage, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (chapterTask != null && Integer.valueOf(3).equals(chapterTask.getStatus())) {
|
|
||||||
failPendingSummaryTask(sumTask, "章节生成失败,无法继续总结");
|
|
||||||
updateMeetingStatus(meetingId, 4);
|
|
||||||
updateProgress(meetingId, -1, "章节生成失败,无法继续总结", 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sumTask != null && canExecuteTask(sumTask)) {
|
if (sumTask != null && canExecuteTask(sumTask)) {
|
||||||
executeSummaryFlow(meeting, sumTask, chapterTask);
|
executeSummaryFlow(meeting, sumTask);
|
||||||
} else if (meeting.getStatus() != 3) {
|
|
||||||
updateMeetingStatus(meetingId, 3);
|
|
||||||
}
|
}
|
||||||
|
reconcileMeetingStatus(meetingId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Meeting {} AI Task Flow failed", meetingId, e);
|
log.error("Meeting {} AI Task Flow failed", meetingId, e);
|
||||||
failPendingSummaryTask(findLatestSummaryTask(meetingId), "转录失败,已跳过总结任务: " + e.getMessage());
|
failPendingSummaryTask(findLatestSummaryTask(meetingId), "转录失败,已跳过总结任务: " + e.getMessage());
|
||||||
|
|
@ -282,6 +267,31 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Async("summaryDispatchExecutor")
|
||||||
|
public void dispatchChapterTask(Long meetingId, Long tenantId, Long userId) {
|
||||||
|
Runnable task = () -> taskSecurityContextRunner.runAsTenantUser(tenantId, userId, () -> doDispatchChapterTask(meetingId));
|
||||||
|
if (summaryTaskExecutor == null) {
|
||||||
|
task.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
summaryTaskExecutor.execute(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDispatchChapterTask(Long meetingId) {
|
||||||
|
Meeting meeting = meetingMapper.selectById(meetingId);
|
||||||
|
if (meeting == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AiTask chapterTask = findLatestTask(meetingId, "CHAPTER");
|
||||||
|
if (chapterTask == null || !canExecuteTask(chapterTask)) {
|
||||||
|
reconcileMeetingStatus(meetingId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
executeChapterFlow(meeting, chapterTask);
|
||||||
|
reconcileMeetingStatus(meetingId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async("summaryDispatchExecutor")
|
@Async("summaryDispatchExecutor")
|
||||||
public void dispatchSummaryTask(Long meetingId, Long tenantId, Long userId) {
|
public void dispatchSummaryTask(Long meetingId, Long tenantId, Long userId) {
|
||||||
|
|
@ -304,32 +314,15 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
triggerExternalSummaryWebhook(meeting, sumTask, chapterTask, "AUTO_SUMMARY_DISPATCH", false);
|
triggerExternalSummaryWebhook(meeting, sumTask, chapterTask, "AUTO_SUMMARY_DISPATCH", false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AiTask chapterTask = findLatestTask(meetingId, "CHAPTER");
|
|
||||||
AiTask sumTask = findLatestTask(meetingId, "SUMMARY");
|
AiTask sumTask = findLatestTask(meetingId, "SUMMARY");
|
||||||
try {
|
try {
|
||||||
if (chapterTask != null && canExecuteTask(chapterTask)) {
|
|
||||||
executeChapterFlow(meeting, chapterTask);
|
|
||||||
}
|
|
||||||
if (chapterTask != null && Integer.valueOf(3).equals(chapterTask.getStatus()) && hasDetailedChapterFailureMessage(chapterTask)) {
|
|
||||||
String chapterFailureMessage = buildChapterFailurePropagationMessage(chapterTask);
|
|
||||||
failPendingSummaryTask(sumTask, chapterFailureMessage);
|
|
||||||
updateMeetingStatus(meetingId, 4);
|
|
||||||
updateProgress(meetingId, -1, chapterFailureMessage, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (chapterTask != null && Integer.valueOf(3).equals(chapterTask.getStatus())) {
|
|
||||||
failPendingSummaryTask(sumTask, "章节生成失败,无法继续总结");
|
|
||||||
updateMeetingStatus(meetingId, 4);
|
|
||||||
updateProgress(meetingId, -1, "章节生成失败,无法继续总结", 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (sumTask != null && canExecuteTask(sumTask)) {
|
if (sumTask != null && canExecuteTask(sumTask)) {
|
||||||
executeSummaryFlow(meeting, sumTask, chapterTask);
|
executeSummaryFlow(meeting, sumTask);
|
||||||
}
|
}
|
||||||
|
reconcileMeetingStatus(meetingId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Re-summary failed for meeting {}", meetingId, e);
|
log.error("Re-summary failed for meeting {}", meetingId, e);
|
||||||
updateMeetingStatus(meetingId, 4);
|
reconcileMeetingStatus(meetingId);
|
||||||
updateProgress(meetingId, -1, "Summary flow failed: " + e.getMessage(), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -994,10 +987,14 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
this.updateById(taskRecord);
|
this.updateById(taskRecord);
|
||||||
|
|
||||||
meeting.setLatestSummaryTaskId(taskRecord.getId());
|
meeting.setLatestSummaryTaskId(taskRecord.getId());
|
||||||
meeting.setStatus(3);
|
|
||||||
meetingMapper.updateById(meeting);
|
meetingMapper.updateById(meeting);
|
||||||
|
|
||||||
updateProgress(meeting.getId(), 100, "全流程分析完成", 0);
|
AiTask latestChapterTask = findLatestTask(meeting.getId(), "CHAPTER");
|
||||||
|
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
||||||
|
updateProgress(meeting.getId(), 100, "全流程分析完成", 0);
|
||||||
|
} else {
|
||||||
|
updateProgress(meeting.getId(), 95, "总结生成完成,等待 AI 目录完成...", 0);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updateAiTaskFail(taskRecord, "LLM 总结失败: " + response.body());
|
updateAiTaskFail(taskRecord, "LLM 总结失败: " + response.body());
|
||||||
throw new RuntimeException("AI总结生成异常");
|
throw new RuntimeException("AI总结生成异常");
|
||||||
|
|
@ -1048,54 +1045,9 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MeetingSummarySource restorePreparedSummarySource(AiTask chapterTask) {
|
private void executeSummaryFlow(Meeting meeting, AiTask sumTask) throws Exception {
|
||||||
if (chapterTask == null || chapterTask.getResponseData() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String rawTranscriptText = stringValue(chapterTask.getResponseData().get("rawTranscriptText"));
|
|
||||||
String chapterOutlineText = stringValue(chapterTask.getResponseData().get("chapterOutlineText"));
|
|
||||||
String text = stringValue(chapterTask.getResponseData().get("summarySourceText"));
|
|
||||||
if ((rawTranscriptText == null || rawTranscriptText.isBlank())
|
|
||||||
&& (text == null || text.isBlank())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object summarySourceSnapshot = chapterTask.getResponseData().get("summarySource");
|
|
||||||
Map<?, ?> snapshot = summarySourceSnapshot instanceof Map<?, ?> map ? map : Map.of();
|
|
||||||
if (text == null || text.isBlank()) {
|
|
||||||
text = rawTranscriptText != null && !rawTranscriptText.isBlank()
|
|
||||||
? rawTranscriptText
|
|
||||||
: chapterOutlineText;
|
|
||||||
}
|
|
||||||
return MeetingSummarySource.builder()
|
|
||||||
.text(text)
|
|
||||||
.sourceType(stringValue(snapshot.get("sourceType")))
|
|
||||||
.fallbackUsed(Boolean.TRUE.equals(snapshot.get("fallbackUsed")))
|
|
||||||
.sourceFingerprint(firstNonBlank(
|
|
||||||
stringValue(snapshot.get("sourceFingerprint")),
|
|
||||||
stringValue(chapterTask.getResponseData().get("sourceFingerprint"))
|
|
||||||
))
|
|
||||||
.chapterVersionId(longValue(firstNonNull(
|
|
||||||
snapshot.get("chapterVersionId"),
|
|
||||||
chapterTask.getResponseData().get("chapterVersionId")
|
|
||||||
)))
|
|
||||||
.chapterCount(intValue(firstNonNull(
|
|
||||||
snapshot.get("chapterCount"),
|
|
||||||
chapterTask.getResponseData().get("chapterCount")
|
|
||||||
)))
|
|
||||||
.algorithmVersion(stringValue(snapshot.get("algorithmVersion")))
|
|
||||||
.generationMode(stringValue(snapshot.get("generationMode")))
|
|
||||||
.rawTranscriptText(rawTranscriptText)
|
|
||||||
.chapterOutlineText(chapterOutlineText)
|
|
||||||
.chapterFilePath(firstNonBlank(
|
|
||||||
stringValue(snapshot.get("chapterFilePath")),
|
|
||||||
stringValue(chapterTask.getResponseData().get("chapterFilePath"))
|
|
||||||
))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeSummaryFlow(Meeting meeting, AiTask sumTask, AiTask chapterTask) throws Exception {
|
|
||||||
if (isExternalSummaryModeEnabled()) {
|
if (isExternalSummaryModeEnabled()) {
|
||||||
|
AiTask chapterTask = findLatestTask(meeting.getId(), "CHAPTER");
|
||||||
triggerExternalSummaryWebhook(meeting, sumTask, chapterTask, "AUTO_AFTER_TRANSCRIPT_READY", false);
|
triggerExternalSummaryWebhook(meeting, sumTask, chapterTask, "AUTO_AFTER_TRANSCRIPT_READY", false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1106,22 +1058,33 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
MeetingSummarySource summarySource = restorePreparedSummarySource(chapterTask);
|
MeetingSummarySource summarySource = buildRawTranscriptSummarySource(meeting);
|
||||||
if (summarySource == null || summarySource.getText() == null || summarySource.getText().isBlank()) {
|
|
||||||
summarySource = meetingTranscriptChapterService.resolveSummarySource(meeting, chapterTask != null ? chapterTask : sumTask);
|
|
||||||
}
|
|
||||||
if (summarySource.getText() == null || summarySource.getText().isBlank()) {
|
if (summarySource.getText() == null || summarySource.getText().isBlank()) {
|
||||||
failPendingSummaryTask(sumTask, "没有转录内容");
|
failPendingSummaryTask(sumTask, "没有转录内容");
|
||||||
updateMeetingStatus(meeting.getId(), 4);
|
reconcileMeetingStatus(meeting.getId());
|
||||||
updateProgress(meeting.getId(), -1, "没有转录内容", 0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
processSummaryTask(meeting, summarySource, sumTask);
|
processSummaryTask(meeting, summarySource, sumTask);
|
||||||
|
reconcileMeetingStatus(meeting.getId());
|
||||||
} finally {
|
} finally {
|
||||||
redisValueSupport.delete(summaryLockKey);
|
redisValueSupport.delete(summaryLockKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MeetingSummarySource buildRawTranscriptSummarySource(Meeting meeting) {
|
||||||
|
MeetingTranscriptSourceVO transcriptSource = meetingTranscriptChapterService.buildTranscriptSource(meeting.getId());
|
||||||
|
String transcriptText = transcriptSource == null ? null : stringValue(transcriptSource.getTranscriptText());
|
||||||
|
return MeetingSummarySource.builder()
|
||||||
|
.text(transcriptText)
|
||||||
|
.sourceType("RAW_TRANSCRIPT")
|
||||||
|
.fallbackUsed(false)
|
||||||
|
.sourceFingerprint(transcriptSource == null ? null : transcriptSource.getSourceFingerprint())
|
||||||
|
.generationMode("NONE")
|
||||||
|
.rawTranscriptText(transcriptText)
|
||||||
|
.chapterOutlineText("")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
private AiTask findLatestTask(Long meetingId, String taskType) {
|
private AiTask findLatestTask(Long meetingId, String taskType) {
|
||||||
return this.getOne(new LambdaQueryWrapper<AiTask>()
|
return this.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
.eq(AiTask::getMeetingId, meetingId)
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
|
|
@ -1176,24 +1139,37 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
&& !Integer.valueOf(3).equals(task.getStatus());
|
&& !Integer.valueOf(3).equals(task.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDetailedChapterFailureMessage(AiTask chapterTask) {
|
public void reconcileMeetingStatus(Long meetingId) {
|
||||||
String failureMessage = buildChapterFailurePropagationMessage(chapterTask);
|
if (meetingId == null) {
|
||||||
return failureMessage != null && !failureMessage.isBlank() && !failureMessage.equals("章节生成失败,无法继续总结");
|
return;
|
||||||
|
}
|
||||||
|
AiTask asrTask = findLatestTask(meetingId, "ASR");
|
||||||
|
AiTask chapterTask = findLatestTask(meetingId, "CHAPTER");
|
||||||
|
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
|
||||||
|
|
||||||
|
if (isTaskFailed(asrTask) || isTaskFailed(chapterTask) || isTaskFailed(summaryTask)) {
|
||||||
|
updateMeetingStatus(meetingId, 4);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTaskCompleted(chapterTask) && isTaskCompleted(summaryTask)) {
|
||||||
|
updateMeetingStatus(meetingId, 3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTaskCompleted(asrTask) || isTaskRunningOrQueued(chapterTask) || isTaskRunningOrQueued(summaryTask)) {
|
||||||
|
updateMeetingStatus(meetingId, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildChapterFailurePropagationMessage(AiTask chapterTask) {
|
private boolean isTaskCompleted(AiTask task) {
|
||||||
if (chapterTask == null) {
|
return task != null && Integer.valueOf(2).equals(task.getStatus());
|
||||||
return "章节生成失败,无法继续总结";
|
}
|
||||||
}
|
|
||||||
String detail = firstNonBlank(
|
private boolean isTaskFailed(AiTask task) {
|
||||||
chapterTask.getErrorMsg(),
|
return task != null && Integer.valueOf(3).equals(task.getStatus());
|
||||||
stringValue(chapterTask.getResponseData() == null ? null : chapterTask.getResponseData().get("failureReason")),
|
}
|
||||||
stringValue(chapterTask.getResponseData() == null ? null : chapterTask.getResponseData().get("exceptionMessage"))
|
|
||||||
);
|
private boolean isTaskRunningOrQueued(AiTask task) {
|
||||||
if (detail == null) {
|
return task != null && (Integer.valueOf(0).equals(task.getStatus()) || Integer.valueOf(1).equals(task.getStatus()));
|
||||||
return "章节生成失败,无法继续总结";
|
|
||||||
}
|
|
||||||
return "章节生成失败,无法继续总结: " + detail;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String stringValue(Object value) {
|
private String stringValue(Object value) {
|
||||||
|
|
@ -1212,18 +1188,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object firstNonNull(Object... values) {
|
|
||||||
if (values == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (Object value : values) {
|
|
||||||
if (value != null) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Long longValue(Object value) {
|
private Long longValue(Object value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -1235,17 +1199,6 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer intValue(Object value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Integer.parseInt(String.valueOf(value).trim());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private AiModelVO resolveAsrModelForRevision(AiTask asrTask) {
|
private AiModelVO resolveAsrModelForRevision(AiTask asrTask) {
|
||||||
if (asrTask == null || asrTask.getTaskConfig() == null) {
|
if (asrTask == null || asrTask.getTaskConfig() == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.imeeting.dto.biz.MeetingSummaryFinalizeDTO;
|
||||||
import com.imeeting.dto.biz.MeetingSummaryOrchestrationTriggerResultVO;
|
import com.imeeting.dto.biz.MeetingSummaryOrchestrationTriggerResultVO;
|
||||||
import com.imeeting.dto.biz.MeetingTranscriptChapterImportDTO;
|
import com.imeeting.dto.biz.MeetingTranscriptChapterImportDTO;
|
||||||
import com.imeeting.dto.biz.MeetingTranscriptChapterImportResultVO;
|
import com.imeeting.dto.biz.MeetingTranscriptChapterImportResultVO;
|
||||||
|
import com.imeeting.dto.biz.MeetingTranscriptSourceVO;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
import com.imeeting.dto.biz.MeetingVO;
|
||||||
import com.imeeting.dto.biz.RealtimeMeetingRuntimeProfile;
|
import com.imeeting.dto.biz.RealtimeMeetingRuntimeProfile;
|
||||||
import com.imeeting.dto.biz.RealtimeMeetingResumeConfig;
|
import com.imeeting.dto.biz.RealtimeMeetingResumeConfig;
|
||||||
|
|
@ -428,8 +429,9 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
realtimeMeetingSessionStateService.clear(meetingId);
|
realtimeMeetingSessionStateService.clear(meetingId);
|
||||||
meeting.setStatus(2);
|
meeting.setStatus(2);
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
updateMeetingProgress(meetingId, 90, "正在生成会议总结...", 0);
|
updateMeetingProgress(meetingId, 85, "正在生成 AI 目录与总结...", 0);
|
||||||
meetingDomainSupport.prewarmPlaybackAudioAfterCommit(meeting.getAudioUrl());
|
meetingDomainSupport.prewarmPlaybackAudioAfterCommit(meeting.getAudioUrl());
|
||||||
|
aiTaskService.dispatchChapterTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
aiTaskService.dispatchSummaryTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
aiTaskService.dispatchSummaryTask(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,6 +503,15 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
|
|
||||||
resetAiTask(summaryTask, summaryTask.getTaskConfig());
|
resetAiTask(summaryTask, summaryTask.getTaskConfig());
|
||||||
aiTaskService.updateById(summaryTask);
|
aiTaskService.updateById(summaryTask);
|
||||||
|
AiTask chapterTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
|
.eq(AiTask::getTaskType, "CHAPTER")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
if (chapterTask != null) {
|
||||||
|
resetAiTask(chapterTask, chapterTask.getTaskConfig());
|
||||||
|
aiTaskService.updateById(chapterTask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetAiTask(AiTask task, Map<String, Object> taskConfig) {
|
private void resetAiTask(AiTask task, Map<String, Object> taskConfig) {
|
||||||
|
|
@ -746,14 +757,9 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
throw new RuntimeException("总结任务不存在或不属于当前会议");
|
throw new RuntimeException("总结任务不存在或不属于当前会议");
|
||||||
}
|
}
|
||||||
|
|
||||||
MeetingTranscriptChapterVersion currentVersion = meetingTranscriptChapterService.getCurrentVersion(meeting.getId());
|
MeetingTranscriptSourceVO transcriptSource = meetingTranscriptChapterService.buildTranscriptSource(meeting.getId());
|
||||||
if (currentVersion == null) {
|
String currentFingerprint = transcriptSource == null ? null : transcriptSource.getSourceFingerprint();
|
||||||
throw new RuntimeException("当前会议不存在有效章节版本");
|
if (currentFingerprint == null || !Objects.equals(currentFingerprint, command.getSourceFingerprint())) {
|
||||||
}
|
|
||||||
if (!Objects.equals(currentVersion.getId(), command.getChapterVersionId())) {
|
|
||||||
throw new RuntimeException("章节版本不是当前生效版本,拒绝回填总结");
|
|
||||||
}
|
|
||||||
if (!Objects.equals(currentVersion.getSourceFingerprint(), command.getSourceFingerprint())) {
|
|
||||||
throw new RuntimeException("转录指纹已变化,拒绝回填过期总结结果");
|
throw new RuntimeException("转录指纹已变化,拒绝回填过期总结结果");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -764,12 +770,11 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
? new HashMap<>()
|
? new HashMap<>()
|
||||||
: new HashMap<>(summaryTask.getResponseData());
|
: new HashMap<>(summaryTask.getResponseData());
|
||||||
Map<String, Object> summarySource = new HashMap<>();
|
Map<String, Object> summarySource = new HashMap<>();
|
||||||
summarySource.put("sourceType", "CHAPTER_VERSION");
|
summarySource.put("sourceType", "RAW_TRANSCRIPT");
|
||||||
summarySource.put("chapterVersionId", currentVersion.getId());
|
summarySource.put("sourceFingerprint", currentFingerprint);
|
||||||
summarySource.put("chapterCount", currentVersion.getChapterCount());
|
if (command.getChapterVersionId() != null) {
|
||||||
summarySource.put("sourceFingerprint", currentVersion.getSourceFingerprint());
|
summarySource.put("chapterVersionId", command.getChapterVersionId());
|
||||||
summarySource.put("algorithmVersion", currentVersion.getAlgorithmVersion());
|
}
|
||||||
summarySource.put("generationMode", currentVersion.getGenerationMode());
|
|
||||||
responseData.put("summarySource", summarySource);
|
responseData.put("summarySource", summarySource);
|
||||||
|
|
||||||
Map<String, Object> summaryBundle = new HashMap<>();
|
Map<String, Object> summaryBundle = new HashMap<>();
|
||||||
|
|
@ -786,9 +791,19 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
aiTaskService.updateById(summaryTask);
|
aiTaskService.updateById(summaryTask);
|
||||||
|
|
||||||
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
||||||
meeting.setStatus(3);
|
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
updateMeetingProgress(meeting.getId(), 100, "外部总结回填完成", 0);
|
aiTaskService.reconcileMeetingStatus(meeting.getId());
|
||||||
|
|
||||||
|
AiTask latestChapterTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meeting.getId())
|
||||||
|
.eq(AiTask::getTaskType, "CHAPTER")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
if (latestChapterTask != null && Integer.valueOf(2).equals(latestChapterTask.getStatus())) {
|
||||||
|
updateMeetingProgress(meeting.getId(), 100, "外部总结回填完成", 0);
|
||||||
|
} else {
|
||||||
|
updateMeetingProgress(meeting.getId(), 95, "外部总结回填完成,等待 AI 目录完成...", 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -870,16 +885,15 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
|
|
||||||
if ("CHAPTER".equals(stage)) {
|
if ("CHAPTER".equals(stage)) {
|
||||||
markTaskFailed(chapterTask, "外部章节编排失败: " + errorMessage, command.getRawError());
|
markTaskFailed(chapterTask, "外部章节编排失败: " + errorMessage, command.getRawError());
|
||||||
markTaskFailed(summaryTask, "外部章节编排失败,无法继续总结: " + errorMessage, command.getRawError());
|
|
||||||
} else {
|
} else {
|
||||||
markTaskFailed(summaryTask, "外部总结编排失败: " + errorMessage, command.getRawError());
|
markTaskFailed(summaryTask, "外部总结编排失败: " + errorMessage, command.getRawError());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (summaryTask != null) {
|
if (summaryTask != null) {
|
||||||
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
meeting.setLatestSummaryTaskId(summaryTask.getId());
|
||||||
|
meetingService.updateById(meeting);
|
||||||
}
|
}
|
||||||
meeting.setStatus(4);
|
aiTaskService.reconcileMeetingStatus(meeting.getId());
|
||||||
meetingService.updateById(meeting);
|
|
||||||
updateMeetingProgress(meeting.getId(), -1, errorMessage, 0);
|
updateMeetingProgress(meeting.getId(), -1, errorMessage, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -892,34 +906,20 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String effectiveSummaryDetailLevel = resolveSummaryDetailLevel(summaryDetailLevel != null ? summaryDetailLevel : meeting.getSummaryDetailLevel());
|
String effectiveSummaryDetailLevel = resolveSummaryDetailLevel(summaryDetailLevel != null ? summaryDetailLevel : meeting.getSummaryDetailLevel());
|
||||||
Long effectiveChapterModelId = chapterModelId != null ? chapterModelId : summaryModelId;
|
meetingDomainSupport.createSummaryTask(
|
||||||
meetingDomainSupport.createChapterTask(
|
|
||||||
meetingId,
|
meetingId,
|
||||||
summaryModelId,
|
summaryModelId,
|
||||||
effectiveChapterModelId,
|
|
||||||
promptId,
|
promptId,
|
||||||
userPrompt,
|
userPrompt,
|
||||||
effectiveSummaryDetailLevel
|
effectiveSummaryDetailLevel
|
||||||
);
|
);
|
||||||
if (Objects.equals(effectiveChapterModelId, summaryModelId)) {
|
|
||||||
meetingDomainSupport.createSummaryTask(meetingId, summaryModelId, promptId, userPrompt, effectiveSummaryDetailLevel);
|
|
||||||
} else {
|
|
||||||
meetingDomainSupport.createSummaryTask(
|
|
||||||
meetingId,
|
|
||||||
summaryModelId,
|
|
||||||
effectiveChapterModelId,
|
|
||||||
promptId,
|
|
||||||
userPrompt,
|
|
||||||
effectiveSummaryDetailLevel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
|
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
|
||||||
meeting.setStatus(2);
|
meeting.setStatus(2);
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
if ("EXTERNAL_N8N".equalsIgnoreCase(summaryOrchestrationMode)) {
|
if ("EXTERNAL_N8N".equalsIgnoreCase(summaryOrchestrationMode)) {
|
||||||
updateMeetingProgress(meetingId, 95, "等待外部章节与总结编排...", 0);
|
updateMeetingProgress(meetingId, 95, "等待外部总结编排...", 0);
|
||||||
} else {
|
} else {
|
||||||
updateMeetingProgress(meetingId, 85, "重新总结已提交,正在生成章节...", 0);
|
updateMeetingProgress(meetingId, 90, "重新总结已提交,正在生成总结...", 0);
|
||||||
}
|
}
|
||||||
dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
}
|
}
|
||||||
|
|
@ -996,6 +996,74 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void retrySummary(Long meetingId) {
|
||||||
|
Meeting meeting = meetingService.getById(meetingId);
|
||||||
|
if (meeting == null) {
|
||||||
|
throw new RuntimeException("会议不存在");
|
||||||
|
}
|
||||||
|
if (meeting.getStatus() == 2) {
|
||||||
|
throw new RuntimeException("当前会议仍在处理中,请稍后再试");
|
||||||
|
}
|
||||||
|
long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>()
|
||||||
|
.eq(MeetingTranscript::getMeetingId, meetingId));
|
||||||
|
if (transcriptCount <= 0) {
|
||||||
|
throw new RuntimeException("当前会议没有可用转录,无法重试总结");
|
||||||
|
}
|
||||||
|
AiTask summaryTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
|
.eq(AiTask::getTaskType, "SUMMARY")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
if (summaryTask == null || summaryTask.getTaskConfig() == null) {
|
||||||
|
throw new RuntimeException("未找到可用的总结任务配置");
|
||||||
|
}
|
||||||
|
if (!Integer.valueOf(3).equals(summaryTask.getStatus())) {
|
||||||
|
throw new RuntimeException("当前总结环节未失败,无需重试");
|
||||||
|
}
|
||||||
|
resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig()));
|
||||||
|
aiTaskService.updateById(summaryTask);
|
||||||
|
meeting.setStatus(2);
|
||||||
|
meetingService.updateById(meeting);
|
||||||
|
updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0);
|
||||||
|
dispatchSummaryTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void retryChapter(Long meetingId) {
|
||||||
|
Meeting meeting = meetingService.getById(meetingId);
|
||||||
|
if (meeting == null) {
|
||||||
|
throw new RuntimeException("会议不存在");
|
||||||
|
}
|
||||||
|
if (meeting.getStatus() == 2) {
|
||||||
|
throw new RuntimeException("当前会议仍在处理中,请稍后再试");
|
||||||
|
}
|
||||||
|
long transcriptCount = transcriptMapper.selectCount(new LambdaQueryWrapper<MeetingTranscript>()
|
||||||
|
.eq(MeetingTranscript::getMeetingId, meetingId));
|
||||||
|
if (transcriptCount <= 0) {
|
||||||
|
throw new RuntimeException("当前会议没有可用转录,无法重试 AI 目录");
|
||||||
|
}
|
||||||
|
AiTask chapterTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
|
.eq(AiTask::getTaskType, "CHAPTER")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
if (chapterTask == null || chapterTask.getTaskConfig() == null) {
|
||||||
|
throw new RuntimeException("未找到可用的 AI 目录任务配置");
|
||||||
|
}
|
||||||
|
if (!Integer.valueOf(3).equals(chapterTask.getStatus())) {
|
||||||
|
throw new RuntimeException("当前 AI 目录环节未失败,无需重试");
|
||||||
|
}
|
||||||
|
resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig()));
|
||||||
|
aiTaskService.updateById(chapterTask);
|
||||||
|
meeting.setStatus(2);
|
||||||
|
meetingService.updateById(meeting);
|
||||||
|
updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0);
|
||||||
|
dispatchChapterTaskAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
|
}
|
||||||
|
|
||||||
private void clearLegacyDispatchState(Long meetingId) {
|
private void clearLegacyDispatchState(Long meetingId) {
|
||||||
if (compatibilityRedisTemplate == null || meetingId == null) {
|
if (compatibilityRedisTemplate == null || meetingId == null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -1092,6 +1160,19 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
aiTaskService.updateById(task);
|
aiTaskService.updateById(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dispatchChapterTaskAfterCommit(Long meetingId, Long tenantId, Long userId) {
|
||||||
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
|
aiTaskService.dispatchChapterTask(meetingId, tenantId, userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||||
|
@Override
|
||||||
|
public void afterCommit() {
|
||||||
|
aiTaskService.dispatchChapterTask(meetingId, tenantId, userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void dispatchSummaryTaskAfterCommit(Long meetingId, Long tenantId, Long userId) {
|
private void dispatchSummaryTaskAfterCommit(Long meetingId, Long tenantId, Long userId) {
|
||||||
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||||
aiTaskService.dispatchSummaryTask(meetingId, tenantId, userId);
|
aiTaskService.dispatchSummaryTask(meetingId, tenantId, userId);
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ public class MeetingMcpToolService {
|
||||||
if (summaryCompleted) {
|
if (summaryCompleted) {
|
||||||
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask));
|
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask));
|
||||||
}
|
}
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
if (isFailed(asrTask)) {
|
||||||
return new LegacyMeetingPreviewResult(
|
return new LegacyMeetingPreviewResult(
|
||||||
"503",
|
"503",
|
||||||
buildFailureMessage(asrTask, "转译"),
|
buildFailureMessage(asrTask, "转译"),
|
||||||
|
|
@ -369,7 +369,7 @@ public class MeetingMcpToolService {
|
||||||
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
||||||
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
||||||
}
|
}
|
||||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
if (isFailed(asrTask)) {
|
||||||
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||||
}
|
}
|
||||||
if (isFailed(summaryTask)) {
|
if (isFailed(summaryTask)) {
|
||||||
|
|
|
||||||
|
|
@ -380,6 +380,20 @@ export const retryMeetingTranscription = (meetingId: number) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const retryMeetingSummary = (meetingId: number) => {
|
||||||
|
return http.post<{ code: string; data: boolean; msg: string }>(
|
||||||
|
`/api/biz/meeting/${meetingId}/summary/retry`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const retryMeetingChapter = (meetingId: number) => {
|
||||||
|
return http.post<{ code: string; data: boolean; msg: string }>(
|
||||||
|
`/api/biz/meeting/${meetingId}/chapters/retry`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateMeetingBasic = (data: UpdateMeetingBasicCommand) => {
|
export const updateMeetingBasic = (data: UpdateMeetingBasicCommand) => {
|
||||||
return http.put<{ code: string; data: boolean; msg: string }>(
|
return http.put<{ code: string; data: boolean; msg: string }>(
|
||||||
`/api/biz/meeting/${data.meetingId}/basic`,
|
`/api/biz/meeting/${data.meetingId}/basic`,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ import {
|
||||||
reSummary,
|
reSummary,
|
||||||
resolveAudioMimeType,
|
resolveAudioMimeType,
|
||||||
resolveMeetingPlaybackAudioUrl,
|
resolveMeetingPlaybackAudioUrl,
|
||||||
|
retryMeetingChapter,
|
||||||
|
retryMeetingSummary,
|
||||||
retryMeetingTranscription,
|
retryMeetingTranscription,
|
||||||
updateMeetingBasic,
|
updateMeetingBasic,
|
||||||
updateMeetingTranscript,
|
updateMeetingTranscript,
|
||||||
|
|
@ -1015,8 +1017,15 @@ const MeetingDetail: React.FC = () => {
|
||||||
return false;
|
return false;
|
||||||
}, [meeting]);
|
}, [meeting]);
|
||||||
|
|
||||||
const canRetrySummary = isOwner && transcripts.length > 0 && meeting?.status !== 1 && meeting?.status !== 2;
|
const canRetrySummary = isOwner
|
||||||
|
&& transcripts.length > 0
|
||||||
|
&& meeting?.status !== 1
|
||||||
|
&& meeting?.status !== 2
|
||||||
|
&& meeting?.latestSummaryAttemptStatus !== 3
|
||||||
|
&& meeting?.latestChapterAttemptStatus !== 3;
|
||||||
const canRetryTranscription = isOwner && meeting?.status === 4 && transcripts.length === 0 && !!meeting?.audioUrl;
|
const canRetryTranscription = isOwner && meeting?.status === 4 && transcripts.length === 0 && !!meeting?.audioUrl;
|
||||||
|
const canRetryFailedSummaryTask = isOwner && meeting?.latestSummaryAttemptStatus === 3 && meeting?.status !== 2;
|
||||||
|
const canRetryFailedChapterTask = isOwner && meeting?.latestChapterAttemptStatus === 3 && meeting?.status !== 2;
|
||||||
const hasSummaryContent = Boolean(meeting?.summaryContent?.trim());
|
const hasSummaryContent = Boolean(meeting?.summaryContent?.trim());
|
||||||
const hasCatalogContent = catalogChapterLinks.length > 0;
|
const hasCatalogContent = catalogChapterLinks.length > 0;
|
||||||
const generationFailureNotice = useMemo<MeetingStateNotice | null>(() => {
|
const generationFailureNotice = useMemo<MeetingStateNotice | null>(() => {
|
||||||
|
|
@ -1362,13 +1371,11 @@ const MeetingDetail: React.FC = () => {
|
||||||
setMeeting((current) => current ? {
|
setMeeting((current) => current ? {
|
||||||
...current,
|
...current,
|
||||||
status: 2,
|
status: 2,
|
||||||
latestChapterAttemptStatus: 0,
|
|
||||||
latestChapterAttemptErrorMsg: undefined,
|
|
||||||
latestSummaryAttemptStatus: 0,
|
latestSummaryAttemptStatus: 0,
|
||||||
latestSummaryAttemptErrorMsg: undefined,
|
latestSummaryAttemptErrorMsg: undefined,
|
||||||
} : current);
|
} : current);
|
||||||
setGenerationProgress({
|
setGenerationProgress({
|
||||||
percent: 85,
|
percent: 90,
|
||||||
message: '已重新发起总结任务',
|
message: '已重新发起总结任务',
|
||||||
updateAt: Date.now(),
|
updateAt: Date.now(),
|
||||||
eta: 0,
|
eta: 0,
|
||||||
|
|
@ -1455,6 +1462,44 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRetrySummaryTask = async () => {
|
||||||
|
setActionLoading(true);
|
||||||
|
try {
|
||||||
|
await retryMeetingSummary(Number(id));
|
||||||
|
message.success('已重新提交总结任务');
|
||||||
|
setMeeting((current) => current ? {
|
||||||
|
...current,
|
||||||
|
status: 2,
|
||||||
|
latestSummaryAttemptStatus: 0,
|
||||||
|
latestSummaryAttemptErrorMsg: undefined,
|
||||||
|
} : current);
|
||||||
|
await fetchData(Number(id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setActionLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRetryChapterTask = async () => {
|
||||||
|
setActionLoading(true);
|
||||||
|
try {
|
||||||
|
await retryMeetingChapter(Number(id));
|
||||||
|
message.success('已重新提交 AI 目录任务');
|
||||||
|
setMeeting((current) => current ? {
|
||||||
|
...current,
|
||||||
|
status: 2,
|
||||||
|
latestChapterAttemptStatus: 0,
|
||||||
|
latestChapterAttemptErrorMsg: undefined,
|
||||||
|
} : current);
|
||||||
|
await fetchData(Number(id));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setActionLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeywordToggle = (keyword: string, checked: boolean) => {
|
const handleKeywordToggle = (keyword: string, checked: boolean) => {
|
||||||
setSelectedKeywords((current) => {
|
setSelectedKeywords((current) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
|
|
@ -1976,6 +2021,16 @@ const MeetingDetail: React.FC = () => {
|
||||||
重新总结
|
重新总结
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{canRetryFailedChapterTask && (
|
||||||
|
<Button icon={<SyncOutlined />} onClick={handleRetryChapterTask} loading={actionLoading}>
|
||||||
|
重试 AI 目录
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{canRetryFailedSummaryTask && (
|
||||||
|
<Button icon={<SyncOutlined />} onClick={handleRetrySummaryTask} loading={actionLoading}>
|
||||||
|
重试总结
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{canRetryTranscription && (
|
{canRetryTranscription && (
|
||||||
<Button icon={<SyncOutlined />} onClick={handleRetryTranscription} loading={actionLoading}>
|
<Button icon={<SyncOutlined />} onClick={handleRetryTranscription} loading={actionLoading}>
|
||||||
重新识别
|
重新识别
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue