diff --git a/src/main/java/dev/marcospaulo/canalhandia/Ai.java b/src/main/java/dev/marcospaulo/canalhandia/Ai.java index 5fdad58..89d8f1e 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Ai.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Ai.java @@ -62,6 +62,13 @@ final class Ai { record Answered(UUID asker, String question, String answer) { } + /** + * How many answers players have flagged wrong with {@code /ia feedback ruim}. + * Shown in {@code /canalhandia status}. Simple in-memory counter — a restart + * resets it, like {@link #askedToday}. + */ + private int feedbackWrong; + /** Package-private so Task 11's {@code /ia corrigir} can read what to correct. */ Answered lastAnswer() { return lastAnswer; @@ -77,6 +84,21 @@ final class Ai { return corrections; } + /** A player marked the last answer wrong with {@code /ia feedback ruim}. */ + boolean flagLastAnswerWrong() { + if (lastAnswer == null) { + return false; + } + feedbackWrong++; + plugin.getLogger().info("IA: resposta marcada como errada — pergunta: " + lastAnswer.question()); + return true; + } + + /** How many answers players have flagged wrong. Shown in /canalhandia status. */ + int feedbackWrong() { + return feedbackWrong; + } + Ai(Canalhandia plugin) { this.plugin = plugin; Settings settings = plugin.settings(); diff --git a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java index a802a98..a0bfa4e 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java +++ b/src/main/java/dev/marcospaulo/canalhandia/Canalhandia.java @@ -71,7 +71,8 @@ public final class Canalhandia extends JavaPlugin implements Listener { CanalhandiaCommand root = new CanalhandiaCommand(this); for (String name : List.of("canalhandia", "curiosidade", "adivinha", "enquete", "ranking", - "reagir", "reacoes", "palpite", "votar", "legal", "wow", "top", "f", "ia")) { + "reagir", "reacoes", "palpite", "votar", "legal", "wow", "top", "f", "ia", "iap", + "errado")) { register(name, root); } @@ -305,16 +306,35 @@ public final class Canalhandia extends JavaPlugin implements Listener { } /** - * Opens the reaction row on a public AI answer. Minimal for now: reuses - * the shared {@link #openReactions()} machinery. Task 11 wires the - * asker-gated feedback path and the typed {@code /reagir} twin. + * Opens the reaction row on a public AI answer: 👍 (reuses the shared + * {@code joia}/{@code /legal} reaction so the typed Bedrock twin resolves + * via {@link Settings#reactionForCommand}) and ❌ ({@code /errado}, new). + * + *

The {@code askerId} param is kept in the signature because + * {@link Ai#deliver} passes it, but is not used structurally here. */ void openAiReactions(UUID askerId) { - // TODO Task 11: asker-gated feedback and the typed /reagir twin use askerId. if (!settings.reactionsEnabled()) { return; } - openReactions(); + Reactions reactions = new Reactions(nextId++, List.of( + new ReactionDef("joia", "[👍]", "[+1]", "legal"), + new ReactionDef("errado", "[❌]", "[ERRADO]", "errado"))); + liveReactions = reactions; + remember(reactions); + reactions.show(); + getServer().getScheduler().runTaskLater(this, () -> { + reactions.hide(); + if (liveReactions == reactions) { + liveReactions = null; + } + if (reactions.hasAnyVote()) { + broadcastPerPlatform(bedrock -> Component.text(" ") + .append(reactions.summary(bedrock, settings.summaryNames()))); + } + }, settings.reactionWindowSeconds() * 20L); + broadcastPerPlatform(bedrock -> Component.text(" ") + .append(reactions.buttons(bedrock))); } /** diff --git a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java index dbe5b85..c43ddff 100644 --- a/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java +++ b/src/main/java/dev/marcospaulo/canalhandia/CanalhandiaCommand.java @@ -14,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -46,7 +47,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { case "palpite" -> guessLatest(sender, args); case "votar" -> voteLatest(sender, args); case "reacoes" -> whoReacted(sender); - case "ia" -> ia(sender, args); + case "ia" -> ia(sender, args, false); + case "iap" -> ia(sender, args, true); default -> { String reaction = plugin.settings().reactionForCommand(command.getName()); if (reaction != null) { @@ -656,10 +658,12 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { } Msg.line(sender, "categorias ativas", enabled.isEmpty() ? "nenhuma" : String.join(", ", enabled)); Msg.line(sender, "ia", plugin.ai().configured() - ? settings.aiModel() + " · " + plugin.ai().askedToday() + "/" - + settings.aiDailyLimit() + " hoje · cooldown " - + settings.aiCooldownSeconds() + "s · " + ? settings.aiModel() + " · perfil " + settings.aiProfile().name().toLowerCase(Locale.ROOT) + + " · " + plugin.ai().askedToday() + "/" + settings.aiDailyLimit() + " hoje" + + " · cooldown " + settings.aiCooldownSeconds() + "s · " + (settings.aiPublic() ? "resposta pública" : "resposta privada") + + " · " + plugin.ai().corrections().all().size() + " correções" + + " · " + plugin.ai().feedbackWrong() + " feedback ruim" : "sem chave configurada"); } @@ -754,7 +758,7 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { * has it out of the box and LuckPerms can hand it to anyone else with * {@code lp user permission set canalhandia.ia true}. */ - private boolean ia(CommandSender sender, String[] args) { + private boolean ia(CommandSender sender, String[] args, boolean isPrivate) { if (!sender.hasPermission("canalhandia.ia")) { return denied(sender); } @@ -766,14 +770,74 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter { Msg.error(sender, "Só jogadores podem usar /ia."); return true; } + // Subcommands come before the question. Tradeoff: a question that + // literally starts with "perfil", "corrigir" or "feedback" is hijacked + // — e.g. "/ia perfil do servidor" runs /ia perfil with no profile name + // and shows usage instead of asking. Acceptable on a small server, + // where the operator commands matter more than the edge case. + if (args.length > 0) { + switch (args[0].toLowerCase(Locale.ROOT)) { + case "perfil" -> { iaProfile(sender, args); return true; } + case "corrigir" -> { iaCorrect(sender, args); return true; } + case "feedback" -> { iaFeedback(sender, args); return true; } + default -> { /* fall through: treat all args as the question */ } + } + } if (args.length == 0) { - Msg.error(sender, "Uso: /ia "); + Msg.error(sender, isPrivate ? "Uso: /iap " : "Uso: /ia "); return true; } - plugin.ai().ask(player, String.join(" ", args)); + plugin.ai().ask(player, String.join(" ", args), isPrivate); return true; } + private void iaProfile(CommandSender sender, String[] args) { + if (!sender.hasPermission("canalhandia.ia.perfil")) { + denied(sender); + return; + } + if (args.length < 2) { + Msg.error(sender, "Uso: /ia perfil (atual: " + + plugin.settings().aiProfile().name().toLowerCase(Locale.ROOT) + ")"); + return; + } + AiProfile profile = AiProfile.byKey(args[1]); + plugin.settings().aiProfile(profile); + Msg.ok(sender, "Perfil da IA: " + profile.name().toLowerCase(Locale.ROOT) + + (profile == AiProfile.PRECISO ? " (consulta a wiki)" : " (sem wiki, mais rápido)")); + } + + private void iaCorrect(CommandSender sender, String[] args) { + if (!sender.hasPermission("canalhandia.ia.corrigir")) { + denied(sender); + return; + } + Ai.Answered last = plugin.ai().lastAnswer(); + if (last == null) { + Msg.error(sender, "Nenhuma resposta recente para corrigir."); + return; + } + if (args.length < 2) { + Msg.error(sender, "Uso: /ia corrigir "); + return; + } + String correct = String.join(" ", Arrays.copyOfRange(args, 1, args.length)); + plugin.ai().corrections().add(last.question(), correct); + Msg.ok(sender, "Correção registrada para perguntas parecidas com: " + last.question()); + } + + private void iaFeedback(CommandSender sender, String[] args) { + if (args.length < 2 || !args[1].equalsIgnoreCase("ruim")) { + Msg.error(sender, "Uso: /ia feedback ruim (marca a última resposta como errada)"); + return; + } + if (plugin.ai().flagLastAnswerWrong()) { + Msg.ok(sender, "Obrigado pelo feedback. Um operador vai revisar."); + } else { + Msg.error(sender, "Nenhuma resposta recente para marcar."); + } + } + private boolean admin(CommandSender sender) { if (sender.hasPermission(ADMIN)) { return true; diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a012eb7..c267706 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -93,6 +93,10 @@ reacoes: java: "[🔥]" texto: "[TOP]" comando: "top" + errado: + java: "[❌]" + texto: "[ERRADO]" + comando: "errado" # --- Jogos ------------------------------------------------------------------- diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d77347e..916a7f8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -60,6 +60,13 @@ commands: description: Faz uma pergunta simples para a IA. usage: /ia aliases: [pergunta] + iap: + description: Pergunta para a IA, resposta só para você. + usage: /iap + aliases: [iaprivado] + errado: + description: Reage com "errado" à última mensagem (resposta da IA). + usage: /errado permissions: # Declared explicitly: an undeclared Bukkit permission falls back to op-only, @@ -82,6 +89,15 @@ permissions: canalhandia.ia: description: Permite usar /ia. Padrão op; o LuckPerms pode conceder a outros. default: op + canalhandia.ia.privado: + description: Permite perguntar em privado com /iap. + default: true + canalhandia.ia.corrigir: + description: Permite registrar correções para as respostas da IA. + default: op + canalhandia.ia.perfil: + description: Permite trocar o perfil da IA entre economico e preciso. + default: op canalhandia.isento: description: Quem tem isto nunca é sorteado como assunto. default: false