Modernize Feedback service

This commit is contained in:
2025-11-13 13:05:46 +01:00
parent b43ec0856b
commit 98483338b4
9 changed files with 172 additions and 184 deletions

View File

@@ -37,4 +37,11 @@ dependencies {
runtimeOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
compileOnly("org.projectlombok:lombok:1.18.42")
annotationProcessor("org.projectlombok:lombok:1.18.42")
testCompileOnly("org.projectlombok:lombok:1.18.42")
testAnnotationProcessor("org.projectlombok:lombok:1.18.42")
} }

View File

@@ -0,0 +1,51 @@
package com.retailstore.feedback;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
/**
* Thread pool executor that allows for pausing and resuming tasks
*/
public class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private final ReentrantLock pauseLock = new ReentrantLock();
private final Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingDeque<>());
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) {
unpaused.await();
}
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}

View File

@@ -1,5 +1,7 @@
package com.retailstore.feedback; package com.retailstore.feedback;
import com.retailstore.feedback.model.FeedbackEntry;
import com.retailstore.feedback.model.FeedbackEntry.FeedbackEntryBuilder;
import edu.stanford.nlp.pipeline.CoreDocument; import edu.stanford.nlp.pipeline.CoreDocument;
import edu.stanford.nlp.pipeline.CoreSentence; import edu.stanford.nlp.pipeline.CoreSentence;
import edu.stanford.nlp.pipeline.StanfordCoreNLP; import edu.stanford.nlp.pipeline.StanfordCoreNLP;
@@ -117,9 +119,8 @@ public class SentimentAnalyzer {
if (feedbackEntry != null) { if (feedbackEntry != null) {
// Analyze sentiment // Analyze sentiment
log.info("Analyzing sentiment for entry #{}", feedbackEntry.getId()); log.info("Analyzing sentiment for entry #{}", feedbackEntry.getId());
String sentiment = analyzeSentiment(feedbackEntry.comment); String sentiment = analyzeSentiment(feedbackEntry.getComment());
feedbackEntry.setSentiment(sentiment); feedbackEntry = feedbackEntry.withSentiment(sentiment);
feedbackEntries.add(feedbackEntry); feedbackEntries.add(feedbackEntry);
} else { } else {
log.error("Failed to parse entry at position {}", i); log.error("Failed to parse entry at position {}", i);
@@ -136,7 +137,7 @@ public class SentimentAnalyzer {
* @return FeedbackEntry object * @return FeedbackEntry object
*/ */
private FeedbackEntry parseEntry(String entryText) { private FeedbackEntry parseEntry(String entryText) {
FeedbackEntry entry = new FeedbackEntry(); FeedbackEntryBuilder entryBuilder = FeedbackEntry.builder();
try { try {
// Split into lines for easier parsing // Split into lines for easier parsing
@@ -150,7 +151,7 @@ public class SentimentAnalyzer {
if (line.startsWith("Feedback #")) { if (line.startsWith("Feedback #")) {
Matcher numberMatcher = FEEDBACK_NUMBER_PATTERN.matcher(line); Matcher numberMatcher = FEEDBACK_NUMBER_PATTERN.matcher(line);
if (numberMatcher.find()) { if (numberMatcher.find()) {
entry.setId(Integer.parseInt(numberMatcher.group(1))); entryBuilder.id(Integer.parseInt(numberMatcher.group(1)));
} }
} }
@@ -158,7 +159,7 @@ public class SentimentAnalyzer {
else if (line.startsWith("Customer:")) { else if (line.startsWith("Customer:")) {
Matcher customerMatcher = CUSTOMER_PATTERN.matcher(line); Matcher customerMatcher = CUSTOMER_PATTERN.matcher(line);
if (customerMatcher.find()) { if (customerMatcher.find()) {
entry.setCustomer(customerMatcher.group(1).trim()); entryBuilder.customer(customerMatcher.group(1).trim());
} }
} }
@@ -166,7 +167,7 @@ public class SentimentAnalyzer {
else if (line.startsWith("Department:")) { else if (line.startsWith("Department:")) {
Matcher departmentMatcher = DEPARTMENT_PATTERN.matcher(line); Matcher departmentMatcher = DEPARTMENT_PATTERN.matcher(line);
if (departmentMatcher.find()) { if (departmentMatcher.find()) {
entry.setDepartment(departmentMatcher.group(1).trim()); entryBuilder.department(departmentMatcher.group(1).trim());
} }
} }
@@ -174,7 +175,7 @@ public class SentimentAnalyzer {
else if (line.startsWith("Date:")) { else if (line.startsWith("Date:")) {
Matcher dateMatcher = DATE_PATTERN.matcher(line); Matcher dateMatcher = DATE_PATTERN.matcher(line);
if (dateMatcher.find()) { if (dateMatcher.find()) {
entry.setDate(dateMatcher.group(1).trim()); entryBuilder.date(dateMatcher.group(1).trim());
} }
} }
@@ -182,7 +183,7 @@ public class SentimentAnalyzer {
else if (line.startsWith("Comment:")) { else if (line.startsWith("Comment:")) {
Matcher commentMatcher = COMMENT_PATTERN.matcher(line); Matcher commentMatcher = COMMENT_PATTERN.matcher(line);
if (commentMatcher.find()) { if (commentMatcher.find()) {
entry.setComment(commentMatcher.group(1).trim()); entryBuilder.comment(commentMatcher.group(1).trim());
} }
} }
@@ -190,7 +191,8 @@ public class SentimentAnalyzer {
} }
// Validate that we have all required fields // Validate that we have all required fields
if (entry.getId() > 0 && entry.getComment() != null) { FeedbackEntry entry = entryBuilder.build();
if (entry.validate()) {
return entry; return entry;
} else { } else {
log.error("Invalid entry - missing ID or comment"); log.error("Invalid entry - missing ID or comment");
@@ -212,6 +214,7 @@ public class SentimentAnalyzer {
* @return Sentiment (VERY_POSITIVE, POSITIVE, NEUTRAL, NEGATIVE, VERY_NEGATIVE) * @return Sentiment (VERY_POSITIVE, POSITIVE, NEUTRAL, NEGATIVE, VERY_NEGATIVE)
*/ */
public String analyzeSentiment(String comment) { public String analyzeSentiment(String comment) {
// Create a document from the comment // Create a document from the comment
CoreDocument doc = new CoreDocument(comment); CoreDocument doc = new CoreDocument(comment);
@@ -300,44 +303,4 @@ public class SentimentAnalyzer {
} }
} }
} }
/**
* Inner class representing a feedback entry with sentiment analysis
*/
public static class FeedbackEntry {
private int id;
private String customer;
private String department;
private String date;
private String comment;
private String sentiment;
// Default values to avoid null
public FeedbackEntry() {
this.customer = "";
this.department = "";
this.date = "";
this.comment = "";
this.sentiment = "";
}
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getCustomer() { return customer; }
public void setCustomer(String customer) { this.customer = customer; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getDate() { return date; }
public void setDate(String date) { this.date = date; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
public String getSentiment() { return sentiment; }
public void setSentiment(String sentiment) { this.sentiment = sentiment; }
}
} }

View File

@@ -20,8 +20,11 @@ import java.util.List;
@Controller @Controller
public class FeedbackController { public class FeedbackController {
@Autowired private final FeedbackService feedbackService;
private FeedbackService feedbackService;
public FeedbackController(FeedbackService feedbackService) {
this.feedbackService = feedbackService;
}
/** /**
* Displays the dashboard page. * Displays the dashboard page.

View File

@@ -1,29 +1,14 @@
package com.retailstore.feedback.model; package com.retailstore.feedback.model;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
/** /**
* Represents feedback enhanced with AI-generated category and actionable insights. * Represents feedback enhanced with AI-generated category and actionable insights.
*/ */
@Getter
@SuperBuilder
public class EnhancedFeedback extends FeedbackEntry { public class EnhancedFeedback extends FeedbackEntry {
private String category; private String category;
private String actionableInsight; private String actionableInsight;
// Default constructor
public EnhancedFeedback() {
super();
}
// Constructor based on FeedbackEntry
public EnhancedFeedback(FeedbackEntry entry) {
super(entry.getId(), entry.getCustomer(), entry.getDepartment(),
entry.getDate(), entry.getComment(), entry.getSentiment());
}
// Getters and setters for additional fields
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getActionableInsight() { return actionableInsight; }
public void setActionableInsight(String actionableInsight) {
this.actionableInsight = actionableInsight;
}
} }

View File

@@ -1,46 +1,34 @@
package com.retailstore.feedback.model; package com.retailstore.feedback.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.With;
import lombok.experimental.SuperBuilder;
/** /**
* Represents a feedback entry with sentiment analysis. * Represents a feedback entry with sentiment analysis.
*/ */
@Getter
@SuperBuilder
@AllArgsConstructor
public class FeedbackEntry { public class FeedbackEntry {
private int id; private int id;
private String customer;
private String department;
private String date;
private String comment;
private String sentiment;
// Default constructor @Builder.Default
public FeedbackEntry() {} private String customer = "";
@Builder.Default
private String department = "";
@Builder.Default
private String date = "";
@Builder.Default
private String comment = "";
@With
@Builder.Default
private String sentiment = "";
// Constructor with parameters public boolean validate() {
public FeedbackEntry(int id, String customer, String department, return id > 0 && comment != null;
String date, String comment, String sentiment) {
this.id = id;
this.customer = customer;
this.department = department;
this.date = date;
this.comment = comment;
this.sentiment = sentiment;
} }
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getCustomer() { return customer; }
public void setCustomer(String customer) { this.customer = customer; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getDate() { return date; }
public void setDate(String date) { this.date = date; }
public String getComment() { return comment; }
public void setComment(String comment) { this.comment = comment; }
public String getSentiment() { return sentiment; }
public void setSentiment(String sentiment) { this.sentiment = sentiment; }
} }

View File

@@ -1,44 +1,21 @@
package com.retailstore.feedback.model; package com.retailstore.feedback.model;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* Represents a summary of feedback data for the dashboard. * Represents a summary of feedback data for the dashboard.
*/ */
@Getter
@Builder
public class FeedbackSummary { public class FeedbackSummary {
private int totalFeedback; private final int totalFeedback;
private Map<String, Integer> sentimentCounts; private final Map<String, Integer> sentimentCounts;
private Map<String, Integer> categoryCounts; private final Map<String, Integer> categoryCounts;
private Map<String, Integer> departmentCounts; private final Map<String, Integer> departmentCounts;
private List<EnhancedFeedback> recentFeedback; private final List<EnhancedFeedback> recentFeedback;
// Default constructor
public FeedbackSummary() {}
// Getters and setters
public int getTotalFeedback() { return totalFeedback; }
public void setTotalFeedback(int totalFeedback) {
this.totalFeedback = totalFeedback;
}
public Map<String, Integer> getSentimentCounts() { return sentimentCounts; }
public void setSentimentCounts(Map<String, Integer> sentimentCounts) {
this.sentimentCounts = sentimentCounts;
}
public Map<String, Integer> getCategoryCounts() { return categoryCounts; }
public void setCategoryCounts(Map<String, Integer> categoryCounts) {
this.categoryCounts = categoryCounts;
}
public Map<String, Integer> getDepartmentCounts() { return departmentCounts; }
public void setDepartmentCounts(Map<String, Integer> departmentCounts) {
this.departmentCounts = departmentCounts;
}
public List<EnhancedFeedback> getRecentFeedback() { return recentFeedback; }
public void setRecentFeedback(List<EnhancedFeedback> recentFeedback) {
this.recentFeedback = recentFeedback;
}
} }

View File

@@ -5,6 +5,9 @@ import com.retailstore.feedback.model.FeedbackEntry;
import com.retailstore.feedback.model.EnhancedFeedback; import com.retailstore.feedback.model.EnhancedFeedback;
import com.retailstore.feedback.model.FeedbackSummary; import com.retailstore.feedback.model.FeedbackSummary;
import com.retailstore.feedback.model.FeedbackSummary.FeedbackSummaryBuilder;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -12,6 +15,7 @@ import java.io.BufferedReader;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -82,7 +86,7 @@ public class FeedbackService {
} }
// Process each line // Process each line
if (line.trim().isEmpty() && entryText.length() > 0) { if (line.trim().isEmpty() && !entryText.isEmpty()) {
// We've reached the end of an entry // We've reached the end of an entry
FeedbackEntry entry = parseFeedbackEntry(entryText.toString()); FeedbackEntry entry = parseFeedbackEntry(entryText.toString());
if (entry != null) { if (entry != null) {
@@ -95,7 +99,7 @@ public class FeedbackService {
} }
// Process the last entry if there is one // Process the last entry if there is one
if (entryText.length() > 0) { if (!entryText.isEmpty()) {
FeedbackEntry entry = parseFeedbackEntry(entryText.toString()); FeedbackEntry entry = parseFeedbackEntry(entryText.toString());
if (entry != null) { if (entry != null) {
entries.add(entry); entries.add(entry);
@@ -113,12 +117,12 @@ public class FeedbackService {
* @return FeedbackEntry object or null if parsing fails * @return FeedbackEntry object or null if parsing fails
*/ */
private FeedbackEntry parseFeedbackEntry(String text) { private FeedbackEntry parseFeedbackEntry(String text) {
FeedbackEntry entry = new FeedbackEntry(); FeedbackEntry.FeedbackEntryBuilder<?, ?> entryBuilder = FeedbackEntry.builder();
// Extract feedback ID // Extract feedback ID
Matcher idMatcher = FEEDBACK_PATTERN.matcher(text); Matcher idMatcher = FEEDBACK_PATTERN.matcher(text);
if (idMatcher.find()) { if (idMatcher.find()) {
entry.setId(Integer.parseInt(idMatcher.group(1))); entryBuilder.id(Integer.parseInt(idMatcher.group(1)));
} else { } else {
return null; return null;
} }
@@ -126,34 +130,34 @@ public class FeedbackService {
// Extract customer // Extract customer
Matcher customerMatcher = CUSTOMER_PATTERN.matcher(text); Matcher customerMatcher = CUSTOMER_PATTERN.matcher(text);
if (customerMatcher.find()) { if (customerMatcher.find()) {
entry.setCustomer(customerMatcher.group(1)); entryBuilder.customer(customerMatcher.group(1));
} }
// Extract department // Extract department
Matcher departmentMatcher = DEPARTMENT_PATTERN.matcher(text); Matcher departmentMatcher = DEPARTMENT_PATTERN.matcher(text);
if (departmentMatcher.find()) { if (departmentMatcher.find()) {
entry.setDepartment(departmentMatcher.group(1)); entryBuilder.department(departmentMatcher.group(1));
} }
// Extract date // Extract date
Matcher dateMatcher = DATE_PATTERN.matcher(text); Matcher dateMatcher = DATE_PATTERN.matcher(text);
if (dateMatcher.find()) { if (dateMatcher.find()) {
entry.setDate(dateMatcher.group(1)); entryBuilder.date(dateMatcher.group(1));
} }
// Extract comment // Extract comment
Matcher commentMatcher = COMMENT_PATTERN.matcher(text); Matcher commentMatcher = COMMENT_PATTERN.matcher(text);
if (commentMatcher.find()) { if (commentMatcher.find()) {
entry.setComment(commentMatcher.group(1)); entryBuilder.comment(commentMatcher.group(1));
} }
// Extract sentiment // Extract sentiment
Matcher sentimentMatcher = SENTIMENT_PATTERN.matcher(text); Matcher sentimentMatcher = SENTIMENT_PATTERN.matcher(text);
if (sentimentMatcher.find()) { if (sentimentMatcher.find()) {
entry.setSentiment(sentimentMatcher.group(1)); entryBuilder.sentiment(sentimentMatcher.group(1));
} }
return entry; return entryBuilder.build();
} }
/** /**
@@ -162,14 +166,16 @@ public class FeedbackService {
* @return List of EnhancedFeedback objects * @return List of EnhancedFeedback objects
* @throws IOException If an I/O error occurs * @throws IOException If an I/O error occurs
*/ */
public synchronized List<EnhancedFeedback> getEnhancedFeedback() throws IOException { @EventListener(ApplicationStartedEvent.class)
public List<EnhancedFeedback> getEnhancedFeedback() throws IOException {
// Return cached data if available // Return cached data if available
if (enhancedFeedbackCache != null) { if (enhancedFeedbackCache != null) {
return enhancedFeedbackCache; return enhancedFeedbackCache;
} }
enhancedFeedbackCache = Collections.emptyList();
List<FeedbackEntry> entries = readFeedbackData();
List<EnhancedFeedback> enhancedEntries = new ArrayList<>(); List<EnhancedFeedback> enhancedEntries = new ArrayList<>();
List<FeedbackEntry> entries = readFeedbackData();
for (FeedbackEntry entry : entries) { for (FeedbackEntry entry : entries) {
EnhancedFeedback enhancedEntry = enhanceFeedback(entry); EnhancedFeedback enhancedEntry = enhanceFeedback(entry);
@@ -189,14 +195,20 @@ public class FeedbackService {
* @return EnhancedFeedback with AI-generated category and actionable insight * @return EnhancedFeedback with AI-generated category and actionable insight
*/ */
private EnhancedFeedback enhanceFeedback(FeedbackEntry entry) { private EnhancedFeedback enhanceFeedback(FeedbackEntry entry) {
EnhancedFeedback enhancedEntry = new EnhancedFeedback(entry); EnhancedFeedback.EnhancedFeedbackBuilder<?, ?> enhancedEntryBuilder = EnhancedFeedback.builder()
.id(entry.getId())
.date(entry.getDate())
.comment(entry.getComment())
.customer(entry.getCustomer())
.department(entry.getDepartment())
.sentiment(entry.getSentiment());
// Create a prompt for Gemini // Create a prompt for Gemini
String prompt = PROMPT.formatted(entry.getComment(), entry.getDepartment(), entry.getSentiment()); String prompt = PROMPT.formatted(entry.getComment(), entry.getDepartment(), entry.getSentiment());
try { try {
// Call Gemini API and parse the response // Call Gemini API and parse the response
String response = geminiService.generateContent(prompt); String response = geminiService.generateContent(prompt).get(3, TimeUnit.SECONDS);
// Parse JSON response // Parse JSON response
// This is a simple parsing approach - for production, use a proper JSON parser // This is a simple parsing approach - for production, use a proper JSON parser
@@ -206,27 +218,27 @@ public class FeedbackService {
Pattern categoryPattern = Pattern.compile("\"category\"\\s*:\\s*\"([^\"]+)\""); Pattern categoryPattern = Pattern.compile("\"category\"\\s*:\\s*\"([^\"]+)\"");
Matcher categoryMatcher = categoryPattern.matcher(jsonResponse); Matcher categoryMatcher = categoryPattern.matcher(jsonResponse);
if (categoryMatcher.find()) { if (categoryMatcher.find()) {
enhancedEntry.setCategory(categoryMatcher.group(1)); enhancedEntryBuilder.category(categoryMatcher.group(1));
} else { } else {
enhancedEntry.setCategory("Uncategorized"); enhancedEntryBuilder.category("Uncategorized");
} }
// Extract actionable insight // Extract actionable insight
Pattern insightPattern = Pattern.compile("\"actionableInsight\"\\s*:\\s*\"([^\"]+)\""); Pattern insightPattern = Pattern.compile("\"actionableInsight\"\\s*:\\s*\"([^\"]+)\"");
Matcher insightMatcher = insightPattern.matcher(jsonResponse); Matcher insightMatcher = insightPattern.matcher(jsonResponse);
if (insightMatcher.find()) { if (insightMatcher.find()) {
enhancedEntry.setActionableInsight(insightMatcher.group(1)); enhancedEntryBuilder.actionableInsight(insightMatcher.group(1));
} else { } else {
enhancedEntry.setActionableInsight("No specific action recommended."); enhancedEntryBuilder.actionableInsight("No specific action recommended.");
} }
} catch (Exception e) { } catch (Exception e) {
// Handle API errors gracefully // Handle API errors gracefully
enhancedEntry.setCategory("Error in processing"); enhancedEntryBuilder.category("Error in processing");
enhancedEntry.setActionableInsight("Could not generate insight due to API error: " + e.getMessage()); enhancedEntryBuilder.actionableInsight("Could not generate insight due to API error: " + e.getMessage());
} }
return enhancedEntry; return enhancedEntryBuilder.build();
} }
/** /**
@@ -237,10 +249,8 @@ public class FeedbackService {
*/ */
public FeedbackSummary generateFeedbackSummary() throws IOException { public FeedbackSummary generateFeedbackSummary() throws IOException {
List<EnhancedFeedback> allFeedback = getEnhancedFeedback(); List<EnhancedFeedback> allFeedback = getEnhancedFeedback();
FeedbackSummary summary = new FeedbackSummary(); FeedbackSummaryBuilder summaryBuilder = FeedbackSummary.builder()
.totalFeedback(allFeedback.size());
// Set total feedback count
summary.setTotalFeedback(allFeedback.size());
// Count sentiments // Count sentiments
Map<String, Integer> sentimentCounts = new HashMap<>(); Map<String, Integer> sentimentCounts = new HashMap<>();
@@ -248,7 +258,7 @@ public class FeedbackService {
String sentiment = feedback.getSentiment(); String sentiment = feedback.getSentiment();
sentimentCounts.put(sentiment, sentimentCounts.getOrDefault(sentiment, 0) + 1); sentimentCounts.put(sentiment, sentimentCounts.getOrDefault(sentiment, 0) + 1);
} }
summary.setSentimentCounts(sentimentCounts); summaryBuilder.sentimentCounts(sentimentCounts);
// Count categories // Count categories
Map<String, Integer> categoryCounts = new HashMap<>(); Map<String, Integer> categoryCounts = new HashMap<>();
@@ -256,7 +266,7 @@ public class FeedbackService {
String category = feedback.getCategory(); String category = feedback.getCategory();
categoryCounts.put(category, categoryCounts.getOrDefault(category, 0) + 1); categoryCounts.put(category, categoryCounts.getOrDefault(category, 0) + 1);
} }
summary.setCategoryCounts(categoryCounts); summaryBuilder.categoryCounts(categoryCounts);
// Count departments // Count departments
Map<String, Integer> departmentCounts = new HashMap<>(); Map<String, Integer> departmentCounts = new HashMap<>();
@@ -264,15 +274,15 @@ public class FeedbackService {
String department = feedback.getDepartment(); String department = feedback.getDepartment();
departmentCounts.put(department, departmentCounts.getOrDefault(department, 0) + 1); departmentCounts.put(department, departmentCounts.getOrDefault(department, 0) + 1);
} }
summary.setDepartmentCounts(departmentCounts); summaryBuilder.departmentCounts(departmentCounts);
// Get recent feedback (last 5 entries) // Get recent feedback (last 5 entries)
List<EnhancedFeedback> recentFeedback = allFeedback.stream() List<EnhancedFeedback> recentFeedback = allFeedback.stream()
.sorted(Comparator.comparing(EnhancedFeedback::getId).reversed()) .sorted(Comparator.comparing(EnhancedFeedback::getId).reversed())
.limit(5) .limit(5)
.collect(Collectors.toList()); .collect(Collectors.toList());
summary.setRecentFeedback(recentFeedback); summaryBuilder.recentFeedback(recentFeedback);
return summary; return summaryBuilder.build();
} }
} }

View File

@@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.retailstore.feedback.PausableThreadPoolExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
@@ -16,18 +18,23 @@ import org.springframework.http.*;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.*;
/** /**
* Service for interacting with Google's Gemini AI using REST API * Service for interacting with Google's Gemini AI using REST API
*/ */
@Slf4j
@Service @Service
public class GeminiService { public class GeminiService {
private final RestTemplate restTemplate; private final RestTemplate restTemplate;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final String serviceUri; private final String serviceUri;
private static final PausableThreadPoolExecutor promptExecution = new PausableThreadPoolExecutor(1,1,30, TimeUnit.SECONDS);
private static final ScheduledExecutorService scheduleExecution = Executors.newSingleThreadScheduledExecutor();
public GeminiService(@Value("${gemini.api.url}") String url, @Value("${gemini.api.key}") String apiKey) { public GeminiService(@Value("${gemini.api.url}") String url, @Value("${gemini.api.key}") String apiKey) {
this.objectMapper = new JsonMapper(); this.objectMapper = new JsonMapper();
serviceUri = UriComponentsBuilder serviceUri = UriComponentsBuilder
@@ -45,7 +52,11 @@ public class GeminiService {
* @param prompt The prompt to send to Gemini * @param prompt The prompt to send to Gemini
* @return The response from Gemini * @return The response from Gemini
*/ */
public String generateContent(String prompt) { public Future<String> generateContent(String prompt) {
return promptExecution.submit(() -> executePrompt(prompt));
}
private String executePrompt(String prompt) {
try { try {
// Create the request body using Jackson // Create the request body using Jackson
ObjectNode requestBody = objectMapper.createObjectNode(); ObjectNode requestBody = objectMapper.createObjectNode();
@@ -91,18 +102,11 @@ public class GeminiService {
return "No response from Gemini"; return "No response from Gemini";
} catch (Exception e) { } catch (Exception e) {
System.err.println("Error calling Gemini API: " + e.getMessage()); log.error("Error calling Gemini API: {}", e.getMessage(), e);
e.printStackTrace(); log.info("Pausing Gemini API calls for 10 seconds...");
promptExecution.pause();
scheduleExecution.schedule(promptExecution::resume, 10, TimeUnit.SECONDS);
return "Error: " + e.getMessage(); return "Error: " + e.getMessage();
} }
} }
/**
* Test method to verify API connection
*/
public void testConnection() {
String testPrompt = "Say 'Hello, World!' if you can hear me.";
String response = generateContent(testPrompt);
System.out.println("Gemini API Test Response: " + response);
}
} }