-
-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f49d430
commit 158379e
Showing
2 changed files
with
166 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
...cation/src/main/java/org/togetherjava/tjbot/features/tophelper/ai/AITopHelperCommand.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |