import { ApiError, apiGet, apiPost, } from "@/lib/api/client"; import { isLocalResourceId, withDemoFallback } from "@/lib/api/mode"; import type { ApiDocument, BrainAskPayload, BrainAskResponse, BrainGenerationResponse, BrainGeneratePayload, BrainLastNightPlanOutput, BrainAnswerCorrectionOutput, BrainPyqAnalysisOutput, BrainSourcesListResponse, BrainSourceResponse, BrainVideoPlanPayload, BrainVideoPlanResponse, BrainExamAnswerOutput, DocumentChunk, FlashcardResponseItem, NotesOutput, QuizQuestionResponse, StudyPathGeneratePayload, BrainStudyPathResponse, StudyProfilePayload, StudyProfileResponse, StudyRequestAnalyzePayload, StudyRequestAnalysis, StudentLevel, TextSourcePayload, VideoMode, } from "@/lib/api/types"; import { demoUser } from "@/lib/frontend-demo-data"; import { getLocalDocuments, retrieveLocalDocumentChunks, saveLocalDocument, } from "@/lib/frontend-local-store"; const OFFLINE_EVIDENCE_LABEL = "Offline preview — not connected to DocDoe AI"; const OFFLINE_TRUST_NOTE = "Offline preview only. Connect to the DocDoe backend for source-grounded AI answers."; function safeTitle(value: string) { return value .trim() .replace(/\s+/g, " ") .replace(/\b\w/g, (letter) => letter.toUpperCase()); } function containsAny(text: string, patterns: string[]) { return patterns.some((pattern) => text.includes(pattern)); } function inferSubject(text: string) { const subjects = [ "physics", "chemistry", "biology", "maths", "mathematics", "computer science", "english", "social science", ]; const found = subjects.find((subject) => text.includes(subject)); if (!found) return null; return found === "maths" ? "Maths" : safeTitle(found); } function inferTopic(rawText: string, lowerText: string, subject: string | null) { const knownTopics = [ "electromagnetic induction", "photosynthesis", "organic chemistry", "trigonometry", "python basics", "current electricity", "life processes", "chemical reactions", "faraday's law", "lenz's law", ]; const known = knownTopics.find((topic) => lowerText.includes(topic)); if (known) return safeTitle(known); const afterSubject = subject ? rawText.split(new RegExp(subject, "i"))[1]?.trim() : ""; const candidate = (afterSubject || rawText) .replace(/\b(exam|tomorrow|today|need|want|a\+|full score|good marks|pass|study|prepare|class|kerala|cbse|sslc|jee|neet)\b/gi, " ") .replace(/[^\w\s+.-]/g, " ") .replace(/\s+/g, " ") .trim(); if (candidate.length > 3 && !/^(asdf|test|hello|hi)$/i.test(candidate)) { return safeTitle(candidate.split(" ").slice(0, 5).join(" ")); } return null; } function inferAnalysis(payload: StudyRequestAnalyzePayload): StudyRequestAnalysis { const rawText = payload.raw_text.trim(); const lowerText = rawText.toLowerCase(); const subject = inferSubject(lowerText); const topic = inferTopic(rawText, lowerText, subject); const exam = containsAny(lowerText, ["kerala", "+2", "hse"]) ? "Kerala +2" : containsAny(lowerText, ["sslc"]) ? "SSLC" : containsAny(lowerText, ["cbse"]) ? "CBSE" : containsAny(lowerText, ["jee"]) ? "JEE" : containsAny(lowerText, ["neet"]) ? "NEET" : null; const grade = containsAny(lowerText, ["+2", "class 12", "12th"]) ? "12" : containsAny(lowerText, ["+1", "class 11", "11th"]) ? "11" : null; const board = exam?.includes("Kerala") ? "Kerala HSE" : exam === "CBSE" ? "CBSE" : null; const goal = containsAny(lowerText, ["a+", "a plus"]) ? "A+" : containsAny(lowerText, ["full score", "rank", "top score"]) ? "Full score" : containsAny(lowerText, ["pass"]) ? "Pass" : "Good marks"; const timeLeft = containsAny(lowerText, ["tomorrow"]) ? "tomorrow" : containsAny(lowerText, ["today", "1 hour"]) ? "1 hour" : containsAny(lowerText, ["3 hours"]) ? "3 hours" : containsAny(lowerText, ["5 hours"]) ? "5 hours" : containsAny(lowerText, ["7 days", "week"]) ? "7 days" : null; const level: StudentLevel | null = containsAny(lowerText, ["beginner", "basics", "from zero"]) ? "beginner" : containsAny(lowerText, ["advanced", "full-score", "rank"]) ? "advanced" : null; const language = containsAny(lowerText, ["malayalam"]) ? "Malayalam + English" : containsAny(lowerText, ["hindi"]) ? "Hindi + English" : null; const primaryNeed = containsAny(lowerText, ["video"]) ? "study_video" : containsAny(lowerText, ["quiz"]) ? "quiz_me" : containsAny(lowerText, ["last night", "tomorrow"]) ? "last_night_plan" : "what_to_study"; const missingFields = [ exam ? null : "exam", subject ? null : "subject", topic ? null : "topic", timeLeft ? null : "time_left", level ? null : "level", language ? null : "language_preference", ].filter((field): field is string => Boolean(field)); const confidence = Math.max(0.35, Math.min(0.92, 1 - missingFields.length * 0.11)); return { raw_text: rawText, exam, board, grade, subject, chapter: topic, topic, goal, time_left: timeLeft, level, language_preference: language, primary_need: primaryNeed, confidence, missing_fields: missingFields, warnings: [ ...(confidence < 0.55 ? ["DocDoe needs a clearer exam, subject, or chapter before building your path."] : []), "PYQ data is not available yet. DocDoe will use your source, topic, goal, and time.", ], syllabus_status: payload.syllabus_text?.trim() ? "provided" : "not_provided", pyq_status: "unavailable", }; } export function brainSourceToApiDocument(source: BrainSourceResponse): ApiDocument { return { id: source.id, user_id: source.user_id, title: source.title, file_name: source.file_name, file_type: source.file_type || source.source_type, subject: source.subject, chapter: source.chapter, syllabus: source.syllabus, status: source.status, extracted_text: source.preview_text, chunk_count: source.chunk_count, created_at: source.created_at, }; } function apiDocumentToBrainSource(document: ApiDocument, sourceType = "syllabus_text"): BrainSourceResponse { return { id: document.id, user_id: document.user_id, title: document.title, source_type: sourceType, status: document.status, file_name: document.file_name, file_type: document.file_type, subject: document.subject, chapter: document.chapter, syllabus: document.syllabus, word_count: Math.round((document.extracted_text_length ?? 0) / 5), chunk_count: document.chunk_count, preview_text: null, // Use /documents/{id}/preview endpoint for preview text created_at: document.created_at, updated_at: document.created_at, }; } function localTextSource(payload: TextSourcePayload): BrainSourceResponse { const now = new Date().toISOString(); const document: ApiDocument = { id: `doc-local-text-${Date.now()}`, user_id: demoUser.id, title: payload.title || payload.chapter || "Study source", file_name: payload.title || payload.chapter || "Study source", file_type: payload.source_type, subject: payload.subject ?? "Study", chapter: payload.chapter ?? payload.title, syllabus: payload.syllabus ?? "Study plan", status: "ready", extracted_text: payload.text, chunk_count: Math.max(1, Math.ceil(payload.text.length / 420)), created_at: now, }; return apiDocumentToBrainSource(saveLocalDocument(document), payload.source_type); } function fallbackProfile(payload: StudyProfilePayload): StudyProfileResponse { const now = new Date().toISOString(); return { id: `sp-local-${Date.now()}`, user_id: demoUser.id, exam: payload.exam ?? null, board: payload.board ?? null, grade: payload.grade ?? null, subject: payload.subject ?? null, chapter: payload.chapter ?? null, topic: payload.topic ?? null, goal: payload.goal ?? null, time_left: payload.time_left ?? null, level: payload.level ?? "intermediate", language_preference: payload.language_preference ?? "English", primary_need: payload.primary_need ?? "what_to_study", weak_areas: payload.weak_areas ?? [], onboarding_completed: payload.onboarding_completed ?? false, extra: payload.extra ?? {}, created_at: now, updated_at: now, }; } function planDurationMinutes(timeLeft: string | null | undefined) { if (!timeLeft) return 300; const value = timeLeft.toLowerCase(); if (value.includes("1")) return 60; if (value.includes("3")) return 180; if (value.includes("5") || value.includes("tomorrow")) return 300; if (value.includes("7")) return 420; return 600; } function fallbackStudyPath(payload: StudyPathGeneratePayload): BrainStudyPathResponse { const topic = payload.topic || payload.syllabus_text?.split(/\s+/).slice(0, 4).join(" ") || "your chapter"; const duration = planDurationMinutes(payload.time_left); const blocks = [ { title: `${topic} meaning`, reason_type: "concept", task: "Read the core definition, then write it once in your own words.", expected_output: "One crisp definition and three keywords.", actions: ["Read", "Underline keywords", "Write a 2-mark answer"], }, { title: "Scoring points", reason_type: "exam", task: "Convert the chapter into mark-wise bullet answers.", expected_output: "1-mark, 2-mark, and 4-mark answer frames.", actions: ["Make notes", "Practice answer", "Self-check"], }, { title: "Final active recall", reason_type: "memory", task: "Close the source and answer five quick questions.", expected_output: "A short checklist of weak points to revise.", actions: ["Quiz", "Revise", "Repeat mistakes"], }, ]; return { title: `${safeTitle(topic)} study path`, exam: payload.exam ?? null, subject: payload.subject ?? null, topic, goal: payload.goal ?? "Good marks", time_left: payload.time_left ?? "5 hours", level: payload.level ?? "intermediate", readiness_score: 0.66, next_best_action: `Start with ${topic}, then generate notes and a short quiz.`, study_timeline: blocks.map((block, index) => ({ order: index + 1, title: block.title, duration_minutes: Math.max(20, Math.round(duration / blocks.length)), reason: index === 0 ? `This locks the meaning before exam answers.` : index === 1 ? `Marks come from structure, exact keywords, and clear points.` : `Active recall shows what still needs revision.`, reason_type: block.reason_type, task: block.task, expected_output: block.expected_output, actions: block.actions, source_basis: payload.source_ids?.length ? "selected_sources" : payload.source_id ? "selected_source" : "study_profile", })), weak_areas: ["concept clarity", "answer writing"], easy_marks: [`1-mark definition of ${topic}`, "Keyword-only short answers"], danger_areas: ["Writing long paragraphs instead of mark-wise points"], revision_keywords: ["definition", "keywords", "diagram", "example"], source_basis: payload.source_ids?.length ? "user_sources" : payload.source_id ? "user_source" : "study_profile", trust_notes: [ OFFLINE_TRUST_NOTE, payload.source_ids?.length ? `Using ${payload.source_ids.length} selected sources for this path.` : payload.source_id ? "Using your selected source for this path." : "No source uploaded yet. DocDoe is using your study setup.", ], evidence_label: OFFLINE_EVIDENCE_LABEL, analysis: {}, }; } const TOPIC_KNOWLEDGE: Record = { "electromagnetic induction": { definition: "Electromagnetic induction is the production of an electromotive force (EMF) across an electrical conductor in a changing magnetic field. When the magnetic flux through a circuit changes, an EMF is induced in it.", keyPoints: [ "Faraday's First Law: Whenever the magnetic flux linked with a circuit changes, an EMF is induced in the circuit.", "Faraday's Second Law: The magnitude of induced EMF is equal to the rate of change of magnetic flux, e = -dΦ/dt.", "Lenz's Law: The direction of induced EMF opposes the change in flux that causes it (conservation of energy).", "Motional EMF: When a conductor moves in a magnetic field, EMF = Bvl (B = field, v = velocity, l = length).", "Self-inductance (L): EMF induced in a coil due to change in its own current, e = -L(dI/dt).", "Mutual inductance (M): EMF induced in one coil due to change in current in a nearby coil.", ], formula: "e = -N(dΦ/dt) where Φ = B·A·cos(θ)", diagram: "Draw a coil connected to a galvanometer, with a bar magnet moving toward it. Label: coil turns (N), magnetic field lines, direction of induced current, galvanometer deflection.", examTip: "In Kerala +2 boards, Faraday's law appears as a 4-mark or 5-mark question almost every year. Always write the mathematical expression with the negative sign (Lenz's law).", realLife: "Electric generators at power stations use electromagnetic induction — rotating coils in magnetic fields produce the electricity that powers your home.", mistakes: ["Forgetting the negative sign in Faraday's law (it represents Lenz's law)", "Confusing magnetic flux (Φ = BA cos θ) with magnetic field (B)", "Not drawing the diagram for AC generator questions"], }, "faraday": { definition: "Faraday's Law of Electromagnetic Induction states that the induced EMF in a circuit is directly proportional to the rate of change of magnetic flux through the circuit. Mathematically, e = -N(dΦ/dt), where N is the number of turns and Φ is the magnetic flux.", keyPoints: [ "First Law: A change in magnetic flux through a circuit induces an EMF.", "Second Law: The induced EMF equals the negative rate of change of flux linkage: e = -N(dΦ/dt).", "The negative sign comes from Lenz's Law — the induced current opposes the change causing it.", "Magnetic flux Φ = B·A·cos(θ), where B is field strength, A is area, θ is angle between B and normal to A.", "More turns (N) = larger induced EMF. Faster flux change = larger EMF.", "This is the principle behind generators, transformers, and induction cooktops.", ], formula: "e = -N(dΦ/dt), where Φ = BAcos(θ)", examTip: "For a 5-mark answer: (1) State the law, (2) Write the formula, (3) Explain each symbol, (4) Mention Lenz's law connection, (5) Give one application.", realLife: "When you swipe a credit card, the changing magnetic stripe induces tiny currents in the reader — that's Faraday's law in action.", mistakes: ["Writing e = N(dΦ/dt) without the minus sign", "Not specifying what Φ represents", "Confusing EMF with current"], }, "lenz": { definition: "Lenz's Law states that the direction of the induced current is such that it opposes the change in magnetic flux that produced it. This is a consequence of the conservation of energy.", keyPoints: [ "The induced current creates its own magnetic field that opposes the original change.", "If flux is increasing → induced current creates opposing field. If decreasing → supporting field.", "Lenz's law explains the negative sign in Faraday's law: e = -N(dΦ/dt).", "Without Lenz's law, energy could be created from nothing — violating conservation of energy.", "The opposing nature means you must do work to maintain the change, converting mechanical → electrical energy.", ], examTip: "Board exams often ask: 'Explain the significance of the negative sign in Faraday's law.' Your answer should clearly connect it to Lenz's law and conservation of energy.", realLife: "When you drop a magnet through a copper tube, it falls slowly because the induced currents oppose its motion — that's Lenz's law slowing it down.", mistakes: ["Saying Lenz's law opposes the flux instead of opposing the CHANGE in flux", "Not connecting it to energy conservation"], }, "ac generator": { definition: "An AC generator (alternator) converts mechanical energy into alternating current electrical energy using the principle of electromagnetic induction. A coil rotating in a uniform magnetic field produces a sinusoidal EMF.", keyPoints: [ "Main parts: Armature coil, field magnets, slip rings, brushes, and axle.", "Working principle: When the coil rotates in the magnetic field, the flux changes continuously, inducing an alternating EMF.", "EMF equation: e = NBAω sin(ωt), where ω = angular velocity.", "Peak EMF (e₀) = NBAω occurs when the coil is parallel to the field (θ = 90°).", "EMF is zero when the coil is perpendicular to the field (θ = 0°, 180°).", "One full rotation = one complete AC cycle.", ], formula: "e = NBAω sin(ωt) = e₀ sin(ωt)", diagram: "Draw a rectangular coil ABCD between two pole pieces N and S. Show slip rings R₁, R₂ connected to the coil ends, brushes B₁, B₂ pressing against rings, and external load. Label the axis of rotation.", examTip: "AC generator is a strong practice topic. Draw and label the diagram clearly, then explain the working step by step.", realLife: "Hydroelectric dams use massive AC generators where water turbines spin the coil to produce electricity for cities.", mistakes: ["Confusing slip rings (AC) with split-ring commutator (DC)", "Forgetting to label the diagram", "Not writing the condition for maximum and zero EMF"], }, "self inductance": { definition: "Self-inductance is the property of a coil by which it opposes any change in current flowing through it by inducing an EMF in itself. The self-induced EMF is e = -L(dI/dt), where L is the inductance in Henry.", keyPoints: [ "When current through a coil changes, the magnetic flux linked with the coil changes, inducing a back EMF.", "Self-inductance L = NΦ/I for a solenoid: L = μ₀n²Al (n = turns/length, A = area, l = length).", "Energy stored in an inductor: U = ½LI².", "SI unit of inductance is Henry (H). 1 H = 1 V·s/A.", "Self-inductance is the electrical analogue of inertia in mechanics.", ], formula: "L = μ₀n²Al, e = -L(dI/dt), U = ½LI²", examTip: "When asked about self-inductance, always mention the analogy with inertia — examiners look for conceptual understanding.", realLife: "The spark you see when unplugging an appliance is caused by self-inductance — the coil tries to maintain current flow.", mistakes: ["Confusing self-inductance with mutual inductance", "Forgetting that L depends on the geometry of the coil, not the current"], }, "mutual inductance": { definition: "Mutual inductance is the property by which a change in current in one coil induces an EMF in a neighboring coil. If current I₁ in coil 1 produces flux Φ₂ in coil 2, then M = N₂Φ₂/I₁.", keyPoints: [ "Mutual inductance depends on: geometry, number of turns, distance between coils, and the medium.", "The induced EMF in coil 2: e₂ = -M(dI₁/dt).", "M₁₂ = M₂₁ = M (reciprocity theorem).", "For two coaxial solenoids: M = μ₀n₁n₂Al.", "Transformers work on the principle of mutual inductance.", ], formula: "M = μ₀n₁n₂Al, e₂ = -M(dI₁/dt)", examTip: "Mutual inductance questions often connect to transformers. Mention the transformer principle when discussing M.", realLife: "Wireless phone chargers use mutual inductance — a coil in the pad induces current in a coil inside your phone.", mistakes: ["Assuming M depends on current (it depends only on geometry)", "Not mentioning the reciprocity theorem"], }, "eddy currents": { definition: "Eddy currents are loops of electric current induced within conductors by a changing magnetic field. They flow in closed loops within the conductor, perpendicular to the magnetic field.", keyPoints: [ "Caused by changing magnetic flux through a bulk conductor (not just a wire loop).", "They produce heating (I²R losses) and opposing magnetic effects.", "Applications: electromagnetic braking, induction heating, speedometers, metal detectors.", "To reduce eddy current losses, cores are laminated (thin insulated sheets).", "Eddy currents follow Lenz's law — they oppose the change causing them.", ], examTip: "Usually a 1-mark or 2-mark question. Know 3 applications and the lamination technique.", realLife: "Induction cooktops heat pans using eddy currents — the changing magnetic field from the coil induces currents in the pan's metal base.", mistakes: ["Not mentioning lamination as the method to reduce eddy currents", "Confusing eddy currents with regular induced currents in a wire"], }, "transformer": { definition: "A transformer is a device that transfers electrical energy between two circuits through mutual inductance. It can step up or step down AC voltage. Vs/Vp = Ns/Np = Ip/Is.", keyPoints: [ "Works only with AC (needs changing flux for induction).", "Step-up: Ns > Np → voltage increases, current decreases.", "Step-down: Ns < Np → voltage decreases, current increases.", "Ideal transformer: VpIp = VsIs (power conserved).", "Core is laminated soft iron to reduce eddy current losses.", "Energy losses: copper loss (I²R), eddy currents, hysteresis, flux leakage.", ], formula: "Vs/Vp = Ns/Np, efficiency = (Ps/Pp) × 100%", examTip: "Always mention the ideal transformer equation AND the energy losses. Examiners give marks for mentioning at least 3 types of losses.", realLife: "The large boxes on electric poles are transformers stepping down 11kV to 230V for your home.", mistakes: ["Saying transformers work with DC", "Forgetting to mention lamination for reducing losses"], }, }; function findTopicMatch(question: string, sourceContext?: string): { definition: string; keyPoints: string[]; formula?: string; diagram?: string; examTip: string; realLife: string; mistakes: string[] } | null { const q = question.toLowerCase(); for (const [key, data] of Object.entries(TOPIC_KNOWLEDGE)) { if (q.includes(key)) return data; } if (q.includes("emf") || q.includes("flux")) return TOPIC_KNOWLEDGE["electromagnetic induction"]; if (q.includes("generator") || q.includes("alternator")) return TOPIC_KNOWLEDGE["ac generator"]; if (q.includes("inductor") || q.includes("self induct") || q.includes("back emf")) return TOPIC_KNOWLEDGE["self inductance"]; if (q.includes("mutual") || q.includes("two coil")) return TOPIC_KNOWLEDGE["mutual inductance"]; if (q.includes("eddy")) return TOPIC_KNOWLEDGE["eddy currents"]; if (q.includes("step up") || q.includes("step down") || q.includes("transformer")) return TOPIC_KNOWLEDGE["transformer"]; if (sourceContext) { const sc = sourceContext.toLowerCase(); for (const [key, data] of Object.entries(TOPIC_KNOWLEDGE)) { if (sc.includes(key)) return data; } if (sc.includes("electromagnetic") || sc.includes("induction")) return TOPIC_KNOWLEDGE["electromagnetic induction"]; } return null; } function classifyQuestion(question: string): "explain" | "formula" | "diagram" | "exam_answer" | "mistakes" | "real_life" | "summarize" | "general" { const q = question.toLowerCase(); if (q.includes("formula") || q.includes("equation") || q.includes("derive") || q.includes("mathematical")) return "formula"; if (q.includes("diagram") || q.includes("draw") || q.includes("label") || q.includes("figure")) return "diagram"; if (q.includes("mistake") || q.includes("error") || q.includes("wrong") || q.includes("avoid")) return "mistakes"; if (q.includes("mark") || q.includes("exam") || q.includes("answer format") || q.includes("board")) return "exam_answer"; if (q.includes("real") || q.includes("life") || q.includes("example") || q.includes("daily") || q.includes("application")) return "real_life"; if (q.includes("summar") || q.includes("brief") || q.includes("short") || q.includes("points") || q.includes("key")) return "summarize"; if (q.includes("explain") || q.includes("what is") || q.includes("define") || q.includes("concept") || q.includes("important") || q.includes("simply")) return "explain"; return "general"; } function fallbackAsk(payload: BrainAskPayload): BrainAskResponse { const sourceIds = payload.source_ids?.length ? payload.source_ids : payload.source_id ? [payload.source_id] : []; const chunks: DocumentChunk[] = sourceIds .filter((id) => isLocalResourceId(id)) .flatMap((id) => retrieveLocalDocumentChunks(id, payload.question, 2).chunks); const primaryChunk = chunks[0]; const sourceTitles = sourceIds.map((id) => getLocalDocuments().find((doc) => doc.id === id)?.title).filter((title): title is string => Boolean(title)); const topic = findTopicMatch(payload.question, sourceTitles.join(" ")); const questionType = classifyQuestion(payload.question); let answer: string; let sections: Array<{ title: string; body: string }>; let suggestedActions: string[]; if (topic) { switch (questionType) { case "explain": answer = topic.definition; sections = [ { title: "Key concepts", body: topic.keyPoints.slice(0, 4).join("\n") }, ...(topic.formula ? [{ title: "Formula", body: topic.formula }] : []), { title: "Exam tip", body: topic.examTip }, ]; suggestedActions = ["Ask for diagram help", "Generate a quiz on this", "Explain with real-life example"]; break; case "formula": answer = topic.formula ? `The key formula is: ${topic.formula}. ${topic.keyPoints.find((p) => p.toLowerCase().includes("formula") || p.toLowerCase().includes("equation")) || topic.keyPoints[1] || ""}` : topic.definition; sections = [ ...(topic.formula ? [{ title: "Formula", body: topic.formula }] : []), { title: "How to apply", body: topic.keyPoints.filter((p) => p.includes("=") || p.includes("where")).join("\n") || topic.keyPoints.slice(0, 3).join("\n") }, { title: "Common mistakes", body: topic.mistakes.join("\n") }, ]; suggestedActions = ["Show me worked examples", "What are common numerical problems?", "Explain the derivation"]; break; case "diagram": answer = topic.diagram || `For ${payload.question.slice(0, 60)}, you'll need a clear labelled diagram. ${topic.keyPoints[0]}`; sections = [ ...(topic.diagram ? [{ title: "Diagram instructions", body: topic.diagram }] : []), { title: "Key labels to include", body: topic.keyPoints.slice(0, 3).join("\n") }, { title: "Examiner note", body: topic.examTip }, ]; suggestedActions = ["What marks does the diagram carry?", "Explain the working from the diagram", "Common diagram mistakes"]; break; case "exam_answer": answer = `Here's how to structure your exam answer: Start with the definition, then the mathematical expression, followed by key points. ${topic.examTip}`; sections = [ { title: "Definition (1-2 marks)", body: topic.definition }, ...(topic.formula ? [{ title: "Formula (1 mark)", body: topic.formula }] : []), { title: "Key points to include", body: topic.keyPoints.slice(0, 4).join("\n") }, { title: "Avoid these mistakes", body: topic.mistakes.join("\n") }, ]; suggestedActions = ["Write a full 5-mark answer", "What keywords get marks?", "Show marking scheme"]; break; case "mistakes": answer = `Here are the most common mistakes students make: ${topic.mistakes[0]}. Knowing these can help you avoid losing marks.`; sections = [ { title: "Common mistakes", body: topic.mistakes.map((m, i) => `${i + 1}. ${m}`).join("\n") }, { title: "How to avoid them", body: topic.examTip }, { title: "Quick check", body: topic.keyPoints[0] }, ]; suggestedActions = ["Give me a quiz to test this", "What are examiner expectations?", "Show the correct approach"]; break; case "real_life": answer = topic.realLife; sections = [ { title: "Real-world connection", body: topic.realLife }, { title: "The science behind it", body: topic.keyPoints.slice(0, 2).join("\n") }, { title: "Why this matters for exams", body: "Real-life examples are often asked as 1-mark or 2-mark questions. Examiners love when you connect theory to applications." }, ]; suggestedActions = ["Give more applications", "Now explain the theory", "Create flashcards"]; break; case "summarize": answer = `Quick summary: ${topic.definition.split(". ").slice(0, 2).join(". ")}.`; sections = [ { title: "Key points", body: topic.keyPoints.join("\n") }, ...(topic.formula ? [{ title: "Important formula", body: topic.formula }] : []), { title: "Exam tip", body: topic.examTip }, ]; suggestedActions = ["Explain in more detail", "Create flashcards from this", "Give me practice questions"]; break; default: answer = topic.definition; sections = [ { title: "Key concepts", body: topic.keyPoints.slice(0, 3).join("\n") }, ...(topic.formula ? [{ title: "Formula", body: topic.formula }] : []), { title: "Exam focus", body: topic.examTip }, ]; suggestedActions = ["Explain deeper", "Show diagram", "Write exam answer"]; } } else if (primaryChunk?.chunk_text) { answer = `From your material: ${primaryChunk.chunk_text}`; sections = [ { title: "From your source", body: primaryChunk.chunk_text }, { title: "Study approach", body: "Read this section carefully, underline keywords, and try to write the main idea in one sentence.\nThen check if you can recall it without looking." }, ]; suggestedActions = ["Summarize this section", "Create a quiz from this", "Explain this simply"]; } else { const questionTopic = payload.question.replace(/[?.,!]/g, "").trim(); answer = `DocDoe needs a live backend connection to answer "${questionTopic.slice(0, 80)}".`; sections = [ { title: "Connect the knowledge brain", body: "Sign in and ensure the DocDoe backend is running. The AI engine retrieves your uploaded sources, applies exam intelligence, and generates grounded answers.", }, { title: "Source-grounded mode", body: "Upload a textbook chapter, notes, or question paper. DocDoe retrieves relevant chunks and answers from your material — not generic templates.", }, { title: "Offline preview limits", body: "Without the backend, only cached physics scaffolds are available. Real production answers require the connected AI pipeline.", }, ]; suggestedActions = ["Upload your textbook PDF", "Select an uploaded source", "Retry after signing in"]; } return { answer, sections, citations: chunks.length > 0 ? chunks.slice(0, 5).map((chunk) => ({ chunk_id: chunk.id, chunk_index: chunk.chunk_index, heading: chunk.heading, page_number: chunk.page_number, snippet: chunk.chunk_text.slice(0, 140), source_id: chunk.document_id, source_title: sourceTitles[0] ?? null, })) : sourceTitles.map((title) => ({ chunk_id: null, chunk_index: null, heading: title, page_number: null, snippet: null, source_id: null, source_title: title, })), suggested_actions: suggestedActions, trust_note: sourceIds.length ? `${OFFLINE_TRUST_NOTE} Showing local source excerpt.` : topic ? `${OFFLINE_TRUST_NOTE} Showing cached topic scaffold.` : OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function fallbackExamAnswer(payload: BrainGeneratePayload): BrainGenerationResponse { const topic = payload.topic || "this topic"; const output: BrainExamAnswerOutput = { one_mark: [{ question: `Define ${topic}.`, answer: `${topic} is the core idea from this chapter, written in one crisp line.` }], two_mark: [{ question: `Explain ${topic} in two points.`, answer: "Write the definition, then add one keyword-based example." }], four_mark: [{ question: `Write a 4-mark answer on ${topic}.`, answer: "Definition, principle, keyword explanation, and one example.", diagram_needed: true }], six_mark: [{ question: `Prepare a long answer for ${topic}.`, answer: "Start with definition, add law/principle, explain steps, include diagram, finish with application.", diagram_needed: true }], keywords_to_underline: ["definition", "principle", "diagram", "example"], diagram_required: true, common_mistake: "Writing long story answers instead of clean mark-wise points.", examiner_tip: "Marks come from exact keywords, neat structure, and a labelled diagram when needed.", answer_writing_formula: ["Definition", "Principle", "Steps", "Diagram", "Application"], trust_note: payload.source_ids?.length ? `Using ${payload.source_ids.length} selected sources.` : "Using your selected topic and source.", }; return { type: "exam_answer", topic, output, trust_note: output.trust_note, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function fallbackAnswerCorrection(payload: BrainGeneratePayload): BrainGenerationResponse { const question = String(payload.options?.question || payload.topic || "this question"); const marks = Number(payload.options?.marks || 5) || 5; const output: BrainAnswerCorrectionOutput = { syllabus_position: payload.subject ? `${payload.subject} -> ${question}` : question, mark_scheme_assumption: `Assuming this is a ${marks}-mark answer.`, score: `Estimated score: not available offline/${marks}`, what_correct: ["Offline preview cannot grade the pasted answer reliably."], marks_lost: ["Connect to the live backend to calculate marks lost from the answer."], missing_keywords: ["exact keywords", "marking points", "board format"], corrected_board_answer: "Connect to DocDoe AI to rewrite this in board-answer format.", how_to_improve: ["Use the live backend for source-grounded answer correction."], quick_retry_task: "Connect to the backend and run Correct my answer again.", source_truth: OFFLINE_TRUST_NOTE, source_evidence: [], assumed_marks: marks, official_scheme_available: false, }; return { type: "answer_correction", topic: question, output, trust_note: OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function fallbackNotes(payload: BrainGeneratePayload): BrainGenerationResponse { const topic = payload.topic || "this topic"; return { type: "notes", topic, output: { title: `${topic} scoring notes`, summary: `DocDoe shaped ${topic} into exam-focused notes.`, student_level_summary: `This is tuned for a ${payload.level ?? "intermediate"} student.`, must_learn_first: [`Meaning of ${topic}`, "One clean example", "A short exam answer frame"], key_points: [`Meaning of ${topic}`, "Important keywords", "Exam answer structure", "One diagram, formula, or example if the chapter supports it"], important_definitions: [`Define ${topic} in one crisp line.`], exam_keywords: ["definition", "principle", "example", "diagram"], scoring_keywords: ["definition", "principle", "example", "diagram"], memory_tricks: ["Definition first, example second, keywords always."], exam_focus: ["Write short point-wise answers.", "Underline exact terms."], possible_exam_questions: [`Define ${topic}.`, `Write a short note on ${topic}.`, `Give one example or application of ${topic}.`], common_mistakes: ["Writing a long paragraph without keywords.", "Skipping the example or diagram when the question asks for it."], last_minute_revision: ["Revise definition", "Practice one 4-mark answer"], ten_minute_revision: ["Read the definition aloud.", "Write three keywords without looking.", "Answer one short question.", "Circle the mistake you usually make."], quick_check: [`Define ${topic}.`, "List three keywords."], next_study_action: `Turn ${topic} into a 5-question quiz, then revise only the wrong answers.`, }, trust_note: payload.source_ids?.length ? `${OFFLINE_TRUST_NOTE} Using ${payload.source_ids.length} selected sources.` : OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function fallbackQuiz(payload: BrainGeneratePayload): BrainGenerationResponse<{ quiz_title: string; questions: QuizQuestionResponse[] }> { const topic = payload.topic || "this topic"; const requestedCount = Math.max(1, Math.min(Number(payload.options?.num_questions ?? payload.options?.question_count ?? 5) || 5, 15)); const baseQuestions: QuizQuestionResponse[] = [ { question: `What is the core meaning of ${topic}?`, type: "mcq", options: [ "A crisp definition of the main idea", "A random example without keywords", "A long story answer", "A diagram label only", ], answer: "A crisp definition of the main idea", explanation: "Direct definitions are valuable when phrased clearly.", difficulty: "easy", common_trap: "Starting with examples before defining the term.", exam_tip: "Write the definition first, then one keyword.", }, { question: `Which keywords should you underline for ${topic}?`, type: "mcq", options: [ "Exact terms from the chapter", "Only filler sentences", "Unrelated examples", "Personal opinions", ], answer: "Exact terms from the chapter", explanation: "Keywords help examiners find the scoring points fast.", difficulty: "medium", common_trap: "Using casual words instead of textbook terms.", exam_tip: "Underline exact terms after writing the answer.", }, { question: `Write a two-point answer for ${topic}.`, type: "mcq", options: [ "Definition plus one clear exam point", "Only the chapter title", "Three unrelated facts", "A diagram without labels", ], answer: "Definition plus one clear exam point", explanation: "Two-mark answers need structure more than length.", difficulty: "medium", common_trap: "Writing one long sentence with no separate points.", exam_tip: "Use two bullets for two marks.", }, { question: `What diagram or example can support ${topic}?`, type: "mcq", options: [ "The simplest labelled diagram or class example", "An unlabelled rough sketch", "An unrelated picture", "No support at all", ], answer: "The simplest labelled diagram or class example", explanation: "Visual support helps when the chapter has a process or setup.", difficulty: "medium", common_trap: "Drawing without labels.", exam_tip: "Labels often carry marks in exams.", }, { question: `What is a common mistake in ${topic}?`, type: "mcq", options: [ "Skipping keywords and writing a story answer", "Using point-wise structure", "Underlining exact terms", "Adding a neat labelled diagram", ], answer: "Skipping keywords and writing a story answer", explanation: "Short, keyword-rich answers score better.", difficulty: "hard", common_trap: "Trying to write everything you remember.", exam_tip: "Write what earns marks first.", }, ]; const questions = Array.from({ length: requestedCount }, (_, index) => { const base = baseQuestions[index % baseQuestions.length]; return index < baseQuestions.length ? base : { ...base, question: `${base.question} Practice ${index + 1}.`, }; }); return { type: "quiz", topic, output: { quiz_title: `${topic} quick test`, questions, }, trust_note: payload.source_ids?.length ? `${OFFLINE_TRUST_NOTE} Using ${payload.source_ids.length} selected sources.` : OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, is_fallback: true, generation_time_ms: 0, cache_hit: false, }; } function fallbackFlashcards(payload: BrainGeneratePayload): BrainGenerationResponse<{ cards: FlashcardResponseItem[] }> { const topic = payload.topic || "this topic"; return { type: "flashcards", topic, output: { cards: [ { front: `Define ${topic}`, back: "A crisp one-line definition with exact keywords.", hint: "Start with the core process.", tag: "definition", memory_hook: "One line first, details later." }, { front: "Exam answer order", back: "Definition > principle > keywords > example or diagram.", hint: "Structure scores marks.", tag: "answer_writing", memory_hook: "D-P-K-E." }, { front: "What should you underline?", back: "Only the exact scoring words: definition terms, laws, formulas, labels, and named examples.", hint: "Think like an examiner.", tag: "keywords" }, { front: "Common mistake", back: "Writing a long story answer without point-wise structure.", hint: "Length is not the same as marks.", tag: "avoid" }, { front: "Two-mark frame", back: `Point 1: Define ${topic}. Point 2: Add one keyword-based example or application.`, hint: "Two points for two marks.", tag: "2_mark" }, { front: "Four-mark frame", back: "Definition, principle, two key points, then example or diagram.", hint: "Use four scoring beats.", tag: "4_mark" }, { front: "Final recall check", back: `Close the notes and say: meaning of ${topic}, three keywords, one example, one mistake to avoid.`, hint: "No looking.", tag: "recall" }, { front: "When stuck", back: "Ask DocDoe for a simpler explanation, then quiz only the weak part.", hint: "Recover fast.", tag: "next_step" }, ], }, trust_note: payload.source_ids?.length ? `${OFFLINE_TRUST_NOTE} Using ${payload.source_ids.length} selected sources.` : OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function fallbackLastNightPlan(payload: BrainGeneratePayload): BrainGenerationResponse { const topic = payload.topic || "your chapter"; const output: BrainLastNightPlanOutput = { time_blocks: [ { label: "Chapter pass", minutes: 45 }, { label: "Definitions + keywords", minutes: 35 }, { label: "Exam answer practice", minutes: 60 }, { label: "Quick self-test", minutes: 30 }, { label: "Final revision", minutes: 20 }, ], must_study: [`Core definition of ${topic}`, "Important keywords", "Mark-wise answer frames"], skip_now: ["Deep extra reading", "Unseen long derivations unless your teacher marked them"], easy_marks: ["Direct definitions", "Diagram labels", "Short keyword answers"], revision_keywords: ["definition", "formula", "diagram", "keywords"], final_checklist: ["Definition is crisp", "Keywords are ready", "One answer practiced", "Sleep plan is calm"], answer_writing_formula: ["Definition", "Keyword", "Point", "Example"], calming_note: "You do not need to master everything tonight. Lock the scoring parts first.", trust_note: "PYQ data is not available yet. DocDoe is using your source, topic, goal, and time.", }; return { type: "last_night_plan", topic, output, trust_note: output.trust_note, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } function durationForVideoMode(mode: VideoMode) { if (mode === "quick_concept") return { range: "5 minutes", minutes: 5, planningOnly: false }; if (mode === "exam_focus") return { range: "10 minutes", minutes: 10, planningOnly: false }; if (mode === "deep_masterclass") return { range: "30 minutes", minutes: 30, planningOnly: false }; return { range: "30 minutes", minutes: 30, planningOnly: false }; } function fallbackVideoPlan(payload: BrainVideoPlanPayload): BrainVideoPlanResponse { const duration = durationForVideoMode(payload.video_mode); return { title: `${payload.topic} - ${payload.video_mode === "quick_concept" ? "Quick Concept" : payload.video_mode === "exam_focus" ? "Exam Focus" : payload.video_mode === "deep_masterclass" ? "Deep Masterclass" : "Full Chapter War Mode"}`, video_mode: payload.video_mode, target_duration_range: duration.range, estimated_duration_minutes: duration.minutes, render_supported: !duration.planningOnly, planning_only: duration.planningOnly, scenes: [ { scene_type: "intro", title: `What is ${payload.topic}?`, narration: `Start with the simplest meaning of ${payload.topic}, then connect it to the exam question style.`, visual_direction: payload.teaching_style, on_screen_text: payload.topic, duration_seconds: 45, subtitle_segments: [`DocDoe explains ${payload.topic} from the basics.`], exam_tip: "Define the concept in one line before expanding.", pyq_connection: null, }, { scene_type: "exam_focus", title: "Scoring points", narration: "Turn the concept into short mark-wise answers.", visual_direction: "clean_explainer", on_screen_text: "Definition -> Keywords -> Example", duration_seconds: 80, subtitle_segments: ["Use definition, keywords, and one clean example."], exam_tip: "Underline keywords in the final answer.", pyq_connection: null, }, { scene_type: "worked_example", title: "One exam-style example", narration: "Show one short answer and point out exactly where marks are earned.", visual_direction: "split_answer_breakdown", on_screen_text: "Where the marks are", duration_seconds: 95, subtitle_segments: ["Definition earns the first mark.", "Keywords and example complete the answer."], exam_tip: "Practice one answer immediately after watching.", pyq_connection: null, }, { scene_type: "recall", title: "Pause and recall", narration: "Ask the student to pause, close notes, and recall the definition, three keywords, and one mistake.", visual_direction: "clean_recall_screen", on_screen_text: "Pause: 3 keywords + 1 mistake", duration_seconds: 60, subtitle_segments: ["Pause the video.", "Say three keywords before continuing."], exam_tip: "Active recall beats rereading.", pyq_connection: null, }, ], narration_style: payload.teaching_style, visual_style: payload.teaching_style === "anime_tutor" ? "anime_visual_explainer" : "clean_explainer", exam_focus: payload.video_mode !== "quick_concept", answer_cards: [ { label: "Exam keywords", items: ["definition", "keywords", "diagram", "example"] }, { label: "Answer frame", items: ["Start with the definition", "Add the principle or formula", "Use two short points", "End with an example"] }, ], quiz_moments: ["Pause and answer one 2-mark question.", "Recall three keywords without looking.", "Spot the common mistake before the final recap."], revision_keywords: ["definition", "keywords", "example", "diagram", "common mistake"], duration_validation: {}, trust_note: OFFLINE_TRUST_NOTE, evidence_label: OFFLINE_EVIDENCE_LABEL, }; } export function analyzeStudyRequest(payload: StudyRequestAnalyzePayload) { if (!payload.raw_text.trim()) { throw new ApiError({ message: "Please describe what you want to study.", status: 400, body: null, }); } return withDemoFallback( () => apiPost("/study/analyze-request", payload), () => inferAnalysis(payload), ); } export function saveStudyProfile(payload: StudyProfilePayload) { return withDemoFallback( () => apiPost("/study-profile", payload), () => fallbackProfile(payload), ); } export function getStudyProfile() { return withDemoFallback( () => apiGet("/study-profile/me"), () => fallbackProfile({}), ); } export function createTextSource(payload: TextSourcePayload) { return withDemoFallback( () => apiPost("/sources/text", payload), () => localTextSource(payload), ); } export async function listBrainSourcesAsDocuments() { return withDemoFallback( async () => { const result = await apiGet("/sources"); return result.sources.map(brainSourceToApiDocument); }, () => getLocalDocuments(), ); } export function generateStudyPath(payload: StudyPathGeneratePayload) { return withDemoFallback( () => apiPost("/study-path/generate", payload, { timeoutMs: 60000 }), () => fallbackStudyPath(payload), ); } export function askDocDoe(payload: BrainAskPayload, options?: { signal?: AbortSignal }) { return withDemoFallback( () => apiPost("/ask", payload, { timeoutMs: 60000, signal: options?.signal }), () => fallbackAsk(payload), ); } /** * Stream /ask tokens via SSE for real-time StudyCast feel. * Uses fetch + reader (EventSource can't easily do POST+auth). */ export async function streamAskDocDoe( payload: BrainAskPayload, callbacks: { onDelta: (token: string) => void; onDone: (meta: { model: string; isFallback?: boolean }) => void; onError: (message: string) => void; }, signal?: AbortSignal, ): Promise { const authToken = (await import("@/lib/auth/auth-client")).getAuthToken(); const streamSignal = AbortSignal.any([ AbortSignal.timeout(120_000), ...(signal ? [signal] : []), ]); let response: Response; try { response = await fetch(`${(await import("@/lib/api/client")).getApiBaseUrl()}/ask/stream`, { method: "POST", signal: streamSignal, headers: { "Content-Type": "application/json", Accept: "text/event-stream", ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}), }, body: JSON.stringify(payload), }); } catch { if (streamSignal.aborted) { callbacks.onError(signal?.aborted ? "Response stopped." : "The response timed out. Please retry."); return; } callbacks.onError("Ask stream unreachable."); return; } if (!response.ok) { callbacks.onError("Could not stream ask answer."); return; } const reader = response.body?.getReader(); if (!reader) { callbacks.onError("Stream unsupported."); return; } const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) { callbacks.onError("The response ended before it was complete. Please retry."); return; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() ?? ""; for (const line of lines) { if (!line.startsWith("data: ")) continue; const raw = line.slice(6).trim(); if (!raw) continue; try { const ev = JSON.parse(raw) as Record; if (ev.error) { callbacks.onError(String(ev.error)); return; } if (ev.delta) callbacks.onDelta(String(ev.delta)); if (ev.done) { callbacks.onDone({ model: String(ev.model ?? "docdoe"), isFallback: !!ev.is_fallback }); return; } } catch {} } } } catch { callbacks.onError(streamSignal.aborted ? (signal?.aborted ? "Response stopped." : "The response timed out. Please retry.") : "Ask stream lost. Please retry."); } finally { await reader.cancel().catch(() => undefined); reader.releaseLock(); } } export function generateBrainExamAnswer(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/exam-answer", payload, { timeoutMs: 60000 }), () => fallbackExamAnswer(payload), ); } export function generateBrainAnswerCorrection(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/answer-correction", payload, { timeoutMs: 90000 }), () => fallbackAnswerCorrection(payload), ); } export function generateBrainPyqAnalysis(payload: BrainGeneratePayload) { const output: BrainPyqAnalysisOutput = { summary: "Upload PYQ papers to analyze repeated patterns.", data_source_label: "No uploaded PYQ evidence", total_questions_found: 0, years_seen: [], year_count: 0, is_full_ten_year_analysis: false, coverage_note: "Upload PYQ papers to analyze repeated patterns.", pattern_note: "No uploaded PYQ questions matched this request.", repeated_topics: [], marks_distribution: [], answer_type_distribution: [], sample_questions: [], evidence_question_ids: [], confidence: 0, next_step: "Upload previous year question papers for this subject or chapter.", }; return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/pyq-analysis", payload, { timeoutMs: 60000 }), () => ({ type: "pyq_analysis", topic: payload.topic ?? "", output, trust_note: output.coverage_note, evidence_label: "No uploaded PYQ evidence", is_fallback: true, }), ); } export function generateBrainNotes(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/notes", payload, { timeoutMs: 60000 }), () => fallbackNotes(payload), ); } export function generateBrainQuiz(payload: BrainGeneratePayload) { const requestedCount = Number(payload.options?.num_questions ?? payload.options?.question_count ?? 5) || 5; const timeoutMs = requestedCount >= 10 ? 240000 : 90000; return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/quiz", payload, { timeoutMs }), () => fallbackQuiz(payload), ); } export function generateBrainFlashcards(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/flashcards", payload, { timeoutMs: 60000 }), () => fallbackFlashcards(payload), ); } export function generateBrainExamMode(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/exam-mode", payload, { timeoutMs: 60000 }), () => fallbackExamAnswer(payload), ); } export function generateBrainLastNightPlan(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/last-night-plan", payload, { timeoutMs: 60000 }), () => fallbackLastNightPlan(payload), ); } export function generateBrainVideoPlan(payload: BrainVideoPlanPayload) { return withDemoFallback( () => apiPost("/video-generator/plan", payload, { timeoutMs: 180000 }), () => fallbackVideoPlan(payload), ); } export type DeepChapterOutput = { syllabus_position: string; chapter_title: string; what_this_chapter_is_about: string; why_students_struggle: string; learning_path: string[]; prerequisite_topics: string[]; deep_concept_sections: Array<{ name: string; simple_explanation: string; deep_explanation: string; real_example: string; equation: string; why_it_matters: string; exam_use: string; common_mistake: string; practice_question: string; }>; derivations: Array<{ name: string; syllabus_position: string; prerequisite: string; symbols: string[]; assumptions: string[]; steps: string[]; final_formula: string; unit_check: string; common_mistakes: string[]; board_answer_format: string; }>; formulas: Array<{ name: string; formula: string; meaning: string; unit: string; when_to_use: string; condition: string }>; numerical_patterns: Array<{ pattern_name: string; formula_used: string; method: string; example: string; common_mistakes: string[] }>; diagrams_needed: string[]; pyq_patterns: Array<{ question: string; marks: string; year: string; topic: string }>; pyq_note: string; board_answer_keywords: string[]; common_mistakes: string[]; important_questions: string[]; practice_questions: string[]; quick_revision_box: string[]; next_best_action: string; source_used: string; }; export type VisualLessonOutput = { lesson_title: string; syllabus_position: string; learning_goal: string; visual_style: string; concept_flow: string[]; derivation_frames: Array<{ frame_number: number; teaching_purpose: string; visual_type: string; visual_description: string; on_screen_text: string; narration: string; why_this_helps: string; common_misunderstanding_fixed: string; exam_connection: string; duration_seconds: number; }>; diagram_frames: Array<{ frame_number: number; teaching_purpose: string; visual_type: string; visual_description: string; on_screen_text: string; narration: string; why_this_helps: string; exam_connection: string; duration_seconds: number; }>; numerical_frames: Array<{ frame_number: number; teaching_purpose: string; visual_type: string; visual_description: string; on_screen_text: string; narration: string; why_this_helps: string; exam_connection: string; duration_seconds: number; }>; recap_frames: Array<{ frame_number: number; teaching_purpose: string; visual_type: string; visual_description: string; on_screen_text: string; narration: string; why_this_helps: string; exam_connection: string; duration_seconds: number; }>; practice_frames: Array<{ frame_number: number; teaching_purpose: string; visual_type: string; visual_description: string; on_screen_text: string; narration: string; why_this_helps: string; exam_connection: string; duration_seconds: number; }>; total_estimated_seconds: number; total_frames: number; narration_script: string; }; export type StudyIntentOutput = { raw_text: string; board: string; class_level: string; stream: string; subject: string; chapter: string; topic: string; goal: string; urgency: string; language: string; requested_mode: string; confidence: number; missing_fields: string[]; clarification_question: string; }; export function generateBrainDeepChapter(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/deep-chapter", payload, { timeoutMs: 90000 }), () => ({ type: "deep_chapter", topic: payload.topic ?? "", output: {} as DeepChapterOutput, trust_note: "Offline preview.", is_fallback: true }), ); } export function generateBrainVisualLesson(payload: BrainGeneratePayload) { return withDemoFallback( () => apiPost, BrainGeneratePayload>("/generate/visual-lesson", payload, { timeoutMs: 90000 }), () => ({ type: "visual_lesson", topic: payload.topic ?? "", output: {} as VisualLessonOutput, trust_note: "Offline preview.", is_fallback: true }), ); }