Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0c74b67
[benc/prompt-json-docs] Add doc comments for CategorizerPromptJSON
benchristel Apr 2, 2026
8378681
[benc/prompt-json-docs] Document UnsupportedWidgetPromptJSON
benchristel Apr 2, 2026
755254c
[benc/prompt-json-docs] Document DefinitionPromptJSON
benchristel Apr 2, 2026
8e7ffff
[benc/prompt-json-docs] Document DropdownPromptJSON
benchristel Apr 2, 2026
7767fc8
[benc/prompt-json-docs] Document explanation prompt JSON
benchristel Apr 3, 2026
5df1486
[benc/prompt-json-docs] Document expression prompt JSON
benchristel Apr 3, 2026
b7b215c
[benc/prompt-json-docs] Add doc comments for graded group prompt JSON
benchristel Apr 3, 2026
2f39fd2
[benc/prompt-json-docs] Document GradedGroupSet widget prompt JSON
benchristel Apr 3, 2026
d8da863
[benc/prompt-json-docs] Document grapher prompt JSON
benchristel Apr 3, 2026
24de8fd
[benc/prompt-json-docs] Document group widget prompt JSON
benchristel Apr 3, 2026
a6547c8
[benc/prompt-json-docs] Document image widget prompt JSON
benchristel Apr 3, 2026
712a8a0
[benc/prompt-json-docs] Document InputNumberPromptJSON
benchristel Apr 6, 2026
250d23e
[benc/prompt-json-docs] Fix type safety issues in InteractiveGraphPro…
benchristel Apr 6, 2026
006f86c
[benc/prompt-json-docs] Document InteractiveGraphPromptJSON
benchristel Apr 6, 2026
17f3aec
[benc/prompt-json-docs] Link to Jira ticket in TODOs
benchristel Apr 6, 2026
ace123f
[benc/prompt-json-docs] Add TODOs
benchristel Apr 6, 2026
54636cf
[benc/prompt-json-docs] Fix lint
benchristel Apr 6, 2026
c07788b
[benc/prompt-json-docs] Link to Jira ticket in TODO
benchristel Apr 6, 2026
b79590c
[benc/prompt-json-docs] Add TODO
benchristel Apr 6, 2026
83b9f41
[benc/prompt-json-docs] Document LabelImagePromptJSON
benchristel Apr 6, 2026
758eb5c
[benc/prompt-json-docs] Document MatcherPromptJSON
benchristel Apr 6, 2026
bde7295
[benc/prompt-json-docs] Document MatrixPromptJSON
benchristel Apr 6, 2026
6bfff0c
[benc/prompt-json-docs] Document NumberLinePromptJSON
benchristel Apr 6, 2026
85203e1
[benc/prompt-json-docs] Document NumericInputPromptJSON
benchristel Apr 6, 2026
e0693a2
[benc/prompt-json-docs] Document NOrdererPromptJSON
benchristel Apr 6, 2026
2ed2de1
[benc/prompt-json-docs] Document RadioPromptJSON
benchristel Apr 6, 2026
15b2af7
[benc/prompt-json-docs] Document SorterPromptJSON
benchristel Apr 6, 2026
782be05
[benc/prompt-json-docs] Export PromptJSON types
benchristel Apr 7, 2026
f35f71b
[benc/prompt-json-docs] Clarify doc comments
benchristel Apr 8, 2026
06da644
[benc/prompt-json-docs] docs(changeset): Document the `*PromptJSON` t…
benchristel Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/perseus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,7 @@ export type {
RendererPromptJSON,
WidgetPromptJSON,
} from "./widget-ai-utils/prompt-types";
export type {CategorizerPromptJSON} from "./widget-ai-utils/categorizer/categorizer-ai-utils";
export type {DefinitionPromptJSON} from "./widget-ai-utils/definition/definition-ai-utils";
export type {DropdownPromptJSON} from "./widget-ai-utils/dropdown/dropdown-ai-utils";
export type {UnsupportedWidgetPromptJSON} from "./widget-ai-utils/unsupported-widget";
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
import type categorizer from "../../widgets/categorizer/categorizer";
import type React from "react";

