Skip to content

Commit

Permalink
Mod is now 100x better
Browse files Browse the repository at this point in the history
* 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
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 176 deletions.
180 changes: 4 additions & 176 deletions src/main/kotlin/baf/chat/Bafmod3.kt
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)
})
}
}
168 changes: 168 additions & 0 deletions src/main/kotlin/baf/chat/Bafmod3Commands.kt
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()
}
}
}
Loading

0 comments on commit bd4340e

Please sign in to comment.