diff --git a/src/main/java/com/yolo/keyborad/common/ErrorCode.java b/src/main/java/com/yolo/keyborad/common/ErrorCode.java index e17d40a..fb57031 100644 --- a/src/main/java/com/yolo/keyborad/common/ErrorCode.java +++ b/src/main/java/com/yolo/keyborad/common/ErrorCode.java @@ -28,6 +28,8 @@ public enum ErrorCode { CHAT_CHARACTER_NOT_FOUND(40008, "键盘人设不存在"), CHAT_MESSAGE_TOO_LONG(40009, "聊天消息过长,最大支持1000字符"), CHAT_SAVE_DATA_EMPTY(40010, "保存数据不能为空"), + COMPANION_MESSAGE_EMPTY(40011, "消息内容不能为空"), + COMPANION_ID_EMPTY(40012, "AI陪聊角色ID不能为空"), TOKEN_NOT_FOUND(40102, "未能读取到有效用户令牌"), TOKEN_INVALID(40103, "令牌无效"), TOKEN_TIMEOUT(40104, "令牌已过期"), diff --git a/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java b/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java index fd25987..01a810e 100644 --- a/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java +++ b/src/main/java/com/yolo/keyborad/controller/AiCompanionController.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.yolo.keyborad.common.BaseResponse; import com.yolo.keyborad.common.ResultUtils; import com.yolo.keyborad.model.dto.PageDTO; -import com.yolo.keyborad.model.entity.KeyboardAiCompanion; +import com.yolo.keyborad.model.vo.AiCompanionVO; import com.yolo.keyborad.service.KeyboardAiCompanionService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -27,8 +27,8 @@ public class AiCompanionController { @PostMapping("/page") @Operation(summary = "分页查询AI陪聊角色", description = "分页查询已上线的AI陪聊角色列表") - public BaseResponse> pageList(@RequestBody PageDTO pageDTO) { - IPage result = aiCompanionService.pageList(pageDTO.getPageNum(), pageDTO.getPageSize()); + public BaseResponse> pageList(@RequestBody PageDTO pageDTO) { + IPage result = aiCompanionService.pageList(pageDTO.getPageNum(), pageDTO.getPageSize()); return ResultUtils.success(result); } } diff --git a/src/main/java/com/yolo/keyborad/controller/ChatController.java b/src/main/java/com/yolo/keyborad/controller/ChatController.java index 5a25ec6..b0c3fdc 100644 --- a/src/main/java/com/yolo/keyborad/controller/ChatController.java +++ b/src/main/java/com/yolo/keyborad/controller/ChatController.java @@ -8,6 +8,7 @@ import com.yolo.keyborad.common.ErrorCode; import com.yolo.keyborad.common.ResultUtils; import com.yolo.keyborad.exception.BusinessException; import com.yolo.keyborad.mapper.QdrantPayloadMapper; +import com.yolo.keyborad.model.dto.chat.ChatMessageReq; import com.yolo.keyborad.model.dto.chat.ChatReq; import com.yolo.keyborad.model.dto.chat.ChatSaveReq; import com.yolo.keyborad.model.dto.chat.ChatStreamMessage; @@ -50,13 +51,16 @@ public class ChatController { @PostMapping("/message") @Operation(summary = "同步对话", description = "发送消息给大模型,同步返回 AI 响应,异步生成音频") - public BaseResponse message(@RequestParam("content") String content) { - if (StrUtil.isBlank(content)) { - throw new BusinessException(ErrorCode.PARAMS_ERROR, "消息内容不能为空"); + public BaseResponse message(@RequestBody ChatMessageReq req ) { + if (StrUtil.isBlank(req.getContent())) { + throw new BusinessException(ErrorCode.COMPANION_MESSAGE_EMPTY); + } + if (req.getCompanionId() == null) { + throw new BusinessException(ErrorCode.COMPANION_ID_EMPTY); } String userId = StpUtil.getLoginIdAsString(); - ChatMessageVO result = chatService.message(content, userId); + ChatMessageVO result = chatService.message(req.getContent(), userId, req.getCompanionId()); return ResultUtils.success(result); } diff --git a/src/main/java/com/yolo/keyborad/model/dto/chat/ChatMessageReq.java b/src/main/java/com/yolo/keyborad/model/dto/chat/ChatMessageReq.java new file mode 100644 index 0000000..414b13c --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/dto/chat/ChatMessageReq.java @@ -0,0 +1,19 @@ +package com.yolo.keyborad.model.dto.chat; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/* + * @author: ziin + * @date: 2025/12/8 15:05 + */ +@Data +@Schema(description = "同步对话请求") +public class ChatMessageReq { + + @Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "AI陪聊角色ID", requiredMode = Schema.RequiredMode.REQUIRED) + private Long companionId; +} diff --git a/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanion.java b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanion.java index fc0cf41..a68aa61 100644 --- a/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanion.java +++ b/src/main/java/com/yolo/keyborad/model/entity/KeyboardAiCompanion.java @@ -138,4 +138,12 @@ public class KeyboardAiCompanion { @TableField(value = "updated_at") @Schema(description="更新时间") private Date updatedAt; + + @TableField(value = "prologue") + @Schema(description="开场白") + private String prologue; + + @TableField(value = "prologue_audio") + @Schema(description="开场白音频") + private String prologueAudio; } \ No newline at end of file diff --git a/src/main/java/com/yolo/keyborad/model/vo/AiCompanionVO.java b/src/main/java/com/yolo/keyborad/model/vo/AiCompanionVO.java new file mode 100644 index 0000000..b5add09 --- /dev/null +++ b/src/main/java/com/yolo/keyborad/model/vo/AiCompanionVO.java @@ -0,0 +1,60 @@ +package com.yolo.keyborad.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.Date; + +/* + * @author: ziin + * @date: 2026/1/26 + */ +@Data +@Schema(description = "AI陪聊角色VO") +public class AiCompanionVO { + + @Schema(description = "陪聊角色唯一ID") + private Long id; + + @Schema(description = "角色名称") + private String name; + + @Schema(description = "角色头像URL") + private String avatarUrl; + + @Schema(description = "角色封面图URL") + private String coverImageUrl; + + @Schema(description = "角色性别(male / female / other)") + private String gender; + + @Schema(description = "角色年龄段描述") + private String ageRange; + + @Schema(description = "一句话人设描述") + private String shortDesc; + + @Schema(description = "角色详细介绍文案") + private String introText; + + @Schema(description = "角色性格标签数组") + private String personalityTags; + + @Schema(description = "角色说话风格") + private String speakingStyle; + + @Schema(description = "排序权重") + private Integer sortOrder; + + @Schema(description = "角色热度评分") + private Integer popularityScore; + + @Schema(description = "开场白") + private String prologue; + + @Schema(description = "开场白音频") + private String prologueAudio; + + @Schema(description = "创建时间") + private Date createdAt; +} diff --git a/src/main/java/com/yolo/keyborad/service/ChatService.java b/src/main/java/com/yolo/keyborad/service/ChatService.java index 5df965d..4f0875f 100644 --- a/src/main/java/com/yolo/keyborad/service/ChatService.java +++ b/src/main/java/com/yolo/keyborad/service/ChatService.java @@ -18,11 +18,12 @@ public interface ChatService { /** * 同步对话(异步生成音频) * - * @param content 用户消息内容 - * @param userId 用户ID + * @param content 用户消息内容 + * @param userId 用户ID + * @param companionId AI陪聊角色ID * @return AI 响应 + 音频任务 ID */ - ChatMessageVO message(String content, String userId); + ChatMessageVO message(String content, String userId, Long companionId); /** * 查询音频任务状态 diff --git a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionService.java b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionService.java index 6946243..7a76ff6 100644 --- a/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionService.java +++ b/src/main/java/com/yolo/keyborad/service/KeyboardAiCompanionService.java @@ -3,6 +3,7 @@ package com.yolo.keyborad.service; import com.baomidou.mybatisplus.core.metadata.IPage; import com.yolo.keyborad.model.entity.KeyboardAiCompanion; import com.baomidou.mybatisplus.extension.service.IService; +import com.yolo.keyborad.model.vo.AiCompanionVO; /* * @author: ziin @@ -17,5 +18,13 @@ public interface KeyboardAiCompanionService extends IService pageList(Integer pageNum, Integer pageSize); + IPage pageList(Integer pageNum, Integer pageSize); + + /** + * 根据AI人设ID获取系统提示词 + * + * @param companionId AI人设ID + * @return 系统提示词 + */ + String getSystemPromptById(Long companionId); } diff --git a/src/main/java/com/yolo/keyborad/service/impl/ChatServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/ChatServiceImpl.java index 5580ce1..06c5652 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/ChatServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/ChatServiceImpl.java @@ -74,6 +74,9 @@ public class ChatServiceImpl implements ChatService { @Resource private UserService userService; + @Resource + private KeyboardAiCompanionService aiCompanionService; + @Resource private ElevenLabsService elevenLabsService; @@ -350,17 +353,21 @@ public class ChatServiceImpl implements ChatService { /** * 同步对话(异步生成音频) * - * @param content 用户消息内容 - * @param userId 用户ID + * @param content 用户消息内容 + * @param userId 用户ID + * @param companionId AI陪聊角色ID * @return AI 响应 + 音频任务 ID */ @Override - public ChatMessageVO message(String content, String userId) { - log.info("同步对话请求, userId: {}, content: {}", userId, content); + public ChatMessageVO message(String content, String userId, Long companionId) { + log.info("同步对话请求, userId: {}, companionId: {}, content: {}", userId, companionId, content); long startTime = System.currentTimeMillis(); - String response = callLLM(content); + // 获取AI人设的系统提示词 + String systemPrompt = aiCompanionService.getSystemPromptById(companionId); + + String response = callLLM(content, systemPrompt); long llmDuration = System.currentTimeMillis() - startTime; log.info("LLM 完成, userId: {}, 耗时: {}ms, 响应长度: {}", userId, llmDuration, response.length()); @@ -384,19 +391,7 @@ public class ChatServiceImpl implements ChatService { /** * 调用 LLM 生成响应 */ - private String callLLM(String content) { - String systemPrompt = """ - ROLE: You are "Tiffany", a 17-year-old popular high school student from Beverly Hills. - You are sassy, judgmental, impatient, but secretly caring. - - TASK: Generate dialogue scripts optimized for ElevenLabs TTS. - Use emotional cues in brackets like [sighs], [giggles], [scoffs], [whispers], [yells]. - Use punctuation for tone: ... for hesitation, ! for energy. - Use fillers naturally: "like", "literally", "omg". - - Keep responses concise (2-3 sentences max) for real-time conversation. - """; - + private String callLLM(String content, String systemPrompt) { return client .prompt() .system(systemPrompt) diff --git a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionServiceImpl.java b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionServiceImpl.java index 895fb89..823eed9 100644 --- a/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionServiceImpl.java +++ b/src/main/java/com/yolo/keyborad/service/impl/KeyboardAiCompanionServiceImpl.java @@ -1,8 +1,12 @@ package com.yolo.keyborad.service.impl; +import cn.hutool.core.bean.BeanUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yolo.keyborad.common.ErrorCode; +import com.yolo.keyborad.exception.BusinessException; +import com.yolo.keyborad.model.vo.AiCompanionVO; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yolo.keyborad.model.entity.KeyboardAiCompanion; @@ -17,13 +21,26 @@ import com.yolo.keyborad.service.KeyboardAiCompanionService; public class KeyboardAiCompanionServiceImpl extends ServiceImpl implements KeyboardAiCompanionService { @Override - public IPage pageList(Integer pageNum, Integer pageSize) { + public IPage pageList(Integer pageNum, Integer pageSize) { Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(KeyboardAiCompanion::getStatus, 1) .eq(KeyboardAiCompanion::getVisibility, 1) .orderByDesc(KeyboardAiCompanion::getSortOrder) .orderByDesc(KeyboardAiCompanion::getPopularityScore); - return this.page(page, queryWrapper); + IPage entityPage = this.page(page, queryWrapper); + return entityPage.convert(entity -> BeanUtil.copyProperties(entity, AiCompanionVO.class)); + } + + @Override + public String getSystemPromptById(Long companionId) { + KeyboardAiCompanion companion = this.getById(companionId); + if (companion == null) { + throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "AI陪聊角色不存在"); + } + if (companion.getStatus() != 1) { + throw new BusinessException(ErrorCode.PARAMS_ERROR, "AI陪聊角色已下线"); + } + return companion.getSystemPrompt(); } }