-
-
Notifications
You must be signed in to change notification settings - Fork 105
Feature: Implement /rewrite command for message improvement using Cha… #1378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/Features.java
Outdated
Show resolved
Hide resolved
tj-wazei
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please see comments. There's a lot of final abuse that does nothing but add clutter to the code. It's best to use final when you need it.
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteCommand.java
Show resolved
Hide resolved
| return; | ||
| } | ||
|
|
||
| final String response = buildResponse(userMessage, rewrittenMessage.orElseThrow(), tone); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary use of orElseThrow since it will never throw. You are doing an isEmpty check above.
Ideally, this entire block is a good candidate for rewrittenMessage.ifPresentOrElse() if you want that fluency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to be fair, if (foo.isEmpty()) ... foo.orElseThrow() is the normal way ur supposed to deal with this in case you are not able to utilize the other, better, methods of the API. (but in this case, try to put the extraction of the content right after the if-check, not 20 lines later)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
u missed the point on this one @firasrg. all u did now was swapping orElseThrow with get, thats not an improvement. orElseThrow is better.
what wazei was suggesting is that u should prefer one of the "this or that" methods from the Optional-API over "if ... return ... orElseThrow".
So you should use sth like ifPresentOrElse(...) or ifPresent(...) (check out the API for a good fit) instead of
if (maybeFoo.isEmpty()) {
...
return;
}
Foo foo = maybeFoo.orElseThrow();
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zabuzard I think it's solved with #1378 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@firasrg either make it .get() or perhaps the following:
rewrittenMessage.ifPresentOrElse(
rewrittenText -> {
event.getHook().editOriginal(rewrittenText).queue();
},
() -> {
logger.debug("Failed to obtain a response for /{}, original message: '{}'", COMMAND_NAME, userMessage);
event.getHook()
.editOriginal("An error occurred while processing your request. Please try again later.")
.queue();
}
);There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just don't want to see your code suggesting a failure path that can’t happen
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want, you can also remove Optional entirely, and inside rewrite
return chatGptService.askRaw(rewritePrompt, aiModel).orElse(""); // null, empty string whatever
then your logic could be:
if(rewrittenMessage.isBlank()) {
logger.debug("Failed to obtain a response for /{}, original message: '{}'",
COMMAND_NAME, userMessage);
event.getHook()
.editOriginal(
"An error occurred while processing your request. Please try again later.")
.queue();
return;
}
logger.debug("Rewrite successful; rewritten message length: {}", rewrittenMessage.length());
event.getHook().sendMessage(rewrittenMessage).setEphemeral(true).queue();
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
| private Optional<String> rewrite(String userMessage, MsgTone tone) { | ||
| final String rewritePrompt = buildChatGptPrompt(userMessage, tone); | ||
|
|
||
| return chatGptService.ask(rewritePrompt, tone.displayName, CHAT_GPT_MODEL); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to update the ask method. The original implementor of the ChatGptService did not make this abstract enough so your request will be mixed with the following prefixed:
For code supplied for review, refer to the old code supplied rather than rewriting the code. DON'T supply a corrected version of the code.
KEEP IT CONCISE, NOT MORE THAN 280 WORDS
%s
Question: %s
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
| @Override | ||
| public void onSlashCommand(SlashCommandInteractionEvent event) { | ||
| final String userMessage = | ||
| Objects.requireNonNull(event.getOption(MESSAGE_OPTION)).getAsString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for requireNonNull
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the getOption() return is Nullable based on its docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, but in this case its a required parameter, so it wont be null.
Also even if it was null, this is the worst way to handle it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the api declares it as nullable and thats correct. but the question here is: what do you expect to get?
since u declared this parameter as required beforehand, it must not be null. if it is null, there is a severe bug in JDA and the bot should crash! (fail-fast).
so what you want is "crash if null". and for that you can either write Objects.requireNonNull(foo).bar() or simply just foo.bar(). Both are valid options to handle it and you can make an argument for either option.
we have had this discussion roughly two years back with a bigger audience and decided that Objects.requireNonNull adds too much visual clutter for this and foo.bar() is generally preferred.
but ultimately its a bit NIT.
situations where Objects.requireNonNull is really important to use though is when you do not have a situation like foo.bar() but bar(foo), as in this case a NPE wouldnt be thrown instantly. so bar(Objects.requireNonNull(foo)) would be reasonable in that situation to achieve fail-fast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to fail fast without requireNonNull(), we grab the required message option once, throw if it’s unexpectedly missing (using the MESSAGE_OPTION in the message), then call getAsString()
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteCommand.java
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteCommand.java
Show resolved
Hide resolved
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java
Outdated
Show resolved
Hide resolved
Zabuzard
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff. Apart from what the others mentioned, im happy now 👍
| if (response.length() <= Message.MAX_CONTENT_LENGTH) { | ||
| return attempt; | ||
| } | ||
|
|
||
| logger.debug("Rewritten message exceeded {} characters; retrying with stricter constraint", | ||
| Message.MAX_CONTENT_LENGTH); | ||
|
|
||
| final String shortenPrompt = rewritePrompt | ||
| + "\n\nConstraint reminder: Your previous rewrite exceeded " | ||
| + Message.MAX_CONTENT_LENGTH | ||
| + " characters. Provide a revised rewrite strictly under " | ||
| + Message.MAX_CONTENT_LENGTH + " characters while preserving meaning and tone."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the MAX_MESSAGE_LENGTH constant defined in the class in place of Message.MAX_CONTENT_LENGTH where appropriate.
|
tj-wazei
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there, I still really hate the usage of final, my eyes kept having to "jump" as I was reading your code.
| .model(chatModel.toChatModel()) | ||
| .input(inputPrompt) | ||
| .input(prompt) | ||
| .maxOutputTokens(MAX_TOKENS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we're here now, something we've learned is that the MAX_TOKENS should not exceed 2000. Please can you update MAX_TOKENS to equal Message.MAX_CONTENT_LENGTH
| - Improve clarity and structure | ||
| - Maintain the original meaning | ||
| - Avoid em-dashes (—) | ||
| - Stay under %d characters (strict limit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stay under %d characters (strict limit) can be enforced via the MAX_TOKENS field in ChatGptService. Consider adding a parameter to the ask method. If doing so, ensure to validate that the value <= Message.MAX_TOKENS
| private static String createAiPrompt(String userMessage, MessageTone tone) { | ||
| return """ | ||
| You are rewriting a Discord text chat message for clarity and professionalism. | ||
| Keep it conversational and casual—NOT email or formal document format. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep it conversational and casual, not email or formal document format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additionally, let's move this out into a constant e.g.
private static final String AI_REWRITE_PROMPT_TEMPLATE = """
You are rewriting a Discord text chat message for clarity and professionalism.
Keep it conversational and casual, not email or formal document format.
Tone: %s
Rewrite the message to:
- Improve clarity and structure
- Maintain the original meaning
- Avoid em-dashes (—)
- Stay under %d characters (strict limit)
If the message is already well-written, make only minor improvements.
Message to rewrite:
%s
""".stripIndent();
...
private static String createAiPrompt(String userMessage, MessageTone tone) {
return AI_REWRITE_PROMPT_TEMPLATE.formatted(
tone.description,
MAX_MESSAGE_LENGTH,
userMessage
);
}| private final ChatGptService chatGptService; | ||
|
|
||
| private static ChatGptModel selectAiModel(MessageTone tone) { | ||
| return switch (tone) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional: Just add the ChatGptModel to the enum and remove this entire function.
|
|
||
| private final ChatGptService chatGptService; | ||
|
|
||
| private static ChatGptModel selectAiModel(MessageTone tone) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we move all private static functions to the bottom of the class please? Naturally, we expect to see the constructor first.
| final OptionMapping messageOption = event.getOption(MESSAGE_OPTION); | ||
|
|
||
| if (messageOption == null) { | ||
| throw new IllegalStateException("Required option '" + MESSAGE_OPTION + "' is missing"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: technically correct but semantically, I'd prefer IllegalArgumentException.
application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteCommand.java
Show resolved
Hide resolved
| return MessageTone.CLEAR; | ||
| } | ||
|
|
||
| final String toneValue = toneOption.getAsString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be inlined i.e. return MessageTone.valueOf(toneOption.getAsString());
| return attempt; | ||
| } | ||
|
|
||
| logger.debug("Rewritten message exceeded {} characters; retrying with stricter constraint", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of this code can be removed if you use the max token parameter.
This method can then be:
private Optional<String> rewrite(String userMessage, MessageTone tone) {
return chatGptService.askRaw(createAiPrompt(userMessage, tone), selectAiModel(tone)); // if updating askRaw pass the MAX_CONTENT_LENGTH as a parameter
}| return chatGptService.askRaw(shortenPrompt, aiModel); | ||
| } | ||
|
|
||
| private enum MessageTone { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned earlier, you can add the ChatGptModel here and entirely remove the selectAiModel method



New Feature: Rewrite Command
The
/rewritecommand allows users to have their messages rewritten in a clearer, more professional, or better structured form using ChatGPT AI (GPT 5 mini). The rewritten message is displayed as an ephemeral message (visible only to the user), making it perfect for getting quick writing improvements without cluttering the channel where they are.Usage
Parameters
CLEARif not providedAvailable Tones
How It Works
Example
UPDATE 16 Jan 2026
/rewrite-msgfor consistency with the class in source code.UPDATE 20 Jan 2026
/rewrite.Prohas been renamed toProfessionnalChatGptServiceAPI,askRawto avoid touching existing method and for future reusabilities