@speakableio/core 0.1.106 → 1.0.1

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.
Files changed (57) hide show
  1. package/dist/analytics.js +329 -25
  2. package/dist/analytics.js.map +1 -1
  3. package/dist/index.native.d.mts +2836 -0
  4. package/dist/index.native.d.ts +2272 -27
  5. package/dist/index.native.js +2995 -166
  6. package/dist/index.native.js.map +1 -1
  7. package/dist/index.native.mjs +3322 -0
  8. package/dist/index.native.mjs.map +1 -0
  9. package/dist/index.web.d.mts +2836 -0
  10. package/dist/index.web.js +3244 -12
  11. package/dist/index.web.js.map +1 -1
  12. package/package.json +10 -61
  13. package/dist/assignment.constants-BIKM6fYi.d.ts +0 -32
  14. package/dist/assignment.model-DLMWAp0Y.d.ts +0 -301
  15. package/dist/card.constants-DhKFipX3.d.ts +0 -54
  16. package/dist/chunk-233VJDUF.js +0 -149
  17. package/dist/chunk-233VJDUF.js.map +0 -1
  18. package/dist/chunk-2CRI5MJP.js +0 -225
  19. package/dist/chunk-2CRI5MJP.js.map +0 -1
  20. package/dist/chunk-AWVUNWML.js +0 -141
  21. package/dist/chunk-AWVUNWML.js.map +0 -1
  22. package/dist/chunk-CJ5JXKII.js +0 -129
  23. package/dist/chunk-CJ5JXKII.js.map +0 -1
  24. package/dist/chunk-EEBMPASA.js +0 -21
  25. package/dist/chunk-EEBMPASA.js.map +0 -1
  26. package/dist/chunk-H5XNOXRC.js +0 -11
  27. package/dist/chunk-H5XNOXRC.js.map +0 -1
  28. package/dist/chunk-LZG3MTSH.js +0 -53
  29. package/dist/chunk-LZG3MTSH.js.map +0 -1
  30. package/dist/chunk-OLSTHM2U.js +0 -154
  31. package/dist/chunk-OLSTHM2U.js.map +0 -1
  32. package/dist/chunk-TQGDTKTE.js +0 -13
  33. package/dist/chunk-TQGDTKTE.js.map +0 -1
  34. package/dist/chunk-YKUMIPSO.js +0 -212
  35. package/dist/chunk-YKUMIPSO.js.map +0 -1
  36. package/dist/chunk-YMJRCINF.js +0 -68
  37. package/dist/chunk-YMJRCINF.js.map +0 -1
  38. package/dist/chunk-YO34TZYN.js +0 -28
  39. package/dist/chunk-YO34TZYN.js.map +0 -1
  40. package/dist/const.d.ts +0 -331
  41. package/dist/const.js +0 -193
  42. package/dist/const.js.map +0 -1
  43. package/dist/hooks.d.ts +0 -294
  44. package/dist/hooks.js +0 -1015
  45. package/dist/hooks.js.map +0 -1
  46. package/dist/index.web.d.ts +0 -405
  47. package/dist/models.d.ts +0 -56
  48. package/dist/models.js +0 -17
  49. package/dist/models.js.map +0 -1
  50. package/dist/notification.constants-Da4-_0kX.d.ts +0 -21
  51. package/dist/repos.d.ts +0 -209
  52. package/dist/repos.js +0 -26
  53. package/dist/repos.js.map +0 -1
  54. package/dist/utils.d.ts +0 -39
  55. package/dist/utils.js +0 -180
  56. package/dist/utils.js.map +0 -1
  57. /package/dist/{analytics.d.ts → analytics.d.mts} +0 -0
@@ -1,26 +1,142 @@
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
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/entry-points/index.native.ts
31
+ var index_native_exports = {};
32
+ __export(index_native_exports, {
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
+ getWordHash: () => getWordHash,
76
+ purify: () => purify,
77
+ refsCardsFiresotre: () => refsCardsFiresotre,
78
+ refsSetsFirestore: () => refsSetsFirestore,
79
+ scoreQueryKeys: () => scoreQueryKeys,
80
+ setsQueryKeys: () => setsQueryKeys,
81
+ updateCardInCache: () => updateCardInCache,
82
+ updateSetInCache: () => updateSetInCache,
83
+ useActivity: () => useActivity,
84
+ useActivityFeedbackAccess: () => useActivityFeedbackAccess,
85
+ useAssignment: () => useAssignment,
86
+ useBaseOpenAI: () => useBaseOpenAI,
87
+ useCards: () => useCards,
88
+ useClearScore: () => useClearScore,
89
+ useCreateCard: () => useCreateCard,
90
+ useCreateCards: () => useCreateCards,
91
+ useCreateNotification: () => useCreateNotification,
92
+ useGetCard: () => useGetCard,
93
+ useOrganizationAccess: () => useOrganizationAccess,
94
+ useScore: () => useScore,
95
+ useSet: () => useSet,
96
+ useSpeakableApi: () => useSpeakableApi,
97
+ useSubmitAssignmentScore: () => useSubmitAssignmentScore,
98
+ useSubmitPracticeScore: () => useSubmitPracticeScore,
99
+ useUpdateCardScore: () => useUpdateCardScore,
100
+ useUpdateScore: () => useUpdateScore,
101
+ useUpdateStudentVocab: () => useUpdateStudentVocab,
102
+ useUserCredits: () => useUserCredits
103
+ });
104
+ module.exports = __toCommonJS(index_native_exports);
105
+
106
+ // src/providers/SpeakableProvider.tsx
107
+ var import_react = require("react");
108
+ var import_jsx_runtime = require("react/jsx-runtime");
109
+ var FsCtx = (0, import_react.createContext)(null);
110
+ function SpeakableProvider({
111
+ user,
112
+ children,
113
+ queryClient,
114
+ permissions,
115
+ fsClient
116
+ }) {
117
+ const [speakableApi, setSpeakableApi] = (0, import_react.useState)(null);
118
+ (0, import_react.useEffect)(() => {
119
+ setSpeakableApi(fsClient);
120
+ }, [fsClient]);
121
+ if (!speakableApi) return null;
122
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
123
+ FsCtx.Provider,
124
+ {
125
+ value: {
126
+ speakableApi,
127
+ queryClient,
128
+ user,
129
+ permissions
130
+ },
131
+ children
132
+ }
133
+ );
134
+ }
135
+ function useSpeakableApi() {
136
+ const ctx = (0, import_react.useContext)(FsCtx);
137
+ if (!ctx) throw new Error("useSpeakableApi must be used within a SpeakableProvider");
138
+ return ctx;
139
+ }
24
140
 
25
141
  // src/utils/error-handler.ts
