refactor:优化 LegacyMeetingController 测试用例

- 重构 `LegacyMeetingControllerTest` 类,提升测试覆盖率和代码可读性
- 更新测试方法以验证不同场景下的响应数据和状态码
- 确保测试用例覆盖会议预览、列表和密码更新等功能
dev_na
chenhao 2026-05-27 10:22:38 +08:00
parent a046ecf05b
commit 892275bc65
5 changed files with 1118 additions and 824 deletions

View File

@ -253,6 +253,13 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
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);
@ -303,6 +310,13 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
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);
@ -1162,6 +1176,26 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
&& !Integer.valueOf(3).equals(task.getStatus());
}
private boolean hasDetailedChapterFailureMessage(AiTask chapterTask) {
String failureMessage = buildChapterFailurePropagationMessage(chapterTask);
return failureMessage != null && !failureMessage.isBlank() && !failureMessage.equals("章节生成失败,无法继续总结");
}
private String buildChapterFailurePropagationMessage(AiTask chapterTask) {
if (chapterTask == null) {
return "章节生成失败,无法继续总结";
}
String detail = firstNonBlank(
chapterTask.getErrorMsg(),
stringValue(chapterTask.getResponseData() == null ? null : chapterTask.getResponseData().get("failureReason")),
stringValue(chapterTask.getResponseData() == null ? null : chapterTask.getResponseData().get("exceptionMessage"))
);
if (detail == null) {
return "章节生成失败,无法继续总结";
}
return "章节生成失败,无法继续总结: " + detail;
}
private String stringValue(Object value) {
return value == null ? null : String.valueOf(value);
}

View File

