From a72b5db745766ddfa11a775e391f9e9ca5e6c6ca Mon Sep 17 00:00:00 2001 From: tmcdonnell2 Date: Tue, 4 Apr 2023 16:39:06 -0700 Subject: [PATCH 1/5] Update ChatGPTCommand.java to include character limiting and rate limiting on time per user. Include modal when no option is included for formatting question to ChatGPT. --- .../features/chaptgpt/ChatGptCommand.java | 86 ++++++++++++++++++- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java index ce1b9c02f0..a9d0a9a2bc 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java @@ -1,22 +1,41 @@ package org.togetherjava.tjbot.features.chaptgpt; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.components.Modal; +import net.dv8tion.jda.api.interactions.components.text.TextInput; +import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; import org.togetherjava.tjbot.features.CommandVisibility; import org.togetherjava.tjbot.features.SlashCommandAdapter; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; + /** * The implemented command is {@code /chatgpt}, which allows users to ask ChatGPT a question, upon * which it will respond with an AI generated answer. */ public final class ChatGptCommand extends SlashCommandAdapter { private static final String QUESTION_OPTION = "question"; + private static final String MESSAGE_INPUT = "message"; + private static final int MESSAGE_INPUT_LENGTH = 200; + private static final int MIN_MESSAGE_INPUT_LENGTH = 4; + private static final Duration CACHE_DURATION = Duration.of(10, ChronoUnit.SECONDS); private final ChatGptService chatGptService; + private final Cache userIdToAskedAtCache = + Caffeine.newBuilder().maximumSize(1_000).expireAfterWrite(CACHE_DURATION).build(); + /** * Creates an instance of the chatgpt command. - * + * * @param chatGptService ChatGptService - Needed to make calls to ChatGPT API */ public ChatGptCommand(ChatGptService chatGptService) { @@ -24,14 +43,73 @@ public ChatGptCommand(ChatGptService chatGptService) { this.chatGptService = chatGptService; - getData().addOption(OptionType.STRING, QUESTION_OPTION, "What do you want to ask?", true); + getData().addOption(OptionType.STRING, QUESTION_OPTION, "What do you want to ask?", false); } @Override public void onSlashCommand(SlashCommandInteractionEvent event) { + Instant previousAskTime = userIdToAskedAtCache.getIfPresent(event.getMember().getId()); + if (previousAskTime != null) { + long timeRemainingUntilNextAsk = CACHE_DURATION.getSeconds() + - Duration.between(previousAskTime, Instant.now()).get(ChronoUnit.SECONDS); + String s = ""; + if (timeRemainingUntilNextAsk > 1) { + s = "s"; + } + + event + .reply("Sorry, you need to wait another " + timeRemainingUntilNextAsk + " second" + + s + " before asking chatGPT another question.") + .setEphemeral(true) + .queue(); + return; + } + + if (event.getInteraction().getOptions().isEmpty()) { + TextInput body = TextInput + .create(MESSAGE_INPUT, "Ask ChatGPT a question or get help with code", + TextInputStyle.PARAGRAPH) + .setPlaceholder("Put your question for ChatGPT here") + .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MESSAGE_INPUT_LENGTH) + .build(); + + Modal modal = Modal.create(generateComponentId(), "ChatGPT").addActionRow(body).build(); + event.replyModal(modal).queue(); + return; + } + + if (event.getOption(QUESTION_OPTION).getAsString().length() > MESSAGE_INPUT_LENGTH) { + event.getHook() + .sendMessage("Questions to ChatGPT must be less than " + MESSAGE_INPUT_LENGTH + + " characters in length. Please rephrase and try again.") + .queue(); + return; + } + + event.deferReply().queue(); + + Optional optional = + chatGptService.ask(event.getOption(QUESTION_OPTION).getAsString()); + if (optional.isPresent()) { + userIdToAskedAtCache.put(event.getMember().getId(), Instant.now()); + } + + String response = optional.orElse( + "An error has occurred while trying to communicate with ChatGPT. Please try again later"); + event.getHook().sendMessage(response).queue(); + } + + @Override + public void onModalSubmitted(ModalInteractionEvent event, List args) { event.deferReply().queue(); - String response = chatGptService.ask(event.getOption(QUESTION_OPTION).getAsString()) - .orElse("An error has occurred while trying to communication with ChatGPT. Please try again later"); + + Optional optional = chatGptService.ask(event.getValue(MESSAGE_INPUT).getAsString()); + if (optional.isPresent()) { + userIdToAskedAtCache.put(event.getMember().getId(), Instant.now()); + } + + String response = optional.orElse( + "An error has occurred while trying to communicate with ChatGPT. Please try again later"); event.getHook().sendMessage(response).queue(); } } From 11f3e8a617f2aa70cfe8ccfd97b05d1e0e0ffa09 Mon Sep 17 00:00:00 2001 From: tmcdonnell2 Date: Wed, 5 Apr 2023 09:27:17 -0700 Subject: [PATCH 2/5] Requested changes to ChatGptCommand.java: - Variable name clean up - Command now only uses modal - Remove optional s from string --- .../features/chaptgpt/ChatGptCommand.java | 44 ++++--------------- 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java index a9d0a9a2bc..a5f18d5293 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java @@ -4,7 +4,6 @@ import com.github.benmanes.caffeine.cache.Caffeine; import net.dv8tion.jda.api.events.interaction.ModalInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; -import net.dv8tion.jda.api.interactions.commands.OptionType; import net.dv8tion.jda.api.interactions.components.Modal; import net.dv8tion.jda.api.interactions.components.text.TextInput; import net.dv8tion.jda.api.interactions.components.text.TextInputStyle; @@ -23,9 +22,8 @@ * which it will respond with an AI generated answer. */ public final class ChatGptCommand extends SlashCommandAdapter { - private static final String QUESTION_OPTION = "question"; - private static final String MESSAGE_INPUT = "message"; - private static final int MESSAGE_INPUT_LENGTH = 200; + private static final String QUESTION_INPUT = "question"; + private static final int MAX_MESSAGE_INPUT_LENGTH = 200; private static final int MIN_MESSAGE_INPUT_LENGTH = 4; private static final Duration CACHE_DURATION = Duration.of(10, ChronoUnit.SECONDS); private final ChatGptService chatGptService; @@ -42,8 +40,6 @@ public ChatGptCommand(ChatGptService chatGptService) { super("chatgpt", "Ask the ChatGPT AI a question!", CommandVisibility.GUILD); this.chatGptService = chatGptService; - - getData().addOption(OptionType.STRING, QUESTION_OPTION, "What do you want to ask?", false); } @Override @@ -52,14 +48,10 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { if (previousAskTime != null) { long timeRemainingUntilNextAsk = CACHE_DURATION.getSeconds() - Duration.between(previousAskTime, Instant.now()).get(ChronoUnit.SECONDS); - String s = ""; - if (timeRemainingUntilNextAsk > 1) { - s = "s"; - } event - .reply("Sorry, you need to wait another " + timeRemainingUntilNextAsk + " second" - + s + " before asking chatGPT another question.") + .reply("Sorry, you need to wait another " + timeRemainingUntilNextAsk + + " second(s) before asking chatGPT another question.") .setEphemeral(true) .queue(); return; @@ -67,43 +59,23 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { if (event.getInteraction().getOptions().isEmpty()) { TextInput body = TextInput - .create(MESSAGE_INPUT, "Ask ChatGPT a question or get help with code", + .create(QUESTION_INPUT, "Ask ChatGPT a question or get help with code", TextInputStyle.PARAGRAPH) .setPlaceholder("Put your question for ChatGPT here") - .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MESSAGE_INPUT_LENGTH) + .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH) .build(); Modal modal = Modal.create(generateComponentId(), "ChatGPT").addActionRow(body).build(); event.replyModal(modal).queue(); - return; - } - - if (event.getOption(QUESTION_OPTION).getAsString().length() > MESSAGE_INPUT_LENGTH) { - event.getHook() - .sendMessage("Questions to ChatGPT must be less than " + MESSAGE_INPUT_LENGTH - + " characters in length. Please rephrase and try again.") - .queue(); - return; - } - - event.deferReply().queue(); - - Optional optional = - chatGptService.ask(event.getOption(QUESTION_OPTION).getAsString()); - if (optional.isPresent()) { - userIdToAskedAtCache.put(event.getMember().getId(), Instant.now()); } - - String response = optional.orElse( - "An error has occurred while trying to communicate with ChatGPT. Please try again later"); - event.getHook().sendMessage(response).queue(); } @Override public void onModalSubmitted(ModalInteractionEvent event, List args) { event.deferReply().queue(); - Optional optional = chatGptService.ask(event.getValue(MESSAGE_INPUT).getAsString()); + Optional optional = + chatGptService.ask(event.getValue(QUESTION_INPUT).getAsString()); if (optional.isPresent()) { userIdToAskedAtCache.put(event.getMember().getId(), Instant.now()); } From cf43e51fbdc178d23444049257e641f695b48ba3 Mon Sep 17 00:00:00 2001 From: tmcdonnell2 Date: Wed, 5 Apr 2023 13:11:27 -0700 Subject: [PATCH 3/5] Requested changes to ChatGptCommand.java: - CACHE_DURATION -> COMMAND_COOLDOWN - Unwind if statement --- .../features/chaptgpt/ChatGptCommand.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java index a5f18d5293..8ec3a38471 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java @@ -25,11 +25,11 @@ public final class ChatGptCommand extends SlashCommandAdapter { private static final String QUESTION_INPUT = "question"; private static final int MAX_MESSAGE_INPUT_LENGTH = 200; private static final int MIN_MESSAGE_INPUT_LENGTH = 4; - private static final Duration CACHE_DURATION = Duration.of(10, ChronoUnit.SECONDS); + private static final Duration COMMAND_COOLDOWN = Duration.of(10, ChronoUnit.SECONDS); private final ChatGptService chatGptService; private final Cache userIdToAskedAtCache = - Caffeine.newBuilder().maximumSize(1_000).expireAfterWrite(CACHE_DURATION).build(); + Caffeine.newBuilder().maximumSize(1_000).expireAfterWrite(COMMAND_COOLDOWN).build(); /** * Creates an instance of the chatgpt command. @@ -46,7 +46,7 @@ public ChatGptCommand(ChatGptService chatGptService) { public void onSlashCommand(SlashCommandInteractionEvent event) { Instant previousAskTime = userIdToAskedAtCache.getIfPresent(event.getMember().getId()); if (previousAskTime != null) { - long timeRemainingUntilNextAsk = CACHE_DURATION.getSeconds() + long timeRemainingUntilNextAsk = COMMAND_COOLDOWN.getSeconds() - Duration.between(previousAskTime, Instant.now()).get(ChronoUnit.SECONDS); event @@ -57,17 +57,15 @@ public void onSlashCommand(SlashCommandInteractionEvent event) { return; } - if (event.getInteraction().getOptions().isEmpty()) { - TextInput body = TextInput - .create(QUESTION_INPUT, "Ask ChatGPT a question or get help with code", - TextInputStyle.PARAGRAPH) - .setPlaceholder("Put your question for ChatGPT here") - .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH) - .build(); + TextInput body = TextInput + .create(QUESTION_INPUT, "Ask ChatGPT a question or get help with code", + TextInputStyle.PARAGRAPH) + .setPlaceholder("Put your question for ChatGPT here") + .setRequiredRange(MIN_MESSAGE_INPUT_LENGTH, MAX_MESSAGE_INPUT_LENGTH) + .build(); - Modal modal = Modal.create(generateComponentId(), "ChatGPT").addActionRow(body).build(); - event.replyModal(modal).queue(); - } + Modal modal = Modal.create(generateComponentId(), "ChatGPT").addActionRow(body).build(); + event.replyModal(modal).queue(); } @Override From b223792a8f6e098700830e57809cc1255fa2a879 Mon Sep 17 00:00:00 2001 From: tmcdonnell2 Date: Thu, 6 Apr 2023 20:59:38 -0700 Subject: [PATCH 4/5] Add new catch for RuntimeException to ChatGptService.java in order to allow for correct path to indicate to users that something went wrong. --- .../togetherjava/tjbot/features/chaptgpt/ChatGptService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java index 55c6eeb9bf..08fbe7c134 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptService.java @@ -73,6 +73,9 @@ public Optional ask(String question) { "There was an error using the OpenAI API: {} Code: {} Type: {} Status Code: {}", openAiHttpException.getMessage(), openAiHttpException.code, openAiHttpException.type, openAiHttpException.statusCode); + } catch (RuntimeException runtimeException) { + logger.warn("There was an error using the OpenAI API: {}", + runtimeException.getMessage()); } return Optional.empty(); } From 103868b1169d93bc6432926fd48c70954e70468c Mon Sep 17 00:00:00 2001 From: tmcdonnell2 Date: Fri, 14 Apr 2023 15:00:02 -0700 Subject: [PATCH 5/5] Requested changes to time calculation for cooldown in onSlashCommand() for ChatGptCommand.java --- .../togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java index 8ec3a38471..e53a34391d 100644 --- a/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java +++ b/application/src/main/java/org/togetherjava/tjbot/features/chaptgpt/ChatGptCommand.java @@ -46,8 +46,9 @@ public ChatGptCommand(ChatGptService chatGptService) { public void onSlashCommand(SlashCommandInteractionEvent event) { Instant previousAskTime = userIdToAskedAtCache.getIfPresent(event.getMember().getId()); if (previousAskTime != null) { - long timeRemainingUntilNextAsk = COMMAND_COOLDOWN.getSeconds() - - Duration.between(previousAskTime, Instant.now()).get(ChronoUnit.SECONDS); + long timeRemainingUntilNextAsk = + COMMAND_COOLDOWN.minus(Duration.between(previousAskTime, Instant.now())) + .toSeconds(); event .reply("Sorry, you need to wait another " + timeRemainingUntilNextAsk