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 13, 2024
1 parent f49d430 commit 158379e
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 0 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
@@ -0,0 +1,164 @@
package org.togetherjava.tjbot.features.tophelper.ai;

import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message;
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;

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;
}

// The question threads posted in #questions
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<?>> futures = new ArrayList<>();

for (ThreadChannel question : questions) {

// Skip any questions that were not created since the first of the current month
OffsetDateTime creation = question.getTimeCreated();
if (creation.isBefore(getFirstDayOfMonth())) {
continue;
}

CompletableFuture<?> future = new CompletableFuture<>();
StringBuilder allMessages = new StringBuilder();

MessageHistory messageHistory =
MessageHistory.getHistoryFromBeginning(question).complete();
List<Message> history = messageHistory.getRetrievedHistory();

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.ask(CHATGPT_PROMPT.formatted(allMessages.toString()));
topHelper.ifPresent(potentialTopHelpers::add);
} else {
logger.trace("No messages found");
}

future.complete(null);
futures.add(future);
}

for (CompletableFuture<?> future : futures) {
future.join();
}

sendCommandResponse(event, potentialTopHelpers);
}

private void sendCommandResponse(SlashCommandInteractionEvent event,
List<String> potentialTopHelpers) {
List<String> topHelpers = calculateTopHelpers(potentialTopHelpers);
StringBuilder response = new StringBuilder();

// Ensure top helper is valid and generate response message
for (String topHelper : topHelpers) {
try {
User user = event.getJDA().getUserById(topHelper);
if (user != null) {
String mention = user.getAsMention();
response.append(mention).append("\n");
}
} catch (NumberFormatException e) {
logger.debug("ChatGPT could not determine top helper: {}", topHelper);
}
}

EmbedBuilder eb = new EmbedBuilder();
eb.setColor(Color.MAGENTA);
eb.setTitle("Top Helpers");
if (response.isEmpty()) {
eb.setDescription("None at the moment");
} else {
eb.setDescription(response.toString());
}
event.getHook().editOriginalEmbeds(eb.build()).queue();
}

private static List<String> calculateTopHelpers(List<String> potentialTopHelpers) {
Map<String, Integer> frequencyMap = new HashMap<>();
for (String helper : potentialTopHelpers) {
frequencyMap.put(helper, frequencyMap.getOrDefault(helper, 0) + 1);
}
List<Map.Entry<String, Integer>> sortedEntries = new ArrayList<>(frequencyMap.entrySet());
sortedEntries.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));
List<String> topHelpers = new ArrayList<>();
for (int i = 0; i < Math.min(5, sortedEntries.size()); i++) {
topHelpers.add(sortedEntries.get(i).getKey());
}
return topHelpers;
}

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

0 comments on commit 158379e

Please sign in to comment.