-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Config now supports flexible values for temperature, and other important parameters. * Bafchat's name is now configurable. * Config can be updated via the /bafchat set command. * Config can be reset with /bafchat reset. * Better error messages. * Winning success messages. * Split from one class into multiple.
- Loading branch information
Bafmaster
committed
Jun 14, 2023
1 parent
3d00b97
commit bd4340e
Showing
5 changed files
with
362 additions
and
176 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,185 +1,13 @@ | ||
package baf.chat | ||
|
||
import com.mojang.brigadier.CommandDispatcher | ||
import com.mojang.brigadier.arguments.StringArgumentType | ||
import com.google.gson.Gson | ||
import com.google.gson.JsonObject | ||
import com.google.gson.JsonParser | ||
import net.fabricmc.api.ModInitializer | ||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback | ||
import net.minecraft.client.MinecraftClient | ||
import net.minecraft.command.CommandRegistryAccess | ||
import net.minecraft.server.command.CommandManager | ||
import net.minecraft.server.command.ServerCommandSource | ||
import net.minecraft.text.Text | ||
import java.io.BufferedReader | ||
import java.io.DataOutputStream | ||
import java.io.File | ||
import java.io.FileWriter | ||
import java.io.InputStreamReader | ||
import java.net.HttpURLConnection | ||
import java.net.URL | ||
import java.nio.charset.StandardCharsets | ||
|
||
class Bafmod3 : ModInitializer { | ||
override fun onInitialize() { | ||
createConfigFileIfNeeded() | ||
|
||
CommandRegistrationCallback.EVENT.register { commandDispatcher: CommandDispatcher<ServerCommandSource>, _: CommandRegistryAccess, _: CommandManager.RegistrationEnvironment -> | ||
val localAiCommand = CommandManager.literal("bafchat") | ||
.then(CommandManager.argument("message", StringArgumentType.greedyString()) | ||
.executes { context -> | ||
val message = StringArgumentType.getString(context, "message") | ||
sendToAIChatbot(message) | ||
1 | ||
}) | ||
|
||
val llmCommand = CommandManager.literal("llm") | ||
.then(CommandManager.argument("message", StringArgumentType.greedyString()) | ||
.executes { context -> | ||
val message = StringArgumentType.getString(context, "message") | ||
sendToAIChatbot(message) | ||
1 | ||
}) | ||
|
||
commandDispatcher.register(localAiCommand) | ||
commandDispatcher.register(llmCommand) | ||
} | ||
|
||
} | ||
|
||
fun sendChatMessage(message: String) { | ||
val minecraftClient = MinecraftClient.getInstance() | ||
val playerName = minecraftClient.player?.name?.toString()?.replace(Regex("literal\\{(.*?)\\}"), "$1") ?: "" | ||
val chatText = Text.of("§3[$playerName] §f$message") | ||
minecraftClient.inGameHud.chatHud.addMessage(chatText) | ||
} | ||
|
||
private fun sendToAIChatbot(message: String) { | ||
sendChatMessage(message) // Call sendChatMessage() before sending the message to the API | ||
val configPath = "./config/Bafmod3Config.json" | ||
val configFile = File(configPath) | ||
|
||
if (!configFile.exists()) { | ||
println("Config file not found: $configPath") | ||
return | ||
} | ||
|
||
val configFileContents = configFile.readText() | ||
val configJson = Gson().fromJson(configFileContents, JsonObject::class.java) | ||
val apiUrl = configJson.getAsJsonPrimitive("apiUrl")?.asString | ||
val prompt = configJson.getAsJsonPrimitive("prompt")?.asString | ||
|
||
if (apiUrl == null) { | ||
println("apiUrl not found in the config file") | ||
return | ||
} | ||
|
||
if (prompt == null) { | ||
println("prompt not found in the config file") | ||
return | ||
} | ||
|
||
val formattedPrompt = prompt.replace("{{message}}", message) | ||
println("Formatted Prompt: $formattedPrompt") | ||
|
||
val url = URL(apiUrl) | ||
val connection = url.openConnection() as HttpURLConnection | ||
connection.requestMethod = "POST" | ||
connection.doOutput = true | ||
|
||
val postData = """ | ||
{ | ||
"prompt": "$formattedPrompt", | ||
"stream": true, | ||
"max_tokens": 64, | ||
"temperature": 0.5, | ||
"top_p": 0.9 | ||
} | ||
""".trimIndent() | ||
|
||
val postDataBytes = postData.toByteArray(StandardCharsets.UTF_8) | ||
connection.setRequestProperty("Content-Type", "application/json; charset=utf-8") | ||
connection.setRequestProperty("Content-Length", postDataBytes.size.toString()) | ||
|
||
val outputStream = DataOutputStream(connection.outputStream) | ||
outputStream.write(postDataBytes) | ||
outputStream.flush() | ||
outputStream.close() | ||
|
||
val responseCode = connection.responseCode | ||
if (responseCode == HttpURLConnection.HTTP_OK) { | ||
val responseStringBuilder = StringBuilder() | ||
|
||
val reader = BufferedReader(InputStreamReader(connection.inputStream)) | ||
var line: String? = reader.readLine() | ||
|
||
while (line != null) { | ||
if (line.startsWith("data: ")) { | ||
val responseData = line.substring("data: ".length) | ||
println("Received response data: $responseData") | ||
|
||
if (responseData == "[DONE]") { | ||
// Handle the completion of the response stream | ||
break | ||
} | ||
|
||
try { | ||
val jsonResponse = JsonParser.parseString(responseData).asJsonObject | ||
val choicesArray = jsonResponse.getAsJsonArray("choices") | ||
|
||
if (choicesArray != null && choicesArray.size() > 0) { | ||
val choice = choicesArray[0].asJsonObject | ||
val choiceText = choice.getAsJsonPrimitive("text").asString | ||
responseStringBuilder.append(choiceText).append("") | ||
} | ||
} catch (e: Exception) { | ||
println("Error processing the API response: ${e.message}") | ||
} | ||
} | ||
|
||
line = reader.readLine() | ||
} | ||
|
||
reader.close() | ||
|
||
val responseString = responseStringBuilder.toString() | ||
println("Response: $responseString") | ||
|
||
val minecraftClient = MinecraftClient.getInstance() | ||
val chatText: Text = Text.of("§9[Bafchat]§f$responseString") | ||
minecraftClient.inGameHud.chatHud.addMessage(chatText) | ||
|
||
println("Message successfully sent to the API.") | ||
} else { | ||
println("Failed to send the message. Response code: $responseCode") | ||
// Handle the error if needed | ||
} | ||
|
||
connection.disconnect() | ||
} | ||
|
||
private fun createConfigFileIfNeeded() { | ||
val configPath = "./config/Bafmod3Config.json" | ||
val configFile = File(configPath) | ||
|
||
if (!configFile.exists()) { | ||
val defaultConfigContent = """ | ||
{ | ||
"apiUrl": "http://localhost:8000/completions", | ||
"prompt": "You are a helpful Minecraft assistant who helps answer the player's questions with short, concise, but friendly answers in 60 completion_tokens or less.\\n<human>: Hey can you help me?\\n<bot>: Sure, let me know what you need help with, and I'll do my best to help.\\n<human>: {{message}}\\n<bot>:" | ||
} | ||
""".trimIndent() | ||
|
||
try { | ||
FileWriter(configFile).use { writer -> | ||
writer.write(defaultConfigContent) | ||
} | ||
println("Config file created: $configPath") | ||
} catch (e: Exception) { | ||
println("Failed to create the config file: $configPath") | ||
e.printStackTrace() | ||
} | ||
} | ||
Bafmod3ConfigHandler.createConfigFileIfNeeded() | ||
CommandRegistrationCallback.EVENT.register(CommandRegistrationCallback { dispatcher, _, _ -> | ||
Bafmod3Commands.registerCommands(dispatcher) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package baf.chat | ||
|
||
import baf.chat.Bafmod3Sender.sendToAIChatbot | ||
import com.google.gson.Gson | ||
import com.google.gson.JsonParser | ||
import com.mojang.brigadier.CommandDispatcher | ||
import com.mojang.brigadier.arguments.StringArgumentType | ||
import com.mojang.brigadier.builder.LiteralArgumentBuilder.literal | ||
import com.mojang.brigadier.suggestion.SuggestionProvider | ||
import net.minecraft.server.command.CommandManager | ||
import net.minecraft.server.command.ServerCommandSource | ||
import java.io.File | ||
import java.io.FileWriter | ||
|
||
object Bafmod3Commands { | ||
private const val CONFIG_PATH = "./config/Bafmod3Config.json" | ||
|
||
fun registerCommands(dispatcher: CommandDispatcher<ServerCommandSource>) { | ||
val localAiCommand = literal<ServerCommandSource>("bafchat") | ||
.then(literal<ServerCommandSource>("set") | ||
.then(CommandManager.argument("key", StringArgumentType.word()) | ||
.suggests(suggestConfigKeys()) | ||
.then(CommandManager.argument("value", StringArgumentType.greedyString()) | ||
.executes { context -> | ||
val key = StringArgumentType.getString(context, "key") | ||
val value = StringArgumentType.getString(context, "value") | ||
updateConfigValue(key, value, context.source) | ||
1 | ||
}) | ||
) | ||
) | ||
.then(CommandManager.argument("message", StringArgumentType.greedyString()) | ||
.executes { context -> | ||
val message = StringArgumentType.getString(context, "message") | ||
sendToAIChatbot(message) | ||
1 | ||
}) | ||
.then(literal<ServerCommandSource>("list") | ||
.executes { context -> | ||
listConfigValues(context.source) | ||
1 | ||
} | ||
) | ||
.then(literal<ServerCommandSource>("reset") | ||
.executes { context -> | ||
resetConfig(context.source) | ||
1 | ||
} | ||
) | ||
|
||
val llmCommand = literal<ServerCommandSource>("llm") | ||
.then(CommandManager.argument("message", StringArgumentType.greedyString()) | ||
.executes { context -> | ||
val message = StringArgumentType.getString(context, "message") | ||
sendToAIChatbot(message) | ||
1 | ||
}) | ||
|
||
dispatcher.register(localAiCommand) | ||
dispatcher.register(llmCommand) | ||
} | ||
|
||
private fun updateConfigValue(key: String, value: String, source: ServerCommandSource) { | ||
val configFile = File(CONFIG_PATH) | ||
|
||
if (!configFile.exists()) { | ||
Bafmod3Deliver.sendChatMessage("Config file not found: $CONFIG_PATH", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
|
||
val configFileContents = configFile.readText() | ||
val configJson = JsonParser.parseString(configFileContents).asJsonObject | ||
|
||
if (!configJson.has(key)) { | ||
Bafmod3Deliver.sendChatMessage("Key '$key' does not exist in the config file.", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
|
||
when (key) { | ||
"stream" -> { | ||
if (value !in listOf("true", "false")) { | ||
Bafmod3Deliver.sendChatMessage("Invalid value for '$key'. Allowed values: true, false", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
} | ||
"max_tokens" -> { | ||
val intValue = value.toIntOrNull() | ||
if (intValue == null || intValue < 0) { | ||
Bafmod3Deliver.sendChatMessage("Invalid value for '$key'. Expected a non-negative integer.", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
} | ||
"temperature", "top_p" -> { | ||
val doubleValue = value.toDoubleOrNull() | ||
if (doubleValue == null || doubleValue <= 0 || doubleValue > 1) { | ||
Bafmod3Deliver.sendChatMessage("Invalid value for '$key'. Expected a double between 0 and 1 (exclusive).", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
} | ||
} | ||
|
||
configJson.addProperty(key, value) | ||
|
||
try { | ||
val gson = Gson() | ||
val prettyJson = gson.toJson(configJson) | ||
|
||
if (prettyJson == configFileContents) { | ||
Bafmod3Deliver.sendChatMessage("No changes were made to the config file for key '$key'.", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
|
||
FileWriter(configFile).use { writer -> | ||
writer.write(prettyJson) | ||
} | ||
Bafmod3Deliver.sendChatMessage("Config value updated: $key = $value", Bafmod3Deliver.MessageType.SUCCESS_MESSAGE) | ||
} catch (e: Exception) { | ||
Bafmod3Deliver.sendChatMessage("Failed to update config value: $key", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
e.printStackTrace() | ||
} | ||
} | ||
|
||
private fun suggestConfigKeys(): SuggestionProvider<ServerCommandSource> { | ||
return SuggestionProvider { context, builder -> | ||
val configJson = Bafmod3ConfigHandler.getConfigJson() | ||
val keys = configJson.keySet() | ||
val remaining = builder.remaining.lowercase() | ||
keys.filter { it.startsWith(remaining) }.forEach(builder::suggest) | ||
builder.buildFuture() | ||
} | ||
} | ||
|
||
private fun listConfigValues(source: ServerCommandSource) { | ||
val configFile = File(CONFIG_PATH) | ||
|
||
if (!configFile.exists()) { | ||
Bafmod3Deliver.sendChatMessage("Config file not found: $CONFIG_PATH", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
|
||
val configFileContents = configFile.readText() | ||
val configJson = JsonParser.parseString(configFileContents).asJsonObject | ||
|
||
val configList = configJson.entrySet().joinToString("\n") { (key, value) -> | ||
"$key = $value" | ||
} | ||
|
||
Bafmod3Deliver.sendChatMessage("Current config values:\n$configList", Bafmod3Deliver.MessageType.SUCCESS_MESSAGE) | ||
} | ||
|
||
private fun resetConfig(source: ServerCommandSource) { | ||
val configFile = File(CONFIG_PATH) | ||
|
||
if (!configFile.exists()) { | ||
Bafmod3Deliver.sendChatMessage("Config file not found: $CONFIG_PATH", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
return | ||
} | ||
|
||
try { | ||
configFile.delete() | ||
Bafmod3ConfigHandler.createConfigFileIfNeeded() | ||
Bafmod3Deliver.sendChatMessage("Config file reset.", Bafmod3Deliver.MessageType.SUCCESS_MESSAGE) | ||
} catch (e: Exception) { | ||
Bafmod3Deliver.sendChatMessage("Failed to reset the config file.", Bafmod3Deliver.MessageType.BAFCHAT_MESSAGE) | ||
e.printStackTrace() | ||
} | ||
} | ||
} |
Oops, something went wrong.