diff --git a/commands/chat.js b/commands/chat.js index 3703e12..4319e3e 100644 --- a/commands/chat.js +++ b/commands/chat.js @@ -1,7 +1,8 @@ -const libCore = require("../libCore.js"); +const { submitPromptTransaction } = require("../controllers/chatGptController"); const { SlashCommandBuilder } = require('discord.js'); const { DebugBuilder } = require("../utilities/debugBuilder"); const log = new DebugBuilder("server", "chat"); +const { EmbedBuilder } = require('discord.js'); module.exports = { @@ -12,6 +13,10 @@ module.exports = { option.setName('prompt') .setDescription('The prompt to be sent to ChatGPT') .setRequired(true)) + .addBooleanOption(option => + option.setName('public') + .setDescription("Set this to false if you would like the message to only be visible to you.") + .setRequired(false)) .addNumberOption(option => option.setName('temperature') .setDescription('Set the temperature, 0 = repetitive, 1 = random; Defaults to 0') @@ -20,23 +25,17 @@ module.exports = { option.setName('tokens') .setDescription('The max amount of tokens to be spent') .setRequired(false)), - example: "chat [tell me a story] [0.07] []", // Need to figure out the tokens + example: "chat [tell me a story] [0.07] [400]", // Need to figure out the tokens isPrivileged: false, requiresTokens: true, defaultTokenUsage: 100, deferInitialReply: true, async execute(interaction) { - try { - var params = {}; - var promptText = interaction.options.getString('prompt'); - var temperature = interaction.options.getNumber('temperature'); - var maxTokens = interaction.options.getNumber('tokens') ?? this.defaultTokenUsage; - - if (temperature) params._temperature = temperature; - if (maxTokens) params._max_tokens = maxTokens; - - var gptResponse = await libCore.getChat(promptText, params); - await interaction.editReply(`${interaction.member.user} ${gptResponse}`); + try { + submitPromptTransaction(interaction, async (err, result) => { + if (err) throw err; + await interaction.editReply({ content: `${interaction.member.user} ${result.promptResult}`, ephemeral: false }); + }); // Needs reply code to reply to the generation }catch(err){ diff --git a/controllers/accountController.js b/controllers/accountController.js index 92e64bf..deb841a 100644 --- a/controllers/accountController.js +++ b/controllers/accountController.js @@ -36,6 +36,16 @@ exports.createAccount = (_discordAccountId, callback) => { }) } +exports.withdrawBalance = async (_withdrawAmount, _accountId, callback) => { + userStorage.updateBalance('withdraw', _withdrawAmount, _accountId, async (err, result) => { + if (err) return callback(err, undefined); + + if(result) return callback(undefined, result); + + return callback(undefined, undefined); + }) +} + exports.verifyBalance = (_tokensToBeUsed, _accountId, callback) => { userStorage.checkBalance(_tokensToBeUsed, _accountId, (err, results) => { if (err) return callback(err, undefined); diff --git a/controllers/chatGptController.js b/controllers/chatGptController.js index 466b435..8ed1ba2 100644 --- a/controllers/chatGptController.js +++ b/controllers/chatGptController.js @@ -1,3 +1,49 @@ +const { DebugBuilder } = require("../utilities/debugBuilder"); +const log = new DebugBuilder("server", "chatGptController"); + +const { createTransaction } = require("./transactionController"); + +const { Configuration, OpenAIApi } = require('openai'); +const configuration = new Configuration({ + organization: process.env.OPENAI_ORG, + apiKey: process.env.OPENAI_KEY +}); + +const openai = new OpenAIApi(configuration); + + +async function getGeneration(_prompt, callback, { _model = "text-davinci-003", _temperature = 0, _max_tokens = 100}) { + log.DEBUG("Getting chat with these properties: ", _prompt, _model, _temperature, _max_tokens) + try{ + /* + const response = await openai.createCompletion({ + model: _model, + prompt: _prompt, + temperature: _temperature, + max_tokens: _max_tokens + }); + */ + + var response = { + "id": "ABD123", + "usage": { + "total_tokens": _max_tokens + }, + "data": { + "choices": [ + { + "text": "ASKLDJHASLDJALSKDJAKLSDJLASKDJALSKD" + } + ] + } + }; + return callback(undefined, response); + } catch (err){ + return callback(err, undefined); + } + //var responseData = response.data.choices[0].text; +} + /** * Use ChatGPT to generate a response * @@ -5,18 +51,30 @@ * @param {*} param1 Default parameters can be modified * @returns */ -exports.getChat = async (_prompt, { _model = "text-davinci-003", _temperature = 0, _max_tokens = 100 }) => { - log.DEBUG("Getting chat with these properties: ", _prompt, _model, _temperature, _max_tokens) - return - /* - const response = await openai.createCompletion({ - model: _model, - prompt: _prompt, - temperature: _temperature, - max_tokens: _max_tokens - }); - - var responseData = response.data.choices[0].text; - return responseData; - */ +exports.submitPromptTransaction = async (interaction, callback) => { + var params = {}; + var promptText = interaction.options.getString('prompt'); + var temperature = interaction.options.getNumber('temperature'); + var maxTokens = interaction.options.getNumber('tokens'); + + if (temperature) params._temperature = temperature; + if (maxTokens) params._max_tokens = maxTokens; + + getGeneration(promptText, (err, gptResult) => { + if (err) callback(err, undefined); + + // TODO - Use the pricing table to calculate discord tokens + const discordTokensUsed = gptResult.usage.total_tokens; + + if (gptResult){ + createTransaction(gptResult.id, interaction.member.user, discordTokensUsed, gptResult.usage.total_tokens, 1, async (err, transactionResult) => { + if (err) callback(err, undefined); + + if (transactionResult){ + log.DEBUG("Transaction Created: ", transactionResult); + callback(undefined, ({ promptResult: gptResult.data.choices[0].text, totalTokens: discordTokensUsed})); + } + }); + } + }, { _temperature: temperature, _max_tokens: maxTokens }); } \ No newline at end of file diff --git a/controllers/transactionController.js b/controllers/transactionController.js new file mode 100644 index 0000000..9599244 --- /dev/null +++ b/controllers/transactionController.js @@ -0,0 +1,35 @@ +// Controller for managing transactions + +const { DebugBuilder } = require("../utilities/debugBuilder"); +const log = new DebugBuilder("server", "transactionController"); + +const { TransactionStorage } = require("../libStorage"); +const transactionStorage = new TransactionStorage(); + +const { BaseTransaction } = require("../utilities/recordHelper"); +const { withdrawBalance } = require("./accountController"); + +exports.createTransaction = async (_provider_transaction_id, _account_id, _discord_tokens_used, _provider_tokens_used, _provider_id, callback) => { + if (!_provider_transaction_id && !_account_id && !_discord_tokens_used && !_provider_id) return callback(new Error("Invalid vars when creating transaction", {vars: [_provider_transaction_id, _account_id, _discord_tokens_used, _provider_id, callback]})) + + const newTransaction = new BaseTransaction(_provider_transaction_id, _account_id, _discord_tokens_used, _provider_tokens_used, _provider_id, callback); + log.DEBUG("New Transaction Object: ", newTransaction); + withdrawBalance(newTransaction.discord_tokens_used, newTransaction.account_id, (err, withdrawResult) => { + if (err) return callback(err, undefined); + + if (withdrawResult){ + log.DEBUG("New withdraw result: ", withdrawResult); + transactionStorage.createTransaction(newTransaction, async (err, transactionResult) =>{ + if (err) return callback(err, undefined); + + if(transactionResult){ + log.DEBUG("New transaction result: ", transactionResult); + return callback(undefined, transactionResult); + } + }) + } + else { + return callback(undefined, undefined); + } + }); +} \ No newline at end of file diff --git a/events/interactionCreate.js b/events/interactionCreate.js index 896ea73..0c9eafe 100644 --- a/events/interactionCreate.js +++ b/events/interactionCreate.js @@ -22,7 +22,7 @@ module.exports = { await authorizeCommand(interaction, command, async () => { await authorizeTokenUsage(interaction, command, async () => { try { - if (command.deferInitialReply) await interaction.deferReply({ ephemeral: true }); + if (command.deferInitialReply || !interaction.options.getBool('public')) await interaction.deferReply({ ephemeral: true }); command.execute(interaction); } catch (error) { log.ERROR(error); diff --git a/libCore.js b/libCore.js index 1bfb15f..677efc8 100644 --- a/libCore.js +++ b/libCore.js @@ -10,15 +10,6 @@ const { all } = require('axios'); const { DebugBuilder } = require("./utilities/debugBuilder"); const log = new DebugBuilder("server", "libCore"); -/* OpenAI config -const { Configuration, OpenAIApi } = require('openai'); -const configuration = new Configuration({ - organization: process.env.OPENAI_ORG, - apiKey: process.env.OPENAI_API -}); - -const openai = new OpenAIApi(configuration); -*/ // Setup Storage handlers var feedStorage = new FeedStorage(); var postStorage = new PostStorage(); diff --git a/libStorage.js b/libStorage.js index dc3c223..88bcaba 100644 --- a/libStorage.js +++ b/libStorage.js @@ -187,30 +187,60 @@ exports.UserStorage = class UserStorage extends Storage { } }) } + + /** + * Update a user's account Balance + * + * @param {string} _updateType The type of update to make to the account [ withdraw | deposit ] + * @param {number} _updateAmount The amount to update the account + * @param {number} _account_id The ID of the account to update + * @param {function} callback The callback function to call with the results + * @returns Result from the SQL query or false + */ + updateBalance(_updateType, _updateAmount, _account_id, callback){ + var sqlQuery = ""; + switch(_updateType){ + case "withdraw": + // Code here to withdraw funds + sqlQuery = `UPDATE ${this.dbTable} SET balance=balance-${_updateAmount};`; + break; + case "deposit": + // Code here to withdraw funds + sqlQuery = `UPDATE ${this.dbTable} SET balance=balance+${_updateAmount};`; + break; + default: + log.ERROR('Update type not valid: ', _updateType); + return callback(new Error("Update type not valid")); + } + + if(!sqlQuery) return callback(new Error("SQL Query empty"), undefined); - updateBalance(){ - + runSQL(sqlQuery, this.connection, (err, rows) => { + if (err) return callback(err, undefined); + if (rows?.affectedRows > 0) return callback(undefined, rows); + return callback(undefined, undefined); + }) } } -/* + exports.TransactionStorage = class TransactionStorage extends Storage { constructor() { super(transactionsTable); } createTransaction(transaction, callback){ - const sqlQuery = `INSERT INTO ${this.dbTable} () VALUES ('${}');`; + var sqlQuery = `INSERT INTO ${this.dbTable} (transaction_id, account_id, discord_tokens_used, provider_tokens_used, provider_id, order_date) VALUES ('${transaction.transaction_id}', '${transaction.account_id}', '${transaction.discord_tokens_used}', '${transaction.provider_tokens_used}', '${transaction.provider_id}', '${returnMysqlTime()}');`; log.DEBUG(`Adding new entry with SQL query: '${sqlQuery}'`) runSQL(sqlQuery, this.connection, (err, rows) => { if (err) return callback(err, undefined); - if (rows[0]?.id) return callback(undefined, rows); + if (rows?.affectedRows > 0) return callback(undefined, rows); return callback(undefined, undefined); }) } } -*/ + exports.FeedStorage = class FeedStorage extends Storage { constructor() { super(rssFeedsTable); diff --git a/utilities/recordHelper.js b/utilities/recordHelper.js index 4f172e8..7a7b10c 100644 --- a/utilities/recordHelper.js +++ b/utilities/recordHelper.js @@ -69,4 +69,31 @@ exports.RSSPostRecord = class RSSPostRecord extends baseRSSRecord{ super(_id, _title, _link, _category); this.guid = _guid; } -} \ No newline at end of file +} + +/** + * Base transaction + */ +class BaseTransaction { + constructor(_provider_transaction_id, _account_id, _discord_tokens_used, _provider_tokens_used, _provider_id) { + this.transaction_id = _provider_transaction_id; + this.account_id = _account_id; + this.discord_tokens_used = _discord_tokens_used; + this.provider_tokens_used = _provider_tokens_used; + this.provider_id = _provider_id; + } +} + +exports.BaseTransaction = BaseTransaction; + +/** + * Base transaction + */ +class BaseUserAccount { + constructor(_discord_account_id, _account_id) { + this.discord_account_id = _discord_account_id; + this.account_id = _account_id; + } +} + +exports.BaseUserAccount = BaseUserAccount; \ No newline at end of file