diff --git a/backend/src/main/java/com/imeeting/controller/biz/MeetingController.java b/backend/src/main/java/com/imeeting/controller/biz/MeetingController.java index da914ef..3286dbd 100644 --- a/backend/src/main/java/com/imeeting/controller/biz/MeetingController.java +++ b/backend/src/main/java/com/imeeting/controller/biz/MeetingController.java @@ -301,17 +301,40 @@ public class MeetingController { return markdown; } if (!markdown.startsWith("---")) { - return markdown; + return unwrapMarkdownFence(markdown); } int second = markdown.indexOf("\n---", 3); if (second < 0) { - return markdown; + return unwrapMarkdownFence(markdown); } int contentStart = second + 4; if (contentStart < markdown.length() && markdown.charAt(contentStart) == '\n') { contentStart++; } - return markdown.substring(contentStart).trim(); + return unwrapMarkdownFence(markdown.substring(contentStart).trim()); + } + + private String unwrapMarkdownFence(String markdown) { + if (markdown == null) { + return null; + } + String normalized = markdown.trim(); + if (!normalized.startsWith("```")) { + return normalized; + } + int firstLineEnd = normalized.indexOf('\n'); + if (firstLineEnd < 0) { + return normalized; + } + String firstLine = normalized.substring(0, firstLineEnd).trim().toLowerCase(); + if (!"```".equals(firstLine) && !"```markdown".equals(firstLine) && !"```md".equals(firstLine)) { + return normalized; + } + int lastFence = normalized.lastIndexOf("\n```"); + if (lastFence <= firstLineEnd) { + return normalized.substring(firstLineEnd + 1).trim(); + } + return normalized.substring(firstLineEnd + 1, lastFence).trim(); } @GetMapping("/transcripts/{id}") @@ -423,6 +446,9 @@ public class MeetingController { return ApiResponse.error("无权修改此会议信息"); } + if (meeting.getSummaryContent() != null) { + meetingService.updateSummaryContent(meeting.getId(), meeting.getSummaryContent()); + } return ApiResponse.ok(meetingService.updateById(meeting)); } diff --git a/backend/src/main/java/com/imeeting/entity/biz/Meeting.java b/backend/src/main/java/com/imeeting/entity/biz/Meeting.java index 8e05954..32799dc 100644 --- a/backend/src/main/java/com/imeeting/entity/biz/Meeting.java +++ b/backend/src/main/java/com/imeeting/entity/biz/Meeting.java @@ -34,4 +34,7 @@ public class Meeting extends BaseEntity { private String creatorName; private Long latestSummaryTaskId; + + @TableField(exist = false) + private String summaryContent; } diff --git a/backend/src/main/java/com/imeeting/service/biz/MeetingService.java b/backend/src/main/java/com/imeeting/service/biz/MeetingService.java index 9041437..166a002 100644 --- a/backend/src/main/java/com/imeeting/service/biz/MeetingService.java +++ b/backend/src/main/java/com/imeeting/service/biz/MeetingService.java @@ -21,6 +21,7 @@ public interface MeetingService extends IService { void completeRealtimeMeeting(Long meetingId, String audioUrl); void updateSpeakerInfo(Long meetingId, String speakerId, String newName, String label); void updateMeetingParticipants(Long meetingId, String participants); + void updateSummaryContent(Long meetingId, String summaryContent); void reSummary(Long meetingId, Long summaryModelId, Long promptId); java.util.Map getDashboardStats(Long tenantId, Long userId, boolean isAdmin); List getRecentMeetings(Long tenantId, Long userId, boolean isAdmin, int limit); diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java index d951f39..c209fe0 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/AiTaskServiceImpl.java @@ -333,7 +333,7 @@ public class AiTaskServiceImpl extends ServiceImpl impleme JsonNode respNode = objectMapper.readTree(response.body()); if (response.statusCode() == 200 && respNode.has("choices")) { - String content = respNode.path("choices").path(0).path("message").path("content").asText(); + String content = sanitizeSummaryContent(respNode.path("choices").path(0).path("message").path("content").asText()); // Save to File String timestamp = java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now()); @@ -387,6 +387,33 @@ public class AiTaskServiceImpl extends ServiceImpl impleme return httpClient.send(HttpRequest.newBuilder().uri(URI.create(url)).GET().build(), HttpResponse.BodyHandlers.ofString()).body(); } + private String sanitizeSummaryContent(String content) { + if (content == null || content.isBlank()) { + return content; + } + String normalized = content.trim(); + int thinkEndIndex = normalized.lastIndexOf(""); + if (thinkEndIndex >= 0) { + normalized = normalized.substring(thinkEndIndex + "".length()).trim(); + } + if (!normalized.startsWith("```")) { + return normalized; + } + int firstLineEnd = normalized.indexOf('\n'); + if (firstLineEnd < 0) { + return normalized; + } + String firstLine = normalized.substring(0, firstLineEnd).trim().toLowerCase(); + if (!"```".equals(firstLine) && !"```markdown".equals(firstLine) && !"```md".equals(firstLine)) { + return normalized; + } + int lastFence = normalized.lastIndexOf("\n```"); + if (lastFence <= firstLineEnd) { + return normalized.substring(firstLineEnd + 1).trim(); + } + return normalized.substring(firstLineEnd + 1, lastFence).trim(); + } + private void updateMeetingStatus(Long id, int status) { Meeting m = new Meeting(); m.setId(id); m.setStatus(status); meetingMapper.updateById(m); } diff --git a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingServiceImpl.java b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingServiceImpl.java index 3ee8598..c202dd6 100644 --- a/backend/src/main/java/com/imeeting/service/biz/impl/MeetingServiceImpl.java +++ b/backend/src/main/java/com/imeeting/service/biz/impl/MeetingServiceImpl.java @@ -256,6 +256,35 @@ public class MeetingServiceImpl extends ServiceImpl impl .set(Meeting::getParticipants, participants == null ? "" : participants)); } + @Override + @Transactional(rollbackFor = Exception.class) + public void updateSummaryContent(Long meetingId, String summaryContent) { + Meeting meeting = this.getById(meetingId); + if (meeting == null) { + throw new RuntimeException("Meeting not found"); + } + + AiTask summaryTask = findLatestSummaryTask(meeting); + if (summaryTask == null || summaryTask.getResultFilePath() == null || summaryTask.getResultFilePath().isBlank()) { + throw new RuntimeException("Summary file not found"); + } + + String basePath = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/"; + Path summaryPath = Paths.get(basePath, summaryTask.getResultFilePath().replace("\\", "/")); + try { + Path parent = summaryPath.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + + String existingContent = Files.exists(summaryPath) ? Files.readString(summaryPath, StandardCharsets.UTF_8) : ""; + String frontMatter = extractFrontMatter(existingContent, meeting, summaryTask); + Files.writeString(summaryPath, frontMatter + normalizeSummaryMarkdown(summaryContent), StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException("Update summary file failed", e); + } + } + @Override @Transactional(rollbackFor = Exception.class) public void reSummary(Long meetingId, Long summaryModelId, Long promptId) { @@ -435,19 +464,7 @@ public class MeetingServiceImpl extends ServiceImpl impl private String loadSummaryContent(Meeting meeting) { try { - AiTask summaryTask = null; - if (meeting.getLatestSummaryTaskId() != null) { - summaryTask = aiTaskMapper.selectById(meeting.getLatestSummaryTaskId()); - } - if (summaryTask == null || summaryTask.getResultFilePath() == null || summaryTask.getResultFilePath().isBlank()) { - summaryTask = aiTaskMapper.selectOne(new LambdaQueryWrapper() - .eq(AiTask::getMeetingId, meeting.getId()) - .eq(AiTask::getTaskType, "SUMMARY") - .eq(AiTask::getStatus, 2) - .isNotNull(AiTask::getResultFilePath) - .orderByDesc(AiTask::getId) - .last("LIMIT 1")); - } + AiTask summaryTask = findLatestSummaryTask(meeting); if (summaryTask == null || summaryTask.getResultFilePath() == null || summaryTask.getResultFilePath().isBlank()) { return null; } @@ -466,6 +483,64 @@ public class MeetingServiceImpl extends ServiceImpl impl } } + private AiTask findLatestSummaryTask(Meeting meeting) { + AiTask summaryTask = null; + if (meeting.getLatestSummaryTaskId() != null) { + summaryTask = aiTaskMapper.selectById(meeting.getLatestSummaryTaskId()); + } + if (summaryTask == null || summaryTask.getResultFilePath() == null || summaryTask.getResultFilePath().isBlank()) { + summaryTask = aiTaskMapper.selectOne(new LambdaQueryWrapper() + .eq(AiTask::getMeetingId, meeting.getId()) + .eq(AiTask::getTaskType, "SUMMARY") + .eq(AiTask::getStatus, 2) + .isNotNull(AiTask::getResultFilePath) + .orderByDesc(AiTask::getId) + .last("LIMIT 1")); + } + return summaryTask; + } + + private String extractFrontMatter(String markdown, Meeting meeting, AiTask summaryTask) { + if (markdown != null && markdown.startsWith("---")) { + int second = markdown.indexOf("\n---", 3); + if (second >= 0) { + int end = second + 4; + if (end < markdown.length() && markdown.charAt(end) == '\n') { + end++; + } + return markdown.substring(0, end); + } + } + return "---\n" + + "updatedAt: " + LocalDateTime.now() + "\n" + + "meetingId: " + meeting.getId() + "\n" + + "summaryTaskId: " + summaryTask.getId() + "\n" + + "---\n\n"; + } + + private String normalizeSummaryMarkdown(String markdown) { + if (markdown == null) { + return ""; + } + String normalized = markdown.trim(); + if (!normalized.startsWith("```")) { + return normalized; + } + int firstLineEnd = normalized.indexOf('\n'); + if (firstLineEnd < 0) { + return normalized; + } + String firstLine = normalized.substring(0, firstLineEnd).trim().toLowerCase(); + if (!"```".equals(firstLine) && !"```markdown".equals(firstLine) && !"```md".equals(firstLine)) { + return normalized; + } + int lastFence = normalized.lastIndexOf("\n```"); + if (lastFence <= firstLineEnd) { + return normalized.substring(firstLineEnd + 1).trim(); + } + return normalized.substring(firstLineEnd + 1, lastFence).trim(); + } + private Integer resolveMeetingDuration(Long meetingId) { MeetingTranscript latestTranscript = transcriptMapper.selectOne(new LambdaQueryWrapper() .eq(MeetingTranscript::getMeetingId, meetingId) @@ -492,16 +567,16 @@ public class MeetingServiceImpl extends ServiceImpl impl return markdown; } if (!markdown.startsWith("---")) { - return markdown; + return normalizeSummaryMarkdown(markdown); } int second = markdown.indexOf("\n---", 3); if (second < 0) { - return markdown; + return normalizeSummaryMarkdown(markdown); } int contentStart = second + 4; if (contentStart < markdown.length() && markdown.charAt(contentStart) == '\n') { contentStart++; } - return markdown.substring(contentStart).trim(); + return normalizeSummaryMarkdown(markdown.substring(contentStart).trim()); } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index f623705..e477a08 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -3,12 +3,12 @@ spring: datasource: - url: jdbc:postgresql://192.168.1.55:5432/imeeting_db + url: jdbc:postgresql://10.100.51.199:5432/imeeting_db username: postgres password: postgres data: redis: - host: 192.168.1.55 + host: 10.100.51.199 port: 6379 password: unis@123 database: 15