From f976669e0cf1f798cf69a6acb146d3b19fa1cb5c Mon Sep 17 00:00:00 2001 From: mantagen Date: Wed, 28 Aug 2024 13:26:59 +0200 Subject: [PATCH 01/11] feat: updates to download resources page --- .../app/aila/[id]/download/DownloadView.tsx | 178 ++++++++++++++ .../[id]/download/SurveyDialogLauncher.ts | 24 ++ .../src/app/aila/[id]/download/index.tsx | 232 ------------------ .../src/app/aila/[id]/download/page.tsx | 2 +- .../app/aila/[id]/download/useDownloadView.ts | 55 +++++ .../Chat/hooks/useProgressForDownloads.ts | 88 +++++++ .../AppComponents/Chat/Chat/utils/index.ts | 28 --- .../LessonPlanProgressDropdown.tsx | 60 ++--- .../findListOfUndefinedKeysFromZod.ts | 36 --- packages/exports/src/schema/input.schema.ts | 1 + 10 files changed, 361 insertions(+), 343 deletions(-) create mode 100644 apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx create mode 100644 apps/nextjs/src/app/aila/[id]/download/SurveyDialogLauncher.ts delete mode 100644 apps/nextjs/src/app/aila/[id]/download/index.tsx create mode 100644 apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts create mode 100644 apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts delete mode 100644 packages/exports/src/dataHelpers/findListOfUndefinedKeysFromZod.ts 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..e06db24ef --- /dev/null +++ b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx @@ -0,0 +1,178 @@ +"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

+

