feat: 添加会议MCP工具服务和优化章节查询
- 新增 `MeetingMcpToolService` 以提供会议列表、预览、详情和Markdown包 - 在 `MeetingQueryService` 中添加忽略租户的章节查询方法 - 更新 `MeetingPublicPreviewController` 以使用新的章节查询方法dev_na
parent
ccb408ade5
commit
b9ec41095f
|
|
@ -53,7 +53,7 @@ public class MeetingPublicPreviewController {
|
|||
data.getMeeting().setAccessPassword(null);
|
||||
}
|
||||
data.setTranscripts(meetingQueryService.getTranscripts(id));
|
||||
data.setChapters(meetingQueryService.getChapters(id));
|
||||
data.setChapters(meetingQueryService.getChaptersIgnoreTenant(id));
|
||||
return ApiResponse.ok(data);
|
||||
} catch (RuntimeException ex) {
|
||||
return ApiResponse.error(ex.getMessage());
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public interface MeetingQueryService {
|
|||
|
||||
List<Map<String, Object>> getChapters(Long meetingId);
|
||||
|
||||
List<Map<String, Object>> getChaptersIgnoreTenant(Long meetingId);
|
||||
|
||||
MeetingTranscriptSourceVO getTranscriptSource(Long meetingId);
|
||||
|
||||
MeetingSummaryPromptContextVO buildSummaryPromptContext(Long meetingId, MeetingSummaryPromptContextRequestDTO request);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,15 @@ public class MeetingQueryServiceImpl implements MeetingQueryService {
|
|||
return meetingTranscriptChapterService.listDisplayChapterAnalysis(meeting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Map<String, Object>> getChaptersIgnoreTenant(Long meetingId) {
|
||||
Meeting meeting = meetingMapper.selectByIdIgnoreTenant(meetingId);
|
||||
if (meeting == null) {
|
||||
return List.of();
|
||||
}
|
||||
return meetingTranscriptChapterService.listDisplayChapterAnalysis(meeting);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MeetingTranscriptSourceVO getTranscriptSource(Long meetingId) {
|
||||
return meetingTranscriptChapterService.buildTranscriptSource(meetingId);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,588 @@
|
|||
package com.imeeting.service.mcp;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imeeting.common.RedisKeys;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingItemResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingListResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
||||
import com.imeeting.dto.android.legacy.LegacyMeetingTagResponse;
|
||||
import com.imeeting.dto.biz.MeetingTranscriptSourceVO;
|
||||
import com.imeeting.dto.biz.MeetingVO;
|
||||
import com.imeeting.entity.biz.AiTask;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.service.biz.AiTaskService;
|
||||
import com.imeeting.service.biz.MeetingAccessService;
|
||||
import com.imeeting.service.biz.MeetingQueryService;
|
||||
import com.imeeting.service.biz.MeetingService;
|
||||
import com.imeeting.service.biz.MeetingTranscriptChapterService;
|
||||
import com.imeeting.service.biz.MeetingTranscriptFileService;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import com.unisbase.dto.PageResult;
|
||||
import com.unisbase.entity.SysUser;
|
||||
import com.unisbase.mapper.SysUserMapper;
|
||||
import com.unisbase.security.LoginUser;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeetingMcpToolService {
|
||||
|
||||
private static final String STAGE_DATA_INITIALIZATION = "data_initialization";
|
||||
private static final String STAGE_AUDIO_TRANSCRIPTION = "audio_transcription";
|
||||
private static final String STAGE_SUMMARY_GENERATION = "summary_generation";
|
||||
private static final String STAGE_COMPLETED = "completed";
|
||||
|
||||
private final MeetingQueryService meetingQueryService;
|
||||
private final MeetingAccessService meetingAccessService;
|
||||
private final MeetingService meetingService;
|
||||
private final AiTaskService aiTaskService;
|
||||
private final PromptTemplateService promptTemplateService;
|
||||
private final MeetingTranscriptFileService meetingTranscriptFileService;
|
||||
private final MeetingTranscriptChapterService meetingTranscriptChapterService;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${unisbase.app.server-base-url:}")
|
||||
private String serverBaseUrl;
|
||||
|
||||
public LegacyMeetingListResponse listCurrentUserMeetings(Integer page, Integer pageSize, String title) {
|
||||
LoginUser loginUser = currentLoginUser();
|
||||
int normalizedPage = normalizePositive(page, 1);
|
||||
int normalizedPageSize = normalizePositive(pageSize, 10);
|
||||
boolean isAdmin = Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())
|
||||
|| Boolean.TRUE.equals(loginUser.getIsTenantAdmin());
|
||||
|
||||
PageResult<List<MeetingVO>> result = meetingQueryService.pageMeetings(
|
||||
normalizedPage,
|
||||
normalizedPageSize,
|
||||
normalizeOptionalText(title),
|
||||
loginUser.getTenantId(),
|
||||
loginUser.getUserId(),
|
||||
resolveCreatorName(loginUser),
|
||||
"all",
|
||||
isAdmin
|
||||
);
|
||||
|
||||
LegacyMeetingListResponse data = new LegacyMeetingListResponse();
|
||||
data.setPage(normalizedPage);
|
||||
data.setPageSize(normalizedPageSize);
|
||||
data.setTotal(result == null ? 0L : result.getTotal());
|
||||
data.setTotalPages(normalizedPageSize <= 0 ? 0 : (data.getTotal() + normalizedPageSize - 1) / normalizedPageSize);
|
||||
data.setHasMore(normalizedPage < data.getTotalPages());
|
||||
data.setMeetings(result == null || result.getRecords() == null
|
||||
? List.of()
|
||||
: result.getRecords().stream().map(this::buildListItem).toList());
|
||||
return data;
|
||||
}
|
||||
|
||||
public LegacyMeetingPreviewResult getMeetingPreview(Long meetingId) {
|
||||
if (meetingId == null) {
|
||||
throw new IllegalArgumentException("meetingId is required");
|
||||
}
|
||||
LoginUser loginUser = currentLoginUser();
|
||||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||||
meetingAccessService.assertCanViewMeeting(meeting, loginUser);
|
||||
return buildPreviewResult(meeting);
|
||||
}
|
||||
|
||||
public Map<String, Object> getMeetingRichDetail(Long meetingId) {
|
||||
if (meetingId == null) {
|
||||
throw new IllegalArgumentException("meetingId is required");
|
||||
}
|
||||
LoginUser loginUser = currentLoginUser();
|
||||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||||
meetingAccessService.assertCanViewMeeting(meeting, loginUser);
|
||||
|
||||
MeetingVO detail = meetingQueryService.getDetail(meetingId);
|
||||
normalizeMeetingAudioUrls(detail);
|
||||
LegacyMeetingPreviewResult preview = buildPreviewResult(meeting);
|
||||
Map<String, Object> previewData = preview.getData() == null
|
||||
? new LinkedHashMap<>()
|
||||
: objectMapper.convertValue(preview.getData(), new TypeReference<Map<String, Object>>() {});
|
||||
List<Map<String, Object>> chapters = meetingQueryService.getChapters(meetingId);
|
||||
MeetingTranscriptSourceVO transcriptSource = meetingQueryService.getTranscriptSource(meetingId);
|
||||
Map<String, Object> analysis = detail == null || detail.getAnalysis() == null ? Map.of() : detail.getAnalysis();
|
||||
|
||||
previewData.put("previewCode", preview.getCode());
|
||||
previewData.put("previewMessage", preview.getMessage());
|
||||
previewData.put("meetingDetail", detail);
|
||||
previewData.put("keywords", normalizeStringList(analysis.get("keywords")));
|
||||
previewData.put("analysis", analysis);
|
||||
previewData.put("chapters", chapters);
|
||||
previewData.put("transcriptSource", transcriptSource);
|
||||
previewData.put("audioUrl", detail == null ? toAbsoluteUrl(meeting.getAudioUrl()) : detail.getAudioUrl());
|
||||
previewData.put("playbackAudioUrl", detail == null ? null : detail.getPlaybackAudioUrl());
|
||||
previewData.put("hasRawTranscript", transcriptSource != null
|
||||
&& transcriptSource.getTranscriptText() != null
|
||||
&& !transcriptSource.getTranscriptText().isBlank());
|
||||
return previewData;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMeetingMarkdownBundle(Long meetingId) {
|
||||
if (meetingId == null) {
|
||||
throw new IllegalArgumentException("meetingId is required");
|
||||
}
|
||||
LoginUser loginUser = currentLoginUser();
|
||||
Meeting meeting = meetingAccessService.requireMeeting(meetingId);
|
||||
meetingAccessService.assertCanViewMeeting(meeting, loginUser);
|
||||
|
||||
MeetingVO detail = meetingQueryService.getDetail(meetingId);
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("meetingId", meetingId);
|
||||
result.put("summaryMarkdown", detail == null ? null : detail.getSummaryContent());
|
||||
result.put("transcriptMarkdown", meetingTranscriptFileService.loadTranscriptMarkdown(meeting, detail));
|
||||
result.put("chapterMarkdown", meetingTranscriptChapterService.loadCurrentChapterMarkdown(meeting));
|
||||
return result;
|
||||
}
|
||||
|
||||
private LegacyMeetingPreviewResult buildPreviewResult(Meeting meeting) {
|
||||
if (meeting == null) {
|
||||
return new LegacyMeetingPreviewResult("404", "会议不存在", null);
|
||||
}
|
||||
|
||||
Long meetingId = meeting.getId();
|
||||
AiTask asrTask = findLatestTask(meetingId, "ASR");
|
||||
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
|
||||
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus());
|
||||
MeetingVO detail = (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted)
|
||||
? meetingQueryService.getDetail(meetingId)
|
||||
: null;
|
||||
boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank();
|
||||
|
||||
if (hasSummary) {
|
||||
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask));
|
||||
}
|
||||
if (summaryCompleted) {
|
||||
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask));
|
||||
}
|
||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"503",
|
||||
buildFailureMessage(asrTask, "转译"),
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 50, STAGE_AUDIO_TRANSCRIPTION))
|
||||
);
|
||||
}
|
||||
if (isFailed(summaryTask)) {
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"503",
|
||||
buildFailureMessage(summaryTask, "总结"),
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("转译或总结失败", 75, STAGE_SUMMARY_GENERATION))
|
||||
);
|
||||
}
|
||||
|
||||
Integer realtimeProgress = resolveRealtimeProgress(meetingId);
|
||||
if (realtimeProgress != null) {
|
||||
if (realtimeProgress >= 100) {
|
||||
MeetingVO completedDetail = detail != null ? detail : meetingQueryService.getDetail(meetingId);
|
||||
boolean completedHasSummary = completedDetail != null
|
||||
&& completedDetail.getSummaryContent() != null
|
||||
&& !completedDetail.getSummaryContent().isBlank();
|
||||
if (completedHasSummary) {
|
||||
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, completedDetail, summaryTask));
|
||||
}
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"504",
|
||||
"处理已完成,但摘要尚未同步,请稍后重试",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
|
||||
);
|
||||
}
|
||||
if (realtimeProgress < 90) {
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"400",
|
||||
"会议正在处理中",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("正在转译音频", 50, STAGE_AUDIO_TRANSCRIPTION))
|
||||
);
|
||||
}
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"400",
|
||||
"会议正在处理中",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("正在生成总结", 75, STAGE_SUMMARY_GENERATION))
|
||||
);
|
||||
}
|
||||
|
||||
boolean isSummaryStage = isSummaryStage(meeting.getStatus(), summaryTask);
|
||||
boolean isAsrStage = isAsrStage(meeting.getStatus(), asrTask, hasAudio(meeting), isSummaryStage);
|
||||
|
||||
if (!isAsrStage && !isSummaryStage) {
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"400",
|
||||
"会议正在处理中",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("会议数据准备中", 25, STAGE_DATA_INITIALIZATION))
|
||||
);
|
||||
}
|
||||
if (!isSummaryStage) {
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"400",
|
||||
"会议正在处理中",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("正在转译音频", 50, STAGE_AUDIO_TRANSCRIPTION))
|
||||
);
|
||||
}
|
||||
return new LegacyMeetingPreviewResult(
|
||||
"400",
|
||||
"会议正在处理中",
|
||||
buildProcessingPreview(meeting, summaryTask, processingStatus("正在生成总结", 75, STAGE_SUMMARY_GENERATION))
|
||||
);
|
||||
}
|
||||
|
||||
private LegacyMeetingPreviewDataResponse buildCompletedPreview(Meeting meeting, MeetingVO detail, AiTask summaryTask) {
|
||||
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
||||
data.setMeetingId(meeting.getId());
|
||||
data.setTitle(meeting.getTitle());
|
||||
data.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||
data.setSummary(detail == null ? null : detail.getSummaryContent());
|
||||
data.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||
Long promptId = resolvePromptId(summaryTask);
|
||||
data.setPromptId(promptId);
|
||||
data.setPromptName(resolvePromptName(promptId));
|
||||
List<LegacyMeetingAttendeeResponse> attendees = buildAttendees(meeting.getParticipants());
|
||||
data.setAttendees(attendees);
|
||||
data.setAttendeesCount(attendees.size());
|
||||
data.setHasPassword(meeting.getAccessPassword() != null && !meeting.getAccessPassword().isBlank());
|
||||
data.setProcessingStatus(processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED));
|
||||
return data;
|
||||
}
|
||||
|
||||
private LegacyMeetingPreviewDataResponse buildProcessingPreview(Meeting meeting,
|
||||
AiTask summaryTask,
|
||||
LegacyMeetingProcessingStatusResponse status) {
|
||||
LegacyMeetingPreviewDataResponse data = new LegacyMeetingPreviewDataResponse();
|
||||
data.setMeetingId(meeting.getId());
|
||||
data.setTitle(meeting.getTitle());
|
||||
data.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||
data.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||
Long promptId = resolvePromptId(summaryTask);
|
||||
data.setPromptId(promptId);
|
||||
data.setPromptName(resolvePromptName(promptId));
|
||||
data.setHasPassword(meeting.getAccessPassword() != null && !meeting.getAccessPassword().isBlank());
|
||||
data.setProcessingStatus(status);
|
||||
return data;
|
||||
}
|
||||
|
||||
private LegacyMeetingItemResponse buildListItem(MeetingVO meeting) {
|
||||
LegacyMeetingItemResponse item = new LegacyMeetingItemResponse();
|
||||
item.setMeetingId(meeting.getId());
|
||||
item.setTitle(meeting.getTitle());
|
||||
item.setMeetingTime(formatDateTime(meeting.getMeetingTime()));
|
||||
item.setCreatedAt(formatDateTime(meeting.getCreatedAt()));
|
||||
item.setCreatorId(meeting.getCreatorId());
|
||||
item.setCreatorUsername(resolveCreatorDisplayName(meeting.getCreatorId(), meeting.getCreatorName()));
|
||||
item.setAudioFilePath(toAbsoluteUrl(meeting.getAudioUrl()));
|
||||
item.setAudioDuration(meeting.getDuration());
|
||||
item.setAccessPassword(resolveAccessPassword(meeting.getId()));
|
||||
|
||||
List<Long> attendeeIds = meeting.getParticipantIds() == null ? List.of() : meeting.getParticipantIds();
|
||||
item.setAttendeeIds(attendeeIds);
|
||||
item.setAttendees(buildAttendees(attendeeIds));
|
||||
item.setTags(buildTags(meeting.getTags()));
|
||||
item.setSummary(resolveListSummary(meeting.getId()));
|
||||
|
||||
LegacyMeetingProcessingStatusResponse status = buildListStatus(meeting);
|
||||
item.setOverallStatus(status.getOverallStatus());
|
||||
item.setOverallProgress(status.getOverallProgress());
|
||||
item.setCurrentStage(translateListStage(status.getCurrentStage()));
|
||||
return item;
|
||||
}
|
||||
|
||||
private LegacyMeetingProcessingStatusResponse buildListStatus(MeetingVO meeting) {
|
||||
Long meetingId = meeting.getId();
|
||||
AiTask asrTask = findLatestTask(meetingId, "ASR");
|
||||
AiTask summaryTask = findLatestTask(meetingId, "SUMMARY");
|
||||
boolean summaryCompleted = summaryTask != null && Integer.valueOf(2).equals(summaryTask.getStatus());
|
||||
|
||||
if (Integer.valueOf(3).equals(meeting.getStatus()) || summaryCompleted) {
|
||||
return new LegacyMeetingProcessingStatusResponse("completed", 100, STAGE_COMPLETED);
|
||||
}
|
||||
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
|
||||
return new LegacyMeetingProcessingStatusResponse("failed", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||
}
|
||||
if (isFailed(summaryTask)) {
|
||||
return new LegacyMeetingProcessingStatusResponse("failed", 75, STAGE_SUMMARY_GENERATION);
|
||||
}
|
||||
|
||||
boolean isSummaryStage = isSummaryStage(meeting.getStatus(), summaryTask);
|
||||
boolean isAsrStage = isAsrStage(meeting.getStatus(), asrTask, hasAudio(meeting), isSummaryStage);
|
||||
|
||||
if (!isAsrStage && !isSummaryStage) {
|
||||
return new LegacyMeetingProcessingStatusResponse("pending", 0, STAGE_DATA_INITIALIZATION);
|
||||
}
|
||||
if (isSummaryStage) {
|
||||
return new LegacyMeetingProcessingStatusResponse("summarizing", 75, STAGE_SUMMARY_GENERATION);
|
||||
}
|
||||
return new LegacyMeetingProcessingStatusResponse("transcribing", 50, STAGE_AUDIO_TRANSCRIPTION);
|
||||
}
|
||||
|
||||
private String buildFailureMessage(AiTask failedTask, String stageName) {
|
||||
String error = failedTask == null || failedTask.getErrorMsg() == null || failedTask.getErrorMsg().isBlank()
|
||||
? "处理失败"
|
||||
: failedTask.getErrorMsg();
|
||||
return "会议" + stageName + "失败: " + error;
|
||||
}
|
||||
|
||||
private boolean isRunningAsr(AiTask task) {
|
||||
return task != null && (Integer.valueOf(0).equals(task.getStatus()) || Integer.valueOf(1).equals(task.getStatus()));
|
||||
}
|
||||
|
||||
private boolean isRunningSummary(AiTask task) {
|
||||
return task != null && (Integer.valueOf(0).equals(task.getStatus()) || Integer.valueOf(1).equals(task.getStatus()));
|
||||
}
|
||||
|
||||
private boolean isFailed(AiTask task) {
|
||||
return task != null && Integer.valueOf(3).equals(task.getStatus());
|
||||
}
|
||||
|
||||
private AiTask findLatestTask(Long meetingId, String taskType) {
|
||||
return aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meetingId)
|
||||
.eq(AiTask::getTaskType, taskType)
|
||||
.orderByDesc(AiTask::getId)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
private Long resolvePromptId(AiTask summaryTask) {
|
||||
if (summaryTask == null || summaryTask.getTaskConfig() == null) {
|
||||
return null;
|
||||
}
|
||||
Object rawPromptId = summaryTask.getTaskConfig().get("promptId");
|
||||
if (rawPromptId == null) {
|
||||
return null;
|
||||
}
|
||||
if (rawPromptId instanceof Number number) {
|
||||
return number.longValue();
|
||||
}
|
||||
String value = String.valueOf(rawPromptId).trim();
|
||||
if (value.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolvePromptName(Long promptId) {
|
||||
if (promptId == null) {
|
||||
return null;
|
||||
}
|
||||
PromptTemplate template = promptTemplateService.getById(promptId);
|
||||
return template == null ? null : template.getTemplateName();
|
||||
}
|
||||
|
||||
private List<LegacyMeetingAttendeeResponse> buildAttendees(String participants) {
|
||||
return buildAttendees(parseParticipantIds(participants));
|
||||
}
|
||||
|
||||
private List<LegacyMeetingAttendeeResponse> buildAttendees(List<Long> participantIds) {
|
||||
if (participantIds == null || participantIds.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
Map<Long, SysUser> userMap = sysUserMapper.selectBatchIds(participantIds).stream()
|
||||
.collect(Collectors.toMap(SysUser::getUserId, user -> user, (left, right) -> left, LinkedHashMap::new));
|
||||
|
||||
return participantIds.stream()
|
||||
.map(userId -> {
|
||||
SysUser user = userMap.get(userId);
|
||||
String caption = user == null
|
||||
? String.valueOf(userId)
|
||||
: (user.getDisplayName() != null ? user.getDisplayName() : user.getUsername());
|
||||
String username = user == null ? null : user.getUsername();
|
||||
return new LegacyMeetingAttendeeResponse(userId, username, caption);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<LegacyMeetingTagResponse> buildTags(String rawTags) {
|
||||
if (rawTags == null || rawTags.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(rawTags.split(","))
|
||||
.map(String::trim)
|
||||
.filter(value -> !value.isEmpty())
|
||||
.map(value -> new LegacyMeetingTagResponse(null, value))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private List<Long> parseParticipantIds(String participants) {
|
||||
if (participants == null || participants.isBlank()) {
|
||||
return List.of();
|
||||
}
|
||||
return Arrays.stream(participants.split(","))
|
||||
.map(String::trim)
|
||||
.filter(value -> !value.isEmpty())
|
||||
.map(value -> {
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String resolveListSummary(Long meetingId) {
|
||||
MeetingVO detail = meetingQueryService.getDetail(meetingId);
|
||||
if (detail == null || detail.getSummaryContent() == null || detail.getSummaryContent().isBlank()) {
|
||||
return null;
|
||||
}
|
||||
String summary = detail.getSummaryContent().trim();
|
||||
return summary.length() <= 240 ? summary : summary.substring(0, 240);
|
||||
}
|
||||
|
||||
private String resolveAccessPassword(Long meetingId) {
|
||||
Meeting meeting = meetingService.getById(meetingId);
|
||||
return meeting == null ? null : normalizeOptionalText(meeting.getAccessPassword());
|
||||
}
|
||||
|
||||
private String resolveCreatorDisplayName(Long creatorId, String fallbackName) {
|
||||
if (creatorId == null) {
|
||||
return fallbackName;
|
||||
}
|
||||
SysUser creator = sysUserMapper.selectById(creatorId);
|
||||
if (creator == null) {
|
||||
return fallbackName;
|
||||
}
|
||||
if (creator.getDisplayName() != null && !creator.getDisplayName().isBlank()) {
|
||||
return creator.getDisplayName();
|
||||
}
|
||||
if (creator.getUsername() != null && !creator.getUsername().isBlank()) {
|
||||
return creator.getUsername();
|
||||
}
|
||||
return fallbackName;
|
||||
}
|
||||
|
||||
private void normalizeMeetingAudioUrls(MeetingVO meeting) {
|
||||
if (meeting == null) {
|
||||
return;
|
||||
}
|
||||
meeting.setAudioUrl(toAbsoluteUrl(meeting.getAudioUrl()));
|
||||
meeting.setPlaybackAudioUrl(toAbsoluteUrl(meeting.getPlaybackAudioUrl()));
|
||||
}
|
||||
|
||||
private String toAbsoluteUrl(String url) {
|
||||
if (url == null || url.isBlank()) {
|
||||
return url;
|
||||
}
|
||||
String trimmedUrl = url.trim();
|
||||
if (trimmedUrl.matches("^[a-zA-Z][a-zA-Z\\d+\\-.]*://.*$") || trimmedUrl.startsWith("//")) {
|
||||
return trimmedUrl;
|
||||
}
|
||||
if (serverBaseUrl == null || serverBaseUrl.isBlank()) {
|
||||
return trimmedUrl;
|
||||
}
|
||||
String base = serverBaseUrl.trim();
|
||||
if (base.endsWith("/") && trimmedUrl.startsWith("/")) {
|
||||
return base.substring(0, base.length() - 1) + trimmedUrl;
|
||||
}
|
||||
if (!base.endsWith("/") && !trimmedUrl.startsWith("/")) {
|
||||
return base + "/" + trimmedUrl;
|
||||
}
|
||||
return base + trimmedUrl;
|
||||
}
|
||||
|
||||
private boolean hasAudio(Meeting meeting) {
|
||||
return meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank();
|
||||
}
|
||||
|
||||
private boolean hasAudio(MeetingVO meeting) {
|
||||
return meeting.getAudioUrl() != null && !meeting.getAudioUrl().isBlank();
|
||||
}
|
||||
|
||||
private boolean isSummaryStage(Integer meetingStatus, AiTask summaryTask) {
|
||||
return Integer.valueOf(2).equals(meetingStatus) || isRunningSummary(summaryTask);
|
||||
}
|
||||
|
||||
private boolean isAsrStage(Integer meetingStatus, AiTask asrTask, boolean hasAudio, boolean isSummaryStage) {
|
||||
return Integer.valueOf(1).equals(meetingStatus)
|
||||
|| isRunningAsr(asrTask)
|
||||
|| (hasAudio && !isSummaryStage);
|
||||
}
|
||||
|
||||
private Integer resolveRealtimeProgress(Long meetingId) {
|
||||
String rawProgress = redisTemplate.opsForValue().get(RedisKeys.meetingProgressKey(meetingId));
|
||||
if (rawProgress == null || rawProgress.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
JsonNode progress = objectMapper.readTree(rawProgress);
|
||||
return progress.hasNonNull("percent") ? progress.path("percent").asInt() : null;
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LegacyMeetingProcessingStatusResponse processingStatus(String overallStatus, int overallProgress, String currentStage) {
|
||||
return new LegacyMeetingProcessingStatusResponse(overallStatus, overallProgress, currentStage);
|
||||
}
|
||||
|
||||
private String formatDateTime(LocalDateTime value) {
|
||||
return value == null ? null : value.toString();
|
||||
}
|
||||
|
||||
private String translateListStage(String stage) {
|
||||
if (STAGE_SUMMARY_GENERATION.equals(stage)) {
|
||||
return "llm";
|
||||
}
|
||||
if (STAGE_COMPLETED.equals(stage)) {
|
||||
return "completed";
|
||||
}
|
||||
return "transcription";
|
||||
}
|
||||
|
||||
private LoginUser currentLoginUser() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null || !(authentication.getPrincipal() instanceof LoginUser loginUser)) {
|
||||
throw new IllegalStateException("MCP login user is required");
|
||||
}
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
private String resolveCreatorName(LoginUser loginUser) {
|
||||
return loginUser.getDisplayName() != null ? loginUser.getDisplayName() : loginUser.getUsername();
|
||||
}
|
||||
|
||||
private List<String> normalizeStringList(Object value) {
|
||||
if (!(value instanceof List<?> list)) {
|
||||
return List.of();
|
||||
}
|
||||
return list.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(String::valueOf)
|
||||
.map(String::trim)
|
||||
.filter(item -> !item.isEmpty())
|
||||
.toList();
|
||||
}
|
||||
|
||||
private int normalizePositive(Integer value, int defaultValue) {
|
||||
return value == null || value <= 0 ? defaultValue : value;
|
||||
}
|
||||
|
||||
private String normalizeOptionalText(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = value.trim();
|
||||
return normalized.isEmpty() ? null : normalized;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue