From 1bbdabbb3e4ad0bbbecc9547492230fd01059fe0 Mon Sep 17 00:00:00 2001 From: milo Date: Thu, 17 Apr 2025 21:13:23 +0200 Subject: [PATCH] new models and embed to --- .gitignore | 1 + index.js | 269 +++++++++++++++++++++++++++++++++------------- package-lock.json | 244 +++++++++++++++++++++++++++++++++++++++++ package.json | 2 + utils.js | 40 +++++-- 5 files changed, 473 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index 37d7e73..4ee630d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .env +flopobot.db diff --git a/index.js b/index.js index 35bd053..cf04b95 100644 --- a/index.js +++ b/index.js @@ -93,7 +93,7 @@ async function getOnlineUsersWithRole(guild_id=process.env.GUILD_ID, role_id=pro } } -// Login to Discord using your bot token (set BOT_TOKEN in your .env file) +// Login to Discord using bot token (optional) client.login(process.env.BOT_TOKEN); // Listen for message events @@ -101,18 +101,12 @@ client.on('messageCreate', async (message) => { // Ignore messages from bots to avoid feedback loops if (message.author.bot) return; - // Check if the message content includes the word "quoi" (case-insensitive) - if (message.content.toLowerCase().includes("quoi")) { - let prob = Math.random() - console.log(`feur ${prob}`) - if (prob < process.env.FEUR_PROB) { - // Send a message "feur" to the same channel - message.channel.send(`feur`) - .catch(console.error); - } - } - else if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { - //let akhyAuthor = akhysData.get(message.author.id) + + + if (message.content.toLowerCase().startsWith(`<@${process.env.APP_ID}>`) || message.mentions.repliedUser?.id === process.env.APP_ID) { + let startTime = Date.now() + console.log('-------------------------------') + console.log('Request received : ' + startTime) let akhyAuthor = await getUser.get(message.author.id) const now = Date.now(); @@ -123,7 +117,7 @@ client.on('messageCreate', async (message) => { if (updatedTimestamps.length >= MAX_REQUESTS_PER_INTERVAL) { console.log(akhyAuthor.warned ? `${message.author.username} is restricted : ${updatedTimestamps}` : `Rate limit exceeded for ${message.author.username}`); - if (!akhyAuthor.warned) message.channel.send(`T'abuses fréro, attends un peu ⏳`); + if (!akhyAuthor.warned) message.reply(`T'abuses fréro, attends un peu ⏳`); // akhyAuthor.warned = true; // akhyAuthor.warns++; // akhyAuthor.allTimeWarns++; @@ -164,11 +158,6 @@ client.on('messageCreate', async (message) => { // Track this new usage updatedTimestamps.push(now); requestTimestamps.set(akhyAuthor.id, updatedTimestamps); - -// Proceed with your logic - // akhyAuthor.warned = false; - // akhyAuthor.warns = 0; - // akhyAuthor.totalRequests++; await updateManyUsers([ { id: akhyAuthor.id, @@ -183,78 +172,130 @@ client.on('messageCreate', async (message) => { akhyAuthor = await getUser.get(akhyAuthor.id) try { - // Fetch last 10 messages from the channel - const fetched = await message.channel.messages.fetch({ limit: 50 }); + // Fetch last messages from the channel + const fetched = await message.channel.messages.fetch({ limit: 100 }); const messagesArray = Array.from(fetched.values()).reverse(); // oldest to newest + console.log('after Discord fetch : ' + startTime + ', ' + (Date.now() - startTime)) const requestMessage = message.content.replace(`<@${process.env.APP_ID}>`, '') - // Map to OpenAI format + // Map to OpenAI/Gemini format + console.log(process.env.MODEL) let formatted = messagesArray.map(msg => ({ role: msg.author.bot ? "assistant" : "user", content: `${msg.author.id} | ${msg.content} | ${msg.id}`, })); - - const members = await getOnlineUsersWithRole(process.env.GUILD_ID, process.env.VOTING_ROLE_ID); const allAkhys = await getAllUsers.all() + if (process.env.MODEL === 'OpenAI' || process.env.MODEL === 'Gemini') { + - formatted.push({ - role: 'developer', - content: `Les prochaines entrées sont les différents utilisateurs présents. Chaque entrée comporte l'id, le nom sur le serveur et le nom sur discord d'un utilisateur`, - }) - // members.forEach(member => { - // formatted.push({ - // role: 'developer', - // content: `${member.user.id} : ${member.user.global_name}, ${member.user.username}`, - // }) - // }) - allAkhys.forEach(akhy => { + formatted.push({ + role: 'developer', + content: `Les prochaines entrées sont les différents utilisateurs présents. Chaque entrée comporte l'id, le nom sur le serveur et le nom sur discord d'un utilisateur`, + }) + allAkhys.forEach(akhy => { + formatted.push({ + role: 'developer', + content: `${akhy.id} : ${akhy.globalName}, ${akhy.username}`, + }) + }) + + // Add a final user prompt to clarify the request + formatted.push( + { + role: "developer", + content: "Sachant que chaque message d'utilisateur comporte l'id de l'utilisateur ayant écrit le message au début de l'entrée, le contenu du message, et l'id du message pour finir (formaté comme suit : user_id | content | message_id, par contre ne formatte jamais tes réponses ainsi, met juste la partie content). Adopte une attitude détendue et répond comme si tu participais à la conversation, essaye d'imiter au mieux la façon de parler des utilisateurs et/ou d'un utilisateur de twitter (X). N'hésites pas à utiliser des abréviations mais sans en abuser. Fait plutôt court, une ou deux phrases maximum " + }, + { + role: "developer", + content: `L'utilisateur qui s'adresse a toi dans la prochaine phrase est : ${akhyAuthor.id}, si le message de l'utilisateur est vide et/ou ne comporte que ton ID, agis comme s'il voulait savoir si tu es présent, et réponds de manière très très courte dans ce cas, 2 ou 3 mots` + }, + { + role: "user", + content: requestMessage.length > 1 ? requestMessage : 'Répond de manière approprié aux derniers messages de cette conversation. Sans prendre en compte mon dernier message vide', + }, + { + role: 'developer', + content: message.mentions.repliedUser?.id ? `La phrase de l'utilisateur répond à un message de ${message.mentions.repliedUser?.id === process.env.APP_ID ? 'toi-même' : message.mentions.repliedUser?.id}, l'id du message est : ${message.reference?.messageId}` : '', + }, + { + role: "developer", + content: "Considère chaque messages d'utilisateurs afin d'établir un contexte de la situation, si tu ne comprends pas le dernière demande utilisateur analyse le reste des demandes." + }, + { + role: "developer", + content: 'En te basant sur la liste des utilisateurs et des id utilisateurs présent au début de chaque message, lorsque tu parles d\'un utilisateur présent dans cette liste que ce soit via son \'user.global_name\', son \'user.username\' ou son \'user.id\' , identifie le avec son \'user.id\' plutôt que d\'utiliser son \'user.global_name\', ça doit ressembler à ça en remplaçant \'ID\' <@ID>. Fait le la première fois que tu évoques l\'utilisateur mais donne juste son \'user.global_name\' ensuite', + }, + { + role: "developer", + content: `Ton id est : ${process.env.APP_ID}, évite de l'utiliser et ne formatte pas tes messages avec ton propre id, si jamais tu utilises un id formatte le comme suit : <@ID>, en remplacant ID par l'id. Ton username et global_name sont : ${process.env.APP_NAME}` + }); + } else if (process.env.MODEL === 'Mistral') { + // Map to Mistral format formatted.push({ - role: 'developer', - content: `${akhy.id} : ${akhy.globalName}, ${akhy.username}`, - }) - }) + role: 'system', + content: `Les prochaines entrées sont les différents utilisateurs présents. Chaque entrée comporte l'id, le nom sur le serveur et le nom sur discord d'un utilisateur`, + }); - // Add a final user prompt to clarify the request - formatted.push( + allAkhys.forEach(akhy => { + formatted.push({ + role: 'system', + content: `${akhy.id} : ${akhy.globalName}, ${akhy.username}`, + }); + }); + + formatted.push( { - role: "developer", - content: "Sachant que chaque message d'utilisateur comporte l'id de l'utilisateur ayant écrit le message au début de l'entrée, le contenu du message, et l'id du message pour finir (formaté comme suit : user_id | content | message_id, par contre ne formatte jamais tes réponses ainsi, met juste la partie content). Adopte une attitude détendue et répond comme si tu participais à la conversation, essaye d'imiter au mieux la façon de parler des utilisateurs et/ou d'un utilisateur de twitter (X). N'hésites pas à utiliser des abréviations mais sans en abuser. Fait plutôt court, une ou deux phrases maximum " + role: "system", + content: "Sachant que chaque message d'utilisateur comporte l'id de l'utilisateur ayant écrit le message au début de l'entrée, le contenu du message, et l'id du message pour finir (formaté comme suit : user_id | content | message_id, par contre ne formatte jamais tes réponses ainsi, met juste la partie content). Adopte une attitude détendue et répond comme si tu participais à la conversation, essaye d'imiter au mieux la façon de parler des utilisateurs et/ou d'un utilisateur de twitter (X). N'hésites pas à utiliser des abréviations mais sans en abuser. Fait plutôt court, une ou deux phrases maximum." }, { - role: "developer", - content: `L'utilisateur qui s'adresse a toi dans la prochaine phrase est : ${akhyAuthor.id}, si le message de l'utilisateur est vide et/ou ne comporte que ton ID, agis comme s'il voulait savoir si tu es présent, et réponds de manière très très courte dans ce cas, 2 ou 3 mots` + role: "system", + content: `L'utilisateur qui s'adresse a toi dans la prochaine phrase est : ${akhyAuthor.id}, si le message de l'utilisateur est vide et/ou ne comporte que ton ID, agis comme s'il voulait savoir si tu es présent, et réponds de manière très très courte dans ce cas, 2 ou 3 mots.` }, { role: "user", content: requestMessage.length > 1 ? requestMessage : 'Répond de manière approprié aux derniers messages de cette conversation. Sans prendre en compte mon dernier message vide', }, { - role: 'developer', + role: 'system', content: message.mentions.repliedUser?.id ? `La phrase de l'utilisateur répond à un message de ${message.mentions.repliedUser?.id === process.env.APP_ID ? 'toi-même' : message.mentions.repliedUser?.id}, l'id du message est : ${message.reference?.messageId}` : '', }, { - role: "developer", + role: "system", content: "Considère chaque messages d'utilisateurs afin d'établir un contexte de la situation, si tu ne comprends pas le dernière demande utilisateur analyse le reste des demandes." }, { - role: "developer", + role: "system", content: 'En te basant sur la liste des utilisateurs et des id utilisateurs présent au début de chaque message, lorsque tu parles d\'un utilisateur présent dans cette liste que ce soit via son \'user.global_name\', son \'user.username\' ou son \'user.id\' , identifie le avec son \'user.id\' plutôt que d\'utiliser son \'user.global_name\', ça doit ressembler à ça en remplaçant \'ID\' <@ID>. Fait le la première fois que tu évoques l\'utilisateur mais donne juste son \'user.global_name\' ensuite', }, { - role: "developer", + role: "system", content: `Ton id est : ${process.env.APP_ID}, évite de l'utiliser et ne formatte pas tes messages avec ton propre id, si jamais tu utilises un id formatte le comme suit : <@ID>, en remplacant ID par l'id. Ton username et global_name sont : ${process.env.APP_NAME}` - }); + } + ); + } // 'Je chill zbi (ntm a vouloir gaspiller les token)' // IA en pause // await gork(formatted); IA en marche const reply = await gork(formatted); + console.log('after AI fetch : ' + startTime + ', ' + (Date.now() - startTime)) + // Send response to the channel - await message.channel.send(reply); + await message.reply(reply); } catch (err) { console.error("Error fetching or sending messages:", err); - await message.channel.send("Oups, y'a eu un problème!"); + await message.reply("Oups, y'a eu un problème!"); + } + } + else if (message.content.toLowerCase().includes("quoi")) { + let prob = Math.random() + console.log(`feur ${prob}`) + if (prob < process.env.FEUR_PROB) { + // Send a message "feur" to the same channel + message.channel.send(`feur`) + .catch(console.error); } } else if (message.content.toLowerCase().startsWith('membres')) { @@ -364,7 +405,7 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun console.log(name) // "test" command - if (name === 'test') { + /*if (name === 'test') { // Send a message into the channel where command was triggered from return res.send({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, @@ -410,7 +451,7 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun ], }, }); - } + }*/ // 'timeout' command if (name === 'timeout') { @@ -422,10 +463,16 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun const akhy = req.body.data.options[0].value; const time = req.body.data.options[1].value; + const guild = await client.guilds.fetch(req.body.guild_id); + const fromMember = await guild.members.fetch(userId); + const toMember = await guild.members.fetch(akhy); + // Save the poll information along with channel ID so we can notify later activePolls[id] = { id: userId, + username: fromMember.user, toUserId: akhy, + toUsername: toMember.user, time: time, time_display: formatTime(time), for: 0, @@ -466,15 +513,31 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun { method: 'PATCH', body: { - content: - `> Le vote pour timeout <@${poll.toUserId}> pendant ${poll.time_display} a échoué 😔\n > \n` + - `> Il manquait **${votesNeeded}** vote(s)\n`, + embeds: [ + { + title: `Le vote pour timeout ${poll.toUsername} pendant ${poll.time_display} a échoué 😔`, + description: `Il manquait **${votesNeeded}** vote(s)`, + fields: [ + { + name: 'Pour', + value: '✅ ' + poll.for, + inline: true, + }, + { + name: 'Temps restant', + value: '⏳ ' + countdownText, + inline: false, + }, + ], + color: 0xF2F3F3, // You can set the color of the embed + }, + ], components: [], }, } ); } catch (err) { - console.error('Error updating countdown:', err); + console.error('Error sending message', err); } clearInterval(countdownInterval); return; @@ -486,11 +549,25 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun { method: 'PATCH', body: { - content: - `> <@${poll.id}> propose de timeout <@${poll.toUserId}> pendant ${poll.time_display}\n > \n` + - `> ✅ **${poll.for}**\n > \n` + - `> Il manque **${votesNeeded}** vote(s)\n` + - `> ⏳ Temps restant : ${countdownText}\n`, + embeds: [ + { + title: `Timeout`, + description: `**${poll.username}** propose de timeout **${poll.toUsername}** pendant ${poll.time_display}\nIl manque **${votesNeeded}** vote(s)`, + fields: [ + { + name: 'Pour', + value: '✅ ' + poll.for, + inline: true, + }, + { + name: 'Temps restant', + value: '⏳ ' + countdownText, + inline: false, + }, + ], + color: 0xF2F3F3, // You can set the color of the embed + }, + ], components: [ { type: MessageComponentTypes.ACTION_ROW, @@ -526,10 +603,25 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun return res.send({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { - content: `> <@${activePolls[id].id}> propose de timeout <@${activePolls[id].toUserId}> pendant ${activePolls[id].time_display}\n > \n` + - `> ✅ **${activePolls[id].for}**\n > \n` + - `> Il manque **${votesNeeded}** vote(s)\n` + - `> ⏳ Temps restant : ${countdownText}\n`, + embeds: [ + { + title: `Timeout`, + description: `**${activePolls[id].username}** propose de timeout **${activePolls[id].toUsername}** pendant ${activePolls[id].time_display}\nIl manque **${votesNeeded}** vote(s)`, + fields: [ + { + name: 'Pour', + value: '✅ ' + activePolls[id].for, + inline: true, + }, + { + name: 'Temps restant', + value: '⏳ ' + countdownText, + inline: false, + }, + ], + color: 0xF2F3F3, // You can set the color of the embed + }, + ], components: [ { type: MessageComponentTypes.ACTION_ROW, @@ -695,15 +787,25 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun if (poll.for >= poll.requiredMajority) { try { // Build the updated poll message content - const updatedContent = `> <@${poll.id}> propose de timeout <@${poll.toUserId}> pendant ${poll.time_display}\n > \n` + - `> ✅ **${poll.for}** votes au total\n\n`; - await DiscordRequest( poll.endpoint, { method: 'PATCH', body: { - content: updatedContent, + embeds: [ + { + title: `Timeout`, + description: `Proposition de timeout **${activePolls[id].toUsername}** pendant ${activePolls[id].time_display}`, + fields: [ + { + name: 'Votes totaux', + value: '✅ ' + activePolls[id].for, + inline: true, + }, + ], + color: 0xF2F3F3, // You can set the color of the embed + }, + ], components: [], // remove buttons }, } @@ -748,17 +850,31 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun const countdownText = `**${minutes}m ${seconds}s** restantes`; try { // Build the updated poll message content - const updatedContent = `> <@${poll.id}> propose de timeout <@${poll.toUserId}> pendant ${poll.time_display}\n > \n` + - `> ✅ **${poll.for}**\n > \n` + - `> Il manque **${votesNeeded}** vote(s)\n` + - `> ⏳ Temps restant : ${countdownText}\n`; await DiscordRequest( poll.endpoint, { method: 'PATCH', body: { - content: updatedContent, + embeds: [ + { + title: `Timeout`, + description: `**${poll.username}** propose de timeout **${poll.toUsername}** pendant ${poll.time_display}\nIl manque **${votesNeeded}** vote(s)`, + fields: [ + { + name: 'Pour', + value: '✅ ' + poll.for, + inline: true, + }, + { + name: 'Temps restant', + value: '⏳ ' + countdownText, + inline: false, + }, + ], + color: 0xF2F3F3, // You can set the color of the embed + }, + ], components: req.body.message.components, // preserve the buttons }, } @@ -781,7 +897,6 @@ app.post('/interactions', verifyKeyMiddleware(process.env.PUBLIC_KEY), async fun return; } - console.error('unknown interaction type', type); return res.status(400).json({ error: 'unknown interaction type' }); }); diff --git a/package-lock.json b/package-lock.json index f307f47..79e5e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@google/genai": "^0.8.0", + "@mistralai/mistralai": "^1.6.0", "better-sqlite3": "^11.9.1", "discord-interactions": "^4.0.0", "discord.js": "^14.18.0", @@ -151,6 +153,30 @@ "url": "https://github.com/discordjs/discord.js?sponsor" } }, + "node_modules/@google/genai": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-0.8.0.tgz", + "integrity": "sha512-Zs+OGyZKyMbFofGJTR9/jTQSv8kITh735N3tEuIZj4VlMQXTC0soCFahysJ9NaeenRlD7xGb6fyqmX+FwrpU6Q==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.14.2", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@mistralai/mistralai": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.6.0.tgz", + "integrity": "sha512-PQwGV3+n7FbE7Dp3Vnd8DAa3ffx6WuVV966Gfmf4QvzwcO3Mvxpz0SnJ/PjaZcsCwApBCZpNyQzvarAKEQLKeQ==", + "dependencies": { + "zod-to-json-schema": "^3.24.1" + }, + "peerDependencies": { + "zod": ">= 3" + } + }, "node_modules/@sapphire/async-queue": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", @@ -247,6 +273,15 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -323,6 +358,15 @@ "prebuild-install": "^7.1.1" } }, + "node_modules/bignumber.js": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.2.1.tgz", + "integrity": "sha512-+NzaKgOUvInq9TIUZ1+DRspzf/HApkCwD4btfuasFTdrfnOxqx853TgDpMolp+uv4RpRp7bPcEU2zKr9+fRmyw==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -428,6 +472,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -689,6 +739,15 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -837,6 +896,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -962,6 +1027,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1018,6 +1126,32 @@ "node": ">= 6" } }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1030,6 +1164,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1095,6 +1242,42 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -1210,6 +1393,48 @@ "node": ">=0.12.0" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2214,6 +2439,25 @@ "optional": true } } + }, + "node_modules/zod": { + "version": "3.24.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", + "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } } } diff --git a/package.json b/package.json index e6c8097..965a0a8 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "author": "Shay DeWael", "license": "MIT", "dependencies": { + "@google/genai": "^0.8.0", + "@mistralai/mistralai": "^1.6.0", "better-sqlite3": "^11.9.1", "discord-interactions": "^4.0.0", "discord.js": "^14.18.0", diff --git a/utils.js b/utils.js index e839cab..d22ac67 100644 --- a/utils.js +++ b/utils.js @@ -1,5 +1,7 @@ import 'dotenv/config'; import OpenAI from "openai"; +import { GoogleGenAI } from "@google/genai"; +import { Mistral } from '@mistralai/mistralai'; export async function DiscordRequest(endpoint, options) { // append endpoint to root API URL @@ -105,13 +107,39 @@ export function formatTime(time) { return parts.join(', ').replace(/,([^,]*)$/, ' et$1'); } +const openai = new OpenAI(); +const gemini = new GoogleGenAI({ apiKey: process.env.GEMINI_KEY}) +const leChat = new Mistral({apiKey: process.env.MISTRAL_KEY}); + export async function gork(messageHistory) { - const openai = new OpenAI(); + if (process.env.MODEL === 'OpenAI') { + // OPEN AI + const completion = await openai.chat.completions.create({ + model: "gpt-4.1-mini", + messages: messageHistory, + }); - const completion = await openai.chat.completions.create({ - model: "gpt-4.1-mini", - messages: messageHistory, - }); + return completion.choices[0].message.content; + } + else if (process.env.MODEL === 'Gemini') { + //GEMINI + const formattedHistory = messageHistory.map(msg => { + return `${msg.role}: ${msg.content}`; + }).join('\n'); + const response = await gemini.models.generateContent({ + model: "gemini-2.0-flash-lite", + contents: formattedHistory, + }) + return response.text + } else if (process.env.MODEL === 'Mistral') { + // MISTRAL + const chatResponse = await leChat.chat.complete({ + model: 'mistral-large-latest', + messages: messageHistory, + }) - return completion.choices[0].message.content; + return chatResponse.choices[0].message.content + } else { + return "Pas d'IA" + } } \ No newline at end of file