26
142
  var ServiceError = class extends Error {
@@ -284,12 +400,12 @@ async function _getAllAssignments() {
284
400
  var getAllAssignments = withErrorHandler(_getAllAssignments, "getAllAssignments");
285
401
 
286
402
  // src/domains/assignment/utils/check-assignment-availability.ts
287
- import dayjs from "dayjs";
403
+ var import_dayjs = __toESM(require("dayjs"));
288
404
  var checkAssignmentAvailability = (scheduledTime) => {
289
405
  if (!scheduledTime) return true;
290
- const scheduledDate = typeof scheduledTime === "string" ? dayjs(scheduledTime) : dayjs(scheduledTime.toDate());
406
+ const scheduledDate = typeof scheduledTime === "string" ? (0, import_dayjs.default)(scheduledTime) : (0, import_dayjs.default)(scheduledTime.toDate());
291
407
  if (!scheduledDate.isValid()) return true;
292
- return dayjs().isAfter(scheduledDate);
408
+ return (0, import_dayjs.default)().isAfter(scheduledDate);
293
409
  };
294
410
 
295
411
  // src/domains/assignment/services/get-assignment.service.ts
@@ -328,178 +444,2891 @@ var createAssignmentRepo = () => {
328
444
  };
329
445
  };
330
446
 
331
- // src/domains/cards/services/create-card.service.ts
332
- import { v4 } from "uuid";
333
-
334
- // src/domains/cards/card.constants.ts
335
- var CARDS_COLLECTION = "flashcards";
336
- var refsCardsFiresotre = {
337
- allCards: CARDS_COLLECTION,
338
- card: (id) => `${CARDS_COLLECTION}/${id}`
447
+ // src/domains/assignment/hooks/assignment.hooks.ts
448
+ var import_react_query = require("@tanstack/react-query");
449
+ var assignmentQueryKeys = {
450
+ all: ["assignments"],
451
+ byId: (id) => [...assignmentQueryKeys.all, id],
452
+ list: () => [...assignmentQueryKeys.all, "list"]
339
453
  };
454
+ function useAssignment({
455
+ assignmentId,
456
+ enabled = true,
457
+ analyticType,
458
+ userId
459
+ }) {
460
+ const { speakableApi } = useSpeakableApi();
461
+ return (0, import_react_query.useQuery)({
462
+ queryKey: assignmentQueryKeys.byId(assignmentId),
463
+ queryFn: () => speakableApi.assignmentRepo.getAssignment({
464
+ assignmentId,
465
+ analyticType,
466
+ currentUserId: userId
467
+ }),
468
+ enabled
469
+ });
470
+ }
340
471
 
341
- // src/utils/text-utils.ts
342
- import sha1 from "js-sha1";
343
- var purify = (word) => {
344
- return word.normalize("NFD").replace(/\/([^" "]*)/g, "").replace(/\([^()]*\)/g, "").replace(/([^()]*)/g, "").replace(/[\u0300-\u036f]/g, "").replace(/[-]/g, " ").replace(/[.,/#!¡¿?؟。,.?$%^&*;:{}=\-_`~()’'…\s]/g, "").replace(/\s\s+/g, " ").toLowerCase().trim();
345
- };
346
- var cleanString = (words) => {
347
- const splitWords = words == null ? void 0 : words.split("+");
348
- if (splitWords && splitWords.length === 1) {
349
- const newWord = purify(words);
350
- return newWord;
351
- } else if (splitWords && splitWords.length > 1) {
352
- const split = splitWords.map((w) => purify(w));
353
- return split;
472
+ // src/domains/assignment/hooks/score-hooks.ts
473
+ var import_react_query2 = require("@tanstack/react-query");
474
+
475
+ // src/utils/debounce.utils.ts
476
+ function debounce(func, waitFor) {
477
+ let timeoutId;
478
+ return (...args) => new Promise((resolve, reject) => {
479
+ if (timeoutId) {
480
+ clearTimeout(timeoutId);
481
+ }
482
+ timeoutId = setTimeout(async () => {
483
+ try {
484
+ const result = await func(...args);
485
+ resolve(result);
486
+ } catch (error) {
487
+ reject(error);
488
+ }
489
+ }, waitFor);
490
+ });
491
+ }
492
+
493
+ // src/lib/tanstack/handle-optimistic-update-query.ts
494
+ var handleOptimisticUpdate = async ({
495
+ queryClient,
496
+ queryKey,
497
+ newData
498
+ }) => {
499
+ await queryClient.cancelQueries({
500
+ queryKey
501
+ });
502
+ const previousData = queryClient.getQueryData(queryKey);
503
+ if (previousData === void 0) {
504
+ queryClient.setQueryData(queryKey, newData);
354
505
  } else {
355
- return "";
506
+ queryClient.setQueryData(queryKey, { ...previousData, ...newData });
356
507
  }
508
+ return { previousData };
357
509
  };
358
- var getWordHash = (word, language) => {
359
- const cleanedWord = cleanString(word);
360
- const wordHash = sha1(`${language}-${cleanedWord}`);
361
- console.log("wordHash core library", wordHash);
362
- return wordHash;
510
+
511
+ // src/constants/speakable-plans.ts
512
+ var FEEDBACK_PLANS = {
513
+ FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
514
+ // Transcript from the audio
515
+ FEEDBACK_SUMMARY: "FEEDBACK_SUMMARY",
516
+ // Chatty summary (Free plan)
517
+ FEEDBACK_GRAMMAR_INSIGHTS: "FEEDBACK_GRAMMAR_INSIGHTS",
518
+ // Grammar insights
519
+ FEEDBACK_SUGGESTED_RESPONSE: "FEEDBACK_SUGGESTED_RESPONSE",
520
+ // Suggested Response
521
+ FEEDBACK_RUBRIC: "FEEDBACK_RUBRIC",
522
+ // Suggested Response
523
+ FEEDBACK_GRADING_STANDARDS: "FEEDBACK_GRADING_STANDARDS",
524
+ // ACTFL / WIDA Estimate
525
+ FEEDBACK_TARGET_LANGUAGE: "FEEDBACK_TARGET_LANGUAGE",
526
+ // Ability to set the feedback language to the target language of the student
527
+ FEEDBACK_DISABLE_ALLOW_RETRIES: "FEEDBACK_DISABLE_ALLOW_RETRIES"
528
+ // Turn of allow retries
529
+ };
530
+ var AUTO_GRADING_PLANS = {
531
+ AUTO_GRADING_PASS_FAIL: "AUTO_GRADING_PASS_FAIL",
532
+ // Pass / fail grading
533
+ AUTO_GRADING_RUBRICS: "AUTO_GRADING_RUBRICS",
534
+ // Autograded rubrics
535
+ AUTO_GRADING_STANDARDS_BASED: "AUTO_GRADING_STANDARDS_BASED"
536
+ // Standards based grading
537
+ };
538
+ var AI_ASSISTANT_PLANS = {
539
+ AI_ASSISTANT_DOCUMENT_UPLOADS: "AI_ASSISTANT_DOCUMENT_UPLOADS",
540
+ // Allow document uploading
541
+ AI_ASSISTANT_UNLIMITED_USE: "AI_ASSISTANT_UNLIMITED_USE"
542
+ // Allow unlimited use of AI assistant. Otherwise, limits are used.
543
+ };
544
+ var ASSIGNMENT_SETTINGS_PLANS = {
545
+ ASSESSMENTS: "ASSESSMENTS",
546
+ // Ability to create assessment assignment types
547
+ GOOGLE_CLASSROOM_GRADE_PASSBACK: "GOOGLE_CLASSROOM_GRADE_PASSBACK"
548
+ // Assignment scores can sync with classroom
549
+ };
550
+ var ANALYTICS_PLANS = {
551
+ ANALYTICS_GRADEBOOK: "ANALYTICS_GRADEBOOK",
552
+ // Access to the gradebook page
553
+ ANALYTICS_CLASSROOM_ANALYTICS: "ANALYTICS_CLASSROOM_ANALYTICS",
554
+ // Access to the classroom analytics page
555
+ ANALYTICS_STUDENT_PROGRESS_REPORTS: "ANALYTICS_STUDENT_PROGRESS_REPORTS",
556
+ // Access to the panel that shows an individual student's progress and assignments
557
+ ANALYTICS_ASSIGNMENT_RESULTS: "ANALYTICS_ASSIGNMENT_RESULTS",
558
+ // Access to the assigment RESULTS page
559
+ ANALYTICS_ORGANIZATION: "ANALYTICS_ORGANIZATION"
560
+ // Access to the organization analytics panel (for permitted admins)
561
+ };
562
+ var SPACES_PLANS = {
563
+ SPACES_CREATE_SPACE: "SPACES_CREATE_SPACE",
564
+ // Ability to create spaces
565
+ SPACES_CHECK_POINTS: "SPACES_CHECK_POINTS"
566
+ // Feature not available yet. Ability to create checkpoints for spaces for data aggregation
567
+ };
568
+ var DISCOVER_PLANS = {
569
+ DISCOVER_ORGANIZATION_LIBRARY: "DISCOVER_ORGANIZATION_LIBRARY"
570
+ // Access to the organizations shared library
571
+ };
572
+ var MEDIA_AREA_PLANS = {
573
+ MEDIA_AREA_DOCUMENT_UPLOAD: "MEDIA_AREA_DOCUMENT_UPLOAD",
574
+ MEDIA_AREA_AUDIO_FILES: "MEDIA_AREA_AUDIO_FILES"
363
575
  };
576
+ var FREE_PLAN = [];
577
+ var TEACHER_PRO_PLAN = [
578
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
579
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
580
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
581
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
582
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
583
+ SPACES_PLANS.SPACES_CREATE_SPACE
584
+ // AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
585
+ ];
586
+ var SCHOOL_STARTER = [
587
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
588
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
589
+ FEEDBACK_PLANS.FEEDBACK_GRAMMAR_INSIGHTS,
590
+ FEEDBACK_PLANS.FEEDBACK_SUGGESTED_RESPONSE,
591
+ FEEDBACK_PLANS.FEEDBACK_RUBRIC,
592
+ FEEDBACK_PLANS.FEEDBACK_GRADING_STANDARDS,
593
+ FEEDBACK_PLANS.FEEDBACK_DISABLE_ALLOW_RETRIES,
594
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
595
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
596
+ AUTO_GRADING_PLANS.AUTO_GRADING_RUBRICS,
597
+ AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
598
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_DOCUMENT_UPLOADS,
599
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_UNLIMITED_USE,
600
+ // ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS,
601
+ ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK,
602
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
603
+ ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS,
604
+ ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS,
605
+ // ANALYTICS_PLANS.ANALYTICS_ORGANIZATION,
606
+ SPACES_PLANS.SPACES_CREATE_SPACE,
607
+ SPACES_PLANS.SPACES_CHECK_POINTS,
608
+ // DISCOVER_PLANS.DISCOVER_ORGANIZATION_LIBRARY,
609
+ MEDIA_AREA_PLANS.MEDIA_AREA_DOCUMENT_UPLOAD,
610
+ MEDIA_AREA_PLANS.MEDIA_AREA_AUDIO_FILES
611
+ ];
612
+ var ORGANIZATION_PLAN = [
613
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
614
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
615
+ FEEDBACK_PLANS.FEEDBACK_GRAMMAR_INSIGHTS,
616
+ FEEDBACK_PLANS.FEEDBACK_SUGGESTED_RESPONSE,
617
+ FEEDBACK_PLANS.FEEDBACK_RUBRIC,
618
+ FEEDBACK_PLANS.FEEDBACK_GRADING_STANDARDS,
619
+ FEEDBACK_PLANS.FEEDBACK_DISABLE_ALLOW_RETRIES,
620
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
621
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
622
+ AUTO_GRADING_PLANS.AUTO_GRADING_RUBRICS,
623
+ AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
624
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_DOCUMENT_UPLOADS,
625
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_UNLIMITED_USE,
626
+ ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS,
627
+ ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK,
628
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
629
+ ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS,
630
+ ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS,
631
+ ANALYTICS_PLANS.ANALYTICS_ORGANIZATION,
632
+ SPACES_PLANS.SPACES_CREATE_SPACE,
633
+ SPACES_PLANS.SPACES_CHECK_POINTS,
634
+ DISCOVER_PLANS.DISCOVER_ORGANIZATION_LIBRARY,
635
+ MEDIA_AREA_PLANS.MEDIA_AREA_DOCUMENT_UPLOAD,
636
+ MEDIA_AREA_PLANS.MEDIA_AREA_AUDIO_FILES
637
+ ];
638
+ var SpeakablePlanTypes = {
639
+ basic: "basic",
640
+ teacher_pro: "teacher_pro",
641
+ school_starter: "school_starter",
642
+ organization: "organization",
643
+ // OLD PLANS
644
+ starter: "starter",
645
+ growth: "growth",
646
+ professional: "professional"
647
+ };
648
+ var SpeakablePermissionsMap = {
649
+ [SpeakablePlanTypes.basic]: FREE_PLAN,
650
+ [SpeakablePlanTypes.starter]: TEACHER_PRO_PLAN,
651
+ [SpeakablePlanTypes.teacher_pro]: TEACHER_PRO_PLAN,
652
+ [SpeakablePlanTypes.growth]: ORGANIZATION_PLAN,
653
+ [SpeakablePlanTypes.professional]: ORGANIZATION_PLAN,
654
+ [SpeakablePlanTypes.organization]: ORGANIZATION_PLAN,
655
+ [SpeakablePlanTypes.school_starter]: SCHOOL_STARTER
656
+ };
657
+ var SpeakablePlanHierarchy = [
658
+ SpeakablePlanTypes.basic,
659
+ SpeakablePlanTypes.starter,
660
+ SpeakablePlanTypes.teacher_pro,
661
+ SpeakablePlanTypes.growth,
662
+ SpeakablePlanTypes.professional,
663
+ SpeakablePlanTypes.school_starter,
664
+ SpeakablePlanTypes.organization
665
+ ];
364
666
 
365
- // src/domains/cards/services/get-card-verification-status.service.ts
366
- var charactarLanguages = ["zh", "ja", "ko"];
367
- var getVerificationStatus = async (target_text, language) => {
368
- if ((target_text == null ? void 0 : target_text.length) < 3 && !charactarLanguages.includes(language)) {
369
- return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
667
+ // src/hooks/usePermissions.ts
668
+ var usePermissions = () => {
669
+ const { permissions } = useSpeakableApi();
670
+ const has = (permission) => {
671
+ var _a;
672
+ return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
673
+ };
674
+ return {
675
+ plan: permissions.plan,
676
+ permissionsLoaded: permissions.loaded,
677
+ isStripePlan: permissions.isStripePlan,
678
+ refreshDate: permissions.refreshDate,
679
+ isInstitutionPlan: permissions.isInstitutionPlan,
680
+ subscriptionId: permissions.subscriptionId,
681
+ contact: permissions.contact,
682
+ hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
683
+ hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
684
+ hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
685
+ hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
686
+ hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
687
+ permissions: permissions || [],
688
+ hasStudentPortfolios: permissions.hasStudentPortfolios,
689
+ isFreeOrgTrial: permissions.type === "free_org_trial",
690
+ freeOrgTrialExpired: permissions.freeOrgTrialExpired
691
+ };
692
+ };
693
+ var usePermissions_default = usePermissions;
694
+
695
+ // src/domains/notification/notification.constants.ts
696
+ var SPEAKABLE_NOTIFICATIONS = {
697
+ NEW_ASSIGNMENT: "new_assignment",
698
+ ASSESSMENT_SUBMITTED: "assessment_submitted",
699
+ ASSESSMENT_SCORED: "assessment_scored",
700
+ NEW_COMMENT: "NEW_COMMENT"
701
+ };
702
+ var SpeakableNotificationTypes = {
703
+ NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
704
+ FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
705
+ MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
706
+ PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
707
+ STUDENT_PROGRESS: "STUDENT_PROGRESS",
708
+ PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
709
+ PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
710
+ // New notifications
711
+ ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
712
+ // Notification FOR TEACHER when student submits assessment
713
+ ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
714
+ // Notification FOR STUDENT when teacher scores assessment
715
+ // Comment
716
+ NEW_COMMENT: "NEW_COMMENT"
717
+ };
718
+
719
+ // src/domains/notification/services/create-notification.service.ts
720
+ var import_dayjs2 = __toESM(require("dayjs"));
721
+
722
+ // src/constants/web.constants.ts
723
+ var WEB_BASE_URL = "https://app.speakable.io";
724
+
725
+ // src/domains/notification/services/send-notification.service.ts
726
+ var _sendNotification = async (sendTo, notification) => {
727
+ var _a, _b, _c;
728
+ const results = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createNotificationV2")) == null ? void 0 : _c({
729
+ sendTo,
730
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
731
+ notification
732
+ }));
733
+ return results;
734
+ };
735
+ var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
736
+
737
+ // src/domains/notification/services/create-notification.service.ts
738
+ var createNotification = async ({
739
+ data,
740
+ type,
741
+ userId,
742
+ profile
743
+ }) => {
744
+ let result;
745
+ switch (type) {
746
+ case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
747
+ result = await handleAssignNotifPromise({ data, profile });
748
+ break;
749
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
750
+ result = await createAssessmentSubmissionNotification({
751
+ data,
752
+ profile,
753
+ userId
754
+ });
755
+ break;
756
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
757
+ result = await createAssessmentScoredNotification({
758
+ data,
759
+ profile
760
+ });
761
+ break;
762
+ default:
763
+ result = null;
764
+ break;
370
765
  }
371
- const hash = getWordHash(target_text, language);
372
- const response = await api.getDoc(`checked-pronunciations/${hash}`);
766
+ return result;
767
+ };
768
+ var handleAssignNotifPromise = async ({
769
+ data: assignments,
770
+ profile
771
+ }) => {
772
+ if (!assignments.length) return;
373
773
  try {
374
- if (response.data) {
375
- return processRecord(response.data);
376
- } else {
377
- return "NOT_CHECKED" /* NOT_CHECKED */;
774
+ const notifsPromises = assignments.map(async (assignment) => {
775
+ const {
776
+ section,
777
+ section: { members },
778
+ ...rest
779
+ } = assignment;
780
+ if (!section || !members) throw new Error("Invalid assignment data");
781
+ const data = { section, sendTo: members, assignment: { ...rest } };
782
+ return createNewAssignmentNotification({ data, profile });
783
+ });
784
+ await Promise.all(notifsPromises);
785
+ return {
786
+ success: true,
787
+ message: "Assignment notifications sent successfully"
788
+ };
789
+ } catch (error) {
790
+ console.error("Error in handleAssignNotifPromise:", error);
791
+ throw error;
792
+ }
793
+ };
794
+ var createNewAssignmentNotification = async ({
795
+ data,
796
+ profile
797
+ }) => {
798
+ var _a;
799
+ const { assignment, sendTo } = data;
800
+ const teacherName = profile.displayName || "Your teacher";
801
+ const dueDate = assignment.dueDateTimestamp ? (0, import_dayjs2.default)(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
802
+ const results = await sendNotification(sendTo, {
803
+ courseId: assignment.courseId,
804
+ type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
805
+ senderName: teacherName,
806
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
807
+ messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
808
+ title: "New Assignment Available!",
809
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
810
+ });
811
+ return results;
812
+ };
813
+ var createAssessmentSubmissionNotification = async ({
814
+ data: assignment,
815
+ profile,
816
+ userId
817
+ }) => {
818
+ var _a;
819
+ const studentName = profile.displayName || "Your student";
820
+ const results = await sendNotification(assignment.owners, {
821
+ courseId: assignment.courseId,
822
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
823
+ link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
824
+ title: `Assessment Submitted!`,
825
+ senderName: studentName,
826
+ messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
827
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
828
+ });
829
+ return results;
830
+ };
831
+ var createAssessmentScoredNotification = async ({
832
+ data,
833
+ profile
834
+ }) => {
835
+ var _a, _b, _c, _d, _e;
836
+ const { assignment, sendTo } = data;
837
+ const teacherName = profile.displayName || "Your teacher";
838
+ const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
839
+ const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
840
+ const results = await sendNotification(sendTo, {
841
+ courseId: assignment.courseId,
842
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
843
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
844
+ title,
845
+ messagePreview,
846
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
847
+ senderName: teacherName
848
+ });
849
+ await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
850
+ assessmentTitle: assignment.name,
851
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
852
+ senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
853
+ studentId: sendTo[0],
854
+ teacherName: profile.displayName
855
+ }));
856
+ return results;
857
+ };
858
+
859
+ // src/domains/notification/hooks/notification.hooks.ts
860
+ var notificationQueryKeys = {
861
+ all: ["notifications"],
862
+ byId: (id) => [...notificationQueryKeys.all, id]
863
+ };
864
+ var useCreateNotification = () => {
865
+ const { user, queryClient } = useSpeakableApi();
866
+ const handleCreateNotifications = async (type, data) => {
867
+ var _a, _b;
868
+ const result = await createNotification({
869
+ type,
870
+ userId: user.auth.uid,
871
+ profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
872
+ data
873
+ });
874
+ queryClient.invalidateQueries({
875
+ queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
876
+ });
877
+ return result;
878
+ };
879
+ return {
880
+ createNotification: handleCreateNotifications
881
+ };
882
+ };
883
+
884
+ // src/hooks/useGoogleClassroom.ts
885
+ var useGoogleClassroom = () => {
886
+ const submitAssignmentToGoogleClassroom = async ({
887
+ assignment,
888
+ scores,
889
+ googleUserId = null
890
+ // optional to override the user's googleUserId
891
+ }) => {
892
+ var _a, _b, _c;
893
+ try {
894
+ const { googleClassroomUserId = null } = scores;
895
+ const googleId = googleUserId || googleClassroomUserId;
896
+ if (!googleId)
897
+ return {
898
+ error: true,
899
+ message: "No Google Classroom ID found"
900
+ };
901
+ const { courseWorkId, maxPoints, owners, courseId } = assignment;
902
+ const draftGrade = (scores == null ? void 0 : scores.score) ? (scores == null ? void 0 : scores.score) / 100 * maxPoints : 0;
903
+ const result = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentToGoogleClassroomV2")) == null ? void 0 : _c({
904
+ teacherId: owners[0],
905
+ courseId,
906
+ courseWorkId,
907
+ userId: googleId,
908
+ draftGrade
909
+ }));
910
+ return result;
911
+ } catch (error) {
912
+ return { error: true, message: error.message };
378
913
  }
379
- } catch (e) {
380
- return "NOT_CHECKED" /* NOT_CHECKED */;
914
+ };
915
+ return {
916
+ submitAssignmentToGoogleClassroom
917
+ };
918
+ };
919
+
920
+ // src/lib/firebase/firebase-analytics/grading-standard.ts
921
+ var logGradingStandardLog = (data) => {
922
+ var _a, _b, _c;
923
+ if (data.courseId && data.type && data.level) {
924
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
925
+ eventType: data.type || "custom",
926
+ level: data.level,
927
+ courseId: data.courseId
928
+ });
381
929
  }
930
+ api.logEvent("logGradingStandard", data);
382
931
  };
383
- var processRecord = (data) => {
384
- const { pronunciations = 0, fails = 0 } = data;
385
- const attempts = pronunciations + fails;
386
- const successRate = attempts > 0 ? pronunciations / attempts * 100 : 0;
387
- let newStatus = null;
388
- if (attempts < 6) {
389
- return "NOT_CHECKED" /* NOT_CHECKED */;
932
+
933
+ // src/constants/analytics.constants.ts
934
+ var ANALYTICS_EVENT_TYPES = {
935
+ VOICE_SUCCESS: "voice_success",
936
+ VOICE_FAIL: "voice_fail",
937
+ RESPOND_CARD_SUCCESS: "respond_card_success",
938
+ RESPOND_CARD_FAIL: "respond_card_fail",
939
+ RESPOND_WRITE_CARD_SUCCESS: "respond_write_card_success",
940
+ RESPOND_WRITE_CARD_FAIL: "respond_write_card_fail",
941
+ RESPOND_WRITE_CARD_SUBMITTED: "respond_write_card_submitted",
942
+ RESPOND_WRITE_CARD_ERROR: "respond_write_card_error",
943
+ RESPOND_CARD_ERROR: "respond_card_error",
944
+ RESPOND_CARD_SUBMITTED: "respond_card_submitted",
945
+ RESPOND_FREE_PLAN: "respond_free_plan",
946
+ RESPOND_WRITE_FREE_PLAN: "respond_write_free_plan",
947
+ SUBMISSION: "assignment_submitted",
948
+ ASSIGNMENT_STARTED: "assignment_started",
949
+ CREATE_ASSIGNMENT: "create_assignment",
950
+ MC_SUCCESS: "multiple_choice_success",
951
+ MC_FAIL: "multiple_choice_fail",
952
+ MC_ERROR: "multiple_choice_error",
953
+ ACTFL_LEVEL: "actfl_level",
954
+ WIDA_LEVEL: "wida_level"
955
+ };
956
+
957
+ // src/lib/firebase/firebase-analytics/assignment.ts
958
+ var logOpenAssignment = (data = {}) => {
959
+ api.logEvent("open_assignment", data);
960
+ };
961
+ var logOpenActivityPreview = (data = {}) => {
962
+ api.logEvent("open_activity_preview", data);
963
+ };
964
+ var logSubmitAssignment = (data = {}) => {
965
+ var _a, _b, _c;
966
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
967
+ eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
968
+ ...data
969
+ });
970
+ api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
971
+ };
972
+ var logStartAssignment = (data = {}) => {
973
+ var _a, _b, _c;
974
+ if (data.courseId) {
975
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
976
+ eventType: ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED,
977
+ ...data
978
+ });
390
979
  }
