@speakableio/core 1.0.0 → 1.0.1

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