forked from JabRef/jabref
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for scite.ai (JabRef#10617)
* task: first cut of Scite tab and associated preference * task: comments and minor refactoring * task: Updated readme.md for issue #375 * fix: Import ordering and markdown space * fix: imports and whitespace (checkstyle) * fix: imports and whitespace (checkstyle) * fix: imports and whitespace (checkstyle) * fix: Removed unnecessary parentheses (OpenRewrite) * fix: Unit tests and localisation keys * fix: Removed unnecessary heading in CHANGELOG.md * fix: Made SciteTallyDTO a record type and moved it to new file * fix: Made the scite.ai base url a constant * fix: Exception handling now catches most specific exceptions, and rethrows them as FetcherException * fix: style for scite message box * fix: Catch specific exception and display error message rather than throwing a generic unchecked exception * fix: Localization for SciteTab error messages, and "Scite" name is no longer localized * some small refactoring * checkstyle and rename * Update CHANGELOG.md * Update EntryEditorTab.java * move and checkstyle * fix l10n and rename --------- Co-authored-by: Siedlerchr <siedlerkiller@gmail.com> Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com>
- Loading branch information
1 parent
934a0c8
commit 8a800e6
Showing
14 changed files
with
490 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package org.jabref.gui.entryeditor; | ||
|
||
import java.io.IOException; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
import javafx.geometry.HPos; | ||
import javafx.scene.control.Hyperlink; | ||
import javafx.scene.control.Label; | ||
import javafx.scene.control.ProgressIndicator; | ||
import javafx.scene.control.Tooltip; | ||
import javafx.scene.layout.ColumnConstraints; | ||
import javafx.scene.layout.GridPane; | ||
import javafx.scene.layout.VBox; | ||
import javafx.scene.text.Text; | ||
|
||
import org.jabref.gui.DialogService; | ||
import org.jabref.gui.desktop.JabRefDesktop; | ||
import org.jabref.gui.util.TaskExecutor; | ||
import org.jabref.logic.l10n.Localization; | ||
import org.jabref.model.entry.BibEntry; | ||
import org.jabref.preferences.PreferencesService; | ||
|
||
import com.tobiasdiez.easybind.EasyBind; | ||
import org.controlsfx.control.HyperlinkLabel; | ||
|
||
public class SciteTab extends EntryEditorTab { | ||
|
||
public static final String NAME = "Scite"; | ||
public static final String SCITE_REPORTS_URL_BASE = "https://scite.ai/reports/"; | ||
|
||
private final GridPane sciteResultsPane; | ||
private final ProgressIndicator progressIndicator; | ||
private final SciteTabViewModel viewModel; | ||
private final PreferencesService preferencesService; | ||
private final DialogService dialogService; | ||
|
||
public SciteTab(PreferencesService preferencesService, TaskExecutor taskExecutor, DialogService dialogService) { | ||
this.preferencesService = preferencesService; | ||
this.viewModel = new SciteTabViewModel(preferencesService, taskExecutor); | ||
this.dialogService = dialogService; | ||
this.sciteResultsPane = new GridPane(); | ||
this.progressIndicator = new ProgressIndicator(); | ||
setText(NAME); | ||
setTooltip(new Tooltip(Localization.lang("Search scite.ai for Smart Citations"))); | ||
setSciteResultsPane(); | ||
} | ||
|
||
private void setSciteResultsPane() { | ||
progressIndicator.setMaxSize(100, 100); | ||
sciteResultsPane.add(progressIndicator, 0, 0); | ||
|
||
ColumnConstraints column = new ColumnConstraints(); | ||
column.setPercentWidth(100); | ||
column.setHalignment(HPos.CENTER); | ||
|
||
sciteResultsPane.getColumnConstraints().setAll(column); | ||
sciteResultsPane.setId("scitePane"); | ||
setContent(sciteResultsPane); | ||
|
||
EasyBind.subscribe(viewModel.statusProperty(), status -> { | ||
sciteResultsPane.getChildren().clear(); | ||
switch (status) { | ||
case IN_PROGRESS -> | ||
sciteResultsPane.add(progressIndicator, 0, 0); | ||
case FOUND -> | ||
viewModel.getCurrentResult().ifPresent(result -> sciteResultsPane.add(getTalliesPane(result), 0, 0)); | ||
case ERROR -> | ||
sciteResultsPane.add(getErrorPane(), 0, 0); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public boolean shouldShow(BibEntry entry) { | ||
return viewModel.shouldShow(); | ||
} | ||
|
||
@Override | ||
protected void bindToEntry(BibEntry entry) { | ||
viewModel.bindToEntry(entry); | ||
} | ||
|
||
private VBox getErrorPane() { | ||
Label titleLabel = new Label(Localization.lang("Error")); | ||
titleLabel.getStyleClass().add("scite-error-label"); | ||
Text errorMessageText = new Text(viewModel.searchErrorProperty().get()); | ||
VBox errorMessageBox = new VBox(30, titleLabel, errorMessageText); | ||
errorMessageBox.getStyleClass().add("scite-error-box"); | ||
return errorMessageBox; | ||
} | ||
|
||
private VBox getTalliesPane(SciteTallyModel tallModel) { | ||
Label titleLabel = new Label(Localization.lang("Tallies for %0", tallModel.doi())); | ||
titleLabel.getStyleClass().add("scite-tallies-label"); | ||
Text message = new Text(String.format("Total Citations: %d\nSupporting: %d\nContradicting: %d\nMentioning: %d\nUnclassified: %d\nCiting Publications: %d", | ||
tallModel.total(), | ||
tallModel.supporting(), | ||
tallModel.contradicting(), | ||
tallModel.mentioning(), | ||
tallModel.unclassified(), | ||
tallModel.citingPublications() | ||
)); | ||
|
||
String url = SCITE_REPORTS_URL_BASE + URLEncoder.encode(tallModel.doi(), StandardCharsets.UTF_8); | ||
VBox messageBox = getMessageBox(url, titleLabel, message); | ||
messageBox.getStyleClass().add("scite-message-box"); | ||
return messageBox; | ||
} | ||
|
||
private VBox getMessageBox(String url, Label titleLabel, Text message) { | ||
HyperlinkLabel link = new HyperlinkLabel(Localization.lang("See full report at [%0]", url)); | ||
link.setOnAction(event -> { | ||
if (event.getSource() instanceof Hyperlink) { | ||
var filePreferences = preferencesService.getFilePreferences(); | ||
try { | ||
JabRefDesktop.openBrowser(url, filePreferences); | ||
} catch (IOException ioex) { | ||
// Can't throw a checked exception from here, so display a message to the user instead. | ||
dialogService.showErrorDialogAndWait( | ||
"An error occurred opening web browser", | ||
"JabRef was unable to open a web browser for link:\n\n" + url + "\n\nError Message:\n\n" + ioex.getMessage(), | ||
ioex | ||
); | ||
} | ||
} | ||
}); | ||
|
||
return new VBox(30, titleLabel, message, link); | ||
} | ||
} |
131 changes: 131 additions & 0 deletions
131
src/main/java/org/jabref/gui/entryeditor/SciteTabViewModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package org.jabref.gui.entryeditor; | ||
|
||
import java.io.IOException; | ||
import java.net.MalformedURLException; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.net.URL; | ||
import java.util.Optional; | ||
import java.util.concurrent.Future; | ||
|
||
import javafx.beans.property.ObjectProperty; | ||
import javafx.beans.property.SimpleObjectProperty; | ||
import javafx.beans.property.SimpleStringProperty; | ||
import javafx.beans.property.StringProperty; | ||
|
||
import org.jabref.gui.AbstractViewModel; | ||
import org.jabref.gui.util.BackgroundTask; | ||
import org.jabref.gui.util.TaskExecutor; | ||
import org.jabref.logic.importer.FetcherException; | ||
import org.jabref.logic.l10n.Localization; | ||
import org.jabref.logic.net.URLDownload; | ||
import org.jabref.model.entry.BibEntry; | ||
import org.jabref.model.entry.identifier.DOI; | ||
import org.jabref.preferences.PreferencesService; | ||
|
||
import kong.unirest.json.JSONObject; | ||
import org.tinylog.Logger; | ||
|
||
public class SciteTabViewModel extends AbstractViewModel { | ||
|
||
/** | ||
* Status enum for Scite tab | ||
*/ | ||
public enum SciteStatus { | ||
IN_PROGRESS, | ||
FOUND, | ||
ERROR | ||
} | ||
|
||
private static final String BASE_URL = "https://api.scite.ai/"; | ||
private final PreferencesService preferencesService; | ||
private final TaskExecutor taskExecutor; | ||
private final ObjectProperty<SciteStatus> status; | ||
private final StringProperty searchError; | ||
private Optional<SciteTallyModel> currentResult = Optional.empty(); | ||
|
||
private Future<?> searchTask; | ||
|
||
public SciteTabViewModel(PreferencesService preferencesService, TaskExecutor taskExecutor) { | ||
this.preferencesService = preferencesService; | ||
this.taskExecutor = taskExecutor; | ||
this.status = new SimpleObjectProperty<>(SciteStatus.IN_PROGRESS); | ||
this.searchError = new SimpleStringProperty(""); | ||
} | ||
|
||
public boolean shouldShow() { | ||
return preferencesService.getEntryEditorPreferences().shouldShowSciteTab(); | ||
} | ||
|
||
public void bindToEntry(BibEntry entry) { | ||
// If a search is already running, cancel it | ||
cancelSearch(); | ||
|
||
if (entry == null) { | ||
searchError.set(Localization.lang("No active entry")); | ||
status.set(SciteStatus.ERROR); | ||
return; | ||
} | ||
|
||
// The scite.ai api requires a DOI | ||
if (entry.getDOI().isEmpty()) { | ||
searchError.set(Localization.lang("This entry does not have a DOI")); | ||
status.set(SciteStatus.ERROR); | ||
return; | ||
} | ||
|
||
searchTask = BackgroundTask.wrap(() -> fetchTallies(entry.getDOI().get())) | ||
.onRunning(() -> status.set(SciteStatus.IN_PROGRESS)) | ||
.onSuccess(result -> { | ||
currentResult = Optional.of(result); | ||
status.set(SciteStatus.FOUND); | ||
}) | ||
.onFailure(error -> { | ||
searchError.set(error.getMessage()); | ||
status.set(SciteStatus.ERROR); | ||
}) | ||
.executeWith(taskExecutor); | ||
} | ||
|
||
private void cancelSearch() { | ||
if (searchTask == null || searchTask.isCancelled() || searchTask.isDone()) { | ||
return; | ||
} | ||
|
||
status.set(SciteStatus.IN_PROGRESS); | ||
searchTask.cancel(true); | ||
} | ||
|
||
public SciteTallyModel fetchTallies(DOI doi) throws FetcherException { | ||
try { | ||
URL url = new URI(BASE_URL + "tallies/" + doi.getDOI()).toURL(); | ||
URLDownload download = new URLDownload(url); | ||
String response = download.asString(); | ||
Logger.debug("Response {}", response); | ||
JSONObject tallies = new JSONObject(response); | ||
if (tallies.has("detail")) { | ||
String message = tallies.getString("detail"); | ||
throw new FetcherException(message); | ||
} else if (!tallies.has("total")) { | ||
throw new FetcherException("Unexpected result data!"); | ||
} | ||
return SciteTallyModel.fromJSONObject(tallies); | ||
} catch (MalformedURLException | URISyntaxException ex) { | ||
throw new FetcherException("Malformed url for DOs", ex); | ||
} catch (IOException ioex) { | ||
throw new FetcherException("Failed to retrieve tallies for DOI - IO Exception", ioex); | ||
} | ||
} | ||
|
||
public ObjectProperty<SciteStatus> statusProperty() { | ||
return status; | ||
} | ||
|
||
public StringProperty searchErrorProperty() { | ||
return searchError; | ||
} | ||
|
||
public Optional<SciteTallyModel> getCurrentResult() { | ||
return currentResult; | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/org/jabref/gui/entryeditor/SciteTallyModel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package org.jabref.gui.entryeditor; | ||
|
||
import kong.unirest.json.JSONObject; | ||
|
||
/** | ||
* Simple model object to hold the scite.ai tallies data for a given DOI | ||
*/ | ||
public record SciteTallyModel( | ||
String doi, | ||
int total, | ||
int supporting, | ||
int contradicting, | ||
int mentioning, | ||
int unclassified, | ||
int citingPublications) { | ||
|
||
/** | ||
* Creates a {@link SciteTallyModel} from a JSONObject (dictionary/map) | ||
* | ||
* @param jsonObject The JSON object holding the tally values | ||
* @return a new {@link SciteTallyModel} | ||
*/ | ||
public static SciteTallyModel fromJSONObject(JSONObject jsonObject) { | ||
return new SciteTallyModel( | ||
jsonObject.getString("doi"), | ||
jsonObject.getInt("total"), | ||
jsonObject.getInt("supporting"), | ||
jsonObject.getInt("contradicting"), | ||
jsonObject.getInt("mentioning"), | ||
jsonObject.getInt("unclassified"), | ||
jsonObject.getInt("citingPublications") | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.