diff --git a/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts b/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts
index 42f59dafc..ca1a25a18 100644
--- a/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts
+++ b/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts
@@ -1,14 +1,14 @@
export const lessonSections = [
"Title",
"Subject",
- "Key Stage",
- "Learning Outcome",
- "Learning Cycles",
- "Prior Knowledge",
- "Key Learning Points",
+ "Key stage",
+ "Learning outcome",
+ "Learning cycles",
+ "Prior knowledge",
+ "Key learning points",
"Misconceptions",
"Keywords",
- "Starter Quiz",
- "Cycle 1",
- "Exit Quiz",
+ "Starter quiz",
+ "Learning cycle 1",
+ "Exit quiz",
];
diff --git a/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx
new file mode 100644
index 000000000..818d42298
--- /dev/null
+++ b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import { AilaPersistedChat } from "@oakai/aila/src/protocol/schema";
+import { Box, Flex, Grid } from "@radix-ui/themes";
+
+import Layout from "@/components/AppComponents/Layout";
+import Button from "@/components/Button";
+import DialogContents from "@/components/DialogControl/DialogContents";
+import { DialogRoot } from "@/components/DialogControl/DialogRoot";
+import { DownloadButton } from "@/components/DownloadButton";
+import { Icon } from "@/components/Icon";
+
+import { SurveyDialogLauncher } from "./SurveyDialogLauncher";
+import { useDownloadView } from "./useDownloadView";
+
+type DownloadViewProps = Readonly<{
+ chat: AilaPersistedChat;
+ featureFlag: boolean;
+}>;
+export function DownloadView({
+ chat,
+ featureFlag,
+}: Readonly) {
+ const { lessonPlan } = chat;
+ const {
+ lessonSlidesExport,
+ worksheetExport,
+ lessonPlanExport,
+ starterQuizExport,
+ exitQuizExport,
+ additionalMaterialsExport,
+ sections,
+ totalSections,
+ totalSectionsComplete,
+ } = useDownloadView(chat);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
Download resources
+
+ Choose the resources you would like to generate and download.
+
+
+
+
+ lessonPlanExport.start()}
+ title="Lesson"
+ subTitle="Overview of the complete lesson"
+ downloadAvailable={!!lessonPlanExport.readyToExport}
+ downloadLoading={lessonPlanExport.status === "loading"}
+ data={lessonPlanExport.data}
+ exportsType="lessonPlanDoc"
+ data-testid="chat-download-lesson-plan"
+ lesson={lessonPlan}
+ />
+ starterQuizExport.start()}
+ title="Starter quiz"
+ subTitle="Questions and answers to assess prior knowledge"
+ downloadAvailable={!!starterQuizExport.readyToExport}
+ downloadLoading={starterQuizExport.status === "loading"}
+ data={starterQuizExport.data}
+ exportsType="starterQuiz"
+ lesson={lessonPlan}
+ />
+ lessonSlidesExport.start()}
+ data-testid="chat-download-slides-btn"
+ title="Slide deck"
+ subTitle="Learning outcome, keywords and learning cycles"
+ downloadAvailable={lessonSlidesExport.readyToExport}
+ downloadLoading={lessonSlidesExport.status === "loading"}
+ data={lessonSlidesExport.data}
+ exportsType="lessonSlides"
+ lesson={lessonPlan}
+ />
+ worksheetExport.start()}
+ title="Worksheet"
+ subTitle="Practice tasks"
+ downloadAvailable={!!worksheetExport.readyToExport}
+ downloadLoading={worksheetExport.status === "loading"}
+ data={worksheetExport.data}
+ lesson={lessonPlan}
+ exportsType="worksheet"
+ />
+ exitQuizExport.start()}
+ title="Exit quiz"
+ subTitle="Questions and answers to assess understanding"
+ downloadAvailable={!!exitQuizExport.readyToExport}
+ downloadLoading={exitQuizExport.status === "loading"}
+ data={exitQuizExport.data}
+ exportsType="exitQuiz"
+ lesson={lessonPlan}
+ />
+ {lessonPlan.additionalMaterials &&
+ lessonPlan.additionalMaterials !== "None" && (
+ additionalMaterialsExport.start()}
+ title="Additional materials"
+ subTitle="Document containing any additional materials"
+ downloadAvailable={
+ !!additionalMaterialsExport.readyToExport
+ }
+ downloadLoading={
+ additionalMaterialsExport.status === "loading"
+ }
+ data={additionalMaterialsExport.data}
+ exportsType="additionalMaterials"
+ lesson={lessonPlan}
+ />
+ )}
+
+
+
+
+ {`${totalSectionsComplete} of ${totalSections} sections complete`}
+
+
+ {sections.map((section) => {
+ return (
+
+
+
+
+ {section.label}
+
+ );
+ })}
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/nextjs/src/app/aila/[id]/download/SurveyDialogLauncher.ts b/apps/nextjs/src/app/aila/[id]/download/SurveyDialogLauncher.ts
new file mode 100644
index 000000000..d672efb9b
--- /dev/null
+++ b/apps/nextjs/src/app/aila/[id]/download/SurveyDialogLauncher.ts
@@ -0,0 +1,24 @@
+import { useEffect } from "react";
+
+import { usePosthogFeedbackSurvey } from "hooks/surveys/usePosthogFeedbackSurvey";
+
+import { useDialog } from "@/components/AppComponents/DialogContext";
+
+export function SurveyDialogLauncher() {
+ const { survey } = usePosthogFeedbackSurvey({
+ closeDialog: () => null,
+ surveyName: "End of Aila generation survey launch aug24",
+ });
+ const { setDialogWindow } = useDialog();
+
+ useEffect(() => {
+ if (survey) {
+ const timer = setTimeout(() => {
+ setDialogWindow("feedback");
+ }, 3000);
+ return () => clearTimeout(timer);
+ }
+ }, [survey, setDialogWindow]);
+
+ return null;
+}
diff --git a/apps/nextjs/src/app/aila/[id]/download/index.tsx b/apps/nextjs/src/app/aila/[id]/download/index.tsx
deleted file mode 100644
index 49347ebfe..000000000
--- a/apps/nextjs/src/app/aila/[id]/download/index.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-
-import { AilaPersistedChat } from "@oakai/aila/src/protocol/schema";
-import { Box, Flex, Grid } from "@radix-ui/themes";
-import { lessonSections } from "ai-apps/lesson-planner/lessonSection";
-import { usePosthogFeedbackSurvey } from "hooks/surveys/usePosthogFeedbackSurvey";
-
-import { setLessonPlanProgress } from "@/components/AppComponents/Chat/Chat/utils";
-import { useDialog } from "@/components/AppComponents/DialogContext";
-import Layout from "@/components/AppComponents/Layout";
-import Button from "@/components/Button";
-import DialogContents from "@/components/DialogControl/DialogContents";
-import { DialogRoot } from "@/components/DialogControl/DialogRoot";
-import { DownloadButton } from "@/components/DownloadButton";
-import { useExportAdditionalMaterials } from "@/components/ExportsDialogs/useExportAdditionalMaterials";
-import { useExportLessonPlanDoc } from "@/components/ExportsDialogs/useExportLessonPlanDoc";
-import { useExportLessonSlides } from "@/components/ExportsDialogs/useExportLessonSlides";
-import { useExportQuizDoc } from "@/components/ExportsDialogs/useExportQuizDoc";
-import { useExportWorksheetSlides } from "@/components/ExportsDialogs/useExportWorksheetSlides";
-import { Icon } from "@/components/Icon";
-
-interface DownloadPageProps {
- chat: AilaPersistedChat;
- featureFlag: boolean;
-}
-export default function DownloadPage({
- chat,
- featureFlag,
-}: Readonly) {
- return (
-
-
-
- );
-}
-
-export function DownloadPageContents({ chat }: Readonly) {
- const { setDialogWindow } = useDialog();
-
- const [undefinedLessonPlanSections, setUndefinedLessonPlanSections] =
- useState([]);
- const lessonPlan = chat.lessonPlan;
- useEffect(() => {
- setLessonPlanProgress({
- lessonPlan: lessonPlan,
- setUndefinedItems: setUndefinedLessonPlanSections,
- });
- }, [lessonPlan, setUndefinedLessonPlanSections]);
-
- const exportProps = {
- onStart: () => null,
- lesson: lessonPlan,
- chatId: chat.id,
- messageId: chat.messages.length,
- active: true,
- };
-
- const lessonSlidesExport = useExportLessonSlides(exportProps);
-
- const worksheetExport = useExportWorksheetSlides(exportProps);
-
- const lessonPlanExport = useExportLessonPlanDoc(exportProps);
-
- const starterQuizExport = useExportQuizDoc({
- ...exportProps,
- quizType: "starter",
- });
-
- const exitQuizExport = useExportQuizDoc({
- ...exportProps,
- quizType: "exit",
- });
-
- const additionalMaterialsExport = useExportAdditionalMaterials(exportProps);
-
- const { survey } = usePosthogFeedbackSurvey({
- closeDialog: () => null,
- surveyName: "End of Aila generation survey launch aug24",
- });
-
- useEffect(() => {
- if (survey) {
- const timer = setTimeout(() => {
- setDialogWindow("feedback");
- }, 3000);
- return () => clearTimeout(timer);
- }
- }, [survey, setDialogWindow]);
-
- return (
-
-
-
-
-
-
-
-
-
Download resources
-
- Complete all 12 sections of your lesson to unlock resources
- below. If a section is missing, just ask Aila to help you
- complete your lesson.
-
-
-
-
- lessonPlanExport.start()}
- title="Lesson plan"
- subTitle="All sections"
- downloadAvailable={!!lessonPlanExport.readyToExport}
- downloadLoading={lessonPlanExport.status === "loading"}
- data={lessonPlanExport.data}
- exportsType="lessonPlanDoc"
- data-testid="chat-download-lesson-plan"
- lesson={lessonPlan}
- />
- starterQuizExport.start()}
- title="Starter quiz"
- subTitle="Questions and answers"
- downloadAvailable={!!starterQuizExport.readyToExport}
- downloadLoading={starterQuizExport.status === "loading"}
- data={starterQuizExport.data}
- exportsType="starterQuiz"
- lesson={lessonPlan}
- />
- lessonSlidesExport.start()}
- data-testid="chat-download-slides-btn"
- title="Slide deck"
- subTitle="Outcomes, key words, 1-3 learning cycles, summary"
- downloadAvailable={lessonSlidesExport.readyToExport}
- downloadLoading={lessonSlidesExport.status === "loading"}
- data={lessonSlidesExport.data}
- exportsType="lessonSlides"
- lesson={lessonPlan}
- />
- worksheetExport.start()}
- title="Worksheet"
- subTitle="Interactive activities"
- downloadAvailable={!!worksheetExport.readyToExport}
- downloadLoading={worksheetExport.status === "loading"}
- data={worksheetExport.data}
- lesson={lessonPlan}
- exportsType="worksheet"
- />
- exitQuizExport.start()}
- title="Exit quiz"
- subTitle="Questions and answers"
- downloadAvailable={!!exitQuizExport.readyToExport}
- downloadLoading={exitQuizExport.status === "loading"}
- data={exitQuizExport.data}
- exportsType="exitQuiz"
- lesson={lessonPlan}
- />
- {lessonPlan.additionalMaterials &&
- lessonPlan.additionalMaterials !== "None" && (
- additionalMaterialsExport.start()}
- title="Additional materials"
- subTitle="Document containing any additional materials"
- downloadAvailable={
- !!additionalMaterialsExport.readyToExport
- }
- downloadLoading={
- additionalMaterialsExport.status === "loading"
- }
- data={additionalMaterialsExport.data}
- exportsType="additionalMaterials"
- lesson={lessonPlan}
- />
- )}
-
-
-
-
- {`${lessonSections.length - undefinedLessonPlanSections.length}
- of ${lessonSections.length} sections complete`}
-
-
- {lessonSections.map((section) => {
- return (
-
-
-
-
-
- {section.includes("Cycle 1") ? "Cycles 1-3" : section}
-
-
- );
- })}
-
-
-
-
-
-
- );
-}
diff --git a/apps/nextjs/src/app/aila/[id]/download/page.tsx b/apps/nextjs/src/app/aila/[id]/download/page.tsx
index a077481d9..64c13c5ac 100644
--- a/apps/nextjs/src/app/aila/[id]/download/page.tsx
+++ b/apps/nextjs/src/app/aila/[id]/download/page.tsx
@@ -5,7 +5,7 @@ import { type Metadata } from "next";
import { getChatById } from "@/app/actions";
import { serverSideFeatureFlag } from "@/utils/serverSideFeatureFlag";
-import DownloadView from ".";
+import { DownloadView } from "./DownloadView";
export interface DownloadPageProps {
params: {
diff --git a/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts b/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts
new file mode 100644
index 000000000..67692096b
--- /dev/null
+++ b/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts
@@ -0,0 +1,55 @@
+import { AilaPersistedChat } from "@oakai/aila/src/protocol/schema";
+
+import { useProgressForDownloads } from "@/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads";
+import { useExportAdditionalMaterials } from "@/components/ExportsDialogs/useExportAdditionalMaterials";
+import { useExportLessonPlanDoc } from "@/components/ExportsDialogs/useExportLessonPlanDoc";
+import { useExportLessonSlides } from "@/components/ExportsDialogs/useExportLessonSlides";
+import { useExportQuizDoc } from "@/components/ExportsDialogs/useExportQuizDoc";
+import { useExportWorksheetSlides } from "@/components/ExportsDialogs/useExportWorksheetSlides";
+
+export function useDownloadView({
+ id,
+ lessonPlan,
+ messages,
+}: AilaPersistedChat) {
+ const exportProps = {
+ onStart: () => null,
+ lesson: lessonPlan,
+ chatId: id,
+ messageId: messages.length,
+ active: true,
+ };
+
+ const lessonSlidesExport = useExportLessonSlides(exportProps);
+
+ const worksheetExport = useExportWorksheetSlides(exportProps);
+
+ const lessonPlanExport = useExportLessonPlanDoc(exportProps);
+
+ const starterQuizExport = useExportQuizDoc({
+ ...exportProps,
+ quizType: "starter",
+ });
+
+ const exitQuizExport = useExportQuizDoc({
+ ...exportProps,
+ quizType: "exit",
+ });
+
+ const additionalMaterialsExport = useExportAdditionalMaterials(exportProps);
+
+ const { sections, totalSections, totalSectionsComplete } =
+ useProgressForDownloads({ lessonPlan, isStreaming: false });
+
+ return {
+ lessonSlidesExport,
+ worksheetExport,
+ lessonPlanExport,
+ starterQuizExport,
+ exitQuizExport,
+ additionalMaterialsExport,
+ sections,
+ totalSections,
+ totalSectionsComplete,
+ };
+}
diff --git a/apps/nextjs/src/app/aila/help/index.tsx b/apps/nextjs/src/app/aila/help/index.tsx
index 6b64a81be..169a73e15 100644
--- a/apps/nextjs/src/app/aila/help/index.tsx
+++ b/apps/nextjs/src/app/aila/help/index.tsx
@@ -115,7 +115,7 @@ const Help = () => {
include the following sections:
- - Lesson learning outcome
+ - Learning outcome
- Learning cycle outcomes
- Prior knowledge
- Key learning points
diff --git a/apps/nextjs/src/app/quiz-designer/preview/[slug]/preview.tsx b/apps/nextjs/src/app/quiz-designer/preview/[slug]/preview.tsx
index 3d46801c6..b77d2c825 100644
--- a/apps/nextjs/src/app/quiz-designer/preview/[slug]/preview.tsx
+++ b/apps/nextjs/src/app/quiz-designer/preview/[slug]/preview.tsx
@@ -33,7 +33,7 @@ export default function QuizPreview(questions, featureFlag) {
return (
-
Question {index + 1}:
+
Question {index + 1}.
{question.question.value}
diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts
new file mode 100644
index 000000000..8a03c54c5
--- /dev/null
+++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts
@@ -0,0 +1,127 @@
+import { useMemo } from "react";
+
+import { LooseLessonPlan } from "@oakai/aila/src/protocol/schema";
+import { lessonPlanSectionsSchema } from "@oakai/exports/src/schema/input.schema";
+import { ZodIssue } from "zod";
+
+/**
+ * For a given list of Zod issues and lessonPlan fields, checks that none of
+ * the errors pertain to the fields.
+ */
+function getCompleteness(errors: ZodIssue[], fields: string[]) {
+ const hasErrorInSomeField = errors.reduce(
+ (acc, curr) => acc || fields.some((field) => curr.path[0] === field),
+ false,
+ );
+
+ return !hasErrorInSomeField;
+}
+type ProgressSections = {
+ label: string;
+ key: string;
+ complete: boolean;
+}[];
+type ProgressForDownloads = {
+ sections: ProgressSections;
+ totalSections: number;
+ totalSectionsComplete: number;
+};
+
+export function useProgressForDownloads({
+ lessonPlan,
+ isStreaming,
+}: {
+ lessonPlan: LooseLessonPlan;
+ isStreaming: boolean;
+}): ProgressForDownloads {
+ return useMemo(() => {
+ const parsedLessonPlan = lessonPlanSectionsSchema.safeParse(lessonPlan);
+ const errors =
+ parsedLessonPlan.error?.errors.filter((error) => {
+ if (isStreaming) {
+ /**
+ * When we have partial data, we get errors about nested
+ * properties. We want to filter these out during streaming to avoid a
+ * flickering progress bar.
+ */
+ return error.path.length === 1;
+ }
+ if (!isStreaming) {
+ /**
+ * When not streaming, we want to check that each section is successfully
+ * parsed, so we keep all errors.
+ *
+ * This means we know it's in the correct structure for exports.
+ *
+ * Most of the time, this will be the same as the above condition because
+ * we don't expect to have errors in the data structure when Aila is not
+ * streaming.
+ */
+ return true;
+ }
+ }) || [];
+ const sections = [
+ {
+ label: "Lesson details",
+ key: "title",
+ complete: getCompleteness(errors, ["title", "subject", "keyStage"]),
+ },
+ {
+ label: "Learning outcome",
+ key: "learningOutcome",
+ complete: getCompleteness(errors, ["learningOutcome"]),
+ },
+ {
+ label: "Learning cycle outcomes",
+ key: "learningCycles",
+ complete: getCompleteness(errors, ["learningCycles"]),
+ },
+ {
+ label: "Prior knowledge",
+ key: "priorKnowledge",
+ complete: getCompleteness(errors, ["priorKnowledge"]),
+ },
+ {
+ label: "Key learning points",
+ key: "keyLearningPoints",
+ complete: getCompleteness(errors, ["keyLearningPoints"]),
+ },
+ {
+ label: "Misconceptions",
+ key: "misconceptions",
+ complete: getCompleteness(errors, ["misconceptions"]),
+ },
+ {
+ label: "Keywords",
+ key: "keywords",
+ complete: getCompleteness(errors, ["keywords"]),
+ },
+ {
+ label: "Starter quiz",
+ key: "starterQuiz",
+ complete: getCompleteness(errors, ["starterQuiz"]),
+ },
+ {
+ label: "Learning cycles",
+ key: "cycle1",
+ complete: getCompleteness(errors, ["cycle1", "cycle2", "cycle3"]),
+ },
+ {
+ label: "Exit quiz",
+ key: "exitQuiz",
+ complete: getCompleteness(errors, ["exitQuiz"]),
+ },
+ ];
+
+ const totalSections = sections.length;
+ const totalSectionsComplete = sections.filter(
+ (section) => section.complete,
+ ).length;
+
+ return {
+ sections,
+ totalSections,
+ totalSectionsComplete,
+ };
+ }, [lessonPlan, isStreaming]);
+}
diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/utils/index.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/utils/index.ts
index ae04d9409..8cbf375e4 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/Chat/utils/index.ts
+++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/utils/index.ts
@@ -1,34 +1,6 @@
import { type LooseLessonPlan } from "@oakai/aila/src/protocol/schema";
-import { findListOfUndefinedKeysFromZod } from "@oakai/exports/src/dataHelpers/findListOfUndefinedKeysFromZod";
-import { lessonPlanSectionsSchema } from "@oakai/exports/src/schema/input.schema";
import { type Message } from "ai/react";
-export function setLessonPlanProgress({
- lessonPlan,
- setUndefinedItems,
-}: {
- lessonPlan: LooseLessonPlan;
- setUndefinedItems: React.Dispatch
>;
-}) {
- const parseForProgress = lessonPlanSectionsSchema.safeParse(lessonPlan);
-
- if (!parseForProgress.success) {
- const undefinedItems = findListOfUndefinedKeysFromZod(
- parseForProgress.error.errors as {
- expected: string;
- received: string;
- code: string;
- path: (string | number)[];
- message: string;
- }[],
- );
-
- setUndefinedItems(undefinedItems);
- } else {
- setUndefinedItems([]);
- }
-}
-
export function findLatestServerSideState(workingMessages: Message[]) {
console.log("Finding latest server-side state", { workingMessages });
const lastMessage = workingMessages[workingMessages.length - 1];
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-dropdownsection.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-dropdownsection.tsx
index f8c599961..bf3560881 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/chat-dropdownsection.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-dropdownsection.tsx
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { sectionToMarkdown } from "@oakai/aila/src/protocol/sectionToMarkdown";
+import { camelCaseToSentenceCase } from "@oakai/core/src/utils/camelCaseToSentenceCase";
import { lessonSectionTitlesAndMiniDescriptions } from "data/lessonSectionTitlesAndMiniDescriptions";
import { Icon } from "@/components/Icon";
@@ -94,9 +95,7 @@ const DropDownSection = ({
onClick={() => setIsOpen(!isOpen)}
className="flex w-full justify-between"
>
-
- {humanizeCamelCaseString(objectKey)}
-
+ {sectionTitle(objectKey)}
@@ -153,13 +152,12 @@ const valuesAreEqual = (
return val1 === val2;
};
-export function humanizeCamelCaseString(str: string) {
+export function sectionTitle(str: string) {
if (str.startsWith("cycle")) {
- return "Cycle " + str.split("cycle")[1];
+ return "Learning cycle " + str.split("cycle")[1];
}
- return str.replace(/([A-Z])/g, " $1").replace(/^./, function (str) {
- return str.toUpperCase();
- });
+
+ return camelCaseToSentenceCase(str);
}
export default DropDownSection;
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanMapToMarkDown.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanMapToMarkDown.tsx
index 55256f80e..584496591 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanMapToMarkDown.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-lessonPlanMapToMarkDown.tsx
@@ -4,7 +4,7 @@ import { LooseLessonPlan } from "@oakai/aila/src/protocol/schema";
import { sectionToMarkdown } from "@oakai/aila/src/protocol/sectionToMarkdown";
import { lessonSectionTitlesAndMiniDescriptions } from "data/lessonSectionTitlesAndMiniDescriptions";
-import { humanizeCamelCaseString } from "./chat-dropdownsection";
+import { sectionTitle } from "./chat-dropdownsection";
import { notEmpty } from "./chat-lessonPlanDisplay";
import { MemoizedReactMarkdownWithStyles } from "./markdown";
@@ -72,7 +72,7 @@ const ChatSection = ({ sectionRefs, objectKey, value }) => {
lessonPlanSectionDescription={
lessonSectionTitlesAndMiniDescriptions[objectKey]?.description
}
- markdown={`# ${objectKey.includes("cycle") ? "Cycle " + objectKey.split("cycle")[1] : humanizeCamelCaseString(objectKey)}
+ markdown={`# ${sectionTitle(objectKey)}
${sectionToMarkdown(objectKey, value)}`}
/>
diff --git a/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx b/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx
index 74a535932..55bfe94b2 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx
@@ -17,27 +17,27 @@ const ChatStartAccordion = () => {
const slidesLessonSections = lessonSections.filter(
(section) =>
section !== "Title" &&
- section !== "Key Stage" &&
+ section !== "Key stage" &&
section !== "Subject" &&
- section !== "Prior Knowledge" &&
- section !== "Key Learning Points" &&
+ section !== "Prior knowledge" &&
+ section !== "Key learning points" &&
section !== "Misconceptions" &&
- section !== "Starter Quiz" &&
+ section !== "Starter quiz" &&
section !== "Exit Quiz",
);
const quizLessonSections = lessonSections.filter(
(section) =>
section !== "Title" &&
- section !== "Key Stage" &&
+ section !== "Key stage" &&
section !== "Subject" &&
- section !== "Prior Knowledge" &&
- section !== "Key Learning Points" &&
+ section !== "Prior lnowledge" &&
+ section !== "Key learning points" &&
section !== "Misconceptions" &&
- section !== "Cycle 1" &&
+ section !== "Learning cycle 1" &&
section !== "Keywords" &&
- section !== "Learning Cycles" &&
- section !== "Learning Outcome",
+ section !== "Learning cycles" &&
+ section !== "Learning outcome",
);
return (
@@ -51,7 +51,7 @@ const ChatStartAccordion = () => {
{lessonSections.map((section) => {
if (
section == "Title" ||
- section == "Key Stage" ||
+ section == "Key stage" ||
section == "Subject"
) {
return null;
@@ -79,7 +79,7 @@ const ChatStartAccordion = () => {
{slidesLessonSections.map((section) => {
if (
section == "Title" ||
- section == "Key Stage" ||
+ section == "Key stage" ||
section == "Subject"
) {
return null;
@@ -107,7 +107,7 @@ const ChatStartAccordion = () => {
{quizLessonSections.map((section) => {
if (
section == "Title" ||
- section == "Key Stage" ||
+ section == "Key stage" ||
section == "Subject"
) {
return null;
diff --git a/apps/nextjs/src/components/AppComponents/Chat/empty-screen-accordian.tsx b/apps/nextjs/src/components/AppComponents/Chat/empty-screen-accordian.tsx
index b5f767af9..073de838a 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/empty-screen-accordian.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/empty-screen-accordian.tsx
@@ -16,18 +16,18 @@ const EmptyScreenAccordion = () => {
(section) =>
![
"Title",
- "Key Stage",
+ "Key stage",
"Subject",
- "Prior Knowledge",
- "Key Learning Points",
+ "Prior knowledge",
+ "Key learning points",
"Misconceptions",
- "Starter Quiz",
- "Exit Quiz",
+ "Starter quiz",
+ "Exit quiz",
].includes(section),
);
const quizLessonSections = lessonSections.filter(
- (section) => !["Title", "Key Stage", "Subject"].includes(section),
+ (section) => !["Title", "Key stage", "Subject"].includes(section),
);
return (
@@ -41,7 +41,7 @@ const EmptyScreenAccordion = () => {
{lessonSections.map((section) => {
if (
section === "Title" ||
- section === "Key Stage" ||
+ section === "Key stage" ||
section === "Subject"
) {
return null;
@@ -69,7 +69,7 @@ const EmptyScreenAccordion = () => {
{slidesLessonSections.map((section) => {
if (
section == "Title" ||
- section == "Key Stage" ||
+ section == "Key stage" ||
section == "Subject"
) {
return null;
@@ -97,7 +97,7 @@ const EmptyScreenAccordion = () => {
{quizLessonSections.map((section) => {
if (
section == "Title" ||
- section == "Key Stage" ||
+ section == "Key stage" ||
section == "Subject"
) {
return null;
diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx
index 413e72904..cbac20242 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.stories.tsx
@@ -1,3 +1,9 @@
+import {
+ Cycle,
+ Keyword,
+ Misconception,
+ Quiz,
+} from "@oakai/aila/src/protocol/schema";
import type { Meta, StoryObj } from "@storybook/react";
import { LessonPlanProgressDropdown } from "./LessonPlanProgressDropdown";
@@ -14,11 +20,15 @@ type Story = StoryObj;
export const Default: Story = {
args: {
lessonPlan: {
+ // 1 (lesson details)
title: "Introduction to Glaciation",
keyStage: "key-stage-3",
subject: "geography",
+ // 2
learningOutcome: "Sample learning outcome",
+ // 3
learningCycles: ["Sample learning cycles"],
+ // 4
priorKnowledge: ["Sample prior knowledge"],
},
sectionRefs: {
@@ -40,11 +50,23 @@ export const PartiallyCompleted: Story = {
args: {
...Default.args,
lessonPlan: {
- ...(Default?.args?.lessonPlan ?? {}),
+ // 1 (lesson details)
+ title: "Introduction to Glaciation",
+ keyStage: "key-stage-3",
+ subject: "geography",
+ // 2
+ learningOutcome: "Sample learning outcome",
+ // 3
+ learningCycles: ["Sample learning cycles"],
+ // 4
+ priorKnowledge: ["Sample prior knowledge"],
+ // 5
keyLearningPoints: ["Sample key learning point"],
- misconceptions: [{ misconception: "Sample misconception" }],
- cycle1: { title: "Sample cycle 1" },
- // Only cycle1 is completed
+ // 6
+ misconceptions: [sampleMisconception()],
+ // 7
+ cycle1: sampleCycle(1),
+ // Only cycle1 is completed, but that is sufficient for downloads
},
},
};
@@ -53,22 +75,30 @@ export const FullyCompleted: Story = {
args: {
...Default.args,
lessonPlan: {
+ // 1 (lesson details)
title: "Introduction to Glaciation",
keyStage: "key-stage-3",
subject: "geography",
+ // 2
learningOutcome: "Sample learning outcome",
+ // 3
learningCycles: ["Sample learning cycles"],
+ // 4
priorKnowledge: ["Sample prior knowledge"],
+ // 5
keyLearningPoints: ["Sample key learning points"],
- misconceptions: [{ misconception: "Sample misconceptions" }],
- keywords: [{ keyword: "Sample keyword" }],
- starterQuiz: [
- { question: "Sample starter quiz", answers: ["Sample answer"] },
- ],
- cycle1: { title: "Sample cycle 1" },
- cycle2: { title: "Sample cycle 2" },
- cycle3: { title: "Sample cycle 3" },
- exitQuiz: [{ question: "Sample exit quiz", answers: ["Answer"] }],
+ // 6
+ misconceptions: [sampleMisconception()],
+ // 7
+ keywords: [sampleKeyword()],
+ // 8
+ starterQuiz: sampleQuiz(),
+ // 9
+ cycle1: sampleCycle(1),
+ cycle2: sampleCycle(2),
+ cycle3: sampleCycle(3),
+ // 10
+ exitQuiz: sampleQuiz(),
additionalMaterials: "Sample additional materials",
},
},
@@ -78,10 +108,58 @@ export const PartialCycles: Story = {
args: {
...Default.args,
lessonPlan: {
+ // 1 - 4
...(Default?.args?.lessonPlan ?? {}),
- cycle1: { title: "Sample cycle 1" },
- cycle2: { title: "Sample cycle 2" },
- // cycle3 is missing
+ // 5
+ cycle1: sampleCycle(1),
+ cycle2: sampleCycle(2),
+ // cycle3 is missing, but that is sufficient for downloads
},
},
};
+
+function sampleCycle(i: number): Cycle {
+ return {
+ title: `Sample cycle ${i}`,
+ durationInMinutes: 10,
+ explanation: {
+ spokenExplanation: "Sample spoken explanation",
+ accompanyingSlideDetails: "Sample slide details",
+ imagePrompt: "Sample image prompt",
+ slideText: "Sample slide text",
+ },
+ checkForUnderstanding: [
+ {
+ question: "Sample question",
+ answers: ["Sample answer"],
+ distractors: ["Sample distractor"],
+ },
+ ],
+ practice: "Sample practice",
+ feedback: "Sample feedback",
+ };
+}
+
+function sampleMisconception(): Misconception {
+ return {
+ misconception: "Sample misconception",
+ description: "Sample description",
+ };
+}
+
+function sampleKeyword(): Keyword {
+ return {
+ keyword: "Sample keyword",
+ definition: "Sample description",
+ };
+}
+
+function sampleQuiz(): Quiz {
+ return [
+ {
+ question: "Sample question",
+ answers: ["Sample answer"],
+ distractors: ["Sample distractor"],
+ },
+ ];
+}
diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.test.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.test.tsx
index 1ba24fc35..6665475c5 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.test.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.test.tsx
@@ -21,23 +21,23 @@ const { Default, PartiallyCompleted, FullyCompleted, PartialCycles } =
describe("LessonPlanProgressDropdown", () => {
it("renders the Default story with correct closed state", () => {
render();
- expect(screen.getByText("6 of 12 sections complete")).toBeInTheDocument();
+ expect(screen.getByText("4 of 10 sections complete")).toBeInTheDocument();
expect(screen.queryByText("Cycles")).not.toBeInTheDocument();
});
it("renders the PartiallyCompleted story with correct closed state", () => {
render();
- expect(screen.getByText("8 of 12 sections complete")).toBeInTheDocument();
+ expect(screen.getByText("7 of 10 sections complete")).toBeInTheDocument();
});
it("renders the FullyCompleted story with correct closed state", () => {
render();
- expect(screen.getByText("12 of 12 sections complete")).toBeInTheDocument();
+ expect(screen.getByText("10 of 10 sections complete")).toBeInTheDocument();
});
it("renders the PartialCycles story with correct closed state", () => {
render();
- expect(screen.getByText("6 of 12 sections complete")).toBeInTheDocument();
+ expect(screen.getByText("5 of 10 sections complete")).toBeInTheDocument();
});
it("displays the dropdown menu when clicked and shows correct completed sections", async () => {
@@ -59,23 +59,21 @@ describe("LessonPlanProgressDropdown", () => {
"lesson-plan-progress-dropdown-content",
);
expect(cyclesSection).toBeInTheDocument();
- expect(cyclesSection).toHaveTextContent("Cycles 1-3");
+ expect(cyclesSection).toHaveTextContent("Learning cycles");
const dropdownContent = screen.getByTestId(
"lesson-plan-progress-dropdown-content",
);
const sectionStates = [
- { name: "Title", completed: true },
- { name: "Subject", completed: true },
- { name: "Key Stage", completed: true },
- { name: "Learning Outcome", completed: true },
- { name: "Learning Cycles", completed: true },
- { name: "Prior Knowledge", completed: true },
- { name: "Key Learning Points", completed: true },
+ { name: "Lesson details", completed: true },
+ { name: "Learning outcome", completed: true },
+ { name: "Learning cycle outcomes", completed: true },
+ { name: "Prior knowledge", completed: true },
+ { name: "Key learning points", completed: true },
{ name: "Misconceptions", completed: true },
{ name: "Keywords", completed: false },
- { name: "Starter Quiz", completed: false },
- { name: "Cycles 1-3", completed: false },
- { name: "Exit Quiz", completed: false },
+ { name: "Starter quiz", completed: false },
+ { name: "Learning cycles", completed: true },
+ { name: "Exit quiz", completed: false },
];
sectionStates.forEach(({ name, completed }) => {
@@ -108,18 +106,16 @@ describe("LessonPlanProgressDropdown", () => {
"lesson-plan-progress-dropdown-content",
);
const allSections = [
- "Title",
- "Subject",
- "Key Stage",
- "Learning Outcome",
- "Learning Cycles",
- "Prior Knowledge",
- "Key Learning Points",
+ "Lesson details",
+ "Learning outcome",
+ "Learning cycle outcomes",
+ "Prior knowledge",
+ "Key learning points",
"Misconceptions",
"Keywords",
- "Starter Quiz",
- "Cycles 1-3",
- "Exit Quiz",
+ "Starter quiz",
+ "Learning cycles",
+ "Exit quiz",
];
allSections.forEach((name) => {
@@ -147,18 +143,16 @@ describe("LessonPlanProgressDropdown", () => {
"lesson-plan-progress-dropdown-content",
);
const sectionStates = [
- { name: "Title", completed: true },
- { name: "Subject", completed: true },
- { name: "Key Stage", completed: true },
- { name: "Learning Outcome", completed: true },
- { name: "Learning Cycles", completed: true },
- { name: "Prior Knowledge", completed: true },
- { name: "Key Learning Points", completed: false },
+ { name: "Lesson details", completed: true },
+ { name: "Learning outcome", completed: true },
+ { name: "Learning cycle outcomes", completed: true },
+ { name: "Prior knowledge", completed: true },
+ { name: "Key learning points", completed: false },
{ name: "Misconceptions", completed: false },
{ name: "Keywords", completed: false },
- { name: "Starter Quiz", completed: false },
- { name: "Cycles 1-3", completed: false },
- { name: "Exit Quiz", completed: false },
+ { name: "Starter quiz", completed: false },
+ { name: "Learning cycles", completed: true },
+ { name: "Exit quiz", completed: false },
];
sectionStates.forEach(({ name, completed }) => {
diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx
index cb206f8d7..72b1aa62a 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useMemo } from "react";
+import React, { useState } from "react";
import { LooseLessonPlan } from "@oakai/aila/src/protocol/schema";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
@@ -7,53 +7,22 @@ import { Flex } from "@radix-ui/themes";
import { Icon } from "@/components/Icon";
import { scrollToRef } from "@/utils/scrollToRef";
-export const LESSON_PLAN_SECTIONS = [
- { key: "title", title: "Title" },
- { key: "subject", title: "Subject" },
- { key: "keyStage", title: "Key Stage" },
- { key: "learningOutcome", title: "Learning Outcome" },
- { key: "learningCycles", title: "Learning Cycles" },
- { key: "priorKnowledge", title: "Prior Knowledge" },
- { key: "keyLearningPoints", title: "Key Learning Points" },
- { key: "misconceptions", title: "Misconceptions" },
- { key: "keywords", title: "Keywords" },
- { key: "starterQuiz", title: "Starter Quiz" },
- { key: "cycles", title: "Cycles 1-3" },
- { key: "exitQuiz", title: "Exit Quiz" },
- // { key: "additionalMaterials", title: "Additional Materials" }, Do not show the additional materials section
-] as const;
+import { useProgressForDownloads } from "../Chat/hooks/useProgressForDownloads";
-export type LessonPlanSectionKey = (typeof LESSON_PLAN_SECTIONS)[number]["key"];
-
-export type LessonPlanProgressDropdownProps = {
+type LessonPlanProgressDropdownProps = Readonly<{
lessonPlan: LooseLessonPlan;
+ isStreaming: boolean;
sectionRefs: Record>;
documentContainerRef: React.MutableRefObject;
-};
-
-export const lessonPlanSectionIsComplete = (
- lessonPlan: LooseLessonPlan,
- key: LessonPlanSectionKey,
-) => {
- if (key === "cycles") {
- return lessonPlan.cycle1 && lessonPlan.cycle2 && lessonPlan.cycle3;
- }
- return lessonPlan[key] !== undefined && lessonPlan[key] !== null;
-};
+}>;
export const LessonPlanProgressDropdown: React.FC<
LessonPlanProgressDropdownProps
-> = ({ lessonPlan, sectionRefs, documentContainerRef }) => {
+> = ({ lessonPlan, sectionRefs, documentContainerRef, isStreaming }) => {
+ const { sections, totalSections, totalSectionsComplete } =
+ useProgressForDownloads({ lessonPlan, isStreaming });
const [openProgressDropDown, setOpenProgressDropDown] = useState(false);
- const completedSections = useMemo(() => {
- return LESSON_PLAN_SECTIONS.filter(({ key }) =>
- lessonPlanSectionIsComplete(lessonPlan, key),
- ).length;
- }, [lessonPlan]);
-
- const allCyclesComplete = lessonPlanSectionIsComplete(lessonPlan, "cycles");
-
return (
- {`${completedSections} of ${LESSON_PLAN_SECTIONS.length} sections complete`}
+ {`${totalSectionsComplete} of ${totalSections} sections complete`}
- {LESSON_PLAN_SECTIONS.map(({ key, title }) => (
-
+ {sections.map(({ label, complete, key }) => (
+
))}
diff --git a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx
index c1cb0b125..5b2b1399b 100644
--- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx
+++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx
@@ -29,6 +29,7 @@ const ExportButtons = ({
@@ -79,10 +80,10 @@ const ExportButtons = ({
export default ExportButtons;
export function handleRewordingSections(section: string) {
- if (section.includes("Cycle 1")) {
- return "Learning Cycles";
+ if (section.includes("Learning cycle 1")) {
+ return "Learning cycles";
}
- if (section === "Learning Cycles") {
+ if (section === "Learning cycles") {
return "Learning Cycle Outcomes";
}
return section;
diff --git a/apps/nextjs/src/components/DownloadButton.tsx b/apps/nextjs/src/components/DownloadButton.tsx
index 91e9d299f..9c6104f20 100644
--- a/apps/nextjs/src/components/DownloadButton.tsx
+++ b/apps/nextjs/src/components/DownloadButton.tsx
@@ -71,7 +71,7 @@ export const DownloadButton = ({
- Download {title} (.{ext})
+ Download {title.toLocaleLowerCase()} (.{ext})
All sections
diff --git a/packages/aila/src/protocol/sectionToMarkdown.ts b/packages/aila/src/protocol/sectionToMarkdown.ts
index 8f48efa64..4bef3bd89 100644
--- a/packages/aila/src/protocol/sectionToMarkdown.ts
+++ b/packages/aila/src/protocol/sectionToMarkdown.ts
@@ -1,4 +1,4 @@
-import { humanizeCamelCaseString } from "@oakai/core/src/utils/humanizeCamelCaseString";
+import { camelCaseToSentenceCase } from "@oakai/core/src/utils/camelCaseToSentenceCase";
import { isArray, isNumber, isObject, isString } from "remeda";
import {
@@ -20,18 +20,18 @@ export function sortIgnoringSpecialChars(strings: string[]): string[] {
}
const keyToHeadingMappings = {
- spokenExplanation: "Teacher Explanation",
- imagePrompt: "Image Search Suggestion",
- cycle1: "Learning Cycle 1",
- cycle2: "Learning Cycle 2",
- cycle3: "Learning Cycle 3",
+ spokenExplanation: "Teacher explanation",
+ imagePrompt: "Image search suggestion",
+ cycle1: "Learning cycle 1",
+ cycle2: "Learning cycle 2",
+ cycle3: "Learning cycle 3",
};
export function keyToHeading(key: string) {
if (key in keyToHeadingMappings) {
return keyToHeadingMappings[key as keyof typeof keyToHeadingMappings];
}
- return humanizeCamelCaseString(key);
+ return camelCaseToSentenceCase(key);
}
export function sectionToMarkdown(
key: string,
@@ -120,7 +120,7 @@ export function organiseAnswersAndDistractors(quiz: QuizOptional) {
...answers,
...distractors,
]).join("\n");
- return `### ${i + 1}: ${v.question ?? "…"}\n\n${answersAndDistractors}`;
+ return `### ${i + 1}. ${v.question ?? "…"}\n\n${answersAndDistractors}`;
})
.join("\n\n");
}
diff --git a/packages/core/src/models/lessonPlans.ts b/packages/core/src/models/lessonPlans.ts
index bfabf076b..37a7ce5eb 100644
--- a/packages/core/src/models/lessonPlans.ts
+++ b/packages/core/src/models/lessonPlans.ts
@@ -20,8 +20,8 @@ import { inngest } from "../client";
import { createOpenAIClient } from "../llm/openai";
import { template } from "../prompts/lesson-assistant";
import { RAG } from "../rag";
+import { camelCaseToSentenceCase } from "../utils/camelCaseToSentenceCase";
import { embedWithCache } from "../utils/embeddings";
-import { humanizeCamelCaseString } from "../utils/humanizeCamelCaseString";
import { Caption, CaptionsSchema } from "./types/caption";
// Simplifies the input to a string for embedding
@@ -319,7 +319,7 @@ ${part.content}`;
}
const text = lessonPlan.parts
.map(
- (p) => `${humanizeCamelCaseString(p.key)}
+ (p) => `${camelCaseToSentenceCase(p.key)}
${p.content}`,
)
.join("\n\n");
diff --git a/packages/core/src/prompts/lesson-assistant/old.ts b/packages/core/src/prompts/lesson-assistant/old.ts
index d529624db..4ae3db5e3 100644
--- a/packages/core/src/prompts/lesson-assistant/old.ts
+++ b/packages/core/src/prompts/lesson-assistant/old.ts
@@ -483,7 +483,7 @@ Are you happy with the learning outcomes and learning cycles?
If not, select **'Retry'** or type an edit in the text box below.
-When you are happy with this section, tap **'Continue'** and I will suggest 'prior knowledge', 'key learning points', 'common misconceptions' and 'key words'.
+When you are happy with this section, tap **'Continue'** and I will suggest 'prior knowledge', 'key learning points', 'common misconceptions' and 'keywords'.
END OF EXAMPLE HAPPINESS CHECK
START OF SECOND EXAMPLE HAPPINESS CHECK
diff --git a/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts b/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts
index 40e1929b6..854d7ea39 100644
--- a/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts
+++ b/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts
@@ -104,7 +104,7 @@ Are you happy with the learning outcomes and learning cycles?
If not, select **'Retry'** or type an edit in the text box below.
-When you are happy with this section, tap **'Continue'** and I will suggest 'prior knowledge', 'key learning points', 'common misconceptions' and 'key words'.
+When you are happy with this section, tap **'Continue'** and I will suggest 'prior knowledge', 'key learning points', 'common misconceptions' and 'keywords'.
END OF EXAMPLE HAPPINESS CHECK
START OF SECOND EXAMPLE HAPPINESS CHECK
diff --git a/packages/core/src/utils/camelCaseToSentenceCase.ts b/packages/core/src/utils/camelCaseToSentenceCase.ts
new file mode 100644
index 000000000..79b81c414
--- /dev/null
+++ b/packages/core/src/utils/camelCaseToSentenceCase.ts
@@ -0,0 +1,6 @@
+export function camelCaseToSentenceCase(str: string) {
+ return str
+ .replace(/([A-Z0-9])/g, " $1") // Insert a space before each uppercase letter or digit
+ .replace(/^./, (str) => str.toUpperCase())
+ .replace(/(?<=\s)[A-Z]/g, (str) => str.toLowerCase());
+}
diff --git a/packages/core/src/utils/humanizeCamelCaseString.ts b/packages/core/src/utils/humanizeCamelCaseString.ts
deleted file mode 100644
index ff987fa5d..000000000
--- a/packages/core/src/utils/humanizeCamelCaseString.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export function humanizeCamelCaseString(str: string) {
- return str.replace(/([A-Z0-9])/g, " $1").replace(/^./, function (str) {
- return str.toUpperCase();
- });
-}
diff --git a/packages/exports/src/dataHelpers/findListOfUndefinedKeysFromZod.ts b/packages/exports/src/dataHelpers/findListOfUndefinedKeysFromZod.ts
deleted file mode 100644
index 648fe23d0..000000000
--- a/packages/exports/src/dataHelpers/findListOfUndefinedKeysFromZod.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-export function humanizeCamelCaseString(str: string) {
- return str.replace(/([A-Z])/g, " $1").replace(/^./, function (str) {
- return str.toUpperCase();
- });
-}
-
-export function findListOfUndefinedKeysFromZod(
- errors: {
- expected: string;
- received: string;
- code: string;
- path: (string | number)[];
- message: string;
- }[],
-) {
- const undefinedItems: (string | number)[] = [];
-
- // Map through errors and find undefined items
- errors.forEach((e) => {
- if (e.received === "undefined") {
- undefinedItems.push(...e.path);
- }
- });
-
- // Make array human readable
- const humanReadableArray = undefinedItems.map((item) => {
- if (typeof item === "number") {
- return `[${item}]`;
- }
- if (item.includes("cycle")) {
- return "Cycle " + item.split("cycle")[1];
- }
- return humanizeCamelCaseString(item);
- });
- return humanReadableArray;
-}
diff --git a/packages/exports/src/schema/input.schema.ts b/packages/exports/src/schema/input.schema.ts
index 838ed8338..0a5d9993e 100644
--- a/packages/exports/src/schema/input.schema.ts
+++ b/packages/exports/src/schema/input.schema.ts
@@ -78,6 +78,7 @@ export const lessonPlanSectionsSchema = z.object({
cycle3: cycleSchema.nullish(),
additionalMaterials: z.string().nullish(),
});
+export type LessonPlanSections = z.infer
;
export type LessonPlanDocInputData = z.infer;