Skip to content

Commit

Permalink
Merge branch 'main' into chore-sentence-case
Browse files Browse the repository at this point in the history
  • Loading branch information
JBR90 committed Nov 13, 2024
2 parents c96fe52 + d0fe2d0 commit 147b2b7
Show file tree
Hide file tree
Showing 28 changed files with 1,094 additions and 175 deletions.
8 changes: 8 additions & 0 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## [1.14.2](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.14.1...v1.14.2) (2024-11-12)


### Bug Fixes

* design-changes-to-footer ([#324](https://github.com/oaknational/oak-ai-lesson-assistant/issues/324)) ([273cfdc](https://github.com/oaknational/oak-ai-lesson-assistant/commit/273cfdc668ca45def0b8a68dc08b7301974e1def))
* only categorise initial user input once ([#348](https://github.com/oaknational/oak-ai-lesson-assistant/issues/348)) ([dd5bf71](https://github.com/oaknational/oak-ai-lesson-assistant/commit/dd5bf71a21421ac6e0beb60b4bab560cb159d877))

## [1.14.1](https://github.com/oaknational/oak-ai-lesson-assistant/compare/v1.14.0...v1.14.1) (2024-11-07)


Expand Down
4 changes: 3 additions & 1 deletion apps/nextjs/src/app/api/chat/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const defaultConfig: Config = {
prisma: globalPrisma,
createAila: async (options) => {
const webActionsPlugin = createWebActionsPlugin(globalPrisma);
return new Aila({
const createdAila = new Aila({
...options,
plugins: [...(options.plugins || []), webActionsPlugin],
prisma: options.prisma ?? globalPrisma,
Expand All @@ -26,5 +26,7 @@ export const defaultConfig: Config = {
userId: undefined,
},
});
await createdAila.initialise();
return createdAila;
},
};
2 changes: 1 addition & 1 deletion apps/nextjs/src/app/api/chat/errorHandling.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe("handleChatException", () => {
type: "error",
value: "Rate limit exceeded",
message:
"**Unfortunately you’ve exceeded your fair usage limit for today.** Please come back in 1 hour. If you require a higher limit, please [make a request](https://forms.gle/tHsYMZJR367zydsG8).",
"**Unfortunately you’ve exceeded your fair usage limit for today.** Please come back in 1 hour. If you require a higher limit, please [make a request](https://share.hsforms.com/118hyngR-QSS0J7vZEVlRSgbvumd).",
});
});
});
Expand Down
4 changes: 3 additions & 1 deletion apps/nextjs/src/app/api/chat/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ describe("Chat API Route", () => {
chatCategoriser: mockChatCategoriser,
},
};
return new Aila(ailaConfig);
const ailaInstance = new Aila(ailaConfig);
await ailaInstance.initialise();
return ailaInstance;
}),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
prisma: {} as any,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useRef, useState } from "react";

import { getLastAssistantMessage } from "@oakai/aila/src/helpers/chat/getLastAssistantMessage";
import { OakBox } from "@oaknational/oak-components";
import type { AilaUserModificationAction } from "@prisma/client";

import { useLessonChat } from "@/components/ContextProviders/ChatProvider";
import { trpc } from "@/utils/trpc";

import ActionButton from "./action-button";
import type {
AdditionalMaterialOptions,
ModifyOptions,
} from "./action-button.types";
import { ActionDropDown } from "./action-drop-down";
import type { FeedbackOption } from "./drop-down-form-wrapper";

type ActionButtonWrapperProps = {
sectionTitle: string;
sectionPath: string;
sectionValue: Record<string, unknown> | string | Array<unknown>;
options: ModifyOptions | AdditionalMaterialOptions;
buttonText: string;
actionButtonLabel: string;
userSuggestionTitle: string;
tooltip: string;
generateMessage: (
option: FeedbackOption<AilaUserModificationAction>,
userFeedbackText: string,
) => string;
};

