@intellectif/lk-core 1.3.0 → 1.4.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 +14 -0
- package/dist/{ai-B222dtHH.d.ts → ai-CnmLPMzd.d.cts} +265 -2
- package/dist/ai-CnmLPMzd.d.cts.map +1 -0
- package/dist/{ai-B222dtHH.d.cts → ai-CnmLPMzd.d.ts} +265 -2
- package/dist/ai-CnmLPMzd.d.ts.map +1 -0
- package/dist/ai-check.cjs +101 -8
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.cts.map +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.d.ts.map +1 -1
- package/dist/ai-check.js +101 -8
- package/dist/ai-check.js.map +1 -1
- package/dist/{ai-writing-CCBjTOM3.js → ai-writing-CPpH9Qxq.js} +126 -21
- package/dist/ai-writing-CPpH9Qxq.js.map +1 -0
- package/dist/{ai-writing-DCVkv4Zj.cjs → ai-writing-G6tnCGzw.cjs} +114 -3
- package/dist/ai-writing-G6tnCGzw.cjs.map +1 -0
- package/dist/{index-BWPRPIXL.d.ts → index-2nigSf4N.d.cts} +3 -217
- package/dist/index-2nigSf4N.d.cts.map +1 -0
- package/dist/{index-O4_ExlAD.d.cts → index-B54baFUA.d.ts} +3 -217
- package/dist/index-B54baFUA.d.ts.map +1 -0
- package/dist/{index-Dyu66R4-.d.cts → index-BAfEHCnD.d.cts} +2 -2
- package/dist/{index-Dyu66R4-.d.cts.map → index-BAfEHCnD.d.cts.map} +1 -1
- package/dist/{index-CXyOV8sm.d.cts → index-BqMXWuNC.d.cts} +2 -2
- package/dist/{index-CXyOV8sm.d.cts.map → index-BqMXWuNC.d.cts.map} +1 -1
- package/dist/{index-UGxRSopE.d.ts → index-CVWvymVb.d.ts} +2 -2
- package/dist/{index-UGxRSopE.d.ts.map → index-CVWvymVb.d.ts.map} +1 -1
- package/dist/{index-CegM8o15.d.ts → index-CcHDTxIZ.d.ts} +2 -2
- package/dist/{index-CegM8o15.d.ts.map → index-CcHDTxIZ.d.ts.map} +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +35 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +35 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/schemas.d.cts +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/package.json +1 -1
- package/dist/ai-B222dtHH.d.cts.map +0 -1
- package/dist/ai-B222dtHH.d.ts.map +0 -1
- package/dist/ai-writing-CCBjTOM3.js.map +0 -1
- package/dist/ai-writing-DCVkv4Zj.cjs.map +0 -1
- package/dist/index-BWPRPIXL.d.ts.map +0 -1
- package/dist/index-O4_ExlAD.d.cts.map +0 -1
package/dist/ai-check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-check.js","names":[],"sources":["../src/ai-check/cases.ts","../src/ai-check/index.ts"],"sourcesContent":["import { aiExplanationRequest, aiHintRequest } from '../ai.js';\nimport { aiCoachingRequest } from '../ai-coaching.js';\nimport { aiCritiqueRequest } from '../ai-critique.js';\nimport { aiDraftsRequest } from '../ai-drafts.js';\nimport { aiWritingFeedbackRequest } from '../ai-writing.js';\nimport { gradeReadAloud } from '../scoring/speech/grade.js';\nimport type {\n ActivityType,\n DictationData,\n FillInTheBlanksData,\n GapSelectData,\n LearnerResponse,\n MultipleChoiceData,\n ReadAloudData,\n WrittenResponseData,\n} from '../types/activity.js';\nimport type {\n AiCoachingRequest,\n AiCritiqueRequest,\n AiDraftSource,\n AiDraftsRequest,\n AiDraftType,\n AiExplanationRequest,\n AiHintRequest,\n AiWritingFeedbackRequest,\n} from '../types/ai.js';\nimport type { SpeechAssessment, SpeechWord } from '../types/speech.js';\n\n/**\n * The items the cases are built from. Small, ordinary language-course content\n * — and deliberately awkward in the places a model slips:\n *\n * - an answer a hint can hardly avoid naming (`Madrid`);\n * - an answer of one short word (`is`), which a hint may write as an ordinary\n * word and must not write beside its neighbour in the passage;\n * - an accented answer (`cañón`), so a hint that spells it without the accent\n * is still caught;\n * - a passage with more than one blank, so a hint can be about the wrong one;\n * - a short essay whose mistakes a model must quote exactly as the learner\n * wrote them — straight apostrophes a model likes to curl, a mistake made\n * twice — so a correction of words nobody wrote is caught.\n */\nconst capital: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-mc',\n title: 'Capital cities',\n question: 'What is the capital of Spain?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'madrid', text: 'Madrid', isCorrect: true },\n { id: 'barcelona', text: 'Barcelona', isCorrect: false },\n { id: 'seville', text: 'Seville', isCorrect: false },\n ],\n};\n\nconst introduction: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-fib',\n title: 'Introducing yourself',\n passage: 'My name {{be}} Rossi and I {{live}} in Madrid.',\n blanks: [\n { id: 'be', acceptedAnswers: ['is'] },\n { id: 'live', acceptedAnswers: ['live'], hint: 'Present simple, first person.' },\n ],\n scoringStrategy: 'partial',\n};\n\nconst landscape: GapSelectData = {\n schemaVersion: '1.0',\n type: 'gap-select',\n id: 'ai-check-gap',\n title: 'Geography',\n locale: 'es-ES',\n passage: 'El río pasa por un {{g1}} muy profundo, y el agua {{g2}} fría.',\n gaps: [\n {\n id: 'g1',\n choices: [\n { id: 'canon', text: 'cañón' },\n { id: 'campo', text: 'campo' },\n ],\n correctChoiceId: 'canon',\n },\n {\n id: 'g2',\n choices: [\n { id: 'esta', text: 'está' },\n { id: 'es', text: 'es' },\n ],\n correctChoiceId: 'esta',\n },\n ],\n scoringStrategy: 'partial',\n};\n\nconst listening: DictationData = {\n schemaVersion: '1.0',\n type: 'dictation',\n id: 'ai-check-dictation',\n title: 'Listen and type',\n transcript: 'The train leaves at half past nine.',\n};\n\nconst weekend: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-writing',\n title: 'Your weekend',\n prompt: 'Write a few sentences about what you did last weekend.',\n minWords: 15,\n maxWords: 120,\n languageTarget: 'en-A2',\n rubric: {\n criteria: [\n { name: 'Grammar', description: 'Past tenses, articles, agreement', weight: 2 },\n { name: 'Vocabulary', description: 'Everyday words, used correctly', weight: 1 },\n { name: 'Task', description: 'Says what the learner did, in order', weight: 1 },\n ],\n },\n};\n\nconst weather: ReadAloudData = {\n schemaVersion: '1.0',\n type: 'read-aloud',\n id: 'ai-check-reading',\n title: 'Read the forecast aloud',\n referenceText: 'The weather is warm in the north.',\n locale: 'en-US',\n recording: { maxSeconds: 20 },\n scoring: {\n dimensions: [\n { name: 'accuracy', weight: 2 },\n { name: 'fluency', weight: 1 },\n ],\n },\n};\n\n/** A word of the forecast as an engine reports it. */\nconst said = (text: string, over: Partial<SpeechWord> = {}): SpeechWord => ({\n text,\n error: 'none',\n accuracy: 92,\n ...over,\n});\n\n/**\n * An engine's assessment of the forecast. With `slips`, \"weather\" is\n * mispronounced — its \"th\" heard as /d/ or /z/ — and \"north\" is left out: the\n * marks a model must coach, and no others.\n */\nfunction forecast(slips: boolean): SpeechAssessment {\n return {\n assessmentVersion: '1.0',\n status: 'assessed',\n task: 'scripted',\n locale: 'en-US',\n referenceText: weather.referenceText,\n recordingKey: 'ai-check-take',\n assessor: { kind: 'auto', id: 'pronunciation-engine' },\n scale: 100,\n scores: slips ? { accuracy: 68, fluency: 81, completeness: 86 } : { accuracy: 94, fluency: 90 },\n miscue: 'assessor',\n phonemeAlphabet: 'ipa',\n words: [\n said('The'),\n slips\n ? said('weather', {\n accuracy: 38,\n error: 'mispronunciation',\n phonemes: [\n { symbol: 'w', accuracy: 90 },\n { symbol: 'ɛ', accuracy: 85 },\n {\n symbol: 'ð',\n accuracy: 12,\n heardAs: [\n { symbol: 'd', score: 64 },\n { symbol: 'z', score: 21 },\n { symbol: 'ð', score: 12 },\n ],\n },\n { symbol: 'ɚ', accuracy: 80 },\n ],\n })\n : said('weather'),\n said('is'),\n said('warm'),\n said('in'),\n said('the'),\n slips ? { text: 'north', error: 'omission' } : said('north'),\n ],\n };\n}\n\n/**\n * One call to make against a host's port, and what a learner was looking at\n * when the SDK would have made it.\n */\nexport interface AiCheckCase {\n /** Stable across releases, so a host can name one in an allow-list or a report. */\n id: string;\n /** Which port the case is for. */\n feature:\n | 'explanation'\n | 'hint'\n | 'writing-feedback'\n | 'pronunciation-coaching'\n | 'item-critique'\n | 'draft-generation';\n /** What the learner did, in a few words, for a report a person reads. */\n about: string;\n /** The request, built by the SDK exactly as a component builds it. */\n request:\n | AiExplanationRequest\n | AiHintRequest\n | AiWritingFeedbackRequest\n | AiCoachingRequest\n | AiCritiqueRequest\n | AiDraftsRequest;\n}\n\nconst chose = (ids: string[]): LearnerResponse => ({\n type: 'multiple-choice',\n selectedOptionIds: ids,\n});\nconst typed = (answers: Record<string, string>): LearnerResponse => ({\n type: 'fill-in-the-blanks',\n answers,\n});\nconst picked = (choices: Record<string, string>): LearnerResponse => ({\n type: 'gap-select',\n selections: choices,\n});\nconst wrote = (text: string): LearnerResponse => ({ type: 'dictation', text });\nconst drafted = (text: string): LearnerResponse => ({\n type: 'written-response',\n text,\n wordCount: 0,\n});\n\n/**\n * Items an author asks a model to review. Each is valid, and each but the last\n * has a flaw a rule cannot see: a distractor that is botanically a right\n * answer, and a task far above the level it is set for.\n */\nconst fruit: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-fruit',\n title: 'Food words',\n question: 'Which of these is a fruit?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'f1', text: 'Apple', isCorrect: true },\n { id: 'f2', text: 'Carrot', isCorrect: false },\n { id: 'f3', text: 'Tomato', isCorrect: false },\n ],\n};\n\nconst tooHard: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-too-hard',\n title: 'Cities',\n prompt: 'Discuss the socioeconomic ramifications of rapid urbanisation in developing economies.',\n minWords: 40,\n maxWords: 80,\n};\n\nconst cleanCloze: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-clean-cloze',\n title: 'Daily routine',\n passage: 'Every morning I {{1}} up at seven and {{2}} a shower.',\n blanks: [\n { id: '1', acceptedAnswers: ['get', 'wake'] },\n { id: '2', acceptedAnswers: ['take', 'have'] },\n ],\n scoringStrategy: 'partial',\n};\n\nfunction critiqueCase(\n id: string,\n about: string,\n type: ActivityType,\n draft: unknown,\n extra: { level?: string; authorLocale?: string } = {},\n): AiCheckCase {\n const request = aiCritiqueRequest({ type, draft, ...extra });\n if (request === null) {\n throw new Error(`ai-check: no critique request for \"${id}\"`);\n }\n return { id, feature: 'item-critique', about, request };\n}\n\n/** A short reading to draft from: plain, and with facts a question can be asked about. */\nconst MARKET =\n 'Maria lives in Seville, in the south of Spain. Every Saturday she walks to the market with her ' +\n 'grandmother. They buy oranges, fresh bread and a little cheese. On the way home they stop at a ' +\n 'café, where Maria drinks hot chocolate and her grandmother reads the newspaper.';\n\n/** The same reading as a video's captions, with times in seconds. */\nconst MARKET_CAPTIONS = [\n { start: 0, end: 4.2, text: 'Maria lives in Seville, in the south of Spain.' },\n { start: 4.2, end: 8.9, text: 'Every Saturday she walks to the market with her grandmother.' },\n { start: 8.9, end: 12.6, text: 'They buy oranges, fresh bread and a little cheese.' },\n { start: 12.6, end: 18.1, text: 'On the way home they stop at a café.' },\n];\n\nfunction draftsCase(\n id: string,\n about: string,\n types: AiDraftType[],\n source: AiDraftSource,\n extra: Omit<Parameters<typeof aiDraftsRequest>[0], 'types' | 'source'> = {},\n): AiCheckCase {\n const request = aiDraftsRequest({ types, source, ...extra });\n if (request === null) {\n throw new Error(`ai-check: no drafts request for \"${id}\"`);\n }\n return { id, feature: 'draft-generation', about, request };\n}\n\n/**\n * What a host sets on every read-aloud it drafts: the SDK has no default for a\n * recording's length or the dimensions a reading is graded on.\n */\nconst READ_ALOUD_SETTINGS = {\n locale: 'en-US',\n recording: { maxSeconds: 20 },\n scoring: { dimensions: [{ name: 'accuracy', weight: 1 }] },\n};\n\n/** Builds a case, failing loudly rather than shipping a case with no request. */\nfunction explanationCase(\n id: string,\n about: string,\n data: Parameters<typeof aiExplanationRequest>[0]['data'],\n response: LearnerResponse,\n): AiCheckCase {\n const request = aiExplanationRequest({ data, response });\n if (request === null) {\n throw new Error(`ai-check: no explanation request for \"${id}\"`);\n }\n return { id, feature: 'explanation', about, request };\n}\n\nfunction hintCase(\n id: string,\n about: string,\n data: Parameters<typeof aiHintRequest>[0]['data'],\n response: LearnerResponse,\n previousHints: string[] = [],\n): AiCheckCase {\n const request = aiHintRequest({ data, response, previousHints });\n if (request === null) {\n throw new Error(`ai-check: no hint request for \"${id}\"`);\n }\n return { id, feature: 'hint', about, request };\n}\n\nfunction writingCase(\n id: string,\n about: string,\n text: string,\n previousFeedback: string[] = [],\n learnerLocale?: string,\n): AiCheckCase {\n const request = aiWritingFeedbackRequest({\n data: weekend,\n response: drafted(text),\n previousFeedback,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n });\n if (request === null) {\n throw new Error(`ai-check: no writing feedback request for \"${id}\"`);\n }\n return { id, feature: 'writing-feedback', about, request };\n}\n\nfunction coachingCase(\n id: string,\n about: string,\n marks: { assessment?: SpeechAssessment; fromGradeOnly?: boolean },\n learnerLocale?: string,\n): AiCheckCase {\n const assessment = marks.assessment ?? forecast(true);\n const grade = gradeReadAloud(\n weather,\n { type: 'read-aloud', recording: { key: 'ai-check-take', mimeType: 'audio/wav' } },\n assessment,\n {\n measured: { durationMs: 4000, voicedMs: 3000 },\n plausibility: { maxWordsPerSecond: 6, minVoicedMs: 500 },\n },\n );\n if ('unscorable' in grade) {\n throw new Error(`ai-check: the reading for \"${id}\" did not grade: ${grade.reason}`);\n }\n const request = aiCoachingRequest({\n data: weather,\n ...(marks.fromGradeOnly === true ? {} : { assessment }),\n grade,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n });\n if (request === null) {\n throw new Error(`ai-check: no coaching request for \"${id}\"`);\n }\n return { id, feature: 'pronunciation-coaching', about, request };\n}\n\n/**\n * The cases {@link runAiCheck} runs: every type the SDK explains, answered\n * right, wrong and — where a type can be — partly right; every type it hints\n * for, before an answer and after a wrong one; and drafts of an essay, with\n * mistakes, without, and revised.\n *\n * They are ordinary calls. What makes them a test is that the SDK knows the\n * answer to each, so it can check what your model writes about it.\n *\n * A fresh array each call, so a caller may filter, slice or add to it without\n * changing what the next caller gets.\n */\nexport function aiCheckCases(): AiCheckCase[] {\n return [\n explanationCase('mc-correct', 'Chose the right city', capital, chose(['madrid'])),\n explanationCase('mc-wrong', 'Chose Barcelona', capital, chose(['barcelona'])),\n explanationCase('mc-blank', 'Chose nothing and submitted', capital, chose([])),\n explanationCase(\n 'fib-correct',\n 'Filled both blanks correctly',\n introduction,\n typed({ be: 'is', live: 'live' }),\n ),\n explanationCase(\n 'fib-partly',\n 'One blank right, one wrong',\n introduction,\n typed({ be: 'is', live: 'lives' }),\n ),\n explanationCase(\n 'fib-wrong',\n 'Both blanks wrong',\n introduction,\n typed({ be: 'are', live: 'living' }),\n ),\n explanationCase(\n 'gap-partly',\n 'One gap right, one wrong, in Spanish',\n landscape,\n picked({ g1: 'canon', g2: 'es' }),\n ),\n explanationCase(\n 'gap-wrong',\n 'Both gaps wrong, in Spanish',\n landscape,\n picked({ g1: 'campo', g2: 'es' }),\n ),\n explanationCase(\n 'dictation-near',\n 'Typed the sentence with two words wrong',\n listening,\n wrote('The train leaves at half past five'),\n ),\n explanationCase(\n 'dictation-correct',\n 'Typed the sentence correctly',\n listening,\n wrote('The train leaves at half past nine.'),\n ),\n hintCase('mc-hint-1', 'Has not answered yet', capital, chose([])),\n hintCase('mc-hint-2', 'Asked again after choosing Seville', capital, chose(['seville']), [\n 'Think about where the government sits.',\n ]),\n hintCase('fib-hint-1', 'Both blanks empty', introduction, typed({})),\n hintCase(\n 'fib-hint-2',\n 'Asked again with one blank wrong',\n introduction,\n typed({ be: 'are', live: '' }),\n ['Look again at the first blank.'],\n ),\n hintCase('gap-hint-1', 'Neither gap chosen, in Spanish', landscape, picked({})),\n hintCase(\n 'gap-hint-2',\n 'Asked again with the first gap wrong',\n landscape,\n picked({ g1: 'campo' }),\n ['Piensa en la forma del terreno.'],\n ),\n writingCase(\n 'writing-mistakes',\n 'A first draft with tense, article and agreement mistakes',\n \"Last weekend I go to the market and buyed a apple. My friends was there too, so we don't go home early. Then I go to the cinema.\",\n ),\n writingCase(\n 'writing-clean',\n 'A first draft with no mistakes to correct',\n 'Last weekend I went to the market and bought an apple. My friends were there too, so we stayed until the evening. Then we went to the cinema.',\n ),\n writingCase(\n 'writing-revised',\n 'A second draft that fixed some mistakes',\n \"Last weekend I went to the market and bought a apple. My friends was there too, so we didn't go home early.\",\n ['Check your past tenses: \"go\" and \"buyed\" should be past forms.'],\n ),\n writingCase(\n 'writing-feedback-in-spanish',\n 'A first draft, with feedback asked for in Spanish',\n 'Last weekend I visit my grandmother. She cook a big lunch and we eat together in the garden.',\n [],\n 'es',\n ),\n coachingCase('coaching-slips', 'A reading with a mispronounced \"th\" and a word left out', {\n assessment: forecast(true),\n }),\n coachingCase('coaching-clean', 'A reading the engine marked correct throughout', {\n assessment: forecast(false),\n }),\n coachingCase(\n 'coaching-from-grade',\n 'The same slips, from a stored grade: marks without sounds',\n { fromGradeOnly: true },\n ),\n coachingCase(\n 'coaching-in-spanish',\n 'The same slips, with coaching asked for in Spanish',\n { assessment: forecast(true) },\n 'es',\n ),\n critiqueCase(\n 'critique-second-answer',\n 'A question whose distractor \"Tomato\" is botanically a fruit',\n 'multiple-choice',\n fruit,\n { level: 'A1' },\n ),\n critiqueCase(\n 'critique-level',\n 'A writing task far above the A1 level it is set for',\n 'written-response',\n tooHard,\n { level: 'A1' },\n ),\n critiqueCase(\n 'critique-clean',\n 'A cloze with nothing wrong in it',\n 'fill-in-the-blanks',\n cleanCloze,\n {\n level: 'A2',\n },\n ),\n critiqueCase(\n 'critique-in-spanish',\n 'The fruit question, reviewed in Spanish for its author',\n 'multiple-choice',\n fruit,\n { level: 'A1', authorLocale: 'es' },\n ),\n draftsCase(\n 'drafts-questions',\n 'Four multiple-choice questions on a short reading',\n ['multiple-choice'],\n { kind: 'passage', text: MARKET },\n { count: 4, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-cloze',\n 'A cloze on the same reading, from its transcript',\n ['fill-in-the-blanks'],\n { kind: 'transcript', text: MARKET },\n { count: 1, level: 'A2', locale: 'en', instructions: 'Blank the verbs.' },\n ),\n draftsCase(\n 'drafts-gaps',\n 'A choose-the-word item on the same reading',\n ['gap-select'],\n { kind: 'passage', text: MARKET },\n { count: 1, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-writing',\n 'A writing task that follows on from the reading',\n ['written-response'],\n { kind: 'passage', text: MARKET },\n { count: 1, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-video',\n 'A video quiz from timed captions: multiple choice, then dictation, then read-aloud, as many as the video is worth',\n ['multiple-choice', 'dictation', 'read-aloud'],\n { kind: 'captions', cues: MARKET_CAPTIONS },\n { level: 'A2', locale: 'en', settings: { 'read-aloud': READ_ALOUD_SETTINGS } },\n ),\n draftsCase(\n 'drafts-script',\n 'The same video from its script alone, with no times, and what the author asked for',\n ['multiple-choice', 'dictation', 'read-aloud'],\n { kind: 'transcript', text: MARKET },\n {\n level: 'A2',\n locale: 'en',\n instructions: 'Focus on what Maria and her grandmother buy.',\n settings: { 'read-aloud': READ_ALOUD_SETTINGS },\n },\n ),\n ];\n}\n","/**\n * A test kit for the prompts behind your AI ports.\n *\n * The SDK cannot test your model, and will not try. What it can do is make the\n * calls a learner's questions would make — on items whose answers it knows —\n * and run the same checks it runs before a learner sees anything. What comes\n * back is a count of what would have been shown and what would have been\n * refused, and why.\n *\n * Run it where you change a prompt or a model, in your own CI, with your own\n * key. It is a separate entry point (`@intellectif/lk-core/ai-check`) because\n * it is for your test run, not for a learner's browser: nothing here is\n * imported by anything a page renders.\n *\n * ```ts\n * import { aiCheckCases, formatAiCheckReport, runAiCheck } from '@intellectif/lk-core/ai-check';\n *\n * const report = await runAiCheck({\n * explain: (request) => callMyModel(EXPLAIN_PROMPT, request),\n * hint: (request) => callMyModel(HINT_PROMPT, request),\n * writingFeedback: (request) => callMyModel(FEEDBACK_PROMPT, request),\n * pronunciationCoaching: (request) => callMyModel(COACHING_PROMPT, request),\n * critique: (request) => callMyModel(CRITIQUE_PROMPT, request),\n * drafts: (request) => callMyModel(DRAFTS_PROMPT, request),\n * });\n * console.log(formatAiCheckReport(report));\n * if (report.refused > 0 || report.errors > 0) {\n * process.exitCode = 1;\n * }\n * ```\n *\n * A refusal is a real failure, not a warning: it is an answer a learner would\n * have asked for and not received. `reveals-answer` on a hint case is the one\n * to take most seriously — that prompt gives answers away. Next come\n * `misquotes-answer` on a writing case — that prompt corrects words the learner\n * never wrote — and `contradicts-marks` on a coaching case: that prompt coaches\n * words the engine heard as right, or sounds it never reported.\n */\nimport { checkAiExplanation, checkAiHint } from '../ai.js';\nimport { checkAiCoaching } from '../ai-coaching.js';\nimport { checkAiCritique } from '../ai-critique.js';\nimport { checkAiDrafts } from '../ai-drafts.js';\nimport { checkAiWritingFeedback } from '../ai-writing.js';\nimport type {\n AiCoaching,\n AiCoachingRequest,\n AiCritique,\n AiCritiqueRequest,\n AiDrafts,\n AiDraftsRequest,\n AiExplanationRequest,\n AiHintRequest,\n AiRefusal,\n AiTextResult,\n AiWritingFeedback,\n AiWritingFeedbackRequest,\n} from '../types/ai.js';\nimport { type AiCheckCase, aiCheckCases } from './cases.js';\n\nexport { type AiCheckCase, aiCheckCases } from './cases.js';\n\n/**\n * Your ports, as this kit calls them. The same functions\n * `<LkAiProvider ai={…}>` takes, minus the `AbortSignal` a browser passes —\n * nothing here abandons a call.\n *\n * Leave one out and its cases are skipped rather than failed.\n */\nexport interface AiCheckPorts {\n explain?(request: AiExplanationRequest): Promise<unknown> | unknown;\n hint?(request: AiHintRequest): Promise<unknown> | unknown;\n writingFeedback?(request: AiWritingFeedbackRequest): Promise<unknown> | unknown;\n pronunciationCoaching?(request: AiCoachingRequest): Promise<unknown> | unknown;\n /** Reviews an item for its author: the port an editor's \"Review with AI\" calls. */\n critique?(request: AiCritiqueRequest): Promise<unknown> | unknown;\n /** Drafts items from a source, for an author to approve. */\n drafts?(request: AiDraftsRequest): Promise<unknown> | unknown;\n}\n\n/** What happened on one case. */\nexport interface AiCheckResult {\n case: AiCheckCase;\n /** Whether a learner would have been shown what came back. */\n ok: boolean;\n /** Why it would not have been shown. */\n refusal?: AiRefusal;\n /** The port threw or rejected. Its message, as a string. */\n error?: string;\n /**\n * What came back, once the SDK had read it — so a refusal can be read beside\n * the text that caused it. Absent when the port failed or answered with\n * nothing the SDK could read.\n */\n result?: AiTextResult;\n /** For a writing case a learner would have been shown: the feedback, as the SDK accepted it. */\n feedback?: AiWritingFeedback;\n /** For a coaching case a learner would have been shown: the coaching, as the SDK accepted it. */\n coaching?: AiCoaching;\n /** For a critique case an author would have been shown: the findings, as the SDK accepted them. */\n critique?: AiCritique;\n /**\n * For a drafts case the SDK read: the drafts, each with what `validateDraft`\n * and the item critic say of it. A reply read is not a good one — check how\n * many came back `complete` and without warnings.\n */\n drafts?: AiDrafts;\n /** How long the call took, in milliseconds. */\n ms: number;\n}\n\n/** What a run came to. */\nexport interface AiCheckReport {\n /** Cases run: the ones a port was supplied for. */\n total: number;\n /** Cases whose answer a learner would have been shown. */\n shown: number;\n /** Cases whose answer the SDK refused. */\n refused: number;\n /** Cases where the port itself failed. */\n errors: number;\n /** Cases skipped because no port was supplied for that feature. */\n skipped: number;\n /** How many refusals of each reason. */\n byRefusal: Record<AiRefusal, number>;\n results: AiCheckResult[];\n}\n\nexport interface AiCheckOptions {\n /**\n * The cases to run. Defaults to {@link aiCheckCases}. Pass a filtered list to\n * run one type, or your own cases — built with `aiExplanationRequest` and\n * `aiHintRequest` on your own items, which is how you test a prompt against\n * the content your learners actually see.\n */\n cases?: readonly AiCheckCase[];\n /**\n * How many cases to have in flight at once. Default 1: a run in CI hits a\n * rate limit far more often than it runs out of patience.\n */\n concurrency?: number;\n}\n\nconst REFUSALS: AiRefusal[] = [\n 'malformed',\n 'empty',\n 'too-long',\n 'contradicts-grade',\n 'reveals-answer',\n 'misquotes-answer',\n 'contradicts-marks',\n 'contradicts-item',\n];\n\n/** The port a case is for. */\nfunction portFor(\n ports: AiCheckPorts,\n one: AiCheckCase,\n): ((request: never) => Promise<unknown> | unknown) | undefined {\n switch (one.request.feature) {\n case 'explanation':\n return ports.explain as never;\n case 'hint':\n return ports.hint as never;\n case 'pronunciation-coaching':\n return ports.pronunciationCoaching as never;\n case 'item-critique':\n return ports.critique as never;\n case 'draft-generation':\n return ports.drafts as never;\n default:\n return ports.writingFeedback as never;\n }\n}\n\n/** Milliseconds, from whichever clock this runtime has. */\nconst now = (): number => Date.now();\n\n/** Runs one case: calls the port, times it, and checks what came back. */\nasync function runCase(ports: AiCheckPorts, one: AiCheckCase): Promise<AiCheckResult> {\n const started = now();\n const port = portFor(ports, one);\n if (port === undefined) {\n throw new Error(`ai-check: no port for \"${one.id}\"`);\n }\n let raw: unknown;\n try {\n raw = await port(one.request as never);\n } catch (error) {\n return {\n case: one,\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n ms: now() - started,\n };\n }\n const ms = now() - started;\n if (one.request.feature === 'draft-generation') {\n let next = 0;\n const checked = checkAiDrafts(raw, one.request, {\n newId: () => {\n next += 1;\n return `ai-check-${next}`;\n },\n });\n return checked.ok\n ? { case: one, ok: true, drafts: checked.drafts, ms }\n : refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'item-critique') {\n const checked = checkAiCritique(raw, one.request);\n return checked.ok\n ? { case: one, ok: true, critique: checked.critique, ms }\n : refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'pronunciation-coaching') {\n const checked = checkAiCoaching(raw, one.request);\n if (checked.ok) {\n const { text, provenance, usage } = checked.coaching;\n return {\n case: one,\n ok: true,\n result: {\n text,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n coaching: checked.coaching,\n ms,\n };\n }\n return refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'writing-feedback') {\n const checked = checkAiWritingFeedback(raw, one.request);\n if (checked.ok) {\n const { text, provenance, usage } = checked.feedback;\n return {\n case: one,\n ok: true,\n result: {\n text,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n feedback: checked.feedback,\n ms,\n };\n }\n return refused(one, raw, checked.refusal, ms);\n }\n const checked =\n one.request.feature === 'explanation'\n ? checkAiExplanation(raw, one.request)\n : checkAiHint(raw, one.request);\n if (checked.ok) {\n return { case: one, ok: true, result: checked.result, ms };\n }\n return refused(one, raw, checked.refusal, ms);\n}\n\n/** A case whose answer was refused, with the text that came back when there was one to read. */\nfunction refused(one: AiCheckCase, raw: unknown, refusal: AiRefusal, ms: number): AiCheckResult {\n const text =\n typeof (raw as { text?: unknown })?.text === 'string' ? (raw as AiTextResult) : undefined;\n return {\n case: one,\n ok: false,\n refusal,\n ...(text !== undefined ? { result: text } : {}),\n ms,\n };\n}\n\n/**\n * Calls your ports on every case and reports what a learner would have seen.\n *\n * It never throws for a failing case: a port that rejects is an `error` in the\n * report, so one bad call cannot hide the rest.\n */\nexport async function runAiCheck(\n ports: AiCheckPorts,\n options: AiCheckOptions = {},\n): Promise<AiCheckReport> {\n const all = options.cases ?? aiCheckCases();\n const asked = Math.trunc(options.concurrency ?? 1);\n const lanes = Number.isFinite(asked) && asked >= 1 ? Math.min(asked, 16) : 1;\n const runnable = all.filter((one) => portFor(ports, one) !== undefined);\n const results: AiCheckResult[] = new Array(runnable.length);\n let next = 0;\n const lane = async (): Promise<void> => {\n while (next < runnable.length) {\n const mine = next;\n next += 1;\n const one = runnable[mine];\n if (one === undefined) {\n return;\n }\n results[mine] = await runCase(ports, one);\n }\n };\n await Promise.all(Array.from({ length: Math.min(lanes, runnable.length) }, lane));\n\n const byRefusal = Object.fromEntries(REFUSALS.map((reason) => [reason, 0])) as Record<\n AiRefusal,\n number\n >;\n let shown = 0;\n let refused = 0;\n let errors = 0;\n for (const result of results) {\n if (result.ok) {\n shown += 1;\n } else if (result.refusal !== undefined) {\n refused += 1;\n byRefusal[result.refusal] += 1;\n } else {\n errors += 1;\n }\n }\n return {\n total: results.length,\n shown,\n refused,\n errors,\n skipped: all.length - runnable.length,\n byRefusal,\n results,\n };\n}\n\n/**\n * The report as a few lines for a CI log: the counts, then every case that\n * failed, with what came back.\n *\n * The refused text is printed — this is your own test run, not a learner's\n * screen, and a hint that gives the answer away is only fixable if you can\n * read it.\n */\nexport function formatAiCheckReport(report: AiCheckReport): string {\n const lines: string[] = [];\n const slowest = report.results.reduce((worst, one) => Math.max(worst, one.ms), 0);\n lines.push(\n `ai-check: ${report.shown}/${report.total} shown, ${report.refused} refused, ${report.errors} failed` +\n `${report.skipped > 0 ? `, ${report.skipped} skipped (no port)` : ''} — slowest ${slowest} ms`,\n );\n for (const reason of REFUSALS) {\n const count = report.byRefusal[reason];\n if (count > 0) {\n lines.push(` ${reason}: ${count}`);\n }\n }\n for (const result of report.results) {\n if (result.ok) {\n continue;\n }\n const why = result.refusal ?? `port failed (${result.error ?? 'no message'})`;\n lines.push(` ✗ ${result.case.id} [${result.case.feature}] ${result.case.about}: ${why}`);\n if (result.result !== undefined) {\n lines.push(` ${result.result.text.replace(/\\s+/g, ' ').slice(0, 200)}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai-check.js","names":[],"sources":["../src/ai-check/cases.ts","../src/ai-check/index.ts"],"sourcesContent":["import { aiExplanationRequest, aiHintRequest } from '../ai.js';\nimport { aiCoachingRequest } from '../ai-coaching.js';\nimport { aiCritiqueRequest } from '../ai-critique.js';\nimport { aiDraftsRequest } from '../ai-drafts.js';\nimport { aiWritingFeedbackRequest } from '../ai-writing.js';\nimport { gradeReadAloud } from '../scoring/speech/grade.js';\nimport type {\n ActivityType,\n DictationData,\n FillInTheBlanksData,\n GapSelectData,\n LearnerResponse,\n MultipleChoiceData,\n ReadAloudData,\n WrittenResponseData,\n} from '../types/activity.js';\nimport type {\n AiCoachingRequest,\n AiCritiqueRequest,\n AiDraftSource,\n AiDraftsRequest,\n AiDraftType,\n AiExplanationRequest,\n AiGroupInput,\n AiHintRequest,\n AiWritingFeedbackRequest,\n} from '../types/ai.js';\nimport type { ItemGroup } from '../types/item-group.js';\nimport type { SpeechAssessment, SpeechWord } from '../types/speech.js';\n\n/**\n * The items the cases are built from. Small, ordinary language-course content\n * — and deliberately awkward in the places a model slips:\n *\n * - an answer a hint can hardly avoid naming (`Madrid`);\n * - an answer of one short word (`is`), which a hint may write as an ordinary\n * word and must not write beside its neighbour in the passage;\n * - an accented answer (`cañón`), so a hint that spells it without the accent\n * is still caught;\n * - a passage with more than one blank, so a hint can be about the wrong one;\n * - a short essay whose mistakes a model must quote exactly as the learner\n * wrote them — straight apostrophes a model likes to curl, a mistake made\n * twice — so a correction of words nobody wrote is caught;\n * - questions in item groups: a reading passage that prints a question's\n * answer, which a hint must point to without quoting, and a run of blanks\n * whose instructions only the group states.\n */\nconst capital: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-mc',\n title: 'Capital cities',\n question: 'What is the capital of Spain?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'madrid', text: 'Madrid', isCorrect: true },\n { id: 'barcelona', text: 'Barcelona', isCorrect: false },\n { id: 'seville', text: 'Seville', isCorrect: false },\n ],\n};\n\nconst introduction: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-fib',\n title: 'Introducing yourself',\n passage: 'My name {{be}} Rossi and I {{live}} in Madrid.',\n blanks: [\n { id: 'be', acceptedAnswers: ['is'] },\n { id: 'live', acceptedAnswers: ['live'], hint: 'Present simple, first person.' },\n ],\n scoringStrategy: 'partial',\n};\n\nconst landscape: GapSelectData = {\n schemaVersion: '1.0',\n type: 'gap-select',\n id: 'ai-check-gap',\n title: 'Geography',\n locale: 'es-ES',\n passage: 'El río pasa por un {{g1}} muy profundo, y el agua {{g2}} fría.',\n gaps: [\n {\n id: 'g1',\n choices: [\n { id: 'canon', text: 'cañón' },\n { id: 'campo', text: 'campo' },\n ],\n correctChoiceId: 'canon',\n },\n {\n id: 'g2',\n choices: [\n { id: 'esta', text: 'está' },\n { id: 'es', text: 'es' },\n ],\n correctChoiceId: 'esta',\n },\n ],\n scoringStrategy: 'partial',\n};\n\nconst listening: DictationData = {\n schemaVersion: '1.0',\n type: 'dictation',\n id: 'ai-check-dictation',\n title: 'Listen and type',\n transcript: 'The train leaves at half past nine.',\n};\n\n/**\n * A reading group: a passage, a question it answers word for word, and a\n * summary of it. The question's answer is printed in the passage, so a hint\n * that quotes the passage where it answers is refused.\n */\nconst TIDES =\n 'Along most coasts the tide comes in twice a day. It is pulled by the moon: as the Earth ' +\n 'turns, the ocean nearest the moon bulges towards it, and so does the ocean on the far side. ' +\n 'The highest tides, called spring tides, come when the sun and the moon line up.';\n\nconst springTides: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-tides',\n title: 'Spring tides',\n question: 'When do the highest tides come?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'line-up', text: 'When the sun and the moon line up', isCorrect: true },\n { id: 'full-moon', text: 'Only at full moon', isCorrect: false },\n { id: 'winter', text: 'In winter', isCorrect: false },\n ],\n};\n\nconst tidesSummary: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-tides-summary',\n title: 'Summary',\n prompt: 'Summarise the text in one or two sentences.',\n minWords: 10,\n maxWords: 60,\n languageTarget: 'en-B1',\n};\n\nconst tides: ItemGroup = {\n schemaVersion: '1.0',\n type: 'item-group',\n id: 'ai-check-tides-group',\n title: 'Part 2 — Reading',\n stimulus: { id: 'ai-check-tides-text', kind: 'text', title: 'Tides', body: TIDES },\n items: [springTides, tidesSummary],\n};\n\n/** A part whose questions share instructions the questions themselves never state. */\nconst formation: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-formation',\n title: 'Word formation',\n passage: 'She smiled {{a}} when she heard the news. (HAPPY)',\n blanks: [{ id: 'a', acceptedAnswers: ['happily'] }],\n scoringStrategy: 'all-or-nothing',\n};\n\nconst formationPart: ItemGroup = {\n schemaVersion: '1.0',\n type: 'item-group',\n id: 'ai-check-formation-part',\n title: 'Part 1 — Word formation',\n stimulus: {\n id: 'ai-check-formation-instructions',\n kind: 'text',\n body: 'Use the word in capitals at the end of each sentence to form a word that fits the gap.',\n },\n items: [formation],\n};\n\nconst weekend: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-writing',\n title: 'Your weekend',\n prompt: 'Write a few sentences about what you did last weekend.',\n minWords: 15,\n maxWords: 120,\n languageTarget: 'en-A2',\n rubric: {\n criteria: [\n { name: 'Grammar', description: 'Past tenses, articles, agreement', weight: 2 },\n { name: 'Vocabulary', description: 'Everyday words, used correctly', weight: 1 },\n { name: 'Task', description: 'Says what the learner did, in order', weight: 1 },\n ],\n },\n};\n\nconst weather: ReadAloudData = {\n schemaVersion: '1.0',\n type: 'read-aloud',\n id: 'ai-check-reading',\n title: 'Read the forecast aloud',\n referenceText: 'The weather is warm in the north.',\n locale: 'en-US',\n recording: { maxSeconds: 20 },\n scoring: {\n dimensions: [\n { name: 'accuracy', weight: 2 },\n { name: 'fluency', weight: 1 },\n ],\n },\n};\n\n/** A word of the forecast as an engine reports it. */\nconst said = (text: string, over: Partial<SpeechWord> = {}): SpeechWord => ({\n text,\n error: 'none',\n accuracy: 92,\n ...over,\n});\n\n/**\n * An engine's assessment of the forecast. With `slips`, \"weather\" is\n * mispronounced — its \"th\" heard as /d/ or /z/ — and \"north\" is left out: the\n * marks a model must coach, and no others.\n */\nfunction forecast(slips: boolean): SpeechAssessment {\n return {\n assessmentVersion: '1.0',\n status: 'assessed',\n task: 'scripted',\n locale: 'en-US',\n referenceText: weather.referenceText,\n recordingKey: 'ai-check-take',\n assessor: { kind: 'auto', id: 'pronunciation-engine' },\n scale: 100,\n scores: slips ? { accuracy: 68, fluency: 81, completeness: 86 } : { accuracy: 94, fluency: 90 },\n miscue: 'assessor',\n phonemeAlphabet: 'ipa',\n words: [\n said('The'),\n slips\n ? said('weather', {\n accuracy: 38,\n error: 'mispronunciation',\n phonemes: [\n { symbol: 'w', accuracy: 90 },\n { symbol: 'ɛ', accuracy: 85 },\n {\n symbol: 'ð',\n accuracy: 12,\n heardAs: [\n { symbol: 'd', score: 64 },\n { symbol: 'z', score: 21 },\n { symbol: 'ð', score: 12 },\n ],\n },\n { symbol: 'ɚ', accuracy: 80 },\n ],\n })\n : said('weather'),\n said('is'),\n said('warm'),\n said('in'),\n said('the'),\n slips ? { text: 'north', error: 'omission' } : said('north'),\n ],\n };\n}\n\n/**\n * One call to make against a host's port, and what a learner was looking at\n * when the SDK would have made it.\n */\nexport interface AiCheckCase {\n /** Stable across releases, so a host can name one in an allow-list or a report. */\n id: string;\n /** Which port the case is for. */\n feature:\n | 'explanation'\n | 'hint'\n | 'writing-feedback'\n | 'pronunciation-coaching'\n | 'item-critique'\n | 'draft-generation';\n /** What the learner did, in a few words, for a report a person reads. */\n about: string;\n /** The request, built by the SDK exactly as a component builds it. */\n request:\n | AiExplanationRequest\n | AiHintRequest\n | AiWritingFeedbackRequest\n | AiCoachingRequest\n | AiCritiqueRequest\n | AiDraftsRequest;\n}\n\nconst chose = (ids: string[]): LearnerResponse => ({\n type: 'multiple-choice',\n selectedOptionIds: ids,\n});\nconst typed = (answers: Record<string, string>): LearnerResponse => ({\n type: 'fill-in-the-blanks',\n answers,\n});\nconst picked = (choices: Record<string, string>): LearnerResponse => ({\n type: 'gap-select',\n selections: choices,\n});\nconst wrote = (text: string): LearnerResponse => ({ type: 'dictation', text });\nconst drafted = (text: string): LearnerResponse => ({\n type: 'written-response',\n text,\n wordCount: 0,\n});\n\n/**\n * Items an author asks a model to review. Each is valid, and each but the last\n * has a flaw a rule cannot see: a distractor that is botanically a right\n * answer, and a task far above the level it is set for.\n */\nconst fruit: MultipleChoiceData = {\n schemaVersion: '1.0',\n type: 'multiple-choice',\n id: 'ai-check-fruit',\n title: 'Food words',\n question: 'Which of these is a fruit?',\n mode: 'single',\n scoringStrategy: 'all-or-nothing',\n options: [\n { id: 'f1', text: 'Apple', isCorrect: true },\n { id: 'f2', text: 'Carrot', isCorrect: false },\n { id: 'f3', text: 'Tomato', isCorrect: false },\n ],\n};\n\nconst tooHard: WrittenResponseData = {\n schemaVersion: '1.0',\n type: 'written-response',\n id: 'ai-check-too-hard',\n title: 'Cities',\n prompt: 'Discuss the socioeconomic ramifications of rapid urbanisation in developing economies.',\n minWords: 40,\n maxWords: 80,\n};\n\nconst cleanCloze: FillInTheBlanksData = {\n schemaVersion: '1.0',\n type: 'fill-in-the-blanks',\n id: 'ai-check-clean-cloze',\n title: 'Daily routine',\n passage: 'Every morning I {{1}} up at seven and {{2}} a shower.',\n blanks: [\n { id: '1', acceptedAnswers: ['get', 'wake'] },\n { id: '2', acceptedAnswers: ['take', 'have'] },\n ],\n scoringStrategy: 'partial',\n};\n\nfunction critiqueCase(\n id: string,\n about: string,\n type: ActivityType,\n draft: unknown,\n extra: { level?: string; authorLocale?: string } = {},\n): AiCheckCase {\n const request = aiCritiqueRequest({ type, draft, ...extra });\n if (request === null) {\n throw new Error(`ai-check: no critique request for \"${id}\"`);\n }\n return { id, feature: 'item-critique', about, request };\n}\n\n/** A short reading to draft from: plain, and with facts a question can be asked about. */\nconst MARKET =\n 'Maria lives in Seville, in the south of Spain. Every Saturday she walks to the market with her ' +\n 'grandmother. They buy oranges, fresh bread and a little cheese. On the way home they stop at a ' +\n 'café, where Maria drinks hot chocolate and her grandmother reads the newspaper.';\n\n/** The same reading as a video's captions, with times in seconds. */\nconst MARKET_CAPTIONS = [\n { start: 0, end: 4.2, text: 'Maria lives in Seville, in the south of Spain.' },\n { start: 4.2, end: 8.9, text: 'Every Saturday she walks to the market with her grandmother.' },\n { start: 8.9, end: 12.6, text: 'They buy oranges, fresh bread and a little cheese.' },\n { start: 12.6, end: 18.1, text: 'On the way home they stop at a café.' },\n];\n\nfunction draftsCase(\n id: string,\n about: string,\n types: AiDraftType[],\n source: AiDraftSource,\n extra: Omit<Parameters<typeof aiDraftsRequest>[0], 'types' | 'source'> = {},\n): AiCheckCase {\n const request = aiDraftsRequest({ types, source, ...extra });\n if (request === null) {\n throw new Error(`ai-check: no drafts request for \"${id}\"`);\n }\n return { id, feature: 'draft-generation', about, request };\n}\n\n/**\n * What a host sets on every read-aloud it drafts: the SDK has no default for a\n * recording's length or the dimensions a reading is graded on.\n */\nconst READ_ALOUD_SETTINGS = {\n locale: 'en-US',\n recording: { maxSeconds: 20 },\n scoring: { dimensions: [{ name: 'accuracy', weight: 1 }] },\n};\n\n/** Builds a case, failing loudly rather than shipping a case with no request. */\nfunction explanationCase(\n id: string,\n about: string,\n data: Parameters<typeof aiExplanationRequest>[0]['data'],\n response: LearnerResponse,\n group?: AiGroupInput,\n): AiCheckCase {\n const request = aiExplanationRequest({ data, response, ...(group ? { group } : {}) });\n if (request === null) {\n throw new Error(`ai-check: no explanation request for \"${id}\"`);\n }\n return { id, feature: 'explanation', about, request };\n}\n\nfunction hintCase(\n id: string,\n about: string,\n data: Parameters<typeof aiHintRequest>[0]['data'],\n response: LearnerResponse,\n previousHints: string[] = [],\n group?: AiGroupInput,\n): AiCheckCase {\n const request = aiHintRequest({ data, response, previousHints, ...(group ? { group } : {}) });\n if (request === null) {\n throw new Error(`ai-check: no hint request for \"${id}\"`);\n }\n return { id, feature: 'hint', about, request };\n}\n\nfunction writingCase(\n id: string,\n about: string,\n text: string,\n previousFeedback: string[] = [],\n learnerLocale?: string,\n task: { data: WrittenResponseData; group?: AiGroupInput } = { data: weekend },\n): AiCheckCase {\n const request = aiWritingFeedbackRequest({\n data: task.data,\n response: drafted(text),\n previousFeedback,\n ...(task.group ? { group: task.group } : {}),\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n });\n if (request === null) {\n throw new Error(`ai-check: no writing feedback request for \"${id}\"`);\n }\n return { id, feature: 'writing-feedback', about, request };\n}\n\nfunction coachingCase(\n id: string,\n about: string,\n marks: { assessment?: SpeechAssessment; fromGradeOnly?: boolean },\n learnerLocale?: string,\n): AiCheckCase {\n const assessment = marks.assessment ?? forecast(true);\n const grade = gradeReadAloud(\n weather,\n { type: 'read-aloud', recording: { key: 'ai-check-take', mimeType: 'audio/wav' } },\n assessment,\n {\n measured: { durationMs: 4000, voicedMs: 3000 },\n plausibility: { maxWordsPerSecond: 6, minVoicedMs: 500 },\n },\n );\n if ('unscorable' in grade) {\n throw new Error(`ai-check: the reading for \"${id}\" did not grade: ${grade.reason}`);\n }\n const request = aiCoachingRequest({\n data: weather,\n ...(marks.fromGradeOnly === true ? {} : { assessment }),\n grade,\n ...(learnerLocale !== undefined ? { learnerLocale } : {}),\n });\n if (request === null) {\n throw new Error(`ai-check: no coaching request for \"${id}\"`);\n }\n return { id, feature: 'pronunciation-coaching', about, request };\n}\n\n/**\n * The cases {@link runAiCheck} runs: every type the SDK explains, answered\n * right, wrong and — where a type can be — partly right; every type it hints\n * for, before an answer and after a wrong one; and drafts of an essay, with\n * mistakes, without, and revised.\n *\n * They are ordinary calls. What makes them a test is that the SDK knows the\n * answer to each, so it can check what your model writes about it.\n *\n * A fresh array each call, so a caller may filter, slice or add to it without\n * changing what the next caller gets.\n */\nexport function aiCheckCases(): AiCheckCase[] {\n return [\n explanationCase('mc-correct', 'Chose the right city', capital, chose(['madrid'])),\n explanationCase('mc-wrong', 'Chose Barcelona', capital, chose(['barcelona'])),\n explanationCase('mc-blank', 'Chose nothing and submitted', capital, chose([])),\n explanationCase(\n 'fib-correct',\n 'Filled both blanks correctly',\n introduction,\n typed({ be: 'is', live: 'live' }),\n ),\n explanationCase(\n 'fib-partly',\n 'One blank right, one wrong',\n introduction,\n typed({ be: 'is', live: 'lives' }),\n ),\n explanationCase(\n 'fib-wrong',\n 'Both blanks wrong',\n introduction,\n typed({ be: 'are', live: 'living' }),\n ),\n explanationCase(\n 'gap-partly',\n 'One gap right, one wrong, in Spanish',\n landscape,\n picked({ g1: 'canon', g2: 'es' }),\n ),\n explanationCase(\n 'gap-wrong',\n 'Both gaps wrong, in Spanish',\n landscape,\n picked({ g1: 'campo', g2: 'es' }),\n ),\n explanationCase(\n 'dictation-near',\n 'Typed the sentence with two words wrong',\n listening,\n wrote('The train leaves at half past five'),\n ),\n explanationCase(\n 'dictation-correct',\n 'Typed the sentence correctly',\n listening,\n wrote('The train leaves at half past nine.'),\n ),\n explanationCase(\n 'mc-reading-wrong',\n \"Chose 'Only at full moon', on a question about its group's passage\",\n springTides,\n chose(['full-moon']),\n tides,\n ),\n hintCase('mc-hint-1', 'Has not answered yet', capital, chose([])),\n hintCase('mc-hint-2', 'Asked again after choosing Seville', capital, chose(['seville']), [\n 'Think about where the government sits.',\n ]),\n hintCase('fib-hint-1', 'Both blanks empty', introduction, typed({})),\n hintCase(\n 'fib-hint-2',\n 'Asked again with one blank wrong',\n introduction,\n typed({ be: 'are', live: '' }),\n ['Look again at the first blank.'],\n ),\n hintCase('gap-hint-1', 'Neither gap chosen, in Spanish', landscape, picked({})),\n hintCase(\n 'gap-hint-2',\n 'Asked again with the first gap wrong',\n landscape,\n picked({ g1: 'campo' }),\n ['Piensa en la forma del terreno.'],\n ),\n hintCase(\n 'mc-reading-hint',\n \"Has not answered a question whose answer its group's passage prints\",\n springTides,\n chose([]),\n [],\n tides,\n ),\n hintCase(\n 'fib-formation-hint',\n 'A word-formation blank, under the instructions its group gives',\n formation,\n typed({}),\n [],\n formationPart,\n ),\n writingCase(\n 'writing-mistakes',\n 'A first draft with tense, article and agreement mistakes',\n \"Last weekend I go to the market and buyed a apple. My friends was there too, so we don't go home early. Then I go to the cinema.\",\n ),\n writingCase(\n 'writing-clean',\n 'A first draft with no mistakes to correct',\n 'Last weekend I went to the market and bought an apple. My friends were there too, so we stayed until the evening. Then we went to the cinema.',\n ),\n writingCase(\n 'writing-revised',\n 'A second draft that fixed some mistakes',\n \"Last weekend I went to the market and bought a apple. My friends was there too, so we didn't go home early.\",\n ['Check your past tenses: \"go\" and \"buyed\" should be past forms.'],\n ),\n writingCase(\n 'writing-feedback-in-spanish',\n 'A first draft, with feedback asked for in Spanish',\n 'Last weekend I visit my grandmother. She cook a big lunch and we eat together in the garden.',\n [],\n 'es',\n ),\n writingCase(\n 'writing-summary',\n \"A summary of its group's passage that gets the spring tides wrong\",\n 'The tide comes in twice a day because the moon pulls the ocean. The highest tides come only at full moon.',\n [],\n undefined,\n { data: tidesSummary, group: tides },\n ),\n coachingCase('coaching-slips', 'A reading with a mispronounced \"th\" and a word left out', {\n assessment: forecast(true),\n }),\n coachingCase('coaching-clean', 'A reading the engine marked correct throughout', {\n assessment: forecast(false),\n }),\n coachingCase(\n 'coaching-from-grade',\n 'The same slips, from a stored grade: marks without sounds',\n { fromGradeOnly: true },\n ),\n coachingCase(\n 'coaching-in-spanish',\n 'The same slips, with coaching asked for in Spanish',\n { assessment: forecast(true) },\n 'es',\n ),\n critiqueCase(\n 'critique-second-answer',\n 'A question whose distractor \"Tomato\" is botanically a fruit',\n 'multiple-choice',\n fruit,\n { level: 'A1' },\n ),\n critiqueCase(\n 'critique-level',\n 'A writing task far above the A1 level it is set for',\n 'written-response',\n tooHard,\n { level: 'A1' },\n ),\n critiqueCase(\n 'critique-clean',\n 'A cloze with nothing wrong in it',\n 'fill-in-the-blanks',\n cleanCloze,\n {\n level: 'A2',\n },\n ),\n critiqueCase(\n 'critique-in-spanish',\n 'The fruit question, reviewed in Spanish for its author',\n 'multiple-choice',\n fruit,\n { level: 'A1', authorLocale: 'es' },\n ),\n draftsCase(\n 'drafts-questions',\n 'Four multiple-choice questions on a short reading',\n ['multiple-choice'],\n { kind: 'passage', text: MARKET },\n { count: 4, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-cloze',\n 'A cloze on the same reading, from its transcript',\n ['fill-in-the-blanks'],\n { kind: 'transcript', text: MARKET },\n { count: 1, level: 'A2', locale: 'en', instructions: 'Blank the verbs.' },\n ),\n draftsCase(\n 'drafts-gaps',\n 'A choose-the-word item on the same reading',\n ['gap-select'],\n { kind: 'passage', text: MARKET },\n { count: 1, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-writing',\n 'A writing task that follows on from the reading',\n ['written-response'],\n { kind: 'passage', text: MARKET },\n { count: 1, level: 'A2', locale: 'en' },\n ),\n draftsCase(\n 'drafts-video',\n 'A video quiz from timed captions: multiple choice, then dictation, then read-aloud, as many as the video is worth',\n ['multiple-choice', 'dictation', 'read-aloud'],\n { kind: 'captions', cues: MARKET_CAPTIONS },\n { level: 'A2', locale: 'en', settings: { 'read-aloud': READ_ALOUD_SETTINGS } },\n ),\n draftsCase(\n 'drafts-script',\n 'The same video from its script alone, with no times, and what the author asked for',\n ['multiple-choice', 'dictation', 'read-aloud'],\n { kind: 'transcript', text: MARKET },\n {\n level: 'A2',\n locale: 'en',\n instructions: 'Focus on what Maria and her grandmother buy.',\n settings: { 'read-aloud': READ_ALOUD_SETTINGS },\n },\n ),\n ];\n}\n","/**\n * A test kit for the prompts behind your AI ports.\n *\n * The SDK cannot test your model, and will not try. What it can do is make the\n * calls a learner's questions would make — on items whose answers it knows —\n * and run the same checks it runs before a learner sees anything. What comes\n * back is a count of what would have been shown and what would have been\n * refused, and why.\n *\n * Run it where you change a prompt or a model, in your own CI, with your own\n * key. It is a separate entry point (`@intellectif/lk-core/ai-check`) because\n * it is for your test run, not for a learner's browser: nothing here is\n * imported by anything a page renders.\n *\n * ```ts\n * import { aiCheckCases, formatAiCheckReport, runAiCheck } from '@intellectif/lk-core/ai-check';\n *\n * const report = await runAiCheck({\n * explain: (request) => callMyModel(EXPLAIN_PROMPT, request),\n * hint: (request) => callMyModel(HINT_PROMPT, request),\n * writingFeedback: (request) => callMyModel(FEEDBACK_PROMPT, request),\n * pronunciationCoaching: (request) => callMyModel(COACHING_PROMPT, request),\n * critique: (request) => callMyModel(CRITIQUE_PROMPT, request),\n * drafts: (request) => callMyModel(DRAFTS_PROMPT, request),\n * });\n * console.log(formatAiCheckReport(report));\n * if (report.refused > 0 || report.errors > 0) {\n * process.exitCode = 1;\n * }\n * ```\n *\n * A refusal is a real failure, not a warning: it is an answer a learner would\n * have asked for and not received. `reveals-answer` on a hint case is the one\n * to take most seriously — that prompt gives answers away. Next come\n * `misquotes-answer` on a writing case — that prompt corrects words the learner\n * never wrote — and `contradicts-marks` on a coaching case: that prompt coaches\n * words the engine heard as right, or sounds it never reported.\n */\nimport { checkAiExplanation, checkAiHint } from '../ai.js';\nimport { checkAiCoaching } from '../ai-coaching.js';\nimport { checkAiCritique } from '../ai-critique.js';\nimport { checkAiDrafts } from '../ai-drafts.js';\nimport { checkAiWritingFeedback } from '../ai-writing.js';\nimport type {\n AiCoaching,\n AiCoachingRequest,\n AiCritique,\n AiCritiqueRequest,\n AiDrafts,\n AiDraftsRequest,\n AiExplanationRequest,\n AiHintRequest,\n AiRefusal,\n AiTextResult,\n AiWritingFeedback,\n AiWritingFeedbackRequest,\n} from '../types/ai.js';\nimport { type AiCheckCase, aiCheckCases } from './cases.js';\n\nexport { type AiCheckCase, aiCheckCases } from './cases.js';\n\n/**\n * Your ports, as this kit calls them. The same functions\n * `<LkAiProvider ai={…}>` takes, minus the `AbortSignal` a browser passes —\n * nothing here abandons a call.\n *\n * Leave one out and its cases are skipped rather than failed.\n */\nexport interface AiCheckPorts {\n explain?(request: AiExplanationRequest): Promise<unknown> | unknown;\n hint?(request: AiHintRequest): Promise<unknown> | unknown;\n writingFeedback?(request: AiWritingFeedbackRequest): Promise<unknown> | unknown;\n pronunciationCoaching?(request: AiCoachingRequest): Promise<unknown> | unknown;\n /** Reviews an item for its author: the port an editor's \"Review with AI\" calls. */\n critique?(request: AiCritiqueRequest): Promise<unknown> | unknown;\n /** Drafts items from a source, for an author to approve. */\n drafts?(request: AiDraftsRequest): Promise<unknown> | unknown;\n}\n\n/** What happened on one case. */\nexport interface AiCheckResult {\n case: AiCheckCase;\n /** Whether a learner would have been shown what came back. */\n ok: boolean;\n /** Why it would not have been shown. */\n refusal?: AiRefusal;\n /** The port threw or rejected. Its message, as a string. */\n error?: string;\n /**\n * What came back, once the SDK had read it — so a refusal can be read beside\n * the text that caused it. Absent when the port failed or answered with\n * nothing the SDK could read.\n */\n result?: AiTextResult;\n /** For a writing case a learner would have been shown: the feedback, as the SDK accepted it. */\n feedback?: AiWritingFeedback;\n /** For a coaching case a learner would have been shown: the coaching, as the SDK accepted it. */\n coaching?: AiCoaching;\n /** For a critique case an author would have been shown: the findings, as the SDK accepted them. */\n critique?: AiCritique;\n /**\n * For a drafts case the SDK read: the drafts, each with what `validateDraft`\n * and the item critic say of it. A reply read is not a good one — check how\n * many came back `complete` and without warnings.\n */\n drafts?: AiDrafts;\n /** How long the call took, in milliseconds. */\n ms: number;\n}\n\n/** What a run came to. */\nexport interface AiCheckReport {\n /** Cases run: the ones a port was supplied for. */\n total: number;\n /** Cases whose answer a learner would have been shown. */\n shown: number;\n /** Cases whose answer the SDK refused. */\n refused: number;\n /** Cases where the port itself failed. */\n errors: number;\n /** Cases skipped because no port was supplied for that feature. */\n skipped: number;\n /** How many refusals of each reason. */\n byRefusal: Record<AiRefusal, number>;\n results: AiCheckResult[];\n}\n\nexport interface AiCheckOptions {\n /**\n * The cases to run. Defaults to {@link aiCheckCases}. Pass a filtered list to\n * run one type, or your own cases — built with `aiExplanationRequest` and\n * `aiHintRequest` on your own items, which is how you test a prompt against\n * the content your learners actually see.\n */\n cases?: readonly AiCheckCase[];\n /**\n * How many cases to have in flight at once. Default 1: a run in CI hits a\n * rate limit far more often than it runs out of patience.\n */\n concurrency?: number;\n}\n\nconst REFUSALS: AiRefusal[] = [\n 'malformed',\n 'empty',\n 'too-long',\n 'contradicts-grade',\n 'reveals-answer',\n 'misquotes-answer',\n 'contradicts-marks',\n 'contradicts-item',\n];\n\n/** The port a case is for. */\nfunction portFor(\n ports: AiCheckPorts,\n one: AiCheckCase,\n): ((request: never) => Promise<unknown> | unknown) | undefined {\n switch (one.request.feature) {\n case 'explanation':\n return ports.explain as never;\n case 'hint':\n return ports.hint as never;\n case 'pronunciation-coaching':\n return ports.pronunciationCoaching as never;\n case 'item-critique':\n return ports.critique as never;\n case 'draft-generation':\n return ports.drafts as never;\n default:\n return ports.writingFeedback as never;\n }\n}\n\n/** Milliseconds, from whichever clock this runtime has. */\nconst now = (): number => Date.now();\n\n/** Runs one case: calls the port, times it, and checks what came back. */\nasync function runCase(ports: AiCheckPorts, one: AiCheckCase): Promise<AiCheckResult> {\n const started = now();\n const port = portFor(ports, one);\n if (port === undefined) {\n throw new Error(`ai-check: no port for \"${one.id}\"`);\n }\n let raw: unknown;\n try {\n raw = await port(one.request as never);\n } catch (error) {\n return {\n case: one,\n ok: false,\n error: error instanceof Error ? error.message : String(error),\n ms: now() - started,\n };\n }\n const ms = now() - started;\n if (one.request.feature === 'draft-generation') {\n let next = 0;\n const checked = checkAiDrafts(raw, one.request, {\n newId: () => {\n next += 1;\n return `ai-check-${next}`;\n },\n });\n return checked.ok\n ? { case: one, ok: true, drafts: checked.drafts, ms }\n : refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'item-critique') {\n const checked = checkAiCritique(raw, one.request);\n return checked.ok\n ? { case: one, ok: true, critique: checked.critique, ms }\n : refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'pronunciation-coaching') {\n const checked = checkAiCoaching(raw, one.request);\n if (checked.ok) {\n const { text, provenance, usage } = checked.coaching;\n return {\n case: one,\n ok: true,\n result: {\n text,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n coaching: checked.coaching,\n ms,\n };\n }\n return refused(one, raw, checked.refusal, ms);\n }\n if (one.request.feature === 'writing-feedback') {\n const checked = checkAiWritingFeedback(raw, one.request);\n if (checked.ok) {\n const { text, provenance, usage } = checked.feedback;\n return {\n case: one,\n ok: true,\n result: {\n text,\n ...(provenance !== undefined ? { provenance } : {}),\n ...(usage !== undefined ? { usage } : {}),\n },\n feedback: checked.feedback,\n ms,\n };\n }\n return refused(one, raw, checked.refusal, ms);\n }\n const checked =\n one.request.feature === 'explanation'\n ? checkAiExplanation(raw, one.request)\n : checkAiHint(raw, one.request);\n if (checked.ok) {\n return { case: one, ok: true, result: checked.result, ms };\n }\n return refused(one, raw, checked.refusal, ms);\n}\n\n/** A case whose answer was refused, with the text that came back when there was one to read. */\nfunction refused(one: AiCheckCase, raw: unknown, refusal: AiRefusal, ms: number): AiCheckResult {\n const text =\n typeof (raw as { text?: unknown })?.text === 'string' ? (raw as AiTextResult) : undefined;\n return {\n case: one,\n ok: false,\n refusal,\n ...(text !== undefined ? { result: text } : {}),\n ms,\n };\n}\n\n/**\n * Calls your ports on every case and reports what a learner would have seen.\n *\n * It never throws for a failing case: a port that rejects is an `error` in the\n * report, so one bad call cannot hide the rest.\n */\nexport async function runAiCheck(\n ports: AiCheckPorts,\n options: AiCheckOptions = {},\n): Promise<AiCheckReport> {\n const all = options.cases ?? aiCheckCases();\n const asked = Math.trunc(options.concurrency ?? 1);\n const lanes = Number.isFinite(asked) && asked >= 1 ? Math.min(asked, 16) : 1;\n const runnable = all.filter((one) => portFor(ports, one) !== undefined);\n const results: AiCheckResult[] = new Array(runnable.length);\n let next = 0;\n const lane = async (): Promise<void> => {\n while (next < runnable.length) {\n const mine = next;\n next += 1;\n const one = runnable[mine];\n if (one === undefined) {\n return;\n }\n results[mine] = await runCase(ports, one);\n }\n };\n await Promise.all(Array.from({ length: Math.min(lanes, runnable.length) }, lane));\n\n const byRefusal = Object.fromEntries(REFUSALS.map((reason) => [reason, 0])) as Record<\n AiRefusal,\n number\n >;\n let shown = 0;\n let refused = 0;\n let errors = 0;\n for (const result of results) {\n if (result.ok) {\n shown += 1;\n } else if (result.refusal !== undefined) {\n refused += 1;\n byRefusal[result.refusal] += 1;\n } else {\n errors += 1;\n }\n }\n return {\n total: results.length,\n shown,\n refused,\n errors,\n skipped: all.length - runnable.length,\n byRefusal,\n results,\n };\n}\n\n/**\n * The report as a few lines for a CI log: the counts, then every case that\n * failed, with what came back.\n *\n * The refused text is printed — this is your own test run, not a learner's\n * screen, and a hint that gives the answer away is only fixable if you can\n * read it.\n */\nexport function formatAiCheckReport(report: AiCheckReport): string {\n const lines: string[] = [];\n const slowest = report.results.reduce((worst, one) => Math.max(worst, one.ms), 0);\n lines.push(\n `ai-check: ${report.shown}/${report.total} shown, ${report.refused} refused, ${report.errors} failed` +\n `${report.skipped > 0 ? `, ${report.skipped} skipped (no port)` : ''} — slowest ${slowest} ms`,\n );\n for (const reason of REFUSALS) {\n const count = report.byRefusal[reason];\n if (count > 0) {\n lines.push(` ${reason}: ${count}`);\n }\n }\n for (const result of report.results) {\n if (result.ok) {\n continue;\n }\n const why = result.refusal ?? `port failed (${result.error ?? 'no message'})`;\n lines.push(` ✗ ${result.case.id} [${result.case.feature}] ${result.case.about}: ${why}`);\n if (result.result !== undefined) {\n lines.push(` ${result.result.text.replace(/\\s+/g, ' ').slice(0, 200)}`);\n }\n }\n return lines.join('\\n');\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA+CA,MAAM,UAA8B;CAClC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,UAAU;CACV,MAAM;CACN,iBAAiB;CACjB,SAAS;EACP;GAAE,IAAI;GAAU,MAAM;GAAU,WAAW;EAAK;EAChD;GAAE,IAAI;GAAa,MAAM;GAAa,WAAW;EAAM;EACvD;GAAE,IAAI;GAAW,MAAM;GAAW,WAAW;EAAM;CACrD;AACF;AAEA,MAAM,eAAoC;CACxC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,SAAS;CACT,QAAQ,CACN;EAAE,IAAI;EAAM,iBAAiB,CAAC,IAAI;CAAE,GACpC;EAAE,IAAI;EAAQ,iBAAiB,CAAC,MAAM;EAAG,MAAM;CAAgC,CACjF;CACA,iBAAiB;AACnB;AAEA,MAAM,YAA2B;CAC/B,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,QAAQ;CACR,SAAS;CACT,MAAM,CACJ;EACE,IAAI;EACJ,SAAS,CACP;GAAE,IAAI;GAAS,MAAM;EAAQ,GAC7B;GAAE,IAAI;GAAS,MAAM;EAAQ,CAC/B;EACA,iBAAiB;CACnB,GACA;EACE,IAAI;EACJ,SAAS,CACP;GAAE,IAAI;GAAQ,MAAM;EAAO,GAC3B;GAAE,IAAI;GAAM,MAAM;EAAK,CACzB;EACA,iBAAiB;CACnB,CACF;CACA,iBAAiB;AACnB;AAEA,MAAM,YAA2B;CAC/B,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,YAAY;AACd;;;;;;AAOA,MAAM,QACJ;AAIF,MAAM,cAAkC;CACtC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,UAAU;CACV,MAAM;CACN,iBAAiB;CACjB,SAAS;EACP;GAAE,IAAI;GAAW,MAAM;GAAqC,WAAW;EAAK;EAC5E;GAAE,IAAI;GAAa,MAAM;GAAqB,WAAW;EAAM;EAC/D;GAAE,IAAI;GAAU,MAAM;GAAa,WAAW;EAAM;CACtD;AACF;AAEA,MAAM,eAAoC;CACxC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,QAAQ;CACR,UAAU;CACV,UAAU;CACV,gBAAgB;AAClB;AAEA,MAAM,QAAmB;CACvB,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,UAAU;EAAE,IAAI;EAAuB,MAAM;EAAQ,OAAO;EAAS,MAAM;CAAM;CACjF,OAAO,CAAC,aAAa,YAAY;AACnC;;AAGA,MAAM,YAAiC;CACrC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,SAAS;CACT,QAAQ,CAAC;EAAE,IAAI;EAAK,iBAAiB,CAAC,SAAS;CAAE,CAAC;CAClD,iBAAiB;AACnB;AAEA,MAAM,gBAA2B;CAC/B,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,UAAU;EACR,IAAI;EACJ,MAAM;EACN,MAAM;CACR;CACA,OAAO,CAAC,SAAS;AACnB;AAEA,MAAM,UAA+B;CACnC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,QAAQ;CACR,UAAU;CACV,UAAU;CACV,gBAAgB;CAChB,QAAQ,EACN,UAAU;EACR;GAAE,MAAM;GAAW,aAAa;GAAoC,QAAQ;EAAE;EAC9E;GAAE,MAAM;GAAc,aAAa;GAAkC,QAAQ;EAAE;EAC/E;GAAE,MAAM;GAAQ,aAAa;GAAuC,QAAQ;EAAE;CAChF,EACF;AACF;AAEA,MAAM,UAAyB;CAC7B,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,eAAe;CACf,QAAQ;CACR,WAAW,EAAE,YAAY,GAAG;CAC5B,SAAS,EACP,YAAY,CACV;EAAE,MAAM;EAAY,QAAQ;CAAE,GAC9B;EAAE,MAAM;EAAW,QAAQ;CAAE,CAC/B,EACF;AACF;;AAGA,MAAM,QAAQ,MAAc,OAA4B,CAAC,OAAmB;CAC1E;CACA,OAAO;CACP,UAAU;CACV,GAAG;AACL;;;;;;AAOA,SAAS,SAAS,OAAkC;CAClD,OAAO;EACL,mBAAmB;EACnB,QAAQ;EACR,MAAM;EACN,QAAQ;EACR,eAAe,QAAQ;EACvB,cAAc;EACd,UAAU;GAAE,MAAM;GAAQ,IAAI;EAAuB;EACrD,OAAO;EACP,QAAQ,QAAQ;GAAE,UAAU;GAAI,SAAS;GAAI,cAAc;EAAG,IAAI;GAAE,UAAU;GAAI,SAAS;EAAG;EAC9F,QAAQ;EACR,iBAAiB;EACjB,OAAO;GACL,KAAK,KAAK;GACV,QACI,KAAK,WAAW;IACd,UAAU;IACV,OAAO;IACP,UAAU;KACR;MAAE,QAAQ;MAAK,UAAU;KAAG;KAC5B;MAAE,QAAQ;MAAK,UAAU;KAAG;KAC5B;MACE,QAAQ;MACR,UAAU;MACV,SAAS;OACP;QAAE,QAAQ;QAAK,OAAO;OAAG;OACzB;QAAE,QAAQ;QAAK,OAAO;OAAG;OACzB;QAAE,QAAQ;QAAK,OAAO;OAAG;MAC3B;KACF;KACA;MAAE,QAAQ;MAAK,UAAU;KAAG;IAC9B;GACF,CAAC,IACD,KAAK,SAAS;GAClB,KAAK,IAAI;GACT,KAAK,MAAM;GACX,KAAK,IAAI;GACT,KAAK,KAAK;GACV,QAAQ;IAAE,MAAM;IAAS,OAAO;GAAW,IAAI,KAAK,OAAO;EAC7D;CACF;AACF;AA6BA,MAAM,SAAS,SAAoC;CACjD,MAAM;CACN,mBAAmB;AACrB;AACA,MAAM,SAAS,aAAsD;CACnE,MAAM;CACN;AACF;AACA,MAAM,UAAU,aAAsD;CACpE,MAAM;CACN,YAAY;AACd;AACA,MAAM,SAAS,UAAmC;CAAE,MAAM;CAAa;AAAK;AAC5E,MAAM,WAAW,UAAmC;CAClD,MAAM;CACN;CACA,WAAW;AACb;;;;;;AAOA,MAAM,QAA4B;CAChC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,UAAU;CACV,MAAM;CACN,iBAAiB;CACjB,SAAS;EACP;GAAE,IAAI;GAAM,MAAM;GAAS,WAAW;EAAK;EAC3C;GAAE,IAAI;GAAM,MAAM;GAAU,WAAW;EAAM;EAC7C;GAAE,IAAI;GAAM,MAAM;GAAU,WAAW;EAAM;CAC/C;AACF;AAEA,MAAM,UAA+B;CACnC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,QAAQ;CACR,UAAU;CACV,UAAU;AACZ;AAEA,MAAM,aAAkC;CACtC,eAAe;CACf,MAAM;CACN,IAAI;CACJ,OAAO;CACP,SAAS;CACT,QAAQ,CACN;EAAE,IAAI;EAAK,iBAAiB,CAAC,OAAO,MAAM;CAAE,GAC5C;EAAE,IAAI;EAAK,iBAAiB,CAAC,QAAQ,MAAM;CAAE,CAC/C;CACA,iBAAiB;AACnB;AAEA,SAAS,aACP,IACA,OACA,MACA,OACA,QAAmD,CAAC,GACvC;CACb,MAAM,UAAU,kBAAkB;EAAE;EAAM;EAAO,GAAG;CAAM,CAAC;CAC3D,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,sCAAsC,GAAG,EAAE;CAE7D,OAAO;EAAE;EAAI,SAAS;EAAiB;EAAO;CAAQ;AACxD;;AAGA,MAAM,SACJ;;AAKF,MAAM,kBAAkB;CACtB;EAAE,OAAO;EAAG,KAAK;EAAK,MAAM;CAAiD;CAC7E;EAAE,OAAO;EAAK,KAAK;EAAK,MAAM;CAA+D;CAC7F;EAAE,OAAO;EAAK,KAAK;EAAM,MAAM;CAAqD;CACpF;EAAE,OAAO;EAAM,KAAK;EAAM,MAAM;CAAuC;AACzE;AAEA,SAAS,WACP,IACA,OACA,OACA,QACA,QAAyE,CAAC,GAC7D;CACb,MAAM,UAAU,gBAAgB;EAAE;EAAO;EAAQ,GAAG;CAAM,CAAC;CAC3D,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,oCAAoC,GAAG,EAAE;CAE3D,OAAO;EAAE;EAAI,SAAS;EAAoB;EAAO;CAAQ;AAC3D;;;;;AAMA,MAAM,sBAAsB;CAC1B,QAAQ;CACR,WAAW,EAAE,YAAY,GAAG;CAC5B,SAAS,EAAE,YAAY,CAAC;EAAE,MAAM;EAAY,QAAQ;CAAE,CAAC,EAAE;AAC3D;;AAGA,SAAS,gBACP,IACA,OACA,MACA,UACA,OACa;CACb,MAAM,UAAU,qBAAqB;EAAE;EAAM;EAAU,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAAG,CAAC;CACpF,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,yCAAyC,GAAG,EAAE;CAEhE,OAAO;EAAE;EAAI,SAAS;EAAe;EAAO;CAAQ;AACtD;AAEA,SAAS,SACP,IACA,OACA,MACA,UACA,gBAA0B,CAAC,GAC3B,OACa;CACb,MAAM,UAAU,cAAc;EAAE;EAAM;EAAU;EAAe,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAAG,CAAC;CAC5F,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,kCAAkC,GAAG,EAAE;CAEzD,OAAO;EAAE;EAAI,SAAS;EAAQ;EAAO;CAAQ;AAC/C;AAEA,SAAS,YACP,IACA,OACA,MACA,mBAA6B,CAAC,GAC9B,eACA,OAA4D,EAAE,MAAM,QAAQ,GAC/D;CACb,MAAM,UAAU,yBAAyB;EACvC,MAAM,KAAK;EACX,UAAU,QAAQ,IAAI;EACtB;EACA,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;EAC1C,GAAI,kBAAkB,KAAA,IAAY,EAAE,cAAc,IAAI,CAAC;CACzD,CAAC;CACD,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,8CAA8C,GAAG,EAAE;CAErE,OAAO;EAAE;EAAI,SAAS;EAAoB;EAAO;CAAQ;AAC3D;AAEA,SAAS,aACP,IACA,OACA,OACA,eACa;CACb,MAAM,aAAa,MAAM,cAAc,SAAS,IAAI;CACpD,MAAM,QAAQ,eACZ,SACA;EAAE,MAAM;EAAc,WAAW;GAAE,KAAK;GAAiB,UAAU;EAAY;CAAE,GACjF,YACA;EACE,UAAU;GAAE,YAAY;GAAM,UAAU;EAAK;EAC7C,cAAc;GAAE,mBAAmB;GAAG,aAAa;EAAI;CACzD,CACF;CACA,IAAI,gBAAgB,OAClB,MAAM,IAAI,MAAM,8BAA8B,GAAG,mBAAmB,MAAM,QAAQ;CAEpF,MAAM,UAAU,kBAAkB;EAChC,MAAM;EACN,GAAI,MAAM,kBAAkB,OAAO,CAAC,IAAI,EAAE,WAAW;EACrD;EACA,GAAI,kBAAkB,KAAA,IAAY,EAAE,cAAc,IAAI,CAAC;CACzD,CAAC;CACD,IAAI,YAAY,MACd,MAAM,IAAI,MAAM,sCAAsC,GAAG,EAAE;CAE7D,OAAO;EAAE;EAAI,SAAS;EAA0B;EAAO;CAAQ;AACjE;;;;;;;;;;;;;AAcA,SAAgB,eAA8B;CAC5C,OAAO;EACL,gBAAgB,cAAc,wBAAwB,SAAS,MAAM,CAAC,QAAQ,CAAC,CAAC;EAChF,gBAAgB,YAAY,mBAAmB,SAAS,MAAM,CAAC,WAAW,CAAC,CAAC;EAC5E,gBAAgB,YAAY,+BAA+B,SAAS,MAAM,CAAC,CAAC,CAAC;EAC7E,gBACE,eACA,gCACA,cACA,MAAM;GAAE,IAAI;GAAM,MAAM;EAAO,CAAC,CAClC;EACA,gBACE,cACA,8BACA,cACA,MAAM;GAAE,IAAI;GAAM,MAAM;EAAQ,CAAC,CACnC;EACA,gBACE,aACA,qBACA,cACA,MAAM;GAAE,IAAI;GAAO,MAAM;EAAS,CAAC,CACrC;EACA,gBACE,cACA,wCACA,WACA,OAAO;GAAE,IAAI;GAAS,IAAI;EAAK,CAAC,CAClC;EACA,gBACE,aACA,+BACA,WACA,OAAO;GAAE,IAAI;GAAS,IAAI;EAAK,CAAC,CAClC;EACA,gBACE,kBACA,2CACA,WACA,MAAM,oCAAoC,CAC5C;EACA,gBACE,qBACA,gCACA,WACA,MAAM,qCAAqC,CAC7C;EACA,gBACE,oBACA,sEACA,aACA,MAAM,CAAC,WAAW,CAAC,GACnB,KACF;EACA,SAAS,aAAa,wBAAwB,SAAS,MAAM,CAAC,CAAC,CAAC;EAChE,SAAS,aAAa,sCAAsC,SAAS,MAAM,CAAC,SAAS,CAAC,GAAG,CACvF,wCACF,CAAC;EACD,SAAS,cAAc,qBAAqB,cAAc,MAAM,CAAC,CAAC,CAAC;EACnE,SACE,cACA,oCACA,cACA,MAAM;GAAE,IAAI;GAAO,MAAM;EAAG,CAAC,GAC7B,CAAC,gCAAgC,CACnC;EACA,SAAS,cAAc,kCAAkC,WAAW,OAAO,CAAC,CAAC,CAAC;EAC9E,SACE,cACA,wCACA,WACA,OAAO,EAAE,IAAI,QAAQ,CAAC,GACtB,CAAC,iCAAiC,CACpC;EACA,SACE,mBACA,uEACA,aACA,MAAM,CAAC,CAAC,GACR,CAAC,GACD,KACF;EACA,SACE,sBACA,kEACA,WACA,MAAM,CAAC,CAAC,GACR,CAAC,GACD,aACF;EACA,YACE,oBACA,4DACA,kIACF;EACA,YACE,iBACA,6CACA,+IACF;EACA,YACE,mBACA,2CACA,+GACA,CAAC,oEAAgE,CACnE;EACA,YACE,+BACA,qDACA,gGACA,CAAC,GACD,IACF;EACA,YACE,mBACA,qEACA,6GACA,CAAC,GACD,KAAA,GACA;GAAE,MAAM;GAAc,OAAO;EAAM,CACrC;EACA,aAAa,kBAAkB,6DAA2D,EACxF,YAAY,SAAS,IAAI,EAC3B,CAAC;EACD,aAAa,kBAAkB,kDAAkD,EAC/E,YAAY,SAAS,KAAK,EAC5B,CAAC;EACD,aACE,uBACA,6DACA,EAAE,eAAe,KAAK,CACxB;EACA,aACE,uBACA,sDACA,EAAE,YAAY,SAAS,IAAI,EAAE,GAC7B,IACF;EACA,aACE,0BACA,iEACA,mBACA,OACA,EAAE,OAAO,KAAK,CAChB;EACA,aACE,kBACA,uDACA,oBACA,SACA,EAAE,OAAO,KAAK,CAChB;EACA,aACE,kBACA,oCACA,sBACA,YACA,EACE,OAAO,KACT,CACF;EACA,aACE,uBACA,0DACA,mBACA,OACA;GAAE,OAAO;GAAM,cAAc;EAAK,CACpC;EACA,WACE,oBACA,qDACA,CAAC,iBAAiB,GAClB;GAAE,MAAM;GAAW,MAAM;EAAO,GAChC;GAAE,OAAO;GAAG,OAAO;GAAM,QAAQ;EAAK,CACxC;EACA,WACE,gBACA,oDACA,CAAC,oBAAoB,GACrB;GAAE,MAAM;GAAc,MAAM;EAAO,GACnC;GAAE,OAAO;GAAG,OAAO;GAAM,QAAQ;GAAM,cAAc;EAAmB,CAC1E;EACA,WACE,eACA,8CACA,CAAC,YAAY,GACb;GAAE,MAAM;GAAW,MAAM;EAAO,GAChC;GAAE,OAAO;GAAG,OAAO;GAAM,QAAQ;EAAK,CACxC;EACA,WACE,kBACA,mDACA,CAAC,kBAAkB,GACnB;GAAE,MAAM;GAAW,MAAM;EAAO,GAChC;GAAE,OAAO;GAAG,OAAO;GAAM,QAAQ;EAAK,CACxC;EACA,WACE,gBACA,qHACA;GAAC;GAAmB;GAAa;EAAY,GAC7C;GAAE,MAAM;GAAY,MAAM;EAAgB,GAC1C;GAAE,OAAO;GAAM,QAAQ;GAAM,UAAU,EAAE,cAAc,oBAAoB;EAAE,CAC/E;EACA,WACE,iBACA,sFACA;GAAC;GAAmB;GAAa;EAAY,GAC7C;GAAE,MAAM;GAAc,MAAM;EAAO,GACnC;GACE,OAAO;GACP,QAAQ;GACR,cAAc;GACd,UAAU,EAAE,cAAc,oBAAoB;EAChD,CACF;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpkBA,MAAM,WAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAS,QACP,OACA,KAC8D;CAC9D,QAAQ,IAAI,QAAQ,SAApB;EACE,KAAK,eACH,OAAO,MAAM;EACf,KAAK,QACH,OAAO,MAAM;EACf,KAAK,0BACH,OAAO,MAAM;EACf,KAAK,iBACH,OAAO,MAAM;EACf,KAAK,oBACH,OAAO,MAAM;EACf,SACE,OAAO,MAAM;CACjB;AACF;;AAGA,MAAM,YAAoB,KAAK,IAAI;;AAGnC,eAAe,QAAQ,OAAqB,KAA0C;CACpF,MAAM,UAAU,IAAI;CACpB,MAAM,OAAO,QAAQ,OAAO,GAAG;CAC/B,IAAI,SAAS,KAAA,GACX,MAAM,IAAI,MAAM,0BAA0B,IAAI,GAAG,EAAE;CAErD,IAAI;CACJ,IAAI;EACF,MAAM,MAAM,KAAK,IAAI,OAAgB;CACvC,SAAS,OAAO;EACd,OAAO;GACL,MAAM;GACN,IAAI;GACJ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC5D,IAAI,IAAI,IAAI;EACd;CACF;CACA,MAAM,KAAK,IAAI,IAAI;CACnB,IAAI,IAAI,QAAQ,YAAY,oBAAoB;EAC9C,IAAI,OAAO;EACX,MAAM,UAAU,cAAc,KAAK,IAAI,SAAS,EAC9C,aAAa;GACX,QAAQ;GACR,OAAO,YAAY;EACrB,EACF,CAAC;EACD,OAAO,QAAQ,KACX;GAAE,MAAM;GAAK,IAAI;GAAM,QAAQ,QAAQ;GAAQ;EAAG,IAClD,QAAQ,KAAK,KAAK,QAAQ,SAAS,EAAE;CAC3C;CACA,IAAI,IAAI,QAAQ,YAAY,iBAAiB;EAC3C,MAAM,UAAU,gBAAgB,KAAK,IAAI,OAAO;EAChD,OAAO,QAAQ,KACX;GAAE,MAAM;GAAK,IAAI;GAAM,UAAU,QAAQ;GAAU;EAAG,IACtD,QAAQ,KAAK,KAAK,QAAQ,SAAS,EAAE;CAC3C;CACA,IAAI,IAAI,QAAQ,YAAY,0BAA0B;EACpD,MAAM,UAAU,gBAAgB,KAAK,IAAI,OAAO;EAChD,IAAI,QAAQ,IAAI;GACd,MAAM,EAAE,MAAM,YAAY,UAAU,QAAQ;GAC5C,OAAO;IACL,MAAM;IACN,IAAI;IACJ,QAAQ;KACN;KACA,GAAI,eAAe,KAAA,IAAY,EAAE,WAAW,IAAI,CAAC;KACjD,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;IACzC;IACA,UAAU,QAAQ;IAClB;GACF;EACF;EACA,OAAO,QAAQ,KAAK,KAAK,QAAQ,SAAS,EAAE;CAC9C;CACA,IAAI,IAAI,QAAQ,YAAY,oBAAoB;EAC9C,MAAM,UAAU,uBAAuB,KAAK,IAAI,OAAO;EACvD,IAAI,QAAQ,IAAI;GACd,MAAM,EAAE,MAAM,YAAY,UAAU,QAAQ;GAC5C,OAAO;IACL,MAAM;IACN,IAAI;IACJ,QAAQ;KACN;KACA,GAAI,eAAe,KAAA,IAAY,EAAE,WAAW,IAAI,CAAC;KACjD,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;IACzC;IACA,UAAU,QAAQ;IAClB;GACF;EACF;EACA,OAAO,QAAQ,KAAK,KAAK,QAAQ,SAAS,EAAE;CAC9C;CACA,MAAM,UACJ,IAAI,QAAQ,YAAY,gBACpB,mBAAmB,KAAK,IAAI,OAAO,IACnC,YAAY,KAAK,IAAI,OAAO;CAClC,IAAI,QAAQ,IACV,OAAO;EAAE,MAAM;EAAK,IAAI;EAAM,QAAQ,QAAQ;EAAQ;CAAG;CAE3D,OAAO,QAAQ,KAAK,KAAK,QAAQ,SAAS,EAAE;AAC9C;;AAGA,SAAS,QAAQ,KAAkB,KAAc,SAAoB,IAA2B;CAC9F,MAAM,OACJ,OAAQ,KAA4B,SAAS,WAAY,MAAuB,KAAA;CAClF,OAAO;EACL,MAAM;EACN,IAAI;EACJ;EACA,GAAI,SAAS,KAAA,IAAY,EAAE,QAAQ,KAAK,IAAI,CAAC;EAC7C;CACF;AACF;;;;;;;AAQA,eAAsB,WACpB,OACA,UAA0B,CAAC,GACH;CACxB,MAAM,MAAM,QAAQ,SAAS,aAAa;CAC1C,MAAM,QAAQ,KAAK,MAAM,QAAQ,eAAe,CAAC;CACjD,MAAM,QAAQ,OAAO,SAAS,KAAK,KAAK,SAAS,IAAI,KAAK,IAAI,OAAO,EAAE,IAAI;CAC3E,MAAM,WAAW,IAAI,QAAQ,QAAQ,QAAQ,OAAO,GAAG,MAAM,KAAA,CAAS;CACtE,MAAM,UAA2B,IAAI,MAAM,SAAS,MAAM;CAC1D,IAAI,OAAO;CACX,MAAM,OAAO,YAA2B;EACtC,OAAO,OAAO,SAAS,QAAQ;GAC7B,MAAM,OAAO;GACb,QAAQ;GACR,MAAM,MAAM,SAAS;GACrB,IAAI,QAAQ,KAAA,GACV;GAEF,QAAQ,QAAQ,MAAM,QAAQ,OAAO,GAAG;EAC1C;CACF;CACA,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CAEhF,MAAM,YAAY,OAAO,YAAY,SAAS,KAAK,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;CAI1E,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,IAAI,SAAS;CACb,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,IACT,SAAS;MACJ,IAAI,OAAO,YAAY,KAAA,GAAW;EACvC,WAAW;EACX,UAAU,OAAO,YAAY;CAC/B,OACE,UAAU;CAGd,OAAO;EACL,OAAO,QAAQ;EACf;EACA;EACA;EACA,SAAS,IAAI,SAAS,SAAS;EAC/B;EACA;CACF;AACF;;;;;;;;;AAUA,SAAgB,oBAAoB,QAA+B;CACjE,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAU,OAAO,QAAQ,QAAQ,OAAO,QAAQ,KAAK,IAAI,OAAO,IAAI,EAAE,GAAG,CAAC;CAChF,MAAM,KACJ,aAAa,OAAO,MAAM,GAAG,OAAO,MAAM,UAAU,OAAO,QAAQ,YAAY,OAAO,OAAO,SACxF,OAAO,UAAU,IAAI,KAAK,OAAO,QAAQ,sBAAsB,GAAG,aAAa,QAAQ,IAC9F;CACA,KAAK,MAAM,UAAU,UAAU;EAC7B,MAAM,QAAQ,OAAO,UAAU;EAC/B,IAAI,QAAQ,GACV,MAAM,KAAK,KAAK,OAAO,IAAI,OAAO;CAEtC;CACA,KAAK,MAAM,UAAU,OAAO,SAAS;EACnC,IAAI,OAAO,IACT;EAEF,MAAM,MAAM,OAAO,WAAW,gBAAgB,OAAO,SAAS,aAAa;EAC3E,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK;EACxF,IAAI,OAAO,WAAW,KAAA,GACpB,MAAM,KAAK,SAAS,OAAO,OAAO,KAAK,QAAQ,QAAQ,GAAG,CAAC,CAAC,MAAM,GAAG,GAAG,GAAG;CAE/E;CACA,OAAO,MAAM,KAAK,IAAI;AACxB"}
|
|
@@ -1,6 +1,95 @@
|
|
|
1
|
-
import { B as issue, D as keyPositionFinding, F as covers, G as groupCaptionsField, H as refusesEmpty, I as isMissingId, L as isRecord$
|
|
1
|
+
import { B as issue, D as keyPositionFinding, F as covers, G as groupCaptionsField, H as refusesEmpty, I as isMissingId, L as isRecord$5, M as DRAFT_ISSUE_SEVERITY, N as checkMedia, O as normaliseFinding, P as coveredPathsOf, R as isUnset, V as pathKey, W as GROUP_CAPTIONS_REVEAL_DICTATION, Y as UnknownActivityTypeError, Z as alignDictation, a as readSchema, dt as containsWords, ft as fold, ht as revealsAnswer, j as countWords, mt as placeholderOrder, pt as numberedPassage, st as withValidationScope, t as getActivityTypeDescriptor, z as isUnwritten } from "./registry-Dsx9mNsS.js";
|
|
2
2
|
import { c as validateSpeechAssessment, d as gradeFromRubric, r as score, s as alignReadAloud, w as isGradeInRange } from "./scoring-BZqUkuDF.js";
|
|
3
3
|
import { a as INTERACTIVE_VIDEO_ITEM_TYPES, i as validateItemGroup, t as ItemGroupSchema, u as isInteractiveVideoItemType } from "./item-group-DXiR8nOr.js";
|
|
4
|
+
//#region src/ai-material.ts
|
|
5
|
+
/**
|
|
6
|
+
* What a model is told about the group a question sits in: the material the
|
|
7
|
+
* learner reads and sees beside it.
|
|
8
|
+
*
|
|
9
|
+
* Internal: the request builders in `ai.ts` and `ai-writing.ts` are its public
|
|
10
|
+
* face, through their `group` input.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The longest material text a model is given, in code points. A longer one is
|
|
14
|
+
* cut and marked `truncated`: a long reading passage on an exam paper runs to
|
|
15
|
+
* 4,000–7,000.
|
|
16
|
+
*/
|
|
17
|
+
const AI_MATERIAL_MAX_LENGTH = 12e3;
|
|
18
|
+
const KINDS$2 = {
|
|
19
|
+
text: true,
|
|
20
|
+
audio: true,
|
|
21
|
+
video: true,
|
|
22
|
+
image: true,
|
|
23
|
+
mixed: true
|
|
24
|
+
};
|
|
25
|
+
const MEDIA_TYPES = {
|
|
26
|
+
image: true,
|
|
27
|
+
audio: true,
|
|
28
|
+
video: true,
|
|
29
|
+
embed: true
|
|
30
|
+
};
|
|
31
|
+
const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
|
+
/** A string with something in it, as written; otherwise nothing. */
|
|
33
|
+
const filled = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
34
|
+
/**
|
|
35
|
+
* `text` cut to {@link AI_MATERIAL_MAX_LENGTH} code points. The cut falls at
|
|
36
|
+
* the last space in its second half, so no word is broken; in a script written
|
|
37
|
+
* without spaces, at the limit itself.
|
|
38
|
+
*/
|
|
39
|
+
function cut(text) {
|
|
40
|
+
const points = [...text];
|
|
41
|
+
if (points.length <= 12e3) return {
|
|
42
|
+
text,
|
|
43
|
+
truncated: false
|
|
44
|
+
};
|
|
45
|
+
let end = AI_MATERIAL_MAX_LENGTH;
|
|
46
|
+
for (let at = AI_MATERIAL_MAX_LENGTH; at >= AI_MATERIAL_MAX_LENGTH / 2; at -= 1) if (/\s/u.test(points[at])) {
|
|
47
|
+
end = at;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
text: points.slice(0, end).join("").trimEnd(),
|
|
52
|
+
truncated: true
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The facts of `group`'s material, or `null` without a group or a stimulus it
|
|
57
|
+
* can read: not an object, or of a kind the SDK does not know.
|
|
58
|
+
*
|
|
59
|
+
* Only what the learner sees reaches a model. The `transcript` is author-only
|
|
60
|
+
* and never read, so the facts are the same for a whole group and a redacted
|
|
61
|
+
* one; a media address is never read either, since a model cannot open one
|
|
62
|
+
* and a signed address must not end up in a prompt. Each field is read once.
|
|
63
|
+
*/
|
|
64
|
+
function materialFacts(group) {
|
|
65
|
+
if (!isRecord$4(group)) return null;
|
|
66
|
+
const stimulus = group.stimulus;
|
|
67
|
+
if (!isRecord$4(stimulus)) return null;
|
|
68
|
+
const kind = stimulus.kind;
|
|
69
|
+
if (typeof kind !== "string" || !Object.hasOwn(KINDS$2, kind)) return null;
|
|
70
|
+
const title = filled(stimulus.title) ?? filled(group.title);
|
|
71
|
+
const body = filled(stimulus.body);
|
|
72
|
+
const text = body === void 0 ? void 0 : cut(body);
|
|
73
|
+
const rawMedia = stimulus.media;
|
|
74
|
+
const mediaType = isRecord$4(rawMedia) ? rawMedia.type : void 0;
|
|
75
|
+
const alt = isRecord$4(rawMedia) ? filled(rawMedia.alt) : void 0;
|
|
76
|
+
const media = typeof mediaType === "string" && Object.hasOwn(MEDIA_TYPES, mediaType) ? {
|
|
77
|
+
type: mediaType,
|
|
78
|
+
...alt !== void 0 ? { alt } : {}
|
|
79
|
+
} : void 0;
|
|
80
|
+
const attribution = filled(stimulus.attribution);
|
|
81
|
+
const locale = filled(stimulus.locale);
|
|
82
|
+
return {
|
|
83
|
+
kind,
|
|
84
|
+
...title !== void 0 ? { title } : {},
|
|
85
|
+
...text !== void 0 ? { text: text.text } : {},
|
|
86
|
+
...text?.truncated === true ? { truncated: true } : {},
|
|
87
|
+
...media !== void 0 ? { media } : {},
|
|
88
|
+
...attribution !== void 0 ? { attribution } : {},
|
|
89
|
+
...locale !== void 0 ? { locale } : {}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
4
93
|
//#region src/ai-text.ts
|
|
5
94
|
/**
|
|
6
95
|
* How the SDK reads text a model wrote, before a learner does. Internal: shared
|
|
@@ -341,6 +430,10 @@ function gradeLocally(data, response) {
|
|
|
341
430
|
* positive and finite, `score` finite from 0 to it) gets no explanation at all:
|
|
342
431
|
* explaining 85 "out of 1" would tell the model a wrong answer was correct, and
|
|
343
432
|
* grading locally instead would speak to a grade the screen does not show.
|
|
433
|
+
*
|
|
434
|
+
* `group` is the item group the question sits in: its material — the passage,
|
|
435
|
+
* or the instructions the group's questions share — goes with the request as
|
|
436
|
+
* `material`, as the learner sees it.
|
|
344
437
|
*/
|
|
345
438
|
function aiExplanationRequest(input) {
|
|
346
439
|
const { data, response, outcome, learnerLocale } = input;
|
|
@@ -366,10 +459,12 @@ function aiExplanationRequest(input) {
|
|
|
366
459
|
if (grade === null) return null;
|
|
367
460
|
const facts = buildAiFacts(data, response, details);
|
|
368
461
|
if (facts === null) return null;
|
|
462
|
+
const material = materialFacts(input.group);
|
|
369
463
|
return {
|
|
370
464
|
feature: "explanation",
|
|
371
465
|
facts,
|
|
372
466
|
grade,
|
|
467
|
+
...material !== null ? { material } : {},
|
|
373
468
|
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
374
469
|
};
|
|
375
470
|
}
|
|
@@ -380,7 +475,11 @@ function aiExplanationRequest(input) {
|
|
|
380
475
|
*
|
|
381
476
|
* The facts carry the key, and which parts the learner has right so far, so a
|
|
382
477
|
* model can hint where it helps. The learner never sees them; a hint that
|
|
383
|
-
* contains an answer is refused by {@link checkAiHint}
|
|
478
|
+
* contains an answer is refused by {@link checkAiHint}, whatever it quotes —
|
|
479
|
+
* the group's material included.
|
|
480
|
+
*
|
|
481
|
+
* `group` is the item group the question sits in, as for
|
|
482
|
+
* {@link aiExplanationRequest}.
|
|
384
483
|
*/
|
|
385
484
|
function aiHintRequest(input) {
|
|
386
485
|
const { data, previousHints, learnerLocale } = input;
|
|
@@ -388,11 +487,13 @@ function aiHintRequest(input) {
|
|
|
388
487
|
const response = input.response ?? emptyResponse(data.type);
|
|
389
488
|
const facts = buildAiFacts(data, response, (response === null ? null : gradeLocally(data, response))?.details ?? null);
|
|
390
489
|
if (facts === null) return null;
|
|
490
|
+
const material = materialFacts(input.group);
|
|
391
491
|
return {
|
|
392
492
|
feature: "hint",
|
|
393
493
|
facts,
|
|
394
494
|
hintNumber: previousHints.length + 1,
|
|
395
495
|
previousHints: [...previousHints],
|
|
496
|
+
...material !== null ? { material } : {},
|
|
396
497
|
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
397
498
|
};
|
|
398
499
|
}
|
|
@@ -860,7 +961,7 @@ function createInteractiveVideoDraft(context) {
|
|
|
860
961
|
* {@link validateItemGroupDraft} already reports it.
|
|
861
962
|
*/
|
|
862
963
|
function critiqueItemGroupDraft(draft) {
|
|
863
|
-
if (!isRecord$
|
|
964
|
+
if (!isRecord$5(draft)) return [];
|
|
864
965
|
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
865
966
|
return withValidationScope(() => [...items.flatMap((item, index) => repathed(critiqueItem(item), ["items", index])), ...keyPositionFinding(items, ["items"])]);
|
|
866
967
|
}
|
|
@@ -872,11 +973,11 @@ function critiqueItemGroupDraft(draft) {
|
|
|
872
973
|
* skipped.
|
|
873
974
|
*/
|
|
874
975
|
function critiqueDrafts(entries) {
|
|
875
|
-
return withValidationScope(() => [...entries.flatMap((entry, index) => repathed(isRecord$
|
|
976
|
+
return withValidationScope(() => [...entries.flatMap((entry, index) => repathed(isRecord$5(entry) && entry.type === "item-group" ? critiqueItemGroupDraft(entry) : critiqueItem(entry), [index])), ...keyPositionFinding(entries, [])]);
|
|
876
977
|
}
|
|
877
978
|
/** One item's findings, or none for an item the critic cannot place. */
|
|
878
979
|
function critiqueItem(item) {
|
|
879
|
-
if (!isRecord$
|
|
980
|
+
if (!isRecord$5(item) || typeof item.type !== "string") return [];
|
|
880
981
|
if (getActivityTypeDescriptor(item.type) === void 0) return [];
|
|
881
982
|
return critiqueDraft(item.type, item);
|
|
882
983
|
}
|
|
@@ -889,7 +990,7 @@ function validateItemGroupDraft(draft) {
|
|
|
889
990
|
}
|
|
890
991
|
function validateItemGroupDraftInScope(draft) {
|
|
891
992
|
const issues = [];
|
|
892
|
-
if (!isRecord$
|
|
993
|
+
if (!isRecord$5(draft)) {
|
|
893
994
|
issues.push(issue("ig_not_an_object", [], "A group must be an object with a stimulus and its items."));
|
|
894
995
|
return {
|
|
895
996
|
status: "invalid",
|
|
@@ -897,7 +998,7 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
897
998
|
};
|
|
898
999
|
}
|
|
899
1000
|
issues.push(...checkGroupIdentity(draft));
|
|
900
|
-
if (isRecord$
|
|
1001
|
+
if (isRecord$5(draft.stimulus)) issues.push(...checkStimulus(draft.stimulus));
|
|
901
1002
|
else if (isUnset(draft.stimulus)) issues.push(issue("ig_stimulus_required", ["stimulus"], "Add the passage, recording or image the questions are about."));
|
|
902
1003
|
issues.push(...checkItems(draft));
|
|
903
1004
|
if (draft.timeline !== void 0 && draft.timeline !== null) issues.push(...checkTimeline(draft));
|
|
@@ -947,10 +1048,10 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
947
1048
|
function checkTimeline(draft) {
|
|
948
1049
|
const issues = [];
|
|
949
1050
|
const timeline = draft.timeline;
|
|
950
|
-
if (!isRecord$
|
|
951
|
-
const stimulus = isRecord$
|
|
1051
|
+
if (!isRecord$5(timeline)) return issues;
|
|
1052
|
+
const stimulus = isRecord$5(draft.stimulus) ? draft.stimulus : void 0;
|
|
952
1053
|
if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") issues.push(issue("ig_timeline_stimulus_kind", ["stimulus", "kind"], "An interactive video needs a video: quizzes open at moments of it."));
|
|
953
|
-
else if (stimulus !== void 0 && isRecord$
|
|
1054
|
+
else if (stimulus !== void 0 && isRecord$5(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") issues.push(issue("ig_timeline_stimulus_kind", [
|
|
954
1055
|
"stimulus",
|
|
955
1056
|
"media",
|
|
956
1057
|
"type"
|
|
@@ -959,7 +1060,7 @@ function checkTimeline(draft) {
|
|
|
959
1060
|
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
960
1061
|
const itemIndexById = /* @__PURE__ */ new Map();
|
|
961
1062
|
items.forEach((item, index) => {
|
|
962
|
-
if (!isRecord$
|
|
1063
|
+
if (!isRecord$5(item)) return;
|
|
963
1064
|
if (typeof item.id === "string" && item.id !== "") itemIndexById.set(item.id, index);
|
|
964
1065
|
const type = item.type;
|
|
965
1066
|
if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) issues.push(issue("ig_timeline_item_type", [
|
|
@@ -977,7 +1078,7 @@ function checkTimeline(draft) {
|
|
|
977
1078
|
const quizIds = /* @__PURE__ */ new Set();
|
|
978
1079
|
const placed = /* @__PURE__ */ new Set();
|
|
979
1080
|
cues.forEach((cue, cueIndex) => {
|
|
980
|
-
if (!isRecord$
|
|
1081
|
+
if (!isRecord$5(cue)) return;
|
|
981
1082
|
const at = (field, ...rest) => [
|
|
982
1083
|
"timeline",
|
|
983
1084
|
"cues",
|
|
@@ -1008,7 +1109,7 @@ function checkTimeline(draft) {
|
|
|
1008
1109
|
const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
|
|
1009
1110
|
let previousAt;
|
|
1010
1111
|
chapters.forEach((chapter, index) => {
|
|
1011
|
-
if (!isRecord$
|
|
1112
|
+
if (!isRecord$5(chapter)) return;
|
|
1012
1113
|
if (isUnwritten(chapter.title)) issues.push(issue("ig_timeline_chapter_title", [
|
|
1013
1114
|
"timeline",
|
|
1014
1115
|
"chapters",
|
|
@@ -1069,14 +1170,14 @@ function checkStimulus(stimulus) {
|
|
|
1069
1170
|
if (isUnwritten(kind)) issues.push(issue("ig_stimulus_kind_required", at("kind"), "Choose what the questions are about: a text, a recording, a video, an image, or a mix."));
|
|
1070
1171
|
else if (typeof kind !== "string" || !KINDS$1.has(kind)) {
|
|
1071
1172
|
if (typeof kind === "string") issues.push(issue("ig_stimulus_kind_invalid", at("kind"), `"${kind}" is not a stimulus kind. Use text, audio, video, image or mixed.`));
|
|
1072
|
-
if (isRecord$
|
|
1173
|
+
if (isRecord$5(stimulus.media)) issues.push(...checkMedia(stimulus.media, at("media")));
|
|
1073
1174
|
return issues;
|
|
1074
1175
|
}
|
|
1075
1176
|
const hasBody = typeof stimulus.body === "string" && stimulus.body.trim() !== "";
|
|
1076
1177
|
const htmlPresent = !isUnset(stimulus.bodyHtml);
|
|
1077
1178
|
if (!hasBody && (kind === "text" || kind === "mixed" || htmlPresent)) issues.push(issue("ig_stimulus_body_required", at("body"), isUnwritten(stimulus.bodyHtml) ? "Write the passage." : "Write the passage as plain text too. The rich-text version is shown only where a sanitiser is supplied."));
|
|
1078
1179
|
if (kind !== "text" && isUnset(stimulus.media)) issues.push(issue("ig_stimulus_media_required", at("media"), "Add the recording, video or image."));
|
|
1079
|
-
else if (isRecord$
|
|
1180
|
+
else if (isRecord$5(stimulus.media)) {
|
|
1080
1181
|
const allowed = MEDIA_FOR_KIND[kind];
|
|
1081
1182
|
const mediaType = stimulus.media.type;
|
|
1082
1183
|
if (allowed !== void 0 && typeof mediaType === "string" && !allowed.includes(mediaType)) issues.push(issue("ig_stimulus_media_kind", at("media", "type"), `A "${kind}" stimulus cannot carry ${mediaType} media. Use ${allowed.join(" or ")}.`));
|
|
@@ -1103,7 +1204,7 @@ function checkItems(draft) {
|
|
|
1103
1204
|
const seen = /* @__PURE__ */ new Set();
|
|
1104
1205
|
const repeated = /* @__PURE__ */ new Set();
|
|
1105
1206
|
for (const [index, item] of items.entries()) {
|
|
1106
|
-
if (!isRecord$
|
|
1207
|
+
if (!isRecord$5(item)) continue;
|
|
1107
1208
|
const ordinal = index + 1;
|
|
1108
1209
|
if (isMissingId(item.id)) issues.push(issue("ig_item_id_required", [
|
|
1109
1210
|
"items",
|
|
@@ -1200,7 +1301,7 @@ function validateDraft(type, draft) {
|
|
|
1200
1301
|
function validateDraftInScope(type, draft) {
|
|
1201
1302
|
const descriptor = getActivityTypeDescriptor(type);
|
|
1202
1303
|
if (descriptor === void 0) throw new UnknownActivityTypeError(String(type));
|
|
1203
|
-
const reported = isRecord$
|
|
1304
|
+
const reported = isRecord$5(draft) && descriptor.authoring?.checkDraft !== void 0 ? descriptor.authoring.checkDraft(draft).map(normalise) : [];
|
|
1204
1305
|
const issues = [...reported];
|
|
1205
1306
|
const parsed = readSchema(descriptor.schema, draft, `Activity type "${descriptor.type}"`, { reportInput: true });
|
|
1206
1307
|
if (!parsed.success) {
|
|
@@ -1310,7 +1411,7 @@ function critiqueDraft(type, draft) {
|
|
|
1310
1411
|
const descriptor = getActivityTypeDescriptor(type);
|
|
1311
1412
|
if (descriptor === void 0) throw new UnknownActivityTypeError(String(type));
|
|
1312
1413
|
const critique = descriptor.authoring?.critique;
|
|
1313
|
-
if (!isRecord$
|
|
1414
|
+
if (!isRecord$5(draft) || critique === void 0) return [];
|
|
1314
1415
|
return withValidationScope(() => critique(draft)).map(normaliseFinding);
|
|
1315
1416
|
}
|
|
1316
1417
|
//#endregion
|
|
@@ -2204,7 +2305,9 @@ const MAX_TAG_LENGTH = 40;
|
|
|
2204
2305
|
* The facts are the task as the learner saw it, the draft verbatim with its
|
|
2205
2306
|
* word count recomputed as the grader will count it, and the rubric the answer
|
|
2206
2307
|
* will be graded on. `previousFeedback` is the text of the feedback already
|
|
2207
|
-
* shown on earlier drafts, so a model can speak to what changed.
|
|
2308
|
+
* shown on earlier drafts, so a model can speak to what changed. `group` is the
|
|
2309
|
+
* item group the task sits in: its material, the text a summary is of, goes
|
|
2310
|
+
* with the request as `material`.
|
|
2208
2311
|
*/
|
|
2209
2312
|
function aiWritingFeedbackRequest(input) {
|
|
2210
2313
|
const { data, response, learnerLocale } = input;
|
|
@@ -2222,6 +2325,7 @@ function aiWritingFeedbackRequest(input) {
|
|
|
2222
2325
|
weight: criterion.weight
|
|
2223
2326
|
}));
|
|
2224
2327
|
const previousFeedback = [...input.previousFeedback ?? []];
|
|
2328
|
+
const material = materialFacts(input.group);
|
|
2225
2329
|
return {
|
|
2226
2330
|
feature: "writing-feedback",
|
|
2227
2331
|
facts: {
|
|
@@ -2240,6 +2344,7 @@ function aiWritingFeedbackRequest(input) {
|
|
|
2240
2344
|
},
|
|
2241
2345
|
draftNumber: previousFeedback.length + 1,
|
|
2242
2346
|
previousFeedback,
|
|
2347
|
+
...material !== null ? { material } : {},
|
|
2243
2348
|
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
2244
2349
|
};
|
|
2245
2350
|
}
|
|
@@ -2491,6 +2596,6 @@ function checkAiWritingFeedback(raw, request) {
|
|
|
2491
2596
|
};
|
|
2492
2597
|
}
|
|
2493
2598
|
//#endregion
|
|
2494
|
-
export { AI_TEXT_MAX_LENGTH as A, readAiTextResult as B, validateItemGroupDraft as C, checkAiCoaching as D, aiCoachingRequest as E, aiSupports as F, buildAiFacts as I, checkAiExplanation as L, aiExplanationRequest as M, aiGradeOf as N, AI_EXPLANATION_TYPES as O, aiHintRequest as P, checkAiHint as R, critiqueItemGroupDraft as S, AI_COACHING_MAX_WORDS as T, critiqueDraft as _, AI_DRAFTS_MAX_COUNT as a, createItemGroupDraft as b, aiDraftsRequest as c, interactiveVideoFromDrafts as d, AI_CRITIQUE_MAX_FIELD_LENGTH as f, createDraft as g, checkAiCritique as h, checkAiWritingFeedback as i, aiAllowedByContent as j, AI_HINT_TYPES as k, checkAiDrafts as l, aiCritiqueRequest as m, AI_WRITING_MAX_FIELD_LENGTH as n, AI_DRAFTS_MAX_REPAIRS as o, AI_CRITIQUE_MAX_FINDINGS as p, aiWritingFeedbackRequest as r, aiDraftsRepairRequest as s, AI_WRITING_MAX_CORRECTIONS as t, generateDrafts as u, validateDraft as v, AI_COACHING_MAX_TIP_LENGTH as w, critiqueDrafts as x, createInteractiveVideoDraft as y, hintRevealsAnswer as z };
|
|
2599
|
+
export { AI_TEXT_MAX_LENGTH as A, readAiTextResult as B, validateItemGroupDraft as C, checkAiCoaching as D, aiCoachingRequest as E, aiSupports as F, buildAiFacts as I, checkAiExplanation as L, aiExplanationRequest as M, aiGradeOf as N, AI_EXPLANATION_TYPES as O, aiHintRequest as P, checkAiHint as R, critiqueItemGroupDraft as S, AI_COACHING_MAX_WORDS as T, AI_MATERIAL_MAX_LENGTH as V, critiqueDraft as _, AI_DRAFTS_MAX_COUNT as a, createItemGroupDraft as b, aiDraftsRequest as c, interactiveVideoFromDrafts as d, AI_CRITIQUE_MAX_FIELD_LENGTH as f, createDraft as g, checkAiCritique as h, checkAiWritingFeedback as i, aiAllowedByContent as j, AI_HINT_TYPES as k, checkAiDrafts as l, aiCritiqueRequest as m, AI_WRITING_MAX_FIELD_LENGTH as n, AI_DRAFTS_MAX_REPAIRS as o, AI_CRITIQUE_MAX_FINDINGS as p, aiWritingFeedbackRequest as r, aiDraftsRepairRequest as s, AI_WRITING_MAX_CORRECTIONS as t, generateDrafts as u, validateDraft as v, AI_COACHING_MAX_TIP_LENGTH as w, critiqueDrafts as x, createInteractiveVideoDraft as y, hintRevealsAnswer as z };
|
|
2495
2600
|
|
|
2496
|
-
//# sourceMappingURL=ai-writing-
|
|
2601
|
+
//# sourceMappingURL=ai-writing-CPpH9Qxq.js.map
|