New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@brightspace-ui/htmleditor

Package Overview
Dependencies
Maintainers
3
Versions
767
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@brightspace-ui/htmleditor - npm Package Compare versions

Comparing version 2.84.1 to 2.84.3

components/practices/ai-adapter/adapter.js

17

components/plugins/practices.js

@@ -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",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc