@nualang/nualang-ui-components 0.1.1255 → 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.
@@ -79,33 +79,51 @@ function CreateAssignmentDialog({
79
79
  exercises: submittedExercises,
80
80
  ...initialData
81
81
  });
82
- const classroomQuery = _Queries.classrooms.useClassroom(async classroomId => {
83
- const allClassroomsCourses = [];
84
- await Promise.all(classroomId.map(async cId => {
85
- if (!cId) return;
86
- const response = await getClassroom(cId);
87
- allClassroomsCourses.push(response?.Item?.courses || []);
88
- }));
89
- const flatClassrooms = allClassroomsCourses.flat();
90
- const seen = new Set();
91
- const uniqueClassroomsCourses = flatClassrooms.filter(classR => {
92
- if (seen.has(classR)) return false;
93
- seen.add(classR);
94
- return true;
95
- });
96
- return uniqueClassroomsCourses;
97
- }, {
98
- classroomId: assignment?.classroomId
99
- }, {
100
- enabled: !!(assignment.classroomId.length > 0)
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 selectedClassroom = (0, _react.useMemo)(() => classroomQuery.isSuccess && classroomQuery.data ? classroomQuery.data : {}, [classroomQuery.data, classroomQuery.isSuccess]);
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: selectedClassroom?.toString()
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: results.map(result => result.data),
150
+ data: allMembers,
127
151
  pending: results.some(result => result.isPending)
128
152
  };
129
153
  }
130
154
  });
131
- const selectedMembers = (0, _react.useMemo)(() => {
132
- if (!classroomMembersQueries.pending && Array.isArray(classroomMembersQueries.data[0].Items) && classroomMembersQueries.data[0].Items.length) {
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 = flatMembers.filter(member => {
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 selectedMembers => {
164
- const formattedMembers = await getMemberDetails(selectedMembers);
186
+ const classroomMembers = async uniqueClassroomMembers => {
187
+ const formattedMembers = await getMemberDetails(uniqueClassroomMembers);
165
188
  setMembers(formattedMembers);
166
189
  };
167
190
  (0, _react.useEffect)(() => {
168
- classroomMembers(selectedMembers);
169
- }, [selectedMembers]);
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 = selectedMembers.filter(member => member.classroomId === cId).map(student => student.memberId).filter(studentId => assignment?.assignedStudents.includes(studentId));
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.Grid, {
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)(Badge, {
257
- component: "div",
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 generateTranslation = async () => {
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
- const generateTranslation = async () => {
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: "generateTranslation",
242
+ promptKey: "generateTranslations",
229
243
  promptVariables: {
230
244
  learnLang,
231
245
  forLang,
232
246
  phrase
233
247
  }
234
248
  });
235
- chatGptResponse = chatGptResponse.replaceAll("```", '').replaceAll("json", '');
236
- const translations = JSON.parse(chatGptResponse);
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: generateTranslation,
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
- const removeExtraTextFromChatGptResponse = response => {
73
- let stack = [];
74
- let startIndex = -1;
75
- let endIndex = -1;
76
- for (let i = 0; i < response.length; i++) {
77
- if (response[i] === "[") {
78
- if (stack.length === 0) {
79
- startIndex = i;
80
- }
81
- stack.push("[");
82
- } else if (response[i] === "]") {
83
- stack.pop();
84
- if (stack.length === 0) {
85
- endIndex = i;
86
- break;
87
- }
88
- }
89
- }
90
- if (startIndex !== -1 && endIndex !== -1) {
91
- return response.substring(startIndex, endIndex + 1);
92
- }
93
- return "";
94
- };
95
- function fixMissingComma(jsonString) {
96
- const fixedJsonString = jsonString.replace(/}\s*{/g, "},{");
97
- try {
98
- const jsonObject = JSON.parse(fixedJsonString);
99
- return jsonObject;
100
- } catch (error) {
101
- console.error("Invalid JSON after attempting to fix:", error);
102
- return null;
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
- response = chatGptResponse;
158
- const jsonResponse = removeExtraTextFromChatGptResponse(response);
159
- const botScript = fixMissingComma(jsonResponse);
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) {
@@ -113,29 +113,6 @@ function GeneratePhrases({
113
113
  [name]: value
114
114
  }));
115
115
  };
116
- const extractPhrasesArray = response => {
117
- let stack = [];
118
- let startIndex = -1;
119
- let endIndex = -1;
120
- for (let i = 0; i < response.length; i++) {
121
- if (response[i] === "[") {
122
- if (stack.length === 0) {
123
- startIndex = i;
124
- }
125
- stack.push("[");
126
- } else if (response[i] === "]") {
127
- stack.pop();
128
- if (stack.length === 0) {
129
- endIndex = i;
130
- break;
131
- }
132
- }
133
- }
134
- if (startIndex !== -1 && endIndex !== -1) {
135
- return response.substring(startIndex, endIndex + 1);
136
- }
137
- return "";
138
- };
139
116
  function validateResponse(response) {
140
117
  if (!Array.isArray(response)) {
141
118
  return false;
@@ -184,12 +161,11 @@ function GeneratePhrases({
184
161
  phrases
185
162
  }
186
163
  });
187
- const phrasesArray = extractPhrasesArray(chatGptResponse);
188
- const parsedPhrases = JSON.parse(phrasesArray);
189
- if (!validateResponse(parsedPhrases)) {
190
- throw new Error("Invalid response from GPT-4o-mini");
164
+ const phrasesArray = chatGptResponse.result;
165
+ if (!validateResponse(phrasesArray)) {
166
+ throw new Error("Invalid AI response");
191
167
  }
192
- setGeneratedPhrases(parsedPhrases);
168
+ setGeneratedPhrases(phrasesArray);
193
169
  setIsGenerating(false);
194
170
  } catch (error) {
195
171
  console.error(error);
@@ -87,7 +87,7 @@ function GenerateQuestion({
87
87
  try {
88
88
  setIsMessageGenerating(true);
89
89
  setErrorMessage(null);
90
- const chatGptResponse = await makeChatGptApiRequest({
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
- if (chatGptResponse.endsWith("...") || chatGptResponse.trim().endsWith(",")) {
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: "Continue: " + chatGptResponse
255
+ prompt: continuationPrompt
239
256
  });
240
- chatGptResponse += continuationResponse;
257
+ chatGptResponse = continuationResponse;
241
258
  } else {
242
259
  isComplete = true;
243
260
  }
244
261
  }
245
- response = chatGptResponse;
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: roleplayScript,
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.href}?plan=learn&learnlang=${classroom.learnLang}`,
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.href}?plan=learn&learnlang=${course.learnLang}`,
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,
@@ -75,7 +75,7 @@ const RecordingListCards = (0, _mui.withStyles)(({
75
75
  variant: "headline",
76
76
  color: "inherit",
77
77
  fontSize: 24,
78
- children: t("recordings")
78
+ children: t("submissions")
79
79
  })
80
80
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
81
81
  className: classes.grow
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1255",
3
+ "version": "0.1.1256",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",