@speakableio/core 0.1.32 → 0.1.34

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.
@@ -95,7 +95,7 @@ var FirebaseAPI = class _FirebaseAPI {
95
95
  (_a = this.config) == null ? void 0 : _a.logEvent(name, data);
96
96
  }
97
97
  accessQueryConstraints() {
98
- const { query: query2, orderBy: orderBy2, limit: limit2, startAt: startAt2, startAfter: startAfter2, endAt: endAt2, endBefore: endBefore2 } = this.helpers;
98
+ const { query: query2, orderBy: orderBy2, limit: limit2, startAt: startAt2, startAfter: startAfter2, endAt: endAt2, endBefore: endBefore2, where: where2 } = this.helpers;
99
99
  return {
100
100
  query: query2,
101
101
  orderBy: orderBy2,
@@ -103,7 +103,8 @@ var FirebaseAPI = class _FirebaseAPI {
103
103
  startAt: startAt2,
104
104
  startAfter: startAfter2,
105
105
  endAt: endAt2,
106
- endBefore: endBefore2
106
+ endBefore: endBefore2,
107
+ where: where2
107
108
  };
108
109
  }
109
110
  accessHelpers() {
@@ -135,12 +136,13 @@ var FirebaseAPI = class _FirebaseAPI {
135
136
  const q = queryConstraints.length > 0 ? query2(collectionRef, ...queryConstraints) : collectionRef;
136
137
  const querySnapshot = await getDocs2(q);
137
138
  const data = querySnapshot.docs.map((doc2) => ({
138
- id: doc2.id,
139
- data: doc2.data()
139
+ data: doc2.data(),
140
+ id: doc2.id
140
141
  }));
141
142
  return {
142
143
  data,
143
- querySnapshot
144
+ querySnapshot,
145
+ empty: querySnapshot.empty
144
146
  };
145
147
  }
146
148
  async addDoc(path, data) {
@@ -789,7 +791,12 @@ var SpeakableFirebaseFunctions = {
789
791
  submitAssessment: api.httpsCallable("submitAssessment"),
790
792
  sendAssessmentScoredEmail: api.httpsCallable("sendAssessmentScoredEmail"),
791
793
  createNotification: api.httpsCallable("createNotificationV2"),
792
- updateCourseAnalytics: api.httpsCallable("handleCouresAnalyticsEvent")
794
+ updateCourseAnalytics: api.httpsCallable("handleCouresAnalyticsEvent"),
795
+ checkStudentTeacherPlan: api.httpsCallable("checkStudentTeacherPlan"),
796
+ getGeminiFeedback: api.httpsCallable("callGetFeedback"),
797
+ getProficiencyEstimate: api.httpsCallable("getProficiencyEstimate"),
798
+ getAssemblyAITranscript: api.httpsCallable("transcribeAssemblyAIAudio"),
799
+ createChatCompletion: api.httpsCallable("createChatCompletion")
793
800
  };
794
801
 
795
802
  // src/domains/notification/services/send-notification.service.ts
@@ -951,8 +958,256 @@ var useCreateNotification = () => {
951
958
  };
952
959
  };
953
960
 
954
- // src/domains/assignment/hooks/score-hooks.ts
955
- import { useMutation as useMutation2, useQuery as useQuery3 } from "@tanstack/react-query";
961
+ // src/constants/all-langs.json
962
+ var all_langs_default = {
963
+ af: "Afrikaans",
964
+ sq: "Albanian",
965
+ am: "Amharic",
966
+ ar: "Arabic",
967
+ hy: "Armenian",
968
+ az: "Azerbaijani",
969
+ eu: "Basque",
970
+ be: "Belarusian",
971
+ bn: "Bengali",
972
+ bs: "Bosnian",
973
+ bg: "Bulgarian",
974
+ ca: "Catalan",
975
+ ceb: "Cebuano",
976
+ zh: "Chinese",
977
+ co: "Corsican",
978
+ hr: "Croatian",
979
+ cs: "Czech",
980
+ da: "Danish",
981
+ nl: "Dutch",
982
+ en: "English",
983
+ eo: "Esperanto",
984
+ et: "Estonian",
985
+ fi: "Finnish",
986
+ fr: "French",
987
+ fy: "Frisian",
988
+ gl: "Galician",
989
+ ka: "Georgian",
990
+ de: "German",
991
+ el: "Greek",
992
+ gu: "Gujarati",
993
+ ht: "Haitian Creole",
994
+ ha: "Hausa",
995
+ haw: "Hawaiian",
996
+ he: "Hebrew",
997
+ hi: "Hindi",
998
+ hmn: "Hmong",
999
+ hu: "Hungarian",
1000
+ is: "Icelandic",
1001
+ ig: "Igbo",
1002
+ id: "Indonesian",
1003
+ ga: "Irish",
1004
+ it: "Italian",
1005
+ ja: "Japanese",
1006
+ jv: "Javanese",
1007
+ kn: "Kannada",
1008
+ kk: "Kazakh",
1009
+ km: "Khmer",
1010
+ ko: "Korean",
1011
+ ku: "Kurdish",
1012
+ ky: "Kyrgyz",
1013
+ lo: "Lao",
1014
+ la: "Latin",
1015
+ lv: "Latvian",
1016
+ lt: "Lithuanian",
1017
+ lb: "Luxembourgish",
1018
+ mk: "Macedonian",
1019
+ mg: "Malagasy",
1020
+ ms: "Malay",
1021
+ ml: "Malayalam",
1022
+ mt: "Maltese",
1023
+ mi: "Maori",
1024
+ mr: "Marathi",
1025
+ mn: "Mongolian",
1026
+ my: "Myanmar (Burmese)",
1027
+ ne: "Nepali",
1028
+ no: "Norwegian",
1029
+ ny: "Nyanja (Chichewa)",
1030
+ ps: "Pashto",
1031
+ fa: "Persian",
1032
+ pl: "Polish",
1033
+ pt: "Portuguese",
1034
+ pa: "Punjabi",
1035
+ ro: "Romanian",
1036
+ ru: "Russian",
1037
+ sm: "Samoan",
1038
+ gd: "Scots Gaelic",
1039
+ sr: "Serbian",
1040
+ st: "Sesotho",
1041
+ sn: "Shona",
1042
+ sd: "Sindhi",
1043
+ si: "Sinhala (Sinhalese)",
1044
+ sk: "Slovak",
1045
+ sl: "Slovenian",
1046
+ so: "Somali",
1047
+ es: "Spanish",
1048
+ su: "Sundanese",
1049
+ sw: "Swahili",
1050
+ sv: "Swedish",
1051
+ tl: "Tagalog (Filipino)",
1052
+ tg: "Tajik",
1053
+ ta: "Tamil",
1054
+ te: "Telugu",
1055
+ th: "Thai",
1056
+ tr: "Turkish",
1057
+ uk: "Ukrainian",
1058
+ ur: "Urdu",
1059
+ uz: "Uzbek",
1060
+ vi: "Vietnamese",
1061
+ cy: "Welsh",
1062
+ xh: "Xhosa",
1063
+ yi: "Yiddish",
1064
+ yo: "Yoruba",
1065
+ zu: "Zulu"
1066
+ };
1067
+
1068
+ // src/utils/ai/get-respond-card-tool.ts
1069
+ var getRespondCardTool = ({
1070
+ language,
1071
+ standard = "actfl"
1072
+ }) => {
1073
+ const lang = all_langs_default[language] || "English";
1074
+ const tool = {
1075
+ tool_choice: {
1076
+ type: "function",
1077
+ function: { name: "get_feedback" }
1078
+ },
1079
+ tools: [
1080
+ {
1081
+ type: "function",
1082
+ function: {
1083
+ name: "get_feedback",
1084
+ description: "Get feedback on a student's response",
1085
+ parameters: {
1086
+ type: "object",
1087
+ required: [
1088
+ "success",
1089
+ "score",
1090
+ "score_justification",
1091
+ "errors",
1092
+ "improvedResponse",
1093
+ "compliments"
1094
+ ],
1095
+ properties: {
1096
+ success: {
1097
+ type: "boolean",
1098
+ 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."
1099
+ },
1100
+ errors: {
1101
+ type: "array",
1102
+ items: {
1103
+ type: "object",
1104
+ required: ["error", "grammar_error_type", "correction", "justification"],
1105
+ properties: {
1106
+ error: {
1107
+ type: "string",
1108
+ description: "The grammatical error in the student's response."
1109
+ },
1110
+ correction: {
1111
+ type: "string",
1112
+ description: "The suggested correction to the error"
1113
+ },
1114
+ justification: {
1115
+ type: "string",
1116
+ description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
1117
+ },
1118
+ grammar_error_type: {
1119
+ type: "string",
1120
+ enum: [
1121
+ "subjVerbAgree",
1122
+ "tenseErrors",
1123
+ "articleMisuse",
1124
+ "prepositionErrors",
1125
+ "adjNounAgree",
1126
+ "pronounErrors",
1127
+ "wordOrder",
1128
+ "verbConjugation",
1129
+ "pluralization",
1130
+ "negationErrors",
1131
+ "modalVerbMisuse",
1132
+ "relativeClause",
1133
+ "auxiliaryVerb",
1134
+ "complexSentenceAgreement",
1135
+ "idiomaticExpression",
1136
+ "registerInconsistency",
1137
+ "voiceMisuse"
1138
+ ],
1139
+ 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"
1140
+ }
1141
+ }
1142
+ },
1143
+ 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."
1144
+ },
1145
+ compliments: {
1146
+ type: "array",
1147
+ items: {
1148
+ type: "string"
1149
+ },
1150
+ description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
1151
+ },
1152
+ improvedResponse: {
1153
+ type: "string",
1154
+ description: "An improved response with proper grammar and more detail, if applicable."
1155
+ },
1156
+ score: {
1157
+ type: "number",
1158
+ description: "A score between 0 and 100, reflecting the overall quality of the response"
1159
+ },
1160
+ score_justification: {
1161
+ type: "string",
1162
+ description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
1163
+ }
1164
+ }
1165
+ }
1166
+ }
1167
+ }
1168
+ ]
1169
+ };
1170
+ if (standard === "wida") {
1171
+ const wida_level = {
1172
+ type: "number",
1173
+ enum: [1, 2, 3, 4, 5, 6],
1174
+ 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
1175
+
1176
+ 1 - Entering
1177
+ 2 - Emerging
1178
+ 3 - Developing
1179
+ 4 - Expanding
1180
+ 5 - Bridging
1181
+ 6 - Reaching
1182
+
1183
+ 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.
1184
+ `
1185
+ };
1186
+ const wida_justification = {
1187
+ type: "string",
1188
+ description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
1189
+ };
1190
+ tool.tools[0].function.parameters.required.push("wida_level");
1191
+ tool.tools[0].function.parameters.required.push("wida_justification");
1192
+ tool.tools[0].function.parameters.properties.wida_level = wida_level;
1193
+ tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
1194
+ } else {
1195
+ const actfl_level = {
1196
+ type: "string",
1197
+ enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
1198
+ 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"
1199
+ };
1200
+ const actfl_justification = {
1201
+ type: "string",
1202
+ description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
1203
+ };
1204
+ tool.tools[0].function.parameters.required.push("actfl_level");
1205
+ tool.tools[0].function.parameters.required.push("actfl_justification");
1206
+ tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
1207
+ tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
1208
+ }
1209
+ return tool;
1210
+ };
956
1211
 
957
1212
  // src/utils/debounce.utils.ts
958
1213
  function debounce(func, waitFor) {
@@ -972,6 +1227,9 @@ function debounce(func, waitFor) {
972
1227
  });
973
1228
  }
974
1229
 
1230
+ // src/domains/assignment/hooks/score-hooks.ts
1231
+ import { useMutation as useMutation2, useQuery as useQuery3 } from "@tanstack/react-query";
1232
+
975
1233
  // src/lib/tanstack/handle-optimistic-update-query.ts
976
1234
  var handleOptimisticUpdate = async ({
977
1235
  queryClient,
@@ -2190,6 +2448,465 @@ var submitLTIScore = async ({
2190
2448
  }
2191
2449
  };
2192
2450
 
2451
+ // src/hooks/useCredits.ts
2452
+ import { useQuery as useQuery4 } from "@tanstack/react-query";
2453
+ var creditQueryKeys = {
2454
+ userCredits: (uid) => ["userCredits", uid]
2455
+ };
2456
+ var useUserCredits = () => {
2457
+ const { user } = useSpeakableApi();
2458
+ const email = user.auth.email;
2459
+ const uid = user.auth.uid;
2460
+ const query2 = useQuery4({
2461
+ queryKey: creditQueryKeys.userCredits(uid),
2462
+ queryFn: () => fetchUserCredits({ uid, email }),
2463
+ enabled: !!uid,
2464
+ refetchInterval: 1e3 * 60 * 5
2465
+ });
2466
+ return {
2467
+ ...query2
2468
+ };
2469
+ };
2470
+ var fetchUserCredits = async ({ uid, email }) => {
2471
+ if (!uid) {
2472
+ throw new Error("User ID is required");
2473
+ }
2474
+ const contractSnap = await api.getDoc(`creditContracts/${uid}`);
2475
+ if (contractSnap.data == null) {
2476
+ return {
2477
+ id: uid,
2478
+ userId: uid,
2479
+ email,
2480
+ effectivePlanId: "free_tier",
2481
+ status: "inactive",
2482
+ isUnlimited: false,
2483
+ creditsAvailable: 100,
2484
+ creditsAllocatedThisPeriod: 100,
2485
+ topOffCreditsAvailable: 0,
2486
+ topOffCreditsTotal: 0,
2487
+ allocationSource: "free_tier",
2488
+ sourceDetails: {},
2489
+ periodStart: null,
2490
+ periodEnd: null,
2491
+ planTermEndTimestamp: null,
2492
+ ownerType: "individual",
2493
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2494
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
2495
+ };
2496
+ }
2497
+ const contractData = contractSnap.data;
2498
+ const monthlyCredits = (contractData == null ? void 0 : contractData.creditsAvailable) || 0;
2499
+ const topOffCredits = (contractData == null ? void 0 : contractData.topOffCreditsAvailable) || 0;
2500
+ const totalCredits = monthlyCredits + topOffCredits;
2501
+ return {
2502
+ id: contractSnap.id,
2503
+ ...contractData,
2504
+ // Add computed total for convenience
2505
+ totalCreditsAvailable: totalCredits
2506
+ };
2507
+ };
2508
+
2509
+ // src/hooks/useOrganizationAccess.ts
2510
+ import { useQuery as useQuery5 } from "@tanstack/react-query";
2511
+ var useOrganizationAccess = () => {
2512
+ const { user } = useSpeakableApi();
2513
+ const email = user.auth.email;
2514
+ const query2 = useQuery5({
2515
+ queryKey: ["organizationAccess", email],
2516
+ queryFn: async () => {
2517
+ if (!email) {
2518
+ return {
2519
+ hasUnlimitedAccess: false,
2520
+ subscriptionId: null,
2521
+ organizationId: null,
2522
+ organizationName: null,
2523
+ subscriptionEndDate: null,
2524
+ accessType: "individual"
2525
+ };
2526
+ }
2527
+ return getOrganizationAccess(email);
2528
+ },
2529
+ enabled: !!email,
2530
+ // Only run query if we have a user email
2531
+ staleTime: 5 * 60 * 1e3,
2532
+ // Consider data fresh for 5 minutes
2533
+ gcTime: 10 * 60 * 1e3,
2534
+ // Keep in cache for 10 minutes
2535
+ retry: 2
2536
+ // Retry failed requests twice
2537
+ });
2538
+ return {
2539
+ ...query2
2540
+ };
2541
+ };
2542
+ var getOrganizationAccess = async (email) => {
2543
+ const { limit: limit2, where: where2 } = api.accessQueryConstraints();
2544
+ try {
2545
+ const organizationSnapshot = await api.getDocs(
2546
+ "organizations",
2547
+ where2("members", "array-contains", email),
2548
+ where2("masterSubscriptionStatus", "==", "active"),
2549
+ limit2(1)
2550
+ );
2551
+ if (!organizationSnapshot.empty) {
2552
+ const orgData = organizationSnapshot.data[0];
2553
+ return {
2554
+ hasUnlimitedAccess: true,
2555
+ subscriptionId: orgData == null ? void 0 : orgData.masterSubscriptionId,
2556
+ organizationId: orgData.id,
2557
+ organizationName: orgData.name || "Unknown Organization",
2558
+ subscriptionEndDate: orgData.masterSubscriptionEndDate || null,
2559
+ accessType: "organization"
2560
+ };
2561
+ }
2562
+ const institutionSnapshot = await api.getDocs(
2563
+ "institution_subscriptions",
2564
+ where2("users", "array-contains", email),
2565
+ where2("active", "==", true),
2566
+ limit2(1)
2567
+ );
2568
+ if (!institutionSnapshot.empty) {
2569
+ const institutionData = institutionSnapshot.data[0];
2570
+ const isUnlimited = (institutionData == null ? void 0 : institutionData.plan) === "organization" || (institutionData == null ? void 0 : institutionData.plan) === "school_starter";
2571
+ return {
2572
+ hasUnlimitedAccess: isUnlimited,
2573
+ subscriptionId: institutionData.id,
2574
+ organizationId: institutionData == null ? void 0 : institutionData.institutionId,
2575
+ organizationName: institutionData.name || institutionData.institutionId || "Legacy Institution",
2576
+ subscriptionEndDate: institutionData.endDate || null,
2577
+ accessType: "institution_subscriptions"
2578
+ };
2579
+ }
2580
+ return {
2581
+ hasUnlimitedAccess: false,
2582
+ subscriptionId: null,
2583
+ organizationId: null,
2584
+ organizationName: null,
2585
+ subscriptionEndDate: null,
2586
+ accessType: "individual"
2587
+ };
2588
+ } catch (error) {
2589
+ console.error("Error checking organization access:", error);
2590
+ return {
2591
+ hasUnlimitedAccess: false,
2592
+ subscriptionId: null,
2593
+ organizationId: null,
2594
+ organizationName: null,
2595
+ subscriptionEndDate: null,
2596
+ accessType: "individual"
2597
+ };
2598
+ }
2599
+ };
2600
+
2601
+ // src/hooks/useActivityFeedbackAccess.ts
2602
+ import { useQuery as useQuery6 } from "@tanstack/react-query";
2603
+ var activityFeedbackAccessQueryKeys = {
2604
+ activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
2605
+ };
2606
+ var useActivityFeedbackAccess = ({
2607
+ aiEnabled = false,
2608
+ isActivityRoute = false
2609
+ }) => {
2610
+ var _a, _b, _c;
2611
+ const { user } = useSpeakableApi();
2612
+ const uid = user.auth.uid;
2613
+ const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
2614
+ const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
2615
+ const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
2616
+ const query2 = useQuery6({
2617
+ queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
2618
+ aiEnabled,
2619
+ isActivityRoute
2620
+ }),
2621
+ queryFn: async () => {
2622
+ var _a2, _b2;
2623
+ if (!uid) {
2624
+ return {
2625
+ canAccessFeedback: false,
2626
+ reason: "Missing user ID",
2627
+ isUnlimited: false,
2628
+ accessType: "none"
2629
+ };
2630
+ }
2631
+ try {
2632
+ if (aiEnabled) {
2633
+ return {
2634
+ canAccessFeedback: true,
2635
+ reason: "AI feedback enabled",
2636
+ isUnlimited: true,
2637
+ accessType: "ai_enabled"
2638
+ };
2639
+ }
2640
+ if (isTeacher || userRoles.includes("ADMIN")) {
2641
+ return {
2642
+ canAccessFeedback: true,
2643
+ reason: "Teacher preview access",
2644
+ isUnlimited: true,
2645
+ accessType: "teacher_preview"
2646
+ };
2647
+ }
2648
+ if (isStudent && isActivityRoute) {
2649
+ try {
2650
+ const result = await ((_b2 = (_a2 = SpeakableFirebaseFunctions) == null ? void 0 : _a2.checkStudentTeacherPlan) == null ? void 0 : _b2.call(_a2, {
2651
+ studentId: uid
2652
+ }));
2653
+ const planCheckResult = result.data;
2654
+ if (planCheckResult.canAccessFeedback) {
2655
+ return {
2656
+ canAccessFeedback: true,
2657
+ reason: planCheckResult.reason || "Student access via teacher with active plan",
2658
+ isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
2659
+ accessType: "student_with_teacher_plan"
2660
+ };
2661
+ } else {
2662
+ return {
2663
+ canAccessFeedback: false,
2664
+ reason: planCheckResult.reason || "No teacher with active plan found",
2665
+ isUnlimited: false,
2666
+ accessType: "none"
2667
+ };
2668
+ }
2669
+ } catch (error) {
2670
+ console.error("Error checking student teacher plan:", error);
2671
+ return {
2672
+ canAccessFeedback: false,
2673
+ reason: "Error checking teacher plans",
2674
+ isUnlimited: false,
2675
+ accessType: "none"
2676
+ };
2677
+ }
2678
+ }
2679
+ return {
2680
+ canAccessFeedback: false,
2681
+ reason: "No access permissions found for current context",
2682
+ isUnlimited: false,
2683
+ accessType: "none"
2684
+ };
2685
+ } catch (error) {
2686
+ console.error("Error checking activity feedback access:", error);
2687
+ return {
2688
+ canAccessFeedback: false,
2689
+ reason: "Error checking access permissions",
2690
+ isUnlimited: false,
2691
+ accessType: "none"
2692
+ };
2693
+ }
2694
+ },
2695
+ enabled: !!uid,
2696
+ staleTime: 5 * 60 * 1e3,
2697
+ // 5 minutes
2698
+ gcTime: 10 * 60 * 1e3
2699
+ // 10 minutes
2700
+ });
2701
+ return {
2702
+ ...query2
2703
+ };
2704
+ };
2705
+
2706
+ // src/hooks/useOpenAI.ts
2707
+ var getGeminiFeedback = SpeakableFirebaseFunctions.getGeminiFeedback;
2708
+ var getProficiencyEstimate = SpeakableFirebaseFunctions.getProficiencyEstimate;
2709
+ var getAssemblyAITranscript = SpeakableFirebaseFunctions.getAssemblyAITranscript;
2710
+ var useBaseOpenAI = ({
2711
+ onTranscriptSuccess,
2712
+ onTranscriptError,
2713
+ onCompletionSuccess,
2714
+ onCompletionError,
2715
+ aiEnabled,
2716
+ submitAudioResponse,
2717
+ uploadAudioAndGetTranscript
2718
+ }) => {
2719
+ const { user, queryClient } = useSpeakableApi();
2720
+ const currentUserId = user.auth.uid;
2721
+ const { data: feedbackAccess } = useActivityFeedbackAccess({
2722
+ aiEnabled
2723
+ });
2724
+ const getTransctipt = async (audioUrl, language) => {
2725
+ try {
2726
+ const { data: transcript } = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
2727
+ audioUrl,
2728
+ language
2729
+ }));
2730
+ onTranscriptSuccess(transcript);
2731
+ return transcript;
2732
+ } catch (error) {
2733
+ console.log("error", error);
2734
+ onTranscriptError({
2735
+ type: "TRANSCRIPT",
2736
+ message: (error == null ? void 0 : error.message) || "Error getting transcript"
2737
+ });
2738
+ throw new Error(error);
2739
+ }
2740
+ };
2741
+ const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
2742
+ var _a, _b, _c, _d, _e;
2743
+ const responseTool = getRespondCardTool({
2744
+ language: feedbackLanguage,
2745
+ standard: gradingStandard
2746
+ });
2747
+ try {
2748
+ const {
2749
+ data: {
2750
+ response,
2751
+ prompt_tokens = 0,
2752
+ completion_tokens = 0,
2753
+ success: aiSuccess = false
2754
+ // the AI was able to generate a response
2755
+ }
2756
+ } = await ((_b = (_a = SpeakableFirebaseFunctions).createChatCompletion) == null ? void 0 : _b.call(_a, {
2757
+ chat: {
2758
+ model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
2759
+ messages,
2760
+ temperature: 0.7,
2761
+ ...responseTool
2762
+ },
2763
+ type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
2764
+ }));
2765
+ const functionArguments = JSON.parse(((_e = (_d = (_c = response == null ? void 0 : response.tool_calls) == null ? void 0 : _c[0]) == null ? void 0 : _d.function) == null ? void 0 : _e.arguments) || "{}");
2766
+ const result = {
2767
+ ...functionArguments,
2768
+ prompt_tokens,
2769
+ completion_tokens,
2770
+ aiSuccess
2771
+ };
2772
+ onCompletionSuccess(result);
2773
+ return result;
2774
+ } catch (error) {
2775
+ onCompletionError({
2776
+ type: "COMPLETION",
2777
+ message: (error == null ? void 0 : error.message) || "Error getting completion"
2778
+ });
2779
+ throw new Error(error);
2780
+ }
2781
+ };
2782
+ const getFeedback = async ({
2783
+ cardId,
2784
+ language = "en",
2785
+ // required
2786
+ writtenResponse = null,
2787
+ // if the type = RESPOND_WRITE
2788
+ audio = null,
2789
+ autoGrade = true,
2790
+ file = null
2791
+ }) => {
2792
+ try {
2793
+ if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
2794
+ const result = {
2795
+ noFeedbackAvailable: true,
2796
+ success: true,
2797
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
2798
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
2799
+ };
2800
+ onCompletionSuccess(result);
2801
+ return result;
2802
+ }
2803
+ let transcript;
2804
+ if (writtenResponse) {
2805
+ transcript = writtenResponse;
2806
+ onTranscriptSuccess(writtenResponse);
2807
+ } else if (typeof audio === "string" && file) {
2808
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
2809
+ transcript = await getTransctipt(audio, language);
2810
+ onTranscriptSuccess(transcript);
2811
+ } else {
2812
+ console.info(
2813
+ `Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
2814
+ );
2815
+ }
2816
+ } else {
2817
+ transcript = await uploadAudioAndGetTranscript(audio || "", language);
2818
+ }
2819
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
2820
+ const results = await getAIResponse({
2821
+ cardId,
2822
+ transcript: transcript || ""
2823
+ });
2824
+ let output = results;
2825
+ if (!autoGrade) {
2826
+ output = {
2827
+ ...output,
2828
+ noFeedbackAvailable: true,
2829
+ success: true
2830
+ };
2831
+ }
2832
+ onCompletionSuccess(output);
2833
+ return output;
2834
+ } else {
2835
+ const result = {
2836
+ noFeedbackAvailable: true,
2837
+ success: true,
2838
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
2839
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
2840
+ };
2841
+ onCompletionSuccess(result);
2842
+ return result;
2843
+ }
2844
+ } catch (error) {
2845
+ console.error("Error getting feedback:", error);
2846
+ throw new Error(error);
2847
+ }
2848
+ };
2849
+ const getAIResponse = async ({ cardId, transcript }) => {
2850
+ try {
2851
+ const card = getCardFromCache({
2852
+ cardId,
2853
+ queryClient
2854
+ });
2855
+ let feedbackData;
2856
+ let proficiencyData = {};
2857
+ if (card && card.grading_method === "manual") {
2858
+ } else if (card && card.grading_method !== "standards_based") {
2859
+ const [geminiResult, proficiencyResult] = await Promise.all([
2860
+ getGeminiFeedback == null ? void 0 : getGeminiFeedback({
2861
+ cardId,
2862
+ studentId: currentUserId,
2863
+ studentResponse: transcript
2864
+ }),
2865
+ getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
2866
+ cardId,
2867
+ studentId: currentUserId,
2868
+ studentResponse: transcript
2869
+ })
2870
+ ]);
2871
+ proficiencyData = proficiencyResult.data || {};
2872
+ feedbackData = {
2873
+ ...geminiResult.data,
2874
+ // @ts-ignore
2875
+ proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
2876
+ };
2877
+ } else {
2878
+ const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
2879
+ cardId,
2880
+ studentId: currentUserId,
2881
+ studentResponse: transcript
2882
+ }));
2883
+ feedbackData = geminiResult.data;
2884
+ }
2885
+ const results = {
2886
+ ...feedbackData,
2887
+ // ...proficiencyData,
2888
+ aiSuccess: true,
2889
+ promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
2890
+ transcript
2891
+ };
2892
+ return results;
2893
+ } catch (error) {
2894
+ onCompletionError({
2895
+ type: "AI_FEEDBACK",
2896
+ message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
2897
+ });
2898
+ throw new Error(error);
2899
+ }
2900
+ };
2901
+ return {
2902
+ submitAudioResponse,
2903
+ uploadAudioAndGetTranscript,
2904
+ getTransctipt,
2905
+ getFreeResponseCompletion,
2906
+ getFeedback
2907
+ };
2908
+ };
2909
+
2193
2910
  // src/lib/create-firebase-client-native.ts
