package com.vincent.rsf.server.ai.service.impl.chat; import com.vincent.rsf.server.ai.dto.AiChatThinkingEventDto; import org.springframework.util.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.time.Instant; import java.util.Objects; public class AiThinkingTraceEmitter { private final AiSseEventPublisher aiSseEventPublisher; private final SseEmitter emitter; private final String requestId; private final Long sessionId; private String currentPhase; private String currentStatus; public AiThinkingTraceEmitter(AiSseEventPublisher aiSseEventPublisher, SseEmitter emitter, String requestId, Long sessionId) { this.aiSseEventPublisher = aiSseEventPublisher; this.emitter = emitter; this.requestId = requestId; this.sessionId = sessionId; } public void startAnalyze() { if (currentPhase != null) { return; } currentPhase = "ANALYZE"; currentStatus = "STARTED"; emitThinkingEvent("ANALYZE", "STARTED", "正在分析问题", "已接收你的问题,正在理解意图并判断是否需要调用工具。", null); } public void onToolStart(String toolName, String toolCallId) { switchPhase("TOOL_CALL", "STARTED", "正在调用工具", "已判断需要调用工具,正在查询相关信息。", null); currentStatus = "UPDATED"; emitThinkingEvent("TOOL_CALL", "UPDATED", "正在调用工具", "正在调用工具 " + safeLabel(toolName, "未知工具") + " 获取所需信息。", toolCallId); } public void onToolResult(String toolName, String toolCallId, boolean failed) { currentPhase = "TOOL_CALL"; currentStatus = failed ? "FAILED" : "UPDATED"; emitThinkingEvent("TOOL_CALL", failed ? "FAILED" : "UPDATED", failed ? "工具调用失败" : "工具调用完成", failed ? "工具 " + safeLabel(toolName, "未知工具") + " 调用失败,正在评估失败影响并整理可用信息。" : "工具 " + safeLabel(toolName, "未知工具") + " 已返回结果,正在继续分析并提炼关键信息。", toolCallId); } public void startAnswer() { switchPhase("ANSWER", "STARTED", "正在整理答案", "已完成分析,正在组织最终回复内容。", null); } public void completeCurrentPhase() { if (!StringUtils.hasText(currentPhase) || isTerminalStatus(currentStatus)) { return; } currentStatus = "COMPLETED"; emitThinkingEvent(currentPhase, "COMPLETED", resolveCompleteTitle(currentPhase), resolveCompleteContent(currentPhase), null); } public void markTerminated(String terminalStatus) { if (!StringUtils.hasText(currentPhase) || isTerminalStatus(currentStatus)) { return; } currentStatus = terminalStatus; emitThinkingEvent(currentPhase, terminalStatus, "ABORTED".equals(terminalStatus) ? "思考已中止" : "思考失败", "ABORTED".equals(terminalStatus) ? "本轮对话已被中止,思考过程提前结束。" : "本轮对话在生成答案前失败,当前思考过程已停止。", null); } private void switchPhase(String nextPhase, String nextStatus, String title, String content, String toolCallId) { if (!Objects.equals(currentPhase, nextPhase)) { completeCurrentPhase(); } currentPhase = nextPhase; currentStatus = nextStatus; emitThinkingEvent(nextPhase, nextStatus, title, content, toolCallId); } private void emitThinkingEvent(String phase, String status, String title, String content, String toolCallId) { aiSseEventPublisher.emitSafely(emitter, "thinking", AiChatThinkingEventDto.builder() .requestId(requestId) .sessionId(sessionId) .phase(phase) .status(status) .title(title) .content(content) .toolCallId(toolCallId) .timestamp(Instant.now().toEpochMilli()) .build()); } private boolean isTerminalStatus(String status) { return "COMPLETED".equals(status) || "FAILED".equals(status) || "ABORTED".equals(status); } private String resolveCompleteTitle(String phase) { if ("ANSWER".equals(phase)) { return "答案整理完成"; } if ("TOOL_CALL".equals(phase)) { return "工具分析完成"; } return "问题分析完成"; } private String resolveCompleteContent(String phase) { if ("ANSWER".equals(phase)) { return "最终答复已生成完成。"; } if ("TOOL_CALL".equals(phase)) { return "工具调用阶段已结束,相关信息已整理完毕。"; } return "问题意图和处理方向已分析完成。"; } private String safeLabel(String value, String fallback) { return StringUtils.hasText(value) ? value : fallback; } }