diff --git a/CustomerSupportChatBot/build.gradle b/CustomerSupportChatBot/build.gradle new file mode 100644 index 0000000..59e21ec --- /dev/null +++ b/CustomerSupportChatBot/build.gradle @@ -0,0 +1,31 @@ +import org.springframework.boot.gradle.plugin.SpringBootPlugin + +apply plugin: 'java' +apply plugin: 'org.springframework.boot' +apply plugin: 'io.spring.dependency-management' + +description = "Customer Support ChatBot using Google Gemini AI" + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +repositories { + mavenCentral() +} + +dependencyManagement { + imports { + mavenBom SpringBootPlugin.BOM_COORDINATES + } +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework:spring-webflux' + implementation "me.paulschwarz:spring-dotenv:4.0.0" + + runtimeOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/CustomerSupportChatbotApplication.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/CustomerSupportChatbotApplication.java new file mode 100644 index 0000000..362006d --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/CustomerSupportChatbotApplication.java @@ -0,0 +1,13 @@ +package com.example.customersupportchatbot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class CustomerSupportChatbotApplication { + + public static void main(String[] args) { + SpringApplication.run(CustomerSupportChatbotApplication.class, args); + } + +} diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/ChatbotController.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/ChatbotController.java new file mode 100644 index 0000000..bdac2fe --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/ChatbotController.java @@ -0,0 +1,44 @@ +package com.example.customersupportchatbot.controller; + +import com.example.customersupportchatbot.model.ChatRequest; +import com.example.customersupportchatbot.model.ChatResponse; +import com.example.customersupportchatbot.model.EnhancedChatResponse; +import com.example.customersupportchatbot.service.GeminiAIService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +@RequestMapping("/api/chatbot") +public class ChatbotController { + + private final GeminiAIService geminiAIService; + + @Autowired + public ChatbotController(GeminiAIService geminiAIService) { + this.geminiAIService = geminiAIService; + } + + @PostMapping("/chat") + public Mono chat(@RequestBody ChatRequest request) { + if (request.getQuery() == null || request.getQuery().trim().isEmpty()) { + return Mono.just(new ChatResponse("Please provide a valid question or message.")); + } + return geminiAIService + .generateResponse(request.getQuery()) + .map(ChatResponse::new); + } + + @PostMapping("/enhanced-chat") + public Mono enhancedChat(@RequestBody ChatRequest request) { + if (request.getQuery() == null || request.getQuery().trim().isEmpty()) { + return Mono.just(new EnhancedChatResponse("Please provide a valid question or message.", "error")); + } + return geminiAIService + .generateResponseWithCategory(request.getQuery()) + .map(result -> new EnhancedChatResponse(result.get("response"), result.get("category"))); + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/HealthController.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/HealthController.java new file mode 100644 index 0000000..fa063c7 --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/controller/HealthController.java @@ -0,0 +1,26 @@ +package com.example.customersupportchatbot.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/health") +public class HealthController { + + // Constructor injection of our service + @Autowired + public HealthController() { + } + + /** + * Simple health check endpoint to verify the API is running + * + * @return A message indicating the API is up and running + */ + @GetMapping(value = {"", "/"}) + public String health() { + return "Chatbot API is up and running!"; + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatRequest.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatRequest.java new file mode 100644 index 0000000..ad573ab --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatRequest.java @@ -0,0 +1,19 @@ +package com.example.customersupportchatbot.model; + +public class ChatRequest { + private String query; + + public ChatRequest() {} + + public ChatRequest(String query) { + this.query = query; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatResponse.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatResponse.java new file mode 100644 index 0000000..940c5e5 --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/ChatResponse.java @@ -0,0 +1,19 @@ +package com.example.customersupportchatbot.model; + +public class ChatResponse { + private String response; + + public ChatResponse() {} + + public ChatResponse(String response) { + this.response = response; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/EnhancedChatResponse.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/EnhancedChatResponse.java new file mode 100644 index 0000000..0044497 --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/EnhancedChatResponse.java @@ -0,0 +1,29 @@ +package com.example.customersupportchatbot.model; + +public class EnhancedChatResponse { + private String response; + private String category; + + public EnhancedChatResponse() {} + + public EnhancedChatResponse(String response, String category) { + this.response = response; + this.category = category; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiRequest.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiRequest.java new file mode 100644 index 0000000..66a67fb --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiRequest.java @@ -0,0 +1,57 @@ +package com.example.customersupportchatbot.model; + +import java.util.List; + +public class GeminiRequest { + private List contents; + + public GeminiRequest() {} + + 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() {} + + 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() {} + + public Part(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiResponse.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiResponse.java new file mode 100644 index 0000000..fd6ebc4 --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/model/GeminiResponse.java @@ -0,0 +1,75 @@ +package com.example.customersupportchatbot.model; + +import java.util.List; + +public class GeminiResponse { + private List candidates; + + public GeminiResponse() {} + + public GeminiResponse(List candidates) { + this.candidates = candidates; + } + + public List getCandidates() { + return candidates; + } + + public void setCandidates(List candidates) { + this.candidates = candidates; + } + + public static class Candidate { + private Content content; + + public Candidate() {} + + public Candidate(Content content) { + this.content = content; + } + + public Content getContent() { + return content; + } + + public void setContent(Content content) { + this.content = content; + } + } + + public static class Content { + private List parts; + + public Content() { } + + 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() { } + + public Part(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/service/GeminiAIService.java b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/service/GeminiAIService.java new file mode 100644 index 0000000..c559d55 --- /dev/null +++ b/CustomerSupportChatBot/src/main/java/com/example/customersupportchatbot/service/GeminiAIService.java @@ -0,0 +1,200 @@ +package com.example.customersupportchatbot.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class GeminiAIService { + + private static final Logger log = LoggerFactory.getLogger(GeminiAIService.class); + + private static final String fullPrompt = """ + You are a helpful customer support chatbot. + Provide a concise and friendly response to this customer query: + + %s + """; + + private static final String fullPromptEnhanced = """ + You are a helpful customer support chatbot. + Provide a response to this customer query: + + %s + + Also classify this query into exactly ONE of these categories: + - account + - billing + - technical + - general + + Format your response as follows: + + CATEGORY: [the category] + RESPONSE: [your helpful response] + """; + + private final WebClient webClient; + private final String apiKey; + private final String apiUrl; + + public GeminiAIService(@Value("${gemini.api.url}") String url, @Value("${gemini.api.key}") String apiKey) { + this.apiKey = apiKey; + this.apiUrl = url; + this.webClient = WebClient.builder() + .baseUrl(url + "?key={key}") + .defaultUriVariables(Map.of("key", apiKey)) + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + } + + /** + * Generates a response using the Gemini API + * + * @param query The user's query + * @return The response from the model + */ + public Mono generateResponse(String query) { + + // Create the request body with proper structure + Map partObject = new HashMap<>(); + partObject.put("text", fullPrompt.formatted(query)); + + Map contentObject = new HashMap<>(); + contentObject.put("parts", List.of(partObject)); + contentObject.put("role", "user"); + + Map requestBody = new HashMap<>(); + requestBody.put("contents", List.of(contentObject)); + + // Debug: Print the request being sent + log.info("Sending request to Gemini API:"); + log.info("URL: {}?key={}...", apiUrl, apiKey.substring(0, 5)); + log.info("Request body: {}", requestBody); + + // Make the API request with more detailed error handling + return webClient.post() + .bodyValue(requestBody) + .retrieve() + .bodyToMono(Map.class) + .map(response -> { + + // Debug: Print response structure + log.info("Response received: {}", response); + + // Extract the text from the response + if (response != null && response.containsKey("candidates")) { + List> candidates = (List>) response.get("candidates"); + if (!candidates.isEmpty()) { + Map content = (Map) candidates.get(0).get("content"); + List> responseParts = (List>) content.get("parts"); + if (!responseParts.isEmpty()) { + return (String) responseParts.get(0).get("text"); + } + } + } + + return "I'm sorry, I couldn't process your request."; + }) + + .onErrorResume(WebClientResponseException.class, e -> { + // Handle HTTP error responses specifically + log.error("Error calling Gemini API: {} {}", e.getStatusCode(), e.getStatusText()); + log.error("Response body: {}", e.getResponseBodyAsString()); + + // Return a more informative error message + if (e.getStatusCode().value() == 400) { + return Mono.just("I'm sorry, there was an error with the request format (400 Bad Request). Please ensure your API key is correct and try again."); + } else if (e.getStatusCode().value() == 401 || e.getStatusCode().value() == 403) { + return Mono.just("I'm sorry, there was an authentication error. Please check your API key."); + } else if (e.getStatusCode().value() == 429) { + return Mono.just("I'm sorry, we've hit the rate limit for the Gemini API. Please try again in a minute."); + } else { + return Mono.just("I'm sorry, there was an error calling the Gemini API: " + e.getStatusCode() + " " + e.getStatusText()); + } + + }) + + .onErrorResume(Exception.class, e -> { + // General error handling + log.error("Unexpected error calling Gemini API: {}", e.getMessage(), e); + return Mono.just("I'm sorry, there was an unexpected error processing your request: " + e.getMessage()); + }); + } + + public Mono> generateResponseWithCategory(String query) { + + // Create the request body with proper structure (same as before) + Map partObject = new HashMap<>(); + partObject.put("text", fullPrompt.formatted(query)); + + Map contentObject = new HashMap<>(); + contentObject.put("parts", List.of(partObject)); + contentObject.put("role", "user"); + + Map requestBody = new HashMap<>(); + requestBody.put("contents", List.of(contentObject)); + + // Make the API request (same as before) + return webClient.post() + .bodyValue(requestBody) + .retrieve() + .bodyToMono(Map.class) + .map(response -> { + + // Extract the text from the response + String responseText = ""; + if (response != null && response.containsKey("candidates")) { + List> candidates = (List>) response.get("candidates"); + if (!candidates.isEmpty()) { + Map content = (Map) candidates.get(0).get("content"); + List> responseParts = (List>) content.get("parts"); + if (!responseParts.isEmpty()) { + responseText = (String) responseParts.get(0).get("text"); + } + } + } + + // Parse the category and response from the text + Map result = new HashMap<>(); + + if (responseText.contains("CATEGORY:") && responseText.contains("RESPONSE:")) { + String category = responseText + .substring(responseText.indexOf("CATEGORY:") + 9, responseText.indexOf("RESPONSE:")) + .trim(); + + String chatResponse = responseText + .substring(responseText.indexOf("RESPONSE:") + 9) + .trim(); + + result.put("category", category); + result.put("response", chatResponse); + } else { + // Fallback if the format is not as expected + result.put("category", "general"); + result.put("response", responseText); + } + + return result; + + }) + + .onErrorResume(e -> { + // Handle errors (same as before) + Map errorResult = new HashMap<>(); + errorResult.put("category", "error"); + errorResult.put("response", "I'm sorry, there was an error processing your request: " + e.getMessage()); + return Mono.just(errorResult); + }); + } +} \ No newline at end of file diff --git a/CustomerSupportChatBot/src/main/resources/application.properties b/CustomerSupportChatBot/src/main/resources/application.properties new file mode 100644 index 0000000..04f89bc --- /dev/null +++ b/CustomerSupportChatBot/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.application.name=customer-support-chatbot +server.port=8080 + +# Gemini API configuration +gemini.api.key=${GEMINI_API_KEY} +gemini.api.url=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent +logging.level.org.springframework.web.reactive.function.client=DEBUG \ No newline at end of file diff --git a/CustomerSupportChatBot/src/test/java/com/example/customersupportchatbot/CustomerSupportChatbotApplicationTests.java b/CustomerSupportChatBot/src/test/java/com/example/customersupportchatbot/CustomerSupportChatbotApplicationTests.java new file mode 100644 index 0000000..adf6225 --- /dev/null +++ b/CustomerSupportChatBot/src/test/java/com/example/customersupportchatbot/CustomerSupportChatbotApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.customersupportchatbot; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class CustomerSupportChatbotApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/settings.gradle b/settings.gradle index bf28a8f..489d279 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,4 +9,5 @@ include 'SmartClinicManagementSystem:app' include 'SoftwareDevChatbot' include 'RegressionPredictionLab' include 'SentimentAnalysisLab' -include 'ImageRecognitionLab' \ No newline at end of file +include 'ImageRecognitionLab' +include 'CustomerSupportChatBot' \ No newline at end of file