Download src/lib/api/studio.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 23.6 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/studio.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/api/studio.ts
-
curl -L -o studio.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/studio.ts
23.6 kB
| import type { StudioToolId } from "@/components/dashboard/studioTools"; | |
| import { | |
| generateBrainAnswerCorrection, | |
| generateBrainExamAnswer, | |
| generateBrainFlashcards, | |
| generateBrainLastNightPlan, | |
| generateBrainNotes, | |
| generateBrainQuiz, | |
| generateBrainVideoPlan, | |
| generateStudyPath, | |
| } from "@/lib/api/brain"; | |
| import { getStudyPath, getWhatToStudy } from "@/lib/api/intelligence"; | |
| import { | |
| normalizeExamAnswer, | |
| normalizeFlashcards, | |
| normalizeNotes, | |
| normalizePYQAnalysis, | |
| normalizeQuiz, | |
| normalizeStudyPlan, | |
| normalizeVideoPlan, | |
| } from "@/lib/api/normalizers"; | |
| import { analyzeStructuredPreviousPapers, listPreviousPapers } from "@/lib/api/previous-papers"; | |
| import { createVideoScenePlan } from "@/lib/api/video"; | |
| import type { | |
| BrainAnswerCorrectionOutput, | |
| BrainGeneratePayload, | |
| ExamModeAnswer, | |
| ExamModeFourMarkAnswer, | |
| ExamModeTwoMarkAnswer, | |
| StudentLevel, | |
| StudyPathPlanType, | |
| VideoScene, | |
| } from "@/lib/api/types"; | |
| type SourceValidationError = { | |
| valid: false; | |
| error: string; | |
| }; | |
| type SourceValidationResult = { valid: true } | SourceValidationError; | |
| type SourceWithStatus = { | |
| id: string; | |
| status?: "uploaded" | "processing" | "ready" | "failed"; | |
| }; | |
| export type AnswerCorrectionInput = { | |
| question: string; | |
| studentAnswer: string; | |
| marks: "1" | "2" | "3" | "4" | "5" | "6" | "not_sure"; | |
| subject?: string; | |
| chapter?: string; | |
| }; | |
| function validateSourceForTool(documents: SourceWithStatus[], sourceIds: string[]): SourceValidationResult { | |
| if (sourceIds.length === 0) { | |
| return { valid: false, error: "Choose a source first so I can make this from your material." }; | |
| } | |
| const selectedDocuments = documents.filter((doc) => sourceIds.includes(doc.id)); | |
| if (selectedDocuments.length === 0) { | |
| return { valid: false, error: "Selected source not found. Please choose a valid source." }; | |
| } | |
| const processingSources = selectedDocuments.filter((doc) => doc.status === "processing" || doc.status === "uploaded"); | |
| if (processingSources.length > 0) { | |
| return { valid: false, error: "This source is still being read. I'll unlock source-based tools when it's ready." }; | |
| } | |
| const failedSources = selectedDocuments.filter((doc) => doc.status === "failed"); | |
| if (failedSources.length > 0) { | |
| return { valid: false, error: "I couldn't read this file properly. Try retrying extraction or upload a clearer file." }; | |
| } | |
| // If any source has no status, treat it as potentially not ready | |
| const unknownStatusSources = selectedDocuments.filter((doc) => !doc.status); | |
| if (unknownStatusSources.length > 0 && unknownStatusSources.length === selectedDocuments.length) { | |
| return { valid: false, error: "Source status unknown. Please wait for the source to finish processing." }; | |
| } | |
| return { valid: true }; | |
| } | |
| export type StudioRunSection = { | |
| title: string; | |
| items: string[]; | |
| variant?: "default" | "caution" | "action" | "keywords"; | |
| }; | |
| export type StudioRunResult = { | |
| toolId: StudioToolId; | |
| title: string; | |
| summary: string; | |
| sections: StudioRunSection[]; | |
| status: "ready" | "empty" | "fallback"; | |
| generationTimeMs?: number | null; | |
| cacheHit?: boolean; | |
| raw?: unknown; | |
| evidenceLabel?: string | null; | |
| }; | |
| type RunStudioToolInput = { | |
| toolId: StudioToolId; | |
| documentId: string; | |
| sourceIds?: string[]; | |
| sourceTitle: string; | |
| language?: string; | |
| studentLevel?: StudentLevel; | |
| weakAreas?: string[]; | |
| goal?: string; | |
| timeLeft?: string; | |
| topic?: string; | |
| studyProfileId?: string | null; | |
| quizQuestionCount?: number; | |
| documents?: SourceWithStatus[] | undefined; | |
| answerCorrection?: AnswerCorrectionInput; | |
| }; | |
| const toolTitles: Record<StudioToolId, string> = { | |
| "smart-notes": "Teach this syllabus topic", | |
| "pyq-intelligence": "Show exam pattern from uploaded PYQs", | |
| "ai-quiz": "Test me on this topic", | |
| flashcards: "Lock exam keywords", | |
| "study-videos": "Teach with a visual lesson", | |
| "exam-answer": "Make board-answer version", | |
| "answer-correction": "Correct my answer", | |
| "last-night-prep": "Tell me what to study tonight", | |
| "study-plan": "Build my chapter roadmap", | |
| "what-to-study": "What To Study First", | |
| "check-level": "Check My Level", | |
| "exam-war-mode": "Exam War Mode", | |
| }; | |
| function baseResult(input: RunStudioToolInput, summary: string, sections: StudioRunSection[], raw?: unknown, evidenceLabel?: string | null): StudioRunResult { | |
| return { | |
| toolId: input.toolId, | |
| title: `${toolTitles[input.toolId]}: ${input.sourceTitle}`, | |
| summary, | |
| sections, | |
| status: "ready", | |
| raw, | |
| evidenceLabel: evidenceLabel ?? null, | |
| }; | |
| } | |
| function answerToLine(answer: ExamModeAnswer | ExamModeTwoMarkAnswer | ExamModeFourMarkAnswer | { question?: string; answer?: string }) { | |
| if ("question" in answer && "answer" in answer) return `${answer.question}: ${answer.answer}`; | |
| return "Mark-wise answer outline"; | |
| } | |
| function planTypeForTool(toolId: StudioToolId): StudyPathPlanType { | |
| if (toolId === "last-night-prep") return "1h"; | |
| if (toolId === "exam-war-mode") return "5h"; | |
| return "7d"; | |
| } | |
| function evidenceLabelFromRaw(raw: unknown): string | null { | |
| if (raw && typeof raw === "object" && "evidence_label" in raw) { | |
| const label = (raw as { evidence_label?: unknown }).evidence_label; | |
| return typeof label === "string" ? label : null; | |
| } | |
| return null; | |
| } | |
| // The title must describe what the analysis actually did, never a span the data | |
| // does not establish. The analyzer is upload-gated: it compares only the user's | |
| // uploaded and verified papers and refuses pattern analysis below 2 years | |
| // (backend/app/services/pyq_pattern_analyzer.py). "10-year" must not appear | |
| // unless a genuine verified evidence label (8+ distinct years) is present. | |
| function pyqTitleFromEvidence(label: string | null, paperCount: number) { | |
| const text = label?.toLowerCase() ?? ""; | |
| const hasVerifiedEvidence = | |
| text.includes("verified") && !text.includes("not verified") && !text.includes("unverified"); | |
| if (hasVerifiedEvidence) return "Verified multi-year PYQ signals"; | |
| if (paperCount === 1) return "Signals from this paper"; | |
| return "Signals across uploaded papers"; | |
| } | |
| function uploadedPaperEvidenceLabel(paperCount: number) { | |
| return `Based on ${paperCount} uploaded question paper${paperCount !== 1 ? "s" : ""}`; | |
| } | |
| export async function runStudioTool(input: RunStudioToolInput): Promise<StudioRunResult> { | |
| const language = input.language ?? "English"; | |
| const sourceIds = input.sourceIds?.length ? input.sourceIds : [input.documentId]; | |
| // Validate sources if documents are provided | |
| if (input.documents && input.documents.length > 0) { | |
| const validation = validateSourceForTool(input.documents, sourceIds); | |
| if (!validation.valid) { | |
| return { | |
| toolId: input.toolId, | |
| title: `${toolTitles[input.toolId]}: ${input.sourceTitle}`, | |
| summary: validation.error, | |
| sections: [{ title: "Error", items: [validation.error], variant: "caution" as const }], | |
| status: "empty", | |
| }; | |
| } | |
| } | |
| const topic = input.topic || input.sourceTitle.replace(/^\d+\s+selected\s+sources$/i, "selected sources"); | |
| const brainPayload: BrainGeneratePayload = { | |
| topic, | |
| subject: undefined, | |
| source_id: input.documentId, | |
| source_ids: sourceIds, | |
| study_profile_id: input.studyProfileId ?? null, | |
| level: input.studentLevel ?? "intermediate", | |
| goal: input.goal ?? "good marks", | |
| language_preference: language, | |
| options: { tool: input.toolId, weak_areas: input.weakAreas ?? [] }, | |
| }; | |
| if (input.toolId === "smart-notes") { | |
| const result = await generateBrainNotes({ ...brainPayload, options: { mode: "smart_notes" } }); | |
| const notes = normalizeNotes(result.output); | |
| const sections: StudioRunSection[] = [ | |
| { title: "Key points", items: notes.key_points }, | |
| { title: "Exam focus", items: notes.exam_focus ?? notes.exam_keywords ?? [] }, | |
| ...(notes.scoring_keywords?.length ? [{ title: "Scoring keywords", items: notes.scoring_keywords, variant: "keywords" as const }] : []), | |
| ...(notes.common_mistakes?.length ? [{ title: "Common mistakes", items: notes.common_mistakes }] : []), | |
| { title: "Last-minute revision", items: notes.last_minute_revision ?? [] }, | |
| ...(notes.ten_minute_revision?.length ? [{ title: "10-min revision", items: notes.ten_minute_revision }] : []), | |
| ...(notes.caution_note ? [{ title: "Caution", items: [notes.caution_note], variant: "caution" as const }] : []), | |
| ...(notes.next_study_action ? [{ title: "Next move", items: [notes.next_study_action], variant: "action" as const }] : []), | |
| ]; | |
| return baseResult( | |
| input, | |
| notes.source_summary ?? notes.summary ?? notes.student_level_summary ?? "DocDoe created source-based revision notes.", | |
| sections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| } | |
| if (input.toolId === "ai-quiz") { | |
| const questionCount = input.quizQuestionCount ?? 5; | |
| const result = await generateBrainQuiz({ | |
| ...brainPayload, | |
| options: { | |
| ...(brainPayload.options ?? {}), | |
| difficulty: "exam", | |
| num_questions: questionCount, | |
| question_count: questionCount, | |
| }, | |
| }); | |
| const questions = normalizeQuiz(result.output); | |
| const traps = questions.map((q) => q.common_trap).filter((t): t is string => Boolean(t)); | |
| const quizSections: StudioRunSection[] = [ | |
| { title: "Practice questions", items: questions.map((question) => question.question) }, | |
| ...(traps.length ? [{ title: "Common traps", items: traps, variant: "caution" as const }] : []), | |
| ]; | |
| const quizResult = baseResult( | |
| input, | |
| questions.length > 0 | |
| ? result.cache_hit | |
| ? "DocDoe reused your recent quiz instantly." | |
| : questionCount >= 10 | |
| ? `DocDoe created a ${questionCount}-question practice set from your selected source.` | |
| : "DocDoe created a short practice quiz from the active source." | |
| : "DocDoe could not create quiz questions from this source yet.", | |
| quizSections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| return { | |
| ...quizResult, | |
| generationTimeMs: result.generation_time_ms ?? null, | |
| cacheHit: Boolean(result.cache_hit), | |
| }; | |
| } | |
| if (input.toolId === "flashcards") { | |
| const result = await generateBrainFlashcards({ ...brainPayload, options: { card_count: 8 } }); | |
| const cards = normalizeFlashcards(result.output); | |
| const hooks = cards.map((c) => c.memory_hook).filter((h): h is string => Boolean(h)); | |
| const examUses = cards.map((c) => c.exam_use).filter((u): u is string => Boolean(u)); | |
| const flashSections: StudioRunSection[] = [ | |
| { title: "Cards", items: cards.map((card) => `${card.front} — ${card.back}`) }, | |
| ...(hooks.length ? [{ title: "Memory hooks", items: hooks }] : []), | |
| ...(examUses.length ? [{ title: "Exam use", items: examUses }] : []), | |
| ]; | |
| return baseResult( | |
| input, | |
| cards.length > 0 | |
| ? "DocDoe created active-recall cards for quick revision." | |
| : "DocDoe could not create flashcards from this source yet.", | |
| flashSections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| } | |
| if (input.toolId === "exam-answer") { | |
| const result = await generateBrainExamAnswer(brainPayload); | |
| const output = normalizeExamAnswer(result.output); | |
| const answers = [ | |
| ...(output?.one_mark ?? []), | |
| ...(output?.two_mark ?? []), | |
| ...(output?.four_mark ?? []), | |
| ...result.output.six_mark, | |
| ...(output?.one_mark_answers ?? []), | |
| ...(output?.two_mark_answers ?? []), | |
| ...(output?.four_mark_answers ?? []), | |
| ]; | |
| const examSections: StudioRunSection[] = [ | |
| { title: "Mark-wise answers", items: answers.map(answerToLine).slice(0, 6) }, | |
| { title: "Keywords", items: output?.keywords_to_use ?? output?.important_keywords ?? result.output.keywords_to_underline ?? [] }, | |
| ...(result.output.scoring_keywords?.length ? [{ title: "Scoring keywords", items: result.output.scoring_keywords, variant: "keywords" as const }] : []), | |
| { title: "Common mistakes", items: output?.mistakes_to_avoid ?? [result.output.common_mistake] }, | |
| { title: "Examiner tip", items: [result.output.examiner_tip, ...(result.output.answer_writing_formula ?? [])] }, | |
| ...(result.output.caution_note ? [{ title: "Caution", items: [result.output.caution_note], variant: "caution" as const }] : []), | |
| ...(result.output.next_study_action ? [{ title: "Next move", items: [result.output.next_study_action], variant: "action" as const }] : []), | |
| ]; | |
| return baseResult( | |
| input, | |
| "DocDoe shaped this into mark-wise exam answers.", | |
| examSections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| } | |
| if (input.toolId === "answer-correction") { | |
| const correction = input.answerCorrection; | |
| if (!correction?.question.trim() || !correction.studentAnswer.trim()) { | |
| return { | |
| toolId: input.toolId, | |
| title: `${toolTitles[input.toolId]}: ${input.sourceTitle}`, | |
| summary: "Paste the question and your answer before correction.", | |
| sections: [{ title: "Missing input", items: ["Paste the question and your answer before correction."], variant: "caution" as const }], | |
| status: "empty", | |
| }; | |
| } | |
| const result = await generateBrainAnswerCorrection({ | |
| ...brainPayload, | |
| topic: correction.question, | |
| subject: correction.subject || brainPayload.subject, | |
| options: { | |
| ...(brainPayload.options ?? {}), | |
| mode: "answer_correction", | |
| question: correction.question, | |
| student_answer: correction.studentAnswer, | |
| marks: correction.marks, | |
| subject: correction.subject, | |
| chapter: correction.chapter, | |
| }, | |
| }); | |
| const output = result.output as BrainAnswerCorrectionOutput; | |
| const isSourceMismatch = output.source_truth.toLowerCase().includes("not found"); | |
| const correctionSections: StudioRunSection[] = [ | |
| { title: "Syllabus position", items: [output.syllabus_position, output.mark_scheme_assumption, output.score] }, | |
| { title: "What you wrote correctly", items: output.what_correct }, | |
| { title: "Marks lost", items: output.marks_lost, variant: isSourceMismatch ? "caution" : "default" }, | |
| { title: "Missing keywords", items: output.missing_keywords, variant: "keywords" }, | |
| { title: "Corrected board-answer version", items: [output.corrected_board_answer] }, | |
| { title: "How to improve next time", items: output.how_to_improve }, | |
| { title: "Quick retry task", items: [output.quick_retry_task], variant: "action" }, | |
| ...(output.source_evidence?.length ? [{ title: "Source evidence", items: output.source_evidence, variant: "keywords" as const }] : []), | |
| { title: "Source truth", items: [output.source_truth], variant: isSourceMismatch ? "caution" : "default" }, | |
| ]; | |
| return baseResult( | |
| input, | |
| isSourceMismatch | |
| ? output.source_truth | |
| : `${output.score}. DocDoe found the marks lost and rewrote the answer in board format.`, | |
| correctionSections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| } | |
| if (input.toolId === "pyq-intelligence") { | |
| const papers = await listPreviousPapers(); | |
| if (papers.length === 0) { | |
| return { | |
| toolId: input.toolId, | |
| title: "Upload question papers to unlock PYQ signals", | |
| summary: "DocDoe can explain this material, but PYQ trends need actual previous papers. Add past papers for this subject to compare patterns.", | |
| sections: [ | |
| { title: "Upload question paper", items: ["Upload question papers to unlock PYQ signals."] }, | |
| { title: "Continue with material-only questions", items: ["Likely questions from this material"] }, | |
| ], | |
| status: "empty", | |
| evidenceLabel: "Based on uploaded material only", | |
| }; | |
| } | |
| const result = await analyzeStructuredPreviousPapers({ | |
| paper_ids: papers.map((paper) => paper.id), | |
| subject: papers[0]?.subject ?? "Study", | |
| syllabus: papers[0]?.syllabus ?? "Indian Board Exams", | |
| target_exam: "Board exam", | |
| }); | |
| const analysis = normalizePYQAnalysis(result); | |
| const pyqSections: StudioRunSection[] = [ | |
| { | |
| title: "Repeated topics", | |
| items: analysis?.repeated_topics.map((topic) => `${topic.topic}: ${topic.reason}`) ?? [], | |
| }, | |
| { | |
| title: "High probability", | |
| items: analysis?.high_probability_topics.map((topic) => `${topic.topic}: ${topic.reason}`) ?? [], | |
| }, | |
| { title: "Study priority", items: analysis?.study_priority ?? [] }, | |
| ...(analysis?.one_paper_limit_note ? [{ title: "Note", items: [analysis.one_paper_limit_note], variant: "caution" as const }] : []), | |
| ...(analysis?.next_study_action ? [{ title: "Next move", items: [analysis.next_study_action], variant: "action" as const }] : []), | |
| ...(analysis?.caution_note ? [{ title: "Caution", items: [analysis.caution_note], variant: "caution" as const }] : []), | |
| ]; | |
| const evidenceLabel = evidenceLabelFromRaw(result) ?? uploadedPaperEvidenceLabel(papers.length); | |
| const pyqResult = baseResult( | |
| input, | |
| analysis?.summary ?? "DocDoe checked available previous papers for patterns.", | |
| pyqSections, | |
| result, | |
| evidenceLabel, | |
| ); | |
| return { | |
| ...pyqResult, | |
| title: pyqTitleFromEvidence(evidenceLabel, papers.length), | |
| evidenceLabel, | |
| }; | |
| } | |
| if (input.toolId === "what-to-study") { | |
| const result = await getWhatToStudy(input.documentId, input.studentLevel ?? "intermediate", "good marks"); | |
| return baseResult( | |
| input, | |
| "DocDoe prioritized the chapter from your source, goal, and current level.", | |
| [ | |
| { title: "Study first", items: result.must_study_topics.map((topic) => `${topic.topic}: ${topic.reason}`) }, | |
| { title: "Quick wins", items: result.easy_marks.map((topic) => `${topic.topic}: ${topic.reason}`) }, | |
| { title: "Skip for now", items: result.skip_for_now_topics.map((topic) => `${topic.topic}: ${topic.reason}`) }, | |
| ], | |
| result, | |
| result.evidence_label ?? null, | |
| ); | |
| } | |
| if (input.toolId === "study-plan" || input.toolId === "last-night-prep" || input.toolId === "exam-war-mode") { | |
| if (input.toolId === "last-night-prep") { | |
| const result = await generateBrainLastNightPlan({ | |
| ...brainPayload, | |
| time_left: input.timeLeft ?? "5 hours", | |
| }); | |
| const plan = result.output; | |
| const lnpSections: StudioRunSection[] = [ | |
| ...(plan.do_first?.length ? [{ title: "Do first", items: plan.do_first }] : []), | |
| { title: "Time blocks", items: plan.time_blocks.map((block) => `${block.label}: ${block.minutes} min`) }, | |
| ...(plan.if_30_minutes?.length ? [{ title: "If 30 minutes", items: plan.if_30_minutes }] : []), | |
| ...(plan.if_2_hours?.length ? [{ title: "If 2 hours", items: plan.if_2_hours }] : []), | |
| ...(plan.if_half_day?.length ? [{ title: "If half day", items: plan.if_half_day }] : []), | |
| { title: "Must study", items: plan.must_study }, | |
| { title: "Skip for now", items: plan.skip_now }, | |
| ...(plan.skip_or_skim?.length ? [{ title: "Skip or skim", items: plan.skip_or_skim }] : []), | |
| ...(plan.quick_recall_questions?.length ? [{ title: "Quick recall", items: plan.quick_recall_questions }] : []), | |
| { title: "Final checklist", items: plan.final_checklist }, | |
| ...(plan.final_check_before_sleep?.length ? [{ title: "Final check before sleep", items: plan.final_check_before_sleep }] : []), | |
| ...(plan.caution_note ? [{ title: "Caution", items: [plan.caution_note], variant: "caution" as const }] : []), | |
| { title: "Trust note", items: [result.trust_note ?? plan.trust_note ?? "Using your selected source and study goal."] }, | |
| ]; | |
| return baseResult( | |
| input, | |
| plan.calming_note ?? "DocDoe built a calm last-night plan with scoring priorities.", | |
| lnpSections, | |
| result, | |
| result.evidence_label, | |
| ); | |
| } | |
| const planType = planTypeForTool(input.toolId); | |
| const goal = input.toolId === "exam-war-mode" ? "pass" : "good marks"; | |
| const result = sourceIds.length > 1 | |
| ? await generateStudyPath({ | |
| source_id: input.documentId, | |
| source_ids: sourceIds, | |
| topic, | |
| goal, | |
| level: input.studentLevel ?? "intermediate", | |
| language_preference: language, | |
| use_my_profile: true, | |
| }) | |
| : await getStudyPath(input.documentId, planType, input.studentLevel ?? "intermediate", goal); | |
| const plan = "study_timeline" in result | |
| ? { | |
| blocks: result.study_timeline.map((item) => ({ | |
| time_block: `${item.duration_minutes} min`, | |
| topic: item.title, | |
| reason: item.reason, | |
| task: item.task, | |
| output_expected: item.expected_output, | |
| revision_checkpoint: item.actions.join(", "), | |
| })), | |
| } | |
| : normalizeStudyPlan(result); | |
| const studyPathEvidenceLabel = "evidence_label" in result && typeof (result as { evidence_label?: unknown }).evidence_label === "string" | |
| ? (result as { evidence_label: string }).evidence_label | |
| : null; | |
| return baseResult( | |
| input, | |
| `DocDoe built a ${planType} plan with checkpoints and revision blocks.`, | |
| [ | |
| { | |
| title: "Time blocks", | |
| items: plan?.blocks.map((block) => `${block.time_block}: ${block.task}`) ?? [], | |
| }, | |
| { | |
| title: "Why this order", | |
| items: plan?.blocks.map((block) => `${block.topic}: ${block.reason}`) ?? [], | |
| }, | |
| ], | |
| result, | |
| studyPathEvidenceLabel, | |
| ); | |
| } | |
| if (input.toolId === "study-videos") { | |
| const brainPlan = await generateBrainVideoPlan({ | |
| topic, | |
| video_mode: "exam_focus", | |
| teaching_style: "visual_tutor", | |
| language_preference: language, | |
| format: "16:9", | |
| source_id: input.documentId, | |
| source_ids: sourceIds, | |
| level: input.studentLevel ?? "intermediate", | |
| goal: input.goal ?? "good marks", | |
| }); | |
| if (brainPlan.planning_only || !brainPlan.render_supported) { | |
| return baseResult( | |
| input, | |
| "DocDoe created a study video plan. This long video is planned first. Rendering support will be added later.", | |
| [ | |
| { title: "Duration", items: [brainPlan.target_duration_range, `${brainPlan.estimated_duration_minutes} minutes estimated`] }, | |
| { title: "Scene plan", items: brainPlan.scenes.map((scene) => `${scene.title}: ${scene.on_screen_text}`) }, | |
| { title: "Trust note", items: [brainPlan.trust_note] }, | |
| ], | |
| brainPlan, | |
| brainPlan.evidence_label, | |
| ); | |
| } | |
| const result = await createVideoScenePlan({ | |
| document_id: input.documentId, | |
| title: input.sourceTitle, | |
| language, | |
| duration_minutes: 5, | |
| visual_style: "clean_explainer", | |
| video_format: "16:9", | |
| }); | |
| const plan = normalizeVideoPlan(result); | |
| const scenes = "scenes" in (plan ?? {}) ? (plan as { scenes: VideoScene[] }).scenes : []; | |
| return baseResult( | |
| input, | |
| "DocDoe created a teachable scene plan. Long videos can take more time to prepare.", | |
| [{ title: "Scene plan", items: scenes.map((scene) => `${scene.screen_text}: ${scene.visual_hint}`) }], | |
| result, | |
| result.evidence_label ?? null, | |
| ); | |
| } | |
| return { | |
| toolId: input.toolId, | |
| title: `${toolTitles[input.toolId]}: ${input.sourceTitle}`, | |
| summary: "Choose this tool from Studio to continue.", | |
| sections: [{ title: "Next step", items: ["DocDoe is ready when you are."] }], | |
| status: "fallback", | |
| }; | |
| } | |