diff --git a/README.md b/README.md index 1c63c06..226df30 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,12 @@ The module will never send a request to GPT-3 without being told to by pressing > WARNING! Using any of these functions will send a request to GPT-3 for which you will be charged like any other request made by this module. As such please be careful implenting them in macros and other modules. Test your code well before implementing these functions and I strongly advice users to avoid looping and recursive functions. Functions to construct and send your own prompts are provided under `game.modules.get('ai-description-generator').api`: -- `constructPrompt(language, system, world, entityType, subject, key)`: Construct and sends a prompt based on the provided context similar to how the base module does it. +- `constructPrompt(language, system, world, subject, subjectType, key)`: Construct and sends a prompt based on the provided context similar to how the base module does it. - `language`: The language GPT-3 will be encouraged to respond in. Use `game.settings.get('ai-description-generator', 'language')` to use the language provided in the module's/core's settings. - `system`: The RPG system to be used for context. Use `game.settings.get('ai-description-generator', 'system')` to use the system that was provided in the module's settings. - `world`: The world/setting to be used for context. Use `game.settings.get('ai-description-generator', 'world')` to use the world that was provided in the module's settings. - - `entityType`: Either `creature`, `item`, or `spell`. - `subject`: The name of the subject. + - `subjectType`: Additional information about the nature of the `subject`, like `creature` or `spell`. - `key`: Your API key. Use `game.settings.get('ai-description-generator', 'key')` to use the key that was provided in the module's settings. - `sendPrompt(prompt, key)`: Sends a completely custom prompt. - `prompt`: The prompt you want to send. diff --git a/module.json b/module.json index 9af000f..1a6e3ef 100644 --- a/module.json +++ b/module.json @@ -2,7 +2,7 @@ "id": "ai-description-generator", "title": "AI Description Generator", "description": "Utilizes the GPT-3 AI to generate descriptions from within Foundry.", - "version": "1.2.0", + "version": "1.3.0", "authors": [ { "name": "Pepijn Sietsema", diff --git a/module.zip b/module.zip index 277db64..2344240 100644 Binary files a/module.zip and b/module.zip differ diff --git a/scripts/chat_commands.js b/scripts/chat_commands.js index 8203441..fa0a8d4 100644 --- a/scripts/chat_commands.js +++ b/scripts/chat_commands.js @@ -27,8 +27,8 @@ export function addChatCommands(log, data, chatData) { game.settings.get('ai-description-generator', 'language'), game.settings.get('ai-description-generator', 'system'), game.settings.get('ai-description-generator', 'world'), - subjectType, subject, + subjectType, game.settings.get('ai-description-generator', 'key') ); return false; diff --git a/scripts/generator.js b/scripts/generator.js index 3535329..d0ddfed 100644 --- a/scripts/generator.js +++ b/scripts/generator.js @@ -1,5 +1,6 @@ -export function constructPrompt(language, system, world, subjectType, subject, key) { - var prompt = '' +//Construct a prompt based on the given parameters. +export function constructPrompt(language, system, world, subject, subjectType, key) { + //A mapping for Foundry's languages since only the key is stored but the value is needed. const foundryLanguages = { "en": "English", "fr": "French", @@ -30,31 +31,47 @@ export function constructPrompt(language, system, world, subjectType, subject, k 'aus' ]; - if (language == '') language = foundryLanguages[game.settings.get('core', 'language')]; - if (!englishLanguages.includes(language.toLowerCase())) prompt += `Reply in ${language}. ` - prompt += `This is a tabletop roleplaying game using the ${system}`; - if (!system.toLowerCase().includes('system')) prompt += ' system'; - if (world) prompt += ` and the ${world} setting`; - prompt += `. Give a cool short sensory description the game master can use for a ${subject}`; - switch (subjectType.toLowerCase()) { - case 'creature': - case 'item': - case 'spell': - prompt += ` ${subjectType}`; + const defaultPrompt = 'Reply in {language}. This is a tabletop roleplaying game using the {system} system and the {world} setting. Give a cool short sensory description the game master can use for a {subject} {subjectType}.'; + var prompt = game.settings.get('ai-description-generator', 'prompt'); + if (prompt === defaultPrompt) { + //If the module's language setting is left blank use the core language setting instead. + if (language == '') language = foundryLanguages[game.settings.get('core', 'language')]; + //Remove the language request from the prompt for English languages. + if (englishLanguages.includes(language.toLowerCase())) prompt = prompt.replace('Reply in {language}. ', ''); + //If the system name already includes the word 'system' remove it from the prompt. + if (system.toLowerCase().includes('system')) prompt = prompt.replace(' system ', ' '); + //If no world is given remove it from the prompt. + if (world == '') prompt = prompt.replace(' and the {world} setting', ''); + //If no subject type is given remove it from the prompt. + if (subjectType == '') prompt = prompt.replace(' {subjectType}', ''); } - prompt += '.'; + var prompt_mapping = { + '{language}': language, + '{system}': system, + '{world}': world, + '{subject}': subject, + '{subjectType}': subjectType + }; + for (const [key, value] of Object.entries(prompt_mapping)) { + prompt = prompt.replace(key, value); + } + //Send the prompt. return sendPrompt(prompt, key) } +//Send a prompt the GPT-3. export function sendPrompt(prompt, key) { console.log(`AI Description Generator | Sending the following prompt to GPT-3: ${prompt}`); var response = ''; + + //Setup a http request to OpenAI's API using the provided key. var oHttp = new XMLHttpRequest(); oHttp.open("POST", "https://api.openai.com/v1/completions"); oHttp.setRequestHeader("Accept", "application/json"); oHttp.setRequestHeader("Content-Type", "application/json"); oHttp.setRequestHeader("Authorization", 'Bearer ' + key); + //Add a listener to the request to catch its response. oHttp.onreadystatechange = function () { if (oHttp.readyState === 4) { var oJson = {} @@ -81,8 +98,9 @@ export function sendPrompt(prompt, key) { } }; + //The data to send including the prompt. var data = { - model: 'text-davinci-003', + model: game.settings.get('ai-description-generator', 'model'), prompt: prompt, max_tokens: game.settings.get('ai-description-generator', 'max_tokens'), user: '1', @@ -92,5 +110,6 @@ export function sendPrompt(prompt, key) { stop: ["#", ";"] //Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. }; + //Send the data. oHttp.send(JSON.stringify(data)); } \ No newline at end of file diff --git a/scripts/main.js b/scripts/main.js index 96401b6..6961a9b 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -3,11 +3,13 @@ import { registerAPI } from './api.js'; import { constructPrompt } from './generator.js'; import { addChatCommands } from './chat_commands.js'; +//Register the settings and api function when Foundry is ready. Hooks.once('init', () => { registerSettings(); registerAPI(); }) +//Add a new button to the header of the actor sheet. Hooks.on('getActorSheetHeaderButtons', (sheet, headerButtons) => { if (!game.user.isGM) return; headerButtons.unshift({ @@ -19,14 +21,15 @@ Hooks.on('getActorSheetHeaderButtons', (sheet, headerButtons) => { game.settings.get('ai-description-generator', 'language'), game.settings.get('ai-description-generator', 'system'), game.settings.get('ai-description-generator', 'world'), - 'creature', sheet.object.name, + 'creature', game.settings.get('ai-description-generator', 'key') ); } }) }) +//Add a new button the the header of the itme sheet. Spells are also considered items. Hooks.on('getItemSheetHeaderButtons', (sheet, headerButtons) => { headerButtons.unshift({ label: 'GPT-3', @@ -37,8 +40,8 @@ Hooks.on('getItemSheetHeaderButtons', (sheet, headerButtons) => { game.settings.get('ai-description-generator', 'language'), game.settings.get('ai-description-generator', 'system'), game.settings.get('ai-description-generator', 'world'), - sheet.object.type == 'spell' ? 'spell': 'item', sheet.object.name, + sheet.object.type == 'spell' ? 'spell': 'item', game.settings.get('ai-description-generator', 'key') ); } diff --git a/scripts/settings.js b/scripts/settings.js index 1ea3979..c852108 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -53,6 +53,15 @@ export function registerSettings() { default: 'GPT-3' }); + game.settings.register('ai-description-generator', 'prompt', { + name: 'AI Prompt', + hint: 'The prompt that is used to contruct a request for GPT-3. Only alter this if you are dissatified with the results and know what you are doing!', + scope: 'world', + config: true, + type: String, + default: 'Reply in {language}. This is a tabletop roleplaying game using the {system} system and the {world} setting. Give a cool short sensory description the game master can use for a {subject} {subjectType}.' + }); + game.settings.register('ai-description-generator', 'max_tokens', { name: 'AI Max Tokens', hint: 'The maximum amount of tokens the AI can use per request.', @@ -92,6 +101,16 @@ export function registerSettings() { default: 0.0 }); + game.settings.register('ai-description-generator', 'model', { + name: 'AI Model', + hint: 'GPT-3 offers 4 main models, davanci-003 being the latest. This setting should not be changed if you do not know what this means.', + scope: 'world', + config: true, + type: String, + default: 'text-davinci-003', + choices: { 'text-davinci-003': 'text-davinci-003', 'text-curie-001': 'text-curie-001', 'text-babbage-001': 'text-babbage-001', 'text-ada-001': 'text-ada-001' } + }); + game.settings.register('ai-description-generator', 'api', { name: 'Enable API Functions', hint: 'Exposes functions to construct and send prompts in macros or other modules.',