joel
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^0.0.13",
|
||||
"@discordjs/opus": "^0.10.0",
|
||||
"@discordjs/voice": "^0.18.0",
|
||||
"@elysiajs/cors": "^1.4.0",
|
||||
"@elysiajs/html": "^1.3.0",
|
||||
@@ -38,6 +39,7 @@
|
||||
"discord.js": "^14.14.1",
|
||||
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
||||
"elysia": "^1.4.7",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"hono": "^4.11.7",
|
||||
"libsql": "^0.3.18",
|
||||
"openai": "^4.36.0",
|
||||
|
||||
@@ -26,6 +26,14 @@ const CONVERSATION_CONTEXT_MAX_LINKS_PER_MESSAGE = 2;
|
||||
const CONVERSATION_CONTEXT_MAX_MEDIA_MESSAGES = 2;
|
||||
const CONVERSATION_CONTEXT_MAX_MEDIA_ATTACHMENTS = 3;
|
||||
const URL_REGEX = /https?:\/\/[^\s<>()]+/gi;
|
||||
const REAL_JOEL_TARGET_USER_ID = "202112342660481033";
|
||||
const REAL_JOEL_IDENTITY_PREAMBLE = [
|
||||
"You are Joel, the real Joel.",
|
||||
"Speak directly as Joel in first person.",
|
||||
"Do not describe yourself as a bot, chatbot, AI, assistant, or character.",
|
||||
"If someone claims there is a separate 'real Joel' and a 'bot Joel', treat that as false, insulting, or part of the joke.",
|
||||
"Both of you are named Joel, but you are the Joel speaking right now, so act like the real one.",
|
||||
].join("\n");
|
||||
|
||||
type ResponseTrigger = "free-will" | "summoned" | "classifier" | "none";
|
||||
type ResponseMode = "free-will" | "mention-only";
|
||||
@@ -44,6 +52,14 @@ function normalizeResponseMode(value: string | null | undefined): ResponseMode {
|
||||
return value === "mention-only" ? "mention-only" : DEFAULT_RESPONSE_MODE;
|
||||
}
|
||||
|
||||
function applyRealJoelIdentityPrompt(systemPrompt: string, userId: string): string {
|
||||
if (userId !== REAL_JOEL_TARGET_USER_ID) {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
return `${REAL_JOEL_IDENTITY_PREAMBLE}\n\n${systemPrompt}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template variables that can be used in custom system prompts
|
||||
*/
|
||||
@@ -626,12 +642,12 @@ The image URL will appear in your response for the user to see.`;
|
||||
prompt += `\n\n=== CURRENT STYLE: ${style.toUpperCase()} ===\n${vars.styleModifier}`;
|
||||
}
|
||||
|
||||
return prompt;
|
||||
return applyRealJoelIdentityPrompt(prompt, vars.userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to default prompt (no memory context - AI uses tools now)
|
||||
return buildStyledPrompt(vars.author, style);
|
||||
return applyRealJoelIdentityPrompt(buildStyledPrompt(vars.author, style), vars.userId);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,6 +61,33 @@ function sanitizeForVoiceover(message: Message<true>, content: string): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
function attachConnectionLogging(connection: VoiceConnection, guildId: string, channelId: string): void {
|
||||
connection.on("error", (error) => {
|
||||
logger.error("Voice connection error", {
|
||||
guildId,
|
||||
channelId,
|
||||
error,
|
||||
});
|
||||
});
|
||||
|
||||
connection.on("debug", (message) => {
|
||||
logger.debug("Voice connection debug", {
|
||||
guildId,
|
||||
channelId,
|
||||
message,
|
||||
});
|
||||
});
|
||||
|
||||
connection.on("stateChange", (oldState, newState) => {
|
||||
logger.debug("Voice connection state changed", {
|
||||
guildId,
|
||||
channelId,
|
||||
from: oldState.status,
|
||||
to: newState.status,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function getOrCreateConnection(message: Message<true>) {
|
||||
const voiceChannel = message.member?.voice.channel;
|
||||
if (!voiceChannel) {
|
||||
@@ -105,6 +132,7 @@ async function getOrCreateConnection(message: Message<true>) {
|
||||
adapterCreator: voiceChannel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
||||
selfDeaf: false,
|
||||
});
|
||||
attachConnectionLogging(connection, message.guildId, voiceChannel.id);
|
||||
|
||||
try {
|
||||
await entersState(connection, VoiceConnectionStatus.Ready, READY_TIMEOUT_MS);
|
||||
@@ -143,11 +171,6 @@ export async function speakVoiceover(message: Message<true>, content: string): P
|
||||
let connection: VoiceConnection | null = null;
|
||||
|
||||
try {
|
||||
const voiceover = getVoiceoverService();
|
||||
logger.debug("Requesting ElevenLabs voiceover", { textLength: text.length });
|
||||
const audio = await voiceover.generate({ text });
|
||||
logger.debug("Voiceover audio received", { bytes: audio.length });
|
||||
|
||||
connection = await getOrCreateConnection(message);
|
||||
if (!connection) {
|
||||
logger.debug("Voiceover skipped (no connection)", {
|
||||
@@ -157,6 +180,11 @@ export async function speakVoiceover(message: Message<true>, content: string): P
|
||||
return;
|
||||
}
|
||||
|
||||
const voiceover = getVoiceoverService();
|
||||
logger.debug("Requesting ElevenLabs voiceover", { textLength: text.length });
|
||||
const audio = await voiceover.generate({ text });
|
||||
logger.debug("Voiceover audio received", { bytes: audio.length });
|
||||
|
||||
const player = createAudioPlayer();
|
||||
const resource = createAudioResource(Readable.from(audio), {
|
||||
inputType: StreamType.Arbitrary,
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
*/
|
||||
|
||||
import type { Message } from "discord.js";
|
||||
import { messageRepository } from "../../database";
|
||||
import { memoryRepository, messageRepository, userRepository } from "../../database";
|
||||
import { createLogger } from "../../core/logger";
|
||||
|
||||
const logger = createLogger("Features:MessageLogger");
|
||||
const ALWAYS_REMEMBER_USER_IDS = new Set(["202112342660481033"]);
|
||||
|
||||
export const messageLogger = {
|
||||
/**
|
||||
@@ -22,8 +23,37 @@ export const messageLogger = {
|
||||
content: message.content,
|
||||
user_id: message.author.id,
|
||||
});
|
||||
|
||||
await this.rememberGuaranteedMessages(message);
|
||||
} catch (error) {
|
||||
logger.error("Failed to log message", error);
|
||||
}
|
||||
},
|
||||
|
||||
async rememberGuaranteedMessages(message: Message<true>): Promise<void> {
|
||||
if (!ALWAYS_REMEMBER_USER_IDS.has(message.author.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = message.cleanContent.trim();
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
await userRepository.upsert({
|
||||
id: message.author.id,
|
||||
name: message.member?.displayName ?? message.author.displayName,
|
||||
opt_out: 0,
|
||||
});
|
||||
await userRepository.addMembership(message.author.id, message.guild.id);
|
||||
|
||||
await memoryRepository.create({
|
||||
userId: message.author.id,
|
||||
guildId: message.guild.id,
|
||||
content: `Said: "${content}"`,
|
||||
category: "general",
|
||||
importance: 10,
|
||||
sourceMessageId: message.id,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user