Skip to content

Commit e4f888c

Browse files
committed
Feature: Implement /rewrite command for message improvement using ChatGPT
1 parent 44bb86b commit e4f888c

File tree

4 files changed

+292
-0
lines changed

4 files changed

+292
-0
lines changed

application/src/main/java/org/togetherjava/tjbot/features/Features.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand;
4141
import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener;
4242
import org.togetherjava.tjbot.features.messages.MessageCommand;
43+
import org.togetherjava.tjbot.features.messages.RewriteMsgCommand;
44+
import org.togetherjava.tjbot.features.messages.RewriteMsgService;
4345
import org.togetherjava.tjbot.features.moderation.BanCommand;
4446
import org.togetherjava.tjbot.features.moderation.KickCommand;
4547
import org.togetherjava.tjbot.features.moderation.ModerationActionsStore;
@@ -125,6 +127,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
125127
TopHelpersService topHelpersService = new TopHelpersService(database);
126128
TopHelpersAssignmentRoutine topHelpersAssignmentRoutine =
127129
new TopHelpersAssignmentRoutine(config, topHelpersService);
130+
RewriteMsgService rewriteService = new RewriteMsgService(chatGptService, helpSystemHelper);
128131

129132
// NOTE The system can add special system relevant commands also by itself,
130133
// hence this list may not necessarily represent the full list of all commands actually
@@ -205,6 +208,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
205208
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
206209
features.add(new JShellCommand(jshellEval));
207210
features.add(new MessageCommand());
211+
features.add(new RewriteMsgCommand(rewriteService));
208212

