From 755c8510d7bfa4c628348ad2878accb2690912d1 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Sat, 27 May 2023 20:41:08 -0400 Subject: [PATCH 1/8] First basic implementation of ChatGPT --- functions.js | 24 ++++++++++++++++++++++++ package.json | 7 ++++--- slash-commands/chat.js | 20 ++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 slash-commands/chat.js diff --git a/functions.js b/functions.js index 025c020..2d2650e 100644 --- a/functions.js +++ b/functions.js @@ -21,6 +21,14 @@ const Discord = require('discord.js'); // Fuzzy text matching for db lookups const FuzzySearch = require('fuzzy-search'); +// OpenAI +const { Configuration, OpenAIApi } = require("openai"); + +const configuration = new Configuration({ + apiKey: process.env.OPENAI_API_KEY, +}); +const openai = new OpenAIApi(configuration); + // Various imports from other files const config = require('./config.json'); const strings = require('./strings.json'); @@ -530,6 +538,22 @@ const functions = { } } }, + openAI: { + chatPrompt(userPrompt) { + return new Promise(async (resolve, reject) => { + const response = await openai.createCompletion({ + model: 'text-davinci-003', + prompt: userPrompt, + temperature: 0, + max_tokens: 250 + }).catch(e => { + reject(e); + return null; + }); + resolve(response); + }); + } + }, // Parent-Level functions (miscellaneuous) closeRequest(requestId, interaction) { if (interaction.user.id == ownerId) { diff --git a/package.json b/package.json index 7c84570..05f8e83 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nodbot", - "version": "3.1.0", - "description": "Nods and Nod Accessories.", + "version": "3.2.0", + "description": "Nods and Nod Accessories, now with ChatGPT!", "main": "main.js", "dependencies": { "@discordjs/builders": "^0.16.0", @@ -12,10 +12,11 @@ "dotenv": "^10.0.0", "fuzzy-search": "^3.2.1", "mysql": "^2.18.1", + "openai": "^3.2.1", "tenorjs": "^1.0.10" }, "engines": { - "node": "16.x" + "node": "18.x" }, "devDependencies": { "eslint": "^7.32.0" diff --git a/slash-commands/chat.js b/slash-commands/chat.js new file mode 100644 index 0000000..848efe8 --- /dev/null +++ b/slash-commands/chat.js @@ -0,0 +1,20 @@ +const { SlashCommandBuilder } = require('@discordjs/builders'); +const fn = require('../functions.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('chat') + .setDescription('Send a message to ChatGPT') + .addStringOption(o => + o.setName("prompt") + .setDescription("Prompt to send to ChatGPT") + .setRequired(true) + ), + async execute(interaction) { + await interaction.deferReply(); + const userPrompt = interaction.options.getString("prompt"); + const response = await fn.openAI.chatPrompt(userPrompt).catch(e => console.error(e)); + const responseText = response.data.choices[0].text; + await interaction.editReply(`${responseText}`); + }, +}; \ No newline at end of file From 53625be91f3f91483d4f4e1adb2236236372bbc8 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Sun, 28 May 2023 10:08:51 -0400 Subject: [PATCH 2/8] Basic Dall-e implementation --- functions.js | 36 +++++++++++++++++++++++++++++++++++- slash-commands/dalle.js | 25 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 slash-commands/dalle.js diff --git a/functions.js b/functions.js index 2d2650e..3a0ebe2 100644 --- a/functions.js +++ b/functions.js @@ -28,6 +28,14 @@ const configuration = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); const openai = new OpenAIApi(configuration); +async function openAIStatus(o) { + const response = await o.listModels(); + const models = response.data.data; + models.forEach(e => { + console.log(`Model ID: ${e.id}`); + }); +}; +openAIStatus(openai); // Various imports from other files const config = require('./config.json'); @@ -544,7 +552,7 @@ const functions = { const response = await openai.createCompletion({ model: 'text-davinci-003', prompt: userPrompt, - temperature: 0, + temperature: 0.7, max_tokens: 250 }).catch(e => { reject(e); @@ -552,6 +560,20 @@ const functions = { }); resolve(response); }); + }, + imagePrompt(userPrompt, userId) { + return new Promise(async (resolve, reject) => { + try { + const response = await openai.createImage({ + prompt: userPrompt, + user: userId + }); + resolve(response.data.data[0].url); + } catch (e) { + reject(e); + return; + } + }); } }, // Parent-Level functions (miscellaneuous) @@ -595,6 +617,18 @@ const functions = { return newText + ' <:spongebob:1053398825965985822>'; }, + generateErrorId() { + const digitCount = 10; + const digits = []; + for (let i = 0; i < digitCount; i++) { + const randBase = Math.random(); + const randNumRaw = randBase * 10; + const randNumRound = Math.floor(randNumRaw); + digits.push(randNumRound); + } + const errorId = digits.join(""); + return errorId; + } }; module.exports = functions; \ No newline at end of file diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js new file mode 100644 index 0000000..eb97f9d --- /dev/null +++ b/slash-commands/dalle.js @@ -0,0 +1,25 @@ +const { SlashCommandBuilder } = require('@discordjs/builders'); +const fn = require('../functions.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('dalle') + .setDescription('Generate an image with DALL-e') + .addStringOption(o => + o.setName("prompt") + .setDescription("Prompt to send to DALL-e") + .setRequired(true) + ), + async execute(interaction) { + try { + await interaction.deferReply(); + const userPrompt = interaction.options.getString("prompt"); + const response = await fn.openAI.imagePrompt(userPrompt); + await interaction.editReply(`${response}`); + } catch (err) { + const errorId = fn.generateErrorId(); + console.error(`${errorId}: ${err}`); + await interaction.editReply(`An error has occured. Error ID: ${errorId}\n${err}`); + } + }, +}; \ No newline at end of file From 4e2e8bc70290b2571662d532678ac0634e537e06 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Mon, 29 May 2023 08:47:07 -0400 Subject: [PATCH 3/8] Add size option and response embed for dall-e --- functions.js | 11 ++++++++++- slash-commands/dalle.js | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/functions.js b/functions.js index 3a0ebe2..6296e14 100644 --- a/functions.js +++ b/functions.js @@ -377,6 +377,14 @@ const functions = { interaction.reply({ embeds: [ strainEmbed ]}); }, + dalle(prompt, imageUrl) { + const dalleEmbed = new Discord.MessageEmbed() + .setAuthor({ name: "NodDraw" }) + .setTimestamp() + .setImage(imageUrl) + .setFooter({ text: prompt }); + return { embeds: [dalleEmbed] }; + } }, collect: { gifName(interaction) { @@ -561,11 +569,12 @@ const functions = { resolve(response); }); }, - imagePrompt(userPrompt, userId) { + imagePrompt(userPrompt, size, userId) { return new Promise(async (resolve, reject) => { try { const response = await openai.createImage({ prompt: userPrompt, + size: size, user: userId }); resolve(response.data.data[0].url); diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js index eb97f9d..ccb1ad6 100644 --- a/slash-commands/dalle.js +++ b/slash-commands/dalle.js @@ -9,13 +9,25 @@ module.exports = { o.setName("prompt") .setDescription("Prompt to send to DALL-e") .setRequired(true) - ), + ) + .addStringOption(o => + o.setName("size") + .setDescription("1024x1024, 512x512, 256x256") + .setRequired(false) + .addChoices( + { name: "1024x1024 (2¢)", value: "1024x1024" }, + { name: "512x512 (1.8¢)", value: "512x512" }, + { name: "256x256 (1.6¢)", value: "256x256" } + )), async execute(interaction) { try { await interaction.deferReply(); const userPrompt = interaction.options.getString("prompt"); - const response = await fn.openAI.imagePrompt(userPrompt); - await interaction.editReply(`${response}`); + const size = interaction.options.getString("size") ? interaction.options.getString("size") : "512x512"; + + const imageUrl = await fn.openAI.imagePrompt(userPrompt, size); + const dalleEmbed = fn.embeds.dalle(userPrompt, imageUrl); + await interaction.editReply(dalleEmbed); } catch (err) { const errorId = fn.generateErrorId(); console.error(`${errorId}: ${err}`); From 2c75ff09d95a054dad8af67767be2c04fc2ff9b9 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Mon, 29 May 2023 09:22:11 -0400 Subject: [PATCH 4/8] Improved embed style. --- functions.js | 24 +++++++++++++++++++----- slash-commands/chat.js | 4 ++-- slash-commands/dalle.js | 2 +- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/functions.js b/functions.js index 6296e14..2547257 100644 --- a/functions.js +++ b/functions.js @@ -377,13 +377,27 @@ const functions = { interaction.reply({ embeds: [ strainEmbed ]}); }, - dalle(prompt, imageUrl) { + dalle(user, prompt, imageUrl) { const dalleEmbed = new Discord.MessageEmbed() - .setAuthor({ name: "NodDraw" }) - .setTimestamp() + .setAuthor({ name: "NodBot powered by DALL-E", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) + .addFields( + { name: "Prompt", value: prompt } + ) .setImage(imageUrl) - .setFooter({ text: prompt }); + .setFooter({ text: user.username, iconURL: user.avatarURL() }) + .setTimestamp(); return { embeds: [dalleEmbed] }; + }, + gpt(user, prompt, response) { + const gptEmbed = new Discord.MessageEmbed() + .setAuthor({ name: "NodBot powered by GPT-3", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) + .addFields( + { name: "Prompt", value: prompt }, + { name: "Response", value: response } + ) + .setFooter({ text: user.username, iconURL: user.avatarURL() }) + .setTimestamp(); + return { embeds: [gptEmbed] }; } }, collect: { @@ -566,7 +580,7 @@ const functions = { reject(e); return null; }); - resolve(response); + resolve(response.data.choices[0].text); }); }, imagePrompt(userPrompt, size, userId) { diff --git a/slash-commands/chat.js b/slash-commands/chat.js index 848efe8..e3aee29 100644 --- a/slash-commands/chat.js +++ b/slash-commands/chat.js @@ -14,7 +14,7 @@ module.exports = { await interaction.deferReply(); const userPrompt = interaction.options.getString("prompt"); const response = await fn.openAI.chatPrompt(userPrompt).catch(e => console.error(e)); - const responseText = response.data.choices[0].text; - await interaction.editReply(`${responseText}`); + const gptEmbed = fn.embeds.gpt(interaction.user, userPrompt, response); + await interaction.editReply(gptEmbed); }, }; \ No newline at end of file diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js index ccb1ad6..ff105f6 100644 --- a/slash-commands/dalle.js +++ b/slash-commands/dalle.js @@ -26,7 +26,7 @@ module.exports = { const size = interaction.options.getString("size") ? interaction.options.getString("size") : "512x512"; const imageUrl = await fn.openAI.imagePrompt(userPrompt, size); - const dalleEmbed = fn.embeds.dalle(userPrompt, imageUrl); + const dalleEmbed = fn.embeds.dalle(interaction.user, userPrompt, imageUrl); await interaction.editReply(dalleEmbed); } catch (err) { const errorId = fn.generateErrorId(); From efdc605dc0373f71041091829bbaa36a854032df Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Wed, 31 May 2023 08:07:12 -0400 Subject: [PATCH 5/8] Adding costs to embeds --- functions.js | 17 ++++++----------- slash-commands/chat.js | 7 ++++++- slash-commands/dalle.js | 2 +- strings.json | 10 ++++++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/functions.js b/functions.js index 2547257..fd0e479 100644 --- a/functions.js +++ b/functions.js @@ -377,26 +377,21 @@ const functions = { interaction.reply({ embeds: [ strainEmbed ]}); }, - dalle(user, prompt, imageUrl) { + dalle(prompt, imageUrl, size) { const dalleEmbed = new Discord.MessageEmbed() .setAuthor({ name: "NodBot powered by DALL-E", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) .addFields( { name: "Prompt", value: prompt } ) .setImage(imageUrl) - .setFooter({ text: user.username, iconURL: user.avatarURL() }) - .setTimestamp(); + .setFooter({ text: `This ${size} image cost ${strings.costs.dalle[size]}¢ to generate.` }) return { embeds: [dalleEmbed] }; }, - gpt(user, prompt, response) { + gpt(prompt, response, usage) { const gptEmbed = new Discord.MessageEmbed() .setAuthor({ name: "NodBot powered by GPT-3", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) - .addFields( - { name: "Prompt", value: prompt }, - { name: "Response", value: response } - ) - .setFooter({ text: user.username, iconURL: user.avatarURL() }) - .setTimestamp(); + .setDescription(`**Prompt**\n${prompt}\n\n**Response**\n${response}`) + .setFooter({ text: `This prompt used ${usage.tokens} tokens for a cost of ${usage.usd}¢` }) return { embeds: [gptEmbed] }; } }, @@ -580,7 +575,7 @@ const functions = { reject(e); return null; }); - resolve(response.data.choices[0].text); + resolve(response.data); }); }, imagePrompt(userPrompt, size, userId) { diff --git a/slash-commands/chat.js b/slash-commands/chat.js index e3aee29..a538115 100644 --- a/slash-commands/chat.js +++ b/slash-commands/chat.js @@ -14,7 +14,12 @@ module.exports = { await interaction.deferReply(); const userPrompt = interaction.options.getString("prompt"); const response = await fn.openAI.chatPrompt(userPrompt).catch(e => console.error(e)); - const gptEmbed = fn.embeds.gpt(interaction.user, userPrompt, response); + const responseText = response.choices[0].text.slice(2); + const usage = { + tokens: response.usage.total_tokens, + usd: response.usage.total_tokens * ( 0.2 / 1000 ) // 0.2¢ per 1000 tokens or 0.0002¢ per token. + }; + const gptEmbed = fn.embeds.gpt(userPrompt, responseText, usage); await interaction.editReply(gptEmbed); }, }; \ No newline at end of file diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js index ff105f6..a848e02 100644 --- a/slash-commands/dalle.js +++ b/slash-commands/dalle.js @@ -26,7 +26,7 @@ module.exports = { const size = interaction.options.getString("size") ? interaction.options.getString("size") : "512x512"; const imageUrl = await fn.openAI.imagePrompt(userPrompt, size); - const dalleEmbed = fn.embeds.dalle(interaction.user, userPrompt, imageUrl); + const dalleEmbed = fn.embeds.dalle(userPrompt, imageUrl, size); await interaction.editReply(dalleEmbed); } catch (err) { const errorId = fn.generateErrorId(); diff --git a/strings.json b/strings.json index bcf8a14..b82b6db 100644 --- a/strings.json +++ b/strings.json @@ -22,5 +22,15 @@ "bussin fr, no cap", "ongggg no :billed_cap: fr fr" ], + "costs": { + "gpt": { + "gpt-3.5-turbo": 0.2 + }, + "dalle": { + "256x256": 1.6, + "512x512": 1.8, + "1024x1024": 2.0 + } + }, "temp": {} } \ No newline at end of file From 200468ac1001cd291f3d2cd5481be812e5134f30 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Wed, 31 May 2023 08:33:03 -0400 Subject: [PATCH 6/8] Implement logging to db --- functions.js | 8 +++++++- slash-commands/chat.js | 3 ++- slash-commands/dalle.js | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/functions.js b/functions.js index fd0e479..54c1da4 100644 --- a/functions.js +++ b/functions.js @@ -391,7 +391,7 @@ const functions = { const gptEmbed = new Discord.MessageEmbed() .setAuthor({ name: "NodBot powered by GPT-3", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) .setDescription(`**Prompt**\n${prompt}\n\n**Response**\n${response}`) - .setFooter({ text: `This prompt used ${usage.tokens} tokens for a cost of ${usage.usd}¢` }) + .setFooter({ text: `This prompt used ${usage.tokens} tokens for a cost of ${usage.usdc}¢` }) return { embeds: [gptEmbed] }; } }, @@ -488,6 +488,12 @@ const functions = { resolve(); }); }) + }, + openai(user, prompt, engine, tokens, usdc) { + const query = `INSERT INTO openai (user, prompt, engine, tokens, usdc) VALUES (${db.escape(user)}, ${db.escape(prompt)}, ${db.escape(engine)}, ${db.escape(tokens)}, ${db.escape(usdc)})`; + db.query(query, (err) => { + if (err) throw err; + }); } }, download: { diff --git a/slash-commands/chat.js b/slash-commands/chat.js index a538115..2418a7b 100644 --- a/slash-commands/chat.js +++ b/slash-commands/chat.js @@ -17,9 +17,10 @@ module.exports = { const responseText = response.choices[0].text.slice(2); const usage = { tokens: response.usage.total_tokens, - usd: response.usage.total_tokens * ( 0.2 / 1000 ) // 0.2¢ per 1000 tokens or 0.0002¢ per token. + usdc: response.usage.total_tokens * ( 0.2 / 1000 ) // 0.2¢ per 1000 tokens or 0.0002¢ per token. }; const gptEmbed = fn.embeds.gpt(userPrompt, responseText, usage); await interaction.editReply(gptEmbed); + fn.upload.openai(interaction.user.id, userPrompt, "gpt-3.5-turbo", usage.tokens, usage.usdc); }, }; \ No newline at end of file diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js index a848e02..7636c1a 100644 --- a/slash-commands/dalle.js +++ b/slash-commands/dalle.js @@ -1,5 +1,6 @@ const { SlashCommandBuilder } = require('@discordjs/builders'); const fn = require('../functions.js'); +const strings = require("../strings.json"); module.exports = { data: new SlashCommandBuilder() @@ -28,6 +29,7 @@ module.exports = { const imageUrl = await fn.openAI.imagePrompt(userPrompt, size); const dalleEmbed = fn.embeds.dalle(userPrompt, imageUrl, size); await interaction.editReply(dalleEmbed); + fn.upload.openai(interaction.user.id, userPrompt, "dalle", 0, strings.costs.dalle[size]); } catch (err) { const errorId = fn.generateErrorId(); console.error(`${errorId}: ${err}`); From 08618e2be27fbc38f039dee25f5fcd573079ad9f Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Thu, 15 Jun 2023 19:07:16 -0400 Subject: [PATCH 7/8] Experimental generating content embed --- functions.js | 8 ++++++++ slash-commands/chat.js | 1 + slash-commands/dalle.js | 1 + 3 files changed, 10 insertions(+) diff --git a/functions.js b/functions.js index 54c1da4..df69ff9 100644 --- a/functions.js +++ b/functions.js @@ -393,6 +393,14 @@ const functions = { .setDescription(`**Prompt**\n${prompt}\n\n**Response**\n${response}`) .setFooter({ text: `This prompt used ${usage.tokens} tokens for a cost of ${usage.usdc}¢` }) return { embeds: [gptEmbed] }; + }, + generatingResponse() { + const embed = new Discord.MessageEmbed() + .setAuthor({ name: "NodBot powered by OpenAI", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) + .setImage("https://tenor.com/view/sucks-gif-4499658") + .setDescription("Generating a response, please stand by.") + .setFooter({ text: "Ligma balls" }); + return { embeds: [embed] }; } }, collect: { diff --git a/slash-commands/chat.js b/slash-commands/chat.js index 2418a7b..ed21ea5 100644 --- a/slash-commands/chat.js +++ b/slash-commands/chat.js @@ -12,6 +12,7 @@ module.exports = { ), async execute(interaction) { await interaction.deferReply(); + await interaction.editReply(fn.embeds.generatingResponse()); const userPrompt = interaction.options.getString("prompt"); const response = await fn.openAI.chatPrompt(userPrompt).catch(e => console.error(e)); const responseText = response.choices[0].text.slice(2); diff --git a/slash-commands/dalle.js b/slash-commands/dalle.js index 7636c1a..029b066 100644 --- a/slash-commands/dalle.js +++ b/slash-commands/dalle.js @@ -23,6 +23,7 @@ module.exports = { async execute(interaction) { try { await interaction.deferReply(); + await interaction.editReply(fn.embeds.generatingResponse()); const userPrompt = interaction.options.getString("prompt"); const size = interaction.options.getString("size") ? interaction.options.getString("size") : "512x512"; From 7c09aaffaa7c94d775aa310ebbbce76e35450b75 Mon Sep 17 00:00:00 2001 From: Skylar Grant Date: Thu, 15 Jun 2023 19:16:50 -0400 Subject: [PATCH 8/8] Minor updates --- functions.js | 2 +- slash-commands/chat.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions.js b/functions.js index df69ff9..369af8b 100644 --- a/functions.js +++ b/functions.js @@ -397,7 +397,7 @@ const functions = { generatingResponse() { const embed = new Discord.MessageEmbed() .setAuthor({ name: "NodBot powered by OpenAI", iconURL: "https://assets.vfsh.dev/openai-logos/PNGs/openai-logomark.png" }) - .setImage("https://tenor.com/view/sucks-gif-4499658") + .setImage("https://media.tenor.com/aHMHzNGCb4kAAAAC/sucks.gif") .setDescription("Generating a response, please stand by.") .setFooter({ text: "Ligma balls" }); return { embeds: [embed] }; diff --git a/slash-commands/chat.js b/slash-commands/chat.js index ed21ea5..efeaad7 100644 --- a/slash-commands/chat.js +++ b/slash-commands/chat.js @@ -15,7 +15,7 @@ module.exports = { await interaction.editReply(fn.embeds.generatingResponse()); const userPrompt = interaction.options.getString("prompt"); const response = await fn.openAI.chatPrompt(userPrompt).catch(e => console.error(e)); - const responseText = response.choices[0].text.slice(2); + const responseText = response.choices[0].text; const usage = { tokens: response.usage.total_tokens, usdc: response.usage.total_tokens * ( 0.2 / 1000 ) // 0.2¢ per 1000 tokens or 0.0002¢ per token.