/**
* JSON describing a categorizer widget. Intended for consumption by AI tools.
* A categorizer displays a table where each row represents an item to be
* categorized, and each column represents a category. Each table cell contains
* a bubble that the learner can fill to indicate that that item belongs in
* that category. Only one bubble in each row can be filled. Learners fill
* bubbles by clicking on them.
*/
export type CategorizerPromptJSON = {
type: "categorizer";

/**
* The configuration of the widget, set by the content creator.
*/
options: {
/**
* The items for the learner to categorize.
*/
items: ReadonlyArray<string>;

/**
* Categories to which items can be assigned.
*/
categories: ReadonlyArray<string>;
};

/**
* The current state of the widget user interface. Usually represents a
* learner's attempt to answer a question.
Comment on lines +31 to +32
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think... but are there situations where this isn't the learner's attempt?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the editors? I guess not all widgets use userInput for editing.

I'm trying to cover all the bases because we don't control how Perseus is used, only its behavior.

*/
userInput: {
/**
* The category indices for each item. Elements in this array
* correspond to elements of `options.items`, and refer to indices of
* the `categories` array. For example, a value of `[2, null, 0]` means
* that the first item is in category 2, the second item has not been
* assigned to a category, and the third item is in category 0.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first item in the array (2) means the third category was selected, right (they're 0-based array indices). It becomes clear when I read to the end and see that the "third item is in category 0" but it is slightly confusing for me.

*/
itemToCategoryMapping: ReadonlyArray<number | null | undefined>;
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import type definition from "../../widgets/definition/definition";
import type React from "react";

/**
* JSON describing a definition widget. Intended for consumption by AI tools.
* The definition widget usually appears inline in a paragraph of text. It
* renders a term and displays its definition in a popover when clicked.
* Learners' interactions with this widget are not graded.
*/
export type DefinitionPromptJSON = {
type: "definition";
/** The definition of the term. */
definition: string;
/** The term being defined. */
togglePrompt: string;
};

Expand Down
14 changes: 14 additions & 0 deletions packages/perseus/src/widget-ai-utils/dropdown/dropdown-ai-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import type dropdown from "../../widgets/dropdown/dropdown";
import type React from "react";

/**
* JSON describing a dropdown widget. Intended for consumption by AI tools.
* The dropdown widget displays a menu of options.
*/
export type DropdownPromptJSON = {
type: "dropdown";

/**
* The configuration of the widget, set by the content creator.
*/
options: {
/** The choices present in the dropdown */
items: ReadonlyArray<string>;
};

/**
* The current state of the widget user interface. Usually represents a
* learner's attempt to answer a question.
*/
userInput: {
selectedIndex: number;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import type explanation from "../../widgets/explanation/explanation";
import type React from "react";

/**
* JSON describing an explanation widget. Intended for consumption by AI tools.
* The explanation widget displays a disclosure element that the user can
* click to see more details about a topic. Learners' interactions with this
* widget are not graded.
*/
export type ExplanationPromptJSON = {
type: "explanation";
/**
* The clickable text shown when the disclosure is closed.
*/
showPrompt: string;
/**
* The text shown when the disclosure is open.
*/
explanation: string;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import type {PerseusExpressionUserInput} from "@khanacademy/perseus-core";

/**
* JSON describing an expression widget. Intended for consumption by AI tools.
* The expression widget shows an input field that allows a user to input a
* math expression or equation.
*/
export type ExpressionPromptJSON = {
type: "expression";

/** The label shown on the input field */
label?: string;

/**
* The current state of the widget user interface. Usually represents a
* learner's attempt to answer a question.
*/
userInput: {
/** The expression or equation input by the user. Uses TeX syntax. */
value: string;
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ import type gradedGroupSet from "../../widgets/graded-group-set";
import type {GradedGroupPromptJSON} from "../graded-group/graded-group-ai-utils";
import type React from "react";

/**
* JSON describing a graded group set widget. Intended for consumption by AI tools.
* A graded group set displays several review questions (GradedGroups) in a
* carousel-like UI. The learner's score on a graded group set is not recorded.
* Graded group sets are provided for learners to check their own understanding
* of a concept.
*/
export type GradedGroupSetPromptJSON = {
type: "graded-group-set";

/** The configuration of the widget, set by the content creator. */
options: {
/** The number of questions (`GradedGroup`s) in this widget */
groupCount: number;

/** The currently-displayed question */
currentGroup: GradedGroupPromptJSON;
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
import type {RendererPromptJSON} from "../prompt-types";

export type GradedGroupPromptJSON = RendererPromptJSON & {
/**
* JSON describing a graded group widget. Intended for consumption by AI tools.
* The graded group widget displays a self-contained question with its own
* "check answer" button. The learner's score on this question is not recorded.
* Graded groups are provided for learners to check their own understanding of
* a concept.
*/
export interface GradedGroupPromptJSON extends RendererPromptJSON {
type: "graded-group";

/** Displayed above the graded group. */
title: string;
hint: RendererPromptJSON | string;
};

/**
* Explanation of the question, which the learner can show or hide by
* clicking a button. There is no penalty for looking at this hint.
*/
hint: RendererPromptJSON;
}

export const getPromptJSON = (
title: string,
Expand Down
43 changes: 43 additions & 0 deletions packages/perseus/src/widget-ai-utils/grapher/grapher-ai-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,60 @@ import type grapher from "../../widgets/grapher/grapher";
import type {GrapherAnswerTypes} from "@khanacademy/perseus-core";
import type React from "react";

/**
* JSON describing a grapher widget. Intended for consumption by AI tools.
* The grapher widget displays a Cartesian plane where the learner can plot an
* equation by clicking and dragging control points.
*/
export type GrapherPromptJSON = {
type: "grapher";

/** The configuration of the widget, set by the content creator. */
options: {
/**
* The types of equations the learner can plot. If there is more than
* one entry in this array, the widget displays a set of buttons for
* selecting the graph type.
*/
availableTypes: ReadonlyArray<string>;

/**
* The bounds of the graph, in the format
* `[[xMin, xMax], [yMin, yMax]]`.
*/
range: [x: [min: number, max: number], y: [min: number, max: number]];

/**
* Labels for the x and y axes of the graph, in the format `[x, y]`.
*/
labels: ReadonlyArray<string>;

/**
* The spacing between tick markings on the graph axes, measured in
* units on the Cartesian plane. Format: `[xTickStep, yTickStep]`.
*/
tickStep: [number, number];

/**
* The spacing between grid lines, measured in units on the Cartesian
* plane. Format: `[xGridStep, yGridStep]`.
*/
gridStep?: [number, number];

/**
* Control points snap to coordinates that are multiples of the snap
* step. Format: `[xSnapStep, ySnapStep]`.
*/
snapStep?: [number, number];

/** An image displayed behind the graph */
backgroundImageUrl?: string | null;
};

/**
* The current state of the widget user interface. Usually represents a
* learner's attempt to answer a question.
*/
userInput: GrapherAnswerTypes;
};

Expand Down
6 changes: 6 additions & 0 deletions packages/perseus/src/widget-ai-utils/group/group-ai-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type {RendererPromptJSON} from "../prompt-types";

/**
* JSON describing a group widget. Intended for consumption by AI tools.
* A group widget is simply an embedded Markdown document that can contain
* other widgets. To the user, the group widget looks like part of the
* surrounding document.
*/
export type GroupPromptJSON = RendererPromptJSON & {
type: "group";
};
Expand Down
20 changes: 20 additions & 0 deletions packages/perseus/src/widget-ai-utils/image/image-ai-utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
import type image from "../../widgets/image/image.class";
import type React from "react";

/**
* JSON describing an image widget. Intended for consumption by AI tools.
*/
export type ImagePromptJSON = {
type: "image";

/** Configuration set by the content creator. */
options: {
/**
* Text displayed to screenreaders and browsers without graphical
* capabilities
*/
altText: string;

/**
* Displayed above the image. May contain Markdown formatting, and TeX
* delimited by dollar signs, e.g. `$\dfrac{1}{2}$`.
*/
title: string;

/**
* Displayed below the image. May contain Markdown formatting, and TeX
* delimited by dollar signs, e.g. `$\dfrac{1}{2}$`.
*/
caption: string;

imageUrl: string | null | undefined;
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
import type inputNumber from "../../widgets/input-number/input-number";
import type React from "react";

/**
* JSON describing an InputNumber widget. Intended for consumption by AI tools.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there's benefits, but perhaps we describe this widget as being deprecated and that numeric-input is preferred?

* An InputNumber displays a text field where users can enter numbers in a
* variety of formats: decimals, integers, fractions, mixed numbers,
* percentages, and multiples of pi. The allowed formats are configurable by
* the content creator.
*/
export type InputNumberPromptJSON = {
type: "input-number";

/** The configuration of the widget, set by the content creator. */
options: {
/**
* Indicates how answers in unsimplified form are scored.
*
* - "optional" means the answer can be unsimplified.
* - "required" means an unsimplified answer is considered invalid,
* and the learner can try again without penalty.
* - "enforced" means unsimplified answers are counted as incorrect.
*/
// TODO(LEMS-4033): render an intelligible string for simplify; the
// current values ("optional", "required", "enforced") aren't
// self-explanatory.
simplify: string;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related, but can this be a union of the actual string values this can be? It is for the main widget options:

simplify: "required" | "optional" | "enforced";

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's technically a breaking change, since client code could be instantiating this type with other string values, but I don't think anyone is actually doing that.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I'm not going to change this type at the moment, because it will change again when we resolve this TODO comment:

// TODO(LEMS-4033): render an intelligible string for simplify; the
        //  current values ("optional", "required", "enforced") aren't
        //  self-explanatory.

Since we're passing this data to an LLM, we should use descriptive English strings, not enum values.

/** The expected numeric form, e.g. "rational", "decimal" */
answerType: string;
};

/**
* The current state of the widget user interface. Usually represents a
* learner's attempt to answer a question.
*/
userInput: {
/** The text input by the user */
value: string;
};
};
Expand Down
Loading
Loading