diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/asr/service/AliAsrServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/asr/service/AliAsrServiceImpl.java index e4b8e25d..57d28790 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/asr/service/AliAsrServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/asr/service/AliAsrServiceImpl.java @@ -11,8 +11,17 @@ import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import org.dromara.ai.domain.AiAsrRecord; +import org.dromara.ai.mapper.AiAsrRecordMapper; +import org.dromara.common.constant.HwMomAiConstants; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.encrypt.utils.EncryptUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.api.RemoteConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; @@ -20,6 +29,7 @@ import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; +import java.util.Date; import java.util.List; /** @@ -30,16 +40,23 @@ public class AliAsrServiceImpl implements AliAsrService { private static final Logger logger = LoggerFactory.getLogger(AliAsrServiceImpl.class); + @Autowired + private AiAsrRecordMapper aiAsrRecordMapper; + // 从配置文件读取API Key -// @Value("${aliyun.asr.api-key}") - private String apiKey = "sk-ce77518fcbc54b2bbee2a7c793be6492"; + @Value("${asr.aliyun.api-key}") + private String apiKey; // 临时文件存储路径 // @Value("${file.upload.temp-dir}") // private String tempDir; // 模型名称 - private static final String MODEL_NAME = "sensevoice-v1"; + // 从配置文件读取API Key + @Value("${asr.aliyun.model-name}") + private String modelName; + +// private static final String MODEL_NAME = "sensevoice-v1"; // 语言提示 private static final String[] LANGUAGE_HINTS = {"zh"}; @@ -73,9 +90,13 @@ public class AliAsrServiceImpl implements AliAsrService { public String recognizeSpeechByUrl(String audioUrl) { try { // 构建识别参数 + if(StringUtils.isEmpty(apiKey) || StringUtils.isEmpty(modelName)){ + throw new RuntimeException("请配置apiKey和modelName"); + } + TranscriptionParam param = TranscriptionParam.builder() - .apiKey(apiKey) - .model(MODEL_NAME) + .apiKey(EncryptUtils.decryptByBase64(apiKey)) + .model(modelName) .fileUrls(Arrays.asList(audioUrl)) .parameter("language_hints", LANGUAGE_HINTS) .build(); @@ -88,6 +109,15 @@ public class AliAsrServiceImpl implements AliAsrService { logger.info("语音识别任务提交成功,TaskId: {}", taskId); // 循环获取任务执行结果,直到任务结束 + //保存语音识别记录 + AiAsrRecord aiAsrRecord = new AiAsrRecord(); + aiAsrRecord.setModelName(modelName); + aiAsrRecord.setAsrFileUrl(audioUrl); + aiAsrRecord.setTenantId(LoginHelper.getTenantId()); + aiAsrRecord.setCreateBy(LoginHelper.getUserId()); + aiAsrRecord.setCreateDept(LoginHelper.getDeptId()); + aiAsrRecord.setCreateTime(new Date()); + while (true) { result = transcription.fetch(TranscriptionQueryParam.FromTranscriptionParam(param, taskId)); @@ -95,6 +125,10 @@ public class AliAsrServiceImpl implements AliAsrService { logger.info("语音识别任务完成,TaskId: {}", taskId); break; } else if (result.getTaskStatus() == TaskStatus.FAILED) { + aiAsrRecord.setAsrFailedReason(result.getResults().toString()); + aiAsrRecord.setTaskId(taskId); + aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_FAILED); + aiAsrRecordMapper.insert(aiAsrRecord); logger.error("语音识别任务失败,TaskId: {}, 错误信息: {}", taskId, result.getResults().toString()); throw new RuntimeException("语音识别任务失败: " + result.getResults().toString()); } @@ -104,7 +138,13 @@ public class AliAsrServiceImpl implements AliAsrService { } // 解析识别结果 - return parseRecognitionResult(result); + String recognitionResult = parseRecognitionResult(result); + + aiAsrRecord.setAsrContent(recognitionResult); + aiAsrRecord.setAsrResult(HwMomAiConstants.AI_ASR_RESULT_SUCCESS); + aiAsrRecordMapper.insert(aiAsrRecord); + + return recognitionResult; } catch (Exception e) { logger.error("语音识别过程发生错误", e); diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAsrRecordController.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAsrRecordController.java new file mode 100644 index 00000000..caf4ad79 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/controller/AiAsrRecordController.java @@ -0,0 +1,117 @@ +package org.dromara.ai.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.ai.domain.vo.AiAsrRecordVo; +import org.dromara.ai.domain.bo.AiAsrRecordBo; +import org.dromara.ai.service.IAiAsrRecordService; +import org.dromara.common.mybatis.core.page.TableDataInfo; + +/** + * AI语音识别记录 + * 前端访问路由地址为:/ai/asrRecord + * + * @author xins + * @date 2025-10-13 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/asrRecord") +public class AiAsrRecordController extends BaseController { + + private final IAiAsrRecordService aiAsrRecordService; + + /** + * 查询AI语音识别记录列表 + */ + @SaCheckPermission("ai:asrRecord:list") + @GetMapping("/list") + public TableDataInfo list(AiAsrRecordBo bo, PageQuery pageQuery) { + return aiAsrRecordService.queryPageList(bo, pageQuery); + } + + /** + * 导出AI语音识别记录列表 + */ + @SaCheckPermission("ai:asrRecord:export") + @Log(title = "AI语音识别记录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(AiAsrRecordBo bo, HttpServletResponse response) { + List list = aiAsrRecordService.queryList(bo); + ExcelUtil.exportExcel(list, "AI语音识别记录", AiAsrRecordVo.class, response); + } + + /** + * 获取AI语音识别记录详细信息 + * + * @param asrRecordId 主键 + */ + @SaCheckPermission("ai:asrRecord:query") + @GetMapping("/{asrRecordId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long asrRecordId) { + return R.ok(aiAsrRecordService.queryById(asrRecordId)); + } + + /** + * 新增AI语音识别记录 + */ + @SaCheckPermission("ai:asrRecord:add") + @Log(title = "AI语音识别记录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody AiAsrRecordBo bo) { + return toAjax(aiAsrRecordService.insertByBo(bo)); + } + + /** + * 修改AI语音识别记录 + */ + @SaCheckPermission("ai:asrRecord:edit") + @Log(title = "AI语音识别记录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody AiAsrRecordBo bo) { + return toAjax(aiAsrRecordService.updateByBo(bo)); + } + + /** + * 删除AI语音识别记录 + * + * @param asrRecordIds 主键串 + */ + @SaCheckPermission("ai:asrRecord:remove") + @Log(title = "AI语音识别记录", businessType = BusinessType.DELETE) + @DeleteMapping("/{asrRecordIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] asrRecordIds) { + return toAjax(aiAsrRecordService.deleteWithValidByIds(List.of(asrRecordIds), true)); + } + + + /** + * 下拉框查询AI语音识别记录列表 + */ + + @GetMapping("/getAiAsrRecordList") + public R> getAiAsrRecordList(AiAsrRecordBo bo) { + List list = aiAsrRecordService.queryList(bo); + return R.ok(list); + } +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiAsrRecord.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiAsrRecord.java new file mode 100644 index 00000000..c708095f --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/AiAsrRecord.java @@ -0,0 +1,107 @@ +package org.dromara.ai.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * AI语音识别记录对象 ai_asr_record + * + * @author xins + * @date 2025-10-13 + */ +@Data +@TableName("ai_asr_record") +public class AiAsrRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录ID,主键 + */ + @TableId(value = "asr_record_id", type = IdType.AUTO) + private Long asrRecordId; + + /** + * AI平台ID,关联ai_platform + */ + private Long platformId; + + /** + * 模型名称 + */ + private String modelName; + + /** + * 语音识别文件地址 + */ + private String asrFileUrl; + + /** + * 任务ID + */ + private String taskId; + + /** + * 语音识别内容 + */ + private String asrContent; + + /** + * 识别结果(1成功,0失败) + */ + private String asrResult; + + /** + * 识别失败原因 + */ + private String asrFailedReason; + + /** + * 租户编号 + */ + private String tenantId; + + + /** + * 搜索值 + */ + @JsonIgnore + @TableField(exist = false) + private String searchValue; + + /** + * 创建部门 + */ + @TableField(fill = FieldFill.INSERT) + private Long createDept; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + private Long createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** + * 请求参数 + */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @TableField(exist = false) + private Map params = new HashMap<>(); +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiAsrRecordBo.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiAsrRecordBo.java new file mode 100644 index 00000000..36f1a5c0 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/bo/AiAsrRecordBo.java @@ -0,0 +1,68 @@ +package org.dromara.ai.domain.bo; + +import com.alibaba.excel.annotation.ExcelProperty; +import org.dromara.ai.domain.AiAsrRecord; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +/** + * AI语音识别记录业务对象 ai_asr_record + * + * @author xins + * @date 2025-10-13 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = AiAsrRecord.class, reverseConvertGenerate = false) +public class AiAsrRecordBo extends BaseEntity { + + /** + * 记录ID,主键 + */ + private Long asrRecordId; + + /** + * AI平台ID,关联ai_platform + */ + private Long platformId; + + /** + * 模型名称 + */ + @ExcelProperty(value = "模型名称") + @NotBlank(message = "模型名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String modelName; + + /** + * 语音识别文件地址 + */ + private String asrFileUrl; + + /** + * 任务ID + */ + private String taskId; + + /** + * 语音识别内容 + */ + private String asrContent; + + /** + * 识别结果(1成功,0失败) + */ + @NotBlank(message = "识别结果(1成功,0失败)不能为空", groups = { AddGroup.class, EditGroup.class }) + private String asrResult; + + /** + * 识别失败原因 + */ + private String asrFailedReason; + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiAsrRecordVo.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiAsrRecordVo.java new file mode 100644 index 00000000..5a7f8e9a --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/domain/vo/AiAsrRecordVo.java @@ -0,0 +1,80 @@ +package org.dromara.ai.domain.vo; + +import org.dromara.ai.domain.AiAsrRecord; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * AI语音识别记录视图对象 ai_asr_record + * + * @author xins + * @date 2025-10-13 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = AiAsrRecord.class) +public class AiAsrRecordVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 记录ID,主键 + */ + @ExcelProperty(value = "记录ID,主键") + private Long asrRecordId; + + /** + * AI平台ID,关联ai_platform + */ + @ExcelProperty(value = "AI平台ID") + private Long platformId; + + /** + * 模型名称 + */ + @ExcelProperty(value = "模型名称") + private String modelName; + + /** + * 语音识别文件地址 + */ + @ExcelProperty(value = "语音识别文件地址") + private String asrFileUrl; + + /** + * 任务ID + */ + @ExcelProperty(value = "任务ID") + private String taskId; + + /** + * 语音识别内容 + */ + @ExcelProperty(value = "语音识别内容") + private String asrContent; + + /** + * 识别结果(1成功,0失败) + */ + @ExcelProperty(value = "识别结果(1成功,0失败)") + private String asrResult; + + /** + * 识别失败原因 + */ + @ExcelProperty(value = "识别失败原因") + private String asrFailedReason; + + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiAsrRecordMapper.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiAsrRecordMapper.java new file mode 100644 index 00000000..f90f0721 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/mapper/AiAsrRecordMapper.java @@ -0,0 +1,15 @@ +package org.dromara.ai.mapper; + +import org.dromara.ai.domain.AiAsrRecord; +import org.dromara.ai.domain.vo.AiAsrRecordVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * AI语音识别记录Mapper接口 + * + * @author xins + * @date 2025-10-13 + */ +public interface AiAsrRecordMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIMessage.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIMessage.java index ca056cda..6ef091c9 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIMessage.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/dto/AIMessage.java @@ -25,5 +25,10 @@ public class AIMessage { * 时间戳 */ private Long timestamp; + + /** + * AI模型ID + */ + private Long modelId; } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/IUnifiedAIProviderProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/IUnifiedAIProviderProcessor.java index c771c781..17f9a461 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/IUnifiedAIProviderProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/IUnifiedAIProviderProcessor.java @@ -96,8 +96,11 @@ public interface IUnifiedAIProviderProcessor { * @param takeFlag * @param completeFlag * @param userId + * @param tenantId + * @param deptId */ public void saveTokenUsage(String messageDetailType, String questionContent, String answerContent, TokenUsage tokenUsage, Long modelId, Long knowledgeBaseId, Long knowledgeContentId, - Long chatMessageId,String sessionId,String takeFlag,String completeFlag,Long userId); + Long chatMessageId,String sessionId,String takeFlag,String completeFlag, + Long userId,String tenantId,Long deptId); } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/BaseAIProviderProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/BaseAIProviderProcessor.java index 9fb73f4f..e5951db9 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/BaseAIProviderProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/BaseAIProviderProcessor.java @@ -9,8 +9,10 @@ import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.dromara.ai.domain.AiChatMessage; import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.domain.AiTokenUsage; +import org.dromara.ai.domain.vo.AiModelVo; import org.dromara.ai.mapper.AiChatMessageDetailMapper; import org.dromara.ai.mapper.AiChatMessageMapper; +import org.dromara.ai.mapper.AiModelMapper; import org.dromara.ai.mapper.AiTokenUsageMapper; import org.dromara.ai.process.dto.AIMessage; import org.dromara.ai.process.dto.AIRequest; @@ -20,6 +22,7 @@ import org.dromara.ai.process.enums.AIChatMessageTypeEnum; import org.dromara.ai.process.provider.processor.IUnifiedAIProviderProcessor; import org.dromara.ai.test.ChatRequest; import org.dromara.common.constant.HwMomAiConstants; +import org.dromara.common.encrypt.utils.EncryptUtils; import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.system.api.model.LoginUser; import org.springframework.beans.factory.annotation.Autowired; @@ -50,6 +53,8 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce private AiChatMessageDetailMapper aiChatMessageDetailMapper; @Autowired private AiTokenUsageMapper aiTokenUsageMapper; + @Autowired + private AiModelMapper aiModelMapper; // 用于解析流式JSON块的模式 private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}"); @@ -82,34 +87,18 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce } } - /** - * 从流式JSON中提取内容(由子类实现) - */ - protected abstract String extractContentFromStreamJson(String jsonChunk) throws Exception; - - /** - * 从普通响应JSON中提取内容(由子类实现) - */ - protected abstract AIResponse extractAIResponse(String json) throws Exception; - - /** - * 处理SSE流中的JSON块 - */ - protected Flux extractJsonChunks(String rawChunk) { - // 简单实现,假设每个事件都是完整的JSON -// return Flux.just(data); - System.out.println("RawChunk: " + rawChunk); - // 从原始块中提取完整的JSON对象 - Matcher matcher = JSON_PATTERN.matcher(rawChunk); - Flux chunks = Flux.empty(); - while (matcher.find()) { - chunks = chunks.concatWithValues(matcher.group()); + protected void configureApiKey(AIRequest request) { + if(request.getModelId()!=null){ + AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId()); + if (aiModelVo != null) { + if (aiModelVo.getApiKey() == null) { + throw new RuntimeException("请设置AI模型的API Key"); + } + request.setApiKey(EncryptUtils.decryptByBase64(aiModelVo.getApiKey())); + } } - return chunks; } - - /** * 普通http请求(非流式请求),直接返回完整回复 * @param url @@ -117,24 +106,7 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce * @param apiKey * @return AIResponse */ - public Mono standardRequest(String url,String requestBody, String apiKey) { - return webClient.post() - .uri(url) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .bodyValue(requestBody) - .retrieve() - .bodyToMono(String.class) - .flatMap(response -> { - try { - return Mono.just(extractAIResponse(response)); - } catch (Exception e) { - return buildErrorResponse("解析响应失败: " + e.getMessage()); - } - }) - .onErrorResume(e -> buildErrorResponse("API调用失败: " + e.getMessage())); -// .timeout(Duration.ofSeconds(30)); - } + public abstract Mono standardRequest(String url,String requestBody, String apiKey); /** @@ -166,6 +138,37 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce } + /** + * 从流式JSON中提取内容(由子类实现) + */ + protected abstract String extractContentFromStreamJson(String jsonChunk) throws Exception; + + /** + * 从普通响应JSON中提取内容(由子类实现) + */ + protected abstract AIResponse extractAIResponse(String json) throws Exception; + + /** + * 处理SSE流中的JSON块 + */ + protected Flux extractJsonChunks(String rawChunk) { + // 简单实现,假设每个事件都是完整的JSON +// return Flux.just(data); + System.out.println("RawChunk: " + rawChunk); + // 从原始块中提取完整的JSON对象 + Matcher matcher = JSON_PATTERN.matcher(rawChunk); + Flux chunks = Flux.empty(); + while (matcher.find()) { + chunks = chunks.concatWithValues(matcher.group()); + } + return chunks; + } + + + + + + @@ -238,7 +241,8 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION, objectMapper.writeValueAsString(request.getQuestionContent()), objectMapper.writeValueAsString(fullResponse), tokenUsage, request.getModelId(), request.getKnowledgeBaseId(), null, - aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1", loginUser.getUserId()); + aiChatMessage.getChatMessageId(),request.getSessionId(),request.getCarryHistoryFlag(),"1", + loginUser.getUserId(),loginUser.getTenantId(), loginUser.getDeptId()); // AiChatMessageDetail aiChatMessageDetail = new AiChatMessageDetail(); @@ -283,11 +287,14 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce * @param takeFlag * @param completeFlag * @param userId + * @param tenantId + * @param deptId */ @Override public void saveTokenUsage(String messageDetailType, String questionContent, String answerContent, TokenUsage tokenUsage, Long modelId, Long knowledgeBaseId, Long knowledgeContentId, - Long chatMessageId,String sessionId,String takeFlag,String completeFlag,Long userId) { + Long chatMessageId,String sessionId,String takeFlag,String completeFlag, + Long userId,String tenantId,Long deptId) { Long promptToken = tokenUsage!=null ? tokenUsage.getPromptToken():null; Long completionToken = tokenUsage!=null ? tokenUsage.getCompletionToken():null; Long totalToken = tokenUsage!=null ? tokenUsage.getTotalToken():null; @@ -306,6 +313,9 @@ public abstract class BaseAIProviderProcessor implements IUnifiedAIProviderProce aiChatMessageDetail.setCompleteFlag(completeFlag); aiChatMessageDetail.setChatMessageId(chatMessageId); aiChatMessageDetail.setSessionId(sessionId); + aiChatMessageDetail.setCreateBy(userId); + aiChatMessageDetail.setTenantId(tenantId); + aiChatMessageDetail.setCreateDept(deptId); aiChatMessageDetailMapper.insert(aiChatMessageDetail); MPJLambdaWrapper lqw = JoinWrappers.lambda(AiTokenUsage.class) diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java index 4d1616f6..40886afd 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/DeepSeekProcessor.java @@ -25,6 +25,8 @@ import org.dromara.ai.service.IAiChatMessageService; import org.dromara.common.encrypt.utils.EncryptUtils; import org.dromara.system.api.model.LoginUser; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -47,16 +49,38 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor { // 用于解析流式JSON块的模式 private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}"); - // private final String apiKey = "sk-e1df7a607644479e8ebad3be233ddafa"; - private final String apiUrl = "https://api.deepseek.com/v1/"; - private static final String API_URL = "https://api.deepseek.com/v1/chat/completions"; private final String deepSeekChatModel = "deepseek-chat"; - @Autowired - private AiModelMapper aiModelMapper; + /** + * 普通http请求(非流式请求),直接返回完整回复 + * @param url + * @param requestBody + * @param apiKey + * @return AIResponse + */ + @Override + public Mono standardRequest(String url,String requestBody, String apiKey) { + return webClient.post() + .uri(url) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .bodyValue(requestBody) + .retrieve() + .bodyToMono(String.class) + .flatMap(response -> { + try { + return Mono.just(extractAIResponse(response)); + } catch (Exception e) { + return buildErrorResponse("解析响应失败: " + e.getMessage()); + } + }) + .onErrorResume(e -> buildErrorResponse("API调用失败: " + e.getMessage())); +// .timeout(Duration.ofSeconds(30)); + } + public Mono chatTest(AIRequest request) { AIMessage aiMessage = new AIMessage(); aiMessage.setRole("user"); @@ -334,16 +358,7 @@ public class DeepSeekProcessor extends BaseAIProviderProcessor { return ""; } - private void configureApiKey(AIRequest request) { - AiModelVo aiModelVo = aiModelMapper.selectVoById(request.getModelId()); - if (aiModelVo != null) { - if (aiModelVo.getApiKey() == null) { - throw new RuntimeException("请设置AI模型的API Key"); - } - request.setApiKey(EncryptUtils.decryptByBase64(aiModelVo.getApiKey())); - } - } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TencentLkeProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TencentLkeProcessor.java index b51ca894..88d8c86f 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TencentLkeProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TencentLkeProcessor.java @@ -60,6 +60,17 @@ public class TencentLkeProcessor extends BaseAIProviderProcessor { properties.getTencentLke().getModel()); } + /** + * 普通http请求(非流式请求),直接返回完整回复 + * @param url + * @param requestBody + * @param apiKey + * @return AIResponse + */ + @Override + public Mono standardRequest(String url,String requestBody, String apiKey){ + return null; + } @Override public List getEmbeddingTest(AIRequest aiRequest) { diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TongYiQianWenProcessor.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TongYiQianWenProcessor.java index c3f14e53..72915803 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TongYiQianWenProcessor.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/process/provider/processor/impl/TongYiQianWenProcessor.java @@ -2,12 +2,14 @@ package org.dromara.ai.process.provider.processor.impl; //import com.example.deepseek.dto.EmbeddingResponse; +import com.alibaba.dashscope.aigc.generation.*; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse; +import org.checkerframework.checker.units.qual.A; import org.dromara.ai.domain.AiChatMessage; import org.dromara.ai.domain.AiChatMessageDetail; import org.dromara.ai.mapper.AiChatMessageDetailMapper; @@ -22,14 +24,28 @@ import org.dromara.system.api.model.LoginUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; +import reactor.core.publisher.FluxSink; import reactor.core.publisher.Mono; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.Arrays; +import java.lang.System; + +import com.alibaba.dashscope.common.Message; +import com.alibaba.dashscope.common.Role; +import com.alibaba.dashscope.exception.ApiException; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.exception.NoApiKeyException; +import io.reactivex.Flowable; +import io.reactivex.schedulers.Schedulers; + /** * @Author xins * @Date 2025/8/15 11:00 @@ -42,11 +58,7 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor { // 用于解析流式JSON块的模式 private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}"); - private final String apiKey = "sk-e1df7a607644479e8ebad3be233ddafa"; - private final String apiUrl = "https://api.t.com/v1/"; - - private static final String API_URL = "https://api.deepseek.com/v1/chat/completions"; - private final String deepSeekChatModel = "deepseek-chat"; + private static final String ALIYUN_MODEL_NAME = "qwen-plus"; @Autowired @@ -54,14 +66,57 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor { @Autowired private AiChatMessageDetailMapper aiChatMessageDetailMapper; + /** + * 普通http请求(非流式请求),直接返回完整回复 + * @param url + * @param requestBody + * @param apiKey + * @return AIResponse + */ + @Override + public Mono standardRequest(String url,String requestBody, String apiKey){ + return null; + } + + + /** + * 普通http请求(非流式请求),直接返回完整回复 + * @param aiRequest + * @return AIResponse + */ + private Mono aliStandardRequest(AIRequest aiRequest){ + Generation gen = new Generation(); + Message systemMsg = Message.builder() + .role(Role.SYSTEM.getValue()) + .content("You are a helpful assistant.") + .build(); + Message userMsg = Message.builder() + .role(Role.USER.getValue()) + .content(aiRequest.getText()) + .build(); + GenerationParam param = GenerationParam.builder() + // 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx") + .apiKey(aiRequest.getApiKey()) + // 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models + .model(ALIYUN_MODEL_NAME) + .messages(Arrays.asList(systemMsg, userMsg)) + .resultFormat(GenerationParam.ResultFormat.MESSAGE) + .build(); + try { + GenerationResult generationResult = gen.call(param); + return Mono.just(extractAliAIResponse(generationResult)); + } catch (NoApiKeyException e) { + throw new RuntimeException(e); + } catch (InputRequiredException e) { + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public Mono chatTest(AIRequest request) { - AIMessage aiMessage = new AIMessage(); - aiMessage.setRole("user"); - aiMessage.setContent("这是一个测试请求,请回复'测试成功'"); - request.setMessages(Collections.singletonList( - aiMessage - )); + request.setText("这是一个测试请求,请回复'测试成功'"); String apiEncryptFlag = request.getApiKeyEncryptFlag(); if(apiEncryptFlag.equals("1")){ request.setApiKey(EncryptUtils.decryptByBase64(request.getApiKey())); @@ -74,56 +129,89 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor { @Override public Mono chat(AIRequest request) { try { - ObjectNode rootNode = objectMapper.createObjectNode(); - rootNode.put("model", deepSeekChatModel); - rootNode.set("messages", objectMapper.valueToTree(request.getMessages())); - - if (request.getTemperature() != null) { - rootNode.put("temperature", request.getTemperature()); - } - if (request.getMaxTokens() != null) { - rootNode.put("max_tokens", request.getMaxTokens()); - } - - String requestBody = objectMapper.writeValueAsString(rootNode); - return standardRequest(API_URL, requestBody, request.getApiKey()); - } catch (IOException e) { + configureApiKey(request); + return aliStandardRequest(request); + } catch (Exception e) { return buildErrorResponse("构建请求失败: " + e.getMessage()); } } + /** + * 创建通义千问流式调用 + */ + private Flowable createStreamFlowable(AIRequest aiRequest) + throws ApiException, NoApiKeyException, InputRequiredException { + Generation gen = new Generation(); + + List aiMessages = aiRequest.getMessages(); + + List messages = new ArrayList<>(); + Message systemMsg = Message.builder() + .role(Role.SYSTEM.getValue()) + .content("You are a helpful assistant.") + .build(); + + messages.add(systemMsg); + for(AIMessage aiMessage:aiMessages){ + Message inputMessage = Message.builder() + .role(aiMessage.getRole()) + .content(aiMessage.getContent()) + .build(); + messages.add(inputMessage); + } + + GenerationParam param = GenerationParam.builder() + .apiKey(aiRequest.getApiKey()) + .model(ALIYUN_MODEL_NAME) + .messages(messages) + .resultFormat(GenerationParam.ResultFormat.MESSAGE) + .build(); + + return gen.streamCall(param); + } + + @Override public Flux chatStreamContent(AIRequest request, LoginUser loginUser) { - try { - ObjectNode rootNode = objectMapper.createObjectNode(); - rootNode.put("model", deepSeekChatModel); - rootNode.set("messages", objectMapper.valueToTree(request.getMessages())); - rootNode.put("stream", true); + return null; + } - if (request.getTemperature() != null) { - rootNode.put("temperature", request.getTemperature()); - } - -// request.setApiKey(); - // 执行流式请求并收集完整响应 - StringBuilder fullResponseBuilder = new StringBuilder(); - String requestBody = objectMapper.writeValueAsString(rootNode); + /** + * 从 GenerationResult 中提取内容 + */ + private static String extractContent(GenerationResult result) { + if (result == null || result.getOutput() == null) { return null; -// return executeStreamRequest(API_URL, requestBody, apiKey).doOnNext(chunk -> { -// // 收集每个chunk -// fullResponseBuilder.append(chunk); -// }) -// .doOnComplete(() -> { -// // 流完成后保存到数据库 -// saveChatMessage(request, fullResponseBuilder.toString()); -// }) -// .doOnError(error -> { -// // 错误处理 -//// log.error("流式请求出错", error); -// }); - } catch (IOException e) { - return Flux.error(new RuntimeException("构建请求失败: " + e.getMessage())); + } + + try { + return result.getOutput() + .getChoices() + .get(0) + .getMessage() + .getContent(); + } catch (Exception e) { + return null; + } + } + + /** + * 检查是否生成完成 + */ + private static boolean isFinished(GenerationResult result) { + if (result == null || result.getOutput() == null) { + return false; + } + + try { + String finishReason = result.getOutput() + .getChoices() + .get(0) + .getFinishReason(); + return "stop".equals(finishReason); + } catch (Exception e) { + return false; } } @@ -162,6 +250,18 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor { } } + protected AIResponse extractAliAIResponse(GenerationResult generationResult) throws Exception { + List choices = generationResult.getOutput().getChoices(); + String content = choices.get(0).getMessage().getContent(); + GenerationUsage generationUsage = generationResult.getUsage(); + TokenUsage tokenUsage = new TokenUsage( + generationUsage.getInputTokens().longValue(),generationUsage.getOutputTokens().longValue(), + generationUsage.getTotalTokens().longValue() + ); + + return new AIResponse(content, tokenUsage); + } + @Override protected AIResponse extractAIResponse(String json) throws Exception { JsonNode node = objectMapper.readTree(json); @@ -264,7 +364,67 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor { @Override public Flux chatStream(AIRequest request, LoginUser loginUser) { - return null; + configureApiKey(request); + return Flux.create(sink -> { + try { + Flowable flowable = createStreamFlowable(request); + AtomicBoolean isCompleted = new AtomicBoolean(false); + // 用于收集完整响应和token信息 + TokenUsage finalTokenUsage = new TokenUsage(0L, 0L, 0L); + flowable.subscribe( + // onNext + generationResult -> { + if (sink.isCancelled()) { + return; + } + + try { + String content = extractContent(generationResult); + if (content != null && !content.isEmpty()) { + sink.next(content); + } + + // 检查是否结束 + if (isFinished(generationResult)) { + if (!isCompleted.getAndSet(true)) { + sink.complete(); + } + + GenerationUsage usage = generationResult.getUsage(); + finalTokenUsage.setPromptToken(usage.getInputTokens().longValue()); + finalTokenUsage.setCompletionToken(usage.getOutputTokens().longValue()); + finalTokenUsage.setTotalToken(usage.getTotalTokens().longValue()); + + saveChatMessage(request, content, finalTokenUsage, loginUser); + } + } catch (Exception e) { + sink.error(e); + } + }, + // onError + error -> { + if (!isCompleted.getAndSet(true)) { + sink.error(error); + } + }, + // onComplete + () -> { + if (!isCompleted.getAndSet(true)) { + sink.complete(); + } + } + ); + + // 设置取消回调 + sink.onCancel(() -> { + isCompleted.set(true); + // 可以在这里添加资源清理逻辑 + }); + + } catch (Exception e) { + sink.error(e); + } + }, FluxSink.OverflowStrategy.BUFFER); } @Override diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiAsrRecordService.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiAsrRecordService.java new file mode 100644 index 00000000..5533442d --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/IAiAsrRecordService.java @@ -0,0 +1,69 @@ +package org.dromara.ai.service; + +import org.dromara.ai.domain.AiAsrRecord; +import org.dromara.ai.domain.vo.AiAsrRecordVo; +import org.dromara.ai.domain.bo.AiAsrRecordBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * AI语音识别记录Service接口 + * + * @author xins + * @date 2025-10-13 + */ +public interface IAiAsrRecordService { + + /** + * 查询AI语音识别记录 + * + * @param asrRecordId 主键 + * @return AI语音识别记录 + */ + AiAsrRecordVo queryById(Long asrRecordId); + + /** + * 分页查询AI语音识别记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return AI语音识别记录分页列表 + */ + TableDataInfo queryPageList(AiAsrRecordBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的AI语音识别记录列表 + * + * @param bo 查询条件 + * @return AI语音识别记录列表 + */ + List queryList(AiAsrRecordBo bo); + + /** + * 新增AI语音识别记录 + * + * @param bo AI语音识别记录 + * @return 是否新增成功 + */ + Boolean insertByBo(AiAsrRecordBo bo); + + /** + * 修改AI语音识别记录 + * + * @param bo AI语音识别记录 + * @return 是否修改成功 + */ + Boolean updateByBo(AiAsrRecordBo bo); + + /** + * 校验并批量删除AI语音识别记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java index 14924580..9c7eec35 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AIAssistantServiceImpl.java @@ -150,7 +150,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService { tencentLkeProcessor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_QUESTION_VECTOR,"AI问答获取向量", null, tokenUsage, embeddingModelId, aiRequest.getKnowledgeBaseId(), null, - null, null, "0", "1",LoginHelper.getUserId()); + null, null, "0", "1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId()); List queryEmbedding = tencentLkeProcessor.getEmbedding(embeddingResponses); @@ -284,7 +284,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService { //``` processor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_SQL,prompt, content,response.block().getTokenUsage(), aiRequest.getModelId(), null,null, - null,null,"0","1",LoginHelper.getUserId()); + null,null,"0","1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId()); return extractSqlFromContent(content); } else { @@ -434,6 +434,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService { Long modelId = aiFillFormRequest.getModelId(); AIRequest aiRequest = new AIRequest(); aiRequest.setMessages(Collections.singletonList(aiMessage)); + aiRequest.setText(sb.toString()); aiRequest.setModelId(modelId); Mono response = processor.chat(aiRequest); @@ -443,7 +444,7 @@ public class AIAssistantServiceImpl implements IAIAssistantService { parseRelateTable(aiFormSettingDetailList, contentJson); processor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_FORM,sb.toString(), content, response.block().getTokenUsage(), modelId, null, null, - null, null, "0", "1",LoginHelper.getUserId()); + null, null, "0", "1",LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId()); System.out.println(contentJson.toJSONString()); return contentJson; diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiAsrRecordServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiAsrRecordServiceImpl.java new file mode 100644 index 00000000..c45a5151 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiAsrRecordServiceImpl.java @@ -0,0 +1,136 @@ +package org.dromara.ai.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.toolkit.JoinWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.dromara.ai.domain.bo.AiAsrRecordBo; +import org.dromara.ai.domain.vo.AiAsrRecordVo; +import org.dromara.ai.domain.AiAsrRecord; +import org.dromara.ai.mapper.AiAsrRecordMapper; +import org.dromara.ai.service.IAiAsrRecordService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * AI语音识别记录Service业务层处理 + * + * @author xins + * @date 2025-10-13 + */ +@RequiredArgsConstructor +@Service +public class AiAsrRecordServiceImpl implements IAiAsrRecordService { + + private final AiAsrRecordMapper baseMapper; + + /** + * 查询AI语音识别记录 + * + * @param asrRecordId 主键 + * @return AI语音识别记录 + */ + @Override + public AiAsrRecordVo queryById(Long asrRecordId){ + return baseMapper.selectVoById(asrRecordId); + } + + /** + * 分页查询AI语音识别记录列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return AI语音识别记录分页列表 + */ + @Override + public TableDataInfo queryPageList(AiAsrRecordBo bo, PageQuery pageQuery) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的AI语音识别记录列表 + * + * @param bo 查询条件 + * @return AI语音识别记录列表 + */ + @Override + public List queryList(AiAsrRecordBo bo) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private MPJLambdaWrapper buildQueryWrapper(AiAsrRecordBo bo) { + Map params = bo.getParams(); + MPJLambdaWrapper lqw = JoinWrappers.lambda(AiAsrRecord.class) + .selectAll(AiAsrRecord.class) + .eq(bo.getAsrRecordId() != null, AiAsrRecord::getAsrRecordId, bo.getAsrRecordId()) + .eq(StringUtils.isNotBlank(bo.getAsrFileUrl()), AiAsrRecord::getAsrFileUrl, bo.getAsrFileUrl()) + .eq(StringUtils.isNotBlank(bo.getAsrContent()), AiAsrRecord::getAsrContent, bo.getAsrContent()) + .eq(StringUtils.isNotBlank(bo.getAsrResult()), AiAsrRecord::getAsrResult, bo.getAsrResult()) + .eq(StringUtils.isNotBlank(bo.getAsrFailedReason()), AiAsrRecord::getAsrFailedReason, bo.getAsrFailedReason()) + .orderByDesc(AiAsrRecord::getCreateTime); + return lqw; + } + + /** + * 新增AI语音识别记录 + * + * @param bo AI语音识别记录 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(AiAsrRecordBo bo) { + AiAsrRecord add = MapstructUtils.convert(bo, AiAsrRecord.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setAsrRecordId(add.getAsrRecordId()); + } + return flag; + } + + /** + * 修改AI语音识别记录 + * + * @param bo AI语音识别记录 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(AiAsrRecordBo bo) { + AiAsrRecord update = MapstructUtils.convert(bo, AiAsrRecord.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(AiAsrRecord entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除AI语音识别记录信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiChatMessageDetailServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiChatMessageDetailServiceImpl.java index 74b9420c..8a6837b7 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiChatMessageDetailServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiChatMessageDetailServiceImpl.java @@ -172,6 +172,7 @@ public class AiChatMessageDetailServiceImpl implements IAiChatMessageDetailServi aiAssistantMessage.setRole("assistant"); aiAssistantMessage.setContent(aiChatMessageDetail.getAnswerContent()); aiAssistantMessage.setTimestamp(aiChatMessageDetail.getCreateTime().getTime()); + aiAssistantMessage.setModelId(aiChatMessageDetail.getModelId()); aiMessages.add(aiAssistantMessage); } diff --git a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiKnowledgeBaseServiceImpl.java b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiKnowledgeBaseServiceImpl.java index d63bd692..61b391c5 100644 --- a/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiKnowledgeBaseServiceImpl.java +++ b/ruoyi-modules/hwmom-ai/src/main/java/org/dromara/ai/service/impl/AiKnowledgeBaseServiceImpl.java @@ -357,7 +357,7 @@ public class AiKnowledgeBaseServiceImpl implements IAiKnowledgeBaseService { TokenUsage tokenUsage = new TokenUsage(null,null,usage.getTotalTokens()); tencentLkeProcessor.saveTokenUsage(HwMomAiConstants.AI_CHAT_MESSAGE_DETAIL_TYPE_CONTENT_VECTOR,"AI上传知识库内容获取向量", null, tokenUsage, modelId, aiRequest.getKnowledgeBaseId(), aiKnowledgeEmbedding.getKnowledgeContentId(), - null, null, "0", "1", LoginHelper.getUserId()); + null, null, "0", "1", LoginHelper.getUserId(),LoginHelper.getTenantId(),LoginHelper.getDeptId()); // 转换结果格式 diff --git a/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiAsrRecordMapper.xml b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiAsrRecordMapper.xml new file mode 100644 index 00000000..9e9ab540 --- /dev/null +++ b/ruoyi-modules/hwmom-ai/src/main/resources/mapper/ai/AiAsrRecordMapper.xml @@ -0,0 +1,7 @@ + + + + +