Merge branch 'dev_na' of http://111.10.202.195:3000/chenh/imeeting into dev_ymcg
# Conflicts: # frontend/src/pages/business/MeetingDetail.tsxdev_na
commit
4bc3f65a10
|
|
@ -75,6 +75,12 @@ public class MeetingVO {
|
|||
@Schema(description = "总结模板ID")
|
||||
private Long promptId;
|
||||
|
||||
@Schema(description = "最终生效热词组ID")
|
||||
private Long hotWordGroupId;
|
||||
|
||||
@Schema(description = "最终生效热词组名称")
|
||||
private String hotWordGroupName;
|
||||
|
||||
@Schema(description = "是否启用 AI 目录")
|
||||
private Boolean aiCatalogEnabled;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ public class RealtimeMeetingResumeConfig {
|
|||
private Boolean saveAudio;
|
||||
@Schema(description = "热词列表")
|
||||
private List<Map<String, Object>> hotwords;
|
||||
@Schema(description = "最终生效热词组ID")
|
||||
private Long hotWordGroupId;
|
||||
@Schema(description = "腾讯说话人上下文 ID")
|
||||
private String speakerContextId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1512,6 +1512,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
|||
resumeConfig.setEnableTextRefine(runtimeProfile.getResolvedEnableTextRefine());
|
||||
resumeConfig.setSaveAudio(runtimeProfile.getResolvedSaveAudio());
|
||||
resumeConfig.setHotwords(resolveRealtimeHotwords(runtimeProfile.getResolvedHotWords(), runtimeProfile.getResolvedHotWordGroupId()));
|
||||
resumeConfig.setHotWordGroupId(runtimeProfile.getResolvedHotWordGroupId());
|
||||
return resumeConfig;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||
import com.imeeting.common.MeetingConstants;
|
||||
import com.imeeting.common.SysParamKeys;
|
||||
import com.imeeting.entity.biz.AiTask;
|
||||
import com.imeeting.entity.biz.HotWordGroup;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.MeetingTranscript;
|
||||
import com.imeeting.event.MeetingCreatedEvent;
|
||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||
import com.imeeting.service.biz.AiTaskService;
|
||||
import com.imeeting.service.biz.HotWordGroupService;
|
||||
import com.imeeting.service.biz.MeetingPointsService;
|
||||
import com.imeeting.service.biz.RealtimeMeetingSessionStateService;
|
||||
import com.imeeting.service.biz.MeetingSummaryFileService;
|
||||
import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
||||
import com.unisbase.entity.SysUser;
|
||||
|
|
@ -57,6 +60,8 @@ public class MeetingDomainSupport {
|
|||
private final ApplicationEventPublisher eventPublisher;
|
||||
private final MeetingSummaryFileService meetingSummaryFileService;
|
||||
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
||||
private final HotWordGroupService hotWordGroupService;
|
||||
private final RealtimeMeetingSessionStateService realtimeMeetingSessionStateService;
|
||||
private final SysParamService sysParamService;
|
||||
|
||||
@Value("${unisbase.app.upload-path}")
|
||||
|
|
@ -441,9 +446,42 @@ public class MeetingDomainSupport {
|
|||
vo.setSummaryContent(meetingSummaryFileService.loadSummaryContent(meeting));
|
||||
vo.setAnalysis(meetingSummaryFileService.loadSummaryAnalysis(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) {
|
||||
AiTask latestSummaryTask = resolveLatestSummaryTask(meeting);
|
||||
if (latestSummaryTask == null || latestSummaryTask.getTaskConfig() == null) {
|
||||
|
|
@ -477,6 +515,17 @@ public class MeetingDomainSupport {
|
|||
return aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meeting.getId())
|
||||
.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)
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
|
@ -517,6 +566,17 @@ public class MeetingDomainSupport {
|
|||
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) {
|
||||
if (summaryDetailLevel == null || summaryDetailLevel.isBlank()) {
|
||||
return MeetingConstants.SUMMARY_DETAIL_STANDARD;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ export interface MeetingVO {
|
|||
summaryDetailLevel?: SummaryDetailLevel;
|
||||
summaryModelId: number;
|
||||
promptId?: number;
|
||||
hotWordGroupId?: number;
|
||||
hotWordGroupName?: string;
|
||||
aiCatalogEnabled?: boolean;
|
||||
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
||||
audioSaveMessage?: string;
|
||||
|
|
|
|||
|
|
@ -1390,6 +1390,35 @@ const MeetingDetail: React.FC = () => {
|
|||
const meetingId = meeting?.id ?? (id ? Number(id) : NaN);
|
||||
return buildMeetingPreviewUrl(meetingShareBaseUrl, meetingId, 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(() => {
|
||||
if (!meeting) return false;
|
||||
|
|
@ -2376,23 +2405,41 @@ const MeetingDetail: React.FC = () => {
|
|||
className="meeting-detail-section-card"
|
||||
contentClassName="meeting-detail-section-content"
|
||||
title={(
|
||||
<span className="meeting-detail-section-title">
|
||||
<FileTextOutlined />
|
||||
<span className="meeting-detail-title-text">{meeting.title}</span>
|
||||
{isOwner && (
|
||||
<button type="button" className="meeting-detail-title-edit" onClick={handleEditMeeting} aria-label="编辑会议信息">
|
||||
<EditOutlined />
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
description={(
|
||||
<span className="meeting-detail-meta-row">
|
||||
<span>
|
||||
<ClockCircleOutlined /> {dayjs(meeting.meetingTime).format('YYYY-MM-DD HH:mm')}
|
||||
</span>
|
||||
<span>{meeting.participants || '未指定'}</span>
|
||||
</span>
|
||||
<div className="meeting-detail-title-wrap">
|
||||
<div className="meeting-detail-title-icon">
|
||||
<FileTextOutlined />
|
||||
</div>
|
||||
<div className="meeting-detail-title-copy">
|
||||
<div className="meeting-detail-title-row">
|
||||
<span className="meeting-detail-title-text">{meeting.title}</span>
|
||||
{isOwner && (
|
||||
<button type="button" className="meeting-detail-title-edit" onClick={handleEditMeeting} aria-label="编辑会议信息">
|
||||
<EditOutlined />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="meeting-detail-meta-row">
|
||||
<span>
|
||||
<ClockCircleOutlined /> {dayjs(meeting.meetingTime).format('YYYY-MM-DD HH:mm')}
|
||||
</span>
|
||||
<span>{meeting.participants || '未指定'}</span>
|
||||
</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>
|
||||
)}
|
||||
extra={(
|
||||
<Space size={10} wrap>
|
||||
|
|
@ -3052,6 +3099,29 @@ const MeetingDetail: React.FC = () => {
|
|||
color: #6e7695;
|
||||
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 {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue