@speakableio/core 0.1.57 → 0.1.59

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.
@@ -363,890 +363,63 @@ function useAssignment({
363
363
  });
364
364
  }
365
365
 
366
- // src/domains/cards/card.hooks.ts
367
- import { useMutation, useQueries } from "@tanstack/react-query";
368
- import { useMemo } from "react";
369
-
370
- // src/domains/cards/card.constants.ts
371
- var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
372
- FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
373
- FeedbackTypesCard2["Wida"] = "wida";
374
- FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
375
- FeedbackTypesCard2["Actfl"] = "actfl";
376
- FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
377
- return FeedbackTypesCard2;
378
- })(FeedbackTypesCard || {});
379
- var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
380
- LeniencyCard2["CONFIDENCE"] = "confidence";
381
- LeniencyCard2["EASY"] = "easy";
382
- LeniencyCard2["NORMAL"] = "normal";
383
- LeniencyCard2["HARD"] = "hard";
384
- return LeniencyCard2;
385
- })(LeniencyCard || {});
386
- var LENIENCY_OPTIONS = [
387
- {
388
- label: "Build Confidence - most lenient",
389
- value: "confidence" /* CONFIDENCE */
390
- },
391
- {
392
- label: "Very Lenient",
393
- value: "easy" /* EASY */
394
- },
395
- {
396
- label: "Normal",
397
- value: "normal" /* NORMAL */
398
- },
399
- {
400
- label: "No leniency - most strict",
401
- value: "hard" /* HARD */
402
- }
403
- ];
404
- var STUDENT_LEVELS_OPTIONS = [
405
- {
406
- label: "Beginner",
407
- description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
408
- value: "beginner"
409
- },
410
- {
411
- label: "Elementary",
412
- description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
413
- value: "elementary"
414
- },
415
- {
416
- label: "Intermediate",
417
- description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
418
- value: "intermediate"
419
- },
420
- {
421
- label: "Advanced",
422
- description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
423
- value: "advanced"
424
- },
425
- {
426
- label: "Fluent",
427
- description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
428
- value: "fluent"
429
- },
430
- {
431
- label: "Native-like",
432
- description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
433
- value: "nativeLike"
434
- }
435
- ];
436
- var BASE_RESPOND_FIELD_VALUES = {
437
- title: "",
438
- allowRetries: true,
439
- respondTime: 180,
440
- maxCharacters: 1e3
441
- };
442
- var BASE_REPEAT_FIELD_VALUES = {
443
- repeat: 1
444
- };
445
- var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
446
- MCQType: "single",
447
- answer: ["A"],
448
- choices: [
449
- { option: "A", value: "Option A" },
450
- { option: "B", value: "Option B" },
451
- { option: "C", value: "Option C" }
452
- ]
453
- };
454
- var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
455
- VerificationCardStatus2["VERIFIED"] = "VERIFIED";
456
- VerificationCardStatus2["WARNING"] = "WARNING";
457
- VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
458
- VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
459
- VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
460
- return VerificationCardStatus2;
461
- })(VerificationCardStatus || {});
462
- var CARDS_COLLECTION = "flashcards";
463
- var refsCardsFiresotre = {
464
- allCards: CARDS_COLLECTION,
465
- card: (id) => `${CARDS_COLLECTION}/${id}`
466
- };
366
+ // src/domains/assignment/hooks/score-hooks.ts
367
+ import { useMutation, useQuery as useQuery2 } from "@tanstack/react-query";
467
368
 
