feat: /iap, reactions on answers, feedback and /ia corrigir
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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).
|
||||
*
|
||||
* <p>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)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 <nome> 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 <pergunta>");
|
||||
Msg.error(sender, isPrivate ? "Uso: /iap <pergunta>" : "Uso: /ia <pergunta>");
|
||||
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 <economico|preciso> (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 <resposta correta>");
|
||||
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;
|
||||
|
||||
@@ -93,6 +93,10 @@ reacoes:
|
||||
java: "[🔥]"
|
||||
texto: "[TOP]"
|
||||
comando: "top"
|
||||
errado:
|
||||
java: "[❌]"
|
||||
texto: "[ERRADO]"
|
||||
comando: "errado"
|
||||
|
||||
# --- Jogos -------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -60,6 +60,13 @@ commands:
|
||||
description: Faz uma pergunta simples para a IA.
|
||||
usage: /ia <pergunta>
|
||||
aliases: [pergunta]
|
||||
iap:
|
||||
description: Pergunta para a IA, resposta só para você.
|
||||
usage: /iap <pergunta>
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user