feat: 增强会议总结和积分管理功能
- 添加 `MeetingSummaryPromptAssembler` 依赖并更新相关方法 - 优化 `resolveHostName` 方法为 `resolveMeetingUserName`,并调整相关调用 - 更新 `updateMeetingBasic` 方法,支持更新摘要模型 ID 和提示 ID - 在 `retrySummary` 和 `retryChapter` 方法中添加对摘要模型 ID 和提示 ID 的处理 - 优化前端积分管理页面,新增个人账户余额画廊和统计卡片 - 调整积分管理页面的布局和样式,提升用户体验dev_na
parent
178b920581
commit
2e05a25e63
|
|
@ -6,6 +6,7 @@ import com.imeeting.common.MeetingConstants;
|
||||||
import com.imeeting.common.SysParamKeys;
|
import com.imeeting.common.SysParamKeys;
|
||||||
import com.imeeting.common.exception.ExistingOfflineMeetingException;
|
import com.imeeting.common.exception.ExistingOfflineMeetingException;
|
||||||
import com.imeeting.dto.android.AndroidAuthContext;
|
import com.imeeting.dto.android.AndroidAuthContext;
|
||||||
|
import com.imeeting.dto.android.AndroidOfflineMeetingCreateCommand;
|
||||||
import com.imeeting.dto.android.AndroidMeetingCreateResponse;
|
import com.imeeting.dto.android.AndroidMeetingCreateResponse;
|
||||||
import com.imeeting.dto.android.AndroidMeetingConfigVo;
|
import com.imeeting.dto.android.AndroidMeetingConfigVo;
|
||||||
import com.imeeting.dto.android.AndroidMeetingListItemVO;
|
import com.imeeting.dto.android.AndroidMeetingListItemVO;
|
||||||
|
|
@ -15,7 +16,6 @@ import com.imeeting.dto.android.AndroidUnifiedMeetingStatusRequest;
|
||||||
import com.imeeting.dto.android.AndroidUnifiedMeetingStatusResponse;
|
import com.imeeting.dto.android.AndroidUnifiedMeetingStatusResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAccessPasswordRequest;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingAttendeeResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewDataResponse;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
import com.imeeting.dto.android.legacy.LegacyMeetingPreviewResult;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
import com.imeeting.dto.android.legacy.LegacyMeetingProcessingStatusResponse;
|
||||||
|
|
@ -161,8 +161,7 @@ public class AndroidMeetingController {
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@Log(value = "新增Android会议", type = "Android会议管理")
|
@Log(value = "新增Android会议", type = "Android会议管理")
|
||||||
public ApiResponse<Object> create(HttpServletRequest request,
|
public ApiResponse<Object> create(HttpServletRequest request,
|
||||||
|
@RequestBody AndroidOfflineMeetingCreateCommand command) {
|
||||||
@RequestBody LegacyMeetingCreateRequest command) {
|
|
||||||
AndroidRequestLogHelper.logRequest(log, "Android会议", "创建离线会议接口", "request", command);
|
AndroidRequestLogHelper.logRequest(log, "Android会议", "创建离线会议接口", "request", command);
|
||||||
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||||||
resolvePublicDeviceTenantId(request, command, authContext);
|
resolvePublicDeviceTenantId(request, command, authContext);
|
||||||
|
|
@ -478,7 +477,7 @@ public class AndroidMeetingController {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resolvePublicDeviceTenantId(HttpServletRequest request,
|
private void resolvePublicDeviceTenantId(HttpServletRequest request,
|
||||||
LegacyMeetingCreateRequest command,
|
AndroidOfflineMeetingCreateCommand command,
|
||||||
AndroidAuthContext authContext) {
|
AndroidAuthContext authContext) {
|
||||||
if (command == null || command.getTenantId() != null || authContext == null || !authContext.isAnonymous()) {
|
if (command == null || command.getTenantId() != null || authContext == null || !authContext.isAnonymous()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -569,6 +569,7 @@ public class MeetingController {
|
||||||
LoginUser loginUser = currentLoginUser();
|
LoginUser loginUser = currentLoginUser();
|
||||||
Meeting meeting = meetingAccessService.requireMeeting(id);
|
Meeting meeting = meetingAccessService.requireMeeting(id);
|
||||||
meetingAccessService.assertCanEditMeeting(meeting, loginUser);
|
meetingAccessService.assertCanEditMeeting(meeting, loginUser);
|
||||||
|
assertPromptAvailable(command.getPromptId(), loginUser);
|
||||||
command.setMeetingId(id);
|
command.setMeetingId(id);
|
||||||
meetingCommandService.updateMeetingBasic(command);
|
meetingCommandService.updateMeetingBasic(command);
|
||||||
return ApiResponse.ok(true);
|
return ApiResponse.ok(true);
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,9 @@ public class MeetingPointsManagementController {
|
||||||
@GetMapping("/overview")
|
@GetMapping("/overview")
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
public ApiResponse<MeetingPointsOverviewVO> getOverview() {
|
public ApiResponse<MeetingPointsOverviewVO> getOverview() {
|
||||||
return ApiResponse.ok(meetingPointsQueryService.getOverview(currentLoginUser().getTenantId()));
|
LoginUser loginUser = currentLoginUser();
|
||||||
|
boolean isAdmin = Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()) || Boolean.TRUE.equals(loginUser.getIsTenantAdmin());
|
||||||
|
return ApiResponse.ok(meetingPointsQueryService.getOverview(loginUser.getTenantId(), loginUser.getUserId(), isAdmin));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "分页查询积分消耗流水")
|
@Operation(summary = "分页查询积分消耗流水")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.imeeting.dto.android;
|
||||||
|
|
||||||
|
import com.imeeting.common.MeetingConstants;
|
||||||
|
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Schema(description = "Android离线会议创建请求")
|
||||||
|
public class AndroidOfflineMeetingCreateCommand extends LegacyMeetingCreateRequest {
|
||||||
|
|
||||||
|
@Schema(description = "总结模型ID")
|
||||||
|
private Long summaryModelId;
|
||||||
|
|
||||||
|
@Schema(description = "总结模板ID")
|
||||||
|
private Long promptId;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "总结详细程度:DETAILED=详细,STANDARD=标准,BRIEF=简洁",
|
||||||
|
allowableValues = {
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_DETAILED,
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_STANDARD,
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_BRIEF
|
||||||
|
}
|
||||||
|
)
|
||||||
|
private String summaryDetailLevel;
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@ package com.imeeting.dto.biz;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "积分管理总览视图")
|
@Schema(description = "积分管理总览视图")
|
||||||
public class MeetingPointsOverviewVO {
|
public class MeetingPointsOverviewVO {
|
||||||
|
|
@ -18,10 +20,10 @@ public class MeetingPointsOverviewVO {
|
||||||
@Schema(description = "公共账户累计消耗积分")
|
@Schema(description = "公共账户累计消耗积分")
|
||||||
private Long publicTotalPointsUsed;
|
private Long publicTotalPointsUsed;
|
||||||
|
|
||||||
@Schema(description = "个人账户余额汇总")
|
@Schema(description = "个人账户余额")
|
||||||
private Long personalBalance;
|
private Long personalBalance;
|
||||||
|
|
||||||
@Schema(description = "个人账户累计消耗积分汇总")
|
@Schema(description = "个人账户累计消耗积分")
|
||||||
private Long personalTotalPointsUsed;
|
private Long personalTotalPointsUsed;
|
||||||
|
|
||||||
@Schema(description = "当前模式下可用总积分")
|
@Schema(description = "当前模式下可用总积分")
|
||||||
|
|
@ -29,4 +31,10 @@ public class MeetingPointsOverviewVO {
|
||||||
|
|
||||||
@Schema(description = "累计消耗次数")
|
@Schema(description = "累计消耗次数")
|
||||||
private Long totalChargeCount;
|
private Long totalChargeCount;
|
||||||
|
|
||||||
|
@Schema(description = "当前用户是否管理员")
|
||||||
|
private Boolean admin;
|
||||||
|
|
||||||
|
@Schema(description = "管理员可见的个人账户列表")
|
||||||
|
private List<MeetingPointsPersonalAccountVO> personalAccounts;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.imeeting.dto.biz;
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -12,16 +13,22 @@ import java.util.Map;
|
||||||
public class MeetingVO {
|
public class MeetingVO {
|
||||||
@Schema(description = "会议 ID")
|
@Schema(description = "会议 ID")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Schema(description = "租户 ID")
|
@Schema(description = "租户 ID")
|
||||||
private Long tenantId;
|
private Long tenantId;
|
||||||
@Schema(description = "创建人用户 ID")
|
|
||||||
|
@Schema(description = "创建人用户ID")
|
||||||
private Long creatorId;
|
private Long creatorId;
|
||||||
|
|
||||||
@Schema(description = "创建人名称")
|
@Schema(description = "创建人名称")
|
||||||
private String creatorName;
|
private String creatorName;
|
||||||
@Schema(description = "主持人用户 ID")
|
|
||||||
|
@Schema(description = "主持人用户ID")
|
||||||
private Long hostUserId;
|
private Long hostUserId;
|
||||||
|
|
||||||
@Schema(description = "主持人名称")
|
@Schema(description = "主持人名称")
|
||||||
private String hostName;
|
private String hostName;
|
||||||
|
|
||||||
@Schema(description = "会议标题")
|
@Schema(description = "会议标题")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
|
|
@ -29,58 +36,90 @@ public class MeetingVO {
|
||||||
@Schema(description = "会议时间")
|
@Schema(description = "会议时间")
|
||||||
private LocalDateTime meetingTime;
|
private LocalDateTime meetingTime;
|
||||||
|
|
||||||
@Schema(description = "参会人 ID 串,逗号分隔")
|
@Schema(description = "参会人ID串,逗号分隔")
|
||||||
private String participants;
|
private String participants;
|
||||||
@Schema(description = "参会人 ID 列表")
|
|
||||||
|
@Schema(description = "参会人ID列表")
|
||||||
private List<Long> participantIds;
|
private List<Long> participantIds;
|
||||||
|
|
||||||
@Schema(description = "标签串")
|
@Schema(description = "标签串")
|
||||||
private String tags;
|
private String tags;
|
||||||
|
|
||||||
@Schema(description = "音频地址")
|
@Schema(description = "音频地址")
|
||||||
private String audioUrl;
|
private String audioUrl;
|
||||||
|
|
||||||
@Schema(description = "浏览器播放音频地址")
|
@Schema(description = "浏览器播放音频地址")
|
||||||
private String playbackAudioUrl;
|
private String playbackAudioUrl;
|
||||||
|
|
||||||
@Schema(description = "会议类型")
|
@Schema(description = "会议类型")
|
||||||
private String meetingType;
|
private String meetingType;
|
||||||
|
|
||||||
@Schema(description = "会议来源")
|
@Schema(description = "会议来源")
|
||||||
private String meetingSource;
|
private String meetingSource;
|
||||||
|
|
||||||
@Schema(description = "来源设备编码")
|
@Schema(description = "来源设备编码")
|
||||||
private String sourceDeviceCode;
|
private String sourceDeviceCode;
|
||||||
|
|
||||||
@Schema(description = "来源设备模式")
|
@Schema(description = "来源设备模式")
|
||||||
private String sourceDeviceMode;
|
private String sourceDeviceMode;
|
||||||
|
|
||||||
@Schema(description = "离线录音阶段:ACTIVE / PRE_END / UPLOAD_FINISHED")
|
@Schema(description = "离线录音阶段:ACTIVE / PRE_END / UPLOAD_FINISHED")
|
||||||
private String offlineRecordingStatus;
|
private String offlineRecordingStatus;
|
||||||
|
|
||||||
@Schema(description = "总结详细程度")
|
@Schema(description = "总结详细程度")
|
||||||
private String summaryDetailLevel;
|
private String summaryDetailLevel;
|
||||||
|
|
||||||
|
@Schema(description = "总结模型ID")
|
||||||
|
private Long summaryModelId;
|
||||||
|
|
||||||
|
@Schema(description = "总结模板ID")
|
||||||
|
private Long promptId;
|
||||||
|
|
||||||
@Schema(description = "音频保存状态")
|
@Schema(description = "音频保存状态")
|
||||||
private String audioSaveStatus;
|
private String audioSaveStatus;
|
||||||
|
|
||||||
@Schema(description = "音频保存说明")
|
@Schema(description = "音频保存说明")
|
||||||
private String audioSaveMessage;
|
private String audioSaveMessage;
|
||||||
|
|
||||||
@Schema(description = "访问密码")
|
@Schema(description = "访问密码")
|
||||||
private String accessPassword;
|
private String accessPassword;
|
||||||
|
|
||||||
@Schema(description = "音频时长,单位秒")
|
@Schema(description = "音频时长,单位秒")
|
||||||
private Integer duration;
|
private Integer duration;
|
||||||
|
|
||||||
@Schema(description = "会议最终有效录音时长,单位秒")
|
@Schema(description = "会议最终有效录音时长,单位秒")
|
||||||
private Integer effectiveAudioDurationSeconds;
|
private Integer effectiveAudioDurationSeconds;
|
||||||
|
|
||||||
@Schema(description = "会议摘要内容")
|
@Schema(description = "会议摘要内容")
|
||||||
private String summaryContent;
|
private String summaryContent;
|
||||||
|
|
||||||
@Schema(description = "最后一次用户补充提示词")
|
@Schema(description = "最后一次用户补充提示词")
|
||||||
private String lastUserPrompt;
|
private String lastUserPrompt;
|
||||||
|
|
||||||
@Schema(description = "分析结果")
|
@Schema(description = "分析结果")
|
||||||
private Map<String, Object> analysis;
|
private Map<String, Object> analysis;
|
||||||
|
|
||||||
@Schema(description = "最近一次总结尝试任务 ID")
|
@Schema(description = "最近一次总结尝试任务 ID")
|
||||||
private Long latestSummaryAttemptTaskId;
|
private Long latestSummaryAttemptTaskId;
|
||||||
|
|
||||||
@Schema(description = "最近一次总结尝试任务状态")
|
@Schema(description = "最近一次总结尝试任务状态")
|
||||||
private Integer latestSummaryAttemptStatus;
|
private Integer latestSummaryAttemptStatus;
|
||||||
|
|
||||||
@Schema(description = "最近一次总结尝试错误信息")
|
@Schema(description = "最近一次总结尝试错误信息")
|
||||||
private String latestSummaryAttemptErrorMsg;
|
private String latestSummaryAttemptErrorMsg;
|
||||||
|
|
||||||
@Schema(description = "最近一次总结尝试阻塞原因")
|
@Schema(description = "最近一次总结尝试阻塞原因")
|
||||||
private String latestSummaryAttemptBlockedReason;
|
private String latestSummaryAttemptBlockedReason;
|
||||||
|
|
||||||
@Schema(description = "最近一次章节尝试任务 ID")
|
@Schema(description = "最近一次章节尝试任务 ID")
|
||||||
private Long latestChapterAttemptTaskId;
|
private Long latestChapterAttemptTaskId;
|
||||||
|
|
||||||
@Schema(description = "最近一次章节尝试任务状态")
|
@Schema(description = "最近一次章节尝试任务状态")
|
||||||
private Integer latestChapterAttemptStatus;
|
private Integer latestChapterAttemptStatus;
|
||||||
|
|
||||||
@Schema(description = "最近一次章节尝试错误信息")
|
@Schema(description = "最近一次章节尝试错误信息")
|
||||||
private String latestChapterAttemptErrorMsg;
|
private String latestChapterAttemptErrorMsg;
|
||||||
|
|
||||||
@Schema(description = "会议状态")
|
@Schema(description = "会议状态")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.imeeting.dto.biz;
|
package com.imeeting.dto.biz;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.imeeting.common.MeetingConstants;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
@ -14,6 +16,17 @@ public class UpdateMeetingBasicCommand {
|
||||||
private LocalDateTime meetingTime;
|
private LocalDateTime meetingTime;
|
||||||
|
|
||||||
private String tags;
|
private String tags;
|
||||||
|
|
||||||
private String accessPassword;
|
private String accessPassword;
|
||||||
|
private Long promptId;
|
||||||
|
private Long summaryModelId;
|
||||||
|
|
||||||
|
@Schema(
|
||||||
|
description = "总结详细程度:DETAILED=详细,STANDARD=标准,BRIEF=简洁",
|
||||||
|
allowableValues = {
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_DETAILED,
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_STANDARD,
|
||||||
|
MeetingConstants.SUMMARY_DETAIL_BRIEF
|
||||||
|
}
|
||||||
|
)
|
||||||
|
private String summaryDetailLevel;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ public class Meeting extends BaseEntity {
|
||||||
@Schema(description = "总结详细程度")
|
@Schema(description = "总结详细程度")
|
||||||
private String summaryDetailLevel;
|
private String summaryDetailLevel;
|
||||||
|
|
||||||
|
@Schema(description = "总结模型ID")
|
||||||
|
private Long summaryModelId;
|
||||||
|
|
||||||
|
@Schema(description = "总结模板ID")
|
||||||
|
private Long promptId;
|
||||||
|
|
||||||
@Schema(description = "会议最终有效录音时长(秒)")
|
@Schema(description = "会议最终有效录音时长(秒)")
|
||||||
private Integer effectiveAudioDurationSeconds;
|
private Integer effectiveAudioDurationSeconds;
|
||||||
|
|
||||||
|
|
@ -63,7 +69,6 @@ public class Meeting extends BaseEntity {
|
||||||
private String audioSaveMessage;
|
private String audioSaveMessage;
|
||||||
|
|
||||||
@Schema(description = "访问密码")
|
@Schema(description = "访问密码")
|
||||||
|
|
||||||
private String accessPassword;
|
private String accessPassword;
|
||||||
|
|
||||||
@Schema(description = "创建人ID")
|
@Schema(description = "创建人ID")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.imeeting.common.MeetingConstants;
|
import com.imeeting.common.MeetingConstants;
|
||||||
import com.imeeting.common.exception.ExistingOfflineMeetingException;
|
import com.imeeting.common.exception.ExistingOfflineMeetingException;
|
||||||
import com.imeeting.dto.android.AndroidAuthContext;
|
import com.imeeting.dto.android.AndroidAuthContext;
|
||||||
|
import com.imeeting.dto.android.AndroidOfflineMeetingCreateCommand;
|
||||||
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
import com.imeeting.dto.android.legacy.LegacyMeetingCreateRequest;
|
||||||
import com.imeeting.dto.android.legacy.LegacyUploadAudioResponse;
|
import com.imeeting.dto.android.legacy.LegacyUploadAudioResponse;
|
||||||
import com.imeeting.dto.biz.MeetingVO;
|
import com.imeeting.dto.biz.MeetingVO;
|
||||||
|
|
@ -99,6 +100,31 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
throw new ExistingOfflineMeetingException(existingMeeting.getId());
|
throw new ExistingOfflineMeetingException(existingMeeting.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Long requestedSummaryModelId = request instanceof AndroidOfflineMeetingCreateCommand androidCommand
|
||||||
|
? androidCommand.getSummaryModelId()
|
||||||
|
: null;
|
||||||
|
Long requestedPromptId = request instanceof AndroidOfflineMeetingCreateCommand androidCommand
|
||||||
|
? androidCommand.getPromptId()
|
||||||
|
: null;
|
||||||
|
String requestedSummaryDetailLevel = request instanceof AndroidOfflineMeetingCreateCommand androidCommand
|
||||||
|
? androidCommand.getSummaryDetailLevel()
|
||||||
|
: MeetingConstants.SUMMARY_DETAIL_STANDARD;
|
||||||
|
RealtimeMeetingRuntimeProfile runtimeProfile = runtimeProfileResolver.resolve(
|
||||||
|
tenantId,
|
||||||
|
null,
|
||||||
|
requestedSummaryModelId,
|
||||||
|
requestedPromptId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
String resolvedCreatorName = meetingDomainSupport.resolveUserDisplayName(creatorUserId, creatorName);
|
||||||
Meeting meeting = meetingDomainSupport.initMeeting(
|
Meeting meeting = meetingDomainSupport.initMeeting(
|
||||||
request.getTitle().trim(),
|
request.getTitle().trim(),
|
||||||
meetingTime,
|
meetingTime,
|
||||||
|
|
@ -109,10 +135,12 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
MeetingConstants.SOURCE_ANDROID,
|
MeetingConstants.SOURCE_ANDROID,
|
||||||
tenantId,
|
tenantId,
|
||||||
creatorUserId,
|
creatorUserId,
|
||||||
creatorName,
|
resolvedCreatorName,
|
||||||
creatorUserId,
|
creatorUserId,
|
||||||
creatorName,
|
resolvedCreatorName,
|
||||||
MeetingConstants.SUMMARY_DETAIL_STANDARD,
|
runtimeProfile.getResolvedSummaryModelId(),
|
||||||
|
runtimeProfile.getResolvedPromptId(),
|
||||||
|
requestedSummaryDetailLevel,
|
||||||
0,
|
0,
|
||||||
authContext == null ? null : authContext.getDeviceId(),
|
authContext == null ? null : authContext.getDeviceId(),
|
||||||
sourceDeviceMode
|
sourceDeviceMode
|
||||||
|
|
@ -152,8 +180,12 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
if (transcriptCount > 0) {
|
if (transcriptCount > 0) {
|
||||||
throw new RuntimeException("当前会议已存在转录内容,旧版 Android 上传不支持替换已生成的转录");
|
throw new RuntimeException("当前会议已存在转录内容,旧版 Android 上传不支持替换已生成的转录");
|
||||||
}
|
}
|
||||||
if (promptId != null && !promptTemplateService.isTemplateEnabledForUser(
|
Long effectivePromptId = promptId != null ? promptId : meeting.getPromptId();
|
||||||
promptId,
|
Long effectiveSummaryModelId = modelCode != null && !modelCode.isBlank()
|
||||||
|
? resolveSummaryModelId(modelCode, loginUser.getTenantId())
|
||||||
|
: meeting.getSummaryModelId();
|
||||||
|
if (effectivePromptId != null && !promptTemplateService.isTemplateEnabledForUser(
|
||||||
|
effectivePromptId,
|
||||||
loginUser.getTenantId(),
|
loginUser.getTenantId(),
|
||||||
loginUser.getUserId(),
|
loginUser.getUserId(),
|
||||||
loginUser.getIsPlatformAdmin(),
|
loginUser.getIsPlatformAdmin(),
|
||||||
|
|
@ -164,8 +196,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
RealtimeMeetingRuntimeProfile profile = runtimeProfileResolver.resolve(
|
RealtimeMeetingRuntimeProfile profile = runtimeProfileResolver.resolve(
|
||||||
loginUser.getTenantId(),
|
loginUser.getTenantId(),
|
||||||
null,
|
null,
|
||||||
resolveSummaryModelId(modelCode, loginUser.getTenantId()),
|
effectiveSummaryModelId,
|
||||||
promptId,
|
effectivePromptId,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
@ -181,6 +213,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
||||||
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
||||||
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
||||||
|
meeting.setSummaryModelId(profile.getResolvedSummaryModelId());
|
||||||
|
meeting.setPromptId(profile.getResolvedPromptId());
|
||||||
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
||||||
meeting.setAudioSaveMessage(null);
|
meeting.setAudioSaveMessage(null);
|
||||||
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
||||||
|
|
@ -188,8 +222,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
|
|
||||||
resetOrCreateAsrTask(meetingId, profile);
|
resetOrCreateAsrTask(meetingId, profile);
|
||||||
resetOrCreateChapterTask(meetingId, profile);
|
resetOrCreateChapterTask(meetingId, profile, null, meeting.getSummaryDetailLevel());
|
||||||
resetOrCreateSummaryTask(meetingId, profile);
|
resetOrCreateSummaryTask(meetingId, profile, null, meeting.getSummaryDetailLevel());
|
||||||
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
});
|
});
|
||||||
return new LegacyUploadAudioResponse(meetingId, relocatedUrl);
|
return new LegacyUploadAudioResponse(meetingId, relocatedUrl);
|
||||||
|
|
@ -223,8 +257,12 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
throw new RuntimeException("当前会议已存在转录内容,不支持替换已生成的转录");
|
throw new RuntimeException("当前会议已存在转录内容,不支持替换已生成的转录");
|
||||||
}
|
}
|
||||||
LoginUser loginUser = toMeetingOwnerLoginUser(meeting);
|
LoginUser loginUser = toMeetingOwnerLoginUser(meeting);
|
||||||
if (promptId != null && !promptTemplateService.isTemplateEnabledForUser(
|
Long effectivePromptId = promptId != null ? promptId : meeting.getPromptId();
|
||||||
promptId,
|
Long effectiveSummaryModelId = modelCode != null && !modelCode.isBlank()
|
||||||
|
? resolveSummaryModelId(modelCode, meeting.getTenantId())
|
||||||
|
: meeting.getSummaryModelId();
|
||||||
|
if (effectivePromptId != null && !promptTemplateService.isTemplateEnabledForUser(
|
||||||
|
effectivePromptId,
|
||||||
loginUser.getTenantId(),
|
loginUser.getTenantId(),
|
||||||
loginUser.getUserId(),
|
loginUser.getUserId(),
|
||||||
Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()),
|
Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()),
|
||||||
|
|
@ -234,8 +272,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
RealtimeMeetingRuntimeProfile profile = runtimeProfileResolver.resolve(
|
RealtimeMeetingRuntimeProfile profile = runtimeProfileResolver.resolve(
|
||||||
meeting.getTenantId(),
|
meeting.getTenantId(),
|
||||||
null,
|
null,
|
||||||
resolveSummaryModelId(modelCode, meeting.getTenantId()),
|
effectiveSummaryModelId,
|
||||||
promptId,
|
effectivePromptId,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
|
@ -250,6 +288,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
String relocatedUrl = meetingDomainSupport.relocateAudioUrl(meetingId, stagingUrl);
|
||||||
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
taskSecurityContextRunner.runAsTenantUser(meeting.getTenantId(), meeting.getCreatorId(), () -> {
|
||||||
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
meetingDomainSupport.applyMeetingAudioMetadata(meeting, relocatedUrl);
|
||||||
|
meeting.setSummaryModelId(profile.getResolvedSummaryModelId());
|
||||||
|
meeting.setPromptId(profile.getResolvedPromptId());
|
||||||
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_SUCCESS);
|
||||||
meeting.setAudioSaveMessage(null);
|
meeting.setAudioSaveMessage(null);
|
||||||
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
meeting.setOfflineRecordingStatus(MeetingConstants.OFFLINE_RECORDING_UPLOAD_FINISHED);
|
||||||
|
|
@ -257,8 +297,8 @@ public class LegacyMeetingAdapterServiceImpl implements LegacyMeetingAdapterServ
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
|
|
||||||
resetOrCreateAsrTask(meetingId, profile);
|
resetOrCreateAsrTask(meetingId, profile);
|
||||||
resetOrCreateChapterTask(meetingId, profile);
|
resetOrCreateChapterTask(meetingId, profile, null, meeting.getSummaryDetailLevel());
|
||||||
resetOrCreateSummaryTask(meetingId, profile);
|
resetOrCreateSummaryTask(meetingId, profile, null, meeting.getSummaryDetailLevel());
|
||||||
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
dispatchTasksAfterCommit(meetingId, meeting.getTenantId(), meeting.getCreatorId());
|
||||||
});
|
});
|
||||||
return new LegacyUploadAudioResponse(meetingId, relocatedUrl);
|
return new LegacyUploadAudioResponse(meetingId, relocatedUrl);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import com.unisbase.dto.PageResult;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface MeetingPointsQueryService {
|
public interface MeetingPointsQueryService {
|
||||||
MeetingPointsOverviewVO getOverview(Long tenantId);
|
MeetingPointsOverviewVO getOverview(Long tenantId, Long userId, boolean isAdmin);
|
||||||
|
|
||||||
PageResult<List<MeetingPointsLedgerListItemVO>> pageLedgers(Long tenantId,
|
PageResult<List<MeetingPointsLedgerListItemVO>> pageLedgers(Long tenantId,
|
||||||
Integer current,
|
Integer current,
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
private final RealtimeMeetingAudioStorageService realtimeMeetingAudioStorageService;
|
private final RealtimeMeetingAudioStorageService realtimeMeetingAudioStorageService;
|
||||||
private final MeetingProgressService meetingProgressService;
|
private final MeetingProgressService meetingProgressService;
|
||||||
private final MeetingPointsService meetingPointsService;
|
private final MeetingPointsService meetingPointsService;
|
||||||
|
private final MeetingSummaryPromptAssembler meetingSummaryPromptAssembler;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
private final MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger;
|
||||||
private final AndroidPushMessageService androidPushMessageService;
|
private final AndroidPushMessageService androidPushMessageService;
|
||||||
|
|
@ -105,6 +106,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
RealtimeMeetingAudioStorageService realtimeMeetingAudioStorageService,
|
RealtimeMeetingAudioStorageService realtimeMeetingAudioStorageService,
|
||||||
MeetingProgressService meetingProgressService,
|
MeetingProgressService meetingProgressService,
|
||||||
MeetingPointsService meetingPointsService,
|
MeetingPointsService meetingPointsService,
|
||||||
|
MeetingSummaryPromptAssembler meetingSummaryPromptAssembler,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
MeetingExternalSummaryWebhookTrigger meetingExternalSummaryWebhookTrigger,
|
||||||
AndroidPushMessageService androidPushMessageService,
|
AndroidPushMessageService androidPushMessageService,
|
||||||
|
|
@ -125,6 +127,7 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
this.realtimeMeetingAudioStorageService = realtimeMeetingAudioStorageService;
|
this.realtimeMeetingAudioStorageService = realtimeMeetingAudioStorageService;
|
||||||
this.meetingProgressService = meetingProgressService;
|
this.meetingProgressService = meetingProgressService;
|
||||||
this.meetingPointsService = meetingPointsService;
|
this.meetingPointsService = meetingPointsService;
|
||||||
|
this.meetingSummaryPromptAssembler = meetingSummaryPromptAssembler;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
this.meetingExternalSummaryWebhookTrigger = meetingExternalSummaryWebhookTrigger;
|
||||||
this.androidPushMessageService = androidPushMessageService;
|
this.androidPushMessageService = androidPushMessageService;
|
||||||
|
|
@ -138,10 +141,12 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
public MeetingVO createMeeting(CreateMeetingCommand command, Long tenantId, Long creatorId, String creatorName, String meetingSource) {
|
public MeetingVO createMeeting(CreateMeetingCommand command, Long tenantId, Long creatorId, String creatorName, String meetingSource) {
|
||||||
RealtimeMeetingRuntimeProfile runtimeProfile = resolveCreateProfile(command, tenantId);
|
RealtimeMeetingRuntimeProfile runtimeProfile = resolveCreateProfile(command, tenantId);
|
||||||
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
||||||
String hostName = resolveHostName(command.getHostName(), creatorName, creatorId, hostUserId);
|
String resolvedCreatorName = resolveMeetingUserName(creatorId, creatorName);
|
||||||
|
String hostName = resolveMeetingUserName(hostUserId, resolvedCreatorName);
|
||||||
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
||||||
Meeting meeting = meetingDomainSupport.initMeeting(command.getTitle(), command.getMeetingTime(), command.getParticipants(), command.getTags(),
|
Meeting meeting = meetingDomainSupport.initMeeting(command.getTitle(), command.getMeetingTime(), command.getParticipants(), command.getTags(),
|
||||||
command.getAudioUrl(), MeetingConstants.TYPE_OFFLINE, meetingSource, tenantId, creatorId, creatorName, hostUserId, hostName, summaryDetailLevel, 0);
|
command.getAudioUrl(), MeetingConstants.TYPE_OFFLINE, meetingSource, tenantId, creatorId, resolvedCreatorName,
|
||||||
|
hostUserId, hostName, runtimeProfile.getResolvedSummaryModelId(), runtimeProfile.getResolvedPromptId(), summaryDetailLevel, 0);
|
||||||
meetingService.save(meeting);
|
meetingService.save(meeting);
|
||||||
|
|
||||||
AiTask asrTask = new AiTask();
|
AiTask asrTask = new AiTask();
|
||||||
|
|
@ -215,10 +220,12 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
public MeetingVO createRealtimeMeeting(CreateRealtimeMeetingCommand command, Long tenantId, Long creatorId, String creatorName, String meetingSource) {
|
public MeetingVO createRealtimeMeeting(CreateRealtimeMeetingCommand command, Long tenantId, Long creatorId, String creatorName, String meetingSource) {
|
||||||
RealtimeMeetingRuntimeProfile runtimeProfile = resolveCreateProfile(command, tenantId);
|
RealtimeMeetingRuntimeProfile runtimeProfile = resolveCreateProfile(command, tenantId);
|
||||||
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
||||||
String hostName = resolveHostName(command.getHostName(), creatorName, creatorId, hostUserId);
|
String resolvedCreatorName = resolveMeetingUserName(creatorId, creatorName);
|
||||||
|
String hostName = resolveMeetingUserName(hostUserId, resolvedCreatorName);
|
||||||
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
||||||
Meeting meeting = meetingDomainSupport.initMeeting(command.getTitle(), command.getMeetingTime(), command.getParticipants(), command.getTags(),
|
Meeting meeting = meetingDomainSupport.initMeeting(command.getTitle(), command.getMeetingTime(), command.getParticipants(), command.getTags(),
|
||||||
null, MeetingConstants.TYPE_REALTIME, meetingSource, tenantId, creatorId, creatorName, hostUserId, hostName, summaryDetailLevel, 0);
|
null, MeetingConstants.TYPE_REALTIME, meetingSource, tenantId, creatorId, resolvedCreatorName,
|
||||||
|
hostUserId, hostName, runtimeProfile.getResolvedSummaryModelId(), runtimeProfile.getResolvedPromptId(), summaryDetailLevel, 0);
|
||||||
meetingService.save(meeting);
|
meetingService.save(meeting);
|
||||||
Long chapterModelId = command.getChapterModelId() != null ? command.getChapterModelId() : runtimeProfile.getResolvedSummaryModelId();
|
Long chapterModelId = command.getChapterModelId() != null ? command.getChapterModelId() : runtimeProfile.getResolvedSummaryModelId();
|
||||||
meetingDomainSupport.createChapterTask(
|
meetingDomainSupport.createChapterTask(
|
||||||
|
|
@ -278,7 +285,8 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
command.getHotWords()
|
command.getHotWords()
|
||||||
);
|
);
|
||||||
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
Long hostUserId = resolveHostUserId(command.getHostUserId(), creatorId);
|
||||||
String hostName = resolveHostName(command.getHostName(), creatorName, creatorId, hostUserId);
|
String resolvedCreatorName = resolveMeetingUserName(creatorId, creatorName);
|
||||||
|
String hostName = resolveMeetingUserName(hostUserId, resolvedCreatorName);
|
||||||
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
String summaryDetailLevel = resolveSummaryDetailLevel(command.getSummaryDetailLevel());
|
||||||
Meeting meeting = meetingDomainSupport.initMeeting(
|
Meeting meeting = meetingDomainSupport.initMeeting(
|
||||||
command.getTitle(),
|
command.getTitle(),
|
||||||
|
|
@ -290,9 +298,11 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
MeetingConstants.SOURCE_ANDROID,
|
MeetingConstants.SOURCE_ANDROID,
|
||||||
tenantId,
|
tenantId,
|
||||||
creatorId,
|
creatorId,
|
||||||
creatorName,
|
resolvedCreatorName,
|
||||||
hostUserId,
|
hostUserId,
|
||||||
hostName,
|
hostName,
|
||||||
|
runtimeProfile.getResolvedSummaryModelId(),
|
||||||
|
runtimeProfile.getResolvedPromptId(),
|
||||||
summaryDetailLevel,
|
summaryDetailLevel,
|
||||||
0,
|
0,
|
||||||
deviceCode,
|
deviceCode,
|
||||||
|
|
@ -691,12 +701,36 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updateMeetingBasic(UpdateMeetingBasicCommand command) {
|
public void updateMeetingBasic(UpdateMeetingBasicCommand command) {
|
||||||
|
if (command.getSummaryModelId() != null || command.getPromptId() != null) {
|
||||||
|
Meeting meeting = meetingService.getById(command.getMeetingId());
|
||||||
|
if (meeting == null) {
|
||||||
|
throw new RuntimeException("会议不存在");
|
||||||
|
}
|
||||||
|
meetingRuntimeProfileResolver.resolve(
|
||||||
|
meeting.getTenantId(),
|
||||||
|
null,
|
||||||
|
command.getSummaryModelId() != null ? command.getSummaryModelId() : meeting.getSummaryModelId(),
|
||||||
|
command.getPromptId() != null ? command.getPromptId() : meeting.getPromptId(),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of()
|
||||||
|
);
|
||||||
|
}
|
||||||
meetingService.update(new LambdaUpdateWrapper<Meeting>()
|
meetingService.update(new LambdaUpdateWrapper<Meeting>()
|
||||||
.eq(Meeting::getId, command.getMeetingId())
|
.eq(Meeting::getId, command.getMeetingId())
|
||||||
.set(command.getTitle() != null, Meeting::getTitle, command.getTitle())
|
.set(command.getTitle() != null, Meeting::getTitle, command.getTitle())
|
||||||
.set(command.getMeetingTime() != null, Meeting::getMeetingTime, command.getMeetingTime())
|
.set(command.getMeetingTime() != null, Meeting::getMeetingTime, command.getMeetingTime())
|
||||||
.set(command.getTags() != null, Meeting::getTags, command.getTags())
|
.set(command.getTags() != null, Meeting::getTags, command.getTags())
|
||||||
.set(command.getAccessPassword() != null, Meeting::getAccessPassword, normalizeAccessPassword(command.getAccessPassword())));
|
.set(command.getAccessPassword() != null, Meeting::getAccessPassword, normalizeAccessPassword(command.getAccessPassword()))
|
||||||
|
.set(command.getSummaryModelId() != null, Meeting::getSummaryModelId, command.getSummaryModelId())
|
||||||
|
.set(command.getPromptId() != null, Meeting::getPromptId, command.getPromptId())
|
||||||
|
.set(command.getSummaryDetailLevel() != null, Meeting::getSummaryDetailLevel, resolveSummaryDetailLevel(command.getSummaryDetailLevel())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeAccessPassword(String accessPassword) {
|
private String normalizeAccessPassword(String accessPassword) {
|
||||||
|
|
@ -997,15 +1031,33 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
throw new RuntimeException("会议不存在");
|
throw new RuntimeException("会议不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
String effectiveSummaryDetailLevel = resolveSummaryDetailLevel(summaryDetailLevel != null ? summaryDetailLevel : meeting.getSummaryDetailLevel());
|
AiTask latestSummaryTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
|
.eq(AiTask::getTaskType, "SUMMARY")
|
||||||
|
.orderByDesc(AiTask::getId)
|
||||||
|
.last("LIMIT 1"));
|
||||||
|
Long effectiveSummaryModelId = summaryModelId != null ? summaryModelId : resolveMeetingSummaryModelId(meeting, latestSummaryTask);
|
||||||
|
Long effectivePromptId = promptId != null ? promptId : resolveMeetingPromptId(meeting, latestSummaryTask);
|
||||||
|
Long effectiveChapterModelId = chapterModelId != null ? chapterModelId : resolveMeetingChapterModelId(meeting, latestSummaryTask, effectiveSummaryModelId);
|
||||||
|
String effectiveSummaryDetailLevel = resolveSummaryDetailLevel(summaryDetailLevel != null ? summaryDetailLevel : resolveMeetingSummaryDetailLevel(meeting, latestSummaryTask));
|
||||||
|
String effectiveUserPrompt = userPrompt != null ? userPrompt : resolveMeetingUserPrompt(latestSummaryTask);
|
||||||
|
if (effectiveSummaryModelId == null) {
|
||||||
|
throw new RuntimeException("缺少 summaryModelId,无法创建总结任务");
|
||||||
|
}
|
||||||
|
if (effectivePromptId == null) {
|
||||||
|
throw new RuntimeException("缺少 promptId,无法创建总结任务");
|
||||||
|
}
|
||||||
meetingDomainSupport.createSummaryTask(
|
meetingDomainSupport.createSummaryTask(
|
||||||
meetingId,
|
meetingId,
|
||||||
summaryModelId,
|
effectiveSummaryModelId,
|
||||||
promptId,
|
effectiveChapterModelId,
|
||||||
userPrompt,
|
effectivePromptId,
|
||||||
|
effectiveUserPrompt,
|
||||||
effectiveSummaryDetailLevel,
|
effectiveSummaryDetailLevel,
|
||||||
"RESUMMARY"
|
"RESUMMARY"
|
||||||
);
|
);
|
||||||
|
meeting.setSummaryModelId(effectiveSummaryModelId);
|
||||||
|
meeting.setPromptId(effectivePromptId);
|
||||||
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
|
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
|
||||||
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
|
|
@ -1057,31 +1109,48 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
|
|
||||||
resetAiTask(asrTask, new HashMap<>(asrTask.getTaskConfig()));
|
resetAiTask(asrTask, new HashMap<>(asrTask.getTaskConfig()));
|
||||||
aiTaskService.updateById(asrTask);
|
aiTaskService.updateById(asrTask);
|
||||||
|
Long effectiveSummaryModelId = resolveMeetingSummaryModelId(meeting, summaryTask);
|
||||||
|
Long effectivePromptId = resolveMeetingPromptId(meeting, summaryTask);
|
||||||
|
Long effectiveChapterModelId = resolveMeetingChapterModelId(meeting, summaryTask, effectiveSummaryModelId);
|
||||||
|
String effectiveUserPrompt = resolveMeetingUserPrompt(summaryTask);
|
||||||
|
String effectiveSummaryDetailLevel = resolveMeetingSummaryDetailLevel(meeting, summaryTask);
|
||||||
AiTask chapterTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
AiTask chapterTask = aiTaskService.getOne(new LambdaQueryWrapper<AiTask>()
|
||||||
.eq(AiTask::getMeetingId, meetingId)
|
.eq(AiTask::getMeetingId, meetingId)
|
||||||
.eq(AiTask::getTaskType, "CHAPTER")
|
.eq(AiTask::getTaskType, "CHAPTER")
|
||||||
.orderByDesc(AiTask::getId)
|
.orderByDesc(AiTask::getId)
|
||||||
.last("LIMIT 1"));
|
.last("LIMIT 1"));
|
||||||
if (chapterTask == null) {
|
if (chapterTask == null) {
|
||||||
Long summaryModelId = longValue(summaryTask.getTaskConfig().get("summaryModelId"));
|
|
||||||
Long chapterModelId = longValue(summaryTask.getTaskConfig().get("chapterModelId"));
|
|
||||||
Long promptId = longValue(summaryTask.getTaskConfig().get("promptId"));
|
|
||||||
String userPrompt = stringValue(summaryTask.getTaskConfig().get("userPrompt"));
|
|
||||||
chapterTask = meetingDomainSupport.createChapterTask(
|
chapterTask = meetingDomainSupport.createChapterTask(
|
||||||
meetingId,
|
meetingId,
|
||||||
summaryModelId,
|
effectiveSummaryModelId,
|
||||||
chapterModelId != null ? chapterModelId : summaryModelId,
|
effectiveChapterModelId,
|
||||||
promptId,
|
effectivePromptId,
|
||||||
userPrompt,
|
effectiveUserPrompt,
|
||||||
meeting.getSummaryDetailLevel()
|
effectiveSummaryDetailLevel
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig()));
|
resetAiTask(chapterTask, meetingSummaryPromptAssembler.buildTaskConfig(
|
||||||
|
effectiveSummaryModelId,
|
||||||
|
effectiveChapterModelId,
|
||||||
|
effectivePromptId,
|
||||||
|
effectiveUserPrompt,
|
||||||
|
effectiveSummaryDetailLevel
|
||||||
|
));
|
||||||
aiTaskService.updateById(chapterTask);
|
aiTaskService.updateById(chapterTask);
|
||||||
}
|
}
|
||||||
resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig()));
|
resetAiTask(summaryTask, buildSummaryTaskConfigForRetry(
|
||||||
|
summaryTask,
|
||||||
|
effectiveSummaryModelId,
|
||||||
|
effectiveChapterModelId,
|
||||||
|
effectivePromptId,
|
||||||
|
effectiveUserPrompt,
|
||||||
|
effectiveSummaryDetailLevel
|
||||||
|
));
|
||||||
aiTaskService.updateById(summaryTask);
|
aiTaskService.updateById(summaryTask);
|
||||||
|
|
||||||
|
meeting.setSummaryModelId(effectiveSummaryModelId);
|
||||||
|
meeting.setPromptId(effectivePromptId);
|
||||||
|
meeting.setSummaryDetailLevel(effectiveSummaryDetailLevel);
|
||||||
meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode());
|
meeting.setStatus(MeetingStatusEnum.TRANSCRIBING.getCode());
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
clearLegacyDispatchState(meetingId);
|
clearLegacyDispatchState(meetingId);
|
||||||
|
|
@ -1115,8 +1184,19 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
if (!Integer.valueOf(3).equals(summaryTask.getStatus())) {
|
if (!Integer.valueOf(3).equals(summaryTask.getStatus())) {
|
||||||
throw new RuntimeException("当前总结环节未失败,无需重试");
|
throw new RuntimeException("当前总结环节未失败,无需重试");
|
||||||
}
|
}
|
||||||
resetAiTask(summaryTask, new HashMap<>(summaryTask.getTaskConfig()));
|
Long effectiveSummaryModelId = resolveMeetingSummaryModelId(meeting, summaryTask);
|
||||||
|
resetAiTask(summaryTask, buildSummaryTaskConfigForRetry(
|
||||||
|
summaryTask,
|
||||||
|
effectiveSummaryModelId,
|
||||||
|
resolveMeetingChapterModelId(meeting, summaryTask, effectiveSummaryModelId),
|
||||||
|
resolveMeetingPromptId(meeting, summaryTask),
|
||||||
|
resolveMeetingUserPrompt(summaryTask),
|
||||||
|
resolveMeetingSummaryDetailLevel(meeting, summaryTask)
|
||||||
|
));
|
||||||
aiTaskService.updateById(summaryTask);
|
aiTaskService.updateById(summaryTask);
|
||||||
|
meeting.setSummaryModelId(effectiveSummaryModelId);
|
||||||
|
meeting.setPromptId(resolveMeetingPromptId(meeting, summaryTask));
|
||||||
|
meeting.setSummaryDetailLevel(resolveMeetingSummaryDetailLevel(meeting, summaryTask));
|
||||||
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0);
|
updateMeetingProgress(meetingId, 90, "已重新提交总结任务,正在生成总结...", 0);
|
||||||
|
|
@ -1149,8 +1229,18 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
if (!Integer.valueOf(3).equals(chapterTask.getStatus())) {
|
if (!Integer.valueOf(3).equals(chapterTask.getStatus())) {
|
||||||
throw new RuntimeException("当前 AI 目录环节未失败,无需重试");
|
throw new RuntimeException("当前 AI 目录环节未失败,无需重试");
|
||||||
}
|
}
|
||||||
resetAiTask(chapterTask, new HashMap<>(chapterTask.getTaskConfig()));
|
Long effectiveSummaryModelId = resolveMeetingSummaryModelId(meeting, chapterTask);
|
||||||
|
resetAiTask(chapterTask, meetingSummaryPromptAssembler.buildTaskConfig(
|
||||||
|
effectiveSummaryModelId,
|
||||||
|
resolveMeetingChapterModelId(meeting, chapterTask, effectiveSummaryModelId),
|
||||||
|
resolveMeetingPromptId(meeting, chapterTask),
|
||||||
|
resolveMeetingUserPrompt(chapterTask),
|
||||||
|
resolveMeetingSummaryDetailLevel(meeting, chapterTask)
|
||||||
|
));
|
||||||
aiTaskService.updateById(chapterTask);
|
aiTaskService.updateById(chapterTask);
|
||||||
|
meeting.setSummaryModelId(effectiveSummaryModelId);
|
||||||
|
meeting.setPromptId(resolveMeetingPromptId(meeting, chapterTask));
|
||||||
|
meeting.setSummaryDetailLevel(resolveMeetingSummaryDetailLevel(meeting, chapterTask));
|
||||||
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
meeting.setStatus(MeetingStatusEnum.SUMMARIZING.getCode());
|
||||||
meetingService.updateById(meeting);
|
meetingService.updateById(meeting);
|
||||||
updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0);
|
updateMeetingProgress(meetingId, 85, "已重新提交 AI 目录任务,正在生成目录...", 0);
|
||||||
|
|
@ -1222,6 +1312,58 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
return value == null ? null : String.valueOf(value);
|
return value == null ? null : String.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long resolveMeetingSummaryModelId(Meeting meeting, AiTask task) {
|
||||||
|
if (meeting != null && meeting.getSummaryModelId() != null) {
|
||||||
|
return meeting.getSummaryModelId();
|
||||||
|
}
|
||||||
|
return task == null || task.getTaskConfig() == null ? null : longValue(task.getTaskConfig().get("summaryModelId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long resolveMeetingChapterModelId(Meeting meeting, AiTask task, Long fallbackSummaryModelId) {
|
||||||
|
Long chapterModelId = task == null || task.getTaskConfig() == null ? null : longValue(task.getTaskConfig().get("chapterModelId"));
|
||||||
|
return chapterModelId != null ? chapterModelId : fallbackSummaryModelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long resolveMeetingPromptId(Meeting meeting, AiTask task) {
|
||||||
|
if (meeting != null && meeting.getPromptId() != null) {
|
||||||
|
return meeting.getPromptId();
|
||||||
|
}
|
||||||
|
return task == null || task.getTaskConfig() == null ? null : longValue(task.getTaskConfig().get("promptId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMeetingSummaryDetailLevel(Meeting meeting, AiTask task) {
|
||||||
|
if (meeting != null && meeting.getSummaryDetailLevel() != null && !meeting.getSummaryDetailLevel().isBlank()) {
|
||||||
|
return resolveSummaryDetailLevel(meeting.getSummaryDetailLevel());
|
||||||
|
}
|
||||||
|
return resolveSummaryDetailLevel(task == null || task.getTaskConfig() == null ? null : stringValue(task.getTaskConfig().get("summaryDetailLevel")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMeetingUserPrompt(AiTask task) {
|
||||||
|
return task == null || task.getTaskConfig() == null ? null : stringValue(task.getTaskConfig().get("userPrompt"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildSummaryTaskConfigForRetry(AiTask summaryTask,
|
||||||
|
Long summaryModelId,
|
||||||
|
Long chapterModelId,
|
||||||
|
Long promptId,
|
||||||
|
String userPrompt,
|
||||||
|
String summaryDetailLevel) {
|
||||||
|
Map<String, Object> taskConfig = meetingSummaryPromptAssembler.buildTaskConfig(
|
||||||
|
summaryModelId,
|
||||||
|
chapterModelId,
|
||||||
|
promptId,
|
||||||
|
userPrompt,
|
||||||
|
summaryDetailLevel
|
||||||
|
);
|
||||||
|
String chargeTriggerType = summaryTask == null || summaryTask.getTaskConfig() == null
|
||||||
|
? null
|
||||||
|
: stringValue(summaryTask.getTaskConfig().get("chargeTriggerType"));
|
||||||
|
taskConfig.put("chargeTriggerType", chargeTriggerType == null || chargeTriggerType.isBlank()
|
||||||
|
? "AUTO_SUMMARY"
|
||||||
|
: chargeTriggerType.trim().toUpperCase());
|
||||||
|
return taskConfig;
|
||||||
|
}
|
||||||
|
|
||||||
private AiTask resolveOwnedTask(Long taskId, Long meetingId, String expectedType) {
|
private AiTask resolveOwnedTask(Long taskId, Long meetingId, String expectedType) {
|
||||||
if (taskId == null) {
|
if (taskId == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -1424,14 +1566,8 @@ public class MeetingCommandServiceImpl implements MeetingCommandService {
|
||||||
return requestedHostUserId != null ? requestedHostUserId : creatorId;
|
return requestedHostUserId != null ? requestedHostUserId : creatorId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveHostName(String requestedHostName, String creatorName, Long creatorId, Long hostUserId) {
|
private String resolveMeetingUserName(Long userId, String fallbackName) {
|
||||||
if (requestedHostName != null && !requestedHostName.isBlank()) {
|
return meetingDomainSupport.resolveUserDisplayName(userId, fallbackName);
|
||||||
return requestedHostName.trim();
|
|
||||||
}
|
|
||||||
if (hostUserId != null && Objects.equals(hostUserId, creatorId)) {
|
|
||||||
return creatorName;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveSummaryDetailLevel(String requestedSummaryDetailLevel) {
|
private String resolveSummaryDetailLevel(String requestedSummaryDetailLevel) {
|
||||||
|
|
|
||||||
|
|
@ -64,16 +64,20 @@ public class MeetingDomainSupport {
|
||||||
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
||||||
String audioUrl, String meetingType, String meetingSource,
|
String audioUrl, String meetingType, String meetingSource,
|
||||||
Long tenantId, Long creatorId, String creatorName,
|
Long tenantId, Long creatorId, String creatorName,
|
||||||
Long hostUserId, String hostName, String summaryDetailLevel, int status) {
|
Long hostUserId, String hostName,
|
||||||
|
Long summaryModelId, Long promptId,
|
||||||
|
String summaryDetailLevel, int status) {
|
||||||
return initMeeting(title, meetingTime, participants, tags, audioUrl, meetingType, meetingSource,
|
return initMeeting(title, meetingTime, participants, tags, audioUrl, meetingType, meetingSource,
|
||||||
tenantId, creatorId, creatorName, hostUserId, hostName, summaryDetailLevel, status,
|
tenantId, creatorId, creatorName, hostUserId, hostName, summaryModelId, promptId, summaryDetailLevel, status,
|
||||||
null, null);
|
null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
public Meeting initMeeting(String title, LocalDateTime meetingTime, String participants, String tags,
|
||||||
String audioUrl, String meetingType, String meetingSource,
|
String audioUrl, String meetingType, String meetingSource,
|
||||||
Long tenantId, Long creatorId, String creatorName,
|
Long tenantId, Long creatorId, String creatorName,
|
||||||
Long hostUserId, String hostName, String summaryDetailLevel, int status,
|
Long hostUserId, String hostName,
|
||||||
|
Long summaryModelId, Long promptId,
|
||||||
|
String summaryDetailLevel, int status,
|
||||||
String sourceDeviceCode, String sourceDeviceMode) {
|
String sourceDeviceCode, String sourceDeviceMode) {
|
||||||
Meeting meeting = new Meeting();
|
Meeting meeting = new Meeting();
|
||||||
meeting.setTitle(title);
|
meeting.setTitle(title);
|
||||||
|
|
@ -90,6 +94,8 @@ public class MeetingDomainSupport {
|
||||||
meeting.setAudioUrl(audioUrl);
|
meeting.setAudioUrl(audioUrl);
|
||||||
meeting.setSourceDeviceCode(sourceDeviceCode);
|
meeting.setSourceDeviceCode(sourceDeviceCode);
|
||||||
meeting.setSourceDeviceMode(sourceDeviceMode);
|
meeting.setSourceDeviceMode(sourceDeviceMode);
|
||||||
|
meeting.setSummaryModelId(summaryModelId);
|
||||||
|
meeting.setPromptId(promptId);
|
||||||
meeting.setSummaryDetailLevel(normalizeSummaryDetailLevel(summaryDetailLevel));
|
meeting.setSummaryDetailLevel(normalizeSummaryDetailLevel(summaryDetailLevel));
|
||||||
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_NONE);
|
meeting.setAudioSaveStatus(RealtimeMeetingAudioStorageService.STATUS_NONE);
|
||||||
if (MeetingConstants.TYPE_OFFLINE.equalsIgnoreCase(meetingType)) {
|
if (MeetingConstants.TYPE_OFFLINE.equalsIgnoreCase(meetingType)) {
|
||||||
|
|
@ -99,16 +105,21 @@ public class MeetingDomainSupport {
|
||||||
return meeting;
|
return meeting;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long promptId, String userPrompt) {
|
public String resolveUserDisplayName(Long userId, String fallbackName) {
|
||||||
return createSummaryTask(
|
if (userId == null) {
|
||||||
meetingId,
|
return fallbackName;
|
||||||
summaryModelId,
|
}
|
||||||
summaryModelId,
|
SysUser user = sysUserMapper.selectById(userId);
|
||||||
promptId,
|
if (user == null) {
|
||||||
userPrompt,
|
return fallbackName;
|
||||||
MeetingConstants.SUMMARY_DETAIL_STANDARD,
|
}
|
||||||
"AUTO_SUMMARY"
|
if (user.getDisplayName() != null && !user.getDisplayName().isBlank()) {
|
||||||
);
|
return user.getDisplayName().trim();
|
||||||
|
}
|
||||||
|
if (user.getUsername() != null && !user.getUsername().isBlank()) {
|
||||||
|
return user.getUsername().trim();
|
||||||
|
}
|
||||||
|
return fallbackName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long promptId,
|
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long promptId,
|
||||||
|
|
@ -137,16 +148,6 @@ public class MeetingDomainSupport {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiTask createChapterTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId, String userPrompt) {
|
|
||||||
return createChapterTask(
|
|
||||||
meetingId,
|
|
||||||
summaryModelId,
|
|
||||||
chapterModelId,
|
|
||||||
promptId,
|
|
||||||
userPrompt,
|
|
||||||
MeetingConstants.SUMMARY_DETAIL_STANDARD
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AiTask createChapterTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId,
|
public AiTask createChapterTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId,
|
||||||
String userPrompt, String summaryDetailLevel) {
|
String userPrompt, String summaryDetailLevel) {
|
||||||
|
|
@ -166,17 +167,7 @@ public class MeetingDomainSupport {
|
||||||
return chapterTask;
|
return chapterTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId, String userPrompt) {
|
|
||||||
return createSummaryTask(
|
|
||||||
meetingId,
|
|
||||||
summaryModelId,
|
|
||||||
chapterModelId,
|
|
||||||
promptId,
|
|
||||||
userPrompt,
|
|
||||||
MeetingConstants.SUMMARY_DETAIL_STANDARD,
|
|
||||||
"AUTO_SUMMARY"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId,
|
public AiTask createSummaryTask(Long meetingId, Long summaryModelId, Long chapterModelId, Long promptId,
|
||||||
String userPrompt, String summaryDetailLevel) {
|
String userPrompt, String summaryDetailLevel) {
|
||||||
|
|
@ -406,6 +397,8 @@ public class MeetingDomainSupport {
|
||||||
vo.setSourceDeviceCode(meeting.getSourceDeviceCode());
|
vo.setSourceDeviceCode(meeting.getSourceDeviceCode());
|
||||||
vo.setSourceDeviceMode(meeting.getSourceDeviceMode());
|
vo.setSourceDeviceMode(meeting.getSourceDeviceMode());
|
||||||
vo.setOfflineRecordingStatus(meeting.getOfflineRecordingStatus());
|
vo.setOfflineRecordingStatus(meeting.getOfflineRecordingStatus());
|
||||||
|
vo.setSummaryModelId(meeting.getSummaryModelId());
|
||||||
|
vo.setPromptId(meeting.getPromptId());
|
||||||
vo.setSummaryDetailLevel(normalizeSummaryDetailLevel(meeting.getSummaryDetailLevel()));
|
vo.setSummaryDetailLevel(normalizeSummaryDetailLevel(meeting.getSummaryDetailLevel()));
|
||||||
vo.setAudioSaveStatus(meeting.getAudioSaveStatus());
|
vo.setAudioSaveStatus(meeting.getAudioSaveStatus());
|
||||||
vo.setAudioSaveMessage(meeting.getAudioSaveMessage());
|
vo.setAudioSaveMessage(meeting.getAudioSaveMessage());
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.imeeting.dto.biz.MeetingPointsChargeItemVO;
|
||||||
import com.imeeting.dto.biz.MeetingPointsLedgerDetailVO;
|
import com.imeeting.dto.biz.MeetingPointsLedgerDetailVO;
|
||||||
import com.imeeting.dto.biz.MeetingPointsLedgerListItemVO;
|
import com.imeeting.dto.biz.MeetingPointsLedgerListItemVO;
|
||||||
import com.imeeting.dto.biz.MeetingPointsOverviewVO;
|
import com.imeeting.dto.biz.MeetingPointsOverviewVO;
|
||||||
|
import com.imeeting.dto.biz.MeetingPointsPersonalAccountVO;
|
||||||
import com.imeeting.entity.biz.Meeting;
|
import com.imeeting.entity.biz.Meeting;
|
||||||
import com.imeeting.entity.biz.MeetingPointsAccount;
|
import com.imeeting.entity.biz.MeetingPointsAccount;
|
||||||
import com.imeeting.entity.biz.MeetingPointsLedger;
|
import com.imeeting.entity.biz.MeetingPointsLedger;
|
||||||
|
|
@ -71,22 +72,28 @@ public class MeetingPointsQueryServiceImpl implements MeetingPointsQueryService
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MeetingPointsOverviewVO getOverview(Long tenantId) {
|
public MeetingPointsOverviewVO getOverview(Long tenantId, Long userId, boolean isAdmin) {
|
||||||
String accountMode = resolveAccountMode();
|
String accountMode = resolveAccountMode();
|
||||||
String chargePriority = resolveChargePriority();
|
String chargePriority = resolveChargePriority();
|
||||||
MeetingPointsAccount publicAccount = findAccount(tenantId, PUBLIC_ACCOUNT_USER_ID);
|
MeetingPointsAccount publicAccount = findAccount(tenantId, PUBLIC_ACCOUNT_USER_ID);
|
||||||
long publicBalance = publicAccount == null ? 0L : defaultLong(publicAccount.getCurrentBalance());
|
long publicBalance = publicAccount == null ? 0L : defaultLong(publicAccount.getCurrentBalance());
|
||||||
long publicTotalUsed = publicAccount == null ? 0L : defaultLong(publicAccount.getTotalPointsUsed());
|
long publicTotalUsed = publicAccount == null ? 0L : defaultLong(publicAccount.getTotalPointsUsed());
|
||||||
|
|
||||||
long personalBalance = 0L;
|
|
||||||
long personalTotalUsed = 0L;
|
|
||||||
List<MeetingPointsAccount> personalAccounts = meetingPointsAccountService.list(new LambdaQueryWrapper<MeetingPointsAccount>()
|
List<MeetingPointsAccount> personalAccounts = meetingPointsAccountService.list(new LambdaQueryWrapper<MeetingPointsAccount>()
|
||||||
.eq(MeetingPointsAccount::getTenantId, tenantId)
|
.eq(MeetingPointsAccount::getTenantId, tenantId)
|
||||||
.ne(MeetingPointsAccount::getUserId, PUBLIC_ACCOUNT_USER_ID));
|
.ne(MeetingPointsAccount::getUserId, PUBLIC_ACCOUNT_USER_ID));
|
||||||
|
long personalBalance = 0L;
|
||||||
|
long personalTotalUsed = 0L;
|
||||||
|
if (isAdmin) {
|
||||||
for (MeetingPointsAccount account : personalAccounts) {
|
for (MeetingPointsAccount account : personalAccounts) {
|
||||||
personalBalance += defaultLong(account.getCurrentBalance());
|
personalBalance += defaultLong(account.getCurrentBalance());
|
||||||
personalTotalUsed += defaultLong(account.getTotalPointsUsed());
|
personalTotalUsed += defaultLong(account.getTotalPointsUsed());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
MeetingPointsAccount currentUserAccount = findAccount(tenantId, userId);
|
||||||
|
personalBalance = currentUserAccount == null ? 0L : defaultLong(currentUserAccount.getCurrentBalance());
|
||||||
|
personalTotalUsed = currentUserAccount == null ? 0L : defaultLong(currentUserAccount.getTotalPointsUsed());
|
||||||
|
}
|
||||||
|
|
||||||
List<Long> scopedOwnerUserIds = resolveScopedOwnerUserIds(tenantId);
|
List<Long> scopedOwnerUserIds = resolveScopedOwnerUserIds(tenantId);
|
||||||
long totalChargeCount = 0L;
|
long totalChargeCount = 0L;
|
||||||
|
|
@ -110,6 +117,8 @@ public class MeetingPointsQueryServiceImpl implements MeetingPointsQueryService
|
||||||
vo.setPersonalTotalPointsUsed(personalTotalUsed);
|
vo.setPersonalTotalPointsUsed(personalTotalUsed);
|
||||||
vo.setTotalAvailableBalance(resolveVisibleTotalBalance(accountMode, publicBalance, personalBalance));
|
vo.setTotalAvailableBalance(resolveVisibleTotalBalance(accountMode, publicBalance, personalBalance));
|
||||||
vo.setTotalChargeCount(totalChargeCount);
|
vo.setTotalChargeCount(totalChargeCount);
|
||||||
|
vo.setAdmin(isAdmin);
|
||||||
|
vo.setPersonalAccounts(buildPersonalAccountOverview(accountMode, isAdmin, personalAccounts));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,6 +431,39 @@ public class MeetingPointsQueryServiceImpl implements MeetingPointsQueryService
|
||||||
return publicBalance + personalBalance;
|
return publicBalance + personalBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<MeetingPointsPersonalAccountVO> buildPersonalAccountOverview(String accountMode,
|
||||||
|
boolean isAdmin,
|
||||||
|
List<MeetingPointsAccount> personalAccounts) {
|
||||||
|
if (!isAdmin || (!ACCOUNT_MODE_PERSONAL.equals(accountMode) && !ACCOUNT_MODE_BOTH.equals(accountMode))) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
if (personalAccounts == null || personalAccounts.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<Long> userIds = personalAccounts.stream()
|
||||||
|
.map(MeetingPointsAccount::getUserId)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
Map<Long, SysUser> userMap = sysUserMapper.selectBatchIds(userIds).stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toMap(SysUser::getUserId, user -> user, (left, right) -> left, HashMap::new));
|
||||||
|
|
||||||
|
return personalAccounts.stream()
|
||||||
|
.sorted((left, right) -> Long.compare(defaultLong(right.getCurrentBalance()), defaultLong(left.getCurrentBalance())))
|
||||||
|
.map(account -> {
|
||||||
|
SysUser user = userMap.get(account.getUserId());
|
||||||
|
MeetingPointsPersonalAccountVO item = new MeetingPointsPersonalAccountVO();
|
||||||
|
item.setUserId(account.getUserId());
|
||||||
|
item.setUsername(user == null ? null : user.getUsername());
|
||||||
|
item.setDisplayName(resolveOwnerName(user, account.getUserId()));
|
||||||
|
item.setCurrentBalance(defaultLong(account.getCurrentBalance()));
|
||||||
|
item.setTotalPointsUsed(defaultLong(account.getTotalPointsUsed()));
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
private long defaultLong(Long value) {
|
private long defaultLong(Long value) {
|
||||||
return value == null ? 0L : value;
|
return value == null ? 0L : value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,21 @@ public class MeetingQueryServiceImpl implements MeetingQueryService {
|
||||||
throw new RuntimeException("缺少可用的总结任务配置");
|
throw new RuntimeException("缺少可用的总结任务配置");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long summaryModelId = firstLong(request == null ? null : request.getSummaryModelId(), latestSummaryTask.getTaskConfig().get("summaryModelId"));
|
Long summaryModelId = firstLong(
|
||||||
Long chapterModelId = firstLong(request == null ? null : request.getChapterModelId(), latestSummaryTask.getTaskConfig().get("chapterModelId"), summaryModelId);
|
request == null ? null : request.getSummaryModelId(),
|
||||||
Long promptId = firstLong(request == null ? null : request.getPromptId(), latestSummaryTask.getTaskConfig().get("promptId"));
|
meeting.getSummaryModelId(),
|
||||||
|
latestSummaryTask.getTaskConfig().get("summaryModelId")
|
||||||
|
);
|
||||||
|
Long chapterModelId = firstLong(
|
||||||
|
request == null ? null : request.getChapterModelId(),
|
||||||
|
latestSummaryTask.getTaskConfig().get("chapterModelId"),
|
||||||
|
summaryModelId
|
||||||
|
);
|
||||||
|
Long promptId = firstLong(
|
||||||
|
request == null ? null : request.getPromptId(),
|
||||||
|
meeting.getPromptId(),
|
||||||
|
latestSummaryTask.getTaskConfig().get("promptId")
|
||||||
|
);
|
||||||
String userPrompt = request != null && request.getUserPrompt() != null
|
String userPrompt = request != null && request.getUserPrompt() != null
|
||||||
? request.getUserPrompt()
|
? request.getUserPrompt()
|
||||||
: stringValue(latestSummaryTask.getTaskConfig().get("userPrompt"));
|
: stringValue(latestSummaryTask.getTaskConfig().get("userPrompt"));
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ export interface MeetingVO {
|
||||||
sourceDeviceCode?: string;
|
sourceDeviceCode?: string;
|
||||||
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
||||||
summaryDetailLevel?: SummaryDetailLevel;
|
summaryDetailLevel?: SummaryDetailLevel;
|
||||||
|
summaryModelId: number;
|
||||||
|
promptId?: number;
|
||||||
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
||||||
audioSaveMessage?: string;
|
audioSaveMessage?: string;
|
||||||
accessPassword?: string;
|
accessPassword?: string;
|
||||||
|
|
@ -150,6 +152,9 @@ export interface UpdateMeetingBasicCommand {
|
||||||
meetingTime?: string;
|
meetingTime?: string;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
accessPassword?: string | null;
|
accessPassword?: string | null;
|
||||||
|
summaryModelId: number;
|
||||||
|
promptId?: number;
|
||||||
|
summaryDetailLevel?: SummaryDetailLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MeetingUpdateBasicDTO = UpdateMeetingBasicCommand;
|
export type MeetingUpdateBasicDTO = UpdateMeetingBasicCommand;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,16 @@ export interface MeetingPointsOverviewVO {
|
||||||
personalTotalPointsUsed: number;
|
personalTotalPointsUsed: number;
|
||||||
totalAvailableBalance: number;
|
totalAvailableBalance: number;
|
||||||
totalChargeCount: number;
|
totalChargeCount: number;
|
||||||
|
admin?: boolean;
|
||||||
|
personalAccounts?: MeetingPointsPersonalAccountVO[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MeetingPointsPersonalAccountVO {
|
||||||
|
userId: number;
|
||||||
|
username?: string;
|
||||||
|
displayName?: string;
|
||||||
|
currentBalance: number;
|
||||||
|
totalPointsUsed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeetingPointsChargeItemVO {
|
export interface MeetingPointsChargeItemVO {
|
||||||
|
|
|
||||||
|
|
@ -1799,9 +1799,13 @@ const MeetingDetail: React.FC = () => {
|
||||||
summaryForm.setFieldsValue({
|
summaryForm.setFieldsValue({
|
||||||
summaryModelId:
|
summaryModelId:
|
||||||
summaryForm.getFieldValue('summaryModelId') ??
|
summaryForm.getFieldValue('summaryModelId') ??
|
||||||
|
meeting?.summaryModelId ??
|
||||||
llmModels.find((model) => model.isDefault === 1)?.id ??
|
llmModels.find((model) => model.isDefault === 1)?.id ??
|
||||||
llmModels[0]?.id,
|
llmModels[0]?.id,
|
||||||
promptId: summaryForm.getFieldValue('promptId') ?? prompts[0]?.id,
|
promptId:
|
||||||
|
summaryForm.getFieldValue('promptId') ??
|
||||||
|
meeting?.promptId ??
|
||||||
|
prompts[0]?.id,
|
||||||
userPrompt: meeting?.lastUserPrompt ?? '',
|
userPrompt: meeting?.lastUserPrompt ?? '',
|
||||||
summaryDetailLevel:
|
summaryDetailLevel:
|
||||||
summaryForm.getFieldValue('summaryDetailLevel') ??
|
summaryForm.getFieldValue('summaryDetailLevel') ??
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { EyeOutlined, PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons";
|
import { EyeOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, UserOutlined } from "@ant-design/icons";
|
||||||
import { listUsers } from "@/api";
|
import { listUsers } from "@/api";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Col,
|
Col,
|
||||||
Descriptions,
|
Descriptions,
|
||||||
|
Empty,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
|
|
@ -31,10 +32,11 @@ import {
|
||||||
type MeetingPointsLedgerDetailVO,
|
type MeetingPointsLedgerDetailVO,
|
||||||
type MeetingPointsLedgerListItemVO,
|
type MeetingPointsLedgerListItemVO,
|
||||||
type MeetingPointsOverviewVO,
|
type MeetingPointsOverviewVO,
|
||||||
|
type MeetingPointsPersonalAccountVO,
|
||||||
} from "@/api/business/meetingPoints";
|
} from "@/api/business/meetingPoints";
|
||||||
import type { SysUser } from "@/types";
|
import type { SysUser } from "@/types";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
const POINTS_TYPE_OPTIONS = [
|
const POINTS_TYPE_OPTIONS = [
|
||||||
{ label: "全部类型", value: "" },
|
{ label: "全部类型", value: "" },
|
||||||
|
|
@ -42,9 +44,13 @@ const POINTS_TYPE_OPTIONS = [
|
||||||
{ label: "总结", value: "LLM" },
|
{ label: "总结", value: "LLM" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ACCOUNT_MODE_PUBLIC = "PUBLIC";
|
||||||
|
const ACCOUNT_MODE_PERSONAL = "PERSONAL";
|
||||||
|
const ACCOUNT_MODE_BOTH = "BOTH";
|
||||||
|
|
||||||
function getAccountModeLabel(mode?: string) {
|
function getAccountModeLabel(mode?: string) {
|
||||||
if (mode === "PERSONAL") return "个人账户";
|
if (mode === ACCOUNT_MODE_PERSONAL) return "个人账户";
|
||||||
if (mode === "BOTH") return "公共和个人共存";
|
if (mode === ACCOUNT_MODE_BOTH) return "公共 + 个人";
|
||||||
return "公共账户";
|
return "公共账户";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,11 +89,79 @@ function formatDateTime(value?: string) {
|
||||||
return value ? value.replace("T", " ").substring(0, 19) : "-";
|
return value ? value.replace("T", " ").substring(0, 19) : "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSummaryCards(overview: MeetingPointsOverviewVO | null) {
|
||||||
|
if (!overview) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const isAdmin = Boolean(overview.admin);
|
||||||
|
const isPublicOnly = overview.accountMode === ACCOUNT_MODE_PUBLIC;
|
||||||
|
const isPersonalOnly = overview.accountMode === ACCOUNT_MODE_PERSONAL;
|
||||||
|
const showPublicSummary = !isPersonalOnly || isAdmin;
|
||||||
|
const showPersonalSummary = !isPublicOnly;
|
||||||
|
|
||||||
|
const cards: Array<{
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
value: number;
|
||||||
|
accent: string;
|
||||||
|
note: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
if (showPublicSummary) {
|
||||||
|
cards.push(
|
||||||
|
{
|
||||||
|
key: "public-balance",
|
||||||
|
title: "公共账户余额",
|
||||||
|
value: overview.publicBalance ?? 0,
|
||||||
|
accent: "linear-gradient(135deg, rgba(18, 87, 241, 0.14), rgba(18, 87, 241, 0.02))",
|
||||||
|
note: "用于统一分配和公共扣费",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "public-used",
|
||||||
|
title: "公共账户累计消耗积分",
|
||||||
|
value: overview.publicTotalPointsUsed ?? 0,
|
||||||
|
accent: "linear-gradient(135deg, rgba(11, 132, 98, 0.14), rgba(11, 132, 98, 0.02))",
|
||||||
|
note: "公共账户历史累计扣减",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPersonalSummary) {
|
||||||
|
cards.push(
|
||||||
|
{
|
||||||
|
key: "personal-balance",
|
||||||
|
title: isAdmin ? "个人账户余额汇总" : "个人账户余额",
|
||||||
|
value: overview.personalBalance ?? 0,
|
||||||
|
accent: "linear-gradient(135deg, rgba(148, 77, 255, 0.14), rgba(148, 77, 255, 0.02))",
|
||||||
|
note: isAdmin ? "管理员视角下的个人账户汇总" : "当前账号可用积分",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "personal-used",
|
||||||
|
title: isAdmin ? "个人账户累计消耗积分汇总" : "个人账户累计消耗积分",
|
||||||
|
value: overview.personalTotalPointsUsed ?? 0,
|
||||||
|
accent: "linear-gradient(135deg, rgba(237, 108, 2, 0.14), rgba(237, 108, 2, 0.02))",
|
||||||
|
note: isAdmin ? "管理员视角下的个人账户消耗汇总" : "当前账号历史累计扣减",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cards.push({
|
||||||
|
key: "charge-count",
|
||||||
|
title: "累计消耗次数",
|
||||||
|
value: overview.totalChargeCount ?? 0,
|
||||||
|
accent: "linear-gradient(135deg, rgba(48, 48, 48, 0.12), rgba(48, 48, 48, 0.02))",
|
||||||
|
note: "已发生扣费的总结记录数",
|
||||||
|
});
|
||||||
|
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
export default function MeetingPointsManagement() {
|
export default function MeetingPointsManagement() {
|
||||||
const [overview, setOverview] = useState<MeetingPointsOverviewVO | null>(null);
|
const [overview, setOverview] = useState<MeetingPointsOverviewVO | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [detailLoading, setDetailLoading] = useState(false);
|
const [detailLoading, setDetailLoading] = useState(false);
|
||||||
const [transferLoading, setTransferLoading] = useState(false);
|
const [transferLoading, setTransferLoading] = useState(false);
|
||||||
|
const [usersLoading, setUsersLoading] = useState(false);
|
||||||
const [records, setRecords] = useState<MeetingPointsLedgerListItemVO[]>([]);
|
const [records, setRecords] = useState<MeetingPointsLedgerListItemVO[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [detailOpen, setDetailOpen] = useState(false);
|
const [detailOpen, setDetailOpen] = useState(false);
|
||||||
|
|
@ -102,14 +176,26 @@ export default function MeetingPointsManagement() {
|
||||||
});
|
});
|
||||||
const [transferForm] = Form.useForm();
|
const [transferForm] = Form.useForm();
|
||||||
|
|
||||||
|
const isAdmin = Boolean(overview?.admin);
|
||||||
|
const isPublicOnly = overview?.accountMode === ACCOUNT_MODE_PUBLIC;
|
||||||
|
const isPersonalOnly = overview?.accountMode === ACCOUNT_MODE_PERSONAL;
|
||||||
|
const showTransferButton = isAdmin && !isPublicOnly;
|
||||||
|
const showPersonalGallery = isAdmin && !isPublicOnly;
|
||||||
|
const summaryCards = buildSummaryCards(overview);
|
||||||
|
|
||||||
const loadOverview = async () => {
|
const loadOverview = async () => {
|
||||||
const data = await getMeetingPointsOverview();
|
const data = await getMeetingPointsOverview();
|
||||||
setOverview(data);
|
setOverview(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
|
setUsersLoading(true);
|
||||||
|
try {
|
||||||
const data = await listUsers();
|
const data = await listUsers();
|
||||||
setUsers(data || []);
|
setUsers(data || []);
|
||||||
|
} finally {
|
||||||
|
setUsersLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadPage = async (nextParams = params) => {
|
const loadPage = async (nextParams = params) => {
|
||||||
|
|
@ -124,7 +210,7 @@ export default function MeetingPointsManagement() {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void Promise.all([loadOverview(), loadPage(), loadUsers()]);
|
void Promise.all([loadOverview(), loadPage()]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
|
|
@ -145,7 +231,7 @@ export default function MeetingPointsManagement() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
await Promise.all([loadOverview(), loadPage(), loadUsers()]);
|
await Promise.all([loadOverview(), loadPage()]);
|
||||||
message.success("已刷新积分数据");
|
message.success("已刷新积分数据");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -160,6 +246,13 @@ export default function MeetingPointsManagement() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenTransfer = async () => {
|
||||||
|
setTransferOpen(true);
|
||||||
|
if (!users.length) {
|
||||||
|
await loadUsers();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleTransferSubmit = async () => {
|
const handleTransferSubmit = async () => {
|
||||||
const values = await transferForm.validateFields();
|
const values = await transferForm.validateFields();
|
||||||
setTransferLoading(true);
|
setTransferLoading(true);
|
||||||
|
|
@ -281,14 +374,16 @@ export default function MeetingPointsManagement() {
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
title="积分管理"
|
title="积分管理"
|
||||||
subtitle="查看公共账户、个人账户和总结扣费流水"
|
subtitle="根据当前扣费模式查看公共账户、个人账户和会议积分消耗轨迹"
|
||||||
headerExtra={
|
headerExtra={
|
||||||
<Space>
|
<Space>
|
||||||
<Tag color="processing">当前模式:{getAccountModeLabel(overview?.accountMode)}</Tag>
|
<Tag color="processing">当前模式:{getAccountModeLabel(overview?.accountMode)}</Tag>
|
||||||
<Tag color="blue">优先级:{getChargePriorityLabel(overview?.chargePriority)}</Tag>
|
<Tag color="blue">优先级:{getChargePriorityLabel(overview?.chargePriority)}</Tag>
|
||||||
<Button icon={<PlusOutlined />} onClick={() => setTransferOpen(true)}>
|
{showTransferButton ? (
|
||||||
|
<Button icon={<PlusOutlined />} onClick={() => void handleOpenTransfer()}>
|
||||||
分配积分
|
分配积分
|
||||||
</Button>
|
</Button>
|
||||||
|
) : null}
|
||||||
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
<Button icon={<ReloadOutlined />} onClick={() => void handleRefresh()}>
|
||||||
刷新
|
刷新
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -317,41 +412,176 @@ export default function MeetingPointsManagement() {
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
<Card
|
||||||
<Col xs={24} md={6}>
|
bordered={false}
|
||||||
<Card>
|
style={{
|
||||||
<Statistic title="当前模式可用总积分" value={overview?.totalAvailableBalance ?? 0} />
|
marginBottom: 16,
|
||||||
</Card>
|
borderRadius: 24,
|
||||||
</Col>
|
background:
|
||||||
<Col xs={24} md={6}>
|
"linear-gradient(180deg, rgba(248,250,252,0.96) 0%, rgba(255,255,255,0.98) 100%)",
|
||||||
<Card>
|
boxShadow: "0 18px 40px rgba(15, 23, 42, 0.06)",
|
||||||
<Statistic title="公共账户余额" value={overview?.publicBalance ?? 0} />
|
}}
|
||||||
</Card>
|
>
|
||||||
</Col>
|
<Space direction="vertical" size={20} style={{ width: "100%" }}>
|
||||||
<Col xs={24} md={6}>
|
<div style={{ display: "flex", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
|
||||||
<Card>
|
<div>
|
||||||
<Statistic title="个人账户余额汇总" value={overview?.personalBalance ?? 0} />
|
<Text type="secondary" style={{ letterSpacing: 1.4 }}>
|
||||||
</Card>
|
POINTS OPERATIONS BOARD
|
||||||
</Col>
|
</Text>
|
||||||
<Col xs={24} md={6}>
|
<Title level={4} style={{ margin: "8px 0 0" }}>
|
||||||
<Card>
|
{isPublicOnly
|
||||||
<Statistic title="累计消耗次数" value={overview?.totalChargeCount ?? 0} />
|
? "当前为公共账户结算视图"
|
||||||
</Card>
|
: isPersonalOnly
|
||||||
</Col>
|
? "当前为个人账户结算视图"
|
||||||
</Row>
|
: "当前为公共与个人混合结算视图"}
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<div style={{ textAlign: "right" }}>
|
||||||
|
<Text type="secondary">统计口径</Text>
|
||||||
|
<div style={{ marginTop: 6 }}>
|
||||||
|
<Tag color={isAdmin ? "gold" : "default"}>{isAdmin ? "管理员视角" : "当前用户视角"}</Tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col xs={24} md={12}>
|
{summaryCards.map((item) => (
|
||||||
<Card>
|
<Col xs={24} md={12} xl={24 / Math.min(summaryCards.length, 5)} key={item.key}>
|
||||||
<Statistic title="公共账户累计消耗积分" value={overview?.publicTotalPointsUsed ?? 0} />
|
<Card
|
||||||
</Card>
|
size="small"
|
||||||
</Col>
|
style={{
|
||||||
<Col xs={24} md={12}>
|
borderRadius: 20,
|
||||||
<Card>
|
border: "1px solid rgba(15, 23, 42, 0.06)",
|
||||||
<Statistic title="个人账户累计消耗积分汇总" value={overview?.personalTotalPointsUsed ?? 0} />
|
background: item.accent,
|
||||||
|
minHeight: 142,
|
||||||
|
}}
|
||||||
|
styles={{ body: { padding: 18 } }}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" size={10} style={{ width: "100%" }}>
|
||||||
|
<Text type="secondary">{item.title}</Text>
|
||||||
|
<Statistic value={item.value} valueStyle={{ fontSize: 30, fontWeight: 700, color: "#111827" }} />
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
{item.note}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{showPersonalGallery ? (
|
||||||
|
<Card
|
||||||
|
bordered={false}
|
||||||
|
style={{
|
||||||
|
marginBottom: 16,
|
||||||
|
borderRadius: 24,
|
||||||
|
background: "linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(245,247,250,0.96) 100%)",
|
||||||
|
boxShadow: "0 16px 34px rgba(15, 23, 42, 0.05)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" size={18} style={{ width: "100%" }}>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", gap: 16, flexWrap: "wrap" }}>
|
||||||
|
<div>
|
||||||
|
<Title level={5} style={{ margin: 0 }}>
|
||||||
|
个人账户余额画廊
|
||||||
|
</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
仅管理员可见,按当前余额从高到低展示全部个人账户。
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Tag color="purple">{overview?.personalAccounts?.length ?? 0} 个账户</Tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{overview?.personalAccounts?.length ? (
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{overview.personalAccounts.map((account: MeetingPointsPersonalAccountVO, index) => (
|
||||||
|
<Col xs={24} sm={12} xl={8} xxl={6} key={account.userId}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
borderRadius: 20,
|
||||||
|
border: "1px solid rgba(124, 58, 237, 0.08)",
|
||||||
|
background:
|
||||||
|
index < 3
|
||||||
|
? "linear-gradient(145deg, rgba(255,255,255,1) 0%, rgba(245,241,255,0.98) 100%)"
|
||||||
|
: "linear-gradient(145deg, rgba(255,255,255,1) 0%, rgba(249,250,251,0.98) 100%)",
|
||||||
|
minHeight: 158,
|
||||||
|
}}
|
||||||
|
styles={{ body: { padding: 18 } }}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" size={14} style={{ width: "100%" }}>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
|
||||||
|
<Space size={12} align="start">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: 38,
|
||||||
|
height: 38,
|
||||||
|
borderRadius: 14,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
background: "rgba(124, 58, 237, 0.10)",
|
||||||
|
color: "#7c3aed",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserOutlined />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text strong style={{ display: "block" }}>
|
||||||
|
{account.displayName || account.username || `用户 #${account.userId}`}
|
||||||
|
</Text>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
{account.username ? `@${account.username}` : `ID ${account.userId}`}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
{index < 3 ? <Tag color="gold">TOP {index + 1}</Tag> : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">当前余额</Text>
|
||||||
|
<div style={{ fontSize: 30, fontWeight: 700, lineHeight: 1.2, color: "#111827" }}>
|
||||||
|
{account.currentBalance ?? 0}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "grid",
|
||||||
|
gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
|
||||||
|
gap: 10,
|
||||||
|
padding: 12,
|
||||||
|
borderRadius: 16,
|
||||||
|
background: "rgba(15, 23, 42, 0.03)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
累计消耗
|
||||||
|
</Text>
|
||||||
|
<div style={{ fontWeight: 600 }}>{account.totalPointsUsed ?? 0}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
|
账户类型
|
||||||
|
</Text>
|
||||||
|
<div style={{ fontWeight: 600 }}>个人</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
) : (
|
||||||
|
<Empty description="当前没有可展示的个人账户数据" />
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
className="app-page__content-card"
|
className="app-page__content-card"
|
||||||
|
|
@ -365,7 +595,7 @@ export default function MeetingPointsManagement() {
|
||||||
dataSource={records}
|
dataSource={records}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
totalCount={total}
|
totalCount={total}
|
||||||
scroll={{ y: "calc(100vh - 470px)", x: 1200 }}
|
scroll={{ y: "calc(100vh - 510px)", x: 1200 }}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -447,6 +677,7 @@ export default function MeetingPointsManagement() {
|
||||||
<Form.Item name="targetUserId" label="目标用户" rules={[{ required: true, message: "请选择目标用户" }]}>
|
<Form.Item name="targetUserId" label="目标用户" rules={[{ required: true, message: "请选择目标用户" }]}>
|
||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
|
loading={usersLoading}
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
placeholder="请选择用户"
|
placeholder="请选择用户"
|
||||||
options={users
|
options={users
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ export interface MeetingVO {
|
||||||
meetingSource?: "WEB" | "ANDROID";
|
meetingSource?: "WEB" | "ANDROID";
|
||||||
sourceDeviceCode?: string;
|
sourceDeviceCode?: string;
|
||||||
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
||||||
|
summaryDetailLevel?: "DETAILED" | "STANDARD" | "BRIEF";
|
||||||
|
summaryModelId?: number;
|
||||||
|
promptId?: number;
|
||||||
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
audioSaveStatus?: "NONE" | "SUCCESS" | "FAILED";
|
||||||
audioSaveMessage?: string;
|
audioSaveMessage?: string;
|
||||||
accessPassword?: string | null;
|
accessPassword?: string | null;
|
||||||
|
|
@ -103,6 +106,9 @@ export interface UpdateMeetingBasicCommand {
|
||||||
meetingTime?: string;
|
meetingTime?: string;
|
||||||
tags?: string;
|
tags?: string;
|
||||||
accessPassword?: string | null;
|
accessPassword?: string | null;
|
||||||
|
summaryModelId?: number;
|
||||||
|
promptId?: number;
|
||||||
|
summaryDetailLevel?: "DETAILED" | "STANDARD" | "BRIEF";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MeetingPageResult {
|
export interface MeetingPageResult {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue