{"version":3,"file":"openai.min.js","sources":["https:\/\/moodle.sonsbeekmedia.nl\/caie_test\/mod\/teachingtools\/teachingapp\/openai\/amd\/src\/openai.js"],"sourcesContent":["\/* eslint-disable max-len *\/\n\/\/ This file is part of Moodle - http:\/\/moodle.org\/\n\/\/\n\/\/ Moodle is free software: you can redistribute it and\/or modify\n\/\/ it under the terms of the GNU General Public License as published by\n\/\/ the Free Software Foundation, either version 3 of the License, or\n\/\/ (at your option) any later version.\n\/\/\n\/\/ Moodle is distributed in the hope that it will be useful,\n\/\/ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\/\/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\/\/ GNU General Public License for more details.\n\/\/\n\/\/ You should have received a copy of the GNU General Public License\n\/\/ along with Moodle. If not, see .\n\n\/**\n * The Drag and Match game.\n *\n * @module teachingapp_openai\/openai\n * @class openai\n * @copyright 2023 Sonsbeekmedia\n * @author Bas Brands \n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n *\/\n\nimport GameEngineLoader from 'mod_teachingtools\/game_engine';\nimport StoreConfigElement from 'mod_teachingtools\/names\/store_config_element';\nimport {get_strings as getStrings} from 'core\/str';\nimport Notification from 'core\/notification';\nimport * as Toast from 'core\/toast';\n\nconst defaultNames = {\n names: [\n {\n name: 'Palm oil',\n story: '',\n question: 'Give another argument why the use of oil from oil palm plantations in Southeast Asia is less sustainable ' +\n ' from an ecological point of view than the use of algae oil produced locally.',\n answers: [\n 'Many pesticides are used on oil palm plantations.',\n 'The processing into palm oil causes a lot of CO2 emissions.',\n 'The oil palm plantations are often located in tropical rainforests.',\n 'Producing the oil locally reduces the use of energy for transport.',\n ],\n },\n {\n name: 'Reflexes',\n story: 'A car accident has damaged Tobias\\'s spinal cord so much that he can no longer move and feel his legs.' +\n 'With the help of a BerkelBike he may be able to cycle again. To use a BerkelBike, the nerves from the spinal cord to' +\n ' the leg muscles must still function. To check this, a doctor tests the reflexes of the legs. She taps Tobias ' +\n ' knee tendon with a hammer, after which muscles in Tobias\\' thigh contract.',\n question: 'Explain why the spinal cord injury has no effect on the patellar reflex.',\n markers: [\n 'The answer must show that (for the patellar tendon reflex) no connection to the brain is required \/ that only ' +\n 'part of (the nerve cells from) the spinal cord is involved \/ that the reflex arc is still intact.'\n ],\n answers: [\n 'A reflex does not have to be consciously controlled'\n ],\n },\n {\n name: 'Carnivorous plants',\n story: 'The Portuguese sundew (Drosophyllum lusitanicum) is a carnivorous plant found on dry soils in northern ' +\n 'Morocco and southern Spain and Portugal. The plant depends on insects for its nitrogen compounds. The insects are ' +\n 'trapped by the sticky secretions of the glands located on the long leaves. Other glands in the leaf secrete enzymes ' +\n 'that digest the insects.',\n question: 'A researcher in the Netherlands wants to do a follow-up experiment with the Portuguese sundew. ' +\n ' He conducts these tests in a closed greenhouse because of the risks of introducing an exotic species. ' +\n ' Describe a risk to native plants as a result of introducing Portuguese sundew in the Netherlands.',\n answers: [\n 'The Portuguese sundew may compete with native plants for nutrients.',\n 'Pollinators of native plants may be caught by the sticky secretions of the Portuguese sundew.',\n 'The Portuguese sunday may carry diseases that are harmful to native plants.',\n ],\n },\n {\n name: 'Sweat',\n story: 'Sweat glands in the armpits excrete organic substances in addition to water and salts. These organic ' +\n 'substances are a breeding ground for bacteria. Although everyone excretes the same substances when sweating, the ' +\n 'smell of sweat can vary greatly. The smell depends on the types of bacteria that live on your skin. In the community' +\n ' of bacteria in the armpit, one sex is usually dominant. Often that is Staphylococcus, sometimes that is ' +\n ' Corynebacterium. The latter produces foul-smelling gases during the decomposition of organic substances. \\n\\n ' +\n ' Chris also appeared to suffer from Corynebacterium. He hypothesized that if Corynebacterium could suddenly ' +\n ' colonize his armpits, other bacteria that do not produce odorous substances could do the same. To investigate this, '+\n ' he designed an experiment. \\n\\n' +\n ' Chris asked subjects not to wash for several days, not to use deodorant and to wear cotton pads under their ' +\n ' armpits during that time. \\n\\n' +\n ' An odor panel then assessed the armpit odor in the subjects themselves (Figure 1), and the odor of the cotton pads ' +\n ' without the subjects being present.',\n question: 'Explain why the odor test is more valid if the odor panel smells the cotton pads rather than the armpits ' +\n 'themselves.',\n markers: [\n 'The answer must show that other variables do not play a role when using cotton pads. ',\n ],\n answers: [\n 'When you smell a cotton pad, you only smell the armpit scent (and not the smell of other body parts).',\n 'If the armpit is smelled, the appearance of the test subject influence the judgment about the smell.',\n 'The armpit odor of test subjects found to be attractive probably rated more positively.'\n ],\n }\n ]\n};\n\n\/**\n * Magnetic words game.\n *\n * User inputs words, each word gets transformed into a draggable box that allows you to move them around as you want to.\n * @param {Array} names - The names of the game.\n * @param {HTMLElement} rootElement - The root element for the game.\n *\/\n class GameElement {\n constructor(names, rootElement) {\n this.rootElement = rootElement;\n this.names = names;\n if (this.names.names.length == 0) {\n this.names.names = defaultNames.names;\n }\n this.storeConfig = new StoreConfigElement(this.names, this.rootElement);\n\n \/\/ Setup the zones where the content goes.\n this.drawZone = rootElement.querySelector('[data-region=\"draw-zone\"]');\n this.input = rootElement.querySelector('[data-region=\"input\"]');\n this.marker = rootElement.querySelector('[data-region=\"marker\"]');\n this.markerResponse = this.rootElement.querySelector('[data-region=\"aimarker-response\"]');\n this.aiResponseRegion = rootElement.querySelector('[data-region=\"airesponse\"]');\n this.conversationRegion = rootElement.querySelector('[data-region=\"conversation\"]');\n this.debugRegion = this.drawZone.querySelector('[data-region=\"response\"]');\n this.promptRegion = this.drawZone.querySelector('[data-region=\"prompt\"]');\n this.modeDescriptionRegion = this.drawZone.querySelector('[data-region=\"modedescription\"]');\n this.configuratorZone = this.rootElement.querySelector('[data-region=\"configurator\"]');\n this.modeDescriptionRegion = this.rootElement.querySelector('[data-region=\"modedescription\"]');\n this.currentQuestionRegion = this.drawZone.querySelector('[data-region=\"currentquestion\"]');\n this.getAiResponseButton = this.drawZone.querySelector('[data-region=\"getairesponse\"]');\n this.robot = rootElement.querySelector('[data-region=\"robot\"]');\n\n \/\/ Setup the variables used by our game.\n this.lastAIResponse = '';\n this.templates = {};\n this.sets = [];\n this.setsData = [];\n this.currentSet = 0;\n this.editSet = 0;\n this.modes = ['bestanswer', 'marking', 'aifeedback', 'examinerfeedback'];\n\n \/\/ Setup the event listeners.\n this.runGame();\n }\n\n \/**\n * Called from the Names class when the names have been updated.\n * @param {Array} names New names.\n *\/\n async update(names) {\n this.names.names = names;\n this.parseInput();\n this.render();\n }\n\n \/**\n * Run the game.\n *\/\n async runGame() {\n this.parseInput();\n await this.setAiConfig();\n await this.addEventListeners();\n await this.createConfigurator();\n this.render();\n }\n\n \/**\n * Set the AI configuration.\n *\/\n async setAiConfig() {\n let temperature = await this.storeConfig.getCustomData('temperature');\n let maxTokens = await this.storeConfig.getCustomData('maxTokens');\n let frequencyPenalty = await this.storeConfig.getCustomData('frequencyPenalty');\n let presencePenalty = await this.storeConfig.getCustomData('presencePenalty');\n let model = await this.storeConfig.getCustomData('model');\n\n this.mode = await this.storeConfig.getCustomData('mode');\n if (!this.mode) {\n this.mode = 'bestanswer';\n }\n this.apiKey = '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';\n this.openAiConfig = {\n model: model ? model : 'davinci',\n n: (this.mode == 'bestanswer') ? 4 : 1,\n temperature: temperature ? parseFloat(temperature, 1) : 0.5,\n \/\/ eslint-disable-next-line camelcase\n max_tokens: maxTokens ? parseInt(maxTokens) : 300,\n \/\/ eslint-disable-next-line camelcase\n frequency_penalty: frequencyPenalty ? parseFloat(frequencyPenalty, 1) : 0,\n \/\/ eslint-disable-next-line camelcase\n presence_penalty: presencePenalty ? parseFloat(presencePenalty, 1) : 0,\n stop: [\"Question:\", \".\\n\"]\n };\n this.maxTry = 5;\n this.trycount = 0;\n }\n\n \/**\n * Add event listeners.\n *\/\n async addEventListeners() {\n \/\/ eslint-disable-next-line complexity\n this.rootElement.addEventListener('click', (event) => {\n if (event.target.closest('[data-action=\"next\"]')) {\n this.currentSet++;\n if (this.currentSet >= this.setsData.length) {\n this.currentSet = 0;\n }\n this.render();\n }\n if (event.target.closest('[data-action=\"prev\"]')) {\n this.currentSet--;\n if (this.currentSet < 0) {\n this.currentSet = this.setsData.length - 1;\n }\n this.render();\n }\n if (event.target.closest('[data-action=\"editnext\"]')) {\n this.editSet++;\n if (this.editSet >= this.setsData.length) {\n this.editSet = 0;\n }\n this.loadQuestionForm();\n }\n if (event.target.closest('[data-action=\"editprev\"]')) {\n this.editSet--;\n if (this.editSet < 0) {\n this.editSet = this.setsData.length - 1;\n }\n this.loadQuestionForm();\n }\n if (event.target.closest('[data-action=\"editset\"]')) {\n this.editSet = parseInt(event.target.dataset.set);\n this.loadQuestionForm();\n }\n if (event.target.closest('[data-action=\"addset\"]')) {\n const newSet = {\n 'name': '',\n 'story': '',\n 'question': '',\n 'answers': [],\n 'markers': []\n };\n this.editSet = this.setsData.length;\n this.names.add(newSet);\n this.loadQuestionForm();\n }\n if (event.target.closest('[data-action=\"deleteset\"]')) {\n \/\/ Remove the current set form this.sets\n this.names.remove(this.editSet);\n this.editSet = this.editSet - 1;\n this.loadQuestionForm();\n }\n if (event.target.closest('[data-action=\"addanswer\"]')) {\n const answerFormField = this.rootElement.querySelector('[data-formfield=\"answer\"]');\n const answer = answerFormField.value.trim();\n if (answer.length > 0) {\n this.setsData[this.editSet].answers.push(answer);\n }\n answerFormField.value = '';\n this.loadAnswersAndMarkers();\n }\n if (event.target.closest('[data-action=\"addmarker\"]')) {\n const markerFormField = this.rootElement.querySelector('[data-formfield=\"marker\"]');\n const marker = markerFormField.value.trim();\n if (marker.length > 0) {\n this.setsData[this.editSet].markers.push(marker);\n }\n markerFormField.value = '';\n this.loadAnswersAndMarkers();\n }\n if (event.target.closest('[data-action=\"removeanswer\"]')) {\n const target = event.target.closest('[data-action=\"removeanswer\"]');\n const index = parseInt(target.dataset.index);\n this.setsData[this.editSet].answers.splice(index, 1);\n this.loadAnswersAndMarkers();\n }\n if (event.target.closest('[data-action=\"removemarker\"]')) {\n const target = event.target.closest('[data-action=\"removemarker\"]');\n const index = parseInt(target.dataset.index);\n this.setsData[this.editSet].markers.splice(index, 1);\n this.loadAnswersAndMarkers();\n }\n if (event.target.matches('[data-action=\"saveset\"]')) {\n this.saveQuestionForm();\n }\n if (event.target.closest('[data-action=\"submitinput\"]')) {\n const input = this.getUserInput();\n if (!input) {\n Toast.add('Please enter a question.', 'error');\n } else {\n this.askAI();\n }\n }\n if (event.target.closest('[data-action=\"markanswer\"]')) {\n const input = this.getMarkerInput();\n if (!input) {\n Toast.add('Please mark the answer.', 'error');\n } else {\n this.askAI();\n }\n }\n });\n\n const getAiResponseButton = this.rootElement.querySelector('[data-action=\"get-ai-response\"]');\n getAiResponseButton.addEventListener('click', async() => {\n this.render();\n await this.askAI();\n });\n\n }\n\n \/**\n * Create the configurator.\n *\/\n async createConfigurator() {\n await this.getModeDescriptions();\n\n \/\/ Configuration options in the first tab.\n const buttons = this.configuratorZone.querySelectorAll('[data-mode]');\n const modeDisplay = this.rootElement.querySelector('[data-region=\"mode-display\"]');\n modeDisplay.innerHTML = this.mode;\n buttons.forEach((button) => {\n if (button.dataset.mode == this.mode) {\n button.classList.add('active');\n } else {\n button.classList.remove('active');\n }\n });\n buttons.forEach((button) => {\n button.addEventListener('click', (event) => {\n buttons.forEach((button) => {\n button.classList.remove('active');\n });\n this.mode = event.target.dataset.mode;\n button.classList.add('active');\n this.getModeDescriptions();\n this.openAiConfig.n = (this.mode == 'bestanswer') ? 4 : 1;\n modeDisplay.innerHTML = this.mode;\n this.storeConfig.setCustomData('mode', this.mode);\n this.render();\n });\n });\n const temperatureRange = this.configuratorZone.querySelector('[data-action=\"temperature\"]');\n const temperatureDisplay = this.configuratorZone.querySelector('[data-region=\"temperature-display\"]');\n temperatureRange.value = this.openAiConfig.temperature;\n temperatureDisplay.innerHTML = this.openAiConfig.temperature;\n temperatureRange.addEventListener('input', (event) => {\n this.openAiConfig.temperature = parseFloat(event.target.value, 1);\n this.storeConfig.setCustomData('temperature', this.openAiConfig.temperature);\n temperatureDisplay.innerHTML = this.openAiConfig.temperature;\n this.render();\n });\n\n const modelSelect = this.configuratorZone.querySelector('[data-action=\"model\"]');\n modelSelect.value = this.openAiConfig.model;\n this.ModelPrice = this.configuratorZone.querySelector('option[value=\"' + this.openAiConfig.model + '\"]').dataset.price;\n modelSelect.addEventListener('change', (event) => {\n this.openAiConfig.model = event.target.value;\n this.storeConfig.setCustomData('model', this.openAiConfig.model);\n this.ModelPrice = this.configuratorZone.querySelector('option[value=\"' + this.openAiConfig.model + '\"]').dataset.price;\n this.render();\n });\n\n const tokenRange = this.configuratorZone.querySelector('[data-action=\"tokens\"]');\n const tokenDisplay = this.configuratorZone.querySelector('[data-region=\"tokens-display\"]');\n tokenRange.value = this.openAiConfig.max_tokens;\n tokenDisplay.innerHTML = this.openAiConfig.max_tokens;\n tokenRange.addEventListener('input', (event) => {\n \/\/ eslint-disable-next-line camelcase\n this.openAiConfig.max_tokens = parseInt(event.target.value);\n tokenDisplay.innerHTML = this.openAiConfig.max_tokens;\n this.storeConfig.setCustomData('maxTokens', event.target.value);\n this.render();\n });\n\n \/\/ Configuration options in the second tab.\n this.loadQuestionForm();\n }\n\n \/**\n * Load the question form.\n *\/\n loadQuestionForm() {\n \/\/ Create the set pagination navigation using the html template.\n const firstNavItem = this.configuratorZone.querySelector('[data-region=\"first-nav-item\"]');\n \/\/ Remove all added pagination items.\n const paginationItems = this.configuratorZone.querySelectorAll('[data-region=\"added-pagination\"]');\n paginationItems.forEach((item) => {\n item.remove();\n });\n let newPagination = '';\n this.setsData.forEach((_set, index) => {\n const paginationItem = this.getTemplate('set-pagination',\n {\n index: index,\n displayvalue: index + 1,\n active: index === this.editSet ? 'active' : ''\n });\n newPagination += paginationItem;\n });\n \/\/ Inser the newPagination into the DOM after the first nav item.\n firstNavItem.insertAdjacentHTML('afterend', newPagination);\n\n\n const questionFormRegion = this.configuratorZone.querySelector('[data-region=\"question-form\"]');\n questionFormRegion.innerHTML = '';\n\n \/\/ Load the form template.\n const questionFormTemplate = this.rootElement.querySelector('[data-template=\"question-form\"]').cloneNode(true);\n const formFieldName = questionFormTemplate.querySelector('[data-formfield=\"name\"]');\n const formFieldStory = questionFormTemplate.querySelector('[data-formfield=\"story\"]');\n const formFieldQuestion = questionFormTemplate.querySelector('[data-formfield=\"question\"]');\n \/\/ Set the form field values.\n if (this.setsData[this.editSet]) {\n formFieldName.value = this.setsData[this.editSet].name;\n formFieldStory.value = this.setsData[this.editSet].story;\n formFieldQuestion.value = this.setsData[this.editSet].question;\n\n }\n\n questionFormRegion.appendChild(questionFormTemplate);\n this.loadAnswersAndMarkers();\n }\n\n loadAnswersAndMarkers() {\n const answersContainer = this.rootElement.querySelector('[data-region=\"answers\"]');\n answersContainer.innerHTML = '';\n const markersContainer = this.rootElement.querySelector('[data-region=\"markers\"]');\n markersContainer.innerHTML = '';\n if (this.setsData[this.editSet]) {\n \/\/ Load the answers.\n this.setsData[this.editSet].answers.forEach((answer, index) => {\n const answerTemplate = this.getTemplate('answer', {answer: answer, index: index});\n answersContainer.insertAdjacentHTML('beforeend', answerTemplate);\n });\n \/\/ Load the markers.\n this.setsData[this.editSet].markers.forEach((marker, index) => {\n const markerTemplate = this.getTemplate('marker', {marker: marker, index: index});\n markersContainer.insertAdjacentHTML('beforeend', markerTemplate);\n });\n }\n }\n\n \/**\n * Save the question form.\n *\/\n saveQuestionForm() {\n const questionFormRegion = this.configuratorZone.querySelector('[data-region=\"question-form\"]');\n const formFieldName = questionFormRegion.querySelector('[data-formfield=\"name\"]');\n const formFieldStory = questionFormRegion.querySelector('[data-formfield=\"story\"]');\n const formFieldQuestion = questionFormRegion.querySelector('[data-formfield=\"question\"]');\n const name = formFieldName.value;\n const story = formFieldStory.value;\n const question = formFieldQuestion.value;\n\n \/\/ Check if the name is not empty.\n if (name === '') {\n Notification.alert('Please enter a name for the set.');\n return;\n }\n\n \/\/ Check if the question is not empty.\n if (question === '') {\n Notification.alert('Please enter a question for the set.');\n return;\n }\n\n \/\/ Check if the name is not already used.\n if (this.setsData.find(set => set.name === name && set !== this.setsData[this.editSet])) {\n Notification.alert('The name is already used.');\n return;\n }\n\n \/\/ Save the data.\n this.setsData[this.editSet].name = name;\n this.setsData[this.editSet].story = story;\n this.setsData[this.editSet].question = question;\n this.names.update(this.editSet, this.setsData[this.editSet]);\n Toast.add('Changes saved.');\n }\n\n \/**\n * Get the mode description strings.\n *\/\n async getModeDescriptions() {\n let requestStrings = [];\n this.modesStringMap = [];\n\n if (this.modesStringMap.length == 0) {\n this.modes.forEach((mode) => {\n requestStrings.push({\n \"key\": mode,\n \"component\": \"teachingapp_openai\"\n });\n });\n let modeStrings = await getStrings(requestStrings)\n .then(fetchedStrings => {\n return fetchedStrings;\n }).catch(Notification.exception);\n\n \/\/ Create a new Array with the mode as key and the description as value.\n this.modes.forEach((mode, index) => {\n this.modesStringMap[mode] = modeStrings[index];\n });\n }\n\n this.modeDescriptionRegion.innerHTML = this.modesStringMap[this.mode];\n return this.modesStringMap;\n }\n\n \/**\n * Replace the template placeholders with the data from the object. The template placeholders are in the format\n * [[property]]. The property is the name of the property in the object.\n * If placeholders are in the [[#property]] format then the property is an array and the template will be repeated\n * for each item in the array.\n * @param {HTMLElement} template - The template element.\n * @param {Object} data - The data object.\n * @returns {string} - The template with the placeholders replaced.\n *\/\n getTemplate(template, data) {\n if (!this.templates[template]) {\n const templateElement = document.querySelector(`[data-template=\"${template}\"]`);\n if (!templateElement) {\n throw new Error(`Template ${template} not found`);\n }\n this.templates[template] = templateElement;\n }\n const templateString = this.templates[template].innerHTML;\n const templatePlaceholders = templateString.match(\/\\[\\[.*?\\]\\]\/g);\n let output = templateString;\n if (!templatePlaceholders) {\n return output;\n }\n templatePlaceholders.forEach((placeholder) => {\n const property = placeholder.replace('[[', '').replace(']]', '');\n if (property.startsWith('#')) {\n const arrayProperty = property.replace('#', '');\n const arrayTemplate = output.match(new RegExp(`(.*?)`, 's'))[1];\n let arrayOutput = '';\n data[arrayProperty].forEach((item) => {\n let itemOutput = arrayTemplate;\n const itemPlaceholders = itemOutput.match(\/\\[\\[.*?\\]\\]\/g);\n itemPlaceholders.forEach((itemPlaceholder) => {\n const itemProperty = itemPlaceholder.replace('[[', '').replace(']]', '');\n itemOutput = itemOutput.replace(itemPlaceholder, item[itemProperty]);\n });\n arrayOutput = arrayOutput + itemOutput;\n });\n output = output.replace(arrayTemplate, arrayOutput);\n } else {\n output = output.replace(placeholder, data[property]);\n }\n });\n return output;\n }\n\n \/**\n * Parse the question sets and prepare the questions for the game. Each set can have a story, a question, a marker and\n * example answers. The questions are stored in the sets array.\n *\/\n parseInput() {\n this.setsData = [];\n this.names.names.forEach((name, index) => {\n if (!name.markers) {\n name.markers = [];\n }\n if (!name.answers) {\n name.answers = [];\n }\n if (!name.name) {\n name.name = 'Question ' + (index + 1);\n }\n const setData = {\n name: name.name,\n story: name.story,\n question: name.question,\n markers: name.markers,\n answers: name.answers,\n };\n this.setsData[index] = setData;\n });\n }\n\n \/**\n * Renders a set, a set is a story, a question, a marker and example answers.\n *\n * @returns {String} The rendered set.\n *\/\n renderSet() {\n const set = this.setsData[this.currentSet];\n let content = '';\n content += this.getTemplate('name', set);\n content += this.getTemplate('story', set);\n content += this.getTemplate('question', set);\n return content;\n }\n\n \/**\n * Render the game.\n * @returns {void}\n *\/\n render() {\n this.clearRegions();\n this.renderQuestionCounterText();\n this.conversationRegion.innerHTML = this.renderSet();\n if (this.mode === 'marking' || this.mode === 'aifeedback') {\n this.input.innerHTML = this.getTemplate('input', {});\n }\n if (this.mode === 'bestanswer' || this.mode === 'examinerfeedback') {\n this.getAiResponseButton.classList.remove('d-none');\n }\n this.updateTokenDisplay();\n this.renderPrompt();\n }\n\n \/**\n * Render the current question counter\n *\/\n renderQuestionCounterText() {\n this.currentQuestionRegion.innerHTML = `${this.currentSet + 1} of ${this.setsData.length}`;\n }\n\n \/**\n * Clear all the regions.\n *\/\n clearRegions() {\n this.lastAIResponse = null;\n this.getAiResponseButton.classList.add('d-none');\n this.conversationRegion.innerHTML = '';\n this.aiResponseRegion = this.rootElement.querySelector('[data-region=\"airesponse\"]');\n this.aiResponseRegion.innerHTML = '';\n this.marker.innerHTML = '';\n this.markerResponse.innerHTML = '';\n this.debugRegion.innerHTML = '';\n this.promptRegion.innerHTML = '';\n this.input.innerHTML = '';\n }\n\n \/**\n * Render the OpenAI response in the debug region.\n * @param {Object} response - The response from OpenAI.\n *\/\n renderDebug(response) {\n this.debugRegion.innerHTML = this.debugRegion.innerHTML + JSON.stringify(response, null, 2) + this.trycount + '
';\n }\n\n \/**\n * Render the prompt send to OpenAI in the debug region.\n *\/\n renderPrompt() {\n if (this.openAiConfig) {\n this.promptRegion.innerHTML = this.getPrompt().text;\n }\n }\n\n \/**\n * Update the token display.\n * @param {Object|Void} response\n *\/\n updateTokenDisplay(response) {\n const tokensUsed = this.rootElement.querySelector('[data-region=\"tokens-used\"]');\n const tokensNow = this.rootElement.querySelector('[data-region=\"tokens-now\"]');\n const tokensCost = this.rootElement.querySelector('[data-region=\"tokens-cost\"]');\n const tokensPrice = this.rootElement.querySelector('[data-region=\"tokens-price\"]');\n tokensPrice.innerHTML = this.ModelPrice;\n let used = 0;\n if (response) {\n used = response.usage.total_tokens;\n }\n tokensNow.innerHTML = used;\n const localStorateUsed = localStorage.getItem('tokensUsed');\n if (localStorateUsed) {\n used = parseInt(localStorateUsed) + used;\n }\n tokensUsed.innerHTML = used;\n tokensCost.innerHTML = parseFloat(parseFloat(this.ModelPrice, 5) * (used \/ 1000), 5).toFixed(2);\n\n localStorage.setItem('tokensUsed', used);\n }\n\n \/**\n * Get the prompt for the question.\n * @returns {string} The prompt.\n *\/\n getPrompt() {\n const setData = this.setsData[this.currentSet];\n\n const prompt = {};\n\n if (this.mode === 'bestanswer') {\n prompt.text = 'You are a student at a college working on an exam, you know none, some or all of the facts below.\\n';\n prompt.text += 'Your confidence is in the range from 0 to 100%.\\n';\n prompt.text += 'Give an answer to the question with a confidence score.\\n ';\n setData.answers.forEach((answer) => {\n prompt.text += 'Fact: ' + answer + '\\n';\n });\n\n if (setData.story) {\n prompt.text += 'The background information for this question is: ' + setData.story;\n }\n\n prompt.text += '\\nQuestion: ' + setData.question + '\\n';\n \/\/ Get a random best answer type and remove it from the array.\n prompt.text += 'Answer + confidence score:' + '\\n';\n }\n\n if (this.mode === 'marking') {\n const userinput = this.getUserInput();\n if (!userinput) {\n prompt.text = 'Waiting for your input...';\n return prompt;\n }\n prompt.text = 'You are a teacher grading an exam question.\\n';\n prompt.text += 'Question: ' + setData.question + '\\n';\n prompt.text += 'The student has given the following answer to the question:\\n';\n prompt.text += 'Answer: ' + this.getUserInput() + '\\n';\n if (setData.markers.length > 0) {\n prompt.text += 'You use the following markers to grade the question:\\n';\n setData.markers.forEach((marker) => {\n prompt.text += 'Marker: ' + marker + '\\n';\n });\n }\n prompt.text += 'Your grade and motivation: ';\n }\n\n if (this.mode === 'aifeedback') {\n const userinput = this.getUserInput();\n if (!userinput) {\n prompt.text = 'Waiting for your input...';\n return prompt;\n }\n prompt.text = 'You are a teacher grading an exam question.\\n';\n prompt.text += 'Question: ' + setData.question + '\\n';\n prompt.text += 'The student has given the following answer to the question:\\n';\n prompt.text += 'Answer: ' + this.getUserInput() + '\\n';\n prompt.text += 'State if the answer is correct and provide some feedback: ';\n }\n\n if (this.mode === 'examinerfeedback' && this.lastAIResponse === null) {\n prompt.text = 'You are a student answering exam questions.\\n';\n if (setData.answers.length > 0) {\n prompt.text += 'You know some or none of the facts below.\\n';\n }\n setData.answers.forEach((answer) => {\n prompt.text += 'Fact: ' + answer + '\\n';\n });\n\n if (setData.story) {\n prompt.text += 'The background information for this question is: ' + setData.story;\n }\n\n prompt.text += '\\nQuestion: ' + setData.question + '\\n';\n \/\/ Get a random best answer type and remove it from the array.\n prompt.text += 'Answer:' + '\\n';\n }\n\n if (this.mode === 'examinerfeedback' && this.lastAIResponse !== null) {\n let markers = '';\n if (setData.markers.length > 0) {\n prompt.text += 'You use the following markers to grade the question:\\n';\n setData.markers.forEach((marker) => {\n markers += 'criterium: ' + marker + '\\n';\n });\n } else {\n Notification.alert('No markers defined for this question.');\n }\n const markerfeedback = this.getMarkerInput();\n if (!markerfeedback) {\n prompt.text = 'Waiting for your feedback...';\n return prompt;\n }\n prompt.text = `You are supporting teachers marking exam questions.\nThe question is:\n${setData.question}\n\nThe student answer is:\n${this.lastAIResponse}\n\nThe criteria for marking the answer are:\n${markers}\n\nThe teacher has marked the student:\n${markerfeedback}\n\nGive the teacher feedback on their marking, indicate if the marking guide was applied correctly\\n`;\n }\n\n return prompt;\n }\n\n \/**\n * Get the user input.\n * @returns {string|boolean} The user input or false if there is no input.\n *\/\n getUserInput() {\n const input = this.input.querySelector('textarea').value;\n if (input) {\n return input.trim();\n } else {\n return false;\n }\n }\n\n \/**\n * Get the marker input.\n * @returns {string|boolean} The marker input or false if there is no input.\n *\/\n getMarkerInput() {\n const input = this.marker.querySelector('textarea').value;\n if (input) {\n return input.trim();\n } else {\n return false;\n }\n }\n\n renderLoadingSvg() {\n const aiResponseRegion = this.aiResponseRegion;\n aiResponseRegion.innerHTML = '';\n\n const loadingSvg = document.createElement('div');\n loadingSvg.classList.add('loading', 'col-12', 'text-center', 'my-5', 'h2');\n\n const loadingText = document.createElement('div');\n loadingText.classList.add('loading-text');\n loadingText.innerHTML = 'Loading...';\n\n loadingSvg.appendChild(loadingText);\n aiResponseRegion.appendChild(loadingSvg);\n\n this.robot.classList.add('robot_speaking');\n }\n \/**\n * Ask the AI a question.\n *\/\n async askAI() {\n const prompt = this.getPrompt();\n this.renderPrompt();\n this.renderLoadingSvg();\n \/\/ Get the response from the AI for each prompt.\n this.hasresponse = false;\n let response = {};\n this.trycount = 0;\n\n while (this.hasresponse == false && this.trycount < this.maxTry) {\n response = await this.openAiRequest(prompt.text);\n response.choices.forEach((choice) => {\n if (choice.text) {\n if (choice.text.trim() != '' && choice.text.trim() != ' ') {\n this.hasresponse = true;\n }\n }\n if (choice.message) {\n if (choice.message.content.trim() != '' && choice.message.content.trim() != ' ') {\n this.hasresponse = true;\n }\n }\n });\n this.trycount++;\n }\n\n this.robot.classList.remove('robot_speaking');\n \/\/ Render the response.\n this.aiResponseRegion.innerHTML = '';\n let containerclass = response.choices.length > 3 ? 'col-4' : 'col-12';\n if (this.mode === 'examinerfeedback') {\n containerclass = 'col-12';\n }\n response.choices.forEach((choice) => {\n let responseTemplate = {};\n if (choice.text) {\n responseTemplate = {\n containerclass: containerclass,\n response: choice.text,\n finish: choice.finish_reason,\n };\n this.lastAIResponse = choice.text;\n }\n if (choice.message) {\n responseTemplate = {\n containerclass: containerclass,\n response: choice.message.content,\n finish: choice.finish_reason,\n };\n this.lastAIResponse = choice.message.content;\n }\n const rendered = this.getTemplate('airesponse', responseTemplate);\n this.aiResponseRegion.innerHTML += rendered;\n });\n\n if (this.mode === 'examinerfeedback') {\n let markers = '';\n const setData = this.setsData[this.currentSet];\n if (!setData.markers) {\n setData.markers = [];\n }\n setData.markers.forEach((marker) => {\n markers += this.getTemplate('markeritem', {text: marker});\n });\n\n this.marker.innerHTML = this.getTemplate('markerfeedback', {markers: markers});\n this.getAiResponseButton.classList.add('d-none');\n this.aiResponseRegion = this.rootElement.querySelector('[data-region=\"aimarker-response\"]');\n }\n }\n\n \/**\n * Send a message to the AI.\n * @param {String} prompt The prompt to send.\n * @returns {Object} The response from the AI.\n * @throws {Error} If the AI is not available.\n * @throws {Error} If the AI returns an error.\n * @throws {Error} If the AI returns an empty response.\n *\/\n async openAiRequest(prompt) {\n const config = JSON.parse(JSON.stringify(this.openAiConfig));\n\n const url = 'https:\/\/api.openai.com\/v1\/chat\/completions';\n config.messages = [\n {\n role: \"system\",\n content: \"You are a student answering exam questions.\",\n },\n {\n role: \"user\",\n content: prompt,\n }\n ];\n config.model = 'gpt-4o-mini';\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application\/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify(config),\n });\n if (response.status !== 200) {\n this.aiResponseRegion.innerHTML = '';\n\n const alert = document.createElement('div');\n alert.classList.add('alert', 'alert-danger');\n alert.innerHTML = `The AI is not available. Please try again later. (${response.status})`;\n this.aiResponseRegion.appendChild(alert);\n\n throw new Error(`OpenAI returned status ${response.status}`);\n }\n const responseJson = await response.json();\n if (responseJson.error) {\n throw new Error(responseJson.error);\n }\n this.renderDebug(responseJson);\n this.updateTokenDisplay(responseJson);\n if (!responseJson.choices || responseJson.choices.length === 0) {\n throw new Error('Empty response from OpenAI');\n }\n return responseJson;\n }\n}\n\nconst init = async(cmid) => {\n const GE = new GameEngineLoader('openai', cmid);\n await GE.load();\n const game = new GameElement(GE.names, GE.rootElement);\n GE.run(game);\n};\n\nexport default {\n init: init,\n GameElement\n};\n"],"names":["defaultNames","name","story","question","answers","markers","GameElement","constructor","names","rootElement","this","length","storeConfig","StoreConfigElement","drawZone","querySelector","input","marker","markerResponse","aiResponseRegion","conversationRegion","debugRegion","promptRegion","modeDescriptionRegion","configuratorZone","currentQuestionRegion","getAiResponseButton","robot","lastAIResponse","templates","sets","setsData","currentSet","editSet","modes","runGame","parseInput","render","setAiConfig","addEventListeners","createConfigurator","temperature","getCustomData","maxTokens","frequencyPenalty","presencePenalty","model","mode","apiKey","openAiConfig","n","parseFloat","max_tokens","parseInt","frequency_penalty","presence_penalty","stop","maxTry","trycount","addEventListener","event","target","closest","loadQuestionForm","dataset","set","newSet","add","remove","answerFormField","answer","value","trim","push","loadAnswersAndMarkers","markerFormField","index","splice","matches","saveQuestionForm","getUserInput","askAI","Toast","getMarkerInput","async","getModeDescriptions","buttons","querySelectorAll","modeDisplay","innerHTML","forEach","button","classList","setCustomData","temperatureRange","temperatureDisplay","modelSelect","ModelPrice","price","tokenRange","tokenDisplay","firstNavItem","item","newPagination","_set","paginationItem","getTemplate","displayvalue","active","insertAdjacentHTML","questionFormRegion","questionFormTemplate","cloneNode","formFieldName","formFieldStory","formFieldQuestion","appendChild","answersContainer","markersContainer","answerTemplate","markerTemplate","find","alert","update","requestStrings","modesStringMap","modeStrings","then","fetchedStrings","catch","Notification","exception","template","data","templateElement","document","Error","templateString","templatePlaceholders","match","output","placeholder","property","replace","startsWith","arrayProperty","arrayTemplate","RegExp","arrayOutput","itemOutput","itemPlaceholder","itemProperty","setData","renderSet","content","clearRegions","renderQuestionCounterText","updateTokenDisplay","renderPrompt","renderDebug","response","JSON","stringify","getPrompt","text","tokensUsed","tokensNow","tokensCost","used","usage","total_tokens","localStorateUsed","localStorage","getItem","toFixed","setItem","prompt","markerfeedback","renderLoadingSvg","loadingSvg","createElement","loadingText","hasresponse","openAiRequest","choices","choice","message","containerclass","responseTemplate","finish","finish_reason","rendered","config","parse","messages","role","fetch","method","headers","Authorization","body","status","responseJson","json","error","init","GE","GameEngineLoader","cmid","load","game","run"],"mappings":"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\/F,cAAc,2BACjDmG,eAAiBJ,mBAAmB\/F,cAAc,4BAClDoG,kBAAoBL,mBAAmB\/F,cAAc,+BACrDd,KAAOgH,cAAc1C,MACrBrE,MAAQgH,eAAe3C,MACvBpE,SAAWgH,kBAAkB5C,MAGtB,KAATtE,KAMa,KAAbE,SAMAO,KAAKqB,SAAS0F,MAAKxD,KAAOA,IAAIhE,OAASA,MAAQgE,MAAQvD,KAAKqB,SAASrB,KAAKuB,iCAC7DyF,MAAM,mCAKlB3F,SAASrB,KAAKuB,SAAShC,KAAOA,UAC9B8B,SAASrB,KAAKuB,SAAS\/B,MAAQA,WAC\/B6B,SAASrB,KAAKuB,SAAS9B,SAAWA,cAClCK,MAAMmH,OAAOjH,KAAKuB,QAASvB,KAAKqB,SAASrB,KAAKuB,UACnDiD,MAAMf,IAAI,yCAfOuD,MAAM,8DANNA,MAAM,oEA4BnBE,eAAiB,WAChBC,eAAiB,GAEY,GAA9BnH,KAAKmH,eAAelH,OAAa,MAC5BuB,MAAMwD,SAAS3C,OAChB6E,eAAenD,KAAK,KACT1B,eACM,8BAGjB+E,kBAAoB,oBAAWF,gBAC9BG,MAAKC,gBACKA,iBACRC,MAAMC,sBAAaC,gBAGrBjG,MAAMwD,SAAQ,CAAC3C,KAAM6B,cACjBiD,eAAe9E,MAAQ+E,YAAYlD,sBAI3CrD,sBAAsBkE,UAAY\/E,KAAKmH,eAAenH,KAAKqC,MACzDrC,KAAKmH,eAYhBnB,YAAY0B,SAAUC,UACb3H,KAAKmB,UAAUuG,UAAW,OACrBE,gBAAkBC,SAASxH,wCAAiCqH,oBAC7DE,sBACK,IAAIE,yBAAkBJ,6BAE3BvG,UAAUuG,UAAYE,sBAEzBG,eAAiB\/H,KAAKmB,UAAUuG,UAAU3C,UAC1CiD,qBAAuBD,eAAeE,MAAM,oBAC9CC,OAASH,sBACRC,sBAGLA,qBAAqBhD,SAASmD,oBACpBC,SAAWD,YAAYE,QAAQ,KAAM,IAAIA,QAAQ,KAAM,OACzDD,SAASE,WAAW,KAAM,OACpBC,cAAgBH,SAASC,QAAQ,IAAK,IACtCG,cAAgBN,OAAOD,MAAM,IAAIQ,yBAAeF,6CAA8BA,yBAAqB,MAAM,OAC3GG,YAAc,GAClBf,KAAKY,eAAevD,SAASY,WACrB+C,WAAaH,cACQG,WAAWV,MAAM,gBACzBjD,SAAS4D,wBAChBC,aAAeD,gBAAgBP,QAAQ,KAAM,IAAIA,QAAQ,KAAM,IACrEM,WAAaA,WAAWN,QAAQO,gBAAiBhD,KAAKiD,kBAE1DH,aAA4BC,cAEhCT,OAASA,OAAOG,QAAQG,cAAeE,kBAEvCR,OAASA,OAAOG,QAAQF,YAAaR,KAAKS,cAG3CF,QAtBIA,OA6BfxG,kBACSL,SAAW,QACXvB,MAAMA,MAAMkF,SAAQ,CAACzF,KAAM2E,SACvB3E,KAAKI,UACNJ,KAAKI,QAAU,IAEdJ,KAAKG,UACNH,KAAKG,QAAU,IAEdH,KAAKA,OACNA,KAAKA,KAAO,aAAe2E,MAAQ,UAEjC4E,QAAU,CACZvJ,KAAMA,KAAKA,KACXC,MAAOD,KAAKC,MACZC,SAAUF,KAAKE,SACfE,QAASJ,KAAKI,QACdD,QAASH,KAAKG,cAEb2B,SAAS6C,OAAS4E,WAS\/BC,kBACUxF,IAAMvD,KAAKqB,SAASrB,KAAKsB,gBAC3B0H,QAAU,UACdA,SAAWhJ,KAAKgG,YAAY,OAAQzC,KACpCyF,SAAWhJ,KAAKgG,YAAY,QAASzC,KACrCyF,SAAWhJ,KAAKgG,YAAY,WAAYzC,KACjCyF,QAOXrH,cACSsH,oBACAC,iCACAxI,mBAAmBqE,UAAY\/E,KAAK+I,YACvB,YAAd\/I,KAAKqC,MAAoC,eAAdrC,KAAKqC,YAC3B\/B,MAAMyE,UAAY\/E,KAAKgG,YAAY,QAAS,KAEnC,eAAdhG,KAAKqC,MAAuC,qBAAdrC,KAAKqC,WAC9BrB,oBAAoBkE,UAAUxB,OAAO,eAEzCyF,0BACAC,eAMTF,iCACSnI,sBAAsBgE,oBAAe\/E,KAAKsB,WAAa,iBAAQtB,KAAKqB,SAASpB,QAMtFgJ,oBACS\/H,eAAiB,UACjBF,oBAAoBkE,UAAUzB,IAAI,eAClC\/C,mBAAmBqE,UAAY,QAC\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"}