Download src/components/tuition/chapter-dashboard/classSessionProgress.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 5.8 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/tuition/chapter-dashboard/classSessionProgress.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/components/tuition/chapter-dashboard/classSessionProgress.ts
-
curl -L -o classSessionProgress.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/components/tuition/chapter-dashboard/classSessionProgress.ts
5.8 kB
| import type { TuitionLessonStepId } from "./classSessionData"; | |
| import { tuitionLessonSteps } from "./classSessionData"; | |
| import type { TuitionSessionData } from "./sessionData"; | |
| /** | |
| * Tuition class session progress. | |
| * | |
| * The 11-step guided class (TuitionClassSessionView) held all progress in | |
| * ephemeral React state, so a refresh lost everything. This persists the | |
| * resumable parts keyed by the class session, so /tuition restores where the | |
| * student left off. localStorage is the offline source of truth; the backend | |
| * (classSessionProgressApi) syncs it across devices. | |
| */ | |
| const VALID_STEP_IDS = new Set(tuitionLessonSteps.map((step) => step.id)); | |
| /** Map legacy step ids (e.g. "recap") onto the current continuous flow. */ | |
| export function normalizeLessonStepId(stepId: string | undefined | null): TuitionLessonStepId { | |
| if (stepId === "recap") return "save-revision"; | |
| if (stepId && VALID_STEP_IDS.has(stepId as TuitionLessonStepId)) { | |
| return stepId as TuitionLessonStepId; | |
| } | |
| return tuitionLessonSteps[0].id; | |
| } | |
| export type ClassSessionProgressState = { | |
| started: boolean; | |
| activeStepId: TuitionLessonStepId; | |
| maxUnlockedIndex: number; | |
| recallAnswer: string; | |
| recallSubmitted: boolean; | |
| practiceAnswer: string; | |
| practiceSubmitted: boolean; | |
| quizAnswers: Record<string, string>; | |
| quizSubmitted: boolean; | |
| revisionSaved: boolean; | |
| trickSaved: boolean; | |
| assessmentAttemptId?: string | null; | |
| lessonProgressId?: string | null; | |
| /** Steps the student explicitly completed in the class flow (not mastery). */ | |
| completedStepIds?: TuitionLessonStepId[]; | |
| /** Authoritative session id/revision when this is an authenticated class. */ | |
| tuitionSessionId?: string | null; | |
| tuitionSessionRevision?: number | null; | |
| writtenAttemptId?: string | null; | |
| /** Number of authoritative written submissions for this class. */ | |
| writtenAttemptCount?: number; | |
| /** Adaptive checkpoint loop state (runId keeps replays idempotent). */ | |
| checkpointState?: { | |
| runId: string; | |
| questions: Record< | |
| string, | |
| { | |
| phase: "answering" | "hint_retry" | "explained" | "transfer" | "done"; | |
| attempt: number; | |
| correct: boolean | null; | |
| transferCorrect: boolean | null; | |
| errorCategory: string | null; | |
| } | |
| >; | |
| } | null; | |
| }; | |
| export type ClassSessionProgress = ClassSessionProgressState & { | |
| classSessionId: string; | |
| updatedAt: string; | |
| }; | |
| export const CLASS_SESSION_PROGRESS_PREFIX = "docdoe:tuition-class-progress:"; | |
| type SessionIdParts = Pick<TuitionSessionData, "board" | "subject" | "chapterId" | "missionId">; | |
| /** Stable id for a class session, used as the persistence key. */ | |
| export function classSessionId(session: SessionIdParts): string { | |
| return [session.board ?? "", session.subject ?? "", session.chapterId ?? "chapter", session.missionId ?? "mission"] | |
| .map((part) => part.trim()) | |
| .join("|"); | |
| } | |
| export function readClassSessionProgress(id: string): ClassSessionProgress | null { | |
| if (typeof window === "undefined") return null; | |
| try { | |
| const raw = window.localStorage.getItem(CLASS_SESSION_PROGRESS_PREFIX + id); | |
| return raw ? (JSON.parse(raw) as ClassSessionProgress) : null; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| export function writeClassSessionProgress(id: string, state: ClassSessionProgressState): ClassSessionProgress { | |
| const payload: ClassSessionProgress = { ...state, classSessionId: id, updatedAt: new Date().toISOString() }; | |
| if (typeof window !== "undefined") { | |
| try { | |
| window.localStorage.setItem(CLASS_SESSION_PROGRESS_PREFIX + id, JSON.stringify(payload)); | |
| } catch { | |
| // Storage is a convenience layer; the backend copy remains authoritative. | |
| } | |
| } | |
| return payload; | |
| } | |
| /** Merge a stored snapshot into the default state (used to seed React state). */ | |
| export function applyClassSessionProgress( | |
| defaults: ClassSessionProgressState, | |
| saved: ClassSessionProgress | null, | |
| ): ClassSessionProgressState { | |
| if (!saved) return defaults; | |
| const activeStepId = normalizeLessonStepId(saved.activeStepId ?? defaults.activeStepId); | |
| const maxStepIndex = Math.max(0, tuitionLessonSteps.length - 1); | |
| const maxUnlockedIndex = Math.max( | |
| 0, | |
| Math.min(maxStepIndex, saved.maxUnlockedIndex ?? defaults.maxUnlockedIndex), | |
| ); | |
| // Empty practice answers are never treated as submitted after refresh. | |
| const practiceAnswer = saved.practiceAnswer ?? defaults.practiceAnswer; | |
| const practiceSubmitted = | |
| Boolean(saved.practiceSubmitted ?? defaults.practiceSubmitted) && practiceAnswer.trim().length > 0; | |
| return { | |
| started: saved.started ?? defaults.started, | |
| activeStepId, | |
| maxUnlockedIndex, | |
| recallAnswer: saved.recallAnswer ?? defaults.recallAnswer, | |
| recallSubmitted: saved.recallSubmitted ?? defaults.recallSubmitted, | |
| practiceAnswer, | |
| practiceSubmitted, | |
| quizAnswers: saved.quizAnswers ?? defaults.quizAnswers, | |
| quizSubmitted: saved.quizSubmitted ?? defaults.quizSubmitted, | |
| revisionSaved: saved.revisionSaved ?? defaults.revisionSaved, | |
| trickSaved: saved.trickSaved ?? defaults.trickSaved, | |
| assessmentAttemptId: | |
| saved.assessmentAttemptId ?? defaults.assessmentAttemptId ?? null, | |
| lessonProgressId: | |
| saved.lessonProgressId ?? defaults.lessonProgressId ?? null, | |
| completedStepIds: | |
| saved.completedStepIds ?? defaults.completedStepIds ?? [], | |
| tuitionSessionId: | |
| saved.tuitionSessionId ?? defaults.tuitionSessionId ?? null, | |
| tuitionSessionRevision: | |
| saved.tuitionSessionRevision ?? defaults.tuitionSessionRevision ?? null, | |
| writtenAttemptId: | |
| saved.writtenAttemptId ?? defaults.writtenAttemptId ?? null, | |
| writtenAttemptCount: | |
| saved.writtenAttemptCount ?? defaults.writtenAttemptCount ?? 0, | |
| checkpointState: saved.checkpointState ?? defaults.checkpointState ?? null, | |
| }; | |
| } | |