File size: 7,504 Bytes
65d68f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | import type {
ApiDocument,
DocumentStatus,
ExamModeOutput,
FlashcardResponseItem,
FlashcardsPayload,
NotesOutput,
PreviousPaperAnalysisResponse,
QuizQuestionResponse,
QuizQuestionsPayload,
StudyPathResponse,
VideoPlanV2Response,
VideoRenderJob,
VideoRenderJobStatus,
VideoScenePlanResponse,
} from "@/lib/api/types";
type UnknownRecord = Record<string, unknown>;
function isRecord(value: unknown): value is UnknownRecord {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function readString(value: unknown, fallback = "") {
return typeof value === "string" && value.trim() ? value : fallback;
}
function readStringArray(value: unknown): string[] {
if (!Array.isArray(value)) return [];
return value.map((item) => String(item)).filter((item) => item.trim().length > 0);
}
export function normalizeDocument(value: unknown): ApiDocument {
const record = isRecord(value) ? value : {};
const now = new Date().toISOString();
return {
id: readString(record.id, `doc-local-${Date.now()}`),
user_id: readString(record.user_id, "demo-user-student"),
title: readString(record.title, "Study source"),
file_name: readString(record.file_name, "source.pdf"),
file_type: readString(record.file_type, "PDF"),
subject: typeof record.subject === "string" ? record.subject : null,
chapter: typeof record.chapter === "string" ? record.chapter : null,
syllabus: typeof record.syllabus === "string" ? record.syllabus : null,
status: normalizeDocumentStatus(record.status),
// extracted_text removed — raw OCR text is not returned by the API
extracted_text_length: typeof record.extracted_text_length === "number" ? record.extracted_text_length : null,
chunk_count: typeof record.chunk_count === "number" ? record.chunk_count : 0,
created_at: readString(record.created_at, now),
};
}
export function normalizeDocumentStatus(value: unknown): DocumentStatus {
if (value === "ready" || value === "processing" || value === "failed" || value === "uploaded") {
return value;
}
return "ready";
}
export function normalizeNotes(value: unknown): NotesOutput {
const record = isRecord(value) ? value : {};
// Backend returns exam_keywords; exam_focus is a frontend-side alias
const examKeywords = readStringArray(record.exam_keywords);
return {
title: readString(record.title, "Smart Notes"),
summary: typeof record.summary === "string" ? record.summary : undefined,
student_level_summary: typeof record.student_level_summary === "string" ? record.student_level_summary : undefined,
key_points: readStringArray(record.key_points).length ? readStringArray(record.key_points) : ["Review the main ideas first."],
important_keywords: readStringArray(record.important_keywords),
important_definitions: readStringArray(record.important_definitions),
formulas: readStringArray(record.formulas),
diagrams_to_practice: readStringArray(record.diagrams_to_practice),
memory_tricks: readStringArray(record.memory_tricks),
exam_keywords: examKeywords,
exam_focus: readStringArray(record.exam_focus).length ? readStringArray(record.exam_focus) : examKeywords,
possible_exam_questions: readStringArray(record.possible_exam_questions),
last_minute_revision: readStringArray(record.last_minute_revision),
quick_check: readStringArray(record.quick_check),
source_summary: typeof record.source_summary === "string" ? record.source_summary : undefined,
scoring_keywords: readStringArray(record.scoring_keywords),
common_mistakes: readStringArray(record.common_mistakes),
ten_minute_revision: readStringArray(record.ten_minute_revision),
next_study_action: typeof record.next_study_action === "string" ? record.next_study_action : undefined,
caution_note: typeof record.caution_note === "string" ? record.caution_note : undefined,
};
}
export function normalizeQuiz(value: unknown): QuizQuestionResponse[] {
const payload: QuizQuestionsPayload | unknown = value;
if (Array.isArray(payload)) return payload.filter(isRecord).map(normalizeQuizQuestion);
if (isRecord(payload) && Array.isArray(payload.questions)) {
return payload.questions.filter(isRecord).map(normalizeQuizQuestion);
}
return [];
}
function normalizeQuizQuestion(value: UnknownRecord): QuizQuestionResponse {
return {
question: readString(value.question, "Practice question"),
type: readString(value.type, "MCQ"),
options: readStringArray(value.options),
answer: readString(value.answer, "Review the explanation."),
explanation: typeof value.explanation === "string" ? value.explanation : undefined,
difficulty: readString(value.difficulty, "Practice"),
topic: typeof value.topic === "string" ? value.topic : undefined,
why_this_matters: typeof value.why_this_matters === "string" ? value.why_this_matters : undefined,
common_trap: typeof value.common_trap === "string" ? value.common_trap : undefined,
};
}
export function normalizeFlashcards(value: unknown): FlashcardResponseItem[] {
const payload: FlashcardsPayload | unknown = value;
if (Array.isArray(payload)) return payload.filter(isRecord).map(normalizeFlashcard);
if (isRecord(payload) && Array.isArray(payload.cards)) {
return payload.cards.filter(isRecord).map(normalizeFlashcard);
}
return [];
}
function normalizeFlashcard(value: UnknownRecord): FlashcardResponseItem {
return {
front: readString(value.front, "Key idea"),
back: readString(value.back, "Short explanation"),
hint: typeof value.hint === "string" ? value.hint : undefined,
tag: typeof value.tag === "string" ? value.tag : undefined,
topic: typeof value.topic === "string" ? value.topic : undefined,
memory_hook: typeof value.memory_hook === "string" ? value.memory_hook : undefined,
exam_use: typeof value.exam_use === "string" ? value.exam_use : undefined,
};
}
export function normalizePYQAnalysis(value: unknown): PreviousPaperAnalysisResponse | null {
return isRecord(value) && Array.isArray(value.repeated_topics)
? (value as PreviousPaperAnalysisResponse)
: null;
}
export function normalizeStudyPlan(value: unknown): StudyPathResponse | null {
return isRecord(value) && Array.isArray(value.blocks) ? (value as StudyPathResponse) : null;
}
export function normalizeExamAnswer(value: unknown): ExamModeOutput | null {
return isRecord(value) ? (value as ExamModeOutput) : null;
}
export function normalizeVideoPlan(value: unknown): VideoScenePlanResponse | VideoPlanV2Response | null {
if (!isRecord(value)) return null;
if (Array.isArray(value.scenes) || Array.isArray(value.sections)) {
return value as VideoScenePlanResponse | VideoPlanV2Response;
}
return null;
}
export function normalizeRenderJob(value: unknown): VideoRenderJob | null {
return isRecord(value) && typeof value.id === "string" ? (value as VideoRenderJob) : null;
}
export function normalizeRenderStatus(value: unknown): VideoRenderJobStatus {
if (
value === "ready" ||
value === "failed" ||
value === "queued" ||
value === "running" ||
value === "planning" ||
value === "script_ready" ||
value === "audio_generating" ||
value === "visuals_generating" ||
value === "rendering" ||
value === "uploading" ||
value === "complete"
) {
return value;
}
if (value === "completed") return "complete";
if (value === "processing") return "running";
return "queued";
}
|