package com.emotion.service.impl; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.emotion.dto.request.EpicScriptCreateWithDialogueRequest; import com.emotion.dto.request.ai.AiRuntimeRequest; import com.emotion.dto.response.EpicScriptCreateWithDialogueResponse; import com.emotion.dto.response.ai.AiRuntimeTestResponse; import com.emotion.entity.Conversation; import com.emotion.entity.EpicScript; import com.emotion.entity.Message; import com.emotion.mapper.EpicScriptMapper; import com.emotion.service.AiRuntimeService; import com.emotion.service.ConversationService; import com.emotion.service.EpicScriptDialogueService; import com.emotion.service.EpicScriptService; import com.emotion.service.MessageService; import com.emotion.util.SnowflakeIdGenerator; import com.emotion.util.UserContextHolder; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; /** * 剧本对话创建服务实现类 */ @Slf4j @Service public class EpicScriptDialogueServiceImpl implements EpicScriptDialogueService { @Autowired private EpicScriptService epicScriptService; @Autowired private ConversationService conversationService; @Autowired private MessageService messageService; @Autowired private AiRuntimeService aiRuntimeService; @Autowired private SnowflakeIdGenerator snowflakeIdGenerator; @Override @Transactional(rollbackFor = Exception.class) public EpicScriptCreateWithDialogueResponse createWithDialogue(EpicScriptCreateWithDialogueRequest request) { String currentUserId = UserContextHolder.getCurrentUserId(); if (currentUserId == null) { throw new IllegalStateException("用户未登录"); } String scriptId = snowflakeIdGenerator.nextIdAsString(); String conversationId = snowflakeIdGenerator.nextIdAsString(); LocalDateTime now = LocalDateTime.now(); // 1. 创建剧本 EpicScript script = new EpicScript(); script.setId(scriptId); script.setUserId(currentUserId); script.setTitle("我的人生剧本"); // 默认标题,后续会被 AI 生成的标题覆盖 script.setTheme(request.getTheme()); script.setStyle(StringUtils.hasText(request.getStyle()) ? request.getStyle() : "career"); script.setLength(StringUtils.hasText(request.getLength()) ? request.getLength() : "medium"); script.setConversationId(conversationId); script.setPlotJson(new HashMap<>()); script.setIsSelected(0); epicScriptService.save(script); // 2. 创建对话 Conversation conversation = new Conversation(); conversation.setId(conversationId); conversation.setUserId(currentUserId); conversation.setUserType("registered"); conversation.setScriptId(scriptId); conversation.setTitle(script.getTitle()); conversation.setType("script"); conversation.setConversationStatus("active"); conversation.setStartTime(now); conversation.setLastActiveTime(now); conversation.setMessageCount(0); conversationService.save(conversation); // 3. 调用 AI 生成完整剧本 String aiOutput = invokeScriptGenerate(request, currentUserId, conversationId, null); // 4. 解析 AI 输出获取剧本元数据 Map metadata = parseScriptOutput(aiOutput); // 从 AI 输出中提取标题,如果没有则使用默认值 String aiTitle = (String) metadata.getOrDefault("title", null); if (StringUtils.hasText(aiTitle)) { script.setTitle(aiTitle); } script.setPlotIntro((String) metadata.getOrDefault("plotIntro", "")); script.setPlotTurning((String) metadata.getOrDefault("plotTurning", "")); script.setPlotClimax((String) metadata.getOrDefault("plotClimax", "")); script.setPlotEnding((String) metadata.getOrDefault("plotEnding", "")); Map plotJson = script.getPlotJson(); plotJson.putAll(metadata); script.setPlotJson(plotJson); // 5. 创建系统欢迎消息(messageOrder = 1) Message systemWelcome = new Message(); systemWelcome.setId(snowflakeIdGenerator.nextIdAsString()); systemWelcome.setConversationId(conversationId); systemWelcome.setScriptId(scriptId); systemWelcome.setUserId(currentUserId); systemWelcome.setContent(buildSystemWelcome(metadata)); systemWelcome.setType("system"); systemWelcome.setSender("system"); systemWelcome.setTimestamp(now); systemWelcome.setMessageOrder(1L); systemWelcome.setStatus("sent"); systemWelcome.setIsRead(1); messageService.createMessage(systemWelcome); // 6. 创建用户消息(messageOrder = 2) Message userMessage = new Message(); userMessage.setId(snowflakeIdGenerator.nextIdAsString()); userMessage.setConversationId(conversationId); userMessage.setScriptId(scriptId); userMessage.setUserId(currentUserId); userMessage.setContent(request.getTheme()); userMessage.setType("chat"); userMessage.setSender("user"); userMessage.setTimestamp(now); userMessage.setMessageOrder(2L); userMessage.setStatus("sent"); userMessage.setIsRead(1); messageService.createMessage(userMessage); // 7. 创建 AI 助手开场白消息(messageOrder = 3) String aiContent = buildDisplayContent(metadata); Message aiMessage = new Message(); aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); aiMessage.setConversationId(conversationId); aiMessage.setScriptId(scriptId); aiMessage.setUserId(currentUserId); aiMessage.setContent(aiContent); aiMessage.setType("script"); aiMessage.setSender("assistant"); aiMessage.setTimestamp(LocalDateTime.now()); aiMessage.setMessageOrder(3L); aiMessage.setVersionNumber(1); aiMessage.setStatus("sent"); aiMessage.setMetadata(JSON.toJSONString(metadata)); messageService.createMessage(aiMessage); // 8. 更新剧本信息(已在前面完成) script.setCurrentVersionMessageId(aiMessage.getId()); epicScriptService.updateById(script); // 9. 更新对话当前消息和消息计数 conversation.setCurrentMessageId(aiMessage.getId()); conversation.setMessageCount(3); conversationService.updateById(conversation); // 10. 构建响应 EpicScriptCreateWithDialogueResponse response = new EpicScriptCreateWithDialogueResponse(); BeanUtils.copyProperties(script, response); response.setScriptId(scriptId); response.setConversationId(conversationId); response.setCurrentVersionMessageId(aiMessage.getId()); return response; } private String invokeScriptGenerate(EpicScriptCreateWithDialogueRequest request, String userId, String conversationId, String userMessageId) { JSONObject inputs = new JSONObject(); inputs.put("input", request.getTheme()); inputs.put("prompt", request.getTheme()); inputs.put("theme", request.getTheme()); inputs.put("style", StringUtils.hasText(request.getStyle()) ? request.getStyle() : "career"); inputs.put("length", StringUtils.hasText(request.getLength()) ? request.getLength() : "medium"); inputs.put("characterInfo", request.getCharacterInfo()); inputs.put("lifeEventsSummary", request.getLifeEventsSummary()); inputs.put("useSocialInsights", request.getUseSocialInsights()); inputs.put("conversationId", conversationId); inputs.put("userMessageId", userMessageId); // 使用 AiRuntimeRequest 调用 AI 运行时 AiRuntimeRequest runtimeRequest = new AiRuntimeRequest(); runtimeRequest.setSceneCode("script_generate"); runtimeRequest.setUserId(userId); runtimeRequest.setInputs(inputs); AiRuntimeTestResponse response = aiRuntimeService.test(runtimeRequest); if (response == null || !"success".equals(response.getStatus()) || !StringUtils.hasText(response.getOutput())) { String message = response == null ? "AI_RUNTIME_EMPTY_RESPONSE" : response.getErrorMessage(); throw new IllegalStateException(StringUtils.hasText(message) ? message : "AI_RUNTIME_FAILED"); } return response.getOutput(); } private Map parseScriptOutput(String output) { if (!StringUtils.hasText(output)) { return new HashMap<>(); } try { return JSON.parseObject(output, Map.class); } catch (Exception e) { log.warn("剧本输出解析失败,使用原始文本作为内容: {}", e.getMessage()); Map fallback = new HashMap<>(); fallback.put("title", "我的人生剧本"); fallback.put("plotIntro", output); return fallback; } } /** * 保存小说生成结果到数据库 * 创建 EpicScript、Conversation 和 3 条 Message 记录 * * @param currentUserId 当前用户 ID * @param theme 用户主题 * @param fullText 完整小说文本 * @param metadata 大纲元数据(标题等) * @return Map 包含 scriptId、conversationId、currentVersionMessageId */ public Map saveNovelResult(String currentUserId, String theme, String fullText, Map metadata) { String scriptId = snowflakeIdGenerator.nextIdAsString(); String conversationId = snowflakeIdGenerator.nextIdAsString(); LocalDateTime now = LocalDateTime.now(); // 1. 创建剧本 EpicScript script = new EpicScript(); script.setId(scriptId); script.setUserId(currentUserId); script.setTitle(metadata != null && metadata.get("title") != null ? (String) metadata.get("title") : "我的人生剧本"); script.setTheme(theme); script.setStyle("career"); script.setLength("medium"); script.setConversationId(conversationId); script.setPlotIntro(fullText); script.setPlotTurning(""); script.setPlotClimax(""); script.setPlotEnding(""); script.setPlotJson(metadata != null ? metadata : new HashMap<>()); script.setIsSelected(0); epicScriptService.save(script); // 2. 创建对话 Conversation conversation = new Conversation(); conversation.setId(conversationId); conversation.setUserId(currentUserId); conversation.setUserType("registered"); conversation.setScriptId(scriptId); conversation.setTitle(script.getTitle()); conversation.setType("script"); conversation.setConversationStatus("active"); conversation.setStartTime(now); conversation.setLastActiveTime(now); conversation.setMessageCount(3); conversationService.save(conversation); // 3. 系统欢迎消息(messageOrder = 1) Message systemWelcome = new Message(); systemWelcome.setId(snowflakeIdGenerator.nextIdAsString()); systemWelcome.setConversationId(conversationId); systemWelcome.setScriptId(scriptId); systemWelcome.setUserId(currentUserId); systemWelcome.setContent("欢迎来到《" + script.getTitle() + "》剧本世界。\n\n让我们一起探索你的心愿故事。"); systemWelcome.setType("system"); systemWelcome.setSender("system"); systemWelcome.setTimestamp(now); systemWelcome.setMessageOrder(1L); systemWelcome.setStatus("sent"); systemWelcome.setIsRead(1); messageService.createMessage(systemWelcome); // 4. 用户输入消息(messageOrder = 2) Message userMessage = new Message(); userMessage.setId(snowflakeIdGenerator.nextIdAsString()); userMessage.setConversationId(conversationId); userMessage.setScriptId(scriptId); userMessage.setUserId(currentUserId); userMessage.setContent(theme); userMessage.setType("chat"); userMessage.setSender("user"); userMessage.setTimestamp(now); userMessage.setMessageOrder(2L); userMessage.setStatus("sent"); userMessage.setIsRead(1); messageService.createMessage(userMessage); // 5. AI 小说全文消息(messageOrder = 3) Message aiMessage = new Message(); aiMessage.setId(snowflakeIdGenerator.nextIdAsString()); aiMessage.setConversationId(conversationId); aiMessage.setScriptId(scriptId); aiMessage.setUserId(currentUserId); aiMessage.setContent(fullText); aiMessage.setType("script"); aiMessage.setSender("assistant"); aiMessage.setTimestamp(now); aiMessage.setMessageOrder(3L); aiMessage.setVersionNumber(1); aiMessage.setStatus("sent"); aiMessage.setMetadata(metadata != null ? JSON.toJSONString(metadata) : "{}"); messageService.createMessage(aiMessage); // 6. 更新剧本和对话 script.setCurrentVersionMessageId(aiMessage.getId()); epicScriptService.updateById(script); conversation.setCurrentMessageId(aiMessage.getId()); conversationService.updateById(conversation); Map result = new HashMap<>(); result.put("scriptId", scriptId); result.put("conversationId", conversationId); result.put("currentVersionMessageId", aiMessage.getId()); return result; } private String buildDisplayContent(Map metadata) { StringBuilder sb = new StringBuilder(); appendSection(sb, metadata.get("title"), null); appendSection(sb, metadata.get("plotIntro"), "序幕:低谷回响"); appendSection(sb, metadata.get("plotTurning"), "转折:契机出现"); appendSection(sb, metadata.get("plotClimax"), "高潮:命运抉择"); appendSection(sb, metadata.get("plotEnding"), "结局:新的开始"); return sb.toString(); } private String buildSystemWelcome(Map metadata) { if (metadata == null || metadata.isEmpty()) { return "欢迎来到情绪博物馆,让我们一起探索你的内心世界。"; } String title = (String) metadata.getOrDefault("title", "我的人生剧本"); StringBuilder sb = new StringBuilder(); sb.append("欢迎来到《").append(title).append("》剧本世界。\n\n"); sb.append("在这里,你将体验一段关于"); sb.append(metadata.getOrDefault("plotIntro", "情感探索")).append("的旅程。"); return sb.toString(); } private void appendSection(StringBuilder sb, Object value, String label) { if (value == null) { return; } String text = String.valueOf(value); if (!StringUtils.hasText(text)) { return; } if (sb.length() > 0) { sb.append("\n\n"); } if (StringUtils.hasText(label)) { sb.append("【").append(label).append("】\n"); } sb.append(text); } }