468
- // src/domains/cards/services/get-card.service.ts
469
- async function _getCard(params) {
470
- const ref = refsCardsFiresotre.card(params.cardId);
471
- const response = await api.getDoc(ref);
472
- if (!response.data) return null;
473
- return response.data;
369
+ // src/utils/debounce.utils.ts
370
+ function debounce(func, waitFor) {
371
+ let timeoutId;
372
+ return (...args) => new Promise((resolve, reject) => {
373
+ if (timeoutId) {
374
+ clearTimeout(timeoutId);
375
+ }
376
+ timeoutId = setTimeout(async () => {
377
+ try {
378
+ const result = await func(...args);
379
+ resolve(result);
380
+ } catch (error) {
381
+ reject(error);
382
+ }
383
+ }, waitFor);
384
+ });
474
385
  }
475
- var getCard = withErrorHandler(_getCard, "getCard");
476
-
477
- // src/domains/cards/services/create-card.service.ts
478
- import { v4 } from "uuid";
479
-
480
- // src/domains/cards/card.model.ts
481
- var CardActivityType = /* @__PURE__ */ ((CardActivityType2) => {
482
- CardActivityType2["READ_REPEAT"] = "READ_REPEAT";
483
- CardActivityType2["VIDEO"] = "VIDEO";
484
- CardActivityType2["TEXT"] = "TEXT";
485
- CardActivityType2["READ_RESPOND"] = "READ_RESPOND";
486
- CardActivityType2["FREE_RESPONSE"] = "FREE_RESPONSE";
487
- CardActivityType2["REPEAT"] = "REPEAT";
488
- CardActivityType2["RESPOND"] = "RESPOND";
489
- CardActivityType2["RESPOND_WRITE"] = "RESPOND_WRITE";
490
- CardActivityType2["TEXT_TO_SPEECH"] = "TEXT_TO_SPEECH";
491
- CardActivityType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
492
- CardActivityType2["PODCAST"] = "PODCAST";
493
- CardActivityType2["MEDIA_PAGE"] = "MEDIA_PAGE";
494
- CardActivityType2["WRITE"] = "WRITE";
495
- CardActivityType2["SHORT_ANSWER"] = "SHORT_ANSWER";
496
- CardActivityType2["SHORT_STORY"] = "SHORT_STORY";
497
- CardActivityType2["SPEAK"] = "SPEAK";
498
- CardActivityType2["CONVERSATION"] = "CONVERSATION";
499
- CardActivityType2["CONVERSATION_WRITE"] = "CONVERSATION_WRITE";
500
- CardActivityType2["DIALOGUE"] = "DIALOGUE";
501
- CardActivityType2["INSTRUCTION"] = "INSTRUCTION";
502
- CardActivityType2["LISTEN"] = "LISTEN";
503
- CardActivityType2["READ"] = "READ";
504
- CardActivityType2["ANSWER"] = "ANSWER";
505
- return CardActivityType2;
506
- })(CardActivityType || {});
507
- var RESPOND_CARD_ACTIVITY_TYPES = [
508
- "READ_RESPOND" /* READ_RESPOND */,
509
- "RESPOND" /* RESPOND */,
510
- "RESPOND_WRITE" /* RESPOND_WRITE */,
511
- "FREE_RESPONSE" /* FREE_RESPONSE */
512
- ];
513
- var MULTIPLE_CHOICE_CARD_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
514
- var REPEAT_CARD_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
515
- var RESPOND_WRITE_CARD_ACTIVITY_TYPES = [
516
- "RESPOND_WRITE" /* RESPOND_WRITE */,
517
- "FREE_RESPONSE" /* FREE_RESPONSE */
518
- ];
519
- var RESPOND_AUDIO_CARD_ACTIVITY_TYPES = [
520
- "RESPOND" /* RESPOND */,
521
- "READ_RESPOND" /* READ_RESPOND */
522
- ];
523
- var ALLOWED_CARD_ACTIVITY_TYPES_FOR_SUMMARY = [
524
- "REPEAT" /* REPEAT */,
525
- "RESPOND" /* RESPOND */,
526
- "READ_REPEAT" /* READ_REPEAT */,
527
- "READ_RESPOND" /* READ_RESPOND */,
528
- "FREE_RESPONSE" /* FREE_RESPONSE */,
529
- "RESPOND_WRITE" /* RESPOND_WRITE */,
530
- "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */
531
- ];
532
386
 
533
- // src/utils/text-utils.ts
534
- import sha1 from "js-sha1";
535
- var purify = (word) => {
536
- 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();
537
- };
538
- var cleanString = (words) => {
539
- const splitWords = words == null ? void 0 : words.split("+");
540
- if (splitWords && splitWords.length === 1) {
541
- const newWord = purify(words);
542
- return newWord;
543
- } else if (splitWords && splitWords.length > 1) {
544
- const split = splitWords.map((w) => purify(w));
545
- return split;
387
+ // src/lib/tanstack/handle-optimistic-update-query.ts
388
+ var handleOptimisticUpdate = async ({
389
+ queryClient,
390
+ queryKey,
391
+ newData
392
+ }) => {
393
+ await queryClient.cancelQueries({
394
+ queryKey
395
+ });
396
+ const previousData = queryClient.getQueryData(queryKey);
397
+ if (previousData === void 0) {
398
+ queryClient.setQueryData(queryKey, newData);
546
399
  } else {
547
- return "";
400
+ queryClient.setQueryData(queryKey, { ...previousData, ...newData });
548
401
  }
549
- };
550
- var getWordHash = (word, language) => {
551
- const cleanedWord = cleanString(word);
552
- const wordHash = sha1(`${language}-${cleanedWord}`);
553
- console.log("wordHash core library", wordHash);
554
- return wordHash;
402
+ return { previousData };
555
403
  };
556
404
 
557
- // src/domains/cards/services/get-card-verification-status.service.ts
558
- var charactarLanguages = ["zh", "ja", "ko"];
559
- var getVerificationStatus = async (target_text, language) => {
560
- if ((target_text == null ? void 0 : target_text.length) < 3 && !charactarLanguages.includes(language)) {
561
- return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
562
- }
563
- const hash = getWordHash(target_text, language);
564
- const response = await api.getDoc(`checked-pronunciations/${hash}`);
565
- try {
566
- if (response.data) {
567
- return processRecord(response.data);
568
- } else {
569
- return "NOT_CHECKED" /* NOT_CHECKED */;
570
- }
571
- } catch (e) {
572
- return "NOT_CHECKED" /* NOT_CHECKED */;
573
- }
574
- };
575
- var processRecord = (data) => {
576
- const { pronunciations = 0, fails = 0 } = data;
577
- const attempts = pronunciations + fails;
578
- const successRate = attempts > 0 ? pronunciations / attempts * 100 : 0;
579
- let newStatus = null;
580
- if (attempts < 6) {
581
- return "NOT_CHECKED" /* NOT_CHECKED */;
582
- }
583
- if (successRate > 25) {
584
- newStatus = "VERIFIED" /* VERIFIED */;
585
- } else if (successRate > 10) {
586
- newStatus = "WARNING" /* WARNING */;
587
- } else if (fails > 20 && successRate < 10 && pronunciations > 1) {
588
- newStatus = "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
589
- } else if (pronunciations === 0 && fails > 20) {
590
- newStatus = "NOT_WORKING" /* NOT_WORKING */;
591
- } else {
592
- newStatus = "NOT_CHECKED" /* NOT_CHECKED */;
593
- }
594
- return newStatus;
595
- };
596
-
597
- // src/domains/cards/services/create-card.service.ts
598
- async function _createCard({ data }) {
599
- const response = await api.addDoc(refsCardsFiresotre.allCards, data);
600
- return response;
601
- }
602
- var createCard = withErrorHandler(_createCard, "createCard");
603
- async function _createCards({ cards }) {
604
- const { writeBatch: writeBatch2, doc: doc2 } = api.accessHelpers();
605
- const batch = writeBatch2();
606
- const cardsWithId = [];
607
- for (const card of cards) {
608
- const cardId = v4();
609
- const ref = doc2(refsCardsFiresotre.card(cardId));
610
- const newCardObject = {
611
- ...card,
612
- id: cardId
613
- };
614
- if (card.type === "READ_REPEAT" /* READ_REPEAT */ && card.target_text && card.language) {
615
- const verificationStatus = await getVerificationStatus(card.target_text, card.language);
616
- newCardObject.verificationStatus = verificationStatus || null;
617
- }
618
- cardsWithId.push(newCardObject);
619
- batch.set(ref, newCardObject);
620
- }
621
- await batch.commit();
622
- return cardsWithId;
623
- }
624
- var createCards = withErrorHandler(_createCards, "createCards");
625
-
626
- // src/domains/cards/card.hooks.ts
627
- var cardsQueryKeys = {
628
- all: ["cards"],
629
- one: (params) => [...cardsQueryKeys.all, params.cardId]
630
- };
631
- function useCards({
632
- cardIds,
633
- enabled = true,
634
- asObject
635
- }) {
636
- const queries = useQueries({
637
- queries: cardIds.map((cardId) => ({
638
- enabled: enabled && cardIds.length > 0,
639
- queryKey: cardsQueryKeys.one({
640
- cardId
641
- }),
642
- queryFn: () => getCard({ cardId })
643
- }))
644
- });
645
- const cards = queries.map((query2) => query2.data).filter(Boolean);
646
- const cardsObject = useMemo(() => {
647
- if (!asObject) return null;
648
- return cards.reduce((acc, card) => {
649
- acc[card.id] = card;
650
- return acc;
651
- }, {});
652
- }, [asObject, cards]);
653
- return {
654
- cards,
655
- cardsObject,
656
- cardsQueries: queries
657
- };
658
- }
659
- function useCreateCard() {
660
- const { queryClient } = useSpeakableApi();
661
- const mutationCreateCard = useMutation({
662
- mutationFn: createCard,
663
- onSuccess: (cardCreated) => {
664
- queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
665
- }
666
- });
667
- return {
668
- mutationCreateCard
669
- };
670
- }
671
- function useCreateCards() {
672
- const mutationCreateCards = useMutation({
673
- mutationFn: createCards
674
- });
675
- return {
676
- mutationCreateCards
677
- };
678
- }
679
- function getCardFromCache({
680
- cardId,
681
- queryClient
682
- }) {
683
- return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
684
- }
685
- function updateCardInCache({
686
- cardId,
687
- card,
688
- queryClient
689
- }) {
690
- queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
691
- }
692
-
693
- // src/domains/cards/card.repo.ts
694
- var createCardRepo = () => {
695
- return {
696
- createCard,
697
- createCards,
698
- getCard
699
- };
700
- };
701
-
702
- // src/domains/sets/set.hooks.ts
703
- import { useQuery as useQuery2 } from "@tanstack/react-query";
704
-
705
- // src/domains/sets/set.constants.ts
706
- var SETS_COLLECTION = "sets";
707
- var refsSetsFirestore = {
708
- allSets: SETS_COLLECTION,
709
- set: (id) => `${SETS_COLLECTION}/${id}`
710
- };
711
-
712
- // src/domains/sets/services/get-set.service.ts
713
- async function _getSet({ setId }) {
714
- const response = await api.getDoc(refsSetsFirestore.set(setId));
715
- return response.data;
716
- }
717
- var getSet = withErrorHandler(_getSet, "getSet");
718
-
719
- // src/domains/sets/set.hooks.ts
720
- var setsQueryKeys = {
721
- all: ["sets"],
722
- one: (params) => [...setsQueryKeys.all, params.setId]
723
- };
724
- var useSet = ({ setId, enabled }) => {
725
- return useQuery2({
726
- queryKey: setsQueryKeys.one({ setId }),
727
- queryFn: () => getSet({ setId }),
728
- enabled: setId !== void 0 && setId !== "" && enabled
729
- });
730
- };
731
- function getSetFromCache({
732
- setId,
733
- queryClient
734
- }) {
735
- if (!setId) return null;
736
- return queryClient.getQueryData(setsQueryKeys.one({ setId }));
737
- }
738
- function updateSetInCache({
739
- set,
740
- queryClient
741
- }) {
742
- const { id, ...setData } = set;
743
- queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
744
- }
745
-
746
- // src/domains/sets/set.repo.ts
747
- var createSetRepo = () => {
748
- return {
749
- getSet
750
- };
751
- };
752
-
753
- // src/domains/notification/notification.constants.ts
754
- var SPEAKABLE_NOTIFICATIONS = {
755
- NEW_ASSIGNMENT: "new_assignment",
756
- ASSESSMENT_SUBMITTED: "assessment_submitted",
757
- ASSESSMENT_SCORED: "assessment_scored",
758
- NEW_COMMENT: "NEW_COMMENT"
759
- };
760
- var SpeakableNotificationTypes = {
761
- NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
762
- FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
763
- MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
764
- PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
765
- STUDENT_PROGRESS: "STUDENT_PROGRESS",
766
- PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
767
- PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
768
- // New notifications
769
- ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
770
- // Notification FOR TEACHER when student submits assessment
771
- ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
772
- // Notification FOR STUDENT when teacher scores assessment
773
- // Comment
774
- NEW_COMMENT: "NEW_COMMENT"
775
- };
776
-
777
- // src/domains/notification/services/create-notification.service.ts
778
- import dayjs2 from "dayjs";
779
-
780
- // src/constants/web.constants.ts
781
- var WEB_BASE_URL = "https://app.speakable.io";
782
-
783
- // src/domains/notification/services/send-notification.service.ts
784
- var _sendNotification = async (sendTo, notification) => {
785
- var _a, _b, _c;
786
- const results = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createNotificationV2")) == null ? void 0 : _c({
787
- sendTo,
788
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
789
- notification
790
- }));
791
- return results;
792
- };
793
- var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
794
-
795
- // src/domains/notification/services/create-notification.service.ts
796
- var createNotification = async ({
797
- data,
798
- type,
799
- userId,
800
- profile
801
- }) => {
802
- let result;
803
- switch (type) {
804
- case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
805
- result = await handleAssignNotifPromise({ data, profile });
806
- break;
807
- case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
808
- result = await createAssessmentSubmissionNotification({
809
- data,
810
- profile,
811
- userId
812
- });
813
- break;
814
- case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
815
- result = await createAssessmentScoredNotification({
816
- data,
817
- profile
818
- });
819
- break;
820
- default:
821
- result = null;
822
- break;
823
- }
824
- return result;
825
- };
826
- var handleAssignNotifPromise = async ({
827
- data: assignments,
828
- profile
829
- }) => {
830
- if (!assignments.length) return;
831
- try {
832
- const notifsPromises = assignments.map(async (assignment) => {
833
- const {
834
- section,
835
- section: { members },
836
- ...rest
837
- } = assignment;
838
- if (!section || !members) throw new Error("Invalid assignment data");
839
- const data = { section, sendTo: members, assignment: { ...rest } };
840
- return createNewAssignmentNotification({ data, profile });
841
- });
842
- await Promise.all(notifsPromises);
843
- return {
844
- success: true,
845
- message: "Assignment notifications sent successfully"
846
- };
847
- } catch (error) {
848
- console.error("Error in handleAssignNotifPromise:", error);
849
- throw error;
850
- }
851
- };
852
- var createNewAssignmentNotification = async ({
853
- data,
854
- profile
855
- }) => {
856
- var _a;
857
- const { assignment, sendTo } = data;
858
- const teacherName = profile.displayName || "Your teacher";
859
- const dueDate = assignment.dueDateTimestamp ? dayjs2(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
860
- const results = await sendNotification(sendTo, {
861
- courseId: assignment.courseId,
862
- type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
863
- senderName: teacherName,
864
- link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
865
- messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
866
- title: "New Assignment Available!",
867
- imageUrl: (_a = profile.image) == null ? void 0 : _a.url
868
- });
869
- return results;
870
- };
871
- var createAssessmentSubmissionNotification = async ({
872
- data: assignment,
873
- profile,
874
- userId
875
- }) => {
876
- var _a;
877
- const studentName = profile.displayName || "Your student";
878
- const results = await sendNotification(assignment.owners, {
879
- courseId: assignment.courseId,
880
- type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
881
- link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
882
- title: `Assessment Submitted!`,
883
- senderName: studentName,
884
- messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
885
- imageUrl: (_a = profile.image) == null ? void 0 : _a.url
886
- });
887
- return results;
888
- };
889
- var createAssessmentScoredNotification = async ({
890
- data,
891
- profile
892
- }) => {
893
- var _a, _b, _c, _d, _e;
894
- const { assignment, sendTo } = data;
895
- const teacherName = profile.displayName || "Your teacher";
896
- const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
897
- const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
898
- const results = await sendNotification(sendTo, {
899
- courseId: assignment.courseId,
900
- type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
901
- link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
902
- title,
903
- messagePreview,
904
- imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
905
- senderName: teacherName
906
- });
907
- await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
908
- assessmentTitle: assignment.name,
909
- link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
910
- senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
911
- studentId: sendTo[0],
912
- teacherName: profile.displayName
913
- }));
914
- return results;
915
- };
916
-
917
- // src/domains/notification/hooks/notification.hooks.ts
918
- var notificationQueryKeys = {
919
- all: ["notifications"],
920
- byId: (id) => [...notificationQueryKeys.all, id]
921
- };
922
- var useCreateNotification = () => {
923
- const { user, queryClient } = useSpeakableApi();
924
- const handleCreateNotifications = async (type, data) => {
925
- var _a, _b;
926
- const result = await createNotification({
927
- type,
928
- userId: user.auth.uid,
929
- profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
930
- data
931
- });
932
- queryClient.invalidateQueries({
933
- queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
934
- });
935
- return result;
936
- };
937
- return {
938
- createNotification: handleCreateNotifications
939
- };
940
- };
941
-
942
- // src/constants/all-langs.json
943
- var all_langs_default = {
944
- af: "Afrikaans",
945
- sq: "Albanian",
946
- am: "Amharic",
947
- ar: "Arabic",
948
- hy: "Armenian",
949
- az: "Azerbaijani",
950
- eu: "Basque",
951
- be: "Belarusian",
952
- bn: "Bengali",
953
- bs: "Bosnian",
954
- bg: "Bulgarian",
955
- ca: "Catalan",
956
- ceb: "Cebuano",
957
- zh: "Chinese",
958
- co: "Corsican",
959
- hr: "Croatian",
960
- cs: "Czech",
961
- da: "Danish",
962
- nl: "Dutch",
963
- en: "English",
964
- eo: "Esperanto",
965
- et: "Estonian",
966
- fi: "Finnish",
967
- fr: "French",
968
- fy: "Frisian",
969
- gl: "Galician",
970
- ka: "Georgian",
971
- de: "German",
972
- el: "Greek",
973
- gu: "Gujarati",
974
- ht: "Haitian Creole",
975
- ha: "Hausa",
976
- haw: "Hawaiian",
977
- he: "Hebrew",
978
- hi: "Hindi",
979
- hmn: "Hmong",
980
- hu: "Hungarian",
981
- is: "Icelandic",
982
- ig: "Igbo",
983
- id: "Indonesian",
984
- ga: "Irish",
985
- it: "Italian",
986
- ja: "Japanese",
987
- jv: "Javanese",
988
- kn: "Kannada",
989
- kk: "Kazakh",
990
- km: "Khmer",
991
- ko: "Korean",
992
- ku: "Kurdish",
993
- ky: "Kyrgyz",
994
- lo: "Lao",
995
- la: "Latin",
996
- lv: "Latvian",
997
- lt: "Lithuanian",
998
- lb: "Luxembourgish",
999
- mk: "Macedonian",
1000
- mg: "Malagasy",
1001
- ms: "Malay",
1002
- ml: "Malayalam",
1003
- mt: "Maltese",
1004
- mi: "Maori",
1005
- mr: "Marathi",
1006
- mn: "Mongolian",
1007
- my: "Myanmar (Burmese)",
1008
- ne: "Nepali",
1009
- no: "Norwegian",
1010
- ny: "Nyanja (Chichewa)",
1011
- ps: "Pashto",
1012
- fa: "Persian",
1013
- pl: "Polish",
1014
- pt: "Portuguese",
1015
- pa: "Punjabi",
1016
- ro: "Romanian",
1017
- ru: "Russian",
1018
- sm: "Samoan",
1019
- gd: "Scots Gaelic",
1020
- sr: "Serbian",
1021
- st: "Sesotho",
1022
- sn: "Shona",
1023
- sd: "Sindhi",
1024
- si: "Sinhala (Sinhalese)",
1025
- sk: "Slovak",
1026
- sl: "Slovenian",
1027
- so: "Somali",
1028
- es: "Spanish",
1029
- su: "Sundanese",
1030
- sw: "Swahili",
1031
- sv: "Swedish",
1032
- tl: "Tagalog (Filipino)",
1033
- tg: "Tajik",
1034
- ta: "Tamil",
1035
- te: "Telugu",
1036
- th: "Thai",
1037
- tr: "Turkish",
1038
- uk: "Ukrainian",
1039
- ur: "Urdu",
1040
- uz: "Uzbek",
1041
- vi: "Vietnamese",
1042
- cy: "Welsh",
1043
- xh: "Xhosa",
1044
- yi: "Yiddish",
1045
- yo: "Yoruba",
1046
- zu: "Zulu"
1047
- };
1048
-
1049
- // src/utils/ai/get-respond-card-tool.ts
1050
- var getRespondCardTool = ({
1051
- language,
1052
- standard = "actfl"
1053
- }) => {
1054
- const lang = all_langs_default[language] || "English";
1055
- const tool = {
1056
- tool_choice: {
1057
- type: "function",
1058
- function: { name: "get_feedback" }
1059
- },
1060
- tools: [
1061
- {
1062
- type: "function",
1063
- function: {
1064
- name: "get_feedback",
1065
- description: "Get feedback on a student's response",
1066
- parameters: {
1067
- type: "object",
1068
- required: [
1069
- "success",
1070
- "score",
1071
- "score_justification",
1072
- "errors",
1073
- "improvedResponse",
1074
- "compliments"
1075
- ],
1076
- properties: {
1077
- success: {
1078
- type: "boolean",
1079
- 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."
1080
- },
1081
- errors: {
1082
- type: "array",
1083
- items: {
1084
- type: "object",
1085
- required: ["error", "grammar_error_type", "correction", "justification"],
1086
- properties: {
1087
- error: {
1088
- type: "string",
1089
- description: "The grammatical error in the student's response."
1090
- },
1091
- correction: {
1092
- type: "string",
1093
- description: "The suggested correction to the error"
1094
- },
1095
- justification: {
1096
- type: "string",
1097
- description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
1098
- },
1099
- grammar_error_type: {
1100
- type: "string",
1101
- enum: [
1102
- "subjVerbAgree",
1103
- "tenseErrors",
1104
- "articleMisuse",
1105
- "prepositionErrors",
1106
- "adjNounAgree",
1107
- "pronounErrors",
1108
- "wordOrder",
1109
- "verbConjugation",
1110
- "pluralization",
1111
- "negationErrors",
1112
- "modalVerbMisuse",
1113
- "relativeClause",
1114
- "auxiliaryVerb",
1115
- "complexSentenceAgreement",
1116
- "idiomaticExpression",
1117
- "registerInconsistency",
1118
- "voiceMisuse"
1119
- ],
1120
- 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"
1121
- }
1122
- }
1123
- },
1124
- 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."
1125
- },
1126
- compliments: {
1127
- type: "array",
1128
- items: {
1129
- type: "string"
1130
- },
1131
- description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
1132
- },
1133
- improvedResponse: {
1134
- type: "string",
1135
- description: "An improved response with proper grammar and more detail, if applicable."
1136
- },
1137
- score: {
1138
- type: "number",
1139
- description: "A score between 0 and 100, reflecting the overall quality of the response"
1140
- },
1141
- score_justification: {
1142
- type: "string",
1143
- description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
1144
- }
1145
- }
1146
- }
1147
- }
1148
- }
1149
- ]
1150
- };
1151
- if (standard === "wida") {
1152
- const wida_level = {
1153
- type: "number",
1154
- enum: [1, 2, 3, 4, 5, 6],
1155
- 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
1156
-
1157
- 1 - Entering
1158
- 2 - Emerging
1159
- 3 - Developing
1160
- 4 - Expanding
1161
- 5 - Bridging
1162
- 6 - Reaching
1163
-
1164
- 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.
1165
- `
1166
- };
1167
- const wida_justification = {
1168
- type: "string",
1169
- description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
1170
- };
1171
- tool.tools[0].function.parameters.required.push("wida_level");
1172
- tool.tools[0].function.parameters.required.push("wida_justification");
1173
- tool.tools[0].function.parameters.properties.wida_level = wida_level;
1174
- tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
1175
- } else {
1176
- const actfl_level = {
1177
- type: "string",
1178
- enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
1179
- 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"
1180
- };
1181
- const actfl_justification = {
1182
- type: "string",
1183
- description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
1184
- };
1185
- tool.tools[0].function.parameters.required.push("actfl_level");
1186
- tool.tools[0].function.parameters.required.push("actfl_justification");
1187
- tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
1188
- tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
1189
- }
1190
- return tool;
1191
- };
1192
-
1193
- // src/utils/debounce.utils.ts
1194
- function debounce(func, waitFor) {
1195
- let timeoutId;
1196
- return (...args) => new Promise((resolve, reject) => {
1197
- if (timeoutId) {
1198
- clearTimeout(timeoutId);
1199
- }
1200
- timeoutId = setTimeout(async () => {
1201
- try {
1202
- const result = await func(...args);
1203
- resolve(result);
1204
- } catch (error) {
1205
- reject(error);
1206
- }
1207
- }, waitFor);
1208
- });
1209
- }
1210
-
1211
- // src/domains/assignment/hooks/score-hooks.ts
1212
- import { useMutation as useMutation2, useQuery as useQuery3 } from "@tanstack/react-query";
1213
-
1214
- // src/lib/tanstack/handle-optimistic-update-query.ts
1215
- var handleOptimisticUpdate = async ({
1216
- queryClient,
1217
- queryKey,
1218
- newData
1219
- }) => {
1220
- await queryClient.cancelQueries({
1221
- queryKey
1222
- });
1223
- const previousData = queryClient.getQueryData(queryKey);
1224
- if (previousData === void 0) {
1225
- queryClient.setQueryData(queryKey, newData);
1226
- } else {
1227
- queryClient.setQueryData(queryKey, { ...previousData, ...newData });
1228
- }
1229
- return { previousData };
1230
- };
1231
-
1232
- // src/constants/speakable-plans.ts
1233
- var FEEDBACK_PLANS = {
1234
- FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
1235
- // Transcript from the audio
1236
- FEEDBACK_SUMMARY: "FEEDBACK_SUMMARY",
1237
- // Chatty summary (Free plan)
1238
- FEEDBACK_GRAMMAR_INSIGHTS: "FEEDBACK_GRAMMAR_INSIGHTS",
1239
- // Grammar insights
1240
- FEEDBACK_SUGGESTED_RESPONSE: "FEEDBACK_SUGGESTED_RESPONSE",
1241
- // Suggested Response
1242
- FEEDBACK_RUBRIC: "FEEDBACK_RUBRIC",
1243
- // Suggested Response
1244
- FEEDBACK_GRADING_STANDARDS: "FEEDBACK_GRADING_STANDARDS",
1245
- // ACTFL / WIDA Estimate
1246
- FEEDBACK_TARGET_LANGUAGE: "FEEDBACK_TARGET_LANGUAGE",
1247
- // Ability to set the feedback language to the target language of the student
1248
- FEEDBACK_DISABLE_ALLOW_RETRIES: "FEEDBACK_DISABLE_ALLOW_RETRIES"
1249
- // Turn of allow retries
405
+ // src/constants/speakable-plans.ts
406
+ var FEEDBACK_PLANS = {
407
+ FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
408
+ // Transcript from the audio
409
+ FEEDBACK_SUMMARY: "FEEDBACK_SUMMARY",
410
+ // Chatty summary (Free plan)
411
+ FEEDBACK_GRAMMAR_INSIGHTS: "FEEDBACK_GRAMMAR_INSIGHTS",
412
+ // Grammar insights
413
+ FEEDBACK_SUGGESTED_RESPONSE: "FEEDBACK_SUGGESTED_RESPONSE",
414
+ // Suggested Response
415
+ FEEDBACK_RUBRIC: "FEEDBACK_RUBRIC",
416
+ // Suggested Response
417
+ FEEDBACK_GRADING_STANDARDS: "FEEDBACK_GRADING_STANDARDS",
418
+ // ACTFL / WIDA Estimate
419
+ FEEDBACK_TARGET_LANGUAGE: "FEEDBACK_TARGET_LANGUAGE",
420
+ // Ability to set the feedback language to the target language of the student
421
+ FEEDBACK_DISABLE_ALLOW_RETRIES: "FEEDBACK_DISABLE_ALLOW_RETRIES"
422
+ // Turn of allow retries
1250
423
  };
1251
424
  var AUTO_GRADING_PLANS = {
1252
425
  AUTO_GRADING_PASS_FAIL: "AUTO_GRADING_PASS_FAIL",
@@ -1366,52 +539,241 @@ var SpeakablePlanTypes = {
1366
539
  growth: "growth",
1367
540
  professional: "professional"
1368
541
  };
1369
- var SpeakablePermissionsMap = {
1370
- [SpeakablePlanTypes.basic]: FREE_PLAN,
1371
- [SpeakablePlanTypes.starter]: TEACHER_PRO_PLAN,
1372
- [SpeakablePlanTypes.teacher_pro]: TEACHER_PRO_PLAN,
1373
- [SpeakablePlanTypes.growth]: ORGANIZATION_PLAN,
1374
- [SpeakablePlanTypes.professional]: ORGANIZATION_PLAN,
1375
- [SpeakablePlanTypes.organization]: ORGANIZATION_PLAN,
1376
- [SpeakablePlanTypes.school_starter]: SCHOOL_STARTER
542
+ var SpeakablePermissionsMap = {
543
+ [SpeakablePlanTypes.basic]: FREE_PLAN,
544
+ [SpeakablePlanTypes.starter]: TEACHER_PRO_PLAN,
545
+ [SpeakablePlanTypes.teacher_pro]: TEACHER_PRO_PLAN,
546
+ [SpeakablePlanTypes.growth]: ORGANIZATION_PLAN,
547
+ [SpeakablePlanTypes.professional]: ORGANIZATION_PLAN,
548
+ [SpeakablePlanTypes.organization]: ORGANIZATION_PLAN,
549
+ [SpeakablePlanTypes.school_starter]: SCHOOL_STARTER
550
+ };
551
+ var SpeakablePlanHierarchy = [
552
+ SpeakablePlanTypes.basic,
553
+ SpeakablePlanTypes.starter,
554
+ SpeakablePlanTypes.teacher_pro,
555
+ SpeakablePlanTypes.growth,
556
+ SpeakablePlanTypes.professional,
557
+ SpeakablePlanTypes.school_starter,
558
+ SpeakablePlanTypes.organization
559
+ ];
560
+
561
+ // src/hooks/usePermissions.ts
562
+ var usePermissions = () => {
563
+ const { permissions } = useSpeakableApi();
564
+ const has = (permission) => {
565
+ var _a;
566
+ return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
567
+ };
568
+ return {
569
+ plan: permissions.plan,
570
+ permissionsLoaded: permissions.loaded,
571
+ isStripePlan: permissions.isStripePlan,
572
+ refreshDate: permissions.refreshDate,
573
+ isInstitutionPlan: permissions.isInstitutionPlan,
574
+ subscriptionId: permissions.subscriptionId,
575
+ contact: permissions.contact,
576
+ hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
577
+ hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
578
+ hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
579
+ hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
580
+ hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
581
+ permissions: permissions || [],
582
+ hasStudentPortfolios: permissions.hasStudentPortfolios,
583
+ isFreeOrgTrial: permissions.type === "free_org_trial",
584
+ freeOrgTrialExpired: permissions.freeOrgTrialExpired
585
+ };
586
+ };
587
+ var usePermissions_default = usePermissions;
588
+
589
+ // src/domains/notification/notification.constants.ts
590
+ var SPEAKABLE_NOTIFICATIONS = {
591
+ NEW_ASSIGNMENT: "new_assignment",
592
+ ASSESSMENT_SUBMITTED: "assessment_submitted",
593
+ ASSESSMENT_SCORED: "assessment_scored",
594
+ NEW_COMMENT: "NEW_COMMENT"
595
+ };
596
+ var SpeakableNotificationTypes = {
597
+ NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
598
+ FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
599
+ MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
600
+ PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
601
+ STUDENT_PROGRESS: "STUDENT_PROGRESS",
602
+ PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
603
+ PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
604
+ // New notifications
605
+ ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
606
+ // Notification FOR TEACHER when student submits assessment
607
+ ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
608
+ // Notification FOR STUDENT when teacher scores assessment
609
+ // Comment
610
+ NEW_COMMENT: "NEW_COMMENT"
611
+ };
612
+
613
+ // src/domains/notification/services/create-notification.service.ts
614
+ import dayjs2 from "dayjs";
615
+
616
+ // src/constants/web.constants.ts
617
+ var WEB_BASE_URL = "https://app.speakable.io";
618
+
619
+ // src/domains/notification/services/send-notification.service.ts
620
+ var _sendNotification = async (sendTo, notification) => {
621
+ var _a, _b, _c;
622
+ const results = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createNotificationV2")) == null ? void 0 : _c({
623
+ sendTo,
624
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
625
+ notification
626
+ }));
627
+ return results;
628
+ };
629
+ var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
630
+
631
+ // src/domains/notification/services/create-notification.service.ts
632
+ var createNotification = async ({
633
+ data,
634
+ type,
635
+ userId,
636
+ profile
637
+ }) => {
638
+ let result;
639
+ switch (type) {
640
+ case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
641
+ result = await handleAssignNotifPromise({ data, profile });
642
+ break;
643
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
644
+ result = await createAssessmentSubmissionNotification({
645
+ data,
646
+ profile,
647
+ userId
648
+ });
649
+ break;
650
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
651
+ result = await createAssessmentScoredNotification({
652
+ data,
653
+ profile
654
+ });
655
+ break;
656
+ default:
657
+ result = null;
658
+ break;
659
+ }
660
+ return result;
661
+ };
662
+ var handleAssignNotifPromise = async ({
663
+ data: assignments,
664
+ profile
665
+ }) => {
666
+ if (!assignments.length) return;
667
+ try {
668
+ const notifsPromises = assignments.map(async (assignment) => {
669
+ const {
670
+ section,
671
+ section: { members },
672
+ ...rest
673
+ } = assignment;
674
+ if (!section || !members) throw new Error("Invalid assignment data");
675
+ const data = { section, sendTo: members, assignment: { ...rest } };
676
+ return createNewAssignmentNotification({ data, profile });
677
+ });
678
+ await Promise.all(notifsPromises);
679
+ return {
680
+ success: true,
681
+ message: "Assignment notifications sent successfully"
682
+ };
683
+ } catch (error) {
684
+ console.error("Error in handleAssignNotifPromise:", error);
685
+ throw error;
686
+ }
687
+ };
688
+ var createNewAssignmentNotification = async ({
689
+ data,
690
+ profile
691
+ }) => {
692
+ var _a;
693
+ const { assignment, sendTo } = data;
694
+ const teacherName = profile.displayName || "Your teacher";
695
+ const dueDate = assignment.dueDateTimestamp ? dayjs2(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
696
+ const results = await sendNotification(sendTo, {
697
+ courseId: assignment.courseId,
698
+ type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
699
+ senderName: teacherName,
700
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
701
+ messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
702
+ title: "New Assignment Available!",
703
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
704
+ });
705
+ return results;
706
+ };
707
+ var createAssessmentSubmissionNotification = async ({
708
+ data: assignment,
709
+ profile,
710
+ userId
711
+ }) => {
712
+ var _a;
713
+ const studentName = profile.displayName || "Your student";
714
+ const results = await sendNotification(assignment.owners, {
715
+ courseId: assignment.courseId,
716
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
717
+ link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
718
+ title: `Assessment Submitted!`,
719
+ senderName: studentName,
720
+ messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
721
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
722
+ });
723
+ return results;
724
+ };
725
+ var createAssessmentScoredNotification = async ({
726
+ data,
727
+ profile
728
+ }) => {
729
+ var _a, _b, _c, _d, _e;
730
+ const { assignment, sendTo } = data;
731
+ const teacherName = profile.displayName || "Your teacher";
732
+ const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
733
+ const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
734
+ const results = await sendNotification(sendTo, {
735
+ courseId: assignment.courseId,
736
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
737
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
738
+ title,
739
+ messagePreview,
740
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
741
+ senderName: teacherName
742
+ });
743
+ await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
744
+ assessmentTitle: assignment.name,
745
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
746
+ senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
747
+ studentId: sendTo[0],
748
+ teacherName: profile.displayName
749
+ }));
750
+ return results;
1377
751
  };
1378
- var SpeakablePlanHierarchy = [
1379
- SpeakablePlanTypes.basic,
1380
- SpeakablePlanTypes.starter,
1381
- SpeakablePlanTypes.teacher_pro,
1382
- SpeakablePlanTypes.growth,
1383
- SpeakablePlanTypes.professional,
1384
- SpeakablePlanTypes.school_starter,
1385
- SpeakablePlanTypes.organization
1386
- ];
1387
752
 
1388
- // src/hooks/usePermissions.ts
1389
- var usePermissions = () => {
1390
- const { permissions } = useSpeakableApi();
1391
- const has = (permission) => {
1392
- var _a;
1393
- return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
753
+ // src/domains/notification/hooks/notification.hooks.ts
754
+ var notificationQueryKeys = {
755
+ all: ["notifications"],
756
+ byId: (id) => [...notificationQueryKeys.all, id]
757
+ };
758
+ var useCreateNotification = () => {
759
+ const { user, queryClient } = useSpeakableApi();
760
+ const handleCreateNotifications = async (type, data) => {
761
+ var _a, _b;
762
+ const result = await createNotification({
763
+ type,
764
+ userId: user.auth.uid,
765
+ profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
766
+ data
767
+ });
768
+ queryClient.invalidateQueries({
769
+ queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
770
+ });
771
+ return result;
1394
772
  };
1395
773
  return {
1396
- plan: permissions.plan,
1397
- permissionsLoaded: permissions.loaded,
1398
- isStripePlan: permissions.isStripePlan,
1399
- refreshDate: permissions.refreshDate,
1400
- isInstitutionPlan: permissions.isInstitutionPlan,
1401
- subscriptionId: permissions.subscriptionId,
1402
- contact: permissions.contact,
1403
- hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
1404
- hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
1405
- hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
1406
- hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
1407
- hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
1408
- permissions: permissions || [],
1409
- hasStudentPortfolios: permissions.hasStudentPortfolios,
1410
- isFreeOrgTrial: permissions.type === "free_org_trial",
1411
- freeOrgTrialExpired: permissions.freeOrgTrialExpired
774
+ createNotification: handleCreateNotifications
1412
775
  };
1413
776
  };
1414
- var usePermissions_default = usePermissions;
1415
777
 
1416
778
  // src/hooks/useGoogleClassroom.ts
1417
779
  var useGoogleClassroom = () => {
@@ -1685,446 +1047,1091 @@ var calculateScoreAndProgress = (scores, cardsList, weights) => {
1685
1047
  };
1686
1048
  var calculateScoreAndProgress_default = calculateScoreAndProgress;
1687
1049
 
1688
- // src/domains/assignment/services/update-score.service.ts
1689
- async function _updateScore(params) {
1690
- const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1691
- id: params.activityId,
1692
- userId: params.userId
1693
- }) : refsScoresPractice.practiceScores({
1694
- setId: params.activityId,
1695
- userId: params.userId
1050
+ // src/domains/assignment/services/update-score.service.ts
1051
+ async function _updateScore(params) {
1052
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1053
+ id: params.activityId,
1054
+ userId: params.userId
1055
+ }) : refsScoresPractice.practiceScores({
1056
+ setId: params.activityId,
1057
+ userId: params.userId
1058
+ });
1059
+ await api.updateDoc(path, {
1060
+ ...params.data
1061
+ });
1062
+ }
1063
+ var updateScore = withErrorHandler(_updateScore, "updateScore");
1064
+ async function _updateCardScore(params) {
1065
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1066
+ id: params.activityId,
1067
+ userId: params.userId
1068
+ }) : refsScoresPractice.practiceScores({
1069
+ setId: params.activityId,
1070
+ userId: params.userId
1071
+ });
1072
+ const updates = Object.keys(params.updates.cardScore).reduce(
1073
+ (acc, key) => {
1074
+ acc[`cards.${params.cardId}.${key}`] = params.updates.cardScore[key];
1075
+ return acc;
1076
+ },
1077
+ {}
1078
+ );
1079
+ if (params.updates.progress) {
1080
+ updates.progress = params.updates.progress;
1081
+ }
1082
+ if (params.updates.score) {
1083
+ updates.score = params.updates.score;
1084
+ }
1085
+ await api.updateDoc(path, {
1086
+ ...updates
1087
+ });
1088
+ }
1089
+ var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
1090
+
1091
+ // src/domains/assignment/services/clear-score.service.ts
1092
+ import dayjs3 from "dayjs";
1093
+ async function clearScore(params) {
1094
+ var _a, _b, _c, _d, _e;
1095
+ const update = {
1096
+ [`cards.${params.cardId}`]: {
1097
+ attempts: (_a = params.cardScores.attempts) != null ? _a : 1,
1098
+ correct: (_b = params.cardScores.correct) != null ? _b : 0,
1099
+ // save old score history
1100
+ history: [
1101
+ {
1102
+ ...params.cardScores,
1103
+ attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1104
+ correct: (_d = params.cardScores.correct) != null ? _d : 0,
1105
+ retryTime: dayjs3().format("YYYY-MM-DD HH:mm:ss"),
1106
+ history: null
1107
+ },
1108
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1109
+ ...(_e = params.cardScores.history) != null ? _e : []
1110
+ ]
1111
+ }
1112
+ };
1113
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1114
+ id: params.activityId,
1115
+ userId: params.userId
1116
+ }) : refsScoresPractice.practiceScores({
1117
+ setId: params.activityId,
1118
+ userId: params.userId
1119
+ });
1120
+ await api.updateDoc(path, update);
1121
+ return {
1122
+ update,
1123
+ activityId: params.activityId
1124
+ };
1125
+ }
1126
+
1127
+ // src/domains/assignment/services/submit-assignment-score.service.ts
1128
+ import dayjs4 from "dayjs";
1129
+ async function _submitAssignmentScore({
1130
+ cardIds,
1131
+ assignment,
1132
+ weights,
1133
+ userId,
1134
+ status,
1135
+ studentName
1136
+ }) {
1137
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1138
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1139
+ const fieldsUpdated = {
1140
+ submitted: true,
1141
+ progress: 100,
1142
+ submissionDate: serverTimestamp2(),
1143
+ status
1144
+ };
1145
+ if (assignment.isAssessment) {
1146
+ const result = await handleAssessment(
1147
+ assignment,
1148
+ userId,
1149
+ cardIds,
1150
+ weights,
1151
+ fieldsUpdated,
1152
+ studentName
1153
+ );
1154
+ return result;
1155
+ } else if (assignment.courseId) {
1156
+ await handleCourseAssignment(assignment, userId);
1157
+ }
1158
+ await api.updateDoc(path, { ...fieldsUpdated });
1159
+ return { success: true, fieldsUpdated };
1160
+ }
1161
+ var submitAssignmentScore = withErrorHandler(
1162
+ _submitAssignmentScore,
1163
+ "submitAssignmentScore"
1164
+ );
1165
+ async function handleAssessment(assignment, userId, cardIds, weights, fieldsUpdated, studentName) {
1166
+ var _a, _b, _c;
1167
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1168
+ const response = await api.getDoc(path);
1169
+ if (!response.data) {
1170
+ throw new Error("Score not found");
1171
+ }
1172
+ const { score: scoreCalculated } = calculateScoreAndProgress_default(response.data, cardIds, weights);
1173
+ await api.updateDoc(path, { score: scoreCalculated, status: "PENDING_REVIEW" });
1174
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssessment")) == null ? void 0 : _c({
1175
+ assignmentId: assignment.id,
1176
+ assignmentTitle: assignment.name,
1177
+ userId,
1178
+ teacherId: assignment.owners[0],
1179
+ studentName
1180
+ }));
1181
+ fieldsUpdated.status = "PENDING_REVIEW";
1182
+ return { success: true, fieldsUpdated };
1183
+ }
1184
+ async function handleCourseAssignment(assignment, userId) {
1185
+ var _a, _b, _c;
1186
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentV2")) == null ? void 0 : _c({
1187
+ assignmentId: assignment.id,
1188
+ userId
1189
+ }));
1190
+ }
1191
+ async function submitPracticeScore({
1192
+ setId,
1193
+ userId,
1194
+ scores
1195
+ }) {
1196
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1197
+ const date = dayjs4().format("YYYY-MM-DD-HH-mm");
1198
+ const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
1199
+ const fieldsUpdated = {
1200
+ ...scores,
1201
+ submitted: true,
1202
+ progress: 100,
1203
+ submissionDate: serverTimestamp2(),
1204
+ status: "SUBMITTED"
1205
+ };
1206
+ await api.setDoc(ref, { ...fieldsUpdated });
1207
+ const refScores = refsScoresPractice.practiceScores({ userId, setId });
1208
+ await api.deleteDoc(refScores);
1209
+ return { success: true, fieldsUpdated };
1210
+ }
1211
+
1212
+ // src/domains/assignment/hooks/score-hooks.ts
1213
+ var scoreQueryKeys = {
1214
+ all: ["scores"],
1215
+ byId: (id) => [...scoreQueryKeys.all, id],
1216
+ list: () => [...scoreQueryKeys.all, "list"]
1217
+ };
1218
+ function useScore({
1219
+ isAssignment,
1220
+ activityId,
1221
+ userId = "",
1222
+ courseId,
1223
+ enabled = true,
1224
+ googleClassroomUserId
1225
+ }) {
1226
+ return useQuery2({
1227
+ queryFn: () => getScore({
1228
+ userId,
1229
+ courseId,
1230
+ activityId,
1231
+ googleClassroomUserId,
1232
+ isAssignment
1233
+ }),
1234
+ queryKey: scoreQueryKeys.byId(activityId),
1235
+ enabled
1696
1236
  });
1697
- await api.updateDoc(path, {
1698
- ...params.data
1237
+ }
1238
+ var debounceUpdateScore = debounce(updateScore, 1e3);
1239
+ function useUpdateScore() {
1240
+ const { queryClient } = useSpeakableApi();
1241
+ const mutation = useMutation({
1242
+ mutationFn: debounceUpdateScore,
1243
+ onMutate: (variables) => {
1244
+ return handleOptimisticUpdate({
1245
+ queryClient,
1246
+ queryKey: scoreQueryKeys.byId(variables.activityId),
1247
+ newData: variables.data
1248
+ });
1249
+ },
1250
+ onError: (_, variables, context) => {
1251
+ if (context == null ? void 0 : context.previousData)
1252
+ queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1253
+ },
1254
+ onSettled: (_, err, variables) => {
1255
+ queryClient.invalidateQueries({
1256
+ queryKey: scoreQueryKeys.byId(variables.activityId)
1257
+ });
1258
+ }
1699
1259
  });
1260
+ return {
1261
+ mutationUpdateScore: mutation
1262
+ };
1700
1263
  }
1701
- var updateScore = withErrorHandler(_updateScore, "updateScore");
1702
- async function _updateCardScore(params) {
1703
- const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1704
- id: params.activityId,
1705
- userId: params.userId
1706
- }) : refsScoresPractice.practiceScores({
1707
- setId: params.activityId,
1708
- userId: params.userId
1264
+ function useUpdateCardScore({
1265
+ isAssignment,
1266
+ activityId,
1267
+ userId,
1268
+ cardIds,
1269
+ weights
1270
+ }) {
1271
+ const { queryClient } = useSpeakableApi();
1272
+ const queryKey = scoreQueryKeys.byId(activityId);
1273
+ const mutation = useMutation({
1274
+ mutationFn: async ({ cardId, cardScore }) => {
1275
+ const previousScores = queryClient.getQueryData(queryKey);
1276
+ const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1277
+ previousScores: previousScores != null ? previousScores : {},
1278
+ cardId,
1279
+ cardScore,
1280
+ cardIds,
1281
+ weights
1282
+ });
1283
+ await updateCardScore({
1284
+ userId,
1285
+ cardId,
1286
+ isAssignment,
1287
+ activityId,
1288
+ updates: {
1289
+ cardScore: updatedCardScore,
1290
+ progress,
1291
+ score
1292
+ }
1293
+ });
1294
+ return { cardId, scoresUpdated: newScoreUpdated };
1295
+ },
1296
+ onMutate: ({ cardId, cardScore }) => {
1297
+ queryClient.setQueryData(queryKey, (previousScore) => {
1298
+ const updates = handleOptimisticScore({
1299
+ score: previousScore,
1300
+ cardId,
1301
+ cardScore,
1302
+ cardIds,
1303
+ weights
1304
+ });
1305
+ return {
1306
+ ...previousScore,
1307
+ ...updates
1308
+ };
1309
+ });
1310
+ },
1311
+ // eslint-disable-next-line no-unused-vars
1312
+ onError: (error) => {
1313
+ console.log(error.message);
1314
+ const previousData = queryClient.getQueryData(queryKey);
1315
+ if (previousData) {
1316
+ queryClient.setQueryData(queryKey, previousData);
1317
+ }
1318
+ }
1709
1319
  });
1710
- const updates = Object.keys(params.updates.cardScore).reduce(
1711
- (acc, key) => {
1712
- acc[`cards.${params.cardId}.${key}`] = params.updates.cardScore[key];
1713
- return acc;
1320
+ return {
1321
+ mutationUpdateCardScore: mutation
1322
+ };
1323
+ }
1324
+ var getScoreUpdated = ({
1325
+ cardId,
1326
+ cardScore,
1327
+ previousScores,
1328
+ cardIds,
1329
+ weights
1330
+ }) => {
1331
+ var _a, _b;
1332
+ const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
1333
+ const newCardScore = {
1334
+ ...previousCard != null ? previousCard : {},
1335
+ ...cardScore
1336
+ };
1337
+ const newScores = {
1338
+ ...previousScores,
1339
+ cards: {
1340
+ ...(_b = previousScores.cards) != null ? _b : {},
1341
+ [cardId]: newCardScore
1342
+ }
1343
+ };
1344
+ const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
1345
+ return {
1346
+ newScoreUpdated: newScores,
1347
+ updatedCardScore: cardScore,
1348
+ score,
1349
+ progress
1350
+ };
1351
+ };
1352
+ var handleOptimisticScore = ({
1353
+ score,
1354
+ cardId,
1355
+ cardScore,
1356
+ cardIds,
1357
+ weights
1358
+ }) => {
1359
+ var _a;
1360
+ let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
1361
+ cards = {
1362
+ ...cards,
1363
+ [cardId]: {
1364
+ ...cards[cardId],
1365
+ ...cardScore
1366
+ }
1367
+ };
1368
+ const { score: scoreValue, progress } = calculateScoreAndProgress_default(
1369
+ // @ts-ignore
1370
+ {
1371
+ ...score != null ? score : {},
1372
+ cards
1714
1373
  },
1715
- {}
1374
+ cardIds,
1375
+ weights
1716
1376
  );
1717
- if (params.updates.progress) {
1718
- updates.progress = params.updates.progress;
1719
- }
1720
- if (params.updates.score) {
1721
- updates.score = params.updates.score;
1722
- }
1723
- await api.updateDoc(path, {
1724
- ...updates
1377
+ return { cards, score: scoreValue, progress };
1378
+ };
1379
+ function useClearScore() {
1380
+ const { queryClient } = useSpeakableApi();
1381
+ const mutation = useMutation({
1382
+ mutationFn: clearScore,
1383
+ onSettled: (result) => {
1384
+ var _a;
1385
+ queryClient.invalidateQueries({
1386
+ queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
1387
+ });
1388
+ }
1389
+ });
1390
+ return {
1391
+ mutationClearScore: mutation
1392
+ };
1393
+ }
1394
+ function useSubmitAssignmentScore({
1395
+ onAssignmentSubmitted,
1396
+ studentName
1397
+ }) {
1398
+ const { queryClient } = useSpeakableApi();
1399
+ const { hasGoogleClassroomGradePassback } = usePermissions_default();
1400
+ const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
1401
+ const { createNotification: createNotification2 } = useCreateNotification();
1402
+ const mutation = useMutation({
1403
+ mutationFn: async ({
1404
+ assignment,
1405
+ userId,
1406
+ cardIds,
1407
+ weights,
1408
+ scores,
1409
+ status
1410
+ }) => {
1411
+ try {
1412
+ const scoreUpdated = await submitAssignmentScore({
1413
+ assignment,
1414
+ userId,
1415
+ cardIds,
1416
+ weights,
1417
+ status,
1418
+ studentName
1419
+ });
1420
+ if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
1421
+ await submitAssignmentToGoogleClassroom({
1422
+ assignment,
1423
+ scores
1424
+ });
1425
+ }
1426
+ if (assignment.isAssessment) {
1427
+ createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1428
+ }
1429
+ if (assignment == null ? void 0 : assignment.id) {
1430
+ logSubmitAssignment({
1431
+ courseId: assignment == null ? void 0 : assignment.courseId
1432
+ });
1433
+ }
1434
+ onAssignmentSubmitted(assignment.id);
1435
+ queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1436
+ ...scores,
1437
+ ...scoreUpdated.fieldsUpdated
1438
+ });
1439
+ return {
1440
+ success: true,
1441
+ message: "Score submitted successfully"
1442
+ };
1443
+ } catch (error) {
1444
+ return {
1445
+ success: false,
1446
+ error
1447
+ };
1448
+ }
1449
+ }
1725
1450
  });
1451
+ return {
1452
+ submitAssignmentScore: mutation.mutateAsync,
1453
+ isLoading: mutation.isPending
1454
+ };
1726
1455
  }
1727
- var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
1728
-
1729
- // src/domains/assignment/services/clear-score.service.ts
1730
- import dayjs3 from "dayjs";
1731
- async function clearScore(params) {
1732
- var _a, _b, _c, _d, _e;
1733
- const update = {
1734
- [`cards.${params.cardId}`]: {
1735
- attempts: (_a = params.cardScores.attempts) != null ? _a : 1,
1736
- correct: (_b = params.cardScores.correct) != null ? _b : 0,
1737
- // save old score history
1738
- history: [
1739
- {
1740
- ...params.cardScores,
1741
- attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1742
- correct: (_d = params.cardScores.correct) != null ? _d : 0,
1743
- retryTime: dayjs3().format("YYYY-MM-DD HH:mm:ss"),
1744
- history: null
1745
- },
1746
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1747
- ...(_e = params.cardScores.history) != null ? _e : []
1748
- ]
1456
+ function useSubmitPracticeScore() {
1457
+ const { queryClient } = useSpeakableApi();
1458
+ const mutation = useMutation({
1459
+ mutationFn: async ({
1460
+ setId,
1461
+ userId,
1462
+ scores
1463
+ }) => {
1464
+ try {
1465
+ await submitPracticeScore({
1466
+ setId,
1467
+ userId,
1468
+ scores
1469
+ });
1470
+ queryClient.invalidateQueries({
1471
+ queryKey: scoreQueryKeys.byId(setId)
1472
+ });
1473
+ return {
1474
+ success: true,
1475
+ message: "Score submitted successfully"
1476
+ };
1477
+ } catch (error) {
1478
+ return {
1479
+ success: false,
1480
+ error
1481
+ };
1482
+ }
1749
1483
  }
1750
- };
1751
- const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1752
- id: params.activityId,
1753
- userId: params.userId
1754
- }) : refsScoresPractice.practiceScores({
1755
- setId: params.activityId,
1756
- userId: params.userId
1757
1484
  });
1758
- await api.updateDoc(path, update);
1759
1485
  return {
1760
- update,
1761
- activityId: params.activityId
1486
+ submitPracticeScore: mutation.mutateAsync,
1487
+ isLoading: mutation.isPending
1762
1488
  };
1763
1489
  }
1764
1490
 
1765
- // src/domains/assignment/services/submit-assignment-score.service.ts
1766
- import dayjs4 from "dayjs";
1767
- async function _submitAssignmentScore({
1768
- cardIds,
1769
- assignment,
1770
- weights,
1771
- userId,
1772
- status,
1773
- studentName
1774
- }) {
1775
- const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1776
- const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1777
- const fieldsUpdated = {
1778
- submitted: true,
1779
- progress: 100,
1780
- submissionDate: serverTimestamp2(),
1781
- status
1782
- };
1783
- if (assignment.isAssessment) {
1784
- const result = await handleAssessment(
1785
- assignment,
1786
- userId,
1787
- cardIds,
1788
- weights,
1789
- fieldsUpdated,
1790
- studentName
1791
- );
1792
- return result;
1793
- } else if (assignment.courseId) {
1794
- await handleCourseAssignment(assignment, userId);
1491
+ // src/domains/cards/card.hooks.ts
1492
+ import { useMutation as useMutation2, useQueries, useQuery as useQuery3 } from "@tanstack/react-query";
1493
+ import { useMemo } from "react";
1494
+
1495
+ // src/domains/cards/card.constants.ts
1496
+ var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
1497
+ FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
1498
+ FeedbackTypesCard2["Wida"] = "wida";
1499
+ FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
1500
+ FeedbackTypesCard2["Actfl"] = "actfl";
1501
+ FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
1502
+ return FeedbackTypesCard2;
1503
+ })(FeedbackTypesCard || {});
1504
+ var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
1505
+ LeniencyCard2["CONFIDENCE"] = "confidence";
1506
+ LeniencyCard2["EASY"] = "easy";
1507
+ LeniencyCard2["NORMAL"] = "normal";
1508
+ LeniencyCard2["HARD"] = "hard";
1509
+ return LeniencyCard2;
1510
+ })(LeniencyCard || {});
1511
+ var LENIENCY_OPTIONS = [
1512
+ {
1513
+ label: "Build Confidence - most lenient",
1514
+ value: "confidence" /* CONFIDENCE */
1515
+ },
1516
+ {
1517
+ label: "Very Lenient",
1518
+ value: "easy" /* EASY */
1519
+ },
1520
+ {
1521
+ label: "Normal",
1522
+ value: "normal" /* NORMAL */
1523
+ },
1524
+ {
1525
+ label: "No leniency - most strict",
1526
+ value: "hard" /* HARD */
1527
+ }
1528
+ ];
1529
+ var STUDENT_LEVELS_OPTIONS = [
1530
+ {
1531
+ label: "Beginner",
1532
+ description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
1533
+ value: "beginner"
1534
+ },
1535
+ {
1536
+ label: "Elementary",
1537
+ description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
1538
+ value: "elementary"
1539
+ },
1540
+ {
1541
+ label: "Intermediate",
1542
+ description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
1543
+ value: "intermediate"
1544
+ },
1545
+ {
1546
+ label: "Advanced",
1547
+ description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
1548
+ value: "advanced"
1549
+ },
1550
+ {
1551
+ label: "Fluent",
1552
+ description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
1553
+ value: "fluent"
1554
+ },
1555
+ {
1556
+ label: "Native-like",
1557
+ description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
1558
+ value: "nativeLike"
1559
+ }
1560
+ ];
1561
+ var BASE_RESPOND_FIELD_VALUES = {
1562
+ title: "",
1563
+ allowRetries: true,
1564
+ respondTime: 180,
1565
+ maxCharacters: 1e3
1566
+ };
1567
+ var BASE_REPEAT_FIELD_VALUES = {
1568
+ repeat: 1
1569
+ };
1570
+ var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
1571
+ MCQType: "single",
1572
+ answer: ["A"],
1573
+ choices: [
1574
+ { option: "A", value: "Option A" },
1575
+ { option: "B", value: "Option B" },
1576
+ { option: "C", value: "Option C" }
1577
+ ]
1578
+ };
1579
+ var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
1580
+ VerificationCardStatus2["VERIFIED"] = "VERIFIED";
1581
+ VerificationCardStatus2["WARNING"] = "WARNING";
1582
+ VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
1583
+ VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
1584
+ VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
1585
+ return VerificationCardStatus2;
1586
+ })(VerificationCardStatus || {});
1587
+ var CARDS_COLLECTION = "flashcards";
1588
+ var refsCardsFiresotre = {
1589
+ allCards: CARDS_COLLECTION,
1590
+ card: (id) => `${CARDS_COLLECTION}/${id}`
1591
+ };
1592
+
1593
+ // src/domains/cards/services/get-card.service.ts
1594
+ async function _getCard(params) {
1595
+ const ref = refsCardsFiresotre.card(params.cardId);
1596
+ const response = await api.getDoc(ref);
1597
+ if (!response.data) return null;
1598
+ return response.data;
1599
+ }
1600
+ var getCard = withErrorHandler(_getCard, "getCard");
1601
+
1602
+ // src/domains/cards/services/create-card.service.ts
1603
+ import { v4 } from "uuid";
1604
+
1605
+ // src/domains/cards/card.model.ts
1606
+ var CardActivityType = /* @__PURE__ */ ((CardActivityType2) => {
1607
+ CardActivityType2["READ_REPEAT"] = "READ_REPEAT";
1608
+ CardActivityType2["VIDEO"] = "VIDEO";
1609
+ CardActivityType2["TEXT"] = "TEXT";
1610
+ CardActivityType2["READ_RESPOND"] = "READ_RESPOND";
1611
+ CardActivityType2["FREE_RESPONSE"] = "FREE_RESPONSE";
1612
+ CardActivityType2["REPEAT"] = "REPEAT";
1613
+ CardActivityType2["RESPOND"] = "RESPOND";
1614
+ CardActivityType2["RESPOND_WRITE"] = "RESPOND_WRITE";
1615
+ CardActivityType2["TEXT_TO_SPEECH"] = "TEXT_TO_SPEECH";
1616
+ CardActivityType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
1617
+ CardActivityType2["PODCAST"] = "PODCAST";
1618
+ CardActivityType2["MEDIA_PAGE"] = "MEDIA_PAGE";
1619
+ CardActivityType2["WRITE"] = "WRITE";
1620
+ CardActivityType2["SHORT_ANSWER"] = "SHORT_ANSWER";
1621
+ CardActivityType2["SHORT_STORY"] = "SHORT_STORY";
1622
+ CardActivityType2["SPEAK"] = "SPEAK";
1623
+ CardActivityType2["CONVERSATION"] = "CONVERSATION";
1624
+ CardActivityType2["CONVERSATION_WRITE"] = "CONVERSATION_WRITE";
1625
+ CardActivityType2["DIALOGUE"] = "DIALOGUE";
1626
+ CardActivityType2["INSTRUCTION"] = "INSTRUCTION";
1627
+ CardActivityType2["LISTEN"] = "LISTEN";
1628
+ CardActivityType2["READ"] = "READ";
1629
+ CardActivityType2["ANSWER"] = "ANSWER";
1630
+ return CardActivityType2;
1631
+ })(CardActivityType || {});
1632
+ var RESPOND_CARD_ACTIVITY_TYPES = [
1633
+ "READ_RESPOND" /* READ_RESPOND */,
1634
+ "RESPOND" /* RESPOND */,
1635
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1636
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1637
+ ];
1638
+ var MULTIPLE_CHOICE_CARD_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
1639
+ var REPEAT_CARD_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
1640
+ var RESPOND_WRITE_CARD_ACTIVITY_TYPES = [
1641
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1642
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1643
+ ];
1644
+ var RESPOND_AUDIO_CARD_ACTIVITY_TYPES = [
1645
+ "RESPOND" /* RESPOND */,
1646
+ "READ_RESPOND" /* READ_RESPOND */
1647
+ ];
1648
+ var ALLOWED_CARD_ACTIVITY_TYPES_FOR_SUMMARY = [
1649
+ "REPEAT" /* REPEAT */,
1650
+ "RESPOND" /* RESPOND */,
1651
+ "READ_REPEAT" /* READ_REPEAT */,
1652
+ "READ_RESPOND" /* READ_RESPOND */,
1653
+ "FREE_RESPONSE" /* FREE_RESPONSE */,
1654
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1655
+ "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */
1656
+ ];
1657
+
1658
+ // src/utils/text-utils.ts
1659
+ import sha1 from "js-sha1";
1660
+ var purify = (word) => {
1661
+ 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();
1662
+ };
1663
+ var cleanString = (words) => {
1664
+ const splitWords = words == null ? void 0 : words.split("+");
1665
+ if (splitWords && splitWords.length === 1) {
1666
+ const newWord = purify(words);
1667
+ return newWord;
1668
+ } else if (splitWords && splitWords.length > 1) {
1669
+ const split = splitWords.map((w) => purify(w));
1670
+ return split;
1671
+ } else {
1672
+ return "";
1673
+ }
1674
+ };
1675
+ var getWordHash = (word, language) => {
1676
+ const cleanedWord = cleanString(word);
1677
+ const wordHash = sha1(`${language}-${cleanedWord}`);
1678
+ console.log("wordHash core library", wordHash);
1679
+ return wordHash;
1680
+ };
1681
+
1682
+ // src/domains/cards/services/get-card-verification-status.service.ts
1683
+ var charactarLanguages = ["zh", "ja", "ko"];
1684
+ var getVerificationStatus = async (target_text, language) => {
1685
+ if ((target_text == null ? void 0 : target_text.length) < 3 && !charactarLanguages.includes(language)) {
1686
+ return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1795
1687
  }
1796
- await api.updateDoc(path, { ...fieldsUpdated });
1797
- return { success: true, fieldsUpdated };
1798
- }
1799
- var submitAssignmentScore = withErrorHandler(
1800
- _submitAssignmentScore,
1801
- "submitAssignmentScore"
1802
- );
1803
- async function handleAssessment(assignment, userId, cardIds, weights, fieldsUpdated, studentName) {
1804
- var _a, _b, _c;
1805
- const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1806
- const response = await api.getDoc(path);
1807
- if (!response.data) {
1808
- throw new Error("Score not found");
1688
+ const hash = getWordHash(target_text, language);
1689
+ const response = await api.getDoc(`checked-pronunciations/${hash}`);
1690
+ try {
1691
+ if (response.data) {
1692
+ return processRecord(response.data);
1693
+ } else {
1694
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1695
+ }
1696
+ } catch (e) {
1697
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1809
1698
  }
1810
- const { score: scoreCalculated } = calculateScoreAndProgress_default(response.data, cardIds, weights);
1811
- await api.updateDoc(path, { score: scoreCalculated, status: "PENDING_REVIEW" });
1812
- await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssessment")) == null ? void 0 : _c({
1813
- assignmentId: assignment.id,
1814
- assignmentTitle: assignment.name,
1815
- userId,
1816
- teacherId: assignment.owners[0],
1817
- studentName
1818
- }));
1819
- fieldsUpdated.status = "PENDING_REVIEW";
1820
- return { success: true, fieldsUpdated };
1821
- }
1822
- async function handleCourseAssignment(assignment, userId) {
1823
- var _a, _b, _c;
1824
- await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentV2")) == null ? void 0 : _c({
1825
- assignmentId: assignment.id,
1826
- userId
1827
- }));
1828
- }
1829
- async function submitPracticeScore({
1830
- setId,
1831
- userId,
1832
- scores
1833
- }) {
1834
- const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1835
- const date = dayjs4().format("YYYY-MM-DD-HH-mm");
1836
- const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
1837
- const fieldsUpdated = {
1838
- ...scores,
1839
- submitted: true,
1840
- progress: 100,
1841
- submissionDate: serverTimestamp2(),
1842
- status: "SUBMITTED"
1843
- };
1844
- await api.setDoc(ref, { ...fieldsUpdated });
1845
- const refScores = refsScoresPractice.practiceScores({ userId, setId });
1846
- await api.deleteDoc(refScores);
1847
- return { success: true, fieldsUpdated };
1848
- }
1849
-
1850
- // src/domains/assignment/hooks/score-hooks.ts
1851
- var scoreQueryKeys = {
1852
- all: ["scores"],
1853
- byId: (id) => [...scoreQueryKeys.all, id],
1854
- list: () => [...scoreQueryKeys.all, "list"]
1855
1699
  };
1856
- function useScore({
1857
- isAssignment,
1858
- activityId,
1859
- userId = "",
1860
- courseId,
1861
- enabled = true,
1862
- googleClassroomUserId
1863
- }) {
1864
- return useQuery3({
1865
- queryFn: () => getScore({
1866
- userId,
1867
- courseId,
1868
- activityId,
1869
- googleClassroomUserId,
1870
- isAssignment
1871
- }),
1872
- queryKey: scoreQueryKeys.byId(activityId),
1873
- enabled
1874
- });
1700
+ var processRecord = (data) => {
1701
+ const { pronunciations = 0, fails = 0 } = data;
1702
+ const attempts = pronunciations + fails;
1703
+ const successRate = attempts > 0 ? pronunciations / attempts * 100 : 0;
1704
+ let newStatus = null;
1705
+ if (attempts < 6) {
1706
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1707
+ }
1708
+ if (successRate > 25) {
1709
+ newStatus = "VERIFIED" /* VERIFIED */;
1710
+ } else if (successRate > 10) {
1711
+ newStatus = "WARNING" /* WARNING */;
1712
+ } else if (fails > 20 && successRate < 10 && pronunciations > 1) {
1713
+ newStatus = "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1714
+ } else if (pronunciations === 0 && fails > 20) {
1715
+ newStatus = "NOT_WORKING" /* NOT_WORKING */;
1716
+ } else {
1717
+ newStatus = "NOT_CHECKED" /* NOT_CHECKED */;
1718
+ }
1719
+ return newStatus;
1720
+ };
1721
+
1722
+ // src/domains/cards/services/create-card.service.ts
1723
+ async function _createCard({ data }) {
1724
+ const response = await api.addDoc(refsCardsFiresotre.allCards, data);
1725
+ return response;
1875
1726
  }
1876
- var debounceUpdateScore = debounce(updateScore, 1e3);
1877
- function useUpdateScore() {
1878
- const { queryClient } = useSpeakableApi();
1879
- const mutation = useMutation2({
1880
- mutationFn: debounceUpdateScore,
1881
- onMutate: (variables) => {
1882
- return handleOptimisticUpdate({
1883
- queryClient,
1884
- queryKey: scoreQueryKeys.byId(variables.activityId),
1885
- newData: variables.data
1886
- });
1887
- },
1888
- onError: (_, variables, context) => {
1889
- if (context == null ? void 0 : context.previousData)
1890
- queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1891
- },
1892
- onSettled: (_, err, variables) => {
1893
- queryClient.invalidateQueries({
1894
- queryKey: scoreQueryKeys.byId(variables.activityId)
1895
- });
1727
+ var createCard = withErrorHandler(_createCard, "createCard");
1728
+ async function _createCards({ cards }) {
1729
+ const { writeBatch: writeBatch2, doc: doc2 } = api.accessHelpers();
1730
+ const batch = writeBatch2();
1731
+ const cardsWithId = [];
1732
+ for (const card of cards) {
1733
+ const cardId = v4();
1734
+ const ref = doc2(refsCardsFiresotre.card(cardId));
1735
+ const newCardObject = {
1736
+ ...card,
1737
+ id: cardId
1738
+ };
1739
+ if (card.type === "READ_REPEAT" /* READ_REPEAT */ && card.target_text && card.language) {
1740
+ const verificationStatus = await getVerificationStatus(card.target_text, card.language);
1741
+ newCardObject.verificationStatus = verificationStatus || null;
1896
1742
  }
1897
- });
1898
- return {
1899
- mutationUpdateScore: mutation
1900
- };
1743
+ cardsWithId.push(newCardObject);
1744
+ batch.set(ref, newCardObject);
1745
+ }
1746
+ await batch.commit();
1747
+ return cardsWithId;
1901
1748
  }
1902
- function useUpdateCardScore({
1903
- isAssignment,
1904
- activityId,
1905
- userId,
1749
+ var createCards = withErrorHandler(_createCards, "createCards");
1750
+
1751
+ // src/domains/cards/card.hooks.ts
1752
+ var cardsQueryKeys = {
1753
+ all: ["cards"],
1754
+ one: (params) => [...cardsQueryKeys.all, params.cardId]
1755
+ };
1756
+ function useCards({
1906
1757
  cardIds,
1907
- weights
1758
+ enabled = true,
1759
+ asObject
1908
1760
  }) {
1909
- const { queryClient } = useSpeakableApi();
1910
- const queryKey = scoreQueryKeys.byId(activityId);
1911
- const mutation = useMutation2({
1912
- mutationFn: async ({ cardId, cardScore }) => {
1913
- const previousScores = queryClient.getQueryData(queryKey);
1914
- const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1915
- previousScores: previousScores != null ? previousScores : {},
1916
- cardId,
1917
- cardScore,
1918
- cardIds,
1919
- weights
1920
- });
1921
- await updateCardScore({
1922
- userId,
1923
- cardId,
1924
- isAssignment,
1925
- activityId,
1926
- updates: {
1927
- cardScore: updatedCardScore,
1928
- progress,
1929
- score
1930
- }
1931
- });
1932
- return { cardId, scoresUpdated: newScoreUpdated };
1933
- },
1934
- onMutate: ({ cardId, cardScore }) => {
1935
- queryClient.setQueryData(queryKey, (previousScore) => {
1936
- const updates = handleOptimisticScore({
1937
- score: previousScore,
1938
- cardId,
1939
- cardScore,
1940
- cardIds,
1941
- weights
1942
- });
1943
- return {
1944
- ...previousScore,
1945
- ...updates
1946
- };
1947
- });
1948
- },
1949
- // eslint-disable-next-line no-unused-vars
1950
- onError: (error) => {
1951
- console.log(error.message);
1952
- const previousData = queryClient.getQueryData(queryKey);
1953
- if (previousData) {
1954
- queryClient.setQueryData(queryKey, previousData);
1955
- }
1956
- }
1761
+ const queries = useQueries({
1762
+ queries: cardIds.map((cardId) => ({
1763
+ enabled: enabled && cardIds.length > 0,
1764
+ queryKey: cardsQueryKeys.one({
1765
+ cardId
1766
+ }),
1767
+ queryFn: () => getCard({ cardId })
1768
+ }))
1957
1769
  });
1770
+ const cards = queries.map((query2) => query2.data).filter(Boolean);
1771
+ const cardsObject = useMemo(() => {
1772
+ if (!asObject) return null;
1773
+ return cards.reduce((acc, card) => {
1774
+ acc[card.id] = card;
1775
+ return acc;
1776
+ }, {});
1777
+ }, [asObject, cards]);
1958
1778
  return {
1959
- mutationUpdateCardScore: mutation
1779
+ cards,
1780
+ cardsObject,
1781
+ cardsQueries: queries
1960
1782
  };
1961
1783
  }
1962
- var getScoreUpdated = ({
1963
- cardId,
1964
- cardScore,
1965
- previousScores,
1966
- cardIds,
1967
- weights
1968
- }) => {
1969
- var _a, _b;
1970
- const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
1971
- const newCardScore = {
1972
- ...previousCard != null ? previousCard : {},
1973
- ...cardScore
1974
- };
1975
- const newScores = {
1976
- ...previousScores,
1977
- cards: {
1978
- ...(_b = previousScores.cards) != null ? _b : {},
1979
- [cardId]: newCardScore
1784
+ function useCreateCard() {
1785
+ const { queryClient } = useSpeakableApi();
1786
+ const mutationCreateCard = useMutation2({
1787
+ mutationFn: createCard,
1788
+ onSuccess: (cardCreated) => {
1789
+ queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
1980
1790
  }
1791
+ });
1792
+ return {
1793
+ mutationCreateCard
1981
1794
  };
1982
- const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
1795
+ }
1796
+ function useCreateCards() {
1797
+ const mutationCreateCards = useMutation2({
1798
+ mutationFn: createCards
1799
+ });
1983
1800
  return {
1984
- newScoreUpdated: newScores,
1985
- updatedCardScore: cardScore,
1986
- score,
1987
- progress
1801
+ mutationCreateCards
1988
1802
  };
1989
- };
1990
- var handleOptimisticScore = ({
1991
- score,
1803
+ }
1804
+ function getCardFromCache({
1992
1805
  cardId,
1993
- cardScore,
1994
- cardIds,
1995
- weights
1996
- }) => {
1997
- var _a;
1998
- let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
1999
- cards = {
2000
- ...cards,
2001
- [cardId]: {
2002
- ...cards[cardId],
2003
- ...cardScore
2004
- }
1806
+ queryClient
1807
+ }) {
1808
+ return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
1809
+ }
1810
+ function updateCardInCache({
1811
+ cardId,
1812
+ card,
1813
+ queryClient
1814
+ }) {
1815
+ queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
1816
+ }
1817
+ function useGetCard({ cardId }) {
1818
+ const query2 = useQuery3({
1819
+ queryKey: cardsQueryKeys.one({ cardId }),
1820
+ queryFn: () => getCard({ cardId })
1821
+ });
1822
+ return query2;
1823
+ }
1824
+
1825
+ // src/domains/cards/card.repo.ts
1826
+ var createCardRepo = () => {
1827
+ return {
1828
+ createCard,
1829
+ createCards,
1830
+ getCard
2005
1831
  };
2006
- const { score: scoreValue, progress } = calculateScoreAndProgress_default(
2007
- // @ts-ignore
2008
- {
2009
- ...score != null ? score : {},
2010
- cards
2011
- },
2012
- cardIds,
2013
- weights
2014
- );
2015
- return { cards, score: scoreValue, progress };
2016
1832
  };
2017
- function useClearScore() {
2018
- const { queryClient } = useSpeakableApi();
2019
- const mutation = useMutation2({
2020
- mutationFn: clearScore,
2021
- onSettled: (result) => {
2022
- var _a;
2023
- queryClient.invalidateQueries({
2024
- queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
2025
- });
2026
- }
2027
- });
2028
- return {
2029
- mutationClearScore: mutation
2030
- };
2031
- }
2032
- function useSubmitAssignmentScore({
2033
- onAssignmentSubmitted,
2034
- studentName
2035
- }) {
2036
- const { queryClient } = useSpeakableApi();
2037
- const { hasGoogleClassroomGradePassback } = usePermissions_default();
2038
- const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
2039
- const { createNotification: createNotification2 } = useCreateNotification();
2040
- const mutation = useMutation2({
2041
- mutationFn: async ({
2042
- assignment,
2043
- userId,
2044
- cardIds,
2045
- weights,
2046
- scores,
2047
- status
2048
- }) => {
2049
- try {
2050
- const scoreUpdated = await submitAssignmentScore({
2051
- assignment,
2052
- userId,
2053
- cardIds,
2054
- weights,
2055
- status,
2056
- studentName
2057
- });
2058
- if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
2059
- await submitAssignmentToGoogleClassroom({
2060
- assignment,
2061
- scores
2062
- });
2063
- }
2064
- if (assignment.isAssessment) {
2065
- createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
2066
- }
2067
- if (assignment == null ? void 0 : assignment.id) {
2068
- logSubmitAssignment({
2069
- courseId: assignment == null ? void 0 : assignment.courseId
2070
- });
1833
+
1834
+ // src/domains/sets/set.hooks.ts
1835
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
1836
+
1837
+ // src/domains/sets/set.constants.ts
1838
+ var SETS_COLLECTION = "sets";
1839
+ var refsSetsFirestore = {
1840
+ allSets: SETS_COLLECTION,
1841
+ set: (id) => `${SETS_COLLECTION}/${id}`
1842
+ };
1843
+
1844
+ // src/domains/sets/services/get-set.service.ts
1845
+ async function _getSet({ setId }) {
1846
+ const response = await api.getDoc(refsSetsFirestore.set(setId));
1847
+ return response.data;
1848
+ }
1849
+ var getSet = withErrorHandler(_getSet, "getSet");
1850
+
1851
+ // src/domains/sets/set.hooks.ts
1852
+ var setsQueryKeys = {
1853
+ all: ["sets"],
1854
+ one: (params) => [...setsQueryKeys.all, params.setId]
1855
+ };
1856
+ var useSet = ({ setId, enabled }) => {
1857
+ return useQuery4({
1858
+ queryKey: setsQueryKeys.one({ setId }),
1859
+ queryFn: () => getSet({ setId }),
1860
+ enabled: setId !== void 0 && setId !== "" && enabled
1861
+ });
1862
+ };
1863
+ function getSetFromCache({
1864
+ setId,
1865
+ queryClient
1866
+ }) {
1867
+ if (!setId) return null;
1868
+ return queryClient.getQueryData(setsQueryKeys.one({ setId }));
1869
+ }
1870
+ function updateSetInCache({
1871
+ set,
1872
+ queryClient
1873
+ }) {
1874
+ const { id, ...setData } = set;
1875
+ queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
1876
+ }
1877
+
1878
+ // src/domains/sets/set.repo.ts
1879
+ var createSetRepo = () => {
1880
+ return {
1881
+ getSet
1882
+ };
1883
+ };
1884
+
1885
+ // src/constants/all-langs.json
1886
+ var all_langs_default = {
1887
+ af: "Afrikaans",
1888
+ sq: "Albanian",
1889
+ am: "Amharic",
1890
+ ar: "Arabic",
1891
+ hy: "Armenian",
1892
+ az: "Azerbaijani",
1893
+ eu: "Basque",
1894
+ be: "Belarusian",
1895
+ bn: "Bengali",
1896
+ bs: "Bosnian",
1897
+ bg: "Bulgarian",
1898
+ ca: "Catalan",
1899
+ ceb: "Cebuano",
1900
+ zh: "Chinese",
1901
+ co: "Corsican",
1902
+ hr: "Croatian",
1903
+ cs: "Czech",
1904
+ da: "Danish",
1905
+ nl: "Dutch",
1906
+ en: "English",
1907
+ eo: "Esperanto",
1908
+ et: "Estonian",
1909
+ fi: "Finnish",
1910
+ fr: "French",
1911
+ fy: "Frisian",
1912
+ gl: "Galician",
1913
+ ka: "Georgian",
1914
+ de: "German",
1915
+ el: "Greek",
1916
+ gu: "Gujarati",
1917
+ ht: "Haitian Creole",
1918
+ ha: "Hausa",
1919
+ haw: "Hawaiian",
1920
+ he: "Hebrew",
1921
+ hi: "Hindi",
1922
+ hmn: "Hmong",
1923
+ hu: "Hungarian",
1924
+ is: "Icelandic",
1925
+ ig: "Igbo",
1926
+ id: "Indonesian",
1927
+ ga: "Irish",
1928
+ it: "Italian",
1929
+ ja: "Japanese",
1930
+ jv: "Javanese",
1931
+ kn: "Kannada",
1932
+ kk: "Kazakh",
1933
+ km: "Khmer",
1934
+ ko: "Korean",
1935
+ ku: "Kurdish",
1936
+ ky: "Kyrgyz",
1937
+ lo: "Lao",
1938
+ la: "Latin",
1939
+ lv: "Latvian",
1940
+ lt: "Lithuanian",
1941
+ lb: "Luxembourgish",
1942
+ mk: "Macedonian",
1943
+ mg: "Malagasy",
1944
+ ms: "Malay",
1945
+ ml: "Malayalam",
1946
+ mt: "Maltese",
1947
+ mi: "Maori",
1948
+ mr: "Marathi",
1949
+ mn: "Mongolian",
1950
+ my: "Myanmar (Burmese)",
1951
+ ne: "Nepali",
1952
+ no: "Norwegian",
1953
+ ny: "Nyanja (Chichewa)",
1954
+ ps: "Pashto",
1955
+ fa: "Persian",
1956
+ pl: "Polish",
1957
+ pt: "Portuguese",
1958
+ pa: "Punjabi",
1959
+ ro: "Romanian",
1960
+ ru: "Russian",
1961
+ sm: "Samoan",
1962
+ gd: "Scots Gaelic",
1963
+ sr: "Serbian",
1964
+ st: "Sesotho",
1965
+ sn: "Shona",
1966
+ sd: "Sindhi",
1967
+ si: "Sinhala (Sinhalese)",
1968
+ sk: "Slovak",
1969
+ sl: "Slovenian",
1970
+ so: "Somali",
1971
+ es: "Spanish",
1972
+ su: "Sundanese",
1973
+ sw: "Swahili",
1974
+ sv: "Swedish",
1975
+ tl: "Tagalog (Filipino)",
1976
+ tg: "Tajik",
1977
+ ta: "Tamil",
1978
+ te: "Telugu",
1979
+ th: "Thai",
1980
+ tr: "Turkish",
1981
+ uk: "Ukrainian",
1982
+ ur: "Urdu",
1983
+ uz: "Uzbek",
1984
+ vi: "Vietnamese",
1985
+ cy: "Welsh",
1986
+ xh: "Xhosa",
1987
+ yi: "Yiddish",
1988
+ yo: "Yoruba",
1989
+ zu: "Zulu"
1990
+ };
1991
+
1992
+ // src/utils/ai/get-respond-card-tool.ts
1993
+ var getRespondCardTool = ({
1994
+ language,
1995
+ standard = "actfl"
1996
+ }) => {
1997
+ const lang = all_langs_default[language] || "English";
1998
+ const tool = {
1999
+ tool_choice: {
2000
+ type: "function",
2001
+ function: { name: "get_feedback" }
2002
+ },
2003
+ tools: [
2004
+ {
2005
+ type: "function",
2006
+ function: {
2007
+ name: "get_feedback",
2008
+ description: "Get feedback on a student's response",
2009
+ parameters: {
2010
+ type: "object",
2011
+ required: [
2012
+ "success",
2013
+ "score",
2014
+ "score_justification",
2015
+ "errors",
2016
+ "improvedResponse",
2017
+ "compliments"
2018
+ ],
2019
+ properties: {
2020
+ success: {
2021
+ type: "boolean",
2022
+ 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."
2023
+ },
2024
+ errors: {
2025
+ type: "array",
2026
+ items: {
2027
+ type: "object",
2028
+ required: ["error", "grammar_error_type", "correction", "justification"],
2029
+ properties: {
2030
+ error: {
2031
+ type: "string",
2032
+ description: "The grammatical error in the student's response."
2033
+ },
2034
+ correction: {
2035
+ type: "string",
2036
+ description: "The suggested correction to the error"
2037
+ },
2038
+ justification: {
2039
+ type: "string",
2040
+ description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
2041
+ },
2042
+ grammar_error_type: {
2043
+ type: "string",
2044
+ enum: [
2045
+ "subjVerbAgree",
2046
+ "tenseErrors",
2047
+ "articleMisuse",
2048
+ "prepositionErrors",
2049
+ "adjNounAgree",
2050
+ "pronounErrors",
2051
+ "wordOrder",
2052
+ "verbConjugation",
2053
+ "pluralization",
2054
+ "negationErrors",
2055
+ "modalVerbMisuse",
2056
+ "relativeClause",
2057
+ "auxiliaryVerb",
2058
+ "complexSentenceAgreement",
2059
+ "idiomaticExpression",
2060
+ "registerInconsistency",
2061
+ "voiceMisuse"
2062
+ ],
2063
+ 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"
2064
+ }
2065
+ }
2066
+ },
2067
+ 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."
2068
+ },
2069
+ compliments: {
2070
+ type: "array",
2071
+ items: {
2072
+ type: "string"
2073
+ },
2074
+ description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
2075
+ },
2076
+ improvedResponse: {
2077
+ type: "string",
2078
+ description: "An improved response with proper grammar and more detail, if applicable."
2079
+ },
2080
+ score: {
2081
+ type: "number",
2082
+ description: "A score between 0 and 100, reflecting the overall quality of the response"
2083
+ },
2084
+ score_justification: {
2085
+ type: "string",
2086
+ description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
2087
+ }
2088
+ }
2089
+ }
2071
2090
  }
2072
- onAssignmentSubmitted(assignment.id);
2073
- queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
2074
- ...scores,
2075
- ...scoreUpdated.fieldsUpdated
2076
- });
2077
- return {
2078
- success: true,
2079
- message: "Score submitted successfully"
2080
- };
2081
- } catch (error) {
2082
- return {
2083
- success: false,
2084
- error
2085
- };
2086
- }
2087
- }
2088
- });
2089
- return {
2090
- submitAssignmentScore: mutation.mutateAsync,
2091
- isLoading: mutation.isPending
2092
- };
2093
- }
2094
- function useSubmitPracticeScore() {
2095
- const { queryClient } = useSpeakableApi();
2096
- const mutation = useMutation2({
2097
- mutationFn: async ({
2098
- setId,
2099
- userId,
2100
- scores
2101
- }) => {
2102
- try {
2103
- await submitPracticeScore({
2104
- setId,
2105
- userId,
2106
- scores
2107
- });
2108
- queryClient.invalidateQueries({
2109
- queryKey: scoreQueryKeys.byId(setId)
2110
- });
2111
- return {
2112
- success: true,
2113
- message: "Score submitted successfully"
2114
- };
2115
- } catch (error) {
2116
- return {
2117
- success: false,
2118
- error
2119
- };
2120
2091
  }
2121
- }
2122
- });
2123
- return {
2124
- submitPracticeScore: mutation.mutateAsync,
2125
- isLoading: mutation.isPending
2092
+ ]
2126
2093
  };
2127
- }
2094
+ if (standard === "wida") {
2095
+ const wida_level = {
2096
+ type: "number",
2097
+ enum: [1, 2, 3, 4, 5, 6],
2098
+ 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
2099
+
2100
+ 1 - Entering
2101
+ 2 - Emerging
2102
+ 3 - Developing
2103
+ 4 - Expanding
2104
+ 5 - Bridging
2105
+ 6 - Reaching
2106
+
2107
+ 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.
2108
+ `
2109
+ };
2110
+ const wida_justification = {
2111
+ type: "string",
2112
+ description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
2113
+ };
2114
+ tool.tools[0].function.parameters.required.push("wida_level");
2115
+ tool.tools[0].function.parameters.required.push("wida_justification");
2116
+ tool.tools[0].function.parameters.properties.wida_level = wida_level;
2117
+ tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
2118
+ } else {
2119
+ const actfl_level = {
2120
+ type: "string",
2121
+ enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
2122
+ 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"
2123
+ };
2124
+ const actfl_justification = {
2125
+ type: "string",
2126
+ description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
2127
+ };
2128
+ tool.tools[0].function.parameters.required.push("actfl_level");
2129
+ tool.tools[0].function.parameters.required.push("actfl_justification");
2130
+ tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
2131
+ tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
2132
+ }
2133
+ return tool;
2134
+ };
2128
2135
 
