@nualang/nualang-ui-components 0.1.1254 → 0.1.1256
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Assignments/CreateAssignmentDialog/CreateAssignmentDialog.js +54 -31
- package/dist/Cards/SubscriptionPlan/SubscriptionPlan.js +13 -16
- package/dist/Dialogs/CreatePhrase/CreatePhrase.js +22 -6
- package/dist/Dialogs/GenerateBot/GenerateBot.js +60 -37
- package/dist/Dialogs/GeneratePhrases/GeneratePhrases.js +181 -132
- package/dist/Dialogs/GenerateQuestion/GenerateQuestion.js +1 -3
- package/dist/Dialogs/GenerateRoleplay/GenerateRoleplay.js +23 -11
- package/dist/Editors/Bot/Editor/Editor.js +1 -2
- package/dist/Screens/Classrooms/ViewClassroom/ViewClassroom.js +2 -2
- package/dist/Screens/Courses/ViewCourse/ViewCourse.js +1 -1
- package/dist/Tables/Progress/ProgressTable.js +2 -2
- package/dist/Tables/Progress/utils.js +36 -20
- package/dist/Tables/RecordingListCards/RecordingListCards.js +1 -1
- package/dist/utils/index.js +9 -1
- package/package.json +1 -1
|
@@ -79,33 +79,51 @@ function CreateAssignmentDialog({
|
|
|
79
79
|
exercises: submittedExercises,
|
|
80
80
|
...initialData
|
|
81
81
|
});
|
|
82
|
-
const classroomQuery =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
const classroomQuery = useQueries({
|
|
83
|
+
queries: (Array.isArray(assignment.classroomId) ? assignment.classroomId : [assignment.classroomId]).map(cId => ({
|
|
84
|
+
queryKey: _Queries.classrooms.classroomKeys.item(cId, username),
|
|
85
|
+
queryFn: () => getClassroom(cId, filters),
|
|
86
|
+
queryOptions: {
|
|
87
|
+
enabled: !!(Array.isArray(assignment.classroomId) ? assignment.classroomId.length > 0 : assignment.classroomId)
|
|
88
|
+
}
|
|
89
|
+
})),
|
|
90
|
+
combine: results => {
|
|
91
|
+
const normalizedData = results.map(result => {
|
|
92
|
+
const data = result.data;
|
|
93
|
+
if (data?.classroomId) {
|
|
94
|
+
return data;
|
|
95
|
+
}
|
|
96
|
+
if (data?.Item?.classroomId) {
|
|
97
|
+
return data.Item;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}).filter(Boolean);
|
|
101
|
+
return {
|
|
102
|
+
data: normalizedData,
|
|
103
|
+
pending: results.some(result => result.isPending)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
101
106
|
});
|
|
102
|
-
const
|
|
107
|
+
const selectedClassroomCourses = (0, _react.useMemo)(() => {
|
|
108
|
+
if (!classroomQuery.pending && classroomQuery.data.length > 0) {
|
|
109
|
+
const allCourses = classroomQuery.data.flatMap(classroom => classroom.courses || []);
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
const uniqueCourses = allCourses.filter(courseId => {
|
|
112
|
+
if (seen.has(courseId)) return false;
|
|
113
|
+
seen.add(courseId);
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
return uniqueCourses;
|
|
117
|
+
} else {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}, [classroomQuery.data, classroomQuery.pending]);
|
|
103
121
|
const coursesQuery = _Queries.courses.useCourses(async filters => {
|
|
104
122
|
const response = await getCourses(filters);
|
|
105
123
|
return response;
|
|
106
124
|
}, {
|
|
107
125
|
filters: {
|
|
108
|
-
courseIds:
|
|
126
|
+
courseIds: selectedClassroomCourses?.toString()
|
|
109
127
|
}
|
|
110
128
|
}, {
|
|
111
129
|
enabled: !!assignment.classroomId
|
|
@@ -122,17 +140,22 @@ function CreateAssignmentDialog({
|
|
|
122
140
|
}
|
|
123
141
|
})),
|
|
124
142
|
combine: results => {
|
|
143
|
+
const allMembers = results.flatMap(result => {
|
|
144
|
+
if (Array.isArray(result?.data?.Items)) {
|
|
145
|
+
return result.data.Items;
|
|
146
|
+
}
|
|
147
|
+
return [];
|
|
148
|
+
});
|
|
125
149
|
return {
|
|
126
|
-
data:
|
|
150
|
+
data: allMembers,
|
|
127
151
|
pending: results.some(result => result.isPending)
|
|
128
152
|
};
|
|
129
153
|
}
|
|
130
154
|
});
|
|
131
|
-
const
|
|
132
|
-
if (!classroomMembersQueries.pending &&
|
|
133
|
-
const flatMembers = classroomMembersQueries.data[0].Items.flat();
|
|
155
|
+
const uniqueClassroomMembers = (0, _react.useMemo)(() => {
|
|
156
|
+
if (!classroomMembersQueries.pending && classroomMembersQueries.data.length > 0) {
|
|
134
157
|
const seen = new Set();
|
|
135
|
-
const uniqueMembers =
|
|
158
|
+
const uniqueMembers = classroomMembersQueries.data.filter(member => {
|
|
136
159
|
if (seen.has(member.memberId)) return false;
|
|
137
160
|
seen.add(member.memberId);
|
|
138
161
|
return true;
|
|
@@ -160,13 +183,13 @@ function CreateAssignmentDialog({
|
|
|
160
183
|
courses: filteredCourses
|
|
161
184
|
});
|
|
162
185
|
}, [selectedCourses, submittedExercises]);
|
|
163
|
-
const classroomMembers = async
|
|
164
|
-
const formattedMembers = await getMemberDetails(
|
|
186
|
+
const classroomMembers = async uniqueClassroomMembers => {
|
|
187
|
+
const formattedMembers = await getMemberDetails(uniqueClassroomMembers);
|
|
165
188
|
setMembers(formattedMembers);
|
|
166
189
|
};
|
|
167
190
|
(0, _react.useEffect)(() => {
|
|
168
|
-
classroomMembers(
|
|
169
|
-
}, [
|
|
191
|
+
classroomMembers(uniqueClassroomMembers);
|
|
192
|
+
}, [uniqueClassroomMembers]);
|
|
170
193
|
(0, _react.useEffect)(() => {
|
|
171
194
|
setAssignment(prev => ({
|
|
172
195
|
...prev,
|
|
@@ -227,7 +250,7 @@ function CreateAssignmentDialog({
|
|
|
227
250
|
const handleCreateAssignment = async () => {
|
|
228
251
|
try {
|
|
229
252
|
await Promise.all(assignment.classroomId.map(async cId => {
|
|
230
|
-
const selectedClassroomMembers =
|
|
253
|
+
const selectedClassroomMembers = uniqueClassroomMembers.filter(member => member.classroomId === cId).map(student => student.memberId).filter(studentId => assignment?.assignedStudents.includes(studentId));
|
|
231
254
|
const formattedAssignment = {
|
|
232
255
|
...assignment,
|
|
233
256
|
classroomId: cId,
|
|
@@ -108,18 +108,6 @@ const PlanButton = (0, _system.styled)(_material.Button)(({
|
|
|
108
108
|
border: bgPrimary ? `1px solid ${theme.palette.primary.dark}` : null
|
|
109
109
|
}
|
|
110
110
|
}));
|
|
111
|
-
const Badge = (0, _system.styled)(_material.Typography)(({
|
|
112
|
-
theme
|
|
113
|
-
}) => ({
|
|
114
|
-
backgroundColor: theme.palette.common.white,
|
|
115
|
-
color: theme.palette.common.black,
|
|
116
|
-
fontSize: theme.typography.pxToRem(10),
|
|
117
|
-
borderRadius: "9px",
|
|
118
|
-
padding: theme.spacing(0.25, 1),
|
|
119
|
-
display: "flex",
|
|
120
|
-
alignItems: "center",
|
|
121
|
-
marginBottom: "2px"
|
|
122
|
-
}));
|
|
123
111
|
const Plus = (0, _system.styled)("img")(({
|
|
124
112
|
theme
|
|
125
113
|
}) => ({
|
|
@@ -239,8 +227,7 @@ function SubscriptionPlan({
|
|
|
239
227
|
variant: "h3",
|
|
240
228
|
priceSmall: displayPlan.priceSmall,
|
|
241
229
|
children: displayPlan.price ? displayPlan.price : displayPrice
|
|
242
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.
|
|
243
|
-
direction: "column",
|
|
230
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
244
231
|
sx: {
|
|
245
232
|
display: "flex",
|
|
246
233
|
justifyContent: "center"
|
|
@@ -253,8 +240,18 @@ function SubscriptionPlan({
|
|
|
253
240
|
children: t(displayPlan.footnote)
|
|
254
241
|
})
|
|
255
242
|
}),
|
|
256
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(
|
|
257
|
-
|
|
243
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Box, {
|
|
244
|
+
sx: {
|
|
245
|
+
fontSize: theme => theme.typography.pxToRem(10),
|
|
246
|
+
backgroundColor: "common.white",
|
|
247
|
+
color: "common.black",
|
|
248
|
+
borderRadius: "9px",
|
|
249
|
+
py: 0.25,
|
|
250
|
+
px: 1,
|
|
251
|
+
display: "flex",
|
|
252
|
+
alignItems: "center",
|
|
253
|
+
mb: "2px"
|
|
254
|
+
},
|
|
258
255
|
children: t(displayPlan.badge)
|
|
259
256
|
})
|
|
260
257
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {}), priceInfo && priceInfo.interval && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Typography, {
|
|
@@ -208,7 +208,7 @@ function CreatePhrase({
|
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
// // translation
|
|
211
|
-
// const
|
|
211
|
+
// const generateTranslations = async () => {
|
|
212
212
|
// const translatedText = await handleTranslate(phrase, learnLang, forLang);
|
|
213
213
|
// if (translatedText && translatedText !== "") {
|
|
214
214
|
// setAutoTranslatedText(translatedText);
|
|
@@ -221,19 +221,35 @@ function CreatePhrase({
|
|
|
221
221
|
// }
|
|
222
222
|
// };
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
function validateResponse(response) {
|
|
225
|
+
if (!Array.isArray(response)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
for (const item of response) {
|
|
229
|
+
if (item === null || Array.isArray(item)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
const generateTranslations = async () => {
|
|
225
239
|
try {
|
|
226
240
|
let chatGptResponse = await makeChatGptApiRequest({
|
|
227
241
|
model: "gpt-4o-mini",
|
|
228
|
-
promptKey: "
|
|
242
|
+
promptKey: "generateTranslations",
|
|
229
243
|
promptVariables: {
|
|
230
244
|
learnLang,
|
|
231
245
|
forLang,
|
|
232
246
|
phrase
|
|
233
247
|
}
|
|
234
248
|
});
|
|
235
|
-
|
|
236
|
-
|
|
249
|
+
const translations = chatGptResponse.translations;
|
|
250
|
+
if (!validateResponse(translations)) {
|
|
251
|
+
throw new Error("Invalid AI response");
|
|
252
|
+
}
|
|
237
253
|
setTranslationList(prevTranslationList => {
|
|
238
254
|
const newTranslations = translations.filter(text => !prevTranslationList.includes(text));
|
|
239
255
|
return [...prevTranslationList, ...newTranslations];
|
|
@@ -409,7 +425,7 @@ function CreatePhrase({
|
|
|
409
425
|
children: t("add_alternative_version")
|
|
410
426
|
})
|
|
411
427
|
}), learnLang !== forLang && /*#__PURE__*/(0, _jsxRuntime.jsx)(_DefaultColourButton.default, {
|
|
412
|
-
onClick:
|
|
428
|
+
onClick: generateTranslations,
|
|
413
429
|
disabled: !phrase || Array.isArray(translationList) && translationList.includes(autoTranslatedText),
|
|
414
430
|
size: "sm",
|
|
415
431
|
className: classes.alternativeVersionButton,
|
|
@@ -17,6 +17,26 @@ var _jsxRuntime = require("react/jsx-runtime");
|
|
|
17
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
18
18
|
//components
|
|
19
19
|
|
|
20
|
+
function validateResponse(response) {
|
|
21
|
+
if (!Array.isArray(response)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
for (const item of response) {
|
|
25
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (!Object.hasOwn(item, "question") || !Object.hasOwn(item, "answer")) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (typeof item.question !== "string" || item.question.trim() === "") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (typeof item.answer !== "string" || item.answer.trim() === "") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
20
40
|
function GenerateBotDialog({
|
|
21
41
|
t = text => text,
|
|
22
42
|
isGenerateBotDialogOpen,
|
|
@@ -69,39 +89,42 @@ function GenerateBotDialog({
|
|
|
69
89
|
const handleCloseDialog = () => {
|
|
70
90
|
setIsGenerateBotDialogOpen(false);
|
|
71
91
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
|
|
93
|
+
// const removeExtraTextFromChatGptResponse = (response) => {
|
|
94
|
+
// let stack = [];
|
|
95
|
+
// let startIndex = -1;
|
|
96
|
+
// let endIndex = -1;
|
|
97
|
+
// for (let i = 0; i < response.length; i++) {
|
|
98
|
+
// if (response[i] === "[") {
|
|
99
|
+
// if (stack.length === 0) {
|
|
100
|
+
// startIndex = i;
|
|
101
|
+
// }
|
|
102
|
+
// stack.push("[");
|
|
103
|
+
// } else if (response[i] === "]") {
|
|
104
|
+
// stack.pop();
|
|
105
|
+
// if (stack.length === 0) {
|
|
106
|
+
// endIndex = i;
|
|
107
|
+
// break;
|
|
108
|
+
// }
|
|
109
|
+
// }
|
|
110
|
+
// }
|
|
111
|
+
// if (startIndex !== -1 && endIndex !== -1) {
|
|
112
|
+
// return response.substring(startIndex, endIndex + 1);
|
|
113
|
+
// }
|
|
114
|
+
// return "";
|
|
115
|
+
// };
|
|
116
|
+
|
|
117
|
+
// function fixMissingComma(jsonString) {
|
|
118
|
+
// const fixedJsonString = jsonString.replace(/}\s*{/g, "},{");
|
|
119
|
+
// try {
|
|
120
|
+
// const jsonObject = JSON.parse(fixedJsonString);
|
|
121
|
+
// return jsonObject;
|
|
122
|
+
// } catch (error) {
|
|
123
|
+
// console.error("Invalid JSON after attempting to fix:", error);
|
|
124
|
+
// return null;
|
|
125
|
+
// }
|
|
126
|
+
// }
|
|
127
|
+
|
|
105
128
|
const sampleQuestionArray = [{
|
|
106
129
|
question: "What day comes after Monday? ",
|
|
107
130
|
answer: "(Tuesday)"
|
|
@@ -137,7 +160,6 @@ function GenerateBotDialog({
|
|
|
137
160
|
try {
|
|
138
161
|
setIsBotGenerating(true);
|
|
139
162
|
setErrorMessage("");
|
|
140
|
-
let response;
|
|
141
163
|
let chatGptResponse = await makeChatGptApiRequest({
|
|
142
164
|
model: "gpt-4o-mini",
|
|
143
165
|
promptKey: "generateBot",
|
|
@@ -154,9 +176,10 @@ function GenerateBotDialog({
|
|
|
154
176
|
usePhrasesContext: usePhrasesContext
|
|
155
177
|
}
|
|
156
178
|
});
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
const botScript = chatGptResponse.result;
|
|
180
|
+
if (!validateResponse(botScript)) {
|
|
181
|
+
throw new Error("Invalid AI response");
|
|
182
|
+
}
|
|
160
183
|
setIsBotGenerating(false);
|
|
161
184
|
return botScript;
|
|
162
185
|
} catch (error) {
|
|
@@ -8,12 +8,68 @@ var _react = require("react");
|
|
|
8
8
|
var _ResponsiveDialog = _interopRequireDefault(require("../ResponsiveDialog/ResponsiveDialog"));
|
|
9
9
|
var _iconsMaterial = require("@mui/icons-material");
|
|
10
10
|
var _material = require("@mui/material");
|
|
11
|
+
var _styles = require("@mui/material/styles");
|
|
11
12
|
var _reactRouterDom = require("react-router-dom");
|
|
12
13
|
var _Info = _interopRequireDefault(require("@mui/icons-material/Info"));
|
|
13
14
|
var _NualaCreating = _interopRequireDefault(require("../../Misc/NualaCreating/NualaCreating"));
|
|
14
15
|
var _InputHelper = _interopRequireDefault(require("../../Forms/InputHelper/InputHelper"));
|
|
16
|
+
var _mui = require("tss-react/mui");
|
|
15
17
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
16
18
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
19
|
+
const useStyles = (0, _mui.makeStyles)()(theme => ({
|
|
20
|
+
phraseText: {
|
|
21
|
+
width: "80%",
|
|
22
|
+
wordBreak: "break-all"
|
|
23
|
+
},
|
|
24
|
+
phraseTextSmall: {
|
|
25
|
+
width: "60%",
|
|
26
|
+
wordBreak: "break-all"
|
|
27
|
+
},
|
|
28
|
+
avatar: {
|
|
29
|
+
cursor: "pointer",
|
|
30
|
+
margin: theme.spacing()
|
|
31
|
+
},
|
|
32
|
+
avatarNoClick: {
|
|
33
|
+
margin: theme.spacing()
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
function Phrase({
|
|
37
|
+
alternativeVersions,
|
|
38
|
+
translations,
|
|
39
|
+
phrase
|
|
40
|
+
}) {
|
|
41
|
+
const theme = (0, _styles.useTheme)();
|
|
42
|
+
const {
|
|
43
|
+
classes
|
|
44
|
+
} = useStyles();
|
|
45
|
+
const isLargeScreen = (0, _material.useMediaQuery)(theme.breakpoints.up("sm"));
|
|
46
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.ListItem, {
|
|
47
|
+
"data-cy": "phrase-list-item",
|
|
48
|
+
children: isLargeScreen ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
49
|
+
container: true,
|
|
50
|
+
sx: {
|
|
51
|
+
width: "100%"
|
|
52
|
+
},
|
|
53
|
+
className: classes.phraseText,
|
|
54
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.ListItemText, {
|
|
55
|
+
className: classes.phrases,
|
|
56
|
+
primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
|
|
57
|
+
secondary: (translations || []).join(", ")
|
|
58
|
+
})
|
|
59
|
+
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
60
|
+
container: true,
|
|
61
|
+
sx: {
|
|
62
|
+
width: "100%"
|
|
63
|
+
},
|
|
64
|
+
className: classes.phraseTextSmall,
|
|
65
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.ListItemText, {
|
|
66
|
+
className: classes.phrases,
|
|
67
|
+
primary: `${phrase}${Array.isArray(alternativeVersions) && alternativeVersions.length ? " - " : ""}${(alternativeVersions || []).join(", ")}`,
|
|
68
|
+
secondary: (translations || []).join(", ")
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
});
|
|
72
|
+
}
|
|
17
73
|
function GeneratePhrases({
|
|
18
74
|
t,
|
|
19
75
|
open,
|
|
@@ -31,6 +87,7 @@ function GeneratePhrases({
|
|
|
31
87
|
const [values, setValues] = (0, _react.useState)(initialValues);
|
|
32
88
|
const [errorMessage, setErrorMessage] = (0, _react.useState)(null);
|
|
33
89
|
const [phraseType, setPhraseType] = (0, _react.useState)("words");
|
|
90
|
+
const [generatedPhrases, setGeneratedPhrases] = (0, _react.useState)([]);
|
|
34
91
|
const isPhrases = currentPhrases && currentPhrases.length > 0;
|
|
35
92
|
const phrases = isPhrases ? currentPhrases.map(({
|
|
36
93
|
phrase,
|
|
@@ -56,29 +113,6 @@ function GeneratePhrases({
|
|
|
56
113
|
[name]: value
|
|
57
114
|
}));
|
|
58
115
|
};
|
|
59
|
-
const extractPhrasesArray = response => {
|
|
60
|
-
let stack = [];
|
|
61
|
-
let startIndex = -1;
|
|
62
|
-
let endIndex = -1;
|
|
63
|
-
for (let i = 0; i < response.length; i++) {
|
|
64
|
-
if (response[i] === "[") {
|
|
65
|
-
if (stack.length === 0) {
|
|
66
|
-
startIndex = i;
|
|
67
|
-
}
|
|
68
|
-
stack.push("[");
|
|
69
|
-
} else if (response[i] === "]") {
|
|
70
|
-
stack.pop();
|
|
71
|
-
if (stack.length === 0) {
|
|
72
|
-
endIndex = i;
|
|
73
|
-
break;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (startIndex !== -1 && endIndex !== -1) {
|
|
78
|
-
return response.substring(startIndex, endIndex + 1);
|
|
79
|
-
}
|
|
80
|
-
return "";
|
|
81
|
-
};
|
|
82
116
|
function validateResponse(response) {
|
|
83
117
|
if (!Array.isArray(response)) {
|
|
84
118
|
return false;
|
|
@@ -127,25 +161,32 @@ function GeneratePhrases({
|
|
|
127
161
|
phrases
|
|
128
162
|
}
|
|
129
163
|
});
|
|
130
|
-
const phrasesArray =
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
throw new Error("Invalid response from GPT-3.5");
|
|
164
|
+
const phrasesArray = chatGptResponse.result;
|
|
165
|
+
if (!validateResponse(phrasesArray)) {
|
|
166
|
+
throw new Error("Invalid AI response");
|
|
134
167
|
}
|
|
135
|
-
|
|
168
|
+
setGeneratedPhrases(phrasesArray);
|
|
136
169
|
setIsGenerating(false);
|
|
137
|
-
handleClose();
|
|
138
170
|
} catch (error) {
|
|
139
171
|
console.error(error);
|
|
140
172
|
setIsGenerating(false);
|
|
141
173
|
setErrorMessage("There was a problem generating phrases. Please try again.");
|
|
142
174
|
}
|
|
143
175
|
};
|
|
176
|
+
const handleConfirm = () => {
|
|
177
|
+
handleAppendPhrases(generatedPhrases);
|
|
178
|
+
setGeneratedPhrases([]);
|
|
179
|
+
handleClose();
|
|
180
|
+
};
|
|
181
|
+
const handleCancel = () => {
|
|
182
|
+
setGeneratedPhrases([]);
|
|
183
|
+
handleClose();
|
|
184
|
+
};
|
|
144
185
|
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_ResponsiveDialog.default, {
|
|
145
186
|
open: open,
|
|
146
|
-
handleClose:
|
|
147
|
-
handleSubmit: handleGeneratePhrases,
|
|
148
|
-
submitText: t("generate_phrases"),
|
|
187
|
+
handleClose: handleCancel,
|
|
188
|
+
handleSubmit: generatedPhrases.length > 0 ? handleConfirm : handleGeneratePhrases,
|
|
189
|
+
submitText: generatedPhrases.length > 0 ? t("confirm") : t("generate_phrases"),
|
|
149
190
|
dialogTitle: isGenerating ? t("generating_phrases") : t("generate_phrases"),
|
|
150
191
|
isSubmitDisabled: isGenerating,
|
|
151
192
|
isExperimentalFeature: true,
|
|
@@ -170,111 +211,119 @@ function GeneratePhrases({
|
|
|
170
211
|
size: 12,
|
|
171
212
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.LinearProgress, {})
|
|
172
213
|
}), !isGenerating && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
173
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
children: [generatedPhrases.length === 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
215
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
216
|
+
fontSize: 14,
|
|
217
|
+
sx: {
|
|
218
|
+
opacity: 0.8,
|
|
219
|
+
marginBottom: 1
|
|
220
|
+
},
|
|
221
|
+
children: t("required_field")
|
|
222
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
223
|
+
size: 12,
|
|
224
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Alert, {
|
|
225
|
+
severity: "info",
|
|
226
|
+
children: [t("experimental_feature_desc_phrases"), " -", " ", /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactRouterDom.Link, {
|
|
227
|
+
to: "/contact",
|
|
228
|
+
children: [t("contact_form"), "."]
|
|
229
|
+
})]
|
|
230
|
+
})
|
|
231
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
232
|
+
size: 12,
|
|
233
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_InputHelper.default, {
|
|
234
|
+
t: t,
|
|
235
|
+
id: "phrasesTopic",
|
|
236
|
+
name: "phrasesTopic",
|
|
237
|
+
"data-cy": "phrasesTopic",
|
|
238
|
+
label: t("choose_topic_for_phrases"),
|
|
239
|
+
value: values.phrasesTopic,
|
|
240
|
+
type: "text",
|
|
241
|
+
margin: "normal",
|
|
242
|
+
variant: "outlined",
|
|
243
|
+
fullWidth: true,
|
|
244
|
+
required: true,
|
|
245
|
+
onChange: handleChange,
|
|
246
|
+
multiline: true,
|
|
247
|
+
rows: 3
|
|
248
|
+
})
|
|
249
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
250
|
+
mb: 2,
|
|
251
|
+
size: 12,
|
|
252
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.TextField, {
|
|
253
|
+
id: "topicGoal",
|
|
254
|
+
name: "topicGoal",
|
|
255
|
+
"data-cy": "topicGoal",
|
|
256
|
+
label: t("topic_goal"),
|
|
257
|
+
placeholder: t("topic_goal_placeholder"),
|
|
258
|
+
value: values.topicGoal,
|
|
259
|
+
type: "text",
|
|
260
|
+
margin: "normal",
|
|
261
|
+
variant: "outlined",
|
|
262
|
+
fullWidth: true,
|
|
263
|
+
onChange: handleChange,
|
|
264
|
+
InputProps: {
|
|
265
|
+
endAdornment: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.InputAdornment, {
|
|
266
|
+
position: "end",
|
|
267
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Tooltip, {
|
|
268
|
+
title: t("topic_goal_info_text"),
|
|
269
|
+
placement: "top",
|
|
270
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Info.default, {
|
|
271
|
+
fontSize: "small",
|
|
272
|
+
style: {
|
|
273
|
+
color: "lightgrey"
|
|
274
|
+
}
|
|
275
|
+
})
|
|
233
276
|
})
|
|
234
277
|
})
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
281
|
+
mb: 2,
|
|
282
|
+
size: 12,
|
|
283
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.TextField, {
|
|
284
|
+
select: true,
|
|
285
|
+
label: t("phrase_type"),
|
|
286
|
+
value: phraseType,
|
|
287
|
+
onChange: e => setPhraseType(e.target.value),
|
|
288
|
+
fullWidth: true,
|
|
289
|
+
variant: "outlined",
|
|
290
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.MenuItem, {
|
|
291
|
+
value: "words",
|
|
292
|
+
children: t("words")
|
|
293
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.MenuItem, {
|
|
294
|
+
value: "sentences",
|
|
295
|
+
children: t("sentences")
|
|
296
|
+
})]
|
|
297
|
+
})
|
|
298
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Grid, {
|
|
299
|
+
size: 12,
|
|
300
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
301
|
+
id: "input-slider",
|
|
302
|
+
gutterBottom: true,
|
|
303
|
+
children: t("how_many_phrases", {
|
|
304
|
+
phraseAmount: values.phraseAmount
|
|
235
305
|
})
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
value: "words",
|
|
250
|
-
children: t("words")
|
|
251
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.MenuItem, {
|
|
252
|
-
value: "sentences",
|
|
253
|
-
children: t("sentences")
|
|
306
|
+
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Slider, {
|
|
307
|
+
"aria-label": "Number of phrases",
|
|
308
|
+
defaultValue: values.phraseAmount,
|
|
309
|
+
valueLabelDisplay: "auto",
|
|
310
|
+
value: values.phraseAmount,
|
|
311
|
+
onChange: (e, value) => setValues({
|
|
312
|
+
...values,
|
|
313
|
+
phraseAmount: value
|
|
314
|
+
}),
|
|
315
|
+
step: 2,
|
|
316
|
+
marks: true,
|
|
317
|
+
min: 4,
|
|
318
|
+
max: 16
|
|
254
319
|
})]
|
|
255
|
-
})
|
|
256
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Grid, {
|
|
257
|
-
size: 12,
|
|
258
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
259
|
-
id: "input-slider",
|
|
260
|
-
gutterBottom: true,
|
|
261
|
-
children: t("how_many_phrases", {
|
|
262
|
-
phraseAmount: values.phraseAmount
|
|
263
|
-
})
|
|
264
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Slider, {
|
|
265
|
-
"aria-label": "Number of phrases",
|
|
266
|
-
defaultValue: values.phraseAmount,
|
|
267
|
-
valueLabelDisplay: "auto",
|
|
268
|
-
value: values.phraseAmount,
|
|
269
|
-
onChange: (e, value) => setValues({
|
|
270
|
-
...values,
|
|
271
|
-
phraseAmount: value
|
|
272
|
-
}),
|
|
273
|
-
step: 2,
|
|
274
|
-
marks: true,
|
|
275
|
-
min: 4,
|
|
276
|
-
max: 16
|
|
277
320
|
})]
|
|
321
|
+
}), generatedPhrases.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
322
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
323
|
+
children: t("generated_phrases")
|
|
324
|
+
}), generatedPhrases.map((phrase, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(Phrase, {
|
|
325
|
+
...phrase
|
|
326
|
+
}, index))]
|
|
278
327
|
}), errorMessage && /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
|
|
279
328
|
mt: 2,
|
|
280
329
|
size: 12,
|
|
@@ -87,7 +87,7 @@ function GenerateQuestion({
|
|
|
87
87
|
try {
|
|
88
88
|
setIsMessageGenerating(true);
|
|
89
89
|
setErrorMessage(null);
|
|
90
|
-
const
|
|
90
|
+
const newQuestion = await makeChatGptApiRequest({
|
|
91
91
|
model: "gpt-4o-mini",
|
|
92
92
|
promptKey: "generateAnswer",
|
|
93
93
|
promptVariables: {
|
|
@@ -100,8 +100,6 @@ function GenerateQuestion({
|
|
|
100
100
|
questionLanguage
|
|
101
101
|
}
|
|
102
102
|
});
|
|
103
|
-
const jsonResponse = extractJsonObject(chatGptResponse);
|
|
104
|
-
const newQuestion = JSON.parse(jsonResponse);
|
|
105
103
|
if (!validateResponse(newQuestion)) {
|
|
106
104
|
throw new Error("Invalid response from GPT-3.5");
|
|
107
105
|
}
|
|
@@ -231,25 +231,37 @@ function GenerateRoleplayDialog({
|
|
|
231
231
|
}
|
|
232
232
|
});
|
|
233
233
|
let isComplete = false;
|
|
234
|
+
let fullResult = [];
|
|
234
235
|
while (!isComplete) {
|
|
235
|
-
|
|
236
|
+
let parsed;
|
|
237
|
+
try {
|
|
238
|
+
parsed = typeof chatGptResponse === "string" ? JSON.parse(chatGptResponse) : chatGptResponse;
|
|
239
|
+
} catch (e) {
|
|
240
|
+
console.warn("Failed to parse response – assuming truncation");
|
|
241
|
+
parsed = {
|
|
242
|
+
result: []
|
|
243
|
+
}; // fallback
|
|
244
|
+
}
|
|
245
|
+
if (Array.isArray(parsed.result)) {
|
|
246
|
+
fullResult = [...fullResult, ...parsed.result];
|
|
247
|
+
}
|
|
248
|
+
// Check last item for signs of truncation
|
|
249
|
+
const lastItem = parsed.result?.[parsed.result.length - 1];
|
|
250
|
+
const endsSuspiciously = lastItem?.text?.trim?.().endsWith("...") || lastItem?.text?.trim?.().endsWith(",");
|
|
251
|
+
if (endsSuspiciously) {
|
|
252
|
+
const continuationPrompt = `Continue the following in the same JSON format:\n\n${JSON.stringify(parsed.result, null, 2)}`;
|
|
236
253
|
const continuationResponse = await makeChatGptApiRequest({
|
|
237
254
|
model: "gpt-4o-mini",
|
|
238
|
-
prompt:
|
|
255
|
+
prompt: continuationPrompt
|
|
239
256
|
});
|
|
240
|
-
chatGptResponse
|
|
257
|
+
chatGptResponse = continuationResponse;
|
|
241
258
|
} else {
|
|
242
259
|
isComplete = true;
|
|
243
260
|
}
|
|
244
261
|
}
|
|
245
|
-
response =
|
|
262
|
+
response = fullResult;
|
|
246
263
|
}
|
|
247
|
-
|
|
248
|
-
// Log the full response for debugging
|
|
249
|
-
|
|
250
|
-
const jsonResponse = removeExtraTextFromChatGptResponse(response);
|
|
251
|
-
const roleplayScript = fixMissingComma(jsonResponse);
|
|
252
|
-
if (validateScript(roleplayScript)) {
|
|
264
|
+
if (validateScript(response)) {
|
|
253
265
|
const generatedRoleplay = {
|
|
254
266
|
roleplayId: (0, _utils.randomId)(),
|
|
255
267
|
roleplayName: values.roleplayTopic,
|
|
@@ -260,7 +272,7 @@ function GenerateRoleplayDialog({
|
|
|
260
272
|
learnLang: learnLang,
|
|
261
273
|
translationEnabled: "enable",
|
|
262
274
|
repliesEnabled: "enable",
|
|
263
|
-
script:
|
|
275
|
+
script: response,
|
|
264
276
|
actor1: {
|
|
265
277
|
name: getAllVoices()[0].name,
|
|
266
278
|
voice: getAllVoices()[0].name,
|
|
@@ -88,8 +88,7 @@ const useStyles = (0, _mui.makeStyles)()((theme, {
|
|
|
88
88
|
color: theme.palette.text.secondary
|
|
89
89
|
},
|
|
90
90
|
characterDropdown: {
|
|
91
|
-
margin: theme.spacing(1)
|
|
92
|
-
marginTop: "26px"
|
|
91
|
+
margin: theme.spacing(1)
|
|
93
92
|
},
|
|
94
93
|
formattingOptionsButton: {
|
|
95
94
|
backgroundColor: isFormattingOptionsOpen ? theme.palette.action.selected : null
|
|
@@ -1423,7 +1423,7 @@ function Classroom({
|
|
|
1423
1423
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_ResponsiveTabs.default, {
|
|
1424
1424
|
t: t,
|
|
1425
1425
|
tabs: tabs,
|
|
1426
|
-
fabs: isCreator && isVideoChatEnabled && isVideoChatEnabledInSettings && !isArchived ? courses?.length ? fabs : [[vchatFab]] : isCreator && courses?.length && !isArchived ? [[fabs[0][0]]] : [],
|
|
1426
|
+
fabs: isCreator && isVideoChatEnabled && isVideoChatEnabledInSettings && !isArchived ? courses?.length ? fabs : [[vchatFab]] : isCreator && courses?.length && !isArchived ? [[fabs[0][0], assignmentsFab]] : [],
|
|
1427
1427
|
centered: true,
|
|
1428
1428
|
scrollable: true,
|
|
1429
1429
|
hasNewAssignments: hasNewAssignments,
|
|
@@ -1480,7 +1480,7 @@ function Classroom({
|
|
|
1480
1480
|
open: isInviteDialogOpen,
|
|
1481
1481
|
handleClose: handleCloseInviteDialog,
|
|
1482
1482
|
onSubmit: handleInvites,
|
|
1483
|
-
inviteLink: `${window.location.
|
|
1483
|
+
inviteLink: `${window.location.origin}/classrooms/${classroomId}?plan=learn&learnlang=${classroom.learnLang}`,
|
|
1484
1484
|
enrolmentKey: classroom.visibility === "private" ? classroom.enrolmentKey : "",
|
|
1485
1485
|
inviteType: "classroom",
|
|
1486
1486
|
attributes: attributes,
|
|
@@ -960,7 +960,7 @@ function Course({
|
|
|
960
960
|
open: isInviteDialogOpen,
|
|
961
961
|
handleClose: handleCloseInviteDialog,
|
|
962
962
|
onSubmit: handleInvites,
|
|
963
|
-
inviteLink: `${window.location.
|
|
963
|
+
inviteLink: `${window.location.origin}/courses/${courseId}?plan=learn&learnlang=${course.learnLang}`,
|
|
964
964
|
enrolmentKey: course.visibility === "private" ? course.enrolmentKey : "",
|
|
965
965
|
inviteType: "course",
|
|
966
966
|
attributes: attributes,
|
|
@@ -346,10 +346,10 @@ function AssignmentCellData({
|
|
|
346
346
|
if (filter === "percentage_complete" && data.percentComplete) {
|
|
347
347
|
cellData = data.percentComplete;
|
|
348
348
|
cellData.type = "percent";
|
|
349
|
-
} else if (filter === "correct_answer_percentage" && data.percentCorrect) {
|
|
349
|
+
} else if (filter === "correct_answer_percentage" && data.percentCorrect !== null) {
|
|
350
350
|
cellData = data.percentCorrect;
|
|
351
351
|
cellData.type = "correct";
|
|
352
|
-
} else if (filter === "avg_pronunciation_score" && data.avgPronunciationScore) {
|
|
352
|
+
} else if (filter === "avg_pronunciation_score" && data.avgPronunciationScore !== null) {
|
|
353
353
|
cellData = data.avgPronunciationScore;
|
|
354
354
|
cellData.type = "avgPronuc";
|
|
355
355
|
}
|
|
@@ -89,25 +89,16 @@ const formatMemberCourseCompletions = (courses = [], completions = [], assignmen
|
|
|
89
89
|
};
|
|
90
90
|
exports.formatMemberCourseCompletions = formatMemberCourseCompletions;
|
|
91
91
|
const formatMemberAssignmentCompletions = (assignments = [], courses = [], completions = []) => {
|
|
92
|
-
const stats = assignments.reduce((obj,
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
a.percentCorrect.total += course.percentCorrect.total;
|
|
103
|
-
a.percentCorrect.percentage = Math.floor(a.percentCorrect.completed / a.percentCorrect.total * 100);
|
|
104
|
-
}
|
|
105
|
-
if (a && course && course.avgPronunciationScore && course.avgPronunciationScore.percentage > 0) {
|
|
106
|
-
a.avgPronunciationScore.total += 1;
|
|
107
|
-
a.avgPronunciationScore.percentage = Math.floor(course.avgPronunciationScore.percentage / a.avgPronunciationScore.total);
|
|
108
|
-
}
|
|
109
|
-
return a;
|
|
110
|
-
}, {
|
|
92
|
+
const stats = assignments.reduce((obj, assignment) => {
|
|
93
|
+
const assignmentCompletions = completions.filter(completion => assignment.exercises?.some(e => {
|
|
94
|
+
if (e.name && completion.exercise !== e.name) return false;
|
|
95
|
+
if (e.roleplayId && completion.roleplayId !== e.roleplayId) return false;
|
|
96
|
+
if (e.botId && completion.botId !== e.botId) return false;
|
|
97
|
+
if (e.courseSectionTopicId && `${completion.courseSectionId}|${completion.topicId}` !== e.courseSectionTopicId) return false;
|
|
98
|
+
return true;
|
|
99
|
+
}));
|
|
100
|
+
const assignmentCourseCompletions = formatMemberCourseCompletions(courses, assignmentCompletions, assignment?.exercises);
|
|
101
|
+
const accumulator = {
|
|
111
102
|
percentComplete: {
|
|
112
103
|
completed: 0,
|
|
113
104
|
total: 0,
|
|
@@ -120,10 +111,35 @@ const formatMemberAssignmentCompletions = (assignments = [], courses = [], compl
|
|
|
120
111
|
},
|
|
121
112
|
avgPronunciationScore: {
|
|
122
113
|
total: 0,
|
|
114
|
+
sum: 0,
|
|
123
115
|
percentage: 0
|
|
124
116
|
}
|
|
117
|
+
};
|
|
118
|
+
Object.values(assignmentCourseCompletions).forEach(course => {
|
|
119
|
+
if (course?.percentComplete) {
|
|
120
|
+
accumulator.percentComplete.completed += course.percentComplete.completed || 0;
|
|
121
|
+
accumulator.percentComplete.total += course.percentComplete.total || 0;
|
|
122
|
+
}
|
|
123
|
+
if (course?.percentCorrect) {
|
|
124
|
+
accumulator.percentCorrect.completed += course.percentCorrect.completed || 0;
|
|
125
|
+
accumulator.percentCorrect.total += course.percentCorrect.total || 0;
|
|
126
|
+
}
|
|
127
|
+
if (course?.avgPronunciationScore && course.avgPronunciationScore.percentage >= 0) {
|
|
128
|
+
accumulator.avgPronunciationScore.total += 1;
|
|
129
|
+
accumulator.avgPronunciationScore.sum += course.avgPronunciationScore.percentage;
|
|
130
|
+
}
|
|
125
131
|
});
|
|
126
|
-
|
|
132
|
+
if (accumulator.percentComplete.total > 0) {
|
|
133
|
+
accumulator.percentComplete.percentage = Math.floor(accumulator.percentComplete.completed / accumulator.percentComplete.total * 100);
|
|
134
|
+
}
|
|
135
|
+
if (accumulator.percentCorrect.total > 0) {
|
|
136
|
+
accumulator.percentCorrect.percentage = Math.floor(accumulator.percentCorrect.completed / accumulator.percentCorrect.total * 100);
|
|
137
|
+
}
|
|
138
|
+
if (accumulator.avgPronunciationScore.total > 0) {
|
|
139
|
+
accumulator.avgPronunciationScore.percentage = Math.floor(accumulator.avgPronunciationScore.sum / accumulator.avgPronunciationScore.total);
|
|
140
|
+
}
|
|
141
|
+
delete accumulator.avgPronunciationScore.sum;
|
|
142
|
+
obj[assignment.assignmentId] = accumulator;
|
|
127
143
|
return obj;
|
|
128
144
|
}, {});
|
|
129
145
|
return stats;
|
package/dist/utils/index.js
CHANGED
|
@@ -144,7 +144,15 @@ const getScoreValues = (type, id, completions) => {
|
|
|
144
144
|
percentCorrect.total = totalAnswers;
|
|
145
145
|
percentCorrect.percentage = totalAnswers > 0 ? Math.floor(totalAnswersCorrect / totalAnswers * 100) : 0;
|
|
146
146
|
const avgPronunciationScore = pronunciationScoreCount > 0 ? Math.floor(totalPronunciationScore / pronunciationScoreCount) : -1;
|
|
147
|
-
|
|
147
|
+
if (totalAnswers > 0) {
|
|
148
|
+
return [percentCorrect, avgPronunciationScore];
|
|
149
|
+
} else {
|
|
150
|
+
return [{
|
|
151
|
+
completed: 0,
|
|
152
|
+
total: 0,
|
|
153
|
+
percentage: 0
|
|
154
|
+
}, avgPronunciationScore];
|
|
155
|
+
}
|
|
148
156
|
};
|
|
149
157
|
exports.getScoreValues = getScoreValues;
|
|
150
158
|
const calcTopicCompletions = (topic, completions = [], isSectionHidden, assignmentExercises) => {
|