@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.
@@ -1,26 +1,37 @@
1
- // src/lib/create-firebase-client-native.ts
2
- import {
3
- getDoc,
4
- getDocs,
5
- addDoc,
6
- setDoc,
7
- updateDoc,
8
- deleteDoc,
9
- runTransaction,
10
- writeBatch,
11
- doc,
12
- collection,
13
- query,
14
- serverTimestamp,
15
- orderBy,
16
- limit,
17
- startAt,
18
- startAfter,
19
- endAt,
20
- endBefore,
21
- where,
22
- increment
23
- } from "@react-native-firebase/firestore";
1
+ // src/providers/SpeakableProvider.tsx
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var FsCtx = createContext(null);
5
+ function SpeakableProvider({
6
+ user,
7
+ children,
8
+ queryClient,
9
+ permissions,
10
+ fsClient
11
+ }) {
12
+ const [speakableApi, setSpeakableApi] = useState(null);
13
+ useEffect(() => {
14
+ setSpeakableApi(fsClient);
15
+ }, [fsClient]);
16
+ if (!speakableApi) return null;
17
+ return /* @__PURE__ */ jsx(
18
+ FsCtx.Provider,
19
+ {
20
+ value: {
21
+ speakableApi,
22
+ queryClient,
23
+ user,
24
+ permissions
25
+ },
26
+ children
27
+ }
28
+ );
29
+ }
30
+ function useSpeakableApi() {
31
+ const ctx = useContext(FsCtx);
32
+ if (!ctx) throw new Error("useSpeakableApi must be used within a SpeakableProvider");
33
+ return ctx;
34
+ }
24
35
 
25
36
  // src/utils/error-handler.ts
26
37
  var ServiceError = class extends Error {
@@ -330,13 +341,30 @@ var createAssignmentRepo = () => {
330
341
  };
331
342
  };
332
343
 
333
- // src/providers/SpeakableProvider.tsx
334
- import { createContext, useContext, useEffect, useState } from "react";
335
- import { jsx } from "react/jsx-runtime";
336
- var FsCtx = createContext(null);
337
-
338
344
  // src/domains/assignment/hooks/assignment.hooks.ts
339
345
  import { useQuery } from "@tanstack/react-query";
346
+ var assignmentQueryKeys = {
347
+ all: ["assignments"],
348
+ byId: (id) => [...assignmentQueryKeys.all, id],
349
+ list: () => [...assignmentQueryKeys.all, "list"]
350
+ };
351
+ function useAssignment({
352
+ assignmentId,
353
+ enabled = true,
354
+ analyticType,
355
+ userId
356
+ }) {
357
+ const { speakableApi } = useSpeakableApi();
358
+ return useQuery({
359
+ queryKey: assignmentQueryKeys.byId(assignmentId),
360
+ queryFn: () => speakableApi.assignmentRepo.getAssignment({
361
+ assignmentId,
362
+ analyticType,
363
+ currentUserId: userId
364
+ }),
365
+ enabled
366
+ });
367
+ }
340
368
 
341
369
  // src/domains/assignment/hooks/score-hooks.ts
342
370
  import { useMutation, useQuery as useQuery2 } from "@tanstack/react-query";
@@ -359,6 +387,24 @@ function debounce(func, waitFor) {
359
387
  });
360
388
  }
361
389
 
390
+ // src/lib/tanstack/handle-optimistic-update-query.ts
391
+ var handleOptimisticUpdate = async ({
392
+ queryClient,
393
+ queryKey,
394
+ newData
395
+ }) => {
396
+ await queryClient.cancelQueries({
397
+ queryKey
398
+ });
399
+ const previousData = queryClient.getQueryData(queryKey);
400
+ if (previousData === void 0) {
401
+ queryClient.setQueryData(queryKey, newData);
402
+ } else {
403
+ queryClient.setQueryData(queryKey, { ...previousData, ...newData });
404
+ }
405
+ return { previousData };
406
+ };
407
+
362
408
  // src/constants/speakable-plans.ts
363
409
  var FEEDBACK_PLANS = {
364
410
  FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
@@ -515,9 +561,64 @@ var SpeakablePlanHierarchy = [
515
561
  SpeakablePlanTypes.organization
516
562
  ];
517
563
 
564
+ // src/hooks/usePermissions.ts
565
+ var usePermissions = () => {
566
+ const { permissions } = useSpeakableApi();
567
+ const has = (permission) => {
568
+ var _a;
569
+ return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
570
+ };
571
+ return {
572
+ plan: permissions.plan,
573
+ permissionsLoaded: permissions.loaded,
574
+ isStripePlan: permissions.isStripePlan,
575
+ refreshDate: permissions.refreshDate,
576
+ isInstitutionPlan: permissions.isInstitutionPlan,
577
+ subscriptionId: permissions.subscriptionId,
578
+ contact: permissions.contact,
579
+ hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
580
+ hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
581
+ hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
582
+ hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
583
+ hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
584
+ permissions: permissions || [],
585
+ hasStudentPortfolios: permissions.hasStudentPortfolios,
586
+ isFreeOrgTrial: permissions.type === "free_org_trial",
587
+ freeOrgTrialExpired: permissions.freeOrgTrialExpired
588
+ };
589
+ };
590
+ var usePermissions_default = usePermissions;
591
+
592
+ // src/domains/notification/notification.constants.ts
593
+ var SPEAKABLE_NOTIFICATIONS = {
594
+ NEW_ASSIGNMENT: "new_assignment",
595
+ ASSESSMENT_SUBMITTED: "assessment_submitted",
596
+ ASSESSMENT_SCORED: "assessment_scored",
597
+ NEW_COMMENT: "NEW_COMMENT"
598
+ };
599
+ var SpeakableNotificationTypes = {
600
+ NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
601
+ FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
602
+ MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
603
+ PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
604
+ STUDENT_PROGRESS: "STUDENT_PROGRESS",
605
+ PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
606
+ PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
607
+ // New notifications
608
+ ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
609
+ // Notification FOR TEACHER when student submits assessment
610
+ ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
611
+ // Notification FOR STUDENT when teacher scores assessment
612
+ // Comment
613
+ NEW_COMMENT: "NEW_COMMENT"
614
+ };
615
+
518
616
  // src/domains/notification/services/create-notification.service.ts
519
617
  import dayjs2 from "dayjs";
520
618
 
619
+ // src/constants/web.constants.ts
620
+ var WEB_BASE_URL = "https://app.speakable.io";
621
+
521
622
  // src/domains/notification/services/send-notification.service.ts
522
623
  var _sendNotification = async (sendTo, notification) => {
523
624
  var _a, _b, _c;
@@ -530,6 +631,202 @@ var _sendNotification = async (sendTo, notification) => {
530
631
  };
531
632
  var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
532
633
 
634
+ // src/domains/notification/services/create-notification.service.ts
635
+ var createNotification = async ({
636
+ data,
637
+ type,
638
+ userId,
639
+ profile
640
+ }) => {
641
+ let result;
642
+ switch (type) {
643
+ case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
644
+ result = await handleAssignNotifPromise({ data, profile });
645
+ break;
646
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
647
+ result = await createAssessmentSubmissionNotification({
648
+ data,
649
+ profile,
650
+ userId
651
+ });
652
+ break;
653
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
654
+ result = await createAssessmentScoredNotification({
655
+ data,
656
+ profile
657
+ });
658
+ break;
659
+ default:
660
+ result = null;
661
+ break;
662
+ }
663
+ return result;
664
+ };
665
+ var handleAssignNotifPromise = async ({
666
+ data: assignments,
667
+ profile
668
+ }) => {
669
+ if (!assignments.length) return;
670
+ try {
671
+ const notifsPromises = assignments.map(async (assignment) => {
672
+ const {
673
+ section,
674
+ section: { members },
675
+ ...rest
676
+ } = assignment;
677
+ if (!section || !members) throw new Error("Invalid assignment data");
678
+ const data = { section, sendTo: members, assignment: { ...rest } };
679
+ return createNewAssignmentNotification({ data, profile });
680
+ });
681
+ await Promise.all(notifsPromises);
682
+ return {
683
+ success: true,
684
+ message: "Assignment notifications sent successfully"
685
+ };
686
+ } catch (error) {
687
+ console.error("Error in handleAssignNotifPromise:", error);
688
+ throw error;
689
+ }
690
+ };
691
+ var createNewAssignmentNotification = async ({
692
+ data,
693
+ profile
694
+ }) => {
695
+ var _a;
696
+ const { assignment, sendTo } = data;
697
+ const teacherName = profile.displayName || "Your teacher";
698
+ const dueDate = assignment.dueDateTimestamp ? dayjs2(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
699
+ const results = await sendNotification(sendTo, {
700
+ courseId: assignment.courseId,
701
+ type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
702
+ senderName: teacherName,
703
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
704
+ messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
705
+ title: "New Assignment Available!",
706
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
707
+ });
708
+ return results;
709
+ };
710
+ var createAssessmentSubmissionNotification = async ({
711
+ data: assignment,
712
+ profile,
713
+ userId
714
+ }) => {
715
+ var _a;
716
+ const studentName = profile.displayName || "Your student";
717
+ const results = await sendNotification(assignment.owners, {
718
+ courseId: assignment.courseId,
719
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
720
+ link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
721
+ title: `Assessment Submitted!`,
722
+ senderName: studentName,
723
+ messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
724
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
725
+ });
726
+ return results;
727
+ };
728
+ var createAssessmentScoredNotification = async ({
729
+ data,
730
+ profile
731
+ }) => {
732
+ var _a, _b, _c, _d, _e;
733
+ const { assignment, sendTo } = data;
734
+ const teacherName = profile.displayName || "Your teacher";
735
+ const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
736
+ const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
737
+ const results = await sendNotification(sendTo, {
738
+ courseId: assignment.courseId,
739
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
740
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
741
+ title,
742
+ messagePreview,
743
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
744
+ senderName: teacherName
745
+ });
746
+ await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
747
+ assessmentTitle: assignment.name,
748
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
749
+ senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
750
+ studentId: sendTo[0],
751
+ teacherName: profile.displayName
752
+ }));
753
+ return results;
754
+ };
755
+
756
+ // src/domains/notification/hooks/notification.hooks.ts
757
+ var notificationQueryKeys = {
758
+ all: ["notifications"],
759
+ byId: (id) => [...notificationQueryKeys.all, id]
760
+ };
761
+ var useCreateNotification = () => {
762
+ const { user, queryClient } = useSpeakableApi();
763
+ const handleCreateNotifications = async (type, data) => {
764
+ var _a, _b;
765
+ const result = await createNotification({
766
+ type,
767
+ userId: user.auth.uid,
768
+ profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
769
+ data
770
+ });
771
+ queryClient.invalidateQueries({
772
+ queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
773
+ });
774
+ return result;
775
+ };
776
+ return {
777
+ createNotification: handleCreateNotifications
778
+ };
779
+ };
780
+
781
+ // src/hooks/useGoogleClassroom.ts
782
+ var useGoogleClassroom = () => {
783
+ const submitAssignmentToGoogleClassroom = async ({
784
+ assignment,
785
+ scores,
786
+ googleUserId = null
787
+ // optional to override the user's googleUserId
788
+ }) => {
789
+ var _a, _b, _c;
790
+ try {
791
+ const { googleClassroomUserId = null } = scores;
792
+ const googleId = googleUserId || googleClassroomUserId;
793
+ if (!googleId)
794
+ return {
795
+ error: true,
796
+ message: "No Google Classroom ID found"
797
+ };
798
+ const { courseWorkId, maxPoints, owners, courseId } = assignment;
799
+ const draftGrade = (scores == null ? void 0 : scores.score) ? (scores == null ? void 0 : scores.score) / 100 * maxPoints : 0;
800
+ const result = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentToGoogleClassroomV2")) == null ? void 0 : _c({
801
+ teacherId: owners[0],
802
+ courseId,
803
+ courseWorkId,
804
+ userId: googleId,
805
+ draftGrade
806
+ }));
807
+ return result;
808
+ } catch (error) {
809
+ return { error: true, message: error.message };
810
+ }
811
+ };
812
+ return {
813
+ submitAssignmentToGoogleClassroom
814
+ };
815
+ };
816
+
817
+ // src/lib/firebase/firebase-analytics/grading-standard.ts
818
+ var logGradingStandardLog = (data) => {
819
+ var _a, _b, _c;
820
+ if (data.courseId && data.type && data.level) {
821
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
822
+ eventType: data.type || "custom",
823
+ level: data.level,
824
+ courseId: data.courseId
825
+ });
826
+ }
827
+ api.logEvent("logGradingStandard", data);
828
+ };
829
+
533
830
  // src/constants/analytics.constants.ts
