Skip to content

Commit

Permalink
Create /top-helper-ai slash command
Browse files Browse the repository at this point in the history
  • Loading branch information
surajkumar committed Sep 14, 2024
1 parent f49d430 commit 4c5efe9
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
import org.togetherjava.tjbot.features.tophelper.TopHelpersCommand;
import org.togetherjava.tjbot.features.tophelper.TopHelpersMessageListener;
import org.togetherjava.tjbot.features.tophelper.TopHelpersPurgeMessagesRoutine;
import org.togetherjava.tjbot.features.tophelper.ai.AITopHelperCommand;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -192,6 +193,7 @@ public static Collection<Feature> createFeatures(JDA jda, Database database, Con
features.add(new BookmarksCommand(bookmarksSystem));
features.add(new ChatGptCommand(chatGptService, helpSystemHelper));
features.add(new JShellCommand(jshellEval));
features.add(new AITopHelperCommand(chatGptService));

FeatureBlacklist<Class<?>> blacklist = blacklistConfig.normal();
return blacklist.filterStream(features.stream(), Object::getClass).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,7 @@ public Optional<String> ask(String question, String context) {
.formatted(context, instructions, question);
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(),
Objects.requireNonNull(questionWithContext));
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(AI_MODEL)
.messages(List.of(chatMessage))
.frequencyPenalty(FREQUENCY_PENALTY)
.temperature(TEMPERATURE)
.maxTokens(MAX_TOKENS)
.n(MAX_NUMBER_OF_RESPONSES)
.build();
ChatCompletionRequest chatCompletionRequest = chatCompletionRequest(chatMessage);

String response = openAiService.createChatCompletion(chatCompletionRequest)
.getChoices()
Expand All @@ -133,4 +126,32 @@ public Optional<String> ask(String question, String context) {
}
return Optional.empty();
}

public Optional<String> askQuestion(String question) {
ChatCompletionRequest chatCompletionRequest =
chatCompletionRequest(new ChatMessage(ChatMessageRole.USER.value(), question));

String response = openAiService.createChatCompletion(chatCompletionRequest)
.getChoices()
.getFirst()
.getMessage()
.getContent();

if (response == null) {
return Optional.empty();
}

return Optional.of(response);
}

private static ChatCompletionRequest chatCompletionRequest(ChatMessage chatMessage) {
return ChatCompletionRequest.builder()
.model(AI_MODEL)
.messages(List.of(chatMessage))
.frequencyPenalty(FREQUENCY_PENALTY)
.temperature(TEMPERATURE)
.maxTokens(MAX_TOKENS)
.n(MAX_NUMBER_OF_RESPONSES)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package org.togetherjava.tjbot.features.tophelper.ai;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageHistory;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
import net.dv8tion.jda.api.entities.channel.concrete.ThreadChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
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.ChatGptService;

import java.awt.*;
import java.time.OffsetDateTime;
import java.time.YearMonth;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

public class AITopHelperCommand extends SlashCommandAdapter {
private static final Logger logger = LoggerFactory.getLogger(AITopHelperCommand.class);
private static final String COMMAND_NAME = "top-helper-ai";
private static final String QUESTIONS_CHANNEL_NAME = "questions";
private static final String CHATGPT_PROMPT =
"""
The following contains user IDs and their message. Using the messages provided by each user,
which user ID was the most helpful/answered the question. If there are no meaningful messages, you must still choose somebody.
ONLY provide the user ID of that person. Do not reply with anything else.
%s
""";

private final ChatGptService chatGptService;

public AITopHelperCommand(ChatGptService chatGptService) {
super(COMMAND_NAME,
"Uses AI to determine who the current top helper is from the beginning of the month",
CommandVisibility.GUILD);
this.chatGptService = chatGptService;
}

@Override
public void onSlashCommand(SlashCommandInteractionEvent event) {
List<ForumChannel> channels =
event.getJDA().getForumChannelsByName(QUESTIONS_CHANNEL_NAME, true);

if (channels.isEmpty()) {
event.reply("No forum " + QUESTIONS_CHANNEL_NAME + " found").queue();
return;
}

List<ThreadChannel> questions = channels.getFirst().getThreadChannels();

if (questions.isEmpty()) {
event.reply("No thread channels found").queue();
return;
}

event.deferReply().queue();
determineTopHelper(event, questions);
}

private void determineTopHelper(SlashCommandInteractionEvent event,
List<ThreadChannel> questions) {
List<String> potentialTopHelpers = new ArrayList<>();

List<CompletableFuture<Void>> futures = questions.stream()
.filter(question -> !question.getTimeCreated().isBefore(getFirstDayOfMonth()))
.map(question -> processQuestionAsync(question, potentialTopHelpers))
.toList();

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

sendCommandResponse(event, potentialTopHelpers);
}

private CompletableFuture<Void> processQuestionAsync(ThreadChannel question,
List<String> potentialTopHelpers) {
return MessageHistory.getHistoryFromBeginning(question)
.submit()
.thenApply(MessageHistory::getRetrievedHistory)
.thenAccept(history -> {
StringBuilder allMessages = new StringBuilder();
history.forEach(message -> {
User author = message.getAuthor();
if (author.getIdLong() != question.getOwnerIdLong() && !author.isBot()) {
String content = message.getContentStripped();
allMessages.append(author.getIdLong())
.append(": ")
.append(content)
.append("\n\n");
}
});

if (!allMessages.isEmpty()) {
Optional<String> topHelper = chatGptService
.askQuestion(CHATGPT_PROMPT.formatted(allMessages.toString()));
topHelper.ifPresent(potentialTopHelpers::add);
} else {
logger.trace("No messages found");
}
});
}

private void sendCommandResponse(SlashCommandInteractionEvent event,
List<String> potentialTopHelpers) {
Map<String, Integer> topHelpers = calculateTopHelpers(potentialTopHelpers);

String response = topHelpers.entrySet().stream().map(entry -> {
String userId = entry.getKey();
int count = entry.getValue();
try {
User user = event.getJDA().getUserById(userId);
return (user != null) ? user.getAsMention() + " " + count : null;
} catch (NumberFormatException e) {
logger.debug("Invalid user ID encountered: {}", userId);
return null;
}
}).filter(Objects::nonNull).collect(Collectors.joining("\n"));

EmbedBuilder eb = new EmbedBuilder().setColor(Color.MAGENTA)
.setTitle("Top Helpers")
.setDescription(response.isEmpty() ? "None at the moment" : response);

event.getHook().editOriginalEmbeds(eb.build()).queue();
}

private static Map<String, Integer> calculateTopHelpers(List<String> potentialTopHelpers) {
Map<String, Integer> frequencyMap = new HashMap<>();
for (String helper : potentialTopHelpers) {
frequencyMap.merge(helper, 1, Integer::sum);
}
return frequencyMap;
}

private static OffsetDateTime getFirstDayOfMonth() {
OffsetDateTime now = OffsetDateTime.now();
return YearMonth.from(now).atDay(1).atStartOfDay().atOffset(now.getOffset());
}
}

0 comments on commit 4c5efe9

Please sign in to comment.