From e4f888ccc13d55b487d396cd5485f41757ed0c65 Mon Sep 17 00:00:00 2001 From: Firas Regaieg Date: Tue, 13 Jan 2026 22:45:16 +0100 Subject: [PATCH 1/2] Feature: Implement /rewrite command for message improvement using ChatGPT --- .../togetherjava/tjbot/features/Features.java | 4 + .../features/messages/RewriteMsgCommand.java | 107 ++++++++++++++ .../features/messages/RewriteMsgService.java | 139 ++++++++++++++++++ .../features/messages/RewriteMsgTone.java | 42 ++++++ 4 files changed, 292 insertions(+) create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java create mode 100644 application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 8ed07eff6c..5d90873229 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -40,6 +40,8 @@ import org.togetherjava.tjbot.features.mathcommands.wolframalpha.WolframAlphaCommand; import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener; import org.togetherjava.tjbot.features.messages.MessageCommand; +import org.togetherjava.tjbot.features.messages.RewriteMsgCommand; +import org.togetherjava.tjbot.features.messages.RewriteMsgService; import org.togetherjava.tjbot.features.moderation.BanCommand; import org.togetherjava.tjbot.features.moderation.KickCommand; import org.togetherjava.tjbot.features.moderation.ModerationActionsStore; @@ -125,6 +127,7 @@ public static Collection createFeatures(JDA jda, Database database, Con TopHelpersService topHelpersService = new TopHelpersService(database); TopHelpersAssignmentRoutine topHelpersAssignmentRoutine = new TopHelpersAssignmentRoutine(config, topHelpersService); + RewriteMsgService rewriteService = new RewriteMsgService(chatGptService, helpSystemHelper); // NOTE The system can add special system relevant commands also by itself, // hence this list may not necessarily represent the full list of all commands actually @@ -205,6 +208,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new ChatGptCommand(chatGptService, helpSystemHelper)); features.add(new JShellCommand(jshellEval)); features.add(new MessageCommand()); + features.add(new RewriteMsgCommand(rewriteService)); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java new file mode 100644 index 0000000000..d56af57076 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java @@ -0,0 +1,107 @@ +package org.togetherjava.tjbot.features.messages; + +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.features.CommandVisibility; +import org.togetherjava.tjbot.features.SlashCommandAdapter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * The implemented command is {@code /rewrite}, which allows users to have their message rewritten + * in a clearer, more professional, or better structured form using ChatGPT AI. + *

+ * The rewritten message is shown as an ephemeral message visible only to the user who triggered the + * command, making it perfect for getting quick writing improvements without cluttering the channel. + *

+ * Users can optionally specify a tone/style for the rewrite. If not provided, defaults to CLEAR. + */ +public final class RewriteMsgCommand extends SlashCommandAdapter { + private static final Logger logger = LoggerFactory.getLogger(RewriteMsgCommand.class); + public static final String COMMAND_NAME = "rewrite"; + private static final String MESSAGE_OPTION = "message"; + private static final String TONE_OPTION = "tone"; + private static final int MAX_MESSAGE_LENGTH = 500; + private static final int MIN_MESSAGE_LENGTH = 3; + + private final RewriteMsgService rewriteMsgService; + + + public RewriteMsgCommand(RewriteMsgService rewriteMsgService) { + super(COMMAND_NAME, "Rewrite your message in a clearer, more professional form", + CommandVisibility.GUILD); + + this.rewriteMsgService = rewriteMsgService; + + logger.debug("Initializing RewriteMsgCommand with ChatGptService and HelpSystemHelper"); + + final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION, + "The tone/style for the rewritten message (default: " + + RewriteMsgTone.CLEAR.getDisplayName() + ")", + false); + + logger.debug("Adding tone choices to command options"); + Arrays.stream(RewriteMsgTone.values()).forEach(tone -> { + toneOption.addChoice(tone.getDisplayName(), tone.name()); + logger.debug("Added tone choice: {} ({})", tone.getDisplayName(), tone.name()); + }); + + final OptionData messageOption = + new OptionData(OptionType.STRING, MESSAGE_OPTION, "The message you want to rewrite", + true) + .setMinLength(MIN_MESSAGE_LENGTH) + .setMaxLength(MAX_MESSAGE_LENGTH); + + logger.debug("Configured message option: min={}, max={}", MIN_MESSAGE_LENGTH, + MAX_MESSAGE_LENGTH); + + super.getData().addOptions(messageOption, toneOption); + logger.debug("RewriteMsgCommand initialization complete"); + } + + @Override + public void onSlashCommand(SlashCommandInteractionEvent event) { + logger.debug("onSlashCommand method invoked"); + event.deferReply(true).queue(); + logger.debug("Reply deferred as ephemeral"); + + final String userId = event.getUser().getId(); + + logger.info("Rewrite command triggered by user: {}", userId); + + final String userMessage = + this.rewriteMsgService.validateMsg(event.getOption(MESSAGE_OPTION), userId); + + final RewriteMsgTone tone = + this.rewriteMsgService.parseTone(event.getOption(TONE_OPTION), userId); + + final Optional rewrittenMessage = + this.rewriteMsgService.rewrite(userMessage, tone, userId); + + final Optional responseEmbed = + this.rewriteMsgService.buildResponse(userMessage, rewrittenMessage.orElse(null), + tone, userId, event.getJDA().getSelfUser()); + + logger.debug("Sending embed response to user: {}", userId); + + if (responseEmbed.isPresent()) { + event.getHook() + .sendMessageEmbeds(responseEmbed.get()) + .queue(_ -> logger.info("Rewrite response sent successfully to user: {}", userId), + error -> logger.error("Failed to send rewrite response to user: {}", userId, + error)); + } else { + logger.error("Failed to build response embed for user: {}", userId); + event.getHook() + .sendMessage( + "An error occurred while processing your request. Please try again later.") + .queue(); + } + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java new file mode 100644 index 0000000000..2bbc047afa --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java @@ -0,0 +1,139 @@ +package org.togetherjava.tjbot.features.messages; + +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.SelfUser; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.togetherjava.tjbot.features.chatgpt.ChatGptModel; +import org.togetherjava.tjbot.features.chatgpt.ChatGptService; +import org.togetherjava.tjbot.features.help.HelpSystemHelper; + +import java.util.Optional; + +/** + * Service for handling rewrite command business logic and ChatGPT integration. + */ +public class RewriteMsgService { + private static final Logger logger = LoggerFactory.getLogger(RewriteMsgService.class); + private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel.HIGH_QUALITY; + + private final ChatGptService chatGptService; + private final HelpSystemHelper helper; + + /** + * Creates a new RewriteMsgService. + * + * @param chatGptService the ChatGPT service + * @param helper the help system helper for embed formatting + */ + public RewriteMsgService(ChatGptService chatGptService, HelpSystemHelper helper) { + this.chatGptService = chatGptService; + this.helper = helper; + } + + public String validateMsg(@Nullable OptionMapping messageOption, String userId) { + logger.debug("Extracting message option for user: {}", userId); + logger.debug("Retrieved message option: {}", messageOption != null ? "present" : "null"); + + final String userMessage = messageOption != null ? messageOption.getAsString() : ""; + + if (userMessage.isEmpty()) { + logger.warn("User {} provided an empty message", userId); + } else { + logger.debug("User {} provided message of length: {}", userId, userMessage.length()); + logMessagePreview(userMessage); + } + + return userMessage; + } + + public RewriteMsgTone parseTone(@Nullable OptionMapping toneOption, String userId) { + logger.debug("Extracting tone option for user: {}", userId); + logger.debug("Retrieved tone option: {}", toneOption != null ? "present" : "null"); + + if (toneOption == null) { + logger.debug("Tone option not provided, using default: {}", + RewriteMsgTone.CLEAR.getDisplayName()); + return RewriteMsgTone.CLEAR; + } + + final String toneValue = toneOption.getAsString(); + try { + final RewriteMsgTone tone = RewriteMsgTone.valueOf(toneValue); + logger.debug("Parsed tone value from option: {}", toneValue); + return tone; + } catch (IllegalArgumentException e) { + logger.error("Invalid tone value provided: {}, using default CLEAR", toneValue, e); + return RewriteMsgTone.CLEAR; + } + } + + public Optional rewrite(String userMessage, RewriteMsgTone tone, String userId) { + logger.debug("Rewriting message for user {} with tone: {}", userId, tone.getDisplayName()); + + final String rewritePrompt = buildChatGptPrompt(userMessage, tone); + logger.debug("ChatGPT prompt prepared: {} characters", rewritePrompt.length()); + + try { + final Optional rewrittenMessage = chatGptService.ask(rewritePrompt, + "Professional writing improvement", CHAT_GPT_MODEL); + + if (rewrittenMessage.isPresent()) { + logger.info("Successfully rewrote message for user: {} with tone: {}", userId, + tone.getDisplayName()); + logMessagePreview(rewrittenMessage.get()); + } else { + logger.warn("ChatGPT returned empty response for user: {}", userId); + } + + return rewrittenMessage; + } catch (Exception e) { + logger.error("Failed to rewrite message for user: {}", userId, e); + return Optional.empty(); + } + } + + public Optional buildResponse(String userMessage, + @Nullable String rewrittenMessage, RewriteMsgTone tone, String userId, + SelfUser selfUser) { + logger.debug("Building response embed for user: {}", userId); + + final String responseContent = rewrittenMessage != null ? rewrittenMessage + : "Sorry, I couldn't rewrite your message at this time. Please try again later."; + final String embedTitle = "Rewritten message (" + tone.getDisplayName() + ")"; + logger.debug("Prepared embed title: {}", embedTitle); + + try { + final MessageEmbed responseEmbed = helper.generateGptResponseEmbed( + "**Original:**\n" + userMessage + "\n\n**Rewritten:**\n" + responseContent, + selfUser, embedTitle, CHAT_GPT_MODEL); + logger.debug("Message embed created successfully for user: {}", userId); + return Optional.of(responseEmbed); + } catch (Exception e) { + logger.error("Failed to create message embed for user: {}", userId, e); + return Optional.empty(); + } + } + + private String buildChatGptPrompt(String userMessage, RewriteMsgTone tone) { + return """ + Please rewrite the following message to make it clearer, more professional, \ + and better structured. Maintain the original meaning while improving the quality \ + of the writing. Do NOT use em-dashes (—). %s + + If the message is already well-written, provide minor improvements. + + Original message: + %s""".formatted(tone.getPromptInstruction(), userMessage); + } + + private void logMessagePreview(String message) { + final int previewLength = Math.min(50, message.length()); + final String preview = message.substring(0, previewLength); + + logger.debug("Message content preview: {}", preview); + } +} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java new file mode 100644 index 0000000000..debc2d1503 --- /dev/null +++ b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java @@ -0,0 +1,42 @@ +package org.togetherjava.tjbot.features.messages; + +/** + * Enum representing the available tone/style options for message rewriting. + *

+ * Each tone provides a specific instruction to ChatGPT for how to approach the rewrite. + */ +public enum RewriteMsgTone { + CLEAR("Clear"), + PRO("Pro"), + DETAILED("Detailed"), + TECHNICAL("Technical"); + + private final String displayName; + + RewriteMsgTone(String displayName) { + this.displayName = displayName; + } + + /** + * Gets the display name of this tone. + * + * @return the display name + */ + public String getDisplayName() { + return displayName; + } + + /** + * Gets the prompt instruction for this tone. + * + * @return the prompt instruction to include in ChatGPT prompt + */ + public String getPromptInstruction() { + return switch (this) { + case CLEAR -> "Make it clear and easy to understand."; + case PRO -> "Use a professional and polished tone."; + case DETAILED -> "Expand with more detail and explanation."; + case TECHNICAL -> "Use technical and specialized language where appropriate."; + }; + } +} From 5707f50ac71722c1618ace857dab13f4055ed36f Mon Sep 17 00:00:00 2001 From: Firas Regaieg Date: Fri, 16 Jan 2026 18:49:10 +0100 Subject: [PATCH 2/2] feature "rewrite-msg command": applies changes due to Zabu's first review; --- .../togetherjava/tjbot/features/Features.java | 4 +- .../features/messages/RewriteMsgCommand.java | 144 ++++++++++++------ .../features/messages/RewriteMsgService.java | 139 ----------------- .../features/messages/RewriteMsgTone.java | 42 ----- 4 files changed, 96 insertions(+), 233 deletions(-) delete mode 100644 application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java delete mode 100644 application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java diff --git a/application/src/main/java/org/togetherjava/tjbot/features/Features.java b/application/src/main/java/org/togetherjava/tjbot/features/Features.java index 88bf4032c9..619a83a601 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/Features.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/Features.java @@ -42,7 +42,6 @@ import org.togetherjava.tjbot.features.mediaonly.MediaOnlyChannelListener; import org.togetherjava.tjbot.features.messages.MessageCommand; import org.togetherjava.tjbot.features.messages.RewriteMsgCommand; -import org.togetherjava.tjbot.features.messages.RewriteMsgService; import org.togetherjava.tjbot.features.moderation.BanCommand; import org.togetherjava.tjbot.features.moderation.KickCommand; import org.togetherjava.tjbot.features.moderation.ModerationActionsStore; @@ -128,7 +127,6 @@ public static Collection createFeatures(JDA jda, Database database, Con TopHelpersService topHelpersService = new TopHelpersService(database); TopHelpersAssignmentRoutine topHelpersAssignmentRoutine = new TopHelpersAssignmentRoutine(config, topHelpersService); - RewriteMsgService rewriteService = new RewriteMsgService(chatGptService, helpSystemHelper); // NOTE The system can add special system relevant commands also by itself, // hence this list may not necessarily represent the full list of all commands actually @@ -210,7 +208,7 @@ public static Collection createFeatures(JDA jda, Database database, Con features.add(new ChatGptCommand(chatGptService, helpSystemHelper)); features.add(new JShellCommand(jshellEval)); features.add(new MessageCommand()); - features.add(new RewriteMsgCommand(rewriteService)); + features.add(new RewriteMsgCommand(chatGptService)); FeatureBlacklist> blacklist = blacklistConfig.normal(); return blacklist.filterStream(features.stream(), Object::getClass).toList(); diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java index d56af57076..7877d35c6f 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgCommand.java @@ -1,21 +1,25 @@ package org.togetherjava.tjbot.features.messages; -import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.SlashCommandAdapter; +import org.togetherjava.tjbot.features.chatgpt.ChatGptModel; +import org.togetherjava.tjbot.features.chatgpt.ChatGptService; import java.util.Arrays; +import java.util.Objects; import java.util.Optional; /** - * The implemented command is {@code /rewrite}, which allows users to have their message rewritten - * in a clearer, more professional, or better structured form using ChatGPT AI. + * The implemented command is {@code /rewrite-msg}, which allows users to have their message + * rewritten in a clearer, more professional, or better structured form using ChatGPT AI. *

* The rewritten message is shown as an ephemeral message visible only to the user who triggered the * command, making it perfect for getting quick writing improvements without cluttering the channel. @@ -24,33 +28,49 @@ */ public final class RewriteMsgCommand extends SlashCommandAdapter { private static final Logger logger = LoggerFactory.getLogger(RewriteMsgCommand.class); - public static final String COMMAND_NAME = "rewrite"; + public static final String COMMAND_NAME = "rewrite-msg"; private static final String MESSAGE_OPTION = "message"; private static final String TONE_OPTION = "tone"; private static final int MAX_MESSAGE_LENGTH = 500; private static final int MIN_MESSAGE_LENGTH = 3; + private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel.HIGH_QUALITY; - private final RewriteMsgService rewriteMsgService; + private final ChatGptService chatGptService; + private static String buildResponse(String userMessage, String rewrittenMessage, MsgTone tone) { + final String toneLabel = tone.displayName; - public RewriteMsgCommand(RewriteMsgService rewriteMsgService) { - super(COMMAND_NAME, "Rewrite your message in a clearer, more professional form", - CommandVisibility.GUILD); + return """ + **Rewritten message (%s)** - this.rewriteMsgService = rewriteMsgService; + **Original:** + %s - logger.debug("Initializing RewriteMsgCommand with ChatGptService and HelpSystemHelper"); + **Rewritten:** + %s""".formatted(toneLabel, userMessage, rewrittenMessage); + } - final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION, - "The tone/style for the rewritten message (default: " - + RewriteMsgTone.CLEAR.getDisplayName() + ")", - false); + private static String buildChatGptPrompt(String userMessage, MsgTone tone) { + return """ + Please rewrite the following message to make it clearer, more professional, \ + and better structured. Maintain the original meaning while improving the quality \ + of the writing. Do NOT use em-dashes (—). %s + + If the message is already well-written, provide minor improvements. - logger.debug("Adding tone choices to command options"); - Arrays.stream(RewriteMsgTone.values()).forEach(tone -> { - toneOption.addChoice(tone.getDisplayName(), tone.name()); - logger.debug("Added tone choice: {} ({})", tone.getDisplayName(), tone.name()); - }); + Original message: + %s""".formatted(tone.description, userMessage); + } + + /** + * Creates the slash command definition and configures available options for rewriting messages. + * + * @param chatGptService service for interacting with ChatGPT + */ + public RewriteMsgCommand(ChatGptService chatGptService) { + super(COMMAND_NAME, "Let AI rephrase and improve your message", CommandVisibility.GUILD); + + this.chatGptService = chatGptService; final OptionData messageOption = new OptionData(OptionType.STRING, MESSAGE_OPTION, "The message you want to rewrite", @@ -58,50 +78,76 @@ public RewriteMsgCommand(RewriteMsgService rewriteMsgService) { .setMinLength(MIN_MESSAGE_LENGTH) .setMaxLength(MAX_MESSAGE_LENGTH); - logger.debug("Configured message option: min={}, max={}", MIN_MESSAGE_LENGTH, - MAX_MESSAGE_LENGTH); + final OptionData toneOption = new OptionData(OptionType.STRING, TONE_OPTION, + "The tone/style for the rewritten message (default: " + MsgTone.CLEAR.displayName + + ")", + false); + + Arrays.stream(MsgTone.values()) + .forEach(tone -> toneOption.addChoice(tone.displayName, tone.name())); - super.getData().addOptions(messageOption, toneOption); - logger.debug("RewriteMsgCommand initialization complete"); + getData().addOptions(messageOption, toneOption); } @Override public void onSlashCommand(SlashCommandInteractionEvent event) { - logger.debug("onSlashCommand method invoked"); + final String userMessage = + Objects.requireNonNull(event.getOption(MESSAGE_OPTION)).getAsString(); + + final MsgTone tone = parseTone(event.getOption(TONE_OPTION), event.getUser().getId()); + event.deferReply(true).queue(); - logger.debug("Reply deferred as ephemeral"); - final String userId = event.getUser().getId(); + final Optional rewrittenMessage = this.rewrite(userMessage, tone); - logger.info("Rewrite command triggered by user: {}", userId); + if (rewrittenMessage.isEmpty()) { + logger.debug("Failed to obtain a response for /rewrite-msg, original message: '{}'", + userMessage); + event.getHook() + .editOriginal( + "An error occurred while processing your request. Please try again later.") + .queue(); + return; + } - final String userMessage = - this.rewriteMsgService.validateMsg(event.getOption(MESSAGE_OPTION), userId); + final String response = buildResponse(userMessage, rewrittenMessage.orElseThrow(), tone); - final RewriteMsgTone tone = - this.rewriteMsgService.parseTone(event.getOption(TONE_OPTION), userId); + event.getHook().editOriginal(response).queue(); + } - final Optional rewrittenMessage = - this.rewriteMsgService.rewrite(userMessage, tone, userId); + private MsgTone parseTone(@Nullable OptionMapping toneOption, String userId) + throws IllegalArgumentException { + if (toneOption == null) { + logger.debug("Tone option not provided for user: {}, using default CLEAR", userId); + return MsgTone.CLEAR; + } - final Optional responseEmbed = - this.rewriteMsgService.buildResponse(userMessage, rewrittenMessage.orElse(null), - tone, userId, event.getJDA().getSelfUser()); + final String toneValue = toneOption.getAsString(); + final MsgTone tone = MsgTone.valueOf(toneValue); - logger.debug("Sending embed response to user: {}", userId); + logger.debug("Parsed tone '{}' for user: {}", tone.displayName, userId); - if (responseEmbed.isPresent()) { - event.getHook() - .sendMessageEmbeds(responseEmbed.get()) - .queue(_ -> logger.info("Rewrite response sent successfully to user: {}", userId), - error -> logger.error("Failed to send rewrite response to user: {}", userId, - error)); - } else { - logger.error("Failed to build response embed for user: {}", userId); - event.getHook() - .sendMessage( - "An error occurred while processing your request. Please try again later.") - .queue(); + return tone; + } + + private Optional rewrite(String userMessage, MsgTone tone) { + final String rewritePrompt = buildChatGptPrompt(userMessage, tone); + + return chatGptService.ask(rewritePrompt, tone.displayName, CHAT_GPT_MODEL); + } + + private enum MsgTone { + CLEAR("Clear", "Make it clear and easy to understand."), + PRO("Pro", "Use a professional and polished tone."), + DETAILED("Detailed", "Expand with more detail and explanation."), + TECHNICAL("Technical", "Use technical and specialized language where appropriate."); + + private final String displayName; + private final String description; + + MsgTone(String displayName, String description) { + this.displayName = displayName; + this.description = description; } } } diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java deleted file mode 100644 index 2bbc047afa..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgService.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.togetherjava.tjbot.features.messages; - -import net.dv8tion.jda.api.entities.MessageEmbed; -import net.dv8tion.jda.api.entities.SelfUser; -import net.dv8tion.jda.api.interactions.commands.OptionMapping; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.togetherjava.tjbot.features.chatgpt.ChatGptModel; -import org.togetherjava.tjbot.features.chatgpt.ChatGptService; -import org.togetherjava.tjbot.features.help.HelpSystemHelper; - -import java.util.Optional; - -/** - * Service for handling rewrite command business logic and ChatGPT integration. - */ -public class RewriteMsgService { - private static final Logger logger = LoggerFactory.getLogger(RewriteMsgService.class); - private static final ChatGptModel CHAT_GPT_MODEL = ChatGptModel.HIGH_QUALITY; - - private final ChatGptService chatGptService; - private final HelpSystemHelper helper; - - /** - * Creates a new RewriteMsgService. - * - * @param chatGptService the ChatGPT service - * @param helper the help system helper for embed formatting - */ - public RewriteMsgService(ChatGptService chatGptService, HelpSystemHelper helper) { - this.chatGptService = chatGptService; - this.helper = helper; - } - - public String validateMsg(@Nullable OptionMapping messageOption, String userId) { - logger.debug("Extracting message option for user: {}", userId); - logger.debug("Retrieved message option: {}", messageOption != null ? "present" : "null"); - - final String userMessage = messageOption != null ? messageOption.getAsString() : ""; - - if (userMessage.isEmpty()) { - logger.warn("User {} provided an empty message", userId); - } else { - logger.debug("User {} provided message of length: {}", userId, userMessage.length()); - logMessagePreview(userMessage); - } - - return userMessage; - } - - public RewriteMsgTone parseTone(@Nullable OptionMapping toneOption, String userId) { - logger.debug("Extracting tone option for user: {}", userId); - logger.debug("Retrieved tone option: {}", toneOption != null ? "present" : "null"); - - if (toneOption == null) { - logger.debug("Tone option not provided, using default: {}", - RewriteMsgTone.CLEAR.getDisplayName()); - return RewriteMsgTone.CLEAR; - } - - final String toneValue = toneOption.getAsString(); - try { - final RewriteMsgTone tone = RewriteMsgTone.valueOf(toneValue); - logger.debug("Parsed tone value from option: {}", toneValue); - return tone; - } catch (IllegalArgumentException e) { - logger.error("Invalid tone value provided: {}, using default CLEAR", toneValue, e); - return RewriteMsgTone.CLEAR; - } - } - - public Optional rewrite(String userMessage, RewriteMsgTone tone, String userId) { - logger.debug("Rewriting message for user {} with tone: {}", userId, tone.getDisplayName()); - - final String rewritePrompt = buildChatGptPrompt(userMessage, tone); - logger.debug("ChatGPT prompt prepared: {} characters", rewritePrompt.length()); - - try { - final Optional rewrittenMessage = chatGptService.ask(rewritePrompt, - "Professional writing improvement", CHAT_GPT_MODEL); - - if (rewrittenMessage.isPresent()) { - logger.info("Successfully rewrote message for user: {} with tone: {}", userId, - tone.getDisplayName()); - logMessagePreview(rewrittenMessage.get()); - } else { - logger.warn("ChatGPT returned empty response for user: {}", userId); - } - - return rewrittenMessage; - } catch (Exception e) { - logger.error("Failed to rewrite message for user: {}", userId, e); - return Optional.empty(); - } - } - - public Optional buildResponse(String userMessage, - @Nullable String rewrittenMessage, RewriteMsgTone tone, String userId, - SelfUser selfUser) { - logger.debug("Building response embed for user: {}", userId); - - final String responseContent = rewrittenMessage != null ? rewrittenMessage - : "Sorry, I couldn't rewrite your message at this time. Please try again later."; - final String embedTitle = "Rewritten message (" + tone.getDisplayName() + ")"; - logger.debug("Prepared embed title: {}", embedTitle); - - try { - final MessageEmbed responseEmbed = helper.generateGptResponseEmbed( - "**Original:**\n" + userMessage + "\n\n**Rewritten:**\n" + responseContent, - selfUser, embedTitle, CHAT_GPT_MODEL); - logger.debug("Message embed created successfully for user: {}", userId); - return Optional.of(responseEmbed); - } catch (Exception e) { - logger.error("Failed to create message embed for user: {}", userId, e); - return Optional.empty(); - } - } - - private String buildChatGptPrompt(String userMessage, RewriteMsgTone tone) { - return """ - Please rewrite the following message to make it clearer, more professional, \ - and better structured. Maintain the original meaning while improving the quality \ - of the writing. Do NOT use em-dashes (—). %s - - If the message is already well-written, provide minor improvements. - - Original message: - %s""".formatted(tone.getPromptInstruction(), userMessage); - } - - private void logMessagePreview(String message) { - final int previewLength = Math.min(50, message.length()); - final String preview = message.substring(0, previewLength); - - logger.debug("Message content preview: {}", preview); - } -} diff --git a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java b/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java deleted file mode 100644 index debc2d1503..0000000000 --- a/application/src/main/java/org/togetherjava/tjbot/features/messages/RewriteMsgTone.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.togetherjava.tjbot.features.messages; - -/** - * Enum representing the available tone/style options for message rewriting. - *

- * Each tone provides a specific instruction to ChatGPT for how to approach the rewrite. - */ -public enum RewriteMsgTone { - CLEAR("Clear"), - PRO("Pro"), - DETAILED("Detailed"), - TECHNICAL("Technical"); - - private final String displayName; - - RewriteMsgTone(String displayName) { - this.displayName = displayName; - } - - /** - * Gets the display name of this tone. - * - * @return the display name - */ - public String getDisplayName() { - return displayName; - } - - /** - * Gets the prompt instruction for this tone. - * - * @return the prompt instruction to include in ChatGPT prompt - */ - public String getPromptInstruction() { - return switch (this) { - case CLEAR -> "Make it clear and easy to understand."; - case PRO -> "Use a professional and polished tone."; - case DETAILED -> "Expand with more detail and explanation."; - case TECHNICAL -> "Use technical and specialized language where appropriate."; - }; - } -}