209213
FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
210214
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package org.togetherjava.tjbot.features.messages;
2+
3+
import net.dv8tion.jda.api.entities.MessageEmbed;
4+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
5+
import net.dv8tion.jda.api.interactions.commands.OptionType;
6+
import net.dv8tion.jda.api.interactions.commands.build.OptionData;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import org.togetherjava.tjbot.features.CommandVisibility;
11+
import org.togetherjava.tjbot.features.SlashCommandAdapter;
12+
13+
import java.util.Arrays;
14+
import java.util.Optional;
15+
16+
/**
17+
* The implemented command is {@code /rewrite}, which allows users to have their message rewritten
18+
* in a clearer, more professional, or better structured form using ChatGPT AI.
19+
* <p>
20+
* The rewritten message is shown as an ephemeral message visible only to the user who triggered the
21+
* command, making it perfect for getting quick writing improvements without cluttering the channel.
22+
* <p>
23+
* Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR.
24+
*/
25+
public final class RewriteMsgCommand extends SlashCommandAdapter {
26+
private static final Logger logger = LoggerFactory.getLogger(RewriteMsgCommand.class);
27+
public static final String COMMAND_NAME = "rewrite";
28+
private static final String MESSAGE_OPTION = "message";
29+
private static final String TONE_OPTION = "tone";
30+
private static final int MAX_MESSAGE_LENGTH = 500;
31+
private static final int MIN_MESSAGE_LENGTH = 3;
32+
33+
private final RewriteMsgService rewriteMsgService;
34+
35+
36+
public RewriteMsgCommand(RewriteMsgService rewriteMsgService) {
37+
super(COMMAND_NAME, "Rewrite your message in a clearer, more professional form",
38+
CommandVisibility.GUILD);
39+
40+
this.rewriteMsgService = rewriteMsgService;
41+
42+
logger.debug("Initializing RewriteMsgCommand with ChatGptService and HelpSystemHelper");
43+
44+
final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION,
45+
"The tone/style for the rewritten message (default: "
46+
+ RewriteMsgTone.CLEAR.getDisplayName() + ")",
47+
false);
48+
49+
logger.debug("Adding tone choices to command options");
50+
Arrays.stream(RewriteMsgTone.values()).forEach(tone -> {
51+
toneOption.addChoice(tone.getDisplayName(), tone.name());
52+
logger.debug("Added tone choice: {} ({})", tone.getDisplayName(), tone.name());
53+
});
54+
55+
final OptionData messageOption =
56+
new OptionData(OptionType.STRING, MESSAGE_OPTION, "The message you want to rewrite",
57+
true)
58+
.setMinLength(MIN_MESSAGE_LENGTH)
59+
.setMaxLength(MAX_MESSAGE_LENGTH);
60+
61+
logger.debug("Configured message option: min={}, max={}", MIN_MESSAGE_LENGTH,
62+
MAX_MESSAGE_LENGTH);
63+
64+
super.getData().addOptions(messageOption, toneOption);
65+
logger.debug("RewriteMsgCommand initialization complete");
66+
}
67+
68+
@Override
69+
public void onSlashCommand(SlashCommandInteractionEvent event) {
70+
logger.debug("onSlashCommand method invoked");
71+
event.deferReply(true).queue();
72+
logger.debug("Reply deferred as ephemeral");
73+
74+
final String userId = event.getUser().getId();
75+
76+
logger.info("Rewrite command triggered by user: {}", userId);
77+
78+
final String userMessage =
79+
this.rewriteMsgService.validateMsg(event.getOption(MESSAGE_OPTION), userId);
80+
81+
final RewriteMsgTone tone =
82+
this.rewriteMsgService.parseTone(event.getOption(TONE_OPTION), userId);
83+
84+
final Optional<String> rewrittenMessage =
85+
this.rewriteMsgService.rewrite(userMessage, tone, userId);
86+
87+
final Optional<MessageEmbed> responseEmbed =
88+
this.rewriteMsgService.buildResponse(userMessage, rewrittenMessage.orElse(null),
89+
tone, userId, event.getJDA().getSelfUser());
90+
91+
logger.debug("Sending embed response to user: {}", userId);
92+
93+
if (responseEmbed.isPresent()) {
94+
event.getHook()
95+
.sendMessageEmbeds(responseEmbed.get())
96+
.queue(_ -> logger.info("Rewrite response sent successfully to user: {}", userId),
97+
error -> logger.error("Failed to send rewrite response to user: {}", userId,
98+
error));
99+
} else {
100+
logger.error("Failed to build response embed for user: {}", userId);
101+
event.getHook()
102+
.sendMessage(
103+
"An error occurred while processing your request. Please try again later.")
104+
.queue();
105+
}
106+
}
107+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package org.togetherjava.tjbot.features.messages;
2+
3+
import net.dv8tion.jda.api.entities.MessageEmbed;
4+
import net.dv8tion.jda.api.entities.SelfUser;
5+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
6+
import org.jetbrains.annotations.Nullable;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import org.togetherjava.tjbot.features.chatgpt.ChatGptModel;
11+
import org.togetherjava.tjbot.features.chatgpt.ChatGptService;
12+
import org.togetherjava.tjbot.features.help.HelpSystemHelper;
13+
14+
import java.util.Optional;
15+
16+
/**
17+
* Service for handling rewrite command business logic and ChatGPT integration.
18+
*/
19+
public class RewriteMsgService {
20+
private static final Logger logger = LoggerFactory.getLogger(RewriteMsgService.class);
21+
private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel.HIGH_QUALITY;
22+
23+
private final ChatGptService chatGptService;
24+
private final HelpSystemHelper helper;
25+
26+
/**
27+
* Creates a new RewriteMsgService.
28+
*
29+
* @param chatGptService the ChatGPT service
30+
* @param helper the help system helper for embed formatting
31+
*/
32+
public RewriteMsgService(ChatGptService chatGptService, HelpSystemHelper helper) {
33+
this.chatGptService = chatGptService;
34+
this.helper = helper;
35+
}
36+
37+
public String validateMsg(@Nullable OptionMapping messageOption, String userId) {
38+
logger.debug("Extracting message option for user: {}", userId);
39+
logger.debug("Retrieved message option: {}", messageOption != null ? "present" : "null");
40+
41+
final String userMessage = messageOption != null ? messageOption.getAsString() : "";
42+
43+
if (userMessage.isEmpty()) {
44+
logger.warn("User {} provided an empty message", userId);
45+
} else {
46+
logger.debug("User {} provided message of length: {}", userId, userMessage.length());
47+
logMessagePreview(userMessage);
48+
}
49+
50+
return userMessage;
51+
}
52+
53+
public RewriteMsgTone parseTone(@Nullable OptionMapping toneOption, String userId) {
54+
logger.debug("Extracting tone option for user: {}", userId);
55+
logger.debug("Retrieved tone option: {}", toneOption != null ? "present" : "null");
56+
57+
if (toneOption == null) {
58+
logger.debug("Tone option not provided, using default: {}",
59+
RewriteMsgTone.CLEAR.getDisplayName());
60+
return RewriteMsgTone.CLEAR;
61+
}
62+
63+
final String toneValue = toneOption.getAsString();
64+
try {
65+
final RewriteMsgTone tone = RewriteMsgTone.valueOf(toneValue);
66+
logger.debug("Parsed tone value from option: {}", toneValue);
67+
return tone;
68+
} catch (IllegalArgumentException e) {
69+
logger.error("Invalid tone value provided: {}, using default CLEAR", toneValue, e);
70+
return RewriteMsgTone.CLEAR;
71+
}
72+
}
73+
74+
public Optional<String> rewrite(String userMessage, RewriteMsgTone tone, String userId) {
75+
logger.debug("Rewriting message for user {} with tone: {}", userId, tone.getDisplayName());
76+
77+
final String rewritePrompt = buildChatGptPrompt(userMessage, tone);
78+
logger.debug("ChatGPT prompt prepared: {} characters", rewritePrompt.length());
79+
80+
try {
81+
final Optional<String> rewrittenMessage = chatGptService.ask(rewritePrompt,
82+
"Professional writing improvement", CHAT_GPT_MODEL);
83+
84+
if (rewrittenMessage.isPresent()) {
85+
logger.info("Successfully rewrote message for user: {} with tone: {}", userId,
86+
tone.getDisplayName());
87+
logMessagePreview(rewrittenMessage.get());
88+
} else {
89+
logger.warn("ChatGPT returned empty response for user: {}", userId);
90+
}
91+
92+
return rewrittenMessage;
93+
} catch (Exception e) {
94+
logger.error("Failed to rewrite message for user: {}", userId, e);
95+
return Optional.empty();
96+
}
97+
}
98+
99+
public Optional<MessageEmbed> buildResponse(String userMessage,
100+
@Nullable String rewrittenMessage, RewriteMsgTone tone, String userId,
101+
SelfUser selfUser) {
102+
logger.debug("Building response embed for user: {}", userId);
103+
104+
final String responseContent = rewrittenMessage != null ? rewrittenMessage
105+
: "Sorry, I couldn't rewrite your message at this time. Please try again later.";
106+
final String embedTitle = "Rewritten message (" + tone.getDisplayName() + ")";
107+
logger.debug("Prepared embed title: {}", embedTitle);
108+
109+
try {
110+
final MessageEmbed responseEmbed = helper.generateGptResponseEmbed(
111+
"**Original:**\n" + userMessage + "\n\n**Rewritten:**\n" + responseContent,
112+
selfUser, embedTitle, CHAT_GPT_MODEL);
113+
logger.debug("Message embed created successfully for user: {}", userId);
114+
return Optional.of(responseEmbed);
115+
} catch (Exception e) {
116+
logger.error("Failed to create message embed for user: {}", userId, e);
117+
return Optional.empty();
118+
}
119+
}
120+
121+
private String buildChatGptPrompt(String userMessage, RewriteMsgTone tone) {
122+
return """
123+
Please rewrite the following message to make it clearer, more professional, \
124+
and better structured. Maintain the original meaning while improving the quality \
125+
of the writing. Do NOT use em-dashes (—). %s
126+
127+
If the message is already well-written, provide minor improvements.
128+
129+
Original message:
130+
%s""".formatted(tone.getPromptInstruction(), userMessage);
131+
}
132+
133+
private void logMessagePreview(String message) {
134+
final int previewLength = Math.min(50, message.length());
135+
final String preview = message.substring(0, previewLength);
136+
137+
logger.debug("Message content preview: {}", preview);
138+
}
139+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.togetherjava.tjbot.features.messages;
2+
3+
/**
4+
* Enum representing the available tone/style options for message rewriting.
5+
* <p>
6+
* Each tone provides a specific instruction to ChatGPT for how to approach the rewrite.
7+
*/
8+
public enum RewriteMsgTone {
9+
CLEAR("Clear"),
10+
PRO("Pro"),
11+
DETAILED("Detailed"),
12+
TECHNICAL("Technical");
13+
14+
private final String displayName;
15+
16+
RewriteMsgTone(String displayName) {
17+
this.displayName = displayName;
18+
}
19+
20+
/**
21+
* Gets the display name of this tone.
22+
*
23+
* @return the display name
24+
*/
25+
public String getDisplayName() {
26+
return displayName;
27+
}
28+
29+
/**
30+
* Gets the prompt instruction for this tone.
31+
*
32+
* @return the prompt instruction to include in ChatGPT prompt
33+
*/
34+
public String getPromptInstruction() {
35+
return switch (this) {
36+
case CLEAR -> "Make it clear and easy to understand.";
37+
case PRO -> "Use a professional and polished tone.";
38+
case DETAILED -> "Expand with more detail and explanation.";
39+
case TECHNICAL -> "Use technical and specialized language where appropriate.";
40+
};
41+
}
42+
}

0 commit comments

Comments
 (0)