diff --git a/prod/classes/ai.js b/prod/classes/ai.js deleted file mode 100644 index 3404f6c..0000000 --- a/prod/classes/ai.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @copyright NHCarrigan - * @license Naomi's Public License - * @author Naomi Carrigan - */ -import { Anthropic } from "@anthropic-ai/sdk"; -import { GoogleGenAI, } from "@google/genai"; -import { AttachmentBuilder } from "discord.js"; -/** - * Utility class for generating project information and images. - */ -export class Ai { - anthropic; - gemini; - /** - * Creates a new instance of the Ai class. - * @param anthropicKey - The API key for the Anthropic API. - * @param geminiKey - The API key for the Gemini API. - */ - constructor(anthropicKey, geminiKey) { - this.anthropic = new Anthropic({ - apiKey: anthropicKey, - }); - this.gemini = new GoogleGenAI({ - apiKey: geminiKey, - }); - } - /** - * Generates a list of potential project names and a full body anime girl mascot for the project. - * @param prompt - The user's prompt for the project. - * @returns A message create options object containing the project name, description, and image. - */ - async generateProjectInfo(prompt) { - const projectRequest = await fetch("https://data.nhcarrigan.com/projects.json"); - const projectResponse - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Fetch does not accept a generic. - = (await projectRequest.json()); - const names = await this.generateText(`Your task is to generate a project name based on the user's description. Provide ONLY a list of 1-5 fitting names, and an explanation for why you chose them. Note that project names should be unique. Here's a list of all existing project names: ${projectResponse. - map((p) => { - return p.name; - }). - join(", ")}`, prompt); - const image = await this.generateImage(prompt); - if (image === null) { - return { - content: `Project Name: ${names}\nProject Description: ${prompt}\nSorry, I was unable to generate an image for you.`, - }; - } - return { - content: `Project Name: ${names}\nProject Description: ${prompt}`, - files: [new AttachmentBuilder(image, { name: "avatar.png" })], - }; - } - async generateText(system, prompt) { - const response = await this.anthropic.messages.create({ - // eslint-disable-next-line @typescript-eslint/naming-convention -- SDK requirement. - max_tokens: 1000, - messages: [ - { - content: prompt, - role: "user", - }, - ], - model: "claude-sonnet-4-5-20250929", - system: system, - }); - const text = response.content. - filter((c) => { - return c.type === "text"; - }). - map((c) => { - return c.text; - }). - join(""); - return text; - } - async generateImage(prompt) { - const response = await this.gemini.models.generateContent({ - config: { - imageConfig: { aspectRatio: "3:4" }, - systemInstruction: "Your task is to generate a full body anime girl mascot for this project. This means the full character should be visible. The image should have a white background. NEVER include text in the image, no text anywhere at all. The project description is provided by the user.", - }, - contents: prompt, - model: "gemini-2.5-flash-image", - }); - const image = response.candidates?.[0]?.content?.parts?.find((p) => { - return Boolean(p.inlineData); - }); - const base64 = image?.inlineData?.data; - if (base64 === undefined) { - return null; - } - return Buffer.from(base64, "base64"); - } -} diff --git a/prod/index.js b/prod/index.js deleted file mode 100644 index 1eeb6ca..0000000 --- a/prod/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * @copyright NHCarrigan - * @license Naomi's Public License - * @author Naomi Carrigan - */ -import { Logger } from "@nhcarrigan/logger"; -import { Client, Events, GatewayIntentBits } from "discord.js"; -import { Ai } from "./classes/ai.js"; -if (process.env.ANTHROPIC_API_KEY === undefined - || process.env.GEMINI_API_KEY === undefined - || process.env.LOG_TOKEN === undefined - || process.env.DISCORD_TOKEN === undefined) { - throw new Error(`ANTHROPIC_API_KEY, GEMINI_API_KEY, LOG_TOKEN, and DISCORD_TOKEN must be set.`); -} -const ai = new Ai(process.env.ANTHROPIC_API_KEY, process.env.GEMINI_API_KEY); -const logger = new Logger("Nomena", process.env.LOG_TOKEN); -const bot = new Client({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - ], -}); -bot.once("ready", () => { - void logger.log("debug", "Nomena is online!"); -}); -// eslint-disable-next-line @typescript-eslint/no-misused-promises -- Lazy. -bot.on(Events.MessageCreate, async (message) => { - if (!message.mentions.has("1433657054433771621", { - ignoreEveryone: true, - ignoreRepliedUser: true, - ignoreRoles: true, - })) { - return; - } - if (message.author.id !== "465650873650118659") { - await message.reply("Sorry, I can only generate project ideas for Naomi."); - } - await message.channel.sendTyping(); - const prompt = message.content.replace(/<@!?1433657054433771621>/, "").trim(); - const projectInfo = await ai.generateProjectInfo(prompt); - await message.reply(projectInfo); -}); -await bot.login(process.env.DISCORD_TOKEN);