Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"placement.min.js","sources":["../src/placement.js"],"sourcesContent":["// 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 <http://www.gnu.org/licenses/>.\n\n/**\n * Module to load and render the tools for the AI assist plugin.\n *\n * @module     aiplacement_courseassist/placement\n * @copyright  2024 Huong Nguyen <huongnv13@gmail.com>\n * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Ajax from 'core/ajax';\nimport 'core/copy_to_clipboard';\nimport Notification from 'core/notification';\nimport Selectors from 'aiplacement_courseassist/selectors';\nimport Policy from 'core_ai/policy';\nimport AIHelper from 'core_ai/helper';\nimport DrawerEvents from 'core/drawer_events';\nimport {subscribe} from 'core/pubsub';\nimport * as MessageDrawerHelper from 'core_message/message_drawer_helper';\nimport {getString} from 'core/str';\nimport * as FocusLock from 'core/local/aria/focuslock';\nimport {isSmall} from \"core/pagehelpers\";\n\nconst AICourseAssist = class {\n\n    /**\n     * The user ID.\n     * @type {Integer}\n     */\n    userId;\n    /**\n     * The context ID.\n     * @type {Integer}\n     */\n    contextId;\n\n    /**\n     * Constructor.\n     * @param {Integer} userId The user ID.\n     * @param {Integer} contextId The context ID.\n     */\n    constructor(userId, contextId) {\n        this.userId = userId;\n        this.contextId = contextId;\n\n        this.aiDrawerElement = document.querySelector(Selectors.ELEMENTS.AIDRAWER);\n        this.aiDrawerBodyElement = document.querySelector(Selectors.ELEMENTS.AIDRAWER_BODY);\n        this.pageElement = document.querySelector(Selectors.ELEMENTS.PAGE);\n        this.jumpToElement = document.querySelector(Selectors.ELEMENTS.JUMPTO);\n        this.actionElement = document.querySelector(Selectors.ELEMENTS.ACTION);\n        this.aiDrawerCloseElement = this.aiDrawerElement.querySelector(Selectors.ELEMENTS.AIDRAWER_CLOSE);\n        this.lastAction = '';\n        this.responses = new Map();\n        this.isDrawerFocusLocked = false;\n\n        this.registerEventListeners();\n    }\n\n    /**\n     * Register event listeners.\n     */\n    registerEventListeners() {\n        document.addEventListener('click', async(e) => {\n            // Display summarise.\n            const summariseAction = e.target.closest(Selectors.ACTIONS.SUMMARY);\n            if (summariseAction) {\n                e.preventDefault();\n                this.openAIDrawer();\n                this.lastAction = 'summarise';\n                this.actionElement.focus();\n                const isPolicyAccepted = await this.isPolicyAccepted();\n                if (!isPolicyAccepted) {\n                    // Display policy.\n                    this.displayPolicy();\n                    return;\n                }\n                this.displayAction(this.lastAction);\n            }\n            // Display explain.\n            const explainAction = e.target.closest(Selectors.ACTIONS.EXPLAIN);\n            if (explainAction) {\n                e.preventDefault();\n                this.openAIDrawer();\n                this.lastAction = 'explain';\n                this.actionElement.focus();\n                const isPolicyAccepted = await this.isPolicyAccepted();\n                if (!isPolicyAccepted) {\n                    // Display policy.\n                    this.displayPolicy();\n                    return;\n                }\n                this.displayAction(this.lastAction);\n            }\n            // Close AI drawer.\n            const closeAiDrawer = e.target.closest(Selectors.ELEMENTS.AIDRAWER_CLOSE);\n            if (closeAiDrawer) {\n                e.preventDefault();\n                this.closeAIDrawer();\n            }\n        });\n\n        document.addEventListener('keydown', e => {\n            if (this.isAIDrawerOpen() && e.key === 'Escape') {\n                this.closeAIDrawer();\n            }\n        });\n\n        // Close AI drawer if message drawer is shown.\n        subscribe(DrawerEvents.DRAWER_SHOWN, () => {\n            if (this.isAIDrawerOpen()) {\n                this.closeAIDrawer();\n            }\n        });\n\n        // Focus on the AI drawer's close button when the jump-to element is focused.\n        this.jumpToElement.addEventListener('focus', () => {\n            this.aiDrawerCloseElement.focus();\n        });\n\n        // Focus on the action element when the AI drawer container receives focus.\n        this.aiDrawerElement.addEventListener('focus', () => {\n            this.actionElement.focus();\n        });\n\n        // Remove active from the action element when it loses focus.\n        this.actionElement.addEventListener('blur', () => {\n            this.actionElement.classList.remove('active');\n        });\n    }\n\n    /**\n     * Register event listeners for the policy.\n     */\n    registerPolicyEventListeners() {\n        const acceptAction = document.querySelector(Selectors.ACTIONS.ACCEPT);\n        const declineAction = document.querySelector(Selectors.ACTIONS.DECLINE);\n        if (acceptAction && this.lastAction.length) {\n            acceptAction.addEventListener('click', (e) => {\n                e.preventDefault();\n                this.acceptPolicy().then(() => {\n                    return this.displayAction(this.lastAction);\n                }).catch(Notification.exception);\n            });\n        }\n        if (declineAction) {\n            declineAction.addEventListener('click', (e) => {\n                e.preventDefault();\n                this.closeAIDrawer();\n            });\n        }\n    }\n\n    /**\n     * Register event listeners for the error.\n     */\n    registerErrorEventListeners() {\n        const retryAction = document.querySelector(Selectors.ACTIONS.RETRY);\n        if (retryAction && this.lastAction.length) {\n            retryAction.addEventListener('click', (e) => {\n                e.preventDefault();\n                this.displayAction(this.lastAction);\n            });\n        }\n    }\n\n    /**\n     * Register event listeners for the responses.\n     */\n    registerResponseEventListeners() {\n        // Get all regenerate action buttons (one per response in the AI drawer).\n        const regenerateActions = document.querySelectorAll(Selectors.ACTIONS.REGENERATE);\n        // Add event listeners for each regenerate action.\n        regenerateActions.forEach(regenerateAction => {\n            const responseElement = regenerateAction.closest(Selectors.ELEMENTS.RESPONSE);\n            if (regenerateAction && responseElement) {\n                // Get the action that this response is associated with.\n                const actionPerformed = responseElement.getAttribute('data-action-performed');\n                regenerateAction.addEventListener('click', (e) => {\n                    e.preventDefault();\n                    // Remove the old response before displaying the new one.\n                    this.removeResponseFromStack(actionPerformed);\n                    this.displayAction(actionPerformed);\n                });\n            }\n        });\n    }\n\n    registerLoadingEventListeners() {\n        const cancelAction = document.querySelector(Selectors.ACTIONS.CANCEL);\n        if (cancelAction) {\n            cancelAction.addEventListener('click', (e) => {\n                e.preventDefault();\n                this.setRequestCancelled();\n                this.toggleAIDrawer();\n                this.removeResponseFromStack('loading');\n                // Refresh the response stack to avoid false indication of loading.\n                const responses = this.getResponseStack();\n                this.aiDrawerBodyElement.innerHTML = responses;\n            });\n        }\n    }\n\n    /**\n     * Check if the AI drawer is open.\n     * @return {boolean} True if the AI drawer is open, false otherwise.\n     */\n    isAIDrawerOpen() {\n        return this.aiDrawerElement.classList.contains('show');\n    }\n\n    /**\n     * Check if the request is cancelled.\n     * @return {boolean} True if the request is cancelled, false otherwise.\n     */\n    isRequestCancelled() {\n        return this.aiDrawerBodyElement.dataset.cancelled === '1';\n    }\n\n    setRequestCancelled() {\n        this.aiDrawerBodyElement.dataset.cancelled = '1';\n    }\n\n    /**\n     * Open the AI drawer.\n     */\n    openAIDrawer() {\n        // Close message drawer if it is shown.\n        MessageDrawerHelper.hide();\n        this.aiDrawerElement.classList.add('show');\n        this.aiDrawerElement.setAttribute('tabindex', 0);\n        this.aiDrawerBodyElement.setAttribute('aria-live', 'polite');\n        if (!this.pageElement.classList.contains('show-drawer-right')) {\n            this.addPadding();\n        }\n        this.jumpToElement.setAttribute('tabindex', 0);\n        this.jumpToElement.focus();\n\n        // If the AI drawer is opened on a small screen, we need to trap the focus tab within the AI drawer.\n        if (isSmall()) {\n            FocusLock.trapFocus(this.aiDrawerElement);\n            this.aiDrawerElement.setAttribute('aria-modal', 'true');\n            this.aiDrawerElement.setAttribute('role', 'dialog');\n            this.isDrawerFocusLocked = true;\n        }\n    }\n\n    /**\n     * Close the AI drawer.\n     */\n    closeAIDrawer() {\n        // Untrap focus if it was locked.\n        if (this.isDrawerFocusLocked) {\n            FocusLock.untrapFocus();\n            this.aiDrawerElement.removeAttribute('aria-modal');\n            this.aiDrawerElement.setAttribute('role', 'region');\n        }\n\n        this.aiDrawerElement.classList.remove('show');\n        this.aiDrawerElement.setAttribute('tabindex', -1);\n        this.aiDrawerBodyElement.removeAttribute('aria-live');\n        if (this.pageElement.classList.contains('show-drawer-right') && this.aiDrawerBodyElement.dataset.removepadding === '1') {\n            this.removePadding();\n        }\n        this.jumpToElement.setAttribute('tabindex', -1);\n\n        // We can enforce a focus-visible state on the focus element using element.focus({focusVisible: true}).\n        // Unfortunately, this feature isn't supported in all browsers, only Firefox provides support for it.\n        // Therefore, we will apply the active class to the action element and set focus on it.\n        // This action will make the action element appear focused.\n        // When the action element loses focus,\n        // we will remove the active class at {@see registerEventListeners()}\n        this.actionElement.classList.add('active');\n        this.actionElement.focus();\n    }\n\n    /**\n     * Toggle the AI drawer.\n     */\n    toggleAIDrawer() {\n        if (this.isAIDrawerOpen()) {\n            this.closeAIDrawer();\n        } else {\n            this.openAIDrawer();\n        }\n    }\n\n    /**\n     * Add padding to the page to make space for the AI drawer.\n     */\n    addPadding() {\n        this.pageElement.classList.add('show-drawer-right');\n        this.aiDrawerBodyElement.dataset.removepadding = '1';\n    }\n\n    /**\n     * Remove padding from the page.\n     */\n    removePadding() {\n        this.pageElement.classList.remove('show-drawer-right');\n        this.aiDrawerBodyElement.dataset.removepadding = '0';\n    }\n\n    /**\n     * Get important params related to the action.\n     * @param {string} action The action to use.\n     * @returns {object} The params to use for the action.\n     */\n    async getParamsForAction(action) {\n        let params = {};\n\n        switch (action) {\n            case 'summarise':\n                params.method = 'aiplacement_courseassist_summarise_text';\n                params.heading = await getString('aisummary', 'aiplacement_courseassist');\n                break;\n\n            case 'explain':\n                params.method = 'aiplacement_courseassist_explain_text';\n                params.heading = await getString('aiexplain', 'aiplacement_courseassist');\n                break;\n        }\n\n        return params;\n    }\n\n    /**\n     * Check if the policy is accepted.\n     * @return {bool} True if the policy is accepted, false otherwise.\n     */\n    async isPolicyAccepted() {\n        return await Policy.getPolicyStatus(this.userId);\n    }\n\n    /**\n     * Accept the policy.\n     * @return {Promise<Object>}\n     */\n    acceptPolicy() {\n        return Policy.acceptPolicy();\n    }\n\n    /**\n     * Check if the AI drawer has already generated content for a particular action.\n     * @param {string} action The action to check.\n     * @return {boolean} True if the AI drawer has generated content, false otherwise.\n     */\n    hasGeneratedContent(action) {\n        return this.responses.has(action);\n    }\n\n    /**\n     * Display the policy.\n     */\n    displayPolicy() {\n        Templates.render('core_ai/policyblock', {}).then((html) => {\n            this.aiDrawerBodyElement.innerHTML = html;\n            this.registerPolicyEventListeners();\n            return;\n        }).catch(Notification.exception);\n    }\n\n    /**\n     * Display the loading spinner.\n     */\n    displayLoading() {\n        Templates.render('aiplacement_courseassist/loading', {}).then((html) => {\n            this.addResponseToStack('loading', html);\n            const responses = this.getResponseStack();\n            this.aiDrawerBodyElement.innerHTML = responses;\n            this.registerLoadingEventListeners();\n            return;\n        }).then(() => {\n            this.removeResponseFromStack('loading');\n            return;\n        }).catch(Notification.exception);\n    }\n\n    /**\n     * Display the action result in the AI drawer.\n     * @param {string} action The action to display.\n     */\n    async displayAction(action) {\n        if (this.hasGeneratedContent(action)) {\n            // Scroll to generated content.\n            const existingReponse = document.querySelector('[data-action-performed=\"' + action + '\"]');\n            if (existingReponse) {\n                this.aiDrawerBodyElement.scrollTop = existingReponse.offsetTop;\n            }\n        } else {\n            // Display loading spinner.\n            this.displayLoading();\n            // Clear the drawer to prevent including the previously generated response in the new response prompt.\n            this.aiDrawerBodyElement.innerHTML = '';\n            const params = await this.getParamsForAction(action);\n            const request = {\n                methodname: params.method,\n                args: {\n                    contextid: this.contextId,\n                    prompttext: this.getTextContent(),\n                }\n            };\n            try {\n                const responseObj = await Ajax.call([request])[0];\n                if (responseObj.error) {\n                    this.displayError();\n                    return;\n                } else {\n                    if (!this.isRequestCancelled()) {\n                        // Perform replacements on the generated context to ensure it is formatted correctly.\n                        const generatedContent = AIHelper.formatResponse(responseObj.generatedcontent);\n                        this.displayResponse(generatedContent, action);\n                        return;\n                    } else {\n                        this.aiDrawerBodyElement.dataset.cancelled = '0';\n                    }\n                }\n            } catch (error) {\n                window.console.log(error);\n                this.displayError();\n            }\n        }\n    }\n\n    /**\n     * Add the HTML response to the response stack.\n     * The stack will be used to display all responses in the AI drawer.\n     * @param {String} action The action key.\n     * @param {String} html The HTML to store.\n     */\n    addResponseToStack(action, html) {\n        this.responses.set(action, html);\n    }\n\n    /**\n     * Remove a stored response, allowing for a regenerated one.\n     * @param {String} action The action key.\n     */\n    removeResponseFromStack(action) {\n        if (this.responses.has(action)) {\n            this.responses.delete(action);\n        }\n    }\n\n    /**\n     * Return a stack of HTML responses.\n     * @return {String} HTML responses.\n     */\n    getResponseStack() {\n        let stack = '';\n        // Reverse to get newest first.\n        const responses = [...this.responses.values()].reverse();\n        for (const response of responses) {\n            stack += response;\n        }\n        return stack;\n    }\n\n    /**\n     * Display the responses.\n     * @param {String} content The content to display.\n     * @param {String} action The action used.\n     */\n    async displayResponse(content, action) {\n        const params = await this.getParamsForAction(action);\n        const args = {\n            content: content,\n            heading: params.heading,\n            action: action,\n        };\n        Templates.render('aiplacement_courseassist/response', args).then((html) => {\n            this.addResponseToStack(action, html);\n            const responses = this.getResponseStack();\n            this.aiDrawerBodyElement.innerHTML = responses;\n            this.registerResponseEventListeners();\n            return;\n        }).catch(Notification.exception);\n    }\n\n    /**\n     * Display the error.\n     */\n    displayError() {\n        Templates.render('aiplacement_courseassist/error', {}).then((html) => {\n            this.addResponseToStack('error', html);\n            const responses = this.getResponseStack();\n            this.aiDrawerBodyElement.innerHTML = responses;\n            this.registerErrorEventListeners();\n            return;\n        }).then(() => {\n            this.removeResponseFromStack('error');\n            return;\n        }).catch(Notification.exception);\n    }\n\n    /**\n     * Get the text content of the main region.\n     * @return {String} The text content.\n     */\n    getTextContent() {\n        const mainRegion = document.querySelector(Selectors.ELEMENTS.MAIN_REGION);\n        return mainRegion.innerText || mainRegion.textContent;\n    }\n};\n\nexport default AICourseAssist;\n"],"names":["constructor","userId","contextId","aiDrawerElement","document","querySelector","Selectors","ELEMENTS","AIDRAWER","aiDrawerBodyElement","AIDRAWER_BODY","pageElement","PAGE","jumpToElement","JUMPTO","actionElement","ACTION","aiDrawerCloseElement","this","AIDRAWER_CLOSE","lastAction","responses","Map","isDrawerFocusLocked","registerEventListeners","addEventListener","async","e","target","closest","ACTIONS","SUMMARY","preventDefault","openAIDrawer","focus","isPolicyAccepted","displayPolicy","displayAction","EXPLAIN","closeAIDrawer","isAIDrawerOpen","key","DrawerEvents","DRAWER_SHOWN","classList","remove","registerPolicyEventListeners","acceptAction","ACCEPT","declineAction","DECLINE","length","acceptPolicy","then","catch","Notification","exception","registerErrorEventListeners","retryAction","RETRY","registerResponseEventListeners","querySelectorAll","REGENERATE","forEach","regenerateAction","responseElement","RESPONSE","actionPerformed","getAttribute","removeResponseFromStack","registerLoadingEventListeners","cancelAction","CANCEL","setRequestCancelled","toggleAIDrawer","getResponseStack","innerHTML","contains","isRequestCancelled","dataset","cancelled","MessageDrawerHelper","hide","add","setAttribute","addPadding","FocusLock","trapFocus","untrapFocus","removeAttribute","removepadding","removePadding","action","params","method","heading","Policy","getPolicyStatus","hasGeneratedContent","has","render","html","displayLoading","addResponseToStack","existingReponse","scrollTop","offsetTop","request","methodname","getParamsForAction","args","contextid","prompttext","getTextContent","responseObj","Ajax","call","error","displayError","generatedContent","AIHelper","formatResponse","generatedcontent","displayResponse","window","console","log","set","delete","stack","values","reverse","response","content","mainRegion","MAIN_REGION","innerText","textContent"],"mappings":"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"}