+ 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} + /> + )} + + +
+ + {`${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..24860c182 --- /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); + + return { + lessonSlidesExport, + worksheetExport, + lessonPlanExport, + starterQuizExport, + exitQuizExport, + additionalMaterialsExport, + sections, + totalSections, + totalSectionsComplete, + }; +} 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..8e4bf5389 --- /dev/null +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -0,0 +1,88 @@ +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; +} + +export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { + return useMemo(() => { + const parsedLessonPlan = lessonPlanSectionsSchema.safeParse(lessonPlan); + const errors = parsedLessonPlan.error?.errors || []; + const sections = [ + { + label: "Lesson details", + key: "title", + complete: getCompleteness(errors, ["title", "subject", "keyStage"]), + }, + { + label: "Lesson 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: "Key words", + key: "keywords", + complete: getCompleteness(errors, ["keywords"]), + }, + { + label: "Starter quiz", + key: "starterQuiz", + complete: getCompleteness(errors, ["starterQuiz"]), + }, + { + label: "Cycles 1 to 3", + 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]); +} 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/export-buttons/LessonPlanProgressDropdown.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx index cb206f8d7..6121e3cd0 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,21 @@ 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; 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 }) => { + const { sections, totalSections, totalSectionsComplete } = + useProgressForDownloads(lessonPlan); 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/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; From 821bb856a573504b5cd953709b764caaa50413c3 Mon Sep 17 00:00:00 2001 From: mantagen Date: Wed, 28 Aug 2024 13:33:31 +0200 Subject: [PATCH 02/11] copy changes --- .../src/app/aila/[id]/download/DownloadView.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx index e06db24ef..eb777b66e 100644 --- a/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx +++ b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx @@ -61,9 +61,7 @@ export function DownloadView({

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. + Choose the resources you would like to generate and download.

lessonPlanExport.start()} - title="Lesson plan" - subTitle="All sections" + title="Lesson" + subTitle="Overview of the complete lesson" downloadAvailable={!!lessonPlanExport.readyToExport} downloadLoading={lessonPlanExport.status === "loading"} data={lessonPlanExport.data} @@ -88,7 +86,7 @@ export function DownloadView({ starterQuizExport.start()} title="Starter quiz" - subTitle="Questions and answers" + subTitle="Questions and answers to assess prior knowledge" downloadAvailable={!!starterQuizExport.readyToExport} downloadLoading={starterQuizExport.status === "loading"} data={starterQuizExport.data} @@ -99,7 +97,7 @@ export function DownloadView({ onClick={() => lessonSlidesExport.start()} data-testid="chat-download-slides-btn" title="Slide deck" - subTitle="Outcomes, key words, 1-3 learning cycles, summary" + subTitle="Learning outcome, key words and learning cycles" downloadAvailable={lessonSlidesExport.readyToExport} downloadLoading={lessonSlidesExport.status === "loading"} data={lessonSlidesExport.data} @@ -109,7 +107,7 @@ export function DownloadView({ worksheetExport.start()} title="Worksheet" - subTitle="Interactive activities" + subTitle="Practice tasks" downloadAvailable={!!worksheetExport.readyToExport} downloadLoading={worksheetExport.status === "loading"} data={worksheetExport.data} @@ -119,7 +117,7 @@ export function DownloadView({ exitQuizExport.start()} title="Exit quiz" - subTitle="Questions and answers" + subTitle="Questions and answers to assess understanding" downloadAvailable={!!exitQuizExport.readyToExport} downloadLoading={exitQuizExport.status === "loading"} data={exitQuizExport.data} From 21eab934c48555b0d31db4f71a73e5b6db3a6d1f Mon Sep 17 00:00:00 2001 From: mantagen Date: Wed, 28 Aug 2024 13:41:09 +0200 Subject: [PATCH 03/11] copy --- .../AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts index 8e4bf5389..4007b9c00 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -63,7 +63,7 @@ export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { complete: getCompleteness(errors, ["starterQuiz"]), }, { - label: "Cycles 1 to 3", + label: "Learning cycles", key: "cycle1", complete: getCompleteness(errors, ["cycle1", "cycle2", "cycle3"]), }, From 708796590a78cb02b6391d24bac2cbdac72b6f0f Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 11:36:35 +0200 Subject: [PATCH 04/11] fix tests --- .../LessonPlanProgressDropdown.test.tsx | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) 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..13753ae1c 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,25 @@ 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("4 of 10 sections complete")).toBeInTheDocument(); + expect(screen.getByText("5 of 10 sections complete")).toBeInTheDocument(); + expect(screen.getByText("6 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("4 of 10 sections complete")).toBeInTheDocument(); }); it("displays the dropdown menu when clicked and shows correct completed sections", async () => { @@ -59,23 +61,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: "Lesson 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: false }, + { name: "Exit quiz", completed: false }, ]; sectionStates.forEach(({ name, completed }) => { @@ -108,18 +108,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", + "Lesson 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 +145,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: "Lesson 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: false }, + { name: "Exit quiz", completed: false }, ]; sectionStates.forEach(({ name, completed }) => { From 4ca8fff63482922c3ccc81c9d60da4461593b10b Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 12:23:43 +0200 Subject: [PATCH 05/11] changes test logic to reflect that only one learning cycle is valie --- .../Chat/hooks/useProgressForDownloads.ts | 2 +- .../LessonPlanProgressDropdown.stories.tsx | 110 +++++++++++++++--- .../LessonPlanProgressDropdown.test.tsx | 16 ++- 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts index 4007b9c00..f50550b96 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -68,7 +68,7 @@ export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { complete: getCompleteness(errors, ["cycle1", "cycle2", "cycle3"]), }, { - label: "Exit Quiz", + label: "Exit quiz", key: "exitQuiz", complete: getCompleteness(errors, ["exitQuiz"]), }, 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 13753ae1c..d14f7d746 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 @@ -27,9 +27,7 @@ describe("LessonPlanProgressDropdown", () => { it("renders the PartiallyCompleted story with correct closed state", () => { render(); - // expect(screen.getByText("4 of 10 sections complete")).toBeInTheDocument(); - expect(screen.getByText("5 of 10 sections complete")).toBeInTheDocument(); - expect(screen.getByText("6 of 10 sections complete")).toBeInTheDocument(); + expect(screen.getByText("7 of 10 sections complete")).toBeInTheDocument(); }); it("renders the FullyCompleted story with correct closed state", () => { @@ -39,7 +37,7 @@ describe("LessonPlanProgressDropdown", () => { it("renders the PartialCycles story with correct closed state", () => { render(); - expect(screen.getByText("4 of 10 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 () => { @@ -72,9 +70,9 @@ describe("LessonPlanProgressDropdown", () => { { name: "Prior knowledge", completed: true }, { name: "Key learning points", completed: true }, { name: "Misconceptions", completed: true }, - { name: "Keywords", completed: false }, + { name: "Key words", completed: false }, { name: "Starter quiz", completed: false }, - { name: "Learning cycles", completed: false }, + { name: "Learning cycles", completed: true }, { name: "Exit quiz", completed: false }, ]; @@ -114,7 +112,7 @@ describe("LessonPlanProgressDropdown", () => { "Prior knowledge", "Key learning points", "Misconceptions", - "Keywords", + "Key words", "Starter quiz", "Learning cycles", "Exit quiz", @@ -151,9 +149,9 @@ describe("LessonPlanProgressDropdown", () => { { name: "Prior knowledge", completed: true }, { name: "Key learning points", completed: false }, { name: "Misconceptions", completed: false }, - { name: "Keywords", completed: false }, + { name: "Key words", completed: false }, { name: "Starter quiz", completed: false }, - { name: "Learning cycles", completed: false }, + { name: "Learning cycles", completed: true }, { name: "Exit quiz", completed: false }, ]; From cc42587a145e157db15880a1c303bdcb281cc7c0 Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 13:22:31 +0200 Subject: [PATCH 06/11] types on kew words and learning outcomes --- .../src/app/aila/[id]/download/DownloadView.tsx | 2 +- apps/nextjs/src/app/aila/help/index.tsx | 2 +- .../Chat/Chat/hooks/useProgressForDownloads.ts | 4 ++-- .../LessonPlanProgressDropdown.test.tsx | 12 ++++++------ packages/core/src/prompts/lesson-assistant/old.ts | 2 +- .../lesson-assistant/parts/yourInstructions.ts | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx index eb777b66e..818d42298 100644 --- a/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx +++ b/apps/nextjs/src/app/aila/[id]/download/DownloadView.tsx @@ -97,7 +97,7 @@ export function DownloadView({ onClick={() => lessonSlidesExport.start()} data-testid="chat-download-slides-btn" title="Slide deck" - subTitle="Learning outcome, key words and learning cycles" + subTitle="Learning outcome, keywords and learning cycles" downloadAvailable={lessonSlidesExport.readyToExport} downloadLoading={lessonSlidesExport.status === "loading"} data={lessonSlidesExport.data} 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/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts index f50550b96..94c3ba0f1 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -28,7 +28,7 @@ export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { complete: getCompleteness(errors, ["title", "subject", "keyStage"]), }, { - label: "Lesson learning outcome", + label: "Learning outcome", key: "learningOutcome", complete: getCompleteness(errors, ["learningOutcome"]), }, @@ -53,7 +53,7 @@ export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { complete: getCompleteness(errors, ["misconceptions"]), }, { - label: "Key words", + label: "Keywords", key: "keywords", complete: getCompleteness(errors, ["keywords"]), }, 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 d14f7d746..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 @@ -65,12 +65,12 @@ describe("LessonPlanProgressDropdown", () => { ); const sectionStates = [ { name: "Lesson details", completed: true }, - { name: "Lesson learning outcome", 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: "Key words", completed: false }, + { name: "Keywords", completed: false }, { name: "Starter quiz", completed: false }, { name: "Learning cycles", completed: true }, { name: "Exit quiz", completed: false }, @@ -107,12 +107,12 @@ describe("LessonPlanProgressDropdown", () => { ); const allSections = [ "Lesson details", - "Lesson learning outcome", + "Learning outcome", "Learning cycle outcomes", "Prior knowledge", "Key learning points", "Misconceptions", - "Key words", + "Keywords", "Starter quiz", "Learning cycles", "Exit quiz", @@ -144,12 +144,12 @@ describe("LessonPlanProgressDropdown", () => { ); const sectionStates = [ { name: "Lesson details", completed: true }, - { name: "Lesson learning outcome", 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: "Key words", completed: false }, + { name: "Keywords", completed: false }, { name: "Starter quiz", completed: false }, { name: "Learning cycles", completed: true }, { name: "Exit quiz", completed: false }, 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 267f5997c..014a2f62d 100644 --- a/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts +++ b/packages/core/src/prompts/lesson-assistant/parts/yourInstructions.ts @@ -103,7 +103,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 From aaf843455c5a803c81bad0cf05e0ff12873da55a Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 13:23:30 +0200 Subject: [PATCH 07/11] sentence case --- apps/nextjs/src/components/DownloadButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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
    From d09522b9f7a29f43d9b48f70a4da51082500999b Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 14:58:55 +0200 Subject: [PATCH 08/11] fix progress bar flickering --- .../app/aila/[id]/download/useDownloadView.ts | 2 +- .../Chat/hooks/useProgressForDownloads.ts | 45 +++++++++++++++++-- .../LessonPlanProgressDropdown.tsx | 5 ++- .../Chat/export-buttons/index.tsx | 1 + 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts b/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts index 24860c182..67692096b 100644 --- a/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts +++ b/apps/nextjs/src/app/aila/[id]/download/useDownloadView.ts @@ -39,7 +39,7 @@ export function useDownloadView({ const additionalMaterialsExport = useExportAdditionalMaterials(exportProps); const { sections, totalSections, totalSectionsComplete } = - useProgressForDownloads(lessonPlan); + useProgressForDownloads({ lessonPlan, isStreaming: false }); return { lessonSlidesExport, diff --git a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts index 94c3ba0f1..8a03c54c5 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts +++ b/apps/nextjs/src/components/AppComponents/Chat/Chat/hooks/useProgressForDownloads.ts @@ -16,11 +16,50 @@ function getCompleteness(errors: ZodIssue[], fields: string[]) { return !hasErrorInSomeField; } +type ProgressSections = { + label: string; + key: string; + complete: boolean; +}[]; +type ProgressForDownloads = { + sections: ProgressSections; + totalSections: number; + totalSectionsComplete: number; +}; -export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { +export function useProgressForDownloads({ + lessonPlan, + isStreaming, +}: { + lessonPlan: LooseLessonPlan; + isStreaming: boolean; +}): ProgressForDownloads { return useMemo(() => { const parsedLessonPlan = lessonPlanSectionsSchema.safeParse(lessonPlan); - const errors = parsedLessonPlan.error?.errors || []; + 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", @@ -84,5 +123,5 @@ export function useProgressForDownloads(lessonPlan: LooseLessonPlan) { totalSections, totalSectionsComplete, }; - }, [lessonPlan]); + }, [lessonPlan, isStreaming]); } 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 6121e3cd0..72b1aa62a 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/LessonPlanProgressDropdown.tsx @@ -11,15 +11,16 @@ import { useProgressForDownloads } from "../Chat/hooks/useProgressForDownloads"; type LessonPlanProgressDropdownProps = Readonly<{ lessonPlan: LooseLessonPlan; + isStreaming: boolean; sectionRefs: Record>; documentContainerRef: React.MutableRefObject; }>; export const LessonPlanProgressDropdown: React.FC< LessonPlanProgressDropdownProps -> = ({ lessonPlan, sectionRefs, documentContainerRef }) => { +> = ({ lessonPlan, sectionRefs, documentContainerRef, isStreaming }) => { const { sections, totalSections, totalSectionsComplete } = - useProgressForDownloads(lessonPlan); + useProgressForDownloads({ lessonPlan, isStreaming }); const [openProgressDropDown, setOpenProgressDropDown] = useState(false); return ( 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..d10d3c466 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 = ({
    From d6a05e9aeb7701df8d7b2b088834b7830d0a2c5c Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 15:10:38 +0200 Subject: [PATCH 09/11] sentence case --- .../ai-apps/lesson-planner/lessonSection.ts | 14 +++++------ .../Chat/chat-start-accordion.tsx | 24 +++++++++---------- .../Chat/empty-screen-accordian.tsx | 18 +++++++------- .../Chat/export-buttons/index.tsx | 4 ++-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts b/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts index 42f59dafc..23b4b8a81 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", + "Starter quiz", "Cycle 1", - "Exit Quiz", + "Exit quiz", ]; 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..b74bb6aca 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 !== "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/index.tsx b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx index d10d3c466..a6e624e14 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx @@ -81,9 +81,9 @@ export default ExportButtons; export function handleRewordingSections(section: string) { if (section.includes("Cycle 1")) { - return "Learning Cycles"; + return "Learning cycles"; } - if (section === "Learning Cycles") { + if (section === "Learning cycles") { return "Learning Cycle Outcomes"; } return section; From bd6b6674ec3523cdf1041424eb2a7d66a1d592ee Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 15:45:01 +0200 Subject: [PATCH 10/11] sentence case --- .../src/ai-apps/lesson-planner/lessonSection.ts | 2 +- .../AppComponents/Chat/chat-dropdownsection.tsx | 14 ++++++-------- .../Chat/chat-lessonPlanMapToMarkDown.tsx | 4 ++-- .../AppComponents/Chat/chat-start-accordion.tsx | 2 +- .../AppComponents/Chat/export-buttons/index.tsx | 2 +- packages/aila/src/protocol/sectionToMarkdown.ts | 14 +++++++------- packages/core/src/models/lessonPlans.ts | 4 ++-- packages/core/src/utils/camelCaseToSentenceCase.ts | 6 ++++++ packages/core/src/utils/humanizeCamelCaseString.ts | 5 ----- 9 files changed, 26 insertions(+), 27 deletions(-) create mode 100644 packages/core/src/utils/camelCaseToSentenceCase.ts delete mode 100644 packages/core/src/utils/humanizeCamelCaseString.ts diff --git a/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts b/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts index 23b4b8a81..ca1a25a18 100644 --- a/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts +++ b/apps/nextjs/src/ai-apps/lesson-planner/lessonSection.ts @@ -9,6 +9,6 @@ export const lessonSections = [ "Misconceptions", "Keywords", "Starter quiz", - "Cycle 1", + "Learning cycle 1", "Exit quiz", ]; 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 b74bb6aca..55bfe94b2 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/chat-start-accordion.tsx @@ -34,7 +34,7 @@ const ChatStartAccordion = () => { section !== "Prior lnowledge" && section !== "Key learning points" && section !== "Misconceptions" && - section !== "Cycle 1" && + section !== "Learning cycle 1" && section !== "Keywords" && section !== "Learning cycles" && section !== "Learning outcome", 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 a6e624e14..5b2b1399b 100644 --- a/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx +++ b/apps/nextjs/src/components/AppComponents/Chat/export-buttons/index.tsx @@ -80,7 +80,7 @@ const ExportButtons = ({ export default ExportButtons; export function handleRewordingSections(section: string) { - if (section.includes("Cycle 1")) { + if (section.includes("Learning cycle 1")) { return "Learning cycles"; } if (section === "Learning cycles") { diff --git a/packages/aila/src/protocol/sectionToMarkdown.ts b/packages/aila/src/protocol/sectionToMarkdown.ts index 8f48efa64..18db71373 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, 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/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(); - }); -} From 45cd59de7a7c04b0fe4ffe018ccd14db3f16b9bc Mon Sep 17 00:00:00 2001 From: mantagen Date: Thu, 29 Aug 2024 16:24:29 +0200 Subject: [PATCH 11/11] colon to dot after quiz question numbers --- apps/nextjs/src/app/quiz-designer/preview/[slug]/preview.tsx | 2 +- packages/aila/src/protocol/sectionToMarkdown.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/packages/aila/src/protocol/sectionToMarkdown.ts b/packages/aila/src/protocol/sectionToMarkdown.ts index 18db71373..4bef3bd89 100644 --- a/packages/aila/src/protocol/sectionToMarkdown.ts +++ b/packages/aila/src/protocol/sectionToMarkdown.ts @@ -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"); }