@speakableio/core 0.1.31 → 0.1.33

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() {
@@ -121,8 +122,8 @@ var FirebaseAPI = class _FirebaseAPI {
121
122
  const docRef = doc2(this.db, path);
122
123
  const docSnap = await getDoc2(docRef);
123
124
  const data = docSnap.exists() ? {
124
- id: docSnap.id,
125
- ...docSnap.data()
125
+ ...docSnap.data(),
126
+ id: docSnap.id
126
127
  } : null;
127
128
  return {
128
129
  id: docSnap.id,
@@ -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) {
@@ -148,8 +150,8 @@ var FirebaseAPI = class _FirebaseAPI {
148
150
  const collectionRef = collection2(this.db, path);
149
151
  const docRef = await addDoc2(collectionRef, data);
150
152
  return {
151
- id: docRef.id,
152
- ...data
153
+ ...data,
154
+ id: docRef.id
153
155
  };
154
156
  }
155
157
  async setDoc(path, data, options = {}) {
@@ -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,473 @@ 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
+ isAssignmentRoute = false
2610
+ }) => {
2611
+ var _a, _b, _c;
2612
+ const { user } = useSpeakableApi();
2613
+ const uid = user.auth.uid;
2614
+ const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
2615
+ const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
2616
+ const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
2617
+ const { data: userCredits } = useUserCredits();
2618
+ const { data: organizationAccess } = useOrganizationAccess();
2619
+ const query2 = useQuery6({
2620
+ queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
2621
+ aiEnabled,
2622
+ isStudent: isStudent != null ? isStudent : false,
2623
+ isAssignmentRoute,
2624
+ isActivityRoute,
2625
+ uid,
2626
+ organizationAccess: organizationAccess != null ? organizationAccess : null,
2627
+ userCredits
2628
+ }),
2629
+ queryFn: async () => {
2630
+ var _a2, _b2;
2631
+ if (!uid) {
2632
+ return {
2633
+ canAccessFeedback: false,
2634
+ reason: "Missing user ID",
2635
+ isUnlimited: false,
2636
+ accessType: "none"
2637
+ };
2638
+ }
2639
+ try {
2640
+ if (aiEnabled) {
2641
+ return {
2642
+ canAccessFeedback: true,
2643
+ reason: "AI feedback enabled",
2644
+ isUnlimited: true,
2645
+ accessType: "ai_enabled"
2646
+ };
2647
+ }
2648
+ if (isTeacher || userRoles.includes("ADMIN")) {
2649
+ return {
2650
+ canAccessFeedback: true,
2651
+ reason: "Teacher preview access",
2652
+ isUnlimited: true,
2653
+ accessType: "teacher_preview"
2654
+ };
2655
+ }
2656
+ if (isStudent && isActivityRoute) {
2657
+ try {
2658
+ const result = await ((_b2 = (_a2 = SpeakableFirebaseFunctions) == null ? void 0 : _a2.checkStudentTeacherPlan) == null ? void 0 : _b2.call(_a2, {
2659
+ studentId: uid
2660
+ }));
2661
+ const planCheckResult = result.data;
2662
+ if (planCheckResult.canAccessFeedback) {
2663
+ return {
2664
+ canAccessFeedback: true,
2665
+ reason: planCheckResult.reason || "Student access via teacher with active plan",
2666
+ isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
2667
+ accessType: "student_with_teacher_plan"
2668
+ };
2669
+ } else {
2670
+ return {
2671
+ canAccessFeedback: false,
2672
+ reason: planCheckResult.reason || "No teacher with active plan found",
2673
+ isUnlimited: false,
2674
+ accessType: "none"
2675
+ };
2676
+ }
2677
+ } catch (error) {
2678
+ console.error("Error checking student teacher plan:", error);
2679
+ return {
2680
+ canAccessFeedback: false,
2681
+ reason: "Error checking teacher plans",
2682
+ isUnlimited: false,
2683
+ accessType: "none"
2684
+ };
2685
+ }
2686
+ }
2687
+ return {
2688
+ canAccessFeedback: false,
2689
+ reason: "No access permissions found for current context",
2690
+ isUnlimited: false,
2691
+ accessType: "none"
2692
+ };
2693
+ } catch (error) {
2694
+ console.error("Error checking activity feedback access:", error);
2695
+ return {
2696
+ canAccessFeedback: false,
2697
+ reason: "Error checking access permissions",
2698
+ isUnlimited: false,
2699
+ accessType: "none"
2700
+ };
2701
+ }
2702
+ },
2703
+ enabled: !!uid,
2704
+ staleTime: 5 * 60 * 1e3,
2705
+ // 5 minutes
2706
+ gcTime: 10 * 60 * 1e3
2707
+ // 10 minutes
2708
+ });
2709
+ return {
2710
+ ...query2
2711
+ };
2712
+ };
2713
+
2714
+ // src/hooks/useOpenAI.ts
2715
+ var getGeminiFeedback = SpeakableFirebaseFunctions.getGeminiFeedback;
2716
+ var getProficiencyEstimate = SpeakableFirebaseFunctions.getProficiencyEstimate;
2717
+ var getAssemblyAITranscript = SpeakableFirebaseFunctions.getAssemblyAITranscript;
2718
+ var useBaseOpenAI = ({
2719
+ onTranscriptSuccess,
2720
+ onTranscriptError,
2721
+ onCompletionSuccess,
2722
+ onCompletionError,
2723
+ aiEnabled,
2724
+ submitAudioResponse,
2725
+ uploadAudioAndGetTranscript
2726
+ }) => {
2727
+ const { user, queryClient } = useSpeakableApi();
2728
+ const currentUserId = user.auth.uid;
2729
+ const { data: feedbackAccess } = useActivityFeedbackAccess({
2730
+ aiEnabled
2731
+ });
2732
+ const getTransctipt = async (audioUrl, language) => {
2733
+ try {
2734
+ const { data: transcript } = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
2735
+ audioUrl,
2736
+ language
2737
+ }));
2738
+ onTranscriptSuccess(transcript);
2739
+ return transcript;
2740
+ } catch (error) {
2741
+ console.log("error", error);
2742
+ onTranscriptError({
2743
+ type: "TRANSCRIPT",
2744
+ message: (error == null ? void 0 : error.message) || "Error getting transcript"
2745
+ });
2746
+ throw new Error(error);
2747
+ }
2748
+ };
2749
+ const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
2750
+ var _a, _b, _c, _d, _e;
2751
+ const responseTool = getRespondCardTool({
2752
+ language: feedbackLanguage,
2753
+ standard: gradingStandard
2754
+ });
2755
+ try {
2756
+ const {
2757
+ data: {
2758
+ response,
2759
+ prompt_tokens = 0,
2760
+ completion_tokens = 0,
2761
+ success: aiSuccess = false
2762
+ // the AI was able to generate a response
2763
+ }
2764
+ } = await ((_b = (_a = SpeakableFirebaseFunctions).createChatCompletion) == null ? void 0 : _b.call(_a, {
2765
+ chat: {
2766
+ model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
2767
+ messages,
2768
+ temperature: 0.7,
2769
+ ...responseTool
2770
+ },
2771
+ type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
2772
+ }));
2773
+ 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) || "{}");
2774
+ const result = {
2775
+ ...functionArguments,
2776
+ prompt_tokens,
2777
+ completion_tokens,
2778
+ aiSuccess
2779
+ };
2780
+ onCompletionSuccess(result);
2781
+ return result;
2782
+ } catch (error) {
2783
+ onCompletionError({
2784
+ type: "COMPLETION",
2785
+ message: (error == null ? void 0 : error.message) || "Error getting completion"
2786
+ });
2787
+ throw new Error(error);
2788
+ }
2789
+ };
2790
+ const getFeedback = async ({
2791
+ cardId,
2792
+ language = "en",
2793
+ // required
2794
+ writtenResponse = null,
2795
+ // if the type = RESPOND_WRITE
2796
+ audio = null,
2797
+ autoGrade = true,
2798
+ file = null
2799
+ }) => {
2800
+ try {
2801
+ if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
2802
+ const result = {
2803
+ noFeedbackAvailable: true,
2804
+ success: true,
2805
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
2806
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
2807
+ };
2808
+ onCompletionSuccess(result);
2809
+ return result;
2810
+ }
2811
+ let transcript;
2812
+ if (writtenResponse) {
2813
+ transcript = writtenResponse;
2814
+ onTranscriptSuccess(writtenResponse);
2815
+ } else if (typeof audio === "string" && file) {
2816
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
2817
+ transcript = await getTransctipt(audio, language);
2818
+ onTranscriptSuccess(transcript);
2819
+ } else {
2820
+ console.info(
2821
+ `Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
2822
+ );
2823
+ }
2824
+ } else {
2825
+ transcript = await uploadAudioAndGetTranscript(audio || "", language);
2826
+ }
2827
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
2828
+ const results = await getAIResponse({
2829
+ cardId,
2830
+ transcript: transcript || ""
2831
+ });
2832
+ let output = results;
2833
+ if (!autoGrade) {
2834
+ output = {
2835
+ ...output,
2836
+ noFeedbackAvailable: true,
2837
+ success: true
2838
+ };
2839
+ }
2840
+ onCompletionSuccess(output);
2841
+ return output;
2842
+ } else {
2843
+ const result = {
2844
+ noFeedbackAvailable: true,
2845
+ success: true,
2846
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
2847
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
2848
+ };
2849
+ onCompletionSuccess(result);
2850
+ return result;
2851
+ }
2852
+ } catch (error) {
2853
+ console.error("Error getting feedback:", error);
2854
+ throw new Error(error);
2855
+ }
2856
+ };
2857
+ const getAIResponse = async ({ cardId, transcript }) => {
2858
+ try {
2859
+ const card = getCardFromCache({
2860
+ cardId,
2861
+ queryClient
2862
+ });
2863
+ let feedbackData;
2864
+ let proficiencyData = {};
2865
+ if (card && card.grading_method === "manual") {
2866
+ } else if (card && card.grading_method !== "standards_based") {
2867
+ const [geminiResult, proficiencyResult] = await Promise.all([
2868
+ getGeminiFeedback == null ? void 0 : getGeminiFeedback({
2869
+ cardId,
2870
+ studentId: currentUserId,
2871
+ studentResponse: transcript
2872
+ }),
2873
+ getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
2874
+ cardId,
2875
+ studentId: currentUserId,
2876
+ studentResponse: transcript
2877
+ })
2878
+ ]);
2879
+ proficiencyData = proficiencyResult.data || {};
2880
+ feedbackData = {
2881
+ ...geminiResult.data,
2882
+ // @ts-ignore
2883
+ proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
2884
+ };
2885
+ } else {
2886
+ const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
2887
+ cardId,
2888
+ studentId: currentUserId,
2889
+ studentResponse: transcript
2890
+ }));
2891
+ feedbackData = geminiResult.data;
2892
+ }
2893
+ const results = {
2894
+ ...feedbackData,
2895
+ // ...proficiencyData,
2896
+ aiSuccess: true,
2897
+ promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
2898
+ transcript
2899
+ };
2900
+ return results;
2901
+ } catch (error) {
2902
+ onCompletionError({
2903
+ type: "AI_FEEDBACK",
2904
+ message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
2905
+ });
2906
+ throw new Error(error);
2907
+ }
2908
+ };
2909
+ return {
2910
+ submitAudioResponse,
2911
+ uploadAudioAndGetTranscript,
2912
+ getTransctipt,
2913
+ getFreeResponseCompletion,
2914
+ getFeedback
2915
+ };
2916
+ };
2917
+
2193
2918
  // src/lib/create-firebase-client-native.ts
2194
2919
  import {
2195
2920
  getDoc,
@@ -2209,7 +2934,8 @@ import {
2209
2934
  startAt,
2210
2935
  startAfter,
2211
2936
  endAt,
2212
- endBefore
2937
+ endBefore,
2938
+ where
2213
2939
  } from "@react-native-firebase/firestore";
2214
2940
 
2215
2941
  // src/lib/create-firebase-client.ts
@@ -2256,7 +2982,8 @@ var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
2256
2982
  startAt,
2257
2983
  startAfter,
2258
2984
  endAt,
2259
- endBefore
2985
+ endBefore,
2986
+ where
2260
2987
  }
2261
2988
  });
2262
2989
  };
@@ -2282,24 +3009,34 @@ export {
2282
3009
  VerificationCardStatus,
2283
3010
  assignmentQueryKeys,
2284
3011
  cardsQueryKeys,
3012
+ cleanString,
2285
3013
  createAssignmentRepo,
2286
3014
  createCardRepo,
2287
3015
  createFsClientNative as createFsClient,
2288
3016
  createSetRepo,
3017
+ creditQueryKeys,
3018
+ debounce,
2289
3019
  getCardFromCache,
3020
+ getRespondCardTool,
2290
3021
  getSetFromCache,
3022
+ getWordHash,
3023
+ purify,
2291
3024
  refsCardsFiresotre,
2292
3025
  refsSetsFirestore,
2293
3026
  setsQueryKeys,
2294
3027
  updateCardInCache,
2295
3028
  updateSetInCache,
2296
3029
  useActivity,
3030
+ useActivityFeedbackAccess,
2297
3031
  useAssignment,
3032
+ useBaseOpenAI,
2298
3033
  useCards,
2299
3034
  useCreateCard,
2300
3035
  useCreateCards,
2301
3036
  useCreateNotification,
3037
+ useOrganizationAccess,
2302
3038
  useSet,
2303
- useSpeakableApi
3039
+ useSpeakableApi,
3040
+ useUserCredits
2304
3041
  };
2305
3042
  //# sourceMappingURL=index.native.mjs.map