| import { UserProfile, Exam, Question, Attempt, StudentGroup } from '../types'; |
|
|
| export class DataService { |
| private static authFetch(url: string, init: RequestInit = {}): Promise<Response> { |
| const token = localStorage.getItem('local_user_token'); |
| const headers = { |
| ...(init.headers || {}), |
| } as Record<string, string>; |
| |
| if (token) { |
| headers['Authorization'] = `Bearer ${token}`; |
| } |
| |
| return fetch(url, { |
| ...init, |
| headers |
| }); |
| } |
|
|
| |
| static async getProfile(): Promise<UserProfile | null> { |
| try { |
| const response = await this.authFetch('/api/auth/me'); |
| if (!response.ok) return null; |
| return response.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| static async getAllStudents(): Promise<UserProfile[]> { |
| try { |
| const response = await this.authFetch('/api/students'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| |
| static async createQuestion(question: Partial<Question>): Promise<Question> { |
| const response = await this.authFetch('/api/questions/create', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(question) |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to create question"); |
| } |
| return response.json(); |
| } |
|
|
| |
| static async startAiCompilationJob(text: string): Promise<{ jobId: string }> { |
| const response = await this.authFetch('/api/ai/jobs/start', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ text }) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Failed to start AI compilation workspace"); |
| } |
| return response.json(); |
| } |
|
|
| |
| static async getAiJobStatus(jobId: string): Promise<any> { |
| const response = await this.authFetch(`/api/ai/jobs/status/${jobId}`, { |
| method: 'GET' |
| }); |
| if (!response.ok) throw new Error("Failed to consult background job telemetry"); |
| return response.json(); |
| } |
|
|
| |
| static async parseDocument(text: string, model?: string): Promise<{ questions: Question[], auditReport?: any, modelUsed?: string }> { |
| const { jobId } = await this.startAiCompilationJob(text); |
| while (true) { |
| await new Promise(resolve => setTimeout(resolve, 1000)); |
| const job = await this.getAiJobStatus(jobId); |
| if (job.status === 'completed') { |
| return { |
| questions: job.result.questions, |
| auditReport: job.result.auditReport, |
| modelUsed: 'meta/llama-3.1-8b-instruct + meta/llama-3.1-70b-instruct' |
| }; |
| } |
| if (job.status === 'failed') { |
| throw new Error(job.error || "Background parsing pipeline returned a fatal error"); |
| } |
| } |
| } |
|
|
| |
| static async validateQuestions(questions: Question[]): Promise<{ examQualityScore: number; warnings: any[]; duplicates: any[] }> { |
| const response = await this.authFetch('/api/ai/validate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ questions }) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Academic validation report generation failed"); |
| } |
| return response.json(); |
| } |
|
|
| |
| static async explainAnalytics(metrics: any): Promise<{ summary: string; strengths: string[]; weaknesses: string[]; recommendations: string[] }> { |
| const response = await this.authFetch('/api/ai/analytics-explain', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ metrics }) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Analytics explain compiler failed"); |
| } |
| return response.json(); |
| } |
|
|
| |
| static async askAiAssistant(prompt: string, context?: any): Promise<{ response: string; modelUsed: string }> { |
| const response = await this.authFetch('/api/ai/assistant', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ prompt, context }) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Academic Assistant is currently unresponsive"); |
| } |
| return response.json(); |
| } |
|
|
| static async getTeacherQuestions(): Promise<Question[]> { |
| try { |
| const response = await this.authFetch('/api/questions/list'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| static async deleteQuestion(id: string): Promise<void> { |
| const response = await this.authFetch(`/api/questions/delete/${id}`, { |
| method: 'DELETE' |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to delete question"); |
| } |
| } |
|
|
| |
| static async createGroup(group: Partial<StudentGroup>): Promise<StudentGroup> { |
| const response = await this.authFetch('/api/groups/create', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(group) |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to create group"); |
| } |
| return response.json(); |
| } |
|
|
| static async deleteGroup(id: string): Promise<void> { |
| const response = await this.authFetch(`/api/groups/delete/${id}`, { |
| method: 'DELETE' |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to delete group"); |
| } |
| } |
|
|
| static async getTeacherGroups(): Promise<StudentGroup[]> { |
| try { |
| const response = await this.authFetch('/api/groups/list'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| static async getTeacherStats() { |
| try { |
| const response = await this.authFetch('/api/teacher/stats'); |
| if (!response.ok) throw new Error("Failed to consult teacher dashboard stats"); |
| return response.json(); |
| } catch (e) { |
| console.error(e); |
| return { totalExams: 0, totalQuestions: 0, totalSubmissions: 0, avgScore: 0 }; |
| } |
| } |
|
|
| static async getStudentGroups(): Promise<StudentGroup[]> { |
| try { |
| const response = await this.authFetch('/api/student/groups'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| static async getAttempt(id: string): Promise<Attempt | null> { |
| try { |
| const response = await this.authFetch(`/api/attempts/get/${id}`); |
| if (!response.ok) return null; |
| return response.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| static async getQuestionsByIds(ids: string[]): Promise<Question[]> { |
| if (ids.length === 0) return []; |
| try { |
| const response = await this.authFetch('/api/questions/batch', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ ids }) |
| }); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| |
| static async createExam(exam: Partial<Exam>): Promise<string> { |
| const response = await this.authFetch('/api/exams/create', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(exam) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Failed to create exam"); |
| } |
| const data = await response.json(); |
| return data.id; |
| } |
|
|
| static async getExam(id: string): Promise<Exam | null> { |
| try { |
| const response = await this.authFetch(`/api/exams/get/${id}`); |
| if (!response.ok) return null; |
| return response.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| static async getTeacherExams(): Promise<Exam[]> { |
| try { |
| const response = await this.authFetch('/api/exams/list'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| static async updateExam(id: string, data: Partial<Exam>): Promise<void> { |
| const response = await this.authFetch(`/api/exams/update/${id}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data) |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to update exam"); |
| } |
| } |
|
|
| static async getExamAttempts(examId: string): Promise<Attempt[]> { |
| try { |
| const response = await this.authFetch(`/api/exams/attempts/${examId}`); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
|
|
| |
| static async syncDraft(draftData: any): Promise<any> { |
| try { |
| const response = await this.authFetch('/api/drafts/sync', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(draftData) |
| }); |
| if (!response.ok) return null; |
| return response.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| static async getDraft(): Promise<any | null> { |
| try { |
| const response = await this.authFetch('/api/drafts/get'); |
| if (!response.ok) return null; |
| return response.json(); |
| } catch { |
| return null; |
| } |
| } |
|
|
| static async clearDraft(): Promise<void> { |
| try { |
| await this.authFetch('/api/drafts/clear', { method: 'POST' }); |
| } catch (e) { |
| console.warn("Clear draft failed:", e); |
| } |
| } |
|
|
| |
| static async startAttempt(examId: string, student: UserProfile): Promise<string> { |
| const response = await this.authFetch('/api/attempts/start', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ examId, student }) |
| }); |
| if (!response.ok) { |
| throw new Error("Failed to start exam attempt"); |
| } |
| const data = await response.json(); |
| return data.attemptId; |
| } |
|
|
| static async updateAttemptProgress(id: string, answers: any, timeSpent: number): Promise<void> { |
| try { |
| await this.authFetch(`/api/attempts/progress/${id}`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ answers, timeSpent }) |
| }); |
| } catch (e) { |
| console.warn("Progress sync failed", e); |
| } |
| } |
|
|
| static async submitAttempt( |
| attemptId: string, |
| examId: string, |
| answers: any, |
| timeSpent: number, |
| offlineDuration: number = 0, |
| submissionTimestamp?: string |
| ): Promise<any> { |
| const response = await this.authFetch('/api/exams/submit', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| attemptId, |
| examId, |
| answers, |
| timeSpent, |
| offlineDuration, |
| submissionTimestamp |
| }) |
| }); |
| if (!response.ok) { |
| const err = await response.json().catch(() => ({})); |
| throw new Error(err.error || "Failed to submit exam attempt"); |
| } |
| return response.json(); |
| } |
|
|
| static async getStudentAttempts(): Promise<Attempt[]> { |
| try { |
| const response = await this.authFetch('/api/student/attempts'); |
| if (!response.ok) return []; |
| return response.json(); |
| } catch { |
| return []; |
| } |
| } |
| } |
|
|