feat: 新增 EpicScriptDialogueService 接口

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 17:15:47 +08:00
parent 88d586bab1
commit 56369fdeb2
2 changed files with 247 additions and 0 deletions
@@ -0,0 +1,15 @@
package com.emotion.service;
import com.emotion.dto.request.EpicScriptCreateWithDialogueRequest;
import com.emotion.dto.response.EpicScriptCreateWithDialogueResponse;
/**
* 剧本对话创建服务
*/
public interface EpicScriptDialogueService {
/**
* 创建剧本并创建对话,生成第一条 AI 剧本消息
*/
EpicScriptCreateWithDialogueResponse createWithDialogue(EpicScriptCreateWithDialogueRequest request);
}
@@ -0,0 +1,232 @@
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(buildTitle(request.getTheme()));
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. 保存用户消息
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(1L);
userMessage.setStatus("sent");
userMessage.setIsRead(1);
messageService.createMessage(userMessage);
// 4. 调用 AI 生成完整剧本
String aiOutput = invokeScriptGenerate(request, currentUserId, conversationId, userMessage.getId());
// 5. 解析并保存 AI 剧本消息
Map<String, Object> metadata = parseScriptOutput(aiOutput);
String content = buildDisplayContent(metadata);
Message aiMessage = new Message();
aiMessage.setId(snowflakeIdGenerator.nextIdAsString());
aiMessage.setConversationId(conversationId);
aiMessage.setScriptId(scriptId);
aiMessage.setUserId(currentUserId);
aiMessage.setContent(content);
aiMessage.setType("script");
aiMessage.setSender("assistant");
aiMessage.setTimestamp(LocalDateTime.now());
aiMessage.setMessageOrder(2L);
aiMessage.setVersionNumber(1);
aiMessage.setStatus("sent");
aiMessage.setMetadata(JSON.toJSONString(metadata));
messageService.createMessage(aiMessage);
// 6. 更新剧本快照
script.setCurrentVersionMessageId(aiMessage.getId());
script.setTitle((String) metadata.getOrDefault("title", script.getTitle()));
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<String, Object> plotJson = script.getPlotJson();
plotJson.putAll(metadata);
script.setPlotJson(plotJson);
epicScriptService.updateById(script);
// 7. 更新对话当前消息
conversation.setCurrentMessageId(aiMessage.getId());
conversation.setMessageCount(2);
conversationService.updateById(conversation);
// 8. 构建响应
EpicScriptCreateWithDialogueResponse response = new EpicScriptCreateWithDialogueResponse();
BeanUtils.copyProperties(script, response);
response.setScriptId(scriptId);
response.setConversationId(conversationId);
response.setCurrentVersionMessageId(aiMessage.getId());
return response;
}
private String buildTitle(String theme) {
if (!StringUtils.hasText(theme)) {
return "我的人生剧本";
}
return theme.length() > 20 ? theme.substring(0, 20) + "..." : theme;
}
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<String, Object> 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<String, Object> fallback = new HashMap<>();
fallback.put("title", "我的人生剧本");
fallback.put("plotIntro", output);
return fallback;
}
}
private String buildDisplayContent(Map<String, Object> 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 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);
}
}