const ActionButtonWrapper = ({
sectionTitle,
sectionPath,
sectionValue,
options,
actionButtonLabel,
tooltip,
buttonText,
userSuggestionTitle,
generateMessage,
}: ActionButtonWrapperProps) => {
const dropdownRef = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [userFeedbackText, setUserFeedbackText] = useState("");
const [selectedRadio, setSelectedRadio] =
useState<FeedbackOption<AilaUserModificationAction> | null>(null);

const chat = useLessonChat();
const { append, id, messages } = chat;
const { mutateAsync } = trpc.chat.chatFeedback.modifySection.useMutation();

const lastAssistantMessage = getLastAssistantMessage(messages);

const recordUserModifySectionContent = async () => {
if (selectedRadio && lastAssistantMessage) {
const payload = {
chatId: id,
messageId: lastAssistantMessage.id,
sectionPath,
sectionValue,
action: selectedRadio.enumValue,
actionOtherText: userFeedbackText || null,
};
await mutateAsync(payload);
}
};

const handleSubmit = async () => {
if (!selectedRadio) return;
const message = generateMessage(selectedRadio, userFeedbackText);
await Promise.all([
append({ content: message, role: "user" }),
recordUserModifySectionContent(),
]);
setIsOpen(false);
};

return (
<OakBox $position="relative" ref={dropdownRef}>
<ActionButton onClick={() => setIsOpen(!isOpen)} tooltip={tooltip}>
{actionButtonLabel}
</ActionButton>

{isOpen && (
<ActionDropDown
sectionTitle={sectionTitle}
options={options}
selectedRadio={selectedRadio}
setSelectedRadio={setSelectedRadio}
isOpen={isOpen}
setIsOpen={setIsOpen}
setUserFeedbackText={setUserFeedbackText}
handleSubmit={handleSubmit}
buttonText={buttonText}
userSuggestionTitle={userSuggestionTitle}
dropdownRef={dropdownRef}
id={id}
/>
)}
</OakBox>
);
};

export default ActionButtonWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export const additionalMaterialsModifyOptions = [
{
label: "A homework task",
enumValue: "ADD_HOMEWORK_TASK",
chatMessage: "Add a homework task",
},
{
label: "A narrative for my explanation",
enumValue: "ADD_NARRATIVE",
chatMessage: "Add a narrative for my explanation",
},
{
label: "Additional practice questions",
enumValue: "ADD_PRACTICE_QUESTIONS",
chatMessage: "Add additional practice questions",
},
{
label: "Practical instructions (if relevant)",
enumValue: "ADD_PRACTICAL_INSTRUCTIONS",
chatMessage: "Add practical instructions",
},
{ label: "Other", enumValue: "OTHER" },
] as const;

export type AdditionalMaterialOptions = typeof additionalMaterialsModifyOptions;

export const modifyOptions = [
{
label: "Make it easier",
enumValue: "MAKE_IT_EASIER",
chatMessage: "easier",
},
{
label: "Make it harder",
enumValue: "MAKE_IT_HARDER",
chatMessage: "harder",
},
{
label: "Shorten content",
enumValue: "SHORTEN_CONTENT",
chatMessage: "shorter",
},
{
label: "Add more detail",
enumValue: "ADD_MORE_DETAIL",
chatMessage: "more detailed",
},
{ label: "Other", enumValue: "OTHER" },
] as const;

export type ModifyOptions = typeof modifyOptions;
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Dispatch, RefObject, SetStateAction } from "react";

import { aiLogger } from "@oakai/logger";
import { OakP, OakRadioGroup } from "@oaknational/oak-components";
import { $Enums, AilaUserModificationAction } from "@prisma/client";
import { TextArea } from "@radix-ui/themes";

import {
AdditionalMaterialOptions,
ModifyOptions,
} from "./action-button.types";
import { DropDownFormWrapper, FeedbackOption } from "./drop-down-form-wrapper";
import { SmallRadioButton } from "./small-radio-button";

