@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.mjs
CHANGED
|
@@ -9019,7 +9019,7 @@ var translation_default = {
|
|
|
9019
9019
|
subject: "Subject",
|
|
9020
9020
|
category: "Category",
|
|
9021
9021
|
topic: "Topic",
|
|
9022
|
-
|
|
9022
|
+
code: "LO ID"
|
|
9023
9023
|
},
|
|
9024
9024
|
confirmModal: {
|
|
9025
9025
|
title: "Confirm Data Import",
|
|
@@ -9434,7 +9434,7 @@ var translation_default2 = {
|
|
|
9434
9434
|
subject: "M\xF4n h\u1ECDc",
|
|
9435
9435
|
category: "Danh m\u1EE5c",
|
|
9436
9436
|
topic: "Ch\u1EE7 \u0111\u1EC1",
|
|
9437
|
-
|
|
9437
|
+
code: "M\xE3 MTH"
|
|
9438
9438
|
},
|
|
9439
9439
|
confirmModal: {
|
|
9440
9440
|
title: "X\xE1c nh\u1EADn Nh\u1EADp D\u1EEF li\u1EC7u",
|
|
@@ -37506,7 +37506,7 @@ var cva = (base3, config3) => (props) => {
|
|
|
37506
37506
|
|
|
37507
37507
|
// src/react-ui/components/elements/label.tsx
|
|
37508
37508
|
var labelVariants = cva(
|
|
37509
|
-
"text-sm font-
|
|
37509
|
+
"text-sm font-Medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
37510
37510
|
);
|
|
37511
37511
|
var Label2 = React169.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React169.createElement(
|
|
37512
37512
|
Root3,
|
|
@@ -62744,7 +62744,7 @@ var MarkdownRenderer = ({
|
|
|
62744
62744
|
},
|
|
62745
62745
|
h1: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("h1", { ...props, className: "text-3xl font-bold mb-6 mt-8 first:mt-0" }),
|
|
62746
62746
|
h2: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("h2", { ...props, className: "text-2xl font-semibold mb-4 mt-6" }),
|
|
62747
|
-
h3: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("h3", { ...props, className: "text-xl font-
|
|
62747
|
+
h3: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("h3", { ...props, className: "text-xl font-Medium mb-3 mt-5" }),
|
|
62748
62748
|
ul: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("ul", { ...props, className: "my-4 space-y-2 list-disc list-inside" }),
|
|
62749
62749
|
ol: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("ol", { ...props, className: "my-4 space-y-2 list-decimal list-inside" }),
|
|
62750
62750
|
p: ({ node: node2, ...props }) => /* @__PURE__ */ React169__default.createElement("p", { ...props, className: "mb-4 leading-7" }),
|
|
@@ -63218,7 +63218,7 @@ var Input = React169.forwardRef(
|
|
|
63218
63218
|
{
|
|
63219
63219
|
type,
|
|
63220
63220
|
className: cn(
|
|
63221
|
-
"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-
|
|
63221
|
+
"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",
|
|
63222
63222
|
className
|
|
63223
63223
|
),
|
|
63224
63224
|
ref,
|
|
@@ -63393,7 +63393,7 @@ init_react_shim();
|
|
|
63393
63393
|
// src/react-ui/components/elements/button.tsx
|
|
63394
63394
|
init_react_shim();
|
|
63395
63395
|
var buttonVariants = cva(
|
|
63396
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-
|
|
63396
|
+
"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",
|
|
63397
63397
|
{
|
|
63398
63398
|
variants: {
|
|
63399
63399
|
variant: {
|
|
@@ -68346,7 +68346,7 @@ var MatchingQuestionUI = ({
|
|
|
68346
68346
|
if (showCorrectAnswer && selectedOptionId) {
|
|
68347
68347
|
borderColor = isSelectionCorrect ? "border-green-500" : "border-destructive";
|
|
68348
68348
|
}
|
|
68349
|
-
return /* @__PURE__ */ React169__default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-
|
|
68349
|
+
return /* @__PURE__ */ React169__default.createElement("div", { key: promptItem.id, className: `p-3 border rounded-md ${borderColor} transition-colors bg-background` }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `select-prompt-${promptItem.id}`, className: "font-Medium text-base block mb-2 whitespace-normal" }, /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: promptItem.content })), /* @__PURE__ */ React169__default.createElement(
|
|
68350
68350
|
Select2,
|
|
68351
68351
|
{
|
|
68352
68352
|
value: selectedOptionId,
|
|
@@ -68401,7 +68401,7 @@ var DragAndDropQuestionUI = ({
|
|
|
68401
68401
|
} else {
|
|
68402
68402
|
itemStyle += " border-muted";
|
|
68403
68403
|
}
|
|
68404
|
-
return /* @__PURE__ */ React169__default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `select-draggable-${item.id}`, className: "font-
|
|
68404
|
+
return /* @__PURE__ */ React169__default.createElement("div", { key: item.id, className: itemStyle }, /* @__PURE__ */ React169__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__default.createElement(MarkdownRenderer, { content: item.content })), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 w-full sm:w-auto" }, /* @__PURE__ */ React169__default.createElement(
|
|
68405
68405
|
Select2,
|
|
68406
68406
|
{
|
|
68407
68407
|
value: selectedDropZoneId,
|
|
@@ -95417,7 +95417,7 @@ var TabsTrigger2 = React169.forwardRef(({ className, ...props }, ref) => /* @__P
|
|
|
95417
95417
|
{
|
|
95418
95418
|
ref,
|
|
95419
95419
|
className: cn(
|
|
95420
|
-
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-
|
|
95420
|
+
"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",
|
|
95421
95421
|
className
|
|
95422
95422
|
),
|
|
95423
95423
|
...props
|
|
@@ -96117,7 +96117,7 @@ var AccordionTrigger2 = React169.forwardRef(({ className, children, ...props },
|
|
|
96117
96117
|
{
|
|
96118
96118
|
ref,
|
|
96119
96119
|
className: cn(
|
|
96120
|
-
"flex flex-1 items-center justify-between py-4 font-
|
|
96120
|
+
"flex flex-1 items-center justify-between py-4 font-Medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
|
96121
96121
|
className
|
|
96122
96122
|
),
|
|
96123
96123
|
...props
|
|
@@ -96904,7 +96904,7 @@ var QuizResult = ({
|
|
|
96904
96904
|
}
|
|
96905
96905
|
return String(answer);
|
|
96906
96906
|
};
|
|
96907
|
-
return /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t4("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React169__default.createElement(CardDescription, { className: "text-center text-lg" }, t4("practiceFlow.results.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t4("practiceFlow.results.overallScore"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.points"))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.percentage"))), /* @__PURE__ */ React169__default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React169__default.createElement(CircleCheckBig, { className: "h-10 w-10" }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.passed"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React169__default.createElement(CircleX, { className: "h-10 w-10" }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.failed"))))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__default.createElement("span", null, t4("practiceFlow.results.timeSpent")), /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold" }, result.totalTimeSpentSeconds?.toFixed(0) ?? "N/A", " ", t4("practiceFlow.results.timeUnit"))), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__default.createElement("span", null, t4("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React169__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__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React169__default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React169__default.createElement(AccordionItem2, { value: "question-breakdown" }, /* @__PURE__ */ React169__default.createElement(AccordionTrigger2, { className: "text-lg font-semibold" }, t4("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React169__default.createElement(AccordionContent2, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React169__default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index3) => /* @__PURE__ */ React169__default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold" }, t4("common.questions", { count: index3 + 1 })), qResult.isCorrect ? /* @__PURE__ */ React169__default.createElement("span", { className: "text-green-600 font-
|
|
96907
|
+
return /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full max-w-3xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-3xl font-headline text-center" }, t4("practiceFlow.results.title", { quizTitle })), /* @__PURE__ */ React169__default.createElement(CardDescription, { className: "text-center text-lg" }, t4("practiceFlow.results.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement(Card, { className: "bg-secondary/50" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__default.createElement(BarChart2, { className: "mr-2 h-5 w-5 text-primary" }), t4("practiceFlow.results.overallScore"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "grid grid-cols-1 md:grid-cols-3 gap-4 text-center" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.score, " / ", result.maxScore), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.points"))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "text-3xl font-bold text-primary" }, result.percentage.toFixed(2), "%"), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("practiceFlow.results.percentage"))), /* @__PURE__ */ React169__default.createElement("div", null, result.passed !== void 0 && (result.passed ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center text-green-600" }, /* @__PURE__ */ React169__default.createElement(CircleCheckBig, { className: "h-10 w-10" }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.passed"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center text-destructive" }, /* @__PURE__ */ React169__default.createElement(CircleX, { className: "h-10 w-10" }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xl font-semibold mt-1" }, t4("practiceFlow.results.failed"))))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 text-sm" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__default.createElement(Clock, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__default.createElement("span", null, t4("practiceFlow.results.timeSpent")), /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold" }, result.totalTimeSpentSeconds?.toFixed(0) ?? "N/A", " ", t4("practiceFlow.results.timeUnit"))), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 p-3 bg-muted rounded-md" }, /* @__PURE__ */ React169__default.createElement(Percent, { className: "h-5 w-5 text-primary" }), /* @__PURE__ */ React169__default.createElement("span", null, t4("practiceFlow.results.avgTimePerQuestion")), /* @__PURE__ */ React169__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__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-lg" }, "SCORM Sync Status")), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement("p", { className: `flex items-center ${result.scormStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.scormStatus === "error" && /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold ml-1" }, result.scormStatus)), result.scormError && /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.scormError))), result.webhookStatus && result.webhookStatus !== "idle" && /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-lg" }, "Webhook Sync Status")), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement("p", { className: `flex items-center ${result.webhookStatus === "error" ? "text-destructive" : "text-muted-foreground"}` }, result.webhookStatus === "error" && /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-2 h-4 w-4" }), "Status: ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold ml-1" }, result.webhookStatus)), result.webhookError && /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-destructive mt-1" }, "Details: ", result.webhookError))), /* @__PURE__ */ React169__default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, /* @__PURE__ */ React169__default.createElement(AccordionItem2, { value: "question-breakdown" }, /* @__PURE__ */ React169__default.createElement(AccordionTrigger2, { className: "text-lg font-semibold" }, t4("practiceFlow.results.questionBreakdown")), /* @__PURE__ */ React169__default.createElement(AccordionContent2, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[300px] pr-4" }, /* @__PURE__ */ React169__default.createElement("ul", { className: "space-y-4" }, result.questionResults.map((qResult, index3) => /* @__PURE__ */ React169__default.createElement("li", { key: qResult.questionId, className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold" }, t4("common.questions", { count: index3 + 1 })), qResult.isCorrect ? /* @__PURE__ */ React169__default.createElement("span", { className: "text-green-600 font-Medium flex items-center" }, /* @__PURE__ */ React169__default.createElement(CircleCheckBig, { className: "mr-1 h-4 w-4" }), " ", t4("practiceFlow.results.passed")) : /* @__PURE__ */ React169__default.createElement("span", { className: "text-destructive font-Medium flex items-center" }, /* @__PURE__ */ React169__default.createElement(CircleX, { className: "mr-1 h-4 w-4" }), " ", t4("practiceFlow.results.failed"))), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React169__default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.yourAnswer")), " ", getAnswerDisplay(qResult.userAnswer)), !qResult.isCorrect && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm" }, /* @__PURE__ */ React169__default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.correctAnswer")), " ", getAnswerDisplay(qResult.correctAnswer)), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React169__default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.pointsEarned")), " ", qResult.pointsEarned), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-muted-foreground" }, /* @__PURE__ */ React169__default.createElement("span", { className: "font-Medium" }, t4("practiceFlow.results.timeSpent")), " ", qResult.timeSpentSeconds?.toFixed(0) ?? "N/A", t4("practiceFlow.results.timeUnit")))))))))), /* @__PURE__ */ React169__default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, onExitQuiz && /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline", onClick: onExitQuiz, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), t4("common.exit")), showReviewButton && onGenerateReview && /* @__PURE__ */ React169__default.createElement(
|
|
96908
96908
|
Button,
|
|
96909
96909
|
{
|
|
96910
96910
|
onClick: onGenerateReview,
|
|
@@ -97219,7 +97219,7 @@ var QuizDataManagement = ({ onQuizLoad, currentQuiz }) => {
|
|
|
97219
97219
|
});
|
|
97220
97220
|
}
|
|
97221
97221
|
};
|
|
97222
|
-
return /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full shadow-lg" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, null, "Quiz Data Management"), /* @__PURE__ */ React169__default.createElement(CardDescription, null, "Import a quiz from a JSON file or export the current quiz configuration.")), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col gap-2 mb-4" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "quiz-file-input", className: "text-sm font-
|
|
97222
|
+
return /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full shadow-lg" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, null, "Quiz Data Management"), /* @__PURE__ */ React169__default.createElement(CardDescription, null, "Import a quiz from a JSON file or export the current quiz configuration.")), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col gap-2 mb-4" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "quiz-file-input", className: "text-sm font-Medium" }, "Import Quiz (JSON)"), /* @__PURE__ */ React169__default.createElement("div", { className: "relative" }, /* @__PURE__ */ React169__default.createElement(
|
|
97223
97223
|
Input,
|
|
97224
97224
|
{
|
|
97225
97225
|
id: "quiz-file-input",
|
|
@@ -97227,7 +97227,7 @@ var QuizDataManagement = ({ onQuizLoad, currentQuiz }) => {
|
|
|
97227
97227
|
accept: ".json",
|
|
97228
97228
|
ref: fileInputRef,
|
|
97229
97229
|
onChange: handleFileChange,
|
|
97230
|
-
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-
|
|
97230
|
+
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"
|
|
97231
97231
|
}
|
|
97232
97232
|
))), error && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-destructive flex items-center" }, /* @__PURE__ */ React169__default.createElement(CircleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__default.createElement(CardFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleExportQuiz, disabled: !currentQuiz, variant: "outline" }, /* @__PURE__ */ React169__default.createElement(Download, { className: "mr-2 h-4 w-4" }), " Export Current Quiz")));
|
|
97233
97233
|
};
|
|
@@ -97662,6 +97662,7 @@ var Close = DialogClose;
|
|
|
97662
97662
|
|
|
97663
97663
|
// src/react-ui/components/elements/dialog.tsx
|
|
97664
97664
|
var Dialog2 = Root10;
|
|
97665
|
+
var DialogTrigger2 = Trigger4;
|
|
97665
97666
|
var DialogPortal2 = Portal3;
|
|
97666
97667
|
var DialogClose2 = Close;
|
|
97667
97668
|
var DialogOverlay2 = React169.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React169.createElement(
|
|
@@ -97860,7 +97861,7 @@ var CommandGroup = React169.forwardRef(({ className, ...props }, ref) => /* @__P
|
|
|
97860
97861
|
{
|
|
97861
97862
|
ref,
|
|
97862
97863
|
className: cn(
|
|
97863
|
-
"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-
|
|
97864
|
+
"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",
|
|
97864
97865
|
className
|
|
97865
97866
|
),
|
|
97866
97867
|
...props
|
|
@@ -98261,7 +98262,7 @@ var TrueFalseQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98261
98262
|
const handleCorrectAnswerChange = (value) => {
|
|
98262
98263
|
onFormChange({ correctAnswer: value === "true" });
|
|
98263
98264
|
};
|
|
98264
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98265
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "True/False Specifics"), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Correct Answer"), /* @__PURE__ */ React169__default.createElement(
|
|
98265
98266
|
RadioGroup2,
|
|
98266
98267
|
{
|
|
98267
98268
|
value: question2.correctAnswer ? "true" : "false",
|
|
@@ -98298,7 +98299,7 @@ var MultipleChoiceQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98298
98299
|
const handleCorrectAnswerChange = (optionId) => {
|
|
98299
98300
|
onFormChange({ correctAnswerId: optionId });
|
|
98300
98301
|
};
|
|
98301
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98302
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Multiple Choice Specifics"), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Options"), question2.options.length === 0 && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No options added yet."), /* @__PURE__ */ React169__default.createElement(
|
|
98302
98303
|
RadioGroup2,
|
|
98303
98304
|
{
|
|
98304
98305
|
value: question2.correctAnswerId,
|
|
@@ -98360,7 +98361,7 @@ var MultipleResponseQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98360
98361
|
const newCorrectAnswerIds = question2.correctAnswerIds.filter((id3) => id3 !== optionIdToDelete);
|
|
98361
98362
|
onFormChange({ options: newOptions, correctAnswerIds: newCorrectAnswerIds });
|
|
98362
98363
|
};
|
|
98363
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98364
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Multiple Response Specifics"), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Options (Select all correct answers)"), question2.options.length === 0 && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No options added yet."), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 mt-2" }, question2.options.map((option, index3) => (
|
|
98364
98365
|
// *** CHANGED: Adjusted layout for SimpleMarkdownEditor ***
|
|
98365
98366
|
/* @__PURE__ */ React169__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__default.createElement(
|
|
98366
98367
|
Checkbox2,
|
|
@@ -98582,7 +98583,7 @@ var ShortAnswerQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98582
98583
|
const toggleCaseSensitive = (checked) => {
|
|
98583
98584
|
onFormChange({ isCaseSensitive: checked });
|
|
98584
98585
|
};
|
|
98585
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98586
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Short Answer Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Accepted Answers"), /* @__PURE__ */ React169__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__default.createElement("p", { className: "text-sm text-muted-foreground mt-1" }, "No accepted answers defined yet."), question2.acceptedAnswers.map((answer, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: index3, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__default.createElement(
|
|
98586
98587
|
Input,
|
|
98587
98588
|
{
|
|
98588
98589
|
type: "text",
|
|
@@ -98632,7 +98633,7 @@ var NumericQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98632
98633
|
onFormChange({ tolerance: numValue });
|
|
98633
98634
|
}
|
|
98634
98635
|
};
|
|
98635
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98636
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Numeric Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `num-answer-${question2.id}` }, "Correct Numerical Answer"), /* @__PURE__ */ React169__default.createElement(
|
|
98636
98637
|
Input,
|
|
98637
98638
|
{
|
|
98638
98639
|
id: `num-answer-${question2.id}`,
|
|
@@ -98745,7 +98746,7 @@ var FillInTheBlanksQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98745
98746
|
const toggleCaseSensitive = (checked) => {
|
|
98746
98747
|
onFormChange({ isCaseSensitive: checked });
|
|
98747
98748
|
};
|
|
98748
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98749
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Fill In The Blanks Specifics"), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Question Segments"), /* @__PURE__ */ React169__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__default.createElement("div", { key: `segment-${index3}`, className: "flex items-start space-x-2 mt-2 p-2 border rounded-md bg-background" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex-grow space-y-1" }, /* @__PURE__ */ React169__default.createElement("span", { className: "text-xs font-Medium capitalize text-muted-foreground" }, segment.type, " Segment ", index3 + 1), segment.type === "text" ? (
|
|
98749
98750
|
// *** CHANGED: Replaced Textarea with SimpleMarkdownEditor ***
|
|
98750
98751
|
/* @__PURE__ */ React169__default.createElement(
|
|
98751
98752
|
SimpleMarkdownEditor,
|
|
@@ -98818,7 +98819,7 @@ var SequenceQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98818
98819
|
}
|
|
98819
98820
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
98820
98821
|
};
|
|
98821
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98822
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Sequence Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Sequence Items"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "space-y-3 mt-2" }, question2.items.map((item, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: item.id, className: "flex items-start space-x-2 p-2 border rounded-md bg-background" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex-grow" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "text-xs font-Medium text-muted-foreground" }, "Item ", index3 + 1), /* @__PURE__ */ React169__default.createElement(
|
|
98822
98823
|
SimpleMarkdownEditor,
|
|
98823
98824
|
{
|
|
98824
98825
|
value: item.content,
|
|
@@ -98921,7 +98922,7 @@ var MatchingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
98921
98922
|
}
|
|
98922
98923
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
98923
98924
|
};
|
|
98924
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
98925
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Matching Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Prompts (Items to be matched)"), question2.prompts.map((promptItem, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: promptItem.id, className: "space-y-1" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "text-xs text-muted-foreground" }, "Prompt ", index3 + 1), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeletePrompt(index3), className: "h-7 w-7 text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__default.createElement(
|
|
98925
98926
|
SimpleMarkdownEditor,
|
|
98926
98927
|
{
|
|
98927
98928
|
value: promptItem.content,
|
|
@@ -99011,7 +99012,7 @@ var DragAndDropQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99011
99012
|
}
|
|
99012
99013
|
return htmlString.replace(/<[^>]*>?/gm, "");
|
|
99013
99014
|
};
|
|
99014
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
99015
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Drag and Drop Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `dnd-bgimage-${question2.id}` }, "Background Image URL (Optional)"), /* @__PURE__ */ React169__default.createElement(
|
|
99015
99016
|
Input,
|
|
99016
99017
|
{
|
|
99017
99018
|
id: `dnd-bgimage-${question2.id}`,
|
|
@@ -99098,7 +99099,7 @@ var HotspotQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99098
99099
|
}
|
|
99099
99100
|
onFormChange({ correctHotspotIds: newCorrectIds });
|
|
99100
99101
|
};
|
|
99101
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
99102
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Hotspot Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `hs-imageurl-${question2.id}` }, "Image URL"), /* @__PURE__ */ React169__default.createElement(
|
|
99102
99103
|
Input,
|
|
99103
99104
|
{
|
|
99104
99105
|
id: `hs-imageurl-${question2.id}`,
|
|
@@ -99116,7 +99117,7 @@ var HotspotQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99116
99117
|
onChange: (e3) => onFormChange({ imageAltText: e3.target.value || void 0 }),
|
|
99117
99118
|
placeholder: "Describe the image"
|
|
99118
99119
|
}
|
|
99119
|
-
)), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Hotspot Areas"), /* @__PURE__ */ React169__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__default.createElement("div", { key: hotspot.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-
|
|
99120
|
+
)), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Hotspot Areas"), /* @__PURE__ */ React169__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__default.createElement("div", { key: hotspot.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-Medium" }, "Hotspot ", index3 + 1, " (ID: ", hotspot.id, ")"), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeleteHotspot(index3), className: "h-8 w-8 text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `hs-shape-${hotspot.id}`, className: "text-xs" }, "Shape"), /* @__PURE__ */ React169__default.createElement(Select2, { value: hotspot.shape, onValueChange: (value) => handleHotspotChange(index3, "shape", value) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: `hs-shape-${hotspot.id}` }, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "rect" }, "Rectangle"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "circle" }, "Circle")))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__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__default.createElement(
|
|
99120
99121
|
Input,
|
|
99121
99122
|
{
|
|
99122
99123
|
id: `hs-coords-${hotspot.id}`,
|
|
@@ -99148,7 +99149,7 @@ var BlocklyProgrammingQuestionForm = ({ question: question2, onFormChange }) =>
|
|
|
99148
99149
|
const handleFieldChange = (field, value) => {
|
|
99149
99150
|
onFormChange({ [field]: value });
|
|
99150
99151
|
};
|
|
99151
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
99152
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Blockly Programming Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `blockly-toolbox-${question2.id}` }, "Toolbox Definition (XML)"), /* @__PURE__ */ React169__default.createElement(
|
|
99152
99153
|
Textarea,
|
|
99153
99154
|
{
|
|
99154
99155
|
id: `blockly-toolbox-${question2.id}`,
|
|
@@ -99197,7 +99198,7 @@ var ScratchProgrammingQuestionForm = ({ question: question2, onFormChange }) =>
|
|
|
99197
99198
|
const handleFieldChange = (field, value) => {
|
|
99198
99199
|
onFormChange({ [field]: value });
|
|
99199
99200
|
};
|
|
99200
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
99201
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Scratch Programming Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "scratch-toolbox-" + question2.id }, "Toolbox Definition (XML for Blockly)"), /* @__PURE__ */ React169__default.createElement(
|
|
99201
99202
|
Textarea,
|
|
99202
99203
|
{
|
|
99203
99204
|
id: "scratch-toolbox-" + question2.id,
|
|
@@ -99274,7 +99275,7 @@ var CodingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99274
99275
|
const newTestCases = question2.testCases.filter((_2, i2) => i2 !== index3);
|
|
99275
99276
|
onFormChange({ testCases: newTestCases });
|
|
99276
99277
|
};
|
|
99277
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-
|
|
99278
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4 border rounded-md bg-muted/30" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-Medium text-md" }, "Coding Question Specifics"), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `coding-lang-${question2.id}` }, "Programming Language"), /* @__PURE__ */ React169__default.createElement(
|
|
99278
99279
|
Select2,
|
|
99279
99280
|
{
|
|
99280
99281
|
value: question2.codingLanguage,
|
|
@@ -99300,7 +99301,7 @@ var CodingQuestionForm = ({ question: question2, onFormChange }) => {
|
|
|
99300
99301
|
placeholder: "Enter the complete, correct code solution here.",
|
|
99301
99302
|
className: "min-h-[200px] font-mono text-xs"
|
|
99302
99303
|
}
|
|
99303
|
-
)), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Test Cases"), question2.testCases.map((tc, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: tc.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-
|
|
99304
|
+
)), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Test Cases"), question2.testCases.map((tc, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: tc.id, className: "p-3 border rounded-md bg-background space-y-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-Medium" }, "Test Case ", index3 + 1), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => handleDeleteTestCase(index3), className: "h-8 w-8 text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `tc-input-${tc.id}`, className: "text-xs" }, "Input (JSON Array)"), /* @__PURE__ */ React169__default.createElement(
|
|
99304
99305
|
Input,
|
|
99305
99306
|
{
|
|
99306
99307
|
id: `tc-input-${tc.id}`,
|
|
@@ -99468,7 +99469,7 @@ var EditQuestionModal = ({
|
|
|
99468
99469
|
if (!isOpen || !editedQuestion) return null;
|
|
99469
99470
|
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
99470
99471
|
if (!open) onClose();
|
|
99471
|
-
} }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-[600px] md:max-w-[800px] lg:max-w-[1000px] max-h-[90vh]" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__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__default.createElement(DialogDescription2, null, "Configure the details for this question. Current type:", " ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold" }, editedQuestion.questionType))), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "max-h-[calc(80vh-150px)] p-1 pr-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "prompt", className: "font-semibold" }, "Question Prompt"), /* @__PURE__ */ React169__default.createElement(SimpleMarkdownEditor, { value: editedQuestion.prompt, onChange: (htmlContent) => handleSpecificFieldChange({ prompt: htmlContent }) })), renderSpecificForm(), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "points" }, "Points"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "difficulty" }, "Difficulty"), /* @__PURE__ */ React169__default.createElement(Select2, { value: editedQuestion.difficulty || "
|
|
99472
|
+
} }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-[600px] md:max-w-[800px] lg:max-w-[1000px] max-h-[90vh]" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__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__default.createElement(DialogDescription2, null, "Configure the details for this question. Current type:", " ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-semibold" }, editedQuestion.questionType))), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "max-h-[calc(80vh-150px)] p-1 pr-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6 p-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "prompt", className: "font-semibold" }, "Question Prompt"), /* @__PURE__ */ React169__default.createElement(SimpleMarkdownEditor, { value: editedQuestion.prompt, onChange: (htmlContent) => handleSpecificFieldChange({ prompt: htmlContent }) })), renderSpecificForm(), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "points" }, "Points"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "difficulty" }, "Difficulty"), /* @__PURE__ */ React169__default.createElement(Select2, { value: editedQuestion.difficulty || "Medium", onValueChange: (value) => handleSpecificFieldChange({ difficulty: value }) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Easy" }, "Easy"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Medium" }, "Medium"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Hard" }, "Hard"))))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "explanation" }, "Explanation (Optional)"), /* @__PURE__ */ React169__default.createElement(SimpleMarkdownEditor, { value: editedQuestion.explanation || "", onChange: (htmlContent) => handleSpecificFieldChange({ explanation: htmlContent }), minHeight: "100px" })), /* @__PURE__ */ React169__default.createElement("details", { className: "group", open: hasDropdownMetadata }, /* @__PURE__ */ React169__default.createElement("summary", { className: "cursor-pointer font-semibold text-primary hover:underline" }, "Advanced Metadata (Optional)"), renderMetadataFields()))), /* @__PURE__ */ React169__default.createElement(DialogFooter, { className: "pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", onClick: handleSaveClick }, /* @__PURE__ */ React169__default.createElement(Save, { className: "mr-2 h-4 w-4" }), " Save Question"))));
|
|
99472
99473
|
};
|
|
99473
99474
|
|
|
99474
99475
|
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
@@ -99618,12 +99619,12 @@ var QuizContextSchema = z.object({
|
|
|
99618
99619
|
originalSubject: z.string().optional(),
|
|
99619
99620
|
originalCategory: z.string().optional(),
|
|
99620
99621
|
originalTopic: z.string().optional(),
|
|
99621
|
-
|
|
99622
|
+
description: z.string().optional().describe("The full description of the learning objective for deep context."),
|
|
99622
99623
|
gradeBand: z.string().optional()
|
|
99623
99624
|
});
|
|
99624
99625
|
var BaseQuestionGenerationClientInputSchema = z.object({
|
|
99625
99626
|
language: z.string().optional().default("English"),
|
|
99626
|
-
difficulty: z.enum(["
|
|
99627
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]),
|
|
99627
99628
|
quizContext: QuizContextSchema.optional(),
|
|
99628
99629
|
imageUrl: z.string().url().optional().describe("Optional URL of an image to be used as context.")
|
|
99629
99630
|
});
|
|
@@ -99632,7 +99633,7 @@ var BaseQuestionZodSchema = z.object({
|
|
|
99632
99633
|
prompt: z.string().min(1),
|
|
99633
99634
|
points: z.number().min(0).optional(),
|
|
99634
99635
|
explanation: z.string().optional(),
|
|
99635
|
-
difficulty: z.enum(["
|
|
99636
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99636
99637
|
topic: z.string().optional(),
|
|
99637
99638
|
category: z.string().optional(),
|
|
99638
99639
|
subject: z.string().optional(),
|
|
@@ -99728,7 +99729,7 @@ var AITrueFalseOutputFieldsSchema = z.object({
|
|
|
99728
99729
|
correctAnswer: z.boolean(),
|
|
99729
99730
|
explanation: z.string().optional().describe("An explanation of why the statement is true or false, especially important if false."),
|
|
99730
99731
|
points: z.number().optional().default(10),
|
|
99731
|
-
difficulty: z.enum(["
|
|
99732
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99732
99733
|
topic: z.string().optional(),
|
|
99733
99734
|
verifiedCategory: z.string().optional()
|
|
99734
99735
|
// Thêm để xác thực
|
|
@@ -99749,7 +99750,7 @@ Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a bool
|
|
|
99749
99750
|
const misconceptionGuidance = quizContext?.targetMisconception ? `**Target Misconception:** The statement you create MUST be FALSE and based on this common mistake: "${quizContext.targetMisconception}"` : "";
|
|
99750
99751
|
const contextStrings = [
|
|
99751
99752
|
`**Required Category:** ${category}`,
|
|
99752
|
-
quizContext?.
|
|
99753
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
99753
99754
|
imageContextInstruction,
|
|
99754
99755
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
99755
99756
|
misconceptionGuidance,
|
|
@@ -99760,7 +99761,7 @@ Previous attempts failed. Ensure the JSON is valid and 'correctAnswer' is a bool
|
|
|
99760
99761
|
correctAnswer: true,
|
|
99761
99762
|
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 '!'.",
|
|
99762
99763
|
points: 10,
|
|
99763
|
-
difficulty: "
|
|
99764
|
+
difficulty: "Easy",
|
|
99764
99765
|
topic: "Swift Optionals",
|
|
99765
99766
|
verifiedCategory: category
|
|
99766
99767
|
}, null, 2);
|
|
@@ -99896,7 +99897,7 @@ var AIMCQOutputFieldsSchema = z.object({
|
|
|
99896
99897
|
correctTempOptionId: z.string().describe("The temporary ID of the correct option from the generated options array."),
|
|
99897
99898
|
explanation: z.string().optional().describe("A brief explanation of why the answer is correct."),
|
|
99898
99899
|
points: z.number().optional().default(10),
|
|
99899
|
-
difficulty: z.enum(["
|
|
99900
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
99900
99901
|
topic: z.string().optional(),
|
|
99901
99902
|
verifiedCategory: z.string().optional().describe("The category this question actually addresses.")
|
|
99902
99903
|
});
|
|
@@ -99915,7 +99916,7 @@ Previous attempts failed...
|
|
|
99915
99916
|
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.` : "";
|
|
99916
99917
|
const contextStrings = [
|
|
99917
99918
|
`**Required Category:** ${category}`,
|
|
99918
|
-
quizContext?.
|
|
99919
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
99919
99920
|
imageContextInstruction,
|
|
99920
99921
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
99921
99922
|
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers: "${quizContext.targetMisconception}"`,
|
|
@@ -99932,7 +99933,7 @@ Previous attempts failed...
|
|
|
99932
99933
|
correctTempOptionId: "C",
|
|
99933
99934
|
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.`,
|
|
99934
99935
|
points: 10,
|
|
99935
|
-
difficulty: "
|
|
99936
|
+
difficulty: "Easy",
|
|
99936
99937
|
topic: `Control Flow in ${category}`,
|
|
99937
99938
|
verifiedCategory: category
|
|
99938
99939
|
}, null, 2);
|
|
@@ -100064,807 +100065,6 @@ async function generateMCQQuestion(clientInput, apiKey) {
|
|
|
100064
100065
|
return { error: errorMessage };
|
|
100065
100066
|
}
|
|
100066
100067
|
|
|
100067
|
-
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
100068
|
-
var supportedQuestionTypesForAI = [
|
|
100069
|
-
{ value: "true_false", label: "True/False" },
|
|
100070
|
-
{ value: "multiple_choice", label: "Multiple Choice" },
|
|
100071
|
-
{ value: "multiple_response", label: "Multiple Response" },
|
|
100072
|
-
{ value: "short_answer", label: "Short Answer" },
|
|
100073
|
-
{ value: "numeric", label: "Numeric" },
|
|
100074
|
-
{ value: "fill_in_the_blanks", label: "Fill In The Blanks" },
|
|
100075
|
-
{ value: "sequence", label: "Sequence" },
|
|
100076
|
-
{ value: "matching", label: "Matching" }
|
|
100077
|
-
];
|
|
100078
|
-
var AIQuestionGeneratorModal = ({
|
|
100079
|
-
isOpen,
|
|
100080
|
-
onClose,
|
|
100081
|
-
onQuestionGenerated,
|
|
100082
|
-
language: language3,
|
|
100083
|
-
questionType: questionTypeProp,
|
|
100084
|
-
subjects = [],
|
|
100085
|
-
topics = [],
|
|
100086
|
-
gradeLevels = [],
|
|
100087
|
-
bloomLevels = []
|
|
100088
|
-
}) => {
|
|
100089
|
-
const [prompt, setPrompt] = useState("");
|
|
100090
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
100091
|
-
const [error, setError] = useState(null);
|
|
100092
|
-
const { toast: toast2 } = useToast();
|
|
100093
|
-
const [subjectCode, setSubjectCode] = useState("");
|
|
100094
|
-
const [topicCode, setTopicCode] = useState("");
|
|
100095
|
-
const [gradeBand, setGradeBand] = useState("");
|
|
100096
|
-
const [bloomLevelCode, setBloomLevelCode] = useState("");
|
|
100097
|
-
const [selectedQuestionType, setSelectedQuestionType] = useState("multiple_choice");
|
|
100098
|
-
const [isApiKeyManagerModalOpen, setIsApiKeyManagerModalOpen] = useState(false);
|
|
100099
|
-
const [geminiApiKeyExists, setGeminiApiKeyExists] = useState(false);
|
|
100100
|
-
const finalQuestionType = questionTypeProp || selectedQuestionType;
|
|
100101
|
-
useEffect(() => {
|
|
100102
|
-
if (isOpen) {
|
|
100103
|
-
setPrompt("");
|
|
100104
|
-
setError(null);
|
|
100105
|
-
setIsLoading(false);
|
|
100106
|
-
setSubjectCode("");
|
|
100107
|
-
setTopicCode("");
|
|
100108
|
-
setGradeBand("");
|
|
100109
|
-
setBloomLevelCode("");
|
|
100110
|
-
setSelectedQuestionType(questionTypeProp || "multiple_choice");
|
|
100111
|
-
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
100112
|
-
}
|
|
100113
|
-
}, [isOpen, questionTypeProp]);
|
|
100114
|
-
const filteredTopics = useMemo(() => {
|
|
100115
|
-
if (!subjectCode) return [];
|
|
100116
|
-
return topics.filter((t4) => t4.subjectCode === subjectCode);
|
|
100117
|
-
}, [subjectCode, topics]);
|
|
100118
|
-
const handleApiKeyModalClose = () => {
|
|
100119
|
-
setIsApiKeyManagerModalOpen(false);
|
|
100120
|
-
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
100121
|
-
};
|
|
100122
|
-
const handleSubmit = async () => {
|
|
100123
|
-
if (!prompt.trim()) {
|
|
100124
|
-
setError("Please provide a prompt for the question.");
|
|
100125
|
-
return;
|
|
100126
|
-
}
|
|
100127
|
-
const geminiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
|
|
100128
|
-
if (!geminiKey) {
|
|
100129
|
-
setError("Gemini API Key is not set. Please configure it.");
|
|
100130
|
-
setGeminiApiKeyExists(false);
|
|
100131
|
-
return;
|
|
100132
|
-
}
|
|
100133
|
-
setGeminiApiKeyExists(true);
|
|
100134
|
-
setError(null);
|
|
100135
|
-
setIsLoading(true);
|
|
100136
|
-
try {
|
|
100137
|
-
const quizContext = {
|
|
100138
|
-
plannedTopic: prompt,
|
|
100139
|
-
originalSubject: subjectCode,
|
|
100140
|
-
originalTopic: topicCode,
|
|
100141
|
-
gradeBand,
|
|
100142
|
-
plannedBloomLevel: bloomLevelCode
|
|
100143
|
-
};
|
|
100144
|
-
const baseClientInput = {
|
|
100145
|
-
language: language3,
|
|
100146
|
-
difficulty: "medium",
|
|
100147
|
-
quizContext
|
|
100148
|
-
};
|
|
100149
|
-
let generatedResult = {};
|
|
100150
|
-
switch (finalQuestionType) {
|
|
100151
|
-
case "true_false":
|
|
100152
|
-
generatedResult = await generateTrueFalseQuestion(baseClientInput, geminiKey);
|
|
100153
|
-
break;
|
|
100154
|
-
case "multiple_choice":
|
|
100155
|
-
generatedResult = await generateMCQQuestion({ ...baseClientInput, numberOfOptions: 4 }, geminiKey);
|
|
100156
|
-
break;
|
|
100157
|
-
// Add other cases as needed
|
|
100158
|
-
default:
|
|
100159
|
-
throw new Error(`AI generation for '${finalQuestionType}' is not implemented in this flow.`);
|
|
100160
|
-
}
|
|
100161
|
-
if (generatedResult.error) {
|
|
100162
|
-
throw new Error(generatedResult.error);
|
|
100163
|
-
}
|
|
100164
|
-
if (generatedResult.question) {
|
|
100165
|
-
const completeQuestion = generatedResult.question;
|
|
100166
|
-
completeQuestion.subject = subjectCode;
|
|
100167
|
-
completeQuestion.topic = topicCode;
|
|
100168
|
-
completeQuestion.gradeBand = gradeBand;
|
|
100169
|
-
completeQuestion.bloomLevel = bloomLevelCode;
|
|
100170
|
-
if (completeQuestion.points === void 0) completeQuestion.points = 10;
|
|
100171
|
-
onQuestionGenerated(completeQuestion);
|
|
100172
|
-
toast2({ title: "AI Question Generated", description: "Review and edit the generated question." });
|
|
100173
|
-
onClose();
|
|
100174
|
-
} else {
|
|
100175
|
-
throw new Error("AI did not return a valid question object.");
|
|
100176
|
-
}
|
|
100177
|
-
} catch (e3) {
|
|
100178
|
-
console.error("AI Question Generation Error:", e3);
|
|
100179
|
-
let errorMessage = e3.message || "An unknown error occurred.";
|
|
100180
|
-
if (typeof errorMessage === "string" && errorMessage.includes("API key not valid")) {
|
|
100181
|
-
errorMessage = "The provided Google Gemini API Key is invalid. Please check it.";
|
|
100182
|
-
setGeminiApiKeyExists(false);
|
|
100183
|
-
}
|
|
100184
|
-
setError(errorMessage);
|
|
100185
|
-
toast2({ title: "AI Generation Failed", description: errorMessage, variant: "destructive" });
|
|
100186
|
-
} finally {
|
|
100187
|
-
setIsLoading(false);
|
|
100188
|
-
}
|
|
100189
|
-
};
|
|
100190
|
-
const comboboxOptions = {
|
|
100191
|
-
subjects: subjects.map((s4) => ({ value: s4.code, label: s4.name })),
|
|
100192
|
-
topics: filteredTopics.map((t4) => ({ value: t4.code, label: t4.name })),
|
|
100193
|
-
gradeLevels: gradeLevels.map((g) => ({ value: g.code, label: g.name })),
|
|
100194
|
-
bloomLevels: bloomLevels.map((b2) => ({ value: b2.code, label: b2.name }))
|
|
100195
|
-
};
|
|
100196
|
-
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
100197
|
-
if (!open) onClose();
|
|
100198
|
-
} }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-[600px]" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center font-headline text-2xl" }, /* @__PURE__ */ React169__default.createElement(WandSparkles, { className: "mr-2 h-6 w-6 text-primary" }), " AI Question Generator"), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, "Provide a prompt and metadata to generate a '", finalQuestionType, "' question.")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 py-4 max-h-[60vh] overflow-y-auto px-2" }, !geminiApiKeyExists && /* @__PURE__ */ React169__default.createElement("div", { className: "p-3 mb-4 border border-amber-500 bg-amber-50 rounded-md text-amber-700" }), !questionTypeProp && /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-q-type-select" }, "Question Type"), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedQuestionType, onValueChange: (v) => setSelectedQuestionType(v) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "ai-q-type-select" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, supportedQuestionTypesForAI.map((type) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: type.value, value: type.value }, type.label))))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-prompt" }, "Prompt / Topic"), /* @__PURE__ */ React169__default.createElement(
|
|
100199
|
-
Textarea,
|
|
100200
|
-
{
|
|
100201
|
-
id: "ai-prompt",
|
|
100202
|
-
value: prompt,
|
|
100203
|
-
onChange: (e3) => setPrompt(e3.target.value),
|
|
100204
|
-
placeholder: "e.g., The process of photosynthesis, The causes of World War II, Basic Algebra Equations",
|
|
100205
|
-
className: "min-h-[100px]"
|
|
100206
|
-
}
|
|
100207
|
-
)), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Subject"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.subjects, value: subjectCode, onChange: setSubjectCode, placeholder: "Select or type a Subject..." })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Topic"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.topics, value: topicCode, onChange: setTopicCode, placeholder: "Select or type a Topic...", disabled: !subjectCode })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Grade Level"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.gradeLevels, value: gradeBand, onChange: setGradeBand, placeholder: "Select or type a Grade..." })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Bloom's Level"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.bloomLevels, value: bloomLevelCode, onChange: setBloomLevelCode, placeholder: "Select a Bloom's Level..." }))), error && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-destructive flex items-center mt-2" }, /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", onClick: handleSubmit, disabled: isLoading || !prompt.trim() || !geminiApiKeyExists }, isLoading ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(WandSparkles, { className: "mr-2 h-4 w-4" }), "Generate Question")))), /* @__PURE__ */ React169__default.createElement(APIKeyManagerModal, { isOpen: isApiKeyManagerModalOpen, onClose: handleApiKeyModalClose }));
|
|
100208
|
-
};
|
|
100209
|
-
|
|
100210
|
-
// src/react-ui/components/authoring/AIFullQuizGeneratorModal.tsx
|
|
100211
|
-
init_react_shim();
|
|
100212
|
-
|
|
100213
|
-
// src/ai/flows/generate-quiz-plan.ts
|
|
100214
|
-
init_react_shim();
|
|
100215
|
-
|
|
100216
|
-
// src/ai/flows/generate-quiz-plan-types.ts
|
|
100217
|
-
init_react_shim();
|
|
100218
|
-
var TopicWithMetadataSchema = z.object({
|
|
100219
|
-
topic: z.string().min(1),
|
|
100220
|
-
ratio: z.number().min(0).max(100),
|
|
100221
|
-
originalLoId: z.string().optional(),
|
|
100222
|
-
originalSubject: z.string().optional(),
|
|
100223
|
-
originalCategory: z.string().optional(),
|
|
100224
|
-
originalTopic: z.string().optional(),
|
|
100225
|
-
commonMisconceptions: z.array(z.string()).optional()
|
|
100226
|
-
});
|
|
100227
|
-
var BloomLevelStringsEnum = z.enum(["remembering", "understanding", "applying", "analyzing", "evaluating", "creating"]);
|
|
100228
|
-
var fullQuizSupportedQuestionTypesArray = [
|
|
100229
|
-
"true_false",
|
|
100230
|
-
"multiple_choice",
|
|
100231
|
-
"multiple_response",
|
|
100232
|
-
"short_answer",
|
|
100233
|
-
"numeric",
|
|
100234
|
-
"fill_in_the_blanks",
|
|
100235
|
-
"sequence",
|
|
100236
|
-
"matching",
|
|
100237
|
-
"drag_and_drop",
|
|
100238
|
-
"coding"
|
|
100239
|
-
];
|
|
100240
|
-
z.object({
|
|
100241
|
-
language: z.string().optional().default("English"),
|
|
100242
|
-
totalQuestions: z.number().int().min(1).max(50),
|
|
100243
|
-
numCodingQuestions: z.number().optional().default(0),
|
|
100244
|
-
topics: z.array(TopicWithMetadataSchema).min(1),
|
|
100245
|
-
bloomLevels: z.array(z.object({
|
|
100246
|
-
level: BloomLevelStringsEnum,
|
|
100247
|
-
ratio: z.number().min(0).max(100)
|
|
100248
|
-
})).min(1),
|
|
100249
|
-
selectedContextIds: z.array(z.string()).optional(),
|
|
100250
|
-
selectedQuestionTypes: z.array(z.enum(fullQuizSupportedQuestionTypesArray)).min(1),
|
|
100251
|
-
imageContexts: z.array(z.custom()).optional().describe("Library of available image contexts for the AI to use.")
|
|
100252
|
-
});
|
|
100253
|
-
var PlannedQuestionSchema = z.object({
|
|
100254
|
-
plannedTopic: z.string().min(1).describe("The specific, assessable topic for this question."),
|
|
100255
|
-
plannedQuestionType: z.enum(fullQuizSupportedQuestionTypesArray).describe("The specific question type chosen."),
|
|
100256
|
-
plannedBloomLevel: BloomLevelStringsEnum.describe("The Bloom's level assigned."),
|
|
100257
|
-
plannedContextId: z.string().optional().describe("The specific context ID chosen for this question."),
|
|
100258
|
-
imageId: z.string().nullable().optional().describe("The ID of the image from the context library to be used for this question."),
|
|
100259
|
-
targetMisconception: z.string().optional().describe("A specific common misconception this question should target."),
|
|
100260
|
-
difficultyReason: z.string().optional().describe("Strategic explanation of difficulty choice and placement."),
|
|
100261
|
-
topicSpecificity: z.enum(["broad", "focused", "specific"]).optional().describe("How specific the topic coverage should be."),
|
|
100262
|
-
originalLoId: z.string().optional(),
|
|
100263
|
-
originalSubject: z.string().optional(),
|
|
100264
|
-
originalCategory: z.string().optional(),
|
|
100265
|
-
originalTopic: z.string().optional()
|
|
100266
|
-
});
|
|
100267
|
-
var GenerateQuizPlanOutputSchema = z.object({
|
|
100268
|
-
quizPlan: z.array(PlannedQuestionSchema).describe("A detailed plan for each question in the quiz."),
|
|
100269
|
-
diversityMetrics: z.object({
|
|
100270
|
-
questionTypeDistribution: z.record(z.number()).optional(),
|
|
100271
|
-
bloomLevelDistribution: z.record(z.number()).optional(),
|
|
100272
|
-
maxConsecutiveSameType: z.number().optional()
|
|
100273
|
-
}).optional().describe("Metrics showing the diversity achieved in the plan."),
|
|
100274
|
-
planningStrategy: z.object({
|
|
100275
|
-
overallApproach: z.string().optional(),
|
|
100276
|
-
keyDecisions: z.array(z.string()).optional()
|
|
100277
|
-
}).optional()
|
|
100278
|
-
});
|
|
100279
|
-
|
|
100280
|
-
// src/ai/flows/generate-quiz-plan.ts
|
|
100281
|
-
var QuizPlanLogger = class {
|
|
100282
|
-
constructor() {
|
|
100283
|
-
this.logs = [];
|
|
100284
|
-
this.startTime = Date.now();
|
|
100285
|
-
}
|
|
100286
|
-
log(phase, data, duration) {
|
|
100287
|
-
this.logs.push({
|
|
100288
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
100289
|
-
phase,
|
|
100290
|
-
data,
|
|
100291
|
-
duration
|
|
100292
|
-
});
|
|
100293
|
-
if (duration !== void 0) {
|
|
100294
|
-
console.log(`[${phase}] Completed in ${duration}ms:`, data);
|
|
100295
|
-
} else {
|
|
100296
|
-
console.log(`[${phase}]:`, data);
|
|
100297
|
-
}
|
|
100298
|
-
}
|
|
100299
|
-
getLogs() {
|
|
100300
|
-
return this.logs;
|
|
100301
|
-
}
|
|
100302
|
-
getTotalDuration() {
|
|
100303
|
-
return Date.now() - this.startTime;
|
|
100304
|
-
}
|
|
100305
|
-
};
|
|
100306
|
-
function generateQuestionTypeSelectionGuidance() {
|
|
100307
|
-
return `
|
|
100308
|
-
QUESTION TYPE SELECTION BEST PRACTICES:
|
|
100309
|
-
|
|
100310
|
-
**TRUE_FALSE (true_false)**
|
|
100311
|
-
- Best for: Binary concepts, fact verification, common misconceptions
|
|
100312
|
-
- Bloom levels: Primarily Remembering, Understanding
|
|
100313
|
-
- Use when: Testing definitive statements, clarifying misconceptions
|
|
100314
|
-
- Example: "Photosynthesis only occurs during daytime" (targets timing misconception)
|
|
100315
|
-
|
|
100316
|
-
**MULTIPLE CHOICE (multiple_choice)**
|
|
100317
|
-
- Best for: Concept selection, process understanding, comparison
|
|
100318
|
-
- Bloom levels: All levels, especially Understanding and Applying
|
|
100319
|
-
- Use when: Testing conceptual understanding with clear alternatives
|
|
100320
|
-
- Example: "Which factor most affects enzyme activity?" (applying knowledge)
|
|
100321
|
-
|
|
100322
|
-
**MULTIPLE RESPONSE (multiple_response)**
|
|
100323
|
-
- Best for: Identifying multiple correct factors, comprehensive understanding
|
|
100324
|
-
- Bloom levels: Understanding, Analyzing, Evaluating
|
|
100325
|
-
- Use when: Multiple correct answers exist, testing thorough knowledge
|
|
100326
|
-
- Example: "Select all factors that influence plant growth" (analyzing components)
|
|
100327
|
-
|
|
100328
|
-
**FILL IN THE BLANKS (fill_in_the_blanks)**
|
|
100329
|
-
- Best for: Key terminology, formulas, specific facts
|
|
100330
|
-
- Bloom levels: Remembering, Understanding
|
|
100331
|
-
- Use when: Testing precise recall of important terms/concepts
|
|
100332
|
-
- Example: "The process of _____ converts light energy to chemical energy"
|
|
100333
|
-
|
|
100334
|
-
**NUMERIC (numeric)**
|
|
100335
|
-
- Best for: Calculations, quantitative problems, formula application
|
|
100336
|
-
- Bloom levels: Applying, Analyzing
|
|
100337
|
-
- Use when: Mathematical computations are required
|
|
100338
|
-
- Example: "Calculate the molarity of a 2L solution containing 0.5 moles of NaCl"
|
|
100339
|
-
|
|
100340
|
-
**MATCHING (matching)**
|
|
100341
|
-
- Best for: Connecting related concepts, terminology pairs
|
|
100342
|
-
- Bloom levels: Remembering, Understanding
|
|
100343
|
-
- Use when: Testing relationships between terms and definitions
|
|
100344
|
-
- Example: Match organelles with their functions
|
|
100345
|
-
|
|
100346
|
-
**SEQUENCE (sequence)**
|
|
100347
|
-
- Best for: Process steps, chronological order, procedural knowledge
|
|
100348
|
-
- Bloom levels: Understanding, Applying, Analyzing
|
|
100349
|
-
- Use when: Order or sequence is critical to understanding
|
|
100350
|
-
- Example: "Arrange the steps of mitosis in correct order"
|
|
100351
|
-
|
|
100352
|
-
**DRAG AND DROP (drag_and_drop)**
|
|
100353
|
-
- Best for: Categorization, classification, spatial relationships
|
|
100354
|
-
- Bloom levels: Understanding, Applying, Analyzing
|
|
100355
|
-
- Use when: Grouping or organizing information is key
|
|
100356
|
-
- Example: "Classify these compounds as acids, bases, or neutral"
|
|
100357
|
-
|
|
100358
|
-
**SHORT ANSWER (short_answer)**
|
|
100359
|
-
- Best for: Explanations, definitions, problem-solving steps
|
|
100360
|
-
- Bloom levels: Understanding, Applying, Analyzing, Evaluating, Creating
|
|
100361
|
-
- Use when: Requiring explanatory responses, open-ended thinking
|
|
100362
|
-
- Example: "Explain why enzymes are specific to certain substrates"
|
|
100363
|
-
|
|
100364
|
-
**CODING (coding)**
|
|
100365
|
-
- Best for: Programming problems, algorithm implementation
|
|
100366
|
-
- Bloom levels: Applying, Analyzing, Evaluating, Creating
|
|
100367
|
-
- Use when: Technical implementation or code analysis is required
|
|
100368
|
-
- Example: "Write a function to calculate factorial recursively"
|
|
100369
|
-
`;
|
|
100370
|
-
}
|
|
100371
|
-
function generateAdvancedBloomGuidance() {
|
|
100372
|
-
return `
|
|
100373
|
-
ADVANCED BLOOM'S TAXONOMY & QUESTION TYPE OPTIMIZATION:
|
|
100374
|
-
|
|
100375
|
-
**REMEMBERING (Knowledge Recall)**
|
|
100376
|
-
- Primary types: true_false, fill_in_the_blanks, matching
|
|
100377
|
-
- Secondary types: multiple_choice (simple recall)
|
|
100378
|
-
- Focus: Facts, terms, basic concepts, definitions
|
|
100379
|
-
- Misconception addressing: Use true_false to clarify common confusions
|
|
100380
|
-
|
|
100381
|
-
**UNDERSTANDING (Comprehension)**
|
|
100382
|
-
- Primary types: multiple_choice, short_answer, matching
|
|
100383
|
-
- Secondary types: multiple_response, sequence
|
|
100384
|
-
- Focus: Explanations, interpretations, examples, classifications
|
|
100385
|
-
- Best for: "Explain why...", "What does this mean...", "Give an example..."
|
|
100386
|
-
|
|
100387
|
-
**APPLYING (Using Knowledge)**
|
|
100388
|
-
- Primary types: numeric, short_answer, sequence, coding
|
|
100389
|
-
- Secondary types: multiple_choice, drag_and_drop
|
|
100390
|
-
- Focus: Problem-solving, implementing procedures, using methods
|
|
100391
|
-
- Best for: Calculations, step-by-step processes, practical applications
|
|
100392
|
-
|
|
100393
|
-
**ANALYZING (Breaking Down Information)**
|
|
100394
|
-
- Primary types: multiple_response, short_answer, sequence, coding
|
|
100395
|
-
- Secondary types: drag_and_drop, multiple_choice
|
|
100396
|
-
- Focus: Identifying components, relationships, cause-effect
|
|
100397
|
-
- Best for: "What are the factors...", "How do these relate...", "Break down..."
|
|
100398
|
-
|
|
100399
|
-
**EVALUATING (Making Judgments)**
|
|
100400
|
-
- Primary types: short_answer, multiple_response, coding
|
|
100401
|
-
- Secondary types: multiple_choice (with justification)
|
|
100402
|
-
- Focus: Critiquing, judging, comparing alternatives, decision-making
|
|
100403
|
-
- Best for: "Which is better and why...", "Evaluate the approach...", "Critique..."
|
|
100404
|
-
|
|
100405
|
-
**CREATING (Producing New Work)**
|
|
100406
|
-
- Primary types: short_answer, coding, sequence
|
|
100407
|
-
- Secondary types: drag_and_drop (design tasks)
|
|
100408
|
-
- Focus: Designing, planning, producing, constructing
|
|
100409
|
-
- Best for: "Design a solution...", "Create a plan...", "Develop a strategy..."
|
|
100410
|
-
`;
|
|
100411
|
-
}
|
|
100412
|
-
function generateDiversityRules() {
|
|
100413
|
-
return `
|
|
100414
|
-
ENHANCED DIVERSITY & QUALITY ASSURANCE RULES:
|
|
100415
|
-
|
|
100416
|
-
**Question Type Distribution Strategy:**
|
|
100417
|
-
1. Never place more than 3 consecutive questions of the same type
|
|
100418
|
-
2. Distribute question types based on their cognitive complexity
|
|
100419
|
-
3. For quizzes with 10+ questions, use at least 4 different question types
|
|
100420
|
-
4. For quizzes with 20+ questions, use at least 6 different question types
|
|
100421
|
-
5. Balance quick-answer types (true_false, multiple_choice) with deeper types (short_answer, coding)
|
|
100422
|
-
|
|
100423
|
-
**Intelligent Difficulty Progression:**
|
|
100424
|
-
1. Opening (20%): Start with confidence-building questions (Remembering/Understanding)
|
|
100425
|
-
2. Building (40%): Gradually increase complexity (Understanding/Applying)
|
|
100426
|
-
3. Peak (30%): Most challenging questions (Analyzing/Evaluating/Creating)
|
|
100427
|
-
4. Closing (10%): Moderate challenge to end positively
|
|
100428
|
-
|
|
100429
|
-
**Misconception-Driven Planning:**
|
|
100430
|
-
- When common misconceptions are provided, prioritize question types that can effectively address them
|
|
100431
|
-
- Use true_false for binary misconceptions
|
|
100432
|
-
- Use multiple_choice for concept selection misconceptions
|
|
100433
|
-
- Use short_answer for complex misconceptions requiring explanation
|
|
100434
|
-
|
|
100435
|
-
**Contextual Intelligence:**
|
|
100436
|
-
- Programming/Technical topics: Favor coding, numeric, short_answer
|
|
100437
|
-
- Process-oriented topics: Favor sequence, short_answer, drag_and_drop
|
|
100438
|
-
- Conceptual topics: Favor multiple_choice, multiple_response, true_false
|
|
100439
|
-
- Factual topics: Favor fill_in_the_blanks, matching, true_false
|
|
100440
|
-
`;
|
|
100441
|
-
}
|
|
100442
|
-
async function generateQuizPlan(clientInput, apiKey, imageContexts = []) {
|
|
100443
|
-
const logger = new QuizPlanLogger();
|
|
100444
|
-
try {
|
|
100445
|
-
logger.log("VALIDATION_START", {
|
|
100446
|
-
totalQuestions: clientInput.totalQuestions,
|
|
100447
|
-
availableTypes: clientInput.selectedQuestionTypes,
|
|
100448
|
-
topicCount: clientInput.topics.length,
|
|
100449
|
-
bloomLevelCount: clientInput.bloomLevels.length
|
|
100450
|
-
});
|
|
100451
|
-
const totalTopicRatio = clientInput.topics.reduce((sum, t4) => sum + t4.ratio, 0);
|
|
100452
|
-
if (Math.abs(totalTopicRatio - 100) > 1) {
|
|
100453
|
-
throw new Error(`Total topic ratio must be 100%. Current sum: ${totalTopicRatio.toFixed(1)}%`);
|
|
100454
|
-
}
|
|
100455
|
-
const totalBloomRatio = clientInput.bloomLevels.reduce((sum, b2) => sum + b2.ratio, 0);
|
|
100456
|
-
if (Math.abs(totalBloomRatio - 100) > 1) {
|
|
100457
|
-
throw new Error(`Total Bloom level ratio must be 100%. Current sum: ${totalBloomRatio.toFixed(1)}%`);
|
|
100458
|
-
}
|
|
100459
|
-
logger.log("VALIDATION_SUCCESS", {
|
|
100460
|
-
topicRatioSum: totalTopicRatio,
|
|
100461
|
-
bloomRatioSum: totalBloomRatio
|
|
100462
|
-
});
|
|
100463
|
-
const aiStartTime = Date.now();
|
|
100464
|
-
const ai = new GoogleGenAI({
|
|
100465
|
-
apiKey
|
|
100466
|
-
});
|
|
100467
|
-
const model = "gemini-2.5-pro";
|
|
100468
|
-
const config3 = {
|
|
100469
|
-
temperature: 0.8,
|
|
100470
|
-
thinkingConfig: {
|
|
100471
|
-
thinkingBudget: 4096
|
|
100472
|
-
},
|
|
100473
|
-
responseMimeType: "application/json"
|
|
100474
|
-
};
|
|
100475
|
-
logger.log("AI_INITIALIZATION", { model }, Date.now() - aiStartTime);
|
|
100476
|
-
const { language: language3, totalQuestions, numCodingQuestions = 0 } = clientInput;
|
|
100477
|
-
const promptStartTime = Date.now();
|
|
100478
|
-
const topicsDistribution = clientInput.topics.map((t4) => {
|
|
100479
|
-
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}%`;
|
|
100480
|
-
if (t4.commonMisconceptions && t4.commonMisconceptions.length > 0) {
|
|
100481
|
-
topicString += `
|
|
100482
|
-
- Common Misconceptions: [${t4.commonMisconceptions.join(", ")}]`;
|
|
100483
|
-
}
|
|
100484
|
-
return topicString;
|
|
100485
|
-
}).join("\n ");
|
|
100486
|
-
const bloomDistribution = clientInput.bloomLevels.map(
|
|
100487
|
-
(b2) => `- Level: "${b2.level}", Ratio: ${b2.ratio}%`
|
|
100488
|
-
).join("\n ");
|
|
100489
|
-
let questionTypesForPrompt = [...clientInput.selectedQuestionTypes];
|
|
100490
|
-
if (numCodingQuestions === 0) {
|
|
100491
|
-
questionTypesForPrompt = questionTypesForPrompt.filter(
|
|
100492
|
-
(type) => type !== "short_answer" && type !== "coding"
|
|
100493
|
-
);
|
|
100494
|
-
}
|
|
100495
|
-
const allowedQuestionTypes = questionTypesForPrompt.map((t4) => `'${t4}'`).join(", ");
|
|
100496
|
-
const codingRequirement = numCodingQuestions > 0 ? `
|
|
100497
|
-
**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.` : "";
|
|
100498
|
-
const imageContextSection = imageContexts && imageContexts.length > 0 ? `
|
|
100499
|
-
## AVAILABLE IMAGE CONTEXT LIBRARY
|
|
100500
|
-
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.
|
|
100501
|
-
|
|
100502
|
-
\`\`\`json
|
|
100503
|
-
${JSON.stringify(imageContexts.map((img) => ({ imageId: img.id, subject: img.subject, category: img.category, topic: img.topic, description: img.detailedDescription })), null, 2)}
|
|
100504
|
-
\`\`\`
|
|
100505
|
-
` : "";
|
|
100506
|
-
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.
|
|
100507
|
-
|
|
100508
|
-
${generateQuestionTypeSelectionGuidance()}
|
|
100509
|
-
|
|
100510
|
-
${generateAdvancedBloomGuidance()}
|
|
100511
|
-
|
|
100512
|
-
${generateDiversityRules()}
|
|
100513
|
-
|
|
100514
|
-
## COMPREHENSIVE QUIZ REQUIREMENTS:
|
|
100515
|
-
|
|
100516
|
-
**Target Language**: ${language3}
|
|
100517
|
-
**Total Questions**: ${totalQuestions}${codingRequirement}
|
|
100518
|
-
|
|
100519
|
-
**Topic Distribution & Learning Context** (follow precisely):
|
|
100520
|
-
${topicsDistribution}
|
|
100521
|
-
|
|
100522
|
-
**Cognitive Complexity Distribution** (follow precisely):
|
|
100523
|
-
${bloomDistribution}
|
|
100524
|
-
|
|
100525
|
-
**Available Question Arsenal**: ${allowedQuestionTypes}
|
|
100526
|
-
|
|
100527
|
-
**Image Resources**: ${imageContextSection}
|
|
100528
|
-
|
|
100529
|
-
## STRATEGIC PLANNING METHODOLOGY:
|
|
100530
|
-
|
|
100531
|
-
1. **Misconception Analysis**: If common misconceptions are provided, design questions specifically to address and correct them
|
|
100532
|
-
2. **Question Type Intelligence**: Select question types based on the cognitive demands and content nature
|
|
100533
|
-
3. **Difficulty Orchestration**: Create a learning journey that builds confidence while challenging appropriately
|
|
100534
|
-
4. **Diversity Optimization**: Ensure variety prevents monotony and maintains engagement
|
|
100535
|
-
5. **Context Sensitivity**: Match question types to topic characteristics (technical, conceptual, procedural)
|
|
100536
|
-
6. **Image Context Integration**: Intelligently associate questions with relevant images from the provided library to enhance contextual understanding.
|
|
100537
|
-
|
|
100538
|
-
## ENHANCED OUTPUT FORMAT:
|
|
100539
|
-
|
|
100540
|
-
Return ONLY a JSON object with this EXACT structure:
|
|
100541
|
-
|
|
100542
|
-
\`\`\`json
|
|
100543
|
-
{
|
|
100544
|
-
"quizPlan": [
|
|
100545
|
-
{
|
|
100546
|
-
"plannedTopic": "Specific, assessable topic derived from provided context",
|
|
100547
|
-
"plannedQuestionType": "question_type_from_allowed_list",
|
|
100548
|
-
"plannedBloomLevel": "bloom_level_from_requirements",
|
|
100549
|
-
"plannedContextId": "THEO_ABS",
|
|
100550
|
-
"imageId": "imgctx_12345abcde", // or null
|
|
100551
|
-
"targetMisconception": "Specific misconception this question addresses (or 'none' if not applicable)",
|
|
100552
|
-
"difficultyReason": "Strategic explanation of difficulty choice and placement",
|
|
100553
|
-
"topicSpecificity": "broad|focused|specific",
|
|
100554
|
-
"originalLoId": "corresponding_LoId_from_input",
|
|
100555
|
-
"originalSubject": "corresponding_Subject_from_input",
|
|
100556
|
-
"originalCategory": "corresponding_Category_from_input",
|
|
100557
|
-
"originalTopic": "corresponding_Topic_from_input"
|
|
100558
|
-
}
|
|
100559
|
-
],
|
|
100560
|
-
"diversityMetrics": {
|
|
100561
|
-
"questionTypeDistribution": {"type1": count1, "type2": count2},
|
|
100562
|
-
"bloomLevelDistribution": {"level1": count1, "level2": count2},
|
|
100563
|
-
"maxConsecutiveSameType": number,
|
|
100564
|
-
"difficultyProgression": "description of how difficulty progresses",
|
|
100565
|
-
"misconceptionCoverage": number_of_misconceptions_addressed
|
|
100566
|
-
},
|
|
100567
|
-
"planningStrategy": {
|
|
100568
|
-
"overallApproach": "Brief description of the strategic approach taken",
|
|
100569
|
-
"keyDecisions": ["Major decision 1", "Major decision 2", "Major decision 3"]
|
|
100570
|
-
}
|
|
100571
|
-
}
|
|
100572
|
-
\`\`\`
|
|
100573
|
-
|
|
100574
|
-
Execute this plan with pedagogical precision. The quiz should feel like a carefully crafted learning journey that challenges and educates simultaneously.`;
|
|
100575
|
-
logger.log("PROMPT_PREPARATION", {
|
|
100576
|
-
promptLength: enhancedPromptText.length,
|
|
100577
|
-
topicCount: clientInput.topics.length,
|
|
100578
|
-
misconceptionCount: clientInput.topics.reduce((sum, t4) => sum + (t4.commonMisconceptions?.length || 0), 0)
|
|
100579
|
-
}, Date.now() - promptStartTime);
|
|
100580
|
-
const generationStartTime = Date.now();
|
|
100581
|
-
const contents = [
|
|
100582
|
-
{
|
|
100583
|
-
role: "user",
|
|
100584
|
-
parts: [
|
|
100585
|
-
{
|
|
100586
|
-
text: enhancedPromptText
|
|
100587
|
-
}
|
|
100588
|
-
]
|
|
100589
|
-
}
|
|
100590
|
-
];
|
|
100591
|
-
const aiResult = await ai.models.generateContent({
|
|
100592
|
-
model,
|
|
100593
|
-
config: config3,
|
|
100594
|
-
contents
|
|
100595
|
-
});
|
|
100596
|
-
const response = aiResult;
|
|
100597
|
-
const generationDuration = Date.now() - generationStartTime;
|
|
100598
|
-
logger.log("AI_GENERATION", {
|
|
100599
|
-
responseLength: response.candidates?.[0]?.content?.parts?.[0]?.text?.length || 0,
|
|
100600
|
-
duration: generationDuration
|
|
100601
|
-
}, generationDuration);
|
|
100602
|
-
const processingStartTime = Date.now();
|
|
100603
|
-
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
100604
|
-
let jsonText = rawText;
|
|
100605
|
-
if (!rawText.trim().startsWith("{") && !rawText.trim().startsWith("[")) {
|
|
100606
|
-
jsonText = extractJsonFromMarkdown(rawText);
|
|
100607
|
-
}
|
|
100608
|
-
logger.log("JSON_EXTRACTION", {
|
|
100609
|
-
rawTextLength: rawText.length,
|
|
100610
|
-
extractedJsonLength: jsonText.length
|
|
100611
|
-
});
|
|
100612
|
-
const aiGeneratedContent = GenerateQuizPlanOutputSchema.parse(JSON.parse(jsonText));
|
|
100613
|
-
logger.log("SCHEMA_VALIDATION", { success: true }, Date.now() - processingStartTime);
|
|
100614
|
-
const validationStartTime = Date.now();
|
|
100615
|
-
if (aiGeneratedContent.quizPlan.length !== clientInput.totalQuestions) {
|
|
100616
|
-
throw new Error(`AI planned for ${aiGeneratedContent.quizPlan.length} questions, but ${clientInput.totalQuestions} were requested.`);
|
|
100617
|
-
}
|
|
100618
|
-
const invalidTypes = [];
|
|
100619
|
-
aiGeneratedContent.quizPlan.forEach((item, index3) => {
|
|
100620
|
-
if (!clientInput.selectedQuestionTypes.includes(item.plannedQuestionType)) {
|
|
100621
|
-
invalidTypes.push(`Question ${index3 + 1}: '${item.plannedQuestionType}'`);
|
|
100622
|
-
}
|
|
100623
|
-
});
|
|
100624
|
-
if (invalidTypes.length > 0) {
|
|
100625
|
-
throw new Error(`Invalid question types found: ${invalidTypes.join(", ")}`);
|
|
100626
|
-
}
|
|
100627
|
-
const codingQuestions = aiGeneratedContent.quizPlan.filter((q2) => q2.plannedQuestionType === "coding");
|
|
100628
|
-
if (numCodingQuestions > 0 && codingQuestions.length !== numCodingQuestions) {
|
|
100629
|
-
throw new Error(`Expected ${numCodingQuestions} coding questions, but got ${codingQuestions.length}`);
|
|
100630
|
-
}
|
|
100631
|
-
const diversityAnalysis = validateConsecutiveTypes(aiGeneratedContent.quizPlan);
|
|
100632
|
-
logger.log("VALIDATION_COMPLETE", {
|
|
100633
|
-
questionCount: aiGeneratedContent.quizPlan.length,
|
|
100634
|
-
codingQuestionCount: codingQuestions.length,
|
|
100635
|
-
maxConsecutiveType: diversityAnalysis.maxConsecutive,
|
|
100636
|
-
questionTypeDistribution: aiGeneratedContent.diversityMetrics?.questionTypeDistribution || {}
|
|
100637
|
-
}, Date.now() - validationStartTime);
|
|
100638
|
-
const finalResult = {
|
|
100639
|
-
...aiGeneratedContent,
|
|
100640
|
-
logs: logger.getLogs()
|
|
100641
|
-
};
|
|
100642
|
-
logger.log("GENERATION_COMPLETE", {
|
|
100643
|
-
totalDuration: logger.getTotalDuration(),
|
|
100644
|
-
success: true,
|
|
100645
|
-
finalQuestionCount: finalResult.quizPlan.length
|
|
100646
|
-
}, logger.getTotalDuration());
|
|
100647
|
-
console.log("\n=== QUIZ PLAN GENERATION SUMMARY ===");
|
|
100648
|
-
console.log(`\u2705 Successfully generated ${finalResult.quizPlan.length} questions`);
|
|
100649
|
-
console.log(`\u23F1\uFE0F Total generation time: ${logger.getTotalDuration()}ms`);
|
|
100650
|
-
console.log(`\u{1F3AF} Question types: ${Object.keys(finalResult.diversityMetrics?.questionTypeDistribution || {}).join(", ")}`);
|
|
100651
|
-
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(finalResult.diversityMetrics?.bloomLevelDistribution || {}).join(", ")}`);
|
|
100652
|
-
if (numCodingQuestions > 0) {
|
|
100653
|
-
console.log(`\u{1F4BB} Coding questions: ${codingQuestions.length}/${numCodingQuestions} required`);
|
|
100654
|
-
}
|
|
100655
|
-
console.log(JSON.stringify(finalResult));
|
|
100656
|
-
console.log("=====================================\n");
|
|
100657
|
-
return finalResult;
|
|
100658
|
-
} catch (error) {
|
|
100659
|
-
logger.log("ERROR", {
|
|
100660
|
-
message: error.message,
|
|
100661
|
-
stack: error.stack,
|
|
100662
|
-
totalDuration: logger.getTotalDuration()
|
|
100663
|
-
});
|
|
100664
|
-
console.error("\u274C Quiz Plan Generation Failed:", error.message);
|
|
100665
|
-
console.error("\u{1F4CB} Full logs available in returned object");
|
|
100666
|
-
throw new Error(`Failed to generate Quiz Plan: ${error.message}`);
|
|
100667
|
-
}
|
|
100668
|
-
}
|
|
100669
|
-
function validateConsecutiveTypes(quizPlan) {
|
|
100670
|
-
let maxConsecutive = 1;
|
|
100671
|
-
let maxType = quizPlan[0]?.plannedQuestionType || "";
|
|
100672
|
-
let maxStartIndex = 0;
|
|
100673
|
-
let currentConsecutive = 1;
|
|
100674
|
-
let currentType = quizPlan[0]?.plannedQuestionType || "";
|
|
100675
|
-
let currentStartIndex = 0;
|
|
100676
|
-
for (let i2 = 1; i2 < quizPlan.length; i2++) {
|
|
100677
|
-
if (quizPlan[i2].plannedQuestionType === currentType) {
|
|
100678
|
-
currentConsecutive++;
|
|
100679
|
-
} else {
|
|
100680
|
-
if (currentConsecutive > maxConsecutive) {
|
|
100681
|
-
maxConsecutive = currentConsecutive;
|
|
100682
|
-
maxType = currentType;
|
|
100683
|
-
maxStartIndex = currentStartIndex;
|
|
100684
|
-
}
|
|
100685
|
-
currentConsecutive = 1;
|
|
100686
|
-
currentType = quizPlan[i2].plannedQuestionType;
|
|
100687
|
-
currentStartIndex = i2;
|
|
100688
|
-
}
|
|
100689
|
-
}
|
|
100690
|
-
if (currentConsecutive > maxConsecutive) {
|
|
100691
|
-
maxConsecutive = currentConsecutive;
|
|
100692
|
-
maxType = currentType;
|
|
100693
|
-
maxStartIndex = currentStartIndex;
|
|
100694
|
-
}
|
|
100695
|
-
return {
|
|
100696
|
-
maxConsecutive,
|
|
100697
|
-
type: maxType,
|
|
100698
|
-
startIndex: maxStartIndex
|
|
100699
|
-
};
|
|
100700
|
-
}
|
|
100701
|
-
|
|
100702
|
-
// src/ai/flows/generate-questions-from-quiz-plan.ts
|
|
100703
|
-
init_react_shim();
|
|
100704
|
-
|
|
100705
|
-
// src/services/TopicDataService.ts
|
|
100706
|
-
init_react_shim();
|
|
100707
|
-
var TopicDataService = class {
|
|
100708
|
-
/**
|
|
100709
|
-
* Saves an array of LearningObjective objects to Local Storage, overwriting existing data.
|
|
100710
|
-
* @param data The array of learning objectives to save.
|
|
100711
|
-
*/
|
|
100712
|
-
static saveData(data) {
|
|
100713
|
-
try {
|
|
100714
|
-
if (typeof window === "undefined") return;
|
|
100715
|
-
const serializedData = JSON.stringify(data);
|
|
100716
|
-
localStorage.setItem(this.STORAGE_KEY, serializedData);
|
|
100717
|
-
} catch (error) {
|
|
100718
|
-
console.error("Error saving learning objectives to Local Storage:", error);
|
|
100719
|
-
}
|
|
100720
|
-
}
|
|
100721
|
-
/**
|
|
100722
|
-
* Merges a new set of learning objectives with the existing data in Local Storage.
|
|
100723
|
-
* If an LO ID from newData already exists, it will be updated. Otherwise, it will be added.
|
|
100724
|
-
* @param newData The array of new or updated learning objectives.
|
|
100725
|
-
*/
|
|
100726
|
-
static mergeData(newData) {
|
|
100727
|
-
const existingData = this.getData();
|
|
100728
|
-
const loMap = new Map(existingData.map((lo) => [lo.loId, lo]));
|
|
100729
|
-
newData.forEach((newLo) => {
|
|
100730
|
-
loMap.set(newLo.loId, newLo);
|
|
100731
|
-
});
|
|
100732
|
-
const mergedData = Array.from(loMap.values());
|
|
100733
|
-
this.saveData(mergedData);
|
|
100734
|
-
}
|
|
100735
|
-
/**
|
|
100736
|
-
* Retrieves the array of LearningObjective objects from Local Storage.
|
|
100737
|
-
* @returns An array of learning objectives, or an empty array if none are found or an error occurs.
|
|
100738
|
-
*/
|
|
100739
|
-
static getData() {
|
|
100740
|
-
try {
|
|
100741
|
-
if (typeof window === "undefined") return [];
|
|
100742
|
-
const storedData = localStorage.getItem(this.STORAGE_KEY);
|
|
100743
|
-
return storedData ? JSON.parse(storedData) : [];
|
|
100744
|
-
} catch (error) {
|
|
100745
|
-
console.error("Error retrieving learning objectives from Local Storage:", error);
|
|
100746
|
-
this.clearData();
|
|
100747
|
-
return [];
|
|
100748
|
-
}
|
|
100749
|
-
}
|
|
100750
|
-
/**
|
|
100751
|
-
* Removes all learning objective data from Local Storage.
|
|
100752
|
-
*/
|
|
100753
|
-
static clearData() {
|
|
100754
|
-
try {
|
|
100755
|
-
if (typeof window === "undefined") return;
|
|
100756
|
-
localStorage.removeItem(this.STORAGE_KEY);
|
|
100757
|
-
} catch (error) {
|
|
100758
|
-
console.error("Error clearing learning objectives from Local Storage:", error);
|
|
100759
|
-
}
|
|
100760
|
-
}
|
|
100761
|
-
/**
|
|
100762
|
-
* Parses TSV content into an array of LearningObjective objects.
|
|
100763
|
-
* @param tsvContent The raw string content from a .tsv file.
|
|
100764
|
-
* @returns An object containing the successfully parsed data and any errors encountered.
|
|
100765
|
-
*/
|
|
100766
|
-
static parseTSV(tsvContent) {
|
|
100767
|
-
const lines = tsvContent.split("\n").filter((line) => line.trim() !== "");
|
|
100768
|
-
if (lines.length < 2) {
|
|
100769
|
-
return { data: [], errors: ["File is empty or contains only a header."] };
|
|
100770
|
-
}
|
|
100771
|
-
const headerLine = lines.shift();
|
|
100772
|
-
const headers = headerLine.split(" ").map((h3) => h3.trim());
|
|
100773
|
-
if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
|
|
100774
|
-
const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
|
|
100775
|
-
return { data: [], errors: [errorMsg] };
|
|
100776
|
-
}
|
|
100777
|
-
const data = [];
|
|
100778
|
-
const errors2 = [];
|
|
100779
|
-
lines.forEach((line, index3) => {
|
|
100780
|
-
const values = line.split(" ").map((v) => v.trim());
|
|
100781
|
-
if (values.length !== this.EXPECTED_HEADERS.length) {
|
|
100782
|
-
errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
|
|
100783
|
-
return;
|
|
100784
|
-
}
|
|
100785
|
-
const [
|
|
100786
|
-
loId,
|
|
100787
|
-
loDescription,
|
|
100788
|
-
subject,
|
|
100789
|
-
category,
|
|
100790
|
-
topic,
|
|
100791
|
-
keywordsStr,
|
|
100792
|
-
grade,
|
|
100793
|
-
stemElementsStr,
|
|
100794
|
-
bloomLevelsStr
|
|
100795
|
-
] = values;
|
|
100796
|
-
if (!loId || !subject || !category || !topic) {
|
|
100797
|
-
errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, Subject, Category, or Topic).`);
|
|
100798
|
-
return;
|
|
100799
|
-
}
|
|
100800
|
-
const learningObjective = {
|
|
100801
|
-
loId,
|
|
100802
|
-
loDescription,
|
|
100803
|
-
subject,
|
|
100804
|
-
category,
|
|
100805
|
-
topic,
|
|
100806
|
-
keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
|
|
100807
|
-
grade,
|
|
100808
|
-
stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
|
|
100809
|
-
bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
|
|
100810
|
-
};
|
|
100811
|
-
data.push(learningObjective);
|
|
100812
|
-
});
|
|
100813
|
-
return { data, errors: errors2 };
|
|
100814
|
-
}
|
|
100815
|
-
/**
|
|
100816
|
-
* Gets a unique list of all subjects from the stored data.
|
|
100817
|
-
* @returns An array of subject strings.
|
|
100818
|
-
*/
|
|
100819
|
-
static getSubjects() {
|
|
100820
|
-
const data = this.getData();
|
|
100821
|
-
const subjects = data.map((item) => item.subject);
|
|
100822
|
-
return [...new Set(subjects)].sort();
|
|
100823
|
-
}
|
|
100824
|
-
/**
|
|
100825
|
-
* Gets a unique list of categories for a given subject.
|
|
100826
|
-
* @param subject The subject to filter by.
|
|
100827
|
-
* @returns An array of category strings.
|
|
100828
|
-
*/
|
|
100829
|
-
static getCategoriesBySubject(subject) {
|
|
100830
|
-
const data = this.getData();
|
|
100831
|
-
const categories = data.filter((item) => item.subject === subject).map((item) => item.category);
|
|
100832
|
-
return [...new Set(categories)].sort();
|
|
100833
|
-
}
|
|
100834
|
-
/**
|
|
100835
|
-
* Gets a unique list of topics for a given category.
|
|
100836
|
-
* @param category The category to filter by.
|
|
100837
|
-
* @returns An array of topic strings.
|
|
100838
|
-
*/
|
|
100839
|
-
static getTopicsByCategory(category) {
|
|
100840
|
-
const data = this.getData();
|
|
100841
|
-
const topics = data.filter((item) => item.category === category).map((item) => item.topic);
|
|
100842
|
-
return [...new Set(topics)].sort();
|
|
100843
|
-
}
|
|
100844
|
-
/**
|
|
100845
|
-
* Retrieves all LearningObjective details for a given list of topics.
|
|
100846
|
-
* @param topics An array of topic strings to search for.
|
|
100847
|
-
* @returns An array of matching LearningObjective objects.
|
|
100848
|
-
*/
|
|
100849
|
-
static getLearningObjectivesByTopics(topics) {
|
|
100850
|
-
const data = this.getData();
|
|
100851
|
-
const topicSet = new Set(topics);
|
|
100852
|
-
return data.filter((item) => topicSet.has(item.topic));
|
|
100853
|
-
}
|
|
100854
|
-
};
|
|
100855
|
-
TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
|
|
100856
|
-
TopicDataService.EXPECTED_HEADERS = [
|
|
100857
|
-
"LO ID",
|
|
100858
|
-
"LO Description",
|
|
100859
|
-
"Subject",
|
|
100860
|
-
"Category",
|
|
100861
|
-
"Topic",
|
|
100862
|
-
"Keywords",
|
|
100863
|
-
"Grade",
|
|
100864
|
-
"STEM Element(s)",
|
|
100865
|
-
"Bloom\u2019s Level(s) Guideline"
|
|
100866
|
-
];
|
|
100867
|
-
|
|
100868
100068
|
// src/ai/flows/question-gen/generate-mrq-question.ts
|
|
100869
100069
|
init_react_shim();
|
|
100870
100070
|
|
|
@@ -100881,7 +100081,7 @@ var AIMRQOutputFieldsSchema = z.object({
|
|
|
100881
100081
|
correctTempOptionIds: z.array(z.string()).min(1),
|
|
100882
100082
|
explanation: z.string().optional(),
|
|
100883
100083
|
points: z.number().optional().default(10),
|
|
100884
|
-
difficulty: z.enum(["
|
|
100084
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
100885
100085
|
topic: z.string().optional(),
|
|
100886
100086
|
verifiedCategory: z.string().optional().describe("The category this question actually addresses.")
|
|
100887
100087
|
});
|
|
@@ -100900,7 +100100,7 @@ Previous attempts failed due to validation errors. Pay close attention to the nu
|
|
|
100900
100100
|
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.` : "";
|
|
100901
100101
|
const contextStrings = [
|
|
100902
100102
|
`**Required Category:** ${category} (This is the ONLY language to be used)`,
|
|
100903
|
-
quizContext?.
|
|
100103
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
100904
100104
|
imageContextInstruction,
|
|
100905
100105
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
100906
100106
|
quizContext?.targetMisconception && `**Target Misconception:** Use this to create plausible incorrect answers (distractors). The misconception is: "${quizContext.targetMisconception}"`,
|
|
@@ -100918,7 +100118,7 @@ Previous attempts failed due to validation errors. Pay close attention to the nu
|
|
|
100918
100118
|
correctTempOptionIds: ["A", "C", "D"],
|
|
100919
100119
|
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.",
|
|
100920
100120
|
points: 10,
|
|
100921
|
-
difficulty: "
|
|
100121
|
+
difficulty: "Medium",
|
|
100922
100122
|
topic: "Programming Paradigms",
|
|
100923
100123
|
verifiedCategory: category
|
|
100924
100124
|
}, null, 2);
|
|
@@ -101082,7 +100282,7 @@ var AIShortAnswerOutputFieldsSchema = z.object({
|
|
|
101082
100282
|
// isCaseSensitive không cần thiết ở đây, chúng ta sẽ quản lý nó ở phía client
|
|
101083
100283
|
explanation: z.string().optional(),
|
|
101084
100284
|
points: z.number().optional().default(10),
|
|
101085
|
-
difficulty: z.enum(["
|
|
100285
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101086
100286
|
topic: z.string().optional(),
|
|
101087
100287
|
verifiedCategory: z.string().optional()
|
|
101088
100288
|
// Thêm để xác thực
|
|
@@ -101102,7 +100302,7 @@ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strin
|
|
|
101102
100302
|
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.` : "";
|
|
101103
100303
|
const contextStrings = [
|
|
101104
100304
|
`**Required Category:** ${category}`,
|
|
101105
|
-
quizContext?.
|
|
100305
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101106
100306
|
imageContextInstruction,
|
|
101107
100307
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101108
100308
|
quizContext?.targetMisconception && `**Target Misconception:** The question should require an answer that corrects this specific misconception: "${quizContext.targetMisconception}"`
|
|
@@ -101112,7 +100312,7 @@ Previous attempts failed. Ensure 'acceptedAnswers' is a non-empty array of strin
|
|
|
101112
100312
|
acceptedAnswers: ["let"],
|
|
101113
100313
|
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.",
|
|
101114
100314
|
points: 10,
|
|
101115
|
-
difficulty: "
|
|
100315
|
+
difficulty: "Easy",
|
|
101116
100316
|
topic: "Swift Constants",
|
|
101117
100317
|
verifiedCategory: category
|
|
101118
100318
|
}, null, 2);
|
|
@@ -101245,7 +100445,7 @@ var AINumericOutputFieldsSchema = z.object({
|
|
|
101245
100445
|
tolerance: z.number().min(0).optional(),
|
|
101246
100446
|
explanation: z.string().optional(),
|
|
101247
100447
|
points: z.number().optional().default(10),
|
|
101248
|
-
difficulty: z.enum(["
|
|
100448
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101249
100449
|
topic: z.string().optional(),
|
|
101250
100450
|
verifiedCategory: z.string().optional()
|
|
101251
100451
|
// Thêm để xác thực
|
|
@@ -101265,7 +100465,7 @@ Previous attempts failed. Ensure the 'answer' is a valid number and fits within
|
|
|
101265
100465
|
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.` : "";
|
|
101266
100466
|
const contextStrings = [
|
|
101267
100467
|
`**Required Category:** ${category}`,
|
|
101268
|
-
quizContext?.
|
|
100468
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101269
100469
|
imageContextInstruction,
|
|
101270
100470
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101271
100471
|
quizContext?.targetMisconception && `**Target Misconception:** The question should clarify this numerical error: "${quizContext.targetMisconception}"`
|
|
@@ -101281,7 +100481,7 @@ Previous attempts failed. Ensure the 'answer' is a valid number and fits within
|
|
|
101281
100481
|
tolerance: 0,
|
|
101282
100482
|
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).",
|
|
101283
100483
|
points: 10,
|
|
101284
|
-
difficulty: "
|
|
100484
|
+
difficulty: "Medium",
|
|
101285
100485
|
topic: "Swift Data Types",
|
|
101286
100486
|
verifiedCategory: category
|
|
101287
100487
|
}, null, 2);
|
|
@@ -101431,7 +100631,7 @@ var AIFillInTheBlanksOutputFieldsSchema = z.object({
|
|
|
101431
100631
|
})).min(1).describe("An array of text and blank segments representing the question."),
|
|
101432
100632
|
explanation: z.string().optional(),
|
|
101433
100633
|
points: z.number().optional().default(10),
|
|
101434
|
-
difficulty: z.enum(["
|
|
100634
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101435
100635
|
topic: z.string().optional(),
|
|
101436
100636
|
verifiedCategory: z.string().optional()
|
|
101437
100637
|
// Thêm để xác thực
|
|
@@ -101451,7 +100651,7 @@ Previous attempts failed. Pay strict attention to the JSON schema, especially th
|
|
|
101451
100651
|
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.` : "";
|
|
101452
100652
|
const contextStrings = [
|
|
101453
100653
|
`**Required Category:** ${category}`,
|
|
101454
|
-
quizContext?.
|
|
100654
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101455
100655
|
imageContextInstruction,
|
|
101456
100656
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101457
100657
|
quizContext?.targetMisconception && `**Target Misconception:** Design the blank to test this specific point: "${quizContext.targetMisconception}"`
|
|
@@ -101465,7 +100665,7 @@ Previous attempts failed. Pay strict attention to the JSON schema, especially th
|
|
|
101465
100665
|
],
|
|
101466
100666
|
explanation: "The 'func' keyword is used to declare a function in the Swift programming language.",
|
|
101467
100667
|
points: 10,
|
|
101468
|
-
difficulty: "
|
|
100668
|
+
difficulty: "Easy",
|
|
101469
100669
|
topic: "Swift Function Declaration",
|
|
101470
100670
|
verifiedCategory: category
|
|
101471
100671
|
}, null, 2);
|
|
@@ -101619,7 +100819,7 @@ var AISequenceOutputFieldsSchema = z.object({
|
|
|
101619
100819
|
itemsInCorrectOrder: z.array(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."),
|
|
101620
100820
|
explanation: z.string().optional(),
|
|
101621
100821
|
points: z.number().optional().default(10),
|
|
101622
|
-
difficulty: z.enum(["
|
|
100822
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101623
100823
|
topic: z.string().optional(),
|
|
101624
100824
|
verifiedCategory: z.string().optional()
|
|
101625
100825
|
// Thêm để xác thực
|
|
@@ -101639,7 +100839,7 @@ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the
|
|
|
101639
100839
|
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.` : "";
|
|
101640
100840
|
const contextStrings = [
|
|
101641
100841
|
`**Required Category:** ${category}`,
|
|
101642
|
-
quizContext?.
|
|
100842
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101643
100843
|
imageContextInstruction,
|
|
101644
100844
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101645
100845
|
quizContext?.targetMisconception && `**Target Misconception:** The sequence should clarify this specific process error: "${quizContext.targetMisconception}"`
|
|
@@ -101654,7 +100854,7 @@ Previous attempts failed. Ensure the 'itemsInCorrectOrder' array has exactly the
|
|
|
101654
100854
|
],
|
|
101655
100855
|
explanation: "This is the fundamental sequence for a basic data task in URLSession.",
|
|
101656
100856
|
points: 10,
|
|
101657
|
-
difficulty: "
|
|
100857
|
+
difficulty: "Medium",
|
|
101658
100858
|
topic: "Swift Networking",
|
|
101659
100859
|
verifiedCategory: category
|
|
101660
100860
|
}, null, 2);
|
|
@@ -101796,7 +100996,7 @@ var AIMatchingOutputFieldsSchema = z.object({
|
|
|
101796
100996
|
})).min(2),
|
|
101797
100997
|
explanation: z.string().optional(),
|
|
101798
100998
|
points: z.number().optional().default(10),
|
|
101799
|
-
difficulty: z.enum(["
|
|
100999
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
101800
101000
|
topic: z.string().optional(),
|
|
101801
101001
|
verifiedCategory: z.string().optional()
|
|
101802
101002
|
// Thêm để xác thực
|
|
@@ -101816,7 +101016,7 @@ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the
|
|
|
101816
101016
|
const imageContextInstruction = imageUrl ? `**Image Context:** You MUST analyze the provided image. The matching pairs must be directly related to the content of this image.` : "";
|
|
101817
101017
|
const contextStrings = [
|
|
101818
101018
|
`**Required Category:** ${category}`,
|
|
101819
|
-
quizContext?.
|
|
101019
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
101820
101020
|
imageContextInstruction,
|
|
101821
101021
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
101822
101022
|
quizContext?.targetMisconception && `**Target Misconception:** Design a pair that specifically tests this confusion: "${quizContext.targetMisconception}"`
|
|
@@ -101830,7 +101030,7 @@ Previous attempts failed. Please ensure the 'correctPairs' array has exactly the
|
|
|
101830
101030
|
],
|
|
101831
101031
|
explanation: "These are the fundamental characteristics of Swift's main collection types.",
|
|
101832
101032
|
points: 10,
|
|
101833
|
-
difficulty: "
|
|
101033
|
+
difficulty: "Easy",
|
|
101834
101034
|
topic: "Swift Collection Types",
|
|
101835
101035
|
verifiedCategory: category
|
|
101836
101036
|
}, null, 2);
|
|
@@ -101960,6 +101160,821 @@ async function generateMatchingQuestion(clientInput, apiKey) {
|
|
|
101960
101160
|
return { error: errorMessage };
|
|
101961
101161
|
}
|
|
101962
101162
|
|
|
101163
|
+
// src/react-ui/components/authoring/AIQuestionGeneratorModal.tsx
|
|
101164
|
+
var supportedQuestionTypesForAI = [
|
|
101165
|
+
{ value: "true_false", label: "True/False" },
|
|
101166
|
+
{ value: "multiple_choice", label: "Multiple Choice" },
|
|
101167
|
+
{ value: "multiple_response", label: "Multiple Response" },
|
|
101168
|
+
{ value: "short_answer", label: "Short Answer" },
|
|
101169
|
+
{ value: "numeric", label: "Numeric" },
|
|
101170
|
+
{ value: "fill_in_the_blanks", label: "Fill In The Blanks" },
|
|
101171
|
+
{ value: "sequence", label: "Sequence" },
|
|
101172
|
+
{ value: "matching", label: "Matching" }
|
|
101173
|
+
];
|
|
101174
|
+
var AIQuestionGeneratorModal = ({
|
|
101175
|
+
isOpen,
|
|
101176
|
+
onClose,
|
|
101177
|
+
onQuestionGenerated,
|
|
101178
|
+
language: language3,
|
|
101179
|
+
questionType: questionTypeProp,
|
|
101180
|
+
subjects = [],
|
|
101181
|
+
topics = [],
|
|
101182
|
+
gradeLevels = [],
|
|
101183
|
+
bloomLevels = []
|
|
101184
|
+
}) => {
|
|
101185
|
+
const [prompt, setPrompt] = useState("");
|
|
101186
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
101187
|
+
const [error, setError] = useState(null);
|
|
101188
|
+
const { toast: toast2 } = useToast();
|
|
101189
|
+
const [subjectCode, setSubjectCode] = useState("");
|
|
101190
|
+
const [topicCode, setTopicCode] = useState("");
|
|
101191
|
+
const [gradeBand, setGradeBand] = useState("");
|
|
101192
|
+
const [bloomLevelCode, setBloomLevelCode] = useState("");
|
|
101193
|
+
const [selectedQuestionType, setSelectedQuestionType] = useState("multiple_choice");
|
|
101194
|
+
const [numberOfOptions, setNumberOfOptions] = useState(4);
|
|
101195
|
+
const [minCorrectAnswers, setMinCorrectAnswers] = useState(2);
|
|
101196
|
+
const [maxCorrectAnswers, setMaxCorrectAnswers] = useState(3);
|
|
101197
|
+
const [isCaseSensitive, setIsCaseSensitive] = useState(false);
|
|
101198
|
+
const [numberOfBlanks, setNumberOfBlanks] = useState(2);
|
|
101199
|
+
const [numberOfSequenceItems, setNumberOfSequenceItems] = useState(4);
|
|
101200
|
+
const [numberOfMatchingPairs, setNumberOfMatchingPairs] = useState(4);
|
|
101201
|
+
const [isApiKeyManagerModalOpen, setIsApiKeyManagerModalOpen] = useState(false);
|
|
101202
|
+
const [geminiApiKeyExists, setGeminiApiKeyExists] = useState(false);
|
|
101203
|
+
const finalQuestionType = questionTypeProp || selectedQuestionType;
|
|
101204
|
+
useEffect(() => {
|
|
101205
|
+
if (isOpen) {
|
|
101206
|
+
setPrompt("");
|
|
101207
|
+
setError(null);
|
|
101208
|
+
setIsLoading(false);
|
|
101209
|
+
setSubjectCode("");
|
|
101210
|
+
setTopicCode("");
|
|
101211
|
+
setGradeBand("");
|
|
101212
|
+
setBloomLevelCode("");
|
|
101213
|
+
setSelectedQuestionType(questionTypeProp || "multiple_choice");
|
|
101214
|
+
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
101215
|
+
}
|
|
101216
|
+
}, [isOpen, questionTypeProp]);
|
|
101217
|
+
const filteredTopics = useMemo(() => {
|
|
101218
|
+
if (!subjectCode) return [];
|
|
101219
|
+
return topics.filter((t4) => t4.subjectCode === subjectCode);
|
|
101220
|
+
}, [subjectCode, topics]);
|
|
101221
|
+
const handleApiKeyModalClose = () => {
|
|
101222
|
+
setIsApiKeyManagerModalOpen(false);
|
|
101223
|
+
setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
|
|
101224
|
+
};
|
|
101225
|
+
const handleSubmit = async () => {
|
|
101226
|
+
if (!prompt.trim()) {
|
|
101227
|
+
setError("Please provide a prompt for the question.");
|
|
101228
|
+
return;
|
|
101229
|
+
}
|
|
101230
|
+
const geminiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
|
|
101231
|
+
if (!geminiKey) {
|
|
101232
|
+
setError("Gemini API Key is not set. Please configure it.");
|
|
101233
|
+
setGeminiApiKeyExists(false);
|
|
101234
|
+
return;
|
|
101235
|
+
}
|
|
101236
|
+
setGeminiApiKeyExists(true);
|
|
101237
|
+
setError(null);
|
|
101238
|
+
setIsLoading(true);
|
|
101239
|
+
try {
|
|
101240
|
+
const quizContext = {
|
|
101241
|
+
plannedTopic: prompt,
|
|
101242
|
+
originalSubject: subjectCode,
|
|
101243
|
+
originalTopic: topicCode,
|
|
101244
|
+
gradeBand,
|
|
101245
|
+
plannedBloomLevel: bloomLevelCode
|
|
101246
|
+
};
|
|
101247
|
+
const baseClientInput = {
|
|
101248
|
+
language: language3,
|
|
101249
|
+
difficulty: "Medium",
|
|
101250
|
+
quizContext
|
|
101251
|
+
};
|
|
101252
|
+
let generatedResult = {};
|
|
101253
|
+
switch (finalQuestionType) {
|
|
101254
|
+
case "true_false":
|
|
101255
|
+
generatedResult = await generateTrueFalseQuestion(baseClientInput, geminiKey);
|
|
101256
|
+
break;
|
|
101257
|
+
case "multiple_choice":
|
|
101258
|
+
generatedResult = await generateMCQQuestion({ ...baseClientInput, numberOfOptions }, geminiKey);
|
|
101259
|
+
break;
|
|
101260
|
+
case "multiple_response":
|
|
101261
|
+
generatedResult = await generateMRQQuestion({ ...baseClientInput, numberOfOptions, minCorrectAnswers, maxCorrectAnswers }, geminiKey);
|
|
101262
|
+
break;
|
|
101263
|
+
case "short_answer":
|
|
101264
|
+
generatedResult = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive }, geminiKey);
|
|
101265
|
+
break;
|
|
101266
|
+
case "numeric":
|
|
101267
|
+
generatedResult = await generateNumericQuestion({ ...baseClientInput, allowDecimals: true, tolerance: 0 }, geminiKey);
|
|
101268
|
+
break;
|
|
101269
|
+
case "fill_in_the_blanks":
|
|
101270
|
+
generatedResult = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks, isCaseSensitive }, geminiKey);
|
|
101271
|
+
break;
|
|
101272
|
+
case "sequence":
|
|
101273
|
+
generatedResult = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: numberOfSequenceItems }, geminiKey);
|
|
101274
|
+
break;
|
|
101275
|
+
case "matching":
|
|
101276
|
+
generatedResult = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: numberOfMatchingPairs, shuffleOptions: true }, geminiKey);
|
|
101277
|
+
break;
|
|
101278
|
+
default:
|
|
101279
|
+
throw new Error(`AI generation for '${finalQuestionType}' is not implemented in this flow.`);
|
|
101280
|
+
}
|
|
101281
|
+
if (generatedResult.error) {
|
|
101282
|
+
throw new Error(generatedResult.error);
|
|
101283
|
+
}
|
|
101284
|
+
if (generatedResult.question) {
|
|
101285
|
+
const completeQuestion = generatedResult.question;
|
|
101286
|
+
completeQuestion.subject = subjectCode;
|
|
101287
|
+
completeQuestion.topic = topicCode;
|
|
101288
|
+
completeQuestion.gradeBand = gradeBand;
|
|
101289
|
+
completeQuestion.bloomLevel = bloomLevelCode;
|
|
101290
|
+
if (completeQuestion.points === void 0) completeQuestion.points = 10;
|
|
101291
|
+
onQuestionGenerated(completeQuestion);
|
|
101292
|
+
toast2({ title: "AI Question Generated", description: "Review and edit the generated question." });
|
|
101293
|
+
onClose();
|
|
101294
|
+
} else {
|
|
101295
|
+
throw new Error("AI did not return a valid question object.");
|
|
101296
|
+
}
|
|
101297
|
+
} catch (e3) {
|
|
101298
|
+
console.error("AI Question Generation Error:", e3);
|
|
101299
|
+
let errorMessage = e3.message || "An unknown error occurred.";
|
|
101300
|
+
if (typeof errorMessage === "string" && errorMessage.includes("API key not valid")) {
|
|
101301
|
+
errorMessage = "The provided Google Gemini API Key is invalid. Please check it.";
|
|
101302
|
+
setGeminiApiKeyExists(false);
|
|
101303
|
+
}
|
|
101304
|
+
setError(errorMessage);
|
|
101305
|
+
toast2({ title: "AI Generation Failed", description: errorMessage, variant: "destructive" });
|
|
101306
|
+
} finally {
|
|
101307
|
+
setIsLoading(false);
|
|
101308
|
+
}
|
|
101309
|
+
};
|
|
101310
|
+
const comboboxOptions = {
|
|
101311
|
+
subjects: subjects.map((s4) => ({ value: s4.code, label: s4.name })),
|
|
101312
|
+
topics: filteredTopics.map((t4) => ({ value: t4.code, label: t4.name })),
|
|
101313
|
+
gradeLevels: gradeLevels.map((g) => ({ value: g.code, label: g.name })),
|
|
101314
|
+
bloomLevels: bloomLevels.map((b2) => ({ value: b2.code, label: b2.name }))
|
|
101315
|
+
};
|
|
101316
|
+
const renderSpecificParams = () => {
|
|
101317
|
+
switch (finalQuestionType) {
|
|
101318
|
+
case "multiple_choice":
|
|
101319
|
+
case "multiple_response":
|
|
101320
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-num-options" }, "Number of Options (2-8)"), /* @__PURE__ */ React169__default.createElement(
|
|
101321
|
+
Input,
|
|
101322
|
+
{
|
|
101323
|
+
id: "ai-num-options",
|
|
101324
|
+
type: "number",
|
|
101325
|
+
value: numberOfOptions,
|
|
101326
|
+
onChange: (e3) => setNumberOfOptions(parseInt(e3.target.value, 10)),
|
|
101327
|
+
min: 2,
|
|
101328
|
+
max: 8
|
|
101329
|
+
}
|
|
101330
|
+
), finalQuestionType === "multiple_response" && /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-min-correct" }, "Min Correct Answers"), /* @__PURE__ */ React169__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__default.createElement(Label2, { htmlFor: "ai-max-correct" }, "Max Correct Answers"), /* @__PURE__ */ React169__default.createElement(Input, { id: "ai-max-correct", type: "number", value: maxCorrectAnswers, onChange: (e3) => setMaxCorrectAnswers(parseInt(e3.target.value, 10)), min: minCorrectAnswers, max: numberOfOptions })));
|
|
101331
|
+
case "short_answer":
|
|
101332
|
+
case "fill_in_the_blanks":
|
|
101333
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-2 pt-4 border-t" }, /* @__PURE__ */ React169__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__default.createElement(Label2, { htmlFor: "ai-case-sensitive" }, "Case Sensitive Answers"), finalQuestionType === "fill_in_the_blanks" && /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1 ml-4" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-num-blanks" }, "Number of Blanks (1-5)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "ai-num-blanks", type: "number", value: numberOfBlanks, onChange: (e3) => setNumberOfBlanks(parseInt(e3.target.value, 10)), min: 1, max: 5 })));
|
|
101334
|
+
case "sequence":
|
|
101335
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-num-seq-items" }, "Number of Items to Sequence (2-10)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "ai-num-seq-items", type: "number", value: numberOfSequenceItems, onChange: (e3) => setNumberOfSequenceItems(parseInt(e3.target.value, 10)), min: 2, max: 10 }));
|
|
101336
|
+
case "matching":
|
|
101337
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-num-match-pairs" }, "Number of Pairs to Match (2-8)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "ai-num-match-pairs", type: "number", value: numberOfMatchingPairs, onChange: (e3) => setNumberOfMatchingPairs(parseInt(e3.target.value, 10)), min: 2, max: 8 }));
|
|
101338
|
+
default:
|
|
101339
|
+
return null;
|
|
101340
|
+
}
|
|
101341
|
+
};
|
|
101342
|
+
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
|
|
101343
|
+
if (!open) onClose();
|
|
101344
|
+
} }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-[600px]" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center font-headline text-2xl" }, /* @__PURE__ */ React169__default.createElement(WandSparkles, { className: "mr-2 h-6 w-6 text-primary" }), " AI Question Generator"), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, "Provide a prompt and metadata to generate a '", finalQuestionType, "' question.")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 py-4 max-h-[60vh] overflow-y-auto px-2" }, !geminiApiKeyExists && /* @__PURE__ */ React169__default.createElement("div", { className: "p-3 mb-4 border border-amber-500 bg-amber-50 rounded-md text-amber-700" }), !questionTypeProp && /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-q-type-select" }, "Question Type"), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedQuestionType, onValueChange: (v) => setSelectedQuestionType(v) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "ai-q-type-select" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, supportedQuestionTypesForAI.map((type) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: type.value, value: type.value }, type.label))))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-prompt" }, "Prompt / Topic"), /* @__PURE__ */ React169__default.createElement(
|
|
101345
|
+
Textarea,
|
|
101346
|
+
{
|
|
101347
|
+
id: "ai-prompt",
|
|
101348
|
+
value: prompt,
|
|
101349
|
+
onChange: (e3) => setPrompt(e3.target.value),
|
|
101350
|
+
placeholder: "e.g., The process of photosynthesis, The causes of World War II, Basic Algebra Equations",
|
|
101351
|
+
className: "min-h-[100px]"
|
|
101352
|
+
}
|
|
101353
|
+
)), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Subject"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.subjects, value: subjectCode, onChange: setSubjectCode, placeholder: "Select or type a Subject..." })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Topic"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.topics, value: topicCode, onChange: setTopicCode, placeholder: "Select or type a Topic...", disabled: !subjectCode })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Grade Level"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.gradeLevels, value: gradeBand, onChange: setGradeBand, placeholder: "Select or type a Grade..." })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Bloom's Level"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.bloomLevels, value: bloomLevelCode, onChange: setBloomLevelCode, placeholder: "Select a Bloom's Level..." }))), renderSpecificParams(), error && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-destructive flex items-center mt-2" }, /* @__PURE__ */ React169__default.createElement(TriangleAlert, { className: "mr-1 h-4 w-4" }), " ", error)), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, "Cancel")), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", onClick: handleSubmit, disabled: isLoading || !prompt.trim() || !geminiApiKeyExists }, isLoading ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(WandSparkles, { className: "mr-2 h-4 w-4" }), "Generate Question")))), /* @__PURE__ */ React169__default.createElement(APIKeyManagerModal, { isOpen: isApiKeyManagerModalOpen, onClose: handleApiKeyModalClose }));
|
|
101354
|
+
};
|
|
101355
|
+
|
|
101356
|
+
// src/react-ui/components/authoring/AIFullQuizGeneratorModal.tsx
|
|
101357
|
+
init_react_shim();
|
|
101358
|
+
|
|
101359
|
+
// src/ai/flows/generate-quiz-plan.ts
|
|
101360
|
+
init_react_shim();
|
|
101361
|
+
|
|
101362
|
+
// src/ai/flows/generate-quiz-plan-types.ts
|
|
101363
|
+
init_react_shim();
|
|
101364
|
+
var TopicWithMetadataSchema = z.object({
|
|
101365
|
+
topic: z.string().min(1),
|
|
101366
|
+
ratio: z.number().min(0).max(100),
|
|
101367
|
+
originalLoId: z.string().optional(),
|
|
101368
|
+
originalSubject: z.string().optional(),
|
|
101369
|
+
originalCategory: z.string().optional(),
|
|
101370
|
+
originalTopic: z.string().optional(),
|
|
101371
|
+
commonMisconceptions: z.array(z.string()).optional()
|
|
101372
|
+
});
|
|
101373
|
+
var BloomLevelStringsEnum = z.enum(["remembering", "understanding", "applying", "analyzing", "evaluating", "creating"]);
|
|
101374
|
+
var fullQuizSupportedQuestionTypesArray = [
|
|
101375
|
+
"true_false",
|
|
101376
|
+
"multiple_choice",
|
|
101377
|
+
"multiple_response",
|
|
101378
|
+
"short_answer",
|
|
101379
|
+
"numeric",
|
|
101380
|
+
"fill_in_the_blanks",
|
|
101381
|
+
"sequence",
|
|
101382
|
+
"matching",
|
|
101383
|
+
"drag_and_drop",
|
|
101384
|
+
"coding"
|
|
101385
|
+
];
|
|
101386
|
+
z.object({
|
|
101387
|
+
language: z.string().optional().default("English"),
|
|
101388
|
+
totalQuestions: z.number().int().min(1).max(50),
|
|
101389
|
+
numCodingQuestions: z.number().optional().default(0),
|
|
101390
|
+
topics: z.array(TopicWithMetadataSchema).min(1),
|
|
101391
|
+
bloomLevels: z.array(z.object({
|
|
101392
|
+
level: BloomLevelStringsEnum,
|
|
101393
|
+
ratio: z.number().min(0).max(100)
|
|
101394
|
+
})).min(1),
|
|
101395
|
+
selectedContextIds: z.array(z.string()).optional(),
|
|
101396
|
+
selectedQuestionTypes: z.array(z.enum(fullQuizSupportedQuestionTypesArray)).min(1),
|
|
101397
|
+
imageContexts: z.array(z.custom()).optional().describe("Library of available image contexts for the AI to use.")
|
|
101398
|
+
});
|
|
101399
|
+
var PlannedQuestionSchema = z.object({
|
|
101400
|
+
plannedTopic: z.string().min(1).describe("The specific, assessable topic for this question."),
|
|
101401
|
+
plannedQuestionType: z.enum(fullQuizSupportedQuestionTypesArray).describe("The specific question type chosen."),
|
|
101402
|
+
plannedBloomLevel: BloomLevelStringsEnum.describe("The Bloom's level assigned."),
|
|
101403
|
+
plannedContextId: z.string().optional().describe("The specific context ID chosen for this question."),
|
|
101404
|
+
imageId: z.string().nullable().optional().describe("The ID of the image from the context library to be used for this question."),
|
|
101405
|
+
targetMisconception: z.string().optional().describe("A specific common misconception this question should target."),
|
|
101406
|
+
difficultyReason: z.string().optional().describe("Strategic explanation of difficulty choice and placement."),
|
|
101407
|
+
topicSpecificity: z.enum(["broad", "focused", "specific"]).optional().describe("How specific the topic coverage should be."),
|
|
101408
|
+
originalLoId: z.string().optional(),
|
|
101409
|
+
originalSubject: z.string().optional(),
|
|
101410
|
+
originalCategory: z.string().optional(),
|
|
101411
|
+
originalTopic: z.string().optional()
|
|
101412
|
+
});
|
|
101413
|
+
var GenerateQuizPlanOutputSchema = z.object({
|
|
101414
|
+
quizPlan: z.array(PlannedQuestionSchema).describe("A detailed plan for each question in the quiz."),
|
|
101415
|
+
diversityMetrics: z.object({
|
|
101416
|
+
questionTypeDistribution: z.record(z.number()).optional(),
|
|
101417
|
+
bloomLevelDistribution: z.record(z.number()).optional(),
|
|
101418
|
+
maxConsecutiveSameType: z.number().optional()
|
|
101419
|
+
}).optional().describe("Metrics showing the diversity achieved in the plan."),
|
|
101420
|
+
planningStrategy: z.object({
|
|
101421
|
+
overallApproach: z.string().optional(),
|
|
101422
|
+
keyDecisions: z.array(z.string()).optional()
|
|
101423
|
+
}).optional()
|
|
101424
|
+
});
|
|
101425
|
+
|
|
101426
|
+
// src/ai/flows/generate-quiz-plan.ts
|
|
101427
|
+
var QuizPlanLogger = class {
|
|
101428
|
+
constructor() {
|
|
101429
|
+
this.logs = [];
|
|
101430
|
+
this.startTime = Date.now();
|
|
101431
|
+
}
|
|
101432
|
+
log(phase, data, duration) {
|
|
101433
|
+
this.logs.push({
|
|
101434
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101435
|
+
phase,
|
|
101436
|
+
data,
|
|
101437
|
+
duration
|
|
101438
|
+
});
|
|
101439
|
+
if (duration !== void 0) {
|
|
101440
|
+
console.log(`[${phase}] Completed in ${duration}ms:`, data);
|
|
101441
|
+
} else {
|
|
101442
|
+
console.log(`[${phase}]:`, data);
|
|
101443
|
+
}
|
|
101444
|
+
}
|
|
101445
|
+
getLogs() {
|
|
101446
|
+
return this.logs;
|
|
101447
|
+
}
|
|
101448
|
+
getTotalDuration() {
|
|
101449
|
+
return Date.now() - this.startTime;
|
|
101450
|
+
}
|
|
101451
|
+
};
|
|
101452
|
+
function generateQuestionTypeSelectionGuidance() {
|
|
101453
|
+
return `
|
|
101454
|
+
QUESTION TYPE SELECTION BEST PRACTICES:
|
|
101455
|
+
|
|
101456
|
+
**TRUE_FALSE (true_false)**
|
|
101457
|
+
- Best for: Binary concepts, fact verification, common misconceptions
|
|
101458
|
+
- Bloom levels: Primarily Remembering, Understanding
|
|
101459
|
+
- Use when: Testing definitive statements, clarifying misconceptions
|
|
101460
|
+
- Example: "Photosynthesis only occurs during daytime" (targets timing misconception)
|
|
101461
|
+
|
|
101462
|
+
**MULTIPLE CHOICE (multiple_choice)**
|
|
101463
|
+
- Best for: Concept selection, process understanding, comparison
|
|
101464
|
+
- Bloom levels: All levels, especially Understanding and Applying
|
|
101465
|
+
- Use when: Testing conceptual understanding with clear alternatives
|
|
101466
|
+
- Example: "Which factor most affects enzyme activity?" (applying knowledge)
|
|
101467
|
+
|
|
101468
|
+
**MULTIPLE RESPONSE (multiple_response)**
|
|
101469
|
+
- Best for: Identifying multiple correct factors, comprehensive understanding
|
|
101470
|
+
- Bloom levels: Understanding, Analyzing, Evaluating
|
|
101471
|
+
- Use when: Multiple correct answers exist, testing thorough knowledge
|
|
101472
|
+
- Example: "Select all factors that influence plant growth" (analyzing components)
|
|
101473
|
+
|
|
101474
|
+
**FILL IN THE BLANKS (fill_in_the_blanks)**
|
|
101475
|
+
- Best for: Key terminology, formulas, specific facts
|
|
101476
|
+
- Bloom levels: Remembering, Understanding
|
|
101477
|
+
- Use when: Testing precise recall of important terms/concepts
|
|
101478
|
+
- Example: "The process of _____ converts light energy to chemical energy"
|
|
101479
|
+
|
|
101480
|
+
**NUMERIC (numeric)**
|
|
101481
|
+
- Best for: Calculations, quantitative problems, formula application
|
|
101482
|
+
- Bloom levels: Applying, Analyzing
|
|
101483
|
+
- Use when: Mathematical computations are required
|
|
101484
|
+
- Example: "Calculate the molarity of a 2L solution containing 0.5 moles of NaCl"
|
|
101485
|
+
|
|
101486
|
+
**MATCHING (matching)**
|
|
101487
|
+
- Best for: Connecting related concepts, terminology pairs
|
|
101488
|
+
- Bloom levels: Remembering, Understanding
|
|
101489
|
+
- Use when: Testing relationships between terms and definitions
|
|
101490
|
+
- Example: Match organelles with their functions
|
|
101491
|
+
|
|
101492
|
+
**SEQUENCE (sequence)**
|
|
101493
|
+
- Best for: Process steps, chronological order, procedural knowledge
|
|
101494
|
+
- Bloom levels: Understanding, Applying, Analyzing
|
|
101495
|
+
- Use when: Order or sequence is critical to understanding
|
|
101496
|
+
- Example: "Arrange the steps of mitosis in correct order"
|
|
101497
|
+
|
|
101498
|
+
**DRAG AND DROP (drag_and_drop)**
|
|
101499
|
+
- Best for: Categorization, classification, spatial relationships
|
|
101500
|
+
- Bloom levels: Understanding, Applying, Analyzing
|
|
101501
|
+
- Use when: Grouping or organizing information is key
|
|
101502
|
+
- Example: "Classify these compounds as acids, bases, or neutral"
|
|
101503
|
+
|
|
101504
|
+
**SHORT ANSWER (short_answer)**
|
|
101505
|
+
- Best for: Explanations, definitions, problem-solving steps
|
|
101506
|
+
- Bloom levels: Understanding, Applying, Analyzing, Evaluating, Creating
|
|
101507
|
+
- Use when: Requiring explanatory responses, open-ended thinking
|
|
101508
|
+
- Example: "Explain why enzymes are specific to certain substrates"
|
|
101509
|
+
|
|
101510
|
+
**CODING (coding)**
|
|
101511
|
+
- Best for: Programming problems, algorithm implementation
|
|
101512
|
+
- Bloom levels: Applying, Analyzing, Evaluating, Creating
|
|
101513
|
+
- Use when: Technical implementation or code analysis is required
|
|
101514
|
+
- Example: "Write a function to calculate factorial recursively"
|
|
101515
|
+
`;
|
|
101516
|
+
}
|
|
101517
|
+
function generateAdvancedBloomGuidance() {
|
|
101518
|
+
return `
|
|
101519
|
+
ADVANCED BLOOM'S TAXONOMY & QUESTION TYPE OPTIMIZATION:
|
|
101520
|
+
|
|
101521
|
+
**REMEMBERING (Knowledge Recall)**
|
|
101522
|
+
- Primary types: true_false, fill_in_the_blanks, matching
|
|
101523
|
+
- Secondary types: multiple_choice (simple recall)
|
|
101524
|
+
- Focus: Facts, terms, basic concepts, definitions
|
|
101525
|
+
- Misconception addressing: Use true_false to clarify common confusions
|
|
101526
|
+
|
|
101527
|
+
**UNDERSTANDING (Comprehension)**
|
|
101528
|
+
- Primary types: multiple_choice, short_answer, matching
|
|
101529
|
+
- Secondary types: multiple_response, sequence
|
|
101530
|
+
- Focus: Explanations, interpretations, examples, classifications
|
|
101531
|
+
- Best for: "Explain why...", "What does this mean...", "Give an example..."
|
|
101532
|
+
|
|
101533
|
+
**APPLYING (Using Knowledge)**
|
|
101534
|
+
- Primary types: numeric, short_answer, sequence, coding
|
|
101535
|
+
- Secondary types: multiple_choice, drag_and_drop
|
|
101536
|
+
- Focus: Problem-solving, implementing procedures, using methods
|
|
101537
|
+
- Best for: Calculations, step-by-step processes, practical applications
|
|
101538
|
+
|
|
101539
|
+
**ANALYZING (Breaking Down Information)**
|
|
101540
|
+
- Primary types: multiple_response, short_answer, sequence, coding
|
|
101541
|
+
- Secondary types: drag_and_drop, multiple_choice
|
|
101542
|
+
- Focus: Identifying components, relationships, cause-effect
|
|
101543
|
+
- Best for: "What are the factors...", "How do these relate...", "Break down..."
|
|
101544
|
+
|
|
101545
|
+
**EVALUATING (Making Judgments)**
|
|
101546
|
+
- Primary types: short_answer, multiple_response, coding
|
|
101547
|
+
- Secondary types: multiple_choice (with justification)
|
|
101548
|
+
- Focus: Critiquing, judging, comparing alternatives, decision-making
|
|
101549
|
+
- Best for: "Which is better and why...", "Evaluate the approach...", "Critique..."
|
|
101550
|
+
|
|
101551
|
+
**CREATING (Producing New Work)**
|
|
101552
|
+
- Primary types: short_answer, coding, sequence
|
|
101553
|
+
- Secondary types: drag_and_drop (design tasks)
|
|
101554
|
+
- Focus: Designing, planning, producing, constructing
|
|
101555
|
+
- Best for: "Design a solution...", "Create a plan...", "Develop a strategy..."
|
|
101556
|
+
`;
|
|
101557
|
+
}
|
|
101558
|
+
function generateDiversityRules() {
|
|
101559
|
+
return `
|
|
101560
|
+
ENHANCED DIVERSITY & QUALITY ASSURANCE RULES:
|
|
101561
|
+
|
|
101562
|
+
**Question Type Distribution Strategy:**
|
|
101563
|
+
1. Never place more than 3 consecutive questions of the same type
|
|
101564
|
+
2. Distribute question types based on their cognitive complexity
|
|
101565
|
+
3. For quizzes with 10+ questions, use at least 4 different question types
|
|
101566
|
+
4. For quizzes with 20+ questions, use at least 6 different question types
|
|
101567
|
+
5. Balance quick-answer types (true_false, multiple_choice) with deeper types (short_answer, coding)
|
|
101568
|
+
|
|
101569
|
+
**Intelligent Difficulty Progression:**
|
|
101570
|
+
1. Opening (20%): Start with confidence-building questions (Remembering/Understanding)
|
|
101571
|
+
2. Building (40%): Gradually increase complexity (Understanding/Applying)
|
|
101572
|
+
3. Peak (30%): Most challenging questions (Analyzing/Evaluating/Creating)
|
|
101573
|
+
4. Closing (10%): Moderate challenge to end positively
|
|
101574
|
+
|
|
101575
|
+
**Misconception-Driven Planning:**
|
|
101576
|
+
- When common misconceptions are provided, prioritize question types that can effectively address them
|
|
101577
|
+
- Use true_false for binary misconceptions
|
|
101578
|
+
- Use multiple_choice for concept selection misconceptions
|
|
101579
|
+
- Use short_answer for complex misconceptions requiring explanation
|
|
101580
|
+
|
|
101581
|
+
**Contextual Intelligence:**
|
|
101582
|
+
- Programming/Technical topics: Favor coding, numeric, short_answer
|
|
101583
|
+
- Process-oriented topics: Favor sequence, short_answer, drag_and_drop
|
|
101584
|
+
- Conceptual topics: Favor multiple_choice, multiple_response, true_false
|
|
101585
|
+
- Factual topics: Favor fill_in_the_blanks, matching, true_false
|
|
101586
|
+
`;
|
|
101587
|
+
}
|
|
101588
|
+
async function generateQuizPlan(clientInput, apiKey, imageContexts = []) {
|
|
101589
|
+
const logger = new QuizPlanLogger();
|
|
101590
|
+
try {
|
|
101591
|
+
logger.log("VALIDATION_START", {
|
|
101592
|
+
totalQuestions: clientInput.totalQuestions,
|
|
101593
|
+
availableTypes: clientInput.selectedQuestionTypes,
|
|
101594
|
+
topicCount: clientInput.topics.length,
|
|
101595
|
+
bloomLevelCount: clientInput.bloomLevels.length
|
|
101596
|
+
});
|
|
101597
|
+
const totalTopicRatio = clientInput.topics.reduce((sum, t4) => sum + t4.ratio, 0);
|
|
101598
|
+
if (Math.abs(totalTopicRatio - 100) > 1) {
|
|
101599
|
+
throw new Error(`Total topic ratio must be 100%. Current sum: ${totalTopicRatio.toFixed(1)}%`);
|
|
101600
|
+
}
|
|
101601
|
+
const totalBloomRatio = clientInput.bloomLevels.reduce((sum, b2) => sum + b2.ratio, 0);
|
|
101602
|
+
if (Math.abs(totalBloomRatio - 100) > 1) {
|
|
101603
|
+
throw new Error(`Total Bloom level ratio must be 100%. Current sum: ${totalBloomRatio.toFixed(1)}%`);
|
|
101604
|
+
}
|
|
101605
|
+
logger.log("VALIDATION_SUCCESS", {
|
|
101606
|
+
topicRatioSum: totalTopicRatio,
|
|
101607
|
+
bloomRatioSum: totalBloomRatio
|
|
101608
|
+
});
|
|
101609
|
+
const aiStartTime = Date.now();
|
|
101610
|
+
const ai = new GoogleGenAI({
|
|
101611
|
+
apiKey
|
|
101612
|
+
});
|
|
101613
|
+
const model = "gemini-2.5-pro";
|
|
101614
|
+
const config3 = {
|
|
101615
|
+
temperature: 0.8,
|
|
101616
|
+
thinkingConfig: {
|
|
101617
|
+
thinkingBudget: 4096
|
|
101618
|
+
},
|
|
101619
|
+
responseMimeType: "application/json"
|
|
101620
|
+
};
|
|
101621
|
+
logger.log("AI_INITIALIZATION", { model }, Date.now() - aiStartTime);
|
|
101622
|
+
const { language: language3, totalQuestions, numCodingQuestions = 0 } = clientInput;
|
|
101623
|
+
const promptStartTime = Date.now();
|
|
101624
|
+
const topicsDistribution = clientInput.topics.map((t4) => {
|
|
101625
|
+
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}%`;
|
|
101626
|
+
if (t4.commonMisconceptions && t4.commonMisconceptions.length > 0) {
|
|
101627
|
+
topicString += `
|
|
101628
|
+
- Common Misconceptions: [${t4.commonMisconceptions.join(", ")}]`;
|
|
101629
|
+
}
|
|
101630
|
+
return topicString;
|
|
101631
|
+
}).join("\n ");
|
|
101632
|
+
const bloomDistribution = clientInput.bloomLevels.map(
|
|
101633
|
+
(b2) => `- Level: "${b2.level}", Ratio: ${b2.ratio}%`
|
|
101634
|
+
).join("\n ");
|
|
101635
|
+
let questionTypesForPrompt = [...clientInput.selectedQuestionTypes];
|
|
101636
|
+
if (numCodingQuestions === 0) {
|
|
101637
|
+
questionTypesForPrompt = questionTypesForPrompt.filter(
|
|
101638
|
+
(type) => type !== "short_answer" && type !== "coding"
|
|
101639
|
+
);
|
|
101640
|
+
}
|
|
101641
|
+
const allowedQuestionTypes = questionTypesForPrompt.map((t4) => `'${t4}'`).join(", ");
|
|
101642
|
+
const codingRequirement = numCodingQuestions > 0 ? `
|
|
101643
|
+
**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.` : "";
|
|
101644
|
+
const imageContextSection = imageContexts && imageContexts.length > 0 ? `
|
|
101645
|
+
## AVAILABLE IMAGE CONTEXT LIBRARY
|
|
101646
|
+
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.
|
|
101647
|
+
|
|
101648
|
+
\`\`\`json
|
|
101649
|
+
${JSON.stringify(imageContexts.map((img) => ({ imageId: img.id, subject: img.subject, category: img.category, topic: img.topic, description: img.detailedDescription })), null, 2)}
|
|
101650
|
+
\`\`\`
|
|
101651
|
+
` : "";
|
|
101652
|
+
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.
|
|
101653
|
+
|
|
101654
|
+
${generateQuestionTypeSelectionGuidance()}
|
|
101655
|
+
|
|
101656
|
+
${generateAdvancedBloomGuidance()}
|
|
101657
|
+
|
|
101658
|
+
${generateDiversityRules()}
|
|
101659
|
+
|
|
101660
|
+
## COMPREHENSIVE QUIZ REQUIREMENTS:
|
|
101661
|
+
|
|
101662
|
+
**Target Language**: ${language3}
|
|
101663
|
+
**Total Questions**: ${totalQuestions}${codingRequirement}
|
|
101664
|
+
|
|
101665
|
+
**Topic Distribution & Learning Context** (follow precisely):
|
|
101666
|
+
${topicsDistribution}
|
|
101667
|
+
|
|
101668
|
+
**Cognitive Complexity Distribution** (follow precisely):
|
|
101669
|
+
${bloomDistribution}
|
|
101670
|
+
|
|
101671
|
+
**Available Question Arsenal**: ${allowedQuestionTypes}
|
|
101672
|
+
|
|
101673
|
+
**Image Resources**: ${imageContextSection}
|
|
101674
|
+
|
|
101675
|
+
## STRATEGIC PLANNING METHODOLOGY:
|
|
101676
|
+
|
|
101677
|
+
1. **Misconception Analysis**: If common misconceptions are provided, design questions specifically to address and correct them
|
|
101678
|
+
2. **Question Type Intelligence**: Select question types based on the cognitive demands and content nature
|
|
101679
|
+
3. **Difficulty Orchestration**: Create a learning journey that builds confidence while challenging appropriately
|
|
101680
|
+
4. **Diversity Optimization**: Ensure variety prevents monotony and maintains engagement
|
|
101681
|
+
5. **Context Sensitivity**: Match question types to topic characteristics (technical, conceptual, procedural)
|
|
101682
|
+
6. **Image Context Integration**: Intelligently associate questions with relevant images from the provided library to enhance contextual understanding.
|
|
101683
|
+
|
|
101684
|
+
## ENHANCED OUTPUT FORMAT:
|
|
101685
|
+
|
|
101686
|
+
Return ONLY a JSON object with this EXACT structure:
|
|
101687
|
+
|
|
101688
|
+
\`\`\`json
|
|
101689
|
+
{
|
|
101690
|
+
"quizPlan": [
|
|
101691
|
+
{
|
|
101692
|
+
"plannedTopic": "Specific, assessable topic derived from provided context",
|
|
101693
|
+
"plannedQuestionType": "question_type_from_allowed_list",
|
|
101694
|
+
"plannedBloomLevel": "bloom_level_from_requirements",
|
|
101695
|
+
"plannedContextId": "THEO_ABS",
|
|
101696
|
+
"imageId": "imgctx_12345abcde", // or null
|
|
101697
|
+
"targetMisconception": "Specific misconception this question addresses (or 'none' if not applicable)",
|
|
101698
|
+
"difficultyReason": "Strategic explanation of difficulty choice and placement",
|
|
101699
|
+
"topicSpecificity": "broad|focused|specific",
|
|
101700
|
+
"originalLoId": "corresponding_LoId_from_input",
|
|
101701
|
+
"originalSubject": "corresponding_Subject_from_input",
|
|
101702
|
+
"originalCategory": "corresponding_Category_from_input",
|
|
101703
|
+
"originalTopic": "corresponding_Topic_from_input"
|
|
101704
|
+
}
|
|
101705
|
+
],
|
|
101706
|
+
"diversityMetrics": {
|
|
101707
|
+
"questionTypeDistribution": {"type1": count1, "type2": count2},
|
|
101708
|
+
"bloomLevelDistribution": {"level1": count1, "level2": count2},
|
|
101709
|
+
"maxConsecutiveSameType": number,
|
|
101710
|
+
"difficultyProgression": "description of how difficulty progresses",
|
|
101711
|
+
"misconceptionCoverage": number_of_misconceptions_addressed
|
|
101712
|
+
},
|
|
101713
|
+
"planningStrategy": {
|
|
101714
|
+
"overallApproach": "Brief description of the strategic approach taken",
|
|
101715
|
+
"keyDecisions": ["Major decision 1", "Major decision 2", "Major decision 3"]
|
|
101716
|
+
}
|
|
101717
|
+
}
|
|
101718
|
+
\`\`\`
|
|
101719
|
+
|
|
101720
|
+
Execute this plan with pedagogical precision. The quiz should feel like a carefully crafted learning journey that challenges and educates simultaneously.`;
|
|
101721
|
+
logger.log("PROMPT_PREPARATION", {
|
|
101722
|
+
promptLength: enhancedPromptText.length,
|
|
101723
|
+
topicCount: clientInput.topics.length,
|
|
101724
|
+
misconceptionCount: clientInput.topics.reduce((sum, t4) => sum + (t4.commonMisconceptions?.length || 0), 0)
|
|
101725
|
+
}, Date.now() - promptStartTime);
|
|
101726
|
+
const generationStartTime = Date.now();
|
|
101727
|
+
const contents = [
|
|
101728
|
+
{
|
|
101729
|
+
role: "user",
|
|
101730
|
+
parts: [
|
|
101731
|
+
{
|
|
101732
|
+
text: enhancedPromptText
|
|
101733
|
+
}
|
|
101734
|
+
]
|
|
101735
|
+
}
|
|
101736
|
+
];
|
|
101737
|
+
const aiResult = await ai.models.generateContent({
|
|
101738
|
+
model,
|
|
101739
|
+
config: config3,
|
|
101740
|
+
contents
|
|
101741
|
+
});
|
|
101742
|
+
const response = aiResult;
|
|
101743
|
+
const generationDuration = Date.now() - generationStartTime;
|
|
101744
|
+
logger.log("AI_GENERATION", {
|
|
101745
|
+
responseLength: response.candidates?.[0]?.content?.parts?.[0]?.text?.length || 0,
|
|
101746
|
+
duration: generationDuration
|
|
101747
|
+
}, generationDuration);
|
|
101748
|
+
const processingStartTime = Date.now();
|
|
101749
|
+
const rawText = response.candidates?.[0]?.content?.parts?.[0]?.text || "";
|
|
101750
|
+
let jsonText = rawText;
|
|
101751
|
+
if (!rawText.trim().startsWith("{") && !rawText.trim().startsWith("[")) {
|
|
101752
|
+
jsonText = extractJsonFromMarkdown(rawText);
|
|
101753
|
+
}
|
|
101754
|
+
logger.log("JSON_EXTRACTION", {
|
|
101755
|
+
rawTextLength: rawText.length,
|
|
101756
|
+
extractedJsonLength: jsonText.length
|
|
101757
|
+
});
|
|
101758
|
+
const aiGeneratedContent = GenerateQuizPlanOutputSchema.parse(JSON.parse(jsonText));
|
|
101759
|
+
logger.log("SCHEMA_VALIDATION", { success: true }, Date.now() - processingStartTime);
|
|
101760
|
+
const validationStartTime = Date.now();
|
|
101761
|
+
if (aiGeneratedContent.quizPlan.length !== clientInput.totalQuestions) {
|
|
101762
|
+
throw new Error(`AI planned for ${aiGeneratedContent.quizPlan.length} questions, but ${clientInput.totalQuestions} were requested.`);
|
|
101763
|
+
}
|
|
101764
|
+
const invalidTypes = [];
|
|
101765
|
+
aiGeneratedContent.quizPlan.forEach((item, index3) => {
|
|
101766
|
+
if (!clientInput.selectedQuestionTypes.includes(item.plannedQuestionType)) {
|
|
101767
|
+
invalidTypes.push(`Question ${index3 + 1}: '${item.plannedQuestionType}'`);
|
|
101768
|
+
}
|
|
101769
|
+
});
|
|
101770
|
+
if (invalidTypes.length > 0) {
|
|
101771
|
+
throw new Error(`Invalid question types found: ${invalidTypes.join(", ")}`);
|
|
101772
|
+
}
|
|
101773
|
+
const codingQuestions = aiGeneratedContent.quizPlan.filter((q2) => q2.plannedQuestionType === "coding");
|
|
101774
|
+
if (numCodingQuestions > 0 && codingQuestions.length !== numCodingQuestions) {
|
|
101775
|
+
throw new Error(`Expected ${numCodingQuestions} coding questions, but got ${codingQuestions.length}`);
|
|
101776
|
+
}
|
|
101777
|
+
const diversityAnalysis = validateConsecutiveTypes(aiGeneratedContent.quizPlan);
|
|
101778
|
+
logger.log("VALIDATION_COMPLETE", {
|
|
101779
|
+
questionCount: aiGeneratedContent.quizPlan.length,
|
|
101780
|
+
codingQuestionCount: codingQuestions.length,
|
|
101781
|
+
maxConsecutiveType: diversityAnalysis.maxConsecutive,
|
|
101782
|
+
questionTypeDistribution: aiGeneratedContent.diversityMetrics?.questionTypeDistribution || {}
|
|
101783
|
+
}, Date.now() - validationStartTime);
|
|
101784
|
+
const finalResult = {
|
|
101785
|
+
...aiGeneratedContent,
|
|
101786
|
+
logs: logger.getLogs()
|
|
101787
|
+
};
|
|
101788
|
+
logger.log("GENERATION_COMPLETE", {
|
|
101789
|
+
totalDuration: logger.getTotalDuration(),
|
|
101790
|
+
success: true,
|
|
101791
|
+
finalQuestionCount: finalResult.quizPlan.length
|
|
101792
|
+
}, logger.getTotalDuration());
|
|
101793
|
+
console.log("\n=== QUIZ PLAN GENERATION SUMMARY ===");
|
|
101794
|
+
console.log(`\u2705 Successfully generated ${finalResult.quizPlan.length} questions`);
|
|
101795
|
+
console.log(`\u23F1\uFE0F Total generation time: ${logger.getTotalDuration()}ms`);
|
|
101796
|
+
console.log(`\u{1F3AF} Question types: ${Object.keys(finalResult.diversityMetrics?.questionTypeDistribution || {}).join(", ")}`);
|
|
101797
|
+
console.log(`\u{1F9E0} Bloom levels: ${Object.keys(finalResult.diversityMetrics?.bloomLevelDistribution || {}).join(", ")}`);
|
|
101798
|
+
if (numCodingQuestions > 0) {
|
|
101799
|
+
console.log(`\u{1F4BB} Coding questions: ${codingQuestions.length}/${numCodingQuestions} required`);
|
|
101800
|
+
}
|
|
101801
|
+
console.log(JSON.stringify(finalResult));
|
|
101802
|
+
console.log("=====================================\n");
|
|
101803
|
+
return finalResult;
|
|
101804
|
+
} catch (error) {
|
|
101805
|
+
logger.log("ERROR", {
|
|
101806
|
+
message: error.message,
|
|
101807
|
+
stack: error.stack,
|
|
101808
|
+
totalDuration: logger.getTotalDuration()
|
|
101809
|
+
});
|
|
101810
|
+
console.error("\u274C Quiz Plan Generation Failed:", error.message);
|
|
101811
|
+
console.error("\u{1F4CB} Full logs available in returned object");
|
|
101812
|
+
throw new Error(`Failed to generate Quiz Plan: ${error.message}`);
|
|
101813
|
+
}
|
|
101814
|
+
}
|
|
101815
|
+
function validateConsecutiveTypes(quizPlan) {
|
|
101816
|
+
let maxConsecutive = 1;
|
|
101817
|
+
let maxType = quizPlan[0]?.plannedQuestionType || "";
|
|
101818
|
+
let maxStartIndex = 0;
|
|
101819
|
+
let currentConsecutive = 1;
|
|
101820
|
+
let currentType = quizPlan[0]?.plannedQuestionType || "";
|
|
101821
|
+
let currentStartIndex = 0;
|
|
101822
|
+
for (let i2 = 1; i2 < quizPlan.length; i2++) {
|
|
101823
|
+
if (quizPlan[i2].plannedQuestionType === currentType) {
|
|
101824
|
+
currentConsecutive++;
|
|
101825
|
+
} else {
|
|
101826
|
+
if (currentConsecutive > maxConsecutive) {
|
|
101827
|
+
maxConsecutive = currentConsecutive;
|
|
101828
|
+
maxType = currentType;
|
|
101829
|
+
maxStartIndex = currentStartIndex;
|
|
101830
|
+
}
|
|
101831
|
+
currentConsecutive = 1;
|
|
101832
|
+
currentType = quizPlan[i2].plannedQuestionType;
|
|
101833
|
+
currentStartIndex = i2;
|
|
101834
|
+
}
|
|
101835
|
+
}
|
|
101836
|
+
if (currentConsecutive > maxConsecutive) {
|
|
101837
|
+
maxConsecutive = currentConsecutive;
|
|
101838
|
+
maxType = currentType;
|
|
101839
|
+
maxStartIndex = currentStartIndex;
|
|
101840
|
+
}
|
|
101841
|
+
return {
|
|
101842
|
+
maxConsecutive,
|
|
101843
|
+
type: maxType,
|
|
101844
|
+
startIndex: maxStartIndex
|
|
101845
|
+
};
|
|
101846
|
+
}
|
|
101847
|
+
|
|
101848
|
+
// src/ai/flows/generate-questions-from-quiz-plan.ts
|
|
101849
|
+
init_react_shim();
|
|
101850
|
+
|
|
101851
|
+
// src/services/TopicDataService.ts
|
|
101852
|
+
init_react_shim();
|
|
101853
|
+
var TopicDataService = class {
|
|
101854
|
+
static saveData(data) {
|
|
101855
|
+
try {
|
|
101856
|
+
if (typeof window === "undefined") return;
|
|
101857
|
+
const serializedData = JSON.stringify(data);
|
|
101858
|
+
localStorage.setItem(this.STORAGE_KEY, serializedData);
|
|
101859
|
+
} catch (error) {
|
|
101860
|
+
console.error("Error saving learning objectives to Local Storage:", error);
|
|
101861
|
+
}
|
|
101862
|
+
}
|
|
101863
|
+
static mergeData(newData) {
|
|
101864
|
+
const existingData = this.getData();
|
|
101865
|
+
const loMap = new Map(existingData.map((lo) => [lo.code, lo]));
|
|
101866
|
+
newData.forEach((newLo) => {
|
|
101867
|
+
loMap.set(newLo.code, newLo);
|
|
101868
|
+
});
|
|
101869
|
+
const mergedData = Array.from(loMap.values());
|
|
101870
|
+
this.saveData(mergedData);
|
|
101871
|
+
}
|
|
101872
|
+
static getData() {
|
|
101873
|
+
try {
|
|
101874
|
+
if (typeof window === "undefined") return [];
|
|
101875
|
+
const storedData = localStorage.getItem(this.STORAGE_KEY);
|
|
101876
|
+
return storedData ? JSON.parse(storedData) : [];
|
|
101877
|
+
} catch (error) {
|
|
101878
|
+
console.error("Error retrieving learning objectives from Local Storage:", error);
|
|
101879
|
+
this.clearData();
|
|
101880
|
+
return [];
|
|
101881
|
+
}
|
|
101882
|
+
}
|
|
101883
|
+
static clearData() {
|
|
101884
|
+
try {
|
|
101885
|
+
if (typeof window === "undefined") return;
|
|
101886
|
+
localStorage.removeItem(this.STORAGE_KEY);
|
|
101887
|
+
} catch (error) {
|
|
101888
|
+
console.error("Error clearing learning objectives from Local Storage:", error);
|
|
101889
|
+
}
|
|
101890
|
+
}
|
|
101891
|
+
static parseTSV(tsvContent) {
|
|
101892
|
+
const lines = tsvContent.split("\n").filter((line) => line.trim() !== "");
|
|
101893
|
+
if (lines.length < 2) {
|
|
101894
|
+
return { data: [], errors: ["File is empty or contains only a header."] };
|
|
101895
|
+
}
|
|
101896
|
+
const headerLine = lines.shift();
|
|
101897
|
+
const headers = headerLine.split(" ").map((h3) => h3.trim());
|
|
101898
|
+
if (headers.length !== this.EXPECTED_HEADERS.length || !this.EXPECTED_HEADERS.every((h3, i2) => h3 === headers[i2])) {
|
|
101899
|
+
const errorMsg = `Invalid TSV header. Expected: "${this.EXPECTED_HEADERS.join(" ")}". Received: "${headers.join(" ")}"`;
|
|
101900
|
+
return { data: [], errors: [errorMsg] };
|
|
101901
|
+
}
|
|
101902
|
+
const data = [];
|
|
101903
|
+
const errors2 = [];
|
|
101904
|
+
lines.forEach((line, index3) => {
|
|
101905
|
+
const values = line.split(" ").map((v) => v.trim());
|
|
101906
|
+
if (values.length !== this.EXPECTED_HEADERS.length) {
|
|
101907
|
+
errors2.push(`Line ${index3 + 2}: Incorrect number of columns. Expected ${this.EXPECTED_HEADERS.length}, but got ${values.length}.`);
|
|
101908
|
+
return;
|
|
101909
|
+
}
|
|
101910
|
+
const [
|
|
101911
|
+
loId,
|
|
101912
|
+
name3,
|
|
101913
|
+
loDescription,
|
|
101914
|
+
subject,
|
|
101915
|
+
category,
|
|
101916
|
+
topic,
|
|
101917
|
+
keywordsStr,
|
|
101918
|
+
grade,
|
|
101919
|
+
stemElementsStr,
|
|
101920
|
+
bloomLevelsStr
|
|
101921
|
+
] = values;
|
|
101922
|
+
if (!loId || !loDescription || !subject || !category || !topic) {
|
|
101923
|
+
errors2.push(`Line ${index3 + 2}: Missing required fields (LO ID, LO Description, Subject, Category, or Topic).`);
|
|
101924
|
+
return;
|
|
101925
|
+
}
|
|
101926
|
+
const learningObjective = {
|
|
101927
|
+
id: generateUniqueId("lo_"),
|
|
101928
|
+
code: loId,
|
|
101929
|
+
name: name3,
|
|
101930
|
+
description: loDescription,
|
|
101931
|
+
// Can be the same as name or enhanced later
|
|
101932
|
+
subject,
|
|
101933
|
+
category,
|
|
101934
|
+
topic,
|
|
101935
|
+
grade,
|
|
101936
|
+
keywords: keywordsStr.split(",").map((k3) => k3.trim()).filter(Boolean),
|
|
101937
|
+
stemElements: stemElementsStr.split(",").map((s4) => s4.trim()).filter(Boolean),
|
|
101938
|
+
bloomLevelsGuideline: bloomLevelsStr.split(",").map((b2) => b2.trim()).filter(Boolean)
|
|
101939
|
+
};
|
|
101940
|
+
data.push(learningObjective);
|
|
101941
|
+
});
|
|
101942
|
+
return { data, errors: errors2 };
|
|
101943
|
+
}
|
|
101944
|
+
static getSubjects() {
|
|
101945
|
+
const data = this.getData();
|
|
101946
|
+
const subjects = data.map((item) => item.subject);
|
|
101947
|
+
return [...new Set(subjects)].sort();
|
|
101948
|
+
}
|
|
101949
|
+
static getCategoriesBySubject(subject) {
|
|
101950
|
+
const data = this.getData();
|
|
101951
|
+
const categories = data.filter((item) => item.subject === subject).map((item) => item.category);
|
|
101952
|
+
return [...new Set(categories)].sort();
|
|
101953
|
+
}
|
|
101954
|
+
static getTopicsByCategory(category) {
|
|
101955
|
+
const data = this.getData();
|
|
101956
|
+
const topics = data.filter((item) => item.category === category).map((item) => item.topic);
|
|
101957
|
+
return [...new Set(topics)].sort();
|
|
101958
|
+
}
|
|
101959
|
+
static getLearningObjectivesByTopics(topics) {
|
|
101960
|
+
const data = this.getData();
|
|
101961
|
+
const topicSet = new Set(topics);
|
|
101962
|
+
return data.filter((item) => topicSet.has(item.topic));
|
|
101963
|
+
}
|
|
101964
|
+
};
|
|
101965
|
+
TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
|
|
101966
|
+
TopicDataService.EXPECTED_HEADERS = [
|
|
101967
|
+
"LO ID",
|
|
101968
|
+
"LO Description",
|
|
101969
|
+
"Subject",
|
|
101970
|
+
"Category",
|
|
101971
|
+
"Topic",
|
|
101972
|
+
"Keywords",
|
|
101973
|
+
"Grade",
|
|
101974
|
+
"STEM Element(s)",
|
|
101975
|
+
"Bloom\u2019s Level(s) Guideline"
|
|
101976
|
+
];
|
|
101977
|
+
|
|
101963
101978
|
// src/ai/flows/question-gen/generate-coding-question.ts
|
|
101964
101979
|
init_react_shim();
|
|
101965
101980
|
|
|
@@ -101997,7 +102012,7 @@ Previous attempts failed. Pay strict attention to the JSON schema and all rules.
|
|
|
101997
102012
|
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.` : "";
|
|
101998
102013
|
const contextStrings = [
|
|
101999
102014
|
`**Subject:** ${subject}`,
|
|
102000
|
-
quizContext?.
|
|
102015
|
+
quizContext?.description && `**Learning Objective:** ${quizContext.description}`,
|
|
102001
102016
|
imageContextInstruction,
|
|
102002
102017
|
quizContext?.plannedBloomLevel && `**Cognitive Level (Bloom's):** ${quizContext.plannedBloomLevel}`,
|
|
102003
102018
|
quizContext?.targetMisconception && `**Target Misconception:** The problem should test against this common error: "${quizContext.targetMisconception}"`
|
|
@@ -102154,9 +102169,9 @@ var calculateCombinedDifficulty = (plannedQ) => {
|
|
|
102154
102169
|
break;
|
|
102155
102170
|
}
|
|
102156
102171
|
const totalScore = bloomScore + contextScore + questionTypeScore;
|
|
102157
|
-
if (totalScore <= 4) return "
|
|
102158
|
-
if (totalScore <= 7) return "
|
|
102159
|
-
return "
|
|
102172
|
+
if (totalScore <= 4) return "Easy";
|
|
102173
|
+
if (totalScore <= 7) return "Medium";
|
|
102174
|
+
return "Hard";
|
|
102160
102175
|
};
|
|
102161
102176
|
async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
102162
102177
|
const { quizPlan, language: language3, imageContexts } = clientInput;
|
|
@@ -102169,7 +102184,7 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
102169
102184
|
let lastError = null;
|
|
102170
102185
|
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
102171
102186
|
try {
|
|
102172
|
-
const fullLO = plannedQ.originalLoId ? allLearningObjectives.find((lo) => lo.
|
|
102187
|
+
const fullLO = plannedQ.originalLoId ? allLearningObjectives.find((lo) => lo.code === plannedQ.originalLoId) : null;
|
|
102173
102188
|
const quizContext = {
|
|
102174
102189
|
plannedTopic: plannedQ.plannedTopic,
|
|
102175
102190
|
plannedQuestionType: plannedQ.plannedQuestionType,
|
|
@@ -102182,7 +102197,7 @@ async function generateQuestionsFromQuizPlan(clientInput, apiKey) {
|
|
|
102182
102197
|
originalSubject: plannedQ.originalSubject,
|
|
102183
102198
|
originalCategory: plannedQ.originalCategory,
|
|
102184
102199
|
originalTopic: plannedQ.originalTopic,
|
|
102185
|
-
|
|
102200
|
+
description: fullLO?.description || plannedQ.plannedTopic
|
|
102186
102201
|
};
|
|
102187
102202
|
const imageUrl = plannedQ.imageId && imageContexts ? imageContexts.find((ctx) => ctx.id === plannedQ.imageId)?.imageUrl : void 0;
|
|
102188
102203
|
const baseClientInput = {
|
|
@@ -102536,7 +102551,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102536
102551
|
};
|
|
102537
102552
|
const renderContent3 = () => {
|
|
102538
102553
|
if (currentStage === "review") {
|
|
102539
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 py-4" }, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-lg font-
|
|
102554
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 py-4" }, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-lg font-Medium mb-2 text-primary flex items-center" }, /* @__PURE__ */ React169__default.createElement(Eye, { className: "mr-2 h-5 w-5" }), " Review & Adjust AI Generated Quiz Plan"), /* @__PURE__ */ React169__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__default.createElement(ScrollArea2, { className: "max-h-[calc(60vh - 120px)] pr-3" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, aiGeneratedPlan.map((plannedQ, index3) => /* @__PURE__ */ React169__default.createElement(Card, { key: `planned-${index3}-${plannedQ.plannedTopic.replace(/\s/g, "")}`, className: "p-3" }, /* @__PURE__ */ React169__default.createElement(CardContent, { className: "p-0 space-y-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React169__default.createElement("p", { className: "font-semibold text-sm" }, "Q", index3 + 1, ": ", plannedQ.plannedTopic), /* @__PURE__ */ React169__default.createElement("div", { className: "flex space-x-1" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleMovePlannedQuestion(index3, "up"), disabled: index3 === 0 }, /* @__PURE__ */ React169__default.createElement(ArrowUp, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleMovePlannedQuestion(index3, "down"), disabled: index3 === aiGeneratedPlan.length - 1 }, /* @__PURE__ */ React169__default.createElement(ArrowDown, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7 text-destructive", onClick: () => handleRemovePlannedQuestion(index3) }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-3 items-end" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `review-qtype-${index3}`, className: "text-xs" }, "Question Type"), /* @__PURE__ */ React169__default.createElement(
|
|
102540
102555
|
Select2,
|
|
102541
102556
|
{
|
|
102542
102557
|
value: plannedQ.plannedQuestionType,
|
|
@@ -102565,7 +102580,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102565
102580
|
min: "1",
|
|
102566
102581
|
max: "100"
|
|
102567
102582
|
}
|
|
102568
|
-
)), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-
|
|
102583
|
+
)), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Topic Distribution"), topics.map((topicItem, index3) => /* @__PURE__ */ React169__default.createElement("div", { key: topicItem.id, className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ React169__default.createElement(
|
|
102569
102584
|
Input,
|
|
102570
102585
|
{
|
|
102571
102586
|
type: "text",
|
|
@@ -102585,7 +102600,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102585
102600
|
min: "0",
|
|
102586
102601
|
max: "100"
|
|
102587
102602
|
}
|
|
102588
|
-
), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeTopic(topicItem.id), disabled: topics.length <= 1 }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: addTopic }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-
|
|
102603
|
+
), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeTopic(topicItem.id), disabled: topics.length <= 1 }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", size: "sm", onClick: addTopic }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Bloom Level Distribution"), bloomLevels.map((bloomItem) => /* @__PURE__ */ React169__default.createElement("div", { key: bloomItem.id, className: "flex items-center gap-2 mb-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "w-32 capitalize flex-shrink-0" }, bloomItem.level), /* @__PURE__ */ React169__default.createElement(
|
|
102589
102604
|
Input,
|
|
102590
102605
|
{
|
|
102591
102606
|
type: "number",
|
|
@@ -102596,7 +102611,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102596
102611
|
min: "0",
|
|
102597
102612
|
max: "100"
|
|
102598
102613
|
}
|
|
102599
|
-
)))), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-
|
|
102614
|
+
)))), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Relevant Contexts (Optional, Select Multiple)"), /* @__PURE__ */ React169__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__default.createElement("div", { key: opt.contextId, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__default.createElement(
|
|
102600
102615
|
Checkbox2,
|
|
102601
102616
|
{
|
|
102602
102617
|
id: `ctx-full-${opt.contextId}`,
|
|
@@ -102618,7 +102633,7 @@ var AIFullQuizGeneratorModal = ({
|
|
|
102618
102633
|
placeholder: "Enter your specific custom context here...",
|
|
102619
102634
|
className: "min-h-[60px] mt-2 text-sm"
|
|
102620
102635
|
}
|
|
102621
|
-
)), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-
|
|
102636
|
+
)), /* @__PURE__ */ React169__default.createElement("fieldset", { className: "border p-3 rounded-md" }, /* @__PURE__ */ React169__default.createElement("legend", { className: "text-sm font-Medium px-1" }, "Question Types to Use (Select Multiple)"), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-2 mt-2 max-h-40 overflow-y-auto" }, availableQuestionTypesForFullQuiz.map((qType) => /* @__PURE__ */ React169__default.createElement("div", { key: qType.value, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__default.createElement(
|
|
102622
102637
|
Checkbox2,
|
|
102623
102638
|
{
|
|
102624
102639
|
id: `qtype-full-${qType.value}`,
|
|
@@ -103018,7 +103033,7 @@ var AlertTitle = React169.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
103018
103033
|
"h5",
|
|
103019
103034
|
{
|
|
103020
103035
|
ref,
|
|
103021
|
-
className: cn("mb-1 font-
|
|
103036
|
+
className: cn("mb-1 font-Medium leading-none tracking-tight", className),
|
|
103022
103037
|
...props
|
|
103023
103038
|
}
|
|
103024
103039
|
));
|
|
@@ -103063,7 +103078,7 @@ var BaseRawQuestionSchema = z.object({
|
|
|
103063
103078
|
points: z.number().optional(),
|
|
103064
103079
|
explanation: z.string().optional(),
|
|
103065
103080
|
topic: z.string().optional(),
|
|
103066
|
-
difficulty: z.enum(["
|
|
103081
|
+
difficulty: z.enum(["Easy", "Medium", "Hard"]).optional(),
|
|
103067
103082
|
bloomLevel: z.string().optional()
|
|
103068
103083
|
});
|
|
103069
103084
|
var RawMCQSchema = BaseRawQuestionSchema.extend({
|
|
@@ -103416,7 +103431,7 @@ var QuizEditorService = class {
|
|
|
103416
103431
|
questionType: type,
|
|
103417
103432
|
prompt: "",
|
|
103418
103433
|
points: 10,
|
|
103419
|
-
difficulty: "
|
|
103434
|
+
difficulty: "Medium"
|
|
103420
103435
|
};
|
|
103421
103436
|
switch (type) {
|
|
103422
103437
|
case "true_false":
|
|
@@ -103643,7 +103658,7 @@ var SelectedQuestionsPanel = ({
|
|
|
103643
103658
|
},
|
|
103644
103659
|
/* @__PURE__ */ React169__default.createElement(ArrowDown, { className: "h-3 w-3" })
|
|
103645
103660
|
)),
|
|
103646
|
-
/* @__PURE__ */ React169__default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React169__default.createElement("div", { className: "font-
|
|
103661
|
+
/* @__PURE__ */ React169__default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React169__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__default.createElement("div", { className: "text-xs text-muted-foreground mt-1" }, q2.questionType, " \u2022 ", q2.points || 0, " pts")),
|
|
103647
103662
|
/* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-1 flex-shrink-0" }, /* @__PURE__ */ React169__default.createElement(
|
|
103648
103663
|
Button,
|
|
103649
103664
|
{
|
|
@@ -104030,7 +104045,7 @@ var TableFooter = React169.forwardRef(({ className, ...props }, ref) => /* @__PU
|
|
|
104030
104045
|
{
|
|
104031
104046
|
ref,
|
|
104032
104047
|
className: cn(
|
|
104033
|
-
"border-t bg-muted/50 font-
|
|
104048
|
+
"border-t bg-muted/50 font-Medium [&>tr]:last:border-b-0",
|
|
104034
104049
|
className
|
|
104035
104050
|
),
|
|
104036
104051
|
...props
|
|
@@ -104054,7 +104069,7 @@ var TableHead = React169.forwardRef(({ className, ...props }, ref) => /* @__PURE
|
|
|
104054
104069
|
{
|
|
104055
104070
|
ref,
|
|
104056
104071
|
className: cn(
|
|
104057
|
-
"h-12 px-4 text-left align-middle font-
|
|
104072
|
+
"h-12 px-4 text-left align-middle font-Medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
104058
104073
|
className
|
|
104059
104074
|
),
|
|
104060
104075
|
...props
|
|
@@ -104144,20 +104159,6 @@ var questionTypeManager = new LocalStorageManager("question_types");
|
|
|
104144
104159
|
var learningObjectiveManager = new LocalStorageManager("learning_objectives");
|
|
104145
104160
|
var contextManager = new LocalStorageManager("contexts");
|
|
104146
104161
|
var approachManager = new LocalStorageManager("approaches");
|
|
104147
|
-
function mapRawDifficultyToStandard(rawDifficulty) {
|
|
104148
|
-
switch (rawDifficulty) {
|
|
104149
|
-
case "E":
|
|
104150
|
-
case "E~M":
|
|
104151
|
-
return "easy";
|
|
104152
|
-
case "M":
|
|
104153
|
-
case "M~H":
|
|
104154
|
-
return "medium";
|
|
104155
|
-
case "H":
|
|
104156
|
-
return "hard";
|
|
104157
|
-
default:
|
|
104158
|
-
return "medium";
|
|
104159
|
-
}
|
|
104160
|
-
}
|
|
104161
104162
|
var MetadataService = class {
|
|
104162
104163
|
};
|
|
104163
104164
|
// --- Subject Services ---
|
|
@@ -104215,15 +104216,10 @@ MetadataService.deleteContext = (code4) => contextManager.delete(code4);
|
|
|
104215
104216
|
MetadataService.getApproaches = () => approachManager.getAll().sort((a4, b2) => a4.code.localeCompare(b2.code));
|
|
104216
104217
|
MetadataService.saveApproaches = (items) => approachManager.saveAll(items);
|
|
104217
104218
|
MetadataService.addApproach = (approachData) => {
|
|
104218
|
-
|
|
104219
|
-
return approachManager.add({ ...approachData, difficulty });
|
|
104219
|
+
return approachManager.add(approachData);
|
|
104220
104220
|
};
|
|
104221
104221
|
MetadataService.updateApproach = (id3, approachData) => {
|
|
104222
|
-
|
|
104223
|
-
if (approachData.rawDifficulty) {
|
|
104224
|
-
updates.difficulty = mapRawDifficultyToStandard(approachData.rawDifficulty);
|
|
104225
|
-
}
|
|
104226
|
-
return approachManager.update(id3, updates);
|
|
104222
|
+
return approachManager.update(id3, approachData);
|
|
104227
104223
|
};
|
|
104228
104224
|
MetadataService.deleteApproach = (code4) => approachManager.delete(code4);
|
|
104229
104225
|
// --- LearningObjective Services ---
|
|
@@ -104233,8 +104229,12 @@ MetadataService.getLearningObjectives = (subjectCode) => {
|
|
|
104233
104229
|
return filtered.sort((a4, b2) => a4.name.localeCompare(b2.name));
|
|
104234
104230
|
};
|
|
104235
104231
|
MetadataService.saveLearningObjectives = (items) => learningObjectiveManager.saveAll(items);
|
|
104236
|
-
MetadataService.addLearningObjective = (
|
|
104237
|
-
|
|
104232
|
+
MetadataService.addLearningObjective = (item) => {
|
|
104233
|
+
return learningObjectiveManager.add(item);
|
|
104234
|
+
};
|
|
104235
|
+
MetadataService.updateLearningObjective = (id3, updates) => {
|
|
104236
|
+
return learningObjectiveManager.update(id3, updates);
|
|
104237
|
+
};
|
|
104238
104238
|
MetadataService.deleteLearningObjective = (code4) => learningObjectiveManager.delete(code4);
|
|
104239
104239
|
|
|
104240
104240
|
// node_modules/date-fns/addDays.mjs
|
|
@@ -106134,7 +106134,7 @@ function QuestionList({
|
|
|
106134
106134
|
if (questions.length === 0) {
|
|
106135
106135
|
return /* @__PURE__ */ React169__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.");
|
|
106136
106136
|
}
|
|
106137
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[30%]" }, "Question Text"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Type"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Grade"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Bloom's"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Last Modified"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, questions.map((question2) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: question2.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-
|
|
106137
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[30%]" }, "Question Text"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Type"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Grade"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Bloom's"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Last Modified"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, questions.map((question2) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: question2.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium max-w-xs truncate", title: question2.text }, /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: question2.questionConfig.prompt })), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, question2.code), /* @__PURE__ */ React169__default.createElement(TableCell, null, /* @__PURE__ */ React169__default.createElement(Badge2, { variant: "secondary" }, getLookupName(question2.questionTypeCode, metadata.questionTypes))), /* @__PURE__ */ React169__default.createElement(TableCell, null, getLookupName(question2.subjectCode, metadata.subjects)), /* @__PURE__ */ React169__default.createElement(TableCell, null, getLookupName(question2.topicCode, metadata.topics)), /* @__PURE__ */ React169__default.createElement(TableCell, null, getLookupName(question2.gradeLevelCode, metadata.gradeLevels)), /* @__PURE__ */ React169__default.createElement(TableCell, null, /* @__PURE__ */ React169__default.createElement(Badge2, { variant: "outline" }, getLookupName(question2.bloomLevelCode, metadata.bloomLevels))), /* @__PURE__ */ React169__default.createElement(TableCell, null, format(new Date(question2.lastModified), "MMM d, yyyy")), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, onView && /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onView(question2), className: "mr-1" }, /* @__PURE__ */ React169__default.createElement(Eye, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onEdit(question2), className: "mr-1" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => onDelete(question2), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" }))))))));
|
|
106138
106138
|
}
|
|
106139
106139
|
|
|
106140
106140
|
// src/react-ui/components/authoring/QuestionFilters.tsx
|
|
@@ -106215,7 +106215,7 @@ function QuestionFilters({
|
|
|
106215
106215
|
onChange: (e3) => setSearchTerm(e3.target.value),
|
|
106216
106216
|
className: "lg:col-span-2 xl:col-span-1"
|
|
106217
106217
|
}
|
|
106218
|
-
), /* @__PURE__ */ React169__default.createElement(Select2, { value: subjectCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setSubjectCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Subjects"), subjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: topicCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setTopicCode), disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Topic" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Topics"), filteredTopics.map((t4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: gradeLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setGradeLevelCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Grade Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Grade Levels"), gradeLevels.map((gl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: bloomLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setBloomLevelCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Bloom's Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Levels"), bloomLevels.map((bl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: questionTypeCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setQuestionTypeCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Question Type" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Types"), questionTypes.map((qt) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: difficulty || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setDifficulty) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Difficulty" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Difficulties"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "
|
|
106218
|
+
), /* @__PURE__ */ React169__default.createElement(Select2, { value: subjectCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setSubjectCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Subjects"), subjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: topicCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setTopicCode), disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Topic" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Topics"), filteredTopics.map((t4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: gradeLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setGradeLevelCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Grade Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Grade Levels"), gradeLevels.map((gl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: bloomLevelCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setBloomLevelCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Bloom's Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Levels"), bloomLevels.map((bl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: questionTypeCode || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setQuestionTypeCode) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Question Type" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Types"), questionTypes.map((qt) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: difficulty || ALL_ITEMS_VALUE, onValueChange: createSelectHandler(setDifficulty) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Difficulty" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: ALL_ITEMS_VALUE }, "All Difficulties"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Easy" }, "Easy"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Medium" }, "Medium"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "Hard" }, "Hard"))), /* @__PURE__ */ React169__default.createElement("div", { className: "flex gap-2 col-span-full sm:col-span-1 xl:col-span-2 xl:col-start-6" }, /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleApplyFilters, className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React169__default.createElement(Search, { className: "mr-2 h-4 w-4" }), " Apply"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleClearFilters, variant: "outline", className: "w-full sm:w-auto flex-grow" }, /* @__PURE__ */ React169__default.createElement(CircleX, { className: "mr-2 h-4 w-4" }), " Clear"))));
|
|
106219
106219
|
}
|
|
106220
106220
|
|
|
106221
106221
|
// src/react-ui/components/authoring/QuestionFormDialog.tsx
|
|
@@ -106323,7 +106323,7 @@ function QuestionFormDialog({
|
|
|
106323
106323
|
};
|
|
106324
106324
|
const dialogTitle = questionToEdit ? "Edit Question Metadata" : "Create New Question";
|
|
106325
106325
|
const dialogDescription = "First, define the metadata for the question. Then, edit the question's content and logic.";
|
|
106326
|
-
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, dialogDescription)), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Question Code"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gradeLevelCode" }, "Grade Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: gradeLevelCode, onValueChange: setGradeLevelCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "gradeLevelCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Grade Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, gradeLevels.map((gl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__default.createElement(Select2, { value: subjectCode, onValueChange: setSubjectCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, subjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "topicCode" }, "Topic"), /* @__PURE__ */ React169__default.createElement(Select2, { value: topicCode, onValueChange: setTopicCode, disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "topicCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Topic" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, filteredTopics.map((t4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Bloom's Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: bloomLevelCode, onValueChange: setBloomLevelCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "bloomLevelCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Bloom's Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, bloomLevels.map((bl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name))))), /* @__PURE__ */ React169__default.createElement("div", { className: "pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Question Content & Logic"), questionConfig ? /* @__PURE__ */ React169__default.createElement("div", { className: "p-3 mt-2 border rounded-md bg-muted/30 flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "font-
|
|
106326
|
+
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, dialogTitle), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, dialogDescription)), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Question Code"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gradeLevelCode" }, "Grade Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: gradeLevelCode, onValueChange: setGradeLevelCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "gradeLevelCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Grade Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, gradeLevels.map((gl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: gl.code, value: gl.code }, gl.name)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__default.createElement(Select2, { value: subjectCode, onValueChange: setSubjectCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, subjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4.code, value: s4.code }, s4.name))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "topicCode" }, "Topic"), /* @__PURE__ */ React169__default.createElement(Select2, { value: topicCode, onValueChange: setTopicCode, disabled: !subjectCode || filteredTopics.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "topicCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Topic" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, filteredTopics.map((t4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t4.code, value: t4.code }, t4.name)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Bloom's Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: bloomLevelCode, onValueChange: setBloomLevelCode }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "bloomLevelCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select Bloom's Level" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, bloomLevels.map((bl) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: bl.code, value: bl.code }, bl.name))))), /* @__PURE__ */ React169__default.createElement("div", { className: "pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement(Label2, { className: "font-semibold" }, "Question Content & Logic"), questionConfig ? /* @__PURE__ */ React169__default.createElement("div", { className: "p-3 mt-2 border rounded-md bg-muted/30 flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "font-Medium" }, "Type: ", /* @__PURE__ */ React169__default.createElement("span", { className: "font-normal" }, questionConfig.questionType)), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground truncate max-w-md" }, "Prompt: ", questionConfig.prompt.replace(/<[^>]*>?/gm, "") || "Not set")), /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline", onClick: () => handleOpenQuestionEditor() }, /* @__PURE__ */ React169__default.createElement(SquarePen, { className: "mr-2 h-4 w-4" }), " Edit Content")) : /* @__PURE__ */ React169__default.createElement("div", { className: "p-3 mt-2 border-dashed border-2 rounded-md text-center" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground mb-2" }, "No content has been created yet."), /* @__PURE__ */ React169__default.createElement(Button, { variant: "default", onClick: () => handleOpenQuestionEditor("multiple_choice") }, /* @__PURE__ */ React169__default.createElement(BookCopy, { className: "mr-2 h-4 w-4" }), " Create Question Content")))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !questionConfig }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save Question")))), questionConfig && /* @__PURE__ */ React169__default.createElement(
|
|
106327
106327
|
EditQuestionModal,
|
|
106328
106328
|
{
|
|
106329
106329
|
isOpen: isQuestionEditorOpen,
|
|
@@ -110641,10 +110641,10 @@ var RoadmapService = class {
|
|
|
110641
110641
|
}
|
|
110642
110642
|
return null;
|
|
110643
110643
|
}
|
|
110644
|
-
static updateRoadmapItemStatus(
|
|
110644
|
+
static updateRoadmapItemStatus(code4, isCompleted) {
|
|
110645
110645
|
const roadmap = this.getRoadmap();
|
|
110646
110646
|
if (roadmap) {
|
|
110647
|
-
const itemIndex = roadmap.items.findIndex((item) => item.
|
|
110647
|
+
const itemIndex = roadmap.items.findIndex((item) => item.code === code4);
|
|
110648
110648
|
if (itemIndex > -1) {
|
|
110649
110649
|
roadmap.items[itemIndex].isCompleted = isCompleted;
|
|
110650
110650
|
this.saveRoadmap(roadmap);
|
|
@@ -111225,7 +111225,7 @@ z.object({
|
|
|
111225
111225
|
startDate: z.string().describe("The start date for the analysis period in ISO format (YYYY-MM-DD)."),
|
|
111226
111226
|
endDate: z.string().describe("The end date for the analysis period in ISO format (YYYY-MM-DD)."),
|
|
111227
111227
|
allAvailableTopics: z.array(z.object({
|
|
111228
|
-
|
|
111228
|
+
code: z.string(),
|
|
111229
111229
|
subject: z.string(),
|
|
111230
111230
|
category: z.string(),
|
|
111231
111231
|
topic: z.string()
|
|
@@ -111236,7 +111236,7 @@ var RoadmapItemSchema = z.object({
|
|
|
111236
111236
|
topicName: z.string(),
|
|
111237
111237
|
reason: z.string(),
|
|
111238
111238
|
suggestedDifficulty: z.enum(["Very Easy", "Easy", "Medium", "Hard", "Expert"]),
|
|
111239
|
-
|
|
111239
|
+
code: z.string(),
|
|
111240
111240
|
isCompleted: z.boolean()
|
|
111241
111241
|
});
|
|
111242
111242
|
var WeeklyRoadmapSchema = z.object({
|
|
@@ -111301,7 +111301,7 @@ All topic names and loIds in your "weeklyRoadmap" output MUST be chosen directly
|
|
|
111301
111301
|
- **gamificationRemarks**: Write an encouraging message mentioning their achievements.
|
|
111302
111302
|
2. **For "weeklyRoadmap":**
|
|
111303
111303
|
- Create a 5-item roadmap for the upcoming week.
|
|
111304
|
-
- Prioritize the "areasForImprovement" you identified. For each, find the corresponding entry in "All Available Topics" and use its "topicName" and "
|
|
111304
|
+
- Prioritize the "areasForImprovement" you identified. For each, find the corresponding entry in "All Available Topics" and use its "topicName" and "code".
|
|
111305
111305
|
- If more items are needed, select related topics from "All Available Topics".
|
|
111306
111306
|
|
|
111307
111307
|
**IF Practice History IS EMPTY:**
|
|
@@ -111312,7 +111312,7 @@ All topic names and loIds in your "weeklyRoadmap" output MUST be chosen directly
|
|
|
111312
111312
|
- **gamificationRemarks**: Write a general motivational message about starting to learn.
|
|
111313
111313
|
2. **For "weeklyRoadmap":**
|
|
111314
111314
|
- Create a 5-item "starter" roadmap.
|
|
111315
|
-
- Select 5 diverse and foundational topics directly from the "All Available Topics" list. Use their exact "topicName" and "
|
|
111315
|
+
- Select 5 diverse and foundational topics directly from the "All Available Topics" list. Use their exact "topicName" and "code".
|
|
111316
111316
|
- For the "reason", explain that this is a good starting point to explore the subject.
|
|
111317
111317
|
|
|
111318
111318
|
--- END LOGIC FLOW ---
|
|
@@ -111340,7 +111340,7 @@ The 'suggestedDifficulty' field MUST ALWAYS be one of these exact English string
|
|
|
111340
111340
|
"topicName": "Topic C",
|
|
111341
111341
|
"reason": "To strengthen your understanding of this key area.",
|
|
111342
111342
|
"suggestedDifficulty": "Easy",
|
|
111343
|
-
"
|
|
111343
|
+
"code": "lo-id-for-topic-c-from-the-list",
|
|
111344
111344
|
"isCompleted": false
|
|
111345
111345
|
}
|
|
111346
111346
|
]
|
|
@@ -111602,11 +111602,11 @@ init_react_shim();
|
|
|
111602
111602
|
// src/ai/flows/assess-and-map-document-types.ts
|
|
111603
111603
|
init_react_shim();
|
|
111604
111604
|
var LearningObjectiveContextSchema = z.object({
|
|
111605
|
-
|
|
111605
|
+
code: z.string(),
|
|
111606
111606
|
subject: z.string(),
|
|
111607
111607
|
category: z.string(),
|
|
111608
111608
|
topic: z.string(),
|
|
111609
|
-
|
|
111609
|
+
description: z.string()
|
|
111610
111610
|
});
|
|
111611
111611
|
z.object({
|
|
111612
111612
|
language: z.string().default("English"),
|
|
@@ -111614,7 +111614,7 @@ z.object({
|
|
|
111614
111614
|
learningObjectives: z.array(LearningObjectiveContextSchema).min(1, { message: "At least one learning objective is required for mapping." })
|
|
111615
111615
|
});
|
|
111616
111616
|
var MappedLOSchema = z.object({
|
|
111617
|
-
|
|
111617
|
+
code: z.string().describe("The exact code from the provided learning objectives list that matches the document content."),
|
|
111618
111618
|
confidence: z.number().min(0).max(100).describe("A confidence score (0-100) of how well the document maps to this LO."),
|
|
111619
111619
|
reasoning: z.string().describe("A brief explanation for why this mapping is relevant.")
|
|
111620
111620
|
});
|
|
@@ -111650,7 +111650,7 @@ You are an expert curriculum analyst. Your task is to analyze a given document a
|
|
|
111650
111650
|
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).
|
|
111651
111651
|
|
|
111652
111652
|
2. **Specific Mapping:** Identify which specific LOs from the list are directly addressed by the document. For each match you find, provide:
|
|
111653
|
-
- The exact "
|
|
111653
|
+
- The exact "code" of the matched LO.
|
|
111654
111654
|
- A "confidence" score (0-100) for that specific match.
|
|
111655
111655
|
- A brief "reasoning" in ${language3} explaining why the document content maps to that LO.
|
|
111656
111656
|
|
|
@@ -111665,7 +111665,7 @@ Return a single, valid JSON object in this EXACT format. Do not include any othe
|
|
|
111665
111665
|
"isFreestyleRecommended": false,
|
|
111666
111666
|
"mappedLOs": [
|
|
111667
111667
|
{
|
|
111668
|
-
"
|
|
111668
|
+
"code": "SWIFT_FUNC_01",
|
|
111669
111669
|
"confidence": 95,
|
|
111670
111670
|
"reasoning": "The document provides a detailed explanation of function syntax and default parameters, which directly aligns with this learning objective."
|
|
111671
111671
|
}
|
|
@@ -111760,7 +111760,7 @@ Return the response as a single JSON object with a key "generatedQuestions" cont
|
|
|
111760
111760
|
"correctTempOptionId": "A",
|
|
111761
111761
|
"explanation": "The document states that mitochondria are the powerhouses of the cell, responsible for cellular respiration.",
|
|
111762
111762
|
"points": 10,
|
|
111763
|
-
"difficulty": "
|
|
111763
|
+
"difficulty": "Medium",
|
|
111764
111764
|
"topic": "Cell Biology"
|
|
111765
111765
|
},
|
|
111766
111766
|
{
|
|
@@ -111769,7 +111769,7 @@ Return the response as a single JSON object with a key "generatedQuestions" cont
|
|
|
111769
111769
|
"correctAnswer": false,
|
|
111770
111770
|
"explanation": "The text specifies that the cell wall is a feature of plant cells, not animal cells.",
|
|
111771
111771
|
"points": 10,
|
|
111772
|
-
"difficulty": "
|
|
111772
|
+
"difficulty": "Easy",
|
|
111773
111773
|
"topic": "Cell Biology"
|
|
111774
111774
|
}
|
|
111775
111775
|
]
|
|
@@ -112602,7 +112602,7 @@ var QuizReview = ({
|
|
|
112602
112602
|
};
|
|
112603
112603
|
return /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full max-w-4xl mx-auto shadow-xl" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-3xl font-headline text-center flex items-center justify-center" }, /* @__PURE__ */ React169__default.createElement(BookOpen, { className: "mr-3 h-8 w-8 text-primary" }), "AI-Powered Quiz Review"), /* @__PURE__ */ React169__default.createElement(CardDescription, { className: "text-center text-lg" }, "Let's break down your results and reinforce your learning.")), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement(Card, { className: "bg-muted/30" }, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-xl flex items-center" }, /* @__PURE__ */ React169__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-yellow-500" }), "Key Concepts Summary")), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: reviewContent.overallSummary }))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-xl font-semibold mb-2" }, "Detailed Question Analysis"), /* @__PURE__ */ React169__default.createElement(Accordion2, { type: "single", collapsible: true, className: "w-full" }, quizResult.questionResults.map((qResult, index3) => {
|
|
112604
112604
|
const aiReview = getReviewForQuestion(qResult.questionId);
|
|
112605
|
-
return /* @__PURE__ */ React169__default.createElement(AccordionItem2, { value: `item-${index3}`, key: qResult.questionId }, /* @__PURE__ */ React169__default.createElement(AccordionTrigger2, null, /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-between w-full pr-2" }, /* @__PURE__ */ React169__default.createElement("span", { className: "text-left font-
|
|
112605
|
+
return /* @__PURE__ */ React169__default.createElement(AccordionItem2, { value: `item-${index3}`, key: qResult.questionId }, /* @__PURE__ */ React169__default.createElement(AccordionTrigger2, null, /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-between w-full pr-2" }, /* @__PURE__ */ React169__default.createElement("span", { className: "text-left font-Medium" }, "Question ", index3 + 1), qResult.isCorrect ? /* @__PURE__ */ React169__default.createElement("span", { className: "text-sm text-green-600 font-Medium flex items-center gap-1" }, /* @__PURE__ */ React169__default.createElement(CircleCheckBig, { className: "h-4 w-4" }), " Correct") : /* @__PURE__ */ React169__default.createElement("span", { className: "text-sm text-destructive font-Medium flex items-center gap-1" }, /* @__PURE__ */ React169__default.createElement(CircleX, { className: "h-4 w-4" }), " Incorrect"))), /* @__PURE__ */ React169__default.createElement(AccordionContent2, { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-md bg-background" }, /* @__PURE__ */ React169__default.createElement("p", { className: "font-semibold mb-2" }, "Original Question:"), /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: qResult.prompt })), qResult.questionType === "coding" ? renderCodingResult(qResult) : renderStandardResult(qResult), aiReview && /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border-l-4 border-primary bg-primary/10 rounded-r-md" }, /* @__PURE__ */ React169__default.createElement("p", { className: "font-semibold text-primary mb-2" }, "AI Tutor Explanation:"), /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: aiReview.explanation }))));
|
|
112606
112606
|
}))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-xl font-semibold mb-2" }, "Topics for Further Study"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-wrap gap-2" }, reviewContent.relatedTopics.map((topic, index3) => /* @__PURE__ */ React169__default.createElement(Badge2, { key: index3, variant: "secondary", className: "text-base px-3 py-1" }, topic))))), /* @__PURE__ */ React169__default.createElement(CardFooter, { className: "flex flex-col sm:flex-row justify-between gap-2" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline", onClick: onBackToResults, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__default.createElement(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Back to Results"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: onExit, className: "w-full sm:w-auto" }, /* @__PURE__ */ React169__default.createElement(LogOut, { className: "mr-2 h-4 w-4" }), "Finish & Exit")));
|
|
112607
112607
|
};
|
|
112608
112608
|
|
|
@@ -112649,7 +112649,7 @@ var PracticeHistoryTable = ({ history: history2, maxHeight = "400px" }) => {
|
|
|
112649
112649
|
if (percentage >= 50) return "secondary";
|
|
112650
112650
|
return "destructive";
|
|
112651
112651
|
};
|
|
112652
|
-
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, null, t4("history.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("history.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "w-full border rounded-md", style: { height: maxHeight } }, /* @__PURE__ */ React169__default.createElement(TooltipProvider2, { delayDuration: 100 }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, { className: "sticky top-0 bg-muted z-10" }, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[120px]" }, t4("history.headers.date")), /* @__PURE__ */ React169__default.createElement(TableHead, null, t4("history.headers.topic")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[80px]" }, t4("history.headers.score")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[90px]" }, t4("history.headers.percentage")))), /* @__PURE__ */ React169__default.createElement(TableBody, null, history2.length > 0 ? history2.map((session) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: session.id, onClick: () => handleRowClick(session), className: "cursor-pointer" }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-
|
|
112652
|
+
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, null, t4("history.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("history.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "w-full border rounded-md", style: { height: maxHeight } }, /* @__PURE__ */ React169__default.createElement(TooltipProvider2, { delayDuration: 100 }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, { className: "sticky top-0 bg-muted z-10" }, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[120px]" }, t4("history.headers.date")), /* @__PURE__ */ React169__default.createElement(TableHead, null, t4("history.headers.topic")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[80px]" }, t4("history.headers.score")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[90px]" }, t4("history.headers.percentage")))), /* @__PURE__ */ React169__default.createElement(TableBody, null, history2.length > 0 ? history2.map((session) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: session.id, onClick: () => handleRowClick(session), className: "cursor-pointer" }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium text-xs text-muted-foreground" }, formatDate(session.timestamp)), /* @__PURE__ */ React169__default.createElement(TableCell, null, /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col gap-1.5" }, session.topics.map((topicInfo, index3) => /* @__PURE__ */ React169__default.createElement(Tooltip2, { key: index3 }, /* @__PURE__ */ React169__default.createElement(TooltipTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement("p", { className: "font-semibold text-sm truncate" }, topicInfo.topic)), /* @__PURE__ */ React169__default.createElement(TooltipContent2, null, /* @__PURE__ */ React169__default.createElement("p", null, /* @__PURE__ */ React169__default.createElement("strong", null, t4("settingsModal.topics.tableHeaders.subject"), ":"), " ", topicInfo.subject), /* @__PURE__ */ React169__default.createElement("p", null, /* @__PURE__ */ React169__default.createElement("strong", null, t4("settingsModal.topics.tableHeaders.category"), ":"), " ", topicInfo.category)))))), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right font-mono text-sm" }, session.score !== null ? `${session.score}/${session.maxScore}` : "N/A"), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, session.percentage !== null && /* @__PURE__ */ React169__default.createElement(
|
|
112653
112653
|
Badge2,
|
|
112654
112654
|
{
|
|
112655
112655
|
variant: getPercentageBadgeVariant(session.percentage),
|
|
@@ -133464,12 +133464,12 @@ var ChartTooltipContent = React169.forwardRef(
|
|
|
133464
133464
|
const itemConfig = getPayloadConfigFromPayload(config3, item, key);
|
|
133465
133465
|
const value = !labelKey && typeof label === "string" ? config3[label]?.label || label : itemConfig?.label;
|
|
133466
133466
|
if (labelFormatter) {
|
|
133467
|
-
return /* @__PURE__ */ React169.createElement("div", { className: cn("font-
|
|
133467
|
+
return /* @__PURE__ */ React169.createElement("div", { className: cn("font-Medium", labelClassName) }, labelFormatter(value, payload));
|
|
133468
133468
|
}
|
|
133469
133469
|
if (!value) {
|
|
133470
133470
|
return null;
|
|
133471
133471
|
}
|
|
133472
|
-
return /* @__PURE__ */ React169.createElement("div", { className: cn("font-
|
|
133472
|
+
return /* @__PURE__ */ React169.createElement("div", { className: cn("font-Medium", labelClassName) }, value);
|
|
133473
133473
|
}, [
|
|
133474
133474
|
label,
|
|
133475
133475
|
labelFormatter,
|
|
@@ -133532,7 +133532,7 @@ var ChartTooltipContent = React169.forwardRef(
|
|
|
133532
133532
|
)
|
|
133533
133533
|
},
|
|
133534
133534
|
/* @__PURE__ */ React169.createElement("div", { className: "grid gap-1.5" }, nestLabel ? tooltipLabel : null, /* @__PURE__ */ React169.createElement("span", { className: "text-muted-foreground" }, itemConfig?.label || item.name)),
|
|
133535
|
-
item.value && /* @__PURE__ */ React169.createElement("span", { className: "font-mono font-
|
|
133535
|
+
item.value && /* @__PURE__ */ React169.createElement("span", { className: "font-mono font-Medium tabular-nums text-foreground" }, item.value.toLocaleString())
|
|
133536
133536
|
))
|
|
133537
133537
|
);
|
|
133538
133538
|
}))
|
|
@@ -134709,7 +134709,7 @@ var ManageTopics = () => {
|
|
|
134709
134709
|
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Card, { className: "w-full max-w-4xl mx-auto shadow-none border-none" }, /* @__PURE__ */ React169__default.createElement(CardHeader, { className: "px-1" }, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex items-center text-2xl font-headline" }, /* @__PURE__ */ React169__default.createElement(FileText, { className: "mr-3 h-6 w-6 text-primary" }), t4("settingsModal.topics.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("settingsModal.topics.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-6 px-1" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "tsv-importer", className: "text-lg font-semibold" }, t4("settingsModal.topics.importData")), /* @__PURE__ */ React169__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__default.createElement("div", { className: "flex-grow" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.importHint")), /* @__PURE__ */ React169__default.createElement("a", { href: "#", className: "text-xs text-primary hover:underline", onClick: (e3) => {
|
|
134710
134710
|
e3.preventDefault();
|
|
134711
134711
|
alert("Header format:\nLO ID LO Description Subject Category Topic Keywords Grade STEM Element(s) Bloom\u2019s Level(s) Guideline");
|
|
134712
|
-
} }, t4("settingsModal.topics.viewHeaderFormat"))), /* @__PURE__ */ React169__default.createElement(Input, { id: "tsv-importer", type: "file", ref: fileInputRef, accept: ".tsv,text/tab-separated-values", onChange: handleFileChange, className: "hidden" }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: () => fileInputRef.current?.click() }, /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.chooseFile")))), importErrors.length > 0 && /* @__PURE__ */ React169__default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__default.createElement(AlertTitle, null, t4("settingsModal.topics.importErrors")), /* @__PURE__ */ React169__default.createElement(AlertDescription, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-24 mt-2" }, /* @__PURE__ */ React169__default.createElement("ul", { className: "list-disc list-inside text-xs space-y-1" }, importErrors.map((error, index3) => /* @__PURE__ */ React169__default.createElement("li", { key: index3 }, error)))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between mb-2" }, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-lg font-semibold" }, t4("settingsModal.topics.currentDataTitle")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.showingCount", { shown: filteredLearningObjectives.length, total: learningObjectives.length }))), /* @__PURE__ */ React169__default.createElement("div", { className: "mb-4" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subject-filter" }, t4("settingsModal.topics.filterBySubject")), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedSubjectFilter, onValueChange: setSelectedSubjectFilter, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "subject-filter", className: "w-full sm:w-[280px]" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.topics.filterPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "all" }, t4("settingsModal.topics.allSubjects")), subjects.map((subject) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: subject, value: subject }, subject))))), /* @__PURE__ */ React169__default.createElement("div", { className: "border rounded-lg" }, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-72" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, { className: "sticky top-0 bg-muted" }, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.subject")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.category")), /* @__PURE__ */ React169__default.createElement(TableHead, null, t4("settingsModal.topics.tableHeaders.topic")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[100px]" }, t4("settingsModal.topics.tableHeaders.
|
|
134712
|
+
} }, t4("settingsModal.topics.viewHeaderFormat"))), /* @__PURE__ */ React169__default.createElement(Input, { id: "tsv-importer", type: "file", ref: fileInputRef, accept: ".tsv,text/tab-separated-values", onChange: handleFileChange, className: "hidden" }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: () => fileInputRef.current?.click() }, /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.chooseFile")))), importErrors.length > 0 && /* @__PURE__ */ React169__default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__default.createElement(AlertTitle, null, t4("settingsModal.topics.importErrors")), /* @__PURE__ */ React169__default.createElement(AlertDescription, null, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-24 mt-2" }, /* @__PURE__ */ React169__default.createElement("ul", { className: "list-disc list-inside text-xs space-y-1" }, importErrors.map((error, index3) => /* @__PURE__ */ React169__default.createElement("li", { key: index3 }, error)))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between mb-2" }, /* @__PURE__ */ React169__default.createElement("h3", { className: "text-lg font-semibold" }, t4("settingsModal.topics.currentDataTitle")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.topics.showingCount", { shown: filteredLearningObjectives.length, total: learningObjectives.length }))), /* @__PURE__ */ React169__default.createElement("div", { className: "mb-4" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subject-filter" }, t4("settingsModal.topics.filterBySubject")), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedSubjectFilter, onValueChange: setSelectedSubjectFilter, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "subject-filter", className: "w-full sm:w-[280px]" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.topics.filterPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "all" }, t4("settingsModal.topics.allSubjects")), subjects.map((subject) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: subject, value: subject }, subject))))), /* @__PURE__ */ React169__default.createElement("div", { className: "border rounded-lg" }, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-72" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, { className: "sticky top-0 bg-muted" }, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.subject")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[150px]" }, t4("settingsModal.topics.tableHeaders.category")), /* @__PURE__ */ React169__default.createElement(TableHead, null, t4("settingsModal.topics.tableHeaders.topic")), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "w-[100px]" }, t4("settingsModal.topics.tableHeaders.code")))), /* @__PURE__ */ React169__default.createElement(TableBody, null, isLoading ? /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableCell, { colSpan: 4, className: "text-center" }, t4("common.loading"))) : filteredLearningObjectives.length > 0 ? filteredLearningObjectives.map((lo) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: lo.code }, /* @__PURE__ */ React169__default.createElement(TableCell, null, lo.subject), /* @__PURE__ */ React169__default.createElement(TableCell, null, lo.category), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, lo.topic), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, lo.code))) : /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableCell, { colSpan: 4, className: "text-center h-24 text-muted-foreground" }, t4("settingsModal.topics.emptyData"))))))))), /* @__PURE__ */ React169__default.createElement(CardFooter, { className: "px-1" }, /* @__PURE__ */ React169__default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "destructive", disabled: learningObjectives.length === 0 }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "mr-2 h-4 w-4" }), t4("settingsModal.topics.clearAllData"))), /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, t4("settingsModal.topics.clearDataConfirmationTitle")), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, t4("settingsModal.topics.clearDataConfirmationMessage", { count: learningObjectives.length }))), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: handleClearData }, t4("settingsModal.topics.confirmDelete"))))))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isConfirmModalOpen, onOpenChange: setIsConfirmModalOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, t4("settingsModal.topics.confirmModal.title")), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, t4("settingsModal.topics.confirmModal.description", { count: parsedImportData?.data.length || 0 }))), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, { className: "gap-2" }, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { onClick: () => setParsedImportData(null) }, t4("common.cancel")), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: handleConfirmMerge, className: "bg-blue-600 hover:bg-blue-700" }, t4("settingsModal.topics.confirmModal.mergeButton")), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: handleConfirmOverwrite, className: "bg-amber-600 hover:bg-amber-700" }, t4("settingsModal.topics.confirmModal.overwriteButton"))))));
|
|
134713
134713
|
};
|
|
134714
134714
|
|
|
134715
134715
|
// src/react-ui/components/app/ManageImageContexts.tsx
|
|
@@ -135116,7 +135116,7 @@ var SettingsModal = ({ isOpen, onClose, defaultTab = "personal" }) => {
|
|
|
135116
135116
|
return goal.id;
|
|
135117
135117
|
}
|
|
135118
135118
|
};
|
|
135119
|
-
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl lg:max-w-3xl max-h-[85vh] flex flex-col" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, { className: "shrink-0" }, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Settings, { className: "mr-2 h-5 w-5 text-primary" }), t4("settingsModal.title")), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, t4("settingsModal.description"))), /* @__PURE__ */ React169__default.createElement(Tabs2, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "pt-2 flex-1 flex flex-col min-h-0" }, /* @__PURE__ */ React169__default.createElement(TabsList2, { className: "grid w-full grid-cols-5 shrink-0" }, /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "personal" }, /* @__PURE__ */ React169__default.createElement(User, { className: "mr-1 h-4 w-4" }), t4("settingsModal.personalTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "topics" }, /* @__PURE__ */ React169__default.createElement(ListTodo, { className: "mr-1 h-4 w-4" }), t4("settingsModal.topicsTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "imageContexts" }, /* @__PURE__ */ React169__default.createElement(ImagePlus, { className: "mr-1 h-4 w-4" }), "Images"), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "layout" }, /* @__PURE__ */ React169__default.createElement(LayoutDashboard, { className: "mr-1 h-4 w-4" }), t4("settingsModal.layoutTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "apiKeys" }, /* @__PURE__ */ React169__default.createElement(KeyRound, { className: "mr-1 h-4 w-4" }), t4("settingsModal.apiKeysTab"))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "personal", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-full pr-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3" }, t4("settingsModal.personal.basicInfo")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "full-name" }, t4("settingsModal.personal.fullName")), /* @__PURE__ */ React169__default.createElement(Input, { id: "full-name", value: fullName, onChange: (e3) => setFullName(e3.target.value), placeholder: t4("settingsModal.personal.fullNamePlaceholder") })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "weekly-goal" }, t4("settingsModal.personal.weeklyGoal")), /* @__PURE__ */ React169__default.createElement(Input, { id: "weekly-goal", type: "number", value: weeklyGoal, onChange: (e3) => setWeeklyGoal(parseInt(e3.target.value, 10) || 0), min: "1" })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "language-select", className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Languages, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.languageSelectLabel")), /* @__PURE__ */ React169__default.createElement(Select2, { value: language3, onValueChange: changeLanguage2 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "language-select" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select a language..." })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "en" }, "English"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "vi" }, "Ti\u1EBFng Vi\u1EC7t")))))), /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3 flex items-center" }, /* @__PURE__ */ React169__default.createElement(Target, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.advancedGoals")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2 mb-4" }, advancedGoals.map((goal) => /* @__PURE__ */ React169__default.createElement("div", { key: goal.id, className: "flex items-center justify-between p-2 bg-muted/50 rounded-md" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm flex-1" }, renderGoalDescription(goal)), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleDeleteGoal(goal.id) }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), advancedGoals.length === 0 && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.personal.noGoals"))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement("h5", { className: "font-medium" }, t4("settingsModal.personal.addNewGoal")), /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalType, onValueChange: (v) => setNewGoalType(v) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.goalTypePlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "average_score_subject" }, t4("settingsModal.personal.goalType.avgScoreSubject")), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "mastery_topic" }, t4("settingsModal.personal.goalType.masteryTopic")))), newGoalType === "average_score_subject" && /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") })), newGoalType === "mastery_topic" && /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalTopic, onValueChange: setNewGoalTopic, disabled: !newGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.topicPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, topicsForSelectedSubject.map((t5) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t5, value: t5 }, t5)))), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") }), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalConsecutive, onChange: (e3) => setNewGoalConsecutive(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.consecutiveSessionsPlaceholder") })), newGoalType && /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", onClick: handleAddNewGoal }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.addGoalButton"))))))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "topics", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ManageTopics, null)), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "imageContexts", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ManageImageContexts, null)), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "layout", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold" }, t4("settingsModal.layout.title")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground mt-1 mb-3" }, t4("settingsModal.layout.description")), /* @__PURE__ */ React169__default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(RefreshCw, { className: "mr-2 h-4 w-4" }), t4("settingsModal.layout.resetButton"))), /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, t4("settingsModal.layout.resetConfirmationTitle")), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, t4("settingsModal.layout.resetConfirmationMessage"))), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: handleResetLayout }, t4("settingsModal.layout.confirmReset"))))))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "apiKeys", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gemini-api-key" }, t4("settingsModal.apiKeys.geminiKey")), /* @__PURE__ */ React169__default.createElement(Input, { id: "gemini-api-key", type: "password", value: geminiApiKey, onChange: (e3) => setGeminiApiKey(e3.target.value), placeholder: t4("settingsModal.apiKeys.geminiKeyPlaceholder") }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-muted-foreground" }, t4("settingsModal.apiKeys.storageHint"))))), /* @__PURE__ */ React169__default.createElement(DialogFooter, { className: "gap-2 sm:justify-end pt-4 shrink-0" }, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, t4("common.close"))), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", onClick: handleSave }, /* @__PURE__ */ React169__default.createElement(Save, { className: "mr-2 h-4 w-4" }), t4("settingsModal.saveChanges")))));
|
|
135119
|
+
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-xl md:max-w-2xl lg:max-w-3xl max-h-[85vh] flex flex-col" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, { className: "shrink-0" }, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Settings, { className: "mr-2 h-5 w-5 text-primary" }), t4("settingsModal.title")), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, t4("settingsModal.description"))), /* @__PURE__ */ React169__default.createElement(Tabs2, { value: activeTab, onValueChange: (value) => setActiveTab(value), className: "pt-2 flex-1 flex flex-col min-h-0" }, /* @__PURE__ */ React169__default.createElement(TabsList2, { className: "grid w-full grid-cols-5 shrink-0" }, /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "personal" }, /* @__PURE__ */ React169__default.createElement(User, { className: "mr-1 h-4 w-4" }), t4("settingsModal.personalTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "topics" }, /* @__PURE__ */ React169__default.createElement(ListTodo, { className: "mr-1 h-4 w-4" }), t4("settingsModal.topicsTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "imageContexts" }, /* @__PURE__ */ React169__default.createElement(ImagePlus, { className: "mr-1 h-4 w-4" }), "Images"), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "layout" }, /* @__PURE__ */ React169__default.createElement(LayoutDashboard, { className: "mr-1 h-4 w-4" }), t4("settingsModal.layoutTab")), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "apiKeys" }, /* @__PURE__ */ React169__default.createElement(KeyRound, { className: "mr-1 h-4 w-4" }), t4("settingsModal.apiKeysTab"))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "personal", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-full pr-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3" }, t4("settingsModal.personal.basicInfo")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "full-name" }, t4("settingsModal.personal.fullName")), /* @__PURE__ */ React169__default.createElement(Input, { id: "full-name", value: fullName, onChange: (e3) => setFullName(e3.target.value), placeholder: t4("settingsModal.personal.fullNamePlaceholder") })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "weekly-goal" }, t4("settingsModal.personal.weeklyGoal")), /* @__PURE__ */ React169__default.createElement(Input, { id: "weekly-goal", type: "number", value: weeklyGoal, onChange: (e3) => setWeeklyGoal(parseInt(e3.target.value, 10) || 0), min: "1" })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "language-select", className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Languages, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.languageSelectLabel")), /* @__PURE__ */ React169__default.createElement(Select2, { value: language3, onValueChange: changeLanguage2 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "language-select" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select a language..." })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "en" }, "English"), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "vi" }, "Ti\u1EBFng Vi\u1EC7t")))))), /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3 flex items-center" }, /* @__PURE__ */ React169__default.createElement(Target, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.advancedGoals")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2 mb-4" }, advancedGoals.map((goal) => /* @__PURE__ */ React169__default.createElement("div", { key: goal.id, className: "flex items-center justify-between p-2 bg-muted/50 rounded-md" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm flex-1" }, renderGoalDescription(goal)), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", className: "h-7 w-7", onClick: () => handleDeleteGoal(goal.id) }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4 text-destructive" })))), advancedGoals.length === 0 && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground" }, t4("settingsModal.personal.noGoals"))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3 pt-4 border-t" }, /* @__PURE__ */ React169__default.createElement("h5", { className: "font-Medium" }, t4("settingsModal.personal.addNewGoal")), /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalType, onValueChange: (v) => setNewGoalType(v) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.goalTypePlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "average_score_subject" }, t4("settingsModal.personal.goalType.avgScoreSubject")), /* @__PURE__ */ React169__default.createElement(SelectItem2, { value: "mastery_topic" }, t4("settingsModal.personal.goalType.masteryTopic")))), newGoalType === "average_score_subject" && /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") })), newGoalType === "mastery_topic" && /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-2 animate-in fade-in" }, /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalSubject, onValueChange: setNewGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.subjectPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, allSubjects.map((s4) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: s4, value: s4 }, s4)))), /* @__PURE__ */ React169__default.createElement(Select2, { value: newGoalTopic, onValueChange: setNewGoalTopic, disabled: !newGoalSubject }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: t4("settingsModal.personal.topicPlaceholder") })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, topicsForSelectedSubject.map((t5) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: t5, value: t5 }, t5)))), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalTargetValue, onChange: (e3) => setNewGoalTargetValue(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.targetScorePlaceholder") }), /* @__PURE__ */ React169__default.createElement(Input, { type: "number", value: newGoalConsecutive, onChange: (e3) => setNewGoalConsecutive(parseInt(e3.target.value)), placeholder: t4("settingsModal.personal.consecutiveSessionsPlaceholder") })), newGoalType && /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", onClick: handleAddNewGoal }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), t4("settingsModal.personal.addGoalButton"))))))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "topics", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ManageTopics, null)), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "imageContexts", className: "flex-1 overflow-auto mt-4" }, /* @__PURE__ */ React169__default.createElement(ManageImageContexts, null)), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "layout", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 border rounded-lg" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold" }, t4("settingsModal.layout.title")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-muted-foreground mt-1 mb-3" }, t4("settingsModal.layout.description")), /* @__PURE__ */ React169__default.createElement(AlertDialog2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(RefreshCw, { className: "mr-2 h-4 w-4" }), t4("settingsModal.layout.resetButton"))), /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, t4("settingsModal.layout.resetConfirmationTitle")), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, t4("settingsModal.layout.resetConfirmationMessage"))), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, null, t4("common.cancel")), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: handleResetLayout }, t4("settingsModal.layout.confirmReset"))))))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "apiKeys", className: "space-y-4 pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gemini-api-key" }, t4("settingsModal.apiKeys.geminiKey")), /* @__PURE__ */ React169__default.createElement(Input, { id: "gemini-api-key", type: "password", value: geminiApiKey, onChange: (e3) => setGeminiApiKey(e3.target.value), placeholder: t4("settingsModal.apiKeys.geminiKeyPlaceholder") }), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-muted-foreground" }, t4("settingsModal.apiKeys.storageHint"))))), /* @__PURE__ */ React169__default.createElement(DialogFooter, { className: "gap-2 sm:justify-end pt-4 shrink-0" }, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, t4("common.close"))), /* @__PURE__ */ React169__default.createElement(Button, { type: "button", onClick: handleSave }, /* @__PURE__ */ React169__default.createElement(Save, { className: "mr-2 h-4 w-4" }), t4("settingsModal.saveChanges")))));
|
|
135120
135120
|
};
|
|
135121
135121
|
|
|
135122
135122
|
// src/react-ui/components/dashboard/Cheatsheet.tsx
|
|
@@ -135215,7 +135215,7 @@ var Cheatsheet = () => {
|
|
|
135215
135215
|
}
|
|
135216
135216
|
return null;
|
|
135217
135217
|
};
|
|
135218
|
-
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), t4("knowledgeCards.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("knowledgeCards.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement(Input, { type: "search", placeholder: t4("knowledgeCards.searchPlaceholder"), value: searchQuery, onChange: (e3) => setSearchQuery2(e3.target.value), disabled: allCards.length === 0 }), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, allCards.length > 0 ? filteredCards.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredCards.map((card) => /* @__PURE__ */ React169__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-
|
|
135218
|
+
return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), t4("knowledgeCards.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("knowledgeCards.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, { className: "space-y-4" }, /* @__PURE__ */ React169__default.createElement(Input, { type: "search", placeholder: t4("knowledgeCards.searchPlaceholder"), value: searchQuery, onChange: (e3) => setSearchQuery2(e3.target.value), disabled: allCards.length === 0 }), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, allCards.length > 0 ? filteredCards.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredCards.map((card) => /* @__PURE__ */ React169__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__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("knowledgeCards.noMatch"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("knowledgeCards.empty")))), renderActionArea())), /* @__PURE__ */ React169__default.createElement(
|
|
135219
135219
|
CardViewerDialog,
|
|
135220
135220
|
{
|
|
135221
135221
|
isOpen: isDialogOpen,
|
|
@@ -135242,9 +135242,9 @@ var StatCard = ({
|
|
|
135242
135242
|
isLoading = false
|
|
135243
135243
|
}) => {
|
|
135244
135244
|
if (isLoading) {
|
|
135245
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-sm font-
|
|
135245
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-sm font-Medium" }, title), /* @__PURE__ */ React169__default.createElement(Skeleton, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement(Skeleton, { className: "h-8 w-3/4 mb-2" }), /* @__PURE__ */ React169__default.createElement(Skeleton, { className: "h-4 w-1/2" })));
|
|
135246
135246
|
}
|
|
135247
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-sm font-
|
|
135247
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2" }, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "text-sm font-Medium" }, title), /* @__PURE__ */ React169__default.createElement("div", { className: "text-muted-foreground" }, icon)), /* @__PURE__ */ React169__default.createElement(CardContent, null, /* @__PURE__ */ React169__default.createElement("div", { className: "text-2xl font-bold" }, value, unit2 && /* @__PURE__ */ React169__default.createElement("span", { className: "text-xl font-Medium text-muted-foreground ml-1" }, unit2)), context && /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs text-muted-foreground" }, context)));
|
|
135248
135248
|
};
|
|
135249
135249
|
|
|
135250
135250
|
// src/react-ui/components/dashboard/PerformanceSnapshot.tsx
|
|
@@ -135343,7 +135343,7 @@ var RoadmapChecklist = () => {
|
|
|
135343
135343
|
}, []);
|
|
135344
135344
|
const handleStartPractice = useCallback((item) => {
|
|
135345
135345
|
const practiceConfig = {
|
|
135346
|
-
loIds: [item.
|
|
135346
|
+
loIds: [item.code],
|
|
135347
135347
|
difficulty: item.suggestedDifficulty,
|
|
135348
135348
|
language: "Vietnamese"
|
|
135349
135349
|
};
|
|
@@ -135353,7 +135353,7 @@ var RoadmapChecklist = () => {
|
|
|
135353
135353
|
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(CalendarCheck, { className: "mr-2 h-5 w-5 text-primary" }), t4("roadmap.title")), /* @__PURE__ */ React169__default.createElement(CardDescription, null, t4("roadmap.description"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, !roadmap || !roadmap.items || roadmap.items.length === 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "text-center text-muted-foreground py-8" }, /* @__PURE__ */ React169__default.createElement("p", null, t4("roadmap.emptyState")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm" }, t4("roadmap.emptyStateSuggestion"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, roadmap.items.map((item, index3) => /* @__PURE__ */ React169__default.createElement(
|
|
135354
135354
|
"div",
|
|
135355
135355
|
{
|
|
135356
|
-
key: `${item.
|
|
135356
|
+
key: `${item.code}-${index3}`,
|
|
135357
135357
|
className: "flex items-center justify-between p-3 border rounded-md bg-background hover:bg-muted/50 transition-colors"
|
|
135358
135358
|
},
|
|
135359
135359
|
/* @__PURE__ */ React169__default.createElement("div", { className: "flex items-start gap-3" }, /* @__PURE__ */ React169__default.createElement(
|
|
@@ -136896,7 +136896,7 @@ function Calendar2({
|
|
|
136896
136896
|
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
|
136897
136897
|
month: "space-y-4",
|
|
136898
136898
|
caption: "flex justify-center pt-1 relative items-center",
|
|
136899
|
-
caption_label: "text-sm font-
|
|
136899
|
+
caption_label: "text-sm font-Medium",
|
|
136900
136900
|
nav: "space-x-1 flex items-center",
|
|
136901
136901
|
nav_button: cn(
|
|
136902
136902
|
buttonVariants({ variant: "outline" }),
|
|
@@ -137018,7 +137018,7 @@ var AnalysisDialog = ({ isOpen, onClose }) => {
|
|
|
137018
137018
|
}
|
|
137019
137019
|
try {
|
|
137020
137020
|
const allAvailableTopics = TopicDataService.getData().map((lo) => ({
|
|
137021
|
-
|
|
137021
|
+
code: lo.code,
|
|
137022
137022
|
subject: lo.subject,
|
|
137023
137023
|
category: lo.category,
|
|
137024
137024
|
topic: lo.topic
|
|
@@ -137471,7 +137471,7 @@ var GeneratedQuizzesCard = () => {
|
|
|
137471
137471
|
onChange: (e3) => setSearchQuery2(e3.target.value),
|
|
137472
137472
|
disabled: uniqueQuizzes.length === 0
|
|
137473
137473
|
}
|
|
137474
|
-
), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm font-
|
|
137474
|
+
), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm font-Medium truncate pr-2", title: quiz.title }, quiz.title), /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", variant: "ghost", onClick: () => handleRetake(quiz) }, /* @__PURE__ */ React169__default.createElement(CirclePlay, { className: "mr-2 h-4 w-4" }), t4("common.retake"))))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.generated.noMatch"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.generated.empty"))))));
|
|
137475
137475
|
};
|
|
137476
137476
|
|
|
137477
137477
|
// src/react-ui/components/app/UploadResourceModal.tsx
|
|
@@ -137551,11 +137551,12 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137551
137551
|
language: i18n.language === "vi" ? "Vietnamese" : "English",
|
|
137552
137552
|
documentContent: textContent,
|
|
137553
137553
|
learningObjectives: learningObjectives.map((lo) => ({
|
|
137554
|
-
|
|
137554
|
+
name: lo.name,
|
|
137555
|
+
code: lo.code,
|
|
137555
137556
|
subject: lo.subject,
|
|
137556
137557
|
category: lo.category,
|
|
137557
137558
|
topic: lo.topic,
|
|
137558
|
-
|
|
137559
|
+
description: lo.description || ""
|
|
137559
137560
|
}))
|
|
137560
137561
|
}, apiKey);
|
|
137561
137562
|
setAnalysisResult(result);
|
|
@@ -137588,12 +137589,12 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137588
137589
|
generatedQuestions = result.generatedQuestions;
|
|
137589
137590
|
} else {
|
|
137590
137591
|
const plan = analysisResult.mappedLOs.map((lo) => {
|
|
137591
|
-
const sourceLO = TopicDataService.getData().find((orig) => orig.
|
|
137592
|
+
const sourceLO = TopicDataService.getData().find((orig) => orig.code === lo.code);
|
|
137592
137593
|
return {
|
|
137593
137594
|
plannedTopic: lo.reasoning,
|
|
137594
137595
|
plannedQuestionType: "multiple_choice",
|
|
137595
137596
|
plannedBloomLevel: "understanding",
|
|
137596
|
-
originalLoId: lo.
|
|
137597
|
+
originalLoId: lo.code,
|
|
137597
137598
|
originalTopic: sourceLO?.topic,
|
|
137598
137599
|
originalCategory: sourceLO?.category,
|
|
137599
137600
|
originalSubject: sourceLO?.subject
|
|
@@ -137638,7 +137639,7 @@ var UploadResourceModal = ({ isOpen, onClose }) => {
|
|
|
137638
137639
|
return /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center justify-center h-48" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-12 w-12 animate-spin text-primary" }), /* @__PURE__ */ React169__default.createElement("p", { className: "mt-4 text-muted-foreground font-semibold" }, stage === "analyzing" && t4("dialogs.uploadResource.analyzing"), stage === "generating" && t4("dialogs.uploadResource.generating")));
|
|
137639
137640
|
case "result":
|
|
137640
137641
|
if (!analysisResult) return null;
|
|
137641
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "py-4 space-y-4" }, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardContent, { className: "p-4" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-2" }, "AI Analysis Complete"), analysisResult.isFreestyleRecommended ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-start gap-3 text-amber-700" }, /* @__PURE__ */ React169__default.createElement(BrainCircuit, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "font-
|
|
137642
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "py-4 space-y-4" }, /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardContent, { className: "p-4" }, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-2" }, "AI Analysis Complete"), analysisResult.isFreestyleRecommended ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-start gap-3 text-amber-700" }, /* @__PURE__ */ React169__default.createElement(BrainCircuit, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "font-Medium" }, t4("dialogs.uploadResource.freestyleRecommended")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm" }, t4("dialogs.uploadResource.freestyleDescription")))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-start gap-3 text-green-700" }, /* @__PURE__ */ React169__default.createElement(CircleCheckBig, { className: "h-5 w-5 mt-1 flex-shrink-0" }), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("p", { className: "font-Medium" }, t4("dialogs.uploadResource.curriculumMatch")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm" }, t4("dialogs.uploadResource.curriculumDescription")), /* @__PURE__ */ React169__default.createElement("p", { className: "text-xs mt-2 font-semibold" }, t4("dialogs.uploadResource.mappedTopics")), /* @__PURE__ */ React169__default.createElement("ul", { className: "list-disc list-inside text-xs" }, analysisResult.mappedLOs.map((lo) => /* @__PURE__ */ React169__default.createElement("li", { key: lo.code }, TopicDataService.getData().find((orig) => orig.code === lo.code)?.topic || lo.code))))))), error && /* @__PURE__ */ React169__default.createElement(Alert, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(CircleAlert, { className: "h-4 w-4" }), /* @__PURE__ */ React169__default.createElement(AlertTitle, null, t4("common.error")), /* @__PURE__ */ React169__default.createElement(AlertDescription, null, error)));
|
|
137642
137643
|
}
|
|
137643
137644
|
};
|
|
137644
137645
|
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-lg" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(FileText, { className: "mr-2 h-5 w-5" }), t4("dialogs.uploadResource.title")), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, t4("dialogs.uploadResource.description"))), renderContent3(), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "outline" }, t4("common.cancel"))), stage === "result" && /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleGenerateQuiz }, /* @__PURE__ */ React169__default.createElement(Sparkles, { className: "mr-2 h-4 w-4" }), t4("dialogs.uploadResource.generateButton")))));
|
|
@@ -137759,7 +137760,7 @@ var FreestyleQuizzesCard = () => {
|
|
|
137759
137760
|
onChange: (e3) => setSearchQuery2(e3.target.value),
|
|
137760
137761
|
disabled: uniqueQuizzes.length === 0
|
|
137761
137762
|
}
|
|
137762
|
-
), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm font-
|
|
137763
|
+
), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "h-[180px] w-full rounded-md border p-2" }, uniqueQuizzes.length > 0 ? filteredQuizzes.length > 0 ? /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-1" }, filteredQuizzes.map((quiz) => /* @__PURE__ */ React169__default.createElement("div", { key: quiz.id, className: "flex items-center justify-between p-2 rounded-md hover:bg-accent" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm font-Medium truncate pr-2", title: quiz.title }, quiz.title), /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", variant: "ghost", onClick: () => handleRetake(quiz) }, /* @__PURE__ */ React169__default.createElement(CirclePlay, { className: "mr-2 h-4 w-4" }), t4("common.retake"))))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.freestyle.noMatch"))) : /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center justify-center h-full" }, /* @__PURE__ */ React169__default.createElement("p", { className: "text-muted-foreground" }, t4("quizLists.freestyle.empty"))))));
|
|
137763
137764
|
};
|
|
137764
137765
|
|
|
137765
137766
|
// src/react-ui/components/common/ClientTranslation.tsx
|
|
@@ -137909,7 +137910,7 @@ var PersonalPracticeDashboard = ({ settingsPath, initialHistory, initialStats, i
|
|
|
137909
137910
|
}
|
|
137910
137911
|
try {
|
|
137911
137912
|
const allAvailableTopics = TopicDataService.getData().map((lo) => ({
|
|
137912
|
-
|
|
137913
|
+
code: lo.code,
|
|
137913
137914
|
subject: lo.subject,
|
|
137914
137915
|
category: lo.category,
|
|
137915
137916
|
topic: lo.topic
|
|
@@ -138403,7 +138404,7 @@ var PracticeModeController = () => {
|
|
|
138403
138404
|
try {
|
|
138404
138405
|
const config3 = JSON.parse(suggestedConfigString);
|
|
138405
138406
|
const allLOs = TopicDataService.getData();
|
|
138406
|
-
const suggestedLOs = allLOs.filter((lo) => config3.loIds?.includes(lo.
|
|
138407
|
+
const suggestedLOs = allLOs.filter((lo) => config3.loIds?.includes(lo.code));
|
|
138407
138408
|
if (suggestedLOs.length > 0) {
|
|
138408
138409
|
setInitialSuggestedLOs(suggestedLOs);
|
|
138409
138410
|
setInitialSuggestedDifficulty(config3.difficulty || "Medium");
|
|
@@ -138446,9 +138447,10 @@ var PracticeModeController = () => {
|
|
|
138446
138447
|
totalQuestions,
|
|
138447
138448
|
numCodingQuestions,
|
|
138448
138449
|
topics: selectedLOs.map((lo) => ({
|
|
138449
|
-
topic: lo.
|
|
138450
|
+
topic: lo.name || lo.code,
|
|
138451
|
+
// FIX: Provide fallback for name
|
|
138450
138452
|
ratio: 100 / selectedLOs.length,
|
|
138451
|
-
originalLoId: lo.
|
|
138453
|
+
originalLoId: lo.code,
|
|
138452
138454
|
originalSubject: lo.subject,
|
|
138453
138455
|
originalCategory: lo.category,
|
|
138454
138456
|
originalTopic: lo.topic
|
|
@@ -138640,7 +138642,7 @@ var SuggestionDialog = ({
|
|
|
138640
138642
|
if (!suggestion) {
|
|
138641
138643
|
return /* @__PURE__ */ React169__default.createElement("div", { className: "flex flex-col items-center justify-center h-64 gap-4" }, /* @__PURE__ */ React169__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."));
|
|
138642
138644
|
}
|
|
138643
|
-
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 bg-muted/50 rounded-lg" }, /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: suggestion.suggestionText })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3" }, "K\u1EBF ho\u1EA1ch Luy\u1EC7n t\u1EADp \u0111\u01B0\u1EE3c G\u1EE3i \xFD:"), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "max-h-[200px] pr-3" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, suggestion.suggestedTopics.map((topic) => /* @__PURE__ */ React169__default.createElement("div", { key: topic.
|
|
138645
|
+
return /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React169__default.createElement("div", { className: "p-4 bg-muted/50 rounded-lg" }, /* @__PURE__ */ React169__default.createElement(MarkdownRenderer, { content: suggestion.suggestionText })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement("h4", { className: "font-semibold mb-3" }, "K\u1EBF ho\u1EA1ch Luy\u1EC7n t\u1EADp \u0111\u01B0\u1EE3c G\u1EE3i \xFD:"), /* @__PURE__ */ React169__default.createElement(ScrollArea2, { className: "max-h-[200px] pr-3" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, suggestion.suggestedTopics.map((topic) => /* @__PURE__ */ React169__default.createElement("div", { key: topic.code, className: "flex items-center justify-between p-3 border rounded-md" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex-1 mr-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2 mb-1" }, topic.reason === "review" ? /* @__PURE__ */ React169__default.createElement(Badge2, { variant: "destructive" }, /* @__PURE__ */ React169__default.createElement(RefreshCw, { className: "h-3 w-3 mr-1.5" }), "\xD4n t\u1EADp") : /* @__PURE__ */ React169__default.createElement(Badge2, { variant: "secondary" }, /* @__PURE__ */ React169__default.createElement(Search, { className: "h-3 w-3 mr-1.5" }), "Kh\xE1m ph\xE1")), /* @__PURE__ */ React169__default.createElement("p", { className: "font-Medium" }, topic.topicName)), /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", onClick: () => onStartSuggestedPractice(topic) }, /* @__PURE__ */ React169__default.createElement(CirclePlay, { className: "h-4 w-4 mr-2" }), "B\u1EAFt \u0111\u1EA7u")))))));
|
|
138644
138646
|
};
|
|
138645
138647
|
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => !open && onClose() }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-lg md:max-w-xl" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, { className: "flex items-center text-2xl" }, /* @__PURE__ */ React169__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__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__default.createElement("div", { className: "py-4" }, renderContent3()), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(DialogClose2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline" }, "\u0110\xF3ng")))));
|
|
138646
138648
|
};
|
|
@@ -138650,12 +138652,96 @@ init_react_shim();
|
|
|
138650
138652
|
|
|
138651
138653
|
// src/react-ui/components/metadata/SubjectManager.tsx
|
|
138652
138654
|
init_react_shim();
|
|
138655
|
+
|
|
138656
|
+
// src/react-ui/components/metadata/MetadataImportControls.tsx
|
|
138657
|
+
init_react_shim();
|
|
138658
|
+
function MetadataImportControls({ metadataName, onImport }) {
|
|
138659
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
138660
|
+
const [jsonString, setJsonString] = useState("");
|
|
138661
|
+
const [isImporting, startImportTransition] = useTransition();
|
|
138662
|
+
const fileInputRef = useRef(null);
|
|
138663
|
+
const { toast: toast2 } = useToast();
|
|
138664
|
+
const processAndImportRecords = async (records, importSource) => {
|
|
138665
|
+
if (records.length === 0) {
|
|
138666
|
+
toast2({ title: "No Data", description: `The selected ${importSource === "file" ? "file" : "JSON string"} contains no data to import.`, variant: "destructive" });
|
|
138667
|
+
return;
|
|
138668
|
+
}
|
|
138669
|
+
await onImport(records);
|
|
138670
|
+
setIsOpen(false);
|
|
138671
|
+
setJsonString("");
|
|
138672
|
+
};
|
|
138673
|
+
const handleFileSelected = (event) => {
|
|
138674
|
+
const file = event.target.files?.[0];
|
|
138675
|
+
if (!file) return;
|
|
138676
|
+
if (file.type !== "application/json" && !file.name.endsWith(".csv")) {
|
|
138677
|
+
toast2({ title: "Invalid File Type", description: "Please select a JSON or CSV file.", variant: "destructive" });
|
|
138678
|
+
if (event.target) event.target.value = "";
|
|
138679
|
+
return;
|
|
138680
|
+
}
|
|
138681
|
+
startImportTransition(async () => {
|
|
138682
|
+
try {
|
|
138683
|
+
const fileContent = await file.text();
|
|
138684
|
+
let records = [];
|
|
138685
|
+
if (file.type === "application/json") {
|
|
138686
|
+
records = JSON.parse(fileContent);
|
|
138687
|
+
if (!Array.isArray(records)) throw new Error("JSON file must contain an array of objects.");
|
|
138688
|
+
} else if (file.name.endsWith(".csv")) {
|
|
138689
|
+
const lines = fileContent.split(/\r\n|\n/).filter((line) => line.trim() !== "");
|
|
138690
|
+
if (lines.length < 2) throw new Error("CSV must have a header and at least one data row.");
|
|
138691
|
+
const headers = lines[0].split(",").map((h3) => h3.trim());
|
|
138692
|
+
records = lines.slice(1).map((line) => {
|
|
138693
|
+
const values = line.split(",").map((v) => v.trim());
|
|
138694
|
+
const record = {};
|
|
138695
|
+
headers.forEach((header, index3) => {
|
|
138696
|
+
record[header] = values[index3];
|
|
138697
|
+
});
|
|
138698
|
+
return record;
|
|
138699
|
+
});
|
|
138700
|
+
}
|
|
138701
|
+
await processAndImportRecords(records, "file");
|
|
138702
|
+
} catch (err) {
|
|
138703
|
+
toast2({ title: "Import Error", description: `Failed to process file: ${err.message}`, variant: "destructive" });
|
|
138704
|
+
}
|
|
138705
|
+
});
|
|
138706
|
+
if (event.target) event.target.value = "";
|
|
138707
|
+
};
|
|
138708
|
+
const handleJsonStringImport = () => {
|
|
138709
|
+
if (!jsonString.trim()) {
|
|
138710
|
+
toast2({ title: "Missing Data", description: "JSON string cannot be empty.", variant: "destructive" });
|
|
138711
|
+
return;
|
|
138712
|
+
}
|
|
138713
|
+
startImportTransition(async () => {
|
|
138714
|
+
try {
|
|
138715
|
+
const records = JSON.parse(jsonString);
|
|
138716
|
+
if (!Array.isArray(records)) throw new Error("JSON string must represent an array of objects.");
|
|
138717
|
+
await processAndImportRecords(records, "text");
|
|
138718
|
+
} catch (err) {
|
|
138719
|
+
toast2({ title: "Import Error", description: `Failed to process JSON string: ${err.message}`, variant: "destructive" });
|
|
138720
|
+
}
|
|
138721
|
+
});
|
|
138722
|
+
};
|
|
138723
|
+
return /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: setIsOpen }, /* @__PURE__ */ React169__default.createElement(DialogTrigger2, { asChild: true }, /* @__PURE__ */ React169__default.createElement(Button, { size: "sm", variant: "outline" }, /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), " Import ", metadataName)), /* @__PURE__ */ React169__default.createElement(DialogContent2, null, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, "Bulk Import ", metadataName), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, "Import multiple records from a file or by pasting JSON data.")), /* @__PURE__ */ React169__default.createElement(Tabs2, { defaultValue: "file" }, /* @__PURE__ */ React169__default.createElement(TabsList2, { className: "grid w-full grid-cols-2" }, /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "file" }, /* @__PURE__ */ React169__default.createElement(FileJson, { className: "mr-2 h-4 w-4" }), " From File"), /* @__PURE__ */ React169__default.createElement(TabsTrigger2, { value: "text" }, /* @__PURE__ */ React169__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), " From Text")), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "file", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__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__default.createElement(Button, { variant: "outline", onClick: () => fileInputRef.current?.click(), disabled: isImporting }, isImporting ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(Upload, { className: "mr-2 h-4 w-4" }), isImporting ? "Importing..." : "Select File"), /* @__PURE__ */ React169__default.createElement("input", { type: "file", ref: fileInputRef, onChange: handleFileSelected, accept: ".json,.csv", className: "hidden" }))), /* @__PURE__ */ React169__default.createElement(TabsContent2, { value: "text", className: "pt-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-3" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "jsonInput" }, "JSON Data (Array of Objects)"), /* @__PURE__ */ React169__default.createElement(
|
|
138724
|
+
Textarea,
|
|
138725
|
+
{
|
|
138726
|
+
id: "jsonInput",
|
|
138727
|
+
value: jsonString,
|
|
138728
|
+
onChange: (e3) => setJsonString(e3.target.value),
|
|
138729
|
+
placeholder: '[{"code": "SUB1", "name": "Subject 1"}, ...]',
|
|
138730
|
+
rows: 8,
|
|
138731
|
+
className: "font-mono text-xs",
|
|
138732
|
+
disabled: isImporting
|
|
138733
|
+
}
|
|
138734
|
+
), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleJsonStringImport, disabled: isImporting || !jsonString.trim() }, isImporting ? /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }) : /* @__PURE__ */ React169__default.createElement(ClipboardPaste, { className: "mr-2 h-4 w-4" }), "Import from Text"))))));
|
|
138735
|
+
}
|
|
138736
|
+
|
|
138737
|
+
// src/react-ui/components/metadata/SubjectManager.tsx
|
|
138653
138738
|
function SubjectManager({
|
|
138654
138739
|
initialData,
|
|
138655
138740
|
isLoading: isLoadingProp,
|
|
138656
138741
|
onAdd,
|
|
138657
138742
|
onUpdate,
|
|
138658
|
-
onDelete
|
|
138743
|
+
onDelete,
|
|
138744
|
+
onBulkAdd
|
|
138659
138745
|
}) {
|
|
138660
138746
|
const [subjects, setSubjects] = useState([]);
|
|
138661
138747
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -138753,12 +138839,37 @@ function SubjectManager({
|
|
|
138753
138839
|
}
|
|
138754
138840
|
});
|
|
138755
138841
|
};
|
|
138756
|
-
|
|
138842
|
+
const handleImport = async (records) => {
|
|
138843
|
+
if (!onBulkAdd) return;
|
|
138844
|
+
const validatedRecords = records.map((rec) => {
|
|
138845
|
+
if (typeof rec.code === "string" && typeof rec.name === "string") {
|
|
138846
|
+
return { code: rec.code, name: rec.name };
|
|
138847
|
+
}
|
|
138848
|
+
return null;
|
|
138849
|
+
}).filter((rec) => rec !== null);
|
|
138850
|
+
if (validatedRecords.length !== records.length) {
|
|
138851
|
+
toast2({
|
|
138852
|
+
title: "Import Warning",
|
|
138853
|
+
description: "Some records had invalid or missing 'code' or 'name' fields and were ignored.",
|
|
138854
|
+
variant: "destructive"
|
|
138855
|
+
});
|
|
138856
|
+
}
|
|
138857
|
+
if (validatedRecords.length > 0) {
|
|
138858
|
+
await onBulkAdd(validatedRecords);
|
|
138859
|
+
}
|
|
138860
|
+
};
|
|
138861
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(BookCopy, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Subjects"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(
|
|
138862
|
+
MetadataImportControls,
|
|
138863
|
+
{
|
|
138864
|
+
metadataName: "Subjects",
|
|
138865
|
+
onImport: handleImport
|
|
138866
|
+
}
|
|
138867
|
+
), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Subject")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : subjects.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No subjects found. Add one to get started!") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Created At"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Updated At"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, subjects.map((subject) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: subject.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, subject.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, subject.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, format(new Date(subject.createdAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React169__default.createElement(TableCell, null, format(new Date(subject.updatedAt), "dd/MM/yyyy HH:mm")), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(subject), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(subject), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentSubject ? "Edit Subject" : "Add New Subject"), /* @__PURE__ */ React169__default.createElement(DialogDescription2, null, currentSubject ? "Update the details of the subject." : "Enter details for the new subject.")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "subjectCode", value: subjectCode, onChange: (e3) => setSubjectCode(e3.target.value.toUpperCase()), placeholder: "e.g., MATH", disabled: !!currentSubject })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectName" }, "Subject Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "subjectName", value: subjectName, onChange: (e3) => setSubjectName(e3.target.value), placeholder: "e.g., Mathematics" }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !subjectName.trim() || !subjectCode.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This action cannot be undone. This will permanently delete the subject "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
138757
138868
|
}
|
|
138758
138869
|
|
|
138759
138870
|
// src/react-ui/components/metadata/GradeLevelManager.tsx
|
|
138760
138871
|
init_react_shim();
|
|
138761
|
-
function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
138872
|
+
function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
138762
138873
|
const [items, setItems] = useState([]);
|
|
138763
138874
|
const [isLoading, setIsLoading] = useState(true);
|
|
138764
138875
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
@@ -138855,7 +138966,26 @@ function GradeLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpd
|
|
|
138855
138966
|
}
|
|
138856
138967
|
});
|
|
138857
138968
|
};
|
|
138858
|
-
|
|
138969
|
+
const handleImport = async (records) => {
|
|
138970
|
+
if (!onBulkAdd) return;
|
|
138971
|
+
const validatedRecords = records.map((rec) => {
|
|
138972
|
+
if (typeof rec.code === "string" && typeof rec.name === "string") {
|
|
138973
|
+
return { code: rec.code, name: rec.name };
|
|
138974
|
+
}
|
|
138975
|
+
return null;
|
|
138976
|
+
}).filter((rec) => rec !== null);
|
|
138977
|
+
if (validatedRecords.length !== records.length) {
|
|
138978
|
+
toast2({
|
|
138979
|
+
title: "Import Warning",
|
|
138980
|
+
description: "Some records had invalid or missing 'code' or 'name' fields and were ignored.",
|
|
138981
|
+
variant: "destructive"
|
|
138982
|
+
});
|
|
138983
|
+
}
|
|
138984
|
+
if (validatedRecords.length > 0) {
|
|
138985
|
+
await onBulkAdd(validatedRecords);
|
|
138986
|
+
}
|
|
138987
|
+
};
|
|
138988
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Award, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Grade Levels"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Grade Levels", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Grade Level")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No grade levels found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Grade Level" : "Add New Grade Level")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., G9", disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Grade 9" }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
138859
138989
|
}
|
|
138860
138990
|
|
|
138861
138991
|
// src/react-ui/components/metadata/TopicManager.tsx
|
|
@@ -138866,7 +138996,8 @@ function TopicManager({
|
|
|
138866
138996
|
isLoading: isLoadingProp,
|
|
138867
138997
|
onAdd,
|
|
138868
138998
|
onUpdate,
|
|
138869
|
-
onDelete
|
|
138999
|
+
onDelete,
|
|
139000
|
+
onBulkAdd
|
|
138870
139001
|
}) {
|
|
138871
139002
|
const [topics, setTopics] = useState([]);
|
|
138872
139003
|
const [subjects, setSubjects] = useState([]);
|
|
@@ -138970,15 +139101,34 @@ function TopicManager({
|
|
|
138970
139101
|
}
|
|
138971
139102
|
});
|
|
138972
139103
|
};
|
|
139104
|
+
const handleImport = async (records) => {
|
|
139105
|
+
if (!onBulkAdd) return;
|
|
139106
|
+
const validatedRecords = records.map((rec) => {
|
|
139107
|
+
if (typeof rec.code === "string" && typeof rec.name === "string" && typeof rec.subjectCode === "string") {
|
|
139108
|
+
return { code: rec.code, name: rec.name, subjectCode: rec.subjectCode };
|
|
139109
|
+
}
|
|
139110
|
+
return null;
|
|
139111
|
+
}).filter((rec) => rec !== null);
|
|
139112
|
+
if (validatedRecords.length !== records.length) {
|
|
139113
|
+
toast2({
|
|
139114
|
+
title: "Import Warning",
|
|
139115
|
+
description: "Some records had invalid or missing 'code', 'name', or 'subjectCode' fields and were ignored.",
|
|
139116
|
+
variant: "destructive"
|
|
139117
|
+
});
|
|
139118
|
+
}
|
|
139119
|
+
if (validatedRecords.length > 0) {
|
|
139120
|
+
await onBulkAdd(validatedRecords);
|
|
139121
|
+
}
|
|
139122
|
+
};
|
|
138973
139123
|
const getSubjectName = (subjectCode) => {
|
|
138974
139124
|
return subjects.find((s4) => s4.code === subjectCode)?.name || "N/A";
|
|
138975
139125
|
};
|
|
138976
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Tag, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Topics"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm", disabled: subjects.length === 0 }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic")), subjects.length === 0 && !isLoading && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-destructive" }, "Please add subjects before adding topics.")), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : topics.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No topics found. Add one to get started!") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, topics.map((topic) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: topic.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, topic.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-
|
|
139126
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Tag, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Topics"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Topics", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm", disabled: subjects.length === 0 }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Topic"))), subjects.length === 0 && !isLoading && /* @__PURE__ */ React169__default.createElement("p", { className: "text-sm text-destructive" }, "Please add subjects before adding topics.")), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : topics.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No topics found. Add one to get started!") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, topics.map((topic) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: topic.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, topic.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, topic.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, getSubjectName(topic.subjectCode), " (", topic.subjectCode, ")"), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(topic), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(topic), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Topic" : "Add New Topic")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Topic Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., ALG-BASICS", disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Topic Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Algebra Basics" })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject"), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedSubjectCode, onValueChange: setSelectedSubjectCode, disabled: subjects.length === 0 }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, { id: "subjectCode" }, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select a subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, subjects.map((subject) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: subject.code, value: subject.code }, subject.name, " (", subject.code, ")")))))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() || !selectedSubjectCode }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete topic "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
138977
139127
|
}
|
|
138978
139128
|
|
|
138979
139129
|
// src/react-ui/components/metadata/CategoryManager.tsx
|
|
138980
139130
|
init_react_shim();
|
|
138981
|
-
function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139131
|
+
function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
138982
139132
|
const [items, setItems] = useState([]);
|
|
138983
139133
|
const [isLoading, setIsLoading] = useState(true);
|
|
138984
139134
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
@@ -139078,12 +139228,37 @@ function CategoryManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdat
|
|
|
139078
139228
|
}
|
|
139079
139229
|
});
|
|
139080
139230
|
};
|
|
139081
|
-
|
|
139231
|
+
const handleImport = async (records) => {
|
|
139232
|
+
if (!onBulkAdd) return;
|
|
139233
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139234
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139235
|
+
acc.valid.push({
|
|
139236
|
+
code: rec.code,
|
|
139237
|
+
name: rec.name,
|
|
139238
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139239
|
+
});
|
|
139240
|
+
} else {
|
|
139241
|
+
acc.invalidCount++;
|
|
139242
|
+
}
|
|
139243
|
+
return acc;
|
|
139244
|
+
}, { valid: [], invalidCount: 0 });
|
|
139245
|
+
if (validationResult.invalidCount > 0) {
|
|
139246
|
+
toast2({
|
|
139247
|
+
title: "Import Warning",
|
|
139248
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139249
|
+
variant: "destructive"
|
|
139250
|
+
});
|
|
139251
|
+
}
|
|
139252
|
+
if (validationResult.valid.length > 0) {
|
|
139253
|
+
await onBulkAdd(validationResult.valid);
|
|
139254
|
+
}
|
|
139255
|
+
};
|
|
139256
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Layers, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Categories"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Categories", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Category")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No categories found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Category" : "Add New Category")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., CORE_CONCEPT", disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Core Concept" })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Fundamental ideas within a subject." }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemName.trim() || !itemCode.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139082
139257
|
}
|
|
139083
139258
|
|
|
139084
139259
|
// src/react-ui/components/metadata/BloomLevelManager.tsx
|
|
139085
139260
|
init_react_shim();
|
|
139086
|
-
function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139261
|
+
function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139087
139262
|
const [items, setItems] = useState([]);
|
|
139088
139263
|
const [isLoading, setIsLoading] = useState(true);
|
|
139089
139264
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
@@ -139183,12 +139358,37 @@ function BloomLevelManager({ initialData, isLoading: isLoadingProp, onAdd, onUpd
|
|
|
139183
139358
|
}
|
|
139184
139359
|
});
|
|
139185
139360
|
};
|
|
139186
|
-
|
|
139361
|
+
const handleImport = async (records) => {
|
|
139362
|
+
if (!onBulkAdd) return;
|
|
139363
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139364
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139365
|
+
acc.valid.push({
|
|
139366
|
+
code: rec.code,
|
|
139367
|
+
name: rec.name,
|
|
139368
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139369
|
+
});
|
|
139370
|
+
} else {
|
|
139371
|
+
acc.invalidCount++;
|
|
139372
|
+
}
|
|
139373
|
+
return acc;
|
|
139374
|
+
}, { valid: [], invalidCount: 0 });
|
|
139375
|
+
if (validationResult.invalidCount > 0) {
|
|
139376
|
+
toast2({
|
|
139377
|
+
title: "Import Warning",
|
|
139378
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139379
|
+
variant: "destructive"
|
|
139380
|
+
});
|
|
139381
|
+
}
|
|
139382
|
+
if (validationResult.valid.length > 0) {
|
|
139383
|
+
await onBulkAdd(validationResult.valid);
|
|
139384
|
+
}
|
|
139385
|
+
};
|
|
139386
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Brain, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Bloom's Levels"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Bloom's Levels", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Bloom's Level")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Bloom's Levels found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Bloom's Level" : "Add New Bloom's Level")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., REMEMBER", disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Remembering" })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Recall facts and basic concepts." }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139187
139387
|
}
|
|
139188
139388
|
|
|
139189
139389
|
// src/react-ui/components/metadata/QuestionTypeManager.tsx
|
|
139190
139390
|
init_react_shim();
|
|
139191
|
-
function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139391
|
+
function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139192
139392
|
const [items, setItems] = useState([]);
|
|
139193
139393
|
const [isLoading, setIsLoading] = useState(true);
|
|
139194
139394
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
@@ -139258,6 +139458,31 @@ function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onU
|
|
|
139258
139458
|
}
|
|
139259
139459
|
});
|
|
139260
139460
|
};
|
|
139461
|
+
const handleImport = async (records) => {
|
|
139462
|
+
if (!onBulkAdd) return;
|
|
139463
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139464
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139465
|
+
acc.valid.push({
|
|
139466
|
+
code: rec.code,
|
|
139467
|
+
name: rec.name,
|
|
139468
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139469
|
+
});
|
|
139470
|
+
} else {
|
|
139471
|
+
acc.invalidCount++;
|
|
139472
|
+
}
|
|
139473
|
+
return acc;
|
|
139474
|
+
}, { valid: [], invalidCount: 0 });
|
|
139475
|
+
if (validationResult.invalidCount > 0) {
|
|
139476
|
+
toast2({
|
|
139477
|
+
title: "Import Warning",
|
|
139478
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139479
|
+
variant: "destructive"
|
|
139480
|
+
});
|
|
139481
|
+
}
|
|
139482
|
+
if (validationResult.valid.length > 0) {
|
|
139483
|
+
await onBulkAdd(validationResult.valid);
|
|
139484
|
+
}
|
|
139485
|
+
};
|
|
139261
139486
|
const handleSubmit = () => {
|
|
139262
139487
|
if (!itemName.trim() || !itemCode.trim()) {
|
|
139263
139488
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
@@ -139288,22 +139513,19 @@ function QuestionTypeManager({ initialData, isLoading: isLoadingProp, onAdd, onU
|
|
|
139288
139513
|
}
|
|
139289
139514
|
});
|
|
139290
139515
|
};
|
|
139291
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(CircleHelp, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Question Types"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Question Type"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Question Types found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-
|
|
139516
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(CircleHelp, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Question Types"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Question Types", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Question Type")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Question Types found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Question Type" : "Add New Question Type")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Multiple Choice" })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__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__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139292
139517
|
}
|
|
139293
139518
|
|
|
139294
139519
|
// src/react-ui/components/metadata/LearningObjectiveManager.tsx
|
|
139295
139520
|
init_react_shim();
|
|
139296
|
-
function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139521
|
+
function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139297
139522
|
const [items, setItems] = useState([]);
|
|
139298
139523
|
const [subjects, setSubjects] = useState([]);
|
|
139299
139524
|
const [isLoading, setIsLoading] = useState(true);
|
|
139300
139525
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
139301
139526
|
const [isAlertOpen, setIsAlertOpen] = useState(false);
|
|
139302
139527
|
const [currentItem, setCurrentItem] = useState(null);
|
|
139303
|
-
const [
|
|
139304
|
-
const [itemCode, setItemCode] = useState("");
|
|
139305
|
-
const [itemDescription, setItemDescription] = useState("");
|
|
139306
|
-
const [selectedSubjectCode, setSelectedSubjectCode] = useState(void 0);
|
|
139528
|
+
const [formState, setFormState] = useState({});
|
|
139307
139529
|
const [itemToDelete, setItemToDelete] = useState(null);
|
|
139308
139530
|
const [isPending, startTransition] = useTransition();
|
|
139309
139531
|
const { toast: toast2 } = useToast();
|
|
@@ -139330,20 +139552,17 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139330
139552
|
refreshData();
|
|
139331
139553
|
}
|
|
139332
139554
|
}, [isControlled, initialData, subjectsProp, isLoadingProp]);
|
|
139555
|
+
const handleFormChange = (field, value) => {
|
|
139556
|
+
setFormState((prev) => ({ ...prev, [field]: value }));
|
|
139557
|
+
};
|
|
139333
139558
|
const handleAddItem = () => {
|
|
139334
139559
|
setCurrentItem(null);
|
|
139335
|
-
|
|
139336
|
-
setItemCode("");
|
|
139337
|
-
setItemDescription("");
|
|
139338
|
-
setSelectedSubjectCode(subjects.length > 0 ? subjects[0].code : void 0);
|
|
139560
|
+
setFormState({ subjectCode: subjects.length > 0 ? subjects[0].code : "" });
|
|
139339
139561
|
setIsDialogOpen(true);
|
|
139340
139562
|
};
|
|
139341
139563
|
const handleEditItem = (item) => {
|
|
139342
139564
|
setCurrentItem(item);
|
|
139343
|
-
|
|
139344
|
-
setItemCode(item.code);
|
|
139345
|
-
setItemDescription(item.description || "");
|
|
139346
|
-
setSelectedSubjectCode(item.subjectCode);
|
|
139565
|
+
setFormState(item);
|
|
139347
139566
|
setIsDialogOpen(true);
|
|
139348
139567
|
};
|
|
139349
139568
|
const handleDeleteItem = (item) => {
|
|
@@ -139370,7 +139589,7 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139370
139589
|
});
|
|
139371
139590
|
};
|
|
139372
139591
|
const handleSubmit = () => {
|
|
139373
|
-
if (!
|
|
139592
|
+
if (!formState.name?.trim() || !formState.code?.trim()) {
|
|
139374
139593
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
139375
139594
|
return;
|
|
139376
139595
|
}
|
|
@@ -139378,17 +139597,17 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139378
139597
|
try {
|
|
139379
139598
|
if (currentItem) {
|
|
139380
139599
|
if (isControlled && onUpdate) {
|
|
139381
|
-
await onUpdate({
|
|
139600
|
+
await onUpdate({ ...currentItem, ...formState });
|
|
139382
139601
|
} else {
|
|
139383
|
-
MetadataService.updateLearningObjective(currentItem.id,
|
|
139602
|
+
MetadataService.updateLearningObjective(currentItem.id, formState);
|
|
139384
139603
|
refreshData();
|
|
139385
139604
|
}
|
|
139386
139605
|
toast2({ title: "Success", description: "Learning Objective updated." });
|
|
139387
139606
|
} else {
|
|
139388
139607
|
if (isControlled && onAdd) {
|
|
139389
|
-
await onAdd(
|
|
139608
|
+
await onAdd(formState);
|
|
139390
139609
|
} else {
|
|
139391
|
-
MetadataService.addLearningObjective(
|
|
139610
|
+
MetadataService.addLearningObjective(formState);
|
|
139392
139611
|
refreshData();
|
|
139393
139612
|
}
|
|
139394
139613
|
toast2({ title: "Success", description: "Learning Objective added." });
|
|
@@ -139399,16 +139618,51 @@ function LearningObjectiveManager({ initialData, subjects: subjectsProp, isLoadi
|
|
|
139399
139618
|
}
|
|
139400
139619
|
});
|
|
139401
139620
|
};
|
|
139402
|
-
const
|
|
139403
|
-
if (!
|
|
139404
|
-
|
|
139621
|
+
const handleImport = async (records) => {
|
|
139622
|
+
if (!onBulkAdd) return;
|
|
139623
|
+
const parseStringToArray = (input) => {
|
|
139624
|
+
if (Array.isArray(input)) return input;
|
|
139625
|
+
if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
|
|
139626
|
+
return [];
|
|
139627
|
+
};
|
|
139628
|
+
const validatedRecords = records.reduce((acc, rec) => {
|
|
139629
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139630
|
+
acc.push({
|
|
139631
|
+
code: rec.code,
|
|
139632
|
+
name: rec.name,
|
|
139633
|
+
description: rec.description,
|
|
139634
|
+
subject: rec.subject,
|
|
139635
|
+
subjectCode: rec.subjectCode,
|
|
139636
|
+
category: rec.category,
|
|
139637
|
+
categoryCode: rec.categoryCode,
|
|
139638
|
+
topic: rec.topic,
|
|
139639
|
+
topicCode: rec.topicCode,
|
|
139640
|
+
grade: rec.grade,
|
|
139641
|
+
gradeCode: rec.gradeCode,
|
|
139642
|
+
keywords: parseStringToArray(rec.keywords),
|
|
139643
|
+
stemElements: parseStringToArray(rec.stemElements),
|
|
139644
|
+
bloomLevelsGuideline: parseStringToArray(rec.bloomLevelsGuideline)
|
|
139645
|
+
});
|
|
139646
|
+
}
|
|
139647
|
+
return acc;
|
|
139648
|
+
}, []);
|
|
139649
|
+
if (validatedRecords.length !== records.length) {
|
|
139650
|
+
toast2({
|
|
139651
|
+
title: "Import Warning",
|
|
139652
|
+
description: `${records.length - validatedRecords.length} records had invalid or missing required fields ('code', 'name') and were ignored.`,
|
|
139653
|
+
variant: "destructive"
|
|
139654
|
+
});
|
|
139655
|
+
}
|
|
139656
|
+
if (validatedRecords.length > 0) {
|
|
139657
|
+
await onBulkAdd(validatedRecords);
|
|
139658
|
+
}
|
|
139405
139659
|
};
|
|
139406
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "
|
|
139660
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Lightbulb, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Learning Objectives"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Learning Objectives", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Learning Objective")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Learning Objectives found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Subject"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Topic"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.subject || item.subjectCode), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.topic || item.topicCode), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Learning Objective" : "Add New Learning Objective")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "code", value: formState.code || "", onChange: (e3) => handleFormChange("code", e3.target.value.toUpperCase()), disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "name" }, "Name (Description)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "name", value: formState.name || "", onChange: (e3) => handleFormChange("name", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "subjectCode" }, "Subject Code"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.subjectCode || "", onValueChange: (value) => handleFormChange("subjectCode", value) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, { placeholder: "Select a subject" })), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, subjects.map((subject) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: subject.id, value: subject.code }, subject.name))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "topicCode" }, "Topic Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "topicCode", value: formState.topicCode || "", onChange: (e3) => handleFormChange("topicCode", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "categoryCode" }, "Category Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "categoryCode", value: formState.categoryCode || "", onChange: (e3) => handleFormChange("categoryCode", e3.target.value) })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "gradeCode" }, "Grade Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "gradeCode", value: formState.gradeCode || "", onChange: (e3) => handleFormChange("gradeCode", e3.target.value) }))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "keywords" }, "Keywords (comma-separated)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "keywords", value: formState.keywords?.join(", ") || "", onChange: (e3) => handleFormChange("keywords", e3.target.value.split(",").map((s4) => s4.trim())) }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !formState.name?.trim() || !formState.code?.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139407
139661
|
}
|
|
139408
139662
|
|
|
139409
139663
|
// src/react-ui/components/metadata/ContextManager.tsx
|
|
139410
139664
|
init_react_shim();
|
|
139411
|
-
function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete }) {
|
|
139665
|
+
function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate, onDelete, onBulkAdd }) {
|
|
139412
139666
|
const [items, setItems] = useState([]);
|
|
139413
139667
|
const [isLoading, setIsLoading] = useState(true);
|
|
139414
139668
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
@@ -139478,6 +139732,31 @@ function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate
|
|
|
139478
139732
|
}
|
|
139479
139733
|
});
|
|
139480
139734
|
};
|
|
139735
|
+
const handleImport = async (records) => {
|
|
139736
|
+
if (!onBulkAdd) return;
|
|
139737
|
+
const validationResult = records.reduce((acc, rec) => {
|
|
139738
|
+
if (typeof rec.code === "string" && rec.code.trim() && typeof rec.name === "string" && rec.name.trim()) {
|
|
139739
|
+
acc.valid.push({
|
|
139740
|
+
code: rec.code,
|
|
139741
|
+
name: rec.name,
|
|
139742
|
+
description: typeof rec.description === "string" ? rec.description : void 0
|
|
139743
|
+
});
|
|
139744
|
+
} else {
|
|
139745
|
+
acc.invalidCount++;
|
|
139746
|
+
}
|
|
139747
|
+
return acc;
|
|
139748
|
+
}, { valid: [], invalidCount: 0 });
|
|
139749
|
+
if (validationResult.invalidCount > 0) {
|
|
139750
|
+
toast2({
|
|
139751
|
+
title: "Import Warning",
|
|
139752
|
+
description: `${validationResult.invalidCount} records had invalid or missing 'code' or 'name' fields and were ignored.`,
|
|
139753
|
+
variant: "destructive"
|
|
139754
|
+
});
|
|
139755
|
+
}
|
|
139756
|
+
if (validationResult.valid.length > 0) {
|
|
139757
|
+
await onBulkAdd(validationResult.valid);
|
|
139758
|
+
}
|
|
139759
|
+
};
|
|
139481
139760
|
const handleSubmit = () => {
|
|
139482
139761
|
if (!itemName.trim() || !itemCode.trim()) {
|
|
139483
139762
|
toast2({ title: "Validation Error", description: "Code and Name are required.", variant: "destructive" });
|
|
@@ -139508,13 +139787,13 @@ function ContextManager({ initialData, isLoading: isLoadingProp, onAdd, onUpdate
|
|
|
139508
139787
|
}
|
|
139509
139788
|
});
|
|
139510
139789
|
};
|
|
139511
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(ScanText, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Contexts"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Context"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Contexts found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-
|
|
139790
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(ScanText, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Contexts"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Categories", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Context")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Contexts found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Code"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Description"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-mono text-xs" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.description), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-md" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Context" : "Add New Context")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemCode" }, "Code"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemCode", value: itemCode, onChange: (e3) => setItemCode(e3.target.value.toUpperCase()), placeholder: "e.g., HIST_INQ", disabled: !!currentItem })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemName" }, "Name"), /* @__PURE__ */ React169__default.createElement(Input, { id: "itemName", value: itemName, onChange: (e3) => setItemName(e3.target.value), placeholder: "e.g., Historical Inquiry" })), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "itemDescription" }, "Description (Optional)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "itemDescription", value: itemDescription, onChange: (e3) => setItemDescription(e3.target.value), placeholder: "e.g., Analyzing primary and secondary sources." }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending || !itemCode.trim() || !itemName.trim() }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.name, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139512
139791
|
}
|
|
139513
139792
|
|
|
139514
139793
|
// src/react-ui/components/metadata/ApproachManager.tsx
|
|
139515
139794
|
init_react_shim();
|
|
139516
139795
|
var knowledgeDimensions = ["Factual", "Conceptual", "Procedural"];
|
|
139517
|
-
var
|
|
139796
|
+
var standardDifficulties = ["Easy", "Medium", "Hard"];
|
|
139518
139797
|
function ApproachManager({
|
|
139519
139798
|
initialData,
|
|
139520
139799
|
bloomLevels: bloomLevelsProp,
|
|
@@ -139522,7 +139801,8 @@ function ApproachManager({
|
|
|
139522
139801
|
isLoading: isLoadingProp,
|
|
139523
139802
|
onAdd,
|
|
139524
139803
|
onUpdate,
|
|
139525
|
-
onDelete
|
|
139804
|
+
onDelete,
|
|
139805
|
+
onBulkAdd
|
|
139526
139806
|
}) {
|
|
139527
139807
|
const [items, setItems] = useState([]);
|
|
139528
139808
|
const [bloomLevels, setBloomLevels] = useState([]);
|
|
@@ -139563,7 +139843,7 @@ function ApproachManager({
|
|
|
139563
139843
|
const resetForm = () => {
|
|
139564
139844
|
setFormState({
|
|
139565
139845
|
knowledgeDimension: "Factual",
|
|
139566
|
-
|
|
139846
|
+
difficulty: ["Medium"],
|
|
139567
139847
|
bloomLevelCode: bloomLevels.length > 0 ? bloomLevels[0].code : "",
|
|
139568
139848
|
iSpringQuizType: questionTypes.length > 0 ? questionTypes[0].code : "multiple_choice"
|
|
139569
139849
|
});
|
|
@@ -139575,18 +139855,7 @@ function ApproachManager({
|
|
|
139575
139855
|
};
|
|
139576
139856
|
const handleEditItem = (item) => {
|
|
139577
139857
|
setCurrentItem(item);
|
|
139578
|
-
setFormState(
|
|
139579
|
-
code: item.code,
|
|
139580
|
-
verbEn: item.verbEn,
|
|
139581
|
-
verbVi: item.verbVi,
|
|
139582
|
-
bloomLevelCode: item.bloomLevelCode,
|
|
139583
|
-
knowledgeDimension: item.knowledgeDimension,
|
|
139584
|
-
iSpringQuizType: item.iSpringQuizType,
|
|
139585
|
-
rawDifficulty: item.rawDifficulty,
|
|
139586
|
-
suggestContext: item.suggestContext,
|
|
139587
|
-
exampleEn: item.exampleEn,
|
|
139588
|
-
exampleVi: item.exampleVi
|
|
139589
|
-
});
|
|
139858
|
+
setFormState(item);
|
|
139590
139859
|
setIsDialogOpen(true);
|
|
139591
139860
|
};
|
|
139592
139861
|
const handleDeleteItem = (item) => {
|
|
@@ -139613,9 +139882,9 @@ function ApproachManager({
|
|
|
139613
139882
|
});
|
|
139614
139883
|
};
|
|
139615
139884
|
const handleSubmit = () => {
|
|
139616
|
-
const { code: code4, verbEn, verbVi, bloomLevelCode, iSpringQuizType, knowledgeDimension,
|
|
139617
|
-
if (!code4?.trim() || !verbEn?.trim() || !verbVi?.trim() || !bloomLevelCode || !iSpringQuizType || !knowledgeDimension || !
|
|
139618
|
-
toast2({ title: "Validation Error", description: "All fields except examples and context are required.", variant: "destructive" });
|
|
139885
|
+
const { code: code4, name: name3, verbEn, verbVi, bloomLevelCode, iSpringQuizType, knowledgeDimension, difficulty } = formState;
|
|
139886
|
+
if (!code4?.trim() || !name3?.trim() || !verbEn?.trim() || !verbVi?.trim() || !bloomLevelCode || !iSpringQuizType || !knowledgeDimension || !difficulty || difficulty.length === 0) {
|
|
139887
|
+
toast2({ title: "Validation Error", description: "All fields except examples and context are required, and at least one difficulty must be selected.", variant: "destructive" });
|
|
139619
139888
|
return;
|
|
139620
139889
|
}
|
|
139621
139890
|
startTransition(async () => {
|
|
@@ -139643,7 +139912,47 @@ function ApproachManager({
|
|
|
139643
139912
|
}
|
|
139644
139913
|
});
|
|
139645
139914
|
};
|
|
139646
|
-
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Settings2, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Approaches"), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Approach"))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Approaches found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Approach ID"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Verb (VI)"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Cognitive Level"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "iSpring Type"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-medium" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.verbVi), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.bloomLevelCode), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.iSpringQuizType), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Approach" : "Add New Approach")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Approach Code"), /* @__PURE__ */ React169__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__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "verbEn" }, "Verb (English)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "verbEn", value: formState.verbEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbEn: e3.target.value })), placeholder: "e.g., Identify" }))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "verbVi" }, "Verb (Vietnamese)"), /* @__PURE__ */ React169__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__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Cognitive Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.bloomLevelCode, onValueChange: (v) => setFormState((s4) => ({ ...s4, bloomLevelCode: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, bloomLevels.map((level) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: level.code, value: level.code }, level.name)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "knowledgeDimension" }, "Knowledge Dimension"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.knowledgeDimension, onValueChange: (v) => setFormState((s4) => ({ ...s4, knowledgeDimension: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, knowledgeDimensions.map((kd) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: kd, value: kd }, kd))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "iSpringQuizType" }, "iSpring Quiz Type"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.iSpringQuizType, onValueChange: (v) => setFormState((s4) => ({ ...s4, iSpringQuizType: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, questionTypes.map((qt) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "rawDifficulty" }, "Raw Difficulty"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.rawDifficulty, onValueChange: (v) => setFormState((s4) => ({ ...s4, rawDifficulty: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, rawDifficultyLevels.map((rd) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: rd, value: rd }, rd)))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "suggestContext" }, "Suggest Context (comma-separated codes)"), /* @__PURE__ */ React169__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__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "exampleEn" }, "Example (English)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "exampleEn", value: formState.exampleEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleEn: e3.target.value })), placeholder: "English example prompt..." })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "exampleVi" }, "Example (Vietnamese)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "exampleVi", value: formState.exampleVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleVi: e3.target.value })), placeholder: "Vietnamese example prompt..." }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.code, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139915
|
+
const handleImport = async (records) => {
|
|
139916
|
+
if (!onBulkAdd) return;
|
|
139917
|
+
const parseStringToArray = (input) => {
|
|
139918
|
+
if (Array.isArray(input)) return input;
|
|
139919
|
+
if (typeof input === "string") return input.split(",").map((s4) => s4.trim()).filter(Boolean);
|
|
139920
|
+
return [];
|
|
139921
|
+
};
|
|
139922
|
+
const validatedRecords = records.reduce((acc, rec) => {
|
|
139923
|
+
if (rec.code && rec.name && rec.verbEn && rec.verbVi && rec.knowledgeDimension && rec.iSpringQuizType && rec.difficulty && rec.bloomLevelCode) {
|
|
139924
|
+
acc.push({
|
|
139925
|
+
code: rec.code,
|
|
139926
|
+
name: rec.name,
|
|
139927
|
+
verbEn: rec.verbEn,
|
|
139928
|
+
verbVi: rec.verbVi,
|
|
139929
|
+
knowledgeDimension: rec.knowledgeDimension,
|
|
139930
|
+
iSpringQuizType: rec.iSpringQuizType,
|
|
139931
|
+
difficulty: parseStringToArray(rec.difficulty),
|
|
139932
|
+
bloomLevelCode: rec.bloomLevelCode,
|
|
139933
|
+
suggestContext: rec.suggestContext,
|
|
139934
|
+
exampleEn: rec.exampleEn,
|
|
139935
|
+
exampleVi: rec.exampleVi
|
|
139936
|
+
});
|
|
139937
|
+
}
|
|
139938
|
+
return acc;
|
|
139939
|
+
}, []);
|
|
139940
|
+
if (validatedRecords.length !== records.length) {
|
|
139941
|
+
toast2({
|
|
139942
|
+
title: "Import Warning",
|
|
139943
|
+
description: `${records.length - validatedRecords.length} records had missing required fields and were ignored.`,
|
|
139944
|
+
variant: "destructive"
|
|
139945
|
+
});
|
|
139946
|
+
}
|
|
139947
|
+
if (validatedRecords.length > 0) {
|
|
139948
|
+
await onBulkAdd(validatedRecords);
|
|
139949
|
+
}
|
|
139950
|
+
};
|
|
139951
|
+
return /* @__PURE__ */ React169__default.createElement(Card, null, /* @__PURE__ */ React169__default.createElement(CardHeader, null, /* @__PURE__ */ React169__default.createElement(CardTitle, { className: "flex justify-between items-center" }, /* @__PURE__ */ React169__default.createElement("span", { className: "flex items-center" }, /* @__PURE__ */ React169__default.createElement(Settings2, { className: "mr-2 h-5 w-5 text-primary" }), " Manage Approaches"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center gap-2" }, onBulkAdd && /* @__PURE__ */ React169__default.createElement(MetadataImportControls, { metadataName: "Approaches", onImport: handleImport }), /* @__PURE__ */ React169__default.createElement(Button, { onClick: handleAddItem, size: "sm" }, /* @__PURE__ */ React169__default.createElement(CirclePlus, { className: "mr-2 h-4 w-4" }), " Add Approach")))), /* @__PURE__ */ React169__default.createElement(CardContent, null, isLoading ? /* @__PURE__ */ React169__default.createElement("div", { className: "flex justify-center items-center h-32" }, /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "h-8 w-8 animate-spin text-primary" })) : items.length === 0 ? /* @__PURE__ */ React169__default.createElement("p", { className: "text-center text-muted-foreground py-4" }, "No Approaches found.") : /* @__PURE__ */ React169__default.createElement("div", { className: "overflow-x-auto" }, /* @__PURE__ */ React169__default.createElement(Table3, null, /* @__PURE__ */ React169__default.createElement(TableHeader, null, /* @__PURE__ */ React169__default.createElement(TableRow, null, /* @__PURE__ */ React169__default.createElement(TableHead, null, "Approach ID"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Name"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Verb (VI)"), /* @__PURE__ */ React169__default.createElement(TableHead, null, "Cognitive Level"), /* @__PURE__ */ React169__default.createElement(TableHead, { className: "text-right w-[120px]" }, "Actions"))), /* @__PURE__ */ React169__default.createElement(TableBody, null, items.map((item) => /* @__PURE__ */ React169__default.createElement(TableRow, { key: item.id }, /* @__PURE__ */ React169__default.createElement(TableCell, { className: "font-Medium" }, item.code), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.name), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.verbVi), /* @__PURE__ */ React169__default.createElement(TableCell, null, item.bloomLevelCode), /* @__PURE__ */ React169__default.createElement(TableCell, { className: "text-right" }, /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleEditItem(item), className: "mr-2" }, /* @__PURE__ */ React169__default.createElement(PenLine, { className: "h-4 w-4" })), /* @__PURE__ */ React169__default.createElement(Button, { variant: "ghost", size: "icon", onClick: () => handleDeleteItem(item), className: "text-destructive hover:text-destructive" }, /* @__PURE__ */ React169__default.createElement(Trash2, { className: "h-4 w-4" })))))))), /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isDialogOpen, onOpenChange: setIsDialogOpen }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-2xl max-h-[90vh] overflow-y-auto" }, /* @__PURE__ */ React169__default.createElement(DialogHeader, null, /* @__PURE__ */ React169__default.createElement(DialogTitle2, null, currentItem ? "Edit Approach" : "Add New Approach")), /* @__PURE__ */ React169__default.createElement("div", { className: "grid gap-4 py-4" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "code" }, "Approach Code"), /* @__PURE__ */ React169__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__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "name" }, "Name / Description"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "verbEn" }, "Verb (English)"), /* @__PURE__ */ React169__default.createElement(Input, { id: "verbEn", value: formState.verbEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, verbEn: e3.target.value })), placeholder: "e.g., Identify" })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "verbVi" }, "Verb (Vietnamese)"), /* @__PURE__ */ React169__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__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4" }, /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "bloomLevelCode" }, "Cognitive Level"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.bloomLevelCode, onValueChange: (v) => setFormState((s4) => ({ ...s4, bloomLevelCode: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, bloomLevels.map((level) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: level.code, value: level.code }, level.name))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "knowledgeDimension" }, "Knowledge Dimension"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.knowledgeDimension, onValueChange: (v) => setFormState((s4) => ({ ...s4, knowledgeDimension: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, knowledgeDimensions.map((kd) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: kd, value: kd }, kd))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "iSpringQuizType" }, "iSpring Quiz Type"), /* @__PURE__ */ React169__default.createElement(Select2, { value: formState.iSpringQuizType, onValueChange: (v) => setFormState((s4) => ({ ...s4, iSpringQuizType: v })) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, questionTypes.map((qt) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: qt.code, value: qt.code }, qt.name)))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, null, "Difficulty"), /* @__PURE__ */ React169__default.createElement("div", { className: "flex items-center space-x-4 pt-2" }, standardDifficulties.map((diff2) => /* @__PURE__ */ React169__default.createElement("div", { key: diff2, className: "flex items-center space-x-2" }, /* @__PURE__ */ React169__default.createElement(Checkbox2, { id: `diff-${diff2}`, checked: (formState.difficulty || []).includes(diff2), onCheckedChange: (checked) => {
|
|
139952
|
+
const current = formState.difficulty || [];
|
|
139953
|
+
const newDiff = checked ? [...current, diff2] : current.filter((d) => d !== diff2);
|
|
139954
|
+
setFormState((s4) => ({ ...s4, difficulty: newDiff }));
|
|
139955
|
+
} }), /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: `diff-${diff2}` }, diff2))))), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "suggestContext" }, "Suggest Context (comma-separated codes)"), /* @__PURE__ */ React169__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__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "exampleEn" }, "Example (English)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "exampleEn", value: formState.exampleEn || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleEn: e3.target.value })), placeholder: "English example prompt..." })), /* @__PURE__ */ React169__default.createElement("div", null, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "exampleVi" }, "Example (Vietnamese)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "exampleVi", value: formState.exampleVi || "", onChange: (e3) => setFormState((s4) => ({ ...s4, exampleVi: e3.target.value })), placeholder: "Vietnamese example prompt..." }))), /* @__PURE__ */ React169__default.createElement(DialogFooter, null, /* @__PURE__ */ React169__default.createElement(Button, { type: "button", variant: "outline", onClick: () => setIsDialogOpen(false), disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(Button, { type: "submit", onClick: handleSubmit, disabled: isPending }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Save")))), /* @__PURE__ */ React169__default.createElement(AlertDialog2, { open: isAlertOpen, onOpenChange: setIsAlertOpen }, /* @__PURE__ */ React169__default.createElement(AlertDialogContent2, null, /* @__PURE__ */ React169__default.createElement(AlertDialogHeader, null, /* @__PURE__ */ React169__default.createElement(AlertDialogTitle2, null, "Are you sure?"), /* @__PURE__ */ React169__default.createElement(AlertDialogDescription2, null, 'This will permanently delete "', itemToDelete?.code, '".')), /* @__PURE__ */ React169__default.createElement(AlertDialogFooter, null, /* @__PURE__ */ React169__default.createElement(AlertDialogCancel2, { disabled: isPending }, "Cancel"), /* @__PURE__ */ React169__default.createElement(AlertDialogAction2, { onClick: confirmDelete, disabled: isPending, className: "bg-destructive hover:bg-destructive/90" }, isPending && /* @__PURE__ */ React169__default.createElement(LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }), " Delete"))))));
|
|
139647
139956
|
}
|
|
139648
139957
|
|
|
139649
139958
|
// src/react-ui/components/metadata/MetadataTabs.tsx
|
|
@@ -140319,7 +140628,7 @@ var ToastAction2 = React169.forwardRef(({ className, ...props }, ref) => /* @__P
|
|
|
140319
140628
|
{
|
|
140320
140629
|
ref,
|
|
140321
140630
|
className: cn(
|
|
140322
|
-
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-
|
|
140631
|
+
"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",
|
|
140323
140632
|
className
|
|
140324
140633
|
),
|
|
140325
140634
|
...props
|
|
@@ -141914,4 +142223,4 @@ react-tooltip/dist/react-tooltip.min.mjs:
|
|
|
141914
142223
|
*)
|
|
141915
142224
|
*/
|
|
141916
142225
|
|
|
141917
|
-
export { AIFullQuizGeneratorModal, AIQuestionGeneratorModal, APIKeyManagerModal, Accordion2 as Accordion, AccordionContent2 as AccordionContent, AccordionItem2 as AccordionItem, AccordionTrigger2 as AccordionTrigger, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, ApiKeySettings, ApproachManager, Badge2 as Badge, BloomLevelManager, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryManager, Cheatsheet, Checkbox2 as Checkbox, ContextManager, Dialog2 as Dialog, DialogContent2 as DialogContent, DialogDescription2 as DialogDescription, DialogFooter, DialogHeader, DialogTitle2 as DialogTitle, EditQuestionModal, FreestyleQuizzesCard, GeneratedQuizzesCard, GradeLevelManager, ImportQuestionsModal, Input, Label2 as Label, LanguageProvider, LearningObjectiveManager, ManageTopics, MetadataTabs, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, Progress2 as Progress, QuestionFilters, QuestionFormDialog, QuestionList, QuestionPreviewModal, QuestionRenderer, QuestionTypeManager, QuizAuthoringTool, QuizDataManagement, QuizPlayer, QuizResult, QuizReview, QuizSettingsForm, RadioGroup2 as RadioGroup, RadioGroupItem2 as RadioGroupItem, SCORMExportModal, ScrollArea2 as ScrollArea, Select2 as Select, SelectContent2 as SelectContent, SelectItem2 as SelectItem, SelectTrigger2 as SelectTrigger, SelectValue2 as SelectValue, SelectedQuestionsPanel, SettingsModal, Skeleton, SubjectManager, SuggestionDialog, Tabs2 as Tabs, TabsContent2 as TabsContent, TabsList2 as TabsList, TabsTrigger2 as TabsTrigger, Toaster, Tooltip2 as Tooltip, TooltipContent2 as TooltipContent, TooltipProvider2 as TooltipProvider, TooltipTrigger2 as TooltipTrigger, TopicManager, UploadResourceModal, toast, useToast };
|
|
142226
|
+
export { AIFullQuizGeneratorModal, AIQuestionGeneratorModal, APIKeyManagerModal, Accordion2 as Accordion, AccordionContent2 as AccordionContent, AccordionItem2 as AccordionItem, AccordionTrigger2 as AccordionTrigger, Achievements, ActivityCalendar, Alert, AlertDescription, AlertTitle, ApiKeySettings, ApproachManager, Badge2 as Badge, BloomLevelManager, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryManager, Cheatsheet, Checkbox2 as Checkbox, ContextManager, Dialog2 as Dialog, DialogContent2 as DialogContent, DialogDescription2 as DialogDescription, DialogFooter, DialogHeader, DialogTitle2 as DialogTitle, EditQuestionModal, FreestyleQuizzesCard, GeneratedQuizzesCard, GradeLevelManager, ImportQuestionsModal, Input, Label2 as Label, LanguageProvider, LearningObjectiveManager, ManageTopics, MetadataImportControls, MetadataTabs, PerformanceCharts, PersonalPracticeDashboard, PracticeHistoryTable, PracticeModeController, Progress2 as Progress, QuestionFilters, QuestionFormDialog, QuestionList, QuestionPreviewModal, QuestionRenderer, QuestionTypeManager, QuizAuthoringTool, QuizDataManagement, QuizPlayer, QuizResult, QuizReview, QuizSettingsForm, RadioGroup2 as RadioGroup, RadioGroupItem2 as RadioGroupItem, SCORMExportModal, ScrollArea2 as ScrollArea, Select2 as Select, SelectContent2 as SelectContent, SelectItem2 as SelectItem, SelectTrigger2 as SelectTrigger, SelectValue2 as SelectValue, SelectedQuestionsPanel, SettingsModal, Skeleton, SubjectManager, SuggestionDialog, Tabs2 as Tabs, TabsContent2 as TabsContent, TabsList2 as TabsList, TabsTrigger2 as TabsTrigger, Toaster, Tooltip2 as Tooltip, TooltipContent2 as TooltipContent, TooltipProvider2 as TooltipProvider, TooltipTrigger2 as TooltipTrigger, TopicManager, UploadResourceModal, toast, useToast };
|