@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.
- package/dist/index.native.d.mts +1264 -5
- package/dist/index.native.mjs +752 -15
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.web.d.mts +1264 -5
- package/dist/index.web.js +752 -15
- 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() {
|
|
@@ -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
|
-
|
|
125
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -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
|
-
|
|
152
|
-
|
|
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/
|
|
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,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
|