diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml
index 76621b5..28211c5 100644
--- a/.gitea/workflows/build.yml
+++ b/.gitea/workflows/build.yml
@@ -24,4 +24,4 @@ jobs:
./gradlew build && ls -lah && ls */ -lah && ls */* -lah&& ls */*/* -lah
- name: Build | publish jar
run: |
- curl --insecure --user username:mypass -T build/libs/OllamaChat-1.0.2.jar ftp://192.168.10.133:/
\ No newline at end of file
+ curl --insecure --user username:mypass -T build/libs/OllamaChat-1.1.4.jar ftp://192.168.10.133:/
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 78090fc..524f096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,3 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
-
-build
-.gradle
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 13566b8..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 3566ed5..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-OllamaChat
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index b86273d..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
deleted file mode 100644
index 6b01108..0000000
--- a/.idea/discord.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
deleted file mode 100644
index ce1c62c..0000000
--- a/.idea/gradle.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/highlightedFiles.xml b/.idea/highlightedFiles.xml
deleted file mode 100644
index f9d3c5e..0000000
--- a/.idea/highlightedFiles.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 7c47dd4..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index dc14fc0..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules/OllamaChat.main.iml b/.idea/modules/OllamaChat.main.iml
deleted file mode 100644
index bbeeb3e..0000000
--- a/.idea/modules/OllamaChat.main.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
- PAPER
- ADVENTURE
-
- 1
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 927b30f..d45560a 100644
--- a/README.md
+++ b/README.md
@@ -1,64 +1,101 @@
-
-
-# Ollama-Chat
-
+
+# OllamaChat
[](https://modrinth.com/plugin/ollama-chat)
-
## Overview
-**Ollama-Chat** is a cutting-edge Minecraft plugin that brings the power of Ollama and other AI models directly into your Minecraft world. This plugin enables players to interact with AI in real-time, creating a unique and immersive gameplay experience. Whether you want to chat with an AI companion, ask questions, or simply explore the capabilities of AI, Ollama-Chat makes it possible within the Minecraft universe.
+**OllamaChat** is a cutting-edge Minecraft plugin that integrates Ollama and OpenAI-class APIs, enabling real-time AI interactions, multi-language support, and advanced prompt and conversation management for immersive in-game experiences.
## Features
-- **AI-Powered Conversations**: Communicate with AI entities in Minecraft by sending messages prefixed with `@bot`. The AI will respond intelligently, providing a dynamic and engaging interaction.
-- **Ollama Integration**: Leverage the advanced capabilities of Ollama to enhance your Minecraft experience.
-- **Multi-Language Support**: Supports multiple languages (e.g., English, Simplified Chinese) through language files in the `lang` folder.
-- **Toggle AI Services**: Enable or disable AI services dynamically using the `/ollamachat toggle ` command.
-- **Simple Commands**: Use the `/ollamachat reload` command to reload the plugin configuration instantly, ensuring seamless updates without server restarts.
+- **AI-Powered Conversations**: Chat with AI using `@bot` or `@ai` prefixes for dynamic, intelligent responses.
+- **Ollama & OpenAI Integration**: Leverage advanced AI models to enhance your Minecraft experience.
+- **Multi-Language Support**: Supports multiple languages (e.g., English, Simplified Chinese) via `lang` folder files.
+- **Toggle AI Services**: Enable/disable AI services with `/ollamachat toggle `.
+- **Prompt Management**: Create, delete, list, select, or clear custom prompts to tailor AI interactions.
+- **Conversation Management**: Start, switch, delete, or view player-specific conversations with AI entities.
+- **Smart Response Suggestions**: Generate configurable, clickable follow-up suggestions with hover text and rate limiting.
+- **Tab Completion**: Enhanced usability with Tab completion for `/ollamachat` subcommands.
+- **Progress Display**: Visual status bar for prompt answer generation (0% to 100%).
+- **Configurable Settings**: Customize suggestion count, prompt templates, preset responses, and model toggles.
## Usage
### Chatting with AI
-To interact with the AI, simply type `@bot` followed by your message in the Minecraft chat. The AI will process your input and respond accordingly.
+Type `@bot` or `@ai` followed by your message in Minecraft chat to interact with the AI.
**Example:**
```
-@bot What is the weather like today?
+@bot What's the best way to build a castle?
```
### Commands
-- **/ollamachat reload**: Reloads the plugin configuration, including language files and AI settings.
-- **/ollamachat toggle **: Enables or disables the specified AI service.
-- **/aichat **: Interacts with other AI services (e.g., OpenAI).
+- **/ollamachat reload**: Reloads plugin configuration and language files (`ollamachat.reload`).
+- **/ollamachat toggle **: Enables/disables specified AI service (`ollamachat.toggle`).
+- **/aichat **: Interacts with other AI services (`ollamachat.use`).
+- **/ollamachat prompt set **: Creates a new prompt (`ollamachat.prompt.set`).
+- **/ollamachat prompt delete **: Deletes a prompt (`ollamachat.prompt.delete`).
+- **/ollamachat prompt list**: Lists all prompts (`ollamachat.prompt.list`).
+- **/ollamachat prompt select **: Sets default prompt (`ollamachat.prompt.select`).
+- **/ollamachat prompt clear**: Resets default prompt (`ollamachat.prompt.select`).
+- **/ollamachat conversation new **: Starts a new conversation (`ollamachat.conversation.new`).
+- **/ollamachat conversation select **: Switches conversations (`ollamachat.conversation.select`).
+- **/ollamachat conversation delete **: Deletes a conversation (`ollamachat.conversation.delete`).
+- **/ollamachat conversation list **: Lists conversations for an AI (`ollamachat.conversation.list`).
+- **/ollamachat suggests toggle**: Toggles suggested responses (`ollamachat.suggests.toggle`).
+
+### Permissions
+
+| Command | Permission | Description |
+|---------|------------|-------------|
+| `/ollamachat reload` | `ollamachat.reload` | Reloads plugin configuration. |
+| `/ollamachat toggle ` | `ollamachat.toggle` | Toggles specified AI service. |
+| `/aichat ` | `ollamachat.use` | Sends a message to specified AI. |
+| `/ollamachat prompt set ` | `ollamachat.prompt.set` | Creates and saves a new prompt. |
+| `/ollamachat prompt delete ` | `ollamachat.prompt.delete` | Deletes a specified prompt. |
+| `/ollamachat prompt list` | `ollamachat.prompt.list` | Lists all prompts and current default. |
+| `/ollamachat prompt select ` | `ollamachat.prompt.select` | Sets a prompt as default. |
+| `/ollamachat prompt clear` | `ollamachat.prompt.select` | Resets default prompt. |
+| `/ollamachat conversation new ` | `ollamachat.conversation.new` | Starts a new conversation. |
+| `/ollamachat conversation select ` | `ollamachat.conversation.select` | Switches to an existing conversation. |
+| `/ollamachat conversation delete ` | `ollamachat.conversation.delete` | Deletes a conversation. |
+| `/ollamachat conversation list ` | `ollamachat.conversation.list` | Lists all conversations for an AI. |
+| `/ollamachat suggests toggle` | `ollamachat.suggests.toggle` | Toggles suggested responses. |
+| `/ollamachat suggests-presets toggle` | `ollamachat.suggests-presets.toggle` | Toggles preset suggested responses. |
**Example:**
```
-/aichat openai Tell me a joke
+/aichat ollama Tell me about Redstone
+/ollamachat prompt set creativePrompt "Act as a creative Minecraft builder"
+/ollamachat suggests toggle
```
## Installation
1. **Download the Plugin**: Obtain the latest version of **Ollama-Chat** from the [official repository](https://github.com/mcraftbbs/Ollama-Chat).
-2. **Install the Plugin**: Place the downloaded `.jar` file into the `plugins` folder of your Minecraft server.
-3. **Configure the Plugin**: Modify the `config.yml` file to customize AI settings.
-4. **Reload the Plugin**: Use the `/ollamachat reload` command to apply any configuration changes.
+2. **Install**: Place the `.jar` file in your server's `plugins` folder.
+3. **Configure**: Edit `config.yml` to customize AI settings, prompts, and suggestions.
+4. **Reload**: Use `/ollamachat reload` to apply changes.
## Configuration
-The plugin's configuration file (`config.yml`) allows you to customize various aspects of the AI interactions.
+Customize AI interactions via `config.yml`:
-Example `config.yml`:
```yaml
# Ollama API
ollama-api-url: "http://localhost:11434/api/generate"
model: "llama3"
ollama-enabled: true
+# Streaming settings
+stream-settings:
+ enabled: true
+
# Chat
-trigger-prefix: "@bot "
-response-prefix: "§b[AI] §r"
+trigger-prefixes:
+ - "@bot"
+ - "@ai"
# Length
max-response-length: 500
@@ -67,7 +104,48 @@ max-response-length: 500
max-history: 5
# Language Settings
-language: "en" # Default language (en or zh_cn)
+language: "en_us"
+
+# Progress Display Settings
+progress-display:
+ enabled: true
+ type: "bossbar"
+ color: "BLUE"
+ style: "SOLID"
+ update-interval: 1
+
+# Suggested Response
+suggested-responses-enabled: false
+suggested-response-models:
+ - "llama3"
+suggested-response-count: 3
+suggested-response-prompt: "Conversation:\nUser: {prompt}\nAI: {response}\n\nBased on the above conversation, suggest {count} natural follow-up responses the user might want to say. They should be conversational in tone rather than questions. List them as:\n1. Response 1\n2. Response 2\n3. Response 3"
+suggested-response-presets:
+ - "I see what you mean."
+ - "That's interesting!"
+ - "Tell me more about that."
+suggested-response-presets-enabled: false
+suggested-response-model-toggles:
+ - "llama3"
+suggested-response-cooldown: 10
+
+# Database (for mysql, set database.type: mysql.)
+database:
+ type: sqlite
+ mysql:
+ host: localhost
+ port: 3306
+ database: ollamachat
+ username: root
+ password: ""
+
+# Default prompt
+default-prompt: ""
+
+# Custom prompts
+prompts:
+# friendly: "You are a friendly assistant who responds in a cheerful tone."
+# formal: "You are a professional assistant who responds formally."
# Other AI Configurations
other-ai-configs:
@@ -75,21 +153,21 @@ other-ai-configs:
api-url: "https://api.openai.com/v1/chat/completions"
api-key: "your-openai-api-key"
model: "gpt-4"
- enabled: true
+ enabled: false
+ messages-format: true
```
## Contributing
-We welcome contributions from the community to improve **Ollama-Chat**! If you have ideas, bug reports, or feature requests, please open an issue or submit a pull request on our [GitHub repository](https://github.com/mcraftbbs/Ollama-Chat).
+We welcome contributions! Submit issues or pull requests on our [GitHub repository](https://github.com/mcraftbbs/Ollama-Chat).
## License
-**Ollama-Chat** is licensed under the MIT License. For more details, see the [LICENSE](LICENSE) file.
+Licensed under the MIT License. See [LICENSE](https://github.com/mcraftbbs/Ollama-Chat?tab=MIT-1-ov-file).
## Support
-For assistance, questions, or feedback, please visit our [GitHub repository](https://github.com/mcraftbbs/Ollama-Chat) or join our [community server](https://chat.sarskin.cn/invite/iHgI6LTX).
+For help, visit our [GitHub repository](https://github.com/mcraftbbs/Ollama-Chat) or join our [community server](https://chat.sarskin.cn/invite/iHgI6LTX).
----
**Note**: **Ollama-Chat** is actively developed, with new features and improvements being added regularly. Stay tuned for updates!
diff --git a/build.gradle b/build.gradle
index 723ac50..75f6a97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@ plugins {
}
group = 'com'
-version = '1.0.2'
+version = '1.1.4'
repositories {
mavenCentral()
@@ -19,6 +19,8 @@ repositories {
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT")
+ compileOnly("mysql:mysql-connector-java:8.0.33")
+ compileOnly("org.xerial:sqlite-jdbc:3.46.0.0")
}
def targetJavaVersion = 21
@@ -47,3 +49,5 @@ processResources {
expand props
}
}
+
+
diff --git a/gradlew b/gradlew
old mode 100755
new mode 100644
diff --git a/src/main/java/com/ollamachat/AIService.java b/src/main/java/com/ollamachat/AIService.java
index f54c76e..c198fb0 100644
--- a/src/main/java/com/ollamachat/AIService.java
+++ b/src/main/java/com/ollamachat/AIService.java
@@ -1,15 +1,15 @@
package com.ollamachat;
import com.google.gson.Gson;
-import org.bukkit.Bukkit;
+import com.google.gson.JsonObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.concurrent.CompletableFuture;
public class AIService {
@@ -22,14 +22,28 @@ public class AIService {
this.gson = new Gson();
}
- public CompletableFuture sendRequest(String apiUrl, String apiKey, String model, String prompt) {
+ public CompletableFuture sendRequest(String apiUrl, String apiKey, String model, String prompt, boolean isMessagesFormat) {
return CompletableFuture.supplyAsync(() -> {
try {
- Map requestBody = Map.of(
- "model", model,
- "prompt", prompt,
- "stream", false
- );
+ Map requestBody;
+ if (isMessagesFormat) {
+ requestBody = Map.of(
+ "model", model,
+ "messages", List.of(
+ Map.of(
+ "role", "user",
+ "content", prompt
+ )
+ ),
+ "stream", false
+ );
+ } else {
+ requestBody = Map.of(
+ "model", model,
+ "prompt", prompt,
+ "stream", false
+ );
+ }
String jsonRequest = gson.toJson(requestBody);
@@ -38,6 +52,10 @@ public class AIService {
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonRequest));
+ if (apiKey != null && !apiKey.isEmpty()) {
+ requestBuilder.header("Authorization", "Bearer " + apiKey);
+ }
+
HttpRequest request = requestBuilder.build();
HttpResponse response = httpClient.send(
@@ -46,18 +64,102 @@ public class AIService {
);
if (response.statusCode() == 200) {
- Bukkit.getConsoleSender().sendMessage("AI API Response: " + response.body());
return response.body();
} else {
- Bukkit.getConsoleSender().sendMessage("AI API Response-ERR: " + response.body());
throw new RuntimeException("AI API Error: " + response.body());
}
} catch (Exception e) {
- Bukkit.getConsoleSender().sendMessage("AI API Response-ERR: " + e.getMessage());
throw new RuntimeException("Failed to get response from AI: " + e.getMessage(), e);
}
});
}
+
+ public CompletableFuture sendStreamingRequest(String apiUrl, String apiKey, String model, String prompt, Consumer responseConsumer, boolean isMessagesFormat) {
+ return CompletableFuture.runAsync(() -> {
+ try {
+ Map requestBody;
+ if (isMessagesFormat) {
+ requestBody = Map.of(
+ "model", model,
+ "messages", List.of(
+ Map.of(
+ "role", "user",
+ "content", prompt
+ )
+ ),
+ "stream", true
+ );
+ } else {
+ requestBody = Map.of(
+ "model", model,
+ "prompt", prompt,
+ "stream", true
+ );
+ }
+
+ String jsonRequest = gson.toJson(requestBody);
+
+ HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
+ .uri(URI.create(apiUrl))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(jsonRequest));
+
+ if (apiKey != null && !apiKey.isEmpty()) {
+ requestBuilder.header("Authorization", "Bearer " + apiKey);
+ }
+
+ HttpRequest request = requestBuilder.build();
+
+ HttpResponse response = httpClient.send(
+ request,
+ HttpResponse.BodyHandlers.ofString()
+ );
+
+ if (response.statusCode() == 200) {
+ StringBuilder buffer = new StringBuilder();
+ int minBufferLength = 50; // Minimum length before sending
+ String[] lines = response.body().split("\n");
+ for (String line : lines) {
+ if (!line.trim().isEmpty()) {
+ if (isMessagesFormat && line.startsWith("data: ")) {
+ String jsonData = line.substring(6); // Remove "data: " prefix
+ if (jsonData.equals("[DONE]")) continue;
+ JsonObject json = gson.fromJson(jsonData, JsonObject.class);
+ if (json.has("choices")) {
+ String partialResponse = json.getAsJsonArray("choices")
+ .get(0).getAsJsonObject()
+ .get("delta").getAsJsonObject()
+ .get("content").getAsString();
+ buffer.append(partialResponse);
+ }
+ } else if (!isMessagesFormat) {
+ JsonObject json = gson.fromJson(line, JsonObject.class);
+ if (json.has("response")) {
+ String partialResponse = json.get("response").getAsString();
+ buffer.append(partialResponse);
+ }
+ }
+
+ // Check if buffer ends with a sentence boundary or is long enough
+ String currentBuffer = buffer.toString();
+ if (currentBuffer.endsWith(".") || currentBuffer.endsWith("?") ||
+ currentBuffer.endsWith("!") || currentBuffer.length() >= minBufferLength) {
+ responseConsumer.accept(currentBuffer);
+ buffer.setLength(0); // Clear buffer
+ }
+ }
+ }
+ // Send any remaining content in the buffer
+ if (buffer.length() > 0) {
+ responseConsumer.accept(buffer.toString());
+ }
+ } else {
+ throw new RuntimeException("AI API Error: " + response.body());
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to get streaming response from AI: " + e.getMessage(), e);
+ }
+ });
+ }
}
-
diff --git a/src/main/java/com/ollamachat/ChatHistoryManager.java b/src/main/java/com/ollamachat/ChatHistoryManager.java
index c263d83..44761b1 100644
--- a/src/main/java/com/ollamachat/ChatHistoryManager.java
+++ b/src/main/java/com/ollamachat/ChatHistoryManager.java
@@ -2,6 +2,8 @@ package com.ollamachat;
import org.bukkit.entity.Player;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
public class ChatHistoryManager {
@@ -17,11 +19,35 @@ public class ChatHistoryManager {
databaseManager.savePlayerInfo(player.getUniqueId(), player.getName());
}
- public void saveChatHistory(UUID playerUuid, String aiModel, String prompt, String response) {
- databaseManager.saveChatHistory(playerUuid, aiModel, prompt, response);
+ public String createConversation(UUID playerUuid, String aiModel, String convName) {
+ return databaseManager.createConversation(playerUuid, aiModel, convName);
}
- public String getChatHistory(UUID playerUuid, String aiModel) {
- return databaseManager.getChatHistory(playerUuid, aiModel, maxHistory);
+ public boolean conversationExistsByName(UUID playerUuid, String aiModel, String convName) {
+ return databaseManager.conversationExistsByName(playerUuid, aiModel, convName);
}
-}
+
+ public String getConversationId(UUID playerUuid, String aiModel, String convName) {
+ return databaseManager.getConversationId(playerUuid, aiModel, convName);
+ }
+
+ public boolean conversationExists(UUID playerUuid, String aiModel, String convId) {
+ return databaseManager.conversationExists(playerUuid, aiModel, convId);
+ }
+
+ public boolean deleteConversation(UUID playerUuid, String aiModel, String convId) {
+ return databaseManager.deleteConversation(playerUuid, aiModel, convId);
+ }
+
+ public Map listConversations(UUID playerUuid, String aiModel) {
+ return databaseManager.listConversations(playerUuid, aiModel);
+ }
+
+ public void saveChatHistory(UUID playerUuid, String aiModel, String conversationId, String prompt, String response) {
+ databaseManager.saveChatHistory(playerUuid, aiModel, conversationId, prompt, response);
+ }
+
+ public String getChatHistory(UUID playerUuid, String aiModel, String conversationId) {
+ return databaseManager.getChatHistory(playerUuid, aiModel, conversationId, maxHistory);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ollamachat/DatabaseManager.java b/src/main/java/com/ollamachat/DatabaseManager.java
index 9d7fe1d..401741f 100644
--- a/src/main/java/com/ollamachat/DatabaseManager.java
+++ b/src/main/java/com/ollamachat/DatabaseManager.java
@@ -1,81 +1,308 @@
+
package com.ollamachat;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.plugin.java.JavaPlugin;
+
import java.sql.*;
+import java.util.HashMap;
+import java.util.Map;
import java.util.UUID;
+import java.util.logging.Logger;
public class DatabaseManager {
- private Connection connection;
+ private final JavaPlugin plugin;
+ private final Logger logger;
+ private String databaseType; // "sqlite" or "mysql"
+ private Connection sqliteConnection; // For SQLite, maintain a single connection
+ private final ClassLoader dependencyClassLoader;
+ private String mysqlUrl;
+ private String mysqlUsername;
+ private String mysqlPassword;
- public DatabaseManager() {
+ public DatabaseManager(JavaPlugin plugin, ClassLoader dependencyClassLoader) {
+ this.plugin = plugin;
+ this.logger = plugin.getLogger();
+ this.dependencyClassLoader = dependencyClassLoader;
initializeDatabase();
}
private void initializeDatabase() {
+ FileConfiguration config = plugin.getConfig();
+ databaseType = config.getString("database.type", "sqlite").toLowerCase();
+
try {
- Class.forName("org.sqlite.JDBC");
- connection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
+ if (databaseType.equals("mysql")) {
+ initializeMySQL();
+ } else {
+ initializeSQLite();
+ }
createTables();
} catch (Exception e) {
+ logger.severe("Failed to initialize database: " + e.getMessage());
e.printStackTrace();
+ throw new RuntimeException("Database initialization failed", e);
+ }
+ }
+
+ private void initializeSQLite() {
+ try {
+ // Use the dependency class loader to load SQLite driver
+ Class.forName("org.sqlite.JDBC", true, dependencyClassLoader);
+ } catch (ClassNotFoundException e) {
+ logger.severe("SQLite JDBC driver not found. Ensure 'sqlite-jdbc' dependency is included in the plugin.");
+ throw new RuntimeException("SQLite driver not found", e);
+ }
+
+ try {
+ databaseType = "sqlite";
+ sqliteConnection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
+ sqliteConnection.setAutoCommit(true);
+ logger.info("SQLite database initialized successfully.");
+ } catch (SQLException e) {
+ logger.severe("Failed to initialize SQLite database: " + e.getMessage());
+ throw new RuntimeException("SQLite initialization failed", e);
+ }
+ }
+
+ private void initializeMySQL() {
+ try {
+ // Use the dependency class loader to load MySQL driver
+ Class.forName("com.mysql.cj.jdbc.Driver", true, dependencyClassLoader);
+ } catch (ClassNotFoundException e) {
+ logger.severe("MySQL JDBC driver not found. Ensure 'mysql-connector-java' dependency is included in the plugin.");
+ logger.warning("Falling back to SQLite due to missing MySQL driver.");
+ initializeSQLite();
+ return;
+ }
+
+ try {
+ FileConfiguration config = plugin.getConfig();
+ String host = config.getString("database.mysql.host", "localhost");
+ int port = config.getInt("database.mysql.port", 3306);
+ String database = config.getString("database.mysql.database", "ollamachat");
+ mysqlUsername = config.getString("database.mysql.username", "root");
+ mysqlPassword = config.getString("database.mysql.password", "");
+ mysqlUrl = String.format("jdbc:mysql://%s:%d/%s?useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true", host, port, database);
+
+ // Test connection
+ try (Connection conn = DriverManager.getConnection(mysqlUrl, mysqlUsername, mysqlPassword)) {
+ logger.info("MySQL database initialized successfully.");
+ }
+ } catch (SQLException e) {
+ logger.severe("Failed to initialize MySQL database: " + e.getMessage());
+ logger.warning("Falling back to SQLite due to MySQL initialization failure.");
+ initializeSQLite();
+ }
+ }
+
+ private Connection getConnection() throws SQLException {
+ if (databaseType.equals("sqlite")) {
+ if (sqliteConnection == null || sqliteConnection.isClosed()) {
+ sqliteConnection = DriverManager.getConnection("jdbc:sqlite:plugins/OllamaChat/chat_history.db");
+ sqliteConnection.setAutoCommit(true);
+ }
+ return sqliteConnection;
+ } else {
+ return DriverManager.getConnection(mysqlUrl, mysqlUsername, mysqlPassword);
}
}
private void createTables() throws SQLException {
- try (Statement stmt = connection.createStatement()) {
+ try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS players (" +
"uuid TEXT PRIMARY KEY," +
"username TEXT NOT NULL)");
- stmt.execute("CREATE TABLE IF NOT EXISTS chat_history (" +
- "id INTEGER PRIMARY KEY AUTOINCREMENT," +
+ stmt.execute("CREATE TABLE IF NOT EXISTS conversations (" +
+ "conversation_id TEXT NOT NULL," +
"player_uuid TEXT NOT NULL," +
"ai_model TEXT NOT NULL," +
+ "conversation_name TEXT NOT NULL," +
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
+ "PRIMARY KEY (conversation_id, player_uuid, ai_model)," +
+ "FOREIGN KEY (player_uuid) REFERENCES players(uuid))");
+
+ stmt.execute("CREATE TABLE IF NOT EXISTS chat_history (" +
+ "id INTEGER PRIMARY KEY " + (databaseType.equals("sqlite") ? "AUTOINCREMENT" : "AUTO_INCREMENT") + "," +
+ "player_uuid TEXT NOT NULL," +
+ "ai_model TEXT NOT NULL," +
+ "conversation_id TEXT," +
"timestamp DATETIME DEFAULT CURRENT_TIMESTAMP," +
"prompt TEXT NOT NULL," +
"response TEXT NOT NULL," +
- "FOREIGN KEY (player_uuid) REFERENCES players(uuid))");
+ "FOREIGN KEY (player_uuid) REFERENCES players(uuid)," +
+ "FOREIGN KEY (conversation_id, player_uuid, ai_model) REFERENCES conversations(conversation_id, player_uuid, ai_model))");
}
}
public void savePlayerInfo(UUID uuid, String username) {
- String sql = "INSERT OR REPLACE INTO players(uuid, username) VALUES(?, ?)";
- try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ String sql;
+ if (databaseType.equals("sqlite")) {
+ sql = "INSERT OR REPLACE INTO players (uuid, username) VALUES (?, ?)";
+ } else {
+ sql = "INSERT INTO players (uuid, username) VALUES (?, ?) ON DUPLICATE KEY UPDATE username = ?";
+ }
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, uuid.toString());
pstmt.setString(2, username);
+ if (databaseType.equals("mysql")) {
+ pstmt.setString(3, username); // For ON DUPLICATE KEY UPDATE
+ }
pstmt.executeUpdate();
} catch (SQLException e) {
+ logger.severe("Failed to save player info: " + e.getMessage());
e.printStackTrace();
}
}
- public void saveChatHistory(UUID playerUuid, String aiModel, String prompt, String response) {
- String sql = "INSERT INTO chat_history(player_uuid, ai_model, prompt, response) VALUES(?, ?, ?, ?)";
- try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ public String createConversation(UUID playerUuid, String aiModel, String convName) {
+ String convId = UUID.randomUUID().toString();
+ String sql = "INSERT INTO conversations (conversation_id, player_uuid, ai_model, conversation_name) VALUES (?, ?, ?, ?)";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
+ pstmt.setString(1, convId);
+ pstmt.setString(2, playerUuid.toString());
+ pstmt.setString(3, aiModel);
+ pstmt.setString(4, convName);
+ pstmt.executeUpdate();
+ return convId;
+ } catch (SQLException e) {
+ logger.severe("Failed to create conversation: " + e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public boolean conversationExistsByName(UUID playerUuid, String aiModel, String convName) {
+ String sql = "SELECT 1 FROM conversations WHERE conversation_name = ? AND player_uuid = ? AND ai_model = ?";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
+ pstmt.setString(1, convName);
+ pstmt.setString(2, playerUuid.toString());
+ pstmt.setString(3, aiModel);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ return rs.next();
+ }
+ } catch (SQLException e) {
+ logger.severe("Failed to check conversation existence: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public String getConversationId(UUID playerUuid, String aiModel, String convName) {
+ String sql = "SELECT conversation_id FROM conversations WHERE conversation_name = ? AND player_uuid = ? AND ai_model = ?";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
+ pstmt.setString(1, convName);
+ pstmt.setString(2, playerUuid.toString());
+ pstmt.setString(3, aiModel);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ if (rs.next()) {
+ return rs.getString("conversation_id");
+ }
+ }
+ } catch (SQLException e) {
+ logger.severe("Failed to get conversation ID: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public boolean conversationExists(UUID playerUuid, String aiModel, String convId) {
+ String sql = "SELECT 1 FROM conversations WHERE conversation_id = ? AND player_uuid = ? AND ai_model = ?";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
+ pstmt.setString(1, convId);
+ pstmt.setString(2, playerUuid.toString());
+ pstmt.setString(3, aiModel);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ return rs.next();
+ }
+ } catch (SQLException e) {
+ logger.severe("Failed to check conversation existence: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public boolean deleteConversation(UUID playerUuid, String aiModel, String convId) {
+ String sqlDeleteHistory = "DELETE FROM chat_history WHERE conversation_id = ? AND player_uuid = ? AND ai_model = ?";
+ String sqlDeleteConv = "DELETE FROM conversations WHERE conversation_id = ? AND player_uuid = ? AND ai_model = ?";
+ try (Connection conn = getConnection();
+ PreparedStatement pstmtHistory = conn.prepareStatement(sqlDeleteHistory);
+ PreparedStatement pstmtConv = conn.prepareStatement(sqlDeleteConv)) {
+ pstmtHistory.setString(1, convId);
+ pstmtHistory.setString(2, playerUuid.toString());
+ pstmtHistory.setString(3, aiModel);
+ pstmtConv.setString(1, convId);
+ pstmtConv.setString(2, playerUuid.toString());
+ pstmtConv.setString(3, aiModel);
+ int rowsAffected = pstmtHistory.executeUpdate() + pstmtConv.executeUpdate();
+ return rowsAffected > 0;
+ } catch (SQLException e) {
+ logger.severe("Failed to delete conversation: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public Map listConversations(UUID playerUuid, String aiModel) {
+ Map conversations = new HashMap<>();
+ String sql = "SELECT conversation_id, conversation_name FROM conversations WHERE player_uuid = ? AND ai_model = ?";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, playerUuid.toString());
pstmt.setString(2, aiModel);
- pstmt.setString(3, prompt);
- pstmt.setString(4, response);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ while (rs.next()) {
+ conversations.put(rs.getString("conversation_id"), rs.getString("conversation_name"));
+ }
+ }
+ } catch (SQLException e) {
+ logger.severe("Failed to list conversations: " + e.getMessage());
+ e.printStackTrace();
+ }
+ return conversations;
+ }
+
+ public void saveChatHistory(UUID playerUuid, String aiModel, String conversationId, String prompt, String response) {
+ String sql = "INSERT INTO chat_history (player_uuid, ai_model, conversation_id, prompt, response) VALUES (?, ?, ?, ?, ?)";
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
+ pstmt.setString(1, playerUuid.toString());
+ pstmt.setString(2, aiModel);
+ if (conversationId != null) {
+ pstmt.setString(3, conversationId);
+ } else {
+ pstmt.setNull(3, Types.VARCHAR);
+ }
+ pstmt.setString(4, prompt);
+ pstmt.setString(5, response);
pstmt.executeUpdate();
} catch (SQLException e) {
+ logger.severe("Failed to save chat history: " + e.getMessage());
e.printStackTrace();
}
}
- public String getChatHistory(UUID playerUuid, String aiModel, int maxHistory) {
+ public String getChatHistory(UUID playerUuid, String aiModel, String conversationId, int maxHistory) {
StringBuilder history = new StringBuilder();
String sql = "SELECT prompt, response FROM chat_history " +
"WHERE player_uuid = ? AND ai_model = ? " +
+ (conversationId != null ? "AND conversation_id = ? " : "AND conversation_id IS NULL ") +
"ORDER BY timestamp DESC LIMIT ?";
- try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
+ try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, playerUuid.toString());
pstmt.setString(2, aiModel);
- pstmt.setInt(3, maxHistory);
- ResultSet rs = pstmt.executeQuery();
- while (rs.next()) {
- history.insert(0, "User: " + rs.getString("prompt") + "\n");
- history.insert(0, "AI: " + rs.getString("response") + "\n");
+ int index = 3;
+ if (conversationId != null) {
+ pstmt.setString(index++, conversationId);
+ }
+ pstmt.setInt(index, maxHistory);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ while (rs.next()) {
+ history.insert(0, "User: " + rs.getString("prompt") + "\n");
+ history.insert(0, "AI: " + rs.getString("response") + "\n");
+ }
}
} catch (SQLException e) {
+ logger.severe("Failed to get chat history: " + e.getMessage());
e.printStackTrace();
}
return history.toString();
@@ -83,11 +310,20 @@ public class DatabaseManager {
public void close() {
try {
- if (connection != null) {
- connection.close();
+ if (databaseType.equals("sqlite") && sqliteConnection != null && !sqliteConnection.isClosed()) {
+ sqliteConnection.close();
}
+ // No need to close MySQL connections explicitly as they are managed per query
} catch (SQLException e) {
+ logger.severe("Failed to close database: " + e.getMessage());
e.printStackTrace();
}
}
}
+
+
+
+
+
+
+
diff --git a/src/main/java/com/ollamachat/DependencyLoader.java b/src/main/java/com/ollamachat/DependencyLoader.java
new file mode 100644
index 0000000..0459840
--- /dev/null
+++ b/src/main/java/com/ollamachat/DependencyLoader.java
@@ -0,0 +1,114 @@
+package com.ollamachat;
+
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+public class DependencyLoader {
+ private final JavaPlugin plugin;
+ private final Logger logger;
+ private final File libDir;
+ private URLClassLoader dependencyClassLoader;
+
+ public DependencyLoader(JavaPlugin plugin) {
+ this.plugin = plugin;
+ this.logger = plugin.getLogger();
+ this.libDir = new File(plugin.getDataFolder(), "libs");
+ }
+
+ public boolean loadDependencies() {
+ try {
+ // Create libs directory if it doesn't exist
+ if (!libDir.exists()) {
+ libDir.mkdirs();
+ }
+
+ // Define dependencies to download
+ List dependencies = new ArrayList<>();
+ dependencies.add(new Dependency("com.mysql", "mysql-connector-j", "8.0.33"));
+ dependencies.add(new Dependency("org.xerial", "sqlite-jdbc", "3.46.0.0"));
+
+ // Download each dependency
+ List jarUrls = new ArrayList<>();
+ for (Dependency dep : dependencies) {
+ File jarFile = downloadDependency(dep);
+ if (jarFile != null) {
+ jarUrls.add(jarFile.toURI().toURL());
+ logger.info("Found dependency: " + jarFile.getName());
+ } else {
+ logger.warning("Failed to download dependency: " + dep.artifactId + "-" + dep.version);
+ }
+ }
+
+ if (jarUrls.isEmpty()) {
+ logger.warning("No dependencies were successfully downloaded.");
+ return false;
+ }
+
+ // Create a new classloader with the dependencies
+ dependencyClassLoader = new URLClassLoader(
+ jarUrls.toArray(new URL[0]),
+ plugin.getClass().getClassLoader()
+ );
+
+ // Set this classloader as the thread context classloader
+ Thread.currentThread().setContextClassLoader(dependencyClassLoader);
+ logger.info("Successfully loaded " + jarUrls.size() + " dependencies");
+
+ return true;
+ } catch (Exception e) {
+ logger.severe("Failed to load dependencies: " + e.getMessage());
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public ClassLoader getDependencyClassLoader() {
+ return dependencyClassLoader;
+ }
+
+ private File downloadDependency(Dependency dep) {
+ String fileName = dep.artifactId + "-" + dep.version + ".jar";
+ File file = new File(libDir, fileName);
+
+ if (file.exists()) {
+ return file;
+ }
+
+ String mavenPath = String.format("%s/%s/%s/%s-%s.jar",
+ dep.groupId.replace(".", "/"), dep.artifactId, dep.version, dep.artifactId, dep.version);
+ String url = "https://repo1.maven.org/maven2/" + mavenPath;
+
+ try (InputStream in = new URL(url).openStream()) {
+ Files.copy(in, file.toPath());
+ logger.info("Downloaded dependency: " + fileName);
+ return file;
+ } catch (IOException e) {
+ logger.severe("Failed to download dependency " + fileName + ": " + e.getMessage());
+ return null;
+ }
+ }
+
+ private static class Dependency {
+ String groupId;
+ String artifactId;
+ String version;
+
+ Dependency(String groupId, String artifactId, String version) {
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ this.version = version;
+ }
+ }
+}
+
+
+
+
diff --git a/src/main/java/com/ollamachat/Ollamachat.java b/src/main/java/com/ollamachat/Ollamachat.java
deleted file mode 100644
index c3a6f2c..0000000
--- a/src/main/java/com/ollamachat/Ollamachat.java
+++ /dev/null
@@ -1,352 +0,0 @@
-package com.ollamachat;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import net.kyori.adventure.text.Component;
-import org.bukkit.Bukkit;
-import org.bukkit.command.Command;
-import org.bukkit.command.CommandSender;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.bukkit.configuration.file.YamlConfiguration;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.AsyncPlayerChatEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
-
-public class Ollamachat extends JavaPlugin implements Listener {
-
- private AIService aiService;
- private Gson gson;
- private String ollamaApiUrl;
- private String ollamaModel;
- private String triggerPrefix;
- private int maxResponseLength;
- private Map otherAIConfigs;
- private boolean ollamaEnabled;
- private Map otherAIEnabled;
-
- private FileConfiguration langConfig;
- private DatabaseManager databaseManager;
- private ChatHistoryManager chatHistoryManager;
- private int maxHistory;
-
- @Override
- public void onEnable() {
- saveDefaultConfig();
- reloadConfigValues();
- String language = getConfig().getString("language", "en");
- loadLanguageFile(language);
-
- updateCommandUsages();
-
- databaseManager = new DatabaseManager();
- maxHistory = getConfig().getInt("max-history", 5);
- chatHistoryManager = new ChatHistoryManager(databaseManager, maxHistory);
-
- aiService = new AIService();
- gson = new Gson();
-
- getServer().getPluginManager().registerEvents(this, this);
- getCommand("ollamachat").setExecutor(this);
- getCommand("aichat").setExecutor(this);
- }
-
- private void updateCommandUsages() {
- String usageOllamachat = getMessage("usage-ollamachat", null);
- String usageAichat = getMessage("usage-aichat", null);
-
- getCommand("ollamachat").setUsage(usageOllamachat);
- getCommand("aichat").setUsage(usageAichat);
- }
-
- @Override
- public void onDisable() {
- databaseManager.close();
- }
-
- private void updateConfig() {
- FileConfiguration config = getConfig();
-
- if (!config.contains("ollama-enabled")) {
- config.set("ollama-enabled", true);
- }
- if (!config.contains("language")) {
- config.set("language", "en");
- }
- if (!config.contains("other-ai-configs")) {
- config.createSection("other-ai-configs");
- }
- if (!config.contains("max-history")) {
- config.set("max-history", 5);
- }
-
- saveConfig();
- }
-
- private void reloadConfigValues() {
- File configFile = new File(getDataFolder(), "config.yml");
- if (!configFile.exists()) {
- saveDefaultConfig();
- } else {
- try {
- YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
- if (!config.contains("ollama-api-url") || !config.contains("model")) {
- getLogger().warning(getMessage("config-invalid", null));
- configFile.delete();
- saveDefaultConfig();
- }
- } catch (Exception e) {
- getLogger().severe(getMessage("config-load-failed", Map.of("error", e.getMessage())));
- configFile.delete();
- saveDefaultConfig();
- }
- }
-
- reloadConfig();
- updateConfig();
-
- FileConfiguration config = getConfig();
- ollamaApiUrl = config.getString("ollama-api-url", "http://localhost:11434/api/generate");
- ollamaModel = config.getString("model", "llama3");
- triggerPrefix = config.getString("trigger-prefix", "@bot ");
- maxResponseLength = config.getInt("max-response-length", 500);
- ollamaEnabled = config.getBoolean("ollama-enabled", true);
- maxHistory = config.getInt("max-history", 5);
-
- otherAIConfigs = new HashMap<>();
- otherAIEnabled = new HashMap<>();
- if (config.contains("other-ai-configs")) {
- for (String aiName : config.getConfigurationSection("other-ai-configs").getKeys(false)) {
- String apiUrl = config.getString("other-ai-configs." + aiName + ".api-url");
- String apiKey = config.getString("other-ai-configs." + aiName + ".api-key");
- String model = config.getString("other-ai-configs." + aiName + ".model");
- boolean enabled = config.getBoolean("other-ai-configs." + aiName + ".enabled", true);
- otherAIConfigs.put(aiName, new AIConfig(apiUrl, apiKey, model));
- otherAIEnabled.put(aiName, enabled);
- }
- }
- }
-
- private void loadLanguageFile(String language) {
- File langFolder = new File(getDataFolder(), "lang");
- if (!langFolder.exists()) {
- langFolder.mkdirs();
- }
-
- File langFile = new File(langFolder, language + ".lang");
- if (!langFile.exists()) {
- saveResource("lang/" + language + ".lang", false);
- }
-
- try {
- langConfig = YamlConfiguration.loadConfiguration(langFile);
- } catch (Exception e) {
- getLogger().severe("Failed to load language file: " + langFile.getName());
- e.printStackTrace();
- }
- }
-
- private String getMessage(String key, Map placeholders) {
- String message = langConfig.getString(key, "§cMissing language key: " + key);
- if (placeholders != null) {
- for (Map.Entry entry : placeholders.entrySet()) {
- message = message.replace("{" + entry.getKey() + "}", entry.getValue());
- }
- }
- return message;
- }
-
- @EventHandler
- public void onPlayerJoin(PlayerJoinEvent event) {
- chatHistoryManager.savePlayerInfo(event.getPlayer());
- }
-
- @EventHandler
- public void onPlayerChat(AsyncPlayerChatEvent event) {
- if (!ollamaEnabled) return;
-
- String message = event.getMessage();
- Player player = event.getPlayer();
-
- if (message.startsWith(triggerPrefix)) {
- /*event.setCancelled(true);*/
- String prompt = message.substring(triggerPrefix.length()).trim();
-
- if (!prompt.isEmpty()) {
- processOllamaQueryAsync(player, prompt);
- }
- }
- }
-
- private void processOllamaQueryAsync(Player player, String prompt) {
- CompletableFuture.runAsync(() -> {
- try {
- String history = chatHistoryManager.getChatHistory(player.getUniqueId(), "ollama");
-
- String context = history + "User: " + prompt;
-
- String responseBody = aiService.sendRequest(ollamaApiUrl, null, ollamaModel, context).join();
- OllamaResponse ollamaResponse = gson.fromJson(responseBody, OllamaResponse.class);
-
- chatHistoryManager.saveChatHistory(player.getUniqueId(), "ollama", prompt, ollamaResponse.response);
-
- sendFormattedResponse(player, ollamaResponse.response);
- } catch (Exception e) {
- getLogger().severe("Error processing Ollama request: " + e.getMessage());
- sendErrorMessage(player, getMessage("error-prefix", null) + "Failed to get response from Ollama");
- }
- });
- }
-
- private void processOtherAIQueryAsync(Player player, String aiName, String prompt) {
- if (!otherAIEnabled.getOrDefault(aiName, false)) {
- sendErrorMessage(player, getMessage("error-prefix", null) + getMessage("toggle-disabled", Map.of("ai-name", aiName)));
- return;
- }
-
- CompletableFuture.runAsync(() -> {
- try {
- String history = chatHistoryManager.getChatHistory(player.getUniqueId(), aiName);
-
- String context = history + "User: " + prompt;
-
- AIConfig aiConfig = otherAIConfigs.get(aiName);
- String responseBody = aiService.sendRequest(
- aiConfig.getApiUrl(),
- aiConfig.getApiKey(),
- aiConfig.getModel(),
- context
- ).join();
-
- String response = parseAIResponse(aiName, responseBody);
-
- chatHistoryManager.saveChatHistory(
- player.getUniqueId(),
- aiName,
- prompt,
- response
- );
-
- sendFormattedResponse(player, response);
- } catch (Exception e) {
- getLogger().severe("Error processing " + aiName + " request: " + e.getMessage());
- sendErrorMessage(player, getMessage("error-prefix", null) + "Failed to get response from " + aiName);
- }
- });
- }
-
- private String parseAIResponse(String aiName, String responseBody) {
- switch (aiName.toLowerCase()) {
- case "openai":
- JsonObject json = gson.fromJson(responseBody, JsonObject.class);
- return json.getAsJsonArray("choices")
- .get(0).getAsJsonObject()
- .get("message").getAsJsonObject()
- .get("content").getAsString();
- default:
- return gson.fromJson(responseBody, OllamaResponse.class).response;
- }
- }
-
- private void sendFormattedResponse(Player player, String response) {
-
- response = response.replaceAll("User:", "§7User:").replaceAll("AI:", "");
-
- if (response.trim().isEmpty()) {
- Bukkit.broadcast(Component.text(getMessage("response-prefix", null) + "§cNastala chyba, zkuste se zeptat znovu."));
- return;
- }
-
- if (response.length() > maxResponseLength) {
- response = response.substring(0, maxResponseLength) + "...";
- }
-
- Bukkit.broadcast(Component.text(getMessage("response-prefix", null) + response));
- /*player.sendMessage(getMessage("response-prefix", null) + response);*/
- }
-
- private void sendErrorMessage(Player player, String errorMessage) {
- player.sendMessage(getMessage("error-prefix", null) + errorMessage);
- }
-
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- if (command.getName().equalsIgnoreCase("ollamachat")) {
- if (args.length > 0 && args[0].equalsIgnoreCase("reload")) {
- reloadConfigValues();
- loadLanguageFile(getConfig().getString("language", "en"));
- sender.sendMessage(getMessage("reload-success", null));
- return true;
- } else if (args.length > 1 && args[0].equalsIgnoreCase("toggle")) {
- String aiName = args[1];
- if (aiName.equalsIgnoreCase("ollama")) {
- ollamaEnabled = !ollamaEnabled;
- sender.sendMessage(getMessage(ollamaEnabled ? "ollama-enabled" : "ollama-disabled", null));
- } else if (otherAIConfigs.containsKey(aiName)) {
- boolean newState = !otherAIEnabled.getOrDefault(aiName, false);
- otherAIEnabled.put(aiName, newState);
- sender.sendMessage(getMessage(newState ? "toggle-enabled" : "toggle-disabled", Map.of("ai-name", aiName)));
- } else {
- sender.sendMessage(getMessage("invalid-ai-name", Map.of("ai-list", String.join(", ", otherAIConfigs.keySet()))));
- }
- return true;
- }
- } else if (command.getName().equalsIgnoreCase("aichat")) {
- if (args.length < 2) {
- sender.sendMessage(getMessage("usage-aichat", null));
- return true;
- }
-
- String aiName = args[0];
- String prompt = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length));
-
- if (otherAIConfigs.containsKey(aiName)) {
- if (sender instanceof Player) {
- processOtherAIQueryAsync((Player) sender, aiName, prompt);
- } else {
- sender.sendMessage(getMessage("player-only", null));
- }
- } else {
- sender.sendMessage(getMessage("invalid-ai-name", Map.of("ai-list", String.join(", ", otherAIConfigs.keySet()))));
- }
- return true;
- }
- return false;
- }
-
- private static class AIConfig {
- private final String apiUrl;
- private final String apiKey;
- private final String model;
-
- public AIConfig(String apiUrl, String apiKey, String model) {
- this.apiUrl = apiUrl;
- this.apiKey = apiKey;
- this.model = model;
- }
-
- public String getApiUrl() {
- return apiUrl;
- }
-
- public String getApiKey() {
- return apiKey;
- }
-
- public String getModel() {
- return model;
- }
- }
-
- private static class OllamaResponse {
- public String response;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/ollamachat/ProgressManager.java b/src/main/java/com/ollamachat/ProgressManager.java
new file mode 100644
index 0000000..07d8a6c
--- /dev/null
+++ b/src/main/java/com/ollamachat/ProgressManager.java
@@ -0,0 +1,104 @@
+package com.ollamachat;
+
+import com.ollamachat.core.Ollamachat;
+import com.ollamachat.core.ConfigManager;
+import org.bukkit.Bukkit;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.boss.BossBar;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class ProgressManager {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager; // Add ConfigManager reference
+ private final Map bossBars = new HashMap<>();
+ private final Map tasks = new HashMap<>();
+
+ public ProgressManager(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager(); // Initialize ConfigManager
+ }
+
+ public void startProgress(Player player, String title, BarColor color, BarStyle style) {
+ UUID uuid = player.getUniqueId();
+ cleanup(player);
+
+ if (plugin.getConfig().getString("progress-display.type", "bossbar").equalsIgnoreCase("bossbar")) {
+ handleBossBarProgress(player, uuid, title, color, style);
+ } else {
+ handleActionBarProgress(player, uuid);
+ }
+ }
+
+ private void handleBossBarProgress(Player player, UUID uuid, String title, BarColor color, BarStyle style) {
+ BossBar bossBar = Bukkit.createBossBar(
+ configManager.getMessage("generating-status", Map.of("progress", "0")), // Use configManager
+ color,
+ style
+ );
+ bossBar.addPlayer(player);
+ bossBar.setProgress(0.0);
+ bossBar.setVisible(true);
+ bossBars.put(uuid, bossBar);
+
+ long interval = 20L * plugin.getConfig().getInt("progress-display.update-interval", 1);
+ tasks.put(uuid, Bukkit.getScheduler().runTaskTimer(plugin, () -> {
+ if (!player.isOnline()) {
+ cleanup(player);
+ }
+ }, 0, interval));
+ }
+
+ private void handleActionBarProgress(Player player, UUID uuid) {
+ long interval = 20L * plugin.getConfig().getInt("progress-display.update-interval", 1);
+ tasks.put(uuid, Bukkit.getScheduler().runTaskTimer(plugin, () -> {
+ if (!player.isOnline()) {
+ cleanup(player);
+ return;
+ }
+ player.sendActionBar(configManager.getMessage("generating-status", Map.of("progress", "0"))); // Use configManager
+ }, 0, interval));
+ }
+
+ public void complete(Player player) {
+ UUID uuid = player.getUniqueId();
+ if (bossBars.containsKey(uuid)) {
+ BossBar bossBar = bossBars.get(uuid);
+ bossBar.setProgress(1.0);
+ bossBar.setTitle(configManager.getMessage("complete-status", null)); // Use configManager
+ bossBar.setColor(BarColor.GREEN);
+ Bukkit.getScheduler().runTaskLater(plugin, () -> cleanup(player), 40L);
+ } else {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> cleanup(player), 40L);
+ }
+ }
+
+ public void error(Player player) {
+ UUID uuid = player.getUniqueId();
+ if (bossBars.containsKey(uuid)) {
+ BossBar bossBar = bossBars.get(uuid);
+ bossBar.setTitle(configManager.getMessage("error-status", null)); // Use configManager
+ bossBar.setColor(BarColor.RED);
+ Bukkit.getScheduler().runTaskLater(plugin, () -> cleanup(player), 40L);
+ }
+ }
+
+ public void cleanup(Player player) {
+ UUID uuid = player.getUniqueId();
+ if (tasks.containsKey(uuid)) {
+ tasks.get(uuid).cancel();
+ tasks.remove(uuid);
+ }
+ if (bossBars.containsKey(uuid)) {
+ bossBars.get(uuid).removeAll();
+ bossBars.remove(uuid);
+ }
+ }
+}
+
+
diff --git a/src/main/java/com/ollamachat/chat/ChatTriggerHandler.java b/src/main/java/com/ollamachat/chat/ChatTriggerHandler.java
new file mode 100644
index 0000000..c9937b2
--- /dev/null
+++ b/src/main/java/com/ollamachat/chat/ChatTriggerHandler.java
@@ -0,0 +1,164 @@
+package com.ollamachat.chat;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.ollamachat.AIService;
+import com.ollamachat.core.ConfigManager;
+import com.ollamachat.core.Ollamachat;
+import org.bukkit.Bukkit;
+import org.bukkit.boss.BarColor;
+import org.bukkit.boss.BarStyle;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ChatTriggerHandler implements Listener {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager;
+ private final AIService aiService;
+ private final SuggestedResponseHandler suggestedResponseHandler;
+ private final Gson gson;
+
+ public ChatTriggerHandler(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager();
+ this.aiService = new AIService();
+ this.suggestedResponseHandler = new SuggestedResponseHandler(plugin);
+ this.gson = new Gson();
+ }
+
+ @EventHandler
+ public void onPlayerChat(AsyncPlayerChatEvent event) {
+ if (!configManager.isOllamaEnabled()) return;
+
+ String message = event.getMessage();
+ Player player = event.getPlayer();
+
+ for (String prefix : configManager.getTriggerPrefixes()) {
+ if (message.startsWith(prefix)) {
+ //event.setCancelled(true);
+ String prompt = message.substring(prefix.length()).trim();
+ if (!prompt.isEmpty()) {
+ processAIQuery(player, "ollama", prompt);
+ }
+ break;
+ }
+ }
+ }
+
+ public void processAIQuery(Player player, String aiName, String prompt) {
+ if (!aiName.equalsIgnoreCase("ollama") && !configManager.getOtherAIEnabled().getOrDefault(aiName, false)) {
+ sendErrorMessage(player, configManager.getMessage("error-prefix", null) + configManager.getMessage("toggle-disabled", Map.of("ai-name", aiName)));
+ return;
+ }
+
+ if (plugin.getConfig().getBoolean("progress-display.enabled", true)) {
+ BarColor color = BarColor.valueOf(plugin.getConfig().getString("progress-display.color", "BLUE"));
+ BarStyle style = BarStyle.valueOf(plugin.getConfig().getString("progress-display.style", "SOLID"));
+ plugin.getProgressManager().startProgress(player, configManager.getMessage("generating-status", null), color, style);
+ }
+
+ CompletableFuture.runAsync(() -> {
+ try {
+ UUID playerUuid = player.getUniqueId();
+ // Save player info to ensure uuid exists in players table
+ plugin.getChatHistoryManager().savePlayerInfo(player);
+
+ String conversationName = configManager.getSelectedConversations()
+ .computeIfAbsent(playerUuid, k -> new HashMap<>())
+ .getOrDefault(aiName, null);
+ String conversationId = conversationName != null
+ ? plugin.getChatHistoryManager().getConversationId(playerUuid, aiName, conversationName)
+ : null;
+ String history = plugin.getChatHistoryManager().getChatHistory(playerUuid, aiName, conversationId);
+ String selectedPrompt = configManager.getPrompts().getOrDefault(configManager.getDefaultPrompt(), "");
+ String context = history + (selectedPrompt.isEmpty() ? "" : selectedPrompt + "\n") + "User: " + prompt;
+
+ String apiUrl, apiKey, model;
+ boolean isMessagesFormat;
+ if (aiName.equalsIgnoreCase("ollama")) {
+ apiUrl = configManager.getOllamaApiUrl();
+ apiKey = null;
+ model = configManager.getOllamaModel();
+ isMessagesFormat = false;
+ } else {
+ ConfigManager.AIConfig aiConfig = configManager.getOtherAIConfigs().get(aiName);
+ apiUrl = aiConfig.getApiUrl();
+ apiKey = aiConfig.getApiKey();
+ model = aiConfig.getModel();
+ isMessagesFormat = aiConfig.isMessagesFormat();
+ }
+
+ String finalResponse;
+ if (configManager.isStreamingEnabled()) {
+ StringBuilder fullResponse = new StringBuilder();
+ AtomicBoolean isFirstMessage = new AtomicBoolean(true);
+ aiService.sendStreamingRequest(apiUrl, apiKey, model, context, partialResponse -> {
+ if (player.isOnline()) {
+ String formattedPartial = partialResponse.length() > configManager.getMaxResponseLength()
+ ? partialResponse.substring(0, configManager.getMaxResponseLength()) + "..."
+ : partialResponse;
+ String message = isFirstMessage.get()
+ ? configManager.getMessage("response-prefix", null) + formattedPartial
+ : formattedPartial;
+ //player.sendMessage(message);
+ Bukkit.broadcastMessage(message);
+ isFirstMessage.set(false);
+ fullResponse.append(partialResponse);
+ }
+ }, isMessagesFormat).join();
+ finalResponse = fullResponse.toString();
+ } else {
+ String responseBody = aiService.sendRequest(apiUrl, apiKey, model, context, isMessagesFormat).join();
+ if (isMessagesFormat) {
+ JsonObject json = gson.fromJson(responseBody, JsonObject.class);
+ finalResponse = json.getAsJsonArray("choices")
+ .get(0).getAsJsonObject()
+ .get("message").getAsJsonObject()
+ .get("content").getAsString();
+ } else {
+ JsonObject json = gson.fromJson(responseBody, JsonObject.class);
+ finalResponse = json.get("response").getAsString();
+ }
+ if (player.isOnline()) {
+ sendFormattedResponse(player, finalResponse);
+ }
+ }
+
+ if (!finalResponse.isEmpty()) {
+ plugin.getChatHistoryManager().saveChatHistory(playerUuid, aiName, conversationId, prompt, finalResponse);
+ suggestedResponseHandler.sendSuggestedResponses(player, aiName, prompt, finalResponse);
+ }
+ plugin.getProgressManager().complete(player);
+ } catch (Exception e) {
+ plugin.getLogger().severe("Error processing " + aiName + " request: " + e.getMessage());
+ if (player.isOnline()) {
+ sendErrorMessage(player, configManager.getMessage("error-prefix", null) + "Failed to get response from " + aiName);
+ }
+ plugin.getProgressManager().error(player);
+ }
+ });
+ }
+
+ private void sendFormattedResponse(Player player, String response) {
+ if (response.length() > configManager.getMaxResponseLength()) {
+ response = response.substring(0, configManager.getMaxResponseLength()) + "...";
+ }
+ //player.sendMessage(configManager.getMessage("response-prefix", null) + response);
+ Bukkit.broadcastMessage(configManager.getMessage("response-prefix", null) + response);
+ }
+
+ private void sendErrorMessage(Player player, String errorMessage) {
+ player.sendMessage(errorMessage);
+ }
+}
+
+
+
diff --git a/src/main/java/com/ollamachat/chat/SuggestedResponseHandler.java b/src/main/java/com/ollamachat/chat/SuggestedResponseHandler.java
new file mode 100644
index 0000000..802da9b
--- /dev/null
+++ b/src/main/java/com/ollamachat/chat/SuggestedResponseHandler.java
@@ -0,0 +1,181 @@
+package com.ollamachat.chat;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.ollamachat.AIService;
+import com.ollamachat.core.ConfigManager;
+import com.ollamachat.core.Ollamachat;
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.*;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+
+public class SuggestedResponseHandler {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager;
+ private final AIService aiService;
+ private final Gson gson;
+ private final Map lastSuggestionTimes;
+
+ public SuggestedResponseHandler(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager();
+ this.aiService = new AIService();
+ this.gson = new Gson();
+ this.lastSuggestionTimes = new HashMap<>();
+ }
+
+ public boolean isSuggestionsEnabledForPlayer(Player player) {
+ Map playerSuggestionToggles = plugin.getPlayerSuggestionToggles();
+ return playerSuggestionToggles.computeIfAbsent(player.getUniqueId(), k -> true);
+ }
+
+ public void toggleSuggestionsForPlayer(Player player, boolean enabled) {
+ plugin.getPlayerSuggestionToggles().put(player.getUniqueId(), enabled);
+ }
+
+ public boolean canGenerateSuggestions(Player player) {
+ int cooldown = configManager.getSuggestedResponseCooldown();
+ if (cooldown <= 0) return true;
+ long currentTime = System.currentTimeMillis();
+ Long lastTime = lastSuggestionTimes.get(player.getUniqueId());
+ if (lastTime == null || (currentTime - lastTime) / 1000 >= cooldown) {
+ lastSuggestionTimes.put(player.getUniqueId(), currentTime);
+ return true;
+ }
+ return false;
+ }
+
+ public void sendSuggestedResponses(Player player, String originalAIName, String originalPrompt, String originalResponse) {
+ if (!configManager.isSuggestedResponsesEnabled() || !isSuggestionsEnabledForPlayer(player)) return;
+ if (!canGenerateSuggestions(player)) {
+ int cooldown = configManager.getSuggestedResponseCooldown();
+ long lastTime = lastSuggestionTimes.get(player.getUniqueId());
+ long secondsRemaining = cooldown - (System.currentTimeMillis() - lastTime) / 1000;
+ sendMessage(player, ChatColor.RED + configManager.getMessage("suggests-rate-limit",
+ Map.of("seconds", String.valueOf(secondsRemaining))));
+ return;
+ }
+
+ List suggestedResponses = new ArrayList<>();
+ if (configManager.isSuggestedResponsePresetsEnabled()) {
+ suggestedResponses.addAll(configManager.getSuggestedResponsePresets());
+ }
+
+ List suggestedModels = configManager.getSuggestedResponseModels();
+ if (!suggestedModels.isEmpty()) {
+ CompletableFuture.runAsync(() -> {
+ for (String model : suggestedModels) {
+ if (model.equalsIgnoreCase(originalAIName)) continue;
+ if (!configManager.getSuggestedResponseModelToggles().getOrDefault(model, true)) continue;
+ try {
+ String apiUrl;
+ String apiKey;
+ boolean isMessagesFormat;
+ ConfigManager.AIConfig aiConfig = configManager.getOtherAIConfigs().get(model);
+ if (aiConfig != null && configManager.getOtherAIEnabled().getOrDefault(model, false)) {
+ apiUrl = aiConfig.getApiUrl();
+ apiKey = aiConfig.getApiKey();
+ isMessagesFormat = aiConfig.isMessagesFormat();
+ } else {
+ apiUrl = configManager.getOllamaApiUrl();
+ apiKey = null;
+ isMessagesFormat = false;
+ }
+ String promptTemplate = configManager.getSuggestedResponsePrompt();
+ String context = promptTemplate
+ .replace("{prompt}", originalPrompt)
+ .replace("{response}", originalResponse)
+ .replace("{count}", String.valueOf(configManager.getSuggestedResponseCount()));
+ String responseBody = aiService.sendRequest(apiUrl, apiKey, model, context, isMessagesFormat).join();
+ String suggestedText;
+ if (isMessagesFormat) {
+ JsonObject json = gson.fromJson(responseBody, JsonObject.class);
+ suggestedText = json.getAsJsonArray("choices")
+ .get(0).getAsJsonObject()
+ .get("message").getAsJsonObject()
+ .get("content").getAsString();
+ } else {
+ JsonObject json = gson.fromJson(responseBody, JsonObject.class);
+ suggestedText = json.get("response").getAsString();
+ }
+ String[] suggestions = suggestedText.split("\n");
+ for (String suggestion : suggestions) {
+ String cleanedSuggestion = suggestion.replaceAll("^\\d+\\.\\s*", "").trim();
+ if (!cleanedSuggestion.isEmpty()) {
+ suggestedResponses.add("[" + model + "] " + cleanedSuggestion);
+ }
+ }
+ } catch (Exception e) {
+ plugin.getLogger().warning("Failed to get suggested response from " + model + ": " + e.getMessage());
+ }
+ }
+
+ if (player.isOnline() && !suggestedResponses.isEmpty()) {
+ sendMessage(player, ChatColor.GREEN + configManager.getMessage("suggested-responses-header", null));
+ for (String response : suggestedResponses) {
+ if (response.length() > configManager.getMaxResponseLength()) {
+ response = response.substring(0, configManager.getMaxResponseLength()) + "...";
+ sendMessage(player, ChatColor.YELLOW + configManager.getMessage("response-truncated", null));
+ }
+ sendClickableMessage(player,
+ configManager.getMessage("suggested-response-prefix", null) + response,
+ "/aichat " + originalAIName + " " + response.replace("\"", "\\\""),
+ configManager.getMessage("suggested-response-hover", null));
+ }
+ }
+ });
+ } else if (player.isOnline() && !suggestedResponses.isEmpty()) {
+ sendMessage(player, ChatColor.GREEN + configManager.getMessage("suggested-responses-header", null));
+ for (String response : suggestedResponses) {
+ if (response.length() > configManager.getMaxResponseLength()) {
+ response = response.substring(0, configManager.getMaxResponseLength()) + "...";
+ sendMessage(player, ChatColor.YELLOW + configManager.getMessage("response-truncated", null));
+ }
+ sendClickableMessage(player,
+ configManager.getMessage("suggested-response-prefix", null) + response,
+ "/aichat " + originalAIName + " " + response.replace("\"", "\\\""),
+ configManager.getMessage("suggested-response-hover", null));
+ }
+ }
+ }
+
+ private void sendMessage(Player player, String message) {
+ if (player.isOnline()) {
+ player.sendMessage(message);
+ }
+ }
+
+ private void sendClickableMessage(Player player, String text, String command, String hoverText) {
+ TextComponent message = new TextComponent(text);
+ message.setColor(ChatColor.WHITE);
+ message.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, command));
+ message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
+ new ComponentBuilder(hoverText).color(ChatColor.YELLOW).create()));
+ player.spigot().sendMessage(message);
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/ollamachat/command/AIChatCommand.java b/src/main/java/com/ollamachat/command/AIChatCommand.java
new file mode 100644
index 0000000..1c3f8ca
--- /dev/null
+++ b/src/main/java/com/ollamachat/command/AIChatCommand.java
@@ -0,0 +1,48 @@
+package com.ollamachat.command;
+
+import com.ollamachat.core.Ollamachat;
+import com.ollamachat.core.ConfigManager;
+import com.ollamachat.chat.ChatTriggerHandler;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.Map;
+
+public class AIChatCommand implements CommandExecutor {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager;
+ private final ChatTriggerHandler chatTriggerHandler;
+
+ public AIChatCommand(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager();
+ this.chatTriggerHandler = new ChatTriggerHandler(plugin);
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (args.length < 2) {
+ sender.sendMessage(configManager.getMessage("usage-aichat", null));
+ return true;
+ }
+
+ String aiName = args[0];
+ String prompt = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
+
+ if (aiName.equalsIgnoreCase("ollama") || configManager.getOtherAIConfigs().containsKey(aiName)) {
+ if (sender instanceof Player) {
+ chatTriggerHandler.processAIQuery((Player) sender, aiName, prompt);
+ } else {
+ sender.sendMessage(configManager.getMessage("player-only", null));
+ }
+ } else {
+ sender.sendMessage(configManager.getMessage("invalid-ai-name", Map.of("ai-list", String.join(", ", configManager.getOtherAIConfigs().keySet()))));
+ }
+ return true;
+ }
+}
+
+
diff --git a/src/main/java/com/ollamachat/command/OllamaChatCommand.java b/src/main/java/com/ollamachat/command/OllamaChatCommand.java
new file mode 100644
index 0000000..d868496
--- /dev/null
+++ b/src/main/java/com/ollamachat/command/OllamaChatCommand.java
@@ -0,0 +1,265 @@
+package com.ollamachat.command;
+
+import com.ollamachat.core.Ollamachat;
+import com.ollamachat.core.ConfigManager;
+import com.ollamachat.chat.ChatTriggerHandler;
+import org.bukkit.ChatColor;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandExecutor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OllamaChatCommand implements CommandExecutor {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager;
+ private final ChatTriggerHandler chatTriggerHandler;
+
+ public OllamaChatCommand(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager();
+ this.chatTriggerHandler = new ChatTriggerHandler(plugin);
+ }
+
+ @Override
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+ if (args.length == 0) {
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("usage-ollamachat", null));
+ return true;
+ }
+
+ if (args[0].equalsIgnoreCase("reload")) {
+ if (!sender.hasPermission("ollamachat.reload")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ configManager.reloadConfigValues();
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("reload-success", null));
+ return true;
+ } else if (args[0].equalsIgnoreCase("toggle") && args.length > 1) {
+ if (!sender.hasPermission("ollamachat.toggle")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String aiName = args[1];
+ if (aiName.equalsIgnoreCase("ollama")) {
+ configManager.setOllamaEnabled(!configManager.isOllamaEnabled());
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage(configManager.isOllamaEnabled() ? "ollama-enabled" : "ollama-disabled", null));
+ } else if (configManager.getOtherAIConfigs().containsKey(aiName)) {
+ boolean newState = !configManager.getOtherAIEnabled().getOrDefault(aiName, false);
+ configManager.getOtherAIEnabled().put(aiName, newState);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage(newState ? "toggle-enabled" : "toggle-disabled", Map.of("ai-name", aiName)));
+ } else {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("invalid-ai-name", Map.of("ai-list", String.join(", ", configManager.getOtherAIConfigs().keySet()))));
+ }
+ return true;
+ } else if (args[0].equalsIgnoreCase("prompt") && args.length > 1) {
+ String subCommand = args[1].toLowerCase();
+ if (subCommand.equals("set") && args.length > 3) {
+ if (!sender.hasPermission("ollamachat.prompt.set")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String promptName = args[2];
+ String promptContent = String.join(" ", Arrays.copyOfRange(args, 3, args.length));
+ plugin.getConfig().set("prompts." + promptName, promptContent);
+ plugin.saveConfig();
+ configManager.getPrompts().put(promptName, promptContent);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-set", Map.of("name", promptName)));
+ return true;
+ } else if (subCommand.equals("delete") && args.length == 3) {
+ if (!sender.hasPermission("ollamachat.prompt.delete")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String promptName = args[2];
+ if (configManager.getPrompts().containsKey(promptName)) {
+ plugin.getConfig().set("prompts." + promptName, null);
+ if (configManager.getDefaultPrompt().equals(promptName)) {
+ plugin.getConfig().set("default-prompt", "");
+ configManager.setDefaultPrompt("");
+ }
+ plugin.saveConfig();
+ configManager.getPrompts().remove(promptName);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-deleted", Map.of("name", promptName)));
+ } else {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("prompt-not-found", Map.of("name", promptName)));
+ }
+ return true;
+ } else if (subCommand.equals("list")) {
+ if (!sender.hasPermission("ollamachat.prompt.list")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ if (configManager.getPrompts().isEmpty()) {
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-list-empty", null));
+ } else {
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-list", Map.of("prompts", String.join(", ", configManager.getPrompts().keySet()))));
+ }
+ if (!configManager.getDefaultPrompt().isEmpty() && configManager.getPrompts().containsKey(configManager.getDefaultPrompt())) {
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-default", Map.of("name", configManager.getDefaultPrompt())));
+ } else if (!configManager.getDefaultPrompt().isEmpty()) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("prompt-default-invalid", Map.of("name", configManager.getDefaultPrompt())));
+ }
+ return true;
+ } else if (subCommand.equals("select") && args.length == 3) {
+ if (!sender.hasPermission("ollamachat.prompt.select")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String promptName = args[2];
+ if (configManager.getPrompts().containsKey(promptName)) {
+ plugin.getConfig().set("default-prompt", promptName);
+ plugin.saveConfig();
+ configManager.setDefaultPrompt(promptName);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-selected", Map.of("name", promptName)));
+ } else {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("prompt-not-found", Map.of("name", promptName)));
+ }
+ return true;
+ } else if (subCommand.equals("clear")) {
+ if (!sender.hasPermission("ollamachat.prompt.select")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ plugin.getConfig().set("default-prompt", "");
+ plugin.saveConfig();
+ configManager.setDefaultPrompt("");
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("prompt-cleared", null));
+ return true;
+ } else {
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("prompt-usage", null));
+ return true;
+ }
+ } else if (args[0].equalsIgnoreCase("conversation") && args.length > 1) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("player-only", null));
+ return true;
+ }
+ Player player = (Player) sender;
+ String subCommand = args[1].toLowerCase();
+ String aiName = args.length > 2 ? args[2] : "ollama";
+ if (!aiName.equals("ollama") && !configManager.getOtherAIConfigs().containsKey(aiName)) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("invalid-ai-name", Map.of("ai-list", String.join(", ", configManager.getOtherAIConfigs().keySet()))));
+ return true;
+ }
+ if (subCommand.equals("new") && args.length == 4) {
+ if (!sender.hasPermission("ollamachat.conversation.new")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String convName = args[3];
+ String convId = plugin.getChatHistoryManager().createConversation(player.getUniqueId(), aiName, convName);
+ configManager.getSelectedConversations().computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(aiName, convName);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-created", Map.of("name", convName, "ai-name", aiName)));
+ return true;
+ } else if (subCommand.equals("select") && args.length == 4) {
+ if (!sender.hasPermission("ollamachat.conversation.select")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String convName = args[3];
+ if (plugin.getChatHistoryManager().conversationExistsByName(player.getUniqueId(), aiName, convName)) {
+ configManager.getSelectedConversations().computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(aiName, convName);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-selected", Map.of("name", convName, "ai-name", aiName)));
+ } else {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("conversation-not-found", Map.of("name", convName)));
+ }
+ return true;
+ } else if (subCommand.equals("delete") && args.length == 4) {
+ if (!sender.hasPermission("ollamachat.conversation.delete")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String convName = args[3];
+ String convId = plugin.getChatHistoryManager().getConversationId(player.getUniqueId(), aiName, convName);
+ if (convId != null && plugin.getChatHistoryManager().deleteConversation(player.getUniqueId(), aiName, convId)) {
+ Map convMap = configManager.getSelectedConversations().get(player.getUniqueId());
+ if (convMap != null && convName.equals(convMap.get(aiName))) {
+ convMap.remove(aiName);
+ }
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-deleted", Map.of("name", convName, "ai-name", aiName)));
+ } else {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("conversation-not-found", Map.of("name", convName)));
+ }
+ return true;
+ } else if (subCommand.equals("list") && args.length == 3) {
+ if (!sender.hasPermission("ollamachat.conversation.list")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ Map conversations = plugin.getChatHistoryManager().listConversations(player.getUniqueId(), aiName);
+ if (conversations.isEmpty()) {
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-list-empty", Map.of("ai-name", aiName)));
+ } else {
+ String convList = String.join(", ", conversations.values());
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-list", Map.of("conversations", convList, "ai-name", aiName)));
+ }
+ String selectedConv = configManager.getSelectedConversations().computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).get(aiName);
+ if (selectedConv != null && conversations.containsValue(selectedConv)) {
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("conversation-default", Map.of("name", selectedConv, "ai-name", aiName)));
+ } else if (selectedConv != null) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("conversation-default-invalid", Map.of("name", selectedConv, "ai-name", aiName)));
+ }
+ return true;
+ } else {
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("conversation-usage", null));
+ return true;
+ }
+ } else if (args[0].equalsIgnoreCase("suggests") && args.length == 2) {
+ if (!(sender instanceof Player)) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("player-only", null));
+ return true;
+ }
+ if (!sender.hasPermission("ollamachat.suggests.toggle")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ Player player = (Player) sender;
+ String subCommand = args[1].toLowerCase();
+ if (subCommand.equals("on")) {
+ plugin.getSuggestedResponseHandler().toggleSuggestionsForPlayer(player, true);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("suggests-enabled", Map.of("player", player.getName())));
+ return true;
+ } else if (subCommand.equals("off")) {
+ plugin.getSuggestedResponseHandler().toggleSuggestionsForPlayer(player, false);
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("suggests-disabled", Map.of("player", player.getName())));
+ return true;
+ } else {
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("usage-ollamachat", null));
+ return true;
+ }
+ } else if (args[0].equalsIgnoreCase("suggests-presets") && args.length == 2) {
+ if (!sender.hasPermission("ollamachat.suggests-presets.toggle")) {
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("no-permission", null));
+ return true;
+ }
+ String subCommand = args[1].toLowerCase();
+ if (subCommand.equals("on")) {
+ configManager.setSuggestedResponsePresetsEnabled(true);
+ sender.sendMessage(ChatColor.GREEN + configManager.getMessage("suggests-presets-enabled", null));
+ return true;
+ } else if (subCommand.equals("off")) {
+ configManager.setSuggestedResponsePresetsEnabled(false);
+ sender.sendMessage(ChatColor.RED + configManager.getMessage("suggests-presets-disabled", null));
+ return true;
+ } else {
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("usage-ollamachat", null));
+ return true;
+ }
+ }
+ sender.sendMessage(ChatColor.YELLOW + configManager.getMessage("usage-ollamachat", null));
+ return true;
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/ollamachat/command/OllamaChatTabCompleter.java b/src/main/java/com/ollamachat/command/OllamaChatTabCompleter.java
new file mode 100644
index 0000000..7f8acd4
--- /dev/null
+++ b/src/main/java/com/ollamachat/command/OllamaChatTabCompleter.java
@@ -0,0 +1,143 @@
+package com.ollamachat.command;
+
+import com.ollamachat.core.Ollamachat;
+import com.ollamachat.core.ConfigManager;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabCompleter;
+import org.bukkit.entity.Player;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class OllamaChatTabCompleter implements TabCompleter {
+ private final Ollamachat plugin;
+ private final ConfigManager configManager;
+
+ public OllamaChatTabCompleter(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.configManager = plugin.getConfigManager();
+ }
+
+ @Override
+ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+ List completions = new ArrayList<>();
+
+ if (command.getName().equalsIgnoreCase("ollamachat")) {
+ if (args.length == 1) {
+ List subCommands = new ArrayList<>();
+ if (sender.hasPermission("ollamachat.reload")) {
+ subCommands.add("reload");
+ }
+ if (sender.hasPermission("ollamachat.toggle")) {
+ subCommands.add("toggle");
+ }
+ if (sender.hasPermission("ollamachat.prompt.set") ||
+ sender.hasPermission("ollamachat.prompt.delete") ||
+ sender.hasPermission("ollamachat.prompt.list") ||
+ sender.hasPermission("ollamachat.prompt.select")) {
+ subCommands.add("prompt");
+ }
+ if (sender instanceof Player && (
+ sender.hasPermission("ollamachat.conversation.new") ||
+ sender.hasPermission("ollamachat.conversation.select") ||
+ sender.hasPermission("ollamachat.conversation.delete") ||
+ sender.hasPermission("ollamachat.conversation.list"))) {
+ subCommands.add("conversation");
+ }
+ if (sender instanceof Player && sender.hasPermission("ollamachat.suggests.toggle")) {
+ subCommands.add("suggests");
+ }
+ if (sender.hasPermission("ollamachat.suggests-presets.toggle")) {
+ subCommands.add("suggests-presets");
+ }
+ return filterCompletions(subCommands, args[0]);
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("toggle") && sender.hasPermission("ollamachat.toggle")) {
+ List aiNames = new ArrayList<>();
+ aiNames.add("ollama");
+ aiNames.addAll(configManager.getOtherAIConfigs().keySet());
+ return filterCompletions(aiNames, args[1]);
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("prompt") && (
+ sender.hasPermission("ollamachat.prompt.set") ||
+ sender.hasPermission("ollamachat.prompt.delete") ||
+ sender.hasPermission("ollamachat.prompt.list") ||
+ sender.hasPermission("ollamachat.prompt.select"))) {
+ List promptSubCommands = new ArrayList<>();
+ if (sender.hasPermission("ollamachat.prompt.set")) {
+ promptSubCommands.add("set");
+ }
+ if (sender.hasPermission("ollamachat.prompt.delete")) {
+ promptSubCommands.add("delete");
+ }
+ if (sender.hasPermission("ollamachat.prompt.list")) {
+ promptSubCommands.add("list");
+ }
+ if (sender.hasPermission("ollamachat.prompt.select")) {
+ promptSubCommands.add("select");
+ promptSubCommands.add("clear");
+ }
+ return filterCompletions(promptSubCommands, args[1]);
+ } else if (args.length == 3 && args[0].equalsIgnoreCase("prompt") && (
+ args[1].equalsIgnoreCase("delete") || args[1].equalsIgnoreCase("select")) &&
+ (sender.hasPermission("ollamachat.prompt.delete") || sender.hasPermission("ollamachat.prompt.select"))) {
+ return filterCompletions(new ArrayList<>(configManager.getPrompts().keySet()), args[2]);
+ } else if (args.length == 2 && args[0].equalsIgnoreCase("conversation") && sender instanceof Player &&
+ (sender.hasPermission("ollamachat.conversation.new") ||
+ sender.hasPermission("ollamachat.conversation.select") ||
+ sender.hasPermission("ollamachat.conversation.delete") ||
+ sender.hasPermission("ollamachat.conversation.list"))) {
+ List convSubCommands = new ArrayList<>();
+ if (sender.hasPermission("ollamachat.conversation.new")) {
+ convSubCommands.add("new");
+ }
+ if (sender.hasPermission("ollamachat.conversation.select")) {
+ convSubCommands.add("select");
+ }
+ if (sender.hasPermission("ollamachat.conversation.delete")) {
+ convSubCommands.add("delete");
+ }
+ if (sender.hasPermission("ollamachat.conversation.list")) {
+ convSubCommands.add("list");
+ }
+ return filterCompletions(convSubCommands, args[1]);
+ } else if (args.length == 3 && args[0].equalsIgnoreCase("conversation") && sender instanceof Player) {
+ List aiNames = new ArrayList<>();
+ aiNames.add("ollama");
+ aiNames.addAll(configManager.getOtherAIConfigs().keySet());
+ return filterCompletions(aiNames, args[2]);
+ } else if (args.length == 4 && args[0].equalsIgnoreCase("conversation") &&
+ (args[1].equalsIgnoreCase("select") || args[1].equalsIgnoreCase("delete")) && sender instanceof Player &&
+ (sender.hasPermission("ollamachat.conversation.select") || sender.hasPermission("ollamachat.conversation.delete"))) {
+ String aiName = args[2];
+ Map conversations = plugin.getChatHistoryManager().listConversations(((Player) sender).getUniqueId(), aiName);
+ return filterCompletions(new ArrayList<>(conversations.values()), args[3]);
+ } else if (args.length == 2 && (args[0].equalsIgnoreCase("suggests") || args[0].equalsIgnoreCase("suggests-presets")) && sender instanceof Player &&
+ (sender.hasPermission("ollamachat.suggests.toggle") || sender.hasPermission("ollamachat.suggests-presets.toggle"))) {
+ return filterCompletions(Arrays.asList("on", "off"), args[1]);
+ }
+ } else if (command.getName().equalsIgnoreCase("aichat") && sender.hasPermission("ollamachat.use")) {
+ if (args.length == 1) {
+ List aiNames = new ArrayList<>();
+ aiNames.add("ollama");
+ aiNames.addAll(configManager.getOtherAIConfigs().keySet());
+ return filterCompletions(aiNames, args[0]);
+ }
+ }
+
+ return completions;
+ }
+
+ private List filterCompletions(List options, String input) {
+ return options.stream()
+ .filter(option -> option.toLowerCase().startsWith(input.toLowerCase()))
+ .collect(Collectors.toList());
+ }
+}
+
+
+
+
+
diff --git a/src/main/java/com/ollamachat/core/ConfigManager.java b/src/main/java/com/ollamachat/core/ConfigManager.java
new file mode 100644
index 0000000..0080af1
--- /dev/null
+++ b/src/main/java/com/ollamachat/core/ConfigManager.java
@@ -0,0 +1,325 @@
+package com.ollamachat.core;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.*;
+
+public class ConfigManager {
+ private final Ollamachat plugin;
+ private final Gson gson;
+ private JsonObject langConfig;
+ private String ollamaApiUrl;
+ private String ollamaModel;
+ private List triggerPrefixes;
+ private int maxResponseLength;
+ private Map otherAIConfigs;
+ private boolean ollamaEnabled;
+ private Map otherAIEnabled;
+ private boolean streamingEnabled;
+ private String defaultPrompt;
+ private Map prompts;
+ private Map> selectedConversations;
+ private int maxHistory;
+ private List suggestedResponseModels;
+ private boolean suggestedResponsesEnabled;
+ private int suggestedResponseCount;
+ private String suggestedResponsePrompt;
+ private List suggestedResponsePresets;
+ private Map suggestedResponseModelToggles;
+ private int suggestedResponseCooldown;
+ private boolean suggestedResponsePresetsEnabled;
+
+ public ConfigManager(Ollamachat plugin) {
+ this.plugin = plugin;
+ this.gson = new Gson();
+ this.selectedConversations = new HashMap<>();
+ this.suggestedResponseModelToggles = new HashMap<>();
+ }
+
+ public void initialize() {
+ plugin.saveDefaultConfig();
+ reloadConfigValues();
+ loadLanguageFile(plugin.getConfig().getString("language", "en_us"));
+ }
+
+ private void updateConfig() {
+ FileConfiguration config = plugin.getConfig();
+
+ if (!config.contains("ollama-enabled")) config.set("ollama-enabled", true);
+ if (!config.contains("language")) config.set("language", "en_us");
+ if (!config.contains("other-ai-configs")) config.createSection("other-ai-configs");
+ if (!config.contains("max-history")) config.set("max-history", 5);
+ if (!config.contains("stream-settings")) config.set("stream-settings.enabled", true);
+ if (!config.contains("prompts")) config.createSection("prompts");
+ if (!config.contains("default-prompt")) config.set("default-prompt", "");
+ if (!config.contains("trigger-prefixes")) {
+ config.set("trigger-prefixes", Arrays.asList("@bot", "@ai"));
+ }
+ if (!config.contains("suggested-response-models")) {
+ config.set("suggested-response-models", Arrays.asList("llama3"));
+ }
+ if (!config.contains("suggested-responses-enabled")) config.set("suggested-responses-enabled", true);
+ if (!config.contains("suggested-response-count")) config.set("suggested-response-count", 3);
+ if (!config.contains("suggested-response-prompt")) {
+ config.set("suggested-response-prompt", "Conversation:\nUser: {prompt}\nAI: {response}\n\nBased on the above conversation, suggest {count} natural follow-up responses the user might want to say. They should be conversational in tone rather than questions. List them as:\n1. Response 1\n2. Response 2\n3. Response 3");
+ }
+ if (!config.contains("suggested-response-presets")) {
+ config.set("suggested-response-presets", Arrays.asList("I see what you mean.", "That's interesting!", "Tell me more about that."));
+ }
+ if (!config.contains("suggested-response-model-toggles")) {
+ config.createSection("suggested-response-model-toggles");
+ for (String model : config.getStringList("suggested-response-models")) {
+ if (!config.contains("suggested-response-model-toggles." + model)) {
+ config.set("suggested-response-model-toggles." + model, true);
+ }
+ }
+ }
+ if (!config.contains("suggested-response-cooldown")) config.set("suggested-response-cooldown", 10);
+ if (!config.contains("suggested-response-presets-enabled")) config.set("suggested-response-presets-enabled", true);
+ if (!config.contains("database")) {
+ config.set("database.type", "sqlite");
+ config.set("database.mysql.host", "localhost");
+ config.set("database.mysql.port", 3306);
+ config.set("database.mysql.database", "ollamachat");
+ config.set("database.mysql.username", "root");
+ config.set("database.mysql.password", "");
+ }
+
+ plugin.saveConfig();
+ }
+
+ public void reloadConfigValues() {
+ File configFile = new File(plugin.getDataFolder(), "config.yml");
+ if (!configFile.exists()) {
+ plugin.saveDefaultConfig();
+ } else {
+ try {
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
+ if (!config.contains("ollama-api-url") || !config.contains("model")) {
+ plugin.getLogger().warning(getMessage("config-invalid", null));
+ configFile.delete();
+ plugin.saveDefaultConfig();
+ }
+ } catch (Exception e) {
+ plugin.getLogger().severe(getMessage("config-load-failed", Map.of("error", e.getMessage())));
+ configFile.delete();
+ plugin.saveDefaultConfig();
+ }
+ }
+
+ plugin.reloadConfig();
+ updateConfig();
+
+ FileConfiguration config = plugin.getConfig();
+ ollamaApiUrl = config.getString("ollama-api-url", "http://localhost:11434/api/generate");
+ ollamaModel = config.getString("model", "llama3");
+ triggerPrefixes = config.getStringList("trigger-prefixes");
+ maxResponseLength = config.getInt("max-response-length", 500);
+ ollamaEnabled = config.getBoolean("ollama-enabled", true);
+ maxHistory = config.getInt("max-history", 5);
+ streamingEnabled = config.getBoolean("stream-settings.enabled", true);
+ defaultPrompt = config.getString("default-prompt", "");
+ suggestedResponseModels = config.getStringList("suggested-response-models");
+ suggestedResponsesEnabled = config.getBoolean("suggested-responses-enabled", true);
+ suggestedResponseCount = config.getInt("suggested-response-count", 3);
+ suggestedResponsePrompt = config.getString("suggested-response-prompt", "Conversation:\nUser: {prompt}\nAI: {response}\n\nBased on the above conversation, suggest {count} natural follow-up responses the user might want to say. They should be conversational in tone rather than questions. List them as:\n1. Response 1\n2. Response 2\n3. Response 3");
+ suggestedResponsePresets = config.getStringList("suggested-response-presets");
+ suggestedResponseCooldown = config.getInt("suggested-response-cooldown", 10);
+ suggestedResponsePresetsEnabled = config.getBoolean("suggested-response-presets-enabled", true);
+
+ prompts = new HashMap<>();
+ if (config.contains("prompts") && config.getConfigurationSection("prompts") != null) {
+ for (String promptName : config.getConfigurationSection("prompts").getKeys(false)) {
+ String promptContent = config.getString("prompts." + promptName);
+ prompts.put(promptName, promptContent);
+ }
+ }
+
+ otherAIConfigs = new HashMap<>();
+ otherAIEnabled = new HashMap<>();
+ if (config.contains("other-ai-configs") && config.getConfigurationSection("other-ai-configs") != null) {
+ for (String aiName : config.getConfigurationSection("other-ai-configs").getKeys(false)) {
+ String apiUrl = config.getString("other-ai-configs." + aiName + ".api-url");
+ String apiKey = config.getString("other-ai-configs." + aiName + ".api-key");
+ String model = config.getString("other-ai-configs." + aiName + ".model");
+ boolean enabled = config.getBoolean("other-ai-configs." + aiName + ".enabled", true);
+ boolean isMessagesFormat = config.getBoolean("other-ai-configs." + aiName + ".messages-format", false);
+ otherAIConfigs.put(aiName, new AIConfig(apiUrl, apiKey, model, isMessagesFormat));
+ otherAIEnabled.put(aiName, enabled);
+ }
+ }
+
+ suggestedResponseModelToggles = new HashMap<>();
+ if (config.contains("suggested-response-model-toggles") && config.getConfigurationSection("suggested-response-model-toggles") != null) {
+ for (String model : config.getConfigurationSection("suggested-response-model-toggles").getKeys(false)) {
+ suggestedResponseModelToggles.put(model, config.getBoolean("suggested-response-model-toggles." + model, true));
+ }
+ }
+ }
+
+ private void loadLanguageFile(String language) {
+ File langFolder = new File(plugin.getDataFolder(), "lang");
+ if (!langFolder.exists()) {
+ langFolder.mkdirs();
+ }
+
+ File langFile = new File(langFolder, language + ".json");
+ if (!langFile.exists()) {
+ plugin.saveResource("lang/" + language + ".json", false);
+ }
+
+ try (FileReader reader = new FileReader(langFile)) {
+ langConfig = gson.fromJson(reader, JsonObject.class);
+ if (langConfig == null) {
+ langConfig = new JsonObject();
+ plugin.getLogger().warning("Language file is empty or invalid: " + langFile.getName());
+ }
+ } catch (IOException e) {
+ plugin.getLogger().severe("Failed to load language file: " + langFile.getName() + " - " + e.getMessage());
+ langConfig = new JsonObject();
+ }
+ }
+
+ public String getMessage(String key, Map placeholders) {
+ String message = langConfig != null && langConfig.has(key) ? langConfig.get(key).getAsString() : "§c[OllamaChat] Missing language key: " + key;
+ if (placeholders != null) {
+ for (Map.Entry entry : placeholders.entrySet()) {
+ message = message.replace("{" + entry.getKey() + "}", entry.getValue());
+ }
+ }
+ return message;
+ }
+
+ public boolean isSuggestedResponsePresetsEnabled() {
+ return suggestedResponsePresetsEnabled;
+ }
+
+ public void setSuggestedResponsePresetsEnabled(boolean enabled) {
+ this.suggestedResponsePresetsEnabled = enabled;
+ plugin.getConfig().set("suggested-response-presets-enabled", enabled);
+ plugin.saveConfig();
+ }
+
+ // Getters
+ public String getOllamaApiUrl() {
+ return ollamaApiUrl;
+ }
+
+ public String getOllamaModel() {
+ return ollamaModel;
+ }
+
+ public List getTriggerPrefixes() {
+ return triggerPrefixes;
+ }
+
+ public int getMaxResponseLength() {
+ return maxResponseLength;
+ }
+
+ public Map getOtherAIConfigs() {
+ return otherAIConfigs;
+ }
+
+ public boolean isOllamaEnabled() {
+ return ollamaEnabled;
+ }
+
+ public void setOllamaEnabled(boolean enabled) {
+ this.ollamaEnabled = enabled;
+ }
+
+ public Map getOtherAIEnabled() {
+ return otherAIEnabled;
+ }
+
+ public boolean isStreamingEnabled() {
+ return streamingEnabled;
+ }
+
+ public String getDefaultPrompt() {
+ return defaultPrompt;
+ }
+
+ public void setDefaultPrompt(String prompt) {
+ this.defaultPrompt = prompt;
+ }
+
+ public Map getPrompts() {
+ return prompts;
+ }
+
+ public Map> getSelectedConversations() {
+ return selectedConversations;
+ }
+
+ public int getMaxHistory() {
+ return maxHistory;
+ }
+
+ public List getSuggestedResponseModels() {
+ return suggestedResponseModels;
+ }
+
+ public boolean isSuggestedResponsesEnabled() {
+ return suggestedResponsesEnabled;
+ }
+
+ public int getSuggestedResponseCount() {
+ return suggestedResponseCount;
+ }
+
+ public String getSuggestedResponsePrompt() {
+ return suggestedResponsePrompt;
+ }
+
+ public List getSuggestedResponsePresets() {
+ return suggestedResponsePresets;
+ }
+
+ public Map getSuggestedResponseModelToggles() {
+ return suggestedResponseModelToggles;
+ }
+
+ public int getSuggestedResponseCooldown() {
+ return suggestedResponseCooldown;
+ }
+
+ public static class AIConfig {
+ private final String apiUrl;
+ private final String apiKey;
+ private final String model;
+ private final boolean isMessagesFormat;
+
+ public AIConfig(String apiUrl, String apiKey, String model, boolean isMessagesFormat) {
+ this.apiUrl = apiUrl;
+ this.apiKey = apiKey;
+ this.model = model;
+ this.isMessagesFormat = isMessagesFormat;
+ }
+
+ public String getApiUrl() {
+ return apiUrl;
+ }
+
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ public String getModel() {
+ return model;
+ }
+
+ public boolean isMessagesFormat() {
+ return isMessagesFormat;
+ }
+ }
+}
+
+
diff --git a/src/main/java/com/ollamachat/core/Ollamachat.java b/src/main/java/com/ollamachat/core/Ollamachat.java
new file mode 100644
index 0000000..9cc4f64
--- /dev/null
+++ b/src/main/java/com/ollamachat/core/Ollamachat.java
@@ -0,0 +1,112 @@
+
+package com.ollamachat.core;
+
+import com.ollamachat.ChatHistoryManager;
+import com.ollamachat.DatabaseManager;
+import com.ollamachat.DependencyLoader;
+import com.ollamachat.ProgressManager;
+import com.ollamachat.chat.ChatTriggerHandler;
+import com.ollamachat.chat.SuggestedResponseHandler;
+import com.ollamachat.command.AIChatCommand;
+import com.ollamachat.command.OllamaChatCommand;
+import com.ollamachat.command.OllamaChatTabCompleter;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class Ollamachat extends JavaPlugin {
+ private ConfigManager configManager;
+ private DatabaseManager databaseManager;
+ private ChatHistoryManager chatHistoryManager;
+ private ProgressManager progressManager;
+ private SuggestedResponseHandler suggestedResponseHandler;
+ private Map playerSuggestionToggles;
+
+ @Override
+ public void onEnable() {
+ // Load dependencies first
+ DependencyLoader dependencyLoader = new DependencyLoader(this);
+ ClassLoader dependencyClassLoader;
+ try {
+ dependencyLoader.loadDependencies();
+ dependencyClassLoader = dependencyLoader.getClass().getClassLoader();
+ } catch (Exception e) {
+ getLogger().severe("Failed to load dependencies: " + e.getMessage());
+ getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ configManager = new ConfigManager(this);
+ configManager.initialize();
+
+ try {
+ databaseManager = new DatabaseManager(this, dependencyClassLoader);
+ } catch (Exception e) {
+ getLogger().severe("Failed to initialize DatabaseManager: " + e.getMessage());
+ getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ int maxHistory = configManager.getMaxHistory();
+ chatHistoryManager = new ChatHistoryManager(databaseManager, maxHistory);
+ progressManager = new ProgressManager(this);
+ suggestedResponseHandler = new SuggestedResponseHandler(this);
+ playerSuggestionToggles = new HashMap<>();
+
+ // Register events
+ getServer().getPluginManager().registerEvents(new ChatTriggerHandler(this), this);
+
+ // Register commands
+ getCommand("ollamachat").setExecutor(new OllamaChatCommand(this));
+ getCommand("ollamachat").setTabCompleter(new OllamaChatTabCompleter(this));
+ getCommand("aichat").setExecutor(new AIChatCommand(this));
+ getCommand("aichat").setTabCompleter(new OllamaChatTabCompleter(this));
+ }
+
+ @Override
+ public void onDisable() {
+ if (databaseManager != null) {
+ databaseManager.close();
+ } else {
+ getLogger().warning("DatabaseManager was null, skipping close.");
+ }
+ if (progressManager != null) {
+ getServer().getOnlinePlayers().forEach(progressManager::cleanup);
+ }
+ }
+
+ // Getter methods
+ public ConfigManager getConfigManager() {
+ return configManager;
+ }
+
+ public DatabaseManager getDatabaseManager() {
+ return databaseManager;
+ }
+
+ public ChatHistoryManager getChatHistoryManager() {
+ return chatHistoryManager;
+ }
+
+ public ProgressManager getProgressManager() {
+ return progressManager;
+ }
+
+ public SuggestedResponseHandler getSuggestedResponseHandler() {
+ return suggestedResponseHandler;
+ }
+
+ public Map getPlayerSuggestionToggles() {
+ return playerSuggestionToggles;
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index c96b93b..0f153ff 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -3,9 +3,14 @@ ollama-api-url: "http://localhost:11434/api/generate"
model: "llama3"
ollama-enabled: true
+# Streaming settings
+stream-settings:
+ enabled: true # Whether to enable streaming
+
# Chat
-trigger-prefix: "@bot "
-response-prefix: "§b[AI] §r"
+trigger-prefixes:
+ - "@bot"
+ - "@ai"
# Length
max-response-length: 500
@@ -14,7 +19,50 @@ max-response-length: 500
max-history: 5
# Language Settings
-language: "en"
+language: "en_us"
+
+# Progress Display Settings
+progress-display:
+ enabled: true # Whether to enable progress display
+ type: "bossbar" # Display type (bossbar or actionbar)
+ color: "BLUE" # BossBar color (BLUE, GREEN, RED, etc.)
+ style: "SOLID" # BossBar style (SOLID, SEGMENTED_6, etc.)
+ update-interval: 1 # Progress update frequency (in seconds)
+
+# Suggested Response
+suggested-responses-enabled: false
+suggested-response-models:
+ - "llama3"
+suggested-response-count: 3
+suggested-response-prompt: "Conversation:\nUser: {prompt}\nAI: {response}\n\nBased on the above conversation, suggest {count} natural follow-up responses the user might want to say. They should be conversational in tone rather than questions. List them as:\n1. Response 1\n2. Response 2\n3. Response 3"
+suggested-response-presets:
+ - "I see what you mean."
+ - "That's interesting!"
+ - "Tell me more about that."
+suggested-response-model-toggles:
+ - llama3
+suggested-response-cooldown: 10
+suggested-response-presets-enabled: false
+
+# Database (for mysql, set database.type: mysql.)
+database:
+ type: sqlite
+ mysql:
+ host: localhost
+ port: 3306
+ database: ollamachat
+ username: root
+ password: ""
+
+
+# Default prompt to prepend to user inputs (empty for none)
+default-prompt: ""
+
+# Custom prompts
+prompts:
+# Example:
+# friendly: "You are a friendly assistant who responds in a cheerful tone."
+# formal: "You are a professional assistant who responds formally."
# Other AI Configurations
other-ai-configs:
@@ -22,5 +70,6 @@ other-ai-configs:
api-url: "https://api.openai.com/v1/chat/completions"
api-key: "your-openai-api-key"
model: "gpt-4"
- enabled: true
+ enabled: false
+ messages-format: true
diff --git a/src/main/resources/lang/en.lang b/src/main/resources/lang/en.lang
deleted file mode 100644
index d1a1179..0000000
--- a/src/main/resources/lang/en.lang
+++ /dev/null
@@ -1,18 +0,0 @@
-# General messages
-reload-success: "§aConfiguration reloaded."
-toggle-enabled: "§a{ai-name} is now enabled."
-toggle-disabled: "§a{ai-name} is now disabled."
-invalid-ai-name: "§cInvalid AI name. Available AIs: {ai-list}."
-usage-aichat: "§cUsage: /aichat "
-usage-ollamachat: "§cUsage: /ollamachat >"
-player-only: "§cThis command can only be used by players."
-ollama-enabled: "§aOllama is now enabled."
-ollama-disabled: "§aOllama is now disabled."
-
-# Chat interaction
-response-prefix: "§b[AI] §r"
-error-prefix: "§c[Error] §r"
-
-# Log messages
-config-invalid: "§cConfig file is invalid or incomplete. Regenerating default config..."
-config-load-failed: "§cFailed to load config file: {error}. Regenerating default config..."
\ No newline at end of file
diff --git a/src/main/resources/lang/en_us.json b/src/main/resources/lang/en_us.json
new file mode 100644
index 0000000..48640ed
--- /dev/null
+++ b/src/main/resources/lang/en_us.json
@@ -0,0 +1,51 @@
+{
+ "usage-ollamachat": "§e[OllamaChat] Usage:\n- /ollamachat reload\n- /ollamachat toggle \n- /ollamachat prompt [args]\n- /ollamachat conversation [args]",
+ "usage-aichat": "§e[OllamaChat] Usage:\n- /aichat ",
+ "reload-success": "§a[OllamaChat] Configuration reloaded successfully!",
+ "ollama-enabled": "§a[OllamaChat] Ollama integration enabled!",
+ "ollama-disabled": "§c[OllamaChat] Ollama integration disabled!",
+ "toggle-enabled": "§a[OllamaChat] {ai-name} integration enabled!",
+ "toggle-disabled": "§c[OllamaChat] {ai-name} integration disabled!",
+ "invalid-ai-name": "§c[OllamaChat] Invalid AI name! Available AIs: {ai-list}",
+ "player-only": "§c[OllamaChat] This command can only be used by players!",
+ "config-invalid": "§c[OllamaChat] Configuration file is invalid, resetting to default.",
+ "config-load-failed": "§c[OllamaChat] Failed to load configuration: {error}",
+ "response-prefix": "§7[OllamaChat] §f",
+ "error-prefix": "§c[OllamaChat] §f",
+ "generating-status": "§a[OllamaChat] Generating... ({progress}%)",
+ "complete-status": "§a[OllamaChat] Completed!",
+ "error-status": "§c[OllamaChat] Error!",
+ "no-permission": "§c[OllamaChat] You do not have permission to use this command!",
+ "prompt-usage": "§e[OllamaChat] Prompt Usage:\n- /ollamachat prompt set \n- /ollamachat prompt delete \n- /ollamachat prompt list\n- /ollamachat prompt select \n- /ollamachat prompt clear",
+ "prompt-set": "§a[OllamaChat] Prompt '{name}' set successfully!",
+ "prompt-deleted": "§a[OllamaChat] Prompt '{name}' deleted successfully!",
+ "prompt-not-found": "§c[OllamaChat] Prompt '{name}' not found!",
+ "prompt-list": "§a[OllamaChat] Available prompts:\n- {prompts}",
+ "prompt-list-empty": "§a[OllamaChat] No prompts available.",
+ "prompt-selected": "§a[OllamaChat] Default prompt set to '{name}'.",
+ "prompt-cleared": "§a[OllamaChat] Default prompt cleared.",
+ "prompt-default": "§a[OllamaChat] Current default prompt: '{name}'",
+ "prompt-default-invalid": "§c[OllamaChat] Current default prompt '{name}' is invalid (not found).",
+ "conversation-usage": "§e[OllamaChat] Conversation Usage:\n- /ollamachat conversation new \n- /ollamachat conversation select \n- /ollamachat conversation delete \n- /ollamachat conversation list ",
+ "conversation-created": "§a[OllamaChat] Conversation '{name}' created for {ai-name}!",
+ "conversation-selected": "§a[OllamaChat] Selected conversation '{name}' for {ai-name}.",
+ "conversation-deleted": "§a[OllamaChat] Conversation '{name}' deleted for {ai-name}.",
+ "conversation-not-found": "§c[OllamaChat] Conversation '{name}' not found!",
+ "conversation-list": "§a[OllamaChat] Available conversations for {ai-name}:\n- {conversations}",
+ "conversation-list-empty": "§a[OllamaChat] No conversations available for {ai-name}.",
+ "conversation-default": "§a[OllamaChat] Current conversation for {ai-name}: '{name}'",
+ "conversation-default-invalid": "§c[OllamaChat] Current conversation '{name}' for {ai-name} is invalid (not found).",
+ "suggested-responses-header": "§a[OllamaChat] Suggested Responses:",
+ "suggested-response-prefix": "§7[Click to Reply] §f",
+ "general-error": "§c[OllamaChat] An unexpected error occurred: {error}",
+ "streaming-interrupted": "§c[OllamaChat] Streaming response was interrupted.",
+ "database-error": "§c[OllamaChat] Database error occurred: {error}",
+ "response-truncated": "§e[OllamaChat] Response was truncated due to length limit.",
+ "json-render-error": "§c[OllamaChat] Failed to render suggested response: {error}",
+ "suggests-enabled": "§a[OllamaChat] Suggested responses enabled for {player}!",
+ "suggests-disabled": "§c[OllamaChat] Suggested responses disabled for {player}!",
+ "suggests-rate-limit": "§c[OllamaChat] Please wait {seconds} seconds before generating more suggestions.",
+ "suggested-response-hover": "§eClick to send this suggestion as a prompt!",
+ "suggests-presets-enabled": "§a[OllamaChat] Suggested response presets have been enabled.",
+ "suggests-presets-disabled": "§c[OllamaChat] Suggested response presets have been disabled."
+}
\ No newline at end of file
diff --git a/src/main/resources/lang/zh_cn.json b/src/main/resources/lang/zh_cn.json
new file mode 100644
index 0000000..4233874
--- /dev/null
+++ b/src/main/resources/lang/zh_cn.json
@@ -0,0 +1,52 @@
+{
+ "usage-ollamachat": "§e[OllamaChat] 用法说明:\n- /ollamachat reload\n- /ollamachat toggle \n- /ollamachat prompt [参数]\n- /ollamachat conversation [参数]",
+ "usage-aichat": "§e[OllamaChat] 用法说明:\n- /aichat <提示内容>",
+ "reload-success": "§a[OllamaChat] 配置已成功重新加载!",
+ "ollama-enabled": "§a[OllamaChat] Ollama 集成功能已启用!",
+ "ollama-disabled": "§c[OllamaChat] Ollama 集成功能已停用!",
+ "toggle-enabled": "§a[OllamaChat] {ai-name} 集成功能已启用!",
+ "toggle-disabled": "§c[OllamaChat] {ai-name} 集成功能已停用!",
+ "invalid-ai-name": "§c[OllamaChat] 无效的 AI 名称!可用 AI 列表:{ai-list}",
+ "player-only": "§c[OllamaChat] 此命令仅限玩家使用!",
+ "config-invalid": "§c[OllamaChat] 配置文件无效,已重置为默认设置。",
+ "config-load-failed": "§c[OllamaChat] 加载配置失败:{error}",
+ "response-prefix": "§7[OllamaChat] §f",
+ "error-prefix": "§c[OllamaChat] §f",
+ "generating-status": "§a[OllamaChat] 生成中... ({progress}%)",
+ "complete-status": "§a[OllamaChat] 已完成!",
+ "error-status": "§c[OllamaChat] 出错!",
+ "no-permission": "§c[OllamaChat] 您没有权限使用此命令!",
+ "prompt-usage": "§e[OllamaChat] 提示词用法说明:\n- /ollamachat prompt set <名称> <内容>\n- /ollamachat prompt delete <名称>\n- /ollamachat prompt list\n- /ollamachat prompt select <名称>\n- /ollamachat prompt clear",
+ "prompt-set": "§a[OllamaChat] 提示词 '{name}' 已设置成功!",
+ "prompt-deleted": "§a[OllamaChat] 提示词 '{name}' 已删除成功!",
+ "prompt-not-found": "§c[OllamaChat] 未找到提示词 '{name}'!",
+ "prompt-list": "§a[OllamaChat] 可用提示词列表:\n- {prompts}",
+ "prompt-list-empty": "§a[OllamaChat] 暂无可用提示词。",
+ "prompt-selected": "§a[OllamaChat] 已将 '{name}' 设为默认提示词。",
+ "prompt-cleared": "§a[OllamaChat] 默认提示词已清除。",
+ "prompt-default": "§a[OllamaChat] 当前默认提示词:'{name}'",
+ "prompt-default-invalid": "§c[OllamaChat] 当前默认提示词 '{name}' 无效(未找到)。",
+ "conversation-usage": "§e[OllamaChat] 对话管理用法说明:\n- /ollamachat conversation new <名称>\n- /ollamachat conversation select \n- /ollamachat conversation delete \n- /ollamachat conversation list ",
+ "conversation-created": "§a[OllamaChat] 已为 {ai-name} 创建对话 '{name}'!",
+ "conversation-selected": "§a[OllamaChat] 已为 {ai-name} 选择对话 '{name}'。",
+ "conversation-deleted": "§a[OllamaChat] 已为 {ai-name} 删除对话 '{name}'。",
+ "conversation-not-found": "§c[OllamaChat] 未找到对话 '{name}'!",
+ "conversation-list": "§a[OllamaChat] {ai-name} 的可用对话列表:\n- {conversations}",
+ "conversation-list-empty": "§a[OllamaChat] {ai-name} 暂无可用对话。",
+ "conversation-default": "§a[OllamaChat] {ai-name} 的当前对话:'{name}'",
+ "conversation-default-invalid": "§c[OllamaChat] {ai-name} 的当前对话 '{name}' 无效(未找到)。",
+ "suggested-responses-header": "§a[OllamaChat] 推荐回复:",
+ "suggested-response-prefix": "§7[点击回复] §f",
+ "general-error": "§c[OllamaChat] 发生意外错误:{error}",
+ "streaming-interrupted": "§c[OllamaChat] 流式响应已中断。",
+ "database-error": "§c[OllamaChat] 数据库错误:{error}",
+ "response-truncated": "§e[OllamaChat] 响应因长度限制已被截断。",
+ "json-render-error": "§c[OllamaChat] 未能渲染成功推荐回复: {error}",
+ "suggests-enabled": "§a[OllamaChat] 已为玩家{player}启用推荐回复!",
+ "suggests-disabled": "§c[OllamaChat] 已为玩家{player}禁用推荐回复!",
+ "suggests-rate-limit": "§c[OllamaChat] 请等待{seconds}秒后再生成推荐回复",
+ "suggested-response-hover": "§e点击发送此推荐内容!",
+ "suggests-presets-enabled": "§a[OllamaChat] 已启用预设推荐回复",
+ "suggests-presets-disabled": "§c[OllamaChat] 已启用预设推荐回复"
+}
+
diff --git a/src/main/resources/lang/zh_cn.lang b/src/main/resources/lang/zh_cn.lang
deleted file mode 100644
index c2389c2..0000000
--- a/src/main/resources/lang/zh_cn.lang
+++ /dev/null
@@ -1,18 +0,0 @@
-# 通用消息
-reload-success: "§a配置已重新加载。"
-toggle-enabled: "§a{ai-name} 已启用。"
-toggle-disabled: "§a{ai-name} 已禁用。"
-invalid-ai-name: "§c无效的 AI 名称。可用的 AI: {ai-list}。"
-usage-aichat: "§c用法: /aichat <提示>"
-usage-ollamachat: "§c用法: /ollamachat >"
-player-only: "§c该命令只能由玩家使用。"
-ollama-enabled: "§aOllama 已启用。"
-ollama-disabled: "§aOllama 已禁用。"
-
-# 聊天交互
-response-prefix: "§b[AI] §r"
-error-prefix: "§c[错误] §r"
-
-# 日志消息
-config-invalid: "§c配置文件无效或不完整,正在重新生成默认配置..."
-config-load-failed: "§c加载配置文件失败: {error},正在重新生成默认配置..."
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 8773e01..438745e 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -1,25 +1,58 @@
name: OllamaChat
-version: '1.0.2'
-main: com.ollamachat.Ollamachat
+version: '1.1.4'
+main: com.ollamachat.core.Ollamachat
api-version: '1.21'
authors: [xwwsdd]
-description: A plugin used to connect Ollama with Minecraft.
+description: A plugin used to connect Ollama and OtherAI with Minecraft.
website: https://chat.sarskin.cn/invite/iHgI6LTX
commands:
ollamachat:
description: Manage OllamaChat plugin (reload configuration or toggle AI)
usage: "{usage-ollamachat}"
- permission: ollamachat.admin
+ permission: ollamachat.reload
aichat:
description: Interact with other AI services
usage: "{usage-aichat}"
permission: ollamachat.use
permissions:
- ollamachat.admin:
- description: Allows managing the plugin (reload and toggle AI)
+ ollamachat.reload:
+ description: Allows managing the plugin (reload)
+ default: op
+ ollamachat.toggle:
+ description: Allows managing the plugin (toggle AI)
default: op
ollamachat.use:
description: Allows using the /aichat command
- default: true
\ No newline at end of file
+ default: true
+ ollamachat.prompt.set:
+ description: Allows setting a new prompt
+ default: op
+ ollamachat.prompt.delete:
+ description: Allows deleting a prompt
+ default: op
+ ollamachat.prompt.list:
+ description: Allows listing all prompts
+ default: op
+ ollamachat.prompt.select:
+ description: Allows selecting or clearing a prompt
+ default: op
+ ollamachat.conversation.new:
+ description: Allows creating a new conversation
+ default: true
+ ollamachat.conversation.select:
+ description: Allows selecting a conversation
+ default: true
+ ollamachat.conversation.list:
+ description: Allows listing conversations
+ default: true
+ ollamachat.conversation.delete:
+ description: Allows deleting a conversation
+ default: true
+ ollamachat.suggests.toggle:
+ description: Allows toggling suggested responses on or off
+ default: true
+ ollamachat.suggests-presets.toggle:
+ description: Allows toggling of suggested response presets
+ default: op
\ No newline at end of file