From 9c2289b3fb0e415c4cef979ab146dc583ef91b7d Mon Sep 17 00:00:00 2001 From: Matthew Date: Fri, 15 Nov 2024 12:35:43 -0600 Subject: [PATCH] Rename AI files (#1860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary: [There was discussion around the names of things in the AI folder being confusing.](https://khanacademy.slack.com/archives/C01AZ9H8TTQ/p1731537082583919) What this PR does: 1. renames `prompt-utils.ts` files to `[widget]-ai-utils.ts` 2. most of these directories had two test files (1) testing `getPromptJSON` directly and (2) testing `getPromptJSON` on the widget export; I just combined these test files and renamed them to `[widget]-ai-utils.test.ts` 3. updates the REAME No real logic changes, just code shoveling. Author: handeyeco Reviewers: SonicScrewdriver, handeyeco Required Reviewers: Approved By: SonicScrewdriver Checks: ✅ Publish npm snapshot (ubuntu-latest, 20.x), ✅ Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x), ✅ Cypress (ubuntu-latest, 20.x), ✅ Check for .changeset entries for all changed files (ubuntu-latest, 20.x), ✅ Check builds for changes in size (ubuntu-latest, 20.x), ✅ Publish Storybook to Chromatic (ubuntu-latest, 20.x), ✅ gerald Pull Request URL: https://github.com/Khan/perseus/pull/1860 --- .changeset/nasty-bikes-travel.md | 5 + packages/perseus/src/index.ts | 5 +- packages/perseus/src/prompt-types.ts | 68 --- packages/perseus/src/renderer.tsx | 5 +- packages/perseus/src/server-item-renderer.tsx | 5 +- packages/perseus/src/types.ts | 2 +- .../perseus/src/widget-ai-utils/README.md | 30 +- ...r.test.ts => categorizer-ai-utils.test.ts} | 32 +- ...rompt-utils.ts => categorizer-ai-utils.ts} | 0 .../categorizer/prompt-utils.test.ts | 30 -- ...am.test.ts => cs-program-ai-utils.test.ts} | 16 +- ...prompt-utils.ts => cs-program-ai-utils.ts} | 0 .../cs-program/prompt-utils.test.ts | 13 - ...on.test.ts => definition-ai-utils.test.ts} | 19 +- ...prompt-utils.ts => definition-ai-utils.ts} | 0 .../definition/prompt-utils.test.ts | 18 - ...down.test.ts => dropdown-ai-utils.test.ts} | 30 +- .../{prompt-utils.ts => dropdown-ai-utils.ts} | 0 .../dropdown/prompt-utils.test.ts | 28 -- ...n.test.ts => explanation-ai-utils.test.ts} | 21 +- ...rompt-utils.ts => explanation-ai-utils.ts} | 0 .../explanation/prompt-utils.test.ts | 18 - ...on.test.ts => expression-ai-utils.test.ts} | 20 +- ...prompt-utils.ts => expression-ai-utils.ts} | 0 .../expression/prompt-utils.test.ts | 19 - ...t.ts => graded-group-set-ai-utils.test.ts} | 94 +++- ...-utils.ts => graded-group-set-ai-utils.ts} | 2 +- .../graded-group-set/prompt-utils.test.ts | 93 ---- ....test.ts => graded-group-ai-utils.test.ts} | 108 ++++- ...ompt-utils.ts => graded-group-ai-utils.ts} | 2 +- .../graded-group/prompt-utils.test.ts | 109 ----- ...apher.test.ts => grapher-ai-utils.test.ts} | 94 +++- .../{prompt-utils.ts => grapher-ai-utils.ts} | 0 .../grapher/prompt-utils.test.ts | 93 ---- .../{group.test.ts => group-ai-utils.test.ts} | 95 +++- ...testdata.ts => group-ai-utils.testdata.ts} | 0 .../{prompt-utils.ts => group-ai-utils.ts} | 2 +- .../group/prompt-utils.test.ts | 93 ---- ...iframe.test.ts => iframe-ai-utils.test.ts} | 14 +- .../{prompt-utils.ts => iframe-ai-utils.ts} | 0 .../iframe/prompt-utils.test.ts | 13 - .../{image.test.ts => image-ai-utils.test.ts} | 27 +- .../{prompt-utils.ts => image-ai-utils.ts} | 0 .../image/prompt-utils.test.ts | 26 - .../input-number-ai-utils.test.ts | 99 ++++ ...ompt-utils.ts => input-number-ai-utils.ts} | 0 ...n.test.ts => interaction-ai-utils.test.ts} | 14 +- ...rompt-utils.ts => interaction-ai-utils.ts} | 0 .../interaction/prompt-utils.test.ts | 13 - ....ts => interactive-graph-ai-utils.test.ts} | 445 +++++++++++++++++- ...utils.ts => interactive-graph-ai-utils.ts} | 0 .../interactive-graph.test.ts | 442 ----------------- ...e.test.ts => label-image-ai-utils.test.ts} | 91 +++- ...rompt-utils.ts => label-image-ai-utils.ts} | 0 .../label-image/prompt-utils.test.ts | 91 ---- ...her.test.tsx => matcher-ai-utils.test.tsx} | 41 +- .../{prompt-utils.ts => matcher-ai-utils.ts} | 0 .../matcher/prompt-utils.test.ts | 41 -- ...matrix.test.ts => matrix-ai-utils.test.ts} | 35 +- .../{prompt-utils.ts => matrix-ai-utils.ts} | 0 .../matrix/prompt-utils.test.ts | 34 -- ...tils.test.ts => measurer-ai-utils.test.ts} | 4 +- .../{prompt-utils.ts => measurer-ai-utils.ts} | 0 ...e.test.ts => number-line-ai-utils.test.ts} | 32 +- ...rompt-utils.ts => number-line-ai-utils.ts} | 0 .../number-line/prompt-utils.test.ts | 31 -- ...derer.test.ts => orderer-ai-utils.test.ts} | 42 +- .../{prompt-utils.ts => orderer-ai-utils.ts} | 0 .../orderer/prompt-utils.test.ts | 41 -- ...f.test.ts => passage-ref-ai-utils.test.ts} | 23 +- ...rompt-utils.ts => passage-ref-ai-utils.ts} | 0 .../passage-ref/prompt-utils.test.ts | 22 - ...utils.test.ts => passage-ai-utils.test.ts} | 57 ++- .../{prompt-utils.ts => passage-ai-utils.ts} | 0 .../widget-ai-utils/passage/passage.test.ts | 54 --- ...t-utils.ts => phet-simulation-ai-utils.ts} | 0 .../phet-simulation/phet-simulation.test.ts | 58 --- .../phet-simulation/prompt-utils.test.ts | 61 ++- ...utils.test.ts => plotter-ai-utils.test.ts} | 4 +- .../{prompt-utils.ts => plotter-ai-utils.ts} | 0 .../src/widget-ai-utils/prompt-types.ts | 68 +++ .../python-program/prompt-utils.test.ts | 13 - ...rogram.test.ts => python-ai-utils.test.ts} | 14 +- .../{prompt-utils.ts => python-ai-utils.ts} | 0 .../radio/prompt-utils.test.ts | 55 --- .../{radio.test.ts => radio-ai-utils.test.ts} | 55 ++- .../{prompt-utils.ts => radio-ai-utils.ts} | 0 ...-utils.test.ts => sorter-ai-utils.test.ts} | 4 +- .../{prompt-utils.ts => sorter-ai-utils.ts} | 0 .../src/widget-ai-utils/unsupported-widget.ts | 2 +- .../video/prompt-utils.test.ts | 44 +- .../{prompt-utils.ts => video-ai-utils.ts} | 0 .../src/widget-ai-utils/video/video.test.ts | 41 -- .../src/widgets/categorizer/categorizer.tsx | 4 +- .../src/widgets/cs-program/cs-program.tsx | 2 +- .../src/widgets/definition/definition.tsx | 4 +- .../perseus/src/widgets/dropdown/dropdown.tsx | 4 +- .../src/widgets/explanation/explanation.tsx | 4 +- .../src/widgets/expression/expression.tsx | 4 +- .../graded-group-set/graded-group-set.tsx | 4 +- .../src/widgets/graded-group/graded-group.tsx | 4 +- .../perseus/src/widgets/grapher/grapher.tsx | 4 +- packages/perseus/src/widgets/group/group.tsx | 4 +- .../perseus/src/widgets/iframe/iframe.tsx | 2 +- packages/perseus/src/widgets/image/image.tsx | 4 +- .../src/widgets/input-number/input-number.tsx | 4 +- .../src/widgets/interaction/interaction.tsx | 2 +- .../perseus/src/widgets/interactive-graph.tsx | 4 +- .../src/widgets/label-image/label-image.tsx | 4 +- .../perseus/src/widgets/matcher/matcher.tsx | 4 +- .../perseus/src/widgets/matrix/matrix.tsx | 4 +- .../perseus/src/widgets/measurer/measurer.tsx | 2 +- .../src/widgets/number-line/number-line.tsx | 4 +- .../perseus/src/widgets/orderer/orderer.tsx | 4 +- .../src/widgets/passage-ref/passage-ref.tsx | 4 +- .../perseus/src/widgets/passage/passage.tsx | 4 +- .../phet-simulation/phet-simulation.tsx | 2 +- .../perseus/src/widgets/plotter/plotter.tsx | 2 +- .../widgets/python-program/python-program.tsx | 2 +- .../src/widgets/radio/radio-component.tsx | 4 +- .../perseus/src/widgets/sorter/sorter.tsx | 4 +- packages/perseus/src/widgets/video/video.tsx | 2 +- 122 files changed, 1755 insertions(+), 1665 deletions(-) create mode 100644 .changeset/nasty-bikes-travel.md delete mode 100644 packages/perseus/src/prompt-types.ts rename packages/perseus/src/widget-ai-utils/categorizer/{categorizer.test.ts => categorizer-ai-utils.test.ts} (76%) rename packages/perseus/src/widget-ai-utils/categorizer/{prompt-utils.ts => categorizer-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/cs-program/{cs-program.test.ts => cs-program-ai-utils.test.ts} (77%) rename packages/perseus/src/widget-ai-utils/cs-program/{prompt-utils.ts => cs-program-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/definition/{definition.test.ts => definition-ai-utils.test.ts} (75%) rename packages/perseus/src/widget-ai-utils/definition/{prompt-utils.ts => definition-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/definition/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/dropdown/{dropdown.test.ts => dropdown-ai-utils.test.ts} (73%) rename packages/perseus/src/widget-ai-utils/dropdown/{prompt-utils.ts => dropdown-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/explanation/{explanation.test.ts => explanation-ai-utils.test.ts} (73%) rename packages/perseus/src/widget-ai-utils/explanation/{prompt-utils.ts => explanation-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/explanation/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/expression/{expression.test.ts => expression-ai-utils.test.ts} (78%) rename packages/perseus/src/widget-ai-utils/expression/{prompt-utils.ts => expression-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/expression/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/graded-group-set/{graded-group-set.test.ts => graded-group-set-ai-utils.test.ts} (61%) rename packages/perseus/src/widget-ai-utils/graded-group-set/{prompt-utils.ts => graded-group-set-ai-utils.ts} (88%) delete mode 100644 packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/graded-group/{graded-group.test.ts => graded-group-ai-utils.test.ts} (72%) rename packages/perseus/src/widget-ai-utils/graded-group/{prompt-utils.ts => graded-group-ai-utils.ts} (91%) delete mode 100644 packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/grapher/{grapher.test.ts => grapher-ai-utils.test.ts} (50%) rename packages/perseus/src/widget-ai-utils/grapher/{prompt-utils.ts => grapher-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/grapher/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/group/{group.test.ts => group-ai-utils.test.ts} (51%) rename packages/perseus/src/widget-ai-utils/group/{group.testdata.ts => group-ai-utils.testdata.ts} (100%) rename packages/perseus/src/widget-ai-utils/group/{prompt-utils.ts => group-ai-utils.ts} (86%) delete mode 100644 packages/perseus/src/widget-ai-utils/group/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/iframe/{iframe.test.ts => iframe-ai-utils.test.ts} (83%) rename packages/perseus/src/widget-ai-utils/iframe/{prompt-utils.ts => iframe-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/iframe/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/image/{image.test.ts => image-ai-utils.test.ts} (78%) rename packages/perseus/src/widget-ai-utils/image/{prompt-utils.ts => image-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/image/prompt-utils.test.ts create mode 100644 packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.test.ts rename packages/perseus/src/widget-ai-utils/input-number/{prompt-utils.ts => input-number-ai-utils.ts} (100%) rename packages/perseus/src/widget-ai-utils/interaction/{interaction.test.ts => interaction-ai-utils.test.ts} (96%) rename packages/perseus/src/widget-ai-utils/interaction/{prompt-utils.ts => interaction-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/interaction/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/interactive-graph/{prompt-utils.test.ts => interactive-graph-ai-utils.test.ts} (50%) rename packages/perseus/src/widget-ai-utils/interactive-graph/{prompt-utils.ts => interactive-graph-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph.test.ts rename packages/perseus/src/widget-ai-utils/label-image/{label-image.test.ts => label-image-ai-utils.test.ts} (63%) rename packages/perseus/src/widget-ai-utils/label-image/{prompt-utils.ts => label-image-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/label-image/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/matcher/{matcher.test.tsx => matcher-ai-utils.test.tsx} (82%) rename packages/perseus/src/widget-ai-utils/matcher/{prompt-utils.ts => matcher-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/matcher/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/matrix/{matrix.test.ts => matrix-ai-utils.test.ts} (76%) rename packages/perseus/src/widget-ai-utils/matrix/{prompt-utils.ts => matrix-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/matrix/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/measurer/{prompt-utils.test.ts => measurer-ai-utils.test.ts} (74%) rename packages/perseus/src/widget-ai-utils/measurer/{prompt-utils.ts => measurer-ai-utils.ts} (100%) rename packages/perseus/src/widget-ai-utils/number-line/{number-line.test.ts => number-line-ai-utils.test.ts} (71%) rename packages/perseus/src/widget-ai-utils/number-line/{prompt-utils.ts => number-line-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/number-line/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/orderer/{orderer.test.ts => orderer-ai-utils.test.ts} (62%) rename packages/perseus/src/widget-ai-utils/orderer/{prompt-utils.ts => orderer-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/orderer/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/passage-ref/{passage-ref.test.ts => passage-ref-ai-utils.test.ts} (68%) rename packages/perseus/src/widget-ai-utils/passage-ref/{prompt-utils.ts => passage-ref-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/passage/{prompt-utils.test.ts => passage-ai-utils.test.ts} (69%) rename packages/perseus/src/widget-ai-utils/passage/{prompt-utils.ts => passage-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/passage/passage.test.ts rename packages/perseus/src/widget-ai-utils/phet-simulation/{prompt-utils.ts => phet-simulation-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation.test.ts rename packages/perseus/src/widget-ai-utils/plotter/{prompt-utils.test.ts => plotter-ai-utils.test.ts} (74%) rename packages/perseus/src/widget-ai-utils/plotter/{prompt-utils.ts => plotter-ai-utils.ts} (100%) create mode 100644 packages/perseus/src/widget-ai-utils/prompt-types.ts delete mode 100644 packages/perseus/src/widget-ai-utils/python-program/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/python-program/{python-program.test.ts => python-ai-utils.test.ts} (76%) rename packages/perseus/src/widget-ai-utils/python-program/{prompt-utils.ts => python-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/radio/prompt-utils.test.ts rename packages/perseus/src/widget-ai-utils/radio/{radio.test.ts => radio-ai-utils.test.ts} (66%) rename packages/perseus/src/widget-ai-utils/radio/{prompt-utils.ts => radio-ai-utils.ts} (100%) rename packages/perseus/src/widget-ai-utils/sorter/{prompt-utils.test.ts => sorter-ai-utils.test.ts} (86%) rename packages/perseus/src/widget-ai-utils/sorter/{prompt-utils.ts => sorter-ai-utils.ts} (100%) rename packages/perseus/src/widget-ai-utils/video/{prompt-utils.ts => video-ai-utils.ts} (100%) delete mode 100644 packages/perseus/src/widget-ai-utils/video/video.test.ts diff --git a/.changeset/nasty-bikes-travel.md b/.changeset/nasty-bikes-travel.md new file mode 100644 index 0000000000..dd76f3b00a --- /dev/null +++ b/.changeset/nasty-bikes-travel.md @@ -0,0 +1,5 @@ +--- +"@khanacademy/perseus": patch +--- + +Rename AI util files diff --git a/packages/perseus/src/index.ts b/packages/perseus/src/index.ts index 1571da3bfb..505a3e597c 100644 --- a/packages/perseus/src/index.ts +++ b/packages/perseus/src/index.ts @@ -255,4 +255,7 @@ export type { TagsShape, } from "./multi-items/shape-types"; export type {Path} from "./multi-items/trees"; -export type {RendererPromptJSON, WidgetPromptJSON} from "./prompt-types"; +export type { + RendererPromptJSON, + WidgetPromptJSON, +} from "./widget-ai-utils/prompt-types"; diff --git a/packages/perseus/src/prompt-types.ts b/packages/perseus/src/prompt-types.ts deleted file mode 100644 index 964110e6d1..0000000000 --- a/packages/perseus/src/prompt-types.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type {CategorizerPromptJSON} from "./widget-ai-utils/categorizer/prompt-utils"; -import type {DefinitionPromptJSON} from "./widget-ai-utils/definition/prompt-utils"; -import type {DropdownPromptJSON} from "./widget-ai-utils/dropdown/prompt-utils"; -import type {ExplanationPromptJSON} from "./widget-ai-utils/explanation/prompt-utils"; -import type {ExpressionPromptJSON} from "./widget-ai-utils/expression/prompt-utils"; -import type {GradedGroupPromptJSON} from "./widget-ai-utils/graded-group/prompt-utils"; -import type {GradedGroupSetPromptJSON} from "./widget-ai-utils/graded-group-set/prompt-utils"; -import type {GrapherPromptJSON} from "./widget-ai-utils/grapher/prompt-utils"; -import type {GroupPromptJSON} from "./widget-ai-utils/group/prompt-utils"; -import type {ImagePromptJSON} from "./widget-ai-utils/image/prompt-utils"; -import type {InputNumberPromptJSON} from "./widget-ai-utils/input-number/prompt-utils"; -import type {LabelImagePromptJSON} from "./widget-ai-utils/label-image/prompt-utils"; -import type {MatcherPromptJSON} from "./widget-ai-utils/matcher/prompt-utils"; -import type {MatrixPromptJSON} from "./widget-ai-utils/matrix/prompt-utils"; -import type {NumberLinePromptJSON} from "./widget-ai-utils/number-line/prompt-utils"; -import type {NumericInputPromptJSON} from "./widget-ai-utils/numeric-input/prompt-utils"; -import type {OrdererPromptJSON} from "./widget-ai-utils/orderer/prompt-utils"; -import type {PassagePromptJSON} from "./widget-ai-utils/passage/prompt-utils"; -import type {PassageRefPromptJSON} from "./widget-ai-utils/passage-ref/prompt-utils"; -import type {RadioPromptJSON} from "./widget-ai-utils/radio/prompt-utils"; -import type {SorterPromptJSON} from "./widget-ai-utils/sorter/prompt-utils"; -import type {UnsupportedWidgetPromptJSON} from "./widget-ai-utils/unsupported-widget"; - -export type UnsupportedWidget = - | "cs-program" - | "iframe" - | "interaction" - | "interactive-graph-unsupported" - | "measurer" - | "phet-simulation" - | "plotter" - | "python-program" - | "video"; - -export type WidgetPromptJSON = - | CategorizerPromptJSON - | DefinitionPromptJSON - | DropdownPromptJSON - | ExplanationPromptJSON - | ExpressionPromptJSON - | GradedGroupPromptJSON - | GradedGroupSetPromptJSON - | GrapherPromptJSON - | GroupPromptJSON - | ImagePromptJSON - | InputNumberPromptJSON - | LabelImagePromptJSON - | MatcherPromptJSON - | MatrixPromptJSON - | NumberLinePromptJSON - | NumericInputPromptJSON - | OrdererPromptJSON - | PassagePromptJSON - | PassageRefPromptJSON - | RadioPromptJSON - | SorterPromptJSON - | UnsupportedWidgetPromptJSON; - -export type RendererPromptJSON = { - content: string; - widgets: { - [widgetId: string]: WidgetPromptJSON; - }; -}; - -export interface GetPromptJSONInterface { - getPromptJSON(): RendererPromptJSON; -} diff --git a/packages/perseus/src/renderer.tsx b/packages/perseus/src/renderer.tsx index 2b9c5452f4..41e383a641 100644 --- a/packages/perseus/src/renderer.tsx +++ b/packages/perseus/src/renderer.tsx @@ -40,7 +40,6 @@ import type { PerseusWidgetsMap, ShowSolutions, } from "./perseus-types"; -import type {GetPromptJSONInterface, RendererPromptJSON} from "./prompt-types"; import type {PerseusStrings} from "./strings"; import type { APIOptions, @@ -53,6 +52,10 @@ import type { WidgetProps, } from "./types"; import type {UserInputArray, UserInputMap} from "./validation.types"; +import type { + GetPromptJSONInterface, + RendererPromptJSON, +} from "./widget-ai-utils/prompt-types"; import type {KeypadAPI} from "@khanacademy/math-input"; import type {LinterContextProps} from "@khanacademy/perseus-linter"; diff --git a/packages/perseus/src/server-item-renderer.tsx b/packages/perseus/src/server-item-renderer.tsx index 74d711cf17..ad470a8d21 100644 --- a/packages/perseus/src/server-item-renderer.tsx +++ b/packages/perseus/src/server-item-renderer.tsx @@ -22,12 +22,15 @@ import Renderer from "./renderer"; import Util from "./util"; import type {PerseusItem, ShowSolutions} from "./perseus-types"; -import type {GetPromptJSONInterface, RendererPromptJSON} from "./prompt-types"; import type { FocusPath, PerseusDependenciesV2, SharedRendererProps, } from "./types"; +import type { + GetPromptJSONInterface, + RendererPromptJSON, +} from "./widget-ai-utils/prompt-types"; import type {KeypadAPI} from "@khanacademy/math-input"; import type { KeypadContextRendererInterface, diff --git a/packages/perseus/src/types.ts b/packages/perseus/src/types.ts index 136fccd5a8..ec8da5f961 100644 --- a/packages/perseus/src/types.ts +++ b/packages/perseus/src/types.ts @@ -7,7 +7,6 @@ import type { PerseusWidget, PerseusWidgetsMap, } from "./perseus-types"; -import type {WidgetPromptJSON} from "./prompt-types"; import type {PerseusStrings} from "./strings"; import type {SizeClass} from "./util/sizing-utils"; import type { @@ -16,6 +15,7 @@ import type { UserInputArray, UserInputMap, } from "./validation.types"; +import type {WidgetPromptJSON} from "./widget-ai-utils/prompt-types"; import type {KeypadAPI} from "@khanacademy/math-input"; import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core"; import type {LinterContextProps} from "@khanacademy/perseus-linter"; diff --git a/packages/perseus/src/widget-ai-utils/README.md b/packages/perseus/src/widget-ai-utils/README.md index 0aac86c8cc..c5625e5556 100644 --- a/packages/perseus/src/widget-ai-utils/README.md +++ b/packages/perseus/src/widget-ai-utils/README.md @@ -1,15 +1,25 @@ -# What are these utilities for? +# Widget AI utils -We want to have a representation of Perseus widgets that LLMs can understand. -They understand JSON pretty well! This is a set of utility functions for each -widget that get the minimum amount of data for an LLM to understand the state -of the widget, as well as the current user input. +> [!CAUTION] +> This code is not meant to be interwoven with Perseus' core logic. +> We want to keep it as isolated as possible from the rest of Perseus. + +> [!CAUTION] +> Please be cautious making changes within this folder, +> it's an external API consumed by the team working on Khanmigo. + +## Motivation + +1. The team working on Khanmigo needed access to Perseus data for prompt engineering - including both external-facing things (like the ItemData and UserInput) and internal things (like RenderProps) +2. The Perseus team didn't want to write a blank check exposing all of our internal data because that would make it difficult for us to safely make internal changes +3. The goal of the helpers in this folder is to have a set of functions that can take _all_ the information the Khanmigo team could possibly want and return only the pieces the Khanmigo team needs + +## What are these utilities for? + +We want to have a representation of Perseus widgets that LLMs can understand. They understand JSON pretty well! This is a set of utility functions for each widget that get the minimum amount of data for an LLM to understand the state of the widget, as well as the current user input. ### Why don't we just pass the raw widget json? -We do not want to have the an external API relying on the internal -representation of Perseus widgets. That would couple the widgets implementation -to an external API, which would be subject to breakage. This approach balances -having an explicit API, while keeping Perseus widgets mostly unaware of the -LLM prompt requirements. +We do not want to have the external API relying on the internal +representation of Perseus widgets. That would couple the widgets implementation to an external API, which would be subject to breakage. This approach balances having an explicit API, while keeping Perseus widgets mostly unaware of the LLM prompt requirements. diff --git a/packages/perseus/src/widget-ai-utils/categorizer/categorizer.test.ts b/packages/perseus/src/widget-ai-utils/categorizer/categorizer-ai-utils.test.ts similarity index 76% rename from packages/perseus/src/widget-ai-utils/categorizer/categorizer.test.ts rename to packages/perseus/src/widget-ai-utils/categorizer/categorizer-ai-utils.test.ts index 198aef2412..7748b75808 100644 --- a/packages/perseus/src/widget-ai-utils/categorizer/categorizer.test.ts +++ b/packages/perseus/src/widget-ai-utils/categorizer/categorizer-ai-utils.test.ts @@ -5,10 +5,13 @@ import {testDependencies} from "../../../../../testing/test-dependencies"; import * as Dependencies from "../../dependencies"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./categorizer-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; +import type {PerseusCategorizerUserInput} from "../../validation.types"; import type {UserEvent} from "@testing-library/user-event"; -export const randomizedQuestion: PerseusRenderer = { +const randomizedQuestion: PerseusRenderer = { content: "**Classify each graph according to the kind of relationship it suggests.**\n\n$\\qquad\\qquad\\quad\\text{Graph 1}\\qquad\\qquad\\quad\\qquad\\qquad\\quad\\text{Graph 2}$\n\n\n\n[[\u2603 categorizer 1]]\n\n**Graph 1.**\n\n![](https://ka-perseus-graphie.s3.amazonaws.com/049c091ed0978112aba3a36b0591d992baf7b1ac.png)\n\n**Graph 2.**\n\n![](https://ka-perseus-graphie.s3.amazonaws.com/40df186f39fb6d65de6bee0d8b681502d10cb37a.png) \n", images: {}, @@ -35,7 +38,7 @@ export const randomizedQuestion: PerseusRenderer = { }, }; -describe("categorizer widget", () => { +describe("Categorizer AI utils", () => { let userEvent: UserEvent; beforeEach(() => { userEvent = userEventLib.setup({ @@ -47,6 +50,31 @@ describe("categorizer widget", () => { ); }); + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + items: ["Luke Skywalker", "Darth Vader", "Yoda", "Han Solo"], + categories: ["Galactic Empire", "Rebel Alliance"], + values: [1, 0, 1, 1], + }; + + const userInput: PerseusCategorizerUserInput = { + values: [1, 0, 0, 1], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "categorizer", + options: { + items: ["Luke Skywalker", "Darth Vader", "Yoda", "Han Solo"], + categories: ["Galactic Empire", "Rebel Alliance"], + }, + userInput: { + itemToCategoryMapping: [1, 0, 0, 1], + }, + }); + }); + it("should get prompt json which matches the state of the UI for a randomized question", async () => { // arrange const {renderer} = renderQuestion(randomizedQuestion); diff --git a/packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/categorizer/categorizer-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/categorizer/categorizer-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.test.ts deleted file mode 100644 index ead765fa65..0000000000 --- a/packages/perseus/src/widget-ai-utils/categorizer/prompt-utils.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {PerseusCategorizerUserInput} from "../../validation.types"; - -describe("Categorizer getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - items: ["Luke Skywalker", "Darth Vader", "Yoda", "Han Solo"], - categories: ["Galactic Empire", "Rebel Alliance"], - values: [1, 0, 1, 1], - }; - - const userInput: PerseusCategorizerUserInput = { - values: [1, 0, 0, 1], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "categorizer", - options: { - items: ["Luke Skywalker", "Darth Vader", "Yoda", "Han Solo"], - categories: ["Galactic Empire", "Rebel Alliance"], - }, - userInput: { - itemToCategoryMapping: [1, 0, 0, 1], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/cs-program/cs-program.test.ts b/packages/perseus/src/widget-ai-utils/cs-program/cs-program-ai-utils.test.ts similarity index 77% rename from packages/perseus/src/widget-ai-utils/cs-program/cs-program.test.ts rename to packages/perseus/src/widget-ai-utils/cs-program/cs-program-ai-utils.test.ts index 8016484371..5ff58542cf 100644 --- a/packages/perseus/src/widget-ai-utils/cs-program/cs-program.test.ts +++ b/packages/perseus/src/widget-ai-utils/cs-program/cs-program-ai-utils.test.ts @@ -1,8 +1,10 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./cs-program-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; -export const question1: PerseusRenderer = { +const question1: PerseusRenderer = { content: "[[\u2603 cs-program 1]]\n\n", images: {}, widgets: { @@ -28,7 +30,17 @@ export const question1: PerseusRenderer = { }, }; -describe("cs-program widget", () => { +describe("CS Program AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const resultJSON = getPromptJSON(); + + expect(resultJSON).toEqual({ + type: "cs-program", + isSupported: false, + message: "", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1, {isMobile: false}); diff --git a/packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/cs-program/cs-program-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/cs-program/cs-program-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.test.ts deleted file mode 100644 index 243c6c8ebe..0000000000 --- a/packages/perseus/src/widget-ai-utils/cs-program/prompt-utils.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("CS Program getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const resultJSON = getPromptJSON(); - - expect(resultJSON).toEqual({ - type: "cs-program", - isSupported: false, - message: "", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/definition/definition.test.ts b/packages/perseus/src/widget-ai-utils/definition/definition-ai-utils.test.ts similarity index 75% rename from packages/perseus/src/widget-ai-utils/definition/definition.test.ts rename to packages/perseus/src/widget-ai-utils/definition/definition-ai-utils.test.ts index 5548b7ed56..751f5d2070 100644 --- a/packages/perseus/src/widget-ai-utils/definition/definition.test.ts +++ b/packages/perseus/src/widget-ai-utils/definition/definition-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./definition-ai-utils"; + const question = { content: "Read the excerpt and answer the question below. \n\nThe Governor and Council of the Massachusetts had much conference many days; and at last . . . . concluded a peace and friendship with [[\u2603 definition 1]], upon these conditions.", @@ -23,7 +25,22 @@ const question = { }, } as const; -describe("definition widget", () => { +describe("Definition AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + definition: "to confuse or fluster", + togglePrompt: "bumfuzzle", + }; + + const resultJSON = getPromptJSON(renderProps); + + expect(resultJSON).toEqual({ + type: "definition", + definition: "to confuse or fluster", + togglePrompt: "bumfuzzle", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question); diff --git a/packages/perseus/src/widget-ai-utils/definition/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/definition/definition-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/definition/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/definition/definition-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/definition/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/definition/prompt-utils.test.ts deleted file mode 100644 index d7a5ef98e2..0000000000 --- a/packages/perseus/src/widget-ai-utils/definition/prompt-utils.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Definition getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - definition: "to confuse or fluster", - togglePrompt: "bumfuzzle", - }; - - const resultJSON = getPromptJSON(renderProps); - - expect(resultJSON).toEqual({ - type: "definition", - definition: "to confuse or fluster", - togglePrompt: "bumfuzzle", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/dropdown/dropdown.test.ts b/packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.test.ts similarity index 73% rename from packages/perseus/src/widget-ai-utils/dropdown/dropdown.test.ts rename to packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.test.ts index 18aeaa9df9..279de3fe63 100644 --- a/packages/perseus/src/widget-ai-utils/dropdown/dropdown.test.ts +++ b/packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.test.ts @@ -3,10 +3,13 @@ import {userEvent as userEventLib} from "@testing-library/user-event"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./dropdown-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; +import type {PerseusDropdownUserInput} from "../../validation.types"; import type {UserEvent} from "@testing-library/user-event"; -export const question1: PerseusRenderer = { +const question1: PerseusRenderer = { content: "The total number of boxes the forklift can carry is [[☃ dropdown 1]] $60$.", images: {}, @@ -38,7 +41,7 @@ export const question1: PerseusRenderer = { }, }; -describe("dropdown widget", () => { +describe("Dropdown AI utils", () => { let userEvent: UserEvent; beforeEach(() => { userEvent = userEventLib.setup({ @@ -46,6 +49,29 @@ describe("dropdown widget", () => { }); }); + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + choices: ["Pickles", "Tomato", "Onion", "Lettuce"], + }; + + const userInput: PerseusDropdownUserInput = { + value: 3, + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "dropdown", + options: { + items: ["Pickles", "Tomato", "Onion", "Lettuce"], + }, + userInput: { + // Offset to account for placeholder + selectedIndex: 2, + }, + }); + }); + it("should get prompt json which matches the state of the UI for a randomized question", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.test.ts deleted file mode 100644 index 55e5cafa73..0000000000 --- a/packages/perseus/src/widget-ai-utils/dropdown/prompt-utils.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {PerseusDropdownUserInput} from "../../validation.types"; - -describe("Dropdown getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - choices: ["Pickles", "Tomato", "Onion", "Lettuce"], - }; - - const userInput: PerseusDropdownUserInput = { - value: 3, - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "dropdown", - options: { - items: ["Pickles", "Tomato", "Onion", "Lettuce"], - }, - userInput: { - // Offset to account for placeholder - selectedIndex: 2, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/explanation/explanation.test.ts b/packages/perseus/src/widget-ai-utils/explanation/explanation-ai-utils.test.ts similarity index 73% rename from packages/perseus/src/widget-ai-utils/explanation/explanation.test.ts rename to packages/perseus/src/widget-ai-utils/explanation/explanation-ai-utils.test.ts index 009305a911..86b2c88755 100644 --- a/packages/perseus/src/widget-ai-utils/explanation/explanation.test.ts +++ b/packages/perseus/src/widget-ai-utils/explanation/explanation-ai-utils.test.ts @@ -1,8 +1,10 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./explanation-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; -export const question1: PerseusRenderer = { +const question1: PerseusRenderer = { content: "Here's the explanation\n[[\u2603 explanation 1]]\nDid you get that?", images: {}, @@ -30,7 +32,22 @@ export const question1: PerseusRenderer = { }, }; -describe("explanation widget", () => { +describe("Explanation getPromptJSON", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + showPrompt: "Show explanation", + explanation: "This is the explanation", + }; + + const resultJSON = getPromptJSON(renderProps); + + expect(resultJSON).toEqual({ + type: "explanation", + showPrompt: "Show explanation", + explanation: "This is the explanation", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/explanation/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/explanation/explanation-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/explanation/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/explanation/explanation-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/explanation/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/explanation/prompt-utils.test.ts deleted file mode 100644 index 6030406e10..0000000000 --- a/packages/perseus/src/widget-ai-utils/explanation/prompt-utils.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Explanation getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - showPrompt: "Show explanation", - explanation: "This is the explanation", - }; - - const resultJSON = getPromptJSON(renderProps); - - expect(resultJSON).toEqual({ - type: "explanation", - showPrompt: "Show explanation", - explanation: "This is the explanation", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/expression/expression.test.ts b/packages/perseus/src/widget-ai-utils/expression/expression-ai-utils.test.ts similarity index 78% rename from packages/perseus/src/widget-ai-utils/expression/expression.test.ts rename to packages/perseus/src/widget-ai-utils/expression/expression-ai-utils.test.ts index 1d541aa252..9dcfa83d99 100644 --- a/packages/perseus/src/widget-ai-utils/expression/expression.test.ts +++ b/packages/perseus/src/widget-ai-utils/expression/expression-ai-utils.test.ts @@ -3,6 +3,8 @@ import {act} from "@testing-library/react"; import {ItemExtras} from "../../perseus-types"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./expression-ai-utils"; + import type {PerseusAnswerArea, PerseusRenderer} from "../../perseus-types"; const expression = { @@ -37,7 +39,23 @@ const expression = { hints: [], }; -describe("expression widget", () => { +describe("Expression AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + visibleLabel: "Enter an expression", + }; + + const resultJSON = getPromptJSON(renderProps, "2 + 2"); + + expect(resultJSON).toEqual({ + type: "expression", + label: "Enter an expression", + userInput: { + value: "2 + 2", + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion( diff --git a/packages/perseus/src/widget-ai-utils/expression/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/expression/expression-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/expression/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/expression/expression-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/expression/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/expression/prompt-utils.test.ts deleted file mode 100644 index bf38043ddd..0000000000 --- a/packages/perseus/src/widget-ai-utils/expression/prompt-utils.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Expression getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - visibleLabel: "Enter an expression", - }; - - const resultJSON = getPromptJSON(renderProps, "2 + 2"); - - expect(resultJSON).toEqual({ - type: "expression", - label: "Enter an expression", - userInput: { - value: "2 + 2", - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set.test.ts b/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.test.ts similarity index 61% rename from packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set.test.ts rename to packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.test.ts index 3d118638f1..56bddb3c41 100644 --- a/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set.test.ts +++ b/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.test.ts @@ -6,9 +6,11 @@ import * as Dependencies from "../../dependencies"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; import {article1} from "../../widgets/graded-group-set/graded-group-set.testdata"; +import {getPromptJSON} from "./graded-group-set-ai-utils"; + import type {UserEvent} from "@testing-library/user-event"; -describe("graded-group-set widget", () => { +describe("GradedGroupSet AI utils", () => { let userEvent: UserEvent; beforeEach(() => { @@ -21,6 +23,96 @@ describe("graded-group-set widget", () => { ); }); + it("it returns JSON with the expected format and fields", () => { + const activeGroupJSON: any = { + title: "Problem 1a", + content: "$0.5 + 0.4 =$ [[☃ numeric-input 1]]", + widgets: { + "numeric-input 1": { + type: "numeric-input", + alignment: "default", + options: { + labelText: "Numeric input label", + size: "Normal", + coefficient: false, + static: false, + }, + userInput: { + currentValue: "42", + }, + }, + }, + hint: { + content: + "There are many ways to solve this problem. Let's see two student solutions.\n\n###Student A's solution:\n\nI thought in terms of tenths.\n\n$\\phantom{=}0.5 + 0.4$\n\n$=5$ tenths $+ ~4$ tenths\n\n$=9$ tenths\n\n$=0.9$\n\n###Student B's solution:\n\nI used tenths grids.\n\n[[☃ image 1]]\n\n[[☃ image 2]]\n\n[[☃ image 3]]\n\n$\\blueD{0.5} + \\greenD{0.4} = 0.9$\n\n###The answer:\n\n$0.5 + 0.4 = 0.9$", + widgets: { + "image 1": { + options: { + title: "", + backgroundImage: { + url: "web+graphie://ka-perseus-graphie.s3.amazonaws.com/2a56a60275b7227ed9c5b89e489587c8cb13eb7b", + }, + labels: [], + alt: "A square divided into 10 rows to show tenths. 5 of the rows are shaded to represent 5 tenths.", + caption: "", + }, + type: "image", + }, + }, + }, + }; + + const renderProps: any = { + gradedGroups: [{title: "Problem 1a"}, {title: "Problem 1b"}], + }; + + const result = getPromptJSON(renderProps, activeGroupJSON); + + expect(result).toEqual({ + type: "graded-group-set", + options: { + groupCount: 2, + currentGroup: { + title: "Problem 1a", + content: "$0.5 + 0.4 =$ [[☃ numeric-input 1]]", + widgets: { + "numeric-input 1": { + type: "numeric-input", + alignment: "default", + options: { + labelText: "Numeric input label", + size: "Normal", + coefficient: false, + static: false, + }, + userInput: { + currentValue: "42", + }, + }, + }, + hint: { + content: + "There are many ways to solve this problem. Let's see two student solutions.\n\n###Student A's solution:\n\nI thought in terms of tenths.\n\n$\\phantom{=}0.5 + 0.4$\n\n$=5$ tenths $+ ~4$ tenths\n\n$=9$ tenths\n\n$=0.9$\n\n###Student B's solution:\n\nI used tenths grids.\n\n[[☃ image 1]]\n\n[[☃ image 2]]\n\n[[☃ image 3]]\n\n$\\blueD{0.5} + \\greenD{0.4} = 0.9$\n\n###The answer:\n\n$0.5 + 0.4 = 0.9$", + widgets: { + "image 1": { + options: { + title: "", + backgroundImage: { + url: "web+graphie://ka-perseus-graphie.s3.amazonaws.com/2a56a60275b7227ed9c5b89e489587c8cb13eb7b", + }, + labels: [], + alt: "A square divided into 10 rows to show tenths. 5 of the rows are shaded to represent 5 tenths.", + caption: "", + }, + type: "image", + }, + }, + }, + }, + }, + }); + }); + it("should get prompt json which matches the state of the UI when the hint is collapsed", async () => { // Arrange const {renderer} = renderQuestion(article1); diff --git a/packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.ts similarity index 88% rename from packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.ts index b90ca809e9..8221a5b618 100644 --- a/packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.ts +++ b/packages/perseus/src/widget-ai-utils/graded-group-set/graded-group-set-ai-utils.ts @@ -1,5 +1,5 @@ import type gradedGroupSet from "../../widgets/graded-group-set"; -import type {GradedGroupPromptJSON} from "../graded-group/prompt-utils"; +import type {GradedGroupPromptJSON} from "../graded-group/graded-group-ai-utils"; import type React from "react"; export type GradedGroupSetPromptJSON = { diff --git a/packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.test.ts deleted file mode 100644 index 54a8335e4d..0000000000 --- a/packages/perseus/src/widget-ai-utils/graded-group-set/prompt-utils.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("GradedGroupSet getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const activeGroupJSON: any = { - title: "Problem 1a", - content: "$0.5 + 0.4 =$ [[☃ numeric-input 1]]", - widgets: { - "numeric-input 1": { - type: "numeric-input", - alignment: "default", - options: { - labelText: "Numeric input label", - size: "Normal", - coefficient: false, - static: false, - }, - userInput: { - currentValue: "42", - }, - }, - }, - hint: { - content: - "There are many ways to solve this problem. Let's see two student solutions.\n\n###Student A's solution:\n\nI thought in terms of tenths.\n\n$\\phantom{=}0.5 + 0.4$\n\n$=5$ tenths $+ ~4$ tenths\n\n$=9$ tenths\n\n$=0.9$\n\n###Student B's solution:\n\nI used tenths grids.\n\n[[☃ image 1]]\n\n[[☃ image 2]]\n\n[[☃ image 3]]\n\n$\\blueD{0.5} + \\greenD{0.4} = 0.9$\n\n###The answer:\n\n$0.5 + 0.4 = 0.9$", - widgets: { - "image 1": { - options: { - title: "", - backgroundImage: { - url: "web+graphie://ka-perseus-graphie.s3.amazonaws.com/2a56a60275b7227ed9c5b89e489587c8cb13eb7b", - }, - labels: [], - alt: "A square divided into 10 rows to show tenths. 5 of the rows are shaded to represent 5 tenths.", - caption: "", - }, - type: "image", - }, - }, - }, - }; - - const renderProps: any = { - gradedGroups: [{title: "Problem 1a"}, {title: "Problem 1b"}], - }; - - const result = getPromptJSON(renderProps, activeGroupJSON); - - expect(result).toEqual({ - type: "graded-group-set", - options: { - groupCount: 2, - currentGroup: { - title: "Problem 1a", - content: "$0.5 + 0.4 =$ [[☃ numeric-input 1]]", - widgets: { - "numeric-input 1": { - type: "numeric-input", - alignment: "default", - options: { - labelText: "Numeric input label", - size: "Normal", - coefficient: false, - static: false, - }, - userInput: { - currentValue: "42", - }, - }, - }, - hint: { - content: - "There are many ways to solve this problem. Let's see two student solutions.\n\n###Student A's solution:\n\nI thought in terms of tenths.\n\n$\\phantom{=}0.5 + 0.4$\n\n$=5$ tenths $+ ~4$ tenths\n\n$=9$ tenths\n\n$=0.9$\n\n###Student B's solution:\n\nI used tenths grids.\n\n[[☃ image 1]]\n\n[[☃ image 2]]\n\n[[☃ image 3]]\n\n$\\blueD{0.5} + \\greenD{0.4} = 0.9$\n\n###The answer:\n\n$0.5 + 0.4 = 0.9$", - widgets: { - "image 1": { - options: { - title: "", - backgroundImage: { - url: "web+graphie://ka-perseus-graphie.s3.amazonaws.com/2a56a60275b7227ed9c5b89e489587c8cb13eb7b", - }, - labels: [], - alt: "A square divided into 10 rows to show tenths. 5 of the rows are shaded to represent 5 tenths.", - caption: "", - }, - type: "image", - }, - }, - }, - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/graded-group/graded-group.test.ts b/packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.test.ts similarity index 72% rename from packages/perseus/src/widget-ai-utils/graded-group/graded-group.test.ts rename to packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.test.ts index 49bd17b3fa..e785a29c35 100644 --- a/packages/perseus/src/widget-ai-utils/graded-group/graded-group.test.ts +++ b/packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.test.ts @@ -5,7 +5,11 @@ import {testDependencies} from "../../../../../testing/test-dependencies"; import * as Dependencies from "../../dependencies"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./graded-group-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; +import type {CategorizerPromptJSON} from "../categorizer/categorizer-ai-utils"; +import type {ImagePromptJSON} from "../image/image-ai-utils"; import type {UserEvent} from "@testing-library/user-event"; const question: PerseusRenderer = { @@ -75,7 +79,7 @@ const question: PerseusRenderer = { }, }; -describe("graded-group widget", () => { +describe("GradedGroup AI utils", () => { let userEvent: UserEvent; beforeEach(() => { @@ -88,6 +92,108 @@ describe("graded-group widget", () => { ); }); + it("it returns JSON with the expected format and fields when rendererJSON is undefined", () => { + const title = "title"; + const rendererJSON = undefined; + const hintRendererJSON = { + content: "hint content", + widgets: {}, + }; + const result = getPromptJSON(title, rendererJSON, hintRendererJSON); + + expect(result).toEqual({ + type: "graded-group", + title, + content: "", + widgets: {}, + hint: { + content: "hint content", + widgets: {}, + }, + }); + }); + + it("it returns JSON with the expected format and fields", () => { + const title = "title"; + const rendererJSON = { + title: "Metabolic strategies of bacteria", + content: + "1. **Which of the following statements about metabolic strategies of bacteria are true?**\n\n [[☃ categorizer 1]]", + images: {}, + widgets: { + "categorizer 1": { + type: "categorizer", + options: { + items: [ + "Some bacteria conduct photosynthesis and produce oxygen, much like plants.", + "Bacteria are always autotrophic but they may get energy from either light or chemical sources.", + "Some chemosynthetic bacteria introduce energy and fixed carbon into communities where photosynthesis is not possible (e.g., deep-sea vents).", + "Some bacteria live symbiotically inside of host organisms and provide the host with nutrients.", + ], + categories: ["True", "False"], + }, + userInput: { + itemToCategoryMapping: [0, 0, 0, 0], + }, + } satisfies CategorizerPromptJSON, + }, + }; + + const hintRendererJSON = { + content: "hint content", + widgets: { + "image 1": { + type: "image", + options: { + altText: "alt text", + title: "title", + caption: "caption", + imageUrl: "url", + }, + } satisfies ImagePromptJSON, + }, + }; + const result = getPromptJSON(title, rendererJSON, hintRendererJSON); + + expect(result).toEqual({ + type: "graded-group", + title, + content: + "1. **Which of the following statements about metabolic strategies of bacteria are true?**\n\n [[☃ categorizer 1]]", + images: {}, + widgets: { + "categorizer 1": { + type: "categorizer", + options: { + items: [ + "Some bacteria conduct photosynthesis and produce oxygen, much like plants.", + "Bacteria are always autotrophic but they may get energy from either light or chemical sources.", + "Some chemosynthetic bacteria introduce energy and fixed carbon into communities where photosynthesis is not possible (e.g., deep-sea vents).", + "Some bacteria live symbiotically inside of host organisms and provide the host with nutrients.", + ], + categories: ["True", "False"], + }, + userInput: { + itemToCategoryMapping: [0, 0, 0, 0], + }, + }, + }, + hint: { + content: "hint content", + widgets: { + "image 1": { + type: "image", + options: { + altText: "alt text", + title: "title", + caption: "caption", + imageUrl: "url", + }, + }, + }, + }, + }); + }); it("should get prompt json which matches the state of the UI when the hint is collapsed", async () => { // Arrange const {renderer} = renderQuestion(question); diff --git a/packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.ts similarity index 91% rename from packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.ts index 76d81250bd..8f9ab6a30a 100644 --- a/packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.ts +++ b/packages/perseus/src/widget-ai-utils/graded-group/graded-group-ai-utils.ts @@ -1,4 +1,4 @@ -import type {RendererPromptJSON} from "../../prompt-types"; +import type {RendererPromptJSON} from "../prompt-types"; export type GradedGroupPromptJSON = RendererPromptJSON & { type: "graded-group"; diff --git a/packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.test.ts deleted file mode 100644 index d9f7c060c1..0000000000 --- a/packages/perseus/src/widget-ai-utils/graded-group/prompt-utils.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {CategorizerPromptJSON} from "../categorizer/prompt-utils"; -import type {ImagePromptJSON} from "../image/prompt-utils"; - -describe("GradedGroup getPromptJSON", () => { - it("it returns JSON with the expected format and fields when rendererJSON is undefined", () => { - const title = "title"; - const rendererJSON = undefined; - const hintRendererJSON = { - content: "hint content", - widgets: {}, - }; - const result = getPromptJSON(title, rendererJSON, hintRendererJSON); - - expect(result).toEqual({ - type: "graded-group", - title, - content: "", - widgets: {}, - hint: { - content: "hint content", - widgets: {}, - }, - }); - }); - - it("it returns JSON with the expected format and fields", () => { - const title = "title"; - const rendererJSON = { - title: "Metabolic strategies of bacteria", - content: - "1. **Which of the following statements about metabolic strategies of bacteria are true?**\n\n [[☃ categorizer 1]]", - images: {}, - widgets: { - "categorizer 1": { - type: "categorizer", - options: { - items: [ - "Some bacteria conduct photosynthesis and produce oxygen, much like plants.", - "Bacteria are always autotrophic but they may get energy from either light or chemical sources.", - "Some chemosynthetic bacteria introduce energy and fixed carbon into communities where photosynthesis is not possible (e.g., deep-sea vents).", - "Some bacteria live symbiotically inside of host organisms and provide the host with nutrients.", - ], - categories: ["True", "False"], - }, - userInput: { - itemToCategoryMapping: [0, 0, 0, 0], - }, - } satisfies CategorizerPromptJSON, - }, - }; - - const hintRendererJSON = { - content: "hint content", - widgets: { - "image 1": { - type: "image", - options: { - altText: "alt text", - title: "title", - caption: "caption", - imageUrl: "url", - }, - } satisfies ImagePromptJSON, - }, - }; - const result = getPromptJSON(title, rendererJSON, hintRendererJSON); - - expect(result).toEqual({ - type: "graded-group", - title, - content: - "1. **Which of the following statements about metabolic strategies of bacteria are true?**\n\n [[☃ categorizer 1]]", - images: {}, - widgets: { - "categorizer 1": { - type: "categorizer", - options: { - items: [ - "Some bacteria conduct photosynthesis and produce oxygen, much like plants.", - "Bacteria are always autotrophic but they may get energy from either light or chemical sources.", - "Some chemosynthetic bacteria introduce energy and fixed carbon into communities where photosynthesis is not possible (e.g., deep-sea vents).", - "Some bacteria live symbiotically inside of host organisms and provide the host with nutrients.", - ], - categories: ["True", "False"], - }, - userInput: { - itemToCategoryMapping: [0, 0, 0, 0], - }, - }, - }, - hint: { - content: "hint content", - widgets: { - "image 1": { - type: "image", - options: { - altText: "alt text", - title: "title", - caption: "caption", - imageUrl: "url", - }, - }, - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/grapher/grapher.test.ts b/packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.test.ts similarity index 50% rename from packages/perseus/src/widget-ai-utils/grapher/grapher.test.ts rename to packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.test.ts index bb2b6e7889..d16b21600d 100644 --- a/packages/perseus/src/widget-ai-utils/grapher/grapher.test.ts +++ b/packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./grapher-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; const question: PerseusRenderer = { @@ -50,7 +52,97 @@ const question: PerseusRenderer = { }, }; -describe("grapher widget", () => { +describe("Grapher AI utils", () => { + it("it returns JSON with the expected format and fields for a linear graph", () => { + const renderProps: any = { + availableTypes: ["linear"], + graph: { + range: [0, 10, 0, 10], + labels: ["x", "y"], + step: 1, + gridStep: 1, + snapStep: 1, + backgroundImage: {url: "http://khanaacademy.org/image.jpg"}, + }, + }; + + const userInput: any = { + type: "linear", + coords: [ + [0, 0], + [1, 1], + ], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "grapher", + options: { + availableTypes: ["linear"], + range: [0, 10, 0, 10], + labels: ["x", "y"], + tickStep: 1, + gridStep: 1, + snapStep: 1, + backgroundImageUrl: "http://khanaacademy.org/image.jpg", + }, + userInput: { + type: "linear", + coords: [ + [0, 0], + [1, 1], + ], + }, + }); + }); + + it('it returns JSON with the expected format and fields for a "logarithm" graph', () => { + const renderProps: any = { + availableTypes: ["lograithm"], + graph: { + range: [0, 10, 0, 10], + labels: ["x", "y"], + step: 1, + gridStep: 1, + snapStep: 1, + backgroundImage: {url: ""}, + }, + }; + + const userInput: any = { + type: "logarithm", + coords: [ + [0, 0], + [1, 1], + ], + asymptote: [-1, 4], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "grapher", + options: { + availableTypes: ["lograithm"], + range: [0, 10, 0, 10], + labels: ["x", "y"], + tickStep: 1, + gridStep: 1, + snapStep: 1, + backgroundImageUrl: "", + }, + userInput: { + type: "logarithm", + coords: [ + [0, 0], + [1, 1], + ], + asymptote: [-1, 4], + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question); diff --git a/packages/perseus/src/widget-ai-utils/grapher/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/grapher/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/grapher/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/grapher/prompt-utils.test.ts deleted file mode 100644 index fdbb58e7b2..0000000000 --- a/packages/perseus/src/widget-ai-utils/grapher/prompt-utils.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Grapher getPromptJSON", () => { - it("it returns JSON with the expected format and fields for a linear graph", () => { - const renderProps: any = { - availableTypes: ["linear"], - graph: { - range: [0, 10, 0, 10], - labels: ["x", "y"], - step: 1, - gridStep: 1, - snapStep: 1, - backgroundImage: {url: "http://khanaacademy.org/image.jpg"}, - }, - }; - - const userInput: any = { - type: "linear", - coords: [ - [0, 0], - [1, 1], - ], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "grapher", - options: { - availableTypes: ["linear"], - range: [0, 10, 0, 10], - labels: ["x", "y"], - tickStep: 1, - gridStep: 1, - snapStep: 1, - backgroundImageUrl: "http://khanaacademy.org/image.jpg", - }, - userInput: { - type: "linear", - coords: [ - [0, 0], - [1, 1], - ], - }, - }); - }); - - it('it returns JSON with the expected format and fields for a "logarithm" graph', () => { - const renderProps: any = { - availableTypes: ["lograithm"], - graph: { - range: [0, 10, 0, 10], - labels: ["x", "y"], - step: 1, - gridStep: 1, - snapStep: 1, - backgroundImage: {url: ""}, - }, - }; - - const userInput: any = { - type: "logarithm", - coords: [ - [0, 0], - [1, 1], - ], - asymptote: [-1, 4], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "grapher", - options: { - availableTypes: ["lograithm"], - range: [0, 10, 0, 10], - labels: ["x", "y"], - tickStep: 1, - gridStep: 1, - snapStep: 1, - backgroundImageUrl: "", - }, - userInput: { - type: "logarithm", - coords: [ - [0, 0], - [1, 1], - ], - asymptote: [-1, 4], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/group/group.test.ts b/packages/perseus/src/widget-ai-utils/group/group-ai-utils.test.ts similarity index 51% rename from packages/perseus/src/widget-ai-utils/group/group.test.ts rename to packages/perseus/src/widget-ai-utils/group/group-ai-utils.test.ts index 96c338eb5c..cccb08b76d 100644 --- a/packages/perseus/src/widget-ai-utils/group/group.test.ts +++ b/packages/perseus/src/widget-ai-utils/group/group-ai-utils.test.ts @@ -1,8 +1,99 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; -import {question1} from "./group.testdata"; +import {getPromptJSON} from "./group-ai-utils"; +import {question1} from "./group-ai-utils.testdata"; + +import type {RendererPromptJSON} from "../prompt-types"; + +describe("Group AI utils", () => { + it("should return a GroupPromptJSON with default values when rendererJSON is undefined", () => { + const result = getPromptJSON(undefined); + + expect(result).toEqual({ + type: "group", + content: "", + widgets: {}, + }); + }); + + it("should return a GroupPromptJSON when rendererJSON has multiple widgets", () => { + // Arrange + const rendererJSON: RendererPromptJSON = { + content: + "**In one week, how many more hours are in the periods with a $35$ percent discount than in the periods with the regular price?**\n\n[[☃ radio 1]]", + widgets: { + "radio 1": { + type: "radio", + hasNoneOfTheAbove: false, + options: [ + {value: "$45$"}, + {value: "$42$"}, + {value: "$30$"}, + {value: "$18$"}, + {value: "$15$"}, + ], + userInput: { + selectedOptions: [false, false, false, false, false], + }, + }, + "matrix 1": { + type: "matrix", + options: { + height: 3, + width: 3, + }, + userInput: { + answerRows: [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + }, + }, + }, + }; + + // Act + const result = getPromptJSON(rendererJSON); + + // Assert + expect(result).toEqual({ + type: "group", + content: + "**In one week, how many more hours are in the periods with a $35$ percent discount than in the periods with the regular price?**\n\n[[☃ radio 1]]", + widgets: { + "radio 1": { + type: "radio", + hasNoneOfTheAbove: false, + options: [ + {value: "$45$"}, + {value: "$42$"}, + {value: "$30$"}, + {value: "$18$"}, + {value: "$15$"}, + ], + userInput: { + selectedOptions: [false, false, false, false, false], + }, + }, + "matrix 1": { + type: "matrix", + options: { + height: 3, + width: 3, + }, + userInput: { + answerRows: [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9], + ], + }, + }, + }, + }); + }); -describe("group widget", () => { it("Should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/group/group.testdata.ts b/packages/perseus/src/widget-ai-utils/group/group-ai-utils.testdata.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/group/group.testdata.ts rename to packages/perseus/src/widget-ai-utils/group/group-ai-utils.testdata.ts diff --git a/packages/perseus/src/widget-ai-utils/group/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/group/group-ai-utils.ts similarity index 86% rename from packages/perseus/src/widget-ai-utils/group/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/group/group-ai-utils.ts index c2cc9f5cbb..7a269c5e98 100644 --- a/packages/perseus/src/widget-ai-utils/group/prompt-utils.ts +++ b/packages/perseus/src/widget-ai-utils/group/group-ai-utils.ts @@ -1,4 +1,4 @@ -import type {RendererPromptJSON} from "../../prompt-types"; +import type {RendererPromptJSON} from "../prompt-types"; export type GroupPromptJSON = RendererPromptJSON & { type: "group"; diff --git a/packages/perseus/src/widget-ai-utils/group/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/group/prompt-utils.test.ts deleted file mode 100644 index d5a24631d7..0000000000 --- a/packages/perseus/src/widget-ai-utils/group/prompt-utils.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {RendererPromptJSON} from "../../prompt-types"; - -describe("Group getPromptJSON", () => { - it("should return a GroupPromptJSON with default values when rendererJSON is undefined", () => { - const result = getPromptJSON(undefined); - - expect(result).toEqual({ - type: "group", - content: "", - widgets: {}, - }); - }); - - it("should return a GroupPromptJSON when rendererJSON has multiple widgets", () => { - // Arrange - const rendererJSON: RendererPromptJSON = { - content: - "**In one week, how many more hours are in the periods with a $35$ percent discount than in the periods with the regular price?**\n\n[[☃ radio 1]]", - widgets: { - "radio 1": { - type: "radio", - hasNoneOfTheAbove: false, - options: [ - {value: "$45$"}, - {value: "$42$"}, - {value: "$30$"}, - {value: "$18$"}, - {value: "$15$"}, - ], - userInput: { - selectedOptions: [false, false, false, false, false], - }, - }, - "matrix 1": { - type: "matrix", - options: { - height: 3, - width: 3, - }, - userInput: { - answerRows: [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ], - }, - }, - }, - }; - - // Act - const result = getPromptJSON(rendererJSON); - - // Assert - expect(result).toEqual({ - type: "group", - content: - "**In one week, how many more hours are in the periods with a $35$ percent discount than in the periods with the regular price?**\n\n[[☃ radio 1]]", - widgets: { - "radio 1": { - type: "radio", - hasNoneOfTheAbove: false, - options: [ - {value: "$45$"}, - {value: "$42$"}, - {value: "$30$"}, - {value: "$18$"}, - {value: "$15$"}, - ], - userInput: { - selectedOptions: [false, false, false, false, false], - }, - }, - "matrix 1": { - type: "matrix", - options: { - height: 3, - width: 3, - }, - userInput: { - answerRows: [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - ], - }, - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/iframe/iframe.test.ts b/packages/perseus/src/widget-ai-utils/iframe/iframe-ai-utils.test.ts similarity index 83% rename from packages/perseus/src/widget-ai-utils/iframe/iframe.test.ts rename to packages/perseus/src/widget-ai-utils/iframe/iframe-ai-utils.test.ts index b21be78cd7..5bc2541d52 100644 --- a/packages/perseus/src/widget-ai-utils/iframe/iframe.test.ts +++ b/packages/perseus/src/widget-ai-utils/iframe/iframe-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./iframe-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; const question1: PerseusRenderer = { @@ -36,7 +38,17 @@ const question1: PerseusRenderer = { }, }; -describe("iframe widget", () => { +describe("Iframe AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const resultJSON = getPromptJSON(); + + expect(resultJSON).toEqual({ + type: "iframe", + isSupported: false, + message: "", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1, {isMobile: false}); diff --git a/packages/perseus/src/widget-ai-utils/iframe/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/iframe/iframe-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/iframe/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/iframe/iframe-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/iframe/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/iframe/prompt-utils.test.ts deleted file mode 100644 index b742018e18..0000000000 --- a/packages/perseus/src/widget-ai-utils/iframe/prompt-utils.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Iframe getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const resultJSON = getPromptJSON(); - - expect(resultJSON).toEqual({ - type: "iframe", - isSupported: false, - message: "", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/image/image.test.ts b/packages/perseus/src/widget-ai-utils/image/image-ai-utils.test.ts similarity index 78% rename from packages/perseus/src/widget-ai-utils/image/image.test.ts rename to packages/perseus/src/widget-ai-utils/image/image-ai-utils.test.ts index 227da3a1d9..15a2c9bbb8 100644 --- a/packages/perseus/src/widget-ai-utils/image/image.test.ts +++ b/packages/perseus/src/widget-ai-utils/image/image-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./image-ai-utils"; + import type {ImageWidget} from "../../perseus-types"; const question = { @@ -37,7 +39,30 @@ const question = { }, } as const; -describe("image widget", () => { +describe("Image AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + alt: "An image of a textbook", + title: "Textbook", + caption: "A textbook", + backgroundImage: { + url: "https://www.khanacademy.org/some-image.png", + }, + }; + + const resultJSON = getPromptJSON(renderProps); + + expect(resultJSON).toEqual({ + type: "image", + options: { + altText: "An image of a textbook", + title: "Textbook", + caption: "A textbook", + imageUrl: "https://www.khanacademy.org/some-image.png", + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question, {isMobile: false}); diff --git a/packages/perseus/src/widget-ai-utils/image/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/image/image-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/image/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/image/image-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/image/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/image/prompt-utils.test.ts deleted file mode 100644 index aa4b13656d..0000000000 --- a/packages/perseus/src/widget-ai-utils/image/prompt-utils.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Image getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - alt: "An image of a textbook", - title: "Textbook", - caption: "A textbook", - backgroundImage: { - url: "https://www.khanacademy.org/some-image.png", - }, - }; - - const resultJSON = getPromptJSON(renderProps); - - expect(resultJSON).toEqual({ - type: "image", - options: { - altText: "An image of a textbook", - title: "Textbook", - caption: "A textbook", - imageUrl: "https://www.khanacademy.org/some-image.png", - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.test.ts b/packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.test.ts new file mode 100644 index 0000000000..9f5e5671cf --- /dev/null +++ b/packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.test.ts @@ -0,0 +1,99 @@ +import {screen} from "@testing-library/react"; +import {userEvent as userEventLib} from "@testing-library/user-event"; + +import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; + +import {getPromptJSON} from "./input-number-ai-utils"; + +import type {InputNumberWidget, PerseusRenderer} from "../../perseus-types"; +import type {PerseusInputNumberUserInput} from "../../validation.types"; +import type {UserEvent} from "@testing-library/user-event"; + +const question: PerseusRenderer = { + content: + "A sequence is defined recursively as follows:\n\n\n$\\qquad\\displaystyle{{a}_{n}}=-\\frac{1}{a_{n-1}-1} \n~~~~~~\\text{ with}\\qquad\\displaystyle{{a}_{0}}=\\frac{1}{2}\\,$\n\n\nFind the term $a_3$ in the sequence.\n\n[[\u2603 input-number 1]]", + images: {}, + widgets: { + "input-number 1": { + graded: true, + version: { + major: 0, + minor: 0, + }, + static: false, + type: "input-number", + options: { + maxError: 0.1, + inexact: false, + value: 0.5, + simplify: "required", + answerType: "number", + size: "normal", + }, + alignment: "default", + } as InputNumberWidget, + }, +}; + +describe("InputNumber AI utils", () => { + let userEvent: UserEvent; + beforeEach(() => { + userEvent = userEventLib.setup({ + advanceTimers: jest.advanceTimersByTime, + }); + }); + + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + simplify: "optional", + answerType: "integer", + }; + + const userInput: PerseusInputNumberUserInput = { + currentValue: "123", + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "input-number", + options: { + simplify: "optional", + answerType: "integer", + }, + userInput: { + value: "123", + }, + }); + }); + + xit("should get prompt json which matches the state of the UI", async () => { + // Arrange + const {renderer} = renderQuestion(question); + + // Act + const input = "40"; + const textbox = screen.getByRole("textbox"); + await userEvent.click(textbox); + await userEvent.type(textbox, input); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "A sequence is defined recursively as follows:\n\n\n$\\qquad\\displaystyle{{a}_{n}}=-\\frac{1}{a_{n-1}-1} \n~~~~~~\\text{ with}\\qquad\\displaystyle{{a}_{0}}=\\frac{1}{2}\\,$\n\n\nFind the term $a_3$ in the sequence.\n\n[[\u2603 input-number 1]]", + widgets: { + "input-number 1": { + type: "input-number", + options: { + simplify: "required", + answerType: "number", + }, + userInput: { + value: "40", + }, + }, + }, + }); + }); +}); diff --git a/packages/perseus/src/widget-ai-utils/input-number/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/input-number/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/input-number/input-number-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/interaction/interaction.test.ts b/packages/perseus/src/widget-ai-utils/interaction/interaction-ai-utils.test.ts similarity index 96% rename from packages/perseus/src/widget-ai-utils/interaction/interaction.test.ts rename to packages/perseus/src/widget-ai-utils/interaction/interaction-ai-utils.test.ts index 13d342d98a..b434dd94db 100644 --- a/packages/perseus/src/widget-ai-utils/interaction/interaction.test.ts +++ b/packages/perseus/src/widget-ai-utils/interaction/interaction-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./interaction-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; const question1: PerseusRenderer = { @@ -262,7 +264,17 @@ const question1: PerseusRenderer = { }, }; -describe("interaction widget", () => { +describe("Interaction AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const resultJSON = getPromptJSON(); + + expect(resultJSON).toEqual({ + type: "interaction", + isSupported: false, + message: "", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/interaction/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/interaction/interaction-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/interaction/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/interaction/interaction-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/interaction/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/interaction/prompt-utils.test.ts deleted file mode 100644 index 788eeae1a6..0000000000 --- a/packages/perseus/src/widget-ai-utils/interaction/prompt-utils.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Interaction getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const resultJSON = getPromptJSON(); - - expect(resultJSON).toEqual({ - type: "interaction", - isSupported: false, - message: "", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/interactive-graph/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.test.ts similarity index 50% rename from packages/perseus/src/widget-ai-utils/interactive-graph/prompt-utils.test.ts rename to packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.test.ts index a9e8301b84..b4632503a1 100644 --- a/packages/perseus/src/widget-ai-utils/interactive-graph/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.test.ts @@ -1,6 +1,36 @@ -import {getPromptJSON} from "./prompt-utils"; +import {userEvent as userEventLib} from "@testing-library/user-event"; + +import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import { + angleQuestion, + circleQuestion, + linearQuestion, + linearSystemQuestion, + pointQuestion, + polygonQuestion, + quadraticQuestion, + rayQuestion, + segmentQuestion, + sinusoidQuestion, +} from "../../widgets/interactive-graphs/interactive-graph.testdata"; +import {trueForAllMafsSupportedGraphTypes} from "../../widgets/interactive-graphs/mafs-supported-graph-types"; + +import {getPromptJSON} from "./interactive-graph-ai-utils"; + +import type {UserEvent} from "@testing-library/user-event"; + +const apiOptions = { + flags: {mafs: trueForAllMafsSupportedGraphTypes}, +}; + +describe("InteractiveGraph AI utils", () => { + let userEvent: UserEvent; + beforeEach(() => { + userEvent = userEventLib.setup({ + advanceTimers: jest.advanceTimersByTime, + }); + }); -describe("getPromptJSON", () => { it("should return JSON for an angle graph", () => { const renderProps: any = { graph: { @@ -590,4 +620,415 @@ describe("getPromptJSON", () => { getPromptJSON(renderProps, userInput); }).toThrow("Unhandled case for 'fake-graph-type'"); }); + + it("should get prompt JSON for an angle graph", async () => { + // Arrange + const {renderer} = renderQuestion(angleQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "**Drag the vertex of the angle to place the vertex at point $\\text{A}$.** \n\n**Drag another point on the angle to make one of the rays go through point $\\text{B}$.**\n\n**Make the other ray go through one of the unlabeled black points to create an acute angle.** \n*The arc symbol near the vertex indicates the angle being measured.*\n\n[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "angle", + angleOffsetDegrees: 1, + startCoords: undefined, + }, + backgroundImageUrl: + "https://ka-perseus-graphie.s3.amazonaws.com/807ea77cf7031c1b9a45694083f05b5e09b01946.png", + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [6.998933866094739, 0.12216684506098452], + [0, 0], + [6.535062985480412, 2.5085756468171017], + ], + angleOffsetDegrees: 1, + }, + }, + }, + }); + }); + + it("should get prompt JSON for a circle graph", async () => { + // Arrange + const {renderer} = renderQuestion(circleQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: "[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "circle", + startParams: { + center: undefined, + radius: undefined, + }, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + center: [0, 0], + radius: 2, + }, + }, + }, + }); + }); + + it("should get prompt JSON for a linear graph", async () => { + // Arrange + const {renderer} = renderQuestion(linearQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: "[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "linear", + startCoords: undefined, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [-5, 5], + [5, 5], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a linear system graph", async () => { + // Arrange + const {renderer} = renderQuestion(linearSystemQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: "[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "linear-system", + startCoords: undefined, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [ + [-5, 5], + [5, 5], + ], + [ + [-5, -5], + [5, -5], + ], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a point graph", async () => { + // Arrange + const {renderer} = renderQuestion(pointQuestion, apiOptions); + + // Act + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "We want to find the zeros of this polynomial:\n\n$p(x)=x(2x+5)(x+1)$\n\n**Plot all the zeros ($x$-intercepts) of the polynomial in the interactive graph.**\n\n[[\u2603 interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "point", + numPoints: "unlimited", + startCoords: undefined, + }, + backgroundImageUrl: + "web+graphie://ka-perseus-graphie.s3.amazonaws.com/9e825947f778170369f22da5f87239cbf4c1ebe3", + range: [ + [-4, 4], + [-4, 4], + ], + labels: ["x", "y"], + }, + userInput: { + coords: undefined, + }, + }, + }, + }); + }); + + it("should get prompt JSON for a polygon graph", async () => { + // Arrange + const {renderer} = renderQuestion(polygonQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "**Sides shown** Drag the vertices of the triangle below to draw a right triangle with side lengths $3$, $4$, and $5$. \n[[\u2603 interactive-graph 1]] \n", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "polygon", + match: undefined, + numSides: 3, + startCoords: undefined, + }, + backgroundImageUrl: null, + range: [ + [-1, 6], + [-1, 6], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [3.5, 2], + [2.5, 4], + [1.5, 2], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a quadratic graph", async () => { + // Arrange + const {renderer} = renderQuestion(quadraticQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: "[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "quadratic", + startCoords: undefined, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [-5, 5], + [0, -5], + [5, 5], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a ray graph", async () => { + // Arrange + const {renderer} = renderQuestion(rayQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "**Move the ray so it has an endpoint at point $\\text{B}$ and goes through point $\\text{A}$. Then complete the statement below.**\n\n[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "ray", + startCoords: undefined, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [-5, 5], + [5, 5], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a segment graph", async () => { + // Arrange + const {renderer} = renderQuestion(segmentQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "Line segment $\\overline{OG}$ is rotated $180^\\circ$ about the point $(-2,4)$. \n\n**Draw the image of this rotation using the interactive graph.**\n\n*The direction of a rotation by a positive angle is counter-clockwise.* \n\n[[☃ interactive-graph 1]]\n\n", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "segment", + startCoords: undefined, + numSegments: 1, + }, + backgroundImageUrl: null, + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [ + [-5, 5], + [5, 5], + ], + ], + }, + }, + }, + }); + }); + + it("should get prompt JSON for a sinusoid graph", async () => { + // Arrange + const {renderer} = renderQuestion(sinusoidQuestion, apiOptions); + + // Act + await userEvent.tab(); + await userEvent.tab(); + await userEvent.keyboard("{arrowup}{arrowdown}"); + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "**Graph $h(x)=3\\sin(2x-\\pi)+2$ in the interactive widget.** \n*Note that one moveable point always defines an extremum point in the graph and the other point always defines a neighbouring intersection with the midline.*\n\n[[☃ interactive-graph 1]]", + widgets: { + "interactive-graph 1": { + type: "interactive-graph", + options: { + graph: { + type: "sinusoid", + startCoords: undefined, + }, + backgroundImageUrl: + "https://ka-perseus-graphie.s3.amazonaws.com/ba6cf7327a7aaed2386ca00d48b6d554a357ac57.png", + range: [ + [-10, 10], + [-10, 10], + ], + labels: ["x", "y"], + }, + userInput: { + coords: [ + [0, 0], + [3, 2], + ], + }, + }, + }, + }); + }); }); diff --git a/packages/perseus/src/widget-ai-utils/interactive-graph/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/interactive-graph/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph.test.ts b/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph.test.ts deleted file mode 100644 index bd75755237..0000000000 --- a/packages/perseus/src/widget-ai-utils/interactive-graph/interactive-graph.test.ts +++ /dev/null @@ -1,442 +0,0 @@ -import {userEvent as userEventLib} from "@testing-library/user-event"; - -import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; -import { - angleQuestion, - circleQuestion, - linearQuestion, - linearSystemQuestion, - pointQuestion, - polygonQuestion, - quadraticQuestion, - rayQuestion, - segmentQuestion, - sinusoidQuestion, -} from "../../widgets/interactive-graphs/interactive-graph.testdata"; -import {trueForAllMafsSupportedGraphTypes} from "../../widgets/interactive-graphs/mafs-supported-graph-types"; - -import type {UserEvent} from "@testing-library/user-event"; - -const apiOptions = { - flags: {mafs: trueForAllMafsSupportedGraphTypes}, -}; - -describe("interactive-graph widget", () => { - let userEvent: UserEvent; - beforeEach(() => { - userEvent = userEventLib.setup({ - advanceTimers: jest.advanceTimersByTime, - }); - }); - - it("should get prompt JSON for an angle graph", async () => { - // Arrange - const {renderer} = renderQuestion(angleQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "**Drag the vertex of the angle to place the vertex at point $\\text{A}$.** \n\n**Drag another point on the angle to make one of the rays go through point $\\text{B}$.**\n\n**Make the other ray go through one of the unlabeled black points to create an acute angle.** \n*The arc symbol near the vertex indicates the angle being measured.*\n\n[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "angle", - angleOffsetDegrees: 1, - startCoords: undefined, - }, - backgroundImageUrl: - "https://ka-perseus-graphie.s3.amazonaws.com/807ea77cf7031c1b9a45694083f05b5e09b01946.png", - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [6.998933866094739, 0.12216684506098452], - [0, 0], - [6.535062985480412, 2.5085756468171017], - ], - angleOffsetDegrees: 1, - }, - }, - }, - }); - }); - - it("should get prompt JSON for a circle graph", async () => { - // Arrange - const {renderer} = renderQuestion(circleQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: "[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "circle", - startParams: { - center: undefined, - radius: undefined, - }, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - center: [0, 0], - radius: 2, - }, - }, - }, - }); - }); - - it("should get prompt JSON for a linear graph", async () => { - // Arrange - const {renderer} = renderQuestion(linearQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: "[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "linear", - startCoords: undefined, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [-5, 5], - [5, 5], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a linear system graph", async () => { - // Arrange - const {renderer} = renderQuestion(linearSystemQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: "[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "linear-system", - startCoords: undefined, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [ - [-5, 5], - [5, 5], - ], - [ - [-5, -5], - [5, -5], - ], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a point graph", async () => { - // Arrange - const {renderer} = renderQuestion(pointQuestion, apiOptions); - - // Act - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "We want to find the zeros of this polynomial:\n\n$p(x)=x(2x+5)(x+1)$\n\n**Plot all the zeros ($x$-intercepts) of the polynomial in the interactive graph.**\n\n[[\u2603 interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "point", - numPoints: "unlimited", - startCoords: undefined, - }, - backgroundImageUrl: - "web+graphie://ka-perseus-graphie.s3.amazonaws.com/9e825947f778170369f22da5f87239cbf4c1ebe3", - range: [ - [-4, 4], - [-4, 4], - ], - labels: ["x", "y"], - }, - userInput: { - coords: undefined, - }, - }, - }, - }); - }); - - it("should get prompt JSON for a polygon graph", async () => { - // Arrange - const {renderer} = renderQuestion(polygonQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "**Sides shown** Drag the vertices of the triangle below to draw a right triangle with side lengths $3$, $4$, and $5$. \n[[\u2603 interactive-graph 1]] \n", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "polygon", - match: undefined, - numSides: 3, - startCoords: undefined, - }, - backgroundImageUrl: null, - range: [ - [-1, 6], - [-1, 6], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [3.5, 2], - [2.5, 4], - [1.5, 2], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a quadratic graph", async () => { - // Arrange - const {renderer} = renderQuestion(quadraticQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: "[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "quadratic", - startCoords: undefined, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [-5, 5], - [0, -5], - [5, 5], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a ray graph", async () => { - // Arrange - const {renderer} = renderQuestion(rayQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "**Move the ray so it has an endpoint at point $\\text{B}$ and goes through point $\\text{A}$. Then complete the statement below.**\n\n[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "ray", - startCoords: undefined, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [-5, 5], - [5, 5], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a segment graph", async () => { - // Arrange - const {renderer} = renderQuestion(segmentQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "Line segment $\\overline{OG}$ is rotated $180^\\circ$ about the point $(-2,4)$. \n\n**Draw the image of this rotation using the interactive graph.**\n\n*The direction of a rotation by a positive angle is counter-clockwise.* \n\n[[☃ interactive-graph 1]]\n\n", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "segment", - startCoords: undefined, - numSegments: 1, - }, - backgroundImageUrl: null, - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [ - [-5, 5], - [5, 5], - ], - ], - }, - }, - }, - }); - }); - - it("should get prompt JSON for a sinusoid graph", async () => { - // Arrange - const {renderer} = renderQuestion(sinusoidQuestion, apiOptions); - - // Act - await userEvent.tab(); - await userEvent.tab(); - await userEvent.keyboard("{arrowup}{arrowdown}"); - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "**Graph $h(x)=3\\sin(2x-\\pi)+2$ in the interactive widget.** \n*Note that one moveable point always defines an extremum point in the graph and the other point always defines a neighbouring intersection with the midline.*\n\n[[☃ interactive-graph 1]]", - widgets: { - "interactive-graph 1": { - type: "interactive-graph", - options: { - graph: { - type: "sinusoid", - startCoords: undefined, - }, - backgroundImageUrl: - "https://ka-perseus-graphie.s3.amazonaws.com/ba6cf7327a7aaed2386ca00d48b6d554a357ac57.png", - range: [ - [-10, 10], - [-10, 10], - ], - labels: ["x", "y"], - }, - userInput: { - coords: [ - [0, 0], - [3, 2], - ], - }, - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/label-image/label-image.test.ts b/packages/perseus/src/widget-ai-utils/label-image/label-image-ai-utils.test.ts similarity index 63% rename from packages/perseus/src/widget-ai-utils/label-image/label-image.test.ts rename to packages/perseus/src/widget-ai-utils/label-image/label-image-ai-utils.test.ts index b131d5b25f..df7ff58e19 100644 --- a/packages/perseus/src/widget-ai-utils/label-image/label-image.test.ts +++ b/packages/perseus/src/widget-ai-utils/label-image/label-image-ai-utils.test.ts @@ -3,6 +3,8 @@ import {userEvent as userEventLib} from "@testing-library/user-event"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./label-image-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; import type {UserEvent} from "@testing-library/user-event"; @@ -68,7 +70,7 @@ const textQuestion: PerseusRenderer = { }, }; -describe("label image widget", () => { +describe("LabelImage AI utils", () => { let userEvent: UserEvent; beforeEach(() => { userEvent = userEventLib.setup({ @@ -76,6 +78,93 @@ describe("label image widget", () => { }); }); + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + markers: [ + { + answers: ["SUVs"], + label: "The fourth unlabeled bar line.", + x: 25, + y: 17.7, + }, + { + answers: ["Trucks"], + label: "The third unlabeled bar line.", + x: 25, + y: 35.3, + }, + { + answers: ["Cars"], + label: "The second unlabeled bar line.", + x: 25, + y: 53, + }, + { + answers: ["Vans"], + label: "The first unlabeled bar line.", + x: 25, + y: 70.3, + }, + ], + choices: ["Trucks", "Vans", "Cars", "SUVs"], + imageUrl: + "web+graphie://ka-perseus-graphie.s3.amazonaws.com/56c60c72e96cd353e4a8b5434506cd3a21e717af", + imageAlt: + "A bar graph with four bar lines that shows the horizontal axis labeled Number in the parking lot and the vertical axis labeled Vehicle Type. The horizontal axis is labeled, from left to right: 0, 10, 20, 30, 40, and 50. The vertical axis has, from the bottom to the top, four unlabeled bar lines as follows: the first unlabeled bar line extends to the middle of 0 and 10, the second unlabeled bar line extends to 40, the third unlabeled bar line extends to the middle of 20 and 30, and fourth unlabeled bar line extends to 10.", + }; + + const userInput: any = { + markers: [ + { + label: "The fourth unlabeled bar line.", + selected: ["Vans"], + }, + { + label: "The third unlabeled bar line.", + selected: ["SUVs"], + }, + ], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "label-image", + options: { + markers: [ + { + label: "The fourth unlabeled bar line.", + }, + { + label: "The third unlabeled bar line.", + }, + { + label: "The second unlabeled bar line.", + }, + { + label: "The first unlabeled bar line.", + }, + ], + choices: ["Trucks", "Vans", "Cars", "SUVs"], + imageUrl: + "web+graphie://ka-perseus-graphie.s3.amazonaws.com/56c60c72e96cd353e4a8b5434506cd3a21e717af", + imageAlt: + "A bar graph with four bar lines that shows the horizontal axis labeled Number in the parking lot and the vertical axis labeled Vehicle Type. The horizontal axis is labeled, from left to right: 0, 10, 20, 30, 40, and 50. The vertical axis has, from the bottom to the top, four unlabeled bar lines as follows: the first unlabeled bar line extends to the middle of 0 and 10, the second unlabeled bar line extends to 40, the third unlabeled bar line extends to the middle of 20 and 30, and fourth unlabeled bar line extends to 10.", + }, + userInput: { + markers: [ + { + label: "The fourth unlabeled bar line.", + selected: ["Vans"], + }, + { + label: "The third unlabeled bar line.", + selected: ["SUVs"], + }, + ], + }, + }); + }); it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(textQuestion); diff --git a/packages/perseus/src/widget-ai-utils/label-image/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/label-image/label-image-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/label-image/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/label-image/label-image-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/label-image/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/label-image/prompt-utils.test.ts deleted file mode 100644 index 635c695c6e..0000000000 --- a/packages/perseus/src/widget-ai-utils/label-image/prompt-utils.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("LabelImage getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - markers: [ - { - answers: ["SUVs"], - label: "The fourth unlabeled bar line.", - x: 25, - y: 17.7, - }, - { - answers: ["Trucks"], - label: "The third unlabeled bar line.", - x: 25, - y: 35.3, - }, - { - answers: ["Cars"], - label: "The second unlabeled bar line.", - x: 25, - y: 53, - }, - { - answers: ["Vans"], - label: "The first unlabeled bar line.", - x: 25, - y: 70.3, - }, - ], - choices: ["Trucks", "Vans", "Cars", "SUVs"], - imageUrl: - "web+graphie://ka-perseus-graphie.s3.amazonaws.com/56c60c72e96cd353e4a8b5434506cd3a21e717af", - imageAlt: - "A bar graph with four bar lines that shows the horizontal axis labeled Number in the parking lot and the vertical axis labeled Vehicle Type. The horizontal axis is labeled, from left to right: 0, 10, 20, 30, 40, and 50. The vertical axis has, from the bottom to the top, four unlabeled bar lines as follows: the first unlabeled bar line extends to the middle of 0 and 10, the second unlabeled bar line extends to 40, the third unlabeled bar line extends to the middle of 20 and 30, and fourth unlabeled bar line extends to 10.", - }; - - const userInput: any = { - markers: [ - { - label: "The fourth unlabeled bar line.", - selected: ["Vans"], - }, - { - label: "The third unlabeled bar line.", - selected: ["SUVs"], - }, - ], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "label-image", - options: { - markers: [ - { - label: "The fourth unlabeled bar line.", - }, - { - label: "The third unlabeled bar line.", - }, - { - label: "The second unlabeled bar line.", - }, - { - label: "The first unlabeled bar line.", - }, - ], - choices: ["Trucks", "Vans", "Cars", "SUVs"], - imageUrl: - "web+graphie://ka-perseus-graphie.s3.amazonaws.com/56c60c72e96cd353e4a8b5434506cd3a21e717af", - imageAlt: - "A bar graph with four bar lines that shows the horizontal axis labeled Number in the parking lot and the vertical axis labeled Vehicle Type. The horizontal axis is labeled, from left to right: 0, 10, 20, 30, 40, and 50. The vertical axis has, from the bottom to the top, four unlabeled bar lines as follows: the first unlabeled bar line extends to the middle of 0 and 10, the second unlabeled bar line extends to 40, the third unlabeled bar line extends to the middle of 20 and 30, and fourth unlabeled bar line extends to 10.", - }, - userInput: { - markers: [ - { - label: "The fourth unlabeled bar line.", - selected: ["Vans"], - }, - { - label: "The third unlabeled bar line.", - selected: ["SUVs"], - }, - ], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/matcher/matcher.test.tsx b/packages/perseus/src/widget-ai-utils/matcher/matcher-ai-utils.test.tsx similarity index 82% rename from packages/perseus/src/widget-ai-utils/matcher/matcher.test.tsx rename to packages/perseus/src/widget-ai-utils/matcher/matcher-ai-utils.test.tsx index f38bd72f93..a67055c8ed 100644 --- a/packages/perseus/src/widget-ai-utils/matcher/matcher.test.tsx +++ b/packages/perseus/src/widget-ai-utils/matcher/matcher-ai-utils.test.tsx @@ -5,7 +5,10 @@ import {testDependencies} from "../../../../../testing/test-dependencies"; import * as Dependencies from "../../dependencies"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./matcher-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; +import type {PerseusMatcherUserInput} from "../../validation.types"; const question1: PerseusRenderer = { content: @@ -39,7 +42,7 @@ const question1: PerseusRenderer = { }, }; -describe("matcher widget", () => { +describe("Matcher AI utils", () => { beforeEach(() => { jest.spyOn(Dependencies, "getDependencies").mockReturnValue({ ...testDependencies, @@ -58,6 +61,42 @@ describe("matcher widget", () => { }); }); + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + labels: { + left: "Number", + right: "Letter", + }, + left: ["1", "2", "3"], + right: ["a", "b", "c"], + orderMatters: false, + }; + + const userInput: PerseusMatcherUserInput = { + left: ["3", "1", "2"], + right: ["a", "b", "c"], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "matcher", + options: { + labels: { + left: "Number", + right: "Letter", + }, + left: ["1", "2", "3"], + right: ["a", "b", "c"], + orderMatters: false, + }, + userInput: { + left: ["3", "1", "2"], + right: ["a", "b", "c"], + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1, {isMobile: false}); diff --git a/packages/perseus/src/widget-ai-utils/matcher/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/matcher/matcher-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/matcher/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/matcher/matcher-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/matcher/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/matcher/prompt-utils.test.ts deleted file mode 100644 index c08d8f1bdf..0000000000 --- a/packages/perseus/src/widget-ai-utils/matcher/prompt-utils.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {PerseusMatcherUserInput} from "../../validation.types"; - -describe("Matcher getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - labels: { - left: "Number", - right: "Letter", - }, - left: ["1", "2", "3"], - right: ["a", "b", "c"], - orderMatters: false, - }; - - const userInput: PerseusMatcherUserInput = { - left: ["3", "1", "2"], - right: ["a", "b", "c"], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "matcher", - options: { - labels: { - left: "Number", - right: "Letter", - }, - left: ["1", "2", "3"], - right: ["a", "b", "c"], - orderMatters: false, - }, - userInput: { - left: ["3", "1", "2"], - right: ["a", "b", "c"], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/matrix/matrix.test.ts b/packages/perseus/src/widget-ai-utils/matrix/matrix-ai-utils.test.ts similarity index 76% rename from packages/perseus/src/widget-ai-utils/matrix/matrix.test.ts rename to packages/perseus/src/widget-ai-utils/matrix/matrix-ai-utils.test.ts index 0daa88c072..8c284b647b 100644 --- a/packages/perseus/src/widget-ai-utils/matrix/matrix.test.ts +++ b/packages/perseus/src/widget-ai-utils/matrix/matrix-ai-utils.test.ts @@ -3,6 +3,8 @@ import {userEvent as userEventLib} from "@testing-library/user-event"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./matrix-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; import type {UserEvent} from "@testing-library/user-event"; @@ -33,7 +35,7 @@ const question: PerseusRenderer = { }, }; -describe("matrix widget", () => { +describe("Matrix AI utils", () => { let userEvent: UserEvent; beforeEach(() => { userEvent = userEventLib.setup({ @@ -41,6 +43,37 @@ describe("matrix widget", () => { }); }); + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + matrixBoardSize: [4, 3], + }; + + const userInput: any = { + answers: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "matrix", + options: { + width: 3, + height: 4, + }, + userInput: { + answerRows: [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + ], + }, + }); + }); + it("Should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question); diff --git a/packages/perseus/src/widget-ai-utils/matrix/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/matrix/matrix-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/matrix/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/matrix/matrix-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/matrix/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/matrix/prompt-utils.test.ts deleted file mode 100644 index 40936e5fec..0000000000 --- a/packages/perseus/src/widget-ai-utils/matrix/prompt-utils.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Matrix getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - matrixBoardSize: [4, 3], - }; - - const userInput: any = { - answers: [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - ], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "matrix", - options: { - width: 3, - height: 4, - }, - userInput: { - answerRows: [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], - ], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/measurer/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/measurer/measurer-ai-utils.test.ts similarity index 74% rename from packages/perseus/src/widget-ai-utils/measurer/prompt-utils.test.ts rename to packages/perseus/src/widget-ai-utils/measurer/measurer-ai-utils.test.ts index ac0fc10b2b..5addea9791 100644 --- a/packages/perseus/src/widget-ai-utils/measurer/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/measurer/measurer-ai-utils.test.ts @@ -1,6 +1,6 @@ -import {getPromptJSON} from "./prompt-utils"; +import {getPromptJSON} from "./measurer-ai-utils"; -describe("Measurer getPromptJSON", () => { +describe("Measurer AI utils", () => { it("it returns JSON with the expected format and fields", () => { const resultJSON = getPromptJSON(); diff --git a/packages/perseus/src/widget-ai-utils/measurer/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/measurer/measurer-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/measurer/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/measurer/measurer-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/number-line/number-line.test.ts b/packages/perseus/src/widget-ai-utils/number-line/number-line-ai-utils.test.ts similarity index 71% rename from packages/perseus/src/widget-ai-utils/number-line/number-line.test.ts rename to packages/perseus/src/widget-ai-utils/number-line/number-line-ai-utils.test.ts index eeeb32b002..4561d18f5d 100644 --- a/packages/perseus/src/widget-ai-utils/number-line/number-line.test.ts +++ b/packages/perseus/src/widget-ai-utils/number-line/number-line-ai-utils.test.ts @@ -2,6 +2,8 @@ import {act} from "@testing-library/react"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./number-line-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; export const question: PerseusRenderer = { @@ -33,7 +35,35 @@ export const question: PerseusRenderer = { }, }; -describe("number-line widget", () => { +describe("NumberLine AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + range: [0, 10], + numDivisions: 10, + snapDivisions: 2, + }; + + const userInput: any = { + numLinePosition: 5, + numDivisions: 10, + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "number-line", + options: { + range: [0, 10], + numDivisions: 10, + snapDivisions: 2, + }, + userInput: { + numLinePosition: 5, + numDivisions: 10, + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question); diff --git a/packages/perseus/src/widget-ai-utils/number-line/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/number-line/number-line-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/number-line/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/number-line/number-line-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/number-line/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/number-line/prompt-utils.test.ts deleted file mode 100644 index 542442c495..0000000000 --- a/packages/perseus/src/widget-ai-utils/number-line/prompt-utils.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("NumberLine getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - range: [0, 10], - numDivisions: 10, - snapDivisions: 2, - }; - - const userInput: any = { - numLinePosition: 5, - numDivisions: 10, - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "number-line", - options: { - range: [0, 10], - numDivisions: 10, - snapDivisions: 2, - }, - userInput: { - numLinePosition: 5, - numDivisions: 10, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/orderer/orderer.test.ts b/packages/perseus/src/widget-ai-utils/orderer/orderer-ai-utils.test.ts similarity index 62% rename from packages/perseus/src/widget-ai-utils/orderer/orderer.test.ts rename to packages/perseus/src/widget-ai-utils/orderer/orderer-ai-utils.test.ts index a45a1ae981..6ee4e430c2 100644 --- a/packages/perseus/src/widget-ai-utils/orderer/orderer.test.ts +++ b/packages/perseus/src/widget-ai-utils/orderer/orderer-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./orderer-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; const question1: PerseusRenderer = { @@ -30,7 +32,45 @@ const question1: PerseusRenderer = { }, }; -describe("orderer widget", () => { +describe("Orderer AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + options: [ + { + content: "Third item", + images: {}, + widgets: {}, + }, + { + content: "First item", + images: {}, + widgets: {}, + }, + { + content: "Second item", + images: {}, + widgets: {}, + }, + ], + }; + + const userInput = { + current: ["First item", "Second item", "Third item"], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "orderer", + options: { + options: ["Third item", "First item", "Second item"], + }, + userInput: { + values: ["First item", "Second item", "Third item"], + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/orderer/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/orderer/orderer-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/orderer/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/orderer/orderer-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/orderer/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/orderer/prompt-utils.test.ts deleted file mode 100644 index 4e022a45fe..0000000000 --- a/packages/perseus/src/widget-ai-utils/orderer/prompt-utils.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Orderer getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - options: [ - { - content: "Third item", - images: {}, - widgets: {}, - }, - { - content: "First item", - images: {}, - widgets: {}, - }, - { - content: "Second item", - images: {}, - widgets: {}, - }, - ], - }; - - const userInput = { - current: ["First item", "Second item", "Third item"], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "orderer", - options: { - options: ["Third item", "First item", "Second item"], - }, - userInput: { - values: ["First item", "Second item", "Third item"], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/passage-ref/passage-ref.test.ts b/packages/perseus/src/widget-ai-utils/passage-ref/passage-ref-ai-utils.test.ts similarity index 68% rename from packages/perseus/src/widget-ai-utils/passage-ref/passage-ref.test.ts rename to packages/perseus/src/widget-ai-utils/passage-ref/passage-ref-ai-utils.test.ts index c590c3c986..24b342c171 100644 --- a/packages/perseus/src/widget-ai-utils/passage-ref/passage-ref.test.ts +++ b/packages/perseus/src/widget-ai-utils/passage-ref/passage-ref-ai-utils.test.ts @@ -2,6 +2,8 @@ import {act} from "@testing-library/react"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./passage-ref-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; const question1: PerseusRenderer = { @@ -24,7 +26,26 @@ const question1: PerseusRenderer = { }, }; -describe("passage-ref widget", () => { +describe("PassageRef AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const renderProps: any = { + passageNumber: 1, + referenceNumber: 1, + summaryText: "This is text summarizing the passage.", + }; + + const resultJSON = getPromptJSON(renderProps); + + expect(resultJSON).toEqual({ + type: "passage-ref", + options: { + passageNumber: 1, + referenceNumber: 1, + summaryText: "This is text summarizing the passage.", + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1); diff --git a/packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/passage-ref/passage-ref-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/passage-ref/passage-ref-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.test.ts deleted file mode 100644 index 274e0e8fa4..0000000000 --- a/packages/perseus/src/widget-ai-utils/passage-ref/prompt-utils.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("PassageRef getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const renderProps: any = { - passageNumber: 1, - referenceNumber: 1, - summaryText: "This is text summarizing the passage.", - }; - - const resultJSON = getPromptJSON(renderProps); - - expect(resultJSON).toEqual({ - type: "passage-ref", - options: { - passageNumber: 1, - referenceNumber: 1, - summaryText: "This is text summarizing the passage.", - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/passage/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/passage/passage-ai-utils.test.ts similarity index 69% rename from packages/perseus/src/widget-ai-utils/passage/prompt-utils.test.ts rename to packages/perseus/src/widget-ai-utils/passage/passage-ai-utils.test.ts index 5f6e1ea8bc..f3792909de 100644 --- a/packages/perseus/src/widget-ai-utils/passage/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/passage/passage-ai-utils.test.ts @@ -1,6 +1,35 @@ -import {getPromptJSON} from "./prompt-utils"; +import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; -describe("Passage getPromptJSON", () => { +import {getPromptJSON} from "./passage-ai-utils"; + +import type {PerseusRenderer} from "../../perseus-types"; + +const question1: PerseusRenderer = { + content: "[[☃ passage 1]]\n\n", + images: {}, + widgets: { + "passage 1": { + alignment: "default", + graded: true, + options: { + footnotes: "", + passageText: + "Sociologists study folktales because they provide a means of understanding the distinctive values of a culture. However, the folktales in almost all cultures are adaptations of the same ancient narratives to the local milieu.\n", + passageTitle: "Why do sociologists study folktales?", + showLineNumbers: false, + static: false, + }, + static: false, + type: "passage", + version: { + major: 0, + minor: 0, + }, + }, + }, +}; + +describe("Passage AI utils", () => { it("it returns JSON with the expected format and fields", () => { const renderProps: any = { footnotes: "An example footnote", @@ -21,4 +50,28 @@ describe("Passage getPromptJSON", () => { }, }); }); + + it("should get prompt json which matches the state of the UI", async () => { + // Arrange + const {renderer} = renderQuestion(question1); + + // Act + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: "[[☃ passage 1]]\n\n", + widgets: { + "passage 1": { + type: "passage", + options: { + footnotes: "", + passageText: + "Sociologists study folktales because they provide a means of understanding the distinctive values of a culture. However, the folktales in almost all cultures are adaptations of the same ancient narratives to the local milieu.\n", + passageTitle: "Why do sociologists study folktales?", + }, + }, + }, + }); + }); }); diff --git a/packages/perseus/src/widget-ai-utils/passage/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/passage/passage-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/passage/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/passage/passage-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/passage/passage.test.ts b/packages/perseus/src/widget-ai-utils/passage/passage.test.ts deleted file mode 100644 index 09cabc0de4..0000000000 --- a/packages/perseus/src/widget-ai-utils/passage/passage.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; - -import type {PerseusRenderer} from "../../perseus-types"; - -const question1: PerseusRenderer = { - content: "[[☃ passage 1]]\n\n", - images: {}, - widgets: { - "passage 1": { - alignment: "default", - graded: true, - options: { - footnotes: "", - passageText: - "Sociologists study folktales because they provide a means of understanding the distinctive values of a culture. However, the folktales in almost all cultures are adaptations of the same ancient narratives to the local milieu.\n", - passageTitle: "Why do sociologists study folktales?", - showLineNumbers: false, - static: false, - }, - static: false, - type: "passage", - version: { - major: 0, - minor: 0, - }, - }, - }, -}; - -describe("passage widget", () => { - it("should get prompt json which matches the state of the UI", async () => { - // Arrange - const {renderer} = renderQuestion(question1); - - // Act - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: "[[☃ passage 1]]\n\n", - widgets: { - "passage 1": { - type: "passage", - options: { - footnotes: "", - passageText: - "Sociologists study folktales because they provide a means of understanding the distinctive values of a culture. However, the folktales in almost all cultures are adaptations of the same ancient narratives to the local milieu.\n", - passageTitle: "Why do sociologists study folktales?", - }, - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation.test.ts b/packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation.test.ts deleted file mode 100644 index ed37321ae8..0000000000 --- a/packages/perseus/src/widget-ai-utils/phet-simulation/phet-simulation.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; - -import type {PerseusRenderer} from "../../perseus-types"; - -const question1: PerseusRenderer = { - content: - "Do this fun PhET simulation! A projectile data lab!\n[[\u2603 phet-simulation 1]]\n", - images: {}, - widgets: { - "phet-simulation 1": { - graded: false, - version: {major: 0, minor: 0}, - static: false, - type: "phet-simulation", - options: { - url: "https://phet.colorado.edu/sims/html/projectile-data-lab/latest/projectile-data-lab_all.html", - description: "Projectile Data Lab", - }, - alignment: "default", - }, - }, -}; - -describe("phet-simulation widget", () => { - beforeEach(() => { - global.fetch = jest.fn(() => - Promise.resolve({ - json: () => - Promise.resolve({ - en: {stringConstant: "localized string"}, - }), - ok: true, - }), - ) as jest.Mock; - global.URL.canParse = jest.fn(() => true) as jest.Mock; - }); - - it("should get prompt json which matches the state of the UI", async () => { - // Arrange - const {renderer} = renderQuestion(question1, {isMobile: false}); - - // Act - const json = renderer.getPromptJSON(); - - // Assert - expect(json).toEqual({ - content: - "Do this fun PhET simulation! A projectile data lab!\n[[\u2603 phet-simulation 1]]\n", - widgets: { - "phet-simulation 1": { - type: "phet-simulation", - isSupported: false, - message: "", - }, - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.test.ts index 775e8dda7a..07470e9704 100644 --- a/packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/phet-simulation/prompt-utils.test.ts @@ -1,6 +1,42 @@ -import {getPromptJSON} from "./prompt-utils"; +import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; + +import {getPromptJSON} from "./phet-simulation-ai-utils"; + +import type {PerseusRenderer} from "../../perseus-types"; + +const question1: PerseusRenderer = { + content: + "Do this fun PhET simulation! A projectile data lab!\n[[\u2603 phet-simulation 1]]\n", + images: {}, + widgets: { + "phet-simulation 1": { + graded: false, + version: {major: 0, minor: 0}, + static: false, + type: "phet-simulation", + options: { + url: "https://phet.colorado.edu/sims/html/projectile-data-lab/latest/projectile-data-lab_all.html", + description: "Projectile Data Lab", + }, + alignment: "default", + }, + }, +}; + +describe("PhET Simulation AI utils", () => { + beforeEach(() => { + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => + Promise.resolve({ + en: {stringConstant: "localized string"}, + }), + ok: true, + }), + ) as jest.Mock; + global.URL.canParse = jest.fn(() => true) as jest.Mock; + }); -describe("PhET Simulation getPromptJSON", () => { it("it returns JSON with the expected format and fields", () => { const resultJSON = getPromptJSON(); @@ -10,4 +46,25 @@ describe("PhET Simulation getPromptJSON", () => { message: "", }); }); + + it("should get prompt json which matches the state of the UI", async () => { + // Arrange + const {renderer} = renderQuestion(question1, {isMobile: false}); + + // Act + const json = renderer.getPromptJSON(); + + // Assert + expect(json).toEqual({ + content: + "Do this fun PhET simulation! A projectile data lab!\n[[\u2603 phet-simulation 1]]\n", + widgets: { + "phet-simulation 1": { + type: "phet-simulation", + isSupported: false, + message: "", + }, + }, + }); + }); }); diff --git a/packages/perseus/src/widget-ai-utils/plotter/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/plotter/plotter-ai-utils.test.ts similarity index 74% rename from packages/perseus/src/widget-ai-utils/plotter/prompt-utils.test.ts rename to packages/perseus/src/widget-ai-utils/plotter/plotter-ai-utils.test.ts index 5c8ad40653..86f0d3d822 100644 --- a/packages/perseus/src/widget-ai-utils/plotter/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/plotter/plotter-ai-utils.test.ts @@ -1,6 +1,6 @@ -import {getPromptJSON} from "./prompt-utils"; +import {getPromptJSON} from "./plotter-ai-utils"; -describe("Plotter getPromptJSON", () => { +describe("Plotter AI utils", () => { it("it returns JSON with the expected format and fields", () => { const resultJSON = getPromptJSON(); diff --git a/packages/perseus/src/widget-ai-utils/plotter/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/plotter/plotter-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/plotter/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/plotter/plotter-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/prompt-types.ts b/packages/perseus/src/widget-ai-utils/prompt-types.ts new file mode 100644 index 0000000000..83aa7fabd2 --- /dev/null +++ b/packages/perseus/src/widget-ai-utils/prompt-types.ts @@ -0,0 +1,68 @@ +import type {CategorizerPromptJSON} from "./categorizer/categorizer-ai-utils"; +import type {DefinitionPromptJSON} from "./definition/definition-ai-utils"; +import type {DropdownPromptJSON} from "./dropdown/dropdown-ai-utils"; +import type {ExplanationPromptJSON} from "./explanation/explanation-ai-utils"; +import type {ExpressionPromptJSON} from "./expression/expression-ai-utils"; +import type {GradedGroupPromptJSON} from "./graded-group/graded-group-ai-utils"; +import type {GradedGroupSetPromptJSON} from "./graded-group-set/graded-group-set-ai-utils"; +import type {GrapherPromptJSON} from "./grapher/grapher-ai-utils"; +import type {GroupPromptJSON} from "./group/group-ai-utils"; +import type {ImagePromptJSON} from "./image/image-ai-utils"; +import type {InputNumberPromptJSON} from "./input-number/input-number-ai-utils"; +import type {LabelImagePromptJSON} from "./label-image/label-image-ai-utils"; +import type {MatcherPromptJSON} from "./matcher/matcher-ai-utils"; +import type {MatrixPromptJSON} from "./matrix/matrix-ai-utils"; +import type {NumberLinePromptJSON} from "./number-line/number-line-ai-utils"; +import type {NumericInputPromptJSON} from "./numeric-input/prompt-utils"; +import type {OrdererPromptJSON} from "./orderer/orderer-ai-utils"; +import type {PassagePromptJSON} from "./passage/passage-ai-utils"; +import type {PassageRefPromptJSON} from "./passage-ref/passage-ref-ai-utils"; +import type {RadioPromptJSON} from "./radio/radio-ai-utils"; +import type {SorterPromptJSON} from "./sorter/sorter-ai-utils"; +import type {UnsupportedWidgetPromptJSON} from "./unsupported-widget"; + +export type UnsupportedWidget = + | "cs-program" + | "iframe" + | "interaction" + | "interactive-graph-unsupported" + | "measurer" + | "phet-simulation" + | "plotter" + | "python-program" + | "video"; + +export type WidgetPromptJSON = + | CategorizerPromptJSON + | DefinitionPromptJSON + | DropdownPromptJSON + | ExplanationPromptJSON + | ExpressionPromptJSON + | GradedGroupPromptJSON + | GradedGroupSetPromptJSON + | GrapherPromptJSON + | GroupPromptJSON + | ImagePromptJSON + | InputNumberPromptJSON + | LabelImagePromptJSON + | MatcherPromptJSON + | MatrixPromptJSON + | NumberLinePromptJSON + | NumericInputPromptJSON + | OrdererPromptJSON + | PassagePromptJSON + | PassageRefPromptJSON + | RadioPromptJSON + | SorterPromptJSON + | UnsupportedWidgetPromptJSON; + +export type RendererPromptJSON = { + content: string; + widgets: { + [widgetId: string]: WidgetPromptJSON; + }; +}; + +export interface GetPromptJSONInterface { + getPromptJSON(): RendererPromptJSON; +} diff --git a/packages/perseus/src/widget-ai-utils/python-program/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/python-program/prompt-utils.test.ts deleted file mode 100644 index 26e6546492..0000000000 --- a/packages/perseus/src/widget-ai-utils/python-program/prompt-utils.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -describe("Python Program getPromptJSON", () => { - it("it returns JSON with the expected format and fields", () => { - const resultJSON = getPromptJSON(); - - expect(resultJSON).toEqual({ - type: "python-program", - isSupported: false, - message: "", - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/python-program/python-program.test.ts b/packages/perseus/src/widget-ai-utils/python-program/python-ai-utils.test.ts similarity index 76% rename from packages/perseus/src/widget-ai-utils/python-program/python-program.test.ts rename to packages/perseus/src/widget-ai-utils/python-program/python-ai-utils.test.ts index 1114150530..d4a73293b3 100644 --- a/packages/perseus/src/widget-ai-utils/python-program/python-program.test.ts +++ b/packages/perseus/src/widget-ai-utils/python-program/python-ai-utils.test.ts @@ -1,5 +1,7 @@ import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./python-ai-utils"; + import type {PerseusRenderer} from "../../perseus-types"; export const question1: PerseusRenderer = { @@ -19,7 +21,17 @@ export const question1: PerseusRenderer = { }, }; -describe("python-program widget", () => { +describe("Python Program AI utils", () => { + it("it returns JSON with the expected format and fields", () => { + const resultJSON = getPromptJSON(); + + expect(resultJSON).toEqual({ + type: "python-program", + isSupported: false, + message: "", + }); + }); + it("should get prompt json which matches the state of the UI", async () => { // Arrange const {renderer} = renderQuestion(question1, {isMobile: false}); diff --git a/packages/perseus/src/widget-ai-utils/python-program/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/python-program/python-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/python-program/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/python-program/python-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/radio/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/radio/prompt-utils.test.ts deleted file mode 100644 index a05196d202..0000000000 --- a/packages/perseus/src/widget-ai-utils/radio/prompt-utils.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {getPromptJSON} from "./prompt-utils"; - -import type {PerseusRadioUserInput} from "../../validation.types"; - -describe("Radio getPromptJSON", () => { - it("should get prompt json which matches the state of the UI", () => { - const renderProps: any = { - numCorrect: 1, - countChoices: false, - deselectEnabled: false, - hasNoneOfTheAbove: false, - multipleSelect: false, - choices: [ - { - content: "Content 4", - originalIndex: 3, - }, - { - content: "Content 2", - originalIndex: 1, - }, - { - content: "Content 1", - originalIndex: 0, - }, - - { - content: "Content 3", - originalIndex: 2, - }, - ], - selectedChoices: [true, false, false, false], - }; - - const userInput: PerseusRadioUserInput = { - choicesSelected: [true, false, false, false], - }; - - const resultJSON = getPromptJSON(renderProps, userInput); - - expect(resultJSON).toEqual({ - type: "radio", - hasNoneOfTheAbove: false, - options: [ - {value: "Content 4"}, - {value: "Content 2"}, - {value: "Content 1"}, - {value: "Content 3"}, - ], - userInput: { - selectedOptions: [true, false, false, false], - }, - }); - }); -}); diff --git a/packages/perseus/src/widget-ai-utils/radio/radio.test.ts b/packages/perseus/src/widget-ai-utils/radio/radio-ai-utils.test.ts similarity index 66% rename from packages/perseus/src/widget-ai-utils/radio/radio.test.ts rename to packages/perseus/src/widget-ai-utils/radio/radio-ai-utils.test.ts index b4a883d0b3..f0b984a586 100644 --- a/packages/perseus/src/widget-ai-utils/radio/radio.test.ts +++ b/packages/perseus/src/widget-ai-utils/radio/radio-ai-utils.test.ts @@ -5,7 +5,10 @@ import {testDependencies} from "../../../../../testing/test-dependencies"; import * as Dependencies from "../../dependencies"; import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; +import {getPromptJSON} from "./radio-ai-utils"; + import type {PerseusRenderer, RadioWidget} from "../../perseus-types"; +import type {PerseusRadioUserInput} from "../../validation.types"; import type {UserEvent} from "@testing-library/user-event"; const shuffledQuestion: PerseusRenderer = { @@ -52,7 +55,7 @@ const shuffledQuestion: PerseusRenderer = { }, }; -describe("radio widget", () => { +describe("Radio AI utils", () => { let userEvent: UserEvent; beforeEach(() => { userEvent = userEventLib.setup({ @@ -64,6 +67,56 @@ describe("radio widget", () => { ); }); + it("should get prompt json which matches the state of the UI", () => { + const renderProps: any = { + numCorrect: 1, + countChoices: false, + deselectEnabled: false, + hasNoneOfTheAbove: false, + multipleSelect: false, + choices: [ + { + content: "Content 4", + originalIndex: 3, + }, + { + content: "Content 2", + originalIndex: 1, + }, + { + content: "Content 1", + originalIndex: 0, + }, + + { + content: "Content 3", + originalIndex: 2, + }, + ], + selectedChoices: [true, false, false, false], + }; + + const userInput: PerseusRadioUserInput = { + choicesSelected: [true, false, false, false], + }; + + const resultJSON = getPromptJSON(renderProps, userInput); + + expect(resultJSON).toEqual({ + type: "radio", + hasNoneOfTheAbove: false, + options: [ + {value: "Content 4"}, + {value: "Content 2"}, + {value: "Content 1"}, + {value: "Content 3"}, + ], + userInput: { + selectedOptions: [true, false, false, false], + }, + }); + }); + it("should get prompt json which matches the state of the UI", async () => { const indexToSelect = 1; const {renderer} = renderQuestion(shuffledQuestion); diff --git a/packages/perseus/src/widget-ai-utils/radio/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/radio/radio-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/radio/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/radio/radio-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/sorter/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/sorter/sorter-ai-utils.test.ts similarity index 86% rename from packages/perseus/src/widget-ai-utils/sorter/prompt-utils.test.ts rename to packages/perseus/src/widget-ai-utils/sorter/sorter-ai-utils.test.ts index 341083ca12..6d8fa17169 100644 --- a/packages/perseus/src/widget-ai-utils/sorter/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/sorter/sorter-ai-utils.test.ts @@ -1,8 +1,8 @@ -import {getPromptJSON} from "./prompt-utils"; +import {getPromptJSON} from "./sorter-ai-utils"; import type {PerseusSorterUserInput} from "../../validation.types"; -describe("Sorter getPromptJSON", () => { +describe("Sorter AI utils", () => { it("it returns JSON with the expected format and fields", () => { const userInput: PerseusSorterUserInput = { options: ["Pickles", "Tomato", "Onion", "Lettuce"], diff --git a/packages/perseus/src/widget-ai-utils/sorter/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/sorter/sorter-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/sorter/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/sorter/sorter-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/unsupported-widget.ts b/packages/perseus/src/widget-ai-utils/unsupported-widget.ts index c9c60cc1c7..5c25434a1e 100644 --- a/packages/perseus/src/widget-ai-utils/unsupported-widget.ts +++ b/packages/perseus/src/widget-ai-utils/unsupported-widget.ts @@ -1,4 +1,4 @@ -import type {UnsupportedWidget} from "../prompt-types"; +import type {UnsupportedWidget} from "./prompt-types"; export type UnsupportedWidgetPromptJSON = { type: UnsupportedWidget; diff --git a/packages/perseus/src/widget-ai-utils/video/prompt-utils.test.ts b/packages/perseus/src/widget-ai-utils/video/prompt-utils.test.ts index 9d9e80d901..66b6806568 100644 --- a/packages/perseus/src/widget-ai-utils/video/prompt-utils.test.ts +++ b/packages/perseus/src/widget-ai-utils/video/prompt-utils.test.ts @@ -1,6 +1,30 @@ -import {getPromptJSON} from "./prompt-utils"; +import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; -describe("Video getPromptJSON", () => { +import {getPromptJSON} from "./video-ai-utils"; + +import type {PerseusRenderer} from "../../perseus-types"; +import type {UnsupportedWidgetPromptJSON} from "../unsupported-widget"; + +export const question: PerseusRenderer = { + content: + "Watch the Biogeography: Where Life Lives video to find the answer.\n\n[[\u2603 video 1]]\n\n", + images: {}, + widgets: { + "video 1": { + graded: true, + version: {major: 0, minor: 0}, + static: false, + type: "video", + options: { + static: false, + location: "biogeography-where-life-lives", + }, + alignment: "block", + }, + }, +}; + +describe("Video AI utils", () => { it("it returns JSON with the expected format and fields", () => { const resultJSON = getPromptJSON(); @@ -10,4 +34,20 @@ describe("Video getPromptJSON", () => { message: "", }); }); + + it("should get prompt json which matches the state of the UI", async () => { + // Arrange + const {renderer} = renderQuestion(question, {isMobile: false}); + const widget = renderer.getWidgetInstance("video 1"); + + // Act + const json = widget?.getPromptJSON?.() as UnsupportedWidgetPromptJSON; + + // Assert + expect(json).toEqual({ + type: "video", + isSupported: false, + message: "", + }); + }); }); diff --git a/packages/perseus/src/widget-ai-utils/video/prompt-utils.ts b/packages/perseus/src/widget-ai-utils/video/video-ai-utils.ts similarity index 100% rename from packages/perseus/src/widget-ai-utils/video/prompt-utils.ts rename to packages/perseus/src/widget-ai-utils/video/video-ai-utils.ts diff --git a/packages/perseus/src/widget-ai-utils/video/video.test.ts b/packages/perseus/src/widget-ai-utils/video/video.test.ts deleted file mode 100644 index 7c7a351cd3..0000000000 --- a/packages/perseus/src/widget-ai-utils/video/video.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import {renderQuestion} from "../../widgets/__testutils__/renderQuestion"; - -import type {PerseusRenderer} from "../../perseus-types"; -import type {UnsupportedWidgetPromptJSON} from "../unsupported-widget"; - -export const question: PerseusRenderer = { - content: - "Watch the Biogeography: Where Life Lives video to find the answer.\n\n[[\u2603 video 1]]\n\n", - images: {}, - widgets: { - "video 1": { - graded: true, - version: {major: 0, minor: 0}, - static: false, - type: "video", - options: { - static: false, - location: "biogeography-where-life-lives", - }, - alignment: "block", - }, - }, -}; - -describe("cs-program widget", () => { - it("should get prompt json which matches the state of the UI", async () => { - // Arrange - const {renderer} = renderQuestion(question, {isMobile: false}); - const widget = renderer.getWidgetInstance("video 1"); - - // Act - const json = widget?.getPromptJSON?.() as UnsupportedWidgetPromptJSON; - - // Assert - expect(json).toEqual({ - type: "video", - isSupported: false, - message: "", - }); - }); -}); diff --git a/packages/perseus/src/widgets/categorizer/categorizer.tsx b/packages/perseus/src/widgets/categorizer/categorizer.tsx index c0ce91575f..93cc0daa8b 100644 --- a/packages/perseus/src/widgets/categorizer/categorizer.tsx +++ b/packages/perseus/src/widgets/categorizer/categorizer.tsx @@ -14,7 +14,7 @@ import Renderer from "../../renderer"; import mediaQueries from "../../styles/media-queries"; import sharedStyles from "../../styles/shared"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/categorizer/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/categorizer/categorizer-ai-utils"; import scoreCategorizer from "./score-categorizer"; @@ -24,7 +24,7 @@ import type { PerseusCategorizerRubric, PerseusCategorizerUserInput, } from "../../validation.types"; -import type {CategorizerPromptJSON} from "../../widget-ai-utils/categorizer/prompt-utils"; +import type {CategorizerPromptJSON} from "../../widget-ai-utils/categorizer/categorizer-ai-utils"; type Props = WidgetProps & { values: ReadonlyArray; diff --git a/packages/perseus/src/widgets/cs-program/cs-program.tsx b/packages/perseus/src/widgets/cs-program/cs-program.tsx index 68c10df5ea..fb1813a23d 100644 --- a/packages/perseus/src/widgets/cs-program/cs-program.tsx +++ b/packages/perseus/src/widgets/cs-program/cs-program.tsx @@ -13,7 +13,7 @@ import {articleMaxWidthInPx} from "../../styles/constants"; import Util from "../../util"; import {isFileProtocol} from "../../util/mobile-native-utils"; import {toAbsoluteUrl} from "../../util/url-utils"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/cs-program/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/cs-program/cs-program-ai-utils"; import scoreCSProgram from "./score-cs-program"; diff --git a/packages/perseus/src/widgets/definition/definition.tsx b/packages/perseus/src/widgets/definition/definition.tsx index 46c94ff947..b003d1a32a 100644 --- a/packages/perseus/src/widgets/definition/definition.tsx +++ b/packages/perseus/src/widgets/definition/definition.tsx @@ -6,7 +6,7 @@ import * as React from "react"; import {PerseusI18nContext} from "../../components/i18n-context"; import {DefinitionConsumer} from "../../definition-context"; import Renderer from "../../renderer"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/definition/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/definition/definition-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import type { @@ -14,7 +14,7 @@ import type { PerseusDefinitionWidgetOptions, } from "../../perseus-types"; import type {Widget, WidgetExports, WidgetProps} from "../../types"; -import type {DefinitionPromptJSON} from "../../widget-ai-utils/definition/prompt-utils"; +import type {DefinitionPromptJSON} from "../../widget-ai-utils/definition/definition-ai-utils"; type RenderProps = PerseusDefinitionWidgetOptions; diff --git a/packages/perseus/src/widgets/dropdown/dropdown.tsx b/packages/perseus/src/widgets/dropdown/dropdown.tsx index 473c96b6e6..4025041791 100644 --- a/packages/perseus/src/widgets/dropdown/dropdown.tsx +++ b/packages/perseus/src/widgets/dropdown/dropdown.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import ReactDOM from "react-dom"; import {ApiOptions} from "../../perseus-api"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/dropdown/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/dropdown/dropdown-ai-utils"; import scoreDropdown from "./score-dropdown"; @@ -13,7 +13,7 @@ import type { PerseusDropdownRubric, PerseusDropdownUserInput, } from "../../validation.types"; -import type {DropdownPromptJSON} from "../../widget-ai-utils/dropdown/prompt-utils"; +import type {DropdownPromptJSON} from "../../widget-ai-utils/dropdown/dropdown-ai-utils"; type Props = WidgetProps & { selected: number; diff --git a/packages/perseus/src/widgets/explanation/explanation.tsx b/packages/perseus/src/widgets/explanation/explanation.tsx index 114cf25237..94f2e80b6e 100644 --- a/packages/perseus/src/widgets/explanation/explanation.tsx +++ b/packages/perseus/src/widgets/explanation/explanation.tsx @@ -10,12 +10,12 @@ import _ from "underscore"; import {PerseusI18nContext} from "../../components/i18n-context"; import * as Changeable from "../../mixins/changeable"; import Renderer from "../../renderer"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/explanation/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/explanation/explanation-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import type {PerseusExplanationWidgetOptions} from "../../perseus-types"; import type {Widget, WidgetExports, WidgetProps} from "../../types"; -import type {ExplanationPromptJSON} from "../../widget-ai-utils/explanation/prompt-utils"; +import type {ExplanationPromptJSON} from "../../widget-ai-utils/explanation/explanation-ai-utils"; type RenderProps = PerseusExplanationWidgetOptions; // transform = _.identity diff --git a/packages/perseus/src/widgets/expression/expression.tsx b/packages/perseus/src/widgets/expression/expression.tsx index f0fb5ee8a7..c50b967721 100644 --- a/packages/perseus/src/widgets/expression/expression.tsx +++ b/packages/perseus/src/widgets/expression/expression.tsx @@ -16,7 +16,7 @@ import {useDependencies} from "../../dependencies"; import * as Changeable from "../../mixins/changeable"; import {ApiOptions, ClassNames as ApiClassNames} from "../../perseus-api"; import a11y from "../../util/a11y"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/expression/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/expression/expression-ai-utils"; import getDecimalSeparator from "./get-decimal-separator"; import scoreExpression from "./score-expression"; @@ -28,7 +28,7 @@ import type { PerseusExpressionRubric, PerseusExpressionUserInput, } from "../../validation.types"; -import type {ExpressionPromptJSON} from "../../widget-ai-utils/expression/prompt-utils"; +import type {ExpressionPromptJSON} from "../../widget-ai-utils/expression/expression-ai-utils"; import type {Keys as Key, KeypadConfiguration} from "@khanacademy/math-input"; import type {PropsFor} from "@khanacademy/wonder-blocks-core"; diff --git a/packages/perseus/src/widgets/graded-group-set/graded-group-set.tsx b/packages/perseus/src/widgets/graded-group-set/graded-group-set.tsx index 6aebbc29ad..25b5ef2c43 100644 --- a/packages/perseus/src/widgets/graded-group-set/graded-group-set.tsx +++ b/packages/perseus/src/widgets/graded-group-set/graded-group-set.tsx @@ -17,7 +17,7 @@ import { negativePhoneMargin, } from "../../styles/constants"; import a11y from "../../util/a11y"; -import {getPromptJSON} from "../../widget-ai-utils/graded-group-set/prompt-utils"; +import {getPromptJSON} from "../../widget-ai-utils/graded-group-set/graded-group-set-ai-utils"; import {GradedGroup} from "../graded-group/graded-group"; import type { @@ -26,7 +26,7 @@ import type { } from "../../perseus-types"; import type {FocusPath, Widget, WidgetExports, WidgetProps} from "../../types"; import type {PerseusGradedGroupSetRubric} from "../../validation.types"; -import type {GradedGroupSetPromptJSON} from "../../widget-ai-utils/graded-group-set/prompt-utils"; +import type {GradedGroupSetPromptJSON} from "../../widget-ai-utils/graded-group-set/graded-group-set-ai-utils"; type IndicatorsProps = { currentGroup: number; diff --git a/packages/perseus/src/widgets/graded-group/graded-group.tsx b/packages/perseus/src/widgets/graded-group/graded-group.tsx index 15777d7016..23eaeef1bc 100644 --- a/packages/perseus/src/widgets/graded-group/graded-group.tsx +++ b/packages/perseus/src/widgets/graded-group/graded-group.tsx @@ -21,7 +21,7 @@ import { tableBackgroundAccent, } from "../../styles/constants"; import a11y from "../../util/a11y"; -import {getPromptJSON} from "../../widget-ai-utils/graded-group/prompt-utils"; +import {getPromptJSON} from "../../widget-ai-utils/graded-group/graded-group-ai-utils"; import GradedGroupAnswerBar from "./graded-group-answer-bar"; @@ -36,7 +36,7 @@ import type { WidgetProps, } from "../../types"; import type {PerseusGradedGroupRubric} from "../../validation.types"; -import type {GradedGroupPromptJSON} from "../../widget-ai-utils/graded-group/prompt-utils"; +import type {GradedGroupPromptJSON} from "../../widget-ai-utils/graded-group/graded-group-ai-utils"; const GRADING_STATUSES = { ungraded: "ungraded" as const, diff --git a/packages/perseus/src/widgets/grapher/grapher.tsx b/packages/perseus/src/widgets/grapher/grapher.tsx index c71bb5aef2..b84f87c687 100644 --- a/packages/perseus/src/widgets/grapher/grapher.tsx +++ b/packages/perseus/src/widgets/grapher/grapher.tsx @@ -18,7 +18,7 @@ import KhanColors from "../../util/colors"; import {getInteractiveBoxFromSizeClass} from "../../util/sizing-utils"; /* Graphie and relevant components. */ /* Mixins. */ -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/grapher/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/grapher/grapher-ai-utils"; import scoreGrapher from "./score-grapher"; import { @@ -40,7 +40,7 @@ import type { PerseusGrapherRubric, PerseusGrapherUserInput, } from "../../validation.types"; -import type {GrapherPromptJSON} from "../../widget-ai-utils/grapher/prompt-utils"; +import type {GrapherPromptJSON} from "../../widget-ai-utils/grapher/grapher-ai-utils"; import type {PropsFor} from "@khanacademy/wonder-blocks-core"; // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'. diff --git a/packages/perseus/src/widgets/group/group.tsx b/packages/perseus/src/widgets/group/group.tsx index f8f512a42d..6c5a3a8fdf 100644 --- a/packages/perseus/src/widgets/group/group.tsx +++ b/packages/perseus/src/widgets/group/group.tsx @@ -6,7 +6,7 @@ import {PerseusI18nContext} from "../../components/i18n-context"; import * as Changeable from "../../mixins/changeable"; import {ApiOptions} from "../../perseus-api"; import Renderer from "../../renderer"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/group/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/group/group-ai-utils"; import type {PerseusGroupWidgetOptions} from "../../perseus-types"; import type { @@ -18,7 +18,7 @@ import type { WidgetProps, } from "../../types"; import type {PerseusGroupRubric, UserInputArray} from "../../validation.types"; -import type {GroupPromptJSON} from "../../widget-ai-utils/group/prompt-utils"; +import type {GroupPromptJSON} from "../../widget-ai-utils/group/group-ai-utils"; type RenderProps = PerseusGroupWidgetOptions; // exports has no 'transform' type Props = WidgetProps; diff --git a/packages/perseus/src/widgets/iframe/iframe.tsx b/packages/perseus/src/widgets/iframe/iframe.tsx index 3bd87caf7e..664d0c572e 100644 --- a/packages/perseus/src/widgets/iframe/iframe.tsx +++ b/packages/perseus/src/widgets/iframe/iframe.tsx @@ -14,7 +14,7 @@ import _ from "underscore"; import {getDependencies} from "../../dependencies"; import * as Changeable from "../../mixins/changeable"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/iframe/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/iframe/iframe-ai-utils"; import {scoreIframe} from "./score-iframe"; diff --git a/packages/perseus/src/widgets/image/image.tsx b/packages/perseus/src/widgets/image/image.tsx index 9f439bf628..29208c1065 100644 --- a/packages/perseus/src/widgets/image/image.tsx +++ b/packages/perseus/src/widgets/image/image.tsx @@ -8,12 +8,12 @@ import {PerseusI18nContext} from "../../components/i18n-context"; import SvgImage from "../../components/svg-image"; import * as Changeable from "../../mixins/changeable"; import Renderer from "../../renderer"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/image/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/image/image-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import type {Range, PerseusImageWidgetOptions} from "../../perseus-types"; import type {ChangeFn, WidgetExports, WidgetProps, Widget} from "../../types"; -import type {ImagePromptJSON} from "../../widget-ai-utils/image/prompt-utils"; +import type {ImagePromptJSON} from "../../widget-ai-utils/image/image-ai-utils"; const defaultBoxSize = 400; const defaultRange: Range = [0, 10]; diff --git a/packages/perseus/src/widgets/input-number/input-number.tsx b/packages/perseus/src/widgets/input-number/input-number.tsx index f2c7bd6216..0799bf2d75 100644 --- a/packages/perseus/src/widgets/input-number/input-number.tsx +++ b/packages/perseus/src/widgets/input-number/input-number.tsx @@ -8,7 +8,7 @@ import {PerseusI18nContext} from "../../components/i18n-context"; import InputWithExamples from "../../components/input-with-examples"; import SimpleKeypadInput from "../../components/simple-keypad-input"; import {ApiOptions} from "../../perseus-api"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/input-number/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/input-number/input-number-ai-utils"; import scoreInputNumber, {answerTypes} from "./score-input-number"; @@ -19,7 +19,7 @@ import type { PerseusInputNumberRubric, PerseusInputNumberUserInput, } from "../../validation.types"; -import type {InputNumberPromptJSON} from "../../widget-ai-utils/input-number/prompt-utils"; +import type {InputNumberPromptJSON} from "../../widget-ai-utils/input-number/input-number-ai-utils"; const formExamples = { integer: function (options, strings: PerseusStrings) { diff --git a/packages/perseus/src/widgets/interaction/interaction.tsx b/packages/perseus/src/widgets/interaction/interaction.tsx index ed16636efe..d27beee785 100644 --- a/packages/perseus/src/widgets/interaction/interaction.tsx +++ b/packages/perseus/src/widgets/interaction/interaction.tsx @@ -8,7 +8,7 @@ import _ from "underscore"; import Graphie from "../../components/graphie"; import * as Changeable from "../../mixins/changeable"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/interaction/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/interaction/interaction-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import type {Coord} from "../../interactive2/types"; diff --git a/packages/perseus/src/widgets/interactive-graph.tsx b/packages/perseus/src/widgets/interactive-graph.tsx index bf70474e1e..703bdb452b 100644 --- a/packages/perseus/src/widgets/interactive-graph.tsx +++ b/packages/perseus/src/widgets/interactive-graph.tsx @@ -28,7 +28,7 @@ import { import GraphUtils from "../util/graph-utils"; import {polar} from "../util/graphie"; import {getInteractiveBoxFromSizeClass} from "../util/sizing-utils"; -import {getPromptJSON} from "../widget-ai-utils/interactive-graph/prompt-utils"; +import {getPromptJSON} from "../widget-ai-utils/interactive-graph/interactive-graph-ai-utils"; import {StatefulMafsGraph} from "./interactive-graphs"; import {getClockwiseAngle} from "./interactive-graphs/math"; @@ -55,7 +55,7 @@ import type { PerseusInteractiveGraphRubric, PerseusInteractiveGraphUserInput, } from "../validation.types"; -import type {InteractiveGraphPromptJSON} from "../widget-ai-utils/interactive-graph/prompt-utils"; +import type {InteractiveGraphPromptJSON} from "../widget-ai-utils/interactive-graph/interactive-graph-ai-utils"; import type {UnsupportedWidgetPromptJSON} from "../widget-ai-utils/unsupported-widget"; const TRASH_ICON_URI = diff --git a/packages/perseus/src/widgets/label-image/label-image.tsx b/packages/perseus/src/widgets/label-image/label-image.tsx index bb5afc0d6b..d86ed6a4a0 100644 --- a/packages/perseus/src/widgets/label-image/label-image.tsx +++ b/packages/perseus/src/widgets/label-image/label-image.tsx @@ -20,7 +20,7 @@ import {useDependencies} from "../../dependencies"; import Renderer from "../../renderer"; import {bodyXsmallBold} from "../../styles/global-styles"; import mediaQueries from "../../styles/media-queries"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/label-image/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/label-image/label-image-ai-utils"; import AnswerChoices from "./answer-choices"; import {HideAnswersToggle} from "./hide-answers-toggle"; @@ -36,7 +36,7 @@ import type { PerseusLabelImageRubric, PerseusLabelImageUserInput, } from "../../validation.types"; -import type {LabelImagePromptJSON} from "../../widget-ai-utils/label-image/prompt-utils"; +import type {LabelImagePromptJSON} from "../../widget-ai-utils/label-image/label-image-ai-utils"; import type {PropsFor} from "@khanacademy/wonder-blocks-core"; import type {CSSProperties} from "aphrodite"; diff --git a/packages/perseus/src/widgets/matcher/matcher.tsx b/packages/perseus/src/widgets/matcher/matcher.tsx index 75b8f96dc6..c09c22e8bf 100644 --- a/packages/perseus/src/widgets/matcher/matcher.tsx +++ b/packages/perseus/src/widgets/matcher/matcher.tsx @@ -9,7 +9,7 @@ import Sortable from "../../components/sortable"; import {getDependencies} from "../../dependencies"; import Renderer from "../../renderer"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/matcher/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/matcher/matcher-ai-utils"; import scoreMatcher from "./score-matcher"; @@ -20,7 +20,7 @@ import type { PerseusMatcherRubric, PerseusMatcherUserInput, } from "../../validation.types"; -import type {MatcherPromptJSON} from "../../widget-ai-utils/matcher/prompt-utils"; +import type {MatcherPromptJSON} from "../../widget-ai-utils/matcher/matcher-ai-utils"; const {shuffle, seededRNG} = Util; const HACKY_CSS_CLASSNAME = "perseus-widget-matcher"; diff --git a/packages/perseus/src/widgets/matrix/matrix.tsx b/packages/perseus/src/widgets/matrix/matrix.tsx index adbd3c985a..c888078a91 100644 --- a/packages/perseus/src/widgets/matrix/matrix.tsx +++ b/packages/perseus/src/widgets/matrix/matrix.tsx @@ -13,7 +13,7 @@ import InteractiveUtil from "../../interactive2/interactive-util"; import {ApiOptions} from "../../perseus-api"; import Renderer from "../../renderer"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/matrix/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/matrix/matrix-ai-utils"; import scoreMatrix from "./score-matrix"; @@ -23,7 +23,7 @@ import type { PerseusMatrixRubric, PerseusMatrixUserInput, } from "../../validation.types"; -import type {MatrixPromptJSON} from "../../widget-ai-utils/matrix/prompt-utils"; +import type {MatrixPromptJSON} from "../../widget-ai-utils/matrix/matrix-ai-utils"; const {assert} = InteractiveUtil; const {stringArrayOfSize} = Util; diff --git a/packages/perseus/src/widgets/measurer/measurer.tsx b/packages/perseus/src/widgets/measurer/measurer.tsx index eb839c8062..4347ec677e 100644 --- a/packages/perseus/src/widgets/measurer/measurer.tsx +++ b/packages/perseus/src/widgets/measurer/measurer.tsx @@ -5,7 +5,7 @@ import _ from "underscore"; import SvgImage from "../../components/svg-image"; import GraphUtils from "../../util/graph-utils"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/measurer/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/measurer/measurer-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import type {Coord} from "../../interactive2/types"; diff --git a/packages/perseus/src/widgets/number-line/number-line.tsx b/packages/perseus/src/widgets/number-line/number-line.tsx index 8059a0bd9a..8d39882601 100644 --- a/packages/perseus/src/widgets/number-line/number-line.tsx +++ b/packages/perseus/src/widgets/number-line/number-line.tsx @@ -12,14 +12,14 @@ import * as Changeable from "../../mixins/changeable"; import {ApiOptions} from "../../perseus-api"; import KhanColors from "../../util/colors"; import KhanMath from "../../util/math"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/number-line/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/number-line/number-line-ai-utils"; import scoreNumberLine from "./score-number-line"; import type {ChangeableProps} from "../../mixins/changeable"; import type {APIOptions, WidgetExports, FocusPath, Widget} from "../../types"; import type {PerseusNumberLineUserInput} from "../../validation.types"; -import type {NumberLinePromptJSON} from "../../widget-ai-utils/number-line/prompt-utils"; +import type {NumberLinePromptJSON} from "../../widget-ai-utils/number-line/number-line-ai-utils"; // @ts-expect-error - TS2339 - Property 'MovablePoint' does not exist on type 'typeof Graphie'. const MovablePoint = Graphie.MovablePoint; diff --git a/packages/perseus/src/widgets/orderer/orderer.tsx b/packages/perseus/src/widgets/orderer/orderer.tsx index 5d62ee6972..1300b06583 100644 --- a/packages/perseus/src/widgets/orderer/orderer.tsx +++ b/packages/perseus/src/widgets/orderer/orderer.tsx @@ -12,7 +12,7 @@ import {Log} from "../../logging/log"; import {ClassNames as ApiClassNames} from "../../perseus-api"; import Renderer from "../../renderer"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/orderer/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/orderer/orderer-ai-utils"; import {scoreOrderer} from "./score-orderer"; @@ -27,7 +27,7 @@ import type { PerseusOrdererRubric, PerseusOrdererUserInput, } from "../../validation.types"; -import type {OrdererPromptJSON} from "../../widget-ai-utils/orderer/prompt-utils"; +import type {OrdererPromptJSON} from "../../widget-ai-utils/orderer/orderer-ai-utils"; import type {LinterContextProps} from "@khanacademy/perseus-linter"; type PlaceholderCardProps = { diff --git a/packages/perseus/src/widgets/passage-ref/passage-ref.tsx b/packages/perseus/src/widgets/passage-ref/passage-ref.tsx index ba451f8fa8..f6f1007621 100644 --- a/packages/perseus/src/widgets/passage-ref/passage-ref.tsx +++ b/packages/perseus/src/widgets/passage-ref/passage-ref.tsx @@ -4,13 +4,13 @@ import _ from "underscore"; import {PerseusI18nContext} from "../../components/i18n-context"; import * as Changeable from "../../mixins/changeable"; import PerseusMarkdown from "../../perseus-markdown"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/passage-ref/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/passage-ref/passage-ref-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import {isPassageWidget} from "../passage/utils"; import type {PerseusPassageRefWidgetOptions} from "../../perseus-types"; import type {ChangeFn, Widget, WidgetExports, WidgetProps} from "../../types"; -import type {PassageRefPromptJSON} from "../../widget-ai-utils/passage-ref/prompt-utils"; +import type {PassageRefPromptJSON} from "../../widget-ai-utils/passage-ref/passage-ref-ai-utils"; const EN_DASH = "\u2013"; diff --git a/packages/perseus/src/widgets/passage/passage.tsx b/packages/perseus/src/widgets/passage/passage.tsx index e83cbf09c3..a3ce686c76 100644 --- a/packages/perseus/src/widgets/passage/passage.tsx +++ b/packages/perseus/src/widgets/passage/passage.tsx @@ -9,7 +9,7 @@ import HighlightableContent from "../../components/highlighting/highlightable-co import {PerseusI18nContext} from "../../components/i18n-context"; import {getDependencies} from "../../dependencies"; import Renderer from "../../renderer"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/passage/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/passage/passage-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import PassageMarkdown from "./passage-markdown"; @@ -23,7 +23,7 @@ import type { PerseusWidget, } from "../../perseus-types"; import type {WidgetExports, WidgetProps, Widget} from "../../types"; -import type {PassagePromptJSON} from "../../widget-ai-utils/passage/prompt-utils"; +import type {PassagePromptJSON} from "../../widget-ai-utils/passage/passage-ai-utils"; import type {SingleASTNode} from "@khanacademy/simple-markdown"; // A fake paragraph to measure the line height of the passage, diff --git a/packages/perseus/src/widgets/phet-simulation/phet-simulation.tsx b/packages/perseus/src/widgets/phet-simulation/phet-simulation.tsx index f9e1756469..4c63ec1eec 100644 --- a/packages/perseus/src/widgets/phet-simulation/phet-simulation.tsx +++ b/packages/perseus/src/widgets/phet-simulation/phet-simulation.tsx @@ -14,7 +14,7 @@ import * as React from "react"; import {PerseusI18nContext} from "../../components/i18n-context"; import {getDependencies} from "../../dependencies"; import {phoneMargin} from "../../styles/constants"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/phet-simulation/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/phet-simulation/phet-simulation-ai-utils"; import type {PerseusPhetSimulationWidgetOptions} from "../../perseus-types"; import type {WidgetExports, WidgetProps, Widget} from "../../types"; diff --git a/packages/perseus/src/widgets/plotter/plotter.tsx b/packages/perseus/src/widgets/plotter/plotter.tsx index 5e557966c9..65dd21cb37 100644 --- a/packages/perseus/src/widgets/plotter/plotter.tsx +++ b/packages/perseus/src/widgets/plotter/plotter.tsx @@ -11,7 +11,7 @@ import {ClassNames as ApiClassNames} from "../../perseus-api"; import KhanColors from "../../util/colors"; import GraphUtils from "../../util/graph-utils"; import KhanMath from "../../util/math"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/plotter/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/plotter/plotter-ai-utils"; import scorePlotter from "./score-plotter"; diff --git a/packages/perseus/src/widgets/python-program/python-program.tsx b/packages/perseus/src/widgets/python-program/python-program.tsx index 61fc8cdbcf..eb688d227d 100644 --- a/packages/perseus/src/widgets/python-program/python-program.tsx +++ b/packages/perseus/src/widgets/python-program/python-program.tsx @@ -6,7 +6,7 @@ import {StyleSheet} from "aphrodite"; import * as React from "react"; import {toAbsoluteUrl} from "../../util/url-utils"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/python-program/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/python-program/python-ai-utils"; import type {FocusPath, Widget, WidgetExports} from "../../types"; import type {UnsupportedWidgetPromptJSON} from "../../widget-ai-utils/unsupported-widget"; diff --git a/packages/perseus/src/widgets/radio/radio-component.tsx b/packages/perseus/src/widgets/radio/radio-component.tsx index 5be5ac3110..7feefac6c7 100644 --- a/packages/perseus/src/widgets/radio/radio-component.tsx +++ b/packages/perseus/src/widgets/radio/radio-component.tsx @@ -4,7 +4,7 @@ import * as React from "react"; import {PerseusI18nContext} from "../../components/i18n-context"; import Renderer from "../../renderer"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/radio/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/radio/radio-ai-utils"; import PassageRef from "../passage-ref/passage-ref"; import BaseRadio from "./base-radio"; @@ -21,7 +21,7 @@ import type { PerseusRadioRubric, PerseusRadioUserInput, } from "../../validation.types"; -import type {RadioPromptJSON} from "../../widget-ai-utils/radio/prompt-utils"; +import type {RadioPromptJSON} from "../../widget-ai-utils/radio/radio-ai-utils"; // RenderProps is the return type for radio.jsx#transform export type RenderProps = { diff --git a/packages/perseus/src/widgets/sorter/sorter.tsx b/packages/perseus/src/widgets/sorter/sorter.tsx index 247a018811..aba485d2b8 100644 --- a/packages/perseus/src/widgets/sorter/sorter.tsx +++ b/packages/perseus/src/widgets/sorter/sorter.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import Sortable from "../../components/sortable"; import Util from "../../util"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/sorter/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/sorter/sorter-ai-utils"; import scoreSorter from "./score-sorter"; @@ -14,7 +14,7 @@ import type { PerseusSorterRubric, PerseusSorterUserInput, } from "../../validation.types"; -import type {SorterPromptJSON} from "../../widget-ai-utils/sorter/prompt-utils"; +import type {SorterPromptJSON} from "../../widget-ai-utils/sorter/sorter-ai-utils"; const {shuffle} = Util; diff --git a/packages/perseus/src/widgets/video/video.tsx b/packages/perseus/src/widgets/video/video.tsx index 559d103b80..20385bcf45 100644 --- a/packages/perseus/src/widgets/video/video.tsx +++ b/packages/perseus/src/widgets/video/video.tsx @@ -11,7 +11,7 @@ import {PerseusI18nContext} from "../../components/i18n-context"; import {getDependencies} from "../../dependencies"; import * as Changeable from "../../mixins/changeable"; import a11y from "../../util/a11y"; -import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/video/prompt-utils"; +import {getPromptJSON as _getPromptJSON} from "../../widget-ai-utils/video/video-ai-utils"; import scoreNoop from "../__shared__/score-noop"; import VideoTranscriptLink from "./video-transcript-link";