From 0944aa1a85e2fcb6771a84b752961bd9262c97f7 Mon Sep 17 00:00:00 2001 From: ddaodan <731882332@qq.com> Date: Sun, 21 Jul 2024 14:18:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=BF=9E=E7=BB=AD=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=20=E8=B0=83=E8=AF=95=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 16 +++- .../ddaodan/MineChatGPT/CommandHandler.java | 83 +++++++++++++++++-- .../ddaodan/MineChatGPT/ConfigManager.java | 36 +++++++- .../MineChatGPT/ConversationContext.java | 29 +++++++ .../java/com/ddaodan/MineChatGPT/Main.java | 3 + .../MineChatGPT/MineChatGPTTabCompleter.java | 2 + src/main/resources/config.yml | 11 +++ src/main/resources/config_zh.yml | 11 +++ src/main/resources/plugin.yml | 8 +- 9 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/ddaodan/MineChatGPT/ConversationContext.java diff --git a/readme.md b/readme.md index d257bec..07e2fad 100644 --- a/readme.md +++ b/readme.md @@ -13,13 +13,24 @@ - [ ] 自定义prompt ## 安装 -- 下载插件,放在plugins文件夹中 -- 重启服务器 +1. 下载插件,放在plugins文件夹中 +2. 重启服务器 > 为兼容更多版本,插件使用1.13版本进行构建,因此在较高版本加载插件时,控制台会出现以下错误信息,这属于正常现象。 > ``` > [Server thread/WARN]: Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug! > [Server thread/WARN]: Legacy plugin MineChatGPT v1.0 does not specify an api-version. > ``` +3. 打开配置文件`config.yml`,修改以下两项设置: +```yaml +# API 相关设置 +api: + # 你的 OpenAI API key,用于身份验证 + # 获取 API key 的方法:访问 https://platform.openai.com/account/api-keys 并创建一个新的 API key + key: "sk-your_openai_api_key" + # OpenAI API 的基础 URL,用于构建请求 + base_url: "https://api.openai.com/v1" +``` +4. 输入`/chatgpt reload`重新加载配置文件 ## 截图 - 服务端截图(Spigot 1.20.1) @@ -95,6 +106,7 @@ version: 2.1 |Mohist 1.20.1|✔| |Spigot 1.20.1|✔| |Spigot 1.12.2|✔| +|KCauldron 1.7.10|×| ## 常见问题 ### `Failed to contact ChatGPT.` `无法联系ChatGPT。` diff --git a/src/main/java/com/ddaodan/MineChatGPT/CommandHandler.java b/src/main/java/com/ddaodan/MineChatGPT/CommandHandler.java index bba1dbc..30e99a1 100644 --- a/src/main/java/com/ddaodan/MineChatGPT/CommandHandler.java +++ b/src/main/java/com/ddaodan/MineChatGPT/CommandHandler.java @@ -9,6 +9,10 @@ import org.json.JSONObject; import java.util.logging.Level; import java.util.logging.Logger; import java.util.List; +import java.util.HashMap; +import java.util.Map; +import java.nio.charset.StandardCharsets; +import java.io.UnsupportedEncodingException; import jodd.http.HttpRequest; import jodd.http.HttpResponse; @@ -16,20 +20,33 @@ public class CommandHandler implements CommandExecutor { private final Main plugin; private final ConfigManager configManager; private static final Logger logger = Logger.getLogger(CommandHandler.class.getName()); + private final Map userContexts; + private final Map userContextEnabled; public CommandHandler(Main plugin, ConfigManager configManager) { this.plugin = plugin; this.configManager = configManager; + this.userContexts = new HashMap<>(); + this.userContextEnabled = new HashMap<>(); } @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + String userId = sender.getName(); + + if (!userContexts.containsKey(userId)) { + userContexts.put(userId, new ConversationContext(configManager.getMaxHistorySize())); + userContextEnabled.put(userId, configManager.isContextEnabled()); // 使用配置文件中的默认值 + } + + ConversationContext conversationContext = userContexts.get(userId); + boolean contextEnabled = userContextEnabled.get(userId); + if (command.getName().equalsIgnoreCase("chatgpt")) { if (args.length == 0) { sendHelpMessage(sender); return true; } - String subCommand = args[0]; if (subCommand.equalsIgnoreCase("reload")) { if (!sender.hasPermission("minechatgpt.reload")) { @@ -70,6 +87,20 @@ public class CommandHandler implements CommandExecutor { sender.sendMessage("- " + model); } return true; + } else if (args.length > 0 && args[0].equalsIgnoreCase("context")) { + contextEnabled = !contextEnabled; // 切换上下文开关状态 + userContextEnabled.put(userId, contextEnabled); + String status = contextEnabled ? configManager.getContextToggleEnabledMessage() : configManager.getContextToggleDisabledMessage(); + sender.sendMessage(configManager.getContextToggleMessage().replace("%s", status)); + return true; + } else if (subCommand.equalsIgnoreCase("clear")) { + if (!sender.hasPermission("minechatgpt.clear")) { + sender.sendMessage(configManager.getNoPermissionMessage().replace("%s", "minechatgpt.clear")); + return true; + } + conversationContext.clearHistory(); + sender.sendMessage(configManager.getClearMessage()); + return true; } else { if (!sender.hasPermission("minechatgpt.use")) { sender.sendMessage(configManager.getNoPermissionMessage().replace("%s", "minechatgpt.use")); @@ -77,49 +108,91 @@ public class CommandHandler implements CommandExecutor { } String question = String.join(" ", args); // Logic to send question to ChatGPT + if (contextEnabled) { + conversationContext.addMessage(question); // 仅在启用上下文时添加到历史记录 + } sender.sendMessage(configManager.getQuestionMessage().replace("%s", question)); - askChatGPT(sender, question); + askChatGPT(sender, question, conversationContext, contextEnabled); return true; } } return false; } - private void askChatGPT(CommandSender sender, String question) { + private void askChatGPT(CommandSender sender, String question, ConversationContext conversationContext, boolean contextEnabled) { + + logger.info("Original question: " + question); + // 尝试将问题转换为 UTF-8 编码 + String utf8Question = convertToUTF8(question); + logger.info("Converted question: " + utf8Question); JSONObject json = new JSONObject(); json.put("model", configManager.getDefaultModel()); JSONArray messages = new JSONArray(); JSONObject message = new JSONObject(); message.put("role", "user"); - message.put("content", question); + message.put("content", utf8Question); + //message.put("content", question); messages.put(message); + if (contextEnabled) { + String history = conversationContext.getConversationHistory(); + if (!history.isEmpty()) { + JSONObject historyMessage = new JSONObject(); + historyMessage.put("role", "system"); + historyMessage.put("content", history); + messages.put(historyMessage); + } + } json.put("messages", messages); json.put("model", configManager.getCurrentModel()); + // 记录构建的请求 + logger.info("Built request: " + json.toString()); HttpRequest request = HttpRequest.post(configManager.getBaseUrl() + "/chat/completions") .header("Content-Type", "application/json") .header("Authorization", "Bearer " + configManager.getApiKey()) .body(json.toString()); + if (configManager.isDebugMode()) { + logger.info("Sending request to ChatGPT: " + request.toString()); + } + HttpResponse response = request.send(); + if (configManager.isDebugMode()) { + logger.info("Received response from ChatGPT: " + response.toString()); + } + if (response.statusCode() == 200) { String responseBody = response.bodyText(); JSONObject jsonResponse = new JSONObject(responseBody); String answer = jsonResponse.getJSONArray("choices").getJSONObject(0).getJSONObject("message").getString("content"); sender.sendMessage(configManager.getChatGPTResponseMessage().replace("%s", answer)); + if (contextEnabled) { + conversationContext.addMessage(answer); // 仅在启用上下文时添加AI响应到历史记录 + } } else { String errorBody = response.bodyText(); logger.log(Level.SEVERE, "Failed to get a response from ChatGPT: " + errorBody); sender.sendMessage(configManager.getChatGPTErrorMessage()); } } - + private String convertToUTF8(String input) { + try { + // 尝试将输入字符串转换为 UTF-8 编码 + byte[] bytes = input.getBytes(StandardCharsets.UTF_8); + return new String(bytes, StandardCharsets.UTF_8); + } catch (Exception e) { + logger.severe("Failed to convert input to UTF-8: " + e.getMessage()); + return input; // 如果转换失败,返回原始输入 + } + } private void sendHelpMessage(CommandSender sender) { sender.sendMessage(configManager.getHelpMessage()); sender.sendMessage(configManager.getHelpAskMessage()); sender.sendMessage(configManager.getHelpReloadMessage()); sender.sendMessage(configManager.getHelpModelMessage()); sender.sendMessage(configManager.getHelpModelListMessage()); + sender.sendMessage(configManager.getHelpContextMessage()); + sender.sendMessage(configManager.getHelpClearMessage()); } } \ No newline at end of file diff --git a/src/main/java/com/ddaodan/MineChatGPT/ConfigManager.java b/src/main/java/com/ddaodan/MineChatGPT/ConfigManager.java index 4e531e3..d9d0f9b 100644 --- a/src/main/java/com/ddaodan/MineChatGPT/ConfigManager.java +++ b/src/main/java/com/ddaodan/MineChatGPT/ConfigManager.java @@ -13,7 +13,9 @@ public class ConfigManager { this.plugin = plugin; reloadConfig(); } - + public boolean isDebugMode() { + return config.getBoolean("debug", false); + } public void reloadConfig() { plugin.reloadConfig(); config = plugin.getConfig(); @@ -74,6 +76,15 @@ public class ConfigManager { return translateColorCodes(config.getString("messages.help_modellist")); } + + public String getHelpContextMessage() { + return translateColorCodes(config.getString("messages.help_context", "/chatgpt context - Toggle context mode.")); + } + + public String getHelpClearMessage() { + return translateColorCodes(config.getString("messages.help_clear", "/chatgpt clear - Clear conversation history.")); + } + public String getModelSwitchMessage() { return translateColorCodes(config.getString("messages.model_switch")); } @@ -105,4 +116,27 @@ public class ConfigManager { public String getCurrentModelInfoMessage() { return translateColorCodes(config.getString("messages.current_model_info")); } + + public int getMaxHistorySize() { + return config.getInt("conversation.max_history_size", 10); + } + + public boolean isContextEnabled() { + return config.getBoolean("conversation.context_enabled", false); + } + + public String getContextToggleMessage() { + return translateColorCodes(config.getString("messages.context_toggle", "Context is now %s.")); + } + public String getContextToggleEnabledMessage() { + return translateColorCodes(config.getString("messages.context_toggle_enabled", "enabled")); + } + + public String getContextToggleDisabledMessage() { + return translateColorCodes(config.getString("messages.context_toggle_disabled", "disabled")); + } + + public String getClearMessage() { + return translateColorCodes(config.getString("messages.clear", "Conversation history has been cleared.")); + } } \ No newline at end of file diff --git a/src/main/java/com/ddaodan/MineChatGPT/ConversationContext.java b/src/main/java/com/ddaodan/MineChatGPT/ConversationContext.java new file mode 100644 index 0000000..7eea1ad --- /dev/null +++ b/src/main/java/com/ddaodan/MineChatGPT/ConversationContext.java @@ -0,0 +1,29 @@ +package com.ddaodan.MineChatGPT; + +import java.util.LinkedList; +import java.util.Queue; + +public class ConversationContext { + private Queue conversationHistory; + private int maxHistorySize; + + public ConversationContext(int maxHistorySize) { + this.maxHistorySize = maxHistorySize; + this.conversationHistory = new LinkedList<>(); + } + + public void addMessage(String message) { + if (conversationHistory.size() >= maxHistorySize) { + conversationHistory.poll(); // Remove the oldest message + } + conversationHistory.offer(message); // Add the new message + } + + public String getConversationHistory() { + return String.join("\n", conversationHistory); + } + + public void clearHistory() { + conversationHistory.clear(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ddaodan/MineChatGPT/Main.java b/src/main/java/com/ddaodan/MineChatGPT/Main.java index 560cdc8..ded2f6f 100644 --- a/src/main/java/com/ddaodan/MineChatGPT/Main.java +++ b/src/main/java/com/ddaodan/MineChatGPT/Main.java @@ -20,6 +20,9 @@ public final class Main extends JavaPlugin { Objects.requireNonNull(getCommand("chatgpt")).setExecutor(commandHandler); Objects.requireNonNull(getCommand("chatgpt")).setTabCompleter(tabCompleter); checkAndUpdateConfig(); + if (configManager.isDebugMode()) { + getLogger().info( "DEBUG MODE IS TRUE!!!!!"); + } // Initialize bStats int pluginId = 22635; new Metrics(this, pluginId); diff --git a/src/main/java/com/ddaodan/MineChatGPT/MineChatGPTTabCompleter.java b/src/main/java/com/ddaodan/MineChatGPT/MineChatGPTTabCompleter.java index 5e4ae36..ce5b0f7 100644 --- a/src/main/java/com/ddaodan/MineChatGPT/MineChatGPTTabCompleter.java +++ b/src/main/java/com/ddaodan/MineChatGPT/MineChatGPTTabCompleter.java @@ -23,6 +23,8 @@ public class MineChatGPTTabCompleter implements TabCompleter { completions.add("reload"); completions.add("model"); completions.add("modellist"); + completions.add("context"); + completions.add("clear"); } else if (args.length == 2 && args[0].equalsIgnoreCase("model")) { // 补全模型名称 completions.addAll(configManager.getModels()); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 408189a..0d3648a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -23,14 +23,23 @@ models: # And more... # The default model to use default_model: "gpt-3.5-turbo" +conversation: + max_history_size: 10 + context_enabled: false # Message settings messages: reload: "&aConfiguration reloaded successfully!" + clear: "Conversation history has been cleared!" help: "&e===== MineChatGPT Help =====" help_ask: "&e/chatgpt - Ask ChatGPT a question." help_reload: "&e/chatgpt reload - Reload the configuration file." help_model: "&e/chatgpt model - Switch to a different model." help_modellist: "&e/chatgpt modellist - List available models." + help_context: "/chatgpt context - Toggle context mode." + help_clear: "/chatgpt clear - Clear conversation history." + context_toggle: "Context is now %s." + context_toggle_enabled: "enabled" + context_toggle_disabled: "disabled" current_model_info: "&eCurrent model: %s. Use /chatgpt model to switch models." model_switch: "&aModel switched to %s" chatgpt_error: "&cFailed to contact ChatGPT." @@ -39,5 +48,7 @@ messages: invalid_model: "&cInvalid model. Use /chatgpt modellist to see available models." available_models: "&eAvailable models:" no_permission: "&cYou do not have permission to use this command. Required permission: %s" +# If you don't know what this is, don't change it +debug: false # DO NOT EDIT!!!!! version: 2.2 \ No newline at end of file diff --git a/src/main/resources/config_zh.yml b/src/main/resources/config_zh.yml index 3c4e8d3..9bbc510 100644 --- a/src/main/resources/config_zh.yml +++ b/src/main/resources/config_zh.yml @@ -23,14 +23,23 @@ models: # 以及更多... # 默认使用的模型 default_model: "gpt-3.5-turbo" +conversation: + max_history_size: 10 + context_enabled: false # 消息相关设置 messages: reload: "&a已重新加载配置文件!" + clear: "&a对话历史已清空!" help: "&e===== MineChatGPT 帮助 =====" help_ask: "&e/chatgpt - 向ChatGPT提问" help_reload: "&e/chatgpt reload - 重新加载配置文件" help_model: "&e/chatgpt model - 切换至其他模型" help_modellist: "&e/chatgpt modellist - 可用的模型列表" + help_context: "&e/chatgpt context - 切换连续对话模式" + help_clear: "/chatgpt clear - 清空对话历史" + context_toggle: "&a连续对话模式已%s。" + context_toggle_enabled: "开启" + context_toggle_disabled: "关闭" current_model_info: "&e当前模型:%s,输入 /chatgpt model 来切换模型。" model_switch: "&a已切换至模型 %s" chatgpt_error: "&c无法联系ChatGPT。" @@ -39,5 +48,7 @@ messages: invalid_model: "&c模型无效。使用 /chatgpt modellist 查看可用模型。" available_models: "&e可用模型列表:" no_permission: "&c你没有权限使用这个指令。需要的权限:%s" +# 如果你不知道这是什么,请不要动 +debug: false # 不要动!!!!! version: 2.2 \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d359219..0625bb0 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -20,4 +20,10 @@ permissions: default: op minechatgpt.modellist: description: Allows listing available models - default: op \ No newline at end of file + default: op + minechatgpt.context: + description: Allows toggling context mode + default: true + chatgpt.clear: + description: Allows clearing conversation history + default: true \ No newline at end of file