Commit 1bb6ba01 by Jonathan Thomas

Adding new timeout Minecraft command, and support for Int and String command…

Adding new timeout Minecraft command, and support for Int and String command args. Updated errors to be RED color. Updated documentation.
parent 7096dc54
Pipeline #12183 passed with stage
in 1 minute 42 seconds
...@@ -4,6 +4,17 @@ All notable changes to **CreatureChat** are documented in this file. The format ...@@ -4,6 +4,17 @@ All notable changes to **CreatureChat** are documented in this file. The format
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Added new `/creaturechat timeout set <seconds>` command
- Added support for commands to use different data types (`String`, `Integer`)
### Changed
- Updated error messages to `RED` color for maximum attention
- Updated `/creaturechat help` output
- Updated `README.md` with new command documentation
## [1.0.3] - 2024-05-10 ## [1.0.3] - 2024-05-10
### Changed ### Changed
......
...@@ -35,6 +35,8 @@ The CreatureChat mod allows users to configure settings via in-game commands. He ...@@ -35,6 +35,8 @@ The CreatureChat mod allows users to configure settings via in-game commands. He
- Sets the URL of the API used to make LLM requests. Defaults to `"https://api.openai.com/v1/chat/completions"` - Sets the URL of the API used to make LLM requests. Defaults to `"https://api.openai.com/v1/chat/completions"`
- **OPTIONAL:** `/creaturechat model set <model>` - **OPTIONAL:** `/creaturechat model set <model>`
- Sets the model used for generating responses in chats. Defaults to `gpt-3.5-turbo`. - Sets the model used for generating responses in chats. Defaults to `gpt-3.5-turbo`.
- **OPTIONAL:** `/creaturechat timeout set <seconds>`
- Sets the timeout (in seconds) for API HTTP requests. Defaults to `10` seconds.
### Configuration Scope: ### Configuration Scope:
**OPTIONAL:** You can specify the configuration scope at the end of each command to determine where settings should be applied: **OPTIONAL:** You can specify the configuration scope at the end of each command to determine where settings should be applied:
......
...@@ -487,7 +487,7 @@ public class ChatDataManager { ...@@ -487,7 +487,7 @@ public class ChatDataManager {
} catch (Exception e) { } catch (Exception e) {
String errorMessage = "Error saving `chatdata.json`. No CreatureChat chat history was saved! " + e.getMessage(); String errorMessage = "Error saving `chatdata.json`. No CreatureChat chat history was saved! " + e.getMessage();
LOGGER.error(errorMessage, e); LOGGER.error(errorMessage, e);
ServerPackets.sendMessageToAllOps(server, errorMessage); ServerPackets.sendErrorToAllOps(server, errorMessage);
} }
} }
......
...@@ -106,6 +106,7 @@ public class ChatGPTRequest { ...@@ -106,6 +106,7 @@ public class ChatGPTRequest {
String apiUrl = config.getUrl(); String apiUrl = config.getUrl();
String apiKey = config.getApiKey(); String apiKey = config.getApiKey();
String modelName = config.getModel(); String modelName = config.getModel();
Integer timeout = config.getTimeout() * 1000;
int maxContextTokens = config.getMaxContextTokens(); int maxContextTokens = config.getMaxContextTokens();
int maxOutputTokens = config.getMaxOutputTokens(); int maxOutputTokens = config.getMaxOutputTokens();
double percentOfContext = config.getPercentOfContext(); double percentOfContext = config.getPercentOfContext();
...@@ -124,8 +125,8 @@ public class ChatGPTRequest { ...@@ -124,8 +125,8 @@ public class ChatGPTRequest {
connection.setRequestProperty("Content-Type", "application/json"); connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + apiKey); connection.setRequestProperty("Authorization", "Bearer " + apiKey);
connection.setDoOutput(true); connection.setDoOutput(true);
connection.setConnectTimeout(10000); // 10 seconds connection timeout connection.setConnectTimeout(timeout); // 10 seconds connection timeout
connection.setReadTimeout(10000); // 10 seconds read timeout connection.setReadTimeout(timeout); // 10 seconds read timeout
// Create messages list (for chat history) // Create messages list (for chat history)
List<ChatGPTRequestMessage> messages = new ArrayList<>(); List<ChatGPTRequestMessage> messages = new ArrayList<>();
......
...@@ -48,7 +48,7 @@ public class ConfigurationHandler { ...@@ -48,7 +48,7 @@ public class ConfigurationHandler {
} catch (IOException e) { } catch (IOException e) {
String errorMessage = "Error saving `creaturechat.json`. CreatureChat config was not saved. " + e.getMessage(); String errorMessage = "Error saving `creaturechat.json`. CreatureChat config was not saved. " + e.getMessage();
LOGGER.error(errorMessage, e); LOGGER.error(errorMessage, e);
ServerPackets.sendMessageToAllOps(ServerPackets.serverInstance, errorMessage); ServerPackets.sendErrorToAllOps(ServerPackets.serverInstance, errorMessage);
return false; return false;
} }
} }
...@@ -68,6 +68,7 @@ public class ConfigurationHandler { ...@@ -68,6 +68,7 @@ public class ConfigurationHandler {
private int maxContextTokens = 16385; private int maxContextTokens = 16385;
private int maxOutputTokens = 200; private int maxOutputTokens = 200;
private double percentOfContext = 0.75; private double percentOfContext = 0.75;
private int timeout = 10;
// Getters and setters for existing fields // Getters and setters for existing fields
public String getApiKey() { return apiKey; } public String getApiKey() { return apiKey; }
...@@ -85,6 +86,9 @@ public class ConfigurationHandler { ...@@ -85,6 +86,9 @@ public class ConfigurationHandler {
public String getModel() { return model; } public String getModel() { return model; }
public void setModel(String model) { this.model = model; } public void setModel(String model) { this.model = model; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
// Getters and setters for new fields // Getters and setters for new fields
public int getMaxContextTokens() { return maxContextTokens; } public int getMaxContextTokens() { return maxContextTokens; }
public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; } public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; }
......
package com.owlmaddie.commands; package com.owlmaddie.commands;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
...@@ -27,64 +29,110 @@ public class CreatureChatCommands { ...@@ -27,64 +29,110 @@ public class CreatureChatCommands {
public static void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher) { public static void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(CommandManager.literal("creaturechat") dispatcher.register(CommandManager.literal("creaturechat")
.then(registerSetCommand("key", "API Key")) .then(registerSetCommand("key", "API Key", StringArgumentType.string()))
.then(registerSetCommand("url", "URL")) .then(registerSetCommand("url", "URL", StringArgumentType.string()))
.then(registerSetCommand("model", "Model")) .then(registerSetCommand("model", "Model", StringArgumentType.string()))
.then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer()))
.then(registerHelpCommand())); .then(registerHelpCommand()));
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerSetCommand(String settingName, String settingDescription) { private static LiteralArgumentBuilder<ServerCommandSource> registerSetCommand(String settingName, String settingDescription, ArgumentType<?> valueType) {
return CommandManager.literal(settingName) return CommandManager.literal(settingName)
.requires(source -> source.hasPermissionLevel(4)) .requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set") .then(CommandManager.literal("set")
.then(CommandManager.argument("value", StringArgumentType.string()) .then(CommandManager.argument("value", valueType)
.then(CommandManager.literal("--config") .then(CommandManager.literal("--config")
.then(CommandManager.literal("default") .then(CommandManager.literal("default")
.executes(context -> setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription))) .executes(context -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), false, settingDescription);
return 1;
})
)
.then(CommandManager.literal("server") .then(CommandManager.literal("server")
.executes(context -> setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), true, settingDescription))) .executes(context -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), true, settingDescription);
else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), true, settingDescription);
return 1;
})
)
) )
.executes(context -> setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription)) // Default to server if not specified .executes(context -> {
if (valueType instanceof StringArgumentType)
return setConfig(context.getSource(), settingName, StringArgumentType.getString(context, "value"), false, settingDescription);
else if (valueType instanceof IntegerArgumentType)
return setConfig(context.getSource(), settingName, IntegerArgumentType.getInteger(context, "value"), false, settingDescription);
return 1;
})
)); ));
} }
private static int setConfig(ServerCommandSource source, String settingName, String value, boolean useServerConfig, String settingDescription) { private static <T> int setConfig(ServerCommandSource source, String settingName, T value, boolean useServerConfig, String settingDescription) {
ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer()); ConfigurationHandler configHandler = new ConfigurationHandler(source.getServer());
ConfigurationHandler.Config config = configHandler.loadConfig(); ConfigurationHandler.Config config = configHandler.loadConfig();
switch (settingName) { try {
case "key": switch (settingName) {
config.setApiKey(value); case "key":
break; config.setApiKey((String) value);
case "url": break;
config.setUrl(value); case "url":
break; config.setUrl((String) value);
case "model": break;
config.setModel(value); case "model":
break; config.setModel((String) value);
break;
case "timeout":
if (value instanceof Integer) {
config.setTimeout((Integer) value);
} else {
throw new IllegalArgumentException("Invalid type for timeout, must be Integer.");
}
break;
default:
throw new IllegalArgumentException("Unknown configuration setting: " + settingName);
}
} catch (ClassCastException e) {
Text errorMessage = Text.literal("Invalid type for setting " + settingName).formatted(Formatting.RED);
source.sendFeedback(() -> errorMessage, false);
LOGGER.error("Type mismatch during configuration setting for: " + settingName, e);
return 0;
} catch (IllegalArgumentException e) {
Text errorMessage = Text.literal(e.getMessage()).formatted(Formatting.RED);
source.sendFeedback(() -> errorMessage, false);
LOGGER.error("Error setting configuration: " + e.getMessage(), e);
return 0;
} }
Text feedbackMessage; Text feedbackMessage;
if (configHandler.saveConfig(config, useServerConfig)) { if (configHandler.saveConfig(config, useServerConfig)) {
// succeeded // succeeded
feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN); feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1;
} else { } else {
// failed // failed
feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED); feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED);
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 0;
} }
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1;
} }
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() { private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() {
return CommandManager.literal("help") return CommandManager.literal("help")
.executes(context -> { .executes(context -> {
String helpMessage = "Usage of CreatureChat Commands:\n" String helpMessage = "Usage of CreatureChat Commands:\n"
+ "/creaturechat key set <key> - Sets the API key.\n" + "/creaturechat key set <key> - Sets the API key\n"
+ "/creaturechat url set \"<url>\" - Sets the URL.\n" + "/creaturechat url set \"<url>\" - Sets the URL\n"
+ "/creaturechat model set <model> - Sets the model.\n" + "/creaturechat model set <model> - Sets the model\n"
+ "/creaturechat timeout set <seconds> - Sets the API timeout\n"
+ "\n" + "\n"
+ "Optional: Append [--config default | server] to any command to specify configuration scope. If --config is not specified, 'default' is assumed'.\n" + "Optional: Append [--config default | server] to any command to specify configuration scope.\n"
+ "\n" + "\n"
+ "Security: Level 4 permission required."; + "Security: Level 4 permission required.";
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false); context.getSource().sendFeedback(() -> Text.literal(helpMessage), false);
......
...@@ -319,7 +319,7 @@ public class ServerPackets { ...@@ -319,7 +319,7 @@ public class ServerPackets {
// Send a chat message to a player which is clickable (for error messages with a link for help) // Send a chat message to a player which is clickable (for error messages with a link for help)
public static void SendClickableError(PlayerEntity player, String message, String url) { public static void SendClickableError(PlayerEntity player, String message, String url) {
MutableText text = Text.literal(message) MutableText text = Text.literal(message)
.formatted(Formatting.BLUE) .formatted(Formatting.RED)
.styled(style -> style .styled(style -> style
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url)) .withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))
.withUnderline(true)); .withUnderline(true));
...@@ -327,7 +327,7 @@ public class ServerPackets { ...@@ -327,7 +327,7 @@ public class ServerPackets {
} }
// Send a clickable message to ALL Ops // Send a clickable message to ALL Ops
public static void sendMessageToAllOps(MinecraftServer server, String message) { public static void sendErrorToAllOps(MinecraftServer server, String message) {
for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) { for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
// Check if the player is an operator // Check if the player is an operator
if (server.getPlayerManager().isOperator(player.getGameProfile())) { if (server.getPlayerManager().isOperator(player.getGameProfile())) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment