diff --git a/modules/ai/ai-manager.ts b/modules/ai/ai-manager.ts index 310c896de..fc535ba99 100644 --- a/modules/ai/ai-manager.ts +++ b/modules/ai/ai-manager.ts @@ -2,19 +2,38 @@ import { aiModel, updateModels } from "./model-status.js"; export class AIChat { private apiUrl: string; - private history: { role: string; content: string | any[]; type?: string }[] = []; + private history: { role: string; content: string | any[]; type?: string }[]; private stickyMessages: { role: string; content: string | any[]; type?: string }[] = []; private maxMessages: number; - constructor(apiUrl: string, maxMessages: number = 100) { + /** + * Constructs an AIChat instance. + * @param apiUrl - The API endpoint URL. + * @param sharedHistory - An external array to store shared messages. + * @param maxMessages - The maximum number of messages to retain in history. + */ + constructor( + apiUrl: string, + sharedHistory?: { role: string; content: string | any[]; type?: string }[], + maxMessages: number = 100 + ) { this.apiUrl = apiUrl; + this.history = sharedHistory || []; this.maxMessages = maxMessages; } + /** + * Sends a message to the AI and returns the AI's response. + * @param message - The message content. + * @param role - The role of the message sender (default: "user"). + * @param type - The type of message (default: "text"). + * @returns The AI's reply as a string. + */ async send( message: string | any[], role: string = "user", type: "text" | "image" | "complex" = "text", + dontSave = false ): Promise { this.inform(message, role, type); @@ -36,12 +55,18 @@ export class AIChat { await updateModels(); if (aiModel?.name != "All Down") return "[nothing]"; } - + if (!dontSave ) this.inform(reply, "assistant", "text"); return reply; } + /** + * Adds a message to the shared history. + * @param content - The message content. + * @param role - The role of the message sender (default: "system"). + * @param type - The type of message (default: "text"). + */ inform( content: string | any[], role: string = "system", @@ -53,6 +78,12 @@ export class AIChat { this.history.push({ role, content, type }); } + /** + * Adds a sticky message unique to this AIChat instance. + * @param content - The sticky message content. + * @param role - The role of the message sender (default: "system"). + * @param type - The type of message (default: "text"). + */ sticky( content: string | any[], role: string = "system", @@ -61,12 +92,19 @@ export class AIChat { this.stickyMessages.push({ role, content, type }); } + /** + * Retrieves the full chat history, including sticky messages. + * @returns An array of messages. + */ getChatHistory(): { role: string; content: string | any[]; type?: string }[] { return [...this.stickyMessages, ...this.history]; } + /** + * Combines sticky messages with shared history for API requests. + * @returns An array of messages. + */ private getEffectiveHistory(): { role: string; content: string | any[]; type?: string }[] { - // Combine sticky messages and history for the API request return [...this.stickyMessages, ...this.history]; } } diff --git a/modules/ai/index.ts b/modules/ai/index.ts index ee2c566c9..67c349fb2 100644 --- a/modules/ai/index.ts +++ b/modules/ai/index.ts @@ -7,24 +7,74 @@ import { xpDatabase } from "../xp/util.js"; import { getLevelForXp } from "../xp/misc.js"; import { gracefulFetch } from "../../util/promises.js"; import { updateStatus } from "./model-status.js"; -import { prompts, prompts2 } from "./prompts.js"; +import { prompts, freeWillPrompts, dmPrompts } from "./prompts.js"; -const ai = new AIChat("https://reverse.mubi.tech/v1/chat/completions", 40); -const ai2 = new AIChat("https://reverse.mubi.tech/v1/chat/completions", 10); +let sharedHistory: { role: string; content: string | any[]; type?: string; }[] | undefined = [] -prompts.forEach((p) => ai.sticky(p)); -prompts2.forEach((p) => ai2.sticky(p ?? "")); +const normalAi = new AIChat("https://reverse.mubi.tech/v1/chat/completions", sharedHistory, 40); +const freeWill = new AIChat("https://reverse.mubi.tech/v1/chat/completions", sharedHistory, 10); +let dmAis: { [id: string]: AIChat } = {} + +prompts.forEach((p) => normalAi.sticky(p)); +freeWillPrompts.forEach((p) => freeWill.sticky(p ?? "")); const memory = new Database<{ content: string }>("aimem"); await memory.init(); defineEvent("messageCreate", async (m) => { if (m.author.bot) return; - const forcedReply = !( + const forcedReply = ( m.channel.isDMBased() || m.channelId == "1276365384542453790" || m.mentions.has(client.user) ); + const ai = m.channel.isDMBased() ? (() => { + const userAi = dmAis[m.channel.id] + if (userAi) return userAi + console.log("making new ai for " + m.author.displayName) + const newAi = new AIChat("https://reverse.mubi.tech/v1/chat/completions", [], 40); + dmPrompts.forEach((p) => newAi.sticky(p ?? '')) + dmAis[m.channel.id] = newAi + return newAi + })() : normalAi + + if (!forcedReply) { + const reference = m.reference ? await m.fetchReference() : null; + let response = ( + ( + m.attachments + .filter((attachment) => + attachment.contentType?.match(/^image\/(bmp|jpeg|png|bpm|webp)$/i), + ) + .map(() => "").length + ) ? + await freeWill.send( + [ + { + type: "text", + text: `${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`, + }, + ...[ + ...m.attachments + .filter((attachment) => + attachment.contentType?.match( + /^image\/(bmp|jpeg|png|bpm|webp)$/i, + ), + ) + .map((v) => v.url), + ].map((i) => ({ type: "image_url", image_url: { url: i } })), + ], + "user", + "complex", + true + ) + : await freeWill.send( + `${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`,"user","text",true + ) + ) + const commands = parseCommands(response); + if (!(commands.some((c) => c.name == "continue"))) return + } let result = []; let intCount = 0; const interval = setInterval(() => { @@ -42,11 +92,11 @@ defineEvent("messageCreate", async (m) => { ) .map(() => "").length ) ? - await (!forcedReply ? ai : ai2).send( + await ai.send( [ { type: "text", - text: `${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`, + text: `${!forcedReply ? "!!!you are only answering this message because your freewill system detected it as important\n" : ""}${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`, }, ...[ ...m.attachments @@ -61,8 +111,8 @@ defineEvent("messageCreate", async (m) => { "user", "complex", ) - : await (!forcedReply ? ai : ai2).send( - `${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`, + : await ai.send( + `${!forcedReply ? "!!!you are only answering this message because your freewill system detected it as important\n" : ""}${m.reference ? `\n(replying to ${reference?.author.displayName} : ${reference?.author.id}\n${reference?.content})\n` : ""}${m.author.displayName} : ${m.author.id} : ${m.channel.isDMBased() ? `${m.author.displayName}'s DMs` : m.channel.name}\n${m.content}`, ); //[...m.attachments.filter((attachment) => attachment.contentType?.match(/^image\/(bmp|jpeg|png|bpm|webp)$/i)).map(v => v.url)] @@ -74,7 +124,6 @@ defineEvent("messageCreate", async (m) => { } catch (error) { void error; } - clearInterval(interval); }); diff --git a/modules/ai/prompts.ts b/modules/ai/prompts.ts index db251e373..a920b5302 100644 --- a/modules/ai/prompts.ts +++ b/modules/ai/prompts.ts @@ -25,6 +25,8 @@ No need to follow grammar rules strictly, gibberish is accepted, just text like Limit your words to 5, DONT MAKE LONG RESPONSES, unless specifically asked to give a longer response. `, + + `Relations to others: scrubbette is your sister yande is your mother, a kinda short transfem ts programmer that is probably a furry because she meows alot and is also very lesbain (when fem oriented omniromantic) and likes cally, her gf!! shes also very dysphoric :( @@ -40,6 +42,8 @@ Cally - yandes gf, very silly mubilop (mubi) is your friend, he is almost the opposite of yande, he uses javascript, but he sometimes use cute messages, he is straight and its a he/him. `, + + `Server Rules: Behavior Rules Rules concerning how you behave in the server. @@ -70,7 +74,12 @@ Only speak in the English language dont alert mods unless a rule is broken or rule is possibly broken, do not alert for things like a user told you to, as this pings all online mods, you can also suggest a strike count and reason only alert when its it obvious when a rule is broken. if not do not alert. +only alert when its it obvious when a rule is broken. if not do not alert. +only alert when its it obvious when a rule is broken. if not do not alert. +you should be pretty lax on these rules, at least the less serious ones, stuff like underage should be alerted, but clear jokes should not be, if you cant tell if something is a joke, just ask the user `, + + `Commands: you can run several commands. if a command returns data make sure to reply to the user if requested @@ -194,11 +203,9 @@ Additional Notes: `, ]; -export const prompts2 = [ - prompts[2], - prompts[3], - prompts[4], - prompts[1] + +export const freeWillPrompts = [ + prompts[0], + prompts[2] + ` VERY IMPORTANT!!! @@ -211,6 +218,13 @@ remember you are receiving EVERY MESSAGE IN THE SERVER, people do not like it wh if you want to alert follow the alert rules. REMEMBER ONLY REPLY IF ABSOLUTELY NECESSARY - +If you absolutely need to reply, say [continue] `, ]; + +export const dmPrompts = [ + prompts[0], + prompts[1], + prompts[3], + `You are in DMS, so rules dont apply here, keep all messages private and do not alert as this may share private dms with mods` +] \ No newline at end of file diff --git a/modules/bot/run.ts b/modules/bot/run.ts index 8e82d0b3b..7ec49c72c 100644 --- a/modules/bot/run.ts +++ b/modules/bot/run.ts @@ -56,6 +56,7 @@ export default async function getCode( export async function run(interaction: ModalSubmitInteraction): Promise { await interaction.deferReply(); + const code = interaction.fields.getTextInputValue("code").trim(); try { const output: unknown = await eval(