joel
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "^0.0.13",
|
"@ai-sdk/openai": "^0.0.13",
|
||||||
|
"@discordjs/opus": "^0.10.0",
|
||||||
"@discordjs/voice": "^0.18.0",
|
"@discordjs/voice": "^0.18.0",
|
||||||
"@elysiajs/cors": "^1.4.0",
|
"@elysiajs/cors": "^1.4.0",
|
||||||
"@elysiajs/html": "^1.3.0",
|
"@elysiajs/html": "^1.3.0",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"discord.js": "^14.14.1",
|
"discord.js": "^14.14.1",
|
||||||
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
||||||
"elysia": "^1.4.7",
|
"elysia": "^1.4.7",
|
||||||
|
"ffmpeg-static": "^5.2.0",
|
||||||
"hono": "^4.11.7",
|
"hono": "^4.11.7",
|
||||||
"libsql": "^0.3.18",
|
"libsql": "^0.3.18",
|
||||||
"openai": "^4.36.0",
|
"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_MESSAGES = 2;
|
||||||
const CONVERSATION_CONTEXT_MAX_MEDIA_ATTACHMENTS = 3;
|
const CONVERSATION_CONTEXT_MAX_MEDIA_ATTACHMENTS = 3;
|
||||||
const URL_REGEX = /https?:\/\/[^\s<>()]+/gi;
|
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 ResponseTrigger = "free-will" | "summoned" | "classifier" | "none";
|
||||||
type ResponseMode = "free-will" | "mention-only";
|
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;
|
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
|
* 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}`;
|
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)
|
// 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;
|
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>) {
|
async function getOrCreateConnection(message: Message<true>) {
|
||||||
const voiceChannel = message.member?.voice.channel;
|
const voiceChannel = message.member?.voice.channel;
|
||||||
if (!voiceChannel) {
|
if (!voiceChannel) {
|
||||||
@@ -105,6 +132,7 @@ async function getOrCreateConnection(message: Message<true>) {
|
|||||||
adapterCreator: voiceChannel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
adapterCreator: voiceChannel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
|
||||||
selfDeaf: false,
|
selfDeaf: false,
|
||||||
});
|
});
|
||||||
|
attachConnectionLogging(connection, message.guildId, voiceChannel.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await entersState(connection, VoiceConnectionStatus.Ready, READY_TIMEOUT_MS);
|
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;
|
let connection: VoiceConnection | null = null;
|
||||||
|
|
||||||
try {
|
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);
|
connection = await getOrCreateConnection(message);
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
logger.debug("Voiceover skipped (no connection)", {
|
logger.debug("Voiceover skipped (no connection)", {
|
||||||
@@ -157,6 +180,11 @@ export async function speakVoiceover(message: Message<true>, content: string): P
|
|||||||
return;
|
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 player = createAudioPlayer();
|
||||||
const resource = createAudioResource(Readable.from(audio), {
|
const resource = createAudioResource(Readable.from(audio), {
|
||||||
inputType: StreamType.Arbitrary,
|
inputType: StreamType.Arbitrary,
|
||||||
|
|||||||
@@ -4,10 +4,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Message } from "discord.js";
|
import type { Message } from "discord.js";
|
||||||
import { messageRepository } from "../../database";
|
import { memoryRepository, messageRepository, userRepository } from "../../database";
|
||||||
import { createLogger } from "../../core/logger";
|
import { createLogger } from "../../core/logger";
|
||||||
|
|
||||||
const logger = createLogger("Features:MessageLogger");
|
const logger = createLogger("Features:MessageLogger");
|
||||||
|
const ALWAYS_REMEMBER_USER_IDS = new Set(["202112342660481033"]);
|
||||||
|
|
||||||
export const messageLogger = {
|
export const messageLogger = {
|
||||||
/**
|
/**
|
||||||
@@ -22,8 +23,37 @@ export const messageLogger = {
|
|||||||
content: message.content,
|
content: message.content,
|
||||||
user_id: message.author.id,
|
user_id: message.author.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.rememberGuaranteedMessages(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to log message", 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