feat(core): 实现热词批量创建及新增会议终端枚举
后端新增热词批量创建接口与相关DTO和VO,新增QtMeetingController支持Qt端会议业务,并新增MeetingTerminalEnum统一管理多终端类型。前端同步适配热词批量创建API并完善会议详情页的终端展示。dev_na
parent
c296600060
commit
eb32f13507
|
|
@ -2,6 +2,8 @@ package com.imeeting.controller.biz;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateDTO;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateResultVO;
|
||||||
import com.imeeting.dto.biz.HotWordBatchGroupDTO;
|
import com.imeeting.dto.biz.HotWordBatchGroupDTO;
|
||||||
import com.imeeting.dto.biz.HotWordDTO;
|
import com.imeeting.dto.biz.HotWordDTO;
|
||||||
import com.imeeting.dto.biz.HotWordVO;
|
import com.imeeting.dto.biz.HotWordVO;
|
||||||
|
|
@ -52,6 +54,18 @@ public class HotWordController {
|
||||||
return ApiResponse.ok(hotWordService.saveHotWord(hotWordDTO, loginUser.getUserId(), targetTenantId));
|
return ApiResponse.ok(hotWordService.saveHotWord(hotWordDTO, loginUser.getUserId(), targetTenantId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "批量新增热词")
|
||||||
|
@PostMapping("/batch")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@Log(value = "批量新增热词", type = "热词管理")
|
||||||
|
public ApiResponse<HotWordBatchCreateResultVO> saveBatch(@RequestBody HotWordBatchCreateDTO dto) {
|
||||||
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
|
Long targetTenantId = resolveTargetTenantId(loginUser, dto.getTenantId());
|
||||||
|
boolean platformAdmin = Boolean.TRUE.equals(loginUser.getIsPlatformAdmin());
|
||||||
|
return ApiResponse.ok(
|
||||||
|
hotWordService.saveHotWordsBatch(dto, loginUser.getUserId(), targetTenantId, platformAdmin));
|
||||||
|
}
|
||||||
|
|
||||||
@Operation(summary = "修改热词")
|
@Operation(summary = "修改热词")
|
||||||
@PutMapping
|
@PutMapping
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,8 @@ public class HotWordGroupController {
|
||||||
this.hotWordGroupService = hotWordGroupService;
|
this.hotWordGroupService = hotWordGroupService;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long resolveTargetTenantId(LoginUser loginUser, Long tenantId) {
|
private Long resolveTargetTenantId(LoginUser loginUser) {
|
||||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()) && Long.valueOf(0L).equals(tenantId)) {
|
return loginUser.getTenantId();
|
||||||
return 0L;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "新增热词组")
|
@Operation(summary = "新增热词组")
|
||||||
|
|
@ -39,7 +36,7 @@ public class HotWordGroupController {
|
||||||
@Log(value = "新增热词组", type = "热词组管理")
|
@Log(value = "新增热词组", type = "热词组管理")
|
||||||
public ApiResponse<HotWordGroupVO> save(@RequestBody HotWordGroupDTO dto) {
|
public ApiResponse<HotWordGroupVO> save(@RequestBody HotWordGroupDTO dto) {
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
Long targetTenantId = resolveTargetTenantId(loginUser, dto.getTenantId());
|
Long targetTenantId = resolveTargetTenantId(loginUser);
|
||||||
return ApiResponse.ok(hotWordGroupService.saveGroup(dto, loginUser.getUserId(), targetTenantId));
|
return ApiResponse.ok(hotWordGroupService.saveGroup(dto, loginUser.getUserId(), targetTenantId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +46,7 @@ public class HotWordGroupController {
|
||||||
@Log(value = "修改热词组", type = "热词组管理")
|
@Log(value = "修改热词组", type = "热词组管理")
|
||||||
public ApiResponse<HotWordGroupVO> update(@RequestBody HotWordGroupDTO dto) {
|
public ApiResponse<HotWordGroupVO> update(@RequestBody HotWordGroupDTO dto) {
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
Long targetTenantId = resolveTargetTenantId(loginUser, dto.getTenantId());
|
Long targetTenantId = resolveTargetTenantId(loginUser);
|
||||||
HotWordGroupVO existing = hotWordGroupService.listVisibleOptions(targetTenantId).stream()
|
HotWordGroupVO existing = hotWordGroupService.listVisibleOptions(targetTenantId).stream()
|
||||||
.filter(item -> item.getId().equals(dto.getId()))
|
.filter(item -> item.getId().equals(dto.getId()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
@ -57,7 +54,6 @@ public class HotWordGroupController {
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
return ApiResponse.error("热词组不存在");
|
return ApiResponse.error("热词组不存在");
|
||||||
}
|
}
|
||||||
dto.setTenantId(targetTenantId);
|
|
||||||
return ApiResponse.ok(hotWordGroupService.updateGroup(dto));
|
return ApiResponse.ok(hotWordGroupService.updateGroup(dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,9 +61,9 @@ public class HotWordGroupController {
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
@Log(value = "删除热词组", type = "热词组管理")
|
@Log(value = "删除热词组", type = "热词组管理")
|
||||||
public ApiResponse<Boolean> delete(@PathVariable Long id, @RequestParam(required = false) Long tenantId) {
|
public ApiResponse<Boolean> delete(@PathVariable Long id) {
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
Long targetTenantId = resolveTargetTenantId(loginUser, tenantId);
|
Long targetTenantId = resolveTargetTenantId(loginUser);
|
||||||
return ApiResponse.ok(hotWordGroupService.removeGroupById(id, targetTenantId));
|
return ApiResponse.ok(hotWordGroupService.removeGroupById(id, targetTenantId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,19 +74,18 @@ public class HotWordGroupController {
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
@RequestParam(defaultValue = "1") Integer current,
|
||||||
@RequestParam(defaultValue = "10") Integer size,
|
@RequestParam(defaultValue = "10") Integer size,
|
||||||
@RequestParam(required = false) String name,
|
@RequestParam(required = false) String name,
|
||||||
@RequestParam(required = false) Integer status,
|
@RequestParam(required = false) Integer status) {
|
||||||
@RequestParam(required = false) Long tenantId) {
|
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
Long targetTenantId = resolveTargetTenantId(loginUser, tenantId);
|
Long targetTenantId = resolveTargetTenantId(loginUser);
|
||||||
return ApiResponse.ok(hotWordGroupService.pageGroups(current, size, name, status, targetTenantId));
|
return ApiResponse.ok(hotWordGroupService.pageGroups(current, size, name, status, targetTenantId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(summary = "查询热词组选项")
|
@Operation(summary = "查询热词组选项")
|
||||||
@GetMapping("/options")
|
@GetMapping("/options")
|
||||||
@PreAuthorize("isAuthenticated()")
|
@PreAuthorize("isAuthenticated()")
|
||||||
public ApiResponse<List<HotWordGroupVO>> options(@RequestParam(required = false) Long tenantId) {
|
public ApiResponse<List<HotWordGroupVO>> options() {
|
||||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||||
Long targetTenantId = resolveTargetTenantId(loginUser, tenantId);
|
Long targetTenantId = resolveTargetTenantId(loginUser);
|
||||||
return ApiResponse.ok(hotWordGroupService.listVisibleOptions(targetTenantId));
|
return ApiResponse.ok(hotWordGroupService.listVisibleOptions(targetTenantId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
package com.imeeting.controller.qt;
|
||||||
|
|
||||||
|
import com.imeeting.dto.android.AndroidAuthContext;
|
||||||
|
import com.imeeting.common.MeetingConstants;
|
||||||
|
import com.imeeting.dto.biz.CreateMeetingCommand;
|
||||||
|
import com.imeeting.dto.biz.MeetingVO;
|
||||||
|
import com.imeeting.enums.MeetingTerminalEnum;
|
||||||
|
import com.imeeting.service.android.AndroidAuthService;
|
||||||
|
import com.imeeting.service.biz.MeetingAuthorizationService;
|
||||||
|
import com.imeeting.service.biz.MeetingCommandService;
|
||||||
|
import com.imeeting.service.biz.PromptTemplateService;
|
||||||
|
import com.imeeting.support.AndroidRequestLogHelper;
|
||||||
|
import com.unisbase.annotation.Anonymous;
|
||||||
|
import com.unisbase.common.ApiResponse;
|
||||||
|
import com.unisbase.common.annotation.Log;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "Qt会议管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/qt/meetings")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class QtMeetingController {
|
||||||
|
|
||||||
|
private final AndroidAuthService androidAuthService;
|
||||||
|
private final MeetingAuthorizationService meetingAuthorizationService;
|
||||||
|
private final MeetingCommandService meetingCommandService;
|
||||||
|
private final PromptTemplateService promptTemplateService;
|
||||||
|
|
||||||
|
@Operation(summary = "创建 Qt 离线会议")
|
||||||
|
@PostMapping
|
||||||
|
@Anonymous
|
||||||
|
@Log(value = "新增 Qt 离线会议", type = "Qt会议管理")
|
||||||
|
public ApiResponse<MeetingVO> createMeeting(HttpServletRequest request,
|
||||||
|
@Valid @RequestBody CreateMeetingCommand command) {
|
||||||
|
AndroidRequestLogHelper.logRequest(log, "Qt会议", "创建离线会议接口", "request", command);
|
||||||
|
AndroidAuthContext authContext = androidAuthService.authenticateHttp(request);
|
||||||
|
meetingAuthorizationService.assertCanCreateMeeting(authContext);
|
||||||
|
assertPromptAvailable(command.getPromptId(), authContext);
|
||||||
|
MeetingVO meeting = meetingCommandService.createMeeting(
|
||||||
|
command,
|
||||||
|
authContext.getTenantId(),
|
||||||
|
authContext.getUserId(),
|
||||||
|
resolveCreatorName(authContext),
|
||||||
|
MeetingTerminalEnum.resolve(authContext.getPlatform()).getCode(),
|
||||||
|
authContext.getDeviceId(),
|
||||||
|
resolveSourceDeviceMode(authContext)
|
||||||
|
);
|
||||||
|
return ApiResponse.ok(meeting);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveCreatorName(AndroidAuthContext authContext) {
|
||||||
|
if (hasText(authContext.getDisplayName())) {
|
||||||
|
return authContext.getDisplayName().trim();
|
||||||
|
}
|
||||||
|
if (hasText(authContext.getUsername())) {
|
||||||
|
return authContext.getUsername().trim();
|
||||||
|
}
|
||||||
|
return hasText(authContext.getDeviceId()) ? "qt:" + authContext.getDeviceId().trim() : "qt";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPromptAvailable(Long promptId, AndroidAuthContext authContext) {
|
||||||
|
if (promptId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean enabled = promptTemplateService.isTemplateEnabledForUser(
|
||||||
|
promptId,
|
||||||
|
authContext.getTenantId(),
|
||||||
|
authContext.getUserId(),
|
||||||
|
authContext.getPlatformAdmin(),
|
||||||
|
authContext.getTenantAdmin()
|
||||||
|
);
|
||||||
|
if (!enabled) {
|
||||||
|
throw new RuntimeException("总结模板不可用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSourceDeviceMode(AndroidAuthContext authContext) {
|
||||||
|
return authContext.isAnonymous()
|
||||||
|
? MeetingConstants.DEVICE_MODE_PUBLIC
|
||||||
|
: MeetingConstants.DEVICE_MODE_PRIVATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasText(String value) {
|
||||||
|
return value != null && !value.isBlank();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.imeeting.dto.biz;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "热词批量新增请求参数")
|
||||||
|
public class HotWordBatchCreateDTO {
|
||||||
|
|
||||||
|
@Schema(description = "租户 ID,平台管理员可传 0 表示平台范围")
|
||||||
|
private Long tenantId;
|
||||||
|
|
||||||
|
@Schema(description = "待新增的热词内容列表")
|
||||||
|
private List<String> words;
|
||||||
|
|
||||||
|
@Schema(description = "所属热词组 ID,为空表示未分组")
|
||||||
|
private Long hotWordGroupId;
|
||||||
|
|
||||||
|
@Schema(description = "备注")
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.imeeting.dto.biz;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "热词批量新增结果")
|
||||||
|
public class HotWordBatchCreateResultVO {
|
||||||
|
|
||||||
|
@Schema(description = "实际新增热词数量")
|
||||||
|
private Integer createdCount;
|
||||||
|
|
||||||
|
@Schema(description = "目标热词组中已存在的热词")
|
||||||
|
private List<String> existingWords;
|
||||||
|
}
|
||||||
|
|
@ -72,9 +72,15 @@ public class MeetingVO {
|
||||||
@Schema(description = "总结模型ID")
|
@Schema(description = "总结模型ID")
|
||||||
private Long summaryModelId;
|
private Long summaryModelId;
|
||||||
|
|
||||||
|
@Schema(description = "总结模型名称")
|
||||||
|
private String summaryModelName;
|
||||||
|
|
||||||
@Schema(description = "总结模板ID")
|
@Schema(description = "总结模板ID")
|
||||||
private Long promptId;
|
private Long promptId;
|
||||||
|
|
||||||
|
@Schema(description = "总结模板名称")
|
||||||
|
private String promptName;
|
||||||
|
|
||||||
@Schema(description = "最终生效热词组ID")
|
@Schema(description = "最终生效热词组ID")
|
||||||
private Long hotWordGroupId;
|
private Long hotWordGroupId;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.imeeting.enums;
|
||||||
|
|
||||||
|
import com.unisbase.common.exception.BusinessException;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public enum MeetingTerminalEnum {
|
||||||
|
WINDOWS("WINDOWS", "Windows"),
|
||||||
|
MACOS("MACOS", "macOS"),
|
||||||
|
KYLIN("KYLIN", "麒麟"),
|
||||||
|
UOS("UOS", "统信"),
|
||||||
|
HARMONYOS("HARMONYOS", "鸿蒙"),
|
||||||
|
WEB("WEB", "Web端"),
|
||||||
|
CUSTOM_TERMINAL("CUSTOM_TERMINAL", "定制终端");
|
||||||
|
|
||||||
|
private static final String LEGACY_ANDROID_CODE = "ANDROID";
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
MeetingTerminalEnum(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCustomTerminalSource(String source) {
|
||||||
|
return CUSTOM_TERMINAL.matches(source) || LEGACY_ANDROID_CODE.equalsIgnoreCase(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MeetingTerminalEnum resolve(String source) {
|
||||||
|
if (source == null || source.isBlank()) {
|
||||||
|
throw new BusinessException("会议终端类型不能为空");
|
||||||
|
}
|
||||||
|
String normalized = source.trim();
|
||||||
|
if (LEGACY_ANDROID_CODE.equalsIgnoreCase(normalized)) {
|
||||||
|
return CUSTOM_TERMINAL;
|
||||||
|
}
|
||||||
|
for (MeetingTerminalEnum terminal : values()) {
|
||||||
|
if (terminal.matches(normalized)) {
|
||||||
|
return terminal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new BusinessException("会议终端类型无效: " + source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSameTerminal(String source, String target) {
|
||||||
|
if (isCustomTerminalSource(source) && isCustomTerminalSource(target)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return source != null && target != null && source.equalsIgnoreCase(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(String source) {
|
||||||
|
return source != null && code.equalsIgnoreCase(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.imeeting.service.biz;
|
package com.imeeting.service.biz;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateDTO;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateResultVO;
|
||||||
import com.imeeting.dto.biz.HotWordDTO;
|
import com.imeeting.dto.biz.HotWordDTO;
|
||||||
import com.imeeting.dto.biz.HotWordVO;
|
import com.imeeting.dto.biz.HotWordVO;
|
||||||
import com.imeeting.entity.biz.HotWord;
|
import com.imeeting.entity.biz.HotWord;
|
||||||
|
|
@ -9,6 +11,8 @@ import java.util.List;
|
||||||
|
|
||||||
public interface HotWordService extends IService<HotWord> {
|
public interface HotWordService extends IService<HotWord> {
|
||||||
HotWordVO saveHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId);
|
HotWordVO saveHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId);
|
||||||
|
|
||||||
|
HotWordBatchCreateResultVO saveHotWordsBatch(HotWordBatchCreateDTO dto, Long userId, Long tenantId, boolean platformAdmin);
|
||||||
HotWordVO updateHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId);
|
HotWordVO updateHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId);
|
||||||
Integer updateHotWordGroupBatch(List<Long> ids, Long hotWordGroupId, Long tenantId);
|
Integer updateHotWordGroupBatch(List<Long> ids, Long hotWordGroupId, Long tenantId);
|
||||||
List<String> generatePinyin(String word);
|
List<String> generatePinyin(String word);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.imeeting.service.biz.impl;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateDTO;
|
||||||
|
import com.imeeting.dto.biz.HotWordBatchCreateResultVO;
|
||||||
import com.imeeting.dto.biz.HotWordDTO;
|
import com.imeeting.dto.biz.HotWordDTO;
|
||||||
import com.imeeting.dto.biz.HotWordVO;
|
import com.imeeting.dto.biz.HotWordVO;
|
||||||
import com.imeeting.entity.biz.HotWord;
|
import com.imeeting.entity.biz.HotWord;
|
||||||
|
|
@ -10,6 +12,7 @@ import com.imeeting.entity.biz.HotWordGroup;
|
||||||
import com.imeeting.mapper.biz.HotWordGroupMapper;
|
import com.imeeting.mapper.biz.HotWordGroupMapper;
|
||||||
import com.imeeting.mapper.biz.HotWordMapper;
|
import com.imeeting.mapper.biz.HotWordMapper;
|
||||||
import com.imeeting.service.biz.HotWordService;
|
import com.imeeting.service.biz.HotWordService;
|
||||||
|
import com.unisbase.common.exception.BusinessException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sourceforge.pinyin4j.PinyinHelper;
|
import net.sourceforge.pinyin4j.PinyinHelper;
|
||||||
|
|
@ -33,6 +36,9 @@ import java.util.stream.Collectors;
|
||||||
public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> implements HotWordService {
|
public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> implements HotWordService {
|
||||||
|
|
||||||
private static final int MAX_HOT_WORDS_PER_GROUP = 200;
|
private static final int MAX_HOT_WORDS_PER_GROUP = 200;
|
||||||
|
private static final int DEFAULT_MATCH_STRATEGY = 1;
|
||||||
|
private static final int DEFAULT_WEIGHT = 2;
|
||||||
|
private static final int ENABLED_STATUS = 1;
|
||||||
|
|
||||||
private final HotWordGroupMapper hotWordGroupMapper;
|
private final HotWordGroupMapper hotWordGroupMapper;
|
||||||
|
|
||||||
|
|
@ -52,12 +58,39 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
||||||
return toVO(hotWord);
|
return toVO(hotWord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public HotWordBatchCreateResultVO saveHotWordsBatch(HotWordBatchCreateDTO dto, Long userId, Long tenantId, boolean platformAdmin) {
|
||||||
|
Set<String> words = normalizeWords(dto.getWords());
|
||||||
|
if (words.isEmpty()) {
|
||||||
|
throw new BusinessException("请选择有效热词");
|
||||||
|
}
|
||||||
|
|
||||||
|
Long groupId = validateGroupForBatchCreate(dto.getHotWordGroupId(), tenantId, platformAdmin);
|
||||||
|
Set<String> existingWords = findExistingWords(words, groupId);
|
||||||
|
List<String> existingWordList = words.stream().filter(existingWords::contains).toList();
|
||||||
|
List<HotWord> hotWords = words.stream()
|
||||||
|
.filter(word -> !existingWords.contains(word))
|
||||||
|
.map(word -> buildBatchHotWord(word, groupId, dto.getRemark(), userId))
|
||||||
|
.toList();
|
||||||
|
HotWordBatchCreateResultVO result = new HotWordBatchCreateResultVO();
|
||||||
|
result.setExistingWords(existingWordList);
|
||||||
|
if (hotWords.isEmpty()) {
|
||||||
|
result.setCreatedCount(0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateGroupCapacityForBatchCreate(groupId, hotWords.size());
|
||||||
|
result.setCreatedCount(this.saveBatch(hotWords) ? hotWords.size() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public HotWordVO updateHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId) {
|
public HotWordVO updateHotWord(HotWordDTO hotWordDTO, Long userId, Long tenantId) {
|
||||||
HotWord hotWord = this.getById(hotWordDTO.getId());
|
HotWord hotWord = this.getById(hotWordDTO.getId());
|
||||||
if (hotWord == null) {
|
if (hotWord == null) {
|
||||||
throw new IllegalArgumentException("热词不存在");
|
throw new BusinessException("热词不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
String oldWord = hotWord.getWord();
|
String oldWord = hotWord.getWord();
|
||||||
|
|
@ -76,20 +109,20 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Integer updateHotWordGroupBatch(List<Long> ids, Long hotWordGroupId, Long tenantId) {
|
public Integer updateHotWordGroupBatch(List<Long> ids, Long hotWordGroupId, Long tenantId) {
|
||||||
if (ids == null || ids.isEmpty()) {
|
if (ids == null || ids.isEmpty()) {
|
||||||
throw new IllegalArgumentException("请选择热词");
|
throw new BusinessException("请选择热词");
|
||||||
}
|
}
|
||||||
Set<Long> uniqueIds = ids.stream()
|
Set<Long> uniqueIds = ids.stream()
|
||||||
.filter(id -> id != null)
|
.filter(id -> id != null)
|
||||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
if (uniqueIds.isEmpty()) {
|
if (uniqueIds.isEmpty()) {
|
||||||
throw new IllegalArgumentException("请选择热词");
|
throw new BusinessException("请选择热词");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<HotWord> hotWords = this.list(new LambdaQueryWrapper<HotWord>()
|
List<HotWord> hotWords = this.list(new LambdaQueryWrapper<HotWord>()
|
||||||
.in(HotWord::getId, uniqueIds)
|
.in(HotWord::getId, uniqueIds)
|
||||||
.eq(HotWord::getTenantId, tenantId));
|
.eq(HotWord::getTenantId, tenantId));
|
||||||
if (hotWords.size() != uniqueIds.size()) {
|
if (hotWords.size() != uniqueIds.size()) {
|
||||||
throw new IllegalArgumentException("部分热词不存在或无权操作");
|
throw new BusinessException("部分热词不存在或无权操作");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hotWordGroupId != null) {
|
if (hotWordGroupId != null) {
|
||||||
|
|
@ -160,16 +193,16 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
||||||
}
|
}
|
||||||
HotWordGroup group = hotWordGroupMapper.selectById(groupId);
|
HotWordGroup group = hotWordGroupMapper.selectById(groupId);
|
||||||
if (group == null || !tenantId.equals(group.getTenantId())) {
|
if (group == null || !tenantId.equals(group.getTenantId())) {
|
||||||
throw new IllegalArgumentException("热词组不存在");
|
throw new BusinessException("热词组不存在");
|
||||||
}
|
}
|
||||||
if (!Integer.valueOf(1).equals(group.getStatus())) {
|
if (!Integer.valueOf(1).equals(group.getStatus())) {
|
||||||
throw new IllegalArgumentException("热词组已禁用");
|
throw new BusinessException("热词组已禁用");
|
||||||
}
|
}
|
||||||
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
||||||
.eq(HotWord::getHotWordGroupId, groupId)
|
.eq(HotWord::getHotWordGroupId, groupId)
|
||||||
.ne(currentHotWordId != null, HotWord::getId, currentHotWordId));
|
.ne(currentHotWordId != null, HotWord::getId, currentHotWordId));
|
||||||
if (currentCount >= MAX_HOT_WORDS_PER_GROUP) {
|
if (currentCount >= MAX_HOT_WORDS_PER_GROUP) {
|
||||||
throw new IllegalArgumentException("热词组最多只能包含 200 个热词");
|
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||||
}
|
}
|
||||||
return group.getId();
|
return group.getId();
|
||||||
}
|
}
|
||||||
|
|
@ -177,10 +210,10 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
||||||
private void validateGroupCapacity(Long groupId, Long tenantId, List<HotWord> movingHotWords) {
|
private void validateGroupCapacity(Long groupId, Long tenantId, List<HotWord> movingHotWords) {
|
||||||
HotWordGroup group = hotWordGroupMapper.selectById(groupId);
|
HotWordGroup group = hotWordGroupMapper.selectById(groupId);
|
||||||
if (group == null || !tenantId.equals(group.getTenantId())) {
|
if (group == null || !tenantId.equals(group.getTenantId())) {
|
||||||
throw new IllegalArgumentException("热词组不存在");
|
throw new BusinessException("热词组不存在");
|
||||||
}
|
}
|
||||||
if (!Integer.valueOf(1).equals(group.getStatus())) {
|
if (!Integer.valueOf(1).equals(group.getStatus())) {
|
||||||
throw new IllegalArgumentException("热词组已禁用");
|
throw new BusinessException("热词组已禁用");
|
||||||
}
|
}
|
||||||
Set<Long> movingIds = movingHotWords.stream().map(HotWord::getId).collect(Collectors.toSet());
|
Set<Long> movingIds = movingHotWords.stream().map(HotWord::getId).collect(Collectors.toSet());
|
||||||
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
||||||
|
|
@ -190,10 +223,71 @@ public class HotWordServiceImpl extends ServiceImpl<HotWordMapper, HotWord> impl
|
||||||
.filter(item -> !groupId.equals(item.getHotWordGroupId()))
|
.filter(item -> !groupId.equals(item.getHotWordGroupId()))
|
||||||
.count();
|
.count();
|
||||||
if (currentCount + incomingCount > MAX_HOT_WORDS_PER_GROUP) {
|
if (currentCount + incomingCount > MAX_HOT_WORDS_PER_GROUP) {
|
||||||
throw new IllegalArgumentException("热词组最多只能包含 200 个热词");
|
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Set<String> normalizeWords(List<String> words) {
|
||||||
|
if (words == null || words.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
return words.stream()
|
||||||
|
.filter(word -> word != null)
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(word -> !word.isEmpty() && !"无".equals(word))
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<String> findExistingWords(Set<String> words, Long groupId) {
|
||||||
|
return this.list(new LambdaQueryWrapper<HotWord>()
|
||||||
|
.eq(groupId != null, HotWord::getHotWordGroupId, groupId)
|
||||||
|
.isNull(groupId == null, HotWord::getHotWordGroupId)
|
||||||
|
.in(HotWord::getWord, words))
|
||||||
|
.stream()
|
||||||
|
.map(HotWord::getWord)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long validateGroupForBatchCreate(Long groupId, Long tenantId, boolean platformAdmin) {
|
||||||
|
if (groupId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HotWordGroup group = hotWordGroupMapper.selectById(groupId);
|
||||||
|
if (group == null || (!platformAdmin && !tenantId.equals(group.getTenantId()))) {
|
||||||
|
throw new BusinessException("热词组不存在");
|
||||||
|
}
|
||||||
|
if (!Integer.valueOf(ENABLED_STATUS).equals(group.getStatus())) {
|
||||||
|
throw new BusinessException("热词组已禁用");
|
||||||
|
}
|
||||||
|
return group.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroupCapacityForBatchCreate(Long groupId, int incomingCount) {
|
||||||
|
if (groupId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
long currentCount = this.count(new LambdaQueryWrapper<HotWord>()
|
||||||
|
.eq(HotWord::getHotWordGroupId, groupId));
|
||||||
|
if (currentCount + incomingCount > MAX_HOT_WORDS_PER_GROUP) {
|
||||||
|
throw new BusinessException("热词组最多只能包含 200 个热词");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HotWord buildBatchHotWord(String word, Long groupId, String remark, Long userId) {
|
||||||
|
HotWord hotWord = new HotWord();
|
||||||
|
hotWord.setWord(word);
|
||||||
|
hotWord.setPinyinList(generatePinyin(word));
|
||||||
|
hotWord.setMatchStrategy(DEFAULT_MATCH_STRATEGY);
|
||||||
|
hotWord.setCategory("");
|
||||||
|
hotWord.setHotWordGroupId(groupId);
|
||||||
|
hotWord.setWeight(DEFAULT_WEIGHT);
|
||||||
|
hotWord.setStatus(ENABLED_STATUS);
|
||||||
|
hotWord.setIsPublic(1);
|
||||||
|
hotWord.setCreatorId(userId);
|
||||||
|
hotWord.setRemark(remark);
|
||||||
|
return hotWord;
|
||||||
|
}
|
||||||
|
|
||||||
private void generateCombinations(List<List<String>> matrix, int index, String current, List<String> result) {
|
private void generateCombinations(List<List<String>> matrix, int index, String current, List<String> result) {
|
||||||
if (index == matrix.size()) {
|
if (index == matrix.size()) {
|
||||||
result.add(current.trim());
|
result.add(current.trim());
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,15 @@ import com.imeeting.entity.biz.AiTask;
|
||||||
import com.imeeting.entity.biz.HotWordGroup;
|
import com.imeeting.entity.biz.HotWordGroup;
|
||||||
import com.imeeting.entity.biz.Meeting;
|
import com.imeeting.entity.biz.Meeting;
|
||||||
import com.imeeting.entity.biz.MeetingTranscript;
|
import com.imeeting.entity.biz.MeetingTranscript;
|
||||||
|
import com.imeeting.entity.biz.PromptTemplate;
|
||||||
import com.imeeting.event.MeetingCreatedEvent;
|
import com.imeeting.event.MeetingCreatedEvent;
|
||||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||||
|
import com.imeeting.dto.biz.AiModelVO;
|
||||||
|
import com.imeeting.service.biz.AiModelService;
|
||||||
import com.imeeting.service.biz.AiTaskService;
|
import com.imeeting.service.biz.AiTaskService;
|
||||||
import com.imeeting.service.biz.HotWordGroupService;
|
import com.imeeting.service.biz.HotWordGroupService;
|
||||||
import com.imeeting.service.biz.MeetingPointsService;
|
import com.imeeting.service.biz.MeetingPointsService;
|
||||||
|
import com.imeeting.service.biz.PromptTemplateService;
|
||||||
import com.imeeting.service.biz.RealtimeMeetingSessionStateService;
|
import com.imeeting.service.biz.RealtimeMeetingSessionStateService;
|
||||||
import com.imeeting.service.biz.MeetingSummaryFileService;
|
import com.imeeting.service.biz.MeetingSummaryFileService;
|
||||||
import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
import com.imeeting.service.realtime.RealtimeMeetingAudioStorageService;
|
||||||
|
|
@ -62,6 +66,8 @@ public class MeetingDomainSupport {
|
||||||
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
private final MeetingPlaybackAudioResolver meetingPlaybackAudioResolver;
|
||||||
private final HotWordGroupService hotWordGroupService;
|
private final HotWordGroupService hotWordGroupService;
|
||||||
private final RealtimeMeetingSessionStateService realtimeMeetingSessionStateService;
|
private final RealtimeMeetingSessionStateService realtimeMeetingSessionStateService;
|
||||||
|
private final AiModelService aiModelService;
|
||||||
|
private final PromptTemplateService promptTemplateService;
|
||||||
private final SysParamService sysParamService;
|
private final SysParamService sysParamService;
|
||||||
|
|
||||||
@Value("${unisbase.app.upload-path}")
|
@Value("${unisbase.app.upload-path}")
|
||||||
|
|
@ -433,6 +439,7 @@ public class MeetingDomainSupport {
|
||||||
vo.setOfflineRecordingStatus(meeting.getOfflineRecordingStatus());
|
vo.setOfflineRecordingStatus(meeting.getOfflineRecordingStatus());
|
||||||
vo.setSummaryModelId(meeting.getSummaryModelId());
|
vo.setSummaryModelId(meeting.getSummaryModelId());
|
||||||
vo.setPromptId(meeting.getPromptId());
|
vo.setPromptId(meeting.getPromptId());
|
||||||
|
fillSummaryConfigurationNames(meeting, vo);
|
||||||
fillEffectiveHotWordGroup(meeting, vo);
|
fillEffectiveHotWordGroup(meeting, vo);
|
||||||
vo.setAiCatalogEnabled(resolveAiCatalogEnabled());
|
vo.setAiCatalogEnabled(resolveAiCatalogEnabled());
|
||||||
vo.setSummaryDetailLevel(normalizeSummaryDetailLevel(meeting.getSummaryDetailLevel()));
|
vo.setSummaryDetailLevel(normalizeSummaryDetailLevel(meeting.getSummaryDetailLevel()));
|
||||||
|
|
@ -475,6 +482,21 @@ public class MeetingDomainSupport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillSummaryConfigurationNames(Meeting meeting, com.imeeting.dto.biz.MeetingVO vo) {
|
||||||
|
if (meeting.getSummaryModelId() != null) {
|
||||||
|
AiModelVO summaryModel = aiModelService.getModelById(meeting.getSummaryModelId(), "LLM");
|
||||||
|
if (summaryModel != null) {
|
||||||
|
vo.setSummaryModelName(summaryModel.getModelName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (meeting.getPromptId() != null) {
|
||||||
|
PromptTemplate promptTemplate = promptTemplateService.getById(meeting.getPromptId());
|
||||||
|
if (promptTemplate != null) {
|
||||||
|
vo.setPromptName(promptTemplate.getTemplateName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void fillEffectiveHotWordGroup(Meeting meeting, com.imeeting.dto.biz.MeetingVO vo) {
|
private void fillEffectiveHotWordGroup(Meeting meeting, com.imeeting.dto.biz.MeetingVO vo) {
|
||||||
Long hotWordGroupId = resolveEffectiveHotWordGroupId(meeting);
|
Long hotWordGroupId = resolveEffectiveHotWordGroupId(meeting);
|
||||||
vo.setHotWordGroupId(hotWordGroupId);
|
vo.setHotWordGroupId(hotWordGroupId);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,18 @@ export interface HotWordBatchGroupDTO {
|
||||||
hotWordGroupId?: number;
|
hotWordGroupId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HotWordBatchCreateDTO {
|
||||||
|
tenantId?: number;
|
||||||
|
words: string[];
|
||||||
|
hotWordGroupId?: number;
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HotWordBatchCreateResultVO {
|
||||||
|
createdCount: number;
|
||||||
|
existingWords: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export const getHotWordPage = (params: {
|
export const getHotWordPage = (params: {
|
||||||
current: number;
|
current: number;
|
||||||
size: number;
|
size: number;
|
||||||
|
|
@ -64,6 +76,13 @@ export const saveHotWord = (data: HotWordDTO) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createHotWordBatch = (data: HotWordBatchCreateDTO) => {
|
||||||
|
return http.post<{ code: string; data: HotWordBatchCreateResultVO; msg: string }>(
|
||||||
|
"/api/biz/hotword/batch",
|
||||||
|
data
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const updateHotWord = (data: HotWordDTO) => {
|
export const updateHotWord = (data: HotWordDTO) => {
|
||||||
return http.put<{ code: string; data: HotWordVO; msg: string }>(
|
return http.put<{ code: string; data: HotWordVO; msg: string }>(
|
||||||
"/api/biz/hotword",
|
"/api/biz/hotword",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ export interface HotWordGroupVO {
|
||||||
|
|
||||||
export interface HotWordGroupDTO {
|
export interface HotWordGroupDTO {
|
||||||
id?: number;
|
id?: number;
|
||||||
tenantId?: number;
|
|
||||||
groupName: string;
|
groupName: string;
|
||||||
status: number;
|
status: number;
|
||||||
remark?: string;
|
remark?: string;
|
||||||
|
|
@ -25,7 +24,6 @@ export const getHotWordGroupPage = (params: {
|
||||||
size: number;
|
size: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
status?: number;
|
status?: number;
|
||||||
tenantId?: number;
|
|
||||||
}) => {
|
}) => {
|
||||||
return http.get<{ code: string; data: { records: HotWordGroupVO[]; total: number }; msg: string }>(
|
return http.get<{ code: string; data: { records: HotWordGroupVO[]; total: number }; msg: string }>(
|
||||||
"/api/biz/hotword-group/page",
|
"/api/biz/hotword-group/page",
|
||||||
|
|
@ -33,10 +31,9 @@ export const getHotWordGroupPage = (params: {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getHotWordGroupOptions = (tenantId?: number) => {
|
export const getHotWordGroupOptions = () => {
|
||||||
return http.get<{ code: string; data: HotWordGroupVO[]; msg: string }>(
|
return http.get<{ code: string; data: HotWordGroupVO[]; msg: string }>(
|
||||||
"/api/biz/hotword-group/options",
|
"/api/biz/hotword-group/options",
|
||||||
{ params: { tenantId } }
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -54,9 +51,8 @@ export const updateHotWordGroup = (data: HotWordGroupDTO) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteHotWordGroup = (id: number, tenantId?: number) => {
|
export const deleteHotWordGroup = (id: number) => {
|
||||||
return http.delete<{ code: string; data: boolean; msg: string }>(
|
return http.delete<{ code: string; data: boolean; msg: string }>(
|
||||||
`/api/biz/hotword-group/${id}`,
|
`/api/biz/hotword-group/${id}`
|
||||||
{ params: { tenantId } }
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@ export interface MeetingVO {
|
||||||
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
sourceDeviceMode?: "PUBLIC" | "PRIVATE";
|
||||||
summaryDetailLevel?: SummaryDetailLevel;
|
summaryDetailLevel?: SummaryDetailLevel;
|
||||||
summaryModelId: number;
|
summaryModelId: number;
|
||||||
|
summaryModelName?: string;
|
||||||
promptId?: number;
|
promptId?: number;
|
||||||
|
promptName?: string;
|
||||||
hotWordGroupId?: number;
|
hotWordGroupId?: number;
|
||||||
hotWordGroupName?: string;
|
hotWordGroupName?: string;
|
||||||
aiCatalogEnabled?: boolean;
|
aiCatalogEnabled?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ const HotWords: React.FC = () => {
|
||||||
const [searchWord, setSearchWord] = useState("");
|
const [searchWord, setSearchWord] = useState("");
|
||||||
const [searchCategory, setSearchCategory] = useState<string | undefined>(undefined);
|
const [searchCategory, setSearchCategory] = useState<string | undefined>(undefined);
|
||||||
const [searchGroupId, setSearchGroupId] = useState<number | undefined>(undefined);
|
const [searchGroupId, setSearchGroupId] = useState<number | undefined>(undefined);
|
||||||
const [hotWordGroupFilter, setHotWordGroupFilter] = useState<HotWordGroupFilter>("all");
|
const [hotWordGroupFilter, setHotWordGroupFilter] = useState<HotWordGroupFilter>("ungrouped");
|
||||||
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
|
|
@ -196,7 +196,7 @@ const HotWords: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const res = await getHotWordGroupOptions(isPlatformAdmin ? activeTenantId : undefined);
|
const res = await getHotWordGroupOptions();
|
||||||
setGroupOptions(res.data?.data || []);
|
setGroupOptions(res.data?.data || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,7 +208,6 @@ const HotWords: React.FC = () => {
|
||||||
size: groupSize,
|
size: groupSize,
|
||||||
name: groupSearchName || undefined,
|
name: groupSearchName || undefined,
|
||||||
status: groupSearchStatus,
|
status: groupSearchStatus,
|
||||||
tenantId: isPlatformAdmin ? activeTenantId : undefined,
|
|
||||||
});
|
});
|
||||||
setGroupData(res.data?.data?.records || []);
|
setGroupData(res.data?.data?.records || []);
|
||||||
setGroupTotal(res.data?.data?.total || 0);
|
setGroupTotal(res.data?.data?.total || 0);
|
||||||
|
|
@ -315,10 +314,10 @@ const HotWords: React.FC = () => {
|
||||||
const values = await groupForm.validateFields();
|
const values = await groupForm.validateFields();
|
||||||
setGroupSubmitLoading(true);
|
setGroupSubmitLoading(true);
|
||||||
if (editingGroupId) {
|
if (editingGroupId) {
|
||||||
await updateHotWordGroup({ ...values, id: editingGroupId, tenantId: isPlatformAdmin ? activeTenantId : undefined });
|
await updateHotWordGroup({...values, id: editingGroupId});
|
||||||
message.success("热词组更新成功");
|
message.success("热词组更新成功");
|
||||||
} else {
|
} else {
|
||||||
await saveHotWordGroup({ ...values, tenantId: isPlatformAdmin ? activeTenantId : undefined });
|
await saveHotWordGroup(values);
|
||||||
message.success("热词组创建成功");
|
message.success("热词组创建成功");
|
||||||
}
|
}
|
||||||
setGroupEditorVisible(false);
|
setGroupEditorVisible(false);
|
||||||
|
|
@ -330,7 +329,7 @@ const HotWords: React.FC = () => {
|
||||||
|
|
||||||
const handleDeleteGroup = async (id: number, e?: React.MouseEvent) => {
|
const handleDeleteGroup = async (id: number, e?: React.MouseEvent) => {
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
await deleteHotWordGroup(id, isPlatformAdmin ? activeTenantId : undefined);
|
await deleteHotWordGroup(id);
|
||||||
message.success("热词组删除成功");
|
message.success("热词组删除成功");
|
||||||
if (searchGroupId === id) {
|
if (searchGroupId === id) {
|
||||||
setSearchGroupId(undefined);
|
setSearchGroupId(undefined);
|
||||||
|
|
@ -401,23 +400,25 @@ const HotWords: React.FC = () => {
|
||||||
setSearchCategory(undefined);
|
setSearchCategory(undefined);
|
||||||
setSearchGroupId(undefined);
|
setSearchGroupId(undefined);
|
||||||
setSelectedGroupName(undefined);
|
setSelectedGroupName(undefined);
|
||||||
setHotWordGroupFilter("all");
|
setHotWordGroupFilter("ungrouped");
|
||||||
bulkGroupForm.resetFields();
|
bulkGroupForm.resetFields();
|
||||||
setBulkGroupEditorVisible(false);
|
setBulkGroupEditorVisible(false);
|
||||||
setCurrent(1);
|
setCurrent(1);
|
||||||
void fetchData({ current: 1, word: "", category: null, groupFilter: "all", searchGroupId: null });
|
void fetchData({current: 1, word: "", category: null, groupFilter: "ungrouped", searchGroupId: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectGroup = (item: GroupListItem) => {
|
const handleSelectGroup = (item: GroupListItem) => {
|
||||||
setSearchGroupId(item.id);
|
setSearchGroupId(item.id);
|
||||||
setSelectedGroupName(item.id ? item.groupName : undefined);
|
setSelectedGroupName(item.id ? item.groupName : undefined);
|
||||||
setHotWordGroupFilter(item.id ?? "all");
|
setHotWordGroupFilter(item.id ?? "ungrouped");
|
||||||
setCurrent(1);
|
setCurrent(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hotWordGroupTitle = searchGroupId
|
const hotWordGroupTitle = hotWordGroupFilter === "ungrouped"
|
||||||
? selectedGroupName || groupData.find((item) => item.id === searchGroupId)?.groupName || groupNameMap[searchGroupId] || "热词列表"
|
? "未分组"
|
||||||
: "全部热词";
|
: typeof hotWordGroupFilter === "number"
|
||||||
|
? selectedGroupName || groupData.find((item) => item.id === hotWordGroupFilter)?.groupName || groupNameMap[hotWordGroupFilter] || "热词列表"
|
||||||
|
: "热词列表";
|
||||||
|
|
||||||
const groupFilterOptions = useMemo(
|
const groupFilterOptions = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
|
@ -428,7 +429,7 @@ const HotWords: React.FC = () => {
|
||||||
[groupOptions]
|
[groupOptions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupListData: GroupListItem[] = [{ id: undefined, groupName: "全部热词" }, ...groupData];
|
const groupListData: GroupListItem[] = [{id: undefined, groupName: "未分组"}, ...groupData];
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
|
@ -558,7 +559,7 @@ const HotWords: React.FC = () => {
|
||||||
loading={groupLoading}
|
loading={groupLoading}
|
||||||
dataSource={groupListData}
|
dataSource={groupListData}
|
||||||
renderItem={(item) => {
|
renderItem={(item) => {
|
||||||
const isSelected = searchGroupId === item.id;
|
const isSelected = item.id ? hotWordGroupFilter === item.id : hotWordGroupFilter === "ungrouped";
|
||||||
const actions = [];
|
const actions = [];
|
||||||
if (item.id) {
|
if (item.id) {
|
||||||
actions.push(
|
actions.push(
|
||||||
|
|
@ -611,7 +612,7 @@ const HotWords: React.FC = () => {
|
||||||
<span>{item.remark || "暂无备注"}</span>
|
<span>{item.remark || "暂无备注"}</span>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
: "查看所有热词"
|
: "查看未分组热词"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
|
|
@ -689,7 +690,8 @@ const HotWords: React.FC = () => {
|
||||||
className="hotwords-search__category"
|
className="hotwords-search__category"
|
||||||
options={categories.map((c) => ({ label: c.itemLabel, value: c.itemValue }))}
|
options={categories.map((c) => ({ label: c.itemLabel, value: c.itemValue }))}
|
||||||
/>
|
/>
|
||||||
<Button onClick={handleResetFilters} disabled={selectedHotWordIds.length === 0 && searchWord === "" && !searchCategory && !searchGroupId && hotWordGroupFilter === "all"}>
|
<Button onClick={handleResetFilters}
|
||||||
|
disabled={selectedHotWordIds.length === 0 && searchWord === "" && !searchCategory && !searchGroupId && hotWordGroupFilter === "ungrouped"}>
|
||||||
重置
|
重置
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,8 @@ import {
|
||||||
updateSpeakerInfo,
|
updateSpeakerInfo,
|
||||||
} from '../../api/business/meeting';
|
} from '../../api/business/meeting';
|
||||||
import { getAiModelDefault, getAiModelPage, AiModelVO } from '../../api/business/aimodel';
|
import { getAiModelDefault, getAiModelPage, AiModelVO } from '../../api/business/aimodel';
|
||||||
import { getHotWordPage, getPinyinSuggestion, saveHotWord } from '../../api/business/hotword';
|
import {createHotWordBatch} from '../../api/business/hotword';
|
||||||
|
import {getHotWordGroupOptions, type HotWordGroupVO} from '../../api/business/hotwordGroup';
|
||||||
import { getPromptPage, PromptTemplateVO } from '../../api/business/prompt';
|
import { getPromptPage, PromptTemplateVO } from '../../api/business/prompt';
|
||||||
import { listUsers } from '../../api';
|
import { listUsers } from '../../api';
|
||||||
import { useDict } from '../../hooks/useDict';
|
import { useDict } from '../../hooks/useDict';
|
||||||
|
|
@ -248,18 +249,20 @@ const parseBulletList = (content?: string | null) =>
|
||||||
const parseOverviewSection = (markdown: string) =>
|
const parseOverviewSection = (markdown: string) =>
|
||||||
extractSection(markdown, ['全文概要', '概要', '摘要', '概览']) || markdown.replace(/^---[\s\S]*?---/, '').trim();
|
extractSection(markdown, ['全文概要', '概要', '摘要', '概览']) || markdown.replace(/^---[\s\S]*?---/, '').trim();
|
||||||
|
|
||||||
|
const isValidKeyword = (value: string) => value.trim() !== '' && value.trim() !== '无';
|
||||||
|
|
||||||
const parseKeywordsSection = (markdown: string, tags: string) => {
|
const parseKeywordsSection = (markdown: string, tags: string) => {
|
||||||
const section = extractSection(markdown, ['关键词', '关键字', '标签']);
|
const section = extractSection(markdown, ['关键词', '关键字', '标签']);
|
||||||
const fromSection = parseBulletList(section)
|
const fromSection = parseBulletList(section)
|
||||||
.flatMap((line) => line.split(/[,,、/]/))
|
.flatMap((line) => line.split(/[,,、/]/))
|
||||||
.map((item) => item.trim())
|
.map((item) => item.trim())
|
||||||
.filter(Boolean);
|
.filter(isValidKeyword);
|
||||||
|
|
||||||
if (fromSection.length) {
|
if (fromSection.length) {
|
||||||
return Array.from(new Set(fromSection)).slice(0, 12);
|
return Array.from(new Set(fromSection)).slice(0, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(new Set((tags || '').split(',').map((item) => item.trim()).filter(Boolean))).slice(0, 12);
|
return Array.from(new Set((tags || '').split(',').map((item) => item.trim()).filter(isValidKeyword))).slice(0, 12);
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildMeetingAnalysis = (
|
const buildMeetingAnalysis = (
|
||||||
|
|
@ -280,7 +283,7 @@ const buildMeetingAnalysis = (
|
||||||
return {
|
return {
|
||||||
overview: String(parsed.overview || '').trim(),
|
overview: String(parsed.overview || '').trim(),
|
||||||
keywords: Array.from(
|
keywords: Array.from(
|
||||||
new Set((Array.isArray(parsed.keywords) ? parsed.keywords : []).map((item) => String(item).trim()).filter(Boolean)),
|
new Set((Array.isArray(parsed.keywords) ? parsed.keywords : []).map((item) => String(item).trim()).filter(isValidKeyword)),
|
||||||
).slice(0, 12),
|
).slice(0, 12),
|
||||||
chapters: chapters
|
chapters: chapters
|
||||||
.map((item: any) => ({
|
.map((item: any) => ({
|
||||||
|
|
@ -1227,6 +1230,10 @@ const MeetingDetail: React.FC = () => {
|
||||||
const [isEditingSummary, setIsEditingSummary] = useState(false);
|
const [isEditingSummary, setIsEditingSummary] = useState(false);
|
||||||
const [summaryDraft, setSummaryDraft] = useState('');
|
const [summaryDraft, setSummaryDraft] = useState('');
|
||||||
const [selectedKeywords, setSelectedKeywords] = useState<string[]>([]);
|
const [selectedKeywords, setSelectedKeywords] = useState<string[]>([]);
|
||||||
|
const [hotWordGroupModalOpen, setHotWordGroupModalOpen] = useState(false);
|
||||||
|
const [hotWordGroupOptions, setHotWordGroupOptions] = useState<HotWordGroupVO[]>([]);
|
||||||
|
const [selectedHotWordGroupId, setSelectedHotWordGroupId] = useState<number | undefined>();
|
||||||
|
const [hotWordGroupLoading, setHotWordGroupLoading] = useState(false);
|
||||||
const [workspaceTab, setWorkspaceTab] = useState<WorkspaceTab>('transcript');
|
const [workspaceTab, setWorkspaceTab] = useState<WorkspaceTab>('transcript');
|
||||||
const [addingHotwords, setAddingHotwords] = useState(false);
|
const [addingHotwords, setAddingHotwords] = useState(false);
|
||||||
const [editingTranscriptId, setEditingTranscriptId] = useState<number | null>(null);
|
const [editingTranscriptId, setEditingTranscriptId] = useState<number | null>(null);
|
||||||
|
|
@ -1288,15 +1295,20 @@ const MeetingDetail: React.FC = () => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
const userProfile = useMemo(() => {
|
||||||
|
const profileStr = sessionStorage.getItem("userProfile");
|
||||||
|
return profileStr ? JSON.parse(profileStr) : {};
|
||||||
|
}, []);
|
||||||
|
const activeTenantId = useMemo(() => Number(localStorage.getItem("activeTenantId") || 0), []);
|
||||||
|
const isPlatformAdmin = userProfile.isPlatformAdmin === true;
|
||||||
const analysis = useMemo(
|
const analysis = useMemo(
|
||||||
() => buildMeetingAnalysis(meeting?.analysis, meeting?.summaryContent, meeting?.tags || ''),
|
() => buildMeetingAnalysis(meeting?.analysis, meeting?.summaryContent, meeting?.tags || ''),
|
||||||
[meeting?.analysis, meeting?.summaryContent, meeting?.tags],
|
[meeting?.analysis, meeting?.summaryContent, meeting?.tags],
|
||||||
);
|
);
|
||||||
const expandKeywords = false;
|
const expandKeywords = true;
|
||||||
const visibleKeywords = expandKeywords ? analysis.keywords : analysis.keywords.slice(0, 9);
|
const visibleKeywords = expandKeywords ? analysis.keywords : analysis.keywords.slice(0, 9);
|
||||||
const meetingTags = useMemo(
|
const meetingTags = useMemo(
|
||||||
() => (meeting?.tags?.split(',').map((item) => item.trim()).filter(Boolean) || []),
|
() => (meeting?.tags?.split(',').map((item) => item.trim()).filter(isValidKeyword) || []),
|
||||||
[meeting?.tags],
|
[meeting?.tags],
|
||||||
);
|
);
|
||||||
const discussionItems = useMemo(() => {
|
const discussionItems = useMemo(() => {
|
||||||
|
|
@ -1388,25 +1400,23 @@ const MeetingDetail: React.FC = () => {
|
||||||
return buildMeetingPreviewUrl(meetingShareBaseUrl, meetingId);
|
return buildMeetingPreviewUrl(meetingShareBaseUrl, meetingId);
|
||||||
}, [meetingShareBaseUrl, meeting?.id, id]);
|
}, [meetingShareBaseUrl, meeting?.id, id]);
|
||||||
const summaryModelDisplayName = useMemo(() => {
|
const summaryModelDisplayName = useMemo(() => {
|
||||||
const matchedModel = llmModels.find((item) => item.id === meeting?.summaryModelId);
|
if (meeting?.summaryModelName?.trim()) {
|
||||||
if (matchedModel?.modelName) {
|
return meeting.summaryModelName.trim();
|
||||||
return matchedModel.modelName;
|
|
||||||
}
|
}
|
||||||
if (meeting?.summaryModelId) {
|
if (meeting?.summaryModelId) {
|
||||||
return `模型 #${meeting.summaryModelId}`;
|
return `模型 #${meeting.summaryModelId}`;
|
||||||
}
|
}
|
||||||
return '未配置';
|
return '未配置';
|
||||||
}, [llmModels, meeting?.summaryModelId]);
|
}, [meeting?.summaryModelId, meeting?.summaryModelName]);
|
||||||
const promptDisplayName = useMemo(() => {
|
const promptDisplayName = useMemo(() => {
|
||||||
const matchedPrompt = prompts.find((item) => item.id === meeting?.promptId);
|
if (meeting?.promptName?.trim()) {
|
||||||
if (matchedPrompt?.templateName) {
|
return meeting.promptName.trim();
|
||||||
return matchedPrompt.templateName;
|
|
||||||
}
|
}
|
||||||
if (meeting?.promptId) {
|
if (meeting?.promptId) {
|
||||||
return `模板 #${meeting.promptId}`;
|
return `模板 #${meeting.promptId}`;
|
||||||
}
|
}
|
||||||
return '未配置';
|
return '未配置';
|
||||||
}, [meeting?.promptId, prompts]);
|
}, [meeting?.promptId, meeting?.promptName]);
|
||||||
const hotWordGroupDisplayName = useMemo(() => {
|
const hotWordGroupDisplayName = useMemo(() => {
|
||||||
if (meeting?.hotWordGroupName?.trim()) {
|
if (meeting?.hotWordGroupName?.trim()) {
|
||||||
return meeting.hotWordGroupName.trim();
|
return meeting.hotWordGroupName.trim();
|
||||||
|
|
@ -1582,7 +1592,6 @@ const MeetingDetail: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
fetchData(Number(id));
|
fetchData(Number(id));
|
||||||
loadAiConfigs();
|
|
||||||
loadUsers();
|
loadUsers();
|
||||||
}, [id, fetchData]);
|
}, [id, fetchData]);
|
||||||
|
|
||||||
|
|
@ -1648,11 +1657,13 @@ const MeetingDetail: React.FC = () => {
|
||||||
getPromptPage({ current: 1, size: 100 }),
|
getPromptPage({ current: 1, size: 100 }),
|
||||||
getAiModelDefault('LLM'),
|
getAiModelDefault('LLM'),
|
||||||
]);
|
]);
|
||||||
setLlmModels((modelRes.data?.data?.records || []).filter((item) => item.status === 1));
|
const models = (modelRes.data?.data?.records || []).filter((item) => item.status === 1);
|
||||||
setPrompts((promptRes.data?.data?.records || []).filter((item) => item.status === 1));
|
const promptTemplates = (promptRes.data?.data?.records || []).filter((item) => item.status === 1);
|
||||||
summaryForm.setFieldsValue({ summaryModelId: defaultRes.data.data?.id });
|
setLlmModels(models);
|
||||||
|
setPrompts(promptTemplates);
|
||||||
|
return {models, promptTemplates, defaultModelId: defaultRes.data.data?.id};
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
return {models: [], promptTemplates: [], defaultModelId: undefined};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1743,17 +1754,19 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenSummaryDrawer = () => {
|
const handleOpenSummaryDrawer = async () => {
|
||||||
|
const {models, promptTemplates, defaultModelId} = await loadAiConfigs();
|
||||||
summaryForm.setFieldsValue({
|
summaryForm.setFieldsValue({
|
||||||
summaryModelId:
|
summaryModelId:
|
||||||
summaryForm.getFieldValue('summaryModelId') ??
|
summaryForm.getFieldValue('summaryModelId') ??
|
||||||
meeting?.summaryModelId ??
|
meeting?.summaryModelId ??
|
||||||
llmModels.find((model) => model.isDefault === 1)?.id ??
|
defaultModelId ??
|
||||||
llmModels[0]?.id,
|
models.find((model) => model.isDefault === 1)?.id ??
|
||||||
|
models[0]?.id,
|
||||||
promptId:
|
promptId:
|
||||||
summaryForm.getFieldValue('promptId') ??
|
summaryForm.getFieldValue('promptId') ??
|
||||||
meeting?.promptId ??
|
meeting?.promptId ??
|
||||||
prompts[0]?.id,
|
promptTemplates[0]?.id,
|
||||||
userPrompt: meeting?.lastUserPrompt ?? '',
|
userPrompt: meeting?.lastUserPrompt ?? '',
|
||||||
summaryDetailLevel:
|
summaryDetailLevel:
|
||||||
summaryForm.getFieldValue('summaryDetailLevel') ??
|
summaryForm.getFieldValue('summaryDetailLevel') ??
|
||||||
|
|
@ -1868,58 +1881,51 @@ const MeetingDetail: React.FC = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenHotWordGroupModal = async () => {
|
||||||
|
if (!selectedKeywords.length) {
|
||||||
|
message.warning('请先选择关键词');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHotWordGroupLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await getHotWordGroupOptions();
|
||||||
|
const options = (response.data?.data || []).filter((item) => item.status === 1 && item.hotWordCount < 200);
|
||||||
|
setHotWordGroupOptions(options);
|
||||||
|
setSelectedHotWordGroupId(options.some((item) => item.id === meeting?.hotWordGroupId) ? meeting?.hotWordGroupId : options[0]?.id);
|
||||||
|
setHotWordGroupModalOpen(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setHotWordGroupLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleAddSelectedHotwords = async () => {
|
const handleAddSelectedHotwords = async () => {
|
||||||
const keywords = selectedKeywords.map((item) => item.trim()).filter(Boolean);
|
const keywords = selectedKeywords.map((item) => item.trim()).filter(isValidKeyword);
|
||||||
if (!keywords.length) {
|
if (!keywords.length) {
|
||||||
message.warning('请先选择关键词');
|
message.warning('请先选择关键词');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (selectedHotWordGroupId === undefined) {
|
||||||
|
message.warning('请选择热词组');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setAddingHotwords(true);
|
setAddingHotwords(true);
|
||||||
try {
|
try {
|
||||||
const existingRes = await getHotWordPage({ current: 1, size: 500, word: '' });
|
const response = await createHotWordBatch({
|
||||||
const existingWords = new Set(
|
words: keywords,
|
||||||
(existingRes.data?.data?.records || [])
|
hotWordGroupId: selectedHotWordGroupId || undefined,
|
||||||
.map((item) => item.word?.trim())
|
remark: meeting ? `来源于会议:${meeting.title}` : '来源于会议关键词',
|
||||||
.filter(Boolean),
|
});
|
||||||
);
|
const result = response.data?.data;
|
||||||
const toCreate = keywords.filter((item) => !existingWords.has(item));
|
const existingWords = result?.existingWords || [];
|
||||||
|
|
||||||
if (!toCreate.length) {
|
|
||||||
message.info('所选关键词已存在于热词库');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
toCreate.map((word) =>
|
|
||||||
(async () => {
|
|
||||||
let pinyinList: string[] = [];
|
|
||||||
try {
|
|
||||||
const pinyinRes = await getPinyinSuggestion(word);
|
|
||||||
pinyinList = (pinyinRes.data?.data || []).map((item) => item.trim()).filter(Boolean);
|
|
||||||
} catch {
|
|
||||||
pinyinList = [];
|
|
||||||
}
|
|
||||||
return saveHotWord({
|
|
||||||
word,
|
|
||||||
pinyinList,
|
|
||||||
matchStrategy: 1,
|
|
||||||
category: '',
|
|
||||||
weight: 2,
|
|
||||||
status: 1,
|
|
||||||
remark: meeting ? `来源于会议:${meeting.title}` : '来源于会议关键词',
|
|
||||||
});
|
|
||||||
})(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const skippedCount = keywords.length - toCreate.length;
|
|
||||||
message.success(
|
message.success(
|
||||||
skippedCount > 0
|
`新增 ${result?.createdCount || 0} 个热词${existingWords.length ? `,[${existingWords.join(', ')}]已存在热词组` : ''}`,
|
||||||
? `已新增 ${toCreate.length} 个热词,跳过 ${skippedCount} 个重复项`
|
|
||||||
: `已新增 ${toCreate.length} 个热词`,
|
|
||||||
);
|
);
|
||||||
setSelectedKeywords([]);
|
setSelectedKeywords([]);
|
||||||
|
setHotWordGroupModalOpen(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -2392,7 +2398,7 @@ const MeetingDetail: React.FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{canRetrySummary && (
|
{canRetrySummary && (
|
||||||
<Button icon={<SyncOutlined />} onClick={handleOpenSummaryDrawer} disabled={actionLoading}>
|
<Button icon={<SyncOutlined/>} onClick={() => void handleOpenSummaryDrawer()} disabled={actionLoading}>
|
||||||
重新总结
|
重新总结
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -2578,9 +2584,9 @@ const MeetingDetail: React.FC = () => {
|
||||||
ghost
|
ghost
|
||||||
disabled={!selectedKeywords.length}
|
disabled={!selectedKeywords.length}
|
||||||
loading={addingHotwords}
|
loading={addingHotwords}
|
||||||
onClick={handleAddSelectedHotwords}
|
onClick={() => void handleOpenHotWordGroupModal()}
|
||||||
>
|
>
|
||||||
加入热词 {selectedKeywords.length > 0 ? `(${selectedKeywords.length})` : ''}
|
加入热词组 {selectedKeywords.length > 0 ? `(${selectedKeywords.length})` : ''}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -4238,6 +4244,33 @@ const MeetingDetail: React.FC = () => {
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="加入热词组"
|
||||||
|
open={hotWordGroupModalOpen}
|
||||||
|
onCancel={() => setHotWordGroupModalOpen(false)}
|
||||||
|
onOk={() => void handleAddSelectedHotwords()}
|
||||||
|
confirmLoading={addingHotwords}
|
||||||
|
okText="加入"
|
||||||
|
destroyOnHidden
|
||||||
|
>
|
||||||
|
<Form layout="vertical" style={{marginTop: 16}}>
|
||||||
|
<Form.Item label="目标热词组" required>
|
||||||
|
<Select
|
||||||
|
value={selectedHotWordGroupId}
|
||||||
|
placeholder={hotWordGroupLoading ? '正在加载热词组' : '请选择热词组'}
|
||||||
|
loading={hotWordGroupLoading}
|
||||||
|
onChange={setSelectedHotWordGroupId}
|
||||||
|
options={[
|
||||||
|
...hotWordGroupOptions.map((item) => ({
|
||||||
|
label: `${item.groupName} (${item.hotWordCount}/200)`,
|
||||||
|
value: item.id,
|
||||||
|
})),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{isOwner && (
|
{isOwner && (
|
||||||
<Modal title="编辑会议信息" open={editVisible} onOk={handleUpdateBasic} onCancel={() => setEditVisible(false)} confirmLoading={actionLoading} width={600} forceRender>
|
<Modal title="编辑会议信息" open={editVisible} onOk={handleUpdateBasic} onCancel={() => setEditVisible(false)} confirmLoading={actionLoading} width={600} forceRender>
|
||||||
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
|
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,7 @@ const PromptTemplates: React.FC = () => {
|
||||||
}, [isPlatformAdmin, templateLevel, activeTenantId]);
|
}, [isPlatformAdmin, templateLevel, activeTenantId]);
|
||||||
|
|
||||||
const loadGroupOptions = async () => {
|
const loadGroupOptions = async () => {
|
||||||
const targetTenantId = isPlatformAdmin && Number(templateLevel) === 1 ? 0 : undefined;
|
const res = await getHotWordGroupOptions();
|
||||||
const res = await getHotWordGroupOptions(targetTenantId ?? (isPlatformAdmin && activeTenantId === 0 ? 0 : undefined));
|
|
||||||
setGroupOptions(res.data?.data || []);
|
setGroupOptions(res.data?.data || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue