@thanh01.pmt/interactive-quiz-kit 1.0.72 → 1.0.74

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/dist/react-ui.mjs CHANGED
@@ -98203,21 +98203,30 @@ var EditableCombobox = ({
98203
98203
  disabled = false
98204
98204
  }) => {
98205
98205
  const [open, setOpen] = React169.useState(false);
98206
- const [inputValue, setInputValue] = React169.useState(value);
98206
+ const [inputValue, setInputValue] = React169.useState("");
98207
98207
  React169.useEffect(() => {
98208
- setInputValue(value);
98209
- }, [value]);
98210
- const handleSelect = (currentValue) => {
98211
- const newValue = currentValue === value ? "" : currentValue;
98212
- onChange(newValue);
98213
- setInputValue(newValue);
98208
+ if (value) {
98209
+ const selectedOption = options.find((option) => option.value.toLowerCase() === value.toLowerCase());
98210
+ setInputValue(selectedOption ? selectedOption.label : value);
98211
+ } else {
98212
+ setInputValue("");
98213
+ }
98214
+ }, [value, options, open]);
98215
+ const handleSelect = (selectedValue) => {
98216
+ onChange(selectedValue);
98214
98217
  setOpen(false);
98215
98218
  };
98216
- const handleBlur = () => {
98217
- onChange(inputValue);
98219
+ const handleOpenChange = (isOpen) => {
98220
+ if (!isOpen) {
98221
+ const match2 = options.find((option) => option.label.toLowerCase() === inputValue.toLowerCase());
98222
+ if (!match2 && inputValue !== (options.find((opt) => opt.value === value)?.label || value)) {
98223
+ onChange(inputValue);
98224
+ }
98225
+ }
98226
+ setOpen(isOpen);
98218
98227
  };
98219
98228
  const displayLabel = options.find((option) => option.value.toLowerCase() === value?.toLowerCase())?.label || value;
98220
- return /* @__PURE__ */ React169.createElement(Popover2, { open, onOpenChange: setOpen }, /* @__PURE__ */ React169.createElement(PopoverTrigger2, { asChild: true }, /* @__PURE__ */ React169.createElement(
98229
+ return /* @__PURE__ */ React169.createElement(Popover2, { open, onOpenChange: handleOpenChange }, /* @__PURE__ */ React169.createElement(PopoverTrigger2, { asChild: true }, /* @__PURE__ */ React169.createElement(
98221
98230
  Button,
98222
98231
  {
98223
98232
  variant: "outline",
@@ -98233,8 +98242,7 @@ var EditableCombobox = ({
98233
98242
  {
98234
98243
  placeholder: searchPlaceholder,
98235
98244
  value: inputValue,
98236
- onValueChange: setInputValue,
98237
- onBlur: handleBlur
98245
+ onValueChange: setInputValue
98238
98246
  }
98239
98247
  ), /* @__PURE__ */ React169.createElement(CommandList, null, /* @__PURE__ */ React169.createElement(CommandEmpty, null, noResultsMessage), /* @__PURE__ */ React169.createElement(CommandGroup, null, options.map((option) => /* @__PURE__ */ React169.createElement(
98240
98248
  CommandItem,
@@ -101171,6 +101179,10 @@ var supportedQuestionTypesForAI = [
101171
101179
  { value: "sequence", label: "Sequence" },
101172
101180
  { value: "matching", label: "Matching" }
101173
101181
  ];
101182
+ var availableLanguages = [
101183
+ { value: "English", label: "English" },
101184
+ { value: "Vietnamese", label: "Ti\u1EBFng Vi\u1EC7t" }
101185
+ ];
101174
101186
  var AIQuestionGeneratorModal = ({
101175
101187
  isOpen,
101176
101188
  onClose,
@@ -101180,51 +101192,65 @@ var AIQuestionGeneratorModal = ({
101180
101192
  subjects = [],
101181
101193
  topics = [],
101182
101194
  gradeLevels = [],
101183
- bloomLevels = []
101195
+ bloomLevels = [],
101196
+ categories = []
101184
101197
  }) => {
101185
- const [prompt, setPrompt] = useState("");
101198
+ const [additionalInfo, setAdditionalInfo] = useState("");
101186
101199
  const [isLoading, setIsLoading] = useState(false);
101187
101200
  const [error, setError] = useState(null);
101188
101201
  const { toast: toast2 } = useToast();
101189
101202
  const [subjectCode, setSubjectCode] = useState("");
101203
+ const [categoryCode, setCategoryCode] = useState("");
101190
101204
  const [topicCode, setTopicCode] = useState("");
101191
101205
  const [gradeBand, setGradeBand] = useState("");
101192
101206
  const [bloomLevelCode, setBloomLevelCode] = useState("");
101193
101207
  const [selectedQuestionType, setSelectedQuestionType] = useState("multiple_choice");
101208
+ const [selectedLanguage, setSelectedLanguage] = useState(language3);
101194
101209
  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
101210
  const [isApiKeyManagerModalOpen, setIsApiKeyManagerModalOpen] = useState(false);
101202
101211
  const [geminiApiKeyExists, setGeminiApiKeyExists] = useState(false);
101203
101212
  const finalQuestionType = questionTypeProp || selectedQuestionType;
101204
101213
  useEffect(() => {
101205
101214
  if (isOpen) {
101206
- setPrompt("");
101215
+ setAdditionalInfo("");
101207
101216
  setError(null);
101208
101217
  setIsLoading(false);
101209
101218
  setSubjectCode("");
101219
+ setCategoryCode("");
101210
101220
  setTopicCode("");
101211
101221
  setGradeBand("");
101212
101222
  setBloomLevelCode("");
101213
101223
  setSelectedQuestionType(questionTypeProp || "multiple_choice");
101224
+ setSelectedLanguage(language3);
101214
101225
  setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
101215
101226
  }
101216
- }, [isOpen, questionTypeProp]);
101227
+ }, [isOpen, questionTypeProp, language3]);
101228
+ const filteredCategories = useMemo(() => {
101229
+ return categories;
101230
+ }, [categories]);
101217
101231
  const filteredTopics = useMemo(() => {
101218
- if (!subjectCode) return [];
101219
- return topics.filter((t4) => t4.subjectCode === subjectCode);
101220
- }, [subjectCode, topics]);
101232
+ if (!subjectCode || !categoryCode) return [];
101233
+ return topics.filter(
101234
+ (t4) => t4.subjectCode === subjectCode
101235
+ /* && t.categoryCode === categoryCode */
101236
+ );
101237
+ }, [subjectCode, categoryCode, topics]);
101238
+ const handleSubjectChange = (newSubjectCode) => {
101239
+ setSubjectCode(newSubjectCode);
101240
+ setCategoryCode("");
101241
+ setTopicCode("");
101242
+ };
101243
+ const handleCategoryChange = (newCategoryCode) => {
101244
+ setCategoryCode(newCategoryCode);
101245
+ setTopicCode("");
101246
+ };
101221
101247
  const handleApiKeyModalClose = () => {
101222
101248
  setIsApiKeyManagerModalOpen(false);
101223
101249
  setGeminiApiKeyExists(APIKeyService.hasAPIKey(GEMINI_API_KEY_SERVICE_NAME));
101224
101250
  };
101225
101251
  const handleSubmit = async () => {
101226
- if (!prompt.trim()) {
101227
- setError("Please provide a prompt for the question.");
101252
+ if (!subjectCode || !categoryCode || !topicCode || !gradeBand || !bloomLevelCode) {
101253
+ setError("Please fill in all required metadata fields: Subject, Category, Topic, Grade Level, and Bloom's Level.");
101228
101254
  return;
101229
101255
  }
101230
101256
  const geminiKey = APIKeyService.getAPIKey(GEMINI_API_KEY_SERVICE_NAME);
@@ -101238,14 +101264,15 @@ var AIQuestionGeneratorModal = ({
101238
101264
  setIsLoading(true);
101239
101265
  try {
101240
101266
  const quizContext = {
101241
- plannedTopic: prompt,
101267
+ plannedTopic: additionalInfo.trim() || topicCode,
101242
101268
  originalSubject: subjectCode,
101243
101269
  originalTopic: topicCode,
101270
+ originalCategory: categoryCode,
101244
101271
  gradeBand,
101245
101272
  plannedBloomLevel: bloomLevelCode
101246
101273
  };
101247
101274
  const baseClientInput = {
101248
- language: language3,
101275
+ language: selectedLanguage,
101249
101276
  difficulty: "Medium",
101250
101277
  quizContext
101251
101278
  };
@@ -101258,33 +101285,32 @@ var AIQuestionGeneratorModal = ({
101258
101285
  generatedResult = await generateMCQQuestion({ ...baseClientInput, numberOfOptions }, geminiKey);
101259
101286
  break;
101260
101287
  case "multiple_response":
101261
- generatedResult = await generateMRQQuestion({ ...baseClientInput, numberOfOptions, minCorrectAnswers, maxCorrectAnswers }, geminiKey);
101288
+ generatedResult = await generateMRQQuestion({ ...baseClientInput, numberOfOptions: 5, minCorrectAnswers: 2, maxCorrectAnswers: 3 }, geminiKey);
101262
101289
  break;
101263
101290
  case "short_answer":
101264
- generatedResult = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive }, geminiKey);
101291
+ generatedResult = await generateShortAnswerQuestion({ ...baseClientInput, isCaseSensitive: false }, geminiKey);
101265
101292
  break;
101266
101293
  case "numeric":
101267
101294
  generatedResult = await generateNumericQuestion({ ...baseClientInput, allowDecimals: true, tolerance: 0 }, geminiKey);
101268
101295
  break;
101269
101296
  case "fill_in_the_blanks":
101270
- generatedResult = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks, isCaseSensitive }, geminiKey);
101297
+ generatedResult = await generateFillInTheBlanksQuestion({ ...baseClientInput, numberOfBlanks: 2, isCaseSensitive: false }, geminiKey);
101271
101298
  break;
101272
101299
  case "sequence":
101273
- generatedResult = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: numberOfSequenceItems }, geminiKey);
101300
+ generatedResult = await generateSequenceQuestion({ ...baseClientInput, numberOfItems: 4 }, geminiKey);
101274
101301
  break;
101275
101302
  case "matching":
101276
- generatedResult = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: numberOfMatchingPairs, shuffleOptions: true }, geminiKey);
101303
+ generatedResult = await generateMatchingQuestion({ ...baseClientInput, numberOfPairs: 4, shuffleOptions: true }, geminiKey);
101277
101304
  break;
101278
101305
  default:
101279
101306
  throw new Error(`AI generation for '${finalQuestionType}' is not implemented in this flow.`);
101280
101307
  }
101281
- if (generatedResult.error) {
101282
- throw new Error(generatedResult.error);
101283
- }
101308
+ if (generatedResult.error) throw new Error(generatedResult.error);
101284
101309
  if (generatedResult.question) {
101285
101310
  const completeQuestion = generatedResult.question;
101286
101311
  completeQuestion.subject = subjectCode;
101287
101312
  completeQuestion.topic = topicCode;
101313
+ completeQuestion.category = categoryCode;
101288
101314
  completeQuestion.gradeBand = gradeBand;
101289
101315
  completeQuestion.bloomLevel = bloomLevelCode;
101290
101316
  if (completeQuestion.points === void 0) completeQuestion.points = 10;
@@ -101310,47 +101336,13 @@ var AIQuestionGeneratorModal = ({
101310
101336
  const comboboxOptions = {
101311
101337
  subjects: subjects.map((s4) => ({ value: s4.code, label: s4.name })),
101312
101338
  topics: filteredTopics.map((t4) => ({ value: t4.code, label: t4.name })),
101339
+ categories: filteredCategories.map((c4) => ({ value: c4.code, label: c4.name })),
101313
101340
  gradeLevels: gradeLevels.map((g) => ({ value: g.code, label: g.name })),
101314
101341
  bloomLevels: bloomLevels.map((b2) => ({ value: b2.code, label: b2.name }))
101315
101342
  };
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
101343
  return /* @__PURE__ */ React169__default.createElement(React169__default.Fragment, null, /* @__PURE__ */ React169__default.createElement(Dialog2, { open: isOpen, onOpenChange: (open) => {
101343
101344
  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 }));
101345
+ } }, /* @__PURE__ */ React169__default.createElement(DialogContent2, { className: "sm:max-w-[700px]" }, /* @__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 metadata and optional info to generate a '", finalQuestionType, "' question.")), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-4 py-4 max-h-[60vh] overflow-y-auto px-2" }, /* @__PURE__ */ React169__default.createElement("div", { className: "grid grid-cols-2 gap-4" }, !questionTypeProp && /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Question Type"), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedQuestionType, onValueChange: (v) => setSelectedQuestionType(v) }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__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, null, "Language"), /* @__PURE__ */ React169__default.createElement(Select2, { value: selectedLanguage, onValueChange: setSelectedLanguage }, /* @__PURE__ */ React169__default.createElement(SelectTrigger2, null, /* @__PURE__ */ React169__default.createElement(SelectValue2, null)), /* @__PURE__ */ React169__default.createElement(SelectContent2, null, availableLanguages.map((lang) => /* @__PURE__ */ React169__default.createElement(SelectItem2, { key: lang.value, value: lang.value }, lang.label)))))), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, { htmlFor: "ai-additional-info" }, "Additional Info (Optional)"), /* @__PURE__ */ React169__default.createElement(Textarea, { id: "ai-additional-info", value: additionalInfo, onChange: (e3) => setAdditionalInfo(e3.target.value), placeholder: "Provide extra context, a specific topic, or a detailed prompt for the AI...", className: "min-h-[100px]" })), /* @__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: handleSubjectChange, placeholder: "Select Subject..." })), /* @__PURE__ */ React169__default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React169__default.createElement(Label2, null, "Category *"), /* @__PURE__ */ React169__default.createElement(EditableCombobox, { options: comboboxOptions.categories, value: categoryCode, onChange: handleCategoryChange, placeholder: "Select Category...", disabled: !subjectCode })), /* @__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 Topic...", disabled: !categoryCode })), /* @__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 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 Bloom's Level..." }))), finalQuestionType === "multiple_choice" && /* @__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(Input, { id: "ai-num-options", type: "number", value: numberOfOptions, onChange: (e3) => setNumberOfOptions(parseInt(e3.target.value, 10)), min: 2, max: 8 })), 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 || !geminiApiKeyExists || !subjectCode || !categoryCode || !topicCode || !gradeBand || !bloomLevelCode }, 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
101346
  };
101355
101347
 
101356
101348
  // src/react-ui/components/authoring/AIFullQuizGeneratorModal.tsx
@@ -101851,12 +101843,10 @@ init_react_shim();
101851
101843
  // src/services/TopicDataService.ts
101852
101844
  init_react_shim();
101853
101845
  var TopicDataService = class {
101854
- // ... saveData, mergeData, getData, clearData methods remain the same ...
101855
101846
  static saveData(data) {
101856
101847
  try {
101857
101848
  if (typeof window === "undefined") return;
101858
- const serializedData = JSON.stringify(data);
101859
- localStorage.setItem(this.STORAGE_KEY, serializedData);
101849
+ localStorage.setItem(this.STORAGE_KEY, JSON.stringify(data));
101860
101850
  } catch (error) {
101861
101851
  console.error("Error saving learning objectives to Local Storage:", error);
101862
101852
  }
@@ -101867,8 +101857,7 @@ var TopicDataService = class {
101867
101857
  newData.forEach((newLo) => {
101868
101858
  loMap.set(newLo.code, newLo);
101869
101859
  });
101870
- const mergedData = Array.from(loMap.values());
101871
- this.saveData(mergedData);
101860
+ this.saveData(Array.from(loMap.values()));
101872
101861
  }
101873
101862
  static getData() {
101874
101863
  try {
@@ -101883,7 +101872,7 @@ var TopicDataService = class {
101883
101872
  }
101884
101873
  static clearData() {
101885
101874
  try {
101886
- if (typeof window === "undefined") return;
101875
+ if (typeof window !== "undefined") return;
101887
101876
  localStorage.removeItem(this.STORAGE_KEY);
101888
101877
  } catch (error) {
101889
101878
  console.error("Error clearing learning objectives from Local Storage:", error);
@@ -101897,6 +101886,12 @@ var TopicDataService = class {
101897
101886
  const headerLine = lines.shift();
101898
101887
  const headers = headerLine.split(" ").map((h3) => h3.trim());
101899
101888
  const headerMap = headers.map((h3) => this.HEADER_MAP[h3] || null);
101889
+ const requiredHeaders = ["LO ID", "LO Name", "Subject", "Subject Code", "Category", "Category Code", "Topic", "Topic Code", "Grade", "Grade Code"];
101890
+ const missingHeaders = requiredHeaders.filter((h3) => !headers.includes(h3));
101891
+ if (missingHeaders.length > 0) {
101892
+ const errorMsg = `Invalid TSV header. Missing required columns: ${missingHeaders.join(", ")}`;
101893
+ return { data: [], errors: [errorMsg] };
101894
+ }
101900
101895
  const data = [];
101901
101896
  const errors2 = [];
101902
101897
  lines.forEach((line, index3) => {
@@ -101905,29 +101900,30 @@ var TopicDataService = class {
101905
101900
  headerMap.forEach((propName, i2) => {
101906
101901
  if (propName) {
101907
101902
  const value = values[i2]?.trim() || "";
101908
- if (propName === "keywords" || propName === "stemElements" || propName === "bloomLevelsGuideline") {
101903
+ if (["keywords", "stemElements", "bloomLevelsGuideline"].includes(propName)) {
101909
101904
  rowObject[propName] = value.split(",").map((s4) => s4.trim()).filter(Boolean);
101910
101905
  } else {
101911
101906
  rowObject[propName] = value;
101912
101907
  }
101913
101908
  }
101914
101909
  });
101915
- if (!rowObject.code) {
101916
- errors2.push(`Line ${index3 + 2}: Missing required value for 'LO ID'.`);
101910
+ if (!rowObject.code || !rowObject.name) {
101911
+ errors2.push(`Line ${index3 + 2}: Missing required values for 'LO ID' or 'LO Name'.`);
101917
101912
  return;
101918
101913
  }
101919
- if (!rowObject.name) {
101920
- rowObject.name = rowObject.code;
101921
- }
101922
101914
  const learningObjective = {
101923
101915
  id: generateUniqueId("lo_"),
101924
101916
  code: rowObject.code,
101925
101917
  name: rowObject.name,
101926
101918
  description: rowObject.description,
101927
101919
  subject: rowObject.subject || "",
101920
+ subjectCode: rowObject.subjectCode,
101928
101921
  category: rowObject.category || "",
101922
+ categoryCode: rowObject.categoryCode,
101929
101923
  topic: rowObject.topic || "",
101924
+ topicCode: rowObject.topicCode,
101930
101925
  grade: rowObject.grade || "",
101926
+ gradeCode: rowObject.gradeCode,
101931
101927
  keywords: rowObject.keywords || [],
101932
101928
  stemElements: rowObject.stemElements || [],
101933
101929
  bloomLevelsGuideline: rowObject.bloomLevelsGuideline || []
@@ -101958,17 +101954,23 @@ var TopicDataService = class {
101958
101954
  }
101959
101955
  };
101960
101956
  TopicDataService.STORAGE_KEY = "interactive_quiz_kit_learning_objectives";
101961
- // Define a map for flexible header mapping
101962
101957
  TopicDataService.HEADER_MAP = {
101963
101958
  "LO ID": "code",
101964
101959
  "LO Name": "name",
101965
- // Ready for future addition
101966
101960
  "LO Description": "description",
101967
101961
  "Subject": "subject",
101962
+ "Subject Code": "subjectCode",
101963
+ // New
101968
101964
  "Category": "category",
101965
+ "Category Code": "categoryCode",
101966
+ // New
101969
101967
  "Topic": "topic",
101968
+ "Topic Code": "topicCode",
101969
+ // New
101970
101970
  "Keywords": "keywords",
101971
101971
  "Grade": "grade",
101972
+ "Grade Code": "gradeCode",
101973
+ // New
101972
101974
  "STEM Element(s)": "stemElements",
101973
101975
  "Bloom\u2019s Level(s) Guideline": "bloomLevelsGuideline"
101974
101976
  };
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default from 'react';
3
3
  import { s as QuizConfig, g as QuizQuestion, Q as QuestionTypeStrings, r as QuizSettings } from './quiz-config-CwaP-pBs.js';
4
- import { Subject, Topic, GradeLevel, BloomLevelType, QuestionInBank, QuestionFilters as QuestionFilters$1, Category, QuestionTypeType, LearningObjective, Context, Approach } from './index.js';
4
+ import { Subject, Topic, GradeLevel, BloomLevelType, Category, QuestionInBank, QuestionFilters as QuestionFilters$1, QuestionTypeType, LearningObjective, Context, Approach } from './index.js';
5
5
  import * as class_variance_authority_types from 'class-variance-authority/types';
6
6
  import * as ToastPrimitives from '@radix-ui/react-toast';
7
7
  import { VariantProps } from 'class-variance-authority';
@@ -23,6 +23,7 @@ interface AIQuestionGeneratorModalProps {
23
23
  topics?: Topic[];
24
24
  gradeLevels?: GradeLevel[];
25
25
  bloomLevels?: BloomLevelType[];
26
+ categories?: Category[];
26
27
  }
27
28
  declare const AIQuestionGeneratorModal: React__default.FC<AIQuestionGeneratorModalProps>;
28
29
 
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default from 'react';
3
3
  import { s as QuizConfig, g as QuizQuestion, Q as QuestionTypeStrings, r as QuizSettings } from './quiz-config-CwaP-pBs.cjs';
4
- import { Subject, Topic, GradeLevel, BloomLevelType, QuestionInBank, QuestionFilters as QuestionFilters$1, Category, QuestionTypeType, LearningObjective, Context, Approach } from './index.cjs';
4
+ import { Subject, Topic, GradeLevel, BloomLevelType, Category, QuestionInBank, QuestionFilters as QuestionFilters$1, QuestionTypeType, LearningObjective, Context, Approach } from './index.cjs';
5
5
  import * as class_variance_authority_types from 'class-variance-authority/types';
6
6
  import * as ToastPrimitives from '@radix-ui/react-toast';
7
7
  import { VariantProps } from 'class-variance-authority';
@@ -23,6 +23,7 @@ interface AIQuestionGeneratorModalProps {
23
23
  topics?: Topic[];
24
24
  gradeLevels?: GradeLevel[];
25
25
  bloomLevels?: BloomLevelType[];
26
+ categories?: Category[];
26
27
  }
27
28
  declare const AIQuestionGeneratorModal: React__default.FC<AIQuestionGeneratorModalProps>;
28
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thanh01.pmt/interactive-quiz-kit",
3
- "version": "1.0.72",
3
+ "version": "1.0.74",
4
4
  "description": "A comprehensive library for creating, managing, and playing interactive quizzes, with AI generation and SCORM support.",
5
5
  "keywords": [
6
6
  "react",