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) {
|
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. */
|
/** Package-private so Task 11's {@code /ia corrigir} can read what to correct. */
|
||||||
Answered lastAnswer() {
|
Answered lastAnswer() {
|
||||||
return lastAnswer;
|
return lastAnswer;
|
||||||
@@ -77,6 +84,21 @@ final class Ai {
|
|||||||
return corrections;
|
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) {
|
Ai(Canalhandia plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
Settings settings = plugin.settings();
|
Settings settings = plugin.settings();
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ public final class Canalhandia extends JavaPlugin implements Listener {
|
|||||||
|
|
||||||
CanalhandiaCommand root = new CanalhandiaCommand(this);
|
CanalhandiaCommand root = new CanalhandiaCommand(this);
|
||||||
for (String name : List.of("canalhandia", "curiosidade", "adivinha", "enquete", "ranking",
|
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);
|
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
|
* Opens the reaction row on a public AI answer: 👍 (reuses the shared
|
||||||
* the shared {@link #openReactions()} machinery. Task 11 wires the
|
* {@code joia}/{@code /legal} reaction so the typed Bedrock twin resolves
|
||||||
* asker-gated feedback path and the typed {@code /reagir} twin.
|
* 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) {
|
void openAiReactions(UUID askerId) {
|
||||||
// TODO Task 11: asker-gated feedback and the typed /reagir twin use askerId.
|
|
||||||
if (!settings.reactionsEnabled()) {
|
if (!settings.reactionsEnabled()) {
|
||||||
return;
|
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.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +47,8 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
case "palpite" -> guessLatest(sender, args);
|
case "palpite" -> guessLatest(sender, args);
|
||||||
case "votar" -> voteLatest(sender, args);
|
case "votar" -> voteLatest(sender, args);
|
||||||
case "reacoes" -> whoReacted(sender);
|
case "reacoes" -> whoReacted(sender);
|
||||||
case "ia" -> ia(sender, args);
|
case "ia" -> ia(sender, args, false);
|
||||||
|
case "iap" -> ia(sender, args, true);
|
||||||
default -> {
|
default -> {
|
||||||
String reaction = plugin.settings().reactionForCommand(command.getName());
|
String reaction = plugin.settings().reactionForCommand(command.getName());
|
||||||
if (reaction != null) {
|
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, "categorias ativas", enabled.isEmpty() ? "nenhuma" : String.join(", ", enabled));
|
||||||
Msg.line(sender, "ia", plugin.ai().configured()
|
Msg.line(sender, "ia", plugin.ai().configured()
|
||||||
? settings.aiModel() + " · " + plugin.ai().askedToday() + "/"
|
? settings.aiModel() + " · perfil " + settings.aiProfile().name().toLowerCase(Locale.ROOT)
|
||||||
+ settings.aiDailyLimit() + " hoje · cooldown "
|
+ " · " + plugin.ai().askedToday() + "/" + settings.aiDailyLimit() + " hoje"
|
||||||
+ settings.aiCooldownSeconds() + "s · "
|
+ " · cooldown " + settings.aiCooldownSeconds() + "s · "
|
||||||
+ (settings.aiPublic() ? "resposta pública" : "resposta privada")
|
+ (settings.aiPublic() ? "resposta pública" : "resposta privada")
|
||||||
|
+ " · " + plugin.ai().corrections().all().size() + " correções"
|
||||||
|
+ " · " + plugin.ai().feedbackWrong() + " feedback ruim"
|
||||||
: "sem chave configurada");
|
: "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
|
* has it out of the box and LuckPerms can hand it to anyone else with
|
||||||
* {@code lp user <nome> permission set canalhandia.ia true}.
|
* {@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")) {
|
if (!sender.hasPermission("canalhandia.ia")) {
|
||||||
return denied(sender);
|
return denied(sender);
|
||||||
}
|
}
|
||||||
@@ -766,14 +770,74 @@ final class CanalhandiaCommand implements CommandExecutor, TabCompleter {
|
|||||||
Msg.error(sender, "Só jogadores podem usar /ia.");
|
Msg.error(sender, "Só jogadores podem usar /ia.");
|
||||||
return true;
|
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) {
|
if (args.length == 0) {
|
||||||
Msg.error(sender, "Uso: /ia <pergunta>");
|
Msg.error(sender, isPrivate ? "Uso: /iap <pergunta>" : "Uso: /ia <pergunta>");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
plugin.ai().ask(player, String.join(" ", args));
|
plugin.ai().ask(player, String.join(" ", args), isPrivate);
|
||||||
return true;
|
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) {
|
private boolean admin(CommandSender sender) {
|
||||||
if (sender.hasPermission(ADMIN)) {
|
if (sender.hasPermission(ADMIN)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -93,6 +93,10 @@ reacoes:
|
|||||||
java: "[🔥]"
|
java: "[🔥]"
|
||||||
texto: "[TOP]"
|
texto: "[TOP]"
|
||||||
comando: "top"
|
comando: "top"
|
||||||
|
errado:
|
||||||
|
java: "[❌]"
|
||||||
|
texto: "[ERRADO]"
|
||||||
|
comando: "errado"
|
||||||
|
|
||||||
# --- Jogos -------------------------------------------------------------------
|
# --- Jogos -------------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ commands:
|
|||||||
description: Faz uma pergunta simples para a IA.
|
description: Faz uma pergunta simples para a IA.
|
||||||
usage: /ia <pergunta>
|
usage: /ia <pergunta>
|
||||||
aliases: [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:
|
permissions:
|
||||||
# Declared explicitly: an undeclared Bukkit permission falls back to op-only,
|
# Declared explicitly: an undeclared Bukkit permission falls back to op-only,
|
||||||
@@ -82,6 +89,15 @@ permissions:
|
|||||||
canalhandia.ia:
|
canalhandia.ia:
|
||||||
description: Permite usar /ia. Padrão op; o LuckPerms pode conceder a outros.
|
description: Permite usar /ia. Padrão op; o LuckPerms pode conceder a outros.
|
||||||
default: op
|
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:
|
canalhandia.isento:
|
||||||
description: Quem tem isto nunca é sorteado como assunto.
|
description: Quem tem isto nunca é sorteado como assunto.
|
||||||
default: false
|
default: false
|
||||||
|
|||||||
Reference in New Issue
Block a user