534
831
  var ANALYTICS_EVENT_TYPES = {
535
832
  VOICE_SUCCESS: "voice_success",
@@ -569,6 +866,20 @@ var ANALYTICS_EVENT_TYPES = {
569
866
  };
570
867
 
571
868
  // src/lib/firebase/firebase-analytics/assignment.ts
869
+ var logOpenAssignment = (data = {}) => {
870
+ api.logEvent("open_assignment", data);
871
+ };
872
+ var logOpenActivityPreview = (data = {}) => {
873
+ api.logEvent("open_activity_preview", data);
874
+ };
875
+ var logSubmitAssignment = (data = {}) => {
876
+ var _a, _b, _c;
877
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
878
+ eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
879
+ ...data
880
+ });
881
+ api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
882
+ };
572
883
  var logStartAssignment = (data = {}) => {
573
884
  var _a, _b, _c;
574
885
  if (data.courseId) {
@@ -811,6 +1122,72 @@ var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
811
1122
 
812
1123
  // src/domains/assignment/services/clear-score.service.ts
813
1124
  import dayjs3 from "dayjs";
1125
+ async function clearScore(params) {
1126
+ var _a, _b, _c, _d, _e;
1127
+ const update = {
1128
+ [`cards.${params.cardId}`]: {
1129
+ attempts: ((_a = params.cardScores.attempts) != null ? _a : 1) + 1,
1130
+ correct: (_b = params.cardScores.correct) != null ? _b : 0,
1131
+ // save old score history
1132
+ history: [
1133
+ {
1134
+ ...params.cardScores,
1135
+ attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1136
+ correct: (_d = params.cardScores.correct) != null ? _d : 0,
1137
+ retryTime: dayjs3().format("YYYY-MM-DD HH:mm:ss"),
1138
+ history: null
1139
+ },
1140
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1141
+ ...(_e = params.cardScores.history) != null ? _e : []
1142
+ ]
1143
+ }
1144
+ };
1145
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1146
+ id: params.activityId,
1147
+ userId: params.userId
1148
+ }) : refsScoresPractice.practiceScores({
1149
+ setId: params.activityId,
1150
+ userId: params.userId
1151
+ });
1152
+ await api.updateDoc(path, update);
1153
+ return {
1154
+ update,
1155
+ activityId: params.activityId
1156
+ };
1157
+ }
1158
+ async function clearScoreV2(params) {
1159
+ var _a, _b, _c, _d, _e;
1160
+ const update = {
1161
+ [`cards.${params.cardId}`]: {
1162
+ ...params.cardScores,
1163
+ attempts: ((_a = params.cardScores.attempts) != null ? _a : 1) + 1,
1164
+ correct: (_b = params.cardScores.correct) != null ? _b : 0,
1165
+ history: [
1166
+ {
1167
+ ...params.cardScores,
1168
+ attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1169
+ correct: (_d = params.cardScores.correct) != null ? _d : 0,
1170
+ retryTime: dayjs3().format("YYYY-MM-DD HH:mm:ss"),
1171
+ history: null
1172
+ },
1173
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1174
+ ...(_e = params.cardScores.history) != null ? _e : []
1175
+ ]
1176
+ }
1177
+ };
1178
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1179
+ id: params.activityId,
1180
+ userId: params.userId
1181
+ }) : refsScoresPractice.practiceScores({
1182
+ setId: params.activityId,
1183
+ userId: params.userId
1184
+ });
1185
+ await api.updateDoc(path, update);
1186
+ return {
1187
+ update,
1188
+ activityId: params.activityId
1189
+ };
1190
+ }
814
1191
 
815
1192
  // src/domains/assignment/services/submit-assignment-score.service.ts
816
1193
  import dayjs4 from "dayjs";
@@ -876,15 +1253,458 @@ async function handleCourseAssignment(assignment, userId) {
876
1253
  userId
877
1254
  }));
878
1255
  }
1256
+ async function submitPracticeScore({
1257
+ setId,
1258
+ userId,
1259
+ scores
1260
+ }) {
1261
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1262
+ const date = dayjs4().format("YYYY-MM-DD-HH-mm");
1263
+ const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
1264
+ const fieldsUpdated = {
1265
+ ...scores,
1266
+ submitted: true,
1267
+ progress: 100,
1268
+ submissionDate: serverTimestamp2(),
1269
+ status: "SUBMITTED"
1270
+ };
1271
+ await api.setDoc(ref, { ...fieldsUpdated });
1272
+ const refScores = refsScoresPractice.practiceScores({ userId, setId });
1273
+ await api.deleteDoc(refScores);
1274
+ return { success: true, fieldsUpdated };
1275
+ }
879
1276
 
880
1277
  // src/domains/assignment/hooks/score-hooks.ts
1278
+ var scoreQueryKeys = {
1279
+ all: ["scores"],
1280
+ byId: (id) => [...scoreQueryKeys.all, id],
1281
+ list: () => [...scoreQueryKeys.all, "list"]
1282
+ };
1283
+ function useScore({
1284
+ isAssignment,
1285
+ activityId,
1286
+ userId = "",
1287
+ courseId,
1288
+ enabled = true,
1289
+ googleClassroomUserId
1290
+ }) {
1291
+ return useQuery2({
1292
+ queryFn: () => getScore({
1293
+ userId,
1294
+ courseId,
1295
+ activityId,
1296
+ googleClassroomUserId,
1297
+ isAssignment
1298
+ }),
1299
+ queryKey: scoreQueryKeys.byId(activityId),
1300
+ enabled
1301
+ });
1302
+ }
881
1303
  var debounceUpdateScore = debounce(updateScore, 500);
1304
+ function useUpdateScore() {
1305
+ const { queryClient } = useSpeakableApi();
1306
+ const mutation = useMutation({
1307
+ mutationFn: debounceUpdateScore,
1308
+ onMutate: (variables) => {
1309
+ return handleOptimisticUpdate({
1310
+ queryClient,
1311
+ queryKey: scoreQueryKeys.byId(variables.activityId),
1312
+ newData: variables.data
1313
+ });
1314
+ },
1315
+ onError: (_, variables, context) => {
1316
+ if (context == null ? void 0 : context.previousData)
1317
+ queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1318
+ },
1319
+ onSettled: (_, err, variables) => {
1320
+ queryClient.invalidateQueries({
1321
+ queryKey: scoreQueryKeys.byId(variables.activityId)
1322
+ });
1323
+ }
1324
+ });
1325
+ return {
1326
+ mutationUpdateScore: mutation
1327
+ };
1328
+ }
1329
+ function useUpdateCardScore({
1330
+ isAssignment,
1331
+ activityId,
1332
+ userId,
1333
+ cardIds,
1334
+ weights
1335
+ }) {
1336
+ const { queryClient } = useSpeakableApi();
1337
+ const queryKey = scoreQueryKeys.byId(activityId);
1338
+ const mutation = useMutation({
1339
+ mutationFn: async ({ cardId, cardScore }) => {
1340
+ const previousScores = queryClient.getQueryData(queryKey);
1341
+ const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1342
+ previousScores: previousScores != null ? previousScores : {},
1343
+ cardId,
1344
+ cardScore,
1345
+ cardIds,
1346
+ weights
1347
+ });
1348
+ await updateCardScore({
1349
+ userId,
1350
+ cardId,
1351
+ isAssignment,
1352
+ activityId,
1353
+ updates: {
1354
+ cardScore: updatedCardScore,
1355
+ progress,
1356
+ score
1357
+ }
1358
+ });
1359
+ return { cardId, scoresUpdated: newScoreUpdated };
1360
+ },
1361
+ onMutate: ({ cardId, cardScore }) => {
1362
+ const previousData = queryClient.getQueryData(queryKey);
1363
+ queryClient.setQueryData(queryKey, (previousScore) => {
1364
+ const updates = handleOptimisticScore({
1365
+ score: previousScore,
1366
+ cardId,
1367
+ cardScore,
1368
+ cardIds,
1369
+ weights
1370
+ });
1371
+ return {
1372
+ ...previousScore,
1373
+ ...updates
1374
+ };
1375
+ });
1376
+ return { previousData };
1377
+ },
1378
+ onError: (error, variables, context) => {
1379
+ console.log("Error updating card score:", error.message);
1380
+ if (context == null ? void 0 : context.previousData) {
1381
+ queryClient.setQueryData(queryKey, context.previousData);
1382
+ }
1383
+ },
1384
+ onSettled: () => {
1385
+ queryClient.invalidateQueries({
1386
+ queryKey
1387
+ });
1388
+ }
1389
+ });
1390
+ return {
1391
+ mutationUpdateCardScore: mutation
1392
+ };
1393
+ }
1394
+ var getScoreUpdated = ({
1395
+ cardId,
1396
+ cardScore,
1397
+ previousScores,
1398
+ cardIds,
1399
+ weights
1400
+ }) => {
1401
+ var _a, _b;
1402
+ const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
1403
+ const newCardScore = {
1404
+ ...previousCard != null ? previousCard : {},
1405
+ ...cardScore
1406
+ };
1407
+ const newScores = {
1408
+ ...previousScores,
1409
+ cards: {
1410
+ ...(_b = previousScores.cards) != null ? _b : {},
1411
+ [cardId]: newCardScore
1412
+ }
1413
+ };
1414
+ const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
1415
+ return {
1416
+ newScoreUpdated: newScores,
1417
+ updatedCardScore: cardScore,
1418
+ score,
1419
+ progress
1420
+ };
1421
+ };
1422
+ var handleOptimisticScore = ({
1423
+ score,
1424
+ cardId,
1425
+ cardScore,
1426
+ cardIds,
1427
+ weights
1428
+ }) => {
1429
+ var _a;
1430
+ let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
1431
+ cards = {
1432
+ ...cards,
1433
+ [cardId]: {
1434
+ ...cards[cardId],
1435
+ ...cardScore
1436
+ }
1437
+ };
1438
+ const { score: scoreValue, progress } = calculateScoreAndProgress_default(
1439
+ // @ts-ignore
1440
+ {
1441
+ ...score != null ? score : {},
1442
+ cards
1443
+ },
1444
+ cardIds,
1445
+ weights
1446
+ );
1447
+ return { cards, score: scoreValue, progress };
1448
+ };
1449
+ function useClearScore() {
1450
+ const { queryClient } = useSpeakableApi();
1451
+ const mutation = useMutation({
1452
+ mutationFn: clearScore,
1453
+ onError: (error) => {
1454
+ console.log("Error clearing score:", error.message);
1455
+ },
1456
+ onSettled: (result) => {
1457
+ var _a;
1458
+ queryClient.invalidateQueries({
1459
+ queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
1460
+ });
1461
+ }
1462
+ });
1463
+ return {
1464
+ mutationClearScore: mutation
1465
+ };
1466
+ }
1467
+ function useClearScoreV2() {
1468
+ const { queryClient } = useSpeakableApi();
1469
+ const mutation = useMutation({
1470
+ mutationFn: clearScoreV2,
1471
+ onError: (error) => {
1472
+ console.log("Error clearing score V2:", error.message);
1473
+ },
1474
+ onSettled: (result) => {
1475
+ var _a;
1476
+ queryClient.invalidateQueries({
1477
+ queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
1478
+ });
1479
+ }
1480
+ });
1481
+ return {
1482
+ mutationClearScore: mutation
1483
+ };
1484
+ }
1485
+ function useSubmitAssignmentScore({
1486
+ onAssignmentSubmitted,
1487
+ studentName
1488
+ }) {
1489
+ const { queryClient } = useSpeakableApi();
1490
+ const { hasGoogleClassroomGradePassback } = usePermissions_default();
1491
+ const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
1492
+ const { createNotification: createNotification2 } = useCreateNotification();
1493
+ const mutation = useMutation({
1494
+ mutationFn: async ({
1495
+ assignment,
1496
+ userId,
1497
+ cardIds,
1498
+ weights,
1499
+ scores,
1500
+ status
1501
+ }) => {
1502
+ try {
1503
+ const scoreUpdated = await submitAssignmentScore({
1504
+ assignment,
1505
+ userId,
1506
+ cardIds,
1507
+ weights,
1508
+ status,
1509
+ studentName
1510
+ });
1511
+ if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
1512
+ await submitAssignmentToGoogleClassroom({
1513
+ assignment,
1514
+ scores
1515
+ });
1516
+ }
1517
+ if (assignment.isAssessment) {
1518
+ createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1519
+ }
1520
+ if (assignment == null ? void 0 : assignment.id) {
1521
+ logSubmitAssignment({
1522
+ courseId: assignment == null ? void 0 : assignment.courseId
1523
+ });
1524
+ }
1525
+ onAssignmentSubmitted(assignment.id);
1526
+ queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1527
+ ...scores,
1528
+ ...scoreUpdated.fieldsUpdated
1529
+ });
1530
+ return {
1531
+ success: true,
1532
+ message: "Score submitted successfully"
1533
+ };
1534
+ } catch (error) {
1535
+ return {
1536
+ success: false,
1537
+ error
1538
+ };
1539
+ }
1540
+ }
1541
+ });
1542
+ return {
1543
+ submitAssignmentScore: mutation.mutateAsync,
1544
+ isLoading: mutation.isPending
1545
+ };
1546
+ }
1547
+ function useSubmitPracticeScore() {
1548
+ const { queryClient } = useSpeakableApi();
1549
+ const mutation = useMutation({
1550
+ mutationFn: async ({
1551
+ setId,
1552
+ userId,
1553
+ scores
1554
+ }) => {
1555
+ try {
1556
+ await submitPracticeScore({
1557
+ setId,
1558
+ userId,
1559
+ scores
1560
+ });
1561
+ queryClient.invalidateQueries({
1562
+ queryKey: scoreQueryKeys.byId(setId)
1563
+ });
1564
+ return {
1565
+ success: true,
1566
+ message: "Score submitted successfully"
1567
+ };
1568
+ } catch (error) {
1569
+ return {
1570
+ success: false,
1571
+ error
1572
+ };
1573
+ }
1574
+ }
1575
+ });
1576
+ return {
1577
+ submitPracticeScore: mutation.mutateAsync,
1578
+ isLoading: mutation.isPending
1579
+ };
1580
+ }
882
1581
 
