package com.example.p1.service; import com.example.p1.config.AiConfiguration; import com.example.p1.controller.ChatController; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.time.Duration; import java.util.List; import java.util.stream.Collectors; @Service public class ChatService { private static final Logger logger = LoggerFactory.getLogger(ChatService.class); private final WebClient webClient; private final AiConfiguration config; public ChatService(WebClient geminiWebClient, AiConfiguration config) { this.webClient = geminiWebClient; this.config = config; } public Mono generateResponse(String message) { logger.debug("Generating response for message: {}", message); GeminiRequest request = createSimpleRequest(message); return callGeminiAPI(request) .map(this::extractTextFromResponse) .doOnSuccess(response -> logger.debug("Generated response: {}", response)) .doOnError(error -> logger.error("Error generating response", error)); } public Mono generateConversationResponse(List messages) { logger.debug("Generating conversation response for {} messages", messages.size()); String conversationContext = buildConversationContext(messages); return generateResponse(conversationContext); } public Flux generateStreamingResponse(String message) { // For demonstration - in real implementation, you'd use Gemini's streaming API return generateResponse(message) .flatMapMany(response -> { String[] words = response.split("\\s+"); return Flux.fromArray(words) .delayElements(Duration.ofMillis(100)) .map(word -> word + " "); }); } private Mono callGeminiAPI(GeminiRequest request) { return webClient.post() .uri("/{model}:generateContent?key={apiKey}", config.getModel(), config.getKey()) .bodyValue(request) .retrieve() .bodyToMono(GeminiResponse.class) .timeout(Duration.ofMillis(config.getTimeout())); } private GeminiRequest createSimpleRequest(String message) { GeminiRequest.Part part = new GeminiRequest.Part(message); GeminiRequest.Content content = new GeminiRequest.Content(List.of(part)); return new GeminiRequest(List.of(content)); } private String buildConversationContext(List messages) { StringBuilder context = new StringBuilder(); context.append("Previous conversation:\n"); for (ChatController.ConversationRequest.Message msg : messages) { context.append(msg.getRole().toUpperCase()) .append(": ") .append(msg.getContent()) .append("\n"); } return context.toString(); } private String extractTextFromResponse(GeminiResponse response) { if (response.getCandidates() != null && !response.getCandidates().isEmpty()) { GeminiResponse.Candidate candidate = response.getCandidates().get(0); if (candidate.getContent() != null && candidate.getContent().getParts() != null && !candidate.getContent().getParts().isEmpty()) { return candidate.getContent().getParts().get(0).getText(); } } return "No response generated"; } // Gemini API DTOs public static class GeminiRequest { private List contents; public GeminiRequest(List contents) { this.contents = contents; } public List getContents() { return contents; } public void setContents(List contents) { this.contents = contents; } public static class Content { private List parts; public Content(List parts) { this.parts = parts; } public List getParts() { return parts; } public void setParts(List parts) { this.parts = parts; } } public static class Part { private String text; public Part(String text) { this.text = text; } public String getText() { return text; } public void setText(String text) { this.text = text; } } } @JsonIgnoreProperties(ignoreUnknown = true) public static class GeminiResponse { private List candidates; public List getCandidates() { return candidates; } public void setCandidates(List candidates) { this.candidates = candidates; } @JsonIgnoreProperties(ignoreUnknown = true) public static class Candidate { private Content content; public Content getContent() { return content; } public void setContent(Content content) { this.content = content; } } @JsonIgnoreProperties(ignoreUnknown = true) public static class Content { private List parts; public List getParts() { return parts; } public void setParts(List parts) { this.parts = parts; } } @JsonIgnoreProperties(ignoreUnknown = true) public static class Part { private String text; public String getText() { return text; } public void setText(String text) { this.text = text; } } } }