@brightspace-ui/htmleditor
Advanced tools
Comparing version 2.84.1 to 2.84.3
@@ -38,2 +38,3 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { sendCancelEditPracticeEvent, sendCancelPracticeEvent, sendDeletePracticeEvent, sendEditPracticeEvent, sendInitializedPracticeEvent, sendInsertPracticeEvent, sendLaunchPracticeEditorEvent, sendUpdatePracticeEvent } from '../events/creator-plus/practices/practices.js'; | ||
import { adaptAiQuestion } from '../practices/ai-adapter/adapter.js'; | ||
import { cmds } from '../commands.js'; | ||
@@ -46,3 +47,2 @@ import { findComposedAncestor } from '@brightspace-ui/core/helpers/dom.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from '../practices/llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -813,4 +813,2 @@ import { PracticesContextMenuPlugin } from '../context-menu/practices/practices.js'; | ||
LlmController.shadowRoot = this.shadowRoot; | ||
// Prevent users from dropping practices in layouts. | ||
@@ -907,3 +905,3 @@ if (this._forbidden) { | ||
<d2l-generate-question-panel template-index="${this._templateIndex}"></d2l-generate-question-panel> | ||
<d2l-generate-question-panel @d2l-genai-question-updated="${this._questionGenerated}" template-index="${this._templateIndex}"></d2l-generate-question-panel> | ||
@@ -1152,2 +1150,13 @@ ${this._buildActivity()} | ||
_questionGenerated(event) { | ||
const generatedQuestion = event.detail; | ||
this.activityData = adaptAiQuestion(this.activityData, generatedQuestion); | ||
if (!this._startedEditing) { | ||
this._startedEditing = true; | ||
} | ||
this.requestUpdate('activityData', {}); | ||
} | ||
_resizeEditorEnd(event) { | ||
@@ -1154,0 +1163,0 @@ if (event.target.querySelector('.d2l-preview > iframe')) { |
import { LlmController } from '../llm-controller.js'; | ||
import { QuestionGenerator } from './questions/question-generator.js'; | ||
import { randomUUID } from './telemetry-handler.js'; | ||
import { requestInstance } from '@brightspace-ui/core/mixins/provider-mixin.js'; | ||
@@ -85,6 +87,4 @@ // TODO - Enhance these states to control all stages of the workflow in the updated designs | ||
const element = LlmController._getTemplateElement(this.templateIndex, LlmController.shadowRoot); | ||
if (element) { | ||
LlmController.clearFields(this.templateIndex, LlmController.shadowRoot); | ||
} | ||
const generator = new QuestionGenerator(this.host); | ||
generator.discard(this.templateIndex); | ||
@@ -108,6 +108,2 @@ this._sourceTextInitialized = false; | ||
async generate(regenerate = false) { | ||
const element = LlmController._getTemplateElement(this.templateIndex, LlmController.shadowRoot); | ||
if (!element) { | ||
return; // Should never happen! | ||
} | ||
this.prevState = this.state; | ||
@@ -133,4 +129,2 @@ this.state = State.Generating; | ||
LlmController.clearFields(this.templateIndex, LlmController.shadowRoot); | ||
let generationResult = undefined; | ||
@@ -158,3 +152,4 @@ const generationId = randomUUID(); | ||
} | ||
generationResult = await element.getLlmSuggestions(data); | ||
const generator = new QuestionGenerator(this.host, this._orgUnitId); | ||
generationResult = await generator.generate(this.templateIndex, data); | ||
} catch (e) { | ||
@@ -167,2 +162,9 @@ this.errorState = State.Error; | ||
async init() { | ||
const context = requestInstance(this.host, 'context'); | ||
if (context) { | ||
this._orgUnitId = context.orgUnitId; | ||
} | ||
} | ||
initSourceText() { | ||
@@ -169,0 +171,0 @@ // Considered using the state such as Initial, to determine whether to initialize source text, |
@@ -102,2 +102,7 @@ import '@brightspace-ui/core/components/alert/alert.js'; | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
this.api.init(); | ||
} | ||
disconnectedCallback() { | ||
@@ -104,0 +109,0 @@ this.api.cancel(); |
@@ -20,3 +20,2 @@ import '@brightspace-ui/core/components/button/button.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -262,4 +261,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._updatedAnswers = []; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -270,3 +267,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -337,119 +333,2 @@ | ||
clearFields() { | ||
this._updateValue('questions:0:text', '', true); | ||
this.questionText = ''; | ||
this._updateValue('customInstructions', '', true); | ||
this.activityData.customInstructions = ''; | ||
this.activityData.questions[0].answers = []; | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
sentence: '[F]:', | ||
answer: /\[answer\d\]:/, | ||
answerIdRegex: /answer\d/, | ||
error: '[E]:', | ||
}; | ||
let fieldToFill = ''; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
let answerId = ''; | ||
let firstAnswer = true; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
if (content.includes(fields.sentence)) { | ||
fieldToFill = fields.sentence; | ||
content = content.split(fields.sentence)[1]; | ||
} else if (content.match(fields.answer)) { | ||
if (firstAnswer) { | ||
const processedQuestion = this.activityData.questions[0].text.replaceAll('(', '[').replaceAll(')', ']'); | ||
this.activityData.questions[0].text = processedQuestion.trim(); | ||
this._updateValue('questions:0:text', processedQuestion.trim()); | ||
this.questionText = processedQuestion; | ||
this._processBlanks(); | ||
firstAnswer = false; | ||
} | ||
answerId = content.match(fields.answerIdRegex)[0]; | ||
content = content.split(fields.answer)[1]; | ||
fieldToFill = fields.answer; | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.sentence) { | ||
this.activityData.questions[0].text = contentFormatted; | ||
this._updateValue('questions:0:text', contentFormatted); | ||
this.questionText = contentFormatted; | ||
} else if (fieldToFill === fields.answer) { | ||
const answerIndex = this.activityData.questions[0].answers.map(function(answer) { return answer.id; }).indexOf(answerId); | ||
if (answerIndex > -1) { | ||
this.activityData.questions[0].answers[answerIndex].answers[0].text = contentFormatted; | ||
this._updateValue(`questions:0:answers:${answerIndex}:answers:0:text`, contentFormatted); | ||
this._updateActivity(); | ||
} | ||
} | ||
}; | ||
// Reset questions | ||
this.activityData.questions[0].text = ''; | ||
this._updateValue('questions:0:text', ''); | ||
this.questionText = ''; | ||
for (let i = 0; i < this.activityData.questions[0].answers.length; i++) { | ||
this.activityData.questions[0].answers[i].answers = this.activityData.questions[0].answers[i].answers.slice(0, 1); | ||
this.activityData.questions[0].answers[i].answers[0].text = ''; | ||
} | ||
const questionType = 'dropDown'; | ||
this._updateActivity(); | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
if (LlmController.cancelled) { | ||
return; | ||
} | ||
// Processing each drop down obj that has their own correct/incorrect answers | ||
for (let i = 0; i < this.activityData.questions[0].answers.length; i++) { | ||
const dropDownObj = this.activityData.questions[0].answers[i]; | ||
const potentialAnswerString = dropDownObj.answers[0].text.trim(); | ||
const potentialAnswersArr = potentialAnswerString.split('||'); | ||
dropDownObj.answers = []; | ||
// Setting correct answers as first index of answers | ||
if (potentialAnswersArr.length > 0) { | ||
dropDownObj.correctAnswers = [0]; | ||
this._updateValue(`questions:0:answers:${i}:correctAnswers`, [0]); | ||
} | ||
// Setting all potential answers, with correct one being first according to prompt | ||
for (let j = 0; j < potentialAnswersArr.length; j += 1) { | ||
const potentialAnswer = potentialAnswersArr[j].trim(); | ||
if (potentialAnswer) { | ||
const answerData = { | ||
text: potentialAnswer, | ||
}; | ||
dropDownObj.answers.push(answerData); | ||
this._updateActivity(); | ||
} | ||
} | ||
} | ||
// Change scoring to "Correct Inputs" by default | ||
this.activityData.scoreCalculation = 1; | ||
this._updateValue('scoreCalculation', 1); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_addIncorrectAnswer(event) { | ||
@@ -456,0 +335,0 @@ const id = event.target.getAttribute('data-id'); |
@@ -22,3 +22,2 @@ import '@brightspace-ui/core/components/button/button.js'; | ||
import { live } from 'lit-html/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -289,4 +288,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._updatedAnswers = []; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -297,3 +294,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -364,91 +360,2 @@ | ||
clearFields() { | ||
this._updateValue('questions:0:text', '', true); | ||
this.questionText = ''; | ||
this._updateValue('customInstructions', '', true); | ||
this.activityData.customInstructions = ''; | ||
this.activityData.questions[0].answers = []; | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
sentence: '[F]:', | ||
answer: /\[answer\d\]:/, | ||
answerIdRegex: /answer\d/, | ||
}; | ||
let fieldToFill = ''; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
let answerId = ''; | ||
let firstAnswer = true; | ||
const createBlanksFromLlm = () => { | ||
const processedQuestion = this.activityData.questions[0].text.replaceAll('(', '[').replaceAll(')', ']').trim(); | ||
this._updateValue('questions:0:text', processedQuestion); | ||
this.questionText = processedQuestion; | ||
this._processBlanks(); | ||
firstAnswer = false; | ||
}; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
if (content.includes(fields.sentence)) { | ||
fieldToFill = fields.sentence; | ||
content = ''; | ||
} else if (content.match(fields.answer)) { | ||
if (firstAnswer) { | ||
createBlanksFromLlm(); | ||
} | ||
answerId = content.match(fields.answerIdRegex)[0]; | ||
content = ''; | ||
fieldToFill = fields.answer; | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.sentence) { | ||
this._updateValue('questions:0:text', contentFormatted); | ||
this.questionText = contentFormatted; | ||
} else if (fieldToFill === fields.answer) { | ||
const answerIndex = this.activityData.questions[0].answers.map(function(answer) { return answer.id; }).indexOf(answerId); | ||
if (answerIndex > -1) { | ||
this._updateValue(`questions:0:answers:${answerIndex}:answers:0:text`, contentFormatted); | ||
this._updateActivity(); | ||
} | ||
} | ||
}; | ||
// Reset questions | ||
this._updateValue('questions:0:text', ''); | ||
this.questionText = ''; | ||
for (let i = 0; i < this.activityData.questions[0].answers.length; i++) { | ||
this.activityData.questions[0].answers[i].answers = this.activityData.questions[0].answers[i].answers.slice(0, 1); | ||
this.activityData.questions[0].answers[i].answers[0].text = ''; | ||
} | ||
const questionType = 'fillBlanks'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
if (firstAnswer) { | ||
// Occurs when a question is generated with answer placeholders but no possible answers after it | ||
// We should still update blanks so they can manually input correct and alternative answers. | ||
createBlanksFromLlm(); | ||
} | ||
this._updateActivity(); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_addAlternativeAnswer(event) { | ||
@@ -455,0 +362,0 @@ const answersLength = this.activityData.questions[0].answers.find(answer => |
@@ -1,4 +0,1 @@ | ||
import { ErrorTypes } from './ai/editor-controller.js'; | ||
import { html } from 'lit'; | ||
import { requestInstance } from '@brightspace-ui/core/mixins/provider-mixin.js'; | ||
@@ -10,4 +7,2 @@ // In case we need any local apis | ||
const GENERATION_THRESHOLD = 20; | ||
export class LlmController { | ||
@@ -19,3 +14,2 @@ static cancelled = false; | ||
static customizedSourceText = ''; | ||
static editor; | ||
static error = false; | ||
@@ -28,117 +22,2 @@ static errorSuggestion = ''; | ||
static generated = false; | ||
static htmlType = { | ||
// Generate button | ||
generateButton: (textToUse, localizations) => { | ||
if (LlmController.genAIEndpoint) { | ||
if (!LlmController._thresholdPassed(textToUse)) { // source text is too short | ||
return html`<d2l-button-subtle icon="tier1:wizard" disabled disabled-tooltip="${localizations.error}" text="${localizations.generate}"></d2l-button-subtle>`; | ||
} else if (LlmController.getDisplayText(textToUse).length > LlmController.sourceTextLimit) { // source text is too long | ||
return html`<d2l-button-subtle id="d2l-llm-generate" disabled-tooltip="${localizations.errorLimitExceeded}" disabled icon="tier1:wizard" @click="${LlmController.getLlmSuggestionOnClick}" text="${localizations.generate}"></d2l-button-subtle>`; | ||
} else { | ||
return html`<d2l-button-subtle id="d2l-llm-generate" ?disabled=${LlmController.loading} icon="tier1:wizard" @click="${LlmController.getLlmSuggestionOnClick}" text="${localizations.generate}"></d2l-button-subtle>`; | ||
} | ||
} | ||
return html``; | ||
}, | ||
questionIcon : (textToUse) => { | ||
if (LlmController.shouldShowQuestionIcon) { | ||
return html` <d2l-icon icon="tier1:help" id="helpIcon"></d2l-icon> | ||
<d2l-tooltip for="helpIcon" state="none"> ${LlmController.getDisplayText(textToUse)} </d2l-tooltip>`; | ||
} | ||
return html``; | ||
}, | ||
errorBanner: () => { | ||
if (LlmController.error) { // LlmController.error | ||
return html` | ||
<div class="d2l-error-box"> | ||
<d2l-icon icon="tier2:unapproved"></d2l-icon> | ||
<div class="d2l-question-summary-text"> | ||
<span>${html`${LlmController.errorText}`}</span><br> | ||
<span>${html`${LlmController.errorSuggestion}`}</span> | ||
</div> | ||
<d2l-button-subtle id="d2l-llm-generate" ?disabled=${LlmController.loading} @click="${LlmController.getLlmSuggestionOnClick}" text="Try Again"></d2l-button-subtle> | ||
</div> | ||
`; | ||
} else { | ||
return html``; | ||
} | ||
}, | ||
// Component below the top buttons | ||
// (Displays when source text exceeds the character limit, contains button to edit text) | ||
sourceTextLimitExceededBanner: (textToUse, localizations) => { | ||
if (LlmController.genAIEndpoint && | ||
textToUse.length > LlmController.sourceTextLimit) { | ||
return html` | ||
<div class="d2l-error-box"> | ||
<d2l-icon icon="tier2:unapproved"></d2l-icon> | ||
<div class="d2l-question-summary-text"> | ||
<span>${localizations.limitError}</span><br> | ||
<span>${localizations.limitErrorSuggestion}</span> | ||
</div> | ||
<d2l-button-subtle id="d2l-llm-generate" ?disabled=${LlmController.loading} @click="${LlmController.openDialog}" text="${localizations.editText}"></d2l-button-subtle> | ||
</div> | ||
`; | ||
} else { | ||
return html``; | ||
} | ||
}, | ||
contentNotMeaningfulBanner: (localizations) => { | ||
if (!LlmController.meaningfulResult) { | ||
return html` | ||
<div class="d2l-error-box"> | ||
<d2l-icon icon="tier2:unapproved"></d2l-icon> | ||
<div class="d2l-question-summary-text"> | ||
<span>${localizations.error}</span><br> | ||
<span>${localizations.suggestion}</span> | ||
</div> | ||
<d2l-button-subtle id="d2l-llm-generate" ?disabled=${LlmController.loading} @click="${LlmController.openDialog}" text="${localizations.editText}"></d2l-button-subtle> | ||
</div> | ||
`; | ||
} | ||
}, | ||
// Top banner component | ||
// (Displays underneath the generate button) | ||
topBanner: (summary, localizations) => { | ||
if (!LlmController.genAIEndpoint) { | ||
return html ``; | ||
} | ||
// Error occurred with question generation | ||
if (LlmController.error) { | ||
/* | ||
return html` | ||
<div class="d2l-info-box"> | ||
<d2l-alert @d2l-alert-button-press=${LlmController.getLlmSuggestionOnClick} has-close-button=true button-text="${localizations.tryAgain}" type="critical">${LlmController.errorText}</d2l-alert> | ||
</div> | ||
`; | ||
*/ | ||
return html``; | ||
} | ||
// Summary after question has been generated | ||
else if (LlmController.generated) { | ||
return html` | ||
<div class="d2l-info-box"> | ||
<d2l-icon icon="tier2:wizard"></d2l-icon> | ||
<div class="d2l-question-summary-text"> | ||
<span>${summary}</span><br> | ||
<span>${localizations.summarySuggestion}</span> | ||
</div> | ||
<d2l-button-subtle id="d2l-llm-generate" ?disabled=${LlmController.loading} @click="${LlmController.openDialog}" text="${localizations.editText}"></d2l-button-subtle> | ||
</div>`; | ||
} | ||
// Loading banner while question is generating | ||
else if (LlmController.loading) { | ||
return html` | ||
<div class="d2l-info-box"> | ||
<div class="d2l-question-summary-loading"><d2l-loading-spinner></d2l-loading-spinner></div> | ||
<div class="d2l-question-summary-loading-text"><span>${LlmController.loadingMessage}</span></div> | ||
<div class="d2l-question-summary-cancel-button"> | ||
<d2l-button-subtle ?disabled=${LlmController.cancelled} text="${localizations.cancel}"></d2l-button-subtle> | ||
</div> | ||
</div>`; | ||
} | ||
else { | ||
return html``; | ||
} | ||
}, | ||
}; | ||
static loading = false; | ||
@@ -151,3 +30,2 @@ static localizations = {}; | ||
static selectedText; | ||
static shadowRoot; | ||
@@ -161,7 +39,2 @@ // API limit is 4096 tokens (~12000 characters) for both the input and the response | ||
// Clears any text in fields and resets the question generation page to default | ||
static clearFields(templateIndex, shadowRoot) { | ||
const element = LlmController._getTemplateElement(templateIndex, shadowRoot); | ||
element.clearFields(); | ||
} | ||
// Gets a string rerepsenting text from the editor, as it is displayed on-screen | ||
@@ -172,178 +45,2 @@ static getDisplayText(text) { | ||
// Function used for the Generate button | ||
static getLlmSuggestionOnClick() { | ||
const dialog = this.shadowRoot.querySelector('#view-summary-dialog'); | ||
if (dialog && dialog.opened) dialog.opened = false; | ||
LlmController.error = false; | ||
LlmController.meaningfulResult = true; | ||
LlmController.errorText = ''; | ||
LlmController.errorSuggestion = ''; | ||
LlmController.getLlmSuggestions(LlmController.templateIndex); | ||
} | ||
static async getLlmSuggestions(templateIndex) { | ||
LlmController.cancelled = false; | ||
LlmController.generated = false; | ||
LlmController.loading = true; | ||
const button = await this.shadowRoot.getElementById('d2l-llm-generate'); | ||
if (button) { | ||
button.disabled = true; | ||
} | ||
const element = LlmController._getTemplateElement(templateIndex, this.shadowRoot); | ||
if (element) { | ||
LlmController.clearFields(templateIndex, this.shadowRoot); | ||
const context = requestInstance(this.editor.getElement(), 'context'); | ||
const component = context.component; | ||
const dialog = component.shadowRoot.querySelector('d2l-htmleditor-practice-dialog'); | ||
dialog._updateActivity(); | ||
try { | ||
await element.getLlmSuggestions(); | ||
} catch (e) { | ||
LlmController.error = true; | ||
LlmController.errorText = LlmController.localizations.serverError; | ||
LlmController.errorSuggestion = LlmController.localizations.contactAdmin; | ||
} | ||
LlmController.loading = false; | ||
if (LlmController.cancelled) { | ||
LlmController.cancelled = false; | ||
LlmController.generated = false; | ||
} else if (LlmController.meaningfulResult) { | ||
LlmController.generated = true; | ||
} | ||
if (button) { | ||
button.disabled = false; | ||
} | ||
dialog._updateActivity(); | ||
} | ||
} | ||
static getRelevantEditorText() { | ||
const curSelection = LlmController.editor.selection.getNode(); | ||
LlmController.seen = []; | ||
const bottomHeader = LlmController._recurseChildFindHeading(curSelection); | ||
if (!bottomHeader) return LlmController.editor.getContent({ format: 'text' }); | ||
const topHeader = LlmController._recurseUpwardFindTargetTag(bottomHeader.previousElementSibling, bottomHeader.tagName); | ||
const foundText = LlmController._getSandwitchedText(topHeader, bottomHeader); | ||
if (!foundText) { | ||
return LlmController.editor.getContent({ format: 'text' }); | ||
} | ||
return foundText; | ||
} | ||
static getTitle(html) { | ||
const parser = new DOMParser(); | ||
const doc = parser.parseFromString(html, 'text/html'); | ||
const firstH1 = doc.querySelector('h1'); | ||
if (firstH1) { | ||
return firstH1.textContent; | ||
} | ||
return null; | ||
} | ||
// Opens the dialog used for customizing source text. | ||
static openDialog() { | ||
const dialog = this.shadowRoot.querySelector('#view-summary-dialog'); | ||
dialog.opened = !dialog.opened; | ||
} | ||
async getLlmStream(questionType, params, processContent) { | ||
LlmController.meaningfulResult = true; | ||
let isFirstChar = true; | ||
const url = `${LlmController.genAIEndpoint}streamQuestions/${questionType}`; | ||
const accessToken = await window.D2L.LP.Web.Authentication.OAuth2.GetToken('*:*:*'); | ||
if (!accessToken) { | ||
//TODO - in forthcoming error handling/message refactoring, consider general token error here | ||
LlmController.error = true; | ||
return; | ||
} | ||
const body = JSON.stringify( | ||
Object.assign({ accessToken, orgUnitId: this.orgUnitId }, params) | ||
); | ||
const response = await fetch(url, { method: 'POST', body, }); | ||
const streamResponse = response.body; | ||
this.response = streamResponse; | ||
if (!streamResponse) { | ||
LlmController.errorText = LlmController.localizations.noResponse; | ||
LlmController.errorSuggestion = LlmController.localizations.contactAdmin; | ||
LlmController.error = true; | ||
return; | ||
} | ||
const reader = streamResponse.getReader(); | ||
const decoder = new TextDecoder(); | ||
let done = false; | ||
let finalContent = ''; | ||
while (!done && !LlmController.cancelled) { | ||
const { value, done: doneReading } = await reader.read(); | ||
if (LlmController.cancelled) { | ||
break; | ||
} | ||
done = doneReading; | ||
const chunkValue = decoder.decode(value); | ||
finalContent += chunkValue; | ||
// processContent(chunkValue); | ||
// multiple tokens are sometimes sent together, by processing each character separately, | ||
// we simplify the processing of the chunks | ||
for (let i = 0; i < chunkValue.length; i += 1) { | ||
const c = chunkValue[i]; | ||
// Handle for errors on back-end | ||
if (c === '!' && isFirstChar) { | ||
LlmController.loading = false; | ||
// If window.localStorage['D2L.Fetch.Tokens'] is invalid | ||
//TODO - in forthcoming error handling/message refactoring, double check and then | ||
//remove these two error if/if-else statements, as well as the relevant localizations | ||
if (response.status === 500 && chunkValue.includes('VERIFY_ACCESS_EXPIRED')) { | ||
LlmController.errorText = LlmController.localizations.accessExpired; | ||
LlmController.errorSuggestion = LlmController.localizations.signIn; | ||
LlmController.error = true; | ||
// If currrent host is not in the environment variable ALLOWED_HOSTS | ||
} else if (response.status === 500 && chunkValue.includes('NOT_ALLOWED_HOST')) { | ||
LlmController.errorText = LlmController.localizations.noAccessToFeature; | ||
LlmController.errorSuggestion = LlmController.localizations.contactAdmin; | ||
LlmController.error = true; | ||
// If user provided source text that isn't relevant for the question type | ||
} else if (response.status === 429 && chunkValue.includes('USAGE_LIMIT_EXCEEDED')) { | ||
LlmController.errorText = LlmController.localizations.consumptionLimit; | ||
LlmController.errorSuggestion = LlmController.localizations.contactAdmin; | ||
LlmController.error = true; | ||
LlmController.errorType = ErrorTypes.ConsumptionLimit; | ||
} else { | ||
LlmController.meaningfulResult = false; | ||
} | ||
return; | ||
// If environment variable OPEN_AI_KEY isn't valid, or any error occurs on Lambda | ||
} else if ((chunkValue.includes('OPENAI_KEY_INVALID') || chunkValue.includes('Internal Server Error')) && isFirstChar) { | ||
LlmController.loading = false; | ||
LlmController.errorText = LlmController.localizations.serverError; | ||
LlmController.errorSuggestion = LlmController.localizations.contactAdmin; | ||
LlmController.error = true; | ||
return; | ||
} | ||
processContent(c); | ||
isFirstChar = false; | ||
} | ||
} | ||
const backendVersion = response.headers.has('x-d2l-version') ? | ||
response.headers.get('x-d2l-version') : ''; | ||
if (LlmController.cancelled) { | ||
reader.cancel('User initiated cancel'); | ||
} | ||
return { | ||
generatedContent: finalContent, | ||
backendVersion | ||
}; | ||
} | ||
/* | ||
@@ -356,35 +53,3 @@ Helper function for: getDisplayText() | ||
} | ||
static _getSandwitchedText(topNode, bottomNode) { | ||
let text = ''; | ||
let cur = topNode; | ||
while (cur && cur !== bottomNode) { | ||
text += `${cur.textContent}\n`; | ||
cur = cur.nextElementSibling; | ||
} | ||
return text; | ||
} | ||
static _getTemplateElement(templateIndex, shadowRoot) { | ||
switch (templateIndex) { | ||
case 0: | ||
return shadowRoot.getElementById('d2l-sorting-practice'); | ||
case 1: | ||
return shadowRoot.getElementById('d2l-sequencing-practice'); | ||
case 3: | ||
return shadowRoot.getElementById('d2l-true-false-practice'); | ||
case 4: | ||
return shadowRoot.getElementById('d2l-dropdown-blanks-practice'); | ||
case 5: | ||
return shadowRoot.getElementById('d2l-multiple-choice-practice'); | ||
case 6: | ||
return shadowRoot.getElementById('d2l-multiple-select-practice'); | ||
case 7: | ||
return shadowRoot.getElementById('d2l-fill-blanks-practice'); | ||
} | ||
} | ||
static _isHeading(tagName) { | ||
const lower = tagName.toLowerCase(); | ||
return lower === 'h1' || lower === 'h2' || lower === 'h3' || lower === 'h4' || lower === 'h5' || lower === 'h6'; | ||
} | ||
/* | ||
@@ -403,50 +68,2 @@ Helper function for: getDisplayText() | ||
} | ||
// this function recurse _Downwards_ to find the next heading | ||
static _recurseChildFindHeading(node) { | ||
if (!node || LlmController.seen.includes(node) || node.id === 'tinymce') return null; | ||
LlmController.seen.push(node); | ||
if (LlmController._isHeading(node.tagName) && node.textContent !== '') { | ||
return node; | ||
} | ||
// check downward children first - lower level | ||
for (const child of node.children) { | ||
const result = LlmController._recurseChildFindHeading(child); | ||
if (result) return result; | ||
} | ||
// check on siblings on same level | ||
const result = LlmController._recurseChildFindHeading(node.nextElementSibling); | ||
if (result) return result; | ||
// check higher up level | ||
return LlmController._recurseChildFindHeading(node.parentNode); | ||
} | ||
// this function recurses _Upwards_ to find the same heading as bottom header | ||
static _recurseUpwardFindTargetTag(curNode, targetTagName) { | ||
if (!curNode) return null; | ||
if (curNode.tagName.toLowerCase() === targetTagName.toLowerCase() && curNode.textContent !== '') { | ||
return curNode; | ||
} | ||
// check the children to see if we | ||
for (const child of curNode.children) { | ||
const result = LlmController._recurseUpwardFindTargetTag(child, targetTagName); | ||
if (result) return result; | ||
} | ||
// lastly, kick off on our sibling.. | ||
return LlmController._recurseUpwardFindTargetTag(curNode.previousElementSibling, targetTagName); | ||
} | ||
/* | ||
(i.e. the Generate button to produce llm results should be enabled) | ||
*/ | ||
static _thresholdPassed(text) { | ||
return LlmController._getRawText(text).replace(/\s/g, '').length > GENERATION_THRESHOLD; | ||
} | ||
} |
@@ -21,3 +21,2 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -229,4 +228,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._templateIndex = 5; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -237,3 +234,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -323,83 +319,2 @@ | ||
clearFields() { | ||
this._updateValue('questions:0:text', ''); | ||
this._updateValue('questions:0:correctAnswers', []); | ||
const curAnswersAmount = this.activityData.questions[0].answers.length; | ||
for (let i = curAnswersAmount; i >= 0 ; i--) { | ||
this.activityData.questions[0].answers.map((ans) => ans.text = ''); | ||
} | ||
this.activityData.questions[0].correctAnswers = []; | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
answer: /\[A\d\]:/, | ||
question: '[Q]:', | ||
correctAnswer: '[CA]:' | ||
}; | ||
let fieldToFill = ''; | ||
let answerCount = -1; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
// process the field delimiters | ||
if (content.includes(fields.question)) { | ||
content = content.split(fields.question)[1]; | ||
fieldToFill = fields.question; | ||
} else if (content.includes(fields.correctAnswer)) { | ||
content = content.split(fields.correctAnswer)[1]; | ||
fieldToFill = fields.correctAnswer; | ||
} else if (content.match(fields.answer)) { | ||
content = content.split(fields.answer)[1]; | ||
fieldToFill = fields.answer; | ||
answerCount += 1; | ||
const curAnswersAmount = this.activityData.questions[0].answers.length; | ||
if (curAnswersAmount <= answerCount) { | ||
this._addAnswer(); | ||
} | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.question) { | ||
this._updateValue('questions:0:text', contentFormatted); | ||
} else if (fieldToFill === fields.correctAnswer) { | ||
if (!content.match(/A\d/)) return; | ||
try { | ||
const parsedCorrectAnswer = [parseInt(contentFormatted[1]) - 1]; | ||
this._updateValue('questions:0:correctAnswers', parsedCorrectAnswer); | ||
} catch (e) { | ||
return; | ||
} | ||
} else if (fieldToFill === fields.answer) { | ||
this._updateValue(`questions:0:answers:${answerCount}:text`, contentFormatted); | ||
} | ||
}; | ||
const curAnswersAmount = this.activityData.questions[0].answers.length; | ||
for (let i = curAnswersAmount; i > 2; i--) { | ||
this._deleteAnswer(i - 1); | ||
} | ||
this._updateValue('questions:0:correctAnswers', []); | ||
const questionType = 'multipleChoice'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
this._updateValue('questions:0:randomize', true); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
processFields(field) { | ||
@@ -406,0 +321,0 @@ if (field.toggle) { |
@@ -20,3 +20,2 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -211,3 +210,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this.lc = new LlmController(); | ||
this.idList = []; | ||
@@ -219,3 +217,2 @@ } | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -307,88 +304,2 @@ | ||
clearFields() { | ||
this._updateValue('questions:0:text', ''); | ||
this._updateValue('questions:0:correctAnswers', []); | ||
const curAnswersAmount = this.activityData.questions[0].answers.length; | ||
for (let i = curAnswersAmount; i >= 0 ; i--) { | ||
this.activityData.questions[0].answers.map((ans) => ans.text = ''); | ||
} | ||
this.activityData.questions[0].correctAnswers = []; | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
answer: /\[A\d\]:/, | ||
question: '[Q]:', | ||
correctAnswer: '[CA]:' | ||
}; | ||
let fieldToFill = ''; | ||
let answerCount = -1; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
// process the field delimiters | ||
if (content.includes(fields.question)) { | ||
content = content.split(fields.question)[1]; | ||
fieldToFill = fields.question; | ||
} else if (content.includes(fields.correctAnswer)) { | ||
content = content.split(fields.correctAnswer)[1]; | ||
fieldToFill = fields.correctAnswer; | ||
} else if (content.match(fields.answer)) { | ||
content = content.split(fields.answer)[1]; | ||
fieldToFill = fields.answer; | ||
answerCount += 1; | ||
const curAnswersAmount = this.activityData.questions[0].answers.length; | ||
if (curAnswersAmount <= answerCount) { | ||
this._addAnswer(); | ||
} | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.question) { | ||
this._updateValue('questions:0:text', contentFormatted); | ||
} else if (fieldToFill === fields.correctAnswer) { | ||
try { | ||
const matches = content.match(/\d/g); | ||
if (!matches) { | ||
return; | ||
} | ||
try { | ||
const correctAnsArr = matches.map((n) => parseInt(n) - 1); | ||
this._updateValue('questions:0:correctAnswers', correctAnsArr); | ||
} catch (e) { | ||
return; | ||
} | ||
} catch (e) { | ||
return; | ||
} | ||
} else if (fieldToFill === fields.answer) { | ||
this._updateValue(`questions:0:answers:${answerCount}:text`, contentFormatted); | ||
} | ||
}; | ||
const questionType = 'multiSelect'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
this.activityData.scoreCalculation = 0; | ||
this._updateValue('scoreCalculation', 0); | ||
this.activityData.questions[0].randomize = true; | ||
this._updateValue('questions:0:randomize', true); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_addAnswer() { | ||
@@ -395,0 +306,0 @@ if (this.activityData.questions[0].answers.length === this._maxAnswersAmount) { |
@@ -27,3 +27,2 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -224,4 +223,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._templateIndex = 1; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -232,3 +229,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -304,100 +300,2 @@ | ||
clearFields() { | ||
this._updateValue('title', ''); | ||
this.activityData.title = ''; | ||
this._updateValue('customInstructions', ''); | ||
this.activityData.customInstructions = ''; | ||
this.activityData.categories = this.activityData.categories.slice(0, 2); | ||
this.activityData.categories[0].id = 1; | ||
this.activityData.categories[1].id = 2; | ||
this.activityData.categories[0].title = ''; | ||
this.activityData.categories[1].title = ''; | ||
this._activeCategory = 0; | ||
this._sortableItems = this._sortableItems.slice(0, 2); | ||
this._sortableItems[0][0].title = ''; | ||
this._sortableItems[1][0].title = ''; | ||
this._sortableItems[0][0].correctId = 1; | ||
this._sortableItems[1][0].correctId = 2; | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
event: '[E]:', | ||
eventDetails: '[D]:', | ||
title: '[T]:', | ||
}; | ||
let fieldToFill = ''; | ||
let eventDetailCount = -1; | ||
let eventCount = -1; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
// process the field delimiters | ||
if (content.includes(fields.title)) { | ||
fieldToFill = fields.title; | ||
content = content.split(fields.title)[1]; | ||
} else if (content.includes(fields.event)) { | ||
content = content.split(fields.event)[1]; | ||
fieldToFill = fields.event; | ||
eventDetailCount = -1; | ||
eventCount += 1; | ||
const existingEventCount = this.activityData.categories.length; | ||
if (eventCount + 1 > existingEventCount) { | ||
this._addStep(); | ||
} | ||
} else if (content.includes(fields.eventDetails)) { | ||
eventDetailCount += 1; | ||
content = content.split(fields.eventDetails)[1]; | ||
fieldToFill = fields.eventDetails; | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.title) { | ||
this._updateValue('title', contentFormatted); | ||
} else if (fieldToFill === fields.event) { | ||
this._updateValue(`categories:${eventCount}:title`, contentFormatted); | ||
} else if (fieldToFill === fields.eventDetails) { | ||
this._sortableItems[eventCount][eventDetailCount].title = contentFormatted; | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
} | ||
}; | ||
// Reset categories and their ids + titles | ||
this.activityData.categories = this.activityData.categories.slice(0, 2); | ||
this.activityData.categories[0].id = 1; | ||
this.activityData.categories[1].id = 2; | ||
this.activityData.categories[0].title = ''; | ||
this.activityData.categories[1].title = ''; | ||
this._activeCategory = 0; | ||
this._sortableItems = this._sortableItems.slice(0, 2); | ||
this._sortableItems[0][0].title = ''; | ||
this._sortableItems[1][0].title = ''; | ||
this._sortableItems[0][0].correctId = 1; | ||
this._sortableItems[1][0].correctId = 2; | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
const questionType = 'sequence'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
this._updateActivity(); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_addStep() { | ||
@@ -404,0 +302,0 @@ const category = { |
@@ -27,3 +27,2 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -373,4 +372,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._templateIndex = 0; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -382,3 +379,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
@@ -464,113 +460,2 @@ const e = this.closest('.d2l-editor'); | ||
clearFields() { | ||
this._updateValue('title', ''); | ||
if (this.activityData.categories) { | ||
this.activityData.categories = this.activityData.categories.slice(0, 2); | ||
this.activityData.categories[0].id = 1; | ||
this.activityData.categories[0].title = ''; | ||
this.activityData.categories[1].id = 2; | ||
this.activityData.categories[1].title = ''; | ||
} | ||
this._activeCategory = 0; | ||
this._sortableItems = []; | ||
this.activityData.customInstructions = ''; | ||
this._updateValue('customInstructions', ''); | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
category: '[C]:', | ||
relatedConcept: '[RF]:', | ||
}; | ||
let fieldToFill = ''; | ||
let relatedConceptCount = -1; | ||
let categoryCount = -1; | ||
const fieldChangeRegex = /\[[A-z]?\d?\]?:?/; | ||
const sortableItems = []; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
// process the field delimiters | ||
if (content.includes(fields.category)) { | ||
content = content.split(fields.category)[1]; | ||
fieldToFill = fields.category; | ||
relatedConceptCount = -1; | ||
categoryCount += 1; | ||
const existingCategoryCount = this.activityData.categories.length; | ||
if (categoryCount + 1 > existingCategoryCount) { | ||
this._addCategory(); | ||
} | ||
sortableItems.push([]); | ||
} else if (content.includes(fields.relatedConcept)) { | ||
relatedConceptCount += 1; | ||
content = content.split(fields.relatedConcept)[1]; | ||
fieldToFill = fields.relatedConcept; | ||
const sortableItem = { | ||
title: '', | ||
correctId: categoryCount + 1, | ||
alt: '', | ||
correctFeedback: '', | ||
id: sortableItems[categoryCount].length, | ||
image: '', | ||
incorrectFeedback: '', | ||
}; | ||
sortableItems[categoryCount].push(sortableItem); | ||
} | ||
// Square brackets present | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (LlmController.cancelled) { | ||
return; | ||
} | ||
if (fieldToFill === fields.category) { | ||
this._updateValue(`categories:${categoryCount}:title`, contentFormatted); | ||
} else if (fieldToFill === fields.relatedConcept) { | ||
sortableItems[categoryCount][relatedConceptCount].title = contentFormatted; | ||
this._sortableItems = sortableItems; | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
} | ||
}; | ||
if (LlmController.cancelled) { | ||
return; | ||
} | ||
// Reset categories and their ids + titles | ||
this.activityData.categories = this.activityData.categories.slice(0, 2); | ||
this.activityData.categories[0].id = 1; | ||
this.activityData.categories[0].title = ''; | ||
this.activityData.categories[1].id = 2; | ||
this.activityData.categories[1].title = ''; | ||
this._activeCategory = 0; | ||
this._sortableItems = []; | ||
this._updateSortableItems(); | ||
this._updateActivity(); | ||
if (LlmController.cancelled) { | ||
return; | ||
} | ||
const questionType = 'sorting'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
this._updateActivity(); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_addCategory() { | ||
@@ -577,0 +462,0 @@ const category = { |
@@ -19,3 +19,2 @@ import '@brightspace-ui/core/components/alert/alert-toast.js'; | ||
import { live } from 'lit/directives/live.js'; | ||
import { LlmController } from './llm-controller.js'; | ||
import { Localizer } from '../../localizer.js'; | ||
@@ -198,4 +197,2 @@ import { practiceTemplates } from './d2l-practices.js'; | ||
this._templateIndex = 3; | ||
this.lc = new LlmController(); | ||
} | ||
@@ -206,3 +203,2 @@ | ||
this._context = this.requestInstance('context'); | ||
this.lc.orgUnitId = this._context.orgUnitId; | ||
} | ||
@@ -266,60 +262,2 @@ | ||
clearFields() { | ||
this._updateValue('questions:0:text', ''); | ||
this._updateValue('questions:0:correctAnswers', []); | ||
} | ||
async getLlmSuggestions(data) { | ||
// Processing function for streaming | ||
let content = ''; | ||
const fields = { | ||
answer: '[A]:', | ||
question: '[Q]:' | ||
}; | ||
let fieldToFill = ''; | ||
const fieldChangeRegex = /\[[A-z]?\]?:?/; | ||
const processContent = (chunkValue) => { | ||
content = content + chunkValue; | ||
// process the field delimiters | ||
if (content.includes(fields.question)) { | ||
content = content.split(fields.question)[1]; | ||
fieldToFill = fields.question; | ||
} else if (content.includes(fields.answer)) { | ||
content = content.split(fields.answer)[1]; | ||
fieldToFill = fields.answer; | ||
} | ||
// Square brackets present and length could be a delimitter | ||
// TODO: factor in square brackets in the response | ||
if (content.match(fieldChangeRegex)) { | ||
return; | ||
} | ||
const contentFormatted = content.trim().replace('"""', ''); | ||
if (fieldToFill === fields.question) { | ||
this._updateValue('questions:0:text', contentFormatted); | ||
} else if (fieldToFill === fields.answer) { | ||
let correctAnswer = -1; | ||
if (content.includes('True')) { | ||
correctAnswer = 0; | ||
} else if (content.includes('False')) { | ||
correctAnswer = 1; | ||
} | ||
this._updateValue('questions:0:correctAnswers', [correctAnswer]); | ||
} | ||
}; | ||
const questionType = 'trueFalse'; | ||
const llmResponse = await this.lc.getLlmStream(questionType, data, processContent); | ||
this._updateActivity(); | ||
return { | ||
...llmResponse, | ||
questionType | ||
}; | ||
} | ||
_buildField(property, index, disabled) { | ||
@@ -326,0 +264,0 @@ const id = getUniqueId(); |
{ | ||
"name": "@brightspace-ui/htmleditor", | ||
"description": "An HTML editor that integrates with Brightspace", | ||
"version": "2.84.1", | ||
"version": "2.84.3", | ||
"type": "module", | ||
@@ -6,0 +6,0 @@ "repository": "https://github.com/BrightspaceUI/htmleditor.git", |
144
2258807
42155