2194
2911
  import {
2195
2912
  getDoc,
@@ -2209,7 +2926,8 @@ import {
2209
2926
  startAt,
2210
2927
  startAfter,
2211
2928
  endAt,
2212
- endBefore
2929
+ endBefore,
2930
+ where
2213
2931
  } from "@react-native-firebase/firestore";
2214
2932
 
2215
2933
  // src/lib/create-firebase-client.ts
@@ -2256,7 +2974,8 @@ var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
2256
2974
  startAt,
2257
2975
  startAfter,
2258
2976
  endAt,
2259
- endBefore
2977
+ endBefore,
2978
+ where
2260
2979
  }
2261
2980
  });
2262
2981
  };
@@ -2282,24 +3001,34 @@ export {
2282
3001
  VerificationCardStatus,
2283
3002
  assignmentQueryKeys,
2284
3003
  cardsQueryKeys,
3004
+ cleanString,
2285
3005
  createAssignmentRepo,
2286
3006
  createCardRepo,
2287
3007
  createFsClientNative as createFsClient,
2288
3008
  createSetRepo,
3009
+ creditQueryKeys,
3010
+ debounce,
2289
3011
  getCardFromCache,
3012
+ getRespondCardTool,
2290
3013
  getSetFromCache,
3014
+ getWordHash,
3015
+ purify,
2291
3016
  refsCardsFiresotre,
2292
3017
  refsSetsFirestore,
2293
3018
  setsQueryKeys,
2294
3019
  updateCardInCache,
2295
3020
  updateSetInCache,
2296
3021
  useActivity,
3022
+ useActivityFeedbackAccess,
2297
3023
  useAssignment,
3024
+ useBaseOpenAI,
2298
3025
  useCards,
2299
3026
  useCreateCard,
2300
3027
  useCreateCards,
2301
3028
  useCreateNotification,
3029
+ useOrganizationAccess,
2302
3030
  useSet,
2303
- useSpeakableApi
3031
+ useSpeakableApi,
3032
+ useUserCredits
2304
3033
  };
2305
3034
  //# sourceMappingURL=index.native.mjs.map