391
- if (successRate > 25) {
392
- newStatus = "VERIFIED" /* VERIFIED */;
393
- } else if (successRate > 10) {
394
- newStatus = "WARNING" /* WARNING */;
395
- } else if (fails > 20 && successRate < 10 && pronunciations > 1) {
396
- newStatus = "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
397
- } else if (pronunciations === 0 && fails > 20) {
398
- newStatus = "NOT_WORKING" /* NOT_WORKING */;
399
- } else {
400
- newStatus = "NOT_CHECKED" /* NOT_CHECKED */;
980
+ api.logEvent(ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED, data);
981
+ };
982
+
983
+ // src/domains/assignment/utils/create-default-score.ts
984
+ var defaultScore = (props) => {
985
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
986
+ const score = {
987
+ progress: 0,
988
+ score: 0,
989
+ startDate: serverTimestamp2(),
990
+ status: "IN_PROGRESS",
991
+ submitted: false,
992
+ cards: {},
993
+ lastPlayed: serverTimestamp2(),
994
+ owners: props.owners,
995
+ userId: props.userId
996
+ };
997
+ if (props.googleClassroomUserId) {
998
+ score.googleClassroomUserId = props.googleClassroomUserId;
401
999
  }
402
- return newStatus;
1000
+ if (props.courseId) {
1001
+ score.courseId = props.courseId;
1002
+ }
1003
+ return score;
403
1004
  };
404
1005
 
405
- // src/domains/cards/services/create-card.service.ts
406
- async function _createCard({ data }) {
407
- const response = await api.addDoc(refsCardsFiresotre.allCards, data);
408
- return response;
409
- }
410
- var createCard = withErrorHandler(_createCard, "createCard");
411
- async function _createCards({ cards }) {
412
- const { writeBatch: writeBatch2, doc: doc2 } = api.accessHelpers();
413
- const batch = writeBatch2();
414
- const cardsWithId = [];
415
- for (const card of cards) {
416
- const cardId = v4();
417
- const ref = doc2(refsCardsFiresotre.card(cardId));
418
- const newCardObject = {
419
- ...card,
420
- id: cardId
1006
+ // src/domains/assignment/score-practice.constants.ts
1007
+ var SCORES_PRACTICE_COLLECTION = "users";
1008
+ var SCORES_PRACTICE_SUBCOLLECTION = "practice";
1009
+ var refsScoresPractice = {
1010
+ practiceScores: (params) => `${SCORES_PRACTICE_COLLECTION}/${params.userId}/${SCORES_PRACTICE_SUBCOLLECTION}/${params.setId}`,
1011
+ practiceScoreHistoryRefDoc: (params) => `${SCORES_PRACTICE_COLLECTION}/${params.userId}/${SCORES_PRACTICE_SUBCOLLECTION}/${params.setId}/attempts/${params.date}`
1012
+ };
1013
+
1014
+ // src/domains/assignment/services/create-score.service.ts
1015
+ async function _createScore(params) {
1016
+ var _a, _b, _c;
1017
+ if (params.isAssignment) {
1018
+ const ref = refsAssignmentFiresotre.assignmentScores({
1019
+ id: params.activityId,
1020
+ userId: params.userId
1021
+ });
1022
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "updateAssignmentGradebookStatus")) == null ? void 0 : _c({
1023
+ assignmentId: params.activityId,
1024
+ userId: params.userId,
1025
+ status: "IN_PROGRESS",
1026
+ score: null
1027
+ }));
1028
+ await api.setDoc(ref, params.scoreData, { merge: true });
1029
+ return {
1030
+ id: params.userId
1031
+ };
1032
+ } else {
1033
+ const ref = refsScoresPractice.practiceScores({
1034
+ userId: params.userId,
1035
+ setId: params.activityId
1036
+ });
1037
+ await api.setDoc(ref, params.scoreData);
1038
+ return {
1039
+ id: params.userId
421
1040
  };
422
- if (card.type === "READ_REPEAT" /* READ_REPEAT */ && card.target_text && card.language) {
423
- const verificationStatus = await getVerificationStatus(card.target_text, card.language);
424
- newCardObject.verificationStatus = verificationStatus || null;
425
- }
426
- cardsWithId.push(newCardObject);
427
- batch.set(ref, newCardObject);
428
1041
  }
429
- await batch.commit();
430
- return cardsWithId;
431
1042
  }
432
- var createCards = withErrorHandler(_createCards, "createCards");
1043
+ var createScore = withErrorHandler(_createScore, "createScore");
433
1044
 
