第58批续23:AI聊天400调试日志增强 + CreditManager GUI管理员直接操作 + 主菜单40号位入口
Check Source / check (push) Successful in 3s
Auto Release / build (push) Successful in 2m26s
Simple Test / test (push) Successful in 1s
Test Env / test (push) Successful in 2s

This commit is contained in:
youpaishidifu
2026-10-28 13:15:03 +08:00
parent 6892c47599
commit d0323ea975
4 changed files with 236 additions and 4 deletions
@@ -392,6 +392,15 @@ public class AIChatManager {
String jsonBody = buildChatRequest(modelFull, systemPrompt, prompt, endpoint.maxToken);
String requestUrl = buildRequestUrl(endpoint);
// ★ 400调试日志:请求发出前打印完整信息
plugin.getLogger().info("[AI聊天-调试] === 请求开始 ===");
plugin.getLogger().info("[AI聊天-调试] 模型全名: " + modelFull);
plugin.getLogger().info("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().info("[AI聊天-调试] 自定义供应商: " + endpoint.isCustom);
plugin.getLogger().info("[AI聊天-调试] 自动补齐开关: " + autoAppendCompletions);
plugin.getLogger().info("[AI聊天-调试] 请求体JSON: " + jsonBody);
plugin.getLogger().info("[AI聊天-调试] === 请求结束 ===");
// 异步发送请求
CompletableFuture.supplyAsync(() -> {
try {
@@ -419,6 +428,19 @@ public class AIChatManager {
if (response.statusCode() != 200) {
String errBody = response.body();
// ★ 400调试日志:打印完整响应头和响应体
plugin.getLogger().warning("[AI聊天-调试] === 请求失败 ===");
plugin.getLogger().warning("[AI聊天-调试] HTTP状态码: " + response.statusCode());
plugin.getLogger().warning("[AI聊天-调试] 模型全名: " + modelFull);
plugin.getLogger().warning("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().warning("[AI聊天-调试] 请求体JSON: " + jsonBody);
// 打印完整响应头
response.headers().map().forEach((name, values) -> {
plugin.getLogger().warning("[AI聊天-调试] 响应头 " + name + ": " + String.join(", ", values));
});
plugin.getLogger().warning("[AI聊天-调试] 响应体: " + errBody);
plugin.getLogger().warning("[AI聊天-调试] === 请求失败结束 ===");
handleRequestFailure(prompt, "HTTP " + response.statusCode() + ": " + errBody);
if (player != null) {
player.sendMessage("§c[AI聊天] §f请求失败: HTTP " + response.statusCode());
@@ -621,6 +643,14 @@ public class AIChatManager {
String jsonBody = buildChatRequest(defaultModelFull, systemPrompt, prompt, endpoint.maxToken);
String requestUrl = buildRequestUrl(endpoint);
// ★ 400调试日志:自动互动请求
plugin.getLogger().info("[AI聊天-调试] === 自动互动请求开始 ===");
plugin.getLogger().info("[AI聊天-调试] 模型全名: " + defaultModelFull);
plugin.getLogger().info("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().info("[AI聊天-调试] 自定义供应商: " + endpoint.isCustom);
plugin.getLogger().info("[AI聊天-调试] 请求体JSON: " + jsonBody);
plugin.getLogger().info("[AI聊天-调试] === 自动互动请求结束 ===");
CompletableFuture.supplyAsync(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
@@ -638,6 +668,19 @@ public class AIChatManager {
}
}).thenAccept(response -> {
if (response == null || response.statusCode() != 200) {
// ★ 400调试日志:自动互动失败
if (response != null) {
plugin.getLogger().warning("[AI聊天-调试] === 自动互动请求失败 ===");
plugin.getLogger().warning("[AI聊天-调试] HTTP状态码: " + response.statusCode());
plugin.getLogger().warning("[AI聊天-调试] 模型全名: " + defaultModelFull);
plugin.getLogger().warning("[AI聊天-调试] 请求URL: " + requestUrl);
plugin.getLogger().warning("[AI聊天-调试] 请求体JSON: " + jsonBody);
response.headers().map().forEach((name, values) -> {
plugin.getLogger().warning("[AI聊天-调试] 响应头 " + name + ": " + String.join(", ", values));
});
plugin.getLogger().warning("[AI聊天-调试] 响应体: " + response.body());
plugin.getLogger().warning("[AI聊天-调试] === 自动互动请求失败结束 ===");
}
handleRequestFailure("[自动] " + prompt.substring(0, Math.min(100, prompt.length())),
"自动互动失败: HTTP " + (response != null ? response.statusCode() : "N/A"));
return;
+179 -4
View File
@@ -61,13 +61,15 @@ public class CreditManager {
ADD_SCORE_TARGET, // 加分 → 等待玩家名
ADD_SCORE_AMOUNT, // 加分 → 等待数值
DEDUCT_SCORE_TARGET, // 扣分 → 等待玩家名
DEDUCT_SCORE_AMOUNT // 扣分 → 等待数值
DEDUCT_SCORE_AMOUNT, // 扣分 → 等待数值
APPROVE_SCORE_CHANGE // 审批通过 → 等待积分变动值
}
// 玩家当前引导状态 (playerName -> FlowState + 临时数据)
private static class FlowData {
FlowState state;
String tempName; // 临时存储的目标玩家名
int recordId; // 临时存储的审批记录ID
}
private final Map<String, FlowData> chatFlows = new ConcurrentHashMap<>();
@@ -445,12 +447,63 @@ public class CreditManager {
"§7内容: " + r.get("content"),
"§7来源: " + r.get("source"),
"",
"§e/credits approve " + r.get("id") + " <通过|驳回> <理由> [积分变动]"));
"§e点击查看详情,右键通过,Shift+右键驳回"));
}
inv.setItem(53, mkItem(Material.ARROW, "§7返回"));
return inv;
}
/**
* 单条待审批记录详情 GUI — 含通过/驳回按钮
*/
public Inventory createPendingDetailGUI(Player p, int recordId) {
Inventory inv = Bukkit.createInventory(null, 9, GUI_TITLE + " - 审批详情");
fillBg(inv);
Map<String, Object> record = null;
for (Map<String, Object> r : getPendingRecords()) {
if (((Number) r.get("id")).intValue() == recordId) {
record = r;
break;
}
}
if (record == null) {
inv.setItem(3, mkItem(Material.BARRIER, "§c§l记录不存在", "§7该记录可能已被审批"));
inv.setItem(4, mkItem(Material.ARROW, "§7返回"));
return inv;
}
String type = (String) record.get("type");
Material mat = TYPE_GOOD.equals(type) ? Material.EMERALD : Material.REDSTONE;
String typeText = TYPE_GOOD.equals(type) ? "§a好人好事" : "§c举报";
// Slot 0: 通过按钮 (EMERALD)
inv.setItem(0, mkItem(Material.EMERALD,
"§a§l通过",
"§7点击通过此条上报",
"§7通过后需要输入积分变动值"));
// Slot 1: 驳回按钮 (REDSTONE)
inv.setItem(1, mkItem(Material.REDSTONE,
"§c§l驳回",
"§7点击驳回此条上报",
"§7驳回则不扣分不加分"));
// Slot 3: 记录详情
inv.setItem(3, mkItem(mat,
"§e§l#" + recordId + " " + record.get("target_player"),
"§7上报人: " + record.get("reporter_player"),
"§7类型: " + typeText,
"§7内容: " + record.get("content"),
"§7来源: " + record.get("source"),
"§7时间: " + formatTime((Long) record.get("created_at"))));
// Slot 4: 返回
inv.setItem(4, mkItem(Material.ARROW, "§7返回"));
return inv;
}
public Inventory createRecordsGUI(Player p) {
Inventory inv = Bukkit.createInventory(null, 54, GUI_TITLE + " - 历史记录");
fillBg(inv);
@@ -688,9 +741,80 @@ public class CreditManager {
return;
}
// 待审批GUI — 点击物品提示用命令审批
// 待审批GUI — 点击记录打开详情,右键直接通过,Shift+右键驳回
if (title != null && title.contains("待审批")) {
p.sendMessage("§c请使用 /credits approve <id> <通过|驳回> <理由> [积分变动]");
// 详情GUI的返回按钮
if (slot == 4) {
// 清除临时状态,避免拦截后续聊天
chatFlows.remove(p.getName());
p.closeInventory();
p.openInventory(createPendingGUI(p));
return;
}
// 详情GUI的按钮
if (title.contains("审批详情")) {
if (slot == 0) { // 通过
// 从当前打开的详情GUI中获取 recordId
// 需要从 GUI title 中解析,或者用 FlowData
// 简化:直接从 chatFlows 获取(如果有),否则提示
FlowData fd = chatFlows.get(p.getName());
if (fd == null) {
// 没有引导状态,需要从 inventory 中推断
// 改用另一种方式:在详情GUI打开时设置一个临时状态
p.sendMessage("§c§l[信誉积分] §c无法获取记录ID,请重新打开详情");
p.closeInventory();
return;
}
int rid = fd.recordId;
// 标记审批通过,等待输入积分变动
fd.state = FlowState.APPROVE_SCORE_CHANGE;
fd.tempName = null;
// 先查目标玩家名
String targetName = getRecordTarget(rid);
fd.tempName = targetName;
p.closeInventory();
p.sendMessage("§a§l[信誉积分] §f审批通过 #" + rid + " (目标: " + targetName + ")");
p.sendMessage("§7请输入积分变动值(正数加分,负数扣分,如 +10 或 -5)");
return;
}
if (slot == 1) { // 驳回
FlowData fd = chatFlows.get(p.getName());
if (fd == null) {
p.sendMessage("§c§l[信誉积分] §c无法获取记录ID,请重新打开详情");
p.closeInventory();
return;
}
int rid = fd.recordId;
approveRecord(rid, false, "管理员GUI驳回", 0, p.getName());
chatFlows.remove(p.getName());
p.closeInventory();
p.sendMessage("§c§l[信誉积分] §f已驳回 #" + rid + " (不扣分不加分)");
return;
}
}
// 列表GUI的返回按钮
if (slot == 53) {
p.closeInventory();
p.openInventory(createMainGUI(p));
return;
}
// 点击记录 → 打开详情GUI
if (slot >= 0 && slot < 49) {
List<Map<String, Object>> records = getPendingRecords();
if (slot < records.size()) {
Map<String, Object> r = records.get(slot);
int recordId = ((Number) r.get("id")).intValue();
// 先清除之前的引导状态,记录ID
chatFlows.remove(p.getName());
FlowData fd = new FlowData();
fd.state = FlowState.APPROVE_SCORE_CHANGE; // 占位,实际点击按钮时才用
fd.recordId = recordId;
chatFlows.put(p.getName(), fd);
p.closeInventory();
p.openInventory(createPendingDetailGUI(p, recordId));
return;
}
}
p.closeInventory();
return;
}
@@ -780,6 +904,23 @@ public class CreditManager {
fd.state = FlowState.DEDUCT_SCORE_AMOUNT;
}
return true;
case APPROVE_SCORE_CHANGE:
try {
String input = msg.trim();
// 支持 +10 / -5 / 10 / -5 格式
int amount = Integer.parseInt(input.replaceFirst("^[+]", ""));
int rid = fd.recordId;
String targetName = fd.tempName;
// 通过审批并加分/扣分
approveRecord(rid, true, "管理员GUI审批通过", amount, p.getName());
chatFlows.remove(p.getName());
p.sendMessage("§a§l[信誉积分] §f已通过 #" + rid + " " + targetName + " §e"
+ (amount > 0 ? "+" : "") + amount);
} catch (NumberFormatException e) {
p.sendMessage("§c请输入数字(如 +10 或 -5)");
}
return true;
}
return true;
}
@@ -823,4 +964,38 @@ public class CreditManager {
public boolean approveFromPhp(int recordId, boolean approved, String reason, int scoreChange, String approverName) {
return approveRecord(recordId, approved, reason, scoreChange, approverName);
}
// ==================== 辅助方法 ====================
/**
* 查询审批记录的目标玩家名
*/
private String getRecordTarget(int recordId) {
try {
PreparedStatement ps = db.getConnection().prepareStatement(
"SELECT target_player FROM credit_records WHERE id = ?");
ps.setInt(1, recordId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
String target = rs.getString("target_player");
ps.close();
return target;
}
ps.close();
} catch (Exception ignored) {}
return null;
}
/**
* 格式化时间戳为可读字符串
*/
private String formatTime(long timestamp) {
if (timestamp <= 0) return "未知";
try {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new java.util.Date(timestamp));
} catch (Exception e) {
return String.valueOf(timestamp);
}
}
}
+6
View File
@@ -225,6 +225,12 @@ public class GUIManager implements Listener {
? "§e点击打开GUI"
: "§e点击打开CLI"));
// ★ 信誉积分 - 40号位(领地系统旁边)
g.setItem(40, mkItem(Material.GOLD_INGOT,
"§d§l信誉积分",
"§7上报好人好事/举报违规",
"§7查看我的积分与明细"));
// ===== Row0(0-8): 自定义菜单项 =====
+8
View File
@@ -7885,6 +7885,14 @@ public class Main extends JavaPlugin
// CLI模式
Bukkit.dispatchCommand(p, "protect menu");
}
} else if (slot == 40) {
// ★ 信誉积分入口
p.closeInventory();
if (creditManager != null) {
p.openInventory(creditManager.createMainGUI(p));
} else {
p.sendMessage("§c信誉积分模块未初始化");
}
}