package com.vincent.rsf.server.ai.service.impl; import com.vincent.rsf.server.ai.config.AiDefaults; import com.vincent.rsf.server.ai.dto.AiChatMemoryDto; import com.vincent.rsf.server.ai.dto.AiChatModelOptionDto; import com.vincent.rsf.server.ai.dto.AiChatRequest; import com.vincent.rsf.server.ai.dto.AiChatRuntimeDto; import com.vincent.rsf.server.ai.dto.AiChatSessionDto; import com.vincent.rsf.server.ai.dto.AiChatSessionPinRequest; import com.vincent.rsf.server.ai.dto.AiChatSessionRenameRequest; import com.vincent.rsf.server.ai.dto.AiResolvedConfig; import com.vincent.rsf.server.ai.service.AiChatMemoryService; import com.vincent.rsf.server.ai.service.AiChatService; import com.vincent.rsf.server.ai.service.AiConfigResolverService; import com.vincent.rsf.server.ai.service.AiParamService; import com.vincent.rsf.server.ai.service.impl.chat.AiChatOrchestrator; import com.vincent.rsf.server.ai.service.impl.chat.AiChatRuntimeAssembler; import com.vincent.rsf.server.ai.store.AiConversationCacheStore; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.List; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @Service @RequiredArgsConstructor public class AiChatServiceImpl implements AiChatService { private final AiConfigResolverService aiConfigResolverService; private final AiChatMemoryService aiChatMemoryService; private final AiParamService aiParamService; private final AiConversationCacheStore aiConversationCacheStore; private final AiChatRuntimeAssembler aiChatRuntimeAssembler; private final AiChatOrchestrator aiChatOrchestrator; @Qualifier("aiChatTaskExecutor") private final Executor aiChatTaskExecutor; @Override public AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long aiParamId, Long userId, Long tenantId) { AiResolvedConfig config = aiConfigResolverService.resolve(promptCode, tenantId, aiParamId); AiChatRuntimeDto cached = aiConversationCacheStore.getRuntime(tenantId, userId, config.getPromptCode(), sessionId, aiParamId); if (cached != null) { return cached; } AiChatMemoryDto memory = aiChatMemoryService.getMemory(userId, tenantId, config.getPromptCode(), sessionId); List modelOptions = aiParamService.listChatModelOptions(tenantId); AiChatRuntimeDto runtime = aiChatRuntimeAssembler.buildRuntimeSnapshot( null, memory.getSessionId(), config, modelOptions, config.getMcpMounts().size(), config.getMcpMounts().stream().map(item -> item.getName()).toList(), List.of(), memory ); aiConversationCacheStore.cacheRuntime(tenantId, userId, config.getPromptCode(), sessionId, aiParamId, runtime); if (memory.getSessionId() != null && !Objects.equals(memory.getSessionId(), sessionId)) { aiConversationCacheStore.cacheRuntime(tenantId, userId, config.getPromptCode(), memory.getSessionId(), aiParamId, runtime); } return runtime; } @Override public List listSessions(String promptCode, String keyword, Long userId, Long tenantId) { AiResolvedConfig config = aiConfigResolverService.resolve(promptCode, tenantId); return aiChatMemoryService.listSessions(userId, tenantId, config.getPromptCode(), keyword); } @Override public SseEmitter stream(AiChatRequest request, Long userId, Long tenantId) { SseEmitter emitter = new SseEmitter(AiDefaults.SSE_TIMEOUT_MS); CompletableFuture.runAsync(() -> aiChatOrchestrator.executeStream(request, userId, tenantId, emitter), aiChatTaskExecutor); return emitter; } @Override public void removeSession(Long sessionId, Long userId, Long tenantId) { aiChatMemoryService.removeSession(userId, tenantId, sessionId); } @Override public AiChatSessionDto renameSession(Long sessionId, AiChatSessionRenameRequest request, Long userId, Long tenantId) { return aiChatMemoryService.renameSession(userId, tenantId, sessionId, request); } @Override public AiChatSessionDto pinSession(Long sessionId, AiChatSessionPinRequest request, Long userId, Long tenantId) { return aiChatMemoryService.pinSession(userId, tenantId, sessionId, request); } @Override public void clearSessionMemory(Long sessionId, Long userId, Long tenantId) { aiChatMemoryService.clearSessionMemory(userId, tenantId, sessionId); } @Override public void retainLatestRound(Long sessionId, Long userId, Long tenantId) { aiChatMemoryService.retainLatestRound(userId, tenantId, sessionId); } }