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
[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).
## [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
### Changed
......
......@@ -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"`
- **OPTIONAL:** `/creaturechat model set <model>`
- 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:
**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 {
} catch (Exception e) {
String errorMessage = "Error saving `chatdata.json`. No CreatureChat chat history was saved! " + e.getMessage();
LOGGER.error(errorMessage, e);
ServerPackets.sendMessageToAllOps(server, errorMessage);
ServerPackets.sendErrorToAllOps(server, errorMessage);
}
}
......
......@@ -106,6 +106,7 @@ public class ChatGPTRequest {
String apiUrl = config.getUrl();
String apiKey = config.getApiKey();
String modelName = config.getModel();
Integer timeout = config.getTimeout() * 1000;
int maxContextTokens = config.getMaxContextTokens();
int maxOutputTokens = config.getMaxOutputTokens();
double percentOfContext = config.getPercentOfContext();
......@@ -124,8 +125,8 @@ public class ChatGPTRequest {
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + apiKey);
connection.setDoOutput(true);
connection.setConnectTimeout(10000); // 10 seconds connection timeout
connection.setReadTimeout(10000); // 10 seconds read timeout
connection.setConnectTimeout(timeout); // 10 seconds connection timeout
connection.setReadTimeout(timeout); // 10 seconds read timeout
// Create messages list (for chat history)
List<ChatGPTRequestMessage> messages = new ArrayList<>();
......
......@@ -48,7 +48,7 @@ public class ConfigurationHandler {
} catch (IOException e) {
String errorMessage = "Error saving `creaturechat.json`. CreatureChat config was not saved. " + e.getMessage();
LOGGER.error(errorMessage, e);
ServerPackets.sendMessageToAllOps(ServerPackets.serverInstance, errorMessage);
ServerPackets.sendErrorToAllOps(ServerPackets.serverInstance, errorMessage);
return false;
}
}
......@@ -68,6 +68,7 @@ public class ConfigurationHandler {
private int maxContextTokens = 16385;
private int maxOutputTokens = 200;
private double percentOfContext = 0.75;
private int timeout = 10;
// Getters and setters for existing fields
public String getApiKey() { return apiKey; }
......@@ -85,6 +86,9 @@ public class ConfigurationHandler {
public String getModel() { return 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
public int getMaxContextTokens() { return maxContextTokens; }
public void setMaxContextTokens(int maxContextTokens) { this.maxContextTokens = maxContextTokens; }
......
package com.owlmaddie.commands;
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.builder.LiteralArgumentBuilder;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
......@@ -27,64 +29,110 @@ public class CreatureChatCommands {
public static void registerCommands(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(CommandManager.literal("creaturechat")
.then(registerSetCommand("key", "API Key"))
.then(registerSetCommand("url", "URL"))
.then(registerSetCommand("model", "Model"))
.then(registerSetCommand("key", "API Key", StringArgumentType.string()))
.then(registerSetCommand("url", "URL", StringArgumentType.string()))
.then(registerSetCommand("model", "Model", StringArgumentType.string()))
.then(registerSetCommand("timeout", "Timeout (seconds)", IntegerArgumentType.integer()))
.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)
.requires(source -> source.hasPermissionLevel(4))
.then(CommandManager.literal("set")
.then(CommandManager.argument("value", StringArgumentType.string())
.then(CommandManager.argument("value", valueType)
.then(CommandManager.literal("--config")
.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")
.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.Config config = configHandler.loadConfig();
try {
switch (settingName) {
case "key":
config.setApiKey(value);
config.setApiKey((String) value);
break;
case "url":
config.setUrl(value);
config.setUrl((String) value);
break;
case "model":
config.setModel(value);
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;
if (configHandler.saveConfig(config, useServerConfig)) {
// succeeded
feedbackMessage = Text.literal(settingDescription + " Set Successfully!").formatted(Formatting.GREEN);
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1;
} else {
// failed
feedbackMessage = Text.literal(settingDescription + " Set Failed!").formatted(Formatting.RED);
}
source.sendFeedback(() -> feedbackMessage, false);
LOGGER.info("Command executed: " + feedbackMessage.getString());
return 1;
return 0;
}
}
private static LiteralArgumentBuilder<ServerCommandSource> registerHelpCommand() {
return CommandManager.literal("help")
.executes(context -> {
String helpMessage = "Usage of CreatureChat Commands:\n"
+ "/creaturechat key set <key> - Sets the API key.\n"
+ "/creaturechat url set \"<url>\" - Sets the URL.\n"
+ "/creaturechat model set <model> - Sets the model.\n"
+ "/creaturechat key set <key> - Sets the API key\n"
+ "/creaturechat url set \"<url>\" - Sets the URL\n"
+ "/creaturechat model set <model> - Sets the model\n"
+ "/creaturechat timeout set <seconds> - Sets the API timeout\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"
+ "Security: Level 4 permission required.";
context.getSource().sendFeedback(() -> Text.literal(helpMessage), false);
......
......@@ -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)
public static void SendClickableError(PlayerEntity player, String message, String url) {
MutableText text = Text.literal(message)
.formatted(Formatting.BLUE)
.formatted(Formatting.RED)
.styled(style -> style
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))
.withUnderline(true));
......@@ -327,7 +327,7 @@ public class ServerPackets {
}
// 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()) {
// Check if the player is an operator
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