const log = aiLogger("chat");

type DropDownProps = {
sectionTitle: string;
options: ModifyOptions | AdditionalMaterialOptions;
selectedRadio: FeedbackOption<AilaUserModificationAction> | null;
setSelectedRadio: Dispatch<
SetStateAction<FeedbackOption<$Enums.AilaUserModificationAction> | null>
>;
isOpen: boolean;
setIsOpen: (open: boolean) => void;
setUserFeedbackText: (text: string) => void;
handleSubmit: (
option: FeedbackOption<AilaUserModificationAction>,
) => Promise<void>;
buttonText: string;
userSuggestionTitle: string;
dropdownRef: RefObject<HTMLDivElement>;
id: string;
};

export const ActionDropDown = ({
sectionTitle,
options,
selectedRadio,
setSelectedRadio,
isOpen,
setIsOpen,
setUserFeedbackText,
handleSubmit,
buttonText,
userSuggestionTitle,
dropdownRef,
id,
}: DropDownProps) => {
return (
<DropDownFormWrapper
onClickActions={handleSubmit}
setIsOpen={setIsOpen}
selectedRadio={selectedRadio}
title={sectionTitle}
buttonText={buttonText}
isOpen={isOpen}
dropdownRef={dropdownRef}
>
<OakRadioGroup
name={`drop-down-${options[0].enumValue}`}
$flexDirection="column"
$gap="space-between-s"
$background="white"
>
{options.map((option) => {
return (
<SmallRadioButton
id={`${id}-modify-options-${option.enumValue}`}
key={`${id}-modify-options-${option.enumValue}`}
value={option.enumValue}
label={handleLabelText({
text: option.label,
section: sectionTitle,
})}
onClick={() => {
setSelectedRadio(option);
}}
/>
);
})}

{selectedRadio?.label === "Other" && (
<>
<OakP $font="body-3">{userSuggestionTitle}</OakP>
<TextArea
data-testid={"modify-other-text-area"}
onChange={(e) => setUserFeedbackText(e.target.value)}
/>
</>
)}
</OakRadioGroup>
</DropDownFormWrapper>
);
};

function handleLabelText({
text,
section,
}: {
text: string;
section: string;
}): string {
log.info("section", section);
if (
section === "Misconceptions" ||
section === "Key learning points" ||
section === "Learning cycles" ||
"additional materials"
) {
if (text.split(" ").includes("it")) {
return text.replace("it", "them");
}
}
return text;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { AilaUserModificationAction } from "@prisma/client";

import ActionButtonWrapper from "./action-button-wrapper";
import { additionalMaterialsModifyOptions } from "./action-button.types";
import type { FeedbackOption } from "./drop-down-form-wrapper";

export type AdditionalMaterialsProps = {
sectionTitle: string;
sectionPath: string;
sectionValue: Record<string, unknown> | string | Array<unknown>;
};

const AddAdditionalMaterialsButton = ({
sectionTitle,
sectionPath,
sectionValue,
}: AdditionalMaterialsProps) => {
const generateMessage = (
option: FeedbackOption<AilaUserModificationAction>,
userFeedbackText: string,
) =>
option.label === "Other"
? `For the ${sectionTitle}, ${userFeedbackText}`
: `${option.chatMessage} to the additional materials section`;

return (
<ActionButtonWrapper
sectionTitle={`Ask Aila to add:`}
sectionPath={sectionPath}
sectionValue={sectionValue}
options={additionalMaterialsModifyOptions}
actionButtonLabel="Add additional materials"
tooltip="Aila can help add additional materials"
userSuggestionTitle="What additional materials would you like to add?"
generateMessage={generateMessage}
buttonText={"Add materials"}
/>
);
};

export default AddAdditionalMaterialsButton;
Loading

0 comments on commit 147b2b7

Please sign in to comment.