forked from Upstream/Ollama-Chat
broadcast
All checks were successful
Build Ollama-chat plugin / Build-latest-jar (push) Successful in 1m49s
All checks were successful
Build Ollama-chat plugin / Build-latest-jar (push) Successful in 1m49s
This commit is contained in:
parent
460ba125a5
commit
8184cf771d
@ -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:/
|
||||
curl --insecure --user username:mypass -T build/libs/OllamaChat-1.1.4.jar ftp://192.168.10.133:/
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
8
.idea/.gitignore
vendored
8
.idea/.gitignore
vendored
@ -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
|
@ -1 +0,0 @@
|
||||
OllamaChat
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="APPLICATION" />
|
||||
<option name="description" value="" />
|
||||
<option name="applicationTheme" value="default" />
|
||||
<option name="iconsTheme" value="default" />
|
||||
<option name="button1Title" value="" />
|
||||
<option name="button1Url" value="" />
|
||||
<option name="button2Title" value="" />
|
||||
<option name="button2Url" value="" />
|
||||
<option name="customApplicationId" value="" />
|
||||
</component>
|
||||
</project>
|
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectTreeColorHighlighter">
|
||||
<files />
|
||||
<colors>
|
||||
<color id="1" value="#4f060d" name="Color 1" enabled="true" />
|
||||
<color id="2" value="#44220e" name="Color 2" enabled="true" />
|
||||
<color id="3" value="#3f371b" name="Color 3" enabled="true" />
|
||||
<color id="4" value="#162c16" name="Color 4" enabled="true" />
|
||||
<color id="5" value="#0f2f47" name="Color 5" enabled="true" />
|
||||
<color id="6" value="#171a34" name="Color 6" enabled="true" />
|
||||
<color id="7" value="#311333" name="Color 7" enabled="true" />
|
||||
<color id="8" value="#1e1e1e" name="Color 8" enabled="true" />
|
||||
</colors>
|
||||
<option name="marksForCollapsedHighlights" value="Dots" />
|
||||
</component>
|
||||
</project>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK" />
|
||||
</project>
|
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/OllamaChat.main.iml" filepath="$PROJECT_DIR$/.idea/modules/OllamaChat.main.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="minecraft" name="Minecraft">
|
||||
<configuration>
|
||||
<autoDetectTypes>
|
||||
<platformType>PAPER</platformType>
|
||||
<platformType>ADVENTURE</platformType>
|
||||
</autoDetectTypes>
|
||||
<projectReimportVersion>1</projectReimportVersion>
|
||||
</configuration>
|
||||
</facet>
|
||||
</component>
|
||||
</module>
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
138
README.md
138
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 <ai-name>` 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 <ai-name>`.
|
||||
- **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 <ai-name>**: Enables or disables the specified AI service.
|
||||
- **/aichat <ai-name> <prompt>**: Interacts with other AI services (e.g., OpenAI).
|
||||
- **/ollamachat reload**: Reloads plugin configuration and language files (`ollamachat.reload`).
|
||||
- **/ollamachat toggle <ai-name>**: Enables/disables specified AI service (`ollamachat.toggle`).
|
||||
- **/aichat <ai-name> <prompt>**: Interacts with other AI services (`ollamachat.use`).
|
||||
- **/ollamachat prompt set <promptName> <promptContent>**: Creates a new prompt (`ollamachat.prompt.set`).
|
||||
- **/ollamachat prompt delete <promptName>**: Deletes a prompt (`ollamachat.prompt.delete`).
|
||||
- **/ollamachat prompt list**: Lists all prompts (`ollamachat.prompt.list`).
|
||||
- **/ollamachat prompt select <promptName>**: Sets default prompt (`ollamachat.prompt.select`).
|
||||
- **/ollamachat prompt clear**: Resets default prompt (`ollamachat.prompt.select`).
|
||||
- **/ollamachat conversation new <aiName> <convName>**: Starts a new conversation (`ollamachat.conversation.new`).
|
||||
- **/ollamachat conversation select <aiName> <convName>**: Switches conversations (`ollamachat.conversation.select`).
|
||||
- **/ollamachat conversation delete <aiName> <convName>**: Deletes a conversation (`ollamachat.conversation.delete`).
|
||||
- **/ollamachat conversation list <aiName>**: 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 <aiName>` | `ollamachat.toggle` | Toggles specified AI service. |
|
||||
| `/aichat <aiName> <message>` | `ollamachat.use` | Sends a message to specified AI. |
|
||||
| `/ollamachat prompt set <promptName> <promptContent>` | `ollamachat.prompt.set` | Creates and saves a new prompt. |
|
||||
| `/ollamachat prompt delete <promptName>` | `ollamachat.prompt.delete` | Deletes a specified prompt. |
|
||||
| `/ollamachat prompt list` | `ollamachat.prompt.list` | Lists all prompts and current default. |
|
||||
| `/ollamachat prompt select <promptName>` | `ollamachat.prompt.select` | Sets a prompt as default. |
|
||||
| `/ollamachat prompt clear` | `ollamachat.prompt.select` | Resets default prompt. |
|
||||
| `/ollamachat conversation new <aiName> <convName>` | `ollamachat.conversation.new` | Starts a new conversation. |
|
||||
| `/ollamachat conversation select <aiName> <convName>` | `ollamachat.conversation.select` | Switches to an existing conversation. |
|
||||
| `/ollamachat conversation delete <aiName> <convName>` | `ollamachat.conversation.delete` | Deletes a conversation. |
|
||||
| `/ollamachat conversation list <aiName>` | `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!
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<String> sendRequest(String apiUrl, String apiKey, String model, String prompt) {
|
||||
public CompletableFuture<String> sendRequest(String apiUrl, String apiKey, String model, String prompt, boolean isMessagesFormat) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Map<String, Object> requestBody = Map.of(
|
||||
"model", model,
|
||||
"prompt", prompt,
|
||||
"stream", false
|
||||
);
|
||||
Map<String, Object> 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<String> 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<Void> sendStreamingRequest(String apiUrl, String apiKey, String model, String prompt, Consumer<String> responseConsumer, boolean isMessagesFormat) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Map<String, Object> 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<String> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<String, String> 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);
|
||||
}
|
||||
}
|
@ -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<String, String> listConversations(UUID playerUuid, String aiModel) {
|
||||
Map<String, String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
114
src/main/java/com/ollamachat/DependencyLoader.java
Normal file
114
src/main/java/com/ollamachat/DependencyLoader.java
Normal file
@ -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<Dependency> 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<URL> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -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<String, AIConfig> otherAIConfigs;
|
||||
private boolean ollamaEnabled;
|
||||
private Map<String, Boolean> 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<String, String> placeholders) {
|
||||
String message = langConfig.getString(key, "§cMissing language key: " + key);
|
||||
if (placeholders != null) {
|
||||
for (Map.Entry<String, String> 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;
|
||||
}
|
||||
}
|
104
src/main/java/com/ollamachat/ProgressManager.java
Normal file
104
src/main/java/com/ollamachat/ProgressManager.java
Normal file
@ -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<UUID, BossBar> bossBars = new HashMap<>();
|
||||
private final Map<UUID, BukkitTask> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
164
src/main/java/com/ollamachat/chat/ChatTriggerHandler.java
Normal file
164
src/main/java/com/ollamachat/chat/ChatTriggerHandler.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
181
src/main/java/com/ollamachat/chat/SuggestedResponseHandler.java
Normal file
181
src/main/java/com/ollamachat/chat/SuggestedResponseHandler.java
Normal file
@ -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<UUID, Long> 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<UUID, Boolean> 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<String> suggestedResponses = new ArrayList<>();
|
||||
if (configManager.isSuggestedResponsePresetsEnabled()) {
|
||||
suggestedResponses.addAll(configManager.getSuggestedResponsePresets());
|
||||
}
|
||||
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
48
src/main/java/com/ollamachat/command/AIChatCommand.java
Normal file
48
src/main/java/com/ollamachat/command/AIChatCommand.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
265
src/main/java/com/ollamachat/command/OllamaChatCommand.java
Normal file
265
src/main/java/com/ollamachat/command/OllamaChatCommand.java
Normal file
@ -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<String, String> 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<String, String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
143
src/main/java/com/ollamachat/command/OllamaChatTabCompleter.java
Normal file
143
src/main/java/com/ollamachat/command/OllamaChatTabCompleter.java
Normal file
@ -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<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (command.getName().equalsIgnoreCase("ollamachat")) {
|
||||
if (args.length == 1) {
|
||||
List<String> 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<String> 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<String> 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<String> 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<String> 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<String, String> 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<String> aiNames = new ArrayList<>();
|
||||
aiNames.add("ollama");
|
||||
aiNames.addAll(configManager.getOtherAIConfigs().keySet());
|
||||
return filterCompletions(aiNames, args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
private List<String> filterCompletions(List<String> options, String input) {
|
||||
return options.stream()
|
||||
.filter(option -> option.toLowerCase().startsWith(input.toLowerCase()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
325
src/main/java/com/ollamachat/core/ConfigManager.java
Normal file
325
src/main/java/com/ollamachat/core/ConfigManager.java
Normal file
@ -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<String> triggerPrefixes;
|
||||
private int maxResponseLength;
|
||||
private Map<String, AIConfig> otherAIConfigs;
|
||||
private boolean ollamaEnabled;
|
||||
private Map<String, Boolean> otherAIEnabled;
|
||||
private boolean streamingEnabled;
|
||||
private String defaultPrompt;
|
||||
private Map<String, String> prompts;
|
||||
private Map<UUID, Map<String, String>> selectedConversations;
|
||||
private int maxHistory;
|
||||
private List<String> suggestedResponseModels;
|
||||
private boolean suggestedResponsesEnabled;
|
||||
private int suggestedResponseCount;
|
||||
private String suggestedResponsePrompt;
|
||||
private List<String> suggestedResponsePresets;
|
||||
private Map<String, Boolean> 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<String, String> placeholders) {
|
||||
String message = langConfig != null && langConfig.has(key) ? langConfig.get(key).getAsString() : "§c[OllamaChat] Missing language key: " + key;
|
||||
if (placeholders != null) {
|
||||
for (Map.Entry<String, String> 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<String> getTriggerPrefixes() {
|
||||
return triggerPrefixes;
|
||||
}
|
||||
|
||||
public int getMaxResponseLength() {
|
||||
return maxResponseLength;
|
||||
}
|
||||
|
||||
public Map<String, AIConfig> getOtherAIConfigs() {
|
||||
return otherAIConfigs;
|
||||
}
|
||||
|
||||
public boolean isOllamaEnabled() {
|
||||
return ollamaEnabled;
|
||||
}
|
||||
|
||||
public void setOllamaEnabled(boolean enabled) {
|
||||
this.ollamaEnabled = enabled;
|
||||
}
|
||||
|
||||
public Map<String, Boolean> getOtherAIEnabled() {
|
||||
return otherAIEnabled;
|
||||
}
|
||||
|
||||
public boolean isStreamingEnabled() {
|
||||
return streamingEnabled;
|
||||
}
|
||||
|
||||
public String getDefaultPrompt() {
|
||||
return defaultPrompt;
|
||||
}
|
||||
|
||||
public void setDefaultPrompt(String prompt) {
|
||||
this.defaultPrompt = prompt;
|
||||
}
|
||||
|
||||
public Map<String, String> getPrompts() {
|
||||
return prompts;
|
||||
}
|
||||
|
||||
public Map<UUID, Map<String, String>> getSelectedConversations() {
|
||||
return selectedConversations;
|
||||
}
|
||||
|
||||
public int getMaxHistory() {
|
||||
return maxHistory;
|
||||
}
|
||||
|
||||
public List<String> getSuggestedResponseModels() {
|
||||
return suggestedResponseModels;
|
||||
}
|
||||
|
||||
public boolean isSuggestedResponsesEnabled() {
|
||||
return suggestedResponsesEnabled;
|
||||
}
|
||||
|
||||
public int getSuggestedResponseCount() {
|
||||
return suggestedResponseCount;
|
||||
}
|
||||
|
||||
public String getSuggestedResponsePrompt() {
|
||||
return suggestedResponsePrompt;
|
||||
}
|
||||
|
||||
public List<String> getSuggestedResponsePresets() {
|
||||
return suggestedResponsePresets;
|
||||
}
|
||||
|
||||
public Map<String, Boolean> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
112
src/main/java/com/ollamachat/core/Ollamachat.java
Normal file
112
src/main/java/com/ollamachat/core/Ollamachat.java
Normal file
@ -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<UUID, Boolean> 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<UUID, Boolean> getPlayerSuggestionToggles() {
|
||||
return playerSuggestionToggles;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 <ai-name> <prompt>"
|
||||
usage-ollamachat: "§cUsage: /ollamachat <reload|toggle <ai-name>>"
|
||||
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..."
|
51
src/main/resources/lang/en_us.json
Normal file
51
src/main/resources/lang/en_us.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"usage-ollamachat": "§e[OllamaChat] Usage:\n- /ollamachat reload\n- /ollamachat toggle <ai-name>\n- /ollamachat prompt <set|delete|list|select|clear> [args]\n- /ollamachat conversation <new|select|delete|list> [args]",
|
||||
"usage-aichat": "§e[OllamaChat] Usage:\n- /aichat <ai-name> <prompt>",
|
||||
"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 <name> <content>\n- /ollamachat prompt delete <name>\n- /ollamachat prompt list\n- /ollamachat prompt select <name>\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 <ai-name> <name>\n- /ollamachat conversation select <ai-name> <id>\n- /ollamachat conversation delete <ai-name> <id>\n- /ollamachat conversation list <ai-name>",
|
||||
"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."
|
||||
}
|
52
src/main/resources/lang/zh_cn.json
Normal file
52
src/main/resources/lang/zh_cn.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"usage-ollamachat": "§e[OllamaChat] 用法说明:\n- /ollamachat reload\n- /ollamachat toggle <AI名称>\n- /ollamachat prompt <set|delete|list|select|clear> [参数]\n- /ollamachat conversation <new|select|delete|list> [参数]",
|
||||
"usage-aichat": "§e[OllamaChat] 用法说明:\n- /aichat <AI名称> <提示内容>",
|
||||
"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 <AI名称> <名称>\n- /ollamachat conversation select <AI名称> <ID>\n- /ollamachat conversation delete <AI名称> <ID>\n- /ollamachat conversation list <AI名称>",
|
||||
"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] 已启用预设推荐回复"
|
||||
}
|
||||
|
@ -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 <ai名称> <提示>"
|
||||
usage-ollamachat: "§c用法: /ollamachat <reload|toggle <ai名称>>"
|
||||
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},正在重新生成默认配置..."
|
@ -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
|
||||
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
|
Loading…
Reference in New Issue
Block a user