883
1582
  // src/domains/cards/card.hooks.ts
884
1583
  import { useMutation as useMutation2, useQueries, useQuery as useQuery3 } from "@tanstack/react-query";
885
1584
  import { useMemo } from "react";
886
1585
 
1586
+ // src/domains/cards/card.model.ts
1587
+ var ActivityPageType = /* @__PURE__ */ ((ActivityPageType2) => {
1588
+ ActivityPageType2["READ_REPEAT"] = "READ_REPEAT";
1589
+ ActivityPageType2["READ_RESPOND"] = "READ_RESPOND";
1590
+ ActivityPageType2["FREE_RESPONSE"] = "FREE_RESPONSE";
1591
+ ActivityPageType2["REPEAT"] = "REPEAT";
1592
+ ActivityPageType2["RESPOND"] = "RESPOND";
1593
+ ActivityPageType2["RESPOND_WRITE"] = "RESPOND_WRITE";
1594
+ ActivityPageType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
1595
+ ActivityPageType2["MEDIA_PAGE"] = "MEDIA_PAGE";
1596
+ ActivityPageType2["SHORT_ANSWER"] = "SHORT_ANSWER";
1597
+ return ActivityPageType2;
1598
+ })(ActivityPageType || {});
1599
+ var RESPOND_PAGE_ACTIVITY_TYPES = [
1600
+ "READ_RESPOND" /* READ_RESPOND */,
1601
+ "RESPOND" /* RESPOND */,
1602
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1603
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1604
+ ];
1605
+ var MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
1606
+ var REPEAT_PAGE_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
1607
+ var RESPOND_WRITE_PAGE_ACTIVITY_TYPES = [
1608
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1609
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1610
+ ];
1611
+ var RESPOND_AUDIO_PAGE_ACTIVITY_TYPES = [
1612
+ "RESPOND" /* RESPOND */,
1613
+ "READ_RESPOND" /* READ_RESPOND */
1614
+ ];
1615
+
887
1616
  // src/domains/cards/card.constants.ts
1617
+ var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
1618
+ FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
1619
+ FeedbackTypesCard2["Wida"] = "wida";
1620
+ FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
1621
+ FeedbackTypesCard2["Actfl"] = "actfl";
1622
+ FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
1623
+ return FeedbackTypesCard2;
1624
+ })(FeedbackTypesCard || {});
1625
+ var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
1626
+ LeniencyCard2["CONFIDENCE"] = "confidence";
1627
+ LeniencyCard2["EASY"] = "easy";
1628
+ LeniencyCard2["NORMAL"] = "normal";
1629
+ LeniencyCard2["HARD"] = "hard";
1630
+ return LeniencyCard2;
1631
+ })(LeniencyCard || {});
1632
+ var LENIENCY_OPTIONS = [
1633
+ {
1634
+ label: "Build Confidence - most lenient",
1635
+ value: "confidence" /* CONFIDENCE */
1636
+ },
1637
+ {
1638
+ label: "Very Lenient",
1639
+ value: "easy" /* EASY */
1640
+ },
1641
+ {
1642
+ label: "Normal",
1643
+ value: "normal" /* NORMAL */
1644
+ },
1645
+ {
1646
+ label: "No leniency - most strict",
1647
+ value: "hard" /* HARD */
1648
+ }
1649
+ ];
1650
+ var STUDENT_LEVELS_OPTIONS = [
1651
+ {
1652
+ label: "Beginner",
1653
+ description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
1654
+ value: "beginner"
1655
+ },
1656
+ {
1657
+ label: "Elementary",
1658
+ description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
1659
+ value: "elementary"
1660
+ },
1661
+ {
1662
+ label: "Intermediate",
1663
+ description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
1664
+ value: "intermediate"
1665
+ },
1666
+ {
1667
+ label: "Advanced",
1668
+ description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
1669
+ value: "advanced"
1670
+ },
1671
+ {
1672
+ label: "Fluent",
1673
+ description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
1674
+ value: "fluent"
1675
+ },
1676
+ {
1677
+ label: "Native-like",
1678
+ description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
1679
+ value: "nativeLike"
1680
+ }
1681
+ ];
1682
+ var BASE_RESPOND_FIELD_VALUES = {
1683
+ title: "",
1684
+ allowRetries: true,
1685
+ respondTime: 180,
1686
+ maxCharacters: 1e3
1687
+ };
1688
+ var BASE_REPEAT_FIELD_VALUES = {
1689
+ repeat: 1
1690
+ };
1691
+ var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
1692
+ MCQType: "single",
1693
+ answer: ["A"],
1694
+ choices: [
1695
+ { option: "A", value: "Option A" },
1696
+ { option: "B", value: "Option B" },
1697
+ { option: "C", value: "Option C" }
1698
+ ]
1699
+ };
1700
+ var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
1701
+ VerificationCardStatus2["VERIFIED"] = "VERIFIED";
1702
+ VerificationCardStatus2["WARNING"] = "WARNING";
1703
+ VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
1704
+ VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
1705
+ VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
1706
+ return VerificationCardStatus2;
1707
+ })(VerificationCardStatus || {});
888
1708
  var CARDS_COLLECTION = "flashcards";
