Skip to content

Commit

Permalink
feat: generate select option quizzes by Ikigai AI (#105)
Browse files Browse the repository at this point in the history
* Add generate select option quizzes

* Generate select options quizzes in frontend
  • Loading branch information
cptrodgers authored Aug 11, 2024
1 parent 1d13ba5 commit c10b1dc
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import Modal from "components/base/Modal";
import { QuizComponentProps } from "../type";
import { IFillInBlankExpectedAnswer } from "store/QuizStore";

export type SelectOptionSettingProps = QuizComponentProps & {
export type FillInBlankSettingProps = QuizComponentProps & {
showSetting: boolean;
setShowSetting: (setting: boolean) => void;
};

const FillInBlankSetting = (props: SelectOptionSettingProps) => {
const FillInBlankSetting = (props: FillInBlankSettingProps) => {
const { upsertQuiz, answerData } = useFillInBlankQuiz(
props.quizId,
props.pageContentId,
Expand Down
111 changes: 110 additions & 1 deletion apps/ikigai/components/QuizGenerator/GeneratedQuizItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import {
CheckboxGroup,
Kbd,
RadioGroup,
Select,
Separator,
Text,
TextField,
} from "@radix-ui/themes";
import styled from "styled-components";
import { Trans } from "@lingui/macro";
import { t, Trans } from "@lingui/macro";
import React from "react";

import {
AIFillInBlankQuiz,
AIGeneratedQuiz,
AIMultipleChoiceQuiz,
AISelectOptionQuiz,
AISingeChoiceQuiz,
} from "store/QuizStore";
import { QuizType } from "graphql/types";
Expand All @@ -38,6 +40,10 @@ export const GeneratedQuizReview = (props: GeneratedChoiceReviewProps) => {
return <GeneratedFillInBlankReview {...props} />;
}

if (props.quiz.quizType === QuizType.SELECT_OPTION) {
return <GeneratedSelectOptionReview {...props} />;
}

return (
<QuizWrapper onClick={props.onSelect} $selected={props.selected}>
<Trans>Not supported quiz type</Trans>
Expand Down Expand Up @@ -173,6 +179,109 @@ export const parseAIFillInBlankQuiz = (
});
};

export const GeneratedSelectOptionReview = ({
quiz,
selected,
onSelect,
}: GeneratedChoiceReviewProps) => {
const quizData = quiz as AISelectOptionQuiz;
const components = parseAISelectOptionQuiz(quizData);
return (
<QuizWrapper $selected={selected} onClick={onSelect}>
{components.map((component, index) =>
component.componentType === "text" ? (
<Text key={index}>{component.data.content}</Text>
) : (
<GeneratedSelectOptionQuizReview
key={index}
item={component.data as AISelectOptionQuizContentComponent}
/>
),
)}
</QuizWrapper>
);
};

export const GeneratedSelectOptionQuizReview = ({
item,
}: {
item: AISelectOptionQuizContentComponent;
}) => {
return (
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 2,
}}
>
<Kbd>Q.{item.position}</Kbd>
<Select.Root size="1" value={item.correctAnswer}>
<Select.Trigger
variant="soft"
placeholder={t`Select answer`}
radius="none"
onClick={(e) => e.stopPropagation()}
/>
<Select.Content position="popper">
<Select.Group>
{item.answers.map((answer) => (
<Select.Item key={answer} value={answer}>
{answer}
</Select.Item>
))}
</Select.Group>
</Select.Content>
</Select.Root>
</div>
);
};

export type AISelectOptionQuizContentComponent = {
content: string;
position: number;
answers: string[];
correctAnswer: string;
};

export type AISelectOptionQuizComponent = {
componentType: "text" | "quiz";
data: { content: string } | AIFillInBlankQuizContentComponent;
};

export const parseAISelectOptionQuiz = (
quiz: AISelectOptionQuiz,
): AISelectOptionQuizComponent[] => {
const extractQuizRegex = /(\[Q\.\d*\])/gm;

const components = quiz.content.split(extractQuizRegex);
return components.map((component) => {
const index = component.search(extractQuizRegex);
if (index > -1) {
let quizNumberStr = component.replace("[Q.", "");
quizNumberStr = quizNumberStr.replace("]", "");
const quizNumber = parseInt(quizNumberStr) || 0;
return {
componentType: "quiz",
data: {
content: quiz.quizzes.find((quiz) => quiz.position === quizNumber)
?.correctAnswer,
position: quizNumber,
correctAnswer: quiz.quizzes.find(
(quiz) => quiz.position === quizNumber,
)?.correctAnswer,
answers: quiz.quizzes.find((quiz) => quiz.position === quizNumber)
?.answers,
},
};
}
return {
componentType: "text",
data: { content: component },
};
});
};

const QuizWrapper = styled.div<{ $selected?: boolean }>`
border: ${(props) =>
props.$selected ? "1px solid var(--indigo-9)" : "1px solid var(--gray-5)"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ import useQuizStore, {
AIFillInBlankQuiz,
AIGeneratedQuiz,
AIMultipleChoiceQuiz,
AISelectOptionQuiz,
AISingeChoiceQuiz,
} from "store/QuizStore";
import {
AISelectOptionQuizContentComponent,
GeneratedQuizReview,
parseAIFillInBlankQuiz,
parseAISelectOptionQuiz,
} from "./GeneratedQuizItem";
import { Editor, JSONContent } from "@tiptap/react";
import { SELECT_OPTION_BLOCK_NAME } from "../Editor/Extensions/QuizBlock/SelectOptionBlock";
import { FILL_IN_BLANK_BLOCK_NAME } from "../Editor/Extensions/QuizBlock/FillInBlankBlock";

export type ReviewGeneratedQuizzesProps = {
quizzes: AIGeneratedQuiz[];
Expand Down Expand Up @@ -83,6 +88,11 @@ export const SelectedGeneratedQuizzes = ({
const quizData = quiz as AIFillInBlankQuiz;
await insertFillInBlank(editor, pageContentId, quizData);
}

if (quiz.quizType === QuizType.SELECT_OPTION) {
const quizData = quiz as AISelectOptionQuiz;
await insertSelectOption(editor, pageContentId, quizData);
}
}

onClose();
Expand All @@ -98,6 +108,7 @@ export const SelectedGeneratedQuizzes = ({
singleChoiceData: [quiz],
multipleChoiceData: [],
fillInBlankData: [],
selectOptionsData: [],
};
const { data } = await quizCovertAI({
variables: {
Expand Down Expand Up @@ -129,6 +140,7 @@ export const SelectedGeneratedQuizzes = ({
singleChoiceData: [],
multipleChoiceData: [quiz],
fillInBlankData: [],
selectOptionsData: [],
};
const { data } = await quizCovertAI({
variables: {
Expand Down Expand Up @@ -178,6 +190,68 @@ export const SelectedGeneratedQuizzes = ({
correctAnswer: component.data.content,
},
],
selectOptionsData: [],
};
const { data } = await quizCovertAI({
variables: {
pageContentId,
data: dataInput,
},
});

if (data) {
data.quizConvertAiQuiz.forEach((quiz) => {
addOrUpdateQuiz(quiz);
});
content.content.push({
type: FILL_IN_BLANK_BLOCK_NAME,
attrs: {
quizId: data.quizConvertAiQuiz[0].id,
},
});
}
}
}

editor
.chain()
.focus("end")
.enter()
.focus("end")
.insertContent(content)
.run();
};

const insertSelectOption = async (
editor: Editor,
pageContentId: string,
quiz: AISelectOptionQuiz,
) => {
const components = parseAISelectOptionQuiz(quiz);

const content: JSONContent = {
type: "paragraph",
content: [],
};

for (const component of components) {
if (component.componentType === "text") {
content.content.push({
type: "text",
text: component.data.content,
});
} else {
const castedData = component.data as AISelectOptionQuizContentComponent;
const dataInput: AIGenerateQuizInput = {
singleChoiceData: [],
multipleChoiceData: [],
fillInBlankData: [],
selectOptionsData: [
{
answers: castedData.answers,
correctAnswer: castedData.correctAnswer,
},
],
};
const { data } = await quizCovertAI({
variables: {
Expand All @@ -191,7 +265,7 @@ export const SelectedGeneratedQuizzes = ({
addOrUpdateQuiz(quiz);
});
content.content.push({
type: "fillInBlank",
type: SELECT_OPTION_BLOCK_NAME,
attrs: {
quizId: data.quizConvertAiQuiz[0].id,
},
Expand Down
8 changes: 8 additions & 0 deletions apps/ikigai/components/QuizGenerator/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ const QuizGeneratorContent = ({
...data.quizGenerateByAi.fillInBlankData,
quizType: QuizType.FILL_IN_BLANK,
});
if (data.quizGenerateByAi.selectOptionsData)
quizzes.push({
...data.quizGenerateByAi.selectOptionsData,
quizType: QuizType.SELECT_OPTION,
});
if (data.quizGenerateByAi.singleChoiceData) {
data.quizGenerateByAi.singleChoiceData.quizzes.forEach((quiz) => {
quizzes.push({ ...quiz, quizType: QuizType.SINGLE_CHOICE });
Expand Down Expand Up @@ -209,6 +214,9 @@ const QuizGeneratorContent = ({
<Select.Item value={QuizType.FILL_IN_BLANK}>
<Trans>Fill In Blank</Trans>
</Select.Item>
<Select.Item value={QuizType.SELECT_OPTION}>
<Trans>Select Option</Trans>
</Select.Item>
</Select.Group>
</Select.Content>
</Select.Root>
Expand Down
Loading

0 comments on commit c10b1dc

Please sign in to comment.