-
Notifications
You must be signed in to change notification settings - Fork 0
AI_Recommendations #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
57a94c0
73e732d
69eb71a
26dd449
41c029d
67bed4f
36509c4
4108fd9
d14fbcd
ee999f7
24435f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package org.example.springboot25.controller; | ||
|
|
||
| import org.example.springboot25.service.AiRecommendationService; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/ai-recommendations") | ||
| public class AiRecommendationRestController { | ||
|
|
||
| private final AiRecommendationService aiRecommendationService; | ||
|
|
||
| public AiRecommendationRestController(AiRecommendationService aiRecommendationService) { | ||
| this.aiRecommendationService = aiRecommendationService; | ||
| } | ||
|
|
||
| // Return recommendation based on cat breed with structured JSON and error handling | ||
| @GetMapping("/{breed}") | ||
| public ResponseEntity<?> getRecommendation(@PathVariable String breed) { | ||
| String recommendation = aiRecommendationService.getRecommendationForBreed(breed); | ||
|
|
||
| // If the breed is not recognized, return error message with 404 | ||
| if (recommendation.contains("Unknown breed")) { | ||
| Map<String, String> errorResponse = new HashMap<>(); | ||
| errorResponse.put("error", "Unknown cat breed"); | ||
| errorResponse.put("message", recommendation); | ||
| return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); | ||
| } | ||
|
|
||
| // Success: return breed and recommendation | ||
| Map<String, String> successResponse = new HashMap<>(); | ||
| successResponse.put("breed", breed); | ||
| successResponse.put("recommendation", recommendation); | ||
| return ResponseEntity.ok(successResponse); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.example.springboot25.controller; | ||
|
|
||
| import org.example.springboot25.service.AiRecommendationService; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.ui.Model; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @Controller | ||
| @RequestMapping("/ai-recommendations") | ||
| public class AiRecommendationViewController { | ||
|
|
||
| private final AiRecommendationService aiRecommendationService; | ||
|
|
||
| public AiRecommendationViewController(AiRecommendationService aiRecommendationService) { | ||
| this.aiRecommendationService = aiRecommendationService; | ||
| } | ||
|
|
||
| // Displays the form where the user can input the cat breed | ||
| @GetMapping | ||
| public String showForm() { | ||
| return "ai_recommendation_form"; | ||
| } | ||
|
|
||
| @PostMapping | ||
| public String showResult(@RequestParam String breed, Model model) { | ||
| // Validate input: ensure the breed is not null or empty | ||
| if (breed == null || breed.trim().isEmpty()) { | ||
| model.addAttribute("error", "Please enter a breed name"); | ||
| return "ai_recommendation_form"; | ||
| } | ||
|
|
||
| breed = breed.trim(); | ||
| String recommendation = aiRecommendationService.getRecommendationForBreed(breed); | ||
| model.addAttribute("breed", breed); | ||
| model.addAttribute("recommendation", recommendation); | ||
| return "ai_recommendation_result"; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| package org.example.springboot25.service; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.*; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.web.client.RestTemplate; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @Service | ||
| public class AiRecommendationService { | ||
|
|
||
| @Value("${openai.api.key}") | ||
| private String apiKey; | ||
|
LinusAltemark marked this conversation as resolved.
|
||
|
|
||
| private final RestTemplate restTemplate = new RestTemplate(); | ||
| private static final String OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"; | ||
|
|
||
| public String getRecommendationForBreed(String breed) { | ||
| if (breed == null || breed.trim().isEmpty()) { | ||
| return "Unknown breed. Please enter a valid cat breed."; | ||
| } | ||
|
|
||
| breed = breed.trim(); | ||
|
|
||
|
LinusAltemark marked this conversation as resolved.
|
||
| try { | ||
| // Prepare the OpenAI API request | ||
| HttpHeaders headers = new HttpHeaders(); | ||
| headers.setContentType(MediaType.APPLICATION_JSON); | ||
| headers.setBearerAuth(apiKey); | ||
|
|
||
| Map<String, Object> message = new HashMap<>(); | ||
| message.put("role", "user"); | ||
| message.put("content", "Give me a short but useful care tip for the cat breed: " + breed); | ||
|
|
||
| Map<String, Object> body = new HashMap<>(); | ||
| body.put("model", "gpt-3.5-turbo"); | ||
| body.put("messages", new Object[]{message}); | ||
| body.put("max_tokens", 50); | ||
|
|
||
| HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers); | ||
|
|
||
| ResponseEntity<Map> response = restTemplate.exchange( | ||
| OPENAI_API_URL, | ||
| HttpMethod.POST, | ||
| entity, | ||
| Map.class | ||
| ); | ||
|
|
||
| if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) { | ||
| Map<String, Object> choice = ((java.util.List<Map<String, Object>>) response.getBody().get("choices")).get(0); | ||
| Map<String, Object> messageContent = (Map<String, Object>) choice.get("message"); | ||
| return (String) messageContent.get("content"); | ||
| } else { | ||
| return "Something went wrong while getting recommendation. Try again later."; | ||
| } | ||
|
Comment on lines
+51
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve response handling with proper model classes The current response parsing uses multiple casts and map operations which is error-prone. Create proper model classes for the OpenAI API response to make the code more maintainable and type-safe. - if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
- Map<String, Object> choice = ((java.util.List<Map<String, Object>>) response.getBody().get("choices")).get(0);
- Map<String, Object> messageContent = (Map<String, Object>) choice.get("message");
- return (String) messageContent.get("content");
- } else {
- return "Something went wrong while getting recommendation. Try again later.";
- }
+ // Create response model classes
+ if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
+ try {
+ OpenAiResponse openAiResponse = objectMapper.convertValue(response.getBody(), OpenAiResponse.class);
+ if (openAiResponse.getChoices() != null && !openAiResponse.getChoices().isEmpty()) {
+ return openAiResponse.getChoices().get(0).getMessage().getContent();
+ }
+ } catch (Exception e) {
+ logger.error("Error parsing OpenAI response", e);
+ }
+ }
+ return "Something went wrong while getting recommendation. Try again later.";And define these model classes: public class OpenAiResponse {
private List<OpenAiChoice> choices;
// getters, setters
}
public class OpenAiChoice {
private OpenAiMessage message;
// getters, setters
}
public class OpenAiMessage {
private String role;
private String content;
// getters, setters
} |
||
|
|
||
| } catch (Exception e) { | ||
| return "Error calling AI service: " + e.getMessage(); | ||
| } | ||
|
Comment on lines
+59
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add logging for exceptions and sanitize error messages The current exception handling includes the raw exception message in the response, which could expose sensitive information and doesn't provide logging for debugging. Add proper logging and sanitize error messages: + import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
@Service
public class AiRecommendationService {
+ private static final Logger logger = LoggerFactory.getLogger(AiRecommendationService.class);
// Other code...
} catch (Exception e) {
+ logger.error("Error calling OpenAI API for breed {}: {}", breed, e.getMessage(), e);
- return "Error calling AI service: " + e.getMessage();
+ return "Error calling AI service. Please try again later.";
} |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <!DOCTYPE html> | ||
| <html xmlns:th="http://www.thymeleaf.org"> | ||
| <head> | ||
| <title>AI Recommendation</title> | ||
| </head> | ||
| <body> | ||
| <h1>Cat AI Recommendation</h1> | ||
| <form action="#" th:action="@{/ai-recommendations}" method="post"> | ||
| <label for="breed">Cat Breed:</label> | ||
| <input type="text" id="breed" name="breed" required> | ||
| <button type="submit">Get Recommendation</button> | ||
| </form> | ||
|
Comment on lines
+8
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve form usability with a dropdown menu instead of text input Since you have a fixed set of cat breeds defined in the CatBreed enum, a dropdown would be more user-friendly than free text input, preventing invalid entries. <form action="#" th:action="@{/ai-recommendations}" method="post">
<label for="breed">Cat Breed:</label>
- <input type="text" id="breed" name="breed" required>
+ <select id="breed" name="breed" required>
+ <option value="">-- Select a breed --</option>
+ <option th:each="breedOption : ${T(org.example.springboot25.entities.CatBreed).values()}"
+ th:value="${breedOption.name()}"
+ th:text="${breedOption.name().replace('_', ' ')}">
+ </option>
+ </select>
<button type="submit">Get Recommendation</button>
</form>This requires passing the CatBreed enum to the template from your controller: @GetMapping
public String showForm(Model model) {
return "ai_recommendation/ai_recommendation_form";
} |
||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,11 @@ | ||||||
| <!DOCTYPE html> | ||||||
| <html xmlns:th="http://www.thymeleaf.org"> | ||||||
| <head> | ||||||
| <title>Recommendation Result</title> | ||||||
| </head> | ||||||
| <body> | ||||||
| <h2>Recommendation for <span th:text="${breed}"></span>:</h2> | ||||||
| <p th:text="${recommendation}"></p> | ||||||
| <a href="/ai-recommendations">Try another</a> | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Use Thymeleaf for the back link Using a direct link may cause issues if the application context path changes. Replace it with a Thymeleaf link. -<a href="/ai-recommendations">Try another</a>
+<a th:href="@{/ai-recommendations}">Try another</a>📝 Committable suggestion
Suggested change
|
||||||
| </body> | ||||||
| </html> | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package org.example.springboot25.service; | ||
|
|
||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
| import static org.assertj.core.api.Assertions.*; | ||
|
|
||
| public class AiRecommendationServiceTest { | ||
|
|
||
| private AiRecommendationService aiRecommendationService; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| aiRecommendationService = new AiRecommendationService(); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldReturnCorrectRecommendationForValidBreed() { | ||
| String result = aiRecommendationService.getRecommendationForBreed("PERSIAN"); | ||
| assertThat(result).contains("Groom daily"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldReturnCorrectRecommendationForBreedWithSpaces() { | ||
| String result = aiRecommendationService.getRecommendationForBreed("american shorthair"); | ||
| assertThat(result).contains("Easy-going"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldReturnCorrectRecommendationForLowercaseInput() { | ||
| String result = aiRecommendationService.getRecommendationForBreed("siamese"); | ||
| assertThat(result).contains("Talkative"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldHandleUnknownBreedGracefully() { | ||
| String result = aiRecommendationService.getRecommendationForBreed("fluffybean"); | ||
| assertThat(result).contains("Unknown breed"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldHandleEmptyStringInput() { | ||
| String result = aiRecommendationService.getRecommendationForBreed(""); | ||
| assertThat(result).contains("Unknown breed"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldHandleWhitespaceOnlyInput() { | ||
| String result = aiRecommendationService.getRecommendationForBreed(" "); | ||
| assertThat(result).contains("Unknown breed"); | ||
| } | ||
|
|
||
| @Test | ||
| void shouldHandleNullInput() { | ||
| String result = aiRecommendationService.getRecommendationForBreed(null); | ||
| assertThat(result).contains("Unknown breed"); | ||
| } | ||
| } | ||
|
|
Uh oh!
There was an error while loading. Please reload this page.