Download src/lib/api/progress-sync.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 3.62 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/progress-sync.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/api/progress-sync.ts
-
curl -L -o progress-sync.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/progress-sync.ts
3.62 kB
| import { apiGet, apiPost } from "@/lib/api/client"; | |
| import type { ActivityEvent, ActivityKind } from "@/lib/student-activity"; | |
| type ApiEnvelope<T> = { | |
| success: boolean; | |
| data: T; | |
| meta: { | |
| timestamp: string; | |
| message?: string; | |
| }; | |
| }; | |
| export type ProgressSyncEvent = { | |
| event_id: string; | |
| event_type: ActivityKind; | |
| title: string; | |
| detail?: string; | |
| subject?: string; | |
| chapter_id?: string; | |
| mission_id?: string; | |
| occurred_at: string; | |
| sample: boolean; | |
| }; | |
| export type ProgressSyncResult = { | |
| synced: boolean; | |
| user_id: string; | |
| events_received: number; | |
| events_stored: number; | |
| duplicates: number; | |
| }; | |
| export type ProgressSyncSummary = { | |
| user_id: string; | |
| total_sessions: number; | |
| total_events: number; | |
| sample_events: number; | |
| total_duration_seconds: number; | |
| by_type: Partial<Record<ActivityKind, number>>; | |
| last_activity_at: string | null; | |
| recent_events: ActivityEvent[]; | |
| }; | |
| const activityKinds = new Set<ActivityKind>([ | |
| "question_asked", | |
| "lesson_started", | |
| "lesson_completed", | |
| "note_saved", | |
| "quiz_attempted", | |
| "demo_class_completed", | |
| ]); | |
| function optionalString(value: unknown): string | undefined { | |
| return typeof value === "string" && value.trim() ? value : undefined; | |
| } | |
| export function toProgressSyncEvent(event: ActivityEvent): ProgressSyncEvent { | |
| return { | |
| event_id: event.id, | |
| event_type: event.kind, | |
| title: event.title, | |
| ...(event.detail ? { detail: event.detail } : {}), | |
| ...(event.subject ? { subject: event.subject } : {}), | |
| ...(event.chapterId ? { chapter_id: event.chapterId } : {}), | |
| ...(event.missionId ? { mission_id: event.missionId } : {}), | |
| occurred_at: event.at, | |
| sample: Boolean(event.sample), | |
| }; | |
| } | |
| function normalizeRemoteEvent(value: unknown): ActivityEvent | null { | |
| if (!value || typeof value !== "object" || Array.isArray(value)) return null; | |
| const row = value as Record<string, unknown>; | |
| if ( | |
| typeof row.id !== "string" || | |
| typeof row.kind !== "string" || | |
| !activityKinds.has(row.kind as ActivityKind) || | |
| typeof row.title !== "string" || | |
| typeof row.at !== "string" || | |
| Number.isNaN(Date.parse(row.at)) | |
| ) { | |
| return null; | |
| } | |
| const kind = row.kind as ActivityKind; | |
| const sample = row.sample === true; | |
| if (kind === "demo_class_completed" && !sample) return null; | |
| return { | |
| id: row.id, | |
| kind, | |
| title: row.title, | |
| at: row.at, | |
| ...(optionalString(row.detail) ? { detail: optionalString(row.detail) } : {}), | |
| ...(optionalString(row.subject) ? { subject: optionalString(row.subject) } : {}), | |
| ...(optionalString(row.chapterId) ? { chapterId: optionalString(row.chapterId) } : {}), | |
| ...(optionalString(row.missionId) ? { missionId: optionalString(row.missionId) } : {}), | |
| ...(kind === "demo_class_completed" | |
| ? { provenance: "demo" as const, sample: true } | |
| : sample | |
| ? { sample: true } | |
| : {}), | |
| }; | |
| } | |
| export async function syncProgressEvents( | |
| events: ActivityEvent[], | |
| ): Promise<ProgressSyncResult> { | |
| const response = await apiPost< | |
| ApiEnvelope<ProgressSyncResult>, | |
| { events: ProgressSyncEvent[] } | |
| >("/sync/progress", { | |
| events: events.map(toProgressSyncEvent), | |
| }); | |
| return response.data; | |
| } | |
| export async function getSyncedProgress(): Promise<ProgressSyncSummary> { | |
| const response = | |
| await apiGet<ApiEnvelope<Omit<ProgressSyncSummary, "recent_events"> & { | |
| recent_events: unknown[]; | |
| }>>("/sync/progress"); | |
| return { | |
| ...response.data, | |
| recent_events: response.data.recent_events | |
| .map(normalizeRemoteEvent) | |
| .filter((event): event is ActivityEvent => event !== null), | |
| }; | |
| } | |