434
- // src/domains/cards/services/get-card.service.ts
435
- async function _getCard(params) {
436
- const ref = refsCardsFiresotre.card(params.cardId);
437
- const response = await api.getDoc(ref);
438
- if (!response.data) return null;
1045
+ // src/domains/assignment/services/get-score.service.ts
1046
+ async function getAssignmentScore({
1047
+ userId,
1048
+ assignment,
1049
+ googleClassroomUserId
1050
+ }) {
1051
+ const path = refsAssignmentFiresotre.assignmentScores({
1052
+ id: assignment.id,
1053
+ userId
1054
+ });
1055
+ const response = await api.getDoc(path);
1056
+ if (response.data == null) {
1057
+ const newScore = {
1058
+ ...defaultScore({
1059
+ owners: [userId],
1060
+ userId,
1061
+ courseId: assignment.courseId,
1062
+ googleClassroomUserId
1063
+ }),
1064
+ assignmentId: assignment.id
1065
+ };
1066
+ logStartAssignment({
1067
+ courseId: assignment.courseId
1068
+ });
1069
+ const result = await createScore({
1070
+ activityId: assignment.id,
1071
+ userId,
1072
+ isAssignment: true,
1073
+ scoreData: newScore
1074
+ });
1075
+ return {
1076
+ ...newScore,
1077
+ id: result.id
1078
+ };
1079
+ }
439
1080
  return response.data;
440
1081
  }
441
- var getCard = withErrorHandler(_getCard, "getCard");
1082
+ async function getPracticeScore({ userId, setId }) {
1083
+ const path = refsScoresPractice.practiceScores({ userId, setId });
1084
+ const response = await api.getDoc(path);
1085
+ if (response.data == null) {
1086
+ const newScore = {
1087
+ ...defaultScore({
1088
+ owners: [userId],
1089
+ userId
1090
+ }),
1091
+ setId
1092
+ };
1093
+ const result = await createScore({
1094
+ activityId: setId,
1095
+ userId,
1096
+ isAssignment: false,
1097
+ scoreData: newScore
1098
+ });
1099
+ return {
1100
+ ...newScore,
1101
+ id: result.id
1102
+ };
1103
+ }
1104
+ return response.data;
1105
+ }
1106
+ async function _getScore(params) {
1107
+ if (params.isAssignment) {
1108
+ return await getAssignmentScore({
1109
+ userId: params.userId,
1110
+ assignment: {
1111
+ id: params.activityId,
1112
+ courseId: params.courseId
1113
+ },
1114
+ googleClassroomUserId: params.googleClassroomUserId
1115
+ });
1116
+ } else {
1117
+ return await getPracticeScore({
1118
+ userId: params.userId,
1119
+ setId: params.activityId
1120
+ });
1121
+ }
1122
+ }
1123
+ var getScore = withErrorHandler(_getScore, "getScore");
442
1124
 
443
- // src/domains/cards/card.repo.ts
444
- var createCardRepo = () => {
445
- return {
446
- createCard,
447
- createCards,
448
- getCard
449
- };
1125
+ // src/domains/assignment/utils/calculateScoreAndProgress.ts
1126
+ var calculateScoreAndProgress = (scores, cardsList, weights) => {
1127
+ const totalSetPoints = cardsList.reduce((acc, cardId) => {
1128
+ acc += (weights == null ? void 0 : weights[cardId]) || 1;
1129
+ return acc;
1130
+ }, 0);
1131
+ const totalPointsAwarded = Object.keys((scores == null ? void 0 : scores.cards) || {}).reduce((acc, cardId) => {
1132
+ var _a, _b;
1133
+ const cardScores = (_a = scores == null ? void 0 : scores.cards) == null ? void 0 : _a[cardId];
1134
+ if ((cardScores == null ? void 0 : cardScores.completed) || (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0) {
1135
+ const score2 = (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0 ? Number((_b = cardScores == null ? void 0 : cardScores.score) != null ? _b : 0) : null;
1136
+ const weight = (weights == null ? void 0 : weights[cardId]) || 1;
1137
+ const fraction = (score2 != null ? score2 : 0) / 100;
1138
+ if (score2 || score2 === 0) {
1139
+ acc += weight * fraction;
1140
+ } else {
1141
+ acc += weight;
1142
+ }
1143
+ }
1144
+ return acc;
1145
+ }, 0);
1146
+ const totalCompletedCards = Object.keys((scores == null ? void 0 : scores.cards) || {}).reduce((acc, cardId) => {
1147
+ var _a;
1148
+ const cardScores = (_a = scores == null ? void 0 : scores.cards) == null ? void 0 : _a[cardId];
1149
+ if ((cardScores == null ? void 0 : cardScores.completed) || (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0) {
1150
+ acc += 1;
1151
+ }
1152
+ return acc;
1153
+ }, 0);
1154
+ const percent = totalPointsAwarded / totalSetPoints;
1155
+ const score = Math.round(percent * 100);
1156
+ const progress = Math.round(totalCompletedCards / (cardsList.length || 1) * 100);
1157
+ return { score, progress };
450
1158
  };
1159
+ var calculateScoreAndProgress_default = calculateScoreAndProgress;
451
1160
 
452
- // src/lib/create-firebase-client.ts
453
- function createFsClientBase({
454
- db,
455
- helpers,
456
- httpsCallable,
457
- logEvent
458
- }) {
459
- const dbAsFirestore = db;
460
- api.initialize({
461
- db: dbAsFirestore,
462
- helpers,
463
- httpsCallable,
464
- logEvent
1161
+ // src/domains/assignment/services/update-score.service.ts
1162
+ async function _updateScore(params) {
1163
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1164
+ id: params.activityId,
1165
+ userId: params.userId
1166
+ }) : refsScoresPractice.practiceScores({
1167
+ setId: params.activityId,
1168
+ userId: params.userId
1169
+ });
1170
+ await api.updateDoc(path, {
1171
+ ...params.data
465
1172
  });
466
- return {
467
- assignmentRepo: createAssignmentRepo(),
468
- cardRepo: createCardRepo()
469
- };
470
1173
  }
1174
+ var updateScore = withErrorHandler(_updateScore, "updateScore");
1175
+ async function _updateCardScore(params) {
1176
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1177
+ id: params.activityId,
1178
+ userId: params.userId
1179
+ }) : refsScoresPractice.practiceScores({
1180
+ setId: params.activityId,
1181
+ userId: params.userId
1182
+ });
1183
+ const updates = Object.keys(params.updates.cardScore).reduce(
1184
+ (acc, key) => {
1185
+ acc[`cards.${params.cardId}.${key}`] = params.updates.cardScore[key];
1186
+ return acc;
1187
+ },
1188
+ {}
1189
+ );
1190
+ if (params.updates.progress) {
1191
+ updates.progress = params.updates.progress;
1192
+ }
1193
+ if (params.updates.score) {
1194
+ updates.score = params.updates.score;
1195
+ }
1196
+ await api.updateDoc(path, {
1197
+ ...updates
1198
+ });
1199
+ }
1200
+ var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
471
1201
 
472
- // src/lib/create-firebase-client-native.ts
473
- var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
1202
+ // src/domains/assignment/services/clear-score.service.ts
1203
+ var import_dayjs3 = __toESM(require("dayjs"));
1204
+ async function clearScore(params) {
1205
+ var _a, _b, _c, _d, _e;
1206
+ const update = {
1207
+ [`cards.${params.cardId}`]: {
1208
+ attempts: ((_a = params.cardScores.attempts) != null ? _a : 1) + 1,
1209
+ correct: (_b = params.cardScores.correct) != null ? _b : 0,
1210
+ // save old score history
1211
+ history: [
1212
+ {
1213
+ ...params.cardScores,
1214
+ attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1215
+ correct: (_d = params.cardScores.correct) != null ? _d : 0,
1216
+ retryTime: (0, import_dayjs3.default)().format("YYYY-MM-DD HH:mm:ss"),
1217
+ history: null
1218
+ },
1219
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1220
+ ...(_e = params.cardScores.history) != null ? _e : []
1221
+ ]
1222
+ }
1223
+ };
1224
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1225
+ id: params.activityId,
1226
+ userId: params.userId
1227
+ }) : refsScoresPractice.practiceScores({
1228
+ setId: params.activityId,
1229
+ userId: params.userId
1230
+ });
1231
+ await api.updateDoc(path, update);
1232
+ return {
1233
+ update,
1234
+ activityId: params.activityId
1235
+ };
1236
+ }
1237
+
1238
+ // src/domains/assignment/services/submit-assignment-score.service.ts
1239
+ var import_dayjs4 = __toESM(require("dayjs"));
1240
+ async function _submitAssignmentScore({
1241
+ cardIds,
1242
+ assignment,
1243
+ weights,
1244
+ userId,
1245
+ status,
1246
+ studentName
1247
+ }) {
1248
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1249
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1250
+ const fieldsUpdated = {
1251
+ submitted: true,
1252
+ progress: 100,
1253
+ submissionDate: serverTimestamp2(),
1254
+ status
1255
+ };
1256
+ if (assignment.isAssessment) {
1257
+ const result = await handleAssessment(
1258
+ assignment,
1259
+ userId,
1260
+ cardIds,
1261
+ weights,
1262
+ fieldsUpdated,
1263
+ studentName
1264
+ );
1265
+ return result;
1266
+ } else if (assignment.courseId) {
1267
+ await handleCourseAssignment(assignment, userId);
1268
+ }
1269
+ await api.updateDoc(path, { ...fieldsUpdated });
1270
+ return { success: true, fieldsUpdated };
1271
+ }
1272
+ var submitAssignmentScore = withErrorHandler(
1273
+ _submitAssignmentScore,
1274
+ "submitAssignmentScore"
1275
+ );
1276
+ async function handleAssessment(assignment, userId, cardIds, weights, fieldsUpdated, studentName) {
1277
+ var _a, _b, _c;
1278
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1279
+ const response = await api.getDoc(path);
1280
+ if (!response.data) {
1281
+ throw new Error("Score not found");
1282
+ }
1283
+ const { score: scoreCalculated } = calculateScoreAndProgress_default(response.data, cardIds, weights);
1284
+ await api.updateDoc(path, { score: scoreCalculated, status: "PENDING_REVIEW" });
1285
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssessment")) == null ? void 0 : _c({
1286
+ assignmentId: assignment.id,
1287
+ assignmentTitle: assignment.name,
1288
+ userId,
1289
+ teacherId: assignment.owners[0],
1290
+ studentName
1291
+ }));
1292
+ fieldsUpdated.status = "PENDING_REVIEW";
1293
+ return { success: true, fieldsUpdated };
1294
+ }
1295
+ async function handleCourseAssignment(assignment, userId) {
1296
+ var _a, _b, _c;
1297
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentV2")) == null ? void 0 : _c({
1298
+ assignmentId: assignment.id,
1299
+ userId
1300
+ }));
1301
+ }
1302
+ async function submitPracticeScore({
1303
+ setId,
1304
+ userId,
1305
+ scores
1306
+ }) {
1307
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1308
+ const date = (0, import_dayjs4.default)().format("YYYY-MM-DD-HH-mm");
1309
+ const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
1310
+ const fieldsUpdated = {
1311
+ ...scores,
1312
+ submitted: true,
1313
+ progress: 100,
1314
+ submissionDate: serverTimestamp2(),
1315
+ status: "SUBMITTED"
1316
+ };
1317
+ await api.setDoc(ref, { ...fieldsUpdated });
1318
+ const refScores = refsScoresPractice.practiceScores({ userId, setId });
1319
+ await api.deleteDoc(refScores);
1320
+ return { success: true, fieldsUpdated };
1321
+ }
1322
+
1323
+ // src/domains/assignment/hooks/score-hooks.ts
1324
+ var scoreQueryKeys = {
1325
+ all: ["scores"],
1326
+ byId: (id) => [...scoreQueryKeys.all, id],
1327
+ list: () => [...scoreQueryKeys.all, "list"]
1328
+ };
1329
+ function useScore({
1330
+ isAssignment,
1331
+ activityId,
1332
+ userId = "",
1333
+ courseId,
1334
+ enabled = true,
1335
+ googleClassroomUserId
1336
+ }) {
1337
+ return (0, import_react_query2.useQuery)({
1338
+ queryFn: () => getScore({
1339
+ userId,
1340
+ courseId,
1341
+ activityId,
1342
+ googleClassroomUserId,
1343
+ isAssignment
1344
+ }),
1345
+ queryKey: scoreQueryKeys.byId(activityId),
1346
+ enabled
1347
+ });
1348
+ }
1349
+ var debounceUpdateScore = debounce(updateScore, 1e3);
1350
+ function useUpdateScore() {
1351
+ const { queryClient } = useSpeakableApi();
1352
+ const mutation = (0, import_react_query2.useMutation)({
1353
+ mutationFn: debounceUpdateScore,
1354
+ onMutate: (variables) => {
1355
+ return handleOptimisticUpdate({
1356
+ queryClient,
1357
+ queryKey: scoreQueryKeys.byId(variables.activityId),
1358
+ newData: variables.data
1359
+ });
1360
+ },
1361
+ onError: (_, variables, context) => {
1362
+ if (context == null ? void 0 : context.previousData)
1363
+ queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1364
+ },
1365
+ onSettled: (_, err, variables) => {
1366
+ queryClient.invalidateQueries({
1367
+ queryKey: scoreQueryKeys.byId(variables.activityId)
1368
+ });
1369
+ }
1370
+ });
1371
+ return {
1372
+ mutationUpdateScore: mutation
1373
+ };
1374
+ }
1375
+ function useUpdateCardScore({
1376
+ isAssignment,
1377
+ activityId,
1378
+ userId,
1379
+ cardIds,
1380
+ weights
1381
+ }) {
1382
+ const { queryClient } = useSpeakableApi();
1383
+ const queryKey = scoreQueryKeys.byId(activityId);
1384
+ const mutation = (0, import_react_query2.useMutation)({
1385
+ mutationFn: async ({ cardId, cardScore }) => {
1386
+ const previousScores = queryClient.getQueryData(queryKey);
1387
+ const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1388
+ previousScores: previousScores != null ? previousScores : {},
1389
+ cardId,
1390
+ cardScore,
1391
+ cardIds,
1392
+ weights
1393
+ });
1394
+ await updateCardScore({
1395
+ userId,
1396
+ cardId,
1397
+ isAssignment,
1398
+ activityId,
1399
+ updates: {
1400
+ cardScore: updatedCardScore,
1401
+ progress,
1402
+ score
1403
+ }
1404
+ });
1405
+ return { cardId, scoresUpdated: newScoreUpdated };
1406
+ },
1407
+ onMutate: ({ cardId, cardScore }) => {
1408
+ queryClient.setQueryData(queryKey, (previousScore) => {
1409
+ const updates = handleOptimisticScore({
1410
+ score: previousScore,
1411
+ cardId,
1412
+ cardScore,
1413
+ cardIds,
1414
+ weights
1415
+ });
1416
+ return {
1417
+ ...previousScore,
1418
+ ...updates
1419
+ };
1420
+ });
1421
+ },
1422
+ // eslint-disable-next-line no-unused-vars
1423
+ onError: (error) => {
1424
+ console.log(error.message);
1425
+ const previousData = queryClient.getQueryData(queryKey);
1426
+ if (previousData) {
1427
+ queryClient.setQueryData(queryKey, previousData);
1428
+ }
1429
+ }
1430
+ });
1431
+ return {
1432
+ mutationUpdateCardScore: mutation
1433
+ };
1434
+ }
1435
+ var getScoreUpdated = ({
1436
+ cardId,
1437
+ cardScore,
1438
+ previousScores,
1439
+ cardIds,
1440
+ weights
1441
+ }) => {
1442
+ var _a, _b;
1443
+ const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
1444
+ const newCardScore = {
1445
+ ...previousCard != null ? previousCard : {},
1446
+ ...cardScore
1447
+ };
1448
+ const newScores = {
1449
+ ...previousScores,
1450
+ cards: {
1451
+ ...(_b = previousScores.cards) != null ? _b : {},
1452
+ [cardId]: newCardScore
1453
+ }
1454
+ };
1455
+ const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
1456
+ return {
1457
+ newScoreUpdated: newScores,
1458
+ updatedCardScore: cardScore,
1459
+ score,
1460
+ progress
1461
+ };
1462
+ };
1463
+ var handleOptimisticScore = ({
1464
+ score,
1465
+ cardId,
1466
+ cardScore,
1467
+ cardIds,
1468
+ weights
1469
+ }) => {
1470
+ var _a;
1471
+ let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
1472
+ cards = {
1473
+ ...cards,
1474
+ [cardId]: {
1475
+ ...cards[cardId],
1476
+ ...cardScore
1477
+ }
1478
+ };
1479
+ const { score: scoreValue, progress } = calculateScoreAndProgress_default(
1480
+ // @ts-ignore
1481
+ {
1482
+ ...score != null ? score : {},
1483
+ cards
1484
+ },
1485
+ cardIds,
1486
+ weights
1487
+ );
1488
+ return { cards, score: scoreValue, progress };
1489
+ };
1490
+ function useClearScore() {
1491
+ const { queryClient } = useSpeakableApi();
1492
+ const mutation = (0, import_react_query2.useMutation)({
1493
+ mutationFn: clearScore,
1494
+ onSettled: (result) => {
1495
+ var _a;
1496
+ queryClient.invalidateQueries({
1497
+ queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
1498
+ });
1499
+ }
1500
+ });
1501
+ return {
1502
+ mutationClearScore: mutation
1503
+ };
1504
+ }
1505
+ function useSubmitAssignmentScore({
1506
+ onAssignmentSubmitted,
1507
+ studentName
1508
+ }) {
1509
+ const { queryClient } = useSpeakableApi();
1510
+ const { hasGoogleClassroomGradePassback } = usePermissions_default();
1511
+ const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
1512
+ const { createNotification: createNotification2 } = useCreateNotification();
1513
+ const mutation = (0, import_react_query2.useMutation)({
1514
+ mutationFn: async ({
1515
+ assignment,
1516
+ userId,
1517
+ cardIds,
1518
+ weights,
1519
+ scores,
1520
+ status
1521
+ }) => {
1522
+ try {
1523
+ const scoreUpdated = await submitAssignmentScore({
1524
+ assignment,
1525
+ userId,
1526
+ cardIds,
1527
+ weights,
1528
+ status,
1529
+ studentName
1530
+ });
1531
+ if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
1532
+ await submitAssignmentToGoogleClassroom({
1533
+ assignment,
1534
+ scores
1535
+ });
1536
+ }
1537
+ if (assignment.isAssessment) {
1538
+ createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1539
+ }
1540
+ if (assignment == null ? void 0 : assignment.id) {
1541
+ logSubmitAssignment({
1542
+ courseId: assignment == null ? void 0 : assignment.courseId
1543
+ });
1544
+ }
1545
+ onAssignmentSubmitted(assignment.id);
1546
+ queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1547
+ ...scores,
1548
+ ...scoreUpdated.fieldsUpdated
1549
+ });
1550
+ return {
1551
+ success: true,
1552
+ message: "Score submitted successfully"
1553
+ };
1554
+ } catch (error) {
1555
+ return {
1556
+ success: false,
1557
+ error
1558
+ };
1559
+ }
1560
+ }
1561
+ });
1562
+ return {
1563
+ submitAssignmentScore: mutation.mutateAsync,
1564
+ isLoading: mutation.isPending
1565
+ };
1566
+ }
1567
+ function useSubmitPracticeScore() {
1568
+ const { queryClient } = useSpeakableApi();
1569
+ const mutation = (0, import_react_query2.useMutation)({
1570
+ mutationFn: async ({
1571
+ setId,
1572
+ userId,
1573
+ scores
1574
+ }) => {
1575
+ try {
1576
+ await submitPracticeScore({
1577
+ setId,
1578
+ userId,
1579
+ scores
1580
+ });
1581
+ queryClient.invalidateQueries({
1582
+ queryKey: scoreQueryKeys.byId(setId)
1583
+ });
1584
+ return {
1585
+ success: true,
1586
+ message: "Score submitted successfully"
1587
+ };
1588
+ } catch (error) {
1589
+ return {
1590
+ success: false,
1591
+ error
1592
+ };
1593
+ }
1594
+ }
1595
+ });
1596
+ return {
1597
+ submitPracticeScore: mutation.mutateAsync,
1598
+ isLoading: mutation.isPending
1599
+ };
1600
+ }
1601
+
1602
+ // src/domains/cards/card.hooks.ts
1603
+ var import_react_query3 = require("@tanstack/react-query");
1604
+ var import_react2 = require("react");
1605
+
1606
+ // src/domains/cards/card.constants.ts
1607
+ var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
1608
+ FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
1609
+ FeedbackTypesCard2["Wida"] = "wida";
1610
+ FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
1611
+ FeedbackTypesCard2["Actfl"] = "actfl";
1612
+ FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
1613
+ return FeedbackTypesCard2;
1614
+ })(FeedbackTypesCard || {});
1615
+ var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
1616
+ LeniencyCard2["CONFIDENCE"] = "confidence";
1617
+ LeniencyCard2["EASY"] = "easy";
1618
+ LeniencyCard2["NORMAL"] = "normal";
1619
+ LeniencyCard2["HARD"] = "hard";
1620
+ return LeniencyCard2;
1621
+ })(LeniencyCard || {});
1622
+ var LENIENCY_OPTIONS = [
1623
+ {
1624
+ label: "Build Confidence - most lenient",
1625
+ value: "confidence" /* CONFIDENCE */
1626
+ },
1627
+ {
1628
+ label: "Very Lenient",
1629
+ value: "easy" /* EASY */
1630
+ },
1631
+ {
1632
+ label: "Normal",
1633
+ value: "normal" /* NORMAL */
1634
+ },
1635
+ {
1636
+ label: "No leniency - most strict",
1637
+ value: "hard" /* HARD */
1638
+ }
1639
+ ];
1640
+ var STUDENT_LEVELS_OPTIONS = [
1641
+ {
1642
+ label: "Beginner",
1643
+ description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
1644
+ value: "beginner"
1645
+ },
1646
+ {
1647
+ label: "Elementary",
1648
+ description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
1649
+ value: "elementary"
1650
+ },
1651
+ {
1652
+ label: "Intermediate",
1653
+ description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
1654
+ value: "intermediate"
1655
+ },
1656
+ {
1657
+ label: "Advanced",
1658
+ description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
1659
+ value: "advanced"
1660
+ },
1661
+ {
1662
+ label: "Fluent",
1663
+ description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
1664
+ value: "fluent"
1665
+ },
1666
+ {
1667
+ label: "Native-like",
1668
+ description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
1669
+ value: "nativeLike"
1670
+ }
1671
+ ];
1672
+ var BASE_RESPOND_FIELD_VALUES = {
1673
+ title: "",
1674
+ allowRetries: true,
1675
+ respondTime: 180,
1676
+ maxCharacters: 1e3
1677
+ };
1678
+ var BASE_REPEAT_FIELD_VALUES = {
1679
+ repeat: 1
1680
+ };
1681
+ var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
1682
+ MCQType: "single",
1683
+ answer: ["A"],
1684
+ choices: [
1685
+ { option: "A", value: "Option A" },
1686
+ { option: "B", value: "Option B" },
1687
+ { option: "C", value: "Option C" }
1688
+ ]
1689
+ };
1690
+ var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
1691
+ VerificationCardStatus2["VERIFIED"] = "VERIFIED";
1692
+ VerificationCardStatus2["WARNING"] = "WARNING";
1693
+ VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
1694
+ VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
1695
+ VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
1696
+ return VerificationCardStatus2;
1697
+ })(VerificationCardStatus || {});
1698
+ var CARDS_COLLECTION = "flashcards";
1699
+ var refsCardsFiresotre = {
1700
+ allCards: CARDS_COLLECTION,
1701
+ card: (id) => `${CARDS_COLLECTION}/${id}`
1702
+ };
1703
+
1704
+ // src/domains/cards/services/get-card.service.ts
1705
+ async function _getCard(params) {
1706
+ const ref = refsCardsFiresotre.card(params.cardId);
1707
+ const response = await api.getDoc(ref);
1708
+ if (!response.data) return null;
1709
+ return response.data;
1710
+ }
1711
+ var getCard = withErrorHandler(_getCard, "getCard");
1712
+
1713
+ // src/domains/cards/services/create-card.service.ts
1714
+ var import_uuid = require("uuid");
1715
+
1716
+ // src/domains/cards/card.model.ts
1717
+ var ActivityPageType = /* @__PURE__ */ ((ActivityPageType2) => {
1718
+ ActivityPageType2["READ_REPEAT"] = "READ_REPEAT";
1719
+ ActivityPageType2["VIDEO"] = "VIDEO";
1720
+ ActivityPageType2["TEXT"] = "TEXT";
1721
+ ActivityPageType2["READ_RESPOND"] = "READ_RESPOND";
1722
+ ActivityPageType2["FREE_RESPONSE"] = "FREE_RESPONSE";
1723
+ ActivityPageType2["REPEAT"] = "REPEAT";
1724
+ ActivityPageType2["RESPOND"] = "RESPOND";
1725
+ ActivityPageType2["RESPOND_WRITE"] = "RESPOND_WRITE";
1726
+ ActivityPageType2["TEXT_TO_SPEECH"] = "TEXT_TO_SPEECH";
1727
+ ActivityPageType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
1728
+ ActivityPageType2["PODCAST"] = "PODCAST";
1729
+ ActivityPageType2["MEDIA_PAGE"] = "MEDIA_PAGE";
1730
+ ActivityPageType2["WRITE"] = "WRITE";
1731
+ ActivityPageType2["SHORT_ANSWER"] = "SHORT_ANSWER";
1732
+ ActivityPageType2["SHORT_STORY"] = "SHORT_STORY";
1733
+ ActivityPageType2["SPEAK"] = "SPEAK";
1734
+ ActivityPageType2["CONVERSATION"] = "CONVERSATION";
1735
+ ActivityPageType2["CONVERSATION_WRITE"] = "CONVERSATION_WRITE";
1736
+ ActivityPageType2["DIALOGUE"] = "DIALOGUE";
1737
+ ActivityPageType2["INSTRUCTION"] = "INSTRUCTION";
1738
+ ActivityPageType2["LISTEN"] = "LISTEN";
1739
+ ActivityPageType2["READ"] = "READ";
1740
+ ActivityPageType2["ANSWER"] = "ANSWER";
1741
+ return ActivityPageType2;
1742
+ })(ActivityPageType || {});
1743
+ var RESPOND_PAGE_ACTIVITY_TYPES = [
1744
+ "READ_RESPOND" /* READ_RESPOND */,
1745
+ "RESPOND" /* RESPOND */,
1746
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1747
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1748
+ ];
1749
+ var MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
1750
+ var REPEAT_PAGE_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
1751
+ var RESPOND_WRITE_PAGE_ACTIVITY_TYPES = [
1752
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1753
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1754
+ ];
1755
+ var RESPOND_AUDIO_PAGE_ACTIVITY_TYPES = [
1756
+ "RESPOND" /* RESPOND */,
1757
+ "READ_RESPOND" /* READ_RESPOND */
1758
+ ];
1759
+
1760
+ // src/utils/text-utils.ts
1761
+ var import_js_sha1 = __toESM(require("js-sha1"));
1762
+ var purify = (word) => {
1763
+ return word.normalize("NFD").replace(/\/([^" "]*)/g, "").replace(/\([^()]*\)/g, "").replace(/([^()]*)/g, "").replace(/[\u0300-\u036f]/g, "").replace(/[-]/g, " ").replace(/[.,/#!¡¿?؟。,.?$%^&*;:{}=\-_`~()’'…\s]/g, "").replace(/\s\s+/g, " ").toLowerCase().trim();
1764
+ };
1765
+ var cleanString = (words) => {
1766
+ const splitWords = words == null ? void 0 : words.split("+");
1767
+ if (splitWords && splitWords.length === 1) {
1768
+ const newWord = purify(words);
1769
+ return newWord;
1770
+ } else if (splitWords && splitWords.length > 1) {
1771
+ const split = splitWords.map((w) => purify(w));
1772
+ return split;
1773
+ } else {
1774
+ return "";
1775
+ }
1776
+ };
1777
+ var getWordHash = (word, language) => {
1778
+ const cleanedWord = cleanString(word);
1779
+ const wordHash = (0, import_js_sha1.default)(`${language}-${cleanedWord}`);
1780
+ console.log("wordHash core library", wordHash);
1781
+ return wordHash;
1782
+ };
1783
+ function getPhraseLength(phrase, input) {
1784
+ if (Array.isArray(phrase) && phrase.includes(input)) {
1785
+ return phrase[phrase.indexOf(input)].split(" ").length;
1786
+ } else {
1787
+ return phrase ? phrase.split(" ").length : 0;
1788
+ }
1789
+ }
1790
+
1791
+ // src/domains/cards/services/get-card-verification-status.service.ts
1792
+ var charactarLanguages = ["zh", "ja", "ko"];
1793
+ var getVerificationStatus = async (target_text, language) => {
1794
+ if ((target_text == null ? void 0 : target_text.length) < 3 && !charactarLanguages.includes(language)) {
1795
+ return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1796
+ }
1797
+ const hash = getWordHash(target_text, language);
1798
+ const response = await api.getDoc(`checked-pronunciations/${hash}`);
1799
+ try {
1800
+ if (response.data) {
1801
+ return processRecord(response.data);
1802
+ } else {
1803
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1804
+ }
1805
+ } catch (e) {
1806
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1807
+ }
1808
+ };
1809
+ var processRecord = (data) => {
1810
+ const { pronunciations = 0, fails = 0 } = data;
1811
+ const attempts = pronunciations + fails;
1812
+ const successRate = attempts > 0 ? pronunciations / attempts * 100 : 0;
1813
+ let newStatus = null;
1814
+ if (attempts < 6) {
1815
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1816
+ }
1817
+ if (successRate > 25) {
1818
+ newStatus = "VERIFIED" /* VERIFIED */;
1819
+ } else if (successRate > 10) {
1820
+ newStatus = "WARNING" /* WARNING */;
1821
+ } else if (fails > 20 && successRate < 10 && pronunciations > 1) {
1822
+ newStatus = "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1823
+ } else if (pronunciations === 0 && fails > 20) {
1824
+ newStatus = "NOT_WORKING" /* NOT_WORKING */;
1825
+ } else {
1826
+ newStatus = "NOT_CHECKED" /* NOT_CHECKED */;
1827
+ }
1828
+ return newStatus;
1829
+ };
1830
+
1831
+ // src/domains/cards/services/create-card.service.ts
1832
+ async function _createCard({ data }) {
1833
+ const response = await api.addDoc(refsCardsFiresotre.allCards, data);
1834
+ return response;
1835
+ }
1836
+ var createCard = withErrorHandler(_createCard, "createCard");
1837
+ async function _createCards({ cards }) {
1838
+ const { writeBatch: writeBatch2, doc: doc2 } = api.accessHelpers();
1839
+ const batch = writeBatch2();
1840
+ const cardsWithId = [];
1841
+ for (const card of cards) {
1842
+ const cardId = (0, import_uuid.v4)();
1843
+ const ref = doc2(refsCardsFiresotre.card(cardId));
1844
+ const newCardObject = {
1845
+ ...card,
1846
+ id: cardId
1847
+ };
1848
+ if (card.type === "READ_REPEAT" /* READ_REPEAT */ && card.target_text && card.language) {
1849
+ const verificationStatus = await getVerificationStatus(card.target_text, card.language);
1850
+ newCardObject.verificationStatus = verificationStatus || null;
1851
+ }
1852
+ cardsWithId.push(newCardObject);
1853
+ batch.set(ref, newCardObject);
1854
+ }
1855
+ await batch.commit();
1856
+ return cardsWithId;
1857
+ }
1858
+ var createCards = withErrorHandler(_createCards, "createCards");
1859
+
1860
+ // src/domains/cards/card.hooks.ts
1861
+ var cardsQueryKeys = {
1862
+ all: ["cards"],
1863
+ one: (params) => [...cardsQueryKeys.all, params.cardId]
1864
+ };
1865
+ function useCards({
1866
+ cardIds,
1867
+ enabled = true,
1868
+ asObject
1869
+ }) {
1870
+ const queries = (0, import_react_query3.useQueries)({
1871
+ queries: cardIds.map((cardId) => ({
1872
+ enabled: enabled && cardIds.length > 0,
1873
+ queryKey: cardsQueryKeys.one({
1874
+ cardId
1875
+ }),
1876
+ queryFn: () => getCard({ cardId })
1877
+ }))
1878
+ });
1879
+ const cards = queries.map((query2) => query2.data).filter(Boolean);
1880
+ const cardsObject = (0, import_react2.useMemo)(() => {
1881
+ if (!asObject) return null;
1882
+ return cards.reduce((acc, card) => {
1883
+ acc[card.id] = card;
1884
+ return acc;
1885
+ }, {});
1886
+ }, [asObject, cards]);
1887
+ return {
1888
+ cards,
1889
+ cardsObject,
1890
+ cardsQueries: queries
1891
+ };
1892
+ }
1893
+ function useCreateCard() {
1894
+ const { queryClient } = useSpeakableApi();
1895
+ const mutationCreateCard = (0, import_react_query3.useMutation)({
1896
+ mutationFn: createCard,
1897
+ onSuccess: (cardCreated) => {
1898
+ queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
1899
+ }
1900
+ });
1901
+ return {
1902
+ mutationCreateCard
1903
+ };
1904
+ }
1905
+ function useCreateCards() {
1906
+ const mutationCreateCards = (0, import_react_query3.useMutation)({
1907
+ mutationFn: createCards
1908
+ });
1909
+ return {
1910
+ mutationCreateCards
1911
+ };
1912
+ }
1913
+ function getCardFromCache({
1914
+ cardId,
1915
+ queryClient
1916
+ }) {
1917
+ return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
1918
+ }
1919
+ function updateCardInCache({
1920
+ cardId,
1921
+ card,
1922
+ queryClient
1923
+ }) {
1924
+ queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
1925
+ }
1926
+ function useGetCard({ cardId, enabled = true }) {
1927
+ const query2 = (0, import_react_query3.useQuery)({
1928
+ queryKey: cardsQueryKeys.one({ cardId }),
1929
+ queryFn: () => getCard({ cardId }),
1930
+ enabled: enabled && !!cardId
1931
+ });
1932
+ return query2;
1933
+ }
1934
+
1935
+ // src/domains/cards/card.repo.ts
1936
+ var createCardRepo = () => {
1937
+ return {
1938
+ createCard,
1939
+ createCards,
1940
+ getCard
1941
+ };
1942
+ };
1943
+
1944
+ // src/domains/cards/utils/check-page-type.ts
1945
+ function checkIsRepeatPage(cardType) {
1946
+ if (cardType === void 0) return false;
1947
+ return REPEAT_PAGE_ACTIVITY_TYPES.includes(cardType);
1948
+ }
1949
+ function checkIsMCPage(cardType) {
1950
+ if (cardType === void 0) return false;
1951
+ return MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES.includes(cardType);
1952
+ }
1953
+ function checkIsRespondPage(cardType) {
1954
+ if (cardType === void 0) return false;
1955
+ return RESPOND_PAGE_ACTIVITY_TYPES.includes(cardType);
1956
+ }
1957
+ function checkIsRespondWrittenPage(cardType) {
1958
+ if (cardType === void 0) return false;
1959
+ return RESPOND_WRITE_PAGE_ACTIVITY_TYPES.includes(cardType);
1960
+ }
1961
+ function checkIsRespondAudioPage(cardType) {
1962
+ if (cardType === void 0) return false;
1963
+ return RESPOND_AUDIO_PAGE_ACTIVITY_TYPES.includes(cardType);
1964
+ }
1965
+ var checkIsMediaPage = (cardType) => {
1966
+ if (cardType === void 0) return false;
1967
+ return cardType === "MEDIA_PAGE" /* MEDIA_PAGE */;
1968
+ };
1969
+ var checkIsShortAnswerPage = (cardType) => {
1970
+ if (cardType === void 0) return false;
1971
+ return cardType === "SHORT_ANSWER" /* SHORT_ANSWER */;
1972
+ };
1973
+ var checkTypePageActivity = (cardType) => {
1974
+ const isRespondAudio = checkIsRespondAudioPage(cardType);
1975
+ const isRespondWritten = checkIsRespondWrittenPage(cardType);
1976
+ const isRespond = checkIsRespondPage(cardType);
1977
+ const isMC = checkIsMCPage(cardType);
1978
+ const isRepeat = checkIsRepeatPage(cardType);
1979
+ const isMediaPage = checkIsMediaPage(cardType);
1980
+ const isShortAnswer = checkIsShortAnswerPage(cardType);
1981
+ return {
1982
+ isRespondAudio,
1983
+ isRespondWritten,
1984
+ isRespond,
1985
+ isMC,
1986
+ isRepeat,
1987
+ isMediaPage,
1988
+ isShortAnswer
1989
+ };
1990
+ };
1991
+
1992
+ // src/domains/cards/utils/get-page-prompt.ts
1993
+ function extractTextFromRichText(richText) {
1994
+ if (!richText) return "";
1995
+ return richText.replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").trim();
1996
+ }
1997
+ function getPagePrompt(card) {
1998
+ if (!card) return { has: false, text: "", rich_text: "", isTextEqualToRichText: false };
1999
+ const { isMC, isRepeat, isRespond, isShortAnswer } = checkTypePageActivity(card == null ? void 0 : card.type);
2000
+ const hidePrompt = (card == null ? void 0 : card.hidePrompt) === true;
2001
+ const createReturnObject = (text, richText) => {
2002
+ const plainText = text || "";
2003
+ const richTextPlain = extractTextFromRichText(richText);
2004
+ return {
2005
+ has: true,
2006
+ text: plainText,
2007
+ rich_text: richText || "",
2008
+ isTextEqualToRichText: plainText.trim() === richTextPlain.trim()
2009
+ };
2010
+ };
2011
+ if (isRepeat) {
2012
+ return createReturnObject(card == null ? void 0 : card.target_text, card == null ? void 0 : card.rich_text);
2013
+ }
2014
+ if (isRespond && !hidePrompt) {
2015
+ return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
2016
+ }
2017
+ if (isMC) {
2018
+ return createReturnObject(card == null ? void 0 : card.question, card == null ? void 0 : card.rich_text);
2019
+ }
2020
+ if (isShortAnswer && !hidePrompt) {
2021
+ return createReturnObject(card == null ? void 0 : card.prompt, card == null ? void 0 : card.rich_text);
2022
+ }
2023
+ return {
2024
+ has: false,
2025
+ text: "",
2026
+ rich_text: "",
2027
+ isTextEqualToRichText: false
2028
+ };
2029
+ }
2030
+
2031
+ // src/domains/cards/utils/get-completed-pages.ts
2032
+ var getTotalCompletedCards = (pageScores) => {
2033
+ return Object.values(pageScores != null ? pageScores : {}).reduce((acc, cardScore) => {
2034
+ var _a, _b;
2035
+ if (((_b = (_a = cardScore.completed) != null ? _a : cardScore.score) != null ? _b : cardScore.score === 0) && !cardScore.media_area_opened) {
2036
+ acc++;
2037
+ }
2038
+ return acc;
2039
+ }, 0);
2040
+ };
2041
+
2042
+ // src/domains/cards/utils/get-label-page.ts
2043
+ var labels = {
2044
+ repeat: {
2045
+ short: "Repeat",
2046
+ long: "Listen & Repeat"
2047
+ },
2048
+ mc: {
2049
+ short: "Multiple Choice",
2050
+ long: "Multiple Choice"
2051
+ },
2052
+ mediaPage: {
2053
+ short: "Media Page",
2054
+ long: "Media Page"
2055
+ },
2056
+ shortAnswer: {
2057
+ short: "Short Answer",
2058
+ long: "Short Answer"
2059
+ },
2060
+ respondWritten: {
2061
+ short: "Open Response",
2062
+ long: "Written Open Response"
2063
+ },
2064
+ respondAudio: {
2065
+ short: "Open Response",
2066
+ long: "Spoken Open Response"
2067
+ }
2068
+ };
2069
+ var getLabelPage = (pageType) => {
2070
+ if (!pageType) {
2071
+ return {
2072
+ short: "",
2073
+ long: ""
2074
+ };
2075
+ }
2076
+ const { isRepeat, isMC, isMediaPage, isShortAnswer, isRespondWritten, isRespondAudio } = checkTypePageActivity(pageType);
2077
+ if (isRepeat) {
2078
+ return labels.repeat;
2079
+ }
2080
+ if (isMC) {
2081
+ return labels.mc;
2082
+ }
2083
+ if (isMediaPage) {
2084
+ return labels.mediaPage;
2085
+ }
2086
+ if (isShortAnswer) {
2087
+ return labels.shortAnswer;
2088
+ }
2089
+ if (isRespondWritten) {
2090
+ return labels.respondWritten;
2091
+ }
2092
+ if (isRespondAudio) {
2093
+ return labels.respondAudio;
2094
+ }
2095
+ return {
2096
+ short: "",
2097
+ long: ""
2098
+ };
2099
+ };
2100
+
2101
+ // src/domains/sets/set.hooks.ts
2102
+ var import_react_query4 = require("@tanstack/react-query");
2103
+
2104
+ // src/domains/sets/set.constants.ts
2105
+ var SETS_COLLECTION = "sets";
2106
+ var refsSetsFirestore = {
2107
+ allSets: SETS_COLLECTION,
2108
+ set: (id) => `${SETS_COLLECTION}/${id}`
2109
+ };
2110
+
2111
+ // src/domains/sets/services/get-set.service.ts
2112
+ async function _getSet({ setId }) {
2113
+ const response = await api.getDoc(refsSetsFirestore.set(setId));
2114
+ return response.data;
2115
+ }
2116
+ var getSet = withErrorHandler(_getSet, "getSet");
2117
+
2118
+ // src/domains/sets/set.hooks.ts
2119
+ var setsQueryKeys = {
2120
+ all: ["sets"],
2121
+ one: (params) => [...setsQueryKeys.all, params.setId]
2122
+ };
2123
+ var useSet = ({ setId, enabled }) => {
2124
+ return (0, import_react_query4.useQuery)({
2125
+ queryKey: setsQueryKeys.one({ setId }),
2126
+ queryFn: () => getSet({ setId }),
2127
+ enabled: setId !== void 0 && setId !== "" && enabled
2128
+ });
2129
+ };
2130
+ function getSetFromCache({
2131
+ setId,
2132
+ queryClient
2133
+ }) {
2134
+ if (!setId) return null;
2135
+ return queryClient.getQueryData(setsQueryKeys.one({ setId }));
2136
+ }
2137
+ function updateSetInCache({
2138
+ set,
2139
+ queryClient
2140
+ }) {
2141
+ const { id, ...setData } = set;
2142
+ queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
2143
+ }
2144
+
2145
+ // src/domains/sets/set.repo.ts
2146
+ var createSetRepo = () => {
2147
+ return {
2148
+ getSet
2149
+ };
2150
+ };
2151
+
2152
+ // src/constants/all-langs.json
2153
+ var all_langs_default = {
2154
+ af: "Afrikaans",
2155
+ sq: "Albanian",
2156
+ am: "Amharic",
2157
+ ar: "Arabic",
2158
+ hy: "Armenian",
2159
+ az: "Azerbaijani",
2160
+ eu: "Basque",
2161
+ be: "Belarusian",
2162
+ bn: "Bengali",
2163
+ bs: "Bosnian",
2164
+ bg: "Bulgarian",
2165
+ ca: "Catalan",
2166
+ ceb: "Cebuano",
2167
+ zh: "Chinese",
2168
+ co: "Corsican",
2169
+ hr: "Croatian",
2170
+ cs: "Czech",
2171
+ da: "Danish",
2172
+ nl: "Dutch",
2173
+ en: "English",
2174
+ eo: "Esperanto",
2175
+ et: "Estonian",
2176
+ fi: "Finnish",
2177
+ fr: "French",
2178
+ fy: "Frisian",
2179
+ gl: "Galician",
2180
+ ka: "Georgian",
2181
+ de: "German",
2182
+ el: "Greek",
2183
+ gu: "Gujarati",
2184
+ ht: "Haitian Creole",
2185
+ ha: "Hausa",
2186
+ haw: "Hawaiian",
2187
+ he: "Hebrew",
2188
+ hi: "Hindi",
2189
+ hmn: "Hmong",
2190
+ hu: "Hungarian",
2191
+ is: "Icelandic",
2192
+ ig: "Igbo",
2193
+ id: "Indonesian",
2194
+ ga: "Irish",
2195
+ it: "Italian",
2196
+ ja: "Japanese",
2197
+ jv: "Javanese",
2198
+ kn: "Kannada",
2199
+ kk: "Kazakh",
2200
+ km: "Khmer",
2201
+ ko: "Korean",
2202
+ ku: "Kurdish",
2203
+ ky: "Kyrgyz",
2204
+ lo: "Lao",
2205
+ la: "Latin",
2206
+ lv: "Latvian",
2207
+ lt: "Lithuanian",
2208
+ lb: "Luxembourgish",
2209
+ mk: "Macedonian",
2210
+ mg: "Malagasy",
2211
+ ms: "Malay",
2212
+ ml: "Malayalam",
2213
+ mt: "Maltese",
2214
+ mi: "Maori",
2215
+ mr: "Marathi",
2216
+ mn: "Mongolian",
2217
+ my: "Myanmar (Burmese)",
2218
+ ne: "Nepali",
2219
+ no: "Norwegian",
2220
+ ny: "Nyanja (Chichewa)",
2221
+ ps: "Pashto",
2222
+ fa: "Persian",
2223
+ pl: "Polish",
2224
+ pt: "Portuguese",
2225
+ pa: "Punjabi",
2226
+ ro: "Romanian",
2227
+ ru: "Russian",
2228
+ sm: "Samoan",
2229
+ gd: "Scots Gaelic",
2230
+ sr: "Serbian",
2231
+ st: "Sesotho",
2232
+ sn: "Shona",
2233
+ sd: "Sindhi",
2234
+ si: "Sinhala (Sinhalese)",
2235
+ sk: "Slovak",
2236
+ sl: "Slovenian",
2237
+ so: "Somali",
2238
+ es: "Spanish",
2239
+ su: "Sundanese",
2240
+ sw: "Swahili",
2241
+ sv: "Swedish",
2242
+ tl: "Tagalog (Filipino)",
2243
+ tg: "Tajik",
2244
+ ta: "Tamil",
2245
+ te: "Telugu",
2246
+ th: "Thai",
2247
+ tr: "Turkish",
2248
+ uk: "Ukrainian",
2249
+ ur: "Urdu",
2250
+ uz: "Uzbek",
2251
+ vi: "Vietnamese",
2252
+ cy: "Welsh",
2253
+ xh: "Xhosa",
2254
+ yi: "Yiddish",
2255
+ yo: "Yoruba",
2256
+ zu: "Zulu"
2257
+ };
2258
+
2259
+ // src/utils/ai/get-respond-card-tool.ts
2260
+ var getRespondCardTool = ({
2261
+ language,
2262
+ standard = "actfl"
2263
+ }) => {
2264
+ const lang = all_langs_default[language] || "English";
2265
+ const tool = {
2266
+ tool_choice: {
2267
+ type: "function",
2268
+ function: { name: "get_feedback" }
2269
+ },
2270
+ tools: [
2271
+ {
2272
+ type: "function",
2273
+ function: {
2274
+ name: "get_feedback",
2275
+ description: "Get feedback on a student's response",
2276
+ parameters: {
2277
+ type: "object",
2278
+ required: [
2279
+ "success",
2280
+ "score",
2281
+ "score_justification",
2282
+ "errors",
2283
+ "improvedResponse",
2284
+ "compliments"
2285
+ ],
2286
+ properties: {
2287
+ success: {
2288
+ type: "boolean",
2289
+ 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."
2290
+ },
2291
+ errors: {
2292
+ type: "array",
2293
+ items: {
2294
+ type: "object",
2295
+ required: ["error", "grammar_error_type", "correction", "justification"],
2296
+ properties: {
2297
+ error: {
2298
+ type: "string",
2299
+ description: "The grammatical error in the student's response."
2300
+ },
2301
+ correction: {
2302
+ type: "string",
2303
+ description: "The suggested correction to the error"
2304
+ },
2305
+ justification: {
2306
+ type: "string",
2307
+ description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
2308
+ },
2309
+ grammar_error_type: {
2310
+ type: "string",
2311
+ enum: [
2312
+ "subjVerbAgree",
2313
+ "tenseErrors",
2314
+ "articleMisuse",
2315
+ "prepositionErrors",
2316
+ "adjNounAgree",
2317
+ "pronounErrors",
2318
+ "wordOrder",
2319
+ "verbConjugation",
2320
+ "pluralization",
2321
+ "negationErrors",
2322
+ "modalVerbMisuse",
2323
+ "relativeClause",
2324
+ "auxiliaryVerb",
2325
+ "complexSentenceAgreement",
2326
+ "idiomaticExpression",
2327
+ "registerInconsistency",
2328
+ "voiceMisuse"
2329
+ ],
2330
+ 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"
2331
+ }
2332
+ }
2333
+ },
2334
+ 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."
2335
+ },
2336
+ compliments: {
2337
+ type: "array",
2338
+ items: {
2339
+ type: "string"
2340
+ },
2341
+ description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
2342
+ },
2343
+ improvedResponse: {
2344
+ type: "string",
2345
+ description: "An improved response with proper grammar and more detail, if applicable."
2346
+ },
2347
+ score: {
2348
+ type: "number",
2349
+ description: "A score between 0 and 100, reflecting the overall quality of the response"
2350
+ },
2351
+ score_justification: {
2352
+ type: "string",
2353
+ description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
2354
+ }
2355
+ }
2356
+ }
2357
+ }
2358
+ }
2359
+ ]
2360
+ };
2361
+ if (standard === "wida") {
2362
+ const wida_level = {
2363
+ type: "number",
2364
+ enum: [1, 2, 3, 4, 5, 6],
2365
+ 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
2366
+
2367
+ 1 - Entering
2368
+ 2 - Emerging
2369
+ 3 - Developing
2370
+ 4 - Expanding
2371
+ 5 - Bridging
2372
+ 6 - Reaching
2373
+
2374
+ 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.
2375
+ `
2376
+ };
2377
+ const wida_justification = {
2378
+ type: "string",
2379
+ description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
2380
+ };
2381
+ tool.tools[0].function.parameters.required.push("wida_level");
2382
+ tool.tools[0].function.parameters.required.push("wida_justification");
2383
+ tool.tools[0].function.parameters.properties.wida_level = wida_level;
2384
+ tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
2385
+ } else {
2386
+ const actfl_level = {
2387
+ type: "string",
2388
+ enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
2389
+ 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"
2390
+ };
2391
+ const actfl_justification = {
2392
+ type: "string",
2393
+ description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
2394
+ };
2395
+ tool.tools[0].function.parameters.required.push("actfl_level");
2396
+ tool.tools[0].function.parameters.required.push("actfl_justification");
2397
+ tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
2398
+ tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
2399
+ }
2400
+ return tool;
2401
+ };
2402
+
2403
+ // src/hooks/useActivity.ts
2404
+ var import_react3 = require("react");
2405
+
2406
+ // src/services/add-grading-standard.ts
2407
+ var addGradingStandardLog = async (gradingStandard, userId) => {
2408
+ logGradingStandardLog(gradingStandard);
2409
+ const path = `users/${userId}/grading_standard_logs`;
2410
+ await api.addDoc(path, gradingStandard);
2411
+ };
2412
+
2413
+ // src/hooks/useActivityTracker.ts
2414
+ var import_uuid2 = require("uuid");
2415
+ function useActivityTracker({ userId }) {
2416
+ const trackActivity = async ({
2417
+ activityName,
2418
+ activityType,
2419
+ id = (0, import_uuid2.v4)(),
2420
+ language = ""
2421
+ }) => {
2422
+ if (userId) {
2423
+ const { doc: doc2, serverTimestamp: serverTimestamp2, setDoc: setDoc2 } = api.accessHelpers();
2424
+ const activityRef = doc2(`users/${userId}/activity/${id}`);
2425
+ const timestamp = serverTimestamp2();
2426
+ await setDoc2(activityRef, {
2427
+ name: activityName,
2428
+ type: activityType,
2429
+ lastSeen: timestamp,
2430
+ id,
2431
+ language
2432
+ });
2433
+ }
2434
+ };
2435
+ return {
2436
+ trackActivity
2437
+ };
2438
+ }
2439
+
2440
+ // src/hooks/useActivity.ts
2441
+ function useActivity({
2442
+ id,
2443
+ isAssignment,
2444
+ onAssignmentSubmitted,
2445
+ ltiData
2446
+ }) {
2447
+ var _a, _b;
2448
+ const { queryClient, user } = useSpeakableApi();
2449
+ const userId = user.auth.uid;
2450
+ const assignmentQuery = useAssignment({
2451
+ assignmentId: id,
2452
+ userId,
2453
+ enabled: isAssignment
2454
+ });
2455
+ const activeAssignment = assignmentQuery.data;
2456
+ const setId = isAssignment ? (_a = activeAssignment == null ? void 0 : activeAssignment.setId) != null ? _a : "" : id;
2457
+ const querySet = useSet({ setId });
2458
+ const setData = querySet.data;
2459
+ const assignmentContent = activeAssignment == null ? void 0 : activeAssignment.content;
2460
+ const assignmentWeights = activeAssignment == null ? void 0 : activeAssignment.weights;
2461
+ const setContent = setData == null ? void 0 : setData.content;
2462
+ const setWeights = setData == null ? void 0 : setData.weights;
2463
+ const contentCardsToUse = isAssignment ? assignmentContent != null ? assignmentContent : setContent : setContent;
2464
+ const weightsToUse = isAssignment ? assignmentWeights != null ? assignmentWeights : setWeights : setWeights;
2465
+ const activityId = isAssignment ? (_b = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b : "" : setId;
2466
+ const { cardsObject, cardsQueries, cards } = useCards({
2467
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2468
+ enabled: querySet.isSuccess,
2469
+ asObject: true
2470
+ });
2471
+ const scoreQuery = useScore({
2472
+ isAssignment,
2473
+ activityId: id,
2474
+ userId,
2475
+ courseId: activeAssignment == null ? void 0 : activeAssignment.courseId,
2476
+ googleClassroomUserId: user.profile.googleClassroomUserId,
2477
+ enabled: isAssignment ? assignmentQuery.isSuccess : querySet.isSuccess
2478
+ });
2479
+ const { mutationUpdateScore } = useUpdateScore();
2480
+ const { mutationUpdateCardScore } = useUpdateCardScore({
2481
+ activityId,
2482
+ isAssignment,
2483
+ userId,
2484
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2485
+ weights: weightsToUse != null ? weightsToUse : {}
2486
+ });
2487
+ const { mutationClearScore } = useClearScore();
2488
+ const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
2489
+ onAssignmentSubmitted,
2490
+ studentName: user.profile.displayName
2491
+ });
2492
+ const { submitPracticeScore: submitPracticeScore2 } = useSubmitPracticeScore();
2493
+ const handleUpdateScore = (data) => {
2494
+ mutationUpdateScore.mutate({
2495
+ data,
2496
+ isAssignment,
2497
+ activityId,
2498
+ userId
2499
+ });
2500
+ };
2501
+ const handleUpdateCardScore = (cardId, cardScore) => {
2502
+ mutationUpdateCardScore.mutate({ cardId, cardScore });
2503
+ if (cardScore.proficiency_level) {
2504
+ logGradingStandardEntry({
2505
+ type: cardScore.proficiency_level.standardId,
2506
+ cardId,
2507
+ gradingStandard: cardScore.proficiency_level
2508
+ });
2509
+ } else if (cardScore.wida || cardScore.actfl) {
2510
+ logGradingStandardEntry({
2511
+ type: cardScore.wida ? "wida" : "actfl",
2512
+ cardId,
2513
+ gradingStandard: cardScore.wida || cardScore.actfl || { level: "", justification: "" }
2514
+ });
2515
+ }
2516
+ };
2517
+ const onClearScore = ({
2518
+ cardId,
2519
+ wasCompleted = true
2520
+ }) => {
2521
+ var _a2, _b2;
2522
+ const currentCard = cardsObject == null ? void 0 : cardsObject[cardId];
2523
+ if ((currentCard == null ? void 0 : currentCard.type) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || (currentCard == null ? void 0 : currentCard.type) === "READ_REPEAT" /* READ_REPEAT */) {
2524
+ return;
2525
+ }
2526
+ const queryKeys = scoreQueryKeys.byId(activityId);
2527
+ const activeCardScores = (_b2 = (_a2 = queryClient.getQueryData(queryKeys)) == null ? void 0 : _a2.cards) == null ? void 0 : _b2[cardId];
2528
+ if (activeCardScores === void 0) return;
2529
+ mutationClearScore.mutate({
2530
+ isAssignment,
2531
+ activityId,
2532
+ cardScores: activeCardScores,
2533
+ cardId,
2534
+ userId
2535
+ });
2536
+ };
2537
+ const onSubmitScore = async () => {
2538
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;
2539
+ try {
2540
+ let results;
2541
+ if (isAssignment) {
2542
+ const cardScores = ((_a2 = scoreQuery.data) == null ? void 0 : _a2.cards) || {};
2543
+ const hasPendingReview = Object.values(cardScores).some(
2544
+ (cardScore) => cardScore.status === "pending_review"
2545
+ );
2546
+ results = await submitAssignmentScore2({
2547
+ assignment: {
2548
+ id: (_c = (_b2 = assignmentQuery.data) == null ? void 0 : _b2.id) != null ? _c : "",
2549
+ name: (_e = (_d = assignmentQuery.data) == null ? void 0 : _d.name) != null ? _e : "",
2550
+ owners: (_g = (_f = assignmentQuery.data) == null ? void 0 : _f.owners) != null ? _g : [],
2551
+ courseId: (_i = (_h = assignmentQuery.data) == null ? void 0 : _h.courseId) != null ? _i : "",
2552
+ courseWorkId: (_k = (_j = assignmentQuery.data) == null ? void 0 : _j.courseWorkId) != null ? _k : "",
2553
+ isAssessment: (_m = (_l = assignmentQuery.data) == null ? void 0 : _l.isAssessment) != null ? _m : false,
2554
+ maxPoints: (_o = (_n = assignmentQuery.data) == null ? void 0 : _n.maxPoints) != null ? _o : 0
2555
+ },
2556
+ userId,
2557
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2558
+ scores: scoreQuery.data,
2559
+ weights: weightsToUse != null ? weightsToUse : {},
2560
+ status: hasPendingReview ? "PENDING_REVIEW" : "FINALIZED"
2561
+ });
2562
+ if ((_p = assignmentQuery.data) == null ? void 0 : _p.ltiDeeplink) {
2563
+ submitLTIScore({
2564
+ maxPoints: (_q = assignmentQuery.data) == null ? void 0 : _q.maxPoints,
2565
+ score: (_s = (_r = scoreQuery.data) == null ? void 0 : _r.score) != null ? _s : 0,
2566
+ SERVICE_KEY: (_t = ltiData == null ? void 0 : ltiData.serviceKey) != null ? _t : "",
2567
+ lineItemId: (_u = ltiData == null ? void 0 : ltiData.lineItemId) != null ? _u : "",
2568
+ lti_id: (_v = ltiData == null ? void 0 : ltiData.lti_id) != null ? _v : ""
2569
+ });
2570
+ }
2571
+ } else {
2572
+ results = await submitPracticeScore2({
2573
+ setId: (_x = (_w = querySet.data) == null ? void 0 : _w.id) != null ? _x : "",
2574
+ userId,
2575
+ scores: scoreQuery.data
2576
+ });
2577
+ }
2578
+ return results;
2579
+ } catch (error) {
2580
+ return {
2581
+ success: false,
2582
+ error
2583
+ };
2584
+ }
2585
+ };
2586
+ const logGradingStandardEntry = ({
2587
+ cardId,
2588
+ gradingStandard,
2589
+ type
2590
+ }) => {
2591
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i;
2592
+ const card = cardsObject == null ? void 0 : cardsObject[cardId];
2593
+ const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
2594
+ const cardScore = (_a2 = scoresObject == null ? void 0 : scoresObject.cards) == null ? void 0 : _a2[cardId];
2595
+ const serverTimestamp2 = api.helpers.serverTimestamp;
2596
+ addGradingStandardLog(
2597
+ {
2598
+ assignmentId: (_b2 = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b2 : "",
2599
+ courseId: (_c = activeAssignment == null ? void 0 : activeAssignment.courseId) != null ? _c : "",
2600
+ teacherId: (_d = activeAssignment == null ? void 0 : activeAssignment.owners[0]) != null ? _d : "",
2601
+ setId: (_e = setData == null ? void 0 : setData.id) != null ? _e : "",
2602
+ cardId,
2603
+ level: gradingStandard.level,
2604
+ justification: gradingStandard.justification,
2605
+ transcript: (_f = cardScore == null ? void 0 : cardScore.transcript) != null ? _f : "",
2606
+ audioUrl: (_g = cardScore == null ? void 0 : cardScore.audio) != null ? _g : "",
2607
+ prompt: (_h = card == null ? void 0 : card.prompt) != null ? _h : "",
2608
+ responseType: (card == null ? void 0 : card.type) === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
2609
+ type,
2610
+ dateMade: serverTimestamp2(),
2611
+ language: (_i = card == null ? void 0 : card.language) != null ? _i : ""
2612
+ },
2613
+ userId
2614
+ );
2615
+ };
2616
+ (0, import_react3.useEffect)(() => {
2617
+ if (isAssignment) {
2618
+ logOpenAssignment({ assignmentId: id });
2619
+ } else {
2620
+ logOpenActivityPreview({ setId: id });
2621
+ }
2622
+ }, []);
2623
+ useInitActivity({
2624
+ assignment: activeAssignment != null ? activeAssignment : void 0,
2625
+ set: setData != null ? setData : void 0,
2626
+ enabled: !!setData,
2627
+ userId
2628
+ });
2629
+ return {
2630
+ set: {
2631
+ data: setData,
2632
+ query: querySet
2633
+ },
2634
+ cards: {
2635
+ data: cardsObject,
2636
+ query: cardsQueries,
2637
+ cardsArray: cards
2638
+ },
2639
+ assignment: {
2640
+ data: isAssignment ? activeAssignment : void 0,
2641
+ query: assignmentQuery
2642
+ },
2643
+ scores: {
2644
+ data: scoreQuery.data,
2645
+ query: scoreQuery,
2646
+ actions: {
2647
+ update: handleUpdateScore,
2648
+ clear: onClearScore,
2649
+ submit: onSubmitScore,
2650
+ updateCard: handleUpdateCardScore,
2651
+ logGradingStandardEntry
2652
+ }
2653
+ }
2654
+ };
2655
+ }
2656
+ var useInitActivity = ({
2657
+ assignment,
2658
+ set,
2659
+ enabled,
2660
+ userId
2661
+ }) => {
2662
+ const { trackActivity } = useActivityTracker({ userId });
2663
+ const init = () => {
2664
+ var _a, _b, _c, _d, _e, _f, _g;
2665
+ if (!enabled) return;
2666
+ if (!assignment) {
2667
+ trackActivity({
2668
+ activityName: (_a = set == null ? void 0 : set.name) != null ? _a : "",
2669
+ activityType: "set",
2670
+ id: set == null ? void 0 : set.id,
2671
+ language: set == null ? void 0 : set.language
2672
+ });
2673
+ } else if (assignment.name) {
2674
+ trackActivity({
2675
+ activityName: assignment.name,
2676
+ activityType: assignment.isAssessment ? "assessment" : "assignment",
2677
+ id: assignment.id,
2678
+ language: set == null ? void 0 : set.language
2679
+ });
2680
+ }
2681
+ if (set == null ? void 0 : set.public) {
2682
+ (_d = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "onSetOpened")) == null ? void 0 : _d({
2683
+ setId: set.id,
2684
+ language: set.language
2685
+ });
2686
+ }
2687
+ (_g = (_f = (_e = api).httpsCallable) == null ? void 0 : _f.call(_e, "updateAlgoliaIndex")) == null ? void 0 : _g({
2688
+ updatePlays: true,
2689
+ objectID: set == null ? void 0 : set.id
2690
+ });
2691
+ };
2692
+ (0, import_react3.useEffect)(() => {
2693
+ init();
2694
+ }, [set]);
2695
+ };
2696
+ var submitLTIScore = async ({
2697
+ maxPoints,
2698
+ score,
2699
+ SERVICE_KEY,
2700
+ lineItemId,
2701
+ lti_id
2702
+ }) => {
2703
+ var _a, _b, _c;
2704
+ try {
2705
+ if (!SERVICE_KEY || !lineItemId || !lti_id) {
2706
+ throw new Error("Missing required LTI credentials");
2707
+ }
2708
+ const earnedPoints = score ? score / 100 * maxPoints : 0;
2709
+ const { data } = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitLTIAssignmentScore")) == null ? void 0 : _c({
2710
+ SERVICE_KEY,
2711
+ scoreData: {
2712
+ lineItemId,
2713
+ userId: lti_id,
2714
+ maxPoints,
2715
+ earnedPoints
2716
+ }
2717
+ }));
2718
+ return { success: true, data };
2719
+ } catch (error) {
2720
+ console.error("Failed to submit LTI score:", error);
2721
+ return {
2722
+ success: false,
2723
+ error: error instanceof Error ? error : new Error("Unknown error occurred")
2724
+ };
2725
+ }
2726
+ };
2727
+
2728
+ // src/hooks/useCredits.ts
2729
+ var import_react_query5 = require("@tanstack/react-query");
2730
+ var creditQueryKeys = {
2731
+ userCredits: (uid) => ["userCredits", uid]
2732
+ };
2733
+ var useUserCredits = () => {
2734
+ const { user } = useSpeakableApi();
2735
+ const email = user.auth.email;
2736
+ const uid = user.auth.uid;
2737
+ const query2 = (0, import_react_query5.useQuery)({
2738
+ queryKey: creditQueryKeys.userCredits(uid),
2739
+ queryFn: () => fetchUserCredits({ uid, email }),
2740
+ enabled: !!uid,
2741
+ refetchInterval: 1e3 * 60 * 5
2742
+ });
2743
+ return {
2744
+ ...query2
2745
+ };
2746
+ };
2747
+ var fetchUserCredits = async ({ uid, email }) => {
2748
+ if (!uid) {
2749
+ throw new Error("User ID is required");
2750
+ }
2751
+ const contractSnap = await api.getDoc(`creditContracts/${uid}`);
2752
+ if (contractSnap.data == null) {
2753
+ return {
2754
+ id: uid,
2755
+ userId: uid,
2756
+ email,
2757
+ effectivePlanId: "free_tier",
2758
+ status: "inactive",
2759
+ isUnlimited: false,
2760
+ creditsAvailable: 100,
2761
+ creditsAllocatedThisPeriod: 100,
2762
+ topOffCreditsAvailable: 0,
2763
+ topOffCreditsTotal: 0,
2764
+ allocationSource: "free_tier",
2765
+ sourceDetails: {},
2766
+ periodStart: null,
2767
+ periodEnd: null,
2768
+ planTermEndTimestamp: null,
2769
+ ownerType: "individual",
2770
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2771
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
2772
+ };
2773
+ }
2774
+ const contractData = contractSnap.data;
2775
+ const monthlyCredits = (contractData == null ? void 0 : contractData.creditsAvailable) || 0;
2776
+ const topOffCredits = (contractData == null ? void 0 : contractData.topOffCreditsAvailable) || 0;
2777
+ const totalCredits = monthlyCredits + topOffCredits;
2778
+ return {
2779
+ id: contractSnap.id,
2780
+ ...contractData,
2781
+ // Add computed total for convenience
2782
+ totalCreditsAvailable: totalCredits
2783
+ };
2784
+ };
2785
+
2786
+ // src/hooks/useOrganizationAccess.ts
2787
+ var import_react_query6 = require("@tanstack/react-query");
2788
+ var useOrganizationAccess = () => {
2789
+ const { user } = useSpeakableApi();
2790
+ const email = user.auth.email;
2791
+ const query2 = (0, import_react_query6.useQuery)({
2792
+ queryKey: ["organizationAccess", email],
2793
+ queryFn: async () => {
2794
+ if (!email) {
2795
+ return {
2796
+ hasUnlimitedAccess: false,
2797
+ subscriptionId: null,
2798
+ organizationId: null,
2799
+ organizationName: null,
2800
+ subscriptionEndDate: null,
2801
+ accessType: "individual"
2802
+ };
2803
+ }
2804
+ return getOrganizationAccess(email);
2805
+ },
2806
+ enabled: !!email,
2807
+ // Only run query if we have a user email
2808
+ staleTime: 5 * 60 * 1e3,
2809
+ // Consider data fresh for 5 minutes
2810
+ gcTime: 10 * 60 * 1e3,
2811
+ // Keep in cache for 10 minutes
2812
+ retry: 2
2813
+ // Retry failed requests twice
2814
+ });
2815
+ return {
2816
+ ...query2
2817
+ };
2818
+ };
2819
+ var getOrganizationAccess = async (email) => {
2820
+ const { limit: limit2, where: where2 } = api.accessQueryConstraints();
2821
+ try {
2822
+ const organizationSnapshot = await api.getDocs(
2823
+ "organizations",
2824
+ where2("members", "array-contains", email),
2825
+ where2("masterSubscriptionStatus", "==", "active"),
2826
+ limit2(1)
2827
+ );
2828
+ if (!organizationSnapshot.empty) {
2829
+ const orgData = organizationSnapshot.data[0];
2830
+ return {
2831
+ hasUnlimitedAccess: true,
2832
+ subscriptionId: orgData == null ? void 0 : orgData.masterSubscriptionId,
2833
+ organizationId: orgData.id,
2834
+ organizationName: orgData.name || "Unknown Organization",
2835
+ subscriptionEndDate: orgData.masterSubscriptionEndDate || null,
2836
+ accessType: "organization"
2837
+ };
2838
+ }
2839
+ const institutionSnapshot = await api.getDocs(
2840
+ "institution_subscriptions",
2841
+ where2("users", "array-contains", email),
2842
+ where2("active", "==", true),
2843
+ limit2(1)
2844
+ );
2845
+ if (!institutionSnapshot.empty) {
2846
+ const institutionData = institutionSnapshot.data[0];
2847
+ const isUnlimited = (institutionData == null ? void 0 : institutionData.plan) === "organization" || (institutionData == null ? void 0 : institutionData.plan) === "school_starter";
2848
+ return {
2849
+ hasUnlimitedAccess: isUnlimited,
2850
+ subscriptionId: institutionData.id,
2851
+ organizationId: institutionData == null ? void 0 : institutionData.institutionId,
2852
+ organizationName: institutionData.name || institutionData.institutionId || "Legacy Institution",
2853
+ subscriptionEndDate: institutionData.endDate || null,
2854
+ accessType: "institution_subscriptions"
2855
+ };
2856
+ }
2857
+ return {
2858
+ hasUnlimitedAccess: false,
2859
+ subscriptionId: null,
2860
+ organizationId: null,
2861
+ organizationName: null,
2862
+ subscriptionEndDate: null,
2863
+ accessType: "individual"
2864
+ };
2865
+ } catch (error) {
2866
+ console.error("Error checking organization access:", error);
2867
+ return {
2868
+ hasUnlimitedAccess: false,
2869
+ subscriptionId: null,
2870
+ organizationId: null,
2871
+ organizationName: null,
2872
+ subscriptionEndDate: null,
2873
+ accessType: "individual"
2874
+ };
2875
+ }
2876
+ };
2877
+
2878
+ // src/hooks/useUpdateStudentVoc.ts
2879
+ var useUpdateStudentVocab = (page) => {
2880
+ const { user } = useSpeakableApi();
2881
+ const currentUserId = user == null ? void 0 : user.auth.uid;
2882
+ if (!page || !currentUserId || !page.target_text || !page.language) {
2883
+ return {
2884
+ studentVocabMarkVoiceSuccess: void 0,
2885
+ studentVocabMarkVoiceFail: void 0
2886
+ };
2887
+ }
2888
+ const getDataObject = () => {
2889
+ var _a, _b;
2890
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2891
+ const language = (_a = page.language) != null ? _a : "en";
2892
+ const word = (_b = page.target_text) != null ? _b : "";
2893
+ const phrase_length = getPhraseLength(word);
2894
+ const wordHash = getWordHash(word, language);
2895
+ const docPath = `users/${currentUserId}/vocab/${wordHash}`;
2896
+ const communityPath = `checked-pronunciations/${wordHash}`;
2897
+ const id = `${language}-${cleanString(word)}`;
2898
+ const data = {
2899
+ id,
2900
+ word,
2901
+ words: (word == null ? void 0 : word.split(" ")) || [],
2902
+ wordHash,
2903
+ language,
2904
+ lastSeen: serverTimestamp2(),
2905
+ phrase_length
2906
+ };
2907
+ return {
2908
+ docPath,
2909
+ communityPath,
2910
+ data
2911
+ };
2912
+ };
2913
+ const markVoiceSuccess = async () => {
2914
+ const { docPath, communityPath, data } = getDataObject();
2915
+ const { increment: increment2 } = api.accessQueryConstraints();
2916
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2917
+ data.voiceSuccess = increment2(1);
2918
+ try {
2919
+ await api.updateDoc(docPath, data);
2920
+ } catch (error) {
2921
+ if (error instanceof Error && error.message === "not-found") {
2922
+ data.firstSeen = serverTimestamp2();
2923
+ await api.setDoc(docPath, data, { merge: true });
2924
+ } else {
2925
+ console.log(error);
2926
+ }
2927
+ }
2928
+ try {
2929
+ data.pronunciations = increment2(1);
2930
+ await api.setDoc(communityPath, data, { merge: true });
2931
+ } catch (error) {
2932
+ console.log(error);
2933
+ }
2934
+ };
2935
+ const markVoiceFail = async () => {
2936
+ const { docPath, communityPath, data } = getDataObject();
2937
+ const { increment: increment2 } = api.accessQueryConstraints();
2938
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2939
+ data.voiceFail = increment2(1);
2940
+ try {
2941
+ await api.updateDoc(docPath, data);
2942
+ } catch (error) {
2943
+ if (error instanceof Error && error.message === "not-found") {
2944
+ data.firstSeen = serverTimestamp2();
2945
+ await api.setDoc(docPath, data, { merge: true });
2946
+ } else {
2947
+ console.log(error);
2948
+ }
2949
+ }
2950
+ try {
2951
+ data.fails = increment2(1);
2952
+ await api.setDoc(communityPath, data, { merge: true });
2953
+ } catch (error) {
2954
+ console.log(error);
2955
+ }
2956
+ };
2957
+ return {
2958
+ studentVocabMarkVoiceSuccess: markVoiceSuccess,
2959
+ studentVocabMarkVoiceFail: markVoiceFail
2960
+ };
2961
+ };
2962
+
2963
+ // src/hooks/useActivityFeedbackAccess.ts
2964
+ var import_react_query7 = require("@tanstack/react-query");
2965
+ var activityFeedbackAccessQueryKeys = {
2966
+ activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
2967
+ };
2968
+ var useActivityFeedbackAccess = ({
2969
+ aiEnabled = false,
2970
+ isActivityRoute = false
2971
+ }) => {
2972
+ var _a, _b, _c;
2973
+ const { user } = useSpeakableApi();
2974
+ const uid = user.auth.uid;
2975
+ const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
2976
+ const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
2977
+ const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
2978
+ const query2 = (0, import_react_query7.useQuery)({
2979
+ queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
2980
+ aiEnabled,
2981
+ isActivityRoute
2982
+ }),
2983
+ queryFn: async () => {
2984
+ var _a2, _b2, _c2;
2985
+ if (!uid) {
2986
+ return {
2987
+ canAccessFeedback: false,
2988
+ reason: "Missing user ID",
2989
+ isUnlimited: false,
2990
+ accessType: "none"
2991
+ };
2992
+ }
2993
+ try {
2994
+ if (aiEnabled) {
2995
+ return {
2996
+ canAccessFeedback: true,
2997
+ reason: "AI feedback enabled",
2998
+ isUnlimited: true,
2999
+ accessType: "ai_enabled"
3000
+ };
3001
+ }
3002
+ if (isTeacher || userRoles.includes("ADMIN")) {
3003
+ return {
3004
+ canAccessFeedback: true,
3005
+ reason: "Teacher preview access",
3006
+ isUnlimited: true,
3007
+ accessType: "teacher_preview"
3008
+ };
3009
+ }
3010
+ if (isStudent && isActivityRoute) {
3011
+ try {
3012
+ const result = await ((_c2 = (_b2 = (_a2 = api).httpsCallable) == null ? void 0 : _b2.call(_a2, "checkStudentTeacherPlan")) == null ? void 0 : _c2({
3013
+ studentId: uid
3014
+ }));
3015
+ const planCheckResult = result.data;
3016
+ if (planCheckResult.canAccessFeedback) {
3017
+ return {
3018
+ canAccessFeedback: true,
3019
+ reason: planCheckResult.reason || "Student access via teacher with active plan",
3020
+ isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
3021
+ accessType: "student_with_teacher_plan"
3022
+ };
3023
+ } else {
3024
+ return {
3025
+ canAccessFeedback: false,
3026
+ reason: planCheckResult.reason || "No teacher with active plan found",
3027
+ isUnlimited: false,
3028
+ accessType: "none"
3029
+ };
3030
+ }
3031
+ } catch (error) {
3032
+ console.error("Error checking student teacher plan:", error);
3033
+ return {
3034
+ canAccessFeedback: false,
3035
+ reason: "Error checking teacher plans",
3036
+ isUnlimited: false,
3037
+ accessType: "none"
3038
+ };
3039
+ }
3040
+ }
3041
+ return {
3042
+ canAccessFeedback: false,
3043
+ reason: "No access permissions found for current context",
3044
+ isUnlimited: false,
3045
+ accessType: "none"
3046
+ };
3047
+ } catch (error) {
3048
+ console.error("Error checking activity feedback access:", error);
3049
+ return {
3050
+ canAccessFeedback: false,
3051
+ reason: "Error checking access permissions",
3052
+ isUnlimited: false,
3053
+ accessType: "none"
3054
+ };
3055
+ }
3056
+ },
3057
+ enabled: !!uid,
3058
+ staleTime: 5 * 60 * 1e3,
3059
+ // 5 minutes
3060
+ gcTime: 10 * 60 * 1e3
3061
+ // 10 minutes
3062
+ });
3063
+ return {
3064
+ ...query2
3065
+ };
3066
+ };
3067
+
3068
+ // src/hooks/useOpenAI.ts
3069
+ var useBaseOpenAI = ({
3070
+ onTranscriptSuccess,
3071
+ onTranscriptError,
3072
+ onCompletionSuccess,
3073
+ onCompletionError,
3074
+ aiEnabled,
3075
+ submitAudioResponse,
3076
+ uploadAudioAndGetTranscript,
3077
+ onGetAudioUrlAndTranscript
3078
+ }) => {
3079
+ const { user, queryClient } = useSpeakableApi();
3080
+ const currentUserId = user.auth.uid;
3081
+ const { data: feedbackAccess } = useActivityFeedbackAccess({
3082
+ aiEnabled
3083
+ });
3084
+ const getTranscript = async (audioUrl, language) => {
3085
+ var _a, _b;
3086
+ try {
3087
+ const getAssemblyAITranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "transcribeAssemblyAIAudio");
3088
+ const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
3089
+ audioUrl,
3090
+ language
3091
+ }));
3092
+ const transcript = response == null ? void 0 : response.data;
3093
+ return transcript;
3094
+ } catch (error) {
3095
+ console.log("error", error);
3096
+ onTranscriptError({
3097
+ type: "TRANSCRIPT",
3098
+ message: (error == null ? void 0 : error.message) || "Error getting transcript"
3099
+ });
3100
+ throw new Error(error);
3101
+ }
3102
+ };
3103
+ const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
3104
+ var _a, _b, _c, _d, _e;
3105
+ const responseTool = getRespondCardTool({
3106
+ language: feedbackLanguage,
3107
+ standard: gradingStandard
3108
+ });
3109
+ try {
3110
+ const createChatCompletion = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createChatCompletion");
3111
+ const {
3112
+ data: {
3113
+ response,
3114
+ prompt_tokens = 0,
3115
+ completion_tokens = 0,
3116
+ success: aiSuccess = false
3117
+ // the AI was able to generate a response
3118
+ }
3119
+ } = await (createChatCompletion == null ? void 0 : createChatCompletion({
3120
+ chat: {
3121
+ model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
3122
+ messages,
3123
+ temperature: 0.7,
3124
+ ...responseTool
3125
+ },
3126
+ type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
3127
+ }));
3128
+ 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) || "{}");
3129
+ const result = {
3130
+ ...functionArguments,
3131
+ prompt_tokens,
3132
+ completion_tokens,
3133
+ aiSuccess
3134
+ };
3135
+ onCompletionSuccess(result);
3136
+ return result;
3137
+ } catch (error) {
3138
+ onCompletionError({
3139
+ type: "COMPLETION",
3140
+ message: (error == null ? void 0 : error.message) || "Error getting completion"
3141
+ });
3142
+ throw new Error(error);
3143
+ }
3144
+ };
3145
+ const getFeedback = async ({
3146
+ cardId,
3147
+ language = "en",
3148
+ // required
3149
+ writtenResponse = null,
3150
+ // if the type = RESPOND_WRITE
3151
+ audio = null,
3152
+ autoGrade = true,
3153
+ file = null
3154
+ }) => {
3155
+ try {
3156
+ if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
3157
+ const result = {
3158
+ noFeedbackAvailable: true,
3159
+ success: true,
3160
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3161
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3162
+ };
3163
+ onCompletionSuccess(result);
3164
+ return result;
3165
+ }
3166
+ let transcript;
3167
+ let audioUrl = void 0;
3168
+ if (writtenResponse) {
3169
+ transcript = writtenResponse;
3170
+ onTranscriptSuccess(writtenResponse);
3171
+ } else if (typeof audio === "string" && file) {
3172
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3173
+ transcript = await getTranscript(audio, language);
3174
+ audioUrl = audio;
3175
+ onTranscriptSuccess(transcript);
3176
+ } else {
3177
+ console.info(
3178
+ `Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
3179
+ );
3180
+ }
3181
+ } else {
3182
+ const response = await uploadAudioAndGetTranscript(audio || "", language);
3183
+ transcript = response.transcript;
3184
+ audioUrl = response.audioUrl;
3185
+ }
3186
+ onGetAudioUrlAndTranscript == null ? void 0 : onGetAudioUrlAndTranscript({ transcript, audioUrl });
3187
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3188
+ const results = await getAIResponse({
3189
+ cardId,
3190
+ transcript: transcript || ""
3191
+ });
3192
+ let output = results;
3193
+ if (!autoGrade) {
3194
+ output = {
3195
+ ...output,
3196
+ noFeedbackAvailable: true,
3197
+ success: true
3198
+ };
3199
+ }
3200
+ onCompletionSuccess(output);
3201
+ return output;
3202
+ } else {
3203
+ const result = {
3204
+ noFeedbackAvailable: true,
3205
+ success: true,
3206
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3207
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3208
+ };
3209
+ onCompletionSuccess(result);
3210
+ return result;
3211
+ }
3212
+ } catch (error) {
3213
+ console.error("Error getting feedback:", error);
3214
+ throw new Error(error);
3215
+ }
3216
+ };
3217
+ const getAIResponse = async ({ cardId, transcript }) => {
3218
+ var _a, _b, _c, _d, _e;
3219
+ try {
3220
+ const getGeminiFeedback = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "callGetFeedback");
3221
+ const getProficiencyEstimate = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "getProficiencyEstimate");
3222
+ const card = getCardFromCache({
3223
+ cardId,
3224
+ queryClient
3225
+ });
3226
+ let feedbackData;
3227
+ let proficiencyData = {};
3228
+ if (card && card.grading_method === "manual") {
3229
+ } else if (card && card.grading_method !== "standards_based") {
3230
+ const [geminiResult, proficiencyResult] = await Promise.all([
3231
+ getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3232
+ cardId,
3233
+ studentId: currentUserId,
3234
+ studentResponse: transcript
3235
+ }),
3236
+ getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
3237
+ cardId,
3238
+ studentId: currentUserId,
3239
+ studentResponse: transcript
3240
+ })
3241
+ ]);
3242
+ proficiencyData = (proficiencyResult == null ? void 0 : proficiencyResult.data) || {};
3243
+ feedbackData = {
3244
+ ...(_e = geminiResult == null ? void 0 : geminiResult.data) != null ? _e : {},
3245
+ // @ts-ignore
3246
+ proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
3247
+ };
3248
+ } else {
3249
+ const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3250
+ cardId,
3251
+ studentId: currentUserId,
3252
+ studentResponse: transcript
3253
+ }));
3254
+ feedbackData = geminiResult == null ? void 0 : geminiResult.data;
3255
+ }
3256
+ const results = {
3257
+ ...feedbackData,
3258
+ // ...proficiencyData,
3259
+ aiSuccess: true,
3260
+ promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
3261
+ transcript
3262
+ };
3263
+ return results;
3264
+ } catch (error) {
3265
+ onCompletionError({
3266
+ type: "AI_FEEDBACK",
3267
+ message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
3268
+ });
3269
+ throw new Error(error);
3270
+ }
3271
+ };
3272
+ return {
3273
+ submitAudioResponse,
3274
+ uploadAudioAndGetTranscript,
3275
+ getTranscript,
3276
+ getFreeResponseCompletion,
3277
+ getFeedback
3278
+ };
3279
+ };
3280
+
3281
+ // src/lib/create-firebase-client-native.ts
3282
+ var import_firestore = require("@react-native-firebase/firestore");
3283
+
3284
+ // src/lib/create-firebase-client.ts
3285
+ function createFsClientBase({
3286
+ db,
3287
+ helpers,
3288
+ httpsCallable,
3289
+ logEvent
3290
+ }) {
3291
+ const dbAsFirestore = db;
3292
+ api.initialize({
3293
+ db: dbAsFirestore,
3294
+ helpers,
3295
+ httpsCallable,
3296
+ logEvent
3297
+ });
3298
+ return {
3299
+ assignmentRepo: createAssignmentRepo(),
3300
+ cardRepo: createCardRepo()
3301
+ };
3302
+ }
3303
+
3304
+ // src/lib/create-firebase-client-native.ts
3305
+ var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
474
3306
  return createFsClientBase({
475
3307
  db,
476
3308
  httpsCallable,
477
3309
  logEvent,
478
3310
  helpers: {
479
- getDoc,
480
- getDocs,
481
- addDoc,
482
- setDoc,
483
- updateDoc,
484
- deleteDoc,
485
- runTransaction,
486
- writeBatch,
487
- doc,
488
- collection,
489
- query,
490
- serverTimestamp,
491
- orderBy,
492
- limit,
493
- startAt,
494
- startAfter,
495
- endAt,
496
- endBefore,
497
- where,
498
- increment
499
- }
500
- });
501
- };
502
- export {
503
- createFsClientNative as createFsClient
3311
+ getDoc: import_firestore.getDoc,
3312
+ getDocs: import_firestore.getDocs,
3313
+ addDoc: import_firestore.addDoc,
3314
+ setDoc: import_firestore.setDoc,
3315
+ updateDoc: import_firestore.updateDoc,
3316
+ deleteDoc: import_firestore.deleteDoc,
3317
+ runTransaction: import_firestore.runTransaction,
3318
+ writeBatch: import_firestore.writeBatch,
3319
+ doc: import_firestore.doc,
3320
+ collection: import_firestore.collection,
3321
+ query: import_firestore.query,
3322
+ serverTimestamp: import_firestore.serverTimestamp,
3323
+ orderBy: import_firestore.orderBy,
3324
+ limit: import_firestore.limit,
3325
+ startAt: import_firestore.startAt,
3326
+ startAfter: import_firestore.startAfter,
3327
+ endAt: import_firestore.endAt,
3328
+ endBefore: import_firestore.endBefore,
3329
+ where: import_firestore.where,
3330
+ increment: import_firestore.increment
3331
+ }
3332
+ });
504
3333
  };
505
3334
  //# sourceMappingURL=index.native.js.map