889
1709
  var refsCardsFiresotre = {
890
1710
  allCards: CARDS_COLLECTION,
@@ -931,6 +1751,13 @@ var getWordHash = (word, language) => {
931
1751
  console.log("wordHash core library", wordHash);
932
1752
  return wordHash;
933
1753
  };
1754
+ function getPhraseLength(phrase, input) {
1755
+ if (Array.isArray(phrase) && phrase.includes(input)) {
1756
+ return phrase[phrase.indexOf(input)].split(" ").length;
1757
+ } else {
1758
+ return phrase ? phrase.split(" ").length : 0;
1759
+ }
1760
+ }
934
1761
 
935
1762
  // src/domains/cards/services/get-card-verification-status.service.ts
936
1763
  var charactarLanguages = ["zh", "ja", "ko"];
@@ -1001,16 +1828,262 @@ async function _createCards({ cards }) {
1001
1828
  }
1002
1829
  var createCards = withErrorHandler(_createCards, "createCards");
1003
1830
 
1004
- // src/domains/cards/card.repo.ts
1005
- var createCardRepo = () => {
1006
- return {
1007
- createCard,
1008
- createCards,
1009
- getCard
1010
- };
1831
+ // src/domains/cards/card.hooks.ts
1832
+ var cardsQueryKeys = {
1833
+ all: ["cards"],
1834
+ one: (params) => [...cardsQueryKeys.all, params.cardId]
1011
1835
  };
1012
-
1013
- // src/domains/sets/set.hooks.ts
1836
+ function useCards({
1837
+ cardIds,
1838
+ enabled = true,
1839
+ asObject
1840
+ }) {
1841
+ const queries = useQueries({
1842
+ queries: cardIds.map((cardId) => ({
1843
+ enabled: enabled && cardIds.length > 0,
1844
+ queryKey: cardsQueryKeys.one({
1845
+ cardId
1846
+ }),
1847
+ queryFn: () => getCard({ cardId })
1848
+ }))
1849
+ });
1850
+ const cards = queries.map((query2) => query2.data).filter(Boolean);
1851
+ const cardsObject = useMemo(() => {
1852
+ if (!asObject) return null;
1853
+ return cards.reduce((acc, card) => {
1854
+ acc[card.id] = card;
1855
+ return acc;
1856
+ }, {});
1857
+ }, [asObject, cards]);
1858
+ return {
1859
+ cards,
1860
+ cardsObject,
1861
+ cardsQueries: queries
1862
+ };
1863
+ }
1864
+ function useCreateCard() {
1865
+ const { queryClient } = useSpeakableApi();
1866
+ const mutationCreateCard = useMutation2({
1867
+ mutationFn: createCard,
1868
+ onSuccess: (cardCreated) => {
1869
+ queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
1870
+ }
1871
+ });
1872
+ return {
1873
+ mutationCreateCard
1874
+ };
1875
+ }
1876
+ function useCreateCards() {
1877
+ const mutationCreateCards = useMutation2({
1878
+ mutationFn: createCards
1879
+ });
1880
+ return {
1881
+ mutationCreateCards
1882
+ };
1883
+ }
1884
+ function getCardFromCache({
1885
+ cardId,
1886
+ queryClient
1887
+ }) {
1888
+ return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
1889
+ }
1890
+ function updateCardInCache({
1891
+ cardId,
1892
+ card,
1893
+ queryClient
1894
+ }) {
1895
+ queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
1896
+ }
1897
+ function useGetCard({ cardId, enabled = true }) {
1898
+ const query2 = useQuery3({
1899
+ queryKey: cardsQueryKeys.one({ cardId }),
1900
+ queryFn: () => getCard({ cardId }),
1901
+ enabled: enabled && !!cardId
1902
+ });
1903
+ return query2;
1904
+ }
1905
+
1906
+ // src/domains/cards/card.repo.ts
1907
+ var createCardRepo = () => {
1908
+ return {
1909
+ createCard,
1910
+ createCards,
1911
+ getCard
1912
+ };
1913
+ };
1914
+
1915
+ // src/domains/cards/utils/check-page-type.ts
1916
+ function checkIsRepeatPage(cardType) {
1917
+ if (cardType === void 0) return false;
1918
+ return REPEAT_PAGE_ACTIVITY_TYPES.includes(cardType);
1919
+ }
1920
+ function checkIsMCPage(cardType) {
1921
+ if (cardType === void 0) return false;
1922
+ return MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES.includes(cardType);
1923
+ }
1924
+ function checkIsRespondPage(cardType) {
1925
+ if (cardType === void 0) return false;
1926
+ return RESPOND_PAGE_ACTIVITY_TYPES.includes(cardType);
1927
+ }
1928
+ function checkIsRespondWrittenPage(cardType) {
1929
+ if (cardType === void 0) return false;
1930
+ return RESPOND_WRITE_PAGE_ACTIVITY_TYPES.includes(cardType);
1931
+ }
1932
+ function checkIsRespondAudioPage(cardType) {
1933
+ if (cardType === void 0) return false;
1934
+ return RESPOND_AUDIO_PAGE_ACTIVITY_TYPES.includes(cardType);
1935
+ }
1936
+ var checkIsMediaPage = (cardType) => {
1937
+ if (cardType === void 0) return false;
1938
+ return cardType === "MEDIA_PAGE" /* MEDIA_PAGE */;
1939
+ };
1940
+ var checkIsShortAnswerPage = (cardType) => {
1941
+ if (cardType === void 0) return false;
1942
+ return cardType === "SHORT_ANSWER" /* SHORT_ANSWER */;
1943
+ };
1944
+ var checkTypePageActivity = (cardType) => {
1945
+ const isRespondAudio = checkIsRespondAudioPage(cardType);
1946
+ const isRespondWritten = checkIsRespondWrittenPage(cardType);
1947
+ const isRespond = checkIsRespondPage(cardType);
1948
+ const isMC = checkIsMCPage(cardType);
1949
+ const isRepeat = checkIsRepeatPage(cardType);
1950
+ const isMediaPage = checkIsMediaPage(cardType);
1951
+ const isShortAnswer = checkIsShortAnswerPage(cardType);
1952
+ const isNoOneOfThem = !isRespond && !isMC && !isRepeat && !isMediaPage && !isShortAnswer;
1953
+ if (isNoOneOfThem) {
1954
+ return {
1955
+ isRespondAudio: false,
1956
+ isRespondWritten: false,
1957
+ isRespond: false,
1958
+ isMC: false,
1959
+ isRepeat: true,
1960
+ isMediaPage: false,
1961
+ isShortAnswer: false,
1962
+ hasSomeType: false
1963
+ };
1964
+ }
1965
+ return {
1966
+ isRespondAudio,
1967
+ isRespondWritten,
1968
+ isRespond,
1969
+ isMC,
1970
+ isRepeat,
1971
+ isMediaPage,
1972
+ isShortAnswer,
1973
+ hasSomeType: true
1974
+ };
1975
+ };
1976
+
1977
+ // src/domains/cards/utils/get-page-prompt.ts
1978
+ function extractTextFromRichText(richText) {
1979
+ if (!richText) return "";
1980
+ return richText.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
1981
+ }
1982
+ function getPagePrompt(card) {
1983
+ if (!card) return { has: false, text: "", rich_text: "", isTextEqualToRichText: false };
1984
+ const { isMC, isRepeat, isRespond, isShortAnswer } = checkTypePageActivity(card == null ? void 0 : card.type);
1985
+ const hidePrompt = (card == null ? void 0 : card.hidePrompt) === true;
1986
+ const createReturnObject = (text, richText) => {
1987
+ const plainText = text || "";
1988
+ const richTextPlain = extractTextFromRichText(richText);
1989
+ return {
1990
+ has: true,
1991
+ text: plainText,
1992
+ rich_text: richText || "",
1993
+ isTextEqualToRichText: plainText.trim() === richTextPlain.trim()
1994
+ };
1995
+ };
1996
+ if (isRepeat) {
1997
+ return createReturnObject(card == null ? void 0 : card.target_text, card == null ? void 0 : card.rich_text);
1998
+ }
1999
+ if (isRespond && !hidePrompt) {
2000
+ return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
2001
+ }
2002
+ if (isMC) {
2003
+ return createReturnObject(card == null ? void 0 : card.question, card == null ? void 0 : card.rich_text);
2004
+ }
2005
+ if (isShortAnswer && !hidePrompt) {
2006
+ return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
2007
+ }
2008
+ return {
2009
+ has: false,
2010
+ text: "",
2011
+ rich_text: "",
2012
+ isTextEqualToRichText: false
2013
+ };
2014
+ }
2015
+
2016
+ // src/domains/cards/utils/get-completed-pages.ts
2017
+ var getTotalCompletedCards = (pageScores) => {
2018
+ return Object.values(pageScores != null ? pageScores : {}).reduce((acc, cardScore) => {
2019
+ var _a, _b;
2020
+ if (((_b = (_a = cardScore.completed) != null ? _a : cardScore.score) != null ? _b : cardScore.score === 0) && !cardScore.media_area_opened) {
2021
+ acc++;
2022
+ }
2023
+ return acc;
2024
+ }, 0);
2025
+ };
2026
+
2027
+ // src/domains/cards/utils/get-label-page.ts
2028
+ var labels = {
2029
+ repeat: {
2030
+ short: "Repeat",
2031
+ long: "Listen & Repeat"
2032
+ },
2033
+ mc: {
2034
+ short: "Multiple Choice",
2035
+ long: "Multiple Choice"
2036
+ },
2037
+ mediaPage: {
2038
+ short: "Media Page",
2039
+ long: "Media Page"
2040
+ },
2041
+ shortAnswer: {
2042
+ short: "Short Answer",
2043
+ long: "Short Answer"
2044
+ },
2045
+ respondWritten: {
2046
+ short: "Open Response",
2047
+ long: "Written Open Response"
2048
+ },
2049
+ respondAudio: {
2050
+ short: "Open Response",
2051
+ long: "Spoken Open Response"
2052
+ }
2053
+ };
2054
+ var getLabelPage = (pageType) => {
2055
+ if (!pageType) {
2056
+ return {
2057
+ short: "",
2058
+ long: ""
2059
+ };
2060
+ }
2061
+ const { isRepeat, isMC, isMediaPage, isShortAnswer, isRespondWritten, isRespondAudio } = checkTypePageActivity(pageType);
2062
+ if (isRepeat) {
2063
+ return labels.repeat;
2064
+ }
2065
+ if (isMC) {
2066
+ return labels.mc;
2067
+ }
2068
+ if (isMediaPage) {
2069
+ return labels.mediaPage;
2070
+ }
2071
+ if (isShortAnswer) {
2072
+ return labels.shortAnswer;
2073
+ }
2074
+ if (isRespondWritten) {
2075
+ return labels.respondWritten;
2076
+ }
2077
+ if (isRespondAudio) {
2078
+ return labels.respondAudio;
2079
+ }
2080
+ return {
2081
+ short: "",
2082
+ long: ""
2083
+ };
2084
+ };
2085
+
2086
+ // src/domains/sets/set.hooks.ts
1014
2087
  import { useQuery as useQuery4 } from "@tanstack/react-query";
1015
2088
 
1016
2089
  // src/domains/sets/set.constants.ts
@@ -1027,6 +2100,1264 @@ async function _getSet({ setId }) {
1027
2100
  }
1028
2101
  var getSet = withErrorHandler(_getSet, "getSet");
1029
2102
 
2103
+ // src/domains/sets/set.hooks.ts
2104
+ var setsQueryKeys = {
2105
+ all: ["sets"],
2106
+ one: (params) => [...setsQueryKeys.all, params.setId]
2107
+ };
2108
+ var useSet = ({ setId, enabled }) => {
2109
+ return useQuery4({
2110
+ queryKey: setsQueryKeys.one({ setId }),
2111
+ queryFn: () => getSet({ setId }),
2112
+ enabled: setId !== void 0 && setId !== "" && enabled
2113
+ });
2114
+ };
2115
+ function getSetFromCache({
2116
+ setId,
2117
+ queryClient
2118
+ }) {
2119
+ if (!setId) return null;
2120
+ return queryClient.getQueryData(setsQueryKeys.one({ setId }));
2121
+ }
2122
+ function updateSetInCache({
2123
+ set,
2124
+ queryClient
2125
+ }) {
2126
+ const { id, ...setData } = set;
2127
+ queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
2128
+ }
2129
+
2130
+ // src/domains/sets/set.repo.ts
2131
+ var createSetRepo = () => {
2132
+ return {
2133
+ getSet
2134
+ };
2135
+ };
2136
+
2137
+ // src/utils/ai/get-transcript.ts
2138
+ async function getTranscript(model, args) {
2139
+ var _a, _b, _c, _d;
2140
+ const getGeminiTranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "getGeminiTranscript");
2141
+ const getAssemblyAITranscript = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "transcribeAssemblyAIAudio");
2142
+ if (model === "gemini") {
2143
+ try {
2144
+ const { data } = await (getGeminiTranscript == null ? void 0 : getGeminiTranscript({
2145
+ audioUrl: args.audioUrl,
2146
+ targetLanguage: args.language,
2147
+ prompt: args.prompt
2148
+ }));
2149
+ return data.transcript;
2150
+ } catch (error) {
2151
+ console.error("Error getting transcript from Gemini:", error);
2152
+ throw error;
2153
+ }
2154
+ }
2155
+ if (model === "assemblyai") {
2156
+ try {
2157
+ const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
2158
+ audioUrl: args.audioUrl,
2159
+ language: args.language
2160
+ }));
2161
+ return response.data;
2162
+ } catch (error) {
2163
+ console.error("Error getting transcript from AssemblyAI:", error);
2164
+ throw error;
2165
+ }
2166
+ }
2167
+ return null;
2168
+ }
2169
+
2170
+ // src/constants/all-langs.json
2171
+ var all_langs_default = {
2172
+ af: "Afrikaans",
2173
+ sq: "Albanian",
2174
+ am: "Amharic",
2175
+ ar: "Arabic",
2176
+ hy: "Armenian",
2177
+ az: "Azerbaijani",
2178
+ eu: "Basque",
2179
+ be: "Belarusian",
2180
+ bn: "Bengali",
2181
+ bs: "Bosnian",
2182
+ bg: "Bulgarian",
2183
+ ca: "Catalan",
2184
+ ceb: "Cebuano",
2185
+ zh: "Chinese",
2186
+ co: "Corsican",
2187
+ hr: "Croatian",
2188
+ cs: "Czech",
2189
+ da: "Danish",
2190
+ nl: "Dutch",
2191
+ en: "English",
2192
+ eo: "Esperanto",
2193
+ et: "Estonian",
2194
+ fi: "Finnish",
2195
+ fr: "French",
2196
+ fy: "Frisian",
2197
+ gl: "Galician",
2198
+ ka: "Georgian",
2199
+ de: "German",
2200
+ el: "Greek",
2201
+ gu: "Gujarati",
2202
+ ht: "Haitian Creole",
2203
+ ha: "Hausa",
2204
+ haw: "Hawaiian",
2205
+ he: "Hebrew",
2206
+ hi: "Hindi",
2207
+ hmn: "Hmong",
2208
+ hu: "Hungarian",
2209
+ is: "Icelandic",
2210
+ ig: "Igbo",
2211
+ id: "Indonesian",
2212
+ ga: "Irish",
2213
+ it: "Italian",
2214
+ ja: "Japanese",
2215
+ jv: "Javanese",
2216
+ kn: "Kannada",
2217
+ kk: "Kazakh",
2218
+ km: "Khmer",
2219
+ ko: "Korean",
2220
+ ku: "Kurdish",
2221
+ ky: "Kyrgyz",
2222
+ lo: "Lao",
2223
+ la: "Latin",
2224
+ lv: "Latvian",
2225
+ lt: "Lithuanian",
2226
+ lb: "Luxembourgish",
2227
+ mk: "Macedonian",
2228
+ mg: "Malagasy",
2229
+ ms: "Malay",
2230
+ ml: "Malayalam",
2231
+ mt: "Maltese",
2232
+ mi: "Maori",
2233
+ mr: "Marathi",
2234
+ mn: "Mongolian",
2235
+ my: "Myanmar (Burmese)",
2236
+ ne: "Nepali",
2237
+ no: "Norwegian",
2238
+ ny: "Nyanja (Chichewa)",
2239
+ ps: "Pashto",
2240
+ fa: "Persian",
2241
+ pl: "Polish",
2242
+ pt: "Portuguese",
2243
+ pa: "Punjabi",
2244
+ ro: "Romanian",
2245
+ ru: "Russian",
2246
+ sm: "Samoan",
2247
+ gd: "Scots Gaelic",
2248
+ sr: "Serbian",
2249
+ st: "Sesotho",
2250
+ sn: "Shona",
2251
+ sd: "Sindhi",
2252
+ si: "Sinhala (Sinhalese)",
2253
+ sk: "Slovak",
2254
+ sl: "Slovenian",
2255
+ so: "Somali",
2256
+ es: "Spanish",
2257
+ su: "Sundanese",
2258
+ sw: "Swahili",
2259
+ sv: "Swedish",
2260
+ tl: "Tagalog (Filipino)",
2261
+ tg: "Tajik",
2262
+ ta: "Tamil",
2263
+ te: "Telugu",
2264
+ th: "Thai",
2265
+ tr: "Turkish",
2266
+ uk: "Ukrainian",
2267
+ ur: "Urdu",
2268
+ uz: "Uzbek",
2269
+ vi: "Vietnamese",
2270
+ cy: "Welsh",
2271
+ xh: "Xhosa",
2272
+ yi: "Yiddish",
2273
+ yo: "Yoruba",
2274
+ zu: "Zulu"
2275
+ };
2276
+
2277
+ // src/utils/ai/get-respond-card-tool.ts
2278
+ var getRespondCardTool = ({
2279
+ language,
2280
+ standard = "actfl"
2281
+ }) => {
2282
+ const lang = all_langs_default[language] || "English";
2283
+ const tool = {
2284
+ tool_choice: {
2285
+ type: "function",
2286
+ function: { name: "get_feedback" }
2287
+ },
2288
+ tools: [
2289
+ {
2290
+ type: "function",
2291
+ function: {
2292
+ name: "get_feedback",
2293
+ description: "Get feedback on a student's response",
2294
+ parameters: {
2295
+ type: "object",
2296
+ required: [
2297
+ "success",
2298
+ "score",
2299
+ "score_justification",
2300
+ "errors",
2301
+ "improvedResponse",
2302
+ "compliments"
2303
+ ],
2304
+ properties: {
2305
+ success: {
2306
+ type: "boolean",
2307
+ 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."
2308
+ },
2309
+ errors: {
2310
+ type: "array",
2311
+ items: {
2312
+ type: "object",
2313
+ required: ["error", "grammar_error_type", "correction", "justification"],
2314
+ properties: {
2315
+ error: {
2316
+ type: "string",
2317
+ description: "The grammatical error in the student's response."
2318
+ },
2319
+ correction: {
2320
+ type: "string",
2321
+ description: "The suggested correction to the error"
2322
+ },
2323
+ justification: {
2324
+ type: "string",
2325
+ description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
2326
+ },
2327
+ grammar_error_type: {
2328
+ type: "string",
2329
+ enum: [
2330
+ "subjVerbAgree",
2331
+ "tenseErrors",
2332
+ "articleMisuse",
2333
+ "prepositionErrors",
2334
+ "adjNounAgree",
2335
+ "pronounErrors",
2336
+ "wordOrder",
2337
+ "verbConjugation",
2338
+ "pluralization",
2339
+ "negationErrors",
2340
+ "modalVerbMisuse",
2341
+ "relativeClause",
2342
+ "auxiliaryVerb",
2343
+ "complexSentenceAgreement",
2344
+ "idiomaticExpression",
2345
+ "registerInconsistency",
2346
+ "voiceMisuse"
2347
+ ],
2348
+ 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"
2349
+ }
2350
+ }
2351
+ },
2352
+ 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."
2353
+ },
2354
+ compliments: {
2355
+ type: "array",
2356
+ items: {
2357
+ type: "string"
2358
+ },
2359
+ description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
2360
+ },
2361
+ improvedResponse: {
2362
+ type: "string",
2363
+ description: "An improved response with proper grammar and more detail, if applicable."
2364
+ },
2365
+ score: {
2366
+ type: "number",
2367
+ description: "A score between 0 and 100, reflecting the overall quality of the response"
2368
+ },
2369
+ score_justification: {
2370
+ type: "string",
2371
+ description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
2372
+ }
2373
+ }
2374
+ }
2375
+ }
2376
+ }
2377
+ ]
2378
+ };
2379
+ if (standard === "wida") {
2380
+ const wida_level = {
2381
+ type: "number",
2382
+ enum: [1, 2, 3, 4, 5, 6],
2383
+ 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
2384
+
2385
+ 1 - Entering
2386
+ 2 - Emerging
2387
+ 3 - Developing
2388
+ 4 - Expanding
2389
+ 5 - Bridging
2390
+ 6 - Reaching
2391
+
2392
+ 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.
2393
+ `
2394
+ };
2395
+ const wida_justification = {
2396
+ type: "string",
2397
+ description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
2398
+ };
2399
+ tool.tools[0].function.parameters.required.push("wida_level");
2400
+ tool.tools[0].function.parameters.required.push("wida_justification");
2401
+ tool.tools[0].function.parameters.properties.wida_level = wida_level;
2402
+ tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
2403
+ } else {
2404
+ const actfl_level = {
2405
+ type: "string",
2406
+ enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
2407
+ 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"
2408
+ };
2409
+ const actfl_justification = {
2410
+ type: "string",
2411
+ description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
2412
+ };
2413
+ tool.tools[0].function.parameters.required.push("actfl_level");
2414
+ tool.tools[0].function.parameters.required.push("actfl_justification");
2415
+ tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
2416
+ tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
2417
+ }
2418
+ return tool;
2419
+ };
2420
+
2421
+ // src/hooks/useActivity.ts
2422
+ import { useEffect as useEffect2 } from "react";
2423
+
2424
+ // src/services/add-grading-standard.ts
2425
+ var addGradingStandardLog = async (gradingStandard, userId) => {
2426
+ logGradingStandardLog(gradingStandard);
2427
+ const path = `users/${userId}/grading_standard_logs`;
2428
+ await api.addDoc(path, gradingStandard);
2429
+ };
2430
+
2431
+ // src/hooks/useActivityTracker.ts
2432
+ import { v4 as v42 } from "uuid";
2433
+ function useActivityTracker({ userId }) {
2434
+ const trackActivity = async ({
2435
+ activityName,
2436
+ activityType,
2437
+ id = v42(),
2438
+ language = ""
2439
+ }) => {
2440
+ if (userId) {
2441
+ const { doc: doc2, serverTimestamp: serverTimestamp2, setDoc: setDoc2 } = api.accessHelpers();
2442
+ const activityRef = doc2(`users/${userId}/activity/${id}`);
2443
+ const timestamp = serverTimestamp2();
2444
+ await setDoc2(activityRef, {
2445
+ name: activityName,
2446
+ type: activityType,
2447
+ lastSeen: timestamp,
2448
+ id,
2449
+ language
2450
+ });
2451
+ }
2452
+ };
2453
+ return {
2454
+ trackActivity
2455
+ };
2456
+ }
2457
+
2458
+ // src/hooks/useActivity.ts
2459
+ function useActivity({
2460
+ id,
2461
+ isAssignment,
2462
+ onAssignmentSubmitted,
2463
+ ltiData
2464
+ }) {
2465
+ var _a, _b;
2466
+ const { queryClient, user } = useSpeakableApi();
2467
+ const userId = user.auth.uid;
2468
+ const assignmentQuery = useAssignment({
2469
+ assignmentId: id,
2470
+ userId,
2471
+ enabled: isAssignment
2472
+ });
2473
+ const activeAssignment = assignmentQuery.data;
2474
+ const setId = isAssignment ? (_a = activeAssignment == null ? void 0 : activeAssignment.setId) != null ? _a : "" : id;
2475
+ const querySet = useSet({ setId });
2476
+ const setData = querySet.data;
2477
+ const assignmentContent = activeAssignment == null ? void 0 : activeAssignment.content;
2478
+ const assignmentWeights = activeAssignment == null ? void 0 : activeAssignment.weights;
2479
+ const setContent = setData == null ? void 0 : setData.content;
2480
+ const setWeights = setData == null ? void 0 : setData.weights;
2481
+ const contentCardsToUse = isAssignment ? assignmentContent != null ? assignmentContent : setContent : setContent;
2482
+ const weightsToUse = isAssignment ? assignmentWeights != null ? assignmentWeights : setWeights : setWeights;
2483
+ const activityId = isAssignment ? (_b = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b : "" : setId;
2484
+ const { cardsObject, cardsQueries, cards } = useCards({
2485
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2486
+ enabled: querySet.isSuccess,
2487
+ asObject: true
2488
+ });
2489
+ const scorableCardIds = (contentCardsToUse != null ? contentCardsToUse : []).filter((cardId) => {
2490
+ const card = cardsObject == null ? void 0 : cardsObject[cardId];
2491
+ return (card == null ? void 0 : card.type) !== "MEDIA_PAGE" /* MEDIA_PAGE */;
2492
+ });
2493
+ const scoreQuery = useScore({
2494
+ isAssignment,
2495
+ activityId: id,
2496
+ userId,
2497
+ courseId: activeAssignment == null ? void 0 : activeAssignment.courseId,
2498
+ googleClassroomUserId: user.profile.googleClassroomUserId,
2499
+ enabled: isAssignment ? assignmentQuery.isSuccess : querySet.isSuccess
2500
+ });
2501
+ const { mutationUpdateScore } = useUpdateScore();
2502
+ const { mutationUpdateCardScore } = useUpdateCardScore({
2503
+ activityId,
2504
+ isAssignment,
2505
+ userId,
2506
+ cardIds: scorableCardIds,
2507
+ weights: weightsToUse != null ? weightsToUse : {}
2508
+ });
2509
+ const { mutationClearScore } = useClearScore();
2510
+ const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
2511
+ onAssignmentSubmitted,
2512
+ studentName: user.profile.displayName
2513
+ });
2514
+ const { submitPracticeScore: submitPracticeScore2 } = useSubmitPracticeScore();
2515
+ const handleUpdateScore = (data) => {
2516
+ mutationUpdateScore.mutate({
2517
+ data,
2518
+ isAssignment,
2519
+ activityId,
2520
+ userId
2521
+ });
2522
+ };
2523
+ const handleUpdateCardScore = (cardId, cardScore) => {
2524
+ mutationUpdateCardScore.mutate({ cardId, cardScore });
2525
+ if (cardScore.proficiency_level) {
2526
+ logGradingStandardEntry({
2527
+ type: cardScore.proficiency_level.standardId,
2528
+ cardId,
2529
+ gradingStandard: cardScore.proficiency_level
2530
+ });
2531
+ } else if (cardScore.wida || cardScore.actfl) {
2532
+ logGradingStandardEntry({
2533
+ type: cardScore.wida ? "wida" : "actfl",
2534
+ cardId,
2535
+ gradingStandard: cardScore.wida || cardScore.actfl || { level: "", justification: "" }
2536
+ });
2537
+ }
2538
+ };
2539
+ const onClearScore = ({
2540
+ cardId,
2541
+ wasCompleted = true
2542
+ }) => {
2543
+ var _a2, _b2;
2544
+ const currentCard = cardsObject == null ? void 0 : cardsObject[cardId];
2545
+ if ((currentCard == null ? void 0 : currentCard.type) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || (currentCard == null ? void 0 : currentCard.type) === "READ_REPEAT" /* READ_REPEAT */) {
2546
+ return;
2547
+ }
2548
+ const queryKeys = scoreQueryKeys.byId(activityId);
2549
+ const activeCardScores = (_b2 = (_a2 = queryClient.getQueryData(queryKeys)) == null ? void 0 : _a2.cards) == null ? void 0 : _b2[cardId];
2550
+ if (activeCardScores === void 0) return;
2551
+ mutationClearScore.mutate({
2552
+ isAssignment,
2553
+ activityId,
2554
+ cardScores: activeCardScores,
2555
+ cardId,
2556
+ userId
2557
+ });
2558
+ };
2559
+ const onSubmitScore = async () => {
2560
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w;
2561
+ try {
2562
+ let results;
2563
+ if (isAssignment) {
2564
+ const someCardIsManualGraded = cards.some((page) => page.grading_method === "manual");
2565
+ results = await submitAssignmentScore2({
2566
+ assignment: {
2567
+ id: (_b2 = (_a2 = assignmentQuery.data) == null ? void 0 : _a2.id) != null ? _b2 : "",
2568
+ name: (_d = (_c = assignmentQuery.data) == null ? void 0 : _c.name) != null ? _d : "",
2569
+ owners: (_f = (_e = assignmentQuery.data) == null ? void 0 : _e.owners) != null ? _f : [],
2570
+ courseId: (_h = (_g = assignmentQuery.data) == null ? void 0 : _g.courseId) != null ? _h : "",
2571
+ courseWorkId: (_j = (_i = assignmentQuery.data) == null ? void 0 : _i.courseWorkId) != null ? _j : "",
2572
+ isAssessment: (_l = (_k = assignmentQuery.data) == null ? void 0 : _k.isAssessment) != null ? _l : false,
2573
+ maxPoints: (_n = (_m = assignmentQuery.data) == null ? void 0 : _m.maxPoints) != null ? _n : 0
2574
+ },
2575
+ userId,
2576
+ cardIds: scorableCardIds,
2577
+ scores: scoreQuery.data,
2578
+ weights: weightsToUse != null ? weightsToUse : {},
2579
+ status: someCardIsManualGraded ? "PENDING_REVIEW" : "SUBMITTED"
2580
+ });
2581
+ if ((_o = assignmentQuery.data) == null ? void 0 : _o.ltiDeeplink) {
2582
+ submitLTIScore({
2583
+ maxPoints: (_p = assignmentQuery.data) == null ? void 0 : _p.maxPoints,
2584
+ score: (_r = (_q = scoreQuery.data) == null ? void 0 : _q.score) != null ? _r : 0,
2585
+ SERVICE_KEY: (_s = ltiData == null ? void 0 : ltiData.serviceKey) != null ? _s : "",
2586
+ lineItemId: (_t = ltiData == null ? void 0 : ltiData.lineItemId) != null ? _t : "",
2587
+ lti_id: (_u = ltiData == null ? void 0 : ltiData.lti_id) != null ? _u : ""
2588
+ });
2589
+ }
2590
+ } else {
2591
+ results = await submitPracticeScore2({
2592
+ setId: (_w = (_v = querySet.data) == null ? void 0 : _v.id) != null ? _w : "",
2593
+ userId,
2594
+ scores: scoreQuery.data
2595
+ });
2596
+ }
2597
+ return results;
2598
+ } catch (error) {
2599
+ return {
2600
+ success: false,
2601
+ error
2602
+ };
2603
+ }
2604
+ };
2605
+ const logGradingStandardEntry = ({
2606
+ cardId,
2607
+ gradingStandard,
2608
+ type
2609
+ }) => {
2610
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i;
2611
+ const card = cardsObject == null ? void 0 : cardsObject[cardId];
2612
+ const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
2613
+ const cardScore = (_a2 = scoresObject == null ? void 0 : scoresObject.cards) == null ? void 0 : _a2[cardId];
2614
+ const serverTimestamp2 = api.helpers.serverTimestamp;
2615
+ addGradingStandardLog(
2616
+ {
2617
+ assignmentId: (_b2 = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b2 : "",
2618
+ courseId: (_c = activeAssignment == null ? void 0 : activeAssignment.courseId) != null ? _c : "",
2619
+ teacherId: (_d = activeAssignment == null ? void 0 : activeAssignment.owners[0]) != null ? _d : "",
2620
+ setId: (_e = setData == null ? void 0 : setData.id) != null ? _e : "",
2621
+ cardId,
2622
+ level: gradingStandard.level,
2623
+ justification: gradingStandard.justification,
2624
+ transcript: (_f = cardScore == null ? void 0 : cardScore.transcript) != null ? _f : "",
2625
+ audioUrl: (_g = cardScore == null ? void 0 : cardScore.audio) != null ? _g : "",
2626
+ prompt: (_h = card == null ? void 0 : card.prompt) != null ? _h : "",
2627
+ responseType: (card == null ? void 0 : card.type) === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
2628
+ type,
2629
+ dateMade: serverTimestamp2(),
2630
+ language: (_i = card == null ? void 0 : card.language) != null ? _i : ""
2631
+ },
2632
+ userId
2633
+ );
2634
+ };
2635
+ useEffect2(() => {
2636
+ if (isAssignment) {
2637
+ logOpenAssignment({ assignmentId: id });
2638
+ } else {
2639
+ logOpenActivityPreview({ setId: id });
2640
+ }
2641
+ }, []);
2642
+ useInitActivity({
2643
+ assignment: activeAssignment != null ? activeAssignment : void 0,
2644
+ set: setData != null ? setData : void 0,
2645
+ enabled: !!setData,
2646
+ userId
2647
+ });
2648
+ return {
2649
+ set: {
2650
+ data: setData,
2651
+ query: querySet
2652
+ },
2653
+ cards: {
2654
+ data: cardsObject,
2655
+ query: cardsQueries,
2656
+ cardsArray: cards
2657
+ },
2658
+ assignment: {
2659
+ data: isAssignment ? activeAssignment : void 0,
2660
+ query: assignmentQuery
2661
+ },
2662
+ scores: {
2663
+ data: scoreQuery.data,
2664
+ query: scoreQuery,
2665
+ actions: {
2666
+ update: handleUpdateScore,
2667
+ clear: onClearScore,
2668
+ submit: onSubmitScore,
2669
+ updateCard: handleUpdateCardScore,
2670
+ logGradingStandardEntry
2671
+ }
2672
+ }
2673
+ };
2674
+ }
2675
+ var useInitActivity = ({
2676
+ assignment,
2677
+ set,
2678
+ enabled,
2679
+ userId
2680
+ }) => {
2681
+ const { trackActivity } = useActivityTracker({ userId });
2682
+ const init = () => {
2683
+ var _a, _b, _c, _d, _e, _f, _g;
2684
+ if (!enabled) return;
2685
+ if (!assignment) {
2686
+ trackActivity({
2687
+ activityName: (_a = set == null ? void 0 : set.name) != null ? _a : "",
2688
+ activityType: "set",
2689
+ id: set == null ? void 0 : set.id,
2690
+ language: set == null ? void 0 : set.language
2691
+ });
2692
+ } else if (assignment.name) {
2693
+ trackActivity({
2694
+ activityName: assignment.name,
2695
+ activityType: assignment.isAssessment ? "assessment" : "assignment",
2696
+ id: assignment.id,
2697
+ language: set == null ? void 0 : set.language
2698
+ });
2699
+ }
2700
+ if (set == null ? void 0 : set.public) {
2701
+ (_d = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "onSetOpened")) == null ? void 0 : _d({
2702
+ setId: set.id,
2703
+ language: set.language
2704
+ });
2705
+ }
2706
+ (_g = (_f = (_e = api).httpsCallable) == null ? void 0 : _f.call(_e, "updateAlgoliaIndex")) == null ? void 0 : _g({
2707
+ updatePlays: true,
2708
+ objectID: set == null ? void 0 : set.id
2709
+ });
2710
+ };
2711
+ useEffect2(() => {
2712
+ init();
2713
+ }, [set]);
2714
+ };
2715
+ var submitLTIScore = async ({
2716
+ maxPoints,
2717
+ score,
2718
+ SERVICE_KEY,
2719
+ lineItemId,
2720
+ lti_id
2721
+ }) => {
2722
+ var _a, _b, _c;
2723
+ try {
2724
+ if (!SERVICE_KEY || !lineItemId || !lti_id) {
2725
+ throw new Error("Missing required LTI credentials");
2726
+ }
2727
+ const earnedPoints = score ? score / 100 * maxPoints : 0;
2728
+ const { data } = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitLTIAssignmentScore")) == null ? void 0 : _c({
2729
+ SERVICE_KEY,
2730
+ scoreData: {
2731
+ lineItemId,
2732
+ userId: lti_id,
2733
+ maxPoints,
2734
+ earnedPoints
2735
+ }
2736
+ }));
2737
+ return { success: true, data };
2738
+ } catch (error) {
2739
+ console.error("Failed to submit LTI score:", error);
2740
+ return {
2741
+ success: false,
2742
+ error: error instanceof Error ? error : new Error("Unknown error occurred")
2743
+ };
2744
+ }
2745
+ };
2746
+
2747
+ // src/hooks/useCredits.ts
2748
+ import { useQuery as useQuery5 } from "@tanstack/react-query";
2749
+ var creditQueryKeys = {
2750
+ userCredits: (uid) => ["userCredits", uid]
2751
+ };
2752
+ var useUserCredits = () => {
2753
+ const { user } = useSpeakableApi();
2754
+ const email = user.auth.email;
2755
+ const uid = user.auth.uid;
2756
+ const query2 = useQuery5({
2757
+ queryKey: creditQueryKeys.userCredits(uid),
2758
+ queryFn: () => fetchUserCredits({ uid, email }),
2759
+ enabled: !!uid,
2760
+ refetchInterval: 1e3 * 60 * 5
2761
+ });
2762
+ return {
2763
+ ...query2
2764
+ };
2765
+ };
2766
+ var fetchUserCredits = async ({ uid, email }) => {
2767
+ if (!uid) {
2768
+ throw new Error("User ID is required");
2769
+ }
2770
+ const contractSnap = await api.getDoc(`creditContracts/${uid}`);
2771
+ if (contractSnap.data == null) {
2772
+ return {
2773
+ id: uid,
2774
+ userId: uid,
2775
+ email,
2776
+ effectivePlanId: "free_tier",
2777
+ status: "inactive",
2778
+ isUnlimited: false,
2779
+ creditsAvailable: 100,
2780
+ creditsAllocatedThisPeriod: 100,
2781
+ topOffCreditsAvailable: 0,
2782
+ topOffCreditsTotal: 0,
2783
+ allocationSource: "free_tier",
2784
+ sourceDetails: {},
2785
+ periodStart: null,
2786
+ periodEnd: null,
2787
+ planTermEndTimestamp: null,
2788
+ ownerType: "individual",
2789
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2790
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
2791
+ };
2792
+ }
2793
+ const contractData = contractSnap.data;
2794
+ const monthlyCredits = (contractData == null ? void 0 : contractData.creditsAvailable) || 0;
2795
+ const topOffCredits = (contractData == null ? void 0 : contractData.topOffCreditsAvailable) || 0;
2796
+ const totalCredits = monthlyCredits + topOffCredits;
2797
+ return {
2798
+ id: contractSnap.id,
2799
+ ...contractData,
2800
+ // Add computed total for convenience
2801
+ totalCreditsAvailable: totalCredits
2802
+ };
2803
+ };
2804
+
2805
+ // src/hooks/useOrganizationAccess.ts
2806
+ import { useQuery as useQuery6 } from "@tanstack/react-query";
2807
+ var useOrganizationAccess = () => {
2808
+ const { user } = useSpeakableApi();
2809
+ const email = user.auth.email;
2810
+ const query2 = useQuery6({
2811
+ queryKey: ["organizationAccess", email],
2812
+ queryFn: async () => {
2813
+ if (!email) {
2814
+ return {
2815
+ hasUnlimitedAccess: false,
2816
+ subscriptionId: null,
2817
+ organizationId: null,
2818
+ organizationName: null,
2819
+ subscriptionEndDate: null,
2820
+ accessType: "individual"
2821
+ };
2822
+ }
2823
+ return getOrganizationAccess(email);
2824
+ },
2825
+ enabled: !!email,
2826
+ // Only run query if we have a user email
2827
+ staleTime: 5 * 60 * 1e3,
2828
+ // Consider data fresh for 5 minutes
2829
+ gcTime: 10 * 60 * 1e3,
2830
+ // Keep in cache for 10 minutes
2831
+ retry: 2
2832
+ // Retry failed requests twice
2833
+ });
2834
+ return {
2835
+ ...query2
2836
+ };
2837
+ };
2838
+ var getOrganizationAccess = async (email) => {
2839
+ const { limit: limit2, where: where2 } = api.accessQueryConstraints();
2840
+ try {
2841
+ const organizationSnapshot = await api.getDocs(
2842
+ "organizations",
2843
+ where2("members", "array-contains", email),
2844
+ where2("masterSubscriptionStatus", "==", "active"),
2845
+ limit2(1)
2846
+ );
2847
+ if (!organizationSnapshot.empty) {
2848
+ const orgData = organizationSnapshot.data[0];
2849
+ return {
2850
+ hasUnlimitedAccess: true,
2851
+ subscriptionId: orgData == null ? void 0 : orgData.masterSubscriptionId,
2852
+ organizationId: orgData.id,
2853
+ organizationName: orgData.name || "Unknown Organization",
2854
+ subscriptionEndDate: orgData.masterSubscriptionEndDate || null,
2855
+ accessType: "organization"
2856
+ };
2857
+ }
2858
+ const institutionSnapshot = await api.getDocs(
2859
+ "institution_subscriptions",
2860
+ where2("users", "array-contains", email),
2861
+ where2("active", "==", true),
2862
+ limit2(1)
2863
+ );
2864
+ if (!institutionSnapshot.empty) {
2865
+ const institutionData = institutionSnapshot.data[0];
2866
+ const isUnlimited = (institutionData == null ? void 0 : institutionData.plan) === "organization" || (institutionData == null ? void 0 : institutionData.plan) === "school_starter";
2867
+ return {
2868
+ hasUnlimitedAccess: isUnlimited,
2869
+ subscriptionId: institutionData.id,
2870
+ organizationId: institutionData == null ? void 0 : institutionData.institutionId,
2871
+ organizationName: institutionData.name || institutionData.institutionId || "Legacy Institution",
2872
+ subscriptionEndDate: institutionData.endDate || null,
2873
+ accessType: "institution_subscriptions"
2874
+ };
2875
+ }
2876
+ return {
2877
+ hasUnlimitedAccess: false,
2878
+ subscriptionId: null,
2879
+ organizationId: null,
2880
+ organizationName: null,
2881
+ subscriptionEndDate: null,
2882
+ accessType: "individual"
2883
+ };
2884
+ } catch (error) {
2885
+ console.error("Error checking organization access:", error);
2886
+ return {
2887
+ hasUnlimitedAccess: false,
2888
+ subscriptionId: null,
2889
+ organizationId: null,
2890
+ organizationName: null,
2891
+ subscriptionEndDate: null,
2892
+ accessType: "individual"
2893
+ };
2894
+ }
2895
+ };
2896
+
2897
+ // src/hooks/useSpeakableTranscript.ts
2898
+ import { useMutation as useMutation3 } from "@tanstack/react-query";
2899
+ function useSpeakableTranscript() {
2900
+ const mutation = useMutation3({
2901
+ mutationFn: async ({
2902
+ model,
2903
+ audioUrl,
2904
+ language,
2905
+ prompt
2906
+ }) => {
2907
+ return getTranscript(model, { audioUrl, language, prompt });
2908
+ },
2909
+ retry: false
2910
+ });
2911
+ return {
2912
+ mutation
2913
+ };
2914
+ }
2915
+
2916
+ // src/hooks/useUpdateStudentVoc.ts
2917
+ var useUpdateStudentVocab = (page) => {
2918
+ const { user } = useSpeakableApi();
2919
+ const currentUserId = user == null ? void 0 : user.auth.uid;
2920
+ if (!page || !currentUserId || !page.target_text || !page.language) {
2921
+ return {
2922
+ studentVocabMarkVoiceSuccess: void 0,
2923
+ studentVocabMarkVoiceFail: void 0
2924
+ };
2925
+ }
2926
+ const getDataObject = () => {
2927
+ var _a, _b;
2928
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2929
+ const language = (_a = page.language) != null ? _a : "en";
2930
+ const word = (_b = page.target_text) != null ? _b : "";
2931
+ const phrase_length = getPhraseLength(word);
2932
+ const wordHash = getWordHash(word, language);
2933
+ const docPath = `users/${currentUserId}/vocab/${wordHash}`;
2934
+ const communityPath = `checked-pronunciations/${wordHash}`;
2935
+ const id = `${language}-${cleanString(word)}`;
2936
+ const data = {
2937
+ id,
2938
+ word,
2939
+ words: (word == null ? void 0 : word.split(" ")) || [],
2940
+ wordHash,
2941
+ language,
2942
+ lastSeen: serverTimestamp2(),
2943
+ phrase_length
2944
+ };
2945
+ return {
2946
+ docPath,
2947
+ communityPath,
2948
+ data
2949
+ };
2950
+ };
2951
+ const markVoiceSuccess = async () => {
2952
+ const { docPath, communityPath, data } = getDataObject();
2953
+ const { increment: increment2 } = api.accessQueryConstraints();
2954
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2955
+ data.voiceSuccess = increment2(1);
2956
+ try {
2957
+ await api.updateDoc(docPath, data);
2958
+ } catch (error) {
2959
+ if (error instanceof Error && error.message === "not-found") {
2960
+ data.firstSeen = serverTimestamp2();
2961
+ await api.setDoc(docPath, data, { merge: true });
2962
+ } else {
2963
+ console.log(error);
2964
+ }
2965
+ }
2966
+ try {
2967
+ data.pronunciations = increment2(1);
2968
+ await api.setDoc(communityPath, data, { merge: true });
2969
+ } catch (error) {
2970
+ console.log(error);
2971
+ }
2972
+ };
2973
+ const markVoiceFail = async () => {
2974
+ const { docPath, communityPath, data } = getDataObject();
2975
+ const { increment: increment2 } = api.accessQueryConstraints();
2976
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2977
+ data.voiceFail = increment2(1);
2978
+ try {
2979
+ await api.updateDoc(docPath, data);
2980
+ } catch (error) {
2981
+ if (error instanceof Error && error.message === "not-found") {
2982
+ data.firstSeen = serverTimestamp2();
2983
+ await api.setDoc(docPath, data, { merge: true });
2984
+ } else {
2985
+ console.log(error);
2986
+ }
2987
+ }
2988
+ try {
2989
+ data.fails = increment2(1);
2990
+ await api.setDoc(communityPath, data, { merge: true });
2991
+ } catch (error) {
2992
+ console.log(error);
2993
+ }
2994
+ };
2995
+ return {
2996
+ studentVocabMarkVoiceSuccess: markVoiceSuccess,
2997
+ studentVocabMarkVoiceFail: markVoiceFail
2998
+ };
2999
+ };
3000
+
3001
+ // src/hooks/useActivityFeedbackAccess.ts
3002
+ import { useQuery as useQuery7 } from "@tanstack/react-query";
3003
+ var activityFeedbackAccessQueryKeys = {
3004
+ activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
3005
+ };
3006
+ var useActivityFeedbackAccess = ({
3007
+ aiEnabled = false,
3008
+ isActivityRoute = false
3009
+ }) => {
3010
+ var _a, _b, _c;
3011
+ const { user } = useSpeakableApi();
3012
+ const uid = user.auth.uid;
3013
+ const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
3014
+ const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
3015
+ const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
3016
+ const query2 = useQuery7({
3017
+ queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
3018
+ aiEnabled,
3019
+ isActivityRoute
3020
+ }),
3021
+ queryFn: async () => {
3022
+ var _a2, _b2, _c2;
3023
+ if (!uid) {
3024
+ return {
3025
+ canAccessFeedback: false,
3026
+ reason: "Missing user ID",
3027
+ isUnlimited: false,
3028
+ accessType: "none"
3029
+ };
3030
+ }
3031
+ try {
3032
+ if (aiEnabled) {
3033
+ return {
3034
+ canAccessFeedback: true,
3035
+ reason: "AI feedback enabled",
3036
+ isUnlimited: true,
3037
+ accessType: "ai_enabled"
3038
+ };
3039
+ }
3040
+ if (isTeacher || userRoles.includes("ADMIN")) {
3041
+ return {
3042
+ canAccessFeedback: true,
3043
+ reason: "Teacher preview access",
3044
+ isUnlimited: true,
3045
+ accessType: "teacher_preview"
3046
+ };
3047
+ }
3048
+ if (isStudent && isActivityRoute) {
3049
+ try {
3050
+ const result = await ((_c2 = (_b2 = (_a2 = api).httpsCallable) == null ? void 0 : _b2.call(_a2, "checkStudentTeacherPlan")) == null ? void 0 : _c2({
3051
+ studentId: uid
3052
+ }));
3053
+ const planCheckResult = result.data;
3054
+ if (planCheckResult.canAccessFeedback) {
3055
+ return {
3056
+ canAccessFeedback: true,
3057
+ reason: planCheckResult.reason || "Student access via teacher with active plan",
3058
+ isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
3059
+ accessType: "student_with_teacher_plan"
3060
+ };
3061
+ } else {
3062
+ return {
3063
+ canAccessFeedback: false,
3064
+ reason: planCheckResult.reason || "No teacher with active plan found",
3065
+ isUnlimited: false,
3066
+ accessType: "none"
3067
+ };
3068
+ }
3069
+ } catch (error) {
3070
+ console.error("Error checking student teacher plan:", error);
3071
+ return {
3072
+ canAccessFeedback: false,
3073
+ reason: "Error checking teacher plans",
3074
+ isUnlimited: false,
3075
+ accessType: "none"
3076
+ };
3077
+ }
3078
+ }
3079
+ return {
3080
+ canAccessFeedback: false,
3081
+ reason: "No access permissions found for current context",
3082
+ isUnlimited: false,
3083
+ accessType: "none"
3084
+ };
3085
+ } catch (error) {
3086
+ console.error("Error checking activity feedback access:", error);
3087
+ return {
3088
+ canAccessFeedback: false,
3089
+ reason: "Error checking access permissions",
3090
+ isUnlimited: false,
3091
+ accessType: "none"
3092
+ };
3093
+ }
3094
+ },
3095
+ enabled: !!uid,
3096
+ staleTime: 5 * 60 * 1e3,
3097
+ // 5 minutes
3098
+ gcTime: 10 * 60 * 1e3
3099
+ // 10 minutes
3100
+ });
3101
+ return {
3102
+ ...query2
3103
+ };
3104
+ };
3105
+
3106
+ // src/hooks/useOpenAI.ts
3107
+ var useBaseOpenAI = ({
3108
+ onTranscriptSuccess,
3109
+ onTranscriptError,
3110
+ onCompletionSuccess,
3111
+ onCompletionError,
3112
+ aiEnabled,
3113
+ submitAudioResponse,
3114
+ uploadAudioAndGetTranscript,
3115
+ onGetAudioUrlAndTranscript
3116
+ }) => {
3117
+ const { user, queryClient } = useSpeakableApi();
3118
+ const currentUserId = user.auth.uid;
3119
+ const { data: feedbackAccess } = useActivityFeedbackAccess({
3120
+ aiEnabled
3121
+ });
3122
+ const getTranscript2 = async (audioUrl, language, prompt) => {
3123
+ var _a, _b, _c, _d;
3124
+ const getGeminiTranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "getGeminiTranscript");
3125
+ const getAssemblyAITranscript = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "transcribeAssemblyAIAudio");
3126
+ try {
3127
+ const { data } = await (getGeminiTranscript == null ? void 0 : getGeminiTranscript({
3128
+ audioUrl,
3129
+ targetLanguage: language,
3130
+ prompt: prompt || ""
3131
+ }));
3132
+ const transcript = data.transcript;
3133
+ if (transcript) {
3134
+ return transcript;
3135
+ }
3136
+ } catch (error) {
3137
+ console.log("Gemini transcript failed, trying AssemblyAI fallback:", error);
3138
+ }
3139
+ try {
3140
+ const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
3141
+ audioUrl,
3142
+ language
3143
+ }));
3144
+ const transcript = response == null ? void 0 : response.data;
3145
+ if (transcript) {
3146
+ return transcript;
3147
+ }
3148
+ throw new Error("Both transcript services failed");
3149
+ } catch (error) {
3150
+ console.log("AssemblyAI transcript also failed:", error);
3151
+ onTranscriptError({
3152
+ type: "TRANSCRIPT",
3153
+ message: (error == null ? void 0 : error.message) || "Error getting transcript from both services"
3154
+ });
3155
+ throw new Error(error);
3156
+ }
3157
+ };
3158
+ const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
3159
+ var _a, _b, _c, _d, _e;
3160
+ const responseTool = getRespondCardTool({
3161
+ language: feedbackLanguage,
3162
+ standard: gradingStandard
3163
+ });
3164
+ try {
3165
+ const createChatCompletion = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createChatCompletion");
3166
+ const {
3167
+ data: {
3168
+ response,
3169
+ prompt_tokens = 0,
3170
+ completion_tokens = 0,
3171
+ success: aiSuccess = false
3172
+ // the AI was able to generate a response
3173
+ }
3174
+ } = await (createChatCompletion == null ? void 0 : createChatCompletion({
3175
+ chat: {
3176
+ model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
3177
+ messages,
3178
+ temperature: 0.7,
3179
+ ...responseTool
3180
+ },
3181
+ type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
3182
+ }));
3183
+ 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) || "{}");
3184
+ const result = {
3185
+ ...functionArguments,
3186
+ prompt_tokens,
3187
+ completion_tokens,
3188
+ aiSuccess
3189
+ };
3190
+ onCompletionSuccess(result);
3191
+ return result;
3192
+ } catch (error) {
3193
+ onCompletionError({
3194
+ type: "COMPLETION",
3195
+ message: (error == null ? void 0 : error.message) || "Error getting completion"
3196
+ });
3197
+ throw new Error(error);
3198
+ }
3199
+ };
3200
+ const getFeedback = async ({
3201
+ cardId,
3202
+ language = "en",
3203
+ // required
3204
+ writtenResponse = null,
3205
+ // if the type = RESPOND_WRITE
3206
+ audio = null,
3207
+ autoGrade = true,
3208
+ file = null,
3209
+ pagePrompt = null
3210
+ }) => {
3211
+ try {
3212
+ if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
3213
+ const result = {
3214
+ noFeedbackAvailable: true,
3215
+ success: true,
3216
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3217
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3218
+ };
3219
+ onCompletionSuccess(result);
3220
+ return result;
3221
+ }
3222
+ let transcript;
3223
+ let audioUrl = void 0;
3224
+ if (writtenResponse) {
3225
+ transcript = writtenResponse;
3226
+ onTranscriptSuccess(writtenResponse);
3227
+ } else if (typeof audio === "string" && file) {
3228
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3229
+ transcript = await getTranscript2(audio, language, pagePrompt != null ? pagePrompt : "");
3230
+ audioUrl = audio;
3231
+ onTranscriptSuccess(transcript);
3232
+ } else {
3233
+ console.info(
3234
+ `Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
3235
+ );
3236
+ }
3237
+ } else {
3238
+ const response = await uploadAudioAndGetTranscript(audio || "", language, pagePrompt != null ? pagePrompt : "");
3239
+ transcript = response.transcript;
3240
+ audioUrl = response.audioUrl;
3241
+ }
3242
+ onGetAudioUrlAndTranscript == null ? void 0 : onGetAudioUrlAndTranscript({ transcript, audioUrl });
3243
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3244
+ const results = await getAIResponse({
3245
+ cardId,
3246
+ transcript: transcript || ""
3247
+ });
3248
+ let output = results;
3249
+ if (!autoGrade) {
3250
+ output = {
3251
+ ...output,
3252
+ noFeedbackAvailable: true,
3253
+ success: true
3254
+ };
3255
+ }
3256
+ onCompletionSuccess(output);
3257
+ return output;
3258
+ } else {
3259
+ const result = {
3260
+ noFeedbackAvailable: true,
3261
+ success: true,
3262
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3263
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3264
+ };
3265
+ onCompletionSuccess(result);
3266
+ return result;
3267
+ }
3268
+ } catch (error) {
3269
+ console.error("Error getting feedback:", error);
3270
+ throw new Error(error);
3271
+ }
3272
+ };
3273
+ const getAIResponse = async ({ cardId, transcript }) => {
3274
+ var _a, _b, _c, _d, _e;
3275
+ try {
3276
+ const getGeminiFeedback = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "callGetFeedback");
3277
+ const getProficiencyEstimate = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "getProficiencyEstimate");
3278
+ const card = getCardFromCache({
3279
+ cardId,
3280
+ queryClient
3281
+ });
3282
+ let feedbackData;
3283
+ let proficiencyData = {};
3284
+ if (card && card.grading_method === "manual") {
3285
+ } else if (card && card.grading_method !== "standards_based") {
3286
+ const [geminiResult, proficiencyResult] = await Promise.all([
3287
+ getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3288
+ cardId,
3289
+ studentId: currentUserId,
3290
+ studentResponse: transcript
3291
+ }),
3292
+ getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
3293
+ cardId,
3294
+ studentId: currentUserId,
3295
+ studentResponse: transcript
3296
+ })
3297
+ ]);
3298
+ proficiencyData = (proficiencyResult == null ? void 0 : proficiencyResult.data) || {};
3299
+ feedbackData = {
3300
+ ...(_e = geminiResult == null ? void 0 : geminiResult.data) != null ? _e : {},
3301
+ // @ts-ignore
3302
+ proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
3303
+ };
3304
+ } else {
3305
+ const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3306
+ cardId,
3307
+ studentId: currentUserId,
3308
+ studentResponse: transcript
3309
+ }));
3310
+ feedbackData = geminiResult == null ? void 0 : geminiResult.data;
3311
+ }
3312
+ const results = {
3313
+ ...proficiencyData,
3314
+ ...feedbackData,
3315
+ aiSuccess: true,
3316
+ promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
3317
+ transcript
3318
+ };
3319
+ return results;
3320
+ } catch (error) {
3321
+ onCompletionError({
3322
+ type: "AI_FEEDBACK",
3323
+ message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
3324
+ });
3325
+ throw new Error(error);
3326
+ }
3327
+ };
3328
+ return {
3329
+ submitAudioResponse,
3330
+ uploadAudioAndGetTranscript,
3331
+ getTranscript: getTranscript2,
3332
+ getFreeResponseCompletion,
3333
+ getFeedback
3334
+ };
3335
+ };
3336
+
3337
+ // src/lib/create-firebase-client-native.ts
3338
+ import {
3339
+ getDoc,
3340
+ getDocs,
3341
+ addDoc,
3342
+ setDoc,
3343
+ updateDoc,
3344
+ deleteDoc,
3345
+ runTransaction,
3346
+ writeBatch,
3347
+ doc,
3348
+ collection,
3349
+ query,
3350
+ serverTimestamp,
3351
+ orderBy,
3352
+ limit,
3353
+ startAt,
3354
+ startAfter,
3355
+ endAt,
3356
+ endBefore,
3357
+ where,
3358
+ increment
3359
+ } from "@react-native-firebase/firestore";
3360
+
1030
3361
  // src/lib/create-firebase-client.ts
