@thanh01.pmt/interactive-quiz-kit 1.0.65 → 1.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HEADLESS.md +1 -1
- package/README.md +2 -2
- package/dist/{ai-ecosystem-DqFRlFU3.d.cts → ai-ecosystem-DyQYZbyX.d.cts} +3 -3
- package/dist/{ai-ecosystem-DqVlSO3r.d.ts → ai-ecosystem-Qa_SdE2T.d.ts} +3 -3
- package/dist/ai.cjs +60 -96
- package/dist/ai.d.cts +127 -127
- package/dist/ai.d.ts +127 -127
- package/dist/ai.mjs +60 -96
- package/dist/authoring.cjs +1576 -1250
- package/dist/authoring.d.cts +4 -4
- package/dist/authoring.d.ts +4 -4
- package/dist/authoring.mjs +1315 -990
- package/dist/index.cjs +22 -37
- package/dist/index.d.cts +24 -27
- package/dist/index.d.ts +24 -27
- package/dist/index.mjs +22 -37
- package/dist/player.cjs +9 -9
- package/dist/player.d.cts +1 -1
- package/dist/player.d.ts +1 -1
- package/dist/player.mjs +9 -9
- package/dist/{quiz-config-o4j2dfsu.d.cts → quiz-config-CwaP-pBs.d.cts} +1 -1
- package/dist/{quiz-config-o4j2dfsu.d.ts → quiz-config-CwaP-pBs.d.ts} +1 -1
- package/dist/react-ui.cjs +1307 -997
- package/dist/react-ui.d.cts +6 -6
- package/dist/react-ui.d.ts +6 -6
- package/dist/react-ui.mjs +1307 -998
- package/dist/{toaster-CKS4zoRY.d.cts → toaster-BVaUJA6E.d.ts} +65 -45
- package/dist/{toaster-DLJ_2W9u.d.ts → toaster-BWaJj0l-.d.cts} +65 -45
- package/package.json +1 -1
package/dist/react-ui.cjs
CHANGED
|
@@ -9085,7 +9085,7 @@ var translation_default = {
|
|
|
9085
9085
|
subject: "Subject",
|
|
9086
9086
|
category: "Category",
|
|
9087
9087
|
topic: "Topic",
|
|
9088
|
-
|
|
9088
|
+
code: "LO ID"
|
|
9089
9089
|
},
|
|
9090
9090
|
confirmModal: {
|
|
9091
9091
|
title: "Confirm Data Import",
|
|
@@ -9500,7 +9500,7 @@ var translation_default2 = {
|
|
|
9500
9500
|
subject: "M\xF4n h\u1ECDc",
|
|
9501
9501
|
category: "Danh m\u1EE5c",
|
|
9502
9502
|
topic: "Ch\u1EE7 \u0111\u1EC1",
|
|
9503
|
-
|
|
9503
|
+
code: "M\xE3 MTH"
|
|
9504
9504
|
},
|
|
9505
9505
|
confirmModal: {
|
|
9506
9506
|
title: "X\xE1c nh\u1EADn Nh\u1EADp D\u1EEF li\u1EC7u",
|
|
@@ -37572,7 +37572,7 @@ var cva = (base3, config3) => (props) => {
|
|
|
37572
37572
|
|
|
37573
37573
|
// src/react-ui/components/elements/label.tsx
|
|
37574
37574
|
var labelVariants = cva(
|
|
37575
|
-
"text-sm font-
|
|
37575
|
+
"text-sm font-Medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
37576
37576
|
);
|
|
37577
37577
|
var Label2 = React169__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React169__namespace.createElement(
|
|
37578
37578
|
Root3,
|
|
@@ -62810,7 +62810,7 @@ var MarkdownRenderer = ({
|
|
|
62810
62810
|
},
|
|
62811
62811
|
h1: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("h1", { ...props, className: "text-3xl font-bold mb-6 mt-8 first:mt-0" }),
|
|
62812
62812
|
h2: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("h2", { ...props, className: "text-2xl font-semibold mb-4 mt-6" }),
|
|
62813
|
-
h3: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("h3", { ...props, className: "text-xl font-
|
|
62813
|
+
h3: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("h3", { ...props, className: "text-xl font-Medium mb-3 mt-5" }),
|
|
62814
62814
|
ul: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("ul", { ...props, className: "my-4 space-y-2 list-disc list-inside" }),
|
|
62815
62815
|
ol: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("ol", { ...props, className: "my-4 space-y-2 list-decimal list-inside" }),
|
|
62816
62816
|
p: ({ node: node2, ...props }) => /* @__PURE__ */ React169__namespace.default.createElement("p", { ...props, className: "mb-4 leading-7" }),
|
|
@@ -63284,7 +63284,7 @@ var Input = React169__namespace.forwardRef(
|
|
|
63284
63284
|
{
|
|
63285
63285
|
type,
|
|
63286
63286
|
className: cn(
|
|
63287
|
-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-
|
|
63287
|
+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-Medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
63288
63288
|
className
|
|
63289
63289
|
),
|
|
63290
63290
|
ref,
|
|
@@ -63459,7 +63459,7 @@ init_react_shim();
|
|
|
63459
63459
|
// src/react-ui/components/elements/button.tsx
|
|
63460
63460
|
init_react_shim();
|
|
63461
63461
|
var buttonVariants = cva(
|
|
63462
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-
|
|
63462
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-Medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
63463
63463
|
{
|
|
63464
63464
|
variants: {
|
|
63465
63465
|
variant: {
|
|
@@ -68412,7 +68412,7 @@ var MatchingQuestionUI = ({
|
|
|
68412
68412
|
if (showCorrectAnswer && selectedOptionId) {
|
|
68413
68413
|
borderColor = isSelectionCorrect ? "border-green-500" : "border-destructive";
|
|
68414
68414
|
}
|
|
68415
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-
|
|
68415
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-Medium text-base block mb-2 whitespace-normal" }, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
68416
68416
|
Select2,
|
|
68417
68417
|
{
|
|
68418
68418
|
value: selectedOptionId,
|
|
@@ -68467,7 +68467,7 @@ var DragAndDropQuestionUI = ({
|
|
|
68467
68467
|
} else {
|
|
68468
68468
|
itemStyle += " border-muted";
|
|
68469
68469
|
}
|
|
68470
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `select-draggable-${item.id}`, className: "font-
|
|
68470
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `select-draggable-${item.id}`, className: "font-Medium text-base mb-2 sm:mb-0 sm:mr-4 flex-1" }, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
68471
68471
|
Select2,
|
|
68472
68472
|
{
|
|
68473
68473
|
value: selectedDropZoneId,
|
|
@@ -95483,7 +95483,7 @@ var TabsTrigger2 = React169__namespace.forwardRef(({ className, ...props }, ref)
|
|
|
95483
95483
|
{
|
|
95484
95484
|
ref,
|
|
95485
95485
|
className: cn(
|
|
95486
|
-
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-
|
|
95486
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-Medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
95487
95487
|
className
|
|
95488
95488
|
),
|
|
95489
95489
|
...props
|
|
@@ -96183,7 +96183,7 @@ var AccordionTrigger2 = React169__namespace.forwardRef(({ className, children, .
|
|
|
96183
96183
|
{
|
|
96184
96184
|
ref,
|
|
96185
96185
|
className: cn(
|
|
96186
|
-
"flex flex-1 items-center justify-between py-4 font-
|
|
96186
|
+
"flex flex-1 items-center justify-between py-4 font-Medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
|
96187
96187
|
className
|
|
96188
96188
|
),
|
|
96189
96189
|
...props
|
|
@@ -96970,7 +96970,7 @@ var QuizResult = ({
|
|
|
96970
96970
|
}
|
|
96971
96971
|
return String(answer);
|
|
96972
96972
|
};
|
|
96973
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t4("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, { className: "text-center text-lg" }, t4("practiceFlow.results.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t4("practiceFlow.results.overallScore"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.points"))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.percentage"))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleCheckBig, { className: "h-10 w-10" }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.passed"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleX, { className: "h-10 w-10" }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.failed"))))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__namespace.default.createElement("span", null, t4("practiceFlow.results.timeSpent")), /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, result.totalTimeSpentSeconds?.toFixed(0) ?? "N/A", " ", t4("practiceFlow.results.timeUnit"))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__namespace.default.createElement("span", null, t4("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, result.averageTimePerQuestionSeconds?.toFixed(1) ?? "N/A", " ", t4("practiceFlow.results.timeUnit")))), result.scormStatus && result.scormStatus !== "idle" && result.scormStatus !== "no_api" && /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React169__namespace.default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionItem2, { value: "question-breakdown" }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionTrigger2, { className: "text-lg font-semibold" }, t4("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React169__namespace.default.createElement(AccordionContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React169__namespace.default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index3) => /* @__PURE__ */ React169__namespace.default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold" }, t4("common.questions", { count: index3 + 1 })), qResult.isCorrect ? /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-green-600 font-
|
|
96973
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t4("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, { className: "text-center text-lg" }, t4("practiceFlow.results.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t4("practiceFlow.results.overallScore"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.points"))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.percentage"))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleCheckBig, { className: "h-10 w-10" }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.passed"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleX, { className: "h-10 w-10" }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.failed"))))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__namespace.default.createElement("span", null, t4("practiceFlow.results.timeSpent")), /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, result.totalTimeSpentSeconds?.toFixed(0) ?? "N/A", " ", t4("practiceFlow.results.timeUnit"))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__namespace.default.createElement("span", null, t4("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, result.averageTimePerQuestionSeconds?.toFixed(1) ?? "N/A", " ", t4("practiceFlow.results.timeUnit")))), result.scormStatus && result.scormStatus !== "idle" && result.scormStatus !== "no_api" && /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React169__namespace.default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionItem2, { value: "question-breakdown" }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionTrigger2, { className: "text-lg font-semibold" }, t4("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React169__namespace.default.createElement(AccordionContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React169__namespace.default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index3) => /* @__PURE__ */ React169__namespace.default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold" }, t4("common.questions", { count: index3 + 1 })), qResult.isCorrect ? /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-green-600 font-Medium flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleCheckBig, { className: "mr-1 h-4 w-4" }), " ", t4("practiceFlow.results.passed")) : /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-destructive font-Medium flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleX, { className: "mr-1 h-4 w-4" }), " ", t4("practiceFlow.results.failed"))), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.yourAnswer")), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.correctAnswer")), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.pointsEarned")), " ", qResult.pointsEarned), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.timeSpent")), " ", qResult.timeSpentSeconds?.toFixed(0) ?? "N/A", t4("practiceFlow.results.timeUnit")))))))))), /* @__PURE__ */ React169__namespace.default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, onExitQuiz && /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), t4("common.exit")), showReviewButton && onGenerateReview && /* @__PURE__ */ React169__namespace.default.createElement(
|
|
96974
96974
|
Button,
|
|
96975
96975
|
{
|
|
96976
96976
|
onClick: onGenerateReview,
|
|
@@ -97285,7 +97285,7 @@ var QuizDataManagement = ({ onQuizLoad, currentQuiz }) => {
|
|
|
97285
97285
|
});
|
|
97286
97286
|
}
|
|
97287
97287
|
};
|
|
97288
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full shadow-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, null, "Quiz Data Management"), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, "Import a quiz from a JSON file or export the current quiz configuration.")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col gap-2 mb-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "quiz-file-input", className: "text-sm font-
|
|
97288
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full shadow-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, null, "Quiz Data Management"), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, "Import a quiz from a JSON file or export the current quiz configuration.")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col gap-2 mb-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "quiz-file-input", className: "text-sm font-Medium" }, "Import Quiz (JSON)"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
97289
97289
|
Input,
|
|
97290
97290
|
{
|
|
97291
97291
|
id: "quiz-file-input",
|
|
@@ -97293,7 +97293,7 @@ var QuizDataManagement = ({ onQuizLoad, currentQuiz }) => {
|
|
|
97293
97293
|
accept: ".json",
|
|
97294
97294
|
ref: fileInputRef,
|
|
97295
97295
|
onChange: handleFileChange,
|
|
97296
|
-
className: "w-full h-10 px-3 py-2 border border-input bg-background text-sm file:border-0 file:bg-transparent file:text-sm file:font-
|
|
97296
|
+
className: "w-full h-10 px-3 py-2 border border-input bg-background text-sm file:border-0 file:bg-transparent file:text-sm file:font-Medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
97297
97297
|
}
|
|
97298
97298
|
))), error && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-destructive flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__namespace.default.createElement(CardFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleExportQuiz, disabled: !currentQuiz, variant: "outline" }, /* @__PURE__ */ React169__namespace.default.createElement(Download, { className: "mr-2 h-4 w-4" }), " Export Current Quiz")));
|
|
97299
97299
|
};
|
|
@@ -97728,6 +97728,7 @@ var Close = DialogClose;
|
|
|
97728
97728
|
|
|
97729
97729
|
// src/react-ui/components/elements/dialog.tsx
|
|
97730
97730
|
var Dialog2 = Root10;
|
|
97731
|
+
var DialogTrigger2 = Trigger4;
|
|
97731
97732
|
var DialogPortal2 = Portal3;
|
|
97732
97733
|
var DialogClose2 = Close;
|
|
97733
97734
|
var DialogOverlay2 = React169__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React169__namespace.createElement(
|
|
@@ -97926,7 +97927,7 @@ var CommandGroup = React169__namespace.forwardRef(({ className, ...props }, ref)
|
|
|
97926
97927
|
{
|
|
97927
97928
|
ref,
|
|
97928
97929
|
className: cn(
|
|
97929
|
-
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-
|
|
97930
|
+
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-Medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
|
97930
97931
|
className
|
|
97931
97932
|
),
|
|
97932
97933
|
...props
|
|
@@ -98327,7 +98328,7 @@ var TrueFalseQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98327
98328
|
const handleCorrectAnswerChange = (value) => {
|
|
98328
98329
|
onFormChange({ correctAnswer: value === "true" });
|
|
98329
98330
|
};
|
|
98330
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98331
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "True/False Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Correct Answer"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98331
98332
|
RadioGroup2,
|
|
98332
98333
|
{
|
|
98333
98334
|
value: question2.correctAnswer ? "true" : "false",
|
|
@@ -98364,7 +98365,7 @@ var MultipleChoiceQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98364
98365
|
const handleCorrectAnswerChange = (optionId) => {
|
|
98365
98366
|
onFormChange({ correctAnswerId: optionId });
|
|
98366
98367
|
};
|
|
98367
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98368
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Multiple Choice Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Options"), question2.options.length === 0 && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No options added yet."), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98368
98369
|
RadioGroup2,
|
|
98369
98370
|
{
|
|
98370
98371
|
value: question2.correctAnswerId,
|
|
@@ -98426,7 +98427,7 @@ var MultipleResponseQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98426
98427
|
const newCorrectAnswerIds = question2.correctAnswerIds.filter((id3) => id3 !== optionIdToDelete);
|
|
98427
98428
|
onFormChange({ options: newOptions, correctAnswerIds: newCorrectAnswerIds });
|
|
98428
98429
|
};
|
|
98429
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98430
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Multiple Response Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Options (Select all correct answers)"), question2.options.length === 0 && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No options added yet."), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 mt-2" }, question2.options.map((option, index3) => (
|
|
98430
98431
|
// *** CHANGED: Adjusted layout for SimpleMarkdownEditor ***
|
|
98431
98432
|
/* @__PURE__ */ React169__namespace.default.createElement("div", { key: option.id, className: "flex items-start space-x-2 p-2 border rounded-md bg-background hover:shadow-sm" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98432
98433
|
Checkbox2,
|
|
@@ -98648,7 +98649,7 @@ var ShortAnswerQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98648
98649
|
const toggleCaseSensitive = (checked) => {
|
|
98649
98650
|
onFormChange({ isCaseSensitive: checked });
|
|
98650
98651
|
};
|
|
98651
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98652
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Short Answer Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Accepted Answers"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, "Enter all valid plain text answers. The system will check against these."), question2.acceptedAnswers.length === 0 && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No accepted answers defined yet."), question2.acceptedAnswers.map((answer, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: index3, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98652
98653
|
Input,
|
|
98653
98654
|
{
|
|
98654
98655
|
type: "text",
|
|
@@ -98698,7 +98699,7 @@ var NumericQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98698
98699
|
onFormChange({ tolerance: numValue });
|
|
98699
98700
|
}
|
|
98700
98701
|
};
|
|
98701
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98702
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Numeric Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `num-answer-${question2.id}` }, "Correct Numerical Answer"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98702
98703
|
Input,
|
|
98703
98704
|
{
|
|
98704
98705
|
id: `num-answer-${question2.id}`,
|
|
@@ -98811,7 +98812,7 @@ var FillInTheBlanksQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98811
98812
|
const toggleCaseSensitive = (checked) => {
|
|
98812
98813
|
onFormChange({ isCaseSensitive: checked });
|
|
98813
98814
|
};
|
|
98814
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98815
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Fill In The Blanks Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Question Segments"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, "Construct the question by adding text and blank segments. The prompt in the main editor will be shown above these segments."), question2.segments.map((segment, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: `segment-${index3}`, className: "flex items-start space-x-2 mt-2 p-2 border rounded-md bg-background" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-grow space-y-1" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-xs font-Medium capitalize text-muted-foreground" }, segment.type, " Segment ", index3 + 1), segment.type === "text" ? (
|
|
98815
98816
|
// *** CHANGED: Replaced Textarea with SimpleMarkdownEditor ***
|
|
98816
98817
|
/* @__PURE__ */ React169__namespace.default.createElement(
|
|
98817
98818
|
SimpleMarkdownEditor,
|
|
@@ -98884,7 +98885,7 @@ var SequenceQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98884
98885
|
}
|
|
98885
98886
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
98886
98887
|
};
|
|
98887
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98888
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Sequence Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Sequence Items"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, "Define all items that need to be put in order. Users will arrange these."), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 mt-2" }, question2.items.map((item, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: item.id, className: "flex items-start space-x-2 p-2 border rounded-md bg-background" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-grow" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "text-xs font-Medium text-muted-foreground" }, "Item ", index3 + 1), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98888
98889
|
SimpleMarkdownEditor,
|
|
98889
98890
|
{
|
|
98890
98891
|
value: item.content,
|
|
@@ -98987,7 +98988,7 @@ var MatchingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98987
98988
|
}
|
|
98988
98989
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
98989
98990
|
};
|
|
98990
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
98991
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Matching Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Prompts (Items to be matched)"), question2.prompts.map((promptItem, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: promptItem.id, className: "space-y-1" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "text-xs text-muted-foreground" }, "Prompt ", index3 + 1), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeletePrompt(index3), className: "h-7 w-7 text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
98991
98992
|
SimpleMarkdownEditor,
|
|
98992
98993
|
{
|
|
98993
98994
|
value: promptItem.content,
|
|
@@ -99077,7 +99078,7 @@ var DragAndDropQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99077
99078
|
}
|
|
99078
99079
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
99079
99080
|
};
|
|
99080
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
99081
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Drag and Drop Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `dnd-bgimage-${question2.id}` }, "Background Image URL (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99081
99082
|
Input,
|
|
99082
99083
|
{
|
|
99083
99084
|
id: `dnd-bgimage-${question2.id}`,
|
|
@@ -99164,7 +99165,7 @@ var HotspotQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99164
99165
|
}
|
|
99165
99166
|
onFormChange({ correctHotspotIds: newCorrectIds });
|
|
99166
99167
|
};
|
|
99167
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
99168
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Hotspot Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `hs-imageurl-${question2.id}` }, "Image URL"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99168
99169
|
Input,
|
|
99169
99170
|
{
|
|
99170
99171
|
id: `hs-imageurl-${question2.id}`,
|
|
@@ -99182,7 +99183,7 @@ var HotspotQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99182
99183
|
onChange: (e3) => onFormChange({ imageAltText: e3.target.value || void 0 }),
|
|
99183
99184
|
placeholder: "Describe the image"
|
|
99184
99185
|
}
|
|
99185
|
-
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Hotspot Areas"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, "Define clickable areas on the image. Coordinates are relative to the image's top-left corner."), question2.hotspots.map((hotspot, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: hotspot.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-
|
|
99186
|
+
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Hotspot Areas"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, "Define clickable areas on the image. Coordinates are relative to the image's top-left corner."), question2.hotspots.map((hotspot, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: hotspot.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-Medium" }, "Hotspot ", index3 + 1, " (ID: ", hotspot.id, ")"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeleteHotspot(index3), className: "h-8 w-8 text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `hs-shape-${hotspot.id}`, className: "text-xs" }, "Shape"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: hotspot.shape, onValueChange: (value) => handleHotspotChange(index3, "shape", value) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: `hs-shape-${hotspot.id}` }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "rect" }, "Rectangle"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "circle" }, "Circle")))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `hs-coords-${hotspot.id}`, className: "text-xs" }, hotspot.shape === "rect" ? "Coords (x, y, width, height)" : "Coords (centerX, centerY, radius)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99186
99187
|
Input,
|
|
99187
99188
|
{
|
|
99188
99189
|
id: `hs-coords-${hotspot.id}`,
|
|
@@ -99214,7 +99215,7 @@ var BlocklyProgrammingQuestionForm = ({ question: question2, onFormChange }) =>
|
|
|
99214
99215
|
const handleFieldChange = (field, value) => {
|
|
99215
99216
|
onFormChange({ [field]: value });
|
|
99216
99217
|
};
|
|
99217
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
99218
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Blockly Programming Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `blockly-toolbox-${question2.id}` }, "Toolbox Definition (XML)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99218
99219
|
Textarea,
|
|
99219
99220
|
{
|
|
99220
99221
|
id: `blockly-toolbox-${question2.id}`,
|
|
@@ -99263,7 +99264,7 @@ var ScratchProgrammingQuestionForm = ({ question: question2, onFormChange }) =>
|
|
|
99263
99264
|
const handleFieldChange = (field, value) => {
|
|
99264
99265
|
onFormChange({ [field]: value });
|
|
99265
99266
|
};
|
|
99266
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
99267
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Scratch Programming Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "scratch-toolbox-" + question2.id }, "Toolbox Definition (XML for Blockly)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99267
99268
|
Textarea,
|
|
99268
99269
|
{
|
|
99269
99270
|
id: "scratch-toolbox-" + question2.id,
|
|
@@ -99340,7 +99341,7 @@ var CodingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99340
99341
|
const newTestCases = question2.testCases.filter((_2, i2) => i2 !== index3);
|
|
99341
99342
|
onFormChange({ testCases: newTestCases });
|
|
99342
99343
|
};
|
|
99343
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-
|
|
99344
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-Medium text-md" }, "Coding Question Specifics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `coding-lang-${question2.id}` }, "Programming Language"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99344
99345
|
Select2,
|
|
99345
99346
|
{
|
|
99346
99347
|
value: question2.codingLanguage,
|
|
@@ -99366,7 +99367,7 @@ var CodingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99366
99367
|
placeholder: "Enter the complete, correct code solution here.",
|
|
99367
99368
|
className: "min-h-[200px] font-mono text-xs"
|
|
99368
99369
|
}
|
|
99369
|
-
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Test Cases"), question2.testCases.map((tc, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: tc.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-
|
|
99370
|
+
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Test Cases"), question2.testCases.map((tc, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: tc.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-Medium" }, "Test Case ", index3 + 1), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeleteTestCase(index3), className: "h-8 w-8 text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `tc-input-${tc.id}`, className: "text-xs" }, "Input (JSON Array)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
99370
99371
|
Input,
|
|
99371
99372
|
{
|
|
99372
99373
|
id: `tc-input-${tc.id}`,
|
|
@@ -99534,7 +99535,7 @@ var EditQuestionModal = ({
|
|
|
99534
99535
|
if (!isOpen || !editedQuestion) return null;
|
|
99535
99536
|
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
99536
99537
|
if (!open) onClose();
|
|
99537
|
-
} }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-[600px] md:max-w-[800px] lg:max-w-[1000px] max-h-[90vh]" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "font-headline text-2xl" }, questionData?.id && !questionData.id.startsWith("new_") && !questionData.id.startsWith("temp_") ? "Edit Question" : "Add New Question"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Configure the details for this question. Current type:", " ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, editedQuestion.questionType))), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "max-h-[calc(80vh-150px)] p-1 pr-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "prompt", className: "font-semibold" }, "Question Prompt"), /* @__PURE__ */ React169__namespace.default.createElement(SimpleMarkdownEditor, { value: editedQuestion.prompt, onChange: (htmlContent) => handleSpecificFieldChange({ prompt: htmlContent }) })), renderSpecificForm(), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "points" }, "Points"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "points", type: "number", value: editedQuestion.points || 0, onChange: (e3) => handleSpecificFieldChange({ points: parseInt(e3.target.value, 10) || 0 }), min: "0" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "difficulty" }, "Difficulty"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: editedQuestion.difficulty || "
|
|
99538
|
+
} }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-[600px] md:max-w-[800px] lg:max-w-[1000px] max-h-[90vh]" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "font-headline text-2xl" }, questionData?.id && !questionData.id.startsWith("new_") && !questionData.id.startsWith("temp_") ? "Edit Question" : "Add New Question"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Configure the details for this question. Current type:", " ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-semibold" }, editedQuestion.questionType))), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "max-h-[calc(80vh-150px)] p-1 pr-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6 p-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "prompt", className: "font-semibold" }, "Question Prompt"), /* @__PURE__ */ React169__namespace.default.createElement(SimpleMarkdownEditor, { value: editedQuestion.prompt, onChange: (htmlContent) => handleSpecificFieldChange({ prompt: htmlContent }) })), renderSpecificForm(), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "points" }, "Points"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "points", type: "number", value: editedQuestion.points || 0, onChange: (e3) => handleSpecificFieldChange({ points: parseInt(e3.target.value, 10) || 0 }), min: "0" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "difficulty" }, "Difficulty"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: editedQuestion.difficulty || "Medium", onValueChange: (value) => handleSpecificFieldChange({ difficulty: value }) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Easy" }, "Easy"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Medium" }, "Medium"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Hard" }, "Hard"))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "explanation" }, "Explanation (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(SimpleMarkdownEditor, { value: editedQuestion.explanation || "", onChange: (htmlContent) => handleSpecificFieldChange({ explanation: htmlContent }), minHeight: "100px" })), /* @__PURE__ */ React169__namespace.default.createElement("details", { className: "group", open: hasDropdownMetadata }, /* @__PURE__ */ React169__namespace.default.createElement("summary", { className: "cursor-pointer font-semibold text-primary hover:underline" }, "Advanced Metadata (Optional)"), renderMetadataFields()))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, { className: "pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", onClick: handleSaveClick }, /* @__PURE__ */ React169__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }), " Save Question"))));
|
|
99538
99539
|
};
|
|
99539
99540
|
|
|
99540
99541
|
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
@@ -99684,12 +99685,12 @@ var QuizContextSchema = zod.z.object({
|
|
|
99684
99685
|
originalSubject: zod.z.string().optional(),
|
|
99685
99686
|
originalCategory: zod.z.string().optional(),
|
|
99686
99687
|
originalTopic: zod.z.string().optional(),
|
|
99687
|
-
|
|
99688
|
+
description: zod.z.string().optional().describe("The full description of the learning objective for deep context."),
|
|
99688
99689
|
gradeBand: zod.z.string().optional()
|
|
99689
99690
|
});
|
|
99690
99691
|
var BaseQuestionGenerationClientInputSchema = zod.z.object({
|
|
99691
99692
|
language: zod.z.string().optional().default("English"),
|
|
99692
|
-
difficulty: zod.z.enum(["
|
|
99693
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]),
|
|
99693
99694
|
quizContext: QuizContextSchema.optional(),
|
|
99694
99695
|
imageUrl: zod.z.string().url().optional().describe("Optional URL of an image to be used as context.")
|
|
99695
99696
|
});
|
|
@@ -99698,7 +99699,7 @@ var BaseQuestionZodSchema = zod.z.object({
|
|
|
99698
99699
|
prompt: zod.z.string().min(1),
|
|
99699
99700
|
points: zod.z.number().min(0).optional(),
|
|
99700
99701
|
explanation: zod.z.string().optional(),
|
|
99701
|
-
difficulty: zod.z.enum(["
|
|
99702
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99702
99703
|
topic: zod.z.string().optional(),
|
|
99703
99704
|
category: zod.z.string().optional(),
|
|
99704
99705
|
subject: zod.z.string().optional(),
|
|
@@ -99794,7 +99795,7 @@ var AITrueFalseOutputFieldsSchema = zod.z.object({
|
|
|
99794
99795
|
correctAnswer: zod.z.boolean(),
|
|
99795
99796
|
explanation: zod.z.string().optional().describe("An explanation of why the statement is true or false, especially important if false."),
|
|
99796
99797
|
points: zod.z.number().optional().default(10),
|
|
99797
|
-
difficulty: zod.z.enum(["
|
|
99798
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99798
99799
|
topic: zod.z.string().optional(),
|
|
99799
99800
|
verifiedCategory: zod.z.string().optional()
|
|
99800
99801
|
// Thêm để xác thực
|
|
@@ -99815,7 +99816,7 @@ Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a bool
|
|
|
99815
99816
|
const misconceptionGuidance = quizContext?.targetMisconception ? `**Target Misconception:** The statement you create MUST be FALSE and based on this common mistake: "${quizContext.targetMisconception}"` : "";
|
|
99816
99817
|
const contextStrings = [
|
|
99817
99818
|
`**Required Category:** ${category}`,
|
|
99818
|
-
quizContext?.
|
|
99819
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
99819
99820
|
imageContextInstruction,
|
|
99820
99821
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
99821
99822
|
misconceptionGuidance,
|
|
@@ -99826,7 +99827,7 @@ Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a bool
|
|
|
99826
99827
|
correctAnswer: true,
|
|
99827
99828
|
explanation: "Optional values in Swift represent the presence or absence of a value. To access the value when it exists, you must unwrap it using methods like 'if let', 'guard let', or the force unwrap operator '!'.",
|
|
99828
99829
|
points: 10,
|
|
99829
|
-
difficulty: "
|
|
99830
|
+
difficulty: "Easy",
|
|
99830
99831
|
topic: "Swift Optionals",
|
|
99831
99832
|
verifiedCategory: category
|
|
99832
99833
|
}, null, 2);
|
|
@@ -99962,7 +99963,7 @@ var AIMCQOutputFieldsSchema = zod.z.object({
|
|
|
99962
99963
|
correctTempOptionId: zod.z.string().describe("The temporary ID of the correct option from the generated options array."),
|
|
99963
99964
|
explanation: zod.z.string().optional().describe("A brief explanation of why the answer is correct."),
|
|
99964
99965
|
points: zod.z.number().optional().default(10),
|
|
99965
|
-
difficulty: zod.z.enum(["
|
|
99966
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99966
99967
|
topic: zod.z.string().optional(),
|
|
99967
99968
|
verifiedCategory: zod.z.string().optional().describe("The category this question actually addresses.")
|
|
99968
99969
|
});
|
|
@@ -99981,7 +99982,7 @@ Previous attempts failed...
|
|
|
99981
99982
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and options must be directly related to the content of this image.` : "";
|
|
99982
99983
|
const contextStrings = [
|
|
99983
99984
|
`**Required Category:** ${category}`,
|
|
99984
|
-
quizContext?.
|
|
99985
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
99985
99986
|
imageContextInstruction,
|
|
99986
99987
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
99987
99988
|
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers: "${quizContext.targetMisconception}"`,
|
|
@@ -99998,7 +99999,7 @@ Previous attempts failed...
|
|
|
99998
99999
|
correctTempOptionId: "C",
|
|
99999
100000
|
explanation: `The 'guard' statement in ${category} provides an early exit from a scope (like a function) if a condition is false, enhancing code readability by handling required conditions upfront.`,
|
|
100000
100001
|
points: 10,
|
|
100001
|
-
difficulty: "
|
|
100002
|
+
difficulty: "Easy",
|
|
100002
100003
|
topic: `Control Flow in ${category}`,
|
|
100003
100004
|
verifiedCategory: category
|
|
100004
100005
|
}, null, 2);
|
|
@@ -100130,807 +100131,6 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
100130
100131
|
return { error: errorMessage };
|
|
100131
100132
|
}
|
|
100132
100133
|
|
|
100133
|
-
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
100134
|
-
var supportedQuestionTypesForAI = [
|
|
100135
|
-
{ value: "true_false", label: "True/False" },
|
|
100136
|
-
{ value: "multiple_choice", label: "Multiple Choice" },
|
|
100137
|
-
{ value: "multiple_response", label: "Multiple Response" },
|
|
100138
|
-
{ value: "short_answer", label: "Short Answer" },
|
|
100139
|
-
{ value: "numeric", label: "Numeric" },
|
|
100140
|
-
{ value: "fill_in_the_blanks", label: "Fill In The Blanks" },
|
|
100141
|
-
{ value: "sequence", label: "Sequence" },
|
|
100142
|
-
{ value: "matching", label: "Matching" }
|
|
100143
|
-
];
|
|
100144
|
-
var AIQuestionGeneratorModal = ({
|
|
100145
|
-
isOpen,
|
|
100146
|
-
onClose,
|
|
100147
|
-
onQuestionGenerated,
|
|
100148
|
-
language: language3,
|
|
100149
|
-
questionType: questionTypeProp,
|
|
100150
|
-
subjects = [],
|
|
100151
|
-
topics = [],
|
|
100152
|
-
gradeLevels = [],
|
|
100153
|
-
bloomLevels = []
|
|
100154
|
-
}) => {
|
|
100155
|
-
const [prompt, setPrompt] = React169.useState("");
|
|
100156
|
-
const [isLoading, setIsLoading] = React169.useState(false);
|
|
100157
|
-
const [error, setError] = React169.useState(null);
|
|
100158
|
-
const { toast: toast2 } = useToast();
|
|
100159
|
-
const [subjectCode, setSubjectCode] = React169.useState("");
|
|
100160
|
-
const [topicCode, setTopicCode] = React169.useState("");
|
|
100161
|
-
const [gradeBand, setGradeBand] = React169.useState("");
|
|
100162
|
-
const [bloomLevelCode, setBloomLevelCode] = React169.useState("");
|
|
100163
|
-
const [selectedQuestionType, setSelectedQuestionType] = React169.useState("multiple_choice");
|
|
100164
|
-
const [isApiKeyManagerModalOpen, setIsApiKeyManagerModalOpen] = React169.useState(false);
|
|
100165
|
-
const [geminiApiKeyExists, setGeminiApiKeyExists] = React169.useState(false);
|
|
100166
|
-
const finalQuestionType = questionTypeProp || selectedQuestionType;
|
|
100167
|
-
React169.useEffect(() => {
|
|
100168
|
-
if (isOpen) {
|
|
100169
|
-
setPrompt("");
|
|
100170
|
-
setError(null);
|
|
100171
|
-
setIsLoading(false);
|
|
100172
|
-
setSubjectCode("");
|
|
100173
|
-
setTopicCode("");
|
|
100174
|
-
setGradeBand("");
|
|
100175
|
-
setBloomLevelCode("");
|
|
100176
|
-
setSelectedQuestionType(questionTypeProp || "multiple_choice");
|
|
100177
|
-
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
100178
|
-
}
|
|
100179
|
-
}, [isOpen, questionTypeProp]);
|
|
100180
|
-
const filteredTopics = React169.useMemo(() => {
|
|
100181
|
-
if (!subjectCode) return [];
|
|
100182
|
-
return topics.filter((t4) => t4.subjectCode === subjectCode);
|
|
100183
|
-
}, [subjectCode, topics]);
|
|
100184
|
-
const handleApiKeyModalClose = () => {
|
|
100185
|
-
setIsApiKeyManagerModalOpen(false);
|
|
100186
|
-
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
100187
|
-
};
|
|
100188
|
-
const handleSubmit = async () => {
|
|
100189
|
-
if (!prompt.trim()) {
|
|
100190
|
-
setError("Please provide a prompt for the question.");
|
|
100191
|
-
return;
|
|
100192
|
-
}
|
|
100193
|
-
const geminiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
|
|
100194
|
-
if (!geminiKey) {
|
|
100195
|
-
setError("Gemini API Key is not set. Please configure it.");
|
|
100196
|
-
setGeminiApiKeyExists(false);
|
|
100197
|
-
return;
|
|
100198
|
-
}
|
|
100199
|
-
setGeminiApiKeyExists(true);
|
|
100200
|
-
setError(null);
|
|
100201
|
-
setIsLoading(true);
|
|
100202
|
-
try {
|
|
100203
|
-
const quizContext = {
|
|
100204
|
-
plannedTopic: prompt,
|
|
100205
|
-
originalSubject: subjectCode,
|
|
100206
|
-
originalTopic: topicCode,
|
|
100207
|
-
gradeBand,
|
|
100208
|
-
plannedBloomLevel: bloomLevelCode
|
|
100209
|
-
};
|
|
100210
|
-
const baseClientInput = {
|
|
100211
|
-
language: language3,
|
|
100212
|
-
difficulty: "medium",
|
|
100213
|
-
quizContext
|
|
100214
|
-
};
|
|
100215
|
-
let generatedResult = {};
|
|
100216
|
-
switch (finalQuestionType) {
|
|
100217
|
-
case "true_false":
|
|
100218
|
-
generatedResult = await generateTrueFalseQuestion(baseClientInput, geminiKey);
|
|
100219
|
-
break;
|
|
100220
|
-
case "multiple_choice":
|
|
100221
|
-
generatedResult = await generateMCQQuestion({ ...baseClientInput, numberOfOptions: 4 }, geminiKey);
|
|
100222
|
-
break;
|
|
100223
|
-
// Add other cases as needed
|
|
100224
|
-
default:
|
|
100225
|
-
throw new Error(`AI generation for '${finalQuestionType}' is not implemented in this flow.`);
|
|
100226
|
-
}
|
|
100227
|
-
if (generatedResult.error) {
|
|
100228
|
-
throw new Error(generatedResult.error);
|
|
100229
|
-
}
|
|
100230
|
-
if (generatedResult.question) {
|
|
100231
|
-
const completeQuestion = generatedResult.question;
|
|
100232
|
-
completeQuestion.subject = subjectCode;
|
|
100233
|
-
completeQuestion.topic = topicCode;
|
|
100234
|
-
completeQuestion.gradeBand = gradeBand;
|
|
100235
|
-
completeQuestion.bloomLevel = bloomLevelCode;
|
|
100236
|
-
if (completeQuestion.points === void 0) completeQuestion.points = 10;
|
|
100237
|
-
onQuestionGenerated(completeQuestion);
|
|
100238
|
-
toast2({ title: "AI Question Generated", description: "Review and edit the generated question." });
|
|
100239
|
-
onClose();
|
|
100240
|
-
} else {
|
|
100241
|
-
throw new Error("AI did not return a valid question object.");
|
|
100242
|
-
}
|
|
100243
|
-
} catch (e3) {
|
|
100244
|
-
console.error("AI Question Generation Error:", e3);
|
|
100245
|
-
let errorMessage = e3.message || "An unknown error occurred.";
|
|
100246
|
-
if (typeof errorMessage === "string" && errorMessage.includes("API key not valid")) {
|
|
100247
|
-
errorMessage = "The provided Google Gemini API Key is invalid. Please check it.";
|
|
100248
|
-
setGeminiApiKeyExists(false);
|
|
100249
|
-
}
|
|
100250
|
-
setError(errorMessage);
|
|
100251
|
-
toast2({ title: "AI Generation Failed", description: errorMessage, variant: "destructive" });
|
|
100252
|
-
} finally {
|
|
100253
|
-
setIsLoading(false);
|
|
100254
|
-
}
|
|
100255
|
-
};
|
|
100256
|
-
const comboboxOptions = {
|
|
100257
|
-
subjects: subjects.map((s4) => ({ value: s4.code, label: s4.name })),
|
|
100258
|
-
topics: filteredTopics.map((t4) => ({ value: t4.code, label: t4.name })),
|
|
100259
|
-
gradeLevels: gradeLevels.map((g) => ({ value: g.code, label: g.name })),
|
|
100260
|
-
bloomLevels: bloomLevels.map((b2) => ({ value: b2.code, label: b2.name }))
|
|
100261
|
-
};
|
|
100262
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
100263
|
-
if (!open) onClose();
|
|
100264
|
-
} }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-[600px]" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center font-headline text-2xl" }, /* @__PURE__ */ React169__namespace.default.createElement(WandSparkles, { className: "mr-2 h-6 w-6 text-primary" }), " AI Question Generator"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Provide a prompt and metadata to generate a '", finalQuestionType, "' question.")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 py-4 max-h-[60vh] overflow-y-auto px-2" }, !geminiApiKeyExists && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-3 mb-4 border border-amber-500 bg-amber-50 rounded-md text-amber-700" }), !questionTypeProp && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-q-type-select" }, "Question Type"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: selectedQuestionType, onValueChange: (v) => setSelectedQuestionType(v) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "ai-q-type-select" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, supportedQuestionTypesForAI.map((type) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: type.value, value: type.value }, type.label))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-prompt" }, "Prompt / Topic"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
100265
|
-
Textarea,
|
|
100266
|
-
{
|
|
100267
|
-
id: "ai-prompt",
|
|
100268
|
-
value: prompt,
|
|
100269
|
-
onChange: (e3) => setPrompt(e3.target.value),
|
|
100270
|
-
placeholder: "e.g., The process of photosynthesis, The causes of World War II, Basic Algebra Equations",
|
|
100271
|
-
className: "min-h-[100px]"
|
|
100272
|
-
}
|
|
100273
|
-
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.subjects, value: subjectCode, onChange: setSubjectCode, placeholder: "Select or type a Subject..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.topics, value: topicCode, onChange: setTopicCode, placeholder: "Select or type a Topic...", disabled: !subjectCode })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Grade Level"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.gradeLevels, value: gradeBand, onChange: setGradeBand, placeholder: "Select or type a Grade..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Bloom's Level"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.bloomLevels, value: bloomLevelCode, onChange: setBloomLevelCode, placeholder: "Select a Bloom's Level..." }))), error && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-destructive flex items-center mt-2" }, /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", onClick: handleSubmit, disabled: isLoading || !prompt.trim() || !geminiApiKeyExists }, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(WandSparkles, { className: "mr-2 h-4 w-4" }), "Generate Question")))), /* @__PURE__ */ React169__namespace.default.createElement(APIKeyManagerModal, { isOpen: isApiKeyManagerModalOpen, onClose: handleApiKeyModalClose }));
|
|
100274
|
-
};
|
|
100275
|
-
|
|
100276
|
-
// src/react-ui/components/authoring/AIFullQuizGeneratorModal.tsx
|
|
100277
|
-
init_react_shim();
|
|
100278
|
-
|
|
100279
|
-
// src/ai/flows/generate-quiz-plan.ts
|
|
100280
|
-
init_react_shim();
|
|
100281
|
-
|
|
100282
|
-
// src/ai/flows/generate-quiz-plan-types.ts
|
|
100283
|
-
init_react_shim();
|
|
100284
|
-
var TopicWithMetadataSchema = zod.z.object({
|
|
100285
|
-
topic: zod.z.string().min(1),
|
|
100286
|
-
ratio: zod.z.number().min(0).max(100),
|
|
100287
|
-
originalLoId: zod.z.string().optional(),
|
|
100288
|
-
originalSubject: zod.z.string().optional(),
|
|
100289
|
-
originalCategory: zod.z.string().optional(),
|
|
100290
|
-
originalTopic: zod.z.string().optional(),
|
|
100291
|
-
commonMisconceptions: zod.z.array(zod.z.string()).optional()
|
|
100292
|
-
});
|
|
100293
|
-
var BloomLevelStringsEnum = zod.z.enum(["remembering", "understanding", "applying", "analyzing", "evaluating", "creating"]);
|
|
100294
|
-
var fullQuizSupportedQuestionTypesArray = [
|
|
100295
|
-
"true_false",
|
|
100296
|
-
"multiple_choice",
|
|
100297
|
-
"multiple_response",
|
|
100298
|
-
"short_answer",
|
|
100299
|
-
"numeric",
|
|
100300
|
-
"fill_in_the_blanks",
|
|
100301
|
-
"sequence",
|
|
100302
|
-
"matching",
|
|
100303
|
-
"drag_and_drop",
|
|
100304
|
-
"coding"
|
|
100305
|
-
];
|
|
100306
|
-
zod.z.object({
|
|
100307
|
-
language: zod.z.string().optional().default("English"),
|
|
100308
|
-
totalQuestions: zod.z.number().int().min(1).max(50),
|
|
100309
|
-
numCodingQuestions: zod.z.number().optional().default(0),
|
|
100310
|
-
topics: zod.z.array(TopicWithMetadataSchema).min(1),
|
|
100311
|
-
bloomLevels: zod.z.array(zod.z.object({
|
|
100312
|
-
level: BloomLevelStringsEnum,
|
|
100313
|
-
ratio: zod.z.number().min(0).max(100)
|
|
100314
|
-
})).min(1),
|
|
100315
|
-
selectedContextIds: zod.z.array(zod.z.string()).optional(),
|
|
100316
|
-
selectedQuestionTypes: zod.z.array(zod.z.enum(fullQuizSupportedQuestionTypesArray)).min(1),
|
|
100317
|
-
imageContexts: zod.z.array(zod.z.custom()).optional().describe("Library of available image contexts for the AI to use.")
|
|
100318
|
-
});
|
|
100319
|
-
var PlannedQuestionSchema = zod.z.object({
|
|
100320
|
-
plannedTopic: zod.z.string().min(1).describe("The specific, assessable topic for this question."),
|
|
100321
|
-
plannedQuestionType: zod.z.enum(fullQuizSupportedQuestionTypesArray).describe("The specific question type chosen."),
|
|
100322
|
-
plannedBloomLevel: BloomLevelStringsEnum.describe("The Bloom's level assigned."),
|
|
100323
|
-
plannedContextId: zod.z.string().optional().describe("The specific context ID chosen for this question."),
|
|
100324
|
-
imageId: zod.z.string().nullable().optional().describe("The ID of the image from the context library to be used for this question."),
|
|
100325
|
-
targetMisconception: zod.z.string().optional().describe("A specific common misconception this question should target."),
|
|
100326
|
-
difficultyReason: zod.z.string().optional().describe("Strategic explanation of difficulty choice and placement."),
|
|
100327
|
-
topicSpecificity: zod.z.enum(["broad", "focused", "specific"]).optional().describe("How specific the topic coverage should be."),
|
|
100328
|
-
originalLoId: zod.z.string().optional(),
|
|
100329
|
-
originalSubject: zod.z.string().optional(),
|
|
100330
|
-
originalCategory: zod.z.string().optional(),
|
|
100331
|
-
originalTopic: zod.z.string().optional()
|
|
100332
|
-
});
|
|
100333
|
-
var GenerateQuizPlanOutputSchema = zod.z.object({
|
|
100334
|
-
quizPlan: zod.z.array(PlannedQuestionSchema).describe("A detailed plan for each question in the quiz."),
|
|
100335
|
-
diversityMetrics: zod.z.object({
|
|
100336
|
-
questionTypeDistribution: zod.z.record(zod.z.number()).optional(),
|
|
100337
|
-
bloomLevelDistribution: zod.z.record(zod.z.number()).optional(),
|
|
100338
|
-
maxConsecutiveSameType: zod.z.number().optional()
|
|
100339
|
-
}).optional().describe("Metrics showing the diversity achieved in the plan."),
|
|
100340
|
-
planningStrategy: zod.z.object({
|
|
100341
|
-
overallApproach: zod.z.string().optional(),
|
|
100342
|
-
keyDecisions: zod.z.array(zod.z.string()).optional()
|
|
100343
|
-
}).optional()
|
|
100344
|
-
});
|
|
100345
|
-
|
|
100346
|
-
// src/ai/flows/generate-quiz-plan.ts
|
|
100347
|
-
var QuizPlanLogger = class {
|
|
100348
|
-
constructor() {
|
|
100349
|
-
this.logs = [];
|
|
100350
|
-
this.startTime = Date.now();
|
|
100351
|
-
}
|
|
100352
|
-
log(phase, data, duration) {
|
|
100353
|
-
this.logs.push({
|
|
100354
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
100355
|
-
phase,
|
|
100356
|
-
data,
|
|
100357
|
-
duration
|
|
100358
|
-
});
|
|
100359
|
-
if (duration !== void 0) {
|
|
100360
|
-
console.log(`[${phase}] Completed in ${duration}ms:`, data);
|
|
100361
|
-
} else {
|
|
100362
|
-
console.log(`[${phase}]:`, data);
|
|
100363
|
-
}
|
|
100364
|
-
}
|
|
100365
|
-
getLogs() {
|
|
100366
|
-
return this.logs;
|
|
100367
|
-
}
|
|
100368
|
-
getTotalDuration() {
|
|
100369
|
-
return Date.now() - this.startTime;
|
|
100370
|
-
}
|
|
100371
|
-
};
|
|
100372
|
-
function generateQuestionTypeSelectionGuidance() {
|
|
100373
|
-
return `
|
|
100374
|
-
QUESTION TYPE SELECTION BEST PRACTICES:
|
|
100375
|
-
|
|
100376
|
-
**TRUE_FALSE (true_false)**
|
|
100377
|
-
- Best for: Binary concepts, fact verification, common misconceptions
|
|
100378
|
-
- Bloom levels: Primarily Remembering, Understanding
|
|
100379
|
-
- Use when: Testing definitive statements, clarifying misconceptions
|
|
100380
|
-
- Example: "Photosynthesis only occurs during daytime" (targets timing misconception)
|
|
100381
|
-
|
|
100382
|
-
**MULTIPLE CHOICE (multiple_choice)**
|
|
100383
|
-
- Best for: Concept selection, process understanding, comparison
|
|
100384
|
-
- Bloom levels: All levels, especially Understanding and Applying
|
|
100385
|
-
- Use when: Testing conceptual understanding with clear alternatives
|
|
100386
|
-
- Example: "Which factor most affects enzyme activity?" (applying knowledge)
|
|
100387
|
-
|
|
100388
|
-
**MULTIPLE RESPONSE (multiple_response)**
|
|
100389
|
-
- Best for: Identifying multiple correct factors, comprehensive understanding
|
|
100390
|
-
- Bloom levels: Understanding, Analyzing, Evaluating
|
|
100391
|
-
- Use when: Multiple correct answers exist, testing thorough knowledge
|
|
100392
|
-
- Example: "Select all factors that influence plant growth" (analyzing components)
|
|
100393
|
-
|
|
100394
|
-
**FILL IN THE BLANKS (fill_in_the_blanks)**
|
|
100395
|
-
- Best for: Key terminology, formulas, specific facts
|
|
100396
|
-
- Bloom levels: Remembering, Understanding
|
|
100397
|
-
- Use when: Testing precise recall of important terms/concepts
|
|
100398
|
-
- Example: "The process of _____ converts light energy to chemical energy"
|
|
100399
|
-
|
|
100400
|
-
**NUMERIC (numeric)**
|
|
100401
|
-
- Best for: Calculations, quantitative problems, formula application
|
|
100402
|
-
- Bloom levels: Applying, Analyzing
|
|
100403
|
-
- Use when: Mathematical computations are required
|
|
100404
|
-
- Example: "Calculate the molarity of a 2L solution containing 0.5 moles of NaCl"
|
|
100405
|
-
|
|
100406
|
-
**MATCHING (matching)**
|
|
100407
|
-
- Best for: Connecting related concepts, terminology pairs
|
|
100408
|
-
- Bloom levels: Remembering, Understanding
|
|
100409
|
-
- Use when: Testing relationships between terms and definitions
|
|
100410
|
-
- Example: Match organelles with their functions
|
|
100411
|
-
|
|
100412
|
-
**SEQUENCE (sequence)**
|
|
100413
|
-
- Best for: Process steps, chronological order, procedural knowledge
|
|
100414
|
-
- Bloom levels: Understanding, Applying, Analyzing
|
|
100415
|
-
- Use when: Order or sequence is critical to understanding
|
|
100416
|
-
- Example: "Arrange the steps of mitosis in correct order"
|
|
100417
|
-
|
|
100418
|
-
**DRAG AND DROP (drag_and_drop)**
|
|
100419
|
-
- Best for: Categorization, classification, spatial relationships
|
|
100420
|
-
- Bloom levels: Understanding, Applying, Analyzing
|
|
100421
|
-
- Use when: Grouping or organizing information is key
|
|
100422
|
-
- Example: "Classify these compounds as acids, bases, or neutral"
|
|
100423
|
-
|
|
100424
|
-
**SHORT ANSWER (short_answer)**
|
|
100425
|
-
- Best for: Explanations, definitions, problem-solving steps
|
|
100426
|
-
- Bloom levels: Understanding, Applying, Analyzing, Evaluating, Creating
|
|
100427
|
-
- Use when: Requiring explanatory responses, open-ended thinking
|
|
100428
|
-
- Example: "Explain why enzymes are specific to certain substrates"
|
|
100429
|
-
|
|
100430
|
-
**CODING (coding)**
|
|
100431
|
-
- Best for: Programming problems, algorithm implementation
|
|
100432
|
-
- Bloom levels: Applying, Analyzing, Evaluating, Creating
|
|
100433
|
-
- Use when: Technical implementation or code analysis is required
|
|
100434
|
-
- Example: "Write a function to calculate factorial recursively"
|
|
100435
|
-
`;
|
|
100436
|
-
}
|
|
100437
|
-
function generateAdvancedBloomGuidance() {
|
|
100438
|
-
return `
|
|
100439
|
-
ADVANCED BLOOM'S TAXONOMY & QUESTION TYPE OPTIMIZATION:
|
|
100440
|
-
|
|
100441
|
-
**REMEMBERING (Knowledge Recall)**
|
|
100442
|
-
- Primary types: true_false, fill_in_the_blanks, matching
|
|
100443
|
-
- Secondary types: multiple_choice (simple recall)
|
|
100444
|
-
- Focus: Facts, terms, basic concepts, definitions
|
|
100445
|
-
- Misconception addressing: Use true_false to clarify common confusions
|
|
100446
|
-
|
|
100447
|
-
**UNDERSTANDING (Comprehension)**
|
|
100448
|
-
- Primary types: multiple_choice, short_answer, matching
|
|
100449
|
-
- Secondary types: multiple_response, sequence
|
|
100450
|
-
- Focus: Explanations, interpretations, examples, classifications
|
|
100451
|
-
- Best for: "Explain why...", "What does this mean...", "Give an example..."
|
|
100452
|
-
|
|
100453
|
-
**APPLYING (Using Knowledge)**
|
|
100454
|
-
- Primary types: numeric, short_answer, sequence, coding
|
|
100455
|
-
- Secondary types: multiple_choice, drag_and_drop
|
|
100456
|
-
- Focus: Problem-solving, implementing procedures, using methods
|
|
100457
|
-
- Best for: Calculations, step-by-step processes, practical applications
|
|
100458
|
-
|
|
100459
|
-
**ANALYZING (Breaking Down Information)**
|
|
100460
|
-
- Primary types: multiple_response, short_answer, sequence, coding
|
|
100461
|
-
- Secondary types: drag_and_drop, multiple_choice
|
|
100462
|
-
- Focus: Identifying components, relationships, cause-effect
|
|
100463
|
-
- Best for: "What are the factors...", "How do these relate...", "Break down..."
|
|
100464
|
-
|
|
100465
|
-
**EVALUATING (Making Judgments)**
|
|
100466
|
-
- Primary types: short_answer, multiple_response, coding
|
|
100467
|
-
- Secondary types: multiple_choice (with justification)
|
|
100468
|
-
- Focus: Critiquing, judging, comparing alternatives, decision-making
|
|
100469
|
-
- Best for: "Which is better and why...", "Evaluate the approach...", "Critique..."
|
|
100470
|
-
|
|
100471
|
-
**CREATING (Producing New Work)**
|
|
100472
|
-
- Primary types: short_answer, coding, sequence
|
|
100473
|
-
- Secondary types: drag_and_drop (design tasks)
|
|
100474
|
-
- Focus: Designing, planning, producing, constructing
|
|
100475
|
-
- Best for: "Design a solution...", "Create a plan...", "Develop a strategy..."
|
|
100476
|
-
`;
|
|
100477
|
-
}
|
|
100478
|
-
function generateDiversityRules() {
|
|
100479
|
-
return `
|
|
100480
|
-
ENHANCED DIVERSITY & QUALITY ASSURANCE RULES:
|
|
100481
|
-
|
|
100482
|
-
**Question Type Distribution Strategy:**
|
|
100483
|
-
1. Never place more than 3 consecutive questions of the same type
|
|
100484
|
-
2. Distribute question types based on their cognitive complexity
|
|
100485
|
-
3. For quizzes with 10+ questions, use at least 4 different question types
|
|
100486
|
-
4. For quizzes with 20+ questions, use at least 6 different question types
|
|
100487
|
-
5. Balance quick-answer types (true_false, multiple_choice) with deeper types (short_answer, coding)
|
|
100488
|
-
|
|
100489
|
-
**Intelligent Difficulty Progression:**
|
|
100490
|
-
1. Opening (20%): Start with confidence-building questions (Remembering/Understanding)
|
|
100491
|
-
2. Building (40%): Gradually increase complexity (Understanding/Applying)
|
|
100492
|
-
3. Peak (30%): Most challenging questions (Analyzing/Evaluating/Creating)
|
|
100493
|
-
4. Closing (10%): Moderate challenge to end positively
|
|
100494
|
-
|
|
100495
|
-
**Misconception-Driven Planning:**
|
|
100496
|
-
- When common misconceptions are provided, prioritize question types that can effectively address them
|
|
100497
|
-
- Use true_false for binary misconceptions
|
|
100498
|
-
- Use multiple_choice for concept selection misconceptions
|
|
100499
|
-
- Use short_answer for complex misconceptions requiring explanation
|
|
100500
|
-
|
|
100501
|
-
**Contextual Intelligence:**
|
|
100502
|
-
- Programming/Technical topics: Favor coding, numeric, short_answer
|
|
100503
|
-
- Process-oriented topics: Favor sequence, short_answer, drag_and_drop
|
|
100504
|
-
- Conceptual topics: Favor multiple_choice, multiple_response, true_false
|
|
100505
|
-
- Factual topics: Favor fill_in_the_blanks, matching, true_false
|
|
100506
|
-
`;
|
|
100507
|
-
}
|
|
100508
|
-
async function generateQuizPlan(clientInput, apiKey, imageContexts = []) {
|
|
100509
|
-
const logger = new QuizPlanLogger();
|
|
100510
|
-
try {
|
|
100511
|
-
logger.log("VALIDATION_START", {
|
|
100512
|
-
totalQuestions: clientInput.totalQuestions,
|
|
100513
|
-
availableTypes: clientInput.selectedQuestionTypes,
|
|
100514
|
-
topicCount: clientInput.topics.length,
|
|
100515
|
-
bloomLevelCount: clientInput.bloomLevels.length
|
|
100516
|
-
});
|
|
100517
|
-
const totalTopicRatio = clientInput.topics.reduce((sum, t4) => sum + t4.ratio, 0);
|
|
100518
|
-
if (Math.abs(totalTopicRatio - 100) > 1) {
|
|
100519
|
-
throw new Error(`Total topic ratio must be 100%. Current sum: ${totalTopicRatio.toFixed(1)}%`);
|
|
100520
|
-
}
|
|
100521
|
-
const totalBloomRatio = clientInput.bloomLevels.reduce((sum, b2) => sum + b2.ratio, 0);
|
|
100522
|
-
if (Math.abs(totalBloomRatio - 100) > 1) {
|
|
100523
|
-
throw new Error(`Total Bloom level ratio must be 100%. Current sum: ${totalBloomRatio.toFixed(1)}%`);
|
|
100524
|
-
}
|
|
100525
|
-
logger.log("VALIDATION_SUCCESS", {
|
|
100526
|
-
topicRatioSum: totalTopicRatio,
|
|
100527
|
-
bloomRatioSum: totalBloomRatio
|
|
100528
|
-
});
|
|
100529
|
-
const aiStartTime = Date.now();
|
|
100530
|
-
const ai = new genai.GoogleGenAI({
|
|
100531
|
-
apiKey
|
|
100532
|
-
});
|
|
100533
|
-
const model = "gemini-2.5-pro";
|
|
100534
|
-
const config3 = {
|
|
100535
|
-
temperature: 0.8,
|
|
100536
|
-
thinkingConfig: {
|
|
100537
|
-
thinkingBudget: 4096
|
|
100538
|
-
},
|
|
100539
|
-
responseMimeType: "application/json"
|
|
100540
|
-
};
|
|
100541
|
-
logger.log("AI_INITIALIZATION", { model }, Date.now() - aiStartTime);
|
|
100542
|
-
const { language: language3, totalQuestions, numCodingQuestions = 0 } = clientInput;
|
|
100543
|
-
const promptStartTime = Date.now();
|
|
100544
|
-
const topicsDistribution = clientInput.topics.map((t4) => {
|
|
100545
|
-
let topicString = `- Topic Context: "${t4.topic}", LoId: "${t4.originalLoId || "nil"}", Subject: "${t4.originalSubject || "nil"}", Category: "${t4.originalCategory || "nil"}", Topic: "${t4.originalTopic || "nil"}", Ratio: ${t4.ratio}%`;
|
|
100546
|
-
if (t4.commonMisconceptions && t4.commonMisconceptions.length > 0) {
|
|
100547
|
-
topicString += `
|
|
100548
|
-
- Common Misconceptions: [${t4.commonMisconceptions.join(", ")}]`;
|
|
100549
|
-
}
|
|
100550
|
-
return topicString;
|
|
100551
|
-
}).join("\n ");
|
|
100552
|
-
const bloomDistribution = clientInput.bloomLevels.map(
|
|
100553
|
-
(b2) => `- Level: "${b2.level}", Ratio: ${b2.ratio}%`
|
|
100554
|
-
).join("\n ");
|
|
100555
|
-
let questionTypesForPrompt = [...clientInput.selectedQuestionTypes];
|
|
100556
|
-
if (numCodingQuestions === 0) {
|
|
100557
|
-
questionTypesForPrompt = questionTypesForPrompt.filter(
|
|
100558
|
-
(type) => type !== "short_answer" && type !== "coding"
|
|
100559
|
-
);
|
|
100560
|
-
}
|
|
100561
|
-
const allowedQuestionTypes = questionTypesForPrompt.map((t4) => `'${t4}'`).join(", ");
|
|
100562
|
-
const codingRequirement = numCodingQuestions > 0 ? `
|
|
100563
|
-
**CRITICAL CODING REQUIREMENT**: Exactly ${numCodingQuestions} questions in the plan MUST be of type 'coding'. These should typically be at 'applying' or higher Bloom levels and focus on implementation, debugging, or algorithm design.` : "";
|
|
100564
|
-
const imageContextSection = imageContexts && imageContexts.length > 0 ? `
|
|
100565
|
-
## AVAILABLE IMAGE CONTEXT LIBRARY
|
|
100566
|
-
You have access to a library of pre-described images. When planning a question, if its subject, category, and topic match an image in this library AND the image's description is relevant to the planned question's topic, you MUST include the corresponding \`imageId\` in your output. Otherwise, leave the \`imageId\` field null or omit it.
|
|
100567
|
-
|
|
100568
|
-
\`\`\`json
|
|
100569
|
-
${JSON.stringify(imageContexts.map((img) => ({ imageId: img.id, subject: img.subject, category: img.category, topic: img.topic, description: img.detailedDescription })), null, 2)}
|
|
100570
|
-
\`\`\`
|
|
100571
|
-
` : "";
|
|
100572
|
-
const enhancedPromptText = `You are an elite educational assessment architect with expertise in cognitive science and adaptive learning. Your mission is to create a strategically optimized quiz plan that maximizes learning effectiveness.
|
|
100573
|
-
|
|
100574
|
-
${generateQuestionTypeSelectionGuidance()}
|
|
100575
|
-
|
|
100576
|
-
${generateAdvancedBloomGuidance()}
|
|
100577
|
-
|
|
100578
|
-
${generateDiversityRules()}
|
|
100579
|
-
|
|
100580
|
-
## COMPREHENSIVE QUIZ REQUIREMENTS:
|
|
100581
|
-
|
|
100582
|
-
**Target Language**: ${language3}
|
|
100583
|
-
**Total Questions**: ${totalQuestions}${codingRequirement}
|
|
100584
|
-
|
|
100585
|
-
**Topic Distribution & Learning Context** (follow precisely):
|
|
100586
|
-
${topicsDistribution}
|
|
100587
|
-
|
|
100588
|
-
**Cognitive Complexity Distribution** (follow precisely):
|
|
100589
|
-
${bloomDistribution}
|
|
100590
|
-
|
|
100591
|
-
**Available Question Arsenal**: ${allowedQuestionTypes}
|
|
100592
|
-
|
|
100593
|
-
**Image Resources**: ${imageContextSection}
|
|
100594
|
-
|
|
100595
|
-
## STRATEGIC PLANNING METHODOLOGY:
|
|
100596
|
-
|
|
100597
|
-
1. **Misconception Analysis**: If common misconceptions are provided, design questions specifically to address and correct them
|
|
100598
|
-
2. **Question Type Intelligence**: Select question types based on the cognitive demands and content nature
|
|
100599
|
-
3. **Difficulty Orchestration**: Create a learning journey that builds confidence while challenging appropriately
|
|
100600
|
-
4. **Diversity Optimization**: Ensure variety prevents monotony and maintains engagement
|
|
100601
|
-
5. **Context Sensitivity**: Match question types to topic characteristics (technical, conceptual, procedural)
|
|
100602
|
-
6. **Image Context Integration**: Intelligently associate questions with relevant images from the provided library to enhance contextual understanding.
|
|
100603
|
-
|
|
100604
|
-
## ENHANCED OUTPUT FORMAT:
|
|
100605
|
-
|
|
100606
|
-
Return ONLY a JSON object with this EXACT structure:
|
|
100607
|
-
|
|
100608
|
-
\`\`\`json
|
|
100609
|
-
{
|
|
100610
|
-
"quizPlan": [
|
|
100611
|
-
{
|
|
100612
|
-
"plannedTopic": "Specific, assessable topic derived from provided context",
|
|
100613
|
-
"plannedQuestionType": "question_type_from_allowed_list",
|
|
100614
|
-
"plannedBloomLevel": "bloom_level_from_requirements",
|
|
100615
|
-
"plannedContextId": "THEO_ABS",
|
|
100616
|
-
"imageId": "imgctx_12345abcde", // or null
|
|
100617
|
-
"targetMisconception": "Specific misconception this question addresses (or 'none' if not applicable)",
|
|
100618
|
-
"difficultyReason": "Strategic explanation of difficulty choice and placement",
|
|
100619
|
-
"topicSpecificity": "broad|focused|specific",
|
|
100620
|
-
"originalLoId": "corresponding_LoId_from_input",
|
|
100621
|
-
"originalSubject": "corresponding_Subject_from_input",
|
|
100622
|
-
"originalCategory": "corresponding_Category_from_input",
|
|
100623
|
-
"originalTopic": "corresponding_Topic_from_input"
|
|
100624
|
-
}
|
|
100625
|
-
],
|
|
100626
|
-
"diversityMetrics": {
|
|
100627
|
-
"questionTypeDistribution": {"type1": count1, "type2": count2},
|
|
100628
|
-
"bloomLevelDistribution": {"level1": count1, "level2": count2},
|
|
100629
|
-
"maxConsecutiveSameType": number,
|
|
100630
|
-
"difficultyProgression": "description of how difficulty progresses",
|
|
100631
|
-
"misconceptionCoverage": number_of_misconceptions_addressed
|
|
100632
|
-
},
|
|
100633
|
-
"planningStrategy": {
|
|
100634
|
-
"overallApproach": "Brief description of the strategic approach taken",
|
|
100635
|
-
"keyDecisions": ["Major decision 1", "Major decision 2", "Major decision 3"]
|
|
100636
|
-
}
|
|
100637
|
-
}
|
|
100638
|
-
\`\`\`
|
|
100639
|
-
|
|
100640
|
-
Execute this plan with pedagogical precision. The quiz should feel like a carefully crafted learning journey that challenges and educates simultaneously.`;
|
|
100641
|
-
logger.log("PROMPT_PREPARATION", {
|
|
100642
|
-
promptLength: enhancedPromptText.length,
|
|
100643
|
-
topicCount: clientInput.topics.length,
|
|
100644
|
-
misconceptionCount: clientInput.topics.reduce((sum, t4) => sum + (t4.commonMisconceptions?.length || 0), 0)
|
|
100645
|
-
}, Date.now() - promptStartTime);
|
|
100646
|
-
const generationStartTime = Date.now();
|
|
100647
|
-
const contents = [
|
|
100648
|
-
{
|
|
100649
|
-
role: "user",
|
|
100650
|
-
parts: [
|
|
100651
|
-
{
|
|
100652
|
-
text: enhancedPromptText
|
|
100653
|
-
}
|
|
100654
|
-
]
|
|
100655
|
-
}
|
|
100656
|
-
];
|
|
100657
|
-
const aiResult = await ai.models.generateContent({
|
|
100658
|
-
model,
|
|
100659
|
-
config: config3,
|
|
100660
|
-
contents
|
|
100661
|
-
});
|
|
100662
|
-
const response = aiResult;
|
|
100663
|
-
const generationDuration = Date.now() - generationStartTime;
|
|
100664
|
-
logger.log("AI_GENERATION", {
|
|
100665
|
-
responseLength: response.candidates?.[0]?.content?.parts?.[0]?.text?.length || 0,
|
|
100666
|
-
duration: generationDuration
|
|
100667
|
-
}, generationDuration);
|
|
100668
|
-
const processingStartTime = Date.now();
|
|
100669
|
-
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
100670
|
-
let jsonText = rawText;
|
|
100671
|
-
if (!rawText.trim().startsWith("{") && !rawText.trim().startsWith("[")) {
|
|
100672
|
-
jsonText = extractJsonFromMarkdown(rawText);
|
|
100673
|
-
}
|
|
100674
|
-
logger.log("JSON_EXTRACTION", {
|
|
100675
|
-
rawTextLength: rawText.length,
|
|
100676
|
-
extractedJsonLength: jsonText.length
|
|
100677
|
-
});
|
|
100678
|
-
const aiGeneratedContent = GenerateQuizPlanOutputSchema.parse(JSON.parse(jsonText));
|
|
100679
|
-
logger.log("SCHEMA_VALIDATION", { success: true }, Date.now() - processingStartTime);
|
|
100680
|
-
const validationStartTime = Date.now();
|
|
100681
|
-
if (aiGeneratedContent.quizPlan.length !== clientInput.totalQuestions) {
|
|
100682
|
-
throw new Error(`AI planned for ${aiGeneratedContent.quizPlan.length} questions, but ${clientInput.totalQuestions} were requested.`);
|
|
100683
|
-
}
|
|
100684
|
-
const invalidTypes = [];
|
|
100685
|
-
aiGeneratedContent.quizPlan.forEach((item, index3) => {
|
|
100686
|
-
if (!clientInput.selectedQuestionTypes.includes(item.plannedQuestionType)) {
|
|
100687
|
-
invalidTypes.push(`Question ${index3 + 1}: '${item.plannedQuestionType}'`);
|
|
100688
|
-
}
|
|
100689
|
-
});
|
|
100690
|
-
if (invalidTypes.length > 0) {
|
|
100691
|
-
throw new Error(`Invalid question types found: ${invalidTypes.join(", ")}`);
|
|
100692
|
-
}
|
|
100693
|
-
const codingQuestions = aiGeneratedContent.quizPlan.filter((q2) => q2.plannedQuestionType === "coding");
|
|
100694
|
-
if (numCodingQuestions > 0 && codingQuestions.length !== numCodingQuestions) {
|
|
100695
|
-
throw new Error(`Expected ${numCodingQuestions} coding questions, but got ${codingQuestions.length}`);
|
|
100696
|
-
}
|
|
100697
|
-
const diversityAnalysis = validateConsecutiveTypes(aiGeneratedContent.quizPlan);
|
|
100698
|
-
logger.log("VALIDATION_COMPLETE", {
|
|
100699
|
-
questionCount: aiGeneratedContent.quizPlan.length,
|
|
100700
|
-
codingQuestionCount: codingQuestions.length,
|
|
100701
|
-
maxConsecutiveType: diversityAnalysis.maxConsecutive,
|
|
100702
|
-
questionTypeDistribution: aiGeneratedContent.diversityMetrics?.questionTypeDistribution || {}
|
|
100703
|
-
}, Date.now() - validationStartTime);
|
|
100704
|
-
const finalResult = {
|
|
100705
|
-
...aiGeneratedContent,
|
|
100706
|
-
logs: logger.getLogs()
|
|
100707
|
-
};
|
|
100708
|
-
logger.log("GENERATION_COMPLETE", {
|
|
100709
|
-
totalDuration: logger.getTotalDuration(),
|
|
100710
|
-
success: true,
|
|
100711
|
-
finalQuestionCount: finalResult.quizPlan.length
|
|
100712
|
-
}, logger.getTotalDuration());
|
|
100713
|
-
console.log("\n=== QUIZ PLAN GENERATION SUMMARY ===");
|
|
100714
|
-
console.log(`\u2705 Successfully generated ${finalResult.quizPlan.length} questions`);
|
|
100715
|
-
console.log(`\u23F1\uFE0F Total generation time: ${logger.getTotalDuration()}ms`);
|
|
100716
|
-
console.log(`\u{1F3AF} Question types: ${Object.keys(finalResult.diversityMetrics?.questionTypeDistribution || {}).join(", ")}`);
|
|
100717
|
-
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(finalResult.diversityMetrics?.bloomLevelDistribution || {}).join(", ")}`);
|
|
100718
|
-
if (numCodingQuestions > 0) {
|
|
100719
|
-
console.log(`\u{1F4BB} Coding questions: ${codingQuestions.length}/${numCodingQuestions} required`);
|
|
100720
|
-
}
|
|
100721
|
-
console.log(JSON.stringify(finalResult));
|
|
100722
|
-
console.log("=====================================\n");
|
|
100723
|
-
return finalResult;
|
|
100724
|
-
} catch (error) {
|
|
100725
|
-
logger.log("ERROR", {
|
|
100726
|
-
message: error.message,
|
|
100727
|
-
stack: error.stack,
|
|
100728
|
-
totalDuration: logger.getTotalDuration()
|
|
100729
|
-
});
|
|
100730
|
-
console.error("\u274C Quiz Plan Generation Failed:", error.message);
|
|
100731
|
-
console.error("\u{1F4CB} Full logs available in returned object");
|
|
100732
|
-
throw new Error(`Failed to generate Quiz Plan: ${error.message}`);
|
|
100733
|
-
}
|
|
100734
|
-
}
|
|
100735
|
-
function validateConsecutiveTypes(quizPlan) {
|
|
100736
|
-
let maxConsecutive = 1;
|
|
100737
|
-
let maxType = quizPlan[0]?.plannedQuestionType || "";
|
|
100738
|
-
let maxStartIndex = 0;
|
|
100739
|
-
let currentConsecutive = 1;
|
|
100740
|
-
let currentType = quizPlan[0]?.plannedQuestionType || "";
|
|
100741
|
-
let currentStartIndex = 0;
|
|
100742
|
-
for (let i2 = 1; i2 < quizPlan.length; i2++) {
|
|
100743
|
-
if (quizPlan[i2].plannedQuestionType === currentType) {
|
|
100744
|
-
currentConsecutive++;
|
|
100745
|
-
} else {
|
|
100746
|
-
if (currentConsecutive > maxConsecutive) {
|
|
100747
|
-
maxConsecutive = currentConsecutive;
|
|
100748
|
-
maxType = currentType;
|
|
100749
|
-
maxStartIndex = currentStartIndex;
|
|
100750
|
-
}
|
|
100751
|
-
currentConsecutive = 1;
|
|
100752
|
-
currentType = quizPlan[i2].plannedQuestionType;
|
|
100753
|
-
currentStartIndex = i2;
|
|
100754
|
-
}
|
|
100755
|
-
}
|
|
100756
|
-
if (currentConsecutive > maxConsecutive) {
|
|
100757
|
-
maxConsecutive = currentConsecutive;
|
|
100758
|
-
maxType = currentType;
|
|
100759
|
-
maxStartIndex = currentStartIndex;
|
|
100760
|
-
}
|
|
100761
|
-
return {
|
|
100762
|
-
maxConsecutive,
|
|
100763
|
-
type: maxType,
|
|
100764
|
-
startIndex: maxStartIndex
|
|
100765
|
-
};
|
|
100766
|
-
}
|
|
100767
|
-
|
|
100768
|
-
// src/ai/flows/generate-questions-from-quiz-plan.ts
|
|
100769
|
-
init_react_shim();
|
|
100770
|
-
|
|
100771
|
-
// src/services/TopicDataService.ts
|
|
100772
|
-
init_react_shim();
|
|
100773
|
-
var TopicDataService = class {
|
|
100774
|
-
/**
|
|
100775
|
-
* Saves an array of LearningObjective objects to Local Storage, overwriting existing data.
|
|
100776
|
-
* @param data The array of learning objectives to save.
|
|
100777
|
-
*/
|
|
100778
|
-
static saveData(data) {
|
|
100779
|
-
try {
|
|
100780
|
-
if (typeof window === "undefined") return;
|
|
100781
|
-
const serializedData = JSON.stringify(data);
|
|
100782
|
-
localStorage.setItem(this.STORAGE_KEY, serializedData);
|
|
100783
|
-
} catch (error) {
|
|
100784
|
-
console.error("Error saving learning objectives to Local Storage:", error);
|
|
100785
|
-
}
|
|
100786
|
-
}
|
|
100787
|
-
/**
|
|
100788
|
-
* Merges a new set of learning objectives with the existing data in Local Storage.
|
|
100789
|
-
* If an LO ID from newData already exists, it will be updated. Otherwise, it will be added.
|
|
100790
|
-
* @param newData The array of new or updated learning objectives.
|
|
100791
|
-
*/
|
|
100792
|
-
static mergeData(newData) {
|
|
100793
|
-
const existingData = this.getData();
|
|
100794
|
-
const loMap = new Map(existingData.map((lo) => [lo.loId, lo]));
|
|
100795
|
-
newData.forEach((newLo) => {
|
|
100796
|
-
loMap.set(newLo.loId, newLo);
|
|
100797
|
-
});
|
|
100798
|
-
const mergedData = Array.from(loMap.values());
|
|
100799
|
-
this.saveData(mergedData);
|
|
100800
|
-
}
|
|
100801
|
-
/**
|
|
100802
|
-
* Retrieves the array of LearningObjective objects from Local Storage.
|
|
100803
|
-
* @returns An array of learning objectives, or an empty array if none are found or an error occurs.
|
|
100804
|
-
*/
|
|
100805
|
-
static getData() {
|
|
100806
|
-
try {
|
|
100807
|
-
if (typeof window === "undefined") return [];
|
|
100808
|
-
const storedData = localStorage.getItem(this.STORAGE_KEY);
|
|
100809
|
-
return storedData ? JSON.parse(storedData) : [];
|
|
100810
|
-
} catch (error) {
|
|
100811
|
-
console.error("Error retrieving learning objectives from Local Storage:", error);
|
|
100812
|
-
this.clearData();
|
|
100813
|
-
return [];
|
|
100814
|
-
}
|
|
100815
|
-
}
|
|
100816
|
-
/**
|
|
100817
|
-
* Removes all learning objective data from Local Storage.
|
|
100818
|
-
*/
|
|
100819
|
-
static clearData() {
|
|
100820
|
-
try {
|
|
100821
|
-
if (typeof window === "undefined") return;
|
|
100822
|
-
localStorage.removeItem(this.STORAGE_KEY);
|
|
100823
|
-
} catch (error) {
|
|
100824
|
-
console.error("Error clearing learning objectives from Local Storage:", error);
|
|
100825
|
-
}
|
|
100826
|
-
}
|
|
100827
|
-
/**
|
|
100828
|
-
* Parses TSV content into an array of LearningObjective objects.
|
|
100829
|
-
* @param tsvContent The raw string content from a .tsv file.
|
|
100830
|
-
* @returns An object containing the successfully parsed data and any errors encountered.
|
|
100831
|
-
*/
|
|
100832
|
-
static parseTSV(tsvContent) {
|
|
100833
|
-
const lines = tsvContent.split("\n").filter((line) => line.trim() !== "");
|
|
100834
|
-
if (lines.length < 2) {
|
|
100835
|
-
return { data: [], errors: ["File is empty or contains only a header."] };
|
|
100836
|
-
}
|
|
100837
|
-
const headerLine = lines.shift();
|
|
100838
|
-
const headers = headerLine.split(" ").map((h3) => h3.trim());
|
|
100839
|
-
if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
|
|
100840
|
-
const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
|
|
100841
|
-
return { data: [], errors: [errorMsg] };
|
|
100842
|
-
}
|
|
100843
|
-
const data = [];
|
|
100844
|
-
const errors2 = [];
|
|
100845
|
-
lines.forEach((line, index3) => {
|
|
100846
|
-
const values = line.split(" ").map((v) => v.trim());
|
|
100847
|
-
if (values.length !== this.EXPECTED_HEADERS.length) {
|
|
100848
|
-
errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
|
|
100849
|
-
return;
|
|
100850
|
-
}
|
|
100851
|
-
const [
|
|
100852
|
-
loId,
|
|
100853
|
-
loDescription,
|
|
100854
|
-
subject,
|
|
100855
|
-
category,
|
|
100856
|
-
topic,
|
|
100857
|
-
keywordsStr,
|
|
100858
|
-
grade,
|
|
100859
|
-
stemElementsStr,
|
|
100860
|
-
bloomLevelsStr
|
|
100861
|
-
] = values;
|
|
100862
|
-
if (!loId || !subject || !category || !topic) {
|
|
100863
|
-
errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, Subject, Category, or Topic).`);
|
|
100864
|
-
return;
|
|
100865
|
-
}
|
|
100866
|
-
const learningObjective = {
|
|
100867
|
-
loId,
|
|
100868
|
-
loDescription,
|
|
100869
|
-
subject,
|
|
100870
|
-
category,
|
|
100871
|
-
topic,
|
|
100872
|
-
keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
|
|
100873
|
-
grade,
|
|
100874
|
-
stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
|
|
100875
|
-
bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
|
|
100876
|
-
};
|
|
100877
|
-
data.push(learningObjective);
|
|
100878
|
-
});
|
|
100879
|
-
return { data, errors: errors2 };
|
|
100880
|
-
}
|
|
100881
|
-
/**
|
|
100882
|
-
* Gets a unique list of all subjects from the stored data.
|
|
100883
|
-
* @returns An array of subject strings.
|
|
100884
|
-
*/
|
|
100885
|
-
static getSubjects() {
|
|
100886
|
-
const data = this.getData();
|
|
100887
|
-
const subjects = data.map((item) => item.subject);
|
|
100888
|
-
return [...new Set(subjects)].sort();
|
|
100889
|
-
}
|
|
100890
|
-
/**
|
|
100891
|
-
* Gets a unique list of categories for a given subject.
|
|
100892
|
-
* @param subject The subject to filter by.
|
|
100893
|
-
* @returns An array of category strings.
|
|
100894
|
-
*/
|
|
100895
|
-
static getCategoriesBySubject(subject) {
|
|
100896
|
-
const data = this.getData();
|
|
100897
|
-
const categories = data.filter((item) => item.subject === subject).map((item) => item.category);
|
|
100898
|
-
return [...new Set(categories)].sort();
|
|
100899
|
-
}
|
|
100900
|
-
/**
|
|
100901
|
-
* Gets a unique list of topics for a given category.
|
|
100902
|
-
* @param category The category to filter by.
|
|
100903
|
-
* @returns An array of topic strings.
|
|
100904
|
-
*/
|
|
100905
|
-
static getTopicsByCategory(category) {
|
|
100906
|
-
const data = this.getData();
|
|
100907
|
-
const topics = data.filter((item) => item.category === category).map((item) => item.topic);
|
|
100908
|
-
return [...new Set(topics)].sort();
|
|
100909
|
-
}
|
|
100910
|
-
/**
|
|
100911
|
-
* Retrieves all LearningObjective details for a given list of topics.
|
|
100912
|
-
* @param topics An array of topic strings to search for.
|
|
100913
|
-
* @returns An array of matching LearningObjective objects.
|
|
100914
|
-
*/
|
|
100915
|
-
static getLearningObjectivesByTopics(topics) {
|
|
100916
|
-
const data = this.getData();
|
|
100917
|
-
const topicSet = new Set(topics);
|
|
100918
|
-
return data.filter((item) => topicSet.has(item.topic));
|
|
100919
|
-
}
|
|
100920
|
-
};
|
|
100921
|
-
TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
|
|
100922
|
-
TopicDataService.EXPECTED_HEADERS = [
|
|
100923
|
-
"LO ID",
|
|
100924
|
-
"LO Description",
|
|
100925
|
-
"Subject",
|
|
100926
|
-
"Category",
|
|
100927
|
-
"Topic",
|
|
100928
|
-
"Keywords",
|
|
100929
|
-
"Grade",
|
|
100930
|
-
"STEM Element(s)",
|
|
100931
|
-
"Bloom\u2019s Level(s) Guideline"
|
|
100932
|
-
];
|
|
100933
|
-
|
|
100934
100134
|
// src/ai/flows/question-gen/generate-mrq-question.ts
|
|
100935
100135
|
init_react_shim();
|
|
100936
100136
|
|
|
@@ -100947,7 +100147,7 @@ var AIMRQOutputFieldsSchema = zod.z.object({
|
|
|
100947
100147
|
correctTempOptionIds: zod.z.array(zod.z.string()).min(1),
|
|
100948
100148
|
explanation: zod.z.string().optional(),
|
|
100949
100149
|
points: zod.z.number().optional().default(10),
|
|
100950
|
-
difficulty: zod.z.enum(["
|
|
100150
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
100951
100151
|
topic: zod.z.string().optional(),
|
|
100952
100152
|
verifiedCategory: zod.z.string().optional().describe("The category this question actually addresses.")
|
|
100953
100153
|
});
|
|
@@ -100966,7 +100166,7 @@ Previous attempts failed due to validation errors. Pay close attention to the nu
|
|
|
100966
100166
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and options must be directly related to the content of this image.` : "";
|
|
100967
100167
|
const contextStrings = [
|
|
100968
100168
|
`**Required Category:** ${category} (This is the ONLY language to be used)`,
|
|
100969
|
-
quizContext?.
|
|
100169
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
100970
100170
|
imageContextInstruction,
|
|
100971
100171
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
100972
100172
|
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers (distractors). The misconception is: "${quizContext.targetMisconception}"`,
|
|
@@ -100984,7 +100184,7 @@ Previous attempts failed due to validation errors. Pay close attention to the nu
|
|
|
100984
100184
|
correctTempOptionIds: ["A", "C", "D"],
|
|
100985
100185
|
explanation: "Object-Oriented, Functional, and Procedural are all major programming paradigms. Assembly is a low-level language, and Middleware is a type of software, not a paradigm.",
|
|
100986
100186
|
points: 10,
|
|
100987
|
-
difficulty: "
|
|
100187
|
+
difficulty: "Medium",
|
|
100988
100188
|
topic: "Programming Paradigms",
|
|
100989
100189
|
verifiedCategory: category
|
|
100990
100190
|
}, null, 2);
|
|
@@ -101148,7 +100348,7 @@ var AIShortAnswerOutputFieldsSchema = zod.z.object({
|
|
|
101148
100348
|
// isCaseSensitive không cần thiết ở đây, chúng ta sẽ quản lý nó ở phía client
|
|
101149
100349
|
explanation: zod.z.string().optional(),
|
|
101150
100350
|
points: zod.z.number().optional().default(10),
|
|
101151
|
-
difficulty: zod.z.enum(["
|
|
100351
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101152
100352
|
topic: zod.z.string().optional(),
|
|
101153
100353
|
verifiedCategory: zod.z.string().optional()
|
|
101154
100354
|
// Thêm để xác thực
|
|
@@ -101168,7 +100368,7 @@ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strin
|
|
|
101168
100368
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and its short answer must be directly related to the content of this image.` : "";
|
|
101169
100369
|
const contextStrings = [
|
|
101170
100370
|
`**Required Category:** ${category}`,
|
|
101171
|
-
quizContext?.
|
|
100371
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101172
100372
|
imageContextInstruction,
|
|
101173
100373
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101174
100374
|
quizContext?.targetMisconception && `**Target Misconception:** The question should require an answer that corrects this specific misconception: "${quizContext.targetMisconception}"`
|
|
@@ -101178,7 +100378,7 @@ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strin
|
|
|
101178
100378
|
acceptedAnswers: ["let"],
|
|
101179
100379
|
explanation: "The 'let' keyword is used to declare constants, which are values that cannot be changed after they are set. 'var' is used for variables.",
|
|
101180
100380
|
points: 10,
|
|
101181
|
-
difficulty: "
|
|
100381
|
+
difficulty: "Easy",
|
|
101182
100382
|
topic: "Swift Constants",
|
|
101183
100383
|
verifiedCategory: category
|
|
101184
100384
|
}, null, 2);
|
|
@@ -101311,7 +100511,7 @@ var AINumericOutputFieldsSchema = zod.z.object({
|
|
|
101311
100511
|
tolerance: zod.z.number().min(0).optional(),
|
|
101312
100512
|
explanation: zod.z.string().optional(),
|
|
101313
100513
|
points: zod.z.number().optional().default(10),
|
|
101314
|
-
difficulty: zod.z.enum(["
|
|
100514
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101315
100515
|
topic: zod.z.string().optional(),
|
|
101316
100516
|
verifiedCategory: zod.z.string().optional()
|
|
101317
100517
|
// Thêm để xác thực
|
|
@@ -101331,7 +100531,7 @@ Previous attempts failed. Ensure the 'answer' is a valid number and fits within
|
|
|
101331
100531
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and its numerical answer must be directly related to the content of this image.` : "";
|
|
101332
100532
|
const contextStrings = [
|
|
101333
100533
|
`**Required Category:** ${category}`,
|
|
101334
|
-
quizContext?.
|
|
100534
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101335
100535
|
imageContextInstruction,
|
|
101336
100536
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101337
100537
|
quizContext?.targetMisconception && `**Target Misconception:** The question should clarify this numerical error: "${quizContext.targetMisconception}"`
|
|
@@ -101347,7 +100547,7 @@ Previous attempts failed. Ensure the 'answer' is a valid number and fits within
|
|
|
101347
100547
|
tolerance: 0,
|
|
101348
100548
|
explanation: "An Int8 uses 8 bits. One bit is for the sign, leaving 7 bits for the value. The range is from -128 to 127 (2^7 - 1).",
|
|
101349
100549
|
points: 10,
|
|
101350
|
-
difficulty: "
|
|
100550
|
+
difficulty: "Medium",
|
|
101351
100551
|
topic: "Swift Data Types",
|
|
101352
100552
|
verifiedCategory: category
|
|
101353
100553
|
}, null, 2);
|
|
@@ -101497,7 +100697,7 @@ var AIFillInTheBlanksOutputFieldsSchema = zod.z.object({
|
|
|
101497
100697
|
})).min(1).describe("An array of text and blank segments representing the question."),
|
|
101498
100698
|
explanation: zod.z.string().optional(),
|
|
101499
100699
|
points: zod.z.number().optional().default(10),
|
|
101500
|
-
difficulty: zod.z.enum(["
|
|
100700
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101501
100701
|
topic: zod.z.string().optional(),
|
|
101502
100702
|
verifiedCategory: zod.z.string().optional()
|
|
101503
100703
|
// Thêm để xác thực
|
|
@@ -101517,7 +100717,7 @@ Previous attempts failed. Pay strict attention to the JSON schema, especially th
|
|
|
101517
100717
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The question and blanks must be directly related to the content of this image.` : "";
|
|
101518
100718
|
const contextStrings = [
|
|
101519
100719
|
`**Required Category:** ${category}`,
|
|
101520
|
-
quizContext?.
|
|
100720
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101521
100721
|
imageContextInstruction,
|
|
101522
100722
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101523
100723
|
quizContext?.targetMisconception && `**Target Misconception:** Design the blank to test this specific point: "${quizContext.targetMisconception}"`
|
|
@@ -101531,7 +100731,7 @@ Previous attempts failed. Pay strict attention to the JSON schema, especially th
|
|
|
101531
100731
|
],
|
|
101532
100732
|
explanation: "The 'func' keyword is used to declare a function in the Swift programming language.",
|
|
101533
100733
|
points: 10,
|
|
101534
|
-
difficulty: "
|
|
100734
|
+
difficulty: "Easy",
|
|
101535
100735
|
topic: "Swift Function Declaration",
|
|
101536
100736
|
verifiedCategory: category
|
|
101537
100737
|
}, null, 2);
|
|
@@ -101685,7 +100885,7 @@ var AISequenceOutputFieldsSchema = zod.z.object({
|
|
|
101685
100885
|
itemsInCorrectOrder: zod.z.array(zod.z.string().min(1)).min(2).describe("An array of strings, with each string representing an item to be sequenced. The array itself MUST be in the correct final order."),
|
|
101686
100886
|
explanation: zod.z.string().optional(),
|
|
101687
100887
|
points: zod.z.number().optional().default(10),
|
|
101688
|
-
difficulty: zod.z.enum(["
|
|
100888
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101689
100889
|
topic: zod.z.string().optional(),
|
|
101690
100890
|
verifiedCategory: zod.z.string().optional()
|
|
101691
100891
|
// Thêm để xác thực
|
|
@@ -101705,7 +100905,7 @@ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the
|
|
|
101705
100905
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The sequence of items must be directly related to the process or content shown in this image.` : "";
|
|
101706
100906
|
const contextStrings = [
|
|
101707
100907
|
`**Required Category:** ${category}`,
|
|
101708
|
-
quizContext?.
|
|
100908
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101709
100909
|
imageContextInstruction,
|
|
101710
100910
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101711
100911
|
quizContext?.targetMisconception && `**Target Misconception:** The sequence should clarify this specific process error: "${quizContext.targetMisconception}"`
|
|
@@ -101720,7 +100920,7 @@ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the
|
|
|
101720
100920
|
],
|
|
101721
100921
|
explanation: "This is the fundamental sequence for a basic data task in URLSession.",
|
|
101722
100922
|
points: 10,
|
|
101723
|
-
difficulty: "
|
|
100923
|
+
difficulty: "Medium",
|
|
101724
100924
|
topic: "Swift Networking",
|
|
101725
100925
|
verifiedCategory: category
|
|
101726
100926
|
}, null, 2);
|
|
@@ -101862,7 +101062,7 @@ var AIMatchingOutputFieldsSchema = zod.z.object({
|
|
|
101862
101062
|
})).min(2),
|
|
101863
101063
|
explanation: zod.z.string().optional(),
|
|
101864
101064
|
points: zod.z.number().optional().default(10),
|
|
101865
|
-
difficulty: zod.z.enum(["
|
|
101065
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101866
101066
|
topic: zod.z.string().optional(),
|
|
101867
101067
|
verifiedCategory: zod.z.string().optional()
|
|
101868
101068
|
// Thêm để xác thực
|
|
@@ -101882,7 +101082,7 @@ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the
|
|
|
101882
101082
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The matching pairs must be directly related to the content of this image.` : "";
|
|
101883
101083
|
const contextStrings = [
|
|
101884
101084
|
`**Required Category:** ${category}`,
|
|
101885
|
-
quizContext?.
|
|
101085
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101886
101086
|
imageContextInstruction,
|
|
101887
101087
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101888
101088
|
quizContext?.targetMisconception && `**Target Misconception:** Design a pair that specifically tests this confusion: "${quizContext.targetMisconception}"`
|
|
@@ -101896,7 +101096,7 @@ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the
|
|
|
101896
101096
|
],
|
|
101897
101097
|
explanation: "These are the fundamental characteristics of Swift's main collection types.",
|
|
101898
101098
|
points: 10,
|
|
101899
|
-
difficulty: "
|
|
101099
|
+
difficulty: "Easy",
|
|
101900
101100
|
topic: "Swift Collection Types",
|
|
101901
101101
|
verifiedCategory: category
|
|
101902
101102
|
}, null, 2);
|
|
@@ -102026,6 +101226,821 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
102026
101226
|
return { error: errorMessage };
|
|
102027
101227
|
}
|
|
102028
101228
|
|
|
101229
|
+
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
101230
|
+
var supportedQuestionTypesForAI = [
|
|
101231
|
+
{ value: "true_false", label: "True/False" },
|
|
101232
|
+
{ value: "multiple_choice", label: "Multiple Choice" },
|
|
101233
|
+
{ value: "multiple_response", label: "Multiple Response" },
|
|
101234
|
+
{ value: "short_answer", label: "Short Answer" },
|
|
101235
|
+
{ value: "numeric", label: "Numeric" },
|
|
101236
|
+
{ value: "fill_in_the_blanks", label: "Fill In The Blanks" },
|
|
101237
|
+
{ value: "sequence", label: "Sequence" },
|
|
101238
|
+
{ value: "matching", label: "Matching" }
|
|
101239
|
+
];
|
|
101240
|
+
var AIQuestionGeneratorModal = ({
|
|
101241
|
+
isOpen,
|
|
101242
|
+
onClose,
|
|
101243
|
+
onQuestionGenerated,
|
|
101244
|
+
language: language3,
|
|
101245
|
+
questionType: questionTypeProp,
|
|
101246
|
+
subjects = [],
|
|
101247
|
+
topics = [],
|
|
101248
|
+
gradeLevels = [],
|
|
101249
|
+
bloomLevels = []
|
|
101250
|
+
}) => {
|
|
101251
|
+
const [prompt, setPrompt] = React169.useState("");
|
|
101252
|
+
const [isLoading, setIsLoading] = React169.useState(false);
|
|
101253
|
+
const [error, setError] = React169.useState(null);
|
|
101254
|
+
const { toast: toast2 } = useToast();
|
|
101255
|
+
const [subjectCode, setSubjectCode] = React169.useState("");
|
|
101256
|
+
const [topicCode, setTopicCode] = React169.useState("");
|
|
101257
|
+
const [gradeBand, setGradeBand] = React169.useState("");
|
|
101258
|
+
const [bloomLevelCode, setBloomLevelCode] = React169.useState("");
|
|
101259
|
+
const [selectedQuestionType, setSelectedQuestionType] = React169.useState("multiple_choice");
|
|
101260
|
+
const [numberOfOptions, setNumberOfOptions] = React169.useState(4);
|
|
101261
|
+
const [minCorrectAnswers, setMinCorrectAnswers] = React169.useState(2);
|
|
101262
|
+
const [maxCorrectAnswers, setMaxCorrectAnswers] = React169.useState(3);
|
|
101263
|
+
const [isCaseSensitive, setIsCaseSensitive] = React169.useState(false);
|
|
101264
|
+
const [numberOfBlanks, setNumberOfBlanks] = React169.useState(2);
|
|
101265
|
+
const [numberOfSequenceItems, setNumberOfSequenceItems] = React169.useState(4);
|
|
101266
|
+
const [numberOfMatchingPairs, setNumberOfMatchingPairs] = React169.useState(4);
|
|
101267
|
+
const [isApiKeyManagerModalOpen, setIsApiKeyManagerModalOpen] = React169.useState(false);
|
|
101268
|
+
const [geminiApiKeyExists, setGeminiApiKeyExists] = React169.useState(false);
|
|
101269
|
+
const finalQuestionType = questionTypeProp || selectedQuestionType;
|
|
101270
|
+
React169.useEffect(() => {
|
|
101271
|
+
if (isOpen) {
|
|
101272
|
+
setPrompt("");
|
|
101273
|
+
setError(null);
|
|
101274
|
+
setIsLoading(false);
|
|
101275
|
+
setSubjectCode("");
|
|
101276
|
+
setTopicCode("");
|
|
101277
|
+
setGradeBand("");
|
|
101278
|
+
setBloomLevelCode("");
|
|
101279
|
+
setSelectedQuestionType(questionTypeProp || "multiple_choice");
|
|
101280
|
+
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
101281
|
+
}
|
|
101282
|
+
}, [isOpen, questionTypeProp]);
|
|
101283
|
+
const filteredTopics = React169.useMemo(() => {
|
|
101284
|
+
if (!subjectCode) return [];
|
|
101285
|
+
return topics.filter((t4) => t4.subjectCode === subjectCode);
|
|
101286
|
+
}, [subjectCode, topics]);
|
|
101287
|
+
const handleApiKeyModalClose = () => {
|
|
101288
|
+
setIsApiKeyManagerModalOpen(false);
|
|
101289
|
+
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
101290
|
+
};
|
|
101291
|
+
const handleSubmit = async () => {
|
|
101292
|
+
if (!prompt.trim()) {
|
|
101293
|
+
setError("Please provide a prompt for the question.");
|
|
101294
|
+
return;
|
|
101295
|
+
}
|
|
101296
|
+
const geminiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
|
|
101297
|
+
if (!geminiKey) {
|
|
101298
|
+
setError("Gemini API Key is not set. Please configure it.");
|
|
101299
|
+
setGeminiApiKeyExists(false);
|
|
101300
|
+
return;
|
|
101301
|
+
}
|
|
101302
|
+
setGeminiApiKeyExists(true);
|
|
101303
|
+
setError(null);
|
|
101304
|
+
setIsLoading(true);
|
|
101305
|
+
try {
|
|
101306
|
+
const quizContext = {
|
|
101307
|
+
plannedTopic: prompt,
|
|
101308
|
+
originalSubject: subjectCode,
|
|
101309
|
+
originalTopic: topicCode,
|
|
101310
|
+
gradeBand,
|
|
101311
|
+
plannedBloomLevel: bloomLevelCode
|
|
101312
|
+
};
|
|
101313
|
+
const baseClientInput = {
|
|
101314
|
+
language: language3,
|
|
101315
|
+
difficulty: "Medium",
|
|
101316
|
+
quizContext
|
|
101317
|
+
};
|
|
101318
|
+
let generatedResult = {};
|
|
101319
|
+
switch (finalQuestionType) {
|
|
101320
|
+
case "true_false":
|
|
101321
|
+
generatedResult = await generateTrueFalseQuestion(baseClientInput, geminiKey);
|
|
101322
|
+
break;
|
|
101323
|
+
case "multiple_choice":
|
|
101324
|
+
generatedResult = await generateMCQQuestion({ ...baseClientInput, numberOfOptions }, geminiKey);
|
|
101325
|
+
break;
|
|
101326
|
+
case "multiple_response":
|
|
101327
|
+
generatedResult = await generateMRQQuestion({ ...baseClientInput, numberOfOptions, minCorrectAnswers, maxCorrectAnswers }, geminiKey);
|
|
101328
|
+
break;
|
|
101329
|
+
case "short_answer":
|
|
101330
|
+
generatedResult = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive }, geminiKey);
|
|
101331
|
+
break;
|
|
101332
|
+
case "numeric":
|
|
101333
|
+
generatedResult = await generateNumericQuestion({ ...baseClientInput, allowDecimals: true, tolerance: 0 }, geminiKey);
|
|
101334
|
+
break;
|
|
101335
|
+
case "fill_in_the_blanks":
|
|
101336
|
+
generatedResult = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks, isCaseSensitive }, geminiKey);
|
|
101337
|
+
break;
|
|
101338
|
+
case "sequence":
|
|
101339
|
+
generatedResult = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: numberOfSequenceItems }, geminiKey);
|
|
101340
|
+
break;
|
|
101341
|
+
case "matching":
|
|
101342
|
+
generatedResult = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: numberOfMatchingPairs, shuffleOptions: true }, geminiKey);
|
|
101343
|
+
break;
|
|
101344
|
+
default:
|
|
101345
|
+
throw new Error(`AI generation for '${finalQuestionType}' is not implemented in this flow.`);
|
|
101346
|
+
}
|
|
101347
|
+
if (generatedResult.error) {
|
|
101348
|
+
throw new Error(generatedResult.error);
|
|
101349
|
+
}
|
|
101350
|
+
if (generatedResult.question) {
|
|
101351
|
+
const completeQuestion = generatedResult.question;
|
|
101352
|
+
completeQuestion.subject = subjectCode;
|
|
101353
|
+
completeQuestion.topic = topicCode;
|
|
101354
|
+
completeQuestion.gradeBand = gradeBand;
|
|
101355
|
+
completeQuestion.bloomLevel = bloomLevelCode;
|
|
101356
|
+
if (completeQuestion.points === void 0) completeQuestion.points = 10;
|
|
101357
|
+
onQuestionGenerated(completeQuestion);
|
|
101358
|
+
toast2({ title: "AI Question Generated", description: "Review and edit the generated question." });
|
|
101359
|
+
onClose();
|
|
101360
|
+
} else {
|
|
101361
|
+
throw new Error("AI did not return a valid question object.");
|
|
101362
|
+
}
|
|
101363
|
+
} catch (e3) {
|
|
101364
|
+
console.error("AI Question Generation Error:", e3);
|
|
101365
|
+
let errorMessage = e3.message || "An unknown error occurred.";
|
|
101366
|
+
if (typeof errorMessage === "string" && errorMessage.includes("API key not valid")) {
|
|
101367
|
+
errorMessage = "The provided Google Gemini API Key is invalid. Please check it.";
|
|
101368
|
+
setGeminiApiKeyExists(false);
|
|
101369
|
+
}
|
|
101370
|
+
setError(errorMessage);
|
|
101371
|
+
toast2({ title: "AI Generation Failed", description: errorMessage, variant: "destructive" });
|
|
101372
|
+
} finally {
|
|
101373
|
+
setIsLoading(false);
|
|
101374
|
+
}
|
|
101375
|
+
};
|
|
101376
|
+
const comboboxOptions = {
|
|
101377
|
+
subjects: subjects.map((s4) => ({ value: s4.code, label: s4.name })),
|
|
101378
|
+
topics: filteredTopics.map((t4) => ({ value: t4.code, label: t4.name })),
|
|
101379
|
+
gradeLevels: gradeLevels.map((g) => ({ value: g.code, label: g.name })),
|
|
101380
|
+
bloomLevels: bloomLevels.map((b2) => ({ value: b2.code, label: b2.name }))
|
|
101381
|
+
};
|
|
101382
|
+
const renderSpecificParams = () => {
|
|
101383
|
+
switch (finalQuestionType) {
|
|
101384
|
+
case "multiple_choice":
|
|
101385
|
+
case "multiple_response":
|
|
101386
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-num-options" }, "Number of Options (2-8)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
101387
|
+
Input,
|
|
101388
|
+
{
|
|
101389
|
+
id: "ai-num-options",
|
|
101390
|
+
type: "number",
|
|
101391
|
+
value: numberOfOptions,
|
|
101392
|
+
onChange: (e3) => setNumberOfOptions(parseInt(e3.target.value, 10)),
|
|
101393
|
+
min: 2,
|
|
101394
|
+
max: 8
|
|
101395
|
+
}
|
|
101396
|
+
), finalQuestionType === "multiple_response" && /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-min-correct" }, "Min Correct Answers"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "ai-min-correct", type: "number", value: minCorrectAnswers, onChange: (e3) => setMinCorrectAnswers(parseInt(e3.target.value, 10)), min: 1, max: numberOfOptions }), /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-max-correct" }, "Max Correct Answers"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "ai-max-correct", type: "number", value: maxCorrectAnswers, onChange: (e3) => setMaxCorrectAnswers(parseInt(e3.target.value, 10)), min: minCorrectAnswers, max: numberOfOptions })));
|
|
101397
|
+
case "short_answer":
|
|
101398
|
+
case "fill_in_the_blanks":
|
|
101399
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-2 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement("input", { type: "checkbox", id: "ai-case-sensitive", checked: isCaseSensitive, onChange: (e3) => setIsCaseSensitive(e3.target.checked), className: "h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary" }), /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-case-sensitive" }, "Case Sensitive Answers"), finalQuestionType === "fill_in_the_blanks" && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1 ml-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-num-blanks" }, "Number of Blanks (1-5)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "ai-num-blanks", type: "number", value: numberOfBlanks, onChange: (e3) => setNumberOfBlanks(parseInt(e3.target.value, 10)), min: 1, max: 5 })));
|
|
101400
|
+
case "sequence":
|
|
101401
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-num-seq-items" }, "Number of Items to Sequence (2-10)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "ai-num-seq-items", type: "number", value: numberOfSequenceItems, onChange: (e3) => setNumberOfSequenceItems(parseInt(e3.target.value, 10)), min: 2, max: 10 }));
|
|
101402
|
+
case "matching":
|
|
101403
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-num-match-pairs" }, "Number of Pairs to Match (2-8)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "ai-num-match-pairs", type: "number", value: numberOfMatchingPairs, onChange: (e3) => setNumberOfMatchingPairs(parseInt(e3.target.value, 10)), min: 2, max: 8 }));
|
|
101404
|
+
default:
|
|
101405
|
+
return null;
|
|
101406
|
+
}
|
|
101407
|
+
};
|
|
101408
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
101409
|
+
if (!open) onClose();
|
|
101410
|
+
} }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-[600px]" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center font-headline text-2xl" }, /* @__PURE__ */ React169__namespace.default.createElement(WandSparkles, { className: "mr-2 h-6 w-6 text-primary" }), " AI Question Generator"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Provide a prompt and metadata to generate a '", finalQuestionType, "' question.")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 py-4 max-h-[60vh] overflow-y-auto px-2" }, !geminiApiKeyExists && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-3 mb-4 border border-amber-500 bg-amber-50 rounded-md text-amber-700" }), !questionTypeProp && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-q-type-select" }, "Question Type"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: selectedQuestionType, onValueChange: (v) => setSelectedQuestionType(v) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "ai-q-type-select" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, supportedQuestionTypesForAI.map((type) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: type.value, value: type.value }, type.label))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "ai-prompt" }, "Prompt / Topic"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
101411
|
+
Textarea,
|
|
101412
|
+
{
|
|
101413
|
+
id: "ai-prompt",
|
|
101414
|
+
value: prompt,
|
|
101415
|
+
onChange: (e3) => setPrompt(e3.target.value),
|
|
101416
|
+
placeholder: "e.g., The process of photosynthesis, The causes of World War II, Basic Algebra Equations",
|
|
101417
|
+
className: "min-h-[100px]"
|
|
101418
|
+
}
|
|
101419
|
+
)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.subjects, value: subjectCode, onChange: setSubjectCode, placeholder: "Select or type a Subject..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.topics, value: topicCode, onChange: setTopicCode, placeholder: "Select or type a Topic...", disabled: !subjectCode })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Grade Level"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.gradeLevels, value: gradeBand, onChange: setGradeBand, placeholder: "Select or type a Grade..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Bloom's Level"), /* @__PURE__ */ React169__namespace.default.createElement(EditableCombobox, { options: comboboxOptions.bloomLevels, value: bloomLevelCode, onChange: setBloomLevelCode, placeholder: "Select a Bloom's Level..." }))), renderSpecificParams(), error && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-destructive flex items-center mt-2" }, /* @__PURE__ */ React169__namespace.default.createElement(TriangleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", onClick: handleSubmit, disabled: isLoading || !prompt.trim() || !geminiApiKeyExists }, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(WandSparkles, { className: "mr-2 h-4 w-4" }), "Generate Question")))), /* @__PURE__ */ React169__namespace.default.createElement(APIKeyManagerModal, { isOpen: isApiKeyManagerModalOpen, onClose: handleApiKeyModalClose }));
|
|
101420
|
+
};
|
|
101421
|
+
|
|
101422
|
+
// src/react-ui/components/authoring/AIFullQuizGeneratorModal.tsx
|
|
101423
|
+
init_react_shim();
|
|
101424
|
+
|
|
101425
|
+
// src/ai/flows/generate-quiz-plan.ts
|
|
101426
|
+
init_react_shim();
|
|
101427
|
+
|
|
101428
|
+
// src/ai/flows/generate-quiz-plan-types.ts
|
|
101429
|
+
init_react_shim();
|
|
101430
|
+
var TopicWithMetadataSchema = zod.z.object({
|
|
101431
|
+
topic: zod.z.string().min(1),
|
|
101432
|
+
ratio: zod.z.number().min(0).max(100),
|
|
101433
|
+
originalLoId: zod.z.string().optional(),
|
|
101434
|
+
originalSubject: zod.z.string().optional(),
|
|
101435
|
+
originalCategory: zod.z.string().optional(),
|
|
101436
|
+
originalTopic: zod.z.string().optional(),
|
|
101437
|
+
commonMisconceptions: zod.z.array(zod.z.string()).optional()
|
|
101438
|
+
});
|
|
101439
|
+
var BloomLevelStringsEnum = zod.z.enum(["remembering", "understanding", "applying", "analyzing", "evaluating", "creating"]);
|
|
101440
|
+
var fullQuizSupportedQuestionTypesArray = [
|
|
101441
|
+
"true_false",
|
|
101442
|
+
"multiple_choice",
|
|
101443
|
+
"multiple_response",
|
|
101444
|
+
"short_answer",
|
|
101445
|
+
"numeric",
|
|
101446
|
+
"fill_in_the_blanks",
|
|
101447
|
+
"sequence",
|
|
101448
|
+
"matching",
|
|
101449
|
+
"drag_and_drop",
|
|
101450
|
+
"coding"
|
|
101451
|
+
];
|
|
101452
|
+
zod.z.object({
|
|
101453
|
+
language: zod.z.string().optional().default("English"),
|
|
101454
|
+
totalQuestions: zod.z.number().int().min(1).max(50),
|
|
101455
|
+
numCodingQuestions: zod.z.number().optional().default(0),
|
|
101456
|
+
topics: zod.z.array(TopicWithMetadataSchema).min(1),
|
|
101457
|
+
bloomLevels: zod.z.array(zod.z.object({
|
|
101458
|
+
level: BloomLevelStringsEnum,
|
|
101459
|
+
ratio: zod.z.number().min(0).max(100)
|
|
101460
|
+
})).min(1),
|
|
101461
|
+
selectedContextIds: zod.z.array(zod.z.string()).optional(),
|
|
101462
|
+
selectedQuestionTypes: zod.z.array(zod.z.enum(fullQuizSupportedQuestionTypesArray)).min(1),
|
|
101463
|
+
imageContexts: zod.z.array(zod.z.custom()).optional().describe("Library of available image contexts for the AI to use.")
|
|
101464
|
+
});
|
|
101465
|
+
var PlannedQuestionSchema = zod.z.object({
|
|
101466
|
+
plannedTopic: zod.z.string().min(1).describe("The specific, assessable topic for this question."),
|
|
101467
|
+
plannedQuestionType: zod.z.enum(fullQuizSupportedQuestionTypesArray).describe("The specific question type chosen."),
|
|
101468
|
+
plannedBloomLevel: BloomLevelStringsEnum.describe("The Bloom's level assigned."),
|
|
101469
|
+
plannedContextId: zod.z.string().optional().describe("The specific context ID chosen for this question."),
|
|
101470
|
+
imageId: zod.z.string().nullable().optional().describe("The ID of the image from the context library to be used for this question."),
|
|
101471
|
+
targetMisconception: zod.z.string().optional().describe("A specific common misconception this question should target."),
|
|
101472
|
+
difficultyReason: zod.z.string().optional().describe("Strategic explanation of difficulty choice and placement."),
|
|
101473
|
+
topicSpecificity: zod.z.enum(["broad", "focused", "specific"]).optional().describe("How specific the topic coverage should be."),
|
|
101474
|
+
originalLoId: zod.z.string().optional(),
|
|
101475
|
+
originalSubject: zod.z.string().optional(),
|
|
101476
|
+
originalCategory: zod.z.string().optional(),
|
|
101477
|
+
originalTopic: zod.z.string().optional()
|
|
101478
|
+
});
|
|
101479
|
+
var GenerateQuizPlanOutputSchema = zod.z.object({
|
|
101480
|
+
quizPlan: zod.z.array(PlannedQuestionSchema).describe("A detailed plan for each question in the quiz."),
|
|
101481
|
+
diversityMetrics: zod.z.object({
|
|
101482
|
+
questionTypeDistribution: zod.z.record(zod.z.number()).optional(),
|
|
101483
|
+
bloomLevelDistribution: zod.z.record(zod.z.number()).optional(),
|
|
101484
|
+
maxConsecutiveSameType: zod.z.number().optional()
|
|
101485
|
+
}).optional().describe("Metrics showing the diversity achieved in the plan."),
|
|
101486
|
+
planningStrategy: zod.z.object({
|
|
101487
|
+
overallApproach: zod.z.string().optional(),
|
|
101488
|
+
keyDecisions: zod.z.array(zod.z.string()).optional()
|
|
101489
|
+
}).optional()
|
|
101490
|
+
});
|
|
101491
|
+
|
|
101492
|
+
// src/ai/flows/generate-quiz-plan.ts
|
|
101493
|
+
var QuizPlanLogger = class {
|
|
101494
|
+
constructor() {
|
|
101495
|
+
this.logs = [];
|
|
101496
|
+
this.startTime = Date.now();
|
|
101497
|
+
}
|
|
101498
|
+
log(phase, data, duration) {
|
|
101499
|
+
this.logs.push({
|
|
101500
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101501
|
+
phase,
|
|
101502
|
+
data,
|
|
101503
|
+
duration
|
|
101504
|
+
});
|
|
101505
|
+
if (duration !== void 0) {
|
|
101506
|
+
console.log(`[${phase}] Completed in ${duration}ms:`, data);
|
|
101507
|
+
} else {
|
|
101508
|
+
console.log(`[${phase}]:`, data);
|
|
101509
|
+
}
|
|
101510
|
+
}
|
|
101511
|
+
getLogs() {
|
|
101512
|
+
return this.logs;
|
|
101513
|
+
}
|
|
101514
|
+
getTotalDuration() {
|
|
101515
|
+
return Date.now() - this.startTime;
|
|
101516
|
+
}
|
|
101517
|
+
};
|
|
101518
|
+
function generateQuestionTypeSelectionGuidance() {
|
|
101519
|
+
return `
|
|
101520
|
+
QUESTION TYPE SELECTION BEST PRACTICES:
|
|
101521
|
+
|
|
101522
|
+
**TRUE_FALSE (true_false)**
|
|
101523
|
+
- Best for: Binary concepts, fact verification, common misconceptions
|
|
101524
|
+
- Bloom levels: Primarily Remembering, Understanding
|
|
101525
|
+
- Use when: Testing definitive statements, clarifying misconceptions
|
|
101526
|
+
- Example: "Photosynthesis only occurs during daytime" (targets timing misconception)
|
|
101527
|
+
|
|
101528
|
+
**MULTIPLE CHOICE (multiple_choice)**
|
|
101529
|
+
- Best for: Concept selection, process understanding, comparison
|
|
101530
|
+
- Bloom levels: All levels, especially Understanding and Applying
|
|
101531
|
+
- Use when: Testing conceptual understanding with clear alternatives
|
|
101532
|
+
- Example: "Which factor most affects enzyme activity?" (applying knowledge)
|
|
101533
|
+
|
|
101534
|
+
**MULTIPLE RESPONSE (multiple_response)**
|
|
101535
|
+
- Best for: Identifying multiple correct factors, comprehensive understanding
|
|
101536
|
+
- Bloom levels: Understanding, Analyzing, Evaluating
|
|
101537
|
+
- Use when: Multiple correct answers exist, testing thorough knowledge
|
|
101538
|
+
- Example: "Select all factors that influence plant growth" (analyzing components)
|
|
101539
|
+
|
|
101540
|
+
**FILL IN THE BLANKS (fill_in_the_blanks)**
|
|
101541
|
+
- Best for: Key terminology, formulas, specific facts
|
|
101542
|
+
- Bloom levels: Remembering, Understanding
|
|
101543
|
+
- Use when: Testing precise recall of important terms/concepts
|
|
101544
|
+
- Example: "The process of _____ converts light energy to chemical energy"
|
|
101545
|
+
|
|
101546
|
+
**NUMERIC (numeric)**
|
|
101547
|
+
- Best for: Calculations, quantitative problems, formula application
|
|
101548
|
+
- Bloom levels: Applying, Analyzing
|
|
101549
|
+
- Use when: Mathematical computations are required
|
|
101550
|
+
- Example: "Calculate the molarity of a 2L solution containing 0.5 moles of NaCl"
|
|
101551
|
+
|
|
101552
|
+
**MATCHING (matching)**
|
|
101553
|
+
- Best for: Connecting related concepts, terminology pairs
|
|
101554
|
+
- Bloom levels: Remembering, Understanding
|
|
101555
|
+
- Use when: Testing relationships between terms and definitions
|
|
101556
|
+
- Example: Match organelles with their functions
|
|
101557
|
+
|
|
101558
|
+
**SEQUENCE (sequence)**
|
|
101559
|
+
- Best for: Process steps, chronological order, procedural knowledge
|
|
101560
|
+
- Bloom levels: Understanding, Applying, Analyzing
|
|
101561
|
+
- Use when: Order or sequence is critical to understanding
|
|
101562
|
+
- Example: "Arrange the steps of mitosis in correct order"
|
|
101563
|
+
|
|
101564
|
+
**DRAG AND DROP (drag_and_drop)**
|
|
101565
|
+
- Best for: Categorization, classification, spatial relationships
|
|
101566
|
+
- Bloom levels: Understanding, Applying, Analyzing
|
|
101567
|
+
- Use when: Grouping or organizing information is key
|
|
101568
|
+
- Example: "Classify these compounds as acids, bases, or neutral"
|
|
101569
|
+
|
|
101570
|
+
**SHORT ANSWER (short_answer)**
|
|
101571
|
+
- Best for: Explanations, definitions, problem-solving steps
|
|
101572
|
+
- Bloom levels: Understanding, Applying, Analyzing, Evaluating, Creating
|
|
101573
|
+
- Use when: Requiring explanatory responses, open-ended thinking
|
|
101574
|
+
- Example: "Explain why enzymes are specific to certain substrates"
|
|
101575
|
+
|
|
101576
|
+
**CODING (coding)**
|
|
101577
|
+
- Best for: Programming problems, algorithm implementation
|
|
101578
|
+
- Bloom levels: Applying, Analyzing, Evaluating, Creating
|
|
101579
|
+
- Use when: Technical implementation or code analysis is required
|
|
101580
|
+
- Example: "Write a function to calculate factorial recursively"
|
|
101581
|
+
`;
|
|
101582
|
+
}
|
|
101583
|
+
function generateAdvancedBloomGuidance() {
|
|
101584
|
+
return `
|
|
101585
|
+
ADVANCED BLOOM'S TAXONOMY & QUESTION TYPE OPTIMIZATION:
|
|
101586
|
+
|
|
101587
|
+
**REMEMBERING (Knowledge Recall)**
|
|
101588
|
+
- Primary types: true_false, fill_in_the_blanks, matching
|
|
101589
|
+
- Secondary types: multiple_choice (simple recall)
|
|
101590
|
+
- Focus: Facts, terms, basic concepts, definitions
|
|
101591
|
+
- Misconception addressing: Use true_false to clarify common confusions
|
|
101592
|
+
|
|
101593
|
+
**UNDERSTANDING (Comprehension)**
|
|
101594
|
+
- Primary types: multiple_choice, short_answer, matching
|
|
101595
|
+
- Secondary types: multiple_response, sequence
|
|
101596
|
+
- Focus: Explanations, interpretations, examples, classifications
|
|
101597
|
+
- Best for: "Explain why...", "What does this mean...", "Give an example..."
|
|
101598
|
+
|
|
101599
|
+
**APPLYING (Using Knowledge)**
|
|
101600
|
+
- Primary types: numeric, short_answer, sequence, coding
|
|
101601
|
+
- Secondary types: multiple_choice, drag_and_drop
|
|
101602
|
+
- Focus: Problem-solving, implementing procedures, using methods
|
|
101603
|
+
- Best for: Calculations, step-by-step processes, practical applications
|
|
101604
|
+
|
|
101605
|
+
**ANALYZING (Breaking Down Information)**
|
|
101606
|
+
- Primary types: multiple_response, short_answer, sequence, coding
|
|
101607
|
+
- Secondary types: drag_and_drop, multiple_choice
|
|
101608
|
+
- Focus: Identifying components, relationships, cause-effect
|
|
101609
|
+
- Best for: "What are the factors...", "How do these relate...", "Break down..."
|
|
101610
|
+
|
|
101611
|
+
**EVALUATING (Making Judgments)**
|
|
101612
|
+
- Primary types: short_answer, multiple_response, coding
|
|
101613
|
+
- Secondary types: multiple_choice (with justification)
|
|
101614
|
+
- Focus: Critiquing, judging, comparing alternatives, decision-making
|
|
101615
|
+
- Best for: "Which is better and why...", "Evaluate the approach...", "Critique..."
|
|
101616
|
+
|
|
101617
|
+
**CREATING (Producing New Work)**
|
|
101618
|
+
- Primary types: short_answer, coding, sequence
|
|
101619
|
+
- Secondary types: drag_and_drop (design tasks)
|
|
101620
|
+
- Focus: Designing, planning, producing, constructing
|
|
101621
|
+
- Best for: "Design a solution...", "Create a plan...", "Develop a strategy..."
|
|
101622
|
+
`;
|
|
101623
|
+
}
|
|
101624
|
+
function generateDiversityRules() {
|
|
101625
|
+
return `
|
|
101626
|
+
ENHANCED DIVERSITY & QUALITY ASSURANCE RULES:
|
|
101627
|
+
|
|
101628
|
+
**Question Type Distribution Strategy:**
|
|
101629
|
+
1. Never place more than 3 consecutive questions of the same type
|
|
101630
|
+
2. Distribute question types based on their cognitive complexity
|
|
101631
|
+
3. For quizzes with 10+ questions, use at least 4 different question types
|
|
101632
|
+
4. For quizzes with 20+ questions, use at least 6 different question types
|
|
101633
|
+
5. Balance quick-answer types (true_false, multiple_choice) with deeper types (short_answer, coding)
|
|
101634
|
+
|
|
101635
|
+
**Intelligent Difficulty Progression:**
|
|
101636
|
+
1. Opening (20%): Start with confidence-building questions (Remembering/Understanding)
|
|
101637
|
+
2. Building (40%): Gradually increase complexity (Understanding/Applying)
|
|
101638
|
+
3. Peak (30%): Most challenging questions (Analyzing/Evaluating/Creating)
|
|
101639
|
+
4. Closing (10%): Moderate challenge to end positively
|
|
101640
|
+
|
|
101641
|
+
**Misconception-Driven Planning:**
|
|
101642
|
+
- When common misconceptions are provided, prioritize question types that can effectively address them
|
|
101643
|
+
- Use true_false for binary misconceptions
|
|
101644
|
+
- Use multiple_choice for concept selection misconceptions
|
|
101645
|
+
- Use short_answer for complex misconceptions requiring explanation
|
|
101646
|
+
|
|
101647
|
+
**Contextual Intelligence:**
|
|
101648
|
+
- Programming/Technical topics: Favor coding, numeric, short_answer
|
|
101649
|
+
- Process-oriented topics: Favor sequence, short_answer, drag_and_drop
|
|
101650
|
+
- Conceptual topics: Favor multiple_choice, multiple_response, true_false
|
|
101651
|
+
- Factual topics: Favor fill_in_the_blanks, matching, true_false
|
|
101652
|
+
`;
|
|
101653
|
+
}
|
|
101654
|
+
async function generateQuizPlan(clientInput, apiKey, imageContexts = []) {
|
|
101655
|
+
const logger = new QuizPlanLogger();
|
|
101656
|
+
try {
|
|
101657
|
+
logger.log("VALIDATION_START", {
|
|
101658
|
+
totalQuestions: clientInput.totalQuestions,
|
|
101659
|
+
availableTypes: clientInput.selectedQuestionTypes,
|
|
101660
|
+
topicCount: clientInput.topics.length,
|
|
101661
|
+
bloomLevelCount: clientInput.bloomLevels.length
|
|
101662
|
+
});
|
|
101663
|
+
const totalTopicRatio = clientInput.topics.reduce((sum, t4) => sum + t4.ratio, 0);
|
|
101664
|
+
if (Math.abs(totalTopicRatio - 100) > 1) {
|
|
101665
|
+
throw new Error(`Total topic ratio must be 100%. Current sum: ${totalTopicRatio.toFixed(1)}%`);
|
|
101666
|
+
}
|
|
101667
|
+
const totalBloomRatio = clientInput.bloomLevels.reduce((sum, b2) => sum + b2.ratio, 0);
|
|
101668
|
+
if (Math.abs(totalBloomRatio - 100) > 1) {
|
|
101669
|
+
throw new Error(`Total Bloom level ratio must be 100%. Current sum: ${totalBloomRatio.toFixed(1)}%`);
|
|
101670
|
+
}
|
|
101671
|
+
logger.log("VALIDATION_SUCCESS", {
|
|
101672
|
+
topicRatioSum: totalTopicRatio,
|
|
101673
|
+
bloomRatioSum: totalBloomRatio
|
|
101674
|
+
});
|
|
101675
|
+
const aiStartTime = Date.now();
|
|
101676
|
+
const ai = new genai.GoogleGenAI({
|
|
101677
|
+
apiKey
|
|
101678
|
+
});
|
|
101679
|
+
const model = "gemini-2.5-pro";
|
|
101680
|
+
const config3 = {
|
|
101681
|
+
temperature: 0.8,
|
|
101682
|
+
thinkingConfig: {
|
|
101683
|
+
thinkingBudget: 4096
|
|
101684
|
+
},
|
|
101685
|
+
responseMimeType: "application/json"
|
|
101686
|
+
};
|
|
101687
|
+
logger.log("AI_INITIALIZATION", { model }, Date.now() - aiStartTime);
|
|
101688
|
+
const { language: language3, totalQuestions, numCodingQuestions = 0 } = clientInput;
|
|
101689
|
+
const promptStartTime = Date.now();
|
|
101690
|
+
const topicsDistribution = clientInput.topics.map((t4) => {
|
|
101691
|
+
let topicString = `- Topic Context: "${t4.topic}", LoId: "${t4.originalLoId || "nil"}", Subject: "${t4.originalSubject || "nil"}", Category: "${t4.originalCategory || "nil"}", Topic: "${t4.originalTopic || "nil"}", Ratio: ${t4.ratio}%`;
|
|
101692
|
+
if (t4.commonMisconceptions && t4.commonMisconceptions.length > 0) {
|
|
101693
|
+
topicString += `
|
|
101694
|
+
- Common Misconceptions: [${t4.commonMisconceptions.join(", ")}]`;
|
|
101695
|
+
}
|
|
101696
|
+
return topicString;
|
|
101697
|
+
}).join("\n ");
|
|
101698
|
+
const bloomDistribution = clientInput.bloomLevels.map(
|
|
101699
|
+
(b2) => `- Level: "${b2.level}", Ratio: ${b2.ratio}%`
|
|
101700
|
+
).join("\n ");
|
|
101701
|
+
let questionTypesForPrompt = [...clientInput.selectedQuestionTypes];
|
|
101702
|
+
if (numCodingQuestions === 0) {
|
|
101703
|
+
questionTypesForPrompt = questionTypesForPrompt.filter(
|
|
101704
|
+
(type) => type !== "short_answer" && type !== "coding"
|
|
101705
|
+
);
|
|
101706
|
+
}
|
|
101707
|
+
const allowedQuestionTypes = questionTypesForPrompt.map((t4) => `'${t4}'`).join(", ");
|
|
101708
|
+
const codingRequirement = numCodingQuestions > 0 ? `
|
|
101709
|
+
**CRITICAL CODING REQUIREMENT**: Exactly ${numCodingQuestions} questions in the plan MUST be of type 'coding'. These should typically be at 'applying' or higher Bloom levels and focus on implementation, debugging, or algorithm design.` : "";
|
|
101710
|
+
const imageContextSection = imageContexts && imageContexts.length > 0 ? `
|
|
101711
|
+
## AVAILABLE IMAGE CONTEXT LIBRARY
|
|
101712
|
+
You have access to a library of pre-described images. When planning a question, if its subject, category, and topic match an image in this library AND the image's description is relevant to the planned question's topic, you MUST include the corresponding \`imageId\` in your output. Otherwise, leave the \`imageId\` field null or omit it.
|
|
101713
|
+
|
|
101714
|
+
\`\`\`json
|
|
101715
|
+
${JSON.stringify(imageContexts.map((img) => ({ imageId: img.id, subject: img.subject, category: img.category, topic: img.topic, description: img.detailedDescription })), null, 2)}
|
|
101716
|
+
\`\`\`
|
|
101717
|
+
` : "";
|
|
101718
|
+
const enhancedPromptText = `You are an elite educational assessment architect with expertise in cognitive science and adaptive learning. Your mission is to create a strategically optimized quiz plan that maximizes learning effectiveness.
|
|
101719
|
+
|
|
101720
|
+
${generateQuestionTypeSelectionGuidance()}
|
|
101721
|
+
|
|
101722
|
+
${generateAdvancedBloomGuidance()}
|
|
101723
|
+
|
|
101724
|
+
${generateDiversityRules()}
|
|
101725
|
+
|
|
101726
|
+
## COMPREHENSIVE QUIZ REQUIREMENTS:
|
|
101727
|
+
|
|
101728
|
+
**Target Language**: ${language3}
|
|
101729
|
+
**Total Questions**: ${totalQuestions}${codingRequirement}
|
|
101730
|
+
|
|
101731
|
+
**Topic Distribution & Learning Context** (follow precisely):
|
|
101732
|
+
${topicsDistribution}
|
|
101733
|
+
|
|
101734
|
+
**Cognitive Complexity Distribution** (follow precisely):
|
|
101735
|
+
${bloomDistribution}
|
|
101736
|
+
|
|
101737
|
+
**Available Question Arsenal**: ${allowedQuestionTypes}
|
|
101738
|
+
|
|
101739
|
+
**Image Resources**: ${imageContextSection}
|
|
101740
|
+
|
|
101741
|
+
## STRATEGIC PLANNING METHODOLOGY:
|
|
101742
|
+
|
|
101743
|
+
1. **Misconception Analysis**: If common misconceptions are provided, design questions specifically to address and correct them
|
|
101744
|
+
2. **Question Type Intelligence**: Select question types based on the cognitive demands and content nature
|
|
101745
|
+
3. **Difficulty Orchestration**: Create a learning journey that builds confidence while challenging appropriately
|
|
101746
|
+
4. **Diversity Optimization**: Ensure variety prevents monotony and maintains engagement
|
|
101747
|
+
5. **Context Sensitivity**: Match question types to topic characteristics (technical, conceptual, procedural)
|
|
101748
|
+
6. **Image Context Integration**: Intelligently associate questions with relevant images from the provided library to enhance contextual understanding.
|
|
101749
|
+
|
|
101750
|
+
## ENHANCED OUTPUT FORMAT:
|
|
101751
|
+
|
|
101752
|
+
Return ONLY a JSON object with this EXACT structure:
|
|
101753
|
+
|
|
101754
|
+
\`\`\`json
|
|
101755
|
+
{
|
|
101756
|
+
"quizPlan": [
|
|
101757
|
+
{
|
|
101758
|
+
"plannedTopic": "Specific, assessable topic derived from provided context",
|
|
101759
|
+
"plannedQuestionType": "question_type_from_allowed_list",
|
|
101760
|
+
"plannedBloomLevel": "bloom_level_from_requirements",
|
|
101761
|
+
"plannedContextId": "THEO_ABS",
|
|
101762
|
+
"imageId": "imgctx_12345abcde", // or null
|
|
101763
|
+
"targetMisconception": "Specific misconception this question addresses (or 'none' if not applicable)",
|
|
101764
|
+
"difficultyReason": "Strategic explanation of difficulty choice and placement",
|
|
101765
|
+
"topicSpecificity": "broad|focused|specific",
|
|
101766
|
+
"originalLoId": "corresponding_LoId_from_input",
|
|
101767
|
+
"originalSubject": "corresponding_Subject_from_input",
|
|
101768
|
+
"originalCategory": "corresponding_Category_from_input",
|
|
101769
|
+
"originalTopic": "corresponding_Topic_from_input"
|
|
101770
|
+
}
|
|
101771
|
+
],
|
|
101772
|
+
"diversityMetrics": {
|
|
101773
|
+
"questionTypeDistribution": {"type1": count1, "type2": count2},
|
|
101774
|
+
"bloomLevelDistribution": {"level1": count1, "level2": count2},
|
|
101775
|
+
"maxConsecutiveSameType": number,
|
|
101776
|
+
"difficultyProgression": "description of how difficulty progresses",
|
|
101777
|
+
"misconceptionCoverage": number_of_misconceptions_addressed
|
|
101778
|
+
},
|
|
101779
|
+
"planningStrategy": {
|
|
101780
|
+
"overallApproach": "Brief description of the strategic approach taken",
|
|
101781
|
+
"keyDecisions": ["Major decision 1", "Major decision 2", "Major decision 3"]
|
|
101782
|
+
}
|
|
101783
|
+
}
|
|
101784
|
+
\`\`\`
|
|
101785
|
+
|
|
101786
|
+
Execute this plan with pedagogical precision. The quiz should feel like a carefully crafted learning journey that challenges and educates simultaneously.`;
|
|
101787
|
+
logger.log("PROMPT_PREPARATION", {
|
|
101788
|
+
promptLength: enhancedPromptText.length,
|
|
101789
|
+
topicCount: clientInput.topics.length,
|
|
101790
|
+
misconceptionCount: clientInput.topics.reduce((sum, t4) => sum + (t4.commonMisconceptions?.length || 0), 0)
|
|
101791
|
+
}, Date.now() - promptStartTime);
|
|
101792
|
+
const generationStartTime = Date.now();
|
|
101793
|
+
const contents = [
|
|
101794
|
+
{
|
|
101795
|
+
role: "user",
|
|
101796
|
+
parts: [
|
|
101797
|
+
{
|
|
101798
|
+
text: enhancedPromptText
|
|
101799
|
+
}
|
|
101800
|
+
]
|
|
101801
|
+
}
|
|
101802
|
+
];
|
|
101803
|
+
const aiResult = await ai.models.generateContent({
|
|
101804
|
+
model,
|
|
101805
|
+
config: config3,
|
|
101806
|
+
contents
|
|
101807
|
+
});
|
|
101808
|
+
const response = aiResult;
|
|
101809
|
+
const generationDuration = Date.now() - generationStartTime;
|
|
101810
|
+
logger.log("AI_GENERATION", {
|
|
101811
|
+
responseLength: response.candidates?.[0]?.content?.parts?.[0]?.text?.length || 0,
|
|
101812
|
+
duration: generationDuration
|
|
101813
|
+
}, generationDuration);
|
|
101814
|
+
const processingStartTime = Date.now();
|
|
101815
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
101816
|
+
let jsonText = rawText;
|
|
101817
|
+
if (!rawText.trim().startsWith("{") && !rawText.trim().startsWith("[")) {
|
|
101818
|
+
jsonText = extractJsonFromMarkdown(rawText);
|
|
101819
|
+
}
|
|
101820
|
+
logger.log("JSON_EXTRACTION", {
|
|
101821
|
+
rawTextLength: rawText.length,
|
|
101822
|
+
extractedJsonLength: jsonText.length
|
|
101823
|
+
});
|
|
101824
|
+
const aiGeneratedContent = GenerateQuizPlanOutputSchema.parse(JSON.parse(jsonText));
|
|
101825
|
+
logger.log("SCHEMA_VALIDATION", { success: true }, Date.now() - processingStartTime);
|
|
101826
|
+
const validationStartTime = Date.now();
|
|
101827
|
+
if (aiGeneratedContent.quizPlan.length !== clientInput.totalQuestions) {
|
|
101828
|
+
throw new Error(`AI planned for ${aiGeneratedContent.quizPlan.length} questions, but ${clientInput.totalQuestions} were requested.`);
|
|
101829
|
+
}
|
|
101830
|
+
const invalidTypes = [];
|
|
101831
|
+
aiGeneratedContent.quizPlan.forEach((item, index3) => {
|
|
101832
|
+
if (!clientInput.selectedQuestionTypes.includes(item.plannedQuestionType)) {
|
|
101833
|
+
invalidTypes.push(`Question ${index3 + 1}: '${item.plannedQuestionType}'`);
|
|
101834
|
+
}
|
|
101835
|
+
});
|
|
101836
|
+
if (invalidTypes.length > 0) {
|
|
101837
|
+
throw new Error(`Invalid question types found: ${invalidTypes.join(", ")}`);
|
|
101838
|
+
}
|
|
101839
|
+
const codingQuestions = aiGeneratedContent.quizPlan.filter((q2) => q2.plannedQuestionType === "coding");
|
|
101840
|
+
if (numCodingQuestions > 0 && codingQuestions.length !== numCodingQuestions) {
|
|
101841
|
+
throw new Error(`Expected ${numCodingQuestions} coding questions, but got ${codingQuestions.length}`);
|
|
101842
|
+
}
|
|
101843
|
+
const diversityAnalysis = validateConsecutiveTypes(aiGeneratedContent.quizPlan);
|
|
101844
|
+
logger.log("VALIDATION_COMPLETE", {
|
|
101845
|
+
questionCount: aiGeneratedContent.quizPlan.length,
|
|
101846
|
+
codingQuestionCount: codingQuestions.length,
|
|
101847
|
+
maxConsecutiveType: diversityAnalysis.maxConsecutive,
|
|
101848
|
+
questionTypeDistribution: aiGeneratedContent.diversityMetrics?.questionTypeDistribution || {}
|
|
101849
|
+
}, Date.now() - validationStartTime);
|
|
101850
|
+
const finalResult = {
|
|
101851
|
+
...aiGeneratedContent,
|
|
101852
|
+
logs: logger.getLogs()
|
|
101853
|
+
};
|
|
101854
|
+
logger.log("GENERATION_COMPLETE", {
|
|
101855
|
+
totalDuration: logger.getTotalDuration(),
|
|
101856
|
+
success: true,
|
|
101857
|
+
finalQuestionCount: finalResult.quizPlan.length
|
|
101858
|
+
}, logger.getTotalDuration());
|
|
101859
|
+
console.log("\n=== QUIZ PLAN GENERATION SUMMARY ===");
|
|
101860
|
+
console.log(`\u2705 Successfully generated ${finalResult.quizPlan.length} questions`);
|
|
101861
|
+
console.log(`\u23F1\uFE0F Total generation time: ${logger.getTotalDuration()}ms`);
|
|
101862
|
+
console.log(`\u{1F3AF} Question types: ${Object.keys(finalResult.diversityMetrics?.questionTypeDistribution || {}).join(", ")}`);
|
|
101863
|
+
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(finalResult.diversityMetrics?.bloomLevelDistribution || {}).join(", ")}`);
|
|
101864
|
+
if (numCodingQuestions > 0) {
|
|
101865
|
+
console.log(`\u{1F4BB} Coding questions: ${codingQuestions.length}/${numCodingQuestions} required`);
|
|
101866
|
+
}
|
|
101867
|
+
console.log(JSON.stringify(finalResult));
|
|
101868
|
+
console.log("=====================================\n");
|
|
101869
|
+
return finalResult;
|
|
101870
|
+
} catch (error) {
|
|
101871
|
+
logger.log("ERROR", {
|
|
101872
|
+
message: error.message,
|
|
101873
|
+
stack: error.stack,
|
|
101874
|
+
totalDuration: logger.getTotalDuration()
|
|
101875
|
+
});
|
|
101876
|
+
console.error("\u274C Quiz Plan Generation Failed:", error.message);
|
|
101877
|
+
console.error("\u{1F4CB} Full logs available in returned object");
|
|
101878
|
+
throw new Error(`Failed to generate Quiz Plan: ${error.message}`);
|
|
101879
|
+
}
|
|
101880
|
+
}
|
|
101881
|
+
function validateConsecutiveTypes(quizPlan) {
|
|
101882
|
+
let maxConsecutive = 1;
|
|
101883
|
+
let maxType = quizPlan[0]?.plannedQuestionType || "";
|
|
101884
|
+
let maxStartIndex = 0;
|
|
101885
|
+
let currentConsecutive = 1;
|
|
101886
|
+
let currentType = quizPlan[0]?.plannedQuestionType || "";
|
|
101887
|
+
let currentStartIndex = 0;
|
|
101888
|
+
for (let i2 = 1; i2 < quizPlan.length; i2++) {
|
|
101889
|
+
if (quizPlan[i2].plannedQuestionType === currentType) {
|
|
101890
|
+
currentConsecutive++;
|
|
101891
|
+
} else {
|
|
101892
|
+
if (currentConsecutive > maxConsecutive) {
|
|
101893
|
+
maxConsecutive = currentConsecutive;
|
|
101894
|
+
maxType = currentType;
|
|
101895
|
+
maxStartIndex = currentStartIndex;
|
|
101896
|
+
}
|
|
101897
|
+
currentConsecutive = 1;
|
|
101898
|
+
currentType = quizPlan[i2].plannedQuestionType;
|
|
101899
|
+
currentStartIndex = i2;
|
|
101900
|
+
}
|
|
101901
|
+
}
|
|
101902
|
+
if (currentConsecutive > maxConsecutive) {
|
|
101903
|
+
maxConsecutive = currentConsecutive;
|
|
101904
|
+
maxType = currentType;
|
|
101905
|
+
maxStartIndex = currentStartIndex;
|
|
101906
|
+
}
|
|
101907
|
+
return {
|
|
101908
|
+
maxConsecutive,
|
|
101909
|
+
type: maxType,
|
|
101910
|
+
startIndex: maxStartIndex
|
|
101911
|
+
};
|
|
101912
|
+
}
|
|
101913
|
+
|
|
101914
|
+
// src/ai/flows/generate-questions-from-quiz-plan.ts
|
|
101915
|
+
init_react_shim();
|
|
101916
|
+
|
|
101917
|
+
// src/services/TopicDataService.ts
|
|
101918
|
+
init_react_shim();
|
|
101919
|
+
var TopicDataService = class {
|
|
101920
|
+
static saveData(data) {
|
|
101921
|
+
try {
|
|
101922
|
+
if (typeof window === "undefined") return;
|
|
101923
|
+
const serializedData = JSON.stringify(data);
|
|
101924
|
+
localStorage.setItem(this.STORAGE_KEY, serializedData);
|
|
101925
|
+
} catch (error) {
|
|
101926
|
+
console.error("Error saving learning objectives to Local Storage:", error);
|
|
101927
|
+
}
|
|
101928
|
+
}
|
|
101929
|
+
static mergeData(newData) {
|
|
101930
|
+
const existingData = this.getData();
|
|
101931
|
+
const loMap = new Map(existingData.map((lo) => [lo.code, lo]));
|
|
101932
|
+
newData.forEach((newLo) => {
|
|
101933
|
+
loMap.set(newLo.code, newLo);
|
|
101934
|
+
});
|
|
101935
|
+
const mergedData = Array.from(loMap.values());
|
|
101936
|
+
this.saveData(mergedData);
|
|
101937
|
+
}
|
|
101938
|
+
static getData() {
|
|
101939
|
+
try {
|
|
101940
|
+
if (typeof window === "undefined") return [];
|
|
101941
|
+
const storedData = localStorage.getItem(this.STORAGE_KEY);
|
|
101942
|
+
return storedData ? JSON.parse(storedData) : [];
|
|
101943
|
+
} catch (error) {
|
|
101944
|
+
console.error("Error retrieving learning objectives from Local Storage:", error);
|
|
101945
|
+
this.clearData();
|
|
101946
|
+
return [];
|
|
101947
|
+
}
|
|
101948
|
+
}
|
|
101949
|
+
static clearData() {
|
|
101950
|
+
try {
|
|
101951
|
+
if (typeof window === "undefined") return;
|
|
101952
|
+
localStorage.removeItem(this.STORAGE_KEY);
|
|
101953
|
+
} catch (error) {
|
|
101954
|
+
console.error("Error clearing learning objectives from Local Storage:", error);
|
|
101955
|
+
}
|
|
101956
|
+
}
|
|
101957
|
+
static parseTSV(tsvContent) {
|
|
101958
|
+
const lines = tsvContent.split("\n").filter((line) => line.trim() !== "");
|
|
101959
|
+
if (lines.length < 2) {
|
|
101960
|
+
return { data: [], errors: ["File is empty or contains only a header."] };
|
|
101961
|
+
}
|
|
101962
|
+
const headerLine = lines.shift();
|
|
101963
|
+
const headers = headerLine.split(" ").map((h3) => h3.trim());
|
|
101964
|
+
if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
|
|
101965
|
+
const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
|
|
101966
|
+
return { data: [], errors: [errorMsg] };
|
|
101967
|
+
}
|
|
101968
|
+
const data = [];
|
|
101969
|
+
const errors2 = [];
|
|
101970
|
+
lines.forEach((line, index3) => {
|
|
101971
|
+
const values = line.split(" ").map((v) => v.trim());
|
|
101972
|
+
if (values.length !== this.EXPECTED_HEADERS.length) {
|
|
101973
|
+
errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
|
|
101974
|
+
return;
|
|
101975
|
+
}
|
|
101976
|
+
const [
|
|
101977
|
+
loId,
|
|
101978
|
+
name3,
|
|
101979
|
+
loDescription,
|
|
101980
|
+
subject,
|
|
101981
|
+
category,
|
|
101982
|
+
topic,
|
|
101983
|
+
keywordsStr,
|
|
101984
|
+
grade,
|
|
101985
|
+
stemElementsStr,
|
|
101986
|
+
bloomLevelsStr
|
|
101987
|
+
] = values;
|
|
101988
|
+
if (!loId || !loDescription || !subject || !category || !topic) {
|
|
101989
|
+
errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
|
|
101990
|
+
return;
|
|
101991
|
+
}
|
|
101992
|
+
const learningObjective = {
|
|
101993
|
+
id: generateUniqueId("lo_"),
|
|
101994
|
+
code: loId,
|
|
101995
|
+
name: name3,
|
|
101996
|
+
description: loDescription,
|
|
101997
|
+
// Can be the same as name or enhanced later
|
|
101998
|
+
subject,
|
|
101999
|
+
category,
|
|
102000
|
+
topic,
|
|
102001
|
+
grade,
|
|
102002
|
+
keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
|
|
102003
|
+
stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
|
|
102004
|
+
bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
|
|
102005
|
+
};
|
|
102006
|
+
data.push(learningObjective);
|
|
102007
|
+
});
|
|
102008
|
+
return { data, errors: errors2 };
|
|
102009
|
+
}
|
|
102010
|
+
static getSubjects() {
|
|
102011
|
+
const data = this.getData();
|
|
102012
|
+
const subjects = data.map((item) => item.subject);
|
|
102013
|
+
return [...new Set(subjects)].sort();
|
|
102014
|
+
}
|
|
102015
|
+
static getCategoriesBySubject(subject) {
|
|
102016
|
+
const data = this.getData();
|
|
102017
|
+
const categories = data.filter((item) => item.subject === subject).map((item) => item.category);
|
|
102018
|
+
return [...new Set(categories)].sort();
|
|
102019
|
+
}
|
|
102020
|
+
static getTopicsByCategory(category) {
|
|
102021
|
+
const data = this.getData();
|
|
102022
|
+
const topics = data.filter((item) => item.category === category).map((item) => item.topic);
|
|
102023
|
+
return [...new Set(topics)].sort();
|
|
102024
|
+
}
|
|
102025
|
+
static getLearningObjectivesByTopics(topics) {
|
|
102026
|
+
const data = this.getData();
|
|
102027
|
+
const topicSet = new Set(topics);
|
|
102028
|
+
return data.filter((item) => topicSet.has(item.topic));
|
|
102029
|
+
}
|
|
102030
|
+
};
|
|
102031
|
+
TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
|
|
102032
|
+
TopicDataService.EXPECTED_HEADERS = [
|
|
102033
|
+
"LO ID",
|
|
102034
|
+
"LO Description",
|
|
102035
|
+
"Subject",
|
|
102036
|
+
"Category",
|
|
102037
|
+
"Topic",
|
|
102038
|
+
"Keywords",
|
|
102039
|
+
"Grade",
|
|
102040
|
+
"STEM Element(s)",
|
|
102041
|
+
"Bloom\u2019s Level(s) Guideline"
|
|
102042
|
+
];
|
|
102043
|
+
|
|
102029
102044
|
// src/ai/flows/question-gen/generate-coding-question.ts
|
|
102030
102045
|
init_react_shim();
|
|
102031
102046
|
|
|
@@ -102063,7 +102078,7 @@ Previous attempts failed. Pay strict attention to the JSON schema and all rules.
|
|
|
102063
102078
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The coding problem must be directly related to processing or interpreting the content of this image.` : "";
|
|
102064
102079
|
const contextStrings = [
|
|
102065
102080
|
`**Subject:** ${subject}`,
|
|
102066
|
-
quizContext?.
|
|
102081
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
102067
102082
|
imageContextInstruction,
|
|
102068
102083
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
102069
102084
|
quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
|
|
@@ -102220,9 +102235,9 @@ var calculateCombinedDifficulty = (plannedQ) => {
|
|
|
102220
102235
|
break;
|
|
102221
102236
|
}
|
|
102222
102237
|
const totalScore = bloomScore + contextScore + questionTypeScore;
|
|
102223
|
-
if (totalScore <= 4) return "
|
|
102224
|
-
if (totalScore <= 7) return "
|
|
102225
|
-
return "
|
|
102238
|
+
if (totalScore <= 4) return "Easy";
|
|
102239
|
+
if (totalScore <= 7) return "Medium";
|
|
102240
|
+
return "Hard";
|
|
102226
102241
|
};
|
|
102227
102242
|
async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
102228
102243
|
const { quizPlan, language: language3, imageContexts } = clientInput;
|
|
@@ -102235,7 +102250,7 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
102235
102250
|
let lastError = null;
|
|
102236
102251
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
102237
102252
|
try {
|
|
102238
|
-
const fullLO = plannedQ.originalLoId ? allLearningObjectives.find((lo) => lo.
|
|
102253
|
+
const fullLO = plannedQ.originalLoId ? allLearningObjectives.find((lo) => lo.code === plannedQ.originalLoId) : null;
|
|
102239
102254
|
const quizContext = {
|
|
102240
102255
|
plannedTopic: plannedQ.plannedTopic,
|
|
102241
102256
|
plannedQuestionType: plannedQ.plannedQuestionType,
|
|
@@ -102248,7 +102263,7 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
102248
102263
|
originalSubject: plannedQ.originalSubject,
|
|
102249
102264
|
originalCategory: plannedQ.originalCategory,
|
|
102250
102265
|
originalTopic: plannedQ.originalTopic,
|
|
102251
|
-
|
|
102266
|
+
description: fullLO?.description || plannedQ.plannedTopic
|
|
102252
102267
|
};
|
|
102253
102268
|
const imageUrl = plannedQ.imageId && imageContexts ? imageContexts.find((ctx) => ctx.id === plannedQ.imageId)?.imageUrl : void 0;
|
|
102254
102269
|
const baseClientInput = {
|
|
@@ -102602,7 +102617,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102602
102617
|
};
|
|
102603
102618
|
const renderContent3 = () => {
|
|
102604
102619
|
if (currentStage === "review") {
|
|
102605
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-lg font-
|
|
102620
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-lg font-Medium mb-2 text-primary flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Eye, { className: "mr-2 h-5 w-5" }), " Review & Adjust AI Generated Quiz Plan"), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "The AI has proposed the following plan. You can change question types, reorder, or remove questions before final generation. Note: 'Drag and Drop' type questions in the plan will be created as placeholders and require manual authoring of items/zones/answers."), aiGeneratedPlan && aiGeneratedPlan.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "max-h-[calc(60vh - 120px)] pr-3" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, aiGeneratedPlan.map((plannedQ, index3) => /* @__PURE__ */ React169__namespace.default.createElement(Card, { key: `planned-${index3}-${plannedQ.plannedTopic.replace(/\s/g, "")}`, className: "p-3" }, /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "p-0 space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-semibold text-sm" }, "Q", index3 + 1, ": ", plannedQ.plannedTopic), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleMovePlannedQuestion(index3, "up"), disabled: index3 === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(ArrowUp, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleMovePlannedQuestion(index3, "down"), disabled: index3 === aiGeneratedPlan.length - 1 }, /* @__PURE__ */ React169__namespace.default.createElement(ArrowDown, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7 text-destructive", onClick: () => handleRemovePlannedQuestion(index3) }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-3 items-end" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `review-qtype-${index3}`, className: "text-xs" }, "Question Type"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
102606
102621
|
Select2,
|
|
102607
102622
|
{
|
|
102608
102623
|
value: plannedQ.plannedQuestionType,
|
|
@@ -102631,7 +102646,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102631
102646
|
min: "1",
|
|
102632
102647
|
max: "100"
|
|
102633
102648
|
}
|
|
102634
|
-
)), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-
|
|
102649
|
+
)), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Topic Distribution"), topics.map((topicItem, index3) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: topicItem.id, className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
102635
102650
|
Input,
|
|
102636
102651
|
{
|
|
102637
102652
|
type: "text",
|
|
@@ -102651,7 +102666,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102651
102666
|
min: "0",
|
|
102652
102667
|
max: "100"
|
|
102653
102668
|
}
|
|
102654
|
-
), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeTopic(topicItem.id), disabled: topics.length <= 1 }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: addTopic }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-
|
|
102669
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeTopic(topicItem.id), disabled: topics.length <= 1 }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: addTopic }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Bloom Level Distribution"), bloomLevels.map((bloomItem) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: bloomItem.id, className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "w-32 capitalize flex-shrink-0" }, bloomItem.level), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
102655
102670
|
Input,
|
|
102656
102671
|
{
|
|
102657
102672
|
type: "number",
|
|
@@ -102662,7 +102677,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102662
102677
|
min: "0",
|
|
102663
102678
|
max: "100"
|
|
102664
102679
|
}
|
|
102665
|
-
)))), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-
|
|
102680
|
+
)))), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Relevant Contexts (Optional, Select Multiple)"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 mt-2 max-h-40 overflow-y-auto" }, contextOptions.filter((opt) => opt.contextId !== "__none__" && opt.contextId !== "__custom__").map((opt) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: opt.contextId, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
102666
102681
|
Checkbox2,
|
|
102667
102682
|
{
|
|
102668
102683
|
id: `ctx-full-${opt.contextId}`,
|
|
@@ -102684,7 +102699,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102684
102699
|
placeholder: "Enter your specific custom context here...",
|
|
102685
102700
|
className: "min-h-[60px] mt-2 text-sm"
|
|
102686
102701
|
}
|
|
102687
|
-
)), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-
|
|
102702
|
+
)), /* @__PURE__ */ React169__namespace.default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Question Types to Use (Select Multiple)"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 mt-2 max-h-40 overflow-y-auto" }, availableQuestionTypesForFullQuiz.map((qType) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: qType.value, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
102688
102703
|
Checkbox2,
|
|
102689
102704
|
{
|
|
102690
102705
|
id: `qtype-full-${qType.value}`,
|
|
@@ -103084,7 +103099,7 @@ var AlertTitle = React169__namespace.forwardRef(({ className, ...props }, ref) =
|
|
|
103084
103099
|
"h5",
|
|
103085
103100
|
{
|
|
103086
103101
|
ref,
|
|
103087
|
-
className: cn("mb-1 font-
|
|
103102
|
+
className: cn("mb-1 font-Medium leading-none tracking-tight", className),
|
|
103088
103103
|
...props
|
|
103089
103104
|
}
|
|
103090
103105
|
));
|
|
@@ -103129,7 +103144,7 @@ var BaseRawQuestionSchema = zod.z.object({
|
|
|
103129
103144
|
points: zod.z.number().optional(),
|
|
103130
103145
|
explanation: zod.z.string().optional(),
|
|
103131
103146
|
topic: zod.z.string().optional(),
|
|
103132
|
-
difficulty: zod.z.enum(["
|
|
103147
|
+
difficulty: zod.z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
103133
103148
|
bloomLevel: zod.z.string().optional()
|
|
103134
103149
|
});
|
|
103135
103150
|
var RawMCQSchema = BaseRawQuestionSchema.extend({
|
|
@@ -103482,7 +103497,7 @@ var QuizEditorService = class {
|
|
|
103482
103497
|
questionType: type,
|
|
103483
103498
|
prompt: "",
|
|
103484
103499
|
points: 10,
|
|
103485
|
-
difficulty: "
|
|
103500
|
+
difficulty: "Medium"
|
|
103486
103501
|
};
|
|
103487
103502
|
switch (type) {
|
|
103488
103503
|
case "true_false":
|
|
@@ -103709,7 +103724,7 @@ var SelectedQuestionsPanel = ({
|
|
|
103709
103724
|
},
|
|
103710
103725
|
/* @__PURE__ */ React169__namespace.default.createElement(ArrowDown, { className: "h-3 w-3" })
|
|
103711
103726
|
)),
|
|
103712
|
-
/* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "font-
|
|
103727
|
+
/* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "font-Medium text-sm leading-5 break-words" }, index3 + 1, ".", " ", q2.prompt.replace(/<[^>]*>?/gm, "").substring(0, 100) || `(${q2.questionType} - Untitled)`), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "text-xs text-muted-foreground mt-1" }, q2.questionType, " \u2022 ", q2.points || 0, " pts")),
|
|
103713
103728
|
/* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-1 flex-shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
103714
103729
|
Button,
|
|
103715
103730
|
{
|
|
@@ -104096,7 +104111,7 @@ var TableFooter = React169__namespace.forwardRef(({ className, ...props }, ref)
|
|
|
104096
104111
|
{
|
|
104097
104112
|
ref,
|
|
104098
104113
|
className: cn(
|
|
104099
|
-
"border-t bg-muted/50 font-
|
|
104114
|
+
"border-t bg-muted/50 font-Medium [&>tr]:last:border-b-0",
|
|
104100
104115
|
className
|
|
104101
104116
|
),
|
|
104102
104117
|
...props
|
|
@@ -104120,7 +104135,7 @@ var TableHead = React169__namespace.forwardRef(({ className, ...props }, ref) =>
|
|
|
104120
104135
|
{
|
|
104121
104136
|
ref,
|
|
104122
104137
|
className: cn(
|
|
104123
|
-
"h-12 px-4 text-left align-middle font-
|
|
104138
|
+
"h-12 px-4 text-left align-middle font-Medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
104124
104139
|
className
|
|
104125
104140
|
),
|
|
104126
104141
|
...props
|
|
@@ -104210,20 +104225,6 @@ var questionTypeManager = new LocalStorageManager("question_types");
|
|
|
104210
104225
|
var learningObjectiveManager = new LocalStorageManager("learning_objectives");
|
|
104211
104226
|
var contextManager = new LocalStorageManager("contexts");
|
|
104212
104227
|
var approachManager = new LocalStorageManager("approaches");
|
|
104213
|
-
function mapRawDifficultyToStandard(rawDifficulty) {
|
|
104214
|
-
switch (rawDifficulty) {
|
|
104215
|
-
case "E":
|
|
104216
|
-
case "E~M":
|
|
104217
|
-
return "easy";
|
|
104218
|
-
case "M":
|
|
104219
|
-
case "M~H":
|
|
104220
|
-
return "medium";
|
|
104221
|
-
case "H":
|
|
104222
|
-
return "hard";
|
|
104223
|
-
default:
|
|
104224
|
-
return "medium";
|
|
104225
|
-
}
|
|
104226
|
-
}
|
|
104227
104228
|
var MetadataService = class {
|
|
104228
104229
|
};
|
|
104229
104230
|
// --- Subject Services ---
|
|
@@ -104281,15 +104282,10 @@ MetadataService.deleteContext = (code4) => contextManager.delete(code4);
|
|
|
104281
104282
|
MetadataService.getApproaches = () => approachManager.getAll().sort((a4, b2) => a4.code.localeCompare(b2.code));
|
|
104282
104283
|
MetadataService.saveApproaches = (items) => approachManager.saveAll(items);
|
|
104283
104284
|
MetadataService.addApproach = (approachData) => {
|
|
104284
|
-
|
|
104285
|
-
return approachManager.add({ ...approachData, difficulty });
|
|
104285
|
+
return approachManager.add(approachData);
|
|
104286
104286
|
};
|
|
104287
104287
|
MetadataService.updateApproach = (id3, approachData) => {
|
|
104288
|
-
|
|
104289
|
-
if (approachData.rawDifficulty) {
|
|
104290
|
-
updates.difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
104291
|
-
}
|
|
104292
|
-
return approachManager.update(id3, updates);
|
|
104288
|
+
return approachManager.update(id3, approachData);
|
|
104293
104289
|
};
|
|
104294
104290
|
MetadataService.deleteApproach = (code4) => approachManager.delete(code4);
|
|
104295
104291
|
// --- LearningObjective Services ---
|
|
@@ -104299,8 +104295,12 @@ MetadataService.getLearningObjectives = (subjectCode) => {
|
|
|
104299
104295
|
return filtered.sort((a4, b2) => a4.name.localeCompare(b2.name));
|
|
104300
104296
|
};
|
|
104301
104297
|
MetadataService.saveLearningObjectives = (items) => learningObjectiveManager.saveAll(items);
|
|
104302
|
-
MetadataService.addLearningObjective = (
|
|
104303
|
-
|
|
104298
|
+
MetadataService.addLearningObjective = (item) => {
|
|
104299
|
+
return learningObjectiveManager.add(item);
|
|
104300
|
+
};
|
|
104301
|
+
MetadataService.updateLearningObjective = (id3, updates) => {
|
|
104302
|
+
return learningObjectiveManager.update(id3, updates);
|
|
104303
|
+
};
|
|
104304
104304
|
MetadataService.deleteLearningObjective = (code4) => learningObjectiveManager.delete(code4);
|
|
104305
104305
|
|
|
104306
104306
|
// node_modules/date-fns/addDays.mjs
|
|
@@ -106200,7 +106200,7 @@ function QuestionList({
|
|
|
106200
106200
|
if (questions.length === 0) {
|
|
106201
106201
|
return /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-8" }, "No questions match the current filters. Try adjusting your search or add new questions.");
|
|
106202
106202
|
}
|
|
106203
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[30%]" }, "Question Text"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Type"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Grade"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Bloom's"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Last Modified"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, questions.map((question2) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: question2.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-
|
|
106203
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[30%]" }, "Question Text"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Type"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Grade"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Bloom's"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Last Modified"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, questions.map((question2) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: question2.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium max-w-xs truncate", title: question2.text }, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: question2.questionConfig.prompt })), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, question2.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, /* @__PURE__ */ React169__namespace.default.createElement(Badge2, { variant: "secondary" }, getLookupName(question2.questionTypeCode, metadata.questionTypes))), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, getLookupName(question2.subjectCode, metadata.subjects)), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, getLookupName(question2.topicCode, metadata.topics)), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, getLookupName(question2.gradeLevelCode, metadata.gradeLevels)), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, /* @__PURE__ */ React169__namespace.default.createElement(Badge2, { variant: "outline" }, getLookupName(question2.bloomLevelCode, metadata.bloomLevels))), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, format(new Date(question2.lastModified), "MMM d, yyyy")), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, onView && /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onView(question2), className: "mr-1" }, /* @__PURE__ */ React169__namespace.default.createElement(Eye, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onEdit(question2), className: "mr-1" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onDelete(question2), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" }))))))));
|
|
106204
106204
|
}
|
|
106205
106205
|
|
|
106206
106206
|
// src/react-ui/components/authoring/QuestionFilters.tsx
|
|
@@ -106281,7 +106281,7 @@ function QuestionFilters({
|
|
|
106281
106281
|
onChange: (e3) => setSearchTerm(e3.target.value),
|
|
106282
106282
|
className: "lg:col-span-2 xl:col-span-1"
|
|
106283
106283
|
}
|
|
106284
|
-
), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: subjectCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setSubjectCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Subjects"), subjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: topicCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setTopicCode), disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Topic" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Topics"), filteredTopics.map((t4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: gradeLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setGradeLevelCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Grade Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Grade Levels"), gradeLevels.map((gl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: bloomLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setBloomLevelCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Bloom's Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Levels"), bloomLevels.map((bl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: questionTypeCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setQuestionTypeCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Question Type" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Types"), questionTypes.map((qt) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: difficulty || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setDifficulty) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Difficulty" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Difficulties"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "
|
|
106284
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: subjectCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setSubjectCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Subjects"), subjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: topicCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setTopicCode), disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Topic" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Topics"), filteredTopics.map((t4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: gradeLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setGradeLevelCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Grade Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Grade Levels"), gradeLevels.map((gl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: bloomLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setBloomLevelCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Bloom's Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Levels"), bloomLevels.map((bl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: questionTypeCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setQuestionTypeCode) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Question Type" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Types"), questionTypes.map((qt) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: difficulty || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setDifficulty) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Difficulty" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Difficulties"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Easy" }, "Easy"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Medium" }, "Medium"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "Hard" }, "Hard"))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex gap-2 col-span-full sm:col-span-1 xl:col-span-2 xl:col-start-6" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleApplyFilters, className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React169__namespace.default.createElement(Search, { className: "mr-2 h-4 w-4" }), " Apply"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleClearFilters, variant: "outline", className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleX, { className: "mr-2 h-4 w-4" }), " Clear"))));
|
|
106285
106285
|
}
|
|
106286
106286
|
|
|
106287
106287
|
// src/react-ui/components/authoring/QuestionFormDialog.tsx
|
|
@@ -106389,7 +106389,7 @@ function QuestionFormDialog({
|
|
|
106389
106389
|
};
|
|
106390
106390
|
const dialogTitle = questionToEdit ? "Edit Question Metadata" : "Create New Question";
|
|
106391
106391
|
const dialogDescription = "First, define the metadata for the question. Then, edit the question's content and logic.";
|
|
106392
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, dialogDescription)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Question Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: code4, onChange: (e3) => setCode(e3.target.value.toUpperCase()), placeholder: "Unique code (e.g., MATH-ALG-001)", disabled: !!questionToEdit })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gradeLevelCode" }, "Grade Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: gradeLevelCode, onValueChange: setGradeLevelCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "gradeLevelCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Grade Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, gradeLevels.map((gl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: subjectCode, onValueChange: setSubjectCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, subjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "topicCode" }, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: topicCode, onValueChange: setTopicCode, disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "topicCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Topic" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, filteredTopics.map((t4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Bloom's Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: bloomLevelCode, onValueChange: setBloomLevelCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "bloomLevelCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Bloom's Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, bloomLevels.map((bl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Question Content & Logic"), questionConfig ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-3 mt-2 border rounded-md bg-muted/30 flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-
|
|
106392
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, dialogDescription)), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Question Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: code4, onChange: (e3) => setCode(e3.target.value.toUpperCase()), placeholder: "Unique code (e.g., MATH-ALG-001)", disabled: !!questionToEdit })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gradeLevelCode" }, "Grade Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: gradeLevelCode, onValueChange: setGradeLevelCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "gradeLevelCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Grade Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, gradeLevels.map((gl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: subjectCode, onValueChange: setSubjectCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, subjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "topicCode" }, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: topicCode, onValueChange: setTopicCode, disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "topicCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Topic" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, filteredTopics.map((t4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Bloom's Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: bloomLevelCode, onValueChange: setBloomLevelCode }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "bloomLevelCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select Bloom's Level" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, bloomLevels.map((bl) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { className: "font-semibold" }, "Question Content & Logic"), questionConfig ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-3 mt-2 border rounded-md bg-muted/30 flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-Medium" }, "Type: ", /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "font-normal" }, questionConfig.questionType)), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground truncate max-w-md" }, "Prompt: ", questionConfig.prompt.replace(/<[^>]*>?/gm, "") || "Not set")), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: () => handleOpenQuestionEditor() }, /* @__PURE__ */ React169__namespace.default.createElement(SquarePen, { className: "mr-2 h-4 w-4" }), " Edit Content")) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-3 mt-2 border-dashed border-2 rounded-md text-center" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground mb-2" }, "No content has been created yet."), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "default", onClick: () => handleOpenQuestionEditor("multiple_choice") }, /* @__PURE__ */ React169__namespace.default.createElement(BookCopy, { className: "mr-2 h-4 w-4" }), " Create Question Content")))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !questionConfig }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save Question")))), questionConfig && /* @__PURE__ */ React169__namespace.default.createElement(
|
|
106393
106393
|
EditQuestionModal,
|
|
106394
106394
|
{
|
|
106395
106395
|
isOpen: isQuestionEditorOpen,
|
|
@@ -110707,10 +110707,10 @@ var RoadmapService = class {
|
|
|
110707
110707
|
}
|
|
110708
110708
|
return null;
|
|
110709
110709
|
}
|
|
110710
|
-
static updateRoadmapItemStatus(
|
|
110710
|
+
static updateRoadmapItemStatus(code4, isCompleted) {
|
|
110711
110711
|
const roadmap = this.getRoadmap();
|
|
110712
110712
|
if (roadmap) {
|
|
110713
|
-
const itemIndex = roadmap.items.findIndex((item) => item.
|
|
110713
|
+
const itemIndex = roadmap.items.findIndex((item) => item.code === code4);
|
|
110714
110714
|
if (itemIndex > -1) {
|
|
110715
110715
|
roadmap.items[itemIndex].isCompleted = isCompleted;
|
|
110716
110716
|
this.saveRoadmap(roadmap);
|
|
@@ -111291,7 +111291,7 @@ zod.z.object({
|
|
|
111291
111291
|
startDate: zod.z.string().describe("The start date for the analysis period in ISO format (YYYY-MM-DD)."),
|
|
111292
111292
|
endDate: zod.z.string().describe("The end date for the analysis period in ISO format (YYYY-MM-DD)."),
|
|
111293
111293
|
allAvailableTopics: zod.z.array(zod.z.object({
|
|
111294
|
-
|
|
111294
|
+
code: zod.z.string(),
|
|
111295
111295
|
subject: zod.z.string(),
|
|
111296
111296
|
category: zod.z.string(),
|
|
111297
111297
|
topic: zod.z.string()
|
|
@@ -111302,7 +111302,7 @@ var RoadmapItemSchema = zod.z.object({
|
|
|
111302
111302
|
topicName: zod.z.string(),
|
|
111303
111303
|
reason: zod.z.string(),
|
|
111304
111304
|
suggestedDifficulty: zod.z.enum(["Very Easy", "Easy", "Medium", "Hard", "Expert"]),
|
|
111305
|
-
|
|
111305
|
+
code: zod.z.string(),
|
|
111306
111306
|
isCompleted: zod.z.boolean()
|
|
111307
111307
|
});
|
|
111308
111308
|
var WeeklyRoadmapSchema = zod.z.object({
|
|
@@ -111367,7 +111367,7 @@ All topic names and loIds in your "weeklyRoadmap" output MUST be chosen directly
|
|
|
111367
111367
|
- **gamificationRemarks**: Write an encouraging message mentioning their achievements.
|
|
111368
111368
|
2. **For "weeklyRoadmap":**
|
|
111369
111369
|
- Create a 5-item roadmap for the upcoming week.
|
|
111370
|
-
- Prioritize the "areasForImprovement" you identified. For each, find the corresponding entry in "All Available Topics" and use its "topicName" and "
|
|
111370
|
+
- Prioritize the "areasForImprovement" you identified. For each, find the corresponding entry in "All Available Topics" and use its "topicName" and "code".
|
|
111371
111371
|
- If more items are needed, select related topics from "All Available Topics".
|
|
111372
111372
|
|
|
111373
111373
|
**IF Practice History IS EMPTY:**
|
|
@@ -111378,7 +111378,7 @@ All topic names and loIds in your "weeklyRoadmap" output MUST be chosen directly
|
|
|
111378
111378
|
- **gamificationRemarks**: Write a general motivational message about starting to learn.
|
|
111379
111379
|
2. **For "weeklyRoadmap":**
|
|
111380
111380
|
- Create a 5-item "starter" roadmap.
|
|
111381
|
-
- Select 5 diverse and foundational topics directly from the "All Available Topics" list. Use their exact "topicName" and "
|
|
111381
|
+
- Select 5 diverse and foundational topics directly from the "All Available Topics" list. Use their exact "topicName" and "code".
|
|
111382
111382
|
- For the "reason", explain that this is a good starting point to explore the subject.
|
|
111383
111383
|
|
|
111384
111384
|
--- END LOGIC FLOW ---
|
|
@@ -111406,7 +111406,7 @@ The 'suggestedDifficulty' field MUST ALWAYS be one of these exact English string
|
|
|
111406
111406
|
"topicName": "Topic C",
|
|
111407
111407
|
"reason": "To strengthen your understanding of this key area.",
|
|
111408
111408
|
"suggestedDifficulty": "Easy",
|
|
111409
|
-
"
|
|
111409
|
+
"code": "lo-id-for-topic-c-from-the-list",
|
|
111410
111410
|
"isCompleted": false
|
|
111411
111411
|
}
|
|
111412
111412
|
]
|
|
@@ -111668,11 +111668,11 @@ init_react_shim();
|
|
|
111668
111668
|
// src/ai/flows/assess-and-map-document-types.ts
|
|
111669
111669
|
init_react_shim();
|
|
111670
111670
|
var LearningObjectiveContextSchema = zod.z.object({
|
|
111671
|
-
|
|
111671
|
+
code: zod.z.string(),
|
|
111672
111672
|
subject: zod.z.string(),
|
|
111673
111673
|
category: zod.z.string(),
|
|
111674
111674
|
topic: zod.z.string(),
|
|
111675
|
-
|
|
111675
|
+
description: zod.z.string()
|
|
111676
111676
|
});
|
|
111677
111677
|
zod.z.object({
|
|
111678
111678
|
language: zod.z.string().default("English"),
|
|
@@ -111680,7 +111680,7 @@ zod.z.object({
|
|
|
111680
111680
|
learningObjectives: zod.z.array(LearningObjectiveContextSchema).min(1, { message: "At least one learning objective is required for mapping." })
|
|
111681
111681
|
});
|
|
111682
111682
|
var MappedLOSchema = zod.z.object({
|
|
111683
|
-
|
|
111683
|
+
code: zod.z.string().describe("The exact code from the provided learning objectives list that matches the document content."),
|
|
111684
111684
|
confidence: zod.z.number().min(0).max(100).describe("A confidence score (0-100) of how well the document maps to this LO."),
|
|
111685
111685
|
reasoning: zod.z.string().describe("A brief explanation for why this mapping is relevant.")
|
|
111686
111686
|
});
|
|
@@ -111716,7 +111716,7 @@ You are an expert curriculum analyst. Your task is to analyze a given document a
|
|
|
111716
111716
|
1. **Overall Relevance Assessment:** Read the document content and compare it against the entire list of LOs. Assign an overall "relevanceScore" from 0 (completely unrelated) to 100 (perfectly aligned with one or more LOs).
|
|
111717
111717
|
|
|
111718
111718
|
2. **Specific Mapping:** Identify which specific LOs from the list are directly addressed by the document. For each match you find, provide:
|
|
111719
|
-
- The exact "
|
|
111719
|
+
- The exact "code" of the matched LO.
|
|
111720
111720
|
- A "confidence" score (0-100) for that specific match.
|
|
111721
111721
|
- A brief "reasoning" in ${language3} explaining why the document content maps to that LO.
|
|
111722
111722
|
|
|
@@ -111731,7 +111731,7 @@ Return a single, valid JSON object in this EXACT format. Do not include any othe
|
|
|
111731
111731
|
"isFreestyleRecommended": false,
|
|
111732
111732
|
"mappedLOs": [
|
|
111733
111733
|
{
|
|
111734
|
-
"
|
|
111734
|
+
"code": "SWIFT_FUNC_01",
|
|
111735
111735
|
"confidence": 95,
|
|
111736
111736
|
"reasoning": "The document provides a detailed explanation of function syntax and default parameters, which directly aligns with this learning objective."
|
|
111737
111737
|
}
|
|
@@ -111826,7 +111826,7 @@ Return the response as a single JSON object with a key "generatedQuestions" cont
|
|
|
111826
111826
|
"correctTempOptionId": "A",
|
|
111827
111827
|
"explanation": "The document states that mitochondria are the powerhouses of the cell, responsible for cellular respiration.",
|
|
111828
111828
|
"points": 10,
|
|
111829
|
-
"difficulty": "
|
|
111829
|
+
"difficulty": "Medium",
|
|
111830
111830
|
"topic": "Cell Biology"
|
|
111831
111831
|
},
|
|
111832
111832
|
{
|
|
@@ -111835,7 +111835,7 @@ Return the response as a single JSON object with a key "generatedQuestions" cont
|
|
|
111835
111835
|
"correctAnswer": false,
|
|
111836
111836
|
"explanation": "The text specifies that the cell wall is a feature of plant cells, not animal cells.",
|
|
111837
111837
|
"points": 10,
|
|
111838
|
-
"difficulty": "
|
|
111838
|
+
"difficulty": "Easy",
|
|
111839
111839
|
"topic": "Cell Biology"
|
|
111840
111840
|
}
|
|
111841
111841
|
]
|
|
@@ -112668,7 +112668,7 @@ var QuizReview = ({
|
|
|
112668
112668
|
};
|
|
112669
112669
|
return /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full max-w-4xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-3xl font-headline text-center flex items-center justify-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BookOpen, { className: "mr-3 h-8 w-8 text-primary" }), "AI-Powered Quiz Review"), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, { className: "text-center text-lg" }, "Let's break down your results and reinforce your learning.")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "bg-muted/30" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-yellow-500" }), "Key Concepts Summary")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: reviewContent.overallSummary }))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-xl font-semibold mb-2" }, "Detailed Question Analysis"), /* @__PURE__ */ React169__namespace.default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, quizResult.questionResults.map((qResult, index3) => {
|
|
112670
112670
|
const aiReview = getReviewForQuestion(qResult.questionId);
|
|
112671
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(AccordionItem2, { value: `item-${index3}`, key: qResult.questionId }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-between w-full pr-2" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-left font-
|
|
112671
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(AccordionItem2, { value: `item-${index3}`, key: qResult.questionId }, /* @__PURE__ */ React169__namespace.default.createElement(AccordionTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-between w-full pr-2" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-left font-Medium" }, "Question ", index3 + 1), qResult.isCorrect ? /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-sm text-green-600 font-Medium flex items-center gap-1" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleCheckBig, { className: "h-4 w-4" }), " Correct") : /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-sm text-destructive font-Medium flex items-center gap-1" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleX, { className: "h-4 w-4" }), " Incorrect"))), /* @__PURE__ */ React169__namespace.default.createElement(AccordionContent2, { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-semibold mb-2" }, "Original Question:"), /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: qResult.prompt })), qResult.questionType === "coding" ? renderCodingResult(qResult) : renderStandardResult(qResult), aiReview && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border-l-4 border-primary bg-primary/10 rounded-r-md" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-semibold text-primary mb-2" }, "AI Tutor Explanation:"), /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: aiReview.explanation }))));
|
|
112672
112672
|
}))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-xl font-semibold mb-2" }, "Topics for Further Study"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-wrap gap-2" }, reviewContent.relatedTopics.map((topic, index3) => /* @__PURE__ */ React169__namespace.default.createElement(Badge2, { key: index3, variant: "secondary", className: "text-base px-3 py-1" }, topic))))), /* @__PURE__ */ React169__namespace.default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: onBackToResults, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Back to Results"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: onExit, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), "Finish & Exit")));
|
|
112673
112673
|
};
|
|
112674
112674
|
|
|
@@ -112715,7 +112715,7 @@ var PracticeHistoryTable = ({ history: history2, maxHeight = "400px" }) => {
|
|
|
112715
112715
|
if (percentage >= 50) return "secondary";
|
|
112716
112716
|
return "destructive";
|
|
112717
112717
|
};
|
|
112718
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, null, t4("history.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("history.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "w-full border rounded-md", style: { height: maxHeight } }, /* @__PURE__ */ React169__namespace.default.createElement(TooltipProvider2, { delayDuration: 100 }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, { className: "sticky top-0 bg-muted z-10" }, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[120px]" }, t4("history.headers.date")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, t4("history.headers.topic")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[80px]" }, t4("history.headers.score")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[90px]" }, t4("history.headers.percentage")))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, history2.length > 0 ? history2.map((session) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: session.id, onClick: () => handleRowClick(session), className: "cursor-pointer" }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-
|
|
112718
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, null, t4("history.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("history.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "w-full border rounded-md", style: { height: maxHeight } }, /* @__PURE__ */ React169__namespace.default.createElement(TooltipProvider2, { delayDuration: 100 }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, { className: "sticky top-0 bg-muted z-10" }, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[120px]" }, t4("history.headers.date")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, t4("history.headers.topic")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[80px]" }, t4("history.headers.score")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[90px]" }, t4("history.headers.percentage")))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, history2.length > 0 ? history2.map((session) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: session.id, onClick: () => handleRowClick(session), className: "cursor-pointer" }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium text-xs text-muted-foreground" }, formatDate(session.timestamp)), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col gap-1.5" }, session.topics.map((topicInfo, index3) => /* @__PURE__ */ React169__namespace.default.createElement(Tooltip2, { key: index3 }, /* @__PURE__ */ React169__namespace.default.createElement(TooltipTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-semibold text-sm truncate" }, topicInfo.topic)), /* @__PURE__ */ React169__namespace.default.createElement(TooltipContent2, null, /* @__PURE__ */ React169__namespace.default.createElement("p", null, /* @__PURE__ */ React169__namespace.default.createElement("strong", null, t4("settingsModal.topics.tableHeaders.subject"), ":"), " ", topicInfo.subject), /* @__PURE__ */ React169__namespace.default.createElement("p", null, /* @__PURE__ */ React169__namespace.default.createElement("strong", null, t4("settingsModal.topics.tableHeaders.category"), ":"), " ", topicInfo.category)))))), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right font-mono text-sm" }, session.score !== null ? `${session.score}/${session.maxScore}` : "N/A"), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, session.percentage !== null && /* @__PURE__ */ React169__namespace.default.createElement(
|
|
112719
112719
|
Badge2,
|
|
112720
112720
|
{
|
|
112721
112721
|
variant: getPercentageBadgeVariant(session.percentage),
|
|
@@ -133530,12 +133530,12 @@ var ChartTooltipContent = React169__namespace.forwardRef(
|
|
|
133530
133530
|
const itemConfig = getPayloadConfigFromPayload(config3, item, key);
|
|
133531
133531
|
const value = !labelKey && typeof label === "string" ? config3[label]?.label || label : itemConfig?.label;
|
|
133532
133532
|
if (labelFormatter) {
|
|
133533
|
-
return /* @__PURE__ */ React169__namespace.createElement("div", { className: cn("font-
|
|
133533
|
+
return /* @__PURE__ */ React169__namespace.createElement("div", { className: cn("font-Medium", labelClassName) }, labelFormatter(value, payload));
|
|
133534
133534
|
}
|
|
133535
133535
|
if (!value) {
|
|
133536
133536
|
return null;
|
|
133537
133537
|
}
|
|
133538
|
-
return /* @__PURE__ */ React169__namespace.createElement("div", { className: cn("font-
|
|
133538
|
+
return /* @__PURE__ */ React169__namespace.createElement("div", { className: cn("font-Medium", labelClassName) }, value);
|
|
133539
133539
|
}, [
|
|
133540
133540
|
label,
|
|
133541
133541
|
labelFormatter,
|
|
@@ -133598,7 +133598,7 @@ var ChartTooltipContent = React169__namespace.forwardRef(
|
|
|
133598
133598
|
)
|
|
133599
133599
|
},
|
|
133600
133600
|
/* @__PURE__ */ React169__namespace.createElement("div", { className: "grid gap-1.5" }, nestLabel ? tooltipLabel : null, /* @__PURE__ */ React169__namespace.createElement("span", { className: "text-muted-foreground" }, itemConfig?.label || item.name)),
|
|
133601
|
-
item.value && /* @__PURE__ */ React169__namespace.createElement("span", { className: "font-mono font-
|
|
133601
|
+
item.value && /* @__PURE__ */ React169__namespace.createElement("span", { className: "font-mono font-Medium tabular-nums text-foreground" }, item.value.toLocaleString())
|
|
133602
133602
|
))
|
|
133603
133603
|
);
|
|
133604
133604
|
}))
|
|
@@ -134775,7 +134775,7 @@ var ManageTopics = () => {
|
|
|
134775
134775
|
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Card, { className: "w-full max-w-4xl mx-auto shadow-none border-none" }, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, { className: "px-1" }, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex items-center text-2xl font-headline" }, /* @__PURE__ */ React169__namespace.default.createElement(FileText, { className: "mr-3 h-6 w-6 text-primary" }), t4("settingsModal.topics.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("settingsModal.topics.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-6 px-1" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "tsv-importer", className: "text-lg font-semibold" }, t4("settingsModal.topics.importData")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "mt-2 p-4 border border-dashed rounded-lg flex flex-col sm:flex-row items-center gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-grow" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.importHint")), /* @__PURE__ */ React169__namespace.default.createElement("a", { href: "#", className: "text-xs text-primary hover:underline", onClick: (e3) => {
|
|
134776
134776
|
e3.preventDefault();
|
|
134777
134777
|
alert("Header format:\nLO ID LO Description Subject Category Topic Keywords Grade STEM Element(s) Bloom\u2019s Level(s) Guideline");
|
|
134778
|
-
} }, t4("settingsModal.topics.viewHeaderFormat"))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "tsv-importer", type: "file", ref: fileInputRef, accept: ".tsv,text/tab-separated-values", onChange: handleFileChange, className: "hidden" }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: () => fileInputRef.current?.click() }, /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.chooseFile")))), importErrors.length > 0 && /* @__PURE__ */ React169__namespace.default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__namespace.default.createElement(AlertTitle, null, t4("settingsModal.topics.importErrors")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDescription, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-24 mt-2" }, /* @__PURE__ */ React169__namespace.default.createElement("ul", { className: "list-disc list-inside text-xs space-y-1" }, importErrors.map((error, index3) => /* @__PURE__ */ React169__namespace.default.createElement("li", { key: index3 }, error)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-lg font-semibold" }, t4("settingsModal.topics.currentDataTitle")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.showingCount", { shown: filteredLearningObjectives.length, total: learningObjectives.length }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "mb-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subject-filter" }, t4("settingsModal.topics.filterBySubject")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: selectedSubjectFilter, onValueChange: setSelectedSubjectFilter, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "subject-filter", className: "w-full sm:w-[280px]" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.topics.filterPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "all" }, t4("settingsModal.topics.allSubjects")), subjects.map((subject) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: subject, value: subject }, subject))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-72" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, { className: "sticky top-0 bg-muted" }, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.subject")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.category")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, t4("settingsModal.topics.tableHeaders.topic")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[100px]" }, t4("settingsModal.topics.tableHeaders.
|
|
134778
|
+
} }, t4("settingsModal.topics.viewHeaderFormat"))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "tsv-importer", type: "file", ref: fileInputRef, accept: ".tsv,text/tab-separated-values", onChange: handleFileChange, className: "hidden" }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: () => fileInputRef.current?.click() }, /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.chooseFile")))), importErrors.length > 0 && /* @__PURE__ */ React169__namespace.default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__namespace.default.createElement(AlertTitle, null, t4("settingsModal.topics.importErrors")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDescription, null, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-24 mt-2" }, /* @__PURE__ */ React169__namespace.default.createElement("ul", { className: "list-disc list-inside text-xs space-y-1" }, importErrors.map((error, index3) => /* @__PURE__ */ React169__namespace.default.createElement("li", { key: index3 }, error)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between mb-2" }, /* @__PURE__ */ React169__namespace.default.createElement("h3", { className: "text-lg font-semibold" }, t4("settingsModal.topics.currentDataTitle")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.showingCount", { shown: filteredLearningObjectives.length, total: learningObjectives.length }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "mb-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subject-filter" }, t4("settingsModal.topics.filterBySubject")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: selectedSubjectFilter, onValueChange: setSelectedSubjectFilter, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "subject-filter", className: "w-full sm:w-[280px]" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.topics.filterPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "all" }, t4("settingsModal.topics.allSubjects")), subjects.map((subject) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: subject, value: subject }, subject))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-72" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, { className: "sticky top-0 bg-muted" }, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.subject")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.category")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, t4("settingsModal.topics.tableHeaders.topic")), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "w-[100px]" }, t4("settingsModal.topics.tableHeaders.code")))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { colSpan: 4, className: "text-center" }, t4("common.loading"))) : filteredLearningObjectives.length > 0 ? filteredLearningObjectives.map((lo) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: lo.code }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, lo.subject), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, lo.category), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, lo.topic), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, lo.code))) : /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { colSpan: 4, className: "text-center h-24 text-muted-foreground" }, t4("settingsModal.topics.emptyData"))))))))), /* @__PURE__ */ React169__namespace.default.createElement(CardFooter, { className: "px-1" }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "destructive", disabled: learningObjectives.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.clearAllData"))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, t4("settingsModal.topics.clearDataConfirmationTitle")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, t4("settingsModal.topics.clearDataConfirmationMessage", { count: learningObjectives.length }))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: handleClearData }, t4("settingsModal.topics.confirmDelete"))))))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isConfirmModalOpen, onOpenChange: setIsConfirmModalOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, t4("settingsModal.topics.confirmModal.title")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, t4("settingsModal.topics.confirmModal.description", { count: parsedImportData?.data.length || 0 }))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, { className: "gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { onClick: () => setParsedImportData(null) }, t4("common.cancel")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: handleConfirmMerge, className: "bg-blue-600 hover:bg-blue-700" }, t4("settingsModal.topics.confirmModal.mergeButton")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: handleConfirmOverwrite, className: "bg-amber-600 hover:bg-amber-700" }, t4("settingsModal.topics.confirmModal.overwriteButton"))))));
|
|
134779
134779
|
};
|
|
134780
134780
|
|
|
134781
134781
|
// src/react-ui/components/app/ManageImageContexts.tsx
|
|
@@ -135182,7 +135182,7 @@ var SettingsModal = ({ isOpen, onClose, defaultTab = "personal" }) => {
|
|
|
135182
135182
|
return goal.id;
|
|
135183
135183
|
}
|
|
135184
135184
|
};
|
|
135185
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl lg:max-w-3xl max-h-[85vh] flex flex-col" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, { className: "shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Settings, { className: "mr-2 h-5 w-5 text-primary" }), t4("settingsModal.title")), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, t4("settingsModal.description"))), /* @__PURE__ */ React169__namespace.default.createElement(Tabs2, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "pt-2 flex-1 flex flex-col min-h-0" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-5 shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "personal" }, /* @__PURE__ */ React169__namespace.default.createElement(User, { className: "mr-1 h-4 w-4" }), t4("settingsModal.personalTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "topics" }, /* @__PURE__ */ React169__namespace.default.createElement(ListTodo, { className: "mr-1 h-4 w-4" }), t4("settingsModal.topicsTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "imageContexts" }, /* @__PURE__ */ React169__namespace.default.createElement(ImagePlus, { className: "mr-1 h-4 w-4" }), "Images"), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "layout" }, /* @__PURE__ */ React169__namespace.default.createElement(LayoutDashboard, { className: "mr-1 h-4 w-4" }), t4("settingsModal.layoutTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "apiKeys" }, /* @__PURE__ */ React169__namespace.default.createElement(KeyRound, { className: "mr-1 h-4 w-4" }), t4("settingsModal.apiKeysTab"))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "personal", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-full pr-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3" }, t4("settingsModal.personal.basicInfo")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "full-name" }, t4("settingsModal.personal.fullName")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "full-name", value: fullName, onChange: (e3) => setFullName(e3.target.value), placeholder: t4("settingsModal.personal.fullNamePlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "weekly-goal" }, t4("settingsModal.personal.weeklyGoal")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "weekly-goal", type: "number", value: weeklyGoal, onChange: (e3) => setWeeklyGoal(parseInt(e3.target.value, 10) || 0), min: "1" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "language-select", className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Languages, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.languageSelectLabel")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: language3, onValueChange: changeLanguage2 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "language-select" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select a language..." })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "en" }, "English"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "vi" }, "Ti\u1EBFng Vi\u1EC7t")))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3 flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Target, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.advancedGoals")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2 mb-4" }, advancedGoals.map((goal) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: goal.id, className: "flex items-center justify-between p-2 bg-muted/50 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm flex-1" }, renderGoalDescription(goal)), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleDeleteGoal(goal.id) }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), advancedGoals.length === 0 && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.personal.noGoals"))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement("h5", { className: "font-medium" }, t4("settingsModal.personal.addNewGoal")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalType, onValueChange: (v) => setNewGoalType(v) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.goalTypePlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "average_score_subject" }, t4("settingsModal.personal.goalType.avgScoreSubject")), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "mastery_topic" }, t4("settingsModal.personal.goalType.masteryTopic")))), newGoalType === "average_score_subject" && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") })), newGoalType === "mastery_topic" && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalTopic, onValueChange: setNewGoalTopic, disabled: !newGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.topicPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, topicsForSelectedSubject.map((t5) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t5, value: t5 }, t5)))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") }), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalConsecutive, onChange: (e3) => setNewGoalConsecutive(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.consecutiveSessionsPlaceholder") })), newGoalType && /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", onClick: handleAddNewGoal }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.addGoalButton"))))))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "topics", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ManageTopics, null)), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "imageContexts", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ManageImageContexts, null)), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "layout", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold" }, t4("settingsModal.layout.title")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1 mb-3" }, t4("settingsModal.layout.description")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(RefreshCw, { className: "mr-2 h-4 w-4" }), t4("settingsModal.layout.resetButton"))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, t4("settingsModal.layout.resetConfirmationTitle")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, t4("settingsModal.layout.resetConfirmationMessage"))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: handleResetLayout }, t4("settingsModal.layout.confirmReset"))))))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "apiKeys", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gemini-api-key" }, t4("settingsModal.apiKeys.geminiKey")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "gemini-api-key", type: "password", value: geminiApiKey, onChange: (e3) => setGeminiApiKey(e3.target.value), placeholder: t4("settingsModal.apiKeys.geminiKeyPlaceholder") }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, t4("settingsModal.apiKeys.storageHint"))))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, { className: "gap-2 sm:justify-end pt-4 shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, t4("common.close"))), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", onClick: handleSave }, /* @__PURE__ */ React169__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }), t4("settingsModal.saveChanges")))));
|
|
135185
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl lg:max-w-3xl max-h-[85vh] flex flex-col" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, { className: "shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Settings, { className: "mr-2 h-5 w-5 text-primary" }), t4("settingsModal.title")), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, t4("settingsModal.description"))), /* @__PURE__ */ React169__namespace.default.createElement(Tabs2, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "pt-2 flex-1 flex flex-col min-h-0" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-5 shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "personal" }, /* @__PURE__ */ React169__namespace.default.createElement(User, { className: "mr-1 h-4 w-4" }), t4("settingsModal.personalTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "topics" }, /* @__PURE__ */ React169__namespace.default.createElement(ListTodo, { className: "mr-1 h-4 w-4" }), t4("settingsModal.topicsTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "imageContexts" }, /* @__PURE__ */ React169__namespace.default.createElement(ImagePlus, { className: "mr-1 h-4 w-4" }), "Images"), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "layout" }, /* @__PURE__ */ React169__namespace.default.createElement(LayoutDashboard, { className: "mr-1 h-4 w-4" }), t4("settingsModal.layoutTab")), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "apiKeys" }, /* @__PURE__ */ React169__namespace.default.createElement(KeyRound, { className: "mr-1 h-4 w-4" }), t4("settingsModal.apiKeysTab"))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "personal", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-full pr-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3" }, t4("settingsModal.personal.basicInfo")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "full-name" }, t4("settingsModal.personal.fullName")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "full-name", value: fullName, onChange: (e3) => setFullName(e3.target.value), placeholder: t4("settingsModal.personal.fullNamePlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "weekly-goal" }, t4("settingsModal.personal.weeklyGoal")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "weekly-goal", type: "number", value: weeklyGoal, onChange: (e3) => setWeeklyGoal(parseInt(e3.target.value, 10) || 0), min: "1" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "language-select", className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Languages, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.languageSelectLabel")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: language3, onValueChange: changeLanguage2 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "language-select" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select a language..." })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "en" }, "English"), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "vi" }, "Ti\u1EBFng Vi\u1EC7t")))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3 flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Target, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.advancedGoals")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2 mb-4" }, advancedGoals.map((goal) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: goal.id, className: "flex items-center justify-between p-2 bg-muted/50 rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm flex-1" }, renderGoalDescription(goal)), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleDeleteGoal(goal.id) }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), advancedGoals.length === 0 && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.personal.noGoals"))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__namespace.default.createElement("h5", { className: "font-Medium" }, t4("settingsModal.personal.addNewGoal")), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalType, onValueChange: (v) => setNewGoalType(v) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.goalTypePlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "average_score_subject" }, t4("settingsModal.personal.goalType.avgScoreSubject")), /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { value: "mastery_topic" }, t4("settingsModal.personal.goalType.masteryTopic")))), newGoalType === "average_score_subject" && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") })), newGoalType === "mastery_topic" && /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: newGoalTopic, onValueChange: setNewGoalTopic, disabled: !newGoalSubject }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.topicPlaceholder") })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, topicsForSelectedSubject.map((t5) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: t5, value: t5 }, t5)))), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") }), /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "number", value: newGoalConsecutive, onChange: (e3) => setNewGoalConsecutive(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.consecutiveSessionsPlaceholder") })), newGoalType && /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", onClick: handleAddNewGoal }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.addGoalButton"))))))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "topics", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ManageTopics, null)), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "imageContexts", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__namespace.default.createElement(ManageImageContexts, null)), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "layout", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold" }, t4("settingsModal.layout.title")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mt-1 mb-3" }, t4("settingsModal.layout.description")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(RefreshCw, { className: "mr-2 h-4 w-4" }), t4("settingsModal.layout.resetButton"))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, t4("settingsModal.layout.resetConfirmationTitle")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, t4("settingsModal.layout.resetConfirmationMessage"))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: handleResetLayout }, t4("settingsModal.layout.confirmReset"))))))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "apiKeys", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gemini-api-key" }, t4("settingsModal.apiKeys.geminiKey")), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "gemini-api-key", type: "password", value: geminiApiKey, onChange: (e3) => setGeminiApiKey(e3.target.value), placeholder: t4("settingsModal.apiKeys.geminiKeyPlaceholder") }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, t4("settingsModal.apiKeys.storageHint"))))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, { className: "gap-2 sm:justify-end pt-4 shrink-0" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, t4("common.close"))), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", onClick: handleSave }, /* @__PURE__ */ React169__namespace.default.createElement(Save, { className: "mr-2 h-4 w-4" }), t4("settingsModal.saveChanges")))));
|
|
135186
135186
|
};
|
|
135187
135187
|
|
|
135188
135188
|
// src/react-ui/components/dashboard/Cheatsheet.tsx
|
|
@@ -135281,7 +135281,7 @@ var Cheatsheet = () => {
|
|
|
135281
135281
|
}
|
|
135282
135282
|
return null;
|
|
135283
135283
|
};
|
|
135284
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), t4("knowledgeCards.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("knowledgeCards.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "search", placeholder: t4("knowledgeCards.searchPlaceholder"), value: searchQuery, onChange: (e3) => setSearchQuery2(e3.target.value), disabled: allCards.length === 0 }), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, allCards.length > 0 ? filteredCards.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredCards.map((card) => /* @__PURE__ */ React169__namespace.default.createElement("button", { key: card.id, onClick: () => handleCardClick(card.id), className: "w-full text-left p-2 rounded-md hover:bg-accent text-sm font-
|
|
135284
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(React169__namespace.default.Fragment, null, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), t4("knowledgeCards.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("knowledgeCards.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Input, { type: "search", placeholder: t4("knowledgeCards.searchPlaceholder"), value: searchQuery, onChange: (e3) => setSearchQuery2(e3.target.value), disabled: allCards.length === 0 }), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, allCards.length > 0 ? filteredCards.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredCards.map((card) => /* @__PURE__ */ React169__namespace.default.createElement("button", { key: card.id, onClick: () => handleCardClick(card.id), className: "w-full text-left p-2 rounded-md hover:bg-accent text-sm font-Medium" }, card.concept))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("knowledgeCards.noMatch"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("knowledgeCards.empty")))), renderActionArea())), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
135285
135285
|
CardViewerDialog,
|
|
135286
135286
|
{
|
|
135287
135287
|
isOpen: isDialogOpen,
|
|
@@ -135308,9 +135308,9 @@ var StatCard = ({
|
|
|
135308
135308
|
isLoading = false
|
|
135309
135309
|
}) => {
|
|
135310
135310
|
if (isLoading) {
|
|
135311
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-sm font-
|
|
135311
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-sm font-Medium" }, title), /* @__PURE__ */ React169__namespace.default.createElement(Skeleton, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement(Skeleton, { className: "h-8 w-3/4 mb-2" }), /* @__PURE__ */ React169__namespace.default.createElement(Skeleton, { className: "h-4 w-1/2" })));
|
|
135312
135312
|
}
|
|
135313
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-sm font-
|
|
135313
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "text-sm font-Medium" }, title), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "text-muted-foreground" }, icon)), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "text-2xl font-bold" }, value, unit2 && /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "text-xl font-Medium text-muted-foreground ml-1" }, unit2)), context && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, context)));
|
|
135314
135314
|
};
|
|
135315
135315
|
|
|
135316
135316
|
// src/react-ui/components/dashboard/PerformanceSnapshot.tsx
|
|
@@ -135409,7 +135409,7 @@ var RoadmapChecklist = () => {
|
|
|
135409
135409
|
}, []);
|
|
135410
135410
|
const handleStartPractice = React169.useCallback((item) => {
|
|
135411
135411
|
const practiceConfig = {
|
|
135412
|
-
loIds: [item.
|
|
135412
|
+
loIds: [item.code],
|
|
135413
135413
|
difficulty: item.suggestedDifficulty,
|
|
135414
135414
|
language: "Vietnamese"
|
|
135415
135415
|
};
|
|
@@ -135419,7 +135419,7 @@ var RoadmapChecklist = () => {
|
|
|
135419
135419
|
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CalendarCheck, { className: "mr-2 h-5 w-5 text-primary" }), t4("roadmap.title")), /* @__PURE__ */ React169__namespace.default.createElement(CardDescription, null, t4("roadmap.description"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, !roadmap || !roadmap.items || roadmap.items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "text-center text-muted-foreground py-8" }, /* @__PURE__ */ React169__namespace.default.createElement("p", null, t4("roadmap.emptyState")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm" }, t4("roadmap.emptyStateSuggestion"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, roadmap.items.map((item, index3) => /* @__PURE__ */ React169__namespace.default.createElement(
|
|
135420
135420
|
"div",
|
|
135421
135421
|
{
|
|
135422
|
-
key: `${item.
|
|
135422
|
+
key: `${item.code}-${index3}`,
|
|
135423
135423
|
className: "flex items-center justify-between p-3 border rounded-md bg-background hover:bg-muted/50 transition-colors"
|
|
135424
135424
|
},
|
|
135425
135425
|
/* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React169__namespace.default.createElement(
|
|
@@ -136962,7 +136962,7 @@ function Calendar2({
|
|
|
136962
136962
|
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
|
136963
136963
|
month: "space-y-4",
|
|
136964
136964
|
caption: "flex justify-center pt-1 relative items-center",
|
|
136965
|
-
caption_label: "text-sm font-
|
|
136965
|
+
caption_label: "text-sm font-Medium",
|
|
136966
136966
|
nav: "space-x-1 flex items-center",
|
|
136967
136967
|
nav_button: cn(
|
|
136968
136968
|
buttonVariants({ variant: "outline" }),
|
|
@@ -137084,7 +137084,7 @@ var AnalysisDialog = ({ isOpen, onClose }) => {
|
|
|
137084
137084
|
}
|
|
137085
137085
|
try {
|
|
137086
137086
|
const allAvailableTopics = TopicDataService.getData().map((lo) => ({
|
|
137087
|
-
|
|
137087
|
+
code: lo.code,
|
|
137088
137088
|
subject: lo.subject,
|
|
137089
137089
|
category: lo.category,
|
|
137090
137090
|
topic: lo.topic
|
|
@@ -137537,7 +137537,7 @@ var GeneratedQuizzesCard = () => {
|
|
|
137537
137537
|
onChange: (e3) => setSearchQuery2(e3.target.value),
|
|
137538
137538
|
disabled: uniqueQuizzes.length === 0
|
|
137539
137539
|
}
|
|
137540
|
-
), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm font-
|
|
137540
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm font-Medium truncate pr-2", title: quiz.title }, quiz.title), /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", variant: "ghost", onClick: () => handleRetake(quiz) }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlay, { className: "mr-2 h-4 w-4" }), t4("common.retake"))))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.generated.noMatch"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.generated.empty"))))));
|
|
137541
137541
|
};
|
|
137542
137542
|
|
|
137543
137543
|
// src/react-ui/components/app/UploadResourceModal.tsx
|
|
@@ -137617,11 +137617,12 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137617
137617
|
language: i18n.language === "vi" ? "Vietnamese" : "English",
|
|
137618
137618
|
documentContent: textContent,
|
|
137619
137619
|
learningObjectives: learningObjectives.map((lo) => ({
|
|
137620
|
-
|
|
137620
|
+
name: lo.name,
|
|
137621
|
+
code: lo.code,
|
|
137621
137622
|
subject: lo.subject,
|
|
137622
137623
|
category: lo.category,
|
|
137623
137624
|
topic: lo.topic,
|
|
137624
|
-
|
|
137625
|
+
description: lo.description || ""
|
|
137625
137626
|
}))
|
|
137626
137627
|
}, apiKey);
|
|
137627
137628
|
setAnalysisResult(result);
|
|
@@ -137654,12 +137655,12 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137654
137655
|
generatedQuestions = result.generatedQuestions;
|
|
137655
137656
|
} else {
|
|
137656
137657
|
const plan = analysisResult.mappedLOs.map((lo) => {
|
|
137657
|
-
const sourceLO = TopicDataService.getData().find((orig) => orig.
|
|
137658
|
+
const sourceLO = TopicDataService.getData().find((orig) => orig.code === lo.code);
|
|
137658
137659
|
return {
|
|
137659
137660
|
plannedTopic: lo.reasoning,
|
|
137660
137661
|
plannedQuestionType: "multiple_choice",
|
|
137661
137662
|
plannedBloomLevel: "understanding",
|
|
137662
|
-
originalLoId: lo.
|
|
137663
|
+
originalLoId: lo.code,
|
|
137663
137664
|
originalTopic: sourceLO?.topic,
|
|
137664
137665
|
originalCategory: sourceLO?.category,
|
|
137665
137666
|
originalSubject: sourceLO?.subject
|
|
@@ -137704,7 +137705,7 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137704
137705
|
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center h-48" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "mt-4 text-muted-foreground font-semibold" }, stage === "analyzing" && t4("dialogs.uploadResource.analyzing"), stage === "generating" && t4("dialogs.uploadResource.generating")));
|
|
137705
137706
|
case "result":
|
|
137706
137707
|
if (!analysisResult) return null;
|
|
137707
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "py-4 space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "p-4" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-2" }, "AI Analysis Complete"), analysisResult.isFreestyleRecommended ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-start gap-3 text-amber-700" }, /* @__PURE__ */ React169__namespace.default.createElement(BrainCircuit, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-
|
|
137708
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "py-4 space-y-4" }, /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardContent, { className: "p-4" }, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-2" }, "AI Analysis Complete"), analysisResult.isFreestyleRecommended ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-start gap-3 text-amber-700" }, /* @__PURE__ */ React169__namespace.default.createElement(BrainCircuit, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-Medium" }, t4("dialogs.uploadResource.freestyleRecommended")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm" }, t4("dialogs.uploadResource.freestyleDescription")))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-start gap-3 text-green-700" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleCheckBig, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-Medium" }, t4("dialogs.uploadResource.curriculumMatch")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm" }, t4("dialogs.uploadResource.curriculumDescription")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-xs mt-2 font-semibold" }, t4("dialogs.uploadResource.mappedTopics")), /* @__PURE__ */ React169__namespace.default.createElement("ul", { className: "list-disc list-inside text-xs" }, analysisResult.mappedLOs.map((lo) => /* @__PURE__ */ React169__namespace.default.createElement("li", { key: lo.code }, TopicDataService.getData().find((orig) => orig.code === lo.code)?.topic || lo.code))))))), error && /* @__PURE__ */ React169__namespace.default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__namespace.default.createElement(AlertTitle, null, t4("common.error")), /* @__PURE__ */ React169__namespace.default.createElement(AlertDescription, null, error)));
|
|
137708
137709
|
}
|
|
137709
137710
|
};
|
|
137710
137711
|
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(FileText, { className: "mr-2 h-5 w-5" }), t4("dialogs.uploadResource.title")), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, t4("dialogs.uploadResource.description"))), renderContent3(), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline" }, t4("common.cancel"))), stage === "result" && /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleGenerateQuiz }, /* @__PURE__ */ React169__namespace.default.createElement(Sparkles, { className: "mr-2 h-4 w-4" }), t4("dialogs.uploadResource.generateButton")))));
|
|
@@ -137825,7 +137826,7 @@ var FreestyleQuizzesCard = () => {
|
|
|
137825
137826
|
onChange: (e3) => setSearchQuery2(e3.target.value),
|
|
137826
137827
|
disabled: uniqueQuizzes.length === 0
|
|
137827
137828
|
}
|
|
137828
|
-
), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm font-
|
|
137829
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm font-Medium truncate pr-2", title: quiz.title }, quiz.title), /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", variant: "ghost", onClick: () => handleRetake(quiz) }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlay, { className: "mr-2 h-4 w-4" }), t4("common.retake"))))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.freestyle.noMatch"))) : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.freestyle.empty"))))));
|
|
137829
137830
|
};
|
|
137830
137831
|
|
|
137831
137832
|
// src/react-ui/components/common/ClientTranslation.tsx
|
|
@@ -137975,7 +137976,7 @@ var PersonalPracticeDashboard = ({ settingsPath, initialHistory, initialStats, i
|
|
|
137975
137976
|
}
|
|
137976
137977
|
try {
|
|
137977
137978
|
const allAvailableTopics = TopicDataService.getData().map((lo) => ({
|
|
137978
|
-
|
|
137979
|
+
code: lo.code,
|
|
137979
137980
|
subject: lo.subject,
|
|
137980
137981
|
category: lo.category,
|
|
137981
137982
|
topic: lo.topic
|
|
@@ -138469,7 +138470,7 @@ var PracticeModeController = () => {
|
|
|
138469
138470
|
try {
|
|
138470
138471
|
const config3 = JSON.parse(suggestedConfigString);
|
|
138471
138472
|
const allLOs = TopicDataService.getData();
|
|
138472
|
-
const suggestedLOs = allLOs.filter((lo) => config3.loIds?.includes(lo.
|
|
138473
|
+
const suggestedLOs = allLOs.filter((lo) => config3.loIds?.includes(lo.code));
|
|
138473
138474
|
if (suggestedLOs.length > 0) {
|
|
138474
138475
|
setInitialSuggestedLOs(suggestedLOs);
|
|
138475
138476
|
setInitialSuggestedDifficulty(config3.difficulty || "Medium");
|
|
@@ -138512,9 +138513,10 @@ var PracticeModeController = () => {
|
|
|
138512
138513
|
totalQuestions,
|
|
138513
138514
|
numCodingQuestions,
|
|
138514
138515
|
topics: selectedLOs.map((lo) => ({
|
|
138515
|
-
topic: lo.
|
|
138516
|
+
topic: lo.name || lo.code,
|
|
138517
|
+
// FIX: Provide fallback for name
|
|
138516
138518
|
ratio: 100 / selectedLOs.length,
|
|
138517
|
-
originalLoId: lo.
|
|
138519
|
+
originalLoId: lo.code,
|
|
138518
138520
|
originalSubject: lo.subject,
|
|
138519
138521
|
originalCategory: lo.category,
|
|
138520
138522
|
originalTopic: lo.topic
|
|
@@ -138706,7 +138708,7 @@ var SuggestionDialog = ({
|
|
|
138706
138708
|
if (!suggestion) {
|
|
138707
138709
|
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center h-64 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-muted-foreground text-center" }, "Kh\xF4ng th\u1EC3 t\u1EA1o g\u1EE3i \xFD v\xE0o l\xFAc n\xE0y. Vui l\xF2ng th\u1EED l\u1EA1i."));
|
|
138708
138710
|
}
|
|
138709
|
-
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 bg-muted/50 rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: suggestion.suggestionText })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3" }, "K\u1EBF ho\u1EA1ch Luy\u1EC7n t\u1EADp \u0111\u01B0\u1EE3c G\u1EE3i \xFD:"), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "max-h-[200px] pr-3" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, suggestion.suggestedTopics.map((topic) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: topic.
|
|
138711
|
+
return /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "p-4 bg-muted/50 rounded-lg" }, /* @__PURE__ */ React169__namespace.default.createElement(MarkdownRenderer, { content: suggestion.suggestionText })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement("h4", { className: "font-semibold mb-3" }, "K\u1EBF ho\u1EA1ch Luy\u1EC7n t\u1EADp \u0111\u01B0\u1EE3c G\u1EE3i \xFD:"), /* @__PURE__ */ React169__namespace.default.createElement(ScrollArea2, { className: "max-h-[200px] pr-3" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, suggestion.suggestedTopics.map((topic) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: topic.code, className: "flex items-center justify-between p-3 border rounded-md" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex-1 mr-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2 mb-1" }, topic.reason === "review" ? /* @__PURE__ */ React169__namespace.default.createElement(Badge2, { variant: "destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(RefreshCw, { className: "h-3 w-3 mr-1.5" }), "\xD4n t\u1EADp") : /* @__PURE__ */ React169__namespace.default.createElement(Badge2, { variant: "secondary" }, /* @__PURE__ */ React169__namespace.default.createElement(Search, { className: "h-3 w-3 mr-1.5" }), "Kh\xE1m ph\xE1")), /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "font-Medium" }, topic.topicName)), /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", onClick: () => onStartSuggestedPractice(topic) }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlay, { className: "h-4 w-4 mr-2" }), "B\u1EAFt \u0111\u1EA7u")))))));
|
|
138710
138712
|
};
|
|
138711
138713
|
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-lg md:max-w-xl" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, { className: "flex items-center text-2xl" }, /* @__PURE__ */ React169__namespace.default.createElement(Lightbulb, { className: "h-6 w-6 mr-2 text-yellow-500" }), "G\u1EE3i \xFD Luy\u1EC7n t\u1EADp t\u1EEB AI"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "D\u1EF1a tr\xEAn k\u1EBFt qu\u1EA3 g\u1EA7n \u0111\xE2y, \u0111\xE2y l\xE0 nh\u1EEFng g\xEC gia s\u01B0 AI \u0111\u1EC1 xu\u1EA5t cho b\u1EA1n.")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "py-4" }, renderContent3()), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline" }, "\u0110\xF3ng")))));
|
|
138712
138714
|
};
|
|
@@ -138716,12 +138718,96 @@ init_react_shim();
|
|
|
138716
138718
|
|
|
138717
138719
|
// src/react-ui/components/metadata/SubjectManager.tsx
|
|
138718
138720
|
init_react_shim();
|
|
138721
|
+
|
|
138722
|
+
// src/react-ui/components/metadata/MetadataImportControls.tsx
|
|
138723
|
+
init_react_shim();
|
|
138724
|
+
function MetadataImportControls({ metadataName, onImport }) {
|
|
138725
|
+
const [isOpen, setIsOpen] = React169.useState(false);
|
|
138726
|
+
const [jsonString, setJsonString] = React169.useState("");
|
|
138727
|
+
const [isImporting, startImportTransition] = React169.useTransition();
|
|
138728
|
+
const fileInputRef = React169.useRef(null);
|
|
138729
|
+
const { toast: toast2 } = useToast();
|
|
138730
|
+
const processAndImportRecords = async (records, importSource) => {
|
|
138731
|
+
if (records.length === 0) {
|
|
138732
|
+
toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
|
|
138733
|
+
return;
|
|
138734
|
+
}
|
|
138735
|
+
await onImport(records);
|
|
138736
|
+
setIsOpen(false);
|
|
138737
|
+
setJsonString("");
|
|
138738
|
+
};
|
|
138739
|
+
const handleFileSelected = (event) => {
|
|
138740
|
+
const file = event.target.files?.[0];
|
|
138741
|
+
if (!file) return;
|
|
138742
|
+
if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
|
|
138743
|
+
toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
|
|
138744
|
+
if (event.target) event.target.value = "";
|
|
138745
|
+
return;
|
|
138746
|
+
}
|
|
138747
|
+
startImportTransition(async () => {
|
|
138748
|
+
try {
|
|
138749
|
+
const fileContent = await file.text();
|
|
138750
|
+
let records = [];
|
|
138751
|
+
if (file.type === "application/json") {
|
|
138752
|
+
records = JSON.parse(fileContent);
|
|
138753
|
+
if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
|
|
138754
|
+
} else if (file.name.endsWith(".csv")) {
|
|
138755
|
+
const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
|
|
138756
|
+
if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
|
|
138757
|
+
const headers = lines[0].split(",").map((h3) => h3.trim());
|
|
138758
|
+
records = lines.slice(1).map((line) => {
|
|
138759
|
+
const values = line.split(",").map((v) => v.trim());
|
|
138760
|
+
const record = {};
|
|
138761
|
+
headers.forEach((header, index3) => {
|
|
138762
|
+
record[header] = values[index3];
|
|
138763
|
+
});
|
|
138764
|
+
return record;
|
|
138765
|
+
});
|
|
138766
|
+
}
|
|
138767
|
+
await processAndImportRecords(records, "file");
|
|
138768
|
+
} catch (err) {
|
|
138769
|
+
toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
|
|
138770
|
+
}
|
|
138771
|
+
});
|
|
138772
|
+
if (event.target) event.target.value = "";
|
|
138773
|
+
};
|
|
138774
|
+
const handleJsonStringImport = () => {
|
|
138775
|
+
if (!jsonString.trim()) {
|
|
138776
|
+
toast2({ title: "Missing Data", description: "JSON string cannot be empty.", variant: "destructive" });
|
|
138777
|
+
return;
|
|
138778
|
+
}
|
|
138779
|
+
startImportTransition(async () => {
|
|
138780
|
+
try {
|
|
138781
|
+
const records = JSON.parse(jsonString);
|
|
138782
|
+
if (!Array.isArray(records)) throw new Error("JSON string must represent an array of objects.");
|
|
138783
|
+
await processAndImportRecords(records, "text");
|
|
138784
|
+
} catch (err) {
|
|
138785
|
+
toast2({ title: "Import Error", description: `Failed to process JSON string: ${err.message}`, variant: "destructive" });
|
|
138786
|
+
}
|
|
138787
|
+
});
|
|
138788
|
+
};
|
|
138789
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__namespace.default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__namespace.default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__namespace.default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-muted-foreground" }, "Select a JSON or CSV file. Ensure column names in the file match the table schema (e.g., 'code', 'name', 'subject_code')."), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__namespace.default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React169__namespace.default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__namespace.default.createElement(
|
|
138790
|
+
Textarea,
|
|
138791
|
+
{
|
|
138792
|
+
id: "jsonInput",
|
|
138793
|
+
value: jsonString,
|
|
138794
|
+
onChange: (e3) => setJsonString(e3.target.value),
|
|
138795
|
+
placeholder: '[{"code": "SUB1", "name": "Subject 1"}, ...]',
|
|
138796
|
+
rows: 8,
|
|
138797
|
+
className: "font-mono text-xs",
|
|
138798
|
+
disabled: isImporting
|
|
138799
|
+
}
|
|
138800
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleJsonStringImport, disabled: isImporting || !jsonString.trim() }, isImporting ? /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__namespace.default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), "Import from Text"))))));
|
|
138801
|
+
}
|
|
138802
|
+
|
|
138803
|
+
// src/react-ui/components/metadata/SubjectManager.tsx
|
|
138719
138804
|
function SubjectManager({
|
|
138720
138805
|
initialData,
|
|
138721
138806
|
isLoading: isLoadingProp,
|
|
138722
138807
|
onAdd,
|
|
138723
138808
|
onUpdate,
|
|
138724
|
-
onDelete
|
|
138809
|
+
onDelete,
|
|
138810
|
+
onBulkAdd
|
|
138725
138811
|
}) {
|
|
138726
138812
|
const [subjects, setSubjects] = React169.useState([]);
|
|
138727
138813
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
@@ -138819,12 +138905,37 @@ function SubjectManager({
|
|
|
138819
138905
|
}
|
|
138820
138906
|
});
|
|
138821
138907
|
};
|
|
138822
|
-
|
|
138908
|
+
const handleImport = async (records) => {
|
|
138909
|
+
if (!onBulkAdd) return;
|
|
138910
|
+
const validatedRecords = records.map((rec) => {
|
|
138911
|
+
if (typeof rec.code === "string" && typeof rec.name === "string") {
|
|
138912
|
+
return { code: rec.code, name: rec.name };
|
|
138913
|
+
}
|
|
138914
|
+
return null;
|
|
138915
|
+
}).filter((rec) => rec !== null);
|
|
138916
|
+
if (validatedRecords.length !== records.length) {
|
|
138917
|
+
toast2({
|
|
138918
|
+
title: "Import Warning",
|
|
138919
|
+
description: "Some records had invalid or missing 'code' or 'name' fields and were ignored.",
|
|
138920
|
+
variant: "destructive"
|
|
138921
|
+
});
|
|
138922
|
+
}
|
|
138923
|
+
if (validatedRecords.length > 0) {
|
|
138924
|
+
await onBulkAdd(validatedRecords);
|
|
138925
|
+
}
|
|
138926
|
+
};
|
|
138927
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Subjects"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(
|
|
138928
|
+
MetadataImportControls,
|
|
138929
|
+
{
|
|
138930
|
+
metadataName: "Subjects",
|
|
138931
|
+
onImport: handleImport
|
|
138932
|
+
}
|
|
138933
|
+
), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Subject")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : subjects.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No subjects found. Add one to get started!") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Created At"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Updated At"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, subjects.map((subject) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: subject.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, subject.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, subject.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, format(new Date(subject.createdAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, format(new Date(subject.updatedAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(subject), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(subject), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentSubject ? "Edit Subject" : "Add New Subject"), /* @__PURE__ */ React169__namespace.default.createElement(DialogDescription2, null, currentSubject ? "Update the details of the subject." : "Enter details for the new subject.")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "subjectCode", value: subjectCode, onChange: (e3) => setSubjectCode(e3.target.value.toUpperCase()), placeholder: "e.g., MATH", disabled: !!currentSubject })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectName" }, "Subject Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "subjectName", value: subjectName, onChange: (e3) => setSubjectName(e3.target.value), placeholder: "e.g., Mathematics" }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !subjectName.trim() || !subjectCode.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This action cannot be undone. This will permanently delete the subject "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
138823
138934
|
}
|
|
138824
138935
|
|
|
138825
138936
|
// src/react-ui/components/metadata/GradeLevelManager.tsx
|
|
138826
138937
|
init_react_shim();
|
|
138827
|
-
function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
138938
|
+
function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
138828
138939
|
const [items, setItems] = React169.useState([]);
|
|
138829
138940
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
138830
138941
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
@@ -138921,7 +139032,26 @@ function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpd
|
|
|
138921
139032
|
}
|
|
138922
139033
|
});
|
|
138923
139034
|
};
|
|
138924
|
-
|
|
139035
|
+
const handleImport = async (records) => {
|
|
139036
|
+
if (!onBulkAdd) return;
|
|
139037
|
+
const validatedRecords = records.map((rec) => {
|
|
139038
|
+
if (typeof rec.code === "string" && typeof rec.name === "string") {
|
|
139039
|
+
return { code: rec.code, name: rec.name };
|
|
139040
|
+
}
|
|
139041
|
+
return null;
|
|
139042
|
+
}).filter((rec) => rec !== null);
|
|
139043
|
+
if (validatedRecords.length !== records.length) {
|
|
139044
|
+
toast2({
|
|
139045
|
+
title: "Import Warning",
|
|
139046
|
+
description: "Some records had invalid or missing 'code' or 'name' fields and were ignored.",
|
|
139047
|
+
variant: "destructive"
|
|
139048
|
+
});
|
|
139049
|
+
}
|
|
139050
|
+
if (validatedRecords.length > 0) {
|
|
139051
|
+
await onBulkAdd(validatedRecords);
|
|
139052
|
+
}
|
|
139053
|
+
};
|
|
139054
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Award, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Grade Levels"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Grade Levels", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Grade Level")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No grade levels found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Grade Level" : "Add New Grade Level")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., G9", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Grade 9" }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
138925
139055
|
}
|
|
138926
139056
|
|
|
138927
139057
|
// src/react-ui/components/metadata/TopicManager.tsx
|
|
@@ -138932,7 +139062,8 @@ function TopicManager({
|
|
|
138932
139062
|
isLoading: isLoadingProp,
|
|
138933
139063
|
onAdd,
|
|
138934
139064
|
onUpdate,
|
|
138935
|
-
onDelete
|
|
139065
|
+
onDelete,
|
|
139066
|
+
onBulkAdd
|
|
138936
139067
|
}) {
|
|
138937
139068
|
const [topics, setTopics] = React169.useState([]);
|
|
138938
139069
|
const [subjects, setSubjects] = React169.useState([]);
|
|
@@ -139036,15 +139167,34 @@ function TopicManager({
|
|
|
139036
139167
|
}
|
|
139037
139168
|
});
|
|
139038
139169
|
};
|
|
139170
|
+
const handleImport = async (records) => {
|
|
139171
|
+
if (!onBulkAdd) return;
|
|
139172
|
+
const validatedRecords = records.map((rec) => {
|
|
139173
|
+
if (typeof rec.code === "string" && typeof rec.name === "string" && typeof rec.subjectCode === "string") {
|
|
139174
|
+
return { code: rec.code, name: rec.name, subjectCode: rec.subjectCode };
|
|
139175
|
+
}
|
|
139176
|
+
return null;
|
|
139177
|
+
}).filter((rec) => rec !== null);
|
|
139178
|
+
if (validatedRecords.length !== records.length) {
|
|
139179
|
+
toast2({
|
|
139180
|
+
title: "Import Warning",
|
|
139181
|
+
description: "Some records had invalid or missing 'code', 'name', or 'subjectCode' fields and were ignored.",
|
|
139182
|
+
variant: "destructive"
|
|
139183
|
+
});
|
|
139184
|
+
}
|
|
139185
|
+
if (validatedRecords.length > 0) {
|
|
139186
|
+
await onBulkAdd(validatedRecords);
|
|
139187
|
+
}
|
|
139188
|
+
};
|
|
139039
139189
|
const getSubjectName = (subjectCode) => {
|
|
139040
139190
|
return subjects.find((s4) => s4.code === subjectCode)?.name || "N/A";
|
|
139041
139191
|
};
|
|
139042
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Tag, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Topics"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm", disabled: subjects.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), subjects.length === 0 && !isLoading && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Please add subjects before adding topics.")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : topics.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No topics found. Add one to get started!") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, topics.map((topic) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: topic.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, topic.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-
|
|
139192
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Tag, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Topics"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Topics", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm", disabled: subjects.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic"))), subjects.length === 0 && !isLoading && /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-sm text-destructive" }, "Please add subjects before adding topics.")), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : topics.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No topics found. Add one to get started!") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, topics.map((topic) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: topic.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, topic.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, topic.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, getSubjectName(topic.subjectCode), " (", topic.subjectCode, ")"), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(topic), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(topic), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Topic" : "Add New Topic")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Topic Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., ALG-BASICS", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Topic Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Algebra Basics" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: selectedSubjectCode, onValueChange: setSelectedSubjectCode, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select a subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, subjects.map((subject) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: subject.code, value: subject.code }, subject.name, " (", subject.code, ")")))))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() || !selectedSubjectCode }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete topic "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139043
139193
|
}
|
|
139044
139194
|
|
|
139045
139195
|
// src/react-ui/components/metadata/CategoryManager.tsx
|
|
139046
139196
|
init_react_shim();
|
|
139047
|
-
function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139197
|
+
function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139048
139198
|
const [items, setItems] = React169.useState([]);
|
|
139049
139199
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
139050
139200
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
@@ -139144,12 +139294,37 @@ function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdat
|
|
|
139144
139294
|
}
|
|
139145
139295
|
});
|
|
139146
139296
|
};
|
|
139147
|
-
|
|
139297
|
+
const handleImport = async (records) => {
|
|
139298
|
+
if (!onBulkAdd) return;
|
|
139299
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139300
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139301
|
+
acc.valid.push({
|
|
139302
|
+
code: rec.code,
|
|
139303
|
+
name: rec.name,
|
|
139304
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139305
|
+
});
|
|
139306
|
+
} else {
|
|
139307
|
+
acc.invalidCount++;
|
|
139308
|
+
}
|
|
139309
|
+
return acc;
|
|
139310
|
+
}, { valid: [], invalidCount: 0 });
|
|
139311
|
+
if (validationResult.invalidCount > 0) {
|
|
139312
|
+
toast2({
|
|
139313
|
+
title: "Import Warning",
|
|
139314
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139315
|
+
variant: "destructive"
|
|
139316
|
+
});
|
|
139317
|
+
}
|
|
139318
|
+
if (validationResult.valid.length > 0) {
|
|
139319
|
+
await onBulkAdd(validationResult.valid);
|
|
139320
|
+
}
|
|
139321
|
+
};
|
|
139322
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Layers, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Categories"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Categories", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Category")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No categories found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Category" : "Add New Category")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., CORE_CONCEPT", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Core Concept" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Fundamental ideas within a subject." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139148
139323
|
}
|
|
139149
139324
|
|
|
139150
139325
|
// src/react-ui/components/metadata/BloomLevelManager.tsx
|
|
139151
139326
|
init_react_shim();
|
|
139152
|
-
function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139327
|
+
function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139153
139328
|
const [items, setItems] = React169.useState([]);
|
|
139154
139329
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
139155
139330
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
@@ -139249,12 +139424,37 @@ function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpd
|
|
|
139249
139424
|
}
|
|
139250
139425
|
});
|
|
139251
139426
|
};
|
|
139252
|
-
|
|
139427
|
+
const handleImport = async (records) => {
|
|
139428
|
+
if (!onBulkAdd) return;
|
|
139429
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139430
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139431
|
+
acc.valid.push({
|
|
139432
|
+
code: rec.code,
|
|
139433
|
+
name: rec.name,
|
|
139434
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139435
|
+
});
|
|
139436
|
+
} else {
|
|
139437
|
+
acc.invalidCount++;
|
|
139438
|
+
}
|
|
139439
|
+
return acc;
|
|
139440
|
+
}, { valid: [], invalidCount: 0 });
|
|
139441
|
+
if (validationResult.invalidCount > 0) {
|
|
139442
|
+
toast2({
|
|
139443
|
+
title: "Import Warning",
|
|
139444
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139445
|
+
variant: "destructive"
|
|
139446
|
+
});
|
|
139447
|
+
}
|
|
139448
|
+
if (validationResult.valid.length > 0) {
|
|
139449
|
+
await onBulkAdd(validationResult.valid);
|
|
139450
|
+
}
|
|
139451
|
+
};
|
|
139452
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Brain, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Bloom's Levels"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Bloom's Levels", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Bloom's Level")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Bloom's Levels found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Bloom's Level" : "Add New Bloom's Level")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., REMEMBER", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Remembering" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Recall facts and basic concepts." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139253
139453
|
}
|
|
139254
139454
|
|
|
139255
139455
|
// src/react-ui/components/metadata/QuestionTypeManager.tsx
|
|
139256
139456
|
init_react_shim();
|
|
139257
|
-
function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139457
|
+
function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139258
139458
|
const [items, setItems] = React169.useState([]);
|
|
139259
139459
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
139260
139460
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
@@ -139324,6 +139524,31 @@ function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onU
|
|
|
139324
139524
|
}
|
|
139325
139525
|
});
|
|
139326
139526
|
};
|
|
139527
|
+
const handleImport = async (records) => {
|
|
139528
|
+
if (!onBulkAdd) return;
|
|
139529
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139530
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139531
|
+
acc.valid.push({
|
|
139532
|
+
code: rec.code,
|
|
139533
|
+
name: rec.name,
|
|
139534
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139535
|
+
});
|
|
139536
|
+
} else {
|
|
139537
|
+
acc.invalidCount++;
|
|
139538
|
+
}
|
|
139539
|
+
return acc;
|
|
139540
|
+
}, { valid: [], invalidCount: 0 });
|
|
139541
|
+
if (validationResult.invalidCount > 0) {
|
|
139542
|
+
toast2({
|
|
139543
|
+
title: "Import Warning",
|
|
139544
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139545
|
+
variant: "destructive"
|
|
139546
|
+
});
|
|
139547
|
+
}
|
|
139548
|
+
if (validationResult.valid.length > 0) {
|
|
139549
|
+
await onBulkAdd(validationResult.valid);
|
|
139550
|
+
}
|
|
139551
|
+
};
|
|
139327
139552
|
const handleSubmit = () => {
|
|
139328
139553
|
if (!itemName.trim() || !itemCode.trim()) {
|
|
139329
139554
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
@@ -139354,22 +139579,19 @@ function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onU
|
|
|
139354
139579
|
}
|
|
139355
139580
|
});
|
|
139356
139581
|
};
|
|
139357
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleHelp, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Question Types"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Question Type"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Question Types found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-
|
|
139582
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(CircleHelp, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Question Types"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Question Types", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Question Type")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Question Types found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Question Type" : "Add New Question Type")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toLowerCase().replace(/ /g, "_")), placeholder: "e.g., multiple_choice", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Multiple Choice" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Select one answer from a list of options." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139358
139583
|
}
|
|
139359
139584
|
|
|
139360
139585
|
// src/react-ui/components/metadata/LearningObjectiveManager.tsx
|
|
139361
139586
|
init_react_shim();
|
|
139362
|
-
function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139587
|
+
function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139363
139588
|
const [items, setItems] = React169.useState([]);
|
|
139364
139589
|
const [subjects, setSubjects] = React169.useState([]);
|
|
139365
139590
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
139366
139591
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
139367
139592
|
const [isAlertOpen, setIsAlertOpen] = React169.useState(false);
|
|
139368
139593
|
const [currentItem, setCurrentItem] = React169.useState(null);
|
|
139369
|
-
const [
|
|
139370
|
-
const [itemCode, setItemCode] = React169.useState("");
|
|
139371
|
-
const [itemDescription, setItemDescription] = React169.useState("");
|
|
139372
|
-
const [selectedSubjectCode, setSelectedSubjectCode] = React169.useState(void 0);
|
|
139594
|
+
const [formState, setFormState] = React169.useState({});
|
|
139373
139595
|
const [itemToDelete, setItemToDelete] = React169.useState(null);
|
|
139374
139596
|
const [isPending, startTransition] = React169.useTransition();
|
|
139375
139597
|
const { toast: toast2 } = useToast();
|
|
@@ -139396,20 +139618,17 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139396
139618
|
refreshData();
|
|
139397
139619
|
}
|
|
139398
139620
|
}, [isControlled, initialData, subjectsProp, isLoadingProp]);
|
|
139621
|
+
const handleFormChange = (field, value) => {
|
|
139622
|
+
setFormState((prev) => ({ ...prev, [field]: value }));
|
|
139623
|
+
};
|
|
139399
139624
|
const handleAddItem = () => {
|
|
139400
139625
|
setCurrentItem(null);
|
|
139401
|
-
|
|
139402
|
-
setItemCode("");
|
|
139403
|
-
setItemDescription("");
|
|
139404
|
-
setSelectedSubjectCode(subjects.length > 0 ? subjects[0].code : void 0);
|
|
139626
|
+
setFormState({ subjectCode: subjects.length > 0 ? subjects[0].code : "" });
|
|
139405
139627
|
setIsDialogOpen(true);
|
|
139406
139628
|
};
|
|
139407
139629
|
const handleEditItem = (item) => {
|
|
139408
139630
|
setCurrentItem(item);
|
|
139409
|
-
|
|
139410
|
-
setItemCode(item.code);
|
|
139411
|
-
setItemDescription(item.description || "");
|
|
139412
|
-
setSelectedSubjectCode(item.subjectCode);
|
|
139631
|
+
setFormState(item);
|
|
139413
139632
|
setIsDialogOpen(true);
|
|
139414
139633
|
};
|
|
139415
139634
|
const handleDeleteItem = (item) => {
|
|
@@ -139436,7 +139655,7 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139436
139655
|
});
|
|
139437
139656
|
};
|
|
139438
139657
|
const handleSubmit = () => {
|
|
139439
|
-
if (!
|
|
139658
|
+
if (!formState.name?.trim() || !formState.code?.trim()) {
|
|
139440
139659
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
139441
139660
|
return;
|
|
139442
139661
|
}
|
|
@@ -139444,17 +139663,17 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139444
139663
|
try {
|
|
139445
139664
|
if (currentItem) {
|
|
139446
139665
|
if (isControlled && onUpdate) {
|
|
139447
|
-
await onUpdate({
|
|
139666
|
+
await onUpdate({ ...currentItem, ...formState });
|
|
139448
139667
|
} else {
|
|
139449
|
-
MetadataService.updateLearningObjective(currentItem.id,
|
|
139668
|
+
MetadataService.updateLearningObjective(currentItem.id, formState);
|
|
139450
139669
|
refreshData();
|
|
139451
139670
|
}
|
|
139452
139671
|
toast2({ title: "Success", description: "Learning Objective updated." });
|
|
139453
139672
|
} else {
|
|
139454
139673
|
if (isControlled && onAdd) {
|
|
139455
|
-
await onAdd(
|
|
139674
|
+
await onAdd(formState);
|
|
139456
139675
|
} else {
|
|
139457
|
-
MetadataService.addLearningObjective(
|
|
139676
|
+
MetadataService.addLearningObjective(formState);
|
|
139458
139677
|
refreshData();
|
|
139459
139678
|
}
|
|
139460
139679
|
toast2({ title: "Success", description: "Learning Objective added." });
|
|
@@ -139465,16 +139684,51 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139465
139684
|
}
|
|
139466
139685
|
});
|
|
139467
139686
|
};
|
|
139468
|
-
const
|
|
139469
|
-
if (!
|
|
139470
|
-
|
|
139687
|
+
const handleImport = async (records) => {
|
|
139688
|
+
if (!onBulkAdd) return;
|
|
139689
|
+
const parseStringToArray = (input) => {
|
|
139690
|
+
if (Array.isArray(input)) return input;
|
|
139691
|
+
if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
|
|
139692
|
+
return [];
|
|
139693
|
+
};
|
|
139694
|
+
const validatedRecords = records.reduce((acc, rec) => {
|
|
139695
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139696
|
+
acc.push({
|
|
139697
|
+
code: rec.code,
|
|
139698
|
+
name: rec.name,
|
|
139699
|
+
description: rec.description,
|
|
139700
|
+
subject: rec.subject,
|
|
139701
|
+
subjectCode: rec.subjectCode,
|
|
139702
|
+
category: rec.category,
|
|
139703
|
+
categoryCode: rec.categoryCode,
|
|
139704
|
+
topic: rec.topic,
|
|
139705
|
+
topicCode: rec.topicCode,
|
|
139706
|
+
grade: rec.grade,
|
|
139707
|
+
gradeCode: rec.gradeCode,
|
|
139708
|
+
keywords: parseStringToArray(rec.keywords),
|
|
139709
|
+
stemElements: parseStringToArray(rec.stemElements),
|
|
139710
|
+
bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
|
|
139711
|
+
});
|
|
139712
|
+
}
|
|
139713
|
+
return acc;
|
|
139714
|
+
}, []);
|
|
139715
|
+
if (validatedRecords.length !== records.length) {
|
|
139716
|
+
toast2({
|
|
139717
|
+
title: "Import Warning",
|
|
139718
|
+
description: `${records.length - validatedRecords.length} records had invalid or missing required fields ('code', 'name') and were ignored.`,
|
|
139719
|
+
variant: "destructive"
|
|
139720
|
+
});
|
|
139721
|
+
}
|
|
139722
|
+
if (validatedRecords.length > 0) {
|
|
139723
|
+
await onBulkAdd(validatedRecords);
|
|
139724
|
+
}
|
|
139471
139725
|
};
|
|
139472
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "
|
|
139726
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => handleFormChange("code", e3.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e3) => handleFormChange("name", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.subjectCode || "", onValueChange: (value) => handleFormChange("subjectCode", value) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, { placeholder: "Select a subject" })), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, subjects.map((subject) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: subject.id, value: subject.code }, subject.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e3) => handleFormChange("topicCode", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e3) => handleFormChange("categoryCode", e3.target.value) })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e3) => handleFormChange("gradeCode", e3.target.value) }))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e3) => handleFormChange("keywords", e3.target.value.split(",").map((s4) => s4.trim())) }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139473
139727
|
}
|
|
139474
139728
|
|
|
139475
139729
|
// src/react-ui/components/metadata/ContextManager.tsx
|
|
139476
139730
|
init_react_shim();
|
|
139477
|
-
function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139731
|
+
function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139478
139732
|
const [items, setItems] = React169.useState([]);
|
|
139479
139733
|
const [isLoading, setIsLoading] = React169.useState(true);
|
|
139480
139734
|
const [isDialogOpen, setIsDialogOpen] = React169.useState(false);
|
|
@@ -139544,6 +139798,31 @@ function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate
|
|
|
139544
139798
|
}
|
|
139545
139799
|
});
|
|
139546
139800
|
};
|
|
139801
|
+
const handleImport = async (records) => {
|
|
139802
|
+
if (!onBulkAdd) return;
|
|
139803
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139804
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139805
|
+
acc.valid.push({
|
|
139806
|
+
code: rec.code,
|
|
139807
|
+
name: rec.name,
|
|
139808
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139809
|
+
});
|
|
139810
|
+
} else {
|
|
139811
|
+
acc.invalidCount++;
|
|
139812
|
+
}
|
|
139813
|
+
return acc;
|
|
139814
|
+
}, { valid: [], invalidCount: 0 });
|
|
139815
|
+
if (validationResult.invalidCount > 0) {
|
|
139816
|
+
toast2({
|
|
139817
|
+
title: "Import Warning",
|
|
139818
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139819
|
+
variant: "destructive"
|
|
139820
|
+
});
|
|
139821
|
+
}
|
|
139822
|
+
if (validationResult.valid.length > 0) {
|
|
139823
|
+
await onBulkAdd(validationResult.valid);
|
|
139824
|
+
}
|
|
139825
|
+
};
|
|
139547
139826
|
const handleSubmit = () => {
|
|
139548
139827
|
if (!itemName.trim() || !itemCode.trim()) {
|
|
139549
139828
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
@@ -139574,13 +139853,13 @@ function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate
|
|
|
139574
139853
|
}
|
|
139575
139854
|
});
|
|
139576
139855
|
};
|
|
139577
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(ScanText, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Contexts"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Context"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Contexts found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-
|
|
139856
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(ScanText, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Contexts"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Categories", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Context")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Contexts found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Context" : "Add New Context")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., HIST_INQ", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Historical Inquiry" })), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Analyzing primary and secondary sources." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139578
139857
|
}
|
|
139579
139858
|
|
|
139580
139859
|
// src/react-ui/components/metadata/ApproachManager.tsx
|
|
139581
139860
|
init_react_shim();
|
|
139582
139861
|
var knowledgeDimensions = ["Factual", "Conceptual", "Procedural"];
|
|
139583
|
-
var
|
|
139862
|
+
var standardDifficulties = ["Easy", "Medium", "Hard"];
|
|
139584
139863
|
function ApproachManager({
|
|
139585
139864
|
initialData,
|
|
139586
139865
|
bloomLevels: bloomLevelsProp,
|
|
@@ -139588,7 +139867,8 @@ function ApproachManager({
|
|
|
139588
139867
|
isLoading: isLoadingProp,
|
|
139589
139868
|
onAdd,
|
|
139590
139869
|
onUpdate,
|
|
139591
|
-
onDelete
|
|
139870
|
+
onDelete,
|
|
139871
|
+
onBulkAdd
|
|
139592
139872
|
}) {
|
|
139593
139873
|
const [items, setItems] = React169.useState([]);
|
|
139594
139874
|
const [bloomLevels, setBloomLevels] = React169.useState([]);
|
|
@@ -139629,7 +139909,7 @@ function ApproachManager({
|
|
|
139629
139909
|
const resetForm = () => {
|
|
139630
139910
|
setFormState({
|
|
139631
139911
|
knowledgeDimension: "Factual",
|
|
139632
|
-
|
|
139912
|
+
difficulty: ["Medium"],
|
|
139633
139913
|
bloomLevelCode: bloomLevels.length > 0 ? bloomLevels[0].code : "",
|
|
139634
139914
|
iSpringQuizType: questionTypes.length > 0 ? questionTypes[0].code : "multiple_choice"
|
|
139635
139915
|
});
|
|
@@ -139641,18 +139921,7 @@ function ApproachManager({
|
|
|
139641
139921
|
};
|
|
139642
139922
|
const handleEditItem = (item) => {
|
|
139643
139923
|
setCurrentItem(item);
|
|
139644
|
-
setFormState(
|
|
139645
|
-
code: item.code,
|
|
139646
|
-
verbEn: item.verbEn,
|
|
139647
|
-
verbVi: item.verbVi,
|
|
139648
|
-
bloomLevelCode: item.bloomLevelCode,
|
|
139649
|
-
knowledgeDimension: item.knowledgeDimension,
|
|
139650
|
-
iSpringQuizType: item.iSpringQuizType,
|
|
139651
|
-
rawDifficulty: item.rawDifficulty,
|
|
139652
|
-
suggestContext: item.suggestContext,
|
|
139653
|
-
exampleEn: item.exampleEn,
|
|
139654
|
-
exampleVi: item.exampleVi
|
|
139655
|
-
});
|
|
139924
|
+
setFormState(item);
|
|
139656
139925
|
setIsDialogOpen(true);
|
|
139657
139926
|
};
|
|
139658
139927
|
const handleDeleteItem = (item) => {
|
|
@@ -139679,9 +139948,9 @@ function ApproachManager({
|
|
|
139679
139948
|
});
|
|
139680
139949
|
};
|
|
139681
139950
|
const handleSubmit = () => {
|
|
139682
|
-
const { code: code4, verbEn, verbVi, bloomLevelCode, iSpringQuizType, knowledgeDimension,
|
|
139683
|
-
if (!code4?.trim() || !verbEn?.trim() || !verbVi?.trim() || !bloomLevelCode || !iSpringQuizType || !knowledgeDimension || !
|
|
139684
|
-
toast2({ title: "Validation Error", description: "All fields except examples and context are required.", variant: "destructive" });
|
|
139951
|
+
const { code: code4, name: name3, verbEn, verbVi, bloomLevelCode, iSpringQuizType, knowledgeDimension, difficulty } = formState;
|
|
139952
|
+
if (!code4?.trim() || !name3?.trim() || !verbEn?.trim() || !verbVi?.trim() || !bloomLevelCode || !iSpringQuizType || !knowledgeDimension || !difficulty || difficulty.length === 0) {
|
|
139953
|
+
toast2({ title: "Validation Error", description: "All fields except examples and context are required, and at least one difficulty must be selected.", variant: "destructive" });
|
|
139685
139954
|
return;
|
|
139686
139955
|
}
|
|
139687
139956
|
startTransition(async () => {
|
|
@@ -139709,7 +139978,47 @@ function ApproachManager({
|
|
|
139709
139978
|
}
|
|
139710
139979
|
});
|
|
139711
139980
|
};
|
|
139712
|
-
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Settings2, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Approaches"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Approach"))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Approaches found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Approach ID"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Verb (VI)"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Cognitive Level"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "iSpring Type"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-medium" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.verbVi), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.bloomLevelCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.iSpringQuizType), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Approach" : "Add New Approach")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Approach Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => setFormState((s4) => ({ ...s4, code: e3.target.value.toUpperCase() })), placeholder: "e.g., REM-FAC-IDT-MCQ", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "verbEn" }, "Verb (English)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "verbEn", value: formState.verbEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbEn: e3.target.value })), placeholder: "e.g., Identify" }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "verbVi" }, "Verb (Vietnamese)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "verbVi", value: formState.verbVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbVi: e3.target.value })), placeholder: "e.g., Nh\u1EADn d\u1EA1ng" })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Cognitive Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.bloomLevelCode, onValueChange: (v) => setFormState((s4) => ({ ...s4, bloomLevelCode: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, bloomLevels.map((level) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: level.code, value: level.code }, level.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "knowledgeDimension" }, "Knowledge Dimension"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.knowledgeDimension, onValueChange: (v) => setFormState((s4) => ({ ...s4, knowledgeDimension: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, knowledgeDimensions.map((kd) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: kd, value: kd }, kd))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "iSpringQuizType" }, "iSpring Quiz Type"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.iSpringQuizType, onValueChange: (v) => setFormState((s4) => ({ ...s4, iSpringQuizType: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, questionTypes.map((qt) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "rawDifficulty" }, "Raw Difficulty"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.rawDifficulty, onValueChange: (v) => setFormState((s4) => ({ ...s4, rawDifficulty: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, rawDifficultyLevels.map((rd) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: rd, value: rd }, rd)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "suggestContext" }, "Suggest Context (comma-separated codes)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "suggestContext", value: formState.suggestContext || "", onChange: (e3) => setFormState((s4) => ({ ...s4, suggestContext: e3.target.value })), placeholder: "e.g., A, B, D, G, H" })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "exampleEn" }, "Example (English)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "exampleEn", value: formState.exampleEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleEn: e3.target.value })), placeholder: "English example prompt..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "exampleVi" }, "Example (Vietnamese)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "exampleVi", value: formState.exampleVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleVi: e3.target.value })), placeholder: "Vietnamese example prompt..." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.code, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139981
|
+
const handleImport = async (records) => {
|
|
139982
|
+
if (!onBulkAdd) return;
|
|
139983
|
+
const parseStringToArray = (input) => {
|
|
139984
|
+
if (Array.isArray(input)) return input;
|
|
139985
|
+
if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
|
|
139986
|
+
return [];
|
|
139987
|
+
};
|
|
139988
|
+
const validatedRecords = records.reduce((acc, rec) => {
|
|
139989
|
+
if (rec.code && rec.name && rec.verbEn && rec.verbVi && rec.knowledgeDimension && rec.iSpringQuizType && rec.difficulty && rec.bloomLevelCode) {
|
|
139990
|
+
acc.push({
|
|
139991
|
+
code: rec.code,
|
|
139992
|
+
name: rec.name,
|
|
139993
|
+
verbEn: rec.verbEn,
|
|
139994
|
+
verbVi: rec.verbVi,
|
|
139995
|
+
knowledgeDimension: rec.knowledgeDimension,
|
|
139996
|
+
iSpringQuizType: rec.iSpringQuizType,
|
|
139997
|
+
difficulty: parseStringToArray(rec.difficulty),
|
|
139998
|
+
bloomLevelCode: rec.bloomLevelCode,
|
|
139999
|
+
suggestContext: rec.suggestContext,
|
|
140000
|
+
exampleEn: rec.exampleEn,
|
|
140001
|
+
exampleVi: rec.exampleVi
|
|
140002
|
+
});
|
|
140003
|
+
}
|
|
140004
|
+
return acc;
|
|
140005
|
+
}, []);
|
|
140006
|
+
if (validatedRecords.length !== records.length) {
|
|
140007
|
+
toast2({
|
|
140008
|
+
title: "Import Warning",
|
|
140009
|
+
description: `${records.length - validatedRecords.length} records had missing required fields and were ignored.`,
|
|
140010
|
+
variant: "destructive"
|
|
140011
|
+
});
|
|
140012
|
+
}
|
|
140013
|
+
if (validatedRecords.length > 0) {
|
|
140014
|
+
await onBulkAdd(validatedRecords);
|
|
140015
|
+
}
|
|
140016
|
+
};
|
|
140017
|
+
return /* @__PURE__ */ React169__namespace.default.createElement(Card, null, /* @__PURE__ */ React169__namespace.default.createElement(CardHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__namespace.default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__namespace.default.createElement(Settings2, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Approaches"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__namespace.default.createElement(MetadataImportControls, { metadataName: "Approaches", onImport: handleImport }), /* @__PURE__ */ React169__namespace.default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__namespace.default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Approach")))), /* @__PURE__ */ React169__namespace.default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__namespace.default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Approaches found.") : /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(Table3, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(TableRow, null, /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Approach ID"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Verb (VI)"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, null, "Cognitive Level"), /* @__PURE__ */ React169__namespace.default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__namespace.default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__namespace.default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "font-Medium" }, item.code), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.name), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.verbVi), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, null, item.bloomLevelCode), /* @__PURE__ */ React169__namespace.default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__namespace.default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__namespace.default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__namespace.default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__namespace.default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__namespace.default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__namespace.default.createElement(DialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(DialogTitle2, null, currentItem ? "Edit Approach" : "Add New Approach")), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "code" }, "Approach Code"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => setFormState((s4) => ({ ...s4, code: e3.target.value.toUpperCase() })), placeholder: "e.g., REM-FAC-IDT-MCQ", disabled: !!currentItem })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "name" }, "Name / Description"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e3) => setFormState((s4) => ({ ...s4, name: e3.target.value })), placeholder: "e.g., Identify a Fact" }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "verbEn" }, "Verb (English)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "verbEn", value: formState.verbEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbEn: e3.target.value })), placeholder: "e.g., Identify" })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "verbVi" }, "Verb (Vietnamese)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "verbVi", value: formState.verbVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbVi: e3.target.value })), placeholder: "e.g., Nh\u1EADn d\u1EA1ng" }))), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4" }, /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Cognitive Level"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.bloomLevelCode, onValueChange: (v) => setFormState((s4) => ({ ...s4, bloomLevelCode: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, bloomLevels.map((level) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: level.code, value: level.code }, level.name))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "knowledgeDimension" }, "Knowledge Dimension"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.knowledgeDimension, onValueChange: (v) => setFormState((s4) => ({ ...s4, knowledgeDimension: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, knowledgeDimensions.map((kd) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: kd, value: kd }, kd))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "iSpringQuizType" }, "iSpring Quiz Type"), /* @__PURE__ */ React169__namespace.default.createElement(Select2, { value: formState.iSpringQuizType, onValueChange: (v) => setFormState((s4) => ({ ...s4, iSpringQuizType: v })) }, /* @__PURE__ */ React169__namespace.default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__namespace.default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__namespace.default.createElement(SelectContent2, null, questionTypes.map((qt) => /* @__PURE__ */ React169__namespace.default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, null, "Difficulty"), /* @__PURE__ */ React169__namespace.default.createElement("div", { className: "flex items-center space-x-4 pt-2" }, standardDifficulties.map((diff2) => /* @__PURE__ */ React169__namespace.default.createElement("div", { key: diff2, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__namespace.default.createElement(Checkbox2, { id: `diff-${diff2}`, checked: (formState.difficulty || []).includes(diff2), onCheckedChange: (checked) => {
|
|
140018
|
+
const current = formState.difficulty || [];
|
|
140019
|
+
const newDiff = checked ? [...current, diff2] : current.filter((d) => d !== diff2);
|
|
140020
|
+
setFormState((s4) => ({ ...s4, difficulty: newDiff }));
|
|
140021
|
+
} }), /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: `diff-${diff2}` }, diff2))))), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "suggestContext" }, "Suggest Context (comma-separated codes)"), /* @__PURE__ */ React169__namespace.default.createElement(Input, { id: "suggestContext", value: formState.suggestContext || "", onChange: (e3) => setFormState((s4) => ({ ...s4, suggestContext: e3.target.value })), placeholder: "e.g., A, B, D, G, H" })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "exampleEn" }, "Example (English)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "exampleEn", value: formState.exampleEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleEn: e3.target.value })), placeholder: "English example prompt..." })), /* @__PURE__ */ React169__namespace.default.createElement("div", null, /* @__PURE__ */ React169__namespace.default.createElement(Label2, { htmlFor: "exampleVi" }, "Example (Vietnamese)"), /* @__PURE__ */ React169__namespace.default.createElement(Textarea, { id: "exampleVi", value: formState.exampleVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleVi: e3.target.value })), placeholder: "Vietnamese example prompt..." }))), /* @__PURE__ */ React169__namespace.default.createElement(DialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.code, '".')), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__namespace.default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__namespace.default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139713
140022
|
}
|
|
139714
140023
|
|
|
139715
140024
|
// src/react-ui/components/metadata/MetadataTabs.tsx
|
|
@@ -140385,7 +140694,7 @@ var ToastAction2 = React169__namespace.forwardRef(({ className, ...props }, ref)
|
|
|
140385
140694
|
{
|
|
140386
140695
|
ref,
|
|
140387
140696
|
className: cn(
|
|
140388
|
-
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-
|
|
140697
|
+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-Medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
|
140389
140698
|
className
|
|
140390
140699
|
),
|
|
140391
140700
|
...props
|
|
@@ -142023,6 +142332,7 @@ exports.Label = Label2;
|
|
|
142023
142332
|
exports.LanguageProvider = LanguageProvider;
|
|
142024
142333
|
exports.LearningObjectiveManager = LearningObjectiveManager;
|
|
142025
142334
|
exports.ManageTopics = ManageTopics;
|
|
142335
|
+
exports.MetadataImportControls = MetadataImportControls;
|
|
142026
142336
|
exports.MetadataTabs = MetadataTabs;
|
|
142027
142337
|
exports.PerformanceCharts = PerformanceCharts;
|
|
142028
142338
|
exports.PersonalPracticeDashboard = PersonalPracticeDashboard;
|