@intellectif/lk-react 18.0.0 → 18.1.0
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/CHANGELOG.md +44 -0
- package/README.md +3 -1
- package/dist/{WrittenResponse-B4kY-U0v.d.ts → WrittenResponse-CeHY5u3W.d.ts} +1 -1
- package/dist/{WrittenResponse-Dl3zah22.d.cts → WrittenResponse-DT_Yqbq1.d.cts} +1 -1
- package/dist/ai/useAiHelp.cjs +20 -0
- package/dist/ai/useAiHelp.cjs.map +1 -0
- package/dist/ai/useAiHelp.d.cts +146 -0
- package/dist/ai/useAiHelp.d.ts +146 -0
- package/dist/ai/useAiHelp.js +7 -0
- package/dist/ai/useAiHelp.js.map +1 -0
- package/dist/{chunk-Z5XY66PO.cjs → chunk-3SDCALS3.cjs} +16 -16
- package/dist/chunk-3SDCALS3.cjs.map +1 -0
- package/dist/{chunk-QA42VDTA.cjs → chunk-3SJ7TVL6.cjs} +12 -11
- package/dist/chunk-3SJ7TVL6.cjs.map +1 -0
- package/dist/{chunk-V5BTVGPA.js → chunk-4NERCBOW.js} +4 -3
- package/dist/chunk-4NERCBOW.js.map +1 -0
- package/dist/{chunk-UYQBEPNZ.js → chunk-6NY7OTOO.js} +15 -8
- package/dist/chunk-6NY7OTOO.js.map +1 -0
- package/dist/{chunk-RZ52SEYV.cjs → chunk-7GYAUA56.cjs} +10 -9
- package/dist/chunk-7GYAUA56.cjs.map +1 -0
- package/dist/{chunk-BAKLJKFZ.cjs → chunk-APZ6JFN6.cjs} +4 -29
- package/dist/chunk-APZ6JFN6.cjs.map +1 -0
- package/dist/chunk-BURYHHBM.js +114 -0
- package/dist/chunk-BURYHHBM.js.map +1 -0
- package/dist/{chunk-H63O5HPW.cjs → chunk-CQNKZ435.cjs} +8 -7
- package/dist/chunk-CQNKZ435.cjs.map +1 -0
- package/dist/chunk-EANGKAKG.cjs +32 -0
- package/dist/chunk-EANGKAKG.cjs.map +1 -0
- package/dist/{chunk-YTGRXD23.js → chunk-FEOFX4QW.js} +4 -25
- package/dist/chunk-FEOFX4QW.js.map +1 -0
- package/dist/{chunk-QJW3K6JN.js → chunk-G7AGM2Y7.js} +4 -3
- package/dist/chunk-G7AGM2Y7.js.map +1 -0
- package/dist/{chunk-HZMILQYO.js → chunk-GM4PXDZ2.js} +5 -4
- package/dist/chunk-GM4PXDZ2.js.map +1 -0
- package/dist/{chunk-QHK6PTUR.js → chunk-IKV2I7KT.js} +5 -4
- package/dist/chunk-IKV2I7KT.js.map +1 -0
- package/dist/{chunk-TI23GIFM.cjs → chunk-JH77MAON.cjs} +12 -11
- package/dist/chunk-JH77MAON.cjs.map +1 -0
- package/dist/{chunk-6DTW5T3Z.js → chunk-JSRKES2Z.js} +5 -4
- package/dist/chunk-JSRKES2Z.js.map +1 -0
- package/dist/chunk-JVWLXVYG.cjs +27 -0
- package/dist/chunk-JVWLXVYG.cjs.map +1 -0
- package/dist/{chunk-UQIRDE52.cjs → chunk-LX2X4KC7.cjs} +6 -5
- package/dist/chunk-LX2X4KC7.cjs.map +1 -0
- package/dist/{chunk-KWKB74KL.js → chunk-MKWBLNTD.js} +198 -44
- package/dist/chunk-MKWBLNTD.js.map +1 -0
- package/dist/chunk-NWGQ7JML.cjs +117 -0
- package/dist/chunk-NWGQ7JML.cjs.map +1 -0
- package/dist/{chunk-6RRFH7C4.cjs → chunk-NZM3M5IR.cjs} +39 -54
- package/dist/chunk-NZM3M5IR.cjs.map +1 -0
- package/dist/chunk-PPFVTXGJ.js +27 -0
- package/dist/chunk-PPFVTXGJ.js.map +1 -0
- package/dist/{chunk-3TJSFPO4.cjs → chunk-PROKRNL2.cjs} +26 -19
- package/dist/chunk-PROKRNL2.cjs.map +1 -0
- package/dist/chunk-TEAVPUMX.js +22 -0
- package/dist/chunk-TEAVPUMX.js.map +1 -0
- package/dist/{chunk-ALH6TUX2.js → chunk-THHFIP4N.js} +5 -4
- package/dist/chunk-THHFIP4N.js.map +1 -0
- package/dist/chunk-TL3GVPBO.cjs +234 -0
- package/dist/chunk-TL3GVPBO.cjs.map +1 -0
- package/dist/{chunk-XEGBGKHV.cjs → chunk-TXMP5AOW.cjs} +206 -52
- package/dist/chunk-TXMP5AOW.cjs.map +1 -0
- package/dist/{chunk-KP57YCYQ.js → chunk-UXV3V5NH.js} +23 -35
- package/dist/chunk-UXV3V5NH.js.map +1 -0
- package/dist/{chunk-H3IRVX7J.js → chunk-WSPVQR4O.js} +9 -9
- package/dist/chunk-WSPVQR4O.js.map +1 -0
- package/dist/{chunk-JKMBFHER.cjs → chunk-X4YU6BX7.cjs} +12 -11
- package/dist/chunk-X4YU6BX7.cjs.map +1 -0
- package/dist/chunk-YGJE7RSW.js +231 -0
- package/dist/chunk-YGJE7RSW.js.map +1 -0
- package/dist/components/ActivityPreview.cjs +14 -11
- package/dist/components/ActivityPreview.d.cts +7 -3
- package/dist/components/ActivityPreview.d.ts +7 -3
- package/dist/components/ActivityPreview.js +13 -10
- package/dist/components/ActivitySequence.cjs +14 -11
- package/dist/components/ActivitySequence.d.cts +75 -11
- package/dist/components/ActivitySequence.d.ts +75 -11
- package/dist/components/ActivitySequence.js +13 -10
- package/dist/components/Dictation.cjs +7 -4
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +6 -3
- package/dist/components/FillInTheBlanks.cjs +7 -4
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +6 -3
- package/dist/components/GapSelect.cjs +7 -4
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +6 -3
- package/dist/components/InteractiveVideo.cjs +14 -11
- package/dist/components/InteractiveVideo.d.cts +2 -2
- package/dist/components/InteractiveVideo.d.ts +2 -2
- package/dist/components/InteractiveVideo.js +12 -9
- package/dist/components/MultipleChoice.cjs +7 -4
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +6 -3
- package/dist/components/PronunciationFeedback.cjs +4 -3
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/PronunciationFeedback.js +3 -2
- package/dist/components/ReadAloud.cjs +6 -4
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +5 -3
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/WrittenResponse.cjs +4 -3
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +3 -2
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/index.cjs +34 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/{strings-PCYKWCwd.d.ts → types-BY-4S5Rd.d.ts} +761 -761
- package/dist/{strings-BOPKhnS-.d.cts → types-DtPYG1hh.d.cts} +761 -761
- package/package.json +11 -1
- package/dist/chunk-3TJSFPO4.cjs.map +0 -1
- package/dist/chunk-5F4Q6JT7.cjs +0 -289
- package/dist/chunk-5F4Q6JT7.cjs.map +0 -1
- package/dist/chunk-6DTW5T3Z.js.map +0 -1
- package/dist/chunk-6RRFH7C4.cjs.map +0 -1
- package/dist/chunk-ALH6TUX2.js.map +0 -1
- package/dist/chunk-BAKLJKFZ.cjs.map +0 -1
- package/dist/chunk-GR33G5QM.js +0 -286
- package/dist/chunk-GR33G5QM.js.map +0 -1
- package/dist/chunk-H3IRVX7J.js.map +0 -1
- package/dist/chunk-H63O5HPW.cjs.map +0 -1
- package/dist/chunk-HZMILQYO.js.map +0 -1
- package/dist/chunk-JKMBFHER.cjs.map +0 -1
- package/dist/chunk-KP57YCYQ.js.map +0 -1
- package/dist/chunk-KWKB74KL.js.map +0 -1
- package/dist/chunk-QA42VDTA.cjs.map +0 -1
- package/dist/chunk-QHK6PTUR.js.map +0 -1
- package/dist/chunk-QJW3K6JN.js.map +0 -1
- package/dist/chunk-RZ52SEYV.cjs.map +0 -1
- package/dist/chunk-TI23GIFM.cjs.map +0 -1
- package/dist/chunk-UQIRDE52.cjs.map +0 -1
- package/dist/chunk-UYQBEPNZ.js.map +0 -1
- package/dist/chunk-V5BTVGPA.js.map +0 -1
- package/dist/chunk-XEGBGKHV.cjs.map +0 -1
- package/dist/chunk-YTGRXD23.js.map +0 -1
- package/dist/chunk-Z5XY66PO.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/MultipleChoice/MultipleChoice.tsx","../src/components/MultipleChoice/index.tsx"],"names":["useLkStrings","useLearnerAi","useMemo","isDevelopment","validateActivity","ActivitySchemaError","useRef","useActivityState","useState","useEffect","randomSessionId","seededShuffle","score","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","jsxs","jsx","Fragment","ActivityMedia","AiHints","FeedbackRegion","AiExplanation","MultipleChoice","ActivityErrorBoundary"],"mappings":"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","file":"chunk-X4YU6BX7.cjs","sourcesContent":["'use client';\n\nimport {\n ActivitySchemaError,\n type ItemOutcome,\n type LearnerResponse,\n type MultipleChoiceData,\n type MultipleChoiceLearnerResponse,\n type MultipleChoiceOption,\n type ScoringDetail,\n score,\n seededShuffle,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport { type CSSProperties, Fragment, useEffect, useMemo, useRef, useState } from 'react';\nimport { useLearnerAi } from '../../ai/LkAiProvider.js';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor, randomSessionId } from '../_internal.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { AiExplanation, AiHints } from '../shared/AiHelp.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport type { ActivityProps } from '../types.js';\n\n/**\n * Selected option ids carried by a learner response. Any other response shape\n * (a caller wiring the wrong item's state through `value`) reads as \"nothing\n * selected\" rather than throwing mid-exam.\n */\nfunction selectionOf(response: LearnerResponse | undefined): string[] {\n return response?.type === 'multiple-choice' ? response.selectedOptionIds : [];\n}\n\n/**\n * Whether an option is part of the correct answer, derived from the SERVER's\n * outcome — review mode never holds an answer key of its own.\n * {@link ScoringDetail.outcome} states this unambiguously. For 0.2-era details\n * that carry only the deprecated `correct` flag (which means \"the learner\n * ACTED correctly on this option\", not \"this option is the answer\") the fact\n * is still recoverable, because we know whether the learner selected it:\n * `isCorrect = wasSelected ? correct : !correct`.\n */\nfunction isAnswerOption(detail: ScoringDetail, wasSelected: boolean): boolean {\n switch (detail.outcome) {\n case 'correct':\n case 'incorrect-omission':\n return true;\n case 'incorrect':\n case 'correct-omission':\n return false;\n default:\n return wasSelected ? detail.correct : !detail.correct;\n }\n}\n\n/**\n * Announcement for `review` mode, built only from the server-supplied outcome.\n *\n * The authored overall feedback is appended by this function rather than being\n * baked into the score sentence, so a translation of \"Score 80%. Passed.\" never\n * has to carry the author's words with it.\n */\nfunction reviewAnnouncement(outcome: ItemOutcome | undefined, s: LkStrings): string | null {\n if (outcome === undefined) {\n return null;\n }\n if (outcome.status === 'scored') {\n const overall = outcome.feedback;\n return `${s.scoreAnnouncement(Math.round(outcome.score * 100), outcome.passed)}${\n overall ? ` ${overall}` : ''\n }`;\n }\n if (outcome.status === 'deferred') {\n // Never \"0%\": ungraded is not the same as wrong.\n return s.notGradedYet;\n }\n if (outcome.status === 'unscorable') {\n return s.noGradeAvailable;\n }\n // `graded` is left silent here deliberately: a returned GradeRecord on a\n // multiple-choice item has no rubric to render, and announcing a bare\n // percentage would duplicate what the marking already shows.\n return null;\n}\n\nexport interface MultipleChoiceProps extends ActivityProps<MultipleChoiceData> {\n /**\n * Seed for the deterministic option shuffle. Supply one (e.g. the attempt\n * id) to make the shuffled order reproducible server-side, stable across\n * page reloads, and identical between SSR and hydration. When absent, a\n * random per-mount session seed is used (order stable within the mount\n * only — the behaviour before `shuffleSeed` existed).\n */\n shuffleSeed?: string;\n}\n\nexport function MultipleChoice({\n data,\n onComplete,\n onSubmit,\n value,\n defaultValue,\n defaultSubmitted,\n onChange,\n renderMode = 'practice',\n outcome,\n sanitizeHtml,\n mediaBudget,\n mediaStrings,\n onInteraction,\n theme,\n locale,\n disabled,\n shuffleSeed,\n strings,\n ai: aiProp,\n}: MultipleChoiceProps) {\n const s = useLkStrings(strings);\n const ai = useLearnerAi(aiProp);\n\n // Dev-only boundary validation (Req 2.3). Throwing during render lets\n // ActivityErrorBoundary catch it. Memoised so it only re-runs on data change.\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // A `redact()` projection cannot satisfy the full content schema — the\n // answer key is gone by design — and must not be checked against the\n // strict redacted schema either, since a `reveal: 'after-submit'`\n // projection legitimately carries answer-key fields for review renders.\n if (data.redacted === true) {\n return null;\n }\n const result = validateActivity('multiple-choice', data);\n return result.success ? null : new ActivitySchemaError('multiple-choice', result.errors);\n }, [data]);\n\n // Lazily created only when shuffling without a caller seed: avoids calling\n // crypto.randomUUID (absent on non-secure http origins) unless needed.\n const sessionIdRef = useRef<string | null>(null);\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n // Uncontrolled state. `defaultValue` seeds the mount only (React convention);\n // to re-seed later, remount with a `key` or drive the component with `value`.\n const [internalSelection, setInternalSelection] = useState<string[]>(() =>\n selectionOf(defaultValue),\n );\n const [summary, setSummary] = useState<string | null>(null);\n\n // Reset on data-prop CHANGE (Req 3.7). The identity guard makes the mount\n // run a no-op, which it always was before `defaultValue` existed — without\n // it this effect would wipe the seed immediately after the first render.\n // (It also makes the reset StrictMode-safe: a remount with unchanged data\n // no longer discards the learner's selection.)\n // Resets back to `defaultValue`, not to empty. Clearing looked safer — it\n // cannot carry a stale answer into a different question — but `data`\n // identity is a poor proxy for \"different question\": a parent that builds\n // entries in render (`activities={raw.map(redact)}`, the documented exam\n // pattern) hands over a new object every render, and clearing wiped every\n // RESTORED answer on the first unrelated re-render, silently. `defaultValue`\n // is by definition the caller's seed for the CURRENT data, which is what\n // FillInTheBlanks already reset to; the two now agree.\n const defaultValueRef = useRef(defaultValue);\n useEffect(() => {\n defaultValueRef.current = defaultValue;\n }, [defaultValue]);\n // A data change must return the item to its SEED, submitted state included —\n // resetting to idle unlocked work the learner had already committed.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n const lastDataRef = useRef(data);\n useEffect(() => {\n if (lastDataRef.current === data) {\n return;\n }\n lastDataRef.current = data;\n setInternalSelection(selectionOf(defaultValueRef.current));\n setSummary(null);\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [data, reset]);\n\n const displayedOptions = useMemo<MultipleChoiceOption[]>(() => {\n if (!data.shuffle) {\n return [...data.options];\n }\n // biome-ignore lint/suspicious/noAssignInExpressions: sanctioned lazy ref initialization\n const seedSource = shuffleSeed ?? (sessionIdRef.current ??= randomSessionId());\n // The seed string is unchanged from when the algorithm lived in this file,\n // so an order a consumer recorded against a seed still reproduces.\n return seededShuffle(data.options, `${seedSource}:${data.id}`);\n }, [data, shuffleSeed]);\n\n // Per-option correctness for `review`, indexed by option id. Null unless the\n // caller supplied a SCORED outcome — the only source of truth this mode has.\n const reviewDetails = useMemo(() => {\n if (renderMode !== 'review' || outcome === undefined || outcome.status !== 'scored') {\n return null;\n }\n return new Map(outcome.details.map((detail) => [detail.itemId, detail]));\n }, [renderMode, outcome]);\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // Practice grades locally, which a redacted projection cannot support:\n // `score()` would throw from the submit handler — where no error boundary\n // can catch it — AFTER the learner has answered. Fail loudly at render\n // instead, in production too. Wiring an exam item into the self-grading\n // mode is precisely the accident `renderMode` exists to prevent.\n if (data.redacted === true && renderMode === 'practice') {\n throw new Error(\n `Multiple Choice \"${data.id}\" received redacted activity data in renderMode \"practice\", ` +\n 'which grades locally and has no answer key to grade against. ' +\n 'Render redacted data with renderMode=\"exam\" (server grades) or \"review\" (pass `outcome`).',\n );\n }\n\n const isSingle = data.mode === 'single';\n const isExam = renderMode === 'exam';\n const isReview = renderMode === 'review';\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const isControlled = value !== undefined;\n const selected = isControlled ? selectionOf(value) : internalSelection;\n /**\n * Whether correctness and authored feedback may be shown. Practice earns it\n * by grading locally after submit; review shows what the server already\n * graded. Exam never reveals anything — not before, not after submit.\n */\n const reveal = isReview || (renderMode === 'practice' && submitted);\n\n const fireInteraction = (\n type: 'option-selected' | 'option-deselected' | 'submitted',\n payload: Record<string, unknown>,\n ): void => {\n onInteraction?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n };\n\n /** Single funnel for every response change: owns state only when uncontrolled. */\n const emitChange = (selectedOptionIds: string[]): void => {\n if (!isControlled) {\n setInternalSelection(selectedOptionIds);\n }\n onChange?.({ type: 'multiple-choice', selectedOptionIds });\n };\n\n const selectSingle = (optionId: string): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange([optionId]);\n fireInteraction('option-selected', { optionId });\n };\n\n const toggleMulti = (optionId: string, checked: boolean): void => {\n if (inactive) {\n return;\n }\n if (state === 'idle') {\n start();\n }\n emitChange(checked ? [...selected, optionId] : selected.filter((id) => id !== optionId));\n fireInteraction(checked ? 'option-selected' : 'option-deselected', { optionId });\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive) {\n return;\n }\n const response: MultipleChoiceLearnerResponse = {\n type: 'multiple-choice',\n selectedOptionIds: selected,\n };\n // Always first, and before anything that can throw: an exam runner must be\n // able to persist the raw response no matter what happens after.\n onSubmit?.(response);\n\n if (isExam) {\n // No score(), no answer key read, no onComplete, no xAPI — the server\n // grades. The announcement deliberately carries no correctness signal.\n complete();\n setSummary(s.answerSubmitted);\n fireInteraction('submitted', { selectedOptionIds: selected });\n return;\n }\n\n const scoringResult = score('multiple-choice', data as MultipleChoiceData, response);\n complete();\n const timeSpent = getTimeSpent();\n const xapiStatement = xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n // Activity-type IRI, interaction type and correct-responses pattern\n // come from the REGISTERED descriptor, so a consumer-registered type\n // gets correct interop without touching this component.\n ...xapiDefinitionFor(data),\n choices: data.options.map((option) => ({\n id: option.id,\n description: { [data.locale ?? 'en-US']: option.text },\n })),\n },\n scoringResult,\n timeSpentMs: timeSpent,\n response: selected.join(','),\n });\n onComplete?.({\n score: scoringResult.score,\n maxScore: scoringResult.maxScore,\n passed: scoringResult.passed,\n timeSpent,\n xapiStatement,\n });\n // Core selects the authored overall feedback on `passed` (B3 fix).\n const overall = scoringResult.feedback;\n setSummary(\n `${s.answerSubmitted} ${s.scoreAnnouncement(\n Math.round(scoringResult.score * 100),\n scoringResult.passed,\n )}${overall ? ` ${overall}` : ''}`,\n );\n fireInteraction('submitted', {\n selectedOptionIds: selected,\n score: scoringResult.score,\n });\n };\n\n const questionId = `${data.id}-question`;\n // Rich text renders ONLY through a caller-supplied sanitiser; without one we\n // fall back to the plain-text field, escaped by React (types.ts HtmlSanitizer).\n const questionHtml =\n sanitizeHtml !== undefined && data.questionHtml !== undefined\n ? sanitizeHtml(data.questionHtml)\n : null;\n\n const optionList = displayedOptions.map((option) => {\n const checked = selected.includes(option.id);\n // `data-correct` means \"this option is part of the correct answer\".\n let correctness: string | undefined;\n if (isReview) {\n const detail = reviewDetails?.get(option.id);\n correctness = detail === undefined ? undefined : String(isAnswerOption(detail, checked));\n } else if (reveal) {\n correctness = String(option.isCorrect);\n }\n const media = option.media;\n // A picture goes INSIDE the label: it has no controls of its own, clicking\n // it is how a learner picks that option, and its `alt` joins the option's\n // accessible name. A recording cannot — see below.\n const picture =\n media?.type === 'image' ? (\n <img className=\"lk-mc-option-image\" src={media.url} alt={media.alt ?? ''} />\n ) : null;\n\n const label = (\n <label className=\"lk-mc-option\" data-correct={correctness}>\n <input\n type={isSingle ? 'radio' : 'checkbox'}\n name={isSingle ? `${data.id}-options` : undefined}\n value={option.id}\n checked={checked}\n disabled={inactive}\n aria-disabled={inactive || undefined}\n onChange={(e) =>\n isSingle ? selectSingle(option.id) : toggleMulti(option.id, e.target.checked)\n }\n />\n <span>{option.text}</span>\n {picture}\n {reveal && option.feedback ? (\n <span className=\"lk-mc-option-feedback\" role=\"note\">\n {option.feedback}\n </span>\n ) : null}\n </label>\n );\n\n if (media?.type !== 'audio') {\n return <Fragment key={option.id}>{label}</Fragment>;\n }\n\n // A recording sits OUTSIDE the label, deliberately. A <label> activates its\n // control for any click inside it, so an <audio> nested in one would select\n // the option the moment the learner pressed play — and a listening item is\n // answered by comparing all the recordings before choosing any of them.\n // Selecting an option must stay something the learner does on purpose.\n return (\n <div className=\"lk-mc-option-media\" key={option.id}>\n {label}\n {/* biome-ignore lint/a11y/useMediaCaption: captions are optional in the data contract — a <track> is rendered when captionsUrl is provided; absence is the author's documented choice (Req 14.5) */}\n <audio\n className=\"lk-mc-option-audio\"\n src={media.url}\n controls\n preload=\"none\"\n aria-label={media.alt ?? option.text}\n >\n {media.captionsUrl ? (\n <track kind=\"captions\" src={media.captionsUrl} label={media.alt ?? option.text} />\n ) : null}\n </audio>\n </div>\n );\n });\n\n return (\n <div className=\"lk-mc\" lang={locale} style={theme as CSSProperties | undefined}>\n {data.media ? (\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n <form onSubmit={handleSubmit}>\n <fieldset disabled={inactive}>\n {questionHtml === null ? (\n <legend id={questionId}>{data.question}</legend>\n ) : (\n <legend\n id={questionId}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is the output of the caller-supplied sanitizeHtml (types.ts HtmlSanitizer contract)\n dangerouslySetInnerHTML={{ __html: questionHtml }}\n />\n )}\n {/*\n single: an explicit radiogroup is meaningful (fieldset's implicit\n role is `group`, not `radiogroup`). multi: the fieldset + legend\n already provide a named group — an explicit role=\"group\" here would\n be a redundant, duplicate same-named group in the a11y tree, so we\n use a plain layout div. (Refines the design ARIA sketch.)\n */}\n {isSingle ? (\n <div role=\"radiogroup\" aria-labelledby={questionId}>\n {optionList}\n </div>\n ) : (\n <div>{optionList}</div>\n )}\n <AiHints\n ai={ai}\n data={data}\n renderMode={renderMode}\n submitted={submitted}\n disabled={disabled === true}\n response={{ type: 'multiple-choice', selectedOptionIds: selected }}\n locale={locale}\n onInteraction={onInteraction}\n strings={s}\n />\n {/* review is read-only: there is nothing left to submit. */}\n {isReview ? null : (\n <button type=\"submit\" disabled={inactive}>\n {s.submit}\n </button>\n )}\n </fieldset>\n </form>\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? reviewAnnouncement(outcome, s) : summary}\n </FeedbackRegion>\n <AiExplanation\n ai={ai}\n data={data}\n renderMode={renderMode}\n submitted={submitted}\n response={{ type: 'multiple-choice', selectedOptionIds: selected }}\n outcome={outcome}\n locale={locale}\n onInteraction={onInteraction}\n strings={s}\n />\n </div>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport {\n MultipleChoice as MultipleChoiceCore,\n type MultipleChoiceProps,\n} from './MultipleChoice.js';\n\nexport type { MultipleChoiceProps } from './MultipleChoice.js';\n\n/**\n * Public Multiple Choice activity: the core component wrapped in\n * `ActivityErrorBoundary` so a render failure (incl. the dev schema-validation\n * throw) degrades to an accessible fallback instead of crashing the host.\n */\nexport function MultipleChoice(props: MultipleChoiceProps) {\n // Read here rather than in the boundary: a class component cannot call a\n // hook, and the fallback it renders is learner-facing text like any other.\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <MultipleChoiceCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useLearnerAi } from './chunk-YNDKAYW3.js';
|
|
3
|
+
import { SequenceSlotContext } from './chunk-TEAVPUMX.js';
|
|
4
|
+
import { isDevelopment } from './chunk-PPFVTXGJ.js';
|
|
5
|
+
import { aiExplanationRequest, checkAiExplanation, aiHintRequest, checkAiHint, aiSupports, aiAllowedByContent } from '@intellectif/lk-core';
|
|
6
|
+
import { useContext, useState, useRef, useEffect, useCallback } from 'react';
|
|
7
|
+
|
|
8
|
+
var DEFAULT_MAX_HINTS = 3;
|
|
9
|
+
var MAX_HINTS_CEILING = 10;
|
|
10
|
+
function hintLimit(ai) {
|
|
11
|
+
const asked = ai?.maxHints;
|
|
12
|
+
return typeof asked === "number" && Number.isInteger(asked) && asked >= 1 ? Math.min(asked, MAX_HINTS_CEILING) : DEFAULT_MAX_HINTS;
|
|
13
|
+
}
|
|
14
|
+
function hintOffered(input) {
|
|
15
|
+
const { data, renderMode, submitted, disabled } = input;
|
|
16
|
+
return renderMode === "practice" && !submitted && !disabled && data.redacted !== true && aiSupports(data.type, "hint") && aiAllowedByContent(data, "hint");
|
|
17
|
+
}
|
|
18
|
+
function explanationOffered(input) {
|
|
19
|
+
const { data, renderMode, submitted, outcome } = input;
|
|
20
|
+
const graded = renderMode === "practice" && submitted || renderMode === "review" && outcome?.status === "scored";
|
|
21
|
+
return graded && aiSupports(data.type, "explanation") && aiAllowedByContent(data, "explanation");
|
|
22
|
+
}
|
|
23
|
+
function callPort(call) {
|
|
24
|
+
try {
|
|
25
|
+
return Promise.resolve(call());
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return Promise.reject(error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function warnRefused(feature, activityId, reason) {
|
|
31
|
+
if (isDevelopment()) {
|
|
32
|
+
console.warn(
|
|
33
|
+
`learning-kit: the AI ${feature} for "${activityId}" was not shown (${reason}). The learner saw "not available" instead.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
var localeOf = (ai, locale) => {
|
|
38
|
+
const chosen = ai?.learnerLocale ?? locale;
|
|
39
|
+
return chosen === void 0 ? {} : { learnerLocale: chosen };
|
|
40
|
+
};
|
|
41
|
+
function explanationKey(renderMode, submitted, response, outcome) {
|
|
42
|
+
let graded = "";
|
|
43
|
+
if (outcome !== void 0) {
|
|
44
|
+
graded = `${outcome.status}:${"score" in outcome ? outcome.score : ""}`;
|
|
45
|
+
}
|
|
46
|
+
return `${renderMode}|${submitted}|${graded}|${JSON.stringify(response)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/ai/useAiHelp.ts
|
|
50
|
+
function modeInForce(asked, around) {
|
|
51
|
+
if (around === "exam") {
|
|
52
|
+
return "exam";
|
|
53
|
+
}
|
|
54
|
+
return asked ?? around ?? "practice";
|
|
55
|
+
}
|
|
56
|
+
function useAiExplanation(input) {
|
|
57
|
+
const { data, response, submitted, outcome } = input;
|
|
58
|
+
const renderMode = modeInForce(input.renderMode, useContext(SequenceSlotContext)?.renderMode);
|
|
59
|
+
const ai = useLearnerAi(input.ai);
|
|
60
|
+
const resetKey = explanationKey(renderMode, submitted, response, outcome);
|
|
61
|
+
const [state, setState] = useState({ key: resetKey, status: "idle" });
|
|
62
|
+
const run = useRef(0);
|
|
63
|
+
const controller = useRef(null);
|
|
64
|
+
const current = state.key === resetKey ? state : { key: resetKey, status: "idle" };
|
|
65
|
+
const offered = ai?.explain !== void 0 && explanationOffered({ data, renderMode, submitted, outcome });
|
|
66
|
+
const latest = useRef({ input, renderMode, ai, resetKey, offered, current });
|
|
67
|
+
latest.current = { input, renderMode, ai, resetKey, offered, current };
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
return () => {
|
|
70
|
+
run.current += 1;
|
|
71
|
+
controller.current?.abort();
|
|
72
|
+
controller.current = null;
|
|
73
|
+
};
|
|
74
|
+
}, [resetKey]);
|
|
75
|
+
const ask = useCallback(() => {
|
|
76
|
+
const now = latest.current;
|
|
77
|
+
const port = now.ai?.explain;
|
|
78
|
+
if (port === void 0 || !now.offered || now.current.status === "loading") {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const key = now.resetKey;
|
|
82
|
+
const request = aiExplanationRequest({
|
|
83
|
+
data: now.input.data,
|
|
84
|
+
response: now.input.response,
|
|
85
|
+
...now.renderMode === "review" && now.input.outcome !== void 0 ? { outcome: now.input.outcome } : {},
|
|
86
|
+
...localeOf(now.ai, now.input.locale)
|
|
87
|
+
});
|
|
88
|
+
if (request === null) {
|
|
89
|
+
setState({ key, status: "unavailable" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
run.current += 1;
|
|
93
|
+
const mine = run.current;
|
|
94
|
+
controller.current?.abort();
|
|
95
|
+
const abort = new AbortController();
|
|
96
|
+
controller.current = abort;
|
|
97
|
+
setState({ key, status: "loading" });
|
|
98
|
+
callPort(() => port(request, { signal: abort.signal })).then(
|
|
99
|
+
(raw) => {
|
|
100
|
+
if (run.current !== mine) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const checked = checkAiExplanation(raw, request);
|
|
104
|
+
if (!checked.ok) {
|
|
105
|
+
warnRefused("explanation", now.input.data.id, checked.refusal);
|
|
106
|
+
setState({ key, status: "unavailable" });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
setState({ key, status: "shown", result: checked.result });
|
|
110
|
+
latest.current.input.onInteraction?.({
|
|
111
|
+
type: "ai-explanation-shown",
|
|
112
|
+
activityId: now.input.data.id,
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
payload: checked.result.provenance !== void 0 ? { provenance: checked.result.provenance } : {}
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
() => {
|
|
118
|
+
if (run.current === mine) {
|
|
119
|
+
setState({ key, status: "unavailable" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}, []);
|
|
124
|
+
return {
|
|
125
|
+
offered,
|
|
126
|
+
status: current.status,
|
|
127
|
+
explanation: current.status === "shown" ? current.result : null,
|
|
128
|
+
ask
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function useAiHints(input) {
|
|
132
|
+
const { data, submitted } = input;
|
|
133
|
+
const renderMode = modeInForce(input.renderMode, useContext(SequenceSlotContext)?.renderMode);
|
|
134
|
+
const disabled = input.disabled === true;
|
|
135
|
+
const ai = useLearnerAi(input.ai);
|
|
136
|
+
const resetKey = data.id;
|
|
137
|
+
const [state, setState] = useState({ key: resetKey, hints: [], status: "idle" });
|
|
138
|
+
const run = useRef(0);
|
|
139
|
+
const controller = useRef(null);
|
|
140
|
+
const current = state.key === resetKey ? state : { key: resetKey, hints: [], status: "idle" };
|
|
141
|
+
const offered = ai?.hint !== void 0 && hintOffered({ data, renderMode, submitted, disabled });
|
|
142
|
+
const limit = hintLimit(ai);
|
|
143
|
+
const latest = useRef({ input, renderMode, ai, offered, limit, current });
|
|
144
|
+
latest.current = { input, renderMode, ai, offered, limit, current };
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
return () => {
|
|
147
|
+
run.current += 1;
|
|
148
|
+
controller.current?.abort();
|
|
149
|
+
controller.current = null;
|
|
150
|
+
};
|
|
151
|
+
}, [resetKey]);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (!offered) {
|
|
154
|
+
run.current += 1;
|
|
155
|
+
controller.current?.abort();
|
|
156
|
+
controller.current = null;
|
|
157
|
+
setState(
|
|
158
|
+
(previous) => previous.status === "loading" ? { ...previous, status: "idle" } : previous
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}, [offered]);
|
|
162
|
+
const ask = useCallback(() => {
|
|
163
|
+
const now = latest.current;
|
|
164
|
+
const port = now.ai?.hint;
|
|
165
|
+
const held = now.current;
|
|
166
|
+
if (port === void 0 || !now.offered || held.status === "loading" || held.hints.length >= now.limit) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const key = held.key;
|
|
170
|
+
const request = aiHintRequest({
|
|
171
|
+
data: now.input.data,
|
|
172
|
+
response: now.input.response,
|
|
173
|
+
previousHints: held.hints.map((hint) => hint.text),
|
|
174
|
+
...localeOf(now.ai, now.input.locale)
|
|
175
|
+
});
|
|
176
|
+
if (request === null) {
|
|
177
|
+
setState({ ...held, status: "unavailable" });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
run.current += 1;
|
|
181
|
+
const mine = run.current;
|
|
182
|
+
controller.current?.abort();
|
|
183
|
+
const abort = new AbortController();
|
|
184
|
+
controller.current = abort;
|
|
185
|
+
setState({ ...held, status: "loading" });
|
|
186
|
+
callPort(() => port(request, { signal: abort.signal })).then(
|
|
187
|
+
(raw) => {
|
|
188
|
+
if (run.current !== mine) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const checked = checkAiHint(raw, request);
|
|
192
|
+
if (!checked.ok) {
|
|
193
|
+
warnRefused("hint", now.input.data.id, checked.refusal);
|
|
194
|
+
setState((shown) => ({ ...shown, status: "unavailable" }));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
setState((shown) => ({
|
|
198
|
+
...shown,
|
|
199
|
+
hints: shown.key === key ? [...shown.hints, checked.result] : shown.hints,
|
|
200
|
+
status: "idle"
|
|
201
|
+
}));
|
|
202
|
+
latest.current.input.onInteraction?.({
|
|
203
|
+
type: "ai-hint-shown",
|
|
204
|
+
activityId: now.input.data.id,
|
|
205
|
+
timestamp: Date.now(),
|
|
206
|
+
payload: {
|
|
207
|
+
hintNumber: request.hintNumber,
|
|
208
|
+
...checked.result.provenance !== void 0 ? { provenance: checked.result.provenance } : {}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
() => {
|
|
213
|
+
if (run.current === mine) {
|
|
214
|
+
setState((shown) => ({ ...shown, status: "unavailable" }));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}, []);
|
|
219
|
+
return {
|
|
220
|
+
offered,
|
|
221
|
+
status: current.status,
|
|
222
|
+
hints: current.hints,
|
|
223
|
+
limit,
|
|
224
|
+
used: current.hints.length,
|
|
225
|
+
ask
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { useAiExplanation, useAiHints };
|
|
230
|
+
//# sourceMappingURL=chunk-YGJE7RSW.js.map
|
|
231
|
+
//# sourceMappingURL=chunk-YGJE7RSW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai/rules.ts","../src/ai/useAiHelp.ts"],"names":[],"mappings":"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","file":"chunk-YGJE7RSW.js","sourcesContent":["/**\n * When AI help is offered, and what the SDK does with what a port answers.\n *\n * Internal: these are the rules behind {@link useAiHints} and\n * {@link useAiExplanation}, which are what a host calls. They live apart from\n * the hooks so the hooks' module exports nothing but the hooks themselves.\n */\nimport {\n type AiRefusal,\n aiAllowedByContent,\n aiSupports,\n type ItemOutcome,\n type LearnerResponse,\n} from '@intellectif/lk-core';\nimport { isDevelopment } from '../components/_internal.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport type { LearnerAi } from './LkAiProvider.js';\n\n/** The most hints a question gives when the host names no number, and the most it may name. */\nconst DEFAULT_MAX_HINTS = 3;\nconst MAX_HINTS_CEILING = 10;\n\n/** `maxHints` as a whole number from 1 to 10; anything else is the default. */\nexport function hintLimit(ai: LearnerAi | undefined): number {\n const asked = ai?.maxHints;\n return typeof asked === 'number' && Number.isInteger(asked) && asked >= 1\n ? Math.min(asked, MAX_HINTS_CEILING)\n : DEFAULT_MAX_HINTS;\n}\n\n/**\n * Whether the learner may ask for a hint: `practice`, before submit, on a\n * question that is not disabled, of a type that takes hints, whose author left\n * hints on, and whole — a redacted item is an exam's, and gives none.\n */\nexport function hintOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n disabled: boolean;\n}): boolean {\n const { data, renderMode, submitted, disabled } = input;\n return (\n renderMode === 'practice' &&\n !submitted &&\n !disabled &&\n data.redacted !== true &&\n aiSupports(data.type, 'hint') &&\n aiAllowedByContent(data, 'hint')\n );\n}\n\n/**\n * Whether the learner may ask for an explanation: of a graded answer — in\n * `practice` once submitted, or in `review` when the grade of record is\n * scored — of a type the SDK explains, whose author left explanations on.\n * Never in `exam`.\n */\nexport function explanationOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n outcome: ItemOutcome | undefined;\n}): boolean {\n const { data, renderMode, submitted, outcome } = input;\n const graded =\n (renderMode === 'practice' && submitted) ||\n (renderMode === 'review' && outcome?.status === 'scored');\n return graded && aiSupports(data.type, 'explanation') && aiAllowedByContent(data, 'explanation');\n}\n\n/** Calls a port, turning a synchronous throw into a rejection. */\nexport function callPort<T>(call: () => Promise<T>): Promise<T> {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n}\n\n/** Tells a developer why a port's answer was not shown. Silent in production. */\nexport function warnRefused(\n feature: 'explanation' | 'hint',\n activityId: string,\n reason: AiRefusal,\n): void {\n if (isDevelopment()) {\n console.warn(\n `learning-kit: the AI ${feature} for \"${activityId}\" was not shown (${reason}). The learner saw \"not available\" instead.`,\n );\n }\n}\n\n/** The language to write in: the host's choice for the learner, else the interface language. */\nexport const localeOf = (\n ai: LearnerAi | undefined,\n locale: string | undefined,\n): { learnerLocale?: string } => {\n const chosen = ai?.learnerLocale ?? locale;\n return chosen === undefined ? {} : { learnerLocale: chosen };\n};\n\n/** What an explanation is about: when any of it changes, the explanation shown no longer is. */\nexport function explanationKey(\n renderMode: RenderMode,\n submitted: boolean,\n response: LearnerResponse,\n outcome: ItemOutcome | undefined,\n): string {\n let graded = '';\n if (outcome !== undefined) {\n graded = `${outcome.status}:${'score' in outcome ? outcome.score : ''}`;\n }\n return `${renderMode}|${submitted}|${graded}|${JSON.stringify(response)}`;\n}\n","'use client';\n\nimport {\n type AiTextResult,\n aiExplanationRequest,\n aiHintRequest,\n checkAiExplanation,\n checkAiHint,\n type InteractionEvent,\n type ItemOutcome,\n type LearnerResponse,\n} from '@intellectif/lk-core';\nimport { useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport { SequenceSlotContext } from '../components/shared/sequence-slot.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport { type LearnerAi, useLearnerAi } from './LkAiProvider.js';\nimport {\n callPort,\n explanationKey,\n explanationOffered,\n hintLimit,\n hintOffered,\n localeOf,\n warnRefused,\n} from './rules.js';\n\n/**\n * The question AI help is being asked about, as the SDK's own components know\n * it. A host drawing its own question passes what it has; everything optional\n * defaults to what the components pass.\n */\nexport interface AiHelpSituation {\n /** The question, as `data` is given to any activity component. */\n data: RenderableActivity;\n /** The learner's answer as it stands: submitted, restored, or being written. */\n response: LearnerResponse;\n /** Whether the learner has committed that answer. */\n submitted: boolean;\n /**\n * Pass on the `renderMode` your question was given. Left out, the paper\n * around the question answers for it — a question inside `<ActivitySequence>`\n * or `<InteractiveVideo>` knows its mode — and `practice` only where there is\n * no paper at all. An `exam` around the question wins over anything passed\n * here.\n */\n renderMode?: RenderMode;\n /**\n * The AI ports to use, overriding {@link LkAiProvider}'s for this question.\n * A question inside `<ActivitySequence>` or `<InteractiveVideo>` is handed\n * the ports in force as `ai` — pass that straight in.\n */\n ai?: LearnerAi;\n /** The interface language: the default language for what a model writes. */\n locale?: string;\n /** Where `ai-hint-shown` and `ai-explanation-shown` go. */\n onInteraction?: (event: InteractionEvent) => void;\n}\n\nexport interface AiExplanationInput extends AiHelpSituation {\n /** The grade of record, which `review` explains. */\n outcome?: ItemOutcome;\n}\n\nexport interface AiExplanationHelp {\n /**\n * Whether to offer the learner an explanation at all: a port is present, the\n * answer is graded, the type is one the SDK explains, and the author left\n * explanations on. False in `exam`, always.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'shown' | 'unavailable';\n /** What to show, once it has arrived and been checked. Its `text` is text, never HTML. */\n explanation: AiTextResult | null;\n /**\n * Asks the port, when the learner presses something. Does nothing while a\n * call is on its way, and nothing at all where `offered` is false — so an\n * exam cannot reach a model through this hook, whatever ports are in scope.\n *\n * One identity for the life of the question, so it is safe in an effect's\n * dependencies.\n */\n ask: () => void;\n}\n\nexport interface AiHintsInput extends AiHelpSituation {\n /** A disabled question gives no hints, as it takes no answer. */\n disabled?: boolean;\n}\n\nexport interface AiHintsHelp {\n /**\n * Whether the learner may ask for hints here at all: a port is present, the\n * mode is `practice`, the answer is not in, the question is not disabled or\n * redacted, the type takes hints and the author left them on. It stays true\n * once `used` reaches `limit` — there are no more to give, but the question\n * is still one that gives them.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'unavailable';\n /** Every hint shown for this question, in the order they were given. */\n hints: readonly AiTextResult[];\n /** The host's `maxHints`, as a whole number from 1 to 10 (3 by default). */\n limit: number;\n /** How many hints have been shown. A refused hint is not counted. */\n used: number;\n /**\n * Asks for the next hint. Does nothing at the limit, while a call is on its\n * way, or where `offered` is false. One identity for the life of the\n * question.\n */\n ask: () => void;\n}\n\n/**\n * The mode a question is answered in: what the caller says, and what the paper\n * around it says.\n *\n * A host drawing its own question is given `renderMode` like every other prop\n * and is expected to pass it on, but nothing makes it — and a hook that took\n * `practice` on trust would offer hints on a paper of record, as soon as an\n * `LkAiProvider` sat anywhere above the pager. So an `exam` around the question\n * wins, and where the caller says nothing the paper answers for it. A question\n * standing on its own belongs to no paper, and is `practice` as before.\n */\nfunction modeInForce(asked: RenderMode | undefined, around: RenderMode | undefined): RenderMode {\n if (around === 'exam') {\n return 'exam';\n }\n return asked ?? around ?? 'practice';\n}\n\ntype ExplanationState =\n | { key: string; status: 'idle' | 'loading' | 'unavailable' }\n | { key: string; status: 'shown'; result: AiTextResult };\n\n/**\n * \"Explain my answer\", without the SDK's own button and panel: the rules, the\n * call, the checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * offers help on exactly the same terms: never in `exam`, never where the\n * item's author switched explanations off, and never an explanation whose\n * verdict contradicts the grade the SDK computed.\n *\n * ```tsx\n * const { offered, status, explanation, ask } = useAiExplanation({\n * data, response, submitted, renderMode, outcome, ai: question.ai,\n * });\n * if (!offered) return null;\n * return explanation === null ? (\n * <button type=\"button\" onClick={ask} aria-busy={status === 'loading'}>Explain my answer</button>\n * ) : (\n * <section>\n * <p>{explanation.text}</p>\n * <p>Written by AI. It can make mistakes.</p>\n * </section>\n * );\n * ```\n *\n * **Say who wrote it.** The SDK's own panel carries \"Written by AI. It can make\n * mistakes.\"; a page that drops that line passes a model's words off as the\n * course's.\n *\n * When the answer or its grade changes, the explanation is dropped and a call\n * still on its way is abandoned — a late explanation never lands beside an\n * answer it was not written for — as it is when the caller unmounts.\n */\nexport function useAiExplanation(input: AiExplanationInput): AiExplanationHelp {\n const { data, response, submitted, outcome } = input;\n const renderMode = modeInForce(input.renderMode, useContext(SequenceSlotContext)?.renderMode);\n const ai = useLearnerAi(input.ai);\n const resetKey = explanationKey(renderMode, submitted, response, outcome);\n const [state, setState] = useState<ExplanationState>({ key: resetKey, status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: ExplanationState =\n state.key === resetKey ? state : { key: resetKey, status: 'idle' };\n const offered =\n ai?.explain !== undefined && explanationOffered({ data, renderMode, submitted, outcome });\n\n // What `ask` reads, as of this render, so `ask` itself can keep one identity\n // for the life of the question. The state goes in too: an event handler runs\n // after the render that set it, so the ref is what the learner last saw.\n const latest = useRef({ input, renderMode, ai, resetKey, offered, current });\n latest.current = { input, renderMode, ai, resetKey, offered, current };\n\n // Another answer, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the answer changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.explain;\n if (port === undefined || !now.offered || now.current.status === 'loading') {\n return;\n }\n const key = now.resetKey;\n const request = aiExplanationRequest({\n data: now.input.data,\n response: now.input.response,\n ...(now.renderMode === 'review' && now.input.outcome !== undefined\n ? { outcome: now.input.outcome }\n : {}),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ key, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ key, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiExplanation(raw, request);\n if (!checked.ok) {\n warnRefused('explanation', now.input.data.id, checked.refusal);\n setState({ key, status: 'unavailable' });\n return;\n }\n setState({ key, status: 'shown', result: checked.result });\n latest.current.input.onInteraction?.({\n type: 'ai-explanation-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload:\n checked.result.provenance !== undefined\n ? { provenance: checked.result.provenance }\n : {},\n });\n },\n () => {\n if (run.current === mine) {\n setState({ key, status: 'unavailable' });\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n explanation: current.status === 'shown' ? current.result : null,\n ask,\n };\n}\n\ninterface HintState {\n key: string;\n hints: AiTextResult[];\n status: 'idle' | 'loading' | 'unavailable';\n}\n\n/**\n * \"Get a hint\", without the SDK's own button and list: the rules, the call, the\n * checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * gives hints on exactly the same terms: only in `practice` before submit, up\n * to the host's limit, never on a redacted item or a type the SDK does not hint\n * for, and never a hint that gives the answer away — which is refused rather\n * than shown, and not counted.\n *\n * ```tsx\n * const { offered, status, hints, used, limit, ask } = useAiHints({\n * data, response, submitted, renderMode, ai: question.ai,\n * });\n * ```\n *\n * **Say who wrote them**, as the SDK's own list does: \"Written by AI. It can\n * make mistakes.\"\n *\n * Keep the hints on screen after submit — the learner can see what they were\n * given — while `offered` turns false. Another question starts with none.\n */\nexport function useAiHints(input: AiHintsInput): AiHintsHelp {\n const { data, submitted } = input;\n const renderMode = modeInForce(input.renderMode, useContext(SequenceSlotContext)?.renderMode);\n const disabled = input.disabled === true;\n const ai = useLearnerAi(input.ai);\n const resetKey = data.id;\n const [state, setState] = useState<HintState>({ key: resetKey, hints: [], status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: HintState =\n state.key === resetKey ? state : { key: resetKey, hints: [], status: 'idle' };\n const offered = ai?.hint !== undefined && hintOffered({ data, renderMode, submitted, disabled });\n const limit = hintLimit(ai);\n\n const latest = useRef({ input, renderMode, ai, offered, limit, current });\n latest.current = { input, renderMode, ai, offered, limit, current };\n\n // Another question, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the question changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n // Submitted, or disabled, while a hint was on its way: it would arrive for an\n // answer that can no longer use it.\n useEffect(() => {\n if (!offered) {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n setState((previous) =>\n previous.status === 'loading' ? { ...previous, status: 'idle' } : previous,\n );\n }\n }, [offered]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.hint;\n const held = now.current;\n if (\n port === undefined ||\n !now.offered ||\n held.status === 'loading' ||\n held.hints.length >= now.limit\n ) {\n return;\n }\n const key = held.key;\n const request = aiHintRequest({\n data: now.input.data,\n response: now.input.response,\n previousHints: held.hints.map((hint) => hint.text),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ ...held, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ ...held, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiHint(raw, request);\n if (!checked.ok) {\n warnRefused('hint', now.input.data.id, checked.refusal);\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n return;\n }\n setState((shown) => ({\n ...shown,\n hints: shown.key === key ? [...shown.hints, checked.result] : shown.hints,\n status: 'idle',\n }));\n latest.current.input.onInteraction?.({\n type: 'ai-hint-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: {\n hintNumber: request.hintNumber,\n ...(checked.result.provenance !== undefined\n ? { provenance: checked.result.provenance }\n : {}),\n },\n });\n },\n () => {\n if (run.current === mine) {\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n hints: current.hints,\n limit,\n used: current.hints.length,\n ask,\n };\n}\n"]}
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
require('../chunk-
|
|
6
|
-
require('../chunk-
|
|
7
|
-
require('../chunk-
|
|
8
|
-
require('../chunk-
|
|
9
|
-
require('../chunk-
|
|
10
|
-
require('../chunk-
|
|
4
|
+
var chunk3SDCALS3_cjs = require('../chunk-3SDCALS3.cjs');
|
|
5
|
+
require('../chunk-CQNKZ435.cjs');
|
|
6
|
+
require('../chunk-X4YU6BX7.cjs');
|
|
7
|
+
require('../chunk-JH77MAON.cjs');
|
|
8
|
+
require('../chunk-3SJ7TVL6.cjs');
|
|
9
|
+
require('../chunk-7GYAUA56.cjs');
|
|
10
|
+
require('../chunk-NWGQ7JML.cjs');
|
|
11
|
+
require('../chunk-TL3GVPBO.cjs');
|
|
11
12
|
require('../chunk-ZDYNAWDZ.cjs');
|
|
12
|
-
require('../chunk-
|
|
13
|
-
require('../chunk-
|
|
13
|
+
require('../chunk-NZM3M5IR.cjs');
|
|
14
|
+
require('../chunk-LX2X4KC7.cjs');
|
|
14
15
|
require('../chunk-UDCQTOVU.cjs');
|
|
15
16
|
require('../chunk-OD65OZIW.cjs');
|
|
17
|
+
require('../chunk-JVWLXVYG.cjs');
|
|
16
18
|
require('../chunk-BRPEB43C.cjs');
|
|
17
|
-
require('../chunk-
|
|
19
|
+
require('../chunk-APZ6JFN6.cjs');
|
|
20
|
+
require('../chunk-EANGKAKG.cjs');
|
|
18
21
|
require('../chunk-L7WY5YFQ.cjs');
|
|
19
22
|
require('../chunk-XOR4MPEN.cjs');
|
|
20
23
|
require('../chunk-AHGB5HH6.cjs');
|
|
@@ -23,7 +26,7 @@ require('../chunk-AHGB5HH6.cjs');
|
|
|
23
26
|
|
|
24
27
|
Object.defineProperty(exports, "ActivityPreview", {
|
|
25
28
|
enumerable: true,
|
|
26
|
-
get: function () { return
|
|
29
|
+
get: function () { return chunk3SDCALS3_cjs.ActivityPreview; }
|
|
27
30
|
});
|
|
28
31
|
//# sourceMappingURL=ActivityPreview.cjs.map
|
|
29
32
|
//# sourceMappingURL=ActivityPreview.cjs.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { LearnerResponse, ItemOutcome, DraftNotComplete, ThemeTokens } from '@intellectif/lk-core';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { e as RenderMode, H as HtmlSanitizer, a as LkStringsOverride } from '../
|
|
4
|
+
import { e as RenderMode, H as HtmlSanitizer, a as LkStringsOverride } from '../types-DtPYG1hh.cjs';
|
|
5
5
|
import { ActivityRenderer } from './ActivitySequence.cjs';
|
|
6
6
|
import '../ai/LkAiProvider.cjs';
|
|
7
7
|
import '../machine-CuwKqWXC.cjs';
|
|
8
|
-
import '../WrittenResponse-
|
|
8
|
+
import '../WrittenResponse-DT_Yqbq1.cjs';
|
|
9
9
|
|
|
10
10
|
interface ActivityPreviewProps {
|
|
11
11
|
/**
|
|
@@ -41,7 +41,11 @@ interface ActivityPreviewProps {
|
|
|
41
41
|
* Renderers for activity types beyond the built-ins, keyed by `type`, as on
|
|
42
42
|
* `<ActivitySequence>`. An entry receives the shared prop contract and
|
|
43
43
|
* nothing else, so — like the shuffle seed below — the in-memory recording
|
|
44
|
-
* binding a previewed read-aloud is given does not reach one.
|
|
44
|
+
* binding a previewed read-aloud is given does not reach one. Nor does the
|
|
45
|
+
* `question` a sequence hands a renderer: a preview is one question with no
|
|
46
|
+
* set around it, so there is no slot to report to and nothing to page away
|
|
47
|
+
* from. A renderer that uses it must treat it as optional, which its type
|
|
48
|
+
* already says.
|
|
45
49
|
*/
|
|
46
50
|
renderers?: Readonly<Record<string, ActivityRenderer>>;
|
|
47
51
|
/**
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { LearnerResponse, ItemOutcome, DraftNotComplete, ThemeTokens } from '@intellectif/lk-core';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { e as RenderMode, H as HtmlSanitizer, a as LkStringsOverride } from '../
|
|
4
|
+
import { e as RenderMode, H as HtmlSanitizer, a as LkStringsOverride } from '../types-BY-4S5Rd.js';
|
|
5
5
|
import { ActivityRenderer } from './ActivitySequence.js';
|
|
6
6
|
import '../ai/LkAiProvider.js';
|
|
7
7
|
import '../machine-CuwKqWXC.js';
|
|
8
|
-
import '../WrittenResponse-
|
|
8
|
+
import '../WrittenResponse-CeHY5u3W.js';
|
|
9
9
|
|
|
10
10
|
interface ActivityPreviewProps {
|
|
11
11
|
/**
|
|
@@ -41,7 +41,11 @@ interface ActivityPreviewProps {
|
|
|
41
41
|
* Renderers for activity types beyond the built-ins, keyed by `type`, as on
|
|
42
42
|
* `<ActivitySequence>`. An entry receives the shared prop contract and
|
|
43
43
|
* nothing else, so — like the shuffle seed below — the in-memory recording
|
|
44
|
-
* binding a previewed read-aloud is given does not reach one.
|
|
44
|
+
* binding a previewed read-aloud is given does not reach one. Nor does the
|
|
45
|
+
* `question` a sequence hands a renderer: a preview is one question with no
|
|
46
|
+
* set around it, so there is no slot to report to and nothing to page away
|
|
47
|
+
* from. A renderer that uses it must treat it as optional, which its type
|
|
48
|
+
* already says.
|
|
45
49
|
*/
|
|
46
50
|
renderers?: Readonly<Record<string, ActivityRenderer>>;
|
|
47
51
|
/**
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { ActivityPreview } from '../chunk-
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-
|
|
5
|
-
import '../chunk-
|
|
6
|
-
import '../chunk-
|
|
7
|
-
import '../chunk-
|
|
8
|
-
import '../chunk-
|
|
2
|
+
export { ActivityPreview } from '../chunk-WSPVQR4O.js';
|
|
3
|
+
import '../chunk-4NERCBOW.js';
|
|
4
|
+
import '../chunk-JSRKES2Z.js';
|
|
5
|
+
import '../chunk-IKV2I7KT.js';
|
|
6
|
+
import '../chunk-THHFIP4N.js';
|
|
7
|
+
import '../chunk-GM4PXDZ2.js';
|
|
8
|
+
import '../chunk-BURYHHBM.js';
|
|
9
|
+
import '../chunk-YGJE7RSW.js';
|
|
9
10
|
import '../chunk-YNDKAYW3.js';
|
|
10
|
-
import '../chunk-
|
|
11
|
-
import '../chunk-
|
|
11
|
+
import '../chunk-UXV3V5NH.js';
|
|
12
|
+
import '../chunk-G7AGM2Y7.js';
|
|
12
13
|
import '../chunk-MCO52PV5.js';
|
|
13
14
|
import '../chunk-YORQQXGO.js';
|
|
15
|
+
import '../chunk-TEAVPUMX.js';
|
|
14
16
|
import '../chunk-RCMYEWBE.js';
|
|
15
|
-
import '../chunk-
|
|
17
|
+
import '../chunk-FEOFX4QW.js';
|
|
18
|
+
import '../chunk-PPFVTXGJ.js';
|
|
16
19
|
import '../chunk-BY4UQH2Y.js';
|
|
17
20
|
import '../chunk-6N43WDVG.js';
|
|
18
21
|
import '../chunk-YRNEOBXA.js';
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkTXMP5AOW_cjs = require('../chunk-TXMP5AOW.cjs');
|
|
5
5
|
require('../chunk-3FBBP5QR.cjs');
|
|
6
|
-
require('../chunk-
|
|
7
|
-
require('../chunk-
|
|
8
|
-
require('../chunk-
|
|
9
|
-
require('../chunk-
|
|
10
|
-
require('../chunk-
|
|
11
|
-
require('../chunk-
|
|
6
|
+
require('../chunk-CQNKZ435.cjs');
|
|
7
|
+
require('../chunk-X4YU6BX7.cjs');
|
|
8
|
+
require('../chunk-JH77MAON.cjs');
|
|
9
|
+
require('../chunk-3SJ7TVL6.cjs');
|
|
10
|
+
require('../chunk-7GYAUA56.cjs');
|
|
11
|
+
require('../chunk-NWGQ7JML.cjs');
|
|
12
|
+
require('../chunk-TL3GVPBO.cjs');
|
|
12
13
|
require('../chunk-ZDYNAWDZ.cjs');
|
|
13
|
-
require('../chunk-
|
|
14
|
-
require('../chunk-
|
|
14
|
+
require('../chunk-NZM3M5IR.cjs');
|
|
15
|
+
require('../chunk-LX2X4KC7.cjs');
|
|
15
16
|
require('../chunk-UDCQTOVU.cjs');
|
|
16
17
|
require('../chunk-OD65OZIW.cjs');
|
|
18
|
+
require('../chunk-JVWLXVYG.cjs');
|
|
17
19
|
require('../chunk-BRPEB43C.cjs');
|
|
18
|
-
require('../chunk-
|
|
20
|
+
require('../chunk-APZ6JFN6.cjs');
|
|
21
|
+
require('../chunk-EANGKAKG.cjs');
|
|
19
22
|
require('../chunk-L7WY5YFQ.cjs');
|
|
20
23
|
require('../chunk-XOR4MPEN.cjs');
|
|
21
24
|
require('../chunk-AHGB5HH6.cjs');
|
|
@@ -24,7 +27,7 @@ require('../chunk-AHGB5HH6.cjs');
|
|
|
24
27
|
|
|
25
28
|
Object.defineProperty(exports, "ActivitySequence", {
|
|
26
29
|
enumerable: true,
|
|
27
|
-
get: function () { return
|
|
30
|
+
get: function () { return chunkTXMP5AOW_cjs.ActivitySequence; }
|
|
28
31
|
});
|
|
29
32
|
//# sourceMappingURL=ActivitySequence.cjs.map
|
|
30
33
|
//# sourceMappingURL=ActivitySequence.cjs.map
|