2129
2136
  // src/hooks/useActivity.ts
2130
2137
  import { useEffect as useEffect2 } from "react";
@@ -2444,7 +2451,7 @@ var submitLTIScore = async ({
2444
2451
  };
2445
2452
 
2446
2453
  // src/hooks/useCredits.ts
2447
- import { useQuery as useQuery4 } from "@tanstack/react-query";
2454
+ import { useQuery as useQuery5 } from "@tanstack/react-query";
2448
2455
  var creditQueryKeys = {
2449
2456
  userCredits: (uid) => ["userCredits", uid]
2450
2457
  };
@@ -2452,7 +2459,7 @@ var useUserCredits = () => {
2452
2459
  const { user } = useSpeakableApi();
2453
2460
  const email = user.auth.email;
2454
2461
  const uid = user.auth.uid;
2455
- const query2 = useQuery4({
2462
+ const query2 = useQuery5({
2456
2463
  queryKey: creditQueryKeys.userCredits(uid),
2457
2464
  queryFn: () => fetchUserCredits({ uid, email }),
2458
2465
  enabled: !!uid,
@@ -2502,11 +2509,11 @@ var fetchUserCredits = async ({ uid, email }) => {
2502
2509
  };
2503
2510
 
2504
2511
  // src/hooks/useOrganizationAccess.ts
2505
- import { useQuery as useQuery5 } from "@tanstack/react-query";
2512
+ import { useQuery as useQuery6 } from "@tanstack/react-query";
2506
2513
  var useOrganizationAccess = () => {
2507
2514
  const { user } = useSpeakableApi();
2508
2515
  const email = user.auth.email;
2509
- const query2 = useQuery5({
2516
+ const query2 = useQuery6({
2510
2517
  queryKey: ["organizationAccess", email],
2511
2518
  queryFn: async () => {
2512
2519
  if (!email) {
@@ -2594,7 +2601,7 @@ var getOrganizationAccess = async (email) => {
2594
2601
  };
2595
2602
 
2596
2603
  // src/hooks/useActivityFeedbackAccess.ts
2597
- import { useQuery as useQuery6 } from "@tanstack/react-query";
2604
+ import { useQuery as useQuery7 } from "@tanstack/react-query";
2598
2605
  var activityFeedbackAccessQueryKeys = {
2599
2606
  activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
2600
2607
  };
@@ -2608,7 +2615,7 @@ var useActivityFeedbackAccess = ({
2608
2615
  const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
2609
2616
  const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
2610
2617
  const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
2611
- const query2 = useQuery6({
2618
+ const query2 = useQuery7({
2612
2619
  queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
2613
2620
  aiEnabled,
2614
2621
  isActivityRoute
@@ -3020,6 +3027,7 @@ export {
3020
3027
  purify,
3021
3028
  refsCardsFiresotre,
3022
3029
  refsSetsFirestore,
3030
+ scoreQueryKeys,
3023
3031
  setsQueryKeys,
3024
3032
  updateCardInCache,
3025
3033
  updateSetInCache,
@@ -3028,12 +3036,19 @@ export {
3028
3036
  useAssignment,
3029
3037
  useBaseOpenAI,
3030
3038
  useCards,
3039
+ useClearScore,
3031
3040
  useCreateCard,
3032
3041
  useCreateCards,
3033
3042
  useCreateNotification,
3043
+ useGetCard,
3034
3044
  useOrganizationAccess,
3045
+ useScore,
3035
3046
  useSet,
3036
3047
  useSpeakableApi,
3048
+ useSubmitAssignmentScore,
3049
+ useSubmitPracticeScore,
3050
+ useUpdateCardScore,
3051
+ useUpdateScore,
3037
3052
  useUserCredits
3038
3053
  };
3039
3054
  //# sourceMappingURL=index.native.mjs.map