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.htm
l 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 dra
wer 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 de
clineAction.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 = r
egenerateAction.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.removeResponseFro
mStack('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.cla
ssList.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.acti
onElement.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 resp
onse 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","o
ffsetTop","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":"qqEAqCuB,MAkBnBA,YAAYC,OAAQC,+FACXD,OAASA,YACTC,UAAYA,eAEZC,gBAAkBC,SAASC,cAAcC,mBAAUC,SAASC,eAC5DC,oBAAsBL,SAASC,cAAcC,mBAAUC,SAASG,oBAChEC,YAAcP,SAASC,cAAcC,mBAAUC,SAASK,WACxDC,cAAgBT,SAASC,cAAcC,mBAAUC,SAASO,aAC1DC,cAAgBX,SAASC,cAAcC,mBAAUC,SAASS,aAC1DC,qBAAuBC,KAAKf,gBAAgBE,cAAcC,mBAAUC,SAASY,qBAC7EC,WAAa,QACbC,UAAY,IAAIC,SAChBC,qBAAsB,OAEtBC,yBAMTA,yBACIpB,SAASqB,iBAAiB,SAASC,MAAAA,OAEPC,EAAEC,OAAOC,QAAQvB,mBAAUwB,QAAQC,SACtC,CACjBJ,EAAEK,sBACGC,oBACAb,WAAa,iBACbL,cAAcmB,kBACYhB,KAAKiB,oCAG3BC,qBAGJC,cAAcnB,KAAKE,eAGNO,EAAEC,OAAOC,QAAQvB,mBAAUwB,QAAQQ,SACtC,CACfX,EAAEK,sBACGC,oBACAb,WAAa,eACbL,cAAcmB,kBACYhB,KAAKiB,oCA
G3BC,qBAGJC,cAAcnB,KAAKE,YAGNO,EAAEC,OAAOC,QAAQvB,mBAAUC,SAASY,kBAEtDQ,EAAEK,sBACGO,oBAIbnC,SAASqB,iBAAiB,WAAWE,IAC7BT,KAAKsB,kBAA8B,WAAVb,EAAEc,UACtBF,yCAKHG,uBAAaC,cAAc,KAC7BzB,KAAKsB,uBACAD,wBAKR1B,cAAcY,iBAAiB,SAAS,UACpCR,qBAAqBiB,gBAIzB/B,gBAAgBsB,iBAAiB,SAAS,UACtCV,cAAcmB,gBAIlBnB,cAAcU,iBAAiB,QAAQ,UACnCV,cAAc6B,UAAUC,OAAO,aAO5CC,qCACUC,aAAe3C,SAASC,cAAcC,mBAAUwB,QAAQkB,QACxDC,cAAgB7C,SAASC,cAAcC,mBAAUwB,QAAQoB,SAC3DH,cAAgB7B,KAAKE,WAAW+B,QAChCJ,aAAatB,iBAAiB,SAAUE,IACpCA,EAAEK,sBACGoB,eAAeC,MAAK,IACdnC,KAAKmB,cAAcnB,KAAKE,cAChCkC,MAAMC,sBAAaC,cAG1BP,eACAA,cAAcxB,iBAAiB,SAAUE,IACrCA,EAAEK,sBACGO,mBAQjBkB,oCACUC,YAActD,SAASC,cAAcC,mBAAUwB,QAAQ6B,OACzDD,aAAexC,KAAKE,WAAW+B,QAC/BO,YAAYjC,iBAAiB,SAAUE,IACnCA,EAAEK,sBACGK,cAAcnB,KAAKE,eAQpCwC,iCAE8BxD,SAASyD,iBAAiBvD,mBAAUwB,QAAQgC,YAEpDC,SAAQC,yBAChBC,gBAAkBD,iBAAiBnC,QAAQvB,mBAAUC,SAAS2D,aAChEF,kBAAoBC,gBAAiB,OAE/BE,gBAAkBF,gBAAgBG,aAAa,yBACrDJ,iBAAiBvC,iBAAiB,SAAUE,IACxCA,EAAEK,sBAEGqC,wBAAwBF,sBACxB9B,cAAc8B,wBAMnCG,sCACUC,aAAenE,SAASC,cAAcC,mBAAUwB,QAA
Q0C,QAC1DD,cACAA,aAAa9C,iBAAiB,SAAUE,IACpCA,EAAEK,sBACGyC,2BACAC,sBACAL,wBAAwB,iBAEvBhD,UAAYH,KAAKyD,wBAClBlE,oBAAoBmE,UAAYvD,aASjDmB,wBACWtB,KAAKf,gBAAgByC,UAAUiC,SAAS,QAOnDC,2BAC0D,MAA/C5D,KAAKT,oBAAoBsE,QAAQC,UAG5CP,2BACShE,oBAAoBsE,QAAQC,UAAY,IAMjD/C,eAEIgD,oBAAoBC,YACf/E,gBAAgByC,UAAUuC,IAAI,aAC9BhF,gBAAgBiF,aAAa,WAAY,QACzC3E,oBAAoB2E,aAAa,YAAa,UAC9ClE,KAAKP,YAAYiC,UAAUiC,SAAS,2BAChCQ,kBAEJxE,cAAcuE,aAAa,WAAY,QACvCvE,cAAcqB,SAGf,4BACAoD,UAAUC,UAAUrE,KAAKf,sBACpBA,gBAAgBiF,aAAa,aAAc,aAC3CjF,gBAAgBiF,aAAa,OAAQ,eACrC7D,qBAAsB,GAOnCgB,gBAEQrB,KAAKK,sBACL+D,UAAUE,mBACLrF,gBAAgBsF,gBAAgB,mBAChCtF,gBAAgBiF,aAAa,OAAQ,gBAGzCjF,gBAAgByC,UAAUC,OAAO,aACjC1C,gBAAgBiF,aAAa,YAAa,QAC1C3E,oBAAoBgF,gBAAgB,aACrCvE,KAAKP,YAAYiC,UAAUiC,SAAS,sBAA2E,MAAnD3D,KAAKT,oBAAoBsE,QAAQW,oBACxFC,qBAEJ9E,cAAcuE,aAAa,YAAa,QAQxCrE,cAAc6B,UAAUuC,IAAI,eAC5BpE,cAAcmB,QAMvBwC,iBACQxD,KAAKsB,sBACAD,qBAEAN,eAOboD,kBACS1E,YAAYiC,UAAUuC,IAAI,0BAC1B1E,oBAAoBsE,QAAQW,cAAgB,IAMrDC,qBACShF,YAAYiC,UAAUC,OAAO,0BAC7BpC,oBAAoBsE,QAAQW,cAAgB,6BAQ5BE,YACjB
C,OAAS,UAELD,YACC,YACDC,OAAOC,OAAS,0CAChBD,OAAOE,cAAgB,kBAAU,YAAa,sCAG7C,UACDF,OAAOC,OAAS,wCAChBD,OAAOE,cAAgB,kBAAU,YAAa,mCAI/CF,6CAQMG,gBAAOC,gBAAgB/E,KAAKjB,QAO7CmD,sBACW4C,gBAAO5C,eAQlB8C,oBAAoBN,eACT1E,KAAKG,UAAU8E,IAAIP,QAM9BxD,mCACcgE,OAAO,sBAAuB,IAAI/C,MAAMgD,YACzC5F,oBAAoBmE,UAAYyB,UAChCvD,kCAENQ,MAAMC,sBAAaC,WAM1B8C,oCACcF,OAAO,mCAAoC,IAAI/C,MAAMgD,YACtDE,mBAAmB,UAAWF,YAC7BhF,UAAYH,KAAKyD,wBAClBlE,oBAAoBmE,UAAYvD,eAChCiD,mCAENjB,MAAK,UACCgB,wBAAwB,cAE9Bf,MAAMC,sBAAaC,+BAONoC,WACZ1E,KAAKgF,oBAAoBN,QAAS,OAE5BY,gBAAkBpG,SAASC,cAAc,2BAA6BuF,OAAS,MACjFY,uBACK/F,oBAAoBgG,UAAYD,gBAAgBE,eAEtD,MAEEJ,sBAEA7F,oBAAoBmE,UAAY,SAE/B+B,QAAU,CACZC,kBAFiB1F,KAAK2F,mBAAmBjB,SAEtBE,OACnBgB,KAAM,CACFC,UAAW7F,KAAKhB,UAChB8G,WAAY9F,KAAK+F,6BAIfC,kBAAoBC,cAAKC,KAAK,CAACT,UAAU,MAC3CO,YAAYG,uBACPC,mBAGApG,KAAK4D,qBAAsB,OAEtByC,iBAAmBC,gBAASC,eAAeP,YAAYQ,mCACxDC,gBAAgBJ,iBAAkB3B,aAGlCnF,oBAAoBsE,QAAQC,UAAY,IAGvD,MAAOqC,OACLO,OAAOC,QAAQC,IAAIT,YACdC,iBAWjBf,mBAAmBX,OAAQS,WAClBhF,UAAU0G,IAAInC,OAAQS,MAO/BhC,wBAAwBuB,QAChB1E,KAA
KG,UAAU8E,IAAIP,cACdvE,UAAU2G,OAAOpC,QAQ9BjB,uBACQsD,MAAQ,SAEN5G,UAAY,IAAIH,KAAKG,UAAU6G,UAAUC,cAC1C,MAAMC,YAAY/G,UACnB4G,OAASG,gBAENH,4BAQWI,QAASzC,cAErBkB,KAAO,CACTuB,QAASA,QACTtC,eAHiB7E,KAAK2F,mBAAmBjB,SAGzBG,QAChBH,OAAQA,2BAEFQ,OAAO,oCAAqCU,MAAMzD,MAAMgD,YACzDE,mBAAmBX,OAAQS,YAC1BhF,UAAYH,KAAKyD,wBAClBlE,oBAAoBmE,UAAYvD,eAChCuC,oCAENN,MAAMC,sBAAaC,WAM1B8D,kCACclB,OAAO,iCAAkC,IAAI/C,MAAMgD,YACpDE,mBAAmB,QAASF,YAC3BhF,UAAYH,KAAKyD,wBAClBlE,oBAAoBmE,UAAYvD,eAChCoC,iCAENJ,MAAK,UACCgB,wBAAwB,YAE9Bf,MAAMC,sBAAaC,WAO1ByD,uBACUqB,WAAalI,SAASC,cAAcC,mBAAUC,SAASgI,oBACtDD,WAAWE,WAAaF,WAAWG"}