1031
3362
  function createFsClientBase({
1032
3363
  db,
@@ -1078,6 +3409,78 @@ var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
1078
3409
  });
1079
3410
  };
1080
3411
  export {
1081
- createFsClientNative as createFsClient
3412
+ ActivityPageType,
3413
+ BASE_MULTIPLE_CHOICE_FIELD_VALUES,
3414
+ BASE_REPEAT_FIELD_VALUES,
3415
+ BASE_RESPOND_FIELD_VALUES,
3416
+ FeedbackTypesCard,
3417
+ FsCtx,
3418
+ LENIENCY_OPTIONS,
3419
+ LeniencyCard,
3420
+ MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES,
3421
+ REPEAT_PAGE_ACTIVITY_TYPES,
3422
+ RESPOND_AUDIO_PAGE_ACTIVITY_TYPES,
3423
+ RESPOND_PAGE_ACTIVITY_TYPES,
3424
+ RESPOND_WRITE_PAGE_ACTIVITY_TYPES,
3425
+ SPEAKABLE_NOTIFICATIONS,
3426
+ STUDENT_LEVELS_OPTIONS,
3427
+ SpeakableNotificationTypes,
3428
+ SpeakableProvider,
3429
+ VerificationCardStatus,
3430
+ assignmentQueryKeys,
3431
+ cardsQueryKeys,
3432
+ checkIsMCPage,
3433
+ checkIsMediaPage,
3434
+ checkIsRepeatPage,
3435
+ checkIsRespondAudioPage,
3436
+ checkIsRespondPage,
3437
+ checkIsRespondWrittenPage,
3438
+ checkIsShortAnswerPage,
3439
+ checkTypePageActivity,
3440
+ cleanString,
3441
+ createAssignmentRepo,
3442
+ createCardRepo,
3443
+ createFsClientNative as createFsClient,
3444
+ createSetRepo,
3445
+ creditQueryKeys,
3446
+ debounce,
3447
+ getCardFromCache,
3448
+ getLabelPage,
3449
+ getPagePrompt,
3450
+ getPhraseLength,
3451
+ getRespondCardTool,
3452
+ getSetFromCache,
3453
+ getTotalCompletedCards,
3454
+ getTranscript,
3455
+ getWordHash,
3456
+ purify,
3457
+ refsCardsFiresotre,
3458
+ refsSetsFirestore,
3459
+ scoreQueryKeys,
3460
+ setsQueryKeys,
3461
+ updateCardInCache,
3462
+ updateSetInCache,
3463
+ useActivity,
3464
+ useActivityFeedbackAccess,
3465
+ useAssignment,
3466
+ useBaseOpenAI,
3467
+ useCards,
3468
+ useClearScore,
3469
+ useClearScoreV2,
3470
+ useCreateCard,
3471
+ useCreateCards,
3472
+ useCreateNotification,
3473
+ useGetCard,
3474
+ useOrganizationAccess,
3475
+ useScore,
3476
+ useSet,
3477
+ useSpeakableApi,
3478
+ useSpeakableTranscript,
3479
+ useSubmitAssignmentScore,
3480
+ useSubmitPracticeScore,
3481
+ useUpdateCardScore,
3482
+ useUpdateScore,
3483
+ useUpdateStudentVocab,
3484
+ useUserCredits
1082
3485
  };
1083
3486
  //# sourceMappingURL=index.native.mjs.map