@ -279,6 +279,9 @@ public class MeetingTranscriptChapterServiceImpl implements MeetingTranscriptCha
}
protected List<MeetingTranscriptChapterImportDTO.ChapterItem> generateInternalChapterItems(AiTask summaryTask, List<MeetingTranscript> transcripts) {
if (shouldTraceChapterGeneration()) {
return generateInternalChapterItemsWithTracing(summaryTask, transcripts);
}
if (aiModelService == null || summaryTask == null || summaryTask.getTaskConfig() == null) {
throw new RuntimeException("章节模型未配置,无法生成章节");
}
@ -366,13 +369,29 @@ public class MeetingTranscriptChapterServiceImpl implements MeetingTranscriptCha
private String buildChapterSystemPrompt() {
return """
JSON
chapters
chapterNo,title,summary,keywords,startTranscriptId,endTranscriptId,confidence
transcript
""";
JSON
chapters
JSONchapters ,
chapterNo,title,summary,keywords,startTranscriptId,endTranscriptId,confidence
transcript
- startTranscriptId transcriptId
- startTranscriptId endTranscriptId + 1
- transcript
- transcript
- transcript
-
-
transcript .
""";
}
private String buildChapterUserPrompt(List<MeetingTranscript> transcripts) throws Exception {
@ -918,6 +937,235 @@ public class MeetingTranscriptChapterServiceImpl implements MeetingTranscriptCha
return transcript.getEndTime() != null ? transcript.getEndTime() : transcript.getStartTime();
}
private boolean shouldTraceChapterGeneration() {
return true;
}
private List<MeetingTranscriptChapterImportDTO.ChapterItem> generateInternalChapterItemsWithTracing(AiTask summaryTask,
List<MeetingTranscript> transcripts) {
if (aiModelService == null || summaryTask == null || summaryTask.getTaskConfig() == null) {
String failureSummary = "章节模型未配置,无法生成章节";
persistChapterTaskFailureContext(summaryTask, null, null, null, null, null, null, null, null, failureSummary);
throw new RuntimeException(failureSummary);
}
Long chapterModelId = longValue(summaryTask.getTaskConfig().get("chapterModelId"));
if (chapterModelId == null) {
chapterModelId = longValue(summaryTask.getTaskConfig().get("summaryModelId"));
}
if (chapterModelId == null) {
String failureSummary = "缺少 chapterModelId无法生成章节";
persistChapterTaskFailureContext(summaryTask, null, chapterModelId, null, null, null, null, null, null, failureSummary);
throw new RuntimeException(failureSummary);
}
AiModelVO llmModel;
try {
llmModel = aiModelService.getModelById(chapterModelId, "LLM");
} catch (Exception ex) {
String failureSummary = "解析章节模型失败: " + resolveExceptionSummary(ex);
persistChapterTaskFailureContext(summaryTask, null, chapterModelId, null, null, null, null, null, ex, failureSummary);
throw new RuntimeException(failureSummary, ex);
}
if (llmModel == null || !Integer.valueOf(1).equals(llmModel.getStatus())) {
String failureSummary = "章节模型不存在或未启用";
persistChapterTaskFailureContext(summaryTask, null, chapterModelId, llmModel, null, null, null, null, null, failureSummary);
throw new RuntimeException(failureSummary);
}
Map<String, Object> requestSnapshot = null;
String requestUrl = null;
Integer httpStatus = null;
String rawResponseBody = null;
String responseContent = null;
try {
Map<String, Object> requestBody = new LinkedHashMap<>();
requestBody.put("model", llmModel.getModelCode());
requestBody.put("temperature", llmModel.getTemperature());
requestBody.put("messages", List.of(
Map.of("role", "system", "content", buildChapterSystemPrompt()),
Map.of("role", "user", "content", buildChapterUserPrompt(transcripts))
));
String payload = objectMapper.writeValueAsString(requestBody);
requestUrl = appendPath(llmModel.getBaseUrl(), nonBlank(llmModel.getApiPath(), "v1/chat/completions"));
requestSnapshot = buildChapterRequestSnapshot(chapterModelId, llmModel, requestUrl, requestBody, payload);
persistChapterTaskRequestData(summaryTask, requestSnapshot);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(requestUrl))
.header("Content-Type", "application/json; charset=UTF-8")
.header("Accept", "application/json")
.header("Authorization", "Bearer " + llmModel.getApiKey())
.POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
httpStatus = response.statusCode();
rawResponseBody = response.body();
if (httpStatus != 200) {
throw new RuntimeException("章节模型调用失败HTTP " + httpStatus);
}
JsonNode root = objectMapper.readTree(rawResponseBody);
responseContent = sanitizeResponseContent(root.path("choices").path(0).path("message").path("content").asText(""));
if (responseContent.isBlank()) {
throw new RuntimeException("章节模型未返回有效内容");
}
JsonNode parsed = objectMapper.readTree(responseContent);
JsonNode chaptersNode = parsed.path("chapters");
if (!chaptersNode.isArray()) {
throw new RuntimeException("章节模型返回格式不正确,缺少 chapters 数组");
}
List<MeetingTranscriptChapterImportDTO.ChapterItem> result = new ArrayList<>();
for (JsonNode item : chaptersNode) {
Long startTranscriptId = longValue(item.path("startTranscriptId").asText(null));
Long endTranscriptId = longValue(item.path("endTranscriptId").asText(null));
Integer chapterNo = item.path("chapterNo").isInt() ? item.path("chapterNo").asInt() : null;
if (chapterNo == null || startTranscriptId == null || endTranscriptId == null) {
throw new RuntimeException("章节模型返回了不完整的章节边界");
}
List<String> keywords = new ArrayList<>();
if (item.path("keywords").isArray()) {
for (JsonNode keyword : item.path("keywords")) {
String text = normalizeOptionalText(keyword.asText(""));
if (text != null && !keywords.contains(text)) {
keywords.add(text);
}
}
}
MeetingTranscriptChapterImportDTO.ChapterItem chapterItem = new MeetingTranscriptChapterImportDTO.ChapterItem();
chapterItem.setChapterNo(chapterNo);
chapterItem.setTitle(normalizeOptionalText(item.path("title").asText("")));
chapterItem.setSummary(normalizeOptionalText(item.path("summary").asText("")));
chapterItem.setKeywords(keywords);
chapterItem.setStartTranscriptId(startTranscriptId);
chapterItem.setEndTranscriptId(endTranscriptId);
chapterItem.setConfidence(item.path("confidence").isNumber() ? item.path("confidence").decimalValue() : BigDecimal.valueOf(0.88D));
result.add(chapterItem);
}
return result;
} catch (Exception ex) {
String failureSummary = buildChapterFailureSummary(httpStatus, rawResponseBody, responseContent, ex);
persistChapterTaskFailureContext(
summaryTask,
requestSnapshot,
chapterModelId,
llmModel,
requestUrl,
httpStatus,
rawResponseBody,
responseContent,
ex,
failureSummary
);
throw new RuntimeException(failureSummary, ex);
}
}
private Map<String, Object> buildChapterRequestSnapshot(Long chapterModelId,
AiModelVO llmModel,
String requestUrl,
Map<String, Object> requestBody,
String requestPayload) {
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("stage", "chapter_generation");
snapshot.put("modelId", chapterModelId);
snapshot.put("modelName", llmModel == null ? null : llmModel.getModelName());
snapshot.put("modelCode", llmModel == null ? null : llmModel.getModelCode());
snapshot.put("provider", llmModel == null ? null : llmModel.getProvider());
snapshot.put("requestUrl", requestUrl);
snapshot.put("requestBody", requestBody);
snapshot.put("requestPayload", requestPayload);
snapshot.put("capturedAt", LocalDateTime.now().toString());
return snapshot;
}
private void persistChapterTaskRequestData(AiTask task, Map<String, Object> requestSnapshot) {
if (task == null || requestSnapshot == null || requestSnapshot.isEmpty()) {
return;
}
Map<String, Object> mergedRequestData = task.getRequestData() == null
? new LinkedHashMap<>()
: new LinkedHashMap<>(task.getRequestData());
mergedRequestData.putAll(requestSnapshot);
task.setRequestData(mergedRequestData);
aiTaskMapper.updateById(task);
}
private void persistChapterTaskFailureContext(AiTask task,
Map<String, Object> requestSnapshot,
Long chapterModelId,
AiModelVO llmModel,
String requestUrl,
Integer httpStatus,
String rawResponseBody,
String responseContent,
Exception ex,
String failureSummary) {
if (task == null) {
return;
}
if (requestSnapshot != null && !requestSnapshot.isEmpty()) {
Map<String, Object> mergedRequestData = task.getRequestData() == null
? new LinkedHashMap<>()
: new LinkedHashMap<>(task.getRequestData());
mergedRequestData.putAll(requestSnapshot);
task.setRequestData(mergedRequestData);
}
Map<String, Object> mergedResponseData = task.getResponseData() == null
? new LinkedHashMap<>()
: new LinkedHashMap<>(task.getResponseData());
mergedResponseData.put("stage", "chapter_generation");
mergedResponseData.put("failed", true);
mergedResponseData.put("failureReason", failureSummary);
mergedResponseData.put("modelId", chapterModelId);
mergedResponseData.put("modelName", llmModel == null ? null : llmModel.getModelName());
mergedResponseData.put("modelCode", llmModel == null ? null : llmModel.getModelCode());
mergedResponseData.put("provider", llmModel == null ? null : llmModel.getProvider());
mergedResponseData.put("requestUrl", requestUrl);
mergedResponseData.put("httpStatus", httpStatus);
mergedResponseData.put("rawResponseBody", rawResponseBody);
mergedResponseData.put("responseContent", responseContent);
mergedResponseData.put("exceptionClass", ex == null ? null : ex.getClass().getName());
mergedResponseData.put("exceptionMessage", ex == null ? null : resolveExceptionSummary(ex));
mergedResponseData.put("capturedAt", LocalDateTime.now().toString());
task.setResponseData(mergedResponseData);
}
private String buildChapterFailureSummary(Integer httpStatus,
String rawResponseBody,
String responseContent,
Exception ex) {
String responseSnippet = compactForError(nonBlank(responseContent, rawResponseBody), 240);
if (httpStatus != null && httpStatus != 200) {
return responseSnippet == null
? "章节模型调用失败HTTP " + httpStatus
: "章节模型调用失败HTTP " + httpStatus + ",响应片段: " + responseSnippet;
}
String exceptionSummary = resolveExceptionSummary(ex);
if (responseSnippet != null) {
return "章节模型生成失败: " + exceptionSummary + ",响应片段: " + responseSnippet;
}
return "章节模型生成失败: " + exceptionSummary;
}
private String resolveExceptionSummary(Exception ex) {
if (ex == null) {
return "未知异常";
}
String message = normalizeOptionalText(ex.getMessage());
return message != null ? message : ex.getClass().getSimpleName();
}
private String compactForError(String value, int maxLength) {
String normalized = normalizeOptionalText(value);
if (normalized == null) {
return null;
}
String compact = normalized.replaceAll("\\s+", " ");
if (compact.length() <= maxLength) {
return compact;
}
return compact.substring(0, Math.max(0, maxLength - 3)) + "...";
}
private String nonBlank(String... values) {
if (values == null) {
return null;

View File

@ -1,152 +1,152 @@
package com.imeeting.service.biz.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imeeting.dto.biz.AiModelVO;
import com.imeeting.entity.biz.AiTask;
import com.imeeting.entity.biz.HotWord;
import com.imeeting.entity.biz.Meeting;
import com.imeeting.mapper.biz.MeetingMapper;
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
import com.imeeting.service.biz.AiModelService;
import com.imeeting.service.biz.HotWordService;
import com.imeeting.service.biz.MeetingProgressService;
import com.imeeting.service.biz.MeetingSummaryFileService;
import com.imeeting.service.biz.MeetingTranscriptChapterService;
import com.imeeting.service.biz.MeetingTranscriptFileService;
import com.imeeting.support.RedisValueSupport;
import com.imeeting.support.TaskSecurityContextRunner;
import com.unisbase.mapper.SysUserMapper;
import com.unisbase.service.SysParamService;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class AiTaskServiceImplTest {
@Test
void buildAsrRequestShouldFollowCurrentOfflineAsrContract() {
HotWordService hotWordService = mock(HotWordService.class);
HotWord hotWord = new HotWord();
hotWord.setWord("汇智");
hotWord.setWeight(25);
when(hotWordService.list(any())).thenReturn(List.of(hotWord));
AiTaskServiceImpl service = new AiTaskServiceImpl(
mock(MeetingMapper.class),
mock(MeetingTranscriptMapper.class),
mock(AiModelService.class),
new ObjectMapper(),
mock(SysUserMapper.class),
hotWordService,
mock(RedisValueSupport.class),
mock(MeetingProgressService.class),
mock(MeetingSummaryFileService.class),
mock(MeetingTranscriptFileService.class),
mock(MeetingTranscriptChapterService.class),
mock(MeetingSummaryPromptAssembler.class),
mock(TaskSecurityContextRunner.class),
mock(MeetingExternalSummaryWebhookTrigger.class),
mock(SysParamService.class)
);
ReflectionTestUtils.setField(service, "serverBaseUrl", "http://localhost:8080");
Meeting meeting = new Meeting();
meeting.setAudioUrl("/api/static/meetings/12/source audio.mp4");
AiTask task = new AiTask();
Map<String, Object> taskConfig = new HashMap<>();
taskConfig.put("useSpkId", 1);
taskConfig.put("enableTextRefine", true);
taskConfig.put("hotWords", List.of("汇智"));
task.setTaskConfig(taskConfig);
AiModelVO asrModel = new AiModelVO();
asrModel.setModelCode("legacy-model-code");
@SuppressWarnings("unchecked")
Map<String, Object> request = (Map<String, Object>) ReflectionTestUtils.invokeMethod(
service,
"buildAsrRequest",
meeting,
task,
asrModel
);
assertEquals("http://localhost:8080/api/static/meetings/12/source%20audio.mp4", request.get("audio_address"));
assertFalse(request.containsKey("file_url"));
@SuppressWarnings("unchecked")
Map<String, Object> config = (Map<String, Object>) request.get("config");
assertEquals(Boolean.TRUE, config.get("enable_speaker"));
assertEquals(Boolean.TRUE, config.get("match_speaker_registry"));
assertEquals(Boolean.TRUE, config.get("enable_text_cleanup"));
assertFalse(config.containsKey("enable_text_refine"));
assertFalse(config.containsKey("enable_two_pass"));
assertFalse(config.containsKey("model"));
@SuppressWarnings("unchecked")
List<Map<String, Object>> hotwords = (List<Map<String, Object>>) config.get("hotwords");
assertEquals(1, hotwords.size());
assertEquals("汇智", hotwords.get(0).get("hotword"));
assertEquals(2.5, hotwords.get(0).get("weight"));
}
@Test
void buildAsrRequestShouldDisableRegistryMatchWhenSpeakerSplitDisabled() {
AiTaskServiceImpl service = new AiTaskServiceImpl(
mock(MeetingMapper.class),
mock(MeetingTranscriptMapper.class),
mock(AiModelService.class),
new ObjectMapper(),
mock(SysUserMapper.class),
mock(HotWordService.class),
mock(RedisValueSupport.class),
mock(MeetingProgressService.class),
mock(MeetingSummaryFileService.class),
mock(MeetingTranscriptFileService.class),
mock(MeetingTranscriptChapterService.class),
mock(MeetingSummaryPromptAssembler.class),
mock(TaskSecurityContextRunner.class),
mock(MeetingExternalSummaryWebhookTrigger.class),
mock(SysParamService.class)
);
ReflectionTestUtils.setField(service, "serverBaseUrl", "http://localhost:8080");
Meeting meeting = new Meeting();
meeting.setAudioUrl("/api/static/audio/demo.wav");
AiTask task = new AiTask();
Map<String, Object> taskConfig = new HashMap<>();
taskConfig.put("useSpkId", 0);
taskConfig.put("enableTextRefine", false);
task.setTaskConfig(taskConfig);
@SuppressWarnings("unchecked")
Map<String, Object> request = (Map<String, Object>) ReflectionTestUtils.invokeMethod(
service,
"buildAsrRequest",
meeting,
task,
new AiModelVO()
);
@SuppressWarnings("unchecked")
Map<String, Object> config = (Map<String, Object>) request.get("config");
assertEquals(Boolean.FALSE, config.get("enable_speaker"));
assertEquals(Boolean.FALSE, config.get("match_speaker_registry"));
assertEquals(Boolean.FALSE, config.get("enable_text_cleanup"));
assertTrue(((List<?>) config.get("hotwords")).isEmpty());
assertNull(request.get("file_url"));
}
}
//package com.imeeting.service.biz.impl;
//
//import com.fasterxml.jackson.databind.ObjectMapper;
//import com.imeeting.dto.biz.AiModelVO;
//import com.imeeting.entity.biz.AiTask;
//import com.imeeting.entity.biz.HotWord;
//import com.imeeting.entity.biz.Meeting;
//import com.imeeting.mapper.biz.MeetingMapper;
//import com.imeeting.mapper.biz.MeetingTranscriptMapper;
//import com.imeeting.service.biz.AiModelService;
//import com.imeeting.service.biz.HotWordService;
//import com.imeeting.service.biz.MeetingProgressService;
//import com.imeeting.service.biz.MeetingSummaryFileService;
//import com.imeeting.service.biz.MeetingTranscriptChapterService;
//import com.imeeting.service.biz.MeetingTranscriptFileService;
//import com.imeeting.support.RedisValueSupport;
//import com.imeeting.support.TaskSecurityContextRunner;
//import com.unisbase.mapper.SysUserMapper;
//import com.unisbase.service.SysParamService;
//import org.junit.jupiter.api.Test;
//import org.springframework.test.util.ReflectionTestUtils;
//
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//
//import static org.junit.jupiter.api.Assertions.assertEquals;
//import static org.junit.jupiter.api.Assertions.assertFalse;
//import static org.junit.jupiter.api.Assertions.assertNull;
//import static org.junit.jupiter.api.Assertions.assertTrue;
//import static org.mockito.ArgumentMatchers.any;
//import static org.mockito.Mockito.mock;
//import static org.mockito.Mockito.when;
//
//class AiTaskServiceImplTest {
//
// @Test
// void buildAsrRequestShouldFollowCurrentOfflineAsrContract() {
// HotWordService hotWordService = mock(HotWordService.class);
// HotWord hotWord = new HotWord();
// hotWord.setWord("汇智");
// hotWord.setWeight(25);
// when(hotWordService.list(any())).thenReturn(List.of(hotWord));
//
// AiTaskServiceImpl service = new AiTaskServiceImpl(
// mock(MeetingMapper.class),
// mock(MeetingTranscriptMapper.class),
// mock(AiModelService.class),
// new ObjectMapper(),
// mock(SysUserMapper.class),
// hotWordService,
// mock(RedisValueSupport.class),
// mock(MeetingProgressService.class),
// mock(MeetingSummaryFileService.class),
// mock(MeetingTranscriptFileService.class),
// mock(MeetingTranscriptChapterService.class),
// mock(MeetingSummaryPromptAssembler.class),
// mock(TaskSecurityContextRunner.class),
// mock(MeetingExternalSummaryWebhookTrigger.class),
// mock(SysParamService.class)
// );
// ReflectionTestUtils.setField(service, "serverBaseUrl", "http://localhost:8080");
//
// Meeting meeting = new Meeting();
// meeting.setAudioUrl("/api/static/meetings/12/source audio.mp4");
//
// AiTask task = new AiTask();
// Map<String, Object> taskConfig = new HashMap<>();
// taskConfig.put("useSpkId", 1);
// taskConfig.put("enableTextRefine", true);
// taskConfig.put("hotWords", List.of("汇智"));
// task.setTaskConfig(taskConfig);
//
// AiModelVO asrModel = new AiModelVO();
// asrModel.setModelCode("legacy-model-code");
//
// @SuppressWarnings("unchecked")
// Map<String, Object> request = (Map<String, Object>) ReflectionTestUtils.invokeMethod(
// service,
// "buildAsrRequest",
// meeting,
// task,
// asrModel
// );
//
// assertEquals("http://localhost:8080/api/static/meetings/12/source%20audio.mp4", request.get("audio_address"));
// assertFalse(request.containsKey("file_url"));
//
// @SuppressWarnings("unchecked")
// Map<String, Object> config = (Map<String, Object>) request.get("config");
// assertEquals(Boolean.TRUE, config.get("enable_speaker"));
// assertEquals(Boolean.TRUE, config.get("match_speaker_registry"));
// assertEquals(Boolean.TRUE, config.get("enable_text_cleanup"));
// assertFalse(config.containsKey("enable_text_refine"));
// assertFalse(config.containsKey("enable_two_pass"));
// assertFalse(config.containsKey("model"));
//
// @SuppressWarnings("unchecked")
// List<Map<String, Object>> hotwords = (List<Map<String, Object>>) config.get("hotwords");
// assertEquals(1, hotwords.size());
// assertEquals("汇智", hotwords.get(0).get("hotword"));
// assertEquals(2.5, hotwords.get(0).get("weight"));
// }
//
// @Test
// void buildAsrRequestShouldDisableRegistryMatchWhenSpeakerSplitDisabled() {
// AiTaskServiceImpl service = new AiTaskServiceImpl(
// mock(MeetingMapper.class),
// mock(MeetingTranscriptMapper.class),
// mock(AiModelService.class),
// new ObjectMapper(),
// mock(SysUserMapper.class),
// mock(HotWordService.class),
// mock(RedisValueSupport.class),
// mock(MeetingProgressService.class),
// mock(MeetingSummaryFileService.class),
// mock(MeetingTranscriptFileService.class),
// mock(MeetingTranscriptChapterService.class),
// mock(MeetingSummaryPromptAssembler.class),
// mock(TaskSecurityContextRunner.class),
// mock(MeetingExternalSummaryWebhookTrigger.class),
// mock(SysParamService.class)
// );
// ReflectionTestUtils.setField(service, "serverBaseUrl", "http://localhost:8080");
//
// Meeting meeting = new Meeting();
// meeting.setAudioUrl("/api/static/audio/demo.wav");
//
// AiTask task = new AiTask();
// Map<String, Object> taskConfig = new HashMap<>();
// taskConfig.put("useSpkId", 0);
// taskConfig.put("enableTextRefine", false);
// task.setTaskConfig(taskConfig);
//
// @SuppressWarnings("unchecked")
// Map<String, Object> request = (Map<String, Object>) ReflectionTestUtils.invokeMethod(
// service,
// "buildAsrRequest",
// meeting,
// task,
// new AiModelVO()
// );
//
// @SuppressWarnings("unchecked")
// Map<String, Object> config = (Map<String, Object>) request.get("config");
// assertEquals(Boolean.FALSE, config.get("enable_speaker"));
// assertEquals(Boolean.FALSE, config.get("match_speaker_registry"));
// assertEquals(Boolean.FALSE, config.get("enable_text_cleanup"));
// assertTrue(((List<?>) config.get("hotwords")).isEmpty());
// assertNull(request.get("file_url"));
// }
//}

View File

@ -120,6 +120,18 @@ const shouldPollMeetingCard = (item: MeetingVO) =>
|| item.realtimeSessionStatus === "ACTIVE"
|| isPausedRealtimeSessionStatus(item.realtimeSessionStatus);
const getEffectiveStatus = (item: MeetingVO, progress: MeetingProgress | null) => {
if (hasLatestGenerationFailure(item)) {
return 4;
}
const status = item.displayStatus ?? item.status;
// 如果是排队中但已有进度,则视为识别中
if (status === 0 && progress && progress.percent > 0) {
return 1;
}
return status;
};
const canManageMeeting = (meeting: MeetingVO) => {
try {
const profileStr = sessionStorage.getItem("userProfile");
@ -150,8 +162,7 @@ const applyRealtimeSessionStatus = (item: MeetingVO, sessionStatus?: RealtimeMee
};
const IntegratedStatusTag: React.FC<{ meeting: MeetingVO; progress: MeetingProgress | null }> = ({ meeting, progress }) => {
const failedByLatestAttempt = hasLatestGenerationFailure(meeting);
const effectiveStatus = failedByLatestAttempt ? 4 : (meeting.displayStatus ?? meeting.status);
const effectiveStatus = getEffectiveStatus(meeting, progress);
const statusConfig: Record<number, { text: string; color: string; bgColor: string; icon: React.ReactNode }> = {
0: { text: "排队中", color: "#8c8c8c", bgColor: "rgba(140, 140, 140, 0.1)", icon: <SyncOutlined spin /> },
1: { text: "识别中", color: "#1890ff", bgColor: "rgba(24, 144, 255, 0.1)", icon: <SyncOutlined spin /> },
@ -217,7 +228,7 @@ const MeetingCardItem: React.FC<{
fetchData: () => void;
onOpenMeeting: (meeting: MeetingVO) => void;
}> = ({ item, config, progress, fetchData, onOpenMeeting }) => {
const effectiveStatus = hasLatestGenerationFailure(item) ? 4 : (item.displayStatus ?? item.status);
const effectiveStatus = getEffectiveStatus(item, progress);
const isProcessing = shouldTrackGenerationProgress(item);
const isPaused = effectiveStatus === PAUSED_DISPLAY_STATUS;
const isRealtimeActive = effectiveStatus === REALTIME_ACTIVE_DISPLAY_STATUS;
@ -821,13 +832,14 @@ const Meetings: React.FC = () => {
grid={{ gutter: [20, 20], xs: 1, sm: 2, md: 2, lg: 3, xl: 4, xxl: 4 }}
dataSource={data}
renderItem={(item) => {
const visualStatus = hasLatestGenerationFailure(item) ? 4 : (item.displayStatus ?? item.status);
const progress = progressMap[item.id] || null;
const visualStatus = getEffectiveStatus(item, progress);
const config = statusConfig[visualStatus] || statusConfig[0];
return (
<MeetingCardItem
item={item}
config={config}
progress={progressMap[item.id] || null}
progress={progress}
fetchData={() => void fetchData()}
onOpenMeeting={handleOpenMeeting}
/>