diff --git a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml index c1141adc779..053953bbab8 100644 --- a/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml +++ b/src/main/java/org/jabref/gui/ai/components/privacynotice/PrivacyNoticeComponent.fxml @@ -59,7 +59,7 @@ - + diff --git a/src/main/java/org/jabref/logic/ai/GenerateEmbeddingsTask.java b/src/main/java/org/jabref/logic/ai/GenerateEmbeddingsTask.java index dfbd533886e..e15dda37fb8 100644 --- a/src/main/java/org/jabref/logic/ai/GenerateEmbeddingsTask.java +++ b/src/main/java/org/jabref/logic/ai/GenerateEmbeddingsTask.java @@ -16,6 +16,7 @@ import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.ai.embeddings.FileToDocument; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.ProgressCounter; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; import org.jabref.preferences.FilePreferences; @@ -33,9 +34,7 @@ public class GenerateEmbeddingsTask extends BackgroundTask { private final BibDatabaseContext bibDatabaseContext; private final FilePreferences filePreferences; - private final IntegerProperty workDone = new SimpleIntegerProperty(0); - private final IntegerProperty workMax = new SimpleIntegerProperty(0); - private Instant oneWorkTimeStart = Instant.now(); + private final ProgressCounter progressCounter = new ProgressCounter(); public GenerateEmbeddingsTask(String citationKey, List linkedFiles, @@ -51,8 +50,7 @@ public GenerateEmbeddingsTask(String citationKey, titleProperty().set(Localization.lang("Generating embeddings for for %0", citationKey)); showToUser(true); - workDone.addListener(obs -> updateProgress()); - workMax.addListener(obs -> updateProgress()); + progressCounter.listenToAllProperties(this::updateProgress); } @Override @@ -103,7 +101,7 @@ private void ingestLinkedFile(LinkedFile linkedFile) throws InterruptedException Optional document = FileToDocument.fromFile(path.get()); if (document.isPresent()) { - fileEmbeddingsManager.addDocument(linkedFile.getLink(), document.get(), currentModificationTimeInSeconds, workDone, workMax); + fileEmbeddingsManager.addDocument(linkedFile.getLink(), document.get(), currentModificationTimeInSeconds, progressCounter.workDoneProperty(), progressCounter.workMaxProperty()); LOGGER.info("Embeddings for file \"{}\" were generated successfully, while processing entry {}", linkedFile.getLink(), citationKey); } else { LOGGER.error("Unable to generate embeddings for file \"{}\", because JabRef was unable to extract text from the file, while processing entry {}", linkedFile.getLink(), citationKey); @@ -115,7 +113,7 @@ private void ingestLinkedFile(LinkedFile linkedFile) throws InterruptedException Optional document = FileToDocument.fromFile(path.get()); if (document.isPresent()) { - fileEmbeddingsManager.addDocument(linkedFile.getLink(), document.get(), 0, workDone, workMax); + fileEmbeddingsManager.addDocument(linkedFile.getLink(), document.get(), 0, progressCounter.workDoneProperty(), progressCounter.workMaxProperty()); LOGGER.info("Embeddings for file \"{}\" were generated successfully while processing entry {}, but the JabRef couldn't check if the file was changed", linkedFile.getLink(), citationKey); } else { LOGGER.info("Unable to generate embeddings for file \"{}\" while processing entry {}, because JabRef was unable to extract text from the file", linkedFile.getLink(), citationKey); @@ -124,24 +122,7 @@ private void ingestLinkedFile(LinkedFile linkedFile) throws InterruptedException } private void updateProgress() { - Instant oneWorkTimeEnd = Instant.now(); - Duration duration = Duration.between(oneWorkTimeStart, oneWorkTimeEnd); - oneWorkTimeStart = oneWorkTimeEnd; - - Duration eta = duration.multipliedBy(workMax.get() - workDone.get()); - - updateProgress(workDone.get(), workMax.get()); - - if (eta.getSeconds() < 60) { - if (eta.getSeconds() == 0) { - updateMessage(Localization.lang("Estimated time left: %0 seconds", 3)); - } else { - updateMessage(Localization.lang("Estimated time left: %0 seconds", eta.getSeconds())); - } - } else if (eta.getSeconds() % 60 == 0) { - updateMessage(Localization.lang("Estimated time left: %0 minutes", eta.getSeconds() / 60)); - } else { - updateMessage(Localization.lang("Estimated time left: %0 minutes, %1 seconds", eta.getSeconds() / 60, eta.getSeconds() % 60)); - } + updateProgress(progressCounter.getWorkDone(), progressCounter.getWorkMax()); + updateMessage(progressCounter.getMessage()); } } diff --git a/src/main/java/org/jabref/logic/ai/summarization/GenerateSummaryTask.java b/src/main/java/org/jabref/logic/ai/summarization/GenerateSummaryTask.java index 8a4817c2da0..f8e2fb9a1a0 100644 --- a/src/main/java/org/jabref/logic/ai/summarization/GenerateSummaryTask.java +++ b/src/main/java/org/jabref/logic/ai/summarization/GenerateSummaryTask.java @@ -15,6 +15,7 @@ import org.jabref.logic.ai.AiService; import org.jabref.logic.ai.embeddings.FileToDocument; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.ProgressCounter; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.LinkedFile; import org.jabref.preferences.FilePreferences; @@ -66,9 +67,7 @@ public class GenerateSummaryTask extends BackgroundTask { private final AiService aiService; private final FilePreferences filePreferences; - private int workDone = 0; - private int workMax = 0; - private Instant oneWorkTimeStart = Instant.now(); + private final ProgressCounter progressCounter = new ProgressCounter(); public GenerateSummaryTask(BibDatabaseContext bibDatabaseContext, String citationKey, @@ -83,6 +82,8 @@ public GenerateSummaryTask(BibDatabaseContext bibDatabaseContext, titleProperty().set(Localization.lang("Waiting summary for %0...", citationKey)); showToUser(true); + + progressCounter.listenToAllProperties(this::updateProgress); } @Override @@ -253,36 +254,17 @@ private static int estimateTokenCount(int numOfChars) { } private void updateProgress() { - Instant oneWorkTimeEnd = Instant.now(); - Duration duration = Duration.between(oneWorkTimeStart, oneWorkTimeEnd); - oneWorkTimeStart = oneWorkTimeEnd; - - Duration eta = duration.multipliedBy(workMax - workDone); - - updateProgress(workDone, workMax); - - if (eta.getSeconds() < 60) { - if (eta.getSeconds() == 0) { - updateMessage(Localization.lang("Estimated time left: %0 seconds", 3)); - } else { - updateMessage(Localization.lang("Estimated time left: %0 seconds", eta.getSeconds())); - } - } else if (eta.getSeconds() % 60 == 0) { - updateMessage(Localization.lang("Estimated time left: %0 minutes", eta.getSeconds() / 60)); - } else { - updateMessage(Localization.lang("Estimated time left: %0 minutes, %1 seconds", eta.getSeconds() / 60, eta.getSeconds() % 60)); - } - - updateProgress(workDone, workMax); + updateProgress(progressCounter.getWorkDone(), progressCounter.getWorkMax()); + updateMessage(progressCounter.getMessage()); } private void addMoreWork(int moreWork) { - workMax += moreWork; + progressCounter.increaseWorkMax(moreWork); updateProgress(); } private void doneOneWork() { - workDone += 1; + progressCounter.increaseWorkDone(1); updateProgress(); } } diff --git a/src/main/java/org/jabref/logic/util/ProgressCounter.java b/src/main/java/org/jabref/logic/util/ProgressCounter.java new file mode 100644 index 00000000000..ce59d972693 --- /dev/null +++ b/src/main/java/org/jabref/logic/util/ProgressCounter.java @@ -0,0 +1,129 @@ +package org.jabref.logic.util; + +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +import org.jabref.logic.l10n.Localization; + +import ai.djl.util.Progress; + +public class ProgressCounter implements Progress { + private final IntegerProperty workDone = new SimpleIntegerProperty(0); + private final IntegerProperty workMax = new SimpleIntegerProperty(0); + private final StringProperty message = new SimpleStringProperty(""); + private Instant oneWorkTimeStart = Instant.now(); + private Duration lastEtaCalculation = Duration.ofDays(1); + + public ProgressCounter() { + workDone.addListener(obs -> update()); + workMax.addListener(obs -> update()); + } + + public void increaseWorkDone(int incr) { + workDone.set(workDone.get() + incr); + } + + public void increaseWorkMax(int incr) { + workMax.set(workMax.get() + incr); + } + + public IntegerProperty workDoneProperty() { + return workDone; + } + + public int getWorkDone() { + return workDone.get(); + } + + public IntegerProperty workMaxProperty() { + return workMax; + } + + public int getWorkMax() { + return workMax.get(); + } + + public ReadOnlyStringProperty messageProperty() { + return message; + } + + public String getMessage() { + return message.get(); + } + + public void listenToAllProperties(Runnable runnable) { + workDoneProperty().addListener(obs -> runnable.run()); + workMaxProperty().addListener(obs -> runnable.run()); + messageProperty().addListener(obs -> runnable.run()); + } + + private void update() { + Instant oneWorkTimeEnd = Instant.now(); + Duration duration = Duration.between(oneWorkTimeStart, oneWorkTimeEnd); + oneWorkTimeStart = oneWorkTimeEnd; + + Duration eta = duration.multipliedBy(workMax.get() - workDone.get()); + + if (lastEtaCalculation.minus(eta).isPositive()) { + updateMessage(eta); + + lastEtaCalculation = eta; + } + } + + @Override + public void reset(String message, long max, String trailingMessage) { + workMax.set((int) max); + workDone.set(0); + // Ignoring message, because 1) it's not localized, 2) we supply our own message in updateMessage(). + } + + @Override + public void start(long initialProgress) { + workDone.set((int) initialProgress); + } + + @Override + public void end() { + // Do nothing. + } + + @Override + public void increment(long increment) { + workDone.set(workDone.get() + (int) increment); + } + + @Override + public void update(long progress, String message) { + workDone.set((int) progress); + // Ignoring message, because 1) it's not localized, 2) we supply our own message in updateMessage(). + } + + private record ProgressMessage(int maxTime, String message) { } + + // The list should be sorted by ProgressMessage.maxTime, smaller first. + private final List PROGRESS_MESSAGES = List.of( + new ProgressMessage(5, Localization.lang("Estimated time left: 5 seconds")), + new ProgressMessage(15, Localization.lang("Estimated time left: 15 seconds")), + new ProgressMessage(30, Localization.lang("Estimated time left: 30 seconds")), + new ProgressMessage(60, Localization.lang("Estimated time left: approx. 1 minute")), + new ProgressMessage(120, Localization.lang("Estimated time left: approx. 2 minutes")), + new ProgressMessage(Integer.MAX_VALUE, Localization.lang("Estimated time left: more than 2 minutes")) + ); + + private void updateMessage(Duration eta) { + for (ProgressMessage progressMessage : PROGRESS_MESSAGES) { + if (eta.getSeconds() <= progressMessage.maxTime()) { + message.set(progressMessage.message()); + break; + } + } + } +}