@speakableio/core 1.0.20 → 1.0.22
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/index.native.d.mts +2571 -2
- package/dist/index.native.d.ts +2571 -2
- package/dist/index.native.js +2415 -12
- package/dist/index.native.js.map +1 -1
- package/dist/index.native.mjs +2441 -38
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.web.d.mts +2571 -2
- package/dist/index.web.js +2441 -38
- package/dist/index.web.js.map +1 -1
- package/package.json +1 -1
package/dist/index.native.js
CHANGED
|
@@ -30,12 +30,116 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/entry-points/index.native.ts
|
|
31
31
|
var index_native_exports = {};
|
|
32
32
|
__export(index_native_exports, {
|
|
33
|
-
|
|
33
|
+
ActivityPageType: () => ActivityPageType,
|
|
34
|
+
BASE_MULTIPLE_CHOICE_FIELD_VALUES: () => BASE_MULTIPLE_CHOICE_FIELD_VALUES,
|
|
35
|
+
BASE_REPEAT_FIELD_VALUES: () => BASE_REPEAT_FIELD_VALUES,
|
|
36
|
+
BASE_RESPOND_FIELD_VALUES: () => BASE_RESPOND_FIELD_VALUES,
|
|
37
|
+
FeedbackTypesCard: () => FeedbackTypesCard,
|
|
38
|
+
FsCtx: () => FsCtx,
|
|
39
|
+
LENIENCY_OPTIONS: () => LENIENCY_OPTIONS,
|
|
40
|
+
LeniencyCard: () => LeniencyCard,
|
|
41
|
+
MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES: () => MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES,
|
|
42
|
+
REPEAT_PAGE_ACTIVITY_TYPES: () => REPEAT_PAGE_ACTIVITY_TYPES,
|
|
43
|
+
RESPOND_AUDIO_PAGE_ACTIVITY_TYPES: () => RESPOND_AUDIO_PAGE_ACTIVITY_TYPES,
|
|
44
|
+
RESPOND_PAGE_ACTIVITY_TYPES: () => RESPOND_PAGE_ACTIVITY_TYPES,
|
|
45
|
+
RESPOND_WRITE_PAGE_ACTIVITY_TYPES: () => RESPOND_WRITE_PAGE_ACTIVITY_TYPES,
|
|
46
|
+
SPEAKABLE_NOTIFICATIONS: () => SPEAKABLE_NOTIFICATIONS,
|
|
47
|
+
STUDENT_LEVELS_OPTIONS: () => STUDENT_LEVELS_OPTIONS,
|
|
48
|
+
SpeakableNotificationTypes: () => SpeakableNotificationTypes,
|
|
49
|
+
SpeakableProvider: () => SpeakableProvider,
|
|
50
|
+
VerificationCardStatus: () => VerificationCardStatus,
|
|
51
|
+
assignmentQueryKeys: () => assignmentQueryKeys,
|
|
52
|
+
cardsQueryKeys: () => cardsQueryKeys,
|
|
53
|
+
checkIsMCPage: () => checkIsMCPage,
|
|
54
|
+
checkIsMediaPage: () => checkIsMediaPage,
|
|
55
|
+
checkIsRepeatPage: () => checkIsRepeatPage,
|
|
56
|
+
checkIsRespondAudioPage: () => checkIsRespondAudioPage,
|
|
57
|
+
checkIsRespondPage: () => checkIsRespondPage,
|
|
58
|
+
checkIsRespondWrittenPage: () => checkIsRespondWrittenPage,
|
|
59
|
+
checkIsShortAnswerPage: () => checkIsShortAnswerPage,
|
|
60
|
+
checkTypePageActivity: () => checkTypePageActivity,
|
|
61
|
+
cleanString: () => cleanString,
|
|
62
|
+
createAssignmentRepo: () => createAssignmentRepo,
|
|
63
|
+
createCardRepo: () => createCardRepo,
|
|
64
|
+
createFsClient: () => createFsClientNative,
|
|
65
|
+
createSetRepo: () => createSetRepo,
|
|
66
|
+
creditQueryKeys: () => creditQueryKeys,
|
|
67
|
+
debounce: () => debounce,
|
|
68
|
+
getCardFromCache: () => getCardFromCache,
|
|
69
|
+
getLabelPage: () => getLabelPage,
|
|
70
|
+
getPagePrompt: () => getPagePrompt,
|
|
71
|
+
getPhraseLength: () => getPhraseLength,
|
|
72
|
+
getRespondCardTool: () => getRespondCardTool,
|
|
73
|
+
getSetFromCache: () => getSetFromCache,
|
|
74
|
+
getTotalCompletedCards: () => getTotalCompletedCards,
|
|
75
|
+
getTranscript: () => getTranscript,
|
|
76
|
+
getWordHash: () => getWordHash,
|
|
77
|
+
purify: () => purify,
|
|
78
|
+
refsCardsFiresotre: () => refsCardsFiresotre,
|
|
79
|
+
refsSetsFirestore: () => refsSetsFirestore,
|
|
80
|
+
scoreQueryKeys: () => scoreQueryKeys,
|
|
81
|
+
setsQueryKeys: () => setsQueryKeys,
|
|
82
|
+
updateCardInCache: () => updateCardInCache,
|
|
83
|
+
updateSetInCache: () => updateSetInCache,
|
|
84
|
+
useActivity: () => useActivity,
|
|
85
|
+
useActivityFeedbackAccess: () => useActivityFeedbackAccess,
|
|
86
|
+
useAssignment: () => useAssignment,
|
|
87
|
+
useBaseOpenAI: () => useBaseOpenAI,
|
|
88
|
+
useCards: () => useCards,
|
|
89
|
+
useClearScore: () => useClearScore,
|
|
90
|
+
useClearScoreV2: () => useClearScoreV2,
|
|
91
|
+
useCreateCard: () => useCreateCard,
|
|
92
|
+
useCreateCards: () => useCreateCards,
|
|
93
|
+
useCreateNotification: () => useCreateNotification,
|
|
94
|
+
useGetCard: () => useGetCard,
|
|
95
|
+
useOrganizationAccess: () => useOrganizationAccess,
|
|
96
|
+
useScore: () => useScore,
|
|
97
|
+
useSet: () => useSet,
|
|
98
|
+
useSpeakableApi: () => useSpeakableApi,
|
|
99
|
+
useSpeakableTranscript: () => useSpeakableTranscript,
|
|
100
|
+
useSubmitAssignmentScore: () => useSubmitAssignmentScore,
|
|
101
|
+
useSubmitPracticeScore: () => useSubmitPracticeScore,
|
|
102
|
+
useUpdateCardScore: () => useUpdateCardScore,
|
|
103
|
+
useUpdateScore: () => useUpdateScore,
|
|
104
|
+
useUpdateStudentVocab: () => useUpdateStudentVocab,
|
|
105
|
+
useUserCredits: () => useUserCredits
|
|
34
106
|
});
|
|
35
107
|
module.exports = __toCommonJS(index_native_exports);
|
|
36
108
|
|
|
37
|
-
// src/
|
|
38
|
-
var
|
|
109
|
+
// src/providers/SpeakableProvider.tsx
|
|
110
|
+
var import_react = require("react");
|
|
111
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
112
|
+
var FsCtx = (0, import_react.createContext)(null);
|
|
113
|
+
function SpeakableProvider({
|
|
114
|
+
user,
|
|
115
|
+
children,
|
|
116
|
+
queryClient,
|
|
117
|
+
permissions,
|
|
118
|
+
fsClient
|
|
119
|
+
}) {
|
|
120
|
+
const [speakableApi, setSpeakableApi] = (0, import_react.useState)(null);
|
|
121
|
+
(0, import_react.useEffect)(() => {
|
|
122
|
+
setSpeakableApi(fsClient);
|
|
123
|
+
}, [fsClient]);
|
|
124
|
+
if (!speakableApi) return null;
|
|
125
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
126
|
+
FsCtx.Provider,
|
|
127
|
+
{
|
|
128
|
+
value: {
|
|
129
|
+
speakableApi,
|
|
130
|
+
queryClient,
|
|
131
|
+
user,
|
|
132
|
+
permissions
|
|
133
|
+
},
|
|
134
|
+
children
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function useSpeakableApi() {
|
|
139
|
+
const ctx = (0, import_react.useContext)(FsCtx);
|
|
140
|
+
if (!ctx) throw new Error("useSpeakableApi must be used within a SpeakableProvider");
|
|
141
|
+
return ctx;
|
|
142
|
+
}
|
|
39
143
|
|
|
40
144
|
// src/utils/error-handler.ts
|
|
41
145
|
var ServiceError = class extends Error {
|
|
@@ -345,13 +449,30 @@ var createAssignmentRepo = () => {
|
|
|
345
449
|
};
|
|
346
450
|
};
|
|
347
451
|
|
|
348
|
-
// src/providers/SpeakableProvider.tsx
|
|
349
|
-
var import_react = require("react");
|
|
350
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
351
|
-
var FsCtx = (0, import_react.createContext)(null);
|
|
352
|
-
|
|
353
452
|
// src/domains/assignment/hooks/assignment.hooks.ts
|
|
354
453
|
var import_react_query = require("@tanstack/react-query");
|
|
454
|
+
var assignmentQueryKeys = {
|
|
455
|
+
all: ["assignments"],
|
|
456
|
+
byId: (id) => [...assignmentQueryKeys.all, id],
|
|
457
|
+
list: () => [...assignmentQueryKeys.all, "list"]
|
|
458
|
+
};
|
|
459
|
+
function useAssignment({
|
|
460
|
+
assignmentId,
|
|
461
|
+
enabled = true,
|
|
462
|
+
analyticType,
|
|
463
|
+
userId
|
|
464
|
+
}) {
|
|
465
|
+
const { speakableApi } = useSpeakableApi();
|
|
466
|
+
return (0, import_react_query.useQuery)({
|
|
467
|
+
queryKey: assignmentQueryKeys.byId(assignmentId),
|
|
468
|
+
queryFn: () => speakableApi.assignmentRepo.getAssignment({
|
|
469
|
+
assignmentId,
|
|
470
|
+
analyticType,
|
|
471
|
+
currentUserId: userId
|
|
472
|
+
}),
|
|
473
|
+
enabled
|
|
474
|
+
});
|
|
475
|
+
}
|
|
355
476
|
|
|
356
477
|
// src/domains/assignment/hooks/score-hooks.ts
|
|
357
478
|
var import_react_query2 = require("@tanstack/react-query");
|
|
@@ -374,6 +495,24 @@ function debounce(func, waitFor) {
|
|
|
374
495
|
});
|
|
375
496
|
}
|
|
376
497
|
|
|
498
|
+
// src/lib/tanstack/handle-optimistic-update-query.ts
|
|
499
|
+
var handleOptimisticUpdate = async ({
|
|
500
|
+
queryClient,
|
|
501
|
+
queryKey,
|
|
502
|
+
newData
|
|
503
|
+
}) => {
|
|
504
|
+
await queryClient.cancelQueries({
|
|
505
|
+
queryKey
|
|
506
|
+
});
|
|
507
|
+
const previousData = queryClient.getQueryData(queryKey);
|
|
508
|
+
if (previousData === void 0) {
|
|
509
|
+
queryClient.setQueryData(queryKey, newData);
|
|
510
|
+
} else {
|
|
511
|
+
queryClient.setQueryData(queryKey, { ...previousData, ...newData });
|
|
512
|
+
}
|
|
513
|
+
return { previousData };
|
|
514
|
+
};
|
|
515
|
+
|
|
377
516
|
// src/constants/speakable-plans.ts
|
|
378
517
|
var FEEDBACK_PLANS = {
|
|
379
518
|
FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
|
|
@@ -530,9 +669,64 @@ var SpeakablePlanHierarchy = [
|
|
|
530
669
|
SpeakablePlanTypes.organization
|
|
531
670
|
];
|
|
532
671
|
|
|
672
|
+
// src/hooks/usePermissions.ts
|
|
673
|
+
var usePermissions = () => {
|
|
674
|
+
const { permissions } = useSpeakableApi();
|
|
675
|
+
const has = (permission) => {
|
|
676
|
+
var _a;
|
|
677
|
+
return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
|
|
678
|
+
};
|
|
679
|
+
return {
|
|
680
|
+
plan: permissions.plan,
|
|
681
|
+
permissionsLoaded: permissions.loaded,
|
|
682
|
+
isStripePlan: permissions.isStripePlan,
|
|
683
|
+
refreshDate: permissions.refreshDate,
|
|
684
|
+
isInstitutionPlan: permissions.isInstitutionPlan,
|
|
685
|
+
subscriptionId: permissions.subscriptionId,
|
|
686
|
+
contact: permissions.contact,
|
|
687
|
+
hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
|
|
688
|
+
hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
|
|
689
|
+
hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
|
|
690
|
+
hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
|
|
691
|
+
hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
|
|
692
|
+
permissions: permissions || [],
|
|
693
|
+
hasStudentPortfolios: permissions.hasStudentPortfolios,
|
|
694
|
+
isFreeOrgTrial: permissions.type === "free_org_trial",
|
|
695
|
+
freeOrgTrialExpired: permissions.freeOrgTrialExpired
|
|
696
|
+
};
|
|
697
|
+
};
|
|
698
|
+
var usePermissions_default = usePermissions;
|
|
699
|
+
|
|
700
|
+
// src/domains/notification/notification.constants.ts
|
|
701
|
+
var SPEAKABLE_NOTIFICATIONS = {
|
|
702
|
+
NEW_ASSIGNMENT: "new_assignment",
|
|
703
|
+
ASSESSMENT_SUBMITTED: "assessment_submitted",
|
|
704
|
+
ASSESSMENT_SCORED: "assessment_scored",
|
|
705
|
+
NEW_COMMENT: "NEW_COMMENT"
|
|
706
|
+
};
|
|
707
|
+
var SpeakableNotificationTypes = {
|
|
708
|
+
NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
|
|
709
|
+
FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
|
|
710
|
+
MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
|
|
711
|
+
PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
|
|
712
|
+
STUDENT_PROGRESS: "STUDENT_PROGRESS",
|
|
713
|
+
PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
|
|
714
|
+
PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
|
|
715
|
+
// New notifications
|
|
716
|
+
ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
|
|
717
|
+
// Notification FOR TEACHER when student submits assessment
|
|
718
|
+
ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
|
|
719
|
+
// Notification FOR STUDENT when teacher scores assessment
|
|
720
|
+
// Comment
|
|
721
|
+
NEW_COMMENT: "NEW_COMMENT"
|
|
722
|
+
};
|
|
723
|
+
|
|
533
724
|
// src/domains/notification/services/create-notification.service.ts
|
|
534
725
|
var import_dayjs2 = __toESM(require("dayjs"));
|
|
535
726
|
|
|
727
|
+
// src/constants/web.constants.ts
|
|
728
|
+
var WEB_BASE_URL = "https://app.speakable.io";
|
|
729
|
+
|
|
536
730
|
// src/domains/notification/services/send-notification.service.ts
|
|
537
731
|
var _sendNotification = async (sendTo, notification) => {
|
|
538
732
|
var _a, _b, _c;
|
|
@@ -545,6 +739,202 @@ var _sendNotification = async (sendTo, notification) => {
|
|
|
545
739
|
};
|
|
546
740
|
var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
|
|
547
741
|
|
|
742
|
+
// src/domains/notification/services/create-notification.service.ts
|
|
743
|
+
var createNotification = async ({
|
|
744
|
+
data,
|
|
745
|
+
type,
|
|
746
|
+
userId,
|
|
747
|
+
profile
|
|
748
|
+
}) => {
|
|
749
|
+
let result;
|
|
750
|
+
switch (type) {
|
|
751
|
+
case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
|
|
752
|
+
result = await handleAssignNotifPromise({ data, profile });
|
|
753
|
+
break;
|
|
754
|
+
case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
|
|
755
|
+
result = await createAssessmentSubmissionNotification({
|
|
756
|
+
data,
|
|
757
|
+
profile,
|
|
758
|
+
userId
|
|
759
|
+
});
|
|
760
|
+
break;
|
|
761
|
+
case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
|
|
762
|
+
result = await createAssessmentScoredNotification({
|
|
763
|
+
data,
|
|
764
|
+
profile
|
|
765
|
+
});
|
|
766
|
+
break;
|
|
767
|
+
default:
|
|
768
|
+
result = null;
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
return result;
|
|
772
|
+
};
|
|
773
|
+
var handleAssignNotifPromise = async ({
|
|
774
|
+
data: assignments,
|
|
775
|
+
profile
|
|
776
|
+
}) => {
|
|
777
|
+
if (!assignments.length) return;
|
|
778
|
+
try {
|
|
779
|
+
const notifsPromises = assignments.map(async (assignment) => {
|
|
780
|
+
const {
|
|
781
|
+
section,
|
|
782
|
+
section: { members },
|
|
783
|
+
...rest
|
|
784
|
+
} = assignment;
|
|
785
|
+
if (!section || !members) throw new Error("Invalid assignment data");
|
|
786
|
+
const data = { section, sendTo: members, assignment: { ...rest } };
|
|
787
|
+
return createNewAssignmentNotification({ data, profile });
|
|
788
|
+
});
|
|
789
|
+
await Promise.all(notifsPromises);
|
|
790
|
+
return {
|
|
791
|
+
success: true,
|
|
792
|
+
message: "Assignment notifications sent successfully"
|
|
793
|
+
};
|
|
794
|
+
} catch (error) {
|
|
795
|
+
console.error("Error in handleAssignNotifPromise:", error);
|
|
796
|
+
throw error;
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
var createNewAssignmentNotification = async ({
|
|
800
|
+
data,
|
|
801
|
+
profile
|
|
802
|
+
}) => {
|
|
803
|
+
var _a;
|
|
804
|
+
const { assignment, sendTo } = data;
|
|
805
|
+
const teacherName = profile.displayName || "Your teacher";
|
|
806
|
+
const dueDate = assignment.dueDateTimestamp ? (0, import_dayjs2.default)(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
|
|
807
|
+
const results = await sendNotification(sendTo, {
|
|
808
|
+
courseId: assignment.courseId,
|
|
809
|
+
type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
|
|
810
|
+
senderName: teacherName,
|
|
811
|
+
link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
|
|
812
|
+
messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
|
|
813
|
+
title: "New Assignment Available!",
|
|
814
|
+
imageUrl: (_a = profile.image) == null ? void 0 : _a.url
|
|
815
|
+
});
|
|
816
|
+
return results;
|
|
817
|
+
};
|
|
818
|
+
var createAssessmentSubmissionNotification = async ({
|
|
819
|
+
data: assignment,
|
|
820
|
+
profile,
|
|
821
|
+
userId
|
|
822
|
+
}) => {
|
|
823
|
+
var _a;
|
|
824
|
+
const studentName = profile.displayName || "Your student";
|
|
825
|
+
const results = await sendNotification(assignment.owners, {
|
|
826
|
+
courseId: assignment.courseId,
|
|
827
|
+
type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
|
|
828
|
+
link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
|
|
829
|
+
title: `Assessment Submitted!`,
|
|
830
|
+
senderName: studentName,
|
|
831
|
+
messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
|
|
832
|
+
imageUrl: (_a = profile.image) == null ? void 0 : _a.url
|
|
833
|
+
});
|
|
834
|
+
return results;
|
|
835
|
+
};
|
|
836
|
+
var createAssessmentScoredNotification = async ({
|
|
837
|
+
data,
|
|
838
|
+
profile
|
|
839
|
+
}) => {
|
|
840
|
+
var _a, _b, _c, _d, _e;
|
|
841
|
+
const { assignment, sendTo } = data;
|
|
842
|
+
const teacherName = profile.displayName || "Your teacher";
|
|
843
|
+
const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
|
|
844
|
+
const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
|
|
845
|
+
const results = await sendNotification(sendTo, {
|
|
846
|
+
courseId: assignment.courseId,
|
|
847
|
+
type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
|
|
848
|
+
link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
|
|
849
|
+
title,
|
|
850
|
+
messagePreview,
|
|
851
|
+
imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
|
|
852
|
+
senderName: teacherName
|
|
853
|
+
});
|
|
854
|
+
await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
|
|
855
|
+
assessmentTitle: assignment.name,
|
|
856
|
+
link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
|
|
857
|
+
senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
|
|
858
|
+
studentId: sendTo[0],
|
|
859
|
+
teacherName: profile.displayName
|
|
860
|
+
}));
|
|
861
|
+
return results;
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// src/domains/notification/hooks/notification.hooks.ts
|
|
865
|
+
var notificationQueryKeys = {
|
|
866
|
+
all: ["notifications"],
|
|
867
|
+
byId: (id) => [...notificationQueryKeys.all, id]
|
|
868
|
+
};
|
|
869
|
+
var useCreateNotification = () => {
|
|
870
|
+
const { user, queryClient } = useSpeakableApi();
|
|
871
|
+
const handleCreateNotifications = async (type, data) => {
|
|
872
|
+
var _a, _b;
|
|
873
|
+
const result = await createNotification({
|
|
874
|
+
type,
|
|
875
|
+
userId: user.auth.uid,
|
|
876
|
+
profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
|
|
877
|
+
data
|
|
878
|
+
});
|
|
879
|
+
queryClient.invalidateQueries({
|
|
880
|
+
queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
|
|
881
|
+
});
|
|
882
|
+
return result;
|
|
883
|
+
};
|
|
884
|
+
return {
|
|
885
|
+
createNotification: handleCreateNotifications
|
|
886
|
+
};
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// src/hooks/useGoogleClassroom.ts
|
|
890
|
+
var useGoogleClassroom = () => {
|
|
891
|
+
const submitAssignmentToGoogleClassroom = async ({
|
|
892
|
+
assignment,
|
|
893
|
+
scores,
|
|
894
|
+
googleUserId = null
|
|
895
|
+
// optional to override the user's googleUserId
|
|
896
|
+
}) => {
|
|
897
|
+
var _a, _b, _c;
|
|
898
|
+
try {
|
|
899
|
+
const { googleClassroomUserId = null } = scores;
|
|
900
|
+
const googleId = googleUserId || googleClassroomUserId;
|
|
901
|
+
if (!googleId)
|
|
902
|
+
return {
|
|
903
|
+
error: true,
|
|
904
|
+
message: "No Google Classroom ID found"
|
|
905
|
+
};
|
|
906
|
+
const { courseWorkId, maxPoints, owners, courseId } = assignment;
|
|
907
|
+
const draftGrade = (scores == null ? void 0 : scores.score) ? (scores == null ? void 0 : scores.score) / 100 * maxPoints : 0;
|
|
908
|
+
const result = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentToGoogleClassroomV2")) == null ? void 0 : _c({
|
|
909
|
+
teacherId: owners[0],
|
|
910
|
+
courseId,
|
|
911
|
+
courseWorkId,
|
|
912
|
+
userId: googleId,
|
|
913
|
+
draftGrade
|
|
914
|
+
}));
|
|
915
|
+
return result;
|
|
916
|
+
} catch (error) {
|
|
917
|
+
return { error: true, message: error.message };
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
return {
|
|
921
|
+
submitAssignmentToGoogleClassroom
|
|
922
|
+
};
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// src/lib/firebase/firebase-analytics/grading-standard.ts
|
|
926
|
+
var logGradingStandardLog = (data) => {
|
|
927
|
+
var _a, _b, _c;
|
|
928
|
+
if (data.courseId && data.type && data.level) {
|
|
929
|
+
(_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
|
|
930
|
+
eventType: data.type || "custom",
|
|
931
|
+
level: data.level,
|
|
932
|
+
courseId: data.courseId
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
api.logEvent("logGradingStandard", data);
|
|
936
|
+
};
|
|
937
|
+
|
|
548
938
|
// src/constants/analytics.constants.ts
|
|
549
939
|
var ANALYTICS_EVENT_TYPES = {
|
|
550
940
|
VOICE_SUCCESS: "voice_success",
|
|
@@ -584,6 +974,20 @@ var ANALYTICS_EVENT_TYPES = {
|
|
|
584
974
|
};
|
|
585
975
|
|
|
586
976
|
// src/lib/firebase/firebase-analytics/assignment.ts
|
|
977
|
+
var logOpenAssignment = (data = {}) => {
|
|
978
|
+
api.logEvent("open_assignment", data);
|
|
979
|
+
};
|
|
980
|
+
var logOpenActivityPreview = (data = {}) => {
|
|
981
|
+
api.logEvent("open_activity_preview", data);
|
|
982
|
+
};
|
|
983
|
+
var logSubmitAssignment = (data = {}) => {
|
|
984
|
+
var _a, _b, _c;
|
|
985
|
+
(_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
|
|
986
|
+
eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
|
|
987
|
+
...data
|
|
988
|
+
});
|
|
989
|
+
api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
|
|
990
|
+
};
|
|
587
991
|
var logStartAssignment = (data = {}) => {
|
|
588
992
|
var _a, _b, _c;
|
|
589
993
|
if (data.courseId) {
|
|
@@ -826,6 +1230,72 @@ var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
|
|
|
826
1230
|
|
|
827
1231
|
// src/domains/assignment/services/clear-score.service.ts
|
|
828
1232
|
var import_dayjs3 = __toESM(require("dayjs"));
|
|
1233
|
+
async function clearScore(params) {
|
|
1234
|
+
var _a, _b, _c, _d, _e;
|
|
1235
|
+
const update = {
|
|
1236
|
+
[`cards.${params.cardId}`]: {
|
|
1237
|
+
attempts: ((_a = params.cardScores.attempts) != null ? _a : 1) + 1,
|
|
1238
|
+
correct: (_b = params.cardScores.correct) != null ? _b : 0,
|
|
1239
|
+
// save old score history
|
|
1240
|
+
history: [
|
|
1241
|
+
{
|
|
1242
|
+
...params.cardScores,
|
|
1243
|
+
attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
|
|
1244
|
+
correct: (_d = params.cardScores.correct) != null ? _d : 0,
|
|
1245
|
+
retryTime: (0, import_dayjs3.default)().format("YYYY-MM-DD HH:mm:ss"),
|
|
1246
|
+
history: null
|
|
1247
|
+
},
|
|
1248
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
1249
|
+
...(_e = params.cardScores.history) != null ? _e : []
|
|
1250
|
+
]
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
|
|
1254
|
+
id: params.activityId,
|
|
1255
|
+
userId: params.userId
|
|
1256
|
+
}) : refsScoresPractice.practiceScores({
|
|
1257
|
+
setId: params.activityId,
|
|
1258
|
+
userId: params.userId
|
|
1259
|
+
});
|
|
1260
|
+
await api.updateDoc(path, update);
|
|
1261
|
+
return {
|
|
1262
|
+
update,
|
|
1263
|
+
activityId: params.activityId
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
async function clearScoreV2(params) {
|
|
1267
|
+
var _a, _b, _c, _d, _e;
|
|
1268
|
+
const update = {
|
|
1269
|
+
[`cards.${params.cardId}`]: {
|
|
1270
|
+
...params.cardScores,
|
|
1271
|
+
attempts: ((_a = params.cardScores.attempts) != null ? _a : 1) + 1,
|
|
1272
|
+
correct: (_b = params.cardScores.correct) != null ? _b : 0,
|
|
1273
|
+
history: [
|
|
1274
|
+
{
|
|
1275
|
+
...params.cardScores,
|
|
1276
|
+
attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
|
|
1277
|
+
correct: (_d = params.cardScores.correct) != null ? _d : 0,
|
|
1278
|
+
retryTime: (0, import_dayjs3.default)().format("YYYY-MM-DD HH:mm:ss"),
|
|
1279
|
+
history: null
|
|
1280
|
+
},
|
|
1281
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
1282
|
+
...(_e = params.cardScores.history) != null ? _e : []
|
|
1283
|
+
]
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
|
|
1287
|
+
id: params.activityId,
|
|
1288
|
+
userId: params.userId
|
|
1289
|
+
}) : refsScoresPractice.practiceScores({
|
|
1290
|
+
setId: params.activityId,
|
|
1291
|
+
userId: params.userId
|
|
1292
|
+
});
|
|
1293
|
+
await api.updateDoc(path, update);
|
|
1294
|
+
return {
|
|
1295
|
+
update,
|
|
1296
|
+
activityId: params.activityId
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
829
1299
|
|
|
830
1300
|
// src/domains/assignment/services/submit-assignment-score.service.ts
|
|
831
1301
|
var import_dayjs4 = __toESM(require("dayjs"));
|
|
@@ -891,15 +1361,458 @@ async function handleCourseAssignment(assignment, userId) {
|
|
|
891
1361
|
userId
|
|
892
1362
|
}));
|
|
893
1363
|
}
|
|
1364
|
+
async function submitPracticeScore({
|
|
1365
|
+
setId,
|
|
1366
|
+
userId,
|
|
1367
|
+
scores
|
|
1368
|
+
}) {
|
|
1369
|
+
const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
|
|
1370
|
+
const date = (0, import_dayjs4.default)().format("YYYY-MM-DD-HH-mm");
|
|
1371
|
+
const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
|
|
1372
|
+
const fieldsUpdated = {
|
|
1373
|
+
...scores,
|
|
1374
|
+
submitted: true,
|
|
1375
|
+
progress: 100,
|
|
1376
|
+
submissionDate: serverTimestamp2(),
|
|
1377
|
+
status: "SUBMITTED"
|
|
1378
|
+
};
|
|
1379
|
+
await api.setDoc(ref, { ...fieldsUpdated });
|
|
1380
|
+
const refScores = refsScoresPractice.practiceScores({ userId, setId });
|
|
1381
|
+
await api.deleteDoc(refScores);
|
|
1382
|
+
return { success: true, fieldsUpdated };
|
|
1383
|
+
}
|
|
894
1384
|
|
|
895
1385
|
// src/domains/assignment/hooks/score-hooks.ts
|
|
1386
|
+
var scoreQueryKeys = {
|
|
1387
|
+
all: ["scores"],
|
|
1388
|
+
byId: (id) => [...scoreQueryKeys.all, id],
|
|
1389
|
+
list: () => [...scoreQueryKeys.all, "list"]
|
|
1390
|
+
};
|
|
1391
|
+
function useScore({
|
|
1392
|
+
isAssignment,
|
|
1393
|
+
activityId,
|
|
1394
|
+
userId = "",
|
|
1395
|
+
courseId,
|
|
1396
|
+
enabled = true,
|
|
1397
|
+
googleClassroomUserId
|
|
1398
|
+
}) {
|
|
1399
|
+
return (0, import_react_query2.useQuery)({
|
|
1400
|
+
queryFn: () => getScore({
|
|
1401
|
+
userId,
|
|
1402
|
+
courseId,
|
|
1403
|
+
activityId,
|
|
1404
|
+
googleClassroomUserId,
|
|
1405
|
+
isAssignment
|
|
1406
|
+
}),
|
|
1407
|
+
queryKey: scoreQueryKeys.byId(activityId),
|
|
1408
|
+
enabled
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
896
1411
|
var debounceUpdateScore = debounce(updateScore, 500);
|
|
1412
|
+
function useUpdateScore() {
|
|
1413
|
+
const { queryClient } = useSpeakableApi();
|
|
1414
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1415
|
+
mutationFn: debounceUpdateScore,
|
|
1416
|
+
onMutate: (variables) => {
|
|
1417
|
+
return handleOptimisticUpdate({
|
|
1418
|
+
queryClient,
|
|
1419
|
+
queryKey: scoreQueryKeys.byId(variables.activityId),
|
|
1420
|
+
newData: variables.data
|
|
1421
|
+
});
|
|
1422
|
+
},
|
|
1423
|
+
onError: (_, variables, context) => {
|
|
1424
|
+
if (context == null ? void 0 : context.previousData)
|
|
1425
|
+
queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
|
|
1426
|
+
},
|
|
1427
|
+
onSettled: (_, err, variables) => {
|
|
1428
|
+
queryClient.invalidateQueries({
|
|
1429
|
+
queryKey: scoreQueryKeys.byId(variables.activityId)
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
});
|
|
1433
|
+
return {
|
|
1434
|
+
mutationUpdateScore: mutation
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
function useUpdateCardScore({
|
|
1438
|
+
isAssignment,
|
|
1439
|
+
activityId,
|
|
1440
|
+
userId,
|
|
1441
|
+
cardIds,
|
|
1442
|
+
weights
|
|
1443
|
+
}) {
|
|
1444
|
+
const { queryClient } = useSpeakableApi();
|
|
1445
|
+
const queryKey = scoreQueryKeys.byId(activityId);
|
|
1446
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1447
|
+
mutationFn: async ({ cardId, cardScore }) => {
|
|
1448
|
+
const previousScores = queryClient.getQueryData(queryKey);
|
|
1449
|
+
const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
|
|
1450
|
+
previousScores: previousScores != null ? previousScores : {},
|
|
1451
|
+
cardId,
|
|
1452
|
+
cardScore,
|
|
1453
|
+
cardIds,
|
|
1454
|
+
weights
|
|
1455
|
+
});
|
|
1456
|
+
await updateCardScore({
|
|
1457
|
+
userId,
|
|
1458
|
+
cardId,
|
|
1459
|
+
isAssignment,
|
|
1460
|
+
activityId,
|
|
1461
|
+
updates: {
|
|
1462
|
+
cardScore: updatedCardScore,
|
|
1463
|
+
progress,
|
|
1464
|
+
score
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
return { cardId, scoresUpdated: newScoreUpdated };
|
|
1468
|
+
},
|
|
1469
|
+
onMutate: ({ cardId, cardScore }) => {
|
|
1470
|
+
const previousData = queryClient.getQueryData(queryKey);
|
|
1471
|
+
queryClient.setQueryData(queryKey, (previousScore) => {
|
|
1472
|
+
const updates = handleOptimisticScore({
|
|
1473
|
+
score: previousScore,
|
|
1474
|
+
cardId,
|
|
1475
|
+
cardScore,
|
|
1476
|
+
cardIds,
|
|
1477
|
+
weights
|
|
1478
|
+
});
|
|
1479
|
+
return {
|
|
1480
|
+
...previousScore,
|
|
1481
|
+
...updates
|
|
1482
|
+
};
|
|
1483
|
+
});
|
|
1484
|
+
return { previousData };
|
|
1485
|
+
},
|
|
1486
|
+
onError: (error, variables, context) => {
|
|
1487
|
+
console.log("Error updating card score:", error.message);
|
|
1488
|
+
if (context == null ? void 0 : context.previousData) {
|
|
1489
|
+
queryClient.setQueryData(queryKey, context.previousData);
|
|
1490
|
+
}
|
|
1491
|
+
},
|
|
1492
|
+
onSettled: () => {
|
|
1493
|
+
queryClient.invalidateQueries({
|
|
1494
|
+
queryKey
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
return {
|
|
1499
|
+
mutationUpdateCardScore: mutation
|
|
1500
|
+
};
|
|
1501
|
+
}
|
|
1502
|
+
var getScoreUpdated = ({
|
|
1503
|
+
cardId,
|
|
1504
|
+
cardScore,
|
|
1505
|
+
previousScores,
|
|
1506
|
+
cardIds,
|
|
1507
|
+
weights
|
|
1508
|
+
}) => {
|
|
1509
|
+
var _a, _b;
|
|
1510
|
+
const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
|
|
1511
|
+
const newCardScore = {
|
|
1512
|
+
...previousCard != null ? previousCard : {},
|
|
1513
|
+
...cardScore
|
|
1514
|
+
};
|
|
1515
|
+
const newScores = {
|
|
1516
|
+
...previousScores,
|
|
1517
|
+
cards: {
|
|
1518
|
+
...(_b = previousScores.cards) != null ? _b : {},
|
|
1519
|
+
[cardId]: newCardScore
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
|
|
1523
|
+
return {
|
|
1524
|
+
newScoreUpdated: newScores,
|
|
1525
|
+
updatedCardScore: cardScore,
|
|
1526
|
+
score,
|
|
1527
|
+
progress
|
|
1528
|
+
};
|
|
1529
|
+
};
|
|
1530
|
+
var handleOptimisticScore = ({
|
|
1531
|
+
score,
|
|
1532
|
+
cardId,
|
|
1533
|
+
cardScore,
|
|
1534
|
+
cardIds,
|
|
1535
|
+
weights
|
|
1536
|
+
}) => {
|
|
1537
|
+
var _a;
|
|
1538
|
+
let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
|
|
1539
|
+
cards = {
|
|
1540
|
+
...cards,
|
|
1541
|
+
[cardId]: {
|
|
1542
|
+
...cards[cardId],
|
|
1543
|
+
...cardScore
|
|
1544
|
+
}
|
|
1545
|
+
};
|
|
1546
|
+
const { score: scoreValue, progress } = calculateScoreAndProgress_default(
|
|
1547
|
+
// @ts-ignore
|
|
1548
|
+
{
|
|
1549
|
+
...score != null ? score : {},
|
|
1550
|
+
cards
|
|
1551
|
+
},
|
|
1552
|
+
cardIds,
|
|
1553
|
+
weights
|
|
1554
|
+
);
|
|
1555
|
+
return { cards, score: scoreValue, progress };
|
|
1556
|
+
};
|
|
1557
|
+
function useClearScore() {
|
|
1558
|
+
const { queryClient } = useSpeakableApi();
|
|
1559
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1560
|
+
mutationFn: clearScore,
|
|
1561
|
+
onError: (error) => {
|
|
1562
|
+
console.log("Error clearing score:", error.message);
|
|
1563
|
+
},
|
|
1564
|
+
onSettled: (result) => {
|
|
1565
|
+
var _a;
|
|
1566
|
+
queryClient.invalidateQueries({
|
|
1567
|
+
queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
return {
|
|
1572
|
+
mutationClearScore: mutation
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
function useClearScoreV2() {
|
|
1576
|
+
const { queryClient } = useSpeakableApi();
|
|
1577
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1578
|
+
mutationFn: clearScoreV2,
|
|
1579
|
+
onError: (error) => {
|
|
1580
|
+
console.log("Error clearing score V2:", error.message);
|
|
1581
|
+
},
|
|
1582
|
+
onSettled: (result) => {
|
|
1583
|
+
var _a;
|
|
1584
|
+
queryClient.invalidateQueries({
|
|
1585
|
+
queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
return {
|
|
1590
|
+
mutationClearScore: mutation
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
function useSubmitAssignmentScore({
|
|
1594
|
+
onAssignmentSubmitted,
|
|
1595
|
+
studentName
|
|
1596
|
+
}) {
|
|
1597
|
+
const { queryClient } = useSpeakableApi();
|
|
1598
|
+
const { hasGoogleClassroomGradePassback } = usePermissions_default();
|
|
1599
|
+
const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
|
|
1600
|
+
const { createNotification: createNotification2 } = useCreateNotification();
|
|
1601
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1602
|
+
mutationFn: async ({
|
|
1603
|
+
assignment,
|
|
1604
|
+
userId,
|
|
1605
|
+
cardIds,
|
|
1606
|
+
weights,
|
|
1607
|
+
scores,
|
|
1608
|
+
status
|
|
1609
|
+
}) => {
|
|
1610
|
+
try {
|
|
1611
|
+
const scoreUpdated = await submitAssignmentScore({
|
|
1612
|
+
assignment,
|
|
1613
|
+
userId,
|
|
1614
|
+
cardIds,
|
|
1615
|
+
weights,
|
|
1616
|
+
status,
|
|
1617
|
+
studentName
|
|
1618
|
+
});
|
|
1619
|
+
if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
|
|
1620
|
+
await submitAssignmentToGoogleClassroom({
|
|
1621
|
+
assignment,
|
|
1622
|
+
scores
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
if (assignment.isAssessment) {
|
|
1626
|
+
createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
|
|
1627
|
+
}
|
|
1628
|
+
if (assignment == null ? void 0 : assignment.id) {
|
|
1629
|
+
logSubmitAssignment({
|
|
1630
|
+
courseId: assignment == null ? void 0 : assignment.courseId
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
onAssignmentSubmitted(assignment.id);
|
|
1634
|
+
queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
|
|
1635
|
+
...scores,
|
|
1636
|
+
...scoreUpdated.fieldsUpdated
|
|
1637
|
+
});
|
|
1638
|
+
return {
|
|
1639
|
+
success: true,
|
|
1640
|
+
message: "Score submitted successfully"
|
|
1641
|
+
};
|
|
1642
|
+
} catch (error) {
|
|
1643
|
+
return {
|
|
1644
|
+
success: false,
|
|
1645
|
+
error
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
return {
|
|
1651
|
+
submitAssignmentScore: mutation.mutateAsync,
|
|
1652
|
+
isLoading: mutation.isPending
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function useSubmitPracticeScore() {
|
|
1656
|
+
const { queryClient } = useSpeakableApi();
|
|
1657
|
+
const mutation = (0, import_react_query2.useMutation)({
|
|
1658
|
+
mutationFn: async ({
|
|
1659
|
+
setId,
|
|
1660
|
+
userId,
|
|
1661
|
+
scores
|
|
1662
|
+
}) => {
|
|
1663
|
+
try {
|
|
1664
|
+
await submitPracticeScore({
|
|
1665
|
+
setId,
|
|
1666
|
+
userId,
|
|
1667
|
+
scores
|
|
1668
|
+
});
|
|
1669
|
+
queryClient.invalidateQueries({
|
|
1670
|
+
queryKey: scoreQueryKeys.byId(setId)
|
|
1671
|
+
});
|
|
1672
|
+
return {
|
|
1673
|
+
success: true,
|
|
1674
|
+
message: "Score submitted successfully"
|
|
1675
|
+
};
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
return {
|
|
1678
|
+
success: false,
|
|
1679
|
+
error
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
return {
|
|
1685
|
+
submitPracticeScore: mutation.mutateAsync,
|
|
1686
|
+
isLoading: mutation.isPending
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
897
1689
|
|
|
898
1690
|
// src/domains/cards/card.hooks.ts
|
|
899
1691
|
var import_react_query3 = require("@tanstack/react-query");
|
|
900
1692
|
var import_react2 = require("react");
|
|
901
1693
|
|
|
1694
|
+
// src/domains/cards/card.model.ts
|
|
1695
|
+
var ActivityPageType = /* @__PURE__ */ ((ActivityPageType2) => {
|
|
1696
|
+
ActivityPageType2["READ_REPEAT"] = "READ_REPEAT";
|
|
1697
|
+
ActivityPageType2["READ_RESPOND"] = "READ_RESPOND";
|
|
1698
|
+
ActivityPageType2["FREE_RESPONSE"] = "FREE_RESPONSE";
|
|
1699
|
+
ActivityPageType2["REPEAT"] = "REPEAT";
|
|
1700
|
+
ActivityPageType2["RESPOND"] = "RESPOND";
|
|
1701
|
+
ActivityPageType2["RESPOND_WRITE"] = "RESPOND_WRITE";
|
|
1702
|
+
ActivityPageType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
|
|
1703
|
+
ActivityPageType2["MEDIA_PAGE"] = "MEDIA_PAGE";
|
|
1704
|
+
ActivityPageType2["SHORT_ANSWER"] = "SHORT_ANSWER";
|
|
1705
|
+
return ActivityPageType2;
|
|
1706
|
+
})(ActivityPageType || {});
|
|
1707
|
+
var RESPOND_PAGE_ACTIVITY_TYPES = [
|
|
1708
|
+
"READ_RESPOND" /* READ_RESPOND */,
|
|
1709
|
+
"RESPOND" /* RESPOND */,
|
|
1710
|
+
"RESPOND_WRITE" /* RESPOND_WRITE */,
|
|
1711
|
+
"FREE_RESPONSE" /* FREE_RESPONSE */
|
|
1712
|
+
];
|
|
1713
|
+
var MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
|
|
1714
|
+
var REPEAT_PAGE_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
|
|
1715
|
+
var RESPOND_WRITE_PAGE_ACTIVITY_TYPES = [
|
|
1716
|
+
"RESPOND_WRITE" /* RESPOND_WRITE */,
|
|
1717
|
+
"FREE_RESPONSE" /* FREE_RESPONSE */
|
|
1718
|
+
];
|
|
1719
|
+
var RESPOND_AUDIO_PAGE_ACTIVITY_TYPES = [
|
|
1720
|
+
"RESPOND" /* RESPOND */,
|
|
1721
|
+
"READ_RESPOND" /* READ_RESPOND */
|
|
1722
|
+
];
|
|
1723
|
+
|
|
902
1724
|
// src/domains/cards/card.constants.ts
|
|
1725
|
+
var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
|
|
1726
|
+
FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
|
|
1727
|
+
FeedbackTypesCard2["Wida"] = "wida";
|
|
1728
|
+
FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
|
|
1729
|
+
FeedbackTypesCard2["Actfl"] = "actfl";
|
|
1730
|
+
FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
|
|
1731
|
+
return FeedbackTypesCard2;
|
|
1732
|
+
})(FeedbackTypesCard || {});
|
|
1733
|
+
var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
|
|
1734
|
+
LeniencyCard2["CONFIDENCE"] = "confidence";
|
|
1735
|
+
LeniencyCard2["EASY"] = "easy";
|
|
1736
|
+
LeniencyCard2["NORMAL"] = "normal";
|
|
1737
|
+
LeniencyCard2["HARD"] = "hard";
|
|
1738
|
+
return LeniencyCard2;
|
|
1739
|
+
})(LeniencyCard || {});
|
|
1740
|
+
var LENIENCY_OPTIONS = [
|
|
1741
|
+
{
|
|
1742
|
+
label: "Build Confidence - most lenient",
|
|
1743
|
+
value: "confidence" /* CONFIDENCE */
|
|
1744
|
+
},
|
|
1745
|
+
{
|
|
1746
|
+
label: "Very Lenient",
|
|
1747
|
+
value: "easy" /* EASY */
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
label: "Normal",
|
|
1751
|
+
value: "normal" /* NORMAL */
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
label: "No leniency - most strict",
|
|
1755
|
+
value: "hard" /* HARD */
|
|
1756
|
+
}
|
|
1757
|
+
];
|
|
1758
|
+
var STUDENT_LEVELS_OPTIONS = [
|
|
1759
|
+
{
|
|
1760
|
+
label: "Beginner",
|
|
1761
|
+
description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
|
|
1762
|
+
value: "beginner"
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
label: "Elementary",
|
|
1766
|
+
description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
|
|
1767
|
+
value: "elementary"
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
label: "Intermediate",
|
|
1771
|
+
description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
|
|
1772
|
+
value: "intermediate"
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
label: "Advanced",
|
|
1776
|
+
description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
|
|
1777
|
+
value: "advanced"
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
label: "Fluent",
|
|
1781
|
+
description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
|
|
1782
|
+
value: "fluent"
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
label: "Native-like",
|
|
1786
|
+
description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
|
|
1787
|
+
value: "nativeLike"
|
|
1788
|
+
}
|
|
1789
|
+
];
|
|
1790
|
+
var BASE_RESPOND_FIELD_VALUES = {
|
|
1791
|
+
title: "",
|
|
1792
|
+
allowRetries: true,
|
|
1793
|
+
respondTime: 180,
|
|
1794
|
+
maxCharacters: 1e3
|
|
1795
|
+
};
|
|
1796
|
+
var BASE_REPEAT_FIELD_VALUES = {
|
|
1797
|
+
repeat: 1
|
|
1798
|
+
};
|
|
1799
|
+
var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
|
|
1800
|
+
MCQType: "single",
|
|
1801
|
+
answer: ["A"],
|
|
1802
|
+
choices: [
|
|
1803
|
+
{ option: "A", value: "Option A" },
|
|
1804
|
+
{ option: "B", value: "Option B" },
|
|
1805
|
+
{ option: "C", value: "Option C" }
|
|
1806
|
+
]
|
|
1807
|
+
};
|
|
1808
|
+
var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
|
|
1809
|
+
VerificationCardStatus2["VERIFIED"] = "VERIFIED";
|
|
1810
|
+
VerificationCardStatus2["WARNING"] = "WARNING";
|
|
1811
|
+
VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
|
|
1812
|
+
VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
|
|
1813
|
+
VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
|
|
1814
|
+
return VerificationCardStatus2;
|
|
1815
|
+
})(VerificationCardStatus || {});
|
|
903
1816
|
var CARDS_COLLECTION = "flashcards";
|
|
904
1817
|
var refsCardsFiresotre = {
|
|
905
1818
|
allCards: CARDS_COLLECTION,
|
|
@@ -946,6 +1859,13 @@ var getWordHash = (word, language) => {
|
|
|
946
1859
|
console.log("wordHash core library", wordHash);
|
|
947
1860
|
return wordHash;
|
|
948
1861
|
};
|
|
1862
|
+
function getPhraseLength(phrase, input) {
|
|
1863
|
+
if (Array.isArray(phrase) && phrase.includes(input)) {
|
|
1864
|
+
return phrase[phrase.indexOf(input)].split(" ").length;
|
|
1865
|
+
} else {
|
|
1866
|
+
return phrase ? phrase.split(" ").length : 0;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
949
1869
|
|
|
950
1870
|
// src/domains/cards/services/get-card-verification-status.service.ts
|
|
951
1871
|
var charactarLanguages = ["zh", "ja", "ko"];
|
|
@@ -1016,6 +1936,81 @@ async function _createCards({ cards }) {
|
|
|
1016
1936
|
}
|
|
1017
1937
|
var createCards = withErrorHandler(_createCards, "createCards");
|
|
1018
1938
|
|
|
1939
|
+
// src/domains/cards/card.hooks.ts
|
|
1940
|
+
var cardsQueryKeys = {
|
|
1941
|
+
all: ["cards"],
|
|
1942
|
+
one: (params) => [...cardsQueryKeys.all, params.cardId]
|
|
1943
|
+
};
|
|
1944
|
+
function useCards({
|
|
1945
|
+
cardIds,
|
|
1946
|
+
enabled = true,
|
|
1947
|
+
asObject
|
|
1948
|
+
}) {
|
|
1949
|
+
const queries = (0, import_react_query3.useQueries)({
|
|
1950
|
+
queries: cardIds.map((cardId) => ({
|
|
1951
|
+
enabled: enabled && cardIds.length > 0,
|
|
1952
|
+
queryKey: cardsQueryKeys.one({
|
|
1953
|
+
cardId
|
|
1954
|
+
}),
|
|
1955
|
+
queryFn: () => getCard({ cardId })
|
|
1956
|
+
}))
|
|
1957
|
+
});
|
|
1958
|
+
const cards = queries.map((query2) => query2.data).filter(Boolean);
|
|
1959
|
+
const cardsObject = (0, import_react2.useMemo)(() => {
|
|
1960
|
+
if (!asObject) return null;
|
|
1961
|
+
return cards.reduce((acc, card) => {
|
|
1962
|
+
acc[card.id] = card;
|
|
1963
|
+
return acc;
|
|
1964
|
+
}, {});
|
|
1965
|
+
}, [asObject, cards]);
|
|
1966
|
+
return {
|
|
1967
|
+
cards,
|
|
1968
|
+
cardsObject,
|
|
1969
|
+
cardsQueries: queries
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function useCreateCard() {
|
|
1973
|
+
const { queryClient } = useSpeakableApi();
|
|
1974
|
+
const mutationCreateCard = (0, import_react_query3.useMutation)({
|
|
1975
|
+
mutationFn: createCard,
|
|
1976
|
+
onSuccess: (cardCreated) => {
|
|
1977
|
+
queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
return {
|
|
1981
|
+
mutationCreateCard
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
function useCreateCards() {
|
|
1985
|
+
const mutationCreateCards = (0, import_react_query3.useMutation)({
|
|
1986
|
+
mutationFn: createCards
|
|
1987
|
+
});
|
|
1988
|
+
return {
|
|
1989
|
+
mutationCreateCards
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
function getCardFromCache({
|
|
1993
|
+
cardId,
|
|
1994
|
+
queryClient
|
|
1995
|
+
}) {
|
|
1996
|
+
return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
|
|
1997
|
+
}
|
|
1998
|
+
function updateCardInCache({
|
|
1999
|
+
cardId,
|
|
2000
|
+
card,
|
|
2001
|
+
queryClient
|
|
2002
|
+
}) {
|
|
2003
|
+
queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
|
|
2004
|
+
}
|
|
2005
|
+
function useGetCard({ cardId, enabled = true }) {
|
|
2006
|
+
const query2 = (0, import_react_query3.useQuery)({
|
|
2007
|
+
queryKey: cardsQueryKeys.one({ cardId }),
|
|
2008
|
+
queryFn: () => getCard({ cardId }),
|
|
2009
|
+
enabled: enabled && !!cardId
|
|
2010
|
+
});
|
|
2011
|
+
return query2;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1019
2014
|
// src/domains/cards/card.repo.ts
|
|
1020
2015
|
var createCardRepo = () => {
|
|
1021
2016
|
return {
|
|
@@ -1025,10 +2020,181 @@ var createCardRepo = () => {
|
|
|
1025
2020
|
};
|
|
1026
2021
|
};
|
|
1027
2022
|
|
|
1028
|
-
// src/domains/
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
2023
|
+
// src/domains/cards/utils/check-page-type.ts
|
|
2024
|
+
function checkIsRepeatPage(cardType) {
|
|
2025
|
+
if (cardType === void 0) return false;
|
|
2026
|
+
return REPEAT_PAGE_ACTIVITY_TYPES.includes(cardType);
|
|
2027
|
+
}
|
|
2028
|
+
function checkIsMCPage(cardType) {
|
|
2029
|
+
if (cardType === void 0) return false;
|
|
2030
|
+
return MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES.includes(cardType);
|
|
2031
|
+
}
|
|
2032
|
+
function checkIsRespondPage(cardType) {
|
|
2033
|
+
if (cardType === void 0) return false;
|
|
2034
|
+
return RESPOND_PAGE_ACTIVITY_TYPES.includes(cardType);
|
|
2035
|
+
}
|
|
2036
|
+
function checkIsRespondWrittenPage(cardType) {
|
|
2037
|
+
if (cardType === void 0) return false;
|
|
2038
|
+
return RESPOND_WRITE_PAGE_ACTIVITY_TYPES.includes(cardType);
|
|
2039
|
+
}
|
|
2040
|
+
function checkIsRespondAudioPage(cardType) {
|
|
2041
|
+
if (cardType === void 0) return false;
|
|
2042
|
+
return RESPOND_AUDIO_PAGE_ACTIVITY_TYPES.includes(cardType);
|
|
2043
|
+
}
|
|
2044
|
+
var checkIsMediaPage = (cardType) => {
|
|
2045
|
+
if (cardType === void 0) return false;
|
|
2046
|
+
return cardType === "MEDIA_PAGE" /* MEDIA_PAGE */;
|
|
2047
|
+
};
|
|
2048
|
+
var checkIsShortAnswerPage = (cardType) => {
|
|
2049
|
+
if (cardType === void 0) return false;
|
|
2050
|
+
return cardType === "SHORT_ANSWER" /* SHORT_ANSWER */;
|
|
2051
|
+
};
|
|
2052
|
+
var checkTypePageActivity = (cardType) => {
|
|
2053
|
+
const isRespondAudio = checkIsRespondAudioPage(cardType);
|
|
2054
|
+
const isRespondWritten = checkIsRespondWrittenPage(cardType);
|
|
2055
|
+
const isRespond = checkIsRespondPage(cardType);
|
|
2056
|
+
const isMC = checkIsMCPage(cardType);
|
|
2057
|
+
const isRepeat = checkIsRepeatPage(cardType);
|
|
2058
|
+
const isMediaPage = checkIsMediaPage(cardType);
|
|
2059
|
+
const isShortAnswer = checkIsShortAnswerPage(cardType);
|
|
2060
|
+
const isNoOneOfThem = !isRespond && !isMC && !isRepeat && !isMediaPage && !isShortAnswer;
|
|
2061
|
+
if (isNoOneOfThem) {
|
|
2062
|
+
return {
|
|
2063
|
+
isRespondAudio: false,
|
|
2064
|
+
isRespondWritten: false,
|
|
2065
|
+
isRespond: false,
|
|
2066
|
+
isMC: false,
|
|
2067
|
+
isRepeat: true,
|
|
2068
|
+
isMediaPage: false,
|
|
2069
|
+
isShortAnswer: false,
|
|
2070
|
+
hasSomeType: false
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
return {
|
|
2074
|
+
isRespondAudio,
|
|
2075
|
+
isRespondWritten,
|
|
2076
|
+
isRespond,
|
|
2077
|
+
isMC,
|
|
2078
|
+
isRepeat,
|
|
2079
|
+
isMediaPage,
|
|
2080
|
+
isShortAnswer,
|
|
2081
|
+
hasSomeType: true
|
|
2082
|
+
};
|
|
2083
|
+
};
|
|
2084
|
+
|
|
2085
|
+
// src/domains/cards/utils/get-page-prompt.ts
|
|
2086
|
+
function extractTextFromRichText(richText) {
|
|
2087
|
+
if (!richText) return "";
|
|
2088
|
+
return richText.replace(/<[^>]*>/g, "").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").trim();
|
|
2089
|
+
}
|
|
2090
|
+
function getPagePrompt(card) {
|
|
2091
|
+
if (!card) return { has: false, text: "", rich_text: "", isTextEqualToRichText: false };
|
|
2092
|
+
const { isMC, isRepeat, isRespond, isShortAnswer } = checkTypePageActivity(card == null ? void 0 : card.type);
|
|
2093
|
+
const hidePrompt = (card == null ? void 0 : card.hidePrompt) === true;
|
|
2094
|
+
const createReturnObject = (text, richText) => {
|
|
2095
|
+
const plainText = text || "";
|
|
2096
|
+
const richTextPlain = extractTextFromRichText(richText);
|
|
2097
|
+
return {
|
|
2098
|
+
has: true,
|
|
2099
|
+
text: plainText,
|
|
2100
|
+
rich_text: richText || "",
|
|
2101
|
+
isTextEqualToRichText: plainText.trim() === richTextPlain.trim()
|
|
2102
|
+
};
|
|
2103
|
+
};
|
|
2104
|
+
if (isRepeat) {
|
|
2105
|
+
return createReturnObject(card == null ? void 0 : card.target_text, card == null ? void 0 : card.rich_text);
|
|
2106
|
+
}
|
|
2107
|
+
if (isRespond && !hidePrompt) {
|
|
2108
|
+
return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
|
|
2109
|
+
}
|
|
2110
|
+
if (isMC) {
|
|
2111
|
+
return createReturnObject(card == null ? void 0 : card.question, card == null ? void 0 : card.rich_text);
|
|
2112
|
+
}
|
|
2113
|
+
if (isShortAnswer && !hidePrompt) {
|
|
2114
|
+
return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
has: false,
|
|
2118
|
+
text: "",
|
|
2119
|
+
rich_text: "",
|
|
2120
|
+
isTextEqualToRichText: false
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/domains/cards/utils/get-completed-pages.ts
|
|
2125
|
+
var getTotalCompletedCards = (pageScores) => {
|
|
2126
|
+
return Object.values(pageScores != null ? pageScores : {}).reduce((acc, cardScore) => {
|
|
2127
|
+
var _a, _b;
|
|
2128
|
+
if (((_b = (_a = cardScore.completed) != null ? _a : cardScore.score) != null ? _b : cardScore.score === 0) && !cardScore.media_area_opened) {
|
|
2129
|
+
acc++;
|
|
2130
|
+
}
|
|
2131
|
+
return acc;
|
|
2132
|
+
}, 0);
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2135
|
+
// src/domains/cards/utils/get-label-page.ts
|
|
2136
|
+
var labels = {
|
|
2137
|
+
repeat: {
|
|
2138
|
+
short: "Repeat",
|
|
2139
|
+
long: "Listen & Repeat"
|
|
2140
|
+
},
|
|
2141
|
+
mc: {
|
|
2142
|
+
short: "Multiple Choice",
|
|
2143
|
+
long: "Multiple Choice"
|
|
2144
|
+
},
|
|
2145
|
+
mediaPage: {
|
|
2146
|
+
short: "Media Page",
|
|
2147
|
+
long: "Media Page"
|
|
2148
|
+
},
|
|
2149
|
+
shortAnswer: {
|
|
2150
|
+
short: "Short Answer",
|
|
2151
|
+
long: "Short Answer"
|
|
2152
|
+
},
|
|
2153
|
+
respondWritten: {
|
|
2154
|
+
short: "Open Response",
|
|
2155
|
+
long: "Written Open Response"
|
|
2156
|
+
},
|
|
2157
|
+
respondAudio: {
|
|
2158
|
+
short: "Open Response",
|
|
2159
|
+
long: "Spoken Open Response"
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
var getLabelPage = (pageType) => {
|
|
2163
|
+
if (!pageType) {
|
|
2164
|
+
return {
|
|
2165
|
+
short: "",
|
|
2166
|
+
long: ""
|
|
2167
|
+
};
|
|
2168
|
+
}
|
|
2169
|
+
const { isRepeat, isMC, isMediaPage, isShortAnswer, isRespondWritten, isRespondAudio } = checkTypePageActivity(pageType);
|
|
2170
|
+
if (isRepeat) {
|
|
2171
|
+
return labels.repeat;
|
|
2172
|
+
}
|
|
2173
|
+
if (isMC) {
|
|
2174
|
+
return labels.mc;
|
|
2175
|
+
}
|
|
2176
|
+
if (isMediaPage) {
|
|
2177
|
+
return labels.mediaPage;
|
|
2178
|
+
}
|
|
2179
|
+
if (isShortAnswer) {
|
|
2180
|
+
return labels.shortAnswer;
|
|
2181
|
+
}
|
|
2182
|
+
if (isRespondWritten) {
|
|
2183
|
+
return labels.respondWritten;
|
|
2184
|
+
}
|
|
2185
|
+
if (isRespondAudio) {
|
|
2186
|
+
return labels.respondAudio;
|
|
2187
|
+
}
|
|
2188
|
+
return {
|
|
2189
|
+
short: "",
|
|
2190
|
+
long: ""
|
|
2191
|
+
};
|
|
2192
|
+
};
|
|
2193
|
+
|
|
2194
|
+
// src/domains/sets/set.hooks.ts
|
|
2195
|
+
var import_react_query4 = require("@tanstack/react-query");
|
|
2196
|
+
|
|
2197
|
+
// src/domains/sets/set.constants.ts
|
|
1032
2198
|
var SETS_COLLECTION = "sets";
|
|
1033
2199
|
var refsSetsFirestore = {
|
|
1034
2200
|
allSets: SETS_COLLECTION,
|
|
@@ -1042,6 +2208,1243 @@ async function _getSet({ setId }) {
|
|
|
1042
2208
|
}
|
|
1043
2209
|
var getSet = withErrorHandler(_getSet, "getSet");
|
|
1044
2210
|
|
|
2211
|
+
// src/domains/sets/set.hooks.ts
|
|
2212
|
+
var setsQueryKeys = {
|
|
2213
|
+
all: ["sets"],
|
|
2214
|
+
one: (params) => [...setsQueryKeys.all, params.setId]
|
|
2215
|
+
};
|
|
2216
|
+
var useSet = ({ setId, enabled }) => {
|
|
2217
|
+
return (0, import_react_query4.useQuery)({
|
|
2218
|
+
queryKey: setsQueryKeys.one({ setId }),
|
|
2219
|
+
queryFn: () => getSet({ setId }),
|
|
2220
|
+
enabled: setId !== void 0 && setId !== "" && enabled
|
|
2221
|
+
});
|
|
2222
|
+
};
|
|
2223
|
+
function getSetFromCache({
|
|
2224
|
+
setId,
|
|
2225
|
+
queryClient
|
|
2226
|
+
}) {
|
|
2227
|
+
if (!setId) return null;
|
|
2228
|
+
return queryClient.getQueryData(setsQueryKeys.one({ setId }));
|
|
2229
|
+
}
|
|
2230
|
+
function updateSetInCache({
|
|
2231
|
+
set,
|
|
2232
|
+
queryClient
|
|
2233
|
+
}) {
|
|
2234
|
+
const { id, ...setData } = set;
|
|
2235
|
+
queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/domains/sets/set.repo.ts
|
|
2239
|
+
var createSetRepo = () => {
|
|
2240
|
+
return {
|
|
2241
|
+
getSet
|
|
2242
|
+
};
|
|
2243
|
+
};
|
|
2244
|
+
|
|
2245
|
+
// src/utils/ai/get-transcript.ts
|
|
2246
|
+
async function getTranscript(model, args) {
|
|
2247
|
+
var _a, _b, _c, _d;
|
|
2248
|
+
const getGeminiTranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "getGeminiTranscript");
|
|
2249
|
+
const getAssemblyAITranscript = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "transcribeAssemblyAIAudio");
|
|
2250
|
+
if (model === "gemini") {
|
|
2251
|
+
try {
|
|
2252
|
+
const { data } = await (getGeminiTranscript == null ? void 0 : getGeminiTranscript({
|
|
2253
|
+
audioUrl: args.audioUrl,
|
|
2254
|
+
targetLanguage: args.language,
|
|
2255
|
+
prompt: args.prompt
|
|
2256
|
+
}));
|
|
2257
|
+
return data.transcript;
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
console.error("Error getting transcript from Gemini:", error);
|
|
2260
|
+
throw error;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if (model === "assemblyai") {
|
|
2264
|
+
try {
|
|
2265
|
+
const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
|
|
2266
|
+
audioUrl: args.audioUrl,
|
|
2267
|
+
language: args.language
|
|
2268
|
+
}));
|
|
2269
|
+
return response.data;
|
|
2270
|
+
} catch (error) {
|
|
2271
|
+
console.error("Error getting transcript from AssemblyAI:", error);
|
|
2272
|
+
throw error;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// src/constants/all-langs.json
|
|
2279
|
+
var all_langs_default = {
|
|
2280
|
+
af: "Afrikaans",
|
|
2281
|
+
sq: "Albanian",
|
|
2282
|
+
am: "Amharic",
|
|
2283
|
+
ar: "Arabic",
|
|
2284
|
+
hy: "Armenian",
|
|
2285
|
+
az: "Azerbaijani",
|
|
2286
|
+
eu: "Basque",
|
|
2287
|
+
be: "Belarusian",
|
|
2288
|
+
bn: "Bengali",
|
|
2289
|
+
bs: "Bosnian",
|
|
2290
|
+
bg: "Bulgarian",
|
|
2291
|
+
ca: "Catalan",
|
|
2292
|
+
ceb: "Cebuano",
|
|
2293
|
+
zh: "Chinese",
|
|
2294
|
+
co: "Corsican",
|
|
2295
|
+
hr: "Croatian",
|
|
2296
|
+
cs: "Czech",
|
|
2297
|
+
da: "Danish",
|
|
2298
|
+
nl: "Dutch",
|
|
2299
|
+
en: "English",
|
|
2300
|
+
eo: "Esperanto",
|
|
2301
|
+
et: "Estonian",
|
|
2302
|
+
fi: "Finnish",
|
|
2303
|
+
fr: "French",
|
|
2304
|
+
fy: "Frisian",
|
|
2305
|
+
gl: "Galician",
|
|
2306
|
+
ka: "Georgian",
|
|
2307
|
+
de: "German",
|
|
2308
|
+
el: "Greek",
|
|
2309
|
+
gu: "Gujarati",
|
|
2310
|
+
ht: "Haitian Creole",
|
|
2311
|
+
ha: "Hausa",
|
|
2312
|
+
haw: "Hawaiian",
|
|
2313
|
+
he: "Hebrew",
|
|
2314
|
+
hi: "Hindi",
|
|
2315
|
+
hmn: "Hmong",
|
|
2316
|
+
hu: "Hungarian",
|
|
2317
|
+
is: "Icelandic",
|
|
2318
|
+
ig: "Igbo",
|
|
2319
|
+
id: "Indonesian",
|
|
2320
|
+
ga: "Irish",
|
|
2321
|
+
it: "Italian",
|
|
2322
|
+
ja: "Japanese",
|
|
2323
|
+
jv: "Javanese",
|
|
2324
|
+
kn: "Kannada",
|
|
2325
|
+
kk: "Kazakh",
|
|
2326
|
+
km: "Khmer",
|
|
2327
|
+
ko: "Korean",
|
|
2328
|
+
ku: "Kurdish",
|
|
2329
|
+
ky: "Kyrgyz",
|
|
2330
|
+
lo: "Lao",
|
|
2331
|
+
la: "Latin",
|
|
2332
|
+
lv: "Latvian",
|
|
2333
|
+
lt: "Lithuanian",
|
|
2334
|
+
lb: "Luxembourgish",
|
|
2335
|
+
mk: "Macedonian",
|
|
2336
|
+
mg: "Malagasy",
|
|
2337
|
+
ms: "Malay",
|
|
2338
|
+
ml: "Malayalam",
|
|
2339
|
+
mt: "Maltese",
|
|
2340
|
+
mi: "Maori",
|
|
2341
|
+
mr: "Marathi",
|
|
2342
|
+
mn: "Mongolian",
|
|
2343
|
+
my: "Myanmar (Burmese)",
|
|
2344
|
+
ne: "Nepali",
|
|
2345
|
+
no: "Norwegian",
|
|
2346
|
+
ny: "Nyanja (Chichewa)",
|
|
2347
|
+
ps: "Pashto",
|
|
2348
|
+
fa: "Persian",
|
|
2349
|
+
pl: "Polish",
|
|
2350
|
+
pt: "Portuguese",
|
|
2351
|
+
pa: "Punjabi",
|
|
2352
|
+
ro: "Romanian",
|
|
2353
|
+
ru: "Russian",
|
|
2354
|
+
sm: "Samoan",
|
|
2355
|
+
gd: "Scots Gaelic",
|
|
2356
|
+
sr: "Serbian",
|
|
2357
|
+
st: "Sesotho",
|
|
2358
|
+
sn: "Shona",
|
|
2359
|
+
sd: "Sindhi",
|
|
2360
|
+
si: "Sinhala (Sinhalese)",
|
|
2361
|
+
sk: "Slovak",
|
|
2362
|
+
sl: "Slovenian",
|
|
2363
|
+
so: "Somali",
|
|
2364
|
+
es: "Spanish",
|
|
2365
|
+
su: "Sundanese",
|
|
2366
|
+
sw: "Swahili",
|
|
2367
|
+
sv: "Swedish",
|
|
2368
|
+
tl: "Tagalog (Filipino)",
|
|
2369
|
+
tg: "Tajik",
|
|
2370
|
+
ta: "Tamil",
|
|
2371
|
+
te: "Telugu",
|
|
2372
|
+
th: "Thai",
|
|
2373
|
+
tr: "Turkish",
|
|
2374
|
+
uk: "Ukrainian",
|
|
2375
|
+
ur: "Urdu",
|
|
2376
|
+
uz: "Uzbek",
|
|
2377
|
+
vi: "Vietnamese",
|
|
2378
|
+
cy: "Welsh",
|
|
2379
|
+
xh: "Xhosa",
|
|
2380
|
+
yi: "Yiddish",
|
|
2381
|
+
yo: "Yoruba",
|
|
2382
|
+
zu: "Zulu"
|
|
2383
|
+
};
|
|
2384
|
+
|
|
2385
|
+
// src/utils/ai/get-respond-card-tool.ts
|
|
2386
|
+
var getRespondCardTool = ({
|
|
2387
|
+
language,
|
|
2388
|
+
standard = "actfl"
|
|
2389
|
+
}) => {
|
|
2390
|
+
const lang = all_langs_default[language] || "English";
|
|
2391
|
+
const tool = {
|
|
2392
|
+
tool_choice: {
|
|
2393
|
+
type: "function",
|
|
2394
|
+
function: { name: "get_feedback" }
|
|
2395
|
+
},
|
|
2396
|
+
tools: [
|
|
2397
|
+
{
|
|
2398
|
+
type: "function",
|
|
2399
|
+
function: {
|
|
2400
|
+
name: "get_feedback",
|
|
2401
|
+
description: "Get feedback on a student's response",
|
|
2402
|
+
parameters: {
|
|
2403
|
+
type: "object",
|
|
2404
|
+
required: [
|
|
2405
|
+
"success",
|
|
2406
|
+
"score",
|
|
2407
|
+
"score_justification",
|
|
2408
|
+
"errors",
|
|
2409
|
+
"improvedResponse",
|
|
2410
|
+
"compliments"
|
|
2411
|
+
],
|
|
2412
|
+
properties: {
|
|
2413
|
+
success: {
|
|
2414
|
+
type: "boolean",
|
|
2415
|
+
description: "Mark true if the student's response was on-topic and generally demonstrated understanding. A few grammar mistakes are acceptable. Mark false if the student's response was off-topic or did not demonstrate understanding."
|
|
2416
|
+
},
|
|
2417
|
+
errors: {
|
|
2418
|
+
type: "array",
|
|
2419
|
+
items: {
|
|
2420
|
+
type: "object",
|
|
2421
|
+
required: ["error", "grammar_error_type", "correction", "justification"],
|
|
2422
|
+
properties: {
|
|
2423
|
+
error: {
|
|
2424
|
+
type: "string",
|
|
2425
|
+
description: "The grammatical error in the student's response."
|
|
2426
|
+
},
|
|
2427
|
+
correction: {
|
|
2428
|
+
type: "string",
|
|
2429
|
+
description: "The suggested correction to the error"
|
|
2430
|
+
},
|
|
2431
|
+
justification: {
|
|
2432
|
+
type: "string",
|
|
2433
|
+
description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
|
|
2434
|
+
},
|
|
2435
|
+
grammar_error_type: {
|
|
2436
|
+
type: "string",
|
|
2437
|
+
enum: [
|
|
2438
|
+
"subjVerbAgree",
|
|
2439
|
+
"tenseErrors",
|
|
2440
|
+
"articleMisuse",
|
|
2441
|
+
"prepositionErrors",
|
|
2442
|
+
"adjNounAgree",
|
|
2443
|
+
"pronounErrors",
|
|
2444
|
+
"wordOrder",
|
|
2445
|
+
"verbConjugation",
|
|
2446
|
+
"pluralization",
|
|
2447
|
+
"negationErrors",
|
|
2448
|
+
"modalVerbMisuse",
|
|
2449
|
+
"relativeClause",
|
|
2450
|
+
"auxiliaryVerb",
|
|
2451
|
+
"complexSentenceAgreement",
|
|
2452
|
+
"idiomaticExpression",
|
|
2453
|
+
"registerInconsistency",
|
|
2454
|
+
"voiceMisuse"
|
|
2455
|
+
],
|
|
2456
|
+
description: "The type of grammatical error found. It should be one of the following categories: subject-verb agreement, tense errors, article misuse, preposition errors, adjective-noun agreement, pronoun errors, word order, verb conjugation, pluralization errors, negation errors, modal verb misuse, relative clause errors, auxiliary verb misuse, complex sentence agreement, idiomatic expression, register inconsistency, or voice misuse"
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
},
|
|
2460
|
+
description: "An array of objects, each representing a grammatical error in the student's response. Each object should have the following properties: error, grammar_error_type, correction, and justification. If there were no errors, return an empty array."
|
|
2461
|
+
},
|
|
2462
|
+
compliments: {
|
|
2463
|
+
type: "array",
|
|
2464
|
+
items: {
|
|
2465
|
+
type: "string"
|
|
2466
|
+
},
|
|
2467
|
+
description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
|
|
2468
|
+
},
|
|
2469
|
+
improvedResponse: {
|
|
2470
|
+
type: "string",
|
|
2471
|
+
description: "An improved response with proper grammar and more detail, if applicable."
|
|
2472
|
+
},
|
|
2473
|
+
score: {
|
|
2474
|
+
type: "number",
|
|
2475
|
+
description: "A score between 0 and 100, reflecting the overall quality of the response"
|
|
2476
|
+
},
|
|
2477
|
+
score_justification: {
|
|
2478
|
+
type: "string",
|
|
2479
|
+
description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
]
|
|
2486
|
+
};
|
|
2487
|
+
if (standard === "wida") {
|
|
2488
|
+
const wida_level = {
|
|
2489
|
+
type: "number",
|
|
2490
|
+
enum: [1, 2, 3, 4, 5, 6],
|
|
2491
|
+
description: `The student's WIDA (World-Class Instructional Design and Assessment) proficiency level. Choose one of the following options: 1, 2, 3, 4, 5, 6 which corresponds to
|
|
2492
|
+
|
|
2493
|
+
1 - Entering
|
|
2494
|
+
2 - Emerging
|
|
2495
|
+
3 - Developing
|
|
2496
|
+
4 - Expanding
|
|
2497
|
+
5 - Bridging
|
|
2498
|
+
6 - Reaching
|
|
2499
|
+
|
|
2500
|
+
This is an estimate based on the level of the student's response. Use the descriptions of the WIDA speaking standards to guide your decision.
|
|
2501
|
+
`
|
|
2502
|
+
};
|
|
2503
|
+
const wida_justification = {
|
|
2504
|
+
type: "string",
|
|
2505
|
+
description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
|
|
2506
|
+
};
|
|
2507
|
+
tool.tools[0].function.parameters.required.push("wida_level");
|
|
2508
|
+
tool.tools[0].function.parameters.required.push("wida_justification");
|
|
2509
|
+
tool.tools[0].function.parameters.properties.wida_level = wida_level;
|
|
2510
|
+
tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
|
|
2511
|
+
} else {
|
|
2512
|
+
const actfl_level = {
|
|
2513
|
+
type: "string",
|
|
2514
|
+
enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
|
|
2515
|
+
description: "The student's ACTFL (American Council on the Teaching of Foreign Languages) proficiency level. Choose one of the following options: NL, NM, NH, IL, IM, IH, AL, AM, AH, S, or D"
|
|
2516
|
+
};
|
|
2517
|
+
const actfl_justification = {
|
|
2518
|
+
type: "string",
|
|
2519
|
+
description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
|
|
2520
|
+
};
|
|
2521
|
+
tool.tools[0].function.parameters.required.push("actfl_level");
|
|
2522
|
+
tool.tools[0].function.parameters.required.push("actfl_justification");
|
|
2523
|
+
tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
|
|
2524
|
+
tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
|
|
2525
|
+
}
|
|
2526
|
+
return tool;
|
|
2527
|
+
};
|
|
2528
|
+
|
|
2529
|
+
// src/hooks/useActivity.ts
|
|
2530
|
+
var import_react3 = require("react");
|
|
2531
|
+
|
|
2532
|
+
// src/services/add-grading-standard.ts
|
|
2533
|
+
var addGradingStandardLog = async (gradingStandard, userId) => {
|
|
2534
|
+
logGradingStandardLog(gradingStandard);
|
|
2535
|
+
const path = `users/${userId}/grading_standard_logs`;
|
|
2536
|
+
await api.addDoc(path, gradingStandard);
|
|
2537
|
+
};
|
|
2538
|
+
|
|
2539
|
+
// src/hooks/useActivityTracker.ts
|
|
2540
|
+
var import_uuid2 = require("uuid");
|
|
2541
|
+
function useActivityTracker({ userId }) {
|
|
2542
|
+
const trackActivity = async ({
|
|
2543
|
+
activityName,
|
|
2544
|
+
activityType,
|
|
2545
|
+
id = (0, import_uuid2.v4)(),
|
|
2546
|
+
language = ""
|
|
2547
|
+
}) => {
|
|
2548
|
+
if (userId) {
|
|
2549
|
+
const { doc: doc2, serverTimestamp: serverTimestamp2, setDoc: setDoc2 } = api.accessHelpers();
|
|
2550
|
+
const activityRef = doc2(`users/${userId}/activity/${id}`);
|
|
2551
|
+
const timestamp = serverTimestamp2();
|
|
2552
|
+
await setDoc2(activityRef, {
|
|
2553
|
+
name: activityName,
|
|
2554
|
+
type: activityType,
|
|
2555
|
+
lastSeen: timestamp,
|
|
2556
|
+
id,
|
|
2557
|
+
language
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
return {
|
|
2562
|
+
trackActivity
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// src/hooks/useActivity.ts
|
|
2567
|
+
function useActivity({
|
|
2568
|
+
id,
|
|
2569
|
+
isAssignment,
|
|
2570
|
+
onAssignmentSubmitted,
|
|
2571
|
+
ltiData
|
|
2572
|
+
}) {
|
|
2573
|
+
var _a, _b;
|
|
2574
|
+
const { queryClient, user } = useSpeakableApi();
|
|
2575
|
+
const userId = user.auth.uid;
|
|
2576
|
+
const assignmentQuery = useAssignment({
|
|
2577
|
+
assignmentId: id,
|
|
2578
|
+
userId,
|
|
2579
|
+
enabled: isAssignment
|
|
2580
|
+
});
|
|
2581
|
+
const activeAssignment = assignmentQuery.data;
|
|
2582
|
+
const setId = isAssignment ? (_a = activeAssignment == null ? void 0 : activeAssignment.setId) != null ? _a : "" : id;
|
|
2583
|
+
const querySet = useSet({ setId });
|
|
2584
|
+
const setData = querySet.data;
|
|
2585
|
+
const assignmentContent = activeAssignment == null ? void 0 : activeAssignment.content;
|
|
2586
|
+
const assignmentWeights = activeAssignment == null ? void 0 : activeAssignment.weights;
|
|
2587
|
+
const setContent = setData == null ? void 0 : setData.content;
|
|
2588
|
+
const setWeights = setData == null ? void 0 : setData.weights;
|
|
2589
|
+
const contentCardsToUse = isAssignment ? assignmentContent != null ? assignmentContent : setContent : setContent;
|
|
2590
|
+
const weightsToUse = isAssignment ? assignmentWeights != null ? assignmentWeights : setWeights : setWeights;
|
|
2591
|
+
const activityId = isAssignment ? (_b = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b : "" : setId;
|
|
2592
|
+
const { cardsObject, cardsQueries, cards } = useCards({
|
|
2593
|
+
cardIds: contentCardsToUse != null ? contentCardsToUse : [],
|
|
2594
|
+
enabled: querySet.isSuccess,
|
|
2595
|
+
asObject: true
|
|
2596
|
+
});
|
|
2597
|
+
const scorableCardIds = (contentCardsToUse != null ? contentCardsToUse : []).filter((cardId) => {
|
|
2598
|
+
const card = cardsObject == null ? void 0 : cardsObject[cardId];
|
|
2599
|
+
return (card == null ? void 0 : card.type) !== "MEDIA_PAGE" /* MEDIA_PAGE */;
|
|
2600
|
+
});
|
|
2601
|
+
const scoreQuery = useScore({
|
|
2602
|
+
isAssignment,
|
|
2603
|
+
activityId: id,
|
|
2604
|
+
userId,
|
|
2605
|
+
courseId: activeAssignment == null ? void 0 : activeAssignment.courseId,
|
|
2606
|
+
googleClassroomUserId: user.profile.googleClassroomUserId,
|
|
2607
|
+
enabled: isAssignment ? assignmentQuery.isSuccess : querySet.isSuccess
|
|
2608
|
+
});
|
|
2609
|
+
const { mutationUpdateScore } = useUpdateScore();
|
|
2610
|
+
const { mutationUpdateCardScore } = useUpdateCardScore({
|
|
2611
|
+
activityId,
|
|
2612
|
+
isAssignment,
|
|
2613
|
+
userId,
|
|
2614
|
+
cardIds: scorableCardIds,
|
|
2615
|
+
weights: weightsToUse != null ? weightsToUse : {}
|
|
2616
|
+
});
|
|
2617
|
+
const { mutationClearScore } = useClearScore();
|
|
2618
|
+
const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
|
|
2619
|
+
onAssignmentSubmitted,
|
|
2620
|
+
studentName: user.profile.displayName
|
|
2621
|
+
});
|
|
2622
|
+
const { submitPracticeScore: submitPracticeScore2 } = useSubmitPracticeScore();
|
|
2623
|
+
const handleUpdateScore = (data) => {
|
|
2624
|
+
mutationUpdateScore.mutate({
|
|
2625
|
+
data,
|
|
2626
|
+
isAssignment,
|
|
2627
|
+
activityId,
|
|
2628
|
+
userId
|
|
2629
|
+
});
|
|
2630
|
+
};
|
|
2631
|
+
const handleUpdateCardScore = (cardId, cardScore) => {
|
|
2632
|
+
mutationUpdateCardScore.mutate({ cardId, cardScore });
|
|
2633
|
+
if (cardScore.proficiency_level) {
|
|
2634
|
+
logGradingStandardEntry({
|
|
2635
|
+
type: cardScore.proficiency_level.standardId,
|
|
2636
|
+
cardId,
|
|
2637
|
+
gradingStandard: cardScore.proficiency_level
|
|
2638
|
+
});
|
|
2639
|
+
} else if (cardScore.wida || cardScore.actfl) {
|
|
2640
|
+
logGradingStandardEntry({
|
|
2641
|
+
type: cardScore.wida ? "wida" : "actfl",
|
|
2642
|
+
cardId,
|
|
2643
|
+
gradingStandard: cardScore.wida || cardScore.actfl || { level: "", justification: "" }
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
const onClearScore = ({
|
|
2648
|
+
cardId,
|
|
2649
|
+
wasCompleted = true
|
|
2650
|
+
}) => {
|
|
2651
|
+
var _a2, _b2;
|
|
2652
|
+
const currentCard = cardsObject == null ? void 0 : cardsObject[cardId];
|
|
2653
|
+
if ((currentCard == null ? void 0 : currentCard.type) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || (currentCard == null ? void 0 : currentCard.type) === "READ_REPEAT" /* READ_REPEAT */) {
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
const queryKeys = scoreQueryKeys.byId(activityId);
|
|
2657
|
+
const activeCardScores = (_b2 = (_a2 = queryClient.getQueryData(queryKeys)) == null ? void 0 : _a2.cards) == null ? void 0 : _b2[cardId];
|
|
2658
|
+
if (activeCardScores === void 0) return;
|
|
2659
|
+
mutationClearScore.mutate({
|
|
2660
|
+
isAssignment,
|
|
2661
|
+
activityId,
|
|
2662
|
+
cardScores: activeCardScores,
|
|
2663
|
+
cardId,
|
|
2664
|
+
userId
|
|
2665
|
+
});
|
|
2666
|
+
};
|
|
2667
|
+
const onSubmitScore = async () => {
|
|
2668
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
|
|
2669
|
+
try {
|
|
2670
|
+
let results;
|
|
2671
|
+
if (isAssignment) {
|
|
2672
|
+
const someCardIsManualGraded = cards.some((page) => page.grading_method === "manual");
|
|
2673
|
+
results = await submitAssignmentScore2({
|
|
2674
|
+
assignment: {
|
|
2675
|
+
id: (_b2 = (_a2 = assignmentQuery.data) == null ? void 0 : _a2.id) != null ? _b2 : "",
|
|
2676
|
+
name: (_d = (_c = assignmentQuery.data) == null ? void 0 : _c.name) != null ? _d : "",
|
|
2677
|
+
owners: (_f = (_e = assignmentQuery.data) == null ? void 0 : _e.owners) != null ? _f : [],
|
|
2678
|
+
courseId: (_h = (_g = assignmentQuery.data) == null ? void 0 : _g.courseId) != null ? _h : "",
|
|
2679
|
+
courseWorkId: (_j = (_i = assignmentQuery.data) == null ? void 0 : _i.courseWorkId) != null ? _j : "",
|
|
2680
|
+
isAssessment: (_l = (_k = assignmentQuery.data) == null ? void 0 : _k.isAssessment) != null ? _l : false,
|
|
2681
|
+
maxPoints: (_n = (_m = assignmentQuery.data) == null ? void 0 : _m.maxPoints) != null ? _n : 0
|
|
2682
|
+
},
|
|
2683
|
+
userId,
|
|
2684
|
+
cardIds: scorableCardIds,
|
|
2685
|
+
scores: scoreQuery.data,
|
|
2686
|
+
weights: weightsToUse != null ? weightsToUse : {},
|
|
2687
|
+
status: someCardIsManualGraded ? "PENDING_REVIEW" : "SUBMITTED"
|
|
2688
|
+
});
|
|
2689
|
+
if ((_o = assignmentQuery.data) == null ? void 0 : _o.ltiDeeplink) {
|
|
2690
|
+
submitLTIScore({
|
|
2691
|
+
maxPoints: (_p = assignmentQuery.data) == null ? void 0 : _p.maxPoints,
|
|
2692
|
+
score: (_r = (_q = scoreQuery.data) == null ? void 0 : _q.score) != null ? _r : 0,
|
|
2693
|
+
SERVICE_KEY: (_s = ltiData == null ? void 0 : ltiData.serviceKey) != null ? _s : "",
|
|
2694
|
+
lineItemId: (_t = ltiData == null ? void 0 : ltiData.lineItemId) != null ? _t : "",
|
|
2695
|
+
lti_id: (_u = ltiData == null ? void 0 : ltiData.lti_id) != null ? _u : ""
|
|
2696
|
+
});
|
|
2697
|
+
}
|
|
2698
|
+
} else {
|
|
2699
|
+
results = await submitPracticeScore2({
|
|
2700
|
+
setId: (_w = (_v = querySet.data) == null ? void 0 : _v.id) != null ? _w : "",
|
|
2701
|
+
userId,
|
|
2702
|
+
scores: scoreQuery.data
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
return results;
|
|
2706
|
+
} catch (error) {
|
|
2707
|
+
return {
|
|
2708
|
+
success: false,
|
|
2709
|
+
error
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
const logGradingStandardEntry = ({
|
|
2714
|
+
cardId,
|
|
2715
|
+
gradingStandard,
|
|
2716
|
+
type
|
|
2717
|
+
}) => {
|
|
2718
|
+
var _a2, _b2, _c, _d, _e, _f, _g, _h, _i;
|
|
2719
|
+
const card = cardsObject == null ? void 0 : cardsObject[cardId];
|
|
2720
|
+
const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
|
|
2721
|
+
const cardScore = (_a2 = scoresObject == null ? void 0 : scoresObject.cards) == null ? void 0 : _a2[cardId];
|
|
2722
|
+
const serverTimestamp2 = api.helpers.serverTimestamp;
|
|
2723
|
+
addGradingStandardLog(
|
|
2724
|
+
{
|
|
2725
|
+
assignmentId: (_b2 = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b2 : "",
|
|
2726
|
+
courseId: (_c = activeAssignment == null ? void 0 : activeAssignment.courseId) != null ? _c : "",
|
|
2727
|
+
teacherId: (_d = activeAssignment == null ? void 0 : activeAssignment.owners[0]) != null ? _d : "",
|
|
2728
|
+
setId: (_e = setData == null ? void 0 : setData.id) != null ? _e : "",
|
|
2729
|
+
cardId,
|
|
2730
|
+
level: gradingStandard.level,
|
|
2731
|
+
justification: gradingStandard.justification,
|
|
2732
|
+
transcript: (_f = cardScore == null ? void 0 : cardScore.transcript) != null ? _f : "",
|
|
2733
|
+
audioUrl: (_g = cardScore == null ? void 0 : cardScore.audio) != null ? _g : "",
|
|
2734
|
+
prompt: (_h = card == null ? void 0 : card.prompt) != null ? _h : "",
|
|
2735
|
+
responseType: (card == null ? void 0 : card.type) === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
|
|
2736
|
+
type,
|
|
2737
|
+
dateMade: serverTimestamp2(),
|
|
2738
|
+
language: (_i = card == null ? void 0 : card.language) != null ? _i : ""
|
|
2739
|
+
},
|
|
2740
|
+
userId
|
|
2741
|
+
);
|
|
2742
|
+
};
|
|
2743
|
+
(0, import_react3.useEffect)(() => {
|
|
2744
|
+
if (isAssignment) {
|
|
2745
|
+
logOpenAssignment({ assignmentId: id });
|
|
2746
|
+
} else {
|
|
2747
|
+
logOpenActivityPreview({ setId: id });
|
|
2748
|
+
}
|
|
2749
|
+
}, []);
|
|
2750
|
+
useInitActivity({
|
|
2751
|
+
assignment: activeAssignment != null ? activeAssignment : void 0,
|
|
2752
|
+
set: setData != null ? setData : void 0,
|
|
2753
|
+
enabled: !!setData,
|
|
2754
|
+
userId
|
|
2755
|
+
});
|
|
2756
|
+
return {
|
|
2757
|
+
set: {
|
|
2758
|
+
data: setData,
|
|
2759
|
+
query: querySet
|
|
2760
|
+
},
|
|
2761
|
+
cards: {
|
|
2762
|
+
data: cardsObject,
|
|
2763
|
+
query: cardsQueries,
|
|
2764
|
+
cardsArray: cards
|
|
2765
|
+
},
|
|
2766
|
+
assignment: {
|
|
2767
|
+
data: isAssignment ? activeAssignment : void 0,
|
|
2768
|
+
query: assignmentQuery
|
|
2769
|
+
},
|
|
2770
|
+
scores: {
|
|
2771
|
+
data: scoreQuery.data,
|
|
2772
|
+
query: scoreQuery,
|
|
2773
|
+
actions: {
|
|
2774
|
+
update: handleUpdateScore,
|
|
2775
|
+
clear: onClearScore,
|
|
2776
|
+
submit: onSubmitScore,
|
|
2777
|
+
updateCard: handleUpdateCardScore,
|
|
2778
|
+
logGradingStandardEntry
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
};
|
|
2782
|
+
}
|
|
2783
|
+
var useInitActivity = ({
|
|
2784
|
+
assignment,
|
|
2785
|
+
set,
|
|
2786
|
+
enabled,
|
|
2787
|
+
userId
|
|
2788
|
+
}) => {
|
|
2789
|
+
const { trackActivity } = useActivityTracker({ userId });
|
|
2790
|
+
const init = () => {
|
|
2791
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2792
|
+
if (!enabled) return;
|
|
2793
|
+
if (!assignment) {
|
|
2794
|
+
trackActivity({
|
|
2795
|
+
activityName: (_a = set == null ? void 0 : set.name) != null ? _a : "",
|
|
2796
|
+
activityType: "set",
|
|
2797
|
+
id: set == null ? void 0 : set.id,
|
|
2798
|
+
language: set == null ? void 0 : set.language
|
|
2799
|
+
});
|
|
2800
|
+
} else if (assignment.name) {
|
|
2801
|
+
trackActivity({
|
|
2802
|
+
activityName: assignment.name,
|
|
2803
|
+
activityType: assignment.isAssessment ? "assessment" : "assignment",
|
|
2804
|
+
id: assignment.id,
|
|
2805
|
+
language: set == null ? void 0 : set.language
|
|
2806
|
+
});
|
|
2807
|
+
}
|
|
2808
|
+
if (set == null ? void 0 : set.public) {
|
|
2809
|
+
(_d = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "onSetOpened")) == null ? void 0 : _d({
|
|
2810
|
+
setId: set.id,
|
|
2811
|
+
language: set.language
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
(_g = (_f = (_e = api).httpsCallable) == null ? void 0 : _f.call(_e, "updateAlgoliaIndex")) == null ? void 0 : _g({
|
|
2815
|
+
updatePlays: true,
|
|
2816
|
+
objectID: set == null ? void 0 : set.id
|
|
2817
|
+
});
|
|
2818
|
+
};
|
|
2819
|
+
(0, import_react3.useEffect)(() => {
|
|
2820
|
+
init();
|
|
2821
|
+
}, [set]);
|
|
2822
|
+
};
|
|
2823
|
+
var submitLTIScore = async ({
|
|
2824
|
+
maxPoints,
|
|
2825
|
+
score,
|
|
2826
|
+
SERVICE_KEY,
|
|
2827
|
+
lineItemId,
|
|
2828
|
+
lti_id
|
|
2829
|
+
}) => {
|
|
2830
|
+
var _a, _b, _c;
|
|
2831
|
+
try {
|
|
2832
|
+
if (!SERVICE_KEY || !lineItemId || !lti_id) {
|
|
2833
|
+
throw new Error("Missing required LTI credentials");
|
|
2834
|
+
}
|
|
2835
|
+
const earnedPoints = score ? score / 100 * maxPoints : 0;
|
|
2836
|
+
const { data } = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitLTIAssignmentScore")) == null ? void 0 : _c({
|
|
2837
|
+
SERVICE_KEY,
|
|
2838
|
+
scoreData: {
|
|
2839
|
+
lineItemId,
|
|
2840
|
+
userId: lti_id,
|
|
2841
|
+
maxPoints,
|
|
2842
|
+
earnedPoints
|
|
2843
|
+
}
|
|
2844
|
+
}));
|
|
2845
|
+
return { success: true, data };
|
|
2846
|
+
} catch (error) {
|
|
2847
|
+
console.error("Failed to submit LTI score:", error);
|
|
2848
|
+
return {
|
|
2849
|
+
success: false,
|
|
2850
|
+
error: error instanceof Error ? error : new Error("Unknown error occurred")
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
|
|
2855
|
+
// src/hooks/useCredits.ts
|
|
2856
|
+
var import_react_query5 = require("@tanstack/react-query");
|
|
2857
|
+
var creditQueryKeys = {
|
|
2858
|
+
userCredits: (uid) => ["userCredits", uid]
|
|
2859
|
+
};
|
|
2860
|
+
var useUserCredits = () => {
|
|
2861
|
+
const { user } = useSpeakableApi();
|
|
2862
|
+
const email = user.auth.email;
|
|
2863
|
+
const uid = user.auth.uid;
|
|
2864
|
+
const query2 = (0, import_react_query5.useQuery)({
|
|
2865
|
+
queryKey: creditQueryKeys.userCredits(uid),
|
|
2866
|
+
queryFn: () => fetchUserCredits({ uid, email }),
|
|
2867
|
+
enabled: !!uid,
|
|
2868
|
+
refetchInterval: 1e3 * 60 * 5
|
|
2869
|
+
});
|
|
2870
|
+
return {
|
|
2871
|
+
...query2
|
|
2872
|
+
};
|
|
2873
|
+
};
|
|
2874
|
+
var fetchUserCredits = async ({ uid, email }) => {
|
|
2875
|
+
if (!uid) {
|
|
2876
|
+
throw new Error("User ID is required");
|
|
2877
|
+
}
|
|
2878
|
+
const contractSnap = await api.getDoc(`creditContracts/${uid}`);
|
|
2879
|
+
if (contractSnap.data == null) {
|
|
2880
|
+
return {
|
|
2881
|
+
id: uid,
|
|
2882
|
+
userId: uid,
|
|
2883
|
+
email,
|
|
2884
|
+
effectivePlanId: "free_tier",
|
|
2885
|
+
status: "inactive",
|
|
2886
|
+
isUnlimited: false,
|
|
2887
|
+
creditsAvailable: 100,
|
|
2888
|
+
creditsAllocatedThisPeriod: 100,
|
|
2889
|
+
topOffCreditsAvailable: 0,
|
|
2890
|
+
topOffCreditsTotal: 0,
|
|
2891
|
+
allocationSource: "free_tier",
|
|
2892
|
+
sourceDetails: {},
|
|
2893
|
+
periodStart: null,
|
|
2894
|
+
periodEnd: null,
|
|
2895
|
+
planTermEndTimestamp: null,
|
|
2896
|
+
ownerType: "individual",
|
|
2897
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2898
|
+
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
const contractData = contractSnap.data;
|
|
2902
|
+
const monthlyCredits = (contractData == null ? void 0 : contractData.creditsAvailable) || 0;
|
|
2903
|
+
const topOffCredits = (contractData == null ? void 0 : contractData.topOffCreditsAvailable) || 0;
|
|
2904
|
+
const totalCredits = monthlyCredits + topOffCredits;
|
|
2905
|
+
return {
|
|
2906
|
+
id: contractSnap.id,
|
|
2907
|
+
...contractData,
|
|
2908
|
+
// Add computed total for convenience
|
|
2909
|
+
totalCreditsAvailable: totalCredits
|
|
2910
|
+
};
|
|
2911
|
+
};
|
|
2912
|
+
|
|
2913
|
+
// src/hooks/useOrganizationAccess.ts
|
|
2914
|
+
var import_react_query6 = require("@tanstack/react-query");
|
|
2915
|
+
var useOrganizationAccess = () => {
|
|
2916
|
+
const { user } = useSpeakableApi();
|
|
2917
|
+
const email = user.auth.email;
|
|
2918
|
+
const query2 = (0, import_react_query6.useQuery)({
|
|
2919
|
+
queryKey: ["organizationAccess", email],
|
|
2920
|
+
queryFn: async () => {
|
|
2921
|
+
if (!email) {
|
|
2922
|
+
return {
|
|
2923
|
+
hasUnlimitedAccess: false,
|
|
2924
|
+
subscriptionId: null,
|
|
2925
|
+
organizationId: null,
|
|
2926
|
+
organizationName: null,
|
|
2927
|
+
subscriptionEndDate: null,
|
|
2928
|
+
accessType: "individual"
|
|
2929
|
+
};
|
|
2930
|
+
}
|
|
2931
|
+
return getOrganizationAccess(email);
|
|
2932
|
+
},
|
|
2933
|
+
enabled: !!email,
|
|
2934
|
+
// Only run query if we have a user email
|
|
2935
|
+
staleTime: 5 * 60 * 1e3,
|
|
2936
|
+
// Consider data fresh for 5 minutes
|
|
2937
|
+
gcTime: 10 * 60 * 1e3,
|
|
2938
|
+
// Keep in cache for 10 minutes
|
|
2939
|
+
retry: 2
|
|
2940
|
+
// Retry failed requests twice
|
|
2941
|
+
});
|
|
2942
|
+
return {
|
|
2943
|
+
...query2
|
|
2944
|
+
};
|
|
2945
|
+
};
|
|
2946
|
+
var getOrganizationAccess = async (email) => {
|
|
2947
|
+
const { limit: limit2, where: where2 } = api.accessQueryConstraints();
|
|
2948
|
+
try {
|
|
2949
|
+
const organizationSnapshot = await api.getDocs(
|
|
2950
|
+
"organizations",
|
|
2951
|
+
where2("members", "array-contains", email),
|
|
2952
|
+
where2("masterSubscriptionStatus", "==", "active"),
|
|
2953
|
+
limit2(1)
|
|
2954
|
+
);
|
|
2955
|
+
if (!organizationSnapshot.empty) {
|
|
2956
|
+
const orgData = organizationSnapshot.data[0];
|
|
2957
|
+
return {
|
|
2958
|
+
hasUnlimitedAccess: true,
|
|
2959
|
+
subscriptionId: orgData == null ? void 0 : orgData.masterSubscriptionId,
|
|
2960
|
+
organizationId: orgData.id,
|
|
2961
|
+
organizationName: orgData.name || "Unknown Organization",
|
|
2962
|
+
subscriptionEndDate: orgData.masterSubscriptionEndDate || null,
|
|
2963
|
+
accessType: "organization"
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
const institutionSnapshot = await api.getDocs(
|
|
2967
|
+
"institution_subscriptions",
|
|
2968
|
+
where2("users", "array-contains", email),
|
|
2969
|
+
where2("active", "==", true),
|
|
2970
|
+
limit2(1)
|
|
2971
|
+
);
|
|
2972
|
+
if (!institutionSnapshot.empty) {
|
|
2973
|
+
const institutionData = institutionSnapshot.data[0];
|
|
2974
|
+
const isUnlimited = (institutionData == null ? void 0 : institutionData.plan) === "organization" || (institutionData == null ? void 0 : institutionData.plan) === "school_starter";
|
|
2975
|
+
return {
|
|
2976
|
+
hasUnlimitedAccess: isUnlimited,
|
|
2977
|
+
subscriptionId: institutionData.id,
|
|
2978
|
+
organizationId: institutionData == null ? void 0 : institutionData.institutionId,
|
|
2979
|
+
organizationName: institutionData.name || institutionData.institutionId || "Legacy Institution",
|
|
2980
|
+
subscriptionEndDate: institutionData.endDate || null,
|
|
2981
|
+
accessType: "institution_subscriptions"
|
|
2982
|
+
};
|
|
2983
|
+
}
|
|
2984
|
+
return {
|
|
2985
|
+
hasUnlimitedAccess: false,
|
|
2986
|
+
subscriptionId: null,
|
|
2987
|
+
organizationId: null,
|
|
2988
|
+
organizationName: null,
|
|
2989
|
+
subscriptionEndDate: null,
|
|
2990
|
+
accessType: "individual"
|
|
2991
|
+
};
|
|
2992
|
+
} catch (error) {
|
|
2993
|
+
console.error("Error checking organization access:", error);
|
|
2994
|
+
return {
|
|
2995
|
+
hasUnlimitedAccess: false,
|
|
2996
|
+
subscriptionId: null,
|
|
2997
|
+
organizationId: null,
|
|
2998
|
+
organizationName: null,
|
|
2999
|
+
subscriptionEndDate: null,
|
|
3000
|
+
accessType: "individual"
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
|
|
3005
|
+
// src/hooks/useSpeakableTranscript.ts
|
|
3006
|
+
var import_react_query7 = require("@tanstack/react-query");
|
|
3007
|
+
function useSpeakableTranscript() {
|
|
3008
|
+
const mutation = (0, import_react_query7.useMutation)({
|
|
3009
|
+
mutationFn: async ({
|
|
3010
|
+
model,
|
|
3011
|
+
audioUrl,
|
|
3012
|
+
language,
|
|
3013
|
+
prompt
|
|
3014
|
+
}) => {
|
|
3015
|
+
return getTranscript(model, { audioUrl, language, prompt });
|
|
3016
|
+
},
|
|
3017
|
+
retry: false
|
|
3018
|
+
});
|
|
3019
|
+
return {
|
|
3020
|
+
mutation
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
// src/hooks/useUpdateStudentVoc.ts
|
|
3025
|
+
var useUpdateStudentVocab = (page) => {
|
|
3026
|
+
const { user } = useSpeakableApi();
|
|
3027
|
+
const currentUserId = user == null ? void 0 : user.auth.uid;
|
|
3028
|
+
if (!page || !currentUserId || !page.target_text || !page.language) {
|
|
3029
|
+
return {
|
|
3030
|
+
studentVocabMarkVoiceSuccess: void 0,
|
|
3031
|
+
studentVocabMarkVoiceFail: void 0
|
|
3032
|
+
};
|
|
3033
|
+
}
|
|
3034
|
+
const getDataObject = () => {
|
|
3035
|
+
var _a, _b;
|
|
3036
|
+
const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
|
|
3037
|
+
const language = (_a = page.language) != null ? _a : "en";
|
|
3038
|
+
const word = (_b = page.target_text) != null ? _b : "";
|
|
3039
|
+
const phrase_length = getPhraseLength(word);
|
|
3040
|
+
const wordHash = getWordHash(word, language);
|
|
3041
|
+
const docPath = `users/${currentUserId}/vocab/${wordHash}`;
|
|
3042
|
+
const communityPath = `checked-pronunciations/${wordHash}`;
|
|
3043
|
+
const id = `${language}-${cleanString(word)}`;
|
|
3044
|
+
const data = {
|
|
3045
|
+
id,
|
|
3046
|
+
word,
|
|
3047
|
+
words: (word == null ? void 0 : word.split(" ")) || [],
|
|
3048
|
+
wordHash,
|
|
3049
|
+
language,
|
|
3050
|
+
lastSeen: serverTimestamp2(),
|
|
3051
|
+
phrase_length
|
|
3052
|
+
};
|
|
3053
|
+
return {
|
|
3054
|
+
docPath,
|
|
3055
|
+
communityPath,
|
|
3056
|
+
data
|
|
3057
|
+
};
|
|
3058
|
+
};
|
|
3059
|
+
const markVoiceSuccess = async () => {
|
|
3060
|
+
const { docPath, communityPath, data } = getDataObject();
|
|
3061
|
+
const { increment: increment2 } = api.accessQueryConstraints();
|
|
3062
|
+
const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
|
|
3063
|
+
data.voiceSuccess = increment2(1);
|
|
3064
|
+
try {
|
|
3065
|
+
await api.updateDoc(docPath, data);
|
|
3066
|
+
} catch (error) {
|
|
3067
|
+
if (error instanceof Error && error.message === "not-found") {
|
|
3068
|
+
data.firstSeen = serverTimestamp2();
|
|
3069
|
+
await api.setDoc(docPath, data, { merge: true });
|
|
3070
|
+
} else {
|
|
3071
|
+
console.log(error);
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
try {
|
|
3075
|
+
data.pronunciations = increment2(1);
|
|
3076
|
+
await api.setDoc(communityPath, data, { merge: true });
|
|
3077
|
+
} catch (error) {
|
|
3078
|
+
console.log(error);
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
const markVoiceFail = async () => {
|
|
3082
|
+
const { docPath, communityPath, data } = getDataObject();
|
|
3083
|
+
const { increment: increment2 } = api.accessQueryConstraints();
|
|
3084
|
+
const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
|
|
3085
|
+
data.voiceFail = increment2(1);
|
|
3086
|
+
try {
|
|
3087
|
+
await api.updateDoc(docPath, data);
|
|
3088
|
+
} catch (error) {
|
|
3089
|
+
if (error instanceof Error && error.message === "not-found") {
|
|
3090
|
+
data.firstSeen = serverTimestamp2();
|
|
3091
|
+
await api.setDoc(docPath, data, { merge: true });
|
|
3092
|
+
} else {
|
|
3093
|
+
console.log(error);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
try {
|
|
3097
|
+
data.fails = increment2(1);
|
|
3098
|
+
await api.setDoc(communityPath, data, { merge: true });
|
|
3099
|
+
} catch (error) {
|
|
3100
|
+
console.log(error);
|
|
3101
|
+
}
|
|
3102
|
+
};
|
|
3103
|
+
return {
|
|
3104
|
+
studentVocabMarkVoiceSuccess: markVoiceSuccess,
|
|
3105
|
+
studentVocabMarkVoiceFail: markVoiceFail
|
|
3106
|
+
};
|
|
3107
|
+
};
|
|
3108
|
+
|
|
3109
|
+
// src/hooks/useActivityFeedbackAccess.ts
|
|
3110
|
+
var import_react_query8 = require("@tanstack/react-query");
|
|
3111
|
+
var activityFeedbackAccessQueryKeys = {
|
|
3112
|
+
activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
|
|
3113
|
+
};
|
|
3114
|
+
var useActivityFeedbackAccess = ({
|
|
3115
|
+
aiEnabled = false,
|
|
3116
|
+
isActivityRoute = false
|
|
3117
|
+
}) => {
|
|
3118
|
+
var _a, _b, _c;
|
|
3119
|
+
const { user } = useSpeakableApi();
|
|
3120
|
+
const uid = user.auth.uid;
|
|
3121
|
+
const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
|
|
3122
|
+
const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
|
|
3123
|
+
const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
|
|
3124
|
+
const query2 = (0, import_react_query8.useQuery)({
|
|
3125
|
+
queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
|
|
3126
|
+
aiEnabled,
|
|
3127
|
+
isActivityRoute
|
|
3128
|
+
}),
|
|
3129
|
+
queryFn: async () => {
|
|
3130
|
+
var _a2, _b2, _c2;
|
|
3131
|
+
if (!uid) {
|
|
3132
|
+
return {
|
|
3133
|
+
canAccessFeedback: false,
|
|
3134
|
+
reason: "Missing user ID",
|
|
3135
|
+
isUnlimited: false,
|
|
3136
|
+
accessType: "none"
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
try {
|
|
3140
|
+
if (aiEnabled) {
|
|
3141
|
+
return {
|
|
3142
|
+
canAccessFeedback: true,
|
|
3143
|
+
reason: "AI feedback enabled",
|
|
3144
|
+
isUnlimited: true,
|
|
3145
|
+
accessType: "ai_enabled"
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
if (isTeacher || userRoles.includes("ADMIN")) {
|
|
3149
|
+
return {
|
|
3150
|
+
canAccessFeedback: true,
|
|
3151
|
+
reason: "Teacher preview access",
|
|
3152
|
+
isUnlimited: true,
|
|
3153
|
+
accessType: "teacher_preview"
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
if (isStudent && isActivityRoute) {
|
|
3157
|
+
try {
|
|
3158
|
+
const result = await ((_c2 = (_b2 = (_a2 = api).httpsCallable) == null ? void 0 : _b2.call(_a2, "checkStudentTeacherPlan")) == null ? void 0 : _c2({
|
|
3159
|
+
studentId: uid
|
|
3160
|
+
}));
|
|
3161
|
+
const planCheckResult = result.data;
|
|
3162
|
+
if (planCheckResult.canAccessFeedback) {
|
|
3163
|
+
return {
|
|
3164
|
+
canAccessFeedback: true,
|
|
3165
|
+
reason: planCheckResult.reason || "Student access via teacher with active plan",
|
|
3166
|
+
isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
|
|
3167
|
+
accessType: "student_with_teacher_plan"
|
|
3168
|
+
};
|
|
3169
|
+
} else {
|
|
3170
|
+
return {
|
|
3171
|
+
canAccessFeedback: false,
|
|
3172
|
+
reason: planCheckResult.reason || "No teacher with active plan found",
|
|
3173
|
+
isUnlimited: false,
|
|
3174
|
+
accessType: "none"
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
} catch (error) {
|
|
3178
|
+
console.error("Error checking student teacher plan:", error);
|
|
3179
|
+
return {
|
|
3180
|
+
canAccessFeedback: false,
|
|
3181
|
+
reason: "Error checking teacher plans",
|
|
3182
|
+
isUnlimited: false,
|
|
3183
|
+
accessType: "none"
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
return {
|
|
3188
|
+
canAccessFeedback: false,
|
|
3189
|
+
reason: "No access permissions found for current context",
|
|
3190
|
+
isUnlimited: false,
|
|
3191
|
+
accessType: "none"
|
|
3192
|
+
};
|
|
3193
|
+
} catch (error) {
|
|
3194
|
+
console.error("Error checking activity feedback access:", error);
|
|
3195
|
+
return {
|
|
3196
|
+
canAccessFeedback: false,
|
|
3197
|
+
reason: "Error checking access permissions",
|
|
3198
|
+
isUnlimited: false,
|
|
3199
|
+
accessType: "none"
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
3202
|
+
},
|
|
3203
|
+
enabled: !!uid,
|
|
3204
|
+
staleTime: 5 * 60 * 1e3,
|
|
3205
|
+
// 5 minutes
|
|
3206
|
+
gcTime: 10 * 60 * 1e3
|
|
3207
|
+
// 10 minutes
|
|
3208
|
+
});
|
|
3209
|
+
return {
|
|
3210
|
+
...query2
|
|
3211
|
+
};
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
// src/hooks/useOpenAI.ts
|
|
3215
|
+
var useBaseOpenAI = ({
|
|
3216
|
+
onTranscriptSuccess,
|
|
3217
|
+
onTranscriptError,
|
|
3218
|
+
onCompletionSuccess,
|
|
3219
|
+
onCompletionError,
|
|
3220
|
+
aiEnabled,
|
|
3221
|
+
submitAudioResponse,
|
|
3222
|
+
uploadAudioAndGetTranscript,
|
|
3223
|
+
onGetAudioUrlAndTranscript
|
|
3224
|
+
}) => {
|
|
3225
|
+
const { user, queryClient } = useSpeakableApi();
|
|
3226
|
+
const currentUserId = user.auth.uid;
|
|
3227
|
+
const { data: feedbackAccess } = useActivityFeedbackAccess({
|
|
3228
|
+
aiEnabled
|
|
3229
|
+
});
|
|
3230
|
+
const getTranscript2 = async (audioUrl, language, prompt) => {
|
|
3231
|
+
var _a, _b, _c, _d;
|
|
3232
|
+
const getGeminiTranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "getGeminiTranscript");
|
|
3233
|
+
const getAssemblyAITranscript = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "transcribeAssemblyAIAudio");
|
|
3234
|
+
try {
|
|
3235
|
+
const { data } = await (getGeminiTranscript == null ? void 0 : getGeminiTranscript({
|
|
3236
|
+
audioUrl,
|
|
3237
|
+
targetLanguage: language,
|
|
3238
|
+
prompt: prompt || ""
|
|
3239
|
+
}));
|
|
3240
|
+
const transcript = data.transcript;
|
|
3241
|
+
if (transcript) {
|
|
3242
|
+
return transcript;
|
|
3243
|
+
}
|
|
3244
|
+
} catch (error) {
|
|
3245
|
+
console.log("Gemini transcript failed, trying AssemblyAI fallback:", error);
|
|
3246
|
+
}
|
|
3247
|
+
try {
|
|
3248
|
+
const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
|
|
3249
|
+
audioUrl,
|
|
3250
|
+
language
|
|
3251
|
+
}));
|
|
3252
|
+
const transcript = response == null ? void 0 : response.data;
|
|
3253
|
+
if (transcript) {
|
|
3254
|
+
return transcript;
|
|
3255
|
+
}
|
|
3256
|
+
throw new Error("Both transcript services failed");
|
|
3257
|
+
} catch (error) {
|
|
3258
|
+
console.log("AssemblyAI transcript also failed:", error);
|
|
3259
|
+
onTranscriptError({
|
|
3260
|
+
type: "TRANSCRIPT",
|
|
3261
|
+
message: (error == null ? void 0 : error.message) || "Error getting transcript from both services"
|
|
3262
|
+
});
|
|
3263
|
+
throw new Error(error);
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
|
|
3267
|
+
var _a, _b, _c, _d, _e;
|
|
3268
|
+
const responseTool = getRespondCardTool({
|
|
3269
|
+
language: feedbackLanguage,
|
|
3270
|
+
standard: gradingStandard
|
|
3271
|
+
});
|
|
3272
|
+
try {
|
|
3273
|
+
const createChatCompletion = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createChatCompletion");
|
|
3274
|
+
const {
|
|
3275
|
+
data: {
|
|
3276
|
+
response,
|
|
3277
|
+
prompt_tokens = 0,
|
|
3278
|
+
completion_tokens = 0,
|
|
3279
|
+
success: aiSuccess = false
|
|
3280
|
+
// the AI was able to generate a response
|
|
3281
|
+
}
|
|
3282
|
+
} = await (createChatCompletion == null ? void 0 : createChatCompletion({
|
|
3283
|
+
chat: {
|
|
3284
|
+
model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
|
|
3285
|
+
messages,
|
|
3286
|
+
temperature: 0.7,
|
|
3287
|
+
...responseTool
|
|
3288
|
+
},
|
|
3289
|
+
type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
|
|
3290
|
+
}));
|
|
3291
|
+
const functionArguments = JSON.parse(((_e = (_d = (_c = response == null ? void 0 : response.tool_calls) == null ? void 0 : _c[0]) == null ? void 0 : _d.function) == null ? void 0 : _e.arguments) || "{}");
|
|
3292
|
+
const result = {
|
|
3293
|
+
...functionArguments,
|
|
3294
|
+
prompt_tokens,
|
|
3295
|
+
completion_tokens,
|
|
3296
|
+
aiSuccess
|
|
3297
|
+
};
|
|
3298
|
+
onCompletionSuccess(result);
|
|
3299
|
+
return result;
|
|
3300
|
+
} catch (error) {
|
|
3301
|
+
onCompletionError({
|
|
3302
|
+
type: "COMPLETION",
|
|
3303
|
+
message: (error == null ? void 0 : error.message) || "Error getting completion"
|
|
3304
|
+
});
|
|
3305
|
+
throw new Error(error);
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
const getFeedback = async ({
|
|
3309
|
+
cardId,
|
|
3310
|
+
language = "en",
|
|
3311
|
+
// required
|
|
3312
|
+
writtenResponse = null,
|
|
3313
|
+
// if the type = RESPOND_WRITE
|
|
3314
|
+
audio = null,
|
|
3315
|
+
autoGrade = true,
|
|
3316
|
+
file = null,
|
|
3317
|
+
pagePrompt = null
|
|
3318
|
+
}) => {
|
|
3319
|
+
try {
|
|
3320
|
+
if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
|
|
3321
|
+
const result = {
|
|
3322
|
+
noFeedbackAvailable: true,
|
|
3323
|
+
success: true,
|
|
3324
|
+
reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
|
|
3325
|
+
accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
|
|
3326
|
+
};
|
|
3327
|
+
onCompletionSuccess(result);
|
|
3328
|
+
return result;
|
|
3329
|
+
}
|
|
3330
|
+
let transcript;
|
|
3331
|
+
let audioUrl = void 0;
|
|
3332
|
+
if (writtenResponse) {
|
|
3333
|
+
transcript = writtenResponse;
|
|
3334
|
+
onTranscriptSuccess(writtenResponse);
|
|
3335
|
+
} else if (typeof audio === "string" && file) {
|
|
3336
|
+
if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
|
|
3337
|
+
transcript = await getTranscript2(audio, language, pagePrompt != null ? pagePrompt : "");
|
|
3338
|
+
audioUrl = audio;
|
|
3339
|
+
onTranscriptSuccess(transcript);
|
|
3340
|
+
} else {
|
|
3341
|
+
console.info(
|
|
3342
|
+
`Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
|
|
3343
|
+
);
|
|
3344
|
+
}
|
|
3345
|
+
} else {
|
|
3346
|
+
const response = await uploadAudioAndGetTranscript(audio || "", language, pagePrompt != null ? pagePrompt : "");
|
|
3347
|
+
transcript = response.transcript;
|
|
3348
|
+
audioUrl = response.audioUrl;
|
|
3349
|
+
}
|
|
3350
|
+
onGetAudioUrlAndTranscript == null ? void 0 : onGetAudioUrlAndTranscript({ transcript, audioUrl });
|
|
3351
|
+
if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
|
|
3352
|
+
const results = await getAIResponse({
|
|
3353
|
+
cardId,
|
|
3354
|
+
transcript: transcript || ""
|
|
3355
|
+
});
|
|
3356
|
+
let output = results;
|
|
3357
|
+
if (!autoGrade) {
|
|
3358
|
+
output = {
|
|
3359
|
+
...output,
|
|
3360
|
+
noFeedbackAvailable: true,
|
|
3361
|
+
success: true
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
onCompletionSuccess(output);
|
|
3365
|
+
return output;
|
|
3366
|
+
} else {
|
|
3367
|
+
const result = {
|
|
3368
|
+
noFeedbackAvailable: true,
|
|
3369
|
+
success: true,
|
|
3370
|
+
reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
|
|
3371
|
+
accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
|
|
3372
|
+
};
|
|
3373
|
+
onCompletionSuccess(result);
|
|
3374
|
+
return result;
|
|
3375
|
+
}
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
console.error("Error getting feedback:", error);
|
|
3378
|
+
throw new Error(error);
|
|
3379
|
+
}
|
|
3380
|
+
};
|
|
3381
|
+
const getAIResponse = async ({ cardId, transcript }) => {
|
|
3382
|
+
var _a, _b, _c, _d, _e;
|
|
3383
|
+
try {
|
|
3384
|
+
const getGeminiFeedback = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "callGetFeedback");
|
|
3385
|
+
const getProficiencyEstimate = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "getProficiencyEstimate");
|
|
3386
|
+
const card = getCardFromCache({
|
|
3387
|
+
cardId,
|
|
3388
|
+
queryClient
|
|
3389
|
+
});
|
|
3390
|
+
let feedbackData;
|
|
3391
|
+
let proficiencyData = {};
|
|
3392
|
+
if (card && card.grading_method === "manual") {
|
|
3393
|
+
} else if (card && card.grading_method !== "standards_based") {
|
|
3394
|
+
const [geminiResult, proficiencyResult] = await Promise.all([
|
|
3395
|
+
getGeminiFeedback == null ? void 0 : getGeminiFeedback({
|
|
3396
|
+
cardId,
|
|
3397
|
+
studentId: currentUserId,
|
|
3398
|
+
studentResponse: transcript
|
|
3399
|
+
}),
|
|
3400
|
+
getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
|
|
3401
|
+
cardId,
|
|
3402
|
+
studentId: currentUserId,
|
|
3403
|
+
studentResponse: transcript
|
|
3404
|
+
})
|
|
3405
|
+
]);
|
|
3406
|
+
proficiencyData = (proficiencyResult == null ? void 0 : proficiencyResult.data) || {};
|
|
3407
|
+
feedbackData = {
|
|
3408
|
+
...(_e = geminiResult == null ? void 0 : geminiResult.data) != null ? _e : {},
|
|
3409
|
+
// @ts-ignore
|
|
3410
|
+
proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
|
|
3411
|
+
};
|
|
3412
|
+
} else {
|
|
3413
|
+
const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
|
|
3414
|
+
cardId,
|
|
3415
|
+
studentId: currentUserId,
|
|
3416
|
+
studentResponse: transcript
|
|
3417
|
+
}));
|
|
3418
|
+
feedbackData = geminiResult == null ? void 0 : geminiResult.data;
|
|
3419
|
+
}
|
|
3420
|
+
const results = {
|
|
3421
|
+
...proficiencyData,
|
|
3422
|
+
...feedbackData,
|
|
3423
|
+
aiSuccess: true,
|
|
3424
|
+
promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
|
|
3425
|
+
transcript
|
|
3426
|
+
};
|
|
3427
|
+
return results;
|
|
3428
|
+
} catch (error) {
|
|
3429
|
+
onCompletionError({
|
|
3430
|
+
type: "AI_FEEDBACK",
|
|
3431
|
+
message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
|
|
3432
|
+
});
|
|
3433
|
+
throw new Error(error);
|
|
3434
|
+
}
|
|
3435
|
+
};
|
|
3436
|
+
return {
|
|
3437
|
+
submitAudioResponse,
|
|
3438
|
+
uploadAudioAndGetTranscript,
|
|
3439
|
+
getTranscript: getTranscript2,
|
|
3440
|
+
getFreeResponseCompletion,
|
|
3441
|
+
getFeedback
|
|
3442
|
+
};
|
|
3443
|
+
};
|
|
3444
|
+
|
|
3445
|
+
// src/lib/create-firebase-client-native.ts
|
|
3446
|
+
var import_firestore = require("@react-native-firebase/firestore");
|
|
3447
|
+
|
|
1045
3448
|
// src/lib/create-firebase-client.ts
|
|
1046
3449
|
function createFsClientBase({
|
|
1047
3450
|
db,
|