@speakableio/core 0.1.67 → 0.1.68

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.
@@ -0,0 +1,3246 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/entry-points/index.native.ts
31
+ var index_native_exports = {};
32
+ __export(index_native_exports, {
33
+ ActivityPageType: () => ActivityPageType,
34
+ BASE_MULTIPLE_CHOICE_FIELD_VALUES: () => BASE_MULTIPLE_CHOICE_FIELD_VALUES,
35
+ BASE_REPEAT_FIELD_VALUES: () => BASE_REPEAT_FIELD_VALUES,
36
+ BASE_RESPOND_FIELD_VALUES: () => BASE_RESPOND_FIELD_VALUES,
37
+ FeedbackTypesCard: () => FeedbackTypesCard,
38
+ FsCtx: () => FsCtx,
39
+ LENIENCY_OPTIONS: () => LENIENCY_OPTIONS,
40
+ LeniencyCard: () => LeniencyCard,
41
+ MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES: () => MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES,
42
+ REPEAT_PAGE_ACTIVITY_TYPES: () => REPEAT_PAGE_ACTIVITY_TYPES,
43
+ RESPOND_AUDIO_PAGE_ACTIVITY_TYPES: () => RESPOND_AUDIO_PAGE_ACTIVITY_TYPES,
44
+ RESPOND_PAGE_ACTIVITY_TYPES: () => RESPOND_PAGE_ACTIVITY_TYPES,
45
+ RESPOND_WRITE_PAGE_ACTIVITY_TYPES: () => RESPOND_WRITE_PAGE_ACTIVITY_TYPES,
46
+ SPEAKABLE_NOTIFICATIONS: () => SPEAKABLE_NOTIFICATIONS,
47
+ STUDENT_LEVELS_OPTIONS: () => STUDENT_LEVELS_OPTIONS,
48
+ SpeakableNotificationTypes: () => SpeakableNotificationTypes,
49
+ SpeakableProvider: () => SpeakableProvider,
50
+ VerificationCardStatus: () => VerificationCardStatus,
51
+ assignmentQueryKeys: () => assignmentQueryKeys,
52
+ cardsQueryKeys: () => cardsQueryKeys,
53
+ checkIsMCPage: () => checkIsMCPage,
54
+ checkIsMediaPage: () => checkIsMediaPage,
55
+ checkIsRepeatPage: () => checkIsRepeatPage,
56
+ checkIsRespondAudioPage: () => checkIsRespondAudioPage,
57
+ checkIsRespondPage: () => checkIsRespondPage,
58
+ checkIsRespondWrittenPage: () => checkIsRespondWrittenPage,
59
+ checkIsShortAnswerPage: () => checkIsShortAnswerPage,
60
+ checkTypePageActivity: () => checkTypePageActivity,
61
+ cleanString: () => cleanString,
62
+ createAssignmentRepo: () => createAssignmentRepo,
63
+ createCardRepo: () => createCardRepo,
64
+ createFsClient: () => createFsClientNative,
65
+ createSetRepo: () => createSetRepo,
66
+ creditQueryKeys: () => creditQueryKeys,
67
+ debounce: () => debounce,
68
+ getCardFromCache: () => getCardFromCache,
69
+ getPagePrompt: () => getPagePrompt,
70
+ getPhraseLength: () => getPhraseLength,
71
+ getRespondCardTool: () => getRespondCardTool,
72
+ getSetFromCache: () => getSetFromCache,
73
+ getWordHash: () => getWordHash,
74
+ purify: () => purify,
75
+ refsCardsFiresotre: () => refsCardsFiresotre,
76
+ refsSetsFirestore: () => refsSetsFirestore,
77
+ scoreQueryKeys: () => scoreQueryKeys,
78
+ setsQueryKeys: () => setsQueryKeys,
79
+ updateCardInCache: () => updateCardInCache,
80
+ updateSetInCache: () => updateSetInCache,
81
+ useActivity: () => useActivity,
82
+ useActivityFeedbackAccess: () => useActivityFeedbackAccess,
83
+ useAssignment: () => useAssignment,
84
+ useBaseOpenAI: () => useBaseOpenAI,
85
+ useCards: () => useCards,
86
+ useClearScore: () => useClearScore,
87
+ useCreateCard: () => useCreateCard,
88
+ useCreateCards: () => useCreateCards,
89
+ useCreateNotification: () => useCreateNotification,
90
+ useGetCard: () => useGetCard,
91
+ useOrganizationAccess: () => useOrganizationAccess,
92
+ useScore: () => useScore,
93
+ useSet: () => useSet,
94
+ useSpeakableApi: () => useSpeakableApi,
95
+ useSubmitAssignmentScore: () => useSubmitAssignmentScore,
96
+ useSubmitPracticeScore: () => useSubmitPracticeScore,
97
+ useUpdateCardScore: () => useUpdateCardScore,
98
+ useUpdateScore: () => useUpdateScore,
99
+ useUpdateStudentVocab: () => useUpdateStudentVocab,
100
+ useUserCredits: () => useUserCredits
101
+ });
102
+ module.exports = __toCommonJS(index_native_exports);
103
+
104
+ // src/providers/SpeakableProvider.tsx
105
+ var import_react = require("react");
106
+ var import_jsx_runtime = require("react/jsx-runtime");
107
+ var FsCtx = (0, import_react.createContext)(null);
108
+ function SpeakableProvider({
109
+ user,
110
+ children,
111
+ queryClient,
112
+ permissions,
113
+ fsClient
114
+ }) {
115
+ const [speakableApi, setSpeakableApi] = (0, import_react.useState)(null);
116
+ (0, import_react.useEffect)(() => {
117
+ setSpeakableApi(fsClient);
118
+ }, [fsClient]);
119
+ if (!speakableApi) return null;
120
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
121
+ FsCtx.Provider,
122
+ {
123
+ value: {
124
+ speakableApi,
125
+ queryClient,
126
+ user,
127
+ permissions
128
+ },
129
+ children
130
+ }
131
+ );
132
+ }
133
+ function useSpeakableApi() {
134
+ const ctx = (0, import_react.useContext)(FsCtx);
135
+ if (!ctx) throw new Error("useSpeakableApi must be used within a SpeakableProvider");
136
+ return ctx;
137
+ }
138
+
139
+ // src/utils/error-handler.ts
140
+ var ServiceError = class extends Error {
141
+ constructor(message, originalError, code) {
142
+ super(message);
143
+ this.originalError = originalError;
144
+ this.code = code;
145
+ this.name = "ServiceError";
146
+ }
147
+ };
148
+ function withErrorHandler(fn, serviceName) {
149
+ return async (...args) => {
150
+ try {
151
+ return await fn(...args);
152
+ } catch (error) {
153
+ if (error instanceof Error && "code" in error) {
154
+ const firebaseError = error;
155
+ throw new ServiceError(
156
+ `Error in ${serviceName}: ${firebaseError.message}`,
157
+ error,
158
+ firebaseError.code
159
+ );
160
+ }
161
+ if (error instanceof Error) {
162
+ throw new ServiceError(`Error in ${serviceName}: ${error.message}`, error);
163
+ }
164
+ throw new ServiceError(`Unknown error in ${serviceName}`, error);
165
+ }
166
+ };
167
+ }
168
+
169
+ // src/lib/firebase/api.ts
170
+ var FirebaseAPI = class _FirebaseAPI {
171
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
172
+ constructor() {
173
+ this.config = null;
174
+ }
175
+ static getInstance() {
176
+ if (!_FirebaseAPI.instance) {
177
+ _FirebaseAPI.instance = new _FirebaseAPI();
178
+ }
179
+ return _FirebaseAPI.instance;
180
+ }
181
+ initialize(config) {
182
+ this.config = config;
183
+ }
184
+ get db() {
185
+ if (!this.config) throw new Error("Firebase API not initialized");
186
+ return this.config.db;
187
+ }
188
+ get helpers() {
189
+ if (!this.config) throw new Error("Firebase API not initialized");
190
+ return this.config.helpers;
191
+ }
192
+ get httpsCallable() {
193
+ var _a;
194
+ return (_a = this.config) == null ? void 0 : _a.httpsCallable;
195
+ }
196
+ logEvent(name, data) {
197
+ var _a;
198
+ (_a = this.config) == null ? void 0 : _a.logEvent(name, data);
199
+ }
200
+ accessQueryConstraints() {
201
+ const { query: query2, orderBy: orderBy2, limit: limit2, startAt: startAt2, startAfter: startAfter2, endAt: endAt2, endBefore: endBefore2, where: where2, increment: increment2 } = this.helpers;
202
+ return {
203
+ query: query2,
204
+ orderBy: orderBy2,
205
+ limit: limit2,
206
+ startAt: startAt2,
207
+ startAfter: startAfter2,
208
+ endAt: endAt2,
209
+ endBefore: endBefore2,
210
+ where: where2,
211
+ increment: increment2
212
+ };
213
+ }
214
+ accessHelpers() {
215
+ const { doc: doc2, collection: collection2, writeBatch: writeBatch2, serverTimestamp: serverTimestamp2, setDoc: setDoc2 } = this.helpers;
216
+ return {
217
+ doc: (path) => doc2(this.db, path),
218
+ collection: (path) => collection2(this.db, path),
219
+ writeBatch: () => writeBatch2(this.db),
220
+ serverTimestamp: serverTimestamp2,
221
+ setDoc: setDoc2
222
+ };
223
+ }
224
+ async getDoc(path) {
225
+ const { getDoc: getDoc2, doc: doc2 } = this.helpers;
226
+ const docRef = doc2(this.db, path);
227
+ const docSnap = await getDoc2(docRef);
228
+ const data = docSnap.exists() ? {
229
+ ...docSnap.data(),
230
+ id: docSnap.id
231
+ } : null;
232
+ return {
233
+ id: docSnap.id,
234
+ data
235
+ };
236
+ }
237
+ async getDocs(path, ...queryConstraints) {
238
+ const { getDocs: getDocs2, query: query2, collection: collection2 } = this.helpers;
239
+ const collectionRef = collection2(this.db, path);
240
+ const q = queryConstraints.length > 0 ? query2(collectionRef, ...queryConstraints) : collectionRef;
241
+ const querySnapshot = await getDocs2(q);
242
+ const data = querySnapshot.docs.map((doc2) => ({
243
+ data: doc2.data(),
244
+ id: doc2.id
245
+ }));
246
+ return {
247
+ data,
248
+ querySnapshot,
249
+ empty: querySnapshot.empty
250
+ };
251
+ }
252
+ async addDoc(path, data) {
253
+ const { addDoc: addDoc2, collection: collection2 } = this.helpers;
254
+ const collectionRef = collection2(this.db, path);
255
+ const docRef = await addDoc2(collectionRef, data);
256
+ return {
257
+ ...data,
258
+ id: docRef.id
259
+ };
260
+ }
261
+ async setDoc(path, data, options = {}) {
262
+ const { setDoc: setDoc2, doc: doc2 } = this.helpers;
263
+ const docRef = doc2(this.db, path);
264
+ await setDoc2(docRef, data, options);
265
+ }
266
+ async updateDoc(path, data) {
267
+ const { updateDoc: updateDoc2, doc: doc2 } = this.helpers;
268
+ const docRef = doc2(this.db, path);
269
+ await updateDoc2(docRef, data);
270
+ }
271
+ async deleteDoc(path) {
272
+ const { deleteDoc: deleteDoc2, doc: doc2 } = this.helpers;
273
+ const docRef = doc2(this.db, path);
274
+ await deleteDoc2(docRef);
275
+ }
276
+ async runTransaction(updateFunction) {
277
+ const { runTransaction: runTransaction2 } = this.helpers;
278
+ return runTransaction2(this.db, updateFunction);
279
+ }
280
+ async runBatch(operations) {
281
+ const { writeBatch: writeBatch2 } = this.helpers;
282
+ const batch = writeBatch2(this.db);
283
+ await Promise.all(operations.map((op) => op()));
284
+ await batch.commit();
285
+ }
286
+ writeBatch() {
287
+ const { writeBatch: writeBatch2 } = this.helpers;
288
+ const batch = writeBatch2(this.db);
289
+ return batch;
290
+ }
291
+ };
292
+ var api = FirebaseAPI.getInstance();
293
+
294
+ // src/domains/assignment/assignment.constants.ts
295
+ var ASSIGNMENT_ANALYTICS_TYPES = [
296
+ "macro" /* Macro */,
297
+ "gradebook" /* Gradebook */,
298
+ "cards" /* Cards */,
299
+ "student" /* Student */,
300
+ "student_summary" /* StudentSummary */
301
+ ];
302
+ var ASSIGNMENTS_COLLECTION = "assignments";
303
+ var ANALYTICS_SUBCOLLECTION = "analytics";
304
+ var SCORES_SUBCOLLECTION = "scores";
305
+ var refsAssignmentFiresotre = {
306
+ allAssignments: () => ASSIGNMENTS_COLLECTION,
307
+ assignment: (params) => `${ASSIGNMENTS_COLLECTION}/${params.id}`,
308
+ assignmentAllAnalytics: (params) => `${ASSIGNMENTS_COLLECTION}/${params.id}/${ANALYTICS_SUBCOLLECTION}`,
309
+ assignmentAnalytics: (params) => `${ASSIGNMENTS_COLLECTION}/${params.id}/${ANALYTICS_SUBCOLLECTION}/${params.type}`,
310
+ assignmentScores: (params) => `${ASSIGNMENTS_COLLECTION}/${params.id}/${SCORES_SUBCOLLECTION}/${params.userId}`
311
+ };
312
+
313
+ // src/domains/assignment/services/get-assignments-score.service.ts
314
+ var _getAssignmentScores = async ({
315
+ assignmentId,
316
+ analyticType = "macro" /* Macro */,
317
+ studentId,
318
+ currentUserId
319
+ }) => {
320
+ if (analyticType === "student" /* Student */) {
321
+ const path = refsAssignmentFiresotre.assignmentScores({
322
+ id: assignmentId,
323
+ userId: currentUserId
324
+ });
325
+ const response = await api.getDoc(path);
326
+ return { scores: response.data, id: assignmentId };
327
+ }
328
+ if (analyticType === "student_summary" /* StudentSummary */ && studentId) {
329
+ const path = refsAssignmentFiresotre.assignmentScores({
330
+ id: assignmentId,
331
+ userId: studentId
332
+ });
333
+ const response = await api.getDoc(path);
334
+ return { scores: response.data, id: assignmentId };
335
+ }
336
+ if (analyticType !== "all" /* All */ && ASSIGNMENT_ANALYTICS_TYPES.includes(analyticType)) {
337
+ const ref = refsAssignmentFiresotre.assignmentAnalytics({
338
+ id: assignmentId,
339
+ type: analyticType
340
+ });
341
+ const docData = await api.getDoc(ref);
342
+ return { scores: docData.data, id: assignmentId };
343
+ } else if (analyticType === "all" /* All */) {
344
+ const ref = refsAssignmentFiresotre.assignmentAllAnalytics({ id: assignmentId });
345
+ const response = await api.getDocs(ref);
346
+ const data = response.data.reduce((acc, curr) => {
347
+ acc[curr.id] = curr;
348
+ return acc;
349
+ }, {});
350
+ return { scores: data, id: assignmentId };
351
+ }
352
+ };
353
+ var getAssignmentScores = withErrorHandler(_getAssignmentScores, "getAssignmentScores");
354
+
355
+ // src/domains/assignment/services/attach-score-assignment.service.ts
356
+ var _attachScoresAssignment = async ({
357
+ assignments,
358
+ analyticType,
359
+ studentId,
360
+ currentUserId
361
+ }) => {
362
+ const scoresPromises = assignments.map((a) => {
363
+ return getAssignmentScores({
364
+ assignmentId: a.id,
365
+ analyticType,
366
+ studentId,
367
+ currentUserId
368
+ });
369
+ });
370
+ const scores = await Promise.all(scoresPromises);
371
+ const scoresObject = scores.reduce((acc, curr) => {
372
+ acc[curr.id] = curr.scores;
373
+ return acc;
374
+ }, {});
375
+ const assignmentsWithScores = assignments.map((a) => {
376
+ var _a;
377
+ return {
378
+ ...a,
379
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/ban-ts-comment
380
+ // @ts-ignore
381
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
382
+ scores: (_a = scoresObject[a.id]) != null ? _a : null
383
+ };
384
+ });
385
+ return assignmentsWithScores;
386
+ };
387
+ var attachScoresAssignment = withErrorHandler(
388
+ _attachScoresAssignment,
389
+ "attachScoresAssignment"
390
+ );
391
+
392
+ // src/domains/assignment/services/get-all-assignment.service.ts
393
+ async function _getAllAssignments() {
394
+ const path = refsAssignmentFiresotre.allAssignments();
395
+ const response = await api.getDocs(path);
396
+ return response.data;
397
+ }
398
+ var getAllAssignments = withErrorHandler(_getAllAssignments, "getAllAssignments");
399
+
400
+ // src/domains/assignment/utils/check-assignment-availability.ts
401
+ var import_dayjs = __toESM(require("dayjs"));
402
+ var checkAssignmentAvailability = (scheduledTime) => {
403
+ if (!scheduledTime) return true;
404
+ const scheduledDate = typeof scheduledTime === "string" ? (0, import_dayjs.default)(scheduledTime) : (0, import_dayjs.default)(scheduledTime.toDate());
405
+ if (!scheduledDate.isValid()) return true;
406
+ return (0, import_dayjs.default)().isAfter(scheduledDate);
407
+ };
408
+
409
+ // src/domains/assignment/services/get-assignment.service.ts
410
+ async function _getAssignment(params) {
411
+ var _a;
412
+ const path = refsAssignmentFiresotre.assignment({ id: params.assignmentId });
413
+ const response = await api.getDoc(path);
414
+ if (!response.data) return null;
415
+ const assignment = response.data;
416
+ const isAvailable = checkAssignmentAvailability(assignment.scheduledTime);
417
+ const assignmentWithId = {
418
+ ...assignment,
419
+ isAvailable,
420
+ id: params.assignmentId,
421
+ scheduledTime: (_a = assignment.scheduledTime) != null ? _a : null
422
+ };
423
+ if (params.analyticType) {
424
+ const assignmentsWithScores = await attachScoresAssignment({
425
+ assignments: [assignmentWithId],
426
+ analyticType: params.analyticType,
427
+ currentUserId: params.currentUserId
428
+ });
429
+ return assignmentsWithScores.length > 0 ? assignmentsWithScores[0] : assignmentWithId;
430
+ }
431
+ return assignmentWithId;
432
+ }
433
+ var getAssignment = withErrorHandler(_getAssignment, "getAssignment");
434
+
435
+ // src/domains/assignment/assignment.repo.ts
436
+ var createAssignmentRepo = () => {
437
+ return {
438
+ getAssignment,
439
+ attachScoresAssignment,
440
+ getAssignmentScores,
441
+ getAllAssignments
442
+ };
443
+ };
444
+
445
+ // src/domains/assignment/hooks/assignment.hooks.ts
446
+ var import_react_query = require("@tanstack/react-query");
447
+ var assignmentQueryKeys = {
448
+ all: ["assignments"],
449
+ byId: (id) => [...assignmentQueryKeys.all, id],
450
+ list: () => [...assignmentQueryKeys.all, "list"]
451
+ };
452
+ function useAssignment({
453
+ assignmentId,
454
+ enabled = true,
455
+ analyticType,
456
+ userId
457
+ }) {
458
+ const { speakableApi } = useSpeakableApi();
459
+ return (0, import_react_query.useQuery)({
460
+ queryKey: assignmentQueryKeys.byId(assignmentId),
461
+ queryFn: () => speakableApi.assignmentRepo.getAssignment({
462
+ assignmentId,
463
+ analyticType,
464
+ currentUserId: userId
465
+ }),
466
+ enabled
467
+ });
468
+ }
469
+
470
+ // src/domains/assignment/hooks/score-hooks.ts
471
+ var import_react_query2 = require("@tanstack/react-query");
472
+
473
+ // src/utils/debounce.utils.ts
474
+ function debounce(func, waitFor) {
475
+ let timeoutId;
476
+ return (...args) => new Promise((resolve, reject) => {
477
+ if (timeoutId) {
478
+ clearTimeout(timeoutId);
479
+ }
480
+ timeoutId = setTimeout(async () => {
481
+ try {
482
+ const result = await func(...args);
483
+ resolve(result);
484
+ } catch (error) {
485
+ reject(error);
486
+ }
487
+ }, waitFor);
488
+ });
489
+ }
490
+
491
+ // src/lib/tanstack/handle-optimistic-update-query.ts
492
+ var handleOptimisticUpdate = async ({
493
+ queryClient,
494
+ queryKey,
495
+ newData
496
+ }) => {
497
+ await queryClient.cancelQueries({
498
+ queryKey
499
+ });
500
+ const previousData = queryClient.getQueryData(queryKey);
501
+ if (previousData === void 0) {
502
+ queryClient.setQueryData(queryKey, newData);
503
+ } else {
504
+ queryClient.setQueryData(queryKey, { ...previousData, ...newData });
505
+ }
506
+ return { previousData };
507
+ };
508
+
509
+ // src/constants/speakable-plans.ts
510
+ var FEEDBACK_PLANS = {
511
+ FEEDBACK_TRANSCRIPT: "FEEDBACK_TRANSCRIPT",
512
+ // Transcript from the audio
513
+ FEEDBACK_SUMMARY: "FEEDBACK_SUMMARY",
514
+ // Chatty summary (Free plan)
515
+ FEEDBACK_GRAMMAR_INSIGHTS: "FEEDBACK_GRAMMAR_INSIGHTS",
516
+ // Grammar insights
517
+ FEEDBACK_SUGGESTED_RESPONSE: "FEEDBACK_SUGGESTED_RESPONSE",
518
+ // Suggested Response
519
+ FEEDBACK_RUBRIC: "FEEDBACK_RUBRIC",
520
+ // Suggested Response
521
+ FEEDBACK_GRADING_STANDARDS: "FEEDBACK_GRADING_STANDARDS",
522
+ // ACTFL / WIDA Estimate
523
+ FEEDBACK_TARGET_LANGUAGE: "FEEDBACK_TARGET_LANGUAGE",
524
+ // Ability to set the feedback language to the target language of the student
525
+ FEEDBACK_DISABLE_ALLOW_RETRIES: "FEEDBACK_DISABLE_ALLOW_RETRIES"
526
+ // Turn of allow retries
527
+ };
528
+ var AUTO_GRADING_PLANS = {
529
+ AUTO_GRADING_PASS_FAIL: "AUTO_GRADING_PASS_FAIL",
530
+ // Pass / fail grading
531
+ AUTO_GRADING_RUBRICS: "AUTO_GRADING_RUBRICS",
532
+ // Autograded rubrics
533
+ AUTO_GRADING_STANDARDS_BASED: "AUTO_GRADING_STANDARDS_BASED"
534
+ // Standards based grading
535
+ };
536
+ var AI_ASSISTANT_PLANS = {
537
+ AI_ASSISTANT_DOCUMENT_UPLOADS: "AI_ASSISTANT_DOCUMENT_UPLOADS",
538
+ // Allow document uploading
539
+ AI_ASSISTANT_UNLIMITED_USE: "AI_ASSISTANT_UNLIMITED_USE"
540
+ // Allow unlimited use of AI assistant. Otherwise, limits are used.
541
+ };
542
+ var ASSIGNMENT_SETTINGS_PLANS = {
543
+ ASSESSMENTS: "ASSESSMENTS",
544
+ // Ability to create assessment assignment types
545
+ GOOGLE_CLASSROOM_GRADE_PASSBACK: "GOOGLE_CLASSROOM_GRADE_PASSBACK"
546
+ // Assignment scores can sync with classroom
547
+ };
548
+ var ANALYTICS_PLANS = {
549
+ ANALYTICS_GRADEBOOK: "ANALYTICS_GRADEBOOK",
550
+ // Access to the gradebook page
551
+ ANALYTICS_CLASSROOM_ANALYTICS: "ANALYTICS_CLASSROOM_ANALYTICS",
552
+ // Access to the classroom analytics page
553
+ ANALYTICS_STUDENT_PROGRESS_REPORTS: "ANALYTICS_STUDENT_PROGRESS_REPORTS",
554
+ // Access to the panel that shows an individual student's progress and assignments
555
+ ANALYTICS_ASSIGNMENT_RESULTS: "ANALYTICS_ASSIGNMENT_RESULTS",
556
+ // Access to the assigment RESULTS page
557
+ ANALYTICS_ORGANIZATION: "ANALYTICS_ORGANIZATION"
558
+ // Access to the organization analytics panel (for permitted admins)
559
+ };
560
+ var SPACES_PLANS = {
561
+ SPACES_CREATE_SPACE: "SPACES_CREATE_SPACE",
562
+ // Ability to create spaces
563
+ SPACES_CHECK_POINTS: "SPACES_CHECK_POINTS"
564
+ // Feature not available yet. Ability to create checkpoints for spaces for data aggregation
565
+ };
566
+ var DISCOVER_PLANS = {
567
+ DISCOVER_ORGANIZATION_LIBRARY: "DISCOVER_ORGANIZATION_LIBRARY"
568
+ // Access to the organizations shared library
569
+ };
570
+ var MEDIA_AREA_PLANS = {
571
+ MEDIA_AREA_DOCUMENT_UPLOAD: "MEDIA_AREA_DOCUMENT_UPLOAD",
572
+ MEDIA_AREA_AUDIO_FILES: "MEDIA_AREA_AUDIO_FILES"
573
+ };
574
+ var FREE_PLAN = [];
575
+ var TEACHER_PRO_PLAN = [
576
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
577
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
578
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
579
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
580
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
581
+ SPACES_PLANS.SPACES_CREATE_SPACE
582
+ // AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
583
+ ];
584
+ var SCHOOL_STARTER = [
585
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
586
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
587
+ FEEDBACK_PLANS.FEEDBACK_GRAMMAR_INSIGHTS,
588
+ FEEDBACK_PLANS.FEEDBACK_SUGGESTED_RESPONSE,
589
+ FEEDBACK_PLANS.FEEDBACK_RUBRIC,
590
+ FEEDBACK_PLANS.FEEDBACK_GRADING_STANDARDS,
591
+ FEEDBACK_PLANS.FEEDBACK_DISABLE_ALLOW_RETRIES,
592
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
593
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
594
+ AUTO_GRADING_PLANS.AUTO_GRADING_RUBRICS,
595
+ AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
596
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_DOCUMENT_UPLOADS,
597
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_UNLIMITED_USE,
598
+ // ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS,
599
+ ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK,
600
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
601
+ ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS,
602
+ ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS,
603
+ // ANALYTICS_PLANS.ANALYTICS_ORGANIZATION,
604
+ SPACES_PLANS.SPACES_CREATE_SPACE,
605
+ SPACES_PLANS.SPACES_CHECK_POINTS,
606
+ // DISCOVER_PLANS.DISCOVER_ORGANIZATION_LIBRARY,
607
+ MEDIA_AREA_PLANS.MEDIA_AREA_DOCUMENT_UPLOAD,
608
+ MEDIA_AREA_PLANS.MEDIA_AREA_AUDIO_FILES
609
+ ];
610
+ var ORGANIZATION_PLAN = [
611
+ FEEDBACK_PLANS.FEEDBACK_TRANSCRIPT,
612
+ FEEDBACK_PLANS.FEEDBACK_SUMMARY,
613
+ FEEDBACK_PLANS.FEEDBACK_GRAMMAR_INSIGHTS,
614
+ FEEDBACK_PLANS.FEEDBACK_SUGGESTED_RESPONSE,
615
+ FEEDBACK_PLANS.FEEDBACK_RUBRIC,
616
+ FEEDBACK_PLANS.FEEDBACK_GRADING_STANDARDS,
617
+ FEEDBACK_PLANS.FEEDBACK_DISABLE_ALLOW_RETRIES,
618
+ FEEDBACK_PLANS.FEEDBACK_TARGET_LANGUAGE,
619
+ AUTO_GRADING_PLANS.AUTO_GRADING_PASS_FAIL,
620
+ AUTO_GRADING_PLANS.AUTO_GRADING_RUBRICS,
621
+ AUTO_GRADING_PLANS.AUTO_GRADING_STANDARDS_BASED,
622
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_DOCUMENT_UPLOADS,
623
+ AI_ASSISTANT_PLANS.AI_ASSISTANT_UNLIMITED_USE,
624
+ ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS,
625
+ ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK,
626
+ ANALYTICS_PLANS.ANALYTICS_GRADEBOOK,
627
+ ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS,
628
+ ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS,
629
+ ANALYTICS_PLANS.ANALYTICS_ORGANIZATION,
630
+ SPACES_PLANS.SPACES_CREATE_SPACE,
631
+ SPACES_PLANS.SPACES_CHECK_POINTS,
632
+ DISCOVER_PLANS.DISCOVER_ORGANIZATION_LIBRARY,
633
+ MEDIA_AREA_PLANS.MEDIA_AREA_DOCUMENT_UPLOAD,
634
+ MEDIA_AREA_PLANS.MEDIA_AREA_AUDIO_FILES
635
+ ];
636
+ var SpeakablePlanTypes = {
637
+ basic: "basic",
638
+ teacher_pro: "teacher_pro",
639
+ school_starter: "school_starter",
640
+ organization: "organization",
641
+ // OLD PLANS
642
+ starter: "starter",
643
+ growth: "growth",
644
+ professional: "professional"
645
+ };
646
+ var SpeakablePermissionsMap = {
647
+ [SpeakablePlanTypes.basic]: FREE_PLAN,
648
+ [SpeakablePlanTypes.starter]: TEACHER_PRO_PLAN,
649
+ [SpeakablePlanTypes.teacher_pro]: TEACHER_PRO_PLAN,
650
+ [SpeakablePlanTypes.growth]: ORGANIZATION_PLAN,
651
+ [SpeakablePlanTypes.professional]: ORGANIZATION_PLAN,
652
+ [SpeakablePlanTypes.organization]: ORGANIZATION_PLAN,
653
+ [SpeakablePlanTypes.school_starter]: SCHOOL_STARTER
654
+ };
655
+ var SpeakablePlanHierarchy = [
656
+ SpeakablePlanTypes.basic,
657
+ SpeakablePlanTypes.starter,
658
+ SpeakablePlanTypes.teacher_pro,
659
+ SpeakablePlanTypes.growth,
660
+ SpeakablePlanTypes.professional,
661
+ SpeakablePlanTypes.school_starter,
662
+ SpeakablePlanTypes.organization
663
+ ];
664
+
665
+ // src/hooks/usePermissions.ts
666
+ var usePermissions = () => {
667
+ const { permissions } = useSpeakableApi();
668
+ const has = (permission) => {
669
+ var _a;
670
+ return (_a = permissions.permissions) == null ? void 0 : _a.includes(permission);
671
+ };
672
+ return {
673
+ plan: permissions.plan,
674
+ permissionsLoaded: permissions.loaded,
675
+ isStripePlan: permissions.isStripePlan,
676
+ refreshDate: permissions.refreshDate,
677
+ isInstitutionPlan: permissions.isInstitutionPlan,
678
+ subscriptionId: permissions.subscriptionId,
679
+ contact: permissions.contact,
680
+ hasGradebook: has(ANALYTICS_PLANS.ANALYTICS_GRADEBOOK),
681
+ hasGoogleClassroomGradePassback: has(ASSIGNMENT_SETTINGS_PLANS.GOOGLE_CLASSROOM_GRADE_PASSBACK),
682
+ hasAssessments: has(ASSIGNMENT_SETTINGS_PLANS.ASSESSMENTS),
683
+ hasSectionAnalytics: has(ANALYTICS_PLANS.ANALYTICS_CLASSROOM_ANALYTICS),
684
+ hasStudentReports: has(ANALYTICS_PLANS.ANALYTICS_STUDENT_PROGRESS_REPORTS),
685
+ permissions: permissions || [],
686
+ hasStudentPortfolios: permissions.hasStudentPortfolios,
687
+ isFreeOrgTrial: permissions.type === "free_org_trial",
688
+ freeOrgTrialExpired: permissions.freeOrgTrialExpired
689
+ };
690
+ };
691
+ var usePermissions_default = usePermissions;
692
+
693
+ // src/domains/notification/notification.constants.ts
694
+ var SPEAKABLE_NOTIFICATIONS = {
695
+ NEW_ASSIGNMENT: "new_assignment",
696
+ ASSESSMENT_SUBMITTED: "assessment_submitted",
697
+ ASSESSMENT_SCORED: "assessment_scored",
698
+ NEW_COMMENT: "NEW_COMMENT"
699
+ };
700
+ var SpeakableNotificationTypes = {
701
+ NEW_ASSIGNMENT: "NEW_ASSIGNMENT",
702
+ FEEDBACK_FROM_TEACHER: "FEEDBACK_FROM_TEACHER",
703
+ MESSAGE_FROM_STUDENT: "MESSAGE_FROM_STUDENT",
704
+ PHRASE_MARKED_CORRECT: "PHRASE_MARKED_CORRECT",
705
+ STUDENT_PROGRESS: "STUDENT_PROGRESS",
706
+ PLAYLIST_FOLLOWERS: "PLAYLIST_FOLLOWERS",
707
+ PLAYLIST_PLAYS: "PLAYLIST_PLAYS",
708
+ // New notifications
709
+ ASSESSMENT_SUBMITTED: "ASSESSMENT_SUBMITTED",
710
+ // Notification FOR TEACHER when student submits assessment
711
+ ASSESSMENT_SCORED: "ASSESSMENT_SCORED",
712
+ // Notification FOR STUDENT when teacher scores assessment
713
+ // Comment
714
+ NEW_COMMENT: "NEW_COMMENT"
715
+ };
716
+
717
+ // src/domains/notification/services/create-notification.service.ts
718
+ var import_dayjs2 = __toESM(require("dayjs"));
719
+
720
+ // src/constants/web.constants.ts
721
+ var WEB_BASE_URL = "https://app.speakable.io";
722
+
723
+ // src/domains/notification/services/send-notification.service.ts
724
+ var _sendNotification = async (sendTo, notification) => {
725
+ var _a, _b, _c;
726
+ const results = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createNotificationV2")) == null ? void 0 : _c({
727
+ sendTo,
728
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
729
+ notification
730
+ }));
731
+ return results;
732
+ };
733
+ var sendNotification = withErrorHandler(_sendNotification, "sendNotification");
734
+
735
+ // src/domains/notification/services/create-notification.service.ts
736
+ var createNotification = async ({
737
+ data,
738
+ type,
739
+ userId,
740
+ profile
741
+ }) => {
742
+ let result;
743
+ switch (type) {
744
+ case SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT:
745
+ result = await handleAssignNotifPromise({ data, profile });
746
+ break;
747
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED:
748
+ result = await createAssessmentSubmissionNotification({
749
+ data,
750
+ profile,
751
+ userId
752
+ });
753
+ break;
754
+ case SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED:
755
+ result = await createAssessmentScoredNotification({
756
+ data,
757
+ profile
758
+ });
759
+ break;
760
+ default:
761
+ result = null;
762
+ break;
763
+ }
764
+ return result;
765
+ };
766
+ var handleAssignNotifPromise = async ({
767
+ data: assignments,
768
+ profile
769
+ }) => {
770
+ if (!assignments.length) return;
771
+ try {
772
+ const notifsPromises = assignments.map(async (assignment) => {
773
+ const {
774
+ section,
775
+ section: { members },
776
+ ...rest
777
+ } = assignment;
778
+ if (!section || !members) throw new Error("Invalid assignment data");
779
+ const data = { section, sendTo: members, assignment: { ...rest } };
780
+ return createNewAssignmentNotification({ data, profile });
781
+ });
782
+ await Promise.all(notifsPromises);
783
+ return {
784
+ success: true,
785
+ message: "Assignment notifications sent successfully"
786
+ };
787
+ } catch (error) {
788
+ console.error("Error in handleAssignNotifPromise:", error);
789
+ throw error;
790
+ }
791
+ };
792
+ var createNewAssignmentNotification = async ({
793
+ data,
794
+ profile
795
+ }) => {
796
+ var _a;
797
+ const { assignment, sendTo } = data;
798
+ const teacherName = profile.displayName || "Your teacher";
799
+ const dueDate = assignment.dueDateTimestamp ? (0, import_dayjs2.default)(assignment.dueDateTimestamp.toDate()).format("MMM Do") : null;
800
+ const results = await sendNotification(sendTo, {
801
+ courseId: assignment.courseId,
802
+ type: SPEAKABLE_NOTIFICATIONS.NEW_ASSIGNMENT,
803
+ senderName: teacherName,
804
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
805
+ messagePreview: `A new assignment "${assignment.name}" is now available. ${dueDate ? `Due ${dueDate}` : ""}`,
806
+ title: "New Assignment Available!",
807
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
808
+ });
809
+ return results;
810
+ };
811
+ var createAssessmentSubmissionNotification = async ({
812
+ data: assignment,
813
+ profile,
814
+ userId
815
+ }) => {
816
+ var _a;
817
+ const studentName = profile.displayName || "Your student";
818
+ const results = await sendNotification(assignment.owners, {
819
+ courseId: assignment.courseId,
820
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SUBMITTED,
821
+ link: `${WEB_BASE_URL}/a/${assignment.id}?studentId=${userId}`,
822
+ title: `Assessment Submitted!`,
823
+ senderName: studentName,
824
+ messagePreview: `${studentName} has submitted the assessment "${assignment.name}"`,
825
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url
826
+ });
827
+ return results;
828
+ };
829
+ var createAssessmentScoredNotification = async ({
830
+ data,
831
+ profile
832
+ }) => {
833
+ var _a, _b, _c, _d, _e;
834
+ const { assignment, sendTo } = data;
835
+ const teacherName = profile.displayName || "Your teacher";
836
+ const title = `${assignment.isAssessment ? "Assessment" : "Assignment"} Reviewed!`;
837
+ const messagePreview = `Your ${assignment.isAssessment ? "assessment" : "assignment"} has been reviewed by your teacher. Click to view the feedback.`;
838
+ const results = await sendNotification(sendTo, {
839
+ courseId: assignment.courseId,
840
+ type: SPEAKABLE_NOTIFICATIONS.ASSESSMENT_SCORED,
841
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
842
+ title,
843
+ messagePreview,
844
+ imageUrl: (_a = profile.image) == null ? void 0 : _a.url,
845
+ senderName: teacherName
846
+ });
847
+ await ((_e = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "sendAssessmentScoredEmail")) == null ? void 0 : _e({
848
+ assessmentTitle: assignment.name,
849
+ link: `${WEB_BASE_URL}/assignment/${assignment.id}`,
850
+ senderImage: ((_d = profile.image) == null ? void 0 : _d.url) || "",
851
+ studentId: sendTo[0],
852
+ teacherName: profile.displayName
853
+ }));
854
+ return results;
855
+ };
856
+
857
+ // src/domains/notification/hooks/notification.hooks.ts
858
+ var notificationQueryKeys = {
859
+ all: ["notifications"],
860
+ byId: (id) => [...notificationQueryKeys.all, id]
861
+ };
862
+ var useCreateNotification = () => {
863
+ const { user, queryClient } = useSpeakableApi();
864
+ const handleCreateNotifications = async (type, data) => {
865
+ var _a, _b;
866
+ const result = await createNotification({
867
+ type,
868
+ userId: user.auth.uid,
869
+ profile: (_a = user == null ? void 0 : user.profile) != null ? _a : {},
870
+ data
871
+ });
872
+ queryClient.invalidateQueries({
873
+ queryKey: notificationQueryKeys.byId((_b = user == null ? void 0 : user.auth.uid) != null ? _b : "")
874
+ });
875
+ return result;
876
+ };
877
+ return {
878
+ createNotification: handleCreateNotifications
879
+ };
880
+ };
881
+
882
+ // src/hooks/useGoogleClassroom.ts
883
+ var useGoogleClassroom = () => {
884
+ const submitAssignmentToGoogleClassroom = async ({
885
+ assignment,
886
+ scores,
887
+ googleUserId = null
888
+ // optional to override the user's googleUserId
889
+ }) => {
890
+ var _a, _b, _c;
891
+ try {
892
+ const { googleClassroomUserId = null } = scores;
893
+ const googleId = googleUserId || googleClassroomUserId;
894
+ if (!googleId)
895
+ return {
896
+ error: true,
897
+ message: "No Google Classroom ID found"
898
+ };
899
+ const { courseWorkId, maxPoints, owners, courseId } = assignment;
900
+ const draftGrade = (scores == null ? void 0 : scores.score) ? (scores == null ? void 0 : scores.score) / 100 * maxPoints : 0;
901
+ const result = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentToGoogleClassroomV2")) == null ? void 0 : _c({
902
+ teacherId: owners[0],
903
+ courseId,
904
+ courseWorkId,
905
+ userId: googleId,
906
+ draftGrade
907
+ }));
908
+ return result;
909
+ } catch (error) {
910
+ return { error: true, message: error.message };
911
+ }
912
+ };
913
+ return {
914
+ submitAssignmentToGoogleClassroom
915
+ };
916
+ };
917
+
918
+ // src/lib/firebase/firebase-analytics/grading-standard.ts
919
+ var logGradingStandardLog = (data) => {
920
+ var _a, _b, _c;
921
+ if (data.courseId && data.type && data.level) {
922
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
923
+ eventType: data.type || "custom",
924
+ level: data.level,
925
+ courseId: data.courseId
926
+ });
927
+ }
928
+ api.logEvent("logGradingStandard", data);
929
+ };
930
+
931
+ // src/constants/analytics.constants.ts
932
+ var ANALYTICS_EVENT_TYPES = {
933
+ VOICE_SUCCESS: "voice_success",
934
+ VOICE_FAIL: "voice_fail",
935
+ RESPOND_CARD_SUCCESS: "respond_card_success",
936
+ RESPOND_CARD_FAIL: "respond_card_fail",
937
+ RESPOND_WRITE_CARD_SUCCESS: "respond_write_card_success",
938
+ RESPOND_WRITE_CARD_FAIL: "respond_write_card_fail",
939
+ RESPOND_FREE_PLAN: "respond_free_plan",
940
+ RESPOND_WRITE_FREE_PLAN: "respond_write_free_plan",
941
+ SUBMISSION: "assignment_submitted",
942
+ ASSIGNMENT_STARTED: "assignment_started",
943
+ CREATE_ASSIGNMENT: "create_assignment",
944
+ MC_SUCCESS: "multiple_choice_success",
945
+ MC_FAIL: "multiple_choice_fail",
946
+ ACTFL_LEVEL: "actfl_level",
947
+ WIDA_LEVEL: "wida_level"
948
+ };
949
+
950
+ // src/lib/firebase/firebase-analytics/assignment.ts
951
+ var logOpenAssignment = (data = {}) => {
952
+ api.logEvent("open_assignment", data);
953
+ };
954
+ var logOpenActivityPreview = (data = {}) => {
955
+ api.logEvent("open_activity_preview", data);
956
+ };
957
+ var logSubmitAssignment = (data = {}) => {
958
+ var _a, _b, _c;
959
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
960
+ eventType: ANALYTICS_EVENT_TYPES.SUBMISSION,
961
+ ...data
962
+ });
963
+ api.logEvent(ANALYTICS_EVENT_TYPES.SUBMISSION, data);
964
+ };
965
+ var logStartAssignment = (data = {}) => {
966
+ var _a, _b, _c;
967
+ if (data.courseId) {
968
+ (_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "handleCouresAnalyticsEvent")) == null ? void 0 : _c({
969
+ eventType: ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED,
970
+ ...data
971
+ });
972
+ }
973
+ api.logEvent(ANALYTICS_EVENT_TYPES.ASSIGNMENT_STARTED, data);
974
+ };
975
+
976
+ // src/domains/assignment/utils/create-default-score.ts
977
+ var defaultScore = (props) => {
978
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
979
+ const score = {
980
+ progress: 0,
981
+ score: 0,
982
+ startDate: serverTimestamp2(),
983
+ status: "IN_PROGRESS",
984
+ submitted: false,
985
+ cards: {},
986
+ lastPlayed: serverTimestamp2(),
987
+ owners: props.owners,
988
+ userId: props.userId
989
+ };
990
+ if (props.googleClassroomUserId) {
991
+ score.googleClassroomUserId = props.googleClassroomUserId;
992
+ }
993
+ if (props.courseId) {
994
+ score.courseId = props.courseId;
995
+ }
996
+ return score;
997
+ };
998
+
999
+ // src/domains/assignment/score-practice.constants.ts
1000
+ var SCORES_PRACTICE_COLLECTION = "users";
1001
+ var SCORES_PRACTICE_SUBCOLLECTION = "practice";
1002
+ var refsScoresPractice = {
1003
+ practiceScores: (params) => `${SCORES_PRACTICE_COLLECTION}/${params.userId}/${SCORES_PRACTICE_SUBCOLLECTION}/${params.setId}`,
1004
+ practiceScoreHistoryRefDoc: (params) => `${SCORES_PRACTICE_COLLECTION}/${params.userId}/${SCORES_PRACTICE_SUBCOLLECTION}/${params.setId}/attempts/${params.date}`
1005
+ };
1006
+
1007
+ // src/domains/assignment/services/create-score.service.ts
1008
+ async function _createScore(params) {
1009
+ var _a, _b, _c;
1010
+ if (params.isAssignment) {
1011
+ const ref = refsAssignmentFiresotre.assignmentScores({
1012
+ id: params.activityId,
1013
+ userId: params.userId
1014
+ });
1015
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "updateAssignmentGradebookStatus")) == null ? void 0 : _c({
1016
+ assignmentId: params.activityId,
1017
+ userId: params.userId,
1018
+ status: "IN_PROGRESS",
1019
+ score: null
1020
+ }));
1021
+ await api.setDoc(ref, params.scoreData, { merge: true });
1022
+ return {
1023
+ id: params.userId
1024
+ };
1025
+ } else {
1026
+ const ref = refsScoresPractice.practiceScores({
1027
+ userId: params.userId,
1028
+ setId: params.activityId
1029
+ });
1030
+ await api.setDoc(ref, params.scoreData);
1031
+ return {
1032
+ id: params.userId
1033
+ };
1034
+ }
1035
+ }
1036
+ var createScore = withErrorHandler(_createScore, "createScore");
1037
+
1038
+ // src/domains/assignment/services/get-score.service.ts
1039
+ async function getAssignmentScore({
1040
+ userId,
1041
+ assignment,
1042
+ googleClassroomUserId
1043
+ }) {
1044
+ const path = refsAssignmentFiresotre.assignmentScores({
1045
+ id: assignment.id,
1046
+ userId
1047
+ });
1048
+ const response = await api.getDoc(path);
1049
+ if (response.data == null) {
1050
+ const newScore = {
1051
+ ...defaultScore({
1052
+ owners: [userId],
1053
+ userId,
1054
+ courseId: assignment.courseId,
1055
+ googleClassroomUserId
1056
+ }),
1057
+ assignmentId: assignment.id
1058
+ };
1059
+ logStartAssignment({
1060
+ courseId: assignment.courseId
1061
+ });
1062
+ const result = await createScore({
1063
+ activityId: assignment.id,
1064
+ userId,
1065
+ isAssignment: true,
1066
+ scoreData: newScore
1067
+ });
1068
+ return {
1069
+ ...newScore,
1070
+ id: result.id
1071
+ };
1072
+ }
1073
+ return response.data;
1074
+ }
1075
+ async function getPracticeScore({ userId, setId }) {
1076
+ const path = refsScoresPractice.practiceScores({ userId, setId });
1077
+ const response = await api.getDoc(path);
1078
+ if (response.data == null) {
1079
+ const newScore = {
1080
+ ...defaultScore({
1081
+ owners: [userId],
1082
+ userId
1083
+ }),
1084
+ setId
1085
+ };
1086
+ const result = await createScore({
1087
+ activityId: setId,
1088
+ userId,
1089
+ isAssignment: false,
1090
+ scoreData: newScore
1091
+ });
1092
+ return {
1093
+ ...newScore,
1094
+ id: result.id
1095
+ };
1096
+ }
1097
+ return response.data;
1098
+ }
1099
+ async function _getScore(params) {
1100
+ if (params.isAssignment) {
1101
+ return await getAssignmentScore({
1102
+ userId: params.userId,
1103
+ assignment: {
1104
+ id: params.activityId,
1105
+ courseId: params.courseId
1106
+ },
1107
+ googleClassroomUserId: params.googleClassroomUserId
1108
+ });
1109
+ } else {
1110
+ return await getPracticeScore({
1111
+ userId: params.userId,
1112
+ setId: params.activityId
1113
+ });
1114
+ }
1115
+ }
1116
+ var getScore = withErrorHandler(_getScore, "getScore");
1117
+
1118
+ // src/domains/assignment/utils/calculateScoreAndProgress.ts
1119
+ var calculateScoreAndProgress = (scores, cardsList, weights) => {
1120
+ const totalSetPoints = cardsList.reduce((acc, cardId) => {
1121
+ acc += (weights == null ? void 0 : weights[cardId]) || 1;
1122
+ return acc;
1123
+ }, 0);
1124
+ const totalPointsAwarded = Object.keys((scores == null ? void 0 : scores.cards) || {}).reduce((acc, cardId) => {
1125
+ var _a, _b;
1126
+ const cardScores = (_a = scores == null ? void 0 : scores.cards) == null ? void 0 : _a[cardId];
1127
+ if ((cardScores == null ? void 0 : cardScores.completed) || (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0) {
1128
+ const score2 = (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0 ? Number((_b = cardScores == null ? void 0 : cardScores.score) != null ? _b : 0) : null;
1129
+ const weight = (weights == null ? void 0 : weights[cardId]) || 1;
1130
+ const fraction = (score2 != null ? score2 : 0) / 100;
1131
+ if (score2 || score2 === 0) {
1132
+ acc += weight * fraction;
1133
+ } else {
1134
+ acc += weight;
1135
+ }
1136
+ }
1137
+ return acc;
1138
+ }, 0);
1139
+ const totalCompletedCards = Object.keys((scores == null ? void 0 : scores.cards) || {}).reduce((acc, cardId) => {
1140
+ var _a;
1141
+ const cardScores = (_a = scores == null ? void 0 : scores.cards) == null ? void 0 : _a[cardId];
1142
+ if ((cardScores == null ? void 0 : cardScores.completed) || (cardScores == null ? void 0 : cardScores.score) || (cardScores == null ? void 0 : cardScores.score) === 0) {
1143
+ acc += 1;
1144
+ }
1145
+ return acc;
1146
+ }, 0);
1147
+ const percent = totalPointsAwarded / totalSetPoints;
1148
+ const score = Math.round(percent * 100);
1149
+ const progress = Math.round(totalCompletedCards / (cardsList.length || 1) * 100);
1150
+ return { score, progress };
1151
+ };
1152
+ var calculateScoreAndProgress_default = calculateScoreAndProgress;
1153
+
1154
+ // src/domains/assignment/services/update-score.service.ts
1155
+ async function _updateScore(params) {
1156
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1157
+ id: params.activityId,
1158
+ userId: params.userId
1159
+ }) : refsScoresPractice.practiceScores({
1160
+ setId: params.activityId,
1161
+ userId: params.userId
1162
+ });
1163
+ await api.updateDoc(path, {
1164
+ ...params.data
1165
+ });
1166
+ }
1167
+ var updateScore = withErrorHandler(_updateScore, "updateScore");
1168
+ async function _updateCardScore(params) {
1169
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1170
+ id: params.activityId,
1171
+ userId: params.userId
1172
+ }) : refsScoresPractice.practiceScores({
1173
+ setId: params.activityId,
1174
+ userId: params.userId
1175
+ });
1176
+ const updates = Object.keys(params.updates.cardScore).reduce(
1177
+ (acc, key) => {
1178
+ acc[`cards.${params.cardId}.${key}`] = params.updates.cardScore[key];
1179
+ return acc;
1180
+ },
1181
+ {}
1182
+ );
1183
+ if (params.updates.progress) {
1184
+ updates.progress = params.updates.progress;
1185
+ }
1186
+ if (params.updates.score) {
1187
+ updates.score = params.updates.score;
1188
+ }
1189
+ await api.updateDoc(path, {
1190
+ ...updates
1191
+ });
1192
+ }
1193
+ var updateCardScore = withErrorHandler(_updateCardScore, "updateCardScore");
1194
+
1195
+ // src/domains/assignment/services/clear-score.service.ts
1196
+ var import_dayjs3 = __toESM(require("dayjs"));
1197
+ async function clearScore(params) {
1198
+ var _a, _b, _c, _d, _e;
1199
+ const update = {
1200
+ [`cards.${params.cardId}`]: {
1201
+ attempts: (_a = params.cardScores.attempts) != null ? _a : 1,
1202
+ correct: (_b = params.cardScores.correct) != null ? _b : 0,
1203
+ // save old score history
1204
+ history: [
1205
+ {
1206
+ ...params.cardScores,
1207
+ attempts: (_c = params.cardScores.attempts) != null ? _c : 1,
1208
+ correct: (_d = params.cardScores.correct) != null ? _d : 0,
1209
+ retryTime: (0, import_dayjs3.default)().format("YYYY-MM-DD HH:mm:ss"),
1210
+ history: null
1211
+ },
1212
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1213
+ ...(_e = params.cardScores.history) != null ? _e : []
1214
+ ]
1215
+ }
1216
+ };
1217
+ const path = params.isAssignment ? refsAssignmentFiresotre.assignmentScores({
1218
+ id: params.activityId,
1219
+ userId: params.userId
1220
+ }) : refsScoresPractice.practiceScores({
1221
+ setId: params.activityId,
1222
+ userId: params.userId
1223
+ });
1224
+ await api.updateDoc(path, update);
1225
+ return {
1226
+ update,
1227
+ activityId: params.activityId
1228
+ };
1229
+ }
1230
+
1231
+ // src/domains/assignment/services/submit-assignment-score.service.ts
1232
+ var import_dayjs4 = __toESM(require("dayjs"));
1233
+ async function _submitAssignmentScore({
1234
+ cardIds,
1235
+ assignment,
1236
+ weights,
1237
+ userId,
1238
+ status,
1239
+ studentName
1240
+ }) {
1241
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1242
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1243
+ const fieldsUpdated = {
1244
+ submitted: true,
1245
+ progress: 100,
1246
+ submissionDate: serverTimestamp2(),
1247
+ status
1248
+ };
1249
+ if (assignment.isAssessment) {
1250
+ const result = await handleAssessment(
1251
+ assignment,
1252
+ userId,
1253
+ cardIds,
1254
+ weights,
1255
+ fieldsUpdated,
1256
+ studentName
1257
+ );
1258
+ return result;
1259
+ } else if (assignment.courseId) {
1260
+ await handleCourseAssignment(assignment, userId);
1261
+ }
1262
+ await api.updateDoc(path, { ...fieldsUpdated });
1263
+ return { success: true, fieldsUpdated };
1264
+ }
1265
+ var submitAssignmentScore = withErrorHandler(
1266
+ _submitAssignmentScore,
1267
+ "submitAssignmentScore"
1268
+ );
1269
+ async function handleAssessment(assignment, userId, cardIds, weights, fieldsUpdated, studentName) {
1270
+ var _a, _b, _c;
1271
+ const path = refsAssignmentFiresotre.assignmentScores({ id: assignment.id, userId });
1272
+ const response = await api.getDoc(path);
1273
+ if (!response.data) {
1274
+ throw new Error("Score not found");
1275
+ }
1276
+ const { score: scoreCalculated } = calculateScoreAndProgress_default(response.data, cardIds, weights);
1277
+ await api.updateDoc(path, { score: scoreCalculated, status: "PENDING_REVIEW" });
1278
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssessment")) == null ? void 0 : _c({
1279
+ assignmentId: assignment.id,
1280
+ assignmentTitle: assignment.name,
1281
+ userId,
1282
+ teacherId: assignment.owners[0],
1283
+ studentName
1284
+ }));
1285
+ fieldsUpdated.status = "PENDING_REVIEW";
1286
+ return { success: true, fieldsUpdated };
1287
+ }
1288
+ async function handleCourseAssignment(assignment, userId) {
1289
+ var _a, _b, _c;
1290
+ await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitAssignmentV2")) == null ? void 0 : _c({
1291
+ assignmentId: assignment.id,
1292
+ userId
1293
+ }));
1294
+ }
1295
+ async function submitPracticeScore({
1296
+ setId,
1297
+ userId,
1298
+ scores
1299
+ }) {
1300
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
1301
+ const date = (0, import_dayjs4.default)().format("YYYY-MM-DD-HH-mm");
1302
+ const ref = refsScoresPractice.practiceScoreHistoryRefDoc({ setId, userId, date });
1303
+ const fieldsUpdated = {
1304
+ ...scores,
1305
+ submitted: true,
1306
+ progress: 100,
1307
+ submissionDate: serverTimestamp2(),
1308
+ status: "SUBMITTED"
1309
+ };
1310
+ await api.setDoc(ref, { ...fieldsUpdated });
1311
+ const refScores = refsScoresPractice.practiceScores({ userId, setId });
1312
+ await api.deleteDoc(refScores);
1313
+ return { success: true, fieldsUpdated };
1314
+ }
1315
+
1316
+ // src/domains/assignment/hooks/score-hooks.ts
1317
+ var scoreQueryKeys = {
1318
+ all: ["scores"],
1319
+ byId: (id) => [...scoreQueryKeys.all, id],
1320
+ list: () => [...scoreQueryKeys.all, "list"]
1321
+ };
1322
+ function useScore({
1323
+ isAssignment,
1324
+ activityId,
1325
+ userId = "",
1326
+ courseId,
1327
+ enabled = true,
1328
+ googleClassroomUserId
1329
+ }) {
1330
+ return (0, import_react_query2.useQuery)({
1331
+ queryFn: () => getScore({
1332
+ userId,
1333
+ courseId,
1334
+ activityId,
1335
+ googleClassroomUserId,
1336
+ isAssignment
1337
+ }),
1338
+ queryKey: scoreQueryKeys.byId(activityId),
1339
+ enabled
1340
+ });
1341
+ }
1342
+ var debounceUpdateScore = debounce(updateScore, 1e3);
1343
+ function useUpdateScore() {
1344
+ const { queryClient } = useSpeakableApi();
1345
+ const mutation = (0, import_react_query2.useMutation)({
1346
+ mutationFn: debounceUpdateScore,
1347
+ onMutate: (variables) => {
1348
+ return handleOptimisticUpdate({
1349
+ queryClient,
1350
+ queryKey: scoreQueryKeys.byId(variables.activityId),
1351
+ newData: variables.data
1352
+ });
1353
+ },
1354
+ onError: (_, variables, context) => {
1355
+ if (context == null ? void 0 : context.previousData)
1356
+ queryClient.setQueryData(scoreQueryKeys.byId(variables.activityId), context.previousData);
1357
+ },
1358
+ onSettled: (_, err, variables) => {
1359
+ queryClient.invalidateQueries({
1360
+ queryKey: scoreQueryKeys.byId(variables.activityId)
1361
+ });
1362
+ }
1363
+ });
1364
+ return {
1365
+ mutationUpdateScore: mutation
1366
+ };
1367
+ }
1368
+ function useUpdateCardScore({
1369
+ isAssignment,
1370
+ activityId,
1371
+ userId,
1372
+ cardIds,
1373
+ weights
1374
+ }) {
1375
+ const { queryClient } = useSpeakableApi();
1376
+ const queryKey = scoreQueryKeys.byId(activityId);
1377
+ const mutation = (0, import_react_query2.useMutation)({
1378
+ mutationFn: async ({ cardId, cardScore }) => {
1379
+ const previousScores = queryClient.getQueryData(queryKey);
1380
+ const { progress, score, newScoreUpdated, updatedCardScore } = getScoreUpdated({
1381
+ previousScores: previousScores != null ? previousScores : {},
1382
+ cardId,
1383
+ cardScore,
1384
+ cardIds,
1385
+ weights
1386
+ });
1387
+ await updateCardScore({
1388
+ userId,
1389
+ cardId,
1390
+ isAssignment,
1391
+ activityId,
1392
+ updates: {
1393
+ cardScore: updatedCardScore,
1394
+ progress,
1395
+ score
1396
+ }
1397
+ });
1398
+ return { cardId, scoresUpdated: newScoreUpdated };
1399
+ },
1400
+ onMutate: ({ cardId, cardScore }) => {
1401
+ queryClient.setQueryData(queryKey, (previousScore) => {
1402
+ const updates = handleOptimisticScore({
1403
+ score: previousScore,
1404
+ cardId,
1405
+ cardScore,
1406
+ cardIds,
1407
+ weights
1408
+ });
1409
+ return {
1410
+ ...previousScore,
1411
+ ...updates
1412
+ };
1413
+ });
1414
+ },
1415
+ // eslint-disable-next-line no-unused-vars
1416
+ onError: (error) => {
1417
+ console.log(error.message);
1418
+ const previousData = queryClient.getQueryData(queryKey);
1419
+ if (previousData) {
1420
+ queryClient.setQueryData(queryKey, previousData);
1421
+ }
1422
+ }
1423
+ });
1424
+ return {
1425
+ mutationUpdateCardScore: mutation
1426
+ };
1427
+ }
1428
+ var getScoreUpdated = ({
1429
+ cardId,
1430
+ cardScore,
1431
+ previousScores,
1432
+ cardIds,
1433
+ weights
1434
+ }) => {
1435
+ var _a, _b;
1436
+ const previousCard = (_a = previousScores.cards) == null ? void 0 : _a[cardId];
1437
+ const newCardScore = {
1438
+ ...previousCard != null ? previousCard : {},
1439
+ ...cardScore
1440
+ };
1441
+ const newScores = {
1442
+ ...previousScores,
1443
+ cards: {
1444
+ ...(_b = previousScores.cards) != null ? _b : {},
1445
+ [cardId]: newCardScore
1446
+ }
1447
+ };
1448
+ const { score, progress } = calculateScoreAndProgress_default(newScores, cardIds, weights);
1449
+ return {
1450
+ newScoreUpdated: newScores,
1451
+ updatedCardScore: cardScore,
1452
+ score,
1453
+ progress
1454
+ };
1455
+ };
1456
+ var handleOptimisticScore = ({
1457
+ score,
1458
+ cardId,
1459
+ cardScore,
1460
+ cardIds,
1461
+ weights
1462
+ }) => {
1463
+ var _a;
1464
+ let cards = { ...(_a = score == null ? void 0 : score.cards) != null ? _a : {} };
1465
+ cards = {
1466
+ ...cards,
1467
+ [cardId]: {
1468
+ ...cards[cardId],
1469
+ ...cardScore
1470
+ }
1471
+ };
1472
+ const { score: scoreValue, progress } = calculateScoreAndProgress_default(
1473
+ // @ts-ignore
1474
+ {
1475
+ ...score != null ? score : {},
1476
+ cards
1477
+ },
1478
+ cardIds,
1479
+ weights
1480
+ );
1481
+ return { cards, score: scoreValue, progress };
1482
+ };
1483
+ function useClearScore() {
1484
+ const { queryClient } = useSpeakableApi();
1485
+ const mutation = (0, import_react_query2.useMutation)({
1486
+ mutationFn: clearScore,
1487
+ onSettled: (result) => {
1488
+ var _a;
1489
+ queryClient.invalidateQueries({
1490
+ queryKey: scoreQueryKeys.byId((_a = result == null ? void 0 : result.activityId) != null ? _a : "")
1491
+ });
1492
+ }
1493
+ });
1494
+ return {
1495
+ mutationClearScore: mutation
1496
+ };
1497
+ }
1498
+ function useSubmitAssignmentScore({
1499
+ onAssignmentSubmitted,
1500
+ studentName
1501
+ }) {
1502
+ const { queryClient } = useSpeakableApi();
1503
+ const { hasGoogleClassroomGradePassback } = usePermissions_default();
1504
+ const { submitAssignmentToGoogleClassroom } = useGoogleClassroom();
1505
+ const { createNotification: createNotification2 } = useCreateNotification();
1506
+ const mutation = (0, import_react_query2.useMutation)({
1507
+ mutationFn: async ({
1508
+ assignment,
1509
+ userId,
1510
+ cardIds,
1511
+ weights,
1512
+ scores,
1513
+ status
1514
+ }) => {
1515
+ try {
1516
+ const scoreUpdated = await submitAssignmentScore({
1517
+ assignment,
1518
+ userId,
1519
+ cardIds,
1520
+ weights,
1521
+ status,
1522
+ studentName
1523
+ });
1524
+ if (assignment.courseWorkId != null && !assignment.isAssessment && hasGoogleClassroomGradePassback) {
1525
+ await submitAssignmentToGoogleClassroom({
1526
+ assignment,
1527
+ scores
1528
+ });
1529
+ }
1530
+ if (assignment.isAssessment) {
1531
+ createNotification2(SpeakableNotificationTypes.ASSESSMENT_SUBMITTED, assignment);
1532
+ }
1533
+ if (assignment == null ? void 0 : assignment.id) {
1534
+ logSubmitAssignment({
1535
+ courseId: assignment == null ? void 0 : assignment.courseId
1536
+ });
1537
+ }
1538
+ onAssignmentSubmitted(assignment.id);
1539
+ queryClient.setQueryData(scoreQueryKeys.byId(assignment.id), {
1540
+ ...scores,
1541
+ ...scoreUpdated.fieldsUpdated
1542
+ });
1543
+ return {
1544
+ success: true,
1545
+ message: "Score submitted successfully"
1546
+ };
1547
+ } catch (error) {
1548
+ return {
1549
+ success: false,
1550
+ error
1551
+ };
1552
+ }
1553
+ }
1554
+ });
1555
+ return {
1556
+ submitAssignmentScore: mutation.mutateAsync,
1557
+ isLoading: mutation.isPending
1558
+ };
1559
+ }
1560
+ function useSubmitPracticeScore() {
1561
+ const { queryClient } = useSpeakableApi();
1562
+ const mutation = (0, import_react_query2.useMutation)({
1563
+ mutationFn: async ({
1564
+ setId,
1565
+ userId,
1566
+ scores
1567
+ }) => {
1568
+ try {
1569
+ await submitPracticeScore({
1570
+ setId,
1571
+ userId,
1572
+ scores
1573
+ });
1574
+ queryClient.invalidateQueries({
1575
+ queryKey: scoreQueryKeys.byId(setId)
1576
+ });
1577
+ return {
1578
+ success: true,
1579
+ message: "Score submitted successfully"
1580
+ };
1581
+ } catch (error) {
1582
+ return {
1583
+ success: false,
1584
+ error
1585
+ };
1586
+ }
1587
+ }
1588
+ });
1589
+ return {
1590
+ submitPracticeScore: mutation.mutateAsync,
1591
+ isLoading: mutation.isPending
1592
+ };
1593
+ }
1594
+
1595
+ // src/domains/cards/card.hooks.ts
1596
+ var import_react_query3 = require("@tanstack/react-query");
1597
+ var import_react2 = require("react");
1598
+
1599
+ // src/domains/cards/card.constants.ts
1600
+ var FeedbackTypesCard = /* @__PURE__ */ ((FeedbackTypesCard2) => {
1601
+ FeedbackTypesCard2["SuggestedResponse"] = "suggested_response";
1602
+ FeedbackTypesCard2["Wida"] = "wida";
1603
+ FeedbackTypesCard2["GrammarInsights"] = "grammar_insights";
1604
+ FeedbackTypesCard2["Actfl"] = "actfl";
1605
+ FeedbackTypesCard2["ProficiencyLevel"] = "proficiency_level";
1606
+ return FeedbackTypesCard2;
1607
+ })(FeedbackTypesCard || {});
1608
+ var LeniencyCard = /* @__PURE__ */ ((LeniencyCard2) => {
1609
+ LeniencyCard2["CONFIDENCE"] = "confidence";
1610
+ LeniencyCard2["EASY"] = "easy";
1611
+ LeniencyCard2["NORMAL"] = "normal";
1612
+ LeniencyCard2["HARD"] = "hard";
1613
+ return LeniencyCard2;
1614
+ })(LeniencyCard || {});
1615
+ var LENIENCY_OPTIONS = [
1616
+ {
1617
+ label: "Build Confidence - most lenient",
1618
+ value: "confidence" /* CONFIDENCE */
1619
+ },
1620
+ {
1621
+ label: "Very Lenient",
1622
+ value: "easy" /* EASY */
1623
+ },
1624
+ {
1625
+ label: "Normal",
1626
+ value: "normal" /* NORMAL */
1627
+ },
1628
+ {
1629
+ label: "No leniency - most strict",
1630
+ value: "hard" /* HARD */
1631
+ }
1632
+ ];
1633
+ var STUDENT_LEVELS_OPTIONS = [
1634
+ {
1635
+ label: "Beginner",
1636
+ description: "Beginner Level: Just starting out. Can say a few basic words and phrases.",
1637
+ value: "beginner"
1638
+ },
1639
+ {
1640
+ label: "Elementary",
1641
+ description: "Elementary Level: Can understand simple sentences and have very basic conversations.",
1642
+ value: "elementary"
1643
+ },
1644
+ {
1645
+ label: "Intermediate",
1646
+ description: "Intermediate Level: Can talk about everyday topics and handle common situations.",
1647
+ value: "intermediate"
1648
+ },
1649
+ {
1650
+ label: "Advanced",
1651
+ description: "Advanced Level: Can speak and understand with ease, and explain ideas clearly.",
1652
+ value: "advanced"
1653
+ },
1654
+ {
1655
+ label: "Fluent",
1656
+ description: "Fluent Level: Speaks naturally and easily. Can use the language in work or school settings.",
1657
+ value: "fluent"
1658
+ },
1659
+ {
1660
+ label: "Native-like",
1661
+ description: "Native-like Level: Understands and speaks like a native. Can discuss complex ideas accurately.",
1662
+ value: "nativeLike"
1663
+ }
1664
+ ];
1665
+ var BASE_RESPOND_FIELD_VALUES = {
1666
+ title: "",
1667
+ allowRetries: true,
1668
+ respondTime: 180,
1669
+ maxCharacters: 1e3
1670
+ };
1671
+ var BASE_REPEAT_FIELD_VALUES = {
1672
+ repeat: 1
1673
+ };
1674
+ var BASE_MULTIPLE_CHOICE_FIELD_VALUES = {
1675
+ MCQType: "single",
1676
+ answer: ["A"],
1677
+ choices: [
1678
+ { option: "A", value: "Option A" },
1679
+ { option: "B", value: "Option B" },
1680
+ { option: "C", value: "Option C" }
1681
+ ]
1682
+ };
1683
+ var VerificationCardStatus = /* @__PURE__ */ ((VerificationCardStatus2) => {
1684
+ VerificationCardStatus2["VERIFIED"] = "VERIFIED";
1685
+ VerificationCardStatus2["WARNING"] = "WARNING";
1686
+ VerificationCardStatus2["NOT_RECOMMENDED"] = "NOT_RECOMMENDED";
1687
+ VerificationCardStatus2["NOT_WORKING"] = "NOT_WORKING";
1688
+ VerificationCardStatus2["NOT_CHECKED"] = "NOT_CHECKED";
1689
+ return VerificationCardStatus2;
1690
+ })(VerificationCardStatus || {});
1691
+ var CARDS_COLLECTION = "flashcards";
1692
+ var refsCardsFiresotre = {
1693
+ allCards: CARDS_COLLECTION,
1694
+ card: (id) => `${CARDS_COLLECTION}/${id}`
1695
+ };
1696
+
1697
+ // src/domains/cards/services/get-card.service.ts
1698
+ async function _getCard(params) {
1699
+ const ref = refsCardsFiresotre.card(params.cardId);
1700
+ const response = await api.getDoc(ref);
1701
+ if (!response.data) return null;
1702
+ return response.data;
1703
+ }
1704
+ var getCard = withErrorHandler(_getCard, "getCard");
1705
+
1706
+ // src/domains/cards/services/create-card.service.ts
1707
+ var import_uuid = require("uuid");
1708
+
1709
+ // src/domains/cards/card.model.ts
1710
+ var ActivityPageType = /* @__PURE__ */ ((ActivityPageType2) => {
1711
+ ActivityPageType2["READ_REPEAT"] = "READ_REPEAT";
1712
+ ActivityPageType2["VIDEO"] = "VIDEO";
1713
+ ActivityPageType2["TEXT"] = "TEXT";
1714
+ ActivityPageType2["READ_RESPOND"] = "READ_RESPOND";
1715
+ ActivityPageType2["FREE_RESPONSE"] = "FREE_RESPONSE";
1716
+ ActivityPageType2["REPEAT"] = "REPEAT";
1717
+ ActivityPageType2["RESPOND"] = "RESPOND";
1718
+ ActivityPageType2["RESPOND_WRITE"] = "RESPOND_WRITE";
1719
+ ActivityPageType2["TEXT_TO_SPEECH"] = "TEXT_TO_SPEECH";
1720
+ ActivityPageType2["MULTIPLE_CHOICE"] = "MULTIPLE_CHOICE";
1721
+ ActivityPageType2["PODCAST"] = "PODCAST";
1722
+ ActivityPageType2["MEDIA_PAGE"] = "MEDIA_PAGE";
1723
+ ActivityPageType2["WRITE"] = "WRITE";
1724
+ ActivityPageType2["SHORT_ANSWER"] = "SHORT_ANSWER";
1725
+ ActivityPageType2["SHORT_STORY"] = "SHORT_STORY";
1726
+ ActivityPageType2["SPEAK"] = "SPEAK";
1727
+ ActivityPageType2["CONVERSATION"] = "CONVERSATION";
1728
+ ActivityPageType2["CONVERSATION_WRITE"] = "CONVERSATION_WRITE";
1729
+ ActivityPageType2["DIALOGUE"] = "DIALOGUE";
1730
+ ActivityPageType2["INSTRUCTION"] = "INSTRUCTION";
1731
+ ActivityPageType2["LISTEN"] = "LISTEN";
1732
+ ActivityPageType2["READ"] = "READ";
1733
+ ActivityPageType2["ANSWER"] = "ANSWER";
1734
+ return ActivityPageType2;
1735
+ })(ActivityPageType || {});
1736
+ var RESPOND_PAGE_ACTIVITY_TYPES = [
1737
+ "READ_RESPOND" /* READ_RESPOND */,
1738
+ "RESPOND" /* RESPOND */,
1739
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1740
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1741
+ ];
1742
+ var MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES = ["MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */];
1743
+ var REPEAT_PAGE_ACTIVITY_TYPES = ["READ_REPEAT" /* READ_REPEAT */, "REPEAT" /* REPEAT */];
1744
+ var RESPOND_WRITE_PAGE_ACTIVITY_TYPES = [
1745
+ "RESPOND_WRITE" /* RESPOND_WRITE */,
1746
+ "FREE_RESPONSE" /* FREE_RESPONSE */
1747
+ ];
1748
+ var RESPOND_AUDIO_PAGE_ACTIVITY_TYPES = [
1749
+ "RESPOND" /* RESPOND */,
1750
+ "READ_RESPOND" /* READ_RESPOND */
1751
+ ];
1752
+
1753
+ // src/utils/text-utils.ts
1754
+ var import_js_sha1 = __toESM(require("js-sha1"));
1755
+ var purify = (word) => {
1756
+ return word.normalize("NFD").replace(/\/([^" "]*)/g, "").replace(/\([^()]*\)/g, "").replace(/([^()]*)/g, "").replace(/[\u0300-\u036f]/g, "").replace(/[-]/g, " ").replace(/[.,/#!¡¿?؟。,.?$%^&*;:{}=\-_`~()’'…\s]/g, "").replace(/\s\s+/g, " ").toLowerCase().trim();
1757
+ };
1758
+ var cleanString = (words) => {
1759
+ const splitWords = words == null ? void 0 : words.split("+");
1760
+ if (splitWords && splitWords.length === 1) {
1761
+ const newWord = purify(words);
1762
+ return newWord;
1763
+ } else if (splitWords && splitWords.length > 1) {
1764
+ const split = splitWords.map((w) => purify(w));
1765
+ return split;
1766
+ } else {
1767
+ return "";
1768
+ }
1769
+ };
1770
+ var getWordHash = (word, language) => {
1771
+ const cleanedWord = cleanString(word);
1772
+ const wordHash = (0, import_js_sha1.default)(`${language}-${cleanedWord}`);
1773
+ console.log("wordHash core library", wordHash);
1774
+ return wordHash;
1775
+ };
1776
+ function getPhraseLength(phrase, input) {
1777
+ if (Array.isArray(phrase) && phrase.includes(input)) {
1778
+ return phrase[phrase.indexOf(input)].split(" ").length;
1779
+ } else {
1780
+ return phrase ? phrase.split(" ").length : 0;
1781
+ }
1782
+ }
1783
+
1784
+ // src/domains/cards/services/get-card-verification-status.service.ts
1785
+ var charactarLanguages = ["zh", "ja", "ko"];
1786
+ var getVerificationStatus = async (target_text, language) => {
1787
+ if ((target_text == null ? void 0 : target_text.length) < 3 && !charactarLanguages.includes(language)) {
1788
+ return "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1789
+ }
1790
+ const hash = getWordHash(target_text, language);
1791
+ const response = await api.getDoc(`checked-pronunciations/${hash}`);
1792
+ try {
1793
+ if (response.data) {
1794
+ return processRecord(response.data);
1795
+ } else {
1796
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1797
+ }
1798
+ } catch (e) {
1799
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1800
+ }
1801
+ };
1802
+ var processRecord = (data) => {
1803
+ const { pronunciations = 0, fails = 0 } = data;
1804
+ const attempts = pronunciations + fails;
1805
+ const successRate = attempts > 0 ? pronunciations / attempts * 100 : 0;
1806
+ let newStatus = null;
1807
+ if (attempts < 6) {
1808
+ return "NOT_CHECKED" /* NOT_CHECKED */;
1809
+ }
1810
+ if (successRate > 25) {
1811
+ newStatus = "VERIFIED" /* VERIFIED */;
1812
+ } else if (successRate > 10) {
1813
+ newStatus = "WARNING" /* WARNING */;
1814
+ } else if (fails > 20 && successRate < 10 && pronunciations > 1) {
1815
+ newStatus = "NOT_RECOMMENDED" /* NOT_RECOMMENDED */;
1816
+ } else if (pronunciations === 0 && fails > 20) {
1817
+ newStatus = "NOT_WORKING" /* NOT_WORKING */;
1818
+ } else {
1819
+ newStatus = "NOT_CHECKED" /* NOT_CHECKED */;
1820
+ }
1821
+ return newStatus;
1822
+ };
1823
+
1824
+ // src/domains/cards/services/create-card.service.ts
1825
+ async function _createCard({ data }) {
1826
+ const response = await api.addDoc(refsCardsFiresotre.allCards, data);
1827
+ return response;
1828
+ }
1829
+ var createCard = withErrorHandler(_createCard, "createCard");
1830
+ async function _createCards({ cards }) {
1831
+ const { writeBatch: writeBatch2, doc: doc2 } = api.accessHelpers();
1832
+ const batch = writeBatch2();
1833
+ const cardsWithId = [];
1834
+ for (const card of cards) {
1835
+ const cardId = (0, import_uuid.v4)();
1836
+ const ref = doc2(refsCardsFiresotre.card(cardId));
1837
+ const newCardObject = {
1838
+ ...card,
1839
+ id: cardId
1840
+ };
1841
+ if (card.type === "READ_REPEAT" /* READ_REPEAT */ && card.target_text && card.language) {
1842
+ const verificationStatus = await getVerificationStatus(card.target_text, card.language);
1843
+ newCardObject.verificationStatus = verificationStatus || null;
1844
+ }
1845
+ cardsWithId.push(newCardObject);
1846
+ batch.set(ref, newCardObject);
1847
+ }
1848
+ await batch.commit();
1849
+ return cardsWithId;
1850
+ }
1851
+ var createCards = withErrorHandler(_createCards, "createCards");
1852
+
1853
+ // src/domains/cards/card.hooks.ts
1854
+ var cardsQueryKeys = {
1855
+ all: ["cards"],
1856
+ one: (params) => [...cardsQueryKeys.all, params.cardId]
1857
+ };
1858
+ function useCards({
1859
+ cardIds,
1860
+ enabled = true,
1861
+ asObject
1862
+ }) {
1863
+ const queries = (0, import_react_query3.useQueries)({
1864
+ queries: cardIds.map((cardId) => ({
1865
+ enabled: enabled && cardIds.length > 0,
1866
+ queryKey: cardsQueryKeys.one({
1867
+ cardId
1868
+ }),
1869
+ queryFn: () => getCard({ cardId })
1870
+ }))
1871
+ });
1872
+ const cards = queries.map((query2) => query2.data).filter(Boolean);
1873
+ const cardsObject = (0, import_react2.useMemo)(() => {
1874
+ if (!asObject) return null;
1875
+ return cards.reduce((acc, card) => {
1876
+ acc[card.id] = card;
1877
+ return acc;
1878
+ }, {});
1879
+ }, [asObject, cards]);
1880
+ return {
1881
+ cards,
1882
+ cardsObject,
1883
+ cardsQueries: queries
1884
+ };
1885
+ }
1886
+ function useCreateCard() {
1887
+ const { queryClient } = useSpeakableApi();
1888
+ const mutationCreateCard = (0, import_react_query3.useMutation)({
1889
+ mutationFn: createCard,
1890
+ onSuccess: (cardCreated) => {
1891
+ queryClient.invalidateQueries({ queryKey: cardsQueryKeys.one({ cardId: cardCreated.id }) });
1892
+ }
1893
+ });
1894
+ return {
1895
+ mutationCreateCard
1896
+ };
1897
+ }
1898
+ function useCreateCards() {
1899
+ const mutationCreateCards = (0, import_react_query3.useMutation)({
1900
+ mutationFn: createCards
1901
+ });
1902
+ return {
1903
+ mutationCreateCards
1904
+ };
1905
+ }
1906
+ function getCardFromCache({
1907
+ cardId,
1908
+ queryClient
1909
+ }) {
1910
+ return queryClient.getQueryData(cardsQueryKeys.one({ cardId }));
1911
+ }
1912
+ function updateCardInCache({
1913
+ cardId,
1914
+ card,
1915
+ queryClient
1916
+ }) {
1917
+ queryClient.setQueryData(cardsQueryKeys.one({ cardId }), card);
1918
+ }
1919
+ function useGetCard({ cardId, enabled = true }) {
1920
+ const query2 = (0, import_react_query3.useQuery)({
1921
+ queryKey: cardsQueryKeys.one({ cardId }),
1922
+ queryFn: () => getCard({ cardId }),
1923
+ enabled: enabled && !!cardId
1924
+ });
1925
+ return query2;
1926
+ }
1927
+
1928
+ // src/domains/cards/card.repo.ts
1929
+ var createCardRepo = () => {
1930
+ return {
1931
+ createCard,
1932
+ createCards,
1933
+ getCard
1934
+ };
1935
+ };
1936
+
1937
+ // src/domains/cards/utils/check-page-type.ts
1938
+ function checkIsRepeatPage(cardType) {
1939
+ if (cardType === void 0) return false;
1940
+ return REPEAT_PAGE_ACTIVITY_TYPES.includes(cardType);
1941
+ }
1942
+ function checkIsMCPage(cardType) {
1943
+ if (cardType === void 0) return false;
1944
+ return MULTIPLE_CHOICE_PAGE_ACTIVITY_TYPES.includes(cardType);
1945
+ }
1946
+ function checkIsRespondPage(cardType) {
1947
+ if (cardType === void 0) return false;
1948
+ return RESPOND_PAGE_ACTIVITY_TYPES.includes(cardType);
1949
+ }
1950
+ function checkIsRespondWrittenPage(cardType) {
1951
+ if (cardType === void 0) return false;
1952
+ return RESPOND_WRITE_PAGE_ACTIVITY_TYPES.includes(cardType);
1953
+ }
1954
+ function checkIsRespondAudioPage(cardType) {
1955
+ if (cardType === void 0) return false;
1956
+ return RESPOND_AUDIO_PAGE_ACTIVITY_TYPES.includes(cardType);
1957
+ }
1958
+ var checkIsMediaPage = (cardType) => {
1959
+ if (cardType === void 0) return false;
1960
+ return cardType === "MEDIA_PAGE" /* MEDIA_PAGE */;
1961
+ };
1962
+ var checkIsShortAnswerPage = (cardType) => {
1963
+ if (cardType === void 0) return false;
1964
+ return cardType === "SHORT_ANSWER" /* SHORT_ANSWER */;
1965
+ };
1966
+ var checkTypePageActivity = (cardType) => {
1967
+ const isRespondAudio = checkIsRespondAudioPage(cardType);
1968
+ const isRespondWritten = checkIsRespondWrittenPage(cardType);
1969
+ const isRespond = checkIsRespondPage(cardType);
1970
+ const isMC = checkIsMCPage(cardType);
1971
+ const isRepeat = checkIsRepeatPage(cardType);
1972
+ const isMediaPage = checkIsMediaPage(cardType);
1973
+ const isShortAnswer = checkIsShortAnswerPage(cardType);
1974
+ return {
1975
+ isRespondAudio,
1976
+ isRespondWritten,
1977
+ isRespond,
1978
+ isMC,
1979
+ isRepeat,
1980
+ isMediaPage,
1981
+ isShortAnswer
1982
+ };
1983
+ };
1984
+
1985
+ // src/domains/cards/utils/get-page-prompt.ts
1986
+ function getPagePrompt(card) {
1987
+ if (!card) return { has: false, text: "" };
1988
+ const { isMC, isRepeat, isRespond, isShortAnswer } = checkTypePageActivity(card == null ? void 0 : card.type);
1989
+ const hidePrompt = (card == null ? void 0 : card.hidePrompt) === true;
1990
+ if (isRepeat) {
1991
+ return {
1992
+ has: true,
1993
+ text: card == null ? void 0 : card.target_text
1994
+ };
1995
+ }
1996
+ if (isRespond && !hidePrompt) {
1997
+ return {
1998
+ has: true,
1999
+ text: card == null ? void 0 : card.prompt
2000
+ };
2001
+ }
2002
+ if (isMC) {
2003
+ return {
2004
+ has: true,
2005
+ text: card == null ? void 0 : card.question
2006
+ };
2007
+ }
2008
+ if (isShortAnswer && !hidePrompt) {
2009
+ return {
2010
+ has: true,
2011
+ text: card == null ? void 0 : card.prompt
2012
+ };
2013
+ }
2014
+ return {
2015
+ has: false,
2016
+ text: ""
2017
+ };
2018
+ }
2019
+
2020
+ // src/domains/sets/set.hooks.ts
2021
+ var import_react_query4 = require("@tanstack/react-query");
2022
+
2023
+ // src/domains/sets/set.constants.ts
2024
+ var SETS_COLLECTION = "sets";
2025
+ var refsSetsFirestore = {
2026
+ allSets: SETS_COLLECTION,
2027
+ set: (id) => `${SETS_COLLECTION}/${id}`
2028
+ };
2029
+
2030
+ // src/domains/sets/services/get-set.service.ts
2031
+ async function _getSet({ setId }) {
2032
+ const response = await api.getDoc(refsSetsFirestore.set(setId));
2033
+ return response.data;
2034
+ }
2035
+ var getSet = withErrorHandler(_getSet, "getSet");
2036
+
2037
+ // src/domains/sets/set.hooks.ts
2038
+ var setsQueryKeys = {
2039
+ all: ["sets"],
2040
+ one: (params) => [...setsQueryKeys.all, params.setId]
2041
+ };
2042
+ var useSet = ({ setId, enabled }) => {
2043
+ return (0, import_react_query4.useQuery)({
2044
+ queryKey: setsQueryKeys.one({ setId }),
2045
+ queryFn: () => getSet({ setId }),
2046
+ enabled: setId !== void 0 && setId !== "" && enabled
2047
+ });
2048
+ };
2049
+ function getSetFromCache({
2050
+ setId,
2051
+ queryClient
2052
+ }) {
2053
+ if (!setId) return null;
2054
+ return queryClient.getQueryData(setsQueryKeys.one({ setId }));
2055
+ }
2056
+ function updateSetInCache({
2057
+ set,
2058
+ queryClient
2059
+ }) {
2060
+ const { id, ...setData } = set;
2061
+ queryClient.setQueryData(setsQueryKeys.one({ setId: id }), setData);
2062
+ }
2063
+
2064
+ // src/domains/sets/set.repo.ts
2065
+ var createSetRepo = () => {
2066
+ return {
2067
+ getSet
2068
+ };
2069
+ };
2070
+
2071
+ // src/constants/all-langs.json
2072
+ var all_langs_default = {
2073
+ af: "Afrikaans",
2074
+ sq: "Albanian",
2075
+ am: "Amharic",
2076
+ ar: "Arabic",
2077
+ hy: "Armenian",
2078
+ az: "Azerbaijani",
2079
+ eu: "Basque",
2080
+ be: "Belarusian",
2081
+ bn: "Bengali",
2082
+ bs: "Bosnian",
2083
+ bg: "Bulgarian",
2084
+ ca: "Catalan",
2085
+ ceb: "Cebuano",
2086
+ zh: "Chinese",
2087
+ co: "Corsican",
2088
+ hr: "Croatian",
2089
+ cs: "Czech",
2090
+ da: "Danish",
2091
+ nl: "Dutch",
2092
+ en: "English",
2093
+ eo: "Esperanto",
2094
+ et: "Estonian",
2095
+ fi: "Finnish",
2096
+ fr: "French",
2097
+ fy: "Frisian",
2098
+ gl: "Galician",
2099
+ ka: "Georgian",
2100
+ de: "German",
2101
+ el: "Greek",
2102
+ gu: "Gujarati",
2103
+ ht: "Haitian Creole",
2104
+ ha: "Hausa",
2105
+ haw: "Hawaiian",
2106
+ he: "Hebrew",
2107
+ hi: "Hindi",
2108
+ hmn: "Hmong",
2109
+ hu: "Hungarian",
2110
+ is: "Icelandic",
2111
+ ig: "Igbo",
2112
+ id: "Indonesian",
2113
+ ga: "Irish",
2114
+ it: "Italian",
2115
+ ja: "Japanese",
2116
+ jv: "Javanese",
2117
+ kn: "Kannada",
2118
+ kk: "Kazakh",
2119
+ km: "Khmer",
2120
+ ko: "Korean",
2121
+ ku: "Kurdish",
2122
+ ky: "Kyrgyz",
2123
+ lo: "Lao",
2124
+ la: "Latin",
2125
+ lv: "Latvian",
2126
+ lt: "Lithuanian",
2127
+ lb: "Luxembourgish",
2128
+ mk: "Macedonian",
2129
+ mg: "Malagasy",
2130
+ ms: "Malay",
2131
+ ml: "Malayalam",
2132
+ mt: "Maltese",
2133
+ mi: "Maori",
2134
+ mr: "Marathi",
2135
+ mn: "Mongolian",
2136
+ my: "Myanmar (Burmese)",
2137
+ ne: "Nepali",
2138
+ no: "Norwegian",
2139
+ ny: "Nyanja (Chichewa)",
2140
+ ps: "Pashto",
2141
+ fa: "Persian",
2142
+ pl: "Polish",
2143
+ pt: "Portuguese",
2144
+ pa: "Punjabi",
2145
+ ro: "Romanian",
2146
+ ru: "Russian",
2147
+ sm: "Samoan",
2148
+ gd: "Scots Gaelic",
2149
+ sr: "Serbian",
2150
+ st: "Sesotho",
2151
+ sn: "Shona",
2152
+ sd: "Sindhi",
2153
+ si: "Sinhala (Sinhalese)",
2154
+ sk: "Slovak",
2155
+ sl: "Slovenian",
2156
+ so: "Somali",
2157
+ es: "Spanish",
2158
+ su: "Sundanese",
2159
+ sw: "Swahili",
2160
+ sv: "Swedish",
2161
+ tl: "Tagalog (Filipino)",
2162
+ tg: "Tajik",
2163
+ ta: "Tamil",
2164
+ te: "Telugu",
2165
+ th: "Thai",
2166
+ tr: "Turkish",
2167
+ uk: "Ukrainian",
2168
+ ur: "Urdu",
2169
+ uz: "Uzbek",
2170
+ vi: "Vietnamese",
2171
+ cy: "Welsh",
2172
+ xh: "Xhosa",
2173
+ yi: "Yiddish",
2174
+ yo: "Yoruba",
2175
+ zu: "Zulu"
2176
+ };
2177
+
2178
+ // src/utils/ai/get-respond-card-tool.ts
2179
+ var getRespondCardTool = ({
2180
+ language,
2181
+ standard = "actfl"
2182
+ }) => {
2183
+ const lang = all_langs_default[language] || "English";
2184
+ const tool = {
2185
+ tool_choice: {
2186
+ type: "function",
2187
+ function: { name: "get_feedback" }
2188
+ },
2189
+ tools: [
2190
+ {
2191
+ type: "function",
2192
+ function: {
2193
+ name: "get_feedback",
2194
+ description: "Get feedback on a student's response",
2195
+ parameters: {
2196
+ type: "object",
2197
+ required: [
2198
+ "success",
2199
+ "score",
2200
+ "score_justification",
2201
+ "errors",
2202
+ "improvedResponse",
2203
+ "compliments"
2204
+ ],
2205
+ properties: {
2206
+ success: {
2207
+ type: "boolean",
2208
+ 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."
2209
+ },
2210
+ errors: {
2211
+ type: "array",
2212
+ items: {
2213
+ type: "object",
2214
+ required: ["error", "grammar_error_type", "correction", "justification"],
2215
+ properties: {
2216
+ error: {
2217
+ type: "string",
2218
+ description: "The grammatical error in the student's response."
2219
+ },
2220
+ correction: {
2221
+ type: "string",
2222
+ description: "The suggested correction to the error"
2223
+ },
2224
+ justification: {
2225
+ type: "string",
2226
+ description: `An explanation of the rationale behind the suggested correction. WRITE THIS IN ${lang}!`
2227
+ },
2228
+ grammar_error_type: {
2229
+ type: "string",
2230
+ enum: [
2231
+ "subjVerbAgree",
2232
+ "tenseErrors",
2233
+ "articleMisuse",
2234
+ "prepositionErrors",
2235
+ "adjNounAgree",
2236
+ "pronounErrors",
2237
+ "wordOrder",
2238
+ "verbConjugation",
2239
+ "pluralization",
2240
+ "negationErrors",
2241
+ "modalVerbMisuse",
2242
+ "relativeClause",
2243
+ "auxiliaryVerb",
2244
+ "complexSentenceAgreement",
2245
+ "idiomaticExpression",
2246
+ "registerInconsistency",
2247
+ "voiceMisuse"
2248
+ ],
2249
+ 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"
2250
+ }
2251
+ }
2252
+ },
2253
+ 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."
2254
+ },
2255
+ compliments: {
2256
+ type: "array",
2257
+ items: {
2258
+ type: "string"
2259
+ },
2260
+ description: `An array of strings, each representing something the student did well. Each string should be WRITTEN IN ${lang}!`
2261
+ },
2262
+ improvedResponse: {
2263
+ type: "string",
2264
+ description: "An improved response with proper grammar and more detail, if applicable."
2265
+ },
2266
+ score: {
2267
+ type: "number",
2268
+ description: "A score between 0 and 100, reflecting the overall quality of the response"
2269
+ },
2270
+ score_justification: {
2271
+ type: "string",
2272
+ description: "An explanation of the rationale behind the assigned score, considering both accuracy and fluency"
2273
+ }
2274
+ }
2275
+ }
2276
+ }
2277
+ }
2278
+ ]
2279
+ };
2280
+ if (standard === "wida") {
2281
+ const wida_level = {
2282
+ type: "number",
2283
+ enum: [1, 2, 3, 4, 5, 6],
2284
+ 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
2285
+
2286
+ 1 - Entering
2287
+ 2 - Emerging
2288
+ 3 - Developing
2289
+ 4 - Expanding
2290
+ 5 - Bridging
2291
+ 6 - Reaching
2292
+
2293
+ 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.
2294
+ `
2295
+ };
2296
+ const wida_justification = {
2297
+ type: "string",
2298
+ description: `An explanation of the rationale behind the assigned WIDA level of the response, considering both accuracy and fluency. WRITE THIS IN ENGLISH!`
2299
+ };
2300
+ tool.tools[0].function.parameters.required.push("wida_level");
2301
+ tool.tools[0].function.parameters.required.push("wida_justification");
2302
+ tool.tools[0].function.parameters.properties.wida_level = wida_level;
2303
+ tool.tools[0].function.parameters.properties.wida_justification = wida_justification;
2304
+ } else {
2305
+ const actfl_level = {
2306
+ type: "string",
2307
+ enum: ["NL", "NM", "NH", "IL", "IM", "IH", "AL", "AM", "AH", "S", "D"],
2308
+ 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"
2309
+ };
2310
+ const actfl_justification = {
2311
+ type: "string",
2312
+ description: "An explanation of the rationale behind the assigned ACTFL level, considering both accuracy and fluency"
2313
+ };
2314
+ tool.tools[0].function.parameters.required.push("actfl_level");
2315
+ tool.tools[0].function.parameters.required.push("actfl_justification");
2316
+ tool.tools[0].function.parameters.properties.actfl_level = actfl_level;
2317
+ tool.tools[0].function.parameters.properties.actfl_justification = actfl_justification;
2318
+ }
2319
+ return tool;
2320
+ };
2321
+
2322
+ // src/hooks/useActivity.ts
2323
+ var import_react3 = require("react");
2324
+
2325
+ // src/services/add-grading-standard.ts
2326
+ var addGradingStandardLog = async (gradingStandard, userId) => {
2327
+ logGradingStandardLog(gradingStandard);
2328
+ const path = `users/${userId}/grading_standard_logs`;
2329
+ await api.addDoc(path, gradingStandard);
2330
+ };
2331
+
2332
+ // src/hooks/useActivityTracker.ts
2333
+ var import_uuid2 = require("uuid");
2334
+ function useActivityTracker({ userId }) {
2335
+ const trackActivity = async ({
2336
+ activityName,
2337
+ activityType,
2338
+ id = (0, import_uuid2.v4)(),
2339
+ language = ""
2340
+ }) => {
2341
+ if (userId) {
2342
+ const { doc: doc2, serverTimestamp: serverTimestamp2, setDoc: setDoc2 } = api.accessHelpers();
2343
+ const activityRef = doc2(`users/${userId}/activity/${id}`);
2344
+ const timestamp = serverTimestamp2();
2345
+ await setDoc2(activityRef, {
2346
+ name: activityName,
2347
+ type: activityType,
2348
+ lastSeen: timestamp,
2349
+ id,
2350
+ language
2351
+ });
2352
+ }
2353
+ };
2354
+ return {
2355
+ trackActivity
2356
+ };
2357
+ }
2358
+
2359
+ // src/hooks/useActivity.ts
2360
+ function useActivity({
2361
+ id,
2362
+ isAssignment,
2363
+ onAssignmentSubmitted,
2364
+ ltiData
2365
+ }) {
2366
+ var _a, _b;
2367
+ const { queryClient, user } = useSpeakableApi();
2368
+ const userId = user.auth.uid;
2369
+ const assignmentQuery = useAssignment({
2370
+ assignmentId: id,
2371
+ userId,
2372
+ enabled: isAssignment
2373
+ });
2374
+ const activeAssignment = assignmentQuery.data;
2375
+ const setId = isAssignment ? (_a = activeAssignment == null ? void 0 : activeAssignment.setId) != null ? _a : "" : id;
2376
+ const querySet = useSet({ setId });
2377
+ const setData = querySet.data;
2378
+ const assignmentContent = activeAssignment == null ? void 0 : activeAssignment.content;
2379
+ const assignmentWeights = activeAssignment == null ? void 0 : activeAssignment.weights;
2380
+ const setContent = setData == null ? void 0 : setData.content;
2381
+ const setWeights = setData == null ? void 0 : setData.weights;
2382
+ const contentCardsToUse = isAssignment ? assignmentContent != null ? assignmentContent : setContent : setContent;
2383
+ const weightsToUse = isAssignment ? assignmentWeights != null ? assignmentWeights : setWeights : setWeights;
2384
+ const activityId = isAssignment ? (_b = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b : "" : setId;
2385
+ const { cardsObject, cardsQueries, cards } = useCards({
2386
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2387
+ enabled: querySet.isSuccess,
2388
+ asObject: true
2389
+ });
2390
+ const scoreQuery = useScore({
2391
+ isAssignment,
2392
+ activityId: id,
2393
+ userId,
2394
+ courseId: activeAssignment == null ? void 0 : activeAssignment.courseId,
2395
+ googleClassroomUserId: user.profile.googleClassroomUserId,
2396
+ enabled: isAssignment ? assignmentQuery.isSuccess : querySet.isSuccess
2397
+ });
2398
+ const { mutationUpdateScore } = useUpdateScore();
2399
+ const { mutationUpdateCardScore } = useUpdateCardScore({
2400
+ activityId,
2401
+ isAssignment,
2402
+ userId,
2403
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2404
+ weights: weightsToUse != null ? weightsToUse : {}
2405
+ });
2406
+ const { mutationClearScore } = useClearScore();
2407
+ const { submitAssignmentScore: submitAssignmentScore2 } = useSubmitAssignmentScore({
2408
+ onAssignmentSubmitted,
2409
+ studentName: user.profile.displayName
2410
+ });
2411
+ const { submitPracticeScore: submitPracticeScore2 } = useSubmitPracticeScore();
2412
+ const handleUpdateScore = (data) => {
2413
+ mutationUpdateScore.mutate({
2414
+ data,
2415
+ isAssignment,
2416
+ activityId,
2417
+ userId
2418
+ });
2419
+ };
2420
+ const handleUpdateCardScore = (cardId, cardScore) => {
2421
+ mutationUpdateCardScore.mutate({ cardId, cardScore });
2422
+ if (cardScore.proficiency_level) {
2423
+ logGradingStandardEntry({
2424
+ type: cardScore.proficiency_level.standardId,
2425
+ cardId,
2426
+ gradingStandard: cardScore.proficiency_level
2427
+ });
2428
+ } else if (cardScore.wida || cardScore.actfl) {
2429
+ logGradingStandardEntry({
2430
+ type: cardScore.wida ? "wida" : "actfl",
2431
+ cardId,
2432
+ gradingStandard: cardScore.wida || cardScore.actfl || { level: "", justification: "" }
2433
+ });
2434
+ }
2435
+ };
2436
+ const onClearScore = ({
2437
+ cardId,
2438
+ wasCompleted = true
2439
+ }) => {
2440
+ var _a2, _b2;
2441
+ const currentCard = cardsObject == null ? void 0 : cardsObject[cardId];
2442
+ if ((currentCard == null ? void 0 : currentCard.type) === "MULTIPLE_CHOICE" /* MULTIPLE_CHOICE */ || (currentCard == null ? void 0 : currentCard.type) === "READ_REPEAT" /* READ_REPEAT */) {
2443
+ return;
2444
+ }
2445
+ const queryKeys = scoreQueryKeys.byId(activityId);
2446
+ const activeCardScores = (_b2 = (_a2 = queryClient.getQueryData(queryKeys)) == null ? void 0 : _a2.cards) == null ? void 0 : _b2[cardId];
2447
+ if (activeCardScores === void 0) return;
2448
+ mutationClearScore.mutate({
2449
+ isAssignment,
2450
+ activityId,
2451
+ cardScores: activeCardScores,
2452
+ cardId,
2453
+ userId
2454
+ });
2455
+ };
2456
+ const onSubmitScore = async () => {
2457
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i, _j;
2458
+ try {
2459
+ let results;
2460
+ if (isAssignment) {
2461
+ const cardScores = ((_a2 = scoreQuery.data) == null ? void 0 : _a2.cards) || {};
2462
+ const hasPendingReview = Object.values(cardScores).some(
2463
+ (cardScore) => cardScore.status === "pending_review"
2464
+ );
2465
+ results = await submitAssignmentScore2({
2466
+ assignment: assignmentQuery.data,
2467
+ userId,
2468
+ cardIds: contentCardsToUse != null ? contentCardsToUse : [],
2469
+ scores: scoreQuery.data,
2470
+ weights: weightsToUse != null ? weightsToUse : {},
2471
+ status: hasPendingReview ? "PENDING_REVIEW" : "FINALIZED"
2472
+ });
2473
+ if ((_b2 = assignmentQuery.data) == null ? void 0 : _b2.ltiDeeplink) {
2474
+ submitLTIScore({
2475
+ maxPoints: (_c = assignmentQuery.data) == null ? void 0 : _c.maxPoints,
2476
+ score: (_e = (_d = scoreQuery.data) == null ? void 0 : _d.score) != null ? _e : 0,
2477
+ SERVICE_KEY: (_f = ltiData == null ? void 0 : ltiData.serviceKey) != null ? _f : "",
2478
+ lineItemId: (_g = ltiData == null ? void 0 : ltiData.lineItemId) != null ? _g : "",
2479
+ lti_id: (_h = ltiData == null ? void 0 : ltiData.lti_id) != null ? _h : ""
2480
+ });
2481
+ }
2482
+ } else {
2483
+ results = await submitPracticeScore2({
2484
+ setId: (_j = (_i = querySet.data) == null ? void 0 : _i.id) != null ? _j : "",
2485
+ userId,
2486
+ scores: scoreQuery.data
2487
+ });
2488
+ }
2489
+ return results;
2490
+ } catch (error) {
2491
+ return {
2492
+ success: false,
2493
+ error
2494
+ };
2495
+ }
2496
+ };
2497
+ const logGradingStandardEntry = ({
2498
+ cardId,
2499
+ gradingStandard,
2500
+ type
2501
+ }) => {
2502
+ var _a2, _b2, _c, _d, _e, _f, _g, _h, _i;
2503
+ const card = cardsObject == null ? void 0 : cardsObject[cardId];
2504
+ const scoresObject = queryClient.getQueryData(scoreQueryKeys.byId(activityId));
2505
+ const cardScore = (_a2 = scoresObject == null ? void 0 : scoresObject.cards) == null ? void 0 : _a2[cardId];
2506
+ const serverTimestamp2 = api.helpers.serverTimestamp;
2507
+ addGradingStandardLog(
2508
+ {
2509
+ assignmentId: (_b2 = activeAssignment == null ? void 0 : activeAssignment.id) != null ? _b2 : "",
2510
+ courseId: (_c = activeAssignment == null ? void 0 : activeAssignment.courseId) != null ? _c : "",
2511
+ teacherId: (_d = activeAssignment == null ? void 0 : activeAssignment.owners[0]) != null ? _d : "",
2512
+ setId: (_e = setData == null ? void 0 : setData.id) != null ? _e : "",
2513
+ cardId,
2514
+ level: gradingStandard.level,
2515
+ justification: gradingStandard.justification,
2516
+ transcript: (_f = cardScore == null ? void 0 : cardScore.transcript) != null ? _f : "",
2517
+ audioUrl: (_g = cardScore == null ? void 0 : cardScore.audio) != null ? _g : "",
2518
+ prompt: (_h = card == null ? void 0 : card.prompt) != null ? _h : "",
2519
+ responseType: (card == null ? void 0 : card.type) === "RESPOND_WRITE" /* RESPOND_WRITE */ ? "written" : "spoken",
2520
+ type,
2521
+ dateMade: serverTimestamp2(),
2522
+ language: (_i = card == null ? void 0 : card.language) != null ? _i : ""
2523
+ },
2524
+ userId
2525
+ );
2526
+ };
2527
+ (0, import_react3.useEffect)(() => {
2528
+ if (isAssignment) {
2529
+ logOpenAssignment({ assignmentId: id });
2530
+ } else {
2531
+ logOpenActivityPreview({ setId: id });
2532
+ }
2533
+ }, []);
2534
+ useInitActivity({
2535
+ assignment: activeAssignment != null ? activeAssignment : void 0,
2536
+ set: setData != null ? setData : void 0,
2537
+ enabled: !!setData,
2538
+ userId
2539
+ });
2540
+ return {
2541
+ set: {
2542
+ data: setData,
2543
+ query: querySet
2544
+ },
2545
+ cards: {
2546
+ data: cardsObject,
2547
+ query: cardsQueries,
2548
+ cardsArray: cards
2549
+ },
2550
+ assignment: {
2551
+ data: isAssignment ? activeAssignment : void 0,
2552
+ query: assignmentQuery
2553
+ },
2554
+ scores: {
2555
+ data: scoreQuery.data,
2556
+ query: scoreQuery,
2557
+ actions: {
2558
+ update: handleUpdateScore,
2559
+ clear: onClearScore,
2560
+ submit: onSubmitScore,
2561
+ updateCard: handleUpdateCardScore,
2562
+ logGradingStandardEntry
2563
+ }
2564
+ }
2565
+ };
2566
+ }
2567
+ var useInitActivity = ({
2568
+ assignment,
2569
+ set,
2570
+ enabled,
2571
+ userId
2572
+ }) => {
2573
+ const { trackActivity } = useActivityTracker({ userId });
2574
+ const init = () => {
2575
+ var _a, _b, _c, _d, _e, _f, _g;
2576
+ if (!enabled) return;
2577
+ if (!assignment) {
2578
+ trackActivity({
2579
+ activityName: (_a = set == null ? void 0 : set.name) != null ? _a : "",
2580
+ activityType: "set",
2581
+ id: set == null ? void 0 : set.id,
2582
+ language: set == null ? void 0 : set.language
2583
+ });
2584
+ } else if (assignment.name) {
2585
+ trackActivity({
2586
+ activityName: assignment.name,
2587
+ activityType: assignment.isAssessment ? "assessment" : "assignment",
2588
+ id: assignment.id,
2589
+ language: set == null ? void 0 : set.language
2590
+ });
2591
+ }
2592
+ if (set == null ? void 0 : set.public) {
2593
+ (_d = (_c = (_b = api).httpsCallable) == null ? void 0 : _c.call(_b, "onSetOpened")) == null ? void 0 : _d({
2594
+ setId: set.id,
2595
+ language: set.language
2596
+ });
2597
+ }
2598
+ (_g = (_f = (_e = api).httpsCallable) == null ? void 0 : _f.call(_e, "updateAlgoliaIndex")) == null ? void 0 : _g({
2599
+ updatePlays: true,
2600
+ objectID: set == null ? void 0 : set.id
2601
+ });
2602
+ };
2603
+ (0, import_react3.useEffect)(() => {
2604
+ init();
2605
+ }, [set]);
2606
+ };
2607
+ var submitLTIScore = async ({
2608
+ maxPoints,
2609
+ score,
2610
+ SERVICE_KEY,
2611
+ lineItemId,
2612
+ lti_id
2613
+ }) => {
2614
+ var _a, _b, _c;
2615
+ try {
2616
+ if (!SERVICE_KEY || !lineItemId || !lti_id) {
2617
+ throw new Error("Missing required LTI credentials");
2618
+ }
2619
+ const earnedPoints = score ? score / 100 * maxPoints : 0;
2620
+ const { data } = await ((_c = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "submitLTIAssignmentScore")) == null ? void 0 : _c({
2621
+ SERVICE_KEY,
2622
+ scoreData: {
2623
+ lineItemId,
2624
+ userId: lti_id,
2625
+ maxPoints,
2626
+ earnedPoints
2627
+ }
2628
+ }));
2629
+ return { success: true, data };
2630
+ } catch (error) {
2631
+ console.error("Failed to submit LTI score:", error);
2632
+ return {
2633
+ success: false,
2634
+ error: error instanceof Error ? error : new Error("Unknown error occurred")
2635
+ };
2636
+ }
2637
+ };
2638
+
2639
+ // src/hooks/useCredits.ts
2640
+ var import_react_query5 = require("@tanstack/react-query");
2641
+ var creditQueryKeys = {
2642
+ userCredits: (uid) => ["userCredits", uid]
2643
+ };
2644
+ var useUserCredits = () => {
2645
+ const { user } = useSpeakableApi();
2646
+ const email = user.auth.email;
2647
+ const uid = user.auth.uid;
2648
+ const query2 = (0, import_react_query5.useQuery)({
2649
+ queryKey: creditQueryKeys.userCredits(uid),
2650
+ queryFn: () => fetchUserCredits({ uid, email }),
2651
+ enabled: !!uid,
2652
+ refetchInterval: 1e3 * 60 * 5
2653
+ });
2654
+ return {
2655
+ ...query2
2656
+ };
2657
+ };
2658
+ var fetchUserCredits = async ({ uid, email }) => {
2659
+ if (!uid) {
2660
+ throw new Error("User ID is required");
2661
+ }
2662
+ const contractSnap = await api.getDoc(`creditContracts/${uid}`);
2663
+ if (contractSnap.data == null) {
2664
+ return {
2665
+ id: uid,
2666
+ userId: uid,
2667
+ email,
2668
+ effectivePlanId: "free_tier",
2669
+ status: "inactive",
2670
+ isUnlimited: false,
2671
+ creditsAvailable: 100,
2672
+ creditsAllocatedThisPeriod: 100,
2673
+ topOffCreditsAvailable: 0,
2674
+ topOffCreditsTotal: 0,
2675
+ allocationSource: "free_tier",
2676
+ sourceDetails: {},
2677
+ periodStart: null,
2678
+ periodEnd: null,
2679
+ planTermEndTimestamp: null,
2680
+ ownerType: "individual",
2681
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2682
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
2683
+ };
2684
+ }
2685
+ const contractData = contractSnap.data;
2686
+ const monthlyCredits = (contractData == null ? void 0 : contractData.creditsAvailable) || 0;
2687
+ const topOffCredits = (contractData == null ? void 0 : contractData.topOffCreditsAvailable) || 0;
2688
+ const totalCredits = monthlyCredits + topOffCredits;
2689
+ return {
2690
+ id: contractSnap.id,
2691
+ ...contractData,
2692
+ // Add computed total for convenience
2693
+ totalCreditsAvailable: totalCredits
2694
+ };
2695
+ };
2696
+
2697
+ // src/hooks/useOrganizationAccess.ts
2698
+ var import_react_query6 = require("@tanstack/react-query");
2699
+ var useOrganizationAccess = () => {
2700
+ const { user } = useSpeakableApi();
2701
+ const email = user.auth.email;
2702
+ const query2 = (0, import_react_query6.useQuery)({
2703
+ queryKey: ["organizationAccess", email],
2704
+ queryFn: async () => {
2705
+ if (!email) {
2706
+ return {
2707
+ hasUnlimitedAccess: false,
2708
+ subscriptionId: null,
2709
+ organizationId: null,
2710
+ organizationName: null,
2711
+ subscriptionEndDate: null,
2712
+ accessType: "individual"
2713
+ };
2714
+ }
2715
+ return getOrganizationAccess(email);
2716
+ },
2717
+ enabled: !!email,
2718
+ // Only run query if we have a user email
2719
+ staleTime: 5 * 60 * 1e3,
2720
+ // Consider data fresh for 5 minutes
2721
+ gcTime: 10 * 60 * 1e3,
2722
+ // Keep in cache for 10 minutes
2723
+ retry: 2
2724
+ // Retry failed requests twice
2725
+ });
2726
+ return {
2727
+ ...query2
2728
+ };
2729
+ };
2730
+ var getOrganizationAccess = async (email) => {
2731
+ const { limit: limit2, where: where2 } = api.accessQueryConstraints();
2732
+ try {
2733
+ const organizationSnapshot = await api.getDocs(
2734
+ "organizations",
2735
+ where2("members", "array-contains", email),
2736
+ where2("masterSubscriptionStatus", "==", "active"),
2737
+ limit2(1)
2738
+ );
2739
+ if (!organizationSnapshot.empty) {
2740
+ const orgData = organizationSnapshot.data[0];
2741
+ return {
2742
+ hasUnlimitedAccess: true,
2743
+ subscriptionId: orgData == null ? void 0 : orgData.masterSubscriptionId,
2744
+ organizationId: orgData.id,
2745
+ organizationName: orgData.name || "Unknown Organization",
2746
+ subscriptionEndDate: orgData.masterSubscriptionEndDate || null,
2747
+ accessType: "organization"
2748
+ };
2749
+ }
2750
+ const institutionSnapshot = await api.getDocs(
2751
+ "institution_subscriptions",
2752
+ where2("users", "array-contains", email),
2753
+ where2("active", "==", true),
2754
+ limit2(1)
2755
+ );
2756
+ if (!institutionSnapshot.empty) {
2757
+ const institutionData = institutionSnapshot.data[0];
2758
+ const isUnlimited = (institutionData == null ? void 0 : institutionData.plan) === "organization" || (institutionData == null ? void 0 : institutionData.plan) === "school_starter";
2759
+ return {
2760
+ hasUnlimitedAccess: isUnlimited,
2761
+ subscriptionId: institutionData.id,
2762
+ organizationId: institutionData == null ? void 0 : institutionData.institutionId,
2763
+ organizationName: institutionData.name || institutionData.institutionId || "Legacy Institution",
2764
+ subscriptionEndDate: institutionData.endDate || null,
2765
+ accessType: "institution_subscriptions"
2766
+ };
2767
+ }
2768
+ return {
2769
+ hasUnlimitedAccess: false,
2770
+ subscriptionId: null,
2771
+ organizationId: null,
2772
+ organizationName: null,
2773
+ subscriptionEndDate: null,
2774
+ accessType: "individual"
2775
+ };
2776
+ } catch (error) {
2777
+ console.error("Error checking organization access:", error);
2778
+ return {
2779
+ hasUnlimitedAccess: false,
2780
+ subscriptionId: null,
2781
+ organizationId: null,
2782
+ organizationName: null,
2783
+ subscriptionEndDate: null,
2784
+ accessType: "individual"
2785
+ };
2786
+ }
2787
+ };
2788
+
2789
+ // src/hooks/useUpdateStudentVoc.ts
2790
+ var useUpdateStudentVocab = (page) => {
2791
+ const { user } = useSpeakableApi();
2792
+ const currentUserId = user == null ? void 0 : user.auth.uid;
2793
+ if (!page || !currentUserId || !page.target_text || !page.language) {
2794
+ return {
2795
+ studentVocabMarkVoiceSuccess: void 0,
2796
+ studentVocabMarkVoiceFail: void 0
2797
+ };
2798
+ }
2799
+ const getDataObject = () => {
2800
+ var _a, _b;
2801
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2802
+ const language = (_a = page.language) != null ? _a : "en";
2803
+ const word = (_b = page.target_text) != null ? _b : "";
2804
+ const phrase_length = getPhraseLength(word);
2805
+ const wordHash = getWordHash(word, language);
2806
+ const docPath = `users/${currentUserId}/vocab/${wordHash}`;
2807
+ const communityPath = `checked-pronunciations/${wordHash}`;
2808
+ const id = `${language}-${cleanString(word)}`;
2809
+ const data = {
2810
+ id,
2811
+ word,
2812
+ words: (word == null ? void 0 : word.split(" ")) || [],
2813
+ wordHash,
2814
+ language,
2815
+ lastSeen: serverTimestamp2(),
2816
+ phrase_length
2817
+ };
2818
+ return {
2819
+ docPath,
2820
+ communityPath,
2821
+ data
2822
+ };
2823
+ };
2824
+ const markVoiceSuccess = async () => {
2825
+ const { docPath, communityPath, data } = getDataObject();
2826
+ const { increment: increment2 } = api.accessQueryConstraints();
2827
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2828
+ data.voiceSuccess = increment2(1);
2829
+ try {
2830
+ await api.updateDoc(docPath, data);
2831
+ } catch (error) {
2832
+ if (error instanceof Error && error.message === "not-found") {
2833
+ data.firstSeen = serverTimestamp2();
2834
+ await api.setDoc(docPath, data);
2835
+ } else {
2836
+ console.log(error);
2837
+ }
2838
+ }
2839
+ try {
2840
+ data.pronunciations = increment2(1);
2841
+ await api.setDoc(communityPath, data, { merge: true });
2842
+ } catch (error) {
2843
+ console.log(error);
2844
+ }
2845
+ };
2846
+ const markVoiceFail = async () => {
2847
+ const { docPath, communityPath, data } = getDataObject();
2848
+ const { increment: increment2 } = api.accessQueryConstraints();
2849
+ const { serverTimestamp: serverTimestamp2 } = api.accessHelpers();
2850
+ data.voiceFail = increment2(1);
2851
+ try {
2852
+ await api.updateDoc(docPath, data);
2853
+ } catch (error) {
2854
+ if (error instanceof Error && error.message === "not-found") {
2855
+ data.firstSeen = serverTimestamp2();
2856
+ await api.setDoc(docPath, data);
2857
+ } else {
2858
+ console.log(error);
2859
+ }
2860
+ }
2861
+ try {
2862
+ data.fails = increment2(1);
2863
+ await api.setDoc(communityPath, data, { merge: true });
2864
+ } catch (error) {
2865
+ console.log(error);
2866
+ }
2867
+ };
2868
+ return {
2869
+ studentVocabMarkVoiceSuccess: markVoiceSuccess,
2870
+ studentVocabMarkVoiceFail: markVoiceFail
2871
+ };
2872
+ };
2873
+
2874
+ // src/hooks/useActivityFeedbackAccess.ts
2875
+ var import_react_query7 = require("@tanstack/react-query");
2876
+ var activityFeedbackAccessQueryKeys = {
2877
+ activityFeedbackAccess: (args) => ["activityFeedbackAccess", ...Object.values(args)]
2878
+ };
2879
+ var useActivityFeedbackAccess = ({
2880
+ aiEnabled = false,
2881
+ isActivityRoute = false
2882
+ }) => {
2883
+ var _a, _b, _c;
2884
+ const { user } = useSpeakableApi();
2885
+ const uid = user.auth.uid;
2886
+ const isTeacher = (_a = user.profile) == null ? void 0 : _a.isTeacher;
2887
+ const isStudent = (_b = user.profile) == null ? void 0 : _b.isStudent;
2888
+ const userRoles = ((_c = user.profile) == null ? void 0 : _c.roles) || [];
2889
+ const query2 = (0, import_react_query7.useQuery)({
2890
+ queryKey: activityFeedbackAccessQueryKeys.activityFeedbackAccess({
2891
+ aiEnabled,
2892
+ isActivityRoute
2893
+ }),
2894
+ queryFn: async () => {
2895
+ var _a2, _b2, _c2;
2896
+ if (!uid) {
2897
+ return {
2898
+ canAccessFeedback: false,
2899
+ reason: "Missing user ID",
2900
+ isUnlimited: false,
2901
+ accessType: "none"
2902
+ };
2903
+ }
2904
+ try {
2905
+ if (aiEnabled) {
2906
+ return {
2907
+ canAccessFeedback: true,
2908
+ reason: "AI feedback enabled",
2909
+ isUnlimited: true,
2910
+ accessType: "ai_enabled"
2911
+ };
2912
+ }
2913
+ if (isTeacher || userRoles.includes("ADMIN")) {
2914
+ return {
2915
+ canAccessFeedback: true,
2916
+ reason: "Teacher preview access",
2917
+ isUnlimited: true,
2918
+ accessType: "teacher_preview"
2919
+ };
2920
+ }
2921
+ if (isStudent && isActivityRoute) {
2922
+ try {
2923
+ const result = await ((_c2 = (_b2 = (_a2 = api).httpsCallable) == null ? void 0 : _b2.call(_a2, "checkStudentTeacherPlan")) == null ? void 0 : _c2({
2924
+ studentId: uid
2925
+ }));
2926
+ const planCheckResult = result.data;
2927
+ if (planCheckResult.canAccessFeedback) {
2928
+ return {
2929
+ canAccessFeedback: true,
2930
+ reason: planCheckResult.reason || "Student access via teacher with active plan",
2931
+ isUnlimited: planCheckResult.hasTeacherWithUnlimitedAccess,
2932
+ accessType: "student_with_teacher_plan"
2933
+ };
2934
+ } else {
2935
+ return {
2936
+ canAccessFeedback: false,
2937
+ reason: planCheckResult.reason || "No teacher with active plan found",
2938
+ isUnlimited: false,
2939
+ accessType: "none"
2940
+ };
2941
+ }
2942
+ } catch (error) {
2943
+ console.error("Error checking student teacher plan:", error);
2944
+ return {
2945
+ canAccessFeedback: false,
2946
+ reason: "Error checking teacher plans",
2947
+ isUnlimited: false,
2948
+ accessType: "none"
2949
+ };
2950
+ }
2951
+ }
2952
+ return {
2953
+ canAccessFeedback: false,
2954
+ reason: "No access permissions found for current context",
2955
+ isUnlimited: false,
2956
+ accessType: "none"
2957
+ };
2958
+ } catch (error) {
2959
+ console.error("Error checking activity feedback access:", error);
2960
+ return {
2961
+ canAccessFeedback: false,
2962
+ reason: "Error checking access permissions",
2963
+ isUnlimited: false,
2964
+ accessType: "none"
2965
+ };
2966
+ }
2967
+ },
2968
+ enabled: !!uid,
2969
+ staleTime: 5 * 60 * 1e3,
2970
+ // 5 minutes
2971
+ gcTime: 10 * 60 * 1e3
2972
+ // 10 minutes
2973
+ });
2974
+ return {
2975
+ ...query2
2976
+ };
2977
+ };
2978
+
2979
+ // src/hooks/useOpenAI.ts
2980
+ var useBaseOpenAI = ({
2981
+ onTranscriptSuccess,
2982
+ onTranscriptError,
2983
+ onCompletionSuccess,
2984
+ onCompletionError,
2985
+ aiEnabled,
2986
+ submitAudioResponse,
2987
+ uploadAudioAndGetTranscript
2988
+ }) => {
2989
+ const { user, queryClient } = useSpeakableApi();
2990
+ const currentUserId = user.auth.uid;
2991
+ const { data: feedbackAccess } = useActivityFeedbackAccess({
2992
+ aiEnabled
2993
+ });
2994
+ const getTranscript = async (audioUrl, language) => {
2995
+ var _a, _b;
2996
+ try {
2997
+ const getAssemblyAITranscript = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "transcribeAssemblyAIAudio");
2998
+ const response = await (getAssemblyAITranscript == null ? void 0 : getAssemblyAITranscript({
2999
+ audioUrl,
3000
+ language
3001
+ }));
3002
+ console.log("response (getTranscript) from getAssemblyAITranscript", {
3003
+ response,
3004
+ audioUrl,
3005
+ language,
3006
+ getAssemblyAITranscript
3007
+ });
3008
+ const transcript = response == null ? void 0 : response.data;
3009
+ onTranscriptSuccess(transcript);
3010
+ return transcript;
3011
+ } catch (error) {
3012
+ console.log("error", error);
3013
+ onTranscriptError({
3014
+ type: "TRANSCRIPT",
3015
+ message: (error == null ? void 0 : error.message) || "Error getting transcript"
3016
+ });
3017
+ throw new Error(error);
3018
+ }
3019
+ };
3020
+ const getFreeResponseCompletion = async (messages, isFreeResponse, feedbackLanguage, gradingStandard = "actfl") => {
3021
+ var _a, _b, _c, _d, _e;
3022
+ const responseTool = getRespondCardTool({
3023
+ language: feedbackLanguage,
3024
+ standard: gradingStandard
3025
+ });
3026
+ try {
3027
+ const createChatCompletion = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "createChatCompletion");
3028
+ const {
3029
+ data: {
3030
+ response,
3031
+ prompt_tokens = 0,
3032
+ completion_tokens = 0,
3033
+ success: aiSuccess = false
3034
+ // the AI was able to generate a response
3035
+ }
3036
+ } = await (createChatCompletion == null ? void 0 : createChatCompletion({
3037
+ chat: {
3038
+ model: isFreeResponse ? "gpt-4-1106-preview" : "gpt-3.5-turbo-1106",
3039
+ messages,
3040
+ temperature: 0.7,
3041
+ ...responseTool
3042
+ },
3043
+ type: isFreeResponse ? "LONG_RESPONSE" : "SHORT_RESPONSE"
3044
+ }));
3045
+ 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) || "{}");
3046
+ const result = {
3047
+ ...functionArguments,
3048
+ prompt_tokens,
3049
+ completion_tokens,
3050
+ aiSuccess
3051
+ };
3052
+ onCompletionSuccess(result);
3053
+ return result;
3054
+ } catch (error) {
3055
+ onCompletionError({
3056
+ type: "COMPLETION",
3057
+ message: (error == null ? void 0 : error.message) || "Error getting completion"
3058
+ });
3059
+ throw new Error(error);
3060
+ }
3061
+ };
3062
+ const getFeedback = async ({
3063
+ cardId,
3064
+ language = "en",
3065
+ // required
3066
+ writtenResponse = null,
3067
+ // if the type = RESPOND_WRITE
3068
+ audio = null,
3069
+ autoGrade = true,
3070
+ file = null
3071
+ }) => {
3072
+ try {
3073
+ if (!(feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback)) {
3074
+ const result = {
3075
+ noFeedbackAvailable: true,
3076
+ success: true,
3077
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3078
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3079
+ };
3080
+ onCompletionSuccess(result);
3081
+ return result;
3082
+ }
3083
+ let transcript;
3084
+ if (writtenResponse) {
3085
+ transcript = writtenResponse;
3086
+ onTranscriptSuccess(writtenResponse);
3087
+ } else if (typeof audio === "string" && file) {
3088
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3089
+ transcript = await getTranscript(audio, language);
3090
+ onTranscriptSuccess(transcript);
3091
+ } else {
3092
+ console.info(
3093
+ `Transcript not available: ${(feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access"}`
3094
+ );
3095
+ }
3096
+ } else {
3097
+ transcript = await uploadAudioAndGetTranscript(audio || "", language);
3098
+ }
3099
+ if (feedbackAccess == null ? void 0 : feedbackAccess.canAccessFeedback) {
3100
+ const results = await getAIResponse({
3101
+ cardId,
3102
+ transcript: transcript || ""
3103
+ });
3104
+ let output = results;
3105
+ if (!autoGrade) {
3106
+ output = {
3107
+ ...output,
3108
+ noFeedbackAvailable: true,
3109
+ success: true
3110
+ };
3111
+ }
3112
+ onCompletionSuccess(output);
3113
+ return output;
3114
+ } else {
3115
+ const result = {
3116
+ noFeedbackAvailable: true,
3117
+ success: true,
3118
+ reason: (feedbackAccess == null ? void 0 : feedbackAccess.reason) || "No feedback access",
3119
+ accessType: (feedbackAccess == null ? void 0 : feedbackAccess.accessType) || "none"
3120
+ };
3121
+ onCompletionSuccess(result);
3122
+ return result;
3123
+ }
3124
+ } catch (error) {
3125
+ console.error("Error getting feedback:", error);
3126
+ throw new Error(error);
3127
+ }
3128
+ };
3129
+ const getAIResponse = async ({ cardId, transcript }) => {
3130
+ var _a, _b, _c, _d, _e;
3131
+ try {
3132
+ const getGeminiFeedback = (_b = (_a = api).httpsCallable) == null ? void 0 : _b.call(_a, "callGetFeedback");
3133
+ const getProficiencyEstimate = (_d = (_c = api).httpsCallable) == null ? void 0 : _d.call(_c, "getProficiencyEstimate");
3134
+ const card = getCardFromCache({
3135
+ cardId,
3136
+ queryClient
3137
+ });
3138
+ let feedbackData;
3139
+ let proficiencyData = {};
3140
+ if (card && card.grading_method === "manual") {
3141
+ } else if (card && card.grading_method !== "standards_based") {
3142
+ const [geminiResult, proficiencyResult] = await Promise.all([
3143
+ getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3144
+ cardId,
3145
+ studentId: currentUserId,
3146
+ studentResponse: transcript
3147
+ }),
3148
+ getProficiencyEstimate == null ? void 0 : getProficiencyEstimate({
3149
+ cardId,
3150
+ studentId: currentUserId,
3151
+ studentResponse: transcript
3152
+ })
3153
+ ]);
3154
+ proficiencyData = (proficiencyResult == null ? void 0 : proficiencyResult.data) || {};
3155
+ feedbackData = {
3156
+ ...(_e = geminiResult == null ? void 0 : geminiResult.data) != null ? _e : {},
3157
+ // @ts-ignore
3158
+ proficiency_level: (proficiencyData == null ? void 0 : proficiencyData.proficiency_level) || null
3159
+ };
3160
+ } else {
3161
+ const geminiResult = await (getGeminiFeedback == null ? void 0 : getGeminiFeedback({
3162
+ cardId,
3163
+ studentId: currentUserId,
3164
+ studentResponse: transcript
3165
+ }));
3166
+ feedbackData = geminiResult == null ? void 0 : geminiResult.data;
3167
+ }
3168
+ const results = {
3169
+ ...feedbackData,
3170
+ // ...proficiencyData,
3171
+ aiSuccess: true,
3172
+ promptSuccess: (feedbackData == null ? void 0 : feedbackData.success) || false,
3173
+ transcript
3174
+ };
3175
+ return results;
3176
+ } catch (error) {
3177
+ onCompletionError({
3178
+ type: "AI_FEEDBACK",
3179
+ message: (error == null ? void 0 : error.message) || "Error getting ai feedback"
3180
+ });
3181
+ throw new Error(error);
3182
+ }
3183
+ };
3184
+ return {
3185
+ submitAudioResponse,
3186
+ uploadAudioAndGetTranscript,
3187
+ getTranscript,
3188
+ getFreeResponseCompletion,
3189
+ getFeedback
3190
+ };
3191
+ };
3192
+
3193
+ // src/lib/create-firebase-client-native.ts
3194
+ var import_firestore = require("@react-native-firebase/firestore");
3195
+
3196
+ // src/lib/create-firebase-client.ts
3197
+ function createFsClientBase({
3198
+ db,
3199
+ helpers,
3200
+ httpsCallable,
3201
+ logEvent
3202
+ }) {
3203
+ const dbAsFirestore = db;
3204
+ api.initialize({
3205
+ db: dbAsFirestore,
3206
+ helpers,
3207
+ httpsCallable,
3208
+ logEvent
3209
+ });
3210
+ return {
3211
+ assignmentRepo: createAssignmentRepo(),
3212
+ cardRepo: createCardRepo()
3213
+ };
3214
+ }
3215
+
3216
+ // src/lib/create-firebase-client-native.ts
3217
+ var createFsClientNative = ({ db, httpsCallable, logEvent }) => {
3218
+ return createFsClientBase({
3219
+ db,
3220
+ httpsCallable,
3221
+ logEvent,
3222
+ helpers: {
3223
+ getDoc: import_firestore.getDoc,
3224
+ getDocs: import_firestore.getDocs,
3225
+ addDoc: import_firestore.addDoc,
3226
+ setDoc: import_firestore.setDoc,
3227
+ updateDoc: import_firestore.updateDoc,
3228
+ deleteDoc: import_firestore.deleteDoc,
3229
+ runTransaction: import_firestore.runTransaction,
3230
+ writeBatch: import_firestore.writeBatch,
3231
+ doc: import_firestore.doc,
3232
+ collection: import_firestore.collection,
3233
+ query: import_firestore.query,
3234
+ serverTimestamp: import_firestore.serverTimestamp,
3235
+ orderBy: import_firestore.orderBy,
3236
+ limit: import_firestore.limit,
3237
+ startAt: import_firestore.startAt,
3238
+ startAfter: import_firestore.startAfter,
3239
+ endAt: import_firestore.endAt,
3240
+ endBefore: import_firestore.endBefore,
3241
+ where: import_firestore.where,
3242
+ increment: import_firestore.increment
3243
+ }
3244
+ });
3245
+ };
3246
+ //# sourceMappingURL=index.native.native.cjs.map