Skip to content

Add Markdown and JSON export buttons to AI Summary and Chat tabs#14486

Merged
koppor merged 35 commits into
JabRef:mainfrom
CXZHANG0508:feat-ai-export
Dec 17, 2025
Merged

Add Markdown and JSON export buttons to AI Summary and Chat tabs#14486
koppor merged 35 commits into
JabRef:mainfrom
CXZHANG0508:feat-ai-export

Conversation

@CXZHANG0508

Copy link
Copy Markdown
Contributor

Closes #13868

Implemented the export functionality for AI Summary and AI Chat as requested. Users can now save the content to Markdown (human-readable) or JSON (machine-readable, OpenAI format). Added an export menu button to the UI and handled the file saving logic.

Steps to test

  1. Open an entry that has a citation key and a linked PDF. I tested the UI logic in a simulated local environment (since I don't have an AI API key)

  2. Go to the AI Summary or AI Chat tab.

image 3. Even without an API key (interface might show an error), you should see the new **Export** button (floppy disk icon) in the toolbar. In the AI Summary tab, the Export button is only visible after a summary has been successfully generated. 屏幕截图 2025-12-01 104756 屏幕截图 2025-11-30 194147
  1. Click the button and select Export to Markdown or Export to JSON.
屏幕截图 2025-12-01 104854 5. Save the file and verify the content: - **Markdown:** Should contain the BibTeX source and the text. - **JSON:** Should contain metadata and a `conversation` list compatible with OpenAI format. - I confirmed that clicking the Export button correctly triggers the 'Save As' file dialog and generates the file with the expected structure.

Mandatory checks

@CXZHANG0508 CXZHANG0508 closed this Dec 1, 2025
@CXZHANG0508 CXZHANG0508 reopened this Dec 1, 2025
@koppor

koppor commented Dec 1, 2025

Copy link
Copy Markdown
Member

@CXZHANG0508 Please do not close PRs and open new ones. Just push changes to your branch.

@CXZHANG0508

Copy link
Copy Markdown
Contributor Author

Understood, thanks!

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: changes-required Pull requests that are not yet complete labels Dec 1, 2025
@calixtus

calixtus commented Dec 2, 2025

Copy link
Copy Markdown
Member

Hi, thanks for your contribution.

Please use a proper PR title. "Feat ai export" is not enough to quickly understand from the table what this PR is about. Remember that in open source you occansionally happen to work with other people together and as long as we dont have the technology for mind sharing and telepathy, we need to keep communicating in a mutual understandable way.

@koppor koppor added status: changes-required Pull requests that are not yet complete status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers and removed status: changes-required Pull requests that are not yet complete labels Dec 2, 2025
@koppor koppor requested a review from InAnYan December 2, 2025 13:59
@CXZHANG0508 CXZHANG0508 changed the title Feat ai export feat(ai): Add Markdown and JSON export buttons to AI Summary and Chat tabs Dec 2, 2025
@CXZHANG0508

Copy link
Copy Markdown
Contributor Author

Thank you for your feedback! I apologize for the previous simple title. I have updated the PR title to be more descriptive.I hope that is clearer.

InAnYan
InAnYan previously approved these changes Dec 3, 2025

@InAnYan InAnYan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

Comment on lines +73 to +79
StringBuilder bibtex = new StringBuilder();
bibtex.append("@").append(entry.getType().getName()).append("{").append(entry.getCitationKey().orElse("")).append(",\n");
for (Field field : entry.getFields()) {
bibtex.append(" ").append(field.getName()).append(" = {").append(entry.getField(field).orElse("")).append("},\n");
}
bibtex.append("}");
root.put("entry_bibtex", bibtex.toString());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - please use getStringRepresentation will be available with #14504

Comment on lines +31 to +32
private final Runnable exportMarkdownCallback;
private final Runnable exportJsonCallback;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotate with @Nullable.

I wonder when this can be null? Maybe, it is always non-null? Please double check the call of SummaryShowingComponent and the ifs later at onExportMarkdown.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it needs to be annotated with @NonNull? (because of your change in line 33)

Comment on lines +27 to +35
sb.append("## Bibtex\n\n```bibtex\n");
sb.append("@").append(entry.getType().getName()).append("{").append(entry.getCitationKey().orElse("")).append(",\n");
for (Field field : entry.getFields()) {
String value = entry.getField(field).orElse("");
sb.append(" ").append(field.getName()).append(" = {").append(value).append("},\n");
}
sb.append("}\n```\n\n");
sb.append("## ").append(contentTitle).append("\n\n");
sb.append(contentBody);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For BibEntry export use getStringRepresentation will be available with #14504

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will wait for it to be merged and then update this PR to use the new method immediately.

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers labels Dec 4, 2025
@koppor

koppor commented Dec 4, 2025

Copy link
Copy Markdown
Member

See #13868 (comment) why there are two PRs.

Comment on lines +23 to +26
@FXML private Text summaryInfoText;
@FXML private CheckBox markdownCheckbox;
@FXML
private Text summaryInfoText;
@FXML
private CheckBox markdownCheckbox;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is some other code style. But I think, its OK to keep the reformatting.

Comment on lines +73 to +98
@FXML private Loadable uiLoadableChatHistory;
@FXML private ChatHistoryComponent uiChatHistory;
@FXML private Button notificationsButton;
@FXML private ChatPromptComponent chatPrompt;
@FXML private Label noticeText;
@FXML private Hyperlink exQuestion1;
@FXML private Hyperlink exQuestion2;
@FXML private Hyperlink exQuestion3;
@FXML private HBox exQuestionBox;
@FXML private HBox followUpQuestionsBox;
@FXML
private Loadable uiLoadableChatHistory;
@FXML
private ChatHistoryComponent uiChatHistory;
@FXML
private Button notificationsButton;
@FXML
private ChatPromptComponent chatPrompt;
@FXML
private Label noticeText;
@FXML
private Hyperlink exQuestion1;
@FXML
private Hyperlink exQuestion2;
@FXML
private Hyperlink exQuestion3;
@FXML
private HBox exQuestionBox;
@FXML
private HBox followUpQuestionsBox;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you revert this reformatting?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I will fix this shortly.

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: changes-required Pull requests that are not yet complete labels Dec 4, 2025
@CXZHANG0508 CXZHANG0508 requested a review from koppor December 5, 2025 16:33
Comment on lines +113 to +114
this.entryTypesManager = entryTypesManager;
this.fieldPreferences = fieldPreferences;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please - group arguments semantically together.

  • aiPreferences
  • (new) fieldPreferences
  • (new) entryTypesManager
  • dialogService

Reason: we opted for dependency injection via constructor - and Java method parameters are a mess somehow... By grouping together preferences, we try to reduce the mess.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somehow achieved, but not fully.

OK for now.

Files.writeString(path, content, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
dialogService.notify(Localization.lang("Export successful"));
} catch (IOException e) {
LOGGER.error(Localization.lang("Problem occurred while writing the export file"), e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No Localization in logging. Maybe, a hint should be added to https://devdocs.jabref.org/code-howtos/logging.html.

dialogService.notify(Localization.lang("Export successful"));
} catch (IOException e) {
LOGGER.error(Localization.lang("Problem occurred while writing the export file"), e);
dialogService.showErrorDialogAndWait(Localization.lang("Save failed"), e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why unspecific here? - I think "Problem occurred while writing the export file" is the better text.

However, please check JabRef_en.properties for other error messages. Maybe you find some with %0 included, where ex.getLocaliizedMessage() is passed to.

Comment on lines +481 to +482
LOGGER.error(Localization.lang("Problem occurred while writing the export file"), e);
dialogService.showErrorDialogAndWait(Localization.lang("Save failed"), e);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above.

Comment on lines +47 to +53
TaskExecutor taskExecutor
TaskExecutor taskExecutor,
BibEntryTypesManager entryTypesManager,
FieldPreferences fieldPreferences

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

</graphic>
<items>
<MenuItem text="%Export in human-readable format (Markdown)" onAction="#exportMarkdown"/>
<MenuItem text="%Export in machine-readable format (JSON)" onAction="#exportJson"/>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<MenuItem text="%Export in machine-readable format (JSON)" onAction="#exportJson"/>
<MenuItem text="%Export to JSON" onAction="#exportJson"/>

Reason: JSON is also considered human readable - in comparison to a binary format

role = "assistant";
content = aiMessage.text();
} else {
continue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment why the other messages are ignored

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@InAnYan @ThiloteE We should include ErrorMessage, shouldn't we?

@CXZHANG0508 Please remove "ErrorMessage" - you have it written twice in the comment

Comment on lines +94 to +96
Map<String, String> msgMap = new HashMap<>();
msgMap.put("role", role);
msgMap.put("content", content);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can shorten this with Map.of(...)

Sid track: However, shouldn't this be a record with String role, String content? - But I think, this is OK, because it might be over-engineered that way

Back=Back
Forward=Forward

Export\ successful=Export successful

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please re-use

export operation finished successfully.

Maybe, you can remove "operation" in the translation string (and change the other parts of JabRef)

Reason: We want to use the same user-facing strings for the same thing - and not have a huge set of similar strings.

Export\ chat\ history=Export chat history
No\ summary\ available\ to\ export=No summary available to export
No\ chat\ history\ to\ export=No chat history to export
Problem\ occurred\ while\ writing\ the\ export\ file=Problem occurred while writing the export file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maaybe re-use

Failed to export file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the missing localization key issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant: Please re-use existing translations. But OK for me to keep two strings. "Someone" will unify later.

@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Dec 5, 2025
@github-actions github-actions Bot removed the status: changes-required Pull requests that are not yet complete label Dec 15, 2025

@koppor koppor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked, works OKish.

Since we run out of capacity for more rounds of this, I merge.

We will create follow-up issues for improvement.

Thank you for the work so for!

Comment on lines +113 to +114
this.entryTypesManager = entryTypesManager;
this.fieldPreferences = fieldPreferences;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was somehow achieved, but not fully.

OK for now.

Comment on lines -96 to +110
this.entries = entries;
this.entries = stateManager.getSelectedEntries();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change - need to be fixed in a follow-up.

@koppor koppor added this pull request to the merge queue Dec 17, 2025
Merged via the queue into JabRef:main with commit e00ad67 Dec 17, 2025
49 checks passed
@koppor

koppor commented Dec 17, 2025

Copy link
Copy Markdown
Member

@CXZHANG0508 I think, I now collected all follow-ups. The most buggy behavior is described in #14647. Maybe, you have an idea. Then you can assign yourself and solve the issue.

@CXZHANG0508 CXZHANG0508 deleted the feat-ai-export branch December 18, 2025 05:58
Siva-Sai22 pushed a commit to Siva-Sai22/jabref that referenced this pull request Dec 19, 2025
…Ref#14486)

* Add export buttons for AI summary and chat

Why:
Quite often, JabRef users would like to share a conversation with AI, or store it in some text file that would be read later without opening JabRef.
However, currently we don't have an option of saving the conversation or summary to some external file.

How:
- Add `MenuButton` with export options to `SummaryShowingComponent.fxml`
  and `AiChatComponent.fxml`.
- Implement `exportMarkdown` method to construct Markdown content
  including the BibTeX source code.
- Implement `exportJson` method using Jackson to generate JSON output
  that adheres to the OpenAI conversation format.
- Add necessary localization keys to `JabRef_en.properties`.
- Update `CHANGELOG.md` to reflect the new feature.

Tags:
ai, export, json, markdown

Fixes JabRef#13868

* refactor(ai): move export logic to AiExporter to fix architecture violation

* refactor(ai): fix architecture violations and checkstyle issues

* feat(ai): Add Markdown and JSON export for AI Summary and AI Chat

* style: reformat code to match guidelines

* feat(ai): Add Markdown and JSON export for AI Summary and AI Chat

* feat(ai): Add Markdown and JSON export for AI Summary and AI Chat

* feat(ai): fix some format problem

* feat(ai): fix some format problem

* feat(ai): fix some format problem

* fixed the formatting issue,but not the getStringRepresentation method

* feat(ai): fix some format problem

* Fix AiExporter to use BibEntry.getStringRepresentation

* feat(ai): fix the unit tests problem

* I have reordered the constructor parameters in relevant functions to group them semantically as suggested

* I have reordered the constructor parameters in relevant functions to group them semantically as suggested

* Add comment explaining why specific chat messages are ignored in export

* fix some format problem

* fix some format problem

* Refactor constructor parameter order for semantic grouping

* fix some formatting issues

* Refactor AiExporter to strictly use StringJoiner, add the errorMessage

* fix some formatting issues

* rename variable sj to stringJoiner and use explicit empty lines

* retry

* retry

* retry

* fix the match issue

* support group export and enforce markdown linting

* fix the formatting issue

* fix the formatting issue

* reTry

* Retry

* Retry

* Retry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add export chat and summary feature to AI functionality

4 participants