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;
+ }
+ }
+ }
+}