@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.
- package/dist/index.native.d.mts +1263 -5
- package/dist/index.native.mjs +740 -11
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.web.d.mts +1263 -5
- package/dist/index.web.js +740 -11
- package/dist/index.web.js.map +1 -1
- package/package.json +1 -1
package/dist/index.native.mjs
CHANGED
|
@@ -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
|
-
|
|
139
|
-
|
|
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/
|
|
955
|
-
|
|
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
|