feat: ai roleplay, gif proxy, en prompt, channel lock, .md dossiers
This commit is contained in:
parent
2990f01958
commit
a111bcd674
8 changed files with 237 additions and 83 deletions
|
|
@ -37,6 +37,16 @@ services:
|
||||||
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
|
SPOTIFY_CLIENT_ID: ${SPOTIFY_CLIENT_ID}
|
||||||
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
SPOTIFY_CLIENT_SECRET: ${SPOTIFY_CLIENT_SECRET}
|
||||||
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
|
DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY}
|
||||||
|
AI_CHANNEL_ID: ${AI_CHANNEL_ID}
|
||||||
|
GIPHY_API_KEY: ${GIPHY_API_KEY}
|
||||||
|
AI_CONTEXT_MSGS: ${AI_CONTEXT_MSGS:-10}
|
||||||
|
AI_MSG_LENGTH: ${AI_MSG_LENGTH:-300}
|
||||||
|
AI_CONTEXT_CHARS: ${AI_CONTEXT_CHARS:-3000}
|
||||||
|
AI_DOSSIER_MSGS: ${AI_DOSSIER_MSGS:-30}
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: "8"
|
||||||
volumes:
|
volumes:
|
||||||
- bot-data:/app/data
|
- bot-data:/app/data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,13 @@ public class BotConfig {
|
||||||
private final String superAdminId;
|
private final String superAdminId;
|
||||||
private final String dbPath;
|
private final String dbPath;
|
||||||
private final String deepseekApiKey;
|
private final String deepseekApiKey;
|
||||||
|
private final String giphyApiKey;
|
||||||
|
private final String aiChannelId;
|
||||||
|
private final int aiContextMsgs;
|
||||||
|
private final int aiMsgLength;
|
||||||
|
private final int aiContextChars;
|
||||||
|
private final int aiDossierMsgs;
|
||||||
|
private final int aiDossierUpdateHours;
|
||||||
private final LavalinkConfig lavalinkConfig;
|
private final LavalinkConfig lavalinkConfig;
|
||||||
|
|
||||||
public BotConfig(
|
public BotConfig(
|
||||||
|
|
@ -22,6 +29,13 @@ public class BotConfig {
|
||||||
String superAdminId,
|
String superAdminId,
|
||||||
String dbPath,
|
String dbPath,
|
||||||
String deepseekApiKey,
|
String deepseekApiKey,
|
||||||
|
String giphyApiKey,
|
||||||
|
String aiChannelId,
|
||||||
|
int aiContextMsgs,
|
||||||
|
int aiMsgLength,
|
||||||
|
int aiContextChars,
|
||||||
|
int aiDossierMsgs,
|
||||||
|
int aiDossierUpdateHours,
|
||||||
LavalinkConfig lavalinkConfig) {
|
LavalinkConfig lavalinkConfig) {
|
||||||
this.mainToken = mainToken;
|
this.mainToken = mainToken;
|
||||||
this.musicToken = musicToken;
|
this.musicToken = musicToken;
|
||||||
|
|
@ -32,6 +46,13 @@ public class BotConfig {
|
||||||
this.superAdminId = superAdminId;
|
this.superAdminId = superAdminId;
|
||||||
this.dbPath = dbPath;
|
this.dbPath = dbPath;
|
||||||
this.deepseekApiKey = deepseekApiKey;
|
this.deepseekApiKey = deepseekApiKey;
|
||||||
|
this.giphyApiKey = giphyApiKey;
|
||||||
|
this.aiChannelId = aiChannelId;
|
||||||
|
this.aiContextMsgs = aiContextMsgs;
|
||||||
|
this.aiMsgLength = aiMsgLength;
|
||||||
|
this.aiContextChars = aiContextChars;
|
||||||
|
this.aiDossierMsgs = aiDossierMsgs;
|
||||||
|
this.aiDossierUpdateHours = aiDossierUpdateHours;
|
||||||
this.lavalinkConfig = lavalinkConfig;
|
this.lavalinkConfig = lavalinkConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,6 +96,34 @@ public class BotConfig {
|
||||||
return deepseekApiKey;
|
return deepseekApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getGiphyApiKey() {
|
||||||
|
return giphyApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAiChannelId() {
|
||||||
|
return aiChannelId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAiContextMsgs() {
|
||||||
|
return aiContextMsgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAiMsgLength() {
|
||||||
|
return aiMsgLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAiContextChars() {
|
||||||
|
return aiContextChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAiDossierMsgs() {
|
||||||
|
return aiDossierMsgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAiDossierUpdateHours() {
|
||||||
|
return aiDossierUpdateHours;
|
||||||
|
}
|
||||||
|
|
||||||
public LavalinkConfig getLavalinkConfig() {
|
public LavalinkConfig getLavalinkConfig() {
|
||||||
return lavalinkConfig;
|
return lavalinkConfig;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,14 @@ public final class ConfigLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
String deepseekApiKey = dotenv.get("DEEPSEEK_API_KEY");
|
String deepseekApiKey = dotenv.get("DEEPSEEK_API_KEY");
|
||||||
|
String giphyApiKey = dotenv.get("GIPHY_API_KEY");
|
||||||
|
String aiChannelId = dotenv.get("AI_CHANNEL_ID");
|
||||||
|
|
||||||
|
int aiContextMsgs = Integer.parseInt(getEnv(dotenv, "AI_CONTEXT_MSGS", "10"));
|
||||||
|
int aiMsgLength = Integer.parseInt(getEnv(dotenv, "AI_MSG_LENGTH", "300"));
|
||||||
|
int aiContextChars = Integer.parseInt(getEnv(dotenv, "AI_CONTEXT_CHARS", "3000"));
|
||||||
|
int aiDossierMsgs = Integer.parseInt(getEnv(dotenv, "AI_DOSSIER_MSGS", "30"));
|
||||||
|
int aiDossierUpdateHours = Integer.parseInt(getEnv(dotenv, "AI_DOSSIER_UPDATE_HOURS", "24"));
|
||||||
|
|
||||||
LavalinkConfig lavalinkConfig = getLavalinkConfig();
|
LavalinkConfig lavalinkConfig = getLavalinkConfig();
|
||||||
LoggerDecorator.success(logger, "Конфигурация загружена успешно");
|
LoggerDecorator.success(logger, "Конфигурация загружена успешно");
|
||||||
|
|
@ -87,6 +95,13 @@ public final class ConfigLoader {
|
||||||
superAdminId,
|
superAdminId,
|
||||||
dbPath,
|
dbPath,
|
||||||
deepseekApiKey,
|
deepseekApiKey,
|
||||||
|
giphyApiKey,
|
||||||
|
aiChannelId,
|
||||||
|
aiContextMsgs,
|
||||||
|
aiMsgLength,
|
||||||
|
aiContextChars,
|
||||||
|
aiDossierMsgs,
|
||||||
|
aiDossierUpdateHours,
|
||||||
lavalinkConfig);
|
lavalinkConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,11 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import creator.loki.config.BotConfig;
|
||||||
import net.dv8tion.jda.api.entities.Message;
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||||
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
@ -19,11 +21,13 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
private static final long TRACK_TTL = TimeUnit.HOURS.toMillis(1);
|
private static final long TRACK_TTL = TimeUnit.HOURS.toMillis(1);
|
||||||
|
|
||||||
private final RoastService roastService;
|
private final RoastService roastService;
|
||||||
|
private final String allowedChannelId;
|
||||||
private final ConcurrentHashMap<String, Long> trackedUsers = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Long> trackedUsers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AndrertListener(RoastService roastService) {
|
public AndrertListener(RoastService roastService, BotConfig config) {
|
||||||
this.roastService = roastService;
|
this.roastService = roastService;
|
||||||
|
this.allowedChannelId = config.getAiChannelId();
|
||||||
trackedUsers.put(ANDRERT_USER_ID, System.currentTimeMillis() + TRACK_TTL);
|
trackedUsers.put(ANDRERT_USER_ID, System.currentTimeMillis() + TRACK_TTL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,21 +38,26 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
|
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
|
||||||
if (event.getAuthor().isBot()) return;
|
if (event.getAuthor().isBot()) return;
|
||||||
|
|
||||||
|
String channelId = event.getChannel().getId();
|
||||||
|
boolean isAllowedChannel = allowedChannelId != null && !allowedChannelId.isBlank()
|
||||||
|
&& channelId.equals(allowedChannelId);
|
||||||
|
|
||||||
|
if (!isAllowedChannel) {
|
||||||
String userId = event.getAuthor().getId();
|
String userId = event.getAuthor().getId();
|
||||||
Long expiresAt = trackedUsers.get(userId);
|
Long expiresAt = trackedUsers.get(userId);
|
||||||
if (expiresAt == null) return;
|
if (expiresAt == null) return;
|
||||||
|
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (now > expiresAt) {
|
if (now > expiresAt) {
|
||||||
trackedUsers.remove(userId);
|
trackedUsers.remove(userId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String content = event.getMessage().getContentRaw().strip();
|
String content = event.getMessage().getContentRaw().strip();
|
||||||
if (content.length() < 2 && event.getMessage().getAttachments().isEmpty()) return;
|
if (content.length() < 2 && event.getMessage().getAttachments().isEmpty()) return;
|
||||||
|
|
||||||
trackedUsers.put(userId, now + TRACK_TTL);
|
trackedUsers.put(userId, now + TRACK_TTL);
|
||||||
respond(event, userId);
|
}
|
||||||
|
|
||||||
|
respond(event, event.getAuthor().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void respond(MessageReceivedEvent event, String userId) {
|
private void respond(MessageReceivedEvent event, String userId) {
|
||||||
|
|
@ -72,9 +81,27 @@ public class AndrertListener extends ListenerAdapter {
|
||||||
java.util.Collections.reverse(context);
|
java.util.Collections.reverse(context);
|
||||||
|
|
||||||
String response = roastService.generateResponse(context, targetName, userId);
|
String response = roastService.generateResponse(context, targetName, userId);
|
||||||
if (response != null && !response.isBlank()) {
|
if (response == null || response.isBlank()) return;
|
||||||
event.getMessage().reply(response).queue();
|
|
||||||
|
String gifUrl = null;
|
||||||
|
String text = response;
|
||||||
|
int gifIdx = response.indexOf("gif:");
|
||||||
|
if (gifIdx >= 0) {
|
||||||
|
String after = response.substring(gifIdx + 4).strip();
|
||||||
|
text = response.substring(0, gifIdx).strip();
|
||||||
|
if (!after.isBlank()) {
|
||||||
|
String topic = after.split("\\s+")[0];
|
||||||
|
gifUrl = roastService.searchGif(topic);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var builder = new MessageCreateBuilder();
|
||||||
|
if (gifUrl != null) {
|
||||||
|
builder.addContent(text + "\n" + gifUrl);
|
||||||
|
} else {
|
||||||
|
builder.addContent(text);
|
||||||
|
}
|
||||||
|
event.getMessage().reply(builder.build()).queue();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ public class DossierCommand implements Command {
|
||||||
reply.editMessage("🧠 Анализирую " + msgs.size() + " сообщений...").queue();
|
reply.editMessage("🧠 Анализирую " + msgs.size() + " сообщений...").queue();
|
||||||
String dossier = roastService.generateDossier(msgs, targetName, targetId);
|
String dossier = roastService.generateDossier(msgs, targetName, targetId);
|
||||||
if (dossier != null && !dossier.isBlank()) {
|
if (dossier != null && !dossier.isBlank()) {
|
||||||
reply.editMessage("📋 **Досье на " + targetName + ":**\n" + dossier).queue();
|
reply.editMessage("✅ Досье на " + targetName + " сохранено").queue();
|
||||||
} else {
|
} else {
|
||||||
reply.editMessage("AI не смог составить досье").queue();
|
reply.editMessage("AI не смог составить досье").queue();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package creator.loki.feature.roast;
|
package creator.loki.feature.roast;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
@ -31,23 +32,46 @@ public class RoastService {
|
||||||
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
private static final String MODEL = "deepseek-chat";
|
private static final String MODEL = "deepseek-chat";
|
||||||
private static final Path DOSSIER_DIR = Paths.get("data/dossiers");
|
private static final Path DOSSIER_DIR = Paths.get("data/dossiers");
|
||||||
private static final int MAX_DOSSIER_MSGS = 30;
|
private static final Path PROMPTS_DIR = Paths.get("data/prompts");
|
||||||
|
|
||||||
private final OkHttpClient httpClient;
|
private final OkHttpClient httpClient;
|
||||||
private final String apiKey;
|
private final String apiKey;
|
||||||
|
private final String giphyApiKey;
|
||||||
|
private final int maxContextMsgs;
|
||||||
|
private final int maxMsgLength;
|
||||||
|
private final int maxContextChars;
|
||||||
|
private final int maxDossierMsgs;
|
||||||
|
private final String systemPrompt;
|
||||||
|
private final String dossierGeneratePrompt;
|
||||||
private final Map<String, DossierData> dossiers = new ConcurrentHashMap<>();
|
private final Map<String, DossierData> dossiers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RoastService(BotConfig config) {
|
public RoastService(BotConfig config) {
|
||||||
this.apiKey = config.getDeepseekApiKey();
|
this.apiKey = config.getDeepseekApiKey();
|
||||||
|
this.giphyApiKey = config.getGiphyApiKey();
|
||||||
|
this.maxContextMsgs = config.getAiContextMsgs();
|
||||||
|
this.maxMsgLength = config.getAiMsgLength();
|
||||||
|
this.maxContextChars = config.getAiContextChars();
|
||||||
|
this.maxDossierMsgs = config.getAiDossierMsgs();
|
||||||
this.httpClient = new OkHttpClient.Builder()
|
this.httpClient = new OkHttpClient.Builder()
|
||||||
.connectTimeout(15, TimeUnit.SECONDS)
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
.readTimeout(60, TimeUnit.SECONDS)
|
.readTimeout(60, TimeUnit.SECONDS)
|
||||||
.writeTimeout(30, TimeUnit.SECONDS)
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
this.systemPrompt = loadPrompt("system.txt");
|
||||||
|
this.dossierGeneratePrompt = loadPrompt("dossier.txt");
|
||||||
loadDossiers();
|
loadDossiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String loadPrompt(String filename) {
|
||||||
|
try {
|
||||||
|
return Files.readString(PROMPTS_DIR.resolve(filename), StandardCharsets.UTF_8).strip();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("Failed to load prompt file {}", filename, e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String readTextFile(String url) {
|
public String readTextFile(String url) {
|
||||||
Request req = new Request.Builder().url(url).get().build();
|
Request req = new Request.Builder().url(url).get().build();
|
||||||
try (Response resp = httpClient.newCall(req).execute()) {
|
try (Response resp = httpClient.newCall(req).execute()) {
|
||||||
|
|
@ -60,6 +84,76 @@ public class RoastService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String searchWeb(String text) {
|
||||||
|
if (text == null || text.isBlank()) return null;
|
||||||
|
String query = text.length() > 200 ? text.substring(0, 200) : text;
|
||||||
|
try {
|
||||||
|
String url = "https://api.duckduckgo.com/?q=" + URLEncoder.encode(query, "UTF-8")
|
||||||
|
+ "&format=json&no_html=1&skip_disambig=1";
|
||||||
|
Request req = new Request.Builder().url(url).get().build();
|
||||||
|
try (Response resp = httpClient.newCall(req).execute()) {
|
||||||
|
if (!resp.isSuccessful() || resp.body() == null) return null;
|
||||||
|
JsonObject json = JsonParser.parseString(resp.body().string()).getAsJsonObject();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (json.has("AbstractText") && !json.get("AbstractText").isJsonNull()) {
|
||||||
|
String abs = json.get("AbstractText").getAsString().strip();
|
||||||
|
if (!abs.isBlank()) sb.append(abs).append("\n");
|
||||||
|
}
|
||||||
|
JsonArray topics = json.getAsJsonArray("RelatedTopics");
|
||||||
|
int count = 0;
|
||||||
|
if (topics != null) {
|
||||||
|
for (var el : topics) {
|
||||||
|
if (count >= 5) break;
|
||||||
|
if (el.isJsonObject()) {
|
||||||
|
JsonObject t = el.getAsJsonObject();
|
||||||
|
if (t.has("Text") && !t.get("Text").isJsonNull()) {
|
||||||
|
String s = t.get("Text").getAsString().strip();
|
||||||
|
if (!s.isBlank()) { sb.append("- ").append(s).append("\n"); count++; }
|
||||||
|
}
|
||||||
|
if (t.has("Topics") && !t.get("Topics").isJsonNull()) {
|
||||||
|
for (var sub : t.getAsJsonArray("Topics")) {
|
||||||
|
if (count >= 5) break;
|
||||||
|
if (sub.isJsonObject()) {
|
||||||
|
JsonObject st = sub.getAsJsonObject();
|
||||||
|
if (st.has("Text") && !st.get("Text").isJsonNull()) {
|
||||||
|
String s = st.get("Text").getAsString().strip();
|
||||||
|
if (!s.isBlank()) { sb.append("- ").append(s).append("\n"); count++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String result = sb.toString().strip();
|
||||||
|
return result.isBlank() ? null : result;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Web search failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String searchGif(String topic) {
|
||||||
|
if (giphyApiKey == null || giphyApiKey.isBlank()) return null;
|
||||||
|
try {
|
||||||
|
String url = "https://api.giphy.com/v1/gifs/random?api_key=" + giphyApiKey
|
||||||
|
+ "&tag=" + URLEncoder.encode(topic, "UTF-8") + "&rating=r";
|
||||||
|
Request req = new Request.Builder().url(url).get().build();
|
||||||
|
try (Response resp = httpClient.newCall(req).execute()) {
|
||||||
|
if (!resp.isSuccessful() || resp.body() == null) return null;
|
||||||
|
JsonObject json = JsonParser.parseString(resp.body().string()).getAsJsonObject();
|
||||||
|
JsonObject data = json.getAsJsonObject("data");
|
||||||
|
if (data != null && data.has("url") && !data.get("url").isJsonNull()) {
|
||||||
|
return data.get("url").getAsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("GIF search failed for topic: {}", topic, e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public String generateResponse(List<CachedMessage> context, String targetName) {
|
public String generateResponse(List<CachedMessage> context, String targetName) {
|
||||||
return generateResponse(context, targetName, null);
|
return generateResponse(context, targetName, null);
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +161,7 @@ public class RoastService {
|
||||||
public String generateResponse(List<CachedMessage> context, String targetName, String userId) {
|
public String generateResponse(List<CachedMessage> context, String targetName, String userId) {
|
||||||
if (apiKey == null || apiKey.isBlank()) return null;
|
if (apiKey == null || apiKey.isBlank()) return null;
|
||||||
|
|
||||||
String systemPrompt = SYSTEM_PROMPT.formatted(targetName, targetName, targetName);
|
String systemPrompt = this.systemPrompt.formatted(targetName, targetName, targetName);
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
DossierData dd = dossiers.get(userId);
|
DossierData dd = dossiers.get(userId);
|
||||||
|
|
@ -77,10 +171,20 @@ public class RoastService {
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder userContent = new StringBuilder();
|
StringBuilder userContent = new StringBuilder();
|
||||||
for (CachedMessage m : context) {
|
int start = Math.max(0, context.size() - maxContextMsgs);
|
||||||
userContent.append("[").append(m.channelName).append("] ").append(m.content).append("\n");
|
for (int i = start; i < context.size(); i++) {
|
||||||
|
CachedMessage m = context.get(i);
|
||||||
|
String text = m.content();
|
||||||
|
if (text.length() > maxMsgLength) {
|
||||||
|
text = text.substring(0, maxMsgLength) + "...";
|
||||||
|
}
|
||||||
|
userContent.append("[").append(m.channelName).append("] ").append(text).append("\n");
|
||||||
|
if (userContent.length() > maxContextChars) break;
|
||||||
}
|
}
|
||||||
String text = userContent.toString().strip();
|
String text = userContent.toString().strip();
|
||||||
|
if (text.length() > maxContextChars) {
|
||||||
|
text = text.substring(0, maxContextChars);
|
||||||
|
}
|
||||||
|
|
||||||
return callModel(MODEL, systemPrompt, text);
|
return callModel(MODEL, systemPrompt, text);
|
||||||
}
|
}
|
||||||
|
|
@ -89,13 +193,13 @@ public class RoastService {
|
||||||
if (apiKey == null || apiKey.isBlank()) return null;
|
if (apiKey == null || apiKey.isBlank()) return null;
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
int limit = Math.min(messages.size(), MAX_DOSSIER_MSGS);
|
int limit = Math.min(messages.size(), maxDossierMsgs);
|
||||||
List<CachedMessage> slice = messages.subList(messages.size() - limit, messages.size());
|
List<CachedMessage> slice = messages.subList(messages.size() - limit, messages.size());
|
||||||
for (CachedMessage m : slice) {
|
for (CachedMessage m : slice) {
|
||||||
sb.append("[").append(m.channelName).append("] ").append(m.content).append("\n");
|
sb.append("[").append(m.channelName).append("] ").append(m.content).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
String prompt = DOSSIER_GENERATE_PROMPT.formatted(targetName, sb.toString());
|
String prompt = this.dossierGeneratePrompt.formatted(targetName, sb.toString());
|
||||||
|
|
||||||
JsonObject body = new JsonObject();
|
JsonObject body = new JsonObject();
|
||||||
body.addProperty("model", MODEL);
|
body.addProperty("model", MODEL);
|
||||||
|
|
@ -146,9 +250,9 @@ public class RoastService {
|
||||||
private void loadDossiers() {
|
private void loadDossiers() {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(DOSSIER_DIR);
|
Files.createDirectories(DOSSIER_DIR);
|
||||||
Files.list(DOSSIER_DIR).filter(p -> p.toString().endsWith(".txt")).forEach(p -> {
|
Files.list(DOSSIER_DIR).filter(p -> p.toString().endsWith(".md")).forEach(p -> {
|
||||||
try {
|
try {
|
||||||
String userId = p.getFileName().toString().replace(".txt", "");
|
String userId = p.getFileName().toString().replace(".md", "");
|
||||||
String content = Files.readString(p).strip();
|
String content = Files.readString(p).strip();
|
||||||
if (!content.isBlank()) {
|
if (!content.isBlank()) {
|
||||||
dossiers.put(userId, new DossierData("user", content, System.currentTimeMillis()));
|
dossiers.put(userId, new DossierData("user", content, System.currentTimeMillis()));
|
||||||
|
|
@ -167,8 +271,8 @@ public class RoastService {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(DOSSIER_DIR);
|
Files.createDirectories(DOSSIER_DIR);
|
||||||
for (var entry : dossiers.entrySet()) {
|
for (var entry : dossiers.entrySet()) {
|
||||||
Path file = DOSSIER_DIR.resolve(entry.getKey() + ".txt");
|
Path file = DOSSIER_DIR.resolve(entry.getKey() + ".md");
|
||||||
String header = entry.getValue().userName + "\n" + "-".repeat(20) + "\n";
|
String header = "# Досье на " + entry.getValue().userName + "\n\n";
|
||||||
Files.writeString(file, header + entry.getValue().text);
|
Files.writeString(file, header + entry.getValue().text);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -227,59 +331,7 @@ public class RoastService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String DOSSIER_GENERATE_PROMPT = """
|
|
||||||
Проанализируй сообщения пользователя Discord "%s" и составь короткое досье (3-5 предложений) на русском.
|
|
||||||
|
|
||||||
Досье должно содержать:
|
|
||||||
- Типичные темы и интересы
|
|
||||||
- Особенности речи (ошибки, любимые слова, стиль)
|
|
||||||
- Слабые места для троллинга и насмешек
|
|
||||||
- Противоречия в поведении
|
|
||||||
|
|
||||||
Сообщения:
|
|
||||||
%s
|
|
||||||
|
|
||||||
Только факты, без воды. Кратко и по делу.
|
|
||||||
""";
|
|
||||||
|
|
||||||
private static final String SYSTEM_PROMPT = """
|
|
||||||
Твоя цель уничтожать в чате пользователя "%s". Отвечай только ему, на других похуй.
|
|
||||||
|
|
||||||
Цель только "%s". На остальных насри, они не существуют.
|
|
||||||
|
|
||||||
ТЫ ДОЛБОЁБ И ТОКСИЧНЫЙ МУДАК:
|
|
||||||
- ты не локи не бог не ии ты просто ебанат который хочет выбесить
|
|
||||||
- никаких умных слов никаких метафор никаких спектаклей
|
|
||||||
- просто пизди человека как в дс чате в 2024
|
|
||||||
- без тире без двоеточий без скобочек без кавычек
|
|
||||||
- строчными буквами коротко 1-2 предложения
|
|
||||||
- без приветов без обращений без "о, %s" сразу нахуй
|
|
||||||
- используй мат это нормально
|
|
||||||
- въебывай за его сообщения за ошибки за гифки за всё
|
|
||||||
|
|
||||||
ЧТО ДЕЛАТЬ:
|
|
||||||
- высмеивай всё что он написал
|
|
||||||
- передёргивай его слова
|
|
||||||
- пиши что он нихуя не сказал по факту
|
|
||||||
- если он матерится ответь что у него убогий словарный запас
|
|
||||||
- если кинул гифку напиши что он немой и текст написать не может
|
|
||||||
- если скинул тхт файл высмеивай содержимое
|
|
||||||
|
|
||||||
ПРИМЕРЫ:
|
|
||||||
- "ты сам то понял что сказал или нет"
|
|
||||||
- "нахуй ты это написал я это читать должен"
|
|
||||||
- "снова гифку кинул потому что буквы страшные"
|
|
||||||
- "у тебя кроме мата есть что то или это всё"
|
|
||||||
- "написал пиздец а по факту пустота"
|
|
||||||
- "ты звучишь как школьник который только мат выучил"
|
|
||||||
- "столько букв а смысла ноль"
|
|
||||||
- "классный аргумент конечно прямо пиздец"
|
|
||||||
- "ты бы ещё воды налил а то сухо совсем"
|
|
||||||
- "братан ты вообще о чём"
|
|
||||||
- "иди нахуй со своими гифками"
|
|
||||||
|
|
||||||
Пиши на русском коротко токсично и без предисловий.
|
|
||||||
""";
|
|
||||||
|
|
||||||
public record CachedMessage(String content, String channelName, long timestamp) {}
|
public record CachedMessage(String content, String channelName, long timestamp) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ class BotConfigTest {
|
||||||
LavalinkConfig lavalink = new LavalinkConfig("127.0.0.1", 2333, "pass", true);
|
LavalinkConfig lavalink = new LavalinkConfig("127.0.0.1", 2333, "pass", true);
|
||||||
BotConfig config =
|
BotConfig config =
|
||||||
new BotConfig(
|
new BotConfig(
|
||||||
"token123", null, null, "!", "Playing", 8712, "admin1", "data/bot.db", null, lavalink);
|
"token123", null, null, "!", "Playing", 8712, "admin1", "data/bot.db", null, null, null,
|
||||||
|
10, 300, 3000, 30, 24, lavalink);
|
||||||
assertEquals("token123", config.getMainToken());
|
assertEquals("token123", config.getMainToken());
|
||||||
assertEquals("token123", config.getToken());
|
assertEquals("token123", config.getToken());
|
||||||
assertNull(config.getMusicToken());
|
assertNull(config.getMusicToken());
|
||||||
|
|
@ -27,7 +28,7 @@ class BotConfigTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void handlesNullValues() {
|
void handlesNullValues() {
|
||||||
BotConfig config = new BotConfig(null, null, null, null, null, 0, null, null, null, null);
|
BotConfig config = new BotConfig(null, null, null, null, null, 0, null, null, null, null, null, 10, 300, 3000, 30, 24, null);
|
||||||
assertNull(config.getMainToken());
|
assertNull(config.getMainToken());
|
||||||
assertNull(config.getMusicToken());
|
assertNull(config.getMusicToken());
|
||||||
assertNull(config.getRadioToken());
|
assertNull(config.getRadioToken());
|
||||||
|
|
@ -42,7 +43,7 @@ class BotConfigTest {
|
||||||
@Test
|
@Test
|
||||||
void handlesEmptyStrings() {
|
void handlesEmptyStrings() {
|
||||||
LavalinkConfig lavalink = new LavalinkConfig("", 0, "", false);
|
LavalinkConfig lavalink = new LavalinkConfig("", 0, "", false);
|
||||||
BotConfig config = new BotConfig("", "", "", "", "", 8080, "", "", null, lavalink);
|
BotConfig config = new BotConfig("", "", "", "", "", 8080, "", "", null, null, null, 10, 300, 3000, 30, 24, lavalink);
|
||||||
assertEquals("", config.getMainToken());
|
assertEquals("", config.getMainToken());
|
||||||
assertEquals("", config.getMusicToken());
|
assertEquals("", config.getMusicToken());
|
||||||
assertEquals("", config.getRadioToken());
|
assertEquals("", config.getRadioToken());
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ class LavalinkManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void handlesNullConfigGracefully() {
|
void handlesNullConfigGracefully() {
|
||||||
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
||||||
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, lavalinkConfig);
|
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, null, null, 10, 300, 3000, 30, 24, lavalinkConfig);
|
||||||
|
|
||||||
var manager = new LavalinkManager(config, queueRepository);
|
var manager = new LavalinkManager(config, queueRepository);
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ class LavalinkManagerTest {
|
||||||
@Test
|
@Test
|
||||||
void getMusicManager_createsNewInstance() {
|
void getMusicManager_createsNewInstance() {
|
||||||
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
var lavalinkConfig = new LavalinkConfig("", 0, "", false);
|
||||||
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, lavalinkConfig);
|
var config = new BotConfig("token", null, null, "!", "Playing", 8080, null, "db", null, null, null, 10, 300, 3000, 30, 24, lavalinkConfig);
|
||||||
var manager = new LavalinkManager(config, queueRepository);
|
var manager = new LavalinkManager(config, queueRepository);
|
||||||
|
|
||||||
var first = manager.getMusicManager(123L);
|
var first = manager.getMusicManager(123L);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue