import { askDocDoe, generateBrainDeepChapter, generateBrainExamAnswer, generateBrainFlashcards, generateBrainLastNightPlan, generateBrainNotes, generateBrainPyqAnalysis, generateBrainQuiz, generateBrainVisualLesson, generateStudyPath, } from "@/lib/api/brain"; import type { ApiDocument, BrainGeneratePayload } from "@/lib/api/types"; import { buildStructuredStudyResponse, shouldUseStructuredStudyResponse } from "@/lib/phase3-learning"; import type { StudyIntent } from "@/lib/study-intents"; import { askModeForIntent, type StudyBrainContext } from "./study-brain-context"; import type { StudyCard, StudyCardMeta } from "./types"; export type RunStudyGenerationInput = { intent: StudyIntent; prompt: string; topic?: string | null; sourceIds: string[]; selectedDocuments: ApiDocument[]; brainContext?: StudyBrainContext; /** * When true, source IDs are stripped before calling the API. * Use for "general_answer" mode where the user asked a topic * question without explicitly requesting their uploaded material. */ generalMode?: boolean; }; function revisionTimeLeftForPrompt(prompt: string) { const value = prompt.toLowerCase(); if (/\b(30\s*min|half\s*hour)\b/.test(value)) return "30_minutes"; if (/\b(1\s*hour|one\s*hour|60\s*min)\b/.test(value)) return "1_hour"; if (/\b(2\s*hours?|two\s*hours?)\b/.test(value)) return "2_hours"; if (/\b(3\s*hours?|three\s*hours?)\b/.test(value)) return "3_hours"; if (/\b(5\s*hours?|five\s*hours?|half\s*day)\b/.test(value)) return "5_hours"; if (/\b(tonight|one\s+night|overnight|cram|last\s+night)\b/.test(value)) return "tonight"; if (/\b(tomorrow|next\s+day|1\s*day|one\s*day)\b/.test(value)) return "tomorrow"; return "tonight"; } function generationPayload( generationTopic: string, sourceIds: string[], brainContext?: StudyBrainContext, extra: Partial = {}, ): BrainGeneratePayload { return { topic: generationTopic, source_id: sourceIds[0] ?? null, source_ids: sourceIds, subject: brainContext?.subject ?? null, study_profile_id: brainContext?.studyProfileId ?? null, level: brainContext?.level ?? null, language_preference: brainContext?.languagePreference ?? null, ...extra, }; } const GENERAL_EVIDENCE_LABEL = "No source attached - general answer"; export function metaFromResponse(response: { trust_note?: string | null; evidence_label?: string | null; is_fallback?: boolean; suggested_actions?: string[]; citations?: unknown[]; }, sourceIds: string[] = []): StudyCardMeta { const trustNote = response.trust_note ?? null; const evidenceLabel = response.evidence_label ?? null; const citations = Array.isArray(response.citations) ? response.citations : []; const looksSourceBacked = evidenceLabel ? /uploaded|source|material|paper/i.test(evidenceLabel) : false; const saysNoSource = trustNote ? /no source attached|not source-grounded|generic/i.test(trustNote) : false; const shouldUseGeneralLabel = sourceIds.length === 0 && citations.length === 0 && (saysNoSource || looksSourceBacked); return { trustNote, evidenceLabel: shouldUseGeneralLabel ? GENERAL_EVIDENCE_LABEL : evidenceLabel, isFallback: Boolean(response.is_fallback), suggestedActions: response.suggested_actions ?? [], }; } export async function runStudyGeneration({ intent, prompt, topic, sourceIds: rawSourceIds, selectedDocuments, brainContext, generalMode = false, }: RunStudyGenerationInput): Promise { const sourceIds = generalMode ? [] : rawSourceIds; const generationTopic = topic?.trim() || prompt; if (sourceIds.length === 0 && shouldUseStructuredStudyResponse(prompt, intent)) { const data = buildStructuredStudyResponse(prompt); return { kind: "structured_study_response", data, topic: data.topic || generationTopic, meta: { trustNote: "Frontend fallback. Backend structured chapter contract is still a TODO.", evidenceLabel: "No source attached - Study Chat fallback", isFallback: true, }, }; } if (intent === "analyze_pyq" || intent === "predict_questions") { const response = await generateBrainPyqAnalysis( generationPayload(generationTopic, sourceIds, brainContext, { subject: selectedDocuments[0]?.subject ?? brainContext?.subject ?? null, options: { raw_text: prompt }, }), ); return { kind: "pyq_analysis", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "generate_notes" || intent === "show_formulas") { const response = await generateBrainNotes( generationPayload(generationTopic, sourceIds, brainContext, { options: { mode: intent === "show_formulas" ? "formulas" : "smart_notes" }, }), ); return { kind: "notes", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "generate_quiz") { const response = await generateBrainQuiz( generationPayload(generationTopic, sourceIds, brainContext, { options: { num_questions: 8 }, }), ); return { kind: "quiz", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "generate_flashcards") { const response = await generateBrainFlashcards( generationPayload(generationTopic, sourceIds, brainContext, { options: { card_count: 10 }, }), ); return { kind: "flashcards", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "generate_exam_answer") { const response = await generateBrainExamAnswer( generationPayload(generationTopic, sourceIds, brainContext), ); return { kind: "exam_answer", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "create_revision_plan") { const response = await generateBrainLastNightPlan( generationPayload(generationTopic, sourceIds, brainContext, { time_left: revisionTimeLeftForPrompt(prompt), }), ); return { kind: "revision_plan", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "continue_learning") { const response = await generateStudyPath({ raw_text: prompt, source_id: sourceIds[0] ?? null, source_ids: sourceIds, subject: brainContext?.subject ?? selectedDocuments[0]?.subject ?? null, topic: generationTopic, goal: "School exam tuition path", time_left: "next exam", use_my_profile: true, level: brainContext?.level ?? null, language_preference: brainContext?.languagePreference ?? null, }); return { kind: "study_path", data: response, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "create_video_plan" || intent === "render_video") { const response = await generateBrainVisualLesson( generationPayload(generationTopic, sourceIds, brainContext, { options: { raw_text: prompt, mode: "visual_lesson" }, }), ); return { kind: "visual_lesson", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } if (intent === "teach_topic" || intent === "ask_doubt" || intent === "explain_source" || intent === "summarize_source" || intent === "use_syllabus_chapter") { const response = await generateBrainDeepChapter( generationPayload(generationTopic, sourceIds, brainContext, { options: { raw_text: prompt }, }), ); return { kind: "deep_chapter", data: response.output, topic: response.topic || generationTopic, meta: metaFromResponse(response, sourceIds), }; } const response = await askDocDoe({ question: prompt, source_id: sourceIds[0] ?? null, source_ids: sourceIds, mode: askModeForIntent(intent, prompt), level: brainContext?.level ?? null, language_preference: brainContext?.languagePreference ?? null, study_profile_id: brainContext?.studyProfileId ?? null, }); return { kind: "explanation", data: response, topic: generationTopic, intent, meta: metaFromResponse(response, sourceIds), }; }