feat: 添加热词组支持并优化相关逻辑
- 在 `RealtimeMeetingResumeConfig` 和 `MeetingVO` 中添加 `hotWordGroupId` 和 `hotWordGroupName` 字段 - 更新 `MeetingCommandServiceImpl` 以设置 `hotWordGroupId` 到 `resumeConfig` - 在 `MeetingDomainSupport` 中添加方法以解析和填充有效的热词组信息 - 更新前端 `meeting.ts` 和 `MeetingDetail.tsx` 以支持热词组显示dev_na
parent
202ce057fd
commit
69e6eff9d9
|
|
@ -75,6 +75,12 @@ public class MeetingVO {
|
||||||
@Schema(description = "总结模板ID")
|
@Schema(description = "总结模板ID")
|
||||||
private Long promptId;
|
private Long promptId;
|
||||||
|
|
||||||
|
@Schema(description = "最终生效热词组ID")
|
||||||
|
private Long hotWordGroupId;
|
||||||
|
|
||||||
|
@Schema(description = "最终生效热词组名称")
|
||||||
|
private String hotWordGroupName;
|
||||||
|
|
||||||
@Schema(description = "是否启用 AI 目录")
|
@Schema(description = "是否启用 AI 目录")
|
||||||
private Boolean aiCatalogEnabled;
|
private Boolean aiCatalogEnabled;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ public class RealtimeMeetingResumeConfig {
|
||||||
private Boolean saveAudio;
|
private Boolean saveAudio;
|
||||||
@Schema(description = "热词列表")
|
@Schema(description = "热词列表")
|
||||||
private List<Map<String, Object>> hotwords;
|
private List<Map<String, Object>> hotwords;
|
||||||
|
@Schema(description = "最终生效热词组ID")
|
||||||
|
private Long hotWordGroupId;
|
||||||
@Schema(description = "腾讯说话人上下文 ID")
|
@Schema(description = "腾讯说话人上下文 ID")
|
||||||
private String speakerContextId;
|
private String speakerContextId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1512,6 +1512,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
resumeConfig.setEnableTextRefine(runtimeProfile.getResolvedEnableTextRefine());
|
resumeConfig.setEnableTextRefine(runtimeProfile.getResolvedEnableTextRefine());
|
||||||
resumeConfig.setSaveAudio(runtimeProfile.getResolvedSaveAudio());
|
resumeConfig.setSaveAudio(runtimeProfile.getResolvedSaveAudio());
|
||||||
resumeConfig.setHotwords(resolveRealtimeHotwords(runtimeProfile.getResolvedHotWords(), runtimeProfile.getResolvedHotWordGroupId()));
|
resumeConfig.setHotwords(resolveRealtimeHotwords(runtimeProfile.getResolvedHotWords(), runtimeProfile.getResolvedHotWordGroupId()));
|
||||||
|
resumeConfig.setHotWordGroupId(runtimeProfile.getResolvedHotWordGroupId());
|
||||||
return resumeConfig;
|
return resumeConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.imeeting.common.MeetingConstants;
|
import com.imeeting.common.MeetingConstants;
|
||||||
import com.imeeting.common.SysParamKeys;
|
import com.imeeting.common.SysParamKeys;
|
||||||
import com.imeeting.entity.biz.AiTask;
|
import com.imeeting.entity.biz.AiTask;
|
||||||
|
import com.imeeting.entity.biz.HotWordGroup;
|
||||||
import com.imeeting.entity.biz.Meeting;
|
import com.imeeting.entity.biz.Meeting;
|
||||||
import com.imeeting.entity.biz.MeetingTranscript;
|
import com.imeeting.entity.biz.MeetingTranscript;
|
||||||
import com.imeeting.event.MeetingCreatedEvent;
|
import com.imeeting.event.MeetingCreatedEvent;
|
||||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.AiTaskService;
|
||||||
|
import com.imeeting.service.biz.HotWordGroupService;
|
||||||
import com.imeeting.service.biz.MeetingPointsService;
|
import com.imeeting.service.biz.MeetingPointsService;
|
||||||
|
import com.imeeting.service.biz.RealtimeMeetingSessionStateService;
|
||||||
import com.imeeting.service.biz.MeetingSummaryFileService;
|
import com.imeeting.service.biz.MeetingSummaryFileService;
|
||||||
import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
||||||
import com.unisbase.entity.SysUser;
|
import com.unisbase.entity.SysUser;
|
||||||
|
|
@ -57,6 +60,8 @@ public class MeetingDomainSupport {
|
||||||
private final ApplicationEventPublisher eventPublisher;
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
private final MeetingSummaryFileService meetingSummaryFileService;
|
private final MeetingSummaryFileService meetingSummaryFileService;
|
||||||
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
||||||
|
private final HotWordGroupService hotWordGroupService;
|
||||||
|
private final RealtimeMeetingSessionStateService realtimeMeetingSessionStateService;
|
||||||
private final SysParamService sysParamService;
|
private final SysParamService sysParamService;
|
||||||
|
|
||||||
@Value("${unisbase.app.upload-path}")
|
@Value("${unisbase.app.upload-path}")
|
||||||
|
|
@ -441,9 +446,42 @@ public class MeetingDomainSupport {
|
||||||
vo.setSummaryContent(meetingSummaryFileService.loadSummaryContent(meeting));
|
vo.setSummaryContent(meetingSummaryFileService.loadSummaryContent(meeting));
|
||||||
vo.setAnalysis(meetingSummaryFileService.loadSummaryAnalysis(meeting));
|
vo.setAnalysis(meetingSummaryFileService.loadSummaryAnalysis(meeting));
|
||||||
vo.setLastUserPrompt(resolveLastSummaryUserPrompt(meeting));
|
vo.setLastUserPrompt(resolveLastSummaryUserPrompt(meeting));
|
||||||
|
fillEffectiveHotWordGroup(meeting, vo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillEffectiveHotWordGroup(Meeting meeting, com.imeeting.dto.biz.MeetingVO vo) {
|
||||||
|
Long hotWordGroupId = resolveEffectiveHotWordGroupId(meeting);
|
||||||
|
vo.setHotWordGroupId(hotWordGroupId);
|
||||||
|
if (hotWordGroupId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HotWordGroup hotWordGroup = hotWordGroupService.getById(hotWordGroupId);
|
||||||
|
if (hotWordGroup != null) {
|
||||||
|
vo.setHotWordGroupName(hotWordGroup.getGroupName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long resolveEffectiveHotWordGroupId(Meeting meeting) {
|
||||||
|
AiTask latestAsrTask = resolveLatestAsrTask(meeting);
|
||||||
|
Long asrHotWordGroupId = longValue(
|
||||||
|
latestAsrTask == null || latestAsrTask.getTaskConfig() == null
|
||||||
|
? null
|
||||||
|
: latestAsrTask.getTaskConfig().get("hotWordGroupId")
|
||||||
|
);
|
||||||
|
if (asrHotWordGroupId != null) {
|
||||||
|
return asrHotWordGroupId;
|
||||||
|
}
|
||||||
|
if (!MeetingConstants.TYPE_REALTIME.equalsIgnoreCase(meeting.getMeetingType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var sessionStatus = realtimeMeetingSessionStateService.getStatus(meeting.getId());
|
||||||
|
if (sessionStatus == null || sessionStatus.getResumeConfig() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sessionStatus.getResumeConfig().getHotWordGroupId();
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveLastSummaryUserPrompt(Meeting meeting) {
|
private String resolveLastSummaryUserPrompt(Meeting meeting) {
|
||||||
AiTask latestSummaryTask = resolveLatestSummaryTask(meeting);
|
AiTask latestSummaryTask = resolveLatestSummaryTask(meeting);
|
||||||
if (latestSummaryTask == null || latestSummaryTask.getTaskConfig() == null) {
|
if (latestSummaryTask == null || latestSummaryTask.getTaskConfig() == null) {
|
||||||
|
|
@ -477,6 +515,17 @@ public class MeetingDomainSupport {
|
||||||
return aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
return aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
.eq(AiTask::getMeetingId, meeting.getId())
|
.eq(AiTask::getMeetingId, meeting.getId())
|
||||||
.eq(AiTask::getTaskType, "SUMMARY")
|
.eq(AiTask::getTaskType, "SUMMARY")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AiTask resolveLatestAsrTask(Meeting meeting) {
|
||||||
|
if (meeting == null || meeting.getId() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meeting.getId())
|
||||||
|
.eq(AiTask::getTaskType, "ASR")
|
||||||
.orderByDesc(AiTask::getId)
|
.orderByDesc(AiTask::getId)
|
||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
}
|
}
|
||||||
|
|
@ -517,6 +566,17 @@ public class MeetingDomainSupport {
|
||||||
return normalized.isEmpty() ? null : normalized;
|
return normalized.isEmpty() ? null : normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long longValue(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Long.parseLong(String.valueOf(value).trim());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String normalizeSummaryDetailLevel(String summaryDetailLevel) {
|
private String normalizeSummaryDetailLevel(String summaryDetailLevel) {
|
||||||
if (summaryDetailLevel == null || summaryDetailLevel.isBlank()) {
|
if (summaryDetailLevel == null || summaryDetailLevel.isBlank()) {
|
||||||
return MeetingConstants.SUMMARY_DETAIL_STANDARD;
|
return MeetingConstants.SUMMARY_DETAIL_STANDARD;
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ export interface MeetingVO {
|
||||||
summaryDetailLevel?: SummaryDetailLevel;
|
summaryDetailLevel?: SummaryDetailLevel;
|
||||||
summaryModelId: number;
|
summaryModelId: number;
|
||||||
promptId?: number;
|
promptId?: number;
|
||||||
|
hotWordGroupId?: number;
|
||||||
|
hotWordGroupName?: string;
|
||||||
aiCatalogEnabled?: boolean;
|
aiCatalogEnabled?: boolean;
|
||||||
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
||||||
audioSaveMessage?: string;
|
audioSaveMessage?: string;
|
||||||
|
|
|
||||||
|
|
@ -1389,6 +1389,35 @@ const MeetingDetail: React.FC = () => {
|
||||||
const meetingId = meeting?.id ?? (id ? Number(id) : NaN);
|
const meetingId = meeting?.id ?? (id ? Number(id) : NaN);
|
||||||
return buildMeetingPreviewUrl(meetingShareBaseUrl, meetingId, previewAccessPassword);
|
return buildMeetingPreviewUrl(meetingShareBaseUrl, meetingId, previewAccessPassword);
|
||||||
}, [meetingShareBaseUrl, meeting?.id, id, previewAccessPassword]);
|
}, [meetingShareBaseUrl, meeting?.id, id, previewAccessPassword]);
|
||||||
|
const summaryModelDisplayName = useMemo(() => {
|
||||||
|
const matchedModel = llmModels.find((item) => item.id === meeting?.summaryModelId);
|
||||||
|
if (matchedModel?.modelName) {
|
||||||
|
return matchedModel.modelName;
|
||||||
|
}
|
||||||
|
if (meeting?.summaryModelId) {
|
||||||
|
return `模型 #${meeting.summaryModelId}`;
|
||||||
|
}
|
||||||
|
return '未配置';
|
||||||
|
}, [llmModels, meeting?.summaryModelId]);
|
||||||
|
const promptDisplayName = useMemo(() => {
|
||||||
|
const matchedPrompt = prompts.find((item) => item.id === meeting?.promptId);
|
||||||
|
if (matchedPrompt?.templateName) {
|
||||||
|
return matchedPrompt.templateName;
|
||||||
|
}
|
||||||
|
if (meeting?.promptId) {
|
||||||
|
return `模板 #${meeting.promptId}`;
|
||||||
|
}
|
||||||
|
return '未配置';
|
||||||
|
}, [meeting?.promptId, prompts]);
|
||||||
|
const hotWordGroupDisplayName = useMemo(() => {
|
||||||
|
if (meeting?.hotWordGroupName?.trim()) {
|
||||||
|
return meeting.hotWordGroupName.trim();
|
||||||
|
}
|
||||||
|
if (meeting?.hotWordGroupId) {
|
||||||
|
return `热词组 #${meeting.hotWordGroupId}`;
|
||||||
|
}
|
||||||
|
return '未使用';
|
||||||
|
}, [meeting?.hotWordGroupId, meeting?.hotWordGroupName]);
|
||||||
|
|
||||||
const isOwner = useMemo(() => {
|
const isOwner = useMemo(() => {
|
||||||
if (!meeting) return false;
|
if (!meeting) return false;
|
||||||
|
|
@ -2392,6 +2421,20 @@ const MeetingDetail: React.FC = () => {
|
||||||
</span>
|
</span>
|
||||||
<span>{meeting.participants || '未指定'}</span>
|
<span>{meeting.participants || '未指定'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="meeting-detail-config-row">
|
||||||
|
<span className="meeting-detail-config-item">
|
||||||
|
<Text type="secondary">总结模型</Text>
|
||||||
|
<strong>{summaryModelDisplayName}</strong>
|
||||||
|
</span>
|
||||||
|
<span className="meeting-detail-config-item">
|
||||||
|
<Text type="secondary">会议模板</Text>
|
||||||
|
<strong>{promptDisplayName}</strong>
|
||||||
|
</span>
|
||||||
|
<span className="meeting-detail-config-item">
|
||||||
|
<Text type="secondary">热词组</Text>
|
||||||
|
<strong>{hotWordGroupDisplayName}</strong>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -3053,6 +3096,29 @@ const MeetingDetail: React.FC = () => {
|
||||||
color: #6e7695;
|
color: #6e7695;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
.meeting-detail-config-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.meeting-detail-config-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(244, 246, 255, 0.92);
|
||||||
|
border: 1px solid rgba(227, 232, 247, 0.95);
|
||||||
|
color: #58627f;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.meeting-detail-config-item strong {
|
||||||
|
color: #313a5a;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
.meeting-detail-workspace {
|
.meeting-detail-workspace {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue